package GCItemsLists;

###################################################
#
#  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 strict;
use locale;

{
    package GCBaseTextList;
    use base "Gtk2::ScrolledWindow";

    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;
        bless ($self, $class);
        $self->{parent} = $parent;
        $self->{preferences} = $parent->{model}->{preferences};
        $self->{preferences}->sortOrder(1)
            if ! $self->{preferences}->exists('sortOrder');
        $self->{count} = 0;
        return $self;
    }

    sub getNbItems
    {
        my $self = shift;
        return $self->{count};
    }

    sub convertIterToChildIter
    {
        my ($self, $iter) = @_;
        my $result = $self->{completeModel}->convert_iter_to_child_iter($iter);
        $result = $self->{subModel}->convert_iter_to_child_iter($result);
        return $result;
    }

    sub convertChildIterToIter
    {
        my ($self, $iter) = @_;
        my $result = $iter;
        $result = $self->{subModel}->convert_child_iter_to_iter($result);
        $result = $self->{completeModel}->convert_child_iter_to_iter($result);
        return $result;
    }

    sub convertIterToString
    {
        my ($self, $iter) = @_;
        return '' if ! $iter;

        return $self->{completeModel}->get_string_from_iter($iter);
    }
    
    sub convertIdxToIter
    {
        my ($self, $idx) = @_;
        $self->{completeModel}->foreach(sub {
            my ($model, $path, $iter) = @_;
            if (($self->convertIterToIdx($iter) == $idx)
             && (!$model->iter_n_children($iter)))
            {
                $self->{currentIterString} = $model->get_path($iter)->to_string;
                return 1;
            }
            return 0;
        });
    }

    sub selectAll
    {
        my $self = shift;

        $self->{list}->get_selection->select_all;
    }

    sub selectIter
    {
        my ($self, $iter, $deactivateUpdate) = @_;
        $self->{deactivateUpdate} = $deactivateUpdate;
        $self->{list}->get_selection->unselect_all;
        $self->{list}->get_selection->select_iter($iter);
        $self->{deactivateUpdate} = 0;
    }
    
    sub getCurrentIter
    {
        my $self = shift;
        my $iter = undef;
        #my $iter = $self->{list}->get_selection->get_selected;
        my @rows = $self->{list}->get_selection->get_selected_rows;
        $iter = $self->{list}->get_model->get_iter($rows[0]) if $rows[0];
        return $iter;
    }

    sub getCurrentItems
    {
        my $self = shift;
        my @indexes;
        my @iterators;
        my @rows = $self->{list}->get_selection->get_selected_rows;
        foreach (@rows)
        {
            my $iter = $self->{list}->get_model->get_iter($_);
            push @iterators, $iter;
            push @indexes, $self->convertIterToIdx($iter);
        }
        @indexes = sort @indexes;
        return (\@indexes, \@iterators) if wantarray;
        return \@indexes;
    }

    sub getCurrentIterFromString
    {
        my $self = shift;
        return ($self->{currentIterString})
              ? $self->{completeModel}->get_iter_from_string($self->{currentIterString})
              : $self->{completeModel}->get_iter_first;
    }

    sub removeCurrentItems
    {
        my ($self) = @_;
        my ($indexes, $iterators) = $self->getCurrentItems;

        $self->{deactivateSortCache} = 1;
        
        my ($nextIter, $newIdx) = $self->getNextIter($iterators->[-1], $indexes);
        my $realIter = $self->convertIterToChildIter($nextIter);
        my $nextPath = $self->{model}->get_path($realIter)->to_string;

        my $count = scalar @$indexes;

        $self->{count} -= $count;
        $self->{nextItemIdx} -= $count;
        
        $self->{list}->expand_to_path($self->{completeModel}->get_path($nextIter))
            if $self->{currentIterString} =~ /:/;
        $self->selectIter($nextIter)
            if ($nextIter);

        my @toBeRemoved;
        my %pathToChange;
        foreach my $number(@$indexes)
        {
            #Shift backward all following items.
            $self->{model}->foreach(sub {
                my ($model, $path, $iter) = @_;
                return 0 if $model->iter_has_child($iter);
                my $currentIdx = ($model->get($iter))[$self->{idxColumn}];
                if ($currentIdx > $number)
                {
                    $pathToChange{$path->to_string}++;
                }
                elsif ($currentIdx == $number)
                {
                    # We store them for future removal
                    push @toBeRemoved, new Gtk2::TreeRowReference($model, $path);
                }
                return 0;
            });
        }

        # Perform the actual shift
        my $offset = 0;
        foreach my $path(keys %pathToChange)
        {
            my $iter = $self->{model}->get_iter(Gtk2::TreePath->new($path));
            my $currentIdx = ($self->{model}->get($iter))[$self->{idxColumn}];
            $self->{model}->set($iter, $self->{idxColumn}, ($currentIdx - $pathToChange{$path}));
            if ($nextPath eq $path)
            {
                $newIdx = $currentIdx - $pathToChange{$path};
            }
            $offset++;
        }
        
        # Update caches
        $self->{sorter}->clear_cache;
        $self->{testCache} = [];

        # Removing all the instances
        foreach(@toBeRemoved)
        {
            $self->removeFromModel($self->{model}->get_iter($_->get_path));
        }

        $self->{deactivateSortCache} = 0;

        return $newIdx;
    }

    sub changeItem
    {
        my ($self, $idx, $previous, $new) = @_;
        $self->convertIdxToIter($idx);
        return $self->changeCurrent($previous, $new, $idx, 0);
    }

}

{
    package GCTextList;

    use Gtk2::SimpleList;
    use base 'GCBaseTextList';

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

        $self->{titleField} = $parent->{model}->{commonFields}->{title};
        $self->{idxColumn} = 1;
        $self->{orderSet} = 0;
        
        $self->set_policy ('automatic', 'automatic');
        $self->set_shadow_type('none');
        
        my $columnType = ($parent->{model}->{fieldsInfo}->{$self->{titleField}}->{type} eq 'number') ? 
                         'Glib::Int' :
                        'Glib::String';
        my $column = Gtk2::TreeViewColumn->new_with_attributes($title,  Gtk2::CellRendererText->new, 
                                                               'text' => 0);
        $column->set_resizable(1);
        $column->set_reorderable(1);
        $column->set_sort_column_id(0);
        # Columns are: Title, Index, isVisible
        $self->{model} = new Gtk2::TreeStore($columnType, 'Glib::Int', 'Glib::Boolean');
        $self->{filter} = new Gtk2::TreeModelFilter($self->{model});
        $self->{filter}->set_visible_column(2);
        {
            package GCSimpleTreeModelSort;
            use Glib::Object::Subclass
                Gtk2::TreeModelSort::,
                interfaces => [ 'Gtk2::TreeDragDest' ],
                ;
            
            sub new
            {
                my ($proto, $childModel) = @_;
                my $class = ref($proto) || $proto;
                return Glib::Object::new ($class, model => $childModel);
            }
        }
        $self->{sorter} = new GCSimpleTreeModelSort($self->{filter});
        $self->{subModel} = $self->{filter};
        $self->{completeModel} = $self->{sorter};
        $self->{list} = Gtk2::TreeView->new_with_model($self->{sorter});
        $self->{list}->append_column($column);
        $self->{list}->set_headers_clickable(1);
        $self->{list}->set_rules_hint(1);
        $self->{list}->set_name('GCItemsTextList');
        $self->{list}->get_selection->set_mode ('multiple');
        if ($parent->{model}->{fieldsInfo}->{$self->{titleField}}->{type} ne 'number')
        {
            $self->{sorter}->set_sort_func(0,
                                           \&sortCaseInsensitive,
                                           $self);
        }

        $self->{list}->get_selection->signal_connect ('changed' => sub {
            return if $self->{deactivateUpdate};
            my @indexes;
            $self->{list}->get_selection->selected_foreach(sub {
                my ($model, $path, $iter, $self) = @_;
                push @indexes, $self->{completeModel}->get_value($iter, 1);
            }, $self);
            return if scalar @indexes == 0;
            $parent->display(@indexes);
            my $iter = $self->getCurrentIter;
            $self->{currentIterString} = $self->convertIterToString($iter);
        });

        $self->{list}->signal_connect ('row-activated' => sub {
           $parent->displayInWindow;
        });
        $self->{list}->signal_connect('button_press_event' => sub {
           my ($widget, $event) = @_;
           return 0 if $event->button ne 3;
            $self->{context}->popup(undef, undef, undef, undef, $event->button, $event->time);
            return 0;
        });

        $self->{list}->signal_connect('key-press-event' => sub {
                my ($widget, $event) = @_;
                my $key = Gtk2::Gdk->keyval_name($event->keyval);
                if ($key eq 'Delete')
                {
                    $self->{parent}->deleteCurrentItem;
                    return 1;
                }
                return 0;
        });

        $self->add($self->{list});
        $self->show_all;
        $self->{currentIdx} = 0;

        return $self;
    }

    sub savePreferences
    {
        my ($self, $preferences) = @_;
        return if !$self->{orderSet};
        my ($fieldId, $order) = $self->{sorter}->get_sort_column_id;
        $preferences->sortField($self->{titleField});
        $preferences->sortOrder(($order eq 'ascending') ? 1 : 0);
    }

    sub couldExpandAll
    {
        my $self = shift;
        
        return 0;
    }

    sub convertIterToIdx
    {
        my ($self, $iter) = @_;
        return 0 if ! $iter;
        return $self->{completeModel}->get_value($iter, 1)
    }

    sub getCurrentIdx
    {
        my $self = shift;
        my $currentIter = $self->getCurrentIter;
        return $self->{completeModel}->get_value(
            $self->getCurrentIter, 1
        )
            if $currentIter;
        return 0;
    }

    sub isUsingDate
    {
        my ($self) = @_;
        return 0;
    }

    sub setSortOrder
    {
        my ($self, $order, $splash, $willFilter) = @_;
        $self->{orderSet} = 1;
        my $progressNeeded = ($splash && !$willFilter);
        my $step;
        if ($progressNeeded)
        {
            $step = GCUtils::round($self->{count} / 7);
            $splash->setProgressForItemsSort(2*$step);
        }
        $self->{sorter}->set_sort_column_id(0,
                                           $self->{preferences}->sortOrder ? 'ascending' : 'descending');
        $self->{sorter}->set_default_sort_func(undef, undef);
        $splash->setProgressForItemsSort(4*$step) if $progressNeeded;
    }

    sub sortCaseInsensitive
    {
        my ($childModel, $iter1, $iter2, $self) = @_;
        if ($self->{deactivateSortCache})
        {
            my $val1 = uc($childModel->get_value($iter1, 0));
            my $val2 = uc($childModel->get_value($iter2, 0));
            return $val1 cmp $val2;
        }
        else
        {
            my $idx1 = $childModel->get_value($iter1, 1);
            my $idx2 = $childModel->get_value($iter2, 1);
            my $val1 = uc($childModel->get_value($iter1, 0));
            my $val2 = uc($childModel->get_value($iter2, 0));
            return $val1 cmp $val2;
        }
    }

    sub testIter
    {
        my ($self, $filter, $items, $iter) = @_;
        my $idx = ($self->{model}->get($iter))[1];
        my $displayed;
        if (defined $self->{testCache}->[$idx])
        {
            $displayed = $self->{testCache}->[$idx];
        }
        else
        {
            $displayed = $self->{testCache}->[$idx] = $filter->test($items->[$idx]);
            # We increment only here to count only unique items
            $self->{count}++ if $displayed;
        }
        $self->{model}->set($iter,
                            2,
                            $displayed
                           );
        return $displayed;
    }

    sub setFilter
    {
        my ($self, $filter, $items, $refresh, $splash) = @_;
        $self->{count} = 0;
        $self->{testCache} = [];
        $self->{tester} = $filter;
        my $iter = $self->{model}->get_iter_first;
        my $idx = 0;
        $self->{model}->foreach(sub {
            $splash->setProgressForItemsSort($idx++) if $splash;
            my ($model, $path, $iter) = @_;
            GCTextList::testIter($self, $filter, $items, $iter);
            return 0;
        });
        $self->{filter}->refilter;
        
        my $currentIter = $self->getCurrentIter;
        return $self->{completeModel}->get_value($currentIter, 1)
            if $currentIter;
        if ($self->{completeModel}->get_iter_first)
        {
            my $idx = $self->{completeModel}->get_value($self->{completeModel}->get_iter_first, 1);
            $self->select($idx);
            return $idx;
        }
        else
        {
            return 0;
        }
        
    }

    sub clearCache
    {
        my $self = shift;
    }

    sub reset
    {
        my $self = shift;
        $self->{list}->set_model(undef);
        $self->{model}->clear;
        $self->{currentIdx} = 0;
        $self->{nextItemIdx} = -1;
    }
    
    sub done
    {
        my $self = shift;
        $self->{list}->set_model($self->{sorter});
    }
    
    sub getNextIter
    {
        my ($self, $viewedIter) = @_;
        my $nextIter = $self->{completeModel}->iter_next($viewedIter);
        # If we removed the last one, we are using the previous one.
        $nextIter = $self->{completeModel}->iter_nth_child(undef, $self->{count} - 1)
            if !$nextIter && ($self->{count} > 0);
        my $newIdx = 0;
        if ($nextIter)
        {
            $newIdx = $self->{completeModel}->get_value($nextIter, 1);
            #$self->selectIter($nextIter);
        }
        return ($nextIter, $newIdx);
    }

    sub addItem
    {
        my ($self, $info, $immediate) = @_;
        $self->{nextItemIdx}++;
        $self->{count}++;
        my @data = (
                    0 => $self->{parent}->transformTitle($info->{$self->{titleField}}),
                    1 => $self->{nextItemIdx},
                    2 => 1
                    );
        $self->{model}->set($self->{model}->append(undef), @data);
    }
    
    sub removeFromModel
    {
        my ($self, $iter) = @_;
        $self->{model}->remove($iter);
    }

    sub removeItem
    {
        my ($self, $number) = @_;
        splice @{$self->{testCache}}, $number, 1;
        #Shift backward all following items.
        $self->{model}->foreach(sub {
            my ($model, $path, $iter) = @_;
            my $currentIdx = $model->get_value($iter,1);
            if ($currentIdx >= $number)
            {
                $model->set($iter, 1, $currentIdx - 1)
                    if $currentIdx != $number;
            }
            return 0;
        });

        $self->{count}--;
        $self->{nextItemIdx}--;
        my $viewedCurrentIter = $self->getCurrentIter;
        my $currentIter = $self->convertIterToChildIter($viewedCurrentIter);
        my $newIdx = $self->selectNextIter($viewedCurrentIter);
        $self->{model}->remove($currentIter);
        return $newIdx;
    }

    sub select
    {
        my ($self, $idx, $init) = @_;
        if ($idx == -1)
        {
            $self->{currentIterString} = '0';
            my $currentIter = $self->{completeModel}->get_iter_first;
            if (!$currentIter)
            {
                $idx = 0;
                return;
            }
            $idx = $self->{completeModel}->get_value($currentIter, 1);
            $self->selectIter($currentIter);
        }
        else
        {
            $self->{currentIterString} = '0' if ! $self->{currentIterString};
            $self->{completeModel}->foreach(sub {
                my ($model, $path, $iter) = @_;
                if ($model->get_value($iter, 1) == $idx)
                {
                    $self->{currentIterString} = $model->get_path($iter)->to_string;
                    $self->selectIter($iter);
                    return 1;
                }
                return 0;
            });
        }
        return $idx;
    }
    
    sub showCurrent
    {
        my $self = shift;
        
        my $path = $self->{list}->get_selection->get_selected_rows;
        $self->{list}->scroll_to_cell($path) if $path;
    }

    sub changeCurrent
    {
        my ($self, $previous, $new, $idx, $wantSelect) = @_;
        my $selected = $self->getCurrentIterFromString;;
        return if ! $selected;
        my $currentIter = $self->convertIterToChildIter($selected);
        my $newValue = $self->{parent}->transformTitle($new->{$self->{titleField}});
        my $newIdx = $idx;
        my $visible = $self->{tester} ? $self->{tester}->test($new) : 1;
        
        if (!$visible)
        {
            my $nextIter;
            ($nextIter, $newIdx) = $self->getNextIter($selected);
            $self->selectIter($nextIter, 1) if $nextIter && $wantSelect;
            $self->{count}--;
        }
        $self->{model}->set($currentIter, 0, $newValue);
        # Update the isVisible field
        $self->{model}->set($currentIter, 2, $visible);

        my $iter = $self->getCurrentIter;
        $self->{currentIterString} = $self->convertIterToString($iter);
        return $newIdx;
    }
    
}

