Merge branch 'dk_notifications'

This commit is contained in:
David Kocik 2021-08-24 16:55:51 +02:00
commit aeac024e73
3 changed files with 180 additions and 68 deletions

View file

@ -167,9 +167,6 @@ void AppConfig::set_defaults()
if (get("show_splash_screen").empty()) if (get("show_splash_screen").empty())
set("show_splash_screen", "1"); set("show_splash_screen", "1");
if (get("last_hint").empty())
set("last_hint", "0");
if (get("show_hints").empty()) if (get("show_hints").empty())
set("show_hints", "1"); set("show_hints", "1");

View file

@ -14,6 +14,31 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <map> #include <map>
#include <cereal/archives/binary.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#define HINTS_CEREAL_VERSION 1
// structure for writing used hints into binary file with version
struct CerealData
{
std::vector<std::string> my_data;
// cereal will supply the version automatically when loading or saving
// The version number comes from the CEREAL_CLASS_VERSION macro
template<class Archive>
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 Slic3r {
namespace GUI { namespace GUI {
@ -31,6 +56,41 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f
else else
ImGui::PushStyleColor(idx, col); ImGui::PushStyleColor(idx, col);
} }
void write_used_binary(const std::vector<std::string>& 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<std::string>& 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 enum TagCheckResult
{ {
TagCheckAffirmative, TagCheckAffirmative,
@ -179,30 +239,19 @@ void launch_browser_if_allowed(const std::string& url)
if (wxGetApp().app_config->get("suppress_hyperlinks") != "1") if (wxGetApp().app_config->get("suppress_hyperlinks") != "1")
wxLaunchDefaultBrowser(url); 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<std::string>& 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 } //namespace
HintDatabase::~HintDatabase()
{
if (m_initialized) {
write_used_binary(m_used_ids);
}
}
void HintDatabase::init() void HintDatabase::init()
{ {
load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini"));
const AppConfig* app_config = wxGetApp().app_config; const AppConfig* app_config = wxGetApp().app_config;
m_hint_id = std::atoi(app_config->get("last_hint").c_str());
m_initialized = true; m_initialized = true;
} }
@ -210,8 +259,6 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
{ {
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
pt::ptree tree; pt::ptree tree;
bool create_pot = !pot_exists();
std::vector<std::string> pot_elements;
boost::nowide::ifstream ifs(path.string()); boost::nowide::ifstream ifs(path.string());
try { try {
pt::read_ini(ifs, tree); 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) { for (const auto& data : section.second) {
dict.emplace(data.first, data.second.data()); dict.emplace(data.first, data.second.data());
} }
// unique id string [hint:id] (trim "hint:")
//unescaping and translating all texts and saving all data common for all hint types std::string id_string = section.first.substr(5);
id_string = std::to_string(std::hash<std::string>{}(id_string));
// unescaping and translating all texts and saving all data common for all hint types
std::string fulltext; std::string fulltext;
std::string text1; std::string text1;
std::string hypertext_text; std::string hypertext_text;
std::string follow_text; std::string follow_text;
// tags
std::string disabled_tags; std::string disabled_tags;
std::string enabled_tags; std::string enabled_tags;
// optional link to documentation (accessed from button)
std::string documentation_link; std::string documentation_link;
// randomized weighted order variables
size_t weight = 1;
bool was_displayed = is_used(id_string);
//unescape text1 //unescape text1
unescape_string_cstyle(_utf8(dict["text"]), fulltext); unescape_string_cstyle(_utf8(dict["text"]), fulltext);
if (create_pot)
pot_elements.emplace_back(fulltext);
// replace <b> and </b> for imgui markers // replace <b> and </b> for imgui markers
std::string marker_s(1, ImGui::ColorMarkerStart); std::string marker_s(1, ImGui::ColorMarkerStart);
std::string marker_e(1, ImGui::ColorMarkerEnd); 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"]; 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 // create HintData
if (dict.find("hypertext_type") != dict.end()) { if (dict.find("hypertext_type") != dict.end()) {
//link to internet //link to internet
if(dict["hypertext_type"] == "link") { if(dict["hypertext_type"] == "link") {
std::string hypertext_link = dict["hypertext_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); m_loaded_hints.emplace_back(hint_data);
// highlight settings // highlight settings
} else if (dict["hypertext_type"] == "settings") { } else if (dict["hypertext_type"] == "settings") {
std::string opt = dict["hypertext_settings_opt"]; std::string opt = dict["hypertext_settings_opt"];
Preset::Type type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str())); Preset::Type type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str()));
std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]); 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); m_loaded_hints.emplace_back(hint_data);
// open preferences // open preferences
} else if(dict["hypertext_type"] == "preferences") { } else if(dict["hypertext_type"] == "preferences") {
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str())); int page = static_cast<Preset::Type>(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); m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "plater") { } else if (dict["hypertext_type"] == "plater") {
std::string item = dict["hypertext_plater_item"]; 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); m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "gizmo") { } else if (dict["hypertext_type"] == "gizmo") {
std::string item = dict["hypertext_gizmo_item"]; 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); m_loaded_hints.emplace_back(hint_data);
} }
else if (dict["hypertext_type"] == "gallery") { 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. // Deselect all objects, otherwise gallery wont show.
wxGetApp().plater()->canvas3D()->deselect_all(); wxGetApp().plater()->canvas3D()->deselect_all();
wxGetApp().obj_list()->load_shape_object_from_gallery(); } }; wxGetApp().obj_list()->load_shape_object_from_gallery(); } };
@ -333,19 +389,17 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
} }
} else { } else {
// plain text without hypertext // 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); 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) { if (! m_initialized) {
init(); init();
//return false; new_hint = true;
} }
if (m_loaded_hints.empty()) if (m_loaded_hints.empty())
{ {
@ -353,23 +407,84 @@ HintData* HintDatabase::get_hint(bool up)
return nullptr; return nullptr;
} }
// shift id if (new_hint)
m_hint_id = (up ? m_hint_id + 1 : m_hint_id ); m_hint_id = get_next();
m_hint_id %= m_loaded_hints.size();
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]; 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<size_t> 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() void NotificationManager::HintNotification::count_spaces()
{ {
//determine line width //determine line width
@ -865,23 +980,12 @@ void NotificationManager::HintNotification::open_documentation()
launch_browser_if_allowed(m_documentation_link); 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) if (hint_data == nullptr)
close(); 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) if(hint_data != nullptr)
{ {
NotificationData nd { NotificationType::DidYouKnowHint, NotificationData nd { NotificationType::DidYouKnowHint,

View file

@ -9,7 +9,10 @@ namespace GUI {
// Database of hints updatable // Database of hints updatable
struct HintData struct HintData
{ {
std::string id_string;
std::string text; std::string text;
size_t weight;
bool was_displayed;
std::string hypertext; std::string hypertext;
std::string follow_text; std::string follow_text;
std::string disabled_tags; std::string disabled_tags;
@ -33,11 +36,12 @@ private:
: m_hint_id(0) : m_hint_id(0)
{} {}
public: public:
~HintDatabase();
HintDatabase(HintDatabase const&) = delete; HintDatabase(HintDatabase const&) = delete;
void operator=(HintDatabase const&) = delete; void operator=(HintDatabase const&) = delete;
// return true if HintData filled; // return true if HintData filled;
HintData* get_hint(bool up = true); HintData* get_hint(bool new_hint = true);
size_t get_count() { size_t get_count() {
if (!m_initialized) if (!m_initialized)
return 0; return 0;
@ -46,10 +50,17 @@ public:
private: private:
void init(); void init();
void load_hints_from_file(const boost::filesystem::path& path); 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; size_t m_hint_id;
bool m_initialized { false }; bool m_initialized { false };
std::vector<HintData> m_loaded_hints; std::vector<HintData> m_loaded_hints;
bool m_sorted_hints { false };
std::vector<std::string> m_used_ids;
bool m_used_ids_loaded { false };
}; };
// Notification class - shows current Hint ("Did you know") // Notification class - shows current Hint ("Did you know")
class NotificationManager::HintNotification : public NotificationManager::PopNotification class NotificationManager::HintNotification : public NotificationManager::PopNotification
@ -58,10 +69,10 @@ public:
HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint) HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint)
: PopNotification(n, id_provider, evt_handler) : PopNotification(n, id_provider, evt_handler)
{ {
retrieve_data(new_hint ? 0 : -1); retrieve_data(new_hint);
} }
virtual void init() override; virtual void init() override;
void open_next() { retrieve_data(0); } void open_next() { retrieve_data(); }
protected: protected:
virtual void set_next_window_size(ImGuiWrapper& imgui) override; virtual void set_next_window_size(ImGuiWrapper& imgui) override;
virtual void count_spaces() override; virtual void count_spaces() override;
@ -87,7 +98,7 @@ protected:
const float win_size_x, const float win_size_y, const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y); const float win_pos_x, const float win_pos_y);
// recursion counter -1 tells to retrieve same hint as last time // 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(); void open_documentation();
bool m_has_hint_data { false }; bool m_has_hint_data { false };