package GCGraphicComponents;

###################################################
#
#  Copyright 2005-2007 Tian
#
#  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
###################################################
use utf8;
use Gtk2;
#use GCBorrowings;

use strict;

sub createWidget
{
    my ($parent, $info, $comparison) = @_;
    my $widget;
    my $withComparisonLabel = 1;

    if ($info->{type} eq 'short text')
    {
        if ($comparison eq 'range')
        {
            $widget = new GCRange('text', $parent->{lang});
            $widget->setWidth(16);
            $withComparisonLabel = 0;
        }
        else
        {
            $widget = new GCShortText;
        }
    }
    elsif ($info->{type} eq 'number')
    {
        #If we want to have values that are less to the specified one,
        #we use max as default to be sure everything will be returned
        #in that case.
        my $default = $info->{min};
        $default = $info->{max}
        if $comparison =~ /^l/;
        if (exists $info->{min})
        {
            if ($comparison eq 'range')
            {
                $widget = new GCRange('number',
                                      $parent->{lang},
                                      $info->{min},
                                      $info->{max},
                                      $info->{step});
                $widget->setWidth(16);
                $withComparisonLabel = 0;
            }
            else
            {
                $widget = new GCNumeric($default,
                                        $info->{min},
                                        $info->{max},
                                        $info->{step});
            }
        }
        else
        {
            if ($comparison eq 'range')
            {
                $widget = new GCRange('numeric text', $parent->{lang});
                $widget->setWidth(16);
                $withComparisonLabel = 0;
            }
            else
            {
                $widget = new GCCheckedText('0-9.');
            }
        }
    }
    elsif ($info->{type} eq 'checked text')
    {
        $widget = new GCCheckedText($info->{format});
    }
    elsif (($info->{type} eq 'history text')
           || (($info->{type} =~ /list/)
               && ($info->{history} ne 'false')))
    {
        $widget = new GCHistoryText;
    }
    elsif ($info->{type} eq 'options')
    {
        $widget = new GCMenuList;
        $widget->setValues($parent->{model}->getValues($info->{values}), $info->{separator});
    }
    elsif ($info->{type} eq 'yesno')
    {
        $widget = new GCCheckBoxWithIgnore($parent);
        $withComparisonLabel = 0;
    }
    elsif ($info->{type} eq 'date')
    {
        $widget = new GCDate($parent->{window}, $parent->{lang}, 1,
                             $parent->{options}->dateFormat);
    }
    else
    {
        $widget = new GCShortText;
    }
    
    return ($widget, $withComparisonLabel);
}

