File Coverage

blib/lib/Tk/StrfClock.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             #! /usr/bin/env perl
2              
3             package Tk::StrfClock;
4              
5             require Exporter;
6              
7 1     1   38305 use 5;
  1         4  
  1         56  
8 1     1   6 use strict;
  1         2  
  1         37  
9 1     1   6 use warnings;
  1         2  
  1         32  
10              
11 1     1   6 use Carp;
  1         1  
  1         99  
12 1     1   1056 use POSIX;
  1         8794  
  1         8  
13 1     1   4543 use Tk;
  0            
  0            
14             use Tk::Label;
15             use Tk::Button;
16             use Tk::Menubutton;
17             use Tk::Optionmenu;
18              
19             our $VERSION = '1.5';
20              
21             use vars qw(@ISA $AUTOLOAD %flags);
22              
23             @ISA = qw (Exporter);
24             our @EXPORT = qw(StrfClock);
25              
26             Construct Tk::Widget 'StrfClock';
27              
28             ;# Extra flags and their defaults.
29             %flags = (
30             -type => 'Label', # Label, Button, etc..
31             -format => '%c', # strftime string
32             -update => 'a', # auto
33             -advance => 0, # seconds
34             -ontick => undef, # function
35             -action => undef, # =~ transformation
36             );
37              
38             sub debug {};
39             ;#sub debug { print STDERR @_; };
40              
41             ;# Create the widget.
42             sub new
43             {
44             debug "args: @_\n";
45              
46             my $class = shift;
47             my $top = shift;
48            
49             # bless
50             my $self = {};
51             bless $self, $class;
52             debug "self is $self.\n";
53              
54             # Initialise.
55             $self->init($top, @_);
56              
57             $self;
58             }
59              
60             ;# Initialise the widget.
61             sub init
62             {
63             debug "args: @_\n";
64              
65             # Grab the args.
66             my $self = shift;
67             my $top = shift;
68             my %args = @_;
69              
70             # Add defaults to the widget.
71             while ( my($k, $v) = each(%flags) )
72             {
73             $self->{$k} = $v;
74             }
75              
76             # Configure the Tk::StrfClock options.
77             for my $a (keys %flags)
78             {
79             next unless (exists($args{$a}));
80             $self->{$a} = delete($args{$a});
81              
82             debug "saving arg $a.\n";
83             }
84              
85             # Construct the base widget depending on the type.
86             if ($self->{'-type'} eq 'Label')
87             { $self->{base} = $top->Label(%args); }
88             elsif ($self->{'-type'} eq 'Button')
89             { $self->{base} = $top->Button(%args); }
90             elsif ($self->{'-type'} eq 'Menubutton')
91             { $self->{base} = $top->Menubutton(%args); }
92             elsif ($self->{'-type'} eq 'Optionmenu')
93             { $self->{base} = $top->Optionmenu(%args); }
94             else
95             {
96             carp "__PACKAGE__: unknown type '$self->{'-type'}'";
97              
98             $self->{'-type'} = 'Label';
99             $self->{base} = $top->Label(%args);
100             }
101              
102             # Sync the string in the base widget.
103             $self->{datetime} = '';
104             $self->{base}->configure(-textvariable => \$self->{datetime});
105              
106             # Start ticking.
107             $self->tick();
108              
109             # return the object.
110             $self;
111             }
112              
113             ;# Pack is just the same as the base widget.
114             ;# Must return the correct object.
115             sub pack { my $self = shift; $self->{base}->pack(@_); $self; }
116              
117             ;# Overload the configure function.
118             sub configure
119             {
120             # Grab the widget and the args.
121             my $self = shift;
122             my %args = @_;
123             ##for (keys %args) { debug "$_ '$args{$_}'\n"; };
124              
125             # Stop -type configures.
126             if (exists($args{'-type'}))
127             {
128             carp "Cannot configure type now!";
129             delete($args{'-type'});
130             }
131            
132             # Configure the other Tk::StrfClock options.
133             for my $a (keys %flags)
134             {
135             next unless (exists($args{$a}));
136             $self->{$a} = delete($args{$a});
137             }
138              
139             # Configure the base widget.
140             $self->{base}->configure(%args);
141              
142             # Retick after the configure - this resets the tick
143             # and does a refresh to boot.
144             $self->tick();
145             }
146              
147             ;# The cget function....
148             sub cget
149             {
150             # Grab the widget and the args.
151             my $self = shift;
152              
153             # Check for private members
154             for my $a (@_)
155             {
156             for my $b (keys %flags)
157             {
158             return $self->{$a} if ($a eq $b);
159             }
160            
161             # Pass onto the base widget
162             return $self->{base}->cget($a);
163             }
164              
165             }
166              
167             ;# DoWhenIdle seems to be replaced by afterIdle in Tk800.018.
168             sub afterIdle { &DoWhenIdle; }
169              
170             sub DoWhenIdle
171             {
172             debug "args: @_\n";
173              
174             my $self = shift;
175              
176             $self->tick();
177             }
178              
179             ;# Refresh the time.
180             sub refresh
181             {
182             debug "args: @_\n";
183             my $self = shift;
184              
185             # don't do anything unless these are set up.....
186             return unless defined($self->cget('-update'));
187             return unless defined($self->cget('-format'));
188              
189             debug "$self: update is '", $self->cget('-update'), "'\n";
190             debug "$self: format is '", $self->cget('-format'), "'\n";
191              
192             # Update the date/time string....
193              
194             # get the localtime details.
195             my @localtime = localtime(time + $self->cget('-advance'));
196              
197             # Note: some POSIX::strftime translate %f to a single f.
198             # So have to deal with this first. In particular, ActivePerl.
199              
200             # deal with %f.....
201             my $str = $self->cget('-format');
202             $str =~ s/%f/&th($localtime[3])/eg;
203              
204             debug "$self: format is now '$str'\n";
205              
206             # finally pass it through strftime.
207             $self->{datetime} = POSIX::strftime($str, @localtime);
208             #
209             # Apply any optional action to the string.
210             my $act = $self->cget('-action');
211             if (defined($act))
212             {
213             debug "$self: format before action is '$str'\n";
214             debug "$self: action is '$act'\n";
215              
216             eval "\$self->{datetime} =~ $act";
217             }
218              
219             @localtime;
220             }
221              
222             ;# Calculate the number of seconds before we need to update.
223             ;# Usage: $nap = $C->until(@localtime);
224             sub until
225             {
226             debug "args: @_\n";
227              
228             my $self = shift;
229             my @localtime = @_;
230              
231             my $update = $self->cget('-update');
232              
233             $update = 'a' if (!defined($update) || $update eq '');
234              
235             # return the update if its just a number.
236             return $update unless ($update =~ /\D/);
237              
238             if ($update =~ /^a/i)
239             {
240             # guess the update.....
241             my $fmt = $self->cget('-format');
242              
243             if ($fmt =~ /%[cST]/) { $update = 's';}
244             elsif ($fmt =~ /%M/ ) { $update = 'm';}
245             elsif ($fmt =~ /%H/ ) { $update = 'h';}
246             elsif ($fmt =~ /%P/i ) { $update = 'p';}
247             else { $update = 'd';}
248             }
249              
250             if ($update =~ /^s/i)
251             {
252             # sync every second.
253             $update = 1;
254             }
255             elsif ($update =~ /^m/i)
256             {
257             # sync on the minute.
258             $update = 60 - $localtime[0];
259             }
260             elsif ($update =~ /^h/i)
261             {
262             # sync on the hour.
263             $update = 3600 - $localtime[0] - 60*$localtime[1];
264             }
265             elsif ($update =~ /^p/i)
266             {
267             # sync at midday and midnight.
268             $update = 12*3600 - $localtime[0] -
269             60*$localtime[1] - 3600*($localtime[2]%12);
270             }
271             elsif ($update =~ /^d/i)
272             {
273             # sync at midnight.
274             $update = 24*3600 - $localtime[0] - 60*$localtime[1] - 3600*$localtime[2];
275             }
276             else
277             {
278             #carp __PACKAGE__ . ": unknown value '$update' for update (resetting to 1 sec).\n";
279              
280             $update = 1;
281             }
282              
283             debug "required nap is $update seconds.\n";
284              
285             $update;
286             }
287              
288             ;# Tick every so often and update the label.
289             ;# $self->tick().
290             sub tick
291             {
292             debug "args: @_\n";
293             my $self = shift;
294              
295             # don't do anything unless these are set up.....
296             return unless defined($self->{'-update'});
297             return unless defined($self->{'-format'});
298              
299             # update the date/time string....
300             my @localtime = $self->refresh();
301              
302             # If update is a letter then sync on a minute, hour or day.
303             my $update = $self->until(@localtime);
304              
305             debug "update is in $update seconds.\n";
306              
307             return undef unless ($update > 0 );
308            
309             # If there is an ontick function, do it.
310             &{$self->cget('-ontick')}($self) if (defined($self->cget('-ontick')));
311              
312             # cancel any previous ticking.
313             if (exists($self->{after}))
314             {
315             debug "cancelling after '$self->{after}'\n";
316              
317             # works but produces an odd error.
318             $self->{base}->afterCancel($self->{after});
319              
320             # work around
321             $self->{base}->Tk::after('cancel' => $self->{after});
322              
323             delete($self->{after});
324             }
325              
326             # don't forget to tick again....
327             $self->{after} = $self->{base}->after($update*1000, [ 'tick', $self]);
328              
329             debug "after ref '", ref($self->{after}), "'\n";
330             debug "$self: updating in $update seconds ($self->{after}).\n";
331              
332             $self->{after};
333             }
334              
335             ;# return the correct ending for first (1st), etc..
336             ;# This is hardwired and needs to be modified
337             ;# for each language.
338             sub th
339             {
340             debug "args @_\n";
341              
342             my $e = shift;
343              
344             # eg. first == 1st....
345             my $f = "th";
346             if ($e =~ /11$/) { $f = "th"; }
347             elsif ($e =~ /12$/) { $f = "th"; }
348             elsif ($e =~ /13$/) { $f = "th"; }
349             elsif ($e =~ /1$/) { $f = "st"; }
350             elsif ($e =~ /2$/) { $f = "nd"; }
351             elsif ($e =~ /3$/) { $f = "rd"; }
352              
353             $f;
354             }
355              
356             ;# Casecade all the missing functions to the base.
357             sub AUTOLOAD
358             {
359             debug "args: @_\n";
360             debug "\$AUTOLOAD=$AUTOLOAD\n";
361              
362             my $self = shift || '';
363             croak "$AUTOLOAD: '$self' is not an object!\n" unless ref($self);
364              
365             # What are we trying to do?
366             my $what = $AUTOLOAD;
367             $what =~ s/.*:://;
368              
369             # Cascade this to the base widget.
370             eval "\$self->{base}->$what(\@_)";
371             }
372              
373             ;# Demonstration application.
374             sub StrfClock
375             {
376             debug __PACKAGE__ . " version $VERSION\n";
377              
378             # do some remedial argument parsing.
379             if (@_ && ($_[0] eq '-d'))
380             {
381             shift(@_);
382              
383             # set up debugging...
384             eval ' sub debug {
385             my ($package, $file, $line,
386             $subroutine, $hasargs, $wantargs) = caller(1);
387             $line = (caller(0))[2];
388            
389             print STDERR "$file:$line $subroutine: ", @_;
390            
391             };
392             ';
393             }
394              
395             # Test script
396             use Tk;
397             #use Tk::StrfClock;
398              
399             my $top=MainWindow->new();
400             $top->title(__PACKAGE__ . " version $VERSION");
401              
402             # Default arguments.
403             my @formats = (
404             '%c',
405             '%I:%M%p, %A, %e%f %B %Y.',
406             '%I:%M%p, %A, %B %e, %Y.',
407             '%Y %B %e %T',
408             '%Y %B %e %H:%M',
409             '%Y %B %e %H%p',
410             '%Y %B %e %T',
411             '%A %p',
412             '%H:%M',
413             '%T',
414             );
415              
416             my @args = ();
417             for (@_)
418             {
419             push (@args, ($_ eq 'test' ) ? @formats : $_);
420             }
421              
422             ########################################
423             # Label
424             if (0)
425             {
426             my $bframe = $top->Frame(
427             )->pack(
428             -expand => 1,
429             -fill => 'y',
430             -side => 'top',
431             -anchor => 'nw',
432             );
433              
434             my $cframe = $top->Frame(
435             #-relief => 'sunken',
436             #-border => 1,
437             -background => 'white',
438             )->pack(
439             -expand => 1,
440             -fill => 'both',
441             -side => 'top',
442             -anchor => 'nw',
443             );
444            
445             # primary Tk::StrfClock widget.
446             my $dt = $cframe->StrfClock(
447             -foreground => 'blue',
448             -background => 'white',
449             -ontick => sub { print $_[0]->{datetime}, "\n"; },
450             )->pack(
451             -anchor => 'w',
452             -expand => 1,
453             -fill => 'y',
454             );
455              
456             # take the first argument if its there.
457             $dt->configure( -format => shift(@args),) if (@args);
458              
459             ###############################################
460             # the File menu button....
461             my $file = $bframe->Menubutton(
462             -text => 'File',
463             -tearoff => 0,
464             -border => 0,
465             -borderwidth => 0,
466             )->pack(
467             -side => 'left'
468             );
469             $file->configure(
470             -activebackground => $file->cget('-background'),
471             );
472              
473              
474             # exit.
475             #$file->separator();
476             $file->command(
477             "-label" => 'Hide Buttons',
478             "-command" => sub { $bframe->packForget(); },
479             );
480             $file->command(
481             "-label" => 'Exit',
482             "-command" => sub { exit; },
483             );
484             ###############################################
485              
486             # the File menu button....
487             my $Format = $bframe->Menubutton(
488             -text => 'Format',
489             -tearoff => 0,
490             -border => 0,
491             -borderwidth => 0,
492             )->pack(
493             -side => 'left',
494             );
495             $Format->configure(
496             -activebackground => $Format->cget('-background'),
497             );
498              
499             for my $format (@formats)
500             {
501             $Format->command(
502             "-label" => $format,
503             "-command" => [ sub { $_[0]->configure(-format => $_[1]); }, $dt, $format ],
504             );
505             }
506              
507              
508             ###############################################
509             # The Tk::StrfClock widgets.
510             my $upd = '';
511             my $adv = 0;
512             local ($_);
513             for (@args)
514             {
515             if (/%/)
516             {
517             $cframe->StrfClock(
518             -format => $_,
519             -update => $upd,
520             -advance=> $adv,
521             )->pack(
522             -anchor => 'w',
523             -expand => 1,
524             -fill => 'y',
525             );
526             }
527             elsif (/^[\+\-]\d+$/) { $adv = $_; }
528             else { $upd = $_; }
529             }
530             }
531              
532             ###################################################
533             # Menubutton
534             #if (0)
535             {
536             # primary StrfClock widget.
537             my $dt = $top->StrfClock(
538             -foreground => 'blue',
539             -background => 'white',
540             -activeforeground => 'red',
541             #-ontick => sub { print $_[0]->{datetime}, "\n"; },
542             -type => 'Menubutton',
543             -action => 's/AM/am/',
544              
545             # Menubutton
546             -tearoff => 0,
547             -border => 0,
548             -borderwidth => 0,
549             )->pack(
550             -anchor => 'w',
551             -expand => 1,
552             -fill => 'y',
553             );
554              
555             # take the first argument if its there.
556             $dt->configure( -format => shift(@args),) if (@args);
557              
558             ###############################################
559              
560             # the menu items.
561             $dt->title('Formats');
562             for my $format (@formats)
563             {
564             $dt->command(
565             "-label" => $format,
566             "-command" => [ sub { $_[0]->configure(-format => $_[1]); }, $dt, $format ],
567             );
568             }
569              
570             $dt->separator();
571             $dt->command(
572             "-label" => 'tick print on',
573             "-command" => [ sub {
574             (shift)->configure(-ontick=> sub { print $_[0]->{datetime}, "\n"; });
575             }, $dt ],
576             );
577             $dt->command(
578             "-label" => 'tick print off',
579             "-command" => [ sub {
580             (shift)->configure(-ontick=>undef);
581             }, $dt ],
582             );
583             $dt->command(
584             "-label" => 'Exit',
585             "-command" => [ sub { exit; } ],
586             );
587             }
588            
589             ###################################################
590             # Optionmenu
591             if (0)
592             {
593             # primary StrfClock widget.
594             my $dt = $top->StrfClock(
595             -foreground => 'green',
596             -background => 'white',
597             -activeforeground => 'red',
598             -ontick => sub { print $_[0]->{datetime}, "\n"; },
599             -type => 'Optionmenu',
600              
601             # base widget
602             -border => 0,
603             -borderwidth => 0,
604             )->pack(
605             -anchor => 'w',
606             -expand => 1,
607             -fill => 'y',
608             );
609              
610             # take the first argument if its there.
611             $dt->configure( -format => shift(@args),) if (@args);
612              
613             ###############################################
614              
615             # the menu items.
616             $dt->title('Formats');
617             for my $format (@formats)
618             {
619             $dt->command(
620             "-label" => $format,
621             "-command" => [ sub { $_[0]->configure(-format => $_[1]); }, $dt, $format ],
622             );
623             }
624              
625             $dt->separator();
626             $dt->command(
627             "-label" => 'Exit',
628             "-command" => [ sub { exit; } ],
629             );
630             }
631            
632             ###################################################
633             # Button
634             if (0)
635             {
636             # primary StrfClock widget.
637             my $dt = $top->StrfClock(
638             -type => 'Button',
639             -format => '%c',
640             -ontick => sub { print "Button: ", $_[0]->{datetime}, "\n"; },
641              
642             # Button
643             -foreground => 'blue',
644             -background => 'white',
645             -border => 0,
646             -borderwidth => 0,
647             -command => [ sub { print "Button\n"; } ],
648             )->pack(
649             -anchor => 'w',
650             -expand => 1,
651             -fill => 'y',
652             );
653              
654             }
655              
656             MainLoop();
657              
658             # Only gets here if the window is killed.
659             exit;
660             }
661              
662             ;# If we are running this file then run the main function....
663             &StrfClock(@ARGV) if ($0 eq __FILE__);
664              
665             1;
666              
667             __END__