File Coverage

blib/lib/Tk/Dial.pm
Criterion Covered Total %
statement 6 67 8.9
branch 0 18 0.0
condition n/a
subroutine 2 6 33.3
pod n/a
total 8 91 8.7


line stmt bran cond sub pod time code
1             package Tk::Dial;
2 1     1   715 use strict;
  1         1  
  1         44  
3             require Tk::Frame;
4              
5 1     1   5 use vars qw($VERSION @ISA);
  1         2  
  1         1097  
6             $VERSION = substr(q$Revision: 1.5 $, 10) + 1;
7              
8             @ISA = qw(Tk::Derived Tk::Frame);
9              
10             my $pi = atan2(1, 1) * 4;
11              
12             Construct Tk::Widget 'Dial';
13              
14             =head1 NAME
15              
16             Tk::Dial - An alternative to the Scale widget
17              
18             =for category Derived Widgets
19              
20             =head1 SYNOPSIS
21              
22             use Tk::Dial;
23              
24             $dial = $widget->Dial(-margin => 20,
25             -radius => 48,
26             -min => 0,
27             -max => 100,
28             -value => 0,
29             -format => '%d');
30              
31              
32             margin - blank space to leave around dial
33             radius - radius of dial
34             min, max - range of possible values
35             value - current value
36             format - printf-style format for displaying format
37              
38             Values shown above are defaults.
39              
40              
41             =head1 DESCRIPTION
42              
43             A dial looks like a speedometer: a 3/4 circle with a needle indicating
44             the current value. Below the graphical dial is an entry that displays
45             the current value, and which can be used to enter a value by hand.
46              
47             The needle is moved by pressing button 1 in the canvas and dragging. The
48             needle will follow the mouse, even if the mouse leaves the canvas, which
49             allows for high precision. Alternatively, the user can enter a value in
50             the entry space and press Return to set the value; the needle will be
51             set accordingly.
52              
53             =head1 TO DO
54              
55             Configure
56             Tick marks
57             Step size
58              
59             =head1 AUTHORS
60              
61             Roy Johnson
62              
63             Based on a similar widget in XV, a program by
64             John Bradley
65              
66             =head1 HISTORY
67            
68             August 1995: Released for critique by pTk mailing list
69              
70             =cut
71              
72              
73             my @flags = qw(-margin -radius -min -max -value -format);
74              
75             sub Populate
76             {
77 0     0     my ($w, $args) = @_;
78              
79 0           @$w{@flags} = (20, 48, (0, 100), 0, '%d');
80 0           my $key;
81 0           for $key (@flags) {
82 0           my $val = delete $args->{$key};
83 0 0         if (defined $val) {
84 0           $$w{$key} = $val;
85             }
86             }
87              
88             # Pass other args on to Frame
89 0           $w->SUPER::Populate($args);
90              
91             # Convenience variables, based on flag settings
92 0           my ($margin, $radius, $min, $max, $format) = @$w{@flags};
93 0           my ($center_x, $center_y) = ($margin + $radius) x 2;
94              
95             # Create Widgets
96 0           my $c = $w->Canvas(-width => 2 * ($radius + $margin),
97             -height => 1.75 * $radius + $margin);
98              
99 0           $c->create('arc',
100             ($center_x - $radius, $center_y - $radius),
101             ($center_x + $radius, $center_y + $radius),
102             -start => -45, -extent => 270, -style => 'chord',
103             -width => 2);
104              
105 0           $c->pack(-expand => 1, -fill => 'both');
106              
107 0           $w->bind($c, '<1>' => \&drawPointer);
108 0           $w->bind($c, '' => \&drawPointer);
109              
110 0           my $e = $w->Entry(-textvariable => \$w->{-value});
111 0           $e->pack();
112              
113 0     0     $w->bind($e, '' => sub { &setvalue($c) });
  0            
114              
115 0           &setvalue($c);
116             }
117             #------------------------------
118             sub drawPointer
119             {
120 0     0     my $c = shift;
121 0           my $w = $c->parent;
122 0           my $e = $c->XEvent;
123              
124             # Convenience variables, based on flag settings
125 0           my ($margin, $radius, $min, $max, $value, $format) = @$w{@flags};
126 0           my ($center_x, $center_y) = ($margin + $radius) x 2;
127              
128 0           my ($delta_x, $delta_y) = ($e->x - $center_x, $e->y - $center_y);
129 0           my $distance = sqrt($delta_x**2 + $delta_y**2);
130 0 0         return if ($distance < 1);
131              
132             # atan2/pi returns the angle in pi-radians, but out-of-phase;
133             # here we correct it to be 0 at the start of the arc
134 0           my $angle = atan2($delta_y, $delta_x) / $pi + 1.25;
135 0 0         if ($angle > 2) { $angle -= 2 }
  0            
136              
137 0 0         if ($angle < 1.5) {
    0          
138 0           my $factor = $radius/$distance;
139 0           my $newx = $center_x + int($factor * $delta_x);
140 0           my $newy = $center_y + int($factor * $delta_y);
141              
142 0           $c->delete('oldpointer');
143 0           $c->create('line', ($newx, $newy, $center_x, $center_y),
144             -arrow => 'first', -tags => 'oldpointer',
145             -width => 2);
146              
147 0           $w->{-value} = sprintf($format,
148             $angle / 1.5 * ($max - $min) + $min);
149             } elsif ($angle < 1.75) {
150 0 0         if ($w->{-value} < $max) {
151 0           &setvalue($c);
152 0           $w->{-value} = $max;
153             }
154             } else {
155 0 0         if ($w->{-value} > $min) {
156 0           &setvalue($c);
157 0           $w->{-value} = $min;
158             }
159             }
160              
161             }
162              
163             #------------------------------
164              
165             sub setvalue {
166 0     0     my $c = shift;
167 0           my $w = $c->parent;
168              
169 0           my $value = $w->{-value};
170              
171             # Convenience variables, based on flag settings
172 0           my ($margin, $radius, $min, $max, $dummy, $format) = @$w{@flags};
173 0           my ($center_x, $center_y) = ($margin + $radius) x 2;
174              
175 0 0         if ($value > $max) {
    0          
176 0           $value = $max;
177             } elsif ($value < $min) {
178 0           $value = $min;
179             }
180              
181 0           $w->{-value} = sprintf($format, $value);
182              
183             # value = (angle / 1.5) * (max-min) + min
184             # Solving backwards...
185             # value - min = angle / 1.5 * (max-min)
186             # (value - min) * 1.5 / (max-min) = angle
187              
188 0           my $angle = ($value - $min) * 1.5 / ($max - $min);
189 0           $angle -= 1.25;
190 0           $angle *= $pi;
191              
192             # Now just figure out X and Y where atan2 == $angle
193 0           my($x, $y) = (cos($angle) * $radius, sin($angle) * $radius);
194 0           $x += $center_x;
195 0           $y += $center_y;
196 0           $c->delete('oldpointer');
197 0           $c->create('line', ($x, $y, $center_x, $center_y),
198             -arrow => 'first', -tags => 'oldpointer',
199             -width => 2);
200              
201             }
202              
203             1;