File Coverage

blib/lib/Tk/QuickTk.pm
Criterion Covered Total %
statement 27 29 93.1
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 36 38 94.7


line stmt bran cond sub pod time code
1             # file: Tk/QuickTk.pm
2             #
3             # Copyright (c) 2000, 2011 John Kirk. All rights reserved.
4             # This program is free software; you can redistribute it and/or
5             # modify it under the same terms as Perl itself.
6             #
7             # The latest version of this module should always be available
8             # at any CPAN (http://www.cpan.org) mirror, or from the author:
9             # http://perl.dystanhays.com/jnk
10              
11             package Tk::QuickTk;
12 2     2   45590 use warnings;
  2         6  
  2         70  
13 2     2   9 use strict;
  2         4  
  2         76  
14              
15             BEGIN {
16 2     2   10 use Exporter ();
  2         7  
  2         44  
17 2     2   2568 use AutoLoader ();
  2         5726  
  2         61  
18 2     2   18 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         5  
  2         281  
19 2     2   5 $VERSION = '0.92';
20 2         50 @ISA = qw(Exporter AutoLoader);
21             #Give a hoot don't pollute, do not export more than needed by default
22 2         5 @EXPORT = qw();
23 2         5 @EXPORT_OK = qw(app createwidget);
24 2         51 %EXPORT_TAGS = ();
25             }
26              
27             require 5.002; # version of Perl
28 2     2   13 use Carp;
  2         3  
  2         197  
29 2     2   1967 use FileHandle; # nice to easily have filehandles as plain variables
  2         33106  
  2         13  
30 2     2   3887 use Tk;
  0            
  0            