{
    package GCGraphicComponent;
    
    sub expand
    {
    }
    
    sub lock
    {
    }

    sub getMainParent
    {
        my $self = shift;
        return if ! $self->{parent};
        my $tmpWidget = $self;
        $tmpWidget = $tmpWidget->{parent} while $tmpWidget && (! $tmpWidget->isa('GCFrame'));
        $self->{mainParent} = $tmpWidget;
    }
    
    sub acceptMarkup
    {
        my $self = shift;
        return 0;
    }

    sub cleanMarkup
    {
        my ($self, $text) = @_;
        $text =~ s|<br ?/?>|\n|g;
        return GCUtils::encodeEntities($text);
    }

    sub selectAll
    {
    }

    sub getTagFromSpan
    {
        my ($self, $desc) = @_;
        my @result = (); #('background' => $self->{background});
        my @keyvalues = split / /, $desc;
        foreach (@keyvalues)
        {
            /([^=]*)=(.*)/;
            my $key = $1;
            my $value = $2;
            $value =~ s/('|")//g;
            #"'
            next if $key =~ /=/;
            push @result, ($key, $value);
        }
        return @result;
    }
    
    sub valueToDisplayed
    {
        # 1st parameter is self
        shift;
        # Here we don't change the value;
        return shift;
    }
    
    sub hasChanged
    {
        my $self = shift;

        return $self->{hasChanged};
    }
    
    sub resetChanged
    {
        my $self = shift;
        $self->{hasChanged} = 0;
    }

    sub activateStateTracking
    {
        # We do nothing by default
        # Other widget should connect a signal handler or something similar
        # when the content has been changed
    }
    
    sub getLinkedValue
    {
    }
    sub setLinkedValue
    {
    }

    sub setLinkedComponent
    {
        my ($self, $linked) = @_;
        $self->{linkedComponent} = $linked;
    }
}

{
    package GCPseudoHistoryComponent;
    
    sub initHistory
    {
        my ($self, $listType) = @_;
        $self->{history} = {};

        # listType contains the type of the original field:
        #   0: No list
        #   >0: Multiple list (number of columns)
        $self->{listType} = $listType;
        
        $self->{history}->{0} = {'' => 1};
        for(my $i = 1; $i < $listType; $i++)
        {
            $self->{history}->{$i} = {'' => 1};
        }
        $self->{listType} = $listType;
    }

    sub addHistory
    {
        my $self = shift;

        my $value = shift;
        my $i;
        if (ref($value) eq 'ARRAY')
        {
            foreach (@$value)
            {
                $i = 0;
                foreach my $item(@$_)
                {
                    $self->{history}->{$i}->{$item} = 1;
                    $i++;
                }
            }
        }
        else
        {
            # The separator used was ; instead of ,
            $value =~ s/;/,/g if $value !~ /,/;
            my @values = split m/,/, $value;
            foreach (@values)
            {
                my @items = split m/;/;
                $i = 0;
                foreach my $item(@items)
                {
                   $self->{history}->{$i}->{$item} = 1;
                   $i++;
                }
            }
        }
    }

    sub setDropDown
    {
    }

    sub getValues
    {
        my $self = shift;
        my @array;
        
        
        if ($self->{listType} < 1)
        {
            @array = sort keys %{$self->{history}->{0}};
        }
        else
        {
            foreach (sort keys %{$self->{history}})
            {
                my @tmpArray = sort keys %{$self->{history}->{$_}};
                push @array, \@tmpArray;
            }
        }
        return \@array;
    }

    sub setValues
    {
        my ($self, $values) = @_;
        if ($self->{listType} == 0)
        {
            $self->{history} = {};
            $self->{history}->{0}->{$_} = 1 foreach (@$values);
        }
        else
        {
            $self->{history} = {};
            for (my $i = 0; $i < $self->{listType}; $i++)
            {
                $self->{history}->{$i}->{$_} = 1 foreach (@{$values->[$i]});
            }
        }
    }
}

{
    package GCLinkedComponent;
    @GCLinkedComponent::ISA = ('GCGraphicComponent');

    sub new
    {
        my ($proto, $linked) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = {linked => $linked};
        bless ($self, $class);
        $linked->setLinkedComponent($self);
        return $self;
    }

    sub setValue
    {
        my $self = shift;
        $self->{hasChanged} = 1;
        return $self->{linked}->setLinkedValue(@_);
    }

    sub getValue
    {
        my $self = shift;
        return $self->{linked}->getLinkedValue(@_);
    }

    sub resetChanged
    {
        my $self = shift;
        $self->{hasChanged} = 0;
        return $self->{linked}->resetChanged(@_);
    }
    
    sub hide
    {
        my $self = shift;
        $self->{linked}->setLinkedActivated(0);
    }

    sub show
    {
        my $self = shift;
        $self->{linked}->setLinkedActivated(1);
    }
}

{
    package GCShortText;
    
    @GCShortText::ISA = ('Gtk2::Entry', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;

        bless ($self, $class);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        return if $self->{readOnly};
        $self->signal_connect('changed' => sub {;
            $self->{hasChanged} = 1;
        });
    }
    
    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub selectAll
    {
        my $self = shift;
        $self->select_region(0, length($self->getValue));
        $self->grab_focus;
    }
    
    sub getValue
    {
        my $self = shift;
        return $self->get_text;
    }
    
    sub setValue
    {
        my ($self, $value) = @_;
        $self->set_text($value);
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }
    
    sub setWidth
    {
        my ($self, $value) = @_;
        $self->set_width_chars($value);
    }
    
    sub setReadOnly
    {
        my $self = shift;
        $self->set_editable(0);
        $self->{readOnly} = 1;
    }

    sub lock
    {
        my ($self, $locked) = @_;
        return if $self->{readOnly};
        #$self->can_focus(!$locked);
        $self->set_editable(!$locked);
    }
}

our $hasSpellChecker;
BEGIN {
    eval 'use Gtk2::Spell';
    if ($@)
    {
        $hasSpellChecker = 0;
    }
    else
    {
        $hasSpellChecker = 1;
    }
}
{
    package GCLongText;
    
    @GCLongText::ISA = ('Gtk2::ScrolledWindow', 'GCGraphicComponent');

    sub new
    {
        my ($proto) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
    
        $self->{text} = new Gtk2::TextView;
        $self->{text}->set_editable(1);
        $self->{text}->set_wrap_mode('word');
        $self->set_border_width(0);
        $self->set_shadow_type('in');
        $self->set_policy('automatic', 'automatic');
        #$self->set_size_request(-1,80);
        
        $self->add($self->{text});
        $self->{spellChecker} = 0;

        bless ($self, $class);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->{text}->get_buffer->signal_connect('changed' => sub {;
            $self->{hasChanged} = 1;
        });
    }
    
    sub setSpellChecking
    {
        my ($self, $activate, $lang) = @_;
        return if ! $GCGraphicComponents::hasSpellChecker;
        if ($activate)
        {
            $lang ||= $ENV{LANG};
            if ($self->{spellChecker})
            {
                return if $lang eq $self->{lang};
                $self->setSpellChecking(0)
            }
            eval {
                $self->{spellChecker} = Gtk2::Spell->new_attach($self->{text});
                $self->{spellChecker}->set_language($lang);
                $self->{lang} = $lang;
            };
            if ($@)
            {
                print "$@\n";
                $self->setSpellChecking(0);
            }
        }
        else
        {
            $self->{spellChecker}->detach if $self->{spellChecker};
            $self->{spellChecker} = 0;
        }
    }
    
    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub getValue
    {
        my $self = shift;
        my $buffer = $self->{text}->get_buffer;
        my $text = $buffer->get_text($buffer->get_start_iter,
                   $buffer->get_end_iter, 1);
        #$text =~s/\n/<br\/>/g;
        return $text;
    }
    
    sub setValue
    {
        my ($self, $text) = @_;
        #$text =~s/<br\/>/\n/g;
        $text = '' if !defined $text;
        $self->{text}->get_buffer->set_text($text);
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->{text}->can_focus(!$locked);
    }
    
    sub setHeight
    {
        my ($self, $height) = @_;
        
        # TODO Change height
    }
}

{
    package GCHistoryText;
    
    @GCHistoryText::ISA = ('Gtk2::Combo', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
        $self->{history} = {'' => 1};
        bless ($self, $class);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->entry->signal_connect('changed' => sub {;
            $self->{hasChanged} = 1;
        });
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub selectAll
    {
        my $self = shift;
        $self->entry->select_region(0, -1);
        $self->entry->grab_focus;
    }

    sub getValue
    {
        my $self = shift;
        my @children = $self->get_children;
        return $children[0]->get_text if $children[0];
    }
    
    sub setValue
    {
        my ($self, $text) = @_;
        my @children = $self->get_children;
        $children[0]->set_text($text);
    }

    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }
    
    sub setWidth
    {
        my ($self, $value) = @_;
        ($self->get_children)[0]->set_width_chars($value);
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    
        ($self->get_children)[0]->can_focus(!$locked);
        ($self->get_children)[1]->set_sensitive(!$locked);
    }
    
    sub addHistory
    {
        my $self = shift;
        my $value = (scalar @_) ? shift : $self->getValue;
        my $noUpdate = shift;
        #$value = ucfirst $value;
        $value =~ s/^\s*//;
        if (!exists $self->{history}->{$value})
        {
            $self->{history}->{$value} = 1;
            if (!$noUpdate)
            {
                $self->setDropDown(sort keys %{$self->{history}});
            }
        }
    }
    
    sub setDropDown
    {
        my $self = shift;
        my @values = (scalar @_) ? @_ : sort keys %{$self->{history}};
        my $previousValue = $self->getValue;
        $self->set_popdown_strings(@values);
        $self->setValue($previousValue);
    }
    
    sub getValues
    {
        my $self = shift;
        my @array = sort keys %{$self->{history}};
        return \@array;
    }

    sub setValues
    {
        my ($self, $values) = @_;
        $self->{history} = {};
        $self->setDropDown(@$values);
#        my $previousValue = $self->getValue;
#        $self->set_popdown_strings(@$values);
#        $self->setValue($previousValue);
        $self->{history}->{$_} = 1 foreach (@$values);
    }

    sub setActivate
    {
        my $value
    }

    sub popup
    {
        my $self = shift;
        ($self->get_children)[1]->grab_focus;
        ($self->get_children)[1]->signal_emit('activate');
    }
}

{
    package GCNumeric;
    
    @GCNumeric::ISA = ('Gtk2::SpinButton', 'GCRatingWidget', 'GCGraphicComponent');
    
    use GCWidgets;
    use GCLang;

    sub new
    {
        my ($proto, $default, $min, $max, $step, $format) = @_;
        my $class = ref($proto) || $proto;

        my $self;

        if (($format eq 'text') || (!$format))
        {
            # Standard numeric field
            $step = 1 if !$step;
            my $pageStep = 10 * $step;

            my $decimals = 0;
            $decimals = length($1) if $step =~ /\.(.*)$/;

            my $accel = 0;
            my $values = ($max - $min) / $step;
            $accel = 0.2 if $values > 100;
            $accel = 0.5 if $values > 500;
            $accel = 1.0 if $values > 2000;
            $default = 0 if $default eq '';
            my $adj = Gtk2::Adjustment->new($default, $min, $max, $step, $pageStep, 0) ;
            $self = $class->SUPER::new($adj, $accel, $decimals);
            $self->{default} = $default;
            $self->{step} = $step;
            $self->{pageStep} = $pageStep;
            $self->{accel} = $accel;
            $self->{format} = 'text';
            $self->set_numeric(1);
        }
        elsif ($format eq 'graphical')
        {
            # Graphical rating widget
            $default = 0 if $default eq '';
            $max = 10 if $max eq '';

            $self = GCRatingWidget->new (maxStars=>$max, rating=>$default, direction=>GCLang::languageDirection($ENV{LANG}));
            $self->{format} = 'graphical';
        }
        bless ($self, $class);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->signal_connect('changed' => sub {;
            $self->{hasChanged} = 1;
        });
    }

    sub isEmpty
    {
        my $self = shift;
        
        if ($self->{format} eq 'text')
        {
            return $self->get_text eq '';
        }
        elsif ($self->{format} eq 'graphical')
        {
            return $self->get('rating') eq 0;
        }
    }

    sub selectAll
    {
        my $self = shift;
        if ($self->{format} eq 'text')
        {
            $self->select_region(0, length($self->getValue));
        }
    }
    
    sub getValue
    {
        my $self = shift;
        my $value;

        if ($self->{format} eq 'text')
        {
            $value = $self->get_text;
            $value =~ s/,/./;
        }
        elsif ($self->{format} eq 'graphical')
        {
            $value = $self->get('rating');
        }

        return $value;
    }

    sub setValue
    {
        my ($self, $text) = @_;

        if ($self->{format} eq 'text')
        {
            $text = $self->{default} if $text eq '';
            $self->set_value($text);
        }
        elsif ($self->{format} eq 'graphical')
        {
            $text = $self->{default} if $text eq '';
            $self->set_rating($text);
        }
    }
    
    sub clear
    {
        my $self = shift;

        if ($self->{format} eq 'text')
        {
            $self->set_value($self->{default});
        }
        elsif ($self->{format} eq 'graphical')
        {
            $self->set_rating($self->{default});
        }
    }
    
    sub setWidth
    {
        my ($self, $value) = @_;
        $self->set_width_chars($value);
    }
    
    sub lock
    {
        my ($self, $locked) = @_;

        if ($self->{format} eq 'text')
        {
            $self->can_focus(!$locked);
            my $step = ($locked ? 0 : $self->{step});
            $self->set_increments($step, $self->{pageStep});
        }
        elsif ($self->{format} eq 'graphical')
        {
            $self->set(sensitive=>!$locked);
        }
    }
}

{
    package GCCheckedText;
    @GCCheckedText::ISA = ('GCShortText');
    
    sub new
    {
        my ($proto, $format) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
        bless ($self, $class);
        my $forbidden = qr/[^$format]/;
        $self->signal_connect('insert-text' => sub {
                # Remove forbidden characters
                $_[1] =~ s/$forbidden//g;
                () # this callback must return either 2 or 0 items.
        });

        return $self;
    }
}

{
    package GCRange;
    
    @GCRange::ISA = ('Gtk2::HBox', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $type, $lang, $min, $max, $step) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
        bless ($self, $class);

        if ($type eq 'text')
        {
            $self->{from} = new GCShortText;
            $self->{to} = new GCShortText;
        }
        elsif ($type eq 'numeric text')
        {
            $self->{from} = new GCCheckedText('0-9.');
            $self->{to} = new GCCheckedText('0-9.');
        }
        else
        {
            $self->{from} = new GCNumeric($min, $min, $max, $step);
            $self->{to} = new GCNumeric($max, $min, $max, $step);
        }
        $self->pack_start(Gtk2::Label->new($lang->{PanelFrom}), 0, 0, 12);
        $self->pack_start($self->{from}, 1, 1, 0);
        $self->pack_start(Gtk2::Label->new($lang->{PanelTo}), 0, 0, 12);
        $self->pack_start($self->{to}, 1, 1, 0);
        
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->{from}->activateStateTracking;
        $self->{to}->activateStateTracking;
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub getValue
    {
        my $self = shift;
        return $self->{from}->getValue.';'.$self->{to}->getValue;
    }
    
    sub setValue
    {
        my ($self, $value) = @_;
        my @values = split m/;/, $value;
        $self->{from}->setValue($values[0]);
        $self->{to}->setValue($values[0]);
    }
    
    sub setWidth
    {
        my ($self, $value) = @_;
        $self->{from}->setWidth($value / 2);
        $self->{to}->setWidth($value / 2);
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->{from}->lock(!$locked);
        $self->{to}->lock(!$locked);
    }
    
    sub signal_connect
    {
        my $self = shift;
        $self->{from}->signal_connect(@_);
        $self->{to}->signal_connect(@_);
    }
    
    sub AUTOLOAD
    {
        my $self = shift;
        my $name = our $AUTOLOAD;
        return if $name =~ /::DESTROY$/;
        $name =~ s/.*?::(.*)/$1/;
        $self->{from}->$name(@_);
        $self->{to}->$name(@_);
    }
}

{
    package GCDate;
    
    @GCDate::ISA = ('Gtk2::HBox', 'GCGraphicComponent');
    
    sub selectValue
    {
        my $self = shift;
        $self->{dialog} = new GCDateSelectionDialog($self->{mainParent})
            if ! $self->{dialog};
        $self->{dialog}->date($self->getValue);
        if ($self->{dialog}->show)
        {
            $self->setValue($self->{dialog}->date);
        }
        $self->{parent}->showMe;
    }

    sub new
    {
        my ($proto, $parent, $lang, $reverseDate, $format) = @_;
        $format ||= '%d/%m/%Y';
        my $class = ref($proto) || $proto;

        my $self = $class->SUPER::new;
        bless ($self, $class);

        $self->{parent} = $parent;
        $self->getMainParent;
        $self->{reverseDate} = $reverseDate;
        $self->{entry} = Gtk2::Entry->new; #_with_max_length(10);
        $self->{entry}->set_width_chars(12);
        $self->{button} = new Gtk2::Button($lang->{PanelDateSelect});
        $self->{button}->signal_connect('clicked' => sub {
            $self->selectValue;
        });
        $self->pack_start($self->{entry}, 1, 1, 0);
        $self->pack_start($self->{button}, 0, 0, 0);
        
        $self->{format} = $format;
        #$self->{format} = '%d %B %Y';
        return $self;
    }

    sub setFormat
    {
        my ($self, $format) = @_;
        my $current = $self->getValue;
        $self->{format} = $format;
        $self->setValue($current);
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->{entry}->signal_connect('changed' => sub {;
            $self->{hasChanged} = 1;
        });
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub getCurrentDate
    {
        my $self = shift;
        
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
        return sprintf('%02d/%02d/%4d', $mday, $mon+1, 1900+$year);
    }
    
    sub getValue
    {
        my $self = shift;
        my $value = $self->{entry}->get_text;
        $value = GCUtils::strToTime($value, $self->{format})
            if $self->{format} && $value;
        return GCPreProcess::reverseDate($value) if $self->{reverseDate};
        return $value;
    }

    sub setValue
    {
        my ($self, $text) = @_;
        $text = GCPreProcess::restoreDate($text) if $self->{reverseDate};
        if ($text eq 'current')
        {
            $text = $self->getCurrentDate;
            $self->{hasChanged} = 1;
        }
        $text = GCUtils::timeToStr($text, $self->{format}) if $self->{format};
        $self->{entry}->set_text($text);
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }

    sub setWidth
    {
        my ($self, $value) = @_;
        $self->set_width_chars($value);
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->{entry}->can_focus(!$locked);
        $self->{button}->set_sensitive(!$locked);
    }
}

{
    package GCFile;
    
    @GCFile::ISA = ('Gtk2::HBox', 'GCGraphicComponent', 'GCPseudoHistoryComponent');

    use File::Basename;

    sub selectValue
    {
		my $self = shift;

        my $dialog = $self->getDialog;
		$dialog->set_filename($self->getValue);
		my $response = $dialog->run;
		if ($response eq 'ok')
		{
	        $self->setValue($dialog->get_filename);
        }
        $dialog->hide;
        $self->{parent}->showMe;
    }
    
    sub setPatternFilter
    {
        my ($self, $patterns) = @_;
        
        $self->getDialog->set_pattern_filter($patterns)
            if $patterns;
    }
    
    sub getDialog
    {
        my $self = shift;
    
        if (! $self->{dialog})
        {
            $self->{dialog} = GCFileChooserDialog->new($self->{title},
                                                       $self->{mainParent},
                                                       $self->{type},
                                                       $self->{withFilter});
        }
        return $self->{dialog};
    }
    
    sub setType
    {
        my ($self, $type, $withFilter) = @_;
        
        if (($type ne $self->{type}) || ($withFilter != $self->{withFilter}))
        {
            $self->{dialog}->destroy
                if $self->{dialog};
            $self->{dialog} = undef;
            $self->{type} = $type;
            $self->{withFilter} = $withFilter;
        }
    }

    sub setTitle
    {
        my ($self, $title) = @_;
        $self->{title} = $title;
        if ($self->{dialog})
        {
            $self->{dialog}->setTitle($title);
        }
    }

    sub new
    {
        my ($proto, $parent, $title, $type, $withFilter, $defaultValue) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
        bless ($self, $class);
        $self->{parent} = $parent;
        $self->getMainParent;
 
        $title ||= $self->{parent}->{lang}->{PanelSelectFileTitle};
        $self->{title} = $title;
        $type ||= 'open';
        $self->{type} = $type;
        $withFilter = 0 if !$withFilter;
        $self->{withFilter} = $withFilter;

        $self->{entry} = Gtk2::Entry->new;
        $self->{button} = GCButton->newFromStock('gtk-open');
        $self->{button}->signal_connect('clicked' => sub {
            $self->selectValue;
        });
        
        $self->pack_start($self->{entry}, 1, 1, 0);

        if ($defaultValue)
        {
            $self->{defaultButton} = GCButton->newFromStock('gtk-undo', 0, $parent->{lang}->{PanelRestoreDefault});
            $parent->{tooltips}->set_tip($self->{defaultButton},
                $parent->{lang}->{PanelRestoreDefault}.$parent->{lang}->{Separator}.$defaultValue)
                    if $parent->{tooltips};
            $self->pack_start($self->{defaultButton}, 0, 0, 0);
            $self->{defaultButton}->signal_connect('clicked' => sub {
                $self->setValue($defaultValue);
            });
        }
        
        $self->pack_start($self->{button}, 0, 0, 0);

        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->{entry}->signal_connect('changed' => sub {;
            $self->{hasChanged} = 1;
        });
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub getValue
    {
        my $self = shift;
        return $self->{entry}->get_text;
    }

    sub setValue
    {
        my ($self, $text) = @_;
        $self->{entry}->set_text($text);
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }

    sub setWidth
    {
        my ($self, $value) = @_;
        $self->{entry}->set_width_chars($value);
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->{entry}->can_focus(!$locked);
        $self->{button}->set_sensitive(!$locked);
    }
}

{
    package GCUrl;
    
    @GCUrl::ISA = ('GCFile');

    sub selectValue
    {
		my $self = shift;
		my $url = $self->getValue;
		return if !$url;
		$self->{opener}->launch($url, 'url');
    }

    sub new
    {
        my ($proto, $opener) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new(undef, '', 'url');
        $self->{opener} = $opener;
        bless ($self, $class);
        return $self;
    }
}

{
    package GCButton;
    
    @GCButton::ISA = ('Gtk2::Button', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $label) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new($label);

        bless ($self, $class);
        return $self;
    }
    
    sub newFromStock
    {
        my ($proto, $stock, $nolabel, $label) = @_;
        my $class = ref($proto) || $proto;

        $nolabel = 0 if ($^O =~ /win32/i);

        my $self = $class->SUPER::new_from_stock($stock);
        
        if ($nolabel)
        {
            my $tmpWidget = $self;
            $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox');
            ($tmpWidget->get_children)[1]->destroy;
        }
        elsif ($label)
        {
            my $tmpWidget = $self;
            $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox');
            ($tmpWidget->get_children)[1]->set_label($label);        
        }

        bless ($self, $class);
        return $self;
    }
    
    sub isEmpty
    {
        my $self = shift;
        
        return 0;
    }
    
    sub getValue
    {
        my $self = shift;
    }
    
    sub setValue
    {
        my ($self, $value) = @_;
        $self->{hasChanged} = 1;
        return $value;
    }

    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->set_sensitive(!$locked);
    }
    
    sub setWidth
    {
        my ($self, $value) = @_;
        $self->set_size_request($value, -1);
    }
}

{
    package GCUrlButton;
    
    @GCUrlButton::ISA = ('GCButton', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $label, $opener) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new($label);
        $self->{defaultLabel} = $label;
        $self->signal_connect('clicked' => sub {
            $opener->launch($self->{url}, 'url');
        });

        bless ($self, $class);
        return $self;
    }
        
    sub getValue
    {
        my $self = shift;

        return $self->{value};
    }
    
    sub setValue
    {
        my ($self, $value) = @_;

        $self->{hasChanged} = 1;
        if ($value =~ /^(.*?)##(.*)$/)
        {
            $self->{url} = $1;
            $self->setLabel($2);
        }
        else
        {
            $self->{url} = $value;
            $self->setLabel($self->{defaultLabel});
        }
        $self->{value} = $value;
        $self->lock(!$self->{url});
    }

    sub setLabel
    {
        my ($self, $label) = @_;
        $self->set_label($label);
    }
}

{
    package GCCheckBox;
    
    @GCCheckBox::ISA = ('Gtk2::CheckButton', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $label) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new($label);

        bless ($self, $class);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;

        $self->signal_connect('toggled' => sub {
            $self->{hasChanged} = 1;
        });
    }

    sub isEmpty
    {
        my $self = shift;
        
        return 0;
    }
    
    sub getValue
    {
        my $self = shift;
        return 1 if ($self->get_active);
        return 0;
    }
    
    sub getValueAsText
    {
        my $self = shift;
        return 'true' if ($self->get_active);
        return 'false';
    }
    
    sub setValue
    {
        my ($self, $value) = @_;
        $self->set_active($value);
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue(0);
    }

    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->set_sensitive(!$locked);
    }
}

