summaryrefslogtreecommitdiff
path: root/lib/gcstar/GCItemsLists/GCTextLists.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gcstar/GCItemsLists/GCTextLists.pm')
-rw-r--r--lib/gcstar/GCItemsLists/GCTextLists.pm2101
1 files changed, 2101 insertions, 0 deletions
diff --git a/lib/gcstar/GCItemsLists/GCTextLists.pm b/lib/gcstar/GCItemsLists/GCTextLists.pm
new file mode 100644
index 0000000..aaa080b
--- /dev/null
+++ b/lib/gcstar/GCItemsLists/GCTextLists.pm
@@ -0,0 +1,2101 @@
+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;
+
+ 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);
+ # 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);
+ }
+
+ 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;