31              
32             # initial hash to bless as content of QuickTk instance:
33             my %proto=('menutypes'=>{'c'=>'command','k'=>'checkbutton','-'=>'separator',
34             'm'=>'cascade','r'=>'radiobutton', },
35             'widgets'=>{ },);
36              
37             # EXPORT_OK routines:
38             sub app;
39             sub createwidget;
40              
41             # internal use only routines:
42             sub _loadwidget;
43             sub _getargs;
44             sub _docode;
45             sub _bindevent;
46             sub _loadmenitm;
47             sub _getttl;
48             sub _getcmd;
49             sub _getsub;
50             sub _getini;
51              
52             # instance constructor routine:
53             sub new { my ($that,$spec,$lname,$genonly)=@_;
54             my $class=ref($that)||$that;
55             my $me={_prop=>\%proto,%proto,}; # initial hash from prototype and ref. to it
56             bless $me,$class;
57             if(defined $genonly) { $$me{genonly}=$genonly; }
58             if(defined $lname) {
59             $$me{lname}=$lname;
60             $$me{lfh}=new FileHandle ">$lname";
61             }
62             if(defined $spec) { # a spec was passed in; load it
63             my $specref;
64             if(!ref $spec) { # it's a filename (also handle case: '')
65             croak "can't read QuickTk spec file: $spec\n" unless -f $spec && -r _;
66             use Text::TreeFile;
67             my $hier=Text::TreeFile->new($spec);
68             $specref=$$hier{top};
69             } elsif(ref($spec) eq 'Text::TreeFile') {
70             $specref=$$spec{top};
71             } elsif(ref($spec) eq 'ARRAY' and scalar @$spec==2 and
72             !ref $$spec[0] and ref($$spec[1]) eq 'ARRAY') {
73             $specref=$spec;
74             } else {
75             croak "can't make a new Tk::QuickTk from spec: $spec\n";
76             }
77             _loadwidget($me,$specref);
78             if(exists $$me{inicode} and defined $$me{inicode} and $$me{inicode}) {
79             my $err=_docode($me,0,$$me{inicode});
80             if($err) {
81             croak "failed to execute initialization code\n";
82             }
83             }
84             }
85             return $me;
86             }
87              
88             # EXPORT_OK; starts up Tk main loop; looks for script file name on commandline
89             # seems to go into Tk's MainLoop even if no script: Better diagnostics?
90             sub app { my ($gen,$gname)=@_; # args in case code generation to be saved
91             my $name=$ARGV[0]
92             or croak "Tk::QuickTk::app() found no filename on the command line\n";
93             my $iname=$name;
94             my $oname;
95             if(defined $gen and $gen ne 'nogen') {
96             $oname=(defined $gname)?$gname:$name.'.pl';
97             print STDERR "Tk::QuickTk::app() logging generated perl-tk code";
98             print STDERR " to file: $oname\n";
99             }
100             my $app=(defined $oname)?Tk::QuickTk->new($iname,$oname)
101             :Tk::QuickTk->new($iname);
102             MainLoop;croak "fell through MainLoop";
103             }
104              
105             # EXPORT_OK; creates a widget on the fly (when it was defined but not created at startup
106             #
107             sub createwidget {
108             my ($gl,$arg,$specname)=@_;
109             my ($code,$err,$menidx,$ret);
110             my %mt=%{$$gl{menutypes}};
111             my $spec=$$gl{widgets}{$specname};
112             croak "couldn't find \"$specname\" widget to create\n" unless defined $spec;
113             my ($level,$momname,$momidx,$name,$type,$cfg,$pak,$children)=@$spec;
114             for($name,$type,$cfg,$pak) {
115             s/\$(\d+)/$$arg[$1-1]/g;
116             }
117             if(defined $momidx) { # this is a menu item
118             $code="\$\$w{${momname}_${momidx}_$$arg[0]}=\$\${momname}->$mt{$type}";
119             if($type ne '-') {
120             $code.="(-label=>\"$name\")";
121             }
122             $err=_docode($gl,$level,$code);
123             $code='';
124             croak "failed to create menu item $momname/${name}_$$arg[0]($momidx)\n" if ($err);
125             if($cfg) {
126             $code="\$\$w{momname}_${momidx}_$$arg[0]}->configure($cfg)";
127             $err=_docode($gl,$level,$code);
128             $code='';
129             if($err) {
130             croak "failed to configure $momname/$name(${momidx}_$$arg[0])\n";
131             }
132             }
133             if(@$children) {
134             $code="\$\$w{${momname}_${momidx}_$$arg[0]_menu}";
135             $code.="=\$\$w{$momname}->Menu";
136             $err=_docode($gl,$level,$code);
137             $code='';
138             if($err) {
139             croak "failed to create Menu: ${momname}_${momidx}_$$arg[0]_menu\n";
140             }
141             $code="\$\$w{${momname}_${momidx}_$$arg[0]}->configure(";
142             $code.="-menu=>\$\$w{${momname}_${momidx}_$$arg[0]_menu})";
143             $err=_docode($gl,$level,$code);
144             my $errname="${momname}_$momidx/${momname}_${momidx}_$$arg[0]_menu";
145             if($err) {
146             croak "failed to configure menu item $errname\n";
147             }
148             }
149             $menidx=0;
150             for(@$children) {
151             _loadmenitm $gl,$_,$level+1,"${momname}_${momidx}_$$arg[0]_menu",$menidx;
152             ++$menidx;
153             }
154             } else { # this is an ordinary widget
155             $code="\$\$w{${name}_$$arg[0]}=\$\$w{$momname}->$type";
156             $code.="($cfg)" if($cfg);
157             $err=_docode($gl,$level,$code);
158             $code='';
159             if($err) {
160             croak "failed to create ${name}_$$arg[0]: $momname/$type\n";
161             }
162             if($type eq 'Menubutton') {
163             $code="\$\$w{${name}_$$arg[0]_menu}=\$\$w{${name}_$$arg[0]}->Menu";
164             $err=_docode($gl,$level,$code);
165             $code='';
166             if($err) {
167             croak "failed to create Menu ${name}_$$arg[0]_menu for Menubutton ${name}_$$arg[0]\n";
168             }
169             $code="\$\$w{${name}_$$arg[0]}->configure(-menu=>\$\$w{${name}_$$arg[0]_menu})";
170             $err=_docode($gl,$level,$code);
171             $code='';
172             if($err) {
173             croak "failed to configure Menubutton ${name}_$$arg[0]\n";
174             }
175             }
176             $menidx=0;
177             for(@$children) {
178             if($type ne 'Menubutton') {
179             if(substr($$_[0],0,1) ne '<') {
180             _loadwidget $gl,$_,$level+1,"${name}_$$arg[0]";
181             } else {
182             _bindevent $gl,$_,$level+1,"${name}_$$arg[0]";
183             }
184             } else {
185             _loadmenitm $gl,$_,$level+1,"${name}_$$arg[0]_menu",$menidx;
186             ++$menidx;
187             }
188             }
189             return if($pak=~/^nopack/);
190             $ret=($pak=~s/^(pack|place|grid),//)?$1:'pack';
191             $code="\$\$w{${name}_$$arg[0]}->$ret($pak)";
192             $err=_docode($gl,$level,$code);
193             $code='';
194             if($err) {
195             croak "failed to pack ${name}_$$arg[0]\n";
196             }
197             }
198            
199             }
200              
201             sub _loadwidget {
202             my ($gl,$spec,$level,$momname)=@_;
203             my ($cfg,$pak,$idx,$ret,$cre,$code,$err);
204             my ($localname,$type,$tail)=split ' ',$$spec[0],3;
205             $momname='' if not defined $momname;
206             my $name=$momname.$localname;
207             $level=0 if not defined $level;
208             if($level==0) {
209             $type='MainWindow' if $type=~/^(Toplevel|Frame)$/;
210             if($type ne 'MainWindow') {
211             croak "Top level widget must be \"MainWindow\", not \"$type\"\n";
212             }
213             ($cre,$cfg,$pak)=_getargs($gl,0,$tail,{'ttl'=>\&_getttl,
214             'ini'=>\&_getini,});
215             if($cre ne 'create') {
216             carp "ignoring 'nocreate' for MainWindow...\n";
217             }
218             if($pak and $pak ne 'nopack') {
219             carp "Packing options ($pak) for MainWindow are being ignored...\n";
220             }
221             $code="\$\$w{$name}=$type->new";
222             $err=_docode($gl,$level,$code);
223             $code='';
224             if($err) {
225             croak "failed to open MainWindow\n";
226             }
227             if($cfg) {
228             $code="\$\$w{$name}->configure($cfg)";
229             $err=_docode($gl,$level,$code);
230             $code='';
231             if($err) {
232             croak "failed to configure MainWindow\n";
233             }
234             }
235             for(@{$$spec[1]}) {
236             if(substr($$_[0],0,1) ne '<') {
237             _loadwidget $gl,$_,$level+1,$name;
238             } else {
239             _bindevent $gl,$_,$level+1,$name;
240             }
241             }
242             return;
243             }
244             if($type eq 'MainWindow') {
245             $type='Toplevel';
246             }
247             if($type eq 'Toplevel') {
248             ($cre,$cfg,$pak)=_getargs($gl,0,$tail,{'ttl'=>\&_getttl,});
249             } elsif($type eq 'Menu') {
250             ($cre,$cfg,$pak)=_getargs($gl,0,$tail,{});
251             } else {
252             ($cre,$cfg,$pak)=_getargs($gl,1,$tail,{'cmd'=>\&_getcmd,
253             'sub'=>\&_getsub,});
254             }
255             if($cre ne 'create') {
256             $$gl{widgets}{$name}=
257             [$level,$momname,undef,$name,$type,$cfg,$pak,$$spec[1]];
258             return;
259             }
260             $code="\$\$w{$name}=\$\$w{$momname}->$type";
261             $code.="($cfg)" if($cfg);
262             $err=_docode($gl,$level,$code);
263             $code='';
264             if($err) {
265             croak "failed to create $name: $momname/$type\n";
266             }
267             if($type eq 'Menubutton') {
268             $code="\$\$w{${name}_menu}=\$\$w{$name}->Menu";
269             $err=_docode($gl,$level,$code);
270             $code='';
271             $code="\$\$w{$name}->configure(-menu=>\$\$w{${name}_menu})";
272             $err=_docode($gl,$level,$code);
273             $code='';
274             if($err) {
275             croak "failed to configure Menubutton\n";
276             }
277             }
278             $idx=0;
279             for(@{$$spec[1]}) {
280             if($type ne 'Menubutton') {
281             if(substr($$_[0],0,1) ne '<') {
282             _loadwidget $gl,$_,$level+1,$name;
283             } else {
284             _bindevent $gl,$_,$level+1,$name;
285             }
286             } else {
287             _loadmenitm $gl,$_,$level+1,"${name}_menu",$idx;++$idx;
288             }
289             }
290             return if($pak=~/^nopack/);
291             $ret=($pak=~s/^(pack|place|grid),//)?$1:'pack';
292             $code="\$\$w{$name}->$ret($pak)";
293             $err=_docode($gl,$level,$code);
294             $code='';
295             if($err) {
296             croak "failed to create menu item $momname/$name\n";
297             }
298             }
299              
300             sub _getargs {
301             my ($gl,$pakq,$inp,$cmds)=@_;
302             my ($opt,$sep,$val,$cdr,@cfg,@pak);
303             # called five places: four in loadwidget() and one in loadmenitm()
304             my $create=1;
305             if($pakq) {
306             while($inp!~/^\s*$/) {
307             ($opt,$sep,$cdr)=($inp=~/^([^ :]*)([ :])(.*)$/);
308             if(!defined $sep) {
309             $opt=$inp;
310             $inp='';
311             $val='-empty-';
312             } elsif($sep eq ' ') {
313             $val='-empty-';
314             $cdr=~s/^\s+//;
315             $inp=$cdr;
316             } else {
317             ($val,$inp)=($cdr=~/^[']([^']*)[']\s*(.*)$/);
318             if(!defined $val) {
319             if(substr($cdr,0,1) eq ' ') {
320             $val='';$cdr=~s/^\s+//;
321             $inp=$cdr;
322             } else {
323             ($val,$inp)=split ' ',$cdr,2;
324             }
325             }
326             }
327             last if $opt eq '';
328              
329             if(!defined $val or $val eq '' or $val eq '-empty-') { # no $val
330             if($opt eq 'nocreate') {
331             $create=0;
332             } elsif($opt=~/^nopack|pack|place|grid$/) {
333             unshift @pak,$opt;
334             } else {
335             push @pak,"-$opt=>\"\"";
336             }
337             } else { # we have $val
338             if($val=~/^\$\$/) {
339             push @pak,"-$opt=>$val";
340             } elsif($val=~/^\$\d+/) {
341             push @pak,"-$opt=>$val";
342             } elsif($val=~/^\$/) {
343             push @pak,"-$opt=>".'$$gl{'.substr($val,1).'}';
344             } elsif($val=~/^\\/) {
345             push @pak,"-$opt=>".'\\$$gl{'.substr($val,1).'}';
346             } elsif($val eq "''") {
347             push @pak,"-$opt=>$val";
348             } elsif($val=~/^\[.*\]$/) {
349             push @pak,"-$opt=>$val";
350             } else {
351             push @pak,"-$opt=>\"$val\"";
352             }
353             } # end of actions for a $val that is present
354             } # end of loop on packing options
355             } # end of stuff to do if this widget allows packing
356             while(defined $inp and $inp!~/^\s*$/) {
357             ($opt,$sep,$cdr)=($inp=~/^([^ :]*)([ :])(.*)$/);
358             if(!defined $sep) {
359             $opt=$inp;
360             $inp='';
361             $val='-empty-';
362             } elsif($sep eq ' ') {
363             $val='-empty-';
364             $cdr=~s/^\s+//;
365             $inp=$cdr;
366             } else {
367             ($val,$inp)=($cdr=~/^["]([^"]+)["]\s*(.*)$/) or
368             ($val,$inp)=($cdr=~/^[']([^']+)[']\s*(.*)$/) or
369             ($val,$inp)=($cdr=~/^([[][^\]]+[\]])\s*(.*)$/);
370             }
371              
372             if(exists $$cmds{$opt}) {
373             if(!defined $val) {
374             $val=$cdr;
375             $inp='';
376             }
377             $val='' if $val eq '-empty-';
378             # The following is subtle or complicated, due to the extra indirection
379             ($opt,$val)=&{$$cmds{$opt}}($gl,$opt,$val);
380             } else {
381             if(!defined $val) {
382             if(substr($cdr,0,1) eq ' ') {
383             $val='';
384             $cdr=~s/^\s+//;
385             $inp=$cdr;
386             } else {
387             ($val,$inp)=split ' ',$cdr,2;
388             }
389             if(!defined $val) {
390             $val=$cdr;
391             $inp='';
392             }
393             }
394              
395             if(defined $val and $val ne '') {
396             if($val eq '-empty-') {
397             $val='';
398             } elsif($val=~/^\$\$/) {
399             # leave $val alone
400             } elsif($val=~/^\\\$\$/) {
401             # leave $val alone
402             } elsif($val=~/^\$\d+/) {
403             # leave $val alone
404             } elsif($val=~/^\$/) {
405             $val='$$gl{'.substr($val,1).'}';
406             } elsif($val=~/^\\/) {
407             $val='\\$$gl{'.substr($val,1).'}';
408             } elsif($val ne "''" and $val!~/^\[.*\]$/
409             and $val!~/^\'.*\'$/ and $val!~/^\".*\"$/) {
410             $val="\"$val\"";
411             }
412             }
413             }
414             last if !defined $opt or $opt eq '';
415             push @cfg,($val ne '')?"-$opt=>".$val:"\"$opt\"";
416             }
417             return ($create?'create':'nocreate',
418             join(',',@cfg),
419             $pakq?join(',',@pak):'nopack');
420             }
421              
422             sub _docode {
423             my ($gl,$level,$code)=@_;
424             $code.=";\n";
425             my $w=$$gl{widgets};
426             $$gl{lfh}->print(' 'x$level,$code) if(exists $$gl{lfh});
427             $code.="1;\n";
428             return undef if $$gl{genonly};
429             my $ret=eval $code;
430             carp $@ if $@; # $@: EVAL_ERROR (msg or '')
431             $code='';
432             return $@;
433             }
434              
435             sub _bindevent {
436             my ($gl,$spec,$level,$momname)=@_;
437             my ($event,$act)=split ' ',$$spec[0],2;
438             my ($code,$err);
439             $code="\$\$w{$momname}->bind(\'$event\'=>$act)";
440             $err=_docode($gl,$level,$code);
441             $code='';
442             if($err) {
443             croak "failed to bind: $event: $err\n";
444             }
445             }
446              
447             sub _loadmenitm {
448             my ($gl,$spec,$level,$momname,$momidx)=@_;
449             my ($localname,$name,$type,$tail,$code,$err,$label);
450             my $trans=1;
451             my %mt=%{$$gl{menutypes}};
452             ($localname,$type,$tail)=split ' ',$$spec[0],3;
453             $name=$momname.$localname;
454             if($type!~/^[-cmrk]$/) { $trans=0;
455             if($type!~/^separator|command|cascade|radiobutton|checkbutton$/i) {
456             croak "unrecgnized menu item type: $type\n";
457             }
458             }
459             my ($cre,$cfg,$pak)=_getargs($gl,0,$tail,{'cmd'=>\&_getcmd,
460             'sub'=>\&_getsub,});
461             if($cre ne 'create') {
462             $$gl{widgets}{"${momname}_$momidx"}=
463             [$level,$momname,$momidx,$name,$type,$cfg,$pak,$$spec[1]];
464             return;
465             }
466             $code="\$\$w{${momname}_$momidx}=\$\$w{$momname}->";
467             $code.=$trans?$mt{$type}:$type;
468             if($type ne '-') {
469             $cfg=~s/(-label=>[^,]+),?//;
470             croak "menu item $name has no label\n" if not defined $1;
471             $code.="($1)";
472             }
473             $err=_docode($gl,$level,$code);
474             $code='';
475             croak "failed to create menu item $momname/$name($momidx)\n" if($err);
476             if($cfg) {
477             $code="\$\$w{${momname}_$momidx}->configure($cfg)";
478             $err=_docode($gl,$level,$code);
479             $code='';
480             if($err) {
481             croak "failed to configure $momname/$name($momidx)\n";
482             }
483             }
484             if(@{$$spec[1]}) {
485             $code="\$\$w{${momname}_${momidx}_menu}";
486             $code.="=\$\$w{$momname}->Menu";
487             $err=_docode($gl,$level,$code);
488             $code='';
489             if($err) {
490             croak "failed to create Menu: ${momname}_${momidx}_menu\n";
491             }
492             $code="\$\$w{${momname}_$momidx}->configure(";
493             $code.="-menu=>\$\$w{${momname}_${momidx}_menu})";
494             $err=_docode($gl,$level,$code);
495             $code='';
496             my $errname="${momname}_$momidx/${momname}_${momidx}_menu";
497             if($err) {
498             croak "failed to create menu item $errname\n";
499             }
500             }
501             my $idx=0;
502             for(@{$$spec[1]}) {
503             _loadmenitm $gl,$_,$level+1,"${momname}_${momidx}_menu",$idx;++$idx;
504             }
505             }
506              
507             sub _getttl {
508             my ($gl,$opt,$inp)=@_;
509             $opt='title';
510             return ($opt,"\"$inp\"");
511             }
512              
513             sub _getcmd {
514             my ($gl,$opt,$inp)=@_;
515             $opt='command';
516             my ($cmd,$dummy,$args)=($inp=~/^([^( ]+)\s*([(](.*)[)])?$/);
517             my $ocmd='[\&main::'.$cmd.',$gl,'.$args.']';
518             return ($opt,$ocmd);
519             }
520              
521             sub _getsub {
522             my ($gl,$opt,$inp)=@_;
523             $opt='command';
524             my $ocmd='sub { '.$inp.' }';
525             return ($opt,$ocmd);
526             }
527              
528             sub _getini {
529             my ($gl,$opt,$inp)=@_;
530             $$gl{inicode}=$inp;
531             return (undef,undef);
532             }
533              
534             1;
535             # The preceding line will make sure the compiler returns a true value
536              
537             __END__