The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Xacobeo::UI::DomView;

=head1 NAME

Xacobeo::UI::DomView - DOM tree view

=head1 SYNOPSIS

	use Xacobeo::DomView;
	use Xacobeo::UI::SourceView;
	
	my $view = Xacobeo::UI::SourceView->new();
	$window->add($view);
	
	# Load a document
	my $document = Xacobeo::Document->new_from_file($file, $type);
	$view->set_document($document);
	$view->load_node($document->documentNode);

=head1 DESCRIPTION

The application's main window. This widget is a L<Gtk2::TreeView>.

=head1 PROPERTIES

The following properties are defined:

=head2 ui-manager

The UI Manager used by this widget.

=head2 action-group

The action group that provides the values in the context menu.

=head2 menu

The context menu of the widget.

=head2 document

The document being displayed.

=head2 namespaces

The namespaces registered in the document.

=head1 METHODS

The following methods are available:

=head2 new

Creates a new instance. This is simply the parent's constructor.

=cut

use strict;
use warnings;

use Data::Dumper;

use Glib qw(TRUE FALSE);
use Gtk2;
use Xacobeo::I18n;
use Xacobeo::XS;
use Xacobeo::Document;

use Xacobeo::GObject;

Xacobeo::GObject->register_package('Gtk2::TreeView' =>
	properties => [
		Glib::ParamSpec->object(
			'ui-manager',
			"UI Manager",
			"The UI Manager that provides the UI",
			'Gtk2::UIManager',
			['readable', 'writable'],
		),

		Glib::ParamSpec->object(
			'action-group',
			"Action Group",
			"The action group with context menu entries",
			'Gtk2::ActionGroup',
			['readable', 'writable'],
		),

		Glib::ParamSpec->object(
			'menu',
			"Context Menu",
			"The context menu for the tree items",
			'Gtk2::ActionGroup',
			['readable', 'writable'],
		),

		Glib::ParamSpec->object(
			'document',
			"Document",
			"The main document being displayed",
			'Xacobeo::Document',
			['readable', 'writable'],
		),

		# FIXME this property is redundant as we can use $self->document->namespaces
		Glib::ParamSpec->scalar(
			'namespaces',
			"Namespaces",
			"The namespaces in the main document",
			['readable', 'writable'],
		),
	],

	signals => {
		'node-selected' => {
			flags       => ['run-last'],
			# Parameters:   Node 
			param_types => ['Glib::Scalar'],
		},
	},
);


my $NODE_POS = 0;
my $NODE_DATA     = $NODE_POS++;
my $NODE_ICON     = $NODE_POS++;
my $NODE_NAME     = $NODE_POS++;
my $NODE_ID_NAME  = $NODE_POS++;
my $NODE_ID_VALUE = $NODE_POS++;


sub INIT_INSTANCE {
	my $self = shift;

	my $model = Gtk2::TreeStore->new(
		'Glib::Scalar', # A reference to the XML::LibXML::Node
		'Glib::String', # The icon to use (ex: 'gtk-directory')
		'Glib::String', # The name of the Element
		'Glib::String', # The name of the ID field
		'Glib::String', # The value of the ID field
	);
	$self->set_model($model);
	$self->set_fixed_height_mode(TRUE);

	my $column = $self->_add_text_column($NODE_NAME, __('Element'), 150);

	# Icon
	my $node_icon = Gtk2::CellRendererPixbuf->new();
	$column->pack_start($node_icon, FALSE);
	$column->set_attributes($node_icon, 'stock-id' => $NODE_ICON);

	# Node attribute name (ID attribute)
	$self->_add_text_column($NODE_ID_NAME, __('ID name'), 75);

	# Node attribute value (ID attribute)
	$self->_add_text_column($NODE_ID_VALUE, __('ID value'), 75);
	

	my $ui_manager = $self->_build_ui_manager();
	$self->ui_manager($ui_manager);

	my $menu = $ui_manager->get_widget('/DomViewPopup');
	$self->menu($menu);

	$self->signal_connect('row-activated' => \&callback_row_activated);
	$self->signal_connect('popup-menu' => \&callback_popup_menu);
	$self->signal_connect('button-press-event' => \&callback_button_press_event);
}


