| 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
|
|
425
|
use 5.008; |
|
|
1
|
|
|
|
|
3
|
|
|
20
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
17
|
|
|
21
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
31
|
|
|
22
|
1
|
|
|
1
|
|
144
|
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 |