PrusaSlicer/src/slic3r/GUI/HintNotification.cpp
David Kocik b9dab7540e Removes bottom status bar.
StatusBar class calls are commented out and replaced with notifications.
SlicicingProgress notification shows progress of slicing, ProgressIndicator notification handles other progress information, like arrange objects etc.
2021-09-10 14:35:13 +02:00

1030 lines
37 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 "libslic3r/AppConfig.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/PresetBundle.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ini_parser.hpp>
#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 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::filesystem::ofstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"), std::ios::binary);
cereal::BinaryOutputArchive archive(file);
HintsCerealData cd { ids };
try
{
archive(cd);
}
catch (const std::exception& ex)
{
BOOST_LOG_TRIVIAL(error) << "Failed to write to hints.cereal. " << ex.what();
}
}
void read_used_binary(std::vector<std::string>& ids)
{
boost::filesystem::path path(boost::filesystem::path(data_dir()) / "cache" / "hints.cereal");
if (!boost::filesystem::exists(path)) {
BOOST_LOG_TRIVIAL(warning) << "Failed to load to hints.cereal. File does not exists. " << path.string();
return;
}
boost::filesystem::ifstream file(path);
cereal::BinaryInputArchive archive(file);
HintsCerealData cd;
try
{
archive(cd);
}
catch (const std::exception& ex)
{
BOOST_LOG_TRIVIAL(error) << "Failed to load to hints.cereal. " << ex.what();
return;
}
ids = cd.my_data;
}
enum TagCheckResult
{
TagCheckAffirmative,
TagCheckNegative,
TagCheckNotCompatible
};
// returns if in mode defined by tag
TagCheckResult tag_check_mode(const std::string& tag)
{
std::vector<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::comExpert) return (tag == "expert" ? 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;
}
// 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;
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;
BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in disabled_tags not compatible.";
}
}
}
}
return true;
}
void launch_browser_if_allowed(const std::string& url)
{
wxGetApp().open_browser_with_warning_dialog(url);
}
} //namespace
HintDatabase::~HintDatabase()
{
if (m_initialized) {
write_used_binary(m_used_ids);
}
}
void HintDatabase::uninit()
{
if (m_initialized) {
write_used_binary(m_used_ids);
}
m_initialized = false;
}
void HintDatabase::init()
{
load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini"));
m_initialized = true;
}
void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
{
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(path.string());
try {
pt::read_ini(ifs, tree);
}
catch (const boost::property_tree::ini_parser::ini_parser_error& err) {
throw Slic3r::RuntimeError(format("Failed loading hints file \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str());
}
for (const auto& section : tree) {
if (boost::starts_with(section.first, "hint:")) {
// create std::map with tree data
std::map<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;
// randomized weighted order variables
size_t weight = 1;
bool was_displayed = is_used(id_string);
//unescape text1
unescape_string_cstyle(_utf8(dict["text"]), fulltext);
// replace <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("weight") != dict.end()) {
weight = (size_t)std::max(1, std::atoi(dict["weight"].c_str()));
}
// create HintData
if (dict.find("hypertext_type") != dict.end()) {
//link to internet
if(dict["hypertext_type"] == "link") {
std::string hypertext_link = dict["hypertext_link"];
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); } };
m_loaded_hints.emplace_back(hint_data);
// highlight settings
} else if (dict["hypertext_type"] == "settings") {
std::string opt = dict["hypertext_settings_opt"];
Preset::Type type = static_cast<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, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } };
m_loaded_hints.emplace_back(hint_data);
// open preferences
} else if(dict["hypertext_type"] == "preferences") {
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str()));
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } };
m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "plater") {
std::string item = dict["hypertext_plater_item"];
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "gizmo") {
std::string item = dict["hypertext_gizmo_item"];
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } };
m_loaded_hints.emplace_back(hint_data);
}
else if (dict["hypertext_type"] == "gallery") {
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() {
// Deselect all objects, otherwise gallery wont show.
wxGetApp().plater()->canvas3D()->deselect_all();
wxGetApp().obj_list()->load_shape_object_from_gallery(); }
};
m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "menubar") {
wxString menu(_L("&" + dict["hypertext_menubar_menu_name"]));
wxString item(_L(dict["hypertext_menubar_item_name"]));
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [menu, item]() { wxGetApp().mainframe->open_menubar_item(menu, item); } };
m_loaded_hints.emplace_back(hint_data);
}
} else {
// plain text without hypertext
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link };
m_loaded_hints.emplace_back(hint_data);
}
}
}
}
HintData* HintDatabase::get_hint(bool new_hint/* = true*/)
{
if (! m_initialized) {
init();
new_hint = true;
}
if (m_loaded_hints.empty())
{
BOOST_LOG_TRIVIAL(error) << "There were no hints loaded from hints.ini file.";
return nullptr;
}
try
{
if (new_hint)
m_hint_id = get_next();
}
catch (const std::exception&)
{
return nullptr;
}
return &m_loaded_hints[m_hint_id];
}
size_t HintDatabase::get_next()
{
if (!m_sorted_hints)
{
auto compare_wieght = [](const HintData& a, const HintData& b){ return a.weight < b.weight; };
std::sort(m_loaded_hints.begin(), m_loaded_hints.end(), compare_wieght);
m_sorted_hints = true;
srand(time(NULL));
}
std::vector<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;
}
void NotificationManager::HintNotification::count_lines()
{
std::string text = m_text1;
size_t last_end = 0;
m_lines_count = 0;
if (text.empty())
return;
m_endlines.clear();
while (last_end < text.length() - 1)
{
size_t next_hard_end = text.find_first_of('\n', last_end);
if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
//next line is ended by '/n'
m_endlines.push_back(next_hard_end);
last_end = next_hard_end + 1;
}
else {
// find next suitable endline
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) {
// more than one line till end
size_t next_space = text.find_first_of(' ', last_end);
if (next_space > 0 && next_space < text.length()) {
size_t next_space_candidate = text.find_first_of(' ', next_space + 1);
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
next_space = next_space_candidate;
next_space_candidate = text.find_first_of(' ', next_space + 1);
}
} else {
next_space = text.length();
}
// when one word longer than line.
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset ||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 5 * 3
) {
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
letter_count++;
}
m_endlines.push_back(last_end + letter_count);
last_end += letter_count;
} else {
m_endlines.push_back(next_space);
last_end = next_space + 1;
}
}
else {
m_endlines.push_back(text.length());
last_end = text.length();
}
}
m_lines_count++;
}
int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0;
int size_of_last_line = ImGui::CalcTextSize(text.substr(prev_end, last_end - prev_end).c_str()).x;
// hypertext calculation
if (!m_hypertext.empty()) {
if (size_of_last_line + ImGui::CalcTextSize(m_hypertext.c_str()).x > m_window_width - m_window_width_offset) {
// hypertext on new line
size_of_last_line = ImGui::CalcTextSize((m_hypertext + " ").c_str()).x;
m_endlines.push_back(last_end);
m_lines_count++;
} else {
size_of_last_line += ImGui::CalcTextSize((m_hypertext + " ").c_str()).x;
}
}
if (!m_text2.empty()) {
text = m_text2;
last_end = 0;
m_endlines2.clear();
// if size_of_last_line too large to fit anything
size_t first_end = std::min(text.find_first_of('\n'), text.find_first_of(' '));
if (size_of_last_line >= m_window_width - m_window_width_offset - ImGui::CalcTextSize(text.substr(0, first_end).c_str()).x) {
m_endlines2.push_back(0);
size_of_last_line = 0;
}
while (last_end < text.length() - 1)
{
size_t next_hard_end = text.find_first_of('\n', last_end);
if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) {
//next line is ended by '/n'
m_endlines2.push_back(next_hard_end);
last_end = next_hard_end + 1;
}
else {
// find next suitable endline
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset - size_of_last_line) {
// more than one line till end
size_t next_space = text.find_first_of(' ', last_end);
if (next_space > 0) {
size_t next_space_candidate = text.find_first_of(' ', next_space + 1);
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) {
next_space = next_space_candidate;
next_space_candidate = text.find_first_of(' ', next_space + 1);
}
}
else {
next_space = text.length();
}
// when one word longer than line.
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset - size_of_last_line ||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 5 * 3
) {
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) {
letter_count++;
}
m_endlines2.push_back(last_end + letter_count);
last_end += letter_count;
}
else {
m_endlines2.push_back(next_space);
last_end = next_space + 1;
}
}
else {
m_endlines2.push_back(text.length());
last_end = text.length();
}
}
if (size_of_last_line == 0) // if first line is continuation of previous text, do not add to line count.
m_lines_count++;
size_of_last_line = 0; // should countain value only for first line (with hypertext)
}
}
}
void NotificationManager::HintNotification::init()
{
// Do not init closing notification
if (is_finished())
return;
count_spaces();
count_lines();
m_multiline = true;
m_notification_start = GLCanvas3D::timestamp_now();
if (m_state == EState::Unknown)
m_state = EState::Shown;
}
void NotificationManager::HintNotification::set_next_window_size(ImGuiWrapper& imgui)
{
/*
m_window_height = m_multiline ?
(m_lines_count + 1.f) * m_line_height :
4.f * m_line_height;
m_window_height += 1 * m_line_height; // top and bottom
*/
m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 5.f * m_line_height);
}
bool NotificationManager::HintNotification::on_text_click()
{
if (m_hypertext_callback != nullptr && (!m_runtime_disable || tags_check(m_disabled_tags, m_enabled_tags)))
m_hypertext_callback();
return false;
}
void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
if (!m_has_hint_data) {
retrieve_data();
}
float x_offset = m_left_indentation;
int last_end = 0;
float starting_y = (m_lines_count < 4 ? m_line_height / 2 * (4 - m_lines_count + 1) : m_line_height / 2);
float shift_y = m_line_height;
std::string line;
for (size_t i = 0; i < (m_multiline ? /*m_lines_count*/m_endlines.size() : 2); i++) {
line.clear();
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) {
if (i == 1 && m_endlines.size() > 2 && !m_multiline) {
// second line with "more" hypertext
line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) {
line = line.substr(0, line.length() - 1);
}
line += "..";
} else {
// regural line
line = m_text1.substr(last_end, m_endlines[i] - last_end);
}
// first line is headline (for hint notification it must be divided by \n)
if (m_text1.find('\n') >= m_endlines[i]) {
line = ImGui::ColorMarkerStart + line + ImGui::ColorMarkerEnd;
}
// Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line)
if (line.find_first_of(ImGui::ColorMarkerEnd) < line.find_first_of(ImGui::ColorMarkerStart)) {
line = ImGui::ColorMarkerStart + line;
}
last_end = m_endlines[i];
if (m_text1.size() > m_endlines[i])
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
imgui.text(line.c_str());
}
}
//hyperlink text
if (!m_multiline && m_lines_count > 2) {
render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true);
} else if (!m_hypertext.empty()) {
render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty()? "": " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext);
}
// text2
if (!m_text2.empty() && m_multiline) {
starting_y += (m_endlines.size() - 1) * shift_y;
last_end = 0;
for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : 2); i++) {
if (i == 0) //first line X is shifted by hypertext
ImGui::SetCursorPosX(x_offset + ImGui::CalcTextSize((line + m_hypertext + (line.empty() ? " " : " ")).c_str()).x);
else
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
line.clear();
if (m_endlines2.size() > i && m_text2.size() >= m_endlines2[i]) {
// regural line
line = m_text2.substr(last_end, m_endlines2[i] - last_end);
// Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line)
if (line.find_first_of(ImGui::ColorMarkerEnd) < line.find_first_of(ImGui::ColorMarkerStart)) {
line = ImGui::ColorMarkerStart + line;
}
last_end = m_endlines2[i];
if (m_text2.size() > m_endlines2[i])
last_end += (m_text2[m_endlines2[i]] == '\n' || m_text2[m_endlines2[i]] == ' ' ? 1 : 0);
imgui.text(line.c_str());
}
}
}
}
void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
button_text = ImGui::CloseNotifButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
ImVec2(win_pos.x, win_pos.y + win_size.y - 2 * m_line_height),
true))
{
button_text = ImGui::CloseNotifHoverButton;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
m_close_b_w = button_size.y;
if (m_lines_count <= 3) {
m_close_b_y = win_size.y / 2 - button_size.y * 1.25f;
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
ImGui::SetCursorPosY(m_close_b_y);
} else {
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
}
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
close();
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.125, win_size.y - 2 * m_line_height))
{
close();
}
ImGui::PopStyleColor(5);
//render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
render_logo(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
render_preferences_button(imgui, win_pos_x, win_pos_y);
if (!m_documentation_link.empty() && wxGetApp().app_config->get("suppress_hyperlinks") != "1")
{
render_documentation_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
}
}
void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
std::string button_text;
button_text = ImGui::PreferencesButton;
//hover
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 15.f, win_pos_y + m_window_height - 1.75f * m_line_height),
ImVec2(win_pos_x, win_pos_y + m_window_height),
true)) {
button_text = ImGui::PreferencesHoverButton;
// tooltip
long time_now = wxGetLocalTime();
if (m_prefe_hover_time > 0 && m_prefe_hover_time < time_now) {
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
ImGui::BeginTooltip();
imgui.text(_u8L("Open Preferences."));
ImGui::EndTooltip();
ImGui::PopStyleColor();
}
if (m_prefe_hover_time == 0)
m_prefe_hover_time = time_now;
} else
m_prefe_hover_time = 0;
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(m_window_width - m_line_height * 1.75f);
if (m_lines_count <= 3) {
ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f);
} else {
ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f);
}
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
wxGetApp().open_preferences(2);
}
ImGui::PopStyleColor(5);
// preferences button is in place of minimize button
m_minimize_b_visible = true;
}
void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
// Used for debuging
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
button_text = ImGui::RightArrowButton;
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(m_window_width - m_line_height * 3.f);
if (m_lines_count <= 3)
ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f);
else
ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f);
if (imgui.button(button_text.c_str(), button_size.x * 0.8f, button_size.y * 1.f))
{
retrieve_data();
}
ImGui::PopStyleColor(5);
}
void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::wstring button_text;
button_text = ImGui::ClippyMarker;//LeftArrowButton;
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 * 2.f, button_pic_size.y * 1.25f * 2.f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y * 1.1f);
ImGui::SetCursorPosX(0);
// shouldnt it render as text?
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
}
ImGui::PopStyleColor(5);
}
void NotificationManager::HintNotification::render_documentation_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::wstring button_text;
button_text = ImGui::DocumentationButton;
std::string placeholder_text;
placeholder_text = ImGui::EjectButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y - 2 * m_line_height),
true))
{
button_text = ImGui::DocumentationHoverButton;
// tooltip
long time_now = wxGetLocalTime();
if (m_docu_hover_time > 0 && m_docu_hover_time < time_now) {
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
ImGui::BeginTooltip();
imgui.text(_u8L("Open Documentation in web browser."));
ImGui::EndTooltip();
ImGui::PopStyleColor();
}
if (m_docu_hover_time == 0)
m_docu_hover_time = time_now;
}
else
m_docu_hover_time = 0;
ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
open_documentation();
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.f, win_size.y - 2 * m_line_height))
{
open_documentation();
}
ImGui::PopStyleColor(5);
}
void NotificationManager::HintNotification::open_documentation()
{
if (!m_documentation_link.empty())
{
launch_browser_if_allowed(m_documentation_link);
}
}
void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true*/)
{
HintData* hint_data = HintDatabase::get_instance().get_hint(new_hint);
if (hint_data == nullptr)
close();
if(hint_data != nullptr)
{
NotificationData nd { NotificationType::DidYouKnowHint,
NotificationLevel::RegularNotificationLevel,
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