File Coverage

blib/lib/Tkx/Scrolled.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             #===============================================================================
2             # Tkx/Scrolled.pm
3             # Copyright 2009-2010 Michael J. Carman. All rights reserved.
4             #===============================================================================
5             package Tkx::Scrolled;
6 1     1   20225 use 5.006;
  1         4  
  1         38  
7 1     1   5 use strict;
  1         2  
  1         30  
8 1     1   4 use warnings;
  1         6  
  1         26  
9              
10 1     1   5 use Carp qw(croak);
  1         1  
  1         64  
11 1     1   398 use Tkx;
  0            
  0            
12             use base qw(Tkx::widget Tkx::MegaConfig);
13              
14             our $VERSION = '0.06';
15              
16             __PACKAGE__->_Mega('tkx_Scrolled');
17              
18             __PACKAGE__->_Config(
19             DEFAULT => ['.scrolled'],
20             );
21              
22             my $initialized;
23             my $tile_available;
24              
25             # regular expression for validation of -scrollbars argument
26             my $valid_scrollbar_position = qr/
27             (?:^o?[ns]?o?[ew]?$) # normal directions: n, s, e, w, ne, nw, se, sw
28             |(?:^o?[ew]o?[ns]$) # unusual variants: en, es, wn, ws
29             /xi;
30              
31             #-------------------------------------------------------------------------------
32             # Subroutine : _ClassInit
33             # Purpose : Perform class initialization.
34             # Notes :
35             #-------------------------------------------------------------------------------
36             sub _ClassInit {
37             # determine availability of themed widgets
38             $tile_available = eval { Tkx::package_require('tile') };
39             $initialized++;
40             }
41              
42              
43             #-------------------------------------------------------------------------------
44             # Method : _Populate
45             # Purpose : Create a new Tkx::Scrolled widget
46             # Notes :
47             #-------------------------------------------------------------------------------
48             sub _Populate {
49             my $class = shift;
50             my $widget = shift;
51             my $path = shift;
52             my $type = shift;
53             my %opt = (
54             -tile => 1,
55             -scrollbars => 'se',
56             @_,
57             );
58              
59             _ClassInit() unless $initialized;
60              
61             # Create the megawidget.
62             my $self = ($tile_available && $opt{-tile})
63             ? $class->new($path)->_parent->new_ttk__frame(-name => $path, -class => 'Tkx_Scrolled')
64             : $class->new($path)->_parent->new_frame(-name => $path, -class => 'Tkx_Scrolled');
65             $self->_class($class);
66            
67             my $data = $self->_data();
68              
69             # Delete megawidget options so that we can safely pass the remaining
70             # ones through to the scrolled subwidget.
71             $data->{-tile} = delete $opt{-tile} && $tile_available;
72             $data->{-scrollbars} = delete $opt{-scrollbars};
73              
74             # Validate requested scrollbar positions.
75             if ($data->{-scrollbars} =~ $valid_scrollbar_position) {
76             $data->{xscrollbar}{optional} = ($data->{-scrollbars} =~ /o[ns]/);
77             $data->{yscrollbar}{optional} = ($data->{-scrollbars} =~ /o[ew]/);
78             }
79             else {
80             croak("Invalid value for option '-scrollbars', must be n, s, e, w or a combination");
81             }
82              
83             # Create the widgets.
84             my $new_thing = "new_$type";
85             my $w = $self->$new_thing(-name => 'scrolled', %opt);
86             my $x = $self->_scrollbar(-name => 'xscrollbar', -orient => 'horizontal');
87             my $y = $self->_scrollbar(-name => 'yscrollbar', -orient => 'vertical');
88              
89             # Create widget bindings. If the scrolled widget is a megawidget (e.g.
90             # Tkx::ROText) any method delegation it does won't work because the
91             # scrollbar widgets operate in Tcl/Tk; they are oblivious to the Perl layer
92             # above it. We use _mpath() to resolve any delegation now and provide the
93             # Tcl pathname of the delegate for use by the scrollbars.
94             $x->configure(-command => [$w->_mpath('xview'), 'xview']);
95             $y->configure(-command => [$w->_mpath('yview'), 'yview']);
96             $w->configure(-xscrollcommand => [\&_set, $self, 'xscrollbar']);
97             $w->configure(-yscrollcommand => [\&_set, $self, 'yscrollbar']);
98              
99             # Determine the placement of the scrollbars. The corner between the
100             # scrollbars is left empty; this keeps the ends of the scrollbars from
101             # overlapping.
102             my ($r, $c);
103             for ($self->_data->{-scrollbars}) {
104             /n/ && do { $r = 0 };
105             /s/ && do { $r = 2 };
106             /e/ && do { $c = 2 };
107             /w/ && do { $c = 0 };
108             }
109              
110             # Layout the widgets
111             $w->g_grid(-row => 1, -column => 1, -sticky => 'nsew');
112             $x->g_grid(-row => $r, -column => 1, -sticky => 'ew' ) if defined $r;
113             $y->g_grid(-row => 1, -column => $c, -sticky => 'ns' ) if defined $c;
114              
115             Tkx::grid('columnconfigure', $self, 1, '-weight', 1);
116             Tkx::grid('rowconfigure', $self, 1, '-weight', 1);
117              
118             Tkx::grid('remove', $x) if $data->{xscrollbar}{optional};
119             Tkx::grid('remove', $y) if $data->{yscrollbar}{optional};
120              
121             return $self;
122             }
123              
124              
125             #-------------------------------------------------------------------------------
126             # Method : _mpath
127             # Purpose : Delegate all method calls to the scrolled subwidget.
128             # Notes :
129             #-------------------------------------------------------------------------------
130             sub _mpath { $_[0] . '.scrolled' }
131              
132              
133             #-------------------------------------------------------------------------------
134             # Method : _scrollbar
135             # Purpose : Create a scrollbar widget using the tile setting.
136             # Notes :
137             #-------------------------------------------------------------------------------
138             sub _scrollbar {
139             my $self = shift;
140              
141             return $self->_data->{-tile}
142             ? $self->new_ttk__scrollbar(@_)
143             : $self->new_scrollbar(@_);
144             }
145              
146              
147             #-------------------------------------------------------------------------------
148             # Method : _set
149             # Purpose : Set a scrollbar, possibly hiding/showing it if it's optional
150             # Notes :
151             # * $self is the *third* argument due to the way the Tcl bridge works.
152             # * There is a (literal) corner case that can cause optional scrollbars to
153             # flicker when:
154             # 1) A line of text is at the bottom of the scrolled area.
155             # 2) The line is the only one long enough to require scrolling.
156             # 3) Drawing the horizontal scrollbar hides the line (making
157             # scrolling unnecessary).
158             # Tk doesn't provide a good mechanism for detecting this and handling it
159             # robustly. Instead we kludge it it by briefly inhibiting removal of a
160             # scrollbar after drawing it.
161             #-------------------------------------------------------------------------------
162             sub _set {
163             my ($first, $last, $self, $kid) = @_;
164             my $sb = $self->_kid($kid);
165             my $cfg = $self->_data()->{$kid};
166              
167             $sb->set($first, $last);
168              
169             return unless $cfg->{optional};
170              
171             if ($first > 0 || $last < 1) {
172             return if Tkx::grid('info', $sb); # Already visible
173             $sb->g_grid(); # Make visible
174              
175             # inhibit immediate removal
176             $cfg->{inhibit} = 1;
177             Tkx::after(10, sub { $cfg->{inhibit} = 0 });
178             }
179             else {
180             return unless Tkx::grid('info', $sb); # Already hidden
181             return if $cfg->{inhibit}; # Can't hide yet
182             Tkx::grid('remove', $sb); # Hide scrollbar
183             }
184             }
185              
186              
187             1;
188              
189             __END__