package Padre::Wx::VCS;
use 5.008;
use strict;
use warnings;
use Padre::Feature ();
use Padre::Role::Task ();
use Padre::Wx ();
use Padre::Wx::Util ();
use Padre::Wx::Role::Idle ();
use Padre::Wx::Role::View ();
use Padre::Wx::FBP::VCS ();
use Padre::Task::VCS ();
use Padre::Logger;
our $VERSION = '0.96';
our @ISA = qw{
Padre::Role::Task
Padre::Wx::Role::Idle
Padre::Wx::Role::View
Padre::Wx::FBP::VCS
};
use constant {
RED => Wx::Colour->new('red'),
DARK_GREEN => Wx::Colour->new( 0x00, 0x90, 0x00 ),
BLUE => Wx::Colour->new('blue'),
GRAY => Wx::Colour->new('gray'),
BLACK => Wx::Colour->new('black'),
};
# Constructor
sub new {
my $class = shift;
my $main = shift;
my $panel = shift || $main->right;
my $self = $class->SUPER::new($panel);
# Set the bitmap button icons
$self->{add}->SetBitmapLabel( Padre::Wx::Icon::find('actions/list-add') );
$self->{delete}->SetBitmapLabel( Padre::Wx::Icon::find('actions/list-remove') );
$self->{update}->SetBitmapLabel( Padre::Wx::Icon::find('actions/stock_update-data') );
$self->{commit}->SetBitmapLabel( Padre::Wx::Icon::find('actions/document-save') );
$self->{revert}->SetBitmapLabel( Padre::Wx::Icon::find('actions/edit-undo') );
$self->{refresh}->SetBitmapLabel( Padre::Wx::Icon::find('actions/view-refresh') );
# Set up column sorting
$self->{sort_column} = 0;
$self->{sort_desc} = 1;
# Setup columns
my @column_headers = (
Wx::gettext('Status'),
Wx::gettext('Path'),
Wx::gettext('Author'),
Wx::gettext('Revision'),
);
my $index = 0;
for my $column_header (@column_headers) {
$self->{list}->InsertColumn( $index++, $column_header );
}
# Column ascending/descending image
my $images = Wx::ImageList->new( 16, 16 );
$self->{images} = {
asc => $images->Add(
Wx::ArtProvider::GetBitmap(
'wxART_GO_UP',
'wxART_OTHER_C',
[ 16, 16 ],
),
),
desc => $images->Add(
Wx::ArtProvider::GetBitmap(
'wxART_GO_DOWN',
'wxART_OTHER_C',
[ 16, 16 ],
),
),
};
$self->{list}->AssignImageList( $images, Wx::IMAGE_LIST_SMALL );
# Tidy the list
Padre::Wx::Util::tidy_list( $self->{list} );
# Add the idle-delayed event handler
Wx::Event::EVT_LIST_ITEM_ACTIVATED(
$self,
$self->{list},
sub {
$_[0]->idle_method( item_activated => $_[1]->GetIndex );
},
);
# Update the checkboxes with their corresponding values in the
# configuration
my $config = $main->config;
$self->{show_normal}->SetValue( $config->vcs_normal_shown );
$self->{show_unversioned}->SetValue( $config->vcs_unversioned_shown );
$self->{show_ignored}->SetValue( $config->vcs_ignored_shown );
# Hide vcs command buttons at startup
$self->{commit}->Hide;
$self->{add}->Hide;
$self->{delete}->Hide;
$self->{revert}->Hide;
$self->{update}->Hide;
return $self;
}
######################################################################
# Padre::Wx::Role::View Methods
sub view_panel {
return 'right';
}
sub view_label {
Wx::gettext('Version Control');
}
sub view_close {
$_[0]->main->show_vcs(0);
}
sub view_start {
}
sub view_stop {
my $self = shift;
# Clear out any state and tasks
$self->task_reset;
$self->clear;
return;
}
#####################################################################
# Event Handlers
sub on_refresh_click {
$_[0]->main->vcs->refresh( $_[0]->current );
}
#####################################################################
# General Methods
# Clear everything...
sub clear {
my $self = shift;
$self->{list}->DeleteAllItems;
$self->_show_command_bar(0);
return;
}
# Nothing to implement here
sub relocale {
return;
}
sub refresh {
my $self = shift;
my $current = shift or return;
my $command = shift || Padre::Task::VCS::VCS_STATUS;
my $document = $current->document;
# Abort any in-flight checks
$self->task_reset;
# Flush old results
$self->clear;
# Do not display anything where there is no open documents
return unless $document;
# Shortcut if there is nothing in the document to do
if ( $document->is_unused ) {
$self->{status}->SetValue( Wx::gettext('Current file is not saved in a version control system') );
return;
}
# Retrieve project version control system
my $vcs = $document->project->vcs;
# No version control system?
unless ($vcs) {
$self->{status}->SetValue( Wx::gettext('Current file is not in a version control system') );
return;
}
# Not supported VCS check
if ( $vcs ne Padre::Constant::SUBVERSION and $vcs ne Padre::Constant::GIT ) {
$self->{status}->SetValue( sprintf( Wx::gettext('%s version control is not currently available'), $vcs ) );
return;
}
# Start a background VCS status task
$self->task_request(
task => 'Padre::Task::VCS',
command => $command,
document => $document,
);
return 1;
}
sub task_finish {
my $self = shift;
my $task = shift;
$self->{model} = Params::Util::_ARRAY0( $task->{model} ) or return;
$self->{vcs} = $task->{vcs} or return;
$self->render;
}
sub render {
my $self = shift;
# Clear if needed. Please note that this is needed
# for sorting
$self->clear;
return unless $self->{model};
# Subversion status codes
my %SVN_STATUS = (
' ' => { name => Wx::gettext('Normal') },
'A' => { name => Wx::gettext('Added') },
'D' => { name => Wx::gettext('Deleted') },
'M' => { name => Wx::gettext('Modified') },
'C' => { name => Wx::gettext('Conflicted') },
'I' => { name => Wx::gettext('Ignored') },
'?' => { name => Wx::gettext('Unversioned') },
'!' => { name => Wx::gettext('Missing') },
'~' => { name => Wx::gettext('Obstructed') }
);
# GIT status code
my %GIT_STATUS = (
' ' => { name => Wx::gettext('Unmodified') },
'M' => { name => Wx::gettext('Modified') },
'A' => { name => Wx::gettext('Added') },
'D' => { name => Wx::gettext('Deleted') },
'R' => { name => Wx::gettext('Renamed') },
'C' => { name => Wx::gettext('Copied') },
'U' => { name => Wx::gettext('Updated but unmerged') },
'?' => { name => Wx::gettext('Unversioned') },
);
my %vcs_status = $self->{vcs} eq Padre::Constant::SUBVERSION ? %SVN_STATUS : %GIT_STATUS;
# Add a zero count key for VCS status hash
$vcs_status{$_}->{count} = 0 for keys %vcs_status;
# Retrieve the state of the checkboxes
my $show_normal = $self->{show_normal}->IsChecked ? 1 : 0;
my $show_unversioned = $self->{show_unversioned}->IsChecked ? 1 : 0;
my $show_ignored = $self->{show_ignored}->IsChecked ? 1 : 0;
my $index = 0;
my $list = $self->{list};
$self->_sort_model(%vcs_status);
my $model = $self->{model};
my $model_index = 0;
for my $rec (@$model) {
my $status = $rec->{status};
my $path_status = $vcs_status{$status};
if ( defined $path_status ) {
if ( $show_normal or $status ne ' ' ) {
if ( $show_unversioned or $status ne '?' ) {
if ( $show_ignored or $status ne 'I' ) {
# Add a version control path to the list
$list->InsertImageStringItem( $index, $path_status->{name}, -1 );
$list->SetItemData( $index, $model_index );
$list->SetItem( $index, 1, $rec->{path} );
my $color;
if ( $status eq ' ' ) {
$color = DARK_GREEN;
} elsif ( $status eq 'A' or $status eq 'D' ) {
$color = RED;
} elsif ( $status eq 'M' ) {
$color = BLUE;
} elsif ( $status eq 'I' ) {
$color = GRAY;
} else {
$color = BLACK;
}
$list->SetItemTextColour( $index, $color );
$list->SetItem( $index, 2, $rec->{author} );
$list->SetItem( $index++, 3, $rec->{revision} );
}
}
}
}
$path_status->{count}++;
$model_index++;
}
# Select the first item
if ( $list->GetItemCount > 0 ) {
$list->SetItemState( 0, Wx::LIST_STATE_SELECTED, Wx::LIST_STATE_SELECTED );
}
# Show Subversion statistics
my $message = '';
for my $status ( sort keys %vcs_status ) {
my $vcs_status_obj = $vcs_status{$status};
next if $vcs_status_obj->{count} == 0;
if ( length($message) > 0 ) {
$message .= Wx::gettext(', ');
}
$message .= sprintf( '%s=%d', $vcs_status_obj->{name}, $vcs_status_obj->{count} );
}
$self->{status}->SetValue($message);
$self->_show_command_bar( $list->GetItemCount > 0 )
if $self->main->config->vcs_enable_command_bar;
# Update the list sort image
$self->set_icon_image( $self->{sort_column}, $self->{sort_desc} );
# Tidy the list
Padre::Wx::Util::tidy_list($list);
return 1;
}
sub _show_command_bar {
my ( $self, $shown ) = @_;
$self->{commit}->Show($shown);
$self->{add}->Show($shown);
$self->{delete}->Show($shown);
$self->{revert}->Show($shown);
$self->{update}->Show($shown);
$self->Layout;
}
sub _sort_model {
my ( $self, %vcs_status ) = @_;
my @model = @{ $self->{model} };
if ( $self->{sort_column} == 0 ) {
# Sort by status
@model = sort { $vcs_status{ $a->{status} }{name} cmp $vcs_status{ $b->{status} }{name} } @model;
} elsif ( $self->{sort_column} == 1 ) {
# Sort by path
@model = sort { $a->{path} cmp $b->{path} } @model;
} elsif ( $self->{sort_column} == 2 ) {
# Sort by author
@model = sort { $a->{author} cmp $b->{author} } @model;
} elsif ( $self->{sort_column} == 3 ) {
# Sort by revision
@model = sort { $a->{revision} cmp $b->{revision} } @model;
}
if ( $self->{sort_desc} ) {
# reverse the sorting
@model = reverse @model;
}
$self->{model} = \@model;
}
# Called when a version control list column is clicked
sub on_list_column_click {
my ( $self, $event ) = @_;
my $column = $event->GetColumn;
my $prevcol = $self->{sort_column};
my $reversed = $self->{sort_desc};
$reversed = $column == $prevcol ? !$reversed : 0;
$self->{sort_column} = $column;
$self->{sort_desc} = $reversed;
# Reset the previous column sort image
$self->set_icon_image( $prevcol, -1 );
$self->render;
return;
}
sub set_icon_image {
my ( $self, $column, $image_index ) = @_;
my $item = Wx::ListItem->new;
$item->SetMask(Wx::LIST_MASK_IMAGE);
$item->SetImage($image_index);
$self->{list}->SetColumn( $column, $item );
return;
}
sub item_activated {
my $self = shift;
my $index = shift;
my $data = $self->{list}->GetItemData($index);
my $rec = $self->{model}->[$data];
my $filename = $rec->{fullpath};
my $main = $self->main;
eval {
# Try to open the file now
if ( my $id = $main->editor_of_file($filename) ) {
my $page = $main->notebook->GetPage($id);
$page->SetFocus;
} else {
$main->setup_editor($filename);
}
};
$main->error( Wx::gettext('Error while trying to perform Padre action') ) if $@;
}
# Called when "Show normal" checkbox is clicked
sub on_show_normal_click {
my ( $self, $event ) = @_;
# Save to configuration
my $config = $self->main->config;
$config->apply( vcs_normal_shown => $event->IsChecked ? 1 : 0 );
$config->write;
# refresh list
$self->render;
}
# Called when "Show unversioned" checkbox is clicked
sub on_show_unversioned_click {
my ( $self, $event ) = @_;
# Save to configuration
my $config = $self->main->config;
$config->apply( vcs_unversioned_shown => $event->IsChecked ? 1 : 0 );
$config->write;
# refresh list
$self->render;
}
# Called when "Show ignored" checkbox is clicked
sub on_show_ignored_click {
my ( $self, $event ) = @_;
# Save to configuration
my $config = $self->main->config;
$config->apply( vcs_ignored_shown => $event->IsChecked ? 1 : 0 );
$config->write;
# refresh list
$self->render;
}
# Called when "Commit" button is clicked
sub on_commit_click {
my $self = shift;
my $main = $self->main;
return
unless $main->yes_no(
Wx::gettext("Do you want to commit?"),
Wx::gettext('Commit file/directory to repository?')
);
$main->vcs->refresh( $self->current, Padre::Task::VCS::VCS_COMMIT );
}
# Called when "Add" button is clicked
sub on_add_click {
my $self = shift;
my $main = $self->main;
my $list = $self->{list};
my $selected_index = $list->GetNextItem( -1, Wx::LIST_NEXT_ALL, Wx::LIST_STATE_SELECTED );
return if $selected_index == -1;
my $rec = $self->{model}->[ $list->GetItemData($selected_index) ] or return;
my $filename = $rec->{fullpath};
return
unless $main->yes_no(
sprintf( Wx::gettext("Do you want to add '%s' to your repository"), $filename ),
Wx::gettext('Add file to repository?')
);
$main->vcs->refresh( $self->current, Padre::Task::VCS::VCS_ADD );
}
# Called when "Delete" checkbox is clicked
sub on_delete_click {
my $self = shift;
my $main = $self->main;
my $list = $self->{list};
my $selected_index = $list->GetNextItem( -1, Wx::LIST_NEXT_ALL, Wx::LIST_STATE_SELECTED );
return if $selected_index == -1;
my $rec = $self->{model}->[ $list->GetItemData($selected_index) ] or return;
my $filename = $rec->{fullpath};
return
unless $main->yes_no(
sprintf( Wx::gettext("Do you want to delete '%s' from your repository"), $filename ),
Wx::gettext('Delete file from repository??')
);
$main->vcs->refresh( $self->current, Padre::Task::VCS::VCS_DELETE );
}
# Called when "Update" button is clicked
sub on_update_click {
my $self = shift;
my $main = $self->main;
$main->vcs->refresh( $main->current, Padre::Task::VCS::VCS_UPDATE );
}
# Called when "Revert" button is clicked
sub on_revert_click {
my $self = shift;
my $main = $self->main;
my $list = $self->{list};
my $selected_index = $list->GetNextItem( -1, Wx::LIST_NEXT_ALL, Wx::LIST_STATE_SELECTED );
return if $selected_index == -1;
my $rec = $self->{model}->[ $list->GetItemData($selected_index) ] or return;
my $filename = $rec->{fullpath};
return
unless $main->yes_no(
sprintf( Wx::gettext("Do you want to revert changes to '%s'"), $filename ),
Wx::gettext('Revert changes?')
);
$main->vcs->refresh( $self->current, Padre::Task::VCS::VCS_REVERT );
}
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.