Index: lib/Padre/Wx/Dialog/Find.pm
===================================================================
--- lib/Padre/Wx/Dialog/Find.pm	(revision 6380)
+++ lib/Padre/Wx/Dialog/Find.pm	(working copy)
@@ -165,7 +165,7 @@
 		Wx::StaticText->new(
 			$self,
 			Wx::wxID_STATIC,
-			Wx::gettext("Find Text:"),
+			Wx::gettext('Find Text:'),
 		),
 		0,
 		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
Index: lib/Padre/Wx/Dialog/FindInFiles.pm
===================================================================
--- lib/Padre/Wx/Dialog/FindInFiles.pm	(revision 0)
+++ lib/Padre/Wx/Dialog/FindInFiles.pm	(revision 0)
@@ -0,0 +1,644 @@
+package Padre::Wx::Dialog::FindInFiles;
+
+use 5.008;
+use strict;
+use warnings;
+use threads;
+use threads::shared;
+use Padre::DB         			 ();
+use Padre::Wx					 ();
+use Padre::Wx::History::ComboBox ();
+
+our $VERSION = '0.40';
+our @ISA	 = qw{
+	Padre::Wx::Role::MainChild
+	Wx::Dialog
+};
+
+my $_ack_loaded = 0;
+
+=pod
+
+=head2 new
+
+	my $find_in_files = Padre::Wx::Dialog::FindInFiles->new($main);
+	
+Create and return a C<Padre::Wx::Dialog::FindInFiles> search widget.
+
+=cut
+
+sub new {
+	my $class = shift;
+	my $main  = shift or die("Did not pass parent to dialog constructor");
+	
+	my $self = $class->SUPER::new(
+		$main,
+		-1,
+		Wx::gettext('Find in Files'),
+		Wx::wxDefaultPosition,
+		Wx::wxDefaultSize,
+		Wx::wxCAPTION | Wx::wxCLOSE_BOX | Wx::wxSYSTEM_MENU | Wx::wxRESIZE_BORDER
+	);
+	
+	$self->{ack_opts}		= {};
+	$self->{ack_iter}		= undef;
+	$self->{ack_stats}		= {};
+	$self->{ack_done_event}	= Wx::NewEventType;
+	$self->{_panel_index}	= 9999999;
+	share($self->{ack_done_event});
+	
+	$self->{_ack_term} = Padre::Wx::History::ComboBox->new(
+		$self,
+		-1,
+		'',
+		Wx::wxDefaultPosition,
+		Wx::wxDefaultSize,
+		'search'
+	);
+	
+	$self->{_ack_dir} = Padre::Wx::History::ComboBox->new(
+		$self,
+		-1,
+		'',
+		Wx::wxDefaultPosition,
+		Wx::wxDefaultSize,
+		'find in'
+	);
+	
+	$self->{_file_types} = Padre::Wx::History::ComboBox->new(
+		$self,
+		-1,
+		'',
+		Wx::wxDefaultPosition,
+		Wx::wxDefaultSize,
+		'find type'
+	);
+	
+	$self->{_find} = Wx::Button->new(
+		$self,
+		Wx::wxID_FIND,
+		Wx::gettext("&Find")
+	);
+	Wx::Event::EVT_BUTTON(
+		$self,
+		$self->{_find},
+		sub {
+			$_[0]->find_clicked;
+		}
+	);
+	$self->{_find}->SetDefault;
+	
+	$self->{_pick_dir} = Wx::Button->new(
+		$self,
+		Wx::wxID_ANY,
+		Wx::gettext('...'),
+		Wx::wxDefaultPosition,
+		Wx::wxDefaultSize,
+		Wx::wxBU_EXACTFIT
+	);
+	Wx::Event::EVT_BUTTON(
+		$self,
+		$self->{_pick_dir},
+		sub {
+			$_[0]->on_pick_dir;
+		}
+	);
+	
+	$self->{_cancel} = Wx::Button->new(
+		$self,
+		Wx::wxID_CANCEL,
+		Wx::gettext("&Cancel"),
+	);
+	Wx::Event::EVT_BUTTON(
+		$self,
+		$self->{_cancel},
+		sub {
+			$_[0]->cancel;
+		}
+	);
+	
+	$self->{_case_insensitive} = Wx::CheckBox->new(
+		$self,
+		-1,
+		Wx::gettext('Case &Insensitive'),
+	);
+	Wx::Event::EVT_CHECKBOX(
+		$self,
+		$self->{_case_insensitive},
+		sub {
+			$_[0]->{_case_insensitive}->SetFocus;
+		}
+	);
+	
+	$self->{_ignore_hidden_subdirs}	= Wx::CheckBox->new(
+		$self,
+		-1,
+		Wx::gettext('I&gnore hidden Subdirectories'),
+	);
+	Wx::Event::EVT_CHECKBOX(
+		$self,
+		$self->{_ignore_hidden_subdirs},
+		sub {
+			$_[0]->{_ignore_hidden_subdirs}->SetFocus;
+		}
+	);
+	
+	# Setting up the grid containing the search criteria
+	my $find_grid = Wx::FlexGridSizer->new(3, 2, 5, 5);
+	$find_grid->AddGrowableCol(1);
+	$find_grid->Add(
+		Wx::StaticText->new(
+			$self,
+			Wx::wxID_STATIC,
+			Wx::gettext('Term:')
+		),
+		0,
+		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL,
+		0
+	);
+	$find_grid->Add(
+		$self->{_ack_term},
+		1,
+		Wx::wxEXPAND,
+		0
+	);
+	$find_grid->Add(
+		Wx::StaticText->new(
+			$self,
+			Wx::wxID_STATIC,
+			Wx::gettext('Dir:')
+		),
+		0,
+		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL,
+		0
+	);
+	# Setting up a sizer containing the directory picker...
+	my $dir_sizer = Wx::BoxSizer->new(
+		Wx::wxHORIZONTAL
+	);
+	$dir_sizer->Add(
+		$self->{_ack_dir},
+		1,
+		Wx::wxEXPAND,
+		0
+	);
+	$dir_sizer->Add(
+		$self->{_pick_dir},
+		0,
+		Wx::wxLEFT,
+		1
+	);
+	# ... and adding it to the grid
+	$find_grid->Add(
+		$dir_sizer,
+		1,
+		Wx::wxEXPAND | Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL,
+		0
+	);
+	$find_grid->Add(
+		Wx::StaticText->new(
+			$self,
+			Wx::wxID_STATIC,
+			Wx::gettext('In Files/Types:')
+		),
+		0,
+		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL,
+		0
+	);
+	$find_grid->Add(
+		$self->{_file_types},
+		1,
+		Wx::wxEXPAND,
+		0
+	);
+	
+	# Setting up the box containing the search criteria
+	my $find_box = Wx::StaticBoxSizer->new(
+		Wx::StaticBox->new(
+			$self,
+			-1,
+			Wx::gettext('Find in Files')
+		),
+		Wx::wxVERTICAL
+	);
+	$find_box->Add(
+		$find_grid,
+		1,
+		Wx::wxEXPAND | Wx::wxALL,
+		5
+	);
+	
+	# Setting up the box containing the search options
+	my $option_box = Wx::StaticBoxSizer->new(
+		Wx::StaticBox->new(
+			$self,
+			-1,
+			Wx::gettext('Options')
+		),
+		Wx::wxVERTICAL
+	);
+	$option_box->Add(
+		$self->{_case_insensitive},
+		0,
+		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
+		5
+	);
+	$option_box->Add(
+		$self->{_ignore_hidden_subdirs},
+		0,
+		Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL | Wx::wxALL,
+		5
+	);
+	
+	# Setting up the box containing the buttons
+	my $button_box = Wx::BoxSizer->new(
+		Wx::wxHORIZONTAL
+	);
+	$button_box->Add(
+		$self->{_find},
+		0,
+		Wx::wxALL,
+		5
+	);
+	$button_box->Add(
+		$self->{_cancel},
+		0,
+		Wx::wxALL,
+		5
+	);	
+	
+	# Setting up the dialog's sizer
+	my $sizer = Wx::BoxSizer->new(
+		Wx::wxVERTICAL
+	);
+	$sizer->Add(
+		$find_box,
+		0,
+		Wx::wxEXPAND | Wx::wxALL,
+		5
+	);
+	$sizer->Add(
+		$option_box,
+		0,
+		Wx::wxEXPAND | Wx::wxALL,
+		5
+	);
+	$sizer->Add(
+		$button_box,
+		0,
+		Wx::wxALIGN_RIGHT | Wx::wxALL,
+		5
+	);
+	
+	# Applying the sizer(s) to the dialog
+	$self->SetSizer($sizer);
+	$sizer->SetSizeHints($self);
+	
+	# Update the dialog from configuration
+	my $config = $self->current->config;
+	$self->{_case_insensitive}->SetValue(!($config->find_case));
+	$self->{_ignore_hidden_subdirs}->SetValue($config->find_nohidden);
+	
+	return $self;
+}
+
+=pod
+
+=head2 cancel
+
+  $self->cancel
+
+Hide dialog when pressed cancel button.
+
+=cut
+
+sub cancel {
+	my $self = shift;
+	$self->Hide;
+
+	# 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;
+}
+
+=pod
+
+=head2 find
+
+	$self->find
+	
+Loading up App::Ack if neccessary, Grabbing currently selected text, placing it
+in the find combobox and showing the dialog.
+
+=cut
+
+sub find {
+	my $self = shift;
+	
+	# Delay App::Ack loading till first use, this reduces memory  usage
+	# and init time
+	unless($_ack_loaded) {
+		my $minver = 1.86;
+		my $error  = "App::Ack $minver required for this feature";
+		
+		# try to load app::ack - we don't require $minver in the eval to
+		# provide a meaningful error message if needed.
+		eval 'use App::Ack'; ## no critic
+		if ($@) {
+			$self->main->error("$error (module not installed)");
+			return;
+		} elsif( $App::Ack::VERSION < $minver ) {
+			$self->main->error("$error (you have $App::Ack::VERSION installed)");
+			return;
+		}
+		# redefine some app::ack subs to display results in padre's output
+		SCOPE: {
+			no warnings 'redefine', 'once';
+			*{App::Ack::print_first_filename} = sub { $self->_print_results("$_[0]\n"); };
+			*{App::Ack::print_separator}      = sub { $self->_print_results("--\n"); };
+			*{App::Ack::print}                = sub { $self->_print_results( $_[0] ); };
+			*{App::Ack::print_filename}       = sub { $self->_print_results("$_[0]$_[1]"); };
+			*{App::Ack::print_line_no}        = sub { $self->_print_results("$_[0]$_[1]"); };
+		}
+		$_ack_loaded = 1;
+	}
+	
+	$self->{_ack_term}->refresh;
+	$self->{_ack_term}->SetValue($self->current->text || '');
+	$self->{_ack_term}->SetFocus;
+	$self->{_ack_dir}->refresh;
+	$self->{_ack_dir}->SetValue($self->current->project->root) if $self->current->project;
+	
+	$self->Show(1) unless $self->IsShown;
+	
+	return;
+}
+
+sub on_pick_dir {
+	my $self  = shift;
+	my $event = shift;
+	
+	my $default_dir = $self->{_ack_dir}->GetValue;
+	unless($default_dir) {
+		my $filename = $self->current->filename;
+		$default_dir = File::Basename::dirname($filename) if $filename;
+	}
+	
+	my $dir_dialog = Wx::DirDialog->new(
+		$self->main,
+		Wx::gettext("Select directory"),
+		$default_dir
+	);
+	return if $dir_dialog->ShowModal == Wx::wxID_CANCEL
+	$self->{_ack_dir}->SetValue($dir_dialog->GetPath);
+	
+	return;
+}
+
+sub find_clicked {
+	my $self   = shift;
+	my $event  = shift;
+	my $main   = $self->current->main;
+	my $config = $self->_sync_config;
+	
+	return unless $self->{_ack_term}->GetValue;
+	
+	# TODO kill the thread before closing the application
+	@_ = (); # cargo cult or bug? see Wx::Thread / Creating new threads
+	
+	$self->{ack_opts} = {};
+	
+	$self->{ack_opts}->{regex} = $self->{_ack_term}->GetValue;
+	if($config->find_nohidden) {
+		$self->{ack_opts}->{all} = 1;
+	} else {
+		$self->{ack_opts}->{u} = 1; # unrestricted
+	}
+	
+	for my $i ( App::Ack::filetypes_supported() ) {
+		$App::Ack::type_wanted{$i} = undef;
+	}
+	if(my $file_types = $self->{_file_types}) {
+		my $is_regex = 1;
+		my $wanted = ($file_types =~ s/^no//)? 0 : 1;
+		for my $i ( App::Ack::filetypes_supported() ) {
+			if ( $i eq $file_types ) {
+				$App::Ack::type_wanted{$i} = $wanted;
+				$is_regex = 0;
+				last;
+			}
+		}
+		$self->{ack_opts}->{G} = quotemeta $file_types if $is_regex;
+	}
+	
+	# case_insensitive
+	$self->{ack_opts}->{i} = $config->find_case if $config->find_case;
+
+	my $what = App::Ack::get_starting_points( [ $self->{_ack_dir}->GetValue ], $self->{ack_opts} );
+	$self->{ack_iter} = App::Ack::get_iterator( $what, $self->{ack_opts} );
+	App::Ack::filetype_setup();
+
+	unless ( $main->{ack} ) {
+		$self->_ack_create_pane;
+	}
+	$main->show_output(1);
+	$self->_ack_show_output(1);
+	$main->{ack}->DeleteAllItems;
+	
+	Wx::Event::EVT_COMMAND( $main, -1, $self->{ack_done_event}, sub { $self->_ack_done(@_); } );
+
+	my $worker = threads->create( sub { $self->_ack_on_thread(@_); } );
+
+	return;
+}
+
+sub _ack_create_pane {
+	my $self = shift;
+	my $main = $self->current->main;
+	
+	$main->{ack} = Wx::ListCtrl->new(
+		$main->bottom,
+		-1,
+		Wx::wxDefaultPosition,
+		Wx::wxDefaultSize,
+		Wx::wxLC_SINGLE_SEL | Wx::wxLC_NO_HEADER | Wx::wxLC_REPORT
+	);
+
+	$main->{ack}->InsertColumn( 0, Wx::gettext('Ack') );
+	$main->{ack}->SetColumnWidth( 0, Wx::wxLIST_AUTOSIZE );
+
+	Wx::Event::EVT_LIST_ITEM_ACTIVATED(
+		$main,
+		$main->{ack},
+		sub { $self->_ack_on_result_selected(@_); },
+	);
+}
+
+sub _ack_show_output {
+	my $self = shift;
+	my $main = $self->current->main;
+	my $on   = @_ ? $_[0] ? 1 : 0 : 1;
+	my $bp   = \$main->{bottom};
+	my $op   = \$main->{ack};
+	my $idx  = ${$bp}->GetPageIndex( ${$op} );
+
+	if ( $idx >= 0 ) {
+		${$bp}->SetSelection($idx);
+	} else {
+		${$bp}->InsertPage(
+			0,
+			${$op},
+			Wx::gettext("Ack"),
+			1,
+		);
+		${$op}->Show;
+	}
+	$main->aui->GetPane('bottom')->Show;
+	$main->aui->Update;
+
+	return;
+}
+
+sub _ack_on_result_selected {
+	my ( $self, $event ) = @_;
+
+	my $text = $event->GetItem->GetText;
+	return if not defined $text;
+
+	my ( $file, $line ) = ( $text =~ /^(.*?)\((\d+)\)\:/ );
+	return unless $line;
+
+	my $main = $self->current->main;
+	my $id   = $main->setup_editor($file);
+	$main->on_nth_pane($id) if $id;
+
+	my $page = $main->current->editor;
+	$line--;
+	$page->goto_line_centerize($line);
+}
+
+sub _ack_done {
+	my ( $self, $main, $event ) = @_;
+
+	my $data = $event->GetData;
+
+	$main = $self->current->main;
+	$main->{ack}->InsertStringItem( $self->{_panel_index}--, $data );
+	$main->{ack}->SetColumnWidth( 0, Wx::wxLIST_AUTOSIZE );
+
+	return;
+}
+
+sub _ack_on_thread {
+	my $self = shift;
+	# clear $self->{ack_stats}; for every request
+	$self->{ack_stats} = {
+		cnt_files   => 0,
+		cnt_matches => 0,
+	};
+
+	App::Ack::print_matches( $self->{ack_iter}, $self->{ack_opts} );
+
+	# summary
+	$self->_send_text( '-' x 39 . "\n" ) if ( $self->{ack_stats}->{cnt_files} );
+	$self->_send_text("Found $self->{ack_stats}->{cnt_files} files and $self->{ack_stats}->{cnt_matches} matches\n");
+}
+
+sub _print_results {
+	my ( $self, $text ) = @_;
+
+	# the first is filename, the second is line number, the third is matched line text
+	# while 'Binary file', there is ONLY filename
+	$self->{ack_stats}->{printed_lines}++;
+
+	# don't print filename again if it's just printed
+	return
+		if ($self->{ack_stats}->{printed_lines} % 3 == 1
+		and $self->{ack_stats}->{last_matched_filename}
+		and $self->{ack_stats}->{last_matched_filename} eq $text );
+	if ( $self->{ack_stats}->{printed_lines} % 3 == 1 ) {
+		if ( $text =~ /^Binary file/ ) {
+			$self->{ack_stats}->{printed_lines} = $self->{ack_stats}->{printed_lines} + 2;
+		}
+
+		$self->{ack_stats}->{last_matched_filename} = $text;
+		$self->{ack_stats}->{cnt_files}++;
+
+		# chop last ':', add \n after $filename
+		chop($text);
+		$text = "Found '$self->{ack_opts}->{regex}' in '$text':\n";
+
+		# new line between different files
+		$self->_send_text( '-' x 39 . "\n" );
+	} elsif ( $self->{ack_stats}->{printed_lines} % 3 == 2 ) {
+		$self->{ack_stats}->{cnt_matches}++;
+
+		# use () to wrap the number, an extra space for line number
+		$text =~ s/(\d+)/\($1\)/;
+		$text .= ' ';
+	}
+
+	#my $end = $result->get_end_iter;
+	#$result->insert($end, $text);
+
+	# just print it when we have \n
+	if ( $text =~ /[\r\n]/ ) {
+		my $filename = $self->{ack_stats}->{last_matched_filename};
+		chop($filename);
+		$text = $filename . $self->{ack_stats}->{last_text} . $text if $self->{ack_stats}->{last_text};
+		delete $self->{ack_stats}->{last_text};
+	} else {
+		$self->{ack_stats}->{last_text} .= $text;
+		return;
+	}
+
+	$self->_send_text($text);
+
+	return;
+}
+
+sub _send_text {
+	my ( $self, $text) = shift;
+	
+	# Wx documentation states that the value passed must be shared.
+	share($text);
+	
+	my $threvent = Wx::PlThreadEvent->new( -1, $self->{ack_done_event}, $text );
+	Wx::PostEvent( $self->current->main, $threvent );
+}
+
+sub _sync_config {
+	my $self   = shift;
+	my $config = $self->current->config;
+	$config->set(find_case	   => !$self->{_case_insensitive}->GetValue);
+	$config->set(find_nohidden => $self->{_ignore_hidden_subdirs}->GetValue);
+	$config->write;
+	
+	return $config;	
+}
+
+1;
+
+=pod
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2008-2009 The Padre development team as listed in Padre.pm.
+
+This program is free software; you can redistribute
+it and/or modify it under the same terms as Perl itself.
+
+The full text of the license can be found in the
+LICENSE file included with this module.
+
+=cut
+
+# Copyright 2008-2009 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.
Index: lib/Padre/Wx/Main.pm
===================================================================
--- lib/Padre/Wx/Main.pm	(revision 6380)
+++ lib/Padre/Wx/Main.pm	(working copy)
@@ -1896,6 +1896,25 @@
 
 =pod
 
+=item * my $find = $main->find_in_files;
+
+Returns the find in files dialog, creating a new one if needed.
+
+=cut
+
+sub find_in_files {
+	my $self = shift;
+
+	unless ( defined $self->{find_in_files} ) {
+		require Padre::Wx::Dialog::FindInFiles;
+		$self->{find_in_files} = Padre::Wx::Dialog::FindInFiles->new($self);
+	}
+
+	return $self->{find_in_files};
+}
+
+=pod
+
 =item * my $value = $main->prompt( $title, $subtitle, $key );
 
 Prompt user with a dialog box about the value that C<$key> should have.
Index: lib/Padre/Wx/Menu/Search.pm
===================================================================
--- lib/Padre/Wx/Menu/Search.pm	(revision 6380)
+++ lib/Padre/Wx/Menu/Search.pm	(working copy)
@@ -126,15 +126,15 @@
 	$self->AppendSeparator;
 
 	# Recursive Search
+	$self->{find_in_files} = $self->Append(
+		-1,
+		Wx::gettext("Find in Fi&les...")
+	),
 	Wx::Event::EVT_MENU(
 		$main,
-		$self->Append(
-			-1,
-			Wx::gettext("Find in Fi&les...")
-		),
+		$self->{find_in_files},
 		sub {
-			require Padre::Wx::Ack;
-			Padre::Wx::Ack::on_ack(@_);
+			$_[0]->find_in_files->find;
 		},
 	);
 
