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

use 5.008005;
use strict;
use warnings;
use Scalar::Util             ();
use Params::Util             ();
use Padre::Feature           ();
use Padre::Role::Task        ();
use Padre::Wx::Role::View    ();
use Padre::Wx::Role::Main    ();
use Padre::Wx::Role::Context ();
use Padre::Wx                ();

our $VERSION = '0.96';
our @ISA     = qw{
	Padre::Role::Task
	Padre::Wx::Role::View
	Padre::Wx::Role::Main
	Padre::Wx::Role::Context
	Wx::Panel
};





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

sub new {
	my $class = shift;
	my $main  = shift;
	my $panel = shift || $main->bottom;

	# Create the parent panel, which will contain the search and tree
	my $self = $class->SUPER::new(
		$panel,
		-1,
		Wx::DefaultPosition,
		Wx::DefaultSize,
	);

	# Temporary store for the task list.
	$self->{model} = [];

	# Remember the last document we were looking at
	$self->{document} = '';

	# Create the search control
	$self->{search} = Wx::TextCtrl->new(
		$self,
		-1,
		'',
		Wx::DefaultPosition,
		Wx::DefaultSize,
		Wx::TE_PROCESS_ENTER | Wx::SIMPLE_BORDER,
	);

	# Create the Todo list
	$self->{list} = Wx::ListBox->new(
		$self,
		-1,
		Wx::DefaultPosition,
		Wx::DefaultSize,
		[],
		Wx::LB_SINGLE | Wx::BORDER_NONE
	);

	# Create a sizer
	my $sizer = Wx::BoxSizer->new(Wx::VERTICAL);
	$sizer->Add( $self->{search}, 0, Wx::ALL | Wx::EXPAND );
	$sizer->Add( $self->{list},   1, Wx::ALL | Wx::EXPAND );

	# Fits panel layout
	$self->SetSizerAndFit($sizer);
	$sizer->SetSizeHints($self);

	# Double-click a function name
	Wx::Event::EVT_LISTBOX_DCLICK(
		$self,
		$self->{list},
		sub {
			$_[0]->on_list_item_activated( $_[1] );
		}
	);

	# Handle double click on list.
	# Overwrite to avoid stealing the focus back from the editor.
	# On Windows this appears to kill the double-click feature entirely.
	unless (Padre::Constant::WIN32) {
		Wx::Event::EVT_LEFT_DCLICK(
			$self->{list},
			sub {
				return;
			}
		);
	}

	# Handle key events
	Wx::Event::EVT_KEY_UP(
		$self->{list},
		sub {
			my ( $this, $event ) = @_;

			my $code = $event->GetKeyCode;
			if ( $code == Wx::K_RETURN ) {
				$self->on_list_item_activated($event);
			} elsif ( $code == Wx::K_ESCAPE ) {

				# Escape key clears search and returns focus
				# to the editor
				$self->{search}->SetValue('');
				$self->main->editor_focus;
			}

			$event->Skip(1);
			return;
		}
	);

	# Handle key events
	Wx::Event::EVT_CHAR(
		$self->{search},
		sub {
			my $this  = shift;
			my $event = shift;
			my $code  = $event->GetKeyCode;

			if ( $code == Wx::K_DOWN || $code == Wx::K_UP || $code == Wx::K_RETURN ) {

				# Up/Down and return keys focus on the functions lists
				$self->{list}->SetFocus;
				my $selection = $self->{list}->GetSelection;
				if ( $selection == -1 && $self->{list}->GetCount > 0 ) {
					$selection = 0;
				}
				$self->{list}->Select($selection);

			} elsif ( $code == Wx::K_ESCAPE ) {

				# Escape key clears search and returns the
				# focus to the editor.
				$self->{search}->SetValue('');
				$self->main->editor_focus;
			}

			$event->Skip(1);
		}
	);

	# React to user search
	Wx::Event::EVT_TEXT(
		$self,
		$self->{search},
		sub {
			$self->render;
		}
	);

	$main->add_refresh_listener($self);

	$self->context_bind;

	if (Padre::Feature::STYLE_GUI) {
		$self->main->theme->apply($self);
	}

	return $self;
}





######################################################################
# Padre::Wx::Role::View Methods

sub view_panel {
	return 'bottom';
}

sub view_label {
	Wx::gettext('Task List');
}

sub view_close {
	$_[0]->task_reset;
	$_[0]->main->show_tasks(0);
}





#####################################################################
# Event Handlers

sub on_list_item_activated {
	my $self   = shift;
	my $editor = $self->current->editor or return;
	my $nth    = $self->{list}->GetSelection;
	my $task   = $self->{model}->[$nth] or return;

	# Move the selection to where we last saw it
	$editor->goto_pos_centerize( $task->{pos} );
	$editor->SetFocus;

	return;
}





######################################################################
# Padre::Wx::Role::Context Methods

sub context_menu {
	my $self = shift;
	my $menu = shift;

	$self->context_append_options( $menu => 'main_tasks_panel' );

	return;
}





######################################################################
# General Methods

# Sets the focus on the search field
sub focus_on_search {
	$_[0]->{search}->SetFocus;
}

sub refresh {
	my $self     = shift;
	my $current  = shift or return;
	my $document = $current->document;
	my $search   = $self->{search};
	my $list     = $self->{list};

	# Flush and hide the list if there is no active document
	unless ($document) {
		my $lock = $self->lock_update;
		$search->Hide;
		$list->Hide;
		$list->Clear;
		$self->{model}    = [];
		$self->{document} = '';
		return;
	}

	# Ensure the widget is visible
	$search->Show(1);
	$list->Show(1);

	# Clear search when it is a different document
	my $id = Scalar::Util::refaddr($document);
	if ( $id ne $self->{document} ) {
		$search->ChangeValue('');
		$self->{document} = $id;
	}

	# Unlike the Function List widget we copied to make this,
	# don't bother with a background task, since this is much quicker.
	my $regexp = $current->config->main_tasks_regexp;
	my $text   = $document->text_get;
	my @items  = ();
	eval {
		while ( $text =~ /$regexp/gim )
		{
			push @items, { text => $1 || '<no text>', 'pos' => pos($text) };
		}
	};
	$self->main->error( sprintf( Wx::gettext('%s in TODO regex, check your config.'), $@ ) ) if $@;
	while ( $text =~ /#\s*(Ticket #\d+.*?)$/gim ) {
		push @items, { text => $1, 'pos' => pos($text) };
	}

	if ( @items == 0 ) {
		$list->Clear;
		$self->{model} = [];
		return;
	}

	# Update the model and rerender
	$self->{model} = \@items;
	$self->render;
}

# Populate the list with search results
sub render {
	my $self   = shift;
	my $model  = $self->{model};
	my $search = $self->{search};
	my $list   = $self->{list};

	# Quote the search string to make it safer
	my $string = $search->GetValue;
	if ( $string eq '' ) {
		$string = '.*';
	} else {
		$string = quotemeta $string;
	}

	# Show the components and populate the function list
	SCOPE: {
		my $lock = $self->lock_update;
		$search->Show(1);
		$list->Show(1);
		$list->Clear;
		foreach my $task ( reverse @$model ) {
			my $text = $task->{text};
			if ( $text =~ /$string/i ) {
				$list->Insert( $text, 0 );
			}
		}
	}

	return 1;
}

1;

# Copyright 2008-2012 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.