File Coverage

blib/lib/Tkx.pm
Criterion Covered Total %
statement 10 107 9.3
branch 0 36 0.0
condition 0 20 0.0
subroutine 4 22 18.1
pod n/a
total 14 185 7.5


line stmt bran cond sub pod time code
1             package Tkx;
2              
3 8     8   54444 use strict;
  8         22  
  8         3477  
4             our $VERSION = '1.09';
5              
6             {
7             # predeclare
8             package Tkx::widget;
9             package Tkx::i;
10             }
11              
12             eval {
13             package_require("Tk");
14             };
15             if ($@) {
16             $@ =~ s/^this isn't a Tk application//; # what crap
17             die $@;
18             }
19              
20             our $TRACE;
21             our $TRACE_MAX_STRING;
22             our $TRACE_COUNT;
23             our $TRACE_TIME;
24             our $TRACE_CALLER;
25              
26             $TRACE = $ENV{PERL_TKX_TRACE} unless defined $TRACE;
27             $TRACE_MAX_STRING = 64 unless defined $TRACE_MAX_STRING;
28             $TRACE_COUNT = 1 unless defined $TRACE_COUNT;
29             $TRACE_TIME = 1 unless defined $TRACE_TIME;
30             $TRACE_CALLER = 1 unless defined $TRACE_CALLER;
31              
32              
33             sub import {
34 0     0     my($class, @subs) = @_;
35 0           my $pkg = caller;
36 0           for (@subs) {
37 0           s/^&//;
38 0 0 0       if (/^[a-zA-Z]\w*/ && $_ ne "import") {
39 8     8   48 no strict 'refs';
  8         13  
  8         3168  
40 0           *{"$pkg\::$_"} = \&$_;
  0            
41             }
42             else {
43 0           die qq("$_" is not exported by the $class module);
44             }
45             }
46             }
47              
48             sub AUTOLOAD {
49 0     0     our $AUTOLOAD;
50 0           my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2);
51 0           return scalar(Tkx::i::call(Tkx::i::expand_name($method), @_));
52             }
53              
54             sub MainLoop () {
55 0     0     while (eval { local $TRACE; Tkx::i::call("winfo", "exists", ".") }) {
  0            
  0            
56 0           Tkx::i::DoOneEvent(0);
57             }
58             }
59              
60             sub SplitList ($) {
61 0     0     my $list = shift;
62 0 0         unless (wantarray) {
63 0           require Carp;
64 0           Carp::croak("Tkx::SplitList needs list context");
65             }
66 0 0 0       return @$list if ref($list) eq "ARRAY" || ref($list) eq "Tcl::List";
67 0           return Tkx::i::call("concat", $list);
68             }
69              
70             *Ev = \&Tcl::Ev;
71              
72             package Tkx::widget;
73              
74 0     0   0 use overload '""' => sub { ${$_[0]} },
  0         0  
75 8     8   22585 fallback => 1;
  8         12215  
  8         87  
