/usr/local/CPAN/Padre/Padre/Wx/Dialog/FindFast.pm


package Padre::Wx::Dialog::FindFast;

# Incremental search

use 5.008;
use strict;
use warnings;
use Padre::Wx       ();
use Padre::Wx::Icon ();

our $VERSION = '0.86';





######################################################################
# Constructor

sub new {
	my $class = shift;
	my $self  = bless {
		restart          => 1,
		backward         => 0,
		default_bgcolour => Wx::Colour->new('#ffffff'),
		error_bgcolour   => Wx::Colour->new('#ffaaaa'),
	}, $class;
	return $self;
}





######################################################################
# Main Methods

#
# search($direction);
#
# initiate/continue searching in $direction.
#
sub search {
	my $self      = shift;
	my $direction = shift;

	# Avoid crash if no file open
	return if not Padre->ide->wx->main->current->editor;

	$self->{backward} = $direction eq 'previous';
	unless ( $self->{panel} ) {
		$self->_create_panel;
	}

	# pane != panel
	my $pane = Padre->ide->wx->main->aui->GetPane('find');
	if ( $pane->IsShown ) {
		$self->_find;
	} else {
		$self->_show_panel;
	}
}

# -- Private methods

sub _find {
	my $self = shift;
	my $main = Padre->ide->wx->main;
	my $page = $main->current->editor or return;
	my $last = $page->GetLength;
	my $text = $page->GetTextRange( 0, $last );

	# build regex depending on what we search for
	my $what;
	if ( $self->{regex}->GetValue ) {

		# regex search, let's validate regex
		$what = $self->{entry}->GetValue;
		eval {qr/$what/};
		if ($@) {

			# regex is invalid
			$self->{entry}->SetBackgroundColour( $self->{error_bgcolour} );
			return;

		} else {

			# regex is valid
			$self->{entry}->SetBackgroundColour( $self->{default_bgcolour} );
		}

	} else {

		# plain text search
		$what = quotemeta $self->{entry}->GetValue;
	}

	my $regex = $self->{case}->GetValue ? qr/($what)/im : qr/($what)/m;

	my ( $from, $to ) =
		$self->{restart}
		? ( 0, $last )
		: $page->GetSelection;
	$self->{restart} = 0;

	# search and highlight
	my ( $start, $end, @matches ) = Padre::Util::get_matches( $text, $regex, $from, $to, $self->{backward} );
	if ( defined $start ) {
		$page->SetSelection( $start, $end );
		$self->{entry}->SetBackgroundColour( $self->{default_bgcolour} );
	} else {
		$self->{entry}->SetBackgroundColour( $self->{error_bgcolour} );
	}
	return;
}

# -- GUI related subs

