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 |