# +-----------------------------------------------------------------------+ # | pLoader - a Perl photo uploader for Piwigo | # +-----------------------------------------------------------------------+ # | Copyright(C) 2008 Piwigo Team http://piwigo.org | # +-----------------------------------------------------------------------+ # | This program is free software; you can redistribute it and/or modify | # | it under the terms of the GNU General Public License as published by | # | the Free Software Foundation | # | | # | This program is distributed in the hope that it will be useful, but | # | WITHOUT ANY WARRANTY; without even the implied warranty of | # | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | # | General Public License for more details. | # | | # | You should have received a copy of the GNU General Public License | # | along with this program; if not, write to the Free Software | # | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | # | USA. | # +-----------------------------------------------------------------------+ package Uploader::GUI::wxFrameAUI; use strict; use Carp; use Wx; use Wx::DND; use Wx qw/ wxYES_NO wxYES wxICON_QUESTION wxITEM_NORMAL wxNullBitmap wxID_OK wxDEFAULT_FRAME_STYLE wxVERTICAL wxGROW wxBITMAP_TYPE_JPEG wxBITMAP_TYPE_GIF wxBITMAP_TYPE_PNG wxBITMAP_TYPE_ANY wxTB_FLAT wxSIZE wxWHITE wxBLACK wxID_CANCEL wxFD_OPEN wxFD_MULTIPLE wxLI_HORIZONTAL wxALIGN_CENTER_VERTICAL wxALL wxDefaultPosition wxDefaultSize wxTheApp wxIMAGE_LIST_NORMAL wxAUI_NB_TAB_MOVE wxAUI_NB_TAB_SPLIT wxNO_BORDER wxTE_MULTILINE wxTE_READONLY wxITEM_NORMAL wxCLIP_CHILDREN wxBORDER_NONE wxNullBitmap wxTR_MULTIPLE wxTR_EXTENDED wxTR_HIDE_ROOT wxTR_HAS_BUTTONS wxTR_EDIT_LABELS wxMAXIMIZE wxOK wxICON_EXCLAMATION wxICON_INFORMATION WXK_DELETE /; use base qw/Wx::Frame Class::Accessor::Fast/; use File::Spec; use Wx::Locale qw/:default/; use POSIX qw(ceil floor); require Win32 if($^O =~ /MSWin32/); my @properties = qw/ progressdlg upload_progressdlg imageviewer tree tree_root treeimglist tree_item_default topsizer pwg manager logwnd oldlogwnd htmlhome notebook categories imagelist image_preview image_prop_piwigo image_prop_exif image_prop_tags notebook_image_properties notebook_global_settings general_settings_panel resized_settings_panel thumbnail_settings_panel hd_settings_panel advanced_settings_panel watermark_settings_panel piwigo_properties exif_properties general_settings resized_settings thumbnail_settings hd_settings advanced_settings watermark_settings toolbar branding perspective current_imageviewer_index imageviewer_mnu tree_mnu imageviewer_select_multi frameLayout piwigo_tags image_tags /; __PACKAGE__->mk_accessors( @properties ); use Wx::Event qw/ EVT_MENU EVT_TREE_SEL_CHANGED EVT_TREE_END_LABEL_EDIT EVT_CLOSE EVT_LIST_END_LABEL_EDIT EVT_LIST_ITEM_SELECTED EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_RIGHT_CLICK EVT_TREE_ITEM_RIGHT_CLICK EVT_LIST_KEY_DOWN EVT_UPDATE_UI /; use Uploader::GUI::wxImageListCtrl; use Uploader::GUI::wxImageProcessingProgressDlg; use Uploader::GUI::wxChoiceFilteredPanel; use Uploader::GUI::wxImageReuploadDlg; use Wx::Html; use Uploader::GUI::wxImagePreview; use Uploader::GUI::wxHtmlWindow; use Data::Dumper; use Wx::AUI; use Storable; use Uploader::GUI::wxPropertyGridPanel; use utf8; $|=1; my $ID_TREE_CTX_MENU = 20000 ; my $ID_IMAGEVIEWER_CTX_MENU = 20100 ; sub new { my( $class, $params ) = @_; my( $self ) = $class->SUPER::new( undef, -1, $params->{title}, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE ); $self->perspective( $params->{perspective} ); $self->current_imageviewer_index( $params->{imageviewer_index}||0 ); $self->frameLayout( $params->{frameLayout} || { 'pX' => -1, 'pY' => -1, W => 800, H => 600 } ); $self->pwg( $params->{pwg} ); $self->imagelist( $params->{imagelist} ); # callback for GUI refresh : add thumbnail images to the imageviewer control $self->imagelist->SetNewFilesViewerRefreshCallback( sub { $self->SetNewFilesViewerRefresh(@_) } ); # callback for GUI refresh : progress dialog display of thumbnail image being created $self->imagelist->SetNewFilesProgressCallback( sub { $self->SetNewFilesProgress(@_) } ); $self->imagelist->SetNewFilesDisplayEndInfoCallback( sub { $self->SetNewFilesDisplayEndInfo(@_) } ); # callback for GUI refresh : remove thumbnail images from imageviewer control $self->imagelist->UploadImagesViewerCallback( sub { $self->UploadImagesViewerRefresh(@_) } ); $self->imagelist->ReuploadCallback( sub { Uploader::GUI::wxImageReuploadDlg->new( { title => gettext("Photo update management"), properties => { $main::ID_REUPLOAD_ACTION_FILES => { selection=>sub {$self->imagelist->reupload_action_files(@_)}, label=>gettext("What shall we do with files? (thumbnail, resized, high resolution)"), labels=>[ gettext("nothing"), gettext("replace"), ], }, $main::ID_REUPLOAD_ACTION_PROPERTIES => { selection=>sub{$self->imagelist->reupload_action_properties(@_)}, label=>gettext("What shall we do with single value properties?(caption, comment, author, create date)"), labels=>[ gettext("nothing"), gettext("fill if empty (only replace properties currently empty in Piwigo)"), gettext("replace"), ], }, $main::ID_REUPLOAD_ACTION_PROPERTIES_M => { selection=>sub{$self->imagelist->reupload_action_properties_m(@_)}, label=>gettext("What shall we do with multiple values properties? (categories, tags)"), labels=>[ gettext("nothing"), gettext("append (keep existing and add new)"), gettext("replace"), ], }, $main::ID_REUPLOAD_NOT_ASK => { value=>sub{$self->imagelist->reupload_not_ask(@_)}, label=>gettext("Do not ask me again"), }, $main::ID_REUPLOAD_TEXT => { label=>gettext("A least one photo has already been added in the past."), }, }, } )->ShowModal(); } ); # callback for GUI refresh : progress dialog display current uploaded image $self->imagelist->progress_thumbnail_refresh( sub { $self->UploadProgressThumbnailRefresh(@_) } ); $self->imagelist->progress_msg_refresh( sub { $self->UploadProgressMessageRefresh(@_) } ); $self->imagelist->progress_msg_details_refresh( sub { $self->UploadProgressMessageDetailsRefresh(@_) } ); $self->imagelist->progressbar_refresh( sub { $self->UploadProgressBarRefresh(@_) } ); $self->imagelist->progress_endinfo_refresh( sub { $self->UploadDisplayEndInfo(@_) } ); $self->imagelist->pwg( $self->pwg ); $self->imagelist->categories( [] ); $self->manager( Wx::AuiManager->new ); $self->manager->SetManagedWindow( $self ); $self->_set_setting_properties; $self->_initFrame; $self->_initMenus; $self->_initEventHandlers; $self->_initImgTypes; $self->imageviewer->SelectItem( $self->current_imageviewer_index ) if $self->imageviewer->GetItemCount; $self; } sub _initImgTypes { my ( $self ) = @_; $self->{IMGTYPE} = { 'jpg' => wxBITMAP_TYPE_JPEG, 'gif' => wxBITMAP_TYPE_GIF, 'png' => wxBITMAP_TYPE_PNG, }; } sub GetWxBitmapType { my ( $self, $type ) = @_; $self->{IMGTYPE}->{$type}; } sub _set_setting_properties { my ( $self ) = @_; $self->piwigo_properties( [ { label => gettext("Photo caption :"), value => sub { $self->imagelist->current_image->site_name(@_) }, frame_callback => sub { $self->imageviewer->Refresh() }, }, { label => gettext("Comment :"), value => sub { $self->imagelist->current_image->site_comment(@_) }, type => "LongText", }, { label => gettext("Author :"), value => sub { $self->imagelist->current_image->site_author(@_) }, }, { label => gettext("File name :"), value => sub { $self->imagelist->current_image->file(@_) }, readonly => 1, }, { label => gettext("Create date :"), value => sub { $self->imagelist->current_image->create_date(@_) }, readonly => 1, }, ] ); $self->exif_properties( [ { label => gettext("Create date :"), value => sub { $self->imagelist->current_image->create_date }, readonly => 1, }, { label => gettext("Model :"), value => sub { $self->imagelist->current_image->exif_tag('Model') }, readonly => 1, }, { label => gettext("Width :"), value => sub { $self->imagelist->current_image->exif_tag('ImageWidth') }, readonly => 1, }, { label => gettext("Height :"), value => sub { $self->imagelist->current_image->exif_tag('ImageHeight') }, readonly => 1, }, { label => gettext("Orientation :"), value => sub { $self->imagelist->current_image->exif_tag('Orientation') }, readonly => 1, }, { label => "ISO :", value => sub { $self->imagelist->current_image->exif_tag('ISO') }, readonly => 1, }, { label => gettext("Shutter speed :"), value => sub { $self->imagelist->current_image->exif_tag('ExposureTime') }, readonly => 1, }, { label => gettext("Aperture :"), value => sub { $self->imagelist->current_image->exif_tag('ApertureValue') }, readonly => 1, }, { label => gettext("Focal length :"), value => sub { $self->imagelist->current_image->exif_tag('FocalLength') }, readonly => 1, }, { label => gettext("Lens :"), value => sub { $self->imagelist->current_image->exif_tag('Lens') }, readonly => 1, }, ] ); $self->general_settings( [ { label => gettext("Default author :"), value => sub { $self->imagelist->author(@_) }, }, { label => gettext("Default photo caption :"), type => 'Choice', value => sub { $self->imagelist->default_photo_name(@_) }, choice => [ map { gettext $_ } @{wxTheApp->default_photo_names} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_default_photo_names->{$value} }, }, { label => gettext("Photo caption prefix :"), value => sub { $self->imagelist->default_name_prefix(@_) }, }, { label => gettext("Resize site image :"), type => 'Bool', value => sub { $self->imagelist->create_resized(@_) }, }, { label => gettext("Auto rotate image :"), type => 'Bool', value => sub { $self->imagelist->auto_rotate(@_) }, }, { label => gettext("Upload high definition :"), type => 'Choice', choice => [ map { gettext $_ } @{wxTheApp->upload_hd} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_upload_hd->{$value} }, value => sub { $self->imagelist->upload_hd(@_) }, }, { label => gettext("Remove uploaded photo from selection :"), type => 'Bool', value => sub { $self->imagelist->remove_uploaded_from_selection(@_) }, }, ] ); $self->resized_settings( [ { label => gettext("Site image width :"), type => 'Number', value => sub { $self->imagelist->resize_w(@_) }, }, { label => gettext("Site image height :"), type => 'Number', value => sub { $self->imagelist->resize_h(@_) }, }, { label => gettext("Site image jpeg quality :"), type => 'Number', value => sub { $self->imagelist->quality(@_) }, }, { label => gettext("Site image filter :"), type => 'Choice', value => sub { $self->imagelist->filter(@_) }, choice => [ qw/Point Box Triangle Hermite Hanning Hamming Blackman Gaussian Quadratic Cubic Catrom Mitchell Lanczos Bessel Sinc/ ], }, { label => gettext("Site image blur :"), type => 'Float', value => sub { $self->imagelist->blur(@_) }, }, { label => gettext("Site image interlace :"), type => 'Choice', value => sub { $self->imagelist->interlace(@_) }, choice => [ qw/None Line Plane Partition JPEG GIF PNG/ ], }, ] ); $self->hd_settings( [ { label => gettext("HD image width :"), type => 'Number', value => sub { $self->imagelist->hd_w(@_) }, }, { label => gettext("HD image height :"), type => 'Number', value => sub { $self->imagelist->hd_h(@_) }, }, { label => gettext("HD image jpeg quality :"), type => 'Number', value => sub { $self->imagelist->hd_quality(@_) }, }, { label => gettext("HD image filter :"), type => 'Choice', value => sub { $self->imagelist->hd_filter(@_) }, choice => [ qw/Point Box Triangle Hermite Hanning Hamming Blackman Gaussian Quadratic Cubic Catrom Mitchell Lanczos Bessel Sinc/ ], }, { label => gettext("HD image blur :"), type => 'Float', value => sub { $self->imagelist->hd_blur(@_) }, }, { label => gettext("HD image interlace :"), type => 'Choice', value => sub { $self->imagelist->hd_interlace(@_) }, choice => [ qw/None Line Plane Partition JPEG GIF PNG/ ], }, ] ); $self->thumbnail_settings( [ { label => gettext("Site thumbnail size :"), type => 'Number', value => sub { $self->imagelist->thumb_size(@_) }, }, { label => gettext("Site thumbnail jpeg quality :"), type => 'Number', value => sub { $self->imagelist->th_quality(@_) }, }, { label => gettext("Photo selection thumbnail size :"), type => 'Number', value => sub { $self->imagelist->wx_thumb_size(@_) }, }, { label => gettext("Photo selection thumbnail jpeg quality :"), type => 'Number', value => sub { $self->imagelist->wx_quality(@_) }, }, ] ); $self->advanced_settings( [ { label => gettext("Transfert chunk size :"), type => 'Number', value => sub { wxTheApp->chunk_size(@_) }, }, ] ); $self->watermark_settings( [ { label => gettext("Activate watermark :"), type => 'Bool', value => sub { $self->imagelist->watermark_activate(@_) }, }, { label => gettext("Activate watermark on high definition :"), type => 'Bool', value => sub { $self->imagelist->watermark_activate_pwg_high(@_) }, }, { label => gettext("Text :"), value => sub { $self->imagelist->watermark_text(@_) }, }, { label => gettext("Text size :"), type => 'Number', value => sub { $self->imagelist->watermark_text_size(@_) }, }, { label => gettext("Color :"), type => 'Choice', value => sub { $self->imagelist->watermark_color(@_) }, choice => [ map { gettext $_ } @{wxTheApp->colors} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_colors->{$value} }, }, { label => gettext("Position :"), type => 'Choice', value => sub { $self->imagelist->watermark_position(@_) }, choice => [ map { gettext $_ } @{wxTheApp->positions} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_positions->{$value} }, }, { label => gettext("Top margin :"), type => 'Number', value => sub { $self->imagelist->watermark_y(@_) }, }, { label => gettext("Left margin :"), type => 'Number', value => sub { $self->imagelist->watermark_x(@_) }, }, ] ); $self->image_tags( sub { eval { $self->imagelist->current_image->site_tags(@_) } } ); $self->piwigo_tags( sub { wxTheApp->pwg->tags } ); } sub _create_piwigo_tag { my ( $self, $name ) = @_; if( Wx::MessageBox( sprintf( gettext("Do you want to create \"%s\" ?"), $name, ), gettext("Piwigo search information"), wxYES_NO | wxICON_QUESTION, ) == wxYES ){ $self->pwg->AddTags($name); $self->pwg->RefreshTags; } } sub _refreshFrame { my ( $self ) = @_; $self->_set_setting_properties; $self->_refresh_settings_panels_properties; map { $_->Refresh; } ( $self->image_prop_piwigo, $self->image_prop_exif, $self->image_prop_tags, $self->general_settings_panel, $self->resized_settings_panel, $self->thumbnail_settings_panel, $self->hd_settings_panel, $self->advanced_settings_panel, $self->watermark_settings_panel, ); $self->manager->Update; } sub _destroy_settings_panels { my ( $self ) = @_; map { $self->manager->DetachPane( $_ ); $_->Show(0); $_->Destroy; }( $self->notebook_global_settings, $self->notebook_image_properties, ); } # sub _initFrame { my ( $self ) = @_; $self->init_panels; $self->init_aui; $self->init_dnd_targets; if( $self->imagelist->wx_thumb_imglist->GetImageCount){ printf("%s images\n", $self->imagelist->wx_thumb_imglist->GetImageCount); $self->ShowImageViewer; $self->imageviewer->Refresh( $self->imagelist->wx_thumb_imglist ); } } sub _refresh_settings_panels_properties { my ( $self ) = @_; $self->image_prop_piwigo->properties( $self->piwigo_properties ); $self->image_prop_exif->properties( $self->exif_properties ); $self->general_settings_panel->properties( $self->general_settings ); $self->resized_settings_panel->properties( $self->resized_settings ); $self->thumbnail_settings_panel->properties( $self->thumbnail_settings, ); $self->hd_settings_panel->properties( $self->hd_settings, ); $self->advanced_settings_panel->properties( $self->advanced_settings, ); $self->watermark_settings_panel->properties( $self->watermark_settings, ); } sub _init_settings_panels { my ( $self ) = @_; $self->image_prop_piwigo( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->piwigo_properties, } ) ); $self->image_prop_exif( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->exif_properties, } ) ); $self->general_settings_panel( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->general_settings, } ) ); $self->resized_settings_panel( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->resized_settings, } ) ); $self->thumbnail_settings_panel( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->thumbnail_settings, } ) ); $self->hd_settings_panel( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->hd_settings, } ) ); $self->watermark_settings_panel( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->watermark_settings, } ) ); $self->advanced_settings_panel( Uploader::GUI::wxPropertyGridPanel->new( { parentwnd => $self, properties => $self->advanced_settings, } ) ); $self->image_prop_tags( Uploader::GUI::wxChoiceFilteredPanel->new( { parentwnd => $self, choices => $self->piwigo_tags, selection => $self->image_tags, creation_callback => sub { $self->_create_piwigo_tag(@_) }, } ) ); $self->general_settings_panel->Show(0); $self->image_preview( Uploader::GUI::wxImagePreview->new( { parentwnd => $self, } ) ); $self->image_prop_piwigo->Show(0); $self->image_prop_exif->Show(0); } sub init_panels { my ( $self ) = @_; my $file = sprintf( "../locale/%s/gs.html", wxTheApp->locale->GetCanonicalName ); $self->htmlhome( Uploader::GUI::wxHtmlWindow->new( $self, -1, [-1, -1], [450, 450] ) ); $self->htmlhome->LoadPage($file) if -e $file ; $self->htmlhome->InitHrefCallbacks( { new_album => sub {$self->OnAddCategories(@_) }, add_photos => sub { $self->OnAddImages(@_) }, upload_photos => sub { $self->ProcessImageSelection(@_)}, } ); $self->imageviewer( Uploader::GUI::wxImageListCtrl->new( { parentwnd => $self, imagelist => $self->imagelist, } ) ); $self->_init_settings_panels; } sub init_dnd_targets { my ( $self ) = @_; $self->imageviewer->SetDropTarget( DNDImageListDropTarget->new( $self->imageviewer ) ); $self->tree->SetDropTarget( DNDCategoryTreeDropTarget->new( $self->tree ) ); } sub init_aui { my ( $self ) = @_; my $url = wxTheApp->site_url; $url = gettext("empty - work Offline") if wxTheApp->use_offline; $self->manager->AddPane ( $self->create_notebook_imagelist, Wx::AuiPaneInfo->new->Name( "ImageViewer" ) ->CenterPane->Resizable->CloseButton(0) ); $self->manager->AddPane( $self->create_tree, Wx::AuiPaneInfo->new->Name( "categories" ) ->Right->Position( 0 )->Resizable->CloseButton(0), ); $self->_init_aui_settings_panels; $self->toolbar( $self->create_toolbar ); $self->manager->AddPane ( $self->toolbar, Wx::AuiPaneInfo->new->Name( "tb1" ) ->ToolbarPane->Top->Row( 1 )->LeftDockable( 0 )->RightDockable( 0 ) ) if defined $self->toolbar; $self->SetSize( $self->frameLayout->{pX}, $self->frameLayout->{pY}, $self->frameLayout->{W}, $self->frameLayout->{H}, ); if($self->perspective){ $self->manager->LoadPerspective($self->perspective); } else { $self->manager->GetPane("categories")->MinSize(300,100); $self->manager->GetPane("tb1")->Caption( gettext("Tools") ); $self->manager->GetPane("image_preview")->Caption(gettext("Preview"))->Show(0); $self->manager->GetPane("getting_started")->Caption(gettext("Getting started"))->Float->Show(1); $self->manager->GetPane("photo_properties")->Caption(gettext("Photo properties"))->Float->Show(0); $self->manager->GetPane("global_settings")->Caption(gettext("Global settings"))->Float->Show(0); } $self->manager->GetPane("categories")->Caption( sprintf("%s - %s", sprintf("Piwigo %s", wxTheApp->branding->{categories}), $url ) ); $self->manager->Update; } sub _init_aui_settings_panels { my ( $self ) = @_; $self->manager->AddPane ( $self->image_preview, Wx::AuiPaneInfo->new->Name( "image_preview" ) ->Floatable(1)->Resizable->CloseButton(1)); $self->manager->AddPane ( $self->htmlhome, Wx::AuiPaneInfo->new->Name( "getting_started" ) ->Floatable(1)->Resizable->CloseButton(1) ); $self->manager->AddPane ( $self->create_notebook_image_properties, Wx::AuiPaneInfo->new->Name( "photo_properties" ) ->Floatable(1)->Resizable->CloseButton(1) ); $self->manager->AddPane ( $self->create_notebook_global_settings, Wx::AuiPaneInfo->new->Name( "global_settings" ) ->Floatable(1)->Resizable->CloseButton(1) ); } sub OnPhotoProperties { my ( $self ) = @_; $self->manager->GetPane("photo_properties")->Show(1); $self->manager->Update(); } sub OnPreview { my ( $self ) = @_; my $preview_pane = $self->manager->GetPane("image_preview"); if (! $preview_pane->IsShown){ $preview_pane->MinSize(100,75)->Float->Show(1); $self->manager->Update(); $self->_image_preview_refresh; $self->image_preview->Refresh; }; } sub OnGettingStarted { my ( $self ) = @_; $self->toolbar->EnableTool(100, 0 ); $self->manager->GetPane("getting_started")->Float->Show(1); $self->manager->Update(); } sub OnGlobalSettings { my ( $self ) = @_; $self->toolbar->EnableTool(104, 0 ); $self->manager->GetPane("global_settings")->Float->Show(1); $self->manager->Update(); } sub OnChooseLanguage { my ( $self ) = @_; my $languages = wxTheApp->available_languages; my $dialog = new Wx::SingleChoiceDialog( undef, gettext( "Choose a language" ), gettext( "Choose a language" ), [ map { sprintf($_->[0], gettext($_->[2])) } @$languages ] ); if( $dialog->ShowModal() == wxID_OK ) { wxTheApp->current_language( $languages->[$dialog->GetSelection][1] ); Wx::LogMessage( sprintf( "%s : %s", gettext("pLoader needs to be restarted to display the new selected language"), gettext($languages->[$dialog->GetSelection][2]) ) ); wxTheApp->layout_clean(1); } $dialog->Destroy; } sub OnDefaultPhotoNameChanged { my ( $self ) = @_; Wx::LogMessage("New default_photo_name %s", $self->imagelist->default_photo_name); } sub create_notebook_imagelist { my( $self ) = @_; my $book = Wx::AuiNotebook->new( $self, -1, [-1, -1], [300, 450], wxBORDER_NONE|wxAUI_NB_TAB_MOVE|wxAUI_NB_TAB_SPLIT); my $imglistpage = [ [ $self->imageviewer, gettext("Photo selection"), '../res/images.png', wxBITMAP_TYPE_PNG, ], ]; $self->notebook($book); $self->_populate_notebook($self->notebook, $imglistpage, 0); return $book; } sub create_notebook_image_properties { my( $self ) = @_; my $book = Wx::AuiNotebook->new( $self, -1, [-1, -1], [450, 450], wxBORDER_NONE|wxAUI_NB_TAB_MOVE|wxAUI_NB_TAB_SPLIT); my $pages = [ [ $self->image_prop_piwigo, "Piwigo", ], [ $self->image_prop_tags, "Tags", ], [ $self->image_prop_exif, "Exif", ], ]; $self->_populate_notebook( $book, $pages ); $self->notebook_image_properties($book); return $book; } sub create_notebook_global_settings { my( $self ) = @_; my $book = Wx::AuiNotebook->new( $self, -1, [-1, -1], [450, 450], wxBORDER_NONE|wxAUI_NB_TAB_MOVE|wxAUI_NB_TAB_SPLIT); my $pages = [ [ $self->general_settings_panel, gettext("General"), ], [ $self->resized_settings_panel, gettext("Resized"), ], [ $self->thumbnail_settings_panel, gettext("Thumbnail"), ], [ $self->hd_settings_panel, gettext("High definition"), ], [ $self->watermark_settings_panel, gettext("Watermark"), ], [ $self->advanced_settings_panel, gettext("Advanced"), ], ]; $self->_populate_notebook( $book, $pages ); $self->notebook_global_settings($book); return $book; } sub create_tree { my ( $self ) = @_; my $images = [ map { Wx::Bitmap->new( $_, wxBITMAP_TYPE_PNG ) } ( '../res/tree_pwg.png', '../res/tree_folder.png', ) ]; $self->treeimglist( Wx::ImageList->new( 16, 16, 1 ) ); map { $self->treeimglist->Add($_); } @$images; $self->tree( Wx::TreeCtrl->new( $self, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE| wxCLIP_CHILDREN| wxTR_HAS_BUTTONS| wxTR_EDIT_LABELS ) ); $self->tree->SetImageList( $self->treeimglist ); $self->populate_tree_categories if !wxTheApp->use_offline; $self->tree ; } sub populate_tree_categories { my ( $self ) = @_; $self->populate_tree( $self->tree, wxTheApp->pwg->categories, ) if defined wxTheApp->pwg ; #$self->tree->ExpandAll; } # returns a valid itemData sub itemData { Wx::TreeItemData->new( $_[0] ) } sub populate_tree { my ( $self, $tree, $tree_items ) = @_; my $root = shift @{$tree_items}; $self->tree_root( $tree->AddRoot( $root->[0], $root->[3], $root->[4], itemData( $root->[2] ) ) ); $self->populate_tree_helper( $tree, $self->tree_root, $tree_items ); $tree->SelectItem( $self->tree_root ); $tree->Expand( $self->tree_root ); } sub populate_tree_helper { my ( $self, $tree, $parent_id, $tree_items ) = @_; my $id; map { my $name = $_->[0]; $id = $tree->AppendItem( $parent_id, $name, defined($_->[3]) ? $_->[3] : 0, defined($_->[4]) ? $_->[4] : 0, itemData( $_->[2]) ); $self->tree_item_default($id) if ! defined $self->tree_item_default; # current item has children if( ref( $_->[1] ) eq 'ARRAY' ) { $self->populate_tree_helper( $tree, $id, $_->[1] ); } } @{$tree_items}; } sub _populate_notebook { my ( $self, $book, $pages, $position ) = @_; my $i = 0; map { my $icon = Wx::Icon->new(); eval { $icon->LoadFile($_->[2], $_->[3]) ; } if defined $_->[2]; my $nb_icon = Wx::Bitmap->new( $icon ); defined $position ? $book->InsertPage($position, $_->[0], $_->[1], 1, $nb_icon ) : $book->AddPage($_->[0], $_->[1], 0, $nb_icon ); $i++; } @$pages; } sub _initMenus { my ( $self ) = @_ ; $self->_tree_mnu; $self->_imageviewer_mnu; } sub _tree_mnu { my ( $self ) = @_; my $ctx_mnu = Wx::Menu->new; map { $ctx_mnu->Append( @$_[0..2], wxITEM_NORMAL )->SetBitmap(Wx::Bitmap->new($_->[3],wxBITMAP_TYPE_PNG)); } ( # workaround : first item does not show bitmap [ 0, "", "", '../res/mnu_folder_new.png', ], [ 1+$ID_TREE_CTX_MENU, wxTheApp->branding->{'Add new category'}, sprintf( "%s %s %s %s", gettext("Add a new"), wxTheApp->branding->{category}, gettext("to the currently selected"), wxTheApp->branding->{category}, ), '../res/mnu_folder_new.png', ], [ 2+$ID_TREE_CTX_MENU, gettext("Refresh"), sprintf( "Refresh %s list.", wxTheApp->branding->{category}, ), '../res/mnu_refresh.png', ], [ 3+$ID_TREE_CTX_MENU, gettext("Expand all"), sprintf( "Expand %s list.", wxTheApp->branding->{category}, ), '../res/mnu_expandall.png', ], [ 4+$ID_TREE_CTX_MENU, gettext("Collapse all"), sprintf( "Collapse %s list.", wxTheApp->branding->{category}, ), '../res/mnu_collapseall.png', ], ); $ctx_mnu->Delete(0); $self->tree_mnu( $ctx_mnu ); } sub _imageviewer_mnu { my ( $self ) = @_; my $ctx_mnu = Wx::Menu->new; map { $ctx_mnu->Append( @$_[0..2] )->SetBitmap(Wx::Bitmap->new($_->[3],wxBITMAP_TYPE_PNG)); } ( # workaround : first item does not show bitmap [ 0, "", "", '../res/mnu_properties.png', ], [ 1+$ID_IMAGEVIEWER_CTX_MENU, gettext("Properties"), gettext("Modify photo properties"), '../res/mnu_properties.png', ], [ 2+$ID_IMAGEVIEWER_CTX_MENU, gettext("Preview"), gettext("Display photo preview"), '../res/mnu_preview.png', ], ); $ctx_mnu->Delete(0); $self->imageviewer_mnu( $ctx_mnu ); } sub _initEventHandlers { my ( $self ) = @_ ; Wx::Event::EVT_MENU( $self, 100, \&OnGettingStarted ); Wx::Event::EVT_MENU( $self, 101, \&OnAddImages ); Wx::Event::EVT_MENU( $self, 102, \&OnRemoveImages ); Wx::Event::EVT_MENU( $self, 103, \&OnUploadImages ); Wx::Event::EVT_MENU( $self, 104, \&OnGlobalSettings ); Wx::Event::EVT_MENU( $self, 105, \&OnChooseLanguage ); EVT_TREE_SEL_CHANGED( $self, $self->tree, \&OnTreeSelChanged ); EVT_TREE_ITEM_RIGHT_CLICK( $self, $self->tree, \&OnTreeItemRightClick ); EVT_TREE_END_LABEL_EDIT( $self, $self->tree, \&OnTreeEndLabelEdit ); EVT_LIST_END_LABEL_EDIT( $self, $self->imageviewer, \&OnImageViewerEndLabelEdit ); EVT_LIST_ITEM_ACTIVATED( $self, $self->imageviewer, \&OnImageViewerItemActivated ); EVT_LIST_ITEM_SELECTED($self, $self->imageviewer, \&OnImageViewerItemSelected) ; EVT_LIST_ITEM_RIGHT_CLICK($self, $self->imageviewer, \&OnImageViewerItemRightClick) ; EVT_LIST_KEY_DOWN($self, $self->imageviewer, \&OnImageViewerKeyDown) ; EVT_CLOSE( $self, \&OnClose ); Wx::Event::EVT_UPDATE_UI( $self, $self, \&OnUpdateUI ); Wx::Event::EVT_MENU( $self, 1+$ID_TREE_CTX_MENU, \&OnAddCategories ); Wx::Event::EVT_MENU( $self, 2+$ID_TREE_CTX_MENU, \&OnRefreshCategories ); Wx::Event::EVT_MENU( $self, 3+$ID_TREE_CTX_MENU, \&OnExpandCategories ); Wx::Event::EVT_MENU( $self, 4+$ID_TREE_CTX_MENU, \&OnCollapseCategories ); Wx::Event::EVT_MENU( $self, 1+$ID_IMAGEVIEWER_CTX_MENU, \&OnPhotoProperties ); Wx::Event::EVT_MENU( $self, 2+$ID_IMAGEVIEWER_CTX_MENU, \&OnPreview ); } { my $prevdir; my $prevfile; sub OnAddImages { my( $self, $event ) = @_; my $dialog = Wx::FileDialog->new ( $self, gettext("Select photos for upload"), $prevfile, $prevdir, sprintf("%s (*.jpg)|*.jpg|All(*.*)|*.*", gettext("JPEG files")), wxFD_OPEN|wxFD_MULTIPLE ); my $file_paths = []; if( $dialog->ShowModal != wxID_CANCEL ) { @$file_paths = $dialog->GetPaths; $self->SetNewFiles($file_paths) ; } $dialog->Destroy; # $event->Skip; } } sub OnUpdateUI { my( $self, $event ) = @_; if( $self->manager ){ if($self->manager->GetPane("global_settings")->IsShown){ $self->toolbar->EnableTool(104, 0); } else{ $self->toolbar->EnableTool(104, 1); } if($self->manager->GetPane("getting_started")->IsShown){ $self->toolbar->EnableTool(100, 0); } else{ $self->toolbar->EnableTool(100, 1); } } } sub OnRemoveImages { my( $self, $event ) = @_; $self->imagelist->RemoveImageSelection; $self->imageviewer->Refresh; $self->image_preview->image( 0 ) if !$self->imageviewer->GetItemCount; $self->image_preview->Refresh; } sub SetNewFiles { my ( $self, $file_paths ) = @_; $self->ShowImageViewer(); $self->progressdlg( Uploader::GUI::wxImageProcessingProgressDlg->new( { title => gettext("Image processing progress information"), bt_label => gettext("Cancel image processing"), bt_close_label => gettext("Close"), } ) ); $self->progressdlg->Show(1); Wx::Yield(); my $files = [ map { # to make sure that unicode chars in filenames are supported { ANSIPathName => $^O =~ /MSWin32/ ? Win32::GetANSIPathName($_) : $_, PathName => $_, }, }@$file_paths ]; @$files = sort { $a->{PathName} cmp $b->{PathName} } @$files; $self->imagelist->SetNewFiles( $files ); } sub OnTreeSelChanged { my( $self, $event ) = @_; my @items = $self->tree->GetSelections; $self->imagelist->categories( [ map { my $category = $self->tree->GetPlData( $_ ); $category->{id} if $category != -1; } @items ] ); } sub OnTreeItemRightClick { my( $self, $event ) = @_; $self->PopupMenu($self->tree_mnu, wxDefaultPosition); } sub OnTreeEndLabelEdit { my( $self, $event ) = @_; my $label = $event->GetLabel; $label =~ s/^\s+$//; if(defined($label) and !( "" eq $label )){ $self->_SetLabel($event) } else{ $event->Veto; } } sub _SetLabel { my( $self, $event ) = @_; my $category = $self->tree->GetPlData($event->GetItem); my $category_id; $category_id = $category->{id} if 'HASH' eq ref($category) ; my $comment; my ( $success, $status_msg, $content ) = $self->pwg->SetInfoCategories( $event->GetLabel, $comment, $category_id ); my $ok = 1; if(!$success){ $ok = 0; } if('fail' eq $content->{stat}){ $ok = 0; } # method call failed if(!$ok){ $event->Veto; Wx::MessageBox( sprintf( "%s %s", gettext("Update failed : "), $status_msg ), gettext("Piwigo update error"), wxOK | wxICON_EXCLAMATION, ); Wx::LogMessage("%s\n\n%s", Dumper($content), gettext("This function is not available. A Piwigo upgrade may resolve this issue.")); } } sub OnImageViewerItemRightClick { my( $self, $event ) = @_; $self->PopupMenu($self->imageviewer_mnu, wxDefaultPosition); } sub OnExpandCategories { my ( $self, $event ) = @_; my $parent_item = $self->tree->GetSelection; $self->tree->ExpandAllChildren($parent_item); $self->tree->EnsureVisible($parent_item); } sub OnCollapseCategories { my ( $self, $event ) = @_; my $parent_item = $self->tree->GetSelection; $self->tree->CollapseAllChildren($parent_item); $self->tree->Expand($parent_item) if -1 == $self->tree->GetPlData($parent_item); } sub OnAddCategories { my ( $self, $event ) = @_; my $parent_item = $self->tree->GetSelection; my $category = $self->tree->GetPlData($parent_item); my $category_id; $category_id = $category->{id} if 'HASH' eq ref($category) ; my $dialog = Wx::TextEntryDialog->new( $self, wxTheApp->branding->{'Category name'}, wxTheApp->branding->{'Add new category'}, wxTheApp->branding->{'New category'}, ); if( $dialog->ShowModal != wxID_CANCEL ) { my $name = $dialog->GetValue; my ( $success, $status_msg, $content ) = $self->pwg->AddCategories( $name, $category_id); if($success){ $self->_append_category($parent_item, $name, $content->{result}{id}); } } $dialog->Destroy; } sub OnRefreshCategories { my ( $self, $event ) = @_; $self->_refresh_all_categories_helper; } sub _refresh_all_categories_helper { my ( $self ) = @_; my $busycursor = Wx::BusyCursor->new(); $self->tree->CollapseAll; $self->tree->DeleteAllItems; $self->imagelist->categories([]); $self->pwg->RefreshCategories(); $self->populate_tree_categories; } sub _append_category { my ( $self, $parent_id, $name, $id ) = @_; $self->tree->SelectItem( $self->tree->AppendItem( $parent_id, $name, 1, -1, Wx::TreeItemData->new( { id => $id } ) ) ); } sub OnImageViewerEndLabelEdit { my( $self, $event ) = @_; my $image = $self->imagelist->GetImage($event->GetIndex); $image->site_name( $event->GetLabel ); $self->image_prop_piwigo->Refresh; } sub OnImageViewerItemActivated { my( $self, $event ) = @_; $self->current_imageviewer_index( $event->GetIndex ); $self->OnPhotoProperties; } sub OnImageViewerItemSelected { my( $self, $event ) = @_; my $bc = Wx::BusyCursor->new; my $indx = $event->GetIndex; $self->_on_imageviewer_item_selected($indx); $event->Skip; } sub _on_imageviewer_item_selected { my ( $self, $index ) = @_; $self->current_imageviewer_index($index); $self->imagelist->SetCurrentImage($index); $self->imagelist->image_selection( $self->imageviewer->GetSelectedItems ); # image preview has a cost, don't do if useless. if ( $self->image_preview->IsShown ){ $self->_image_preview_refresh; $self->image_preview->Refresh; } $self->image_prop_piwigo->Refresh; $self->image_prop_exif->Refresh; $self->image_prop_tags->Refresh; } sub _image_preview_refresh { my ( $self ) = @_; my $current_image = $self->imagelist->current_image; my $image = Wx::Image->new; $image->LoadFile( $current_image->file, wxBITMAP_TYPE_ANY ); if($self->imagelist->auto_rotate){ # exif from original image my $orientation = $current_image->exif_metadata->{Orientation}; # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW if( $orientation =~ m/Rotate (\d+)/ ){ for(my $i=0; $i < floor($1/90) ; $i++){ $image = $image->Rotate90; } } } $self->image_preview->image_size( [$image->GetWidth, $image->GetHeight, ] ); $self->image_preview->image( $image ); } sub OnImageViewerKeyDown { my( $self, $event ) = @_; if(WXK_DELETE == $event->GetKeyCode){ $self->OnRemoveImages(); my $index = $self->current_imageviewer_index < $self->imageviewer->GetItemCount ? $self->current_imageviewer_index : $self->imageviewer->GetItemCount -1 ; $self->imageviewer->SelectItem( $index ); $self->imageviewer->EnsureVisible( $index ); } } sub OnUploadImages { my( $self, $event ) = @_; eval { $self->ProcessImageSelection(); }; } # remove image from imagelist when uploaded sub UploadImagesViewerRefresh { my ( $self ) = @_; $self->imageviewer->Refresh; $self->image_preview->image( 0 ) if !$self->imageviewer->GetItemCount; $self->image_preview->Refresh; Wx::Yield(); } sub UploadProgressMessageRefresh { my ( $self, $msg ) = @_; Wx::Yield(); $self->upload_progressdlg->processing( $msg ); $self->upload_progressdlg->LogProgress(); Wx::Yield(); } sub UploadProgressMessageDetailsRefresh { my ( $self, $msg ) = @_; Wx::Yield(); $self->upload_progressdlg->processing_details( $msg ); $self->upload_progressdlg->LogProgress(); Wx::Yield(); } sub UploadProgressThumbnailRefresh { my ( $self ) = @_; my $imagelist = $self->imagelist ; $self->upload_progressdlg->image->SetBitmap(wxNullBitmap); $self->upload_progressdlg->image->SetBitmap( Wx::Bitmap->new( $self->imagelist->current_image->wx_thumb_file, $self->GetWxBitmapType($self->imagelist->type), ) ); Wx::Yield(); } sub UploadProgressBarRefresh { my ( $self, $value ) = @_; eval { $self->upload_progressdlg->progress( $value ); $self->upload_progressdlg->LogProgress(); }; #croak gettext("Upload cancelled") if $@; Wx::Yield(); } sub SetNewFilesDisplayEndInfo { my ( $self, $msg ) = @_; $self->progressdlg->DisplayEndInfo($msg); } sub UploadDisplayEndInfo { my ( $self, $msg ) = @_; my $imagelist = $self->imagelist ; $self->upload_progressdlg->DisplayEndInfo($msg); } sub ShowImageViewer { my ( $self ) = @_; if(!$self->imageviewer->IsShown){ $self->imageviewer->Show(1); } } sub ActivateImageViewer { my ( $self ) = @_; } sub SetNewFilesViewerRefresh { my ( $self ) = @_; my $wximagelist = $self->imagelist->wx_thumb_imglist; #print Dumper "SetNewFilesViewerRefresh", $self->imagelist->current_image; my $indx = $wximagelist->Add( Wx::Bitmap->new( $self->imagelist->current_image->wx_thumb_file, $self->GetWxBitmapType($self->imagelist->type), ) ) if defined $self->imagelist->current_image->wx_thumb_file; print $self->imagelist->current_image->wx_thumb_file, " added with index ", $indx, "\n"; $self->imageviewer->Refresh( $wximagelist ); Wx::Yield(); } # prepare and upload image_selection sub ProcessImageSelection { my ( $self ) = @_; return if !scalar @{$self->imagelist->sums}; if( scalar @{$self->imagelist->categories} ){ # all selected is implicit if(!scalar @{$self->imageviewer->GetSelectedItems}){ $self->imagelist->image_selection( $self->imageviewer->GetAllItems ); } return if( !defined $self->imagelist->image_selection ); return if( !scalar @{$self->imagelist->image_selection} ); $self->upload_progressdlg( Uploader::GUI::wxImageProcessingProgressDlg->new( { title => gettext("Image upload progress information"), bt_label => gettext("Cancel upload"), bt_close_label => gettext("Close"), } ) ); # modeless dialog $self->upload_progressdlg->Show(1); Wx::Yield(); eval { $self->imagelist->UploadSelection; }; if($@){ Wx::MessageBox( sprintf( gettext("Upload cancelled"), ), gettext("Piwigo upload information"), wxOK | wxICON_INFORMATION, ); } } else { Wx::MessageBox( sprintf( "%s %s", gettext("Please select a valid target"), wxTheApp->branding->{category} ), gettext("Piwigo upload error"), wxOK | wxICON_EXCLAMATION, ); } } sub SetNewFilesProgress { my ( $self ) = @_; my $imagelist = $self->imagelist; $self->progressdlg->processing( sprintf( $imagelist->progress_msg, $imagelist->current_image->file, ) ); eval { $self->progressdlg->image->SetSize([ $imagelist->wx_thumb_size, $imagelist->wx_thumb_size]); $self->progressdlg->image->SetBitmap(wxNullBitmap); $self->progressdlg->image->SetBitmap( Wx::Bitmap->new( $imagelist->current_image->wx_thumb_file, $self->GetWxBitmapType( $imagelist->type ) ) ); $self->progressdlg->progress( $imagelist->count * ( 100/scalar @{$imagelist->new_files} ) ); $self->progressdlg->LogProgress(); }; Wx::Yield(); } sub OnClose { my $self = shift; # Restaure previous log wnd Wx::Log::SetActiveTarget( $self->oldlogwnd ); # allways store wxTheApp->StoreConnectionProperties; $self->imagelist->Store; wxTheApp->login_dlg->Destroy; wxTheApp->perspective( $self->manager->SavePerspective ); wxTheApp->imageviewerIndex( $self->current_imageviewer_index ); my $frameLayout = {}; ( $frameLayout->{pX}, $frameLayout->{pY}, $frameLayout->{W}, $frameLayout->{H} ) = ( $self->GetPositionXY, $self->GetSizeWH ) ; wxTheApp->frameLayout( $frameLayout ); wxTheApp->StoreLayoutProperties; $self->manager->UnInit; $self->Destroy; } sub create_toolbar { my( $self ) = @_; my $tb = Wx::ToolBar->new( $self, -1, wxDefaultPosition, wxDefaultSize, wxTB_FLAT ); $tb->SetToolBitmapSize( wxSIZE( 32, 32 ) ); map { my $icon1 = Wx::Icon->new(); eval { $icon1->LoadFile($_->[2], $_->[3]); }; my $tb_icon1 = Wx::Bitmap->new( $icon1 ); my $icon2 = Wx::Icon->new(); eval { $icon2->LoadFile($_->[5], $_->[3]); }; my $tb_icon2 = Wx::Bitmap->new( $icon2 ); $tb->AddTool( $_->[0], $_->[1], $tb_icon1, $tb_icon2, wxITEM_NORMAL, $_->[1] ); $tb->EnableTool( $_->[0], $_->[4]); } ( [ 100, gettext("Getting started"), '../res/tb_getting_started.png', wxBITMAP_TYPE_PNG, 1, '../res/tb_getting_started.png', gettext("Display getting started panel") ], [ 101, gettext("Add photo to selection"), '../res/tb_add.png', wxBITMAP_TYPE_PNG, 1, '../res/tb_add.png', gettext("Add photo to selection for resizing and uploading") ], [ 102, gettext("Remove photo from selection"), '../res/tb_remove.png', wxBITMAP_TYPE_PNG, 1, '../res/tb_remove.png', gettext("Remove photo from selection. Files are not deleted ") ], [ 103, gettext("Upload to Piwigo"), '../res/tb_upload.png', wxBITMAP_TYPE_PNG, wxTheApp->use_offline ? 0 : 1, '../res/tb_upload.png', gettext("Upload photos to Piwigo.") ], [ 104, gettext("Global settings"), '../res/tb_settings.png', wxBITMAP_TYPE_PNG, 0, '../res/tb_settings.png', gettext("Change global settings.") ], [ 105, gettext("Language choice"), '../res/tb_i18n.png', wxBITMAP_TYPE_PNG, 1, '../res/tb_i18n.png', gettext("Language choice") ], ); if($@){ undef $tb; } else{ $tb->Realize; } return $tb; } sub _create_textctrl { my( $self, $parent, $text, $size ) = @_; return Wx::TextCtrl->new( $parent, -1, $text, [0, 0], $size, wxNO_BORDER | wxTE_MULTILINE | wxTE_READONLY ); } sub create_textctrl { my( $self, $text, $size ) = @_; return $self->_create_textctrl( $self, $text, $size ); } sub DESTROY { my( $self ) = @_; } 1; package DNDImageListDropTarget; use Wx qw/wxTheApp/; use base qw(Wx::FileDropTarget Class::Accessor::Fast); __PACKAGE__->mk_accessors( qw/ imageviewer / ); sub new { my $class = shift; my $imageviewer = shift; my $self = $class->SUPER::new( @_ ); $self->imageviewer($imageviewer); return $self; } sub OnDropFiles { my( $self, $x, $y, $files ) = @_; wxTheApp->frame->SetNewFiles($files) ; } 1; package DNDCategoryTreeDropTarget; use base qw(Wx::TextDropTarget Class::Accessor::Fast); use Data::Dumper; use Wx qw/ wxDragNone wxDragCopy wxDragMove /; __PACKAGE__->mk_accessors( qw/ tree frame / ); sub new { my ( $class, $tree ) = @_; my $self = $class->SUPER::new(); $self->tree($tree); $self->frame($tree->GetParent); return $self; } sub OnDropText { my( $self, $x, $y, $textdata ) = @_; # must be $VAR1 because $textdata is the result of Data::Dumper my $VAR1; eval $textdata; eval { if(scalar @$VAR1){ my @items; if(scalar @items < 2) { my ($dropItem, $flag) = $self->tree->HitTest([$x, $y]); push @items, $dropItem; } else { @items = $self->tree->GetSelections; } # remove root item which is not a valid category @items = grep { $self->tree->GetPlData( $_ ) != -1 } @items; $self->frame->imagelist->categories( [ map { $self->tree->GetPlData( $_ )->{id}; } @items ] ); $self->frame->imagelist->image_selection($VAR1); $self->frame->ProcessImageSelection ; } }; } 1;