# +-----------------------------------------------------------------------+ # | pLoader - a Perl photo uploader for Piwigo | # +-----------------------------------------------------------------------+ # | Copyright(C) 2008-2010 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::Frame; use strict; use Data::Dumper; use Carp; use Wx; use Wx::DND; use Wx qw/ wxCLIP_CHILDREN wxYES_NO wxYES wxICON_QUESTION wxITEM_NORMAL wxID_OK wxDEFAULT_FRAME_STYLE wxVERTICAL wxGROW wxSHAPED wxBITMAP_TYPE_JPEG wxBITMAP_TYPE_GIF wxBITMAP_TYPE_PNG wxBITMAP_TYPE_ANY wxTB_FLAT wxTB_TEXT wxSIZE wxWHITE wxBLACK wxID_CANCEL wxFD_OPEN wxFD_MULTIPLE wxALL wxGROW wxDefaultPosition wxDefaultSize wxTheApp wxIMAGE_LIST_NORMAL wxNO_BORDER wxTE_MULTILINE wxTE_READONLY wxITEM_NORMAL wxBORDER_NONE wxNullBitmap wxTR_MULTIPLE wxTR_EXTENDED wxTR_HIDE_ROOT wxTR_HAS_BUTTONS wxTR_EDIT_LABELS wxMAXIMIZE wxOK wxICON_EXCLAMATION wxICON_INFORMATION WXK_DELETE wxHORIZONTAL wxVERTICAL wxEXPAND wxSWISS wxNORMAL wxBOLD wxALIGN_CENTER wxUSER_ATTENTION_INFO /; use base qw/Wx::Frame Class::Accessor::Fast/; use File::Spec; use Wx::Locale qw/:default/; use POSIX qw(ceil floor); my @properties = qw/ images preferences upload_progressdlg image_progress_columns image_progress_event imageviewer imageviewer_img tree tree_root treeimglist tree_item_default pwg logwnd oldlogwnd image_preview_dlg image_prop_piwigo exif_dlg image_prop_tags preferences_dlg piwigo_property_list exif_properties preferences_properties toolbar branding current_imageviewer_index imageviewer_mnu tree_mnu imageviewer_select_multi frameLayout piwigo_tags image_tags piwigo_photo_properties piwigo_photo_properties_dlg piwigo_photo_properties_tags image_preview_need_refresh imageviewer_refresh imageviewer_item_refresh destination_category_dlg destination_categories categories_treectrl prepare_transfer_notebook /; __PACKAGE__->mk_accessors( @properties ); use Wx::Perl::TextValidator; use Uploader::GUI::wxImageListCtrl; use Uploader::GUI::wxPropertyListDlg; use Uploader::GUI::Preferences; use Uploader::GUI::wxImagePreview; use Uploader::GUI::wxPhotoProperties; use Uploader::GUI::wxImageReuploadDlg; use Uploader::GUI::ImageProgressDlg; use Uploader::GUI::wxDestinationCategoryDlg; use Uploader::GUI::wxCategoryTreeCtrl; 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->pwg( $params->{pwg} ); $self->images( $params->{images} ); $self->preferences( $params->{preferences} ); $self->init_gui_callbacks; $self->init_properties_dlg_bindings; $self->init_frame; $self->init_menus; $self->init_event_handlers; $self->init_image_types; $self->imageviewer->select_item( $self->current_imageviewer_index ) if $self->imageviewer->GetItemCount; # if file in command line parameters, try to load my $files = wxTheApp->argv; $self->add_images($files) if scalar @$files; $self->Show; $self; } sub init_gui_callbacks { my ( $self ) = @_; # callback for GUI refresh : add thumbnail images to the imageviewer control $self->images->add_images_cbk( sub { $self->add_images_viewer_refresh(@_) } ); $self->images->delete_images_cbk( sub { $self->delete_images_viewer_refresh(@_) } ); } sub init_properties_dlg_bindings { my ( $self ) = @_; $self->piwigo_photo_properties( { $main::CAPTION => { label=>gettext("Caption")}, $main::PHOTO_PROPERTIES_CAPTION => { multi_selection_mode => sub { $self->multi_selection_mode }, string_selection => sub { return 1; }, value => sub { $self->images->set_image_selection_name(@_); $self->multi_selection_mode ? $self->images->selection_name : $self->images->current_image->site_name; }, pre_process => sub { sub { $self->images->get_current_image_caption(@_); } }, choices => [ map { gettext $_ } @{wxTheApp->caption_patterns} ], frame_callback => sub { $self->imageviewer_item_refresh(1); }, }, $main::COMMENT => { label=>gettext("Comment")}, $main::PHOTO_PROPERTIES_COMMENT => { value => sub { $self->multi_selection_mode ? $self->images->set_image_selection_comment(@_): $self->images->current_image->site_comment(@_) }, frame_callback => sub { $self->imageviewer_item_refresh(1); }, }, $main::AUTHOR => { label=>gettext("Author")}, $main::PHOTO_PROPERTIES_AUTHOR => { value => sub { $self->multi_selection_mode ? $self->images->set_image_selection_author(@_): $self->images->current_image->site_author(@_) }, frame_callback => sub { $self->imageviewer_item_refresh(1); }, }, $main::TAGS => { label=>gettext("Tags")}, $main::CREATE_DATE => { label=>gettext("Create date")}, $main::PHOTO_PROPERTIES_CREATE_DATE => { value => sub { $self->multi_selection_mode ? $self->images->set_image_selection_create_date(@_): $self->images->current_image->create_date(@_) }, frame_callback => sub { $self->imageviewer_item_refresh(1); }, }, $main::PRIVACY_LEVEL => { label=>gettext("Who can see?")}, $main::PHOTO_PROPERTIES_PRIVACY_LEVEL => { selection => sub { $self->multi_selection_mode ? $self->images->set_image_selection_privacy_level(@_): $self->images->current_image->privacy_level(@_) }, choices => wxTheApp->privacy_level_choices, }, $main::PHOTO_PROPERTIES_NB => { texts => [ gettext("Properties"), gettext("Tags") ], }, $main::PHOTO_PROPERTIES_UPLOAD => { label=>gettext("Upload to Piwigo") }, } ); $self->preferences_properties( { $main::DEFAULT_PHOTO_CAPTION => { label => gettext("Default caption pattern") }, $main::CPANE_RESIZE_ADVANCED => { label => gettext("Advanced") }, $main::CPANE_HD_ADVANCED => { label => gettext("Advanced") }, $main::CPANE_TRANSFERT_ADVANCED => { label => gettext("Advanced") }, $main::GS_THUMBNAIL => { label => gettext("Thumbnail") }, $main::GS_SITE_IMG => { label => gettext("Web sized") }, $main::GS_HD => { label => gettext("High definition") }, $main::GS_CLOSE => { label => gettext("Close")}, $main::GS_THUMBNAIL_SHAPE => { selection => sub { $self->preferences->thumbnail_shape_square(@_) }, choices => [ map { gettext $_ } ( "Rectangular", "Square" ) ], }, $main::GS_DEFAULT_PHOTO_CAPTION => { string_selection => sub { $self->preferences->default_caption_pattern(@_) }, choices => [ map { gettext $_ } @{wxTheApp->caption_patterns} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_caption_patterns->{$value} }, frame_callback => sub { my( $self, $ctrl, $event ) = @_; $self->OnDefaultPhotoCaption($event); }, }, $main::PHOTO_CAPTION_PREFIX => { label => gettext("Default caption") }, $main::GS_PHOTO_CAPTION_PREFIX => { value => sub { $self->preferences->default_caption(@_) }, }, $main::GS_AUTO_ROTATE => { label => gettext("Auto rotate image"), value => sub { $self->preferences->auto_rotate(@_) } }, $main::DEFAULT_AUTHOR => { label => gettext("Default author")}, $main::GS_DEFAULT_AUTHOR => { value => sub { $self->preferences->author(@_) } }, $main::THUMBNAIL_SIZE => { label => gettext("Site thumbnail size") }, $main::GS_THUMBNAIL_SIZE => { value => sub { $self->preferences->thumb_size(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::THUMBNAIL_JPEG_QUALITY => { label => gettext("Site thumbnail jpeg quality") }, $main::GS_THUMBNAIL_JPEG_QUALITY => { value => sub { $self->preferences->th_quality(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::GS_CREATE_RESIZED => { selection => sub { $self->preferences->create_resized(@_) }, choices => [ map { gettext $_ } ( "Use original", "Use resized original" ) ], frame_callback => sub { my( $dlg, $ctrl, $event ) = @_; $dlg->OnCreateResized($event); }, }, $main::SITE_IMG_WIDTH => { label => gettext("Maximum width") }, $main::GS_SITE_IMG_WIDTH => { value => sub { $self->preferences->resize_w(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::SITE_IMG_HEIGHT => { label => gettext("Maximum height") }, $main::GS_SITE_IMG_HEIGHT => { value => sub { $self->preferences->resize_h(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::SITE_IMG_JPEG_QUALITY => { label => gettext("Site image jpeg quality") }, $main::GS_SITE_IMG_JPEG_QUALITY => { value => sub { $self->preferences->quality(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::SITE_IMG_FILTER => { label => gettext("Site image filter") }, $main::GS_SITE_IMG_FILTER => { string_selection => sub { $self->preferences->filter(@_) }, choices => [ qw/Point Box Triangle Hermite Hanning Hamming Blackman Gaussian Quadratic Cubic Catrom Mitchell Lanczos Bessel Sinc/ ], }, $main::SITE_IMG_BLUR => { label => gettext("Site image blur") }, $main::GS_SITE_IMG_BLUR => { value => sub { $self->preferences->blur(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::SITE_IMG_INTERLACE => { label => gettext("Site image interlace") }, $main::GS_SITE_IMG_INTERLACE => { string_selection => sub { $self->preferences->interlace(@_) }, choices => [ qw/None Line Plane Partition JPEG GIF PNG/ ], }, $main::GS_REMOVE_UPLOADED_FROM_SELECTION => { label => gettext("Remove uploaded photo from selection"), value => sub { $self->preferences->remove_uploaded_from_selection(@_) }, }, $main::GS_HD_UPLOAD => { choices => [ map { gettext $_ } @{wxTheApp->upload_hd} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_upload_hd->{$value} }, string_selection => sub { $self->preferences->upload_hd(@_) }, frame_callback => sub { my( $self, $ctrl, $event ) = @_; $self->OnHDUpload($event); }, }, $main::HD_IMG_WIDTH => { label => gettext("Maximum width") }, $main::GS_HD_IMG_WIDTH => { label => gettext("HD image width"), value => sub { $self->preferences->hd_w(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::HD_IMG_HEIGHT => { label => gettext("Maximum height") }, $main::GS_HD_IMG_HEIGHT => { label => gettext("HD image height"), value => sub { $self->preferences->hd_h(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::HD_IMG_JPEG_QUALITY => { label => gettext("HD image jpeg quality") }, $main::GS_HD_IMG_JPEG_QUALITY => { value => sub { $self->preferences->hd_quality(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::HD_IMG_FILTER => { label => gettext("HD image filter") }, $main::GS_HD_IMG_FILTER => { string_selection => sub { $self->preferences->hd_filter(@_) }, choices => [ qw/Point Box Triangle Hermite Hanning Hamming Blackman Gaussian Quadratic Cubic Catrom Mitchell Lanczos Bessel Sinc/ ], }, $main::HD_IMG_BLUR => { label => gettext("HD image blur") }, $main::GS_HD_IMG_BLUR => { value => sub { $self->preferences->hd_blur(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::HD_IMG_INTERLACE => { label => gettext("HD image interlace") }, $main::GS_HD_IMG_INTERLACE => { string_selection => sub { $self->preferences->hd_interlace(@_) }, choices => [ qw/None Line Plane Partition JPEG GIF PNG/ ], }, $main::GS_WMARK_ACTIVATE => { label => gettext("Activate watermark"), value => sub { $self->preferences->watermark_activate(@_) }, frame_callback => sub { my( $self, $ctrl, $event ) = @_; $self->OnWatermark($event); }, }, $main::GS_WMARK_ACTIVATE_HD => { label => gettext("Activate watermark on high definition"), value => sub { $self->preferences->watermark_activate_pwg_high(@_) }, frame_callback => sub { my( $self, $ctrl, $event ) = @_; $self->OnWatermark($event); }, }, $main::WMARK_TEXT => { label => gettext("Text") }, $main::GS_WMARK_TEXT => { value => sub { $self->preferences->watermark_text(@_) }, }, $main::WMARK_TEXT_SIZE => { label => gettext("Text size") }, $main::GS_WMARK_TEXT_SIZE => { value => sub { $self->preferences->watermark_text_size(@_) }, }, $main::WMARK_COLOR => { label => gettext("Color") }, $main::GS_WMARK_COLOR => { string_selection => sub { $self->preferences->watermark_color(@_) }, choices => [ map { gettext $_ } @{wxTheApp->colors} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_colors->{$value} }, }, $main::WMARK_POSITION => { label => gettext("Position") }, $main::GS_WMARK_POSITION => { string_selection => sub { $self->preferences->watermark_position(@_) }, choices => [ map { gettext $_ } @{wxTheApp->positions} ], pre_process => sub { my ( $value ) = @_; wxTheApp->eng_positions->{$value} }, }, $main::WMARK_MARGIN_TOP => { label => gettext("Top margin") }, $main::GS_WMARK_MARGIN_TOP => { value => sub { $self->preferences->watermark_y(@_) }, }, $main::WMARK_MARGIN_LEFT => { label => gettext("Left margin") }, $main::GS_WMARK_MARGIN_LEFT => { value => sub { $self->preferences->watermark_x(@_) }, }, $main::CHUNK_SIZE => { label => gettext("Transfert chunk size") }, $main::GS_CHUNK_SIZE => { value => sub { $self->preferences->chunk_size(@_) }, validator => Wx::Perl::TextValidator->new( '\d' ) }, $main::LANGUAGE => { label => gettext("Choose a language") }, $main::GS_LANGUAGE => { selection => sub { wxTheApp->current_language(@_) }, choices => [ map { sprintf($_->[0], gettext($_->[2])) } @{wxTheApp->available_languages} ], pre_process => sub { my ( $value ) = @_; wxTheApp->available_languages->[$value][1] }, frame_callback => sub { my ( $self, $ctrl, $event ) = @_; Wx::LogMessage( sprintf( "%s : %s", gettext("pLoader needs to be restarted to display the new selected language"), gettext(wxTheApp->available_languages->[$event->GetSelection][2]) ) ); }, }, } ); $self->piwigo_property_list( [ { label => gettext("Photo caption"), }, { label => gettext("Comment"), }, { label => gettext("Author"), }, { label => gettext("File name"), }, { label => gettext("Create date"), }, ] ); $self->exif_properties( [ { label => gettext("Create date"), value => sub { $self->images->current_image->create_date }, readonly => 1, }, { label => gettext("Model"), value => sub { $self->images->current_image->exif_tag('Model') }, readonly => 1, }, { label => gettext("Width"), value => sub { $self->images->current_image->exif_tag('ImageWidth') }, readonly => 1, }, { label => gettext("Height"), value => sub { $self->images->current_image->exif_tag('ImageHeight') }, readonly => 1, }, { label => gettext("Orientation"), value => sub { $self->images->current_image->exif_tag('Orientation') }, readonly => 1, }, { label => "ISO", value => sub { $self->images->current_image->exif_tag('ISO') }, readonly => 1, }, { label => gettext("Shutter speed"), value => sub { $self->images->current_image->exif_tag('ExposureTime') }, readonly => 1, }, { label => gettext("Aperture"), value => sub { $self->images->current_image->exif_tag('ApertureValue') }, readonly => 1, }, { label => gettext("Focal length"), value => sub { $self->images->current_image->exif_tag('FocalLength') }, readonly => 1, }, { label => gettext("Lens"), value => sub { $self->images->current_image->exif_tag('Lens') }, readonly => 1, }, ] ); $self->image_tags( sub { scalar @{$self->images->selection} > 1 ? $self->images->set_image_selection_tags(@_) : $self->images->current_image->site_tags(@_)||[] } ); $self->piwigo_tags( sub { wxTheApp->pwg->tags } ); } sub init_image_types { my ( $self ) = @_; $self->{IMGTYPE} = { 'jpg' => wxBITMAP_TYPE_JPEG, 'gif' => wxBITMAP_TYPE_GIF, 'png' => wxBITMAP_TYPE_PNG, }; } sub init_frame { my ( $self ) = @_; $self->create_toolbar; my $sizer_h = Wx::BoxSizer->new( wxHORIZONTAL ); $self->prepare_transfer_notebook( Wx::Notebook->new( $self, -1, wxDefaultPosition, wxDefaultSize, 0 ) ); $self->imageviewer( Uploader::GUI::wxImageListCtrl->new( { parentwnd => $self->prepare_transfer_notebook, images => $self->images, image_size => $self->preferences->wx_thumb_size, display_mode_cbk => sub { $self->preferences->display_mode(@_) }, columns => $self->piwigo_property_list, } ) ); $self->piwigo_photo_properties_dlg ( Uploader::GUI::wxPhotoProperties->new( { parentwnd => $self, categories => [ @{$self->pwg->categories}], properties => $self->piwigo_photo_properties, tags => { id => $main::PHOTO_PROPERTIES_TAG, choices => $self->piwigo_tags, selection => $self->image_tags, creation_callback => sub { $self->_create_piwigo_tag(@_) }, }, } ) ); $self->piwigo_photo_properties_tags( $self->piwigo_photo_properties_dlg->FindWindow($main::PHOTO_PROPERTIES_TAG) ); $self->tree( $self->piwigo_photo_properties_dlg->FindWindow($main::PHOTO_PROPERTIES_CATEGORIES) ); $self->init_dnd_targets; $self->init_dialogs; $self->show_hide_pwg_categories_empty_msg; if( $self->images->image_count){ $self->on_update_imageviewer_not_empty; } else{ $self->on_update_imageviewer_empty; } $self->prepare_transfer_notebook->AddPage( $self->imageviewer, gettext("Preparation") ); $self->prepare_transfer_notebook->AddPage( $self->upload_progressdlg, gettext("Transfer") ); # the imageviewer has a stretch factor of 1 : expands its size on frame resize $sizer_h->Add( $self->prepare_transfer_notebook, 1, wxEXPAND|wxALL, 2 ); $sizer_h->Add( $self->piwigo_photo_properties_dlg, 0, wxEXPAND|wxALL, 2 ); $self->SetSizer( $sizer_h ); my $new_size = $sizer_h->Fit( $self ); $self->SetMinSize([600, 660]); $self->Center; } sub _create_piwigo_tag { my ( $self, $name ) = @_; if( Wx::MessageBox( sprintf( "%s \"%s\" ?", gettext("Do you want to create"), $name, ), gettext("Piwigo search information"), wxYES_NO | wxICON_QUESTION, ) == wxYES ){ $self->pwg->add_tags($name); $self->pwg->refresh_tags; } } sub on_update_imageviewer_empty { my ( $self ) = @_; $self->piwigo_photo_properties_dlg->SetDisabled; $self->imageviewer->OnEmpty; } sub on_update_imageviewer_not_empty { my ( $self ) = @_; $self->piwigo_photo_properties_dlg->SetEnabled; $self->imageviewer->OnNotEmpty; } sub init_dialogs { my ( $self ) = @_; $self->upload_progressdlg( Uploader::GUI::ImageProgressDlg->new( { parentwnd => $self->prepare_transfer_notebook, progress_column => 2, cancel_cbk => sub { wxTheApp->cancel_all }, clear_cbk => sub { wxTheApp->reset_add_rank }, column_item_data => { 0 => sub { return undef }, 1 => sub { my ( $image ) = @_; $image->{site_name} }, 2 => sub { my ( $image ) = @_; sprintf("%u", $image->{progress}) }, 3 => sub { my ( $image ) = @_; $image->{status} }, }, columns => [ map{ gettext $_ } qw/ Photo Caption Progress Status / ], } ) ); $self->upload_progressdlg->Enable(0); $self->exif_dlg( Uploader::GUI::wxPropertyListDlg->new( { parentwnd => $self, properties => $self->exif_properties, caption => sprintf("%s - EXIF", gettext("Properties")), } ) ); $self->exif_dlg->Hide; $self->preferences_dlg( Uploader::GUI::Preferences->new( { parentwnd => $self, caption => gettext("Preferences"), properties => $self->preferences_properties, } ) ); $self->preferences_dlg->Hide; $self->image_preview_dlg( Uploader::GUI::wxImagePreview->new( { parentwnd => $self, caption => gettext("Preview"), } ) ); $self->destination_category_dlg( Uploader::GUI::wxDestinationCategoryDlg->new( { parentwnd => $self, caption => gettext("Upload to Piwigo"), categories => [@{$self->pwg->categories}], properties => { $main::CHOOSE_DESTINATION => { label => wxTheApp->branding->{'What is the destination category?'} }, $main::DESTINATION_CATEGORIES_OK => { label=>gettext("Upload to Piwigo")}, $main::DESTINATION_CATEGORIES_CANCEL => { label=>gettext("Cancel")}, $main::DESTINATION_CATEGORIES => { id_selection => sub { wxTheApp->transfer_manager->destination_category(@_) }, frame_callback => sub { my ( $dlg, $ctrl, $evt ) = @_; $dlg->FindWindow($main::DESTINATION_CATEGORIES_OK)->Enable(!wxTheApp->transfer_manager->destination_category_is_empty); }, }, }, frame_callback => sub { $self->on_replace_categories }, } ) ); $self->destination_category_dlg->Hide; $self->destination_categories( $self->destination_category_dlg->FindWindow($main::DESTINATION_CATEGORIES) ); $self->categories_treectrl( [ $self->destination_categories, $self->tree, ] ); } sub init_dnd_targets { my ( $self ) = @_; $self->imageviewer->SetDropTarget( DNDImageListDropTarget->new( $self->imageviewer ) ); $self->tree->SetDropTarget( DNDCategoryTreeDropTarget->new( $self->tree ) ); } sub on_photo_properties { my ( $self ) = @_; $self->exif_dlg->Show(1); } sub on_preview { my ( $self ) = @_; $self->image_preview_dlg->Show(1); } sub on_preferences { my ( $self ) = @_; $self->preferences_dlg->Show(1); } sub show_hide_pwg_categories_empty_msg { my ( $self ) = @_; map { $_->GetCount > 1 ? $_->HideEmptyMsg : $_->ShowEmptyMsg; }@{$self->categories_treectrl}; } sub init_menus { 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 ); } ( # workaround : first item does not show bitmap [ 0, "", "", wxTheApp->resource_path('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}, ), wxTheApp->resource_path('mnu_folder_new.png'), ], [ 2+$ID_TREE_CTX_MENU, gettext("Refresh"), sprintf( "Refresh %s list.", wxTheApp->branding->{category}, ), wxTheApp->resource_path('mnu_refresh.png'), ], [ 3+$ID_TREE_CTX_MENU, gettext("Expand all"), sprintf( "Expand %s list.", wxTheApp->branding->{category}, ), wxTheApp->resource_path('mnu_expandall.png'), ], [ 4+$ID_TREE_CTX_MENU, gettext("Collapse all"), sprintf( "Collapse %s list.", wxTheApp->branding->{category}, ), wxTheApp->resource_path('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] ); } ( # workaround : first item does not show bitmap [ 0, "", "", wxTheApp->resource_path('mnu_properties.png'), ], [ 1+$ID_IMAGEVIEWER_CTX_MENU, gettext("Properties"), gettext("Modify photo properties"), wxTheApp->resource_path('mnu_properties.png'), ], [ 2+$ID_IMAGEVIEWER_CTX_MENU, gettext("Preview"), gettext("Display photo preview"), wxTheApp->resource_path('mnu_preview.png'), ], ); $ctx_mnu->Delete(0); $self->imageviewer_mnu( $ctx_mnu ); } sub init_event_handlers { my ( $self ) = @_ ; Wx::Event::EVT_MENU( $self, 101, \&on_add_images ); Wx::Event::EVT_MENU( $self, 102, \&on_remove_images ); Wx::Event::EVT_MENU( $self, 103, \&on_transfer_images ); Wx::Event::EVT_MENU( $self, 104, \&on_preferences ); Wx::Event::EVT_CHOICE( $self, 106, \&on_photo_sel_mode ); Wx::Event::EVT_TREE_SEL_CHANGED( $self, $self->tree, \&on_category_sel_changed ); Wx::Event::EVT_TREE_ITEM_RIGHT_CLICK( $self, $self->tree, \&on_category_right_click ); Wx::Event::EVT_TREE_END_LABEL_EDIT( $self, $self->tree, \&on_category_end_label_edit ); Wx::Event::EVT_LIST_END_LABEL_EDIT( $self, $self->imageviewer, \&on_image_end_label_edit ); Wx::Event::EVT_LIST_ITEM_ACTIVATED( $self, $self->imageviewer, \&on_image_item_activated ); Wx::Event::EVT_LIST_ITEM_SELECTED($self, $self->imageviewer, \&on_image_item_selected) ; Wx::Event::EVT_LIST_ITEM_DESELECTED($self, $self->imageviewer, \&on_image_item_deselected) ; Wx::Event::EVT_LIST_ITEM_RIGHT_CLICK($self, $self->imageviewer, \&on_image_item_right_click) ; Wx::Event::EVT_LIST_KEY_DOWN($self, $self->imageviewer, \&on_image_item_key_down) ; Wx::Event::EVT_CLOSE( $self, \&OnClose ); Wx::Event::EVT_MENU( $self, 1+$ID_TREE_CTX_MENU, \&on_add_category ); Wx::Event::EVT_MENU( $self, 2+$ID_TREE_CTX_MENU, \&on_replace_categories ); Wx::Event::EVT_MENU( $self, 3+$ID_TREE_CTX_MENU, \&on_expand_categories ); Wx::Event::EVT_MENU( $self, 4+$ID_TREE_CTX_MENU, \&on_collapse_categories ); Wx::Event::EVT_MENU( $self, 1+$ID_IMAGEVIEWER_CTX_MENU, \&on_photo_properties ); Wx::Event::EVT_MENU( $self, 2+$ID_IMAGEVIEWER_CTX_MENU, \&on_preview ); Wx::Event::EVT_BUTTON( $self, $main::PHOTO_PROPERTIES_UPLOAD, \&on_transfer_images ); Wx::Event::EVT_BUTTON( $self, $self->imageviewer->add_button->GetId, \&on_add_images ); Wx::Event::EVT_KEY_DOWN($self, \&OnKeyDown ); # only refresh when calling event is finished Wx::Event::EVT_IDLE( $self, sub { my ( $self, $event ) = @_; if ( $self->image_preview_need_refresh and $self->image_preview_dlg->IsShown ){ $self->set_preview_image; $self->image_preview_dlg->Refresh; $self->image_preview_need_refresh(0); } if($self->imageviewer_refresh){ $self->imageviewer->Refresh; $self->imageviewer_refresh(0); } if($self->imageviewer_item_refresh){ # for batch mode map { $self->imageviewer->ItemRefresh( $_ ) } @{$self->imageviewer->selected_items}; $self->imageviewer_item_refresh(0); } if(!$self->selected_images_count and $self->piwigo_photo_properties_dlg->IsEnabled){ $self->piwigo_photo_properties_dlg->SetDisabled; } $self->OnUpdateToolbar; $event->Skip; } ); Wx::Event::EVT_COMMAND( $self, -1, wxTheApp->resize_start_event, sub { my ( $handler, $event ) = @_; my $data = $event->GetData; $handler->upload_progressdlg->Enable(1); $handler->prepare_transfer_notebook->ChangeSelection(1); $handler->upload_progressdlg->add_images($data); wxTheApp->images->remove_processed if $self->preferences->remove_uploaded_from_selection; $handler->refresh_image_context; } ); # resize and transfer progress Wx::Event::EVT_COMMAND( $self, -1, wxTheApp->image_progress_event, sub { my ( $handler, $event ) = @_; my $data = $event->GetData; $handler->upload_progressdlg->update_image_item($data); } ); Wx::Event::EVT_COMMAND( $self, -1, wxTheApp->batch_end_event, sub { my ( $handler, $event ) = @_; my $data = $event->GetData; $handler->upload_progressdlg->batch_end($data); } ); Wx::Event::EVT_COMMAND( $self, -1, wxTheApp->image_done_event, sub { my ( $handler, $event ) = @_; my $data = $event->GetData; if($data->{preferences}{remove_uploaded_from_selection}){ my $gr = $data->{global_rank}; if(defined $gr){ wxTheApp->images->set_to_be_removed($gr) if $data->{transfer_successful}; } } } ); } sub selected_images_count { my ( $self ) = @_; scalar @{$self->imageviewer->selected_items}; } { my $prevfile; my $prevdir; sub on_add_images { my( $self, $event ) = @_; $prevdir = $self->preferences->default_openfile_dir if ( -d $self->preferences->default_openfile_dir); my $dialog = Wx::FileDialog->new ( $self, gettext("Select photos for upload"), $prevdir, $prevfile, sprintf("%s (*.JPG;*.jpg)|*.JPG;*.jpg|%s (*.*)|*.*", gettext("JPEG files"), gettext("All")), wxFD_OPEN|wxFD_MULTIPLE ); my $file_paths = []; if( $dialog->ShowModal != wxID_CANCEL ) { @$file_paths = $dialog->GetPaths; $self->add_images($file_paths) ; } $self->preferences->default_openfile_dir( $dialog->GetDirectory ); $dialog->Destroy; } } sub OnUpdateToolbar { my( $self ) = @_; if($self->preferences_dlg->IsShown){ $self->toolbar->EnableTool(104, 0); } else{ $self->toolbar->EnableTool(104, 1); } if($self->preferences_dlg->IsShown){ $self->toolbar->EnableTool(104, 0); } else{ $self->toolbar->EnableTool(104, 1); } } sub on_remove_images { my( $self, $event ) = @_; $self->images->remove_selection; $self->refresh_image_context; } sub refresh_image_context { my ( $self ) = @_; $self->imageviewer->Refresh; if ($self->imageviewer->is_empty){ $self->image_preview_dlg->image( 0 ); # have to reset $self->piwigo_photo_properties_dlg->ClearProperties; $self->piwigo_photo_properties_tags->ClearAllSelection; $self->images->set_current_image(-1); $self->on_update_imageviewer_empty; } else{ $self->on_update_imageviewer_not_empty; } if(!$self->multi_selection_mode){ $self->on_update_single_selection_mode; } $self->image_preview_dlg->Refresh; $self->exif_dlg->Refresh; } sub add_images { my ( $self, $file_paths ) = @_; $self->show_imageviewer; my $bc = Wx::BusyCursor->new; $self->images->add_images( $file_paths ); } sub on_category_sel_changed { my( $self, $event ) = @_; wxTheApp->transfer_manager->destination_category( $event->GetEventObject->selection_ids ); } sub on_category_right_click { my( $self, $event ) = @_; $self->PopupMenu($self->tree_mnu, wxDefaultPosition); } sub on_category_end_label_edit { my( $self, $event ) = @_; my $label = $event->GetLabel; $label =~ s/^\s+$//; if(defined($label) and !( "" eq $label )){ $self->set_category_label($event) } else{ $event->Veto; } } sub set_category_label { 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->set_info_category( $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 on_image_item_right_click { my( $self, $event ) = @_; $self->PopupMenu($self->imageviewer_mnu, wxDefaultPosition); } sub on_expand_categories { my ( $self, $event ) = @_; my $parent_item = $self->tree->GetSelection; $self->tree->ExpandAllChildren($parent_item); $self->tree->EnsureVisible($parent_item); } sub on_collapse_categories { 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 on_add_category { my ( $self, $event ) = @_; $self->tree->add_category; } sub on_replace_categories { 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->pwg->refresh_categories; $self->tree->categories( $self->pwg->categories ); $self->tree->Populate; $self->show_hide_pwg_categories_empty_msg; } sub on_image_end_label_edit { my( $self, $event ) = @_; my $image = $self->images->get_image($event->GetIndex); $image->site_name( $event->GetLabel ); $self->piwigo_photo_properties_dlg->SetProperties; } sub on_image_item_activated { my( $self, $event ) = @_; $self->current_imageviewer_index( $event->GetIndex ); $self->on_photo_properties; } sub on_image_item_selected { my( $self, $event ) = @_; my $bc = Wx::BusyCursor->new; my $indx = $event->GetIndex; $self->piwigo_photo_properties_dlg->SetEnabled if !$self->piwigo_photo_properties_dlg->IsEnabled; $self->_on_imageviewer_item_selected($indx); $event->Skip; } sub on_image_item_deselected { my( $self, $event ) = @_; my $bc = Wx::BusyCursor->new; $self->_on_imageviewer_item_selection_changed; $event->Skip; } sub _on_imageviewer_item_selected { my ( $self, $index ) = @_; $self->current_imageviewer_index($index); $self->images->set_current_image($index); $self->_on_imageviewer_item_selection_changed; } sub _on_imageviewer_item_selection_changed { my ( $self ) = @_; $self->images->selection( $self->imageviewer->selected_items ); # for batch mode : reset the batch buffer if single selection if($self->multi_selection_mode){ $self->on_update_multi_selection_mode; $self->images->set_image_selection_tags([]); $self->images->selection_privacy_level(-1); $self->images->selection_name(""); $self->images->selection_author(""); $self->images->selection_comment(""); $self->images->selection_create_date(-1); } else{ $self->on_update_single_selection_mode; } # process image_preview in idle time # and when current event is processed # see call to EVT_IDLE $self->image_preview_need_refresh(1); $self->piwigo_photo_properties_dlg->SetProperties if defined $self->piwigo_photo_properties_dlg; $self->exif_dlg->Refresh; $self->piwigo_photo_properties_tags->RefreshChoices; } sub multi_selection_mode { my ( $self ) = @_; $self->images->selection_count > 1; } sub on_update_multi_selection_mode{ my( $self ) = @_; $self->piwigo_photo_properties_dlg->SetMultiSelectionMode( $self->imageviewer->selected_items_count ); } sub on_update_single_selection_mode{ my( $self ) = @_; $self->piwigo_photo_properties_dlg->SetSingleSelectionMode; } sub set_preview_image { my ( $self ) = @_; my $current_image = $self->images->current_image; my $image = Wx::Image->new; $image->LoadFile( $current_image->file, wxBITMAP_TYPE_ANY ); if($self->preferences->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_dlg->image_size( [$image->GetWidth, $image->GetHeight, ] ); $self->image_preview_dlg->image( $image ); } sub on_image_item_key_down { my( $self, $event ) = @_; if(WXK_DELETE == $event->GetKeyCode){ $self->on_remove_images; my $index = $self->current_imageviewer_index < $self->imageviewer->GetItemCount ? $self->current_imageviewer_index : $self->imageviewer->GetItemCount -1 ; $self->imageviewer->select_item( $index ); $self->imageviewer->EnsureVisible( $index ); } } sub on_transfer_images { my( $self, $event ) = @_; # all is default if single selection my $all = 1 unless $self->multi_selection_mode; eval { $self->process_images( $all ); }; } # remove image when uploaded sub UploadImagesViewerRefresh { my ( $self ) = @_; $self->imageviewer->Refresh; if(!$self->imageviewer->GetItemCount){ $self->image_preview_dlg->image(0); $self->on_update_imageviewer_empty; } # reset previous selection $self->images->selection( [] ); if(!$self->multi_selection_mode){ $self->on_update_single_selection_mode; } $self->image_preview_dlg->Refresh; } sub show_imageviewer { my ( $self ) = @_; if(!$self->imageviewer->IsShown){ $self->imageviewer->Show(1); } } sub add_images_viewer_refresh { my ( $self, $wx_thumb_file ) = @_; $self->imageviewer->add_image($wx_thumb_file); $self->on_update_imageviewer_not_empty if $self->imageviewer->GetItemCount; } sub GetWxBitmapType { my ( $self, $type ) = @_; $self->{IMGTYPE}->{$type}; } sub delete_images_viewer_refresh { my ( $self, $indx ) = @_; $self->imageviewer->delete_image($indx); Wx::Yield(); } # prepare and upload image_selection sub process_images { my ( $self, $all_images ) = @_; return if wxTheApp->check_upload; return if $self->images->is_empty; if( wxTheApp->transfer_manager->destination_category_is_empty ){ return unless $self->destination_category_dlg->ShowModal != wxID_CANCEL; } # check if update/insert my $existing = wxTheApp->check_existing($all_images); $self->show_reupload_options if $existing; wxTheApp->start_resize($all_images); } sub show_reupload_options { my ( $self ) = @_; Uploader::GUI::wxImageReuploadDlg->new( { title => gettext("Photo update management"), properties => { $main::ID_REUPLOAD_ACTION_FILES => { selection=>sub {$self->preferences->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->preferences->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->preferences->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->preferences->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(); } sub OnClose { my $self = shift; # Restaure previous log wnd Wx::Log::SetActiveTarget( $self->oldlogwnd ); #destroy hidden dialogs $self->preferences_dlg->Destroy; $self->image_preview_dlg->Destroy; $self->exif_dlg->Destroy; $self->destination_category_dlg->Destroy; wxTheApp->stop_all; $self->Destroy; } sub create_toolbar { my( $self ) = @_; my $tb = Wx::ToolBar->new( $self, -1, wxDefaultPosition, [600, -1], wxTB_FLAT|wxTB_TEXT ); $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, $_->[6] ); $tb->EnableTool( $_->[0], $_->[4]); } ( [ 101, gettext("Add photos"), wxTheApp->resource_path('tb_add.png'), wxBITMAP_TYPE_PNG, 1, wxTheApp->resource_path('tb_add.png'), gettext("Add photos for resizing and uploading") ], [ 102, gettext("Remove selected photos"), wxTheApp->resource_path('tb_remove.png'), wxBITMAP_TYPE_PNG, 1, wxTheApp->resource_path('tb_remove.png'), gettext("Remove selected photos. Original files are not deleted") ], [ 104, gettext("Preferences"), wxTheApp->resource_path('tb_settings.png'), wxBITMAP_TYPE_PNG, 0, wxTheApp->resource_path('tb_settings.png'), gettext("Change global settings") ], ); $tb->AddSeparator; $tb->AddControl( Wx::Choice->new( $tb, 106, wxDefaultPosition, [300, -1], [], ) ); my $ch = $tb->FindWindow(106); $ch->SetToolTip(gettext("How photo selection is displayed")); map { $ch->Append(gettext($_), $_); }( "Thumbnail and caption", "Thumbnail", "Property list" ); $ch->SetStringSelection(gettext($self->preferences->display_mode)); $tb->Realize; $self->toolbar( $tb ); $self->SetToolBar($tb); return $tb; } sub on_photo_sel_mode { my ( $self, $event )= @_; $self->preferences->display_mode( $event->GetClientData ); $self->imageviewer->change_display_mode(1); } sub create_textctrl { my( $self, $text, $size ) = @_; return $self->_create_textctrl( $self, $text, $size ); } 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 on_resize_progress_image { my ( $self, $image ) = @_; $self->upload_progressdlg->update_image_item($image); } 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->add_images($files) ; } 1; package DNDCategoryTreeDropTarget; use base qw(Wx::TextDropTarget Class::Accessor::Fast); use Data::Dumper; use Wx qw/ wxDragNone wxDragCopy wxDragMove wxTheApp /; __PACKAGE__->mk_accessors( qw/ tree / ); sub new { my ( $class, $tree ) = @_; my $self = $class->SUPER::new(); $self->tree($tree); 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; wxTheApp->transfer_manager->destination_category( [ map { $self->tree->GetPlData( $_ )->{id}; } @items ] ); wxTheApp->images->selection($VAR1); wxTheApp->frame->process_images ; } }; } 1;