File Coverage

blib/lib/App/Chart/Gtk2/AnnotationsDialog.pm
Criterion Covered Total %
statement 9 11 81.8
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 13 15 86.6


line stmt bran cond sub pod time code
1             # Copyright 2008, 2009, 2010, 2011, 2013 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::AnnotationsDialog;
18 1     1   405 use 5.010;
  1         2  
19 1     1   4 use strict;
  1         2  
  1         16  
20 1     1   4 use warnings;
  1         1  
  1         20  
21 1     1   244 use Gtk2;
  0            
  0            
22             use Locale::TextDomain ('App-Chart');
23              
24             use Glib::Ex::ConnectProperties 15; # v.15 for tree-selection#not-empty
25             use Gtk2::Ex::DateSpinner;
26             use Gtk2::Ex::DateSpinner::CellRenderer;
27             use Gtk2::Ex::Units;
28             use App::Chart;
29             use App::Chart::Annotation;
30             use App::Chart::DBI;
31              
32             # uncomment this to run the ### lines
33             # use Smart::Comments;
34              
35             use Glib::Object::Subclass
36             'Gtk2::Dialog',
37             properties => [ Glib::ParamSpec->string
38             ('symbol',
39             __('Symbol'),
40             'Blurb.',
41             '', # default
42             Glib::G_PARAM_READWRITE),
43             ];
44              
45             use constant { RESPONSE_DELETE => 0,
46             RESPONSE_TODAY => 1 };
47              
48             use constant { COL_ID => 0,
49             COL_DATE => 1,
50             COL_NOTE => 2 };
51              
52             sub INIT_INSTANCE {
53             my ($self) = @_;
54             ### AnnotationsDialog() INIT_INSTANCE ...
55              
56             $self->set_title (__('Chart: Annotations'));
57             $self->add_buttons (__('_Today') => RESPONSE_TODAY,
58             'gtk-delete' => RESPONSE_DELETE,
59             'gtk-close' => 'close',
60             'gtk-help' => 'help');
61             $self->signal_connect (response => \&_do_response);
62              
63             my $vbox = $self->vbox;
64             my $em = Gtk2::Ex::Units::em($self);
65              
66             my $heading = $self->{'heading'} = Gtk2::Label->new (' ');
67             $vbox->pack_start ($heading, 0,0,0);
68              
69             my $scrolled = Gtk2::ScrolledWindow->new;
70             $scrolled->set (hscrollbar_policy => 'automatic',
71             vscrollbar_policy => 'automatic');
72             $vbox->pack_start ($scrolled, 1,1,0);
73              
74             my $store = $self->{'store'}
75             = Gtk2::ListStore->new ('Glib::String', 'Glib::String', 'Glib::String');
76              
77             my $treeview = $self->{'treeview'}
78             = Gtk2::TreeView->new_with_model ($store);
79             $treeview->set (headers_visible => 1,
80             reorderable => 0);
81             $scrolled->add ($treeview);
82              
83             my $selection = $treeview->get_selection;
84             $selection->signal_connect (changed => \&_do_selection_changed, $self);
85             $selection->set_mode ('single');
86              
87             Glib::Ex::ConnectProperties->new
88             ([$selection, 'tree-selection#not-empty'],
89             [$self, 'response-sensitive#'.RESPONSE_DELETE ]);
90              
91             {
92             my $renderer = Gtk2::Ex::DateSpinner::CellRenderer->new (xalign => 0,
93             ypad => 0,
94             editable => 1);
95             $renderer->signal_connect (edited => \&_do_date_cell_edited, $self);
96             my $column = Gtk2::TreeViewColumn->new_with_attributes
97             (__('Date'), $renderer, text => COL_DATE);
98             $column->set (sizing => 'fixed',
99             fixed_width => 8*$em,
100             resizable => 1);
101             $treeview->append_column ($column);
102             }
103             {
104             my $renderer = Gtk2::CellRendererText->new;
105             $renderer->set (xalign => 0,
106             ypad => 0,
107             editable => 1);
108             $renderer->signal_connect (edited => \&_do_note_cell_edited, $self);
109             my $column = Gtk2::TreeViewColumn->new_with_attributes
110             (__('Note'), $renderer, text => COL_NOTE);
111             $treeview->append_column ($column);
112             }
113              
114             my $hbox = Gtk2::HBox->new (0, 0);
115             $vbox->pack_start ($hbox, 0,0,0);
116              
117             ### $hbox
118             my $datespinner = $self->{'datespinner'} = Gtk2::Ex::DateSpinner->new;
119             ### $datespinner
120             $datespinner->set_today;
121             $hbox->pack_start ($datespinner, 0,0,0);
122              
123             my $entry = $self->{'entry'} = Gtk2::Entry->new;
124             $hbox->pack_start ($entry, 1,1, 0.5 * Gtk2::Ex::Units::em($entry));
125             $entry->grab_focus;
126             $entry->signal_connect (activate => \&_do_entry_enter, $self);
127              
128             my $button = Gtk2::Button->new_with_label (__('Enter'));
129             $hbox->pack_start ($button, 0,0,0);
130             $button->signal_connect (clicked => \&_do_entry_enter, $self);
131              
132             App::Chart::chart_dirbroadcast()->connect_for_object
133             ('data-changed', \&_do_data_changed, $self);
134              
135             $vbox->show_all;
136              
137             # with sensible annotations list and entry sizes
138             Gtk2::Ex::Units::set_default_size_with_subsizes
139             ($self,
140             [$scrolled, -1, '15 lines'],
141             [$entry, '40 ems', -1]);
142             ### AnnotationsDialog() INIT_INSTANCE finished ...
143             }
144              
145             sub SET_PROPERTY {
146             my ($self, $pspec, $newval) = @_;
147             ### AnnotationsDialog() SET_PROPERTY ...
148              
149             my $pname = $pspec->get_name;
150             $self->{$pname} = $newval; # per default GET_PROPERTY
151              
152             if ($pname eq 'symbol') {
153             my $symbol = $newval;
154             $self->set_title (__x('Chart: Annotations: {symbol}',
155             symbol => $symbol));
156             $self->{'heading'}->set_text ($symbol);
157             _fill ($self);
158             }
159             }
160              
161             sub _do_data_changed {
162             my ($self, $symbol_hash) = @_;
163             my $symbol = $self->{'symbol'} // return;
164             if (exists $symbol_hash->{$symbol}) {
165             _fill ($self);
166             }
167             }
168              
169             sub _fill {
170             my ($self) = @_;
171             ### AnnotationsDialog _fill()
172             my $store = $self->{'store'};
173             $store->clear;
174              
175             my $symbol = $self->{'symbol'};
176             if (defined $symbol) {
177             require App::Chart::Series::Database;
178             my $series = App::Chart::Series::Database->new ($symbol);
179             my $aref = $series->annotations;
180             ### $aref
181              
182             foreach my $ann (@$aref) {
183             $store->set ($store->prepend,
184             COL_ID, $ann->{'id'},
185             COL_DATE, $ann->{'date'},
186             COL_NOTE, $ann->{'note'});
187             }
188             }
189             }
190              
191             # 'response' signal handler
192             sub _do_response {
193             my ($self, $response) = @_;
194             ### AnnotationsDialog _do_response()
195             ### $response
196              
197             if ($response eq RESPONSE_TODAY) {
198             $self->{'datespinner'}->set_today;
199              
200             } elsif ($response eq RESPONSE_DELETE) {
201             _do_delete ($self);
202              
203             } elsif ($response eq 'close') {
204             # as per a keyboard close, defaults to raising 'delete-event', which in
205             # turn defaults to a destroy
206             $self->signal_emit ('close');
207              
208             } elsif ($response eq 'help') {
209             require App::Chart::Manual;
210             App::Chart::Manual->open(__p('manual-node','Annotations'), $self);
211             }
212             }
213              
214             # 'changed' on the treeview selection
215             sub _do_selection_changed {
216             my ($selection, $self) = @_;
217              
218             my ($model, $iter) = $selection->get_selected;
219             if ($iter) {
220             my $date = $model->get_value ($iter, COL_DATE);
221             my $str = $model->get_value ($iter, COL_NOTE);
222             $self->{'datespinner'}->set (value => $date);
223             $self->{'entry'}->set_text ($str);
224             }
225             }
226              
227             # 'Delete' button in the dialog
228             sub _do_delete {
229             my ($self) = @_;
230             ### AnnotationsDialog _do_delete()
231              
232             my $symbol = $self->{'symbol'} || return;
233             my $selection = $self->{'treeview'}->get_selection;
234             my ($model, $iter) = $selection->get_selected;
235             if (! $iter) { return; }
236             my $id = $model->get_value ($iter, COL_ID);
237             ### $symbol
238             ### $id
239              
240             my $dbh = App::Chart::DBI->instance;
241             $dbh->do ('DELETE FROM annotation WHERE symbol=? AND id=?',
242             undef,
243             $symbol, $id);
244             App::Chart::chart_dirbroadcast()->send ('data-changed', { $symbol => 1 });
245             }
246              
247             # 'activate' on the Gtk2::Entry and 'clicked' on the Gtk2::Button "Add"
248             sub _do_entry_enter {
249             my ($entry_or_button, $self) = @_;
250              
251             my $symbol = $self->{'symbol'} || return;
252             my $date = $self->{'datespinner'}->get_value;
253             my $str = $self->{'entry'}->get_text;
254              
255             my $dbh = App::Chart::DBI->instance;
256             App::Chart::Database::call_with_transaction
257             ($dbh, sub {
258             my $id = App::Chart::Annotation::next_id ('annotation', $symbol);
259              
260             $dbh->do('INSERT INTO annotation (symbol, id, date, note)
261             VALUES (?,?,?,?)',
262             undef,
263             $symbol, $id, $date, $str);
264             });
265             App::Chart::chart_dirbroadcast()->send ('data-changed', { $symbol => 1 });
266             }
267              
268             # 'edited' signal on the Date column Gtk2::Ex::DateSpinner::CellRenderer
269             sub _do_date_cell_edited {
270             my ($renderer, $pathstr, $newstr, $self) = @_;
271             my $path = Gtk2::TreePath->new_from_string ($pathstr);
272             my $model = $self->{'store'};
273             my $iter = $model->get_iter ($path);
274              
275             my $symbol = $self->{'symbol'} || return;
276             my $id = $model->get_value ($iter, COL_ID);
277              
278             my $dbh = App::Chart::DBI->instance;
279             $dbh->do('UPDATE annotation SET date=? WHERE symbol=? AND id=?',
280             undef,
281             $newstr, $symbol, $id);
282             App::Chart::chart_dirbroadcast()->send ('data-changed', { $symbol => 1 });
283             }
284              
285             # 'edited' signal on the Notes column Gtk2::CellRendererText
286             sub _do_note_cell_edited {
287             my ($renderer, $pathstr, $newstr, $self) = @_;
288             my $path = Gtk2::TreePath->new_from_string ($pathstr);
289             my $model = $self->{'store'};
290             my $iter = $model->get_iter ($path);
291              
292             my $symbol = $self->{'symbol'} || return;
293             my $id = $model->get_value ($iter, COL_ID);
294              
295             my $dbh = App::Chart::DBI->instance;
296             $dbh->do('UPDATE annotation SET note=? WHERE symbol=? AND id=?',
297             undef,
298             $newstr, $symbol, $id);
299             App::Chart::chart_dirbroadcast()->send ('data-changed', { $symbol => 1 });
300             }
301              
302             sub popup {
303             my ($class, $symbol, $parent) = @_;
304             if (! defined $symbol) { $symbol = ''; }
305             require App::Chart::Gtk2::Ex::ToplevelBits;
306             my $dialog = App::Chart::Gtk2::Ex::ToplevelBits::popup
307             ($class,
308             hide_on_delete => 1,
309             screen => $parent);
310             $dialog->set (symbol => $symbol);
311             return $dialog;
312             }
313              
314              
315             1;
316             __END__
317              
318             =for stopwords Popup
319              
320             =head1 NAME
321              
322             App::Chart::Gtk2::AnnotationsDialog -- annotations editing dialog
323              
324             =head1 SYNOPSIS
325              
326             use App::Chart::Gtk2::AnnotationsDialog;
327             App::Chart::Gtk2::AnnotationsDialog->popup;
328              
329             =head1 WIDGET HIERARCHY
330              
331             C<App::Chart::Gtk2::AnnotationsDialog> is a subclass of C<Gtk2::Dialog>.
332              
333             Gtk2::Widget
334             Gtk2::Container
335             Gtk2::Bin
336             Gtk2::Window
337             Gtk2::Dialog
338             App::Chart::Gtk2::AnnotationsDialog
339              
340             =head1 DESCRIPTION
341              
342             ...
343              
344             =head1 FUNCTIONS
345              
346             =over 4
347              
348             =item C<< App::Chart::Gtk2::AnnotationsDialog->popup () >>
349              
350             =item C<< App::Chart::Gtk2::AnnotationsDialog->popup ($symbol) >>
351              
352             Popup a C<AnnotationsDialog> dialog for C<$symbol>, either re-presenting any
353             existing one or otherwise creating a new one.
354              
355             =back
356              
357             =cut