| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Copyright 2007, 2008, 2009, 2010, 2011, 2012 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::View; | 
| 18 | 1 |  |  | 1 |  | 602 | use 5.010; | 
|  | 1 |  |  |  |  | 2 |  | 
| 19 | 1 |  |  | 1 |  | 5 | use strict; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 19 |  | 
| 20 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 28 |  | 
| 21 | 1 |  |  | 1 |  | 4 | use Carp; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 51 |  | 
| 22 | 1 |  |  | 1 |  | 248 | use Glib; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | use Glib::Ex::ConnectProperties; | 
| 24 |  |  |  |  |  |  | use Gtk2 1.220; | 
| 25 |  |  |  |  |  |  | use Gtk2::Ex::AdjustmentBits 43; # v.43 for set_maybe() | 
| 26 |  |  |  |  |  |  | use List::Util qw(min max); | 
| 27 |  |  |  |  |  |  | use Locale::TextDomain ('App-Chart'); | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | use App::Chart::Glib::Ex::MoreUtils; | 
| 30 |  |  |  |  |  |  | use App::Chart; | 
| 31 |  |  |  |  |  |  | use App::Chart::Gtk2::GUI; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | # uncomment this to run the ### lines | 
| 34 |  |  |  |  |  |  | #use Smart::Comments; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | use constant DEFAULT_TIMEBASE_CLASS => 'App::Chart::Timebase::Days'; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | use Glib::Object::Subclass | 
| 39 |  |  |  |  |  |  | 'Gtk2::Table', | 
| 40 |  |  |  |  |  |  | properties => [Glib::ParamSpec->string | 
| 41 |  |  |  |  |  |  | ('symbol', | 
| 42 |  |  |  |  |  |  | __('Symbol'), | 
| 43 |  |  |  |  |  |  | 'The stock or commodity symbol to display, or empty string for none.', | 
| 44 |  |  |  |  |  |  | '', # default | 
| 45 |  |  |  |  |  |  | Glib::G_PARAM_READWRITE), | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | Glib::ParamSpec->string | 
| 48 |  |  |  |  |  |  | ('timebase-class', | 
| 49 |  |  |  |  |  |  | 'timebase-class', | 
| 50 |  |  |  |  |  |  | 'Blurb.', | 
| 51 |  |  |  |  |  |  | DEFAULT_TIMEBASE_CLASS, | 
| 52 |  |  |  |  |  |  | Glib::G_PARAM_READWRITE), | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | Glib::ParamSpec->object | 
| 55 |  |  |  |  |  |  | ('statusbar', | 
| 56 |  |  |  |  |  |  | 'statusbar', | 
| 57 |  |  |  |  |  |  | 'Blurb.', | 
| 58 |  |  |  |  |  |  | 'Gtk2::Statusbar', | 
| 59 |  |  |  |  |  |  | Glib::G_PARAM_READWRITE), | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | Glib::ParamSpec->scalar | 
| 62 |  |  |  |  |  |  | ('viewstyle', | 
| 63 |  |  |  |  |  |  | 'viewstyle', | 
| 64 |  |  |  |  |  |  | 'Blurb.', | 
| 65 |  |  |  |  |  |  | Glib::G_PARAM_READWRITE), | 
| 66 |  |  |  |  |  |  | ]; | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | # FIXME: adjust_splits breaks AnnDrag | 
| 69 |  |  |  |  |  |  | use constant DEFAULT_VIEWSTYLE => | 
| 70 |  |  |  |  |  |  | { adjust_splits     => 0, | 
| 71 |  |  |  |  |  |  | adjust_dividends  => 0, | 
| 72 |  |  |  |  |  |  | adjust_imputation => 1, | 
| 73 |  |  |  |  |  |  | adjust_rollovers  => 0, | 
| 74 |  |  |  |  |  |  | graphs => [ { size => 4, | 
| 75 |  |  |  |  |  |  | linestyle => 'Candles', | 
| 76 |  |  |  |  |  |  | indicators => [{ key => 'SMA', }, | 
| 77 |  |  |  |  |  |  | ], | 
| 78 |  |  |  |  |  |  | }, | 
| 79 |  |  |  |  |  |  | { size => 1, | 
| 80 |  |  |  |  |  |  | indicators => [{ key => 'Volume', } | 
| 81 |  |  |  |  |  |  | ], | 
| 82 |  |  |  |  |  |  | }, | 
| 83 |  |  |  |  |  |  | ], | 
| 84 |  |  |  |  |  |  | }; | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | sub viewstyle_read { | 
| 87 |  |  |  |  |  |  | require App::Chart::DBI; | 
| 88 |  |  |  |  |  |  | my $str = App::Chart::DBI->read_single | 
| 89 |  |  |  |  |  |  | ('SELECT value FROM preference WHERE key=\'viewstyle\''); | 
| 90 |  |  |  |  |  |  | if (! defined $str) { return DEFAULT_VIEWSTYLE; } | 
| 91 |  |  |  |  |  |  | my $viewstyle = eval $str; | 
| 92 |  |  |  |  |  |  | if (! defined $viewstyle) { | 
| 93 |  |  |  |  |  |  | print "chart: oops, bad viewstyle in database, using default: $@"; | 
| 94 |  |  |  |  |  |  | return DEFAULT_VIEWSTYLE; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  | return $viewstyle; | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  | # viewstyle_write(DEFAULT_VIEWSTYLE); | 
| 99 |  |  |  |  |  |  | # print viewstyle_read(DEFAULT_VIEWSTYLE); | 
| 100 |  |  |  |  |  |  | # exit 0; | 
| 101 |  |  |  |  |  |  | sub viewstyle_write { | 
| 102 |  |  |  |  |  |  | my ($viewstyle) = @_; | 
| 103 |  |  |  |  |  |  | require App::Chart::DBI; | 
| 104 |  |  |  |  |  |  | require Data::Dumper; | 
| 105 |  |  |  |  |  |  | my $str = Data::Dumper->new([$viewstyle],['viewstyle'])->Indent(1)->Terse(1)->Sortkeys(1)->Dump; | 
| 106 |  |  |  |  |  |  | require App::Chart::DBI; | 
| 107 |  |  |  |  |  |  | my $dbh = App::Chart::DBI->instance; | 
| 108 |  |  |  |  |  |  | $dbh->do ('INSERT OR REPLACE INTO preference (key, value) | 
| 109 |  |  |  |  |  |  | VALUES (\'viewstyle\',?)', {}, $str); | 
| 110 |  |  |  |  |  |  | App::Chart::chart_dirbroadcast()->send ('viewstyle-changed'); | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | sub INIT_INSTANCE { | 
| 116 |  |  |  |  |  |  | my ($self) = @_; | 
| 117 |  |  |  |  |  |  | $self->{'symbol'} = '';  # per property default above | 
| 118 |  |  |  |  |  |  | $self->{'series_list'} = []; | 
| 119 |  |  |  |  |  |  | $self->{'timebase_class'} = DEFAULT_TIMEBASE_CLASS; | 
| 120 |  |  |  |  |  |  | $self->{'graphs'} = []; | 
| 121 |  |  |  |  |  |  | $self->set(n_rows => 9, | 
| 122 |  |  |  |  |  |  | n_columns => 3); | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | App::Chart::chart_dirbroadcast()->connect_for_object | 
| 125 |  |  |  |  |  |  | ('data-changed', \&_do_data_changed, $self); | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | # FIXME: this induces a rescale at a good time, but otherwise not wanted | 
| 128 |  |  |  |  |  |  | App::Chart::chart_dirbroadcast()->connect_for_object | 
| 129 |  |  |  |  |  |  | ('latest-changed', \&_do_data_changed, $self); | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | App::Chart::Gtk2::GUI::chart_style_widget ('AppChartViewLabel'); | 
| 132 |  |  |  |  |  |  | my $ebox = $self->{'initial'} = Gtk2::EventBox->new; | 
| 133 |  |  |  |  |  |  | $ebox->set_name ('AppChartViewLabel'); | 
| 134 |  |  |  |  |  |  | my $label = Gtk2::Label->new | 
| 135 |  |  |  |  |  |  | (__('Use File/Open to open or add a symbol')); | 
| 136 |  |  |  |  |  |  | $label->set_name ('AppChartViewLabel'); | 
| 137 |  |  |  |  |  |  | $ebox->add ($label); | 
| 138 |  |  |  |  |  |  | $ebox->show_all; | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | $self->attach ($ebox, 0,3, 0,9, | 
| 141 |  |  |  |  |  |  | ['fill','shrink','expand'], | 
| 142 |  |  |  |  |  |  | ['fill','shrink','expand'], 0,0); | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | sub GET_PROPERTY { | 
| 146 |  |  |  |  |  |  | my ($self, $pspec) = @_; | 
| 147 |  |  |  |  |  |  | my $pname = $pspec->get_name; | 
| 148 |  |  |  |  |  |  | if ($pname eq 'viewstyle') { | 
| 149 |  |  |  |  |  |  | if (! $self->{'init_graphs'}) { | 
| 150 |  |  |  |  |  |  | return viewstyle_read(); | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | return $self->{$pname}; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | sub SET_PROPERTY { | 
| 157 |  |  |  |  |  |  | my ($self, $pspec, $newval) = @_; | 
| 158 |  |  |  |  |  |  | my $pname = $pspec->get_name; | 
| 159 |  |  |  |  |  |  | my $oldval = $self->{$pname}; | 
| 160 |  |  |  |  |  |  | ### View SET_PROPERTY: $pname | 
| 161 |  |  |  |  |  |  | ### $newval | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | if ($pname eq 'symbol') { | 
| 164 |  |  |  |  |  |  | $self->set_symbol ($newval); | 
| 165 |  |  |  |  |  |  | return; | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | $self->{$pname} = $newval;  # per default GET_PROPERTY | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | if ($pname eq 'timebase_class') { | 
| 171 |  |  |  |  |  |  | if ($oldval ne $newval) { | 
| 172 |  |  |  |  |  |  | $self->set_symbol ($self->get('symbol')); | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | } elsif ($pname eq 'statusbar') { | 
| 176 |  |  |  |  |  |  | # lose old id | 
| 177 |  |  |  |  |  |  | delete $self->{'crosshair_status_id'}; | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | } elsif ($pname eq 'viewstyle') { | 
| 180 |  |  |  |  |  |  | if ($self->{'init_graphs'}) { | 
| 181 |  |  |  |  |  |  | _update_attach ($self); | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  | if ($self->{'symbol'}) { | 
| 184 |  |  |  |  |  |  | _set_symbol ($self, $self->{'symbol'}); | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 190 |  |  |  |  |  |  | # Crosshair | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | sub crosshair { | 
| 193 |  |  |  |  |  |  | my ($self) = @_; | 
| 194 |  |  |  |  |  |  | return ($self->{'crosshair_object'} | 
| 195 |  |  |  |  |  |  | ||= do { | 
| 196 |  |  |  |  |  |  | _init_graphs ($self); | 
| 197 |  |  |  |  |  |  | require Gtk2::Ex::CrossHair; | 
| 198 |  |  |  |  |  |  | my $ch = Gtk2::Ex::CrossHair->new (widgets => $self->{'graphs'}, | 
| 199 |  |  |  |  |  |  | foreground => 'orange'); | 
| 200 |  |  |  |  |  |  | $ch->signal_connect (moved => \&_do_crosshair_moved, | 
| 201 |  |  |  |  |  |  | App::Chart::Glib::Ex::MoreUtils::ref_weak($self)); | 
| 202 |  |  |  |  |  |  | ### View created crosshair: "$ch" | 
| 203 |  |  |  |  |  |  | $ch; | 
| 204 |  |  |  |  |  |  | }); | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | sub _do_crosshair_moved { | 
| 208 |  |  |  |  |  |  | my ($crosshair, $graph, $x, $y, $ref_weak_self) = @_; | 
| 209 |  |  |  |  |  |  | my $self = $$ref_weak_self or return; | 
| 210 |  |  |  |  |  |  | ### View _do_crosshair_moved() | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | my $statusbar = $self->{'statusbar'} || return; | 
| 213 |  |  |  |  |  |  | my $id = $statusbar->get_context_id (__PACKAGE__ . '.crosshair'); | 
| 214 |  |  |  |  |  |  | $statusbar->pop ($id); | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | if (! defined $x) { return; } | 
| 217 |  |  |  |  |  |  | my $series = $graph->get('series-list')->[0] || return; | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | my $t = $graph->x_to_date ($x); | 
| 220 |  |  |  |  |  |  | my $dstr = $series->timebase->strftime ($App::Chart::option{'d_fmt'}, $t); | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | my $value = $graph->y_to_value ($y); | 
| 223 |  |  |  |  |  |  | my $nf = App::Chart::number_formatter(); | 
| 224 |  |  |  |  |  |  | my $pstr = $nf->format_number ($value, $series->decimals, 0); | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | my $status = $dstr . '  ' . $pstr; | 
| 227 |  |  |  |  |  |  | ### $id | 
| 228 |  |  |  |  |  |  | ### $status | 
| 229 |  |  |  |  |  |  | $statusbar->push ($id, $status); | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | sub _do_lasso_ended { | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | sub _do_graph_button_press { | 
| 237 |  |  |  |  |  |  | my ($graph, $event) = @_; | 
| 238 |  |  |  |  |  |  | my $self = $graph->get_ancestor (__PACKAGE__); | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | if ($event->button == 3) { | 
| 241 |  |  |  |  |  |  | $self->crosshair->start ($event); | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | return Gtk2::EVENT_PROPAGATE; | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | sub set_symbol { | 
| 249 |  |  |  |  |  |  | my ($self, $symbol) = @_; | 
| 250 |  |  |  |  |  |  | $self->{'symbol'} = $symbol; | 
| 251 |  |  |  |  |  |  | if ($self->realized) { | 
| 252 |  |  |  |  |  |  | _set_symbol ($self, $symbol); | 
| 253 |  |  |  |  |  |  | } else { | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | # a nasty hack to get initial pages for scaling after windows realized | 
| 256 |  |  |  |  |  |  | $self->{'realize_set_symbol_id'} ||= | 
| 257 |  |  |  |  |  |  | $self->signal_connect (realize => sub { | 
| 258 |  |  |  |  |  |  | my ($self) = @_; | 
| 259 |  |  |  |  |  |  | # once only | 
| 260 |  |  |  |  |  |  | my $id = delete $self->{'realize_set_symbol_id'}; | 
| 261 |  |  |  |  |  |  | $self->signal_handler_disconnect ($id); | 
| 262 |  |  |  |  |  |  | _set_symbol ($self, $self->{'symbol'}); | 
| 263 |  |  |  |  |  |  | }); | 
| 264 |  |  |  |  |  |  | } | 
| 265 |  |  |  |  |  |  | $self->notify ('symbol'); | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | sub _init_graphs { | 
| 269 |  |  |  |  |  |  | my ($self) = @_; | 
| 270 |  |  |  |  |  |  | if ($self->{'init_graphs'}) { return; } | 
| 271 |  |  |  |  |  |  | ### View _init_graphs() | 
| 272 |  |  |  |  |  |  | $self->{'init_graphs'} = 1; | 
| 273 |  |  |  |  |  |  | $self->{'viewstyle'} = viewstyle_read(); | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | require App::Chart::Gtk2::Graph; | 
| 276 |  |  |  |  |  |  | require App::Chart::Gtk2::HAxis; | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | App::Chart::Gtk2::GUI::chart_style_class ('Gtk2::Ex::NumAxis'); | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | require App::Chart::Gtk2::Heading; | 
| 281 |  |  |  |  |  |  | $self->{'heading'} = App::Chart::Gtk2::Heading->new; | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | # initial horiz scale 4 pixels per date | 
| 284 |  |  |  |  |  |  | require App::Chart::Gtk2::HScale; | 
| 285 |  |  |  |  |  |  | my $hadj = $self->{'hadjustment'} | 
| 286 |  |  |  |  |  |  | = App::Chart::Gtk2::HScale->new (pixel_per_value => 4); | 
| 287 |  |  |  |  |  |  | $self->{'haxis'}   = App::Chart::Gtk2::HAxis->new (adjustment => $hadj); | 
| 288 |  |  |  |  |  |  | $self->{'hscroll'} = Gtk2::HScrollbar->new ($hadj); | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | _update_attach ($self); | 
| 291 |  |  |  |  |  |  | $self->show_all; | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | #   # this is a nasty hack to force the Gtk2::Table to set its childrens' | 
| 294 |  |  |  |  |  |  | #   # sizes now, instead of later under the queue_resize or whatever | 
| 295 |  |  |  |  |  |  | #   $self->size_allocate ($self->allocation); | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | if (my $ebox = delete $self->{'initial'}) { | 
| 298 |  |  |  |  |  |  | $self->remove ($ebox); | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | #   0              1  2  3 | 
| 303 |  |  |  |  |  |  | # 0 +--------------+--+--+ | 
| 304 |  |  |  |  |  |  | #   | heading            | | 
| 305 |  |  |  |  |  |  | # 1 +--------------+--+--+ | 
| 306 |  |  |  |  |  |  | #   |              |v |v | | 
| 307 |  |  |  |  |  |  | #   | upper        |a |s | | 
| 308 |  |  |  |  |  |  | #   |              |x |c | | 
| 309 |  |  |  |  |  |  | #   |              |i |r | | 
| 310 |  |  |  |  |  |  | #   |              |s |o | | 
| 311 |  |  |  |  |  |  | #   |              |  |l | | 
| 312 |  |  |  |  |  |  | #   |              |  |l | | 
| 313 |  |  |  |  |  |  | # 5 +--------------+--+--+ | 
| 314 |  |  |  |  |  |  | #   | gap                | | 
| 315 |  |  |  |  |  |  | # 6 +--------------+--+--+ | 
| 316 |  |  |  |  |  |  | #   |              |v |v | | 
| 317 |  |  |  |  |  |  | #   | lower        |a |s | | 
| 318 |  |  |  |  |  |  | #   |              |x |b | | 
| 319 |  |  |  |  |  |  | # 7 +--------------+--+--+ | 
| 320 |  |  |  |  |  |  | #   | haxis        |     | | 
| 321 |  |  |  |  |  |  | # 8 +--------------+     | | 
| 322 |  |  |  |  |  |  | #   | hscroll      |     | | 
| 323 |  |  |  |  |  |  | # 9 +--------------+--+--+ | 
| 324 |  |  |  |  |  |  | # | 
| 325 |  |  |  |  |  |  | sub _update_attach { | 
| 326 |  |  |  |  |  |  | my ($self) = @_; | 
| 327 |  |  |  |  |  |  | require Gtk2::Ex::TableBits; | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | my $y = 0; | 
| 330 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 331 |  |  |  |  |  |  | ($self, $self->{'heading'}, 0,3, $y,$y+1, | 
| 332 |  |  |  |  |  |  | ['fill','shrink','expand'], [], 0,0); | 
| 333 |  |  |  |  |  |  | $y++; | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | my $graphs = $self->{'graphs'}; | 
| 336 |  |  |  |  |  |  | my @graphstyles = @{$self->{'viewstyle'}->{'graphs'}}; | 
| 337 |  |  |  |  |  |  | while ($#$graphs > max (0, $#graphstyles)) { | 
| 338 |  |  |  |  |  |  | my $graph = pop @$graphs; | 
| 339 |  |  |  |  |  |  | $self->remove ($graph); | 
| 340 |  |  |  |  |  |  | $self->remove ($graph->{'noshrink'}); | 
| 341 |  |  |  |  |  |  | $self->remove ($graph->{'vscroll'}); | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  | $graphs->[0] ||= do { | 
| 344 |  |  |  |  |  |  | my $upper = _make_graph($self); | 
| 345 |  |  |  |  |  |  | delete $upper->{'heading_in_graph'}; | 
| 346 |  |  |  |  |  |  | $self->{'hadjustment'}->set (widget => $upper); | 
| 347 |  |  |  |  |  |  | Glib::Ex::ConnectProperties->new ([$upper,'series-list'], | 
| 348 |  |  |  |  |  |  | [$self->{'heading'},'series-list']); | 
| 349 |  |  |  |  |  |  | $upper; | 
| 350 |  |  |  |  |  |  | }; | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | for (my $i = 0; $i < @graphstyles; $i++) { | 
| 353 |  |  |  |  |  |  | my $graph = ($graphs->[$i] ||= _make_graph($self)); | 
| 354 |  |  |  |  |  |  | ### now graphs: "@$graphs" | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | if ($i > 0) { | 
| 357 |  |  |  |  |  |  | my $gap = ($graph->{'gap'} ||= Glib::Object::new ('Gtk2::DrawingArea', | 
| 358 |  |  |  |  |  |  | height_request => 2)); | 
| 359 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 360 |  |  |  |  |  |  | ($self, $gap, 0,3, $y,$y+1, | 
| 361 |  |  |  |  |  |  | [], [], 0,0); | 
| 362 |  |  |  |  |  |  | $y++; | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | my $graphstyle = $graphstyles[$i]; | 
| 366 |  |  |  |  |  |  | my $size = $graphstyle->{'size'}; | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 369 |  |  |  |  |  |  | ($self, $graph, 0,1, $y,$y+$size, | 
| 370 |  |  |  |  |  |  | ['fill','shrink','expand'], | 
| 371 |  |  |  |  |  |  | ['fill','shrink','expand'], 0,0); | 
| 372 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 373 |  |  |  |  |  |  | ($self, $graph->{'noshrink'}, | 
| 374 |  |  |  |  |  |  | 1,2, $y,$y+$size, | 
| 375 |  |  |  |  |  |  | ['fill','shrink'], | 
| 376 |  |  |  |  |  |  | ['fill','shrink','expand'], 0,0); | 
| 377 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 378 |  |  |  |  |  |  | ($self, $graph->{'vscroll'}, | 
| 379 |  |  |  |  |  |  | 2,3, $y,$y+$size, | 
| 380 |  |  |  |  |  |  | ['fill','shrink'], | 
| 381 |  |  |  |  |  |  | ['fill','shrink','expand'], 0,0); | 
| 382 |  |  |  |  |  |  | $y += $size; | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 386 |  |  |  |  |  |  | ($self, $self->{'haxis'}, 0,1, $y,$y+1, | 
| 387 |  |  |  |  |  |  | ['fill','shrink','expand'], | 
| 388 |  |  |  |  |  |  | ['fill','shrink'], 0,0); | 
| 389 |  |  |  |  |  |  | $y++; | 
| 390 |  |  |  |  |  |  | Gtk2::Ex::TableBits::update_attach | 
| 391 |  |  |  |  |  |  | ($self, $self->{'hscroll'}, 0,1, $y,$y+1, | 
| 392 |  |  |  |  |  |  | ['fill','shrink','expand'], | 
| 393 |  |  |  |  |  |  | ['fill','shrink'], 0,0); | 
| 394 |  |  |  |  |  |  | $y++; | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | if (my $cross = $self->{'crosshair_object'}) { | 
| 397 |  |  |  |  |  |  | ### _update_attach() cross widgets: "@$graphs" | 
| 398 |  |  |  |  |  |  | $cross->set (widgets => $graphs); | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | $self->resize (3, $y); | 
| 402 |  |  |  |  |  |  | } | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | sub _make_graph { | 
| 405 |  |  |  |  |  |  | my ($self) = @_; | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | require App::Chart::Gtk2::Graph; | 
| 408 |  |  |  |  |  |  | require App::Chart::Gtk2::AdjScale; | 
| 409 |  |  |  |  |  |  | my $vadj = App::Chart::Gtk2::AdjScale->new (orientation => 'vertical', | 
| 410 |  |  |  |  |  |  | inverted => 1); | 
| 411 |  |  |  |  |  |  | my $graph = App::Chart::Gtk2::Graph->new (hadjustment => $self->{'hadjustment'}, | 
| 412 |  |  |  |  |  |  | vadjustment => $vadj); | 
| 413 |  |  |  |  |  |  | $graph->{'heading_in_graph'} = 1; | 
| 414 |  |  |  |  |  |  | $vadj->set (widget => $graph); | 
| 415 |  |  |  |  |  |  | $graph->signal_connect (button_press_event => \&_do_graph_button_press); | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | require Gtk2::Ex::NumAxis; | 
| 418 |  |  |  |  |  |  | my $vaxis = $graph->{'vaxis'} | 
| 419 |  |  |  |  |  |  | = Gtk2::Ex::NumAxis->new (adjustment => $vadj, | 
| 420 |  |  |  |  |  |  | inverted   => 1); | 
| 421 |  |  |  |  |  |  | $vaxis->signal_connect (number_to_text => \&_vaxis_number_to_text); | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  | require Gtk2::Ex::NoShrink; | 
| 424 |  |  |  |  |  |  | $graph->{'noshrink'} = Gtk2::Ex::NoShrink->new (child => $vaxis); | 
| 425 |  |  |  |  |  |  | my $vscroll = $graph->{'vscroll'} = Gtk2::VScrollbar->new ($vadj); | 
| 426 |  |  |  |  |  |  | $vscroll->set_inverted (1); | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | $vaxis->add_events (['button-press-mask', | 
| 429 |  |  |  |  |  |  | 'button-motion-mask', | 
| 430 |  |  |  |  |  |  | 'button-release-mask']); | 
| 431 |  |  |  |  |  |  | $vaxis->signal_connect (button_press_event => \&_do_vaxis_button_press); | 
| 432 |  |  |  |  |  |  | $graph->show_all; | 
| 433 |  |  |  |  |  |  | return $graph; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | sub _vaxis_number_to_text { | 
| 437 |  |  |  |  |  |  | my ($axis, $number, $decimals) = @_; | 
| 438 |  |  |  |  |  |  | return App::Chart::number_formatter()->format_number ($number, $decimals, 1); | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | sub _do_vaxis_button_press { | 
| 443 |  |  |  |  |  |  | my ($vaxis, $event) = @_; | 
| 444 |  |  |  |  |  |  | if ($event->button == 1) { | 
| 445 |  |  |  |  |  |  | require Gtk2::Ex::Dragger; | 
| 446 |  |  |  |  |  |  | my $dragger = ($vaxis->{'dragger'} ||= | 
| 447 |  |  |  |  |  |  | Gtk2::Ex::Dragger->new | 
| 448 |  |  |  |  |  |  | (widget      => $vaxis, | 
| 449 |  |  |  |  |  |  | vadjustment => $vaxis->get('adjustment'), | 
| 450 |  |  |  |  |  |  | vinverted   => 1, | 
| 451 |  |  |  |  |  |  | cursor      => 'sb-v-double-arrow', | 
| 452 |  |  |  |  |  |  | confine     => 1)); | 
| 453 |  |  |  |  |  |  | $dragger->start ($event); | 
| 454 |  |  |  |  |  |  | } | 
| 455 |  |  |  |  |  |  | return Gtk2::EVENT_PROPAGATE; | 
| 456 |  |  |  |  |  |  | } | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  | sub _set_symbol { | 
| 459 |  |  |  |  |  |  | my ($self, $symbol) = @_; | 
| 460 |  |  |  |  |  |  | if (! $symbol) { return; } | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | _init_graphs ($self); | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | my $hadj = $self->{'hadjustment'}; | 
| 465 |  |  |  |  |  |  | my $haxis = $self->{'haxis'}; | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | if (! $symbol) { | 
| 468 |  |  |  |  |  |  | foreach my $graph (@{$self->{'graphs'}}) { | 
| 469 |  |  |  |  |  |  | $graph->set('series_list', []); | 
| 470 |  |  |  |  |  |  | $graph->get('vadjustment')->empty | 
| 471 |  |  |  |  |  |  | } | 
| 472 |  |  |  |  |  |  | $hadj->empty; | 
| 473 |  |  |  |  |  |  | return; | 
| 474 |  |  |  |  |  |  | } | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | require App::Chart::Series::Database; | 
| 477 |  |  |  |  |  |  | my $series = App::Chart::Series::Database->new ($symbol); | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | my $viewstyle = $self->{'viewstyle'}; | 
| 480 |  |  |  |  |  |  | if ($viewstyle->{'adjust_splits'} | 
| 481 |  |  |  |  |  |  | || $viewstyle->{'adjust_dividends'} | 
| 482 |  |  |  |  |  |  | || $viewstyle->{'adjust_rollovers'}) { | 
| 483 |  |  |  |  |  |  | require App::Chart::Series::Derived::Adjust; | 
| 484 |  |  |  |  |  |  | $series = App::Chart::Series::Derived::Adjust->derive | 
| 485 |  |  |  |  |  |  | ($series, | 
| 486 |  |  |  |  |  |  | adjust_splits     => $viewstyle->{'adjust_splits'}, | 
| 487 |  |  |  |  |  |  | adjust_dividends  => $viewstyle->{'adjust_dividends'}, | 
| 488 |  |  |  |  |  |  | adjust_imputation => $viewstyle->{'adjust_imputation'}, | 
| 489 |  |  |  |  |  |  | adjust_rollovers  => $viewstyle->{'adjust_rollovers'}); | 
| 490 |  |  |  |  |  |  | } | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | my $timebase_class = $self->{'timebase_class'}; | 
| 493 |  |  |  |  |  |  | if (! $series->timebase->isa ($timebase_class)) { | 
| 494 |  |  |  |  |  |  | ### collapse to: $timebase_class | 
| 495 |  |  |  |  |  |  | $series = $series->collapse ($timebase_class); | 
| 496 |  |  |  |  |  |  | } | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | my $timebase = $series->timebase; | 
| 499 |  |  |  |  |  |  | $haxis->set(timebase => $timebase); | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | my $graphstyles = $viewstyle->{'graphs'} || []; | 
| 502 |  |  |  |  |  |  | my $graphs = $self->{'graphs'}; | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | require App::Chart::Gtk2::Graph::Plugin::Latest; | 
| 505 |  |  |  |  |  |  | require App::Chart::Gtk2::Graph::Plugin::Today; | 
| 506 |  |  |  |  |  |  | require App::Chart::Gtk2::Graph::Plugin::Text; | 
| 507 |  |  |  |  |  |  | require App::Chart::Gtk2::Graph::Plugin::AnnLines; | 
| 508 |  |  |  |  |  |  | my @hrange = (0, $series->hi); | 
| 509 |  |  |  |  |  |  | my @today_hrange; | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | for (my $i = 0; $i < @$graphstyles; $i++) { | 
| 512 |  |  |  |  |  |  | my $graphstyle = $graphstyles->[$i]; | 
| 513 |  |  |  |  |  |  | my $graph = $graphs->[$i] || die; | 
| 514 |  |  |  |  |  |  | my $series_list = graphstyle_to_series_list ($graphstyle, $series); | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | # date range for series, latest, and perhaps today | 
| 517 |  |  |  |  |  |  | if ($i == 0) { | 
| 518 |  |  |  |  |  |  | push @hrange, | 
| 519 |  |  |  |  |  |  | (@today_hrange = App::Chart::Gtk2::Graph::Plugin::Today->hrange ($graph, $series_list)); | 
| 520 |  |  |  |  |  |  | push @hrange, | 
| 521 |  |  |  |  |  |  | (App::Chart::Gtk2::Graph::Plugin::Latest->hrange ($graph, $series_list), | 
| 522 |  |  |  |  |  |  | App::Chart::Gtk2::Graph::Plugin::Text->hrange ($graph, $series_list), | 
| 523 |  |  |  |  |  |  | App::Chart::Gtk2::Graph::Plugin::AnnLines->hrange ($graph, $series_list)); | 
| 524 |  |  |  |  |  |  | } | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | $graph->set('series_list', []); | 
| 527 |  |  |  |  |  |  | $graph->set('series_list', $series_list); | 
| 528 |  |  |  |  |  |  | my $decimals = max (0, map {$_->decimals} @$series_list); | 
| 529 |  |  |  |  |  |  | $graph->{'vaxis'}->set (min_decimals => $decimals); | 
| 530 |  |  |  |  |  |  | ### graph: "$i decimals $decimals" | 
| 531 |  |  |  |  |  |  | } | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | require List::MoreUtils; | 
| 534 |  |  |  |  |  |  | my ($lower, $upper) = List::MoreUtils::minmax (@hrange); | 
| 535 |  |  |  |  |  |  | $upper += 2;  # +1 for inclusive, +1 for bit of margin | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | # rightmost edge | 
| 538 |  |  |  |  |  |  | my $value = $upper; | 
| 539 |  |  |  |  |  |  | my $today = $today_hrange[0]; | 
| 540 |  |  |  |  |  |  | if (defined $today) { | 
| 541 |  |  |  |  |  |  | if ($upper > $today + 10) { | 
| 542 |  |  |  |  |  |  | $value = $today + 4; | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  | } | 
| 545 |  |  |  |  |  |  | $value -= $hadj->page_size; | 
| 546 |  |  |  |  |  |  | $lower = min ($lower, $value); | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | ### View decide hadj: "$lower to $upper, value=$value" | 
| 549 |  |  |  |  |  |  | Gtk2::Ex::AdjustmentBits::set_maybe ($hadj, | 
| 550 |  |  |  |  |  |  | lower => $lower, | 
| 551 |  |  |  |  |  |  | upper => $upper, | 
| 552 |  |  |  |  |  |  | value => $value); | 
| 553 |  |  |  |  |  |  | ### View hadj: $hadj->lower." to $upper =",$timebase->to_iso($upper) | 
| 554 |  |  |  |  |  |  | my ($lo, $hi) = $hadj->value_range_inc; | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | foreach my $graph (@$graphs) { | 
| 557 |  |  |  |  |  |  | $graph->update_v_range; | 
| 558 |  |  |  |  |  |  |  | 
| 559 |  |  |  |  |  |  | #     my $series_list = $graph->{'series_list'}; | 
| 560 |  |  |  |  |  |  | #     my $this_series = $series_list->[0] || next; | 
| 561 |  |  |  |  |  |  | # | 
| 562 |  |  |  |  |  |  | #     my ($p_lo, $p_hi) = $this_series->range ($lo, $hi); | 
| 563 |  |  |  |  |  |  | #     if (! defined $p_lo) { | 
| 564 |  |  |  |  |  |  | #       $p_hi = $p_lo = 0; | 
| 565 |  |  |  |  |  |  | #     } | 
| 566 |  |  |  |  |  |  | #     ### View graph vrange: "$p_lo $p_hi" | 
| 567 |  |  |  |  |  |  | #     Gtk2::Ex::AdjustmentBits::set_maybe ($graph->get('vadjustment'), | 
| 568 |  |  |  |  |  |  | #                                          lower => $p_lo, | 
| 569 |  |  |  |  |  |  | #                                          upper => $p_hi); | 
| 570 |  |  |  |  |  |  | } | 
| 571 |  |  |  |  |  |  | } | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | sub graphstyle_to_series_list { | 
| 574 |  |  |  |  |  |  | my ($graphstyle, $series) = @_; | 
| 575 |  |  |  |  |  |  | ### View graphstyle_to_series_list() | 
| 576 |  |  |  |  |  |  | my @series_list; | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | # top-level series goes into upper graph only | 
| 579 |  |  |  |  |  |  | if (exists $graphstyle->{'linestyle'} | 
| 580 |  |  |  |  |  |  | && ($graphstyle->{'linestyle'}||'') ne 'None') { | 
| 581 |  |  |  |  |  |  | $series->linestyle($graphstyle->{'linestyle'}); | 
| 582 |  |  |  |  |  |  | push @series_list, $series; | 
| 583 |  |  |  |  |  |  | } | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | foreach my $indicatorstyle (@{$graphstyle->{'indicators'}}) { | 
| 586 |  |  |  |  |  |  | my $key = $indicatorstyle->{'key'} || next; | 
| 587 |  |  |  |  |  |  | if ($key eq 'None') { next; } | 
| 588 |  |  |  |  |  |  | ### $indicatorstyle | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  | if (! $series->can($key)) { | 
| 591 |  |  |  |  |  |  | warn "Ignoring unknown indicator '$key'"; | 
| 592 |  |  |  |  |  |  | next; | 
| 593 |  |  |  |  |  |  | } | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | require App::Chart::IndicatorInfo; | 
| 596 |  |  |  |  |  |  | my $info = App::Chart::IndicatorInfo->new ($key); | 
| 597 |  |  |  |  |  |  | my @params; | 
| 598 |  |  |  |  |  |  | foreach my $paraminfo (@{$info->parameter_info}) { | 
| 599 |  |  |  |  |  |  | my $paramkey = $paraminfo->{'key'}; | 
| 600 |  |  |  |  |  |  | push @params, ($indicatorstyle->{$paramkey} | 
| 601 |  |  |  |  |  |  | // $paraminfo->{'default'}); | 
| 602 |  |  |  |  |  |  | } | 
| 603 |  |  |  |  |  |  | ### @params | 
| 604 |  |  |  |  |  |  | my $derived = $series->$key (@params); | 
| 605 |  |  |  |  |  |  | push @series_list, $derived; | 
| 606 |  |  |  |  |  |  | } | 
| 607 |  |  |  |  |  |  | return \@series_list; | 
| 608 |  |  |  |  |  |  | } | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | # 'data-changed' | 
| 611 |  |  |  |  |  |  | sub _do_data_changed { | 
| 612 |  |  |  |  |  |  | my ($self, $symbol_hash) = @_; | 
| 613 |  |  |  |  |  |  | my $symbol = $self->{'symbol'} // return; | 
| 614 |  |  |  |  |  |  | if (exists $symbol_hash->{$symbol}) { | 
| 615 |  |  |  |  |  |  | ### "View _do_data_changed() displayed symbol: $symbol | 
| 616 |  |  |  |  |  |  | _set_symbol ($self, $symbol); | 
| 617 |  |  |  |  |  |  | } | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  |  | 
| 620 |  |  |  |  |  |  | sub centre { | 
| 621 |  |  |  |  |  |  | my ($self) = @_; | 
| 622 |  |  |  |  |  |  | $self->{'graphs'}->[0]->centre; | 
| 623 |  |  |  |  |  |  | #  $self->{'lower'}->centre; | 
| 624 |  |  |  |  |  |  | } | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | sub zoom { | 
| 627 |  |  |  |  |  |  | my ($self, $xfactor, $yfactor) = @_; | 
| 628 |  |  |  |  |  |  | if ($xfactor != 1) { | 
| 629 |  |  |  |  |  |  | my $hadj = $self->{'hadjustment'}; | 
| 630 |  |  |  |  |  |  | my $ppv = $hadj->get_pixel_per_value; | 
| 631 |  |  |  |  |  |  | my $new_ppv = POSIX::ceil ($xfactor * $ppv); | 
| 632 |  |  |  |  |  |  | if ($ppv == $new_ppv) { | 
| 633 |  |  |  |  |  |  | if ($xfactor < 1) { | 
| 634 |  |  |  |  |  |  | $new_ppv = max (1, $new_ppv-1); | 
| 635 |  |  |  |  |  |  | } else { | 
| 636 |  |  |  |  |  |  | $new_ppv = $new_ppv+1; | 
| 637 |  |  |  |  |  |  | } | 
| 638 |  |  |  |  |  |  | } | 
| 639 |  |  |  |  |  |  | if ($ppv != $new_ppv) { | 
| 640 |  |  |  |  |  |  | $hadj->set_pixel_per_value ($new_ppv); | 
| 641 |  |  |  |  |  |  | } | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  | if ($yfactor != 1) { | 
| 644 |  |  |  |  |  |  | foreach my $graph (@{$self->{'graphs'}}) { | 
| 645 |  |  |  |  |  |  | my $vadj = $graph->get('vadjustment'); | 
| 646 |  |  |  |  |  |  | $vadj->set_pixel_per_value ($yfactor * $vadj->get_pixel_per_value); | 
| 647 |  |  |  |  |  |  | } | 
| 648 |  |  |  |  |  |  | } | 
| 649 |  |  |  |  |  |  | } | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | 1; | 
| 652 |  |  |  |  |  |  | __END__ | 
| 653 |  |  |  |  |  |  |  | 
| 654 |  |  |  |  |  |  | =for stopwords viewstyle | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | =head1 NAME | 
| 657 |  |  |  |  |  |  |  | 
| 658 |  |  |  |  |  |  | App::Chart::Gtk2::View -- view widget of heading, graphs, axes, scrollbars, etc | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 661 |  |  |  |  |  |  |  | 
| 662 |  |  |  |  |  |  | my $view = App::Chart::Gtk2::View->new; | 
| 663 |  |  |  |  |  |  |  | 
| 664 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 665 |  |  |  |  |  |  |  | 
| 666 |  |  |  |  |  |  | A C<App::Chart::Gtk2::View> widget displays graphs of the data from a given stock | 
| 667 |  |  |  |  |  |  | symbol, with a "viewstyle" controlling what graphs and indicators are | 
| 668 |  |  |  |  |  |  | presented. | 
| 669 |  |  |  |  |  |  |  | 
| 670 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  | =over 4 | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | =item C<< $view->symbol >> | 
| 675 |  |  |  |  |  |  |  | 
| 676 |  |  |  |  |  |  | Return the currently displayed stock symbol (a string), or C<undef> if none. | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | =item C<< $view->centre >> | 
| 679 |  |  |  |  |  |  |  | 
| 680 |  |  |  |  |  |  | Centre the displayed data within the graph windows.  This is the | 
| 681 |  |  |  |  |  |  | "View/Centre" menu item in the main GUI. | 
| 682 |  |  |  |  |  |  |  | 
| 683 |  |  |  |  |  |  | =back | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  | =head1 PROPERTIES | 
| 686 |  |  |  |  |  |  |  | 
| 687 |  |  |  |  |  |  | =over 4 | 
| 688 |  |  |  |  |  |  |  | 
| 689 |  |  |  |  |  |  | =item C<symbol> (string, default none) | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | =back | 
| 692 |  |  |  |  |  |  |  | 
| 693 |  |  |  |  |  |  | =cut |