File Coverage

blib/lib/Gtk2/Ex/Units.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             # Copyright 2007, 2008, 2009, 2010, 2011, 2012 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-WidgetBits.
4             #
5             # Gtk2-Ex-WidgetBits is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Gtk2-Ex-WidgetBits 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-WidgetBits. If not, see .
17              
18             package Gtk2::Ex::Units;
19 2     2   1395 use 5.008;
  2         15  
  2         82  
20 2     2   10 use strict;
  2         3  
  2         56  
21 2     2   10 use warnings;
  2         3  
  2         54  
22 2     2   9 use Carp;
  2         3  
  2         153  
23 2     2   8645 use Gtk2::Pango; # for PANGO_SCALE
  0            
  0            
24              
25             use Exporter;
26             our @ISA = ('Exporter');
27             our @EXPORT_OK = qw(em ex char_width digit_width line_height
28             width height
29             set_default_size_with_subsizes
30             size_request_with_subsizes);
31             our %EXPORT_TAGS = (all => \@EXPORT_OK);
32              
33             our $VERSION = 48;
34              
35             # uncomment this to run the ### lines
36             #use Smart::Comments;
37              
38              
39             #------------------------------------------------------------------------------
40              
41             if (Gtk2::Gdk::Screen->can('get_width')) {
42             eval "\n#line ".(__LINE__+1)." \"".__FILE__."\"\n" . <<'HERE' or die;
43             ### _to_screen() using target->screen
44              
45             sub _to_screen {
46             my ($target) = @_;
47             if (my $func = $target->can('get_screen')) {
48             $target = &$func ($target);
49             }
50             return ($target
51             || croak "No screen for target $target");
52             }
53             1
54              
55             HERE
56             } else {
57             eval "\n#line ".(__LINE__+1)." \"".__FILE__."\"\n" . <<'HERE' or die;
58             ### _to_screen() using Gtk 2.0.x single-screen
59              
60             {
61             package Gtk2::Ex::Units::DummyScreen;
62             sub get_width { return Gtk2::Gdk->screen_width }
63             sub get_height { return Gtk2::Gdk->screen_height }
64             sub get_width_mm { return Gtk2::Gdk->screen_width_mm }
65             sub get_height_mm { return Gtk2::Gdk->screen_height_mm }
66             }
67             {
68             my $dummy_screen = bless {}, 'Gtk2::Ex::Units::DummyScreen';
69             ### $dummy_screen
70             sub _to_screen {
71             return $dummy_screen;
72             }
73             }
74             1
75             HERE
76             }
77              
78             sub _pango_rect {
79             my ($target, $str, $want_logical) = @_;
80             ### _pango_rect(): [$target, $str]
81              
82             if ($target->can('create_pango_layout')) {
83             # if widget instead of layout
84             $target = $target->create_pango_layout ($str);
85             } else {
86             $target->set_text ($str);
87             }
88             # get_extents() returns ($ink_rect,$logical_rect)
89             return ($target->get_extents)[$want_logical||0];
90             }
91             sub _pango_metrics {
92             my ($target) = @_;
93             my $context = ($target->can('pango_context')
94             ? $target->pango_context # Pango::Layout
95             : $target->get_pango_context); # Gtk2::Widget
96              
97             my $fontdesc = (($target->can('get_font_description') # Pango::Layout
98             && $target->get_font_description)
99             || ($target->can('style') # Gtk2::Widget
100             && $target->style->font_desc)
101             || $context->get_font_description); # if unset
102             return $context->get_metrics ($fontdesc, $context->get_language);
103             }
104              
105             #------------------------------------------------------------------------------
106              
107             sub em {
108             my ($target) = @_;
109             # logical rect to include inter-char spacing, so that "3 em" is roughly
110             # the width needed for "MMM"
111             return _pango_rect($target,'M',1)->{'width'} / Gtk2::Pango::PANGO_SCALE;
112             }
113             sub ex {
114             my ($target) = @_;
115             return _pango_rect($target,'x')->{'height'} / Gtk2::Pango::PANGO_SCALE;
116             }
117             sub line_height {
118             my ($target) = @_;
119             # ink rect of newline is the line height plus line spacing
120             # (whereas log rect of empty is line height without line spacing,
121             # or log rect of newline is two line heights plus one line spacing)
122             return _pango_rect($target,"\n",0)->{'height'} / Gtk2::Pango::PANGO_SCALE;
123             }
124             sub char_width {
125             my ($target) = @_;
126             return _pango_metrics($target)->get_approximate_char_width
127             / Gtk2::Pango::PANGO_SCALE;
128             }
129             sub digit_width {
130             my ($target) = @_;
131             return _pango_metrics($target)->get_approximate_digit_width
132             / Gtk2::Pango::PANGO_SCALE;
133             }
134              
135             #------------------------------------------------------------------------------
136             # width
137              
138             use constant { _pixel => 1,
139             _MILLIMETRES_PER_INCH => 25.4 };
140              
141             sub _mm_width {
142             my ($target) = @_;
143             my $screen = _to_screen($target);
144             return $screen->get_width / $screen->get_width_mm;
145             }
146             sub _inch_width {
147             my ($target) = @_;
148             return _MILLIMETRES_PER_INCH * _mm_width($target);
149             }
150             sub _screen_width {
151             my ($target) = @_;
152             return _to_screen($target)->get_width;
153             }
154             my %width = (pixel => \&_pixel,
155             pixels => \&_pixel,
156             char => \&char_width,
157             chars => \&char_width,
158             em => \&em,
159             ems => \&em,
160             digit => \&digit_width,
161             digits => \&digit_width,
162             mm => \&_mm_width,
163             inch => \&_inch_width,
164             inches => \&_inch_width,
165             screen => \&_screen_width,
166             screens => \&_screen_width,
167             );
168              
169             #------------------------------------------------------------------------------
170             # height
171              
172             sub _mm_height {
173             my ($target) = @_;
174             my $screen = _to_screen($target);
175             return $screen->get_height / $screen->get_height_mm;
176             }
177             sub _inch_height {
178             my ($target) = @_;
179             return _MILLIMETRES_PER_INCH * _mm_height($target);
180             }
181             sub _screen_height {
182             my ($target) = @_;
183             return _to_screen($target)->get_height;
184             }
185              
186             my %height = (pixel => \&_pixel,
187             pixels => \&_pixel,
188             ex => \&ex,
189             exes => \&ex,
190             line => \&line_height,
191             lines => \&line_height,
192             mm => \&_mm_height,
193             inch => \&_inch_height,
194             inches => \&_inch_height,
195             screen => \&_screen_height,
196             screens => \&_screen_height,
197             );
198              
199             #------------------------------------------------------------------------------
200             # shared
201              
202             sub width {
203             push @_, \%width, \%height;
204             goto \&_units;
205             }
206             sub height {
207             push @_, \%height, \%width;
208             goto \&_units;
209             }
210             sub _units {
211             my ($target, $str, $h, $other) = @_;
212             ### _units str: $str
213              
214             # it's easy to forget the $target arg, so check
215             @_ == 4 or croak 'Units width()/height() expects 2 arguments';
216              
217             my ($amount,$unit) = ($str =~ /(.*?)\s*([[:alpha:]_]+)$/s)
218             or return $str;
219              
220             if (my $func = $h->{$unit}) {
221             return $amount * &$func ($target);
222             }
223             croak "Unrecognised unit \"$unit\"";
224             }
225              
226              
227             #-----------------------------------------------------------------------------
228              
229             sub set_default_size_with_subsizes {
230             my $window = $_[0];
231             my $req = size_request_with_subsizes (@_);
232             $window->set_default_size ($req->width, $req->height);
233             }
234              
235             sub size_request_with_subsizes {
236             my ($widget, @elems) = @_;
237              
238             # Each change is guarded as it's made, in case the action on a subsequent
239             # $widget provokes an error, eg. if not a Gtk2::Widget. A guard object
240             # for each widget is a little less code than say an array of saved
241             # settings and a loop to undo them.
242              
243             require Scope::Guard;
244             my @guard;
245              
246             foreach my $elem (@elems) {
247             my ($subwidget, $width, $height) = @$elem;
248             my ($save_width, $save_height) = $subwidget->get_size_request;
249             my $width_pixels = (defined $width
250             ? width($subwidget,$width)
251             : $save_width);
252             my $height_pixels = (defined $height
253             ? height($subwidget,$height)
254             : $save_height);
255             push @guard, Scope::Guard->new
256             (sub { $subwidget->set_size_request ($save_width, $save_height) });
257             $subwidget->set_size_request ($width_pixels, $height_pixels);
258             }
259              
260             return $widget->size_request;
261             }
262              
263             1;
264             __END__