diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 4166658f5a..177d8d708b 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -167,9 +167,6 @@ void AppConfig::set_defaults() if (get("show_splash_screen").empty()) set("show_splash_screen", "1"); - if (get("last_hint").empty()) - set("last_hint", "0"); - if (get("show_hints").empty()) set("show_hints", "1"); diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 42039fa701..cd92cf4801 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -14,6 +14,31 @@ #include #include #include +#include +#include +#include + +#define HINTS_CEREAL_VERSION 1 +// structure for writing used hints into binary file with version +struct CerealData +{ + 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(CerealData, HINTS_CEREAL_VERSION); namespace Slic3r { namespace GUI { @@ -31,6 +56,41 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f 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); + CerealData 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::ifstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal")); + cereal::BinaryInputArchive archive(file); + CerealData 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, @@ -179,30 +239,19 @@ void launch_browser_if_allowed(const std::string& url) if (wxGetApp().app_config->get("suppress_hyperlinks") != "1") wxLaunchDefaultBrowser(url); } -bool pot_exists() -{ - return true; -// return boost::filesystem::exists(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.pot")); -} -void write_pot(const std::vector& elements) -{ - boost::filesystem::ofstream file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.pot")); - - for ( const auto &element : elements) - { - file << "msgid \"" << escape_string_cstyle(element) << "\"\nmsgstr \"\"\n\n"; - } - file.close(); -} } //namespace - +HintDatabase::~HintDatabase() +{ + if (m_initialized) { + write_used_binary(m_used_ids); + } +} void HintDatabase::init() { load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); const AppConfig* app_config = wxGetApp().app_config; - m_hint_id = std::atoi(app_config->get("last_hint").c_str()); m_initialized = true; } @@ -210,8 +259,6 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) { namespace pt = boost::property_tree; pt::ptree tree; - bool create_pot = !pot_exists(); - std::vector pot_elements; boost::nowide::ifstream ifs(path.string()); try { pt::read_ini(ifs, tree); @@ -227,19 +274,24 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) for (const auto& data : section.second) { dict.emplace(data.first, data.second.data()); } - - //unescaping and translating all texts and saving all data common for all hint types + // 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); - if (create_pot) - pot_elements.emplace_back(fulltext); // replace and for imgui markers std::string marker_s(1, ImGui::ColorMarkerStart); std::string marker_e(1, ImGui::ColorMarkerEnd); @@ -295,37 +347,41 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) 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{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(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{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, 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{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } }; + 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{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(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{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_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{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() { + 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(); } }; @@ -333,19 +389,17 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) } } else { // plain text without hypertext - HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link }; + 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); } } } - if (create_pot) - write_pot(pot_elements); } -HintData* HintDatabase::get_hint(bool up) +HintData* HintDatabase::get_hint(bool new_hint/* = true*/) { if (! m_initialized) { init(); - //return false; + new_hint = true; } if (m_loaded_hints.empty()) { @@ -353,23 +407,84 @@ HintData* HintDatabase::get_hint(bool up) return nullptr; } - // shift id - m_hint_id = (up ? m_hint_id + 1 : m_hint_id ); - m_hint_id %= m_loaded_hints.size(); + if (new_hint) + m_hint_id = get_next(); - AppConfig* app_config = wxGetApp().app_config; - app_config->set("last_hint", std::to_string(m_hint_id)); - - //data = &m_loaded_hints[m_hint_id]; - /* - data.text = m_loaded_hints[m_hint_id].text; - data.hypertext = m_loaded_hints[m_hint_id].hypertext; - data.follow_text = m_loaded_hints[m_hint_id].follow_text; - data.callback = m_loaded_hints[m_hint_id].callback; - */ 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; + } + } + } + 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."; + return 0; +} + +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 @@ -865,23 +980,12 @@ void NotificationManager::HintNotification::open_documentation() launch_browser_if_allowed(m_documentation_link); } } -void NotificationManager::HintNotification::retrieve_data(int recursion_counter) +void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true*/) { - HintData* hint_data = HintDatabase::get_instance().get_hint(recursion_counter >= 0 ? true : false); + HintData* hint_data = HintDatabase::get_instance().get_hint(new_hint); if (hint_data == nullptr) close(); - if (hint_data != nullptr && !tags_check(hint_data->disabled_tags, hint_data->enabled_tags)) - { - // Content for different user - retrieve another - size_t count = HintDatabase::get_instance().get_count(); - if ((int)count < recursion_counter) { - BOOST_LOG_TRIVIAL(error) << "Hint notification failed to load data due to recursion counter."; - } else { - retrieve_data(recursion_counter + 1); - } - return; - } if(hint_data != nullptr) { NotificationData nd { NotificationType::DidYouKnowHint, diff --git a/src/slic3r/GUI/HintNotification.hpp b/src/slic3r/GUI/HintNotification.hpp index 78e02a848c..c1e7c0ed3c 100644 --- a/src/slic3r/GUI/HintNotification.hpp +++ b/src/slic3r/GUI/HintNotification.hpp @@ -9,7 +9,10 @@ namespace GUI { // Database of hints updatable struct HintData { + std::string id_string; std::string text; + size_t weight; + bool was_displayed; std::string hypertext; std::string follow_text; std::string disabled_tags; @@ -33,11 +36,12 @@ private: : m_hint_id(0) {} public: + ~HintDatabase(); HintDatabase(HintDatabase const&) = delete; void operator=(HintDatabase const&) = delete; // return true if HintData filled; - HintData* get_hint(bool up = true); + HintData* get_hint(bool new_hint = true); size_t get_count() { if (!m_initialized) return 0; @@ -46,10 +50,17 @@ public: private: void init(); void load_hints_from_file(const boost::filesystem::path& path); + bool is_used(const std::string& id); + void set_used(const std::string& id); + void clear_used(); + // Returns position in m_loaded_hints with next hint chosed randomly with weights + size_t get_next(); size_t m_hint_id; bool m_initialized { false }; std::vector m_loaded_hints; - + bool m_sorted_hints { false }; + std::vector m_used_ids; + bool m_used_ids_loaded { false }; }; // Notification class - shows current Hint ("Did you know") class NotificationManager::HintNotification : public NotificationManager::PopNotification @@ -58,10 +69,10 @@ public: HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint) : PopNotification(n, id_provider, evt_handler) { - retrieve_data(new_hint ? 0 : -1); + retrieve_data(new_hint); } virtual void init() override; - void open_next() { retrieve_data(0); } + void open_next() { retrieve_data(); } protected: virtual void set_next_window_size(ImGuiWrapper& imgui) override; virtual void count_spaces() override; @@ -87,7 +98,7 @@ protected: const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); // recursion counter -1 tells to retrieve same hint as last time - void retrieve_data(int recursion_counter = 0); + void retrieve_data(bool new_hint = true); void open_documentation(); bool m_has_hint_data { false };