Field.pm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  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. die "Method not implemented";
  21. }
  22. sub toggle {
  23. my ($self, $enable) = @_;
  24. $enable ? $self->enable : $self->disable;
  25. }
  26. sub _on_change {
  27. my ($self, $opt_id) = @_;
  28. $self->on_change->($opt_id, $self->get_value)
  29. unless $self->disable_change_event;
  30. }
  31. sub _on_kill_focus {
  32. my ($self, $opt_id, $s, $event) = @_;
  33. # Without this, there will be nasty focus bugs on Windows.
  34. # Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
  35. # non-command events to allow the default handling to take place."
  36. $event->Skip(1);
  37. $self->on_kill_focus->($opt_id);
  38. }
  39. package Slic3r::GUI::OptionsGroup::Field::wxWindow;
  40. use Moo;
  41. extends 'Slic3r::GUI::OptionsGroup::Field';
  42. has 'wxWindow' => (is => 'rw', trigger => 1); # wxWindow object
  43. sub _default_size {
  44. my ($self) = @_;
  45. # default width on Windows is too large
  46. return Wx::Size->new($self->option->width || 60, $self->option->height || -1);
  47. }
  48. sub _trigger_wxWindow {
  49. my ($self) = @_;
  50. $self->set_tooltip($self->option->tooltip);
  51. }
  52. sub set_tooltip {
  53. my ($self, $tooltip) = @_;
  54. $self->wxWindow->SetToolTipString($tooltip)
  55. if $self->wxWindow->can('SetToolTipString');
  56. }
  57. sub set_value {
  58. my ($self, $value) = @_;
  59. $self->disable_change_event(1);
  60. $self->wxWindow->SetValue($value);
  61. $self->disable_change_event(0);
  62. }
  63. sub get_value {
  64. my ($self) = @_;
  65. return $self->wxWindow->GetValue;
  66. }
  67. sub enable {
  68. my ($self) = @_;
  69. $self->wxWindow->Enable;
  70. $self->wxWindow->Refresh;
  71. }
  72. sub disable {
  73. my ($self) = @_;
  74. $self->wxWindow->Disable;
  75. $self->wxWindow->Refresh;
  76. }
  77. package Slic3r::GUI::OptionsGroup::Field::Checkbox;
  78. use Moo;
  79. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  80. use Wx qw(:misc);
  81. use Wx::Event qw(EVT_CHECKBOX);
  82. sub BUILD {
  83. my ($self) = @_;
  84. my $field = Wx::CheckBox->new($self->parent, -1, "");
  85. $self->wxWindow($field);
  86. $field->SetValue($self->option->default);
  87. $field->Disable if $self->option->readonly;
  88. EVT_CHECKBOX($self->parent, $field, sub {
  89. $self->_on_change($self->option->opt_id);
  90. });
  91. }
  92. package Slic3r::GUI::OptionsGroup::Field::SpinCtrl;
  93. use Moo;
  94. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  95. use Wx qw(:misc);
  96. use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
  97. has 'tmp_value' => (is => 'rw');
  98. sub BUILD {
  99. my ($self) = @_;
  100. my $field = Wx::SpinCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
  101. 0, $self->option->min || 0, $self->option->max || 2147483647, $self->option->default);
  102. $self->wxWindow($field);
  103. EVT_SPINCTRL($self->parent, $field, sub {
  104. $self->tmp_value(undef);
  105. $self->_on_change($self->option->opt_id);
  106. });
  107. EVT_TEXT($self->parent, $field, sub {
  108. my ($s, $event) = @_;
  109. # On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
  110. # when it was changed from the text control, so the on_change callback
  111. # gets the old one, and on_kill_focus resets the control to the old value.
  112. # As a workaround, we get the new value from $event->GetString and store
  113. # here temporarily so that we can return it from $self->get_value
  114. $self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
  115. $self->_on_change($self->option->opt_id);
  116. # We don't reset tmp_value here because _on_change might put callbacks
  117. # in the CallAfter queue, and we want the tmp value to be available from
  118. # them as well.
  119. });
  120. EVT_KILL_FOCUS($field, sub {
  121. $self->tmp_value(undef);
  122. $self->_on_kill_focus($self->option->opt_id, @_);
  123. });
  124. }
  125. sub get_value {
  126. my ($self) = @_;
  127. return $self->tmp_value // $self->wxWindow->GetValue;
  128. }
  129. package Slic3r::GUI::OptionsGroup::Field::TextCtrl;
  130. use Moo;
  131. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  132. use Wx qw(:misc :textctrl);
  133. use Wx::Event qw(EVT_TEXT EVT_KILL_FOCUS);
  134. sub BUILD {
  135. my ($self) = @_;
  136. my $style = 0;
  137. $style = wxTE_MULTILINE if $self->option->multiline;
  138. my $field = Wx::TextCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition,
  139. $self->_default_size, $style);
  140. $self->wxWindow($field);
  141. # TODO: test loading a config that has empty string for multi-value options like 'wipe'
  142. EVT_TEXT($self->parent, $field, sub {
  143. $self->_on_change($self->option->opt_id);
  144. });
  145. EVT_KILL_FOCUS($field, sub {
  146. $self->_on_kill_focus($self->option->opt_id, @_);
  147. });
  148. }
  149. sub enable {
  150. my ($self) = @_;
  151. $self->wxWindow->Enable;
  152. $self->wxWindow->SetEditable(1);
  153. }
  154. sub disable {
  155. my ($self) = @_;
  156. $self->wxWindow->Disable;
  157. $self->wxWindow->SetEditable(0);
  158. }
  159. package Slic3r::GUI::OptionsGroup::Field::Choice;
  160. use Moo;
  161. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  162. use List::Util qw(first);
  163. use Wx qw(:misc :combobox);
  164. use Wx::Event qw(EVT_CHOICE EVT_COMBOBOX EVT_TEXT);
  165. sub BUILD {
  166. my ($self) = @_;
  167. my $style = 0;
  168. my $field = 0;
  169. if (defined $self->option->gui_type && $self->option->gui_type ne 'select_open') {
  170. $field = Wx::Choice->new($self->parent, -1, wxDefaultPosition, $self->_default_size,
  171. $self->option->labels || $self->option->values || [], $style);
  172. EVT_CHOICE($self->parent, $field, sub {
  173. $self->_on_change($self->option->opt_id);
  174. });
  175. } else {
  176. $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
  177. $self->option->labels || $self->option->values || [], $style);
  178. EVT_COMBOBOX($self->parent, $field, sub {
  179. $self->_on_change($self->option->opt_id);
  180. });
  181. }
  182. $self->wxWindow($field);
  183. $self->set_value($self->option->default);
  184. EVT_TEXT($self->parent, $field, sub {
  185. $self->_on_change($self->option->opt_id);
  186. });
  187. }
  188. sub set_value {
  189. my ($self, $value) = @_;
  190. $self->disable_change_event(1);
  191. my $idx;
  192. if ($self->option->values) {
  193. $idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
  194. # if value is not among indexes values we use SetValue()
  195. }
  196. if (defined $idx) {
  197. $self->wxWindow->SetSelection($idx);
  198. } else {
  199. $self->wxWindow->SetValue($value);
  200. }
  201. $self->disable_change_event(0);
  202. }
  203. sub set_values {
  204. my ($self, $values) = @_;
  205. $self->disable_change_event(1);
  206. # it looks that Clear() also clears the text field in recent wxWidgets versions,
  207. # but we want to preserve it
  208. my $ww = $self->wxWindow;
  209. my $value = $ww->GetValue;
  210. $ww->Clear;
  211. $ww->Append($_) for @$values;
  212. $ww->SetValue($value);
  213. $self->disable_change_event(0);
  214. }
  215. sub get_value {
  216. my ($self) = @_;
  217. if ($self->option->values) {
  218. my $idx = $self->wxWindow->GetSelection;
  219. if ($idx != &Wx::wxNOT_FOUND) {
  220. return $self->option->values->[$idx];
  221. }
  222. }
  223. return $self->wxWindow->GetValue;
  224. }
  225. package Slic3r::GUI::OptionsGroup::Field::NumericChoice;
  226. use Moo;
  227. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  228. use List::Util qw(first);
  229. use Wx qw(wxTheApp :misc :combobox);
  230. use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
  231. # if option has no 'values', indices are values
  232. # if option has no 'labels', values are labels
  233. sub BUILD {
  234. my ($self) = @_;
  235. my $field = Wx::ComboBox->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
  236. $self->option->labels || $self->option->values);
  237. $self->wxWindow($field);
  238. $self->set_value($self->option->default);
  239. EVT_COMBOBOX($self->parent, $field, sub {
  240. my $disable_change_event = $self->disable_change_event;
  241. $self->disable_change_event(1);
  242. my $idx = $field->GetSelection; # get index of selected value
  243. my $label;
  244. if ($self->option->labels && $idx <= $#{$self->option->labels}) {
  245. $label = $self->option->labels->[$idx];
  246. } elsif ($self->option->values && $idx <= $#{$self->option->values}) {
  247. $label = $self->option->values->[$idx];
  248. } else {
  249. $label = $idx;
  250. }
  251. # The MSW implementation of wxComboBox will leave the field blank if we call
  252. # SetValue() in the EVT_COMBOBOX event handler, so we postpone the call.
  253. wxTheApp->CallAfter(sub {
  254. my $dce = $self->disable_change_event;
  255. $self->disable_change_event(1);
  256. # ChangeValue() is not exported in wxPerl
  257. $field->SetValue($label);
  258. $self->disable_change_event($dce);
  259. });
  260. $self->disable_change_event($disable_change_event);
  261. $self->_on_change($self->option->opt_id);
  262. });
  263. EVT_TEXT($self->parent, $field, sub {
  264. $self->_on_change($self->option->opt_id);
  265. });
  266. }
  267. sub set_value {
  268. my ($self, $value) = @_;
  269. $self->disable_change_event(1);
  270. my $field = $self->wxWindow;
  271. if ($self->option->gui_flags =~ /\bshow_value\b/) {
  272. $field->SetValue($value);
  273. } else {
  274. if ($self->option->values) {
  275. # check whether we have a value index
  276. my $value_idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
  277. if (defined $value_idx) {
  278. $field->SetSelection($value_idx);
  279. $self->disable_change_event(0);
  280. return;
  281. }
  282. } elsif ($self->option->labels && $value <= $#{$self->option->labels}) {
  283. # if we have no values, we expect value to be an index
  284. $field->SetValue($self->option->labels->[$value]);
  285. $self->disable_change_event(0);
  286. return;
  287. }
  288. $field->SetValue($value);
  289. }
  290. $self->disable_change_event(0);
  291. }
  292. sub get_value {
  293. my ($self) = @_;
  294. my $label = $self->wxWindow->GetValue;
  295. if ($self->option->labels) {
  296. my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels};
  297. if (defined $value_idx) {
  298. if ($self->option->values) {
  299. return $self->option->values->[$value_idx];
  300. }
  301. return $value_idx;
  302. }
  303. }
  304. return $label;
  305. }
  306. package Slic3r::GUI::OptionsGroup::Field::ColourPicker;
  307. use Moo;
  308. extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
  309. use Wx qw(:misc :colour);
  310. use Wx::Event qw(EVT_COLOURPICKER_CHANGED);
  311. sub BUILD {
  312. my ($self) = @_;
  313. my $field = Wx::ColourPickerCtrl->new($self->parent, -1,
  314. $self->_string_to_colour($self->option->default), wxDefaultPosition,
  315. $self->_default_size);
  316. $self->wxWindow($field);
  317. EVT_COLOURPICKER_CHANGED($self->parent, $field, sub {
  318. $self->_on_change($self->option->opt_id);
  319. });
  320. }
  321. sub set_value {
  322. my ($self, $value) = @_;
  323. $self->disable_change_event(1);
  324. $self->wxWindow->SetColour($self->_string_to_colour($value));
  325. $self->disable_change_event(0);
  326. }
  327. sub get_value {
  328. my ($self) = @_;
  329. return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
  330. }
  331. sub _string_to_colour {
  332. my ($self, $string) = @_;
  333. $string =~ s/^#//;
  334. return Wx::Colour->new(unpack 'C*', pack 'H*', $string);
  335. }
  336. package Slic3r::GUI::OptionsGroup::Field::wxSizer;
  337. use Moo;
  338. extends 'Slic3r::GUI::OptionsGroup::Field';
  339. has 'wxSizer' => (is => 'rw'); # wxSizer object
  340. package Slic3r::GUI::OptionsGroup::Field::Point;
  341. use Moo;
  342. extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
  343. has 'x_textctrl' => (is => 'rw');
  344. has 'y_textctrl' => (is => 'rw');
  345. use Slic3r::Geometry qw(X Y);
  346. use Wx qw(:misc :sizer);
  347. use Wx::Event qw(EVT_TEXT);
  348. sub BUILD {
  349. my ($self) = @_;
  350. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  351. $self->wxSizer($sizer);
  352. my $field_size = Wx::Size->new(40, -1);
  353. $self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
  354. $self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
  355. my @items = (
  356. Wx::StaticText->new($self->parent, -1, "x:"),
  357. $self->x_textctrl,
  358. Wx::StaticText->new($self->parent, -1, " y:"),
  359. $self->y_textctrl,
  360. );
  361. $sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
  362. if ($self->option->tooltip) {
  363. foreach my $item (@items) {
  364. $item->SetToolTipString($self->option->tooltip)
  365. if $item->can('SetToolTipString');
  366. }
  367. }
  368. EVT_TEXT($self->parent, $_, sub {
  369. $self->_on_change($self->option->opt_id);
  370. }) for $self->x_textctrl, $self->y_textctrl;
  371. }
  372. sub set_value {
  373. my ($self, $value) = @_;
  374. $self->disable_change_event(1);
  375. $self->x_textctrl->SetValue($value->[X]);
  376. $self->y_textctrl->SetValue($value->[Y]);
  377. $self->disable_change_event(0);
  378. }
  379. sub get_value {
  380. my ($self) = @_;
  381. return [
  382. $self->x_textctrl->GetValue,
  383. $self->y_textctrl->GetValue,
  384. ];
  385. }
  386. sub enable {
  387. my ($self) = @_;
  388. $self->x_textctrl->Enable;
  389. $self->y_textctrl->Enable;
  390. }
  391. sub disable {
  392. my ($self) = @_;
  393. $self->x_textctrl->Disable;
  394. $self->y_textctrl->Disable;
  395. }
  396. package Slic3r::GUI::OptionsGroup::Field::Point3;
  397. use Moo;
  398. extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
  399. has 'x_textctrl' => (is => 'rw');
  400. has 'y_textctrl' => (is => 'rw');
  401. has 'z_textctrl' => (is => 'rw');
  402. use Slic3r::Geometry qw(X Y Z);
  403. use Wx qw(:misc :sizer);
  404. use Wx::Event qw(EVT_TEXT);
  405. sub BUILD {
  406. my ($self) = @_;
  407. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  408. $self->wxSizer($sizer);
  409. my $field_size = Wx::Size->new(40, -1);
  410. $self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
  411. $self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
  412. $self->z_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Z], wxDefaultPosition, $field_size));
  413. my @items = (
  414. Wx::StaticText->new($self->parent, -1, "x:"),
  415. $self->x_textctrl,
  416. Wx::StaticText->new($self->parent, -1, " y:"),
  417. $self->y_textctrl,
  418. Wx::StaticText->new($self->parent, -1, " z:"),
  419. $self->z_textctrl,
  420. );
  421. $sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
  422. if ($self->option->tooltip) {
  423. foreach my $item (@items) {
  424. $item->SetToolTipString($self->option->tooltip)
  425. if $item->can('SetToolTipString');
  426. }
  427. }
  428. EVT_TEXT($self->parent, $_, sub {
  429. $self->_on_change($self->option->opt_id);
  430. }) for $self->x_textctrl, $self->y_textctrl, $self->z_textctrl;
  431. }
  432. sub set_value {
  433. my ($self, $value) = @_;
  434. $self->disable_change_event(1);
  435. $self->x_textctrl->SetValue($value->[X]);
  436. $self->y_textctrl->SetValue($value->[Y]);
  437. $self->z_textctrl->SetValue($value->[Z]);
  438. $self->disable_change_event(0);
  439. }
  440. sub get_value {
  441. my ($self) = @_;
  442. return [
  443. $self->x_textctrl->GetValue,
  444. $self->y_textctrl->GetValue,
  445. $self->z_textctrl->GetValue,
  446. ];
  447. }
  448. sub enable {
  449. my ($self) = @_;
  450. $self->x_textctrl->Enable;
  451. $self->y_textctrl->Enable;
  452. $self->z_textctrl->Enable;
  453. }
  454. sub disable {
  455. my ($self) = @_;
  456. $self->x_textctrl->Disable;
  457. $self->y_textctrl->Disable;
  458. $self->z_textctrl->Disable;
  459. }
  460. package Slic3r::GUI::OptionsGroup::Field::Slider;
  461. use Moo;
  462. extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
  463. has 'scale' => (is => 'rw', default => sub { 10 });
  464. has 'slider' => (is => 'rw');
  465. has 'textctrl' => (is => 'rw');
  466. use Slic3r::Geometry qw(X Y);
  467. use Wx qw(:misc :sizer);
  468. use Wx::Event qw(EVT_SLIDER EVT_TEXT EVT_KILL_FOCUS);
  469. sub BUILD {
  470. my ($self) = @_;
  471. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  472. $self->wxSizer($sizer);
  473. my $slider = Wx::Slider->new(
  474. $self->parent, -1,
  475. ($self->option->default // $self->option->min) * $self->scale,
  476. ($self->option->min // 0) * $self->scale,
  477. ($self->option->max // 100) * $self->scale,
  478. wxDefaultPosition,
  479. [ $self->option->width // -1, $self->option->height // -1 ],
  480. );
  481. $self->slider($slider);
  482. my $textctrl = Wx::TextCtrl->new($self->parent, -1, $slider->GetValue/$self->scale,
  483. wxDefaultPosition, [50,-1]);
  484. $self->textctrl($textctrl);
  485. $sizer->Add($slider, 1, wxALIGN_CENTER_VERTICAL, 0);
  486. $sizer->Add($textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
  487. EVT_SLIDER($self->parent, $slider, sub {
  488. $self->_update_textctrl;
  489. $self->_on_change($self->option->opt_id);
  490. });
  491. EVT_TEXT($self->parent, $textctrl, sub {
  492. my $value = $textctrl->GetValue;
  493. if ($value =~ /^-?\d+(\.\d*)?$/) {
  494. # Update the slider without re-updating the text field being modified.
  495. $self->disable_change_event(1);
  496. $self->slider->SetValue($value*$self->scale);
  497. $self->disable_change_event(0);
  498. $self->_on_change($self->option->opt_id);
  499. }
  500. });
  501. EVT_KILL_FOCUS($textctrl, sub {
  502. $self->_update_textctrl;
  503. $self->_on_kill_focus($self->option->opt_id, @_);
  504. });
  505. }
  506. sub set_value {
  507. my ($self, $value) = @_;
  508. $self->disable_change_event(1);
  509. $self->slider->SetValue($value*$self->scale);
  510. $self->_update_textctrl;
  511. $self->disable_change_event(0);
  512. }
  513. sub get_value {
  514. my ($self) = @_;
  515. return $self->slider->GetValue/$self->scale;
  516. }
  517. # Update internal scaling
  518. sub set_scale {
  519. my ($self, $scale) = @_;
  520. $self->disable_change_event(1);
  521. my $current_value = $self->get_value;
  522. $self->slider->SetRange($self->slider->GetMin / $self->scale * $scale, $self->slider->GetMax / $self->scale * $scale);
  523. $self->scale($scale);
  524. $self->set_value($current_value);
  525. $self->disable_change_event(0);
  526. }
  527. sub _update_textctrl {
  528. my ($self) = @_;
  529. $self->textctrl->ChangeValue($self->get_value);
  530. $self->textctrl->SetInsertionPointEnd;
  531. }
  532. sub enable {
  533. my ($self) = @_;
  534. $self->slider->Enable;
  535. $self->textctrl->Enable;
  536. $self->textctrl->SetEditable(1);
  537. }
  538. sub disable {
  539. my ($self) = @_;
  540. $self->slider->Disable;
  541. $self->textctrl->Disable;
  542. $self->textctrl->SetEditable(0);
  543. }
  544. sub set_range {
  545. my ($self, $min, $max) = @_;
  546. $self->slider->SetRange($min * $self->scale, $max * $self->scale);
  547. }
  548. 1;