OptionsGroup.pm 15 KB

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