# Copyright 2007, 2008, 2009, 2010, 2011 Kevin Ryde # This file is part of Chart. # # Chart is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software # Foundation; either version 3, or (at your option) any later version. # # Chart is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with Chart. If not, see . # return ($self->{'symbol_history'} ||= do { # require App::Chart::SymbolHistory; # my $history = App::Chart::SymbolHistory->new # (back_action => $self->{'actiongroup'}->get_action('Back'), # forward_action => $self->{'actiongroup'}->get_action('Forward'), # back_button => $self->{'ui'}->get_widget("/ToolBar/Back"), # forward_button => $self->{'ui'}->get_widget("/ToolBar/Forward")); # $history->signal_connect # (menu_activate => \&_do_symbol_history_menu_activate, $self); # $history; # }); # sub _do_symbol_history_menu_activate { # my ($history, $symbol, $symlist, $self) = @_; # $self->goto_symbol ($symbol, $symlist); # } # $actiongroup->get_action('Back')->set_sensitive (0); # $actiongroup->get_action('Forward')->set_sensitive (0); package App::Chart::SymbolHistory; use 5.008; use strict; use warnings; use Gtk2 1.220; use Scalar::Util; use Locale::TextDomain ('App-Chart'); use App::Chart::Glib::Ex::MoreUtils; use App::Chart; # set this to 1 for some diagnostic prints use constant DEBUG => 0; use constant MAX_HISTORY => 40; use Glib::Object::Subclass 'Glib::Object', signals => { menu_activate => { param_types => ['Glib::String', 'Glib::Scalar'], return_type => undef }, }, properties => [ Glib::ParamSpec->object ('back-action', 'back-action', 'Blurb.', 'Glib::Object', Glib::G_PARAM_READWRITE), Glib::ParamSpec->object ('forward-action', 'forward-action', 'Blurb.', 'Glib::Object', Glib::G_PARAM_READWRITE), Glib::ParamSpec->object ('back-button', 'back-button', 'Blurb.', 'Gtk2::Widget', Glib::G_PARAM_READWRITE), Glib::ParamSpec->object ('forward-button', 'forward-button', 'Blurb.', 'Gtk2::Widget', Glib::G_PARAM_READWRITE), ]; use constant { COL_SYMBOL => 0, COL_SYMLIST => 1 }; #------------------------------------------------------------------------------ sub INIT_INSTANCE { my ($self) = @_; $self->{'back_model'} = Gtk2::ListStore->new ('Glib::String','Glib::Scalar'); $self->{'forward_model'} = Gtk2::ListStore->new ('Glib::String','Glib::Scalar'); $self->{'current'} = undef; App::Chart::chart_dirbroadcast()->connect_for_object ('delete-symbol', \&_do_delete_symbol, $self); } sub SET_PROPERTY { my ($self, $pspec, $newval) = @_; my $pname = $pspec->get_name; $self->{$pname} = $newval; # per default GET_PROPERTY my $ref_weak_self = App::Chart::Glib::Ex::MoreUtils::ref_weak ($self); if ($pname eq 'forward_button') { my $button = $newval; if ($button->isa ('Gtk2::ToolButton')) { $button = $button->get_child; } $button->signal_connect (button_press_event => \&_do_forward_button_press, $ref_weak_self); } elsif ($pname eq 'back_button') { my $button = $newval; if ($button->isa ('Gtk2::ToolButton')) { $button = $button->get_child; } $button->signal_connect (button_press_event => \&_do_back_button_press, $ref_weak_self); } } # 'button-press-event' handler on the back button sub _do_back_button_press { my ($button, $event, $ref_weak_self) = @_; my $self = $$ref_weak_self || return; if ($button->sensitive && $event->button == 3) { $self->back_menu->popup (undef,undef,undef,undef, $event->button, $event->time); } return Gtk2::EVENT_PROPAGATE; } # 'button-press-event' handler on the forward button sub _do_forward_button_press { my ($button, $event, $ref_weak_self) = @_; my $self = $$ref_weak_self || return; if ($button->sensitive && $event->button == 3) { $self->forward_menu->popup (undef,undef,undef,undef, $event->button, $event->time); } return Gtk2::EVENT_PROPAGATE; } sub goto { my ($self, $symbol, $symlist) = @_; if (DEBUG) { print "SymbolHistory goto $symbol $symlist\n"; } if ($self->{'current_symbol'} && $symbol ne $self->{'current_symbol'}) { if (DEBUG) { print " push back ",$self->{'current_symbol'}, " ",$self->{'current_symlist'},"\n"; } my $back_model = $self->{'back_model'}; $back_model->insert_with_values (0, COL_SYMBOL, $self->{'current_symbol'}, COL_SYMLIST, $self->{'current_symlist'}); _update_actions ($self); _limit ($back_model); } else { if (DEBUG) { print " skip same as current\n"; } } $self->{'current_symbol'} = $symbol; $self->{'current_symlist'} = $symlist; } sub back { my ($self) = @_; my $back_model = $self->{'back_model'}; my $iter = $back_model->get_iter_first; if (! $iter) { return (undef, undef); } if (defined $self->{'current_symbol'}) { my $forward_model = $self->{'forward_model'}; $forward_model->insert_with_values (0, COL_SYMBOL, $self->{'current_symbol'}, COL_SYMLIST, $self->{'current_symlist'}); _limit ($forward_model); } my $symbol = $back_model->get_value ($iter, COL_SYMBOL); my $symlist = $back_model->get_value ($iter, COL_SYMLIST); $back_model->remove ($iter); _update_actions ($self); if (DEBUG) { print "SymbolHistory back to $symbol $symlist\n"; } return ($self->{'current_symbol'} = $symbol, $self->{'current_symlist'} = $symlist); } sub forward { my ($self) = @_; my $forward_model = $self->{'forward_model'}; my $iter = $forward_model->get_iter_first; if (! $iter) { return (undef, undef); } my $symbol = $forward_model->get_value ($iter, COL_SYMBOL); my $symlist = $forward_model->get_value ($iter, COL_SYMLIST); $forward_model->remove ($iter); _update_actions ($self); if (DEBUG) { print "SymbolHistory forward to $symbol $symlist\n"; } $self->goto ($symbol, $symlist); return ($self->{'current_symbol'}, $self->{'current_symlist'}); } # set the 'forward-action' and 'back-action' objects sensitive or not # according to there being something to go forward or back to sub _update_actions { my ($self) = @_; if (my $action = $self->{'forward_action'}) { my $model = $self->{'forward_model'}; $action->set_sensitive ($model->get_iter_first); } if (my $action = $self->{'back_action'}) { my $model = $self->{'back_model'}; $action->set_sensitive ($model->get_iter_first); } } # enforce MAX_HISTORY on the given liststore model # if it's too big then remove elements from the end sub _limit { my ($model) = @_; my $len = $model->iter_n_children (undef); for (my $pos = $len - 1; $pos >= MAX_HISTORY; $pos--) { $model->remove ($model->iter_nth_child (undef, $pos)); } } # 'delete-symbol' notify handler sub _do_delete_symbol { my ($self, $symbol) = @_; foreach my $model ($self->{'back_model'}, $self->{'forward_model'}) { require Gtk2::Ex::TreeModelBits; Gtk2::Ex::TreeModelBits::remove_matching_rows ($model, sub { my ($model, $iter) = @_; return ($model->get_value ($iter, COL_SYMBOL) eq $symbol); }); } } #------------------------------------------------------------------------------ # menu sub back_menu { my ($self) = @_; return _make_menu ($self, 'back_menu', 'back_model', __('Chart: Back')); } sub forward_menu { my ($self) = @_; return _make_menu ($self, 'forward_menu', 'forward_model', __('Chart: Forward')); } sub _make_menu { my ($self, $menu_key, $model_key, $tearoff_title) = @_; return ($self->{$menu_key} ||= do { require Gtk2::Ex::MenuView; require Gtk2::Ex::Dashes::MenuItem; my $menuview = Gtk2::Ex::MenuView->new (model => $self->{$model_key}, # tearoff_items => 'top', # tearoff_title => $tearoff_title, # tearoff_type => 'Gtk2::Ex::Dashes::MenuItem', ); $menuview->signal_connect (item_create_or_update => \&_do_item_create_or_update); $menuview->signal_connect (activate => \&_do_menu_activate, App::Chart::Glib::Ex::MoreUtils::ref_weak($self)); # $menuview->signal_connect # (tearoff => \&_do_menu_tearoff, App::Chart::Glib::Ex::MoreUtils::ref_weak($self)); $menuview; }); } # MenuView 'tearoff' # sub _do_menu_tearoff { # my ($menuview, $ref_weak_self) = @_; # my $self = $$ref_weak_self || return; # require App::Chart::BrowseHistoryDialog; # my $dialog = App::Chart::BrowseHistoryDialog->new # (back_model => $self->{'back_model'}, # forward_model => $self->{'forward_model'}); # $dialog->present; # } # MenuView 'item-create-or-update' sub _do_item_create_or_update { my ($menuview, $item, $model, $path, $iter) = @_; $item ||= Gtk2::MenuItem->new_with_label (''); $item->show; my $symbol = $model->get_value ($iter, COL_SYMBOL); require App::Chart::Database; my $name = App::Chart::Database->symbol_name ($symbol); $item->get_child->set_text ($symbol . ($name ? ' - ' . $name : '')); return $item; } sub _do_menu_activate { my ($menuview, $model, $path, $iter, $ref_weak_self) = @_; my $self = $$ref_weak_self || return; my ($pos) = $path->get_indices; if ($model == $self->{'back_model'}) { foreach (0 .. $pos) { $self->back; } } else { foreach (0 .. $pos) { $self->forward; } } $self->signal_emit ('menu_activate', $self->{'current_symbol'}, $self->{'current_symlist'}) } 1; __END__ =for stopwords symlist undef =head1 NAME App::Chart::SymbolHistory -- previously visited symbols =head1 SYNOPSIS use App::Chart::SymbolHistory; my $history = App::Chart::SymbolHistory->new; =head1 OBJECT HIERARCHY C is a subclass of C. Glib::Object App::Chart::SymbolHistory =head1 DESCRIPTION A C object records a history of visited symbol and symlist, allowing navigation back or forward in the list. =head1 FUNCTIONS =over 4 =item C<< App::Chart::SymbolHistory->new >> Create and return a new symbol history object. =cut =item C<< $history->goto ($symbol, $symlist) >> Add symbol+symlist to C<$history> as the currently viewed position. If this is different than previously noted then that previous symbol+symlist is added to the "back" list. =item C<< $history->back() >> =item C<< $history->forward() >> Go back or forward in C<$history>. The return is values C<($symbol, $symlist)> which is where to go to, or C<(undef,undef)> if nothing further to go to. =item C<< $history->back_menu >> =item C<< $history->forward_menu >> Return a C of symbols to go back or forward to. =back =head1 PROPERTIES =over 4 =item C (C, default undef) =item C (C, default undef) Action objects to be set sensitive or insensitive according to whether there's anything to go back or forward to. =back =head1 SIGNALS =over 4 =item C (parameters: history, symbol, symlist) Emitted when an item symbol+symlist is selected from the back or forward menus (as created by the C etc functions above). =back =head1 SEE ALSO L =cut