| dotReader documentation | Contained in the dotReader distribution. |
dtRdr::GUI::Wx::SearchPane - the search pane
my $search = dtRdr::GUI::Wx::SearchPane->new($parent, blah blah);
$self->__create_children;
$self->__do_layout;
$self->__do_properties;
$pane->init($frame);
$self->search;
$self->search_toc($regexp, $book);
$self->search_book($regexp, $book);
$self->hide_message;
$self->flash_message("Nothing Found", 3);
$self->enable_search($bool);
$self->stop_search;
Hides the options pane unless the sticky is set.
$self->hide_options;
$self->_show_options(1|0);
$self->set_options_sticky(1|0);
Returns the key string corresponding to the type_chooser selection.
$self->get_type_choice;
$self->get_search_choice;
$self->_make_found_context($book, $selection);
$pane->goto_item($node, $selection);
Eric Wilhelm <ewilhelm at cpan dot org>
http://scratchcomputing.com/
Copyright (C) 2006 Eric L. Wilhelm and OSoft, All Rights Reserved.
Absolutely, positively NO WARRANTY, neither express or implied, is offered with this software. You use this software at your own risk. In case of loss, no person or entity owes you anything whatsoever. You have been warned.
The dotReader(TM) is OSI Certified Open Source Software licensed under the GNU General Public License (GPL) Version 2, June 1991. Non-encrypted and encrypted packages are usable in connection with the dotReader(TM). The ability to create, edit, or otherwise modify content of such encrypted packages is self-contained within the packages, and NOT provided by the dotReader(TM), and is addressed in a separate commercial license.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
| dotReader documentation | Contained in the dotReader distribution. |
package dtRdr::GUI::Wx::SearchPane; $VERSION = eval{require version}?version::qv($_):$_ for(0.10.1); use warnings; use strict; use Carp; use Wx qw( wxVERTICAL wxHORIZONTAL wxEXPAND wxRIGHT wxADJUST_MINSIZE wxTE_PROCESS_ENTER wxSUNKEN_BORDER ); use base 'Wx::Panel'; use MultiTask::Minion; use Wx::Event; use dtRdr::Logger; use dtRdr::Search::Book; use dtRdr::GUI::Wx::SearchTree; use dtRdr::GUI::Wx::Utils qw(_accel); use dtRdr::Annotation::Range; sub WxPerl::make_style (@) { my (@styles) = @_; my $style = 0; foreach my $item (@styles) { my $method = 'wx' . $item; Wx->can($method) or croak("no style $method"); $style |= Wx->$method; } return($style); } use Class::Accessor::Classy; ro qw( main_frame bv_manager minion text_ctrl go_button stop_button message tree_ctrl sizer hsizer_main opt_sizer opt_sub_sizer type_chooser search_chooser check_case lib_button sticky_button stucky_button hide_opts_button show_opts_button ); rw 'timer'; rw 'options_sticky'; no Class::Accessor::Classy;
sub new { my $class = shift; my ($parent, @args) = @_; my $self = $class->SUPER::new($parent, @args); $self->__create_children; $self->__do_properties; $self->__do_layout; return($self); } # end subroutine new definition ########################################################################
sub __create_children { my $self = shift; $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{hsizer_main} = Wx::BoxSizer->new(wxHORIZONTAL); $self->{opt_sizer} = Wx::FlexGridSizer->new(2,2,0,0); $self->{opt_sub_sizer} = Wx::BoxSizer->new(wxHORIZONTAL); my @PS = (Wx::wxDefaultPosition(), Wx::wxDefaultSize()); $self->{text_ctrl} = Wx::TextCtrl->new($self, -1, "", @PS, wxTE_PROCESS_ENTER ); # some bitmaps my %bmp = qw( go std_button_search stop std_button_stop stick std_button_sticky stuck std_button_stuck hide ctrl_arrow_5_up show ctrl_arrow_5_down ); $_ = dtRdr::GUI::Wx::Utils->Bitmap($_) for(values(%bmp)); { my @choices = ( ['titles', 'T.O.C.' ], ['text', 'Full Text'], ); $self->{_type_choices} = [map({$_->[0]} @choices)]; my $chooser = $self->{type_chooser} = Wx::Choice->new($self, -1, @PS, [map({$_->[1]} @choices)]); $chooser->SetSelection(0); # OSX nit # XXX tooltips on choicers might be too cluttered 1 and $chooser->SetToolTipString( "* Titles and Table of Contents\n" . "* Entire text of book" ); } { my @choices = ( ['phrase', 'Phrase'], ['word', 'Word(s)'], ['regexp', 'RegExp'] ); $self->{_search_choices} = [map({$_->[0]} @choices)]; my $chooser = $self->{search_chooser} = Wx::Choice->new($self, -1, @PS, [map({$_->[1]} @choices)]); $chooser->SetSelection(0); # OSX nit 1 and $chooser->SetToolTipString( "* Free-form search\n" . "* Whole words only\n" . "* Regular expression" ); } ($self->{go_button} = Wx::BitmapButton->new($self, -1, $bmp{go}) )->SetToolTipString('Go'); ($self->{stop_button} = Wx::BitmapButton->new($self, -1, $bmp{stop}) )->SetToolTipString('Stop'); ($self->{lib_button} = Wx::Button->new($self, -1, 'Library') )->SetToolTipString('Select extra books to search'); ($self->{sticky_button} = Wx::BitmapButton->new($self, -1, $bmp{stick}) )->SetToolTipString('Keep options open'); $self->{stucky_button} = Wx::BitmapButton->new($self, -1, $bmp{stuck}); $self->{check_case} = Wx::CheckBox->new($self, -1, "Case"); ($self->{hide_opts_button} = Wx::BitmapButton->new($self, -1, $bmp{hide}) )->SetToolTipString('Hide search options'); ($self->{show_opts_button} = Wx::BitmapButton->new($self, -1, $bmp{show}) )->SetToolTipString('Options'); $self->{message} = Wx::StaticText->new($self, -1, 'no message here', @PS); $self->{tree_ctrl} = dtRdr::GUI::Wx::SearchTree->new($self, -1, @PS, WxPerl::make_style qw( TR_HAS_BUTTONS TR_NO_LINES TR_LINES_AT_ROOT TR_DEFAULT_STYLE SUNKEN_BORDER ), # 'TR_HIDE_ROOT' ); } # end subroutine __create_children definition ########################################################################
sub __do_layout { my $self = shift; $self->SetAutoLayout(1); my $sizer = $self->sizer; my $hsizer_main = $self->hsizer_main; my $opt_sizer = $self->opt_sizer; $self->SetSizer($sizer); #$sizer->Fit($self); # Is glade just wrong? $sizer->SetSizeHints($self); $hsizer_main->Add($self->text_ctrl, 1, wxEXPAND, 0); $hsizer_main->Add($self->go_button, 0, 0, 0); $hsizer_main->Add($self->stop_button, 0, 0, 0); $hsizer_main->Show($self->stop_button, 0); $sizer->Add($hsizer_main, 0, wxEXPAND|wxADJUST_MINSIZE, 0); $sizer->Add($self->show_opts_button, 0, wxEXPAND, 0); $sizer->Show($self->show_opts_button, 0); $opt_sizer->AddGrowableCol(0); $opt_sizer->Add($self->type_chooser, 0, wxEXPAND, 0); # TODO library select dialog $opt_sizer->Add($self->lib_button, 1, 0, 0); $opt_sizer->Add($self->search_chooser, 0, wxEXPAND, 0); { my $sub_sizer = $self->opt_sub_sizer; $sub_sizer->Add($self->check_case, 1, wxEXPAND, 0); $sub_sizer->Add($self->sticky_button, 0, wxEXPAND, 0); $sub_sizer->Add($self->stucky_button, 0, wxEXPAND, 0); $sub_sizer->Show($self->stucky_button, 0); $opt_sizer->Add($sub_sizer, 0, wxEXPAND, 0); } 1 or $opt_sizer->Show($self->lib_button, 0); $sizer->Add($opt_sizer, 0, wxEXPAND, 0); 1 or $sizer->Show($opt_sizer, 0); $sizer->Add($self->hide_opts_button, 0, wxEXPAND, 0); $sizer->Add($self->message, 0, 0, 0); $sizer->Show($self->message, 0); 1 or $sizer->Show(1, 0); # hides it $sizer->Add($self->tree_ctrl, 1, wxEXPAND, 0); $self->set_options_sticky(0); $self->_show_options(1); } # end subroutine __do_layout definition ########################################################################
sub __do_properties { my $self = shift; $self->tree_ctrl->SetBackgroundColour(Wx::Colour->new(244, 245, 255)); } # end subroutine __do_properties definition ########################################################################
sub init { my $self = shift; my ($frame) = @_; $self->{main_frame} = $frame; my @attributes = qw( bv_manager ); foreach my $attrib (@attributes) { my $l_attrib = $attrib; $self->{$l_attrib} = $frame->$attrib; } my $starter = sub {$_[1]->Skip; $self->search}; my $stopper = sub {$_[1]->Skip; $self->stop_search}; # keys Wx::Event::EVT_TEXT_ENTER($self, $self->text_ctrl, $starter); $self->SetAcceleratorTable( Wx::AcceleratorTable->new( $self->_accel('ESCAPE', $stopper)) ); # buttons Wx::Event::EVT_BUTTON($self->go_button, -1, $starter); Wx::Event::EVT_BUTTON($self->stop_button, -1, $stopper); { # expando my $show = sub {$_[1]->Skip; $self->_show_options(1)}; my $hide = sub {$_[1]->Skip; $self->_show_options(0)}; Wx::Event::EVT_BUTTON($self->show_opts_button, -1, $show); Wx::Event::EVT_BUTTON($self->hide_opts_button, -1, $hide); } { # sticky my $stick = sub {$_[1]->Skip; $self->set_options_sticky(1)}; my $stuck = sub {$_[1]->Skip; $self->set_options_sticky(0)}; Wx::Event::EVT_BUTTON($self->sticky_button, -1, $stick); Wx::Event::EVT_BUTTON($self->stucky_button, -1, $stuck); } # pretend we're the main_frame $self->tree_ctrl->init($self); # disable this for now $self->lib_button->Enable(0); } # end subroutine init definition ########################################################################
sub search { my $self = shift; WARN("search"); if($self->minion) { delete($self->{minion})->quit; die "search in bad state"; } my $bvm = $self->bv_manager; my $bv = $bvm->book_view or return; my $book = $bv->book; $book or die "no book?"; $book->drop_selections; my $find = $self->text_ctrl->GetValue; length($find) or return; L->debug("search for $find"); my $type = $self->get_type_choice; { # setup the regexp my $search = $self->get_search_choice; my $mod = $self->check_case->IsChecked ? '#' : 'i'; if($search eq 'regexp') { $find = qr/(?$mod)$find/ } elsif($search eq 'word') { $find = qr/(?$mod)\b\Q$find\E\b/; } elsif($search eq 'phrase') { $find = qr/(?$mod)\Q$find\E/; } } L->debug("regexp compiled to $find"); $self->hide_message; $self->hide_options; $self->enable_search(0); my $tree = $self->tree_ctrl; $tree->DeleteAllItems; $self->{_hits} = {}; if($type eq 'text') { # TODO other types $self->search_book($find, $book); } elsif($type eq 'titles') { $self->search_toc($find, $book); } else { die "'$type' type searches are not supported"; } } # end subroutine search definition ########################################################################
sub search_toc { my $self = shift; my ($find, $book) = @_; # XXX bah -- lots of this is a copy of search_book, but I don't see a # good refactor right now. my $tree = $self->tree_ctrl; $tree->book_root($book); my @nodes = $book->visible_nodes; # that's about it for this # focus the tree on the first hit my $first_hit; $first_hit = sub { undef($first_hit); $tree->SetFocus; $tree->SelectItem($_[0]); }; require Time::HiRes; my $start_time = Time::HiRes::time(); my $hits = 0; my $minion = MultiTask::Minion->make(sub { my $m = shift; return( work => sub { my $node = shift(@nodes); unless($node) { # done $m->finish; L->debug('search done'); return; } my $title = $node->title; #WARN("search ", $title); $title =~ m/$find/ or return; #WARN("hit"); $hits++; my $item = $tree->want_item($node); $tree->SetItemBold($item); $first_hit and $first_hit->($item); }, finish => sub { L->debug("running search finish"); $m->quit; # TODO regen the current page if it is for this book and has hits? unless($hits) { # XXX per $book # XXX $self->nothing_found($book) ? $self->flash_message("Nothing found"); $tree->DeleteAllItems; # XXX $tree->delete_book_items($book); $self->text_ctrl->SetFocus; # XXX iff the focus is on searchbox? } else { my $time = sprintf('%0.1f', Time::HiRes::time() - $start_time); $self->flash_message('Done (' . $hits . ' hits/' . $time . 's)'); } }, quit => sub { $m->SUPER_quit; delete($self->{minion}); $self->enable_search(1); }, ); }); # end make minion $self->{minion} = $minion; $self->main_frame->taskmaster->add($minion); $self->flash_message('Searching...'); } # end subroutine search_toc definition ########################################################################
sub search_book { my $self = shift; my ($find, $book) = @_; my $tree = $self->tree_ctrl; $tree->book_root($book); my $searcher = dtRdr::Search::Book->new( find => $find, book => $book ); # focus the tree on the first hit my $first_hit; $first_hit = sub { undef($first_hit); $tree->SetFocus; $tree->SelectItem($_[0]); }; require Time::HiRes; my $start_time = Time::HiRes::time(); my $hits = 0; my $minion = MultiTask::Minion->make(sub { my $m = shift; return( work => sub { my $result = $searcher->next; unless($result) { # searcher done $m->finish; L->debug('search done'); return; } #WARN("search"); $result->null and return; $hits++; #WARN("hit"); my $node = $result->start_node; my $item = $tree->want_item($node); my $aselect = dtRdr::AnnoSelection->claim($result->selection); my $hitcount; { $self->{_hits}{$node} ||= []; $hitcount = push(@{$self->{_hits}{$node}}, $aselect); # TODO abstract that # plus display "+$childhits" ? (bit trickier) } $tree->SetItemText($item, "($hitcount) " . $node->title); $tree->SetItemBold($item); $first_hit and $first_hit->($item); $book->add_selection($aselect); }, finish => sub { L->debug("running search finish"); $m->quit; # TODO regen the current page if it is for this book and has hits? unless(%{$self->{_hits}}) { # XXX per $book $self->flash_message("Nothing found"); $tree->DeleteAllItems; # XXX $tree->delete_book_items($book); $self->text_ctrl->SetFocus; # XXX iff the focus is on searchbox? } else { my $time = sprintf('%0.1f', Time::HiRes::time() - $start_time); $self->flash_message('Done (' . $hits . ' hits/' . $time . 's)'); } }, quit => sub { $m->SUPER_quit; delete($self->{minion}); $self->enable_search(1); }, ); }); # end make minion $self->{minion} = $minion; $self->main_frame->taskmaster->add($minion); $self->flash_message('Searching...'); } # end subroutine search_book definition ########################################################################
sub hide_message { my $self = shift; $self->sizer->Show($self->message, 0); $self->sizer->Layout; } # end subroutine hide_message definition ########################################################################
sub flash_message { my $self = shift; my ($message, $timeout) = @_; if(my $timer = $self->timer) { $timer->Stop; } $self->message->SetLabel(''); $self->sizer->Show($self->message, 1); $self->sizer->Layout; $self->message->SetLabel($message); $self->set_timer( my $timer = Wx::Timer->new($self) ); if(defined($timeout)) { Wx::Event::EVT_TIMER($self, -1, sub { $timer->Stop; $self->Disconnect($timer, -1, Wx::wxEVT_TIMER()); $self->hide_message; $self->set_timer(undef); }); $timer->Start($timeout * 1000); } } # end subroutine flash_message definition ########################################################################
sub enable_search { my $self = shift; my ($bool) = @_; $self->text_ctrl->Enable($bool); # enable/disable/hide the buttons my $go = $self->go_button; my $stop = $self->stop_button; $go->Enable($bool); $stop->Enable(! $bool); my $hsizer_main = $self->hsizer_main; $hsizer_main->Show(($bool ? $stop : $go), 0); $hsizer_main->Show(($bool ? $go : $stop), 1); $hsizer_main->Layout; } # end subroutine enable_search definition ########################################################################
sub stop_search { my $self = shift; WARN "stop"; if(my $minion = $self->minion) { $minion->quit; } $self->flash_message("Search cancelled", 1); $self->_show_options(1); $self->text_ctrl->SetFocus; } # end subroutine stop_search definition ########################################################################
sub hide_options { my $self = shift; $self->options_sticky and return; $self->_show_options(0); } # end subroutine hide_options definition ########################################################################
sub _show_options { my $self = shift; my ($bool) = @_; my $show = $self->show_opts_button; my $hide = $self->hide_opts_button; my $sizer = $self->sizer; $sizer->Show(($bool ? $show : $hide), 0); $sizer->Show(($bool ? $hide : $show), 1); $sizer->Show($self->opt_sizer, $bool); # XXX sadly, that pokes our subsizer for some reason $self->set_options_sticky($self->options_sticky); $sizer->Layout; $self->opt_sizer->Layout; if($bool) { $self->type_chooser->SetFocus; } else { $self->text_ctrl->SetFocus; } } # end subroutine _show_options definition ########################################################################
sub set_options_sticky { my $self = shift; my ($bool) = @_; my $stick = $self->sticky_button; my $stuck = $self->stucky_button; my $sizer = $self->opt_sub_sizer; $sizer->Show(($bool ? $stick : $stuck), 0); $sizer->Show(($bool ? $stuck : $stick), 1); $sizer->Layout; $self->SUPER::set_options_sticky($bool); } # end subroutine set_options_sticky definition ########################################################################
sub get_type_choice { my $self = shift; my $i = $self->type_chooser->GetSelection; return($self->{_type_choices}[$i]); } # end subroutine get_type_choice definition ########################################################################
sub get_search_choice { my $self = shift; my $i = $self->search_chooser->GetSelection; return($self->{_search_choices}[$i]); } # end subroutine get_search_choice definition ########################################################################
sub _make_found_context { my $self = shift; my ($book, $selection) = @_; # XXX this is too big when the selection is big my $snode = $selection->node; my $nc = $book->get_NC($snode); my ($s, $e) = ($selection->a, $selection->b); WARN("got context $s, $e"); my $len = 10; $s -= $len; $e += $len; $s = 0 if($s < 0); my $span = $snode->word_end - $snode->word_start; $e = $span if($e > $span); WARN("got context $s, $e"); my $context = substr($nc, $s, $e - $s); WARN("got context $context"); return($context); } # end subroutine _make_found_context definition ########################################################################
sub show_context_menu { my $self = shift; my ($node, $point) = (@_); my @selections; my $book = $node->book; # TODO skip-out if this was a TOC search? foreach my $hnode ($node, $book->descendant_nodes($node)) { my $hits = $self->{_hits}{$hnode}; next unless($hits and ref($hits)); push(@selections, @$hits); } # node with no hits in visible children @selections or return; my $menu = Wx::Menu->new; foreach my $hit (@selections) { my $string = $self->_make_found_context($book, $hit); $string =~ s/\t/ /g; # just in case $string =~ s/&/&&/g; my $item = $menu->Append(Wx::NewId(), $string); Wx::Event::EVT_MENU($menu, $item, sub { $_[1]->Skip; $self->goto_item($node, $hit); }); } $self->PopupMenu($menu, $point); } # end subroutine show_context_menu definition ########################################################################
sub goto_item { my $self = shift; my ($node, $selection) = @_; # TODO insert selections in book # TODO set the current hit somewhere in self for prev/next buttons my $bv = $self->bv_manager->book_view; $bv->render_node_by_id($node->id); # scroll if($selection) { $bv->jump_to($selection); } } # end subroutine goto_item definition ########################################################################
# vi:ts=2:sw=2:et:sta 1;