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;

        $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;
