File Coverage

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


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010, 2011, 2015 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              
18             package App::Chart::Gtk2::WatchlistModel;
19 1     1   738 use 5.008;
  1         5  
20 1     1   8 use strict;
  1         4  
  1         33  
21 1     1   9 use warnings;
  1         3  
  1         53  
22 1     1   220 use Gtk2 1.190; # for working TreeModelFilter modify_func
  0            
  0            
23             use Carp;
24             use Locale::TextDomain ('App-Chart');
25              
26             use App::Chart;
27             use App::Chart::Gtk2::Symlist;
28              
29             # uncomment this to run the ### lines
30             #use Devel::Comments;
31              
32             use Gtk2::Ex::TreeModelFilter::Draggable;
33             use base 'Gtk2::Ex::TreeModelFilter::Change';
34             use Glib::Object::Subclass
35             'Gtk2::Ex::TreeModelFilter::Draggable',
36             properties => [ Glib::ParamSpec->object
37             ('symlist',
38             'symlist',
39             'The symlist to present.',
40             'App::Chart::Gtk2::Symlist',
41             ['readable']) ];
42              
43             use constant { COL_SYMBOL => 0,
44             COL_BIDOFFER => 1,
45             COL_LAST => 2,
46             COL_CHANGE => 3,
47             COL_HIGH => 4,
48             COL_LOW => 5,
49             COL_VOLUME => 6,
50             COL_WHEN => 7,
51             COL_NOTE => 8,
52             COL_COLOUR => 9,
53             COL_TOOLTIP => 10,
54             NUM_COLUMNS => 11
55             };
56              
57             my $empty_symlist;
58             sub new {
59             my ($class, $symlist) = @_;
60             if (! defined $symlist) {
61             require App::Chart::Gtk2::Symlist::Constructed;
62             $symlist = ($empty_symlist ||= App::Chart::Gtk2::Symlist::Constructed->new);
63             }
64              
65             my $self = $class->Gtk2::Ex::TreeModelFilter::Draggable::new ($symlist);
66             $self->{'symlist'} = $symlist;
67             $self->set_modify_func ([('Glib::String') x NUM_COLUMNS],
68             \&_model_filter_func);
69             App::Chart::chart_dirbroadcast()->connect_for_object
70             ('latest-changed', \&_do_latest_changed, $self);
71             return $self;
72             }
73              
74             # 'latest-changed' from DirBroadcast
75             sub _do_latest_changed {
76             my ($self, $symbol_hash) = @_;
77             ### WatchlistModel _do_latest_changed(): join(' ',keys %$symbol_hash)
78              
79             my $symlist = $self->{'symlist'};
80              
81             my $h = $symlist->hash;
82             foreach my $symbol (keys %$symbol_hash) {
83             if (exists $h->{$symbol}) {
84             my $index = $h->{$symbol};
85             my $path = Gtk2::TreePath->new_from_indices ($index);
86             my $iter = $symlist->iter_nth_child (undef, $index)
87             || next; # oops, something not up-to-date
88             $self->row_changed ($path, $iter);
89             }
90             }
91              
92             # much slower:
93             #
94             # $symlist->foreach
95             # (sub {
96             # my ($self, $path, $iter) = @_;
97             # my $symbol = $self->get_value($iter,0);
98             # if (exists $symbol_hash->{$symbol}) {
99             # if (DEBUG >= 2) { print "WatchlistModel: changed $symbol ",
100             # $path->to_string,"\n"; }
101             # $self->row_changed ($path, $iter);
102             # }
103             # return 0; # keep iterating
104             # });
105              
106             ### WatchlistModel _do_latest_changed() end ...
107             }
108              
109             sub _model_filter_func {
110             my ($self, $iter, $col) = @_;
111             my $child_model = $self->get_model;
112             my $child_iter = $self->convert_iter_to_child_iter ($iter);
113             my $symbol = $child_model->get_value ($child_iter, 0);
114              
115             if ($col == COL_SYMBOL) {
116             return $symbol;
117             }
118             require App::Chart::Latest;
119             my $latest = App::Chart::Latest->get ($symbol);
120              
121             my $cache = ($latest->{(__PACKAGE__)} ||= []);
122             if (exists $cache->[$col]) { return $cache->[$col]; }
123             my $str;
124              
125             if ($col == COL_BIDOFFER) {
126             my $bid = $latest->{'bid'};
127             my $offer = $latest->{'offer'};
128             if (defined $bid || defined $offer) {
129             $str = (defined $bid ? format_price($bid) : '--')
130             . (defined $bid && defined $offer && $bid > $offer ? 'x' : '/')
131             . (defined $offer ? format_price($offer) : '--');
132             }
133              
134             } elsif ($col == COL_LAST) {
135             $str = format_price ($latest->{'last'});
136              
137             } elsif ($col == COL_CHANGE) {
138             $str = format_price ($latest->{'change'});
139              
140             } elsif ($col == COL_HIGH) {
141             $str = format_price ($latest->{'high'});
142              
143             } elsif ($col == COL_LOW) {
144             $str = format_price ($latest->{'low'});
145              
146             } elsif ($col == COL_VOLUME) {
147             $str = $latest->formatted_volume;
148              
149             } elsif ($col == COL_WHEN) {
150             $str = $latest->short_datetime;
151              
152             } elsif ($col == COL_NOTE) {
153             my @notes = ();
154             my $dividend = $latest->{'dividend'};
155             if (defined $dividend) {
156             push @notes, __x('ex {dividend}', dividend => $dividend);
157             }
158             if ($latest->{'halt'}) { push @notes, __('halt'); }
159             if ($latest->{'limit_up'}) { push @notes, __('limit up'); }
160             if ($latest->{'limit_down'}) { push @notes, __('limit down'); }
161             if (my $note = $latest->{'note'}) { push @notes, $note; }
162             if (my $error = $latest->{'error'}) { push @notes, $error; }
163             $str = join (', ', @notes);
164              
165             } elsif ($col == COL_COLOUR) {
166             require App::Chart::Gtk2::Job::Latest;
167             if ($App::Chart::Gtk2::Job::Latest::inprogress{$symbol}) {
168             $str = '#00007F';
169             } else {
170             my $change = $latest->{'change'};
171             if (defined $change) {
172             if ($change > 0) { $str = '#007F00'; }
173             elsif ($change < 0) { $str = '#7F0000'; }
174             }
175             }
176              
177             } elsif ($col == COL_TOOLTIP) {
178             $str = $symbol;
179             require App::Chart::Database;
180             if (my $name = ($latest->{'name'}
181             || App::Chart::Database->symbol_name ($symbol))) {
182             $str .= ' - ' . $name;
183             }
184             $str .= "\n";
185              
186             if (my $quote_date = $latest->{'quote_date'}) {
187             my $quote_time = $latest->{'quote_time'} || '';
188             $str .= __x("Quote: {quote_date} {quote_time}",
189             quote_date => $quote_date,
190             quote_time => $quote_time);
191             $str .= "\n";
192             }
193              
194             if (my $last_date = $latest->{'last_date'}) {
195             my $last_time = $latest->{'last_time'} || '';
196             $str .= __x("Last: {last_date} {last_time}",
197             last_date => $last_date,
198             last_time => $last_time);
199             $str .= "\n";
200             }
201              
202             require App::Chart::TZ;
203             my $timezone = App::Chart::TZ->for_symbol($symbol);
204             $str .= __x("{location} time; source {source}",
205             location => $timezone->name,
206             source => $latest->{'source'});
207             # tip is markup format, though that's not actually documented as of 2.12
208             $str = Glib::Markup::escape_text ($str);
209             }
210              
211             return ($cache->[$col] = $str);
212             }
213              
214             sub format_price {
215             my ($str) = @_;
216             if (! defined $str) { return ''; }
217             my $nf = App::Chart::number_formatter();
218             return eval { $nf->format_number ($str, App::Chart::count_decimals($str), 1) }
219             // __('[bad]');
220             }
221              
222             sub get_symlist {
223             my ($self) = @_;
224             return $self->get_model;
225             }
226              
227             1;
228             __END__
229              
230             =for stopwords watchlist symlist ie
231              
232             =head1 NAME
233              
234             App::Chart::Gtk2::WatchlistModel -- watchlist data model object
235              
236             =for test_synopsis my ($symlist)
237              
238             =head1 SYNOPSIS
239              
240             use App::Chart::Gtk2::WatchlistModel;
241             my $model = App::Chart::Gtk2::WatchlistModel->new ($symlist);
242              
243             =head1 OBJECT HIERARCHY
244              
245             C<App::Chart::Gtk2::WatchlistModel> is a subclass of C<Gtk2::TreeModelFilter>,
246              
247             Glib::Object
248             Gtk2::TreeModelFilter
249             App::Chart::Gtk2::WatchlistModel
250              
251             =head1 DESCRIPTION
252              
253             A C<App::Chart::Gtk2::WatchlistModel> object presents the data from a given
254             C<App::Chart::Gtk2::Symlist> in a form suitable for
255             C<App::Chart::Gtk2::WatchlistDialog> dialog. Currently this is its sole
256             use.
257              
258             =head1 FUNCTIONS
259              
260             =over 4
261              
262             =item C<< App::Chart::Gtk2::WatchlistModel->new ($symlist) >>
263              
264             Create and return a C<App::Chart::Gtk2::WatchlistModel> object presenting the
265             symbols in C<$symlist>.
266              
267             =back
268              
269             =head1 PROPERTIES
270              
271             =over 4
272              
273             =item C<symlist> (C<App::Chart::Gtk2::Symlist> object, read-only)
274              
275             The symlist to track and get data from. The intention is that this is
276             "construct-only", ie. to be set only when first constructing the model. To
277             get a different symlist then create a new model.
278              
279             =back
280              
281             =head1 SEE ALSO
282              
283             L<App::Chart::Gtk2::WatchlistDialog>
284              
285             =cut