File Coverage

blib/lib/App/Chart/Gtk2/Ticker.pm
Criterion Covered Total %
statement 12 14 85.7
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 17 19 89.4


line stmt bran cond sub pod time code
1             # Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2017 Kevin Ryde
2              
3             # This file is part of Chart.
4             #
5             # Chart is free software; you can redistribute it and/or modify it under the
6             # terms of the GNU General Public License as published by the Free Software
7             # Foundation; either version 3, or (at your option) any later version.
8             #
9             # Chart is distributed in the hope that it will be useful, but WITHOUT ANY
10             # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11             # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12             # details.
13             #
14             # You should have received a copy of the GNU General Public License along
15             # with Chart. If not, see <http://www.gnu.org/licenses/>.
16              
17             package App::Chart::Gtk2::Ticker;
18 2     2   1094 use 5.008;
  2         8  
19 2     2   12 use strict;
  2         5  
  2         45  
20 2     2   12 use warnings;
  2         5  
  2         66  
21 2     2   11 use Carp;
  2         4  
  2         125  
22 2     2   350 use Gtk2 1.200; # for working TreeModelFilter modify_func
  0            
  0            
23             use Gtk2::Ex::TickerView;
24             use List::Util qw(min max);
25             use Locale::TextDomain 'App-Chart';
26              
27             use App::Chart::Glib::Ex::MoreUtils;
28             use Glib::Ex::SignalIds;
29             use Gtk2::Ex::Units;
30             use App::Chart;
31             use App::Chart::Gtk2::GUI;
32             use App::Chart::Gtk2::Symlist;
33              
34             # uncomment this to run the ### lines
35             #use Smart::Comments;
36              
37             BEGIN {
38             Glib::Type->register_enum ('App::Chart::Gtk2::Ticker::menu_position',
39             'centre' => 0,
40             'pointer' => 1);
41             }
42             use Glib::Object::Subclass
43             'Gtk2::Ex::TickerView',
44             signals => { button_press_event => \&_do_button_press_event,
45              
46             menu_created => { param_types => ['Gtk2::Menu'],
47             return_type => undef,
48             },
49             menu_popup => { param_types => ['Glib::Int',
50             'App::Chart::Gtk2::Ticker::menu_position'],
51             return_type => undef,
52             class_closure => \&_do_menu_popup_action,
53             flags => [ 'run-last', 'action' ],
54             },
55             },
56             properties => [Glib::ParamSpec->object
57             ('symlist',
58             'symlist',
59             'App::Chart::Gtk2::Symlist object for the symbols to display.',
60             # App::Chart::Gtk2::Symlist::Join is a ListModelConcat not
61             # a Symlist subclass, allow that by just Glib::Object here
62             'Glib::Object',
63             Glib::G_PARAM_READWRITE),
64             ];
65             App::Chart::Gtk2::GUI::chart_style_class (__PACKAGE__);
66              
67             # priority level "gtk" treating this as widget level default, for overriding
68             # by application or user RC
69             Gtk2::Rc->parse_string (<<'HERE');
70             binding "App__Chart__Gtk2__Ticker_keys" {
71             bind "F10" { "menu_popup" (0, centre) }
72             bind "Pointer_Button3" { "menu_popup" (3, pointer) }
73             }
74             class "App__Chart__Gtk2__Ticker" binding:gtk "App__Chart__Gtk2__Ticker_keys"
75             HERE
76              
77             sub INIT_INSTANCE {
78             my ($self) = @_;
79             $self->add_events (['button-press-mask', 'key-press-mask']);
80             $self->set (fixed_height_mode => 1);
81             $self->set_flags ('can-focus');
82              
83             my $renderer = Gtk2::CellRendererText->new;
84             $renderer->set (xpad => Gtk2::Ex::Units::em($self));
85             $self->pack_start ($renderer, 0);
86             $self->set_attributes ($renderer, markup => 0);
87              
88             # default Favourites symlist, unless it's empty and All is not in which
89             # case use All
90             require App::Chart::Gtk2::Symlist::Favourites;
91             my $symlist = App::Chart::Gtk2::Symlist::Favourites->instance;
92             if ($symlist->is_empty) {
93             require App::Chart::Gtk2::Symlist::All;
94             my $all = App::Chart::Gtk2::Symlist::All->instance;
95             if (! $all->is_empty) {
96             $symlist = $all;
97             }
98             }
99             $self->set (symlist => $symlist);
100             }
101              
102             sub FINALIZE_INSTANCE {
103             my ($self) = @_;
104             # Gtk2::Menu doesn't go away just by weakening if currently popped-up
105             # (because it's then a toplevel presumably); doing ->popdown() works, but
106             # ->destroy() seems the best idea
107             if (my $menu = $self->{'menu'}) {
108             $menu->destroy;
109             }
110             }
111              
112             sub SET_PROPERTY {
113             my ($self, $pspec, $newval) = @_;
114             my $pname = $pspec->get_name;
115             my $oldval = $self->{$pname};
116              
117             if ($pname eq 'symlist' && ($newval||0) != ($oldval||0)) {
118             ### Ticker: "$self changed symlist"
119             my $symlist = $newval;
120             if (defined $symlist && ! $symlist->isa('App::Chart::Gtk2::Symlist')) {
121             croak "App::Chart::Gtk2::Ticker.symlist must be an App::Chart::Gtk2::Symlist";
122             }
123             $self->{$pname} = $newval; # per default GET_PROPERTY
124              
125             require App::Chart::Gtk2::TickerModel;
126             my $model = $self->{'symlist_model'}
127             = $symlist && App::Chart::Gtk2::TickerModel->new ($symlist);
128              
129             my $ref_weak_self = App::Chart::Glib::Ex::MoreUtils::ref_weak ($self);
130             $self->{'symlist_model_ids'} = $model && do {
131             Glib::Ex::SignalIds->new
132             ($model,
133             $model->signal_connect (row_deleted => \&_do_row_deleted,
134             $ref_weak_self),
135             $model->signal_connect (row_inserted => \&_do_row_inserted,
136             $ref_weak_self))
137             };
138              
139             $self->set (model => $model);
140             $self->{'showing_empty'} = 0;
141             _check_empty ($self);
142              
143             } else {
144             $self->{$pname} = $newval; # per default GET_PROPERTY
145             }
146             }
147              
148             # 'button-press-event' class closure
149             sub _do_button_press_event {
150             my ($self, $event) = @_;
151             require App::Chart::Gtk2::Ex::BindingBits;
152             App::Chart::Gtk2::Ex::BindingBits::activate_button_event
153             ('App__Chart__Gtk2__Ticker_keys', $event, $self);
154             return shift->signal_chain_from_overridden(@_);
155             }
156              
157             # 'menu-popup' action signal class closure
158             sub _do_menu_popup_action {
159             my ($self, $button, $where) = @_;
160             ### Ticker: "menu-popup action $button $where"
161              
162             my $position_func; # undef for mouse position if $where empty or undef
163             if ($where && $where ne 'pointer') {
164             $position_func = $self->can("menu_position_func_$where");
165             unless ($position_func) {
166             Glib->warning (undef, warn "Ticker: unrecognised menu position '$where', default to mouse pointer");
167             }
168             }
169             my $symbol;
170             if (! $where || $where eq 'pointer') {
171             my $event = Gtk2->get_current_event;
172             require Gtk2::Ex::WidgetBits;
173             if (my ($x,$y) = Gtk2::Ex::WidgetBits::xy_root_to_widget ($self,
174             $event->x_root,
175             $event->y_root)) {
176             if (my $path = $self->get_path_at_pos ($x, $y)) {
177             if (my $model = $self->get('symlist')) {
178             if (my $iter = $model->get_iter ($path)) {
179             $symbol = $model->get($iter, $model->COL_SYMBOL);
180             }
181             }
182             }
183             }
184             }
185             my $menu = $self->menu;
186             $menu->set_screen ($self->get_screen);
187             $menu->set (symbol => $symbol);
188             $menu->popup (undef, # parent menushell
189             undef, # parent menuitem
190             $position_func,
191             App::Chart::Glib::Ex::MoreUtils::ref_weak($self), # position func data
192             $button,
193             Gtk2->get_current_event_time);
194             }
195              
196             # 'row-deleted' signal on symlist model
197             sub _do_row_deleted {
198             my ($model, $path, $ref_weak_self) = @_;
199             my $self = $$ref_weak_self || return;
200             _check_empty ($self);
201             }
202             # 'row-inserted' signal on symlist model
203             sub _do_row_inserted {
204             my ($model, $path, $iter, $ref_weak_self) = @_;
205             my $self = $$ref_weak_self || return;
206             _check_empty ($self);
207             }
208             sub _check_empty {
209             my ($self) = @_;
210             my $symlist = $self->{'symlist'};
211             my $empty = !$symlist || $symlist->is_empty;
212              
213             if ($empty && ! $self->{'showing_empty'}) {
214             # become empty
215             my $empty_model = ($self->{'empty_model'} ||= do {
216             my $liststore = Gtk2::ListStore->new ('Glib::String');
217             $liststore->append;
218             $liststore;
219             });
220             my $message = $symlist
221             ? __x('{symlistname} list is empty ... ',
222             symlistname => $symlist->name)
223             : __('No symlist ... ');
224             $empty_model->set_value ($empty_model->get_iter_first,
225             0 => $message);
226             $self->set (model => $empty_model);
227             $self->{'showing_empty'} = 1;
228              
229             } elsif ($self->{'showing_empty'} && ! $empty) {
230             $self->set (model => $self->{'symlist_model'});
231             $self->{'showing_empty'} = 0;
232             }
233             }
234              
235             # create and return Gtk2::Menu widget
236             sub menu {
237             my ($self) = @_;
238             return ($self->{'menu'} ||= do {
239             ### Ticker menu doesn't exist, creating
240             require App::Chart::Gtk2::TickerMenu;
241             my $menu = App::Chart::Gtk2::TickerMenu->new (ticker => $self);
242             $self->signal_emit ('menu-created', $menu);
243             $menu;
244             });
245             }
246              
247             # and also 'activate' signal on Help menu item
248             sub help {
249             require App::Chart::Manual;
250             App::Chart::Manual->open(__p('manual-node','Ticker'));
251             }
252              
253             #------------------------------------------------------------------------------
254             # position funcs
255              
256             sub menu_position_func_centre {
257             require Gtk2::Ex::MenuBits;
258             goto &Gtk2::Ex::MenuBits::position_widget_topcentre;
259             }
260              
261              
262             1;
263             __END__
264              
265             =for stopwords symlist submenu Symlist boolean ie
266              
267             =head1 NAME
268              
269             App::Chart::Gtk2::Ticker -- stock ticker widget
270              
271             =head1 SYNOPSIS
272              
273             use App::Chart::Gtk2::Ticker;
274             my $ticker = App::Chart::Gtk2::Ticker->new;
275              
276             my $symlist = App::Chart::Gtk2::Symlist::All->instance;
277             $ticker->set (symlist => $symlist);
278              
279             =head1 WIDGET HIERARCHY
280              
281             C<App::Chart::Gtk2::Ticker> is a subclass of C<Gtk2::Ex::TickerView>,
282              
283             Gtk2::Widget
284             ...
285             Gtk2::Ex::TickerView
286             App::Chart::Gtk2::Ticker
287              
288             =head1 DESCRIPTION
289              
290             A C<App::Chart::Gtk2::Ticker> widget showing stock quotes scrolling across the
291             window for a given symlist (L<App::Chart::Gtk2::Symlist>)
292              
293             =head1 FUNCTIONS
294              
295             =over 4
296              
297             =item C<< App::Chart::Gtk2::Ticker->new (key => value, ...) >>
298              
299             Create and return a C<App::Chart::Gtk2::Ticker> widget. Optional key/value
300             pairs can be given to set initial properties as per
301             C<< Glib::Object->new >>.
302              
303             =item C<< $ticker->menu() >>
304              
305             Return the C<Gtk2::Menu> which is popped up by mouse button 3 in the ticker.
306             An application can add items to this, such as "Hide" or "Quit", or perhaps a
307             submenu to change what's displayed.
308              
309             =item C<< $ticker->refresh() >>
310              
311             Download fresh prices for the symbols displayed. This is the "Refresh" item
312             in the button-3 menu.
313              
314             =item C<< $ticker->help() >>
315              
316             Open the Chart manual at the section on the ticker. This is the "Help" item
317             in the button-3 menu.
318              
319             =back
320              
321             =head1 PROPERTIES
322              
323             =over 4
324              
325             =item symlist (C<App::Chart::Gtk2::Symlist>, default favourites or all)
326              
327             A Symlist object which is the stock symbols to display.
328              
329             =back
330              
331             =head1 SEE ALSO
332              
333             L<App::Chart::Gtk2::TickerMain>,
334             L<App::Chart::Gtk2::Symlist>,
335             C<Gtk2::Ex::TickerView>
336              
337             =head1 HOME PAGE
338              
339             L<http://user42.tuxfamily.org/chart/index.html>
340              
341             =head1 LICENCE
342              
343             Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2017 Kevin Ryde
344              
345             Chart is free software; you can redistribute it and/or modify it under the
346             terms of the GNU General Public License as published by the Free Software
347             Foundation; either version 3, or (at your option) any later version.
348              
349             Chart is distributed in the hope that it will be useful, but WITHOUT ANY
350             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
351             FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
352             details.
353              
354             You should have received a copy of the GNU General Public License along with
355             Chart; see the file F<COPYING>. Failing that, see
356             L<http://www.gnu.org/licenses/>.
357              
358             =cut