File Coverage

blib/lib/Tk/DynaTabFrame.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #234567890123456789012345678901234567890123456789012345678901234567890
2             package Tk::DynaTabFrame;
3              
4             require 5.008;
5              
6 1     1   3881 use Tk;
  0            
  0            
7             use Tk ':variables';
8             use Tk::Balloon;
9              
10             use base qw (Tk::Derived Tk::Frame);
11             use vars qw ($VERSION);
12             use strict;
13             use Carp;
14              
15             $VERSION = '0.23';
16             #
17             # indexes of our tab properties
18             #
19             use constant DTF_IDX_WIDGET => 0;
20             use constant DTF_IDX_CAPTION => 1;
21             use constant DTF_IDX_COLOR => 2;
22             use constant DTF_IDX_ROW => 3;
23             use constant DTF_IDX_COL => 4;
24             use constant DTF_IDX_FRAME => 5;
25             use constant DTF_IDX_WIDTH => 6;
26             use constant DTF_IDX_HEIGHT => 7;
27             use constant DTF_IDX_RAISECOLOR => 8;
28             use constant DTF_IDX_RAISECMD => 9;
29             use constant DTF_IDX_LABEL => 10;
30             use constant DTF_IDX_FLASH_COLOR => 11;
31             use constant DTF_IDX_FLASH_INTVL => 12;
32             use constant DTF_IDX_FLASH_TIME => 13;
33             use constant DTF_IDX_FLASH_ID => 14;
34             use constant DTF_IDX_FLASHED => 15;
35             use constant DTF_IDX_HIDDEN => 16;
36             use constant DTF_IDX_TABTIP => 17;
37              
38             my $close_xpm = << 'end-of-close-xpm';
39             /* XPM */
40             static char * close_xpm[] = {
41             "8 8 3 1",
42             " s None c None",
43             ". c #000000000000",
44             "X c #E0E0FFFFE0E0",
45             ".. ..",
46             " .. .. ",
47             " .... ",
48             " .. ",
49             " .. ",
50             " .... ",
51             " .. .. ",
52             ".. .."};
53              
54             end-of-close-xpm
55              
56             my $close_xpm_6 = << 'end-of-close-xpm-6';
57             /* XPM */
58             static char * close_xpm[] = {
59             "6 6 3 1",
60             " s None c None",
61             ". c #000000000000",
62             "X c #E0E0FFFFE0E0",
63             ".. ..",
64             " .... ",
65             " .. ",
66             " .. ",
67             " .... ",
68             ".. ..",};
69              
70             end-of-close-xpm-6
71              
72             #
73             # map of tabframe directives based on
74             # tab orientation
75             #
76             use constant DTF_TAB_TABSIDE => 0;
77             use constant DTF_TAB_TABFILL => 1;
78             use constant DTF_TAB_TABANCHOR => 2;
79             use constant DTF_TAB_TABSIZER => 3;
80             use constant DTF_TAB_CLIENTSIDE => 4;
81              
82             my %tabalign = (
83             # tabframe -side, tabframe -fill, tabframe -anchor,
84             # tabframe sizing option, clientframe -side
85             nw => [ 'top', 'x', 'nw', '-height', 'bottom'],
86             ne => [ 'top', 'x', 'ne', '-height', 'bottom'],
87             sw => [ 'bottom', 'x', 'sw', '-height', 'top'],
88             se => [ 'bottom', 'x', 'se', '-height', 'top'],
89             en => [ 'right', 'y', 'ne', '-width', 'left'],
90             es => [ 'right', 'y', 'se', '-width', 'left'],
91             wn => [ 'left', 'y', 'nw', '-width', 'right'],
92             ws => [ 'left', 'y', 'sw', '-width', 'right'],
93             );
94              
95             #
96             # bitmaps for border requirements
97             # based on tab orientations
98             # bit 0 => Left
99             # bit 1 => Right
100             # bit 2 => Top
101             # bit 3 => Bottom
102             # 'p' sides are for pseudo tabs
103             #
104             my %borders = qw(
105             nw 7
106             ne 7
107             sw 11
108             se 11
109             en 14
110             es 14
111             wn 13
112             ws 13
113             pnw 6
114             pne 5
115             psw 10
116             pse 9
117             pen 10
118             pes 6
119             pwn 9
120             pws 5
121             );
122             #
123             # pack directives for the tab contents
124             # [ closebtn_side,
125             # closebtn_anchor,
126             # button_side,
127             # button_anchor,
128             # pseudo_anchor
129             # tab direction
130             # ]
131             #
132             use constant DTF_ORIENT_CLOSESIDE => 0;
133             use constant DTF_ORIENT_CLOSEANCHOR => 1;
134             use constant DTF_ORIENT_BTNSIDE => 2;
135             use constant DTF_ORIENT_BTNANCHOR => 3;
136             use constant DTF_ORIENT_PSEUDOANCHOR => 4;
137             use constant DTF_ORIENT_DIRECTION => 5;
138             use constant DTF_ORIENT_ALIGN => 6;
139             use constant DTF_ORIENT_DOWNWARD => 7;
140              
141             use constant DTF_MAX_ROWS => 20;
142             use constant DTF_DFLT_TIPTIME => 450;
143              
144             my %taborient = (
145             'nw', [ 'right', 'ne', 'right', 's', 'e', 1, 0, 1, ],
146             'ne', [ 'right', 'ne', 'right', 's', 'w', 1, 0, 1, ],
147             'sw', [ 'right', 'se', 'right', 'n', 'e', 1, 1, 0, ],
148             'se', [ 'right', 'se', 'right', 'n', 'w', 1, 1, 0, ],
149             'en', [ 'top', 'ne', 'top', 's', 's', 0, 0, 0, ],
150             'es', [ 'top', 'ne', 'top', 's', 'n', 0, 0, 0, ],
151             'wn', [ 'top', 'nw', 'top', 's', 's', 0, 1, 1, ],
152             'ws', [ 'top', 'nw', 'top', 's', 'n', 0, 1, 1, ],
153             );
154             #
155             # page options for Tk::Notebook compatibility
156             #
157             my %page_opts = (
158             '-tabcolor', DTF_IDX_COLOR,
159             '-raisecolor', DTF_IDX_RAISECOLOR,
160             '-image', '-image',
161             '-label', DTF_IDX_LABEL,
162             '-raisecmd', DTF_IDX_RAISECMD,
163             '-state', '-state',
164             '-tabtip', DTF_IDX_TABTIP,
165             '-hidden', DTF_IDX_HIDDEN
166             );
167              
168             Tk::Widget->Construct ('DynaTabFrame');
169              
170             sub Populate {
171             my $this = shift;
172             $this->ConfigSpecs(
173             '-borderwidth' => [['SELF', 'PASSIVE'],
174             'borderwidth', 'BorderWidth', '1'],
175             '-tabcurve' => [['SELF', 'PASSIVE'],
176             'tabcurve', 'TabCurve', 2],
177             '-padx' => [['SELF', 'PASSIVE'], 'padx', 'padx', 5],
178             '-pady' => [['SELF', 'PASSIVE'], 'pady', 'pady', 5],
179             #
180             # for Tk::Notebook compatibility
181             #
182             '-tabpadx' => [['SELF', 'PASSIVE'], 'tabpadx', 'tabpadx', 2],
183             '-tabpady' => [['SELF', 'PASSIVE'], 'tabpady', 'tabpady', 2],
184             '-font' => ['METHOD', 'font', 'Font', undef],
185             '-current' => ['METHOD'],
186             '-raised' => ['METHOD'],
187             '-raised_name' => ['METHOD'],
188             '-tabs' => ['METHOD'],
189             '-delay' => [['SELF', 'PASSIVE'], 'delay', 'Delay', '200'],
190             '-raisecmd' => [['SELF', 'PASSIVE'], 'raisecmd', 'RaiseCmd', undef],
191             '-tablock' => [['SELF', 'PASSIVE'], 'tablock', 'tablock', undef],
192             '-tabrotate' => [['SELF', 'PASSIVE'], 'tabrotate', 'tabrotate', 1],
193             '-tabside' => ['METHOD'],
194             '-tabclose' => ['METHOD'],
195             '-tabscroll' => ['METHOD'],
196             '-tabcolor' => [['SELF', 'PASSIVE'], 'tabcolor', 'tabcolor', undef],
197             '-tabtip' => [['SELF', 'PASSIVE'], 'tabtip', 'tabtip', undef],
198             '-tiptime' => ['METHOD'],
199             '-tipcolor' => ['METHOD'],
200             '-textalign' => [['SELF', 'PASSIVE'], 'textalign', 'textalign', 1],
201             '-backpagecolor' => [['SELF', 'PASSIVE'], 'tabcolor', 'tabcolor', undef],
202             '-raisecolor' => [['SELF', 'PASSIVE'], 'raisecolor', 'raisecolor', undef],
203             );
204              
205             $this->SUPER::Populate(@_);
206             #
207             # set default tab orientation
208             #
209             $this->{Side} = 'nw';
210             $this->{_tiptime} = DTF_DFLT_TIPTIME;
211             $this->{_tipcolor} = 'white';
212             #
213             # get tab alignment info
214             #
215             my ($tabside, $tabfill, $tabanchor, $tabsize, $clientside) =
216             @{$tabalign{$this->{Side}}};
217             #
218             # ButtonFrame is where the tabs are
219             #
220             my $ButtonFrame = $this->{ButtonFrame} = $this->Component(
221             'Frame' => 'ButtonFrame',
222             -borderwidth => 0,
223             -relief => 'flat',
224             $tabsize => 40,
225             )->pack(
226             -anchor => $tabanchor,
227             -side => $tabside,
228             -fill => $tabfill,
229             );
230             #
231             # create the frame we return to the app
232             #
233             my $ClientFrame = $this->{ClientFrame} = $this->Component(
234             'Frame' => 'TabChildFrame',
235             -relief => 'flat',
236             -borderwidth => 0,
237             -height => 60,
238             )->pack(
239             -side => $clientside,
240             -expand => 1,
241             -fill => 'both',
242             );
243             #
244             # a pseudo-frame used to make the raised tab smoothly connect
245             # to the client frame
246             #
247             my $Connector = $this->Component(
248             'Frame' => 'Connector',
249             -relief => 'flat'
250             );
251             #
252             # list of all our current clients
253             #
254             $this->{ClientList} = [ ];
255             #
256             # a quick lookup by caption
257             #
258             $this->{ClientHash} = {};
259             #
260             # a quick lookup of row numbers
261             # so a raise() can just move entire rows around
262             # create first empty row
263             #
264             $this->{RowList} = [ [] ];
265             #
266             # plug into the configure event so we get resizes
267             #
268             $this->{OldWidth} = $ButtonFrame->reqwidth();
269             $this->{OldHeight} = $ButtonFrame->reqheight();
270             $this->bind("" => sub { $this->ConfigDebounce; });
271             #
272             # generate the close button
273             #
274             my $scrwd = $this->screenwidth;
275             $this->{CloseImage} = $this->Pixmap(-data => (($scrwd <= 1024) ? $close_xpm_6 : $close_xpm));
276             return $this;
277             }
278              
279             sub ConfigDebounce {
280             my ($this) = @_;
281             my $w = $Tk::event->w;
282             my $h = $Tk::event->h;
283             #
284             # only post event if we've changed width/height
285             #
286             return 1 if (($this->{OldWidth} == $w) && ($this->{OldHeight} == $h));
287              
288             $this->{LastConfig} = Tk::timeofday;
289             $this->{LastWidth} = $w;
290             $this->{LastHeight} = $h;
291              
292             $this->afterCancel($this->{LastAfterID})
293             if defined($this->{LastAfterID});
294              
295             $this->{LastAfterID} = $this->after(200, # $this->cget('-delay'),
296             sub {
297             $this->TabReconfig();
298             delete $this->{LastAfterID};
299             }
300             );
301             1;
302             }
303              
304             sub TabCreate {
305             my ($this, $Caption, $Color, $RaiseColor, $Image, $Text, $RaiseCmd) = @_;
306             #
307             # always add at (0,0)
308             #
309             my $clients = $this->{ClientList};
310             my $rows = $this->{RowList};
311             my $ButtonFrame = $this->{ButtonFrame};
312             #
313             # create some pseudo tabs
314             # note we only create DTF_MAX_ROWS, so if we have > DTF_MAX_ROWS+1
315             # tab rows, we're in trouble
316             #
317             unless ($this->{PseudoTabs}) {
318             $this->{PseudoTabs} = [ undef ];
319             push @{$this->{PseudoTabs}}, $this->PseudoCreate($_)
320             foreach (1..DTF_MAX_ROWS);
321             }
322             #
323             # create default colors
324             #
325             $this->configure(-tabcolor =>
326             $this->Darken($this->cget(-background), 75))
327             unless $this->cget(-tabcolor);
328             $this->configure(-raisecolor =>
329             $this->Darken($this->cget(-tabcolor), 130))
330             unless $this->cget(-raisecolor);
331              
332             $RaiseColor = $Color ? $this->Darken($Color, 130) :
333             $this->cget(-raisecolor)
334             unless $RaiseColor;
335              
336             $Color = $this->cget(-tabcolor)
337             unless $Color;
338            
339             $RaiseCmd = $this->cget(-raisecmd)
340             unless $RaiseCmd;
341             #
342             # create a new frame for the caller
343             #
344             my $Widget = $this->{ClientFrame}->Frame(
345             -borderwidth => $this->cget ('-borderwidth'),
346             -relief => 'raised');
347             #
348             # create a new frame for a tab
349             #
350             my $TabFrame = $ButtonFrame->Component(
351             'Frame' => 'Button_'.$Widget,
352             -foreground => $this->cget ('-foreground'),
353             -relief => 'flat',
354             -borderwidth => 0,
355             );
356             $TabFrame->configure(-bg => $Color)
357             if $Color;
358              
359             my $to = $taborient{$this->{Side}};
360             #
361             # build a close tab for it if requested
362             #
363             my $CloseBtn;
364             $CloseBtn = $TabFrame->Component(
365             'Button' => 'CloseBtn',
366             -command => [ $this->{Close}, $this, $Caption ],
367             -anchor => $to->[DTF_ORIENT_CLOSEANCHOR],
368             -relief => 'raised',
369             -borderwidth => 1,
370             -takefocus => 1,
371             -padx => 0,
372             -pady => 0,
373             -image => $this->{CloseImage}
374             )
375             if $this->{Close};
376             #
377             # build the tab for it; in future we may support images
378             #
379             my $font = $this->cget(-font);
380             $font = $this->parent()->cget(-font) unless $font;
381             my $padx = $this->cget(-tabpadx);
382             my $pady = $this->cget(-tabpady);
383             my $Button = $TabFrame->Component(
384             'Button' => 'Button',
385             -command => sub { $this->configure (-current => $Widget);},
386             -anchor => 'center',
387             -relief => 'flat',
388             -borderwidth => 0,
389             -takefocus => 1,
390             -padx => 1,
391             -pady => 1,
392             );
393              
394             $Button->configure(-bg => $Color);
395             if (defined($Image)) {
396             $Button->configure(-image => $Image);
397             }
398             else {
399             $Button->configure(-font => $font) if $font;
400             $Text = $Caption ||= $Widget->name()
401             unless $Text;
402             #
403             # make it vertical for side tabs
404             #
405             $Button->configure(-text =>
406             ($this->cget(-textalign) ?
407             ($to->[DTF_ORIENT_DIRECTION] ? $Text : RotateTabText($Text)) :
408             ($to->[DTF_ORIENT_DIRECTION] ? RotateTabText($Text) : $Text))
409             );
410             }
411              
412             $TabFrame->bind('' => sub {$Button->invoke();});
413             #
414             # someday we'll have to figure out how to configure these
415             # events so rolling into a tab brightens and roling out
416             # darkens...wo/ repeating the event ad nauseum...
417             #
418             # $TabFrame->bind('', [ \&OnEnter, $Button, $TabFrame ]);
419             # $TabFrame->bind('', [ \&OnLeave, $Button, $TabFrame ]);
420              
421             # $Button->bind('', [ \&OnEnter, $Button, $TabFrame ]);
422             # $Button->bind('', [ \&OnLeave, $Button, $TabFrame ]);
423              
424             # $Button->bind('', [ \&OnFocusOut, $TabFrame ]);
425             # $Button->bind('', [ \&OnFocusIn, $TabFrame ]);
426             $Button->bind('',
427             sub {($this->children())[0]->focus();});
428             $Button->bind ('' => sub {$Button->invoke();});
429             #
430             # decorate the tab
431             #
432             $this->TabBorder ($TabFrame);
433              
434             my $dark = $Button->Darken($Color, 50);
435             $Button->configure(-highlightthickness => 0,
436             -activebackground => $dark);
437              
438             $CloseBtn->pack(
439             -side => $to->[DTF_ORIENT_CLOSESIDE],
440             -anchor => $to->[DTF_ORIENT_CLOSEANCHOR],
441             -expand => 0,
442             -fill => 'none',
443             -ipadx => 0,
444             -ipady => 0,
445             -padx => 2,
446             -pady => 2,
447             )
448             if $CloseBtn;
449              
450             $Button->pack(
451             -side => $to->[DTF_ORIENT_BTNSIDE],
452             -anchor => $to->[DTF_ORIENT_BTNANCHOR],
453             -expand => 0,
454             -fill => 'none',
455             -ipadx => 0,
456             -ipady => 0,
457             -padx => $padx,
458             -pady => $pady,
459             );
460             #
461             # pack tab in our rowframe 0; redraw if needed
462             # move everything over 1 column in bottom row
463             #
464             $clients->[$_][DTF_IDX_COL]++
465             foreach (@{$rows->[0]});
466             unshift @{$rows->[0]}, scalar @$clients;
467             #
468             # save the client frame, the caption, the tabcolor,
469             # the current row/column coords of our tab, our tabframe,
470             # and the original height of this tab; we'll be stretching
471             # the button later during redraws
472             #
473             push @$clients, [
474             $Widget, # our client frame
475             $Caption, # our identifier
476             $Color, # unraised color
477             0, # row number
478             0, # column number
479             $TabFrame, # our tab frame
480             ($to->[DTF_ORIENT_DIRECTION] ? # the tab width
481             $Button->reqwidth() : $Button->reqheight() ),
482             ($to->[DTF_ORIENT_DIRECTION] ? # the tab height
483             $Button->reqheight() : $Button->reqwidth() ),
484             $RaiseColor, # raised color
485             $RaiseCmd, # callback for raise operations
486             $Text, # tab label text
487             1 # start as visible
488             ];
489             #
490             # reqwidht/height don't seem to include the padx/pady
491             #
492             $$clients[-1][DTF_IDX_HEIGHT] += (2 *
493             ($to->[DTF_ORIENT_DIRECTION] ? $pady : $padx));
494             $$clients[-1][DTF_IDX_WIDTH] += (2 *
495             ($to->[DTF_ORIENT_DIRECTION] ? $padx : $pady));
496             #
497             # map the caption to its position in the client list,
498             # so we can raise and delete it by reference
499             #
500             $this->{ClientHash}{$Caption} = $#$clients;
501             #
502             # redraw everything
503             #
504             $this->TabRedraw(1);
505             #
506             # and raise us
507             #
508             $this->TabRaise($Widget);
509             return $Widget;
510             }
511              
512             sub PseudoCreate {
513             my ($this, $row) = @_;
514             #
515             # create a new frame for a pseudotab
516             #
517             my $TabFrame = $this->{ButtonFrame}->Component(
518             'Frame' => "Pseudo_$row",
519             -foreground => $this->cget ('-foreground'),
520             -relief => 'flat',
521             -borderwidth => 0,
522             );
523              
524             $TabFrame->Component(
525             'Label' => 'Button',
526             -text => ' ',
527             -anchor => $taborient{$this->{Side}}[DTF_ORIENT_PSEUDOANCHOR],
528             -relief => 'flat',
529             -borderwidth => 0,
530             -padx => 2,
531             -pady => 2,
532             )->pack(
533             -expand => 0,
534             -fill => 'both',
535             -ipadx => 0,
536             -ipady => 0,
537             -padx => 3,
538             -pady => 3,
539             );
540             #
541             # decorate the tab
542             #
543             $this->TabBorder ($TabFrame, 1);
544              
545             return $TabFrame;
546             }
547              
548             sub TabRaise {
549             my ($this, $Widget, $silent) = (shift, @_);
550             #
551             # locate the tab row
552             # if its not the first row, then we need to move rows around
553             # and redraw
554             # else just raise it
555             #
556             my $ButtonFrame = $this->{ButtonFrame};
557             my $TabFrame = $ButtonFrame->Subwidget('Button_'.$Widget);
558             my $rotate = $this->cget(-tabrotate);
559             #
560             # find our client
561             #
562             my $clients = $this->{ClientList};
563             #
564             # strange timing issue sometimes leaves a null
565             # entry at our tail
566             #
567             pop @$clients unless defined($clients->[-1]);
568              
569             my $client;
570             #
571             # locate the currently raised tab and restore its
572             # unraised color
573             #
574             foreach $client (@$clients) {
575             last unless $this->{Raised};
576             next unless ($client->[DTF_IDX_WIDGET] eq $this->{Raised});
577             my $Button = $client->[DTF_IDX_FRAME]->Subwidget('Button');
578             $client->[DTF_IDX_FRAME]->configure(-bg => $client->[DTF_IDX_COLOR]);
579             $Button->configure(-bg => $client->[DTF_IDX_COLOR],
580             -activebackground => $client->[DTF_IDX_COLOR],
581             );
582             last;
583             }
584              
585             my $raised = 0;
586             $raised++
587             while (($raised <= $#$clients) &&
588             ($clients->[$raised][DTF_IDX_WIDGET] ne $Widget));
589            
590             return 1 unless ($raised <= $#$clients);
591             $client = $clients->[$raised];
592             my ($r, $c) = ($client->[DTF_IDX_ROW], $client->[DTF_IDX_COL]);
593             my $rows = $this->{RowList};
594             #
595             # undraw the Connector
596             #
597             my $Connector = $this->Subwidget('Connector');
598             $Connector->placeForget(); # if $Connector->is_mapped;
599             delete $this->{Raised};
600              
601             if ($rotate) {
602             #
603             # 3 cases:
604             # we're already at row 0, so just raise
605             # else rotate rows off bottom to top until
606             # raised row is bottom row
607             #
608             if ($r != 0) {
609             #
610             # middle row, or last row that fills the frame:
611             # move the preceding to top, and move the selected row
612             # to the bottom
613             #
614             my $rowcnt = $r;
615             push(@$rows, (shift @$rows)),
616             $rowcnt--
617             while ($rowcnt);
618             #
619             # update client coords
620             #
621             foreach my $i (0..$#$rows) {
622             $clients->[$_][DTF_IDX_ROW] = $i
623             foreach (@{$rows->[$i]});
624             }
625             $this->TabRedraw;
626             }
627             #
628             # first, lower everything below the raised tab
629             # in row 0
630             #
631             my $lowest = $raised;
632             my $pseudos = $this->{PseudoTabs};
633             foreach (@{$rows->[0]}) {
634             $clients->[$_][DTF_IDX_FRAME]->lower($clients->[$lowest][DTF_IDX_FRAME]),
635             $lowest = $_
636             unless ($_ == $raised);
637             }
638             #
639             # now lower everything below its left neighbor
640             #
641             foreach my $i (1..$#$rows) {
642             $clients->[$_][DTF_IDX_FRAME]->lower($clients->[$lowest][DTF_IDX_FRAME]),
643             $lowest = $_
644             foreach (@{$rows->[$i]});
645             }
646             #
647             # now make all pseudos lower
648             #
649             if ($#$rows > 0) {
650             $pseudos->[1]->lower($clients->[$lowest][DTF_IDX_FRAME]);
651             $pseudos->[$_]->lower($pseudos->[$_-1])
652             foreach (2..$#$rows);
653             }
654             $TabFrame->Subwidget('Button')->raise();
655             $TabFrame->raise();
656             } # if rotate
657             $TabFrame->Subwidget('Button')->focus();
658             #
659             # lower the current frame, and then raise the new one
660             # !!!NOTE: we can't use pack() here, as it tends to
661             # expand the area of our container
662             #
663             $Widget->place(-x => 0, -y => 0, -relheight => 1.0, -relwidth => 1.0);
664             $this->{CurrentFrame} = $Widget;
665              
666             pop @$clients unless defined($clients->[-1]);
667             foreach (0..$#$clients) {
668             $clients->[$_][DTF_IDX_WIDGET]->lower($Widget)
669             if ($clients->[$_] && $clients->[$_][DTF_IDX_WIDGET] &&
670             ($clients->[$_][DTF_IDX_WIDGET] ne $Widget));
671             }
672              
673             my $raisecolor = $client->[DTF_IDX_RAISECOLOR];
674             #
675             # used to smoothly connect raised tab to client frame
676             # but only if in row 0
677             #
678             if ($client->[DTF_IDX_ROW] == 0) {
679             $this->update; # make sure the tabs are redrawn
680              
681             my ($connectx, $connectw, $connecty, $connecth);
682             my $horizontal = $taborient{$this->{Side}}[DTF_ORIENT_DIRECTION];
683             my $inside = $taborient{$this->{Side}}[DTF_ORIENT_ALIGN];
684             my $extra = $this->{Close} ? 12 : -3;
685             if ($horizontal) { #
686             $connectx = $TabFrame->x + 2;
687             $connectw = $client->[DTF_IDX_WIDTH] + $extra;
688             #
689             # Y location of connector is either at top (for nw/ne)
690             # or bottom (for sw/se) of the client frame
691             #
692             $connecty = $inside ?
693             $this->{ClientFrame}->height() - 2 : # may need to adjust this offset
694             $this->{ClientFrame}->rooty() - $this->rooty() - 7;
695             $connecth = $this->{ClientFrame}->cget(-borderwidth) + 3;
696             }
697             else { # vertical
698             #
699             # if tabs at left, then position at left edge of client
700             # else position at rt edge
701             #
702             $connectx = $inside ?
703             $this->{ClientFrame}->rootx() - $this->rootx() - 8 :
704             $this->{ClientFrame}->width() - 3; # may need to adjust this offset
705             $connectw = $this->{ClientFrame}->cget(-borderwidth) + 3;
706              
707             $connecty = $TabFrame->y + 3;
708             $connecth = $client->[DTF_IDX_WIDTH] + $extra;
709             }
710             $Connector->place(
711             -x => $connectx,
712             -y => $connecty,
713             -height => $connecth,
714             -width => $connectw,
715             -anchor => 'nw',
716             );
717              
718             $Connector->configure(-background => $raisecolor);
719             $Connector->raise();
720             } # end if raise in row 0
721              
722             $this->{Raised} = $Widget;
723             #
724             # turn off flashing
725             #
726             $this->deflash($client->[DTF_IDX_CAPTION])
727             if $client->[DTF_IDX_FLASH_ID];
728             #
729             # set raised color
730             #
731             $TabFrame->configure(-background => $raisecolor),
732             $TabFrame->Subwidget('Button')->configure(
733             -bg => $raisecolor,
734             -activebackground => $raisecolor);
735             #
736             # callback if defined && allowed
737             #
738             unless ($silent) {
739             my $raisecb = $client->[DTF_IDX_RAISECMD];
740             &$raisecb($client->[DTF_IDX_CAPTION])
741             if ($raisecb && (ref $raisecb) && (ref $raisecb eq 'CODE'));
742             }
743             return $Widget;
744             }
745             #
746             # render tab borders
747             #
748             sub TabBorder {
749             my ($this, $TabFrame, $forpseudo) = @_;
750             my $LineWidth = $this->cget(-borderwidth);
751             my $Background = $this->cget(-background);
752             my $InnerBackground = $TabFrame->Darken($Background, 120),
753             my $Curve = $this->cget (-tabcurve);
754            
755             my $mask = $forpseudo ? $borders{'p' . $this->{Side}} :
756             $borders{$this->{Side}};
757             #
758             # left border
759             # outer:
760             $TabFrame->Frame(
761             -background => 'white',
762             -borderwidth => 0,
763             )->place(
764             -x => 0,
765             -y => $Curve - 1,
766             -width => $LineWidth,
767             -relheight => 1.0,
768             ),
769             #
770             # inner:
771             $TabFrame->Frame(
772             -background => $InnerBackground,
773             -borderwidth => 0,
774             )->place(
775             -x => $LineWidth,
776             -y => $Curve - 1,
777             -width => $LineWidth,
778             -relheight => 1.0,
779             )
780             if ($mask & 1);
781             #
782             # right border
783             # outer:
784             $TabFrame->Frame(
785             -background => 'black',
786             -borderwidth => 0,
787             )->place(
788             -x => - ($LineWidth),
789             -relx => 1.0,
790             -width => $LineWidth,
791             -relheight => 1.0,
792             -y => $Curve,
793             ),
794             #
795             # inner:
796             $TabFrame->Frame(
797             -background => $TabFrame->Darken($Background, 80),
798             -borderwidth => 0,
799             )->place(
800             -x => - ($LineWidth * 2),
801             -width => $LineWidth,
802             -relheight => 1.0,
803             -y => $Curve / 2,
804             -relx => 1.0,
805             )
806             if ($mask & 2);
807             #
808             # top border
809             # outer:
810             $TabFrame->Frame(
811             -background => 'white',
812             -borderwidth => 0,
813             )->place(
814             -x => $Curve - 1,
815             -y => 0,
816             -relwidth => 1.0,
817             -height => $LineWidth,
818             -width => - ($Curve * 2),
819             ),
820             #
821             # inner:
822             $TabFrame->Frame(
823             -background => $InnerBackground,
824             -borderwidth => 0,
825             )->place(
826             -x => $Curve - 1,
827             -y => $LineWidth,
828             -relwidth => 1.0,
829             -height => $LineWidth,
830             -width => - ($Curve * 2),
831             )
832             if ($mask & 4);
833             #
834             # bottom border
835             # outer:
836             $TabFrame->Frame(
837             -background => $InnerBackground,
838             -borderwidth => 0,
839             )->place(
840             -x => $Curve - 1,
841             # -y => - ($LineWidth),
842             -rely => 1.0,
843             -relwidth => 1.0,
844             -height => $LineWidth,
845             -width => - ($Curve),
846             ),
847             #
848             # inner:
849             $TabFrame->Frame(
850             -background => 'black',
851             -borderwidth => 0,
852             -height => $LineWidth,
853             )->place(
854             -x => $Curve - 1,
855             -y => - ($LineWidth),
856             -rely => 1.0,
857             -relwidth => 1.0,
858             -height => $LineWidth,
859             -width => - ($Curve),
860             )
861             if ($mask & 8);
862             }
863              
864             sub TabCurrent {
865             return defined ($_[1]) ?
866             $_[0]->TabRaise($_[1]) :
867             $_[0]{Raised};
868             }
869             #
870             # returns the width of a row
871             #
872             sub GetButtonRowWidth {
873             my ($Width, $this, $row) = (0, shift, shift);
874              
875             return 0
876             unless ($this->{RowList} && ($#{$this->{RowList}} >= $row));
877              
878             my $rowlist = $this->{RowList}[$row];
879             my $tablist = $this->{ClientList};
880             my $extra = 5 + ($this->{Close} ? 15 : 0);
881             my $horizontal = $taborient{$this->{Side}}[DTF_ORIENT_DIRECTION];
882             foreach my $Client (@$rowlist) {
883             $Width += $extra + ($tablist->[$Client][DTF_IDX_WIDTH])
884             if defined($tablist->[$Client]);
885             }
886              
887             return $Width ? $Width - 10 : 0;
888             }
889             #
890             # returns the accumulated height of all our rows
891             #
892             sub GetButtonRowHeight {
893             my ($Height, $this, $row) = (0, shift, shift);
894              
895             return 0
896             unless ($this->{RowList} && ($#{$this->{RowList}} >= $row));
897              
898             my $total_ht = 0;
899             $total_ht += $this->GetRowHeight($_)
900             foreach (0..$row);
901             return $total_ht;
902             }
903             #
904             # returns the height of a single row
905             #
906             sub GetRowHeight {
907             my ($Height, $this, $row) = (0, shift, shift);
908             my $ButtonFrame = $this->{ButtonFrame};
909              
910             return 0
911             unless ($this->{RowList} && ($#{$this->{RowList}} >= $row));
912              
913             my $rowlist = $this->{RowList}[$row];
914             my $tablist = $this->{ClientList};
915             my $total_ht = 0;
916             my $newht = 0;
917             foreach (@$rowlist) {
918             next unless defined($tablist->[$_]);
919             $newht = $tablist->[$_][DTF_IDX_HEIGHT];
920             $Height = $newht if ($newht > $Height);
921             }
922             return $Height;
923             }
924              
925             sub Font {
926             my ($this, $Font) = (shift, @_);
927              
928             my $font = $this->{Font};
929             $font = $this->parent()->cget(-font) unless $font;
930              
931             return ($font)
932             unless (defined ($Font));
933              
934             my $tablist = $this->{ClientList};
935              
936             $_->[DTF_IDX_FRAME]->Subwidget('Button')->configure(-font => $Font)
937             foreach (@$tablist);
938             #
939             # we need to redraw, since this may change our tab layout
940             #
941             $this->TabRedraw(1);
942             return ($this->{Font} = $Font);
943             }
944             #
945             # Reconfigure the tabs on resize event
946             #
947             sub TabReconfig {
948             my $this = shift;
949             return 1
950             if ($this->{Updating} ||
951             ($this->cget(-tablock) && (! $this->cget(-tabclose))));
952             my $buttons = $this->{ButtonFrame};
953             my $clients = $this->{ClientList};
954             #
955             # if nothing to draw, then just update context
956             #
957             $this->{OldWidth} = $this->width,
958             $this->{OldHeight} = $this->height,
959             return 1
960             unless ($#$clients >= 0);
961             #
962             # compute current max row width
963             # compare to current frame width
964             # if maxrow > frame
965             # redraw
966             # elsif maxrow - frame > threshold
967             # redraw
968             #
969             my $rows = $this->{RowList};
970             my $w = $buttons->width();
971             my $h = $buttons->height();
972              
973             my $maxw = 0;
974             foreach (0..$#$rows) {
975             my $rw = $this->GetButtonRowWidth($_);
976             $maxw = $rw if ($maxw < $rw);
977             }
978             #
979             # return unless significantly different from old size
980             #
981             $this->{OldWidth} = $this->width,
982             $this->{OldHeight} = $this->height,
983             return 1
984             unless (($maxw > $w) || ($w - $maxw > 10));
985             #
986             # just redraw everything
987             #
988             $this->{Updating} = 1;
989             $this->TabRedraw(1);
990             $this->{Updating} = undef;
991             $this->{OldWidth} = $this->width;
992             $this->{OldHeight} = $this->height;
993             return 1;
994             }
995             #
996             # redraw our tabs
997             #
998             sub TabRedraw {
999             my ($this, $rearrange) = @_;
1000             #
1001             # compute new display ordering
1002             #
1003             return 1 unless ($#{$this->{ClientList}} >= 0);
1004             my $ButtonFrame = $this->{ButtonFrame};
1005             my $clients = $this->{ClientList};
1006             my $rows = $this->{RowList};
1007             #
1008             # if nothing to draw, bail out
1009             #
1010             return 1 if (($#$rows < 0) ||
1011             (($#$rows == 0) && ($#{$rows->[0]} < 0)));
1012              
1013             my $pseudos = $this->{PseudoTabs};
1014             my $pseudoht;
1015             my $Raised = $this->{Raised}; # save for later
1016             my $roww = 0;
1017             my $raised_row = undef;
1018             my $horizontal = $taborient{$this->{Side}}[DTF_ORIENT_DIRECTION];
1019             my $alignment = $taborient{$this->{Side}}[DTF_ORIENT_PSEUDOANCHOR];
1020             my $downward = $taborient{$this->{Side}}[DTF_ORIENT_DOWNWARD];
1021             my $extra = $this->{Close} ? 15 : 0;
1022             #
1023             # tabspace determined based on orientation
1024             #
1025             my $w = $horizontal ? $ButtonFrame->width() : $ButtonFrame->height();
1026             $w -= 5;
1027              
1028             if ($rearrange) {
1029             #
1030             # rearrange tabs to fit new frame width
1031             #
1032             my @newrows = ([]);
1033             my @tclients = ();
1034             foreach my $row (@$rows) {
1035             foreach (@$row) {
1036             my $client = $clients->[$_];
1037             next
1038             if $client->[DTF_IDX_HIDDEN];
1039              
1040             my $btnw = $extra + $client->[DTF_IDX_WIDTH];
1041             my $row = $#$rows;
1042              
1043             $roww = 0,
1044             push @newrows, [ ]
1045             if (($roww + $btnw > $w) && ($#{$newrows[0]} >= 0));
1046              
1047             $roww += $btnw;
1048             push @{$newrows[-1]}, $_;
1049             $tclients[$_] = [ $#newrows, $#{$newrows[-1]} ];
1050             $raised_row = $#newrows
1051             if ($Raised && $client->[DTF_IDX_WIDGET] &&
1052             ($client->[DTF_IDX_WIDGET] eq $Raised));
1053             }
1054             }
1055             #
1056             # if number of rows exceeds our limit
1057             #
1058             return undef
1059             if ($#newrows > DTF_MAX_ROWS);
1060             #
1061             # save the new row lists
1062             #
1063             $this->{RowList} = \@newrows;
1064             $rows = \@newrows;
1065             foreach my $row (@$rows) {
1066             $clients->[$_]->[DTF_IDX_ROW] = $tclients[$_][0],
1067             $clients->[$_]->[DTF_IDX_COL] = $tclients[$_][1]
1068             foreach (@$row);
1069             }
1070             }
1071             #
1072             # purge all our pseudotabs
1073             #
1074             foreach (@$pseudos) {
1075             next unless $_;
1076             $_->placeForget()
1077             if $_->ismapped();
1078             }
1079             #
1080             # undraw all our buttons
1081             #
1082             foreach my $i (0..$#$rows) {
1083             foreach (@{$rows->[$i]}) {
1084             $clients->[$_][DTF_IDX_FRAME]->placeForget()
1085             if $clients->[$_][DTF_IDX_FRAME]->ismapped();
1086             }
1087             }
1088             #
1089             # adjust our frame height to accomodate the rows
1090             #
1091             my $dim = $horizontal ? '-height' : '-width';
1092             $ButtonFrame->configure(
1093             $dim => $this->GetButtonRowHeight($#$rows) +
1094             ($downward ? 5 : 7));
1095             #
1096             # reconfig tabs to match height of tallest tab in row
1097             #
1098             my @hts = ();
1099             push @hts, $this->GetRowHeight($_)
1100             foreach (0..$#$rows);
1101             #
1102             # redraw all our buttons, starting from the top row
1103             # note: we force each button to fully fill the button frame;
1104             # this improves the visual effect when an upper tab extends
1105             # to the right of the end of the row below it
1106             #
1107             my $Connector = $this->Subwidget('Connector');
1108             $Connector->placeForget(); # if $Connector->is_mapped();
1109             my ($i, $x, $y, $client, $frame);
1110             if ($horizontal) {
1111             if ($downward) {
1112             #
1113             # top tabs:
1114             # draw from outermost row to innermost
1115             #
1116             $i = $#$rows;
1117             $y = 0;
1118             $x = 0;
1119             while ($i >= 0) {
1120             $x = ($alignment eq 'e') ? 0 : $ButtonFrame->width() - 8;
1121              
1122             foreach (0..$#{$rows->[$i]}) {
1123             $client = $clients->[$rows->[$i][$_]];
1124             $frame = $client->[DTF_IDX_FRAME];
1125             $x -= ($client->[DTF_IDX_WIDTH] + $extra)
1126             if ($alignment eq 'w');
1127             $frame->Subwidget('Button')->configure(
1128             -height => $hts[$i]);
1129             $frame->place(
1130             -x => $x,
1131             -y => $y,
1132             -height => $hts[$i] + 6
1133             );
1134              
1135             $x += $client->[DTF_IDX_WIDTH] + $extra
1136             if ($alignment eq 'e');
1137             }
1138             #
1139             # draw pseudotabs if needed
1140             #
1141             $y = $y + $this->GetRowHeight($i)
1142             if $i;
1143             $pseudoht = $this->GetButtonRowHeight($i-1) + 6,
1144             $pseudos->[$i]->place(
1145             -x => 0,
1146             -y => $y + 4,
1147             -width => $ButtonFrame->width() - 8,
1148             -height => $pseudoht)
1149             if $i;
1150             $i--;
1151             }
1152             }
1153             else {
1154             #
1155             # bottom tabs:
1156             # draw from innermost row to innermost
1157             #
1158             $i = 0;
1159             $y = 0;
1160             while ($i <= $#$rows) {
1161             $x = ($alignment eq 'e') ? 0 : $ButtonFrame->width() - 8;
1162              
1163             foreach (0..$#{$rows->[$i]}) {
1164             $client = $clients->[$rows->[$i][$_]];
1165             $frame = $client->[DTF_IDX_FRAME];
1166             $x -= ($client->[DTF_IDX_WIDTH] + $extra)
1167             if ($alignment eq 'w');
1168              
1169             $frame->Subwidget('Button')->configure(
1170             -height => $hts[$i]);
1171             $frame->place(
1172             -x => $x,
1173             -y => $y,
1174             -height => $hts[$i] + 6
1175             );
1176              
1177             $x += $client->[DTF_IDX_WIDTH] + $extra
1178             if ($alignment eq 'e');
1179             }
1180             #
1181             # draw pseudotabs if needed
1182             #
1183             $pseudoht = $this->GetButtonRowHeight($i-1) + 1,
1184             $pseudos->[$i]->place(
1185             -x => 0,
1186             -y => 0,
1187             -width => $ButtonFrame->width() - 8,
1188             -height => $pseudoht)
1189             if $i;
1190              
1191             $y = $y + $this->GetRowHeight($i);
1192             $i++;
1193             } # end while
1194             } # end if downward...else...
1195             } # end if horizontal
1196             else { # vertical tabs
1197             if ($downward) {
1198             #
1199             # left tabs:
1200             # draw from outermost row to innermost
1201             #
1202             $i = $#$rows;
1203             $x = 0;
1204             while ($i >= 0) {
1205             $y = ($alignment eq 's') ? 0 : $ButtonFrame->height() - 8;
1206              
1207             foreach (0..$#{$rows->[$i]}) {
1208             $client = $clients->[$rows->[$i][$_]];
1209             $frame = $client->[DTF_IDX_FRAME];
1210             $y -= ($client->[DTF_IDX_WIDTH] + $extra)
1211             if ($alignment eq 'n');
1212              
1213             $frame->Subwidget('Button')->configure(-width => $hts[$i]);
1214             $frame->place(
1215             -x => $x,
1216             -y => $y,
1217             -width => $hts[$i] + 6
1218             );
1219              
1220             $y += $client->[DTF_IDX_WIDTH] + $extra
1221             if ($alignment eq 's');
1222             }
1223             #
1224             # draw pseudotabs if needed
1225             #
1226             $x += $this->GetRowHeight($i);
1227             $pseudoht = $this->GetButtonRowWidth($i-1) + 6,
1228             $pseudos->[$i]->place(
1229             -x => $x + 4,
1230             -y => 0,
1231             -width => $pseudoht,
1232             -height => $ButtonFrame->height() - 8)
1233             if $i;
1234             $i--;
1235             }
1236             }
1237             else {
1238             #
1239             # right tabs:
1240             # draw from innermost row to innermost
1241             #
1242             $i = 0;
1243             $x = 0;
1244             while ($i <= $#$rows) {
1245             $y = ($alignment eq 's') ? 0 : $ButtonFrame->height() - 8;
1246              
1247             foreach (0..$#{$rows->[$i]}) {
1248             $client = $clients->[$rows->[$i][$_]];
1249             $frame = $client->[DTF_IDX_FRAME];
1250             $y -= ($client->[DTF_IDX_WIDTH] + $extra)
1251             if ($alignment eq 'n');
1252              
1253             $frame->Subwidget('Button')->configure(-width => $hts[$i]);
1254             $frame->place(
1255             -x => $x,
1256             -y => $y,
1257             -width => $hts[$i] + 6
1258             );
1259              
1260             $y += $client->[DTF_IDX_WIDTH] + $extra
1261             if ($alignment eq 's');
1262             }
1263             #
1264             # draw pseudotabs if needed
1265             #
1266             $pseudoht = $this->GetButtonRowHeight($i-1),
1267             $pseudos->[$i]->place(
1268             -x => $x - $pseudoht,
1269             -y => 0,
1270             -width => $pseudoht + 1,
1271             -height => $ButtonFrame->height() - 8)
1272             if $i;
1273             $x += $this->GetRowHeight($i);
1274             $i++;
1275             } # end while
1276             } # end if downward...else...
1277             } # end if horizontal...else...
1278             #
1279             # and reapply our tab order
1280             #
1281             $this->TabOrder;
1282             #
1283             # and reraise in case raised ended up somewhere other than
1284             # bottom row
1285             #
1286             $this->TabRaise($Raised, 1) if $Raised;
1287              
1288             return 1;
1289             }
1290             #
1291             # remove a single tab and re-org the tabs
1292             #
1293             sub TabRemove {
1294             my ($this, $Caption) = @_;
1295             $this->{Updating} = 1;
1296             #
1297             # remove a tab
1298             #
1299             return undef
1300             unless defined($this->{ClientHash}{$Caption});
1301            
1302             my $rows = $this->{RowList};
1303             my $clients = $this->{ClientList};
1304             my $listsz = $#$clients;
1305             my $clientno = $this->{ClientHash}{$Caption};
1306             my $client = $clients->[$clientno];
1307             my $Widget = $client->[DTF_IDX_WIDGET];
1308             my ($r, $c) = ($client->[DTF_IDX_ROW], $client->[DTF_IDX_COL]);
1309             #
1310             # if its the raised widget, then we need to raise
1311             # a tab to replace it (unless its the only widget)
1312             # ...whatever is left at 0,0 sounds good to me...
1313             #
1314             my $row = $rows->[$r];
1315             my $newcurrent = ($client->[DTF_IDX_WIDGET] eq $this->{Raised}) ? 1 : undef;
1316             #
1317             # remove client from lists
1318             #
1319             delete $this->{ClientHash}{$Caption};
1320            
1321             if ($clientno eq $#$clients) {
1322             # Perl bug ? we seem to not get spliced out at ends
1323             pop @$clients;
1324             }
1325             else {
1326             splice @$clients, $clientno, 1;
1327             }
1328             splice @$row, $c, 1;
1329             #
1330             # adjust client positions in this row
1331             #
1332             $clients->[$row->[$_]][DTF_IDX_COL]--
1333             foreach ($c..$#$row);
1334             #
1335             # adjust client indices in the hash
1336             #
1337             foreach (keys %{$this->{ClientHash}}) {
1338             $this->{ClientHash}{$_} -= 1
1339             if ($this->{ClientHash}{$_} > $clientno);
1340             }
1341             #
1342             # adjust all our row index lists
1343             #
1344             foreach my $row (@$rows) {
1345             foreach (0..$#$row) {
1346             $row->[$_]-- if ($row->[$_] > $clientno);
1347             }
1348             }
1349            
1350             my $TabFrame = $client->[DTF_IDX_FRAME];
1351             $TabFrame->packForget();
1352             $TabFrame->destroy();
1353             $Widget->destroy();
1354             #
1355             # if only tab in row, remove the row
1356             # and adjust the clients in following rows
1357             #
1358             if ($#$row < 0) {
1359             foreach my $i ($r+1..$#$rows) {
1360             $row = $rows->[$i];
1361             $clients->[$_][DTF_IDX_ROW] -= 1
1362             foreach (@$row);
1363             }
1364             splice @$rows, $r, 1;
1365             }
1366              
1367             if ($#$rows < 0) {
1368             #
1369             # no rows left, clear everything
1370             #
1371             $this->{Raised} = undef;
1372             $this->Subwidget('Connector')->placeForget();
1373             $this->{CurrentFrame} = undef;
1374             }
1375             elsif ($newcurrent) {
1376             $this->{Raised} = $clients->[$rows->[0][0]][DTF_IDX_WIDGET];
1377             }
1378             #
1379             # redraw everything
1380             #
1381             $this->TabRedraw(1);
1382             $this->{Updating} = undef;
1383             #
1384             # odd behavior (maybe Resize timing issue):
1385             # we occasionally end up with an undef entry at the tail
1386             #
1387             pop @$clients
1388             unless (($listsz - 1) == $#$clients);
1389             return 1;
1390             }
1391             #
1392             # reveal a previously hidden tab and re-org the tabs
1393             #
1394             sub TabReveal {
1395             my ($this, $Caption) = @_;
1396             $this->{Updating} = 1;
1397              
1398             return undef
1399             unless defined($this->{ClientHash}{$Caption});
1400              
1401             my $clients = $this->{ClientList};
1402             my $rows = $this->{RowList};
1403             my $clientno = $this->{ClientHash}{$Caption};
1404             my $client = $clients->[$clientno];
1405             return 1
1406             unless $client->[DTF_IDX_HIDDEN];
1407             #
1408             # make visible and redraw; note we don't
1409             # make it raised yet
1410             #
1411             $client->[DTF_IDX_HIDDEN] = undef;
1412             #
1413             # pack tab in our rowframe 0; redraw if needed
1414             # move everything over 1 column in bottom row
1415             #
1416             $clients->[$_][DTF_IDX_COL]++
1417             foreach (@{$rows->[0]});
1418             unshift @{$rows->[0]}, $clientno;
1419             #
1420             # redraw everything
1421             #
1422             $this->TabRedraw(1);
1423             $this->{Updating} = undef;
1424             #
1425             # if nothing is raised, then raise us
1426             #
1427             $this->raise($Caption)
1428             unless $this->{Raised};
1429             return 1;
1430             }
1431             #
1432             # hide a single tab and re-org the tabs
1433             #
1434             sub TabHide {
1435             my ($this, $Caption) = @_;
1436             $this->{Updating} = 1;
1437              
1438             return undef
1439             unless defined($this->{ClientHash}{$Caption});
1440            
1441             my $rows = $this->{RowList};
1442             my $clients = $this->{ClientList};
1443             my $listsz = $#$clients;
1444             my $clientno = $this->{ClientHash}{$Caption};
1445             my $client = $clients->[$clientno];
1446             my $Widget = $client->[DTF_IDX_WIDGET];
1447             my ($r, $c) = ($client->[DTF_IDX_ROW], $client->[DTF_IDX_COL]);
1448             #
1449             # if its the raised widget, then we need to raise
1450             # a tab to replace it (unless its the only widget)
1451             # ...whatever is left at 0,0 sounds good to me...
1452             #
1453             my $row = $rows->[$r];
1454             my $newcurrent = ($client->[DTF_IDX_WIDGET] eq $this->{Raised}) ? 1 : undef;
1455             #
1456             # hide the client
1457             #
1458             $client->[DTF_IDX_HIDDEN] = 1;
1459             $client->[DTF_IDX_ROW] = undef;
1460             $client->[DTF_IDX_COL] = undef;
1461            
1462             splice @$row, $c, 1;
1463             #
1464             # adjust client positions in this row
1465             #
1466             $clients->[$row->[$_]][DTF_IDX_COL]--
1467             foreach ($c..$#$row);
1468             #
1469             # adjust all our row index lists
1470             #
1471             # foreach my $row (@$rows) {
1472             # foreach (0..$#$row) {
1473             # $row->[$_]-- if ($row->[$_] > $clientno);
1474             # }
1475             # }
1476             #
1477             # force us into unraised color
1478             #
1479             my $TabFrame = $this->{ButtonFrame}->Subwidget('Button_'.$Widget);
1480             $TabFrame->configure(-background => $client->[DTF_IDX_COLOR]);
1481             $TabFrame->Subwidget('Button')->configure(
1482             -bg => $client->[DTF_IDX_COLOR],
1483             -activebackground => $client->[DTF_IDX_COLOR]);
1484              
1485             $client->[DTF_IDX_FRAME]->placeForget()
1486             if $client->[DTF_IDX_FRAME]->ismapped();
1487             #
1488             # if only tab in row, remove the row
1489             # and adjust the clients in following rows
1490             #
1491             if ($#$row < 0) {
1492             foreach my $i ($r+1..$#$rows) {
1493             $row = $rows->[$i];
1494             $clients->[$_][DTF_IDX_ROW] -= 1
1495             foreach (@$row);
1496             }
1497             splice @$rows, $r, 1;
1498             }
1499              
1500             if ($#$rows < 0) {
1501             #
1502             # no rows left, clear everything
1503             #
1504             $this->{Raised} = undef;
1505             $this->Subwidget('Connector')->placeForget();
1506             $this->{CurrentFrame} = undef;
1507             }
1508             elsif ($newcurrent) {
1509             $this->{Raised} = $clients->[$rows->[0][0]][DTF_IDX_WIDGET];
1510             }
1511             #
1512             # redraw everything
1513             #
1514             $this->TabRedraw(1);
1515             $this->{Updating} = undef;
1516             return 1;
1517             }
1518             #
1519             # compute the tabbing traversal order
1520             # note an anomaly:
1521             # if the top row doesn't fill the frame, and a top
1522             # row button is tabbed to, it is automatically moved
1523             # to the 0,0, and its tab order it recomputed. This
1524             # means that its impossible to tab to any tab
1525             # in the top row except the first tab. We may eventually
1526             # change TabRaise to bring the entire top row down
1527             # if a top row tab is raised.
1528             #
1529             sub TabOrder {
1530             my ($this) = @_;
1531            
1532             my $rows = $this->{RowList};
1533             my $clients = $this->{ClientList};
1534             my ($prev, $next);
1535            
1536             foreach my $i (0..$#$rows) {
1537             my $row = $rows->[$i];
1538             foreach my $j (0..$#$row) {
1539             if ($j == 0) {
1540             $prev = ($i == 0) ? $rows->[-1][-1] : $rows->[$i-1][-1];
1541             $next = ($#$row == 0) ?
1542             ($i == $#$rows) ? $rows->[0][0] : $rows->[$i+1][0] :
1543             $row->[$j+1];
1544             }
1545             elsif ($j == $#$row) {
1546             $prev = $row->[$j-1];
1547             $next = ($i == $#$rows) ? $rows->[0][0] : $rows->[$i+1][0];
1548             }
1549             else {
1550             $prev = $row->[$j-1];
1551             $next = $row->[$j+1];
1552             }
1553              
1554             my $button = $clients->[$row->[$j]][DTF_IDX_FRAME]->Subwidget('Button');
1555             my $prevwgt = $clients->[$prev][DTF_IDX_WIDGET];
1556             my $prevbtn = $clients->[$prev][DTF_IDX_FRAME]->Subwidget('Button');
1557             my $nextwgt = $clients->[$next][DTF_IDX_WIDGET];
1558             my $nextbtn = $clients->[$next][DTF_IDX_FRAME]->Subwidget('Button');
1559              
1560             # bind us
1561             $button->bind ('', sub {$prevbtn->focus();});
1562             $button->bind ('', sub {$this->TabRaise($prevwgt);});
1563             $button->bind ('', sub {$nextbtn->focus();});
1564             $button->bind ('', sub {$this->TabRaise($nextwgt);});
1565             }
1566             }
1567             return 1;
1568             }
1569             #
1570             # create a tooltip for the tab
1571             #
1572             sub CreateTabTip {
1573             my ($this, $w, $btn, $tiptext) = @_;
1574             #
1575             # create balloon if none exists
1576             #
1577             $this->{Balloon} = $this->Component(
1578             'Balloon' => 'Balloon',
1579             -state => 'balloon',
1580             -balloonposition => 'widget',
1581             -initwait => $this->{_tiptime},
1582             -background => $this->{_tipcolor})
1583             unless $this->{Balloon};
1584             #
1585             # attach a balloon if tiptext requested
1586             #
1587             $w->[DTF_IDX_TABTIP] = $tiptext;
1588             return $this->{Balloon}->attach($btn, -balloonmsg => $tiptext);
1589             }
1590             #
1591             # change tab's tip text
1592             #
1593             sub UpdateTabTip {
1594             my ($this, $w, $btn, $tiptext) = @_;
1595              
1596             return undef unless $this->{Balloon};
1597             #
1598             # attach a balloon if tiptext requested
1599             #
1600             $this->{Balloon}->detach($btn)
1601             if $w->[DTF_IDX_TABTIP];
1602             $w->[DTF_IDX_TABTIP] = $tiptext;
1603             return $this->{Balloon}->attach($btn, -balloonmsg => $tiptext);
1604             }
1605              
1606             #
1607             # remove a tooltip from the tab
1608             #
1609             sub RemoveTabTip {
1610             my ($this, $w, $btn) = shift;
1611              
1612             return 1 unless $this->{Balloon};
1613              
1614             $w->[DTF_IDX_TABTIP] = undef;
1615             return $this->{Balloon}->detach($btn);
1616             }
1617              
1618             sub current {
1619             shift->TabCurrent (@_);
1620             }
1621              
1622             sub add {
1623             my $this = shift;
1624             #
1625             # make this Notebook compatible
1626             #
1627             my $caption;
1628             $caption = shift
1629             unless ($_[0]=~/^-(caption|tabcolor|raisecolor|tabtip|hidden|state|label|image|text)$/);
1630             my %args = @_;
1631             $caption = $args{-caption} unless $caption;
1632             return undef unless defined($caption);
1633             my $frame = $this->TabCreate(
1634             $caption,
1635             delete $args{'-tabcolor'},
1636             delete $args{'-raisecolor'},
1637             delete $args{'-image'},
1638             delete $args{'-label'},
1639             delete $args{'-raisecmd'},
1640             );
1641             #
1642             # pick up any attributes we didn't process during creation
1643             #
1644             $this->pageconfigure($caption, %args)
1645             if ($frame && %args);
1646             return $frame;
1647             }
1648             #
1649             # turn off flashing tab
1650             #
1651             sub deflash {
1652             my ($this, $page) = @_;
1653              
1654             return undef
1655             unless defined($this->{ClientHash}{$page});
1656            
1657             my $w = $this->{ClientList}[$this->{ClientHash}{$page}];
1658             my $color = (defined($this->{Raised}) &&
1659             ($w->[DTF_IDX_WIDGET] eq $this->{Raised})) ?
1660             $w->[DTF_IDX_RAISECOLOR] :
1661             $w->[DTF_IDX_COLOR];
1662             my $frame = $w->[DTF_IDX_FRAME];
1663             $frame->configure(-bg => $color),
1664             $frame->Subwidget('Button')->configure(
1665             -bg => $color, -activebackground => $color),
1666             $frame->afterCancel($w->[DTF_IDX_FLASH_ID]),
1667             $w->[DTF_IDX_FLASH_ID] = $w->[DTF_IDX_FLASHED] =
1668             $w->[DTF_IDX_FLASH_TIME] = undef
1669             if $w->[DTF_IDX_FLASH_ID];
1670             return $this;
1671             }
1672             #
1673             # flash a tab
1674             #
1675             sub flash {
1676             my ($this, $page, %args) = @_;
1677            
1678             return undef
1679             unless defined($this->{ClientHash}{$page});
1680            
1681             my $w = $this->{ClientList}[$this->{ClientHash}{$page}];
1682             #
1683             # don't start new flash if we already are
1684             #
1685             return $this if $w->[DTF_IDX_FLASH_ID];
1686             $args{-interval} = 300 unless $args{-interval};
1687             $args{-duration} = 5000 unless $args{-duration};
1688            
1689             $w->[DTF_IDX_FLASH_COLOR] = $args{-color} ||= 'blue';
1690             $w->[DTF_IDX_FLASH_INTVL] = $args{-interval};
1691             $w->[DTF_IDX_FLASH_TIME] = Tk::timeofday() + ($args{-duration}/1000);
1692            
1693             $w->[DTF_IDX_FLASH_ID] = $w->[DTF_IDX_FRAME]->repeat(
1694             $w->[DTF_IDX_FLASH_INTVL],
1695             sub {
1696             my $color = (defined($this->{Raised}) &&
1697             ($w->[DTF_IDX_WIDGET] eq $this->{Raised})) ?
1698             $w->[DTF_IDX_RAISECOLOR] :
1699             $w->[DTF_IDX_COLOR];
1700             my $frame = $w->[DTF_IDX_FRAME];
1701             $frame->afterCancel($w->[DTF_IDX_FLASH_ID]),
1702             $frame->configure(-bg => $color),
1703             $frame->Subwidget('Button')->configure(
1704             -bg => $color, -activebackground => $color),
1705             $w->[DTF_IDX_FLASH_ID] = $w->[DTF_IDX_FLASHED] =
1706             $w->[DTF_IDX_FLASH_TIME] = undef,
1707             return 1
1708             if (Tk::timeofday() > $w->[DTF_IDX_FLASH_TIME]);
1709              
1710             $color = $w->[DTF_IDX_FLASH_COLOR] unless $w->[DTF_IDX_FLASHED];
1711             $frame->configure(-bg => $color);
1712             $frame->Subwidget('Button')->configure(
1713             -bg => $color, -activebackground => $color);
1714             $w->[DTF_IDX_FLASHED] = ! $w->[DTF_IDX_FLASHED];
1715             return 1;
1716             }
1717             );
1718             return $this;
1719             }
1720              
1721             sub raised {
1722             shift->TabCurrent (@_);
1723             }
1724             #
1725             # return caption of current raised widget
1726             #
1727             sub raised_name {
1728             my $this = shift;
1729              
1730             return undef unless $this->{Raised};
1731             my $clients = $this->{ClientList};
1732             foreach my $client (@$clients) {
1733             return $client->[DTF_IDX_CAPTION]
1734             if ($client->[DTF_IDX_WIDGET] eq $this->{Raised});
1735             }
1736             return undef;
1737             }
1738             #
1739             # Notebook compatible methods
1740             #
1741             sub pagecget {
1742             my ($this, $page, $option) = @_;
1743              
1744             return undef
1745             unless (defined($this->{ClientHash}{$page}) &&
1746             defined($page_opts{$option}));
1747            
1748             return $page if ($option eq '-caption');
1749              
1750             my $w = $this->{ClientList}[$this->{ClientHash}{$page}];
1751             return ($page_opts{$option}=~/^\d+/) ?
1752             $w->[$page_opts{$option}] :
1753             $w->[DTF_IDX_FRAME]->Subwidget('Button')->cget($page_opts{$option});
1754             }
1755              
1756             sub pageconfigure {
1757             my ($this, $page, %args) = @_;
1758              
1759             return undef
1760             unless defined($this->{ClientHash}{$page});
1761             #
1762             # we're forgiving here if they supply an option we don't
1763             # support
1764             #
1765             my $w = $this->{ClientList}[$this->{ClientHash}{$page}];
1766             my $btn = $w->[DTF_IDX_FRAME]->Subwidget('Button');
1767             foreach (keys %args) {
1768             next unless defined($page_opts{$_});
1769              
1770             if ($_ eq '-hidden') {
1771             #
1772             # check if hiding the page
1773             #
1774             if ($args{$_}) {
1775             next if $w->[DTF_IDX_HIDDEN];
1776             $this->TabHide($page);
1777             }
1778             else {
1779             #
1780             # restore the tab if its hidden
1781             #
1782             $this->TabReveal($page)
1783             if $w->[DTF_IDX_HIDDEN];
1784             }
1785             next;
1786             }
1787             #
1788             # make sure we apply state to the close button too
1789             #
1790             if ($_ eq '-state') {
1791             if ($w->[DTF_IDX_FRAME]->Subwidget('CloseBtn')) {
1792             $w->[DTF_IDX_FRAME]->Subwidget('CloseBtn')->configure(-state => $args{$_});
1793             }
1794             $btn->configure(-state => $args{$_});
1795             }
1796             next
1797             if ($_ eq '-state');
1798              
1799             $btn->configure($page_opts{$_} => $args{$_}), next
1800             unless ($page_opts{$_}=~/^\d+/);
1801             #
1802             # create, update, or remove the button balloon
1803             #
1804             if ($_ eq '-tabtip') {
1805             if ($w->[DTF_IDX_TABTIP]) {
1806             if (defined($args{$_})) {
1807             $this->UpdateTabTip($w, $btn, $args{$_});
1808             }
1809             else {
1810             $this->RemoveTabTip($w, $btn);
1811             }
1812             }
1813             else { # no current tip, create one if requested
1814             $this->CreateTabTip($w, $btn, $args{$_})
1815             if $args{$_};
1816             }
1817             next;
1818             }
1819              
1820             $w->[$page_opts{$_}] = $args{$_};
1821             #
1822             # update the button text; be sure to rotate if needed
1823             #
1824             $btn->configure(
1825             -text => ($taborient{$this->{Side}}[DTF_ORIENT_DIRECTION] ?
1826             $w->[DTF_IDX_LABEL] : RotateTabText($w->[DTF_IDX_LABEL])) ),
1827             next
1828             if ($_ eq '-label');
1829             #
1830             # reconfig the button and tab colors as needed
1831             #
1832             $btn->configure(-bg => $args{$_}),
1833             $w->[DTF_IDX_FRAME]->configure(-bg => $args{$_})
1834             if ((($_ eq '-tabcolor') &&
1835             ((! defined($this->{Raised})) ||
1836             ($w->[DTF_IDX_WIDGET] ne $this->{Raised}))) ||
1837             (($_ eq '-raisecolor') &&
1838             (defined($this->{Raised}) &&
1839             ($w->[DTF_IDX_WIDGET] eq $this->{Raised}))));
1840             }
1841             1;
1842             }
1843              
1844             sub pages {
1845             return keys %{shift->{ClientHash}};
1846             }
1847              
1848             sub tiptime {
1849             my ($this, $time) = @_;
1850              
1851             return $this->{_tiptime}
1852             unless defined($time);
1853             $this->{_tiptime} = $time;
1854             return $this->{Balloon} ?
1855             $this->{Balloon}->configure(-initwait => $time) : 1;
1856             }
1857              
1858             sub tipcolor {
1859             my ($this, $color) = @_;
1860              
1861             return $this->{_tipcolor}
1862             unless defined($color);
1863             $this->{_tipcolor} = $color;
1864             return $this->{Balloon} ?
1865             $this->{Balloon}->configure(-background => $color) : 1;
1866             }
1867              
1868             sub font {
1869             shift->Font(@_);
1870             }
1871             #
1872             # programatically raise a tab using its caption
1873             #
1874             sub raise {
1875             my ($this, $Caption) = @_;
1876             return defined($this->{ClientHash}{$Caption}) ?
1877             $this->TabRaise($this->{ClientList}[$this->{ClientHash}{$Caption}][DTF_IDX_WIDGET]) :
1878             undef;
1879             }
1880             #
1881             # programatically remove a tab using its caption
1882             #
1883             sub delete {
1884             my ($this, $Caption) = @_;
1885             return defined($this->{ClientHash}{$Caption}) ?
1886             $this->TabRemove($Caption) : undef;
1887             }
1888             #
1889             # return a hash of our tabs keyed by caption, so the
1890             # app can e.g., attach a Balloon to them
1891             #
1892             sub tabs {
1893             my ($this) = @_;
1894             my $tabs = { };
1895             my $clients = $this->{ClientList};
1896              
1897             $tabs->{$_} = $clients->[$this->{ClientHash}{$_}][DTF_IDX_FRAME]
1898             foreach (keys %{$this->{ClientHash}});
1899             return $tabs;
1900             }
1901              
1902             sub tabside {
1903             my ($this, $side) = @_;
1904            
1905             return $this->{Side} unless defined($side);
1906             #
1907             # if already populated, don't permit change
1908             #
1909             return undef
1910             unless ($side=~/^([ns][ew]?)|([ew][ns]?)$/i);
1911             #
1912             # if already populated, don't permit change
1913             #
1914             return undef
1915             if $this->{PseudoTabs};
1916              
1917             $side = lc $side;
1918             $side .= 'w' if (($side eq 'n') || ($side eq 's'));
1919             $side .= 'n' if (($side eq 'e') || ($side eq 'w'));
1920             return $side if ($this->{Side} eq $side);
1921              
1922             my $oldside = $this->{Side};
1923             $this->{Side} = $side;
1924             #
1925             # modify all the tabs to move their buttons and closers
1926             #
1927             my $to = $taborient{$side};
1928             my $clients = $this->{ClientList};
1929             my $tab;
1930             my $padx = $this->cget(-tabpadx);
1931             my $pady = $this->cget(-tabpady);
1932             foreach (keys %{$this->{ClientHash}}) {
1933             $tab = $clients->[$this->{ClientHash}{$_}];
1934             my $btn = $tab->[DTF_IDX_FRAME]->Subwidget('Button');
1935             $btn->packForget;
1936             if ($tab->[DTF_IDX_FRAME]->Subwidget('CloseBtn')) {
1937             #
1938             # repack close tab
1939             #
1940             my $closer = $tab->[DTF_IDX_FRAME]->Subwidget('CloseBtn');
1941             $closer->packForget;
1942             $closer->configure(
1943             -anchor => $to->[DTF_ORIENT_CLOSEANCHOR]);
1944             $closer->pack(
1945             -side => $to->[DTF_ORIENT_CLOSESIDE],
1946             -anchor => $to->[DTF_ORIENT_CLOSEANCHOR],
1947             -expand => 0,
1948             -fill => 'none',
1949             -ipadx => 0,
1950             -ipady => 0,
1951             -padx => 1,
1952             -pady => 1,
1953             );
1954             }
1955             #
1956             # rotate button text if needed
1957             #
1958             $btn->configure(-text =>
1959             ($to->[DTF_ORIENT_DIRECTION] ? $tab->[DTF_IDX_LABEL] :
1960             RotateTabText($tab->[DTF_IDX_LABEL])))
1961             if defined($btn->cget(-text));
1962             #
1963             # repack the button
1964             #
1965             $btn->pack(
1966             -side => $to->[DTF_ORIENT_BTNSIDE],
1967             -anchor => $to->[DTF_ORIENT_BTNANCHOR],
1968             -expand => 0,
1969             -fill => 'none',
1970             -ipadx => 0,
1971             -ipady => 0,
1972             -padx => $padx,
1973             -pady => $pady,
1974             );
1975             #
1976             # record new button size
1977             #
1978             $tab->[DTF_IDX_HEIGHT] = $to->[DTF_ORIENT_DIRECTION] ?
1979             $btn->reqheight + (2 * $pady) :
1980             $btn->reqwidth + (2 * $padx);
1981             $tab->[DTF_IDX_WIDTH] = $to->[DTF_ORIENT_DIRECTION] ?
1982             $btn->reqwidth + (2 * $padx) :
1983             $btn->reqheight + (2 * $pady);
1984             }
1985             ######################################################################
1986             #
1987             # we must repack the major frame components for this to work;
1988             # but this process may have some undesirable consequences, and probably
1989             # requires that we do everything in a specific order...
1990             #
1991             ######################################################################
1992             #
1993             # get tab alignment info
1994             #
1995             my ($tabside, $tabfill, $tabanchor, $tabsize, $clientside) =
1996             @{$tabalign{$side}};
1997             #
1998             # unplace the pseudo tabs,
1999             # and remove their current border decorations
2000             # (do we dare to destroy here ???)
2001             #
2002             foreach (@{$this->{PseudoTabs}}) {
2003             next unless $_;
2004             $_->placeForget();
2005             $_->destroy;
2006             }
2007             delete $this->{PseudoTabs};
2008             #
2009             # repack the buttonframe
2010             #
2011             $this->{ButtonFrame}->packForget;
2012             $this->{ClientFrame}->packForget;
2013              
2014             $this->{ButtonFrame}->configure($tabsize => 40);
2015             $this->{ButtonFrame}->pack(
2016             -anchor => $tabanchor,
2017             -side => $tabside,
2018             -fill => $tabfill,
2019             );
2020             #
2021             # repack the client frame
2022             #
2023             $this->{ClientFrame}->pack(
2024             -side => $clientside,
2025             -expand => 'true',
2026             -fill => 'both',
2027             );
2028             #
2029             # prep for reconfig
2030             #
2031             $this->{OldWidth} = $this->{ButtonFrame}->reqwidth();
2032             $this->{OldHeight} = $this->{ButtonFrame}->reqheight();
2033             #
2034             # recreate our pseudotabs
2035             #
2036             # push @{$this->{PseudoTabs}}, $this->PseudoCreate($_)
2037             # foreach (1..DTF_MAX_ROWS);
2038             #
2039             # finally, redraw everyone
2040             #
2041             $this->TabRedraw(1);
2042             return $oldside;
2043             }
2044              
2045             sub tabscroll {
2046             my ($this, $scroll) = shift;
2047              
2048             return $this->{Scrolled} unless defined($scroll);
2049             return 1
2050             unless ($this->{Scrolled} ^ $scroll);
2051             #
2052             # set to requested state and redraw
2053             #
2054             $this->{Scrolled} = $scroll;
2055             return $this->TabRedraw();
2056             }
2057              
2058             sub tabclose {
2059             my ($this, $close) = @_;
2060              
2061             return $this->{Close} unless defined($close);
2062             return 1 unless ($this->{Close} || $close);
2063              
2064             my $clients = $this->{ClientList};
2065              
2066             if ($this->{Close} && (! $close)) {
2067             #
2068             # remove close buttons from everything
2069             #
2070             delete $this->{Close};
2071            
2072             $_->[DTF_IDX_FRAME]->Subwidget('CloseBtn')->packForget,
2073             $_->[DTF_IDX_FRAME]->Subwidget('CloseBtn')->Destroy
2074             foreach (@$clients);
2075             }
2076             elsif ($close && (! $this->{Close})) {
2077             #
2078             # add close buttons to everything
2079             #
2080             $this->{Close} = ((ref $close) && (ref $close eq 'CODE')) ?
2081             $close : \&TabRemove;
2082              
2083             $_->[DTF_IDX_FRAME]->Component(
2084             'Button' => 'CloseBtn',
2085             -command => [ $this->{Close}, $this, $_->[DTF_IDX_CAPTION] ],
2086             -anchor => 'ne',
2087             -relief => 'raised',
2088             -borderwidth => 1,
2089             -takefocus => 1,
2090             -padx => 0,
2091             -pady => 0,
2092             -image => $this->{CloseImage}
2093             )->pack(
2094             -side => 'top',
2095             -anchor => 'ne',
2096             -expand => 0,
2097             -fill => 'none',
2098             -ipadx => 0,
2099             -ipady => 0,
2100             -padx => 0,
2101             -pady => 0,
2102             )
2103             foreach (@$clients);
2104             }
2105             else {
2106             #
2107             # reconfig everyone's close button
2108             #
2109             $this->{Close} = ((ref $close) && (ref $close eq 'CODE')) ?
2110             $close : \&TabRemove;
2111             $_->[DTF_IDX_FRAME]->Subwidget('CloseBtn')->configure(
2112             -command => [ $this->{Close}, $this, $_->[DTF_IDX_CAPTION] ])
2113             foreach (@$clients);
2114             }
2115             return $this->TabRedraw(1);
2116             }
2117             #
2118             # for left/right tabs, we must convert text into
2119             # vertical format
2120             #
2121             sub RotateTabText {
2122             my $text = shift;
2123             my @segments = split /\n/, $text;
2124             my $maxchars = 0;
2125             foreach (@segments) {
2126             $maxchars = length($_)
2127             if ($maxchars < length($_));
2128             }
2129              
2130             $segments[$_] .= (' ' x ($maxchars - length($segments[$_])))
2131             foreach (0..$#segments);
2132              
2133             my @lines = ('') x $maxchars;
2134             my @chars;
2135             foreach my $segment (@segments) {
2136             @chars = split('', $segment);
2137             $lines[$_] .= $chars[$_] . ' '
2138             foreach (0..$#chars);
2139             }
2140             return join("\n", @lines);
2141             }
2142              
2143             sub OnEnter {
2144             my ($widget, $Button, $TabFrame, $Color) = @_;
2145              
2146             $Button->configure(
2147             -bg => $Button->Darken($Button->cget(-bg), 90));
2148             $TabFrame->configure(
2149             -bg => $TabFrame->Darken($TabFrame->cget(-bg), 90));
2150              
2151             $TabFrame->bind('' => undef);
2152             $TabFrame->bind('' => undef);
2153              
2154             $Button->bind('' => undef);
2155             $Button->bind('' => undef);
2156             }
2157              
2158             sub OnLeave {
2159             my ($widget, $Button, $TabFrame) = @_;
2160              
2161             #print "Leaving\n";
2162              
2163             $Button->configure(
2164             -bg => $Button->Darken($Button->cget(-bg), 110));
2165             $TabFrame->configure(
2166             -bg => $TabFrame->Darken($TabFrame->cget(-bg), 110));
2167              
2168             $TabFrame->bind('' => [ \&OnEnter, $Button, $TabFrame ]);
2169             $TabFrame->bind('' => undef);
2170              
2171             $Button->bind('' => [ \&OnEnter, $Button, $TabFrame ]);
2172             $Button->bind('' => undef);
2173             }
2174              
2175             sub OnFocusIn {
2176             my ($Button, $TabFrame) = @_;
2177              
2178             print "FocusIn\n";
2179              
2180             $Button->configure(
2181             -bg => $Button->Darken($Button->cget(-bg), 90));
2182             $TabFrame->configure(
2183             -bg => $TabFrame->Darken($TabFrame->cget(-bg), 90));
2184             }
2185              
2186             sub OnFocusOut {
2187             my ($Button, $TabFrame) = @_;
2188              
2189             print "FocusOut\n";
2190              
2191             $Button->configure(
2192             -bg => $Button->Darken($Button->cget(-bg), 110));
2193             $TabFrame->configure(
2194             -bg => $TabFrame->Darken($TabFrame->cget(-bg), 110));
2195             }
2196              
2197             1;
2198              
2199             __END__