{
    package GCImageList;
    
    use File::Basename;
    use GCUtils;
    use GCStyle;
    use base "Gtk2::VBox";
    use File::Temp qw/ tempfile /;
    
    sub new
    {
        my ($proto, $parent, $columns) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new;
        bless ($self, $class);
        
        $self->{withImage} = $parent->{options}->listBgPicture;
        $self->{useOverlays} = ($parent->{options}->useOverlays) && ($parent->{model}->{collection}->{options}->{overlay}->{image});        
        $self->{preferences} = $parent->{model}->{preferences};
        $self->{coverField} = $parent->{model}->{commonFields}->{cover};
        $self->{titleField} = $parent->{model}->{commonFields}->{title};
        $self->{borrowerField} = $parent->{model}->{commonFields}->{borrower}->{name};
        $self->{orderSet} = 0;
        $self->{selectedIndexes} = {};
        $self->{previousSelectedDisplayed} = 0;
        
        $parent->{options}->listImgSkin($GCStyle::defaultList) if ! $parent->{options}->exists('listImgSkin');
        $self->{skin} = $parent->{options}->listImgSkin;
        $self->{withReflect} = ($self->{withImage} && ($self->{skin} =~ /Glass/ )) ? 1 : 0;

        $self->{preferences}->sortOrder(1)
            if ! $self->{preferences}->exists('sortOrder');
        $parent->{options}->listImgSize(2) if ! $parent->{options}->exists('listImgSize');
        $self->{size} = $parent->{options}->listImgSize;

        # Sets image width/height (for size = 2), getting value from the collection model or setting to
        # default values of 120, 160 if not specified in model file
        $self->{imgWidth} = (exists $parent->{model}->{collection}->{options}->{defaults}->{listImageWidth})
                          ? $parent->{model}->{collection}->{options}->{defaults}->{listImageWidth}
                          : 120;
        $self->{imgHeight} = (exists $parent->{model}->{collection}->{options}->{defaults}->{listImageHeight})
                           ? $parent->{model}->{collection}->{options}->{defaults}->{listImageHeight}
                           : 160;

        $self->{vboxWidth} = $self->{imgWidth} + 10;
        $self->{vboxHeight} = $self->{imgHeight} + 10;
        $self->{vboxHeight} += 20 if $self->{withImage};

        $self->{factor} = ($self->{size} == 0) ? 0.5
                        : ($self->{size} == 1) ? 0.8
                        : ($self->{size} == 3) ? 1.5
                        : ($self->{size} == 4) ? 2
                        :                        1;                        
        $self->{imgWidth} *= $self->{factor};
        $self->{imgHeight} *= $self->{factor};
        $self->{vboxWidth} = $self->{imgWidth} + (10 * $self->{factor});
        $self->{vboxHeight} = $self->{imgHeight} + (10 * $self->{factor});
        $self->{vboxHeight} += (20 * $self->{factor}) if $self->{withImage};
        $self->{vboxHeight} += (30 * $self->{factor}) if $self->{withReflect};
        $self->{pageCount} = int 5 / $self->{factor};
        
        $self->{scroll} = new Gtk2::ScrolledWindow;
        $self->{scroll}->set_policy ('automatic', 'automatic');
        $self->{scroll}->set_shadow_type('none');
        
        $self->{parent} = $parent;
        
        $self->{sortButton} = Gtk2::Button->new_from_stock('gtk-sort-descending');
        $self->{searchEntry} = new Gtk2::Entry;
        $self->{searchEntry}->signal_connect('changed' => sub {
            return if ! $self->{searchEntry}->get_text;
            $self->internalSearch;
        });
        $self->{searchEntry}->signal_connect('key-press-event' => sub {
            my ($widget, $event) = @_;
            Glib::Source->remove($self->{searchTimeOut})
                if $self->{searchTimeOut};
            return if ! $self->{searchEntry}->get_text;
            my $key = Gtk2::Gdk->keyval_name($event->keyval);
            if ($key eq 'Escape')
            {
                $self->hideSearch;
                return 1;
            }
            $self->{searchTimeOut} = Glib::Timeout->add(4000, sub {
                $self->hideSearch;
                $self->{searchTimeOut} = 0;
                return 0;
            });

            return 0;
        });
        
        $self->{list} = new Gtk2::VBox(0,0);
        $self->{tooltips} = Gtk2::Tooltips->new();
        
        $self->{columns} = $columns;
        $self->clearCache;

        # Pixbuf for lending icon
        my $lendImageFile = $ENV{GCS_SHARE_DIR}.'/overlays/lend_';
        $lendImageFile .= ($self->{size} < 1) ? 'verysmall'
                       : ($self->{size} < 2) ? 'small'
                       : ($self->{size} < 3) ? 'med'
                       : ($self->{size} < 4) ? 'large'
                       :                       'xlarge';  
        $self->{lendPixbuf} = Gtk2::Gdk::Pixbuf->new_from_file($lendImageFile.'.png');

        # Pixbuf for favourite icon
        my $favImageFile = $ENV{GCS_SHARE_DIR}.'/overlays/favourite_';
        $favImageFile .= ($self->{size} < 1) ? 'verysmall'
                       : ($self->{size} < 2) ? 'small'
                       : ($self->{size} < 3) ? 'med'
                       : ($self->{size} < 4) ? 'large'
                       :                       'xlarge';  
        $self->{favPixbuf} = Gtk2::Gdk::Pixbuf->new_from_file($favImageFile.'.png');

        if ($self->{useOverlays})
        {
            $self->{overlayImage} = $ENV{GCS_SHARE_DIR}.'/overlays/'.$parent->{model}->{collection}->{options}->{overlay}->{image};
            $self->{overlayPixbuf} = Gtk2::Gdk::Pixbuf->new_from_file($self->{overlayImage});

            $self->{overlayPaddingLeft} = $parent->{model}->{collection}->{options}->{overlay}->{paddingLeft};
            $self->{overlayPaddingRight} = $parent->{model}->{collection}->{options}->{overlay}->{paddingRight};
            $self->{overlayPaddingTop} = $parent->{model}->{collection}->{options}->{overlay}->{paddingTop};
            $self->{overlayPaddingBottom} = $parent->{model}->{collection}->{options}->{overlay}->{paddingBottom}; 
        }

        $self->{list}->set_border_width(0);
        $self->{list}->set_border_width(5) if ! $self->{withImage};

        $self->{scroll}->add_with_viewport($self->{list});

        $self->{list}->parent->signal_connect('button_press_event' => sub {
                my ($widget, $event) = @_;
                $self->{context}->popup(undef, undef, undef, undef, $event->button, $event->time)
                    if $event->button eq 3;
        });

        if ($self->{withImage})
        {
            my $bgdir = $ENV{GCS_SHARE_DIR}.'/list_bg/'.$self->{skin};

            $self->{bgPixmap} = $bgdir.'/list_bg.png';

            my $tmpPixbuf = Gtk2::Gdk::Pixbuf->new_from_file($self->{bgPixmap});
            $tmpPixbuf = GCUtils::scaleMaxPixbuf($tmpPixbuf,
                                                 $self->{vboxWidth},
                                                 $self->{vboxHeight},
                                                 1);
            (my $fh, $self->{tmpBgPixmap}) = tempfile;
            close $fh;
            if ($^O =~ /win32/i)
            {
                # It looks like Win32 version only supports JPEG pictures for background
                $tmpPixbuf->save($self->{tmpBgPixmap}, 'jpeg', quality => '100');
            }
            else
            {
                $tmpPixbuf->save($self->{tmpBgPixmap}, 'png');
            }
            GCUtils::setWidgetPixmap($self->{list}->parent, $self->{tmpBgPixmap});
            
            $self->{backgroundPixbuf} = Gtk2::Gdk::Pixbuf->new_from_file($self->{bgPixmap});
            $self->{backgroundPixbuf} = GCUtils::scaleMaxPixbuf($self->{backgroundPixbuf},
                                                                $self->{vboxWidth},
                                                                $self->{vboxHeight},
                                                                1);
            my @colors = split m/,/, $parent->{options}->listFgColor;
            ($colors[0], $colors[1], $colors[2]) = (65535, 65535, 65535) if !@colors;
            my $red   = int($colors[0] / 257);
            my $green = int($colors[1] / 257);
            my $blue   = int($colors[2] / 257);
            $self->{activeBgValue} = ($red << 16) + ($green << 8) + $blue;


            if ($self->{withReflect}) 
            {
                $self->{foregroundPixbuf} = Gtk2::Gdk::Pixbuf->new_from_file($bgdir.'/list_fg.png');
                $self->{foregroundPixbuf} = GCUtils::scaleMaxPixbuf($self->{foregroundPixbuf},
                                                                    $self->{vboxWidth},
                                                                    $self->{vboxHeight},
                                                                    1);
            }
        }
        else
        {
            my @colors = split m/,/, $parent->{options}->listBgColor;
            ($colors[0], $colors[1], $colors[2]) = (65535, 65535, 65535) if !@colors;
            $self->{inactiveBg} = new Gtk2::Gdk::Color($colors[0], $colors[1], $colors[2]);
            @colors = split m/,/, $parent->{options}->listFgColor;
            ($colors[0], $colors[1], $colors[2]) = (0, 0, 0) if !@colors;
            $self->{activeBg} = new Gtk2::Gdk::Color($colors[0], $colors[1], $colors[2]);

            $self->{list}->parent->modify_bg('normal', $self->{inactiveBg});
            $self->{list}->parent->modify_bg('active', $self->{inactiveBg});
            $self->{list}->parent->modify_bg('prelight', $self->{inactiveBg});
            $self->{list}->parent->modify_bg('selected', $self->{inactiveBg});
            $self->{list}->parent->modify_bg('insensitive', $self->{inactiveBg});
        }

        $self->pack_start($self->{sortButton},0,0,0);
        $self->pack_start($self->{scroll},1,1,0);
        $self->pack_start($self->{searchEntry},0,0,0);
        
        $self->{list}->can_focus(1);
        
        $self->{sortButton}->signal_connect('clicked' => sub {
            $self->setSortOrder(-1);
        });
        
        return $self;
    }

    sub show_all
    {
        my $self = shift;
        $self->SUPER::show_all;
        $self->{searchEntry}->hide;
    }

    sub savePreferences
    {
        my ($self, $preferences) = @_;
        return if !$self->{orderSet};
        $preferences->sortField($self->{titleField});
        $preferences->sortOrder($self->{currentOrder});
    }

    sub couldExpandAll
    {
        my $self = shift;
        
        return 0;
    }

    sub getCurrentIdx
    {
        my $self = shift;
        return $self->{displayedToIdx}->{$self->{current}};
    }

    sub getCurrentItems
    {
        my $self = shift;
        my @indexes = keys %{$self->{selectedIndexes}};
        return \@indexes;
    }

    sub DESTROY
    {
        my $self = shift;
        
        unlink $self->{tmpBgPixmap};
        $self->SUPER::DESTROY;
    }

    sub isUsingDate
    {
        my ($self) = @_;
        return 0;
    }

    sub setSortOrder
    {
        my ($self, $order) = @_;
        $order = 0 if !defined $order;
        $self->{orderSet} = 1;
        $self->{currentOrder} = ($order == -1) ? (1 - $self->{currentOrder})
                                               : $self->{preferences}->sortOrder;
        $self->remove($self->{sortButton});
        $self->{sortButton}->destroy;
        $self->{sortButton} = Gtk2::Button->new_from_stock($self->{currentOrder} ? 'gtk-sort-descending' : 'gtk-sort-ascending');
        $self->pack_start($self->{sortButton},0,0,0);
        $self->reorder_child($self->{sortButton},0);
        $self->{sortButton}->show_all;
        
        $self->{sortButton}->signal_connect('clicked' => sub {
            $self->setSortOrder(-1);
            $self->{initializing} = 0;
        });

        if ($self->{itemsArray})
        {
            if ($order == -1)
            {
                @{$self->{itemsArray}} = reverse @{$self->{itemsArray}};
            }
            else
            {
                sub compare
                {
                    return (
                            $a->{title}
                            cmp
                            $b->{title}
                           );
                }
                if ($self->{currentOrder} == 1)
                {
                    @{$self->{itemsArray}} = sort compare @{$self->{itemsArray}};
                }
                else
                {
                    @{$self->{itemsArray}} = reverse sort compare @{$self->{itemsArray}};
                }
            }
        }
        $self->refresh if ! $self->{initializing};
    }

    sub setFilter
    {
        my ($self, $filter, $items, $refresh, $splash) = @_;
        $self->{displayedNumber} = 0;
        $self->{filter} = $filter;
        
        
        my $current = $self->{current};
        $self->restorePrevious;
        foreach (@{$self->{itemsArray}})
        {
            $_->{displayed} = $filter->test($items->[$_->{idx}]);
            $self->{displayedNumber}++ if $_->{displayed};
        }
        my $newIdx = $self->getFirstVisibleIdx($current);
        my $conversionNeeded = 0;
        $conversionNeeded = 1 if ! exists $self->{boxes}->[$current];
        
        if ($refresh)
        {
            $self->refresh(1, $splash);
            $self->enhanceAllPictures;
        }
        
        $self->{initializing} = 0;
        return $self->displayedToItemsArrayIdx($newIdx)
            if $conversionNeeded;
        return $newIdx;
    }

    sub getFirstVisibleIdx
    {
        my ($self, $displayed) = @_;
        return $displayed if ! exists $self->{boxes}->[$displayed];
        my $currentIdx = $self->{boxes}->[$displayed]->{info}->{idx};
        my $info =  $self->{boxes}->[$displayed]->{info};

        return $currentIdx if (! exists $self->{boxes}->[$displayed])
                           || ($self->{boxes}->[$displayed]->{info}->{displayed});
        my $previous = -1;
        my $after = 0;
        foreach my $item(@{$self->{itemsArray}})
        {
            $after = 1 if $item->{idx} == $currentIdx;
            if ($after)
            {
                return $item->{idx} if $item->{displayed};
            }
            else
            {
                $previous = $item->{idx} if $item->{displayed};
            }
        }
        return $previous;
    }

    sub refresh
    {
        my ($self, $forceClear, $splash) = @_;

        return if $self->{columns} == 0;

        # Store current item index
        my $currentIdx = $self->{displayedToIdx}->{$self->{current}};
        $self->{boxes} = [];
        $self->{displayedToIdx} = {};
        $self->{idxToDisplayed} = {};

        $self->clearView if (! $self->{initializing}) || $forceClear;
        $self->{number} = 0;
        my $idx = 0;
        $self->{collectionDir} = dirname($self->{parent}->{options}->file);
        foreach (@{$self->{itemsArray}})
        {
            $splash->setProgressForItemsSort($idx++) if $splash;
            next if ! $_->{displayed};
            $self->addDisplayedItem($_);
        }
        delete $self->{collectionDir};
        # Determine new current displayed
        $self->{current} = $self->{idxToDisplayed}->{$currentIdx};
        $self->{list}->show_all;
    }

    sub getNbItems
    {
        my $self = shift;
        return $self->{displayedNumber};
    }

    sub clearCache
    {
        my $self = shift;
        
        if ($self->{cache})
        {
            foreach (@{$self->{cache}})
            {
                $_->{eventBox}->destroy
                    if $_->{eventBox};
            }
        }
        
        $self->{cache} = [];
    }

    sub reset
    {
        my $self = shift;
        #Restore current picture if modified
        $self->restorePrevious;
        
        $self->{itemsArray} = [];
        $self->{boxes} = [];
        $self->{number} = 0;
        $self->{count} = 0;
        $self->{displayedNumber} = 0;
        $self->{current} = 0;
        $self->{previous} = 0;
        $self->clearView;
    }

    sub clearView
    {
        my $self = shift;
        $self->{scroll}->child->remove($self->{list});
        my @children = $self->{list}->get_children;
        foreach (@children)
        {
            my @children2 = $_->get_children;
            foreach my $child(@children2)
            {
                $_->remove($child);
            }
            $self->{list}->remove($_);
        }
        $self->{rowContainers} = [];
        $self->{enhanceInformation} = [];
        
        $self->{scroll}->add_with_viewport($self->{list});
        $self->{initializing} = 1;
    }

    sub done
    {
        my ($self, $splash, $refresh) = @_;
        if ($refresh)
        {
            $self->refresh(0, $splash);
            Glib::Timeout->add(1000,
                               \&enhanceAllPictures,
                               $self);
            
            #$self->enhanceAllPictures;
        }
        $self->{initializing} = 0;
        if ($self->{columns} == 0)
        {
            $self->signal_connect('size-allocate' => sub {
                #return if !$self->{initializing};
                my $changed = 0;
                my $width = $self->{scroll}->child->allocation->width - 15;
                return if $width < 0;
                if (($self->{scroll}->get_hscrollbar->visible)
                 || ($width > (($self->{columns} + 1) * $self->{vboxWidth})))
                {
                    my $columns = int ($width / $self->{vboxWidth});
                    if ($columns)
                    {
                        return if $columns == $self->{columns};
                        $self->{columns} = $columns;
                        my $init = $self->{initializing};
                        $self->{initializing} = 1;
                        $self->refresh(1);
                        $self->enhanceAllPictures;
                        $self->{parent}->display($self->select(-1, 1))
                            if !$self->{current};
                        $self->{initializing} = $init;
                    }
                    else
                    {
                        $self->{columns} = 1;
                    }
                    #$self->{list}->show_all;
                }
            });
        }        
    }

    sub createPixbuf
    {
        my ($self, $displayedImage, $borrower, $favourite, $forceEnhancement) = @_;
        my $pixbuf = undef;
        if ($displayedImage)
        {
            eval {
                $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage);
            };
        }
        if ($@ || !$pixbuf)
        {
            $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($self->{parent}->{defaultImage});
        }

        my $width;
        my $height;
        my $boxWidth = $self->{imgWidth};
        my $boxHeight = $self->{imgHeight};

        my $overlayPaddingLeft; 
        my $overlayPaddingRight;
        my $overlayPaddingTop;
        my $overlayPaddingBottom;
        my $targetOverlayHeight;
        my $targetOverlayWidth;
        my $pixbufTempHeight;
        my $pixbufTempWidth;
        my $alpha = 1;

        if ($self->{useOverlays})
        {

            # Calculate size of list image with proportional size of overlay padding added
            $pixbufTempHeight = (($self->{overlayPaddingTop} + $self->{overlayPaddingBottom})/$self->{overlayPixbuf}->get_height + 1) * $pixbuf->get_height;
            $pixbufTempWidth = (($self->{overlayPaddingLeft} + $self->{overlayPaddingRight})/$self->{overlayPixbuf}->get_width + 1) * $pixbuf->get_width;

            # Find out target size of overlay, keeping the same ratio as the size calculated above (ie, list image + relative padding)
            my $ratio = $pixbufTempHeight / $pixbufTempWidth;
            if (($pixbufTempWidth > $boxWidth) || ($pixbufTempHeight > $boxHeight))
            {
                if (($pixbufTempWidth * $boxHeight/$pixbufTempHeight) < $boxWidth )
                {
                    $targetOverlayHeight = $boxHeight;
                    $targetOverlayWidth = int($boxHeight / $ratio);
                }
                else
                {
                    $targetOverlayHeight = int($boxWidth * $ratio);
                    $targetOverlayWidth = $boxWidth;
                }
            }
            else
            {
                # Special case when image is small enough and doesn't need to be resized
                $targetOverlayHeight = $pixbufTempHeight;
                $targetOverlayWidth = $pixbufTempWidth;
            }

            # Calculate final offset amounts for target size of overlay
            $overlayPaddingLeft = int($self->{overlayPaddingLeft} * $targetOverlayWidth / $self->{overlayPixbuf}->get_width);
            $overlayPaddingRight = int($self->{overlayPaddingRight} * $targetOverlayWidth / $self->{overlayPixbuf}->get_width);
            $overlayPaddingTop = int($self->{overlayPaddingTop} * $targetOverlayHeight / $self->{overlayPixbuf}->get_height);
            $overlayPaddingBottom = int($self->{overlayPaddingBottom} * $targetOverlayHeight / $self->{overlayPixbuf}->get_height);

            # Resize the list image to fit into the correct space
            $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf,
                                              $boxWidth - $overlayPaddingLeft - $overlayPaddingRight,
                                              $boxHeight - $overlayPaddingTop - $overlayPaddingBottom,
                                              0,
                                              $self->{initializing} && !$forceEnhancement);
        }
        else
        {
            # No overlays, simple case
            $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf,
                                              $boxWidth,
                                              $boxHeight,
                                              0,
                                              $self->{initializing} && !$forceEnhancement);
        }
        $width = $pixbuf->get_width;
        $height = $pixbuf->get_height;
        
        # Do the composition

        if ($self->{useOverlays})
        {
            if ($self->{withImage})
            {
                # Using background, so center accordingly
                my $offsetX = 5 * $self->{factor} + (($boxWidth - ($width + $overlayPaddingLeft + $overlayPaddingRight)) / 2);
                my $offsetY = 15 * $self->{factor} + ($boxHeight - ($height + $overlayPaddingTop + $overlayPaddingBottom));

                # Make an empty pixbuf to work within
                my $tempPixbuf =Gtk2::Gdk::Pixbuf->new('rgb', 1, 8,
                                                $self->{backgroundPixbuf}->get_width,
                                                $self->{backgroundPixbuf}->get_height);
                $tempPixbuf->fill(0x00000000);

                # Place cover in pixbuf
                $pixbuf->composite($tempPixbuf,
                                      $offsetX + $overlayPaddingLeft, $offsetY + $overlayPaddingTop, 
                                      $width , $height,
                                      $offsetX + $overlayPaddingLeft, $offsetY + $overlayPaddingTop, 
                                      1, 1, 
                                      'nearest', 255);
                $pixbuf = $tempPixbuf;
 
                # Composite overlay picture
                $self->{overlayPixbuf}->composite($pixbuf,
                                                  $offsetX, $offsetY,
                                                  $width + $overlayPaddingLeft + $overlayPaddingRight,
                                                  $height + $overlayPaddingTop + $overlayPaddingBottom,
                                                  $offsetX, $offsetY,
                                                  ($width + $overlayPaddingLeft + $overlayPaddingRight) / $self->{overlayPixbuf}->get_width,
                                                  ($height + $overlayPaddingTop + $overlayPaddingBottom) / $self->{overlayPixbuf}->get_height, 
                                                  'nearest', 255);

                # Overlay borrower image if required
                if ($borrower && ($borrower ne 'none'))
                {
                   # De-saturate borrowed items
                   $pixbuf->saturate_and_pixelate($pixbuf, .1, 0);
                   $self->{lendPixbuf}->composite($pixbuf,
                                                  $pixbuf->get_width - $self->{lendPixbuf}->get_width - $offsetX,
                                                  $offsetY + $height + $overlayPaddingTop + $overlayPaddingBottom - $self->{lendPixbuf}->get_height,
                                                  $self->{lendPixbuf}->get_width, $self->{lendPixbuf}->get_height,
                                                  $pixbuf->get_width - $self->{lendPixbuf}->get_width - $offsetX,
                                                  $offsetY + $height + $overlayPaddingTop + $overlayPaddingBottom - $self->{lendPixbuf}->get_height,
                                                  1, 1, 
                                                  'nearest', 255);
                }

                # Overlay favourite image if required
                if ($favourite)
                {
                   $self->{favPixbuf}->composite($pixbuf,
                                                  $pixbuf->get_width - $self->{favPixbuf}->get_width - $offsetX,
                                                  $offsetY,
                                                  $self->{favPixbuf}->get_width, $self->{favPixbuf}->get_height,
                                                  $pixbuf->get_width - $self->{favPixbuf}->get_width - $offsetX,
                                                  $offsetY,
                                                  1, 1, 
                                                  'nearest', 255);
                }

                # Create and apply reflection if required
                if ($self->{withReflect})
                {
                    my $reflect;
                    $reflect = $pixbuf->flip(0);
                    $reflect->composite(
                        $pixbuf,
                        0, 2 * ($offsetY + $height + $overlayPaddingTop + $overlayPaddingBottom) - $pixbuf->get_height,
                        $pixbuf->get_width,
                        2 * ($pixbuf->get_height - $height - $offsetY - $overlayPaddingTop - $overlayPaddingBottom) - (10 * $self->{factor}),
                        0, 2 * ($offsetY + $height + $overlayPaddingTop + $overlayPaddingBottom) - $pixbuf->get_height,
                        1, 1,
                        'nearest', 100
                    );

                    # Apply foreground fading
                    $self->{foregroundPixbuf}->composite(
                        $pixbuf,
                        0, 0,
                        $pixbuf->get_width, $pixbuf->get_height,
                        0, 0,
                        1, 1,
                        'nearest', 255
                    );
                }

                # Heft created pixbuf onto background
                my $bgPixbuf = $self->{backgroundPixbuf}->copy;
                $pixbuf->composite($bgPixbuf,
                                      0,0,
                                      $pixbuf->get_width , $pixbuf->get_height,
                                      0,0,
                                      1, 1, 
                                      'nearest', 255);
                $pixbuf = $bgPixbuf;

            }
            else
            {
                # Not using background, so we need to make an empty pixbuf which is right size for overlay first
                my $tempPixbuf =Gtk2::Gdk::Pixbuf->new('rgb', 1, 8,
                                                $width + $overlayPaddingLeft + $overlayPaddingRight,
                                                $height + $overlayPaddingTop + $overlayPaddingBottom);
                $tempPixbuf->fill(0x00000000);
                            
                # Now, place list image inside empty pixbuf
                $pixbuf->composite($tempPixbuf,
                                      $overlayPaddingLeft, $overlayPaddingTop, 
                                      $width , $height,
                                      $overlayPaddingLeft, $overlayPaddingTop, 
                                      1, 1, 
                                      'nearest', 255 * $alpha);
                $pixbuf = $tempPixbuf;

                # Place overlay on top of pixbuf
                $self->{overlayPixbuf}->composite($pixbuf,
                                                  0, 0,
                                                  $width + $overlayPaddingLeft + $overlayPaddingRight,
                                                  $height + $overlayPaddingTop + $overlayPaddingBottom,
                                                  0, 0,
                                                  ($width + $overlayPaddingLeft + $overlayPaddingRight) / $self->{overlayPixbuf}->get_width,
                                                  ($height + $overlayPaddingTop + $overlayPaddingBottom) / $self->{overlayPixbuf}->get_height, 
                                                  'nearest', 255 * $alpha);

                # Overlay borrower image if required
                if ($borrower && ($borrower ne 'none'))
                {
                   # De-saturate borrowed items
                   $pixbuf->saturate_and_pixelate($pixbuf, .1, 0);

                   $self->{lendPixbuf}->composite($pixbuf,
                                                  $pixbuf->get_width - $self->{lendPixbuf}->get_width,
                                                  $pixbuf->get_height - $self->{lendPixbuf}->get_height,
                                                  $self->{lendPixbuf}->get_width, $self->{lendPixbuf}->get_height,
                                                  $pixbuf->get_width - $self->{lendPixbuf}->get_width,
                                                  $pixbuf->get_height - $self->{lendPixbuf}->get_height,
                                                  1, 1, 
                                                  'nearest', 255);
                }

                # Overlay favourite image if required
                if ($favourite)
                {
                   $self->{favPixbuf}->composite($pixbuf,
                                                  $pixbuf->get_width - $self->{favPixbuf}->get_width,
                                                  0,
                                                  $self->{favPixbuf}->get_width, $self->{favPixbuf}->get_height,
                                                  $pixbuf->get_width - $self->{favPixbuf}->get_width,
                                                  0,
                                                  1, 1, 
                                                  'nearest', 255);
                }

            }
        }
        else
        {
            # No overlays, nice and simple

            # Overlay borrower image if required
            if ($borrower && ($borrower ne 'none'))
            {
               # De-saturate borrowed items
               $pixbuf->saturate_and_pixelate($pixbuf, .1, 0);
               $self->{lendPixbuf}->composite($pixbuf,
                                              $width - $self->{lendPixbuf}->get_width - $self->{factor},
                                              $height - $self->{lendPixbuf}->get_height - $self->{factor},
                                              $self->{lendPixbuf}->get_width, $self->{lendPixbuf}->get_height,
                                              $width - $self->{lendPixbuf}->get_width - $self->{factor},
                                              $height - $self->{lendPixbuf}->get_height - $self->{factor},
                                              1, 1, 
                                              'nearest', 255);
            }

            # Overlay favourite image if required
            if ($favourite)
            {
               $self->{favPixbuf}->composite($pixbuf,
                                              $width - $self->{favPixbuf}->get_width - $self->{factor},
                                              $self->{factor},
                                              $self->{favPixbuf}->get_width, $self->{favPixbuf}->get_height,
                                              $width - $self->{favPixbuf}->get_width - $self->{factor},
                                              $self->{factor},
                                              1, 1, 
                                              'nearest', 255);
            }

            my $reflect;
            $reflect = $pixbuf->flip(0)
                if $self->{withReflect};

            my $offsetX = 5 * $self->{factor} + (($boxWidth - $width) / 2);
            my $offsetY = 15 * $self->{factor} + ($boxHeight - $height);
            if ($self->{withImage})
            {
                my $bgPixbuf = $self->{backgroundPixbuf}->copy;
                $pixbuf->composite($bgPixbuf,
                                   $offsetX, $offsetY, 
                                   $width, $height,
                                   $offsetX, $offsetY,
                                   1, 1, 
                                   'nearest', 255);
                $pixbuf = $bgPixbuf;
            }

            if ($self->{withReflect})
            {
                $reflect->composite(
                    $pixbuf,
                    $offsetX, $height + $offsetY,
                    $width, $pixbuf->get_height - $height - $offsetY - (10 * $self->{factor}),
                    $offsetX, $height + $offsetY,
                    1, 1,
                    'nearest', 100
                );

                # Apply foreground fading
                $self->{foregroundPixbuf}->composite(
                    $pixbuf,
                    0, 0,
                    $pixbuf->get_width, $pixbuf->get_height,
                    0, 0,
                    1, 1,
                    'nearest', 255
                );
           }
      }


        return $pixbuf;
    }

    sub enhanceAllPictures
    {
        my $self = shift;
        $self->{list}->show_all;
        my $i = 1;
        foreach (@{$self->{enhanceInformation}})
        {
            Glib::Timeout->add(0 + ($i * 50),
                               \&enhancePicture,
                               $_);
              
            $i++;
        }
        $self->{enhanced} = 1;
        return 0;
    }

    sub enhancePicture
    {
        my $data = shift;
        my $eventBox = $data->[1];
        return if ! $eventBox->child;
        my $self = $data->[0];
        my $pixbuf = $self->createPixbuf($data->[2], $data->[3], $data->[4], 1);
        $eventBox->child->set_from_pixbuf($pixbuf);
        $self->{tooltips}->set_tip($eventBox, $data->[5], '');

        #if ($self->{current} == $data->[4])
        if ($eventBox->{selected})
        {
            $self->clearPrevious;
            $self->{previousPixbufs}->{$eventBox->{idx}} = $pixbuf;
            $self->select($self->{displayedToIdx}->{$self->{current}}, 1, 1);
        }
        return 0;
    }
    
    sub createEventBox
    {
        my ($self, $info) = @_;
        my $eventBox = new Gtk2::EventBox;
        $eventBox->can_focus(1);
        # We store the index of the displayed data
        my $image = new Gtk2::Image;

        my $displayedImage = GCUtils::getDisplayedImage($info->{picture},
                                                        undef,
                                                        $self->{parent}->{options}->file,
                                                        $self->{collectionDir});

        my $pixbuf = $self->createPixbuf($displayedImage, $info->{borrower}, $info->{favourite});

        #$self->{tooltips}->set_tip($eventBox, $info->{title}, '');

        if (! $self->{withImage})
        {
            $eventBox->modify_bg('normal', $self->{inactiveBg});
        }

        $image->set_from_pixbuf($pixbuf);
        # ONLY FOR DEBUG
        $eventBox->{picture} = $displayedImage;
        $eventBox->add($image);
        $eventBox->set_size_request($self->{vboxWidth},$self->{vboxHeight});
        if ($self->{initializing})
        {
            push @{$self->{enhanceInformation}}, [$self, $eventBox, $displayedImage, $info->{borrower}, $info->{favourite}, $info->{title}];
        }
        else
        {
            $eventBox->show_all;
        }
        
        return $eventBox;
    }

    sub getFromCache
    {
        my ($self, $info) = @_;
        if (! $self->{cache}->[$info->{idx}])
        {
            my $item = {};
            $item->{eventBox} = $self->createEventBox($info);
            $item->{keyHandler} = 0;
            $item->{mouseHandler} = 0;
        
            $self->{cache}->[$info->{idx}] = $item;
        }
        return $self->{cache}->[$info->{idx}];
    }
    
    sub findPlace
    {
        my ($self, $item, $title) = @_;
        my $refTitle = $title || $item->{title};
        $refTitle = uc($refTitle);
        # First search where it should be inserted
        my $place = 0;
        my $itemsIdx = 0;
        if ($self->{currentOrder} == 1)
        {
            foreach my $followingItem(@{$self->{itemsArray}})
            {
                my $testTitle = uc($followingItem->{title});
                my $cmp = ($testTitle gt $refTitle);
                $itemsIdx++ if ! $cmp;
                
                next if !$followingItem->{displayed};
                last if $cmp;
                $place++;
            }
        }
        else
        {
            foreach my $followingItem(@{$self->{itemsArray}})
            {
                my $cmp = (uc($followingItem->{title})
                           lt
                           $refTitle);
                $itemsIdx++ if ! $cmp;
                next if !$followingItem->{displayed};
                last if $cmp;
                $place++;
            }
        }
        return ($place, $itemsIdx) if wantarray;
        return $place;
    }

    sub addItem
    {
        my ($self, $info, $immediate) = @_;

        my $item = {
                     idx => $self->{count},
                     title => $self->{parent}->transformTitle($info->{$self->{titleField}}),
                     picture => $info->{$self->{coverField}},
                     borrower => $info->{$self->{borrowerField}},
                     favourite => $info->{favourite},
                     displayed => 1,
                   };

        if ($immediate)
        {
            # First search where it should be inserted
            $self->restorePrevious;
            # To force the selection
            $self->{current} = -1;
            my ($place, $itemsArrayIdx) = $self->findPlace($item);

            # Prepare the conversions displayed <-> index
            $self->{displayedToIdx}->{$place} = $self->{count};
            $self->{idxToDisplayed}->{$self->{count}} = $place;                
            # Then we insert it at correct position
            $self->addDisplayedItem($item, $place);
            splice @{$self->{itemsArray}}, $itemsArrayIdx, 0, $item;
        }
        else
        {
            # Here we know it will be sorted after
            push @{$self->{itemsArray}}, $item;
        }
        
        $self->{count}++;
        $self->{displayedNumber}++;
    }

    # Params:
    #         $info:  Information already formated for this class
    #         $place: Optional value to indicate where it should be inserted
    sub addDisplayedItem
    {
        # info is an iternal info generated 
        my ($self, $info, $place) = @_;
        return if ! $self->{columns};
        my $item = $self->getFromCache($info);
        my $eventBox = $item->{eventBox};
        my $i = $info->{idx};
        if (!defined $place)
        {
            $self->{displayedToIdx}->{$self->{number}} = $i;
            $self->{idxToDisplayed}->{$i} = $self->{number};
        }
        # We store it here to be sure we have the correct one
        $eventBox->{idx} = $i;
        $eventBox->{info} = $info;
        
        $eventBox->signal_handler_disconnect($item->{mouseHandler})
            if $item->{mouseHandler};
        $item->{mouseHandler} = $eventBox->signal_connect('button_press_event' => sub {
            my ($widget, $event) = @_;
            if ($event->type ne '2button-press')
            {
                my $state = $event->get_state;
                my $keepPrevious = 0;
                if ($state =~ /control-mask/)
                {
                    $self->select($widget->{idx}, 0, 1);
                }
                elsif ($state =~ /shift-mask/)
                {
                    $self->restorePrevious;
                    my ($min, $max);
                    if ($self->{previousSelectedDisplayed} > $self->{idxToDisplayed}->{$widget->{idx}})
                    {
                        $min = $self->{idxToDisplayed}->{$widget->{idx}};
                        $max = $self->{previousSelectedDisplayed};
                    }
                    else
                    {
                        $min = $self->{previousSelectedDisplayed};
                        $max = $self->{idxToDisplayed}->{$widget->{idx}};
                    }
                    foreach my $displayed($min..$max)
                    {
                        $self->select($self->{displayedToIdx}->{$displayed}, 0, 1);
                    }
                }
                else
                {
                    $self->select($widget->{idx});
                }
                $self->{previousSelectedDisplayed} = $self->{idxToDisplayed}->{$widget->{idx}};
        
                #$self->{parent}->display($widget->{idx}) unless $event->type eq '2button-press';
                $self->{parent}->display(keys %{$self->{selectedIndexes}});
            }
            
            $self->{parent}->displayInWindow if $event->type eq '2button-press';
            $self->{context}->popup(undef, undef, undef, undef, $event->button, $event->time)
                if $event->button eq 3;
            $widget->grab_focus;
        });

        $eventBox->signal_handler_disconnect($item->{keyHandler})
            if $item->{keyHandler};

        $item->{keyHandler} = $eventBox->signal_connect('key-press-event' => sub {
            my ($widget, $event) = @_;
            my $displayed = $self->{idxToDisplayed}->{$widget->{idx}};
            my $key = Gtk2::Gdk->keyval_name($event->keyval);
            if ($key eq 'Delete')
            {
                $self->{parent}->deleteCurrentItem;
                return 1;
            }
            if (($key eq 'Return') || ($key eq 'space'))
            {
                $self->{parent}->displayInWindow;
                return 1;
            }
            my $unicode = Gtk2::Gdk->keyval_to_unicode($event->keyval);
            if ($unicode)
            {
                $self->showSearch(pack('U',$unicode));
                $self->{searchTimeOut} = Glib::Timeout->add(4000, sub {
                    $self->hideSearch;
                    $self->{searchTimeOut} = 0;
                    return 0;
                });
            }
            else
            {
                ($key eq 'Right')      ? $displayed++ :
                ($key eq 'Left')       ? $displayed-- :
                ($key eq 'Down')       ? $displayed += $self->{columns} : 
                ($key eq 'Up')         ? $displayed -= $self->{columns} :
                ($key eq 'Page_Down')  ? $displayed += ($self->{pageCount} * $self->{columns}):
                ($key eq 'Page_Up')    ? $displayed -= ($self->{pageCount} * $self->{columns}):
                ($key eq 'Home')       ? $displayed = 0 :
                ($key eq 'End')        ? $displayed = $self->{displayedNumber} - 1 :
                                         return 1;

                return 1 if ($displayed < 0) || ($displayed >= scalar @{$self->{boxes}});
                my $column = $displayed % $self->{columns};                
                my $valueIdx = $self->{displayedToIdx}->{$displayed};
#                my $keepPrevious = 0;
                my $state = $event->get_state;
                if ($state =~ /control-mask/)
                {
                    $self->select($valueIdx, 0, 1);
                    delete $self->{previousSelectedDisplayed};
                }
                elsif ($state =~ /shift-mask/)
                {
                    $self->{previousSelectedDisplayed} = $self->{idxToDisplayed}->{$widget->{idx}}
                        if !exists $self->{previousSelectedDisplayed};
                    $self->restorePrevious;
                    my ($min, $max);
                    if ($self->{previousSelectedDisplayed} > $displayed)
                    {
                        $min = $displayed;
                        $max = $self->{previousSelectedDisplayed};
                    }
                    else
                    {
                        $min = $self->{previousSelectedDisplayed};
                        $max = $displayed;
                    }
                    foreach my $disp($min..$max)
                    {
                        $self->select($self->{displayedToIdx}->{$disp}, 0, 1);
                    }
                }
                else
                {
                    $self->select($valueIdx);
                    delete $self->{previousSelectedDisplayed};
                }
                
                $self->{parent}->display($valueIdx);
                $self->{boxes}->[$displayed]->grab_focus;
                $self->showCurrent unless (($key eq 'Left')  && ($column != ($self->{columns} - 1)))
                                       || (($key eq 'Right') && ($column != 0));
            }
            return 1;
            
        });

        if (($self->{number} % $self->{columns}) == 0)
        {
            #New row begin
            $self->{currentRow} = new Gtk2::HBox(0,0);
            push @{$self->{rowContainers}}, $self->{currentRow};
            $self->{list}->pack_start($self->{currentRow},0,0,0);
            $self->{currentRow}->show_all if ! $self->{initializing};
        }
        
        if (defined($place))
        {
            # Get the row and col where it should be inserted
            my $itemLine = int $place / $self->{columns};
            my $itemCol = $place % $self->{columns};
            # Insert it at correct place
            $self->{rowContainers}->[$itemLine]->pack_start($eventBox,0,0,0);
            $self->{rowContainers}->[$itemLine]->reorder_child($eventBox, $itemCol);
            $eventBox->show_all;
            $self->shiftItems($place, 1);
            splice @{$self->{boxes}}, $place, 0, $eventBox;
            $self->initConversionTables;            
        }
        else
        {
            $self->{currentRow}->pack_start($eventBox,0,0,0);
            $self->{idxToDisplayed}->{$i} = $self->{number};
            push @{$self->{boxes}}, $eventBox;
        }

        $self->{number}++;
    }
    
    sub grab_focus
    {
        my $self = shift;
        $self->SUPER::grab_focus;
        $self->{boxes}->[$self->{current}]->grab_focus;
    }

    sub displayedToItemsArrayIdx
    {
        my ($self, $displayed) = @_;
        return 0 if ! exists $self->{boxes}->[$displayed];
        return $self->{boxes}->[$displayed]->{info}->{idx};
    }

    sub shiftItems
    {
        my ($self, $place, $direction, $maxPlace) = @_;
        my $idx = $self->{displayedToIdx}->{$place};
        my $itemLine = int $place / $self->{columns};
        my $itemCol = $place % $self->{columns};
        # Did we already remove or add the item ?
        my $alreadyChanged = ($direction < 0) || (defined $maxPlace);
        # Useful to always have the same comparison a few lines below
        # Should be >= for $direction == 1
        # This difference is because we didn't added it yet while it has
        # already been removed in the other direction
        #$itemCol-- if ! (defined $maxPlace);
        $itemCol++ if ($direction < 0);
        # Same here
        $idx-- if $alreadyChanged;
        my $newDisplayed = 0;
        my $currentLine = 0;
        my $currentCol;
        my $shifting = 0;
        # Limit indicates which value for column should make use take action
        # For backward, it's the 1st one. For forward, the last one
        my $limit = 0;
        $limit = ($self->{columns} - 1) if $direction > 0;
        foreach (@{$self->{itemsArray}})
        {
            if (!$_->{displayed})
            {
                $_->{idx} += $direction if ((!defined $maxPlace) && ($_->{idx} > $idx));
                next;
            }
            $currentLine = int $newDisplayed / $self->{columns};
            $currentCol = $newDisplayed % $self->{columns};
            $shifting = $direction if (!$shifting)
                                   && (
                                       ($currentLine >  $itemLine)
                                    || (($currentLine == $itemLine)
                                     && ($currentCol  >=  $itemCol))
                                   );
            $shifting = 0 if (defined $maxPlace) && ($newDisplayed > $maxPlace);
            # When using maxPlace, we are only moving in view
            if ((!defined $maxPlace) && ($_->{idx} > $idx))
            {
                $_->{idx} += $direction;
                $self->{cache}->[$_->{idx}]->{eventBox}->{idx} = $_->{idx}
                    if $_->{idx} > 0;
            }
            if ($shifting)
            {
                # Is this the first/last one in the line?
                if (($currentCol) == $limit)
                {
                    $self->{rowContainers}->[$currentLine]->remove(
                        $self->{cache}->[$_->{idx}]->{eventBox}
                    );
                    $self->{rowContainers}->[$currentLine + $direction]->pack_start(
                        $self->{cache}->[$_->{idx}]->{eventBox},
                        0,0,0
                    );
                    # We can't directly insert on the beginning.
                    # So we need a little adjustement here
                    if ($direction > 0)
                    {
                        $self->{rowContainers}->[$currentLine + $direction]->reorder_child(
                            $self->{cache}->[$_->{idx}]->{eventBox},
                            0
                        );
                    }
                }
            }
            $newDisplayed++;
        }
    }
    
    sub initConversionTables
    {
        my $self = shift;
        my $displayed = 0;
        foreach (@{$self->{boxes}})
        {
            $self->{displayedToIdx}->{$displayed} = $_->{info}->{idx};
            $self->{idxToDisplayed}->{$_->{info}->{idx}} = $displayed;
            $_->{idx} = $_->{info}->{idx};
            $displayed++;
        }
    }
    
    sub removeItem
    {
        my ($self, $idx) = @_;
        $self->{count}--;
        $self->{displayedNumber}--;
        my $displayed = $self->{idxToDisplayed}->{$idx};
        my $itemLine = int $displayed / $self->{columns};
        #my $itemCol = $displayed % $self->{columns};
        $self->{rowContainers}->[$itemLine]->remove(
            $self->{cache}->[$idx]->{eventBox}
        );

        # Remove event box from cache

        my $itemsArrayIdx = $self->displayedToItemsArrayIdx($displayed);

        $self->{cache}->[$idx]->{eventBox}->destroy;
        $self->{cache}->[$idx]->{eventBox} = 0;

        splice @{$self->{cache}}, $idx, 1;

        splice @{$self->{boxes}}, $self->{idxToDisplayed}->{$idx}, 1;

        $self->shiftItems($displayed, -1);
        $self->initConversionTables;

        splice @{$self->{itemsArray}}, $itemsArrayIdx, 1;
        my $next = $self->{displayedToIdx}->{$displayed};
        if ($displayed >= (scalar(@{$self->{boxes}})))
        {
            $next = $self->{displayedToIdx}->{--$displayed}
        }
        $self->{current} = $displayed;
        #$self->select($next, 1);
        
        my $last = scalar @{$self->{itemsArray}};
        delete $self->{idxToDisplayed}->{$last};
        delete $self->{displayedToIdx}->{$last};

        $self->{number}--;

        return $next;
    }

    sub removeCurrentItems
    {
        my ($self) = @_;
        my @indexes = sort {$a <=> $b} keys %{$self->{selectedIndexes}};
        my $nbRemoved = 0;
        $self->restorePrevious;
        my $next;
        foreach my $idx(@indexes)
        {
            $next = $self->removeItem($idx - $nbRemoved);
            $nbRemoved++;
        }
        $self->{selectedIndexes} = {};
        $self->select($next, 1);

        return $next;
    }

    sub restoreItem
    {
        my ($self, $idx) = @_;
        my $previous = $self->{idxToDisplayed}->{$idx};
        return if $previous == -1;
        $self->{boxes}->[$previous]->modify_bg('normal', $self->{inactiveBg})
            if (! $self->{withImage}) && $self->{boxes}->[$previous];
        $self->{boxes}->[$previous]->child->set_from_pixbuf($self->{previousPixbufs}->{$idx})
            if $self->{previousPixbufs}->{$idx} && $self->{boxes}->[$previous]->child;
        $self->{boxes}->[$previous]->{selected} = 0;
        delete $self->{selectedIndexes}->{$idx};
        
    }

    sub restorePrevious
    {
        my $self = shift;
        foreach my $idx(keys %{$self->{selectedIndexes}})
        {
            $self->restoreItem($idx);
        }
        $self->clearPrevious;
    }

    sub clearPrevious
    {
        my $self = shift;
        return if ! $self->{previousPixbufs};
        #$self->{previousPixbuf}->destroy;
        $self->{previousPixbufs} = {};
    }

    sub selectAll
    {
        my $self = shift;

        $self->restorePrevious;
        $self->select($self->{displayedToIdx}->{0}, 1, 0);
        foreach my $displayed(1..scalar(@{$self->{boxes}}) - 1)
        {
            $self->select($self->{displayedToIdx}->{$displayed}, 0, 1);
        }
        $self->{parent}->display(keys %{$self->{selectedIndexes}});
    }

    sub select
    {
        my ($self, $idx, $init, $keepPrevious) = @_;
        
        $idx = $self->{displayedToIdx}->{0} if $idx == -1;
        my $displayed = $self->{idxToDisplayed}->{$idx};
        my @boxes = @{$self->{boxes}};
        return $displayed if ! scalar(@boxes);
        my $alreadySelected = 0;
        $alreadySelected = $boxes[$displayed]->{selected}
            if exists $boxes[$displayed];
        my $nbSelected = scalar keys %{$self->{selectedIndexes}};

        return $displayed if $alreadySelected && ($nbSelected < 2) && (!$init);
        if ($keepPrevious)
        {
            if (($alreadySelected) && ($nbSelected > 1))
            {
                $self->restoreItem($idx);
                return;
            }
            $self->{selectedIndexes}->{$idx} = 1;
        }
        else
        {
            $self->restorePrevious;
            $self->{selectedIndexes} = {$idx => 1};
        }
        $self->{current} = $displayed;
        if (! $self->{withImage})
        {
            $boxes[$displayed]->modify_bg('normal', $self->{activeBg});
        
        }
        my $pixbuf = $boxes[$displayed]->child->get_pixbuf
            if exists $boxes[$displayed];
        $self->clearPrevious unless $keepPrevious;
        $self->{previousPixbufs}->{$idx} = $pixbuf->copy;
        $pixbuf->saturate_and_pixelate($pixbuf, 1.8, 0);
        $pixbuf = $pixbuf->composite_color_simple ($pixbuf->get_width, $pixbuf->get_height, 'nearest',220, 128, $self->{activeBgValue}, $self->{activeBgValue});
        $boxes[$displayed]->child->set_from_pixbuf($pixbuf);
        $boxes[$displayed]->{selected} = 1;
        $self->grab_focus;
        
        return $idx;        
    }
    
    sub showCurrent
    {
        my $self = shift;
        return if ! $self->{columns};
        if ($self->{initializing})
        {
            Glib::Timeout->add(100 ,\&showCurrent, $self);
            return;
        }
 
        my $adj = $self->{scroll}->get_vadjustment;
        my $totalRows = int $self->{number} / $self->{columns};
        my $row = (int $self->{current} / $self->{columns}) - 1;

        $adj->set_value((($adj->upper - $adj->lower) / $totalRows) * $row) if $totalRows > 0;
        return 0;
    }

    sub changeItem
    {
        my ($self, $idx, $previous, $new, $withSelect) = @_;
        return $self->changeCurrent($previous, $new, $idx, 0);
    }

    sub changeCurrent
    {
        my ($self, $previous, $new, $idx, $wantSelect) = @_;
        my $forceSelect = 0;
        #To ease comparison, do some modifications.
        #empty borrower is equivalent to 'none'.
        $previous->{$self->{borrowerField}} = 'none' if $previous->{$self->{borrowerField}} eq '';
        $new->{$self->{borrowerField}} = 'none' if $new->{$self->{borrowerField}} eq '';
        my $previousDisplayed = $self->{idxToDisplayed}->{$idx};
        my $newDisplayed = $previousDisplayed;
        if ($new->{$self->{titleField}} ne $previous->{$self->{titleField}})
        {
            # Adjust title
            my $newTitle = $self->{parent}->transformTitle($new->{$self->{titleField}});
            $self->{boxes}->[$previousDisplayed]->{info}->{title} = $newTitle;
            $self->{tooltips}->set_tip($self->{boxes}->[$previousDisplayed], $newTitle, '');
            my $newItemsArrayIdx;
            ($newDisplayed, $newItemsArrayIdx) = $self->findPlace(undef, $newTitle);
            # We adjust the index as we'll remove an item
            $newDisplayed-- if $newDisplayed > $previousDisplayed;
            if ($previousDisplayed != $newDisplayed)
            {
                #$self->restorePrevious;
                my $itemPreviousLine = int $previousDisplayed / $self->{columns};
                my $itemNewLine = int $newDisplayed / $self->{columns};
                my $itemNewCol = $newDisplayed % $self->{columns};
                my ($direction, $origin, $limit);
                if ($previousDisplayed > $newDisplayed)
                {
                    $direction = 1;
                    $origin = $newDisplayed;
                    $limit = $previousDisplayed - 1;
                }
                else
                {
                    $direction = -1;
                    $origin = $previousDisplayed;
                    $limit = $newDisplayed;
                    $itemNewCol++ if ($itemNewLine > $itemPreviousLine) && ($itemNewCol != 0)
                }
                my $box = $self->{cache}->[$idx]->{eventBox};
                my $previousItemsArrayIdx = $self->displayedToItemsArrayIdx($previousDisplayed);
                #my $newItemsArrayIdx = $self->displayedToItemsArrayIdx($newDisplayed);
                $self->{rowContainers}->[$itemPreviousLine]->remove($box);
                splice @{$self->{boxes}}, $previousDisplayed, 1;
                $self->{rowContainers}->[$itemNewLine]->pack_start($box,0,0,0);
                $self->{rowContainers}->[$itemNewLine]->reorder_child($box, $itemNewCol);

                $self->shiftItems($origin, $direction, $limit);
                my $item = splice @{$self->{itemsArray}}, $previousItemsArrayIdx, 1;
                $newItemsArrayIdx-- if $previousItemsArrayIdx < $newItemsArrayIdx;
                splice @{$self->{itemsArray}}, $newItemsArrayIdx, 0, $item;
                splice @{$self->{boxes}}, $newDisplayed, 0, $box;
                $self->initConversionTables;
            }
        }

        if (($previous->{$self->{coverField}} ne $new->{$self->{coverField}})
         || ($previous->{$self->{borrowerField}} ne $new->{$self->{borrowerField}})
         || ($previous->{favourite} ne $new->{favourite}))
        {
            my ($image, $borrower, $favourite) = ($new->{$self->{coverField}}, $new->{$self->{borrowerField}}, $new->{favourite});
            my @boxes = @{$self->{boxes}};
            my $displayedImage = GCUtils::getDisplayedImage($image,
                                                            undef,
                                                            $self->{parent}->{options}->file);

            my $pixbuf = $self->createPixbuf($displayedImage, $borrower, $favourite);
            $self->{previousPixbufs}->{$idx} = $pixbuf->copy;
            $boxes[$newDisplayed]->child->set_from_pixbuf($pixbuf);
            $forceSelect = 1;
            $wantSelect = 1 if $wantSelect ne '';
        }
        if ($self->{filter})
        {
            # Test visibility
            my $visible = $self->{filter}->test($new);
            if (! $visible)
            {
                $self->{displayedNumber}--;
                $self->restorePrevious if $wantSelect;
                my $itemLine = int $newDisplayed / $self->{columns};
                $self->{rowContainers}->[$itemLine]->remove(
                                                            $self->{cache}->[$idx]->{eventBox}
                                                            );
                my $info = $self->{boxes}->[$newDisplayed]->{info};
                splice @{$self->{boxes}}, $newDisplayed, 1;
                $self->shiftItems($newDisplayed, -1, scalar @{$self->{boxes}});
                $self->initConversionTables;
                $info->{displayed} = $visible;
                $idx = $self->getFirstVisibleIdx($newDisplayed);
                $wantSelect = 0 if ! scalar @{$self->{boxes}}
            }
        }
        $self->select($idx, $forceSelect) if $wantSelect;
        return $idx;
    }

    sub showSearch
    {
        my ($self, $char) = @_;
        $self->{searchEntry}->set_text($char);
        $self->{searchEntry}->show_all;
        $self->activateSearch;
    }

    sub activateSearch
    {
        my ($self) = @_;
        $self->{searchEntry}->grab_focus;
        $self->{searchEntry}->select_region(length($self->{searchEntry}->get_text), -1);
    }

    sub hideSearch
    {
        my $self = shift;        
        $self->{searchEntry}->set_text('');
        $self->{searchEntry}->hide;
        $self->grab_focus;
        $self->{previousSearch} = '';
    }

    sub internalSearch
    {
        my $self = shift;
        
        my $query = $self->{searchEntry}->get_text;
        return if !$query;
        my $newDisplayed = -1;

        my $current = 0;
        my $length = length($query);
        if ($self->{currentOrder})
        {
            if (($length > 1) && ($length > length($self->{previousSearch})))
            {
                $current = $self->{idxToDisplayed}->{$self->{itemsArray}->[$self->{current}]->{idx}};            
            }
            foreach(@{$self->{itemsArray}}[$current..$self->{count} - 1])
            {
                next if !$_->{displayed};
                if ($_->{title} gt $query)
                {
                    $newDisplayed = $self->{idxToDisplayed}->{$_->{idx}};
                    last;
                }
            }
        }
        else
        {
#            if (length($query) > 1)
#            {
#                $current = $self->{idxToDisplayed}->{$self->{itemsArray}->[$self->{current}]->{idx}};            
#            }
            foreach(@{$self->{itemsArray}}[$current..$self->{count} - 1])
            {
                next if !$_->{displayed};
                if (($_->{title} =~ m/^\Q$query\E/i) || ($_->{title} lt $query))
                {
                    $newDisplayed = $self->{idxToDisplayed}->{$_->{idx}};
                    last;
                }
            }
        }

        if ($newDisplayed != -1)
        {
            my $valueIdx = $self->{displayedToIdx}->{$newDisplayed};
            $self->select($valueIdx);
            $self->{parent}->display($valueIdx);
            $self->{boxes}->[$newDisplayed]->grab_focus;
            $self->showCurrent;
            $self->activateSearch;
        }
        $self->{previousSearch} = $query;
    }

}

