Padre::Wx::Dialog::Replace - Find and Replace Widget


Padre documentation Contained in the Padre distribution.

Index


Code Index:

NAME

Top

Padre::Wx::Dialog::Replace - Find and Replace Widget

DESCRIPTION

Top

Padre::Wx:Main implements Padre's Find and Replace dialog box.

METHODS

Top

new

  my $find = Padre::Wx::Dialog::Replace->new($main);
Create and return a C<Padre::Wx::Dialog::Replace> search and replace widget.

find

  $self->find

Grab currently selected text, if any, and place it in find combo box. Bring up the dialog or perform search for string's next occurrence if dialog is already displayed.

TO DO: if selection is more than one line then consider it as the limit of the search and not as the string to be used.

find_button

  $self->find_button

Executed when Find button is clicked.

Performs search on the term specified in the dialog.

close

  $self->close

Hide dialog.

replace_button

  $self->replace_button;

Executed when the Replace button is clicked.

Replaces one appearance of the Find Text with the Replace Text.

If search window is still open, run search on the whole text, again.

replace_all

  $self->replace_all;

Executed when Replace All button is clicked.

Replace all appearances of given string in the current document.

Integration with Padre::Search. Generates a search instance for the currently configured information in the Find dialog.

Returns a Padre::Search object, or undef if current state of the dialog does not result in a valid search.

COPYRIGHT & LICENSE

Top


Padre documentation Contained in the Padre distribution.
package Padre::Wx::Dialog::Replace;

use 5.008;
use strict;
use warnings;
use Params::Util qw{_STRING};
use Padre::DB                    ();
use Padre::Wx                    ();
use Padre::Wx::Role::Main        ();
use Padre::Wx::History::ComboBox ();
our $VERSION = '0.86';
our @ISA     = qw{
	Padre::Wx::Role::Main
	Wx::Dialog
};