76              
77             my %data;
78             my %class;
79             my %mega;
80              
81             sub new {
82 0     0     my $class = shift;
83 0           my $name = shift;
84 0   0       return bless \$name, $class{$name} || $class;
85             }
86              
87             sub _data {
88 0     0     my $self = shift;
89 0   0       return $data{$$self} ||= {};
90             }
91              
92             sub _kid {
93 0     0     my($self, $name) = @_;
94 0 0         substr($name, 0, 0) = $$self eq "." ? "." : "$$self.";
95 0           return $self->_nclass->new($name);
96             }
97              
98             sub _kids {
99 0     0     my $self = shift;
100 0           my $nclass = $self->_nclass;
101 0           return map $nclass->new($_), Tkx::SplitList(Tkx::winfo_children($self));
102             }
103              
104             sub _parent {
105 0     0     my $self = shift;
106 0           my $name = $$self;
107 0 0         return undef if $name eq ".";
108 0           substr($name, rindex($name, ".")) = "";
109 0 0         $name = "." unless length($name);
110 0           return $self->_nclass->new($name);
111             }
112              
113             sub _class {
114 0     0     my $self = shift;
115 0           my $old = ref($self);
116 0 0         if (@_) {
117 0           my $class = shift;
118 0           $class{$$self} = $class;
119 0           bless $self, $class;
120             }
121 0           $old;
122             }
123              
124             sub _Mega {
125 0     0     my $class = shift;
126 0           my $widget = shift;
127 0   0       my $impclass = shift || caller;
128 0           $mega{$widget} = $impclass;
129             }
130              
131             sub _nclass {
132 0     0     __PACKAGE__;
133             }
134              
135             sub _mpath {
136 0     0     my $self = shift;
137 0           $$self;
138             }
139              
140             sub AUTOLOAD {
141 0     0     my $self = shift;
142              
143 0           our $AUTOLOAD;
144 0           my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2);
145              
146 0 0         if (substr($method, 0, 4) eq "new_") {
147 0           my $widget = Tkx::i::expand_name(substr($method, 4));
148 0           my $name;
149 0           for (my $i = 0; $i < @_; $i += 2) {
150 0 0         if ($_[$i] eq "-name") {
151 0           (undef, $name) = splice(@_, $i, 2);
152 0 0         substr($name, 0, 0) = ($$self eq "." ? "." : "$$self.")
    0          
153             if index($name, ".") == -1;
154 0           last;
155             }
156             }
157 0   0       $name ||= Tkx::i::wname($widget, $$self);
158 0 0         if (my $mega = $mega{$widget}) {
159 0           return $mega->_Populate($widget, $name, @_);
160             }
161 0           return $self->_nclass->new(scalar(Tkx::i::call($widget, $name, @_)));
162             }
163              
164 0           my $prefix = substr($method, 0, 2);
165 0 0         if ($prefix eq "m_") {
166 0           my @i = Tkx::i::expand_name(substr($method, 2));
167 0           my $p = $self->_mpath($i[0]);
168 0 0 0       return scalar(Tkx::i::call($p, @i, @_)) if $p eq $$self || !$class{$p};
169 0           return (bless \$p, $class{$p})->$method(@_);
170             }
171              
172 0 0         if ($prefix eq "g_") {
173 0           return scalar(Tkx::i::call(Tkx::i::expand_name(substr($method, 2)), $$self, @_));
174             }
175              
176 0 0         if (index($prefix, "_") != -1) {
177 0           require Carp;
178 0           Carp::croak("method '$method' reserved by Tkx");
179             }
180              
181 0           $method = "m_$method";
182 0           return $self->$method(@_);
183             }
184              
185 0     0     sub DESTROY {} # avoid AUTOLOADing it
186              
187              
188             package Tkx::widget::_destroy;
189              
190             sub new {
191 0     0     my($class, @paths) = @_;
192 0           bless \@paths, $class;
193             }
194              
195             sub DESTROY {
196 0     0     my $self = shift;
197 0           for my $path (@$self) {
198 0 0         if ($path eq ".") {
199 0           %data = ();
200 0           return;
201             }
202              
203 0           my $path_re = qr/^\Q$path\E(?:\.|\z)/;
204 0           for my $hash (\%data, \%class) {
205 0           for my $key (keys %$hash) {
206 0 0         next unless $key =~ $path_re;
207 0           delete $hash->{$key};
208             }
209             }
210             }
211             }
212              
213             package Tkx::i;
214              
215 8     8   28398 use Tcl;
  0            
  0            
