The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Padre::Wx::Dialog::PerlFilter;

# Filter text through Perl source

use 5.008;
use strict;
use warnings;
use Padre::Wx 'RichText';
use Padre::Wx::Icon       ();
use Padre::Wx::Role::Main ();

our $VERSION = '1.00';
our @ISA     = qw{
	Padre::Wx::Role::Main
	Wx::Dialog
};





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

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

	# Create the basic object
	my $self = $class->SUPER::new(
		$parent,
		-1,
		Wx::gettext('Perl Filter'),
		Wx::DefaultPosition,
		Wx::DefaultSize,
		Wx::DEFAULT_FRAME_STYLE,
	);

	# Set basic dialog properties
	$self->SetIcon(Padre::Wx::Icon::PADRE);
	$self->SetMinSize( [ 380, 500 ] );

	# create sizer that will host all controls
	my $sizer = Wx::BoxSizer->new(Wx::HORIZONTAL);
	$self->{sizer} = $sizer;

	# Create the controls
	$self->_create_controls($sizer);

	# Bind the control events
	$self->_bind_events;

	# Tune the size and position it appears
	$self->SetSizer($sizer);
	$self->Fit;
	$self->CentreOnParent;

	return $self;
}

sub _create_controls {
	my ( $self, $sizer ) = @_;

	# Dialog Controls, created in keyboard navigation order

	# Filter type
	$self->{filter_mode} = Wx::RadioBox->new(
		$self, -1,
		Wx::gettext('Input/output:'),
		Wx::DefaultPosition,
		Wx::DefaultSize,
		[   Wx::gettext('$_ for both'),

			# Wx::gettext('STDIN/STDOUT'),
			Wx::gettext('wrap in map { }'),
			Wx::gettext('wrap in grep { }'),
		]
	);
	$self->{filter_mode_values} = { # Set position of each filter mode
		default => 0,
		std     => -1,
		'map'   => 1,
		'grep'  => 2,
	};

	# Perl source
	my $source_label = Wx::StaticText->new( $self, -1, Wx::gettext('&Perl filter source:') );
	$self->{source} = Wx::TextCtrl->new(
		$self, -1, '', Wx::DefaultPosition, Wx::DefaultSize,
		Wx::RE_MULTILINE | Wx::WANTS_CHARS
	);

	# Input text
	my $original_label = Wx::StaticText->new( $self, -1, Wx::gettext('Or&iginal text:') );
	$self->{original_text} = Wx::TextCtrl->new(
		$self, -1, '', Wx::DefaultPosition, Wx::DefaultSize,
		Wx::TE_MULTILINE | Wx::NO_FULL_REPAINT_ON_RESIZE
	);

	# Matched readonly text field
	my $result_label = Wx::StaticText->new( $self, -1, Wx::gettext('&Output text:') );
	$self->{result_text} = Wx::RichTextCtrl->new(
		$self, -1, '', Wx::DefaultPosition, Wx::DefaultSize,
		Wx::RE_MULTILINE | Wx::RE_READONLY | Wx::WANTS_CHARS # Otherwise arrows will not work on win32
	);

	# Run the filter
	$self->{run_button} = Wx::Button->new(
		$self, -1, Wx::gettext('Run filter'),
	);

	# Insert result into current document button_name
	$self->{insert_button} = Wx::Button->new(
		$self, -1, Wx::gettext('Insert'),
	);

	# Close button
	$self->{close_button} = Wx::Button->new(
		$self, Wx::ID_CANCEL, Wx::gettext('&Close'),
	);

	my $buttons = Wx::BoxSizer->new(Wx::HORIZONTAL);
	$buttons->AddStretchSpacer;
	$buttons->Add( $self->{run_button},    0, Wx::ALL, 1 );
	$buttons->Add( $self->{insert_button}, 0, Wx::ALL, 1 );
	$buttons->Add( $self->{close_button},  0, Wx::ALL, 1 );
	$buttons->AddStretchSpacer;

	# Dialog Layout

	# Vertical layout of the left hand side
	my $left = Wx::BoxSizer->new(Wx::VERTICAL);

	$left->Add( $self->{filter_mode}, 0, Wx::ALL | Wx::EXPAND, 1 );

	$left->Add( $source_label,   0, Wx::ALL | Wx::EXPAND, 1 );
	$left->Add( $self->{source}, 1, Wx::ALL | Wx::EXPAND, 1 );

	$left->Add( $original_label,        0, Wx::ALL | Wx::EXPAND, 1 );
	$left->Add( $self->{original_text}, 1, Wx::ALL | Wx::EXPAND, 1 );
	$left->Add( $result_label,          0, Wx::ALL | Wx::EXPAND, 1 );
	$left->Add( $self->{result_text},   1, Wx::ALL | Wx::EXPAND, 1 );
	$left->AddSpacer(5);
	$left->Add( $buttons, 0, Wx::ALL | Wx::EXPAND, 1 );

	# Main sizer
	$sizer->Add( $left, 1, Wx::ALL | Wx::EXPAND, 5 );
}