sub _build_ui_manager {
	my $self = shift;

	my $entries = [
		# Entries (name, stock id, label, accelerator, tooltip, callback)
		[
			'DomViewSelectNode',
			'gtk-jump-to',
			__("_Jump to"),
			undef,
			__("Show the node"),
			sub { $self->do_select_node() }
		],
		[
			'DomViewCopyXPath',
			'gtk-copy',
			__("_Copy XPath"),
			undef,
			__("Copy the node's XPath"),
			sub { $self->do_copy_xpath() }
		],
	];

	my $actions = Gtk2::ActionGroup->new("DomViewActions");
	$self->action_group($actions);
	$actions->add_actions($entries, undef);
	$actions->set_sensitive(FALSE);

	my $ui_manager = Gtk2::UIManager->new();
	$self->ui_manager($ui_manager);

	my $ui_string = <<'__XML__';
<ui>
	<popup name="DomViewPopup">
		<menuitem action='DomViewSelectNode'/>
		<placeholder name="DomViewPlaceholder_1"/>
		<separator/>
		<menuitem action='DomViewCopyXPath'/>
		<placeholder name="DomViewPlaceholder_2"/>
	</popup>
</ui>
__XML__
	$ui_manager->add_ui_from_string($ui_string);

	$ui_manager->insert_action_group($actions, 0);
	return $ui_manager;
}


#
# Transform the signal 'row-activated' into 'node-selected'.
#
sub callback_row_activated {
	my ($self, $path) = @_;

	my $model = $self->get_model;
	my $iter = $model->get_iter($path);
	my $node = $model->get($iter, $NODE_DATA);
	$self->signal_emit('node-selected' => $node);
}


sub do_copy_xpath {
	my $self = shift;

	my $node = $self->get_selected_node or return;
	my $xpath = Xacobeo::XS->get_node_path($node, $self->namespaces);

	foreach my $selection (qw(SELECTION_CLIPBOARD SELECTION_PRIMARY)) {
		my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->$selection);
		$clipboard->set_text($xpath);
	}
}


sub do_select_node {
	my $self = shift;

	my $node = $self->get_selected_node or return;
	$self->signal_emit('node-selected' => $node);
}


sub get_selected_node {
	my $self = shift;
	# Get the selected node and find its xpath
	my $selection = $self->get_selection;
	my ($model, $iter) = $selection->get_selected or return;
	my $node = $model->get($iter, $NODE_DATA);
	return $node;
}


#
# Display a context menu for a given node when right clicking.
#
sub callback_button_press_event {
	my ($self, $event) = @_;

	return FALSE unless $event->button == 3;

	my $path = $self->get_path_at_pos($event->x, $event->y) or return FALSE;

	my $selection = $self->get_selection;
	$selection->unselect_all();
	$selection->select_path($path);

	$self->action_group->set_sensitive(TRUE);

	$self->menu->popup(undef, undef, undef, undef, $event->button, $event->time);

	return TRUE;
}


#
# Display a context menu for a given node when right clicking.
#
sub callback_popup_menu {
	my ($self) = @_;
	$self->menu->popup(undef, undef, undef, undef, 0, 0);
	return TRUE;
}


sub set_document {
	my $self = shift;
	my ($document) = @_;

	$self->document($document);
	$self->namespaces(
		$self->document ? $self->document->namespaces : undef
	);
}


=head2 load_node

Sets the tree view nodes hierarchy based on the given node. This is the method
that will actually add items to the widget.

Parameters:

=over

=item * $node

The node to be loaded into the tree widget; an instance of L<XML::LibXML::Node>.

=back

=cut


sub load_node {
	my $self = shift;
	my ($node) = @_;

	my $store = $self->get_model;

	$self->set_model(undef);
	if (defined $node and defined $store) {
		Xacobeo::XS->load_tree_store($store, $node, $self->namespaces);
	}
	elsif (defined $store) {
		$store->clear();
	}
	$self->set_model($store);

	# Expand the first level
	if (my $iter = $store->get_iter_first) {
		my $path = $store->get_path($iter);
		$self->expand_row($path, FALSE);
	}
}


#
# Adds a text column to the tree view
#
sub _add_text_column {
	my $self = shift;
	my ($field, $title, $width) = @_;

	my $cell = Gtk2::CellRendererText->new();
	my $column = Gtk2::TreeViewColumn->new();
	$column->pack_end($cell, TRUE);

	$column->set_title($title);
	$column->set_resizable(TRUE);
	$column->set_sizing('fixed');
	$column->set_fixed_width($width);
	$column->set_attributes($cell, text => $field);

	$self->append_column($column);

	return $column;
}


# A true value
1;


=head1 AUTHORS

Emmanuel Rodriguez E<lt>potyl@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2008,2009 by Emmanuel Rodriguez.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut