#ifndef slic3r_GUI_Utils_hpp_ #define slic3r_GUI_Utils_hpp_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Event.hpp" class wxCheckBox; class wxTopLevelWindow; class wxRect; #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT #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))) #else #define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) 0 #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT namespace Slic3r { namespace GUI { #ifdef _WIN32 // USB HID attach / detach events from Windows OS. using HIDDeviceAttachedEvent = Event; using HIDDeviceDetachedEvent = Event; wxDECLARE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent); wxDECLARE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent); // Disk aka Volume attach / detach events from Windows OS. using VolumeAttachedEvent = SimpleEvent; using VolumeDetachedEvent = SimpleEvent; wxDECLARE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent); wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent); #endif /* _WIN32 */ wxTopLevelWindow* find_toplevel_parent(wxWindow *window); void on_window_geometry(wxTopLevelWindow *tlw, std::function callback); enum { DPI_DEFAULT = 96 }; int get_dpi_for_window(wxWindow *window); wxFont get_default_font_for_dpi(int dpi); #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) struct DpiChangedEvent : public wxEvent { int dpi; wxRect rect; DpiChangedEvent(wxEventType eventType, int dpi, wxRect rect) : wxEvent(0, eventType), dpi(dpi), rect(rect) {} virtual wxEvent *Clone() const { return new DpiChangedEvent(*this); } }; wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); #endif // !wxVERSION_EQUAL_OR_GREATER_THAN template class DPIAware : public P { 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) : P(parent, id, title, pos, size, style, name) { int dpi = get_dpi_for_window(this); m_scale_factor = (float)dpi / (float)DPI_DEFAULT; m_prev_scale_factor = m_scale_factor; m_normal_font = get_default_font_for_dpi(dpi); /* Because of default window font is a primary display font, * We should set correct font for window before getting em_unit value. */ #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList this->SetFont(m_normal_font); #endif // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT m_em_unit = std::max(10, 10.0f * m_scale_factor); #else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // recalc_font(); #if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); if (!m_can_rescale) return; #if ENABLE_LAYOUT_NO_RESTART if (m_force_rescale || is_new_scale_factor()) rescale(wxRect()); #else if (is_new_scale_factor()) rescale(wxRect()); #endif // ENABLE_LAYOUT_NO_RESTART }); #else this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); if (!m_can_rescale) return; #if ENABLE_LAYOUT_NO_RESTART if (m_force_rescale || is_new_scale_factor()) rescale(evt.rect); #else if (is_new_scale_factor()) rescale(evt.rect); #endif // ENABLE_LAYOUT_NO_RESTART }); #endif // wxVERSION_EQUAL_OR_GREATER_THAN this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event) { event.Skip(); // Suppress application rescaling, when a MainFrame moving is not ended m_can_rescale = false; }); this->Bind(wxEVT_MOVE_END, [this](wxMoveEvent& event) { event.Skip(); m_can_rescale = is_new_scale_factor(); // If scale factor is different after moving of MainFrame ... if (m_can_rescale) // ... rescale application rescale(event.GetRect()); else // set value to _true_ in purpose of possibility of a display dpi changing from System Settings m_can_rescale = true; }); this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) { event.Skip(); on_sys_color_changed(); }); } virtual ~DPIAware() {} float scale_factor() const { return m_scale_factor; } float prev_scale_factor() const { return m_prev_scale_factor; } int em_unit() const { return m_em_unit; } // int font_size() const { return m_font_size; } const wxFont& normal_font() const { return m_normal_font; } #if ENABLE_LAYOUT_NO_RESTART void enable_force_rescale() { m_force_rescale = true; } #endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; virtual void on_sys_color_changed() {}; private: float m_scale_factor; int m_em_unit; // int m_font_size; wxFont m_normal_font; float m_prev_scale_factor; bool m_can_rescale{ true }; #if ENABLE_LAYOUT_NO_RESTART bool m_force_rescale{ false }; #endif // ENABLE_LAYOUT_NO_RESTART int m_new_font_point_size; // void recalc_font() // { // wxClientDC dc(this); // const auto metrics = dc.GetFontMetrics(); // m_font_size = metrics.height; // m_em_unit = metrics.averageWidth; // } // check if new scale is differ from previous bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; } // function for a font scaling of the window void scale_win_font(wxWindow *window, const int font_point_size) { wxFont new_font(window->GetFont()); new_font.SetPointSize(font_point_size); window->SetFont(new_font); } // recursive function for scaling fonts for all controls in Window void scale_controls_fonts(wxWindow *window, const int font_point_size) { auto children = window->GetChildren(); for (auto child : children) { scale_controls_fonts(child, font_point_size); scale_win_font(child, font_point_size); } window->Layout(); } void rescale(const wxRect &suggested_rect) { this->Freeze(); #if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) if (m_force_rescale) { #endif // ENABLE_LAYOUT_NO_RESTART // rescale fonts of all controls scale_controls_fonts(this, m_new_font_point_size); // rescale current window font scale_win_font(this, m_new_font_point_size); #if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) m_force_rescale = false; } #endif // ENABLE_LAYOUT_NO_RESTART // set normal application font as a current window font m_normal_font = this->GetFont(); // update em_unit value for new window font #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT m_em_unit = std::max(10, 10.0f * m_scale_factor); #else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // rescale missed controls sizes and images on_dpi_changed(suggested_rect); this->Layout(); this->Thaw(); // reset previous scale factor from current scale factor value m_prev_scale_factor = m_scale_factor; } }; typedef DPIAware DPIFrame; typedef DPIAware DPIDialog; class EventGuard { // This is a RAII-style smart-ptr-like guard that will bind any event to any event handler // and unbind it as soon as it goes out of scope or unbind() is called. // This can be used to solve the annoying problem of wx events being delivered to freed objects. private: // This is a way to type-erase both the event type as well as the handler: struct EventStorageBase { virtual ~EventStorageBase() {} }; template struct EventStorageFun : EventStorageBase { wxEvtHandler *emitter; EvTag tag; Fun fun; EventStorageFun(wxEvtHandler *emitter, const EvTag &tag, Fun fun) : emitter(emitter) , tag(tag) , fun(std::move(fun)) { emitter->Bind(this->tag, this->fun); } virtual ~EventStorageFun() { emitter->Unbind(tag, fun); } }; template struct EventStorageMethod : EventStorageBase { typedef void(Class::* MethodPtr)(EvArg &); wxEvtHandler *emitter; EvTag tag; MethodPtr method; EvHandler *handler; EventStorageMethod(wxEvtHandler *emitter, const EvTag &tag, MethodPtr method, EvHandler *handler) : emitter(emitter) , tag(tag) , method(method) , handler(handler) { emitter->Bind(tag, method, handler); } virtual ~EventStorageMethod() { emitter->Unbind(tag, method, handler); } }; std::unique_ptr event_storage; public: EventGuard() {} EventGuard(const EventGuard&) = delete; EventGuard(EventGuard &&other) : event_storage(std::move(other.event_storage)) {} template EventGuard(wxEvtHandler *emitter, const EvTag &tag, Fun fun) :event_storage(new EventStorageFun(emitter, tag, std::move(fun))) {} template EventGuard(wxEvtHandler *emitter, const EvTag &tag, void(Class::* method)(EvArg &), EvHandler *handler) :event_storage(new EventStorageMethod(emitter, tag, method, handler)) {} EventGuard& operator=(const EventGuard&) = delete; EventGuard& operator=(EventGuard &&other) { event_storage = std::move(other.event_storage); return *this; } void unbind() { event_storage.reset(nullptr); } explicit operator bool() const { return !!event_storage; } }; class CheckboxFileDialog : public wxFileDialog { public: CheckboxFileDialog(wxWindow *parent, const wxString &checkbox_label, bool checkbox_value, const wxString &message = wxFileSelectorPromptStr, const wxString &default_dir = wxEmptyString, const wxString &default_file = wxEmptyString, const wxString &wildcard = wxFileSelectorDefaultWildcardStr, long style = wxFD_DEFAULT_STYLE, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, const wxString &name = wxFileDialogNameStr ); bool get_checkbox_value() const; private: struct ExtraPanel : public wxPanel { wxCheckBox *cbox; ExtraPanel(wxWindow *parent); static wxWindow* ctor(wxWindow *parent); }; wxString checkbox_label; }; class WindowMetrics { private: wxRect rect; bool maximized; WindowMetrics() : maximized(false) {} public: static WindowMetrics from_window(wxTopLevelWindow *window); static boost::optional deserialize(const std::string &str); const wxRect& get_rect() const { return rect; } bool get_maximized() const { return maximized; } void sanitize_for_display(const wxRect &screen_rect); std::string serialize() const; }; std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics); }} #endif