diff --git a/bin/ui/wndOverlays.ui b/bin/ui/wndOverlays.ui index c40e0363e..d0b10fb42 100644 --- a/bin/ui/wndOverlays.ui +++ b/bin/ui/wndOverlays.ui @@ -90,7 +90,7 @@ - True + False True 2 @@ -121,13 +121,13 @@ - gtk-delete + Remove True True True True True - Delete selected file from the list + Remove selected entry from the list True diff --git a/lib/Biodiverse/GUI/GUIManager.pm b/lib/Biodiverse/GUI/GUIManager.pm index 872fa67ad..e393ec6b5 100644 --- a/lib/Biodiverse/GUI/GUIManager.pm +++ b/lib/Biodiverse/GUI/GUIManager.pm @@ -61,6 +61,7 @@ BEGIN { gladexml => undef, # Main window widgets tabs => [], # Stores refs to Tabs objects. In order of page index. progress_bars => undef, + overlay_components => undef, test_val => '' }; bless $singleton, 'Biodiverse::GUI::GUIManager'; @@ -331,6 +332,16 @@ sub show_progress { } } +sub get_overlay_components { + my ($self) = @_; + return $self->{overlay_components}; +} + +sub set_overlay_components { + my ($self, $components) = @_; + $self->{overlay_components} = $components; +} + ########################################################## # Initialisation ########################################################## diff --git a/lib/Biodiverse/GUI/Grid.pm b/lib/Biodiverse/GUI/Grid.pm index aabe68071..f15cf7d8f 100644 --- a/lib/Biodiverse/GUI/Grid.pm +++ b/lib/Biodiverse/GUI/Grid.pm @@ -616,9 +616,8 @@ sub get_base_struct { # Draws a polygonal shapefile sub set_overlay { - my $self = shift; - my $shapefile = shift; - my $colour = shift || OVERLAY_COLOUR; + my ($self, %args) = @_; + # Delete any existing if ($self->{shapefile_group}) { @@ -626,9 +625,12 @@ sub set_overlay { delete $self->{shapefile_group}; } - if ($shapefile) { - my @args = @{ $self->{dataset_info} }; - $self->load_shapefile(@args, $shapefile, $colour); + if (defined $args{shapefile}) { + $self->load_shapefile( + colour => OVERLAY_COLOUR, # default + dataset_info => $self->{dataset_info}, + %args, + ); } return; @@ -649,7 +651,13 @@ EOL } sub load_shapefile { - my ($self, $min_x, $min_y, $max_x, $max_y, $cell_x, $cell_y, $shapefile, $colour) = @_; + my ($self, %args) = @_; + + my $info = $args{info} // $self->{dataset_info}; + my ($min_x, $min_y, $max_x, $max_y, $cell_x, $cell_y) = @$info; + my $shapefile = $args{shapefile}; + my $colour = $args{colour}; + my $plot_as_poly = $args{plot_as_poly}; my @rect = ( $min_x - $cell_x, @@ -691,7 +699,16 @@ sub load_shapefile { y => 0, ); - $shapefile_group->raise_to_top(); + my $is_polygon = $shapefile->shape_type_text =~ m/polygon/i; + + my $canvas_shape_type = 'Gnome2::Canvas::Line'; + if ($is_polygon && $plot_as_poly) { + $canvas_shape_type = 'Gnome2::Canvas::Polygon'; + $shapefile_group->lower_to_bottom(); + } + else { + $shapefile_group->raise_to_top(); + } $self->{shapefile_group} = $shapefile_group; # Add all shapes @@ -735,7 +752,7 @@ sub load_shapefile { if (@plot_points > 2) { # must have more than one point (two coords) my $poly = Gnome2::Canvas::Item->new ( $shapefile_group, - 'Gnome2::Canvas::Line', + $canvas_shape_type, points => \@plot_points, fill_color_gdk => $colour, ); diff --git a/lib/Biodiverse/GUI/Overlays.pm b/lib/Biodiverse/GUI/Overlays.pm index 1046d2c5a..4f592405d 100644 --- a/lib/Biodiverse/GUI/Overlays.pm +++ b/lib/Biodiverse/GUI/Overlays.pm @@ -1,10 +1,12 @@ package Biodiverse::GUI::Overlays; +use 5.010; use strict; use warnings; use Gtk2; #use Data::Dumper; use Geo::ShapeFile; +use Ref::Util qw/is_hashref/; our $VERSION = '4.99_002'; @@ -14,54 +16,114 @@ use Biodiverse::GUI::Project; my $default_colour = Gtk2::Gdk::Color->parse('#001169'); my $last_selected_colour = $default_colour; +use constant COL_FNAME => 0; +use constant COL_PLOT_POLY => 1; +use constant COL_PLOT_COLOUR => 2; +use constant COL_PLOT_COLOUR_BK => 3; + sub show_dialog { my $grid = shift; # Create dialog my $gui = Biodiverse::GUI::GUIManager->instance; + my $overlay_components = $gui->get_overlay_components; + my $project = $gui->get_project; + + if ($overlay_components) { + my $dlg = $overlay_components->{dialog}; + my $colour_button = $overlay_components->{colour_button}; + $colour_button->set_color($last_selected_colour); + + set_button_actions ( + project => $project, + grid => $grid, + %$overlay_components, + ); + + $dlg->show_all; + return; + } + my $dlgxml = Gtk2::Builder->new(); $dlgxml->add_from_file($gui->get_gtk_ui_file('wndOverlays.ui')); my $dlg = $dlgxml->get_object('wndOverlays'); my $colour_button = $dlgxml->get_object('colorbutton_overlays'); - $dlg->set_transient_for( $gui->get_object('wndMain') ); + $dlg->set_transient_for($gui->get_object('wndMain')); $colour_button->set_color($last_selected_colour); - my $project = $gui->get_project; my $model = make_overlay_model($project); my $list = init_overlay_list($dlgxml, $model); + my %buttons = map {$_ => $dlgxml->get_object($_)} + (qw /btnAdd btnDelete btnClear btnSet btnOverlayCancel btn_overlay_set_default_colour/); + my %components = ( + dialog => $dlg, + colour_button => $colour_button, + list => $list, + buttons => \%buttons, + ); + + my $signals = set_button_actions ( + %components, + project => $project, + grid => $grid, + ); + + # store some but not all components we set actions for + $gui->set_overlay_components ({ + %components, + signals => $signals, + }); + + $dlg->set_modal(1); + $dlg->show_all(); + + return; +} + +sub set_button_actions { + my %args = @_; + my ($list, $project, $grid, $dlg, $colour_button) + = @args{qw /list project grid dialog colour_button/}; + + my $buttons = $args{buttons}; + my $signals = $args{signals} // {}; # Connect buttons - $dlgxml->get_object('btnAdd')->signal_connect( + + # these are always the same + $signals->{btnAdd} //= $buttons->{btnAdd}->signal_connect( clicked => \&on_add, [$list, $project], ); - $dlgxml->get_object('btnDelete')->signal_connect( + $signals->{btnDelete} //= $buttons->{btnDelete}->signal_connect( clicked => \&on_delete, [$list, $project], ); - $dlgxml->get_object('btnClear')->signal_connect( - clicked => \&on_clear, - [$list, $project, $grid, $dlg], - ); - $dlgxml->get_object('btnSet')->signal_connect( - clicked => \&on_set, - [$list, $project, $grid, $dlg, $colour_button], - ); - $dlgxml->get_object('btnOverlayCancel')->signal_connect( + $signals->{btnOverlayCancel} //= $buttons->{btnOverlayCancel}->signal_connect( clicked => \&on_cancel, $dlg, ); - $dlgxml->get_object('btn_overlay_set_default_colour')->signal_connect( + $signals->{btn_overlay_set_default_colour} + //= $buttons->{btn_overlay_set_default_colour}->signal_connect( clicked => \&on_set_default_colour, $colour_button, ); - - $dlg->set_modal(1); - $dlg->show_all(); - - return; + # these vary by grid so need to be disconnected first or we mess up other plots + foreach my $btn (qw/btnClear btnSet/) { + my $id = $signals->{$btn} // next; + $buttons->{$btn}->signal_handler_disconnect($id); + } + $signals->{btnClear} = $buttons->{btnClear}->signal_connect( + clicked => \&on_clear, + [$list, $project, $grid, $dlg], + ); + $signals->{btnSet} = $buttons->{btnSet}->signal_connect( + clicked => \&on_set, + [$list, $project, $grid, $dlg, $colour_button], + ); + return $signals; } sub init_overlay_list { @@ -70,42 +132,135 @@ sub init_overlay_list { my $tree = $dlgxml->get_object('treeOverlays'); my $col_name = Gtk2::TreeViewColumn->new(); - my $name_renderer = Gtk2::CellRendererText->new(); - $col_name->set_title('Filename'); + my $name_renderer = Gtk2::CellRendererText->new (); + #$name_renderer->signal_connect('toggled' => \&_update_colour_for_selection, $model); + $col_name->set_title('File name'); $col_name->pack_start($name_renderer, 1); - $col_name->add_attribute($name_renderer, text => 0); + $col_name->add_attribute($name_renderer, text => COL_FNAME); $tree->insert_column($col_name, -1); - # fiddling around with colour selection - #my $colColour = Gtk2::TreeViewColumn->new(); - #my $colour_button = Gtk2::CellRendererPixbuf->new(); - #$colColour->set_title('Colour'); - #$colColour->pack_start($colour_button, 1); - #$colColour->add_attribute($colour_button, text => 0); - #$tree->insert_column($colColour, -1); - - $tree->set_headers_visible(0); + my $col_plot_poly = Gtk2::TreeViewColumn->new(); + $col_plot_poly->set_title('Plot as polygon'); + my $poly_renderer = Gtk2::CellRendererToggle->new(); + $col_plot_poly->pack_start($poly_renderer, 0); + $poly_renderer->signal_connect('toggled' => \&_plot_poly_toggled, $model); + $col_plot_poly->set_attributes($poly_renderer, + 'active' => 1, + ); + $tree->insert_column($col_plot_poly, -1); + + # my $col_colour = Gtk2::TreeViewColumn->new(); + # my $colour_renderer_toggle = Gtk2::CellRendererToggle->new(); + # my $colour_renderer_text = Gtk2::CellRendererText->new(); + # $col_colour->set_title('Colour'); + # $col_colour->pack_start($colour_renderer_toggle, 0); + # $col_colour->pack_start($colour_renderer_text, 0); + # $col_colour->add_attribute($colour_renderer_toggle, active => COL_PLOT_COLOUR); + # $col_colour->set_attributes($colour_renderer_toggle, active => 1); + # $col_colour->add_attribute($colour_renderer_text, + # 'cell-background' => COL_PLOT_COLOUR_BK, + # ); + # $col_colour->set_attributes($colour_renderer_text, + # 'cell-background' => COL_PLOT_COLOUR_BK, + # ); + # $colour_renderer_toggle->signal_connect( + # 'toggled' => \&_update_colour_for_selection, $model + # ); + # $tree->insert_column($col_colour, -1); + + $tree->set_headers_visible(1); $tree->set_model($model); return $tree; } +sub _plot_poly_toggled { + my ($cell, $path_str, $model) = @_; + + my $path = Gtk2::TreePath->new_from_string ($path_str); + + # get toggled iter + my $iter = $model->get_iter ($path); + my ($bool) = $model->get ($iter, COL_PLOT_POLY); + + # toggle the value + $model->set ($iter, COL_PLOT_POLY, !$bool); +} + +sub _update_colour_for_selection { + my ($cell, $path_str, $model) = @_; + + my $path = Gtk2::TreePath->new_from_string ($path_str); + + # get toggled iter + my $iter = $model->get_iter ($path); + my ($bool) = $model->get ($iter, COL_PLOT_COLOUR); + + my $gui = Biodiverse::GUI::GUIManager->instance; + my $overlay_components = $gui->get_overlay_components; + my $colour_button = $overlay_components->{colour_button}; + + my $colour = $model->get ($iter, COL_PLOT_COLOUR_BK); + if (!defined $colour or $colour eq 'undef') { + $colour = $colour_button->get_colour; #$self->get_last_colour; + } + + if (!Scalar::Util::blessed $colour) { + $colour = Gtk2::Gdk::Color->parse($colour); + } + $colour_button->set_color($colour); + + $colour_button->clicked; + $colour = $colour_button->get_color; + + say STDERR $bool, ' ', $colour->to_string; + + # toggle the value + $model->set ($iter, + COL_PLOT_COLOUR, !$bool, + ); + $model->set ($iter, + COL_PLOT_COLOUR_BK, $colour->to_string, + # 'cell-background' => $colour->to_string, + ); + say '--- ' . $model->get ($iter, COL_PLOT_COLOUR); + say '--- ' . $model->get ($iter, COL_PLOT_COLOUR_BK); +} + # Make the object tree that appears on the left sub make_overlay_model { + my $project = shift; + my $model = Gtk2::ListStore->new( 'Glib::String', - #'Glib::Boolean', # fiddling around with colour selection + 'Glib::Boolean', + # 'Glib::Boolean', # next two are colour stuff + # 'Glib::String', ); - my $project = shift; my $overlays = $project->get_overlay_list(); - foreach my $name (@{$overlays}) { + foreach my $entry (@{$overlays}) { my $iter = $model->append; - $model->set($iter, 0, $name); + if (!is_hashref $entry) { # previous versions did not store these + $entry = { + name => $entry, + plot_as_poly => 0, + # has_colour => undef, + # colour => undef, + }; + } + # use DDP; + # p $entry; + $model->set( + $iter, + COL_FNAME, $entry->{name}, + COL_PLOT_POLY, $entry->{plot_as_poly}, + # COL_PLOT_COLOUR, !!$entry->{colour}, + # COL_PLOT_COLOUR_BK, ($entry->{colour} // 'white'), + ); } - return $model; } @@ -120,9 +275,13 @@ sub get_selection { return if not $path; my $iter = $model->get_iter($path); - my $name = $model->get($iter, 0); + my $name = $model->get($iter, COL_FNAME); + my $plot_as_poly = $model->get($iter, COL_PLOT_POLY); + my $array_iter = $path->to_string; # only works for a simple tree - return wantarray ? ($name, $iter) : $name; + return wantarray + ? (iter => $iter, filename => $name, plot_as_poly => $plot_as_poly, array_iter => $array_iter) + : $name; } @@ -164,11 +323,11 @@ sub on_add { if (!_shp_type_is_point($filename)) { my $iter = $list->get_model->append; - $list->get_model->set($iter, 0, $filename); + $list->get_model->set($iter, COL_FNAME, $filename, COL_PLOT_POLY, 0); my $sel = $list->get_selection; $sel->select_iter($iter); - $project->add_overlay($filename); + $project->add_overlay({name => $filename}); } else { # warn about points - one day we will fix this my $error = "Selected shapefile is a point type."; @@ -193,14 +352,25 @@ sub _shp_type_is_point { return $type =~/point/i; } +sub _shp_type_is_polygon { + my $name = shift; + + my $shpfile = Geo::ShapeFile->new ($name); + my $type = $shpfile->shape_type_text; + + return $type =~/polygon/i; +} + sub on_delete { my $button = shift; my $args = shift; my ($list, $project) = @$args; - my ($filename, $iter) = get_selection($list); - return if not $filename; - $project->delete_overlay($filename); + # my ($iter, $filename, undef, $array_iter) = get_selection($list); + my %results = get_selection($list); + my ($iter, $filename, $array_iter) = @results{qw/iter filename array_iter/}; + return if not defined $filename; + $project->delete_overlay($filename, $array_iter); $list->get_model->remove($iter); return; @@ -212,8 +382,8 @@ sub on_clear { my $args = shift; my ($list, $project, $grid, $dlg) = @$args; - $grid->set_overlay(undef); - $dlg->destroy(); + $grid->set_overlay(); + $dlg->hide(); return; } @@ -223,16 +393,23 @@ sub on_set { my $args = shift; my ($list, $project, $grid, $dlg, $colour_button) = @$args; - my $filename = get_selection($list); + # my ($iter, $filename, $plot_as_poly, $array_iter) = get_selection($list); + my %results = get_selection($list); + my ($iter, $filename, $plot_as_poly, $array_iter) + = @results{qw /iter filename plot_as_poly array_iter/}; my $colour = $colour_button->get_color; - $dlg->destroy; + $dlg->hide; return if not $filename; print "[Overlay] Setting overlay to $filename\n"; - $grid->set_overlay( $project->get_overlay($filename), $colour ); + $grid->set_overlay( + shapefile => $project->get_overlay_shape_object($filename), + colour => $colour, + plot_as_poly => $plot_as_poly, + ); #$dlg->destroy(); $last_selected_colour = $colour; @@ -244,7 +421,7 @@ sub on_cancel { my $button = shift; my $dlg = shift; - $dlg->destroy; + $dlg->hide; return; } diff --git a/lib/Biodiverse/GUI/Project.pm b/lib/Biodiverse/GUI/Project.pm index 898ac4607..124563862 100644 --- a/lib/Biodiverse/GUI/Project.pm +++ b/lib/Biodiverse/GUI/Project.pm @@ -1477,7 +1477,7 @@ sub get_overlay_list { return $self->{OVERLAYS}; } -sub get_overlay { +sub get_overlay_shape_object { my $self = shift; my $name = shift; @@ -1499,27 +1499,30 @@ sub get_overlay { sub delete_overlay { my $self = shift; my $name = shift; + my $array_iter = shift; - # Remove from list - foreach my $i ( 0 .. $#{ $self->{OVERLAYS} } ) { - if ( $self->{OVERLAYS}[$i] eq $name ) { - splice( @{ $self->{OVERLAYS} }, $i, 1 ); - last; - } - } + my $overlays = $self->{OVERLAYS}; - # remove from hash - delete $self->{overlay_objects}{$name}; + # Remove from list unless not found or possible + $array_iter //= List::Util::first {$_ eq $name} @$overlays; + return if $array_iter < 0 || $array_iter > $#$overlays; + + splice( @$overlays, $array_iter, 1 ); + + # remove from hash if no longer needed + if (!grep {$_ eq $name} @$overlays) { + delete $self->{overlay_objects}{$name}; + } return; } sub add_overlay { my $self = shift; - my $name = shift; + my $entry = shift; - $self->{overlay_objects}{$name} = undef; - push @{ $self->{OVERLAYS} }, $name; + $self->{overlay_objects}{$entry->{name}} = undef; + push @{ $self->{OVERLAYS} }, $entry; return; } @@ -1531,13 +1534,14 @@ sub init_overlay_hash { my $existing_overlays = []; my @missing_overlays; - foreach my $name ( @{ $self->{OVERLAYS} } ) { + foreach my $entry ( @{ $self->{OVERLAYS} } ) { + my $name = is_hashref $entry ? $entry->{name} : $entry; if ( Biodiverse::Common->file_is_readable (file_name => $name) ) { # Set hash entry to undef - will load on demand $self->{overlay_objects}{$name} = undef; - push @$existing_overlays, $name; + push @$existing_overlays, $entry; } else { print "[Project] Missing overlay: $name\n"; @@ -1551,7 +1555,7 @@ sub init_overlay_hash { # Tell user if any missing if ( scalar @missing_overlays > 0 ) { my $text = -"The following overlays are missing and have been deleted from the project:\n"; +"The following overlays are missing and have been removed from the project:\n"; foreach my $name (@missing_overlays) { $text .= " $name\n"; }