1 #include "libslic3r/libslic3r.h"
2 #include "DoubleSlider.hpp"
3 #include "libslic3r/GCode.hpp"
4 #include "GUI.hpp"
5 #include "GUI_App.hpp"
6 #include "Plater.hpp"
7 #include "I18N.hpp"
8 #include "ExtruderSequenceDialog.hpp"
9 #include "libslic3r/Print.hpp"
10 #include "libslic3r/AppConfig.hpp"
11 #include "GUI_Utils.hpp"
12
13 #include <wx/button.h>
14 #include <wx/dialog.h>
15 #include <wx/sizer.h>
16 #include <wx/slider.h>
17 #include <wx/menu.h>
18 #include <wx/bmpcbox.h>
19 #include <wx/statline.h>
20 #include <wx/dcclient.h>
21 #include <wx/colordlg.h>
22
23 #include <cmath>
24 #include <boost/algorithm/string/replace.hpp>
25 #include "Field.hpp"
26 #include "format.hpp"
27 #include "NotificationManager.hpp"
28
29 namespace Slic3r {
30
31 using GUI::from_u8;
32 using GUI::into_u8;
33 using GUI::format_wxstr;
34
35 namespace DoubleSlider {
36
37 wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
38
gcode(Type type)39 static std::string gcode(Type type)
40 {
41 const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config();
42 switch (type) {
43 case ColorChange: return config.color_change_gcode;
44 case PausePrint: return config.pause_print_gcode;
45 case Template: return config.template_custom_gcode;
46 default: return "";
47 }
48 }
49
Control(wxWindow * parent,wxWindowID id,int lowerValue,int higherValue,int minValue,int maxValue,const wxPoint & pos,const wxSize & size,long style,const wxValidator & val,const wxString & name)50 Control::Control( wxWindow *parent,
51 wxWindowID id,
52 int lowerValue,
53 int higherValue,
54 int minValue,
55 int maxValue,
56 const wxPoint& pos,
57 const wxSize& size,
58 long style,
59 const wxValidator& val,
60 const wxString& name) :
61 wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE),
62 m_lower_value(lowerValue),
63 m_higher_value (higherValue),
64 m_min_value(minValue),
65 m_max_value(maxValue),
66 m_style(style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style: wxSL_HORIZONTAL),
67 m_extra_style(style == wxSL_VERTICAL ? wxSL_AUTOTICKS | wxSL_VALUE_LABEL : 0)
68 {
69 #ifdef __WXOSX__
70 is_osx = true;
71 #endif //__WXOSX__
72 if (!is_osx)
73 SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
74
75 m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up"));
76 m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down"));
77 m_thumb_size = m_bmp_thumb_lower.GetBmpSize();
78
79 m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
80 m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f");
81 m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del");
82 m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f");
83 m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth();
84
85 m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed");
86 m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f");
87 m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open");
88 m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f");
89 m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth();
90
91 m_bmp_revert = ScalableBitmap(this, "undo");
92 m_revert_icon_dim = m_bmp_revert.GetBmpWidth();
93 m_bmp_cog = ScalableBitmap(this, "cog");
94 m_cog_icon_dim = m_bmp_cog.GetBmpWidth();
95
96 m_selection = ssUndef;
97 m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume printing")));
98 m_ticks.set_extruder_colors(&m_extruder_colors);
99
100 // slider events
101 this->Bind(wxEVT_PAINT, &Control::OnPaint, this);
102 this->Bind(wxEVT_CHAR, &Control::OnChar, this);
103 this->Bind(wxEVT_LEFT_DOWN, &Control::OnLeftDown, this);
104 this->Bind(wxEVT_MOTION, &Control::OnMotion, this);
105 this->Bind(wxEVT_LEFT_UP, &Control::OnLeftUp, this);
106 this->Bind(wxEVT_MOUSEWHEEL, &Control::OnWheel, this);
107 this->Bind(wxEVT_ENTER_WINDOW,&Control::OnEnterWin, this);
108 this->Bind(wxEVT_LEAVE_WINDOW,&Control::OnLeaveWin, this);
109 this->Bind(wxEVT_KEY_DOWN, &Control::OnKeyDown, this);
110 this->Bind(wxEVT_KEY_UP, &Control::OnKeyUp, this);
111 this->Bind(wxEVT_RIGHT_DOWN, &Control::OnRightDown,this);
112 this->Bind(wxEVT_RIGHT_UP, &Control::OnRightUp, this);
113
114 // control's view variables
115 SLIDER_MARGIN = 4 + GUI::wxGetApp().em_unit();
116
117 DARK_ORANGE_PEN = wxPen(wxColour(237, 107, 33));
118 ORANGE_PEN = wxPen(wxColour(253, 126, 66));
119 LIGHT_ORANGE_PEN = wxPen(wxColour(254, 177, 139));
120
121 DARK_GREY_PEN = wxPen(wxColour(128, 128, 128));
122 GREY_PEN = wxPen(wxColour(164, 164, 164));
123 LIGHT_GREY_PEN = wxPen(wxColour(204, 204, 204));
124
125 m_line_pens = { &DARK_GREY_PEN, &GREY_PEN, &LIGHT_GREY_PEN };
126 m_segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN };
127
128 m_font = GetFont();
129 this->SetMinSize(get_min_size());
130 }
131
msw_rescale()132 void Control::msw_rescale()
133 {
134 m_font = GUI::wxGetApp().normal_font();
135
136 m_bmp_thumb_higher.msw_rescale();
137 m_bmp_thumb_lower .msw_rescale();
138 m_thumb_size = m_bmp_thumb_lower.bmp().GetSize();
139
140 m_bmp_add_tick_on .msw_rescale();
141 m_bmp_add_tick_off.msw_rescale();
142 m_bmp_del_tick_on .msw_rescale();
143 m_bmp_del_tick_off.msw_rescale();
144 m_tick_icon_dim = m_bmp_add_tick_on.bmp().GetSize().x;
145
146 m_bmp_one_layer_lock_on .msw_rescale();
147 m_bmp_one_layer_lock_off .msw_rescale();
148 m_bmp_one_layer_unlock_on .msw_rescale();
149 m_bmp_one_layer_unlock_off.msw_rescale();
150 m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x;
151
152 m_bmp_revert.msw_rescale();
153 m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
154 m_bmp_cog.msw_rescale();
155 m_cog_icon_dim = m_bmp_cog.bmp().GetSize().x;
156
157 SLIDER_MARGIN = 4 + GUI::wxGetApp().em_unit();
158
159 SetMinSize(get_min_size());
160 GetParent()->Layout();
161 }
162
sys_color_changed()163 void Control::sys_color_changed()
164 {
165 m_bmp_add_tick_on .msw_rescale();
166 m_bmp_add_tick_off.msw_rescale();
167 m_bmp_del_tick_on .msw_rescale();
168 m_bmp_del_tick_off.msw_rescale();
169 m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth();
170
171 m_bmp_one_layer_lock_on .msw_rescale();
172 m_bmp_one_layer_lock_off .msw_rescale();
173 m_bmp_one_layer_unlock_on .msw_rescale();
174 m_bmp_one_layer_unlock_off.msw_rescale();
175 m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth();
176
177 m_bmp_revert.msw_rescale();
178 m_revert_icon_dim = m_bmp_revert.GetBmpWidth();
179 m_bmp_cog.msw_rescale();
180 m_cog_icon_dim = m_bmp_cog.GetBmpWidth();
181 }
182
GetActiveValue() const183 int Control::GetActiveValue() const
184 {
185 return m_selection == ssLower ?
186 m_lower_value : m_selection == ssHigher ?
187 m_higher_value : -1;
188 }
189
get_min_size() const190 wxSize Control::get_min_size() const
191 {
192 const int min_side = GUI::wxGetApp().em_unit() * ( is_horizontal() ? 5 : 11 );
193 return wxSize(min_side, min_side);
194 }
195
DoGetBestSize() const196 wxSize Control::DoGetBestSize() const
197 {
198 const wxSize size = wxControl::DoGetBestSize();
199 if (size.x > 1 && size.y > 1)
200 return size;
201 return get_min_size();
202 }
203
SetLowerValue(const int lower_val)204 void Control::SetLowerValue(const int lower_val)
205 {
206 m_selection = ssLower;
207 m_lower_value = lower_val;
208 correct_lower_value();
209 Refresh();
210 Update();
211
212 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
213 e.SetEventObject(this);
214 ProcessWindowEvent(e);
215 }
216
SetHigherValue(const int higher_val)217 void Control::SetHigherValue(const int higher_val)
218 {
219 m_selection = ssHigher;
220 m_higher_value = higher_val;
221 correct_higher_value();
222 Refresh();
223 Update();
224
225 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
226 e.SetEventObject(this);
227 ProcessWindowEvent(e);
228 }
229
SetSelectionSpan(const int lower_val,const int higher_val)230 void Control::SetSelectionSpan(const int lower_val, const int higher_val)
231 {
232 m_lower_value = std::max(lower_val, m_min_value);
233 m_higher_value = std::max(std::min(higher_val, m_max_value), m_lower_value);
234 if (m_lower_value < m_higher_value)
235 m_is_one_layer = false;
236
237 Refresh();
238 Update();
239
240 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
241 e.SetEventObject(this);
242 ProcessWindowEvent(e);
243 }
244
SetMaxValue(const int max_value)245 void Control::SetMaxValue(const int max_value)
246 {
247 m_max_value = max_value;
248 Refresh();
249 Update();
250 }
251
SetSliderValues(const std::vector<double> & values)252 void Control::SetSliderValues(const std::vector<double>& values)
253 {
254 m_values = values;
255 m_ruler.count = std::count(m_values.begin(), m_values.end(), m_values.front());
256 }
257
draw_scroll_line(wxDC & dc,const int lower_pos,const int higher_pos)258 void Control::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos)
259 {
260 int width;
261 int height;
262 get_size(&width, &height);
263
264 wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1;
265 wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN;
266 wxCoord line_end_x = is_horizontal() ? width - SLIDER_MARGIN + 1 : width*0.5 - 1;
267 wxCoord line_end_y = is_horizontal() ? height*0.5 - 1 : height - SLIDER_MARGIN + 1;
268
269 wxCoord segm_beg_x = is_horizontal() ? lower_pos : width*0.5 - 1;
270 wxCoord segm_beg_y = is_horizontal() ? height*0.5 - 1 : lower_pos/*-1*/;
271 wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1;
272 wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1;
273
274 for (size_t id = 0; id < m_line_pens.size(); id++) {
275 dc.SetPen(*m_line_pens[id]);
276 dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y);
277 dc.SetPen(*m_segm_pens[id]);
278 dc.DrawLine(segm_beg_x, segm_beg_y, segm_end_x, segm_end_y);
279 if (is_horizontal())
280 line_beg_y = line_end_y = segm_beg_y = segm_end_y += 1;
281 else
282 line_beg_x = line_end_x = segm_beg_x = segm_end_x += 1;
283 }
284 }
285
get_scroll_step()286 double Control::get_scroll_step()
287 {
288 const wxSize sz = get_size();
289 const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y;
290 return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value);
291 }
292
293 // get position on the slider line from entered value
get_position_from_value(const int value)294 wxCoord Control::get_position_from_value(const int value)
295 {
296 const double step = get_scroll_step();
297 const int val = is_horizontal() ? value : m_max_value - value;
298 return wxCoord(SLIDER_MARGIN + int(val*step + 0.5));
299 }
300
get_size() const301 wxSize Control::get_size() const
302 {
303 int w, h;
304 get_size(&w, &h);
305 return wxSize(w, h);
306 }
307
get_size(int * w,int * h) const308 void Control::get_size(int* w, int* h) const
309 {
310 GetSize(w, h);
311 if (m_draw_mode == dmSequentialGCodeView)
312 return; // we have no more icons for drawing
313 is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim;
314 }
315
get_double_value(const SelectedSlider & selection)316 double Control::get_double_value(const SelectedSlider& selection)
317 {
318 if (m_values.empty() || m_lower_value<0)
319 return 0.0;
320 if (m_values.size() <= size_t(m_higher_value)) {
321 correct_higher_value();
322 return m_values.back();
323 }
324 return m_values[selection == ssLower ? m_lower_value : m_higher_value];
325 }
326
GetTicksValues() const327 Info Control::GetTicksValues() const
328 {
329 Info custom_gcode_per_print_z;
330 std::vector<CustomGCode::Item>& values = custom_gcode_per_print_z.gcodes;
331
332 const int val_size = m_values.size();
333 if (!m_values.empty())
334 for (const TickCode& tick : m_ticks.ticks) {
335 if (tick.tick > val_size)
336 break;
337 values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra });
338 }
339
340 if (m_force_mode_apply)
341 custom_gcode_per_print_z.mode = m_mode;
342
343 return custom_gcode_per_print_z;
344 }
345
SetTicksValues(const Info & custom_gcode_per_print_z)346 void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
347 {
348 if (m_values.empty()) {
349 m_ticks.mode = m_mode;
350 return;
351 }
352
353 const bool was_empty = m_ticks.empty();
354
355 m_ticks.ticks.clear();
356 const std::vector<CustomGCode::Item>& heights = custom_gcode_per_print_z.gcodes;
357 for (auto h : heights) {
358 auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon());
359
360 if (it == m_values.end())
361 continue;
362
363 m_ticks.ticks.emplace(TickCode{int(it-m_values.begin()), h.type, h.extruder, h.color, h.extra});
364 }
365
366 if (!was_empty && m_ticks.empty())
367 // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
368 post_ticks_changed_event();
369
370 if (custom_gcode_per_print_z.mode)
371 m_ticks.mode = custom_gcode_per_print_z.mode;
372
373 Refresh();
374 Update();
375 }
376
SetLayersTimes(const std::vector<float> & layers_times)377 void Control::SetLayersTimes(const std::vector<float>& layers_times)
378 {
379 m_layers_times.clear();
380 if (layers_times.empty())
381 return;
382 m_layers_times.resize(layers_times.size(), 0.0);
383 m_layers_times[0] = layers_times[0];
384 for (size_t i = 1; i < layers_times.size(); i++)
385 m_layers_times[i] = m_layers_times[i - 1] + layers_times[i];
386 }
387
SetLayersTimes(const std::vector<double> & layers_times)388 void Control::SetLayersTimes(const std::vector<double>& layers_times)
389 {
390 m_layers_times = layers_times;
391 for (size_t i = 1; i < m_layers_times.size(); i++)
392 m_layers_times[i] += m_layers_times[i - 1];
393 }
394
SetDrawMode(bool is_sla_print,bool is_sequential_print)395 void Control::SetDrawMode(bool is_sla_print, bool is_sequential_print)
396 {
397 m_draw_mode = is_sla_print ? dmSlaPrint :
398 is_sequential_print ? dmSequentialFffPrint :
399 dmRegular;
400 }
401
SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model,const int only_extruder)402 void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder)
403 {
404 m_mode = !is_one_extruder_printed_model ? MultiExtruder :
405 only_extruder < 0 ? SingleExtruder :
406 MultiAsSingle;
407 if (!m_ticks.mode)
408 m_ticks.mode = m_mode;
409 m_only_extruder = only_extruder;
410
411 UseDefaultColors(m_mode == SingleExtruder);
412 }
413
SetExtruderColors(const std::vector<std::string> & extruder_colors)414 void Control::SetExtruderColors( const std::vector<std::string>& extruder_colors)
415 {
416 m_extruder_colors = extruder_colors;
417 }
418
get_lower_and_higher_position(int & lower_pos,int & higher_pos)419 void Control::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
420 {
421 const double step = get_scroll_step();
422 if (is_horizontal()) {
423 lower_pos = SLIDER_MARGIN + int(m_lower_value*step + 0.5);
424 higher_pos = SLIDER_MARGIN + int(m_higher_value*step + 0.5);
425 }
426 else {
427 lower_pos = SLIDER_MARGIN + int((m_max_value - m_lower_value)*step + 0.5);
428 higher_pos = SLIDER_MARGIN + int((m_max_value - m_higher_value)*step + 0.5);
429 }
430 }
431
draw_focus_rect()432 void Control::draw_focus_rect()
433 {
434 if (!m_is_focused)
435 return;
436 const wxSize sz = GetSize();
437 wxPaintDC dc(this);
438 const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT);
439 dc.SetPen(pen);
440 dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
441 dc.DrawRectangle(1, 1, sz.x - 2, sz.y - 2);
442 }
443
render()444 void Control::render()
445 {
446 #ifdef _WIN32
447 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
448 #else
449 SetBackgroundColour(GetParent()->GetBackgroundColour());
450 #endif // _WIN32
451 draw_focus_rect();
452
453 wxPaintDC dc(this);
454 dc.SetFont(m_font);
455
456 const wxCoord lower_pos = get_position_from_value(m_lower_value);
457 const wxCoord higher_pos = get_position_from_value(m_higher_value);
458
459 // draw colored band on the background of a scroll line
460 // and only in a case of no-empty m_values
461 draw_colored_band(dc);
462
463 if (m_extra_style & wxSL_AUTOTICKS)
464 draw_ruler(dc);
465
466 if (!m_render_as_disabled) {
467 // draw line
468 draw_scroll_line(dc, lower_pos, higher_pos);
469
470 // draw color print ticks
471 draw_ticks(dc);
472
473 // draw both sliders
474 draw_thumbs(dc, lower_pos, higher_pos);
475
476 // draw lock/unlock
477 draw_one_layer_icon(dc);
478
479 // draw revert bitmap (if it's shown)
480 draw_revert_icon(dc);
481
482 // draw cog bitmap (if it's shown)
483 draw_cog_icon(dc);
484
485 // draw mouse position
486 draw_tick_on_mouse_position(dc);
487 }
488 }
489
draw_action_icon(wxDC & dc,const wxPoint pt_beg,const wxPoint pt_end)490 void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
491 {
492 const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
493
494 if (!m_enable_action_icon)
495 return;
496
497 // suppress add tick on first layer
498 if (tick == 0)
499 return;
500
501 wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp();
502 if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end())
503 icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp();
504
505 wxCoord x_draw, y_draw;
506 is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim;
507 if (m_selection == ssLower)
508 is_horizontal() ? y_draw = pt_end.y + 3 : x_draw = pt_beg.x - m_tick_icon_dim-2;
509 else
510 is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3;
511
512 dc.DrawBitmap(*icon, x_draw, y_draw);
513
514 //update rect of the tick action icon
515 m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim);
516 }
517
draw_info_line_with_icon(wxDC & dc,const wxPoint & pos,const SelectedSlider selection)518 void Control::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const SelectedSlider selection)
519 {
520 if (m_selection == selection) {
521 //draw info line
522 dc.SetPen(DARK_ORANGE_PEN);
523 const wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x, pos.y/* - 1*/);
524 const wxPoint pt_end = is_horizontal() ? wxPoint(pos.x, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x, pos.y/* - 1*/);
525 dc.DrawLine(pt_beg, pt_end);
526
527 //draw action icon
528 if (m_draw_mode == dmRegular)
529 draw_action_icon(dc, pt_beg, pt_end);
530 }
531 }
532
draw_tick_on_mouse_position(wxDC & dc)533 void Control::draw_tick_on_mouse_position(wxDC& dc)
534 {
535 if (!m_is_focused || m_moving_pos == wxDefaultPosition)
536 return;
537
538 //calculate thumb position on slider line
539 int width, height;
540 get_size(&width, &height);
541
542 int tick = get_tick_near_point(m_moving_pos);
543 if (tick == m_higher_value || tick == m_lower_value)
544 return ;
545
546 auto draw_ticks = [this](wxDC& dc, wxPoint pos, int margin=0 )
547 {
548 wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x+margin, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x , pos.y+margin);
549 wxPoint pt_end = is_horizontal() ? wxPoint(pos.x+margin, pos.y + m_thumb_size.y) : wxPoint(pos.x - 0.5 * m_thumb_size.x + 1, pos.y+margin);
550 dc.DrawLine(pt_beg, pt_end);
551
552 pt_beg = is_horizontal() ? wxPoint(pos.x + margin, pos.y - m_thumb_size.y) : wxPoint(pos.x + 0.5 * m_thumb_size.x, pos.y+margin);
553 pt_end = is_horizontal() ? wxPoint(pos.x + margin, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x + 1, pos.y+margin);
554 dc.DrawLine(pt_beg, pt_end);
555 };
556
557 auto draw_touch = [this](wxDC& dc, wxPoint pos, int margin, bool right_side )
558 {
559 int mult = right_side ? 1 : -1;
560 wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x - margin, pos.y + mult * m_thumb_size.y) : wxPoint(pos.x + mult * m_thumb_size.x, pos.y - margin);
561 wxPoint pt_end = is_horizontal() ? wxPoint(pos.x + margin, pos.y + mult * m_thumb_size.y) : wxPoint(pos.x + mult * m_thumb_size.x, pos.y + margin);
562 dc.DrawLine(pt_beg, pt_end);
563 };
564
565 if (tick > 0) // this tick exists and should be marked as a focused
566 {
567 wxCoord new_pos = get_position_from_value(tick);
568 const wxPoint pos = is_horizontal() ? wxPoint(new_pos, height * 0.5) : wxPoint(0.5 * width, new_pos);
569
570 dc.SetPen(DARK_ORANGE_PEN);
571
572 draw_ticks(dc, pos, -2);
573 draw_ticks(dc, pos, 2 );
574 draw_touch(dc, pos, 2, true);
575 draw_touch(dc, pos, 2, false);
576
577 return;
578 }
579
580 tick = get_value_from_position(m_moving_pos);
581 if (tick > m_max_value || tick < m_min_value || tick == m_higher_value || tick == m_lower_value)
582 return;
583
584 wxCoord new_pos = get_position_from_value(tick);
585 const wxPoint pos = is_horizontal() ? wxPoint(new_pos, height * 0.5) : wxPoint(0.5 * width, new_pos);
586
587 //draw info line
588 dc.SetPen(LIGHT_GREY_PEN);
589 draw_ticks(dc, pos);
590
591 if (m_extra_style & wxSL_VALUE_LABEL) {
592 wxColour old_clr = dc.GetTextForeground();
593 dc.SetTextForeground(GREY_PEN.GetColour());
594 draw_tick_text(dc, pos, tick, ltEstimatedTime, false);
595 dc.SetTextForeground(old_clr);
596 }
597 }
598
short_and_splitted_time(const std::string & time)599 static std::string short_and_splitted_time(const std::string& time)
600 {
601 // Parse the dhms time format.
602 int days = 0;
603 int hours = 0;
604 int minutes = 0;
605 int seconds = 0;
606 if (time.find('d') != std::string::npos)
607 ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
608 else if (time.find('h') != std::string::npos)
609 ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
610 else if (time.find('m') != std::string::npos)
611 ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
612 else if (time.find('s') != std::string::npos)
613 ::sscanf(time.c_str(), "%ds", &seconds);
614
615 // Format the dhm time.
616 char buffer[64];
617 if (days > 0)
618 ::sprintf(buffer, "%dd%dh\n%dm", days, hours, minutes);
619 else if (hours > 0) {
620 if (hours < 10 && minutes < 10 && seconds < 10)
621 ::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds);
622 else if (hours > 10 && minutes > 10 && seconds > 10)
623 ::sprintf(buffer, "%dh\n%dm\n%ds", hours, minutes, seconds);
624 else if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10))
625 ::sprintf(buffer, "%dh\n%dm%ds", hours, minutes, seconds);
626 else
627 ::sprintf(buffer, "%dh%dm\n%ds", hours, minutes, seconds);
628 }
629 else if (minutes > 0) {
630 if (minutes > 10 && seconds > 10)
631 ::sprintf(buffer, "%dm\n%ds", minutes, seconds);
632 else
633 ::sprintf(buffer, "%dm%ds", minutes, seconds);
634 }
635 else
636 ::sprintf(buffer, "%ds", seconds);
637 return buffer;
638 }
639
get_label(int tick,LabelType label_type) const640 wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer*/) const
641 {
642 const size_t value = tick;
643
644 if (m_label_koef == 1.0 && m_values.empty())
645 return wxString::Format("%lu", static_cast<unsigned long>(value));
646 if (value >= m_values.size())
647 return "ErrVal";
648
649 if (m_draw_mode == dmSequentialGCodeView)
650 return wxString::Format("%lu", static_cast<unsigned long>(m_values[value]));
651 else {
652 if (label_type == ltEstimatedTime) {
653 return (value < m_layers_times.size()) ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : "";
654 }
655 wxString str = m_values.empty() ?
656 wxString::Format("%.*f", 2, m_label_koef * value) :
657 wxString::Format("%.*f", 2, m_values[value]);
658 if (label_type == ltHeight)
659 return str;
660 if (label_type == ltHeightWithLayer)
661 return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1);
662 }
663
664 return wxEmptyString;
665 }
666
draw_tick_text(wxDC & dc,const wxPoint & pos,int tick,LabelType label_type,bool right_side) const667 void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, LabelType label_type/* = ltHeight*/, bool right_side/*=true*/) const
668 {
669 wxCoord text_width, text_height;
670 const wxString label = get_label(tick, label_type);
671 dc.GetMultiLineTextExtent(label, &text_width, &text_height);
672 wxPoint text_pos;
673 if (right_side) {
674 if (is_horizontal()) {
675 int width;
676 int height;
677 get_size(&width, &height);
678
679 int x_right = pos.x + 1 + text_width;
680 int xx = (x_right < width) ? pos.x + 1 : pos.x - text_width - 1;
681 text_pos = wxPoint(xx, pos.y + m_thumb_size.x / 2 + 1);
682 }
683 else
684 text_pos = wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1);
685 }
686 else {
687 if (is_horizontal()) {
688 int x = pos.x - text_width - 1;
689 int xx = (x > 0) ? x : pos.x + 1;
690 text_pos = wxPoint(xx, pos.y - m_thumb_size.x / 2 - text_height - 1);
691 }
692 else
693 text_pos = wxPoint(std::max(2, pos.x - text_width - 1 - m_thumb_size.x), pos.y - 0.5 * text_height + 1);
694 }
695
696 if (label_type == ltEstimatedTime)
697 dc.DrawLabel(label, wxRect(text_pos, wxSize(text_width, text_height)), wxALIGN_RIGHT);
698 else
699 dc.DrawText(label, text_pos);
700 }
701
draw_thumb_text(wxDC & dc,const wxPoint & pos,const SelectedSlider & selection) const702 void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
703 {
704 draw_tick_text(dc, pos, selection == ssLower ? m_lower_value : m_higher_value, ltHeightWithLayer, selection == ssLower);
705 }
706
draw_thumb_item(wxDC & dc,const wxPoint & pos,const SelectedSlider & selection)707 void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
708 {
709 wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x);
710 wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y);
711 dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
712
713 // Update thumb rect
714 update_thumb_rect(x_draw, y_draw, selection);
715 }
716
draw_thumb(wxDC & dc,const wxCoord & pos_coord,const SelectedSlider & selection)717 void Control::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection)
718 {
719 //calculate thumb position on slider line
720 int width, height;
721 get_size(&width, &height);
722 const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord);
723
724 // Draw thumb
725 draw_thumb_item(dc, pos, selection);
726
727 // Draw info_line
728 draw_info_line_with_icon(dc, pos, selection);
729
730 // Draw thumb text
731 draw_thumb_text(dc, pos, selection);
732 }
733
draw_thumbs(wxDC & dc,const wxCoord & lower_pos,const wxCoord & higher_pos)734 void Control::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos)
735 {
736 //calculate thumb position on slider line
737 int width, height;
738 get_size(&width, &height);
739 const wxPoint pos_l = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos);
740 const wxPoint pos_h = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos);
741
742 // Draw lower thumb
743 draw_thumb_item(dc, pos_l, ssLower);
744 // Draw lower info_line
745 draw_info_line_with_icon(dc, pos_l, ssLower);
746
747 // Draw higher thumb
748 draw_thumb_item(dc, pos_h, ssHigher);
749 // Draw higher info_line
750 draw_info_line_with_icon(dc, pos_h, ssHigher);
751 // Draw higher thumb text
752 draw_thumb_text(dc, pos_h, ssHigher);
753
754 // Draw lower thumb text
755 draw_thumb_text(dc, pos_l, ssLower);
756 }
757
draw_ticks_pair(wxDC & dc,wxCoord pos,wxCoord mid,int tick_len)758 void Control::draw_ticks_pair(wxDC& dc, wxCoord pos, wxCoord mid, int tick_len)
759 {
760 int mid_space = 9;
761 is_horizontal() ? dc.DrawLine(pos, mid - (mid_space + tick_len), pos, mid - mid_space) :
762 dc.DrawLine(mid - (mid_space + tick_len), pos, mid - mid_space, pos);
763 is_horizontal() ? dc.DrawLine(pos, mid + (mid_space + tick_len), pos, mid + mid_space) :
764 dc.DrawLine(mid + (mid_space + tick_len), pos, mid + mid_space, pos);
765 }
766
draw_ticks(wxDC & dc)767 void Control::draw_ticks(wxDC& dc)
768 {
769 if (m_draw_mode == dmSlaPrint)
770 return;
771
772 dc.SetPen(m_draw_mode == dmRegular ? DARK_GREY_PEN : LIGHT_GREY_PEN );
773 int height, width;
774 get_size(&width, &height);
775 const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width;
776 for (const TickCode& tick : m_ticks.ticks) {
777 if (size_t(tick.tick) >= m_values.size()) {
778 // The case when OnPaint is called before m_ticks.ticks data are updated (specific for the vase mode)
779 break;
780 }
781
782 const wxCoord pos = get_position_from_value(tick.tick);
783 draw_ticks_pair(dc, pos, mid, 7);
784
785 // if current tick if focused, we should to use a specific "focused" icon
786 bool focused_tick = m_moving_pos != wxDefaultPosition && tick.tick == get_tick_near_point(m_moving_pos);
787
788 // get icon name if it is
789 std::string icon_name;
790
791 // if we have non-regular draw mode, all ticks should be marked with error icon
792 if (m_draw_mode != dmRegular)
793 icon_name = focused_tick ? "error_tick_f" : "error_tick";
794 else if (tick.type == ColorChange || tick.type == ToolChange) {
795 if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick]))
796 icon_name = focused_tick ? "error_tick_f" : "error_tick";
797 }
798 else if (tick.type == PausePrint)
799 icon_name = focused_tick ? "pause_print_f" : "pause_print";
800 else
801 icon_name = focused_tick ? "edit_gcode_f" : "edit_gcode";
802
803 // Draw icon for "Pause print", "Custom Gcode" or conflict tick
804 if (!icon_name.empty()) {
805 wxBitmap icon = create_scaled_bitmap(icon_name);
806 wxCoord x_draw, y_draw;
807 is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim;
808 is_horizontal() ? y_draw = mid + 22 : x_draw = mid + m_thumb_size.x + 3;
809
810 dc.DrawBitmap(icon, x_draw, y_draw);
811 }
812 }
813 }
814
get_color_for_tool_change_tick(std::set<TickCode>::const_iterator it) const815 std::string Control::get_color_for_tool_change_tick(std::set<TickCode>::const_iterator it) const
816 {
817 const int current_extruder = it->extruder == 0 ? std::max<int>(m_only_extruder, 1) : it->extruder;
818
819 auto it_n = it;
820 while (it_n != m_ticks.ticks.begin()) {
821 --it_n;
822 if (it_n->type == ColorChange && it_n->extruder == current_extruder)
823 return it_n->color;
824 }
825
826 return m_extruder_colors[current_extruder-1]; // return a color for a specific extruder from the colors list
827 }
828
get_color_for_color_change_tick(std::set<TickCode>::const_iterator it) const829 std::string Control::get_color_for_color_change_tick(std::set<TickCode>::const_iterator it) const
830 {
831 const int def_extruder = std::max<int>(1, m_only_extruder);
832 auto it_n = it;
833 bool is_tool_change = false;
834 while (it_n != m_ticks.ticks.begin()) {
835 --it_n;
836 if (it_n->type == ToolChange) {
837 is_tool_change = true;
838 if (it_n->extruder == it->extruder)
839 return it->color;
840 break;
841 }
842 if (it_n->type == ColorChange && it_n->extruder == it->extruder)
843 return it->color;
844 }
845 if (!is_tool_change && it->extruder == def_extruder)
846 return it->color;
847
848 return "";
849 }
850
get_colored_band_rect()851 wxRect Control::get_colored_band_rect()
852 {
853 int height, width;
854 get_size(&width, &height);
855
856 const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width;
857
858 return is_horizontal() ?
859 wxRect(SLIDER_MARGIN, lround(mid - 0.375 * m_thumb_size.y),
860 width - 2 * SLIDER_MARGIN + 1, lround(0.75 * m_thumb_size.y)) :
861 wxRect(lround(mid - 0.375 * m_thumb_size.x), SLIDER_MARGIN,
862 lround(0.75 * m_thumb_size.x), height - 2 * SLIDER_MARGIN + 1);
863 }
864
draw_colored_band(wxDC & dc)865 void Control::draw_colored_band(wxDC& dc)
866 {
867 if (m_draw_mode != dmRegular)
868 return;
869
870 auto draw_band = [](wxDC& dc, const wxColour& clr, const wxRect& band_rc)
871 {
872 dc.SetPen(clr);
873 dc.SetBrush(clr);
874 dc.DrawRectangle(band_rc);
875 };
876
877 wxRect main_band = get_colored_band_rect();
878
879 // don't color a band for MultiExtruder mode
880 if (m_ticks.empty() || m_mode == MultiExtruder) {
881 #ifdef _WIN32
882 draw_band(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), main_band);
883 #else
884 draw_band(dc, GetParent()->GetBackgroundColour(), main_band);
885 #endif // _WIN32
886 return;
887 }
888
889 const int default_color_idx = m_mode==MultiAsSingle ? std::max<int>(m_only_extruder - 1, 0) : 0;
890 draw_band(dc, wxColour(m_extruder_colors[default_color_idx]), main_band);
891
892 std::set<TickCode>::const_iterator tick_it = m_ticks.ticks.begin();
893
894 while (tick_it != m_ticks.ticks.end())
895 {
896 if ( (m_mode == SingleExtruder && tick_it->type == ColorChange ) ||
897 (m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange)) )
898 {
899 const wxCoord pos = get_position_from_value(tick_it->tick);
900 is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) :
901 main_band.SetBottom(pos - 1);
902
903 const std::string clr_str = m_mode == SingleExtruder ? tick_it->color :
904 tick_it->type == ToolChange ?
905 get_color_for_tool_change_tick(tick_it) :
906 get_color_for_color_change_tick(tick_it);
907
908 if (!clr_str.empty())
909 draw_band(dc, wxColour(clr_str), main_band);
910 }
911 ++tick_it;
912 }
913 }
914
update(wxWindow * win,const std::vector<double> & values,double scroll_step)915 void Control::Ruler::update(wxWindow* win, const std::vector<double>& values, double scroll_step)
916 {
917 int DPI = GUI::get_dpi_for_window(win);
918 int pixels_per_sm = lround((double)(DPI) * 5.0/25.4);
919
920 if (lround(scroll_step) > pixels_per_sm) {
921 long_step = -1.0;
922 return;
923 }
924
925 int pow = -2;
926 int step = 0;
927 auto end_it = count == 1 ? values.end() : values.begin() + lround(values.size() / count);
928
929 while (pow < 3) {
930 for (int istep : {1, 2, 5}) {
931 double val = (double)istep * std::pow(10,pow);
932 auto val_it = std::lower_bound(values.begin(), end_it, val - epsilon());
933
934 if (val_it == values.end())
935 break;
936 int tick = val_it - values.begin();
937
938 // find next tick with istep
939 val *= 2;
940 val_it = std::lower_bound(values.begin(), end_it, val - epsilon());
941 // count of short ticks between ticks
942 int short_ticks_cnt = val_it == values.end() ? tick : val_it - values.begin() - tick;
943
944 if (lround(short_ticks_cnt * scroll_step) > pixels_per_sm) {
945 step = istep;
946 // there couldn't be more then 10 short ticks between ticks
947 short_step = 0.1 * short_ticks_cnt;
948 break;
949 }
950 }
951 if (step > 0)
952 break;
953 pow++;
954 }
955
956 long_step = step == 0 ? -1.0 : (double)step* std::pow(10, pow);
957 }
958
draw_ruler(wxDC & dc)959 void Control::draw_ruler(wxDC& dc)
960 {
961 m_ruler.update(this->GetParent(), m_values, get_scroll_step());
962
963 int height, width;
964 get_size(&width, &height);
965 const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width;
966
967 dc.SetPen(GREY_PEN);
968 wxColour old_clr = dc.GetTextForeground();
969 dc.SetTextForeground(GREY_PEN.GetColour());
970
971 if (m_ruler.long_step < 0)
972 for (size_t tick = 1; tick < m_values.size(); tick++) {
973 wxCoord pos = get_position_from_value(tick);
974 draw_ticks_pair(dc, pos, mid, 5);
975 draw_tick_text(dc, wxPoint(mid, pos), tick);
976 }
977 else {
978 auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) {
979 while (current_tick < max_tick) {
980 wxCoord pos = get_position_from_value(lround(current_tick));
981 draw_ticks_pair(dc, pos, mid, 2);
982 current_tick += m_ruler.short_step;
983 if (current_tick > m_max_value)
984 break;
985 }
986 };
987
988 double short_tick = std::nan("");
989 int tick = 0;
990 double value = 0.0;
991 int sequence = 0;
992
993 int prev_y_pos = -1;
994 wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2;
995 int values_size = (int)m_values.size();
996
997 while (tick <= m_max_value) {
998 value += m_ruler.long_step;
999 if (value > m_values.back() && sequence < m_ruler.count) {
1000 value = m_ruler.long_step;
1001 for (; tick < values_size; tick++)
1002 if (m_values[tick] < value)
1003 break;
1004 // short ticks from the last tick to the end of current sequence
1005 assert(! std::isnan(short_tick));
1006 draw_short_ticks(dc, short_tick, tick);
1007 sequence++;
1008 }
1009 short_tick = tick;
1010
1011 for (; tick < values_size; tick++) {
1012 if (m_values[tick] == value)
1013 break;
1014 if (m_values[tick] > value) {
1015 if (tick > 0)
1016 tick--;
1017 break;
1018 }
1019 }
1020 if (tick > m_max_value)
1021 break;
1022
1023 wxCoord pos = get_position_from_value(tick);
1024 draw_ticks_pair(dc, pos, mid, 5);
1025 if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) {
1026 draw_tick_text(dc, wxPoint(mid, pos), tick);
1027 prev_y_pos = pos;
1028 }
1029
1030 draw_short_ticks(dc, short_tick, tick);
1031
1032 if (value == m_values.back() && sequence < m_ruler.count) {
1033 value = 0.0;
1034 sequence++;
1035 tick++;
1036 }
1037 }
1038 // short ticks from the last tick to the end
1039 draw_short_ticks(dc, short_tick, m_max_value);
1040 }
1041
1042 dc.SetTextForeground(old_clr);
1043 }
1044
draw_one_layer_icon(wxDC & dc)1045 void Control::draw_one_layer_icon(wxDC& dc)
1046 {
1047 if (m_draw_mode == dmSequentialGCodeView)
1048 return;
1049
1050 const wxBitmap& icon = m_is_one_layer ?
1051 m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() :
1052 m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp();
1053
1054 int width, height;
1055 get_size(&width, &height);
1056
1057 wxCoord x_draw, y_draw;
1058 is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim;
1059 is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2;
1060
1061 dc.DrawBitmap(icon, x_draw, y_draw);
1062
1063 //update rect of the lock/unlock icon
1064 m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim);
1065 }
1066
draw_revert_icon(wxDC & dc)1067 void Control::draw_revert_icon(wxDC& dc)
1068 {
1069 if (m_ticks.empty() || m_draw_mode != dmRegular)
1070 return;
1071
1072 int width, height;
1073 get_size(&width, &height);
1074
1075 wxCoord x_draw, y_draw;
1076 is_horizontal() ? x_draw = width-2 : x_draw = 0.25*SLIDER_MARGIN;
1077 is_horizontal() ? y_draw = 0.25*SLIDER_MARGIN: y_draw = height-2;
1078
1079 dc.DrawBitmap(m_bmp_revert.bmp(), x_draw, y_draw);
1080
1081 //update rect of the lock/unlock icon
1082 m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim);
1083 }
1084
draw_cog_icon(wxDC & dc)1085 void Control::draw_cog_icon(wxDC& dc)
1086 {
1087 if (m_draw_mode == dmSequentialGCodeView)
1088 return;
1089
1090 int width, height;
1091 get_size(&width, &height);
1092
1093 wxCoord x_draw, y_draw;
1094 if (m_draw_mode == dmSequentialGCodeView) {
1095 is_horizontal() ? x_draw = width - 2 : x_draw = 0.5 * width - 0.5 * m_cog_icon_dim;
1096 is_horizontal() ? y_draw = 0.5 * height - 0.5 * m_cog_icon_dim : y_draw = height - 2;
1097 }
1098 else {
1099 is_horizontal() ? x_draw = width - 2 : x_draw = width - m_cog_icon_dim - 2;
1100 is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2;
1101 }
1102
1103 dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw);
1104
1105 //update rect of the lock/unlock icon
1106 m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim);
1107 }
1108
update_thumb_rect(const wxCoord begin_x,const wxCoord begin_y,const SelectedSlider & selection)1109 void Control::update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection)
1110 {
1111 const wxRect rect = is_horizontal() ?
1112 wxRect(begin_x + (selection == ssHigher ? m_thumb_size.x / 2 : 0), begin_y, m_thumb_size.x / 2, m_thumb_size.y) :
1113 wxRect(begin_x, begin_y + (selection == ssLower ? m_thumb_size.y / 2 : 0), m_thumb_size.x, m_thumb_size.y / 2);
1114
1115 if (selection == ssLower)
1116 m_rect_lower_thumb = rect;
1117 else
1118 m_rect_higher_thumb = rect;
1119 }
1120
get_value_from_position(const wxCoord x,const wxCoord y)1121 int Control::get_value_from_position(const wxCoord x, const wxCoord y)
1122 {
1123 const int height = get_size().y;
1124 const double step = get_scroll_step();
1125
1126 if (is_horizontal())
1127 return int(double(x - SLIDER_MARGIN) / step + 0.5);
1128
1129 return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5);
1130 }
1131
is_lower_thumb_editable()1132 bool Control::is_lower_thumb_editable()
1133 {
1134 if (m_draw_mode == dmSequentialGCodeView)
1135 return Slic3r::GUI::get_app_config()->get("seq_top_layer_only") == "0";
1136 return true;
1137 }
1138
detect_selected_slider(const wxPoint & pt)1139 bool Control::detect_selected_slider(const wxPoint& pt)
1140 {
1141 if (is_point_in_rect(pt, m_rect_lower_thumb))
1142 m_selection = is_lower_thumb_editable() ? ssLower : ssUndef;
1143 else if(is_point_in_rect(pt, m_rect_higher_thumb))
1144 m_selection = ssHigher;
1145 else
1146 return false; // pt doesn't referenced to any thumb
1147 return true;
1148 }
1149
is_point_in_rect(const wxPoint & pt,const wxRect & rect)1150 bool Control::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
1151 {
1152 return rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() &&
1153 rect.GetTop() <= pt.y && pt.y <= rect.GetBottom();
1154 }
1155
get_tick_near_point(const wxPoint & pt)1156 int Control::get_tick_near_point(const wxPoint& pt)
1157 {
1158 for (auto tick : m_ticks.ticks) {
1159 const wxCoord pos = get_position_from_value(tick.tick);
1160
1161 if (is_horizontal()) {
1162 if (pos - 4 <= pt.x && pt.x <= pos + 4)
1163 return tick.tick;
1164 }
1165 else {
1166 if (pos - 4 <= pt.y && pt.y <= pos + 4)
1167 return tick.tick;
1168 }
1169 }
1170 return -1;
1171 }
1172
ChangeOneLayerLock()1173 void Control::ChangeOneLayerLock()
1174 {
1175 m_is_one_layer = !m_is_one_layer;
1176 m_selection == ssLower ? correct_lower_value() : correct_higher_value();
1177 if (!m_selection) m_selection = ssHigher;
1178
1179 Refresh();
1180 Update();
1181
1182 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
1183 e.SetEventObject(this);
1184 ProcessWindowEvent(e);
1185 }
1186
OnLeftDown(wxMouseEvent & event)1187 void Control::OnLeftDown(wxMouseEvent& event)
1188 {
1189 if (HasCapture())
1190 return;
1191 this->CaptureMouse();
1192
1193 m_is_left_down = true;
1194 m_mouse = maNone;
1195
1196 wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
1197
1198 if (is_point_in_rect(pos, m_rect_one_layer_icon))
1199 m_mouse = maOneLayerIconClick;
1200 else if (is_point_in_rect(pos, m_rect_cog_icon))
1201 m_mouse = maCogIconClick;
1202 else if (m_draw_mode == dmRegular) {
1203 if (is_point_in_rect(pos, m_rect_tick_action)) {
1204 auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value });
1205 m_mouse = it == m_ticks.ticks.end() ? maAddTick : maDeleteTick;
1206 }
1207 else if (is_point_in_rect(pos, m_rect_revert_icon))
1208 m_mouse = maRevertIconClick;
1209 }
1210
1211 if (m_mouse == maNone)
1212 detect_selected_slider(pos);
1213
1214 event.Skip();
1215 }
1216
correct_lower_value()1217 void Control::correct_lower_value()
1218 {
1219 if (m_lower_value < m_min_value)
1220 m_lower_value = m_min_value;
1221 else if (m_lower_value > m_max_value)
1222 m_lower_value = m_max_value;
1223
1224 if ((m_lower_value >= m_higher_value && m_lower_value <= m_max_value) || m_is_one_layer)
1225 m_higher_value = m_lower_value;
1226 }
1227
correct_higher_value()1228 void Control::correct_higher_value()
1229 {
1230 if (m_higher_value > m_max_value)
1231 m_higher_value = m_max_value;
1232 else if (m_higher_value < m_min_value)
1233 m_higher_value = m_min_value;
1234
1235 if ((m_higher_value <= m_lower_value && m_higher_value >= m_min_value) || m_is_one_layer)
1236 m_lower_value = m_higher_value;
1237 }
1238
get_tooltip(int tick)1239 wxString Control::get_tooltip(int tick/*=-1*/)
1240 {
1241 if (m_focus == fiNone)
1242 return "";
1243 if (m_focus == fiOneLayerIcon)
1244 return _L("One layer mode");
1245 if (m_focus == fiRevertIcon)
1246 return _L("Discard all custom changes");
1247 if (m_focus == fiCogIcon)
1248 {
1249 if (m_draw_mode == dmSequentialGCodeView)
1250 return _L("Jump to move") + " (Shift + G)";
1251 else
1252 return m_mode == MultiAsSingle ?
1253 GUI::from_u8((boost::format(_u8L("Jump to height %s\n"
1254 "Set ruler mode\n"
1255 "or Set extruder sequence for the entire print")) % "(Shift + G)").str()) :
1256 GUI::from_u8((boost::format(_u8L("Jump to height %s\n"
1257 "or Set ruler mode")) % "(Shift + G)").str());
1258 }
1259 if (m_focus == fiColorBand)
1260 return m_mode != SingleExtruder ? "" :
1261 _L("Edit current color - Right click the colored slider segment");
1262 if (m_draw_mode == dmSlaPrint)
1263 return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode
1264
1265 wxString tooltip;
1266 const auto tick_code_it = m_ticks.ticks.find(TickCode{tick});
1267
1268 if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist
1269 {
1270 // Show mode as a first string of tooltop
1271 tooltip = " " + _L("Print mode") + ": ";
1272 tooltip += (m_mode == SingleExtruder ? SingleExtruderMode :
1273 m_mode == MultiAsSingle ? MultiAsSingleMode :
1274 MultiExtruderMode );
1275 tooltip += "\n\n";
1276
1277 /* Note: just on OSX!!!
1278 * Right click event causes a little scrolling.
1279 * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick
1280 * Show this information in tooltip
1281 * */
1282
1283 // Show list of actions with new tick
1284 tooltip += ( m_mode == MultiAsSingle ?
1285 _L("Add extruder change - Left click") :
1286 m_mode == SingleExtruder ?
1287 _L("Add color change - Left click for predefined color or "
1288 "Shift + Left click for custom color selection") :
1289 _L("Add color change - Left click") ) + " " +
1290 _L("or press \"+\" key") + "\n" + (
1291 is_osx ?
1292 _L("Add another code - Ctrl + Left click") :
1293 _L("Add another code - Right click") );
1294 }
1295
1296 if (tick_code_it != m_ticks.ticks.end()) // tick exists
1297 {
1298 if (m_draw_mode == dmSequentialFffPrint)
1299 return _L("The sequential print is on.\n"
1300 "It's impossible to apply any custom G-code for objects printing sequentually.\n"
1301 "This code won't be processed during G-code generation.");
1302
1303 // Show custom Gcode as a first string of tooltop
1304 tooltip = " ";
1305 tooltip +=
1306 tick_code_it->type == ColorChange ?
1307 (m_mode == SingleExtruder ?
1308 format_wxstr(_L("Color change (\"%1%\")"), gcode(ColorChange)) :
1309 format_wxstr(_L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) :
1310 tick_code_it->type == PausePrint ?
1311 format_wxstr(_L("Pause print (\"%1%\")"), gcode(PausePrint)) :
1312 tick_code_it->type == Template ?
1313 format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) :
1314 tick_code_it->type == ToolChange ?
1315 format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) :
1316 from_u8(tick_code_it->extra);// tick_code_it->type == Custom
1317
1318 // If tick is marked as a conflict (exclamation icon),
1319 // we should to explain why
1320 ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]);
1321 if (conflict != ctNone)
1322 tooltip += "\n\n" + _L("Note") + "! ";
1323 if (conflict == ctModeConflict)
1324 tooltip += _L("G-code associated to this tick mark is in a conflict with print mode.\n"
1325 "Editing it will cause changes of Slider data.");
1326 else if (conflict == ctMeaninglessColorChange)
1327 tooltip += _L("There is a color change for extruder that won't be used till the end of print job.\n"
1328 "This code won't be processed during G-code generation.");
1329 else if (conflict == ctMeaninglessToolChange)
1330 tooltip += _L("There is an extruder change set to the same extruder.\n"
1331 "This code won't be processed during G-code generation.");
1332 else if (conflict == ctRedundant)
1333 tooltip += _L("There is a color change for extruder that has not been used before.\n"
1334 "Check your settings to avoid redundant color changes.");
1335
1336 // Show list of actions with existing tick
1337 if (m_focus == fiActionIcon)
1338 tooltip += "\n\n" + _L("Delete tick mark - Left click or press \"-\" key") + "\n" + (
1339 is_osx ?
1340 _L("Edit tick mark - Ctrl + Left click") :
1341 _L("Edit tick mark - Right click") );
1342 }
1343 return tooltip;
1344
1345 }
1346
get_edited_tick_for_position(const wxPoint pos,Type type)1347 int Control::get_edited_tick_for_position(const wxPoint pos, Type type /*= ColorChange*/)
1348 {
1349 if (m_ticks.empty())
1350 return -1;
1351
1352 int tick = get_value_from_position(pos);
1353 auto it = std::lower_bound(m_ticks.ticks.begin(), m_ticks.ticks.end(), TickCode{ tick });
1354
1355 while (it != m_ticks.ticks.begin()) {
1356 --it;
1357 if (it->type == type)
1358 return it->tick;
1359 }
1360
1361 return -1;
1362 }
1363
OnMotion(wxMouseEvent & event)1364 void Control::OnMotion(wxMouseEvent& event)
1365 {
1366 bool action = false;
1367
1368 const wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
1369 int tick = -1;
1370
1371 if (!m_is_left_down && !m_is_right_down) {
1372 if (is_point_in_rect(pos, m_rect_one_layer_icon))
1373 m_focus = fiOneLayerIcon;
1374 else if (is_point_in_rect(pos, m_rect_tick_action)) {
1375 m_focus = fiActionIcon;
1376 tick = m_selection == ssLower ? m_lower_value : m_higher_value;
1377 }
1378 else if (!m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon))
1379 m_focus = fiRevertIcon;
1380 else if (is_point_in_rect(pos, m_rect_cog_icon))
1381 m_focus = fiCogIcon;
1382 else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) &&
1383 get_edited_tick_for_position(pos) >= 0 )
1384 m_focus = fiColorBand;
1385 else if (is_point_in_rect(pos, m_rect_lower_thumb))
1386 m_focus = fiLowerThumb;
1387 else if (is_point_in_rect(pos, m_rect_higher_thumb))
1388 m_focus = fiHigherThumb;
1389 else {
1390 m_focus = fiTick;
1391 tick = get_tick_near_point(pos);
1392 }
1393 m_moving_pos = pos;
1394 }
1395 else if (m_is_left_down || m_is_right_down) {
1396 if (m_selection == ssLower) {
1397 int current_value = m_lower_value;
1398 m_lower_value = get_value_from_position(pos.x, pos.y);
1399 correct_lower_value();
1400 action = (current_value != m_lower_value);
1401 }
1402 else if (m_selection == ssHigher) {
1403 int current_value = m_higher_value;
1404 m_higher_value = get_value_from_position(pos.x, pos.y);
1405 correct_higher_value();
1406 action = (current_value != m_higher_value);
1407 }
1408 m_moving_pos = wxDefaultPosition;
1409 }
1410 Refresh();
1411 Update();
1412 event.Skip();
1413
1414 // Set tooltips with information for each icon
1415 this->SetToolTip(get_tooltip(tick));
1416
1417 if (action) {
1418 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
1419 e.SetEventObject(this);
1420 e.SetString("moving");
1421 ProcessWindowEvent(e);
1422 }
1423 }
1424
append_change_extruder_menu_item(wxMenu * menu,bool switch_current_code)1425 void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current_code/* = false*/)
1426 {
1427 const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
1428 if (extruders_cnt > 1) {
1429 std::array<int, 2> active_extruders = get_active_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
1430
1431 std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
1432
1433 wxMenu* change_extruder_menu = new wxMenu();
1434
1435 for (int i = 1; i <= extruders_cnt; i++) {
1436 const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1];
1437 const wxString item_name = wxString::Format(_L("Extruder %d"), i) +
1438 (is_active_extruder ? " (" + _L("active") + ")" : "");
1439
1440 if (m_mode == MultiAsSingle)
1441 append_menu_item(change_extruder_menu, wxID_ANY, item_name, "",
1442 [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, *icons[i-1], menu,
1443 [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater());
1444 }
1445
1446 const wxString change_extruder_menu_name = m_mode == MultiAsSingle ?
1447 (switch_current_code ? _L("Switch code to Change extruder") : _L("Change extruder") ) :
1448 _L("Change extruder (N/A)");
1449
1450 wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _L("Use another extruder"));
1451 change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder"));
1452
1453 GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) {
1454 enable_menu_item(evt, [this]() {return m_mode == MultiAsSingle; }, change_extruder_menu_item, this); },
1455 change_extruder_menu_item->GetId());
1456 }
1457 }
1458
append_add_color_change_menu_item(wxMenu * menu,bool switch_current_code)1459 void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_current_code/* = false*/)
1460 {
1461 const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
1462 if (extruders_cnt > 1) {
1463 int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
1464 std::set<int> used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]);
1465
1466 wxMenu* add_color_change_menu = new wxMenu();
1467
1468 for (int i = 1; i <= extruders_cnt; i++) {
1469 const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder
1470 used_extruders_for_tick.find(i) != used_extruders_for_tick.end();
1471 const wxString item_name = wxString::Format(_L("Extruder %d"), i) +
1472 (is_used_extruder ? " (" + _L("used") + ")" : "");
1473
1474 append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
1475 [this, i](wxCommandEvent&) { add_code_as_tick(ColorChange, i); }, "", menu,
1476 []() { return true; }, GUI::wxGetApp().plater());
1477 }
1478
1479 const wxString menu_name = switch_current_code ?
1480 format_wxstr(_L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) :
1481 format_wxstr(_L("Add color change (%1%) for:"), gcode(ColorChange));
1482 wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, "");
1483 add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m"));
1484 }
1485 }
1486
OnLeftUp(wxMouseEvent & event)1487 void Control::OnLeftUp(wxMouseEvent& event)
1488 {
1489 if (!HasCapture())
1490 return;
1491 this->ReleaseMouse();
1492 m_is_left_down = false;
1493
1494 switch (m_mouse) {
1495 case maNone :
1496 move_current_thumb_to_pos(event.GetLogicalPosition(wxClientDC(this)));
1497 break;
1498 case maDeleteTick :
1499 delete_current_tick();
1500 break;
1501 case maAddTick :
1502 add_current_tick();
1503 break;
1504 case maCogIconClick :
1505 show_cog_icon_context_menu();
1506 break;
1507 case maOneLayerIconClick:
1508 switch_one_layer_mode();
1509 break;
1510 case maRevertIconClick:
1511 discard_all_thicks();
1512 break;
1513 default :
1514 break;
1515 }
1516
1517 Refresh();
1518 Update();
1519 event.Skip();
1520
1521 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
1522 e.SetEventObject(this);
1523 ProcessWindowEvent(e);
1524 }
1525
enter_window(wxMouseEvent & event,const bool enter)1526 void Control::enter_window(wxMouseEvent& event, const bool enter)
1527 {
1528 m_is_focused = enter;
1529 Refresh();
1530 Update();
1531 event.Skip();
1532 }
1533
1534 // "condition" have to be true for:
1535 // - value increase (if wxSL_VERTICAL)
1536 // - value decrease (if wxSL_HORIZONTAL)
move_current_thumb(const bool condition)1537 void Control::move_current_thumb(const bool condition)
1538 {
1539 // m_is_one_layer = wxGetKeyState(WXK_CONTROL);
1540 int delta = condition ? -1 : 1;
1541 if (is_horizontal())
1542 delta *= -1;
1543
1544 // accelerators
1545 int accelerator = 0;
1546 if (wxGetKeyState(WXK_SHIFT))
1547 accelerator += 5;
1548 if (wxGetKeyState(WXK_CONTROL))
1549 accelerator += 5;
1550 if (accelerator > 0)
1551 delta *= accelerator;
1552
1553 #if ENABLE_ARROW_KEYS_WITH_SLIDERS
1554 if (m_selection == ssUndef) m_selection = ssHigher;
1555 #endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
1556
1557 if (m_selection == ssLower) {
1558 m_lower_value -= delta;
1559 correct_lower_value();
1560 }
1561 else if (m_selection == ssHigher) {
1562 m_higher_value -= delta;
1563 correct_higher_value();
1564 }
1565 Refresh();
1566 Update();
1567
1568 wxCommandEvent e(wxEVT_SCROLL_CHANGED);
1569 e.SetEventObject(this);
1570 ProcessWindowEvent(e);
1571 }
1572
OnWheel(wxMouseEvent & event)1573 void Control::OnWheel(wxMouseEvent& event)
1574 {
1575 // Set nearest to the mouse thumb as a selected, if there is not selected thumb
1576 if (m_selection == ssUndef) {
1577 const wxPoint& pt = event.GetLogicalPosition(wxClientDC(this));
1578
1579 if (is_horizontal())
1580 m_selection = abs(pt.x - m_rect_lower_thumb.GetRight()) <=
1581 abs(pt.x - m_rect_higher_thumb.GetLeft()) ?
1582 ssLower : ssHigher;
1583 else
1584 m_selection = abs(pt.y - m_rect_lower_thumb.GetTop()) <=
1585 abs(pt.y - m_rect_higher_thumb.GetBottom()) ?
1586 ssLower : ssHigher;
1587 }
1588
1589 if (m_selection == ssLower && !is_lower_thumb_editable())
1590 m_selection = ssUndef;
1591
1592 move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0);
1593 }
1594
OnKeyDown(wxKeyEvent & event)1595 void Control::OnKeyDown(wxKeyEvent &event)
1596 {
1597 const int key = event.GetKeyCode();
1598 if (m_draw_mode != dmSequentialGCodeView && key == WXK_NUMPAD_ADD) {
1599 // OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice.
1600 // To avoid this case we should suppress second add_tick() call.
1601 m_ticks.suppress_plus(true);
1602 add_current_tick(true);
1603 }
1604 else if (m_draw_mode != dmSequentialGCodeView && (key == WXK_NUMPAD_SUBTRACT || key == WXK_DELETE || key == WXK_BACK)) {
1605 // OnChar() is called immediately after OnKeyDown(), which can cause call of delete_tick() twice.
1606 // To avoid this case we should suppress second delete_tick() call.
1607 m_ticks.suppress_minus(true);
1608 delete_current_tick();
1609 }
1610 else if (m_draw_mode != dmSequentialGCodeView && event.GetKeyCode() == WXK_SHIFT)
1611 UseDefaultColors(false);
1612 else if (is_horizontal()) {
1613 if (m_is_focused) {
1614 if (key == WXK_LEFT || key == WXK_RIGHT)
1615 move_current_thumb(key == WXK_LEFT);
1616 else if (key == WXK_UP || key == WXK_DOWN) {
1617 #if ENABLE_ARROW_KEYS_WITH_SLIDERS
1618 if (key == WXK_DOWN)
1619 m_selection = ssHigher;
1620 else if (key == WXK_UP && is_lower_thumb_editable())
1621 m_selection = ssLower;
1622 #else
1623 if (key == WXK_UP)
1624 m_selection = ssHigher;
1625 else if (key == WXK_DOWN && is_lower_thumb_editable())
1626 m_selection = ssLower;
1627 #endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
1628 Refresh();
1629 }
1630 }
1631 #if ENABLE_ARROW_KEYS_WITH_SLIDERS
1632 else {
1633 if (key == WXK_LEFT || key == WXK_RIGHT)
1634 move_current_thumb(key == WXK_LEFT);
1635 }
1636 #endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
1637 }
1638 else {
1639 if (m_is_focused) {
1640 if (key == WXK_LEFT || key == WXK_RIGHT) {
1641 if (key == WXK_LEFT)
1642 m_selection = ssHigher;
1643 else if (key == WXK_RIGHT && is_lower_thumb_editable())
1644 m_selection = ssLower;
1645 Refresh();
1646 }
1647 else if (key == WXK_UP || key == WXK_DOWN)
1648 move_current_thumb(key == WXK_UP);
1649 }
1650 #if ENABLE_ARROW_KEYS_WITH_SLIDERS
1651 else {
1652 if (key == WXK_UP || key == WXK_DOWN)
1653 move_current_thumb(key == WXK_UP);
1654 }
1655 #endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
1656 }
1657
1658 event.Skip(); // !Needed to have EVT_CHAR generated as well
1659 }
1660
OnKeyUp(wxKeyEvent & event)1661 void Control::OnKeyUp(wxKeyEvent &event)
1662 {
1663 if (event.GetKeyCode() == WXK_CONTROL)
1664 m_is_one_layer = false;
1665 else if (event.GetKeyCode() == WXK_SHIFT)
1666 UseDefaultColors(true);
1667
1668 Refresh();
1669 Update();
1670 event.Skip();
1671 }
1672
OnChar(wxKeyEvent & event)1673 void Control::OnChar(wxKeyEvent& event)
1674 {
1675 const int key = event.GetKeyCode();
1676 if (m_draw_mode != dmSequentialGCodeView) {
1677 if (key == '+' && !m_ticks.suppressed_plus()) {
1678 add_current_tick(true);
1679 m_ticks.suppress_plus(false);
1680 }
1681 else if (key == '-' && !m_ticks.suppressed_minus()) {
1682 delete_current_tick();
1683 m_ticks.suppress_minus(false);
1684 }
1685 }
1686 if (key == 'G')
1687 jump_to_value();
1688 }
1689
OnRightDown(wxMouseEvent & event)1690 void Control::OnRightDown(wxMouseEvent& event)
1691 {
1692 if (HasCapture()) return;
1693 this->CaptureMouse();
1694
1695 const wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
1696
1697 m_mouse = maNone;
1698 if (m_draw_mode == dmRegular) {
1699 if (is_point_in_rect(pos, m_rect_tick_action)) {
1700 const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
1701 m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ?
1702 maAddMenu : maEditMenu;
1703 }
1704 else if (m_mode == SingleExtruder && !detect_selected_slider(pos) && is_point_in_rect(pos, get_colored_band_rect()))
1705 m_mouse = maForceColorEdit;
1706 else if (m_mode == MultiAsSingle && is_point_in_rect(pos, m_rect_cog_icon))
1707 m_mouse = maCogIconMenu;
1708 }
1709 if (m_mouse != maNone || !detect_selected_slider(pos))
1710 return;
1711
1712 if (m_selection == ssLower)
1713 m_higher_value = m_lower_value;
1714 else
1715 m_lower_value = m_higher_value;
1716
1717 // set slider to "one layer" mode
1718 m_is_right_down = m_is_one_layer = true;
1719
1720 Refresh();
1721 Update();
1722 event.Skip();
1723 }
1724
1725 // Get active extruders for tick.
1726 // Means one current extruder for not existing tick OR
1727 // 2 extruders - for existing tick (extruder before ToolChange and extruder of current existing tick)
1728 // Use those values to disable selection of active extruders
get_active_extruders_for_tick(int tick) const1729 std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const
1730 {
1731 int default_initial_extruder = m_mode == MultiAsSingle ? std::max<int>(1, m_only_extruder) : 1;
1732 std::array<int, 2> extruders = { default_initial_extruder, -1 };
1733 if (m_ticks.empty())
1734 return extruders;
1735
1736 auto it = m_ticks.ticks.lower_bound(TickCode{tick});
1737
1738 if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists
1739 extruders[1] = it->extruder;
1740
1741 while (it != m_ticks.ticks.begin()) {
1742 --it;
1743 if(it->type == ToolChange) {
1744 extruders[0] = it->extruder;
1745 break;
1746 }
1747 }
1748
1749 return extruders;
1750 }
1751
1752 // Get used extruders for tick.
1753 // Means all extruders(tools) which will be used during printing from current tick to the end
get_used_extruders_for_tick(int tick,int only_extruder,double print_z,Mode force_mode) const1754 std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode/* = Undef*/) const
1755 {
1756 Mode e_mode = !force_mode ? mode : force_mode;
1757
1758 if (e_mode == MultiExtruder) {
1759 // #ys_FIXME: get tool ordering from _correct_ place
1760 const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering();
1761
1762 if (tool_ordering.empty())
1763 return {};
1764
1765 std::set<int> used_extruders;
1766
1767 auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z));
1768 for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) {
1769 const std::vector<unsigned>& extruders = it_layer_tools->extruders;
1770 for (const auto& extruder : extruders)
1771 used_extruders.emplace(extruder+1);
1772 }
1773
1774 return used_extruders;
1775 }
1776
1777 const int default_initial_extruder = e_mode == MultiAsSingle ? std::max(only_extruder, 1) : 1;
1778 if (ticks.empty() || e_mode == SingleExtruder)
1779 return {default_initial_extruder};
1780
1781 std::set<int> used_extruders;
1782
1783 auto it_start = ticks.lower_bound(TickCode{tick});
1784 auto it = it_start;
1785 if (it == ticks.begin() && it->type == ToolChange &&
1786 tick != it->tick ) // In case of switch of ToolChange to ColorChange, when tick exists,
1787 // we shouldn't change color for extruder, which will be deleted
1788 {
1789 used_extruders.emplace(it->extruder);
1790 if (tick < it->tick)
1791 used_extruders.emplace(default_initial_extruder);
1792 }
1793
1794 while (it != ticks.begin()) {
1795 --it;
1796 if (it->type == ToolChange && tick != it->tick) {
1797 used_extruders.emplace(it->extruder);
1798 break;
1799 }
1800 }
1801
1802 if (it == ticks.begin() && used_extruders.empty())
1803 used_extruders.emplace(default_initial_extruder);
1804
1805 for (it = it_start; it != ticks.end(); ++it)
1806 if (it->type == ToolChange && tick != it->tick)
1807 used_extruders.emplace(it->extruder);
1808
1809 return used_extruders;
1810 }
1811
show_add_context_menu()1812 void Control::show_add_context_menu()
1813 {
1814 wxMenu menu;
1815
1816 if (m_mode == SingleExtruder) {
1817 append_menu_item(&menu, wxID_ANY, _L("Add color change") + " (" + gcode(ColorChange) + ")", "",
1818 [this](wxCommandEvent&) { add_code_as_tick(ColorChange); }, "colorchange_add_m", &menu);
1819
1820 UseDefaultColors(false);
1821 }
1822 else {
1823 append_change_extruder_menu_item(&menu);
1824 append_add_color_change_menu_item(&menu);
1825 }
1826
1827 if (!gcode(PausePrint).empty())
1828 append_menu_item(&menu, wxID_ANY, _L("Add pause print") + " (" + gcode(PausePrint) + ")", "",
1829 [this](wxCommandEvent&) { add_code_as_tick(PausePrint); }, "pause_print", &menu);
1830
1831 if (!gcode(Template).empty())
1832 append_menu_item(&menu, wxID_ANY, _L("Add custom template") + " (" + gcode(Template) + ")", "",
1833 [this](wxCommandEvent&) { add_code_as_tick(Template); }, "edit_gcode", &menu);
1834
1835 append_menu_item(&menu, wxID_ANY, _L("Add custom G-code"), "",
1836 [this](wxCommandEvent&) { add_code_as_tick(Custom); }, "edit_gcode", &menu);
1837
1838 GUI::wxGetApp().plater()->PopupMenu(&menu);
1839 }
1840
show_edit_context_menu()1841 void Control::show_edit_context_menu()
1842 {
1843 wxMenu menu;
1844
1845 std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value });
1846
1847 if (it->type == ToolChange) {
1848 if (m_mode == MultiAsSingle)
1849 append_change_extruder_menu_item(&menu);
1850 append_add_color_change_menu_item(&menu, true);
1851 }
1852 else
1853 append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _L("Edit color") :
1854 it->type == PausePrint ? _L("Edit pause print message") :
1855 _L("Edit custom G-code"), "",
1856 [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu);
1857
1858 if (it->type == ColorChange && m_mode == MultiAsSingle)
1859 append_change_extruder_menu_item(&menu, true);
1860
1861 append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _L("Delete color change") :
1862 it->type == ToolChange ? _L("Delete tool change") :
1863 it->type == PausePrint ? _L("Delete pause print") :
1864 _L("Delete custom G-code"), "",
1865 [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu);
1866
1867 GUI::wxGetApp().plater()->PopupMenu(&menu);
1868 }
1869
show_cog_icon_context_menu()1870 void Control::show_cog_icon_context_menu()
1871 {
1872 wxMenu menu;
1873
1874 append_menu_item(&menu, wxID_ANY, _L("Jump to height") + " (Shift+G)", "",
1875 [this](wxCommandEvent&) { jump_to_value(); }, "", & menu);
1876
1877 wxMenu* ruler_mode_menu = new wxMenu();
1878 if (ruler_mode_menu) {
1879 append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("None"), _L("Hide ruler"),
1880 [this](wxCommandEvent&) { if (m_extra_style != 0) m_extra_style = 0; }, ruler_mode_menu,
1881 []() { return true; }, [this]() { return m_extra_style == 0; }, GUI::wxGetApp().plater());
1882
1883 append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show object height"), _L("Show object height on the ruler"),
1884 [this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style &= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu,
1885 []() { return true; }, [this]() { return m_extra_style & wxSL_AUTOTICKS; }, GUI::wxGetApp().plater());
1886
1887 append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show estimated print time"), _L("Show estimated print time on the ruler"),
1888 [this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style &= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu,
1889 []() { return true; }, [this]() { return m_extra_style & wxSL_VALUE_LABEL; }, GUI::wxGetApp().plater());
1890
1891 append_submenu(&menu, ruler_mode_menu, wxID_ANY, _L("Ruler mode"), _L("Set ruler mode"), "",
1892 [this]() { return true; }, this);
1893 }
1894
1895 if (m_mode == MultiAsSingle && m_draw_mode == dmRegular)
1896 append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "",
1897 [this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu);
1898
1899 GUI::wxGetApp().plater()->PopupMenu(&menu);
1900 }
1901
OnRightUp(wxMouseEvent & event)1902 void Control::OnRightUp(wxMouseEvent& event)
1903 {
1904 if (!HasCapture())
1905 return;
1906 this->ReleaseMouse();
1907 m_is_right_down = m_is_one_layer = false;
1908
1909 if (m_mouse == maForceColorEdit) {
1910 wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
1911 int edited_tick = get_edited_tick_for_position(pos);
1912 if (edited_tick >= 0)
1913 edit_tick(edited_tick);
1914 }
1915 else if (m_mouse == maAddMenu)
1916 show_add_context_menu();
1917 else if (m_mouse == maEditMenu)
1918 show_edit_context_menu();
1919 else if (m_mouse == maCogIconMenu)
1920 show_cog_icon_context_menu();
1921
1922 Refresh();
1923 Update();
1924 event.Skip();
1925 }
1926
get_new_color(const std::string & color)1927 static std::string get_new_color(const std::string& color)
1928 {
1929 wxColour clr(color);
1930 if (!clr.IsOk())
1931 clr = wxColour(0, 0, 0); // Don't set alfa to transparence
1932
1933 auto data = new wxColourData();
1934 data->SetChooseFull(1);
1935 data->SetColour(clr);
1936
1937 wxColourDialog dialog(nullptr, data);
1938 dialog.CenterOnParent();
1939 if (dialog.ShowModal() == wxID_OK)
1940 return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
1941 return "";
1942 }
1943
1944 /* To avoid get an empty string from wxTextEntryDialog
1945 * Let disable OK button, if TextCtrl is empty
1946 * OR input value is our of range (min..max), when min a nd max are positive
1947 * */
upgrade_text_entry_dialog(wxTextEntryDialog * dlg,double min=-1.0,double max=-1.0)1948 static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, double max = -1.0)
1949 {
1950 // detect TextCtrl and OK button
1951 wxTextCtrl* textctrl {nullptr};
1952 wxWindowList& dlg_items = dlg->GetChildren();
1953 for (auto item : dlg_items) {
1954 textctrl = dynamic_cast<wxTextCtrl*>(item);
1955 if (textctrl)
1956 break;
1957 }
1958
1959 if (!textctrl)
1960 return;
1961
1962 textctrl->SetInsertionPointEnd();
1963
1964 wxButton* btn_OK = static_cast<wxButton*>(dlg->FindWindowById(wxID_OK));
1965 btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt)
1966 {
1967 bool disable = textctrl->IsEmpty();
1968 if (!disable && min >= 0.0 && max >= 0.0) {
1969 double value = -1.0;
1970 if (!textctrl->GetValue().ToCDouble(&value)) // input value couldn't be converted to double
1971 disable = true;
1972 else
1973 disable = value < min - epsilon() || value > max + epsilon(); // is input value is out of valid range ?
1974 }
1975
1976 evt.Enable(!disable);
1977 }, btn_OK->GetId());
1978 }
1979
get_custom_code(const std::string & code_in,double height)1980 static std::string get_custom_code(const std::string& code_in, double height)
1981 {
1982 wxString msg_text = _L("Enter custom G-code used on current layer") + ":";
1983 wxString msg_header = format_wxstr(_L("Custom G-code on current layer (%1% mm)."), height);
1984
1985 // get custom gcode
1986 wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in,
1987 wxTextEntryDialogStyle | wxTE_MULTILINE);
1988 upgrade_text_entry_dialog(&dlg);
1989
1990 if (dlg.ShowModal() != wxID_OK)
1991 return "";
1992
1993 return dlg.GetValue().ToStdString();
1994 }
1995
get_pause_print_msg(const std::string & msg_in,double height)1996 static std::string get_pause_print_msg(const std::string& msg_in, double height)
1997 {
1998 wxString msg_text = _L("Enter short message shown on Printer display when a print is paused") + ":";
1999 wxString msg_header = format_wxstr(_L("Message for pause print on current layer (%1% mm)."), height);
2000
2001 // get custom gcode
2002 wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in),
2003 wxTextEntryDialogStyle);
2004 upgrade_text_entry_dialog(&dlg);
2005
2006 if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
2007 return "";
2008
2009 return into_u8(dlg.GetValue());
2010 }
2011
get_value_to_jump(double active_value,double min_z,double max_z,DrawMode mode)2012 static double get_value_to_jump(double active_value, double min_z, double max_z, DrawMode mode)
2013 {
2014 wxString msg_text = (mode == dmSequentialGCodeView) ? _L("Enter the move you want to jump to") + ":" : _L("Enter the height you want to jump to") + ":";
2015 wxString msg_header = (mode == dmSequentialGCodeView) ? _L("Jump to move") : _L("Jump to height");
2016 wxString msg_in = GUI::double_to_string(active_value);
2017
2018 // get custom gcode
2019 wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle);
2020 upgrade_text_entry_dialog(&dlg, min_z, max_z);
2021
2022 if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
2023 return -1.0;
2024
2025 double value = -1.0;
2026 return dlg.GetValue().ToCDouble(&value) ? value : -1.0;
2027 }
2028
add_code_as_tick(Type type,int selected_extruder)2029 void Control::add_code_as_tick(Type type, int selected_extruder/* = -1*/)
2030 {
2031 if (m_selection == ssUndef)
2032 return;
2033 const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
2034
2035 if ( !check_ticks_changed_event(type) )
2036 return;
2037
2038 if (type == ColorChange && gcode(ColorChange).empty())
2039 GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyColorChangeCode);
2040
2041 const int extruder = selected_extruder > 0 ? selected_extruder : std::max<int>(1, m_only_extruder);
2042 const auto it = m_ticks.ticks.find(TickCode{ tick });
2043
2044 if ( it == m_ticks.ticks.end() ) {
2045 // try to add tick
2046 if (!m_ticks.add_tick(tick, type, extruder, m_values[tick]))
2047 return;
2048 }
2049 else if (type == ToolChange || type == ColorChange) {
2050 // try to switch tick code to ToolChange or ColorChange accordingly
2051 if (!m_ticks.switch_code_for_tick(it, type, extruder))
2052 return;
2053 }
2054 else
2055 return;
2056
2057 post_ticks_changed_event(type);
2058 }
2059
add_current_tick(bool call_from_keyboard)2060 void Control::add_current_tick(bool call_from_keyboard /*= false*/)
2061 {
2062 if (m_selection == ssUndef)
2063 return;
2064 const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
2065 auto it = m_ticks.ticks.find(TickCode{ tick });
2066
2067 if (it != m_ticks.ticks.end() || // this tick is already exist
2068 !check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange))
2069 return;
2070
2071 if (m_mode == SingleExtruder)
2072 add_code_as_tick(ColorChange);
2073 else
2074 {
2075 wxMenu menu;
2076
2077 if (m_mode == MultiAsSingle)
2078 append_change_extruder_menu_item(&menu);
2079 else
2080 append_add_color_change_menu_item(&menu);
2081
2082 wxPoint pos = wxDefaultPosition;
2083 /* Menu position will be calculated from mouse click position, but...
2084 * if function is called from keyboard (pressing "+"), we should to calculate it
2085 * */
2086 if (call_from_keyboard) {
2087 int width, height;
2088 get_size(&width, &height);
2089
2090 const wxCoord coord = 0.75 * (is_horizontal() ? height : width);
2091 this->GetPosition(&width, &height);
2092
2093 pos = is_horizontal() ?
2094 wxPoint(get_position_from_value(tick), height + coord) :
2095 wxPoint(width + coord, get_position_from_value(tick));
2096 }
2097
2098 GUI::wxGetApp().plater()->PopupMenu(&menu, pos);
2099 }
2100 }
2101
delete_current_tick()2102 void Control::delete_current_tick()
2103 {
2104 if (m_selection == ssUndef)
2105 return;
2106
2107 auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value });
2108 if (it == m_ticks.ticks.end() ||
2109 !check_ticks_changed_event(it->type))
2110 return;
2111
2112 Type type = it->type;
2113 m_ticks.ticks.erase(it);
2114 post_ticks_changed_event(type);
2115 }
2116
edit_tick(int tick)2117 void Control::edit_tick(int tick/* = -1*/)
2118 {
2119 if (tick < 0)
2120 tick = m_selection == ssLower ? m_lower_value : m_higher_value;
2121 const std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ tick });
2122
2123 if (it == m_ticks.ticks.end() ||
2124 !check_ticks_changed_event(it->type))
2125 return;
2126
2127 Type type = it->type;
2128 if (m_ticks.edit_tick(it, m_values[it->tick]))
2129 post_ticks_changed_event(type);
2130 }
2131
2132 // switch on/off one layer mode
switch_one_layer_mode()2133 void Control::switch_one_layer_mode()
2134 {
2135 m_is_one_layer = !m_is_one_layer;
2136 if (!m_is_one_layer) {
2137 SetLowerValue(m_min_value);
2138 SetHigherValue(m_max_value);
2139 }
2140 m_selection == ssLower ? correct_lower_value() : correct_higher_value();
2141 if (m_selection == ssUndef) m_selection = ssHigher;
2142 }
2143
2144 // discard all custom changes on DoubleSlider
discard_all_thicks()2145 void Control::discard_all_thicks()
2146 {
2147 SetLowerValue(m_min_value);
2148 SetHigherValue(m_max_value);
2149
2150 m_selection == ssLower ? correct_lower_value() : correct_higher_value();
2151 if (m_selection == ssUndef) m_selection = ssHigher;
2152
2153 m_ticks.ticks.clear();
2154 post_ticks_changed_event();
2155
2156 }
2157
2158 // Set current thumb position to the nearest tick (if it is)
2159 // OR to a value corresponding to the mouse click (pos)
move_current_thumb_to_pos(wxPoint pos)2160 void Control::move_current_thumb_to_pos(wxPoint pos)
2161 {
2162 const int tick_val = get_tick_near_point(pos);
2163 const int mouse_val = tick_val >= 0 && m_draw_mode == dmRegular ? tick_val :
2164 get_value_from_position(pos);
2165 if (mouse_val >= 0) {
2166 if (m_selection == ssLower) {
2167 SetLowerValue(mouse_val);
2168 correct_lower_value();
2169 }
2170 else { // even m_selection is ssUndef, upper thumb should be selected
2171 SetHigherValue(mouse_val);
2172 correct_higher_value();
2173 }
2174 }
2175 }
2176
edit_extruder_sequence()2177 void Control::edit_extruder_sequence()
2178 {
2179 if (!check_ticks_changed_event(ToolChange))
2180 return;
2181
2182 GUI::ExtruderSequenceDialog dlg(m_extruders_sequence);
2183 if (dlg.ShowModal() != wxID_OK)
2184 return;
2185 m_extruders_sequence = dlg.GetValue();
2186
2187 m_ticks.erase_all_ticks_with_code(ToolChange);
2188
2189 int tick = 0;
2190 double value = 0.0;
2191 int extruder = 0;
2192 const int extr_cnt = m_extruders_sequence.extruders.size();
2193
2194 while (tick <= m_max_value)
2195 {
2196 const int cur_extruder = m_extruders_sequence.extruders[extruder];
2197
2198 bool meaningless_tick = tick == 0.0 && cur_extruder == extruder;
2199 if (!meaningless_tick)
2200 m_ticks.ticks.emplace(TickCode{tick, ToolChange,cur_extruder + 1, m_extruder_colors[cur_extruder]});
2201
2202 extruder++;
2203 if (extruder == extr_cnt)
2204 extruder = 0;
2205 if (m_extruders_sequence.is_mm_intervals) {
2206 value += m_extruders_sequence.interval_by_mm;
2207 auto val_it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
2208
2209 if (val_it == m_values.end())
2210 break;
2211
2212 tick = val_it - m_values.begin();
2213 }
2214 else
2215 tick += m_extruders_sequence.interval_by_layers;
2216 }
2217
2218 post_ticks_changed_event(ToolChange);
2219 }
2220
jump_to_value()2221 void Control::jump_to_value()
2222 {
2223 double value = get_value_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value],
2224 m_values[m_min_value], m_values[m_max_value], m_draw_mode);
2225 if (value < 0.0)
2226 return;
2227
2228 auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
2229 int tick_value = it - m_values.begin();
2230
2231 if (m_selection == ssLower)
2232 SetLowerValue(tick_value);
2233 else
2234 SetHigherValue(tick_value);
2235 }
2236
post_ticks_changed_event(Type type)2237 void Control::post_ticks_changed_event(Type type /*= Custom*/)
2238 {
2239 // m_force_mode_apply = type != ToolChange; // It looks like this condition is no needed now. Leave it for the testing
2240
2241 wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
2242 }
2243
check_ticks_changed_event(Type type)2244 bool Control::check_ticks_changed_event(Type type)
2245 {
2246 if ( m_ticks.mode == m_mode ||
2247 (type != ColorChange && type != ToolChange) ||
2248 (m_ticks.mode == SingleExtruder && m_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder
2249 (m_ticks.mode == MultiExtruder && m_mode == MultiAsSingle) ) // Just mark ColorChanges for all unused extruders
2250 return true;
2251
2252 if ((m_ticks.mode == SingleExtruder && m_mode == MultiExtruder ) ||
2253 (m_ticks.mode == MultiExtruder && m_mode == SingleExtruder) )
2254 {
2255 if (!m_ticks.has_tick_with_code(ColorChange))
2256 return true;
2257
2258 wxString message = (m_ticks.mode == SingleExtruder ?
2259 _L("The last color change data was saved for a single extruder printing.") :
2260 _L("The last color change data was saved for a multi extruder printing.")
2261 ) + "\n" +
2262 _L("Your current changes will delete all saved color changes.") + "\n\n\t" +
2263 _L("Are you sure you want to continue?");
2264
2265 wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO);
2266 if (msg.ShowModal() == wxID_YES) {
2267 m_ticks.erase_all_ticks_with_code(ColorChange);
2268 post_ticks_changed_event(ColorChange);
2269 }
2270 return false;
2271 }
2272 // m_ticks_mode == MultiAsSingle
2273 if( m_ticks.has_tick_with_code(ToolChange) ) {
2274 wxString message = m_mode == SingleExtruder ? (
2275 _L("The last color change data was saved for a multi extruder printing.") + "\n\n" +
2276 _L("Select YES if you want to delete all saved tool changes, \n"
2277 "NO if you want all tool changes switch to color changes, \n"
2278 "or CANCEL to leave it unchanged.") + "\n\n\t" +
2279 _L("Do you want to delete all saved tool changes?")
2280 ): ( // MultiExtruder
2281 _L("The last color change data was saved for a multi extruder printing with tool changes for whole print.") + "\n\n" +
2282 _L("Your current changes will delete all saved extruder (tool) changes.") + "\n\n\t" +
2283 _L("Are you sure you want to continue?") ) ;
2284
2285 wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO | (m_mode == SingleExtruder ? wxCANCEL : 0));
2286 const int answer = msg.ShowModal();
2287 if (answer == wxID_YES) {
2288 m_ticks.erase_all_ticks_with_code(ToolChange);
2289 post_ticks_changed_event(ToolChange);
2290 }
2291 else if (m_mode == SingleExtruder && answer == wxID_NO) {
2292 m_ticks.switch_code(ToolChange, ColorChange);
2293 post_ticks_changed_event(ColorChange);
2294 }
2295 return false;
2296 }
2297
2298 return true;
2299 }
2300
get_color_for_tick(TickCode tick,Type type,const int extruder)2301 std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder)
2302 {
2303 if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) {
2304 const std::vector<std::string>& colors = ColorPrintColors::get();
2305 if (ticks.empty())
2306 return colors[0];
2307 m_default_color_idx++;
2308
2309 return colors[m_default_color_idx % colors.size()];
2310 }
2311
2312 std::string color = (*m_colors)[extruder - 1];
2313
2314 if (type == ColorChange) {
2315 if (!ticks.empty()) {
2316 auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick );
2317 while (before_tick_it != ticks.begin()) {
2318 --before_tick_it;
2319 if (before_tick_it->type == ColorChange && before_tick_it->extruder == extruder) {
2320 color = before_tick_it->color;
2321 break;
2322 }
2323 }
2324 }
2325
2326 color = get_new_color(color);
2327 }
2328 return color;
2329 }
2330
add_tick(const int tick,Type type,const int extruder,double print_z)2331 bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, double print_z)
2332 {
2333 std::string color;
2334 std::string extra;
2335 if (type == Custom) // custom Gcode
2336 {
2337 extra = get_custom_code(custom_gcode, print_z);
2338 if (extra.empty())
2339 return false;
2340 custom_gcode = extra;
2341 }
2342 else if (type == PausePrint) {
2343 extra = get_pause_print_msg(pause_print_msg, print_z);
2344 if (extra.empty())
2345 return false;
2346 pause_print_msg = extra;
2347 }
2348 else {
2349 color = get_color_for_tick(TickCode{ tick }, type, extruder);
2350 if (color.empty())
2351 return false;
2352 }
2353
2354 if (mode == SingleExtruder)
2355 m_use_default_colors = true;
2356
2357 ticks.emplace(TickCode{ tick, type, extruder, color, extra });
2358 return true;
2359 }
2360
edit_tick(std::set<TickCode>::iterator it,double print_z)2361 bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
2362 {
2363 std::string edited_value;
2364 if (it->type == ColorChange)
2365 edited_value = get_new_color(it->color);
2366 else if (it->type == PausePrint)
2367 edited_value = get_pause_print_msg(it->extra, print_z);
2368 else
2369 edited_value = get_custom_code(it->type == Template ? gcode(Template) : it->extra, print_z);
2370
2371 if (edited_value.empty())
2372 return false;
2373
2374 TickCode changed_tick = *it;
2375 if (it->type == ColorChange) {
2376 if (it->color == edited_value)
2377 return false;
2378 changed_tick.color = edited_value;
2379 }
2380 else if (it->type == Template) {
2381 if (gcode(Template) == edited_value)
2382 return false;
2383 changed_tick.extra = edited_value;
2384 changed_tick.type = Custom;
2385 }
2386 else if (it->type == Custom || it->type == PausePrint) {
2387 if (it->extra == edited_value)
2388 return false;
2389 changed_tick.extra = edited_value;
2390 }
2391
2392 ticks.erase(it);
2393 ticks.emplace(changed_tick);
2394
2395 return true;
2396 }
2397
switch_code(Type type_from,Type type_to)2398 void TickCodeInfo::switch_code(Type type_from, Type type_to)
2399 {
2400 for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; )
2401 if (it->type == type_from) {
2402 TickCode tick = *it;
2403 tick.type = type_to;
2404 tick.extruder = 1;
2405 ticks.erase(it);
2406 it = ticks.emplace(tick).first;
2407 }
2408 else
2409 ++it;
2410 }
2411
switch_code_for_tick(std::set<TickCode>::iterator it,Type type_to,const int extruder)2412 bool TickCodeInfo::switch_code_for_tick(std::set<TickCode>::iterator it, Type type_to, const int extruder)
2413 {
2414 const std::string color = get_color_for_tick(*it, type_to, extruder);
2415 if (color.empty())
2416 return false;
2417
2418 TickCode changed_tick = *it;
2419 changed_tick.type = type_to;
2420 changed_tick.extruder = extruder;
2421 changed_tick.color = color;
2422
2423 ticks.erase(it);
2424 ticks.emplace(changed_tick);
2425
2426 return true;
2427 }
2428
erase_all_ticks_with_code(Type type)2429 void TickCodeInfo::erase_all_ticks_with_code(Type type)
2430 {
2431 for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) {
2432 if (it->type == type)
2433 it = ticks.erase(it);
2434 else
2435 ++it;
2436 }
2437 }
2438
has_tick_with_code(Type type)2439 bool TickCodeInfo::has_tick_with_code(Type type)
2440 {
2441 for (const TickCode& tick : ticks)
2442 if (tick.type == type)
2443 return true;
2444
2445 return false;
2446 }
2447
is_conflict_tick(const TickCode & tick,Mode out_mode,int only_extruder,double print_z)2448 ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z)
2449 {
2450 if ((tick.type == ColorChange && (
2451 (mode == SingleExtruder && out_mode == MultiExtruder ) ||
2452 (mode == MultiExtruder && out_mode == SingleExtruder) )) ||
2453 (tick.type == ToolChange &&
2454 (mode == MultiAsSingle && out_mode != MultiAsSingle)) )
2455 return ctModeConflict;
2456
2457 // check ColorChange tick
2458 if (tick.type == ColorChange) {
2459 // We should mark a tick as a "MeaninglessColorChange",
2460 // if it has a ColorChange for unused extruder from current print to end of the print
2461 std::set<int> used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, only_extruder, print_z, out_mode);
2462
2463 if (used_extruders_for_tick.find(tick.extruder) == used_extruders_for_tick.end())
2464 return ctMeaninglessColorChange;
2465
2466 // We should mark a tick as a "Redundant",
2467 // if it has a ColorChange for extruder that has not been used before
2468 if (mode == MultiAsSingle && tick.extruder != std::max<int>(only_extruder, 1) )
2469 {
2470 auto it = ticks.lower_bound( tick );
2471 if (it == ticks.begin() && it->type == ToolChange && tick.extruder == it->extruder)
2472 return ctNone;
2473
2474 while (it != ticks.begin()) {
2475 --it;
2476 if (it->type == ToolChange && tick.extruder == it->extruder)
2477 return ctNone;
2478 }
2479
2480 return ctRedundant;
2481 }
2482 }
2483
2484 // check ToolChange tick
2485 if (mode == MultiAsSingle && tick.type == ToolChange) {
2486 // We should mark a tick as a "MeaninglessToolChange",
2487 // if it has a ToolChange to the same extruder
2488 auto it = ticks.find(tick);
2489 if (it == ticks.begin())
2490 return tick.extruder == std::max<int>(only_extruder, 1) ? ctMeaninglessToolChange : ctNone;
2491
2492 while (it != ticks.begin()) {
2493 --it;
2494 if (it->type == ToolChange)
2495 return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone;
2496 }
2497 }
2498
2499 return ctNone;
2500 }
2501
2502 } // DoubleSlider
2503
2504 } // Slic3r
2505
2506
2507