1 #include "PrintHostDialogs.hpp"
2
3 #include <algorithm>
4
5 #include <wx/frame.h>
6 #include <wx/progdlg.h>
7 #include <wx/sizer.h>
8 #include <wx/stattext.h>
9 #include <wx/textctrl.h>
10 #include <wx/checkbox.h>
11 #include <wx/button.h>
12 #include <wx/dataview.h>
13 #include <wx/wupdlock.h>
14 #include <wx/debug.h>
15
16 #include "GUI.hpp"
17 #include "GUI_App.hpp"
18 #include "MsgDialog.hpp"
19 #include "I18N.hpp"
20 #include "../Utils/PrintHost.hpp"
21 #include "wxExtensions.hpp"
22 #include "MainFrame.hpp"
23 #include "libslic3r/AppConfig.hpp"
24
25 namespace fs = boost::filesystem;
26
27 namespace Slic3r {
28 namespace GUI {
29
30 static const char *CONFIG_KEY_PATH = "printhost_path";
31 static const char *CONFIG_KEY_PRINT = "printhost_print";
32 static const char *CONFIG_KEY_GROUP = "printhost_group";
33
PrintHostSendDialog(const fs::path & path,bool can_start_print,const wxArrayString & groups)34 PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print, const wxArrayString &groups)
35 : MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), wxID_NONE)
36 , txt_filename(new wxTextCtrl(this, wxID_ANY))
37 , box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _L("Start printing after upload")) : nullptr)
38 , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
39 {
40 #ifdef __APPLE__
41 txt_filename->OSXDisableAllSmartSubstitutions();
42 #endif
43 const AppConfig *app_config = wxGetApp().app_config;
44
45 auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
46 label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
47
48 content_sizer->Add(txt_filename, 0, wxEXPAND);
49 content_sizer->Add(label_dir_hint);
50 content_sizer->AddSpacer(VERT_SPACING);
51 if (box_print != nullptr) {
52 content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
53 box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1");
54 }
55
56 if (combo_groups != nullptr) {
57 // Repetier specific: Show a selection of file groups.
58 auto *label_group = new wxStaticText(this, wxID_ANY, _L("Group"));
59 content_sizer->Add(label_group);
60 content_sizer->Add(combo_groups, 0, wxBOTTOM, 2*VERT_SPACING);
61 wxString recent_group = from_u8(app_config->get("recent", CONFIG_KEY_GROUP));
62 if (! recent_group.empty())
63 combo_groups->SetValue(recent_group);
64 }
65
66 btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
67
68 wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
69 if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
70 recent_path += '/';
71 }
72 const auto recent_path_len = recent_path.Length();
73 recent_path += path.filename().wstring();
74 wxString stem(path.stem().wstring());
75 const auto stem_len = stem.Length();
76
77 txt_filename->SetValue(recent_path);
78 txt_filename->SetFocus();
79
80 Fit();
81 CenterOnParent();
82
83 #ifdef __linux__
84 // On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
85 // and as a result the text is invisible with light mode
86 // see https://github.com/prusa3d/PrusaSlicer/issues/4532
87 // Workaround: Unselect text selection explicitly on kill focus
88 txt_filename->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) {
89 e.Skip();
90 txt_filename->SetInsertionPoint(txt_filename->GetLastPosition());
91 }, txt_filename->GetId());
92 #endif /* __linux__ */
93
94 Bind(wxEVT_SHOW, [=](const wxShowEvent &) {
95 // Another similar case where the function only works with EVT_SHOW + CallAfter,
96 // this time on Mac.
97 CallAfter([=]() {
98 txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
99 });
100 });
101 }
102
filename() const103 fs::path PrintHostSendDialog::filename() const
104 {
105 return into_path(txt_filename->GetValue());
106 }
107
start_print() const108 bool PrintHostSendDialog::start_print() const
109 {
110 return box_print != nullptr ? box_print->GetValue() : false;
111 }
112
group() const113 std::string PrintHostSendDialog::group() const
114 {
115 if (combo_groups == nullptr) {
116 return "";
117 } else {
118 wxString group = combo_groups->GetValue();
119 return into_u8(group);
120 }
121 }
122
EndModal(int ret)123 void PrintHostSendDialog::EndModal(int ret)
124 {
125 if (ret == wxID_OK) {
126 // Persist path and print settings
127 wxString path = txt_filename->GetValue();
128 int last_slash = path.Find('/', true);
129 if (last_slash == wxNOT_FOUND)
130 path.clear();
131 else
132 path = path.SubString(0, last_slash);
133
134 AppConfig *app_config = wxGetApp().app_config;
135 app_config->set("recent", CONFIG_KEY_PATH, into_u8(path));
136 app_config->set("recent", CONFIG_KEY_PRINT, start_print() ? "1" : "0");
137
138 if (combo_groups != nullptr) {
139 wxString group = combo_groups->GetValue();
140 app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group));
141 }
142 }
143
144 MsgDialog::EndModal(ret);
145 }
146
147
148
149 wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
150 wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
151 wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
152
Event(wxEventType eventType,int winid,size_t job_id)153 PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
154 : wxEvent(winid, eventType)
155 , job_id(job_id)
156 {}
157
Event(wxEventType eventType,int winid,size_t job_id,int progress)158 PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress)
159 : wxEvent(winid, eventType)
160 , job_id(job_id)
161 , progress(progress)
162 {}
163
Event(wxEventType eventType,int winid,size_t job_id,wxString error)164 PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
165 : wxEvent(winid, eventType)
166 , job_id(job_id)
167 , error(std::move(error))
168 {}
169
Clone() const170 wxEvent *PrintHostQueueDialog::Event::Clone() const
171 {
172 return new Event(*this);
173 }
174
PrintHostQueueDialog(wxWindow * parent)175 PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
176 : DPIDialog(parent, wxID_ANY, _L("Print host upload queue"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
177 , on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
178 , on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
179 , on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this)
180 {
181 const auto em = GetTextExtent("m").x;
182
183 auto *topsizer = new wxBoxSizer(wxVERTICAL);
184
185 job_list = new wxDataViewListCtrl(this, wxID_ANY);
186 // Note: Keep these in sync with Column
187 job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT);
188 job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT);
189 job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT);
190 job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT);
191 job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT);
192 job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
193
194 auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
195 btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));
196 btn_cancel->Disable();
197 btn_error = new wxButton(this, wxID_ANY, _L("Show error message"));
198 btn_error->Disable();
199 // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
200 auto *btn_close = new wxButton(this, wxID_CANCEL, _L("Close"));
201 btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING);
202 btnsizer->Add(btn_error, 0);
203 btnsizer->AddStretchSpacer();
204 btnsizer->Add(btn_close);
205
206 topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING);
207 topsizer->Add(btnsizer, 0, wxEXPAND);
208 SetSizer(topsizer);
209
210 SetSize(wxSize(HEIGHT * em, WIDTH * em));
211
212 job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); });
213
214 btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
215 int selected = job_list->GetSelectedRow();
216 if (selected == wxNOT_FOUND) { return; }
217
218 const JobState state = get_state(selected);
219 if (state < ST_ERROR) {
220 // TODO: cancel
221 GUI::wxGetApp().printhost_job_queue().cancel(selected);
222 }
223 });
224
225 btn_error->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
226 int selected = job_list->GetSelectedRow();
227 if (selected == wxNOT_FOUND) { return; }
228 GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG));
229 });
230 }
231
append_job(const PrintHostJob & job)232 void PrintHostQueueDialog::append_job(const PrintHostJob &job)
233 {
234 wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job");
235
236 wxVector<wxVariant> fields;
237 fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1)));
238 fields.push_back(wxVariant(0));
239 fields.push_back(wxVariant(_L("Enqueued")));
240 fields.push_back(wxVariant(job.printhost->get_host()));
241 fields.push_back(wxVariant(job.upload_data.upload_path.string()));
242 fields.push_back(wxVariant(""));
243 job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
244 // Both strings are UTF-8 encoded.
245 upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string());
246 }
247
on_dpi_changed(const wxRect & suggested_rect)248 void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect)
249 {
250 const int& em = em_unit();
251
252 msw_buttons_rescale(this, em, { wxID_DELETE, wxID_CANCEL, btn_error->GetId() });
253
254 SetMinSize(wxSize(HEIGHT * em, WIDTH * em));
255
256 Fit();
257 Refresh();
258 }
259
get_state(int idx)260 PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx)
261 {
262 wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list");
263 return static_cast<JobState>(job_list->GetItemData(job_list->RowToItem(idx)));
264 }
265
set_state(int idx,JobState state)266 void PrintHostQueueDialog::set_state(int idx, JobState state)
267 {
268 wxCHECK_RET(idx >= 0 && idx < job_list->GetItemCount(), "Out of bounds access to job list");
269 job_list->SetItemData(job_list->RowToItem(idx), static_cast<wxUIntPtr>(state));
270
271 switch (state) {
272 case ST_NEW: job_list->SetValue(_L("Enqueued"), idx, COL_STATUS); break;
273 case ST_PROGRESS: job_list->SetValue(_L("Uploading"), idx, COL_STATUS); break;
274 case ST_ERROR: job_list->SetValue(_L("Error"), idx, COL_STATUS); break;
275 case ST_CANCELLING: job_list->SetValue(_L("Cancelling"), idx, COL_STATUS); break;
276 case ST_CANCELLED: job_list->SetValue(_L("Cancelled"), idx, COL_STATUS); break;
277 case ST_COMPLETED: job_list->SetValue(_L("Completed"), idx, COL_STATUS); break;
278 }
279 }
280
on_list_select()281 void PrintHostQueueDialog::on_list_select()
282 {
283 int selected = job_list->GetSelectedRow();
284 if (selected != wxNOT_FOUND) {
285 const JobState state = get_state(selected);
286 btn_cancel->Enable(state < ST_ERROR);
287 btn_error->Enable(state == ST_ERROR);
288 Layout();
289 } else {
290 btn_cancel->Disable();
291 }
292 }
293
on_progress(Event & evt)294 void PrintHostQueueDialog::on_progress(Event &evt)
295 {
296 wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
297
298 if (evt.progress < 100) {
299 set_state(evt.job_id, ST_PROGRESS);
300 job_list->SetValue(wxVariant(evt.progress), evt.job_id, COL_PROGRESS);
301 } else {
302 set_state(evt.job_id, ST_COMPLETED);
303 job_list->SetValue(wxVariant(100), evt.job_id, COL_PROGRESS);
304 }
305
306 on_list_select();
307 }
308
on_error(Event & evt)309 void PrintHostQueueDialog::on_error(Event &evt)
310 {
311 wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
312
313 set_state(evt.job_id, ST_ERROR);
314
315 auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.error.ToUTF8())).str());
316 job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
317 job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG); // Stashes the error message into a hidden column for later
318
319 on_list_select();
320
321 GUI::show_error(nullptr, errormsg);
322 }
323
on_cancel(Event & evt)324 void PrintHostQueueDialog::on_cancel(Event &evt)
325 {
326 wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
327
328 set_state(evt.job_id, ST_CANCELLED);
329 job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
330
331 on_list_select();
332 }
get_active_jobs(std::vector<std::pair<std::string,std::string>> & ret)333 void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret)
334 {
335 int ic = job_list->GetItemCount();
336 for (size_t i = 0; i < ic; i++)
337 {
338 auto item = job_list->RowToItem(i);
339 auto data = job_list->GetItemData(item);
340 JobState st = static_cast<JobState>(data);
341 if(st == JobState::ST_NEW || st == JobState::ST_PROGRESS)
342 ret.emplace_back(upload_names[i]);
343 }
344 //job_list->data
345 }
346 }}
347