## @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 <a href="http://map.hut.fi/doc/Geoinformatica/html/">
documentation of Gtk2::Ex::Geo</a> 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
# - <I>key</I> 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;
