summaryrefslogtreecommitdiff
path: root/lib/gcstar/GCMainWindow.pm
diff options
context:
space:
mode:
authorJörg Frings-Fürst <jff@merkur>2014-07-06 15:20:38 +0200
committerJörg Frings-Fürst <jff@merkur>2014-07-06 15:20:38 +0200
commit126bb8cb6b93240bb4d3a2b816b74c286c3d422b (patch)
treee66e1dfe77d53a52539489765c88d23e4423ae27 /lib/gcstar/GCMainWindow.pm
Imported Upstream version 1.7.0upstream/1.7.0
Diffstat (limited to 'lib/gcstar/GCMainWindow.pm')
-rw-r--r--lib/gcstar/GCMainWindow.pm4124
1 files changed, 4124 insertions, 0 deletions
diff --git a/lib/gcstar/GCMainWindow.pm b/lib/gcstar/GCMainWindow.pm
new file mode 100644
index 0000000..b08bd40
--- /dev/null
+++ b/lib/gcstar/GCMainWindow.pm
@@ -0,0 +1,4124 @@
+package GCMainWindow;
+
+###################################################
+#
+# Copyright 2005-2010 Christian Jodar
+#
+# This file is part of GCstar.
+#
+# GCstar 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GCstar 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 GCstar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+#
+###################################################
+
+use utf8;
+
+use strict;
+use Gtk2;
+
+{
+ package GCFrame;
+
+ use Glib::Object::Subclass
+ Gtk2::Window::
+ ;
+
+ @GCFrame::ISA = ('Gtk2::Window');
+
+ # Internal modules
+ use GCDialogs;
+ use GCPlugins;
+ use GCModel;
+ use GCGraphicComponents::GCBaseWidgets;
+ use GCMenu;
+ use GCPanel;
+ use GCUtils 'glob';
+ use GCOptions;
+ use GCStyle;
+ use GCBorrowings;
+ use GCExtract;
+ use GCExport;
+ use GCImport;
+ use GCSplash;
+ use GCLang;
+ use GCDisplay;
+ use GCData;
+ use GCMail;
+ use GCBookmarks;
+ use GCStats;
+
+ use GCItemsLists::GCTextLists;
+ use GCItemsLists::GCImageLists;
+
+ #use GCGenres;
+
+ # Pragmas
+ use filetest 'access';
+
+ # External modules
+ use LWP;
+ use URI::Escape;
+ use File::Basename;
+ use File::Spec;
+ use File::Copy;
+ use File::Path;
+ use IO::Handle;
+ use Storable qw(store_fd fd_retrieve);
+ use File::Temp qw(tempdir);
+ use Encode;
+
+ sub showMe
+ {
+ my $self = shift;
+ $self->present;
+ }
+
+ sub beforeDestroy
+ {
+ my $self = shift;
+ $self->leave;
+ return 1;
+ }
+
+ sub savePreferences
+ {
+ my $self = shift;
+ $self->{itemsView}->savePreferences($self->{model}->{preferences})
+ if $self->{itemsView} && $self->{model} && $self->{model}->{preferences};
+ }
+
+ sub leave
+ {
+ my $self = shift;
+
+ return if !$self->checkAndSave;
+
+ my ($width, $height) = $self->get_size;
+ $self->{options}->file('')
+ if $self->{options}->noautoload;
+ $self->{options}->width($width);
+ $self->{options}->height($height);
+ $self->{options}->split($self->{pane}->get_position) if ($self->{pane});
+ $self->{options}->listPaneSplit($self->{listPane}->get_position) if ($self->{listPane});
+
+ $self->{options}->save;
+ $self->savePreferences;
+ $self->{model}->save
+ if $self->{model};
+
+ $self->{items}->clean;
+
+ if (($^O !~ /win32/i) && ($self->{searchJob}->{pid}))
+ {
+ store_fd {type => 'exit'}, $self->{searchJob}->{command};
+ my $command = $self->{searchJob}->{command};
+ print $command "EXIT\n";
+ close $self->{searchJob}->{command};
+ close $self->{searchJob}->{data};
+ kill 9, $self->{searchJob}->{pid};
+ wait;
+ }
+ $self->{menubar}->save();
+ $self->destroy;
+ }
+
+ sub deleteCurrentItem
+ {
+ my $self = shift;
+ my $response = 'yes';
+ my $confirm = 0;
+
+ if ($self->{options}->confirm)
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'question',
+ 'cancel',
+ $self->{items}->{multipleMode} ? $self->{lang}->{RemoveConfirmPlural} :
+ $self->{lang}->{RemoveConfirm});
+
+ my $delButton = Gtk2::Button->new_from_stock('gtk-delete');
+ $delButton->can_default(1);
+ $dialog->add_action_widget($delButton, 'yes');
+ $delButton->show_all;
+ $dialog->set_default_response('yes');
+ $dialog->set_position('center-on-parent');
+ my $check = new Gtk2::CheckButton($self->{lang}->{OptionsDontAsk});
+ $dialog->vbox->pack_start($check,0,0,5);
+ $dialog->vbox->pack_start(Gtk2::HSeparator->new,0,0,5);
+ $dialog->vbox->show_all;
+ $response = $dialog->run;
+ $confirm = ($check->get_active ? 0 : 1);
+ $dialog->destroy;
+ }
+
+ if ($response eq 'yes')
+ {
+ $self->markAsUpdated;
+ $self->{items}->removeCurrentItems;
+ $self->setNbItems;
+ $self->{options}->confirm($confirm);
+ $self->{panel}->hide if ! $self->{itemsView}->getNbItems;
+ }
+ }
+
+ sub newItem
+ {
+
+ my ($self, $self2, $noDisplay) = @_;
+ $self = $self2 if $self2;
+
+ #$self->{options}->lockPanel(0);
+ # Uncomment to memorize
+ #$self->{panel}->changeState($self->{panel}, 0);
+ $self->{menubar}->setCollectionLock(0);
+
+ my $info = $self->{model}->getDefaultValues;
+ $self->addItem($info, 1, undef, 1);
+
+ $self->displayInWindow(undef, 'item', 1)
+ if $self->{panel}->isReadOnly && !$noDisplay;
+ }
+
+ sub selectAll
+ {
+ my $self = shift;
+
+ $self->{itemsView}->selectAll;
+ }
+
+ sub duplicateItem
+ {
+
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ my @added;
+ my $indexes = $self->{itemsView}->getCurrentItems;
+ foreach my $idx(@$indexes)
+ {
+ my $newItem = Storable::dclone(($self->{items}->getItemsListFiltered)->[$idx]);
+ # Duplicate all the managed pictures
+ foreach (@{$self->{model}->{managedImages}})
+ {
+ (my $origPic = $newItem->{$_}) =~ /^(.*)(\.[^\.]*)$/;
+ next if !$origPic;
+ my ($base, $suffix) = ($1, $2);
+ my $newPic;
+ my $count = 0;
+ $count++ while (-e ($newPic = $base.'_'.$count.$suffix));
+ copy($origPic,$newPic);
+ $self->{items}->markToBeAdded($newPic);
+ $newItem->{$_} = $newPic;
+ }
+ push @added, $newItem;
+ }
+
+ foreach my $info(@added)
+ {
+ $self->addItem($info, 1);
+ }
+ }
+
+ sub loadUrl
+ {
+ my ($self, $url, $existing) = @_;
+
+ my $baseUrl;
+ my $plugin;
+
+ foreach (values %{$self->{model}->getAllPlugins})
+ {
+ ($baseUrl = $_->getItemUrl) =~ s/http:\/\/(w{3})?//;
+ $plugin = $_;
+
+ last if ((($baseUrl) && ($url =~ m/$baseUrl/))
+ && (($plugin->testURL($url)) || (!$plugin->needsLanguageTest())));
+ }
+
+ return unless $url =~ m/$baseUrl/;
+ $self->setWaitCursor($self->{lang}->{StatusGetInfo});
+
+ # Create a new item if we're not wanting to update the existing one
+ $self->newItem
+ if (!$existing);
+ $plugin->{bigPics} = $self->{options}->bigPics;
+ my $info = $plugin->loadUrl($url);
+ $self->restoreCursor;
+ return if ! $info->{$self->{model}->{commonFields}->{title}};
+
+ if ($existing)
+ {
+ # We only want to update blank fields during the refresh
+ foreach my $field (keys(%{$info}))
+ {
+ # Loop through fields returned and check if they were previously blank
+ # or an empty array. Only exception is the press rating field,
+ # which we'll always want to update
+ if ((($self->{panel}->$field) && ($field ne 'ratingpress'))
+ && !((ref($self->{panel}->$field)) && ( @{$self->{panel}->$field} == 0)))
+ {
+ # Ignore field
+ $info->{$field} = $self->{ignoreString};
+ }
+ }
+
+ }
+
+ $self->addItem($info, 0);
+ }
+
+ sub refreshItemForPanel
+ {
+ my ($self, $panel, $url) = @_;
+
+ $self->{previousPanel} = $self->{panel};
+ $self->{panel} = $panel;
+
+ # Clear the ## part off the end of stored urls
+ $url =~ s/#.*$//;
+
+ # Fetch information from the stored url
+ $self->loadUrl($url, 1);
+
+ $self->{panel} = $self->{previousPanel} if ($panel);
+ delete $self->{previousPanel};
+ }
+
+ sub searchItemForPanel
+ {
+ my ($self, $panel, $pluginType) = @_;
+
+ if ($panel)
+ {
+ $self->{previousPanel} = $self->{panel};
+ $self->{panel} = $panel;
+ }
+ my $query;
+ my $field;
+ my %queries;
+ foreach (@{$self->{model}->getSearchFields})
+ {
+ $queries{$_} = $self->{panel}->getValue($_);
+ if (!$query)
+ {
+ $query = $queries{$_};
+ $field = $_;
+ }
+ }
+ $self->searchItem($query, $pluginType, 0, $field, \%queries);
+ $self->{panel} = $self->{previousPanel} if ($panel);
+ delete $self->{previousPanel};
+ }
+
+ sub searchItem
+ {
+ my ($self, $query, $pluginType, $currentPlugin, $searchField, $queries) = @_;
+
+ # Search for information on websites thanks to plugins
+
+ # This will contain the plugin that should be used to fetch the information
+ my $plugin;
+ # When information are previewed, they are stored in this map to avoid getting twice
+ # the same information. The key is the index in the list of results.
+ $self->{previewCache} = {};
+
+ # Fetch mode, if not forced take it from prefs
+ $pluginType=$self->{model}->{preferences}->plugin if !$pluginType;
+
+ # Many sites per field
+ if ($pluginType eq 'multiperfield')
+ {
+ my $whenMultiple='Ask';
+ my $info=$self->searchItemInfoWithPluginList($query, $whenMultiple, $currentPlugin, $searchField, $queries);
+ $self->addItem($info, 0);
+ return;
+ }
+ # Many sites
+ elsif ($pluginType eq 'multi')
+ {
+ # Here we get the next plugin in the list if the user set a list
+ my $pluginName = $self->getDialog('MultiSite')->getPlugin($currentPlugin);
+ $plugin = $self->{model}->getPlugin($pluginName);
+ if (!$plugin)
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'ok',
+ $self->{lang}->{MultiSiteEmptyError});
+
+ $dialog->set_position('center-on-parent');
+ my $response = $dialog->run;
+ $dialog->destroy;
+ return;
+ }
+ }
+ # Ask (within in a specific list)
+ elsif ($pluginType eq 'multiask')
+ {
+ # Display the list of plugins to use
+ $self->getDialog('PluginsAsk')->setModel($self->{model},
+ $self->{model}->{preferences}->multisite);
+ $self->getDialog('PluginsAsk')->query($query, $searchField, $queries);
+ $self->getDialog('PluginsAsk')->show;
+ $plugin = $self->getDialog('PluginsAsk')->plugin;
+ return if !$plugin;
+ ($query, $searchField) = $self->getDialog('PluginsAsk')->query;
+ }
+ # Ask (All sites)
+ # Also activated if forced by user (e.g. if a search returned no result)
+ elsif ($pluginType eq 'ask' || ! $self->{plugin})
+ {
+ $self->getDialog('Plugins')->query($query, $searchField, $queries);
+ $self->getDialog('Plugins')->show;
+ $plugin = $self->getDialog('Plugins')->plugin;
+ return if !$plugin;
+ ($query, $searchField) = $self->getDialog('Plugins')->query;
+ }
+ # Explicit plugin
+ else
+ {
+ $plugin = $self->{plugin};
+ }
+
+ # Loop through search/select routine for plugin's desired number of passes
+ for (my $pass = 1; $pass <= $plugin->getNumberPasses; $pass++)
+ {
+ # Force values of query and search field if they are incompatible with current plugin
+ my $compatible = 1;
+ $compatible = grep /^$searchField$/, @{$plugin->getSearchFieldsArray}
+ if $searchField;
+ if (!$compatible)
+ {
+ # If it is not, we use the 1st compatible one
+ $searchField = $plugin->getSearchFieldsArray->[0];
+ $query = $queries->{$searchField};
+ }
+
+ # If the search cannot be stopped
+ if (! $self->{options}->searchStop)
+ {
+ # Prepare the plugin with required information
+ $plugin->setProxy($self->{options}->proxy);
+ $plugin->setCookieJar($self->{options}->cookieJar);
+
+ # Title to search
+ $plugin->{title} = $query;
+ $plugin->{pass} = $pass;
+ # Type set to load means a search on the website
+ $plugin->{type} = 'load';
+ $plugin->{urlField} = $self->{model}->{commonFields}->{url};
+ $plugin->{bigPics} = $self->{options}->bigPics;
+ $plugin->{searchField} = $searchField;
+
+ $self->setWaitCursor($self->{lang}->{StatusSearch}.' ('.$plugin->getName.')');
+ # Perform the load
+ $plugin->load;
+ $self->restoreCursor;
+ }
+ else
+ {
+ # Prepare the information for search
+ # Nearly the same as the ones set directly on plugin when search cannot be stopped
+ my $info = {
+ name => $plugin->getName,
+ model => $self->{model}->getName,
+ proxy => $self->{options}->proxy,
+ cookieJar => $self->{options}->cookieJar,
+ query => $query,
+ field => $searchField,
+ type => 'load',
+ urlField => $self->{model}->{commonFields}->{url},
+ bigPics => $self->{options}->bigPics,
+ pass => $pass,
+ nextUrl => $plugin->{nextUrl}
+ };
+ # Send the information to the other process that will perform the actual search
+ store_fd $info, $self->{searchJob}->{command};
+ my $getInfo = 0;
+ # Dialog with progress bar and cancel button
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'info',
+ 'cancel',
+ $self->{lang}->{StatusSearch} . "\n" . $plugin->getName);
+
+ my $progress = new Gtk2::ProgressBar;
+ $dialog->vbox->pack_start($progress,1,1,10);
+ $progress->set_pulse_step(0.05);
+ $progress->show_all;
+ my $pulseTimeout = Glib::Timeout->add(50 , sub {
+ # $getInfo will be set by the watcher that looks for data from the other process
+ return 0 if $getInfo;
+ $progress->pulse;
+ return 1;
+ });
+
+ # If there is already something in the pipe...
+ my $rin = '';
+ vec($rin,fileno($self->{searchJob}->{data}),1) = 1;
+ if (select($rin,undef,undef,0.01))
+ {
+ # ... We just get it to empty the pipe
+ my $trash = fd_retrieve($self->{searchJob}->{data});
+ }
+ # Monitor the pipe from process
+ my $watch = Glib::IO->add_watch(fileno($self->{searchJob}->{data}),
+ 'in',
+ sub {
+ return if !$dialog;
+ # Close the dialog window
+ $dialog->response('cancel');
+ # Set variable to indicate we got some data
+ $getInfo = 1;
+ return 0;
+ });
+
+ $dialog->set_position('center-on-parent');
+ # It will run until we get some information, or the user press cancel
+ $dialog->run if !$getInfo;
+ # Stop everything
+ Glib::Source->remove($watch);
+ Glib::Source->remove($pulseTimeout);
+ $dialog->destroy;
+ my $command = $self->{searchJob}->{command};
+ # Did we get something?
+ if ($getInfo)
+ {
+ # Then we read the data from pipe
+ $plugin = fd_retrieve($self->{searchJob}->{data});
+ # And we inform the process we got it
+ print $command "OK\n";
+ }
+ else
+ {
+ # Tell the process the information is no more required
+ print $command "STOP\n";
+ return;
+ }
+ }
+
+ # Get the number of retrieved items
+ my $itemNumber = $plugin->getItemsNumber();
+ $self->{defaultPictureSuffix} = $plugin->getDefaultPictureSuffix;
+
+ # If there is none, we will use next plugin ('multi' mode) or ask the user
+ # to select another one
+ if ($itemNumber == 0)
+ {
+ my $force = 0;
+ my $idx = 0;
+ if (($pluginType eq 'multi') && (($currentPlugin + 1) < $self->getDialog('MultiSite')->getPluginsNumber))
+ {
+ $idx = $currentPlugin + 1;
+ }
+ else
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'yes-no',
+ $self->{lang}->{NoItemFound});
+
+ $dialog->set_position('center-on-parent');
+ my $response = $dialog->run;
+ $dialog->destroy;
+ return if $response ne 'yes';
+ $pluginType = 'ask';
+ }
+ # Call the same method again with new plugin
+ $self->searchItem($query, $pluginType, $idx, $searchField, $queries);
+ }
+ # Just one item, directly fetch its information
+ elsif ($itemNumber == 1)
+ {
+ if ($pass == $plugin->getNumberPasses)
+ {
+ # Final pass, so grab item info
+ $self->downloadItemInfoFromPlugin($plugin, 0);
+ }
+ else
+ {
+ # Still have more passes to do, find the next url to parse
+ my @items = $plugin->getItems();
+ $plugin->{nextUrl} = $items[0]->{nextUrl};
+ }
+ }
+ else
+ {
+ # Should we display the Next button to perform the search on the next website?
+ my $withNext = 0;
+ $withNext = 1 if ($pluginType eq 'multi') && (($currentPlugin + 1) < $self->getDialog('MultiSite')->getPluginsNumber);
+
+ # Get an array with all the results
+ my @items = $plugin->getItems();
+ # Dialog that will contain them
+ my $resultsDialog = $self->getDialog('Results');
+ $resultsDialog->setModel($self->{model}, $self->{model}->{fieldsInfo});
+ $resultsDialog->setMultipleSelection($pass == $plugin->getNumberPasses);
+ $resultsDialog->setWithNext($withNext);
+ $resultsDialog->setSearchPlugin($plugin);
+ $resultsDialog->setList('', @items);
+ my $next = $resultsDialog->show;
+ # If the user selected one of the results
+ if ($resultsDialog->{validated})
+ {
+ if ($pass == $plugin->getNumberPasses)
+ {
+ # Final pass, so grab item information
+
+ my $indexes = $resultsDialog->getItemsIndexes;
+ # Fetch the information from the website
+ $self->downloadItemInfoFromPlugin($plugin, $indexes->[0]);
+ # If more than one were selected, other items are automatically created
+ # and their information fetched
+ if (scalar(@$indexes) > 1)
+ {
+ shift @$indexes;
+ foreach my $idx(@$indexes)
+ {
+ if ((exists $self->{previousPanel})
+ && ($self->{previousPanel}->isReadOnly))
+ {
+ $self->updateSelectedItemInfoFromGivenPanelAndSelect($self->{panel}, $idx);
+ }
+ if ($pass == $plugin->getNumberPasses)
+ {
+ $self->newItem(undef, 0);
+ }
+ $self->downloadItemInfoFromPlugin($plugin, $idx);
+ }
+ }
+ }
+ else
+ {
+ # More passes to do, so find next url to parse
+ # TODO - make sure we stop users from selecting more than one item
+ # on anything but the last pass. At the moment this will ignore
+ # everything but the first item selected
+ if ($resultsDialog->{validated})
+ {
+ my $indexes = $resultsDialog->getItemsIndexes;
+ $plugin->{nextUrl} = @items[$indexes->[0]]->{nextUrl};
+ }
+ }
+ }
+ # If the user pressed Next button, call the same method again with next plugin
+ elsif ($next)
+ {
+ $self->searchItem($query, $pluginType, $currentPlugin + 1, $searchField, $queries);
+ }
+ }
+ }
+ }
+
+ sub searchItemInfoWithPluginList
+ {
+ my ($self, $query, $whenMultiple, $currentPlugin, $searchField, $queries, $otherSourcesNames,$otherSources) = @_;
+
+ # Search for information on websites thanks to plugins
+ # Get info for each fields in the priority asked by $pluginListOrderPerField
+
+ # Get the list of fields and plugin
+ my $myHelperSourcesPerField=$self->getDialog('MultiSitePerField');
+ $myHelperSourcesPerField->setModel($self->{model});
+ my $otherSourcesMenu;
+ $otherSourcesMenu=map { value=>$_,displayed =>$otherSourcesNames->{$_} }, keys %$otherSourcesNames if ($otherSourcesNames);
+ $myHelperSourcesPerField->setSourceList(1, $otherSourcesMenu);
+
+ $myHelperSourcesPerField->resetCurrentFetchingStatus;
+ my $infoPerPlugin={};
+ PLUGIN: while(1){
+ # This will contain the plugin that should be used to fetch the information
+ my $pluginName=$myHelperSourcesPerField->getNextSourceNeeded;
+ last PLUGIN unless ($pluginName);
+ if(grep /$pluginName/, keys %$otherSources)
+ {
+ $infoPerPlugin->{$pluginName}=$otherSources->{$pluginName};
+ }
+ else
+ {
+ my $plugin=$myHelperSourcesPerField->getPlugin($pluginName);
+ # Now we have the plugin to use
+ $infoPerPlugin->{$pluginName} = $self->searchOneItemInfoWithPlugin($query, $whenMultiple, $plugin, $searchField, $queries);
+ }
+ # TODO update the query to avoid re asking if multiple results for next plugin i.e. add info from fetched data to the query
+
+ # Update fields fetched with this plugin
+ $myHelperSourcesPerField->doneWithSourceName($pluginName,$infoPerPlugin->{$pluginName});
+ }
+ my $info=$myHelperSourcesPerField->joinInfo($infoPerPlugin);
+ # TODO manage multiple url, for instance we throw them
+ return $info;
+ }
+
+ sub searchOneItemInfoWithPlugin
+ {
+ my ($self, $query, $whenMultiple, $plugin, $searchField, $queries) = @_;
+ # Search for information on websites thanks to plugins
+ # Return $info
+
+ # When information are previewed, they are stored in this map to avoid getting twice
+ # the same information. The key is the index in the list of results.
+ $self->{previewCache} = {};
+ my $info={};
+ # Loop through search/select routine for plugin's desired number of passes
+ for (my $pass = 1; $pass <= $plugin->getNumberPasses; $pass++)
+ {
+ # Force values of query and search field if they are incompatible with current plugin
+ my $compatible = grep /^$searchField$/, @{$plugin->getSearchFieldsArray};
+ if (!$compatible)
+ {
+ # If it is not, we use the 1st compatible one
+ $searchField = $plugin->getSearchFieldsArray->[0];
+ $query = $queries->{$searchField};
+ }
+
+ # If the search cannot be stopped
+ if (! $self->{options}->searchStop)
+ {
+ # Prepare the plugin with required information
+ $plugin->setProxy($self->{options}->proxy);
+ $plugin->setCookieJar($self->{options}->cookieJar);
+
+ # Title to search
+ $plugin->{title} = $query;
+ $plugin->{pass} = $pass;
+ # Type set to load means a search on the website
+ $plugin->{type} = 'load';
+ $plugin->{urlField} = $self->{model}->{commonFields}->{url};
+ $plugin->{bigPics} = $self->{options}->bigPics;
+ $plugin->{searchField} = $searchField;
+
+ $self->setWaitCursor($self->{lang}->{StatusSearch}.' ('.$plugin->getName.')');
+ # Perform the load
+ $plugin->load;
+ $self->restoreCursor;
+ }
+ else
+ {
+ # Prepare the information for search
+ # Nearly the same as the ones set directly on plugin when search cannot be stopped
+ my $info = {
+ name => $plugin->getName,
+ model => $self->{model}->getName,
+ proxy => $self->{options}->proxy,
+ cookieJar => $self->{options}->cookieJar,
+ query => $query,
+ field => $searchField,
+ type => 'load',
+ urlField => $self->{model}->{commonFields}->{url},
+ bigPics => $self->{options}->bigPics,
+ pass => $pass,
+ nextUrl => $plugin->{nextUrl}
+ };
+ # Send the information to the other process that will perform the actual search
+ store_fd $info, $self->{searchJob}->{command};
+ my $getInfo = 0;
+ # Dialog with progress bar and cancel button
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'info',
+ 'cancel',
+ $self->{lang}->{StatusSearch} . "\n" . $plugin->getName);
+
+ my $progress = new Gtk2::ProgressBar;
+ $dialog->vbox->pack_start($progress,1,1,10);
+ $progress->set_pulse_step(0.05);
+ $progress->show_all;
+ my $pulseTimeout = Glib::Timeout->add(50 , sub {
+ # $getInfo will be set by the watcher that looks for data from the other process
+ return 0 if $getInfo;
+ $progress->pulse;
+ return 1;
+ });
+
+ # If there is already something in the pipe...
+ my $rin = '';
+ vec($rin,fileno($self->{searchJob}->{data}),1) = 1;
+ if (select($rin,undef,undef,0.01))
+ {
+ # ... We just get it to empty the pipe
+ my $trash = fd_retrieve($self->{searchJob}->{data});
+ }
+ # Monitor the pipe from process
+ my $watch = Glib::IO->add_watch(fileno($self->{searchJob}->{data}),
+ 'in',
+ sub {
+ return if !$dialog;
+ # Close the dialog window
+ $dialog->response('cancel');
+ # Set variable to indicate we got some data
+ $getInfo = 1;
+ return 0;
+ });
+
+ $dialog->set_position('center-on-parent');
+ # It will run until we get some information, or the user press cancel
+ $dialog->run if !$getInfo;
+ # Stop everything
+ Glib::Source->remove($watch);
+ Glib::Source->remove($pulseTimeout);
+ $dialog->destroy;
+ my $command = $self->{searchJob}->{command};
+ # Did we get something?
+ if ($getInfo)
+ {
+ # Then we read the data from pipe
+ $plugin = fd_retrieve($self->{searchJob}->{data});
+ # And we inform the process we got it
+ print $command "OK\n";
+ }
+ else
+ {
+ # Tell the process the information is no more required
+ print $command "STOP\n";
+ return;
+ }
+ }
+
+ # Get the number of retrieved items
+ my $itemNumber = $plugin->getItemsNumber();
+ $self->{defaultPictureSuffix} = $plugin->getDefaultPictureSuffix;
+ # If there is none, we will use next plugin ('multi' mode) or ask the user
+ # to select another one
+ if ($itemNumber == 0)
+ {
+
+ }
+ # Just one item, directly fetch its information
+ elsif ($itemNumber == 1 || $whenMultiple eq 'TakeFirst')
+ {
+ if ($pass == $plugin->getNumberPasses)
+ {
+ # Final pass, so grab item info
+ $info=$self->fetchItemInfoFromPlugin($plugin, 0);
+ }
+ else
+ {
+ # Still have more passes to do, find the next url to parse
+ my @items = $plugin->getItems();
+ $plugin->{nextUrl} = $items[0]->{nextUrl};
+ }
+ }
+ else
+ {
+ if($whenMultiple eq 'Ask')
+ {
+ # Get an array with all the results
+ my @items = $plugin->getItems();
+ # Dialog that will contain them
+ my $resultsDialog = $self->getDialog('Results');
+ $resultsDialog->setModel($self->{model}, $self->{model}->{fieldsInfo});
+ $resultsDialog->setMultipleSelection(0);
+ $resultsDialog->setWithNext(0);
+ $resultsDialog->setSearchPlugin($plugin);
+ $resultsDialog->setList('', @items);
+ my $next = $resultsDialog->show;
+ # If the user selected one of the results
+ if ($resultsDialog->{validated})
+ {
+ if ($pass == $plugin->getNumberPasses)
+ {
+ # Final pass, so grab item information
+ my $indexes = $resultsDialog->getItemsIndexes;
+ # Fetch the information from the website
+ $info=$self->fetchItemInfoFromPlugin($plugin, $indexes->[0]);
+ }
+ else
+ {
+ # More passes to do, so find next url to parse
+ if ($resultsDialog->{validated})
+ {
+ my $indexes = $resultsDialog->getItemsIndexes;
+ $plugin->{nextUrl} = @items[$indexes->[0]]->{nextUrl};
+ }
+ }
+ }
+ }
+ }
+ }
+ return $info;
+ }
+
+ sub fetchItemInfoFromPlugin
+ {
+ my ($self, $plugin, $idx) = @_;
+
+ my $info;
+
+ # Gets from cache if we already previewed it
+ if ($self->{previewCache}->{$idx})
+ {
+ $info = $self->{previewCache}->{$idx};
+ }
+ else
+ {
+ # Tell the plugin what we want
+ $plugin->{wantedIdx} = $idx;
+ $plugin->{type} = 'info';
+
+ if (! $self->{options}->searchStop)
+ {
+ $self->setWaitCursor($self->{lang}->{StatusGetInfo});
+ # Fetch the information
+ $info = $plugin->getItemInfo;
+ $self->restoreCursor;
+ }
+ else
+ {
+ # Send directly the plugin to the other process
+ store_fd $plugin, $self->{searchJob}->{command};
+ # Indicates if something was returned by plugin
+ my $getInfo = 0;
+ my $dialogGet = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'info',
+ 'cancel',
+ $self->{lang}->{StatusGetInfo});
+
+ my $progress = new Gtk2::ProgressBar;
+ $dialogGet->vbox->pack_start($progress,1,1,10);
+ $progress->set_pulse_step(0.05);
+ $progress->show_all;
+ my $pulseTimeout = Glib::Timeout->add(50 , sub {
+ # $getInfo will be set by the watcher that looks for data from the other process
+ return 0 if $getInfo;
+ $progress->pulse;
+ return 1;
+ });
+
+ # Monitor the pipe from process
+ my $watch = Glib::IO->add_watch(fileno($self->{searchJob}->{data}),
+ 'in',
+ sub {
+ return if !$dialogGet;
+ $dialogGet->response('cancel');
+ # Set the flag when we got something
+ $getInfo = 1;
+ return 0;
+ });
+
+ $dialogGet->set_position('center-on-parent');
+ # It will run until we get some information, or the user press cancel
+ $dialogGet->run if !$getInfo;
+ # Stop everything
+ Glib::Source->remove($watch);
+ Glib::Source->remove($pulseTimeout);
+ $dialogGet->destroy;
+ my $command = $self->{searchJob}->{command};
+ # Did we get something?
+ if ($getInfo)
+ {
+ # Then we read the data from pipe
+ $info = fd_retrieve($self->{searchJob}->{data});
+ # And we inform the process we got it
+ print $command "OK\n";
+ }
+ else
+ {
+ # Tell the process the information is no more required
+ print $command "STOP\n";
+ }
+ }
+ }
+ return $info;
+ }
+
+ sub downloadItemInfoFromPlugin
+ {
+ my ($self, $plugin, $idx, $withPreview) = @_;
+
+ my $info;
+
+ # Gets from cache if we already previewed it
+ if ($self->{previewCache}->{$idx})
+ {
+ $info = $self->{previewCache}->{$idx};
+ }
+ else
+ {
+ # Tell the plugin what we want
+ $plugin->{wantedIdx} = $idx;
+ $plugin->{type} = 'info';
+
+ if (! $self->{options}->searchStop)
+ {
+ $self->setWaitCursor($self->{lang}->{StatusGetInfo});
+ # Fetch the information
+ $info = $plugin->getItemInfo;
+ $self->restoreCursor;
+ }
+ else
+ {
+ # Send directly the plugin to the other process
+ store_fd $plugin, $self->{searchJob}->{command};
+ # Indicates if something was returned by plugin
+ my $getInfo = 0;
+ my $dialogGet = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'info',
+ 'cancel',
+ $self->{lang}->{StatusGetInfo});
+
+ my $progress = new Gtk2::ProgressBar;
+ $dialogGet->vbox->pack_start($progress,1,1,10);
+ $progress->set_pulse_step(0.05);
+ $progress->show_all;
+ my $pulseTimeout = Glib::Timeout->add(50 , sub {
+ # $getInfo will be set by the watcher that looks for data from the other process
+ return 0 if $getInfo;
+ $progress->pulse;
+ return 1;
+ });
+
+ # Monitor the pipe from process
+ my $watch = Glib::IO->add_watch(fileno($self->{searchJob}->{data}),
+ 'in',
+ sub {
+ return if !$dialogGet;
+ $dialogGet->response('cancel');
+ # Set the flag when we got something
+ $getInfo = 1;
+ return 0;
+ });
+
+ $dialogGet->set_position('center-on-parent');
+ # It will run until we get some information, or the user press cancel
+ $dialogGet->run if !$getInfo;
+ # Stop everything
+ Glib::Source->remove($watch);
+ Glib::Source->remove($pulseTimeout);
+ $dialogGet->destroy;
+ my $command = $self->{searchJob}->{command};
+ # Did we get something?
+ if ($getInfo)
+ {
+ # Then we read the data from pipe
+ $info = fd_retrieve($self->{searchJob}->{data});
+ # And we inform the process we got it
+ print $command "OK\n";
+ }
+ else
+ {
+ # Tell the process the information is no more required
+ print $command "STOP\n";
+ return;
+ }
+ }
+ }
+
+ # If we are doing a preview instead of really fetching the data
+ if ($withPreview)
+ {
+ # Stores in cache
+ $self->{previewCache}->{$idx} = $info;
+ $self->getDialog('ImportFields')->info($info);
+ $self->getDialog('ImportFields')->setReadOnly(1);
+ $self->getDialog('ImportFields')->show;
+ }
+ else
+ {
+ # If user want to select the fields to fetch
+ if ($self->{options}->askImport)
+ {
+ $self->getDialog('ImportFields')->info($info);
+ $self->getDialog('ImportFields')->setReadOnly(0);
+ return if ! $self->getDialog('ImportFields')->show;
+ $info = $self->getDialog('ImportFields')->info;
+ }
+ # Set the information (2nd parameter set to 0 means no item creation).
+ $self->addItem($info, 0);
+ }
+ }
+
+ sub checkDefaultImage
+ {
+ my $self = shift;
+ my $previous = $self->{defaultImage};
+ my $specific = $self->{items}->getInformation->{defaultImage};
+ if ($specific && (-f $specific))
+ {
+ $self->{defaultImage} = $specific;
+ }
+ elsif (-f $self->{model}->{defaultImage})
+ {
+ $self->{defaultImage} = $self->{model}->{defaultImage};
+ }
+ else
+ {
+ if (-f $self->{logosDir}.'no.png')
+ {
+ $self->{defaultImage} = $self->{logosDir}.'no.png';
+ }
+ else
+ {
+ $self->{defaultImage} = $ENV{GCS_SHARE_DIR}.'/no.jpg';
+ }
+ }
+ $self->setItemsList
+ if !$self->{initializing} && $self->{itemsView} && ($previous ne $self->{defaultImage});
+ }
+
+ sub getImagesDir
+ {
+ my $self = shift;
+
+ my $imagesDir = '';
+ $imagesDir = $self->{items}->getInformation->{images}
+ if $self->{items};
+ my $i = 0;
+ #We use the global one if none exists in the collection
+ $imagesDir ||= $self->{options}->images;
+ if ($self->{options}->file)
+ {
+ $imagesDir =~ s/^(%WORKING_DIR%|\.)/dirname($self->{options}->file)/e;
+ $imagesDir =~ s/%FILE_BASE%/basename($self->{options}->file, '.gcs')/e;
+ }
+ else
+ {
+ # If value contains one of the variables and we don't have a directory name
+ # we store everything in the temporary directory
+ $imagesDir = $self->{tmpImageDir}
+ if ($imagesDir =~ m/^(%WORKING_DIR%|\.)/)
+ || ($imagesDir =~ m/%FILE_BASE%/);
+ }
+
+ return GCUtils::pathToUnix($imagesDir);
+ }
+
+ sub getUniqueImageFileName
+ {
+ my ($self, $suffix, $itemTitle, $imagesDir) = @_;
+
+ # If none specified, we use the default one
+ $imagesDir ||= $self->getImagesDir;
+ $imagesDir =~ s+([^/])$+$1/+;
+ my $imagePrefix;
+ if ($self->{options}->useTitleForPics)
+ {
+ $imagePrefix = GCUtils::getSafeFileName($itemTitle);
+ $imagePrefix .= '_';
+ }
+ else
+ {
+ $imagePrefix = $self->{imagePrefix};
+ }
+
+ if ( ! -e $imagesDir)
+ {
+ mkdir($imagesDir);
+ }
+
+ my $filePrefix = $imagesDir.$imagePrefix;
+ my @tmp_filenames;
+ @tmp_filenames = glob $filePrefix.'*.*';
+ my $sysPrefix = $filePrefix;
+ $sysPrefix =~ s/\\/\\\\/g if ($^O =~ /win32/i);
+ my @numbers = sort {$b <=> $a} map {
+ /$sysPrefix([0-9]*)\./ && $1;
+ } @tmp_filenames;
+ my $mostRecent = $numbers[0] || 0;
+
+ my $picture = $filePrefix.$mostRecent.$suffix;
+
+ while (-e $picture)
+ {
+ $mostRecent++;
+ $picture = $filePrefix.$mostRecent.$suffix;
+ }
+ return $picture;
+ }
+
+ sub isManagedPicture
+ {
+ my ($self, $pic) = @_;
+ my $file = GCUtils::getDisplayedImage($pic, '', $self->{options}->file);
+ my $imagePrefix = $self->{imagePrefix};
+ my $imageDir = $self->getImagesDir;
+ if (($file =~ /(\/|\\)$imagePrefix[0-9]*\./)
+ && ($file =~ m|^\Q$imageDir\E|))
+ {
+ return $file;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ sub checkPictureToBeRemoved
+ {
+ my ($self, $pic) = @_;
+ if (my $file = $self->isManagedPicture($pic))
+ {
+ $self->{items}->markToBeRemoved($file);
+ }
+ }
+
+# sub changeInfo
+# {
+# my ($self, $info) = @_;
+#
+# my @genres = split /,/, $info->{type};
+# my $newGenres = '';
+#
+# foreach (@genres)
+# {
+# $newGenres .= $self->getDialog('GenresGroups')->{convertor}->convert($_).',';
+# }
+# $newGenres =~ s/.$//;
+#
+# $info->{type} = $newGenres;
+# }
+
+ sub addItem
+ {
+ my ($self, $info, $newItem, $keepId, $defaultValues) = @_;
+
+ #$self->changeInfo($info);
+ my $ignore = $self->{ignoreString};
+
+ if ($newItem)
+ {
+ $self->{items}->updateSelectedItemInfoFromPanel;
+ $self->markAsUpdated;
+ my $id = $self->{items}->addItem($info, $keepId);
+ my @picFields = ();
+ for my $field (@{$self->{model}->{fieldsNames}})
+ {
+ if ($self->{model}->{fieldsInfo}->{$field}->{type} eq 'image')
+ {
+ push @picFields, $field;
+ next;
+ }
+ $self->{panel}->$field($info->{$field});
+ }
+ $self->{panel}->selectTitle;
+ my $title = $info->{$self->{model}->{commonFields}->{title}};
+ my $imagePrefix = $self->{imagePrefix};
+ foreach my $pic(@picFields)
+ {
+ if ($info->{$pic} && ($info->{$pic} ne $ignore))
+ {
+ $self->checkPictureToBeRemoved($self->{panel}->$pic);
+ ($info->{$pic}, my $picture) = $self->downloadPicture($info->{$pic}, $title)
+ if !$defaultValues;
+
+ # Only set the picture if one was returned. Otherwise the download was rejected, so stick with
+ # the existing picture
+ if ($info->{$pic})
+ {
+ $self->{panel}->$pic($info->{$pic})
+ }
+ else
+ {
+ $info->{$pic} = $ignore;
+ }
+ }
+ }
+ $self->{items}->updateSelectedItemInfoFromPanel(1);
+ $self->{panel}->show if $self->{itemsView}->getNbItems;
+ $self->setNbItems;
+ my $idField = $self->{model}->{commonFields}->{id};
+ $self->{panel}->$idField($id);
+ }
+ else
+ {
+ my $previous = $self->{panel}->getAsHash;
+ my @picFields = ();
+ foreach my $field(@{$self->{model}->{fieldsNames}})
+ {
+ if ($self->{model}->{fieldsInfo}->{$field}->{type} eq 'image')
+ {
+ push @picFields, $field;
+ next;
+ }
+ #next if ($field eq $cover);
+ if (($self->{model}->{fieldsInfo}->{$field}->{imported} ne 'true')
+ || ($info->{$field} eq $ignore)
+ || ($info->{$field} eq ''))
+ {
+ $info->{$field} = $previous->{$field};
+ }
+ else
+ {
+ $self->{panel}->$field($info->{$field});
+ }
+ }
+ my $title = $info->{$self->{model}->{commonFields}->{title}};
+ my $imagePrefix = $self->{imagePrefix};
+ foreach my $pic(@picFields)
+ {
+ if ($info->{$pic} && ($info->{$pic} ne $ignore))
+ {
+ $self->checkPictureToBeRemoved($self->{panel}->$pic);
+ ($info->{$pic}, my $picture) = $self->downloadPicture($info->{$pic}, $title);
+
+ # Only set the picture if one was returned. Otherwise the download was rejected, so stick with
+ # the existing picture
+ if ($info->{$pic})
+ {
+ $self->{panel}->$pic($info->{$pic})
+ }
+ else
+ {
+ $info->{$pic} = $ignore;
+ }
+ }
+ $info->{$pic} = $previous->{$pic} if $info->{$pic} eq $ignore;
+ }
+
+ $self->{items}->updateSelectedItemInfoFromPanel(1);
+ $self->{itemsView}->showCurrent;
+ }
+ $self->{panel}->dataChanged;
+ }
+
+ sub downloadPicture
+ {
+ my ($self, $pictureUrl, $title) = @_;
+
+ $title ||= $self->{panel}->getValue($self->{model}->{commonFields}->{title});
+
+ my ($name,$path,$suffix) = fileparse($pictureUrl, "\.gif", "\.jpg", "\.jpeg", "\.png");
+ $suffix ||= $self->{defaultPictureSuffix};
+ my $picture = $self->getUniqueImageFileName($suffix, $title);
+
+ if ($pictureUrl =~ m|^http://|)
+ {
+ $self->setWaitCursor($self->{lang}->{StatusGetImage});
+ GCUtils::downloadFile($pictureUrl, $picture, $self);
+ $self->restoreCursor;
+
+ # Check for file size of returned file. If it's less than 1000, we'll reject it, since it's
+ # most likely a corrupt file
+ my $filesize = -s $picture;
+ if ($filesize < 1000)
+ {
+ unlink $picture;
+ $picture = "";
+ }
+ }
+ else
+ {
+ copy $pictureUrl, $picture;
+ }
+ $self->{items}->markToBeAdded($picture)
+ if ($picture);
+ return ($self->transformPicturePath($picture), $picture);
+ }
+
+ sub transformPicturePath
+ {
+ my ($self, $path, $file) = @_;
+ return '' if !$path;
+ $file ||= $self->{options}->file;
+ $path = GCUtils::getDisplayedImage($path, $path, $file);
+ my $dir = undef;
+ $dir = dirname($file) if $file;
+ return GCUtils::pathToUnix(File::Spec->rel2abs($path,$dir), 1)
+ if !$self->{options}->useRelativePaths;
+ return GCUtils::pathToUnix(File::Spec->abs2rel($path,$dir));
+ }
+
+ sub addBookmark
+ {
+ my $self = shift;
+
+ my $dialog = $self->getDialog('BookmarkAdder');
+ $dialog->setBookmark($self->{options}->file, $self->{items}->getInformation->{name});
+ $dialog->setBookmarksFolders($self->{bookmarksLoader}->{bookmarks});
+ if ($dialog->show)
+ {
+ $self->{bookmarksLoader}->save($dialog->getBookmarks);
+ }
+ }
+
+ sub editBookmark
+ {
+ my $self = shift;
+
+ #my $dialog = $self->getDialog('BookmarksEdit');
+ my $dialog = new GCBookmarksEditDialog($self);
+
+ $dialog->setBookmarksFolders($self->{bookmarksLoader}->{bookmarks});
+ if ($dialog->show)
+ {
+ $self->{bookmarksLoader}->save($dialog->getBookmarks);
+ }
+ }
+
+ sub addFileHistory
+ {
+ my ($self, $filename) = @_;
+
+ $filename =~ s|/|\\|g if $^O =~ /win32/i;
+ $self->{options}->historysize(5) if !$self->{options}->exists('historysize');
+ my $maxSize = $self->{options}->historysize;
+ my @historyArray = split(/\|/, $self->{options}->history);
+ my $idx = 0;
+ # Remove previous occurence in history if any
+ foreach my $file(@historyArray)
+ {
+ if ($filename eq $file)
+ {
+ splice @historyArray, $idx, 1;
+ last;
+ }
+ $idx++;
+ }
+ # Prepend filename
+ splice @historyArray, 0, 0, $filename;
+ # Shrink array if too big
+ if (scalar @historyArray > $maxSize)
+ {
+ $#historyArray = $maxSize - 1;
+ }
+ $self->{options}->history(join '|', @historyArray);
+ $self->{menubar}->{menuHistoryItem}->remove_submenu;
+ $self->{menubar}->{menuHistory} = new Gtk2::Menu;
+ $self->{menubar}->addHistoryMenu(\@historyArray);
+ }
+
+ sub openFile
+ {
+ my ($self, $filename) = @_;
+ return if !$self->checkAndSave;
+ my ($success, $error) = (1, undef);
+ $self->initProgress($self->{lang}->{StatusLoad});
+ $self->{openingFile} = 1;
+ my $previousFile = $self->{options}->file;
+ $self->setFile($filename);
+ $self->savePreferences;
+ my $previousModel;
+ $previousModel = $self->{model}->getName
+ if $self->{model};
+ my @collectionVersion = split(/\./, $self->{items}->getVersion($filename));
+ if (@collectionVersion)
+ {
+ my @softVersion = split (/\./, $self->{version});
+
+ # We only use 2 major numbers. Last one should not impact data
+ if (($collectionVersion[0] > $softVersion[0])
+ || (($collectionVersion[0] == $softVersion[0])
+ && ($collectionVersion[1] > $softVersion[1])))
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'warning',
+ 'yes-no',
+ $self->{lang}->{OpenVersionWarning}
+ ."\n\n"
+ .$self->{lang}->{OpenVersionQuestion});
+ $dialog->set_default_response('cancel');
+ $dialog->set_position('center-on-parent');
+ my $response = $dialog->run;
+ $success = 0 if $response eq 'no';
+ $dialog->destroy;
+ }
+ }
+
+ if ($success)
+ {
+ ($success, $error) = $self->{items}->load($filename, $self, 1);
+ if ($success)
+ {
+ $self->checkDefaultImage;
+ $self->{filterSearch}->clear if $previousModel ne $self->{model}->getName;
+ $self->refreshFilters;
+ $self->viewAllItems;
+ $self->addFileHistory($filename);
+ $self->{options}->save;
+ $self->selectFirst;
+ $self->refreshTitle;
+ $self->{menubar}->setLock($self->{items}->getLock);
+ $self->endProgress;
+ }
+ else
+ {
+ my $dialog = new GCCriticalErrorDialog(
+ $self,
+ GCUtils::formatOpenSaveError(
+ $self->{lang},
+ $filename,
+ $error
+ )
+ );
+ $dialog->show;
+ }
+ }
+
+ if (!$success)
+ {
+ if ($previousFile)
+ {
+ $self->openFile($previousFile);
+ }
+ else
+ {
+ $self->endProgress;
+ }
+ }
+ $self->{openingFile} = 0;
+ # Just to be sure
+ $self->{saveIsNeeded} = 0;
+ }
+
+ sub openList
+ {
+ my $self = shift;
+ my $fileDialog = new GCFileChooserDialog($self->{lang}->{OpenList}, $self, 'open', 1);
+ $fileDialog->set_pattern_filter([$self->{lang}->{FileGCstarFiles}, '*.gcs']);
+
+ $fileDialog->set_filename($self->{options}->file);
+ my $response = $fileDialog->run;
+ if ($response eq 'ok')
+ {
+ my $fileName = $fileDialog->get_filename;
+ $fileDialog->destroy;
+ $self->openFile($fileName);
+ }
+ else
+ {
+ $fileDialog->destroy;
+ }
+ }
+
+ sub newList
+ {
+ my ($self, $modelName, $modelAlreadySet, $saveAlreadyDone) = @_;
+ return if !$saveAlreadyDone && !$self->checkAndSave;
+
+ if (!$modelAlreadySet)
+ {
+ if ($modelName)
+ {
+ $self->setCurrentModel(
+ $self->{modelsFactory}->getModel($modelName)
+ );
+ }
+ else
+ {
+ if ($self->getDialog('Models')->show)
+ {
+ if ($self->getDialog('Models')->isImporting)
+ {
+ $self->import($self->getDialog('Models')->getImporter);
+ return;
+ }
+ else
+ {
+ my $model = $self->getDialog('Models')->getModel;
+ if ($model->isEmpty)
+ {
+ $self->{model} = $model;
+ $self->editModel;
+ }
+ else
+ {
+ $self->setCurrentModel($model);
+ }
+ }
+ }
+ else
+ {
+ return if !$self->{initializing};
+ $self->setCurrentModel('GCfilms');
+ }
+ }
+ }
+
+ $self->{items}->clearList;
+ $self->reloadDone(1);
+ $self->setFile('');
+ $self->{menubar}->setAddBookmarkActive(0);
+ $self->refreshTitle;
+ }
+
+ sub saveAs
+ {
+ my $self = shift;
+ my $fileDialog = new GCFileChooserDialog($self->{lang}->{SaveList}, $self, 'save', 1, 1);
+ $fileDialog->set_pattern_filter([$self->{lang}->{FileGCstarFiles}, '*.gcs']);
+ $fileDialog->set_filename($self->{options}->file);
+ my $response;
+ while (1)
+ {
+ $response = $fileDialog->run;
+ if ($response eq 'ok')
+ {
+ my $filename = $fileDialog->get_filename;
+ my $previousFile = $self->{options}->file;
+ my $prevImages = $self->getImagesDir;
+ $self->setFile($filename);
+ # We re-generate it because it could have changed with new file name
+ my $newImages = $self->getImagesDir;
+ if (($prevImages ne $newImages) || ($previousFile && ($previousFile ne $filename)))
+ {
+ # The last parameter is for copy. When saving a new file, we move.
+ $self->{items}->setNewImagesDirectory($newImages, $prevImages,
+ $previousFile ? 1 : 0);
+ $self->{items}->setPreviousFile($previousFile)
+ if ($previousFile ne $filename);
+ }
+ if ($self->saveList)
+ {
+ $self->addFileHistory($filename);
+ $self->{options}->save;
+ $self->refreshTitle;
+ last;
+ }
+ else
+ {
+ $self->setFile($previousFile);
+ }
+ }
+ last if ($response ne 'ok')
+ }
+ $fileDialog->destroy;
+
+ # Check if the user has cancelled the dialog without giving a filename, if so
+ # return false to the save as request
+ if ($response eq 'ok')
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ sub checkAndSave
+ {
+ my $self = shift;
+ # Return value 1 means everything is OK.
+ # 0 means the user clicked cancel for save confirmation and then
+ # the process should be stopped.
+ if (!$self->{items}->getNbItems)
+ {
+ $self->removeUpdatedMark;
+ return 1;
+ }
+ $self->{items}->updateSelectedItemInfoFromPanel;
+ return 1 if !$self->{saveIsNeeded};
+ if (($self->{options}->autosave) && ($self->{options}->file))
+ {
+ $self->saveList;
+ return 1;
+ }
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'warning',
+ 'none',
+ $self->{lang}->{SaveUnsavedChanges});
+ my $noButton = Gtk2::Button->new($self->{lang}->{SaveDontSave});
+ $dialog->add_action_widget($noButton, 'no');
+ my $cancelButton = Gtk2::Button->new_from_stock('gtk-cancel');
+ $dialog->add_action_widget($cancelButton, 'cancel');
+ my $saveButton = Gtk2::Button->new_from_stock('gtk-save');
+ $saveButton->can_default(1);
+ $dialog->add_action_widget($saveButton, 'yes');
+ $noButton->show_all;
+ $cancelButton->show_all;
+ $saveButton->show_all;
+ $dialog->set_default_response('yes');
+ $dialog->set_position('center-on-parent');
+ my $response = $dialog->run();
+ $dialog->destroy;
+
+ if ($response eq 'yes')
+ {
+ my $saveResponse = $self->saveList;
+ # If save request has returned false, we need to cancel the current operation
+ return 0 if (!$saveResponse);
+ }
+
+
+ return 0 if $response eq 'cancel';
+ $self->removeUpdatedMark;
+
+ return 1;
+ }
+
+ sub saveList
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+ my $response = 'yes';
+ #$self->{itemsView}->{filter}->refilter;
+ if ($self->{options}->file)
+ {
+ my ($success, $error) = $self->{items}->save($self);
+ if ($success)
+ {
+ return 1;
+ }
+ else
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'ok',
+ GCUtils::formatOpenSaveError(
+ $self->{lang},
+ $self->{options}->file,
+ $error
+ ));
+ $dialog->set_position('center-on-parent');
+ $dialog->run();
+ $dialog->destroy ;
+ return 0;
+ }
+# $self->{items}->save($self);
+ }
+ else
+ {
+ # Run save as dialog
+ my $result = $self->saveAs;
+ # If result of dialog is false, user has cancelled without choosing a filename, so we return false
+ # to the save request to cancel operation
+ if ($result)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ sub selectFirst
+ {
+ my $self = shift;
+
+ $self->{items}->display($self->{items}->select(-1));
+ }
+
+ sub removeSearch
+ {
+ my ($self, $noRefresh) = @_;
+ $self->setFilter('', '', $noRefresh);
+ # Clear search mode
+ $self->{filterSearch}->setMode;
+ $self->{toolbar}->removeSearch
+ if $self->{toolbar};
+ }
+
+ sub search
+ {
+ my ($self, $self2, $value) = @_;
+ $self = $self2 if ($self2 ne 'all') && ($self2 ne 'displayed');
+ $self->{items}->updateSelectedItemInfoFromPanel;
+ $self->{items}->displayCurrent;
+ my $type = 'all';
+ $type = $value if ($self != $self2);
+
+ $self->getDialog('Search')->show;
+
+ my $info = $self->getDialog('Search')->search;
+ return if ! $info;
+
+ $self->{menubar}->selectAll if $type eq 'all';
+ $self->setSearch($info);
+ }
+
+ sub advancedSearch
+ {
+ my $self = shift;
+ my $dialog = $self->getDialog('AdvancedSearch');
+ $dialog->initSearch($self->{filterSearch}->getCurrentSearch);
+ $dialog->show;
+ my $info = $dialog->search;
+ return if ! $info;
+ $self->setSearchWithTypes(info => $info,
+ mode => $dialog->getMode,
+ case => $dialog->getCase,
+ ignoreDiacritics => $dialog->getIgnoreDiacritics);
+ }
+
+ sub addUserFilter
+ {
+ my ($self, $filter) = @_;
+ $self->{model}->addUserFilter($filter);
+ $self->{menubar}->createUserFilters($self->{model});
+ $self->{toolbar}->createUserFilters($self->{model});
+ }
+
+ sub saveCurrentSearch
+ {
+ my $self = shift;
+ my $dialog = $self->getDialog('AdvancedSearch');
+ $dialog->initSearch($self->{filterSearch}->getCurrentSearch);
+ $dialog->saveSearch;
+ }
+
+ sub editSavedSearches
+ {
+ my $self = shift;
+ my $dialog = $self->getDialog('UserFilters');
+ $self->{model}->saveUserFilters;
+ $dialog->setModel($self->{model});
+ if ($dialog->show)
+ {
+ $self->{model}->setUserFilters($dialog->getUserFilters);
+ $self->{model}->deleteUserFilters($dialog->getDeletedFilters);
+ $self->{menubar}->createUserFilters($self->{model});
+ $self->{toolbar}->createUserFilters($self->{model});
+ }
+ }
+
+ sub showBorrowed
+ {
+ my $self = shift;
+ $self->{items}->updateSelectedItemInfoFromPanel;
+ $self->getDialog('Borrowed')->setList($self->{items}, $self->{model});
+ $self->getDialog('Borrowed')->show;
+ }
+
+ sub export
+ {
+ my ($self, $exporter) = @_;
+ $self->getDialog('Export')->setModule($exporter);
+ $self->getDialog('Export')->show;
+ }
+
+ sub import
+ {
+ my ($self, $importer) = @_;
+ $self->getDialog('Import')->setModule($importer);
+ $self->getDialog('Import')->show;
+ }
+
+ sub importWithDetect
+ {
+ my ($self, $file, $realPath) = @_;
+
+ if (!$realPath)
+ {
+ $file =~ s/^file:\/\/(.*)\W*$/$1/;
+ $file =~ s/.$//ms;
+ }
+
+ foreach my $importer(@GCImport::importersArray)
+ {
+ my $current = $importer->getSuffix;
+ next if !$current;
+ #if ($current eq $suffix)
+ if ($file =~ /$current$/)
+ {
+ $self->setWaitCursor($self->{lang}->{StatusGetInfo});
+ my %options;
+ $options{parent} = $self;
+ $options{newList} = 0;
+ $options{file} = $file;
+ $options{lang} = $self->{lang};
+ $importer->process(\%options);
+ #$self->restoreCursor;
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ sub optionsError
+ {
+ my ($self, $type, $errmsg) = @_;
+
+ my $msg;
+ if ($type eq 'open')
+ {
+ $msg = $self->{lang}->{OptionsOpenError};
+ }
+ elsif ($type eq 'create')
+ {
+ $msg = $self->{lang}->{OptionsCreateError};
+ }
+ else
+ {
+ $msg = $self->{lang}->{OptionsSaveError};
+ }
+
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'ok',
+ $msg.$self->{options}->{file}."\n$errmsg");
+
+ $dialog->set_position('center-on-parent');
+ $dialog->run();
+ $dialog->destroy ;
+
+ $self->destroy;
+ }
+
+ sub checkImagesDirectory
+ {
+ my ($self, $withDialog) = @_;
+ my $error = 0;
+ my $imagesDir = $self->getImagesDir;
+ if ( ! -e $imagesDir)
+ {
+ eval {mkpath $imagesDir};
+ $error = 1 if (! -e $imagesDir) && (-e $self->{options}->file);
+ }
+ else
+ {
+ #$error = 1 if !( -d _ && -r _ && -w _ && -x _ )
+ # _ cannot be used when filtest access is on
+ $error = 1 if !( -d $imagesDir && -r $imagesDir && -w $imagesDir && -x $imagesDir );
+ }
+ if ($error)
+ {
+ my $itemsImagesDir = '';
+ $itemsImagesDir = $self->{items}->getInformation->{images}
+ if $self->{items};
+ if ($itemsImagesDir)
+ {
+ # Problem was because of a specific image dir, we clear it
+ $self->{items}->getInformation->{images} = '';
+ # And we check again
+ $error = $self->checkImagesDirectory(0);
+ }
+ }
+ return $error if !$withDialog;
+ if ($error)
+ {
+ $self->{splash}->hide if $self->{splash};
+ my $fileDialog = new GCFileChooserDialog($self->{lang}->{FileChooserOpenDirectory}, $self, 'select-folder');
+ my $errorDialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'ok',
+ $self->{lang}->{ImageError});
+
+ $errorDialog->set_position('center-on-parent');
+ $fileDialog->set_filename($imagesDir);
+ my $response;
+ do
+ {
+ $errorDialog->run();
+ $errorDialog->hide();
+ $response = '';
+ $response = $fileDialog->run;
+ exit 1 if $response ne 'ok';
+ $self->{options}->images($fileDialog->get_filename);
+ } while ($self->checkImagesDirectory(0));
+ $errorDialog->destroy;
+ $fileDialog->destroy;
+ }
+ return $error;
+ }
+
+ sub setFile
+ {
+ my ($self, $filename) = @_;
+
+ $self->{options}->file($filename);
+ $self->{menubar}->setAddBookmarkActive($filename ne '');
+ }
+
+ sub refreshTitle
+ {
+ my $self = shift;
+ my $name = '';
+ if ($self->{options}->file)
+ {
+ $name = $self->{items}->getInformation->{name}
+ if $self->{items};
+ $name ||= basename($self->{options}->file);
+ }
+ else
+ {
+ $name = $self->{lang}->{UnsavedCollection};
+ }
+ $self->{windowTitle} = $name.' - GCstar';
+ $self->set_title($self->{windowTitle});
+ }
+
+ sub properties
+ {
+ my $self = shift;
+
+ my $prevImages = $self->getImagesDir;
+
+ my $dialog = $self->getDialog('Properties');
+ $dialog->setProperties($self->{items}->getInformation,
+ $self->{options}->file,
+ $self->{items}->getNbItems);
+ if ($dialog->show)
+ {
+ $self->{items}->setInformation($dialog->getProperties);
+ $self->checkDefaultImage;
+ $self->refreshTitle;
+ # We check if it has changed
+ my $newImages = $self->getImagesDir;
+ if ($prevImages ne $newImages)
+ {
+ # Here we want to move pictures
+ $self->{items}->setNewImagesDirectory($newImages, $prevImages, 0);
+ }
+ $self->checkSpellChecking;
+ #$self->moveImages($prevImages, $self->{items}->getInformation->{images});
+ $self->markAsUpdated;
+ }
+ }
+
+ sub markAsUpdated
+ {
+ my $self = shift;
+ $self->{saveIsNeeded} = 1;
+ $self->set_title('*'.$self->{windowTitle});
+ $self->{menubar}->setSaveActive(1);
+ $self->{toolbar}->setSaveActive(1);
+ }
+
+ sub removeUpdatedMark
+ {
+ my $self = shift;
+ $self->{saveIsNeeded} = 0;
+ $self->set_title($self->{windowTitle});
+ $self->{menubar}->setSaveActive(0);
+ $self->{toolbar}->setSaveActive(0);
+
+ # Make sure the panel status is resetted
+ for my $field (@{$self->{model}->{fieldsNotFormatted}})
+ {
+ $self->{panel}->{$field}->resetChanged
+ if $self->{panel}->{$field};
+ }
+ $GCGraphicComponent::somethingChanged = 0;
+ }
+
+ sub refreshFilters
+ {
+ my $self = shift;
+ $self->{menubar}->refreshFilters;
+ }
+
+ sub setWaitCursor
+ {
+ my ($self, $message) = @_;
+ $self->setStatus($message);
+ $self->window->set_cursor(Gtk2::Gdk::Cursor->new('watch'));
+ GCUtils::updateUI;
+ }
+ sub restoreCursor
+ {
+ my $self = shift;
+ $self->restoreStatus;
+ $self->window->set_cursor(Gtk2::Gdk::Cursor->new('left_ptr'));
+ GCUtils::updateUI;
+ }
+
+ sub setFilter
+ {
+ my ($self, $filter, $parameter, $noRefresh) = @_;
+ $self->{items}->updateSelectedItemInfoFromPanel(1);
+ $self->{filterSearch}->setFilter($filter, $parameter,
+ $self->{model}->getFilterType($filter),
+ $self->{model});
+ $self->filter(1) unless $noRefresh;
+ }
+
+ sub setQuickSearch
+ {
+ my ($self, $field, $value) = @_;
+ $self->{items}->updateSelectedItemInfoFromPanel(1);
+ my $isNumeric = ($self->{model}->{fieldsInfo}->{$field}->{type} eq 'number');
+ $self->{filterSearch}->setFilter($field, $value,
+ ['contain', $isNumeric, undef],
+ $self->{model});
+ $self->filter(1);
+ }
+
+ sub setSearch
+ {
+ my ($self, $info) = @_;
+ $self->{filterSearch}->clear;
+ $self->{filterSearch}->setFilter($_,
+ $info->{$_},
+ $self->{model}->getFilterType($_),
+ $self->{model})
+ foreach (keys %$info);
+
+ $self->filter(1);
+ }
+
+ sub setSearchWithTypes
+ {
+ my ($self, %searchParameters) = @_;
+
+ $self->{filterSearch}->clear;
+ $self->{filterSearch}->setMode($searchParameters{mode});
+ $self->{filterSearch}->setCase($searchParameters{case});
+ $self->{filterSearch}->setIgnoreDiacritics($searchParameters{ignoreDiacritics});
+ foreach (@{$searchParameters{info}})
+ {
+ $self->{filterSearch}->setFilter($_->{field},
+ $_->{value},
+ $_->{filter},
+ $self->{model},
+ 1);
+ }
+ $self->filter(1);
+ #$self->{filterSearch}->removeTemporaryFilters;
+
+ # What's the point of this line? It clears the filter mode so all filters return to "and" types
+ # resulting in filtered exports giving unexpected results.
+ # Removed 28/10/2009 by zombiepig, but I'm not sure if there'll be unexpected side effects
+ # $self->{filterSearch}->setMode;
+ }
+
+ sub checkPanelVisibility
+ {
+ my $self = shift;
+ if ($self->{itemsView}->getNbItems)
+ {
+ $self->{panel}->show;
+ }
+ else
+ {
+ $self->{panel}->hide(1);
+ }
+ }
+
+ sub filter
+ {
+ my ($self, $refresh, $splash) = @_;
+ $self->{filterSearch}->setModel($self->{model});
+ my $current = $self->{itemsView}->setFilter($self->{filterSearch},
+ $self->{items}->getItemsListFiltered,
+ $refresh,
+ $splash);
+ $self->{items}->display($current);
+ $self->{itemsView}->select($current, 1);
+
+ $self->checkPanelVisibility;
+
+ $self->setNbItems;
+ }
+
+ sub reloadDone
+ {
+ my ($self, $noFilter, $splash) = @_;
+ my $reloadOnDone = $self->{initializing};
+ my $needFilter = ! ($reloadOnDone || $noFilter);
+ if ($self->{itemsView})
+ {
+ $self->{itemsView}->setSortOrder(undef, $splash, $needFilter);
+ $self->{itemsView}->done($splash, $reloadOnDone);
+ }
+ $self->filter(1, $splash) if $needFilter;
+ $self->setNbItems;
+ }
+
+ sub getDialog
+ {
+ my ($self, $name) = @_;
+
+ if ($name eq 'PluginsAsk')
+ {
+ $self->{PluginsAskDialog} = new GCPluginsDialog($self)
+ if !$self->{PluginsAskDialog};
+ }
+ elsif ($name eq 'MultiSite')
+ {
+ $self->{MultiSiteDialog}->{$self->{model}->getName} = new GCMultiSiteDialog($self, $self->{model})
+ if !$self->{MultiSiteDialog}->{$self->{model}->getName};
+ return $self->{MultiSiteDialog}->{$self->{model}->getName};
+ }
+
+ elsif ($name eq 'MultiSitePerField')
+ {
+ $self->{MultiSitePerFieldDialog}->{$self->{model}->getName} = new GCMultiSitePerFieldDialog($self, $self->{model},1)
+ if !$self->{MultiSitePerFieldDialog}->{$self->{model}->getName};
+ return $self->{MultiSitePerFieldDialog}->{$self->{model}->getName};
+ }
+ elsif ($name eq 'About')
+ {
+ $self->{AboutDialog} = new GCAboutDialog($self, $self->{version})
+ if ! $self->{AboutDialog};
+ }
+ elsif ($name eq 'Models')
+ {
+ $self->{ModelsDialog} = new GCModelsDialog($self,
+ $self->{modelsFactory},
+ 1)
+ if ! $self->{ModelsDialog};
+ }
+ else
+ {
+ my $className = 'GC'.$name.'Dialog';
+ if (! $self->{$name.'Dialog'})
+ {
+ $self->{$name.'Dialog'} = new $className($self);
+ # Actually Plugins, Export and Import don't need the parameter, but
+ # it has no impact and make the code more simple
+ $self->{$name.'Dialog'}->setModel($self->{model})
+ if $name =~ /^(AdvancedSearch|Search|Options|QueryReplace|Plugins|Export|Import)$/
+ && $self->{model};
+ }
+ }
+
+ return $self->{$name.'Dialog'};
+ }
+
+ sub options
+ {
+ my ($self, $self2,$tabToShow) = @_;
+ $self = $self2 if $self2;
+ my $transform = $self->{options}->transform;
+ my $articles = $self->{options}->articles;
+ my $formats = $self->{options}->formats;
+ my $layout = $self->{model}->{preferences}->layout;
+ my $panelStyle = $self->{options}->panelStyle;
+ my $toolbar = $self->{options}->toolbar;
+ my $toolbarPosition = $self->{options}->toolbarPosition;
+ my $prevImages = $self->getImagesDir;
+ my $expandersMode = $self->{options}->expandersMode;
+ my $dateFormat = $self->{options}->dateFormat;
+ my $spellCheck = $self->{options}->spellCheck;
+ my $imageEditor = $self->{options}->imageEditor;
+ my $programs = $self->{options}->programs;
+ my $useStars = $self->{options}->useStars;
+
+ $self->savePreferences;
+
+ $self->getDialog('Options')->show($tabToShow);
+
+ my $newImages = $self->getImagesDir;
+ if ($prevImages ne $newImages)
+ {
+ # Here we want to move pictures
+ $self->{items}->setNewImagesDirectory($newImages, $prevImages, 0);
+ $self->markAsUpdated;
+ }
+
+ $self->checkTransform
+ if ($self->{options}->articles ne $articles)
+ || ($self->{options}->transform != $transform);
+
+ # Need to reload the panel if the layout or style has changed
+ $self->changePanel(0,0)
+ if ($self->{model}->{preferences}->layout ne $layout)
+ || ($self->{options}->panelStyle ne $panelStyle)
+ || ($self->{options}->useStars ne $useStars);
+
+ $self->{panel}->setExpandersMode($self->{options}->expandersMode)
+ if $expandersMode ne $self->{options}->expandersMode;
+
+ if ($dateFormat ne $self->{options}->dateFormat)
+ {
+ $self->{panel}->setDateFormat($self->{options}->dateFormat);
+
+ # TODO We could optimize this with a method setDateFormat on
+ # items view that would only change what needed
+ if ($self->{itemsView} # We have a view
+ && ($self->{itemsView}->isUsingDate) # It uses the date format
+ && (!$self->getDialog('Options')->{viewChanged})) # And we didn't already change it
+ {
+ $self->setItemsList(0, 1); # Then we re-create it
+ }
+ }
+
+ if ($spellCheck ne $self->{options}->spellCheck)
+ {
+ $self->checkSpellChecking;
+ }
+
+ $self->checkToolbarPosition
+ if ($self->{options}->toolbarPosition ne $toolbarPosition) || ($self->{options}->toolbar ne $toolbar);
+
+ $self->{menubar}->setDisplayToolbarState($self->{options}->toolbar == 0 ? 0 : 1)
+ if $self->{options}->toolbar != $toolbar;
+
+ $self->checkPlugin;
+ $self->checkView;
+ #$self->checkToolbarOptions
+ }
+
+ sub displayOptions
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ my $dialog = $self->getDialog('DisplayOptions');
+ my $hidden = $self->{model}->{preferences}->hidden;
+ $dialog->show;
+ if ($self->{model}->{preferences}->hidden ne $hidden)
+ {
+ $self->checkPanelContent;
+ $self->markAsUpdated
+ if $self->{model}->isInline;
+ }
+
+ }
+
+ sub toolbarOptions
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ my $dialog = $self->getDialog('ToolbarOptions');
+ if ($dialog->show eq 'ok')
+ {
+ $self->removeToolbar;
+ $self->{toolbar}->destroy;
+ $self->{toolbar} = GCToolBar->new($self);
+ $self->{toolbar}->setModel($self->{model})
+ if $self->{model};
+ $self->checkToolbarOptions;
+ $self->checkToolbarPosition(1);
+ }
+ }
+
+ sub getUserModelsDirError
+ {
+ my $self = shift;
+ return $self->{lang}->{ErrorModelUserDir}
+ .$self->{modelsFactory}->{persoDirectory};
+ }
+
+ sub setCurrentModel
+ {
+ my ($self, $model) = @_;
+ $model = 'GCfilms' if ! $model;
+ $self->savePreferences;
+ $self->{model}->save if $self->{model} && (ref($self->{model}) ne 'HASH');
+ if ((ref $model) =~ /GCModelLoader/)
+ {
+ $self->{model} = $model;
+ }
+ else
+ {
+ $self->{model} = $self->{modelsFactory}->getModel($model)
+ }
+ if (!$self->{model})
+ {
+ return 0;
+ }
+ $self->{items}->initModel($self->{model});
+ return 1;
+ }
+
+ sub preloadModel
+ {
+ my ($self, $model) = @_;
+ # Preload the model into the factory cache
+ $self->{modelsFactory}->getModel($model);
+ }
+
+ sub setCurrentModelFromInline
+ {
+ my ($self, $container) = @_;
+ my $model = GCModelLoader->newFromInline($self, $container);
+ $self->setCurrentModel($model);
+ }
+
+ sub addFieldsToDefaultModel
+ {
+ my ($self, $inlineModel) = @_;
+ my $model = GCModelLoader->newFromInline($self, {inlineModel => $inlineModel, defaultModifier => 1});
+ $self->{model}->addFields($model);
+ $self->notifyModelChange;
+ }
+
+ sub setGrouping
+ {
+ my ($self, $field) = @_;
+ # Automatically switch to detailed list if the current one is text list
+ $self->{options}->view(2)
+ if $self->{options}->view == 0;
+ $self->{model}->{preferences}->groupBy($field);
+ $self->setItemsList(0);
+ }
+
+ sub setLock
+ {
+ my ($self, $value) = @_;
+ $self->{items}->setLock($value);
+ $self->{panel}->changeState($self->{panel}, $value);
+ $self->markAsUpdated;
+ }
+
+ sub notifyModelChange
+ {
+ my ($self, $modelUpdated) = @_;
+
+ # Might need to recreate the Tonight window
+ $self->{remakeItemWindow}->{random} = 1
+ if exists($self->{itemWindow}->{random});
+ $self->{remakeItemWindow}->{item} = 1
+ if exists($self->{itemWindow}->{item});
+ $self->{remakeItemWindow}->{defaultValues} = 1
+ if exists($self->{itemWindow}->{defaultValues});
+
+ # Update strings to reflect model change
+ $self->GCLang::updateModelSpecificStrings;
+
+ #We deactivate some updates
+ my $previousInitializing = $self->{initializing};
+ $self->{initializing} = 1;
+ $self->checkDefaultImage;
+ $self->setItemsList(1, 1);
+ $self->getDialog('DisplayOptions')->createContent($self->{model});
+ $self->changePanel(1, $modelUpdated);
+ if (%{$self->{panel}})
+ {
+ $self->{panel}->createContent($self->{model});
+ $self->{panel}->deactivate if $self->{items}->getLock;
+ }
+ $self->{menubar}->setModel($self->{model});
+ $self->{toolbar}->setModel($self->{model});
+ # Update strings in dialogs if needed
+ $self->updateDialogStrings;
+ $self->checkToolbarOptions;
+
+ $self->getDialog('Search')->setModel($self->{model})
+ if $self->{SearchDialog};
+ $self->getDialog('AdvancedSearch')->setModel($self->{model})
+ if $self->{AdvancedSearchDialog};
+ $self->getDialog('Options')->setModel($self->{model})
+ if $self->{OptionsDialog};
+ $self->getDialog('QueryReplace')->setModel($self->{model})
+ if $self->{QueryReplaceDialog};
+ $self->getDialog('Plugins')->setModel
+ if $self->{PluginsDialog};
+ $self->getDialog('ImportFields')->setModel($self->{model})
+ if $self->{ImportFieldsDialog};
+ $self->getDialog('Export')->setModel
+ if $self->{ExportDialog};
+ $self->getDialog('Import')->setModel
+ if $self->{ImportDialog};
+
+ $self->checkPlugin;
+
+ $self->{initializing} = $previousInitializing;
+ }
+
+ sub updateDialogStrings
+ {
+ my $self = shift;
+
+ # Update model-specific text strings in dialog boxes
+ $self->getDialog('Properties')->{info}->{itemsLabel}->set_label($self->{lang}->{PropertiesItemsNumber});
+ $self->getDialog('Borrowed')->set_title($self->{lang}->{BorrowedTitle});
+ $self->getDialog('Borrowed')->{returned}->get_children->set_label($self->{lang}->{PanelReturned});
+ $self->getDialog('Borrowed')->{display}->get_children->set_label($self->{lang}->{BorrowedDisplayInPanel});
+ $self->getDialog('Search')->set_title($self->{lang}->{SearchTitle});
+ $self->getDialog('AdvancedSearch')->set_title($self->{lang}->{SearchTitle});
+ }
+
+ sub editModel
+ {
+ my $self = shift;
+ my $dialog = $self->getDialog('ModelsSettings');
+ my $isPersonal = $self->{model}->isPersonal;
+ $dialog->setPersonalMode($isPersonal);
+ $dialog->setDescription($self->{model}->getDescription);
+ if ($isPersonal)
+ {
+ $dialog->initFields($self->{model}->getOriginalCollection->{fields}->{field},
+ $self->{model}->getGroups,
+ $self->{model}->getCommonFields,
+ $self->{model}->{hasLending},
+ $self->{model}->{hasTags});
+ $dialog->initFilters($self->{model}->{filters});
+ }
+ else
+ {
+ $dialog->initFields($self->{model}->getAddedFields,
+ $self->{model}->getAddedGroups);
+ }
+ if ($dialog->show)
+ {
+ $self->markAsUpdated;
+ if ($isPersonal)
+ {
+ my $file;
+ ($self->{model}->{collection}->{description}, $self->{model}->{collection}->{name}, $file)
+ = $dialog->getName;
+
+ my %fields = (
+ id => $dialog->getIdField,
+ title => $dialog->getTitleField,
+ cover => $dialog->getCoverField,
+ play => $dialog->getPlayField,
+ );
+ $self->{model}->setOptions(\%fields, 'no.png');
+ $self->{model}->setGroups($dialog->getGroups);
+ $self->{model}->setFields($dialog->{fields}, $dialog->hasLending, $dialog->hasTags);
+ $self->{model}->setFilters($dialog->getFilters($self->{model}->{fieldsInfo}));
+ if ($self->{model}->{collection}->{name})
+ {
+ $self->{model}->saveToFile($file);
+ # This is done twice, because the 1st time the model thought it is online
+ $self->{model}->loadPreferences;
+ }
+ }
+ else
+ {
+ $self->{model}->updateAddedFields($dialog->{fields}, $dialog->getGroups);
+ }
+ #$self->notifyModelChange;
+ $self->{items}->initModel($self->{model}, 1);
+ $self->reloadList;
+ }
+ }
+
+ sub borrowers
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ $self->getDialog('Borrowers')->show;
+ $self->checkBorrowers;
+ # Some models could use the borrower as a filter
+ # TODO only refresh filters when needed (see previous line)
+ $self->refreshFilters;
+ }
+
+# sub genresConversion
+# {
+# my ($self, $self2) = @_;
+# $self = $self2 if $self2;
+#
+# $self->getDialog('GenresGroups')->show;
+# }
+
+ sub queryReplace
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ if ($self->getDialog('QueryReplace')->show)
+ {
+ $self->{items}->queryReplace($self->getDialog('QueryReplace')->{field},
+ $self->getDialog('QueryReplace')->{oldValue},
+ $self->getDialog('QueryReplace')->{newValue},
+ $self->getDialog('QueryReplace')->{caseSensitive});
+ $self->markAsUpdated;
+ }
+ }
+
+ sub about
+ {
+ my $self = shift;
+
+ $self->getDialog('About')->show;
+ }
+
+ sub stats
+ {
+ my $self = shift;
+ my $dialog = $self->getDialog('Stats');
+ (my $title = $self->{windowTitle}) =~ s/ - GCstar$//;
+ $dialog->setData($self->{model}, $self->{items}->getItemsListFiltered, $title);
+ $dialog->show;
+ #my $dialog = new GCStatsDialog($self);
+ #$dialog->setData($self->{model}, $self->{items}->getItemsListFiltered);
+ #$dialog->show;
+ }
+
+ sub showDependencies
+ {
+ my $self = shift;
+ $self->getDialog('Dependencies')->show;
+ }
+
+ sub showAllPlugins
+ {
+ my $self = shift;
+ $self->getDialog('AllPlugins')->show;
+ }
+
+ sub help
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ $self->launch('http://wiki.gcstar.org/', 'url');
+ }
+
+ sub reportBug
+ {
+ my ($self, $self2, $subject, $message) = @_;
+ $self = $self2 if $self2;
+
+ my %topicIds = (
+ EN => '4',
+ FR => '9'
+ );
+
+ my $id = $topicIds{$self->{options}->lang} || $topicIds{EN};
+
+ my $url;
+ if ($message)
+ {
+ # TODO: Move this part across to launchpad when https://bugs.launchpad.net/malone/+bug/552142 is fixed
+ $url = 'http://forums.gcstar.org/postbug.php?req_subject='.uri_escape($subject).'&req_message='.uri_escape($message).'&fid='.$id;
+ $url =~ s/\(/%28/g;
+ $url =~ s/\)/%29/g;
+ }
+ else
+ {
+ $url = 'https://bugs.launchpad.net/gcstar/+filebug';
+ }
+ $self->launch($url, 'url');
+ }
+
+ sub playItem
+ {
+ my $self = shift;
+ my $field = $self->{model}->{commonFields}->{play};
+ my $format = $self->{model}->{fieldsInfo}->{$field}->{format};
+ $self->launch($self->{panel}->$field, $format);
+ }
+
+ sub launch
+ {
+ my ($self, $file, $format, $withParams, $windowParent) = @_;
+
+ $format ||= 'program';
+ my $command;
+ # For image, we have an internal viewer
+ if ($format eq 'image')
+ {
+ $file = GCUtils::getDisplayedImage($file, '', $self->{options}->file);
+ my $dialog = new GCImageDialog($self, $file, $windowParent);
+ $dialog->show;
+ $dialog->destroy;
+
+ if ((exists $self->{itemWindow}->{item}) && ($self->{itemWindow}->{item}->visible))
+ {
+ $self->{itemWindow}->{item}->showMe;
+ }
+ return;
+ }
+ elsif ($format eq 'program')
+ {
+ if ($^O =~ /win32/i)
+ {
+ # Encode filename to work with foreign characters
+ $file = Encode::encode("iso-8859-1", $file);
+
+ if ($withParams)
+ {
+ $command = $file;
+ }
+ else
+ {
+ $file =~ s/([^\\])"/$1\\"/g;
+ $command = '"'.$file.'"';
+ }
+ }
+ else
+ {
+ $command = $file;
+ }
+ }
+ else
+ {
+ # For the other ones, we use external programs
+
+ if ($self->{options}->programs eq 'user')
+ {
+ $command = $self->{options}->browser if $format eq 'url';
+ $command = $self->{options}->player if $format eq 'video';
+ $command = $self->{options}->audio if $format eq 'audio';
+ }
+
+ # If using system default programs, or user has not overriden system default
+ if (( $self->{options}->programs eq 'system' ) || ( !$command ))
+ {
+ $command = ($^O =~ /win32/i) ? ''
+ : ($^O =~ /macos/i) ? '/usr/bin/open'
+ : $ENV{GCS_SHARE_DIR}.'/helpers/xdg-open';
+ }
+
+ if ($file && ($format ne 'url'))
+ {
+ if ($file !~ /^(http|ftp)/)
+ {
+ my $dialog;
+
+ # Encode filename to work with foreign characters under Win32
+ $file = Encode::encode("iso-8859-1", $file) if ($^O =~ /win32/i);
+
+ while (! -e $file)
+ {
+ if (!$dialog)
+ {
+ $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'warning',
+ 'none',
+ $self->{lang}->{PlayFileNotFound}."\n\n".$file);
+ my $cancelButton = Gtk2::Button->new_from_stock('gtk-cancel');
+ $dialog->add_action_widget($cancelButton, 'cancel');
+ my $retryButton = Gtk2::Button->new($self->{lang}->{PlayRetry});
+ $retryButton->can_default(1);
+ $dialog->add_action_widget($retryButton, 'yes');
+ $cancelButton->show_all;
+ $retryButton->show_all;
+ $dialog->set_default_response('yes');
+ $dialog->set_position('center-on-parent');
+ }
+ my $response = $dialog->run;
+ last if $response eq 'cancel';
+ }
+ $dialog->destroy if $dialog;
+ return if ! -e $file;
+ }
+ }
+
+ if ($^O =~ /win32/i)
+ {
+ $command = '"'.$command.'"' if $command;
+ $file =~ s/([^\\])"/$1\\"/g;
+ $command .= ' "'.$file.'"';
+ }
+ else
+ {
+ $file =~ s/([^\\])"/$1\\"/g;
+ #"
+ $command .= ' "'.$file.'"';
+ }
+
+ }
+
+ return if !$command;
+ # Finalize the command line
+ if ($^O =~ /win32/i)
+ {
+ system("start \"\" $command");
+ }
+ else
+ {
+ system "$command &";
+ }
+ if ($ENV{GCS_SET_FAVOURITE_AFTER_PLAY})
+ {
+ my $current = $self->{panel}->favourite;
+ $self->{panel}->favourite($current ? 0 : 1);
+ $self->{items}->updateSelectedItemInfoFromPanel(1);
+ }
+ }
+
+ sub sendBorrowerEmail
+ {
+ my ($self, $info) = @_;
+
+ $self->{mailer} = new GCMailer($self) if ! $self->{mailer};
+ $self->{mailer}->sendBorrowerEmail($info);
+ }
+
+ sub extractInfo
+ {
+ my ($self, $file, $panel) = @_;
+
+ my $infoExtractor = $self->{model}->getExtracter($self, $file, $panel, $self->{model});
+ #$self->getDialog('Extract')->setInfo($infoExtractor->getInfo, $panel);
+ #$self->getDialog('Extract')->show;
+ my $dialog = new GCExtractDialog($self, $self->{model}, $infoExtractor);
+ $dialog->setInfo($infoExtractor, $panel);
+ $dialog->show;
+ }
+
+ sub setStatus
+ {
+ my ($self, $status) = @_;
+ $self->{status}->push(1, $status) if ($self->{status});
+ }
+
+ sub restoreStatus
+ {
+ my $self = shift;
+ $self->{status}->pop(1);
+ }
+
+ sub setNbItems
+ {
+ my $self = shift;
+
+ return if !$self->{itemsView};
+ my $number = $self->{itemsView}->getNbItems;
+ my $status = " $number ".$self->{model}->getDisplayedItems($number);
+ $self->setStatus($status);
+ }
+
+ sub updateSelectedItemInfoFromGivenPanelAndSelect
+ {
+ my ($self, $panel, $idx) = @_;
+ $self->{items}->updateSelectedItemInfoFromGivenPanel($panel);
+ $self->{items}->displayInPanel($self->{panel}, $idx);
+ #Init combo boxes
+ foreach(@{$self->{model}->{fieldsHistory}})
+ {
+ $self->{panel}->{$_}->setValues($panel->getValues($_));
+ }
+ }
+
+ sub display
+ {
+ my ($self, @idx) = @_;
+ if ($self->{items}->display(@idx))
+ {
+ $self->setNbItems;
+ }
+ }
+
+ sub getItemWindow
+ {
+ my ($self, $type) = @_;
+
+ my $created = 0;
+
+ if ((! exists $self->{itemWindow}->{$type}) || ($self->{remakeItemWindow}->{$type}))
+ {
+ $self->{itemWindow}->{$type}->destroy
+ if exists $self->{itemWindow}->{$type};
+
+ $self->{itemWindow}->{$type} =
+ ($type eq 'item') ? new GCItemWindow($self, "") :
+ ($type eq 'random') ? new GCRandomItemWindow($self, "") :
+ ($type eq 'defaultValues') ? new GCDefaultValuesWindow($self, "") :
+ undef;
+ $created = 1;
+ $self->{remakeItemWindow}->{$type} = 0
+ }
+
+ my $window = $self->{itemWindow}->{$type};
+
+ if ($created && $self->{options}->exists('itemWindowWidth'))
+ {
+ $window->set_default_size(
+ $self->{options}->itemWindowWidth,
+ $self->{options}->itemWindowHeight
+ );
+ }
+
+ if ($self->{previousWindowPosition})
+ {
+ $window->move($self->{previousWindowPosition}->{x},
+ $self->{previousWindowPosition}->{y});
+ }
+ $window->{panel}->setBorrowers;
+ $window->{panel}->disableBorrowerChange;
+
+ return $window;
+ }
+
+ sub saveItemWindowSettings
+ {
+ my ($self, $window) = @_;
+
+ my ($width, $height) = $window->get_size;
+ $self->{options}->itemWindowWidth($width);
+ $self->{options}->itemWindowHeight($height);
+
+ ($self->{previousWindowPosition}->{x}, $self->{previousWindowPosition}->{y})
+ = $window->get_position;
+ }
+
+ sub displayInWindow
+ {
+ my ($self, $idx, $type, $select) = @_;
+
+ $type ||= 'item';
+
+ my $title = $self->{items}->getTitle($idx);
+
+ my $window = $self->getItemWindow($type);
+ $window->setTitle($title);
+
+ $self->{items}->displayInPanel($window->{panel}, $idx);
+ $window->{panel}->selectTitle if $select;
+
+ my $code = $window->show;
+
+ if (($type eq 'item') && ($code eq 'ok'))
+ {
+ $self->updateSelectedItemInfoFromGivenPanelAndSelect($window->{panel}, $idx);
+ $self->refreshFilters;
+ }
+
+ $self->saveItemWindowSettings($window);
+
+ $window->hide;
+
+ return $code;
+ }
+
+ sub randomItem
+ {
+ my ($self) = @_;
+ my @tmpArray = undef;
+ $self->{items}->updateSelectedItemInfoFromPanel;
+
+ my $message = '';
+
+ #Initialize items array.
+ $self->{randomPool} = [];
+ my $realId = 0;
+ my $filterSearch = new GCFilterSearch;
+ foreach my $filter(@{$self->{model}->{random}})
+ {
+ $filterSearch->setFilter($filter->{field},
+ $filter->{value},
+ [$filter->{comparison}, $filter->{numeric}],
+ $self->{model});
+ }
+
+ foreach (@{$self->{items}->getItemsListFiltered})
+ {
+ #if (!$_->{seen})
+ if ($filterSearch->test($_))
+ {
+ $_->{realId} = $realId;
+ push @{$self->{randomPool}}, $_;
+ }
+ $realId++;
+ }
+
+ if (scalar @{$self->{randomPool}} > 0)
+ {
+ my $code = 'no';
+ my $idx = 0;
+ while ($code eq 'no')
+ {
+ $idx = int rand(scalar @{$self->{randomPool}});
+ $realId = $self->{randomPool}->[$idx]->{realId};
+ $code = $self->displayInWindow($realId, 'random');
+ splice @{$self->{randomPool}}, $idx, 1;
+ last if ! @{$self->{randomPool}};
+ }
+ $message = $self->{lang}->{RandomEnd} if $code eq 'no';
+ if ($code eq 'ok')
+ {
+ foreach my $filter(@{$self->{model}->{random}})
+ {
+ next if !exists $filter->{after};
+ my $field = $filter->{field};
+ ($self->{items}->getItemsListFiltered)->[$realId]->{$field} = $filter->{after};
+ $self->{panel}->$field($filter->{after})
+ if $self->{items}->{currentItem} == $realId;
+ }
+ }
+ }
+ else
+ {
+ $message = $self->{lang}->{RandomError};
+ }
+
+ if ($message)
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'info',
+ 'ok',
+ $message);
+ $dialog->set_position('center-on-parent');
+ $dialog->run();
+ $dialog->destroy;
+ }
+
+ #Clean items array.
+ foreach (@{$self->{items}->getItemsListFiltered})
+ {
+ delete $_->{realId};
+ }
+ }
+
+ sub setSensitive
+ {
+ my ($self, $sensitive) = @_;
+ $self->{menubar}->set_sensitive($sensitive);
+ $self->{toolbar}->set_sensitive($sensitive);
+ $self->{pane}->set_sensitive($sensitive);
+ }
+
+ sub initProgress
+ {
+ my ($self, $label) = @_;
+ if ($label)
+ {
+ $self->setStatus($label);
+ $self->{restoreNeeded} = 1;
+ }
+ return if $self->{openingFile};
+ $self->setProgress(0.0);
+ $self->setSensitive(0);
+ }
+
+ sub endProgress
+ {
+ my $self = shift;
+ $self->setProgress(1.0);
+ if ($self->{restoreNeeded})
+ {
+ $self->{restoreNeeded} = 0;
+ $self->restoreStatus;
+ }
+ Glib::Timeout->add(500 , sub {
+ $self->setProgress(0.0);
+ return 0;
+ });
+ $self->checkSpellChecking;
+ $self->setSensitive(1);
+ }
+
+ sub setProgress
+ {
+ my ($self, $current) = @_;
+ $self->{progress}->set_fraction($current);
+ GCUtils::updateUI;
+ }
+
+ sub setItemsTotal
+ {
+ my ($self, $total) = @_;
+ $self->{step} = GCUtils::round($total / 7);
+ $self->{step} = 1 if $self->{step} < 1;
+ $self->{total} = $total;
+ }
+
+ sub setProgressForItemsLoad
+ {
+ my ($self, $current) = @_;
+ return if ! $self->{openingFile};
+ return if ($current % $self->{step});
+ if ($self->{total})
+ {
+ my $value = ($current / $self->{total}) / 2;
+ $value = 0.5 if $value > 0.5;
+ #$self->{progress}->set_fraction($value);
+ $self->setProgress($value);
+ }
+ else
+ {
+ #$self->{progress}->set_fraction(0.3);
+ $self->setProgress(0.3);
+ }
+ GCUtils::updateUI;
+ }
+
+ sub setProgressForItemsDisplay
+ {
+ my ($self, $current) = @_;
+ return if ($current % $self->{step});
+ if ($self->{total})
+ {
+ my $value = ($current / (2*$self->{total}));
+ $value = ($value / 2) + 0.5 if $self->{openingFile};
+ $value = 0.75 if $value > 0.75;
+ $self->setProgress($value);
+ #$self->{progress}->set_fraction($value);
+ }
+ else
+ {
+ $self->setProgress(0.5);
+ #$self->{progress}->set_fraction(0.5);
+ }
+ GCUtils::updateUI;
+ }
+
+ sub setProgressForItemsSort
+ {
+ my ($self, $current) = @_;
+ return if ($current % $self->{step});
+ if ($self->{total})
+ {
+ my $value = ($current / (2*$self->{total})) + 0.5;
+ $value = (($value - 0.5) / 2) + 0.75 if $self->{openingFile};
+ $value = 1.0 if $value > 1;
+ $self->setProgress($value);
+ $self->{progress}->set_fraction($value);
+ }
+ else
+ {
+ $self->setProgress(0.7);
+ $self->{progress}->set_fraction(0.7);
+ }
+ GCUtils::updateUI;
+ }
+
+ sub blockListUpdates
+ {
+ my ($self, $value) = @_;
+
+ $self->{items}->{block} = $value;
+ }
+
+ sub reloadList
+ {
+ my $self = shift;
+ $self->{items}->reloadList($self, 1);
+ $self->endProgress;
+ }
+
+ sub setItemsList
+ {
+ my ($self, $init, $doNotSavePreferences) = @_;
+
+ my $view = $self->{options}->view;
+ my $columns = $self->{options}->columns;
+ $columns = 0 if $self->{options}->resizeImgList;
+
+ my $current = -1;
+ if ($self->{itemsView})
+ {
+ $current = $self->{itemsView}->getCurrentIdx if !$init;
+ $self->{listPane}->remove($self->{itemsView}) if $self->{listPane};
+ $self->savePreferences if ! $doNotSavePreferences;
+ $self->{itemsView}->destroy;
+ }
+
+ if ($view == 0)
+ {
+ $self->{itemsView} = new GCTextList($self, $self->{model}->getDisplayedItems);
+ }
+ elsif ($view == 1)
+ {
+ $self->{itemsView} = new GCImageList($self, $columns);
+ }
+ else
+ {
+ $self->{itemsView} = new GCDetailedList($self);
+ }
+ $self->{itemsView}->{initializing} = 1;
+
+ $self->{listOptionsPanel}->setView($view);
+
+ $self->setExpandCollapseInContext($self->{itemsView}->couldExpandAll);
+
+ if ($self->{listPane})
+ {
+ $self->{listPane}->pack1($self->{itemsView},1,0);
+ $self->{itemsView}->show_all;
+ }
+ if ($self->{items})
+ {
+ $self->reloadList if ! $self->{initializing};
+ Glib::Timeout->add(100 ,\&showCurrent, $self);
+ }
+
+ #Change corresponding item in context menu
+ $self->{ignoreContextActivation} = 1;
+
+ if ($self->{options}->tearoffMenus)
+ {
+ $self->{context}->{menuDisplayType}->set_active($view + 1);
+ }
+ else
+ {
+ $self->{context}->{menuDisplayType}->set_active($view);
+ }
+ $self->{context}->{menuDisplayType}->get_active->set_active(1);
+ $self->{ignoreContextActivation} = 0;
+ #Assign context menu to items list that will be in charge of displaying it.
+
+ if (!$init)
+ {
+ $self->checkToolbarOptions; # if !$doNotSavePreferences;
+ $self->{items}->display($current);
+ $self->{itemsView}->select($current);
+ }
+ }
+
+ sub checkToolbarOptions
+ {
+ my $self = shift;
+ $self->{toolbar}->{blocked} = 1;
+ # Text list doesn't support grouping
+ if ($self->{options}->view == 0)
+ {
+ $self->{toolbar}->setGroupField('')
+ }
+ else
+ {
+ $self->{toolbar}->setGroupField($self->{model}->{preferences}->groupBy);
+ }
+ $self->{toolbar}->setItemsList($self->{options}->view);
+ $self->{toolbar}->setLayout($self->{model}->{preferences}->layout);
+ $self->{toolbar}->{blocked} = 0;
+ }
+
+ sub createContextMenu
+ {
+ my $self = shift;
+
+ # Context menu creation. It is displayed when right clicking on a list item.
+ $self->{context} = new Gtk2::Menu;
+
+ if ($self->{options}->tearoffMenus)
+ {
+ $self->{context}->append(Gtk2::TearoffMenuItem->new());
+ }
+
+ $self->{contextNewWindow} = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{MenuNewWindow});
+ $self->{contextNewWindow}->signal_connect("activate" , sub {
+ $self->displayInWindow(undef, 'item');
+ });
+ $self->{context}->append($self->{contextNewWindow});
+
+ $self->{contextExpand}->{expand} = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{ContextExpandAll});
+ $self->{contextExpand}->{expand}->signal_connect('activate', sub {
+ $self->{itemsView}->expandAll;
+ });
+ $self->{context}->append($self->{contextExpand}->{expand});
+
+ $self->{contextExpand}->{collapse} = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{ContextCollapseAll});
+ $self->{contextExpand}->{collapse}->signal_connect('activate', sub {
+ $self->{itemsView}->collapseAll;
+ });
+ $self->{context}->append($self->{contextExpand}->{collapse});
+
+
+ #$self->{contextExpand}->{separator} = new Gtk2::SeparatorMenuItem;
+ $self->{context}->append(Gtk2::SeparatorMenuItem->new);
+
+ $self->{contextDuplicateItem} = Gtk2::ImageMenuItem->new_from_stock('gtk-dnd');
+ $self->{contextDuplicateItem}->set_accel_path('<main>/Edit/gtk-dnd');
+ $self->{contextDuplicateItem}->signal_connect('activate' , sub {
+ $self->duplicateItem;
+ });
+ $self->{context}->append($self->{contextDuplicateItem});
+
+ $self->{contextSelectAllItem} = Gtk2::ImageMenuItem->new_from_stock('gtk-select-all',undef);
+ $self->{contextSelectAllItem}->signal_connect("activate" , sub {
+ $self->selectAll;
+ });
+ $self->{context}->append($self->{contextSelectAllItem});
+
+ $self->{contextItemDelete} = Gtk2::ImageMenuItem->new_from_stock('gtk-delete',undef);
+ $self->{contextItemDelete}->signal_connect("activate" , sub {
+ $self->deleteCurrentItem;
+ });
+ $self->{context}->append($self->{contextItemDelete});
+
+ $self->{context}->append(Gtk2::SeparatorMenuItem->new);
+ $self->{context}->{menuDisplayType} = new Gtk2::Menu;
+ if ($self->{options}->tearoffMenus)
+ {
+ $self->{context}->{menuDisplayType}->append(Gtk2::TearoffMenuItem->new());
+ }
+ my %views = %{$self->{lang}->{OptionsViews}};
+ my $displayGroup = undef;
+ foreach (0..(scalar(keys %views) - 1))
+ {
+ my $item = Gtk2::RadioMenuItem->new_with_mnemonic($displayGroup, $views{$_});
+ $item->signal_connect('activate', sub {
+ my ($widget, $self) = @_;
+ return if ($self->{ignoreContextActivation});
+ if ($widget->get_active)
+ {
+ my $group = $widget->get_group;
+ my $i = 0;
+ $i++ while ($views{$i} ne $widget->child->get_label);
+ $self->{options}->view($i);
+ $self->setItemsList(0)
+ if ! $self->{initializing};
+ $self->checkView;
+
+ }
+ }, $self);
+ $self->{context}->{menuDisplayType}->append($item);
+ $displayGroup = $item->get_group;
+ }
+
+ $self->{context}->{itemDisplayType} = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{OptionsView});
+ $self->{context}->{itemDisplayType}->set_submenu($self->{context}->{menuDisplayType});
+ $self->{context}->append($self->{context}->{itemDisplayType});
+
+ $self->{context}->{displayItem} = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{MenuDisplayMenu});
+ $self->{context}->append($self->{context}->{displayItem});
+
+ $self->{context}->append(Gtk2::SeparatorMenuItem->new);
+
+ # Filters selection
+
+ my $menuDisplay = Gtk2::Menu->new();
+ my $filterItem = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{MenuDisplay});
+
+ $self->{contextViewAllItems} = Gtk2::MenuItem->new_with_mnemonic($self->{lang}->{MenuViewAllItems});
+ $self->{contextViewAllItems}->signal_connect("activate" , sub {
+ $self->viewAllItems;
+ });
+ $menuDisplay->append($self->{contextViewAllItems});
+
+ my $searchSelectedItems = Gtk2::ImageMenuItem->new_from_stock('gtk-find',undef);
+ $searchSelectedItems->signal_connect("activate" , sub {
+ $self->search('displayed');
+ });
+ $menuDisplay->append($searchSelectedItems);
+
+ $filterItem->set_submenu($menuDisplay);
+ $self->{context}->append($filterItem);
+
+ $self->{context}->signal_connect('show' => sub {
+ $self->{menubar}->attachDisplayMenu($self->{context}->{displayItem});
+ });
+
+ $self->{context}->signal_connect('hide' => sub {
+ $self->{menubar}->attachDisplayMenu();
+ });
+
+ $self->{context}->show_all;
+
+ }
+
+ sub setExpandCollapseInContext
+ {
+ my ($self, $active) = @_;
+
+ foreach (keys %{$self->{contextExpand}})
+ {
+ $self->{contextExpand}->{$_}->set_sensitive($active);
+ }
+ }
+
+ sub contextDisplayChange
+ {
+ my ($self, $widget, $menuName, $number) = @_;
+
+ return if $widget && ! $widget->get_active;
+ return if $self->{menubar}->{contextUpdating};
+
+ $self->{menubar}->{contextUpdating} = 1;
+ $self->{menubar}->{$menuName}->set_active($number);
+ $self->{menubar}->{$menuName}->get_active->activate;
+ $self->{menubar}->{contextUpdating} = 0;
+ }
+
+ sub showCurrent
+ {
+ my $self = shift;
+ $self->{itemsView}->showCurrent;
+ return 0;
+ }
+
+ sub viewAllItems
+ {
+ my ($self, $self2) = @_;
+ $self = $self2 if $self2;
+
+ $self->{menubar}->selectAll;
+ }
+
+ sub transformTitle
+ {
+ my ($self, $title) = @_;
+
+ return $title if ! $self->{options}->{options}->{transform};
+
+ return $title if $title !~ $self->{articlesRegexp};
+ #'
+ #return $title if $title !~ /^($self->{articlesRegexp})(\s+|('))(.*)/i;
+ return ucfirst($4)." ($1$3)";
+ }
+
+ sub transformValue
+ {
+ my ($self, $value, $field, $withMulti) = @_;
+
+ my $type = '';
+ $type = $self->{model}->{fieldsInfo}->{$field}->{type}
+ if defined $self->{model}->{fieldsInfo}->{$field}->{type};
+ if ($field eq $self->{model}->{commonFields}->{borrower}->{name})
+ {
+ $value = $self->{lang}->{PanelNobody} if (! $value) || ($value eq 'none');
+ }
+ else
+ {
+ if ($type eq 'date')
+ {
+ $value = GCUtils::timeToStr($value, $self->{options}->dateFormat);
+ }
+ elsif ($type =~ /list$/o)
+ {
+ if ($withMulti)
+ {
+ $value = GCPreProcess::multipleListToArray($value);
+ }
+ else
+ {
+ $value = GCPreProcess::multipleList($value, $type);
+ }
+ }
+ elsif ($type eq 'options')
+ {
+ $value = $self->{items}->valueToDisplayed($value, $field) || $value;
+ }
+ if ($field eq $self->{model}->{commonFields}->{title})
+ {
+ $value = $self->transformTitle($value);
+ }
+ }
+ return $value;
+ }
+
+ sub new
+ {
+ my ($proto, $options, $version, $searchJob) = @_;
+
+ my $class = ref($proto) || $proto;
+ my $self = $class->SUPER::new('toplevel');
+ bless ($self, $class);
+
+ $options->setParent($self);
+ $self->{options} = $options;
+ $self->{version} = $version;
+ $self->{searchJob} = $searchJob;
+
+ $self->{logosDir} = $ENV{GCS_SHARE_DIR}.'/logos/';
+ $self->{hasPictures} = (-f $self->{logosDir}.'splash.png');
+ $self->{tmpImageDir} = tempdir(CLEANUP => 1);
+
+ $self->{lang} = $GCLang::langs{$self->{options}->lang};
+
+ if (! $ENV{GCS_PROFILING})
+ {
+ #GCPlugins::loadPlugins;
+ GCExport::loadExporters;
+ GCImport::loadImporters;
+ }
+
+ if (! $options->exists('style'))
+ {
+ $options->style('Gtk') if ($^O !~ /win32/i);
+ $options->style('GCstar') if ($^O =~ /win32/i);
+ }
+ GCStyle::initStyles;
+ my $style = $GCStyle::styles{$options->style};
+
+ if ((! $options->exists('itemWindowWidth'))
+ || (! $options->exists('itemWindowHeight')))
+ {
+ $options->itemWindowWidth(600);
+ $options->itemWindowHeight(500);
+ }
+
+ $self->{style} = $style;
+
+ $self->{initializing} = 1;
+
+ if (($self->{options}->splash) || (! $self->{options}->exists('splash')))
+ {
+ $self->{splash} = new GCSplashWindow($self, $self->{version});
+ }
+ else
+ {
+ $self->init;
+ $self->loadPrevious;
+ $self->initEnd;
+ $self->{initializing} = 0;
+ $self->setSensitive(1);
+ }
+
+ return $self;
+ }
+
+ sub createStockItems
+ {
+ my $self = shift;
+
+ my $baseStock;
+ $baseStock->{translation_domain} = 'gtk20';
+ $baseStock->{keyval} = 0;
+ $baseStock->{modifier} = [ ];
+
+ $baseStock->{stock_id} = 'gtk-execute';
+ $baseStock->{label} = $self->{lang}->{ToolbarRandom};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-refresh';
+ $baseStock->{label} = $self->{lang}->{ToolbarAll};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-convert';
+ $baseStock->{label} = $self->{lang}->{MenuImport};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-revert-to-saved';
+ $baseStock->{label} = $self->{lang}->{MenuExport};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-dnd';
+ $baseStock->{label} = $self->{lang}->{MenuDuplicate};
+ Gtk2::Stock->add($baseStock);
+ my $iconFactory = Gtk2::IconFactory->new;
+ $iconFactory->add('gtk-dnd', (Gtk2::IconFactory->lookup_default ('gtk-copy')));
+ $iconFactory->add_default;
+
+ $baseStock->{stock_id} = 'gtk-jump-to';
+ $baseStock->{label} = $self->{lang}->{PanelSearchButton};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-help';
+ #$baseStock->{keyval} = 'F1';
+ #$baseStock->{modifier} = [];
+ $baseStock->{label} = $self->{lang}->{MenuHelpContent};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-about';
+ $baseStock->{label} = $self->{lang}->{MenuAbout};
+ Gtk2::Stock->add($baseStock);
+
+ $baseStock->{stock_id} = 'gtk-zoom-in';
+ $baseStock->{label} = $self->{lang}->{ResultsPreview};
+ Gtk2::Stock->add($baseStock);
+
+ my $addStock = Gtk2::Stock->lookup('gtk-add');
+ # Ctrl-T
+ $addStock->{keyval} = ord('T');
+ $addStock->{modifier} = [ 'control-mask' ];
+ Gtk2::Stock->add($addStock);
+
+ my $selectAllStock = Gtk2::Stock->lookup('gtk-select-all');
+ if (!$selectAllStock)
+ {
+ $baseStock->{stock_id} = 'gtk-select-all';
+ $baseStock->{label} = $self->{lang}->{DisplayOptionsAll};
+ $baseStock->{modifier} = [];
+ Gtk2::Stock->add($baseStock);
+ }
+
+ }
+
+ sub init
+ {
+ my $self = shift;
+ my $splash = shift;
+
+ $self->{options}->save if $self->checkImagesDirectory(1);
+
+ $self->createStockItems;
+ $splash->setProgress(0.01) if $splash;
+
+ $self->{modelsFactory} = new GCModelsCache($self);
+
+ Gtk2::Rc->parse($self->{style}->{rcFile});
+
+ $self->{AccelMapFile} = $ENV{GCS_CONFIG_HOME}.'/AccelMap';
+ $self->{agent} = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5) Gecko/20041111 Firefox/1.0';
+ $self->{ignoreString} = 'gcf_000_ignore';
+ $self->{imagePrefix} = 'gcstar_';
+
+ $self->{tooltips} = Gtk2::Tooltips->new();
+ if ($self->{searchJob}->{command})
+ {
+ $self->{searchJob}->{command}->autoflush(1);
+ $self->{searchJob}->{data}->autoflush(1);
+ }
+
+ $self->refreshTitle;
+ my $iconPrefix = $ENV{GCS_SHARE_DIR}.'/icons/gcstar_';
+ my $pixbuf16 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'16x16.png');
+ my $pixbuf32 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'32x32.png');
+ my $pixbuf48 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'48x48.png');
+ my $pixbuf64 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'64x64.png');
+ $self->set_default_icon_list($pixbuf16, $pixbuf32, $pixbuf48, $pixbuf64);
+
+ $self->signal_connect(delete_event => \&beforeDestroy, $self);
+ $self->signal_connect(destroy => sub { Gtk2->main_quit; });
+
+ $self->createContextMenu;
+
+ $splash->setProgress(0.03) if $splash;
+
+ $self->{items} = new GCItems($self);
+ $self->{panel} = 0;
+
+ $self->{menubar} = new GCMenuBar($self, $self->{AccelMapFile});
+ $self->{menubar}->set_name('GCMenubar');
+
+ $self->{bookmarksLoader} = new GCBookmarksLoader($self, $self->{menubar});
+ $self->{toolbar} = GCToolBar->new($self);
+
+ $self->{mainVbox} = new Gtk2::VBox(0, 0);
+ $self->{mainHbox} = new Gtk2::HBox(0, 0);
+ $self->{pane} = new Gtk2::HPaned;
+ $self->{listPane} = new Gtk2::VPaned;
+
+ $self->{pane}->set_position($self->{options}->split);
+ $self->{listPane}->set_position($self->{options}->listPaneSplit);
+ $self->{pane}->pack1($self->{listPane},1,0);
+ $self->{listOptionsPanel} = new GCListOptionsPanel($self->{options}, $self);
+ $self->{listPane}->pack2($self->{listOptionsPanel},1,1);
+
+ $self->{mainVbox}->pack_start($self->{menubar}, 0, 0, 0);
+ $self->{mainHbox}->pack_start($self->{pane},1,1,0);
+ $self->{mainVbox}->pack_start($self->{mainHbox}, 1, 1, 0);
+
+ $self->{status} = Gtk2::Statusbar->new;
+ $self->{status}->set_has_resize_grip(1);
+ $self->{progress} = new Gtk2::ProgressBar;
+ $self->{progress}->set_size_request(100,-1);
+ $self->{status}->pack_start($self->{progress}, 0, 0, 5);
+ $self->{mainVbox}->pack_start($self->{status},0,0,0);
+
+ $self->setSensitive(0);
+ $self->checkToolbarPosition;
+ $self->add($self->{mainVbox});
+
+ $splash->setProgress(0.05) if $splash;
+
+ $self->set_default_size($self->{options}->width,$self->{options}->height);
+
+ $self->drag_dest_set('all', ['copy','private','default','move','link','ask']);
+
+ $self->signal_connect(drag_data_received => \&drop_handler, $self);
+
+ my $target_list = Gtk2::TargetList->new();
+ my $atom1 = Gtk2::Gdk::Atom->new('text/uri-list');
+ my $atom2 = Gtk2::Gdk::Atom->new('text/plain');
+ $target_list->add($atom1, 0, 0);
+ $target_list->add($atom2, 0, 0);
+ if ($^O =~ /win32/i)
+ {
+ my $atom3 = Gtk2::Gdk::Atom->new('DROPFILES_DND');
+ $target_list->add($atom3, 0, 0);
+ }
+
+ $self->drag_dest_set_target_list($target_list);
+
+ sub drop_handler {
+ my ($widget, $context, $widget_x, $widget_y, $data, $info, $time, $self) = @_;
+ my $type = $data->type->name;
+
+ if (($type eq 'text/uri-list')
+ || ($type eq 'DROPFILES_DND'))
+ {
+ my @files = split /\n/, $data->data;
+ my $numbers = scalar @files;
+ $numbers-- if ($files[$#files] =~ /^\W*$/);
+
+ my ($filename, undef, $extension) = fileparse($files[0],qr{\..*});
+ $extension =~ s/[^\.\w]//g;
+ $extension = lc($extension);
+
+ if (($numbers == 1)
+ && ($files[0] =~ /\.gcs.?$/))
+ {
+ # Special case when only one .gcs file is dropped
+ (my $fileName = $files[0]) =~ s/^file:\/\/(.*)\W*$/$1/;
+ $fileName =~ s/.$//ms;
+ $self->openFile($fileName);
+ }
+ elsif (($numbers == 1)
+ && ($files[0] =~ /^http:\/\//))
+ {
+ # One url has been dropped, parse it for item data
+ $self->loadUrl($files[0]);
+ }
+ elsif ((grep {$_ eq $extension} @GCBaseWidgets::videoExtensions)
+ && ($self->{model}->{collection}->{name} eq 'GCfilms'))
+ {
+ # At least one video file was dropped and a movie collection is open
+ $self->handleDroppedFiles(\@files, \@GCBaseWidgets::videoExtensions, 'trailer');
+ }
+ elsif ((grep {$_ eq $extension} @GCBaseWidgets::ebookExtensions)
+ && ($self->{model}->{collection}->{name} eq 'GCbooks'))
+ {
+ # At least one ebook file was dropped and a book collection is open
+ $self->handleDroppedFiles(\@files, \@GCBaseWidgets::ebookExtensions, 'digitalfile');
+ }
+ elsif ((grep {$_ eq $extension} @GCBaseWidgets::audioExtensions)
+ && ($self->{model}->{collection}->{name} eq 'GCmusics'))
+ {
+ # At least one audio file was dropped and a music collection is open
+ $self->handleDroppedFiles(\@files, \@GCBaseWidgets::audioExtensions, 'playlist');
+ }
+ else
+ {
+ my $fileName = $self->{options}->file;
+ #$self->newList;
+ foreach (@files)
+ {
+ if (!$self->importWithDetect($_))
+ {
+ my $dialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'ok',
+ $self->{lang}->{ImportDropError});
+
+ $dialog->set_position('center-on-parent');
+ my $response = $dialog->run;
+ $dialog->destroy;
+
+ $self->openFile($fileName);
+ last;
+ }
+ }
+ #$self->{items}->setStatus;
+ $self->setNbItems;
+ }
+ }
+ elsif ((my $url = $data->data) =~ m/^http:\/\//)
+ {
+ $self->loadUrl($url);
+ }
+ }
+
+ $splash->setProgress(0.07) if $splash;
+
+ $self->show_all;
+
+ $self->checkDisplayed;
+ $self->checkView;
+ $self->checkTransform;
+
+ $self->{filterSearch} = new GCFilterSearch;
+
+ $splash->setProgress(0.09) if $splash;
+
+ $self->{options}->searchStop(0) if ($^O =~ /win32/i);
+ }
+
+ sub handleDroppedFiles
+ {
+ my ($self, $files, $acceptedExtensions, $field) = @_;
+
+ foreach (@$files)
+ {
+ # Split filename up into parts
+ my ($filename, undef, $extension) = fileparse($_,qr{\..*});
+ $extension =~ /(\.\w\w\w)/;
+ $extension = lc($1);
+
+ # Double check that the current file being handled is acceptable
+ if (grep {$_ eq $extension} @$acceptedExtensions)
+ {
+ # Extract a clean version of the filename
+ (my $file = $_) =~ s/\W*$//;
+ $file =~ s/^file:\/\///;
+
+ # Add a new item
+ $self->newItem;
+
+ # Set the file field to match the dropped filename
+ $self->{panel}->$field(uri_unescape($file));
+
+ # Fetch info
+ $filename = uri_unescape($filename);
+ $self->searchItem($filename, undef, 0, $self->{model}->{commonFields}->{title});
+ }
+ }
+ }
+
+ sub setPanel
+ {
+ my $self = shift;
+ my $panelInfo;
+ if (! $self->{model}->{preferences}->exists('layout') || (! $self->{model}->{preferences}->layout))
+ {
+ $panelInfo = $self->{model}->getDefaultPanel;
+ $self->{model}->{preferences}->layout($panelInfo->{name})
+ }
+ else
+ {
+ $panelInfo = $self->{model}->{panels}->{$self->{model}->{preferences}->layout};
+ }
+ if ($panelInfo->{editable} eq 'true')
+ {
+ $self->{panel} = new GCFormPanel($self, $self->{options}, $panelInfo);
+ }
+ else
+ {
+ $self->{panel} = new GCReadOnlyPanel($self, $self->{options}, $panelInfo);
+ }
+ $self->{panel}->setExpandersMode($self->{options}->expandersMode);
+ $self->{panel}->show_all;
+ }
+
+ sub changePanel
+ {
+ # $modelChanged means there was any change in the model and not that we are just
+ # changing the panel because the layout was changed in preferences
+ # $modelUpdated means the model is the same, but some fields were added/removed
+ my ($self, $modelChanged, $modelUpdated) = @_;
+
+ #Save previous histories
+ my %savedHistories;
+ if ($self->{panel})
+ {
+ # If there was no change or just an update, we save the history
+ if ((!$modelChanged) || $modelUpdated)
+ {
+ foreach(@{$self->{model}->{fieldsHistory}})
+ {
+ $savedHistories{$_} = $self->{panel}->getValues($_);
+ }
+ $self->{items}->updateSelectedItemInfoFromPanel if !$modelUpdated;
+ }
+ $self->{scrollPanelItem}->remove($self->{scrollPanelItem}->get_child);
+ $self->{panel}->destroy;
+ $self->{pane}->remove($self->{scrollPanelItem});
+ $self->{scrollPanelItem}->destroy;
+ }
+
+ $self->setPanel;
+ $self->{panel}->createContent($self->{model});
+
+ $self->{scrollPanelItem} = new Gtk2::ScrolledWindow;
+ $self->{scrollPanelItem}->set_policy ('automatic', 'automatic');
+ $self->{scrollPanelItem}->set_shadow_type('none');
+ $self->{scrollPanelItem}->add_with_viewport($self->{panel});
+ $self->{pane}->pack2($self->{scrollPanelItem},1,1);
+ $self->{scrollPanelItem}->show_all;
+ $self->{items}->setPanel($self->{panel});
+ $self->checkBorrowers;
+ $self->checkPanelContent;
+
+ if (%savedHistories)
+ {
+ #Restore histories
+ foreach(@{$self->{model}->{fieldsHistory}})
+ {
+ $self->{panel}->{$_}->setValues($savedHistories{$_});
+ }
+ }
+
+ $self->{items}->displayCurrent;
+ $self->checkToolbarOptions;
+ }
+
+ sub removeToolbar
+ {
+ my $self = shift;
+
+ #Remove previous if exists
+ if ($self->{toolbar})
+ {
+ $self->{mainHbox}->remove($self->{toolbar})
+ if ($self->{toolbar}->parent eq $self->{mainHbox});
+ $self->{mainVbox}->remove($self->{toolbar})
+ if ($self->{toolbar}->parent eq $self->{mainVbox});
+ };
+ }
+
+ sub checkToolbarPosition
+ {
+ my ($self, $deactivateRemoval) = @_;
+
+ $self->removeToolbar if !$deactivateRemoval;
+
+ my $position = $self->{options}->toolbarPosition;
+
+ # 0 => top
+ # 1 => bottom
+ # 2 => left
+ # 3 => right
+
+ if ($position > 1) # left or right
+ {
+ $self->{toolbar}->set_orientation('vertical');
+ $self->{mainHbox}->pack_start($self->{toolbar},0,0,0);
+ if ($position == 2) # left
+ {
+ $self->{mainHbox}->reorder_child($self->{toolbar},0);
+ }
+ else # right
+ {
+ $self->{mainHbox}->reorder_child($self->{toolbar},1);
+ }
+ }
+ else # top or bottom
+ {
+ $self->{toolbar}->set_orientation('horizontal');
+ $self->{mainVbox}->pack_start($self->{toolbar},0,0,0 );
+ if ($position == 1) # bottom
+ {
+ $self->{mainVbox}->reorder_child($self->{toolbar},2);
+ }
+ else # top
+ {
+ $self->{mainVbox}->reorder_child($self->{toolbar},1);
+ }
+ }
+ $self->{toolbar}->show_all;
+ $self->{toolbar}->setShowFieldsSelection($position <= 1);
+ }
+
+ sub checkDisplayed
+ {
+ my $self = shift;
+
+ $self->setDisplayMenuBar($self->{options}->displayMenuBar);
+ $self->setDisplayStatusBar($self->{options}->status);
+ $self->setDisplayToolBar($self->{options}->toolbar);
+ }
+
+ sub checkProxy
+ {
+ my $self= shift;
+
+ $self->{plugin}->setProxy($self->{options}->proxy) if ($self->{plugin});
+ }
+
+ sub checkCookieJar
+ {
+ my $self= shift;
+ $self->{plugin}->setCookieJar($self->{options}->cookieJar) if ($self->{plugin});
+ }
+
+ sub checkPlugin
+ {
+ my $self = shift;
+ $self->{plugin} = undef;
+ $self->{plugin} = $self->{model}->getPlugin($self->{model}->{preferences}->plugin);
+
+ $self->checkProxy;
+ $self->checkCookieJar;
+ }
+
+ sub checkBorrowers
+ {
+ my $self = shift;
+
+ #my @borrowers = split m/\|/, $self->{options}->borrowers;
+ $self->{panel}->setBorrowers;
+ }
+
+ sub checkView
+ {
+ my $self = shift;
+ if ($self->{options}->tearoffMenus)
+ {
+ $self->{context}->{menuDisplayType}->set_active($self->{options}->view + 1);
+ }
+ else
+ {
+ $self->{context}->{menuDisplayType}->set_active($self->{options}->view);
+ }
+ $self->{context}->{menuDisplayType}->get_active->set_active(1);
+ }
+
+ sub checkTransform
+ {
+ my $self = shift;
+
+ my @array = split m/,/, $self->{options}->articles;
+
+ my $tmpExpr = '';
+
+ foreach (@array)
+ {
+ s/^\s*//;
+ s/\s*$//;
+ $tmpExpr .= "\Q$_\E|";
+ }
+ chomp $tmpExpr;
+
+ $self->{articles} = \@array;
+ #$self->{articlesRegexp} = $tmpExpr;
+ $self->{articlesRegexp} = qr/^($tmpExpr)(\s+|('))(.*)/i;
+ #'
+ $self->reloadList if ! $self->{initializing};
+ }
+
+ sub checkPanelContent
+ {
+ my $self = shift;
+
+ my $hasToShow = 1;
+ $hasToShow = 0 if (! $self->{itemsView}->getNbItems);
+
+ $self->{panel}->setShowOption($self->getDialog('DisplayOptions')->{show}, $hasToShow);
+ }
+
+ sub checkSpellChecking
+ {
+ my $self = shift;
+ my $lang;
+ if ($self->{items})
+ {
+ $lang = $self->{items}->getInformation->{lang};
+ $lang =~ s/^(\w*).*$/$1/;
+ }
+ $self->{panel}->setSpellChecking($self->{options}->spellCheck,
+ $lang);
+ }
+
+ sub loadPrevious
+ {
+ my $self = shift;
+ my $splash = shift;
+ $self->{items}->setOptions($self->{options});
+ return if !$self->{options}->file;
+
+ #if ($self->{items}->setOptions($self->{options}, $splash))
+ my ($success, $error) = $self->{items}->load($self->{options}->file, $splash, 0);
+ if ($success)
+ {
+ $self->checkDefaultImage;
+ $self->refreshFilters;
+ $self->refreshTitle;
+ $self->{menubar}->setLock($self->{items}->getLock);
+ $self->addFileHistory($self->{options}->file);
+ $self->checkPanelContent;
+ $self->checkBorrowers;
+# $self->checkSpellChecking;
+ }
+ else
+ {
+ $splash->hide if $splash;
+ my $dialog = new GCCriticalErrorDialog(
+ $self,
+ GCUtils::formatOpenSaveError(
+ $self->{lang},
+ $self->{options}->file,
+ $error
+ )
+ );
+ $dialog->show;
+ $self->{options}->file('');
+
+ }
+
+ #$self->{itemsView}->setSortOrder;
+
+ #$splash->setProgress(0.99) if $splash;
+ }
+
+ sub initEnd
+ {
+ my $self = shift;
+
+ if ($self->{options}->{created})
+ {
+ $self->{options}->checkPreviousGCfilms($self);
+ }
+
+ if (! $self->{options}->file)
+ {
+ $self->{splash}->destroy
+ if $self->{splash};
+ $self->{splash} = 0;
+ $self->newList;
+ }
+
+ $self->reloadDone(0,$self->{splash});
+ #$self->{itemsView}->done;
+ $self->{items}->display($self->{items}->select(-1,1));
+
+ if ($ENV{GCS_PROFILING} > 2)
+ {
+ $self->leave;
+ }
+
+ $self->{changedChecked} = Glib::Timeout->add(2000 , sub {
+ if ($GCGraphicComponent::somethingChanged)
+ {
+ $self->markAsUpdated;
+ $GCGraphicComponent::somethingChanged = 0;
+ }
+ return 1;
+ });
+ }
+
+ sub setFullScreen
+ {
+ my ($self, $fullscreen) = @_;
+
+ if ($fullscreen)
+ {
+ $self->fullscreen;
+ }
+ else
+ {
+ $self->unfullscreen;
+ }
+ }
+
+ sub setDisplayMenuBar
+ {
+ my ($self, $show) = @_;
+
+ if ($show)
+ {
+ $self->{menubar}->show_all;
+ }
+ else
+ {
+ $self->{menubar}->hide;
+ }
+ $self->{options}->displayMenuBar($show);
+ }
+
+ sub setDisplayToolBar
+ {
+ my ($self, $show) = @_;
+
+ if ($show)
+ {
+ $self->{toolbar}->show_all;
+ }
+ else
+ {
+ $self->{toolbar}->hide;
+ }
+ $self->{options}->toolbar($show);
+ }
+
+ sub setDisplayStatusBar
+ {
+ my ($self, $show) = @_;
+
+ if ($show)
+ {
+ $self->{status}->show_all;
+ }
+ else
+ {
+ $self->{status}->hide;
+ }
+ $self->{options}->status($show);
+ }
+
+ sub setDefaultValues
+ {
+ my $self = shift;
+
+ my $window = $self->getItemWindow('defaultValues');
+ my $title = $self->{lang}->{MenuDefaultValues};
+
+ $window->setTitle($title);
+
+ my $info = $self->{model}->getDefaultValues;
+ $self->{items}->displayDataInPanel($window->{panel}, $info);
+ my $code = $window->show;
+
+ if ($code eq 'ok')
+ {
+ my ($changed, $info, $previous) = $self->{items}->getInfoFromPanel($window->{panel}, $info);
+ $self->{model}->setDefaultValues($info);
+ }
+
+ $self->saveItemWindowSettings($window);
+
+ $window->hide;
+
+ return $code;
+
+ }
+}
+
+1;