diff options
Diffstat (limited to 'lib/gcstar/GCItemsLists/GCTextLists.pm')
-rw-r--r-- | lib/gcstar/GCItemsLists/GCTextLists.pm | 2101 |
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; |