1 #ifndef slic3r_GUI_Utils_hpp_
2 #define slic3r_GUI_Utils_hpp_
3
4 #include <memory>
5 #include <string>
6 #include <ostream>
7 #include <functional>
8
9 #include <boost/optional.hpp>
10
11 #include <wx/frame.h>
12 #include <wx/dialog.h>
13 #include <wx/event.h>
14 #include <wx/filedlg.h>
15 #include <wx/gdicmn.h>
16 #include <wx/panel.h>
17 #include <wx/dcclient.h>
18 #include <wx/debug.h>
19 #include <wx/settings.h>
20
21 #include <chrono>
22
23 #include "Event.hpp"
24
25 class wxCheckBox;
26 class wxTopLevelWindow;
27 class wxRect;
28
29 #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
30 #define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION > major) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION > minor)) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION == minor) && (wxRELEASE_NUMBER >= release)))
31 #else
32 #define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) 0
33 #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
34
35 namespace Slic3r {
36 namespace GUI {
37
38 #ifdef _WIN32
39 // USB HID attach / detach events from Windows OS.
40 using HIDDeviceAttachedEvent = Event<std::string>;
41 using HIDDeviceDetachedEvent = Event<std::string>;
42 wxDECLARE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
43 wxDECLARE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
44
45 // Disk aka Volume attach / detach events from Windows OS.
46 using VolumeAttachedEvent = SimpleEvent;
47 using VolumeDetachedEvent = SimpleEvent;
48 wxDECLARE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
49 wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
50 #endif /* _WIN32 */
51
52 wxTopLevelWindow* find_toplevel_parent(wxWindow *window);
53
54 void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback);
55
56 enum { DPI_DEFAULT = 96 };
57
58 int get_dpi_for_window(const wxWindow *window);
59 wxFont get_default_font_for_dpi(const wxWindow* window, int dpi);
get_default_font(const wxWindow * window)60 inline wxFont get_default_font(const wxWindow* window) { return get_default_font_for_dpi(window, get_dpi_for_window(window)); }
61
62 #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
63 struct DpiChangedEvent : public wxEvent {
64 int dpi;
65 wxRect rect;
66
DpiChangedEventSlic3r::GUI::DpiChangedEvent67 DpiChangedEvent(wxEventType eventType, int dpi, wxRect rect)
68 : wxEvent(0, eventType), dpi(dpi), rect(rect)
69 {}
70
CloneSlic3r::GUI::DpiChangedEvent71 virtual wxEvent *Clone() const
72 {
73 return new DpiChangedEvent(*this);
74 }
75 };
76
77 wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent);
78 #endif // !wxVERSION_EQUAL_OR_GREATER_THAN
79
80 template<class P> class DPIAware : public P
81 {
82 public:
DPIAware(wxWindow * parent,wxWindowID id,const wxString & title,const wxPoint & pos=wxDefaultPosition,const wxSize & size=wxDefaultSize,long style=wxDEFAULT_FRAME_STYLE,const wxString & name=wxFrameNameStr)83 DPIAware(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition,
84 const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE, const wxString &name=wxFrameNameStr)
85 : P(parent, id, title, pos, size, style, name)
86 {
87 int dpi = get_dpi_for_window(this);
88 m_scale_factor = (float)dpi / (float)DPI_DEFAULT;
89 m_prev_scale_factor = m_scale_factor;
90 m_normal_font = get_default_font_for_dpi(this, dpi);
91
92 /* Because of default window font is a primary display font,
93 * We should set correct font for window before getting em_unit value.
94 */
95 #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
96 this->SetFont(m_normal_font);
97 #endif
98 this->CenterOnParent();
99
100 // Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
101 // So, calculate the m_em_unit value from the font size, as before
102 #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__)
103 m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
104 #else
105 // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
106 m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
107 #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
108
109 // recalc_font();
110
111 #ifndef __WXOSX__
112 #if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
113 this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) {
114 m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT;
115 m_new_font_point_size = get_default_font_for_dpi(this, evt.GetNewDPI().x).GetPointSize();
116 if (m_can_rescale && (m_force_rescale || is_new_scale_factor()))
117 rescale(wxRect());
118 });
119 #else
120 this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) {
121 m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT;
122
123 m_new_font_point_size = get_default_font_for_dpi(this, evt.dpi).GetPointSize();
124
125 if (!m_can_rescale)
126 return;
127
128 if (m_force_rescale || is_new_scale_factor())
129 rescale(evt.rect);
130 });
131 #endif // wxVERSION_EQUAL_OR_GREATER_THAN
132 #endif // no __WXOSX__
133
134 this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event)
135 {
136 event.Skip();
137
138 // Suppress application rescaling, when a MainFrame moving is not ended
139 m_can_rescale = false;
140 });
141
142 this->Bind(wxEVT_MOVE_END, [this](wxMoveEvent& event)
143 {
144 event.Skip();
145
146 m_can_rescale = is_new_scale_factor();
147
148 // If scale factor is different after moving of MainFrame ...
149 if (m_can_rescale)
150 // ... rescale application
151 rescale(event.GetRect());
152 else
153 // set value to _true_ in purpose of possibility of a display dpi changing from System Settings
154 m_can_rescale = true;
155 });
156
157 this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event)
158 {
159 event.Skip();
160 on_sys_color_changed();
161 });
162 }
163
~DPIAware()164 virtual ~DPIAware() {}
165
scale_factor() const166 float scale_factor() const { return m_scale_factor; }
prev_scale_factor() const167 float prev_scale_factor() const { return m_prev_scale_factor; }
168
em_unit() const169 int em_unit() const { return m_em_unit; }
170 // int font_size() const { return m_font_size; }
normal_font() const171 const wxFont& normal_font() const { return m_normal_font; }
enable_force_rescale()172 void enable_force_rescale() { m_force_rescale = true; }
173
174 protected:
175 virtual void on_dpi_changed(const wxRect &suggested_rect) = 0;
on_sys_color_changed()176 virtual void on_sys_color_changed() {};
177
178 private:
179 float m_scale_factor;
180 int m_em_unit;
181 // int m_font_size;
182
183 wxFont m_normal_font;
184 float m_prev_scale_factor;
185 bool m_can_rescale{ true };
186 bool m_force_rescale{ false };
187
188 int m_new_font_point_size;
189
190 // void recalc_font()
191 // {
192 // wxClientDC dc(this);
193 // const auto metrics = dc.GetFontMetrics();
194 // m_font_size = metrics.height;
195 // m_em_unit = metrics.averageWidth;
196 // }
197
198 // check if new scale is differ from previous
is_new_scale_factor() const199 bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
200
201 // function for a font scaling of the window
scale_win_font(wxWindow * window,const int font_point_size)202 void scale_win_font(wxWindow *window, const int font_point_size)
203 {
204 wxFont new_font(window->GetFont());
205 new_font.SetPointSize(font_point_size);
206 window->SetFont(new_font);
207 }
208
209 // recursive function for scaling fonts for all controls in Window
scale_controls_fonts(wxWindow * window,const int font_point_size)210 void scale_controls_fonts(wxWindow *window, const int font_point_size)
211 {
212 auto children = window->GetChildren();
213
214 for (auto child : children) {
215 scale_controls_fonts(child, font_point_size);
216 scale_win_font(child, font_point_size);
217 }
218
219 window->Layout();
220 }
221
rescale(const wxRect & suggested_rect)222 void rescale(const wxRect &suggested_rect)
223 {
224 this->Freeze();
225
226 m_force_rescale = false;
227 #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
228 // rescale fonts of all controls
229 scale_controls_fonts(this, m_new_font_point_size);
230 // rescale current window font
231 scale_win_font(this, m_new_font_point_size);
232 #endif // wxVERSION_EQUAL_OR_GREATER_THAN
233
234 // set normal application font as a current window font
235 m_normal_font = this->GetFont();
236
237 // update em_unit value for new window font
238 #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
239 m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
240 #else
241 m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
242 #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
243
244 // rescale missed controls sizes and images
245 on_dpi_changed(suggested_rect);
246
247 this->Layout();
248 this->Thaw();
249
250 // reset previous scale factor from current scale factor value
251 m_prev_scale_factor = m_scale_factor;
252 }
253
254 };
255
256 typedef DPIAware<wxFrame> DPIFrame;
257 typedef DPIAware<wxDialog> DPIDialog;
258
259
260 class EventGuard
261 {
262 // This is a RAII-style smart-ptr-like guard that will bind any event to any event handler
263 // and unbind it as soon as it goes out of scope or unbind() is called.
264 // This can be used to solve the annoying problem of wx events being delivered to freed objects.
265
266 private:
267 // This is a way to type-erase both the event type as well as the handler:
268
269 struct EventStorageBase {
~EventStorageBaseSlic3r::GUI::EventGuard::EventStorageBase270 virtual ~EventStorageBase() {}
271 };
272
273 template<class EvTag, class Fun>
274 struct EventStorageFun : EventStorageBase {
275 wxEvtHandler *emitter;
276 EvTag tag;
277 Fun fun;
278
EventStorageFunSlic3r::GUI::EventGuard::EventStorageFun279 EventStorageFun(wxEvtHandler *emitter, const EvTag &tag, Fun fun)
280 : emitter(emitter)
281 , tag(tag)
282 , fun(std::move(fun))
283 {
284 emitter->Bind(this->tag, this->fun);
285 }
286
~EventStorageFunSlic3r::GUI::EventGuard::EventStorageFun287 virtual ~EventStorageFun() { emitter->Unbind(tag, fun); }
288 };
289
290 template<typename EvTag, typename Class, typename EvArg, typename EvHandler>
291 struct EventStorageMethod : EventStorageBase {
292 typedef void(Class::* MethodPtr)(EvArg &);
293
294 wxEvtHandler *emitter;
295 EvTag tag;
296 MethodPtr method;
297 EvHandler *handler;
298
EventStorageMethodSlic3r::GUI::EventGuard::EventStorageMethod299 EventStorageMethod(wxEvtHandler *emitter, const EvTag &tag, MethodPtr method, EvHandler *handler)
300 : emitter(emitter)
301 , tag(tag)
302 , method(method)
303 , handler(handler)
304 {
305 emitter->Bind(tag, method, handler);
306 }
307
~EventStorageMethodSlic3r::GUI::EventGuard::EventStorageMethod308 virtual ~EventStorageMethod() { emitter->Unbind(tag, method, handler); }
309 };
310
311 std::unique_ptr<EventStorageBase> event_storage;
312 public:
EventGuard()313 EventGuard() {}
314 EventGuard(const EventGuard&) = delete;
EventGuard(EventGuard && other)315 EventGuard(EventGuard &&other) : event_storage(std::move(other.event_storage)) {}
316
317 template<class EvTag, class Fun>
EventGuard(wxEvtHandler * emitter,const EvTag & tag,Fun fun)318 EventGuard(wxEvtHandler *emitter, const EvTag &tag, Fun fun)
319 :event_storage(new EventStorageFun<EvTag, Fun>(emitter, tag, std::move(fun)))
320 {}
321
322 template<typename EvTag, typename Class, typename EvArg, typename EvHandler>
EventGuard(wxEvtHandler * emitter,const EvTag & tag,void (Class::* method)(EvArg &),EvHandler * handler)323 EventGuard(wxEvtHandler *emitter, const EvTag &tag, void(Class::* method)(EvArg &), EvHandler *handler)
324 :event_storage(new EventStorageMethod<EvTag, Class, EvArg, EvHandler>(emitter, tag, method, handler))
325 {}
326
327 EventGuard& operator=(const EventGuard&) = delete;
operator =(EventGuard && other)328 EventGuard& operator=(EventGuard &&other)
329 {
330 event_storage = std::move(other.event_storage);
331 return *this;
332 }
333
unbind()334 void unbind() { event_storage.reset(nullptr); }
operator bool() const335 explicit operator bool() const { return !!event_storage; }
336 };
337
338
339 class CheckboxFileDialog : public wxFileDialog
340 {
341 public:
342 CheckboxFileDialog(wxWindow *parent,
343 const wxString &checkbox_label,
344 bool checkbox_value,
345 const wxString &message = wxFileSelectorPromptStr,
346 const wxString &default_dir = wxEmptyString,
347 const wxString &default_file = wxEmptyString,
348 const wxString &wildcard = wxFileSelectorDefaultWildcardStr,
349 long style = wxFD_DEFAULT_STYLE,
350 const wxPoint &pos = wxDefaultPosition,
351 const wxSize &size = wxDefaultSize,
352 const wxString &name = wxFileDialogNameStr
353 );
354
355 bool get_checkbox_value() const;
356
357 private:
358 struct ExtraPanel : public wxPanel
359 {
360 wxCheckBox *cbox;
361
362 ExtraPanel(wxWindow *parent);
363 static wxWindow* ctor(wxWindow *parent);
364 };
365
366 wxString checkbox_label;
367 };
368
369
370 class WindowMetrics
371 {
372 private:
373 wxRect rect;
374 bool maximized;
375
WindowMetrics()376 WindowMetrics() : maximized(false) {}
377 public:
378 static WindowMetrics from_window(wxTopLevelWindow *window);
379 static boost::optional<WindowMetrics> deserialize(const std::string &str);
380
get_rect() const381 const wxRect& get_rect() const { return rect; }
get_maximized() const382 bool get_maximized() const { return maximized; }
383
384 void sanitize_for_display(const wxRect &screen_rect);
385 std::string serialize() const;
386 };
387
388 std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics);
389
hex_digit_to_int(const char c)390 inline int hex_digit_to_int(const char c)
391 {
392 return
393 (c >= '0' && c <= '9') ? int(c - '0') :
394 (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
395 (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
396 }
397
398 class TaskTimer
399 {
400 std::chrono::milliseconds start_timer;
401 std::string task_name;
402 public:
403 TaskTimer(std::string task_name);
404
405 ~TaskTimer();
406 };
407
408 }}
409
410 #endif
411