Field.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. package Slic3r::GUI::OptionsGroup::Field;
  2. use Moo;
  3. # This is a base class for option fields.
  4. has 'parent' => (is => 'ro', required => 1);
  5. has 'option' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option
  6. has 'on_change' => (is => 'rw', default => sub { sub {} });
  7. has 'on_kill_focus' => (is => 'rw', default => sub { sub {} });
  8. has 'disable_change_event' => (is => 'rw', default => sub { 0 });
  9. # This method should not fire the on_change event
  10. sub set_value {
  11. my ($self, $value) = @_;
  12. die "Method not implemented";
  13. }
  14. sub get_value {
  15. my ($self) = @_;
  16. die "Method not implemented";
  17. }
  18. sub set_tooltip {
  19. my ($self, $tooltip) = @_;
  20. $self->SetToolTipString($tooltip)
  21. if $tooltip && $self->can('SetToolTipString');
  22. }
  23. sub toggle {
  24. my ($self, $enable) = @_;
  25. $enable ? $self->enable : $self->disable;
  26. }
  27. sub _on_change {
  28. my ($self, $opt_id) = @_;
  29. $self->on_change->($opt_id)
  30. unless $self->disable_change_event;
  31. }
  32. sub _on_kill_focus {
  33. my ($self, $opt_id, $s, $event) = @_;
  34. # Without this, there will be nasty focus bugs on Windows.
  35. # Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
  36. # non-command events to allow the default handling to take place."
  37. $event->Skip(1);
  38. $self->on_kill_focus->($opt_id);
  39. }
  40. package Slic3r::GUI::OptionsGroup::Field::wxWindow;
  41. use Moo;
  42. extends 'Slic3r::GUI::OptionsGroup::Field';
  43. has 'wxWindow' => (is => 'rw', trigger => 1); # wxWindow object
  44. sub _default_size {
  45. my ($self) = @_;
  46. # default width on Windows is too large
  47. return Wx::Size->new($self->option->width || 60, $self->option->height || -1);
  48. }
  49. sub _trigger_wxWindow {
  50. my ($self) = @_;
  51. $self->wxWindow->SetToolTipString($self->option->tooltip)
  52. if $self->option->tooltip && $self->wxWindow->can('SetToolTipString');
  53. }
  54. sub set_value {
  55. my ($self, $value) = @_;
  56. $self->disable_change_event(1);
  57. $self->wxWindow->SetValue($value);
  58. $self->disable_change_event(0);
  59. }
  60. sub get_value {
  61. my ($self) = @_;
  62. return $self->wxWindow->GetValue;
  63. }
  64. sub enable {
  65. my ($self) = @_;
  66. $self->wxWindow->Enable;
  67. $self->wxWindow->Refresh;
  68. }
  69. sub disable {
  70. my ($self) = @_;
  71. $self->wxWindow->Disable;
  72. $self->wxWindow->Refresh;
  73. }
  74. package Slic3r::GUI::OptionsGroup::Field::Checkbox;
  75. use Moo;
  76. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  77. use Wx qw(:misc);
  78. use Wx::Event qw(EVT_CHECKBOX);
  79. sub BUILD {
  80. my ($self) = @_;
  81. my $field = Wx::CheckBox->new($self->parent, -1, "");
  82. $self->wxWindow($field);
  83. $field->SetValue($self->option->default);
  84. $field->Disable if $self->option->readonly;
  85. EVT_CHECKBOX($self->parent, $field, sub {
  86. $self->_on_change($self->option->opt_id);
  87. });
  88. }
  89. package Slic3r::GUI::OptionsGroup::Field::SpinCtrl;
  90. use Moo;
  91. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  92. use Wx qw(:misc);
  93. use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
  94. has 'tmp_value' => (is => 'rw');
  95. sub BUILD {
  96. my ($self) = @_;
  97. my $field = Wx::SpinCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
  98. 0, $self->option->min || 0, $self->option->max || 2147483647, $self->option->default);
  99. $self->wxWindow($field);
  100. EVT_SPINCTRL($self->parent, $field, sub {
  101. $self->tmp_value(undef);
  102. $self->_on_change($self->option->opt_id);
  103. });
  104. EVT_TEXT($self->parent, $field, sub {
  105. my ($s, $event) = @_;
  106. # On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
  107. # when it was changed from the text control, so the on_change callback
  108. # gets the old one, and on_kill_focus resets the control to the old value.
  109. # As a workaround, we get the new value from $event->GetString and store
  110. # here temporarily so that we can return it from $self->get_value
  111. $self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
  112. $self->_on_change($self->option->opt_id);
  113. # We don't reset tmp_value here because _on_change might put callbacks
  114. # in the CallAfter queue, and we want the tmp value to be available from
  115. # them as well.
  116. });
  117. EVT_KILL_FOCUS($field, sub {
  118. $self->tmp_value(undef);
  119. $self->_on_kill_focus($self->option->opt_id, @_);
  120. });
  121. }
  122. sub get_value {
  123. my ($self) = @_;
  124. return $self->tmp_value // $self->wxWindow->GetValue;
  125. }
  126. package Slic3r::GUI::OptionsGroup::Field::TextCtrl;
  127. use Moo;
  128. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  129. use Wx qw(:misc :textctrl);
  130. use Wx::Event qw(EVT_TEXT EVT_KILL_FOCUS);
  131. sub BUILD {
  132. my ($self) = @_;
  133. my $style = 0;
  134. $style = wxTE_MULTILINE if $self->option->multiline;
  135. my $field = Wx::TextCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition,
  136. $self->_default_size, $style);
  137. $self->wxWindow($field);
  138. # TODO: test loading a config that has empty string for multi-value options like 'wipe'
  139. EVT_TEXT($self->parent, $field, sub {
  140. $self->_on_change($self->option->opt_id);
  141. });
  142. EVT_KILL_FOCUS($field, sub {
  143. $self->_on_kill_focus($self->option->opt_id, @_);
  144. });
  145. }
  146. sub enable {
  147. my ($self) = @_;
  148. $self->wxWindow->Enable;
  149. $self->wxWindow->SetEditable(1);
  150. }
  151. sub disable {
  152. my ($self) = @_;
  153. $self->wxWindow->Disable;
  154. $self->wxWindow->SetEditable(0);
  155. }
  156. package Slic3r::GUI::OptionsGroup::Field::Choice;
  157. use Moo;
  158. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  159. use List::Util qw(first);
  160. use Wx qw(:misc :combobox);
  161. use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
  162. sub BUILD {
  163. my ($self) = @_;
  164. my $style = 0;
  165. $style |= wxCB_READONLY if $self->option->gui_type ne 'select_open';
  166. my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
  167. $self->option->labels || $self->option->values || [], $style);
  168. $self->wxWindow($field);
  169. $self->set_value($self->option->default);
  170. EVT_COMBOBOX($self->parent, $field, sub {
  171. $self->_on_change($self->option->opt_id);
  172. });
  173. EVT_TEXT($self->parent, $field, sub {
  174. $self->_on_change($self->option->opt_id);
  175. });
  176. }
  177. sub set_value {
  178. my ($self, $value) = @_;
  179. $self->disable_change_event(1);
  180. if ($self->option->values) {
  181. my $idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
  182. $self->wxWindow->SetSelection($idx);
  183. } else {
  184. $self->wxWindow->SetValue($value);
  185. }
  186. $self->disable_change_event(0);
  187. }
  188. sub set_values {
  189. my ($self, $values) = @_;
  190. $self->disable_change_event(1);
  191. # it looks that Clear() also clears the text field in recent wxWidgets versions,
  192. # but we want to preserve it
  193. my $ww = $self->wxWindow;
  194. my $value = $ww->GetValue;
  195. $ww->Clear;
  196. $ww->Append($_) for @$values;
  197. $ww->SetValue($value);
  198. $self->disable_change_event(0);
  199. }
  200. sub get_value {
  201. my ($self) = @_;
  202. if ($self->option->values) {
  203. my $idx = $self->wxWindow->GetSelection;
  204. if ($idx != &Wx::wxNOT_FOUND) {
  205. return $self->option->values->[$idx];
  206. }
  207. }
  208. return $self->wxWindow->GetValue;
  209. }
  210. package Slic3r::GUI::OptionsGroup::Field::NumericChoice;
  211. use Moo;
  212. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  213. use List::Util qw(first);
  214. use Wx qw(wxTheApp :misc :combobox);
  215. use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
  216. # if option has no 'values', indices are values
  217. # if option has no 'labels', values are labels
  218. sub BUILD {
  219. my ($self) = @_;
  220. my $field = Wx::ComboBox->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
  221. $self->option->labels || $self->option->values);
  222. $self->wxWindow($field);
  223. $self->set_value($self->option->default);
  224. EVT_COMBOBOX($self->parent, $field, sub {
  225. my $disable_change_event = $self->disable_change_event;
  226. $self->disable_change_event(1);
  227. my $idx = $field->GetSelection; # get index of selected value
  228. my $label;
  229. if ($self->option->labels && $idx <= $#{$self->option->labels}) {
  230. $label = $self->option->labels->[$idx];
  231. } elsif ($self->option->values && $idx <= $#{$self->option->values}) {
  232. $label = $self->option->values->[$idx];
  233. } else {
  234. $label = $idx;
  235. }
  236. # The MSW implementation of wxComboBox will leave the field blank if we call
  237. # SetValue() in the EVT_COMBOBOX event handler, so we postpone the call.
  238. wxTheApp->CallAfter(sub {
  239. my $dce = $self->disable_change_event;
  240. $self->disable_change_event(1);
  241. # ChangeValue() is not exported in wxPerl
  242. $field->SetValue($label);
  243. $self->disable_change_event($dce);
  244. });
  245. $self->disable_change_event($disable_change_event);
  246. $self->_on_change($self->option->opt_id);
  247. });
  248. EVT_TEXT($self->parent, $field, sub {
  249. $self->_on_change($self->option->opt_id);
  250. });
  251. }
  252. sub set_value {
  253. my ($self, $value) = @_;
  254. $self->disable_change_event(1);
  255. my $field = $self->wxWindow;
  256. if ($self->option->gui_flags =~ /\bshow_value\b/) {
  257. $field->SetValue($value);
  258. } else {
  259. if ($self->option->values) {
  260. # check whether we have a value index
  261. my $value_idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
  262. if (defined $value_idx) {
  263. $field->SetSelection($value_idx);
  264. $self->disable_change_event(0);
  265. return;
  266. }
  267. } elsif ($self->option->labels && $value <= $#{$self->option->labels}) {
  268. # if we have no values, we expect value to be an index
  269. $field->SetValue($self->option->labels->[$value]);
  270. $self->disable_change_event(0);
  271. return;
  272. }
  273. $field->SetValue($value);
  274. }
  275. $self->disable_change_event(0);
  276. }
  277. sub get_value {
  278. my ($self) = @_;
  279. my $label = $self->wxWindow->GetValue;
  280. if ($self->option->labels) {
  281. my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels};
  282. if (defined $value_idx) {
  283. if ($self->option->values) {
  284. return $self->option->values->[$value_idx];
  285. }
  286. return $value_idx;
  287. }
  288. }
  289. return $label;
  290. }
  291. package Slic3r::GUI::OptionsGroup::Field::ColourPicker;
  292. use Moo;
  293. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  294. use Wx qw(:misc :colour);
  295. use Wx::Event qw(EVT_COLOURPICKER_CHANGED);
  296. sub BUILD {
  297. my ($self) = @_;
  298. my $field = Wx::ColourPickerCtrl->new($self->parent, -1,
  299. $self->_string_to_colour($self->option->default), wxDefaultPosition,
  300. $self->_default_size);
  301. $self->wxWindow($field);
  302. EVT_COLOURPICKER_CHANGED($self->parent, $field, sub {
  303. $self->_on_change($self->option->opt_id);
  304. });
  305. }
  306. sub set_value {
  307. my ($self, $value) = @_;
  308. $self->disable_change_event(1);
  309. $self->wxWindow->SetColour($self->_string_to_colour($value));
  310. $self->disable_change_event(0);
  311. }
  312. sub get_value {
  313. my ($self) = @_;
  314. return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
  315. }
  316. sub _string_to_colour {
  317. my ($self, $string) = @_;
  318. $string =~ s/^#//;
  319. return Wx::Colour->new(unpack 'C*', pack 'H*', $string);
  320. }
  321. package Slic3r::GUI::OptionsGroup::Field::wxSizer;
  322. use Moo;
  323. extends 'Slic3r::GUI::OptionsGroup::Field';
  324. has 'wxSizer' => (is => 'rw'); # wxSizer object
  325. package Slic3r::GUI::OptionsGroup::Field::Point;
  326. use Moo;
  327. extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
  328. has 'x_textctrl' => (is => 'rw');
  329. has 'y_textctrl' => (is => 'rw');
  330. use Slic3r::Geometry qw(X Y);
  331. use Wx qw(:misc :sizer);
  332. use Wx::Event qw(EVT_TEXT);
  333. sub BUILD {
  334. my ($self) = @_;
  335. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  336. $self->wxSizer($sizer);
  337. my $field_size = Wx::Size->new(40, -1);
  338. $self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
  339. $self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
  340. my @items = (
  341. Wx::StaticText->new($self->parent, -1, "x:"),
  342. $self->x_textctrl,
  343. Wx::StaticText->new($self->parent, -1, " y:"),
  344. $self->y_textctrl,
  345. );
  346. $sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
  347. if ($self->option->tooltip) {
  348. foreach my $item (@items) {
  349. $item->SetToolTipString($self->option->tooltip)
  350. if $item->can('SetToolTipString');
  351. }
  352. }
  353. EVT_TEXT($self->parent, $_, sub {
  354. $self->_on_change($self->option->opt_id);
  355. }) for $self->x_textctrl, $self->y_textctrl;
  356. }
  357. sub set_value {
  358. my ($self, $value) = @_;
  359. $self->disable_change_event(1);
  360. $self->x_textctrl->SetValue($value->[X]);
  361. $self->y_textctrl->SetValue($value->[Y]);
  362. $self->disable_change_event(0);
  363. }
  364. sub get_value {
  365. my ($self) = @_;
  366. return [
  367. $self->x_textctrl->GetValue,
  368. $self->y_textctrl->GetValue,
  369. ];
  370. }
  371. sub enable {
  372. my ($self) = @_;
  373. $self->x_textctrl->Enable;
  374. $self->y_textctrl->Enable;
  375. }
  376. sub disable {
  377. my ($self) = @_;
  378. $self->x_textctrl->Disable;
  379. $self->y_textctrl->Disable;
  380. }
  381. package Slic3r::GUI::OptionsGroup::Field::Slider;
  382. use Moo;
  383. extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
  384. has 'scale' => (is => 'rw', default => sub { 10 });
  385. has 'slider' => (is => 'rw');
  386. has 'statictext' => (is => 'rw');
  387. use Slic3r::Geometry qw(X Y);
  388. use Wx qw(:misc :sizer);
  389. use Wx::Event qw(EVT_SLIDER);
  390. sub BUILD {
  391. my ($self) = @_;
  392. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  393. $self->wxSizer($sizer);
  394. my $slider = Wx::Slider->new(
  395. $self->parent, -1,
  396. ($self->option->default // $self->option->min) * $self->scale,
  397. ($self->option->min // 0) * $self->scale,
  398. ($self->option->max // 100) * $self->scale,
  399. wxDefaultPosition,
  400. [ $self->option->width // -1, $self->option->height // -1 ],
  401. );
  402. $self->slider($slider);
  403. my $statictext = Wx::StaticText->new($self->parent, -1, $slider->GetValue/$self->scale,
  404. wxDefaultPosition, [20,-1]);
  405. $self->statictext($statictext);
  406. $sizer->Add($slider, 1, wxALIGN_CENTER_VERTICAL, 0);
  407. $sizer->Add($statictext, 0, wxALIGN_CENTER_VERTICAL, 0);
  408. EVT_SLIDER($self->parent, $slider, sub {
  409. $self->_update_statictext;
  410. $self->_on_change($self->option->opt_id);
  411. });
  412. }
  413. sub set_value {
  414. my ($self, $value) = @_;
  415. $self->disable_change_event(1);
  416. $self->slider->SetValue($value);
  417. $self->_update_statictext;
  418. $self->disable_change_event(0);
  419. }
  420. sub get_value {
  421. my ($self) = @_;
  422. return $self->slider->GetValue/$self->scale;
  423. }
  424. sub _update_statictext {
  425. my ($self) = @_;
  426. $self->statictext->SetLabel($self->get_value);
  427. }
  428. 1;