line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# GUI shared functions. |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Copyright 2007, 2008, 2009, 2010, 2011 Kevin Ryde |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# This file is part of Chart. |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
# Chart is free software; you can redistribute it and/or modify it under the |
8
|
|
|
|
|
|
|
# terms of the GNU General Public License as published by the Free Software |
9
|
|
|
|
|
|
|
# Foundation; either version 3, or (at your option) any later version. |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# Chart is distributed in the hope that it will be useful, but WITHOUT ANY |
12
|
|
|
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
13
|
|
|
|
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
14
|
|
|
|
|
|
|
# details. |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License along |
17
|
|
|
|
|
|
|
# with Chart. If not, see <http://www.gnu.org/licenses/>. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
package App::Chart::Gtk2::GUI; |
20
|
1
|
|
|
1
|
|
442
|
use 5.006; |
|
1
|
|
|
|
|
3
|
|
21
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
17
|
|
22
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
26
|
|
23
|
1
|
|
|
1
|
|
4
|
use Carp; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
50
|
|
24
|
1
|
|
|
1
|
|
5
|
use List::Util qw(min max); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
51
|
|
25
|
1
|
|
|
1
|
|
319
|
use Locale::TextDomain ('App-Chart'); |
|
1
|
|
|
|
|
17099
|
|
|
1
|
|
|
|
|
31
|
|
26
|
1
|
|
|
1
|
|
5276
|
use Glib; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
use Gtk2; |
28
|
|
|
|
|
|
|
use Gtk2::Pango; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
use App::Chart; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# uncomment this to run the ### lines |
33
|
|
|
|
|
|
|
#use Smart::Comments; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
Glib::set_application_name (__('Chart')); |
36
|
|
|
|
|
|
|
Gtk2::Window->set_default_icon_from_file |
37
|
|
|
|
|
|
|
(App::Chart::datafilename ('chart.xpm')); |
38
|
|
|
|
|
|
|
Gtk2->CHECK_VERSION(2,12,0) |
39
|
|
|
|
|
|
|
or die "App::Chart needs Gtk 2.12 or higher"; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# Perl-Gtk 1.221 no undef |
45
|
|
|
|
|
|
|
sub menu_set_screen { |
46
|
|
|
|
|
|
|
my ($menu, $screen) = @_; |
47
|
|
|
|
|
|
|
# $screen ||= do { |
48
|
|
|
|
|
|
|
# my $display; |
49
|
|
|
|
|
|
|
# ($display = Gtk2::Gdk::Display->get_default) |
50
|
|
|
|
|
|
|
# && $display->get_default_screen; |
51
|
|
|
|
|
|
|
# }; |
52
|
|
|
|
|
|
|
if ($screen) { |
53
|
|
|
|
|
|
|
$menu->set_screen ($screen); |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub string_width { |
60
|
|
|
|
|
|
|
my ($widget_or_layout, $str) = @_; |
61
|
|
|
|
|
|
|
### string_width(): "$widget_or_layout" |
62
|
|
|
|
|
|
|
### $str |
63
|
|
|
|
|
|
|
my $layout; |
64
|
|
|
|
|
|
|
if ($widget_or_layout->can ('create_pango_layout')) { |
65
|
|
|
|
|
|
|
# if widget instead of layout |
66
|
|
|
|
|
|
|
$layout = $widget_or_layout->create_pango_layout ($str); |
67
|
|
|
|
|
|
|
} else { |
68
|
|
|
|
|
|
|
$layout = $widget_or_layout; |
69
|
|
|
|
|
|
|
$layout->set_text ($str); |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
my ($width, $height) = $layout->get_pixel_size; |
72
|
|
|
|
|
|
|
return $width; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# FIXME: really want to clip to the region, not an enclosing rect. The |
79
|
|
|
|
|
|
|
# style paint might be some nice drawing, but the default is a pretty simple |
80
|
|
|
|
|
|
|
# gdk_draw_layout(). |
81
|
|
|
|
|
|
|
# |
82
|
|
|
|
|
|
|
sub draw_text_centred { |
83
|
|
|
|
|
|
|
my ($widget, $region_or_event, $str) = @_; |
84
|
|
|
|
|
|
|
my $win = $widget->window || return; # unrealized |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
my $clip_rect = $region_or_event && do { |
87
|
|
|
|
|
|
|
if (my $func = $region_or_event->can('get_clipbox')) { |
88
|
|
|
|
|
|
|
$func->($region_or_event); |
89
|
|
|
|
|
|
|
} else { |
90
|
|
|
|
|
|
|
$region_or_event->area; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
}; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
my $alloc = $widget->allocation; |
95
|
|
|
|
|
|
|
my $layout = $widget->create_pango_layout ($str); |
96
|
|
|
|
|
|
|
$layout->set_wrap ('word-char'); |
97
|
|
|
|
|
|
|
$layout->set_width ($alloc->width * Gtk2::Pango::PANGO_SCALE); |
98
|
|
|
|
|
|
|
my ($str_width, $str_height) = $layout->get_pixel_size; |
99
|
|
|
|
|
|
|
my $x = max (0, ($alloc->width - $str_width) / 2); |
100
|
|
|
|
|
|
|
my $y = max (0, ($alloc->height - $str_height) / 2); |
101
|
|
|
|
|
|
|
if ($widget->get_flags & 'no-window') { |
102
|
|
|
|
|
|
|
$x += $alloc->x; |
103
|
|
|
|
|
|
|
$y += $alloc->y; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
my $style = $widget->get_style; |
106
|
|
|
|
|
|
|
$style->paint_layout ($win, |
107
|
|
|
|
|
|
|
$widget->state, |
108
|
|
|
|
|
|
|
1, # use text gc |
109
|
|
|
|
|
|
|
$clip_rect, |
110
|
|
|
|
|
|
|
$widget, |
111
|
|
|
|
|
|
|
'centred-text', |
112
|
|
|
|
|
|
|
$x, $y, $layout); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub chart_style_class { |
119
|
|
|
|
|
|
|
my ($class) = @_; |
120
|
|
|
|
|
|
|
_chart_style_parse(); |
121
|
|
|
|
|
|
|
$class =~ s/:/_/g; |
122
|
|
|
|
|
|
|
Gtk2::Rc->parse_string |
123
|
|
|
|
|
|
|
("class \"$class\" style:gtk \"Chart_style\""); |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
sub chart_style_widget { |
126
|
|
|
|
|
|
|
my ($name) = @_; |
127
|
|
|
|
|
|
|
_chart_style_parse(); |
128
|
|
|
|
|
|
|
Gtk2::Rc->parse_string |
129
|
|
|
|
|
|
|
("widget \"*.$name\" style:application \"Chart_style\""); |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
use constant::defer _chart_style_parse => sub { |
132
|
|
|
|
|
|
|
Gtk2::Rc->parse_string (<<'HERE'); |
133
|
|
|
|
|
|
|
style "Chart_style" { |
134
|
|
|
|
|
|
|
# white on black |
135
|
|
|
|
|
|
|
fg[ACTIVE] = { 1.0, 1.0, 1.0 } |
136
|
|
|
|
|
|
|
fg[NORMAL] = { 1.0, 1.0, 1.0 } |
137
|
|
|
|
|
|
|
fg[PRELIGHT] = { 1.0, 1.0, 1.0 } |
138
|
|
|
|
|
|
|
fg[SELECTED] = { 1.0, 1.0, 1.0 } |
139
|
|
|
|
|
|
|
fg[INSENSITIVE] = { 1.0, 1.0, 1.0 } |
140
|
|
|
|
|
|
|
text[ACTIVE] = { 1.0, 1.0, 1.0 } |
141
|
|
|
|
|
|
|
text[NORMAL] = { 1.0, 1.0, 1.0 } |
142
|
|
|
|
|
|
|
text[PRELIGHT] = { 1.0, 1.0, 1.0 } |
143
|
|
|
|
|
|
|
text[SELECTED] = { 1.0, 1.0, 1.0 } |
144
|
|
|
|
|
|
|
text[INSENSITIVE] = { 1.0, 1.0, 1.0 } |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
bg[ACTIVE] = { 0, 0, 0 } |
147
|
|
|
|
|
|
|
bg[NORMAL] = { 0, 0, 0 } |
148
|
|
|
|
|
|
|
bg[PRELIGHT] = { 0, 0, 0 } |
149
|
|
|
|
|
|
|
bg[SELECTED] = { 0, 0, 0 } |
150
|
|
|
|
|
|
|
bg[INSENSITIVE] = { 0, 0, 0 } |
151
|
|
|
|
|
|
|
base[ACTIVE] = { 0, 0, 0 } |
152
|
|
|
|
|
|
|
base[NORMAL] = { 0, 0, 0 } |
153
|
|
|
|
|
|
|
base[PRELIGHT] = { 0, 0, 0 } |
154
|
|
|
|
|
|
|
base[SELECTED] = { 0, 0, 0 } |
155
|
|
|
|
|
|
|
base[INSENSITIVE] = { 0, 0, 0 } |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
HERE |
158
|
|
|
|
|
|
|
return; # nothing |
159
|
|
|
|
|
|
|
}; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# $uri is either a string or a URI object |
165
|
|
|
|
|
|
|
sub browser_open { |
166
|
|
|
|
|
|
|
my ($uri, $parent_widget) = @_; |
167
|
|
|
|
|
|
|
$uri = "$uri"; # stringize URI object |
168
|
|
|
|
|
|
|
### browser_open(): $uri |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
if (Gtk2->can('show_uri')) { # new in Gtk 2.14 |
171
|
|
|
|
|
|
|
my $screen = $parent_widget && $parent_widget->get_screen; |
172
|
|
|
|
|
|
|
if (eval { Gtk2::show_uri ($screen, $uri); 1 }) { |
173
|
|
|
|
|
|
|
return; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
# possible Glib::Error "operation not supported" on http urls |
176
|
|
|
|
|
|
|
### show_uri() error: $@ |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# The quoting, or lack thereof, expected of the url in openURL is in |
180
|
|
|
|
|
|
|
# mozilla XRemoteService.cpp. It looks for ( ) delims, then the last "," |
181
|
|
|
|
|
|
|
# is the last arg to take off new-window, new-tab, noraise, etc. |
182
|
|
|
|
|
|
|
{ |
183
|
|
|
|
|
|
|
my @command = ('mozilla', '-remote', "openURL($uri,new-window)"); |
184
|
|
|
|
|
|
|
### run: @command |
185
|
|
|
|
|
|
|
if (system (@command) == 0) { |
186
|
|
|
|
|
|
|
return; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
### run status: $? |
189
|
|
|
|
|
|
|
### error: "$!" |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
{ |
192
|
|
|
|
|
|
|
my @command = ('sensible-browser', $uri); |
193
|
|
|
|
|
|
|
if (_spawn (@command)) { return } |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
{ |
196
|
|
|
|
|
|
|
my @command = ('mozilla', $uri); |
197
|
|
|
|
|
|
|
if (_spawn (@command)) { return } |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
warn "Cannot run browser: none of show_uri, sensible-browser or mozilla work"; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
sub _spawn { |
202
|
|
|
|
|
|
|
my @command = @_; |
203
|
|
|
|
|
|
|
### spawn(): @command |
204
|
|
|
|
|
|
|
require Proc::SyncExec; |
205
|
|
|
|
|
|
|
my $pid = Proc::SyncExec::sync_exec (\&_spawn_detach, @command); |
206
|
|
|
|
|
|
|
if (! defined $pid) { |
207
|
|
|
|
|
|
|
### cannot run: $! |
208
|
|
|
|
|
|
|
return 0; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
if (waitpid ($pid, 0) != $pid) { |
211
|
|
|
|
|
|
|
warn "Error waiting spawned $pid: $!\n"; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
return 1; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
sub _spawn_detach { |
216
|
|
|
|
|
|
|
if (my $pid = Proc::SyncExec::fork_retry()) { |
217
|
|
|
|
|
|
|
POSIX::_exit (0); # parent |
218
|
|
|
|
|
|
|
} else { |
219
|
|
|
|
|
|
|
## no critic (RequireCheckingReturnValueOfEval) |
220
|
|
|
|
|
|
|
eval { POSIX::setsid() }; |
221
|
|
|
|
|
|
|
return 1; # ok, child continues |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
#----------------------------------------------------------------------------- |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
sub color_object { |
228
|
|
|
|
|
|
|
my ($widget, $colour_str) = @_; |
229
|
|
|
|
|
|
|
if (defined $colour_str) { |
230
|
|
|
|
|
|
|
require App::Chart::Gtk2::Ex::GdkColorAlloc; |
231
|
|
|
|
|
|
|
return ($colour_str, |
232
|
|
|
|
|
|
|
$widget->{'color'}->{$colour_str} |
233
|
|
|
|
|
|
|
||= App::Chart::Gtk2::Ex::GdkColorAlloc->new (widget => $widget, |
234
|
|
|
|
|
|
|
color => $colour_str)); |
235
|
|
|
|
|
|
|
} else { |
236
|
|
|
|
|
|
|
return ('fg', $widget->style->fg($widget->state)); |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub gc_for_colour { |
241
|
|
|
|
|
|
|
my ($widget, $colour_str) = @_; |
242
|
|
|
|
|
|
|
($colour_str, my $color_obj) = color_object ($widget, $colour_str); |
243
|
|
|
|
|
|
|
return ($widget->{'gc_solid'}->{$colour_str} ||= do { |
244
|
|
|
|
|
|
|
require App::Chart::Gtk2::Ex::GtkGCBits; |
245
|
|
|
|
|
|
|
App::Chart::Gtk2::Ex::GtkGCBits->get_for_widget |
246
|
|
|
|
|
|
|
($widget, { foreground => $color_obj, |
247
|
|
|
|
|
|
|
line_style => 'solid', |
248
|
|
|
|
|
|
|
line_width => 0 }) |
249
|
|
|
|
|
|
|
}); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
sub gc_for_colour_dashed { |
252
|
|
|
|
|
|
|
my ($widget, $colour_str) = @_; |
253
|
|
|
|
|
|
|
($colour_str, my $color_obj) = color_object ($widget, $colour_str); |
254
|
|
|
|
|
|
|
return ($widget->{'gc_dash'}->{$colour_str} ||= do { |
255
|
|
|
|
|
|
|
require App::Chart::Gtk2::Ex::GtkGCBits; |
256
|
|
|
|
|
|
|
App::Chart::Gtk2::Ex::GtkGCBits->get_for_widget |
257
|
|
|
|
|
|
|
($widget, { foreground => $color_obj, |
258
|
|
|
|
|
|
|
line_style => 'on_off_dash', |
259
|
|
|
|
|
|
|
line_width => 0 }) |
260
|
|
|
|
|
|
|
}); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
1; |
264
|
|
|
|
|
|
|
__END__ |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# =for stopwords Pango undef Gtk ListStore TreeStore renderer TreeViewColumn TreeView |
267
|
|
|
|
|
|
|
# |
268
|
|
|
|
|
|
|
# =head1 NAME |
269
|
|
|
|
|
|
|
# |
270
|
|
|
|
|
|
|
# App::Chart::Gtk2::GUI -- miscellaneous graphical interface functions |
271
|
|
|
|
|
|
|
# |
272
|
|
|
|
|
|
|
# =head1 SYNOPSIS |
273
|
|
|
|
|
|
|
# |
274
|
|
|
|
|
|
|
# use App::Chart::Gtk2::GUI; |
275
|
|
|
|
|
|
|
# |
276
|
|
|
|
|
|
|
# =head1 FUNCTIONS |
277
|
|
|
|
|
|
|
# |
278
|
|
|
|
|
|
|
# =over 4 |
279
|
|
|
|
|
|
|
# |
280
|
|
|
|
|
|
|
# =cut |
281
|
|
|
|
|
|
|
# |
282
|
|
|
|
|
|
|
# =item App::Chart::Gtk2::GUI::string_width ($widget_or_layout, $str) |
283
|
|
|
|
|
|
|
# |
284
|
|
|
|
|
|
|
# Return the width in pixels of C<$str> in the font of the given widget or |
285
|
|
|
|
|
|
|
# layout (either a C<Gtk2::Widget> or a C<Gtk2::Pango::Layout> object). |
286
|
|
|
|
|
|
|
# |
287
|
|
|
|
|
|
|
# =cut |
288
|
|
|
|
|
|
|
# |
289
|
|
|
|
|
|
|
# =item C<< App::Chart::Gtk2::GUI::draw_text_centred ($widget, $region_or_event, $string) >> |
290
|
|
|
|
|
|
|
# |
291
|
|
|
|
|
|
|
# Draw C<string> centred in the window of C<widget>. If C<widget> isn't |
292
|
|
|
|
|
|
|
# realized yet then do nothing. Pango C<word-char> wrapping is enabled, so |
293
|
|
|
|
|
|
|
# the string is not truncated if it's wider than the window. |
294
|
|
|
|
|
|
|
# |
295
|
|
|
|
|
|
|
# Both windowed and no-window widgets can be given here. C<$region_or_event> |
296
|
|
|
|
|
|
|
# gives a region to clip to, or undef to draw everything. |
297
|
|
|
|
|
|
|
# |
298
|
|
|
|
|
|
|
# =cut |
299
|
|
|
|
|
|
|
# |
300
|
|
|
|
|
|
|
# =item App::Chart::Gtk2::GUI::chart_style_class ($class) |
301
|
|
|
|
|
|
|
# |
302
|
|
|
|
|
|
|
# =item App::Chart::Gtk2::GUI::chart_style_widget ($widgetname) |
303
|
|
|
|
|
|
|
# |
304
|
|
|
|
|
|
|
# Setup package C<$class> or widget C<$widgetname> (per |
305
|
|
|
|
|
|
|
# C<< $widget->set_name >>) to get the Chart graph style settings, which means |
306
|
|
|
|
|
|
|
# black background and white foreground and text. |
307
|
|
|
|
|
|
|
# |
308
|
|
|
|
|
|
|
# C<$class> can include colons like C<"App::Chart::Gtk2::HAxis"> and they're turned |
309
|
|
|
|
|
|
|
# into underscores like C<"App__Chart__Gtk2__HAxis"> which is the Gtk class |
310
|
|
|
|
|
|
|
# name. |
311
|
|
|
|
|
|
|
# |
312
|
|
|
|
|
|
|
# =cut |
313
|
|
|
|
|
|
|
# |
314
|
|
|
|
|
|
|
# =back |