From 1d364d7823264fcce95757cbd54408edd12eb033 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 24 Jul 2012 15:30:50 +0200 Subject: [PATCH] Refactor OptionsGroup to decouple it from the config repository, add ConfigOptionsGroup --- lib/Slic3r.pm | 1 - lib/Slic3r/Config.pm | 8 - lib/Slic3r/GUI/OptionsGroup.pm | 310 ++++++++++++++++++++++++--------- lib/Slic3r/GUI/SkeinPanel.pm | 16 +- lib/Slic3r/GUI/Tab.pm | 67 +++++-- slic3r.pl | 7 +- 6 files changed, 290 insertions(+), 119 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 95f89a48a..957e30078 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -78,7 +78,6 @@ our $bed_temperature = 0; our $first_layer_bed_temperature = $bed_temperature; # extruders -our $extruders_count = 1; our $extruders = []; our $nozzle_diameter = [0.5]; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index c4eb4a328..85cbf1dba 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -96,13 +96,6 @@ our $Options = { cli => 'g0!', type => 'bool', }, - 'extruders_count' => { - label => 'Extruders', - tooltip => 'Number of extruders of the printer.', - gui_only=> 1, - type => 'i', - min => 1, - }, 'gcode_comments' => { label => 'Verbose G-code', tooltip => 'Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down.', @@ -912,7 +905,6 @@ sub validate { qw(nozzle_diameter filament_diameter extrusion_multiplier temperature first_layer_temperature) ); } - $Slic3r::extruders_count = $#$Slic3r::extruders + 1; # calculate flow $Slic3r::flow = $Slic3r::extruders->[0]->make_flow(width => $Slic3r::extrusion_width); diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 31627aef5..ac5ace8c4 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -1,46 +1,79 @@ package Slic3r::GUI::OptionsGroup; -use strict; -use warnings; +use Moo; use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl); use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT); -use base 'Wx::StaticBoxSizer'; +=head1 NAME -# not very elegant, but this solution is temporary waiting for a better GUI -our %reload_callbacks = (); # key => $cb +Slic3r::GUI::OptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containing one or more options -sub new { - my $class = shift; - my ($parent, %p) = @_; +=head1 SYNOPSIS + + my $optgroup = Slic3r::GUI::OptionsGroup->new( + parent => $self->parent, + title => 'Layers', + options => [ + { + opt_key => 'layer_height', # mandatory + type => 'f', # mandatory + label => 'Layer height', + tooltip => 'This setting controls the height (and thus the total number) of the slices/layers.', + sidetext => 'mm', + width => 200, + full_width => 0, + height => 50, + min => 0, + max => 100, + labels => [], + values => [], + default => 0.4, # mandatory + on_change => sub { print "new value is $_[0]\n" }, + }, + ], + on_change => sub { print "new value for $_[0] is $_[1]\n" }, + no_labels => 0, + label_width => 180, + ); + $sizer->Add($optgroup->sizer); + +=cut + +has 'parent' => (is => 'ro', required => 1); +has 'title' => (is => 'ro', required => 1); +has 'options' => (is => 'ro', required => 1, trigger => 1); +has 'on_change' => (is => 'ro', default => sub { sub {} }); +has 'no_labels' => (is => 'ro', default => sub { 0 }); +has 'label_width' => (is => 'ro', default => sub { 180 }); + +has 'sizer' => (is => 'rw'); +has '_triggers' => (is => 'ro', default => sub { {} }); +has '_setters' => (is => 'ro', default => sub { {} }); + +sub _trigger_options {} + +sub BUILD { + my $self = shift; - my $box = Wx::StaticBox->new($parent, -1, $p{title}); - my $self = $class->SUPER::new($box, wxVERTICAL); + { + my $box = Wx::StaticBox->new($self->parent, -1, $self->title); + $self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL)); + } - my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$p{options}}), 2, ($p{no_labels} ? 1 : 2), 0); + my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$self->options}), 2, ($self->no_labels ? 1 : 2), 0); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - $grid_sizer->AddGrowableCol($p{no_labels} ? 0 : 1); + $grid_sizer->AddGrowableCol($self->no_labels ? 0 : 1); my $sidetext_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - my $onChange = $p{on_change} || sub {}; - my $make_cb = sub { - my $cb = shift; - return sub { - $cb->(@_) if !$parent->{disabled}; - }; - }; - - foreach my $original_opt_key (@{$p{options}}) { - my $index; - my $opt_key = $original_opt_key; # leave original one untouched - $opt_key =~ s/#(\d+)$// and $index = $1; + foreach my $opt (@{$self->options}) { + my $opt_key = $opt->{opt_key}; + $self->_triggers->{$opt_key} = $opt->{on_change} || sub {}; - my $opt = $Slic3r::Config::Options->{$opt_key}; my $label; - if (!$p{no_labels}) { - $label = Wx::StaticText->new($parent, -1, "$opt->{label}:", wxDefaultPosition, [$p{label_width} || 180, -1]); - $label->Wrap($p{label_width} || 180) ; # needed to avoid Linux/GTK bug + if (!$self->no_labels) { + $label = Wx::StaticText->new($self->parent, -1, "$opt->{label}:", wxDefaultPosition, [$self->label_width, -1]); + $label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug $grid_sizer->Add($label); } @@ -50,78 +83,48 @@ sub new { $style = wxTE_MULTILINE if $opt->{multiline}; my $size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1); - # if it's an array type but no index was specified, use the serialized version - my ($get_m, $set_m) = $opt->{type} =~ /\@$/ && !defined $index - ? qw(serialize deserialize) - : qw(get_raw set); - - my $get = sub { - my $val = Slic3r::Config->$get_m($opt_key); - if (defined $index) { - $val = $val->[$index]; #/ - } - return $val; - }; $field = $opt->{type} eq 'i' - ? Wx::SpinCtrl->new($parent, -1, $get->(), wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 100, $get->()) - : Wx::TextCtrl->new($parent, -1, $get->(), wxDefaultPosition, $size, $style); - $reload_callbacks{$opt_key} = $make_cb->(sub { $field->SetValue($get->()) }); + ? Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 100, $opt->{default}) + : Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style); + $self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) }; - my $set = sub { - my $val = $field->GetValue; - if (defined $index) { - Slic3r::Config->$get_m($opt_key)->[$index] = $val; - } else { - Slic3r::Config->$set_m($opt_key, $val); - } - $onChange->($opt_key); - }; + my $on_change = sub { $self->_on_change($opt_key, $field->GetValue) }; $opt->{type} eq 'i' - ? EVT_SPINCTRL($parent, $field, $set) - : EVT_TEXT($parent, $field, $set); + ? EVT_SPINCTRL ($self->parent, $field, $on_change) + : EVT_TEXT ($self->parent, $field, $on_change); } elsif ($opt->{type} eq 'bool') { - $field = Wx::CheckBox->new($parent, -1, ""); - $field->SetValue(Slic3r::Config->get_raw($opt_key)); - EVT_CHECKBOX($parent, $field, $make_cb->(sub { Slic3r::Config->set($opt_key, $field->GetValue); $onChange->($opt_key) })); - $reload_callbacks{$opt_key} = $make_cb->(sub { $field->SetValue(Slic3r::Config->get_raw($opt_key)) }); + $field = Wx::CheckBox->new($self->parent, -1, ""); + $field->SetValue($opt->{default}); + EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($opt_key, $field->GetValue); }); + $self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) }; } elsif ($opt->{type} eq 'point') { $field = Wx::BoxSizer->new(wxHORIZONTAL); my $field_size = Wx::Size->new(40, -1); - my $value = Slic3r::Config->get_raw($opt_key); my @items = ( - Wx::StaticText->new($parent, -1, "x:"), - my $x_field = Wx::TextCtrl->new($parent, -1, $value->[0], wxDefaultPosition, $field_size), - Wx::StaticText->new($parent, -1, " y:"), - my $y_field = Wx::TextCtrl->new($parent, -1, $value->[1], wxDefaultPosition, $field_size), + Wx::StaticText->new($self->parent, -1, "x:"), + my $x_field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}->[0], wxDefaultPosition, $field_size), + Wx::StaticText->new($self->parent, -1, " y:"), + my $y_field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}->[1], wxDefaultPosition, $field_size), ); $field->Add($_) for @items; if ($opt->{tooltip}) { $_->SetToolTipString($opt->{tooltip}) for @items; } - my $set_value = sub { - my ($i, $value) = @_; - my $val = Slic3r::Config->get_raw($opt_key); - $val->[$i] = $value; - Slic3r::Config->set($opt_key, $val); + EVT_TEXT($self->parent, $_, sub { $self->_on_change($opt_key, [ $x_field->GetValue, $y_field->GetValue ]) }) + for $x_field, $y_field; + $self->_setters->{$opt_key} = sub { + $x_field->SetValue($_[0][0]); + $y_field->SetValue($_[0][1]); }; - EVT_TEXT($parent, $x_field, $make_cb->(sub { $set_value->(0, $x_field->GetValue); $onChange->($opt_key) })); - EVT_TEXT($parent, $y_field, $make_cb->(sub { $set_value->(1, $y_field->GetValue); $onChange->($opt_key) })); - $reload_callbacks{$opt_key} = $make_cb->(sub { - my $value = Slic3r::Config->get_raw($opt_key); - $x_field->SetValue($value->[0]); - $y_field->SetValue($value->[1]); - }); } elsif ($opt->{type} eq 'select') { - $field = Wx::ComboBox->new($parent, -1, "", wxDefaultPosition, wxDefaultSize, $opt->{labels} || $opt->{values}, wxCB_READONLY); - EVT_COMBOBOX($parent, $field, $make_cb->(sub { - Slic3r::Config->set($opt_key, $opt->{values}[$field->GetSelection]); - $onChange->($opt_key); - })); - $reload_callbacks{$opt_key} = $make_cb->(sub { - my $value = Slic3r::Config->get_raw($opt_key); - $field->SetSelection(grep $opt->{values}[$_] eq $value, 0..$#{$opt->{values}}); + $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, wxDefaultSize, $opt->{labels} || $opt->{values}, wxCB_READONLY); + EVT_COMBOBOX($self->parent, $field, sub { + $self->_on_change($opt_key, $opt->{values}[$field->GetSelection]); }); - $reload_callbacks{$opt_key}->(); + $self->_setters->{$opt_key} = sub { + $field->SetSelection(grep $opt->{values}[$_] eq $_[0], 0..$#{$opt->{values}}); + }; + $self->_setters->{$opt_key}->($opt->{default}); } else { die "Unsupported option type: " . $opt->{type}; } @@ -130,7 +133,7 @@ sub new { if ($opt->{sidetext}) { my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($field); - my $sidetext = Wx::StaticText->new($parent, -1, $opt->{sidetext}, wxDefaultPosition, wxDefaultSize); + my $sidetext = Wx::StaticText->new($self->parent, -1, $opt->{sidetext}, wxDefaultPosition, wxDefaultSize); $sidetext->SetFont($sidetext_font); $sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4); $grid_sizer->Add($sizer); @@ -139,9 +142,142 @@ sub new { } } - $self->Add($grid_sizer, 0, wxEXPAND); + $self->sizer->Add($grid_sizer, 0, wxEXPAND); +} + +sub _on_change { + my $self = shift; + my ($opt_key, $value) = @_; - return $self; + return if $self->sizer->GetStaticBox->GetParent->{disabled}; + $self->_triggers->{$opt_key}->($value); + $self->on_change->($opt_key, $value); +} + +=head2 set_value + +This method accepts an option key and a value. If this option group contains the supplied +option key, its field will be updated with the new value and the method will return a true +value, otherwise it will return false. + +=cut + +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + if ($self->_setters->{$opt_key}) { + $self->_setters->{$opt_key}->($value); + $self->_on_change($opt_key, $value); + return 1; + } + + return 0; +} + +package Slic3r::GUI::ConfigOptionsGroup; +use Moo; + +extends 'Slic3r::GUI::OptionsGroup'; + +=head1 NAME + +Slic3r::GUI::ConfigOptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containing one or more config options + +=head1 SYNOPSIS + + my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( + parent => $self->parent, + title => 'Layers', + options => ['layer_height'], + on_change => sub { print "new value for $_[0] is $_[1]\n" }, + no_labels => 0, + label_width => 180, + ); + $sizer->Add($optgroup->sizer); + +=cut + +use List::Util qw(first); + +sub _trigger_options { + my $self = shift; + + @{$self->options} = map { + my $opt = $_; + if (ref $opt ne 'HASH') { + my $full_key = $opt; + my ($opt_key, $index) = $self->_split_key($full_key); + my $config_opt = $Slic3r::Config::Options->{$opt_key}; + $opt = { + opt_key => $full_key, + config => 1, + (map { $_ => $config_opt->{$_} } qw(type label tooltip sidetext width height full_width min max labels values)), + default => $self->_get_config($opt_key, $index), + on_change => sub { $self->_set_config($opt_key, $index, $_[0]) }, + }; + } + $opt; + } @{$self->options}; +} + +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + if (first { $_->{opt_key} eq $opt_key && !$_->{config} } @{$self->options}) { + return $self->SUPER::set_value($opt_key, $value); + } + + my $changed = 0; + foreach my $full_key (keys %{$self->_setters}) { + my ($key, $index) = $self->_split_key($full_key); + + if ($key eq $opt_key) { + $self->SUPER::set_value($full_key, $self->_get_config($key, $index, $value)); + $changed = 1; + } + } + return $changed; +} + +sub _split_key { + my $self = shift; + my ($opt_key) = @_; + + my $index; + $opt_key =~ s/#(\d+)$// and $index = $1; + return ($opt_key, $index); +} + +sub _get_config { + my $self = shift; + my ($opt_key, $index, $value) = @_; + + my ($get_m, $set_m) = $self->_config_methods($opt_key, $index); + $value ||= Slic3r::Config->$get_m($opt_key); + $value = $value->[$index] if defined $index; + return $value; +} + +sub _set_config { + my $self = shift; + my ($opt_key, $index, $value) = @_; + + my ($get_m, $set_m) = $self->_config_methods($opt_key, $index); + defined $index + ? Slic3r::Config->$get_m($opt_key)->[$index] = $value + : Slic3r::Config->$set_m($opt_key, $value); +} + +sub _config_methods { + my $self = shift; + my ($opt_key, $index) = @_; + + # if it's an array type but no index was specified, use the serialized version + return $Slic3r::Config::Options->{$opt_key}{type} =~ /\@$/ && !defined $index + ? qw(serialize deserialize) + : qw(get_raw set); } 1; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index cb45762ab..98b1ed5ad 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -202,12 +202,22 @@ sub config_wizard { my $self = shift; return unless $self->check_unsaved_changes; - if (Slic3r::GUI::ConfigWizard->new($self)->run) { - $_->() for values %Slic3r::GUI::OptionsGroup::reload_callbacks; - $_->set_dirty(1) for values %{$self->{options_tabs}}; + if (my %settings = Slic3r::GUI::ConfigWizard->new($self)->run) { + $self->set_value($_, $settings{$_}) for keys %settings; } } +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + my $changed = 0; + foreach my $tab (values %{$self->{options_tabs}}) { + $changed = 1 if $tab->set_value($opt_key, $value); + } + return $changed; +} + sub check_unsaved_changes { my $self = shift; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 23bfc3019..c8bbb41bc 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -165,7 +165,7 @@ sub on_select_preset { : $self->{btn_delete_preset}->Enable; } $self->on_preset_loaded; - $_->() for @Slic3r::GUI::OptionsGroup::reload_callbacks{@{$Slic3r::Config::Groups{$self->{presets_group}}}}; + $self->reload_values; $self->set_dirty(0); $Slic3r::Settings->{presets}{$self->{presets_group}} = $preset->{file} ? basename($preset->{file}) : ''; Slic3r::Config->save_settings("$Slic3r::GUI::datadir/slic3r.ini"); @@ -193,6 +193,24 @@ sub add_options_page { return $page; } +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + my $changed = 0; + foreach my $page (@{$self->{pages}}) { + $changed = 1 if $page->set_value($opt_key, $value); + } + return $changed; +} + +sub reload_values { + my $self = shift; + + my $current = Slic3r::Config->current; + $self->set_value($_, $current->{$_}) for keys %$current; +} + sub update_tree { my $self = shift; my ($select) = @_; @@ -449,6 +467,8 @@ sub title { 'Printer Settings' } sub build { my $self = shift; + $self->{extruders_count} = 1; + $self->add_options_page('General', 'printer_empty.png', optgroups => [ { title => 'Size and coordinates', @@ -460,7 +480,17 @@ sub build { }, { title => 'Capabilities', - options => [qw(extruders_count)], + options => [ + { + opt_key => 'extruders_count', + label => 'Extruders', + tooltip => 'Number of extruders of the printer.', + type => 'i', + min => 1, + default => 1, + on_change => sub { $self->{extruders_count} = $_[0] }, + }, + ], }, ]); @@ -493,7 +523,7 @@ sub extruder_options { qw(nozzle_diameter) } sub build_extruder_pages { my $self = shift; - foreach my $extruder_idx (0 .. $Slic3r::extruders_count-1) { + foreach my $extruder_idx (0 .. $self->{extruders_count}-1) { # set default values for my $opt_key ($self->extruder_options) { Slic3r::Config->get_raw($opt_key)->[$extruder_idx] //= Slic3r::Config->get_raw($opt_key)->[0]; #/ @@ -516,7 +546,7 @@ sub build_extruder_pages { # rebuild page list @{$self->{pages}} = ( (grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}), - @{$self->{extruder_pages}}[ 0 .. $Slic3r::extruders_count-1 ], + @{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ], ); } @@ -527,7 +557,7 @@ sub on_value_change { if ($opt_key eq 'extruders_count') { # remove unused pages from list - my @unused_pages = @{ $self->{extruder_pages} }[$Slic3r::extruders_count .. $#{$self->{extruder_pages}}]; + my @unused_pages = @{ $self->{extruder_pages} }[$self->{extruders_count} .. $#{$self->{extruder_pages}}]; for my $page (@unused_pages) { @{$self->{pages}} = grep $_ ne $page, @{$self->{pages}}; $page->{disabled} = 1; @@ -536,7 +566,7 @@ sub on_value_change { # delete values for unused extruders for my $opt_key ($self->extruder_options) { my $values = Slic3r::Config->get_raw($opt_key); - splice @$values, $Slic3r::extruders_count if $Slic3r::extruders_count <= $#$values; + splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values; } # add extra pages @@ -553,11 +583,8 @@ sub on_preset_loaded { # update the extruders count field { - # set value in repository according to the number of nozzle diameters supplied - Slic3r::Config->set('extruders_count', scalar @{ Slic3r::Config->get_raw('nozzle_diameter') }); - - # update the GUI field - $Slic3r::GUI::OptionsGroup::reload_callbacks{extruders_count}->(); + # update the GUI field according to the number of nozzle diameters supplied + $self->set_value('extruders_count', scalar @{ Slic3r::Config->get_raw('nozzle_diameter') }); # update extruder page list $self->on_value_change('extruders_count'); @@ -572,7 +599,7 @@ sub new { my $class = shift; my ($parent, $title, $iconID, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $self->{opt_keys} = []; + $self->{optgroups} = []; $self->{title} = $title; $self->{iconID} = $iconID; @@ -592,8 +619,20 @@ sub append_optgroup { my $self = shift; my %params = @_; - my $optgroup = Slic3r::GUI::OptionsGroup->new($self, label_width => 200, %params); - $self->{vsizer}->Add($optgroup, 0, wxEXPAND | wxALL, 5); + my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(parent => $self, label_width => 200, %params); + $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 5); + push @{$self->{optgroups}}, $optgroup; +} + +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + my $changed = 0; + foreach my $optgroup (@{$self->{optgroups}}) { + $changed = 1 if $optgroup->set_value($opt_key, $value); + } + return $changed; } package Slic3r::GUI::SavePresetWindow; diff --git a/slic3r.pl b/slic3r.pl index 977f7f0fb..54f72c406 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -82,12 +82,7 @@ Slic3r::Config->save($opt{save}) if $opt{save}; # apply command line options to GUI as well and start it if ($gui) { - for my $opt_key (keys %cli_options) { - no warnings 'once'; - ( $Slic3r::GUI::OptionsGroup::reload_callbacks{$opt_key} || sub {} )->(); - my $group = first { $opt_key ~~ @$_ } keys %Slic3r::Groups; - $gui->{skeinpanel}{options_tabs}{$group}->set_dirty(1) if $group; - } + $gui->{skeinpanel}->set_value($_, $cli_options{$_}) for keys %cli_options; $gui->MainLoop; exit; }