{
    package GCDetailedList;
    
    use File::Basename;
    use base 'GCBaseTextList';

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

        $self->{multi} = 1;
        $self->{orderSet} = 0;
        $self->{groupItems} = 0;
        $self->{isUsingDate} = 0;
        $self->{titleField} = $parent->{model}->{commonFields}->{title};
        
        $self->{imgWidth} = 60;
        $self->{imgHeight} = 80;
        
        # Setting default options if they don't exist
        $parent->{options}->listImgSize(2) if ! $parent->{options}->exists('listImgSize');
        $self->{preferences}->details($self->{titleField})
            if ! $self->{preferences}->details;
        $self->{preferences}->groupedFirst(1)
            if ! $self->{preferences}->exists('groupedFirst');
        $self->{preferences}->addCount(0)
            if ! $self->{preferences}->exists('addCount');

        # Image size
        my $size = $parent->{options}->listImgSize;
        $self->{factor} = ($size == 0) ? 0.5
                        : ($size == 1) ? 0.8
                        : ($size == 3) ? 1.5
                        : ($size == 4) ? 2
                        :                1;                        
        $self->{imgWidth} *= $self->{factor};
        $self->{imgHeight} *= $self->{factor};
        
        $self->clearCache;
        
        $self->set_policy ('automatic', 'automatic');
        $self->set_shadow_type('none');

        my @tmpArray = split m/\|/, $self->{preferences}->details;
        $self->{fieldsArray} = \@tmpArray;
        
        $self->{imageIndex} = -1;
        my @columnsType;
        $self->{columnsArray} = [];
        $self->{columns} = {};
        my $col = 0;

        $self->{borrowerField} = $parent->{model}->{commonFields}->{borrower}->{name};

        $self->setGroupingInformation;
        # We don't need count if not grouped
        $self->{addCount} = $self->{groupItems} && $self->{preferences}->addCount;

        $self->{secondaryField} = $self->{preferences}->secondarySort;
        $self->{secondaryIndex} = -1;
        $self->{addSecondary} = 0;

        foreach my $field(@tmpArray)
        {
            my $title = $parent->{model}->{fieldsInfo}->{$field}->{displayed};
        
            my $renderer;
            my $attribute;
            if ($parent->{model}->{fieldsInfo}->{$field}->{type} eq 'image')
            {
                push @columnsType, 'Gtk2::Gdk::Pixbuf';
                $renderer = Gtk2::CellRendererPixbuf->new;
                $attribute = 'pixbuf';
                $self->{imageIndex} = $col;
            }
            elsif ($parent->{model}->{fieldsInfo}->{$field}->{type} eq 'yesno')
            {
                push @columnsType, 'Glib::Boolean';
                $renderer = Gtk2::CellRendererToggle->new;
                $attribute = 'active';
            }
            elsif ($parent->{model}->{fieldsInfo}->{$field}->{type} eq 'number')
            {
                push @columnsType, 'Glib::Double';
                $renderer = Gtk2::CellRendererText->new;
                $attribute = 'text';
            }
            else
            {
                $self->{isUsingDate} = 1
                    if $parent->{model}->{fieldsInfo}->{$field}->{type} eq 'date';
                push @columnsType, 'Glib::String';
                $renderer = Gtk2::CellRendererText->new;
                $attribute = 'text';
            }
            $self->{secondaryIndex} = $col if $field eq $self->{secondaryField};
            $self->{columns}->{$field} = Gtk2::TreeViewColumn->new_with_attributes($title, $renderer, 
                                                                                   ($attribute) ? ($attribute => $col) : ());
            if ($parent->{model}->{fieldsInfo}->{$field}->{type} eq 'number')
            {
                $self->{columns}->{$field}->set_cell_data_func($renderer, sub {
                    my ($column, $cell, $model, $iter, $colNum) = @_;
                    my $value = $model->get_value($iter, $colNum);
                    # Remove trailing 0
                    $value =~ s/\.[0-9]*?0+$//;
                    $cell->set_property('text', $value);
                }, $col);
            }

            # We store the field name in it to ease the save of the column order in preferences
            $self->{columns}->{$field}->{field} = $field;
            $self->{columns}->{$field}->set_resizable(1);
            $self->{columns}->{$field}->set_reorderable(1);
            if ($parent->{model}->{fieldsInfo}->{$field}->{type} eq 'image')
            {
                $self->{columns}->{$field}->set_clickable(0);
            }
            else
            {
                $self->{columns}->{$field}->set_sort_column_id($col);
            }
            push @{$self->{columnsArray}}, $self->{columns}->{$field};
            $self->{fieldToId}->{$field} = $col;
            $col++;
        }
        push @columnsType, 'Glib::Int';
        $self->{idxColumn} = $col;
        push @columnsType, 'Glib::Boolean';
        $self->{visibleCol} = ++$col;
        
        # There is a secondary field for sort, but we didn't add it yet
        if ($self->{secondaryField} && ($self->{secondaryIndex} == -1))
        {
            push @columnsType, 'Glib::String';
            $self->{addSecondary} = 1;
            $self->{secondaryIndex} = ++$col;
        }

        $self->{model} = new Gtk2::TreeStore(@columnsType);        
        {
            package GCTreeModelSort;
            use Glib::Object::Subclass
                Gtk2::TreeModelSort::,
                interfaces => [ Gtk2::TreeDragDest:: ],
                ;
            
            sub new
            {
                my ($proto, $childModel) = @_;
                my $class = ref($proto) || $proto;
                return Glib::Object::new ($class, model => $childModel);
            }
        }

        $self->{filter} = new Gtk2::TreeModelFilter($self->{model});
        $self->{sorter} = new GCTreeModelSort($self->{filter});

        $self->{filter}->set_visible_column($self->{visibleCol});

        $self->{subModel} = $self->{filter};
        $self->{completeModel} = $self->{sorter};

        $self->{list} = Gtk2::TreeView->new_with_model($self->{completeModel});
        $self->{list}->append_column($_) foreach (@{$self->{columnsArray}});
        
        $self->{list}->set_name('GCItemsDetailsList');
        $self->{list}->set_headers_clickable(1);
        $self->{list}->set_rules_hint(1);
        $self->{list}->set_reorderable(1);

        # Restore size of columns
        if ($self->{preferences}->exists('columnsWidths'))
        {
            my $i = 0;
            my @widths = split /\|/, $self->{preferences}->columnsWidths;
             #/ For syntax highlighting
            foreach (@{$self->{columnsArray}})
            {
                $_->set_sizing('fixed');
                $_->set_resizable(1);
                $_->set_fixed_width($widths[$i] || 70);
                $i++;
            }
        }

        # If title is not displayed, we will use the 1st column to display
        # the generated masters
        $self->{generatedField} = $self->{titleField};
        if (!exists $self->{columns}->{$self->{titleField}})
        {
            $self->{generatedField} = $tmpArray[0];
        }
        $self->{generatedIndex} = $self->{fieldToId}->{$self->{generatedField}};
        $self->{list}->set_search_column($self->{columns}->{$self->{generatedField}});
        $self->{list}->set_expander_column($self->{columns}->{$self->{generatedField}});

        # Initializing sort methods
        my $colIdx = 0;
        #my $secondarySort = 1;
        foreach my $field(@tmpArray)
        {
            my ($secondaryIndex, $secondaryField) = ($self->{secondaryIndex}==-1)
                                                  ? ($colIdx, $field)
                                                  : ($self->{secondaryIndex}, $self->{secondaryField});
            my $data = [$self, $colIdx, $secondaryIndex];
            foreach my $sorter($field, $secondaryField)
            {
                next if !$sorter;
                if ($parent->{model}->{fieldsInfo}->{$sorter}->{type} eq 'number')
                {
                    # Small trick to convert number as follows with a letter
                    # in front of them so cmp will work as expected
                    # e.g.: 3 -> b3; 42 -> c42; 56 -> c56; 5897446 -> h5897446
                    # This could not work if your system uses a character encoding
                    # which is not contiguous as EBCDIC
                    push @$data, sub {return chr(length($_[0]*1000)+ord('a')).($_[0]*1000)};
                }
                elsif ($parent->{model}->{fieldsInfo}->{$sorter}->{type} eq 'date')
                {
                    push @$data, \&GCPreProcess::reverseDate;
                }
                else
                {
                    #push @$data, \&uc;
                    push @$data, sub {return uc $_[0]};
                }
            }
            if ($self->{groupItems} && ($self->{preferences}->groupedFirst))
            {
                $self->{sorter}->set_sort_func($colIdx,
                                               \&sortWithParentFirst,
                                               $data);
            }
            else
            {
                $self->{sorter}->set_sort_func($colIdx,
                                               \&sortAll,
                                               $data);
            }
            $colIdx++;
        }
        $self->{list}->get_selection->set_mode ('multiple');
        
        $self->{list}->get_selection->signal_connect('changed' => \&onSelectionChanged,
                                                     $self);

#        $self->{list}->get_selection->set_select_function(sub {
#            my ($selection, $model, $path) = @_;
#            return !$model->iter_has_child($model->get_iter($path));
#        });

        $self->{list}->signal_connect ('row-activated' => sub {
           $parent->displayInWindow;
        });

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

        $self->{list}->signal_connect('key-press-event' => sub {
            my ($treeview, $event) = @_;
            my $key = Gtk2::Gdk->keyval_name($event->keyval);
            if ($key eq 'Delete')
            {
                return 1 if !$self->{count};
                return 1 if !$self->getCurrentIter;
                return 1 if $self->{completeModel}->iter_has_child(
                    $self->getCurrentIter
                );
                $self->{parent}->deleteCurrentItem;
                return 1;
            }
            return 0;
        });

        if ($self->{groupItems})
        {
            my $targetEntryMove = {
                target => 'MY_ROW_TARGET',
                flags => ['same-widget'],
                info => 42,
            };
            
            $self->{list}->enable_model_drag_source('button1-mask','move', $targetEntryMove);
            $self->{list}->enable_model_drag_dest('move', $targetEntryMove);

            $self->{list}->signal_connect('drag_data_get' => sub {
                return 1;
            });
    
            $self->{list}->signal_connect('drag_data_received' => \&dropHandler, $self);
        }
        else
        {
            $self->{list}->unset_rows_drag_dest;
            $self->{list}->unset_rows_drag_source;
        }

        $self->reset;

        $self->show_all;
        return $self;
    }

    sub destroy
    {
        my $self = shift;
        # Unlock panel if we locked it when displaying a category
        $self->{parent}->{panel}->lock(0);
        $self->SUPER::destroy;
    }

    sub onSelectionChanged
    {
        my ($selection, $self) = @_;
        return if $self->{deactivateUpdate};
        my @indexes;
        $self->{list}->get_selection->selected_foreach(sub {
            my ($model, $path, $iter, $self) = @_;
            push @indexes, $self->convertIterToIdx($iter);
        }, $self);
        return if scalar @indexes == 0;
        my $iter = $self->getCurrentIter;
        $self->{selectedIterString} = $self->convertIterToString($iter);
        $self->{currentRemoved} = 0;
        $self->{parent}->display(@indexes);
        $self->{currentIterString} = $self->{selectedIterString}
            if !$self->{currentRemoved};
        $self->checkLock;
    }

    sub savePreferences
    {
        my ($self, $preferences) = @_;

        # Save the columns order and their sizes as pipe separated lists
        my $details = '';
        my $widths = '';
        foreach my $col($self->{list}->get_columns)
        {
            $details .= $col->{field}.'|';
            $widths .= $col->get_width.'|';
        }
        $preferences->details($details);
        $preferences->columnsWidths($widths);

        # We return here if the order has not been set previously
        return if !$self->{orderSet};
        my ($fieldId, $order) = $self->{sorter}->get_sort_column_id;
        $preferences->sortField($self->{fieldsArray}->[$fieldId]);
        $preferences->sortOrder(($order eq 'ascending') ? 1 : 0);

    }

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

    sub expandAll
    {
        my $self = shift;
        
        $self->{list}->expand_all;
    }

    sub collapseAll
    {
        my $self = shift;
        
        $self->{list}->collapse_all;
    }

    sub setGroupingInformation
    {
        my $self = shift;

        $self->{collectionField} = $self->{preferences}->groupBy;
        $self->{groupItems} = ($self->{collectionField} ne '');
        return if !$self->{groupItems};
    }

    sub sortAll
    {
        my ($childModel, @iter, $data);
        ($childModel, $iter[0], $iter[1], $data) = @_;
        my ($self, $colId1, $colId2, $converter1, $converter2) = @$data;

        my @val;
        my $colId;
        my $converter;
        foreach my $i(0..1)
        {
            ($colId, $converter) = ($childModel->iter_parent($iter[$i]))
                                 ? ($colId2, $converter2)
                                 : ($colId1, $converter1);
            push @val, $converter->($childModel->get_value($iter[$i], $colId));
        }
        return $val[0] cmp $val[1];
    }

    sub sortWithParentFirst
    {
        my ($childModel, $iter1, $iter2, $data) = @_;
        my ($self, $colId1, $colId2, $converter1, $converter2) = @$data;

        my $hasChildren1 = $childModel->iter_has_child($iter1);
        my $hasChildren2 = $childModel->iter_has_child($iter2);

        my $colId;
        my $converter;
        if ($hasChildren1 == $hasChildren2)
        {
            ($colId, $converter) = ($childModel->iter_parent($iter1))
                                 ? ($colId2, $converter2)
                                 : ($colId1, $converter1);

            # FIXME If we don't copy the value first, it will crash on win32 systems
            # with an iterator not matching model
            my $val1 = $converter->($childModel->get_value($iter1, $colId));
            my $val2 = $converter->($childModel->get_value($iter2, $colId));
            return $val1 cmp $val2;
        }
        else
        {
            return ($hasChildren1 ? -1 : 1);
        }
    }

    sub dropHandler
    {
        my ($treeview, $context, $widget_x, $widget_y, $data, $info, $time, $self) = @_;
        my $source = $context->get_source_widget;
        return if ($source ne $treeview);
        my $model = $treeview->get_model;
        my ($targetPath, $targetPos) = $treeview->get_dest_row_at_pos($widget_x, $widget_y);
        if ($targetPath)
        {
            my $targetIter = $model->get_iter($targetPath);
            #my $origIter = $treeview->get_selection->get_selected;
            
            # Deactivate DnD for multiple selection
            my @rows = $self->{list}->get_selection->get_selected_rows;
            if (scalar(@rows) > 1)
            {
                $context->finish(1,0,$time);
                return;
            }

            my $origIter = $self->getCurrentIter;            
            if ($model->iter_has_child($origIter))     # We can't move a master
            {
                $context->finish(1,0,$time);
                return;
            }            
            
            my $origIdx = $self->convertIterToIdx($origIter);
            my $origCollection = '';
            my $origParentIter = $model->iter_parent($origIter);
            my ($origParentPath, $origParentChildIter);
            if ($origParentIter)
            {
                $origParentChildIter = $self->convertIterToChildIter($origParentIter);
                $origParentPath = $self->{model}->get_path($origParentChildIter)
                    if ($self->{addCount});
            }
            $origCollection = $self->getIterCollection($origParentChildIter)
                if $origParentChildIter;
            #We cannot drop an item on itself
            if ($targetIter == $origIter)
            {
                $context->finish(1,0,$time);
                return;
            }
            my @origData;
            my $i = 0;
            foreach ($model->get_value($origIter))
            {
                push @origData, $i, $_;
                $i++;
            }
            my $collectionIter = $model->iter_parent($targetIter);
            #if ($collectionIter)
            my $collection = $collectionIter
                           ? $self->getIterCollection($self->convertIterToChildIter($collectionIter))
                           : $self->getIterCollection($self->convertIterToChildIter($targetIter));

            my $newIter;
            my $refreshCountNeeded = 0;
            if ($targetPos =~ /^into/)
            {
                if (
                    (!$model->iter_has_child($targetIter))  # We can't drop on a single item
                 || ($targetPath->get_depth > 1)            # We can't add an item to an item in a collection.
                 || ($model->iter_has_child($origIter))     # We can't move a master
                   )
                {
                    $context->finish(1,0,$time);
                    return;
                }
                else
                {
                    #Creating a new collection item
                    $newIter = $self->{model}->append($self->convertIterToChildIter($targetIter));
                    $refreshCountNeeded = 1;
                }
            }
            else
            {
                my $origPath = $model->get_path($origIter);
                if ($targetPath->get_depth == 1)
                {
                    if  ($origPath->get_depth == 1)
                    {
                        #Just moving a master item is not allowed
                        $context->finish(1,0,$time);
                        return;
                    }
                    else
                    {
                        #We get an item out of a collection
                        $newIter = $self->{model}->append(undef);
                        $collection = '';
                    }
                }
                else
                {
                    #We are placing a collection item
                    $newIter = $self->{model}->append(
                        $self->convertIterToChildIter($model->iter_parent($targetIter))
                    );
                    $refreshCountNeeded = 1;
                }
            }
            
            $self->{model}->set($newIter, @origData);
            if ($self->{addCount})
            {
                # Refreshing target
                $self->refreshCount($self->{model}->iter_parent($newIter))
                    if ($refreshCountNeeded);

                # Refreshing origin
                # We remove 1 to the count because the original has not been removed yet
                # It will be removed when returning from this method
                $self->refreshCount($self->{model}->get_iter($origParentPath), 0, -1)
                    if $origParentPath;
            }
            #$origIter = $treeview->get_selection->get_selected;
            $origIter = $self->getCurrentIter;
            #$self->removeParentIfNeeded($origIter);
            
            # Removing previous instances in other collections
            $self->removeOtherInstances($origCollection, $origIdx);
            
            $self->{parent}->{items}->setValue($origIdx, $self->{collectionField}, $collection);
            $context->finish(1,1,$time);
            $self->select($origIdx);
            $self->{parent}->markAsUpdated;
        }
    }

    sub removeOtherInstances
    {
        my ($self, $collection, $idx, $fullCollection) = @_;
        if ($collection)
        {
            my $collectionArray = 
                $self->transformValue(
                    $fullCollection || $self->{parent}->{items}->getValue($idx, $self->{collectionField}),
                    $self->{collectionField},
                    0,
                    $self->{groupItems}
                );
            if (ref($collectionArray) eq 'ARRAY')
            {
                foreach (@$collectionArray)
                {
                    next if $_ eq $collection;
                    $self->removeInCollection($_, $idx);
                }
            }
            
        }
    }        

    sub removeParentIfNeeded
    {
        my ($self, $iter) = @_;
        #Destroy the previous auto-generated item if there was only one child
        my $parentIter = $self->{model}->iter_parent($self->convertIterToChildIter($iter));
        if ($parentIter && ($self->{model}->iter_n_children($parentIter) <= 1))
        {
            $self->{model}->remove($parentIter);
        }
    }

    sub removeFromModel
    {
        my ($self, $iter) = @_;
        my $parentToRemove = undef;

        my $parentIter = $self->{model}->iter_parent($iter);
        my $refreshCountNeeded = 0;
        if ($parentIter)
        {
            if ($self->{model}->iter_n_children($parentIter) <= 1)
            {
                $parentToRemove = $parentIter;
            }
            else
            {
                # If we removed the 1st one, we should change the index of the master
                # to be the new 1st
                my $removedIdx = $self->convertChildIterToIdx($iter);
                my $firstIdx = $self->convertChildIterToIdx($self->{model}->iter_children($parentIter));
                if ($firstIdx == $removedIdx)
                {
                    my $newIdx = $self->convertChildIterToIdx($self->{model}->iter_nth_child($parentIter, 1));
                    $self->{model}->set($parentIter, $self->{idxColumn}, $newIdx);
                }
            }
            # Update count if needed
            if (($self->{addCount})
             && ($self->{model}->get($iter, $self->{visibleCol}))
             && ($self->{model}->get($parentIter, $self->{visibleCol})))
            {
                $refreshCountNeeded = 1;
            }

        }

        $self->{model}->remove($iter);
        $self->refreshCount($parentIter) if $refreshCountNeeded;
        $self->{model}->remove($parentToRemove) if $parentToRemove;        
    }

    sub isUsingDate
    {
        my ($self) = @_;
        return $self->{isUsingDate};
    }

    sub setSortOrder
    { 
        my ($self, $order, $splash, $willFilter) = @_;
        $self->{orderSet} = 1;
        my $progressNeeded = ($splash && !$willFilter);
        my ($realOrder, $field) = ($self->{preferences}->sortOrder, $self->{preferences}->sortField);
        my $step;
        if ($progressNeeded)
        {
            $step = GCUtils::round($self->{count} / 7);
            $splash->setProgressForItemsSort(2*$step);
        }
        $self->{sorter}->set_sort_column_id($self->{fieldToId}->{$field},
                                           $realOrder ? 'ascending' : 'descending');
        $self->{sorter}->set_default_sort_func(undef, undef);
        $splash->setProgressForItemsSort(4*$step) if $progressNeeded;
    }

    sub testIter
    {
        my ($self, $filter, $items, $iter) = @_;
        my $idx = $self->convertChildIterToIdx($iter);
        my $displayed;
        if (exists $self->{testCache}->[$idx])
        {
            $displayed = $self->{testCache}->[$idx];
        }
        else
        {
            $displayed = $self->{testCache}->[$idx] = $filter->test($items->[$idx]);
            # We increment only here to count only unique items
            $self->{count}++ if $displayed;
        }
        $self->{model}->set($iter,
                            $self->{visibleCol},
                            $displayed
                           );
        return $displayed;
    }

    sub setFilter
    {
        my ($self, $filter, $items, $refresh, $splash) = @_;
        $self->{count} = 0;
        $self->{testCache} = [];
        $self->{tester} = $filter;
        my $idx = 0;
        my $iter = $self->{model}->get_iter_first;
        while ($iter)
        {
            $splash->setProgressForItemsSort($idx++) if $splash;
            if ($self->{model}->iter_has_child($iter))
            {
                my $showParent = 0;
                my $childIter = $self->{model}->iter_children($iter);
                while ($childIter)
                {
                    my $displayed = GCDetailedList::testIter($self, $filter, $items, $childIter);
                    $showParent ||= $displayed;
                    $childIter = $self->{model}->iter_next($childIter);
                }
                $self->{model}->set($iter,
                                    $self->{visibleCol},
                                    $showParent
                                   );
            }
            else
            {
                GCDetailedList::testIter($self, $filter, $items, $iter);
            }
            $iter = $self->{model}->iter_next($iter);
        }
        $self->{filter}->refilter;
        $self->refreshCounts if ($self->{addCount});
        my $currentIter = $self->getCurrentIter;
        return $self->convertIterToIdx($currentIter)
            if $currentIter;
        $idx = $self->convertIterToIdx($self->{completeModel}->get_iter_first);
        return $idx;
    }

    sub getPixbufFromCache
    {
        my ($self, $path) = @_;
        
        my $realPath = GCUtils::getDisplayedImage($path,
                                                  $self->{parent}->{defaultImage},
                                                  $self->{parent}->{options}->file);
        
        if (! $self->{cache}->{$realPath})
        {
            my $pixbuf;
            eval {
                $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($realPath);
            };
            $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($self->{parent}->{defaultImage})
                if $@;

            $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf,
                                              $self->{imgWidth},
                                              $self->{imgHeight},
                                              $self->{withImage});
            $self->{cache}->{$realPath} = $pixbuf;
        }
        return $self->{cache}->{$realPath};
    }

    sub clearCache
    {
        my $self = shift;
        $self->{cache} = {};
    }

    sub reset
    {
        my $self = shift;
        $self->{list}->set_model(undef);
        $self->{model}->clear;
        $self->{alreadyInserted} = {};
        $self->{currentIdx} = 0;
        $self->{nextItemIdx} = -1;

        $self->setGroupingInformation;
    }

    sub done
    {
        my $self = shift;
        $self->{list}->set_model($self->{completeModel});
        $self->refreshCounts if ($self->{addCount});
    }

    sub refreshCount
    {
        my ($self, $iter, $added, $offset) = @_;

        my $subIter = $self->{subModel}->convert_child_iter_to_iter($iter);
        my $nbChildren = $subIter
                       ? $self->{subModel}->iter_n_children($subIter)
                       : ($added ? 1 : 0);
        $nbChildren += $offset if defined $offset;
        # Version below still works if the orders of filter and sorter are inverted
        #my $nbChildren = 0;
        #my $childIter = $self->{model}->iter_children($iter);
        #while ($childIter)
        #{
        #    $nbChildren++ if $self->{model}->get($childIter, $self->{visibleCol});
        #    $childIter = $self->{model}->iter_next($childIter);
        #}
        my $original = $self->getIterCollection($iter);
        my $generated = "$original ($nbChildren)";
        $self->{model}->set($iter,
                            $self->{fieldToId}->{$self->{generatedField}},
                            $generated);
        $self->{originalValue}->{$generated} = $original;
        $self->{model}->set($iter, $self->{visibleCol}, $nbChildren);
    }

    sub refreshCounts
    {
        my $self = shift;
        $self->{model}->foreach(sub {
            my ($model, $path, $iter) = @_;
            if ($model->iter_has_child($iter))
            {
                $self->refreshCount($iter);
            }
            return 0;
        });
    }

    sub transformValue
    {
        my ($self, $value, $field, $isGeneratedMaster, $multiAllowed, $isForSort, $forceArray) = @_;
        
        my $type = '';
        $type = $self->{parent}->{model}->{fieldsInfo}->{$field}->{type}
            if defined $self->{parent}->{model}->{fieldsInfo}->{$field}->{type};

        if ($type eq 'image')
        {
            $value = ($isGeneratedMaster ? undef : $self->getPixbufFromCache($value));
        }
        else
        {
            if ($field eq $self->{borrowerField})
            {
                $value = $self->{parent}->{lang}->{PanelNobody} if (! $value) || ($value eq 'none');
            }
            else
            {
                if ($type eq 'date')
                {
                    $value = GCUtils::timeToStr($value, $self->{parent}->{options}->dateFormat);
                }
                elsif ($type =~ /list$/o)
                {
                    if ($multiAllowed && $self->{multi})
                    {
                        $value = GCPreProcess::multipleListToArray($value);
                    }
                    else
                    {
                        $value = GCPreProcess::multipleList($value, $type);
                    }
                }
                elsif ($type eq 'options')
                {
                    $value = $self->{parent}->{items}->valueToDisplayed($value, $field) || $value;
                }
                if ($field eq $self->{titleField})
                {
                   $value = $self->{parent}->transformTitle($value);
                }
            }
        }
        if ($isForSort)
        {
            if ($type eq 'date')
            {
                $value = GCPreProcess::reverseDate($value);
            }
            else
            {
                $value = uc $value;
            }
        }
        if ($forceArray)
        {
            if (ref($value) ne 'ARRAY')
            {
                my @array = ($value);
                $value = \@array;
            }
        }
        return $value;
    }

    sub convertChildPathToPath
    {
        my ($self, $path) = @_;

        my $result = $path;
        $result = $self->{subModel}->convert_child_path_to_path($result);
        $result = $self->{completeModel}->convert_child_path_to_path($result)
            if $result;
        return $result;
    }

    sub getIterCollection
    {
        my ($self, $iter, $model) = @_;
        $model ||= $self->{model};
        my $val = ($model->get($iter))[$self->{generatedIndex}];
        if ($self->{addCount} && ($val =~ / \(\d+\)$/))
        {
            $val = $self->{originalValue}->{$val}
                if exists $self->{originalValue}->{$val};
        }
        return $val;
    }

    # This one should be called with a sorted/filtered iterator
    sub convertIterToIdx
    {
        my ($self, $iter) = @_;
        return 0 if ! $iter;
        # If we have a master, we return idx from its 1st displayed child
        if ($self->{completeModel}->iter_has_child($iter))
        {
            $iter = $self->{completeModel}->iter_children($iter);
        }
        return $self->{completeModel}->get_value($iter, $self->{idxColumn});
    }

    # This one should be called with an iterator from real model
    sub convertChildIterToIdx
    {
        my ($self, $iter) = @_;
        return 0 if ! $iter;
        return ($self->{model}->get($iter))[$self->{idxColumn}];
    }

    sub getCurrentIdx
    {
        my $self = shift;
        return $self->convertIterToIdx($self->getCurrentIter);
    }
    
    sub findMaster
    {
        my ($self, $collection) = @_;
        my $realCollection = $self->{parent}->transformTitle($collection);
        my $master = $self->{model}->get_iter_first;

        while ($master)
        {
            return $master if $self->getIterCollection($master) eq $realCollection;
            $master = $self->{model}->iter_next($master);
        }
        return undef;
    }

    sub removeInCollection
    {
        my ($self, $collec, $idx) = @_;
        my $master = $self->findMaster($collec);
        my $iter = $self->{model}->iter_nth_child($master, 0) || $master;
        while ($iter)
        {
            last if $idx == $self->convertChildIterToIdx($iter);
            $iter = $self->{model}->iter_next($iter);
        }
        if ($iter)
        {
            my $removedCurrent = ($self->{selectedIterString} eq $self->{currentIterString});
            $self->removeFromModel($iter);
            $self->{currentRemoved} = $removedCurrent;
        }
    }

    sub createRowsData
    {
        my ($self, $info, $idx, $withTest) = @_;
        my @data;
        my $col = 0;
        my $displayed = 1;
        if ($withTest)
        {
            $displayed = $self->{tester}->test($info)
                if $self->{tester};
            $self->{testCache}->[$idx] = $displayed;
        }
        foreach my $field(@{$self->{fieldsArray}})
        {
            my $value = $self->transformValue($info->{$field}, $field, $info->{isGeneratedMaster});
            push @data, $col, $value;
            $col++;
        }
        push @data, $col++, $idx;
        push @data, $col++, $displayed;
        push @data, $col++, $self->transformValue($info->{$self->{secondaryField}})
             if $self->{addSecondary};
        return @data;
    }

    sub addItem
    {
        my ($self, $info, $immediate) = @_;

        $self->{nextItemIdx}++;
        $self->{count}++;

        my $collection = $self->transformValue($info->{$self->{collectionField}}, $self->{collectionField},
                                               0, $self->{groupItems});

        #Creating data;
        my @data = $self->createRowsData($info, $self->{nextItemIdx});

        if (
              (! defined $collection)
           || ($collection eq '')
           || (!$self->{groupItems})
           || (
                (ref($collection) eq 'ARRAY')
             && (
                  (! scalar @$collection)
               || ((scalar @$collection == 1) && ((! defined $collection->[0]) || $collection->[0] eq ''))
                )
              )
           )
        {
            #Simple entry
            $self->{model}->set($self->{model}->append(undef), @data);
            return;
        }
        
        if (ref($collection) ne 'ARRAY')
        {
            my @array = ($collection);
            $collection = \@array;
        }
        foreach (@$collection)
        {
            next if $_ eq '';
            if (exists $self->{alreadyInserted}->{$_})
            {
                #Master already exists
                my $master;
                $master = $self->findMaster($_);
                my $childIter = $self->{model}->append($master);
                $self->{model}->set($childIter, @data);
            }
            else
            {
                #No master and we are a child;
                #Create the master
                my $master = $self->{model}->append(undef);
                $self->{alreadyInserted}->{$_} = 1;
                my $masterName = $_;
                my %info = (
                            $self->{generatedField} => $masterName,
                            #$self->{collectionField} => $_,
                            isGeneratedMaster => 1
                            );
                my @masterData = $self->createRowsData(\%info, -1);
                $self->{model}->set($master, @masterData);
                #Insert the child
                my $childIter = $self->{model}->append($master);
                $self->{model}->set($childIter, @data);
            }
        }
    }

    sub getNextIter
    {
        my ($self, $iter, $indexes) = @_;
        # Return index of iter following current one in view
        my $nextIter = $self->{completeModel}->iter_next($iter);
        if (!$nextIter)
        {
            my $parentIter = $self->{completeModel}->iter_parent($iter);
            if ($parentIter)
            {
                $nextIter = $self->{completeModel}->iter_next($parentIter);
            }
            if (!$nextIter)
            {
                my $nbChildren = $self->{completeModel}->iter_n_children($parentIter);
                if ($nbChildren > 1)
                {
                    $nextIter = $self->{completeModel}->iter_nth_child(
                        $parentIter,
                        $self->{completeModel}->iter_n_children($parentIter) - 2
                    );
                }
                else
                {
                    $nextIter = $self->{completeModel}->iter_nth_child(undef, 0);
                }
            }
        }
        my $idx = $self->convertIterToIdx($nextIter);

        # If the one we got is in the list of the removed ones,
        # We don't try to get another one, but we return the 1st one
        if (GCUtils::inArrayTest($idx, @$indexes))
        {
            $nextIter = $self->{completeModel}->iter_nth_child(undef, 0);
            $idx = $self->convertIterToIdx($nextIter);
        }
        
        return ($nextIter, $idx);
    }

    sub select
    {
        my ($self, $idx, $init) = @_;
        my $currentIter;
        if ($idx == -1)
        {
            $self->{currentIterString} = '0';
            $currentIter = $self->{completeModel}->get_iter_first;
        }
        else
        {
            $self->convertIdxToIter($idx);
            $self->{currentIterString} = '0' if ! $self->{currentIterString};
            $currentIter = $self->getCurrentIterFromString;
        }
        $idx = $self->convertIterToIdx($currentIter);
        return if !$currentIter;
        if ($init)
        {
            my $parent = $self->{completeModel}->iter_parent($currentIter);
            if ($parent)
            {
                my $treePath = $self->{completeModel}->get_path($parent);
                if (!$self->{list}->row_expanded($treePath))
                {
                    $self->{currentIterString} = $self->convertIterToString($parent);
                    $idx = $self->getCurrentIdx;
                }
            }
            if ($self->{completeModel}->iter_has_child($currentIter))
            {
                $currentIter =  $self->{completeModel}->iter_children($currentIter);
                $idx = $self->convertIterToIdx($currentIter);
                $self->{currentIterString} = $self->convertIterToString($currentIter);
            }
        }
        $self->{list}->expand_to_path(Gtk2::TreePath->new($self->{currentIterString}))
            if $self->{currentIterString} =~ /:/;
        #Lock panel if we are on a master
        $self->checkLock($currentIter);
        if ($self->{list}->get_model)
        {
            $self->selectIter($currentIter);
        }
        return $idx;
    }

    sub checkLock
    {
        my ($self, $iter) = @_;
        $iter = $self->getCurrentIter if !$iter;
        if ($self->{completeModel}->iter_n_children($iter) > 0)
        {
            $self->{parent}->{panel}->lock(1);
        }
        else
        {
            $self->{parent}->{panel}->lock(0);
        }
    }
    
    sub showCurrent
    {
        my $self = shift;

        my $path = $self->{list}->get_selection->get_selected_rows;
        $self->{list}->scroll_to_cell($path) if $path;
    }

    sub changeCurrent
    {
        my ($self, $previous, $new, $idx, $wantSelect) = @_;
        my @data = ();
        my $col = 0;
        my $refilterNeeded = 0;
        my $currentIter = $self->getCurrentIterFromString;
        return $idx if !$currentIter;
        my $currentDepth = $self->{completeModel}->get_path($currentIter)->get_depth;
        my $newIdx = $idx;
        if (($currentDepth == 1)
         && $self->{completeModel}->iter_has_child($currentIter))
        {
            # We do nothing for generated masters
            return $newIdx;
        }
        $idx = $self->convertIterToIdx($currentIter);

        my $previousCollection = $self->transformValue($previous->{$self->{collectionField}}, $self->{collectionField});
        my $newCollection = $self->transformValue($new->{$self->{collectionField}}, $self->{collectionField});
        @data = $self->createRowsData($new, $idx, 1);
        # If we hide it, we select next one
        # We double the index because @data contains both values and indexes
        my $visible = $data[2 * $self->{visibleCol} + 1];
        if (! $visible)
        {
            my $nextIter;
            ($nextIter, $newIdx) = $self->getNextIter($currentIter);
            $self->selectIter($nextIter, 1) if $nextIter && $wantSelect;
            $self->{count}--;
        }

        if ($self->{groupItems})
        {
            if ($previousCollection ne $newCollection)
            {
                my $previousCollectionArray;
                my $newCollectionArray;
                #An item is integrated or moved into a collection
                #First we find its master
                my @parents;

                #Changing collection
                $previousCollectionArray = $self->transformValue($previous->{$self->{collectionField}}, $self->{collectionField}, 0, $self->{groupItems}, 0, 1);
                $newCollectionArray = $self->transformValue($new->{$self->{collectionField}}, $self->{collectionField}, 0, $self->{groupItems}, 0, 1);
                foreach my $collec(@$newCollectionArray)
                {
                    next if $collec eq '';
                    push @parents, $self->findMaster($collec);

                    if (!$parents[-1])
                    {
                        $refilterNeeded = 1;
                        #We have to create a new parent
                        #Create the master
                        $parents[-1] = $self->{model}->append(undef);
                        $self->{alreadyInserted}->{$newCollection} = 1;
                        my %info = (
                                    $self->{generatedField} => $collec,
                                    $self->{collectionField} => $newCollection,
                                    isGeneratedMaster => 1
                                    );
                        my @masterData = $self->createRowsData(\%info, -1);
                        $self->{model}->set($parents[-1], @masterData);
                    }
                    else
                    {
                        # The parent already exists
                        # Check if the child were already there
                        
                        # If it was, we remove it from parents
                        pop @parents if GCUtils::inArrayTest($collec, @$previousCollectionArray);
                    }
                }

                my $childIter = 0;
                if (!scalar @$newCollectionArray)
                {
                    $childIter = $self->{model}->append(undef);
                    $self->{model}->set($childIter, @data);
                }
                else
                {
                    foreach my $parent(@parents)
                    {
                        #First we insert it at correct position
                        my $cIter = $self->{model}->append($parent);
                        $self->{model}->set($cIter, @data);
                        # We point to the 1st one
                        $childIter = $cIter if !$childIter;
                        # Update count if needed
                        $self->refreshCount($parent, 1) if ($self->{addCount});
                    }
                }
                
                #First we store if we are removing the one that has been selected
                $self->{currentRemoved} = ($self->{selectedIterString} eq $self->{currentIterString});

                # For generated master, we could have copies of our item in many places.
                # So we need to loop on all the previous collections
                # If we have a real master, we have juste one occurrence, the current one
                if (!scalar @$previousCollectionArray)
                {
                        $self->removeInCollection(undef, $idx);
                }
                else
                {
                    foreach my $collec(@$previousCollectionArray)
                    {
                        $self->removeInCollection($collec, $idx)
                            if !GCUtils::inArrayTest($collec, @$newCollectionArray);
                    }
                }
                if ($childIter)
                {
                    my $childPath = $self->{model}->get_path($childIter);
                    $childPath = $self->convertChildPathToPath($childPath);
                    if ($childPath)
                    {
                        $self->{list}->expand_to_path($childPath);
                        $self->selectIter(
                            $self->{completeModel}->get_iter($childPath)
                        ) if $self->{currentRemoved} && $visible;
                    }
                }
            }
            else
            {
                $self->{model}->foreach(sub {
                    my ($model, $path, $iter) = @_;
                    return 0 if $idx != $self->convertChildIterToIdx($iter);
                    $model->set($iter, @data);
                    return 0;
                });
            }
        }
        else
        {
            $self->{model}->set(
                $self->convertIterToChildIter($currentIter),
                @data
            );               
        }

        # Update count for each parents only if we are not changing collection
        # Because we already did it otherwise
        if ((! $visible)
         && ($self->{groupItems})
         && ($previousCollection eq $newCollection))
        {
            my $collectionArray = $self->transformValue($new->{$self->{collectionField}},
                                                        $self->{collectionField},
                                                        0,
                                                        1,
                                                        0,
                                                        1);
            foreach my $collec(@$collectionArray)
            {
                next if $collec eq '';
                $self->refreshCount($self->findMaster($collec))
                    if ($self->{addCount});
            }
        }
        
        #my $iter = $self->{list}->get_selection->get_selected;
        my $iter = $self->getCurrentIter;
        $self->{selectedIterString} = $self->convertIterToString($iter);
        $self->{currentIterString} = $self->{selectedIterString};
        $self->showCurrent;
        $self->{filter}->refilter if $refilterNeeded;
        return $newIdx;
    }
    
}


1;
