## @class Gtk2::Ex::Geo::Layer # @brief A root class for visual geospatial layers # @author Copyright (c) Ari Jolma # @author This library is free software; you can redistribute it and/or modify # it under the same terms as Perl itself, either Perl version 5.8.5 or, # at your option, any later version of Perl 5 you may have available. package Gtk2::Ex::Geo::Layer; =pod =head1 NAME Gtk2::Ex::Geo::Layer - A root class for visual geospatial layers The documentation of Gtk2::Ex::Geo is written in doxygen format. =cut use strict; use warnings; use Carp; use FileHandle; use Glib qw /TRUE FALSE/; use Graphics::ColorUtils qw /:all/; use Gtk2::Ex::Geo::Dialogs; use vars qw/$MAX_INT $MAX_REAL $COLOR_CELL_SIZE %PALETTE_TYPE %SYMBOL_TYPE %LABEL_PLACEMENT/; BEGIN { use Exporter 'import'; our %EXPORT_TAGS = ( 'all' => [ qw(%PALETTE_TYPE %SYMBOL_TYPE %LABEL_PLACEMENT) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); } $MAX_INT = 999999; $MAX_REAL = 999999999.99; $COLOR_CELL_SIZE = 20; # the integer values are the same as in libral visualization code: %PALETTE_TYPE = ( 'Single color' => 0, Grayscale => 1, Rainbow => 2, 'Color table' => 3, 'Color bins' => 4, 'Red channel' => 5, 'Green channel' => 6, 'Blue channel' => 7, ); %SYMBOL_TYPE = ( 'No symbol' => 0, 'Flow_direction' => 1, Square => 2, Dot => 3, Cross => 4, 'Wind rose' => 6, ); %LABEL_PLACEMENT = ( 'Center' => 0, 'Center left' => 1, 'Center right' => 2, 'Top left' => 3, 'Top center' => 4, 'Top right' => 5, 'Bottom left' => 6, 'Bottom center' => 7, 'Bottom right' => 8, ); ## @cmethod registration() # @brief Returns in an anonymous hash the generic dialogs and commands. sub registration { my $dialogs = Gtk2::Ex::Geo::Dialogs->new(); my $commands = { 'zoom to all' => { nr => 1, text => 'Zoom to all', tip => 'Zoom to all layers.', pos => -1, sub => sub { my(undef, $gui) = @_; $gui->{overlay}->zoom_to_all; } } }; return { dialogs => $dialogs, commands => $commands }; } ## @cmethod @palette_types() # # @brief Returns a list of valid palette types (strings). # @return a list of valid palette types (strings). sub palette_types { return sort {$PALETTE_TYPE{$a} <=> $PALETTE_TYPE{$b}} keys %PALETTE_TYPE; } ## @cmethod @symbol_types() # # @brief Returns a list of valid symbol types (strings). # @return a list of valid symbol types (strings). sub symbol_types { return sort {$SYMBOL_TYPE{$a} <=> $SYMBOL_TYPE{$b}} keys %SYMBOL_TYPE; } ## @cmethod @label_placements() # # @brief Returns a list of valid label_placements (strings). # @return a list of valid label_placements (strings). sub label_placements { return sort {$LABEL_PLACEMENT{$a} <=> $LABEL_PLACEMENT{$b}} keys %LABEL_PLACEMENT; } ## @cmethod $upgrade($object) # # @brief Upgrade object from substance class to the respective layer # class sub upgrade { my($object) = @_; return $object; } ## @cmethod new(%params) # @brief constructs a new layer object or blesses an object into a layer class # Calls defaults with the given parameters. sub new { my($class, %params) = @_; my $self = $params{self} ? $params{self} : {}; bless $self => (ref($class) or $class); $self->defaults(%params); return $self; } ## @method defaults(%params) # @brief assigns default values to attributes # The default values are hard-coded, but they can be overridden with # given values. The given values are lower case. # @todo: document the attributes sub defaults { my($self, %params) = @_; # set defaults for all $self->{NAME} = '' unless exists $self->{NAME}; $self->{ALPHA} = 255 unless exists $self->{ALPHA}; $self->{VISIBLE} = 1 unless exists $self->{VISIBLE}; $self->{PALETTE_TYPE} = 'Single color' unless exists $self->{PALETTE_TYPE}; $self->{SYMBOL_TYPE} = 'No symbol' unless exists $self->{SYMBOL_TYPE}; $self->{SYMBOL_SIZE} = 5 unless exists $self->{SYMBOL_SIZE}; # also the max size of the symbol, if symbol_scale is used $self->{SYMBOL_SCALE_MIN} = 0 unless exists $self->{SYMBOL_SCALE_MIN}; # similar to grayscale scale $self->{SYMBOL_SCALE_MAX} = 0 unless exists $self->{SYMBOL_SCALE_MAX}; $self->{HUE_AT_MIN} = 235 unless exists $self->{HUE_AT_MIN}; # as in libral visual.h $self->{HUE_AT_MAX} = 0 unless exists $self->{HUE_AT_MAX}; # as in libral visual.h $self->{HUE_DIR} = 1 unless exists $self->{HUE_DIR}; # from min up to max $self->{HUE} = -1 unless exists $self->{HUE}; # grayscale is gray scale $self->{SINGLE_COLOR} = [255, 255, 255, 255] unless exists $self->{SINGLE_COLOR}; $self->{COLOR_TABLE} = [] unless exists $self->{COLOR_TABLE}; $self->{COLOR_BINS} = [] unless exists $self->{COLOR_BINS}; # scales are used in rendering in some palette types $self->{COLOR_SCALE_MIN} = 0 unless exists $self->{COLOR_SCALE_MIN}; $self->{COLOR_SCALE_MAX} = 0 unless exists $self->{COLOR_SCALE_MAX}; # focus field is used in rendering and rasterization # this is the name of the field $self->{COLOR_FIELD} = '' unless exists $self->{COLOR_FIELD}; $self->{SYMBOL_FIELD} = 'Fixed size' unless exists $self->{SYMBOL_FIELD}; $self->{LABEL_FIELD} = 'No Labels' unless exists $self->{LABEL_FIELD}; $self->{LABEL_PLACEMENT} = 'Center' unless exists $self->{LABEL_PLACEMENT}; $self->{LABEL_FONT} = 'sans 12' unless exists $self->{LABEL_FONT}; $self->{LABEL_COLOR} = [0, 0, 0, 255] unless exists $self->{LABEL_COLOR}; $self->{LABEL_MIN_SIZE} = 0 unless exists $self->{LABEL_MIN_SIZE}; $self->{BORDER_COLOR} = [] unless exists $self->{BORDER_COLOR}; $self->{SELECTED_FEATURES} = []; # set from input $self->{NAME} = $params{name} if exists $params{name}; $self->{ALPHA} = $params{alpha} if exists $params{alpha}; $self->{VISIBLE} = $params{visible} if exists $params{visible}; $self->{PALETTE_TYPE} = $params{palette_type} if exists $params{palette_type}; $self->{SYMBOL_TYPE} = $params{symbol_type} if exists $params{symbol_type}; $self->{SYMBOL_SIZE} = $params{symbol_size} if exists $params{symbol_size}; $self->{SYMBOL_SCALE_MIN} = $params{scale_min} if exists $params{scale_min}; $self->{SYMBOL_SCALE_MAX} = $params{scale_max} if exists $params{scale_max}; $self->{HUE_AT_MIN} = $params{hue_at_min} if exists $params{hue_at_min}; $self->{HUE_AT_MAX} = $params{hue_at_max} if exists $params{hue_at_max}; $self->{HUE_DIR} = $params{hue_dir} if exists $params{hue_dir}; $self->{HUE} = $params{hue} if exists $params{hue}; @{$self->{SINGLE_COLOR}} = @{$params{single_color}} if exists $params{single_color}; $self->{COLOR_TABLE} = $params{color_table} if exists $params{color_table}; $self->{COLOR_BINS} = $params{color_bins} if exists $params{color_bins}; $self->{COLOR_SCALE_MIN} = $params{color_scale_min} if exists $params{color_scale_min}; $self->{COLOR_SCALE_MAX} = $params{color_scale_max} if exists $params{color_scale_max}; $self->{COLOR_FIELD} = $params{color_field} if exists $params{color_field}; $self->{SYMBOL_FIELD} = $params{symbol_field} if exists $params{symbol_field}; $self->{LABEL_FIELD} = $params{label_field} if exists $params{label_field}; $self->{LABEL_PLACEMENT} = $params{label_placement} if exists $params{label_placement}; $self->{LABEL_FONT} = $params{label_font} if exists $params{label_font}; @{$self->{LABEL_COLOR}} = @{$params{label_color}} if exists $params{label_color}; $self->{LABEL_MIN_SIZE} = $params{label_min_size} if exists $params{label_min_size}; @{$self->{BORDER_COLOR}} = @{$params{border_color}} if exists $params{border_color}; } ##@ignore sub DESTROY { my $self = shift; $self->destroy_dialogs; } ##@ignore # this should not be necessary sub destroy_dialogs { my $self = shift; for (keys %$self) { next unless /_dialog$/; my $dialog = $self->{$_}; next unless $dialog; $dialog = $dialog->get_widget($_); next unless $dialog; $dialog->hide(); $dialog->destroy(); delete $self->{$_}; } } ## @method $type() # # @brief Reports the type of the layer class for the GUI (human readable code). # @return Type of the layer class for the GUI (human readable code). sub type { my $self = shift; return '?'; } ## @method $name($name) # # @brief Get or set the name of the layer. # @param[in] name (optional) Layers name. # @return Name of layer, if no name is given to the method. sub name { my($self, $name) = @_; defined $name ? $self->{NAME} = $name : $self->{NAME}; } ## @method $alpha($alpha) # # @brief Get or set the alpha (transparency) of the layer. # @param[in] alpha (optional) Layers alpha channels value (0 ... 255). # @return Current alpha value, if no parameter is given. sub alpha { my($self, $alpha) = @_; defined $alpha ? $self->{ALPHA} = $alpha : $self->{ALPHA}; } ## @method visible($visible) # # @brief[in] Show or hide the layer. # @param visible If true then the layer is made visible, else hidden. sub visible { my($self, $visible) = @_; defined $visible ? $self->{VISIBLE} = $visible : $self->{VISIBLE}; } ## @method border_color($red, $green, $blue) # @brief Set or get the border color of the features. # @code # $self->border_color($red, $green, $blue); # set # $self->border_color(); # clear, no border # @color = $self->border_color(); # get # @endcode sub border_color { my($self, @color) = @_; @{$self->{BORDER_COLOR}} = @color if @color; return @{$self->{BORDER_COLOR}} if defined wantarray; @{$self->{BORDER_COLOR}} = () unless @color; } ## @method inspect_data # @brief Return data for the inspect window. sub inspect_data { my $self = shift; return $self; } ## @method void properties_dialog(Gtk2::Ex::Glue gui) # # @brief A request to invoke the properties dialog for this layer object. # @param gui A Gtk2::Ex::Glue object (contains predefined dialogs). sub properties_dialog { my($self, $gui) = @_; croak("no properties dialog defined for class ".ref($self)); } ## @method void open_features_dialog(Gtk2::Ex::Glue gui) # # @brief A request to invoke a features dialog for this layer object. # @param gui A Gtk2::Ex::Glue object (contains predefined dialogs). sub open_features_dialog { my($self, $gui) = @_; croak("no features dialog defined for class ".ref($self)); } ## @cmethod hashref menu_items($items) # # @brief Reports the class menu items (name and sub) for the GUI. # @param items pre-defined menu items # @return A reference to an anonymous hash. sub menu_items { my($self, $items) = @_; $items->{x90} = { nr => 90, }; $items->{'_Unselect all'} = { nr => 91, sub => sub { my($self, $gui) = @{$_[1]}; $self->select; $gui->{overlay}->update_image; $self->open_features_dialog($gui) if $self->dialog_visible('features_dialog'); } }; $items->{'_Symbol...'} = { nr => 92, sub => sub { my($self, $gui) = @{$_[1]}; $self->open_symbols_dialog($gui); } }; $items->{'_Colors...'} = { nr => 93, sub => sub { my($self, $gui) = @{$_[1]}; $self->open_colors_dialog($gui); } }; $items->{'_Labeling...'} = { nr => 94, sub => sub { my($self, $gui) = @{$_[1]}; $self->open_labels_dialog($gui); } }; $items->{'_Inspect...'} = { nr => 95, sub => sub { my($self, $gui) = @{$_[1]}; $gui->inspect($self->inspect_data, $self->name); } }; $items->{'_Properties...'} = { nr => 99, sub => sub { my($self, $gui) = @{$_[1]}; $self->properties_dialog($gui); } }; return $items; } ## @method $palette_type($palette_type) # # @brief Get or set the palette type. # @param[in] palette_type (optional) New palette type to set to the layer. # @return The current palette type of the layer. sub palette_type { my($self, $palette_type) = @_; if (defined $palette_type) { croak "Unknown palette type: $palette_type" unless defined $PALETTE_TYPE{$palette_type}; $self->{PALETTE_TYPE} = $palette_type; } else { return $self->{PALETTE_TYPE}; } } ## @method @supported_palette_types() # # @brief Return a list of all by this class supported palette types. # @return A list of all by this class supported palette types. sub supported_palette_types { my($class) = @_; my @ret; for my $t (sort {$PALETTE_TYPE{$a} <=> $PALETTE_TYPE{$b}} keys %PALETTE_TYPE) { push @ret, $t; } return @ret; } ## @method $symbol_type($type) # # @brief Get or set the symbol type. # @param[in] type (optional) New symbol type to set to the layer. # @return The current symbol type of the layer. sub symbol_type { my($self, $symbol_type) = @_; if (defined $symbol_type) { croak "Unknown symbol type: $symbol_type" unless defined $SYMBOL_TYPE{$symbol_type}; $self->{SYMBOL_TYPE} = $symbol_type; } else { return $self->{SYMBOL_TYPE}; } } ## @method @supported_symbol_types() # # @brief Return a list of all by this class supported symbol types. # @return A list of all by this class supported symbol types. sub supported_symbol_types { my($self) = @_; my @ret; for my $t (sort {$SYMBOL_TYPE{$a} <=> $SYMBOL_TYPE{$b}} keys %SYMBOL_TYPE) { push @ret, $t; } return @ret; } ## @method $symbol_size($size) # # @brief Get or set the symbol size. # @param[in] size (optional) The layers symbols new size. # @return The current size of the layers symbol. # @note Even if the layer has at the moment no symbol, the symbol size can be # defined. sub symbol_size { my($self, $size) = @_; defined $size ? $self->{SYMBOL_SIZE} = $size+0 : $self->{SYMBOL_SIZE}; } ## @method @symbol_scale($scale_min, $scale_max) # # @brief Get or set the symbol scale. # @param[in] scale_min (optional) The layers symbols new minimum scale. Scale under # which the symbol is hidden even if the layer is visible. # @param[in] scale_max (optional) The layers symbols new maximum scale. Scale over # which the symbol is hidden even if the layer is visible. # @return The current scale minimum and maximum of the layers symbol. # @note Even if the layer has at the moment no symbol, the symbol scales can be # defined. sub symbol_scale { my($self, $min, $max) = @_; if (defined $min) { $self->{SYMBOL_SCALE_MIN} = $min+0; $self->{SYMBOL_SCALE_MAX} = $max+0; } return ($self->{SYMBOL_SCALE_MIN}, $self->{SYMBOL_SCALE_MAX}); } ## @method @hue_range($min, $max, $dir) # # @brief Determines the hue range # @param min The minimum hue value. # @param max The maximum hue value. # @param dir (1 or -1) Determines whether the rainbow is from min to # max (hue increases, red->green->blue), or from max to min (hue # decreases, red->blue->green). Default is increase. sub hue_range { my($self, $min, $max, $dir) = @_; if (defined $min) { $self->{HUE_AT_MIN} = $min+0; $self->{HUE_AT_MAX} = $max+0; $self->{HUE_DIR} = (!(defined $dir) or $dir == 1) ? 1 : -1; } return ($self->{HUE_AT_MIN}, $self->{HUE_AT_MAX}, $self->{HUE_DIR}); } ## @method $hue($hue) # # @brief Get or set the layers hue. # @param hue (optional) Hue to set to the layer. # @return Returns the layers hue value. # @todo Add a check that the given hue value is between the minimum and maximum? sub hue { my($self, $hue) = @_; defined $hue ? $self->{HUE} = $hue+0 : $self->{HUE}; } ## @method $symbol_field($field_name) # # @brief Get or set the field, which is used for determining the size of the # symbol. # @param[in] field_name (optional) Name of the field determining symbol size. # @return Name of the field determining symbol size. # @exception If field name is given as a parameter, but the field does not # exist in the layer. sub symbol_field { my($self, $field_name) = @_; if (defined $field_name) { my $schema = $self->schema(); if ($field_name eq 'Fixed size' or $schema->{$field_name}) { $self->{SYMBOL_FIELD} = $field_name; } else { croak "Layer ".$self->name()." does not have field with name: $field_name"; } } return $self->{SYMBOL_FIELD}; } ## @method @single_color(@rgba) # # @brief Get or set the color, which is used if palette is 'single color' # @param[in] rgba (optional) A list of channels defining the RGBA color. # @return The current color. # @exception Some color channels are given, but not exactly all four channels. sub single_color { my $self = shift; croak "@_ is not a RGBA color" if @_ and @_ != 4; $self->{SINGLE_COLOR} = [@_] if @_; return @{$self->{SINGLE_COLOR}}; } ## @method @color_scale($scale_min, $scale_max) # # @brief Get or set the range, which is used for coloring in continuous palette # types. # @param[in] scale_min (optional) The layers colors new minimum scale. Scale under # which the color is not shown even if the layer is visible. # @param[in] scale_max (optional) The layers colors new maximum scale. Scale over # which the color is not shown even if the layer is visible. # @return The current scale minimum and maximum of the layers color. sub color_scale { my($self, $min, $max) = @_; if (defined $min) { $self->{COLOR_SCALE_MIN} = $min+0; $self->{COLOR_SCALE_MAX} = $max+0; } return ($self->{COLOR_SCALE_MIN}, $self->{COLOR_SCALE_MAX}); } ## @method $color_field($field_name) # # @brief Get or set the field, which is used for determining the color. # @param[in] field_name (optional) Name of the field determining color. # @return Name of the field determining color. # @exception If field name is given as a parameter, but the field does not # exist in the layer. sub color_field { my($self, $field_name) = @_; if (defined $field_name) { my $schema = $self->schema(); if ($schema->{$field_name}) { $self->{COLOR_FIELD} = $field_name; } else { croak "Layer ", $self->name, " does not have field: $field_name"; } } return $self->{COLOR_FIELD}; } ## @method @color_table($color_table) # # @brief Get or set the color table. # @param[in] color_table (optional) Name of file from where the color table can be # read. # @return Current color table, if no parameter is given. # @exception A filename is given, which can't be opened/read or does not have a # color table. ## @method @color_table(Geo::GDAL::ColorTable color_table) # # @brief Get or set the color table. # @param[in] color_table (optional) Geo::GDAL::ColorTable. # @return Current color table, if no parameter is given. ## @method @color_table(listref color_table) # # @brief Get or set the color table. # @param[in] color_table (optional) Reference to an array having the color table. # @return Current color table, if no parameter is given. sub color_table { my($self, $color_table) = @_; unless (defined $color_table) { $self->{COLOR_TABLE} = [] unless $self->{COLOR_TABLE}; return $self->{COLOR_TABLE}; } if (ref($color_table) eq 'ARRAY') { $self->{COLOR_TABLE} = []; for (@$color_table) { push @{$self->{COLOR_TABLE}}, [@$_]; } } elsif (ref($color_table)) { $self->{COLOR_TABLE} = []; for my $i (0..$color_table->GetCount-1) { my @color = $color_table->GetColorEntryAsRGB($i); push @{$self->{COLOR_TABLE}}, [$i, @color]; } } else { my $fh = new FileHandle; croak "can't read from $color_table: $!\n" unless $fh->open("< $color_table"); $self->{COLOR_TABLE} = []; while (<$fh>) { next if /^#/; my @tokens = split /\s+/; next unless @tokens > 3; $tokens[4] = 255 unless defined $tokens[4]; for (@tokens) { $_ =~ s/\D//g; } for (@tokens[1..4]) { $_ = 0 if $_ < 0; $_ = 255 if $_ > 255; } push @{$self->{COLOR_TABLE}}, \@tokens; } $fh->close; } } ## @method save_color_table($filename) # # @brief Saves the layers color table into the file, which name is given as # parameter. # @param[in] filename Name of file where the color table is saved. # @exception A filename is given, which can't be written to. sub save_color_table { my($self, $filename) = @_; my $fh = new FileHandle; croak "can't write to $filename: $!\n" unless $fh->open("> $filename"); for my $color (@{$self->{COLOR_TABLE}}) { print $fh "@$color\n"; } $fh->close; } ## @method @color_bins($color_bins) # # @brief Get or set the color bins. # @param[in] color_bins (optional) Name of file from where the color bins can be # read. # @return The current color bins if no parameter is given. # @exception A filename is given, which can't be opened/read or does not have # the color bins. ## @method @color_bins(listref color_bins) # # @brief Get or set the color bins. # @param[in] color_bins (optional) Array including the color bins. # @return The current color bins if no parameter is given. sub color_bins { my($self, $color_bins) = @_; unless (defined $color_bins) { $self->{COLOR_BINS} = [] unless $self->{COLOR_BINS}; return $self->{COLOR_BINS}; } if (ref($color_bins) eq 'ARRAY') { $self->{COLOR_BINS} = []; for (@$color_bins) { push @{$self->{COLOR_BINS}}, [@$_]; } } else { my $fh = new FileHandle; croak "can't read from $color_bins: $!\n" unless $fh->open("< $color_bins"); $self->{COLOR_BINS} = []; while (<$fh>) { next if /^#/; my @tokens = split /\s+/; next unless @tokens > 3; $tokens[4] = 255 unless defined $tokens[4]; for (@tokens[1..4]) { $_ =~ s/\D//g; $_ = 0 if $_ < 0; $_ = 255 if $_ > 255; } push @{$self->{COLOR_BINS}}, \@tokens; } $fh->close; } } ## @method save_color_bins($filename) # # @brief Saves the layers color bins into the file, which name is given as # parameter. # @param[in] filename Name of file where the color bins are saved. # @exception A filename is given, which can't be written to. sub save_color_bins { my($self, $filename) = @_; my $fh = new FileHandle; croak "can't write to $filename: $!\n" unless $fh->open("> $filename"); for my $color (@{$self->{COLOR_BINS}}) { print $fh "@$color\n"; } $fh->close; } ## @method hashref labeling($labeling) # # @brief Sets the labeling for the layer. # @param[in] labeling An anonymous hash containing the labeling: # { field => , font => , color => [r, g, b, a], min_size => } # @return labeling in an anonymous hash sub labeling { my($self, $labeling) = @_; if ($labeling) { $self->{LABEL_FIELD} = $labeling->{field}; $self->{LABEL_PLACEMENT} = $labeling->{placement}; $self->{LABEL_FONT} = $labeling->{font}; @{$self->{LABEL_COLOR}} =@{$labeling->{color}}; $self->{LABEL_MIN_SIZE} = $labeling->{min_size}; } else { $labeling = {}; $labeling->{field} = $self->{LABEL_FIELD}; $labeling->{placement} = $self->{LABEL_PLACEMENT}; $labeling->{font} = $self->{LABEL_FONT}; @{$labeling->{color}} = @{$self->{LABEL_COLOR}}; $labeling->{min_size} = $self->{LABEL_MIN_SIZE}; } return $labeling; } ## @method select(%params) # # @brief Select features based on user input. # @param params named params, the key is something that is recognized by the features method # and the value is a geometry the user has defined # - key A Geo::OGR::Geometry object representing the point or area the user has selected # The key, value pair is fed as such to features subroutine. # A call without parameters deselects all features. sub select { my($self, %params) = @_; if (@_ > 1) { for my $key (keys %params) { my $features = $self->features($key => $params{$key}); $self->selected_features($features); } } else { $self->{SELECTED_FEATURES} = []; } } # get or set the selected features, give an array ref sub selected_features { my($self, $selected) = @_; if (defined $selected) { $self->{SELECTED_FEATURES} = $selected; } else { return $self->{SELECTED_FEATURES}; } } # spatial selection of features, return a ref to an array of matching features sub features { } ## @method schema() # # @brief Return the schema of the layer as an anonymous hash. # # For the structure of the schema hash see Geo::Vector::schema sub schema { return { dummy => { TypeName => 'Integer' } }; } sub value_range { return (0, 0); } ## @method render_selection($gc) # # @brief Render the selection using the given graphics context # @param $gc Gtk2::Gdk::GC sub render_selection { } ## @method $bootstrap_dialog($gui, $dialog, $title, $bootstrap) # # @brief Bootstrap the requested dialog. # @return the GladeXML object of the dialog. sub bootstrap_dialog { my($self, $gui, $dialog, $title, $connects) = @_; unless ($self->{$dialog}) { $self->{$dialog} = $gui->get_dialog($dialog); croak "$dialog does not exist" unless $self->{$dialog}; for (keys %$connects) { $self->{$dialog}->get_widget($_)->signal_connect(@{$connects->{$_}}); } } elsif (!$self->{$dialog}->get_widget($dialog)->get('visible')) { $self->{$dialog}->get_widget($dialog)->move(@{$self->{$dialog.'_position'}}); $self->{$dialog}->get_widget($dialog)->show_all; } $self->{$dialog}->get_widget($dialog)->set_title($title); $self->{$dialog}->get_widget($dialog)->present; return $self->{$dialog}; } ## @method $dialog_visible($dialog) # # @brief Return true is the given (name of a ) dialog is visible. sub dialog_visible { my($self, $dialog) = @_; my $d = $self->{$dialog}; return 0 unless $d; return $d->get_widget($dialog)->get('visible'); } ## @method open_symbols_dialog($gui) # @brief Open the symbols dialog for this layer. sub open_symbols_dialog { my($self, $gui) = @_; my $dialog = $self->bootstrap_dialog($gui, 'symbols_dialog', "Symbols for ".$self->name, { symbols_dialog => [delete_event => \&cancel_symbols, [$self, $gui]], symbols_scale_button => [clicked => \&fill_symbol_scale_fields, [$self, $gui]], symbols_field_combobox => [changed=>\&symbol_field_changed, [$self, $gui]], symbols_type_combobox => [changed=>\&symbol_field_changed, [$self, $gui]], symbols_apply_button => [clicked => \&apply_symbols, [$self, $gui, 0]], symbols_cancel_button => [clicked => \&cancel_symbols, [$self, $gui]], symbols_ok_button => [clicked => \&apply_symbols, [$self, $gui, 1]], }); my $symbol_type_combo = $dialog->get_widget('symbols_type_combobox'); my $field_combo = $dialog->get_widget('symbols_field_combobox'); my $scale_min = $dialog->get_widget('symbols_scale_min_entry'); my $scale_max = $dialog->get_widget('symbols_scale_max_entry'); my $size_spin = $dialog->get_widget('symbols_size_spinbutton'); # back up data my $symbol_type = $self->symbol_type(); my $size = $self->symbol_size(); my $field = $self->symbol_field(); my @scale = $self->symbol_scale(); $self->{backup}->{symbol_type} = $symbol_type; $self->{backup}->{symbol_size} = $size; $self->{backup}->{symbol_field} = $field; $self->{backup}->{symbol_scale} = \@scale; # set up the controllers $self->fill_symbol_type_combo($symbol_type); $self->fill_symbol_field_combo($field); $scale_min->set_text($scale[0]); $scale_max->set_text($scale[1]); $size_spin->set_value($size); } ##@ignore sub apply_symbols { my($self, $gui, $close) = @{$_[1]}; my $dialog = $self->{symbols_dialog}; my $symbol_type = $self->get_selected_symbol_type(); $self->symbol_type($symbol_type); my $field_combo = $dialog->get_widget('symbols_field_combobox'); my $field = $self->{index2symbol_field}{$field_combo->get_active()}; $self->symbol_field($field) if defined $field; my $scale_min = $dialog->get_widget('symbols_scale_min_entry'); my $scale_max = $dialog->get_widget('symbols_scale_max_entry'); $self->symbol_scale($scale_min->get_text(), $scale_max->get_text()); my $size_spin = $dialog->get_widget('symbols_size_spinbutton'); my $size = $size_spin->get_value(); $self->symbol_size($size); $self->{symbols_dialog_position} = [$dialog->get_widget('symbols_dialog')->get_position]; $dialog->get_widget('symbols_dialog')->hide() if $close; $gui->set_layer($self); $gui->{overlay}->render; } ##@ignore sub cancel_symbols { my($self, $gui); for (@_) { next unless ref CORE::eq 'ARRAY'; ($self, $gui) = @{$_}; } $self->symbol_type($self->{backup}->{symbol_type}); $self->symbol_field($self->{backup}->{symbol_field}) if $self->{backup}->{symbol_field}; $self->symbol_scale(@{$self->{backup}->{symbol_scale}}); $self->symbol_size($self->{backup}->{symbol_size}); my $dialog = $self->{symbols_dialog}->get_widget('symbols_dialog'); $self->{symbols_dialog_position} = [$dialog->get_position]; $dialog->hide(); $gui->set_layer($self); $gui->{overlay}->render; 1; } ##@ignore sub fill_symbol_type_combo { my($self, $symbol_type) = @_; $symbol_type = '' unless defined $symbol_type; my $combo = $self->{symbols_dialog}->get_widget('symbols_type_combobox'); my $model = $combo->get_model; $model->clear; my @symbol_types = $self->supported_symbol_types(); my $i = 0; my $active = 0; for (@symbol_types) { $model->set ($model->append, 0, $_); $self->{index2symbol_type}{$i} = $_; $self->{symbol_type2index}{$_} = $i; $active = $i if $_ eq $symbol_type; $i++; } $combo->set_active($active); } ##@ignore sub get_selected_symbol_type { my $self = shift; my $combo = $self->{symbols_dialog}->get_widget('symbols_type_combobox'); $self->{index2symbol_type}{$combo->get_active()}; } ##@ignore sub fill_symbol_field_combo { my($self, $symbol_field) = @_; my $combo = $self->{symbols_dialog}->get_widget('symbols_field_combobox'); my $model = $combo->get_model; $model->clear; delete $self->{index2symbol_field}; my $active = 0; my $i = 0; my $name = 'Fixed size'; $model->set($model->append, 0, $name); $active = $i if $name eq $self->symbol_field(); $self->{index2symbol_field}{$i} = $name; $i++; my $schema = $self->schema(); for my $name (sort keys %$schema) { my $type = $schema->{$name}{TypeName}; next unless $type; next unless $type eq 'Integer' or $type eq 'Real'; $model->set($model->append, 0, $name); $active = $i if $name eq $symbol_field; $self->{index2symbol_field}{$i} = $name; $i++; } $combo->set_active($active); } ##@ignore sub get_selected_symbol_field { my $self = shift; my $combo = $self->{symbols_dialog}->get_widget('symbols_field_combobox'); $self->{index2symbol_field}{$combo->get_active()}; } ##@ignore sub fill_symbol_scale_fields { my($self, $gui) = @{$_[1]}; my @range; my $field = $self->get_selected_symbol_field(); return if $field eq 'Fixed size'; my @r = $gui->{overlay}->get_viewport_of_selection; @r = $gui->{overlay}->get_viewport unless @r; eval { @range = $self->value_range(field_name => $field, filter_rect => \@r); }; if ($@) { $gui->message("$@"); return; } $self->{symbols_dialog}->get_widget('symbols_scale_min_entry')->set_text($range[0]); $self->{symbols_dialog}->get_widget('symbols_scale_max_entry')->set_text($range[1]); } ##@ignore sub symbol_field_changed { my($self, $gui) = @{$_[1]}; my $type = $self->get_selected_symbol_type(); my $field = $self->get_selected_symbol_field(); my $dialog = $self->{symbols_dialog}; if ($type eq 'No symbol') { $dialog->get_widget('symbols_size_spinbutton')->set_sensitive(0); $dialog->get_widget('symbols_field_combobox')->set_sensitive(0); } else { $dialog->get_widget('symbols_size_spinbutton')->set_sensitive(1); $dialog->get_widget('symbols_field_combobox')->set_sensitive(1); } if (!$field or $field eq 'Fixed size') { $dialog->get_widget('symbols_scale_min_entry')->set_sensitive(0); $dialog->get_widget('symbols_scale_max_entry')->set_sensitive(0); $dialog->get_widget('symbols_size_label')->set_text('Size: '); } else { $dialog->get_widget('symbols_scale_min_entry')->set_sensitive(1); $dialog->get_widget('symbols_scale_max_entry')->set_sensitive(1); $dialog->get_widget('symbols_size_label')->set_text('Maximum size: '); } } # open colors dialog sub open_colors_dialog { my($self, $gui) = @_; my $dialog = $self->bootstrap_dialog($gui, 'colors_dialog', "Colors for ".$self->name, { colors_dialog => [delete_event => \&cancel_colors, [$self, $gui]], color_scale_button => [clicked => \&fill_color_scale_fields, [$self, $gui]], color_legend_button => [clicked => \&make_color_legend, [$self, $gui]], get_colors_button => [clicked => \&get_colors, [$self, $gui]], open_colors_button => [clicked => \&open_colors_file, [$self, $gui]], save_colors_button => [clicked => \&save_colors_file, [$self, $gui]], edit_color_button => [clicked => \&edit_color, [$self, $gui]], delete_color_button => [clicked => \&delete_color, [$self, $gui]], add_color_button => [clicked => \&add_color, [$self, $gui]], palette_type_combobox => [changed => \&palette_type_changed, [$self, $gui]], color_field_combobox => [changed => \&color_field_changed, [$self, $gui]], min_hue_button => [clicked => \&set_hue_range, [$self, $gui, 'min']], max_hue_button => [clicked => \&set_hue_range, [$self, $gui, 'max']], hue_button => [clicked => \&set_hue, [$self, $gui]], colors_apply_button => [clicked => \&apply_colors, [$self, $gui, 0]], colors_cancel_button => [clicked => \&cancel_colors, [$self, $gui]], colors_ok_button => [clicked => \&apply_colors, [$self, $gui, 1]], }); my $palette_type_combo = $dialog->get_widget('palette_type_combobox'); my $field_combo = $dialog->get_widget('color_field_combobox'); my $scale_min = $dialog->get_widget('color_scale_min_entry'); my $scale_max = $dialog->get_widget('color_scale_max_entry'); my $hue_min = $dialog->get_widget('min_hue_label'); my $hue_max = $dialog->get_widget('max_hue_label'); my $hue_range_sel = $dialog->get_widget('hue_range_combobox'); my $hue_button = $dialog->get_widget('hue_checkbutton'); my $hue = $dialog->get_widget('hue_label'); $self->{current_coloring_type} = ''; # back up data my $palette_type = $self->palette_type(); my @single_color = $self->single_color(); my $field = $self->color_field(); my @scale = $self->color_scale(); my @hue_range = $self->hue_range; my $table = $self->color_table(); my $bins = $self->color_bins(); $self->{backup}->{palette_type} = $palette_type; $self->{backup}->{single_color} = \@single_color; $self->{backup}->{field} = $field; $self->{backup}->{scale} = \@scale; $self->{backup}->{hue_range} = \@hue_range; $self->{backup}->{hue} = $self->hue; $self->{backup}->{table} = $table; $self->{backup}->{bins} = $bins; # set up the controllers $self->fill_palette_type_combo($palette_type); $self->fill_color_field_combo($palette_type); $scale_min->set_text($scale[0]); $scale_max->set_text($scale[1]); $hue_min->set_text($hue_range[0]); $hue_max->set_text($hue_range[1]); $hue_range_sel->set_active($hue_range[2] == 1 ? 0 : 1); $hue_button->set_active($self->hue > 0 ? TRUE : FALSE); my @color = $self->hue; $hue->set_text("@color"); palette_type_changed(undef, [$self, $gui]); color_field_changed(undef, [$self, $gui]); if ($palette_type eq 'Single color') { $self->fill_colors_treeview([[@single_color]]); } elsif ($palette_type eq 'Color table') { $self->fill_colors_treeview($table); } elsif ($palette_type eq 'Color bins') { $self->fill_colors_treeview($bins); } $dialog->get_widget('colors_dialog')->show_all; } ##@ignore sub apply_colors { my($self, $gui, $close) = @{$_[1]}; my $dialog = $self->{colors_dialog}; my $palette_type = $self->get_selected_palette_type(); $self->palette_type($palette_type); my $field_combo = $dialog->get_widget('color_field_combobox'); my $field = $self->{index2field}{$field_combo->get_active()}; $self->color_field($field) if defined $field; my $scale_min = $dialog->get_widget('color_scale_min_entry'); my $scale_max = $dialog->get_widget('color_scale_max_entry'); $self->color_scale($scale_min->get_text(), $scale_max->get_text()); $self->hue_range($dialog->get_widget('min_hue_label')->get_text, $dialog->get_widget('max_hue_label')->get_text, $dialog->get_widget('hue_range_combobox')->get_active == 0 ? 1 : -1); my $hue = $dialog->get_widget('hue_checkbutton')->get_active(); $self->hue($hue ? $dialog->get_widget('hue_label')->get_text : -1); if ($palette_type eq 'Single color') { my $table = $self->get_table_from_treeview(); $self->single_color(@{$table->[0]}); } elsif ($palette_type eq 'Color table') { my $table = $self->get_table_from_treeview(); $self->color_table($table); } elsif ($palette_type eq 'Color bins') { my $table = $self->get_table_from_treeview(); $self->color_bins($table); } if ($palette_type eq 'Grayscale' or $palette_type eq 'Rainbow') { $self->put_scale_in_treeview($dialog->get_widget('colors_treeview'), $palette_type); } $dialog = $dialog->get_widget('colors_dialog'); $self->{colors_dialog_position} = [$dialog->get_position]; $dialog->hide() if $close; $gui->{overlay}->render; } ##@ignore sub cancel_colors { my($self, $gui); for (@_) { next unless ref eq 'ARRAY'; ($self, $gui) = @{$_}; } $self->palette_type($self->{backup}->{palette_type}); $self->single_color(@{$self->{backup}->{single_color}}); $self->color_field($self->{backup}->{field}) if $self->{backup}->{field}; $self->color_table($self->{backup}->{table}); $self->color_bins($self->{backup}->{bins}); $self->color_scale(@{$self->{backup}->{scale}}); $self->hue_range(@{$self->{backup}->{hue_range}}); $self->hue($self->{backup}->{hue}); my $dialog = $self->{colors_dialog}->get_widget('colors_dialog'); $self->{colors_dialog_position} = [$dialog->get_position]; $dialog->hide(); $gui->{overlay}->render; 1; } ##@ignore sub get_selected_palette_type { my $self = shift; my $combo = $self->{colors_dialog}->get_widget('palette_type_combobox'); $self->{index2palette_type}{$combo->get_active()}; } ##@ignore sub get_selected_color_field { my $self = shift; my $combo = $self->{colors_dialog}->get_widget('color_field_combobox'); $self->{index2field}{$combo->get_active()}; } ##@ignore sub get_colors { my($self, $gui) = @{$_[1]}; my $table = $self->colors_from_dialog($gui); $self->fill_colors_treeview($table) if $table; } ##@ignore sub open_colors_file { my($self, $gui) = @{$_[1]}; my $palette_type = $self->get_selected_palette_type(); my $file_chooser = Gtk2::FileChooserDialog->new ("Select a $palette_type file", undef, 'open', 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok'); if ($file_chooser->run eq 'ok') { my $filename = $file_chooser->get_filename; $file_chooser->destroy; my $table = {}; if ($palette_type eq 'Color table') { eval { color_table($table, $filename); $table = color_table($table); } } elsif ($palette_type eq 'Color bins') { eval { color_bins($table, $filename); $table = color_bins($table); } } if ($@) { $gui->message("$@"); } else { $self->fill_colors_treeview($table); } } else { $file_chooser->destroy; } } ##@ignore sub save_colors_file { my($self, $gui) = @{$_[1]}; my $palette_type = $self->get_selected_palette_type(); my $file_chooser = Gtk2::FileChooserDialog->new ("Save $palette_type file as", undef, 'save', 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok'); my $filename; if ($file_chooser->run eq 'ok') { $filename = $file_chooser->get_filename; $file_chooser->destroy; my $table = $self->get_table_from_treeview(); my $obj = {}; if ($palette_type eq 'Color table') { eval { color_table($obj, $table); save_color_table($obj, $filename); } } elsif ($palette_type eq 'Color bins') { eval { color_bins($obj, $table); save_color_bins($obj, $filename); } } if ($@) { $gui->message("$@"); } } else { $file_chooser->destroy; } } ##@ignore sub edit_color { my($self, $gui) = @{$_[1]}; my $palette_type = $self->get_selected_palette_type(); my $treeview = $self->{colors_dialog}->get_widget('colors_treeview'); my $selection = $treeview->get_selection; my @selected = $selection->get_selected_rows; return unless @selected; my $table = $self->get_table_from_treeview(); my $i = $selected[0]->to_string; my @color; if ($palette_type eq 'Single color') { @color = @{$table->[$i]}; } else { @color = @{$table->[$i]}[1..4]; } my $d = Gtk2::ColorSelectionDialog->new('Choose color for selected entries'); my $s = $d->colorsel; $s->set_has_opacity_control(1); my $c = Gtk2::Gdk::Color->new($color[0]*257,$color[1]*257,$color[2]*257); $s->set_current_color($c); $s->set_current_alpha($color[3]*257); if ($d->run eq 'ok') { $d->destroy; $c = $s->get_current_color; @color = (int($c->red/257),int($c->green/257),int($c->blue/257)); $color[3] = int($s->get_current_alpha()/257); if ($palette_type eq 'Single color') { $self->fill_colors_treeview([[@color]]); } else { for (@selected) { my $i = $_->to_string; @{$table->[$i]}[1..4] = @color; } $self->fill_colors_treeview($table); } } else { $d->destroy; } for (@selected) { $selection->select_path($_); } } ##@ignore sub delete_color { my($self, $gui) = @{$_[1]}; my $treeview = $self->{colors_dialog}->get_widget('colors_treeview'); my $selection = $treeview->get_selection; my @selected = $selection->get_selected_rows if $selection; my $model = $treeview->get_model; my $at; for my $selected (@selected) { $at = $selected->to_string; my $iter = $model->get_iter_from_string($at); $model->remove($iter); #splice @$table,$at,1; } $at--; $at = 0 if $at < 0; $treeview->set_cursor(Gtk2::TreePath->new($at)); } ##@ignore sub add_color { my($self, $gui) = @{$_[1]}; my $treeview = $self->{colors_dialog}->get_widget('colors_treeview'); my $selection = $treeview->get_selection; my @selected = $selection->get_selected_rows if $selection; my $at = $selected[0]->to_string if @selected; my $model = $treeview->get_model; my $palette_type = $self->get_selected_palette_type(); my $table = $self->get_table_from_treeview(); $at = $#$table unless defined $at; my $value; if (@$table) { if ($palette_type eq 'Color table') { if ($self->current_coloring_type eq 'Int') { do { $value = $table->[$at]->[0]+1; $at++; } until $at == @$table or $value != $table->[$at]->[0]; } else { $value = 'change this'; $at++; } } elsif ($palette_type eq 'Color bins') { if ($at == $#$table) { if ($self->current_coloring_type eq 'Int') { $value = $MAX_INT; } else { $value = $MAX_REAL; } $at++; } else { $value = ($table->[$at]->[0] + $table->[$at+1]->[0])/2; $at++; } } } else { if ($self->current_coloring_type eq 'String') { $value = 'change this'; } else { $value = 0; } $at = 0; } my $iter = $model->insert(undef, $at); set_color($model,$iter,$value,255,255,255,255); $treeview->set_cursor(Gtk2::TreePath->new($at)); } ##@ignore sub cell_in_colors_treeview_changed { my($cell, $path, $new_value, $data) = @_; my($self, $column) = @$data; my $table = $self->get_table_from_treeview(); $table->[$path]->[$column] = $new_value; $self->fill_colors_treeview($table); } ##@ignore sub palette_type_changed { my($self, $gui) = @{$_[1]}; my $dialog = $self->{colors_dialog}; my $palette_type = $self->get_selected_palette_type(); fill_color_field_combo($self); my $tv = $dialog->get_widget('colors_treeview'); $dialog->get_widget('color_field_label')->set_sensitive(0); $dialog->get_widget('color_field_combobox')->set_sensitive(0); $dialog->get_widget('color_scale_min_entry')->set_sensitive(0); $dialog->get_widget('color_scale_max_entry')->set_sensitive(0); $dialog->get_widget('min_hue_button')->set_sensitive(0); $dialog->get_widget('max_hue_button')->set_sensitive(0); $dialog->get_widget('hue_range_combobox')->set_sensitive(0); $dialog->get_widget('hue_checkbutton')->set_sensitive(0); $dialog->get_widget('hue_button')->set_sensitive(0); $tv->set_sensitive(0); $dialog->get_widget('get_colors_button')->set_sensitive(0); $dialog->get_widget('open_colors_button')->set_sensitive(0); $dialog->get_widget('save_colors_button')->set_sensitive(0); $dialog->get_widget('edit_color_button')->set_sensitive(0); $dialog->get_widget('delete_color_button')->set_sensitive(0); $dialog->get_widget('add_color_button')->set_sensitive(0); if ($palette_type ne 'Single color') { $dialog->get_widget('color_field_label')->set_sensitive(1); $dialog->get_widget('color_field_combobox')->set_sensitive(1); } if ($palette_type eq 'Grayscale') { $dialog->get_widget('hue_checkbutton')->set_sensitive(1); $dialog->get_widget('hue_button')->set_sensitive(1); } elsif ($palette_type eq 'Grayscale' or $palette_type eq 'Rainbow') { $dialog->get_widget('min_hue_button')->set_sensitive(1); $dialog->get_widget('max_hue_button')->set_sensitive(1); $dialog->get_widget('hue_range_combobox')->set_sensitive(1); } if ($palette_type eq 'Single color') { $dialog->get_widget('color_scale_button')->set_sensitive(0); $dialog->get_widget('color_legend_button')->set_sensitive(0); $dialog->get_widget('edit_color_button')->set_sensitive(1); $tv->set_sensitive(1); } elsif ($palette_type eq 'Grayscale' or $palette_type eq 'Rainbow' or $palette_type =~ 'channel') { $dialog->get_widget('color_scale_button')->set_sensitive(1); $dialog->get_widget('color_legend_button')->set_sensitive(1); $dialog->get_widget('color_scale_min_entry')->set_sensitive(1); $dialog->get_widget('color_scale_max_entry')->set_sensitive(1); $tv->set_sensitive(1); } elsif ($palette_type eq 'Color table') { my $s = 1; # $self->current_coloring_type eq 'Int' ? 1 : 0; this may change! $tv->set_sensitive($s); $dialog->get_widget('color_scale_button')->set_sensitive(0); $dialog->get_widget('color_legend_button')->set_sensitive(0); $dialog->get_widget('get_colors_button')->set_sensitive($s); $dialog->get_widget('open_colors_button')->set_sensitive($s); $dialog->get_widget('save_colors_button')->set_sensitive($s); $dialog->get_widget('edit_color_button')->set_sensitive($s); $dialog->get_widget('delete_color_button')->set_sensitive($s); $dialog->get_widget('add_color_button')->set_sensitive($s); } elsif ($palette_type eq 'Color bins') { $tv->set_sensitive(1); $dialog->get_widget('color_scale_button')->set_sensitive(0); $dialog->get_widget('color_legend_button')->set_sensitive(0); $dialog->get_widget('get_colors_button')->set_sensitive(1); $dialog->get_widget('open_colors_button')->set_sensitive(1); $dialog->get_widget('save_colors_button')->set_sensitive(1); $dialog->get_widget('edit_color_button')->set_sensitive(1); $dialog->get_widget('delete_color_button')->set_sensitive(1); $dialog->get_widget('add_color_button')->set_sensitive(1); } $self->create_colors_treeview(); } ##@ignore sub create_colors_treeview { my($self) = @_; my $palette_type = $self->get_selected_palette_type(); my $tv = $self->{colors_dialog}->get_widget('colors_treeview'); if ($palette_type eq 'Grayscale' or $palette_type eq 'Rainbow') { $self->put_scale_in_treeview($tv,$palette_type); return; } my $select = $tv->get_selection; $select->set_mode('multiple'); my $model; my $table; my $type = $self->current_coloring_type; if ($palette_type eq 'Single color') { $model = Gtk2::TreeStore->new(qw/Gtk2::Gdk::Pixbuf Glib::Int Glib::Int Glib::Int Glib::Int/); $table = [[$self->single_color()]]; } elsif ($palette_type eq 'Color table') { $model = Gtk2::TreeStore->new("Glib::$type","Gtk2::Gdk::Pixbuf","Glib::Int","Glib::Int","Glib::Int","Glib::Int"); $table = $self->color_table(); } elsif ($palette_type eq 'Color bins') { $model = Gtk2::TreeStore->new("Glib::$type","Gtk2::Gdk::Pixbuf","Glib::Int","Glib::Int","Glib::Int","Glib::Int"); $table = $self->color_bins(); } $tv->set_model($model); for ($tv->get_columns) { $tv->remove_column($_); } my $i = 0; my $cell; my $column; if ($palette_type ne 'Single color') { $cell = Gtk2::CellRendererText->new; $cell->set(editable => 1); $cell->signal_connect(edited => \&cell_in_colors_treeview_changed, [$self, $i]); $column = Gtk2::TreeViewColumn->new_with_attributes('value', $cell, text => $i++); $tv->append_column($column); } $cell = Gtk2::CellRendererPixbuf->new; $cell->set_fixed_size($COLOR_CELL_SIZE-2,$COLOR_CELL_SIZE-2); $column = Gtk2::TreeViewColumn->new_with_attributes('color', $cell, pixbuf => $i++); $tv->append_column($column); foreach my $c ('red','green','blue','alpha') { $cell = Gtk2::CellRendererText->new; $cell->set(editable => 1); $cell->signal_connect(edited => \&cell_in_colors_treeview_changed, [$self, $i-1]); $column = Gtk2::TreeViewColumn->new_with_attributes($c, $cell, text => $i++); $tv->append_column($column); } $self->fill_colors_treeview($table); } ##@ignore sub color_field_changed { my($self, $gui) = @{$_[1]}; my $palette_type = $self->get_selected_palette_type(); if (($palette_type eq 'Color bins' or $palette_type eq 'Color table') and $self->{current_coloring_type} ne $self->current_coloring_type) { $self->create_colors_treeview(); } } ##@ignore sub fill_color_scale_fields { my($self, $gui) = @{$_[1]}; my @range; my $field = $self->get_selected_color_field(); eval { @range = $self->value_range($field); }; if ($@) { $gui->message("$@"); return; } $self->{colors_dialog}->get_widget('color_scale_min_entry')->set_text($range[0]); $self->{colors_dialog}->get_widget('color_scale_max_entry')->set_text($range[1]); } ##@ignore sub make_color_legend { my($self, $gui) = @{$_[1]}; my $palette_type = $self->get_selected_palette_type(); my $treeview = $self->{colors_dialog}->get_widget('colors_treeview'); $self->put_scale_in_treeview($treeview, $palette_type); } ##@ignore sub fill_palette_type_combo { my($self, $palette_type) = @_; $palette_type = '' unless defined $palette_type; my $combo = $self->{colors_dialog}->get_widget('palette_type_combobox'); my $model = $combo->get_model; $model->clear; my @palette_types = $self->supported_palette_types(); my $i = 0; my $active = 0; delete $self->{index2palette_type}; delete $self->{palette_type2index}; for (@palette_types) { $model->set ($model->append, 0, $_); $self->{index2palette_type}{$i} = $_; $self->{palette_type2index}{$_} = $i; $active = $i if $_ eq $palette_type; $i++; } $combo->set_active($active); return $#palette_types+1; } ##@ignore sub fill_color_field_combo { my($self, $palette_type) = @_; $palette_type = $self->get_selected_palette_type() unless $palette_type; my $combo = $self->{colors_dialog}->get_widget('color_field_combobox'); my $model = $combo->get_model; $model->clear; delete $self->{index2field}; my $active = 0; my $i = 0; my $schema = $self->schema(); for my $name (sort keys %$schema) { my $type = $schema->{$name}{TypeName}; next unless $type; next if $palette_type eq 'Single color'; next if ($palette_type eq 'Grayscale' or $palette_type eq 'Rainbow' or $palette_type eq 'Color bins') and not($type eq 'Integer' or $type eq 'Real'); next if $palette_type eq 'Color table' and !($type eq 'Integer' or $type eq 'String'); $model->set($model->append, 0, $name); $active = $i if $name eq $self->color_field(); $self->{index2field}{$i} = $name; $i++; } $combo->set_active($active); } ##@ignore sub current_coloring_type { my($self) = @_; my $type = ''; my $field = $self->get_selected_color_field(); return unless defined $field; my $schema = $self->schema(); if ($schema->{$field}{TypeName} eq 'Integer') { $type = 'Int'; } elsif ($schema->{$field}{TypeName} eq 'Real') { $type = 'Double'; } elsif ($schema->{$field}{TypeName} eq 'String') { $type = 'String'; } return $type; } ##@ignore sub get_table_from_treeview { my ($self) = @_; my $palette_type = $self->get_selected_palette_type(); my $treeview = $self->{colors_dialog}->get_widget('colors_treeview'); my $model = $treeview->get_model; return unless $model; my %types = ('Glib::String'=>1, 'Glib::Int'=>1, 'Glib::Double'=>1); my @indices; if ($palette_type eq 'Single color') { @indices = (1,2,3,4); } else { @indices = (0,2,3,4,5); } my $iter = $model->get_iter_first(); my @table; while ($iter) { my @row = $model->get($iter, @indices); push @table, [@row]; $iter = $model->iter_next($iter); } return \@table; } ##@ignore sub fill_colors_treeview { my ($self, $table) = @_; my $palette_type = $self->get_selected_palette_type(); my $treeview = $self->{colors_dialog}->get_widget('colors_treeview'); my $model = $treeview->get_model; return unless $model; $model->clear; return unless $table and @$table; if ($palette_type eq 'Single color') { my $iter = $model->append(undef); set_color($model,$iter,undef,@{$table->[0]}); } elsif ($palette_type eq 'Color table') { if ($self->current_coloring_type eq 'Int') { @$table = sort {$a->[0] <=> $b->[0]} @$table; } for my $color (@$table) { my $iter = $model->append(undef); set_color($model,$iter,@$color); } } elsif ($palette_type eq 'Color bins') { @$table = sort {$a->[0] <=> $b->[0]} @$table; $self->{current_coloring_type} = $self->current_coloring_type; my $int = $self->{current_coloring_type} eq 'Int'; for my $i (0..$#$table) { my $color = $table->[$i]; $color->[0] = $int ? $MAX_INT : $MAX_REAL if $i == $#$table; my $iter = $model->append(undef); set_color($model,$iter,@$color); } } } ##@ignore sub set_color { my($model,$iter,$value,@color) = @_; my @set = ($iter); my $j = 0; push @set, ($j++, $value) if defined $value; my $pb = Gtk2::Gdk::Pixbuf->new('rgb',0,8,$COLOR_CELL_SIZE,$COLOR_CELL_SIZE); $pb->fill($color[0] << 24 | $color[1] << 16 | $color[2] << 8); push @set, ($j++, $pb); for my $k (0..3) { push @set, ($j++, $color[$k]); } $model->set(@set); } ##@ignore sub set_hue_range { my($self, $gui, $dir) = @{$_[1]}; my $dialog = $self->{colors_dialog}; my $hue = $dialog->get_widget($dir.'_hue_label')->get_text(); my @color = hsv2rgb($hue, 1, 1); my $color_chooser = Gtk2::ColorSelectionDialog->new('Choose $dir hue for rainbow palette'); my $s = $color_chooser->colorsel; $s->set_has_opacity_control(0); my $c = Gtk2::Gdk::Color->new($color[0]*257,$color[1]*257,$color[2]*257); $s->set_current_color($c); if ($color_chooser->run eq 'ok') { $c = $s->get_current_color; @color = (int($c->red/257),int($c->green/257),int($c->blue/257)); @color = rgb2hsv(@color); my $hue = $dialog->get_widget($dir.'_hue_label')->set_text(int($color[0])); } $color_chooser->destroy; } ##@ignore sub set_hue { my($self, $gui) = @{$_[1]}; my $dialog = $self->{colors_dialog}; my $hue = $dialog->get_widget('hue_label')->get_text(); $hue = 0 if $hue < 0; my @color = hsv2rgb($hue, 1, 1); my $color_chooser = Gtk2::ColorSelectionDialog->new('Choose hue for grayscale palette'); my $s = $color_chooser->colorsel; $s->set_has_opacity_control(0); my $c = Gtk2::Gdk::Color->new($color[0]*257,$color[1]*257,$color[2]*257); $s->set_current_color($c); if ($color_chooser->run eq 'ok') { $c = $s->get_current_color; @color = (int($c->red/257),int($c->green/257),int($c->blue/257)); @color = rgb2hsv(@color); my $hue = $dialog->get_widget('hue_label')->set_text(int($color[0])); $dialog->get_widget('hue_checkbutton')->set_active(TRUE); } $color_chooser->destroy; } ##@ignore sub put_scale_in_treeview { my($self, $tv, $palette_type) = @_; my $model; $model = Gtk2::TreeStore->new(qw/Gtk2::Gdk::Pixbuf Glib::Double/); $tv->set_model($model); for ($tv->get_columns) { $tv->remove_column($_); } my $i = 0; my $cell = Gtk2::CellRendererPixbuf->new; $cell->set_fixed_size($COLOR_CELL_SIZE-2,$COLOR_CELL_SIZE-2); my $column = Gtk2::TreeViewColumn->new_with_attributes('color', $cell, pixbuf => $i++); $tv->append_column($column); $cell = Gtk2::CellRendererText->new; $cell->set(editable => 0); $column = Gtk2::TreeViewColumn->new_with_attributes('value', $cell, text => $i++); $tv->append_column($column); my $dialog = $self->{colors_dialog}; my $min = $dialog->get_widget('color_scale_min_entry')->get_text(); my $max = $dialog->get_widget('color_scale_max_entry')->get_text(); my ($hue_min) = $dialog->get_widget('min_hue_label')->get_text() =~ /(\d+)/; my ($hue_max) = $dialog->get_widget('max_hue_label')->get_text() =~ /(\d+)/; my $hue_dir = $dialog->get_widget('hue_range_combobox')->get_active == 0 ? 1 : -1; # up is 1, down is -1 if ($hue_dir == 1) { $hue_max += 360 if $hue_max < $hue_min; } else { $hue_max -= 360 if $hue_max > $hue_min; } my $hue = $dialog->get_widget('hue_checkbutton')->get_active() ? $dialog->get_widget('hue_label')->get_text() : -1; return if $min eq '' or $max eq ''; my $delta = ($max-$min)/14; my $x = $max; for my $i (1..15) { my $iter = $model->append(undef); my @set = ($iter); my($h,$s,$v); if ($palette_type eq 'Grayscale') { if ($hue < 0) { $h = 0; $s = 0; } else { $h = $hue; $s = 1; } $v = $delta == 0 ? 0 : ($x - $min)/($max - $min)*1; } else { $h = $delta == 0 ? 0 : int($hue_min + ($x - $min)/($max-$min) * ($hue_max-$hue_min) + 0.5); $h -= 360 if $h > 360; $h += 360 if $h < 0; $s = 1; $v = 1; } my $pb = Gtk2::Gdk::Pixbuf->new('rgb', 0, 8, $COLOR_CELL_SIZE, $COLOR_CELL_SIZE); my @color = hsv2rgb($h, $s, $v); $pb->fill($color[0] << 24 | $color[1] << 16 | $color[2] << 8); my $j = 0; push @set, ($j++, $pb); push @set, ($j++, $x); $model->set(@set); $x -= $delta; } } ##@ignore sub colors_from_dialog { my($self, $gui) = @_; my $palette_type = $self->{colors_dialog}->get_widget('palette_type_combobox')->get_active(); $palette_type = $self->{index2palette_type}{$palette_type}; my $dialog = $gui->get_dialog('colors_from_dialog'); $dialog->get_widget('colors_from_dialog')->set_title("Get $palette_type from"); my $tv = $dialog->get_widget('colors_from_treeview'); my $model = Gtk2::TreeStore->new(qw/Glib::String/); $tv->set_model($model); for ($tv->get_columns) { $tv->remove_column($_); } my $i = 0; foreach my $column ('Layer') { my $cell = Gtk2::CellRendererText->new; my $col = Gtk2::TreeViewColumn->new_with_attributes($column, $cell, text => $i++); $tv->append_column($col); } $model->clear; my @names; for my $layer (@{$gui->{overlay}->{layers}}) { next if $layer->name() eq $self->name(); push @names, $layer->name(); $model->set ($model->append(undef), 0, $layer->name()); } #$dialog->move(@{$self->{colors_from_position}}) if $self->{colors_from_position}; $dialog->get_widget('colors_from_dialog')->show_all; $dialog->get_widget('colors_from_dialog')->present; my $response = $dialog->get_widget('colors_from_dialog')->run; my $table; if ($response eq 'ok') { my @sel = $tv->get_selection->get_selected_rows; if (@sel) { my $i = $sel[0]->to_string if @sel; my $from_layer = $gui->{overlay}->get_layer_by_name($names[$i]); if ($palette_type eq 'Color table') { $table = $from_layer->color_table(); } elsif ($palette_type eq 'Color bins') { $table = $from_layer->color_bins(); } } } $dialog->get_widget('colors_from_dialog')->destroy; return $table; } # labels dialog sub open_labels_dialog { my($self, $gui) = @_; my $dialog = $self->bootstrap_dialog($gui, 'labels_dialog', "Labels for ".$self->name, { labels_dialog => [delete_event => \&cancel_labels, [$self, $gui]], labels_font_button => [clicked => \&labels_font, [$self, $gui, 0]], labels_color_button => [clicked => \&labels_color, [$self, $gui, 0]], apply_labels_button => [clicked => \&apply_labels, [$self, $gui, 0]], cancel_labels_button => [clicked => \&cancel_labels, [$self, $gui]], ok_labels_button => [clicked => \&apply_labels, [$self, $gui, 1]], }); # backup my $labeling = $self->{backup}->{labeling} = $self->labeling; # set up controllers my $schema = $self->schema; my $combo = $dialog->get_widget('labels_field_combobox'); my $model = $combo->get_model; $model->clear; my $i = 0; my $active = 0; $model->set ($model->append, 0, 'No Labels'); $active = $i if $labeling->{field} eq 'No Labels'; $i++; for my $fname (sort keys %$schema) { $model->set ($model->append, 0, $fname); $active = $i if $labeling->{field} eq $fname; $i++; } $combo->set_active($active); $combo = $dialog->get_widget('labels_placement_combobox'); $model = $combo->get_model; $model->clear; $i = 0; $active = 0; my $h = \%Gtk2::Ex::Geo::Layer::LABEL_PLACEMENT; for my $e (sort {$h->{$a} <=> $h->{$b}} keys %$h) { $model->set ($model->append, 0, $e); $active = $i if $labeling->{placement} eq $e; $i++; } $combo->set_active($active); $dialog->get_widget('labels_font_label')->set_text($labeling->{font}); $dialog->get_widget('labels_color_label')->set_text("@{$labeling->{color}}"); $dialog->get_widget('labels_min_size_entry')->set_text($labeling->{min_size}); $dialog->get_widget('labels_dialog')->show_all; } ##@ignore sub apply_labels { my($self, $gui, $close) = @{$_[1]}; my $dialog = $self->{labels_dialog}; my $labeling = {}; my $combo = $dialog->get_widget('labels_field_combobox'); my $model = $combo->get_model; my $iter = $model->get_iter_from_string($combo->get_active()); $labeling->{field} = $model->get_value($iter); $combo = $dialog->get_widget('labels_placement_combobox'); $model = $combo->get_model; $iter = $model->get_iter_from_string($combo->get_active()); $labeling->{placement} = $model->get_value($iter); $labeling->{min_size} = $dialog->get_widget('labels_min_size_entry')->get_text; $labeling->{font} = $dialog->get_widget('labels_font_label')->get_text; @{$labeling->{color}} = split(/ /, $dialog->get_widget('labels_color_label')->get_text); $labeling->{min_size} = $dialog->get_widget('labels_min_size_entry')->get_text; $self->labeling($labeling); $self->{labels_dialog_position} = [$dialog->get_widget('labels_dialog')->get_position]; $dialog->get_widget('labels_dialog')->hide() if $close; $gui->set_layer($self); $gui->{overlay}->render; } ##@ignore sub cancel_labels { my($self, $gui); for (@_) { next unless ref eq 'ARRAY'; ($self, $gui) = @{$_}; } $self->labeling($self->{labeling_backup}); my $dialog = $self->{labels_dialog}->get_widget('labels_dialog'); $self->{labels_dialog_position} = [$dialog->get_position]; $dialog->hide(); $gui->set_layer($self); $gui->{overlay}->render; 1; } ##@ignore sub labels_font { my($self, $gui) = @{$_[1]}; my $font_chooser = Gtk2::FontSelectionDialog->new ("Select font for the labels"); my $font_name = $self->{labels_dialog}->get_widget('labels_font_label')->get_text; $font_chooser->set_font_name($font_name); if ($font_chooser->run eq 'ok') { $font_name = $font_chooser->get_font_name; $self->{labels_dialog}->get_widget('labels_font_label')->set_text($font_name); } $font_chooser->destroy; } ##@ignore sub labels_color { my($self, $gui) = @{$_[1]}; my @color = split(/ /, $self->{labels_dialog}->get_widget('labels_color_label')->get_text); my $color_chooser = Gtk2::ColorSelectionDialog->new('Choose color for the label font'); my $s = $color_chooser->colorsel; $s->set_has_opacity_control(1); my $c = Gtk2::Gdk::Color->new($color[0]*257,$color[1]*257,$color[2]*257); $s->set_current_color($c); $s->set_current_alpha($color[3]*257); if ($color_chooser->run eq 'ok') { $c = $s->get_current_color; @color = (int($c->red/257),int($c->green/257),int($c->blue/257)); $color[3] = int($s->get_current_alpha()/257); $self->{labels_dialog}->get_widget('labels_color_label')->set_text("@color"); } $color_chooser->destroy; } ## @ignore sub MIN { $_[0] > $_[1] ? $_[1] : $_[0]; } ## @ignore sub MAX { $_[0] > $_[1] ? $_[0] : $_[1]; } 1;