File Coverage

blib/lib/Tk/Getopt.pm
Criterion Covered Total %
statement 161 795 20.2
branch 69 432 15.9
condition 18 154 11.6
subroutine 16 73 21.9
pod 11 13 84.6
total 275 1467 18.7


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # $Id: Getopt.pm,v 1.65 2008/09/23 19:50:43 eserte Exp $
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 1997,1998,1999,2000,2003,2007,2008 Slaven Rezic. All rights reserved.
8             # This package is free software; you can redistribute it and/or
9             # modify it under the same terms as Perl itself.
10             #
11             # Mail: srezic@cpan.org
12             # WWW: http://www.rezic.de/eserte/
13             #
14              
15             package Tk::Getopt;
16             require 5.005; # calling CODE refs
17 5     5   8702 use strict;
  5         8  
  5         230  
18 5         844 use vars qw($loadoptions $VERSION $x11_pass_through
19             $CHECKMARK_OFF $CHECKMARK_ON
20             $FILE_IMAGE $CURR_GEOMETRY_IMAGE $DEBUG
21 5     5   27 );
  5         9  
22 5     5   28 use constant OPTNAME => 0;
  5         12  
  5         397  
23 5     5   33 use constant OPTTYPE => 1;
  5         6  
  5         202  
24 5     5   25 use constant DEFVAL => 2;
  5         8  
  5         204  
25 5     5   23 use constant OPTEXTRA => 3;
  5         9  
  5         381  
26              
27 5     5   25 use Carp qw();
  5         38  
  5         69917  
