package GCTextLists;

###################################################
#
#  Copyright 2005-2010 Christian Jodar
#
#  This file is part of GCstar.
#
#  GCstar is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  GCstar is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with GCstar; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
###################################################

#
# This file handle the left list part, in text Mode and Detailed Mode
#

use strict;
use locale;        

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

    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
    {
        # Apply the changes from $previous to $new to the listView entry $idx
        # Return the $idx of the new current item ($previous can now be hidden)
        my ($self, $idx, $previous, $new) = @_;
        $self->convertIdxToIter($idx);
        return $self->changeCurrent($previous, $new, $idx, 0);
    }
    
    sub updateMenus
    {    
        # Update menu items to reflect number of items selected
        my ($self, $nbSelected) = @_;
        
        my $menu = $self->{parent}->{menubar};
        my @updateList;
        if ($nbSelected > 1)
        {
            @updateList = (
                [$menu, 'duplicateItem', 'MenuDuplicatePlural'],
                [$menu, 'deleteCurrentItem', 'MenuEditDeleteCurrentPlural'],
                [$self->{parent}, 'contextDuplicateItem', 'MenuDuplicatePlural'],
                [$self->{parent}, 'contextItemDelete', 'MenuEditDeleteCurrentPlural'],
                [$self->{parent}, 'contextNewWindow', 'MenuNewWindowPlural'],
            );
        }
        else
        {
            @updateList = (
                [$menu, 'duplicateItem', 'MenuDuplicate'],
                [$menu, 'deleteCurrentItem', 'MenuEditDeleteCurrent'],
                [$self->{parent}, 'contextDuplicateItem', 'MenuDuplicate'],
                [$self->{parent}, 'contextItemDelete', 'MenuEditDeleteCurrent'],
                [$self->{parent}, 'contextNewWindow', 'MenuNewWindow'],
            );
        }
        foreach (@updateList)
	{
	    $menu->updateItem($_->[0]->{$_->[1]}, $_->[2]);
	}
    }
    
}

