diff options
author | Jörg Frings-Fürst <jff@merkur> | 2014-07-06 15:20:38 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <jff@merkur> | 2014-07-06 15:20:38 +0200 |
commit | 126bb8cb6b93240bb4d3a2b816b74c286c3d422b (patch) | |
tree | e66e1dfe77d53a52539489765c88d23e4423ae27 /lib/gcstar/GCModel.pm |
Imported Upstream version 1.7.0upstream/1.7.0
Diffstat (limited to 'lib/gcstar/GCModel.pm')
-rw-r--r-- | lib/gcstar/GCModel.pm | 2896 |
1 files changed, 2896 insertions, 0 deletions
diff --git a/lib/gcstar/GCModel.pm b/lib/gcstar/GCModel.pm new file mode 100644 index 0000000..2223e3e --- /dev/null +++ b/lib/gcstar/GCModel.pm @@ -0,0 +1,2896 @@ +package GCModel; + +################################################### +# +# 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 strict; +use GCPlugins; + +use Storable; + +our $modelsSuffix = '.gcm'; +our $autoId = 'gcsautoid'; +our $autoField = 'GCSAuto'; +our $noneField = 'GCSNone'; +our $initDefault = 'DefaultNewItem'; +our $linkNameSeparator = '##'; + +{ + package GCModelLoader; + + use GCUtils 'glob'; + use XML::Simple; + $XML::Simple::PREFERRED_PARSER='XML::Parser'; + #$XML::Simple::PREFERRED_PARSER='XML::SAX::Expat'; + + sub load + { + my ($self, $file) = @_; + open XML, $file; + binmode(XML, ':utf8'); + $self->{xmlString} = do {local $/; <XML>}; + $self->loadFromString; + close XML; + } + + sub loadFromString + { + my $self = shift; + my $xs = XML::Simple->new; + return if ! $self->{xmlString}; + $self->{collection} = $xs->XMLin($self->{xmlString}, + ForceArray => ['item', 'values', 'filter', 'panel', 'group', 'field'], + KeyAttr => { + 'values' => 'id' + } + ); + $self->doInitialization; + } + + sub doInitialization + { + my $self = shift; + $self->{groups} = $self->{collection}->{groups}->{group}; + $self->{fields} = $self->{collection}->{fields}->{field}; + #$self->{filters} = $self->{collection}->{filters}->{filter}; + $self->initFilters; + $self->{options} = $self->{collection}->{options}; + $self->{commonFields} = $self->{collection}->{options}->{fields}; + $self->{resultsFields} = $self->{collection}->{options}->{fields}->{results}->{field}; + $self->{summaryFields} = $self->{collection}->{options}->{fields}->{summary}->{field}; + $self->{defaultImage} = $ENV{GCS_SHARE_DIR}.'/logos/'.$self->{options}->{defaults}->{image}; + $self->{userFiltersDir} = $ENV{GCS_DATA_HOME}.'/Filters/'.$self->getName; + $self->{random} = $self->{collection}->{random}->{filter}; + + my $lang = exists $self->{collection}->{lang} ? $self->{collection}->{lang} : "GCgeneric"; + my $langName = $self->{parent}->{options}->lang; + my %tmpLang; + + # The string should be splitted to avoid considering some scopped variables + eval "use GCLang::$langName"."::GCModels::$lang\n"; + eval "%tmpLang = %GCLang::$langName"."::GCModels::".$lang."::lang"; + $self->{lang} = \%tmpLang; + + $self->loadPreferences; + #$self->checkMaster; + $self->addDefaultFields; + $self->addPredefinedValues; + $self->{searchFields} = 0; + $self->checkLending; + $self->checkTags; + $self->initFields; + $self->setDefaults; + $self->initPanels; + } + + sub getSearchFields + { + my $self = shift; + + if (!$self->{searchFields}) + { + if (exists $self->{collection}->{options}->{fields}->{search}) + { + $self->{searchFields} = []; + push @{$self->{searchFields}}, $_ + foreach (@{$self->{collection}->{options}->{fields}->{search}->{field}}) + } + else + { + $self->{searchFields} = [$self->{commonFields}->{title}]; + } + } + return $self->{searchFields}; + } + + sub getSummaryFields + { + my $self = shift; + + if (!$self->{summaryFields}) + { + $self->{summaryFields} = []; + for my $field (@{$self->{resultsFields}}) + { + next if $field eq $self->{commonFields}->{title}; + push @{$self->{summaryFields}}, $field; + } + } + return $self->{summaryFields}; + } + + sub isSearchField + { + my ($self, $field) = @_; + foreach (@{$self->getSearchFields}) + { + return 1 if $field eq $_; + } + return 0; + } + + sub hasRandom + { + my $self = shift; + return ($self->{random} && (scalar @{$self->{random}})) ? 1 : 0; + } + + sub hasPlay + { + my $self = shift; + return ($self->{commonFields}->{play}) ? 1 : 0; + } + + sub saveToFile + { + my ($self, $file) = @_; + + if (!open DATA, '>'.$file) + { + print "Could not save collection description in $file\n"; + return 0; + } + binmode(DATA, ':utf8'); + print DATA '<?xml version="1.0" encoding="UTF-8"?> +'; + print DATA $self->toString('collection', 1); + close DATA; + $self->{isInline} = 0; + } + + sub setFields + { + my ($self, $fields, $hasLending, $hasTags) = @_; + #Force panel generation + delete $self->{collection}->{panels}->{panel}; + $self->{collection}->{fields}->{field} = $fields; + $self->{collection}->{fields}->{lending} = ($hasLending ? 'true' : 'false'); + $self->{collection}->{fields}->{tags} = ($hasTags ? 'true' : 'false'); + $self->doInitialization; + } + + sub setGroups + { + my ($self, $groups) = @_; + + $self->{collection}->{groups}->{group} = $groups; + } + + sub setOptions + { + my ($self, $fields, $defaultImage) = @_; + $self->{collection}->{options}->{fields} = $fields; + $self->{collection}->{options}->{defaults}->{image} = $defaultImage; + } + + sub getCommonFields + { + my $self = shift; + + return $self->{collection}->{options}->{fields}; + } + + sub getOriginalCollection + { + my ($self, $withPanel) = @_; + #First we clone our collection to do remove everything that have been auto-generated + my $collection = Storable::dclone($self->{collection}); + foreach (@{$collection->{fields}->{field}}) + { + delete $_->{hasHistory}; + } + delete $collection->{panels} if ! $withPanel; + if ($self->{hasLending}) + { + my @fields = @{$collection->{fields}->{field}}; + for (my $i = 0; $i <= $#fields; $i++) + { + delete $collection->{fields}->{field}->[$i] + if $fields[$i]->{group} eq 'lending'; + } + delete $collection->{groups}->{group}->[(scalar @{$collection->{groups}->{group}}) - 1]; + } + if ($self->{hasTags}) + { + my @fields = @{$collection->{fields}->{field}}; + for (my $i = 0; $i <= $#fields; $i++) + { + delete $collection->{fields}->{field}->[$i] + if $fields[$i]->{group} eq 'tagpanel'; + } + delete $collection->{groups}->{group}->[(scalar @{$collection->{groups}->{group}}) - 1]; + } + if ($self->{defaultModifier}) + { + delete $collection->{fields}->{lending}; + delete $collection->{fields}->{tags}; + delete $collection->{options}; + delete $collection->{random}; + } + return $collection; + } + + sub getAddedFields + { + my $self = shift; + return [] if ! $self->{addedModel}; + return $self->{addedModel}->{collection}->{fields}->{field}; + } + + sub getAddedGroups + { + my $self = shift; + return {} if ! $self->{addedModel}; + return $self->{addedModel}->getGroups; + } + + sub toString + { + my ($self, $root, $withPanel) = @_; + my $xs = XML::Simple->new; + my $collection = $self->getOriginalCollection($withPanel); + my $xmlData = $xs->XMLout($collection, + RootName => $root, + KeyAttr => {'group' => '', + 'values' => 'id' + }); + if ($self->{defaultModifier}) + { + $xmlData =~ s|(</?)item|$1userItem|g; + } + return $xmlData; + } + + sub toStringAddedFields + { + my ($self, $root) = @_; + if (($self->{addedModel}) + && exists $self->{addedModel}->{fieldsNames} + && scalar @{$self->{addedModel}->{fieldsNames}}) + { + return $self->{addedModel}->toString($root, 1); + } + else + { + return ''; + } + } + + sub loadPreferences + { + my $self = shift; + if (! $self->{isInline}) + { + $self->{configFile} = $ENV{GCS_CONFIG_HOME}.'/GCModels/'.$self->getName.'.conf'; + $self->{preferences} = new GCOptionLoader($self->{configFile}, 0, $self->{parent}->{options}); + } + $self->{defaultValuesFile} = $ENV{GCS_CONFIG_HOME}.'/GCModels/'.$self->getName.'.default.gcs'; + } + + sub setDefaults + { + my $self = shift; + + $self->{fieldsInfo}->{$self->{commonFields}->{title}}->{init} = $GCModel::initDefault + if ! $self->{fieldsInfo}->{$self->{commonFields}->{title}}->{init}; + + $self->{preferences}->groupBy($self->{options}->{defaults}->{groupby}) + if ! $self->{preferences}->exists('groupBy'); + + $self->{preferences}->quickSearchField($self->{commonFields}->{title}) + if ! $self->{preferences}->exists('quickSearchField'); + } + + sub isEmpty + { + my $self = shift; + return (!$self->{fields} || ((scalar @{$self->{fields}}) == 0)); + } + + sub isPersonal + { + my $self = shift; + return ($self->{isInline} || $self->{isPersonal}); + } + + sub isInline + { + my $self = shift; + return $self->{isInline}; + } + + sub initFields + { + my $self = shift; + + my @fieldsNames; + my %fieldsInfo; + my @fieldsHistory; + my @fieldsNotNull; + my @fieldsNotFormatted; + my @fieldsImage; + my @fieldsDate; + my @managedImages; + my $name; + foreach (@{$self->{fields}}) + { + $name = $_->{value}; + push @fieldsNames, $name; + $fieldsInfo{$name} = $_; + $fieldsInfo{$name}->{displayed} = $self->getDisplayedText($fieldsInfo{$name}->{label}); + if (($_->{type} =~ /history/) + || ( + ($_->{type} =~ /list/) + && ((!exists $_->{history}) || ($_->{history} ne 'false')) + )) + { + push @fieldsHistory, $name; + $fieldsInfo{$name}->{hasHistory} = 1; + } + elsif ($_->{type} eq 'image') + { + push @fieldsImage, $name; + push @managedImages, $name if (exists $_->{imported}) && ($_->{imported} eq 'true'); + } + elsif ($_->{type} eq 'date') + { + push @fieldsDate, $name; + } + push @fieldsNotNull, $name + if (exists $_->{notnull}) && ($_->{notnull} eq 'true'); + push @fieldsNotFormatted, $name + if ((!exists $_->{type}) || ($_->{type} ne 'formatted')); + } + $self->{fieldsNames} = \@fieldsNames; + $self->{fieldsInfo} = \%fieldsInfo; + $self->{fieldsHistory} = \@fieldsHistory; + $self->{fieldsNotNull} = \@fieldsNotNull; + $self->{fieldsNotFormatted} = \@fieldsNotFormatted; + $self->{managedImages} = \@managedImages; + $self->{fieldsImage} = \@fieldsImage; + $self->{fieldsDate} = \@fieldsDate; + } + + sub setFilters + { + my ($self, $filters) = @_; + $self->{filters} = []; + $self->{filtersGroup} = []; + my %existingGroups = (); + my $idxGroup = 0; + # The filters have not groups. And we need to do some copy here + #foreach (keys %$filters) + foreach (@{$self->{fieldsNames}}) + { + next if ! exists $filters->{$_}; + my $filter = { + field => $_, + comparison => $filters->{$_}->{comparison}, + numeric => $filters->{$_}->{numeric}, + quick => $filters->{$_}->{quick} + }; + $filter->{labelselect} = $filters->{$_}->{labelselect} if $filters->{$_}->{labelselect}; + push @{$self->{filters}}, $filter; + my $group = $self->{fieldsInfo}->{$_}->{group}; + if (! exists $existingGroups{$group}) + { + $existingGroups{$group} = $idxGroup; + $self->{filtersGroup}->[$idxGroup] = {label => $group}; + $idxGroup++; + } + push @{$self->{filtersGroup}->[$existingGroups{$group}]->{filter}}, $filter; + } + $self->{collection}->{filters}->{group} = $self->{filtersGroup}; + } + + sub initFilters + { + my $self = shift; + + for my $group (@{$self->{collection}->{filters}->{group}}) + { + foreach (@{$group->{filter}}) + { + push @{$self->{filters}}, $_; + } + } + $self->{filtersGroup} = $self->{collection}->{filters}->{group}; + } + + sub getUserFilters + { + my $self = shift; + # Deactive user filters for personal collections without a name + return [] if ! $self->getName; + if (!$self->{userFilters}) + { + my @filters; + mkdir $self->{userFiltersDir}; + foreach my $filterFile(sort {uc($a) cmp uc($b)} glob($self->{userFiltersDir}.'/*')) + { + my $xs = XML::Simple->new; + my $filter = $xs->XMLin($filterFile, + ForceArray => ['info', 'filter']); + $self->{isUserFilterSaved}->{$filter->{name}} = 1; + foreach my $info (@{$filter->{info}}) + { + $info->{filter}->[1] = 0 if ref $info->{filter}->[1] eq 'HASH'; + $info->{filter}->[2] = undef if ref $info->{filter}->[2] eq 'HASH'; + } + push @filters, $filter; + } + $self->{userFilters} = \@filters; + } + return $self->{userFilters}; + } + + sub filterNameToFile + { + my ($self, $name) = @_; + my $fileName = GCUtils::getSafeFileName($name); + return $self->{userFiltersDir}."/$fileName"; + } + + sub saveUserFilters + { + my $self = shift; + my $xs = XML::Simple->new; + foreach my $filter(@{$self->{userFilters}}) + { + next if $self->{isUserFilterSaved}->{$filter->{name}}; + $xs->XMLout($filter, + RootName => 'search', + OutputFile => $self->filterNameToFile($filter->{name})); + $self->{isUserFilterSaved}->{$filter->{name}} = 1; + } + } + + sub setUserFilters + { + my ($self, $filters) = @_; + $self->{userFilters} = $filters; + # Make sure they will be saved if needed + foreach my $filter(@$filters) + { + $self->{isUserFilterSaved}->{$filter->{name}} = $filter->{saved}; + # This should not be saved + delete $filter->{saved}; + } + } + + sub addUserFilter + { + my ($self, $filter) = @_; + push (@{$self->{userFilters}}, $filter); + my @sorted = sort {uc($a->{name}) cmp uc($b->{name})} @{$self->{userFilters}}; + $self->{userFilters} = \@sorted; + $self->{isUserFilterSaved}->{$filter->{name}} = 0; + } + + sub deleteUserFilters + { + my ($self, $names) = @_; + foreach my $name(@$names) + { + unlink $self->filterNameToFile($name); + } + } + + sub existsUserFilter + { + my ($self, $name) = @_; + return 1 if exists $self->{isUserFilterSaved}->{$name}; + return 1 if -e $self->filterNameToFile($name); + } + + sub addDefaultFields + { + my $self = shift; + return if $self->isEmpty || $self->{defaultModifier}; + if (! $self->{commonFields}->{id}) + { + $self->{commonFields}->{id} = $GCModel::autoId; + push @{$self->{fields}}, + { + value => $self->{commonFields}->{id}, + type => 'number', + label => '', + init => '', + group => '', + imported => 'false' + }; + } + } + + sub addPredefinedValues + { + my $self = shift; + + # Add file size units if needed + my @array; + my $val = 0; + foreach (@{$self->{parent}->{lang}->{PropertiesFileSizeSymbols}}) + { + push @array, + {displayed => $_, content => $val++}; + } + $self->{collection}->{options}->{values}->{filesizeunits}->{value} = \@array; + } + + sub removeAddedFields + { + my $self = shift; + return if ! $self->{addedModel}; + delete $self->{addedModel}->{collection}->{panels}->{panel}; + foreach my $container('fieldsNames', 'fieldsHistory', 'fieldsNotNull', + 'fieldsNotFormatted', 'managedImages', 'fieldsImage') + { + my @filtered = grep(!/gcsfield/, @{$self->{$container}}); + $self->{$container} = \@filtered; + } + foreach (keys %{$self->{panels}}) + { + if ($_ eq 'form') + { + if ($self->{formNotebook}) + { + my @filtered = grep(!$_->{userDefined}, @{$self->{formNotebook}->{item}}); + $self->{formNotebook}->{item} = \@filtered; + } + } + else + { + my @filtered = grep(!$_->{userDefined}, @{$self->{panels}->{$_}->{item}}); + $self->{panels}->{$_}->{item} = \@filtered; + } + } + my $addedModel = $self->{addedModel}; + delete $self->{addedModel}; + return $addedModel; + } + + sub updateAddedFields + { + my ($self, $fields, $groups) = @_; + + # First clean what we previously added + my $addedModel = $self->removeAddedFields; + + if (!$addedModel) + { + # We didn't have one previously. We need to create it. + $addedModel = GCModelLoader->newEmpty($self->{parent}, {defaultModifier => 1}); + } + $addedModel->setGroups($groups); + $addedModel->setFields($fields); + + $self->addFields($addedModel); + } + + sub addFields + { + my ($self, $model) = @_; + # Fields are stored in another model + + $self->{addedModel} = $model; + + return if ! scalar @{$model->{fieldsNames}}; + + # Groups part + my %groups = map { $_->{id} => 1 } @{$self->{groups}}; + foreach (@{$model->{groups}}) + { + push @{$self->{groups}}, $_ + if !exists $groups{$_->{id}}; + } + + # initFields part + foreach(@{$model->{fieldsNames}}) + { + push @{$self->{fieldsNames}}, $_; + $self->{fieldsInfo}->{$_} = $model->{fieldsInfo}->{$_}; + } + push @{$self->{fieldsHistory}}, @{$model->{fieldsHistory}}; + push @{$self->{fieldsNotNull}}, @{$model->{fieldsNotNull}}; + push @{$self->{fieldsNotFormatted}}, @{$model->{fieldsNotFormatted}}; + push @{$self->{managedImages}}, @{$model->{managedImages}}; + push @{$self->{fieldsImage}}, @{$model->{fieldsImage}}; + + # initPanels part + foreach (keys %{$model->{panels}}) + { + # We mark them to be able to remove them later + foreach my $container(@{$model->{panels}->{$_}->{item}}) + { + $container->{userDefined} = 1; + } + + if ($_ eq 'form') + { + # Search the main notebook + sub getNotebook + { + my $item = shift; + return $item if $item->{type} eq 'notebook'; + foreach my $child(@{$item->{item}}) + { + my $notebook = getNotebook($child); + return $notebook if $notebook; + } + return undef; + } + $self->{formNotebook} = getNotebook($self->{panels}->{$_}); + push @{$self->{formNotebook}->{item}}, @{$model->{panels}->{$_}->{item}}; + } + else + { + push @{$self->{panels}->{$_}->{item}}, @{$model->{panels}->{$_}->{item}}; + } + } + } + + sub checkLending + { + my $self = shift; + $self->{hasLending} = 0; + return if ! ($self->{collection}->{fields}->{lending} eq 'true'); + $self->{hasLending} = 1; + + # Add group + push @{$self->{groups}}, { + id => 'lending', + label => 'PanelLending' + }; + + # Add lending fields + push @{$self->{fields}}, ( + { + value => 'borrower', + type => 'options', + label => 'PanelBorrower', + init => 'none', + group => 'lending', + imported => 'false' + }, + { + value => 'lendDate', + type => 'date', + label => 'PanelLendDate', + init => '', + group => 'lending', + imported => 'false' + }, + { + value => 'borrowings', + type => 'triple list', + label => 'PanelHistory', + label1 => 'PanelBorrower', + label2 => 'PanelLendDate', + label3 => 'PanelReturnDate', + init => '', + group => 'lending', + imported => 'false' + }, + ); + + # Set fields names + $self->{commonFields}->{borrower} = { + name => 'borrower', + date => 'lendDate', + history => 'borrowings' + }; + } + + sub checkTags + { + my $self = shift; + $self->{hasTags} = 0; + return if ! ($self->{collection}->{fields}->{tags} eq 'true'); + $self->{hasTags} = 1; + + # Add group + push @{$self->{groups}}, { + id => 'tagpanel', + label => 'PanelTags' + }; + # Add tag fields + push @{$self->{fields}}, ( + { + value => 'favourite', + type => 'yesno', + label => 'PanelFavourite', + init => '0', + group => 'tagpanel', + imported => 'false' + }, + { + value => 'tags', + type => 'single list', + label => 'TagsAssigned', + init => '', + group => 'tagpanel', + imported => 'false' + }, + ); + + # Add filter + push @{$self->{filters}}, ( + { + field => 'tags', + comparison => 'contain', + quick => 'true', + numeric => 'false' + }, + { + field => 'favourite', + comparison => 'contain', + quick => 'true', + numeric => 'false' + }, + ); + + } + + sub initPanels + { + my $self = shift; + $self->createDefaultPanel if !$self->{collection}->{panels}->{panel}; + return if !$self->{collection}->{panels}->{panel}; + my @panels = @{$self->{collection}->{panels}->{panel}}; + $self->{panelsNames} = []; + $self->{panels} = {}; + foreach (@panels) + { + push @{$self->{panelsNames}}, $_->{name}; + $self->{panels}->{$_->{name}} = $_; + } + } + + sub createDefaultPanel + { + my $self = shift; + return if (! $self->{commonFields}->{title}) && (!$self->{defaultModifier}); + my $panel; + $panel->{name} = 'form'; + $panel->{label} = 'PanelForm'; + $panel->{editable} = 'true'; + my @items; + + if (!$self->{defaultModifier}) + { + my $header = {type => 'line'}; + push @{$header->{item}}, { + type => 'value', + for => $self->{commonFields}->{id}, + width => '5' + } + if $self->{commonFields}->{id} && ($self->{commonFields}->{id} ne $GCModel::autoId); + push @{$header->{item}}, { + type => 'value', + for => $self->{commonFields}->{title}, + expand => 'true' + }; + push @items, $header; + } + + my $fields = $self->getDisplayedInfo; + my $notebook; + my $hasTabs; + if ($self->{defaultModifier}) + { + $hasTabs = 1; + $notebook = undef; + } + else + { + $hasTabs = (scalar @$fields > 1); + if ($hasTabs) + { + $notebook = {type => 'notebook', expand => 'true'}; + } + } + + my $i = 0; + my $infoFrame; + foreach (@{$fields}) + { + next if (($_->{id} eq 'lending') || ($_->{id} eq 'tagpanel')); + my $tab; + if ($hasTabs) + { + $tab = {type => 'tab', value => 'bla', title => $_->{title}, expand => 'true'}; + } + else + { + $tab = {type => 'box', expand => 'true'}; + $notebook = $tab; + } + my $tabRank = 0; + my $hasInfoFrame = 0; + if ($i == 0 && $self->{commonFields}->{cover}) + { + $tab->{item} = [ + {type => 'line', item => [{type => 'value', + for => $self->{commonFields}->{cover}, + width => 150, + height => 150}]}, + {type => 'table', cols => '2'} + ]; + $infoFrame = {type => 'frame', cols => '2'}; + push @{$tab->{item}->[0]->{item}}, $infoFrame; + $tabRank = 1; + $hasInfoFrame = 1; + } + else + { + $tab->{item} = [{type => 'table', cols => '4', expand => 'true'}]; + } + my $row = 0; + my $frameRow = 0; + foreach my $item (@{$_->{items}}) + { + next if ($item->{id} eq $self->{commonFields}->{id}) + || ($item->{id} eq $self->{commonFields}->{title}) + || ($item->{id} eq $self->{commonFields}->{cover}) + || ($item->{id} eq $self->{commonFields}->{url}); + my $type = $self->{fieldsInfo}->{$item->{id}}->{type}; + my $longField = ($type eq 'long text') || ($type eq 'image') + || ($type =~ /list$/); + my $label = undef; + $label = {type => 'label', for => $item->{id}, + col => '0'} if $type ne 'yesno'; + my $value = {type => 'value', for => $item->{id}, + col => ($label ? 1 : 0), expand => 'true', + colspan => ($label ? 1 : 2)}; + my $extra = undef; + if ($type eq 'file') + { + # Add launcher next to file chooser field + $extra = {type => 'launcher', for => $item->{id}, col => '2'}; + } + if ($hasInfoFrame && ($frameRow < 5) && ! $longField) + { + if ($label) + { + $label->{row} = $frameRow; + push @{$infoFrame->{item}}, $label; + } + $value->{row} = $frameRow; + push @{$infoFrame->{item}}, $value; + if ($extra) + { + $extra->{row} = $frameRow; + push @{$infoFrame->{item}}, $extra; + } + $frameRow++; + } + else + { + if (($type =~ /list$/) && ($self->{fieldsInfo}->{$item->{id}}->{flat} eq 'true')) + { + my $label = + push @{$tab->{item}->[$tabRank]->{item}}, + {type => 'expander', title => $self->{fieldsInfo}->{$item->{id}}->{label}, + collapsed => '%'.$item->{id}.'%', + row => $row, col => 0, colspan => 3, + item => [ + {type => 'value', for => $item->{id}} + ] + }; + } + else + { + my ($colspan, $expand); + if ($longField) + { + $value->{colspan} = 3; + } + else + { + $value->{expand} = 'default'; + } + if ($label) + { + $label->{row} = $row; + push @{$tab->{item}->[$tabRank]->{item}}, $label; + } + $value->{row} = $row; + push @{$tab->{item}->[$tabRank]->{item}}, $value; + if ($extra) + { + $extra->{row} = $row; + push @{$tab->{item}->[$tabRank]->{item}}, $extra; + } + } + $row++; + } + } + $tab->{item}->[$tabRank]->{rows} = $row; + if ($hasTabs) + { + if ($self->{defaultModifier}) + { + push @items, $tab; + } + else + { + push @{$notebook->{item}}, $tab; + } + } + $i++; + } + if ($self->{hasLending}) + { + my $tab = {type => 'tab', value => 'lending', + title => 'PanelLending', + item => [ + {type => 'table', rows => '3', + item => [ + {type => 'label', for => 'borrower', row => '0', col => '0'}, + {type => 'value', for => 'borrower', row => '0', col => '1'}, + {type => 'special', for => 'mailButton', row => '0', col => '2'}, + {type => 'label', for => 'lendDate', row => '1', col => '0'}, + {type => 'value', for => 'lendDate', row => '1', col => '1'}, + {type => 'special', for => 'itemBackButton', row => '1', col => '2'} + ] + }, + {type => 'label', for => 'borrowings', align => 'left'}, + {type => 'line', expand => 'true', + item => [ + {type => 'box', width => '64'}, + {type => 'value', for => 'borrowings', expand => 'true'}, + {type => 'box', width => '64'} + ] + } + ] + }; + push @{$notebook->{item}}, $tab + } + + if ($self->{hasTags}) + { + my $tab = {type => 'tab', value => 'tagpanel', + title => 'PanelTags', + item => [ + {type => 'line', + item => [ + {type => 'value', for => 'favourite'} + ] + }, + {type => 'value', for => 'tags', expand => 'true'} + ] + }; + push @{$notebook->{item}}, $tab + } + + if (!$self->{defaultModifier}) + { + push @items, $notebook; + my $footer = {type => 'line', + item => [ + { + type => 'special', + for => 'deleteButton', + expand => 'true' + } + ] + }; + + unshift @{$footer->{item}}, { + type => 'value', + for => $self->{commonFields}->{url}, + expand => 'true' + } if $self->{commonFields}->{url}; + + push @items, $footer; + } + + $panel->{item} = \@items; + $self->{collection}->{panels}->{panel}->[0] = $panel; + $self->createDefaultReadOnlyPanel; + } + + sub createDefaultReadOnlyPanel + { + my $self = shift; + return if (! $self->{commonFields}->{title}) && (!$self->{defaultModifier}); + my $panel; + $panel->{name} = 'readonly'; + $panel->{label} = 'PanelReadOnly'; + $panel->{editable} = 'false'; + my @items; + + if (!$self->{defaultModifier}) + { + my $header = { + type => 'line', item => [ + {type => 'value', for => $self->{commonFields}->{title}, + style => 'header', row => 0, col => 0, colspan => 3, + expand => 'true'} + ] + }; + push @items, $header; + } + + my $fields = $self->getDisplayedInfo; + my $i = 0; + my $infoTable; + my $currentTable; + foreach (@{$fields}) + { + $infoTable = 0; + next if (($_->{id} eq 'lending') || ($_->{id} eq 'tagpanel')); + my $container; + my $inExpander = 0; + if (($i == 0) && (!$self->{defaultModifier})) + { + if ($self->{commonFields}->{cover}) + { + my $line = {type => 'line', + item => [ + {type => 'box', width => '150', style => 'page', + item => [ + {type => 'value', width => '140', for => $self->{commonFields}->{cover}} + ] + } + ] + }; + my $infoBox = {type => 'box', expand => 'true', item => []}; + $infoTable = {type => 'table', cols => '2', expand => 'true'}; + push @{$infoBox->{item}}, $infoTable; + push @{$line->{item}}, $infoBox; + push @items, $line; + } + $container = {type => 'table', cols => '2', expand => 'true'}; + push @items, $container; + } + else + { + my $expander = {type => 'expander', title => $_->{title}, + item => [ + {type => 'table', cols => 2, expand => 'true', item => []} + ] + }; + push @items, $expander; + $container = $expander->{item}->[0]; + $inExpander = 1; + } + my $row = 0; + my $infoRow = 0; + foreach my $item (@{$_->{items}}) + { + next if ($item->{id} eq $self->{commonFields}->{title}) + || ($item->{id} eq $self->{commonFields}->{cover}) + || ($item->{id} eq $self->{commonFields}->{url}); + my $type = $self->{fieldsInfo}->{$item->{id}}->{type}; + my $longField = ($type eq 'long text') || ($type eq 'image') + || ($type =~ /list$/o); + my $label = undef; + $label = {type => 'label', for => $item->{id}, + col => '0'}; + my $value = {type => 'value', for => $item->{id}, + col => 1, expand => 'true', flat => ($item->{flat} || 'false'), + colspan => 1}; + if ($infoTable && ($infoRow < 5) && ! $longField) + { + $label->{row} = $infoRow; + push @{$infoTable->{item}}, $label; + $value->{row} = $infoRow; + push @{$infoTable->{item}}, $value; + $infoRow++; + } + else + { + my ($colspan, $expand); + $label->{row} = $row; + $value->{row} = $row; + push @{$container->{item}}, $label; + push @{$container->{item}}, $value; + $row++; + } + } + $container->{rows} = $row; + $i++; + } + if ($self->{hasLending}) + { + my $lendingExpander = { + type => 'expander', + title => 'PanelLending', + item => [ + {type => 'table', rows => '4', cols => '2', + item => [ + {type => 'label', for => 'borrower', row => '0', col => '0'}, + {type => 'value', for => 'borrower', row => '0', col => '1', expand => 'true'}, + {type => 'label', for => 'lendDate', row => '1', col => '0'}, + {type => 'value', for => 'lendDate', row => '1', col => '1', expand => 'true'}, + {type => 'line', row => '2', col => '0', colspan => '2', height => '12', expand => 'true'}, + {type => 'line', row => '2', col => '0', colspan => '2', expand => 'true', + item => [ + {type => 'box', width => '50', style => 'page'}, + {type => 'value', for => 'borrowings', expand => 'true'}, + {type => 'box', width => '50', style => 'page'}, + ] + } + ] + } + ] + }; + push @items, $lendingExpander; + } + if ($self->{hasTags}) + { + my $tagsExpander = { + type => 'expander', + title => 'PanelTags', + item => [ + {type => 'value', for => 'tags', expand => 'true'} + ] + }; + push @items, $tagsExpander; + } + + $panel->{item} = \@items; + $self->{collection}->{panels}->{panel}->[1] = $panel; + } + + sub getGroups + { + my $self = shift; + + if (!$self->{groupsHash}) + { + $self->{groupsHash} = {}; + foreach (@{$self->{groups}}) + { + $self->{groupsHash}->{$_->{id}} = { + id => $_->{id}, + label => $_->{label}, + displayed => $self->getDisplayedText($_->{label}) + }; + } + } + return $self->{groupsHash}; + } + + sub getDefaultPanel + { + my $self = shift; + return $self->{panels}->{$self->{panelsNames}->[0]}; + } + + sub getName + { + my $self = shift; + return $self->{collection}->{name}; + } + + sub getDescription + { + my $self = shift; + my $description = $self->{collection}->{description} ? + $self->{collection}->{description} : $self->getDisplayedText('CollectionDescription'); + return $description eq 'CollectionDescription' ? $self->getName : $description; + } + + sub getDisplayedText + { + my ($self, $id) = @_; + + return $self->{fieldsInfo}->{lc $id}->{displayed} + if $id =~ /^gcsfield/i; + return $self->{lang}->{$id} if exists $self->{lang}->{$id}; + return $self->{parent}->{lang}->{$id} if exists $self->{parent}->{lang}->{$id}; + return $id; + } + + sub getDisplayedLabel + { + my ($self, $field) = @_; + + return $self->getDisplayedText($self->{fieldsInfo}->{$field}->{label}) + if exists $self->{fieldsInfo}->{$field}; + return $self->getGroups->{$field}->{displayed} + if exists $self->getGroups->{$field}; + return $self->getDisplayedText($field); + } + + sub getDisplayedValue + { + my ($self, $values, $value) = @_; + foreach (@{$self->{collection}->{options}->{'values'}->{$values}->{value}}) + { + return $self->getDisplayedText($_->{displayed}) if $_->{content} eq $value; + } + return ""; + } + + sub getDisplayedItems + { + my ($self, $number) = @_; + $number = 'X' if !defined $number; + return 'Items' if !exists $self->{lang}->{Items}; + my $items = $self->{lang}->{Items}; + if (ref($items) eq 'HASH') + { + return $items->{$number} if exists $items->{$number}; + return $items->{X}; + } + elsif (ref($items) eq 'CODE') + { + # Example of Items definition: + #Items => sub { + # my $number = shift; + # return 'Movie' if $number < 2; + # return 'Movies'; + #}, + + return $items->($number); + } + else + { + return $items; + } + } + + sub getValues + { + my ($self, $id) = @_; + my @result; + if ($id eq $self->{commonFields}->{borrower}->{name}) + { + @result = ( + {value => '', displayed => ''}, + {value => 'none', displayed => $self->getDisplayedText('PanelNobody')} + ); + my @borrowers = split m/\|/, $self->{parent}->{options}->borrowers; + foreach (@borrowers) + { + push @result, {value => $_, displayed => $_}; + } + push @result, {value => 'unknown', displayed => $self->getDisplayedText('PanelUnknown')}; + } + else + { + if ($id =~ /,/) + { + # If it contains a comma, it needs no translation (used by user-defined collections) + push @result, {displayed => $_, value => $_} + foreach (split /\s*,\s*/, $id); + } + else + { + push @result, {displayed => $self->getDisplayedText($_->{displayed}), value => $_->{content}} + foreach (@{$self->{collection}->{options}->{'values'}->{$id}->{value}}); + } + } + return \@result; + } + + sub getFieldsCopy + { + my $self = shift; + my @result; + foreach (@{$self->{fieldsNames}}) + { + my $info = $self->{fieldsInfo}->{$_}; + #next if exists $info->{linkedto}; + push @result, $_ if $info->{group} + && $info->{label}; + } + return \@result; + } + + sub getDisplayedInfo + { + my $self = shift; + # It will return a reference to an array. Each item will + # be like this : + # title => 'title of the corresponding tab' + # items => [ collection of items {id => '', label => ''} ] + my $result = []; + my %groups; + + my $i = 0; + + foreach (@{$self->{fieldsNames}}) + { + my $info = $self->{fieldsInfo}->{$_}; + next if (! $info->{label}); # || (exists $info->{linkedto}); + my $group = $info->{group}; + push @{$groups{$group}}, { + id => $_, + label => $self->getDisplayedText($info->{label}) + } if $group; + + $i++; + } + foreach (@{$self->{groups}}) + { + push @$result, { + id => $_->{id}, + title => $self->getDisplayedText($_->{label}), + items => $groups{$_->{id}} + }; + } + + return $result; + } + + sub getInitInfo + { + my $self = shift; + my $info = {}; + foreach my $field (@{$self->{fieldsNames}}) + { + $info->{$field} = $self->getDisplayedText($self->{fieldsInfo}->{$field}->{init}); + if ($self->{fieldsInfo}->{$field}->{type} eq 'formatted') + { + $info->{$field} =~ s/\[(.*?)\]//g; + $info->{$field} =~ s/%(.*?)%//g; + } + #foreach (@{$self->{fieldsNames}}); + } + return $info; + } + + # Required to use current class as a parameter of backend + sub preloadModel + { + my $self = shift; + return; + } + + # Required to use current class as a parameter of backend + sub setCurrentModel + { + my $self = shift; + return 1; + } + + # Required to use current class as a parameter of backend + sub transformPicturePath + { + my ($self, @args) = @_; + + # Force the use of absolute path + my $currentOption = $self->{parent}->{options}->useRelativePaths; + $self->{parent}->{options}->useRelativePaths(0); + + return $self->{parent}->transformPicturePath(@args); + + $self->{parent}->{options}->useRelativePaths($currentOption); + } + + sub getDefaultValuesBackend + { + my ($self) = @_; + $self->{defaultValuesBackend} = new GCBackend::GCBeXmlParser($self) + if !$self->{defaultValuesBackend}; + $self->{defaultValuesBackend}->setParameters(file => $self->{defaultValuesFile}, + version => $self->{parent}->{version}); + return $self->{defaultValuesBackend} + } + + sub getDefaultValues + { + my $self = shift; + if (! exists $self->{defaultValues}) + { + # First get the default values as defined in the model + my $info = $self->getInitInfo; + + # Then try to load user ones + if ( -r $self->{defaultValuesFile}) + { + my $backend = $self->getDefaultValuesBackend; + my $loaded = $backend->load; + # We only consider the first item + my $userValues = $loaded->{data}->[0]; + # We store that for later use + $self->{defaultValuesInformation} = $loaded->{information}; + + # Remove 'current' value for date, otherwise we'll store the current date + # as the default value + foreach ($self->{fieldsDate}) + { + delete $userValues->{$_} + if $userValues->{$_} eq 'current'; + } + + # Merge init values with the loaded ones + $info = {%{$info}, %{$userValues}}; + } + + $self->{defaultValues} = $info; + } + return $self->{defaultValues}; + } + + sub setDefaultValues + { + my ($self, $info) = @_; + + $self->{defaultValues} = $info; + my $backend = $self->getDefaultValuesBackend; + + my $result = $backend->save([$info], + $self->{defaultValuesInformation}, + undef, + 1); + if ($result->{error}) + { + return $result->{error}; + } + } + + + sub getFilterType + { + my ($self, $filter) = @_; + + if (! $self->{filterTypes}) + { + foreach (@{$self->{filters}}) + { + $self->{filterTypes}->{$_->{field}} = [$_->{comparison}, $_->{numeric}, $_->{preprocess}]; + } + } + return $self->{filterTypes}->{$filter}; + } + + sub getPluginsNames + { + my $self = shift; + + my $tmp = $GCPlugins::pluginsNameArrays{$self->getName}; + return [] if !$tmp; + return $tmp; + } + + sub getAllPlugins + { + my $self = shift; + + my $tmp = $GCPlugins::pluginsMap{$self->getName}; + return {} if !$tmp; + return $tmp; + } + + sub getPlugin + { + my ($self, $pluginName) = @_; + + return $GCPlugins::pluginsMap{$self->getName}->{$pluginName}; + } + + sub getExtracter + { + my $self = shift; + #return $self->{extracter} if exists $self->{extracter}; + return 0 if $self->isPersonal; + my $extracter; + (my $module = 'GCExtract::GCExtract'.$self->getName) =~ s/(GCExtract)GC(.)/$1\U$2\E/; + eval "use $module"; + my $extracterName = 'GCExtract::'.$self->getName.'Extracter'; + $extracter = new $extracterName(@_); + #$self->{extracter} = ($extracter->{errors} ? 0 : $extracter); + #return $self->{extracter}; + return ($extracter->{errors} ? 0 : $extracter); + } + + sub new + { + my ($proto, $parent, $file, $isPersonal) = @_; + my $class = ref($proto) || $proto; + my $self = {parent => $parent, isPersonal => $isPersonal}; + bless $self, $class; + $self->{isInline} = 0; + + # backend expects the model to be in {model} + # so we store there a reference to ourselves + $self->{model} = $self; + + $self->load($file); + GCPlugins::loadPlugins($self->getName) if !$isPersonal && !$ENV{GCS_PROFILING}; + return $self; + } + + sub newFromInline + { + my ($proto, $parent, $container) = @_; + my $class = ref($proto) || $proto; + my $self = {parent => $parent}; + bless $self, $class; + $self->{isInline} = 1; + $self->{xmlString} = $container->{inlineModel}; + $self->{defaultModifier} = $container->{defaultModifier}; + $self->{preferences} = GCOptionLoader->newFromXmlString($container->{inlinePreferences}, 0, $self->{parent}->{options}); + $self->loadFromString; + return $self; + } + + sub newEmpty + { + my ($proto, $parent, $container) = @_; + my $class = ref($proto) || $proto; + my $self = {parent => $parent, isInline => 1, isPersonal => 1}; + bless $self, $class; + $self->{xmlString} = '<collection><options/><groups/><fields lending="false" tags="false"/></collection>'; + $self->{preferences} = GCOptionLoader->newFromXmlString('',0, $self->{parent}->{options}); + if ($container) + { + $self->{defaultModifier} = $container->{defaultModifier}; + } + $self->loadFromString; + return $self; + } + + sub save + { + my $self = shift; + $self->{preferences}->save + if ! $self->{isInline}; + $self->saveUserFilters; + } +} +{ + package GCModelsChanges; + + use File::Basename; + use GCUtils 'glob'; + my %versionwithchanges; + + sub new + { + my ($proto, $parent,$model) = @_; + my $class = ref($proto) || $proto; + my $self = { + parent => $parent, + cache => {}, + lang => $parent->{lang}, + persoDirectory => $ENV{GCS_DATA_HOME}.'/GCModels/', + defaultDirectory => $ENV{GCS_LIB_DIR}.'/GCModels/', + modelsSuffix => $GCModel::modelsSuffix, + model => $model + }; + + $self->{modelsDirectories} = [$self->{persoDirectory}, $self->{defaultDirectory}], + + bless $self, $class; + $self->loadChangedVersionNumber; + return $self; + } + + sub applyChanges + { + my($self,$data,$fileversion,$gcstarversion)=@_; + my @fileVersionSplited=split /\./,$fileversion; + my @gcstarVersionSplited=split /\./,$gcstarversion; + my $modelChanges=$versionwithchanges{$self->{model}}; + sub compare + { + my ($arr1,$arr2,$i)=@_; + return 0 if ($i>$#$arr1 && $i>$#$arr2); + return -(0>$arr2->[$i])if ($i>$#$arr1); + return (0>$arr1->[$i]) if ($i>$#$arr2); + + return -1 if $arr1->[$i]<$arr2->[$i]; + return 1 if $arr1->[$i]>$arr2->[$i]; + return compare($arr1,$arr2,$i+1); + } + # Warning : we assume that versions are sorted ascend + for my $vers(keys %$modelChanges) + { + my @versSplited=split /\./,$vers; + my $isConcerned=(compare \@fileVersionSplited,\@versSplited)<0; + $isConcerned&=(compare \@versSplited,\@gcstarVersionSplited)<=0; + foreach my $item(@$data) + { + $modelChanges->{$vers}($item); + } + + } + } + + sub loadChangedVersionNumber + { + my($self)=@_; + # TODO load from files + + # Here is, for each model, a hash "versionNumber"=>sub + # example : "1.4.4"=>sub { + # $_[0]->{title}=lc $_[0]->{title}; + # }; + $versionwithchanges{GCfilms}={ + }; + # TODO sort version changes asc + } +} + +{ + package GCModelsCache; + + use File::Basename; + use GCUtils 'glob'; + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + mkdir $ENV{GCS_DATA_HOME}.'/GCModels/'; + mkdir $ENV{GCS_DATA_HOME}.'/Filters/'; + my $self = { + parent => $parent, + cache => {}, + lang => $parent->{lang}, + persoDirectory => $ENV{GCS_DATA_HOME}.'/GCModels/', + defaultDirectory => $ENV{GCS_LIB_DIR}.'/GCModels/', + modelsSuffix => $GCModel::modelsSuffix + }; + + $self->{modelsDirectories} = [$self->{persoDirectory}, $self->{defaultDirectory}], + + bless $self, $class; + return $self; + } + + sub getModel + { + my ($self, $name) = @_; + + my $suffix = $self->{modelsSuffix}; + (my $cacheId = $name) =~ s/$suffix$//; + if (!exists $self->{cache}->{$cacheId}) + { + my $dir; + foreach $dir(@{$self->{modelsDirectories}}) + { + my $file = $dir.$name; + $file .= $self->{modelsSuffix} + if $file !~ /$self->{modelsSuffix}$/; + next if ! -e $file; + $self->{cache}->{$cacheId} = GCModelLoader->new($self->{parent}, + $file, + ($dir eq $self->{persoDirectory})); + } + } + # Clean it if user added some fields + $self->{cache}->{$cacheId}->removeAddedFields + if $self->{cache}->{$cacheId}; + return $self->{cache}->{$cacheId}; + } + + sub getInfoFromFile + { + my ($self, $file) = @_; + my $result; + open DATA, $file; + + while (<DATA>) + { + if ($_ =~ /<collection description="([^"]*?)".*?name="([^"]*?)"/) + { + $result = { + description => $1, + name => $2 + }; + last; + } + } + + close DATA; + return $result; + } + + sub getAllModels + { + my $self = shift; + + my @results; + my $file; + my $dir; + foreach $dir(@{$self->{modelsDirectories}}) + { + foreach $file(glob $dir.'*'.$self->{modelsSuffix}) + { + push @results, $self->getModel(basename($file)); + } + } + return \@results; + } + + sub getPersonalModels + { + my $self = shift; + + #Results is [ {name => '', description => ''} ] + # name could be used with getModel + + my @results; + + foreach (glob $self->{persoDirectory}.'*'.$self->{modelsSuffix}) + { + my $info = $self->getInfoFromFile($_); + next if (! $info->{description}) || (! $info->{name}); + push @results, $info; + } + return \@results; + } + + sub getDefaultModels + { + my $self = shift; + + #Results is [ {name => '', description => ''} ] + # name could be used with getModel + + my @results; + $self->{langName} = $self->{parent}->{options}->lang; + foreach (glob $self->{defaultDirectory}.'*'.$self->{modelsSuffix}) + { + #my $model = $self->getModel(basename($_)); + #push @results, {name => $model->getName, + # description => $model->getDescription}; + push @results, $self->extractInfos($_); + } + @results = sort {$a->{description} cmp $b->{description}} @results; + return \@results; + } + + sub extractInfos + { + my ($self, $file) = @_; + my $result; + + open FILE, $file; + my $lang; + while (<FILE>) + { + $result->{name} = $1 + if /name="(.*?)"/; + if (m|<lang>(.*?)</lang>|) + { + $lang = $1; + last; + } + } + close FILE; + + open LANG, $ENV{GCS_LIB_DIR}.'/GCLang/'.$self->{langName}."/GCModels/$lang.pm"; + binmode(LANG, ':utf8'); + while (<LANG>) + { + if (/CollectionDescription\s*=>\s*'(.*?)',/) + { + ($result->{description} = $1) =~ s/\\'/'/g; + last; + } + + } + close LANG; + + return $result; + } +} + +use Gtk2; + +{ + package GCModelsDialog; + + use GCImport; + use base "GCModalDialog"; + + sub show + { + my $self = shift; + + $self->setModelsList; + + $self->SUPER::show(); + $self->show_all; + + my $result = 0; + $self->{model} = undef; + $self->{gotKey} = 0; + while (1) + { + my $response = $self->run; + if ($response eq 'ok') + { + $result = 1; + my $path = ($self->{modelsList}->get_cursor)[0]; + my @indices = $path ? $path->get_indices : (0); + if ($indices[0] == 0) + { + $self->{model} = GCModelLoader->newEmpty($self->{parent}); + } + else + { + if (!exists $indices[1]) + { + if ($self->{gotKey}) + { + # We expand/collapse row if called by a pressed key + if ($self->{modelsList}->row_expanded($path)) + { + $self->{modelsList}->collapse_row($path) + } + else + { + $self->{modelsList}->expand_row($path, 0) + } + $self->{gotKey} = 0; + } + $self->{modelsList}->grab_focus; + next; + } + my $hasPerso = scalar @{$self->{persoList}}; + if ((($indices[0] == 2) && !$hasPerso) + || ($indices[0] == 3)) + { + # Here we are importing + $self->{model} = 0; + $self->{importer} = $self->{importers}->[$indices[1]]; + } + else + { + my $name = ((($indices[0] == 1) && ($hasPerso)) ? + $self->{persoList}->[$indices[1]]->{name} : $self->{defaultList}->[$indices[1]]->{name}); + $self->{model} = $self->{factory}->getModel($name); + } + } + } + last; + } + $self->hide; + return $result; + } + + sub new + { + my ($proto, $parent, $factory, $init) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $parent->{lang}->{ModelsSelect}); + + bless ($self, $class); + $self->{parent} = $parent; + + my $column = Gtk2::TreeViewColumn->new_with_attributes('', Gtk2::CellRendererText->new, + 'text' => 0); + $self->{modelsModel} = new Gtk2::TreeStore('Glib::String'); + $self->{modelsList} = Gtk2::TreeView->new_with_model($self->{modelsModel}); + $self->{modelsList}->append_column($column); + $self->{modelsList}->set_headers_visible(0); + $self->{modelsList}->signal_connect ('row-activated' => sub { + $self->{gotKey} = 1; + $self->response('ok'); + }); + + my $scrollPanelList = new Gtk2::ScrolledWindow; + $scrollPanelList->set_border_width(5); + $scrollPanelList->set_policy('never', 'automatic'); + $scrollPanelList->set_shadow_type('etched-in'); + $scrollPanelList->add($self->{modelsList}); + + $self->vbox->pack_start($scrollPanelList, 1, 1, 10); + + $self->{factory} = $factory; + $self->{initVersion} = $init; + $self->set_default_size(300,350); + + return $self; + } + + sub isImporting + { + my $self = shift; + return $self->{model} == 0; + } + + sub getModel + { + my $self = shift; + return $self->{model}; + } + + sub getImporter + { + my $self = shift; + return $self->{importer}; + } + + sub setModelsList + { + my $self = shift; + $self->{modelsModel}->clear; + + my $iter = $self->{modelsModel}->append(undef); + $self->{modelsModel}->set($iter, (0 => $self->{parent}->{lang}->{ModelNewType})); + + $self->{persoList} = $self->{factory}->getPersonalModels; + if (scalar @{$self->{persoList}}) + { + my $persoIter = $self->{modelsModel}->append(undef); + $self->{modelsModel}->set($persoIter, (0 => $self->{parent}->{lang}->{ModelsPersonal})); + foreach (@{$self->{persoList}}) + { + $iter = $self->{modelsModel}->append($persoIter); + $self->{modelsModel}->set($iter, (0 => $_->{description})); + } + } + my $defaultIter = $self->{modelsModel}->append(undef); + $self->{modelsModel}->set($defaultIter, (0 => $self->{parent}->{lang}->{ModelsDefault})); + $self->{defaultList} = $self->{factory}->getDefaultModels; + foreach (@{$self->{defaultList}}) + { + $iter = $self->{modelsModel}->append($defaultIter); + $self->{modelsModel}->set($iter, (0 => $_->{description})); + } + $self->{modelsList}->expand_row($self->{modelsModel}->get_path($defaultIter), 0); + $self->{modelsList}->get_selection->select_iter($self->{modelsModel}->get_iter_first); + + $self->addImport if $self->{initVersion}; + + $self->{modelsList}->grab_focus; + } + + sub addImport + { + my $self = shift; + my $importIter = $self->{modelsModel}->append(undef); + (my $label = $self->{parent}->{lang}->{MenuImport}) =~ s/_//g; + $self->{modelsModel}->set($importIter, (0 => $label)); + $self->{importers} = []; + my $iter; + foreach my $importer(@GCImport::importersArray) + { + next if scalar @{$importer->getModels} == 0; + push @{$self->{importers}}, $importer; + $iter = $self->{modelsModel}->append($importIter); + $self->{modelsModel}->set($iter, (0 => $importer->getName)); + } + } +} + + +{ + package GCModelsSettingsDialog; + + use base 'GCModalDialog'; + + use GCGraphicComponents::GCBaseWidgets; + + sub setPersonalMode + { + my ($self, $personal) = @_; + $self->{personal} = $personal; + if ($personal) + { + $self->set_title($self->{parent}->{lang}->{ModelSettings}); + } + else + { + $self->set_title($self->{parent}->{lang}->{PanelUser}); + $self->{notebook}->set_current_page(0); + } + $self->{notebook}->set_show_tabs($personal); + $self->{collectionName}->set_sensitive($personal); + } + + sub setDescription + { + my ($self, $desc) = @_; + $self->{collectionName}->setValue($desc); + } + + sub setSensitive + { + my ($self, $value) = @_; + $self->{fieldsOptionsTable}->set_sensitive($value); + $self->{removeButton}->set_sensitive($value); + $self->{toUp}->set_sensitive($value); + $self->{toDown}->set_sensitive($value); + $self->activateOkButton($value) + if $self->{personal}; + } + + sub initFields + { + my ($self, $fields, $groups, $commonFields, $withLending, $withTags) = @_; + + # A clone was already made by caller + #$self->{fields} = Storable::dclone($fields) if $fields; + $self->{fields} = $fields; + + $self->clearForm; + $self->{params}->{group}->setValues([]); + $self->{currentField} = -1; + $self->{fieldsList}->get_model->clear; + $self->{maxId} = 0; + my $i = -1; + if ($fields) + { + if (scalar @$fields) + { + $self->setSensitive(1); + $self->checkFieldType; + } + else + { + $self->setSensitive(0); + } + $self->{idToLabel} = {}; + foreach (@{$self->{fields}}) + { + $i++; + $_->{label} = $_->{displayed} if $_->{displayed}; + $_->{group} = $groups->{$_->{group}}->{displayed} + if $groups->{$_->{group}}->{displayed}; + $self->{params}->{group}->addHistory($_->{group}); + if ($_->{value} eq $GCModel::autoId) + { + delete $self->{fields}->[$i]; + next; + } + push @{$self->{fieldsList}->{data}}, [$_->{label}]; + $_->{value} =~ /([0-9]+)$/; + $self->{maxId} = $1 if $self->{maxId} < $1; + $self->{idToLabel}->{$_->{value}} = $_->{label}; + $_->{init} = $self->convertFormat($_->{init}, 1) + if $_->{type} eq 'formatted'; + } + $self->displayField(0) if scalar(@{$self->{fields}}); + } + $self->initCommonFields($commonFields); + $self->{hasLending}->setValue($withLending); + $self->{hasTags}->setValue($withTags); + } + + sub convertFormat + { + my ($self, $format, $toUser) = @_; + my $converted = $format; + if ($toUser) + { + $converted =~ s/%(.*?)%/'%'.$self->idToLabel($1).'%'/ge; + } + else + { + $converted =~ s/%(.*?)%/'%'.$self->labelToId($1).'%'/ge; + } + # If some replacements failed, we clean the string + $converted =~ s/%%//g; + return $converted; + } + + sub idToLabel + { + my ($self, $id) = @_; + for my $field (@{$self->{fields}}) + { + return $field->{label} + if $field->{value} eq $id; + } + return ''; + } + + sub labelToId + { + my ($self, $label) = @_; + for my $field (@{$self->{fields}}) + { + return $field->{value} + if $field->{label} eq $label; + } + return ''; + } + + sub initFilters + { + my ($self, $filters) = @_; + + $self->{filters} = {}; + foreach (@{$filters}) + { + $self->{filters}->{$_->{field}} = { + comparison => $_->{comparison}, + numeric => $_->{numeric}, + quick => $_->{quick} + }; + } + + $self->displayFilter(0) if scalar(@{$self->{fields}}); + $self->checkFilterActivated; + } + + sub getFilters + { + my ($self, $fieldsInfo) = @_; + + my $type; + foreach (keys %{$self->{filters}}) + { + my $filter = $self->{filters}->{$_}; + $type = $fieldsInfo->{$_}->{type}; + $filter->{labelselect} = $self->{filter}->{comparison}->valueToDisplayed($filter->{comparison}) + if ($type eq 'number') || ($type eq 'date'); + } + + return $self->{filters}; + } + + sub getGroups + { + my $self = shift; + my $groups = []; + my %alreadyPushed = (); + foreach (@{$self->{fields}}) + { + push @$groups, {id => $_->{group}, label => $_->{group}} + if $_->{group} + && !$alreadyPushed{$_->{group}}; + $alreadyPushed{$_->{group}} = 1; + } + if (! scalar(@$groups)) + { + if ($self->{personal}) + { + push @$groups, {id => $self->{parent}->{lang}->{OptionsMain}, label => 'OptionsMain', + displayed => $self->{parent}->{lang}->{OptionsMain}}; + } + else + { + push @$groups, {id => $self->{parent}->{lang}->{PanelUser}, + label => 'PanelUser', + displayed => $self->{parent}->{lang}->{PanelUser}}; + } + } + my $defaultGroup = $groups->[0]->{id}; + foreach (@{$self->{fields}}) + { + $_->{group} = $defaultGroup if ! $_->{group}; + } + return $groups; + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + + my $response = $self->run; + $self->saveCurrent; + $self->saveCurrentFilter; + $self->hide; + if ($response eq 'ok') + { + # Restore formats + foreach my $field(@{$self->{fields}}) + { + $field->{init} = $self->convertFormat($field->{init}, 0) + if $field->{type} eq 'formatted'; + } + } + return ($response eq 'ok'); + } + + sub getName + { + # returns (collection description, safe name, full file name) + my $self = shift; + my $description = $self->{collectionName}->getValue; + my $name = GCUtils::getSafeFileName($description); + my $file = $ENV{GCS_DATA_HOME}.'/GCModels/'.$name.$GCModel::modelsSuffix; + return ($description, $name, $file); + } + + sub saveCurrent + { + my $self = shift; + return if $self->{currentField} < 0; + foreach (keys %{$self->{params}}) + { + $self->{fields}->[$self->{currentField}]->{$_} = $self->{params}->{$_}->getValue + if $self->{params}->{$_}->is_sensitive; + } + # Some adjustments + my $type = $self->{fields}->[$self->{currentField}]->{type}; + # history text is managed through 2 fields + if ($type eq 'short text') + { + $self->{fields}->[$self->{currentField}]->{type} = 'history text' + if $self->{fields}->[$self->{currentField}]->{history}; + delete $self->{fields}->[$self->{currentField}]->{history}; + } + # By default, the image will be displayed, right click to change it + $self->{fields}->[$self->{currentField}]->{default} = 'view' + if $type eq 'image'; + $self->{fields}->[$self->{currentField}]->{history} = + $self->{fields}->[$self->{currentField}]->{history} ? 'true' : 'false' + if exists $self->{fields}->[$self->{currentField}]->{history}; + $self->{fields}->[$self->{currentField}]->{flat} = + $self->{fields}->[$self->{currentField}]->{flat} ? 'true' : 'false' + if exists $self->{fields}->[$self->{currentField}]->{flat}; + $self->{params}->{group}->addHistory; + } + + sub saveCurrentFilter + { + my $self = shift; + return if $self->{currentFilter} < 0; + # As we use the same model, the index is the same in the fields array + my $field = $self->{fields}->[$self->{currentFilter}]->{value}; + if ($self->{filter}->{activated}->getValue) + { + $self->{filters}->{$field} = { + comparison => $self->{filter}->{comparison}->getValue, + numeric => $self->{filter}->{numeric}->getValueAsText, + quick => $self->{filter}->{quick}->getValueAsText + }; + } + else + { + delete $self->{filters}->{$field}; + } + } + + sub changeCurrentLabel + { + my $self = shift; + return if $self->{currentField} < 0; + $self->{fieldsList}->{data}->[$self->{currentField}]->[0] = $self->{params}->{label}->getValue; + } + + sub displayField + { + my ($self, $idx) = @_; + + $self->{currentField} = $idx; + foreach (keys %{$self->{params}}) + { + my $value = $self->{fields}->[$idx]->{$_}; + if ($_ eq 'init') + { + $value = '' if $value eq $GCModel::initDefault; + } + elsif (($_ eq 'history') || ($_ eq 'flat')) + { + $value = ($value eq 'true') ? 1 : 0; + } + $self->{params}->{$_}->setValue($value); + } + if ($self->{fields}->[$idx]->{type} eq 'history text') + { + $self->{params}->{type}->setValue('short text'); + $self->{params}->{history}->setValue(1); + } + $self->{fieldsList}->select($idx); + } + + sub displayFilter + { + my ($self, $idx) = @_; + + $self->{currentFilter} = $idx; + my $field = $self->{fields}->[$idx]->{value}; + my $hasFilter = (exists $self->{filters}->{$field}); + my $info = $self->{fields}->[$self->{currentFilter}]; + if ($hasFilter) + { + $self->{filter}->{activated}->setValue(1); + $self->{filter}->{comparison}->setValue($self->{filters}->{$field}->{comparison}); + $self->{filter}->{numeric}->setValue($self->{filters}->{$field}->{numeric} eq 'true'); + $self->{filter}->{quick}->setValue($self->{filters}->{$field}->{quick} eq 'true'); + } + else + { + $self->{filter}->{activated}->setValue(0); + $self->{filter}->{comparison}->setValue(0); + $self->{filter}->{numeric}->setValue(0); + $self->{filter}->{quick}->setValue(0); + } + $self->checkFilterSettings; + $self->{filtersList}->select($idx); + } + + sub clearForm + { + my $self = shift; + foreach (keys %{$self->{params}}) + { + $self->{params}->{$_}->setValue(''); + } + + } + + sub moveDownUp + { + my ($self, $dir) = @_; + $self->saveCurrent; + my $newField = $self->{currentField} + $dir; + return if ($newField < 0) || ($newField >= scalar @{$self->{fields}}); + ($self->{fields}->[$self->{currentField}], $self->{fields}->[$newField]) + = ($self->{fields}->[$newField], $self->{fields}->[$self->{currentField}]); + $self->{fieldsList}->{data}->[$self->{currentField}] = [$self->{fields}->[$self->{currentField}]->{label}]; + $self->{fieldsList}->{data}->[$newField] = [$self->{fields}->[$newField]->{label}]; + $self->displayField($newField); + } + + sub addField + { + my $self = shift; + my $newLabel = $self->{parent}->{lang}->{ModelNewField}; + $self->saveCurrent; + $self->{maxId}++; + push (@{$self->{fields}}, {value => $self->{fieldsPrefix}.$self->{maxId}, + label => $newLabel}); + push (@{$self->{fieldsList}->{data}}, [$newLabel]); + my $nbFields = scalar(@{$self->{fields}}); + if ($nbFields) + { + $self->setSensitive(1); + $self->checkFieldType; + } + $self->displayField($nbFields - 1); + $self->{params}->{label}->selectAll; + } + + sub removeCurrent + { + my $self = shift; + splice(@{$self->{fieldsList}->{data}}, $self->{currentField}, 1); + splice(@{$self->{fields}}, $self->{currentField}, 1); + $self->{currentField}-- if $self->{currentField} >= scalar @{$self->{fields}}; + if ($self->{currentField} < 0) + { + $self->clearForm; + $self->setSensitive(0); + } + else + { + $self->displayField($self->{currentField}); + } + } + + sub initCommonFields + { + my ($self, $init) = @_; + + my @imgValues = ({value => $GCModel::autoField, displayed => $self->{parent}->{lang}->{ModelOptionsFieldsAuto}}); + my @txtValues = ({value => $GCModel::autoField, displayed => $self->{parent}->{lang}->{ModelOptionsFieldsAuto}}); + my @fileValues = ({value => $GCModel::autoField, displayed => $self->{parent}->{lang}->{ModelOptionsFieldsAuto}}, + {value => $GCModel::noneField, displayed => $self->{parent}->{lang}->{ModelOptionsFieldsNone}}); + if ($self->{fields}) + { + foreach (@{$self->{fields}}) + { + my $value = {value => $_->{value}, displayed => $_->{label}}; + if ($_->{type} eq 'image') + { + push @imgValues, $value; + } + elsif ($_->{type} eq 'file') + { + push @fileValues, $value; + } + else + { + push @txtValues, $value; + } + } + } + + $self->{common}->{title}->setValues(\@txtValues, 0, 1); + $self->{common}->{id}->setValues(\@txtValues, 0, 1); + $self->{common}->{cover}->setValues(\@imgValues, 0, 1); + $self->{common}->{play}->setValues(\@fileValues, 0, 1); + + if ($init) + { + foreach (keys %{$self->{common}}) + { + my $value = $init->{$_}; + $value ||= (($_ eq 'play') ? $GCModel::noneField : $GCModel::autoField); + $self->{common}->{$_}->setValue($value); + } + } + } + + sub getIdField + { + my $self = shift; + my $value = $self->{common}->{id}->getValue; + $value = '' if $value eq $GCModel::autoField; + return $value; + } + + sub getCoverField + { + my $self = shift; + my $value = $self->{common}->{cover}->getValue; + # We look for the 1st picture + if ($value eq $GCModel::autoField) + { + $value = ''; + foreach (@{$self->{fields}}) + { + ($value = $_->{value}, last) if $_->{type} eq 'image'; + } + } + # TODO We should check it is a picture + return $value; + } + + sub getTitleField + { + my $self = shift; + my $value = $self->{common}->{title}->getValue; + if ($value eq $GCModel::autoField) + { + # TODO We should filter to find the 1st suitable one + $value = $self->{fields}->[0]->{value}; + } + return $value; + } + + sub getPlayField + { + my $self = shift; + my $value = $self->{common}->{play}->getValue; + $value = '' if $value eq $GCModel::noneField; + # We look for the 1st file + if ($value eq $GCModel::autoField) + { + $value = ''; + foreach (@{$self->{fields}}) + { + ($value = $_->{value}, last) if $_->{type} eq 'file'; + } + } + # TODO We should check it is a file + return $value; + } + + sub hasLending + { + my $self = shift; + + return $self->{hasLending}->getValue; + } + + sub hasTags + { + my $self = shift; + + return $self->{hasTags}->getValue; + } + + sub checkFieldType + { + my $self = shift; + + my $type = $self->{params}->{type}->getValue; + + my $sensitive = 0; + $sensitive = 1 if $type eq 'number'; + $self->{minLabel}->set_sensitive($sensitive); + $self->{params}->{min}->set_sensitive($sensitive); + $self->{maxLabel}->set_sensitive($sensitive); + $self->{params}->{max}->set_sensitive($sensitive); + $self->{stepLabel}->set_sensitive($sensitive); + $self->{params}->{step}->set_sensitive($sensitive); + $self->{displayasLabel}->set_sensitive($sensitive); + $self->{params}->{displayas}->set_sensitive($sensitive); + $sensitive = 0; + $sensitive = 1 if $type eq 'options'; + $self->{valuesListLabel}->set_sensitive($sensitive); + $self->{params}->{values}->set_sensitive($sensitive); + $self->{valuesListLegend}->set_sensitive($sensitive); + $sensitive = 0; + $sensitive = 1 if $type eq 'file'; + $self->{formatLabel}->set_sensitive($sensitive); + $self->{params}->{format}->set_sensitive($sensitive); + $sensitive = 0; + $sensitive = 1 if ($type eq 'short text') || ($type =~ /list$/); + $self->{params}->{history}->set_sensitive($sensitive); + $sensitive = 0; + $sensitive = 1 if $type =~ /list$/; + $self->{params}->{flat}->set_sensitive($sensitive); + } + + sub checkFilterActivated + { + my $self = shift; + + my $activated = $self->{filter}->{activated}->getValue; + $self->{comparisonLabel}->set_sensitive($activated); + $self->{filter}->{comparison}->set_sensitive($activated); + $self->{filter}->{numeric}->set_sensitive($activated); + $self->{filter}->{quick}->set_sensitive($activated); + $self->checkFilterSettings; + } + + sub checkFilterSettings + { + my $self = shift; + # We activate quick filter only for some fields + my $activated = 0; + my $info = $self->{fields}->[$self->{currentFilter}]; + my $hasFilter = $self->{filter}->{activated}->getValue; + my $type = $info->{type}; + $activated = 1 + if $hasFilter && ( + ($info->{history} eq 'true') + || ($type eq 'history text') + || ($type eq 'yesno') + || ($type eq 'number') + || ($type eq 'date') + || ($type eq 'options') + ); + $self->{filter}->{quick}->set_sensitive($activated); + $self->{filter}->{quick}->setValue(0) if !$activated; + $activated = 0; + $activated = 1 + if $hasFilter + && ($type ne 'date') + && ($type ne 'yesno') + && ($type ne 'number'); + $self->{filter}->{numeric}->set_sensitive($activated); + $self->{filter}->{numeric}->setValue(($info->{type} eq 'number')) if !$activated; + $activated = 1; + $activated = 0 if $type eq 'image'; + $self->{filter}->{activated}->set_sensitive($activated); + $self->{filter}->{comparison}->set_sensitive($activated); + } + + sub new + { + my ($proto, $parent, $list) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $parent->{lang}->{ModelSettings}, + ); + bless ($self, $class); + + $self->{parent} = $parent; + $self->{tooltips} = Gtk2::Tooltips->new(); + + $self->{currentField} = -1; + $self->{currentFilter} = -1; + $self->{fieldsPrefix} = 'gcsfield'; + + my $hboxCollectionName = new Gtk2::HBox(0,0); + my $collectionNameLabel = new GCLabel($parent->{lang}->{ModelName}); + $self->{collectionName} = new GCShortText; + $self->{tooltips}->set_tip($self->{collectionName}, + $self->{parent}->{lang}->{ModelTooltipName}); + $hboxCollectionName->pack_start($collectionNameLabel, 0, 0, $GCUtils::margin); + $hboxCollectionName->pack_start($self->{collectionName}, 1, 1, 0); + + $self->{notebook} = new Gtk2::Notebook; + $self->{notebook}->set_tab_pos('left'); + $self->{notebook}->set_show_border(0); + + my $vboxFields = new Gtk2::VBox(0,0); + my $vboxOptions = new Gtk2::VBox(0,0); + my $vboxFilters = new Gtk2::VBox(0,0); + + $self->{fieldsList} = new Gtk2::SimpleList( + '' => "text" + ); + $self->{fieldsList}->signal_connect (cursor_changed => sub { + my ($sl, $path, $column) = @_; + my @idx = $sl->get_selected_indices; + $self->changeCurrentLabel; + $self->saveCurrent; + $self->displayField($idx[0]); + }); + + $self->{fieldsList}->set_headers_visible(0); + my $vboxFieldsList = new Gtk2::VBox(0,0); + $self->{scrollPanelFields} = new Gtk2::ScrolledWindow; + $self->{scrollPanelFields}->set_policy ('automatic', 'automatic'); + $self->{scrollPanelFields}->set_shadow_type('etched-in'); + $self->{scrollPanelFields}->add($self->{fieldsList}); + $vboxFieldsList->pack_start($self->{scrollPanelFields},1,1,0); + my $hboxFieldsActions = new Gtk2::HBox(0,0); + my $addButton = GCButton->newFromStock('gtk-add', 0); + $addButton->signal_connect('clicked' => sub { + $self->addField; + }); + $self->{removeButton} = GCButton->newFromStock('gtk-remove', 0); + $self->{removeButton}->signal_connect('clicked' => sub { + $self->removeCurrent; + }); + $hboxFieldsActions->pack_start($addButton,1,1,$GCUtils::halfMargin); + $hboxFieldsActions->pack_start($self->{removeButton},1,1,$GCUtils::halfMargin); + $vboxFieldsList->pack_start($hboxFieldsActions,0,0,$GCUtils::halfMargin); + + my $vboxMove = new Gtk2::VBox(0,0); + $self->{toUp} =GCButton->newFromStock('gtk-go-up', 0); + $self->{toUp}->signal_connect('clicked' => sub { + $self->moveDownUp(-1); + }); + $self->{toDown} =GCButton->newFromStock('gtk-go-down', 0); + $self->{toDown}->signal_connect('clicked' => sub { + $self->moveDownUp(1); + }); + $vboxMove->pack_start($self->{toUp}, 0, 0, $GCUtils::margin); + $vboxMove->pack_start($self->{toDown}, 0, 0, $GCUtils::margin); + + $self->{fieldsOptionsTable} = new Gtk2::Table(18, 4); + $self->{fieldsOptionsTable}->set_row_spacings($GCUtils::halfMargin); + $self->{fieldsOptionsTable}->set_col_spacings($GCUtils::margin); + $self->{fieldsOptionsTable}->set_border_width($GCUtils::margin); + + # Information + ############# + my $nameLabel = GCLabel->new($parent->{lang}->{ModelFieldName}); + $self->{params}->{label} = new GCShortText; + $self->{tooltips}->set_tip($self->{params}->{label}, + $parent->{lang}->{ModelTooltipLabel}); + $self->{params}->{label}->signal_connect('focus-out-event' => sub { + $self->changeCurrentLabel; + return 0; + }); + my $typeLabel = GCLabel->new($parent->{lang}->{ModelFieldType}); + $self->{params}->{type} = new GCMenuList; + $self->{params}->{type}->setValues([ + {value => 'short text', displayed => $parent->{lang}->{ModelFieldTypeShortText}}, + {value => 'long text', displayed => $parent->{lang}->{ModelFieldTypeLongText}}, + {value => 'yesno', displayed => $parent->{lang}->{ModelFieldTypeYesNo}}, + {value => 'number', displayed => $parent->{lang}->{ModelFieldTypeNumber}}, + {value => 'date', displayed => $parent->{lang}->{ModelFieldTypeDate}}, + {value => 'options', displayed => $parent->{lang}->{ModelFieldTypeOptions}}, + {value => 'image', displayed => $parent->{lang}->{ModelFieldTypeImage}}, + {value => 'single list', displayed => $parent->{lang}->{ModelFieldTypeSingleList}}, + {value => 'file', displayed => $parent->{lang}->{ModelFieldTypeFile}}, + {value => 'formatted', displayed => $parent->{lang}->{ModelFieldTypeFormatted}}, + ]); + $self->{params}->{type}->signal_connect('changed' => sub { + $self->checkFieldType; + }); + my $groupLabel = GCLabel->new($parent->{lang}->{ModelFieldGroup}); + $self->{params}->{group} = new GCHistoryText; + $self->{tooltips}->set_tip($self->{params}->{group}, + $parent->{lang}->{ModelTooltipGroup}); + my $informationLabel = new GCHeaderLabel($parent->{lang}->{ModelFieldInformation}); + $self->{fieldsOptionsTable}->attach($informationLabel, 0, 4, 0, 1, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($nameLabel, 2, 3, 1, 2, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{label}, 3, 4, 1, 2, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($typeLabel, 2, 3, 2, 3, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{type}, 3, 4, 2, 3, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($groupLabel, 2, 3, 3, 4, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{group}, 3, 4, 3, 4, ['expand', 'fill'], 'fill', 0, 0); + + # Parameters + ############# + $self->{params}->{history} = GCCheckBox->new($parent->{lang}->{ModelFieldHasHistory}); + $self->{params}->{flat} = GCCheckBox->new($parent->{lang}->{ModelFieldFlat}); + $self->{tooltips}->set_tip($self->{params}->{history}, + $parent->{lang}->{ModelTooltipHistory}); + $self->{formatLabel} = GCLabel->new($parent->{lang}->{ModelFieldFileFormat}); + $self->{params}->{format} = new GCMenuList; + $self->{tooltips}->set_tip($self->{params}->{format}, + $parent->{lang}->{ModelTooltipFormat}); + $self->{params}->{format}->setValues([ + {value => 'file', displayed => $parent->{lang}->{ModelFieldFileFile}}, + {value => 'video', displayed => $parent->{lang}->{ModelFieldFileVideo}}, + {value => 'audio', displayed => $parent->{lang}->{ModelFieldFileAudio}}, + {value => 'image', displayed => $parent->{lang}->{ModelFieldFileImage}}, + {value => 'program', displayed => $parent->{lang}->{ModelFieldFileProgram}}, + {value => 'url', displayed => $parent->{lang}->{ModelFieldFileUrl}}, + {value => 'ebook', displayed => $parent->{lang}->{ModelFieldFileEbook}} + ]); + my $parametersLabel = new GCHeaderLabel($parent->{lang}->{ModelFieldParameters}); + $self->{fieldsOptionsTable}->attach($parametersLabel, 0, 4, 5, 6, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{history}, 2, 4, 6, 7, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{flat}, 2, 4, 7, 8, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{formatLabel}, 2, 3, 8, 9, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{format}, 3, 4, 8, 9, ['expand', 'fill'], 'fill', 0, 0); + + # Values + ############# + my $initLabel = GCLabel->new($parent->{lang}->{ModelFieldInit}); + $self->{params}->{init} = new GCShortText; + $self->{minLabel} = GCLabel->new($parent->{lang}->{ModelFieldMin}); + $self->{params}->{min} = new GCShortText; + $self->{maxLabel} = GCLabel->new($parent->{lang}->{ModelFieldMax}); + $self->{params}->{max} = new GCShortText; + $self->{stepLabel} = GCLabel->new($parent->{lang}->{ModelFieldStep}); + $self->{params}->{step} = new GCCheckedText('0-9.'); + $self->{valuesListLabel} = GCLabel->new($parent->{lang}->{ModelFieldList}); + $self->{params}->{values} = new GCShortText; + $self->{valuesListLegend} = GCLabel->new($parent->{lang}->{ModelFieldListLegend}); + $self->{displayasLabel} = GCLabel->new($parent->{lang}->{ModelFieldDisplayAs}); + $self->{params}->{displayas} = new GCMenuList; + $self->{params}->{displayas}->setValues([ + {value => 'text', displayed => $parent->{lang}->{ModelFieldDisplayAsText}}, + {value => 'graphical', displayed => $parent->{lang}->{ModelFieldDisplayAsGraphical}} + ]); + my $valuesLabel = new GCHeaderLabel($parent->{lang}->{ModelFieldValues}); + $self->{fieldsOptionsTable}->attach($valuesLabel, 0, 4, 10, 11, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($initLabel, 2, 3, 11, 12, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{init}, 3, 4, 11, 12, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{minLabel}, 2, 3, 12, 13, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{min}, 3, 4, 12, 13, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{maxLabel}, 2, 3, 13, 14, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{max}, 3, 4, 13, 14, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{stepLabel}, 2, 3, 14, 15, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{step}, 3, 4, 14, 15, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{displayasLabel}, 2, 3, 15, 16, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{displayas}, 3, 4, 15, 16, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{valuesListLabel}, 2, 3, 16, 17, 'fill', 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{params}->{values}, 3, 4, 16, 17, ['expand', 'fill'], 'fill', 0, 0); + $self->{fieldsOptionsTable}->attach($self->{valuesListLegend}, 3, 4, 17, 18, ['expand', 'fill'], 'fill', 0, 0); + + $self->{scrollFieldsPane} = new Gtk2::ScrolledWindow; + $self->{scrollFieldsPane}->set_policy ('never', 'automatic'); + $self->{scrollFieldsPane}->set_shadow_type('none'); + $self->{scrollFieldsPane}->set_border_width($GCUtils::margin); + $self->{scrollFieldsPane}->add_with_viewport($self->{fieldsOptionsTable}); + + my $hboxFields = new Gtk2::HBox(0,0); + $hboxFields->set_border_width($GCUtils::margin); + $self->{fieldsPane} = new Gtk2::HPaned; + $self->{fieldsPane}->signal_connect('size-allocate' => sub { + $self->{scrollPanelFilters}->set_size_request($self->{scrollPanelFields}->allocation->width, -1); + }); + $hboxFields->pack_start($vboxFieldsList, 1, 1, 0); + $hboxFields->pack_start($vboxMove, 0, 0, $GCUtils::halfMargin); + $self->{fieldsPane}->pack1($hboxFields,1,0); + $self->{fieldsPane}->pack2($self->{scrollFieldsPane},1,0); + $vboxFields->pack_start($self->{fieldsPane}, 1, 1, 0); + + my $optionsTable = new Gtk2::Table(9, 4); + $optionsTable->set_row_spacings($GCUtils::halfMargin); + $optionsTable->set_col_spacings($GCUtils::margin); + $optionsTable->set_border_width($GCUtils::margin); + + my $titleFieldLabel = GCLabel->new($parent->{lang}->{ModelOptionsFieldsTitle}); + $self->{common}->{title} = new GCMenuList; + my $coverFieldLabel = GCLabel->new($parent->{lang}->{ModelOptionsFieldsCover}); + $self->{common}->{cover} = new GCMenuList; + my $idFieldLabel = GCLabel->new($parent->{lang}->{ModelOptionsFieldsId}); + $self->{common}->{id} = new GCMenuList; + my $playFieldLabel = GCLabel->new($parent->{lang}->{ModelOptionsFieldsPlay}); + $self->{common}->{play} = new GCMenuList; + $self->{hasLending} = new GCCheckBox($parent->{lang}->{ModelCollectionSettingsLending}); + $self->{tooltips}->set_tip($self->{hasLending}, + $parent->{lang}->{ModelTooltipLending}); + $self->{hasTags} = new GCCheckBox($parent->{lang}->{ModelCollectionSettingsTagging}); + $self->{tooltips}->set_tip($self->{hasTags}, + $parent->{lang}->{ModelTooltipTagging}); + + my $commonFieldsLabel = new GCHeaderLabel($parent->{lang}->{ModelOptionsFields}); + $optionsTable->attach($commonFieldsLabel, 0, 4, 0, 1, 'fill', 'fill', 0, 0); + $optionsTable->attach($titleFieldLabel, 2, 3, 1, 2, 'fill', 'fill', 0, 0); + $optionsTable->attach($self->{common}->{title}, 3, 4, 1, 2, ['expand', 'fill'], 'fill', 0, 0); + $optionsTable->attach($coverFieldLabel, 2, 3, 2, 3, 'fill', 'fill', 0, 0); + $optionsTable->attach($self->{common}->{cover}, 3, 4, 2, 3, ['expand', 'fill'], 'fill', 0, 0); + $optionsTable->attach($idFieldLabel, 2, 3, 3, 4, 'fill', 'fill', 0, 0); + $optionsTable->attach($self->{common}->{id}, 3, 4, 3, 4, ['expand', 'fill'], 'fill', 0, 0); + $optionsTable->attach($playFieldLabel, 2, 3, 4, 5, 'fill', 'fill', 0, 0); + $optionsTable->attach($self->{common}->{play}, 3, 4, 4, 5, ['expand', 'fill'], 'fill', 0, 0); + my $collectionSettingsLabel = new GCHeaderLabel($parent->{lang}->{ModelCollectionSettings}); + $optionsTable->attach($collectionSettingsLabel, 0, 4, 6, 7, 'fill', 'fill', 0, 0); + $optionsTable->attach($self->{hasLending}, 2, 4, 7, 8, ['expand', 'fill'], 'fill', 0, 0); + $optionsTable->attach($self->{hasTags}, 2, 4, 8, 9, ['expand', 'fill'], 'fill', 0, 0); + + $vboxOptions->pack_start($optionsTable, 1, 1, 0); + + $self->{notebook}->signal_connect('switch-page' => sub { + my ($widget, $pointer, $number) = @_; + $self->saveCurrent; + $self->saveCurrentFilter; + $self->initCommonFields if $number == 1; + }); + + + $self->{filtersList} = new Gtk2::SimpleList('' => "text"); + $self->{filtersList}->set_model($self->{fieldsList}->get_model); + $self->{filtersList}->signal_connect (cursor_changed => sub { + my ($sl, $path, $column) = @_; + my @idx = $sl->get_selected_indices; + $self->saveCurrentFilter; + $self->displayFilter($idx[0]); + }); + + $self->{filtersList}->set_headers_visible(0); + $self->{scrollPanelFilters} = new Gtk2::ScrolledWindow; + $self->{scrollPanelFilters}->set_policy ('automatic', 'automatic'); + $self->{scrollPanelFilters}->set_shadow_type('etched-in'); + $self->{scrollPanelFilters}->add($self->{filtersList}); + + $self->{filter}->{activated} = GCCheckBox->new($parent->{lang}->{ModelFilterActivated}); + $self->{filter}->{activated}->signal_connect('toggled' => sub { + $self->checkFilterActivated; + }); + $self->{comparisonLabel} = GCLabel->new($parent->{lang}->{ModelFilterComparison}); + $self->{filter}->{comparison} = new GCComparisonSelector($parent); + $self->{filter}->{numeric} = GCCheckBox->new($parent->{lang}->{ModelFilterNumeric}); + $self->{tooltips}->set_tip($self->{filter}->{numeric}, + $parent->{lang}->{ModelTooltipNumeric}); + $self->{filter}->{quick} = GCCheckBox->new($parent->{lang}->{ModelFilterQuick}); + $self->{tooltips}->set_tip($self->{filter}->{quick}, + $parent->{lang}->{ModelTooltipQuick}); + + $self->{filtersOptionsTable} = new Gtk2::Table(4, 4); + $self->{filtersOptionsTable}->set_row_spacings($GCUtils::halfMargin); + $self->{filtersOptionsTable}->set_col_spacings($GCUtils::margin); + $self->{filtersOptionsTable}->set_border_width($GCUtils::margin); + + $self->{filtersOptionsTable}->attach($self->{filter}->{activated}, 0, 4, 0, 1, 'fill', 'fill', 0, 0); + $self->{filtersOptionsTable}->attach($self->{comparisonLabel}, 2, 3, 1, 2, 'fill', 'fill', 0, 0); + $self->{filtersOptionsTable}->attach($self->{filter}->{comparison}, 3, 4, 1, 2, 'fill', 'fill', 0, 0); + $self->{filtersOptionsTable}->attach($self->{filter}->{numeric}, 2, 4, 2, 3, 'fill', 'fill', 0, 0); + $self->{filtersOptionsTable}->attach($self->{filter}->{quick}, 2, 4, 3, 4, 'fill', 'fill', 0, 0); + + my $hboxFilters = new Gtk2::HBox(0,0); + $hboxFilters->set_border_width($GCUtils::margin); + $hboxFilters->pack_start($self->{scrollPanelFilters}, 0, 0, 0); + $hboxFilters->pack_start($self->{filtersOptionsTable}, 1, 1, $GCUtils::halfMargin); + $vboxFilters->pack_start($hboxFilters,1,1,0); + + $self->{notebook}->append_page($vboxFields, $parent->{lang}->{ModelFields}); + $self->{notebook}->append_page($vboxOptions, $parent->{lang}->{ModelOptions}); + $self->{notebook}->append_page($vboxFilters, $parent->{lang}->{ModelFilters}); + + $self->vbox->pack_start($hboxCollectionName, 0, 0, $GCUtils::margin); + $self->vbox->pack_start($self->{notebook}, 1, 1, 0); + $self->vbox->show_all; + + # Set dialog size to fit nicely onto screen + my $dialogWidth = $self->get_screen->get_width < 800 ? $self->get_screen->get_width - 50 : 750; + my $dialogHeight = $self->get_screen->get_height < 650 ? $self->get_screen->get_height - 50 : 650; + $self->set_default_size($dialogWidth, $dialogHeight); + + $self->setSensitive(0); + + return $self; + } +} + +1; |