1 #include "GUI_ObjectLayers.hpp"
2 #include "GUI_ObjectList.hpp"
3 
4 #include "OptionsGroup.hpp"
5 #include "GUI_App.hpp"
6 #include "libslic3r/PresetBundle.hpp"
7 #include "libslic3r/Model.hpp"
8 #include "GLCanvas3D.hpp"
9 #include "Plater.hpp"
10 
11 #include <boost/algorithm/string.hpp>
12 
13 #include "I18N.hpp"
14 
15 #include <wx/wupdlock.h>
16 
17 namespace Slic3r
18 {
19 namespace GUI
20 {
21 
ObjectLayers(wxWindow * parent)22 ObjectLayers::ObjectLayers(wxWindow* parent) :
23     OG_Settings(parent, true)
24 {
25     m_grid_sizer = new wxFlexGridSizer(3, 5, wxGetApp().em_unit()); // "Min Z", "Max Z", "Layer height" & buttons sizer
26     m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
27 
28     // Legend for object layers
29     for (const std::string col : { L("Start at height"), L("Stop at height"), L("Layer height") }) {
30         auto temp = new wxStaticText(m_parent, wxID_ANY, _(col), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE);
31         temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
32         temp->SetFont(wxGetApp().bold_font());
33 
34         m_grid_sizer->Add(temp);
35     }
36 
37     m_og->activate();
38     m_og->sizer->Clear(true);
39     m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
40 
41     m_bmp_delete    = ScalableBitmap(parent, "remove_copies"/*"cross"*/);
42     m_bmp_add       = ScalableBitmap(parent, "add_copies");
43 }
44 
select_editor(LayerRangeEditor * editor,const bool is_last_edited_range)45 void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range)
46 {
47     if (is_last_edited_range && m_selection_type == editor->type()) {
48     /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations",
49      * because of selected control's strange behavior:
50      * cursor is set to the control, but blue border - doesn't.
51      * And as a result we couldn't edit this control.
52      * */
53 #ifdef __WXOSX__
54         wxTheApp->CallAfter([editor]() {
55 #endif
56         editor->SetFocus();
57         editor->SetInsertionPointEnd();
58 #ifdef __WXOSX__
59         });
60 #endif
61     }
62 }
63 
create_layer(const t_layer_height_range & range,PlusMinusButton * delete_button,PlusMinusButton * add_button)64 wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinusButton *delete_button, PlusMinusButton *add_button)
65 {
66     const bool is_last_edited_range = range == m_selectable_range;
67 
68     auto set_focus_data = [range, this](const EditorType type)
69     {
70         m_selectable_range = range;
71         m_selection_type = type;
72     };
73 
74     auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed)
75     {
76         // change selectable range for new one, if enter was pressed or if same range was selected
77         if (enter_pressed || m_selectable_range == range)
78             m_selectable_range = new_range;
79         if (enter_pressed)
80             m_selection_type = type;
81     };
82 
83     // Add control for the "Min Z"
84 
85     auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, set_focus_data,
86         [range, update_focus_data, this, delete_button, add_button](coordf_t min_z, bool enter_pressed, bool dont_update_ui)
87     {
88         if (fabs(min_z - range.first) < EPSILON) {
89             m_selection_type = etUndef;
90             return false;
91         }
92 
93         // data for next focusing
94         coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5;
95         const t_layer_height_range new_range = { min_z, max_z };
96         if (delete_button)
97             delete_button->range = new_range;
98         if (add_button)
99             add_button->range = new_range;
100         update_focus_data(new_range, etMinZ, enter_pressed);
101 
102         return wxGetApp().obj_list()->edit_layer_range(range, new_range, dont_update_ui);
103     });
104 
105     select_editor(editor, is_last_edited_range);
106     m_grid_sizer->Add(editor);
107 
108     // Add control for the "Max Z"
109 
110     editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, set_focus_data,
111         [range, update_focus_data, this, delete_button, add_button](coordf_t max_z, bool enter_pressed, bool dont_update_ui)
112     {
113         if (fabs(max_z - range.second) < EPSILON || range.first > max_z) {
114             m_selection_type = etUndef;
115             return false;       // LayersList would not be updated/recreated
116         }
117 
118         // data for next focusing
119         const t_layer_height_range& new_range = { range.first, max_z };
120         if (delete_button)
121             delete_button->range = new_range;
122         if (add_button)
123             add_button->range = new_range;
124         update_focus_data(new_range, etMaxZ, enter_pressed);
125 
126         return wxGetApp().obj_list()->edit_layer_range(range, new_range, dont_update_ui);
127     });
128 
129     select_editor(editor, is_last_edited_range);
130     m_grid_sizer->Add(editor);
131 
132     // Add control for the "Layer height"
133 
134     editor = new LayerRangeEditor(this, double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), etLayerHeight, set_focus_data,
135         [range, this](coordf_t layer_height, bool, bool)
136     {
137         return wxGetApp().obj_list()->edit_layer_range(range, layer_height);
138     });
139 
140     select_editor(editor, is_last_edited_range);
141 
142     auto sizer = new wxBoxSizer(wxHORIZONTAL);
143     sizer->Add(editor);
144 
145     auto temp = new wxStaticText(m_parent, wxID_ANY, _(L("mm")));
146     temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
147     temp->SetFont(wxGetApp().normal_font());
148     sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit());
149 
150     m_grid_sizer->Add(sizer);
151 
152     return sizer;
153 }
154 
create_layers_list()155 void ObjectLayers::create_layers_list()
156 {
157     for (const auto &layer : m_object->layer_config_ranges)
158     {
159         const t_layer_height_range& range = layer.first;
160         auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range);
161         del_btn->SetToolTip(_(L("Remove layer range")));
162 
163         auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range);
164         wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range);
165         add_btn->SetToolTip(tooltip.IsEmpty() ? _(L("Add layer range")) : tooltip);
166         add_btn->Enable(tooltip.IsEmpty());
167 
168         auto sizer = create_layer(range, del_btn, add_btn);
169         sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent));
170         sizer->Add(add_btn);
171 
172         del_btn->Bind(wxEVT_BUTTON, [del_btn](wxEvent &) {
173             wxGetApp().obj_list()->del_layer_range(del_btn->range);
174         });
175 
176         add_btn->Bind(wxEVT_BUTTON, [add_btn](wxEvent &) {
177             wxGetApp().obj_list()->add_layer_range_after_current(add_btn->range);
178         });
179     }
180 }
181 
update_layers_list()182 void ObjectLayers::update_layers_list()
183 {
184     ObjectList* objects_ctrl   = wxGetApp().obj_list();
185     if (objects_ctrl->multiple_selection()) return;
186 
187     const auto item = objects_ctrl->GetSelection();
188     if (!item) return;
189 
190     const int obj_idx = objects_ctrl->get_selected_obj_idx();
191     if (obj_idx < 0) return;
192 
193     const ItemType type = objects_ctrl->GetModel()->GetItemType(item);
194     if (!(type & (itLayerRoot | itLayer))) return;
195 
196     m_object = objects_ctrl->object(obj_idx);
197     if (!m_object || m_object->layer_config_ranges.empty()) return;
198 
199     // Delete all controls from options group except of the legends
200 
201     const int cols = m_grid_sizer->GetEffectiveColsCount();
202     const int rows = m_grid_sizer->GetEffectiveRowsCount();
203     for (int idx = cols*rows-1; idx >= cols; idx--) {
204         wxSizerItem* t = m_grid_sizer->GetItem(idx);
205         if (t->IsSizer())
206             t->GetSizer()->Clear(true);
207         else
208             t->DeleteWindows();
209         m_grid_sizer->Remove(idx);
210     }
211 
212     // Add new control according to the selected item
213 
214     if (type & itLayerRoot)
215         create_layers_list();
216     else
217         create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item), nullptr, nullptr);
218 
219     m_parent->Layout();
220 }
221 
update_scene_from_editor_selection() const222 void ObjectLayers::update_scene_from_editor_selection() const
223 {
224     // needed to show the visual hints in 3D scene
225     wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type);
226 }
227 
UpdateAndShow(const bool show)228 void ObjectLayers::UpdateAndShow(const bool show)
229 {
230     if (show)
231         update_layers_list();
232 
233     OG_Settings::UpdateAndShow(show);
234 }
235 
msw_rescale()236 void ObjectLayers::msw_rescale()
237 {
238     m_bmp_delete.msw_rescale();
239     m_bmp_add.msw_rescale();
240 
241     m_grid_sizer->SetHGap(wxGetApp().em_unit());
242 
243     // rescale edit-boxes
244     const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount();
245     for (int i = 0; i < cells_cnt; i++)
246     {
247         const wxSizerItem* item = m_grid_sizer->GetItem(i);
248         if (item->IsWindow())
249         {
250             LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(item->GetWindow());
251             if (editor != nullptr)
252                 editor->msw_rescale();
253         }
254         else if (item->IsSizer()) // case when we have editor with buttons
255         {
256             wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); // editor
257             if (e_item->IsWindow()) {
258                 LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(e_item->GetWindow());
259                 if (editor != nullptr)
260                     editor->msw_rescale();
261             }
262 
263             const std::vector<size_t> btns = {2, 3};  // del_btn, add_btn
264             for (auto btn : btns)
265             {
266                 wxSizerItem* b_item = item->GetSizer()->GetItem(btn);
267                 if (b_item->IsWindow()) {
268                     auto button = dynamic_cast<PlusMinusButton*>(b_item->GetWindow());
269                     if (button != nullptr)
270                         button->msw_rescale();
271                 }
272             }
273         }
274     }
275     m_grid_sizer->Layout();
276 }
277 
sys_color_changed()278 void ObjectLayers::sys_color_changed()
279 {
280     m_bmp_delete.msw_rescale();
281     m_bmp_add.msw_rescale();
282 
283     m_grid_sizer->SetHGap(wxGetApp().em_unit());
284 
285     // rescale edit-boxes
286     const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount();
287     for (int i = 0; i < cells_cnt; i++)
288     {
289         const wxSizerItem* item = m_grid_sizer->GetItem(i);
290         if (item->IsSizer()) {// case when we have editor with buttons
291             const std::vector<size_t> btns = {2, 3};  // del_btn, add_btn
292             for (auto btn : btns) {
293                 wxSizerItem* b_item = item->GetSizer()->GetItem(btn);
294                 if (b_item->IsWindow()) {
295                     auto button = dynamic_cast<PlusMinusButton*>(b_item->GetWindow());
296                     if (button != nullptr)
297                         button->msw_rescale();
298                 }
299             }
300         }
301     }
302     m_grid_sizer->Layout();
303 }
304 
reset_selection()305 void ObjectLayers::reset_selection()
306 {
307     m_selectable_range = { 0.0, 0.0 };
308     m_selection_type = etLayerHeight;
309 }
310 
LayerRangeEditor(ObjectLayers * parent,const wxString & value,EditorType type,std::function<void (EditorType)> set_focus_data_fn,std::function<bool (coordf_t,bool,bool)> edit_fn)311 LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
312                                     const wxString& value,
313                                     EditorType type,
314                                     std::function<void(EditorType)> set_focus_data_fn,
315                                     std::function<bool(coordf_t, bool, bool)>   edit_fn
316                                     ) :
317     m_valid_value(value),
318     m_type(type),
319     m_set_focus_data(set_focus_data_fn),
320     wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition,
321                wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER)
322 {
323     this->SetFont(wxGetApp().normal_font());
324 
325     // Reset m_enter_pressed flag to _false_, when value is editing
326     this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
327 
328     this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&)
329     {
330         m_enter_pressed     = true;
331         // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip()
332         if (m_type&etLayerHeight) {
333             if (!edit_fn(get_value(), true, false))
334                 SetValue(m_valid_value);
335             else
336                 m_valid_value = double_to_string(get_value());
337             m_call_kill_focus = true;
338         }
339         else if (!edit_fn(get_value(), true, false)) {
340             SetValue(m_valid_value);
341             m_call_kill_focus = true;
342         }
343     }, this->GetId());
344 
345     this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e)
346     {
347         if (!m_enter_pressed) {
348 #ifndef __WXGTK__
349             /* Update data for next editor selection.
350              * But under GTK it looks like there is no information about selected control at e.GetWindow(),
351              * so we'll take it from wxEVT_LEFT_DOWN event
352              * */
353             LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow());
354             if (new_editor)
355                 new_editor->set_focus_data();
356 #endif // not __WXGTK__
357             // If LayersList wasn't updated/recreated, we should call e.Skip()
358             if (m_type & etLayerHeight) {
359                 if (!edit_fn(get_value(), false, dynamic_cast<ObjectLayers::PlusMinusButton*>(e.GetWindow()) != nullptr))
360                     SetValue(m_valid_value);
361                 else
362                     m_valid_value = double_to_string(get_value());
363                 e.Skip();
364             }
365             else if (!edit_fn(get_value(), false, dynamic_cast<ObjectLayers::PlusMinusButton*>(e.GetWindow()) != nullptr)) {
366                 SetValue(m_valid_value);
367                 e.Skip();
368             }
369         }
370         else if (m_call_kill_focus) {
371             m_call_kill_focus = false;
372             e.Skip();
373         }
374     }, this->GetId());
375 
376     this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e)
377     {
378         set_focus_data();
379         parent->update_scene_from_editor_selection();
380         e.Skip();
381     }, this->GetId());
382 
383 #ifdef __WXGTK__ // Workaround! To take information about selectable range
384     this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e)
385     {
386         set_focus_data();
387         e.Skip();
388     }, this->GetId());
389 #endif //__WXGTK__
390 
391     this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event)
392     {
393         // select all text using Ctrl+A
394         if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
395             this->SetSelection(-1, -1); //select all
396         event.Skip();
397     }));
398 }
399 
get_value()400 coordf_t LayerRangeEditor::get_value()
401 {
402     wxString str = GetValue();
403 
404     coordf_t layer_height;
405     // Replace the first occurence of comma in decimal number.
406     str.Replace(",", ".", false);
407     if (str == ".")
408         layer_height = 0.0;
409     else
410     {
411         if (!str.ToCDouble(&layer_height) || layer_height < 0.0f)
412         {
413             show_error(m_parent, _(L("Invalid numeric input.")));
414             SetValue(double_to_string(layer_height));
415         }
416     }
417 
418     return layer_height;
419 }
420 
msw_rescale()421 void LayerRangeEditor::msw_rescale()
422 {
423     SetMinSize(wxSize(8 * wxGetApp().em_unit(), wxDefaultCoord));
424 }
425 
426 } //namespace GUI
427 } //namespace Slic3r
428