{
    package GCCheckBoxWithIgnore;
    
    @GCCheckBoxWithIgnore::ISA = ('Gtk2::HBox', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new(0,0);
        bless ($self, $class);
        
        $self->{check}->[0] = new Gtk2::RadioButton(undef,$parent->{lang}->{CheckUndef});
        $self->{group} = $self->{check}->[0]->get_group;
        $self->{check}->[1] = new Gtk2::RadioButton($self->{group},$parent->{lang}->{CheckNo});
        $self->{check}->[2] = new Gtk2::RadioButton($self->{group},$parent->{lang}->{CheckYes});
        
        $self->pack_start($self->{check}->[0], 0, 0, 10);
        $self->pack_start($self->{check}->[1], 0, 0, 10);
        $self->pack_start($self->{check}->[2], 0, 0, 10);
        
        return $self;
    }
    
    sub activateStateTracking
    {
        my $self = shift;

        foreach (@{$self->{check}})
        {
            $_->signal_connect('toggled' => sub {
                $self->{hasChanged} = 1;
            });
        }
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->{check}->[0]->get_active;
    }
    
    sub getValue
    {
        my $self = shift;
        my $i = 0;
        foreach (@{$self->{check}})
        {
            last if $self->{check}->[$i]->get_active;
            $i++;
        }
        $i--;
        return $i;
    }
    
    sub setValue
    {
        my ($self, $value) = @_;
        $self->{check}->[$value + 1]->set_active(1);
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue(-1);
    }

    sub lock
    {
        my ($self, $locked) = @_;

        $self->set_sensitive(!$locked);
    }
}