#
# _create_panel();
#
# create find panel in aui manager.
#
sub _create_panel {
	my $self = shift;
	my $main = Padre->ide->wx->main;

	# The panel and the boxsizer to place controls
	$self->{outer} = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
	$self->{panel} = Wx::Panel->new( $main, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize );
	$self->{hbox}  = Wx::BoxSizer->new(Wx::wxHORIZONTAL);

	# Close button
	$self->{close} = Wx::BitmapButton->new(
		$self->{panel}, -1,
		Padre::Wx::Icon::find('actions/x-document-close'),
		Wx::Point->new( -1, -1 ),
		Wx::Size->new( -1, -1 ),
		Wx::wxBORDER_NONE,
	);
	Wx::Event::EVT_BUTTON( $main, $self->{close}, sub { $self->_hide_panel } );

	# Search area
	$self->{label} = Wx::StaticText->new( $self->{panel}, -1, Wx::gettext('Find:') );
	$self->{entry} = Wx::TextCtrl->new( $self->{panel}, -1, '' );
	$self->{entry}->SetMinSize( Wx::Size->new( 25 * $self->{entry}->GetCharWidth, -1 ) );
	Wx::Event::EVT_CHAR( $self->{entry}, sub { $self->_on_key_pressed( $_[1] ) } );
	Wx::Event::EVT_TEXT( $main, $self->{entry}, sub { $self->_on_entry_changed( $_[1] ) } );

	# Previous button
	$self->{previous} = Wx::BitmapButton->new(
		$self->{panel}, -1,
		Padre::Wx::Icon::find('actions/go-previous'),
		Wx::Point->new( -1, -1 ),
		Wx::Size->new( -1, -1 ),
		Wx::wxBORDER_NONE
	);

	#$self->{previous}->SetLabel("Next"); # TO DO: should be better but does not work
	Wx::Event::EVT_BUTTON( $main, $self->{previous}, sub { $self->search('previous') } );
	$self->{previous_text} = Wx::Button->new(
		$self->{panel}, -1,
		Wx::gettext('Previ&ous'),
		Wx::Point->new( -1, -1 ),
		Wx::Size->new( -1, -1 ),
		Wx::wxBORDER_NONE,
	);
	Wx::Event::EVT_BUTTON( $main, $self->{previous_text}, sub { $self->search('previous') } );

	# Previous button
	$self->{next} = Wx::BitmapButton->new(
		$self->{panel}, -1,
		Padre::Wx::Icon::find('actions/go-next'),
		Wx::Point->new( -1, -1 ),
		Wx::Size->new( -1, -1 ),
		Wx::wxBORDER_NONE,
	);
	Wx::Event::EVT_BUTTON( $main, $self->{next}, sub { $self->search('next') } );
	$self->{next_text} = Wx::Button->new(
		$self->{panel}, -1,
		Wx::gettext('&Next'),
		Wx::Point->new( -1, -1 ),
		Wx::Size->new( -1, -1 ),
		Wx::wxBORDER_NONE,
	);
	Wx::Event::EVT_BUTTON( $main, $self->{next_text}, sub { $self->search('next') } );

	# Case sensitivity
	$self->{case} = Wx::CheckBox->new( $self->{panel}, -1, Wx::gettext('Case &insensitive') );
	Wx::Event::EVT_CHECKBOX( $main, $self->{case}, sub { $self->_on_case_checked } );

	# Regex search
	$self->{regex} = Wx::CheckBox->new( $self->{panel}, -1, Wx::gettext('Use rege&x') );
	Wx::Event::EVT_CHECKBOX( $main, $self->{regex}, sub { $self->_on_regex_checked } );

	# Place all controls
	$self->{hbox}->Add( $self->{close},         0, Wx::wxALIGN_CENTER_VERTICAL,              0 );
	$self->{hbox}->Add( $self->{label},         0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxLEFT, 10 );
	$self->{hbox}->Add( $self->{entry},         0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( $self->{previous},      0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( $self->{previous_text}, 0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( $self->{next},          0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( $self->{next_text},     0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( $self->{case},          0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( $self->{regex},         0, Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,  5 );
	$self->{hbox}->Add( 0,                      1, Wx::wxEXPAND,                             5 );

	$self->{panel}->SetSizer( $self->{hbox} );
	$self->{panel}->Layout;
	$self->{hbox}->Fit( $self->{panel} );

	$self->{outer}->Add( $self->{panel}, 1, Wx::wxALIGN_LEFT | Wx::wxALL | Wx::wxEXPAND, 5 );

	my $width  = $main->current->editor->GetSize->GetWidth;
	my $height = $self->{panel}->GetSize->GetHeight;
	my $size   = Wx::Size->new( $width, $height );
	$self->{panel}->SetSize($size);

	# manage the pane in aui
	$main->aui->AddPane(
		$self->{panel},
		Padre::Wx->aui_pane_info(
			Name           => 'find',
			CaptionVisible => 0,
			Layer          => 1,
			PaneBorder     => 0,
			)->Bottom->Fixed->Hide,
	);

	return 1;
}

sub _hide_panel {
	my $self = shift;

	# pane != panel
	my $auimngr = Padre->ide->wx->main->aui;
	$auimngr->GetPane('find')->Hide;
	$auimngr->Update;

	$self->{visible} = 0;

	Padre->ide->wx->main->current->editor->SetFocus();

	return 1;
}

sub _show_panel {
	my $self = shift;

	# Show the panel; pane != panel
	my $auimngr = Padre->ide->wx->main->aui;
	$auimngr->GetPane('find')->Show(1);
	$auimngr->Update;

	# Update checkboxes with config values
	# since they might have been updated by find dialog
	my $config = Padre->ide->config;
	$self->{case}->SetValue( $config->find_case   ? 0 : 1 );
	$self->{regex}->SetValue( $config->find_regex ? 0 : 1 );

	# You probably want to use the Find (and with a fresh term)
	$self->{entry}->SetValue('');
	$self->{entry}->SetFocus;

	$self->{visible} = 1;

	return 1;
}

sub visible {
	my $self = shift;
	return $self->{visible} || 0;
}


# -- Event handlers

#
# _on_case_checked()
#
# called when the "case insensitive" checkbox has changed value. in that
# case, we'll restart searching from the start of the document.
#
sub _on_case_checked {
	my $self = shift;
	Padre->ide->config->set(
		'find_case',
		$self->{case}->GetValue ? 0 : 1
	);
	$self->{restart} = 1;
	$self->_find;
	$self->{entry}->SetFocus;
	return;
}

#
# _on_entry_changed()
#
# called when the entry content has changed (keyboard or other mean). in that
# case, we'll start searching from the start of the document.
#
sub _on_entry_changed {
	my $self = shift;
	unless ( $self->{entry}->GetValue eq '' ) {
		$self->{restart} = 1;
		$self->_find;
	}
	return;
}

#
# _on_key_pressed()
#
# called when a key is pressed in the entry. used to trap
#		escape so we abort
#		return = find again
# search, otherwise dispatch event up-stack.
#
sub _on_key_pressed {
	my $self  = shift;
	my $event = shift;
	my $mod   = $event->GetModifiers || 0;
	my $code  = $event->GetKeyCode;

	# Remove the bit ( Wx::wxMOD_META) set by Num Lock being pressed on Linux
	$mod = $mod & ( Wx::wxMOD_ALT + Wx::wxMOD_CMD + Wx::wxMOD_SHIFT );

	if ( $code == Wx::WXK_ESCAPE ) {
		$self->_hide_panel;
		return;
	}
	if ( $code == Wx::WXK_RETURN ) {
		$self->_find;
		return;
	}

	$event->Skip(1);
}

#
# _on_regex_checked()
#
# called when the "use regex" checkbox has changed value. in that case,
# we'll restart searching from the start of the document.
#
sub _on_regex_checked {
	my $self = shift;
	Padre->ide->config->set(
		'find_regex',
		$self->{regex}->GetValue ? 0 : 1,
	);
	$self->{restart} = 1;
	$self->_find;
	$self->{entry}->SetFocus;
	return;
}

1;

# Copyright 2008-2011 The Padre development team as listed in Padre.pm.
# LICENSE
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl 5 itself.