forked from viewit/OrcaSlicer-KX
* Fix calls to depreciated wxPen constructor * Fix use of wxTimerEvent * Fix unrecognized character escape sequence * Fix signed/unsigned mismatch At least as much as possible without significantly altering parts of the application * Clean unreferenced variables * fix mistyped namespace selector * Update deprecated calls * Fix preprocessor statement * Remove empty switch statements * Change int vector used as bool to bool vector * Remove empty control statements and related unused code * Change multi character constant to string constant * Fix discarded return value json::parse was being called on the object, rather than statically like it should be. Also, the value was not being captured. * Rename ICON_SIZE def used by MultiMachine By having the definition in the header, it causes issues when other files define ICON_SIZE. By renaming it to MM_ICON_SIZE, this lessens the issue. It would probably be ideal to have the definitions in the respective .cpp that use them, but it would make it less convenient to update the values if needed in the future. * Remove unused includes * Fix linux/macOS compilation * Hide unused-function errors on non-Windows systems * Disable signed/unsigned comparison mismatch error * Remove/Disable more unused variables Still TODO: check double for loop in Print.cpp * Remove unused variable that was missed * Remove unused variables in libraries in the src folder * Apply temporary fix for subobject linkage error * Remove/Disable last set of unused variables reported by GCC * remove redundant for loop * fix misspelled ifdef check * Update message on dialog * Fix hard-coded platform specific modifier keys * Remove duplicate for loop * Disable -Wmisleading-indentation warning * disable -Wswitch warning * Remove unused local typedefs * Fix -Wunused-value * Fix pragma error on Windows from subobject linkage fix * Fix -Waddress * Fix null conversions (-Wconversion-null) --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
1165 lines
42 KiB
C++
1165 lines
42 KiB
C++
#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 <map>
|
|
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/nowide/fstream.hpp>
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/property_tree/ini_parser.hpp>
|
|
|
|
#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 HintsCerealData
|
|
{
|
|
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(HintsCerealData, HINTS_CEREAL_VERSION);
|
|
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
const std::string BOLD_MARKER_START = "<b>";
|
|
const std::string BOLD_MARKER_END = "</b>";
|
|
const std::string HYPERTEXT_MARKER_START = "<a>";
|
|
const std::string HYPERTEXT_MARKER_END = "</a>";
|
|
|
|
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<std::string>& ids)
|
|
{
|
|
boost::nowide::ofstream file((boost::filesystem::path(data_dir()) / "user" / "hints.cereal").string(), 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<std::string>& ids)
|
|
{
|
|
boost::filesystem::path path(boost::filesystem::path(data_dir()) / "user" / "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::nowide::ifstream file(path.string());
|
|
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<std::string> 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::comDevelop) return (tag == "develop" ? TagCheckAffirmative : TagCheckNegative);
|
|
}
|
|
return TagCheckNotCompatible;
|
|
}
|
|
|
|
TagCheckResult tag_check_tech(const std::string& tag)
|
|
{
|
|
std::vector<std::string> 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<std::string> 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<ConfigOptionStrings>("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<ConfigOptionStrings>("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;
|
|
m_loaded_hints.clear();
|
|
m_sorted_hints = false;
|
|
m_used_ids.clear();
|
|
m_used_ids_loaded = false;
|
|
}
|
|
void HintDatabase::reinit()
|
|
{
|
|
if (m_initialized)
|
|
uninit();
|
|
init();
|
|
}
|
|
void HintDatabase::init()
|
|
{
|
|
load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini"));
|
|
m_initialized = true;
|
|
init_random_hint_id();
|
|
}
|
|
void HintDatabase::init_random_hint_id()
|
|
{
|
|
srand(time(NULL));
|
|
m_hint_id = rand() % m_loaded_hints.size();
|
|
}
|
|
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<std::string, std::string> 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<std::string>{}(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;
|
|
std::string img_url;
|
|
// randomized weighted order variables
|
|
size_t weight = 1;
|
|
bool was_displayed = is_used(id_string);
|
|
//unescape text1
|
|
unescape_string_cstyle(dict["text"], fulltext);
|
|
fulltext = _utf8(fulltext);
|
|
#ifdef __APPLE__
|
|
boost::replace_all(fulltext, "Ctrl+", "?");
|
|
#endif //__APPLE__
|
|
// replace <b> and </b> 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("image") != dict.end()) {
|
|
img_url = dict["image"];
|
|
}
|
|
|
|
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, img_url, [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<Preset::Type>(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, img_url, [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") {
|
|
std::string page = dict["hypertext_preferences_page"];
|
|
std::string item = dict["hypertext_preferences_item"];
|
|
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, img_url, [page, item]() { wxGetApp().open_preferences(1, page); } };// 1 is to modify
|
|
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, img_url, [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, img_url, [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, img_url, []() {
|
|
// 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, img_url, [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, img_url };
|
|
m_loaded_hints.emplace_back(hint_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
HintData* HintDatabase::get_hint(HintDataNavigation nav)
|
|
{
|
|
if (!m_initialized) {
|
|
init();
|
|
nav = HintDataNavigation::Random;
|
|
}
|
|
if (m_loaded_hints.empty())
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "There were no hints loaded from hints.ini file.";
|
|
return nullptr;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (nav == HintDataNavigation::Next)
|
|
m_hint_id = get_next_hint_id();
|
|
if(nav == HintDataNavigation::Prev)
|
|
m_hint_id = get_prev_hint_id();
|
|
// if (nav == HintDataNavigation::Curr)
|
|
if (nav == HintDataNavigation::Random)
|
|
init_random_hint_id();
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return &m_loaded_hints[m_hint_id];
|
|
}
|
|
|
|
size_t HintDatabase::get_next_hint_id()
|
|
{
|
|
return m_hint_id < m_loaded_hints.size() - 1 ? m_hint_id + 1 : 0;
|
|
}
|
|
|
|
size_t HintDatabase::get_prev_hint_id()
|
|
{
|
|
return m_hint_id > 0 ? m_hint_id - 1 : m_loaded_hints.size() - 1;
|
|
}
|
|
|
|
size_t HintDatabase::get_random_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;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
static int get_utf8_seq_length(const char* seq, size_t size)
|
|
{
|
|
int length = 0;
|
|
unsigned char c = seq[0];
|
|
if (c < 0x80) { // 0x00-0x7F
|
|
// is ASCII letter
|
|
length++;
|
|
}
|
|
// Bytes 0x80 to 0xBD are trailer bytes in a multibyte sequence.
|
|
// pos is in the middle of a utf-8 sequence. Add the utf-8 trailer bytes.
|
|
else if (c < 0xC0) { // 0x80-0xBF
|
|
length++;
|
|
while (length < size) {
|
|
c = seq[length];
|
|
if (c < 0x80 || c >= 0xC0) {
|
|
break; // prevent overrun
|
|
}
|
|
length++; // add a utf-8 trailer byte
|
|
}
|
|
}
|
|
// Bytes 0xC0 to 0xFD are header bytes in a multibyte sequence.
|
|
// The number of one bits above the topmost zero bit indicates the number of bytes (including this one) in the whole sequence.
|
|
else if (c < 0xE0) { // 0xC0-0xDF
|
|
// add a utf-8 sequence (2 bytes)
|
|
if (2 > size) {
|
|
return size; // prevent overrun
|
|
}
|
|
length += 2;
|
|
}
|
|
else if (c < 0xF0) { // 0xE0-0xEF
|
|
// add a utf-8 sequence (3 bytes)
|
|
if (3 > size) {
|
|
return size; // prevent overrun
|
|
}
|
|
length += 3;
|
|
}
|
|
else if (c < 0xF8) { // 0xF0-0xF7
|
|
// add a utf-8 sequence (4 bytes)
|
|
if (4 > size) {
|
|
return size; // prevent overrun
|
|
}
|
|
length += 4;
|
|
}
|
|
else if (c < 0xFC) { // 0xF8-0xFB
|
|
// add a utf-8 sequence (5 bytes)
|
|
if (5 > size) {
|
|
return size; // prevent overrun
|
|
}
|
|
length += 5;
|
|
}
|
|
else if (c < 0xFE) { // 0xFC-0xFD
|
|
// add a utf-8 sequence (6 bytes)
|
|
if (6 > size) {
|
|
return size; // prevent overrun
|
|
}
|
|
length += 6;
|
|
}
|
|
else { // 0xFE-0xFF
|
|
// not a utf-8 sequence
|
|
length++;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static int get_utf8_seq_length(const std::string& text, size_t pos)
|
|
{
|
|
assert(pos < text.size());
|
|
return get_utf8_seq_length(text.c_str() + pos, text.size() - pos);
|
|
}
|
|
|
|
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 += get_utf8_seq_length(text, last_end + 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 += get_utf8_seq_length(text, last_end + 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;
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, m_HyperTextColor);
|
|
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());
|
|
}
|
|
|
|
}
|
|
}
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
|
|
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::wstring button_text;
|
|
button_text = m_is_dark ? ImGui::CloseNotifDarkButton : ImGui::CloseNotifButton;
|
|
|
|
ImVec2 button_pic_size = ImGui::CalcTextSize(into_u8(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 (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y + win_size.y / 2 - button_pic_size.y),
|
|
ImVec2(win_pos.x, win_pos.y + win_size.y / 2 + button_pic_size.y),
|
|
true))
|
|
{
|
|
button_text = m_is_dark ? ImGui::CloseNotifHoverDarkButton : ImGui::CloseNotifHoverButton;
|
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
|
close();
|
|
}
|
|
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();
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
auto scale = wxGetApp().plater()->get_current_canvas3D()->get_scale();
|
|
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::wstring button_text;
|
|
button_text = m_is_dark ? ImGui::PreferencesDarkButton : ImGui::PreferencesButton;
|
|
//hover
|
|
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 15.f, win_pos_y + m_window_height - 1.5f * m_line_height),
|
|
ImVec2(win_pos_x, win_pos_y + m_window_height),
|
|
true)) {
|
|
button_text = m_is_dark ? ImGui::PreferencesHoverDarkButton : ImGui::PreferencesHoverButton;
|
|
// tooltip
|
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
|
ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 });
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 8 * scale, 1 * scale });
|
|
ImGui::BeginTooltip();
|
|
imgui.text(_u8L("Open Preferences."));
|
|
ImGui::EndTooltip();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
ImVec2 button_pic_size = ImGui::CalcTextSize(into_u8(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);
|
|
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();
|
|
}
|
|
|
|
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
|
|
auto scale = wxGetApp().plater()->get_current_canvas3D()->get_scale();
|
|
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 = m_is_dark ? ImGui::RightArrowDarkButton : ImGui::RightArrowButton;
|
|
|
|
ImVec2 button_pic_size = ImGui::CalcTextSize(into_u8(button_text).c_str());
|
|
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
|
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 7.5f, win_pos_y + m_window_height - 1.5f * m_line_height),
|
|
ImVec2(win_pos_x - m_window_width / 15.f, win_pos_y + m_window_height),
|
|
true))
|
|
{
|
|
button_text = m_is_dark ? ImGui::RightArrowHoverDarkButton : ImGui::RightArrowHoverButton;
|
|
// tooltip
|
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
|
ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 });
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 8 * scale, 1 * scale });
|
|
ImGui::BeginTooltip();
|
|
imgui.text(_u8L("Open next tip."));
|
|
ImGui::EndTooltip();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
}
|
|
ImGui::SetCursorPosX(m_window_width - m_line_height * 3.f);
|
|
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)
|
|
{
|
|
auto scale = wxGetApp().plater()->get_current_canvas3D()->get_scale();
|
|
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 = m_is_dark ? ImGui::DocumentationDarkButton : ImGui::DocumentationButton;
|
|
std::string placeholder_text;
|
|
placeholder_text = ImGui::EjectButton;
|
|
|
|
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);
|
|
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y + win_size.y / 2 - button_pic_size.y),
|
|
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y / 2 + button_pic_size.y),
|
|
true))
|
|
{
|
|
button_text = m_is_dark ? ImGui::DocumentationHoverDarkButton : ImGui::DocumentationHoverButton;
|
|
// tooltip
|
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
|
ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 });
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 8 * scale, 1 * scale });
|
|
ImGui::BeginTooltip();
|
|
imgui.text(_u8L("Open Documentation in web browser."));
|
|
ImGui::EndTooltip();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
|
open_documentation();
|
|
}
|
|
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();
|
|
}
|
|
|
|
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 ? HintDataNavigation::Next : HintDataNavigation::Curr);
|
|
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
|