mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-05-03 09:15:00 +08:00

Slic3r::Config is now an object. Multiple partial config objects are used throughout the codebase as local repositories, then merged and serialized when necessary.
302 lines
10 KiB
Perl
302 lines
10 KiB
Perl
package Slic3r::GUI::OptionsGroup;
|
|
use Moo;
|
|
|
|
use List::Util qw(first);
|
|
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl);
|
|
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT);
|
|
|
|
=head1 NAME
|
|
|
|
Slic3r::GUI::OptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containing one or more options
|
|
|
|
=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
|
|
readonly => 0,
|
|
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($self->parent, -1, $self->title);
|
|
$self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL));
|
|
}
|
|
|
|
my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$self->options}), 2, ($self->no_labels ? 1 : 2), 0);
|
|
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
|
$grid_sizer->AddGrowableCol($self->no_labels ? 0 : 1);
|
|
|
|
my $sidetext_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|
|
|
foreach my $opt (@{$self->options}) {
|
|
my $opt_key = $opt->{opt_key};
|
|
$self->_triggers->{$opt_key} = $opt->{on_change} || sub {};
|
|
|
|
my $label;
|
|
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);
|
|
}
|
|
|
|
my $field;
|
|
if ($opt->{type} =~ /^(i|f|s|s@)$/) {
|
|
my $style = 0;
|
|
$style = wxTE_MULTILINE if $opt->{multiline};
|
|
my $size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1);
|
|
|
|
$field = $opt->{type} eq 'i'
|
|
? 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);
|
|
$field->Disable if $opt->{readonly};
|
|
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
|
|
|
my $on_change = sub { $self->_on_change($opt_key, $field->GetValue) };
|
|
$opt->{type} eq 'i'
|
|
? EVT_SPINCTRL ($self->parent, $field, $on_change)
|
|
: EVT_TEXT ($self->parent, $field, $on_change);
|
|
} elsif ($opt->{type} eq 'bool') {
|
|
$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 @items = (
|
|
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;
|
|
}
|
|
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]);
|
|
};
|
|
} elsif ($opt->{type} eq 'select') {
|
|
$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]);
|
|
});
|
|
$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};
|
|
}
|
|
$label->SetToolTipString($opt->{tooltip}) if $label && $opt->{tooltip};
|
|
$field->SetToolTipString($opt->{tooltip}) if $opt->{tooltip} && $field->can('SetToolTipString');
|
|
if ($opt->{sidetext}) {
|
|
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
|
$sizer->Add($field);
|
|
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);
|
|
} else {
|
|
$grid_sizer->Add($field, 0, $opt->{full_width} ? wxEXPAND : 0);
|
|
}
|
|
}
|
|
|
|
$self->sizer->Add($grid_sizer, 0, wxEXPAND);
|
|
}
|
|
|
|
sub _option {
|
|
my $self = shift;
|
|
my ($opt_key) = @_;
|
|
|
|
return first { $_->{opt_key} eq $opt_key } @{$self->options};
|
|
}
|
|
|
|
sub _on_change {
|
|
my $self = shift;
|
|
my ($opt_key, $value) = @_;
|
|
|
|
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',
|
|
config => $config,
|
|
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
|
|
|
|
has 'config' => (is => 'ro', required => 1);
|
|
|
|
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 multiline readonly)),
|
|
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) = @_;
|
|
|
|
my $opt = $self->_option($opt_key) or return 0;
|
|
|
|
# if user is setting a non-config option, forward the call to the parent
|
|
if (!$opt->{config}) {
|
|
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->config->set($key, $value);
|
|
$self->SUPER::set_value($full_key, $self->_get_config($key, $index));
|
|
$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) = @_;
|
|
|
|
my ($get_m, $serialized) = $self->_config_methods($opt_key, $index);
|
|
my $value = $self->config->$get_m($opt_key);
|
|
if (defined $index) {
|
|
$value->[$index] //= $value->[0]; #/
|
|
$value = $value->[$index];
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
sub _set_config {
|
|
my $self = shift;
|
|
my ($opt_key, $index, $value) = @_;
|
|
|
|
my ($get_m, $serialized) = $self->_config_methods($opt_key, $index);
|
|
defined $index
|
|
? $self->config->$get_m($opt_key)->[$index] = $value
|
|
: $self->config->set($opt_key, $value, $serialized);
|
|
}
|
|
|
|
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 1)
|
|
: qw(get 0);
|
|
}
|
|
|
|
1;
|