#include "HintNotification.hpp" #include "ImGuiWrapper.hpp" #include "format.hpp" #include "I18N.hpp" #include "GUI_ObjectList.hpp" #include "GLCanvas3D.hpp" #include "MainFrame.hpp" #include "Tab.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/PrintConfig.hpp" #include #include #include #include #include #include #include #include #define HINTS_CEREAL_VERSION 1 // structure for writing used hints into binary file with version struct HintsCerealData { std::vector my_data; // cereal will supply the version automatically when loading or saving // The version number comes from the CEREAL_CLASS_VERSION macro template void serialize(Archive& ar, std::uint32_t const version) { // You can choose different behaviors depending on the version // This is useful if you need to support older variants of your codebase // interacting with newer ones if (version > HINTS_CEREAL_VERSION) throw Slic3r::IOError("Version of hints.cereal is higher than current version."); else ar(my_data); } }; // version of used hints binary file CEREAL_CLASS_VERSION(HintsCerealData, HINTS_CEREAL_VERSION); namespace Slic3r { namespace GUI { const std::string BOLD_MARKER_START = ""; const std::string BOLD_MARKER_END = ""; const std::string HYPERTEXT_MARKER_START = ""; const std::string HYPERTEXT_MARKER_END = ""; namespace { inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) { if (fading_out) ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); else ImGui::PushStyleColor(idx, col); } void write_used_binary(const std::vector& ids) { boost::filesystem::ofstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"), std::ios::binary); cereal::BinaryOutputArchive archive(file); HintsCerealData cd { ids }; try { archive(cd); } catch (const std::exception& ex) { BOOST_LOG_TRIVIAL(error) << "Failed to write to hints.cereal. " << ex.what(); } } void read_used_binary(std::vector& ids) { boost::filesystem::path path(boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"); if (!boost::filesystem::exists(path)) { BOOST_LOG_TRIVIAL(warning) << "Failed to load to hints.cereal. File does not exists. " << path.string(); return; } boost::filesystem::ifstream file(path); cereal::BinaryInputArchive archive(file); HintsCerealData cd; try { archive(cd); } catch (const std::exception& ex) { BOOST_LOG_TRIVIAL(error) << "Failed to load to hints.cereal. " << ex.what(); return; } ids = cd.my_data; } enum TagCheckResult { TagCheckAffirmative, TagCheckNegative, TagCheckNotCompatible }; // returns if in mode defined by tag TagCheckResult tag_check_mode(const std::string& tag) { std::vector allowed_tags = {"simple", "advanced", "expert"}; if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) { ConfigOptionMode config_mode = wxGetApp().get_mode(); if (config_mode == ConfigOptionMode::comSimple) return (tag == "simple" ? TagCheckAffirmative : TagCheckNegative); else if (config_mode == ConfigOptionMode::comAdvanced) return (tag == "advanced" ? TagCheckAffirmative : TagCheckNegative); else if (config_mode == ConfigOptionMode::comExpert) return (tag == "expert" ? TagCheckAffirmative : TagCheckNegative); } return TagCheckNotCompatible; } TagCheckResult tag_check_tech(const std::string& tag) { std::vector allowed_tags = { "FFF", "MMU", "SLA" }; if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) { const PrinterTechnology tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); if (tech == ptFFF) { // MMU / FFF bool is_mmu = wxGetApp().extruders_edited_cnt() > 1; if (tag == "MMU") return (is_mmu ? TagCheckAffirmative : TagCheckNegative); return (tag == "FFF" ? TagCheckAffirmative : TagCheckNegative); } else { // SLA return (tag == "SLA" ? TagCheckAffirmative : TagCheckNegative); } } return TagCheckNotCompatible; } TagCheckResult tag_check_system(const std::string& tag) { std::vector allowed_tags = { "Windows", "Linux", "OSX" }; if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) { if (tag =="Windows") #ifdef WIN32 return TagCheckAffirmative; #else return TagCheckNegative; #endif // WIN32 if (tag == "Linux") #ifdef __linux__ return TagCheckAffirmative; #else return TagCheckNegative; #endif // __linux__ if (tag == "OSX") #ifdef __APPLE__ return TagCheckAffirmative; #else return TagCheckNegative; #endif // __apple__ } return TagCheckNotCompatible; } TagCheckResult tag_check_material(const std::string& tag) { if (const GUI::Tab* tab = wxGetApp().get_tab(Preset::Type::TYPE_FILAMENT)) { // search PrintConfig filament_type to find if allowed tag if (wxGetApp().app_config->get("filament_type").find(tag)) { const Preset& preset = tab->m_presets->get_edited_preset(); const auto* opt = preset.config.opt("filament_type"); if (opt->values[0] == tag) return TagCheckAffirmative; return TagCheckNegative; } return TagCheckNotCompatible; } /* TODO: SLA materials else if (const GUI::Tab* tab = wxGetApp().get_tab(Preset::Type::TYPE_SLA_MATERIAL)) { //if (wxGetApp().app_config->get("material_type").find(tag)) { const Preset& preset = tab->m_presets->get_edited_preset(); const auto* opt = preset.config.opt("material_type"); if (opt->values[0] == tag) return TagCheckAffirmative; return TagCheckNegative; //} return TagCheckNotCompatible; }*/ return TagCheckNotCompatible; } // return true if NOT in disabled mode. bool tags_check(const std::string& disabled_tags, const std::string& enabled_tags) { if (disabled_tags.empty() && enabled_tags.empty()) return true; // enabled tags must ALL return affirmative or check fails if (!enabled_tags.empty()) { std::string tag; for (size_t i = 0; i < enabled_tags.size(); i++) { if (enabled_tags[i] == ' ') { tag.erase(); continue; } if (enabled_tags[i] != ';') { tag += enabled_tags[i]; } if (enabled_tags[i] == ';' || i == enabled_tags.size() - 1) { if (!tag.empty()) { TagCheckResult result; result = tag_check_mode(tag); if (result == TagCheckResult::TagCheckNegative) return false; if (result == TagCheckResult::TagCheckAffirmative) continue; result = tag_check_tech(tag); if (result == TagCheckResult::TagCheckNegative) return false; if (result == TagCheckResult::TagCheckAffirmative) continue; result = tag_check_system(tag); if (result == TagCheckResult::TagCheckNegative) return false; if (result == TagCheckResult::TagCheckAffirmative) continue; result = tag_check_material(tag); if (result == TagCheckResult::TagCheckNegative) return false; if (result == TagCheckResult::TagCheckAffirmative) continue; BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in enabled_tags not compatible."; // non compatible in enabled means return false since all enabled must be affirmative. return false; } } } } // disabled tags must all NOT return affirmative or check fails if (!disabled_tags.empty()) { std::string tag; for (size_t i = 0; i < disabled_tags.size(); i++) { if (disabled_tags[i] == ' ') { tag.erase(); continue; } if (disabled_tags[i] != ';') { tag += disabled_tags[i]; } if (disabled_tags[i] == ';' || i == disabled_tags.size() - 1) { if (!tag.empty()) { TagCheckResult result; result = tag_check_mode(tag); if (result == TagCheckResult::TagCheckNegative) continue; if (result == TagCheckResult::TagCheckAffirmative) return false; result = tag_check_tech(tag); if (result == TagCheckResult::TagCheckNegative) continue; if (result == TagCheckResult::TagCheckAffirmative) return false; result = tag_check_system(tag); if (result == TagCheckResult::TagCheckAffirmative) return false; if (result == TagCheckResult::TagCheckNegative) continue; result = tag_check_material(tag); if (result == TagCheckResult::TagCheckAffirmative) return false; if (result == TagCheckResult::TagCheckNegative) continue; BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in disabled_tags not compatible."; } } } } return true; } void launch_browser_if_allowed(const std::string& url) { wxGetApp().open_browser_with_warning_dialog(url); } } //namespace HintDatabase::~HintDatabase() { if (m_initialized) { write_used_binary(m_used_ids); } } void HintDatabase::uninit() { if (m_initialized) { write_used_binary(m_used_ids); } m_initialized = false; } void HintDatabase::init() { load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); m_initialized = true; } void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) { namespace pt = boost::property_tree; pt::ptree tree; boost::nowide::ifstream ifs(path.string()); try { pt::read_ini(ifs, tree); } catch (const boost::property_tree::ini_parser::ini_parser_error& err) { throw Slic3r::RuntimeError(format("Failed loading hints file \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); } for (const auto& section : tree) { if (boost::starts_with(section.first, "hint:")) { // create std::map with tree data std::map dict; for (const auto& data : section.second) { dict.emplace(data.first, data.second.data()); } // unique id string [hint:id] (trim "hint:") std::string id_string = section.first.substr(5); id_string = std::to_string(std::hash{}(id_string)); // unescaping and translating all texts and saving all data common for all hint types std::string fulltext; std::string text1; std::string hypertext_text; std::string follow_text; // tags std::string disabled_tags; std::string enabled_tags; // optional link to documentation (accessed from button) std::string documentation_link; // randomized weighted order variables size_t weight = 1; bool was_displayed = is_used(id_string); //unescape text1 unescape_string_cstyle(_utf8(dict["text"]), fulltext); // replace and for imgui markers std::string marker_s(1, ImGui::ColorMarkerStart); std::string marker_e(1, ImGui::ColorMarkerEnd); // start marker size_t marker_pos = fulltext.find(BOLD_MARKER_START); while (marker_pos != std::string::npos) { fulltext.replace(marker_pos, 3, marker_s); marker_pos = fulltext.find(BOLD_MARKER_START, marker_pos); } // end marker marker_pos = fulltext.find(BOLD_MARKER_END); while (marker_pos != std::string::npos) { fulltext.replace(marker_pos, 4, marker_e); marker_pos = fulltext.find(BOLD_MARKER_END, marker_pos); } // divide fulltext size_t hypertext_start = fulltext.find(HYPERTEXT_MARKER_START); if (hypertext_start != std::string::npos) { //hypertext exists fulltext.erase(hypertext_start, HYPERTEXT_MARKER_START.size()); if (fulltext.find(HYPERTEXT_MARKER_START) != std::string::npos) { // This must not happen - only 1 hypertext allowed BOOST_LOG_TRIVIAL(error) << "Hint notification with multiple hypertexts: " << _utf8(dict["text"]); continue; } size_t hypertext_end = fulltext.find(HYPERTEXT_MARKER_END); if (hypertext_end == std::string::npos) { // hypertext was not correctly ended BOOST_LOG_TRIVIAL(error) << "Hint notification without hypertext end marker: " << _utf8(dict["text"]); continue; } fulltext.erase(hypertext_end, HYPERTEXT_MARKER_END.size()); if (fulltext.find(HYPERTEXT_MARKER_END) != std::string::npos) { // This must not happen - only 1 hypertext end allowed BOOST_LOG_TRIVIAL(error) << "Hint notification with multiple hypertext end markers: " << _utf8(dict["text"]); continue; } text1 = fulltext.substr(0, hypertext_start); hypertext_text = fulltext.substr(hypertext_start, hypertext_end - hypertext_start); follow_text = fulltext.substr(hypertext_end); } else { text1 = fulltext; } if (dict.find("disabled_tags") != dict.end()) { disabled_tags = dict["disabled_tags"]; } if (dict.find("enabled_tags") != dict.end()) { enabled_tags = dict["enabled_tags"]; } if (dict.find("documentation_link") != dict.end()) { documentation_link = dict["documentation_link"]; } if (dict.find("weight") != dict.end()) { weight = (size_t)std::max(1, std::atoi(dict["weight"].c_str())); } // create HintData if (dict.find("hypertext_type") != dict.end()) { //link to internet if(dict["hypertext_type"] == "link") { std::string hypertext_link = dict["hypertext_link"]; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); } }; m_loaded_hints.emplace_back(hint_data); // highlight settings } else if (dict["hypertext_type"] == "settings") { std::string opt = dict["hypertext_settings_opt"]; Preset::Type type = static_cast(std::atoi(dict["hypertext_settings_type"].c_str())); std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]); HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } }; m_loaded_hints.emplace_back(hint_data); // open preferences } else if(dict["hypertext_type"] == "preferences") { int page = static_cast(std::atoi(dict["hypertext_preferences_page"].c_str())); HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } }; m_loaded_hints.emplace_back(hint_data); } else if (dict["hypertext_type"] == "plater") { std::string item = dict["hypertext_plater_item"]; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } }; m_loaded_hints.emplace_back(hint_data); } else if (dict["hypertext_type"] == "gizmo") { std::string item = dict["hypertext_gizmo_item"]; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } }; m_loaded_hints.emplace_back(hint_data); } else if (dict["hypertext_type"] == "gallery") { HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() { // Deselect all objects, otherwise gallery wont show. wxGetApp().plater()->canvas3D()->deselect_all(); wxGetApp().obj_list()->load_shape_object_from_gallery(); } }; m_loaded_hints.emplace_back(hint_data); } else if (dict["hypertext_type"] == "menubar") { wxString menu(_("&" + dict["hypertext_menubar_menu_name"])); wxString item(_(dict["hypertext_menubar_item_name"])); HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [menu, item]() { wxGetApp().mainframe->open_menubar_item(menu, item); } }; m_loaded_hints.emplace_back(hint_data); } } else { // plain text without hypertext HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link }; m_loaded_hints.emplace_back(hint_data); } } } } HintData* HintDatabase::get_hint(bool new_hint/* = true*/) { if (! m_initialized) { init(); new_hint = true; } if (m_loaded_hints.empty()) { BOOST_LOG_TRIVIAL(error) << "There were no hints loaded from hints.ini file."; return nullptr; } try { if (new_hint) m_hint_id = get_next(); } catch (const std::exception&) { return nullptr; } return &m_loaded_hints[m_hint_id]; } size_t HintDatabase::get_next() { if (!m_sorted_hints) { auto compare_wieght = [](const HintData& a, const HintData& b){ return a.weight < b.weight; }; std::sort(m_loaded_hints.begin(), m_loaded_hints.end(), compare_wieght); m_sorted_hints = true; srand(time(NULL)); } std::vector candidates; // index in m_loaded_hints // total weight size_t total_weight = 0; for (size_t i = 0; i < m_loaded_hints.size(); i++) { if (!m_loaded_hints[i].was_displayed && tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) { candidates.emplace_back(i); total_weight += m_loaded_hints[i].weight; } } // all were shown if (total_weight == 0) { clear_used(); for (size_t i = 0; i < m_loaded_hints.size(); i++) { m_loaded_hints[i].was_displayed = false; if (tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) { candidates.emplace_back(i); total_weight += m_loaded_hints[i].weight; } } } if (total_weight == 0) { BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed. No suitable hint was found."; throw std::exception(); } size_t random_number = rand() % total_weight + 1; size_t current_weight = 0; for (size_t i = 0; i < candidates.size(); i++) { current_weight += m_loaded_hints[candidates[i]].weight; if (random_number <= current_weight) { set_used(m_loaded_hints[candidates[i]].id_string); m_loaded_hints[candidates[i]].was_displayed = true; return candidates[i]; } } BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed."; throw std::exception(); } bool HintDatabase::is_used(const std::string& id) { // load used ids from file if (!m_used_ids_loaded) { read_used_binary(m_used_ids); m_used_ids_loaded = true; } // check if id is in used for (const std::string& used_id : m_used_ids) { if (used_id == id) { return true; } } return false; } void HintDatabase::set_used(const std::string& id) { // check needed? if (!is_used(id)) { m_used_ids.emplace_back(id); } } void HintDatabase::clear_used() { m_used_ids.clear(); } void NotificationManager::HintNotification::count_spaces() { //determine line width m_line_height = ImGui::CalcTextSize("A").y; std::string text; text = ImGui::WarningMarker; float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width * 1.5f + m_line_height / 2; // no left button picture //m_left_indentation = m_line_height; if (m_documentation_link.empty()) m_window_width_offset = m_left_indentation + m_line_height * 3.f; else m_window_width_offset = m_left_indentation + m_line_height * 5.5f; m_window_width = m_line_height * 25; } void NotificationManager::HintNotification::count_lines() { std::string text = m_text1; size_t last_end = 0; m_lines_count = 0; if (text.empty()) return; m_endlines.clear(); while (last_end < text.length() - 1) { size_t next_hard_end = text.find_first_of('\n', last_end); if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { //next line is ended by '/n' m_endlines.push_back(next_hard_end); last_end = next_hard_end + 1; } else { // find next suitable endline if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { // more than one line till end size_t next_space = text.find_first_of(' ', last_end); if (next_space > 0 && next_space < text.length()) { size_t next_space_candidate = text.find_first_of(' ', next_space + 1); while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { next_space = next_space_candidate; next_space_candidate = text.find_first_of(' ', next_space + 1); } } else { next_space = text.length(); } // when one word longer than line. if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset || ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 5 * 3 ) { float width_of_a = ImGui::CalcTextSize("a").x; int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { letter_count++; } m_endlines.push_back(last_end + letter_count); last_end += letter_count; } else { m_endlines.push_back(next_space); last_end = next_space + 1; } } else { m_endlines.push_back(text.length()); last_end = text.length(); } } m_lines_count++; } int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0; int size_of_last_line = ImGui::CalcTextSize(text.substr(prev_end, last_end - prev_end).c_str()).x; // hypertext calculation if (!m_hypertext.empty()) { if (size_of_last_line + ImGui::CalcTextSize(m_hypertext.c_str()).x > m_window_width - m_window_width_offset) { // hypertext on new line size_of_last_line = ImGui::CalcTextSize((m_hypertext + " ").c_str()).x; m_endlines.push_back(last_end); m_lines_count++; } else { size_of_last_line += ImGui::CalcTextSize((m_hypertext + " ").c_str()).x; } } if (!m_text2.empty()) { text = m_text2; last_end = 0; m_endlines2.clear(); // if size_of_last_line too large to fit anything size_t first_end = std::min(text.find_first_of('\n'), text.find_first_of(' ')); if (size_of_last_line >= m_window_width - m_window_width_offset - ImGui::CalcTextSize(text.substr(0, first_end).c_str()).x) { m_endlines2.push_back(0); size_of_last_line = 0; } while (last_end < text.length() - 1) { size_t next_hard_end = text.find_first_of('\n', last_end); if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { //next line is ended by '/n' m_endlines2.push_back(next_hard_end); last_end = next_hard_end + 1; } else { // find next suitable endline if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset - size_of_last_line) { // more than one line till end size_t next_space = text.find_first_of(' ', last_end); if (next_space > 0) { size_t next_space_candidate = text.find_first_of(' ', next_space + 1); while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { next_space = next_space_candidate; next_space_candidate = text.find_first_of(' ', next_space + 1); } } else { next_space = text.length(); } // when one word longer than line. if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset - size_of_last_line || ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 5 * 3 ) { float width_of_a = ImGui::CalcTextSize("a").x; int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a); while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { letter_count++; } m_endlines2.push_back(last_end + letter_count); last_end += letter_count; } else { m_endlines2.push_back(next_space); last_end = next_space + 1; } } else { m_endlines2.push_back(text.length()); last_end = text.length(); } } if (size_of_last_line == 0) // if first line is continuation of previous text, do not add to line count. m_lines_count++; size_of_last_line = 0; // should countain value only for first line (with hypertext) } } } void NotificationManager::HintNotification::init() { // Do not init closing notification if (is_finished()) return; count_spaces(); count_lines(); m_multiline = true; m_notification_start = GLCanvas3D::timestamp_now(); if (m_state == EState::Unknown) m_state = EState::Shown; } void NotificationManager::HintNotification::set_next_window_size(ImGuiWrapper& imgui) { /* m_window_height = m_multiline ? (m_lines_count + 1.f) * m_line_height : 4.f * m_line_height; m_window_height += 1 * m_line_height; // top and bottom */ m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 5.f * m_line_height); } bool NotificationManager::HintNotification::on_text_click() { if (m_hypertext_callback != nullptr && (!m_runtime_disable || tags_check(m_disabled_tags, m_enabled_tags))) m_hypertext_callback(); return false; } void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (!m_has_hint_data) { retrieve_data(); } float x_offset = m_left_indentation; int last_end = 0; float starting_y = (m_lines_count < 4 ? m_line_height / 2 * (4 - m_lines_count + 1) : m_line_height / 2); float shift_y = m_line_height; std::string line; for (size_t i = 0; i < (m_multiline ? /*m_lines_count*/m_endlines.size() : 2); i++) { line.clear(); ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { if (i == 1 && m_endlines.size() > 2 && !m_multiline) { // second line with "more" hypertext line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { line = line.substr(0, line.length() - 1); } line += ".."; } else { // regural line line = m_text1.substr(last_end, m_endlines[i] - last_end); } // first line is headline (for hint notification it must be divided by \n) if (m_text1.find('\n') >= m_endlines[i]) { line = ImGui::ColorMarkerStart + line + ImGui::ColorMarkerEnd; } // Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line) if (line.find_first_of(ImGui::ColorMarkerEnd) < line.find_first_of(ImGui::ColorMarkerStart)) { line = ImGui::ColorMarkerStart + line; } last_end = m_endlines[i]; if (m_text1.size() > m_endlines[i]) last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); imgui.text(line.c_str()); } } //hyperlink text if (!m_multiline && m_lines_count > 2) { render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true); } else if (!m_hypertext.empty()) { render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty()? "": " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); } // text2 if (!m_text2.empty() && m_multiline) { starting_y += (m_endlines.size() - 1) * shift_y; last_end = 0; for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : 2); i++) { if (i == 0) //first line X is shifted by hypertext ImGui::SetCursorPosX(x_offset + ImGui::CalcTextSize((line + m_hypertext + (line.empty() ? " " : " ")).c_str()).x); else ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); line.clear(); if (m_endlines2.size() > i && m_text2.size() >= m_endlines2[i]) { // regural line line = m_text2.substr(last_end, m_endlines2[i] - last_end); // Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line) if (line.find_first_of(ImGui::ColorMarkerEnd) < line.find_first_of(ImGui::ColorMarkerStart)) { line = ImGui::ColorMarkerStart + line; } last_end = m_endlines2[i]; if (m_text2.size() > m_endlines2[i]) last_end += (m_text2[m_endlines2[i]] == '\n' || m_text2[m_endlines2[i]] == ' ' ? 1 : 0); imgui.text(line.c_str()); } } } } void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; button_text = ImGui::CloseNotifButton; if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), ImVec2(win_pos.x, win_pos.y + win_size.y - 2 * m_line_height), true)) { button_text = ImGui::CloseNotifHoverButton; } ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); m_close_b_w = button_size.y; if (m_lines_count <= 3) { m_close_b_y = win_size.y / 2 - button_size.y * 1.25f; ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(m_close_b_y); } else { ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { close(); } //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); if (imgui.button(" ", m_line_height * 2.125, win_size.y - 2 * m_line_height)) { close(); } ImGui::PopStyleColor(5); //render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); render_logo(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); render_preferences_button(imgui, win_pos_x, win_pos_y); if (!m_documentation_link.empty() && wxGetApp().app_config->get("suppress_hyperlinks") != "1") { render_documentation_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); } } void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); std::string button_text; button_text = ImGui::PreferencesButton; //hover if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 15.f, win_pos_y + m_window_height - 1.75f * m_line_height), ImVec2(win_pos_x, win_pos_y + m_window_height), true)) { button_text = ImGui::PreferencesHoverButton; // tooltip long time_now = wxGetLocalTime(); if (m_prefe_hover_time > 0 && m_prefe_hover_time < time_now) { ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); imgui.text(_u8L("Open Preferences.")); ImGui::EndTooltip(); ImGui::PopStyleColor(); } if (m_prefe_hover_time == 0) m_prefe_hover_time = time_now; } else m_prefe_hover_time = 0; ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(m_window_width - m_line_height * 1.75f); if (m_lines_count <= 3) { ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f); } else { ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f); } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { wxGetApp().open_preferences(2); } ImGui::PopStyleColor(5); // preferences button is in place of minimize button m_minimize_b_visible = true; } void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // Used for debuging ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; button_text = ImGui::RightArrowButton; ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(m_window_width - m_line_height * 3.f); if (m_lines_count <= 3) ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f); else ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f); if (imgui.button(button_text.c_str(), button_size.x * 0.8f, button_size.y * 1.f)) { retrieve_data(); } ImGui::PopStyleColor(5); } void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { std::string placeholder_text; placeholder_text = ImGui::EjectButton; ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str()); std::wstring text; text = ImGui::ClippyMarker; ImGui::SetCursorPosX(button_pic_size.x / 3); ImGui::SetCursorPosY(win_size_y / 2 - button_pic_size.y * 2.f); imgui.text(text.c_str()); } void NotificationManager::HintNotification::render_documentation_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::wstring button_text; button_text = ImGui::DocumentationButton; std::string placeholder_text; placeholder_text = ImGui::EjectButton; if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y - 2 * m_line_height), true)) { button_text = ImGui::DocumentationHoverButton; // tooltip long time_now = wxGetLocalTime(); if (m_docu_hover_time > 0 && m_docu_hover_time < time_now) { ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); imgui.text(_u8L("Open Documentation in web browser.")); ImGui::EndTooltip(); ImGui::PopStyleColor(); } if (m_docu_hover_time == 0) m_docu_hover_time = time_now; } else m_docu_hover_time = 0; ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { open_documentation(); } //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); ImGui::SetCursorPosY(0); if (imgui.button(" ", m_line_height * 2.f, win_size.y - 2 * m_line_height)) { open_documentation(); } ImGui::PopStyleColor(5); } void NotificationManager::HintNotification::open_documentation() { if (!m_documentation_link.empty()) { launch_browser_if_allowed(m_documentation_link); } } void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true*/) { HintData* hint_data = HintDatabase::get_instance().get_hint(new_hint); if (hint_data == nullptr) close(); if(hint_data != nullptr) { NotificationData nd { NotificationType::DidYouKnowHint, NotificationLevel::HintNotificationLevel, 0, hint_data->text, hint_data->hypertext, nullptr, hint_data->follow_text }; m_hypertext_callback = hint_data->callback; m_disabled_tags = hint_data->disabled_tags; m_enabled_tags = hint_data->enabled_tags; m_runtime_disable = hint_data->runtime_disable; m_documentation_link = hint_data->documentation_link; m_has_hint_data = true; update(nd); } } } //namespace Slic3r } //namespace GUI