=head1 NAME Using the Tk::EasyGUI Module for Perl =head1 VERSION 0.13 =head1 SYNOPSIS perl gus_example_easy_gui.pl # On UNIX/Linux/Mac OS X and most others C:\Perl\bin\perl.exe gus_example_easy_gui.pl # On Win32 Download this unitary script to anywhere. Open a command line window and C your way to wherever the script is. Then type whichever of the above two lines applys to your own OS. That is the best way to find out what this script does, especially if you are new to Perl. =head1 DESCRIPTION What you have here is a method of banging out quick and dirty Perl/Tk GUIs for use with almost any Perl code that you might have in early development, or for which you may have only temporary need. It does two things for you. Firstly, you can use it to build a simple GUI most quickly. Secondly, the GUI you build will have built-in input-error checking. =head1 PERL NEWBIE INFO This file C had formerly been distributed as singular, with the GUS::EasyGUI package embeded therein. As of version 0.11 that package is formally split out as module Tk::EasyGUI.pm. Thus it is now a separate file and no longer part of this script. This script comprises the upper have of its former self, detached from the former package now become a module. That former package C which is now module C is not yet part of CPAN. You will not find it yet in any archive. You can only get it from me until such time as I get around to uploading it to CPAN. =head1 DEPENDENCIES C Until I will have gotten around to uploading it to CPAN, and assuming that the CPAN folks give their thumbs up to the name C (by no means certain) you may obtain the module from me. F, changing the C<*.txt> extension to C<*.pm> such that it will be found in a path like... C ...on WinXP or a similarly Perl-ish directory path on UNIX and other OSes. =head1 TO DO Add more widget types. =head1 BUGS AND LIMITATIONS Only creates columns of label-entry and rows of button widgets at present. =head1 AUTHOR Gan Uesli Starling > =head1 LICENSE AND COPYRIGHT Copyright (c) 2007 by Gan Uesli Starling. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut =head1 EXAMPLE USAGE Consider all of the POD which follows a tutorial on usage. For me, the author, it is also my test and experimentation suite. All code included herein are intended firstly for my own developmental testing purposess. From your perspective let these serve as usage examples. For your benefit I will document them in nauseating detail. If you are seeing this as POD only, via HTML perhaps, then the code itself will not appear below most of the descriptions. To see the code you will have to look into the file itself. Since this POD is written directly into said file that should pose you no barrier. And doing so will save me the trouble of having to edit everything twice: once in the code where it is actually used and yet again just a handful of lines above in the POD would otherwise display it. So to proceed, if you are viewing this text on-line as HTML extracted from POD, give that up. Download the source C if you don't have it already. Note that if you get on-online, it might have a C<*.txt> extension instead of C<*.pl> (so that the web server won't be inclined to run rather than server it). Then once you've got it, open it into either a plain Jane text editor, or a Perl IDE such as OptiPerl for Win32 or whichever is your favorite. So, to begin... =head2 The Usual Perlish Top-of-File Business Here we have the C statements. In addition to the three below for C, C, and C is where you would also include a C statement for my EasyGUI Perl module. As my module exports, at present, three methods, you will need to quote those as I have done. Also you find the supposedly I for representing any Perl script's version info. =cut use strict; use warnings; use Tk; use Tk::EasyGUI qw( column_of_entries row_of_buttons entries_accept ); my ($VERSION) = '$Revision: 0.13 $' =~ m{ \$Revision: \s+ (\S+) }xm; =head2 Create a Perl/Tk MainWindow Let EasyGUI spit back the Perl/Tk main window for you via its C routine. EasyGUI.pm needs to create a main window the very first thing and gives it back to you when finished. Note how I have called EasyGUI.pm in the following manner... C ...where I is how you want your window titled and I is a reference to some routine of your devising for dealing with any fed back error messages. If you neglect to supply the latter, then a built-in default will take over. In that case they will merely be printed. You might know where to look for them but your GUI users likely will not. =cut my $mw = Tk::EasyGUI::init("Easy GUI - $VERSION", \&append_log); =head2 User-Provided Entry Widget Text Content My module allows you to deal with the text inside of Tk entry widgets either by one of two ways. The easiest is to supply EasyGUI.pm with a reference to some variable of your own. My own test examples follow immediately below. You will note as how they all contain strings. Not just the file path but also the real number is a string. This is important only if you intend to supply start-up defaults for your own, spin-off versions of this script. The reason is that EasyGUI.pm employs regular expressions and string functions to impose the script author's (you) limitations upon those real numbers: max and min for integers and reals; those two plus resolution for real numbers only. So if you supply any default scalars do be sure to provide them as strings as I have done in this example. =cut my $path = '/ram/bar.csv'; # Example path. my $real = '5.0'; # Example real number. my $capitals = 'CAPITAL LETTERS'; # Example string. =pod Likewise for lists, be they of integers or reals. Supply the whole list in the format of a string. You my delimit the elements of a list within said string by commas, colons or semicolons. Whichever you choose, just be sure it does not serve double-duty within the string. In other words, if you choose commas, represent a million like so '1000000' or like so '1_000_000' and not like so '1,000,000' for reasons which are obvious. =cut my $int_list = '0, 1, 2, 3, 4'; my $real_list = '0.9, 1.8, 2.7, 3.6, 4.5'; =head2 Designating a Column of Label/Entry Rows. Here I designate a column of widgets, each row of which will have a label, an entry and in he case of paths, also a browse button. I designate them via a list which begins with an optional anonymous hash followed, if present, by any number of anonymous arrays. To accept my defaults, either supply an empty hash, or none at all. That hash is where you put optional attributes, if any, for the whole column. In my example I merely call out the built-in defaults for the sake of laziness, so that I won't have to tell you what those are later. This being the case, an empty hash or no hash at all would accomplish exactly the same result. Next follow the arrays, the first element of which is a string containing the label you want for that row. This label must be unique to the column. I use that label for a hash key inside EasyGUI.pm. So make sure that it is unique, at least to this column of label/entry widgets. Feel free to duplicate that label in any other column as EasyGUI.pm builds separate hashes internally to represent each. The second element of each anonymous array must also be a string. These are flags to tell EasyGUI.pm what kind of data you intend the entry widget for that row is to hold. They indicate contents as follows... 'po' signifies the entry widget shall hold a open-file-path string 'ps' signifies the entry widget shall hold a save-file-path string 's' signifies an ordinary Perl string 'i' signifies an integer 'r' signifies a real number 'i,i' signifies an integer list 'r,r' signifies a list of real numbers The third element of each anonymous array is either a string containing the default value or else a scalar reference pointing somewhere. The somewhere it points to is one that you have provided. That somewhere must either contain a default-value string or else be empty. Do not be intimidated by my having imposed that you submit lists as a string. Know that you can always revert a string to a list by calling C upon it as I have exampled elsewhere herein. The above-described second and third anonymous array elements are all that are absolutely required to specify a label/entry row in the column. By supplying only these however you will not be able to impose much in the way of limitations upon user-entered data to those entry widgets. =head3 Imposing Constraints Upon User Data Entry For strings you may supply a regular expression composed as a string. This string will be used to match any input which the user shall later type into the entry widget. Internally, EasyGUI.pm uses C upon that RegEx to accomplish its ends. For integers you may supply two additional elements to the anonymous array, a min and a max, in that order. Supply these as simple integers. For reals you may supply not only a min and a max, but also floating point resolution. For lists of either integers or reals you may supply the same constraints as for their singular variants. =head3 All-or-None and Auto-Rollback Constraints By default, if user enters an invalid entry, only that invalid entry will be prohibited from passing through to the output. Its widget will turn red in such cases. Alternately you can flag that all widgets must pass or none shall pass through. If you flag for that then you can also flag whether entries already in widgets shall be rolled back upon the failure of any one of them. Both those flags are controlled by attributes. By default, both are turned off. =cut my @tk_ent = ( { show_feedback => 1, label_width => 10, entry_width => 40, frame_relief => 'sunken', frame_pack_side => 'top', all_or_none => 0, # Do not require all entries must pass. auto_rollback => 0, # Do not roll back if an entry fails. }, # Your attribs. Empty hash or none is okay. ['Log File', 'ps', '/ram/foo.log'], # Data is a string. ['Data File', 'po', \$path], # Data via sref. ['Capitals', 's', \$capitals, '^[A-Z| ]+$'], # Data via sref. ['Integer', 'i', 5, 0, 10], # Data is an integer. ['Int List', 'i,i', \$int_list, -2, 9], # Data via sref. ['Real', 'r', \$real, -10.0, 10.0, 3], # Data via sref. ['Real List', 'r,r', "$real_list", -3.3, 11.9, 2], # Data via sref. ); # shift @tk_ent; # Uncomment to test without hash. =head2 Calling Out the Column of Entries Designation Having designated our desired column of label/entry widgets via a named array, I now make a call to EasyGUI to parse those instructions and follow them, as below. The return value is a hash which internally contains all the widgets which are created by making the call. You will need to store this return value because it will eventually contain your post-verification output data. For arguments the call expects a firstly a Tk window, the same one as was returned when you had previously called C from this package. Secondly it expects a text string with which to name your column of widgets. Don't let this be empty. Also let it be unique among any other columns of widgets should you have more than one. The reason for this is that I use said string as a hash key within EasyGUI.pm. =cut my $wgt_1 = column_of_entries( $mw, 'Options', \@tk_ent ); =head2 Designating a Row of Button Widgets Here I designate a row of button widgets, all sharing a common label. This is done pretty much like I did for columns of widgets above. I provide a named list contain an anonymous hash followed by any number of anonymous arrays. The hash, as previously, may be empty or not there at all. Such defaults as are available for you to alter I show by way of including their defaults in my example anonymous hash. These being the defaults, an empty or missing hash would accomplish the same result. Each button is designated by an anonymous array, the first element of which is a string containing the name for said button. Second must follow a code reference to whatever it is you want to happen when that button is clicked. Those two elements only are required. Optionally you may next provide color designations for the neutral and active button states. The above suffices for ordinary buttons, the kind which only do one thing. For toggle buttons, the kind which alternate between two actions, you may further supply a secondary text label and a secondary code reference. In my example below I demonstrate all possible combinations of anonymous array button designations. =cut # Designate a labeled row of button widgets. my @tk_btn = ( { label_width => 10, frame_relief => 'flat', frame_pack_side => 'top' }, # Optional attribs. Empty hash okay. [ 'Start', sub { print "Start\n" }, # Un-toggled label and toggle-on action. 'gray', 'green', # Un-toggled and toggled colors. 'Stop', sub { print "Stop\n" } # Toggled label and toggle-off action. ], [ 'Run', sub { print "Run\n" }, # Un-toggled label and toggle-on action. 'Pause', sub { print "Pause\n" } # Toggled label and toggle-off action. ], [ 'Submit', sub { submit_entries($wgt_1) }, # Label and action. 'gray', 'yellow' # In-active and active colors. ], [ 'Exit', sub { exit MainLoop }, # Label and action. Default colors. ], ); # shift @tk_btn; # Uncomment to test without optional hash. =head2 Calling Out the Row of Buttons Designation Having designated our desired row of button widgets via a named array, I now make a call to EasyGUI to parse those instructions and follow them, as below. The return value is a hash which internally contains all the widgets which are created by making the call. All the same considerations apply as for the return value of a column of entrys call described previously. For arguments the call expects a firstly a Tk window, the same one as was returned when you had previously called C from this package. Secondly it expects a text string with which to name your row of buttons. Don't let this be empty for the same reasons as stated previously. =cut my $wgt_2 = row_of_buttons( $mw, 'Actions', \@tk_btn ); =head2 Entry Widget Data Verification and Throughput Consider the column of entry widgets as a not-yet-verified input que. A primary feature of EasyGUI.pm is error checking prior to throughput. Having throughput implies there being two stages: input and output. Only verified data will find its way from input through to output. Not only that, no throughput occurs at all unless all data in a given column of entries pass their individual tests. But you can all but ignore this process as it is wholly automatic. All you really need to know are one or two things. The one thing you absolutely must know is how to trigger a verification/throughput sequence. You do that by calling this operation... C ...which internally performs all the test and handles the throughput. The argument which you supply is the same one which was returned when you initially designated your column of label/entry widgets. If all tests pass then data will pass from the input side (Tk entry widget) to the output side. If when you designated the column of label/entry widgets you did so by providing a scalar reference rather than a plain string, then that is where your output will go. If however, you instead provided a plain ASCII string for your entry widget startup defaults, then you will need to fetch your output from out of a hash internal to that same widget supplied as an arg to the C routine. =head3 Data Input/Output Locations Text typed into each entry widget and visible to the user therein is stored like so... C<$wgt_1->{'fm'}->{'Foo Bar'}> ...where it remains unchanged until you choose to verify the entire group of sibling entry widgets resident in a given column. Upon triggering of verification, and supposing that they all pass their individual tests, then data are copied over to an output location like so... C<$wgt_1->{'to'}->{'Foo Bar'}> ...where in both examples above the text I is identical to that originally supplied for each separate label of the label/entry pair. So had you any need of post-start-up writing into a given entry widget, then you would do so that hash which is pointed to by C<{'fm'}> and not to the one pointed to by C<{'to'}>, like so. C<$wgt_1->{'fm'}->{'Foo Bar'} = 'Some text.'; > =head2 Getting Back Verified List Data Some additional complexity is required for getting back list data because my package requires it to be throughput as strings. You do this by making a call to C, like so... C{'to'}->{'Foo Bar'}] );> ...or even more simply via a call to C like so... C{'to'}->{'Foo Bar'};> ...although thereby your array would fill with text strings representing those values. In Perl, however this seldom matters since the interpreter will deal with them in much the same manner. =head2 Triggering a Verification/Throughput Event You can make your call to C however you want. You could, for instance, put the call into a Tk C loop. I have done that a time or two. But let me suggest that the best way is to employ a button widget instead. Below is my own little example of how to do this... =cut # Test entries and accept if valid. # All tested output are in path $wgt_1->{'to'}->{'Label Name'} which will # contain a reference if given one at time of designation. In that case just # use the original reference in its place. sub submit_entries { my $wgt = shift; entries_accept($wgt); print qq{Log File: $wgt_1->{'to'}->{'Log File'} \n}; # Not a ref. print qq{Data File: $path \n}; # As a ref. print qq{Data File Ref: $wgt_1->{'to'}->{'Data File'} \n}; # See the ref. print qq{Capitals: $capitals \n}; # As a ref. print qq{Capitals Ref: $wgt_1->{'to'}->{'Capitals'} \n}; # See the ref. print qq{Integer: $wgt_1->{'to'}->{'Integer'} \n}; # Not a ref. print qq{Int List: $int_list \n}; # As a ref. print qq{Int List Ref: $wgt_1->{'to'}->{'Int List'} \n}; # See the ref. print qq{Real: $real \n}; # As a ref. print qq{Real Ref: $wgt_1->{'to'}->{'Real'} \n}; # See the ref print qq{Real List: $wgt_1->{'to'}->{'Real List'} \n}; # As a string. # Either call split() to re-compose a list from the string. my @real_list; @real_list = split /, /, $wgt_1->{'to'}->{'Real List'}; print 'Via split(): ', join ', ', @real_list, "\n"; # Via split() @real_list = (); # Empty the list for reuse in next test. # Or else call eval() to re-compose a list from the string. @real_list = ( eval qq[ $wgt_1->{'to'}->{'Real List'}] ); print 'Via eval(): ', join ', ', @real_list, "\n"; # Via eval() print "\n"; } MainLoop; =head1 Auxiliary Subroutines Here I present two auxiliary subroutines which are helpful with most every GUI that I have built. One assembles a text string of the current time in the most convenient format of 'YYYY-MM-DD hh:mm:ss' so that you can sort times in chronological order most easily. The second is for writing events to a log file. It likewise posts error messages into the column-of-entries feedback widget. =cut ################ # END GUI STUFF ################ sub current_DTG { my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time); my $dtg = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); return $dtg; } sub append_log { my $txt = shift; my $mesg = current_DTG() . " $txt \n"; my $LOG; my $path_log = ref $wgt_1->{'to'}->{'Log File'} ? ${$wgt_1->{'to'}->{'Log File'}} : $wgt_1->{'to'}->{'Log File'}; if (open $LOG, qq|>>$path_log| ) { print $LOG $mesg; close $LOG; $wgt_1->{'fm'}->{'Feedback'} = qq{Okay! Entries appended to '$path_log' }; } else { $wgt_1->{'fm'}->{'Feedback'} = qq{Oops! Cannot append to log '$path_log': $! }; } } __END__