sub new {
	my $class = shift;
	my $main  = shift;

	# Create the Wx dialog
	my $self = $class->SUPER::new(
		$main,
		-1,
		Wx::gettext('Find and Replace'),
		Wx::wxDefaultPosition,
		Wx::wxDefaultSize,
		Wx::wxCAPTION | Wx::wxCLOSE_BOX | Wx::wxSYSTEM_MENU | Wx::wxRESIZE_BORDER
	);

	# The text to search for
	$self->{find_text} = Padre::Wx::History::ComboBox->new(
		$self,
		-1,
		'',
		Wx::wxDefaultPosition,
		Wx::wxDefaultSize,
		['search'],
	);

	# The text to replace with
	$self->{replace_text} = Padre::Wx::History::ComboBox->new(
		$self,
		-1,
		'',
		Wx::wxDefaultPosition,
		Wx::wxDefaultSize,
		['replace'],
	);

	# "Case Sensitive" option
	$self->{find_case} = Wx::CheckBox->new(
		$self,
		-1,
		Wx::gettext('Case &sensitive'),
	);
	Wx::Event::EVT_CHECKBOX(
		$self,
		$self->{find_case},
		sub {
			$_[0]->{find_text}->SetFocus;
		}
	);

	# "Find as Regex" option
	$self->{find_regex} = Wx::CheckBox->new(
		$self,
		-1,
		Wx::gettext('Regular &Expression'),
	);
	Wx::Event::EVT_CHECKBOX(
		$self,
		$self->{find_regex},
		sub {
			$_[0]->{find_text}->SetFocus;
		}
	);

	# "Find First and Close" option
	$self->{find_first} = Wx::CheckBox->new(
		$self,
		-1,
		Wx::gettext('Close Window on &Hit'),
	);
	Wx::Event::EVT_CHECKBOX(
		$self,
		$self->{find_first},
		sub {
			$_[0]->{find_text}->SetFocus;
		}
	);

	# "Find in Reverse" option
	$self->{find_reverse} = Wx::CheckBox->new(
		$self,
		-1,
		Wx::gettext('Search &Backwards'),
	);
	Wx::Event::EVT_CHECKBOX(
		$self,
		$self->{find_reverse},
		sub {
			$_[0]->{find_text}->SetFocus;
		}
	);

	# The "Replace All" option
	$self->{replace_all} = Wx::CheckBox->new(
		$self,
		-1,
		Wx::gettext('Replace &All'),
	);
	Wx::Event::EVT_CHECKBOX(
		$self,
		$self->{replace_all},
		sub {
			$_[0]->{find_text}->SetFocus;
		}
	);

	# The "Find" button
	$self->{find_button} = Wx::Button->new(
		$self,
		Wx::wxID_FIND,
		Wx::gettext('&Find'),
	);
	Wx::Event::EVT_BUTTON(
		$self,
		$self->{find_button},
		sub {
			$_[0]->find_button;
		}
	);
	Wx::Event::EVT_KEY_DOWN(
		$self->{find_button},
		sub {
			$self->hotkey( $_[1], $self->{find_button} );
		}
	);

	# The "Replace" button
	$self->{replace_button} = Wx::Button->new(
		$self,
		Wx::wxID_REPLACE,
		Wx::gettext('&Replace'),
	);
	Wx::Event::EVT_BUTTON(
		$self,
		$self->{replace_button},
		sub {
			$_[0]->replace_button;
		}
	);
	Wx::Event::EVT_KEY_DOWN(
		$self->{replace_button},
		sub {
			$self->hotkey( $_[1], $self->{replace_button} );
		}
	);
	$self->{replace_button}->SetDefault;

	# The "Close" button
	$self->{close_button} = Wx::Button->new(
		$self,
		Wx::wxID_CANCEL,
		Wx::gettext('&Close'),
	);
	Wx::Event::EVT_BUTTON(
		$self,
		$self->{close_button},
		sub {
			$_[0]->close;
		}
	);

	# Tab order
	$self->{find_regex}->MoveAfterInTabOrder( $self->{find_text} );
	$self->{replace_text}->MoveAfterInTabOrder( $self->{find_regex} );
	$self->{find_case}->MoveAfterInTabOrder( $self->{replace_regex} );
	$self->{find_reverse}->MoveAfterInTabOrder( $self->{find_case} );
	$self->{find_first}->MoveAfterInTabOrder( $self->{find_reverse} );
	$self->{replace_all}->MoveAfterInTabOrder( $self->{find_first} );
	$self->{find_button}->MoveAfterInTabOrder( $self->{replace_all} );
	$self->{replace_button}->MoveAfterInTabOrder( $self->{find_button} );
	$self->{close_button}->MoveAfterInTabOrder( $self->{replace_button} );

	# Form Layout
	# Find sizer begins here
	my $find = Wx::StaticBoxSizer->new(
		Wx::StaticBox->new(
			$self,
			-1,
			Wx::gettext('Find'),
		),
		Wx::wxVERTICAL,
	);
	$find->Add(
		Wx::StaticText->new(
			$self,
			Wx::wxID_STATIC,
			Wx::gettext('Find Text:'),
		),
		0,
		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
		5,
	);
	$find->Add(
		$self->{find_text},
		3,
		Wx::wxGROW | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
		5,
	);
	$find->Add(
		$self->{find_regex},
		0,
		Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
		5,
	);

	# Replace sizer begins here
	my $replace = Wx::StaticBoxSizer->new(
		Wx::StaticBox->new(
			$self,
			-1,
			Wx::gettext('Replace'),
		),
		Wx::wxVERTICAL,
	);
	$replace->Add(
		Wx::StaticText->new(
			$self,
			Wx::wxID_STATIC,
			Wx::gettext('Replace Text:'),
		),
		0,
		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
		5,
	);
	$replace->Add(
		$self->{replace_text},
		3,
		Wx::wxGROW | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
		5,
	);

	# The layout grid for the options
	my $grid = Wx::FlexGridSizer->new( 2, 2, 0, 0 );
	$grid->AddGrowableCol(1);
	$grid->Add(
		$self->{find_case},
		0,
		Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
		5,
	);
	$grid->Add(
		$self->{find_reverse},
		0,
		Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
		5,
	);
	$grid->Add(
		$self->{find_first},
		0,
		Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
		5,
	);
	$grid->Add(
		$self->{replace_all},
		0,
		Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
		5,
	);

	# Options sizer begins here
	my $options = Wx::StaticBoxSizer->new(
		Wx::StaticBox->new(
			$self,
			-1,
			Wx::gettext('Options')
		),
		Wx::wxVERTICAL,
	);
	$options->Add(
		$grid,
		2,
		Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
		0,
	);

	# Sizer for the buttons
	my $bottom = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
	$bottom->Add(
		$self->{find_button},
		0,
		Wx::wxGROW | Wx::wxRIGHT,
		5,
	);
	$bottom->Add(
		$self->{replace_button},
		0,
		Wx::wxGROW | Wx::wxLEFT | Wx::wxRIGHT,
		5,
	);
	$bottom->Add(
		$self->{close_button},
		0,
		Wx::wxGROW | Wx::wxLEFT,
		5,
	);

	# Fill the sizer for the overall dialog
	my $sizer = Wx::FlexGridSizer->new( 1, 1, 0, 0 );
	$sizer->AddGrowableCol(0);
	$sizer->Add(
		$find,
		2,
		Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
		5,
	);
	$sizer->Add(
		$replace,
		2,
		Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
		5,
	);
	$sizer->Add(
		$options,
		2,
		Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
		5,
	);
	$sizer->Add(
		$bottom,
		0,
		Wx::wxALIGN_RIGHT | Wx::wxALL,
		5,
	);

	# Let the widgets control the dialog size
	$self->SetSizer($sizer);
	$sizer->SetSizeHints($self);

	# Update the dialog from configuration
	my $config = $self->current->config;
	$self->{find_case}->SetValue( $config->find_case );
	$self->{find_regex}->SetValue( $config->find_regex );
	$self->{find_first}->SetValue( $config->find_first );
	$self->{find_reverse}->SetValue( $config->find_reverse );
	return $self;
}

