File Coverage

blib/lib/Gtk2/Ex/Dashes.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # Copyright 2010 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-Dashes.
4             #
5             # Gtk2-Ex-Dashes is free software; you can redistribute it and/or modify it
6             # under the terms of the GNU General Public License as published by the Free
7             # Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Gtk2-Ex-Dashes is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Gtk2-Ex-Dashes. If not, see .
17              
18             package Gtk2::Ex::Dashes;
19 2     2   25064 use 5.008;
  2         6  
  2         71  
20 2     2   12 use strict;
  2         2  
  2         59  
21 2     2   10 use warnings;
  2         8  
  2         54  
22 2     2   1867 use Gtk2;
  0            
  0            
23             use List::Util qw(min max);
24             use POSIX ();
25              
26             # uncomment this to run the commented-out ### lines
27             #use Smart::Comments;
28              
29             our $VERSION = 2;
30              
31             use Glib::Object::Subclass
32             'Gtk2::Misc',
33             signals => { size_request => \&_do_size_request,
34             expose_event => \&_do_expose_event,
35             style_set => \&_do_style_or_direction,
36             direction_changed => \&_do_style_or_direction,
37             },
38             properties => [ Glib::ParamSpec->enum
39             ('orientation',
40             'orientation',
41             'Horizontal or vertical line draw.',
42             'Gtk2::Orientation',
43             'horizontal',
44             Glib::G_PARAM_READWRITE),
45             ];
46              
47             # Multiplied by $widget->style->ythickness.
48             # For default theme thickness 2 pixels this gives dash segments 5 pixels
49             # same as Gtk2::TearoffMenuItem.
50             use constant _DASH_FACTOR => 2.5;
51              
52             sub INIT_INSTANCE {
53             my ($self) = @_;
54             $self->set_flags('no-window');
55             }
56              
57             sub SET_PROPERTY {
58             my ($self, $pspec, $newval) = @_;
59             my $pname = $pspec->get_name;
60             my $oldval = $self->get($pname);
61             $self->{$pname} = $newval;
62              
63             # if ($pname eq 'orientation') # the only property
64             #
65             if ($oldval ne $newval) {
66             $self->queue_resize;
67             $self->queue_draw;
68             }
69             }
70              
71             # 'size-request' class closure
72             sub _do_size_request {
73             my ($self, $req) = @_;
74              
75             my ($width, $height) = $self->get_padding;
76             $width *= 2;
77             $height *= 2;
78             if ($self->get('orientation') eq 'horizontal') {
79             $height += $self->style->ythickness;
80             } else {
81             $width += $self->style->xthickness;
82             }
83             ### size_request: "$width x $height"
84             $req->width ($width);
85             $req->height ($height);
86             }
87              
88             sub _do_expose_event {
89             my ($self, $event) = @_;
90             my $clip = $event->area; # Gtk2::Gdk::Rectangle
91             ### expose: $self->get_name, $clip->values
92              
93             my $horizontal = ($self->get('orientation') eq 'horizontal');
94             my $style = $self->style;
95             my $state = $self->state;
96             my $win = $self->window;
97             my $thickness = ($horizontal ? $style->ythickness : $style->xthickness);
98             my $dash_len = POSIX::ceil (_DASH_FACTOR * $thickness);
99             ### $dash_len
100             my $dash_step = 2 * $dash_len;
101              
102             my ($alloc_x, $alloc_y, $alloc_width, $alloc_height)
103             = $self->allocation->values; # Gtk2::Gdk::Rectangle
104             ### alloc: "$alloc_x,$alloc_y ${alloc_width}x$alloc_height"
105              
106             my ($xalign, $yalign) = $self->get_alignment;
107             if ($self->get_direction eq 'rtl') { $xalign = 1 - $xalign; }
108             ### align: $xalign, $yalign
109              
110             {
111             my ($xpad, $ypad) = $self->get_padding;
112             ### padding: $xpad, $ypad
113             if ($xpad || $ypad) {
114             # apply padding by pretending allocation is that much smaller
115             ### rect shrink from: $clip->values
116              
117             $alloc_x += $xpad;
118             $alloc_y += $ypad;
119             if (($alloc_width -= 2*$xpad) <= 0
120             || ($alloc_height -= 2*$ypad) <= 0
121             || ! ($clip = $clip->intersect (Gtk2::Gdk::Rectangle->new
122             ($alloc_x, $alloc_y,
123             $alloc_width, $alloc_height)))) {
124             ### expose of the pad border region, or allocation smaller than padding
125             ### nothing to draw
126             return 0; # Gtk2::EVENT_PROPAGATE
127             }
128             ### to: $clip->values
129             }
130             }
131              
132             if ($horizontal) {
133             my $clip_x = $clip->x;
134              
135             # vertically according to yalign
136             my $y = $alloc_y + POSIX::floor
137             (($alloc_height - $thickness) * $yalign
138             + 0.5); # round
139             ### $y
140              
141             # ENHANCE-ME: if $y puts the line entirely above or below the clip
142             # region then skip the loop. What can be assumed about how $ythickness
143             # affects how much above and below $y the paint_hline() will go?
144              
145             # horizontal beginning according to xalign
146             my $x = $clip_x
147             + ((POSIX::floor (($alloc_width - $dash_len) * $xalign
148             + 0.5) # round
149             + $alloc_x
150             - $clip_x)
151             % -$dash_step); # at or just before $clip_x
152             ### $x
153              
154             my $end = $clip_x + $clip->width; # clip rect
155             for ( ; $x < $end; $x += $dash_step) {
156             $style->paint_hline ($win, $state, $clip, $self, __PACKAGE__,
157             $x, $x+$dash_len, $y);
158             }
159             } else {
160             my $clip_y = $clip->y;
161              
162             # horizontally according to xalign
163             my $x = $alloc_x + POSIX::floor
164             (($alloc_width - $thickness) * $xalign
165             + 0.5); # round
166              
167             # vertical beginning according to yalign
168             my $y = $clip_y
169             + ((POSIX::floor (($alloc_height - $dash_len) * $yalign
170             + 0.5) # round
171             + $alloc_y
172             - $clip_y)
173             % -$dash_step); # at or just before $clip_y
174              
175             my $end = $clip_y + $clip->height; # clip rect
176             for ( ; $y < $end; $y += $dash_step) {
177             $style->paint_vline ($win, $state, $clip, $self, __PACKAGE__,
178             $y, $y+$dash_len, $x);
179             }
180             }
181             return 0; # Gtk2::EVENT_PROPAGATE
182             }
183              
184             # 'style-set' and 'direction-changed' class closure handler
185             # Sharing style-set and direction-changed saves a little code.
186             #
187             # queue_resize() is not wanted for direction-changed, but does no harm. As
188             # of Gtk 2.18 the GtkWidget code in gtk_widget_real_direction_changed()
189             # (previously gtk_widget_direction_changed()) in fact does a queue_resize()
190             # itself. Could avoid it by not chaining up, but perhaps GtkWidget will do
191             # something important there in the future. Either way a direction change
192             # should be infrequent so it doesn't matter much.
193             #
194             sub _do_style_or_direction {
195             my ($self) = @_;
196             ### Dashes _do_style_or_direction(): @_
197             $self->queue_resize;
198             $self->queue_draw;
199             return shift->signal_chain_from_overridden(@_);
200             }
201              
202             1;
203             __END__