package GCBaseWidgets; ################################################### # # 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 utf8; use Gtk2; #use GCBorrowings; use Encode; use strict; our @videoExtensions = ('.aaf','.3gp','.asf','.avi','.flv','.m1v','.m2v','.m4v','.mkv','.mov', '.mp4','.mpeg','.mpg','.mpe','.mxf','.nsv','.ogg','.ogv','.rm','.swf','.wmv', '.iso'); our @ebookExtensions = ('.txt','.htm','.html','.azw','.opf','.tr2','.tr3','.aeh','.fb2','.chm', '.pdf','.ps','.djvu','.lit','.pdb','.dnl','.xeb','.ceb','.lbr','.prc', '.mobi','.epub','.lrf','.lrx','.pdg','.doc','.odt','.cbr','.cbz','.djvu'); our @audioExtensions = ('.m3u','.pls','.asx','.wax','.wvx','.b4s','.kpl','.ram','.smil','.iso', '.cue','.bin','.mp3','.ogg','.oga','.flac'); sub createWidget { my ($parent, $info, $comparison) = @_; my $widget; my $withComparisonLabel = 1; if ($info->{type} eq 'short text') { if ($comparison eq 'range') { $widget = new GCRange('text', $parent->{lang}); $widget->setWidth(16); $withComparisonLabel = 0; } else { $widget = new GCShortText; } } elsif ($info->{type} eq 'number') { #If we want to have values that are less to the specified one, #we use max as default to be sure everything will be returned #in that case. my $default = $info->{min}; $default = $info->{max} if $comparison =~ /^l/; if (exists $info->{min}) { if ($comparison eq 'range') { $widget = new GCRange('number', $parent->{lang}, $info->{min}, $info->{max}, $info->{step}); $widget->setWidth(16); $withComparisonLabel = 0; } else { $widget = new GCNumeric($default, $info->{min}, $info->{max}, $info->{step}); } } else { if ($comparison eq 'range') { $widget = new GCRange('numeric text', $parent->{lang}); $widget->setWidth(16); $withComparisonLabel = 0; } else { $widget = new GCCheckedText('0-9.'); } } } elsif ($info->{type} eq 'checked text') { $widget = new GCCheckedText($info->{format}); } elsif (($info->{type} eq 'history text') || (($info->{type} =~ /list/) && ($info->{history} ne 'false'))) { $widget = new GCHistoryText; } elsif ($info->{type} eq 'options') { $widget = new GCMenuList; $widget->setValues($parent->{model}->getValues($info->{values}), $info->{separator}); } elsif ($info->{type} eq 'yesno') { $widget = new GCCheckBoxWithIgnore($parent); $withComparisonLabel = 0; } elsif ($info->{type} eq 'date') { if ($comparison eq 'range') { $widget = new GCRange('date', $parent->{lang}, undef, undef, undef, $parent); $widget->setWidth(16); $withComparisonLabel = 0; } else { $widget = new GCDate($parent->{window}, $parent->{lang}, 1, $parent->{options}->dateFormat); } } else { $widget = new GCShortText; } return ($widget, $withComparisonLabel); } { package GCGraphicComponent; use base 'Exporter'; our @EXPORT = qw($somethingChanged); our $somethingChanged = 0; sub expand { } sub lock { } sub getMainParent { my $self = shift; return if ! $self->{parent}; my $tmpWidget = $self; $tmpWidget = $tmpWidget->{parent} while $tmpWidget && (! $tmpWidget->isa('GCFrame')); $self->{mainParent} = $tmpWidget; } sub acceptMarkup { my $self = shift; return 0; } sub cleanMarkup { my ($self, $text, $encodeSubset) = @_; $text =~ s|
|\n|g; if ($encodeSubset) { # Encode only the characters set_markup has issues with $text =~ s|&|&|g; $text =~ s|<|<|g; $text =~ s|>|>|g; } else { $text = GCUtils::encodeEntities($text) } return $text; } sub selectAll { } sub getTagFromSpan { my ($self, $desc) = @_; my @result = (); #('background' => $self->{background}); my @keyvalues = split / /, $desc; foreach (@keyvalues) { /([^=]*)=(.*)/; my $key = $1; my $value = $2; $value =~ s/('|")//g; #"' next if $key =~ /=/; push @result, ($key, $value); } return @result; } sub valueToDisplayed { # 1st parameter is self shift; # Here we don't change the value; return shift; } sub hasChanged { my $self = shift; return $self->{hasChanged}; } sub setChanged { my $self = shift; $self->{hasChanged} = 1; $somethingChanged = 1; } sub setWidth { my ($self, $value) = @_; } sub setHeight { my ($self, $height) = @_; } sub resetChanged { my $self = shift; $self->{hasChanged} = 0; } sub activateStateTracking { # We do nothing by default # Other widget should connect a signal handler or something similar # when the content has been changed } sub getLinkedValue { } sub setLinkedValue { } sub setLinkedComponent { my ($self, $linked) = @_; $self->{linkedComponent} = $linked; } } { package GCPseudoHistoryComponent; # # This is an abstract package handling a little history # sub initHistory { my ($self, $listType) = @_; $self->{history} = {}; # listType contains the type of the original field: # 0: No list # >0: Multiple list (number of columns) $self->{listType} = $listType; $self->{history}->{0} = {'' => 1}; for(my $i = 1; $i < $listType; $i++) { $self->{history}->{$i} = {'' => 1}; } $self->{listType} = $listType; } sub addHistory { my $self = shift; my $value = shift; my $i; if (ref($value) eq 'ARRAY') { foreach (@$value) { $i = 0; foreach my $item(@$_) { $self->{history}->{$i}->{$item} = 1; $i++; } } } else { # The separator used was ; instead of , $value =~ s/;/,/g if $value !~ /,/; my @values = split m/,/, $value; foreach (@values) { my @items = split m/;/; $i = 0; foreach my $item(@items) { $self->{history}->{$i}->{$item} = 1; $i++; } } } } sub setDropDown { } sub getValues { my $self = shift; my @array; if ($self->{listType} < 1) { @array = sort keys %{$self->{history}->{0}}; } else { foreach (sort keys %{$self->{history}}) { my @tmpArray = sort keys %{$self->{history}->{$_}}; push @array, \@tmpArray; } } return \@array; } sub setValues { my ($self, $values) = @_; if ($self->{listType} == 0) { $self->{history} = {}; $self->{history}->{0}->{$_} = 1 foreach (@$values); } else { $self->{history} = {}; for (my $i = 0; $i < $self->{listType}; $i++) { $self->{history}->{$i}->{$_} = 1 foreach (@{$values->[$i]}); } } } } { package GCLinkedComponent; @GCLinkedComponent::ISA = ('GCGraphicComponent'); sub new { my ($proto, $linked) = @_; my $class = ref($proto) || $proto; my $self = {linked => $linked}; bless ($self, $class); $linked->setLinkedComponent($self); return $self; } sub setValue { my $self = shift; $self->setChanged; return $self->{linked}->setLinkedValue(@_); } sub getValue { my $self = shift; return $self->{linked}->getLinkedValue(@_); } sub resetChanged { my $self = shift; $self->{hasChanged} = 0; return $self->{linked}->resetChanged(@_); } sub hide { my $self = shift; $self->{linked}->setLinkedActivated(0); } sub show { my $self = shift; $self->{linked}->setLinkedActivated(1); } } { package GCShortText; use Glib::Object::Subclass Gtk2::Entry:: ; @GCShortText::ISA = ('Gtk2::Entry', 'GCGraphicComponent'); sub new { my ($proto) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); return $self; } sub activateStateTracking { my $self = shift; return if $self->{readOnly}; $self->signal_connect('changed' => sub {; $self->setChanged; }); } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub selectAll { my $self = shift; $self->select_region(0, length($self->getValue)); $self->grab_focus; } sub getValue { my $self = shift; return $self->get_text; } sub setValue { my ($self, $value) = @_; $self->set_text($value); } sub clear { my $self = shift; $self->setValue(''); } sub setWidth { my ($self, $value) = @_; $self->set_width_chars($value); } sub setReadOnly { my $self = shift; $self->set_editable(0); $self->{readOnly} = 1; } sub lock { my ($self, $locked) = @_; return if $self->{readOnly}; #$self->can_focus(!$locked); $self->set_editable(!$locked); } } our $hasSpellChecker; BEGIN { eval 'use Gtk2::Spell'; if ($@) { $hasSpellChecker = 0; } else { $hasSpellChecker = 1; } } { package GCLongText; use Glib::Object::Subclass Gtk2::ScrolledWindow:: ; @GCLongText::ISA = ('Gtk2::ScrolledWindow', 'GCGraphicComponent'); sub new { my ($proto) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; $self->{text} = new Gtk2::TextView; $self->{text}->set_editable(1); $self->{text}->set_wrap_mode('word'); $self->set_border_width(0); $self->set_shadow_type('in'); $self->set_policy('automatic', 'automatic'); #$self->set_size_request(-1,80); $self->add($self->{text}); $self->{spellChecker} = 0; bless ($self, $class); return $self; } sub activateStateTracking { my $self = shift; $self->{text}->get_buffer->signal_connect('changed' => sub {; $self->setChanged; }); } sub setSpellChecking { my ($self, $activate, $lang) = @_; return if ! $GCGraphicComponents::hasSpellChecker; if ($activate) { $lang ||= $ENV{LANG}; if ($self->{spellChecker}) { return if $lang eq $self->{lang}; $self->setSpellChecking(0) } eval { $self->{spellChecker} = Gtk2::Spell->new_attach($self->{text}); $self->{spellChecker}->set_language($lang); $self->{lang} = $lang; }; if ($@) { $self->setSpellChecking(0); } } else { $self->{spellChecker}->detach if $self->{spellChecker}; $self->{spellChecker} = 0; } } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub getValue { my $self = shift; my $buffer = $self->{text}->get_buffer; my $text = $buffer->get_text($buffer->get_start_iter, $buffer->get_end_iter, 1); #$text =~s/\n//g; return $text; } sub setValue { my ($self, $text) = @_; #$text =~s//\n/g; $text = '' if !defined $text; $self->{text}->get_buffer->set_text($text); } sub clear { my $self = shift; $self->setValue(''); } sub lock { my ($self, $locked) = @_; $self->{text}->can_focus(!$locked); } sub setHeight { my ($self, $height) = @_; # TODO Change height } } { package GCHistoryText; use Glib::Object::Subclass Gtk2::Combo:: ; @GCHistoryText::ISA = ('Gtk2::Combo', 'GCGraphicComponent'); sub new { my ($proto) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; $self->{history} = {'' => 1}; # Settings for auto-completion $self->{completionModel} = Gtk2::ListStore->new('Glib::String'); $self->{completion} = Gtk2::EntryCompletion->new; $self->{completion}->set_model($self->{completionModel}); $self->{completion}->set_text_column(0); $self->entry->set_completion($self->{completion}); bless ($self, $class); return $self; } sub activateStateTracking { my $self = shift; $self->entry->signal_connect('changed' => sub {; $self->setChanged; }); } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub selectAll { my $self = shift; $self->entry->select_region(0, -1); $self->entry->grab_focus; } sub getValue { my $self = shift; my @children = $self->get_children; return $children[0]->get_text if $children[0]; } sub setValue { my ($self, $text) = @_; my @children = $self->get_children; $children[0]->set_text($text); } sub clear { my $self = shift; $self->setValue(''); } sub setWidth { my ($self, $value) = @_; ($self->get_children)[0]->set_width_chars($value); } sub lock { my ($self, $locked) = @_; ($self->get_children)[0]->can_focus(!$locked); ($self->get_children)[1]->set_sensitive(!$locked); } sub addHistory { my $self = shift; my $value = (scalar @_) ? shift : $self->getValue; my $noUpdate = shift; $value =~ s/^\s*//; if (!exists $self->{history}->{$value}) { $self->{history}->{$value} = 1; if (!$noUpdate) { $self->setDropDown(sort keys %{$self->{history}}); } } } sub setDropDown { my $self = shift; my @values = (scalar @_) ? @_ : sort keys %{$self->{history}}; my $previousValue = $self->getValue; # Update history list $self->set_popdown_strings(@values); # Restore value as it is lost when updating list $self->setValue($previousValue); # Update auto-completion list $self->{completionModel}->clear; foreach (@values) { my $iter = $self->{completionModel}->append; $self->{completionModel}->set($iter, 0 => $_); } } sub getValues { my $self = shift; my @array = sort keys %{$self->{history}}; return \@array; } sub setValues { my ($self, $values) = @_; $self->{history} = {}; $self->setDropDown(@$values); $self->{history}->{$_} = 1 foreach (@$values); } sub setActivate { my $value } sub popup { my $self = shift; ($self->get_children)[1]->grab_focus; ($self->get_children)[1]->signal_emit('activate'); } } { package GCNumeric; use Glib::Object::Subclass Gtk2::SpinButton:: ; @GCNumeric::ISA = ('Gtk2::SpinButton', 'GCRatingWidget', 'GCGraphicComponent'); use GCWidgets; use GCLang; sub new { my ($proto, $default, $min, $max, $step, $format) = @_; my $class = ref($proto) || $proto; my $self; if (($format eq 'text') || (!$format)) { # Standard numeric field $step = 1 if !$step; my $pageStep = 10 * $step; my $decimals = 0; $decimals = length($1) if $step =~ /\.(.*)$/; my $accel = 0; my $values = ($max - $min) / $step; $accel = 0.2 if $values > 100; $accel = 0.5 if $values > 500; $accel = 1.0 if $values > 2000; $default = 0 if $default eq ''; my $adj = Gtk2::Adjustment->new($default, $min, $max, $step, $pageStep, 0) ; $self = $class->SUPER::new($adj, $accel, $decimals); $self->{default} = $default; $self->{step} = $step; $self->{pageStep} = $pageStep; $self->{accel} = $accel; $self->{format} = 'text'; $self->set_numeric(1); } elsif ($format eq 'graphical') { # Graphical rating widget $default = 0 if $default eq ''; $max = 10 if $max eq ''; $self = GCRatingWidget->new (maxStars=>$max, rating=>$default, direction=>GCLang::languageDirection($ENV{LANG})); $self->{format} = 'graphical'; } bless ($self, $class); return $self; } sub activateStateTracking { my $self = shift; $self->signal_connect('changed' => sub {; $self->setChanged; }); } sub isEmpty { my $self = shift; if ($self->{format} eq 'text') { return $self->get_text eq ''; } elsif ($self->{format} eq 'graphical') { return $self->get('rating') eq 0; } } sub selectAll { my $self = shift; if ($self->{format} eq 'text') { $self->select_region(0, length($self->getValue)); } } sub getValue { my $self = shift; my $value; if ($self->{format} eq 'text') { $value = $self->get_text; $value =~ s/,/./; } elsif ($self->{format} eq 'graphical') { $value = $self->get('rating'); } return $value; } sub setValue { my ($self, $text) = @_; if ($self->{format} eq 'text') { $text = $self->{default} if $text eq ''; $self->set_value($text); } elsif ($self->{format} eq 'graphical') { $text = $self->{default} if $text eq ''; $self->set_rating($text); } } sub clear { my $self = shift; if ($self->{format} eq 'text') { $self->set_value($self->{default}); } elsif ($self->{format} eq 'graphical') { $self->set_rating($self->{default}); } } sub setWidth { my ($self, $value) = @_; $self->set_width_chars($value); } sub lock { my ($self, $locked) = @_; if ($self->{format} eq 'text') { $self->can_focus(!$locked); my $step = ($locked ? 0 : $self->{step}); $self->set_increments($step, $self->{pageStep}); } elsif ($self->{format} eq 'graphical') { $self->set(sensitive=>!$locked); } } } { package GCCheckedText; use Glib::Object::Subclass Gtk2::Entry:: ; @GCCheckedText::ISA = ('GCShortText'); sub new { my ($proto, $format) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); my $forbidden = qr/[^$format]/; $self->signal_connect('insert-text' => sub { # Remove forbidden characters $_[1] =~ s/$forbidden//g; () # this callback must return either 2 or 0 items. }); return $self; } } { package GCRange; use Glib::Object::Subclass Gtk2::HBox:: ; @GCRange::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); sub new { my ($proto, $type, $lang, $min, $max, $step, $parent) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); if ($type eq 'text') { $self->{from} = new GCShortText; $self->{to} = new GCShortText; } elsif ($type eq 'numeric text') { new GCCheckedText('0-9.'); $self->{to} = new GCCheckedText('0-9.'); } elsif ($type eq 'date') { $self->{from} = new GCDate($parent->{window}, $lang, 1, $parent->{options}->dateFormat); $self->{to} = new GCDate($parent->{window}, $lang, 1, $parent->{options}->dateFormat); } else { $self->{from} = new GCNumeric($min, $min, $max, $step); $self->{to} = new GCNumeric($max, $min, $max, $step); } $self->pack_start(Gtk2::Label->new($lang->{PanelFrom}), 0, 0, 12); $self->pack_start($self->{from}, 1, 1, 0); $self->pack_start(Gtk2::Label->new($lang->{PanelTo}), 0, 0, 12); $self->pack_start($self->{to}, 1, 1, 0); return $self; } sub activateStateTracking { my $self = shift; $self->{from}->activateStateTracking; $self->{to}->activateStateTracking; } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub getValue { my $self = shift; return $self->{from}->getValue.';'.$self->{to}->getValue; } sub setValue { my ($self, $value) = @_; my @values = split m/;/, $value; $self->{from}->setValue($values[0]); $self->{to}->setValue($values[0]); } sub setWidth { my ($self, $value) = @_; $self->{from}->setWidth($value / 2); $self->{to}->setWidth($value / 2); } sub lock { my ($self, $locked) = @_; $self->{from}->lock(!$locked); $self->{to}->lock(!$locked); } sub signal_connect { my $self = shift; $self->{from}->signal_connect(@_); $self->{to}->signal_connect(@_); } sub AUTOLOAD { my $self = shift; my $name = our $AUTOLOAD; return if $name =~ /::DESTROY$/; $name =~ s/.*?::(.*)/$1/; $self->{from}->$name(@_); $self->{to}->$name(@_); } } { package GCDate; use Glib::Object::Subclass Gtk2::HBox:: ; @GCDate::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); sub selectValue { my $self = shift; $self->{dialog} = new GCDateSelectionDialog($self->{mainParent}) if ! $self->{dialog}; $self->{dialog}->date($self->getRawValue); if ($self->{dialog}->show) { $self->setValue($self->{dialog}->date); } $self->{parent}->showMe; } sub new { my ($proto, $parent, $lang, $reverseDate, $format) = @_; $format ||= '%d/%m/%Y'; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->{parent} = $parent; $self->getMainParent; $self->{reverseDate} = $reverseDate; $self->{entry} = Gtk2::Entry->new; #_with_max_length(10); $self->{entry}->set_width_chars(12); $self->{button} = new Gtk2::Button($lang->{PanelDateSelect}); $self->{button}->signal_connect('clicked' => sub { $self->selectValue; }); $self->pack_start($self->{entry}, 1, 1, 0); $self->pack_start($self->{button}, 0, 0, 0); $self->{format} = $format; #$self->{format} = '%d %B %Y'; return $self; } sub setFormat { my ($self, $format) = @_; my $current = $self->getValue; $self->{format} = $format; $self->setValue($current); } sub activateStateTracking { my $self = shift; $self->{entry}->signal_connect('changed' => sub {; $self->setChanged; }); } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub getCurrentDate { my $self = shift; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf('%02d/%02d/%4d', $mday, $mon+1, 1900+$year); } sub getRawValue { my $self = shift; my $value = $self->{entry}->get_text; $value = GCUtils::strToTime($value, $self->{format}) if $self->{format} && $value; return $value; } sub getValue { my $self = shift; my $value = $self->getRawValue; return GCPreProcess::reverseDate($value) if $self->{reverseDate}; return $value; } sub setValue { my ($self, $text) = @_; $text = GCPreProcess::restoreDate($text) if $self->{reverseDate}; if ($text eq 'current') { $text = $self->getCurrentDate; $self->setChanged; } $text = GCUtils::timeToStr($text, $self->{format}) if $self->{format}; $self->{entry}->set_text($text); } sub clear { my $self = shift; $self->setValue(''); } sub setWidth { my ($self, $value) = @_; $self->{entry}->set_width_chars($value); } sub lock { my ($self, $locked) = @_; $self->{entry}->can_focus(!$locked); $self->{button}->set_sensitive(!$locked); } } { package GCFile; use Glib::Object::Subclass Gtk2::HBox:: ; @GCFile::ISA = ('Gtk2::HBox', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); use File::Basename; sub selectValue { my $self = shift; my $dialog = GCFileChooserDialog->new($self->{title}, $self->{mainParent}, $self->{type}, $self->{withFilter}); $dialog->set_filename($self->getValue); $dialog->set_pattern_filter($self->{patterns}) if $self->{patterns}; my $response = $dialog->run; if ($response eq 'ok') { $self->setValue($dialog->get_filename); } $dialog->hide; $self->{parent}->showMe; } sub setPatternFilter { my ($self, $patterns) = @_; $self->{patterns} = $patterns; } sub setType { my ($self, $type, $withFilter) = @_; if (($type ne $self->{type}) || ($withFilter != $self->{withFilter})) { $self->{dialog}->destroy if $self->{dialog}; $self->{dialog} = undef; $self->{type} = $type; $self->{withFilter} = $withFilter; } } sub setTitle { my ($self, $title) = @_; $self->{title} = $title; if ($self->{dialog}) { $self->{dialog}->setTitle($title); } } sub new { my ($proto, $parent, $title, $type, $withFilter, $defaultValue, $allowContextMenu, $fieldType) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->{parent} = $parent; $self->getMainParent; $title ||= $self->{parent}->{lang}->{PanelSelectFileTitle}; $self->{title} = $title; $type ||= 'open'; $self->{type} = $type; $withFilter = 0 if !$withFilter; $self->{withFilter} = $withFilter; $self->{entry} = Gtk2::Entry->new; $self->{button} = new GCButton($self->{parent}->{lang}->{PanelSelectFileTitle}); # For video/ebook/audio files, set file pattern filters if (($fieldType eq 'video') || ($fieldType eq 'ebook') || ($fieldType eq 'audio')) { $self->setPatternFilter([$self->{parent}->{lang}->{FileVideoFiles}, \@videoExtensions]) if ($fieldType eq 'video'); $self->setPatternFilter([$self->{parent}->{lang}->{FileEbookFiles}, \@ebookExtensions]) if ($fieldType eq 'ebook'); $self->setPatternFilter([$self->{parent}->{lang}->{FileAudioFiles}, \@audioExtensions]) if ($fieldType eq 'audio'); $self->{withFilter} = 1; } $self->{button}->signal_connect('clicked' => sub { $self->selectValue; }); if ($allowContextMenu) { my @subMenuFileChoose; $subMenuFileChoose[0] = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseFile}); $subMenuFileChoose[0]->signal_connect("activate" , sub {$self->selectValue}); $subMenuFileChoose[1] = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseFolder}); $subMenuFileChoose[1]->signal_connect("activate" , sub { $self->{type} = 'select-folder'; $self->{title} = $parent->{lang}->{ContextChooseFolder}; $self->selectValue; }); $self->{button}->setContextMenu(\@subMenuFileChoose); $self->{button}->enableContextMenu; } $self->pack_start($self->{entry}, 1, 1, 0); if ($defaultValue) { $self->{defaultButton} = GCButton->newFromStock('gtk-undo', 0, $parent->{lang}->{PanelRestoreDefault}); $parent->{tooltips}->set_tip($self->{defaultButton}, $parent->{lang}->{PanelRestoreDefault}.$parent->{lang}->{Separator}.$defaultValue) if $parent->{tooltips}; $self->pack_start($self->{defaultButton}, 0, 0, 0); $self->{defaultButton}->signal_connect('clicked' => sub { $self->setValue($defaultValue); }); } # Add the 'select file' button only if the field is not a url field $self->pack_start($self->{button}, 0, 0, 0) if ($fieldType ne 'url'); return $self; } sub activateStateTracking { my $self = shift; $self->{entry}->signal_connect('changed' => sub {; $self->setChanged; }); } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub getValue { my $self = shift; return $self->{entry}->get_text; } sub setValue { my ($self, $text) = @_; $self->{entry}->set_text($text); } sub clear { my $self = shift; $self->setValue(''); } sub setWidth { my ($self, $value) = @_; $self->{entry}->set_width_chars($value); } sub lock { my ($self, $locked) = @_; $self->{entry}->can_focus(!$locked); $self->{button}->set_sensitive(!$locked); } } { package GCUrl; use Glib::Object::Subclass Gtk2::HBox:: ; @GCUrl::ISA = ('GCFile'); sub selectValue { my $self = shift; my $url = $self->getValue; return if !$url; $self->{opener}->launch($url, 'url'); } sub new { my ($proto, $opener) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(undef, '', 'url'); $self->{opener} = $opener; bless ($self, $class); return $self; } } { package GCButton; use Glib::Object::Subclass Gtk2::Button:: ; @GCButton::ISA = ('Gtk2::Button', 'GCGraphicComponent'); sub new { my ($proto, $label) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new($label); bless ($self, $class); return $self; } sub newFromStock { my ($proto, $stock, $nolabel, $label) = @_; my $class = ref($proto) || $proto; $nolabel = 0 if ($^O =~ /win32/i); my $self = $class->SUPER::new_from_stock($stock); if ($nolabel) { my $tmpWidget = $self; $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox'); ($tmpWidget->get_children)[1]->destroy; } elsif ($label) { my $tmpWidget = $self; $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox'); ($tmpWidget->get_children)[1]->set_label($label); } bless ($self, $class); return $self; } sub isEmpty { my $self = shift; return 0; } sub getValue { my $self = shift; } sub setValue { my ($self, $value) = @_; $self->setChanged; return $value; } sub clear { my $self = shift; $self->setValue(''); } sub lock { my ($self, $locked) = @_; $self->set_sensitive(!$locked); } sub setWidth { my ($self, $value) = @_; $self->set_size_request($value, -1); } sub setContextMenu { my ($self, $menu) = @_; $self->{popupContextMenu}=new Gtk2::Menu if !$self->{popupContextMenu}; for my $i(0..$#{$self->{popupContextMenu}->{items}}) { $self->{popupContextMenu}->remove($self->{popupContextMenu}->{items}->[$i]); $self->{popupContextMenu}->{items}->[$i]->destroy; } delete $self->{popupContextMenu}->{items}; for my $i(0..$#$menu) { $self->{popupContextMenu}->{items}->[$i]=$menu->[$i]; $self->{popupContextMenu}->append($self->{popupContextMenu}->{items}->[$i]); } $self->{popupContextMenu}->show_all; } sub enableContextMenu { my ($self,$button) = @_; #TODO enabled context for one button mouses $button=3 if !$button; $self->{contextMenuSignalHandler}=$self->signal_connect('button_press_event' => sub { my ($widget, $event) = @_; return 0 if $event->button ne $button; $self->{popupContextMenu}->popup(undef, undef, undef, undef, $event->button, $event->time); return 0; }) if !$self->{contextMenuSignalHandler}; } sub disableContextMenu { my $self = shift; if($self->{contextMenuSignalHandler}) { $self->signal_handler_disconnect($self->{contextMenuSignalHandler}); delete $self->{contextMenuSignalHandler}; } } } { package GCUrlButton; use Glib::Object::Subclass Gtk2::Button:: ; @GCUrlButton::ISA = ('GCButton', 'GCGraphicComponent'); sub new { my ($proto, $label, $opener) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new($label); $self->{opener}=$opener; $self->{defaultLabel} = $label; $self->{clicSignalHandler}=$self->signal_connect('clicked' => sub { $self->{opener}->launch($self->{url}, 'url'); }); bless ($self, $class); return $self; } sub getValue { my $self = shift; return $self->{value}; } sub setValue { my ($self, $value) = @_; $self->setChanged; my @urls=split ';',$value; if(scalar(@urls)>1) { my (@menu,$i,$url); foreach my $urlName(@urls) { $urlName =~ /^(.*?)##(.*)$/; my $name=$2; my $menuItem=Gtk2::MenuItem->new_with_label($name); $menuItem->signal_connect("activate" ,sub { $self->{opener}->launch($_[1], 'url') },$1); push @menu,$menuItem; } $self->{url} =''; $self->setContextMenu(\@menu); $self->enableContextMenu(1); $self->lock(0); #$self->signal_handler_block($self->{clicSignalHandler}); $self->setLabel($self->{defaultLabel}); } else { if ($value =~ /^(.*?)##(.*)$/) { $self->{url} = $1; $self->setLabel($2); } else { $self->{url} = $value; $self->setLabel($self->{defaultLabel}); } $self->lock(!$self->{url}); $self->disableContextMenu; #$self->signal_handler_unblock($self->{clicHandler}); } $self->{value} = $value; } sub setLabel { my ($self, $label) = @_; $self->set_label($label); } } { package GCCheckBox; use Glib::Object::Subclass Gtk2::CheckButton:: ; @GCCheckBox::ISA = ('Gtk2::CheckButton', 'GCGraphicComponent'); sub new { my ($proto, $label) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new($label); bless ($self, $class); return $self; } sub activateStateTracking { my $self = shift; $self->signal_connect('toggled' => sub { $self->setChanged; }); } sub isEmpty { my $self = shift; return 0; } sub getValue { my $self = shift; return 1 if ($self->get_active); return 0; } sub getValueAsText { my $self = shift; return 'true' if ($self->get_active); return 'false'; } sub setValue { my ($self, $value) = @_; $self->set_active($value); } sub clear { my $self = shift; $self->setValue(0); } sub lock { my ($self, $locked) = @_; $self->set_sensitive(!$locked); } } { package GCCheckBoxWithIgnore; use Glib::Object::Subclass Gtk2::HBox:: ; @GCCheckBoxWithIgnore::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); sub new { my ($proto, $parent) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(0,0); bless ($self, $class); $self->{check}->[0] = new Gtk2::RadioButton(undef,$parent->{lang}->{CheckUndef}); $self->{group} = $self->{check}->[0]->get_group; $self->{check}->[1] = new Gtk2::RadioButton($self->{group},$parent->{lang}->{CheckNo}); $self->{check}->[2] = new Gtk2::RadioButton($self->{group},$parent->{lang}->{CheckYes}); $self->pack_start($self->{check}->[0], 0, 0, 10); $self->pack_start($self->{check}->[1], 0, 0, 10); $self->pack_start($self->{check}->[2], 0, 0, 10); return $self; } sub activateStateTracking { my $self = shift; foreach (@{$self->{check}}) { $_->signal_connect('toggled' => sub { $self->setChanged; }); } } sub isEmpty { my $self = shift; return $self->{check}->[0]->get_active; } sub getValue { my $self = shift; my $i = 0; foreach (@{$self->{check}}) { last if $self->{check}->[$i]->get_active; $i++; } $i--; return $i; } sub setValue { my ($self, $value) = @_; $self->{check}->[$value + 1]->set_active(1); } sub clear { my $self = shift; $self->setValue(-1); } sub lock { my ($self, $locked) = @_; $self->set_sensitive(!$locked); } } { package GCMultipleList; use Glib::Object::Subclass Gtk2::HBox:: ; @GCMultipleList::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); sub new { my ($proto, $parent, $number, $labels, $withHistory, $readonly, $useFiles,$types) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(0,0); $self->{number} = $number; $self->{readonly} = $readonly; $self->{withHistory} = $withHistory; my $hboxActions = new Gtk2::HBox(0,0); my @histories; my @listColumns; my $i; for $i (0..($number - 1)) { push @histories, {'' => 1}; push @listColumns, ($labels->[$i] => 'text'); next if $readonly; if ($useFiles) { $self->{entries}->[$i] = GCFile->new($parent); $self->{entries}->[$i]->{entry}->signal_connect('activate' => sub { $self->addValues; }); } else { if($types && $types->[$i] eq 'date') { $self->{entries}->[$i] = GCDate->new($parent, $parent->{lang}, 1,$parent->{options}->dateFormat); $self->{entries}->[$i]->{entry}->signal_connect('activate' => sub { $self->addValues; $self->{entries}->[0]->grab_focus; }); $self->{withHistoryField}->[$i]=0; } elsif ($withHistory && (!$types || $types->[$i] eq 'history')) { $self->{entries}->[$i] = GCHistoryText->new; $self->{entries}->[$i]->entry->signal_connect('activate' => sub { my $widget = $self->{entries}->[$i]; if ($widget->getValue) { $self->addValues; # FIXME. It seems this does nothing: $self->{entries}->[0]->grab_focus; } $widget->entry->signal_stop_emission_by_name('activate'); }); $self->{withHistoryField}->[$i]=1; } else { $self->{entries}->[$i] = GCShortText->new; $self->{entries}->[$i]->signal_connect('activate' => sub { $self->addValues; $self->{entries}->[0]->grab_focus; }); $self->{withHistoryField}->[$i]=0 if $withHistory; } } $self->{entries}->[$i]->setWidth(12); $hboxActions->pack_start($self->{entries}->[$i], 1, 1, 6); } $self->{histories} = [\@histories]; $self->{box} = new Gtk2::VBox(0,0); # If list belongs to an expander, set box size to a reasonable size $self->{box}->{signalHandler} = $self->{box}->signal_connect('size-allocate' => sub { if (($self->{realParent}) && ($self->{realParent}->isa('GCExpander'))) { my $width = $self->allocation->width - ( 2 * $GCUtils::margin) ; $self->{box}->set_size_request(($width >= -1) ? $width : -1 , -1); return 0; } }); $self->{list} = new Gtk2::SimpleList(@listColumns); for $i (0..($number - 1)) { $self->{list}->set_column_editable($i, 1); } $self->{list}->unset_rows_drag_source; $self->{list}->unset_rows_drag_dest; $self->{list}->set_reorderable(1); #($self->{list}->get_column(0)->get_cell_renderers)[0]->set('wrap-mode' => 'word'); for $i (0..($number - 1)) { $self->{list}->get_column($i)->set_resizable(1); } my $scroll = new Gtk2::ScrolledWindow; $scroll->set_policy ('automatic', 'automatic'); $scroll->set_shadow_type('etched-in'); $scroll->set_size_request(-1, 120); $scroll->add($self->{list}); $self->{box}->pack_start($scroll, 1, 1, 2); if (!$readonly) { $self->{addButton} = GCButton->newFromStock('gtk-add', 0); $self->{addButton}->signal_connect('clicked' => sub { $self->addValues; }); $self->{removeButton} = GCButton->newFromStock('gtk-remove', 0); $hboxActions->pack_start($self->{addButton}, 0, 0, 6); $hboxActions->pack_start($self->{removeButton}, 0, 0, 6); } else { $self->{removeButton} = GCButton->newFromStock('gtk-remove', 0); $self->{clearButton} = GCButton->newFromStock('gtk-clear', 0); $self->{clearButton}->signal_connect('clicked' => sub { $self->clear; }); $hboxActions->pack_start($self->{removeButton}, 1, 0, 6); $hboxActions->pack_start($self->{clearButton}, 1, 0, 6); } $self->{box}->pack_start($hboxActions, 0, 0, 6) if $readonly < 2; $self->{removeButton}->signal_connect('clicked' => sub { my @idx = $self->{list}->get_selected_indices; my $selected = $idx[0]; splice @{$self->{list}->{data}}, $selected, 1; $selected-- if ($selected >= scalar(@{$self->{list}->{data}})); $selected = 0 if $selected < 0 ; $self->{list}->select($selected); }); $self->{list}->signal_connect('key-press-event' => sub { my ($widget, $event) = @_; my $key = Gtk2::Gdk->keyval_name($event->keyval); if ((!$self->{readonly}) && ($key eq 'Delete')) { $self->{removeButton}->activate; return 1; } # Let key be managed by Gtk2 return 0; }); $self->pack_start($self->{box},1,$self->{readonly},0); bless ($self, $class); return $self; } sub activateStateTracking { my $self = shift; $self->{list}->get_model->signal_connect('row-inserted' => sub { $self->setChanged; }); $self->{list}->get_model->signal_connect('row-deleted' => sub { $self->setChanged; }); $self->{list}->get_model->signal_connect('row-changed' => sub { $self->setChanged; }); } sub expand { my $self = shift; $self->set_child_packing($self->{box},1,1,0,'start'); } sub addValues { my ($self, @values) = @_; if (!$self->{readonly}) { for my $i (0..($self->{number} - 1)) { $values[$i] = $self->{entries}->[$i]->getValue if !$values[$i]; $self->{entries}->[$i]->addHistory($values[$i]) if $self->{withHistory} && $self->{withHistoryField}->[$i]; $self->{entries}->[$i]->clear; } } # Check that at least one value is not empty my $isEmpty = 1; for my $val (@values) { if ($val) { $isEmpty = 0; last; } } if (!$isEmpty) { push @{$self->{list}->{data}}, \@values; $self->{list}->select($#{$self->{list}->{data}}); my $path = $self->{list}->get_selection->get_selected_rows; $self->{list}->scroll_to_cell($path) if $path; } } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub getValue { my $self = shift; my $formated = shift; if ($formated) { return GCPreProcess::multipleList($self->{list}->{data}, $self->{number}); } else { # As data in list is a tied array, we need to copy all the values my @value; foreach (@{$self->{list}->{data}}) { push @value, []; foreach my $col(@$_) { push @{$value[-1]}, $col; } } return \@value; } } sub setValue { my ($self, $value) = @_; if (ref($value) eq 'ARRAY') { @{$self->{list}->{data}} = @{$value}; } else { # The separator used was ; instead of , $value =~ s/;/,/g if $value !~ /,/; @{$self->{list}->{data}} = (); my @values = split m/,/, $value; foreach my $entry (@values) { my @items = split m/;/, $entry; s/^\s*// foreach(@items); push @{$self->{list}->{data}}, \@items; } } } sub clear { my $self = shift; $self->setValue(''); } sub lock { my ($self, $locked) = @_; return if $self->{readonly}; $self->{addButton}->set_sensitive(!$locked); $self->{removeButton}->set_sensitive(!$locked); foreach (@{$self->{entries}}) { $_->lock($locked); } } sub addHistory { my $self = shift; my $value = (scalar @_) ? shift : $self->getValue; my $noUpdate = shift; return if $self->{readonly}; my $i; my $item; if (ref($value) eq 'ARRAY') { foreach (@$value) { $i = 0; foreach $item(@$_) { $self->{entries}->[$i]->addHistory($item, $noUpdate) if $self->{withHistoryField}->[$i]; $i++; } } } else { # The separator used was ; instead of , $value =~ s/;/,/g if $value !~ /,/; my @values = split m/,/, $value; foreach (@values) { my @items = split m/;/; $i = 0; foreach my $item(@items) { $self->{entries}->[$i]->addHistory($item, $noUpdate) if $self->{withHistoryField}->[$i]; $i++; } #push @{$self->{list}->{data}}, \@items; } } } sub setDropDown { my $self = shift; my $i = 0; foreach (@{$self->{entries}}) { $_->setDropDown if $self->{withHistoryField}->[$i]; $i++; } } sub getValues { my $self = shift; return [] if $self->{readonly}; my @array; my $i=0; foreach (@{$self->{entries}}) { my $val=[]; $val=$_->getValues if ($self->{withHistoryField}->[$i++]); push @array, $val; } # = sort keys %{$self->{history}}; return \@array; } sub setValues { my ($self, $values) = @_; return if $self->{readonly}; my $i = 0; foreach (@$values) { $self->{entries}->[$i]->setValues($_) if ($self->{withHistoryField}->[$i]); $i++; } } } { package GCItemImage; use Glib::Object::Subclass Gtk2::Image:: ; @GCItemImage::ISA = ('Gtk2::Image', 'GCGraphicComponent'); use File::Spec; use File::Basename; sub new { my ($proto, $options, $parent, $fixedSize, $width, $height) = @_; my $class = ref($proto) || $proto; # my $self = Gtk2::Image->new_from_file($parent->{defaultImage}); my $self = Gtk2::Image->new; $self->{options} = $options; #$self->{defaultImage} = $defaultImage; $self->{parent} = $parent; $self->{displayedImage} = ''; $self->{fixedSize} = $fixedSize; bless ($self, $class); if ($width && $height) { $self->{width} = $width; $self->{height} = $height; } else { $self->{width} = 120; $self->{height} = 160; } $self->{immediate} = 0; return $self; } sub setImmediate { my ($self) = @_; $self->{immediate} = 1; } sub activateStateTracking { my $self = shift; $self->{trackState} = 1; } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub setValue { my ($self, $displayedImage, $placer) = @_; $self->{displayedImage} = $displayedImage; $self->setChanged if $self->{trackState}; if ($self->{immediate}) { $self->setPicture; } else { Glib::Source->remove($self->{timer}) if $self->{timer}; $self->{timer} = Glib::Timeout->add(100, sub { $self->setPicture; $placer->placeImg if $placer; return 0; }); } } sub setPicture { my $self = shift; $self->{timer} = 0; my $displayedImage = GCUtils::getDisplayedImage($self->{displayedImage}, $self->{parent}->{defaultImage}, $self->{options}->file); my $pixbuf; eval { $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage); }; if ($@) { $displayedImage = $self->{parent}->{defaultImage}; $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage); } $self->{realImage} = $displayedImage; $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf, $self->{width}, $self->{height}); $self->set_from_pixbuf($pixbuf); $self->set_size_request($self->{width}, $self->{height}) if $self->{fixedSize}; } sub getValue { my $self = shift; return $self->{displayedImage}; } sub getFile { my $self = shift; return $self->{realImage}; } sub clear { my $self = shift; $self->setValue(''); } sub lock { my ($self, $locked) = @_; } sub setWidth { my ($self, $value) = @_; $self->{width} = $value; } sub setHeight { my ($self, $value) = @_; $self->{height} = $value; } sub getSize { my $self = shift; my $pixbuf = $self->get_pixbuf; return ($pixbuf->get_width, $pixbuf->get_height); } } our $hasGnome2VFS; BEGIN { eval 'use Gnome2::VFS'; if ($@) { $hasGnome2VFS = 0; } else { $hasGnome2VFS = 1; Gnome2::VFS->init(); } } { package GCImageButton; use Glib::Object::Subclass Gtk2::Button:: ; @GCImageButton::ISA = ('Gtk2::Button', 'GCGraphicComponent'); use File::Basename; use Encode; sub animateImg { my ($self, $from, $to) = @_; my $pixbuf1 = Gtk2::Gdk::Pixbuf->new_from_file($from); $pixbuf1 = GCUtils::scaleMaxPixbuf($pixbuf1, $self->{img}->{width}, $self->{img}->{height}); my $pixbuf2 = Gtk2::Gdk::Pixbuf->new_from_file($to); $pixbuf2 = GCUtils::scaleMaxPixbuf($pixbuf2, $self->{img}->{width}, $self->{img}->{height}); my $height = $pixbuf2->get_height; my $width = $pixbuf2->get_width; foreach my $i(0..20) { Glib::Timeout->add(30*$i, sub { my $pixbufA = $pixbuf1->copy; my $pixbufB = $pixbuf2->copy; $pixbufA->composite($pixbufB, 0, 0, int($width - (($i/20)*$width)), $height, 0, 0, 1, 1, 'nearest', 255); $self->{img}->set_from_pixbuf($pixbufB); }); } } sub setImg { my ($self, $value) = @_; $self->{img}->setValue($value, $self); } sub placeImg { my ($self) = @_; my ($picWidth, $picHeight) = $self->{img}->getSize; my ($buttonWidth, $buttonHeight) = ($self->allocation->width, $self->allocation->height); my $x = ($buttonWidth - $picWidth - $GCUtils::margin)/ 2; my $y = ($buttonHeight -$picHeight - $GCUtils::margin)/ 2; # Don't allow negative positions, can happen when button has not been allocated a width/height yet $x = 0 if ($x < 0); $y = 0 if ($y < 0); $self->{layout}->move($self->{img}, $x, $y); } sub changeState { my $self = shift; if ($self->{trackState}) { if ($self->{flipped}) { $self->{linkedComponent}->setChanged; } else { $self->setChanged; } } } sub clearImage { my $self = shift; $self->changeState; $self->{mainParent}->checkPictureToBeRemoved($self->{imageFile}); $self->setValueWithParent(''); } sub changeImage { my ($self, $fileName) = @_; return 0 if $self->{locked}; if (!$fileName) { my $imageDialog = GCFileChooserDialog->new($self->{parent}->{lang}->{PanelImageTitle}, $self->{mainParent}, 'open'); $imageDialog->setWithImagePreview(1); my $currentFile = $self->{img}->getValue; if ($currentFile) { $imageDialog->set_filename($currentFile); } else { $imageDialog->set_filename($self->{previousDirectory}); } my $response = $imageDialog->run; $fileName = $imageDialog->get_filename; $imageDialog->destroy; $self->{parent}->showMe; if ($response eq 'ok') { $self->{previousDirectory} = dirname($fileName); $self->setChanged if $self->{trackState}; } else { return; } } my $ref = ($self->{flipped} ? $self->{backPic} : $self->{imageFile}); if ($fileName ne $ref) { $self->{mainParent}->checkPictureToBeRemoved($ref); $self->changeState; } my $image = $self->{mainParent}->transformPicturePath($fileName); $self->setValueWithParent($image); return; } sub isEmpty { my $self = shift; return $self->getValue eq ''; } sub setValue { my ($self, $value) = @_; $self->setChanged if $self->{trackState}; $self->setActualValue($value); } sub setValueWithParent { my ($self, $value, $keepWatcher) = @_; $self->setActualValue($value, $keepWatcher, $self->{flipped}); if ($self->{isCover} && !$self->{flipped}) { $self->{mainParent}->{items}->updateSelectedItemInfoFromPanel(0, [$self->{name}]); $self->{hasChanged} = 0 if $self->{parent} eq $self->{mainParent}->{panel} && !$keepWatcher; } } sub setActualValue { my ($self, $value, $keepWatcher, $flipped) = @_; Glib::Source->remove($self->{fileWatcher}) if $self->{fileWatcher} && !$keepWatcher; if ($flipped) { $self->{backPic} = $value; } else { $self->{imageFile} = $value; } $self->setImg($value); } sub getValue { my $self = shift; if ($self->{flipped}) { return $self->{imageFile}; } return $self->{img}->getValue; } sub setLinkedActivated { my ($self, $value) = @_; $self->flipImage(1) if $self->{flipped}; $self->{flipActivated} = $value; } sub flipImage { my ($self, $noButton) = @_; my $newLabel; if ($self->{flipped}) { $self->setImg($self->{imageFile}); $self->{frontFlipImage}->show if !$noButton; $self->{backFlipImage}->hide; #$self->animateImg($self->{backPic}, $self->{imageFile}); } else { $self->setImg($self->{backPic}); $self->{backFlipImage}->show if !$noButton; $self->{frontFlipImage}->hide; #$self->animateImg($self->{imageFile}, $self->{backPic}); } $self->{flipped} = !$self->{flipped}; } sub setLinkedValue { my ($self, $linkedValue) = @_; $self->setChanged if $self->{trackState}; $self->{backPic} = $linkedValue; $self->setImg($linkedValue) if $self->{flipped}; } sub getLinkedValue { my ($self, $linkedValue) = @_; return $self->{backPic}; } sub setLinkedComponent { my ($self, $linked) = @_; $self->{linkedComponent} = $linked; $self->{flipActivated} = 1; $self->{frontFlipImage} = Gtk2::Image->new_from_file($ENV{GCS_SHARE_DIR}.'/overlays/flip.png'); $self->{frontFlipImage}->set_no_show_all(1); $self->{backFlipImage} = Gtk2::Image->new_from_file($ENV{GCS_SHARE_DIR}.'/overlays/flip2.png'); $self->{backFlipImage}->set_no_show_all(1); my $pixbuf = $self->{frontFlipImage}->get_pixbuf; my ($picWidth, $picHeight) = ($pixbuf->get_width, $pixbuf->get_height); $self->{addedFlipButton} = 0; $self->signal_connect('enter' => sub { return if ! $self->{flipActivated}; if (!$self->{addedFlipButton}) { $self->{flipX} = $self->{width} - $picWidth - $GCUtils::margin; $self->{flipY} = $self->{height} - $picHeight - $GCUtils::margin; $self->{layout}->put($self->{frontFlipImage}, $self->{flipX}, $self->{flipY}); $self->{layout}->put($self->{backFlipImage}, $self->{flipX}, $self->{flipY}); $self->{addedFlipButton} = 1; } if ($self->{flipped}) { $self->{backFlipImage}->show; } else { $self->{frontFlipImage}->show; } }); $self->signal_connect('leave' => sub { $self->{frontFlipImage}->hide; $self->{backFlipImage}->hide; }); $self->signal_connect('button-release-event' => sub { return 0 if ! $self->{flipActivated}; my ($button, $event) = @_; my ($x, $y) = $event->get_coords; if (($x > $self->{flipX}) && ($y > $self->{flipY})) { $self->flipImage; $self->set_sensitive(0); $self->released; $self->set_sensitive(1); return 1; } else { return 0; } }); $self->signal_connect('key-press-event' => sub { return 0 if ! $self->{flipActivated}; my ($widget, $event) = @_; my $key = Gtk2::Gdk->keyval_name($event->keyval); if (($key eq 'f') || ($key eq 'BackSpace')) { $self->flipImage; return 1; } return 0; }); $self->signal_connect('query_tooltip' => sub { my ($window, $x, $y, $keyboard_mode, $tip) = @_; return if $self->{settingTip}; $self->{settingTip} = 1; if ($self->{flipActivated} && ($x > $self->{flipX}) && ($y > $self->{flipY})) { $self->{tooltips}->set_tip($self, $self->{flipped} ? $self->{parent}->{lang}->{ContextImgFront} : $self->{parent}->{lang}->{ContextImgBack}); } else { $self->{tooltips}->set_tip($self, $self->{tip}); } $self->{settingTip} = 0; return 0; }); } sub clear { my $self = shift; $self->setValue(''); } sub new { my ($proto, $parent, $img, $isCover, $default) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $default = 'view' if !$default; $self->{layout} = new Gtk2::Fixed; $self->{layout}->put($img, 0, 0); $self->add($self->{layout}); $self->{img} = $img; $self->{default} = $default; #$self->set_size_request(130,170); $self->{width} = -1; $self->{height} = -1; $self->{imageFile} = $img->getValue; # True if this is the cover used in image mode $self->{isCover} = $isCover; $self->{parent} = $parent; $self->getMainParent; $self->{tooltips} = $self->{mainParent}->{tooltips}; $self->{tip} = ($default eq 'open') ? $parent->{lang}->{PanelImageTipOpen} : $parent->{lang}->{PanelImageTipView}; $self->{tip} .= $parent->{lang}->{PanelImageTipMenu}; $self->{tooltips}->set_tip($self, $self->{tip}); $self->signal_connect('button_press_event' => sub { my ($widget, $event) = @_; return 0 if $event->button ne 3; $self->createContextMenu(); $self->{imgContext}->popup(undef, undef, undef, undef, $event->button, $event->time); return 0; }); $self->signal_connect('clicked' => sub { $self->changeImage if $self->{default} eq 'open'; $self->showImage if $self->{default} eq 'view'; return 1; }); #Drag and drop a picture on a button $self->drag_dest_set('all', ['copy','private','default','move','link','ask']); my $target_list = Gtk2::TargetList->new(); my $atom1 = Gtk2::Gdk::Atom->new('text/uri-list'); my $atom2 = Gtk2::Gdk::Atom->new('text/plain'); $target_list->add($atom1, 0, 0); $target_list->add($atom2, 0, 0); if ($^O =~ /win32/i) { my $atom2 = Gtk2::Gdk::Atom->new('DROPFILES_DND'); $target_list->add($atom2, 0, 0); } $self->drag_dest_set_target_list($target_list); $self->signal_connect(drag_data_received => sub { my ($widget, $context, $widget_x, $widget_y, $data, $info,$time) = @_; my @files = split /\n/, $data->data; my $fileName = $files[0]; if ($fileName =~ /^http/) { $fileName = $self->{mainParent}->downloadPicture($fileName); } else { $fileName = Glib::filename_from_uri $fileName; $fileName = decode('utf8', $fileName); $fileName =~ s|^file://?(.*)\W*$|$1|; $fileName =~ s|^/*|| if ($^O =~ /win32/i); $fileName =~ s/.$//ms; $fileName =~ s/%20/ /g; } $self->changeImage($fileName); }); $self->{previousDirectory} = ''; $self->{flipped} = 0; $self->{flipActivated} = 0; return $self; } sub createContextMenu { my $self = shift; my $parent; $parent = $self->{parent}; $self->{imgContext} = new Gtk2::Menu; if ($parent->{options}->tearoffMenus) { $self->{imgContext}->append(Gtk2::TearoffMenuItem->new()); } $self->{itemOpen} = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseImage}); my $itemOpenImage = Gtk2::Image->new_from_stock('gtk-open', 'menu'); $self->{itemOpen}->set_image($itemOpenImage); # This item will be deactivated if the component is locked $self->{itemOpen}->set_sensitive(!$self->{locked}); $self->{itemOpen}->signal_connect("activate" , sub { $self->changeImage; }); $self->{imgContext}->append($self->{itemOpen}); $self->{itemShow} = Gtk2::ImageMenuItem->new_from_stock('gtk-zoom-100',undef); $self->{itemShow}->signal_connect("activate" , sub { $self->showImage; }); # Disable for default image $self->{itemShow}->set_sensitive(0) if $self->isDefaultImage(); $self->{imgContext}->append($self->{itemShow}); if ($self->{linkedComponent}) { $self->{itemFlip} = Gtk2::MenuItem->new($self->{flipped} ? $parent->{lang}->{ContextImgFront} : $parent->{lang}->{ContextImgBack}); $self->{itemFlip}->signal_connect("activate" , sub { $self->flipImage; }); $self->{imgContext}->append($self->{itemFlip}); } $self->{itemClear} = Gtk2::ImageMenuItem->new_from_stock('gtk-clear',undef); # This item will be deactivated if the component is locked $self->{itemClear}->set_sensitive(!$self->{locked}); $self->{itemClear}->signal_connect("activate" , sub { $self->clearImage; }); $self->{imgContext}->append($self->{itemClear}); # Disable for default image $self->{itemClear}->set_sensitive(0) if $self->isDefaultImage(); $self->{imgContext}->show_all; my $itemOpenWith = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextOpenWith}); $self->{menuOpenWith} = Gtk2::Menu->new; if ($hasGnome2VFS && ($parent->{options}->programs eq 'system' || $parent->{options}->imageEditor eq '')) { # Get applications for mime types corresponding with image # Get all editors/viewers for jpeg file format my $mimeTest = Gnome2::VFS::Mime::Type->new ("image/jpeg"); my @mimeList = $mimeTest->get_short_list_applications; # Add applications to open with list foreach (@mimeList) { my $launchApp = $_; my $item = Gtk2::MenuItem->new_with_mnemonic($launchApp->get_name); $item->signal_connect ('activate' => sub { $self->openWith($launchApp); }); $self->{menuOpenWith}->append($item); } #Gnome2::VFS -> shutdown(); } elsif ($parent->{options}->programs eq 'system' || $parent->{options}->imageEditor eq '') { # Can't parse applications, so use system default app my $item = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextImageEditor}); my $command; $command = ($^O =~ /win32/i) ? '' : ($^O =~ /macos/i) ? '/usr/bin/open' : $ENV{GCS_SHARE_DIR}.'/helpers/xdg-open'; # Not sure if this is correct, haven't tested with Windows: if ($^O =~ /win32/i) { $command = '"'.$command.'"' if $command; } $item->signal_connect ('activate' => sub { $self->openWithImageEditor($command); }); $self->{menuOpenWith}->append($item); } else { # Use user defined app my $item = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextImageEditor}); $item->signal_connect ('activate' => sub { $self->openWithImageEditor($parent->{options}->imageEditor); }); $self->{menuOpenWith}->append($item); } $itemOpenWith->set_submenu($self->{menuOpenWith}); # Disable for default image $itemOpenWith->set_sensitive(0) if $self->isDefaultImage(); $self->{imgContext}->append($itemOpenWith); $self->{imgContext}->show_all; } sub activateStateTracking { my $self = shift; $self->{trackState} = 1; } sub lock { my ($self, $locked) = @_; $self->{locked} = $locked; } sub showImage { my $self = shift; $self->{mainParent}->launch($self->{img}->getValue, 'image'); } sub isDefaultImage { my ($self) = @_; if ($self->{img}->getFile eq $self->{parent}->{defaultImage}) { return 1; } else { return 0; } } sub openWith { my ($self, $app) = @_; my $cmd; my $escFileName; # Ultra hacky workaround, because $app->{launch} segfaults. See http://bugzilla.gnome.org/show_bug.cgi?id=315049 # Probably should change to gvfs when perl modules are available if ($app->{command} =~ m/(\w*)/) { $cmd = $1; } $escFileName = $self->{img}->getFile; $escFileName =~ s/\ /%20/g; $self->editPicture("$cmd file://$escFileName"); } sub openWithImageEditor { my ($self, $editor) = @_; my $file = $self->{img}->getFile; $file =~ s|/|\\|g if ($^O =~ /win32/i); $self->editPicture("$editor \"$file\""); } sub editPicture { my ($self, $commandLine) = @_; my $file = $self->{img}->getFile; my $flipped = $self->{flipped}; $self->{fileWatchDays} = -M $file; $self->{fileWatcher} = Glib::Timeout->add(1000, sub { my $currentDays = -M $file; if ($currentDays < $self->{fileWatchDays}) { $self->changeState; # We remove it from the pixbuf cache in items view. Useful # for detailed list to be sure it will be re-loaded delete $self->{mainParent}->{itemsView}->{cache}->{$file} if $self->{mainParent}->{itemsView}->{cache}; $self->setValueWithParent($self->{img}->getValue, 1, $flipped); $self->{fileWatchDays} = $currentDays; } return 1; }); $self->{mainParent}->launch($commandLine, 'program', 1); } sub setWidth { my ($self, $value) = @_; $self->{width} = $value; $self->set_size_request($value, $self->{height}); $self->{img}->setWidth($value - $GCUtils::margin); } sub setHeight { my ($self, $value) = @_; $self->{height} = $value; $self->set_size_request($self->{width}, $value); $self->{img}->setHeight($value - $GCUtils::margin); } } { package GCMenuList; use Glib::Object::Subclass Gtk2::ComboBox:: ; @GCMenuList::ISA = ('Gtk2::ComboBox', 'GCGraphicComponent'); our $separatorValue = 'GCSSeparator'; sub isEmpty { my $self = shift; return 1 if ! defined $self->get_active_iter; return ($self->{listModel}->get($self->get_active_iter))[1] eq ''; my $idx = $self->get_history; $idx-- if $idx >= $self->{separatorPosition}; $idx = 0 if $idx < 0; return $self->{'values'}->[$idx]->{displayed} eq ''; } sub valueToDisplayed { my ($self, $value) = @_; foreach (@{$self->{'values'}}) { return $_->{displayed} if $_->{value} eq $value } return ''; } sub getValue { my ($self, $formatted) = @_; my $iter = $self->get_active_iter; my $value = ''; $value = ($self->{listModel}->get($iter))[0] if $iter; $value = $self->valueToDisplayed($value) if $formatted; return $value; } sub getDisplayedValue { my $self = shift; my $iter = $self->get_active_iter; return ($self->{listModel}->get($iter))[1] if $iter; return ''; } sub setValue { my ($self, $value) = @_; $value = 0 if !$value; my $i = 0; if ($value) { foreach (@{$self->{values}}) { last if $_->{value} eq $value; $i++; } } $i++ if $i >= $self->{separatorPosition}; $i-- if ($self->{default} == -1) && ($i >= $self->{count}); $self->set_active($i) if ($i < scalar(@{$self->{values}})); } sub clear { my $self = shift; $self->set_active(0); } sub lock { my ($self, $locked) = @_; $self->set_sensitive(!$locked); } sub getValues { my $self = shift; my @values; return $self->{values}; } sub setValues { my ($self, $values, $separatorPosition, $preserveValue) = @_; if ($self->{title}) { $separatorPosition = 1; unshift @$values, {value => -1, displayed => $self->{title}}; } my $model = $self->{listModel}; my $previous = $self->getValue if $preserveValue; $self->{values} = $values; $self->{separatorPosition} = $separatorPosition || 9999; $model->clear; my $i = 0; foreach (@$values) { if ($i == $self->{separatorPosition}) { $model->set($model->append, 0 => $GCMenuList::separatorValue, 1 => ''); $i++; } $model->set($model->append, 0 => $_->{value}, 1 => $_->{displayed}); $i++; } $self->{count} = $i; $self->setValue($previous) if $preserveValue; $self->set_active(0) if !$preserveValue; } sub setLastForDefault { my $self = shift; $self->{default} = -1; } sub setTitle { my ($self, $title) = @_; $self->{title} = $title; } sub new { my ($proto, $values, $separatorPosition) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->{listModel} = Gtk2::ListStore->new('Glib::String', 'Glib::String'); $self->set_model($self->{listModel}); my $renderer = Gtk2::CellRendererText->new; $self->pack_start($renderer, 1); $self->add_attribute($renderer, text => 1); $self->set_row_separator_func(sub { my ($model, $iter) = @_; my @values = $model->get($iter, 0); my $val = ''; $val = $values[0] if defined $values[0]; return $val eq $GCMenuList::separatorValue; }); $self->setValues($values, $separatorPosition) if $values; $self->{default} = 0; $self->set_focus_on_click(1); return $self; } sub activateStateTracking { my $self = shift; $self->signal_connect('changed' => sub { $self->setChanged; }); } } { package GCHeaderLabel; use Glib::Object::Subclass Gtk2::Label:: ; @GCHeaderLabel::ISA = ('Gtk2::Label', 'GCGraphicComponent'); sub new { my ($proto, $label) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->setText($label); $self->set_alignment(0,1); return $self; } sub setText { my ($self, $label) = @_; $self->set_markup(''.$label.''); } } { package GCLabel; use Glib::Object::Subclass Gtk2::Label:: ; @GCLabel::ISA = ('Gtk2::Label', 'GCGraphicComponent'); sub new { my ($proto, $label, $disableMarkup) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->set_markup($label) unless $disableMarkup; $self->set_label($label) if $disableMarkup; $self->set_alignment(0,0.5); return $self; } } { package GCColorLabel; use Glib::Object::Subclass Gtk2::EventBox:: ; @GCColorLabel::ISA = ('Gtk2::EventBox', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); sub new { my ($proto, $color, $listType) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $listType = 0 if !$listType; $self->modify_bg('normal', $color); $self->{label} = Gtk2::Label->new; $self->{label}->show; $self->{hboxFill} = new Gtk2::HBox(0,0); $self->{hboxFill}->pack_start($self->{label},1,1,0); $self->add($self->{hboxFill}); $self->set_alignment(0,0.5); $self->initHistory($listType); return $self; } sub acceptMarkup { my $self = shift; return 1; } sub setMarkup { my ($self, $text) = @_; $self->{label}->set_markup($text); } sub getValue { my $self = shift; (my $label = $self->{label}->get_label) =~ s/<.*?>(.*?)<\/.*?>/$1/g; return $label; } sub setBgColor { my ($self, $color) = @_; $self->modify_bg('normal', $color); } sub set_justify { my ($self, $value) = @_; $self->{label}->set_justify($value); $self->{label}->set_alignment(0.5,0) if $value eq 'center'; $self->{label}->set_alignment(1,0) if $value eq 'right'; } sub AUTOLOAD { my $self = shift; my $name = our $AUTOLOAD; return if $name =~ /::DESTROY$/; $name =~ s/.*?::(.*)/$1/; $self->{label}->$name(@_); } } { package GCColorLink; use Glib::Object::Subclass Gtk2::EventBox:: ; @GCColorLink::ISA = ('GCColorLabel'); sub new { my ($proto, $color, $opener) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new($color); bless ($self, $class); $self->{opener} = $opener; $self->signal_connect('button-release-event' => sub { my $value = $self->getValue; return if !$value; $self->{opener}->launch($value, 'url'); }); $self->signal_connect('enter-notify-event' => sub { $self->window->set_cursor(Gtk2::Gdk::Cursor->new('hand2')) if $self->getValue; }); #$self->window->set_cursor(Gtk2::Gdk::Cursor->new('watch')); return $self; } } { package GCColorLongLabel; use Glib::Object::Subclass Gtk2::TextView:: ; @GCColorLongLabel::ISA = ('Gtk2::TextView', 'GCGraphicComponent'); sub new { my ($proto, $color) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->set_editable(0); $self->set_wrap_mode('word'); $self->{background} = $color; $self->modify_base('normal', $color); $self->modify_bg('normal', $color); $self->set_border_width($GCUtils::halfMargin); my $layout = $self->create_pango_layout('G'); my (undef, $rect) = $layout->get_pixel_extents; $self->{em} = 1.5 * $rect->{height}; return $self; } sub acceptMarkup { my $self = shift; return 1; } sub setMarkup { my ($self, $text) = @_; #$text =~ s/&/&/g; if ($self->{resize}) { $self->resize; } else { my $buffer = $self->get_buffer; $self->get_buffer->set_text(''); $text =~ s|]*?)>([^<]*?)|$2|; if ($self->{spanTag}) { $buffer->get('tag-table')->remove($self->{spanTag}); } $self->{spanTag} = $buffer->create_tag('span', $self->getTagFromSpan($1)); $buffer->insert_with_tags_by_name ($buffer->get_start_iter, $text, 'span'); } } sub getValue { my $self = shift; (my $label = $self) =~ s/<.*?>(.*?)<\/.*?>/$1/g; return $label; } } { package GCColorTable; use Glib::Object::Subclass Gtk2::Table:: ; @GCColorTable::ISA = ('Gtk2::Table', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); sub new { my ($proto, $columns, $labels, $headerStyle, $contentStyle, $topHeader) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(1, $columns); bless ($self, $class); $self->set_col_spacings(3); $self->set_row_spacings(3); $self->{number} = $columns; $self->{style} = $contentStyle; $self->{firstRow} = 0; if ($topHeader) { $self->{firstRow}++; my $top = new GCColorLabel($headerStyle->{bgColor}); $top->set_padding($GCUtils::halfMargin,$GCUtils::halfMargin); $top->setMarkup('{style}.'>'.$topHeader.''); $self->attach($top, 0, $columns, 0, 1, ['expand', 'fill'], 'fill', 0, 0); } my $i; if ($columns > 1) { for $i(0..($columns - 1)) { my $header = new GCColorLabel($headerStyle->{bgColor}); $header->set_padding($GCUtils::halfMargin,$GCUtils::halfMargin); $header->setMarkup('{style}.'>'.$labels->[$i].''); $self->attach($header, $i, $i + 1, $self->{firstRow}, $self->{firstRow} + 1, ['expand', 'fill'], 'fill', 0, 0); } $self->{firstRow}++; } $self->initHistory($columns); return $self; } sub setValue { my ($self, $value) = @_; $self->{value} = $value; foreach (@{$self->{labels}}) { $self->remove($_); $_->destroy; } $self->{labels} = []; my @lines; if (ref($value) eq 'ARRAY') { @lines = @$value; } else { $value =~ s/^\s*//; @lines = split m/,/, $value; } if ($#lines < 0) { $self->hide_all; return; } my $i = $self->{firstRow}; $self->resize($#lines + 1 + $self->{firstRow}, $self->{number}); foreach (@lines) { my @cols; if (ref($value) eq 'ARRAY') { @cols = @$_; } else { @cols = split m/;/, $_; } my $j = 0; for my $col(@cols) { # TODO Optimize GCColorLongLabel. It offers a better display (no scrollbar) # but it slows down the display. my $label = new GCColorLabel($self->{style}->{bgColor}); $label->set_padding($GCUtils::halfMargin,$GCUtils::halfMargin); #my $label = new GCColorLongLabel($self->{style}->{bgColor}, '2em'); $label->setMarkup('{style}->{style}.'>'.$self->cleanMarkup($col, 1).''); $self->attach($label, $j, $j + 1, $i, $i + 1, ['expand', 'fill'], 'fill', 0, 0); push @{$self->{labels}}, $label; $j++; } $i++; } $self->show_all; } sub getValue { my $self = shift; return $self->{value}; } sub setBgColor { my ($self, $color) = @_; return; } sub set_justify { my ($self, $value) = @_; return; } } { package GCColorText; use Glib::Object::Subclass Gtk2::ScrolledWindow:: ; @GCColorText::ISA = ('Gtk2::ScrolledWindow', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); sub new { my ($proto, $color, $height, $listType) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $listType = 0 if !$listType; $self->{text} = new Gtk2::TextView; $self->{text}->set_editable(0); $self->{text}->set_wrap_mode('word'); $self->{background} = $color; $self->{text}->modify_base('normal', $color); $self->{text}->modify_bg('normal', $color); $self->{text}->set_border_width($GCUtils::halfMargin); $self->set_border_width(0); $self->set_shadow_type('none'); $self->set_policy('automatic', 'automatic'); $self->add($self->{text}); $self->initHistory($listType); my $layout = $self->create_pango_layout('G'); my (undef, $rect) = $layout->get_pixel_extents; $self->{em} = 1.5 * $rect->{height}; $self->setHeight($height) if $height; return $self; } sub acceptMarkup { my $self = shift; return 1; } sub setMarkup { my ($self, $text) = @_; if ($self->{resize}) { $self->resize; } else { my $buffer = $self->{text}->get_buffer; $self->{text}->get_buffer->set_text(''); $text =~ s|]*?)>([^<]*?)|$2|; if ($self->{spanTag}) { $buffer->get('tag-table')->remove($self->{spanTag}); } $self->{spanTag} = $buffer->create_tag('span', $self->getTagFromSpan($1)); $buffer->insert_with_tags_by_name ($buffer->get_start_iter, $text, 'span'); } } sub getValue { my $self = shift; (my $label = $self->{text}) =~ s/<.*?>(.*?)<\/.*?>/$1/g; return $label; } sub setHeight { my ($self, $height) = @_; $height =~ s/^([0-9]+)em$/$1*$self->{em}/e; $self->set_size_request(-1, $height); } sub AUTOLOAD { my $self = shift; my $name = our $AUTOLOAD; return if $name =~ /::DESTROY$/; $name =~ s/.*?::(.*)/$1/; $self->{text}->$name(@_); } } { package GCColorExpander; use Glib::Object::Subclass Gtk2::Expander:: ; @GCColorExpander::ISA = ('GCExpander'); sub new { my ($proto, $label, $bgColor, $fgStyle) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new($label); bless ($self, $class); $self->{label} = new GCColorLabel($bgColor); $self->set_label_widget($self->{label}); $self->{fgStyle} = $fgStyle; return $self; } sub setValue { my ($self, $label, $description) = @_; $label = '{fgStyle}.">$label"; $self->{label}->set_markup($label); if ($description) { $self->{description}->set_label($description); $self->{description}->show; } else { $self->{description}->set_label(''); $self->{description}->hide; } } } { package GCDialogHeader; use Glib::Object::Subclass Gtk2::HBox:: ; @GCDialogHeader::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); sub new { my ($proto, $text, $imageStock, $logosDir) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->{label} = new Gtk2::Label; $self->{label}->set_markup("$text"); $self->{label}->set_alignment(0,0.5); if (-f $logosDir.$imageStock.'.png') { $self->{image} = Gtk2::Image->new_from_file($logosDir.$imageStock.'.png'); $self->pack_start($self->{image},0,1,5); } $self->pack_start($self->{label},0,1,5); return $self; } } { package GCImageBox; use Glib::Object::Subclass Gtk2::VBox:: ; @GCImageBox::ISA = ('Gtk2::VBox', 'GCGraphicComponent'); sub new_from_file { my ($proto, $imageFile, $label) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(); bless ($self, $class); my $image = Gtk2::Image->new_from_file($imageFile); $self->init($image, $label); return $self; } sub new_from_stock { my ($proto, $stockId, $label) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(); bless ($self, $class); my $image = Gtk2::Image->new_from_stock($stockId, 'large-toolbar'); $self->init($image, $label); return $self; } sub init { my ($self, $image, $label) = @_; $self->{label} = new Gtk2::Label($label); $self->pack_start($image, 0, 0, 0); $self->pack_start($self->{label}, 0, 0, 0); $self->show_all; } } { package GCGroup; use Glib::Object::Subclass Gtk2::Frame:: ; @GCGroup::ISA = ('Gtk2::Frame', 'GCGraphicComponent'); sub new { my ($proto, $title) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless ($self, $class); $self->set_shadow_type('none'); $self->set_border_width($GCUtils::margin); $self->{label} = new Gtk2::Label; $self->{label}->set_padding(0,0); #$label->set_border_width(0); $self->setLabel($title); $self->set_label_widget($self->{label}); $self->set_label_align(0,0); $self->{marginBox} = new Gtk2::HBox(0,0); $self->{marginBox}->set_border_width($GCUtils::halfMargin); $self->add($self->{marginBox}); return $self; } sub setLabel { my ($self, $label) = @_; $self->{label}->set_markup(''.$label.''); } sub addWidget { my ($self, $widget, $margin) = @_; $margin = $GCUtils::halfMargin if !$margin; $self->{marginBox}->pack_start($widget, 1, 1, $margin); } sub setPadding { my ($self, $value) = @_; $self->{marginBox}->set_border_width($value); } } { package GCExpander; use Glib::Object::Subclass Gtk2::Expander:: ; @GCExpander::ISA = ('Gtk2::Expander', 'GCGraphicComponent'); sub new { my ($proto, $label, $bold) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new($label); bless ($self, $class); $self->{hbox} = new Gtk2::HBox(0,0); $self->{label} = new Gtk2::Label($label); $self->{label}->set_alignment(0,0.5); $self->{label}->set_markup("$label") if $bold; $self->{description} = new Gtk2::Label; $self->{description}->set_alignment(0,0); eval {$self->{description}->set_line_wrap_mode('word');}; $self->{hbox}->pack_start($self->{label}, 0, 0, 0); $self->{hbox}->pack_start($self->{description}, 1, 1, 0); $self->set_label_widget($self->{hbox}); $self->{signalHandler} = undef; return $self; } sub setMode { my ($self, $mode) = @_; if ($mode eq 'asis') { $self->{description}->set_ellipsize('none'); $self->{description}->set_line_wrap(0); if ($self->{signalHandler}) { $self->signal_handler_disconnect($self->{signalHandler}); $self->{signalHandler} = undef; $self->{description}->set_size_request(-1, -1); } } else { $self->{signalHandler} = $self->signal_connect('size-allocate' => sub { my $width = $self->allocation->width - $self->{label}->allocation->width - ( 4 * $GCUtils::margin); $self->{description}->set_size_request(($width >= -1) ? $width : -1 , -1); return 0; }); if ($mode eq 'wrap') { $self->{description}->set_ellipsize('none'); $self->{description}->set_line_wrap(1); } else { $self->{description}->set_ellipsize('end'); $self->{description}->set_line_wrap(0); } } } sub setValue { my ($self, $label, $description) = @_; $self->{label}->set_label($label); if ($description) { $self->{description}->set_label($description); $self->{description}->show; } else { $self->{description}->set_label(''); $self->{description}->hide; } } } { package GCFieldSelector; use Glib::Object::Subclass Gtk2::ComboBox:: ; @GCFieldSelector::ISA = ('GCMenuList'); our $anyFieldValue = 'GCAnyField'; sub valueToDisplayed { my ($self, $value) = @_; return ''; } sub setValue { my ($self, $value) = @_; $self->{listModel}->foreach(sub { my ($model, $path, $iter) = @_; if ($model->get_value($iter, 0) eq $value) { $self->set_active_iter($iter); return 1; } return 0; }); } sub getValues { my $self = shift; return []; } sub setValues { my ($self, $values, $separatorPosition, $preserveValue) = @_; return; } sub new { my ($proto, $withImages, $model, $advancedFilter, $withAnyField, $withoutEmpty, $uniqueType) = @_; my $class = ref($proto) || $proto; my $self = Gtk2::ComboBox->new; bless($self, $class); $self->{withImages} = $withImages; $self->{advancedFilter} = $advancedFilter; $self->{withAnyField} = $withAnyField; $self->{withoutEmpty} = $withoutEmpty; $self->{uniqueType} = $uniqueType; $self->{listModel} = Gtk2::TreeStore->new('Glib::String', 'Glib::String'); $self->set_model($self->{listModel}); my $renderer = Gtk2::CellRendererText->new; $self->pack_start($renderer, 1); $self->add_attribute($renderer, text => 1); $self->set_cell_data_func($renderer, sub { my ($layout, $cell, $model, $iter) = @_; my $sensitive = !$model->iter_has_child($iter); $cell->set('sensitive', $sensitive); }); $self->{default} = 0; $self->set_focus_on_click(1); $self->setModel($model) if $model; return $self; } sub setModel { my ($self, $model) = @_; $self->{listModel}->clear; my $field; my @fieldsInfo = @{$model->getDisplayedInfo}; if (! $self->{withoutEmpty}) { $self->{listModel}->set($self->{listModel}->append(undef), 0 => '', 1 => ''); } if ($self->{withAnyField}) { $self->{listModel}->set($self->{listModel}->append(undef), 0 => $anyFieldValue, 1 => $model->getDisplayedText('AdvancedSearchAnyField')); } foreach my $group(@fieldsInfo) { my @fields; foreach $field (@{$group->{items}}) { my $id = $field->{id}; next if ($model->{fieldsInfo}->{$id}->{type} eq 'image') && (!$self->{withImages}); next if ($self->{advancedFilter} && ( ($id eq $model->{commonFields}->{id}) || ($id eq $model->{commonFields}->{title}) || ($id eq $model->{commonFields}->{url}) ) ); next if ! $field->{label}; next if $self->{uniqueType} && ($self->{uniqueType} ne $model->{fieldsInfo}->{$id}->{type}); push @fields, $field; } if (scalar @fields) { my $groupIter = $self->{listModel}->append(undef); $self->{listModel}->set($groupIter, 0 => undef, 1 => $group->{title}); foreach $field(sort {$a->{label} cmp $b->{label}} @fields) { my $fieldIter = $self->{listModel}->append($groupIter); $self->{listModel}->set($fieldIter, 0 => $field->{id}, 1 => $field->{label}); } } } $self->{model} = $model; } sub activateStateTracking { my $self = shift; } # Create a widget suitable to enter a value according to current field type # It will destroy the current widget if it exists sub createEntryWidget { # $parent is the class that contains needed information # $comparison is the kind of comparison. Mainly useful if it is 'range' to create 2 fields # $currentWidget is the one we are replacing my ($self, $parent, $comparison, $currentWidget) = @_; my $value; if ($currentWidget) { $value = $currentWidget->getValue; $currentWidget->destroy; } my $widget; my $field = $self->getValue; my $info = $self->{model}->{fieldsInfo}->{$field}; # These ones are required for createWidget $self->{lang} = $parent->{lang}; $self->{options} = $parent->{options}; $self->{window} = $parent; ($widget, undef) = GCBaseWidgets::createWidget($self, $info, $comparison); if ($info->{type} eq 'history text') { $widget->setValues($parent->{parent}->{panel}->getValues($field)); } elsif ($info->{type} eq 'single list') { $widget->setValues($parent->{parent}->{panel}->getValues($field)->[0]); } $widget->setValue($value); return $widget; } } { package GCComparisonSelector; use Glib::Object::Subclass Gtk2::ComboBox:: ; @GCComparisonSelector::ISA = ('GCMenuList'); sub new { my ($proto, $parent) = @_; my $class = ref($proto) || $proto; my $self = $class->SUPER::new; bless($self, $class); $self->setValues([ {value => 'contain', displayed => $parent->{lang}->{ModelFilterContain}}, {value => 'notcontain', displayed => $parent->{lang}->{ModelFilterDoesNotContain}}, {value => 'regexp', displayed => $parent->{lang}->{ModelFilterRegexp}}, {value => 'range', displayed => $parent->{lang}->{ModelFilterRange}}, {value => 'eq', displayed => '='}, {value => 'lt', displayed => '<'}, {value => 'le', displayed => '≤'}, {value => 'gt', displayed => '>'}, {value => 'ge', displayed => '≥'}, ]); return $self; } } 1;