PrinterPanel.pm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. package Slic3r::GUI::Controller::PrinterPanel;
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :window :gauge :timer
  6. :textctrl :font :systemsettings);
  7. use Wx::Event qw(EVT_BUTTON EVT_MOUSEWHEEL EVT_TIMER EVT_SCROLLWIN);
  8. use base qw(Wx::Panel Class::Accessor);
  9. __PACKAGE__->mk_accessors(qw(printer_name config sender jobs
  10. printing status_timer temp_timer));
  11. use constant CONNECTION_TIMEOUT => 3; # seconds
  12. use constant STATUS_TIMER_INTERVAL => 1000; # milliseconds
  13. use constant TEMP_TIMER_INTERVAL => 5000; # milliseconds
  14. sub new {
  15. my ($class, $parent, $printer_name, $config) = @_;
  16. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [500, 250]);
  17. $self->printer_name($printer_name || 'Printer');
  18. $self->config($config);
  19. $self->jobs([]);
  20. # set up the timer that polls for updates
  21. {
  22. my $timer_id = &Wx::NewId();
  23. $self->status_timer(Wx::Timer->new($self, $timer_id));
  24. EVT_TIMER($self, $timer_id, sub {
  25. my ($self, $event) = @_;
  26. if ($self->printing) {
  27. my $queue_size = $self->sender->queue_size;
  28. $self->{gauge}->SetValue($self->{gauge}->GetRange - $queue_size);
  29. if ($queue_size == 0) {
  30. $self->print_completed;
  31. }
  32. }
  33. $self->{log_textctrl}->AppendText("$_\n") for @{$self->sender->purge_log};
  34. {
  35. my $temp = $self->sender->getT;
  36. if ($temp eq '') {
  37. $self->{temp_panel}->Hide;
  38. } else {
  39. if (!$self->{temp_panel}->IsShown) {
  40. $self->{temp_panel}->Show;
  41. $self->Layout;
  42. }
  43. $self->{temp_text}->SetLabel($temp . "°C");
  44. $temp = $self->sender->getB;
  45. if ($temp eq '') {
  46. $self->{bed_temp_text}->SetLabel('n.a.');
  47. } else {
  48. $self->{bed_temp_text}->SetLabel($temp . "°C");
  49. }
  50. }
  51. }
  52. });
  53. }
  54. # set up the timer that sends temperature requests
  55. # (responses are handled by status_timer)
  56. {
  57. my $timer_id = &Wx::NewId();
  58. $self->temp_timer(Wx::Timer->new($self, $timer_id));
  59. EVT_TIMER($self, $timer_id, sub {
  60. my ($self, $event) = @_;
  61. $self->sender->send("M105", 1); # send it through priority queue
  62. });
  63. }
  64. my $box = Wx::StaticBox->new($self, -1, "");
  65. my $sizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
  66. my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
  67. # printer name
  68. {
  69. my $text = Wx::StaticText->new($box, -1, $self->printer_name, wxDefaultPosition, [220,-1]);
  70. my $font = $text->GetFont;
  71. $font->SetPointSize(20);
  72. $text->SetFont($font);
  73. $left_sizer->Add($text, 0, wxEXPAND, 0);
  74. }
  75. # connection info
  76. {
  77. my $conn_sizer = Wx::FlexGridSizer->new(2, 2, 1, 0);
  78. $conn_sizer->SetFlexibleDirection(wxHORIZONTAL);
  79. $conn_sizer->AddGrowableCol(1, 1);
  80. $left_sizer->Add($conn_sizer, 0, wxEXPAND | wxTOP, 5);
  81. {
  82. my $text = Wx::StaticText->new($box, -1, "Port:", wxDefaultPosition, wxDefaultSize);
  83. $text->SetFont($Slic3r::GUI::small_font);
  84. $conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  85. }
  86. my $serial_port_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  87. {
  88. $self->{serial_port_combobox} = Wx::ComboBox->new($box, -1, $config->serial_port, wxDefaultPosition, wxDefaultSize, []);
  89. $self->{serial_port_combobox}->SetFont($Slic3r::GUI::small_font);
  90. $self->update_serial_ports;
  91. $serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1);
  92. }
  93. {
  94. $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
  95. wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
  96. $btn->SetToolTipString("Rescan serial ports")
  97. if $btn->can('SetToolTipString');
  98. $serial_port_sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL, 0);
  99. EVT_BUTTON($self, $btn, sub { $self->update_serial_ports });
  100. }
  101. $conn_sizer->Add($serial_port_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  102. {
  103. my $text = Wx::StaticText->new($box, -1, "Speed:", wxDefaultPosition, wxDefaultSize);
  104. $text->SetFont($Slic3r::GUI::small_font);
  105. $conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  106. }
  107. my $serial_speed_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  108. {
  109. $self->{serial_speed_combobox} = Wx::ComboBox->new($box, -1, $config->serial_speed, wxDefaultPosition, wxDefaultSize,
  110. ["115200", "250000"]);
  111. $self->{serial_speed_combobox}->SetFont($Slic3r::GUI::small_font);
  112. $serial_speed_sizer->Add($self->{serial_speed_combobox}, 0, wxALIGN_CENTER_VERTICAL, 0);
  113. }
  114. {
  115. $self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize);
  116. $btn->SetFont($Slic3r::GUI::small_font);
  117. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
  118. $serial_speed_sizer->Add($btn, 0, wxLEFT, 5);
  119. EVT_BUTTON($self, $btn, \&disconnect);
  120. }
  121. $conn_sizer->Add($serial_speed_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  122. }
  123. # buttons
  124. {
  125. $self->{btn_connect} = my $btn = Wx::Button->new($box, -1, "Connect to printer", wxDefaultPosition, [-1, 40]);
  126. my $font = $btn->GetFont;
  127. $font->SetPointSize($font->GetPointSize + 2);
  128. $btn->SetFont($font);
  129. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("arrow_up.png"), wxBITMAP_TYPE_PNG));
  130. $left_sizer->Add($btn, 0, wxTOP, 15);
  131. EVT_BUTTON($self, $btn, \&connect);
  132. }
  133. # print progress bar
  134. {
  135. my $gauge = $self->{gauge} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize);
  136. $left_sizer->Add($self->{gauge}, 0, wxEXPAND | wxTOP, 15);
  137. $gauge->Hide;
  138. }
  139. # status
  140. $self->{status_text} = Wx::StaticText->new($box, -1, "", wxDefaultPosition, [200,-1]);
  141. $left_sizer->Add($self->{status_text}, 1, wxEXPAND | wxTOP, 15);
  142. # manual control
  143. {
  144. $self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize);
  145. $btn->SetFont($Slic3r::GUI::small_font);
  146. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG));
  147. $btn->Hide;
  148. $left_sizer->Add($btn, 0, wxTOP, 15);
  149. EVT_BUTTON($self, $btn, sub {
  150. my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
  151. ($self, $self->config, $self->sender);
  152. $dlg->ShowModal;
  153. });
  154. }
  155. # temperature
  156. {
  157. my $temp_panel = $self->{temp_panel} = Wx::Panel->new($box, -1);
  158. my $temp_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  159. my $temp_font = Wx::Font->new($Slic3r::GUI::small_font);
  160. $temp_font->SetWeight(wxFONTWEIGHT_BOLD);
  161. {
  162. my $text = Wx::StaticText->new($temp_panel, -1, "Temperature:", wxDefaultPosition, wxDefaultSize);
  163. $text->SetFont($Slic3r::GUI::small_font);
  164. $temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
  165. $self->{temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
  166. $self->{temp_text}->SetFont($temp_font);
  167. $self->{temp_text}->SetForegroundColour(Wx::wxRED);
  168. $temp_sizer->Add($self->{temp_text}, 1, wxALIGN_CENTER_VERTICAL);
  169. }
  170. {
  171. my $text = Wx::StaticText->new($temp_panel, -1, "Bed:", wxDefaultPosition, wxDefaultSize);
  172. $text->SetFont($Slic3r::GUI::small_font);
  173. $temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
  174. $self->{bed_temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
  175. $self->{bed_temp_text}->SetFont($temp_font);
  176. $self->{bed_temp_text}->SetForegroundColour(Wx::wxRED);
  177. $temp_sizer->Add($self->{bed_temp_text}, 1, wxALIGN_CENTER_VERTICAL);
  178. }
  179. $temp_panel->SetSizer($temp_sizer);
  180. $temp_panel->Hide;
  181. $left_sizer->Add($temp_panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 4);
  182. }
  183. # print jobs panel
  184. $self->{print_jobs_sizer} = my $print_jobs_sizer = Wx::BoxSizer->new(wxVERTICAL);
  185. {
  186. my $text = Wx::StaticText->new($box, -1, "Queue:", wxDefaultPosition, wxDefaultSize);
  187. $text->SetFont($Slic3r::GUI::small_font);
  188. $print_jobs_sizer->Add($text, 0, wxEXPAND, 0);
  189. $self->{jobs_panel} = Wx::ScrolledWindow->new($box, -1, wxDefaultPosition, wxDefaultSize,
  190. wxVSCROLL | wxBORDER_NONE);
  191. $self->{jobs_panel}->SetScrollbars(0, 1, 0, 1);
  192. $self->{jobs_panel_sizer} = Wx::BoxSizer->new(wxVERTICAL);
  193. $self->{jobs_panel}->SetSizer($self->{jobs_panel_sizer});
  194. $print_jobs_sizer->Add($self->{jobs_panel}, 1, wxEXPAND, 0);
  195. # TODO: fix this. We're trying to pass the scroll event to the parent but it
  196. # doesn't work.
  197. EVT_SCROLLWIN($self->{jobs_panel}, sub {
  198. my ($panel, $event) = @_;
  199. my $controller = $self->GetParent;
  200. my $new_event = Wx::ScrollWinEvent->new(
  201. $event->GetEventType,
  202. $event->GetPosition,
  203. $event->GetOrientation,
  204. );
  205. $controller->ProcessEvent($new_event);
  206. }) if 0;
  207. }
  208. my $log_sizer = Wx::BoxSizer->new(wxVERTICAL);
  209. {
  210. my $text = Wx::StaticText->new($box, -1, "Log:", wxDefaultPosition, wxDefaultSize);
  211. $text->SetFont($Slic3r::GUI::small_font);
  212. $log_sizer->Add($text, 0, wxEXPAND, 0);
  213. my $log = $self->{log_textctrl} = Wx::TextCtrl->new($box, -1, "", wxDefaultPosition, wxDefaultSize,
  214. wxTE_MULTILINE | wxBORDER_NONE);
  215. $log->SetBackgroundColour($box->GetBackgroundColour);
  216. $log->SetFont($Slic3r::GUI::small_font);
  217. $log->SetEditable(0);
  218. $log_sizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0);
  219. }
  220. $sizer->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
  221. $sizer->Add($print_jobs_sizer, 2, wxEXPAND | wxALL, 0);
  222. $sizer->Add($log_sizer, 1, wxEXPAND | wxLEFT, 15);
  223. $self->SetSizer($sizer);
  224. $self->SetMinSize($self->GetSize);
  225. $self->_update_connection_controls;
  226. return $self;
  227. }
  228. sub is_connected {
  229. my ($self) = @_;
  230. return $self->sender && $self->sender->is_connected;
  231. }
  232. sub _update_connection_controls {
  233. my ($self) = @_;
  234. $self->{btn_connect}->Show;
  235. $self->{btn_disconnect}->Hide;
  236. $self->{serial_port_combobox}->Enable;
  237. $self->{serial_speed_combobox}->Enable;
  238. $self->{btn_rescan_serial}->Enable;
  239. $self->{btn_manual_control}->Hide;
  240. $self->{btn_manual_control}->Disable;
  241. if ($self->is_connected) {
  242. $self->{btn_connect}->Hide;
  243. $self->{btn_manual_control}->Show;
  244. if (!$self->printing || $self->printing->paused) {
  245. $self->{btn_disconnect}->Show;
  246. $self->{btn_manual_control}->Enable;
  247. }
  248. $self->{serial_port_combobox}->Disable;
  249. $self->{serial_speed_combobox}->Disable;
  250. $self->{btn_rescan_serial}->Disable;
  251. }
  252. $self->Layout;
  253. }
  254. sub set_status {
  255. my ($self, $status) = @_;
  256. $self->{status_text}->SetLabel($status);
  257. $self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
  258. $self->{status_text}->Refresh;
  259. $self->Layout;
  260. }
  261. sub connect {
  262. my ($self) = @_;
  263. return if $self->is_connected;
  264. $self->set_status("Connecting...");
  265. $self->sender(Slic3r::GCode::Sender->new);
  266. my $res = $self->sender->connect(
  267. $self->{serial_port_combobox}->GetValue,
  268. $self->{serial_speed_combobox}->GetValue,
  269. );
  270. if (!$res) {
  271. $self->set_status("Connection failed. Check serial port and speed.");
  272. } else {
  273. if ($self->sender->wait_connected) {
  274. $self->set_status("Printer is online. You can now start printing from the queue on the right.");
  275. $self->status_timer->Start(STATUS_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
  276. $self->temp_timer->Start(TEMP_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
  277. # request temperature now, without waiting for the timer
  278. $self->sender->send("M105", 1);
  279. } else {
  280. $self->set_status("Connection failed. Check serial port and speed.");
  281. }
  282. }
  283. $self->_update_connection_controls;
  284. $self->reload_jobs;
  285. }
  286. sub disconnect {
  287. my ($self) = @_;
  288. $self->status_timer->Stop;
  289. $self->temp_timer->Stop;
  290. return if !$self->is_connected;
  291. $self->printing->printing(0) if $self->printing;
  292. $self->printing(undef);
  293. $self->{gauge}->Hide;
  294. $self->{temp_panel}->Hide;
  295. $self->sender->disconnect;
  296. $self->set_status("");
  297. $self->_update_connection_controls;
  298. $self->reload_jobs;
  299. }
  300. sub update_serial_ports {
  301. my ($self) = @_;
  302. my $cb = $self->{serial_port_combobox};
  303. my $current = $cb->GetValue;
  304. $cb->Clear;
  305. $cb->Append($_) for Slic3r::GUI::scan_serial_ports;
  306. $cb->SetValue($current);
  307. }
  308. sub load_print_job {
  309. my ($self, $gcode_file, $filament_stats) = @_;
  310. push @{$self->jobs}, my $job = Slic3r::GUI::Controller::PrinterPanel::PrintJob->new(
  311. id => time() . $gcode_file . rand(1000),
  312. gcode_file => $gcode_file,
  313. filament_stats => $filament_stats,
  314. );
  315. $self->reload_jobs;
  316. return $job;
  317. }
  318. sub delete_job {
  319. my ($self, $job) = @_;
  320. $self->jobs([ grep $_->id ne $job->id, @{$self->jobs} ]);
  321. $self->reload_jobs;
  322. }
  323. sub print_job {
  324. my ($self, $job) = @_;
  325. $self->printing($job);
  326. $job->printing(1);
  327. $self->reload_jobs;
  328. open my $fh, '<', $job->gcode_file;
  329. my $line_count = 0;
  330. while (my $row = <$fh>) {
  331. $self->sender->send($row);
  332. $line_count++;
  333. }
  334. close $fh;
  335. $self->_update_connection_controls;
  336. $self->{gauge}->SetRange($line_count);
  337. $self->{gauge}->SetValue(0);
  338. $self->{gauge}->Enable;
  339. $self->{gauge}->Show;
  340. $self->Layout;
  341. $self->set_status('Printing...');
  342. $self->{log_textctrl}->AppendText(sprintf "=====\n");
  343. $self->{log_textctrl}->AppendText(sprintf "Printing %s\n", $job->name);
  344. $self->{log_textctrl}->AppendText(sprintf "Print started at %s\n", $self->_timestamp);
  345. }
  346. sub print_completed {
  347. my ($self) = @_;
  348. my $job = $self->printing;
  349. $self->printing(undef);
  350. $job->printing(0);
  351. $job->printed(1);
  352. $self->_update_connection_controls;
  353. $self->{gauge}->Hide;
  354. $self->Layout;
  355. $self->set_status('Print completed.');
  356. $self->{log_textctrl}->AppendText(sprintf "Print completed at %s\n", $self->_timestamp);
  357. $self->reload_jobs;
  358. }
  359. sub reload_jobs {
  360. my ($self) = @_;
  361. # reorder jobs
  362. @{$self->jobs} = sort { ($a->printed <=> $b->printed) || ($a->timestamp <=> $b->timestamp) }
  363. @{$self->jobs};
  364. # remove all panels
  365. foreach my $child ($self->{jobs_panel_sizer}->GetChildren) {
  366. my $window = $child->GetWindow;
  367. $self->{jobs_panel_sizer}->Detach($window);
  368. # now $child does not exist anymore
  369. $window->Destroy;
  370. }
  371. # re-add all panels
  372. foreach my $job (@{$self->jobs}) {
  373. my $panel = Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel->new($self->{jobs_panel}, $job);
  374. $self->{jobs_panel_sizer}->Add($panel, 0, wxEXPAND | wxBOTTOM, 5);
  375. $panel->on_delete_job(sub {
  376. my ($job) = @_;
  377. $self->delete_job($job);
  378. });
  379. $panel->on_print_job(sub {
  380. my ($job) = @_;
  381. $self->print_job($job);
  382. });
  383. $panel->on_pause_print(sub {
  384. my ($job) = @_;
  385. $self->sender->pause_queue;
  386. $job->paused(1);
  387. $self->reload_jobs;
  388. $self->_update_connection_controls;
  389. $self->{gauge}->Disable;
  390. $self->set_status('Print is paused. Click on Resume to continue.');
  391. });
  392. $panel->on_abort_print(sub {
  393. my ($job) = @_;
  394. $self->sender->purge_queue;
  395. $self->printing(undef);
  396. $job->printing(0);
  397. $job->paused(0);
  398. $self->reload_jobs;
  399. $self->_update_connection_controls;
  400. $self->{gauge}->Disable;
  401. $self->{gauge}->Hide;
  402. $self->set_status('Print was aborted.');
  403. $self->{log_textctrl}->AppendText(sprintf "Print aborted at %s\n", $self->_timestamp);
  404. });
  405. $panel->on_resume_print(sub {
  406. my ($job) = @_;
  407. $self->sender->resume_queue;
  408. $job->paused(0);
  409. $self->reload_jobs;
  410. $self->_update_connection_controls;
  411. $self->{gauge}->Enable;
  412. $self->set_status('Printing...');
  413. });
  414. $panel->enable_print if $self->is_connected && !$self->printing;
  415. EVT_MOUSEWHEEL($panel, sub {
  416. my (undef, $event) = @_;
  417. Wx::PostEvent($self->{jobs_panel}, $event);
  418. $event->Skip;
  419. });
  420. }
  421. $self->{jobs_panel}->Layout;
  422. $self->{print_jobs_sizer}->Layout;
  423. }
  424. sub _timestamp {
  425. my ($self) = @_;
  426. my @time = localtime(time);
  427. return sprintf '%02d:%02d:%02d', @time[2,1,0];
  428. }
  429. package Slic3r::GUI::Controller::PrinterPanel::PrintJob;
  430. use Moo;
  431. use File::Basename qw(basename);
  432. has 'id' => (is => 'ro', required => 1);
  433. has 'timestamp' => (is => 'ro', default => sub { time });
  434. has 'gcode_file' => (is => 'ro', required => 1);
  435. has 'filament_stats' => (is => 'rw');
  436. has 'printing' => (is => 'rw', default => sub { 0 });
  437. has 'paused' => (is => 'rw', default => sub { 0 });
  438. has 'printed' => (is => 'rw', default => sub { 0 });
  439. sub name {
  440. my ($self) = @_;
  441. return basename($self->gcode_file);
  442. }
  443. package Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel;
  444. use strict;
  445. use warnings;
  446. use utf8;
  447. use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :font :dialog :icon :timer
  448. :colour :brush :pen);
  449. use Wx::Event qw(EVT_BUTTON EVT_TIMER EVT_ERASE_BACKGROUND);
  450. use base qw(Wx::Panel Class::Accessor);
  451. __PACKAGE__->mk_accessors(qw(job on_delete_job on_print_job on_pause_print on_resume_print
  452. on_abort_print blink_timer));
  453. sub new {
  454. my ($class, $parent, $job) = @_;
  455. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
  456. $self->job($job);
  457. $self->SetBackgroundColour(wxWHITE);
  458. {
  459. my $white_brush = Wx::Brush->new(wxWHITE, wxSOLID);
  460. my $pen = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
  461. EVT_ERASE_BACKGROUND($self, sub {
  462. my ($self, $event) = @_;
  463. my $dc = $event->GetDC;
  464. my $size = $self->GetSize;
  465. $dc->SetBrush($white_brush);
  466. $dc->SetPen($pen);
  467. $dc->DrawRoundedRectangle(0, 0, $size->GetWidth,$size->GetHeight, 6);
  468. });
  469. }
  470. my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
  471. {
  472. $self->{job_name_textctrl} = my $text = Wx::StaticText->new($self, -1, $job->name, wxDefaultPosition, wxDefaultSize);
  473. my $font = $text->GetFont;
  474. $font->SetWeight(wxFONTWEIGHT_BOLD);
  475. $text->SetFont($font);
  476. if ($job->printed) {
  477. $text->SetForegroundColour($Slic3r::GUI::grey);
  478. }
  479. $left_sizer->Add($text, 0, wxEXPAND, 0);
  480. }
  481. {
  482. my $filament_stats = join "\n",
  483. map "$_ (" . sprintf("%.2f", $job->filament_stats->{$_}/1000) . "m)",
  484. sort keys %{$job->filament_stats};
  485. my $text = Wx::StaticText->new($self, -1, $filament_stats, wxDefaultPosition, wxDefaultSize);
  486. $text->SetFont($Slic3r::GUI::small_font);
  487. if ($job->printed && !$job->printing) {
  488. $text->SetForegroundColour($Slic3r::GUI::grey);
  489. }
  490. $left_sizer->Add($text, 0, wxEXPAND | wxTOP, 6);
  491. }
  492. my $buttons_sizer = Wx::BoxSizer->new(wxVERTICAL);
  493. my $button_style = Wx::wxBORDER_NONE | wxBU_EXACTFIT;
  494. {
  495. my $btn = $self->{btn_delete} = Wx::Button->new($self, -1, 'Delete',
  496. wxDefaultPosition, wxDefaultSize, $button_style);
  497. $btn->SetToolTipString("Delete this job from print queue")
  498. if $btn->can('SetToolTipString');
  499. $btn->SetFont($Slic3r::GUI::small_font);
  500. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
  501. if ($job->printing) {
  502. $btn->Hide;
  503. }
  504. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  505. EVT_BUTTON($self, $btn, sub {
  506. my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete this print job?", 'Delete Job', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
  507. return unless $res == wxID_YES;
  508. wxTheApp->CallAfter(sub {
  509. $self->on_delete_job->($job);
  510. });
  511. });
  512. }
  513. {
  514. my $label = $job->printed ? 'Print Again' : 'Print This';
  515. my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
  516. $button_style);
  517. $btn->SetFont($Slic3r::GUI::small_bold_font);
  518. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
  519. $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
  520. #$btn->SetBitmapPosition(wxRIGHT);
  521. $btn->Hide;
  522. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  523. EVT_BUTTON($self, $btn, sub {
  524. wxTheApp->CallAfter(sub {
  525. $self->on_print_job->($job);
  526. });
  527. });
  528. }
  529. {
  530. my $btn = $self->{btn_pause} = Wx::Button->new($self, -1, "Pause", wxDefaultPosition, wxDefaultSize,
  531. $button_style);
  532. $btn->SetFont($Slic3r::GUI::small_font);
  533. if (!$job->printing || $job->paused) {
  534. $btn->Hide;
  535. }
  536. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_pause.png"), wxBITMAP_TYPE_PNG));
  537. $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_pause_blue.png"), wxBITMAP_TYPE_PNG));
  538. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  539. EVT_BUTTON($self, $btn, sub {
  540. wxTheApp->CallAfter(sub {
  541. $self->on_pause_print->($job);
  542. });
  543. });
  544. }
  545. {
  546. my $btn = $self->{btn_resume} = Wx::Button->new($self, -1, "Resume", wxDefaultPosition, wxDefaultSize,
  547. $button_style);
  548. $btn->SetFont($Slic3r::GUI::small_font);
  549. if (!$job->printing || !$job->paused) {
  550. $btn->Hide;
  551. }
  552. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
  553. $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
  554. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  555. EVT_BUTTON($self, $btn, sub {
  556. wxTheApp->CallAfter(sub {
  557. $self->on_resume_print->($job);
  558. });
  559. });
  560. }
  561. {
  562. my $btn = $self->{btn_abort} = Wx::Button->new($self, -1, "Abort", wxDefaultPosition, wxDefaultSize,
  563. $button_style);
  564. $btn->SetFont($Slic3r::GUI::small_font);
  565. if (!$job->printing) {
  566. $btn->Hide;
  567. }
  568. $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_stop.png"), wxBITMAP_TYPE_PNG));
  569. $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_stop_blue.png"), wxBITMAP_TYPE_PNG));
  570. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  571. EVT_BUTTON($self, $btn, sub {
  572. wxTheApp->CallAfter(sub {
  573. $self->on_abort_print->($job);
  574. });
  575. });
  576. }
  577. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  578. $sizer->Add($left_sizer, 1, wxEXPAND | wxALL, 6);
  579. $sizer->Add($buttons_sizer, 0, wxEXPAND | wxALL, 6);
  580. $self->SetSizer($sizer);
  581. # set-up the timer that changes the job name color while printing
  582. if ($self->job->printing && !$self->job->paused) {
  583. my $timer_id = &Wx::NewId();
  584. $self->blink_timer(Wx::Timer->new($self, $timer_id));
  585. my $blink = 0; # closure
  586. my $colour = Wx::Colour->new(0, 190, 0);
  587. EVT_TIMER($self, $timer_id, sub {
  588. my ($self, $event) = @_;
  589. $self->{job_name_textctrl}->SetForegroundColour($blink ? Wx::wxBLACK : $colour);
  590. $blink = !$blink;
  591. });
  592. $self->blink_timer->Start(1000, wxTIMER_CONTINUOUS);
  593. }
  594. return $self;
  595. }
  596. sub enable_print {
  597. my ($self) = @_;
  598. if (!$self->job->printing) {
  599. $self->{btn_print}->Show;
  600. }
  601. $self->Layout;
  602. }
  603. sub Destroy {
  604. my ($self) = @_;
  605. # There's a gap between the time Perl destroys the wxPanel object and
  606. # the blink_timer member, so the wxTimer might still fire an event which
  607. # isn't handled properly, causing a crash. So we ensure that blink_timer
  608. # is stopped before we destroy the wxPanel.
  609. $self->blink_timer->Stop if $self->blink_timer;
  610. return $self->SUPER::Destroy;
  611. }
  612. 1;