{
    package GCTextList;

    use Gtk2::SimpleList;
    use GCUtils;
    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;
            my $nbSelected;
            $self->{list}->get_selection->selected_foreach(sub {
                my ($model, $path, $iter, $self) = @_;
                push @indexes, $self->{completeModel}->get_value($iter, 1);
                $nbSelected++;
            }, $self);
            return if scalar @indexes == 0;
            $parent->display(@indexes);
            my $iter = $self->getCurrentIter;
            $self->{currentIterString} = $self->convertIterToString($iter);
            
            # Update menus to reflect number of items selected
            $self->updateMenus($nbSelected);
        });

        $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;
            
            # Check if row clicked on is in the current selection
            my ($path, $column, $cell_x, $cell_y) = $widget->get_path_at_pos( $event->x, $event->y );
            my $selection = $widget->get_selection;
            my @rows = $selection->get_selected_rows;
            my $clickedOnSelection = 0;
            # Loop through selection to see if current row is selected
            foreach my $row(@rows)
            {
                if ($row->to_string eq $path->to_string)
                {
                    $clickedOnSelection = 1;
                }
            }

            # Popup the menu
            $self->{parent}->{context}->popup(undef, undef, undef, undef, $event->button, $event->time);
            
            # If row clicked on was in the selection, return true, else return false to clear selection
            # to clicked on item
            if ($clickedOnSelection)
            {
                return 1;
            }
            else
            {
                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));
            # Use a natural string sort method
            return (GCUtils::gccmp($val1, $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));
            # Use a natural string sort method
            return (GCUtils::gccmp($val1, $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 GCDetailedList;
    
    use File::Basename;
    use base 'GCBaseTextList';
    use GCUtils;

    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
        $self->{preferences}->detailImgSize(2) if ! $self->{preferences}->exists('detailImgSize');
        $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 = $self->{preferences}->detailImgSize;
        $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;
        $self->{numericFields} = [];

        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';
                push @{$self->{numericFields}}, $field;
            }
            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);
                    # TODO - not returning correct value when first column is a number
                    # and a grouping field is set. 
                    # 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++;
            }
        }

        # Use grouping field for generated masters, or if that field isn't displayed,
        # fallback on title, or first column
        if (exists $self->{columns}->{$self->{collectionField}})
        {
            $self->{generatedField} = $self->{collectionField};        
        }
        elsif (exists $self->{columns}->{$self->{titleField}})
        {
            $self->{generatedField} = $self->{titleField};
        }
        else
        {
            $self->{generatedField} = $tmpArray[0];
        }
        $self->{generatedIndex} = $self->{fieldToId}->{$self->{generatedField}};
        $self->{list}->set_expander_column($self->{columns}->{$self->{generatedField}});
        
        if (exists $self->{columns}->{$self->{titleField}})
        {
            $self->{searchField} = $self->{titleField};
        }
        else
        {
            $self->{searchField} = $tmpArray[0];
        }
        $self->{searchIndex} = $self->{fieldToId}->{$self->{searchField}};
        $self->{list}->set_search_column($self->{searchIndex});

        $self->{sorter}->signal_connect('rows-reordered' => sub {
            my ($fieldId, $order) = $self->{sorter}->get_sort_column_id;
            $self->{list}->set_search_column($fieldId);
        });

        # Initializing sort methods
        my $colIdx = 0;
        #my $secondarySort = 1;
        my $sorttype = "";
        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;
                # Work out how to sort the column
                if ((!exists $self->{columns}->{$self->{collectionField}}) &&
                    ($self->{groupItems}) &&
                    ($sorter eq $self->{generatedField}))
                {
                    # The grouping field isn't visible, so we need to set 
                    # the sort order on the column we're using for the group
                    # headers to be sorted using the grouping field type 
                    if (($parent->{model}->{fieldsInfo}->{$self->{collectionField}}->{type} eq 'date') ||
                        ($parent->{model}->{fieldsInfo}->{$self->{collectionField}}->{sorttype} eq 'date'))
                    {
                        $sorttype = "date";
                    }
                    elsif (($parent->{model}->{fieldsInfo}->{$self->{collectionField}}->{type} eq 'number') ||
                             ($parent->{model}->{fieldsInfo}->{$self->{collectionField}}->{sorttype} eq 'number'))
                    {
                        $sorttype = "number";
                    }
                    else
                    {                
                        $sorttype = "text";                    
                    }                
                }
                elsif (($parent->{model}->{fieldsInfo}->{$sorter}->{type} eq 'number') ||
                       ($parent->{model}->{fieldsInfo}->{$sorter}->{sorttype} eq 'number'))
                {
                    $sorttype = "number";
                }
                elsif (($parent->{model}->{fieldsInfo}->{$sorter}->{type} eq 'date') ||
                       ($parent->{model}->{fieldsInfo}->{$sorter}->{sorttype} eq 'date'))
                {
                    $sorttype = "date";
                }
                else
                {
                    $sorttype = "text";
                }
                
                # Set the column for the desired type of sorting
                if ($sorttype 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 ($sorttype 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;
           
            # Check if row clicked on is in the current selection
            my ($path, $column, $cell_x, $cell_y) = $widget->get_path_at_pos( $event->x, $event->y );
            my $selection = $widget->get_selection;
            my @rows = $selection->get_selected_rows;
            my $clickedOnSelection = 0;
            # Loop through selection to see if current row is selected
            foreach my $row(@rows)
            {
                if ($row->to_string eq $path->to_string)
                {
                    $clickedOnSelection = 1;
                }
            }

            # Popup the menu
            $self->{parent}->{context}->popup(undef, undef, undef, undef, $event->button, $event->time);
            
            # If row clicked on was in the selection, return true, else return false to clear selection
            # to clicked on item
            if ($clickedOnSelection)
            {
                return 1;
            }
            else
            {
                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;
        my $nbSelected;
        $self->{list}->get_selection->selected_foreach(sub {
            my ($model, $path, $iter, $self) = @_;
            push @indexes, $self->convertIterToIdx($iter);
            $nbSelected++;
        }, $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;
        
        # Update menu items to reflect number of items selected
        $self->updateMenus($nbSelected);
        
    }

    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 '');
    }

    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 (GCUtils::gccmp($val[0], $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 (GCUtils::gccmp($val1, $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->{list}->set_search_column($self->{fieldToId}->{$field});
        $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);
        
        my %totals;
        my $field;
        my $childIter = $self->{subModel}->iter_children($subIter);
        while ($childIter)
        {
            for $field(@{$self->{numericFields}})
            {
                $totals{$field} += ($self->{subModel}->get($childIter))[$self->{fieldToId}->{$field}];
            }
            $childIter = $self->{subModel}->iter_next($childIter);
        }
        for $field(@{$self->{numericFields}})
        {
            $self->{model}->set($iter, $self->{fieldToId}->{$field}, $totals{$field});
        }
    }

    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
        {
            $value = $self->{parent}->transformValue($value, $field, ($multiAllowed && $self->{multi}));
        }
        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->{model}->iter_has_child($master))
                           && ($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;
