summaryrefslogtreecommitdiff
path: root/lib/gcstar/GCDisplay.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gcstar/GCDisplay.pm')
-rw-r--r--lib/gcstar/GCDisplay.pm1146
1 files changed, 1146 insertions, 0 deletions
diff --git a/lib/gcstar/GCDisplay.pm b/lib/gcstar/GCDisplay.pm
new file mode 100644
index 0000000..41c9157
--- /dev/null
+++ b/lib/gcstar/GCDisplay.pm
@@ -0,0 +1,1146 @@
+package GCDisplay;
+
+###################################################
+#
+# 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
+#
+###################################################
+
+use strict;
+use GCUtils;
+
+{
+ package GCFilterSearch;
+
+ # Used to remove diacritics in test
+ use Unicode::Normalize 'NFKD';
+
+ sub compareExact
+ {
+ my ($field, $value) = @_;
+ return $field eq $value;
+ }
+
+ sub compareContain
+ {
+ my ($field, $value) = @_;
+ return $field =~ m/\Q$value\E/;
+ }
+
+ sub compareNotContain
+ {
+ my ($field, $value) = @_;
+ return $field !~ m/\Q$value\E/;
+ }
+
+ sub compareRegexp
+ {
+ my ($field, $value) = @_;
+ return $field =~ m/$value/;
+ }
+
+ sub compareLessStrings
+ {
+ my ($field, $value) = @_;
+ return $field lt $value;
+ }
+
+ sub compareLessNumbers
+ {
+ my ($field, $value) = @_;
+ return 0 if !defined($field);
+ return $field < $value;
+ }
+
+ sub compareLessOrEqualStrings
+ {
+ my ($field, $value) = @_;
+ return $field le $value;
+ }
+
+ sub compareLessOrEqualNumbers
+ {
+ my ($field, $value) = @_;
+ return 0 if !defined($field);
+ return $field <= $value;
+ }
+
+ sub compareGreaterStrings
+ {
+ my ($field, $value) = @_;
+ return $field gt $value;
+ }
+
+ sub compareGreaterNumbers
+ {
+ my ($field, $value) = @_;
+ return 0 if !defined($field);
+ return $field > $value;
+ }
+
+ sub compareGreaterOrEqualStrings
+ {
+ my ($field, $value) = @_;
+ return $field ge $value;
+ }
+
+ sub compareGreaterOrEqualNumbers
+ {
+ my ($field, $value) = @_;
+ return 0 if !defined($field);
+ return $field >= $value;
+ }
+
+ sub compareRangeStrings
+ {
+ my ($field, $value) = @_;
+ return 1 if $value eq ';';
+ my @values = split m/;/, $value;
+ return ($field ge $values[0]) && ($field le $values[1]);
+ }
+
+ sub compareRangeNumbers
+ {
+ my ($field, $value) = @_;
+ return 1 if $value eq ';';
+ return 0 if !defined($field);
+ my @values = split m/;/, $value;
+ return ($field >= $values[0]) && ($field <= $values[1]);
+ }
+
+ sub new
+ {
+ my ($proto, $info) = @_;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+
+ bless ($self, $class);
+
+ $self->{info} = $info if $info;
+ $self->clear if !$info;
+ $self->setMode;
+ $self->setCase(0);
+ $self->setIgnoreDiacritics(0);
+ return $self;
+ }
+
+ sub clear
+ {
+ my $self = shift;
+
+ $self->{cleared} = 1;
+
+ $self->{info} = {};
+ $self->{currentSearch} = [];
+ }
+
+ sub getComparisonFunction
+ {
+ my ($self, $type) = @_;
+ my ($comparison, $numeric) = @$type;
+ $numeric = 1 if $numeric eq 'true';
+ $numeric = 0 if $numeric eq 'false';
+
+ if ($comparison eq 'eq')
+ {
+ return \&compareExact;
+ }
+ elsif ($comparison eq 'contain')
+ {
+ return \&compareContain;
+ }
+ elsif ($comparison eq 'notcontain')
+ {
+ return \&compareNotContain;
+ }
+ elsif ($comparison eq 'lt')
+ {
+ return \&compareLessStrings if (!$numeric);
+ return \&compareLessNumbers if ($numeric);
+ }
+ elsif ($comparison eq 'le')
+ {
+ return \&compareLessOrEqualStrings if (!$numeric);
+ return \&compareLessOrEqualNumbers if ($numeric);
+ }
+ elsif ($comparison eq 'gt')
+ {
+ return \&compareGreaterStrings if (!$numeric);
+ return \&compareGreaterNumbers if ($numeric);
+ }
+ elsif ($comparison eq 'ge')
+ {
+ return \&compareGreaterOrEqualStrings if (!$numeric);
+ return \&compareGreaterOrEqualNumbers if ($numeric);
+ }
+ elsif ($comparison eq 'range')
+ {
+ return \&compareRangeStrings if (!$numeric);
+ return \&compareRangeNumbers if ($numeric);
+ }
+ elsif ($comparison eq 'regexp')
+ {
+ return \&compareRegexp;
+ }
+ }
+
+ sub setFilter
+ {
+ my ($self, $filter, $value, $type, $model, $add) = @_;
+ if (!$filter)
+ {
+ $self->clear;
+ return;
+ }
+ if ($value eq '')
+ {
+ delete $self->{info}->{$filter};
+ }
+ else
+ {
+ $self->{cleared} = 0;
+ my $preprocess = $type->[2];
+ if (!$preprocess)
+ {
+ my $fieldType = $model->{fieldsInfo}->{$filter}->{type};
+ $preprocess = ($fieldType eq 'date') ? 'reverseDate'
+ : ($fieldType eq 'number') ? 'noNullNumber'
+ : ($fieldType eq 'single list') ? 'singleList'
+ : ($fieldType eq 'double list') ? 'doubleList'
+ : ($fieldType eq 'triple list') ? 'otherList'
+ : ($fieldType =~ /list$/o) ? 'singleList'
+ : '';
+ }
+ my $info = {
+ value => $value,
+ comp => $self->getComparisonFunction($type),
+ preprocess => $preprocess,
+ temporary => $add
+ };
+ if ($add)
+ {
+ push @{$self->{info}->{$filter}}, $info;
+ }
+ else
+ {
+ $self->{info}->{$filter} = [$info];
+ }
+ push @{$self->{currentSearch}}, {
+ field => $filter,
+ value => $value,
+ filter => $type
+ };
+ }
+ }
+
+ sub removeTemporaryFilters
+ {
+ my $self = shift;
+ foreach (keys %{$self->{info}})
+ {
+ foreach my $i(0 .. scalar @{$self->{info}->{$_}} - 1)
+ {
+ delete $self->{info}->{$_}->[$i] if $self->{info}->{$_}->[$i]->{temporary};
+ }
+ }
+ }
+
+ sub setModel
+ {
+ my ($self, $model) = @_;
+
+ $self->{model} = $model;
+ }
+
+ sub test
+ {
+ my ($self, $info) = @_;
+ return 1 if $self->{cleared};
+ my $testAnd = $self->{mode} eq 'and';
+
+ foreach my $field(keys %{$self->{info}})
+ {
+ my $value = '';
+ if ($field eq $GCFieldSelector::anyFieldValue)
+ {
+ # We concatenate all of the values here to perform the
+ # the test on all of the fields in one shot
+ foreach my $key(keys %$info)
+ {
+ if (ref($info->{$key}) eq 'ARRAY')
+ {
+ $value .= GCPreProcess::otherList($info->{$key});
+ }
+ else
+ {
+ $value .= $info->{$key};
+ }
+ }
+ }
+ else
+ {
+ $value = $info->{$field};
+ }
+ foreach my $filter(@{$self->{info}->{$field}})
+ {
+ next if !$filter;
+ if ($filter->{preprocess})
+ {
+ my $preProcess = 'GCPreProcess::'.$filter->{preprocess};
+ eval {
+ no strict qw/refs/;
+ $value = $preProcess->($value);
+ };
+ }
+ my $reference;
+
+ if ($self->{ignoreDiacritics})
+ {
+ # Transform diacritics into single characters
+ # e.g. é -> e; ç -> c
+ # First it normalizes the string to have 2 characters
+ # instead of only one. And then it removes the modifiers
+ ($reference = NFKD($filter->{value})) =~ s/\pm//g;
+ ($value = NFKD($value)) =~ s/\pm//g;
+ }
+ else
+ {
+ $reference = $filter->{value};
+ }
+ if (!$self->{case})
+ {
+ $reference = uc $reference;
+ $value = uc $value;
+ }
+
+ if ($testAnd)
+ {
+ return 0 if ! $filter->{comp}->($value, $reference);
+ }
+ else
+ {
+ return 1 if $filter->{comp}->($value, $reference);
+ }
+ }
+ }
+ return $testAnd;
+ }
+
+ sub setMode
+ {
+ my ($self, $mode) = @_;
+ $mode ||= 'and';
+ #*test = \&testAnd if $mode eq 'and';
+ #*test = \&testOr if $mode eq 'or';
+ $self->{mode} = $mode;
+ }
+
+ sub setCase
+ {
+ my ($self, $case) = @_;
+ $self->{case} = $case;
+ }
+
+ sub setIgnoreDiacritics
+ {
+ my ($self, $id) = @_;
+ $self->{ignoreDiacritics} = $id;
+ }
+
+ sub getCurrentSearch
+ {
+ my $self = shift;
+ return {mode => $self->{mode},
+ info => $self->{currentSearch},
+ case => $self->{case},
+ ignoreDiacritics => $self->{ignoreDiacritics}};
+ }
+
+ sub isEmpty
+ {
+ my $self = shift;
+ return $self->{cleared};
+ }
+}
+
+use Gtk2;
+
+{
+ package GCSearchDialog;
+
+ use GCGraphicComponents::GCBaseWidgets;
+
+ use base 'GCModalDialog';
+
+ sub initValues
+ {
+ my $self = shift;
+
+ my $info = $self->{parent}->{filterSearch}->{info};
+
+ foreach (@{$self->{fields}})
+ {
+ if (exists $info->{$_})
+ {
+ $self->{$_}->setValue($info->{$_}->[0]->{value});
+ }
+ else
+ {
+ $self->{$_}->clear if $self->{$_};
+ }
+ if ($self->{fieldsInfo}->{$_}->{type} eq 'history text')
+ {
+ $self->{$_}->setValues($self->{parent}->{panel}->{$_}->getValues);
+ }
+ if (
+ (
+ ($self->{fieldsInfo}->{$_}->{type} eq 'single list')
+ ||
+ ($self->{fieldsInfo}->{$_}->{type} eq 'double list')
+ )
+ &&
+ (
+ $self->{parent}->{panel}->{$_}->{withHistory}
+ )
+ )
+ {
+ my @values;
+ foreach ($self->{parent}->{panel}->{$_}->getValues)
+ {
+ push @values, $_->[0];
+ }
+ $self->{$_}->setValues(@values);
+ }
+ }
+ }
+
+ sub show
+ {
+ my $self = shift;
+
+ $self->initValues;
+ $self->SUPER::show();
+ $self->show_all;
+ $self->activateOkButton($self->{notEmpty});
+ $self->activateExtraButton($self->{notEmpty});
+ $self->{search} = undef;
+ my $ended = 0;
+ while (!$ended)
+ {
+ my $response = $self->run;
+ if ($response eq 'ok')
+ {
+ my %info;
+
+ foreach (@{$self->{fields}})
+ {
+ $info{$_} = $self->{$_}->getValue
+ if ! $self->{$_}->isEmpty;
+ }
+
+ $self->{parent}->{menubar}->initFilters(\%info);
+
+ $self->{search} = \%info;
+ }
+ if (($response eq 'ok') || ($response eq 'cancel') || ($response eq 'delete-event'))
+ {
+ $ended = 1
+ }
+ elsif ($response eq 'reject')
+ {
+ $self->clear;
+ }
+ }
+ $self->hide;
+ }
+
+ sub clear
+ {
+ my $self = shift;
+ foreach (@{$self->{fields}})
+ {
+ $self->{$_}->clear;
+ }
+ }
+
+ sub search
+ {
+ my $self = shift;
+
+ return $self->{search};
+ }
+
+ sub new
+ {
+ my ($proto, $parent, $specialOK, @extraButtons) = @_;
+ my $class = ref($proto) || $proto;
+ my $self;
+ if ($specialOK)
+ {
+ $self = $class->SUPER::new($parent,
+ $parent->{lang}->{SearchTitle},
+ $specialOK,
+ );
+ }
+ else
+ {
+ $self = $class->SUPER::new($parent,
+ $parent->{lang}->{SearchTitle},
+ 'gtk-find',
+ 0,
+ @extraButtons,
+ 'gtk-clear' => 'reject',
+ );
+ }
+ bless ($self, $class);
+ $self->set_position('center-on-parent');
+ $self->{parent} = $parent;
+
+ # These ones are required for createWidget
+ $self->{lang} = $parent->{lang};
+ $self->{options} = $parent->{options};
+ $self->{window} = $self;
+
+ $self->{comparisonConvertor} = new GCComparisonSelector($parent);
+ $self->{layoutTable} = new Gtk2::Table(1,3,0);
+ $self->{layoutTable}->set_row_spacings($GCUtils::halfMargin);
+ $self->{layoutTable}->set_col_spacings($GCUtils::margin);
+ $self->{layoutTable}->set_border_width($GCUtils::margin);
+
+ $self->vbox->pack_start($self->{layoutTable}, 1, 1, 0);
+ return $self;
+ }
+
+ sub setModel
+ {
+ my ($self, $model) = @_;
+ $self->{model} = $model;
+ foreach ($self->{layoutTable}->get_children)
+ {
+ $self->{layoutTable}->remove($_);
+ $_->destroy;
+ }
+
+ my $fieldsInfo = $model->{fieldsInfo};
+ $self->{fieldsInfo} = $fieldsInfo;
+ my @filtersGroup = @{$model->{filtersGroup}};
+ my @filtersTotal = @{$model->{filters}};
+
+ my $row = 0;
+ my $nbLines = @filtersTotal + (2 * @filtersGroup);
+ if ($nbLines <= 0)
+ {
+ $self->{notEmpty} = 0;
+ $self->{layoutTable}->resize(1, 1);
+ my $label = new GCLabel($self->{parent}->{lang}->{SearchNoField});
+ $self->{layoutTable}->attach($label, 0, 1, 0, 1, 'expand', 'expand', $GCUtils::margin, $GCUtils::margin);
+ return;
+ }
+ $self->{notEmpty} = 1;
+ $self->{layoutTable}->resize($nbLines, 3);
+
+ $self->{fields} = [];
+ foreach my $group(@filtersGroup)
+ {
+ $row++;
+ my @filters = @{$group->{filter}};
+ my $label = new GCHeaderLabel($model->getDisplayedText($group->{label}));
+ $self->{layoutTable}->attach($label, 0, 3, $row, $row + 1, 'fill', 'expand', 0, 0);
+ $row++;
+ my $withComparisonLabel;
+ foreach my $filter(@filters)
+ {
+ my $field = $filter->{field};
+ if ($field ne 'separator')
+ {
+ push @{$self->{fields}}, $field;
+ my $labelText = $fieldsInfo->{$field}->{displayed};
+ $labelText = $model->getDisplayedText($filter->{label}) if $filter->{label};
+ my $label = new GCLabel($labelText);
+ $self->{layoutTable}->attach($label, 0, 1, $row, $row + 1, 'fill', 'fill', 2 * $GCUtils::margin, 0);
+
+ ($self->{$field}, $withComparisonLabel) =
+ GCBaseWidgets::createWidget($self, $fieldsInfo->{$field},
+ $filter->{comparison});
+ $self->{$field}->signal_connect('activate' => sub {$self->response('ok')} )
+ if $self->{$field}->isa('GCShortText');
+ if ($withComparisonLabel
+ && ($filter->{comparison} ne 'eq'))
+ {
+ my $labelComparison = new GCLabel(
+ $self->{comparisonConvertor}->valueToDisplayed($filter->{comparison}),
+ 1
+ );
+ $self->{layoutTable}->attach($labelComparison, 1, 2, $row, $row + 1, 'fill', 'fill', 0, 0);
+ }
+ $self->{layoutTable}->attach($self->{$field}, 2, 3, $row, $row + 1, ['fill', 'expand'], 'expand', 0, 0);
+ $self->{$field}->grab_focus if $row == 2;
+ }
+ else
+ {
+ $self->{layoutTable}->attach(Gtk2::HSeparator->new, 0, 3, $row, $row + 1, 'fill', 'fill', 0, 0);
+ }
+ $row++;
+ }
+ }
+ $self->{layoutTable}->show_all;
+ }
+}
+
+{
+ package GCAdvancedSearchDialog;
+
+ use GCGraphicComponents::GCBaseWidgets;
+
+ use base 'GCSearchDialog';
+
+ sub addItem
+ {
+ my $self = shift;
+ $self->{layoutTable}->resize($self->{nbFields} + 1, 3);
+ my $field = new GCFieldSelector(0, $self->{model}, 0, 1);
+ $field->{number} = $self->{nbFields};
+ push @{$self->{fields}}, $field;
+ $self->{layoutTable}->attach($field, 0, 1, $self->{nbFields}, $self->{nbFields}+1,
+ ['expand', 'fill'], 'fill', 0, 0);
+ $field->show_all;
+
+ my $comp = new GCComparisonSelector($self->{parent});
+ $field->signal_connect('changed' => sub {
+ my ($fs, $cs) = @_;
+ $self->updateField($fs, $cs->getValue);
+ }, $comp);
+ $comp->signal_connect('changed' => sub {
+ my ($cs, $fs) = @_;
+ $self->updateField($fs, $cs->getValue);
+ }, $field);
+ push @{$self->{comps}}, $comp;
+ $self->{layoutTable}->attach($comp, 1, 2, $self->{nbFields}, $self->{nbFields}+1,
+ ['expand', 'fill'], 'fill', 0, 0);
+ $comp->show_all;
+
+ my $value = new GCShortText;
+ push @{$self->{values}}, $value;
+ $self->{layoutTable}->attach($value, 2, 3, $self->{nbFields}, $self->{nbFields}+1,
+ ['expand', 'fill'], 'fill', 0, 0);
+ $value->show_all;
+ $self->{remove}->set_sensitive(1);
+ $self->{nbFields}++;
+ }
+
+ sub removeItem
+ {
+ my $self = shift;
+ $self->{layoutTable}->remove(pop @{$self->{fields}});
+ $self->{layoutTable}->remove(pop @{$self->{comps}});
+ $self->{layoutTable}->remove(pop @{$self->{values}});
+ delete $self->{isNumeric}->[$self->{nbFields}];
+ $self->{layoutTable}->resize(--$self->{nbFields}, 3);
+ $self->{remove}->set_sensitive(0) if $self->{nbFields} < 2;
+ }
+
+ sub generateSearch
+ {
+ my $self = shift;
+ my @info;
+ my $i = 0;
+ foreach (@{$self->{fields}})
+ {
+ my $field = $_->getValue;
+ next if !$field;
+ my $numeric = 'false';
+ if ($self->{model}->{fieldsInfo}->{$field}->{type} eq 'number')
+ {
+ $numeric = 'true';
+ }
+ # We check we still have the same field in case it was changed
+ elsif ($self->{isNumeric}->[$i]->[0] eq $field)
+ {
+ $numeric = $self->{isNumeric}->[$i]->[1];
+ }
+ push @info, {
+ field => $field,
+ value => $self->{values}->[$i]->getValue,
+ filter => [$self->{comps}->[$i]->getValue,
+ $numeric,
+ undef]
+ }
+ if ! $self->{values}->[$i]->isEmpty;
+ $i++;
+ }
+ $self->{search} = \@info;
+ }
+
+ sub initSearch
+ {
+ my ($self, $filter) = @_;
+ if ($filter->{mode} eq 'and')
+ {
+ $self->{testAnd}->set_active(1);
+ }
+ elsif ($filter->{mode} eq 'or')
+ {
+ $self->{testOr}->set_active(1);
+ }
+ $self->{useCase}->set_active($filter->{case});
+ $self->{ignoreDiacritics}->set_active($filter->{ignoreDiacritics});
+ $self->removeItem while $self->{nbFields} > 1;
+ $self->{isNumeric} = [];
+ my $first = 1;
+ foreach my $line(@{$filter->{info}})
+ {
+ $self->addItem if !$first;
+ $first = 0;
+ $self->{fields}->[-1]->setValue($line->{field});
+ $self->{comps}->[-1]->setValue($line->{filter}->[0]);
+ $self->{values}->[-1]->setValue($line->{value});
+ # We also add the field name to be able to check it later
+ push @{$self->{isNumeric}}, [$line->{field}, $line->{filter}->[1]];
+ }
+ }
+
+ sub show
+ {
+ my $self = shift;
+
+ $self->show_all;
+ # If saving the search is not possible, hides the corresponding button
+ if (!$self->{canSave})
+ {
+ ($self->action_area->get_children)[3]->hide;
+ }
+ $self->{search} = undef;
+ my $ended = 0;
+ while (!$ended)
+ {
+ my $response = $self->run;
+ if ($response eq 'ok')
+ {
+ if ($self->{userFilter})
+ {
+ $self->saveSearch;
+ }
+ else
+ {
+ $self->generateSearch;
+ }
+ }
+ else
+ {
+ $self->{search} = undef;
+ }
+ $ended = 1 if ($response eq 'ok') || ($response eq 'cancel') || ($response eq 'delete-event');
+ $self->clear if ($response eq 'reject');
+ $self->saveSearch if ($response eq 'accept');
+ }
+ $self->hide;
+ }
+
+ sub getMode
+ {
+ my $self = shift;
+ return ($self->{testAnd}->get_active ? 'and' : 'or');
+ }
+
+ sub getCase
+ {
+ my $self = shift;
+ return ($self->{useCase}->get_active ? 1 : 0);
+ }
+
+ sub getIgnoreDiacritics
+ {
+ my $self = shift;
+ return ($self->{ignoreDiacritics}->get_active ? 1 : 0);
+ }
+
+ sub saveSearch
+ {
+ my $self = shift;
+
+ my $response;
+ my $name;
+ if ($self->{userFilter} eq 'edit')
+ {
+ $response = 'ok';
+ $name = '';
+ }
+ else
+ {
+ my $dialog = new Gtk2::Dialog($self->{parent}->{lang}->{AdvancedSearchSaveTitle},
+ $self,
+ [qw/modal destroy-with-parent/],
+ @GCDialogs::okCancelButtons
+ );
+
+ my $hbox = Gtk2::HBox->new(0, $GCUtils::halfMargin);
+ my $label = Gtk2::Label->new($self->{parent}->{lang}->{AdvancedSearchSaveName});
+ $hbox->pack_start($label, 0, 0, 0);
+ my $entry = Gtk2::Entry->new;
+ $hbox->pack_start($entry, 1, 1, 0);
+ $hbox->show_all;
+ $dialog->vbox->pack_start($hbox, 1, 1, $GCUtils::margin);
+ $dialog->set_default_response('ok');
+ $entry->set_activates_default(1);
+ while (1)
+ {
+ $response = $dialog->run;
+ if ($response eq 'ok')
+ {
+ $name = $entry->get_text;
+ if ($self->{model}->existsUserFilter($name))
+ {
+ my $errorDialog = Gtk2::MessageDialog->new($self,
+ [qw/modal destroy-with-parent/],
+ 'error',
+ 'ok',
+ $self->{parent}->{lang}->{AdvancedSearchSaveOverwrite});
+ $dialog->set_position('center-on-parent');
+ $errorDialog->run;
+ $errorDialog->destroy;
+ next;
+ }
+ last;
+ }
+ last;
+ }
+ $dialog->destroy;
+ }
+
+ if ($response eq 'ok')
+ {
+ $self->generateSearch;
+ my $info = {
+ 'name' => $name,
+ 'mode' => $self->getMode,
+ 'case' => $self->getCase,
+ 'ignoreDiacritics' => $self->getIgnoreDiacritics,
+ 'info' => $self->{search}
+ };
+ $self->{parent}->addUserFilter($info);
+ }
+ }
+
+ sub new
+ {
+ my ($proto, $parent, $userFilter) = @_;
+ my $class = ref($proto) || $proto;
+ my $self;
+ if ($userFilter)
+ {
+ $self = $class->SUPER::new($parent, 'gtk-save');
+ }
+ else
+ {
+ $self = $class->SUPER::new($parent, undef, 'gtk-save' => 'accept',);
+ }
+ bless ($self, $class);
+
+ $self->{parent} = $parent;
+ $self->{userFilter} = $userFilter;
+ $self->vbox->remove($self->{layoutTable});
+
+ my $allTable = new Gtk2::Table(11,3,0);
+ $allTable->set_row_spacings($GCUtils::halfMargin);
+ $allTable->set_col_spacings($GCUtils::margin);
+ $allTable->set_border_width($GCUtils::margin);
+
+ my $labelType = new GCHeaderLabel($parent->{lang}->{AdvancedSearchType});
+ $self->{testAnd} = new Gtk2::RadioButton(undef, $parent->{lang}->{AdvancedSearchTypeAnd});
+ $self->{testOr} = new Gtk2::RadioButton($self->{testAnd}->get_group, $parent->{lang}->{AdvancedSearchTypeOr});
+
+ my $prefStock = Gtk2::Stock->lookup('gtk-preferences');
+ (my $prefLabel = $prefStock->{label}) =~ s/_//;
+ my $labelPreferences = new GCHeaderLabel($prefLabel);
+ $self->{useCase} = new GCCheckBox($parent->{lang}->{AdvancedSearchUseCase});
+ $self->{ignoreDiacritics} = new GCCheckBox($parent->{lang}->{AdvancedSearchIgnoreDiacritics});
+
+ my $offset1 = 0;
+ $offset1 = 4;
+ $allTable->attach($labelType, 0, 3, $offset1 + 0, $offset1 + 1, 'fill', 'fill', 0, 0);
+ $allTable->attach($self->{testAnd}, 2, 3, $offset1 + 1, $offset1 + 2, 'fill', 'fill', 0, 0);
+ $allTable->attach($self->{testOr}, 2, 3, $offset1 + 2, $offset1 + 3, 'fill', 'fill', 0, 0);
+ $allTable->attach($labelPreferences, 0, 3, $offset1 + 4, $offset1 + 5, 'fill', 'fill', 0, 0);
+ $allTable->attach($self->{useCase}, 2, 3, $offset1 + 5, $offset1 + 6, 'fill', 'fill', 0, 0);
+ $allTable->attach($self->{ignoreDiacritics}, 2, 3, $offset1 + 6, $offset1 + 7, 'fill', 'fill', 0, 0);
+
+ my $labelCriteria = new GCHeaderLabel($parent->{lang}->{AdvancedSearchCriteria});
+ my $scrolled = new Gtk2::ScrolledWindow;
+ $scrolled->set_policy ('never', 'automatic');
+ $scrolled->set_border_width(0);
+ $scrolled->set_shadow_type('none');
+ $scrolled->add_with_viewport($self->{layoutTable});
+
+ my $offset2 = 8;
+ $offset2 = 0;
+ $allTable->attach($labelCriteria, 0, 3, $offset2 + 0, $offset2 + 1, 'fill', 'fill', 0, 0);
+ $allTable->attach($scrolled, 2, 3, $offset2 + 1, $offset2 + 2, ['expand', 'fill'], ['expand', 'fill'], 0, 0);
+
+ my $hboxAction = new Gtk2::HBox(0,0);
+ $self->{add} = Gtk2::Button->new_from_stock('gtk-add');
+ $self->{add}->signal_connect('clicked' => sub {
+ $self->addItem;
+ });
+ $hboxAction->pack_start($self->{add}, 0, 0, 0);
+ $self->{remove} = Gtk2::Button->new_from_stock('gtk-remove');
+ $self->{remove}->signal_connect('clicked' => sub {
+ $self->removeItem;
+ });
+ $hboxAction->pack_start($self->{remove}, 0, 0, $GCUtils::margin);
+# if (!$userFilter)
+# {
+# $self->{save} = Gtk2::Button->new_from_stock('gtk-save');
+# $self->{save}->signal_connect('clicked' => sub {
+# $self->saveSearch;
+# });
+# $hboxAction->pack_end($self->{save}, 0, 0, 0);
+# }
+ $allTable->attach($hboxAction, 2, 3, $offset2 + 2, $offset2 + 3, 'fill', 'fill', 0, 0);
+
+ $self->vbox->pack_start($allTable,1,1,0);
+
+ $self->set_size_request(-1, 400);
+ return $self;
+ }
+
+ sub clear
+ {
+ my $self = shift;
+ $self->setModel($self->{model});
+ }
+
+ sub setModel
+ {
+ my ($self, $model) = @_;
+
+ $self->{model} = $model;
+ # Searches can only be saved for default collections or user collections with a name
+ # (i.e. when the model is not embedded within the collection).
+ $self->{canSave} = ($model->getName) ? 1 : 0;
+ $self->{nbFields} = 0;
+ $self->{fields} = [];
+ $self->{comps} = [];
+ $self->{values} = [];
+ foreach ($self->{layoutTable}->get_children)
+ {
+ $self->{layoutTable}->remove($_);
+ $_->destroy;
+ }
+ $self->addItem;
+ $self->{remove}->set_sensitive(0);
+ $self->{layoutTable}->show_all;
+ }
+
+ sub updateField
+ {
+ my ($self, $fs, $comparison) = @_;
+ my $idx = $fs->{number};
+ my $widget = $self->{values}->[$idx];
+ $self->{layoutTable}->remove($widget);
+
+ my $newWidget;
+ ($newWidget, undef) = $fs->createEntryWidget($self, $comparison, $widget);
+ $newWidget->signal_connect('activate' => sub {$self->response('ok')} )
+ if $newWidget->isa('GCShortText');
+
+ $self->{values}->[$idx] = $newWidget;
+ $self->{layoutTable}->attach($newWidget, 2, 3, $idx, $idx+1,
+ ['expand', 'fill'], 'fill', 0, 0);
+ $newWidget->show_all;
+ }
+}
+
+{
+ package GCUserFiltersDialog;
+
+ use GCGraphicComponents::GCBaseWidgets;
+ use Storable;
+ use base 'GCModalDialog';
+
+ sub setModel
+ {
+ my ($self, $model) = @_;
+ $self->{model} = $model;
+ $self->{filters} = Storable::dclone($model->getUserFilters);
+ $self->initList(1);
+ $self->{deletedFilters} = [];
+ }
+
+ sub initList
+ {
+ my ($self, $saved) = shift;
+ @{$self->{filtersList}->{data}} = ();
+ $self->{initializing} = 1;
+ $self->{filters} = $self->getUserFilters;
+ #my @sorted = sort {uc($a->{name}) cmp uc($b->{name})} @{$self->{filters}};
+ #$self->{filters} = \@sorted;
+ foreach(@{$self->{filters}})
+ {
+ push @{$self->{filtersList}->{data}}, $_->{name};
+ # All of them should be already saved
+ $_->{saved} = 1 if $saved;
+ }
+ $self->{initializing} = 0;
+ }
+
+ sub getUserFilters
+ {
+ my $self = shift;
+ my @sorted = sort {uc($a->{name}) cmp uc($b->{name})} @{$self->{filters}};
+ return \@sorted;
+ return $self->{filters};
+ }
+
+ sub getDeletedFilters
+ {
+ my $self = shift;
+ return $self->{deletedFilters};
+ }
+
+ sub show
+ {
+ my $self = shift;
+
+ $self->SUPER::show();
+ $self->show_all;
+ my $response = $self->run;
+ $self->hide;
+ return ($response eq 'ok');
+ }
+
+ # Callbacked by advanced search dialog
+ sub addUserFilter
+ {
+ my ($self, $filter) = @_;
+ $filter->{saved} = 0;
+ if ($self->{mode} eq 'new')
+ {
+ push (@{$self->{filters}}, $filter);
+ push @{$self->{filtersList}->{data}}, $filter->{name};
+ }
+ else
+ {
+ my $selected = ($self->{filtersList}->get_selected_indices)[0];
+ $filter->{name} = $self->{filters}->[$selected]->{name};
+ $self->{filters}->[$selected] = $filter;
+ }
+ }
+
+ sub newFilter
+ {
+ my $self = shift;
+ $self->{mode} = 'new';
+ $self->{panel} = $self->{parent}->{panel};
+ my $dialog = new GCAdvancedSearchDialog($self, $self->{mode});
+ $dialog->setModel($self->{model});
+ $dialog->show;
+ # To avoid unwanted reference if the panel is changed
+ delete $self->{panel};
+ }
+
+ sub editFilter
+ {
+ my $self = shift;
+ $self->{mode} = 'edit';
+ my $selected = ($self->{filtersList}->get_selected_indices)[0];
+ $self->{panel} = $self->{parent}->{panel};
+ my $dialog = new GCAdvancedSearchDialog($self, $self->{mode});
+ $dialog->setModel($self->{model});
+ $dialog->initSearch($self->{filters}->[$selected]);
+ $dialog->show;
+ # To avoid unwanted reference if the panel is changed
+ delete $self->{panel};
+ }
+
+ sub deleteFilter
+ {
+ my $self = shift;
+ my $selected = ($self->{filtersList}->get_selected_indices)[0];
+ push @{$self->{deletedFilters}}, $self->{filters}->[$selected]->{name};
+ splice @{$self->{filters}}, $selected, 1;
+ splice (@{$self->{filtersList}->{data}}, $selected, 1);
+ $self->{filtersList}->select(0);
+ }
+
+ sub renameFilter
+ {
+ my $self = shift;
+ my $selected = ($self->{filtersList}->get_selected_indices)[0];
+ my $oldName = $self->{filters}->[$selected]->{name};
+ my $newName = $self->{filtersList}->{data}->[$selected]->[0];
+ return if $newName eq $oldName;
+ $self->{filters}->[$selected]->{name} = $newName;
+ $self->{filters}->[$selected]->{saved} = 0;
+ push @{$self->{deletedFilters}}, $oldName;
+ $self->initList(0);
+ }
+
+ sub new
+ {
+ my ($proto, $parent) = @_;
+ my $class = ref($proto) || $proto;
+ my $self = $class->SUPER::new($parent,
+ $parent->{lang}->{MenuSavedSearchesEdit});
+ bless ($self, $class);
+ $self->set_position('center-on-parent');
+ $self->{parent} = $parent;
+ $self->{lang} = $parent->{lang};
+ my $hBox = new Gtk2::HBox(0,0);
+ $hBox->set_border_width($GCUtils::margin);
+ my $buttonBox = new Gtk2::VBox(0,0);
+
+ my $editButton = new Gtk2::Button->new_from_stock('gtk-edit');
+ $editButton->signal_connect('clicked' => sub {
+ $self->editFilter;
+ });
+ my $newButton = new Gtk2::Button->new_from_stock('gtk-new');
+ $newButton->signal_connect('clicked' => sub {
+ $self->newFilter;
+ });
+ my $deleteButton = new Gtk2::Button->new_from_stock('gtk-delete');
+ $deleteButton->signal_connect('clicked' => sub {
+ $self->deleteFilter;
+ });
+
+ $buttonBox->pack_start($newButton, 0, 0, $GCUtils::halfMargin);
+ $buttonBox->pack_start($deleteButton, 0, 0, $GCUtils::halfMargin);
+ $buttonBox->pack_start($editButton, 0, 0, $GCUtils::halfMargin);
+
+ $self->{filtersList} = new Gtk2::SimpleList($parent->{lang}->{MenuSavedSearches} => 'text');
+ $self->{filtersList}->set_column_editable(0, 1);
+ $self->{filtersList}->get_model->signal_connect("row-changed" => sub {
+ return if $self->{initializing};
+ $self->renameFilter;
+ });
+
+ my $scroller = new Gtk2::ScrolledWindow;
+ $scroller->set_policy ('automatic', 'automatic');
+ $scroller->set_shadow_type('etched-in');
+ $scroller->add($self->{filtersList});
+
+ $hBox->pack_start($scroller, 1, 1, $GCUtils::margin);
+ $hBox->pack_start($buttonBox, 0, 0, $GCUtils::margin);
+
+ $self->vbox->pack_start($hBox, 1, 1, 0);
+ $self->set_default_size(400, 400);
+ return $self;
+ }
+}
+
+1;