sub _bind_events {
	my $self = shift;

	# Wx::Event::EVT_KEY_DOWN(
	# $self,
	# sub {
	# my ($key_event) = $_[1];
	# $self->Hide if $key_event->GetKeyCode == Wx::K_ESCAPE;
	# return;
	# }
	# );
	Wx::Event::EVT_TEXT(
		$self,
		$self->{original_text},
		sub { $_[0]->run; },
	);

	# Wx::Event::EVT_KEY_DOWN(
	# $self->{source},
	# sub {
	# my ($key_event) = $_[1];
	# $self->Hide if $key_event->GetKeyCode == Wx::K_ESCAPE;
	# return;
	# }
	# );
	#
	# Wx::Event::EVT_KEY_DOWN(
	# $self->{original_text},
	# sub {
	# my ($key_event) = $_[1];
	# $self->Hide if $key_event->GetKeyCode == Wx::K_ESCAPE;
	# return;
	# }
	# );
	#
	# Wx::Event::EVT_KEY_DOWN(
	# $self->{result_text},
	# sub {
	# my ($key_event) = $_[1];
	# $self->Hide if $key_event->GetKeyCode == Wx::K_ESCAPE;
	# return;
	# }
	# );

	Wx::Event::EVT_BUTTON(
		$self,
		$self->{run_button},
		sub { shift->run; },
	);

	Wx::Event::EVT_BUTTON(
		$self,
		$self->{insert_button},
		sub { shift->_insert_result; },
	);
}

#
# A private method that inserts the current regex into the current document
#
sub _insert_result {
	my $self = shift;

	my $editor = $self->current->editor or return;
	$editor->InsertText( $editor->GetCurrentPos, $self->{result_text}->GetValue );

	return;
}

# -- public methods

sub show {
	my $self = shift;

	if ( $self->IsShown ) {
		$self->SetFocus;
	} else {
		my $editor = $self->current->editor;

		# Insert sample, but do not overwrite an exisiting filter source
		$self->{source}->ChangeValue( Wx::gettext("# Input is in \$_\n\$_ = \$_;\n# Output goes to \$_\n") )
			unless $self->{source}->GetValue;

		if ($editor) {
			my $selection        = $editor->GetSelectedText;
			my $selection_length = length $selection;
			if ( $selection_length > 0 ) {
				$self->{original_text}->ChangeValue($selection);
			} else {
				$self->{original_text}->ChangeValue( $editor->GetText );
			}
		} else {
			$self->{original_text}->ChangeValue('');
		}

		$self->{result_text}->SetValue('');

		$self->Show;
	}

	$self->{source}->SetFocus;

	return;
}

#
# Returns the user input data of the dialog as a hashref
#
sub get_data {
	my $self = shift;

	my %data = (
		text => {
			source        => $self->{source}->GetValue,
			original_text => $self->{original_text}->GetValue,
			result_text   => $self->{result_text}->GetValue,
		},
	);

	return \%data;
}

#
# Sets the user input data of the dialog given a hashref containing the results of get_data
#
sub set_data {
	my ( $self, $data_ref ) = @_;

	foreach my $text_field ( keys %{ $data_ref->{text} } ) {
		$self->{$text_field}->SetValue( $data_ref->{text}->{$text_field} );
	}

	return;
}

sub run {
	my $self = shift;

	my $source        = $self->{source}->GetValue;
	my $original_text = $self->{original_text}->GetValue;
	my $filter_mode   = $self->{filter_mode}->GetSelection;
	my $document      = $self->current->document;
	my $nl            = defined($document) ? $document->newline : "\n"; # Use (bad) default
	my $result_text;

	$self->{result_text}->Clear;

	local $@;

	my $code = eval "use utf8;package " . __PACKAGE__ . "::Sandbox;sub{$source\n}";

	unless ($@) {
		if ( $filter_mode == $self->{filter_mode_values}->{default} ) {
			$result_text = eval {
				local $_ = $original_text;
				$code->();
				$_;
			};
		} elsif ( $filter_mode == $self->{filter_mode_values}->{std} ) {

			# TODO: use STDIN/STDOUT
			#		$_ = $original_text;
			#		$result_text = eval $source;
		} elsif ( $filter_mode == $self->{filter_mode_values}->{map} ) {
			$result_text = eval {
				join( $nl, map { $code->() } split( /$nl/, $original_text ) );
			};
		} elsif ( $filter_mode == $self->{filter_mode_values}->{grep} ) {
			$result_text = eval {
				join( $nl, grep { $code->() } split( /$nl/, $original_text ) );
			};
		}
	}

	# Common eval error handling
	if ($@) {

		# TODO: Set text color red
		$self->{result_text}->SetValue( Wx::gettext("Error:\n") . $@ );
	} elsif ( defined $result_text ) {
		$self->{result_text}->SetValue($result_text);
	} else {

		# TODO: Set text color red
		$self->{result_text}->SetValue('undef');
	}

	return;
}

1;

__END__

=pod

=head1 NAME

Padre::Wx::Dialog::PerlFilter - dialog to make it easy to create a regular expression

=head1 DESCRIPTION


The C<Regex Editor> provides an interface to easily create regular
expressions used in Perl.

The user can insert a regular expression (the surrounding C</> characters are not
needed) and a text. The C<Regex Editor> will automatically display the matching
text in the bottom right window.


At the top of the window the user can select any of the four
regular expression modifiers:

=over

=item Ignore case (i)

=item Single-line (s)

=item Multi-line (m)

=item Extended (x)

=back

Global match

Allow the change/replacement of the // around the regular expression

Highlight the match in the source text instead of in
a separate window

Display the captured groups in a tree hierarchy similar to Rx ?

  Group                  Span (character) Value
  Match 0 (Group 0)      4-7              the actual match

Display the various Perl variable containing the relevant values
e.g. the C<@-> and C<@+> arrays, the C<%+> hash
C<$1>, C<$2>...

point out what to use instead of C<$@> and C<$'> and C<$`>

English explanation of the regular expression

=head1 COPYRIGHT & LICENSE

Copyright 2008-2013 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 5 itself.

=cut