sub find {
	my $self = shift;
	my $text = $self->current->text;

	# No search if no file is open (TO DO ??)
	return unless $self->current->editor;

	# TO DO: if selection is more than one lines then consider it as the
	# limit of the search and not as the string to be used.
	$text = '' if $text =~ /\n/;

	# Clear out and reset the dialog, then prepare the new find
	$self->{find_text}->refresh;
	$self->{replace_text}->refresh;
	if ( $self->IsShown ) {
		$self->find_button;
	} else {
		if ( length $text ) {

			# Go straight to the replace field
			$self->{find_text}->SetValue($text);
			$self->{replace_text}->SetFocus;
		} else {
			$self->{find_text}->SetFocus;
		}
		$self->Show(1);
	}
	return;
}





######################################################################
# Button Events

sub find_button {
	my $self   = shift;
	my $main   = $self->main;
	my $config = $self->save;

	# Generate the search object
	my $search = $self->as_search;
	unless ($search) {
		$main->error('Not a valid search');

		# Move the focus back to the search text
		# so they can tweak their search.
		$self->{find_text}->SetFocus;
		return;
	}

	# Apply the search to the current editor
	$main->search_next($search);

	# If we're only searching once, we won't need the dialog any more
	if ( $self->{find_first}->GetValue ) {
		$self->Hide;
	}
	return;
}

sub close {
	my $self = shift;
	$self->Hide;

	# Keep any setting changes.
	$self->save;

	# As we leave the Find dialog, return the user to the current editor
	# window so they don't need to click it.
	my $editor = $self->current->editor;
	if ($editor) {
		$editor->SetFocus;
	}
	return;
}

