/usr/local/CPAN/Padre-Plugin-Swarm/Padre/Plugin/Swarm/Wx/Resources/TreeCtrl.pm
package Padre::Plugin::Swarm::Wx::Resources::TreeCtrl;
use 5.008;
use strict;
use warnings;
use File::Copy;
use File::Spec ();
use File::Basename ();
use Padre::Current ();
use Padre::Util ();
use Padre::Wx ();
use Padre::Constant ();
our $VERSION = '0.1';
our @ISA = 'Wx::TreeCtrl';
## ALMOST all cargo from the Padre directory class
# Creates a new Directory Browser object
sub new {
my $class = shift;
my $panel = shift;
my $self = $class->SUPER::new(
$panel,
-1,
Wx::wxDefaultPosition,
Wx::wxDefaultSize,
Wx::wxTR_HIDE_ROOT | Wx::wxTR_SINGLE | Wx::wxTR_FULL_ROW_HIGHLIGHT | Wx::wxTR_HAS_BUTTONS
| Wx::wxTR_LINES_AT_ROOT | Wx::wxBORDER_NONE
);
# Files that must be skipped
$self->{CACHED} = {};
# Selected item of each project
$self->{current_item} = {};
# Create the image list
my $images = Wx::ImageList->new( 16, 16 );
$self->{file_types} = {
upper => $images->Add(
Wx::ArtProvider::GetBitmap( 'wxART_GO_DIR_UP', 'wxART_OTHER_C', [ 16, 16 ] ),
),
folder => $images->Add(
Wx::ArtProvider::GetBitmap( 'wxART_FOLDER', 'wxART_OTHER_C', [ 16, 16 ] ),
),
package => $images->Add(
Wx::ArtProvider::GetBitmap( 'wxART_NORMAL_FILE', 'wxART_OTHER_C', [ 16, 16 ] ),
),
};
$self->AssignImageList($images);
# Set up the events
Wx::Event::EVT_TREE_ITEM_ACTIVATED(
$self, $self,
\&_on_tree_item_activated
);
#
# Wx::Event::EVT_SET_FOCUS(
# $self,
# sub {
# $_[0]->parent->refresh;
# },
# );
#
# Wx::Event::EVT_TREE_ITEM_MENU(
# $self, $self,
# \&_on_tree_item_menu,
# );
#
# Wx::Event::EVT_TREE_SEL_CHANGED(
# $self, $self,
# \&_on_tree_sel_changed,
# );
#
# Wx::Event::EVT_TREE_ITEM_EXPANDING(
# $self, $self,
# \&_on_tree_item_expanding,
# );
# who cares?
# Wx::Event::EVT_TREE_ITEM_COLLAPSING(
# $self, $self,
# \&_on_tree_item_collapsing,
# );
# Wx::Event::EVT_TREE_END_LABEL_EDIT(
# $self, $self,
# \&_on_tree_end_label_edit,
# );
# No dragonslop
# Wx::Event::EVT_TREE_BEGIN_DRAG(
# $self, $self,
# \&_on_tree_begin_drag,
# );
#
# Wx::Event::EVT_TREE_END_DRAG(
# $self, $self,
# \&_on_tree_end_drag,
# );
# Set up the root
my $root = $self->AddRoot(
Wx::gettext('Swarm'),
-1, -1,
Wx::TreeItemData->new(
{
node => 'swarm',
type => 'folder',
}
),
);
$self->_update_root_data;
# Ident to sub nodes
$self->SetIndent(10);
return $self;
}
# Returns the Directory Panel object reference
sub parent {
$_[0]->GetParent;
}
# Traverse to the search widget
sub search {
$_[0]->GetParent->search;
}
# Returns the main object reference
sub main {
$_[0]->GetParent->main;
}
sub current {
Padre::Current->new( main => $_[0]->main );
}
# Updates the gui if needed
# TODO refresh should be passed the node to refresh
# and maintain the expand/collapse state of the control
sub refresh {
my $self = shift;
# Gets Root node
my $root = $self->GetRootItem;
# Lock the gui here to make the updates look slicker
# The locker holds the gui freeze until the update is done.
my $lock = $self->main->lock('UPDATE');
$self->_update_root_data;
# Checks expanded sub folders and its content recursively
#$_update_subdirs( $self, $root );
}
sub plugin { Padre::Plugin::Swarm->instance }
# Updates root nodes data to the current project
# Called when turned beteween projects
use Data::Dumper;
sub _update_root_data {
my $self = shift;
# Updates Root node data
my $root = $self->GetRootItem;
$self->DeleteChildren($root);
my $data = $self->GetPlData($root);
my $geo = $self->plugin->geometry;
foreach my $user ( $geo->get_users ) {
my $user_node =
$self->AppendItem( $root, $user , -1 , -1 ,
Wx::TreeItemData->new( { type => 'user' , node=>$user })
);
my @resources = $geo->graph->successors($user);
foreach my $resource ( @resources ) {
$self->AppendItem(
$user_node , $resource , -1 , -1 ,
Wx::TreeItemData->new(
{ type =>'editor' , resource=>$resource }
)
);
}
}
}
sub _list_resources {
my $self = shift;
my $node = shift;
my $node_data = $self->GetPlData($node);
my @nodes = $self->plugin->geometry->get_successors( $node_data );
# Delete node children and populates it again
$self->DeleteChildren($node);
foreach my $each (@nodes) {
my $new_elem = $self->AppendItem(
$node,
$each->{name},
$self->{file_types}->{ $each->{type} },
-1,
Wx::TreeItemData->new(
{ name => $each->{name},
dir => $each->{dir},
type => $each->{type},
}
)
);
if ( $each->{type} eq 'folder' ) {
$self->SetItemHasChildren( $new_elem, 1 );
}
}
}
# Runs thought a directory content recursively looking if each EXPANDED item #
# has changed and updates it #
sub _update_subnodes {
my ( $self, $root ) = @_;
my $parent = $self->parent;
my $plugin = $self->plugin;
my $geometry = $plugin->geometry;
my $node = $root->GetData;
my @children = $geometry->successors( $node );
my $new_root = $self->AppendItem( $root , "$node" );
$self->AppendItem( $new_root, "$_" ) for @children;
}
# Action that must be executed when a item is activated
# Called when the item is actived
sub _on_tree_item_activated {
my ( $self, $event ) = @_;
my $parent = $self->parent;
my $node = $event->GetItem;
my $node_data = $self->GetPlData($node);
# If its a folder expands/collapses it and returns
# or makes it the current project folder, depending
# of the mode view
if ( $node_data->{type} eq 'folder' ) {
$self->Toggle($node);
}
if ($node_data->{type} eq 'editor' ) {
## Another FIXME!
$self->plugin->global->transport->send(
{ type=>'gimme',
resource => $node_data->{resource} }
);
}
# Open the clicked resource
return;
}
# Caches the item path as current selected item
# Called when a item is selected
sub _on_tree_sel_changed {
my ( $self, $event ) = @_;
return if not $self->parent->can('project_dir');
my $node_data = $self->GetPlData( $event->GetItem );
# Caches the item path
$self->{current_item}->{ $self->parent->project_dir } =
File::Spec->catfile( $node_data->{dir}, $node_data->{name} );
}
# Expands the node and loads its content.
# Called when a folder is expanded.
sub _on_tree_item_expanding {
my ( $self, $event ) = @_;
my $node = $event->GetItem;
my $node_data = $self->GetPlData($node);
# Returns if a search is being done (expands only the browser listing)
return if !defined( $self->search );
return if $self->search->{in_use}->{ $self->parent->project_dir };
# The item complete path
my $path = File::Spec->catfile( $node_data->{dir}, $node_data->{name} );
# Cache the expanded state of the node
$self->{CACHED}->{ $self->parent->project_dir }->{Expanded}->{$path} = 1;
# Updates the node content if it changed or has no child
if ( $self->_updated_dir($path) or !$self->GetChildrenCount($node) ) {
$self->_list_dir($node);
}
}
# Deletes nodes Expanded cache param.
# Called when a folder is collapsed.
sub _on_tree_item_collapsing {
my ( $self, $event ) = @_;
my $node = $event->GetItem;
my $node_data = $self->GetPlData($node);
my $project_dir = $self->parent->project_dir;
# If it is the Root node, set Expanded to 0
if ( $node == $self->GetRootItem ) {
$self->{CACHED}->{$project_dir}->{Expanded}->{$project_dir} = 0;
return;
}
# Deletes cache expanded state of the node
delete $self->{CACHED}->{$project_dir}->{Expanded}
->{ File::Spec->catfile( $node_data->{dir}, $node_data->{name} ) };
}
# If the item is not the root node let it to be dragged.
# Called when a item is dragged.
sub _on_tree_begin_drag {
my ( $self, $event ) = @_;
my $node = $event->GetItem;
my $node_data = $self->GetPlData($node);
# Only drags if it's not the Root node
# and if it's not the upper item
if ( $node != $self->GetRootItem
and $node_data->{type} ne 'upper' )
{
$self->{dragged_item} = $node;
$event->Allow;
}
}
# If dragged to a different folder, tries to move (renaming) it to the new
# folder.
# Called just after the item is dragged.
sub _on_tree_end_drag {
my ( $self, $event ) = @_;
my $node = $event->GetItem;
my $node_data = $self->GetPlData($node);
# If drops to a file, the new destination will be it's folder
if ( $node->IsOk and ( !$self->ItemHasChildren($node) and $node_data->{type} ne 'upper' ) ) {
$node = $self->GetItemParent($node);
}
# Returns if the target node doesn't exists
return unless $node->IsOk;
# Gets dragged and target nodes data
my $new_data = $self->GetPlData($node);
my $old_data = $self->GetPlData( $self->{dragged_item} );
# Returns if the target is the file parent
my $from = $old_data->{dir};
my $to = File::Spec->catfile( $new_data->{dir}, $new_data->{name} );
return if $from eq $to;
# The file complete name (path and its name) before and after the move
my $old_file = File::Spec->catfile( $old_data->{dir}, $old_data->{name} );
my $new_file = File::Spec->catfile( $to, $old_data->{name} );
# Alerts if there is a file with the same name in the target
if ( -e $new_file ) {
Wx::MessageBox(
Wx::gettext('A file with the same name already exists in this directory'),
Wx::gettext('Error'),
Wx::wxOK | Wx::wxCENTRE | Wx::wxICON_ERROR
);
return;
}
# Pops up a menu to confirm the
# action do be done
my $menu = Wx::Menu->new;
# Move file or directory
my $menu_mv = $menu->Append(
-1,
Wx::gettext('Move here')
);
Wx::Event::EVT_MENU(
$self, $menu_mv,
sub { $self->_rename_or_move( $old_file, $new_file ) }
);
# Copy file
unless ( -d $old_file ) {
my $menu_cp = $menu->Append(
-1,
Wx::gettext('Copy here')
);
Wx::Event::EVT_MENU(
$self, $menu_cp,
sub { $self->_copy( $old_file, $new_file ) }
);
}
# Cancel action
$menu->AppendSeparator();
my $menu_cl = $menu->Append(
-1,
Wx::gettext('Cancel')
);
# Pops up the context menu
my $x = $event->GetPoint->x;
my $y = $event->GetPoint->y;
$self->PopupMenu( $menu, $x, $y );
}
# Shows up a context menu above an item with its controls
# the file if don't.
# Called when a item context menu is requested.
sub _on_tree_item_menu {
my ( $self, $event ) = @_;
my $node = $event->GetItem;
my $node_data = $self->GetPlData($node);
# Do not show if it is the upper item
return if defined( $node_data->{type} ) and ( $node_data->{type} eq 'upper' );
$node_data->{type} ||= ''; # Defined but empty
my $menu = Wx::Menu->new;
my $selected_dir = $node_data->{dir};
my $selected_path = File::Spec->catfile( $node_data->{dir}, $node_data->{name} );
# Default action - same when the item is activated
my $default = $menu->Append(
-1,
Wx::gettext( $node_data->{type} eq 'folder' ? 'Open Folder' : 'Open File' )
);
Wx::Event::EVT_MENU(
$self, $default,
sub { $self->_on_tree_item_activated($event) }
);
Wx::Event::EVT_MENU(
$self,
$menu->Append( -1, Wx::gettext('Open In File Browser') ),
sub {
#Open the current node in file browser
require Padre::Wx::Directory::OpenInFileBrowserAction;
Padre::Wx::Directory::OpenInFileBrowserAction->new->open_in_file_browser($selected_path);
}
);
$menu->AppendSeparator();
# Rename and/or move the item
my $rename = $menu->Append( -1, Wx::gettext('Rename / Move') );
Wx::Event::EVT_MENU(
$self, $rename,
sub {
$self->EditLabel($node);
},
);
# Move item to trash
# Note: File::Remove->trash() only works on Mac
# Please see ticket:553 (http://padre.perlide.org/trac/ticket/553)
if ( Padre::Constant::MAC or Padre::Constant::WIN32 ) {
my $trash = $menu->Append( -1, Wx::gettext('Move to trash') );
Wx::Event::EVT_MENU(
$self, $trash,
sub {
eval {
if (Padre::Constant::WIN32)
{
# WIN32
require Padre::Util::Win32;
Padre::Util::Win32::Recycle($selected_path);
} else {
# MAC
require File::Remove;
File::Remove->trash($selected_path);
}
};
if ($@) {
my $error_msg = $@;
Wx::MessageBox(
$error_msg, Wx::gettext('Error'),
Wx::wxOK | Wx::wxCENTRE | Wx::wxICON_ERROR
);
}
return;
},
);
}
# Delete item
my $delete = $menu->Append( -1, Wx::gettext('Delete') );
Wx::Event::EVT_MENU(
$self, $delete,
sub {
my $dialog = Wx::MessageDialog->new(
$self,
Wx::gettext('Are you sure you want to delete this item?') . $/ . $selected_path,
Wx::gettext('Delete'),
Wx::wxYES_NO | Wx::wxICON_QUESTION | Wx::wxCENTRE
);
return if $dialog->ShowModal == Wx::wxID_NO;
eval {
require File::Remove;
File::Remove->remove($selected_path);
};
if ($@) {
my $error_msg = $@;
Wx::MessageBox(
$error_msg, Wx::gettext('Error'),
Wx::wxOK | Wx::wxCENTRE | Wx::wxICON_ERROR
);
}
return;
},
);
# ?????
if ( defined $node_data->{type} and ( $node_data->{type} eq 'modules' or $node_data->{type} eq 'pragmata' ) ) {
my $pod = $menu->Append( -1, Wx::gettext("Open &Documentation") );
Wx::Event::EVT_MENU(
$self, $pod,
sub {
# TO DO Fix this wasting of objects (cf. Padre::Wx::Menu::Help)
require Padre::Wx::DocBrowser;
my $help = Padre::Wx::DocBrowser->new;
$help->help( $node_data->{name} );
$help->SetFocus;
$help->Show(1);
return;
},
);
}
$menu->AppendSeparator();
# Shows / Hides hidden files - applied to each directory
my $hiddenFiles = $menu->AppendCheckItem( -1, Wx::gettext('Show hidden files') );
my $applies_to_node = $node;
my $applies_to_path = $selected_path;
if ( $node_data->{type} ne 'folder' ) {
$applies_to_path = $selected_dir;
$applies_to_node = $self->GetParent($node);
}
my $cached = defined($applies_to_path) ? \%{ $self->{CACHED}->{$applies_to_path} } : undef;
my $show = $cached->{ShowHidden};
$hiddenFiles->Check($show);
Wx::Event::EVT_MENU(
$self,
$hiddenFiles,
sub {
$cached->{ShowHidden} = !$show;
$self->_list_dir($applies_to_node);
},
);
# Updates the directory listing
my $reload = $menu->Append( -1, Wx::gettext('Reload') );
Wx::Event::EVT_MENU(
$self, $reload,
sub {
delete $self->{CACHED}->{ $self->GetPlData($node)->{dir} }->{Change};
}
);
# Pops up the context menu
my $x = $event->GetPoint->x;
my $y = $event->GetPoint->y;
$self->PopupMenu( $menu, $x, $y );
return;
}
1;
# Copyright 2008-2010 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.