2011-10-03 09:55:32 +00:00
|
|
|
|
package Slic3r::GUI::OptionsGroup;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
use Moo;
|
2011-10-03 09:55:32 +00:00
|
|
|
|
|
2012-07-27 19:13:03 +00:00
|
|
|
|
use List::Util qw(first);
|
2014-06-14 17:59:59 +00:00
|
|
|
|
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl wxTheApp);
|
2014-04-25 16:36:08 +00:00
|
|
|
|
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER);
|
2011-10-03 09:55:32 +00:00
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
=head1 NAME
|
2011-12-06 21:36:42 +00:00
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
Slic3r::GUI::OptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containing one or more options
|
2011-10-05 16:13:47 +00:00
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
=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
|
2012-07-27 19:13:03 +00:00
|
|
|
|
readonly => 0,
|
2012-07-24 13:30:50 +00:00
|
|
|
|
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,
|
2013-08-25 12:37:50 +00:00
|
|
|
|
extra_column => sub { ... },
|
2012-07-24 13:30:50 +00:00
|
|
|
|
);
|
|
|
|
|
$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);
|
2012-10-25 09:24:56 +00:00
|
|
|
|
has 'lines' => (is => 'lazy');
|
2012-07-24 13:30:50 +00:00
|
|
|
|
has 'on_change' => (is => 'ro', default => sub { sub {} });
|
|
|
|
|
has 'no_labels' => (is => 'ro', default => sub { 0 });
|
2014-06-15 23:49:49 +00:00
|
|
|
|
has 'staticbox' => (is => 'ro', default => sub { 1 });
|
2012-07-24 13:30:50 +00:00
|
|
|
|
has 'label_width' => (is => 'ro', default => sub { 180 });
|
2013-08-25 12:37:50 +00:00
|
|
|
|
has 'extra_column' => (is => 'ro');
|
2014-03-23 14:18:08 +00:00
|
|
|
|
has 'label_font' => (is => 'ro');
|
|
|
|
|
has 'sidetext_font' => (is => 'ro', default => sub { Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) });
|
2014-03-24 00:07:30 +00:00
|
|
|
|
has 'ignore_on_change_return' => (is => 'ro', default => sub { 1 });
|
2012-07-24 13:30:50 +00:00
|
|
|
|
|
|
|
|
|
has 'sizer' => (is => 'rw');
|
|
|
|
|
has '_triggers' => (is => 'ro', default => sub { {} });
|
|
|
|
|
has '_setters' => (is => 'ro', default => sub { {} });
|
|
|
|
|
|
|
|
|
|
sub _trigger_options {}
|
|
|
|
|
|
|
|
|
|
sub BUILD {
|
|
|
|
|
my $self = shift;
|
2011-10-03 09:55:32 +00:00
|
|
|
|
|
2014-06-15 23:49:49 +00:00
|
|
|
|
if ($self->staticbox) {
|
2012-07-24 13:30:50 +00:00
|
|
|
|
my $box = Wx::StaticBox->new($self->parent, -1, $self->title);
|
|
|
|
|
$self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL));
|
2014-06-15 23:49:49 +00:00
|
|
|
|
} else {
|
|
|
|
|
$self->sizer(Wx::BoxSizer->new(wxVERTICAL));
|
2012-07-24 13:30:50 +00:00
|
|
|
|
}
|
2011-10-03 09:55:32 +00:00
|
|
|
|
|
2013-08-25 12:37:50 +00:00
|
|
|
|
my $num_columns = $self->extra_column ? 3 : 2;
|
|
|
|
|
my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$self->options}), $num_columns, 0, 0);
|
2012-07-01 17:24:06 +00:00
|
|
|
|
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
2012-07-24 13:30:50 +00:00
|
|
|
|
$grid_sizer->AddGrowableCol($self->no_labels ? 0 : 1);
|
2012-06-18 09:26:21 +00:00
|
|
|
|
|
2012-10-25 09:24:56 +00:00
|
|
|
|
# TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific
|
|
|
|
|
$self->sizer->Add($grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
|
2012-10-28 11:43:41 +00:00
|
|
|
|
|
|
|
|
|
foreach my $line (@{$self->lines}) {
|
2014-06-16 20:45:13 +00:00
|
|
|
|
# build default callbacks in case we don't call _build_line() below
|
|
|
|
|
foreach my $opt_key (@{$line->{options}}) {
|
|
|
|
|
my $opt = first { $_->{opt_key} eq $opt_key } @{$self->options};
|
|
|
|
|
$self->_setters->{$opt_key} //= sub {};
|
|
|
|
|
$self->_triggers->{$opt_key} = $opt->{on_change} || sub { return 1 };
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-25 12:54:08 +00:00
|
|
|
|
if ($line->{sizer}) {
|
|
|
|
|
$self->sizer->Add($line->{sizer}, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
2014-06-15 23:49:49 +00:00
|
|
|
|
} elsif ($line->{widget} && $line->{full_width}) {
|
|
|
|
|
my $sizer = $line->{widget}->($self->parent);
|
|
|
|
|
$self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
2012-10-28 11:43:41 +00:00
|
|
|
|
} else {
|
|
|
|
|
$self->_build_line($line, $grid_sizer);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-10-25 09:24:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# default behavior: one option per line
|
|
|
|
|
sub _build_lines {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
|
|
my $lines = [];
|
2012-07-24 13:30:50 +00:00
|
|
|
|
foreach my $opt (@{$self->options}) {
|
2012-10-25 09:24:56 +00:00
|
|
|
|
push @$lines, {
|
|
|
|
|
label => $opt->{label},
|
|
|
|
|
sidetext => $opt->{sidetext},
|
|
|
|
|
full_width => $opt->{full_width},
|
|
|
|
|
options => [$opt->{opt_key}],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return $lines;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-25 10:37:02 +00:00
|
|
|
|
sub single_option_line {
|
|
|
|
|
my $class = shift;
|
|
|
|
|
my ($opt_key) = @_;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
label => $Slic3r::Config::Options->{$opt_key}{label},
|
|
|
|
|
sidetext => $Slic3r::Config::Options->{$opt_key}{sidetext},
|
|
|
|
|
options => [$opt_key],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-25 09:24:56 +00:00
|
|
|
|
sub _build_line {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($line, $grid_sizer) = @_;
|
|
|
|
|
|
2013-08-25 12:37:50 +00:00
|
|
|
|
if ($self->extra_column) {
|
2014-03-23 14:25:26 +00:00
|
|
|
|
if (defined (my $item = $self->extra_column->($line))) {
|
2014-03-23 14:18:08 +00:00
|
|
|
|
$grid_sizer->Add($item, 0, wxALIGN_CENTER_VERTICAL, 0);
|
|
|
|
|
} else {
|
|
|
|
|
$grid_sizer->AddSpacer(1);
|
|
|
|
|
}
|
2013-08-25 12:37:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-25 09:24:56 +00:00
|
|
|
|
my $label;
|
|
|
|
|
if (!$self->no_labels) {
|
2012-10-28 11:43:41 +00:00
|
|
|
|
$label = Wx::StaticText->new($self->parent, -1, $line->{label} ? "$line->{label}:" : "", wxDefaultPosition, [$self->label_width, -1]);
|
2014-03-23 14:18:08 +00:00
|
|
|
|
$label->SetFont($self->label_font) if $self->label_font;
|
2012-10-25 09:24:56 +00:00
|
|
|
|
$label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
|
|
|
|
|
$grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
|
|
|
|
$label->SetToolTipString($line->{tooltip}) if $line->{tooltip};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my @fields = ();
|
2012-10-25 10:21:04 +00:00
|
|
|
|
my @field_labels = ();
|
2012-10-25 09:24:56 +00:00
|
|
|
|
foreach my $opt_key (@{$line->{options}}) {
|
|
|
|
|
my $opt = first { $_->{opt_key} eq $opt_key } @{$self->options};
|
2014-06-16 20:45:13 +00:00
|
|
|
|
push @fields, $self->_build_field($opt) unless $line->{widget};
|
2012-10-25 10:21:04 +00:00
|
|
|
|
push @field_labels, $opt->{label};
|
2012-10-25 09:24:56 +00:00
|
|
|
|
}
|
2014-06-15 23:49:49 +00:00
|
|
|
|
if (@fields > 1 || $line->{widget} || $line->{sidetext}) {
|
2012-10-25 09:24:56 +00:00
|
|
|
|
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
2012-10-25 10:21:04 +00:00
|
|
|
|
for my $i (0 .. $#fields) {
|
2012-10-25 10:37:02 +00:00
|
|
|
|
if (@fields > 1 && $field_labels[$i]) {
|
2012-10-25 10:21:04 +00:00
|
|
|
|
my $field_label = Wx::StaticText->new($self->parent, -1, "$field_labels[$i]:", wxDefaultPosition, wxDefaultSize);
|
2014-03-23 14:18:08 +00:00
|
|
|
|
$field_label->SetFont($self->sidetext_font);
|
2012-10-25 10:21:04 +00:00
|
|
|
|
$sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
|
|
|
|
}
|
|
|
|
|
$sizer->Add($fields[$i], 0, wxALIGN_CENTER_VERTICAL, 0);
|
|
|
|
|
}
|
2014-06-15 23:49:49 +00:00
|
|
|
|
if ($line->{widget}) {
|
|
|
|
|
my $widget_sizer = $line->{widget}->($self->parent);
|
|
|
|
|
$sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
|
|
|
|
} elsif ($line->{sidetext}) {
|
2012-10-25 10:21:04 +00:00
|
|
|
|
my $sidetext = Wx::StaticText->new($self->parent, -1, $line->{sidetext}, wxDefaultPosition, wxDefaultSize);
|
2014-03-23 14:18:08 +00:00
|
|
|
|
$sidetext->SetFont($self->sidetext_font);
|
2012-10-25 10:21:04 +00:00
|
|
|
|
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
|
|
|
|
|
}
|
2012-10-25 09:24:56 +00:00
|
|
|
|
$grid_sizer->Add($sizer);
|
|
|
|
|
} else {
|
|
|
|
|
$grid_sizer->Add($fields[0], 0, ($line->{full_width} ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub _build_field {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt) = @_;
|
|
|
|
|
|
|
|
|
|
my $opt_key = $opt->{opt_key};
|
|
|
|
|
|
2014-04-19 16:16:34 +00:00
|
|
|
|
my $on_kill_focus = sub {
|
|
|
|
|
my ($s, $event) = @_;
|
|
|
|
|
|
|
|
|
|
# Without this, there will be nasty focus bugs on Windows.
|
|
|
|
|
# Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
|
|
|
|
|
# non-command events to allow the default handling to take place."
|
|
|
|
|
$event->Skip(1);
|
|
|
|
|
|
|
|
|
|
$self->on_kill_focus($opt_key);
|
|
|
|
|
};
|
|
|
|
|
|
2012-10-25 09:24:56 +00:00
|
|
|
|
my $field;
|
2013-01-29 22:52:38 +00:00
|
|
|
|
my $tooltip = $opt->{tooltip};
|
2014-06-19 18:07:16 +00:00
|
|
|
|
my $type = $opt->{gui_type} || $opt->{type};
|
|
|
|
|
if ($type =~ /^(i|i_enum_open|i_enum_closed|f|s|s@|percent|slider)$/) {
|
2012-10-25 09:24:56 +00:00
|
|
|
|
my $style = 0;
|
|
|
|
|
$style = wxTE_MULTILINE if $opt->{multiline};
|
2013-11-12 16:14:37 +00:00
|
|
|
|
# default width on Windows is too large
|
|
|
|
|
my $size = Wx::Size->new($opt->{width} || 60, $opt->{height} || -1);
|
2012-06-28 12:44:54 +00:00
|
|
|
|
|
2014-03-24 00:07:30 +00:00
|
|
|
|
my $on_change = sub {
|
|
|
|
|
my $value = $field->GetValue;
|
2014-06-19 18:07:16 +00:00
|
|
|
|
$value ||= 0 if $type =~ /^(i|i_enum_open|i_enum_closed|f|percent)$/; # prevent crash trying to pass empty strings to Config
|
2014-03-24 00:07:30 +00:00
|
|
|
|
$self->_on_change($opt_key, $value);
|
|
|
|
|
};
|
2014-06-19 18:07:16 +00:00
|
|
|
|
if ($type eq 'i') {
|
2014-03-22 16:01:48 +00:00
|
|
|
|
$field = Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 2147483647, $opt->{default});
|
2013-03-29 17:56:34 +00:00
|
|
|
|
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
|
|
|
|
EVT_SPINCTRL ($self->parent, $field, $on_change);
|
2014-04-19 16:29:40 +00:00
|
|
|
|
EVT_TEXT ($self->parent, $field, $on_change);
|
2014-04-19 16:05:01 +00:00
|
|
|
|
EVT_KILL_FOCUS($field, $on_kill_focus);
|
2014-06-19 18:07:16 +00:00
|
|
|
|
} elsif ($type eq 'i_enum_open' || $type eq 'i_enum_closed') {
|
2014-03-22 16:01:48 +00:00
|
|
|
|
$field = Wx::ComboBox->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $opt->{labels} || $opt->{values});
|
|
|
|
|
$self->_setters->{$opt_key} = sub {
|
2014-06-19 18:07:16 +00:00
|
|
|
|
my ($value) = @_;
|
|
|
|
|
|
|
|
|
|
if ($opt->{gui_flags} =~ /\bshow_value\b/) {
|
|
|
|
|
$field->SetValue($value);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($opt->{values}) {
|
|
|
|
|
# check whether we have a value index
|
|
|
|
|
my $value_idx = first { $opt->{values}[$_] eq $value } 0..$#{$opt->{values}};
|
|
|
|
|
if (defined $value_idx) {
|
|
|
|
|
$field->SetSelection($value_idx);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ($opt->{labels} && $value <= $#{$opt->{labels}}) {
|
|
|
|
|
$field->SetValue($opt->{labels}[$value]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$field->SetValue($value);
|
2014-03-22 16:01:48 +00:00
|
|
|
|
};
|
2014-06-19 18:07:16 +00:00
|
|
|
|
$self->_setters->{$opt_key}->($opt->{default}); # use label if any
|
2014-03-22 16:01:48 +00:00
|
|
|
|
EVT_COMBOBOX($self->parent, $field, sub {
|
2014-04-19 16:42:52 +00:00
|
|
|
|
# Without CallAfter, the field text is not populated on Windows.
|
2014-06-14 17:59:59 +00:00
|
|
|
|
wxTheApp->CallAfter(sub {
|
2014-06-19 18:07:16 +00:00
|
|
|
|
my $value = $field->GetSelection;
|
|
|
|
|
my $label;
|
|
|
|
|
|
|
|
|
|
if ($opt->{values}) {
|
|
|
|
|
$label = $value = $opt->{values}[$value];
|
|
|
|
|
} elsif ($value <= $#{$opt->{labels}}) {
|
|
|
|
|
$label = $opt->{labels}[$value];
|
|
|
|
|
} else {
|
|
|
|
|
$label = $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$field->SetValue($label);
|
|
|
|
|
$self->_on_change($opt_key, $value);
|
2014-04-19 16:42:52 +00:00
|
|
|
|
});
|
2014-03-22 16:01:48 +00:00
|
|
|
|
});
|
2014-06-19 18:07:16 +00:00
|
|
|
|
EVT_TEXT($self->parent, $field, sub {
|
|
|
|
|
my ($s, $event) = @_;
|
|
|
|
|
$event->Skip;
|
|
|
|
|
wxTheApp->CallAfter(sub {
|
|
|
|
|
my $label = $field->GetValue;
|
|
|
|
|
if (defined (my $value = first { $opt->{labels}[$_] eq $label } 0..$#{$opt->{labels}})) {
|
|
|
|
|
if ($opt->{values}) {
|
|
|
|
|
$value = $opt->{values}[$value];
|
|
|
|
|
}
|
|
|
|
|
$self->_on_change($opt_key, $value);
|
|
|
|
|
} elsif ($label !~ /^[0-9]+$/) {
|
|
|
|
|
# if typed text is not numeric, select the default value
|
|
|
|
|
my $default = 0;
|
|
|
|
|
if ($opt->{values}) {
|
|
|
|
|
$default = $opt->{values}[0];
|
|
|
|
|
}
|
|
|
|
|
$self->_setters->{$opt_key}->($default);
|
|
|
|
|
$self->_on_change($opt_key, $default);
|
|
|
|
|
} else {
|
|
|
|
|
$self->_on_change($opt_key, $label);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} elsif ($type eq 'slider') {
|
2014-04-25 16:36:08 +00:00
|
|
|
|
my $scale = 10;
|
|
|
|
|
$field = Wx::BoxSizer->new(wxHORIZONTAL);
|
|
|
|
|
my $slider = Wx::Slider->new($self->parent, -1, ($opt->{default} // $opt->{min})*$scale, ($opt->{min} // 0)*$scale, ($opt->{max} // 100)*$scale, wxDefaultPosition, $size);
|
|
|
|
|
my $statictext = Wx::StaticText->new($self->parent, -1, $slider->GetValue/$scale);
|
|
|
|
|
$field->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for $slider, $statictext;
|
|
|
|
|
$self->_setters->{$opt_key} = sub {
|
|
|
|
|
$field->SetValue($_[0]*$scale);
|
|
|
|
|
};
|
|
|
|
|
EVT_SLIDER($self->parent, $slider, sub {
|
|
|
|
|
my $value = $slider->GetValue/$scale;
|
|
|
|
|
$statictext->SetLabel($value);
|
|
|
|
|
$self->_on_change($opt_key, $value);
|
|
|
|
|
});
|
2013-03-29 17:56:34 +00:00
|
|
|
|
} else {
|
2014-03-22 16:01:48 +00:00
|
|
|
|
$field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style);
|
2014-05-21 09:38:42 +00:00
|
|
|
|
# value supplied to the setter callback might be undef in case user loads a config
|
|
|
|
|
# that has empty string for multi-value options like 'wipe'
|
|
|
|
|
$self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) if defined $_[0] };
|
2014-03-24 00:07:30 +00:00
|
|
|
|
EVT_TEXT($self->parent, $field, $on_change);
|
2014-04-19 16:05:01 +00:00
|
|
|
|
EVT_KILL_FOCUS($field, $on_kill_focus);
|
2013-03-29 17:56:34 +00:00
|
|
|
|
}
|
2014-03-22 16:01:48 +00:00
|
|
|
|
$field->Disable if $opt->{readonly};
|
2013-01-29 22:52:38 +00:00
|
|
|
|
$tooltip .= " (default: " . $opt->{default} . ")" if ($opt->{default});
|
2014-06-19 18:07:16 +00:00
|
|
|
|
} elsif ($type eq 'bool') {
|
2012-10-25 09:24:56 +00:00
|
|
|
|
$field = Wx::CheckBox->new($self->parent, -1, "");
|
|
|
|
|
$field->SetValue($opt->{default});
|
2013-04-27 18:55:43 +00:00
|
|
|
|
$field->Disable if $opt->{readonly};
|
2012-10-25 09:24:56 +00:00
|
|
|
|
EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($opt_key, $field->GetValue); });
|
|
|
|
|
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
2013-01-29 22:52:38 +00:00
|
|
|
|
$tooltip .= " (default: " . ($opt->{default} ? 'yes' : 'no') . ")" if defined($opt->{default});
|
2014-06-19 18:07:16 +00:00
|
|
|
|
} elsif ($type eq 'point') {
|
2012-10-25 09:24:56 +00:00
|
|
|
|
$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($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
|
2013-01-29 22:52:38 +00:00
|
|
|
|
if ($tooltip) {
|
|
|
|
|
$_->SetToolTipString(
|
|
|
|
|
$tooltip . " (default: " . join(",", @{$opt->{default}}) . ")"
|
|
|
|
|
) for @items;
|
2012-06-18 08:19:24 +00:00
|
|
|
|
}
|
2014-03-24 00:07:30 +00:00
|
|
|
|
foreach my $field ($x_field, $y_field) {
|
|
|
|
|
EVT_TEXT($self->parent, $field, sub { $self->_on_change($opt_key, [ $x_field->GetValue, $y_field->GetValue ]) });
|
2014-04-19 16:05:01 +00:00
|
|
|
|
EVT_KILL_FOCUS($field, $on_kill_focus);
|
2014-03-24 00:07:30 +00:00
|
|
|
|
}
|
2012-10-25 09:24:56 +00:00
|
|
|
|
$self->_setters->{$opt_key} = sub {
|
|
|
|
|
$x_field->SetValue($_[0][0]);
|
|
|
|
|
$y_field->SetValue($_[0][1]);
|
|
|
|
|
};
|
2014-06-19 18:07:16 +00:00
|
|
|
|
} elsif ($type eq 'select') {
|
2012-10-25 09:24:56 +00:00
|
|
|
|
$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});
|
2013-01-29 22:52:38 +00:00
|
|
|
|
|
|
|
|
|
$tooltip .= " (default: "
|
|
|
|
|
. $opt->{labels}[ first { $opt->{values}[$_] eq $opt->{default} } 0..$#{$opt->{values}} ]
|
|
|
|
|
. ")" if ($opt->{default});
|
2012-10-25 09:24:56 +00:00
|
|
|
|
} else {
|
2014-06-19 18:07:16 +00:00
|
|
|
|
die "Unsupported option type: " . $type;
|
2011-10-03 09:55:32 +00:00
|
|
|
|
}
|
2013-01-29 22:52:38 +00:00
|
|
|
|
if ($tooltip && $field->can('SetToolTipString')) {
|
|
|
|
|
$field->SetToolTipString($tooltip);
|
|
|
|
|
}
|
2012-10-25 09:24:56 +00:00
|
|
|
|
return $field;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-07-27 19:13:03 +00:00
|
|
|
|
sub _option {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt_key) = @_;
|
|
|
|
|
|
|
|
|
|
return first { $_->{opt_key} eq $opt_key } @{$self->options};
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
sub _on_change {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt_key, $value) = @_;
|
|
|
|
|
|
|
|
|
|
return if $self->sizer->GetStaticBox->GetParent->{disabled};
|
2014-03-24 00:07:30 +00:00
|
|
|
|
$self->_triggers->{$opt_key}->($value) or $self->ignore_on_change_return or return;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
$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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-24 00:07:30 +00:00
|
|
|
|
sub on_kill_focus {}
|
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
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',
|
2012-07-27 19:13:03 +00:00
|
|
|
|
config => $config,
|
2012-07-24 13:30:50 +00:00
|
|
|
|
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
|
|
|
|
|
|
2012-07-28 09:53:10 +00:00
|
|
|
|
use List::Util qw(first);
|
|
|
|
|
|
2012-07-27 19:13:03 +00:00
|
|
|
|
has 'config' => (is => 'ro', required => 1);
|
2013-08-25 12:37:50 +00:00
|
|
|
|
has 'full_labels' => (is => 'ro', default => sub {0});
|
2014-03-24 00:07:30 +00:00
|
|
|
|
has '+ignore_on_change_return' => (is => 'ro', default => sub { 0 });
|
2012-07-24 13:30:50 +00:00
|
|
|
|
|
|
|
|
|
sub _trigger_options {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
|
2014-06-16 20:45:13 +00:00
|
|
|
|
$self->SUPER::_trigger_options;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
@{$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};
|
2014-03-24 00:07:30 +00:00
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
$opt = {
|
|
|
|
|
opt_key => $full_key,
|
|
|
|
|
config => 1,
|
2013-08-25 12:37:50 +00:00
|
|
|
|
label => ($self->full_labels && defined $config_opt->{full_label}) ? $config_opt->{full_label} : $config_opt->{label},
|
2014-06-19 18:07:16 +00:00
|
|
|
|
(map { $_ => $config_opt->{$_} } qw(type gui_type gui_flags tooltip sidetext width height full_width min max labels values multiline readonly)),
|
2014-03-25 00:20:46 +00:00
|
|
|
|
default => $self->_get_config($opt_key, $index),
|
2014-03-24 00:07:30 +00:00
|
|
|
|
on_change => sub { return $self->_set_config($opt_key, $index, $_[0]) },
|
2012-07-24 13:30:50 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
$opt;
|
|
|
|
|
} @{$self->options};
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-28 09:53:10 +00:00
|
|
|
|
sub _option {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt_key) = @_;
|
|
|
|
|
|
|
|
|
|
return first { $_->{opt_key} =~ /^\Q$opt_key\E(#.+)?$/ } @{$self->options};
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
sub set_value {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt_key, $value) = @_;
|
|
|
|
|
|
2012-07-27 19:13:03 +00:00
|
|
|
|
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}) {
|
2012-07-24 13:30:50 +00:00
|
|
|
|
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) {
|
2012-07-27 19:13:03 +00:00
|
|
|
|
$self->config->set($key, $value);
|
|
|
|
|
$self->SUPER::set_value($full_key, $self->_get_config($key, $index));
|
2014-06-16 20:45:13 +00:00
|
|
|
|
return 1;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-06-16 20:45:13 +00:00
|
|
|
|
|
|
|
|
|
# if we're here, we know this option but we found no setter, so we just propagate it
|
|
|
|
|
if ($self->config->has($opt_key)) {
|
|
|
|
|
$self->config->set($opt_key, $value);
|
|
|
|
|
$self->SUPER::set_value($opt_key, $value);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-24 00:07:30 +00:00
|
|
|
|
sub on_kill_focus {
|
|
|
|
|
my ($self, $full_key) = @_;
|
|
|
|
|
|
|
|
|
|
# when a field loses focus, reapply the config value to it
|
|
|
|
|
# (thus discarding any invalid input and reverting to the last
|
|
|
|
|
# accepted value)
|
|
|
|
|
my ($key, $index) = $self->_split_key($full_key);
|
|
|
|
|
$self->SUPER::set_value($full_key, $self->_get_config($key, $index));
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
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;
|
2014-03-24 00:07:30 +00:00
|
|
|
|
my ($opt_key, $index, $config) = @_;
|
2012-07-24 13:30:50 +00:00
|
|
|
|
|
2012-07-27 19:13:03 +00:00
|
|
|
|
my ($get_m, $serialized) = $self->_config_methods($opt_key, $index);
|
2014-03-24 00:07:30 +00:00
|
|
|
|
my $value = ($config // $self->config)->$get_m($opt_key);
|
2012-07-27 19:13:03 +00:00
|
|
|
|
if (defined $index) {
|
|
|
|
|
$value->[$index] //= $value->[0]; #/
|
|
|
|
|
$value = $value->[$index];
|
|
|
|
|
}
|
2012-07-24 13:30:50 +00:00
|
|
|
|
return $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub _set_config {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt_key, $index, $value) = @_;
|
|
|
|
|
|
2012-07-27 19:13:03 +00:00
|
|
|
|
my ($get_m, $serialized) = $self->_config_methods($opt_key, $index);
|
2013-12-22 00:27:09 +00:00
|
|
|
|
if (defined $index) {
|
|
|
|
|
my $values = $self->config->$get_m($opt_key);
|
|
|
|
|
$values->[$index] = $value;
|
2014-04-19 16:16:34 +00:00
|
|
|
|
|
|
|
|
|
# ignore set() return value
|
|
|
|
|
$self->config->set($opt_key, $values);
|
2013-12-22 00:27:09 +00:00
|
|
|
|
} else {
|
2014-04-19 16:16:34 +00:00
|
|
|
|
if ($serialized) {
|
|
|
|
|
# ignore set_deserialize() return value
|
|
|
|
|
return $self->config->set_deserialize($opt_key, $value);
|
|
|
|
|
} else {
|
|
|
|
|
# ignore set() return value
|
|
|
|
|
return $self->config->set($opt_key, $value);
|
2013-12-22 00:27:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-07-24 13:30:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub _config_methods {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my ($opt_key, $index) = @_;
|
2011-10-03 09:55:32 +00:00
|
|
|
|
|
2012-07-24 13:30:50 +00:00
|
|
|
|
# if it's an array type but no index was specified, use the serialized version
|
2012-07-27 19:13:03 +00:00
|
|
|
|
return ($Slic3r::Config::Options->{$opt_key}{type} =~ /\@$/ && !defined $index)
|
|
|
|
|
? qw(serialize 1)
|
|
|
|
|
: qw(get 0);
|
2011-10-03 09:55:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-28 11:43:41 +00:00
|
|
|
|
package Slic3r::GUI::OptionsGroup::StaticTextLine;
|
|
|
|
|
use Wx qw(:misc :systemsettings);
|
2014-06-15 23:49:49 +00:00
|
|
|
|
use base 'Wx::StaticText';
|
2012-10-28 11:43:41 +00:00
|
|
|
|
|
2014-06-15 23:49:49 +00:00
|
|
|
|
sub new {
|
|
|
|
|
my ($class, $parent) = @_;
|
2012-10-28 11:43:41 +00:00
|
|
|
|
|
2014-06-15 23:49:49 +00:00
|
|
|
|
my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize);
|
2012-10-28 11:43:41 +00:00
|
|
|
|
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
2014-06-15 23:49:49 +00:00
|
|
|
|
$self->SetFont($font);
|
|
|
|
|
return $self;
|
2012-10-28 11:43:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub SetText {
|
2014-06-15 23:49:49 +00:00
|
|
|
|
my ($self, $value) = @_;
|
2012-10-28 11:43:41 +00:00
|
|
|
|
|
2014-06-15 23:49:49 +00:00
|
|
|
|
$self->SetLabel($value);
|
|
|
|
|
$self->Wrap(400);
|
|
|
|
|
$self->GetParent->Layout;
|
2012-10-28 11:43:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-10-03 09:55:32 +00:00
|
|
|
|
1;
|