Files
OrcaSlicer-KX/src/slic3r/GUI/HintNotification.cpp
Ocraftyone dab6fa4db2 Fix Compile Warnings (#5963)
* 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>
2024-07-29 21:00:26 +08:00

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