{
    package GCMultipleList;
    
    @GCMultipleList::ISA = ('Gtk2::HBox', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $parent, $number, $labels, $withHistory, $readonly, $useFiles) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new(0,0);

        $self->{number} = $number;
        $self->{readonly} = $readonly;
        $self->{withHistory} = $withHistory;

		my $hboxActions = new Gtk2::HBox(0,0);

        my @histories;
        my @listColumns;
        my $i;
        for $i (0..($number - 1))
        {
            push @histories, {'' => 1};
            push @listColumns, ($labels->[$i] => 'text');
            next if $readonly;
            if ($useFiles)
            {
                $self->{entries}->[$i] = GCFile->new($parent);
                $self->{entries}->[$i]->{entry}->signal_connect('activate' => sub {
                    $self->addValues;
                });
            }
            else
            {
                if ($withHistory)
                {
                    $self->{entries}->[$i] = GCHistoryText->new;
                    $self->{entries}->[$i]->entry->signal_connect('activate' => sub {
                        my $widget = $self->{entries}->[$i];
                        if ($widget->getValue)
                        {
                            $self->addValues;
                            # FIXME. It seems this does nothing:
                            $self->{entries}->[0]->grab_focus;
                        }
                        $widget->entry->signal_stop_emission_by_name('activate');
                    });
                }
                else
                {
                    $self->{entries}->[$i] = GCShortText->new;
                    $self->{entries}->[$i]->signal_connect('activate' => sub {
                        $self->addValues;
                        $self->{entries}->[0]->grab_focus;
                    });
                }
            }
            $self->{entries}->[$i]->setWidth(12);
            $hboxActions->pack_start($self->{entries}->[$i], 1, 1, 6);
        }

        $self->{histories} = [\@histories];

        $self->{box} = new Gtk2::VBox(0,0);
        $self->{list} = new Gtk2::SimpleList(@listColumns);
        for $i (0..($number - 1))
        {
            $self->{list}->set_column_editable($i, 1);
        }
        $self->{list}->unset_rows_drag_source;
        $self->{list}->unset_rows_drag_dest;
        $self->{list}->set_reorderable(1);
        #($self->{list}->get_column(0)->get_cell_renderers)[0]->set('wrap-mode' => 'word');

        for $i (0..($number - 1))
        {
            $self->{list}->get_column($i)->set_resizable(1);
        }
        my $scroll = new Gtk2::ScrolledWindow;
        $scroll->set_policy ('automatic', 'automatic');
        $scroll->set_shadow_type('etched-in');
        $scroll->set_size_request(-1, 120);
        $scroll->add($self->{list});
        $self->{box}->pack_start($scroll, 1, 1, 2);
        if (!$readonly)
        {
            $self->{addButton} = GCButton->newFromStock('gtk-add', 1);
            $self->{addButton}->signal_connect('clicked' => sub {
                $self->addValues;
            });
            $self->{removeButton} = GCButton->newFromStock('gtk-remove', 1);
            $hboxActions->pack_start($self->{addButton}, 0, 0, 6);
            $hboxActions->pack_start($self->{removeButton}, 0, 0, 6);
        }
        else
        {
            $self->{removeButton} = GCButton->newFromStock('gtk-remove', 0);
            $self->{clearButton} = GCButton->newFromStock('gtk-clear', 0);
            $self->{clearButton}->signal_connect('clicked' => sub {
                $self->clear;
            });            
            $hboxActions->pack_start($self->{removeButton}, 1, 0, 6);
            $hboxActions->pack_start($self->{clearButton}, 1, 0, 6);
        }
        $self->{box}->pack_start($hboxActions, 0, 0, 6)
            if $readonly < 2;

        $self->{removeButton}->signal_connect('clicked' => sub {
            my @idx = $self->{list}->get_selected_indices;
            my $selected = $idx[0];
            splice @{$self->{list}->{data}}, $selected, 1;
            $selected-- if ($selected >= scalar(@{$self->{list}->{data}}));
            $selected = 0 if $selected < 0 ;
            $self->{list}->select($selected);
        });
        
        $self->{list}->signal_connect('key-press-event' => sub {
            my ($widget, $event) = @_;
            my $key = Gtk2::Gdk->keyval_name($event->keyval);
            if ((!$self->{readonly}) && ($key eq 'Delete'))
            {
                $self->{removeButton}->activate;
                return 1;
            }
            # Let key be managed by Gtk2
            return 0;
        });
        

        $self->pack_start($self->{box},1,$self->{readonly},0);

        bless ($self, $class);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->{list}->get_model->signal_connect('row-inserted' => sub {
            $self->{hasChanged} = 1;
        });
        $self->{list}->get_model->signal_connect('row-deleted' => sub {
            $self->{hasChanged} = 1;
        });
        $self->{list}->get_model->signal_connect('row-changed' => sub {
            $self->{hasChanged} = 1;
        });
    }

    sub expand
    {
        my $self = shift;
        $self->set_child_packing($self->{box},1,1,0,'start');
    }

    sub addValues
    {
        my ($self, @values) = @_;
        if (!$self->{readonly})
        {
            for my $i (0..($self->{number} - 1))
            {
                $values[$i] = $self->{entries}->[$i]->getValue if !$values[$i];
                $self->{entries}->[$i]->addHistory($values[$i])
                    if $self->{withHistory};
                $self->{entries}->[$i]->clear;
            }
        }
        # Check that at least one value is not empty
        my $isEmpty = 1;
        for my $val (@values)
        {
            if ($val)
            {
                $isEmpty = 0;
                last;
            }
        }
        if (!$isEmpty)
        {
            push @{$self->{list}->{data}}, \@values;
            $self->{list}->select($#{$self->{list}->{data}});
            my $path = $self->{list}->get_selection->get_selected_rows;
            $self->{list}->scroll_to_cell($path) if $path;
        }
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub getValue
    {
        my $self = shift;
        my $formated = shift;

        if ($formated)
        {
            return GCPreProcess::multipleList($self->{list}->{data}, $self->{number});
        }
        else
        {
            # As data in list is a tied array, we need to copy all the values
            my @value;
            foreach (@{$self->{list}->{data}})
            {
                push @value, [];
                foreach my $col(@$_)
                {
                    push @{$value[-1]}, $col;
                }
            }
            return \@value;
        }
    }
    
    sub setValue
    {
        my ($self, $value) = @_;

        if (ref($value) eq 'ARRAY')
        {
            @{$self->{list}->{data}} = @{$value};
        }
        else
        {
            # The separator used was ; instead of ,
            $value =~ s/;/,/g if $value !~ /,/;
            @{$self->{list}->{data}} = ();
            my @values = split m/,/, $value;
            foreach my $entry (@values)
            {
                my @items = split m/;/, $entry;
                s/^\s*// foreach(@items);
                push @{$self->{list}->{data}}, \@items;
            }
        }
    }
    
    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
        return if $self->{readonly};
        $self->{addButton}->set_sensitive(!$locked);
        $self->{removeButton}->set_sensitive(!$locked);
        foreach (@{$self->{entries}})
        {
            $_->lock($locked);
        }
    }

    sub addHistory
    {
        my $self = shift;
        my $value = (scalar @_) ? shift : $self->getValue;
        my $noUpdate = shift;

        return if $self->{readonly};
        my $i;
        my $item;
        if (ref($value) eq 'ARRAY')
        {
            foreach (@$value)
            {
                $i = 0;
                foreach $item(@$_)
                {
                    $self->{entries}->[$i]->addHistory($item, $noUpdate);
                    $i++;
                }
            }
        }
        else
        {
            # The separator used was ; instead of ,
            $value =~ s/;/,/g if $value !~ /,/;
            my @values = split m/,/, $value;
            foreach (@values)
            {
                my @items = split m/;/;
                $i = 0;
                foreach my $item(@items)
                {
                    $self->{entries}->[$i]->addHistory($item, $noUpdate);
                    $i++;
                }
                #push @{$self->{list}->{data}}, \@items;
            }
        }
    }

    sub setDropDown
    {
        my $self = shift;
        
        foreach (@{$self->{entries}})
        {
            $_->setDropDown;
        }
    }

    sub getValues
    {
        my $self = shift;
        return [] if $self->{readonly};
        my @array;
        foreach (@{$self->{entries}})
        {
            push @array, $_->getValues;
        }
        # = sort keys %{$self->{history}};
        return \@array;
    }
    
    sub setValues
    {
        my ($self, $values) = @_;
        return if $self->{readonly};
        my $i = 0;
        foreach (@$values)
        {
            $self->{entries}->[$i]->setValues($_);
            $i++;
        }
    }
}


{
    package GCItemImage;
    
    @GCItemImage::ISA = ('Gtk2::Image', 'GCGraphicComponent');

    use File::Spec;
    use File::Basename;
    
    sub new
    {
        my ($proto, $options, $parent, $fixedSize, $width, $height) = @_;
        my $class = ref($proto) || $proto;
#        my $self  = Gtk2::Image->new_from_file($parent->{defaultImage});
        my $self  = Gtk2::Image->new;
        $self->{options} = $options;
        #$self->{defaultImage} = $defaultImage;
        $self->{parent} = $parent;
        $self->{displayedImage} = '';
        $self->{fixedSize} = $fixedSize;
        bless ($self, $class);
        if ($width && $height)
        {
            $self->{width} = $width;
            $self->{height} = $height;
        }
        else
        {
            $self->{width} = 120;
            $self->{height} = 160;
        }
        $self->{immediate} = 0;
        return $self;
    }

    sub setImmediate
    {
        my ($self) = @_;
        $self->{immediate} = 1;
    }
    sub activateStateTracking
    {
        my $self = shift;
        $self->{trackState} = 1;
    }

    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub setValue
    {
        my ($self, $displayedImage, $placer) = @_;
        $self->{displayedImage} = $displayedImage;
        $self->{hasChanged} = 1 if $self->{trackState};
        if ($self->{immediate})
        {
            $self->setPicture;
        }
        else
        {
            Glib::Source->remove($self->{timer})
                if $self->{timer};
            $self->{timer} = Glib::Timeout->add(100, sub {
                $self->setPicture;
                $placer->placeImg if $placer;
                return 0;
            });
        }
    }

    sub setPicture
    {
        my $self = shift;
        $self->{timer} = 0;
        my $displayedImage = GCUtils::getDisplayedImage($self->{displayedImage},
                                                        $self->{parent}->{defaultImage},
                                                        $self->{options}->file);
        my $pixbuf;
        eval
        {
            $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage);
        };
        if ($@)
        {
            $displayedImage = $self->{parent}->{defaultImage};
            $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage);
        }
        $self->{realImage} = $displayedImage;
        $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf, $self->{width}, $self->{height});
        $self->set_from_pixbuf($pixbuf);
        $self->set_size_request($self->{width}, $self->{height}) if $self->{fixedSize};
    }
    
    sub getValue
    {
        my $self = shift;
        return $self->{displayedImage};
    }

    sub getFile
    {
        my $self = shift;
        return $self->{realImage};
    }

    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }
    
    sub lock
    {
        my ($self, $locked) = @_;
    }
    
    sub setWidth
    {
        my ($self, $value) = @_;
        $self->{width} = $value;
    }

    sub setHeight
    {
        my ($self, $value) = @_;
        $self->{height} = $value;
    }
    
    sub getSize
    {
        my $self = shift;
        my $pixbuf = $self->get_pixbuf;
        return ($pixbuf->get_width, $pixbuf->get_height);
    }
}


our $hasGnome2VFS;
BEGIN {
    eval 'use Gnome2::VFS';
    if ($@)
    {
        $hasGnome2VFS = 0;
    }
    else
    {
        $hasGnome2VFS = 1;
        Gnome2::VFS->init();
    }
}