216              
217             my $interp;
218             my $trace_count = 0;
219             my $trace_start_time = 0;
220              
221             BEGIN {
222             $Tcl::STACK_TRACE = 0;
223             $interp = Tcl->new;
224             $interp->Init;
225             }
226              
227             sub interp {
228             return $interp;
229             }
230              
231             sub expand_name {
232             my(@f) = (shift);
233             @f = split(/(?
234             for (@f) {
235             s/(?
236             s/(?
237             }
238             wantarray ? @f : $f[0];
239             }
240              
241             sub wname {
242             my($class, $parent) = @_;
243             my $name = lc($class);
244             $name =~ s/.*:://;
245             substr($name, 1) = "";
246             my @kids = call("winfo", "children", $parent);
247             substr($name, 0, 0) = ($parent eq "." ? "." : "$parent.");
248             if (grep $_ eq $name, @kids) {
249             my %kids = map { $_ => 1 } @kids;
250             my $count = 2;
251             $count++ while $kids{"$name$count"};
252             $name .= $count;
253             }
254             $name;
255             }
256              
257             sub call {
258             if ($Tkx::TRACE) {
259             my @prefix = "Tkx";
260             if ($Tkx::TRACE_COUNT) {
261             push(@prefix, ++$trace_count);
262             }
263             if ($Tkx::TRACE_TIME) {
264             my $ts;
265             unless ($trace_start_time) {
266             if (eval { require Time::HiRes }) {
267             $trace_start_time = Time::HiRes::time();
268             }
269             else {
270             $trace_start_time = time;
271             }
272             }
273             if (defined &Time::HiRes::time) {
274             $ts = sprintf "%.1fs", Time::HiRes::time() - $trace_start_time;
275             }
276             else {
277             $ts = time - $trace_start_time;
278             $ts .= "s";
279             }
280             push(@prefix, $ts);
281             }
282             if ($Tkx::TRACE_CALLER) {
283             my $i = 0;
284             while (my($pkg, $file, $line) = caller($i)) {
285             unless ($pkg eq "Tkx" || $pkg =~ /^Tkx::/) {
286             $file =~ s,.*[/\\],,;
287             push(@prefix, $file, $line);
288             last;
289             }
290             $i++;
291             }
292             }
293              
294             my($cmd, @args) = @_;
295             for (@args) {
296             if (ref eq "CODE" || ref eq "ARRAY" && ref($_->[0]) eq "CODE") {
297             $_ = "perl::callback";
298             }
299             elsif (ref eq "ARRAY" || ref eq "Tcl::List") {
300             $_ = $interp->call("format", "[list %s]", $_);
301             }
302             else {
303             if ($TRACE_MAX_STRING && length > $TRACE_MAX_STRING) {
304             substr($_, 2*$TRACE_MAX_STRING/3, -$TRACE_MAX_STRING/3) = " ... ";
305             }
306             s/([\\{}\"\[\]\$])/\\$1/g;
307             s/\r/\\r/g;
308             s/\n/\\n/g;
309             s/\t/\\t/g;
310             s/([^\x00-\xFF])/sprintf "\\u%04x", ord($1)/ge;
311             s/([^\x20-\x7e])/sprintf "\\x%02x", ord($1)/ge;
312             $_ = "{$_}" if / /;
313             }
314             }
315             print STDERR join(" ", (join("-", @prefix) . ":"), $cmd, @args) . "\n";
316             }
317             my @cleanup;
318             if ($_[0] eq "destroy") {
319             my @paths = @_;
320             shift(@paths);
321             push(@cleanup, Tkx::widget::_destroy->new(@paths));
322             }
323              
324             if (wantarray) {
325             my @a = eval { $interp->call(@_) };
326             return @a unless $@;
327             }
328             else {
329             my $a = eval { $interp->call(@_) };
330             return $a unless $@;
331             }
332              
333             # report exception relative to the non-Tkx caller
334             if (!ref($@) && $@ =~ s/( at .*[\\\/](Tkx|Tcl)\.pm line \d+\.\n\z)//) {
335             my $i = 1;
336             my($pkg, $file, $line);
337             while (($pkg, $file, $line) = caller($i)) {
338             last if $pkg !~ /^Tkx(::|$)/;
339             $i++;
340             };
341             $@ .= " at $file line $line.\n";
342             }
343             die $@;
344             }
345              
346             sub DoOneEvent {
347             $interp->DoOneEvent(@_);
348             }
349              
350             1;
351              
352             __END__