File Coverage

blib/lib/App/Chart/Gtk2/IndicatorModel.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 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::IndicatorModel;
19 1     1   497 use 5.008;
  1         5  
20 1     1   9 use strict;
  1         2  
  1         34  
21 1     1   5 use warnings;
  1         2  
  1         25  
22 1     1   306 use Gtk2;
  0            
  0            
23             use List::MoreUtils;
24             use Locale::TextDomain ('App-Chart');
25              
26             use constant DEBUG => 0;
27              
28             use Glib::Object::Subclass
29             'Gtk2::TreeStore';
30              
31             use Class::Singleton 1.03; # 1.03 for _new_instance()
32             use base 'Class::Singleton';
33             *_new_instance = \&Glib::Object::new;
34              
35             my %columns;
36             BEGIN {
37             %columns = (COL_KEY => 0, # string
38             COL_NAME => 1, # string
39             COL_TYPE => 2, # string
40             COL_PRIORITY => 3); # string
41             }
42             use constant ({%columns});
43             use constant NUM_COLS => 4;
44              
45             our $MODEL;
46             use constant::defer INIT_INSTANCE => sub {
47             my ($self) = @_;
48              
49             $self->set_column_types (('Glib::String') x NUM_COLS);
50             @{$self}{keys %columns} = values %columns;
51              
52             $self->set ($self->append(undef),
53             COL_KEY, 'None',
54             COL_NAME, __('None'));
55              
56             my $aref = require App::Chart::Gtk2::IndicatorModelGenerated;
57              
58             # add anything not in IndicatorModelGenerated.pm
59             {
60             require Module::Find;
61             require Gtk2::Ex::TreeModelBits;
62             my %extra;
63             # hash slice, everything on disk
64             @extra{map {s/^App::Chart::Series::Derived:://;$_}
65             Module::Find::findsubmod ('App::Chart::Series::Derived')} = ();
66             # hash slice, drop keys already in the model
67             delete @extra{map {$_->{'key'}} @$aref};
68              
69             # could load each extra to get name,type,priority ...
70             foreach my $key (sort keys %extra) {
71             push @$aref, { key => $key,
72             name => $key,
73             priority => 0 };
74             }
75             }
76              
77             # sort by translated name, case-insensitive
78             @$aref = sort {$b->{'priority'} <=> $a->{'priority'}
79             || lc($a->{'name'}) cmp lc($b->{'name'})
80             || $a->{'name'} cmp $b->{'name'}
81             } @$aref;
82             my ($top, $low)
83             = List::MoreUtils::part {$_->{'priority'} >= 0 ? 0 : 1} @$aref;
84             foreach my $elem (@$top) {
85             $self->set($self->append(undef),
86             COL_KEY, $elem->{'key'},
87             COL_NAME, $elem->{'name'},
88             COL_TYPE, $elem->{'type'},
89             COL_PRIORITY, $elem->{'priority'});
90             }
91             if (@$low) {
92             my $low_iter = $self->append(undef);
93             $self->set ($low_iter,
94             COL_KEY, 'low-priority',
95             COL_NAME, __('Low Priority'));
96             foreach my $elem (@$low) {
97             $self->set($self->append($low_iter),
98             COL_KEY, $elem->{'key'},
99             COL_NAME, $elem->{'name'},
100             COL_TYPE, $elem->{'type'},
101             COL_PRIORITY, $elem->{'priority'});
102             }
103             }
104             if (DEBUG) {
105             require Scalar::Util;
106             Scalar::Util::weaken ($aref);
107             if ($aref) {
108             die "Oops, IndicatorModelGenerated array not destroyed";
109             } else {
110             print "IndicatorModelGenerated array destroyed\n";
111             }
112             }
113              
114             #--------------
115             # TA
116              
117             if (eval { require Finance::TA }) {
118             my $talib_iter = $self->append(undef);
119             $self->set ($talib_iter, COL_NAME, __('TA-Lib'));
120             my $talib_path = $self->get_path ($talib_iter);
121              
122             my %exclude = ('0', 1,
123             'Math Operators', 1,
124             'Math Transform', 1,
125             );
126              
127             my @groups = Finance::TA::TA_GroupTable();
128             @groups = grep {!$exclude{$_}} @groups;
129             foreach my $group (@groups) {
130              
131             my @functions = Finance::TA::TA_FuncTable($group);
132             shift @functions;
133              
134             $talib_iter = $self->get_iter($talib_path);
135             my $group_iter = $self->append($talib_iter);
136             $self->set ($group_iter, COL_NAME, $group);
137             my $group_path = $self->get_path ($group_iter);
138              
139             foreach my $func (@functions) {
140             if ($func eq 'MA') { next; } # selectable MA
141              
142             my $fh;
143             Finance::TA::TA_GetFuncHandle($func, \$fh) == $Finance::TA::TA_SUCCESS
144             or die;
145             my $fi;
146             Finance::TA::TA_GetFuncInfo($fh, \$fi) == $Finance::TA::TA_SUCCESS
147             or die;
148              
149             # flag bits per ta_abstract.h
150             # TA_FUNC_FLG_VOLUME for volume overlay
151             # TA_FUNC_FLG_UNST_PER initial unstable
152             my $flags = $fi->{'flags'};
153             no warnings 'once';
154              
155             # if ($flags & $Finance::TA::TA_FUNC_FLG_CANDLESTICK) { next; }
156              
157             my $type = 'indicator';
158             if ($group eq 'Price Transform') {
159             $type = 'selector';
160             } elsif ($flags & $Finance::TA::TA_FUNC_FLG_OVERLAP) {
161             # output same as input
162             $type = 'average';
163             }
164              
165             my $hint = $fi->{'hint'};
166             my $name = $hint;
167             if ($hint !~ /\Q$func/) {
168             $name = "$func - $name";
169             }
170              
171             $group_iter = $self->get_iter($group_path);
172             my $func_iter = $self->append($group_iter);
173             $self->set ($func_iter,
174             COL_KEY, "TA_$func",
175             COL_NAME, $name,
176             COL_TYPE, $type);
177             }
178              
179             $group_iter = $self->get_iter($group_path);
180             if ($self->iter_n_children ($group_iter) == 0) {
181             $self->remove ($group_iter);
182             }
183             }
184             }
185              
186             #--------------
187             # GT
188              
189             require Module::Find;
190             if (my @modules = Module::Find::findsubmod ('GT::Indicators')) {
191              
192             my %type = (ADL => 'indicator',
193             ADX => 'indicator',
194             ADXR => 'indicator',
195             AROON => 'indicator',
196             AT3 => 'average',
197             ATR => 'indicator',
198             BBO => 'indicator',
199             BOL => 'average',
200             BPCorrelation => [ 'indicator', __('GT Misc') ],
201             CCI => 'indicator',
202             CHAIKIN => 'indicator',
203             CMO => 'indicator',
204              
205             # result is a binary code or something, so might be much to
206             # view
207             CNDL => [ 'indicator', __('GT Misc') ],
208              
209             ChaikinsVola => 'indicator',
210             Chandelier => 'average',
211             DMI => 'indicator',
212             DSS => 'indicator',
213             EMA => 'average',
214             ENV => 'average',
215             EPMA => 'average',
216             EVWMA => 'average',
217             ElderRay => 'indicator',
218             FISH => 'indicator',
219             FRAMA => 'average',
220             ForceIndex => 'indicator',
221             FromTimeframe => 'special', # time collapsing
222             GAPO => 'indicator',
223             GMEAN => [ 'indicator', __('GT Misc') ],
224             HilbertPeriod => [ 'indicator', __('GT Misc') ],
225             HilbertSine => [ 'indicator', __('GT Misc') ],
226             IFISH => 'indicator',
227             InstantTrendLine => 'average',
228             Interquartil => 'indicator',
229             KAMA => 'average',
230             Keltner => 'average',
231             KirshenbaumBands => 'average',
232             # LinearRegression.pm
233             MACD => 'indicator',
234             MAMA => 'average',
235             MASS => 'indicator',
236             MEAN => 'selector',
237             MFI => 'indicator',
238             MOM => 'indicator',
239             MaxDrawDown => 'indicator',
240             MaxPossibleGain => 'indicator',
241             MaxPossibleLoss => 'indicator',
242             OBV => 'indicator',
243              
244             # but param is a date, so probably can't use
245             PERF => [ 'indicator', __('GT Misc') ],
246              
247             PFE => 'indicator',
248             PFEraw => 'indicator',
249             PGO => 'indicator',
250             PP => 'average',
251             PercentagePosition => 'indicator',
252              
253             # but params are strings, so probably can't use
254             Prices => 'selector',
255              
256             QSTICK => 'indicator',
257             RAVI => 'indicator',
258             REMA => 'average',
259             RMI => 'indicator',
260             ROC => 'indicator',
261             RSI => 'indicator',
262             RSquare => 'indicator',
263             Range => [ 'indicator', __('GT Misc') ],
264             SAR => 'average',
265             SMA => 'average',
266             SMI => 'indicator',
267             STO => 'indicator',
268             SWMA => 'average',
269             SafeZone => 'indicator',
270             StandardDeviation => [ 'indicator', __('GT Misc') ],
271             StandardError => [ 'indicator', __('GT Misc') ],
272             T3 => 'average',
273             TDREI => 'indicator',
274             TETHER => 'average',
275             TMA => 'average',
276             TP => 'selector',
277             TR => [ 'indicator', __('GT Misc') ],
278             TRIX => 'average',
279             Test => 'exclude', # development stuff
280             UI => 'indicator',
281             VHF => 'indicator',
282             VOSC => [ 'indicator', __('GT Misc') ],
283             VROC => [ 'indicator', __('GT Misc') ],
284             WMA => 'average',
285             WTCL => 'selector',
286             WWMA => 'average',
287             WilliamsR => 'indicator',
288             ZigZag => 'average',
289             );
290              
291              
292             my $gt_iter = $self->append(undef);
293             $self->set ($gt_iter, COL_NAME, __('Genius Trader'));
294             my $gt_path = $self->get_path ($gt_iter);
295             my %other;
296              
297             foreach my $mod (sort @modules) {
298             $mod =~ s/^GT::Indicators:://;
299              
300             my $type = $type{$mod};
301             if (ref $type) {
302             ($type, my $sub) = @$type;
303             push @{$other{$sub}}, [ $mod, $type ];
304             next;
305             }
306             if (defined $type && $type eq 'exclude') {
307             next;
308             }
309              
310             $gt_iter = $self->get_iter($gt_path);
311             my $mod_iter = $self->append($gt_iter);
312              
313             $self->set ($mod_iter,
314             COL_KEY, "GT_$mod",
315             COL_NAME, __x('GT {name}', name => $mod),
316             COL_TYPE, $type);
317             }
318              
319             foreach my $sub (sort keys %other) {
320             $gt_iter = $self->get_iter($gt_path);
321             my $sub_iter = $self->append($gt_iter);
322             $self->set ($sub_iter, COL_NAME, $sub);
323             my $sub_path = $self->get_path($sub_iter);
324              
325             foreach my $elem (@{$other{$sub}}) {
326             my ($mod, $type) = @$elem;
327              
328             $sub_iter = $self->get_iter($sub_path);
329             my $mod_iter = $self->append($sub_iter);
330             $self->set ($mod_iter,
331             COL_KEY, "GT_$mod",
332             COL_NAME, __x('GT {name}', name => $mod),
333             COL_TYPE, $type);
334             }
335             }
336             }
337              
338             return;
339             };
340              
341              
342             my %by_type;
343             sub by_type {
344             my ($class, $want_type) = @_;
345             if (! $want_type) { return $class->instance; }
346              
347             return ($by_type{$want_type} ||= do {
348             my $model = Gtk2::TreeModelFilter->new ($class->instance);
349             @{$model}{keys %columns} = values %columns;
350              
351             $model->set_visible_func
352             (Gtk2::Ex::TreeModelFilterBits::visible_func_hide_empty_parents
353             (sub {
354             my ($model, $iter) = @_;
355             my $type = $model->get($iter,COL_TYPE);
356             return (! $type || $type eq $want_type);
357             }));
358             $model
359             });
360             }
361              
362             # =over 4
363             #
364             # =item C<< $wrapped_visible_func = Gtk2::Ex::TreeModelFilterBits::visible_func_hide_empty_parents ($visible_func) >>
365             #
366             # Return a function suitable for use as a TreeModelFilter
367             # C<set_visible_func> which applies C<$visible_func> and in addition makes
368             # parent rows visible only if there's at least one visible child row under
369             # it.
370             #
371             # C<$visible_func> is called just like a normal C<set_visible_func>, ie.
372             #
373             # bool = &$visible_func ($model, $iter, $userdata);
374             #
375             # The C<$wrapped_visible_func> returned is designed to be called the same
376             # way.
377             #
378             # If you omit C<$userdata> from the C<set_visible_func> install then just
379             # two arguments are passed. C<$wrapped_visible_func> likewise passes just
380             # two arguments to C<$visible_func> in that case (though it'd be unusual for
381             # that to make much difference).
382             #
383             # =back
384             #
385             # =cut
386              
387             sub Gtk2::Ex::TreeModelFilterBits::visible_func_hide_empty_parents {
388             my ($visible_func) = @_;
389              
390             # cf Sub::Recursive for avoiding circularities
391             #
392             my $weak_wrapped_func;
393             my $wrapped_func = sub {
394             if (! $visible_func->(@_)) { return 0; }
395             my ($model, $iter) = @_;
396             if (my $subiter = $model->iter_children ($iter)) {
397             shift; shift;
398             do {
399             if ($weak_wrapped_func->($model, $subiter, @_)) {
400             return 1;
401             }
402             } while ($subiter = $model->iter_next($subiter));
403             return 0;
404             } else {
405             goto $visible_func;
406             }
407             };
408             require Scalar::Util;
409             $weak_wrapped_func = $wrapped_func;
410             Scalar::Util::weaken ($weak_wrapped_func);
411             return $wrapped_func;
412             }
413              
414             1;
415             __END__