OptionsGroup.pm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. # A dialog group object. Used by the PresetEditor, Preferences dialog, ManualControlDialog etc.
  2. package Slic3r::GUI::OptionsGroup;
  3. use Moo;
  4. use List::Util qw(first);
  5. use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl wxTheApp);
  6. use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER);
  7. has 'parent' => (is => 'ro', required => 1);
  8. has 'title' => (is => 'ro', required => 1);
  9. has 'on_change' => (is => 'rw', default => sub { sub {} });
  10. has 'staticbox' => (is => 'ro', default => sub { 1 });
  11. has 'label_width' => (is => 'rw', default => sub { 180 });
  12. has 'extra_column' => (is => 'rw', default => sub { undef });
  13. has 'label_font' => (is => 'rw');
  14. has 'sidetext_font' => (is => 'rw', default => sub { Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) });
  15. has 'sizer' => (is => 'rw');
  16. has '_disabled' => (is => 'rw', default => sub { 0 });
  17. has '_grid_sizer' => (is => 'rw');
  18. has '_options' => (is => 'ro', default => sub { {} });
  19. has '_fields' => (is => 'ro', default => sub { {} });
  20. sub BUILD {
  21. my $self = shift;
  22. if ($self->staticbox) {
  23. my $box = Wx::StaticBox->new($self->parent, -1, $self->title);
  24. $self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL));
  25. } else {
  26. $self->sizer(Wx::BoxSizer->new(wxVERTICAL));
  27. }
  28. my $num_columns = 1;
  29. ++$num_columns if $self->label_width != 0;
  30. ++$num_columns if $self->extra_column;
  31. $self->_grid_sizer(Wx::FlexGridSizer->new(0, $num_columns, 0, 0));
  32. $self->_grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
  33. $self->_grid_sizer->AddGrowableCol($self->label_width != 0);
  34. # TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific
  35. $self->sizer->Add($self->_grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
  36. }
  37. # this method accepts a Slic3r::GUI::OptionsGroup::Line object
  38. sub append_line {
  39. my ($self, $line) = @_;
  40. if ($line->sizer || ($line->widget && $line->full_width)) {
  41. # full-width widgets are appended *after* the grid sizer, so after all the non-full-width lines
  42. my $sizer = $line->sizer // $line->widget->($self->parent);
  43. $self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
  44. return;
  45. }
  46. my $grid_sizer = $self->_grid_sizer;
  47. # if we have an extra column, build it
  48. if ($self->extra_column) {
  49. if (defined (my $item = $self->extra_column->($line))) {
  50. $grid_sizer->Add($item, 0, wxALIGN_CENTER_VERTICAL, 0);
  51. } else {
  52. # if the callback provides no sizer for the extra cell, put a spacer
  53. $grid_sizer->AddSpacer(1);
  54. }
  55. }
  56. # build label if we have it
  57. my $label;
  58. if ($self->label_width != 0) {
  59. $label = Wx::StaticText->new($self->parent, -1, $line->label ? $line->label . ":" : "", wxDefaultPosition, [$self->label_width, -1]);
  60. $label->SetFont($self->label_font) if $self->label_font;
  61. $label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
  62. $grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
  63. $label->SetToolTipString($line->label_tooltip) if $line->label_tooltip;
  64. }
  65. # if we have a widget, add it to the sizer
  66. if ($line->widget) {
  67. my $widget_sizer = $line->widget->($self->parent);
  68. $grid_sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
  69. return;
  70. }
  71. # if we have a single option with no sidetext just add it directly to the grid sizer
  72. my @options = @{$line->get_options};
  73. $self->_options->{$_->opt_id} = $_ for @options;
  74. if (@options == 1 && !$options[0]->sidetext && !$options[0]->side_widget && !@{$line->get_extra_widgets}) {
  75. my $option = $options[0];
  76. my $field = $self->_build_field($option);
  77. $grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
  78. return;
  79. }
  80. # if we're here, we have more than one option or a single option with sidetext
  81. # so we need a horizontal sizer to arrange these things
  82. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  83. $grid_sizer->Add($sizer, 0, 0, 0);
  84. foreach my $i (0..$#options) {
  85. my $option = $options[$i];
  86. # add label if any
  87. if ($option->label) {
  88. my $field_label = Wx::StaticText->new($self->parent, -1, $option->label . ":", wxDefaultPosition, wxDefaultSize);
  89. $field_label->SetFont($self->sidetext_font);
  90. $sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
  91. }
  92. # add field
  93. my $field = $self->_build_field($option);
  94. $sizer->Add($field, 0, wxALIGN_CENTER_VERTICAL, 0);
  95. # add sidetext if any
  96. if ($option->sidetext) {
  97. my $sidetext = Wx::StaticText->new($self->parent, -1, $option->sidetext, wxDefaultPosition, wxDefaultSize);
  98. $sidetext->SetFont($self->sidetext_font);
  99. $sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
  100. }
  101. # add side widget if any
  102. if ($option->side_widget) {
  103. $sizer->Add($option->side_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);
  104. }
  105. if ($option != $#options) {
  106. $sizer->AddSpacer(4);
  107. }
  108. }
  109. # add extra sizers if any
  110. foreach my $extra_widget (@{$line->get_extra_widgets}) {
  111. $sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
  112. }
  113. }
  114. sub create_single_option_line {
  115. my ($self, $option) = @_;
  116. my $line = Slic3r::GUI::OptionsGroup::Line->new(
  117. label => $option->label,
  118. label_tooltip => $option->tooltip,
  119. );
  120. $option->label("");
  121. $line->append_option($option);
  122. return $line;
  123. }
  124. sub append_single_option_line {
  125. my ($self, $option) = @_;
  126. return $self->append_line($self->create_single_option_line($option));
  127. }
  128. sub _build_field {
  129. my $self = shift;
  130. my ($opt) = @_;
  131. my $opt_id = $opt->opt_id;
  132. my $on_change = sub {
  133. my ($opt_id, $value) = @_;
  134. $self->_on_change($opt_id, $value)
  135. unless $self->_disabled;
  136. };
  137. my $on_kill_focus = sub {
  138. my ($opt_id) = @_;
  139. $self->_on_kill_focus($opt_id);
  140. };
  141. my $type = $opt->{gui_type} || $opt->{type};
  142. my $field;
  143. if ($type eq 'bool') {
  144. $field = Slic3r::GUI::OptionsGroup::Field::Checkbox->new(
  145. parent => $self->parent,
  146. option => $opt,
  147. );
  148. } elsif ($type eq 'i') {
  149. $field = Slic3r::GUI::OptionsGroup::Field::SpinCtrl->new(
  150. parent => $self->parent,
  151. option => $opt,
  152. );
  153. } elsif ($type eq 'color') {
  154. $field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
  155. parent => $self->parent,
  156. option => $opt,
  157. );
  158. } elsif ($type =~ /^(f|s|s@|percent)$/) {
  159. $field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
  160. parent => $self->parent,
  161. option => $opt,
  162. );
  163. } elsif ($type eq 'select' || $type eq 'select_open') {
  164. $field = Slic3r::GUI::OptionsGroup::Field::Choice->new(
  165. parent => $self->parent,
  166. option => $opt,
  167. );
  168. } elsif ($type eq 'f_enum_open' || $type eq 'i_enum_open' || $type eq 'i_enum_closed') {
  169. $field = Slic3r::GUI::OptionsGroup::Field::NumericChoice->new(
  170. parent => $self->parent,
  171. option => $opt,
  172. );
  173. } elsif ($type eq 'point') {
  174. $field = Slic3r::GUI::OptionsGroup::Field::Point->new(
  175. parent => $self->parent,
  176. option => $opt,
  177. );
  178. } elsif ($type eq 'point3'){
  179. $field = Slic3r::GUI::OptionsGroup::Field::Point3->new(
  180. parent => $self->parent,
  181. option => $opt,
  182. );
  183. } elsif ($type eq 'slider') {
  184. $field = Slic3r::GUI::OptionsGroup::Field::Slider->new(
  185. parent => $self->parent,
  186. option => $opt,
  187. );
  188. }
  189. return undef if !$field;
  190. $field->on_change($on_change);
  191. $field->on_kill_focus($on_kill_focus);
  192. $self->_fields->{$opt_id} = $field;
  193. return $field->isa('Slic3r::GUI::OptionsGroup::Field::wxWindow')
  194. ? $field->wxWindow
  195. : $field->wxSizer;
  196. }
  197. sub get_option {
  198. my ($self, $opt_id) = @_;
  199. return undef if !exists $self->_options->{$opt_id};
  200. return $self->_options->{$opt_id};
  201. }
  202. sub get_field {
  203. my ($self, $opt_id) = @_;
  204. return undef if !exists $self->_fields->{$opt_id};
  205. return $self->_fields->{$opt_id};
  206. }
  207. sub get_value {
  208. my ($self, $opt_id) = @_;
  209. return if !exists $self->_fields->{$opt_id};
  210. return $self->_fields->{$opt_id}->get_value;
  211. }
  212. sub set_value {
  213. my ($self, $opt_id, $value) = @_;
  214. return if !exists $self->_fields->{$opt_id};
  215. $self->_fields->{$opt_id}->set_value($value);
  216. }
  217. sub _on_change {
  218. my ($self, $opt_id, $value) = @_;
  219. $self->on_change->($opt_id, $value);
  220. }
  221. sub enable {
  222. my ($self) = @_;
  223. $_->enable for values %{$self->_fields};
  224. }
  225. sub disable {
  226. my ($self) = @_;
  227. $_->disable for values %{$self->_fields};
  228. }
  229. sub _on_kill_focus {
  230. my ($self, $opt_id) = @_;
  231. # nothing
  232. }
  233. package Slic3r::GUI::OptionsGroup::Line;
  234. use Moo;
  235. has 'label' => (is => 'rw', default => sub { "" });
  236. has 'full_width' => (is => 'rw', default => sub { 0 });
  237. has 'label_tooltip' => (is => 'rw', default => sub { "" });
  238. has 'sizer' => (is => 'rw');
  239. has 'widget' => (is => 'rw');
  240. has '_options' => (is => 'ro', default => sub { [] });
  241. has '_extra_widgets' => (is => 'ro', default => sub { [] });
  242. use Wx qw(:button :misc :bitmap);
  243. use Wx::Event qw(EVT_BUTTON);
  244. # this method accepts a Slic3r::GUI::OptionsGroup::Option object
  245. sub append_option {
  246. my ($self, $option) = @_;
  247. push @{$self->_options}, $option;
  248. }
  249. sub append_widget {
  250. my ($self, $widget) = @_;
  251. push @{$self->_extra_widgets}, $widget;
  252. }
  253. sub append_button {
  254. my ($self, $text, $icon, $cb, $ref, $disable) = @_;
  255. $self->append_widget(sub {
  256. my ($parent) = @_;
  257. my $btn = Wx::Button->new($parent, -1,
  258. $text, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
  259. $btn->SetFont($Slic3r::GUI::small_font);
  260. if ($Slic3r::GUI::have_button_icons) {
  261. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG));
  262. }
  263. $btn->Disable if $disable;
  264. $$ref = $btn if $ref;
  265. EVT_BUTTON($parent, $btn, $cb);
  266. return $btn;
  267. });
  268. }
  269. sub get_options {
  270. my ($self) = @_;
  271. return [ @{$self->_options} ];
  272. }
  273. sub get_extra_widgets {
  274. my ($self) = @_;
  275. return [ @{$self->_extra_widgets} ];
  276. }
  277. package Slic3r::GUI::OptionsGroup::Option;
  278. use Moo;
  279. has 'opt_id' => (is => 'rw', required => 1);
  280. has 'type' => (is => 'rw', required => 1);
  281. has 'default' => (is => 'rw', required => 1);
  282. has 'gui_type' => (is => 'rw', default => sub { undef });
  283. has 'gui_flags' => (is => 'rw', default => sub { "" });
  284. has 'label' => (is => 'rw', default => sub { "" });
  285. has 'sidetext' => (is => 'rw', default => sub { "" });
  286. has 'tooltip' => (is => 'rw', default => sub { "" });
  287. has 'multiline' => (is => 'rw', default => sub { 0 });
  288. has 'full_width' => (is => 'rw', default => sub { 0 });
  289. has 'width' => (is => 'rw', default => sub { undef });
  290. has 'height' => (is => 'rw', default => sub { undef });
  291. has 'min' => (is => 'rw', default => sub { undef });
  292. has 'max' => (is => 'rw', default => sub { undef });
  293. has 'labels' => (is => 'rw', default => sub { [] });
  294. has 'values' => (is => 'rw', default => sub { [] });
  295. has 'readonly' => (is => 'rw', default => sub { 0 });
  296. has 'side_widget' => (is => 'rw', default => sub { undef });
  297. package Slic3r::GUI::ConfigOptionsGroup;
  298. use Moo;
  299. use List::Util qw(first);
  300. extends 'Slic3r::GUI::OptionsGroup';
  301. has 'config' => (is => 'ro', required => 1);
  302. has 'full_labels' => (is => 'ro', default => sub { 0 });
  303. has '_opt_map' => (is => 'ro', default => sub { {} });
  304. sub get_option {
  305. my ($self, $opt_key, $opt_index, %params) = @_;
  306. $opt_index //= -1;
  307. if (!$self->config->has($opt_key)) {
  308. die "No $opt_key in ConfigOptionsGroup config";
  309. }
  310. my $opt_id = ($opt_index == -1 ? $opt_key : "${opt_key}#${opt_index}");
  311. $self->_opt_map->{$opt_id} = [ $opt_key, $opt_index ];
  312. my $optdef = $Slic3r::Config::Options->{$opt_key}; # we should access this from $self->config
  313. my $default_value = $self->_get_config_value($opt_key, $opt_index, $optdef->{type} eq 's@');
  314. return Slic3r::GUI::OptionsGroup::Option->new(
  315. opt_id => $opt_id,
  316. type => $optdef->{type},
  317. default => $default_value,
  318. gui_type => $optdef->{gui_type},
  319. gui_flags => $optdef->{gui_flags},
  320. label => ($self->full_labels && defined $optdef->{full_label}) ? $optdef->{full_label} : $optdef->{label},
  321. sidetext => $optdef->{sidetext},
  322. # calling serialize() ensures we get a stringified value
  323. tooltip => $optdef->{tooltip} . " (default: " . $self->config->serialize($opt_key) . ")",
  324. multiline => $optdef->{multiline},
  325. width => $optdef->{width},
  326. min => $optdef->{min},
  327. max => $optdef->{max},
  328. labels => $optdef->{labels},
  329. values => $optdef->{values},
  330. readonly => $optdef->{readonly},
  331. %params,
  332. );
  333. }
  334. sub create_single_option_line {
  335. my ($self, $opt_key, $opt_index, %params) = @_;
  336. my $option;
  337. if (ref($opt_key)) {
  338. $option = $opt_key;
  339. } else {
  340. $option = $self->get_option($opt_key, $opt_index, %params);
  341. }
  342. return $self->SUPER::create_single_option_line($option);
  343. }
  344. sub append_single_option_line {
  345. my ($self, $option, $opt_index, %params) = @_;
  346. return $self->append_line($self->create_single_option_line($option, $opt_index, %params));
  347. }
  348. sub reload_config {
  349. my ($self) = @_;
  350. foreach my $opt_id (keys %{ $self->_opt_map }) {
  351. my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
  352. my $option = $self->_options->{$opt_id};
  353. $self->set_value($opt_id, $self->_get_config_value($opt_key, $opt_index, $option->type eq 's@'));
  354. }
  355. }
  356. sub get_fieldc {
  357. my ($self, $opt_key, $opt_index) = @_;
  358. $opt_index //= -1;
  359. my $opt_id = first { $self->_opt_map->{$_}[0] eq $opt_key && $self->_opt_map->{$_}[1] == $opt_index }
  360. keys %{$self->_opt_map};
  361. return defined($opt_id) ? $self->get_field($opt_id) : undef;
  362. }
  363. sub _get_config_value {
  364. my ($self, $opt_key, $opt_index, $as_string) = @_;
  365. if ($opt_index == -1) {
  366. my $value = $self->config->get($opt_key);
  367. if ($as_string && ref($value) eq 'ARRAY') {
  368. return join "\n", @$value;
  369. }
  370. return $value;
  371. } else {
  372. return $self->config->get_at($opt_key, $opt_index);
  373. }
  374. }
  375. sub _on_change {
  376. my ($self, $opt_id, $value) = @_;
  377. if (exists $self->_opt_map->{$opt_id}) {
  378. my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
  379. my $option = $self->_options->{$opt_id};
  380. # get value
  381. my $field_value = $self->get_value($opt_id);
  382. if ($opt_index == -1) {
  383. if ($option->type eq 's@' && ref($field_value) ne 'ARRAY') {
  384. $field_value = [ split /(?<!\\)[;\n]/, $field_value ];
  385. }
  386. $self->config->set($opt_key, $field_value);
  387. } else {
  388. my $value = $self->config->get($opt_key);
  389. $value->[$opt_index] = $field_value;
  390. $self->config->set($opt_key, $value);
  391. }
  392. }
  393. $self->SUPER::_on_change($opt_id, $value);
  394. }
  395. sub _on_kill_focus {
  396. my ($self, $opt_id) = @_;
  397. # when a field loses focus, reapply the config value to it
  398. # (thus discarding any invalid input and reverting to the last
  399. # accepted value)
  400. $self->reload_config;
  401. }
  402. package Slic3r::GUI::OptionsGroup::StaticText;
  403. use Wx qw(:misc :systemsettings);
  404. use base 'Wx::StaticText';
  405. sub new {
  406. my ($class, $parent) = @_;
  407. my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize);
  408. my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
  409. $self->SetFont($font);
  410. return $self;
  411. }
  412. sub SetText {
  413. my ($self, $value) = @_;
  414. $self->SetLabel($value);
  415. $self->Wrap(400);
  416. $self->GetParent->Layout;
  417. }
  418. 1;