{
    package GCImageButton;

    @GCImageButton::ISA = ('Gtk2::Button', 'GCGraphicComponent');

    use File::Basename;

    sub animateImg
    {
        my ($self, $from, $to) = @_;
        my $pixbuf1 = Gtk2::Gdk::Pixbuf->new_from_file($from);
        $pixbuf1 = GCUtils::scaleMaxPixbuf($pixbuf1, $self->{img}->{width}, $self->{img}->{height});
        my $pixbuf2 = Gtk2::Gdk::Pixbuf->new_from_file($to);
        $pixbuf2 = GCUtils::scaleMaxPixbuf($pixbuf2, $self->{img}->{width}, $self->{img}->{height});
        my $height = $pixbuf2->get_height;
        my $width = $pixbuf2->get_width;
        foreach my $i(0..20)
        {
            Glib::Timeout->add(30*$i, sub {
                my $pixbufA = $pixbuf1->copy;
                my $pixbufB = $pixbuf2->copy;
                $pixbufA->composite($pixbufB, 0, 0, int($width - (($i/20)*$width)), $height, 0, 0, 1, 1, 'nearest', 255);
                $self->{img}->set_from_pixbuf($pixbufB);
            });
        }
    }

    sub setImg
    {
        my ($self, $value) = @_;
        $self->{img}->setValue($value, $self);
    }

    sub placeImg
    {
        my ($self) = @_;
        my ($picWidth, $picHeight) = $self->{img}->getSize;
        my ($buttonWidth, $buttonHeight) = ($self->allocation->width, $self->allocation->height);
        my $x = ($buttonWidth - $picWidth - $GCUtils::margin)/ 2;
        my $y = ($buttonHeight -$picHeight - $GCUtils::margin)/ 2;
        $self->{layout}->move($self->{img}, $x, $y);
    }

    sub changeState
    {
        my $self = shift;
        if ($self->{trackState})
        {
            if ($self->{flipped})
            {
                $self->{linkedComponent}->{hasChanged} = 1;
            }
            else
            {
                $self->{hasChanged} = 1;
            }
        }
    }

    sub clearImage
    {
        my $self = shift;

        $self->changeState;
        $self->{mainParent}->checkPictureToBeRemoved($self->{imageFile});
        $self->setValueWithParent('');
    }

    sub changeImage
    {
        my ($self, $fileName) = @_;
        return 0 if $self->{locked};
        if (!$fileName)
        {
            if (! $self->{imageDialog})
            {
                $self->{imageDialog} = GCFileChooserDialog->new($self->{parent}->{lang}->{PanelImageTitle}, $self->{mainParent}, 'open');
                $self->{imageDialog}->setWithImagePreview(1);    
            }

            my $currentFile = $self->{img}->getValue;
            if ($currentFile)
            {
                $self->{imageDialog}->set_filename($currentFile);
            }
            else
            {
                $self->{imageDialog}->set_filename($self->{previousDirectory});
            }
            my $response = $self->{imageDialog}->run;
            $self->{imageDialog}->hide;
            $self->{parent}->showMe;
            if ($response eq 'ok')
            {
                $fileName = $self->{imageDialog}->get_filename;
                $self->{previousDirectory} = dirname($fileName);
                $self->{hasChanged} = 1 if $self->{trackState};
            }
            else
            {
                return;
            }
        }

        my $ref = ($self->{flipped} ? $self->{backPic} : $self->{imageFile});
        if ($fileName ne $ref)
        {
            $self->{mainParent}->checkPictureToBeRemoved($ref);
            $self->changeState;
        }
        my $image = $self->{mainParent}->transformPicturePath($fileName);
        $self->setValueWithParent($image);
        return;        
    }
    
    sub isEmpty
    {
        my $self = shift;
        
        return $self->getValue eq '';
    }
    
    sub setValue
    {
        my ($self, $value) = @_;        
        $self->{hasChanged} = 1 if $self->{trackState};
        $self->setActualValue($value);
    }

    sub setValueWithParent
    {
        my ($self, $value, $keepWatcher) = @_;
        
        $self->setActualValue($value, $keepWatcher, $self->{flipped});
        if ($self->{isCover} && !$self->{flipped})
        {
            $self->{mainParent}->{items}->updateCurrent(0, [$self->{name}]);
            $self->{hasChanged} = 0 if $self->{parent} eq $self->{mainParent}->{panel} && !$keepWatcher;
        }
    }

    sub setActualValue
    {
        my ($self, $value, $keepWatcher, $flipped) = @_;
        Glib::Source->remove($self->{fileWatcher})
            if $self->{fileWatcher} && !$keepWatcher;
        if ($flipped)
        {
            $self->{backPic} = $value;
        }
        else
        {
            $self->{imageFile} = $value;
        }
        $self->setImg($value);
    }

    sub getValue
    {
        my $self = shift;
        if ($self->{flipped})
        {
            return $self->{imageFile};
        }
        return $self->{img}->getValue;
    }

    sub setLinkedActivated
    {
        my ($self, $value) = @_;
        $self->flipImage(1) if $self->{flipped};
        $self->{flipActivated} = $value;
    }

    sub flipImage
    {
        my ($self, $noButton) = @_;
        my $newLabel;
        if ($self->{flipped})
        {
            $self->setImg($self->{imageFile});
            $self->{frontFlipImage}->show if !$noButton;
            $self->{backFlipImage}->hide;
            #$self->animateImg($self->{backPic}, $self->{imageFile});
        }
        else
        {
            $self->setImg($self->{backPic});
            $self->{backFlipImage}->show if !$noButton;
            $self->{frontFlipImage}->hide;
            #$self->animateImg($self->{imageFile}, $self->{backPic});
        }
        $self->{flipped} = !$self->{flipped};
    }

    sub setLinkedValue
    {
        my ($self, $linkedValue) = @_;
        $self->{hasChanged} = 1 if $self->{trackState};
        $self->{backPic} = $linkedValue;
        $self->setImg($linkedValue) if $self->{flipped};
    }

    sub getLinkedValue
    {
        my ($self, $linkedValue) = @_;
        return $self->{backPic};
    }

    sub setLinkedComponent
    {
        my ($self, $linked) = @_;
        $self->{linkedComponent} = $linked;

        $self->{flipActivated} = 1;
        $self->{frontFlipImage} = Gtk2::Image->new_from_file($ENV{GCS_SHARE_DIR}.'/overlays/flip.png');
        $self->{frontFlipImage}->set_no_show_all(1);
        $self->{backFlipImage} = Gtk2::Image->new_from_file($ENV{GCS_SHARE_DIR}.'/overlays/flip2.png');
        $self->{backFlipImage}->set_no_show_all(1);
        my $pixbuf = $self->{frontFlipImage}->get_pixbuf;
        my ($picWidth, $picHeight) = ($pixbuf->get_width, $pixbuf->get_height);

        $self->{addedFlipButton} = 0;
        $self->signal_connect('enter' => sub {
            return if ! $self->{flipActivated};
            if (!$self->{addedFlipButton})
            {
                $self->{flipX} = $self->{width} - $picWidth - $GCUtils::margin;
                $self->{flipY} = $self->{height} - $picHeight - $GCUtils::margin;
                $self->{layout}->put($self->{frontFlipImage},
                                     $self->{flipX},
                                     $self->{flipY});
                $self->{layout}->put($self->{backFlipImage},
                                     $self->{flipX},
                                     $self->{flipY});
                $self->{addedFlipButton} = 1;
            }
            if ($self->{flipped})
            {
                $self->{backFlipImage}->show;
            }
            else
            {
                $self->{frontFlipImage}->show;
            }
        });
        $self->signal_connect('leave' => sub {
            $self->{frontFlipImage}->hide;
            $self->{backFlipImage}->hide;
        });
        $self->signal_connect('button-release-event' => sub {
            return 0 if ! $self->{flipActivated};
            my ($button, $event) = @_;
            my ($x, $y) = $event->get_coords;
            if (($x > $self->{flipX}) && ($y > $self->{flipY}))
            {
                $self->flipImage;
                $self->set_sensitive(0);
                $self->released;
                $self->set_sensitive(1);
                return 1;
            }
            else
            {
                return 0;
            }
        });
        
        $self->signal_connect('key-press-event' => sub {
            return 0 if ! $self->{flipActivated};
            my ($widget, $event) = @_;
            my $key = Gtk2::Gdk->keyval_name($event->keyval);
            
            if (($key eq 'f') || ($key eq 'BackSpace'))
            {
                $self->flipImage;
                return 1;
            }
            return 0;
        });
        
        $self->signal_connect('query_tooltip' => sub {
            my ($window, $x, $y, $keyboard_mode, $tip) = @_;
            if ($self->{flipActivated} && ($x > $self->{flipX}) && ($y > $self->{flipY}))
            {
                $self->{tooltips}->set_tip($self, $self->{flipped} ?
                                                  $self->{parent}->{lang}->{ContextImgFront} :
                                                  $self->{parent}->{lang}->{ContextImgBack});
            }
            else
            {
                $self->{tooltips}->set_tip($self, $self->{tip});
            }
            return 0;
        });
        
    }

    sub clear
    {
        my $self = shift;
        $self->setValue('');
    }

    sub new
    {
        my ($proto, $parent, $img, $isCover, $default) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;
        bless ($self, $class);

        $default = 'view' if !$default;

        $self->{layout} = new Gtk2::Fixed;
        $self->{layout}->put($img, 0, 0);
        $self->add($self->{layout});
        
        $self->{img} = $img;
        $self->{default} = $default;
        #$self->set_size_request(130,170);
        $self->{width} = -1;
        $self->{height} = -1;
        $self->{imageFile} = $img->getValue;

        # True if this is the cover used in image mode
        $self->{isCover} = $isCover;

        $self->{parent} = $parent;
        $self->getMainParent;
        $self->{tooltips} = $self->{mainParent}->{tooltips};

        $self->{tip} = ($default eq 'open') ? $parent->{lang}->{PanelImageTipOpen} : $parent->{lang}->{PanelImageTipView};
        $self->{tip} .= $parent->{lang}->{PanelImageTipMenu};
        $self->{tooltips}->set_tip($self, $self->{tip});

        $self->signal_connect('button_press_event' => sub {
            my ($widget, $event) = @_;
            return 0 if $event->button ne 3;
            $self->createContextMenu();
            $self->{imgContext}->popup(undef, undef, undef, undef, $event->button, $event->time);
            return 0;
        });

        $self->signal_connect('clicked' => sub {
            $self->changeImage if $self->{default} eq 'open';
            $self->showImage if $self->{default} eq 'view';
            return 1;
        });

        #Drag and drop a picture on a button
        $self->drag_dest_set('all', ['copy','private','default','move','link','ask']);
        my $target_list = Gtk2::TargetList->new();
        my $atom1 = Gtk2::Gdk::Atom->new('text/uri-list');
        my $atom2 = Gtk2::Gdk::Atom->new('text/plain');
        $target_list->add($atom1, 0, 0);
        $target_list->add($atom2, 0, 0);
        if ($^O =~ /win32/i)
        {
            my $atom2 = Gtk2::Gdk::Atom->new('DROPFILES_DND');
            $target_list->add($atom2, 0, 0);
        }
        $self->drag_dest_set_target_list($target_list);
        $self->signal_connect(drag_data_received => sub {
            my ($widget, $context, $widget_x, $widget_y, $data, $info,$time) = @_;
            my @files = split /\n/, $data->data;
            my $fileName = $files[0];
            if ($fileName =~ /^http/)
            {
                $fileName = $self->{mainParent}->downloadPicture($fileName);
            }
            else
            {
                $fileName =~ s|^file://?(.*)\W*$|$1|;
                $fileName =~ s|^/*|| if ($^O =~ /win32/i);
                $fileName =~ s/.$//ms;
                $fileName =~ s/%20/ /g;
            }
            $self->changeImage($fileName);
        });

        $self->{previousDirectory} = '';
        $self->{flipped} = 0;
        $self->{flipActivated} = 0;

        return $self;
    }
    
    sub createContextMenu
    {
        my $self = shift;
        
        my $parent;
        $parent = $self->{parent};

        $self->{imgContext} = new Gtk2::Menu;

        if ($parent->{options}->tearoffMenus)
        {
            $self->{imgContext}->append(Gtk2::TearoffMenuItem->new());
        }

        $self->{itemOpen} = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseImage});
        my $itemOpenImage = Gtk2::Image->new_from_stock('gtk-open', 'menu');
        $self->{itemOpen}->set_image($itemOpenImage);
        $self->{itemOpen}->signal_connect("activate" , sub {
            $self->changeImage;
        });
        $self->{imgContext}->append($self->{itemOpen});
        $self->{itemShow} = Gtk2::ImageMenuItem->new_from_stock('gtk-zoom-100',undef);
        $self->{itemShow}->signal_connect("activate" , sub {
            $self->showImage;
        });
        # Disable for default image
        $self->{itemShow}->set_sensitive(0) if $self->isDefaultImage();
        $self->{imgContext}->append($self->{itemShow});

        if ($self->{linkedComponent})
        {
            $self->{itemFlip} = Gtk2::MenuItem->new($self->{flipped} ?
                                                        $parent->{lang}->{ContextImgFront} :
                                                        $parent->{lang}->{ContextImgBack});
            $self->{itemFlip}->signal_connect("activate" , sub {
                $self->flipImage;
            });
            $self->{imgContext}->append($self->{itemFlip});
        }

        $self->{itemClear} = Gtk2::ImageMenuItem->new_from_stock('gtk-clear',undef);
        $self->{itemClear}->signal_connect("activate" , sub {
            $self->clearImage;
        });
        $self->{imgContext}->append($self->{itemClear});        
        # Disable for default image
        $self->{itemClear}->set_sensitive(0) if $self->isDefaultImage();
        $self->{imgContext}->show_all;

        my $itemOpenWith = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextOpenWith});
        $self->{menuOpenWith} = Gtk2::Menu->new;

        if ($hasGnome2VFS && ($parent->{options}->programs eq 'system' || $parent->{options}->imageEditor eq ''))
        { 
            # Get applications for mime types corresponding with image

            # Get all editors/viewers for jpeg file format
            my $mimeTest = Gnome2::VFS::Mime::Type->new ("image/jpeg");
            my @mimeList = $mimeTest->get_short_list_applications;

            # Add applications to open with list
            foreach (@mimeList)
            {
                my $launchApp = $_;
                my $item = Gtk2::MenuItem->new_with_mnemonic($launchApp->get_name);
                $item->signal_connect ('activate' => sub {
                        $self->openWith($launchApp);
                });
                $self->{menuOpenWith}->append($item);
            }

            #Gnome2::VFS -> shutdown();
        }
        elsif ($parent->{options}->programs eq 'system' || $parent->{options}->imageEditor eq '')
        {
            # Can't parse applications, so use system default app  
            my $item = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextImageEditor});

            my $command;
            $command = ($^O =~ /win32/i) ? ''
                     : ($^O =~ /macos/i) ? '/usr/bin/open'
                     :                     $ENV{GCS_SHARE_DIR}.'/helpers/xdg-open';

            # Not sure if this is correct, haven't tested with Windows:
            if ($^O =~ /win32/i)
            {
                $command = '"'.$command.'"' if $command;
            }

            $item->signal_connect ('activate' => sub {
                     $self->openWithImageEditor($command);
            });

            $self->{menuOpenWith}->append($item);
        }
        else
        {
            # Use user defined app  
            my $item = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextImageEditor});
            $item->signal_connect ('activate' => sub {
                     $self->openWithImageEditor($parent->{options}->imageEditor);
            });

            $self->{menuOpenWith}->append($item);
        }


        $itemOpenWith->set_submenu($self->{menuOpenWith});
       
        # Disable for default image
        $itemOpenWith->set_sensitive(0) if $self->isDefaultImage();

        $self->{imgContext}->append($itemOpenWith);
        $self->{imgContext}->show_all;

    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->{trackState} = 1;
    }

    sub lock
    {
        my ($self, $locked) = @_;
    
        $self->{locked} = $locked;

        # Probably not required anymore, now that menus are generated on the fly:

#        for my $menu('Open', 'Clear')
#        {
#            $self->{'item'.$menu}->set_sensitive(!$locked);
#        }		
    }
    
    sub showImage
    {
        my $self = shift;
        $self->{mainParent}->launch($self->{img}->getValue, 'image');
    }
    
    sub isDefaultImage
    {
        my ($self) = @_;

        if ($self->{img}->getFile eq $self->{parent}->{defaultImage})
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    sub openWith
    {
        my ($self, $app) = @_;
        my $cmd;
        my $escFileName;

        # Ultra hacky workaround, because $app->{launch} segfaults. See http://bugzilla.gnome.org/show_bug.cgi?id=315049
        # Probably should change to gvfs when perl modules are available

        if ($app->{command} =~ m/(\w*)/)
        {
            $cmd = $1;
        }

        $escFileName = $self->{img}->getFile;
        $escFileName =~ s/\ /%20/g;
        $self->editPicture("$cmd file://$escFileName");
    }

    sub openWithImageEditor
    {
        my ($self, $editor) = @_;
        my $file = $self->{img}->getFile;
        $file =~ s|/|\\|g if ($^O =~ /win32/i);
        $self->editPicture("$editor \"$file\"");
    }

    sub editPicture
    {
        my ($self, $commandLine) = @_;
        my $file = $self->{img}->getFile;

        my $flipped = $self->{flipped};
        $self->{fileWatchDays} = -M $file; 
        $self->{fileWatcher} = Glib::Timeout->add(1000, sub {
            my $currentDays = -M $file;
            if ($currentDays < $self->{fileWatchDays})
            {
                $self->changeState;
                # We remove it from the pixbuf cache in items view. Useful
                # for detailed list to be sure it will be re-loaded
                delete $self->{mainParent}->{itemsView}->{cache}->{$file}
                    if $self->{mainParent}->{itemsView}->{cache};
                $self->setValueWithParent($self->{img}->getValue, 1, $flipped);
                $self->{fileWatchDays} = $currentDays; 
            }
            return 1;
        });
        $self->{mainParent}->launch($commandLine, 'program', 1);
    }

    sub setWidth
    {
        my ($self, $value) = @_;
        $self->{width} = $value;
        $self->set_size_request($value, $self->{height});
        $self->{img}->setWidth($value - $GCUtils::margin);
    }

    sub setHeight
    {
        my ($self, $value) = @_;
        $self->{height} = $value;
        $self->set_size_request($self->{width}, $value);
        $self->{img}->setHeight($value - $GCUtils::margin);
    }
    
}

{
    package GCMenuList;

    @GCMenuList::ISA = ('Gtk2::ComboBox', 'GCGraphicComponent');

    our $separatorValue = 'GCSSeparator';

    sub isEmpty
    {
        my $self = shift;
        
        return 1 if ! defined $self->get_active_iter;
        return ($self->{listModel}->get($self->get_active_iter))[1] eq '';
        my $idx = $self->get_history;
        $idx-- if $idx >= $self->{separatorPosition};
        $idx = 0 if $idx < 0;
        return $self->{'values'}->[$idx]->{displayed} eq '';
    }
    
    sub valueToDisplayed
    {
        my ($self, $value) = @_;
    
        foreach (@{$self->{'values'}})
        {
            return $_->{displayed} if $_->{value} eq $value
        }
        return '';
    }
    
    sub getValue
    {
        my ($self, $formatted) = @_;
        my $iter = $self->get_active_iter;
        my $value = '';
        $value = ($self->{listModel}->get($iter))[0] if $iter;
        $value = $self->valueToDisplayed($value) if $formatted;
        return $value;
    }

    sub getDisplayedValue
    {
        my $self = shift;
        my $iter = $self->get_active_iter;
        return ($self->{listModel}->get($iter))[1] if $iter;
        return '';
    }

    sub setValue
    {
        my ($self, $value) = @_;

        $value = 0 if !$value;
        my $i = 0;
        if ($value)
        {
    		foreach (@{$self->{values}})
            {
                last if $_->{value} eq $value;
                $i++;
            }
        }
        $i++ if $i >= $self->{separatorPosition};
        $i-- if ($self->{default} == -1) && ($i >= $self->{count});
        $self->set_active($i)
            if ($i < scalar(@{$self->{values}}));
    }

    sub clear
    {
        my $self = shift;
        $self->set_active(0);
    }

    sub lock
    {
        my ($self, $locked) = @_;
        
        $self->set_sensitive(!$locked);
    }
    
    sub getValues
    {
        my $self = shift;
        
        my @values;
        return $self->{values};
    }
    
    sub setValues
    {
        my ($self, $values, $separatorPosition, $preserveValue) = @_;
        my $model = $self->{listModel};
        my $previous = $self->getValue if $preserveValue;
        $self->{values} = $values;
        $self->{separatorPosition} = $separatorPosition || 9999;

        $model->clear;
        my $i = 0;
        foreach (@$values)
        {
            if ($i == $self->{separatorPosition})
            {
                $model->set($model->append, 0 => $GCMenuList::separatorValue, 1 => '');
                $i++;
            }
            $model->set($model->append,
                        0 => $_->{value},
                        1 => $_->{displayed});
            $i++;
        }

        $self->{count} = $i;
        $self->setValue($previous) if $preserveValue;
        $self->set_active(0) if !$preserveValue;
    }
    
    sub setLastForDefault
    {
        my $self = shift;
        $self->{default} = -1;
    }
    
    sub new
    {
        my ($proto, $values, $separatorPosition) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;
        bless ($self, $class);

        $self->{listModel} = Gtk2::ListStore->new('Glib::String', 'Glib::String');
        $self->set_model($self->{listModel});
        my $renderer = Gtk2::CellRendererText->new;
        $self->pack_start($renderer, 1);
        $self->add_attribute($renderer, text => 1);
        $self->set_row_separator_func(sub {
            my ($model, $iter) = @_;
            my @values = $model->get($iter, 0);
            my $val = '';
            $val = $values[0] if defined $values[0];
            return $val eq $GCMenuList::separatorValue;
        });

        $self->setValues($values, $separatorPosition) if $values;
        $self->{default} = 0;
        $self->set_focus_on_click(1);
        return $self;
    }

    sub activateStateTracking
    {
        my $self = shift;
        $self->signal_connect('changed' => sub {
            $self->{hasChanged} = 1;
        });
    }
}

{
    package GCHeaderLabel;
    use base 'Gtk2::Label';
    
    sub new
    {
        my ($proto, $label) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;
        bless ($self, $class);

        $self->setText($label);
        $self->set_alignment(0,1);
        
        return $self;
    }
    
    sub setText
    {
        my ($self, $label) = @_;
        $self->set_markup('<b>'.$label.'</b>');        
    }
}

{
    package GCLabel;
    
    @GCLabel::ISA = ('Gtk2::Label', 'GCGraphicComponent');

    sub new
    {
        my ($proto, $label, $disableMarkup) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;
        bless ($self, $class);
        $self->set_markup($label) unless $disableMarkup;
        $self->set_label($label) if $disableMarkup;
        $self->set_alignment(0,0.5);
        return $self;
    }
}

{
    package GCColorLabel;
    
    @GCColorLabel::ISA = ('Gtk2::EventBox', 'GCGraphicComponent', 'GCPseudoHistoryComponent');

    sub new
    {
        my ($proto, $color, $listType) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;

        bless ($self, $class);

        $listType = 0 if !$listType;
        $self->modify_bg('normal', $color);
        $self->{label} = Gtk2::Label->new;
        $self->{label}->show;
        $self->{hboxFill} = new Gtk2::HBox(0,0);
        $self->{hboxFill}->pack_start($self->{label},1,1,0);
        $self->add($self->{hboxFill});
        $self->set_alignment(0,0.5);
        $self->initHistory($listType);

        return $self;
    }

    sub acceptMarkup
    {
        my $self = shift;
        return 1;
    }

    sub setMarkup
    {
        my ($self, $text) = @_;
        
        $self->{label}->set_markup($text);
    }

    sub getValue
    {
        my $self = shift;

        (my $label = $self->{label}->get_label) =~ s/<.*?>(.*?)<\/.*?>/$1/g;
        return $label;
    }

    sub setBgColor
    {
        my ($self, $color) = @_;
        $self->modify_bg('normal', $color);
    }

    sub set_justify
    {
        my ($self, $value) = @_;
        $self->{label}->set_justify($value);
        $self->{label}->set_alignment(0.5,0) if $value eq 'center';
        $self->{label}->set_alignment(1,0) if $value eq 'right';
    }
    
    sub AUTOLOAD
    {
        my $self = shift;
        my $name = our $AUTOLOAD;
        return if $name =~ /::DESTROY$/;
        $name =~ s/.*?::(.*)/$1/;
        $self->{label}->$name(@_);
    }
}

{
    package GCColorLink;
    
    @GCColorLink::ISA = ('GCColorLabel');

    sub new
    {
        my ($proto, $color, $opener) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new($color);
        bless ($self, $class);
        $self->{opener} = $opener;
        $self->signal_connect('button-release-event' => sub {
            my $value = $self->getValue;
            return if !$value;
            $self->{opener}->launch($value, 'url');
        });
        $self->signal_connect('enter-notify-event' => sub {
            $self->window->set_cursor(Gtk2::Gdk::Cursor->new('hand2'))
                if $self->getValue;
        });
        #$self->window->set_cursor(Gtk2::Gdk::Cursor->new('watch'));
        return $self;
    }
}

{
    package GCColorLongLabel;

    @GCColorLongLabel::ISA = ('Gtk2::TextView', 'GCGraphicComponent');

    sub new
    {
        my ($proto, $color) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
        bless ($self, $class);

        $self->set_editable(0);
        $self->set_wrap_mode('word');
        $self->{background} = $color;
        $self->modify_base('normal', $color);
        $self->modify_bg('normal', $color);
        $self->set_border_width($GCUtils::halfMargin);

        my $layout = $self->create_pango_layout('G');
        my (undef, $rect) = $layout->get_pixel_extents;
        $self->{em} = 1.5 * $rect->{height};        


        return $self;
    }
    
    sub acceptMarkup
    {
        my $self = shift;
        return 1;
    }

    sub setMarkup
    {
        my ($self, $text) = @_;
        
        #$text =~ s/&/&amp;/g;
        if ($self->{resize})
        {
            $self->resize;
        }
        else
        {
            my $buffer = $self->get_buffer;
            $self->get_buffer->set_text('');
            $text =~ s|<span ([^>]*?)>([^<]*?)</span>|$2|;
            if ($self->{spanTag})
            {
                $buffer->get('tag-table')->remove($self->{spanTag});
            }
            $self->{spanTag} = $buffer->create_tag('span', $self->getTagFromSpan($1));
            $buffer->insert_with_tags_by_name ($buffer->get_start_iter, $text, 'span');
        }
    }

    sub getValue
    {
        my $self = shift;

        (my $label = $self) =~ s/<.*?>(.*?)<\/.*?>/$1/g;
        return $label;
    }

}

{
    package GCColorTable;
    
    @GCColorTable::ISA = ('Gtk2::Table', 'GCGraphicComponent', 'GCPseudoHistoryComponent');

    sub new
    {
        my ($proto, $columns, $labels, $headerStyle, $contentStyle, $topHeader) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new(1, $columns);

        bless ($self, $class);

        $self->set_col_spacings(3);
        $self->set_row_spacings(3);
        $self->{number} = $columns;
        $self->{style} = $contentStyle;
        $self->{firstRow} = 0;
        if ($topHeader)
        {
            $self->{firstRow}++;
            my $top = new GCColorLabel($headerStyle->{bgColor});
            $top->set_padding(6,6);
            $top->setMarkup('<span '.$headerStyle->{style}.'>'.$topHeader.'</span>');
            $self->attach($top, 0, $columns, 0, 1, ['expand', 'fill'], 'fill', 0, 0);            
        }
        my $i;
        if ($columns > 1)
        {
            for $i(0..($columns - 1))
            {
                my $header = new GCColorLabel($headerStyle->{bgColor});
                $header->set_padding(6,6);
                $header->setMarkup('<span '.$headerStyle->{style}.'>'.$labels->[$i].'</span>');
                $self->attach($header, $i, $i + 1, $self->{firstRow}, $self->{firstRow} + 1, ['expand', 'fill'], 'fill', 0, 0);
            }
            $self->{firstRow}++;
        }
        $self->initHistory($columns);
        return $self;
    }

    sub setValue
    {
        my ($self, $value) = @_;
        $self->{value} = $value;
 
        foreach (@{$self->{labels}})
        {
            $self->remove($_);
            $_->destroy;
        }

        $self->{labels} = [];
        my @lines;
        if (ref($value) eq 'ARRAY')
        {
            @lines = @$value;
        }
        else
        {
            $value =~ s/^\s*//;
            @lines = split m/,/, $value;
        }
        if ($#lines < 0)
        {
            $self->hide_all;
            return;
        }
        my $i = $self->{firstRow};
        $self->resize($#lines + 1 + $self->{firstRow}, $self->{number});
        foreach (@lines)
        {
            my @cols;
            if (ref($value) eq 'ARRAY')
            {
                @cols = @$_;
            }
            else
            {
                @cols = split m/;/, $_;
            }
            my $j = 0;
            for my $col(@cols)
            {
                # TODO Optimize GCColorLongLabel. It offers a better display (no scrollbar)
                # but it slows down the display.
                my $label = new GCColorLabel($self->{style}->{bgColor});
                $label->set_padding(6,6);
                #my $label = new GCColorLongLabel($self->{style}->{bgColor}, '2em');
                $label->setMarkup('<span '.$self->{style}->{style}.'>'.$col.'</span>');
                $self->attach($label, $j, $j + 1, $i, $i + 1, ['expand', 'fill'], 'fill', 0, 0);
                push @{$self->{labels}}, $label;
                $j++;
            }
            $i++;
        }
        $self->show_all;
    }

    sub getValue
    {
        my $self = shift;
        return $self->{value};
    }
    
    sub setBgColor
    {
        my ($self, $color) = @_;
        return;
    }

    sub set_justify
    {
        my ($self, $value) = @_;
        return;
    }
    
}

{
    package GCColorText;

    @GCColorText::ISA = ('Gtk2::ScrolledWindow', 'GCGraphicComponent', 'GCPseudoHistoryComponent');

    sub new
    {
        my ($proto, $color, $height, $listType) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new;
        bless ($self, $class);

        $listType = 0 if !$listType;
        $self->{text} = new Gtk2::TextView;
        $self->{text}->set_editable(0);
        $self->{text}->set_wrap_mode('word');
        $self->{background} = $color;
        $self->{text}->modify_base('normal', $color);
        $self->{text}->modify_bg('normal', $color);
        $self->{text}->set_border_width($GCUtils::halfMargin);
        $self->set_border_width(0);
        $self->set_shadow_type('none');
        $self->set_policy('automatic', 'automatic');
        $self->add($self->{text});
        $self->initHistory($listType);

        my $layout = $self->create_pango_layout('G');
        my (undef, $rect) = $layout->get_pixel_extents;
        $self->{em} = 1.5 * $rect->{height};        

        $self->setHeight($height) if $height;

        return $self;
    }
    
    sub acceptMarkup
    {
        my $self = shift;
        return 1;
    }

    sub setMarkup
    {
        my ($self, $text) = @_;
        
        if ($self->{resize})
        {
            $self->resize;
        }
        else
        {
            my $buffer = $self->{text}->get_buffer;
            $self->{text}->get_buffer->set_text('');
            $text =~ s|<span ([^>]*?)>([^<]*?)</span>|$2|;
            if ($self->{spanTag})
            {
                $buffer->get('tag-table')->remove($self->{spanTag});
            }
            $self->{spanTag} = $buffer->create_tag('span', $self->getTagFromSpan($1));
            $buffer->insert_with_tags_by_name ($buffer->get_start_iter, $text, 'span');
        }
    }

    sub getValue
    {
        my $self = shift;

        (my $label = $self->{text}) =~ s/<.*?>(.*?)<\/.*?>/$1/g;
        return $label;
    }

    sub setHeight
    {
        my ($self, $height) = @_;
        $height =~ s/^([0-9]+)em$/$1*$self->{em}/e;
        $self->set_size_request(-1, $height);
    }

    sub AUTOLOAD
    {
        my $self = shift;
        my $name = our $AUTOLOAD;
        return if $name =~ /::DESTROY$/;
        $name =~ s/.*?::(.*)/$1/;
        $self->{text}->$name(@_);
    }
}

{
    package GCColorExpander;
    
    @GCColorExpander::ISA = ('GCExpander');

    sub new
    {
        my ($proto, $label, $bgColor, $fgStyle) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new($label);

        bless ($self, $class);
        $self->{label} = new GCColorLabel($bgColor);
        $self->set_label_widget($self->{label});
        $self->{fgStyle} = $fgStyle;

        return $self;
    }
    
    sub setValue
    {
        my ($self, $label, $description) = @_;

        $label = '<span '.$self->{fgStyle}.">$label</span>";

        $self->{label}->set_markup($label);
        if ($description)
        {
            $self->{description}->set_label($description);
            $self->{description}->show;
        }
        else
        {
            $self->{description}->set_label('');
            $self->{description}->hide;
        }


    }
}

{
    package GCDialogHeader;
    
    use base 'Gtk2::HBox';
    
    sub new
    {
        my ($proto, $text, $imageStock, $logosDir) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;

        bless ($self, $class);
        
        $self->{label} = new Gtk2::Label;
        $self->{label}->set_markup("<span size='large' weight='bold'>$text</span>");
        $self->{label}->set_alignment(0,0.5);
        
        if (-f $logosDir.$imageStock.'.png')
        {
            $self->{image} = Gtk2::Image->new_from_file($logosDir.$imageStock.'.png');
            $self->pack_start($self->{image},0,1,5);
        }
        
        $self->pack_start($self->{label},0,1,5);
        
        return $self;
    }
}

{
    package GCImageBox;
    
    use base 'Gtk2::VBox';
    
    sub new_from_file
    {
        my ($proto, $imageFile, $label) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new();

        bless ($self, $class);

        my $image = Gtk2::Image->new_from_file($imageFile);
        $self->init($image, $label);
        
        return $self;
    }
    sub new_from_stock
    {
        my ($proto, $stockId, $label) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new();

        bless ($self, $class);

        my $image = Gtk2::Image->new_from_stock($stockId, 'large-toolbar');
        $self->init($image, $label);
         
        return $self;        
    }
    
    sub init
    {
        my ($self, $image, $label) = @_;
        
        $self->{label} = new Gtk2::Label($label);
        
        $self->pack_start($image, 0, 0, 0);
        $self->pack_start($self->{label}, 0, 0, 0);
        
        $self->show_all;
    }
}

{
    package GCGroup;

    @GCGroup::ISA = ('Gtk2::Frame', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $title) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;

        bless ($self, $class);

        $self->set_shadow_type('none');
        $self->set_border_width($GCUtils::margin);
        $self->{label} = new Gtk2::Label;
        $self->{label}->set_padding(0,0);
        #$label->set_border_width(0);
        $self->setLabel($title);
        $self->set_label_widget($self->{label});
        $self->set_label_align(0,0);

        $self->{marginBox} = new Gtk2::HBox(0,0);
        $self->{marginBox}->set_border_width($GCUtils::halfMargin);
        $self->add($self->{marginBox});

        return $self;        
    }
    
    sub setLabel
    {
        my ($self, $label) = @_;
        
        $self->{label}->set_markup('<b>'.$label.'</b>');
    }
    
    sub addWidget
    {
        my ($self, $widget, $margin) = @_;
        $margin = $GCUtils::halfMargin if !$margin;
        $self->{marginBox}->pack_start($widget, 1, 1, $margin);
    }
    
    sub setPadding
    {
        my ($self, $value) = @_;
        
        $self->{marginBox}->set_border_width($value);
    }
}

{
    package GCExpander;
    
    @GCExpander::ISA = ('Gtk2::Expander', 'GCGraphicComponent');
    
    sub new
    {
        my ($proto, $label) = @_;
        my $class = ref($proto) || $proto;
    
        my $self = $class->SUPER::new($label);

        bless ($self, $class);
        $self->{hbox} = new Gtk2::HBox(0,0);
        $self->{label} = new Gtk2::Label;
        $self->{label}->set_alignment(0,0.5);
        $self->{description} = new Gtk2::Label;
        $self->{description}->set_alignment(0,0);
        eval {$self->{description}->set_line_wrap_mode('word');};
        $self->{hbox}->pack_start($self->{label}, 0, 0, 0);
        $self->{hbox}->pack_start($self->{description}, 1, 1, 0);
        $self->set_label_widget($self->{hbox});
        $self->{signalHandler} = undef;
        return $self;
    }

    sub setMode
    {
        my ($self, $mode) = @_;
        if ($mode eq 'asis')
        {
            $self->{description}->set_ellipsize('none');
            $self->{description}->set_line_wrap(0);
            if ($self->{signalHandler})
            {
                $self->signal_handler_disconnect($self->{signalHandler});
                $self->{signalHandler} = undef;
                $self->{description}->set_size_request(-1, -1);
            }
        }
        else
        {
            $self->{signalHandler} = $self->signal_connect('size-allocate' => sub {
                my $width = $self->allocation->width 
                          - $self->{label}->allocation->width
                          - ( 4 * $GCUtils::margin);
                $self->{description}->set_size_request(($width >= -1) ? $width : -1 , -1);
                return 0;
            });
            if ($mode eq 'wrap')
            {
                $self->{description}->set_ellipsize('none');
                $self->{description}->set_line_wrap(1);
            }
            else
            {
                $self->{description}->set_ellipsize('end');
                $self->{description}->set_line_wrap(0);
            }
        }
    }

    sub setValue
    {
        my ($self, $label, $description) = @_;

        $self->{label}->set_label($label);
        if ($description)
        {
            $self->{description}->set_label($description);
            $self->{description}->show;
        }
        else
        {
            $self->{description}->set_label('');
            $self->{description}->hide;
        }
    }
}

{
    package GCFieldSelector;

    @GCFieldSelector::ISA = ('GCMenuList');
    our $anyFieldValue = 'GCAnyField';
    
    sub valueToDisplayed
    {
        my ($self, $value) = @_;
        return '';
    }
    
    sub setValue
    {
        my ($self, $value) = @_;

        $self->{listModel}->foreach(sub {
            my ($model, $path, $iter) = @_;
            if ($model->get_value($iter, 0) eq $value)
            {
                $self->set_active_iter($iter);
                return 1;
            }
            return 0;
        });
    }
    
    sub getValues
    {
        my $self = shift;
        return [];
    }
    
    sub setValues
    {
        my ($self, $values, $separatorPosition, $preserveValue) = @_;
        return;
    }
    
    sub new
    {
        my ($proto, $withImages, $model, $advancedFilter, $withAnyField, $withoutEmpty) = @_;
        my $class = ref($proto) || $proto;
        my $self = Gtk2::ComboBox->new;
        bless($self, $class);
        $self->{withImages} = $withImages;
        $self->{advancedFilter} = $advancedFilter;
        $self->{withAnyField} = $withAnyField;
        $self->{withoutEmpty} = $withoutEmpty;
        
        $self->{listModel} = Gtk2::TreeStore->new('Glib::String', 'Glib::String');
        $self->set_model($self->{listModel});
        my $renderer = Gtk2::CellRendererText->new;
        $self->pack_start($renderer, 1);
        $self->add_attribute($renderer, text => 1);

        $self->set_cell_data_func($renderer, sub {
            my ($layout, $cell, $model, $iter) = @_;
            my $sensitive = !$model->iter_has_child($iter);
            $cell->set('sensitive', $sensitive);
        });

        $self->{default} = 0;
        $self->set_focus_on_click(1);
        
        $self->setModel($model) if $model;

        return $self;
    }

    sub setModel
    {
        my ($self, $model) = @_;
        
        $self->{listModel}->clear;
        my $field;
        my @fieldsInfo = @{$model->getDisplayedInfo};
        
        if (! $self->{withoutEmpty})
        {
            $self->{listModel}->set($self->{listModel}->append(undef),
                                0 => '',
                                1 => '');
        }
        if ($self->{withAnyField})
        {
            $self->{listModel}->set($self->{listModel}->append(undef),
                                0 => $anyFieldValue,
                                1 => $model->getDisplayedText('AdvancedSearchAnyField'));
        }
        
        foreach my $group(@fieldsInfo)
        {
            my @fields;
            foreach $field (@{$group->{items}})
            {
                my $id = $field->{id};
                next if ($model->{fieldsInfo}->{$id}->{type} eq 'image') && (!$self->{withImages});
                next if ($self->{advancedFilter}
                      && (
                             ($id eq $model->{commonFields}->{id})
                          || ($id eq $model->{commonFields}->{title})
                          || ($id eq $model->{commonFields}->{url})
                         )
                        );
                next if ! $field->{label};
                push @fields, $field;
            }
            if (scalar @fields)
            {
                my $groupIter = $self->{listModel}->append(undef);
                $self->{listModel}->set($groupIter,
                                    0 => undef,
                                    1 => $group->{title});
                foreach $field(sort {$a->{label} cmp $b->{label}} @fields)
                {
                    my $fieldIter = $self->{listModel}->append($groupIter);
                    $self->{listModel}->set($fieldIter,
                                        0 => $field->{id},
                                        1 => $field->{label});
                }
            }
        }
        $self->{model} = $model;
     }
    sub activateStateTracking
    {
        my $self = shift;
    }

    # Create a widget suitable to enter a value according to current field type
    # It will destroy the current widget if it exists
    sub createEntryWidget
    {
        # $parent is the class that contains needed information
        # $comparison is the kind of comparison. Mainly useful if it is 'range' to create 2 fields
        # $currentWidget is the one we are replacing
        my ($self, $parent, $comparison, $currentWidget) = @_;
        my $value;
        if ($currentWidget)
        {
            $value = $currentWidget->getValue;
            $currentWidget->destroy;
        }
        my $widget;
        my $field = $self->getValue;
        my $info = $self->{model}->{fieldsInfo}->{$field};

        # These ones are required for createWidget
        $self->{lang} = $parent->{lang};
        $self->{options} = $parent->{options};
        $self->{window} = $parent;

        ($widget, undef) = GCGraphicComponents::createWidget($self, $info, $comparison);

        if ($info->{type} eq 'history text')
        {
            $widget->setValues($parent->{parent}->{panel}->getValues($field));
        }
        elsif ($info->{type} eq 'single list')
        {
            $widget->setValues($parent->{parent}->{panel}->getValues($field)->[0]);            
        }
        $widget->setValue($value);

        return $widget;
    }
}

{
    package GCComparisonSelector;
    @GCComparisonSelector::ISA = ('GCMenuList');

    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
        my $self = $class->SUPER::new;
        bless($self, $class);
        $self->setValues([
                          {value => 'contain', displayed => $parent->{lang}->{ModelFilterContain}},
                          {value => 'notcontain', displayed => $parent->{lang}->{ModelFilterDoesNotContain}},
                          {value => 'regexp', displayed => $parent->{lang}->{ModelFilterRegexp}},
                          {value => 'range', displayed => $parent->{lang}->{ModelFilterRange}},
                          {value => 'eq', displayed => '='},
                          {value => 'lt', displayed => '<'},
                          {value => 'le', displayed => '≤'},
                          {value => 'gt', displayed => '>'},
                          {value => 'ge', displayed => '≥'},
                        ]);
        return $self;
     }
}

1;