28              
29             $VERSION = '0.50';
30             $VERSION = eval $VERSION;
31              
32             $DEBUG = 0;
33             $x11_pass_through = 0;
34              
35             sub new {
36 10     10 1 450 my($pkg, %a) = @_;
37 10         19 my $self = {};
38              
39 10 50       45 $self->{'options'} = delete $a{'-options'} if exists $a{'-options'};
40              
41 10 100       55 if (exists $a{'-opttable'}) {
    50          
42 3         16 $self->{'opttable'} = delete $a{'-opttable'};
43 3         7 foreach (@{$self->{'opttable'}}) {
  3         10  
44             # Convert from new style without hash for extra options for
45             # internal operation.
46             # ['opt', '=s', 'defval', 'x' => 'y', 'z' => 'a', ...] into
47             # ['opt', '=s', 'defval', {'x' => 'y', 'z' => 'a', ...}]
48 7 100 66     70 if (ref $_ eq 'ARRAY' and
      100        
49             defined $_->[OPTEXTRA] and
50             ref $_->[OPTEXTRA] ne 'HASH') {
51 1 50       6 if ((@$_ - OPTEXTRA) % 2 != 0) {
52 0         0 warn "Odd number of elements in definition for " . $_->[OPTNAME];
53             }
54 1         6 my %h = splice @$_, OPTEXTRA;
55 1         3 $_->[OPTEXTRA] = \%h;
56             }
57             # Handle aliases
58 7 50 33     51 if (ref $_ eq 'ARRAY' && $_->[OPTNAME] =~ /\|/) {
59 0         0 my($opt, @aliases) = split(/\|/, $_->[OPTNAME]);
60 0         0 $_->[OPTNAME] = $opt;
61 0         0 push(@{$_->[OPTEXTRA]{'aliases'}}, @aliases);
  0         0  
62             }
63             }
64             } elsif (exists $a{'-getopt'}) {
65             # build opttable from -getopt argument
66 7         12 my @optionlist;
67 7         42 my $genprefix = "(--|-|\\+)";
68 7 100       24 if (ref $a{'-getopt'} eq 'HASH') {
69             # convert hash to array
70             @optionlist
71 4         10 = map { ($_, $a{'-getopt'}->{$_}) } keys %{$a{'-getopt'}};
  37         96  
  4         26  
72             } else {
73 3         5 @optionlist = @{$a{'-getopt'}};
  3         33  
74             }
75 7         28 delete $a{'-getopt'};
76             # check if first argument is hash reference
77 7 100       31 if (ref $optionlist[0] eq 'HASH') {
78 2         4 $self->{'options'} = shift @optionlist;
79             }
80 7         27 while (@optionlist > 0) {
81 45         69 my $opt = shift @optionlist;
82             # Strip leading prefix so people can specify "--foo=i"
83             # if they like.
84 45 50       1301 $opt = $2 if $opt =~ /^($genprefix)+(.*)$/;
85              
86 45 50       216 if ($opt !~ /^(\w+[-\w|]*)?(!|[=:][infse][@%]?)?$/) {
87 0         0 warn "Error in option spec: \"", $opt, "\"\n";
88 0         0 next;
89             }
90 45         119 my($o, $c) = ($1, $2);
91 45 100       87 $c = '' unless defined $c;
92 45         783 my @aliases;
93 45 100       101 if ($o =~ /\|/) {
94             # Handle alias names
95 1         4 @aliases = split(/\|/, $o);
96 1         2 $o = shift @aliases;
97             }
98 45         44 my $varref;
99             # If no linkage is supplied in the @optionlist, copy it from
100             # the userlinkage ($self->{'options'}) if available.
101 45 100 100     221 if (defined $self->{'options'} && !ref $optionlist[0]) {
    100          
102 5 100       15 $varref = (exists $self->{'options'}{$o} ?
103             $self->{'options'}{$o} :
104             \$self->{'options'}{$o});
105             } elsif (ref $optionlist[0]) {
106             # link to global variable
107 39         52 $varref = shift @optionlist;
108             }
109 45         60 my %a;
110 45 100       93 if (defined $varref) {
111 44 100       96 if (ref $varref eq 'CODE') {
112 2         3 my $code = $varref;
113             $a{'callback'} = sub {
114 2 100   2   11 if ($self->{'options'}{$o}) {
115 1         4 &$code;
116             }
117 2         12 };
118 2         6 $varref = \$self->{'options'}{$o};
119             }
120 44 50       806 if (ref($varref) =~ /^(SCALAR|HASH|ARRAY)$/) {
121 44         88 $a{'var'} = $varref;
122             } else {
123 0         0 die "Can't handle variable reference of type "
124             . ref $varref;
125             }
126             }
127 45 100       87 if (@aliases) {
128 1         2 $a{'alias'} = \@aliases;
129             }
130 45         47 push(@{$self->{'opttable'}}, [$o, $c, undef, \%a]);
  45         334  
131             }
132             } else {
133 0         0 die "No opttable array ref or getopt hash ref";
134             }
135              
136 10         46 $self->{'caller'} = (caller)[0];
137 10         29 $self->{'filename'} = delete $a{'-filename'};
138 10         21 $self->{'nosafe'} = delete $a{'-nosafe'};
139 10         20 $self->{'useerrordialog'} = delete $a{'-useerrordialog'};
140              
141 10 50       653 die "Unrecognized arguments: " . join(" ", %a) if %a;
142              
143 10         47 bless $self, $pkg;
144             }
145              
146             # Return a list with all option names, that is, section labels and
147             # descriptions are ignored.
148             sub _opt_array {
149 12     12   23 my $self = shift;
150 12         13 my @res;
151 12         31 foreach (@{$self->{'opttable'}}) {
  12         61  
152 80 50 33     543 push @res, $_
153             if ref $_ eq 'ARRAY' and
154             $_->[OPTNAME] ne '';
155             }
156 12         47 @res;
157             }
158              
159             # Return a reference to the option variable given by $opt
160             sub varref {
161 86     86 1 105 my($self, $opt) = @_;
162 86 100       181 if($opt->[OPTEXTRA]{'var'}) {
    50          
163 77         323 $opt->[OPTEXTRA]{'var'};
164             } elsif ($self->{'options'}) {
165 0         0 \$self->{'options'}{$opt->[OPTNAME]};
166             } else {
167             # Link to global $opt_XXX variable.
168             # Make sure a valid perl identifier results.
169 9         26 my $v;
170 9         31 ($v = $opt->[OPTNAME]) =~ s/\W/_/g;
171 9         535 eval q{\$} . $self->{'caller'} . q{::opt_} . $v; # XXX @, %
172             }
173             }
174             # Formerly the varref method was private:
175 0     0   0 sub _varref { shift->varref(@_) }
176              
177             sub optextra {
178 0     0 1 0 my($self, $opt, $arg) = @_;
179 0         0 $opt->[OPTEXTRA]{$arg};
180             }
181              
182             sub _is_separator {
183 0     0   0 my $opt = shift;
184 0 0 0     0 defined $opt->[OPTNAME] && $opt->[OPTNAME] eq '' &&
      0        
185             defined $opt->[DEFVAL] && $opt->[DEFVAL] eq '-';
186             }
187              
188             sub set_defaults {
189 0     0 1 0 my $self = shift;
190 0         0 my $opt;
191 0         0 foreach $opt ($self->_opt_array) {
192 0 0       0 if (defined $opt->[DEFVAL]) {
193 0         0 my $ref = ref $self->varref($opt);
194 0 0       0 if ($ref eq 'ARRAY') {
    0          
    0          
195 0         0 @ {$self->varref($opt)} = @{ $opt->[DEFVAL] };
  0         0  
  0         0  
196             } elsif ($ref eq 'HASH') {
197 0         0 % {$self->varref($opt)} = %{ $opt->[DEFVAL] };
  0         0  
  0         0  
198             } elsif ($ref eq 'SCALAR') {
199 0         0 $ {$self->varref($opt)} = $opt->[DEFVAL];
  0         0  
200             } else {
201 0         0 die "Invalid reference type for option $opt->[OPTNAME] while setting the default value (maybe you should specify as the default value)";
202             }
203             }
204             }
205             }
206              
207             sub load_options {
208 1     1 1 7 my($self, $filename) = @_;
209 1 50       7 $filename = $self->{'filename'} if !$filename;
210 1 50       4 return if !$filename;
211 1 50       4 if ($self->{'nosafe'}) {
212 0         0 require Safe;
213 0         0 my $c = new Safe;
214 0         0 $c->share('$loadoptions');
215 0 0       0 if (!$c->rdo($filename)) {
216 0         0 warn "Can't load $filename";
217 0         0 return undef;
218             }
219             } else {
220 1         2 eval {do $filename};
  1         681  
221 1 50       6 if ($@) {
222 0         0 warn $@;
223 0         0 return undef;
224             }
225             }
226              
227 1         3 my $opt;
228 1         3 foreach $opt ($self->_opt_array) {
229 7 50       18 if (exists $loadoptions->{$opt->[OPTNAME]}) {
230 7 50 66     14 if (ref $self->varref($opt) eq 'CODE') {
    100 66        
    100          
231 0 0       0 $self->varref($opt)->($opt, $loadoptions->{$opt->[OPTNAME]}) if $loadoptions->{$opt->[OPTNAME]};
232             } elsif (ref $self->varref($opt) eq 'ARRAY' &&
233             ref $loadoptions->{$opt->[OPTNAME]} eq 'ARRAY') {
234 1         2 @{ $self->varref($opt) } = @{ $loadoptions->{$opt->[OPTNAME]} };
  1         5  
  1         3  
235             } elsif (ref $self->varref($opt) eq 'HASH' &&
236             ref $loadoptions->{$opt->[OPTNAME]} eq 'HASH') {
237 1         1 %{ $self->varref($opt) } = %{ $loadoptions->{$opt->[OPTNAME]} };
  1         4  
  1         4  
238             } else {
239 5         7 $ {$self->varref($opt)} = $loadoptions->{$opt->[OPTNAME]};
  5         10  
240             }
241             }
242             }
243 1         5 1;
244             }
245              
246             sub save_options {
247 1     1 1 61 my($self, $filename) = @_;
248 1 50       6 $filename = $self->{'filename'} if !$filename;
249 1 50       3 die "Saving disabled" if !$filename;
250 1         77 eval "require Data::Dumper";
251 1 50       8051 if ($@) {
252 0         0 warn $@;
253 0         0 $self->my_die("No Data::Dumper available, cannot save options.\n");
254             } else {
255 1 50       151 if (open(OPT, ">$filename")) {
256 1         2 my %saveoptions;
257             my $opt;
258 1         19 foreach $opt ($self->_opt_array) {
259 11 50       28 if (!$opt->[OPTEXTRA]{'nosave'}) {
260 11         13 my $ref;
261 11 50       18 if ($opt->[OPTEXTRA]{'savevar'}) {
262 0         0 $ref = $opt->[OPTEXTRA]{'savevar'};
263             } else {
264 11         24 $ref = $self->varref($opt);
265             }
266 11 100       36 if (ref($ref) eq 'SCALAR') {
    50          
267 9         32 $saveoptions{$opt->[OPTNAME]} = $$ref;
268             } elsif (ref($ref) =~ /^(HASH|ARRAY)$/) {
269 2         8 $saveoptions{$opt->[OPTNAME]} = $ref;
270             }
271             }
272             }
273 1         3 local $Data::Dumper::Sortkeys = $Data::Dumper::Sortkeys = 1;
274 1         2 local $Data::Dumper::Indent = $Data::Dumper::Indent = 1;
275 1 50       18 if (Data::Dumper->can('Dumpxs')) {
276             # use faster version of Dump
277 1         21 print OPT
278             Data::Dumper->Dumpxs([\%saveoptions], ['loadoptions']);
279             } else {
280 0         0 print OPT
281             Data::Dumper->Dump([\%saveoptions], ['loadoptions']);
282             }
283 1         190 close OPT;
284 1 50       5 warn "Options written to $filename" if $DEBUG;
285 1         8 1;
286             } else {
287 0         0 $self->my_die("Writing to config file <$filename> failed: $!\n");
288 0         0 undef;
289             }
290             }
291             }
292              
293             sub get_options {
294 9     9 1 50 my $self = shift;
295 9         14 my %getopt;
296             my $opt;
297 9         800 foreach $opt ($self->_opt_array) {
298 45         95 $getopt{_getopt_long_string($opt->[OPTNAME], $opt->[OPTTYPE])} =
299             $self->varref($opt);
300             # process aliases
301 45         70 foreach (@{$opt->[OPTEXTRA]{'alias'}}) {
  45         122  
302 3         7 $getopt{_getopt_long_string($_, $opt->[OPTTYPE])} =
303             $self->varref($opt);
304             }
305             }
306 9         7575 require Getopt::Long;
307             # XXX anders implementieren ... vielleicht die X11-Optionen zusätzlich
308             # in die %getopt-Liste reinschreiben?
309 9 50       72470 if ($x11_pass_through) {
310 0         0 Getopt::Long::config('pass_through');
311             }
312 9         72 my $res = Getopt::Long::GetOptions(%getopt);
313             # Hack to pass standard X11 options (as defined in Tk::CmdLine)
314 9 50       7754 if ($x11_pass_through) {
315 0         0 eval {
316 0         0 require Tk::CmdLine;
317 0 0       0 if ($Tk::CmdLine::VERSION >= 3.012) {
318             # XXX nicht ausgetestet
319 0         0 my @args = @ARGV;
320 0   0     0 while (@args && $args[0] =~ /^-(\w+)$/) {
321 0         0 my $sw = $1;
322 0 0       0 return 0 if !$Tk::CmdLine::Method{$sw};
323 0 0       0 if ($Tk::CmdLine::Method{$sw} ne 'Flag_') {
324 0         0 shift @args;
325             }
326 0         0 shift @args;
327             }
328             } else {
329 0         0 my $flag_ref = \&Tk::CmdLine::flag;
330 0         0 my @args = @ARGV;
331 0   0     0 while (@args && $args[0] =~ /^-(\w+)$/) {
332 0         0 my $sw = $1;
333 0 0       0 return 0 if !$Tk::CmdLine::switch{$sw};
334 0 0       0 if ($Tk::CmdLine::switch{$sw} ne $flag_ref) {
335 0         0 shift @args;
336             }
337 0         0 shift @args;
338             }
339             }
340 0         0 $res = 1;
341             };
342 0 0       0 warn $@ if $@;
343             }
344 9         45 $res;
345             }
346              
347             # Builds a string for Getopt::Long. Arguments are option name and option
348             # type (e.g. '!' or '=s').
349             sub _getopt_long_string {
350 48     48   67 my($option, $type) = @_;
351 48 100 66     229 $option . (length($option) == 1 &&
352             (!defined $type || $type eq '' || $type eq '!')
353             ? '' : $type);
354             }
355              
356             # Prints option name with one or two dashes
357             sub _getopt_long_dash {
358 0     0   0 my $option = shift;
359 0 0       0 (length($option) == 1 ? '' : '-') . "-$option";
360             }
361              
362             sub usage {
363 0     0 1 0 my $self = shift;
364 0         0 my $usage = "Usage: $0 [options]\n";
365 0         0 my $opt;
366 0         0 foreach $opt ($self->_opt_array) {
367             # The following prints all options as a comma-seperated list
368             # with one or two dashes, depending on the length of the option.
369             # Options are sorted by length.
370 0         0 $usage .= join(', ',
371 0         0 sort { length $a <=> length $b }
372 0 0       0 map { _getopt_long_dash($_) }
373 0         0 map { ($opt->[OPTTYPE] eq '!' ? "[no]" : "") . $_ }
374 0         0 ($opt->[OPTNAME], @{$opt->[OPTEXTRA]{'alias'}}));
375 0         0 $usage .= "\t";
376 0 0       0 $usage .= $opt->[OPTEXTRA]{'help'} if $opt->[OPTEXTRA]{'help'};
377 0 0       0 $usage .= " (default: " . $opt->[DEFVAL] . ") " if $opt->[DEFVAL];
378 0         0 $usage .= "\n";
379             }
380 0         0 $usage;
381             }
382              
383             sub process_options {
384 1     1 1 10 my($self, $former, $fromgui) = @_;
385 1         2 my $bag = {};
386 1         7 foreach my $optdef ($self->_opt_array) {
387 17         24 my $opt = $optdef->[OPTNAME];
388              
389 17         18 my $callback;
390 17 50       39 if ($fromgui) {
391 0         0 $callback = $optdef->[OPTEXTRA]{'callback-interactive'};
392             }
393 17 50       32 if (!$callback) {
394 17         34 $callback = $optdef->[OPTEXTRA]{'callback'};
395             }
396 17 100       32 if ($callback) {
397             # no warnings here ... it would be too complicated to catch
398             # all undefined values
399 2         6 my $old_w = $^W;
400 2         8 local($^W) = 0;
401             # execute callback if value has changed
402 2 50 0     8 if (!(defined $former
      33        
403             && (!exists $former->{$opt}
404             || $ {$self->varref($optdef)} eq $former->{$opt}))) {
405 2         7 local($^W) = $old_w; # fall back to original value
406 2         7 &$callback(optdef => $optdef, bag => $bag);
407             }
408             }
409 17 50 33     590 if ($optdef->[OPTEXTRA]{'strict'} &&
410             UNIVERSAL::isa($optdef->[OPTEXTRA]{'choices'},'ARRAY')) {
411             # check for valid values (valid are: choices and default value)
412 0           my $v = $ {$self->varref($optdef)};
  0            
413 0           my @choices = @{$optdef->[OPTEXTRA]{'choices'}};
  0            
414 0 0         push(@choices, $optdef->[DEFVAL]) if defined $optdef->[DEFVAL];
415 0           my $seen;
416 0           for my $choice (@choices) {
417 0 0         my $value = (ref $choice eq 'ARRAY' ? $choice->[1] : $choice);
418 0 0         if ($value eq $v) {
419 0           $seen = 1;
420 0           last;
421             }
422             }
423 0 0         if (!$seen) {
424 0 0         if (defined $former) {
425 0           warn "Not allowed: " . $ {$self->varref($optdef)}
  0            
426             . " for -$opt. Using old value $former->{$opt}";
427 0           $ {$self->varref($optdef)} = $former->{$opt};
  0            
428             } else {
429 0           die "Not allowed: "
430 0           . $ {$self->varref($optdef)} . " for -$opt\n"
431             . "Allowed is only: " . join(", ", @choices);
432             }
433             }
434             }
435             }
436             }
437              
438             sub my_die {
439 0     0 0   my($self, $msg, $is_safe) = @_;
440 0           my $use_tk;
441 0 0 0       if ($self->{'useerrordialog'} && defined &Tk::MainWindow::Existing) {
442 0           for my $mw (Tk::MainWindow::Existing()) {
443 0 0         if (Tk::Exists($mw)) {
444 0           $use_tk = $mw;
445 0           last;
446             }
447             }
448 0 0 0       if ($use_tk && !defined $is_safe) {
449 0           for(my $i=0; $i<100; $i++) {
450 0           my(undef,undef,undef,$subroutine) = caller($i);
451 0 0         last if !defined $subroutine;
452 0 0         if ($subroutine eq '(eval)') {
453 0           $use_tk = 0;
454 0           last;
455             }
456             }
457             }
458             }
459 0 0         if ($use_tk) {
460 0           eval {
461 0           $use_tk->messageBox(-icon => "error",
462             -message => $msg,
463             -title => "Error",
464             );
465             };
466 0 0         if ($@) {
467 0           Carp::croak($msg);
468             }
469             } else {
470 0           Carp::croak($msg);
471             }
472             }
473              
474             # try to work around weird browse entry
475             sub _fix_layout {
476 0     0     my($self, $frame, $widget, %args) = @_;
477 0           my($w, $real_w);
478 0 0         if ($Tk::VERSION < 804) {
479 0           my $f = $frame->Frame;
480 0           $f->Label->pack(-side => "left"); # dummy
481 0           $real_w = $f->$widget(%args)->pack(-side => "left", -padx => 1);
482 0           $w = $f;
483             } else {
484 0           $w = $real_w = $frame->$widget(%args);
485             }
486 0           ($w, $real_w);
487             }
488              
489             sub _boolean_widget {
490 0     0     my($self, $frame, $opt) = @_;
491 0           ($self->_fix_layout($frame, "Checkbutton",
492             -variable => $self->varref($opt)))[0];
493             }
494              
495             sub _boolean_checkmark_widget {
496             # XXX hangs with Tk800.014?!
497 0     0     my($self, $frame, $opt) = @_;
498 0           _create_checkmarks($frame);
499 0           ($self->_fix_layout($frame, "Checkbutton",
500             -variable => $self->varref($opt),
501             -image => $CHECKMARK_OFF,
502             -selectimage => $CHECKMARK_ON,
503             -indicatoron => 0,
504             ))[0];
505             }
506              
507             sub _number_widget {
508 0     0     my($self, $frame, $opt) = @_;
509 0 0         ($self->_fix_layout($frame, "Scale",
510             -orient => 'horizontal',
511             -from => $opt->[OPTEXTRA]{'range'}[0],
512             -to => $opt->[OPTEXTRA]{'range'}[1],
513             -showvalue => 1,
514             -resolution => ($opt->[OPTTYPE] =~ /f/ ? 0 : 1),
515             -variable => $self->varref($opt)
516             ))[0];
517             }
518              
519             sub _integer_widget {
520 0     0     my($self, $frame, $opt) = @_;
521 0 0         if (exists $opt->[OPTEXTRA]{'range'}) {
522 0           $self->_number_widget($frame, $opt);
523             } else {
524 0           $self->_string_widget($frame, $opt, -restrict => "=i");
525             }
526             }
527              
528             sub _float_widget {
529 0     0     my($self, $frame, $opt) = @_;
530 0 0         if (exists $opt->[OPTEXTRA]{'range'}) {
531 0           $self->_number_widget($frame, $opt);
532             } else {
533 0           $self->_string_widget($frame, $opt, -restrict => "=f");
534             }
535             }
536              
537             sub _list_widget {
538 0     0     my($self, $frame, $opt) = @_;
539 0 0 0       if ($opt->[OPTEXTRA]{'strict'} && grep { ref $_ eq 'ARRAY' } @{$opt->[OPTEXTRA]{'choices'}}) {
  0            
  0            
540 0           $self->_optionmenu_widget($frame, $opt);
541             } else {
542 0           $self->_browseentry_widget($frame, $opt);
543             }
544             }
545              
546             sub _browseentry_widget {
547 0     0     my($self, $frame, $opt) = @_;
548 0           require Tk::BrowseEntry;
549 0           my %args = (-variable => $self->varref($opt));
550 0 0         if ($opt->[OPTEXTRA]{'strict'}) {
551 0           $args{-state} = "readonly";
552             }
553 0           my $w = $frame->BrowseEntry(%args);
554 0           my %mapping;
555 0           my @optlist = @{$opt->[OPTEXTRA]{'choices'}};
  0            
556 0 0         unshift @optlist, $opt->[DEFVAL] if defined $opt->[DEFVAL];
557 0           my $o;
558             my %seen;
559 0           foreach $o (@optlist) {
560 0 0         if (!$seen{$o}) {
561 0           $w->insert("end", $o);
562 0           $seen{$o}++;
563             }
564             }
565 0           $w;
566             }
567              
568             sub _optionmenu_widget {
569 0     0     my($self, $frame, $opt) = @_;
570 0           require Tk::Optionmenu;
571 0           my $varref = $self->varref($opt);
572             # Have to remember value, otherwise Optionmenu would overwrite it...
573 0           my $value = $$varref;
574 0           my %args = (-variable => $varref,
575             -options => $opt->[OPTEXTRA]{'choices'},
576             );
577 0           my $w = $frame->Optionmenu(%args);
578 0 0         if (defined $value) {
579 0           my $label = $value;
580 0           for my $choice (@{ $opt->[OPTEXTRA]{'choices'} }) {
  0            
581 0 0 0       if (ref $choice eq 'ARRAY' && $choice->[1] eq $value) {
582 0           $label = $choice->[0];
583             }
584             }
585 0           $w->setOption($label, $value);
586             }
587 0           $w;
588             }
589              
590             sub _string_widget {
591 0     0     my($self, $frame, $opt, %args) = @_;
592 0 0         if (exists $opt->[OPTEXTRA]{'choices'}) {
593 0           $self->_list_widget($frame, $opt);
594             } else {
595 0 0         my($e, $ee) = $self->_fix_layout
596             ($frame, "Entry",
597             (defined $opt->[OPTEXTRA]{'length'}
598             ? (-width => $opt->[OPTEXTRA]{'length'}) : ()),
599             -textvariable => $self->varref($opt));
600 0 0 0       if ($args{-restrict} || defined $opt->[OPTEXTRA]{'maxsize'}) {
601 0     0     my $restrict_int = sub { $_[0] =~ /^([+-]?\d+|)$/ };
  0            
602             my $restrict_float = sub {
603 0     0     $_[0] =~ /^(|([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)$/
604 0           };
605             my $restrict_len = sub {
606 0     0     length $_[0] <= $opt->[OPTEXTRA]{'maxsize'}
607 0           };
608 0           eval {
609             $ee->configure
610             (-validate => "all",
611             -vcmd => sub {
612 0 0 0 0     ($args{-restrict} ne "=i" || $restrict_int->($_[0]))
      0        
      0        
      0        
613             &&
614             ($args{-restrict} ne "=f" || $restrict_float->($_[0]))
615             &&
616             (!defined $opt->[OPTEXTRA]{'maxsize'} || $restrict_len->($_[0]))
617 0           });
618             };
619 0 0         warn $@ if $@;
620             }
621 0           $e;
622             }
623             }
624              
625             sub _dir_select {
626 0     0     my($top, $curr_dir) = @_;
627              
628 0 0         if ($top->can("chooseDirectory")) {
629 0           return $top->chooseDirectory(-initialdir => $curr_dir);
630             }
631              
632 0 0         if (eval { require Tk::DirSelect; Tk::DirSelect->VERSION("1.03"); 1 }) {
  0            
  0            
  0            
633 0           return $top->DirSelect(-directory => $curr_dir)->Show;
634             }
635              
636 0           require Tk::DirTree;
637 0           my $t = $top->Toplevel;
638 0           $t->title("Choose directory:");
639 0           my $ok = 0; # flag: "1" means OK, "-1" means cancelled
640              
641             # Create Frame widget before the DirTree widget, so it's always visible
642             # if the window gets resized.
643 0           my $f = $t->Frame->pack(-fill => "x", -side => "bottom");
644              
645 0           my $d;
646             $d = $t->Scrolled('DirTree',
647             -scrollbars => 'osoe',
648             -width => 35,
649             -height => 20,
650             -selectmode => 'browse',
651             -exportselection => 1,
652 0     0     -browsecmd => sub { $curr_dir = shift;
653 0 0         if ($^O ne 'MSWin32') {
654 0           $curr_dir =~ s|^//|/|; # bugfix
655             }
656             },
657              
658             # With this version of -command a double-click will
659             # select the directory
660 0     0     -command => sub { $ok = 1 },
661              
662             # With this version of -command a double-click will
663             # open a directory. Selection is only possible with
664             # the Ok button.
665             #-command => sub { $d->opencmd($_[0]) },
666 0           )->pack(-fill => "both", -expand => 1);
667             # Set the initial directory
668 0           $d->chdir($curr_dir);
669              
670             $f->Button(-text => 'Ok',
671 0     0     -command => sub { $ok = 1 })->pack(-side => 'left');
  0            
672             $f->Button(-text => 'Cancel',
673 0     0     -command => sub { $ok = -1 })->pack(-side => 'left');
  0            
674 0     0     $t->OnDestroy(sub { $ok = -1 });
  0            
675 0           $f->waitVariable(\$ok);
676 0 0         $t->destroy if Tk::Exists($t);
677 0 0         if ($ok == 1) {
678 0           $curr_dir;
679             } else {
680 0           undef;
681             }
682             }
683              
684             sub _filedialog_widget {
685 0     0     my($self, $frame, $opt, %args) = @_;
686 0 0         my $subtype = (exists $args{'-subtype'} ? $args{'-subtype'} : 'file');
687 0           my $topframe = $frame->Frame;
688 0           my $e;
689 0 0         if (exists $opt->[OPTEXTRA]{'choices'}) {
690 0           require Tk::BrowseEntry;
691 0           $e = $topframe->BrowseEntry(-variable => $self->varref($opt));
692 0           my @optlist = @{$opt->[OPTEXTRA]{'choices'}};
  0            
693 0 0         unshift(@optlist, $opt->[DEFVAL]) if defined $opt->[DEFVAL];
694 0           my $o;
695 0           foreach $o (@optlist) {
696 0           $e->insert("end", $o);
697             }
698             } else {
699 0 0         if (!eval '
700             use Tk::PathEntry;
701             my $real_e;
702             ($e, $real_e) = $self->_fix_layout($topframe, "PathEntry",
703             -textvariable => $self->varref($opt));
704             # XXX Escape is already used for cancelling Tk::Getopt
705             $real_e->bind("<$_>" => sub { $real_e->Finish }) for (qw/Return/);
706             1;
707             ') {
708 0           ($e) = $self->_fix_layout($topframe, "Entry",
709             -textvariable => $self->varref($opt));
710             }
711             }
712 0           $e->pack(-side => 'left');
713              
714             my $b = $topframe->Button
715             (_get_browse_args($topframe),
716             -command => sub {
717 0     0     require File::Basename;
718 0           my($fd, $filedialog);
719 0 0         if ($Tk::VERSION >= 800) {
720 0 0         if ($subtype eq 'dir') {
    0          
    0          
721 0           $fd = '_dir_select';
722             } elsif ($subtype eq 'savefile') {
723 0           $fd = 'getSaveFile';
724             } elsif ($subtype eq 'file') {
725 0           $fd = 'getOpenFile';
726             } else {
727 0           die "Unknown subtype <$subtype>";
728             }
729             } else {
730 0           $fd = 'FileDialog';
731 0           eval {
732 0 0         die "nope" if $subtype eq 'dir';
733 0           require Tk::FileDialog;
734             };
735 0 0         if ($@) {
736 0           require Tk::FileSelect;
737 0           $fd = 'FileSelect';
738             }
739             # XXX set FileDialog options via $opt->[3]{'filedialog_opt'}
740 0 0         if ($fd eq 'FileDialog') {
741 0           $filedialog = $topframe->FileDialog
742             (-Title => 'Select file');
743             } else {
744 0           $filedialog = $topframe->FileSelect;
745             }
746             }
747 0           my($dir, $base, $file);
748 0           my $act_val = $ {$self->varref($opt)};
  0            
749 0 0         if ($act_val) {
750 0           $dir = File::Basename::dirname($act_val);
751 0           $base = File::Basename::basename($act_val);
752 0 0         $dir = '.' if (!-d $dir);
753              
754 0 0         if ($fd =~ /^get(Open|Save)File$/) {
    0          
    0          
755 0           $file = $topframe->$fd(-initialdir => $dir,
756             -initialfile => $base,
757             -title => 'Select file',
758             # XXX erst ab 800.013 (?)
759             # -force => 1,
760             );
761             } elsif ($fd eq '_dir_select') {
762 0           $file = _dir_select($topframe, $dir);
763             } elsif ($fd eq 'FileDialog') {
764 0           $file = $filedialog->Show(-Path => $dir,
765             -File => $base);
766             } else {
767 0 0         if ($subtype eq 'dir') {
768 0           $file = $filedialog->Show(-directory => $dir,
769             -verify => [qw(-d)],
770             );
771             } else {
772 0           $file = $filedialog->Show(-directory => $dir);
773             }
774             }
775             } else {
776 0 0         if ($fd =~ /^get(Open|Save)File$/) {
    0          
777 0           $file = $topframe->$fd(-title => 'Select file');
778             } elsif ($fd eq '_dir_select') {
779 0           require Cwd;
780 0           $file = _dir_select($topframe, Cwd::cwd());
781             } else {
782 0 0         if ($subtype eq 'dir') {
783 0           $file = $filedialog->Show(-verify => [qw(-d)]);
784             } else {
785 0           $file = $filedialog->Show;
786             }
787             }
788             }
789 0 0 0       if (defined $file && $file ne "") {
790 0           $ {$self->varref($opt)} = $file;
  0            
791             }
792 0           });
793 0           $b->pack(-side => 'left');
794 0           $topframe;
795             }
796              
797             sub _geometry_widget {
798 0     0     my($self, $frame, $opt) = @_;
799 0 0         my($topframe, $e) = $self->_fix_layout
800             ($frame,
801             'Entry',
802             (defined $opt->[OPTEXTRA]{'length'}
803             ? (-width => $opt->[OPTEXTRA]{'length'}) : ()),
804             -textvariable => $self->varref($opt));
805             $topframe->Button(_get_curr_geometry_args($topframe),
806             -command => sub {
807 0     0     my $mw = $frame->MainWindow;
808 0           $e->delete(0, "end");
809 0           $e->insert("end", $mw->geometry);
810             },
811 0           )->pack(-side => "left");
812 0           $topframe;
813             }
814              
815             sub _color_widget {
816 0     0     return shift->_string_widget(@_);
817              
818             # XXX funktioniert leider nicht...
819 0           my($self, $frame, $opt) = @_;
820 0 0         my($topframe, $e) = $self->_fix_layout
821             ($frame,
822             'Entry',
823             (defined $opt->[OPTEXTRA]{'length'}
824             ? (-width => $opt->[OPTEXTRA]{'length'}) : ()),
825             -textvariable => $self->varref($opt));
826 0 0         if ($frame->can("chooseColor")) {
827             $topframe->Button(-text => "...",
828             -padx => 0, -pady => 0,
829             -command => sub {
830 0     0     my $color = $frame->chooseColor;
831             # (-initialcolor => $e->get);
832 0 0         return unless defined $color;
833 0           $e->delete(0, "end");
834 0           $e->insert("end", $color);
835             },
836 0           )->pack(-side => "left");
837             }
838 0           $topframe;
839             }
840              
841             sub _font_widget {
842 0     0     my($self, $frame, $opt) = @_;
843 0 0         my($topframe, $e) = $self->_fix_layout
844             ($frame,
845             'Entry',
846             (defined $opt->[OPTEXTRA]{'length'}
847             ? (-width => $opt->[OPTEXTRA]{'length'}) : ()),
848             -textvariable => $self->varref($opt));
849 0 0         if (eval {require Tk::Font; require Tk::FontDialog; 1}) {
  0            
  0            
  0            
850             $topframe->Button(-text => "...",
851             -padx => 0, -pady => 0,
852             -command => sub {
853 0     0     my $font = $frame->FontDialog
854             (-initfont => $e->get)->Show;
855 0 0         return unless defined $font;
856 0           $e->delete(0, "end");
857 0           $e->insert("end", $font->Pattern);
858             },
859 0           )->pack(-side => "left");
860             }
861 0           $topframe;
862             }
863              
864             # Creates one page of the Notebook widget
865             # Arguments:
866             # $current_page: Frame for drawing
867             # $optnote: Notebook widget
868             # $current_top: title of Notebook page
869             # $optlist: list of options for this Notebook page
870             # $balloon: Balloon widget
871             # $msglist: (optional) list of messages for this Notebook page
872             sub _create_page {
873 0     0     my($self, $current_page,
874             $optnote, $current_top, $optlist,
875             $balloon, $msglist) = @_;
876 0 0         $current_page = $optnote->{$current_top} if !defined $current_page;
877 0           my $opt;
878 0           my $row = -1;
879              
880 0           my $msgobj;
881 0 0 0       if (ref $msglist and
      0        
882             exists $msglist->{$current_top} and
883             $msglist->{$current_top} ne "") {
884 0           $row++;
885 0           $msgobj = $current_page->Label(-text => $msglist->{$current_top},
886             -justify => "left",
887             )->grid(-row => $row, -column => 0,
888             -columnspan => 3);
889             }
890              
891 0           foreach $opt (@{$optlist->{$current_top}}) {
  0            
892 0           my $f = $current_page;
893 0           $row++;
894 0 0         if (_is_separator($opt)) {
895 0           my $separator = $f->Frame(-height => 2,
896             )->grid(-row => $row,
897             -column => 0,
898             -columnspan => 3,
899             -pady => 3,
900             -padx => 3,
901             -sticky => "ew");
902 0           $separator->configure
903             (-fg => $separator->cget(-bg),
904             -bg => $separator->Darken($separator->cget(-bg), 60));
905 0           next;
906             }
907              
908 0           my $label;
909             my $w;
910 0 0         if (exists $opt->[OPTEXTRA]{'label'}) {
911 0           $label = $opt->[OPTEXTRA]{'label'};
912             } else {
913 0           $label = $opt->[OPTNAME];
914 0 0 0       if ($label =~ /^(.*)-(.*)$/ && $1 eq $current_top) {
915 0           $label = $2;
916             }
917             }
918 0           my $lw = $f->Label(-text => $label)->grid(-row => $row, -column => 0,
919             -sticky => 'w');
920 0 0 0       if (exists $opt->[OPTEXTRA]{'widget'}) {
    0 0        
    0 0        
    0 0        
    0 0        
921             # own widget
922 0           $w = &{$opt->[OPTEXTRA]{'widget'}}($self, $f, $opt);
  0            
923             } elsif (defined $opt->[OPTTYPE] &&
924             $opt->[OPTTYPE] eq '!' or $opt->[OPTTYPE] eq '') {
925 0           $w = $self->_boolean_widget($f, $opt); # XXX _checkmark_
926             } elsif (defined $opt->[OPTTYPE] && $opt->[OPTTYPE] =~ /i/) {
927 0           $w = $self->_integer_widget($f, $opt);
928             } elsif (defined $opt->[OPTTYPE] && $opt->[OPTTYPE] =~ /f/) {
929 0           $w = $self->_float_widget($f, $opt);
930             } elsif (defined $opt->[OPTTYPE] && $opt->[OPTTYPE] =~ /s/) {
931 0 0 0       my $subtype = (defined $opt->[OPTEXTRA] &&
932             exists $opt->[OPTEXTRA]{'subtype'} ?
933             $opt->[OPTEXTRA]{'subtype'} : "");
934 0 0 0       if ($subtype eq 'file' ||
    0 0        
    0          
    0          
935             $subtype eq 'savefile' ||
936             $subtype eq 'dir') {
937 0           $w = $self->_filedialog_widget($f, $opt, -subtype => $subtype);
938             } elsif ($subtype eq 'geometry') {
939 0           $w = $self->_geometry_widget($f, $opt);
940             } elsif ($subtype eq 'color') {
941 0           $w = $self->_color_widget($f, $opt);
942             } elsif ($subtype eq 'font') {
943 0           $w = $self->_font_widget($f, $opt);
944             } else {
945 0           $w = $self->_string_widget($f, $opt);
946             }
947             } else {
948 0           warn "Can't generate option editor entry for $opt->[OPTNAME]";
949             }
950 0 0         if (defined $w) {
951 0           $w->grid(-row => $row, -column => 1, -sticky => 'w');
952             }
953 0 0 0       if (exists $opt->[OPTEXTRA]{'help'} && defined $balloon) {
954 0 0         $balloon->attach($w, -msg => $opt->[OPTEXTRA]{'help'})
955             if defined $w;
956 0 0         $balloon->attach($lw, -msg => $opt->[OPTEXTRA]{'help'})
957             if defined $lw;
958             }
959 0 0         if (exists $opt->[OPTEXTRA]{'longhelp'}) {
960             $f->Button(-text => '?',
961             -padx => 1,
962             -pady => 1,
963             -command => sub {
964 0     0     my $t = $f->Toplevel
965             (-title => $self->{_string}{"helpfor"}
966             . " $label");
967 0           $t->Message(-text => $opt->[OPTEXTRA]{'longhelp'},
968             -justify => 'left')->pack;
969             $t->Button(-text => 'OK',
970 0           -command => sub { $t->destroy }
971 0           )->pack;
972 0           $t->Popup(-popover => "cursor");
973 0           })->grid(-row => $row, -column => 2, -sticky => 'w');
974             }
975             }
976 0           $current_page->grid('columnconfigure', 3, -weight => 1);
977 0           $current_page->grid('rowconfigure', ++$row, -weight => 1);
978             }
979              
980             sub _do_undo {
981 0     0     my($self, $undo_options) = @_;
982 0           my $opt;
983 0           foreach $opt ($self->_opt_array) {
984 0 0         next if $opt->[OPTEXTRA]{'nogui'};
985 0 0         if (exists $undo_options->{$opt->[OPTNAME]}) {
986 0           my $ref = ref $self->varref($opt);
987 0 0         if ($ref eq 'ARRAY') {
    0          
    0          
988 0           my @swap = @ {$self->varref($opt)};
  0            
989 0           @ {$self->varref($opt)} = @{ $undo_options->{$opt->[OPTNAME]} };
  0            
  0            
990 0           @{ $undo_options->{$opt->[OPTNAME]}} = @swap;
  0            
991             } elsif ($ref eq 'HASH') {
992 0           my %swap = % {$self->varref($opt)};
  0            
993 0           % {$self->varref($opt)} = %{ $undo_options->{$opt->[OPTNAME]} };
  0            
  0            
994 0           %{ $undo_options->{$opt->[OPTNAME]}} = %swap;
  0            
995             } elsif ($ref eq 'SCALAR') {
996 0           my $swap = $ {$self->varref($opt)};
  0            
997 0           $ {$self->varref($opt)} = $undo_options->{$opt->[OPTNAME]};
  0            
998 0           $undo_options->{$opt->[OPTNAME]} = $swap;
999             } else {
1000 0           die "Invalid reference type for option $opt->[OPTNAME]";
1001             }
1002             }
1003             }
1004             }
1005              
1006             sub option_dialog {
1007 0     0 1   my($self, $top, %a) = @_;
1008 0           my $button_pressed;
1009 0           $a{'-buttonpressed'} = \$button_pressed;
1010 0           $a{'-wait'} = 1;
1011 0           $self->option_editor($top, %a);
1012 0           $button_pressed;
1013             }
1014              
1015             sub option_editor {
1016 0     0 1   my($self, $top, %a) = @_;
1017 0           my $callback = delete $a{'-callback'};
1018 0           my $nosave = delete $a{'-nosave'};
1019 0           my $buttons = delete $a{'-buttons'};
1020 0   0       my $toplevel = delete $a{'-toplevel'} || 'Toplevel';
1021 0           my $pack = delete $a{'-pack'};
1022 0           my $transient = delete $a{'-transient'};
1023 0           my $use_statusbar = delete $a{'-statusbar'};
1024 0           my $wait = delete $a{'-wait'};
1025 0   0       my $string = delete $a{'-string'} || {};
1026 0 0         my $delay_page_create = (exists $a{'-delaypagecreate'}
1027             ? delete $a{'-delaypagecreate'}
1028             : 1);
1029 0           my $page = delete $a{'-page'};
1030 0           my $button_pressed;
1031 0 0         if (exists $a{'-buttonpressed'}) {
1032 0 0         if (ref $a{'-buttonpressed'} ne "SCALAR") {
1033 0           die "The value for the -buttonpressed option has to be a SCALAR reference, not a " . ref($a{'-buttonpressed'}) . "\n";
1034             }
1035 0           $button_pressed = delete $a{'-buttonpressed'};
1036             } else {
1037             # dummy
1038 0           $button_pressed = \do { my $dummy };
  0            
1039             }
1040             {
1041 0           my %defaults = ('optedit' => 'Option editor',
  0            
1042             'undo' => 'Undo',
1043             'lastsaved' => 'Last saved',
1044             'save' => 'Save',
1045             'defaults' => 'Defaults',
1046             'ok' => 'OK',
1047             'apply' => 'Apply',
1048             'cancel' => 'Cancel',
1049             'helpfor' => 'Help for:',
1050             'oksave' => 'OK',
1051             );
1052 0           for my $key (keys %defaults) {
1053 0 0         next if exists $string->{$key};
1054 0           $string->{$key} = $defaults{$key};
1055             }
1056             }
1057 0           $self->{_string} = $string;
1058              
1059 0 0         if (defined $page) {
1060 0           $self->{'raised'} = $page;
1061             }
1062              
1063             # store old values for undo
1064 0           my %undo_options;
1065             my $opt;
1066 0           foreach $opt ($self->_opt_array) {
1067 0 0         next if $opt->[OPTEXTRA]{'nogui'};
1068 0           my $ref = ref $self->varref($opt);
1069 0 0         if ($ref eq 'ARRAY') {
    0          
    0          
1070 0           @{ $undo_options{$opt->[OPTNAME]} } = @ {$self->varref($opt)};
  0            
  0            
1071             } elsif ($ref eq 'HASH') {
1072 0           %{ $undo_options{$opt->[OPTNAME]} } = % {$self->varref($opt)};
  0            
  0            
1073             } elsif ($ref eq 'SCALAR') {
1074 0           $undo_options{$opt->[OPTNAME]} = $ {$self->varref($opt)};
  0            
1075             } else {
1076 0           die "Invalid reference type for option $opt->[OPTNAME]";
1077             }
1078             }
1079              
1080 0           require Tk;
1081              
1082 0           my $dont_use_notebook = 1;
1083 0           foreach $opt (@{$self->{'opttable'}}) {
  0            
1084 0 0         if (ref $opt ne 'ARRAY') { # found header
1085 0           undef $dont_use_notebook;
1086 0           last;
1087             }
1088             }
1089 0 0         if (!$dont_use_notebook) {
1090 0           eval { require Tk::NoteBook };
  0            
1091 0 0         $dont_use_notebook = 1 if $@;
1092             }
1093              
1094 0           my $dont_use_balloon;
1095 0           eval { require Tk::Balloon };
  0            
1096 0 0         $dont_use_balloon = 1 if $@;
1097              
1098 0           my $cmd = '$top->' . $toplevel . '(%a)';
1099 0           my $opt_editor = eval $cmd;
1100 0 0         die "$@ while evaling $cmd" if $@;
1101 0 0         $opt_editor->transient($transient) if $transient;
1102 0           eval { $opt_editor->configure(-title => $string->{optedit}) };
  0            
1103              
1104 0 0         my $opt_notebook = ($dont_use_notebook ?
1105             $opt_editor->Frame :
1106             $opt_editor->NoteBook(-ipadx => 6, -ipady => 6));
1107 0           $self->{Frame} = $opt_notebook;
1108              
1109 0           my($statusbar, $balloon);
1110 0 0         if (!$dont_use_balloon) {
1111 0 0         if ($use_statusbar) {
1112 0           $statusbar = $opt_editor->Label;
1113             }
1114 0 0         $balloon = $opt_notebook->Balloon($use_statusbar
1115             ? (-statusbar => $statusbar)
1116             : ());
1117             }
1118              
1119 0           my $optlist = {};
1120 0           my $msglist = {};
1121 0           my $current_top;
1122 0 0         if ($dont_use_notebook) {
1123 0           $current_top = $string->{'optedit'};
1124 0           foreach $opt ($self->_opt_array) {
1125 0 0         push(@{$optlist->{$current_top}}, $opt)
  0            
1126             if !$opt->[OPTEXTRA]{'nogui'};
1127             }
1128             # XXX message missing
1129 0           $self->_create_page($opt_notebook, undef, $current_top,
1130             $optlist, $balloon);
1131             } else {
1132 0           my @opttable = @{$self->{'opttable'}};
  0            
1133 0 0         unshift(@opttable, $string->{'optedit'})
1134             if ref $opttable[OPTNAME] eq 'ARRAY'; # put head
1135              
1136 0           my $page_create_page;
1137 0           foreach $opt (@opttable) {
1138 0 0 0       if (ref $opt ne 'ARRAY') {
    0          
1139 0 0 0       if (!$delay_page_create && $page_create_page) {
1140 0           $page_create_page->();
1141 0           undef $page_create_page;
1142             }
1143              
1144 0           my $label = $opt;
1145 0           $current_top = lc($label);
1146 0           my $c = $current_top;
1147 0           $optlist->{$c} = [];
1148 0           $msglist->{$c} = "";
1149 0           my $page_f;
1150             $page_create_page = sub {
1151 0     0     $self->_create_page
1152             ($page_f,
1153             $opt_notebook, $c,
1154             $optlist, $balloon, $msglist);
1155 0           };
1156 0 0         $page_f = $opt_notebook->add
1157             ($c,
1158             -label => $label,
1159             -anchor => 'w',
1160             ($delay_page_create?(-createcmd => $page_create_page):()),
1161             );
1162             } elsif ($opt->[OPTNAME] eq '' && !_is_separator($opt)) {
1163 0           $msglist->{$current_top} = $opt->[DEFVAL];
1164             } else {
1165 0 0         push @{$optlist->{$current_top}}, $opt
  0            
1166             if !$opt->[OPTEXTRA]{'nogui'};
1167             }
1168             }
1169 0 0 0       if (!$delay_page_create && $page_create_page) {
1170 0           $page_create_page->();
1171 0           undef $page_create_page;
1172             }
1173              
1174             }
1175              
1176 0           require Tk::Tiler;
1177 0           my $f;
1178             $f = $opt_editor->Tiler
1179             (-rows => 1,
1180             -columns => 1,
1181             -yscrollcommand => sub {
1182 0     0     my $bw = $f->cget(-highlightthickness);
1183 0 0         return if (!$f->{Sw});
1184 0           my $nenner = int(($f->Width-2*$bw)/$f->{Sw});
1185 0 0         return if (!$nenner);
1186 0           my $rows = @{$f->{Slaves}}/$nenner;
  0            
1187 0 0 0       return if (!$rows or !int($rows));
1188 0 0         if ($rows/int($rows) > 0) {
1189 0           $rows = int($rows)+1;
1190             }
1191 0           $f->GeometryRequest($f->Width,
1192             2*$bw+$rows*$f->{Sh});
1193 0           });
1194             $f->bind('' => sub {
1195 0 0   0     if ($f->y + $f->height > $opt_editor->height) {
1196 0           $opt_editor->geometry($opt_editor->width .
1197             "x" .
1198             ($f->height+$f->y));
1199             }
1200 0           });
1201 0           my @tiler_b;
1202              
1203             my %allowed_button;
1204 0 0         if ($buttons) {
1205 0 0         if (ref $buttons ne 'ARRAY') {
1206 0           undef $buttons;
1207             } else {
1208 0           %allowed_button = map { ($_ => 1) } @$buttons;
  0            
1209             }
1210             }
1211              
1212 0 0 0       if (!$buttons || $allowed_button{'ok'}) {
1213             my $ok_button
1214             = $f->Button(-text => $string->{'ok'},
1215             -underline => 0,
1216             -command => sub {
1217 0     0     $self->process_options(\%undo_options, 1);
1218 0 0         if (!$dont_use_notebook) {
1219 0           $self->{'raised'} = $opt_notebook->raised();
1220             }
1221 0           $opt_editor->destroy;
1222 0           $$button_pressed = 'ok';
1223             }
1224 0           );
1225 0           push @tiler_b, $ok_button;
1226             }
1227              
1228 0 0         if ($allowed_button{'oksave'}) {
1229             my $ok_button
1230             = $f->Button(-text => $string->{'oksave'},
1231             -underline => 0,
1232             -command => sub {
1233 0     0     $top->Busy;
1234 0           eval {
1235 0           $self->save_options;
1236 0           $self->process_options(\%undo_options, 1);
1237 0 0         if (!$dont_use_notebook) {
1238 0           $self->{'raised'} = $opt_notebook->raised();
1239             }
1240             };
1241 0           my $err = $@;
1242 0           $top->Unbusy;
1243 0 0         if ($err) {
1244 0           $self->my_die($err, 'safe');
1245             }
1246 0           $opt_editor->destroy;
1247 0           $$button_pressed = 'ok';
1248             }
1249 0           );
1250 0           push @tiler_b, $ok_button;
1251             }
1252              
1253 0 0 0       if (!$buttons || $allowed_button{'apply'}) {
1254             my $apply_button
1255             = $f->Button(-text => $string->{'apply'},
1256             -command => sub {
1257 0     0     $self->process_options(\%undo_options, 1);
1258             }
1259 0           );
1260 0           push @tiler_b, $apply_button;
1261             }
1262            
1263 0           my $cancel_button;
1264 0 0 0       if (!$buttons || $allowed_button{'cancel'}) {
1265             $cancel_button
1266             = $f->Button(-text => $string->{'cancel'},
1267             -command => sub {
1268 0     0     $self->_do_undo(\%undo_options);
1269 0 0         if (!$dont_use_notebook) {
1270 0           $self->{'raised'} = $opt_notebook->raised();
1271             }
1272 0           $opt_editor->destroy;
1273 0           $$button_pressed = 'cancel';
1274             }
1275 0           );
1276 0           push @tiler_b, $cancel_button;
1277             }
1278              
1279 0 0 0       if (!$buttons || $allowed_button{'undo'}) {
1280             my $undo_button
1281             = $f->Button(-text => $string->{'undo'},
1282             -command => sub {
1283 0     0     $self->_do_undo(\%undo_options);
1284             }
1285 0           );
1286 0           push @tiler_b, $undo_button;
1287             }
1288              
1289 0 0         if ($self->{'filename'}) {
1290 0 0 0       if (!$buttons || $allowed_button{'lastsaved'}) {
1291             my $lastsaved_button
1292             = $f->Button(-text => $string->{'lastsaved'},
1293             -command => sub {
1294 0     0     $top->Busy;
1295 0           $self->load_options;
1296 0           $top->Unbusy;
1297             }
1298 0           );
1299 0           push @tiler_b, $lastsaved_button;
1300             }
1301              
1302 0 0 0       if (!$nosave && (!$buttons || $allowed_button{'save'})) {
      0        
1303 0           my $save_button;
1304             $save_button
1305             = $f->Button(-text => $string->{'save'},
1306             -command => sub {
1307 0     0     $top->Busy;
1308 0           eval { $self->save_options };
  0            
1309 0 0         if ($@ =~ /No Data::Dumper/) {
1310 0           $save_button->configure(-state => 'disabled');
1311             }
1312 0           $top->Unbusy;
1313             }
1314 0           );
1315 0           push @tiler_b, $save_button;
1316             }
1317             }
1318              
1319 0 0 0       if (!$buttons || $allowed_button{'defaults'}) {
1320             my $def_button
1321             = $f->Button(-text => $string->{'defaults'},
1322             -command => sub {
1323 0     0     $self->set_defaults;
1324             }
1325 0           );
1326 0           push @tiler_b, $def_button;
1327             }
1328              
1329 0           $f->Manage(@tiler_b);
1330              
1331 0 0         &$callback($self, $opt_editor) if $callback;
1332              
1333 0 0 0       if (!$dont_use_notebook && defined $self->{'raised'}) {
1334 0           $self->raise_page($self->{'raised'});
1335             }
1336              
1337 0     0     $opt_editor->bind('' => sub { $cancel_button->invoke });
  0            
1338              
1339 0           $f->pack(-fill => 'x', -side => "bottom");
1340 0           $opt_notebook->pack(-expand => 1, -fill => 'both');
1341 0 0         if (defined $statusbar) {
1342 0           $statusbar->pack(-fill => 'x', -anchor => 'w');
1343             }
1344              
1345 0 0         if ($opt_editor->can('Popup')) {
1346 0           $opt_editor->withdraw;
1347 0           $opt_editor->Popup;
1348             }
1349 0 0         if ($wait) {
1350 0 0         if ($pack) {
1351 0           $opt_editor->pack(@$pack);
1352             }
1353 0           my $wait_var = 1;
1354 0     0     $opt_editor->OnDestroy(sub { undef $wait_var });
  0            
1355 0 0         $opt_editor->waitVisibility unless $opt_editor->ismapped;
1356 0           $opt_editor->grab;
1357 0           $opt_editor->waitVariable(\$wait_var);
1358             }
1359              
1360 0           $opt_editor;
1361             }
1362              
1363             sub _create_checkmarks {
1364 0     0     my $w = shift;
1365              
1366 0 0         $CHECKMARK_ON = $w->Photo(-data => <
1367             R0lGODdhDgAOAIAAAP///1FR+ywAAAAADgAOAAACM4SPFplGIXy0yDQK4aNFZAIlhI8QEQkUACKC
1368             4EMERFAI3yg+wb+ICEQjMeGjRWQahfCxAAA7
1369             EOF
1370             unless $CHECKMARK_ON;
1371 0 0         $CHECKMARK_OFF = $w->Photo(-data => <
1372             R0lGODdhDgAOAIAAAAAAAP///ywAAAAADgAOAAACDYyPqcvtD6OctNqrSAEAOw==
1373             EOF
1374             unless $CHECKMARK_OFF;
1375             }
1376              
1377             sub _get_browse_args {
1378 0     0     my $w = shift;
1379 0 0         if (!defined $FILE_IMAGE) {
1380 0           require Tk::Pixmap;
1381 0           $FILE_IMAGE = $w->Pixmap(-file => Tk->findINC("openfolder.xpm"));
1382 0 0         $FILE_IMAGE = 0 if (!$FILE_IMAGE);
1383             }
1384 0 0         if ($FILE_IMAGE) {
1385 0           (-image => $FILE_IMAGE);
1386             } else {
1387 0           (-text => "Browse...");
1388             }
1389             }
1390              
1391             sub _get_curr_geometry_args {
1392 0     0     my $w = shift;
1393 0 0         if (!defined $CURR_GEOMETRY_IMAGE) {
1394 0           require Tk::Photo;
1395 0           $CURR_GEOMETRY_IMAGE = $w->Photo(-file => Tk->findINC("win.xbm"));
1396 0 0         $CURR_GEOMETRY_IMAGE = 0 if (!$CURR_GEOMETRY_IMAGE);
1397             }
1398 0 0         if ($CURR_GEOMETRY_IMAGE) {
1399 0           (-image => $CURR_GEOMETRY_IMAGE);
1400             } else {
1401 0           (-text => "Geom.");
1402             }
1403             }
1404              
1405             sub raise_page {
1406 0     0 0   my($self, $page) = @_;
1407 0           my $opt_notebook = $self->{Frame};
1408 0           $page = lc $page; # always lowercase in NoteBook internals
1409 0           $opt_notebook->raise($page);
1410             }
1411              
1412             1;
1413              
1414             __END__