package Padre::Wx::Dialog::PluginManager; # The Plugin Manager GUI for Padre use 5.008; use strict; use warnings; use Carp (); use Padre::Wx (); use Padre::Wx::Icon (); our $VERSION = '0.50'; our @ISA = 'Wx::Dialog'; ###################################################################### # Constructor sub new { my $class = shift; my $parent = shift; my $manager = shift; # Create the basic object my $self = $class->SUPER::new( $parent, -1, Wx::gettext('Plugin Manager'), Wx::wxDefaultPosition, Wx::wxDefaultSize, Wx::wxDEFAULT_FRAME_STYLE, ); # Set some internal parameters $self->{sortcolumn} = 0; $self->{sortreverse} = 0; # Set basic dialog properties $self->SetIcon(Padre::Wx::Icon::PADRE); $self->SetMinSize( [ 750, 550 ] ); # Store plugin manager $self->{manager} = $manager; unless ( $manager->isa('Padre::PluginManager') ) { Carp::croak("Missing or invalid Padre::PluginManager object"); } # Dialog Controls # Create the plugin list $self->{list} = Wx::ListView->new( $self, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, Wx::wxLC_REPORT | Wx::wxLC_SINGLE_SEL, ); $self->{list}->InsertColumn( 0, Wx::gettext('Name') ); $self->{list}->InsertColumn( 1, Wx::gettext('Version') ); $self->{list}->InsertColumn( 2, Wx::gettext('Status') ); Wx::Event::EVT_LIST_ITEM_SELECTED( $self, $self->{list}, sub { shift->list_item_selected(@_); }, ); Wx::Event::EVT_LIST_ITEM_ACTIVATED( $self, $self->{list}, sub { shift->button_main; }, ); Wx::Event::EVT_LIST_COL_CLICK( $self, $self->{list}, sub { shift->list_col_click(@_); }, ); # Image List $self->{imagelist} = Wx::ImageList->new( 16, 16 ); $self->{list}->AssignImageList( $self->{imagelist}, Wx::wxIMAGE_LIST_SMALL, ); # Plugin Name Header $self->{label} = Wx::StaticText->new( $self, -1, Wx::gettext('Plugin Name'), ); my $font = $self->{label}->GetFont; $font->SetWeight(Wx::wxFONTWEIGHT_BOLD); $font->SetPointSize( $font->GetPointSize + 4 ); $self->{label}->SetFont($font); # Plugin Documentation HTML Window require Padre::Wx::HtmlWindow; $self->{whtml} = Wx::HtmlWindow->new($self); # Enable/Disable Button $self->{button_main} = Wx::Button->new( $self, Wx::wxID_OK, Wx::gettext('Enable'), ); Wx::Event::EVT_BUTTON( $self, $self->{button_main}, sub { $_[0]->button_main; }, ); # Preferences Button $self->{button_preferences} = Wx::Button->new( $self, -1, Wx::gettext('Preferences'), ); Wx::Event::EVT_BUTTON( $self, $self->{button_preferences}, sub { $_[0]->button_preferences; }, ); # Close Button $self->{button_close} = Wx::Button->new( $self, Wx::wxID_CANCEL, Wx::gettext('Close'), ); Wx::Event::EVT_BUTTON( $self, $self->{button_close}, sub { $_[0]->button_close; }, ); # Dialog Layout # Horizontal button sizer my $buttons = Wx::BoxSizer->new(Wx::wxHORIZONTAL); $buttons->AddStretchSpacer; $buttons->Add( $self->{button_main}, 0, Wx::wxALL, 1 ); $buttons->Add( $self->{button_preferences}, 0, Wx::wxALL, 1 ); $buttons->AddStretchSpacer; $buttons->Add( $self->{button_close}, 0, Wx::wxALL, 1 ); $buttons->AddStretchSpacer; # Horizontal plugin name positioning my $header = Wx::BoxSizer->new(Wx::wxHORIZONTAL); $header->AddStretchSpacer; $header->Add( $self->{label}, 0, Wx::wxEXPAND | Wx::wxALIGN_CENTER, 1 ); $header->AddStretchSpacer; # Vertical layout of the right hand side my $right = Wx::BoxSizer->new(Wx::wxVERTICAL); $right->Add( $header, 0, Wx::wxALL | Wx::wxEXPAND, 1 ); $right->Add( $self->{whtml}, 1, Wx::wxALL | Wx::wxALIGN_TOP | Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxEXPAND, 1 ); $right->Add( $buttons, 0, Wx::wxALL | Wx::wxEXPAND, 1 ); # Main sizer my $sizer = Wx::BoxSizer->new(Wx::wxHORIZONTAL); $sizer->Add( $self->{list}, 0, Wx::wxALL | Wx::wxEXPAND, 1 ); $sizer->Add( $right, 1, Wx::wxALL | Wx::wxEXPAND, 1 ); # Tune the size and position it appears $self->SetSizer($sizer); $self->Fit; $self->CentreOnParent; return $self; } # -- public methods sub show { my $self = shift; $self->_refresh_list; # select first item in the list. we don't need to test if # there's at least a plugin, since there will always be # 'my plugin' my $item = $self->{list}->GetItem(0); $item->SetState(Wx::wxLIST_STATE_SELECTED); $self->{list}->SetItem($item); $self->Show; } # GUI Handlers # # $self->list_item_selected( $event ); # # handler called when a list item has been selected. it will in turn update # the right part of the frame. # # $event is a Wx::ListEvent. # sub list_item_selected { my $self = shift; my $event = shift; my $fullname = $event->GetLabel; my $module = $self->{plugin_class}->{$fullname}; my $plugin = $self->{manager}->plugins->{$module}; $self->{plugin} = $plugin; # storing selected plugin $self->{row} = $event->GetIndex; # storing selected row # Updating plugin name in right pane $self->{label}->SetLabel( $plugin->plugin_name ); # Update plugin documentation require Padre::DocBrowser; my $browser = Padre::DocBrowser->new; my $class = $plugin->class; my $doc = $browser->resolve($class); my $output = eval { $browser->browse($doc) }; my $html = $@ ? sprintf( Wx::gettext("Error loading pod for class '%s': %s"), $class, $@ ) : $output->body; $self->{whtml}->SetPage($html); # Update buttons $self->_update_plugin_state; # force window to recompute layout. indeed, changes are that plugin # name has a different length, and thus should be recentered. $self->Layout; } # # $self->list_col_click; # # handler called when a column has been clicked, to reorder the list. # sub list_col_click { my $self = shift; my $event = shift; my $column = $event->GetColumn; my $prevcol = $self->{sortcolumn}; my $reversed = $self->{sortreverse}; $reversed = $column == $prevcol ? !$reversed : 0; $self->{sortcolumn} = $column; $self->{sortreverse} = $reversed; $self->_refresh_list; } # # $self->button_main; # # handler called when the first button has been clicked. # sub button_main { my $self = shift; # find method to call my $method = $self->{action}; # call method $self->$method(); } # # $self->button_preferences; # # handler called when the preferences button has been clicked. # sub button_preferences { $_[0]->{plugin}->object->plugin_preferences; } # # $self->button_close; # # handler called when the close button has been clicked. # sub button_close { $_[0]->Destroy; } # # $self->_plugin_disable; # # Disable plugin, and update gui. # sub _plugin_disable { my $self = shift; my $plugin = $self->{plugin}; my $parent = $self->GetParent; # disable plugin $parent->Freeze; Padre::DB::Plugin->update_enabled( $plugin->class => 0 ); $self->{manager}->_plugin_disable( $plugin->class ); $parent->menu->refresh(1); $parent->Thaw; # Update plugin manager dialog to reflect new state $self->_update_plugin_state; } # # $self->_plugin_enable; # # Enable plugin, and update gui. # sub _plugin_enable { my $self = shift; my $plugin = $self->{plugin}; my $parent = $self->GetParent; # Enable plugin $parent->Freeze; Padre::DB::Plugin->update_enabled( $plugin->class => 1 ); $self->{manager}->_plugin_enable( $plugin->class ); $parent->menu->refresh(1); $parent->Thaw; # Update plugin manager dialog to reflect new state $self->_update_plugin_state; } # # $self->show_error_message; # # show plugin error message, in an error dialog box. # sub show_error_message { my $self = shift; my $message = $self->{plugin}->errstr; my $title = Wx::gettext('Error'); Wx::MessageBox( $message, $title, Wx::wxOK | Wx::wxCENTER, $self ); } # # $dialog->_refresh_list; # # refresh list of plugins and their associated state. list is sorted # according to current sort criterion. # sub _refresh_list { $DB::single = $DB::single = 1; my $self = shift; # Clear image list & fill it again $self->{imagelist}->RemoveAll; # Default plugin icon $self->{imagelist}->Add( Padre::Wx::Icon::find('status/padre-plugin') ); my %icon = ( plugin => 0 ); # Plugin status my $i = 0; foreach my $name ( qw{ enabled disabled error crashed incompatible } ) { $self->{imagelist}->Add( Padre::Wx::Icon::find("status/padre-plugin-$name") ); $icon{$name} = ++$i; } # Get list of plugins, and sort it. Note that $self->{manager}->plugins # names is sorted (with my plugin first), and that perl sort is now # stable: sorting on another criterion will keep the alphabetical order # if new criterion is not enough. my $plugins = $self->{manager}->plugins; my @plugins = map { $plugins->{$_} } $self->{manager}->plugin_order; if ( $self->{sortcolumn} == 1 ) { no warnings; @plugins = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, version->new( $_->version || 0 ) ] } @plugins; } if ( $self->{sortcolumn} == 2 ) { @plugins = sort { $a->status cmp $b->status } @plugins; } if ( $self->{sortreverse} ) { @plugins = reverse @plugins; } # Clear plugin list & fill it again my $idx = -1; my %plugin_class = (); $self->{list}->DeleteAllItems; foreach my $plugin (@plugins) { $plugin_class{ $plugin->plugin_name } = $plugin->class; # Check if plugin is supplying its own icon my $position = 0; my $icon = $plugin->plugin_icon; if ( defined $icon ) { $self->{imagelist}->Add($icon); $position = $self->{imagelist}->GetImageCount - 1; } # Inserting the plugin in the list $self->{list}->InsertStringImageItem( ++$idx, $plugin->plugin_name, $position, ); $self->{list}->SetItem( $idx, 1, $plugin->version || '???' ); $self->{list}->SetItem( $idx, 2, $plugin->status_localized, $icon{ $plugin->status }, ); } # Store mapping of full plugin names / short plugin names $self->{plugin_class} = \%plugin_class; # Auto-resize columns foreach ( 0 .. 2 ) { $self->{list}->SetColumnWidth( $_, Wx::wxLIST_AUTOSIZE ); } # Making sure the list can show all columns my $width = 15; # Taking vertical scrollbar into account foreach ( 0 .. 2 ) { $width += $self->{list}->GetColumnWidth($_); } $self->{list}->SetMinSize( [ $width, -1 ] ); return; } # # $dialog->_update_plugin_state; # # Update button caption & state, as well as status icon in the list, # depending on the new plugin state. # sub _update_plugin_state { my $self = shift; my $plugin = $self->{plugin}; # my $list = $self->{list}; # my $item = $list->GetItem( $self->{row}, 2 ); # Updating buttons my $button_main = $self->{button_main}; my $button_preferences = $self->{button_preferences}; if ( $plugin->error ) { # Plugin is in error state $self->{action} = 'show_error_message'; $button_main->SetLabel( Wx::gettext('Show error message') ); $button_preferences->Disable; $self->{list}->SetItem( $self->{row}, 2, Wx::gettext('error'), 3 ); # $item->SetText( Wx::gettext('error') ); # $item->SetImage(3); # $list->SetItem($item); } elsif ( $plugin->incompatible ) { # Plugin is incompatible $self->{action} = 'show_error_message'; $button_main->SetLabel( Wx::gettext('Show error message') ); $button_preferences->Disable; $self->{list}->SetItem( $self->{row}, 2, Wx::gettext('incompatible'), 5 ); # $item->SetText( Wx::gettext('incompatible') ); # $item->SetImage(5); # $list->SetItem($item); } else { # Plugin is working... if ( $plugin->enabled ) { # ...and enabled $self->{action} = '_plugin_disable'; $button_main->SetLabel( Wx::gettext('Disable') ); $button_main->Enable; $self->{list}->SetItem( $self->{row}, 2, Wx::gettext('enabled'), 1 ); # $item->SetText( Wx::gettext('enabled') ); # $item->SetImage(1); # $list->SetItem($item); } elsif ( $plugin->can_enable ) { # ...and disabled $self->{action} = '_plugin_enable'; $button_main->SetLabel( Wx::gettext('Enable') ); $button_main->Enable; $self->{list}->SetItem( $self->{row}, 2, Wx::gettext('disabled'), 2 ); # $item->SetText( Wx::gettext('disabled') ); # $item->SetImage(2); # $list->SetItem($item); } else { # ...disabled but cannot be enabled $button_main->Disable; } # Updating preferences button if ( $plugin->object->can('plugin_preferences') ) { $button_preferences->Enable; } else { $button_preferences->Disable; } } # Update the list item # $self->_refresh_list; # force window to recompute layout. indeed, changes are that plugin # name has a different length, and thus should be recentered. $self->Layout; } 1; __END__ =pod =head1 NAME Padre::Wx::Dialog::PluginManager - Plugin manager dialog for Padre =head1 DESCRIPTION Padre will have a lot of plugins. First plugin manager was not taking this into account, and the first plugin manager window was too small & too crowded to show them all properly. This revamped plugin manager is now using a list control, and thus can show lots of plugins in an effective manner. Upon selection, the right pane will be updated with the plugin name & plugin documentation. Two buttons will allow to de/activate the plugin (or see plugin error message) and set plugin preferences. Double-clicking on a plugin in the list will de/activate it. =head1 PUBLIC API =head2 Constructor =over 4 =item * my $dialog = P::W::D::PM->new( $parent, $manager ) Create and return a new Wx dialog listing all the plugins. It needs a C<$parent> window and a C object that really handles Padre plugins under the hood. =back =head2 Public methods =over 4 =item * $dialog->show; Request the plugin manager dialog to be shown. It will be refreshed first with a current list of plugins with their state. =back =head1 COPYRIGHT & LICENSE Copyright 2008-2009 The Padre development team as listed in Padre.pm. This program is free software; you can redistribute it and/or modify it under the same terms as Perl 5 itself. =cut # Copyright 2008-2009 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.