From b6f5dc40c5168e5da9b9a4b4648f8763134ad2c5 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 27 Oct 2021 23:13:32 +0200 Subject: [PATCH] Add an option to show "rich tooltip" instead of system tooltip for setting controls. supermerill/SuperSlicer#1720 supermerill/SuperSlicer#1291 --- src/libslic3r/AppConfig.cpp | 9 ++++ src/slic3r/GUI/Field.cpp | 99 +++++++++++++++++++++++++++++----- src/slic3r/GUI/Field.hpp | 24 ++++++++- src/slic3r/GUI/Preferences.cpp | 36 ++++++++++--- src/slic3r/GUI/Preferences.hpp | 1 + 5 files changed, 146 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index bd01b0892..b03b73964 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -194,6 +194,15 @@ void AppConfig::set_defaults() if (get("default_action_on_new_project").empty()) set("default_action_on_new_project", "1"); + + if (get("use_rich_tooltip").empty()) + set("use_rich_tooltip", +#ifndef WIN32 + "1" +#else + "0" +#endif + ); } #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN else { diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index b7010bc1d..b90a89e73 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include "OG_CustomCtrl.hpp" @@ -212,6 +213,77 @@ wxString Field::get_tooltip_text(const wxString& default_string) return tooltip_text; } +wxString Field::get_rich_tooltip_text(const wxString& default_string) +{ + wxString tooltip_text(""); + wxString tooltip = _(m_opt.tooltip); + update_Slic3r_string(tooltip); + + std::string opt_id = m_opt_id; + auto hash_pos = opt_id.find("#"); + if (hash_pos != std::string::npos) { + opt_id.replace(hash_pos, 1, "["); + opt_id += "]"; + } + + if (tooltip.length() > 0) + tooltip_text = tooltip + "\n" + _(L("default value")) + ": " + + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string; + + return tooltip_text; +} + +wxString Field::get_rich_tooltip_title(const wxString& default_string) +{ + + std::string opt_id = m_opt_id; + auto hash_pos = opt_id.find("#"); + if (hash_pos != std::string::npos) { + opt_id.replace(hash_pos, 1, "["); + opt_id += "]"; + } + + return opt_id + ":"; +} + +void Field::set_tooltip(const wxString& default_string, wxWindow* window) { + if (window == nullptr) + window = getWindow(); + if (get_app_config()->get("use_rich_tooltip") == "1") { + this->m_rich_tooltip_timer.m_value = default_string; + window->Bind(wxEVT_ENTER_WINDOW, [this, window](wxMouseEvent& event) { + if (wxGetActiveWindow() && !this->m_rich_tooltip_timer.IsRunning()) { + this->m_rich_tooltip_timer.m_current_window = window; + this->m_rich_tooltip_timer.m_is_rich_tooltip_ready = true; + this->m_rich_tooltip_timer.StartOnce(500); + } + }); + window->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { + this->m_rich_tooltip_timer.m_is_rich_tooltip_ready = false; + wxWindowList tipWindow = this->getWindow()->GetChildren(); + if (tipWindow.size() > 0) { + wxWindow* tooltipWindow = tipWindow.GetLast()->GetData(); + if (tooltipWindow && tooltipWindow == this->m_rich_tooltip_timer.m_current_rich_tooltip) + tooltipWindow->Hide();// DismissAndNotify(); + } + }); + }else + window->SetToolTip(get_tooltip_text(default_string)); +} + +void RichTooltipTimer::Notify() { + if (wxGetActiveWindow() && this->m_is_rich_tooltip_ready && m_current_window) { + this->m_current_rich_tooltip = nullptr; + wxRichToolTip richTooltip( + m_field->get_rich_tooltip_title(this->m_value), + m_field->get_rich_tooltip_text(this->m_value)); + richTooltip.SetTimeout(120000, 0); + richTooltip.ShowFor(m_current_window); + wxWindowList tipWindow = m_current_window->GetChildren(); + this->m_current_rich_tooltip = tipWindow.GetLast()->GetData(); + } +} + bool Field::is_matched(const std::string& string, const std::string& pattern) { std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl @@ -511,7 +583,7 @@ void TextCtrl::BUILD() { m_last_meaningful_value = text_value; const long style = m_opt.multiline ? wxTE_MULTILINE : wxTE_PROCESS_ENTER/*0*/; - auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); + wxTextCtrl* temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); if (parent_is_custom_ctrl && m_opt.height < 0) opt_height = (double)temp->GetSize().GetHeight()/m_em_unit; temp->SetFont(m_opt.is_code ? @@ -526,8 +598,6 @@ void TextCtrl::BUILD() { temp->OSXDisableAllSmartSubstitutions(); #endif // __WXOSX__ - temp->SetToolTip(get_tooltip_text(text_value)); - if (style == wxTE_PROCESS_ENTER) { temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e) { @@ -590,6 +660,8 @@ void TextCtrl::BUILD() { */ // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); + + this->set_tooltip(text_value); } bool TextCtrl::value_was_changed() @@ -736,10 +808,10 @@ void CheckBox::BUILD() { on_change_field(); }), temp->GetId()); - temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false")); - // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); + + this->set_tooltip(check_value ? "true" : "false"); } void CheckBox::set_value(const boost::any& value, bool change_event) @@ -899,11 +971,12 @@ void SpinCtrl::BUILD() { } #endif }), temp->GetId()); - - temp->SetToolTip(get_tooltip_text(text_value)); // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); + + //prblem: it has 2 window, with a child: the mouse enter event won't fire if in children! + this->set_tooltip(text_value); } void SpinCtrl::propagate_value() @@ -1057,7 +1130,7 @@ void Choice::BUILD() { }), temp->GetId()); } - temp->SetToolTip(get_tooltip_text(temp->GetValue())); + this->set_tooltip(temp->GetValue()); } void Choice::suppress_scroll() @@ -1475,9 +1548,9 @@ void ColourPicker::BUILD() // // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); - temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); + window->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), window->GetId()); - temp->SetToolTip(get_tooltip_text(clr.GetAsString())); + this->set_tooltip(clr.GetAsString()); } void ColourPicker::set_undef_value(wxColourPickerCtrl* field) @@ -1588,8 +1661,8 @@ void PointCtrl::BUILD() // // recast as a wxWindow to fit the calling convention sizer = dynamic_cast(temp); - x_textctrl->SetToolTip(get_tooltip_text(X+", "+Y)); - y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y)); + this->set_tooltip(X + ", " + Y, x_textctrl); + this->set_tooltip(X + ", " + Y, y_textctrl); } void PointCtrl::msw_rescale() @@ -1695,7 +1768,7 @@ void StaticText::BUILD() // // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); - temp->SetToolTip(get_tooltip_text(legend)); + this->set_tooltip(legend); } void StaticText::msw_rescale() diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index bd38dc649..b963dd62c 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -39,6 +39,21 @@ using t_back_to_init = std::function; wxString double_to_string(double const value, const int max_precision = 6); wxString get_points_string(const std::vector& values); +class Field; +class RichTooltipTimer : public wxTimer +{ + Field* m_field; +public: + bool m_is_rich_tooltip_ready = false; + wxWindow* m_current_rich_tooltip = nullptr; + wxString m_value; + wxWindow* m_window2 = nullptr; //for point + wxWindow* m_current_window = nullptr; //for point + RichTooltipTimer(Field* field) : m_field(field) {}; + + void Notify() override; +}; + class Field { protected: // factory function to defer and enforce creation of derived type. @@ -110,11 +125,16 @@ public: inline void toggle(bool en) { en ? enable() : disable(); } virtual wxString get_tooltip_text(const wxString& default_string); + // hack via richtooltip that are also hacked + RichTooltipTimer m_rich_tooltip_timer; + virtual wxString get_rich_tooltip_text(const wxString& default_string); + virtual wxString get_rich_tooltip_title(const wxString& default_string); + void set_tooltip(const wxString& default_string, wxWindow* window = nullptr); void field_changed() { on_change_field(); } - Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; - Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; + Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id), m_rich_tooltip_timer(this) {}; + Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id), m_rich_tooltip_timer(this) {}; virtual ~Field(); /// If you don't know what you are getting back, check both methods for nullptr. diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 1d22100ee..433486c9a 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -151,6 +151,7 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("no_defaults") == "1" }); option = Option(def, "no_defaults"); m_optgroups_general.back()->append_single_option_line(option); + m_values_need_restart.push_back("no_defaults"); def.label = L("Show incompatible print and filament presets"); def.type = coBool; @@ -263,6 +264,21 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_new_project") == "1" }); option = Option(def, "default_action_on_new_project"); m_optgroups_general.back()->append_single_option_line(option); + + def.label = L("Use custom tooltip"); + def.type = coBool; + def.tooltip = L("On some OS like MacOS or some Linux, tooltips can't stay on for a long time. This setting replaces native tooltips with custom dialogs to improve readability (only for settings)." + "\nNote that for the number controls, you need to hover the arrows to get the custom tooltip."); + def.set_default_value(new ConfigOptionBool{ app_config->has("use_rich_tooltip") ? app_config->get("use_rich_tooltip") == "1": +#if __APPLE__ + true +#else + false +#endif + }); + option = Option(def, "use_rich_tooltip"); + m_optgroups_general.back()->append_single_option_line(option); + m_values_need_restart.push_back("use_rich_tooltip"); } #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32 @@ -445,12 +461,12 @@ void PreferencesDialog::build() def.label = L("Tab icon size"); def.type = coInt; - def.tooltip = L("Size of the tab icons, in pixels. Set to 0 to remove icons." - "\nYou have to restart the application before any change will be taken into account."); + def.tooltip = L("Size of the tab icons, in pixels. Set to 0 to remove icons."); def.set_default_value(new ConfigOptionInt{ atoi(app_config->get("tab_icon_size").c_str()) }); option = Option(def, "tab_icon_size"); option.opt.width = 6; m_optgroup_gui->append_single_option_line(option); + m_values_need_restart.push_back("tab_icon_size"); } @@ -465,54 +481,54 @@ void PreferencesDialog::build() def.type = coString; def.tooltip = _u8L("Very dark color, in the RGB hex format.") + " " + _u8L("Mainly used as background or dark text color.") - + "\n" + _u8L("You have to restart the application before any change will be taken into account.") + "\n" + _u8L("Slic3r(yellow): ada230, PrusaSlicer(orange): c46737, SuperSlicer(blue): 0047c7"); def.set_default_value(new ConfigOptionString{ app_config->get("color_very_dark") }); option = Option(def, "color_very_dark"); option.opt.width = 6; m_optgroup_gui->append_single_option_line(option); + m_values_need_restart.push_back("color_very_dark"); def.label = L("Dark gui color"); def.type = coString; def.tooltip = _u8L("Dark color, in the RGB hex format.") + " " + _u8L("Mainly used as icon color.") - + "\n" + _u8L("You have to restart the application before any change will be taken into account.") + "\n" + _u8L("Slic3r(yellow): cabe39, PrusaSlicer(orange): ed6b21, SuperSlicer(blue): 2172eb"); def.set_default_value(new ConfigOptionString{ app_config->get("color_dark") }); option = Option(def, "color_dark"); option.opt.width = 6; m_optgroup_gui->append_single_option_line(option); + m_values_need_restart.push_back("color_dark"); def.label = L("Gui color"); def.type = coString; def.tooltip = _u8L("Main color, in the RGB hex format.") - + "\n" + _u8L("You have to restart the application before any change will be taken into account.") + " " + _u8L("Slic3r(yellow): eddc21, PrusaSlicer(orange): fd7e42, SuperSlicer(blue): 428dfd"); def.set_default_value(new ConfigOptionString{ app_config->get("color") }); option = Option(def, "color"); option.opt.width = 6; m_optgroup_gui->append_single_option_line(option); + m_values_need_restart.push_back("color"); def.label = L("Light gui color"); def.type = coString; def.tooltip = _u8L("Light color, in the RGB hex format.") - + "\n" + _u8L("You have to restart the application before any change will be taken into account.") + " " + _u8L("Slic3r(yellow): ffee38, PrusaSlicer(orange): feac8b, SuperSlicer(blue): 8bb9fe"); def.set_default_value(new ConfigOptionString{ app_config->get("color_light") }); option = Option(def, "color_light"); option.opt.width = 6; m_optgroup_gui->append_single_option_line(option); + m_values_need_restart.push_back("color_light"); def.label = L("Very light gui color"); def.type = coString; def.tooltip = _u8L("Very light color, in the RGB hex format.") + " " + _u8L("Mainly used as light text color.") - + "\n" + _u8L("You have to restart the application before any change will be taken into account.") + "\n" + _u8L("Slic3r(yellow): fef48b, PrusaSlicer(orange): ff7d38, SuperSlicer(blue): 428cff"); def.set_default_value(new ConfigOptionString{ app_config->get("color_very_light") }); option = Option(def, "color_very_light"); option.opt.width = 6; m_optgroup_gui->append_single_option_line(option); + m_values_need_restart.push_back("color_very_light"); activate_options_tab(m_optgroup_gui); @@ -557,7 +573,11 @@ void PreferencesDialog::build() void PreferencesDialog::accept() { - if (m_values.find("no_defaults") != m_values.end()) { + bool need_restart = false; + for (auto key : m_values_need_restart) + if (m_values.find(key) != m_values.end()) + need_restart = true; + if (need_restart) { warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME)); } diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index e5ba80b7a..ab372601c 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -18,6 +18,7 @@ class ConfigOptionsGroup; class PreferencesDialog : public DPIDialog { std::map m_values; + std::vector m_values_need_restart; std::vector> m_optgroups_general; std::shared_ptr m_optgroup_paths; std::shared_ptr m_optgroup_camera;