1
package Padre::Wx::Dialog::Replace;
7
Padre::Wx::Dialog::Replace - Find and Replace Widget
11
C<Padre::Wx:Main> implements Padre's Find and Replace dialog box.
20
use Params::Util qw{_STRING};
21
use Padre::Current ();
24
use Padre::Wx::Role::MainChild ();
25
use Padre::Wx::History::ComboBox ();
27
our $VERSION = '0.42';
29
Padre::Wx::Role::MainChild
37
my $find = Padre::Wx::Dialog::Replace->new($main);
39
Create and return a C<Padre::Wx::Dialog::Replace> search and replace widget.
47
die("Did not pass parent to replace dialog constructor");
50
# Create the Wx dialog
51
my $self = $class->SUPER::new(
54
Wx::gettext('Find and Replace'),
55
Wx::wxDefaultPosition,
57
Wx::wxCAPTION | Wx::wxCLOSE_BOX | Wx::wxSYSTEM_MENU | Wx::wxRESIZE_BORDER
60
# The text to search for
61
$self->{find_text} = Padre::Wx::History::ComboBox->new(
65
Wx::wxDefaultPosition,
70
# The text to replace with
71
$self->{replace_text} = Padre::Wx::History::ComboBox->new(
75
Wx::wxDefaultPosition,
80
# "Case Sensitive" option
81
$self->{find_case} = Wx::CheckBox->new(
84
Wx::gettext('Case &Insensitive'),
86
Wx::Event::EVT_CHECKBOX(
90
$_[0]->{find_text}->SetFocus;
94
# "Find as Regex" option
95
$self->{find_regex} = Wx::CheckBox->new(
98
Wx::gettext('&Use Regex'),
100
Wx::Event::EVT_CHECKBOX(
104
$_[0]->{find_text}->SetFocus;
108
# "Find First and Close" option
109
$self->{find_first} = Wx::CheckBox->new(
112
Wx::gettext('Close Window on &hit'),
114
Wx::Event::EVT_CHECKBOX(
118
$_[0]->{find_text}->SetFocus;
122
# "Find in Reverse" option
123
$self->{find_reverse} = Wx::CheckBox->new(
126
Wx::gettext('Search &Backwards'),
128
Wx::Event::EVT_CHECKBOX(
130
$self->{find_reverse},
132
$_[0]->{find_text}->SetFocus;
136
# The "Replace All" option
137
$self->{replace_all} = Wx::CheckBox->new(
140
Wx::gettext('Replace &All'),
142
Wx::Event::EVT_CHECKBOX(
144
$self->{replace_all},
146
$_[0]->{find_text}->SetFocus;
151
$self->{find} = Wx::Button->new(
154
Wx::gettext("&Find Next"),
156
Wx::Event::EVT_BUTTON(
166
$self->_on_hotkey( $_[1]->GetKeyCode );
170
# The "Replace" button
171
$self->{replace} = Wx::Button->new(
174
Wx::gettext("&Replace"),
176
Wx::Event::EVT_BUTTON(
180
$_[0]->replace_clicked;
186
$self->_on_hotkey( $_[1]->GetKeyCode );
189
$self->{replace}->SetDefault;
191
# The "Cancel" button
192
$self->{cancel} = Wx::Button->new(
195
Wx::gettext("&Cancel"),
197
Wx::Event::EVT_BUTTON(
205
# Find sizer begins here
206
my $find = Wx::StaticBoxSizer->new(
218
Wx::gettext("Find Text:"),
221
Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
227
Wx::wxGROW | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
233
Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
237
# Replace sizer begins here
238
my $replace = Wx::StaticBoxSizer->new(
242
Wx::gettext('Replace'),
250
Wx::gettext("Replace Text:"),
253
Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
257
$self->{replace_text},
259
Wx::wxGROW | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
263
# The layout grid for the options
264
my $grid = Wx::FlexGridSizer->new( 2, 2, 0, 0 );
265
$grid->AddGrowableCol(1);
269
Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
273
$self->{find_reverse},
275
Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
281
Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
285
$self->{replace_all},
287
Wx::wxALIGN_LEFT | Wx::wxLEFT | Wx::wxRIGHT | Wx::wxTOP,
291
# Options sizer begins here
292
my $options = Wx::StaticBoxSizer->new(
296
Wx::gettext('Options')
303
Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
307
# Sizer for the buttons
308
my $bottom = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
312
Wx::wxGROW | Wx::wxRIGHT,
318
Wx::wxGROW | Wx::wxLEFT | Wx::wxRIGHT,
324
Wx::wxGROW | Wx::wxLEFT,
328
# Fill the sizer for the overall dialog
329
my $sizer = Wx::FlexGridSizer->new( 1, 1, 0, 0 );
330
$sizer->AddGrowableCol(0);
334
Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
340
Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
346
Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxGROW | Wx::wxALL,
352
Wx::wxALIGN_RIGHT | Wx::wxALL,
356
# Let the widgets control the dialog size
357
$self->SetSizer($sizer);
358
$sizer->SetSizeHints($self);
360
# Update the dialog from configuration
361
my $config = $self->current->config;
362
$self->{find_case}->SetValue( $config->find_case );
363
$self->{find_regex}->SetValue( $config->find_regex );
364
$self->{find_first}->SetValue( $config->find_first );
365
$self->{find_reverse}->SetValue( $config->find_reverse );
376
Hide dialog when pressed cancel button.
384
# As we leave the Find dialog, return the user to the current editor
385
# window so they don't need to click it.
386
my $editor = $self->current->editor;
400
Grab currently selected text, if any, and place it in find combo box.
401
Bring up the dialog or perform search for strings' next occurence
402
if dialog is already displayed.
404
If selection is more than one line then consider it as the limit
405
of the search and not as the string to be used.
411
my $text = $self->current->text;
413
my $editor = $self->current->editor;
414
return unless $editor; # no replace if no file is open
416
# If selection is more than one line then consider it as the limit
417
# of the search and not as the string to be used (which becomes '')
418
if ( $text =~ /\n/ ) {
419
$self->{text_offset} = $editor->GetSelectionStart;
420
$self->{text_offset_end} = $editor->GetSelectionEnd;
423
$self->{text_offset} = 0;
424
$self->{text_offset_end} = $editor->GetLength;
427
# Clear out and reset the dialog, then prepare the new find
428
$self->{find_text}->refresh;
429
$self->{find_text}->SetValue($text);
430
$self->{find_text}->SetFocus;
431
$self->{replace_text}->refresh;
433
if ( $self->IsShown ) {
436
if ( length $text ) {
438
# Go straight to the replace field
439
$self->{replace_text}->SetFocus;
441
$self->{find_text}->SetFocus;
455
Executed when Find button is clicked.
457
Performs search on the term specified in the dialog.
463
my $config = $self->_sync_config;
465
# If we're only searching once, we won't need the dialog any more
466
if ( $config->find_first ) {
470
# Return false if we don't have anything to search for
471
my $search = $self->{find_text}->GetValue;
472
return unless defined _STRING($search);
474
# Get the replace term
475
my $replace = $self->{replace_text}->GetValue;
478
Padre::DB::History->create(
482
Padre::DB::History->create(
487
# Execute the first search
499
Search for given string's next occurence. If no string is available
500
(either as a selected text in editor, if Quick Find is on, or from
501
search history) run C<find> method.
507
my $term = Padre::DB::History->previous('search');
522
Perform backward search for string fetched from search history
523
or run C<find> method if search history is empty.
529
my $term = Padre::DB::History->previous('search');
531
$self->search( rev => 1 );
544
Perform actual search. Highlight (set as selected) found string.
551
my $regex = $self->_get_search or return;
553
# Forwards or backwards
554
my $backwards = $self->config->find_reverse;
556
$backwards = not $backwards;
559
# Find the range to search within
560
my $editor = $self->current->editor;
561
$self->{text} = $editor->GetTextRange( $self->{text_offset}, $self->{text_offset_end} );
562
my ( $from, $to ) = $editor->GetSelection;
564
# Execute the search and move to the resulting location
565
my ( $start, $end, @matches ) = Padre::Util::get_matches(
566
$self->{text}, $regex, $from - $self->{text_offset}, $to - $self->{text_offset},
569
return unless defined $start;
570
$editor->SetSelection( $start + $self->{text_offset}, $end + $self->{text_offset} );
577
=head2 replace_clicked
579
$self->replace_clicked;
581
Executed when the Replace button is clicked.
583
Replaces one appearance of the Find Text with the Replace Text.
585
If search window is still open, run C<search> on the whole text,
590
sub replace_clicked {
592
my $config = $self->_sync_config;
594
# If we're only searching once, we won't need the dialog any more
595
if ( $config->find_first ) {
599
# Return false if we don't have anything to search for
600
my $search = $self->{find_text}->GetValue;
601
return unless defined _STRING($search);
603
# Get the replace term
604
my $replace = $self->{replace_text}->GetValue;
607
Padre::DB::History->create(
611
Padre::DB::History->create(
616
# Execute the replace
617
if ( $self->{replace_all}->GetValue ) {
632
Executed when Replace All button is clicked.
634
Replace all appearances of given string in the current document.
641
# Prepare the search and replace values
642
my $regex = $self->_get_search or return;
643
my $replace = $self->_get_replace;
644
$replace =~ s/\\t/\t/g if length $replace;
646
# Execute the search for all matches
647
my $editor = $self->current->editor;
648
my $text = $editor->GetTextRange( $self->{text_offset}, $self->{text_offset_end} );
649
my ( undef, undef, @matches ) = Padre::Util::get_matches( $text, $regex, 0, 0 );
651
# Replace all matches as a single undo
653
$editor->BeginUndoAction;
654
foreach my $match ( reverse @matches ) {
655
$editor->SetTargetStart( $match->[0] + $self->{text_offset} );
656
$editor->SetTargetEnd( $match->[1] + $self->{text_offset} );
657
$editor->ReplaceTarget($replace);
659
$editor->EndUndoAction;
661
$self->main->message(
663
Wx::gettext('%s occurences were replaced'),
668
$self->main->message( Wx::gettext("Nothing to replace") );
678
Perform actual single replace. Highlight (set as selected) found string.
684
my $current = $self->current;
685
my $text = $current->text;
687
# Prepare the search and replace values
688
my $regex = $self->_get_search or return;
689
my $replace = $self->_get_replace;
690
$replace =~ s/\\t/\t/g if length $replace;
692
# Get current search condition and check if they match
693
my ( $start, $end, @matches ) = Padre::Util::get_matches( $text, $regex, 0, 0 );
695
# If they match replace it
696
if ( defined $start and $start == 0 and $end == length($text) ) {
697
$current->editor->ReplaceSelection($replace);
699
# If replaced text is smaller or larger than original,
700
# change our offset end accordingly
701
if ( length($replace) != ( $end - $start ) ) {
702
$self->{text_offset_end} += ( length($replace) - ( $end - $start ) );
705
# Update text to search with replaced values
706
################### $self->{text} = $current->editor->GetTextRange( $self->{text_offset}, $self->{text_offset_end} );
709
# If search window is still open, run a search on the whole text again
710
unless ( $current->config->find_first ) {
717
#####################################################################
720
# Save the dialog settings to configuration. Returns the config object
725
# Save the search settings to config
726
my $config = $self->current->config;
727
$config->set( find_case => !$self->{find_case}->GetValue );
728
$config->set( find_regex => $self->{find_regex}->GetValue );
729
$config->set( find_first => $self->{find_first}->GetValue );
730
$config->set( find_reverse => $self->{find_reverse}->GetValue );
736
# Internal method. $self->_get_search( $regex )
737
# Prepare and return search term defined as a regular expression.
740
my $config = $self->config;
741
my $term = Padre::DB::History->previous('search');
743
# Escape the raw search term
744
if ( $config->find_regex ) {
746
# Escape non-trailing $ so they won't interpolate
747
$term =~ s/\$(?!\z)/\\\$/g;
751
$term = quotemeta $term;
755
my $regex = eval { $config->find_case ? qr/$term/m : qr/$term/mi };
758
sprintf( Wx::gettext("Cannot build regex for '%s'"), $term ),
759
Wx::gettext('Search error'),
769
# Internal method. $self->_get_replace
770
# Returns previous replacement string from history
771
# or empty if _replace_choice_ widget is empty.
772
# Added to be able to use empty string as a replacement text
773
# but without storing in (the empty string) in history.
776
if ( $self->{replace_text} ) {
777
return $self->{replace_text}->GetValue;
779
return Padre::DB::History->previous('replace');
783
# Adds Ultraedit-like hotkeys for quick find/replace triggering
788
$self->find_clicked if $code == 102; # pressed 'f' hotkey
789
$self->replace_clicked if $code == 114; # pressed 'r' hotkey
798
=head1 COPYRIGHT & LICENSE
800
Copyright 2008-2009 The Padre development team as listed in Padre.pm.
802
This program is free software; you can redistribute
803
it and/or modify it under the same terms as Perl itself.
805
The full text of the license can be found in the
806
LICENSE file included with this module.
810
# Copyright 2008-2009 The Padre development team as listed in Padre.pm.
812
# This program is free software; you can redistribute it and/or
813
# modify it under the same terms as Perl 5 itself.