| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Copyright 2007, 2008, 2009, 2010, 2011, 2013, 2014 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::WatchlistDialog; | 
| 18 | 1 |  |  | 1 |  | 70406 | use 5.010; | 
|  | 1 |  |  |  |  | 13 |  | 
| 19 | 1 |  |  | 1 |  | 5 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 23 |  | 
| 20 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 26 |  | 
| 21 | 1 |  |  | 1 |  | 5 | use Carp; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 59 |  | 
| 22 | 1 |  |  | 1 |  | 691 | use Gtk2 1.220; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | use Locale::TextDomain 1.17; # for __p() | 
| 24 |  |  |  |  |  |  | use Locale::TextDomain ('App-Chart'); | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | use Glib::Ex::ConnectProperties; | 
| 27 |  |  |  |  |  |  | use Gtk2::Ex::EntryBits; | 
| 28 |  |  |  |  |  |  | use Gtk2::Ex::TreeViewBits; | 
| 29 |  |  |  |  |  |  | use Gtk2::Ex::Units; | 
| 30 |  |  |  |  |  |  | use Gtk2::Ex::WidgetCursor; | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | use App::Chart::Gtk2::Ex::CellRendererTextBits; | 
| 33 |  |  |  |  |  |  | use App::Chart::Gtk2::Ex::NotebookLazyPages; | 
| 34 |  |  |  |  |  |  | use App::Chart::Gtk2::Ex::ToplevelBits; | 
| 35 |  |  |  |  |  |  | use App::Chart::Gtk2::Symlist; | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | # uncomment this to run the ### lines | 
| 38 |  |  |  |  |  |  | #use Devel::Comments; | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | BEGIN { | 
| 41 |  |  |  |  |  |  | Gtk2->CHECK_VERSION(2,12,0) | 
| 42 |  |  |  |  |  |  | or die "Need Gtk 2.12 or higher";  # for ->error_bell | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | use constant DEFAULT_SYMLIST_KEY => 'favourites'; | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | # use App::Chart::Gtk2::Ex::ToplevelSingleton hide_on_delete => 1; | 
| 48 |  |  |  |  |  |  | # use base 'App::Chart::Gtk2::Ex::ToplevelSingleton'; | 
| 49 |  |  |  |  |  |  | # sub popup { | 
| 50 |  |  |  |  |  |  | #   my ($class, $parent) = @_; | 
| 51 |  |  |  |  |  |  | #   my $self = $class->instance_for_screen ($parent); | 
| 52 |  |  |  |  |  |  | #   $self->present; | 
| 53 |  |  |  |  |  |  | #   return $self; | 
| 54 |  |  |  |  |  |  | # } | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | use Glib::Object::Subclass | 
| 57 |  |  |  |  |  |  | 'Gtk2::Dialog', | 
| 58 |  |  |  |  |  |  | properties => [ Glib::ParamSpec->object | 
| 59 |  |  |  |  |  |  | ('symlist', | 
| 60 |  |  |  |  |  |  | 'symlist', | 
| 61 |  |  |  |  |  |  | 'The symlist to display.', | 
| 62 |  |  |  |  |  |  | # App::Chart::Gtk2::Symlist::Join isn't a glib derivative | 
| 63 |  |  |  |  |  |  | 'Glib::Object', # 'App::Chart::Gtk2::Symlist', | 
| 64 |  |  |  |  |  |  | Glib::G_PARAM_READWRITE), | 
| 65 |  |  |  |  |  |  | ]; | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | use constant { NOTEBOOK_PAGENUM_SYMBOLS => 0, | 
| 69 |  |  |  |  |  |  | NOTEBOOK_PAGENUM_SYMLISTS => 1 }; | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | use constant { RESPONSE_REFRESH   => 0, | 
| 72 |  |  |  |  |  |  | RESPONSE_DELETE    => 1, | 
| 73 |  |  |  |  |  |  | RESPONSE_INTRADAY  => 2, | 
| 74 |  |  |  |  |  |  | RESPONSE_EDIT_NAME => 3, | 
| 75 |  |  |  |  |  |  | }; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | sub INIT_INSTANCE { | 
| 78 |  |  |  |  |  |  | my ($self) = @_; | 
| 79 |  |  |  |  |  |  | $self->set_title (__('Chart: Watchlist')); | 
| 80 |  |  |  |  |  |  | $self->signal_connect (response => \&_do_response); | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | my $vbox = $self->vbox; | 
| 83 |  |  |  |  |  |  | my $action_area = $self->action_area; | 
| 84 |  |  |  |  |  |  | my $em = Gtk2::Ex::Units::em($self); | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | my $symlist = $self->{'symlist'} | 
| 87 |  |  |  |  |  |  | = App::Chart::Gtk2::Symlist->new_from_key (DEFAULT_SYMLIST_KEY); | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | my $notebook = $self->{'notebook'} = Gtk2::Notebook->new; | 
| 90 |  |  |  |  |  |  | $notebook->set (tab_hborder => 0.5 * $em); | 
| 91 |  |  |  |  |  |  | $vbox->pack_start ($notebook, 1,1,0); | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | #   require App::Chart::Gtk2::SymlistComboBox; | 
| 94 |  |  |  |  |  |  | #   my $combobox = App::Chart::Gtk2::SymlistComboBox->new; | 
| 95 |  |  |  |  |  |  | #   Glib::Ex::ConnectProperties->new ([$self,    'symlist'], | 
| 96 |  |  |  |  |  |  | #                                     [$combobox,'symlist']); | 
| 97 |  |  |  |  |  |  | #   $combobox->signal_connect (changed => \&_do_combobox_changed); | 
| 98 |  |  |  |  |  |  | #   $action_area->add ($combobox); | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | $self->add_buttons ('gtk-refresh'   => RESPONSE_REFRESH, | 
| 101 |  |  |  |  |  |  | 'gtk-delete'    => RESPONSE_DELETE); | 
| 102 |  |  |  |  |  |  | { | 
| 103 |  |  |  |  |  |  | my $intraday_button = $self->{'intraday_button'} | 
| 104 |  |  |  |  |  |  | = Gtk2::Button->new_with_mnemonic (__('_Intraday')); | 
| 105 |  |  |  |  |  |  | $self->add_action_widget ($intraday_button, RESPONSE_INTRADAY); | 
| 106 |  |  |  |  |  |  | } | 
| 107 |  |  |  |  |  |  | { | 
| 108 |  |  |  |  |  |  | my $edit_button = $self->{'edit_button'} | 
| 109 |  |  |  |  |  |  | = Gtk2::Button->new_with_mnemonic (__('_Edit Name')); | 
| 110 |  |  |  |  |  |  | $self->add_action_widget ($edit_button, RESPONSE_EDIT_NAME); | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  | $self->add_buttons ('gtk-close'     => 'close', | 
| 113 |  |  |  |  |  |  | 'gtk-help'      => 'help'); | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | require App::Chart::Gtk2::WatchlistModel; | 
| 118 |  |  |  |  |  |  | my $model = App::Chart::Gtk2::WatchlistModel->new ($symlist); | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | my $symbols_vbox = Gtk2::VBox->new; | 
| 121 |  |  |  |  |  |  | my $symbols_tab_eventbox = $self->{'symbols_tab_eventbox'} | 
| 122 |  |  |  |  |  |  | = Gtk2::EventBox->new; | 
| 123 |  |  |  |  |  |  | $symbols_tab_eventbox->signal_connect | 
| 124 |  |  |  |  |  |  | (button_press_event => \&_do_symbols_tab_button_press); | 
| 125 |  |  |  |  |  |  | my $symbols_tab_label = $self->{'symbols_tab_label'} = Gtk2::Label->new; | 
| 126 |  |  |  |  |  |  | $symbols_tab_label->show; | 
| 127 |  |  |  |  |  |  | $symbols_tab_eventbox->add ($symbols_tab_label); | 
| 128 |  |  |  |  |  |  | $notebook->append_page ($symbols_vbox, $symbols_tab_eventbox); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | my $symlists_vbox = $self->{'symlists_vbox'} = Gtk2::VBox->new; | 
| 131 |  |  |  |  |  |  | $notebook->append_page ($symlists_vbox, __('Symlists')); | 
| 132 |  |  |  |  |  |  | App::Chart::Gtk2::Ex::NotebookLazyPages::set_init | 
| 133 |  |  |  |  |  |  | ($notebook, $symlists_vbox, \&_init_symlists_page); | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | $notebook->signal_connect ('notify::page' => \&_do_notebook_notify_page); | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | my $scrolled = $self->{'symbols_scrolled'} = Gtk2::ScrolledWindow->new; | 
| 138 |  |  |  |  |  |  | $scrolled->set(hscrollbar_policy => 'never'); | 
| 139 |  |  |  |  |  |  | $symbols_vbox->pack_start ($scrolled, 1,1,0); | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | my $treeview = $self->{'symbols_treeview'} | 
| 142 |  |  |  |  |  |  | = Gtk2::TreeView->new_with_model ($model); | 
| 143 |  |  |  |  |  |  | $treeview->set (fixed_height_mode => 1, | 
| 144 |  |  |  |  |  |  | reorderable => $symlist && $symlist->can_edit); | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | $scrolled->add ($treeview); | 
| 147 |  |  |  |  |  |  | $treeview->signal_connect (query_tooltip => \&_do_query_tooltip); | 
| 148 |  |  |  |  |  |  | $treeview->set (has_tooltip => 1); | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 151 |  |  |  |  |  |  | $selection->signal_connect (changed => \&_do_symbol_selection_changed); | 
| 152 |  |  |  |  |  |  | $selection->set_mode ('single'); | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | my $renderer_left = Gtk2::CellRendererText->new; | 
| 155 |  |  |  |  |  |  | $renderer_left->set (xalign => 0, | 
| 156 |  |  |  |  |  |  | ypad => 0); | 
| 157 |  |  |  |  |  |  | $renderer_left->set_fixed_height_from_font (1); | 
| 158 |  |  |  |  |  |  | my $renderer_right = Gtk2::CellRendererText->new; | 
| 159 |  |  |  |  |  |  | $renderer_right->set (xalign => 1, | 
| 160 |  |  |  |  |  |  | ypad => 0); | 
| 161 |  |  |  |  |  |  | $renderer_right->set_fixed_height_from_font (1); | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | { | 
| 164 |  |  |  |  |  |  | my $renderer = $self->{'symbol_renderer'} = Gtk2::CellRendererText->new; | 
| 165 |  |  |  |  |  |  | $renderer->set (xalign => 0, ypad => 0); | 
| 166 |  |  |  |  |  |  | $renderer->set_fixed_height_from_font (1); | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 169 |  |  |  |  |  |  | (__('Symbol'), $renderer, | 
| 170 |  |  |  |  |  |  | text => $model->COL_SYMBOL, | 
| 171 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 172 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 173 |  |  |  |  |  |  | fixed_width => 8*$em, | 
| 174 |  |  |  |  |  |  | resizable => 1); | 
| 175 |  |  |  |  |  |  | App::Chart::Gtk2::Ex::CellRendererTextBits::renderer_edited_set_value | 
| 176 |  |  |  |  |  |  | ($renderer, $column, 0); | 
| 177 |  |  |  |  |  |  | $renderer->signal_connect (edited => \&_do_symbol_renderer_edited); | 
| 178 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  | { | 
| 181 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 182 |  |  |  |  |  |  | (__('Bid/Offer'), $renderer_right, | 
| 183 |  |  |  |  |  |  | text => $model->COL_BIDOFFER, | 
| 184 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 185 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 186 |  |  |  |  |  |  | fixed_width => 12*$em, | 
| 187 |  |  |  |  |  |  | resizable => 1); | 
| 188 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  | { | 
| 191 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 192 |  |  |  |  |  |  | (__('Last'), $renderer_right, | 
| 193 |  |  |  |  |  |  | text => $model->COL_LAST, | 
| 194 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 195 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 196 |  |  |  |  |  |  | fixed_width => 7*$em, | 
| 197 |  |  |  |  |  |  | resizable => 1); | 
| 198 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  | { | 
| 201 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 202 |  |  |  |  |  |  | (__('Change'), $renderer_right, | 
| 203 |  |  |  |  |  |  | text => $model->COL_CHANGE, | 
| 204 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 205 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 206 |  |  |  |  |  |  | fixed_width => 7*$em, | 
| 207 |  |  |  |  |  |  | resizable => 1); | 
| 208 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  | { | 
| 211 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 212 |  |  |  |  |  |  | (__('High'), $renderer_right, | 
| 213 |  |  |  |  |  |  | text => $model->COL_HIGH, | 
| 214 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 215 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 216 |  |  |  |  |  |  | fixed_width => 7*$em, | 
| 217 |  |  |  |  |  |  | resizable => 1); | 
| 218 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  | { | 
| 221 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 222 |  |  |  |  |  |  | (__('Low'), $renderer_right, | 
| 223 |  |  |  |  |  |  | text => $model->COL_LOW, | 
| 224 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 225 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 226 |  |  |  |  |  |  | fixed_width => 7*$em, | 
| 227 |  |  |  |  |  |  | resizable => 1); | 
| 228 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  | { | 
| 231 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 232 |  |  |  |  |  |  | (__('Volume'), $renderer_right, | 
| 233 |  |  |  |  |  |  | text => $model->COL_VOLUME, | 
| 234 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 235 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 236 |  |  |  |  |  |  | fixed_width => 6*$em, | 
| 237 |  |  |  |  |  |  | resizable => 1); | 
| 238 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  | { | 
| 241 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 242 |  |  |  |  |  |  | (__('When'), $renderer_right, | 
| 243 |  |  |  |  |  |  | text => $model->COL_WHEN, | 
| 244 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 245 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 246 |  |  |  |  |  |  | fixed_width => 6*$em, | 
| 247 |  |  |  |  |  |  | resizable => 1); | 
| 248 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  | { | 
| 251 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 252 |  |  |  |  |  |  | (__('Notes'), $renderer_left, | 
| 253 |  |  |  |  |  |  | text => $model->COL_NOTE, | 
| 254 |  |  |  |  |  |  | foreground => $model->COL_COLOUR); | 
| 255 |  |  |  |  |  |  | $column->set (sizing => 'fixed', | 
| 256 |  |  |  |  |  |  | fixed_width => 8*$em, | 
| 257 |  |  |  |  |  |  | resizable => 1); | 
| 258 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  | $treeview->add_events ('button-press-mask'); | 
| 261 |  |  |  |  |  |  | $treeview->signal_connect (button_press_event => \&_do_symbol_menu_popup); | 
| 262 |  |  |  |  |  |  | $treeview->signal_connect (row_activated => \&_do_symbol_treeview_activate); | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | my $hbox = Gtk2::HBox->new; | 
| 265 |  |  |  |  |  |  | $symbols_vbox->pack_start ($hbox, 0,0,0); | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | my $entry_label = Gtk2::Label->new (__('New Symbol')); | 
| 268 |  |  |  |  |  |  | $hbox->pack_start ($entry_label, 0,0,0); | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | my $entry = $self->{'symbol_entry'} = Gtk2::Entry->new; | 
| 271 |  |  |  |  |  |  | $hbox->pack_start ($entry, 1,1,0); | 
| 272 |  |  |  |  |  |  | $entry->signal_connect (activate => \&_do_symbol_entry_activate); | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | { my $button = Gtk2::Button->new_with_label (__('Insert')); | 
| 275 |  |  |  |  |  |  | $hbox->pack_start ($button, 0,0,0); | 
| 276 |  |  |  |  |  |  | $button->signal_connect (clicked => \&_do_symbol_entry_activate); | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | _update_delete_sensitive ($self); | 
| 280 |  |  |  |  |  |  | _update_intraday_sensitive ($self); | 
| 281 |  |  |  |  |  |  | _update_edit_sensitive ($self); | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | $vbox->show_all; | 
| 284 |  |  |  |  |  |  | _do_notebook_notify_page ($notebook); # initial hides | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | # with a sensible rows size for the TreeView | 
| 287 |  |  |  |  |  |  | Gtk2::Ex::Units::set_default_size_with_subsizes | 
| 288 |  |  |  |  |  |  | ($self, [$scrolled, -1, '20 lines']); | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | $self->{'symlist'} = undef; # fake to force update | 
| 291 |  |  |  |  |  |  | $self->set_symlist ($symlist); | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | # # 'notify:symlist' on the App::Chart::Gtk2::SymlistComboBox | 
| 295 |  |  |  |  |  |  | # # switch page to the symbol list display when a symlist is selected | 
| 296 |  |  |  |  |  |  | # sub _do_combobox_changed { | 
| 297 |  |  |  |  |  |  | #   my ($combobox) = @_; | 
| 298 |  |  |  |  |  |  | #   if (DEBUG) { | 
| 299 |  |  |  |  |  |  | #     say "Watchlist symlist combobox changed, switch notebook to symbols"; | 
| 300 |  |  |  |  |  |  | #   } | 
| 301 |  |  |  |  |  |  | #   my $self = $combobox->get_toplevel; | 
| 302 |  |  |  |  |  |  | #   my $notebook = $self->{'notebook'}; | 
| 303 |  |  |  |  |  |  | #   $notebook->set_current_page(NOTEBOOK_PAGENUM_SYMBOLS); | 
| 304 |  |  |  |  |  |  | # } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # 'edited' signal on the Gtk2::CellRendererText in the symbol column, | 
| 307 |  |  |  |  |  |  | # initiate a download of the new symbol | 
| 308 |  |  |  |  |  |  | sub _do_symbol_renderer_edited { | 
| 309 |  |  |  |  |  |  | my ($renderer, $pathstr, $newstr) = @_; | 
| 310 |  |  |  |  |  |  | require App::Chart::Gtk2::Job::Latest; | 
| 311 |  |  |  |  |  |  | App::Chart::Gtk2::Job::Latest->start ([$newstr]); | 
| 312 |  |  |  |  |  |  | } | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | sub _do_notebook_notify_page { | 
| 315 |  |  |  |  |  |  | my ($notebook) = @_; | 
| 316 |  |  |  |  |  |  | my $self = $notebook->get_toplevel; | 
| 317 |  |  |  |  |  |  | ### Watchlist notebook switch to: $notebook->get_current_page | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | my $pagenum = $notebook->get_current_page; | 
| 320 |  |  |  |  |  |  | $self->{'intraday_button'}->set | 
| 321 |  |  |  |  |  |  | (visible => ($pagenum == NOTEBOOK_PAGENUM_SYMBOLS)); | 
| 322 |  |  |  |  |  |  | $self->{'edit_button'}->set | 
| 323 |  |  |  |  |  |  | (visible => ($pagenum == NOTEBOOK_PAGENUM_SYMLISTS)); | 
| 324 |  |  |  |  |  |  | _update_delete_sensitive ($self); | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | sub _init_symlists_page { | 
| 328 |  |  |  |  |  |  | my ($notebook, $vbox, $pagenum) = @_; | 
| 329 |  |  |  |  |  |  | my $self = $notebook->get_toplevel; | 
| 330 |  |  |  |  |  |  | ### Watchlist _init_symlists_page() | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | my $scrolled = $self->{'symlists_scrolled'} = Gtk2::ScrolledWindow->new; | 
| 333 |  |  |  |  |  |  | $scrolled->set (hscrollbar_policy => 'automatic'); | 
| 334 |  |  |  |  |  |  | $vbox->pack_start ($scrolled, 1,1,0); | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | require App::Chart::Gtk2::SymlistListModel; | 
| 337 |  |  |  |  |  |  | my $model = App::Chart::Gtk2::SymlistListModel->instance; | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | my $treeview = $self->{'symlists_treeview'} | 
| 340 |  |  |  |  |  |  | = Gtk2::TreeView->new_with_model ($model); | 
| 341 |  |  |  |  |  |  | $treeview->set (fixed_height_mode => 0, | 
| 342 |  |  |  |  |  |  | reorderable => 1); | 
| 343 |  |  |  |  |  |  | $scrolled->add ($treeview); | 
| 344 |  |  |  |  |  |  | $treeview->signal_connect (row_activated =>\&_do_symlists_treeview_activate); | 
| 345 |  |  |  |  |  |  | $treeview->add_events ('button-press-mask'); | 
| 346 |  |  |  |  |  |  | $treeview->signal_connect (button_press_event => \&_do_symlist_menu_popup); | 
| 347 |  |  |  |  |  |  | # $treeview->signal_connect (query_tooltip => \&_do_query_tooltip); | 
| 348 |  |  |  |  |  |  | # $treeview->set (has_tooltip => 1); | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 351 |  |  |  |  |  |  | $selection->signal_connect (changed => \&_do_symlist_selection_changed); | 
| 352 |  |  |  |  |  |  | $selection->set_mode ('single'); | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | { | 
| 355 |  |  |  |  |  |  | my $renderer = $self->{'symlists_name_renderer'} | 
| 356 |  |  |  |  |  |  | = Gtk2::CellRendererText->new; | 
| 357 |  |  |  |  |  |  | $renderer->set (xalign => 0, | 
| 358 |  |  |  |  |  |  | ypad => 0); | 
| 359 |  |  |  |  |  |  | my $column = $self->{'symlists_name_treecolumn'} | 
| 360 |  |  |  |  |  |  | = Gtk2::TreeViewColumn->new_with_attributes | 
| 361 |  |  |  |  |  |  | (__('Name'), $renderer, text => $model->COL_NAME); | 
| 362 |  |  |  |  |  |  | App::Chart::Gtk2::Ex::CellRendererTextBits::renderer_edited_set_value | 
| 363 |  |  |  |  |  |  | ($renderer, $column, $model->COL_NAME); | 
| 364 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 365 |  |  |  |  |  |  | } | 
| 366 |  |  |  |  |  |  | { | 
| 367 |  |  |  |  |  |  | my $renderer = Gtk2::CellRendererText->new; | 
| 368 |  |  |  |  |  |  | $renderer->set (xalign => 0, | 
| 369 |  |  |  |  |  |  | ypad => 0); | 
| 370 |  |  |  |  |  |  | my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 371 |  |  |  |  |  |  | (__('Key'), $renderer, text => $model->COL_KEY); | 
| 372 |  |  |  |  |  |  | $treeview->append_column ($column); | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  | #   { | 
| 375 |  |  |  |  |  |  | #     my $renderer = Gtk2::CellRendererText->new; | 
| 376 |  |  |  |  |  |  | #     $renderer->set (xalign => 0, | 
| 377 |  |  |  |  |  |  | #                     ypad => 0, | 
| 378 |  |  |  |  |  |  | #                     text => __('Edit Name')); | 
| 379 |  |  |  |  |  |  | #     my $column = Gtk2::TreeViewColumn->new_with_attributes | 
| 380 |  |  |  |  |  |  | #       ('', $renderer); | 
| 381 |  |  |  |  |  |  | #     $treeview->append_column ($column); | 
| 382 |  |  |  |  |  |  | #   } | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | my $hbox = Gtk2::HBox->new; | 
| 385 |  |  |  |  |  |  | $vbox->pack_start ($hbox, 0,0,0); | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | my $entry_label = Gtk2::Label->new (__('New List')); | 
| 388 |  |  |  |  |  |  | $hbox->pack_start ($entry_label, 0,0,0); | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | my $entry = $self->{'symlist_entry'} = Gtk2::Entry->new; | 
| 391 |  |  |  |  |  |  | $hbox->pack_start ($entry, 1,1,0); | 
| 392 |  |  |  |  |  |  | $entry->signal_connect (activate => \&_do_symlist_entry_activate); | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | { my $button = Gtk2::Button->new_with_label (__('Insert')); | 
| 395 |  |  |  |  |  |  | $hbox->pack_start ($button, 0,0,0); | 
| 396 |  |  |  |  |  |  | $button->signal_connect (clicked => \&_do_symlist_entry_activate); | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | $self->{'symlists_setup'} = 1; | 
| 400 |  |  |  |  |  |  | $vbox->show_all; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | sub SET_PROPERTY { | 
| 404 |  |  |  |  |  |  | my ($self, $pspec, $newval) = @_; | 
| 405 |  |  |  |  |  |  | my $pname = $pspec->get_name; | 
| 406 |  |  |  |  |  |  | if ($pspec->get_name eq 'symlist') { | 
| 407 |  |  |  |  |  |  | $self->set_symlist ($newval); | 
| 408 |  |  |  |  |  |  | } else { | 
| 409 |  |  |  |  |  |  | $self->{$pname} = $newval;  # per default GET_PROPERTY | 
| 410 |  |  |  |  |  |  | } | 
| 411 |  |  |  |  |  |  | } | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | sub get_selected_symbol { | 
| 414 |  |  |  |  |  |  | my ($self) = @_; | 
| 415 |  |  |  |  |  |  | my $treeview = $self->{'symbols_treeview'}; | 
| 416 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 417 |  |  |  |  |  |  | my ($model, $iter) = $selection->get_selected; | 
| 418 |  |  |  |  |  |  | if (! defined $iter) { return undef; } | 
| 419 |  |  |  |  |  |  | my ($symbol) = $model->get ($iter, 0); | 
| 420 |  |  |  |  |  |  | return $symbol; | 
| 421 |  |  |  |  |  |  | } | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  | sub set_symlist { | 
| 424 |  |  |  |  |  |  | my ($self, $symlist) = @_; | 
| 425 |  |  |  |  |  |  | ### Watchlist set_symlist() | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | if (my $conn = delete $self->{'symlist_name_conn'}) { | 
| 428 |  |  |  |  |  |  | $conn->disconnect; | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  | my $label = $self->{'symbols_tab_label'}; | 
| 431 |  |  |  |  |  |  | if ($symlist) { | 
| 432 |  |  |  |  |  |  | $self->{'symlist_name_conn'} | 
| 433 |  |  |  |  |  |  | = Glib::Ex::ConnectProperties->new ([$symlist,'name'], | 
| 434 |  |  |  |  |  |  | [$label,  'label']); | 
| 435 |  |  |  |  |  |  | } else { | 
| 436 |  |  |  |  |  |  | $label->set_text (__('(No list)')); | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | if (($symlist||0) eq ($self->{'symlist'}||0)) { | 
| 440 |  |  |  |  |  |  | ### symlist unchanged | 
| 441 |  |  |  |  |  |  | return; | 
| 442 |  |  |  |  |  |  | } | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | ### new symlist: "$symlist" | 
| 445 |  |  |  |  |  |  | Gtk2::Ex::WidgetCursor->busy; | 
| 446 |  |  |  |  |  |  | my $model = $symlist && App::Chart::Gtk2::WatchlistModel->new ($symlist); | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | { | 
| 449 |  |  |  |  |  |  | my $reorderable = $symlist && $symlist->can_edit; | 
| 450 |  |  |  |  |  |  | my $symbols_treeview = $self->{'symbols_treeview'}; | 
| 451 |  |  |  |  |  |  | ### $reorderable | 
| 452 |  |  |  |  |  |  | $symbols_treeview->set (model       => $model, | 
| 453 |  |  |  |  |  |  | reorderable => $reorderable); | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | # FIXME: what was this? dragging text to add a symbol? doing it turns | 
| 456 |  |  |  |  |  |  | # off reorderable circa gtk 2.24 -- another incompatible change probably ... | 
| 457 |  |  |  |  |  |  | # | 
| 458 |  |  |  |  |  |  | # if ($reorderable) { | 
| 459 |  |  |  |  |  |  | #   $symbols_treeview->enable_model_drag_dest | 
| 460 |  |  |  |  |  |  | #     (['move'], { target => 'text/plain', | 
| 461 |  |  |  |  |  |  | #                  flags => []});  # 'other-app' | 
| 462 |  |  |  |  |  |  | # } | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | $self->{'symlist'} = $symlist; | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | if ($symlist) { | 
| 468 |  |  |  |  |  |  | if (my $treeview = $self->{'symlists_treeview'}) { | 
| 469 |  |  |  |  |  |  | my $model = $treeview->get_model; | 
| 470 |  |  |  |  |  |  | my $key = $symlist->key; | 
| 471 |  |  |  |  |  |  | $model->foreach | 
| 472 |  |  |  |  |  |  | (sub { | 
| 473 |  |  |  |  |  |  | my ($model, $path, $iter) = @_; | 
| 474 |  |  |  |  |  |  | my $this_key = $model->get_value ($iter, $model->COL_KEY); | 
| 475 |  |  |  |  |  |  | if ($this_key ne $key) { return 0; } # keep iterating | 
| 476 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 477 |  |  |  |  |  |  | $selection->select_path ($path); | 
| 478 |  |  |  |  |  |  | return 1; # stop iterating | 
| 479 |  |  |  |  |  |  | }); | 
| 480 |  |  |  |  |  |  | } | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | my $editable = $symlist && $symlist->can_edit; | 
| 484 |  |  |  |  |  |  | ### symbol column editable: $editable | 
| 485 |  |  |  |  |  |  | $self->{'symbol_renderer'}->set (editable => $editable); | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | $self->notify ('symlist'); | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | sub get_symlists_selected_key { | 
| 491 |  |  |  |  |  |  | my ($self) = @_; | 
| 492 |  |  |  |  |  |  | my $treeview = $self->{'symlists_treeview'} || return; # if created yet | 
| 493 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 494 |  |  |  |  |  |  | my ($model, $iter) = $selection->get_selected; | 
| 495 |  |  |  |  |  |  | if (! defined $iter) { return; } | 
| 496 |  |  |  |  |  |  | my ($symbol) = $model->get ($iter, $model->COL_KEY); | 
| 497 |  |  |  |  |  |  | return $symbol; | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | sub _update_delete_sensitive { | 
| 501 |  |  |  |  |  |  | my ($self) = @_; | 
| 502 |  |  |  |  |  |  | ### Watchlist _update_delete_sensitive() | 
| 503 |  |  |  |  |  |  | $self->set_response_sensitive (RESPONSE_DELETE, | 
| 504 |  |  |  |  |  |  | _want_delete_sensitive($self)); | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  | sub _want_delete_sensitive { | 
| 507 |  |  |  |  |  |  | my ($self) = @_; | 
| 508 |  |  |  |  |  |  | my $notebook = $self->{'notebook'}; | 
| 509 |  |  |  |  |  |  | my $pagenum = $notebook->get_current_page; | 
| 510 |  |  |  |  |  |  | ### $pagenum | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | if ($pagenum == NOTEBOOK_PAGENUM_SYMLISTS) { | 
| 513 |  |  |  |  |  |  | my $key = $self->get_symlists_selected_key; | 
| 514 |  |  |  |  |  |  | if (! defined $key) { | 
| 515 |  |  |  |  |  |  | ### no selected symlist | 
| 516 |  |  |  |  |  |  | return 0; | 
| 517 |  |  |  |  |  |  | } | 
| 518 |  |  |  |  |  |  | my $symlist = App::Chart::Gtk2::Symlist->new_from_key ($key); | 
| 519 |  |  |  |  |  |  | if (! $symlist) { | 
| 520 |  |  |  |  |  |  | ### no such symlist: $key | 
| 521 |  |  |  |  |  |  | return 0; | 
| 522 |  |  |  |  |  |  | } | 
| 523 |  |  |  |  |  |  | ### can_delete_symlist() on: $key | 
| 524 |  |  |  |  |  |  | return $symlist->can_delete_symlist; | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | } else { | 
| 527 |  |  |  |  |  |  | my $treeview = $self->{'symbols_treeview'}; | 
| 528 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 529 |  |  |  |  |  |  | my ($model, $iter) = $selection->get_selected; | 
| 530 |  |  |  |  |  |  | if (! $iter) { | 
| 531 |  |  |  |  |  |  | ### no selected symbol | 
| 532 |  |  |  |  |  |  | return 0; | 
| 533 |  |  |  |  |  |  | } | 
| 534 |  |  |  |  |  |  | my $symlist = $model->get_model; | 
| 535 |  |  |  |  |  |  | return $symlist->can_edit; | 
| 536 |  |  |  |  |  |  | } | 
| 537 |  |  |  |  |  |  | } | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | sub _update_intraday_sensitive { | 
| 540 |  |  |  |  |  |  | my ($self) = @_; | 
| 541 |  |  |  |  |  |  | ### Watchlist _update_intraday_sensitive() | 
| 542 |  |  |  |  |  |  | my $symbol = $self->get_selected_symbol; | 
| 543 |  |  |  |  |  |  | ### $symbol | 
| 544 |  |  |  |  |  |  | $self->set_response_sensitive (RESPONSE_INTRADAY, | 
| 545 |  |  |  |  |  |  | symbol_intraday_sensitive($symbol)); | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  | sub symbol_intraday_sensitive { | 
| 548 |  |  |  |  |  |  | my ($symbol) = @_; | 
| 549 |  |  |  |  |  |  | if (! $symbol) { return 0; } | 
| 550 |  |  |  |  |  |  | require App::Chart::IntradayHandler; | 
| 551 |  |  |  |  |  |  | return scalar (App::Chart::IntradayHandler->handlers_for_symbol ($symbol)); | 
| 552 |  |  |  |  |  |  | } | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | sub _update_edit_sensitive { | 
| 555 |  |  |  |  |  |  | my ($self) = @_; | 
| 556 |  |  |  |  |  |  | $self->set_response_sensitive (RESPONSE_EDIT_NAME, | 
| 557 |  |  |  |  |  |  | defined $self->get_symlists_selected_key); | 
| 558 |  |  |  |  |  |  | } | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | sub _do_symbols_tab_button_press { | 
| 561 |  |  |  |  |  |  | my ($symbols_tab_eventbox, $event) = @_; | 
| 562 |  |  |  |  |  |  | my $self = $symbols_tab_eventbox->get_toplevel; | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | if ($event->button == 3) { | 
| 565 |  |  |  |  |  |  | require App::Chart::Gtk2::SymlistRadioMenu; | 
| 566 |  |  |  |  |  |  | my $symlist_menu = App::Chart::Gtk2::SymlistRadioMenu->new; | 
| 567 |  |  |  |  |  |  | ### menu destroy connection: $symlist_menu->signal_connect (destroy => sub { print "Watchlist symlist menu destroyed\n" }) | 
| 568 |  |  |  |  |  |  | Glib::Ex::ConnectProperties->new ([$self, 'symlist'], | 
| 569 |  |  |  |  |  |  | [$symlist_menu, 'symlist']); | 
| 570 |  |  |  |  |  |  | $symlist_menu->set_screen ($self->get_screen); | 
| 571 |  |  |  |  |  |  | $symlist_menu->popup (undef,  # parent menushell | 
| 572 |  |  |  |  |  |  | undef,  # parent menuitem | 
| 573 |  |  |  |  |  |  | undef,  # position func | 
| 574 |  |  |  |  |  |  | undef,  # position userdata | 
| 575 |  |  |  |  |  |  | $_,     # button | 
| 576 |  |  |  |  |  |  | $event->time); | 
| 577 |  |  |  |  |  |  | return Gtk2::EVENT_PROPAGATE; | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | # GtkNotebook button press handler can cope with an event from a child | 
| 581 |  |  |  |  |  |  | # widget | 
| 582 |  |  |  |  |  |  | return $self->{'notebook'}->signal_emit ('button_press_event', $event); | 
| 583 |  |  |  |  |  |  | } | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | sub _do_symbol_treeview_activate { | 
| 586 |  |  |  |  |  |  | my ($treeview, $path, $column) = @_; | 
| 587 |  |  |  |  |  |  | my $self = $treeview->get_toplevel; | 
| 588 |  |  |  |  |  |  | my $symlist = $self->{'symlist'}; | 
| 589 |  |  |  |  |  |  | my $iter = $symlist->get_iter ($path); | 
| 590 |  |  |  |  |  |  | my $symbol = $symlist->get_value ($iter, 0); | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | require App::Chart::Gtk2::Main; | 
| 593 |  |  |  |  |  |  | my $main = App::Chart::Gtk2::Main->find_for_dialog ($self); | 
| 594 |  |  |  |  |  |  | $main->goto_symbol ($symbol, $symlist); | 
| 595 |  |  |  |  |  |  | $main->present; | 
| 596 |  |  |  |  |  |  | } | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | sub _do_symlists_treeview_activate { | 
| 599 |  |  |  |  |  |  | my ($treeview, $path, $column) = @_; | 
| 600 |  |  |  |  |  |  | my $model = $treeview->get_model; | 
| 601 |  |  |  |  |  |  | my $iter = $model->get_iter ($path); | 
| 602 |  |  |  |  |  |  | my $key = $model->get_value ($iter, $model->COL_KEY); | 
| 603 |  |  |  |  |  |  | my $symlist = App::Chart::Gtk2::Symlist->new_from_key ($key); | 
| 604 |  |  |  |  |  |  | my $self = $treeview->get_toplevel; | 
| 605 |  |  |  |  |  |  | $self->set_symlist ($symlist); | 
| 606 |  |  |  |  |  |  | $self->{'notebook'}->set_current_page (0); | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | sub _do_symbol_selection_changed { | 
| 610 |  |  |  |  |  |  | my ($selection) = @_; | 
| 611 |  |  |  |  |  |  | my $self = $selection->get_tree_view->get_toplevel; | 
| 612 |  |  |  |  |  |  | _update_intraday_sensitive ($self); | 
| 613 |  |  |  |  |  |  | _update_delete_sensitive ($self); | 
| 614 |  |  |  |  |  |  | } | 
| 615 |  |  |  |  |  |  | sub _do_symlist_selection_changed { | 
| 616 |  |  |  |  |  |  | my ($selection) = @_; | 
| 617 |  |  |  |  |  |  | my $self = $selection->get_tree_view->get_toplevel; | 
| 618 |  |  |  |  |  |  | _update_delete_sensitive ($self); | 
| 619 |  |  |  |  |  |  | _update_edit_sensitive ($self); | 
| 620 |  |  |  |  |  |  | } | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | # 'response' signal handler | 
| 623 |  |  |  |  |  |  | sub _do_response { | 
| 624 |  |  |  |  |  |  | my ($self, $response) = @_; | 
| 625 |  |  |  |  |  |  | ### Watchlist _do_response(): $response | 
| 626 |  |  |  |  |  |  |  | 
| 627 |  |  |  |  |  |  | if ($response eq RESPONSE_REFRESH) { | 
| 628 |  |  |  |  |  |  | $self->refresh; | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | } elsif ($response eq RESPONSE_DELETE) { | 
| 631 |  |  |  |  |  |  | my $notebook = $self->{'notebook'}; | 
| 632 |  |  |  |  |  |  | my $pagenum = $notebook->get_current_page; | 
| 633 |  |  |  |  |  |  | my $treeview; | 
| 634 |  |  |  |  |  |  | if ($pagenum == NOTEBOOK_PAGENUM_SYMLISTS) { | 
| 635 |  |  |  |  |  |  | $treeview = $self->{'symlists_treeview'}; | 
| 636 |  |  |  |  |  |  | # supposed to be insensitive when no selection, but check anyway | 
| 637 |  |  |  |  |  |  | my $key = $self->get_symlists_selected_key || return; | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | # ignore somehow unknown key | 
| 640 |  |  |  |  |  |  | my $symlist = App::Chart::Gtk2::Symlist->new_from_key ($key) || return; | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | if (! $symlist->is_empty) { | 
| 643 |  |  |  |  |  |  | # dialog if symlist not empty | 
| 644 |  |  |  |  |  |  | require App::Chart::Gtk2::DeleteSymlistDialog; | 
| 645 |  |  |  |  |  |  | App::Chart::Gtk2::DeleteSymlistDialog->popup ($symlist, $self); | 
| 646 |  |  |  |  |  |  | return; | 
| 647 |  |  |  |  |  |  | } | 
| 648 |  |  |  |  |  |  | } else { | 
| 649 |  |  |  |  |  |  | $treeview = $self->{'symbols_treeview'}; | 
| 650 |  |  |  |  |  |  | } | 
| 651 |  |  |  |  |  |  | require Gtk2::Ex::TreeViewBits; | 
| 652 |  |  |  |  |  |  | Gtk2::Ex::TreeViewBits::remove_selected_rows ($treeview); | 
| 653 |  |  |  |  |  |  |  | 
| 654 |  |  |  |  |  |  | } elsif ($response eq RESPONSE_INTRADAY) { | 
| 655 |  |  |  |  |  |  | # supposed to be insensitive when no selected symbol, but check anyway | 
| 656 |  |  |  |  |  |  | my $symbol = $self->get_selected_symbol // return; | 
| 657 |  |  |  |  |  |  | App::Chart::Gtk2::Ex::ToplevelBits::popup | 
| 658 |  |  |  |  |  |  | ('App::Chart::Gtk2::IntradayDialog', | 
| 659 |  |  |  |  |  |  | properties => { symbol => $symbol }, | 
| 660 |  |  |  |  |  |  | screen => $self); | 
| 661 |  |  |  |  |  |  |  | 
| 662 |  |  |  |  |  |  | } elsif ($response eq RESPONSE_EDIT_NAME) { | 
| 663 |  |  |  |  |  |  | my $notebook = $self->{'notebook'}; | 
| 664 |  |  |  |  |  |  | my $pagenum = $notebook->get_current_page; | 
| 665 |  |  |  |  |  |  | # supposed to be visible only when symlists showing, but check anyway | 
| 666 |  |  |  |  |  |  | ($pagenum == NOTEBOOK_PAGENUM_SYMLISTS) or return; | 
| 667 |  |  |  |  |  |  |  | 
| 668 |  |  |  |  |  |  | my $treeview = $self->{'symlists_treeview'}; | 
| 669 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 670 |  |  |  |  |  |  | my ($symlists_model, $iter) = $selection->get_selected; | 
| 671 |  |  |  |  |  |  | # supposed to be insensitive if no selection, but check anyway | 
| 672 |  |  |  |  |  |  | if (! defined $iter) { return; } | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | my $path = $symlists_model->get_path($iter); | 
| 675 |  |  |  |  |  |  | ### set_cursor to path: $path->to_string | 
| 676 |  |  |  |  |  |  | $treeview->grab_focus; | 
| 677 |  |  |  |  |  |  | my $renderer = $self->{'symlists_name_renderer'}; | 
| 678 |  |  |  |  |  |  | $renderer->set (editable => 1); | 
| 679 |  |  |  |  |  |  | $treeview->set_cursor ($path, $self->{'symlists_name_treecolumn'}, 1); | 
| 680 |  |  |  |  |  |  | $renderer->set (editable => 0); | 
| 681 |  |  |  |  |  |  |  | 
| 682 |  |  |  |  |  |  | } elsif ($response eq 'close') { | 
| 683 |  |  |  |  |  |  | # as per a keyboard close, defaults to raising 'delete-event', which in | 
| 684 |  |  |  |  |  |  | # turn defaults to a destroy | 
| 685 |  |  |  |  |  |  | $self->signal_emit ('close'); | 
| 686 |  |  |  |  |  |  |  | 
| 687 |  |  |  |  |  |  | } elsif ($response eq 'help') { | 
| 688 |  |  |  |  |  |  | require App::Chart::Manual; | 
| 689 |  |  |  |  |  |  | App::Chart::Manual->open(__p('manual-node','Watchlist'), $self); | 
| 690 |  |  |  |  |  |  | } | 
| 691 |  |  |  |  |  |  | } | 
| 692 |  |  |  |  |  |  |  | 
| 693 |  |  |  |  |  |  | sub refresh { | 
| 694 |  |  |  |  |  |  | my ($self) = @_; | 
| 695 |  |  |  |  |  |  | Gtk2::Ex::WidgetCursor->busy; | 
| 696 |  |  |  |  |  |  | if (my $symlist = $self->{'symlist'}) { | 
| 697 |  |  |  |  |  |  | require App::Chart::Gtk2::Job::Latest; | 
| 698 |  |  |  |  |  |  | App::Chart::Gtk2::Job::Latest->start_symlist ($symlist); | 
| 699 |  |  |  |  |  |  | } | 
| 700 |  |  |  |  |  |  | } | 
| 701 |  |  |  |  |  |  |  | 
| 702 |  |  |  |  |  |  | sub _do_symbol_menu_popup { | 
| 703 |  |  |  |  |  |  | my ($treeview, $event) = @_; | 
| 704 |  |  |  |  |  |  | if ($event->button == 3) { | 
| 705 |  |  |  |  |  |  | require App::Chart::Gtk2::WatchlistSymbolMenu; | 
| 706 |  |  |  |  |  |  | App::Chart::Gtk2::WatchlistSymbolMenu->popup_from_treeview ($event, $treeview); | 
| 707 |  |  |  |  |  |  | } | 
| 708 |  |  |  |  |  |  | return Gtk2::EVENT_PROPAGATE; | 
| 709 |  |  |  |  |  |  | } | 
| 710 |  |  |  |  |  |  |  | 
| 711 |  |  |  |  |  |  | sub _do_symlist_menu_popup { | 
| 712 |  |  |  |  |  |  | # nothing yet ... | 
| 713 |  |  |  |  |  |  | #   my ($treeview, $event) = @_; | 
| 714 |  |  |  |  |  |  | #   my $self = $treeview->get_toplevel; | 
| 715 |  |  |  |  |  |  | return Gtk2::EVENT_PROPAGATE; | 
| 716 |  |  |  |  |  |  | } | 
| 717 |  |  |  |  |  |  |  | 
| 718 |  |  |  |  |  |  | # 'query-tooltip' signal on symbols_treeview | 
| 719 |  |  |  |  |  |  | sub _do_query_tooltip { | 
| 720 |  |  |  |  |  |  | my ($treeview, $x, $y, $keyboard_tip, $tooltip) = @_; | 
| 721 |  |  |  |  |  |  | # ### Watchlist _do_query_tooltip() "$x,$y" | 
| 722 |  |  |  |  |  |  |  | 
| 723 |  |  |  |  |  |  | my ($bin_x, $bin_y, $model, $path, $iter) | 
| 724 |  |  |  |  |  |  | = $treeview->get_tooltip_context ($x, $y, $keyboard_tip); | 
| 725 |  |  |  |  |  |  | if (! defined $path) { return 0; } | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  | my $symbol = $model->get_value($iter, $model->COL_SYMBOL); | 
| 728 |  |  |  |  |  |  | if (! defined $symbol) { return 0; } | 
| 729 |  |  |  |  |  |  | require App::Chart::Latest; | 
| 730 |  |  |  |  |  |  | my $latest = App::Chart::Latest->get ($symbol); | 
| 731 |  |  |  |  |  |  |  | 
| 732 |  |  |  |  |  |  | require App::Chart::Database; | 
| 733 |  |  |  |  |  |  | my $tip = $symbol; | 
| 734 |  |  |  |  |  |  | if (my $name = ($latest->{'name'} | 
| 735 |  |  |  |  |  |  | || App::Chart::Database->symbol_name ($symbol))) { | 
| 736 |  |  |  |  |  |  | $tip .= ' - ' . $name; | 
| 737 |  |  |  |  |  |  | } | 
| 738 |  |  |  |  |  |  | $tip .= "\n"; | 
| 739 |  |  |  |  |  |  |  | 
| 740 |  |  |  |  |  |  | if (my $quote_date = $latest->{'quote_date'}) { | 
| 741 |  |  |  |  |  |  | my $quote_time = $latest->{'quote_time'} || ''; | 
| 742 |  |  |  |  |  |  | $tip .= __x("Quote: {quote_date} {quote_time}", | 
| 743 |  |  |  |  |  |  | quote_date => $quote_date, | 
| 744 |  |  |  |  |  |  | quote_time => $quote_time); | 
| 745 |  |  |  |  |  |  | $tip .= "\n"; | 
| 746 |  |  |  |  |  |  | } | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | if (my $last_date = $latest->{'last_date'}) { | 
| 749 |  |  |  |  |  |  | my $last_time = $latest->{'last_time'} || ''; | 
| 750 |  |  |  |  |  |  | $tip .= __x("Last:  {last_date} {last_time}", | 
| 751 |  |  |  |  |  |  | last_date => $last_date, | 
| 752 |  |  |  |  |  |  | last_time => $last_time); | 
| 753 |  |  |  |  |  |  | $tip .= "\n"; | 
| 754 |  |  |  |  |  |  | } | 
| 755 |  |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  | $tip .= __x('{location} time; source {source}', | 
| 757 |  |  |  |  |  |  | location => App::Chart::TZ->for_symbol($symbol)->name, | 
| 758 |  |  |  |  |  |  | source => $latest->{'source'}); | 
| 759 |  |  |  |  |  |  |  | 
| 760 |  |  |  |  |  |  | ### $tip | 
| 761 |  |  |  |  |  |  | $tooltip->set_text ($tip); | 
| 762 |  |  |  |  |  |  | $treeview->set_tooltip_row ($tooltip, $path); | 
| 763 |  |  |  |  |  |  | return 1; | 
| 764 |  |  |  |  |  |  | } | 
| 765 |  |  |  |  |  |  |  | 
| 766 |  |  |  |  |  |  | sub _do_symlist_entry_activate { | 
| 767 |  |  |  |  |  |  | my ($entry_or_button) = @_; | 
| 768 |  |  |  |  |  |  | my $self = $entry_or_button->get_toplevel; | 
| 769 |  |  |  |  |  |  | my $treeview = $self->{'symlists_treeview'}; | 
| 770 |  |  |  |  |  |  | my $pos = treeview_pos_after_selected_or_top_of_visible ($treeview); | 
| 771 |  |  |  |  |  |  |  | 
| 772 |  |  |  |  |  |  | my $entry = $self->{'symlist_entry'}; | 
| 773 |  |  |  |  |  |  | my $name = $entry->get_text; | 
| 774 |  |  |  |  |  |  | require App::Chart::Gtk2::Symlist::User; | 
| 775 |  |  |  |  |  |  | App::Chart::Gtk2::Symlist::User->add_symlist ($pos, $name); | 
| 776 |  |  |  |  |  |  |  | 
| 777 |  |  |  |  |  |  | my $path = Gtk2::TreePath->new_from_indices ($pos); | 
| 778 |  |  |  |  |  |  | Gtk2::Ex::TreeViewBits::scroll_cursor_to_path ($treeview, $path); | 
| 779 |  |  |  |  |  |  | } | 
| 780 |  |  |  |  |  |  |  | 
| 781 |  |  |  |  |  |  | # 'activate' signal handler on the Gtk2::Entry for a symbol | 
| 782 |  |  |  |  |  |  | sub _do_symbol_entry_activate { | 
| 783 |  |  |  |  |  |  | my ($entry_or_button) = @_; | 
| 784 |  |  |  |  |  |  | my $self = $entry_or_button->get_toplevel; | 
| 785 |  |  |  |  |  |  | my $treeview = $self->{'symbols_treeview'}; | 
| 786 |  |  |  |  |  |  | my $pos = treeview_pos_after_selected_or_top_of_visible ($treeview); | 
| 787 |  |  |  |  |  |  |  | 
| 788 |  |  |  |  |  |  | my $entry = $self->{'symbol_entry'}; | 
| 789 |  |  |  |  |  |  | my $symlist = $self->{'symlist'}; | 
| 790 |  |  |  |  |  |  | if (! $symlist->can_edit) {  # supposed to be insensitive anyway | 
| 791 |  |  |  |  |  |  | $entry->error_bell; | 
| 792 |  |  |  |  |  |  | return; | 
| 793 |  |  |  |  |  |  | } | 
| 794 |  |  |  |  |  |  |  | 
| 795 |  |  |  |  |  |  | # select text for ease of typing another | 
| 796 |  |  |  |  |  |  | Gtk2::Ex::EntryBits::select_region_noclip ($entry, 0, -1); | 
| 797 |  |  |  |  |  |  |  | 
| 798 |  |  |  |  |  |  | my $symbol = $entry->get_text; | 
| 799 |  |  |  |  |  |  | if (my $path = $symlist->find_symbol_path ($symbol)) { | 
| 800 |  |  |  |  |  |  | # already exists, move to it | 
| 801 |  |  |  |  |  |  | Gtk2::Ex::TreeViewBits::scroll_cursor_to_path ($treeview, $path); | 
| 802 |  |  |  |  |  |  | return; | 
| 803 |  |  |  |  |  |  | } | 
| 804 |  |  |  |  |  |  | $symlist->insert_with_values ($pos, 0=>$symbol); | 
| 805 |  |  |  |  |  |  |  | 
| 806 |  |  |  |  |  |  | my $path = Gtk2::TreePath->new_from_indices ($pos); | 
| 807 |  |  |  |  |  |  | Gtk2::Ex::TreeViewBits::scroll_cursor_to_path ($treeview, $path); | 
| 808 |  |  |  |  |  |  |  | 
| 809 |  |  |  |  |  |  | # request this, everything else as extras | 
| 810 |  |  |  |  |  |  | if ($symbol ne '') { | 
| 811 |  |  |  |  |  |  | require App::Chart::Gtk2::Job::Latest; | 
| 812 |  |  |  |  |  |  | App::Chart::Gtk2::Job::Latest->start ([$symbol]); | 
| 813 |  |  |  |  |  |  | } | 
| 814 |  |  |  |  |  |  | } | 
| 815 |  |  |  |  |  |  |  | 
| 816 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 817 |  |  |  |  |  |  | # generic helpers | 
| 818 |  |  |  |  |  |  |  | 
| 819 |  |  |  |  |  |  | sub treeview_pos_after_selected_or_top_of_visible { | 
| 820 |  |  |  |  |  |  | my ($treeview) = @_; | 
| 821 |  |  |  |  |  |  |  | 
| 822 |  |  |  |  |  |  | my ($lo_path, $hi_path) = $treeview->get_visible_range; | 
| 823 |  |  |  |  |  |  | my ($lo) = $lo_path ? $lo_path->get_indices : (-1); | 
| 824 |  |  |  |  |  |  | my ($hi) = $hi_path ? $hi_path->get_indices : (-1); | 
| 825 |  |  |  |  |  |  | my $pos = $lo + 1; | 
| 826 |  |  |  |  |  |  |  | 
| 827 |  |  |  |  |  |  | my $selection = $treeview->get_selection; | 
| 828 |  |  |  |  |  |  | my ($sel_path) = $selection->get_selected_rows; | 
| 829 |  |  |  |  |  |  | if ($sel_path) { | 
| 830 |  |  |  |  |  |  | my ($sel) = $sel_path->get_indices; | 
| 831 |  |  |  |  |  |  | if ($sel >= $lo && $sel <= $hi) { | 
| 832 |  |  |  |  |  |  | $pos = $sel + 1; | 
| 833 |  |  |  |  |  |  | } | 
| 834 |  |  |  |  |  |  | } | 
| 835 |  |  |  |  |  |  | return $pos; | 
| 836 |  |  |  |  |  |  | } | 
| 837 |  |  |  |  |  |  |  | 
| 838 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 839 |  |  |  |  |  |  |  | 
| 840 |  |  |  |  |  |  | sub main { | 
| 841 |  |  |  |  |  |  | my ($class, $args) = @_; | 
| 842 |  |  |  |  |  |  |  | 
| 843 |  |  |  |  |  |  | Gtk2->disable_setlocale;  # leave LC_NUMERIC alone for version nums | 
| 844 |  |  |  |  |  |  | Gtk2->init; | 
| 845 |  |  |  |  |  |  |  | 
| 846 |  |  |  |  |  |  | require Gtk2::Ex::ErrorTextDialog::Handler; | 
| 847 |  |  |  |  |  |  | Glib->install_exception_handler | 
| 848 |  |  |  |  |  |  | (\&Gtk2::Ex::ErrorTextDialog::Handler::exception_handler); | 
| 849 |  |  |  |  |  |  | ## no critic (RequireLocalizedPunctuationVars) | 
| 850 |  |  |  |  |  |  | $SIG{'__WARN__'} = \&Gtk2::Ex::ErrorTextDialog::Handler::exception_handler; | 
| 851 |  |  |  |  |  |  | ## use critic | 
| 852 |  |  |  |  |  |  |  | 
| 853 |  |  |  |  |  |  | require App::Chart::Gtk2::TickerMain; | 
| 854 |  |  |  |  |  |  | my $symlist = App::Chart::Gtk2::TickerMain::args_to_symlist ($args); | 
| 855 |  |  |  |  |  |  |  | 
| 856 |  |  |  |  |  |  | my $self = $class->new; | 
| 857 |  |  |  |  |  |  | $self->set (symlist => $symlist); | 
| 858 |  |  |  |  |  |  | $self->signal_connect (destroy => | 
| 859 |  |  |  |  |  |  | \&App::Chart::Gtk2::TickerMain::_do_destroy_main_quit); | 
| 860 |  |  |  |  |  |  | $self->show_all; | 
| 861 |  |  |  |  |  |  | Gtk2->main; | 
| 862 |  |  |  |  |  |  | } | 
| 863 |  |  |  |  |  |  |  | 
| 864 |  |  |  |  |  |  | 1; | 
| 865 |  |  |  |  |  |  | __END__ | 
| 866 |  |  |  |  |  |  |  | 
| 867 |  |  |  |  |  |  | =for stopwords watchlist Watchlist Popup | 
| 868 |  |  |  |  |  |  |  | 
| 869 |  |  |  |  |  |  | =head1 NAME | 
| 870 |  |  |  |  |  |  |  | 
| 871 |  |  |  |  |  |  | App::Chart::Gtk2::WatchlistDialog -- watchlist dialog module | 
| 872 |  |  |  |  |  |  |  | 
| 873 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 874 |  |  |  |  |  |  |  | 
| 875 |  |  |  |  |  |  | use App::Chart::Gtk2::WatchlistDialog; | 
| 876 |  |  |  |  |  |  |  | 
| 877 |  |  |  |  |  |  | =head1 WIDGET HIERARCHY | 
| 878 |  |  |  |  |  |  |  | 
| 879 |  |  |  |  |  |  | C<App::Chart::Gtk2::WatchlistDialog> is a subclass of C<Gtk2::Dialog>. | 
| 880 |  |  |  |  |  |  |  | 
| 881 |  |  |  |  |  |  | Gtk2::Widget | 
| 882 |  |  |  |  |  |  | Gtk2::Container | 
| 883 |  |  |  |  |  |  | Gtk2::Bin | 
| 884 |  |  |  |  |  |  | Gtk2::Window | 
| 885 |  |  |  |  |  |  | Gtk2::Dialog | 
| 886 |  |  |  |  |  |  | App::Chart::Gtk2::WatchlistDialog | 
| 887 |  |  |  |  |  |  |  | 
| 888 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 889 |  |  |  |  |  |  |  | 
| 890 |  |  |  |  |  |  | A C<App::Chart::Gtk2::WatchlistDialog> widget is a watchlist display and dialog. | 
| 891 |  |  |  |  |  |  |  | 
| 892 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 893 |  |  |  |  |  |  |  | 
| 894 |  |  |  |  |  |  | =over 4 | 
| 895 |  |  |  |  |  |  |  | 
| 896 |  |  |  |  |  |  | =item C<< App::Chart::Gtk2::WatchlistDialog->new (key=>value,...) >> | 
| 897 |  |  |  |  |  |  |  | 
| 898 |  |  |  |  |  |  | Create and return a new Watchlist dialog widget. | 
| 899 |  |  |  |  |  |  |  | 
| 900 |  |  |  |  |  |  | =back | 
| 901 |  |  |  |  |  |  |  | 
| 902 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 903 |  |  |  |  |  |  |  | 
| 904 |  |  |  |  |  |  | L<App::Chart::Gtk2::WatchlistSymbolMenu> | 
| 905 |  |  |  |  |  |  |  | 
| 906 |  |  |  |  |  |  | =cut | 
| 907 |  |  |  |  |  |  |  | 
| 908 |  |  |  |  |  |  | #  App::Chart::Gtk2::WatchlistDialog->popup(); | 
| 909 |  |  |  |  |  |  | # =item C<< App::Chart::Gtk2::WatchlistDialog->popup () >> | 
| 910 |  |  |  |  |  |  | # | 
| 911 |  |  |  |  |  |  | # =item C<< App::Chart::Gtk2::WatchlistDialog->popup ($parent) >> | 
| 912 |  |  |  |  |  |  | # | 
| 913 |  |  |  |  |  |  | # Popup a C<Watchlist> dialog.  This function creates a C<Watchlist> widget | 
| 914 |  |  |  |  |  |  | # the first time it's called, and then on subsequent calls just presents that | 
| 915 |  |  |  |  |  |  | # single dialog. | 
| 916 |  |  |  |  |  |  | # | 
| 917 |  |  |  |  |  |  | # If C<$parent> is supplied then a Watchlist on that display is sought, and if | 
| 918 |  |  |  |  |  |  | # one is created then it's on the same screen as C<$parent>. | 
| 919 |  |  |  |  |  |  |  |