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

use 5.008005;
use strict;
use warnings;
use Carp                     ();
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 = '1.00';
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->right;

	# 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 function 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 functions list
	$self->{list} = Wx::ListBox->new(
		$self,
		-1,
		Wx::DefaultPosition,
		Wx::DefaultSize,
		[],
		Wx::LB_SINGLE | Wx::BORDER_NONE
	);

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

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

	# Handle double-click on a function name
	Wx::Event::EVT_LISTBOX_DCLICK(
		$self,
		$self->{list},
		sub {
			$self->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 in list
	Wx::Event::EVT_KEY_UP(
		$self->{list},
		sub {
			$self->on_search_key_up( $_[1] );
		},
	);

	# Handle char events in search box
	Wx::Event::EVT_CHAR(
		$self->{search},
		sub {
			$self->on_search_char( $_[1] );
		},
	);

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

	# Bind the context menu
	$self->context_bind;

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

	return $self;
}





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

sub view_panel {
	return 'right';
}

sub view_label {
	Wx::gettext('Functions');
}

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

sub view_stop {
	$_[0]->task_reset;
}





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

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

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

	$menu->AppendSeparator;

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

	return;
}





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

sub on_search_key_up {
	my $self  = shift;
	my $event = shift;
	my $code  = $event->GetKeyCode;

	if ( $code == Wx::K_RETURN ) {
		$self->on_list_item_activated($event);
		$self->{search}->SetValue('');

	} 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);
}

sub on_search_char {
	my $self  = 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 focus
		# to the editor
		$self->{search}->SetValue('');
		$self->main->editor_focus;
	}

	$event->Skip(1);
}

sub on_list_item_activated {
	my $self   = shift;
	my $event  = shift;
	my $editor = $self->current->editor or return;

	# Which sub did they click
	my $name = $self->{list}->GetStringSelection;
	if ( defined Params::Util::_STRING($name) ) {
		$editor->goto_function($name);
	}

	return;
}

sub on_context_menu {
	my $self  = shift;
	my $event = shift;

	require Padre::Wx::FunctionList::Menu;
	my $menu = Padre::Wx::FunctionList::Menu->new( $self, $event );

	# Try to determine where to show the context menu
	if ( $event->isa('Wx::MouseEvent') ) {

		# Position is already window relative
		$self->PopupMenu( $menu->wx, $event->GetX, $event->GetY );

	} elsif ( $event->can('GetPosition') ) {

		# Assume other event positions are screen relative
		my $screen = $event->GetPosition;
		my $client = $self->ScreenToClient($screen);
		$self->PopupMenu( $menu->wx, $client->x, $client->y );

	} else {

		# Probably a wxCommandEvent
		# TO DO Capture a better location from the mouse directly
		$self->PopupMenu( $menu->wx, 50, 50 );
	}

	$event->Skip(0);
}





######################################################################
# 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;

	# Abort any in-flight checks
	$self->task_reset;

	# Hide the widgets when no files are open
	unless ($document) {
		$self->{document} = '';
		$self->disable;
		return;
	}

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

	# Nothing to do if there is no content
	my $task = $document->task_functions;
	unless ($task) {
		$self->disable;
		return;
	}

	# Ensure the widget is visible
	$self->enable;

	# Shortcut if there is nothing to search for
	if ( $document->is_unused ) {
		return;
	}

	# Launch the background task
	$self->task_request(
		task  => $task,
		text  => $document->text_get,
		order => $current->config->main_functions_order,
	);

}

sub enable {
	my $self = shift;
	my $lock = $self->lock_update;
	$self->{search}->Show(1);
	$self->{list}->Show(1);

	# Rerun our layout in case the size of the function list
	# geometry changed while we were hidden.
	$self->Layout;
}

sub disable {
	my $self = shift;
	my $lock = $self->lock_update;
	$self->{search}->Hide;
	$self->{list}->Hide;
	$self->{list}->Clear;
	$self->{model} = [];
}

# Set an updated method list from the task
sub task_finish {
	my $self = shift;
	my $task = shift;
	my $list = $task->{list} or return;
	$self->{model} = $list;
	$self->render;
}

# Populate the functions 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 $method ( reverse @$model ) {
			if ( $method =~ /$string/i ) {
				$list->Insert( $method, 0 );
			}
		}
	}

	return 1;
}

1;

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