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