-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathIDEpanedwindow.pm
484 lines (333 loc) · 16.2 KB
/
IDEpanedwindow.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
=head1 NAME
Tk::IDEpanedwindow - Subclass of L<Tk::Panedwindow> to Control Pane Resize Behavior
=head1 SYNOPSIS
use Tk::IDEpanedwindow;
# Create panedwindow (Just like Tk::Panedwindow)
$panedwidnow = $widget->IDEpanedwindow( ? options ? );
# Pack the widget
$panedwidnow->pack(qw/-side top -expand yes -fill both /);
# Create two frames to insert
my $label1 = $panedwidnow->Label(-text => "This is the\nleft side", -background => 'yellow');
my $Frame2 = $panedwidnow->Frame();
# Insert the frames, with expand factors = 1 (both frames will grow/shrink with the size
# of the window)
$pwH->add($label1, -expandfactor => 1, $Frame2, -expandfactor => 1);
=head1 DESCRIPTION
This is a subclass of the L<Tk::Panedwindow> widget that adds a I<expandfactors> option
that controls how the paned-windows are resized when the overall widget is resized.
The parent class L<Tk::Panedwindow> only changes the last pane when the entire widget is resized.
Using the I<-expandfactors> option of this widget, you can control how each paned-window is resized when the overall widget is resized.
Note: The idea for the I<-expandfactors> option is borrowed from the TCL/TK widget I<TixPanedWindow>.
=head1 OPTIONS
In addition to the options from the parent class L<Tk::Panedwindow>, this widget provides the following options:
=over 1
=item expandfactors
Array ref of expand factors to use for each pane in the widget.
Each Expand Factor must be a non-negative number. The default value is 0.
The expand/shrink factor is used to calculate how much each pane should grow or shrink when the size of the PanedWindow
main window is changed. When the main window expands/shrinks by n pixels,
then pane i will grow/shrink by about n * factor(i) / summation(factors), where factor(i) is the expand/shrink factor of pane i
and summation(factors) is the summation of the expand/shrink factors of all the panes.
If summation(factors) is 0.0, however, only the last visible pane will be grown or shrunk.
Note: The behavior of this I<-expandfactors> option is borrowed from the TCL/TK widget I<TixPanedWindow>.
=item fractSizes
Array ref of fractional (i.e. less than one) sizes left over from the last resize of the pane frames.
Even though frame sizes are number of pixels (integers), we keep track of the fractional part of the calculated
frame sizes from resize-event to resize-event. This keeps the sizes of the frames in proportion to each other better
than throwing away the fractional part would.
=back
=head1 ATTRIBUTES
=over 1
=item slaves
Array ref of L<Tk::Widget> objects in each frame of the panedwindow.
=back
=head1 Methods
=cut
package Tk::IDEpanedwindow;
our ($VERSION) = ('0.37_02');
use Carp;
use strict;
use Tk;
use base qw/ Tk::Derived Tk::Panedwindow/;
our ($DEBUG);
Tk::Widget->Construct("IDEpanedwindow");
sub Populate {
my ($cw, $args) = @_;
$cw->SUPER::Populate($args);
# Initialize the slaves attribute
$cw->{slaves} = [];
$cw->{fractSizes} = [];
$cw->ConfigSpecs(
-expandfactors => [ qw/PASSIVE expandfactors expandfactors/, [] ],
);
my ($totalW, $totalH) = (0,0);
# Add Bindings
$cw->bind('<Configure>', sub{
return unless ($cw->ismapped); # Don't do anything until widget is actually displayed
my ($newTotalW, $newTotalH) = ($cw->width, $cw->height);
#print "newTotalW/H $newTotalW/$newTotalH totalW/H = $totalW/$totalH\n";
return if( $totalH == $newTotalH && $newTotalW == $totalW);
if( $totalW == 0 && $totalH == 0){ # Initially just set the totalW/H variables
$totalW = $newTotalW;
$totalH = $newTotalH;
return;
}
#print "new H $newTotalH totalH = $totalH\n";
# Get all widgets managed by pw2
my @widgets = $cw->slaves;
#print "sizeof widgets = ".scalar(@widgets)."\n";
#print "widgets = ".join(", ", @widgets)."\n";
my @heights = ();
my $sizeMethod; # Method used to get widget size, depends on orientation
$sizeMethod = "height" if( $cw->cget(-orient) =~ /vert/);
$sizeMethod = "width" if( $cw->cget(-orient) =~ /horiz/);
foreach my $widget(@widgets){
push @heights, $widget->$sizeMethod();
#print $widget->geometry."\n";
}
#print "Heights = ".join(", ", @heights)."\n";
# Get the total height of the panewindow widget (but will be 1 initially before mapped?)
my $height = $cw->$sizeMethod();
#print "SashCords = ".join(", ", @sashCoords)." height = $height\n";
if($height > 1){
my $expandFactors = $cw->cget(-expandfactors);
my @newHeights = $cw->_getNewSizes( $height, [@heights], [@$expandFactors]);
$cw->adjustSizes([@newHeights]);
# $pw2->sashPlace(0, $sashCoords[0], $height * $ratio);
#print "new Sash Location = ".$height*$ratio."\n";
}
($totalW, $totalH) = ($newTotalW, $newTotalH);
}
);
}
#######################################################################
=head2 add
Over-ridden add method add a new widget to the collection managed by the L<Tk::IDEpanedwindow>.
This method adds a -expandfactor option to the normal options recognized by the parent L<Tk::Panedwindow>.
B<Usage:>
$widget->add(?window ...? ?option value ...?);
=cut
sub add{
my $self = shift;
my @args = @_;
# Parse the args
my @widgets;
my %widgetArgs;
my $widget; # current widget that options apply to
while(@args){
$widget = shift @args;
unless( ref($widget) && $widget->isa("Tk::Widget")){
croak("Error: arg '$widget' supplied to Tk::IDEpanedwindow::add is not a Tk Widget\n");
}
push @widgets, $widget;
# Make hash entry for the args of this widget
my $argsHash = $widgetArgs{"$widget"} = {};
while(@args && $args[0] =~ /^\-/ ){ # Process any arguments
my $key = shift @args;
my $value = shift @args;
$argsHash->{$key} = $value;
}
}
my $expandfactors = $self->cget(-expandfactors);
my $slaves = $self->{slaves};
my $fractSizes = $self->{fractSizes};
## Process the args of each widget
foreach $widget(@widgets){
my $expandfactor = delete $widgetArgs{"$widget"}{-expandfactor} || 0;
# Handle where to put the expandfactor, based on -before or -after args
if( defined($widgetArgs{"$widget"}{-before})){ #
my $before = $widgetArgs{"$widget"}{-before};
my $beforeIndex;
my $index = 0;
foreach (@$slaves){ # Find index of the $before widget
if( $_ eq $before ){
$beforeIndex = $index;
last;
}
}
croak("Error Can't find -before widget $before in slaves list\n") unless defined($beforeIndex);
# Update strucutes in the correct place
splice @$expandfactors, $beforeIndex, 0, $expandfactor;
splice @$slaves, $beforeIndex, 0, $widget;
splice @$fractSizes, $beforeIndex, 0, 0;
}
elsif( defined($widgetArgs{"$widget"}{-after})){ #
my $after = $widgetArgs{"$widget"}{-after};
my $afterIndex;
my $index = 0;
foreach (@$slaves){ # Find index of the $before widget
if( $_ eq $after ){
$afterIndex = $index;
last;
}
}
croak("Error Can't find -after widget $after in slaves list\n") unless defined($afterIndex);
splice @$expandfactors, $afterIndex + 1, 0, $expandfactor;
splice @$slaves, $afterIndex + 1, 0, $widget;
splice @$fractSizes, $afterIndex + 1, 0, 0;
}
else{ # Normal add at the end
push @$expandfactors, $expandfactor;
push @$slaves, $widget;
push @$fractSizes, 0;
}
}
# Save back the populated expandfactors
$self->configure(-expandfactors => $expandfactors);
# Call the parent widget ####
## Build the args to call the parent (minus any expandfactors)
my @parentArgs;
foreach $widget(@widgets){
push @parentArgs, $widget;
my $options = $widgetArgs{"$widget"};
if( keys %$options){ # Add any options for this widget
push @parentArgs, %$options;
}
}
$self->SUPER::add(@parentArgs);
}
#######################################################################
=head2 forget
Over-ridden forget method to delete a widget from the paned-window.
This deletes the widget from our own I<slaves> list before calling the parent method.
B<Usage:>
$widget->forget($window);
=cut
sub forget{
my $self = shift;
my $window = shift;
my $expandfactors = $self->cget(-expandfactors);
my $slaves = $self->{slaves};
my $fractSizes = $self->{fractSizes};
# Find widget in slaves
my $matchIndex = -1;
my $i = 0;
foreach my $slave(@$slaves){
if( $slave eq $window){
$matchIndex = $i;
}
$i++;
}
if( $matchIndex > -1){ # Get rid of this window from our lists, if a match found
splice(@$slaves, $matchIndex, 1);
splice(@$fractSizes, $matchIndex, 1);
splice(@$expandfactors, $matchIndex, 1);
# Save back the populated expandfactors
$self->configure(-expandfactors => $expandfactors);
}
$self->SUPER::forget($window);
}
##################################################
=head2 slaves
Gets (and optionally sets) the slaves attribute.
B<Usage:>
my @slaves = $self->slaves(); # Get slaves
$self->slaves(@slaves); # Set slaves
=cut
sub slaves{
my $self = shift;
if (defined $_[0]) {
my @slaves = @_;
my $slaves = $self->{slaves};
@$slaves = @slaves;
}
my $slaves = $self->{slaves};
return @$slaves;
}
#####################################################################
=head2 _getNewSizes
Internal method to get / calculate the new widget Sizes (Width or height) of a panewindow widget,
based on total pw size, widget sizes, and expand factors.
This is called when the size of the panedwindow widget changes.
B<Usage:>
@newSizes = $self->_getNewSizes($newSize, $sizes);
where: $newSize: Total new size of the panewindow widget
(Along the paned direction)
$sizes: Array ref of old sizes (i.e. not yet adjusted
for the new total-size) for each window
managed by the panedwindow.
=cut
# Sub to get / calculate the new widget Sizes (Width or height) of a panewindow widget,
# based on total pw size, widget sizes, and expand factors
sub _getNewSizes{
my ($pw, $newSize, $sizes) = @_;
my $expandFactors = $pw->cget(-expandfactors);
### Calculate total size using current sizes
my $oldTotalSize = 2; # PW always pads top and bottom by two
my $sashPad = $pw->cget(-sashpad);
my $sashW = $pw->cget(-sashwidth);
my $Sindex = 0;
foreach my $size(@$sizes){
$oldTotalSize += $size;
unless( $Sindex == $#$sizes){ # Add sash width, unless this is the last one
$oldTotalSize += ($sashPad + $sashW + $sashPad);
}
$Sindex++;
}
$oldTotalSize += 2; # PW always pads top and bottom by two
# Calc New Space Delta
my $spaceDelta = $newSize - $oldTotalSize;
#print "####### newHeight = $newSize, totalHeight $oldTotalSize Space Delta = $spaceDelta\n";
#print "$pw orient ".$pw->cget(-orient)." ExpandFactors = ".join(", ", @$expandFactors)."\n";
# Normalize expand factors
my @expandFactors = @$expandFactors; # Copy for us to mess with
my $expandSum = 0;
foreach (@expandFactors){ $expandSum+=$_; };
# If all factors zero, make the last expandFactor 1 ( like default panedwindow behaviour)
if( $expandSum < .0001){
$expandSum = 1;
$expandFactors[-1] = 1;
}
my @normExpand;
foreach (@$expandFactors){ push @normExpand, $_/$expandSum };
# Calulate new heights
my @newSizes;
$Sindex = 0;
my ($newS, $newSfract); # New Size (rounded), and new Fractional size (not rounded)
my $expandFact;
my $fractSizes = $pw->{fractSizes};
foreach my $s (@$sizes){
my $fractSize = $fractSizes->[$Sindex]; # Fractional size for the current frame
$expandFact = $normExpand[$Sindex];
$newSfract = $s + $expandFact*$spaceDelta + $fractSize; # Calc new size, including left-over fraction from last times
$newS = sprintf("%.0f", $newSfract); # Round to get real size
# Save left-over fraction for next time
$fractSizes->[$Sindex] = $newSfract - $newS;
push @newSizes, $newS;
$Sindex++;
}
#print "Old Sizes = ".join(", ", @$sizes)." New Sizes = ".join(", ", @newSizes)."\n";
return @newSizes;
}
################################################################3
=head2 adjustSizes
Method to adjust the sizes of each pane in the paned-window direction.
B<Usage:>
$self->adjustSizes($newSizes);
where: $newSizes: Array ref of new sizes for each window
managed by the panedwindow.
=cut
sub adjustSizes{
my ($pw, $newHeights) = @_;
### Calculate sashCoords
my $sashCoord = 2; # PW always pads top and bottom by two
my $sashPad = $pw->cget(-sashpad);
my $sashW = $pw->cget(-sashwidth);
my $orient = $pw->cget(-orient);
#return if($orient eq 'vertical');
my $Hindex = 0;
foreach my $height(@$newHeights){
$sashCoord += $height;
unless( $Hindex == $#$newHeights){ # Add sash width, unless this is the last one
$sashCoord += $sashPad; # Add padding to get to location of sash
#print "## Setting SashCoord $Hindex to $sashCoord\n";
#print "orient = ".$pw->cget(-orient)."\n";
# Set sashcord based on orientation (horiz or vert)
$pw->sashPlace($Hindex, 2, $sashCoord) if( $orient =~ /vert/);
$pw->sashPlace($Hindex, $sashCoord, 2) if( $orient =~ /horiz/);
$sashCoord += ($sashW + $sashPad); # Add sashwidth and sashpad to get past the sash
}
$Hindex++;
}
}
1;