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