# TO DO: The change to this function that turned it into a dual-purpose function
#       unintentionally transfered responsibility for the implementation of
#       "Replace All" from the main class to a dialog class.
#       This was a mistake, the dialog should not be where this is implemented.
#       Revert this change and restore the independent "Replace All" code, so
#       that the dialog goes back to acting only as controller.
sub replace_button {
	my $self   = shift;
	my $main   = $self->main;
	my $config = $self->save;

	# Generate the search object
	my $search = $self->as_search;
	unless ($search) {
		$main->error('Not a valid search');

		# Move the focus back to the search text
		# so they can tweak their search.
		$self->{find_text}->SetFocus;
		return;
	}

	# If we are replacing everything, hand off to the other method
	if ( $self->{replace_all}->GetValue ) {
		return $self->replace_all;
	}

	# Just replace once
	my $changed = $main->replace_next($search);
	unless ($changed) {
		$main->message(
			sprintf( Wx::gettext('No matches found for "%s".'), $self->{find_text}->GetValue ),
			Wx::gettext('Search and Replace'),
		);
	}

	# Move the focus back to the search text
	# so they can change it if they want.
	$self->{find_text}->SetFocus;
	return;
}

sub replace_all {
	my $self   = shift;
	my $main   = $self->main;
	my $config = $self->save;

	# Generate the search object
	my $search = $self->as_search;
	unless ($search) {
		$main->error('Not a valid search');
		return;
	}

	# Apply the search to the current editor
	my $number_of_changes = $main->replace_all($search);
	if ($number_of_changes) {
		my $message_text =
			$number_of_changes == 1 ? Wx::gettext('Replaced %d match') : Wx::gettext('Replaced %d matches');

		# remark: It would be better to use gettext for plural handling, but wxperl does not seem to support this at the moment.
		$main->info(
			sprintf( $message_text, $number_of_changes ),
			Wx::gettext('Search and Replace')
		);
	} else {
		$main->info(
			sprintf( Wx::gettext('No matches found for "%s".'), $self->{find_text}->GetValue ),
			Wx::gettext('Search and Replace'),
		);
	}

	# Move the focus back to the search text
	# so they can change it if they want.
	$self->{find_text}->SetFocus;
	return;
}





#####################################################################
# Support Methods

sub as_search {
	my $self = shift;
	require Padre::Search;
	Padre::Search->new(
		find_term    => $self->{find_text}->GetValue,
		find_case    => $self->{find_case}->GetValue,
		find_regex   => $self->{find_regex}->GetValue,
		find_reverse => $self->{find_reverse}->GetValue,
		replace_term => $self->{replace_text}->GetValue,
	);
}

# Adds Ultraedit-like hotkeys for quick find/replace triggering
sub hotkey {
	my ( $self, $key_event, $sender ) = @_;

	$self->find_button    if $key_event->GetKeyCode == ord 'F';
	$self->replace_button if $key_event->GetKeyCode == ord 'R';
	$self->close          if $key_event->GetKeyCode == Wx::WXK_ESCAPE;

	if ( $key_event->GetKeyCode == Wx::WXK_TAB ) {
		my $index;
		$index = 1 if $sender->GetId == $self->{find_button}->GetId;
		$index = 2 if $sender->GetId == $self->{replace_button}->GetId;
		$index = 3 if $sender->GetId == $self->{close_button}->GetId;

		if ( $key_event->ShiftDown ) {
			$index--;
		} else {
			$index++;
		}

		my @elements = qw(replace_all find_button replace_button close_button find_regex);
		$self->{ $elements[$index] }->SetFocus;
	}

	return;
}

# Save the dialog settings to configuration.
# Returns the config object as a convenience.
sub save {
	my $self    = shift;
	my $config  = $self->current->config;
	my $changed = 0;
	foreach my $name (qw{ find_case find_regex find_first find_reverse }) {
		my $value = $self->{$name}->GetValue;
		next if $config->$name() == $value;
		$config->set( $name => $value );
		$changed = 1;
	}
	$config->write if $changed;
	return $config;
}

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.