File Coverage

blib/lib/Getopt/Janus/Tk.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1            
2             require 5;
3             package Getopt::Janus::Tk;
4             # Get the program options via a GUI
5            
6            
7             @ISA = ('Getopt::Janus::SessionBase');
8             $VERSION = '1.03';
9 2     2   1745 use strict;
  2         4  
  2         76  
10 2     2   725 use Getopt::Janus::SessionBase ();
  2         5  
  2         40  
11            
12 2     2   13 use Getopt::Janus (); # makes sure Getopt::Janus::DEBUG is defined
  2         6  
  2         54  
13 2     2   78 BEGIN { *DEBUG = \&Getopt::Janus::DEBUG }
14            
15             DEBUG and print "Revving up ", __PACKAGE__, " at debug=", DEBUG, "\n";
16            
17 2     2   1244 use Tk ();
  0            
  0            
18             use Carp ('confess');
19             require Tk::Button;
20             require Tk::Frame;
21             require Tk::Pane;
22             require Tk::Entry;
23            
24             sub to_run_in_eval {1}
25            
26             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
27            
28             sub get_option_values {
29             my $self = shift;
30            
31             my $m;
32             my $run_flag;
33             my $run_flag_set = sub { $run_flag = 1; $m->destroy; return; };
34             $m = $self->set_up_window($m, $run_flag_set);
35            
36             DEBUG and print "\n";
37             Tk::MainLoop();
38             DEBUG and print "\n";
39             DEBUG and print '', !$run_flag_set ? "Aborting.\n" : "Now running.\n";
40            
41             undef $m;
42             exit unless $run_flag;
43             # otherwise fall thru
44             return;
45             }
46            
47             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
48            
49             sub set_up_window {
50             my($self, $widget, $run_flag_set) = @_;
51            
52             my $m = MainWindow->new();
53             $m->title( $self->{'title'} ) if $self->{'title'};
54             $m->bind('' => [$m, 'destroy'] );
55             $m->geometry('+0+0');
56            
57             $self->{'width'} = 0;
58             $self->{'height'} = 0;
59            
60             my $pane = $m->Scrolled( 'Pane',
61             '-scrollbars' => 'osoe',
62             '-sticky' => 'nsew',
63             '-gridded' => 'y'
64             );
65             $pane->pack( '-fill' => 'both', '-expand' => 1 );
66            
67             $self->make_bundles($pane, $m);
68             $self->button_bar($pane, $m,
69             1, # was: !@{$self->{'options'} || []}, # whether to focus the OK button
70             $run_flag_set,
71             );
72             $self->place_window($pane, $m);
73             $m->focus;
74             return $m;
75             }
76            
77             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
78            
79             sub make_bundles {
80             # Iterate over the options, and for each one, make a BUNDLE of GUI things
81             # (a label, some more stuff, a help button, whatever).
82            
83             my($self, $pane, $mainwindow) = @_;
84             my $them = $self->{'options'} || [];
85            
86             foreach my $option (@$them) {
87             my $method = 'make_bundle_'
88             . ( $option->{'type'} || confess "Typeless option?!" );
89             $self->$method($option, $pane, $mainwindow);
90            
91             # And now a little divider
92             my $f = $pane->Frame(qw/ -relief ridge -bd 1 -height 3 /);
93             $f->grid( qw< -columnspan 3 -sticky ew > );
94             $self->consider_grid_row($f);
95             }
96            
97             return;
98             }
99            
100             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
101            
102             sub button_bar {
103             my($self, $pane, $mainwindow, $whether_focus_okay, $run_flag_set) = @_;
104            
105             # A frame within which we can use pack instead of grid:
106             my $button_bundle_frame = $pane->Frame();
107            
108             $button_bundle_frame->grid( qw< -columnspan 3 -sticky s > );
109            
110             my @button_pack_options = qw< -side left -pady 9 -padx 9 >;
111            
112             my $okay;
113            
114             ($okay = $button_bundle_frame->Button(
115             '-text' => 'OK',
116             '-command' => $run_flag_set,
117             ))->pack( @button_pack_options, );
118            
119             $button_bundle_frame->Button(
120             '-text' => 'Cancel',
121             '-command' => [ $mainwindow => 'destroy' ],
122             )->pack( @button_pack_options );
123            
124             my $main_help_box = $self->main_help_maker( $mainwindow );
125             $mainwindow->bind('' => $main_help_box );
126            
127             $button_bundle_frame->Button(
128             '-text' => 'Help',
129             '-command' => $main_help_box,
130             )->pack( @button_pack_options );
131            
132             $self->consider_grid_row( $button_bundle_frame );
133            
134             $okay->focus if $whether_focus_okay;
135            
136             return;
137             }
138            
139             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
140             sub main_help_maker {
141             my($self, $mainwindow) = @_;
142             return sub {
143             my $dialogbox;
144             require Tk::DialogBox;
145             require Tk::Text;
146            
147             {
148             $dialogbox = $mainwindow->DialogBox(
149             '-title' => "Help for $$self{'title'}",
150             '-buttons' => [
151             'OK',
152             $self->{'license'} ? ('See License') : ()
153             ],
154             );
155             my $t = $dialogbox->add('Scrolled' => 'Text' =>
156             -scrollbars => 'oe',
157             -height => 22,
158             -width => 80,
159             -font => 'roman', # no real need for monospace
160             );
161             $t->pack;
162             $t->insert('@0,0', $self->_text_for_program() );
163             $t->configure(qw< -state disabled -takefocus 1 >);
164             # make it non-editable, but selectable
165             }
166            
167            
168             return unless 'See License' eq ($dialogbox->Show || '');
169            
170            
171             {
172             # They chose to see the license. A near-repeat of the
173             # previous block.
174             $dialogbox = $mainwindow->DialogBox(
175             '-title' => "License for $$self{'title'}",
176             '-buttons' => ['OK'],
177             );
178             my $t = $dialogbox->add('Scrolled' => 'Text' =>
179             -scrollbars => 'oe',
180             -height => 22,
181             -width => 80,
182             -font => 'roman', # no real need for monospace
183             );
184             $t->pack;
185             $t->insert('@0,0',
186             $self->{'license'}->()
187             );
188             $t->configure(qw< -state disabled -takefocus 1 >);
189             # Make it non-editable, but selectable
190            
191             $dialogbox->Show; # Don't need the value, tho.
192             }
193             return;
194             };
195             }
196            
197             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
198            
199             sub _text_for_program {
200             my $self = $_[0];
201             return join '',
202             $self->long_help_message(),
203            
204             "\n",
205             "Built with Perl and Getopt::Janus.\n",
206             "(You are running Perl v$] for $^O",
207            
208             (defined(&Win32::BuildNumber) and defined &Win32::BuildNumber())
209             ? (" Win32::BuildNumber \#", &Win32::BuildNumber())
210             : defined($MacPerl::Version)
211             ? " MacPerl v$MacPerl::Version\n"
212             : (),
213            
214             (chr(65) eq 'A') ? () : " non-ASCII",
215             q{).},
216             ;
217             }
218            
219             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
220            
221             sub consider_grid_row {
222             my $self = shift;
223             my $width = 0;
224             my $max_height = 0;
225            
226             DEBUG > 1 and print "Considering grid-row widgets @_\n";
227            
228             foreach my $widget (@_) {
229             DEBUG > 1 and printf " Widget %s is %sw x %sh (%sw x %sh)\n",
230             $widget, $widget->reqwidth, $widget->reqheight,
231             $widget->width || '~', $widget->height || '~';
232             $width += $widget->reqwidth;
233             my $this_height = $widget->reqheight;
234             $max_height = $this_height if $max_height < $this_height;
235             }
236            
237             $self->{'height'} += $max_height;
238             $self->{'width' } = $width if $width > $self->{'width'};
239            
240             DEBUG and printf "Global %sw x %sh\n", $self->{'width'}, $self->{'height'};
241            
242             return;
243             }
244            
245             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
246            
247             sub place_window {
248             my($self, $pane, $m) = @_;
249            
250             # This routine's geometry guessing is potentially inaccurate, but it
251             # works fine most of the time, and doesn't generate any really
252             # spectacular failures even when it doesn't get things quite right.
253            
254             my $height = int( ($self->{'height'} || return) + 60 );
255             my $width = int( ($self->{'width' } || return) + 150 );
256             # Those 60 and 160 are the fudge factor for scrollbars, for
257             # the fact that frames think they're all 1x1, and so on.
258             # (We could ask the frames to update, but this seems to make
259             # things even worse elsewhere!)
260            
261             DEBUG and printf "Pane: %sw x %sh\n", $width, $height;
262            
263             my $max_w = $pane->screenwidth - 60;
264             my $max_h = $pane->screenheight - 60;
265             $width = $max_w if $width > $max_w;
266             $height = $max_h if $height > $max_h;
267            
268             $pane->configure( '-width' => $width , '-height' => $height );
269             return;
270             }
271            
272             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
273            
274             sub common_make_bundle { # operates on an option
275             my($self, $option, $pane, $mainwindow, $new) = @_;
276            
277             DEBUG > 4 and print "self $self, option $option, mw $mainwindow, new $new\n";
278            
279             if( defined( $option->{'short'} ) and $option->{'action'} ) {
280             my $event_spec = '{'short'} . '>';
281             DEBUG > 1 and print "Binding $event_spec to new object $new\'s",
282             " event $$option{'action'}\n";
283             $mainwindow->bind( $event_spec => $option->{'action'} );
284             $option->{'shortcut_key'} = $event_spec;
285             }
286            
287             my @widgets = (
288             $pane-> Label(
289             '-text' => $self->_option_title($option) . ": ",
290             -takefocus => 0,
291             ),
292             $new,
293             $pane->Button(
294             '-text' => '?',
295             '-pady' => 0,
296             '-command' => $self->option_help_maker($option, $pane, $mainwindow),
297             ),
298             );
299            
300             DEBUG > 2 and print "Gridding up widgets @widgets\n";
301            
302             @widgets and $widgets[0]->grid( @widgets[1 .. $#widgets],
303             -padx => 2,
304             # -padx => 5, -pady => 5,
305             );
306            
307             $self->consider_grid_row(@widgets);
308            
309             return;
310             }
311            
312             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313            
314             sub make_bundle_string { # operates on an option
315             my($self, $option, $pane, $mainwindow) = @_;
316             my $widget = $mainwindow->Entry( '-width' => 15, '-textvariable'
317             => $option->{'slot'} || confess "No slot in @{[%$option]})!?"
318             );
319             $option->{'action'} = sub {
320             $widget->focus;
321             $widget->selectionRange('0', 'end');
322             $widget->xviewMoveto(1);
323             $widget->icursor('end');
324             return;
325             };
326            
327             DEBUG and print "Calling _common_make_bundle\n";
328             return $self->common_make_bundle( $option, $pane, $mainwindow, $widget );
329             }
330            
331             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
332            
333             sub make_bundle_yes_no { # operates on an option
334             my($self, $option, $pane, $mainwindow) = @_;
335            
336             require Tk::Checkbutton;
337             my $widget = $mainwindow->Checkbutton(
338             #-text => 'Hi there',
339             -variable => ($option->{'slot'} || confess "No slot in @{[%$option]})!?"),
340             -relief => 'flat'
341             );
342            
343             #$option->{'action'} = sub { $widget->focus; $widget->invoke; return };
344             $option->{'action'} = sub {
345             $widget->focus;
346             $widget->invoke;
347             return;
348             };
349             DEBUG and print "Calling _common_make_bundle\n";
350             return $self->common_make_bundle( $option, $pane, $mainwindow, $widget );
351             }
352            
353             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
354            
355             sub make_bundle_choose { # operates on an option
356             my($self, $option, $pane, $mainwindow) = @_;
357             require Tk::BrowseEntry;
358             my $widget = $mainwindow->BrowseEntry(
359             #-text => 'Hi there',
360             #-relief => 'flat',
361             -variable => ($option->{'slot'} || confess "No slot in @{[%$option]})!?"),
362             -state => 'readonly',
363             -choices => $option->{'from'},
364             );
365            
366             if( $widget->can('space') ) {
367             $option->{'action'} = [ $widget => 'space' ];
368             } else {
369             DEBUG and print "BrowseEntry widget $widget can't do 'space'.\n";
370             }
371            
372             DEBUG and print "Calling _common_make_bundle\n";
373             return $self->common_make_bundle( $option, $pane, $mainwindow, $widget );
374             }
375            
376             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
377            
378             sub make_bundle_new_file { # operates on an option
379             my($self, $option, $pane, $mainwindow) = @_;
380             my $slot = $option->{'slot'} || confess "No slot in @{[%$option]})!?";
381             my $frame = $mainwindow->Frame;
382             my $entry = $frame->Entry( '-width' => 15, '-textvariable' => $slot );
383            
384             my @box_arguments = (
385             '-title' => "Select for output: " . $self->_option_title($option),
386             );
387             if(defined $$slot and $$slot =~ m/\.([\+A-Za-z0-9]{1,6})$/s ) {
388             push @box_arguments, '-filetypes' => [
389             ["$1 Files" => ".$1"],
390             ['All Files' => '*' ],
391             ];
392             }
393            
394             my $button = $frame->Button(
395             '-text' => '>...', # "To..."
396             '-command' => sub {
397             my $new = $mainwindow->getSaveFile(@box_arguments);
398             $$slot = $new if defined $new;
399             $entry->xviewMoveto(1) if defined $$slot; # make the end visible
400             1;
401             },
402             );
403            
404             $option->{'action'} = [$button => 'focus'];
405            
406             $button->grid($entry); # laying them both out in this frame
407             return $self->common_make_bundle( $option, $pane, $mainwindow, $frame );
408             }
409            
410             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
411            
412             sub make_bundle_file { # operates on an option
413             my($self, $option, $pane, $mainwindow) = @_;
414             my $slot = $option->{'slot'} || confess "No slot in @{[%$option]})!?";
415             my $frame = $mainwindow->Frame;
416             my $entry = $frame->Entry( '-width' => 15, '-textvariable' => $slot );
417            
418             my @box_arguments = (
419             '-title' => "Select input: " . $self->_option_title($option),
420             );
421             if(defined $$slot and $$slot =~ m/\.([\+A-Za-z0-9]{1,6})$/s ) {
422             push @box_arguments, '-filetypes' => [
423             ["$1 Files" => ".$1"],
424             ['All Files' => '*' ],
425             ];
426             }
427            
428             my $button = $frame->Button(
429             '-text' => '<...', # "From..."
430             '-command' => sub {
431             my $new = $mainwindow->getOpenFile(@box_arguments);
432             $$slot = $new if defined $new;
433             $entry->xviewMoveto(1) if defined $$slot; # make the end visible
434             1;
435             },
436             );
437             $option->{'action'} = [$button => 'focus'];
438            
439             $button->grid($entry); # laying them both out in this frame
440             return $self->common_make_bundle( $option, $pane, $mainwindow, $frame );
441             }
442            
443             #==========================================================================
444            
445             sub option_help_maker { # operates on an option
446             my($self, $option, $pane, $mainwindow) = @_;
447             my $program_title = $self->{'title'};
448            
449             return sub {
450             require Tk::DialogBox;
451             require Tk::Text;
452            
453             my $dialogbox = $mainwindow->DialogBox(
454             '-title' => "Help for: $$self{'title'}: " .
455             $self->_option_title($option),
456             '-buttons' => ['OK'],
457             );
458             my $t = $dialogbox->add('Scrolled' => 'Text' =>
459             -scrollbars => 'oe',
460             -height => 10,
461             -width => 60,
462             -font => 'roman', # no real need for monospace
463             );
464             $t->pack;
465             $t->insert('@0,0', $self->_text_describing_option($option) );
466             $t->configure(qw<-state disabled -takefocus 1>);
467             # make it non-editable, but selectable
468            
469             $dialogbox->Show;
470             return;
471             };
472             }
473            
474             sub _text_describing_option { # operates on an option
475             my($self, $option) = @_;
476             return join '',
477            
478             "Option name: \"", $self->_option_title($option),
479             "\"\n\n",
480            
481             defined( $option->{'description'} )
482             ? "$$option{'description'}\n\n" : (),
483            
484             "Type: $$option{'type'}\n",
485            
486             defined( $option->{'short'} )
487             ? "Short command-line form: -$$option{'short'}\n" : (),
488            
489             defined( $option->{'long'} )
490             ? "Long command-line form: --$$option{'long'}\n" : (),
491            
492             defined( $option->{'shortcut_key'} )
493             ? "Shortcut key: $$option{'shortcut_key'}\n" : (),
494             ;
495             }
496            
497             sub _option_title { # operates on an option
498             my($self, $option) = @_;
499             return
500             defined( $option->{'title'} ) ? $option->{'title'}
501             : defined( $option->{'long'} ) ? "--$$option{'long'}"
502             : defined( $option->{'short'} ) ? "-$$option{'short'}"
503             : "[???]" # should be unreachable
504             }
505            
506             #==========================================================================
507            
508             sub review_result_screen {
509             my($self, $items) = @_;
510             return unless @$items;
511             DEBUG > 2 and print "Making a new window for ", scalar(@$items), " items\n";
512             require Tk::Checkbutton;
513            
514             my $mainwindow = MainWindow->new;
515             $mainwindow->title("Reviewing Output of $$self{'title'}");
516             my $pane;
517             if(@$items < 4) {
518             $pane = $mainwindow;
519             } else {
520             $pane = $mainwindow->Scrolled( 'Pane',
521             '-scrollbars' => 'osoe',
522             '-sticky' => 'nsew',
523             #'-gridded' => 'y'
524             );
525             $pane->pack( '-fill' => 'both', '-expand' => 1 );
526             }
527            
528             foreach my $i (@$items) {
529             my($f,$d) = @$i;
530             next unless defined $f or defined $d;
531            
532             require Tk::Menubutton;
533             my $mb = $pane->Menubutton(
534             qw/ -relief raised -takefocus 1 -indicatoron 1 -direction right/,
535             -text => $f,
536             );
537            
538             $mb->configure( -menu => $mb->menu(qw/-tearoff 0/) );
539            
540             defined $f and $self->can_open_files and $mb->command(
541             -label => "Run this file",
542             -command => sub { $self->open_file($f) },
543             );
544            
545             defined $f and $self->can_open_directories and $mb->command(
546             -label => "Open this directory",
547             -command => sub { $self->open_directory($d) },
548             );
549             defined $f and $self->can_open_files and $mb->command(
550             -label => "Copy this filespec", -command => sub {
551             $mainwindow->clipboardClear;
552             $mainwindow->clipboardAppend( '--', $f );
553             },
554             );
555             $mb->pack;
556             }
557            
558             # Just a divider:
559             $pane->Frame(qw/ -relief ridge -bd 1 -height 3 /)->pack('-fill' => 'x' );
560            
561             # A frame for the button(s) at the bottom:
562             my $button_bundle_frame = $pane->Frame();
563             $button_bundle_frame->pack;
564            
565             my $done_button;
566             ($done_button = $button_bundle_frame->Button(
567             '-text' => 'Done',
568             '-command' => [ $mainwindow => 'destroy' ],
569             ))->pack( qw< -side left -pady 9 -padx 9 > );
570             $done_button->focus;
571            
572             $mainwindow->bind('' => [$mainwindow, 'destroy'] );
573            
574             DEBUG and print "\n";
575             Tk::MainLoop();
576             DEBUG and print "\n";
577             return;
578             }
579            
580             #==========================================================================
581            
582             sub report_run_error {
583             my($self, $error_text) = @_;
584             $error_text ||= "Unknown error!?";
585            
586             DEBUG and print "Reporting error $@\n";
587            
588             my $m = MainWindow->new;
589             $m->title("Error!");
590             $m->label("An error occurred in the program:\n");
591            
592             my $t = $m->Scrolled( 'Text',
593             -scrollbars => 'oe',
594             -height => 10,
595             -width => 60,
596             );
597             $t->pack;
598             $t->insert('@0,0', $error_text);
599             $t->configure(qw< -state disabled -takefocus 1 >);
600             # make it non-editable, but selectable
601            
602             my $button = $m->Button(
603             '-text' => 'Abort the Program',
604             '-command' => [$m, 'destroy'],
605             );
606             $m->bind('' => [$m, 'destroy'] );
607            
608             $button->pack;
609             $button->focus;
610            
611             $m->geometry('+20+20');
612            
613             DEBUG and print "\n";
614             Tk::MainLoop();
615             DEBUG and print "\n";
616            
617             return;
618             }
619            
620             #==========================================================================
621            
622             __END__