File Coverage

blib/lib/App/Chart/Gtk2/OpenDialog.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 2007, 2008, 2009, 2010, 2011 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::OpenDialog;
18 1     1   350 use 5.010;
  1         2  
19 1     1   4 use strict;
  1         2  
  1         15  
20 1     1   4 use warnings;
  1         2  
  1         19  
21 1     1   4 use Carp;
  1         1  
  1         41  
22 1     1   142 use Gtk2;
  0            
  0            
23             use Locale::TextDomain ('App-Chart');
24              
25             use Gtk2::Ex::EntryBits;
26             use Gtk2::Ex::TreeViewBits;
27             use Gtk2::Ex::Units;
28             use App::Chart;
29             use App::Chart::Database;
30             use App::Chart::Gtk2::GUI;
31              
32             Gtk2->CHECK_VERSION(2,12,0)
33             or die "Need Gtk 2.12 or higher"; # for ->error_bell
34              
35             # uncomment this to run the ### lines
36             #use Smart::Comments;
37              
38             use Glib::Object::Subclass
39             'Gtk2::Dialog',
40             signals => { map => \&_do_map };
41              
42             # use App::Chart::Gtk2::Ex::ToplevelSingleton hide_on_delete => 1;
43             # use base 'App::Chart::Gtk2::Ex::ToplevelSingleton';
44             # sub popup {
45             # my ($class) = @_;
46             # my $self = $class->instance;
47             # $self->present;
48             # return $self;
49             # }
50              
51             use constant { RESPONSE_OPEN => 0,
52             RESPONSE_NEW => 1 };
53              
54             sub INIT_INSTANCE {
55             my ($self) = @_;
56              
57             $self->set_title (__('Chart: Open'));
58             $self->add_buttons ('gtk-open' => RESPONSE_OPEN,
59             'gtk-new' => RESPONSE_NEW,
60             'gtk-cancel' => 'cancel',
61             'gtk-help' => 'help');
62             $self->signal_connect (response => \&_do_response);
63              
64             my $vbox = $self->vbox;
65              
66             my $scrolled = Gtk2::ScrolledWindow->new;
67             $scrolled->set(hscrollbar_policy => 'automatic');
68             $vbox->pack_start ($scrolled, 1,1,0);
69              
70             require App::Chart::Gtk2::OpenModel;
71             my $model = $self->{'model'} = App::Chart::Gtk2::OpenModel->instance;
72             # require App::Chart::Gtk2::SymlistTreeModel;
73             # my $model = $self->{'model'} = App::Chart::Gtk2::SymlistTreeModel->instance;
74              
75             my $treeview = $self->{'treeview'} = Gtk2::TreeView->new_with_model ($model);
76             $treeview->set (reorderable => 1,
77             headers_visible => 0,
78             fixed_height_mode => 1,
79             search_column => $model->COL_ITEM_SYMBOL);
80             $treeview->signal_connect (row_activated => \&_do_row_activated);
81             $scrolled->add ($treeview);
82              
83             foreach my $key ('all', 'favourites') {
84             if (my $path = $model->path_for_key ($key)) {
85             $treeview->expand_row($path, 0);
86             }
87             }
88              
89             my $selection = $treeview->get_selection();
90             $selection->set_mode ('single');
91              
92             my $renderer = Gtk2::CellRendererText->new;
93             $renderer->set (xalign => 0, # left justify
94             ypad => 0);
95             $renderer->set_fixed_height_from_font (1);
96              
97             my $column = Gtk2::TreeViewColumn->new;
98             $column->pack_start ($renderer, 1);
99             $column->set_cell_data_func ($renderer, \&_cell_data_func);
100             $column->set (sizing => 'fixed');
101             $treeview->append_column ($column);
102              
103             my $notfound = $self->{'notfound'}
104             = Gtk2::Label->new (__('Not in database, click "New" to download.
105             Be sure capitalization is right for download.
106             (Or click/return again to really open.)'));
107             $notfound->set_justify ('center');
108             $vbox->pack_start ($notfound, 0,0,0);
109              
110             my $hbox = Gtk2::HBox->new;
111             $vbox->pack_start ($hbox, 0,0,0);
112              
113             $hbox->pack_start (Gtk2::Label->new (__('Symbol')), 0,0,0);
114              
115             my $entry = $self->{'entry'} = Gtk2::Entry->new;
116             $hbox->pack_start ($entry, 1, 1, 0.5 * Gtk2::Ex::Units::em($entry));
117             $entry->signal_connect (activate => \&_do_entry_open);
118             $entry->signal_connect (changed => \&_do_entry_changed);
119              
120             my $button = Gtk2::Button->new_with_label (__('Enter'));
121             $hbox->pack_start ($button, 0,0,0);
122             $button->signal_connect (clicked => \&_do_entry_open);
123              
124             $vbox->show_all;
125             $notfound->hide;
126             $entry->grab_focus;
127              
128             # with a sensible size for the TreeView
129             Gtk2::Ex::Units::set_default_size_with_subsizes
130             ($self, [$scrolled, '40 ems', '20 lines']);
131             }
132              
133             # select etc when newly mapped
134             sub _do_map {
135             my ($self) = @_;
136              
137             $self->{'notfound'}->hide;
138             my $entry = $self->{'entry'};
139             $entry->grab_focus;
140             Gtk2::Ex::EntryBits::select_region_noclip ($entry, 0, -1);
141              
142             return shift->signal_chain_from_overridden(@_);
143             }
144              
145             sub _do_response {
146             my ($self, $response) = @_;
147              
148             if ($response eq RESPONSE_OPEN) {
149             _do_entry_open ($self);
150              
151             } elsif ($response eq RESPONSE_NEW) {
152             my $symbol = $self->entry_str;
153             if ($symbol) { # should be insensitive when empty anyway
154             App::Chart::Database->add_symbol ($symbol);
155             $self->_do_open ($symbol);
156             require App::Chart::Gtk2::DownloadDialog;
157             App::Chart::Gtk2::DownloadDialog->popup_update ($symbol, $self);
158             }
159              
160             } elsif ($response eq 'cancel' || $response eq 'delete-event') {
161             $self->hide;
162              
163             } elsif ($response eq 'help') {
164             require App::Chart::Manual;
165             App::Chart::Manual->open(__p('manual-node','Open'), $self);
166             }
167             }
168              
169             # called:
170             # entry widget 'activate'
171             # enter button 'clicked'
172             # dialog RESPONSE_OPEN
173             #
174             sub _do_entry_open {
175             my ($widget) = @_;
176             my $self = $widget->get_toplevel;
177             my $str = $self->entry_str;
178             if ($str eq '') {
179             $widget->error_bell;
180             return;
181             }
182             my $preferred_symlist = $self->tree_current_symlist;
183             my ($symbol, $symlist)
184             = App::Chart::SymbolMatch::find ($str, $preferred_symlist);
185             if (! $symbol) { $symbol = $str; }
186             $self->_do_open ($symbol, $symlist);
187             }
188              
189             sub _do_open {
190             my ($self, $symbol, $symlist) = @_;
191             ### _do_open: $symbol
192             ### symlist: $symlist && $symlist->key
193             my $notfound = $self->{'notfound'};
194             if (! $notfound->visible
195              
196             && ! App::Chart::Database->symbol_exists($symbol)) {
197             $self->{'entry'}->set_text ($symbol);
198             $notfound->show;
199             return;
200             }
201             $notfound->hide;
202             require App::Chart::Gtk2::Main;
203             my $main = App::Chart::Gtk2::Main->find_for_dialog ($self);
204             $main->show;
205             $self->hide;
206             $main->goto_symbol ($symbol, $symlist);
207             }
208              
209             #------------------------------------------------------------------------------
210              
211             sub tree_current_symlist {
212             my ($self) = @_;
213             my $treeview = $self->{'treeview'};
214             my ($path, $focus_column) = $treeview->get_cursor;
215             if (! $path) { return undef; }
216              
217             my $model = $self->{'model'};
218             my $iter = $model->get_iter ($path) || return undef;
219              
220             # OpenModel
221             return $model->get_value ($iter, $model->COL_SYMLIST_OBJECT);
222              
223             # if (! $model->iter_has_child ($iter)) {
224             # $iter = $model->iter_parent ($iter);
225             # }
226             # my $key = $model->get_value ($iter, $model->COL_SYMLIST_KEY);
227             # return App::Chart::Gtk2::Symlist->new_from_key ($key);
228             }
229              
230             sub entry_str {
231             my ($self) = @_;
232             my $entry = $self->{'entry'};
233             return App::Chart::collapse_whitespace ($entry->get_text());
234             }
235              
236             sub _do_entry_changed {
237             my ($entry) = @_;
238             my $self = $entry->get_toplevel;
239             $self->{'notfound'}->hide;
240              
241             my $str = $self->entry_str;
242             # "New" button active when symbol entered
243             $self->set_response_sensitive (RESPONSE_NEW, $str ne '');
244              
245             require App::Chart::SymbolMatch;
246             my $preferred_symlist = $self->tree_current_symlist;
247             my ($symbol, $symlist)
248             = App::Chart::SymbolMatch::find ($str, $preferred_symlist);
249             ### OpenDialog: $str
250             ### $symbol
251             ### symlist: $symlist && $symlist->name
252             if ($symbol && $symlist) {
253             $self->scroll_to_symbol_and_symlist ($symbol, $symlist);
254             }
255             }
256              
257             sub scroll_to_symbol_and_symlist {
258             my ($self, $symbol, $symlist) = @_;
259             my $treeview = $self->{'treeview'};
260             my $model = $self->{'model'};
261             my $path = $model->path_for_symbol_and_symlist ($symbol, $symlist);
262             if (! $path) {
263             die "OpenDialog: oops, no path for $symbol, $symlist";
264             }
265             ### OpenDialog scroll to: $path->to_string
266             Gtk2::Ex::TreeViewBits::scroll_cursor_to_path ($treeview, $path);
267             }
268              
269             # 'row-activated' signal on the TreeView
270             sub _do_row_activated {
271             my ($treeview, $path, $treeviewcolumn) = @_;
272             ### OpenDialog row_activated: $path->to_string
273             my $self = $treeview->get_toplevel;
274             my $model = $self->{'model'};
275             my $iter = $model->get_iter ($path) || do {
276             $self->error_bell;
277             return;
278             };
279             $self->_do_open ($model->get ($iter, $model->COL_ITEM_SYMBOL),
280             $model->get ($iter, $model->COL_SYMLIST_OBJECT));
281             }
282              
283             # data setup for the renderer
284             sub _cell_data_func {
285             my ($self, $renderer, $model, $iter) = @_;
286             my $path = $model->get_path ($iter);
287             my $str;
288             if ($path->get_depth == 1) {
289             $str = $model->get_value ($iter, $model->COL_SYMLIST_NAME);
290             } else {
291             if (defined ($str = $model->get_value ($iter, $model->COL_ITEM_SYMBOL))) {
292             if (my $name = App::Chart::Database->symbol_name ($str)) {
293             $str .= " - $name";
294             }
295             } else {
296             $str = 'oops, no symbol at path=' . $path->to_string;
297             }
298             }
299             $renderer->set (text => $str);
300             }
301              
302             1;
303             __END__
304              
305              
306             =head1 NAME
307              
308             App::Chart::Gtk2::OpenDialog -- open dialog widget
309              
310             =head1 SYNOPSIS
311              
312             use App::Chart::Gtk2::OpenDialog;
313             my $dialog = App::Chart::Gtk2::OpenDialog->new;
314              
315             =head1 WIDGET HIERARCHY
316              
317             C<App::Chart::Gtk2::OpenDialog> is a subclass of C<Gtk2::Dialog>.
318              
319             Gtk2::Widget
320             Gtk2::Container
321             Gtk2::Bin
322             Gtk2::Window
323             Gtk2::Dialog
324             App::Chart::Gtk2::OpenDialog
325              
326             =head1 DESCRIPTION
327              
328             ...
329              
330             =head1 SIGNALS
331              
332             =over 4
333              
334             =item C<open> (parameters: C<$dialog>, C<$symbol>)
335              
336             Emitted when the user asks to open a symbol, either by clicking from the
337             list or typing in a symbol.
338              
339             =item C<new> (parameters: C<$dialog>, C<$symbol>)
340              
341             Emitted when the user asks to create a new symbol.
342              
343             =back
344              
345             =cut