PrusaSlicer-NonPlainar/lib/Slic3r/GUI/OptionsGroup.pm
bubnikv e386a2bf72 Documentation of the UI config update functions,
documented missing AMF support for the variable layer thickness.
2017-02-07 18:28:53 +01:00

482 lines
16 KiB
Perl
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc.
package Slic3r::GUI::OptionsGroup;
use Moo;
use List::Util qw(first);
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl wxTheApp);
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER);
has 'parent' => (is => 'ro', required => 1);
has 'title' => (is => 'ro', required => 1);
has 'on_change' => (is => 'rw', default => sub { sub {} });
has 'staticbox' => (is => 'ro', default => sub { 1 });
has 'label_width' => (is => 'rw', default => sub { 180 });
has 'extra_column' => (is => 'rw', default => sub { undef });
has 'label_font' => (is => 'rw');
has 'sidetext_font' => (is => 'rw', default => sub { Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) });
has 'sizer' => (is => 'rw');
has '_disabled' => (is => 'rw', default => sub { 0 });
has '_grid_sizer' => (is => 'rw');
has '_options' => (is => 'ro', default => sub { {} });
has '_fields' => (is => 'ro', default => sub { {} });
sub BUILD {
my $self = shift;
if ($self->staticbox) {
my $box = Wx::StaticBox->new($self->parent, -1, $self->title);
$self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL));
} else {
$self->sizer(Wx::BoxSizer->new(wxVERTICAL));
}
my $num_columns = 1;
++$num_columns if $self->label_width != 0;
++$num_columns if $self->extra_column;
$self->_grid_sizer(Wx::FlexGridSizer->new(0, $num_columns, 0, 0));
$self->_grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
$self->_grid_sizer->AddGrowableCol($self->label_width != 0);
# TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific
$self->sizer->Add($self->_grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
}
# this method accepts a Slic3r::GUI::OptionsGroup::Line object
sub append_line {
my ($self, $line) = @_;
if ($line->sizer || ($line->widget && $line->full_width)) {
# full-width widgets are appended *after* the grid sizer, so after all the non-full-width lines
my $sizer = $line->sizer // $line->widget->($self->parent);
$self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
return;
}
my $grid_sizer = $self->_grid_sizer;
# if we have an extra column, build it
if ($self->extra_column) {
if (defined (my $item = $self->extra_column->($line))) {
$grid_sizer->Add($item, 0, wxALIGN_CENTER_VERTICAL, 0);
} else {
# if the callback provides no sizer for the extra cell, put a spacer
$grid_sizer->AddSpacer(1);
}
}
# build label if we have it
my $label;
if ($self->label_width != 0) {
$label = Wx::StaticText->new($self->parent, -1, $line->label ? $line->label . ":" : "", wxDefaultPosition, [$self->label_width, -1]);
$label->SetFont($self->label_font) if $self->label_font;
$label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
$grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
$label->SetToolTipString($line->label_tooltip) if $line->label_tooltip;
}
# if we have a widget, add it to the sizer
if ($line->widget) {
my $widget_sizer = $line->widget->($self->parent);
$grid_sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
return;
}
# if we have a single option with no sidetext just add it directly to the grid sizer
my @options = @{$line->get_options};
$self->_options->{$_->opt_id} = $_ for @options;
if (@options == 1 && !$options[0]->sidetext && !$options[0]->side_widget && !@{$line->get_extra_widgets}) {
my $option = $options[0];
my $field = $self->_build_field($option);
$grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
return;
}
# if we're here, we have more than one option or a single option with sidetext
# so we need a horizontal sizer to arrange these things
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$grid_sizer->Add($sizer, 0, 0, 0);
foreach my $i (0..$#options) {
my $option = $options[$i];
# add label if any
if ($option->label) {
my $field_label = Wx::StaticText->new($self->parent, -1, $option->label . ":", wxDefaultPosition, wxDefaultSize);
$field_label->SetFont($self->sidetext_font);
$sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
}
# add field
my $field = $self->_build_field($option);
$sizer->Add($field, 0, wxALIGN_CENTER_VERTICAL, 0);
# add sidetext if any
if ($option->sidetext) {
my $sidetext = Wx::StaticText->new($self->parent, -1, $option->sidetext, wxDefaultPosition, wxDefaultSize);
$sidetext->SetFont($self->sidetext_font);
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
}
# add side widget if any
if ($option->side_widget) {
$sizer->Add($option->side_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);
}
if ($option != $#options) {
$sizer->AddSpacer(4);
}
}
# add extra sizers if any
foreach my $extra_widget (@{$line->get_extra_widgets}) {
$sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
}
}
sub create_single_option_line {
my ($self, $option) = @_;
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => $option->label,
label_tooltip => $option->tooltip,
);
$option->label("");
$line->append_option($option);
return $line;
}
sub append_single_option_line {
my ($self, $option) = @_;
return $self->append_line($self->create_single_option_line($option));
}
sub _build_field {
my $self = shift;
my ($opt) = @_;
my $opt_id = $opt->opt_id;
my $on_change = sub {
my ($opt_id, $value) = @_;
$self->_on_change($opt_id, $value)
unless $self->_disabled;
};
my $on_kill_focus = sub {
my ($opt_id) = @_;
$self->_on_kill_focus($opt_id);
};
my $type = $opt->{gui_type} || $opt->{type};
my $field;
if ($type eq 'bool') {
$field = Slic3r::GUI::OptionsGroup::Field::Checkbox->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'i') {
$field = Slic3r::GUI::OptionsGroup::Field::SpinCtrl->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'color') {
$field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type =~ /^(f|s|s@|percent)$/) {
$field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'select' || $type eq 'select_open') {
$field = Slic3r::GUI::OptionsGroup::Field::Choice->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'f_enum_open' || $type eq 'i_enum_open' || $type eq 'i_enum_closed') {
$field = Slic3r::GUI::OptionsGroup::Field::NumericChoice->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'point') {
$field = Slic3r::GUI::OptionsGroup::Field::Point->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'slider') {
$field = Slic3r::GUI::OptionsGroup::Field::Slider->new(
parent => $self->parent,
option => $opt,
);
}
return undef if !$field;
$field->on_change($on_change);
$field->on_kill_focus($on_kill_focus);
$self->_fields->{$opt_id} = $field;
return $field->isa('Slic3r::GUI::OptionsGroup::Field::wxWindow')
? $field->wxWindow
: $field->wxSizer;
}
sub get_option {
my ($self, $opt_id) = @_;
return undef if !exists $self->_options->{$opt_id};
return $self->_options->{$opt_id};
}
sub get_field {
my ($self, $opt_id) = @_;
return undef if !exists $self->_fields->{$opt_id};
return $self->_fields->{$opt_id};
}
sub get_value {
my ($self, $opt_id) = @_;
return if !exists $self->_fields->{$opt_id};
return $self->_fields->{$opt_id}->get_value;
}
sub set_value {
my ($self, $opt_id, $value) = @_;
return if !exists $self->_fields->{$opt_id};
$self->_fields->{$opt_id}->set_value($value);
}
sub _on_change {
my ($self, $opt_id, $value) = @_;
$self->on_change->($opt_id, $value);
}
sub enable {
my ($self) = @_;
$_->enable for values %{$self->_fields};
}
sub disable {
my ($self) = @_;
$_->disable for values %{$self->_fields};
}
sub _on_kill_focus {
my ($self, $opt_id) = @_;
# nothing
}
package Slic3r::GUI::OptionsGroup::Line;
use Moo;
has 'label' => (is => 'rw', default => sub { "" });
has 'full_width' => (is => 'rw', default => sub { 0 });
has 'label_tooltip' => (is => 'rw', default => sub { "" });
has 'sizer' => (is => 'rw');
has 'widget' => (is => 'rw');
has '_options' => (is => 'ro', default => sub { [] });
has '_extra_widgets' => (is => 'ro', default => sub { [] });
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
sub append_option {
my ($self, $option) = @_;
push @{$self->_options}, $option;
}
sub append_widget {
my ($self, $widget) = @_;
push @{$self->_extra_widgets}, $widget;
}
sub get_options {
my ($self) = @_;
return [ @{$self->_options} ];
}
sub get_extra_widgets {
my ($self) = @_;
return [ @{$self->_extra_widgets} ];
}
package Slic3r::GUI::OptionsGroup::Option;
use Moo;
has 'opt_id' => (is => 'rw', required => 1);
has 'type' => (is => 'rw', required => 1);
has 'default' => (is => 'rw', required => 1);
has 'gui_type' => (is => 'rw', default => sub { undef });
has 'gui_flags' => (is => 'rw', default => sub { "" });
has 'label' => (is => 'rw', default => sub { "" });
has 'sidetext' => (is => 'rw', default => sub { "" });
has 'tooltip' => (is => 'rw', default => sub { "" });
has 'multiline' => (is => 'rw', default => sub { 0 });
has 'full_width' => (is => 'rw', default => sub { 0 });
has 'width' => (is => 'rw', default => sub { undef });
has 'height' => (is => 'rw', default => sub { undef });
has 'min' => (is => 'rw', default => sub { undef });
has 'max' => (is => 'rw', default => sub { undef });
has 'labels' => (is => 'rw', default => sub { [] });
has 'values' => (is => 'rw', default => sub { [] });
has 'readonly' => (is => 'rw', default => sub { 0 });
has 'side_widget' => (is => 'rw', default => sub { undef });
package Slic3r::GUI::ConfigOptionsGroup;
use Moo;
use List::Util qw(first);
extends 'Slic3r::GUI::OptionsGroup';
has 'config' => (is => 'ro', required => 1);
has 'full_labels' => (is => 'ro', default => sub { 0 });
has '_opt_map' => (is => 'ro', default => sub { {} });
sub get_option {
my ($self, $opt_key, $opt_index) = @_;
$opt_index //= -1;
if (!$self->config->has($opt_key)) {
die "No $opt_key in ConfigOptionsGroup config";
}
my $opt_id = ($opt_index == -1 ? $opt_key : "${opt_key}#${opt_index}");
$self->_opt_map->{$opt_id} = [ $opt_key, $opt_index ];
my $optdef = $Slic3r::Config::Options->{$opt_key}; # we should access this from $self->config
my $default_value = $self->_get_config_value($opt_key, $opt_index, $optdef->{gui_flags} =~ /\bserialized\b/);
return Slic3r::GUI::OptionsGroup::Option->new(
opt_id => $opt_id,
type => $optdef->{type},
default => $default_value,
gui_type => $optdef->{gui_type},
gui_flags => $optdef->{gui_flags},
label => ($self->full_labels && defined $optdef->{full_label}) ? $optdef->{full_label} : $optdef->{label},
sidetext => $optdef->{sidetext},
# calling serialize() ensures we get a stringified value
tooltip => $optdef->{tooltip} . " (default: " . $self->config->serialize($opt_key) . ")",
multiline => $optdef->{multiline},
width => $optdef->{width},
min => $optdef->{min},
max => $optdef->{max},
labels => $optdef->{labels},
values => $optdef->{values},
readonly => $optdef->{readonly},
);
}
sub create_single_option_line {
my ($self, $opt_key, $opt_index) = @_;
my $option;
if (ref($opt_key)) {
$option = $opt_key;
} else {
$option = $self->get_option($opt_key, $opt_index);
}
return $self->SUPER::create_single_option_line($option);
}
sub append_single_option_line {
my ($self, $option, $opt_index) = @_;
return $self->append_line($self->create_single_option_line($option, $opt_index));
}
# Initialize UI components with the config values.
sub reload_config {
my ($self) = @_;
foreach my $opt_id (keys %{ $self->_opt_map }) {
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
my $option = $self->_options->{$opt_id};
$self->set_value($opt_id, $self->_get_config_value($opt_key, $opt_index, $option->gui_flags =~ /\bserialized\b/));
}
}
sub get_fieldc {
my ($self, $opt_key, $opt_index) = @_;
$opt_index //= -1;
my $opt_id = first { $self->_opt_map->{$_}[0] eq $opt_key && $self->_opt_map->{$_}[1] == $opt_index }
keys %{$self->_opt_map};
return defined($opt_id) ? $self->get_field($opt_id) : undef;
}
sub _get_config_value {
my ($self, $opt_key, $opt_index, $deserialize) = @_;
if ($deserialize) {
die "Can't deserialize option indexed value" if $opt_index != -1;
return $self->config->serialize($opt_key);
} else {
return $opt_index == -1
? $self->config->get($opt_key)
: $self->config->get_at($opt_key, $opt_index);
}
}
sub _on_change {
my ($self, $opt_id, $value) = @_;
if (exists $self->_opt_map->{$opt_id}) {
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
my $option = $self->_options->{$opt_id};
# get value
my $field_value = $self->get_value($opt_id);
if ($option->gui_flags =~ /\bserialized\b/) {
die "Can't set serialized option indexed value" if $opt_index != -1;
$self->config->set_deserialize($opt_key, $field_value);
} else {
if ($opt_index == -1) {
$self->config->set($opt_key, $field_value);
} else {
my $value = $self->config->get($opt_key);
$value->[$opt_index] = $field_value;
$self->config->set($opt_key, $value);
}
}
}
$self->SUPER::_on_change($opt_id, $value);
}
sub _on_kill_focus {
my ($self, $opt_id) = @_;
# when a field loses focus, reapply the config value to it
# (thus discarding any invalid input and reverting to the last
# accepted value)
$self->reload_config;
}
package Slic3r::GUI::OptionsGroup::StaticText;
use Wx qw(:misc :systemsettings);
use base 'Wx::StaticText';
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize);
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$self->SetFont($font);
return $self;
}
sub SetText {
my ($self, $value) = @_;
$self->SetLabel($value);
$self->Wrap(400);
$self->GetParent->Layout;
}
1;