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