Files
OrcaSlicer/src/slic3r/GUI/UnsavedChangesDialog.cpp
Ian Bassi 9ec2fe3c45 Add tooltips to Transfer buttons (#13535)
* Add tooltips to Transfer buttons

Set contextual tooltips for the Discard, Transfer and Save buttons when a dependent preset is present. The code computes the previous and new profile names and provides localized explanatory tooltips that clarify that switching will discard changes, transfer "New Value" settings, or save changes into the current profile. This improves UX by making the consequences of each action explicit.

* added some endlines

Co-Authored-By: yw4z <yw4z@outlook.com>

---------

Co-authored-by: yw4z <yw4z@outlook.com>
2026-05-16 21:28:15 +08:00

2433 lines
93 KiB
C++

#include "UnsavedChangesDialog.hpp"
#include <cstddef>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <wx/tokenzr.h>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Color.hpp"
#include "format.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "Tab.hpp"
#include "ExtraRenderers.hpp"
#include "wxExtensions.hpp"
#include "SavePresetDialog.hpp"
#include "MainFrame.hpp"
#include "MsgDialog.hpp"
#include "PresetComboBoxes.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/DialogButtons.hpp"
#include "Widgets/HyperLink.hpp"
using boost::optional;
#ifdef __linux__
#define wxLinux true
#else
#define wxLinux false
#endif
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_DIFF_DIALOG_TRANSFER, SimpleEvent);
// ----------------------------------------------------------------------------
// ModelNode: a node inside DiffModel
// ----------------------------------------------------------------------------
static const std::map<Preset::Type, std::string> type_icon_names = {
{Preset::TYPE_PRINT, "cog" },
{Preset::TYPE_SLA_PRINT, "cog" },
{Preset::TYPE_FILAMENT, "spool" },
{Preset::TYPE_SLA_MATERIAL, "blank_16" },
{Preset::TYPE_PRINTER, "printer" },
};
static std::string get_icon_name(Preset::Type type, PrinterTechnology pt) {
return pt == ptSLA && type == Preset::TYPE_PRINTER ? "sla_printer" : type_icon_names.at(type);
}
static std::string def_text_color()
{
wxColour def_colour = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
return encode_color(ColorRGB(def_colour.Red(), def_colour.Green(), def_colour.Blue()));
}
static std::string grey = "#808080";
static std::string orange = "#ed6b21";
static void color_string(wxString& str, const std::string& color)
{
#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)
str = from_u8((boost::format("<span color=\"%1%\">%2%</span>") % color % into_u8(str)).str());
#endif
}
static void make_string_bold(wxString& str)
{
#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)
str = from_u8((boost::format("<b>%1%</b>") % into_u8(str)).str());
#endif
}
// preset(root) node
ModelNode::ModelNode(Preset::Type preset_type, wxWindow* parent_win, const wxString& text, const std::string& icon_name) :
m_parent_win(parent_win),
m_parent(nullptr),
m_preset_type(preset_type),
m_icon_name(icon_name),
m_text(text)
{
UpdateIcons();
}
// category node
ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name) :
m_parent_win(parent->m_parent_win),
m_parent(parent),
m_icon_name(icon_name),
m_text(text)
{
UpdateIcons();
}
// group node
ModelNode::ModelNode(ModelNode* parent, const wxString& text) :
m_parent_win(parent->m_parent_win),
m_parent(parent),
m_icon_name("node_dot"),
m_text(text)
{
UpdateIcons();
}
#ifdef __linux__
wxIcon ModelNode::get_bitmap(const wxString& color)
#else
wxBitmap ModelNode::get_bitmap(const wxString& color)
#endif // __linux__
{
/* It's supposed that standard size of an icon is 48px*16px for 100% scaled display.
* So set sizes for solid_colored icons used for filament preset
* and scale them in respect to em_unit value
*/
const double em = em_unit(m_parent_win);
const int icon_width = lround(6.4 * em);
const int icon_height = lround(1.6 * em);
BitmapCache bmp_cache;
ColorRGB rgb;
decode_color(into_u8(color), rgb);
// there is no need to scale created solid bitmap
#ifndef __linux__
return bmp_cache.mksolid(icon_width, icon_height, rgb, true);
#else
wxIcon icon;
icon.CopyFromBitmap(bmp_cache.mksolid(icon_width, icon_height, rgb, true));
return icon;
#endif // __linux__
}
// option node
ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value) :
m_parent(parent),
m_old_color(old_value.StartsWith("#") ? old_value : ""),
m_new_color(new_value.StartsWith("#") ? new_value : ""),
m_icon_name("empty"),
m_text(text),
m_old_value(old_value),
m_new_value(new_value),
m_container(false)
{
// check if old/new_value is color
if (m_old_color.IsEmpty()) {
if (!m_new_color.IsEmpty())
m_old_value = _L("Undef");
}
else {
m_old_color_bmp = get_bitmap(m_old_color);
m_old_value.Clear();
}
if (m_new_color.IsEmpty()) {
if (!m_old_color.IsEmpty())
m_new_value = _L("Undef");
}
else {
m_new_color_bmp = get_bitmap(m_new_color);
m_new_value.Clear();
}
// "color" strings
color_string(m_old_value, def_text_color());
color_string(m_new_value, orange);
UpdateIcons();
}
void ModelNode::UpdateEnabling()
{
auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to)
{
#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)
std::string old_val = into_u8(str);
boost::replace_all(old_val, clr_from, clr_to);
str = from_u8(old_val);
#endif
};
if (!m_toggle) {
change_text_color(m_text, def_text_color(), grey);
change_text_color(m_old_value, def_text_color(), grey);
change_text_color(m_new_value, orange,grey);
}
else {
change_text_color(m_text, grey, def_text_color());
change_text_color(m_old_value, grey, def_text_color());
change_text_color(m_new_value, grey, orange);
}
// update icons for the colors
UpdateIcons();
}
void ModelNode::UpdateIcons()
{
// update icons for the colors, if any exists
if (!m_old_color.IsEmpty())
m_old_color_bmp = get_bitmap(m_toggle ? m_old_color : wxString::FromUTF8(grey.c_str()));
if (!m_new_color.IsEmpty())
m_new_color_bmp = get_bitmap(m_toggle ? m_new_color : wxString::FromUTF8(grey.c_str()));
// update main icon, if any exists
if (m_icon_name.empty())
return;
#ifdef __linux__
m_icon.CopyFromBitmap(create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle));
#else
m_icon = create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle);
#endif //__linux__
}
// ----------------------------------------------------------------------------
// DiffModel
// ----------------------------------------------------------------------------
DiffModel::DiffModel(wxWindow* parent) :
m_parent_win(parent)
{
}
wxDataViewItem DiffModel::AddPreset(Preset::Type type, wxString preset_name, PrinterTechnology pt)
{
// "color" strings
color_string(preset_name, def_text_color());
make_string_bold(preset_name);
auto preset = new ModelNode(type, m_parent_win, preset_name, get_icon_name(type, pt));
m_preset_nodes.emplace_back(preset);
wxDataViewItem child((void*)preset);
wxDataViewItem parent(nullptr);
ItemAdded(parent, child);
return child;
}
ModelNode* DiffModel::AddOption(ModelNode* group_node, wxString option_name, wxString old_value, wxString new_value)
{
group_node->Append(std::make_unique<ModelNode>(group_node, option_name, old_value, new_value));
ModelNode* option = group_node->GetChildren().back().get();
wxDataViewItem group_item = wxDataViewItem((void*)group_node);
ItemAdded(group_item, wxDataViewItem((void*)option));
m_ctrl->Expand(group_item);
return option;
}
ModelNode* DiffModel::AddOptionWithGroup(ModelNode* category_node, wxString group_name, wxString option_name, wxString old_value, wxString new_value)
{
category_node->Append(std::make_unique<ModelNode>(category_node, group_name));
ModelNode* group_node = category_node->GetChildren().back().get();
ItemAdded(wxDataViewItem((void*)category_node), wxDataViewItem((void*)group_node));
return AddOption(group_node, option_name, old_value, new_value);
}
ModelNode* DiffModel::AddOptionWithGroupAndCategory(ModelNode* preset_node, wxString category_name, wxString group_name,
wxString option_name, wxString old_value, wxString new_value, const std::string category_icon_name)
{
preset_node->Append(std::make_unique<ModelNode>(preset_node, category_name, category_icon_name));
ModelNode* category_node = preset_node->GetChildren().back().get();
ItemAdded(wxDataViewItem((void*)preset_node), wxDataViewItem((void*)category_node));
return AddOptionWithGroup(category_node, group_name, option_name, old_value, new_value);
}
wxDataViewItem DiffModel::AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name,
wxString old_value, wxString new_value, const std::string category_icon_name)
{
// "color" strings
color_string(category_name, def_text_color());
color_string(group_name, def_text_color());
color_string(option_name, def_text_color());
// "make" strings bold
make_string_bold(category_name);
make_string_bold(group_name);
// add items
for (std::unique_ptr<ModelNode>& preset : m_preset_nodes)
if (preset->type() == type)
{
for (std::unique_ptr<ModelNode> &category : preset->GetChildren())
if (category->text() == category_name)
{
for (std::unique_ptr<ModelNode> &group : category->GetChildren())
if (group->text() == group_name)
return wxDataViewItem((void*)AddOption(group.get(), option_name, old_value, new_value));
return wxDataViewItem((void*)AddOptionWithGroup(category.get(), group_name, option_name, old_value, new_value));
}
return wxDataViewItem((void*)AddOptionWithGroupAndCategory(preset.get(), category_name, group_name, option_name, old_value, new_value, category_icon_name));
}
return wxDataViewItem(nullptr);
}
static void update_children(ModelNode* parent)
{
if (parent->IsContainer()) {
bool toggle = parent->IsToggled();
for (std::unique_ptr<ModelNode> &child : parent->GetChildren()) {
child->Toggle(toggle);
child->UpdateEnabling();
update_children(child.get());
}
}
}
static void update_parents(ModelNode* node)
{
ModelNode* parent = node->GetParent();
if (parent) {
bool toggle = false;
for (std::unique_ptr<ModelNode> &child : parent->GetChildren()) {
if (child->IsToggled()) {
toggle = true;
break;
}
}
parent->Toggle(toggle);
parent->UpdateEnabling();
update_parents(parent);
}
}
void DiffModel::UpdateItemEnabling(wxDataViewItem item)
{
assert(item.IsOk());
ModelNode* node = static_cast<ModelNode*>(item.GetID());
node->UpdateEnabling();
update_children(node);
update_parents(node);
}
bool DiffModel::IsEnabledItem(const wxDataViewItem& item)
{
assert(item.IsOk());
ModelNode* node = static_cast<ModelNode*>(item.GetID());
return node->IsToggled();
}
void DiffModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
{
assert(item.IsOk());
ModelNode* node = static_cast<ModelNode*>(item.GetID());
switch (col)
{
case colToggle:
variant = node->m_toggle;
break;
#ifdef __linux__
case colIconText:
variant << wxDataViewIconText(node->m_text, node->m_icon);
break;
case colOldValue:
variant << wxDataViewIconText(node->m_old_value, node->m_old_color_bmp);
break;
case colNewValue:
variant << wxDataViewIconText(node->m_new_value, node->m_new_color_bmp);
break;
#else
case colIconText:
variant << DataViewBitmapText(node->m_text, node->m_icon);
break;
case colOldValue:
variant << DataViewBitmapText(node->m_old_value, node->m_old_color_bmp);
break;
case colNewValue:
variant << DataViewBitmapText(node->m_new_value, node->m_new_color_bmp);
break;
#endif //__linux__
default:
wxLogError("DiffModel::GetValue: wrong column %d", col);
}
}
bool DiffModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
{
assert(item.IsOk());
ModelNode* node = static_cast<ModelNode*>(item.GetID());
switch (col)
{
case colToggle:
node->m_toggle = variant.GetBool();
return true;
#ifdef __linux__
case colIconText: {
wxDataViewIconText data;
data << variant;
node->m_icon = data.GetIcon();
node->m_text = data.GetText();
return true; }
case colOldValue: {
wxDataViewIconText data;
data << variant;
node->m_old_color_bmp = data.GetIcon();
node->m_old_value = data.GetText();
return true; }
case colNewValue: {
wxDataViewIconText data;
data << variant;
node->m_new_color_bmp = data.GetIcon();
node->m_new_value = data.GetText();
return true; }
#else
case colIconText: {
DataViewBitmapText data;
data << variant;
node->m_icon = data.GetBitmap();
node->m_text = data.GetText();
return true; }
case colOldValue: {
DataViewBitmapText data;
data << variant;
node->m_old_color_bmp = data.GetBitmap();
node->m_old_value = data.GetText();
return true; }
case colNewValue: {
DataViewBitmapText data;
data << variant;
node->m_new_color_bmp = data.GetBitmap();
node->m_new_value = data.GetText();
return true; }
#endif //__linux__
default:
wxLogError("DiffModel::SetValue: wrong column");
}
return false;
}
bool DiffModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const
{
assert(item.IsOk());
if (col == colToggle)
return true;
// disable unchecked nodes
return (static_cast<ModelNode*>(item.GetID()))->IsToggled();
}
wxDataViewItem DiffModel::GetParent(const wxDataViewItem& item) const
{
// the invisible root node has no parent
if (!item.IsOk())
return wxDataViewItem(nullptr);
ModelNode* node = static_cast<ModelNode*>(item.GetID());
if (node->IsRoot())
return wxDataViewItem(nullptr);
return wxDataViewItem((void*)node->GetParent());
}
bool DiffModel::IsContainer(const wxDataViewItem& item) const
{
// the invisble root node can have children
if (!item.IsOk())
return true;
ModelNode* node = static_cast<ModelNode*>(item.GetID());
return node->IsContainer();
}
unsigned int DiffModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
{
ModelNode* parent_node = (ModelNode*)parent.GetID();
const ModelNodePtrArray& children = parent_node ? parent_node->GetChildren() : m_preset_nodes;
for (const std::unique_ptr<ModelNode>& child : children)
array.Add(wxDataViewItem((void*)child.get()));
return array.Count();
}
wxString DiffModel::GetColumnType(unsigned int col) const
{
switch (col)
{
case colToggle:
return "bool";
case colIconText:
case colOldValue:
case colNewValue:
default:
return "DataViewBitmapText";//"string";
}
}
static void rescale_children(ModelNode* parent)
{
if (parent->IsContainer()) {
for (std::unique_ptr<ModelNode> &child : parent->GetChildren()) {
child->UpdateIcons();
rescale_children(child.get());
}
}
}
void DiffModel::Rescale()
{
for (std::unique_ptr<ModelNode> &node : m_preset_nodes) {
node->UpdateIcons();
rescale_children(node.get());
}
}
wxDataViewItem DiffModel::Delete(const wxDataViewItem& item)
{
auto ret_item = wxDataViewItem(nullptr);
ModelNode* node = static_cast<ModelNode*>(item.GetID());
if (!node) // happens if item.IsOk()==false
return ret_item;
// first remove the node from the parent's array of children;
// NOTE: m_preset_nodes is only a vector of _pointers_
// thus removing the node from it doesn't result in freeing it
ModelNodePtrArray& children = node->GetChildren();
// Delete all children
while (!children.empty())
Delete(wxDataViewItem(children.back().get()));
auto node_parent = node->GetParent();
wxDataViewItem parent(node_parent);
ModelNodePtrArray& parents_children = node_parent ? node_parent->GetChildren() : m_preset_nodes;
auto it = find_if(parents_children.begin(), parents_children.end(),
[node](std::unique_ptr<ModelNode>& child) { return child.get() == node; });
assert(it != parents_children.end());
it = parents_children.erase(it);
if (it != parents_children.end())
ret_item = wxDataViewItem(it->get());
// set m_container to FALSE if parent has no child
if (node_parent) {
#ifndef __WXGTK__
if (node_parent->GetChildCount() == 0)
node_parent->m_container = false;
#endif //__WXGTK__
ret_item = parent;
}
// notify control
ItemDeleted(parent, item);
return ret_item;
}
void DiffModel::Clear()
{
while (!m_preset_nodes.empty())
Delete(wxDataViewItem(m_preset_nodes.back().get()));
}
static std::string get_pure_opt_key(std::string opt_key)
{
const int pos = opt_key.find("#");
if (pos > 0)
boost::erase_tail(opt_key, opt_key.size() - pos);
return opt_key;
}
// ----------------------------------------------------------------------------
// DiffViewCtrl
// ----------------------------------------------------------------------------
DiffViewCtrl::DiffViewCtrl(wxWindow* parent, wxSize size)
: wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
),
m_em_unit(em_unit(parent))
{
wxGetApp().UpdateDVCDarkUI(this);
model = new DiffModel(parent);
this->AssociateModel(model);
model->SetAssociatedControl(this);
this->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &DiffViewCtrl::context_menu, this);
this->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, &DiffViewCtrl::context_menu, this);
this->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &DiffViewCtrl::item_value_changed, this);
}
DiffViewCtrl::~DiffViewCtrl() {
this->AssociateModel(nullptr);
delete model;
}
void DiffViewCtrl::AppendBmpTextColumn(const wxString& label, unsigned model_column, int width, bool set_expander/* = false*/)
{
m_columns_width.emplace(this->GetColumnCount(), width);
#ifdef __linux__
wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer();
#ifdef SUPPORTS_MARKUP
rd->EnableMarkup(true);
#endif
wxDataViewColumn* column = new wxDataViewColumn(label, rd, model_column, width * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT);
#else
wxDataViewColumn* column = new wxDataViewColumn(label, new BitmapTextRenderer(true, wxDATAVIEW_CELL_INERT), model_column, width * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE);
#endif //__linux__
this->AppendColumn(column);
if (set_expander)
this->SetExpanderColumn(column);
}
void DiffViewCtrl::AppendToggleColumn_(const wxString& label, unsigned model_column, int width)
{
m_columns_width.emplace(this->GetColumnCount(), width);
AppendToggleColumn(label, model_column, wxDATAVIEW_CELL_ACTIVATABLE, width * m_em_unit);
}
void DiffViewCtrl::Rescale(int em /*= 0*/)
{
if (em > 0) {
for (auto item : m_columns_width)
GetColumn(item.first)->SetWidth(item.second * em);
m_em_unit = em;
}
model->Rescale();
Refresh();
}
void DiffViewCtrl::Append( const std::string& opt_key, Preset::Type type,
wxString category_name, wxString group_name, wxString option_name,
wxString old_value, wxString new_value, const std::string category_icon_name)
{
ItemData item_data = { opt_key, option_name, old_value, new_value, type };
wxString old_val = get_short_string(item_data.old_val);
wxString new_val = get_short_string(item_data.new_val);
if (old_val != item_data.old_val || new_val != item_data.new_val)
item_data.is_long = true;
m_items_map.emplace(model->AddOption(type, category_name, group_name, option_name, old_val, new_val, category_icon_name), item_data);
}
void DiffViewCtrl::Clear()
{
model->Clear();
m_items_map.clear();
}
wxString DiffViewCtrl::get_short_string(wxString full_string)
{
size_t max_len = 30;
if (full_string.IsEmpty() || full_string.StartsWith("#") ||
(full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < max_len))
return full_string;
m_has_long_strings = true;
int n_pos = full_string.Find("\n");
if (n_pos != wxNOT_FOUND && n_pos < (int)max_len)
max_len = n_pos;
full_string.Truncate(max_len);
return full_string + dots;
}
void DiffViewCtrl::context_menu(wxDataViewEvent& event)
{
if (!m_has_long_strings)
return;
wxDataViewItem item = event.GetItem();
if (!item) {
wxPoint mouse_pos = wxGetMousePosition() - this->GetScreenPosition();
wxDataViewColumn* col = nullptr;
this->HitTest(mouse_pos, item, col);
if (!item)
item = this->GetSelection();
if (!item)
return;
}
auto it = m_items_map.find(item);
if (it == m_items_map.end() || !it->second.is_long)
return;
size_t column_cnt = this->GetColumnCount();
const wxString old_value_header = this->GetColumn(column_cnt - 2)->GetTitle();
const wxString new_value_header = this->GetColumn(column_cnt - 1)->GetTitle();
FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val,
old_value_header, new_value_header).ShowModal();
#ifdef __WXOSX__
wxWindow* parent = this->GetParent();
if (parent && parent->IsShown()) {
// if this dialog is shown it have to be Hide and show again to be placed on the very Top of windows
parent->Hide();
parent->Show();
}
#endif // __WXOSX__
}
void DiffViewCtrl::item_value_changed(wxDataViewEvent& event)
{
if (event.GetColumn() != DiffModel::colToggle)
return;
wxDataViewItem item = event.GetItem();
model->UpdateItemEnabling(item);
Refresh();
// update an enabling of the "save/move" buttons
m_empty_selection = selected_options().empty();
}
bool DiffViewCtrl::has_unselected_options()
{
for (auto item : m_items_map)
if (!model->IsEnabledItem(item.first))
return true;
return false;
}
std::vector<std::string> DiffViewCtrl::options(Preset::Type type, bool selected)
{
std::vector<std::string> ret;
for (auto item : m_items_map) {
if (item.second.type == type && model->IsEnabledItem(item.first) == selected)
ret.emplace_back(get_pure_opt_key(item.second.opt_key));
}
return ret;
}
std::vector<std::string> DiffViewCtrl::selected_options()
{
std::vector<std::string> ret;
for (auto item : m_items_map)
if (model->IsEnabledItem(item.first))
ret.emplace_back(get_pure_opt_key(item.second.opt_key));
return ret;
}
//------------------------------------------
// UnsavedChangesDialog
//------------------------------------------
static std::string none{"none"};
#define UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE wxSize(FromDIP(490), FromDIP(374))
#define UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE wxSize(FromDIP(490), FromDIP(60))
#define UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH FromDIP(190)
#define UNSAVE_CHANGE_DIALOG_VALUE_WIDTH FromDIP(150)
#define UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT FromDIP(24)
#define UNSAVE_CHANGE_DIALOG_BUTTON_SIZE wxSize(FromDIP(70), FromDIP(24))
#define THUMB_COLOR wxColor(196, 196, 196)
#define GREY900 wxColour(38, 46, 48)
#define GREY700 wxColour(107,107,107)
#define GREY400 wxColour(206,206,206)
#define GREY300 wxColour(238,238,238)
#define GREY200 wxColour(248,248,248)
UnsavedChangesDialog::UnsavedChangesDialog(const wxString &caption, const wxString &header, const std::string &app_config_key, int act_buttons)
: DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY,
caption + ": " + _L("Unsaved Changes"),
wxDefaultPosition,
wxDefaultSize,
wxCAPTION | wxCLOSE_BOX)
, m_app_config_key(app_config_key)
, m_buttons(act_buttons)
{
build(Preset::TYPE_INVALID, nullptr, "", header);
this->CenterOnScreen();
wxGetApp().UpdateDlgDarkUI(this);
}
UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection *dependent_presets, const std::string &new_selected_preset, bool no_transfer)
: m_new_selected_preset_name(new_selected_preset)
, DPIDialog(static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY,
_L("Transfer or discard changes"),
wxDefaultPosition,
wxDefaultSize,
wxCAPTION | wxCLOSE_BOX)
{
if (new_selected_preset.empty() || no_transfer)
m_buttons &= ~ActionButtons::TRANSFER;
build(type, dependent_presets, new_selected_preset);
this->CenterOnScreen();
wxGetApp().UpdateDlgDarkUI(this);
}
inline int UnsavedChangesDialog::ShowModal()
{
auto choise_key = "save_preset_choise";
auto choise = wxGetApp().app_config->get(choise_key);
long result = 0;
if ((m_buttons & REMEMBER_CHOISE) && !choise.empty() && wxString(choise).ToLong(&result) && (1 << result) & (m_buttons | DONT_SAVE)) {
m_exit_action = Action(result);
return 0;
}
int r = DPIDialog::ShowModal();
if (r != wxID_CANCEL && dynamic_cast<::CheckBox*>(FindWindowById(wxID_APPLY))->GetValue()) {
wxGetApp().app_config->set(choise_key, std::to_string(int(m_exit_action)));
}
return r;
}
void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_presets, const std::string &new_selected_preset, const wxString &header)
{
SetBackgroundColour(*wxWHITE);
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
m_top_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
m_top_line->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_top_line, 0, wxEXPAND, 0);
m_sizer_main->Add(0, 0, 0, wxTOP, 20);
m_action_line = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE, 0);
m_action_line->SetFont(::Label::Body_13);
m_action_line->SetForegroundColour(GREY900);
m_action_line->Wrap(-1);
m_sizer_main->Add(m_action_line, 0, wxLEFT | wxRIGHT, 20);
m_sizer_main->Add(0, 0, 0, wxTOP, 12);
m_panel_tab = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE.x, -1), wxTAB_TRAVERSAL);
m_panel_tab->SetBackgroundColour(GREY200);
wxBoxSizer *m_sizer_tab = new wxBoxSizer(wxVERTICAL);
m_table_top = new wxPanel(m_panel_tab, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_table_top->SetBackgroundColour(wxColour(107, 107, 107));
wxBoxSizer *m_sizer_top = new wxBoxSizer(wxHORIZONTAL);
//m_sizer_top->Add(0, 0, 0, wxLEFT, UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH);
auto m_panel_temp = new wxPanel(m_table_top, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH, -1), wxTAB_TRAVERSAL);
wxBoxSizer *top_title_temp_v = new wxBoxSizer(wxVERTICAL);
top_title_temp_v->SetMinSize(wxSize(UNSAVE_CHANGE_DIALOG_VALUE_WIDTH, -1));
wxBoxSizer *top_title_temp_h = new wxBoxSizer(wxHORIZONTAL);
static_temp_title = new wxStaticText(m_panel_temp, wxID_ANY, _L("Settings"), wxDefaultPosition, wxDefaultSize, 0);
static_temp_title->SetFont(::Label::Body_13);
static_temp_title->Wrap(-1);
static_temp_title->SetForegroundColour(*wxWHITE);
top_title_temp_h->Add(static_temp_title, 0, wxALIGN_CENTER | wxBOTTOM | wxTOP, 5);
top_title_temp_v->Add(top_title_temp_h, 1, wxALIGN_CENTER, 0);
m_panel_temp->SetSizer(top_title_temp_v);
m_panel_temp->Layout();
m_sizer_top->Add(m_panel_temp, 1, wxALIGN_CENTER, 0);
title_block_middle = new wxPanel(m_table_top, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
title_block_middle->SetBackgroundColour(wxColour(172, 172, 172));
m_sizer_top->Add(title_block_middle, 0, wxBOTTOM | wxEXPAND | wxTOP, 2);
auto m_panel_oldv = new wxPanel( m_table_top, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_VALUE_WIDTH,-1), wxTAB_TRAVERSAL );
wxBoxSizer *top_title_oldv = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *top_title_oldv_h = new wxBoxSizer(wxHORIZONTAL);
static_oldv_title = new wxStaticText(m_panel_oldv, wxID_ANY, _L("Old Value"), wxDefaultPosition, wxDefaultSize, 0);
static_oldv_title->SetFont(::Label::Body_13);
static_oldv_title->Wrap(-1);
static_oldv_title->SetForegroundColour(*wxWHITE);
top_title_oldv_h->Add(static_oldv_title, 0, wxALIGN_CENTER | wxBOTTOM | wxTOP, 5);
top_title_oldv->Add(top_title_oldv_h, 1, wxALIGN_CENTER, 0);
m_panel_oldv->SetSizer(top_title_oldv);
m_panel_oldv->Layout();
m_sizer_top->Add(m_panel_oldv, 0, wxALIGN_CENTER, 0);
title_block_right = new wxPanel(m_table_top, wxID_ANY, wxDefaultPosition, wxSize(1, -1), wxTAB_TRAVERSAL);
title_block_right->SetBackgroundColour(wxColour(172, 172, 172));
m_sizer_top->Add(title_block_right, 0, wxBOTTOM | wxEXPAND | wxTOP, 2);
auto m_panel_newv = new wxPanel( m_table_top, wxID_ANY, wxDefaultPosition, wxSize( UNSAVE_CHANGE_DIALOG_VALUE_WIDTH,-1 ), wxTAB_TRAVERSAL );
wxBoxSizer *top_title_newv = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *top_title_newv_h = new wxBoxSizer(wxHORIZONTAL);
static_newv_title = new wxStaticText(m_panel_newv, wxID_ANY, _L("New Value"), wxDefaultPosition, wxDefaultSize, 0);
static_newv_title->SetFont(::Label::Body_13);
static_newv_title->Wrap(-1);
static_newv_title->SetForegroundColour(*wxWHITE);
top_title_newv_h->Add(static_newv_title, 0, wxALIGN_CENTER | wxBOTTOM | wxTOP, 5);
top_title_newv->Add(top_title_newv_h, 1, wxALIGN_CENTER, 0);
m_panel_newv->SetSizer(top_title_newv);
m_panel_newv->Layout();
m_sizer_top->Add(m_panel_newv, 0, wxALIGN_CENTER, 0);
//m_sizer_top->Add(top_title_newv, 1, wxALIGN_CENTER, 0);
m_table_top->SetSizer(m_sizer_top);
m_table_top->Layout();
m_sizer_top->Fit(m_table_top);
m_sizer_tab->Add(m_table_top, 1, 0, 0);
m_scrolledWindow = new wxScrolledWindow(m_panel_tab, wxID_ANY, wxDefaultPosition, UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE, wxNO_BORDER|wxVSCROLL);
m_scrolledWindow->SetScrollRate(0, 5);
m_scrolledWindow->SetBackgroundColour(GREY200);
m_sizer_bottom = new wxBoxSizer(wxVERTICAL);
m_sizer_bottom->Add(m_scrolledWindow, 1, wxEXPAND, 0);
m_sizer_tab->Add(m_sizer_bottom, 0, wxEXPAND, 0);
m_panel_tab->SetSizer(m_sizer_tab);
m_panel_tab->Layout();
m_sizer_tab->Fit(m_panel_tab);
m_sizer_main->Add(m_panel_tab, 0, wxEXPAND | wxLEFT | wxRIGHT, 20);
m_sizer_main->Add(0, 0, 0, wxTOP, 9);
/* m_info_line = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(-1, 44), 0);
m_info_line->SetFont(::Label::Body_13);
m_info_line->Wrap(-1);
m_info_line->SetForegroundColour(wxColour(255, 111, 0));
m_sizer_main->Add(m_info_line, 0, wxLEFT | wxRIGHT, 20);*/
wxBoxSizer *m_sizer_button = new wxBoxSizer(wxHORIZONTAL);
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
auto checkbox = new ::CheckBox(this, wxID_APPLY);
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Remember my choice."), wxDefaultPosition, wxDefaultSize, 0);
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
m_sizer_button->Add(checkbox_sizer, 0, wxLEFT, FromDIP(22));
checkbox_sizer->Show(bool(m_buttons & REMEMBER_CHOISE));
if (dependent_presets != nullptr) {
auto wiki = new HyperLink(this, _L("Help"), "https://www.orcaslicer.com/wiki/transfer_discard_changes");
m_sizer_button->Add(wiki, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(22));
}
m_sizer_button->Add(0, 0, 1, 0, 0);
// Add Buttons
auto add_btn = [this, m_sizer_button, dependent_presets](Button **btn, int &btn_id, const std::string &icon_name, Action close_act, const wxString &label,
bool focus, bool process_enable = true) {
*btn = new Button(this, _L(label));
(*btn)->SetStyle(focus ? ButtonStyle::Confirm : ButtonStyle::Regular, ButtonType::Choice);
(*btn)->Bind(wxEVT_BUTTON, [this, close_act, dependent_presets](wxEvent &) {
bool save_names_and_types = close_act == Action::Save || (close_act == Action::Transfer && ActionButtons::KEEP & m_buttons);
if (save_names_and_types && !save(dependent_presets, close_act == Action::Save)) return;
close(close_act);
});
// if (process_enable) (*btn)->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent &evt) { evt.Enable(m_tree->has_selection()); });
(*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &e) {
show_info_line(Action::Undef);
e.Skip();
});
m_sizer_button->Add(*btn, 0, wxLEFT, FromDIP(ButtonProps::ChoiceButtonGap()));
};
// "Transfer" / "Keep" button
if (ActionButtons::TRANSFER & m_buttons) {
const PresetCollection* switched_presets = type == Preset::TYPE_INVALID ? nullptr : wxGetApp().get_tab(type)->get_presets();
if (dependent_presets && switched_presets && (type == dependent_presets->type() ?
dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() :
switched_presets->get_edited_preset().printer_technology() == switched_presets->find_preset(new_selected_preset)->printer_technology()))
add_btn(&m_transfer_btn, m_move_btn_id, "menu_paste", Action::Transfer, switched_presets->get_edited_preset().name == new_selected_preset ? _L("Transfer") : _L("Transfer"), true);
}
if (!m_transfer_btn && (ActionButtons::KEEP & m_buttons))
add_btn(&m_transfer_btn, m_move_btn_id, "menu_paste", Action::Transfer, _L("Transfer"), true);
{ // "Don't save" / "Discard" button
std::string btn_icon = (ActionButtons::DONT_SAVE & m_buttons) ? "" : (dependent_presets || (ActionButtons::KEEP & m_buttons)) ? "blank_16" : "exit";
wxString btn_label = (ActionButtons::DONT_SAVE & m_buttons) ? _L("Don't save") : _L("Discard");
add_btn(&m_discard_btn, m_continue_btn_id, btn_icon, Action::Discard, btn_label, false);
}
// "Save" button
if (ActionButtons::SAVE & m_buttons) add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save"), false);
if (dependent_presets != nullptr) {
const wxString previous_profile = from_u8(dependent_presets->get_edited_preset().name);
const wxString new_profile = new_selected_preset.empty() ? _L("the new profile") : from_u8(new_selected_preset);
if (m_discard_btn) {
m_discard_btn->SetToolTip(format_wxstr(
_L("Switch to\n\"%1%\"\ndiscarding any changes made in\n\"%2%\"."),
new_profile,
previous_profile));
}
if (m_transfer_btn) {
m_transfer_btn->SetToolTip(format_wxstr(
_L("All \"New Value\" settings modified in\n\"%1%\"\nwill be transferred to\n\"%2%\"."),
previous_profile,
new_profile));
}
if (m_save_btn) {
m_save_btn->SetToolTip(format_wxstr(
_L("All \"New Value\" settings are saved in\n\"%1%\"\nand \"%2%\" will open without any changes."),
previous_profile,
new_profile));
}
}
/* ScalableButton *cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24);
buttons->Add(cancel_btn, 1, wxLEFT | wxRIGHT, 5);
cancel_btn->SetFont(btn_font);*/
/* m_cancel_btn = new Button(this, _L("Cancel"));
m_cancel_btn->SetTextColor(wxColour(107, 107, 107));
m_cancel_btn->Bind(wxEVT_LEFT_DOWN, [this](wxEvent &) { this->EndModal(wxID_CANCEL); });
m_cancel_btn->SetMinSize(UNSAVE_CHANGE_DIALOG_BUTTON_SIZE);
m_cancel_btn->SetCornerRadius(12);
m_sizer_button->Add(m_cancel_btn, 0, wxLEFT, 5);
m_sizer_button->Add(0,0,0,wxRIGHT,20);*/
if (!m_app_config_key.empty()) {}
m_sizer_button->Add(0, 0, 0, wxRIGHT, 20);
m_sizer_main->Add(m_sizer_button, 0, wxEXPAND | wxTOP, 6);
m_sizer_main->Add(0, 0, 1, wxTOP, 18);
SetSizer(m_sizer_main);
Layout();
Fit();
Centre(wxBOTH);
update(type, dependent_presets, new_selected_preset, header);
//SetSizer(topSizer);
//topSizer->SetSizeHints(this);
show_info_line(Action::Undef);
}
void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name)
{
return;
if (action == Action::Undef && !m_has_long_strings)
m_info_line->SetLabel(wxEmptyString);
else {
wxString text;
if (action == Action::Undef)
text = _L("Click the right mouse button to display the full text.");
else if (action == Action::Discard)
text = ActionButtons::DONT_SAVE & m_buttons ? _L("All changes will not be saved") :_L("All changes will be discarded.");
else {
if (preset_name.empty())
text = action == Action::Save ? _L("Save the selected options.") :
ActionButtons::KEEP & m_buttons ? _L("Keep the selected options.") :
_L("Transfer the selected options to the newly selected preset.");
else
text = format_wxstr(
action == Action::Save ?
_L("Save the selected options to preset \n\"%1%\".") :
_L("Transfer the selected options to the newly selected preset \n\"%1%\"."),
preset_name);
//text += "\n" + _L("Unselected options will be reverted.");
}
m_info_line->SetLabel(text);
m_info_line->Show();
}
//Layout();
//Refresh();
}
void UnsavedChangesDialog::close(Action action)
{
if (action == Action::Transfer) {
check_option_valid();
}
m_exit_action = action;
this->EndModal(wxID_CLOSE);
}
bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_save_preset_dialog/* = true*/)
{
names_and_types.clear();
// save one preset
if (dependent_presets) {
const Preset& preset = dependent_presets->get_edited_preset();
std::string name = preset.name;
// for system/default/external presets we should take an edited name
//BBS: add project embedded preset logic and refine is_external
bool save_to_project = false;
if (!preset.can_overwrite()) {
//if (preset.is_system || preset.is_default || preset.is_external) {
SavePresetDialog save_dlg(this, preset.type);
if (save_dlg.ShowModal() != wxID_OK) {
m_exit_action = Action::Discard;
return false;
}
name = save_dlg.get_name();
save_to_project = save_dlg.get_save_to_project_selection(preset.type);
}
//BBS: add project embedded preset relate logic
PresetData preset_data(name, preset.type, save_to_project);
names_and_types.emplace_back(preset_data);
//names_and_types.emplace_back(make_pair(name, preset.type));
}
// save all presets
else
{
std::vector<Preset::Type> types_for_save;
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : wxGetApp().tabs_list)
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) {
const Preset& preset = tab->get_presets()->get_edited_preset();
//BBS: add project embedded preset logic and refine is_external
if (!preset.can_overwrite())
//if (preset.is_system || preset.is_default || preset.is_external)
types_for_save.emplace_back(preset.type);
//BBS: add project embedded preset relate logic
PresetData preset_data(preset.name, preset.type, preset.is_project_embedded);
names_and_types.emplace_back(preset_data);
//names_and_types.emplace_back(make_pair(preset.name, preset.type));
}
if (show_save_preset_dialog && !types_for_save.empty()) {
SavePresetDialog save_dlg(this, types_for_save);
if (save_dlg.ShowModal() != wxID_OK) {
m_exit_action = Action::Discard;
return false;
}
//BBS: add project embedded preset relate logic
for (PresetData& nt : names_and_types) {
const std::string& name = save_dlg.get_name(nt.type);
if (!name.empty())
nt.name = name;
nt.save_to_project = save_dlg.get_save_to_project_selection(nt.type);
}
//for (std::pair<std::string, Preset::Type>& nt : names_and_types) {
// const std::string& name = save_dlg.get_name(nt.second);
// if (!name.empty())
// nt.first = name;
//}
}
}
return true;
}
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1)
{
const ConfigOptionDef& def = config.def()->options.at(opt_key);
const std::vector<std::string>& names = def.enum_labels;//ConfigOptionEnum<T>::get_enum_names();
int val = 0;
if (idx >= 0)
val = dynamic_cast<const ConfigOptionInts*>(config.option(opt_key))->get_at(idx);
else
val = config.option(opt_key)->getInt();
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
// So we should "convert" val to the correct one
if (is_infill) {
for (auto key_val : *def.enum_keys_map)
if (int(key_val.second) == val) {
auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first);
if (it == def.enum_values.end())
return "";
return from_u8(_utf8(names[it - def.enum_values.begin()]));
}
return _L("Undef");
}
return from_u8(_utf8(names[val]));
}
// BBS
#if 0
static size_t get_id_from_opt_key(std::string opt_key)
{
int pos = opt_key.find("#");
if (pos > 0) {
boost::erase_head(opt_key, pos + 1);
return static_cast<size_t>(atoi(opt_key.c_str()));
}
return 0;
}
#endif
static wxString get_full_label(std::string opt_key, const DynamicPrintConfig& config)
{
opt_key = get_pure_opt_key(opt_key);
auto option = config.option(opt_key);
if (!option || option->is_nil())
return _L("N/A");
const ConfigOptionDef* opt = config.def()->get(opt_key);
return opt->full_label.empty() ? opt->label : opt->full_label;
}
static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config)
{
int orig_opt_idx = -1;
int opt_idx = -1;
int pos = opt_key.find("#");
std::string temp_str = opt_key;
if (pos > 0) {
boost::erase_head(temp_str, pos + 1);
orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
}
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
opt_key = get_pure_opt_key(opt_key);
auto option = config.option(opt_key);
if (!option) {
return _L("N/A");
}
if (option->is_scalar() && config.option(opt_key)->is_nil() ||
option->is_vector() && dynamic_cast<const ConfigOptionVectorBase *>(config.option(opt_key))->is_nil(opt_idx))
return _L("N/A");
wxString out;
const ConfigOptionDef* opt = config.def()->get(opt_key);
bool is_nullable = opt->nullable;
switch (opt->type) {
case coInt:
return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str());
case coInts: {
if (is_nullable) {
auto values = config.opt<ConfigOptionIntsNullable>(opt_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
}
else {
auto values = config.opt<ConfigOptionInts>(opt_key);
if (orig_opt_idx >= 0 && orig_opt_idx < values->size()) {
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
}
else {
std::string value_str;
for (int i = 0; i < values->size(); i++) {
value_str += std::to_string(values->get_at(i));
if (i != values->size() - 1) {
value_str += ",";
}
}
return from_u8(value_str);
}
}
return _L("Undef");
}
case coBool:
return config.opt_bool(opt_key) ? "true" : "false";
case coBools: {
if (is_nullable) {
auto values = config.opt<ConfigOptionBoolsNullable>(opt_key);
if (opt_idx < values->size())
return values->get_at(opt_idx) ? "true" : "false";
}
else {
auto values = config.opt<ConfigOptionBools>(opt_key);
if (opt_idx < values->size())
return values->get_at(opt_idx) ? "true" : "false";
}
return _L("Undef");
}
case coPercent:
return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str());
case coPercents: {
if (is_nullable) {
auto values = config.opt<ConfigOptionPercentsNullable>(opt_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
}
else {
auto values = config.opt<ConfigOptionPercents>(opt_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
}
return _L("Undef");
}
case coFloat:
return double_to_string(config.opt_float(opt_key));
case coFloats: {
if (is_nullable) {
auto values = config.opt<ConfigOptionFloatsNullable>(opt_key);
if (opt_idx < values->size())
return double_to_string(values->get_at(opt_idx));
}
else {
auto values = config.opt<ConfigOptionFloats>(opt_key);
if (values && opt_idx < values->size())
return double_to_string(values->get_at(opt_idx));
}
return _L("Undef");
}
case coString:
return from_u8(config.opt_string(opt_key));
case coStrings: {
const ConfigOptionStrings* strings = config.opt<ConfigOptionStrings>(opt_key);
if (strings) {
if (opt_key == "compatible_printers" || opt_key == "compatible_prints") {
if (strings->empty())
return _L("All");
for (size_t id = 0; id < strings->size(); id++)
out += from_u8(strings->get_at(id)) + "\n";
out.RemoveLast(1);
return out;
}
if (!strings->empty() && opt_idx < strings->values.size())
return from_u8(strings->get_at(opt_idx));
}
break;
}
case coFloatOrPercent: {
const ConfigOptionFloatOrPercent* opt = config.opt<ConfigOptionFloatOrPercent>(opt_key);
if (opt)
out = double_to_string(opt->value) + (opt->percent ? "%" : "");
return out;
}
case coEnum: {
return get_string_from_enum(opt_key, config,
opt_key == "top_surface_pattern" ||
opt_key == "bottom_surface_pattern" ||
opt_key == "internal_solid_infill_pattern" ||
opt_key == "sparse_infill_pattern" ||
opt_key == "ironing_pattern" ||
opt_key == "support_ironing_pattern" ||
opt_key == "support_pattern" ||
opt_key == "support_interface_pattern")
;
}
case coEnums: {
return get_string_from_enum(opt_key, config,
opt_key == "top_surface_pattern" ||
opt_key == "bottom_surface_pattern" ||
opt_key == "internal_solid_infill_pattern" ||
opt_key == "sparse_infill_pattern" ||
opt_key == "ironing_pattern" ||
opt_key == "support_ironing_pattern" ||
opt_key == "support_pattern" ||
opt_key == "support_interface_pattern"
, opt_idx);
}
case coPoint: {
Vec2d val = config.opt<ConfigOptionPoint>(opt_key)->value;
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}
case coPoints: {
//BBS: add bed_exclude_area
if (opt_key == "printable_area" || opt_key == "thumbnails") {
ConfigOptionPoints points = *config.option<ConfigOptionPoints>(opt_key);
//BuildVolume build_volume = {points.values, 0.};
return get_thumbnails_string(points.values);
}
else if (opt_key == "bed_exclude_area") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
}
else if (opt_key == "head_wrap_detect_zone") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
}
else if (opt_key == "wrapping_exclude_area") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
}
Vec2d val = config.opt<ConfigOptionPoints>(opt_key)->get_at(opt_idx);
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}
default:
break;
}
return out;
}
void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header)
{
PresetCollection* presets = dependent_presets;
// activate buttons and labels
if (m_save_btn)
m_save_btn->Bind(wxEVT_ENTER_WINDOW, [this, presets](wxMouseEvent &e) {
show_info_line(Action::Save, presets ? presets->get_selected_preset().name : "");
e.Skip();
});
if (m_transfer_btn) {
bool is_empty_name = dependent_presets && type != dependent_presets->type();
m_transfer_btn->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name](wxMouseEvent& e) { show_info_line(Action::Transfer, is_empty_name ? "" : new_selected_preset); e.Skip(); });
}
if (m_discard_btn)
m_discard_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Discard); e.Skip(); });
if (type == Preset::TYPE_INVALID) {
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
int presets_cnt = 0;
for (Tab* tab : wxGetApp().tabs_list)
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
presets_cnt++;
/*m_action_line->SetLabel((header.IsEmpty() ? "" : header + "\n\n") +
_L_PLURAL("The following preset was modified",
"The following presets were modified", presets_cnt));*/
}
else {
wxString action_msg;
if (type == dependent_presets->type()) {
action_msg = format_wxstr(_L("Preset \"%1%\" contains the following unsaved changes:"), presets->get_edited_preset().name);
}
else {
action_msg = format_wxstr(type == Preset::TYPE_PRINTER ?
_L("Preset \"%1%\" is not compatible with the new printer profile and it contains the following unsaved changes:") :
_L("Preset \"%1%\" is not compatible with the new process profile and it contains the following unsaved changes:"),
presets->get_edited_preset().name);
}
//m_action_line->SetLabel(action_msg);
}
wxString action_msg;
if (dependent_presets) {
action_msg = format_wxstr(_L("You have changed some settings of preset \"%1%\"."), dependent_presets->get_edited_preset().name);
if (!m_transfer_btn) {
action_msg += _L("\nYou can save or discard the preset values you have modified.");
} else {
action_msg += _L("\nYou can save or discard the preset values you have modified, or choose to transfer the values you have modified to the new preset.");
}
} else {
action_msg = _L("You have previously modified your settings.");
if (m_transfer_btn)
action_msg += _L("\nYou can discard the preset values you have modified, or choose to transfer the modified values to the new project");
else
action_msg += _L("\nYou can save or discard the preset values you have modified.");
}
m_action_line->SetLabel(action_msg);
update_tree(type, presets);
update_list();
}
void UnsavedChangesDialog::update_list()
{
std::map<wxString, std::vector<PresetItem>> class_g_list;
std::map<wxString, std::vector<wxString>> class_c_list;
// group
for (auto i = 0; i < m_presetitems.size(); i++) {
if (class_g_list.count(m_presetitems[i].group_name) <= 0) {
std::vector<PresetItem> vp;
vp.push_back(m_presetitems[i]);
class_g_list.emplace(m_presetitems[i].group_name, vp);
} else {
//for (auto iter = class_g_list.begin(); iter != class_g_list.end(); iter++) iter->second.push_back(m_presetitems[i]);
class_g_list[m_presetitems[i].group_name].push_back(m_presetitems[i]);
}
}
// category
for (auto i = 0; i < m_presetitems.size(); i++) {
if (class_c_list.count(m_presetitems[i].category_name) <= 0) {
std::vector<wxString> vp;
vp.push_back(m_presetitems[i].group_name);
class_c_list.emplace(m_presetitems[i].category_name, vp);
} else {
/*for (auto iter = class_c_list.begin(); iter != class_c_list.end(); iter++)
iter->second.push_back(m_presetitems[i].group_name);*/
//class_c_list[m_presetitems[i].category_name].push_back(m_presetitems[i].group_name);
std::vector<wxString>::iterator it;
it = find(class_c_list[m_presetitems[i].category_name].begin(), class_c_list[m_presetitems[i].category_name].end(), m_presetitems[i].group_name);
if (it == class_c_list[m_presetitems[i].category_name].end()) {
class_c_list[m_presetitems[i].category_name].push_back(m_presetitems[i].group_name);
}
}
}
auto m_listsizer = new wxBoxSizer(wxVERTICAL);
for (auto iter = class_c_list.begin(); iter != class_c_list.end(); iter++) {
//category
auto panel_category = new wxPanel(m_scrolledWindow, wxID_ANY, wxDefaultPosition, wxSize(-1, UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT), wxTAB_TRAVERSAL);
panel_category->SetBackgroundColour(GREY300);
wxBoxSizer *sizer_category = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *sizer_category_v = new wxBoxSizer(wxHORIZONTAL);
auto text_category = new wxStaticText(panel_category, wxID_ANY, iter->first, wxDefaultPosition, wxSize(-1, -1), 0);
text_category->SetFont(::Label::Head_13);
text_category->SetForegroundColour(GREY900);
text_category->Wrap(-1);
sizer_category_v->Add(text_category, 0, wxALIGN_CENTER | wxLEFT, 23);
sizer_category->Add(sizer_category_v, 1, wxEXPAND, 0);
panel_category->SetSizer(sizer_category);
panel_category->Layout();
m_listsizer->Add(panel_category, 0, wxEXPAND, 0);
/*auto item_line = new wxStaticLine(list, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
item_line->SetForegroundColour(wxColour(206, 206, 206));
item_line->SetBackgroundColour(wxColour(206, 206, 206));
sizer_list->Add(item_line, 0, wxALL, 0); */
// isset group
for (auto i = 0; i < iter->second.size(); i++) {
auto gname = iter->second[i];
for (auto g = 0; g < class_g_list[gname].size(); g++) {
//first group
if (g == 0) {
auto panel_item = new wxWindow(m_scrolledWindow, -1, wxDefaultPosition, wxSize(-1, UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT));
panel_item->SetBackgroundColour(GREY200);
wxBoxSizer *sizer_item = new wxBoxSizer(wxHORIZONTAL);
auto panel_left = new wxPanel(panel_item, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH, -1), wxTAB_TRAVERSAL);
panel_left->SetBackgroundColour(GREY200);
wxBoxSizer *sizer_left_v = new wxBoxSizer(wxVERTICAL);
auto text_left = new wxStaticText(panel_left, wxID_ANY, gname, wxDefaultPosition, wxSize(-1, -1), 0);
text_left->SetFont(::Label::Head_13);
text_left->Wrap(-1);
text_left->SetForegroundColour(GREY700);
sizer_left_v->Add(text_left, 0, wxLEFT, 37);
panel_left->SetSizer(sizer_left_v);
panel_left->Layout();
sizer_item->Add(panel_left, 0, wxALIGN_CENTER, 0);
panel_item->SetSizer(sizer_item);
panel_item->Layout();
m_listsizer->Add(panel_item, 0, wxEXPAND, 0);
}
auto data = class_g_list[gname][g];
auto panel_item = new wxWindow(m_scrolledWindow, -1, wxDefaultPosition, wxSize(-1, UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT));
panel_item->SetBackgroundColour(GREY200);
wxBoxSizer *sizer_item = new wxBoxSizer(wxHORIZONTAL);
auto panel_left = new wxPanel(panel_item, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH , -1), wxTAB_TRAVERSAL);
panel_left->SetBackgroundColour(GREY200);
wxBoxSizer *sizer_left_v = new wxBoxSizer(wxVERTICAL);
auto text_left = new wxStaticText(panel_left, wxID_ANY, data.option_name, wxDefaultPosition, wxSize(-1, -1), 0);
text_left->SetFont(::Label::Body_13);
text_left->Wrap(-1);
text_left->SetForegroundColour(GREY700);
sizer_left_v->Add(text_left, 0, wxLEFT, 51 );
panel_left->SetSizer(sizer_left_v);
panel_left->Layout();
sizer_item->Add(panel_left, 0, wxALIGN_CENTER, 0);
auto panel_oldv = new wxPanel(panel_item, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_VALUE_WIDTH, -1), wxTAB_TRAVERSAL);
wxBoxSizer *sizer_old_v = new wxBoxSizer(wxVERTICAL);
data.old_value = subreplace(data.old_value.ToStdString(), "\n", " ");
auto text_oldv = new wxStaticText(panel_oldv, wxID_ANY, data.old_value, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
text_oldv->SetFont(::Label::Body_13);
text_oldv->Wrap(-1);
text_oldv->SetForegroundColour(GREY700);
sizer_old_v->Add(text_oldv, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, 5);
panel_oldv->SetSizer(sizer_old_v);
panel_oldv->Layout();
sizer_item->Add(panel_oldv, 0, wxALIGN_CENTER, 0);
auto panel_newv = new wxPanel(panel_item, wxID_ANY, wxDefaultPosition, wxSize(UNSAVE_CHANGE_DIALOG_VALUE_WIDTH, -1), wxTAB_TRAVERSAL);
wxBoxSizer *sizer_new_v = new wxBoxSizer(wxVERTICAL);
data.new_value = subreplace(data.new_value.ToStdString(), "\n", " ");
auto text_newv = new wxStaticText(panel_newv, wxID_ANY, data.new_value, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
text_newv->SetFont(::Label::Body_13);
text_newv->Wrap(-1);
text_newv->SetForegroundColour(GREY700);
sizer_new_v->Add(text_newv, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, 5);
panel_newv->SetSizer(sizer_new_v);
panel_newv->Layout();
sizer_item->Add(panel_newv, 0, wxALIGN_CENTER, 0);
panel_item->SetSizer(sizer_item);
panel_item->Layout();
m_listsizer->Add(panel_item, 0, wxEXPAND, 0);
////if (g == class_g_list[gname].size() - 1) {
// auto item_line = new wxStaticLine(list, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
// item_line->SetForegroundColour(wxColour(206, 206, 206));
// item_line->SetBackgroundColour(wxColour(206, 206, 206));
// sizer_list->Add(item_line, 0, wxALL, 0);
////}
}
}
}
m_scrolledWindow->SetSizer(m_listsizer);
// m_scrolledWindow->Layout();
wxSize text_size = m_action_line->GetTextExtent(m_action_line->GetLabel());
int width = UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE.GetWidth();
// +2: Ensure that there is at least one line and that the content contains '\n'
int rows = int(text_size.GetWidth() / width) + 2;
int height = rows * text_size.GetHeight();
m_action_line->SetMinSize(wxSize(width, height));
Layout();
Fit();
}
std::string UnsavedChangesDialog::subreplace(std::string resource_str, std::string sub_str, std::string new_str)
{
std::string dst_str = resource_str;
std::string::size_type pos = 0;
while ((pos = dst_str.find(sub_str)) != std::string::npos)
{
dst_str.replace(pos, sub_str.length(), new_str);
}
return dst_str;
}
void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* presets_)
{
Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher();
searcher.sort_options_by_key();
// list of the presets with unsaved changes
std::vector<PresetCollection*> presets_list;
if (type == Preset::TYPE_INVALID)
{
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : wxGetApp().tabs_list)
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
presets_list.emplace_back(tab->get_presets());
}
else
presets_list.emplace_back(presets_);
// Display a dialog showing the dirty options in a human readable form.
for (PresetCollection* presets : presets_list)
{
const DynamicPrintConfig& old_config = presets->get_selected_preset().config;
const PrinterTechnology& old_pt = presets->get_selected_preset().printer_technology();
const DynamicPrintConfig& new_config = presets->get_edited_preset().config;
type = presets->type();
const std::map<wxString, std::string>& category_icon_map = wxGetApp().get_tab(type)->get_category_icon_map();
//m_tree->model->AddPreset(type, from_u8(presets->get_edited_preset().name), old_pt);
// Collect dirty options.
const bool deep_compare = (type == Preset::TYPE_PRINTER ||
type == Preset::TYPE_FILAMENT || type == Preset::TYPE_SLA_MATERIAL);
auto dirty_options = presets->current_dirty_options(deep_compare);
// process changes of extruders count
if (type == Preset::TYPE_PRINTER && old_pt == ptFFF &&
old_config.opt<ConfigOptionFloats>("nozzle_diameter")->values.size() != new_config.opt<ConfigOptionFloats>("nozzle_diameter")->values.size()) {
wxString local_label = _L("Extruder count");
wxString old_val = from_u8((boost::format("%1%") % old_config.opt<ConfigOptionFloats>("nozzle_diameter")->values.size()).str());
wxString new_val = from_u8((boost::format("%1%") % new_config.opt<ConfigOptionFloats>("nozzle_diameter")->values.size()).str());
//BBS: the page "General" changed to "Basic information" instead
//m_tree->Append("extruders_count", type, _L("General"), _L("Capabilities"), local_label, old_val, new_val, category_icon_map.at("Basic information"));
//m_tree->Append("extruders_count", type, _L("General"), _L("Capabilities"), local_label, old_val, new_val, category_icon_map.at("General"));
if (old_val != new_val) {
PresetItem pi = {type, "extruders_count", _L("General"), _L("Capabilities"), local_label, old_val, new_val};
m_presetitems.push_back(pi);
}
}
for (const std::string& opt_key : dirty_options) {
const std::string lookup_key = get_pure_opt_key(opt_key);
Search::Option option = searcher.get_option(lookup_key, type);
if (get_pure_opt_key(option.opt_key()) != lookup_key)
option = searcher.get_option(opt_key, get_full_label(opt_key, new_config), type);
if (get_pure_opt_key(option.opt_key()) != lookup_key) {
// When the found option is not the requested one.
// This can happen for dirty_options such as:
// "default_print_profile", "printer_model", "printer_settings_id",
// because they do not exist in the searcher.
continue;
}
/*m_tree->Append(opt_key, type, option.category_local, option.group_local, option.label_local,
get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), category_icon_map.at(option.category));*/
//PresetItem pi = {opt_key, type, 1983};
//m_presetitems.push_back()
PresetItem pi = {type, opt_key, option.category_local, option.group_local, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config)};
m_presetitems.push_back(pi);
}
}
// Revert sort of searcher back
searcher.sort_options_by_label();
}
void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect)
{
int em = em_unit();
//msw_buttons_rescale(this, em, { wxID_CANCEL, m_move_btn_id, m_continue_btn_id });
for (auto btn : {m_transfer_btn, m_discard_btn, m_save_btn})
if (btn) btn->Rescale();
//m_cancel_btn->SetMinSize(UNSAVE_CHANGE_DIALOG_BUTTON_SIZE);
const wxSize& size = wxSize(70 * em, 30 * em);
SetMinSize(size);
//m_tree->Rescale(em);
Fit();
Refresh();
}
void UnsavedChangesDialog::on_sys_color_changed()
{
//for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn } )
//btn->msw_rescale();
// msw_rescale updates just icons, so use it
//m_tree->Rescale();
Refresh();
}
bool UnsavedChangesDialog::check_option_valid()
{
return true;
}
//------------------------------------------
// FullCompareDialog
//------------------------------------------
FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value,
const wxString& old_value_header, const wxString& new_value_header)
: wxDialog(nullptr, wxID_ANY, option_name, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
SetBackgroundColour(*wxWHITE);
int border = 10;
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this);
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, 2, 1, 0);
grid_sizer->SetFlexibleDirection(wxBOTH);
grid_sizer->AddGrowableCol(0,1);
grid_sizer->AddGrowableCol(1,1);
grid_sizer->AddGrowableRow(1,1);
auto add_header = [grid_sizer, border, this](wxString label) {
wxStaticText* text = new wxStaticText(this, wxID_ANY, label);
text->SetFont(this->GetFont().Bold());
grid_sizer->Add(text, 0, wxALL, border);
};
add_header(old_value_header);
add_header(new_value_header);
auto get_set_from_val = [](wxString str) {
if (str.Find("\n") == wxNOT_FOUND)
str.Replace(" ", "\n");
std::set<wxString> str_set;
wxStringTokenizer strings(str, "\n");
while (strings.HasMoreTokens())
str_set.emplace(strings.GetNextToken());
return str_set;
};
std::set<wxString> old_set = get_set_from_val(old_value);
std::set<wxString> new_set = get_set_from_val(new_value);
std::set<wxString> old_new_diff_set;
std::set<wxString> new_old_diff_set;
std::set_difference(old_set.begin(), old_set.end(), new_set.begin(), new_set.end(), std::inserter(old_new_diff_set, old_new_diff_set.begin()));
std::set_difference(new_set.begin(), new_set.end(), old_set.begin(), old_set.end(), std::inserter(new_old_diff_set, new_old_diff_set.begin()));
auto add_value = [grid_sizer, border, this](wxString label, const std::set<wxString>& diff_set, bool is_colored = false) {
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(400, 400), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_DEFAULT | wxTE_RICH);
wxGetApp().UpdateDarkUI(text);
text->SetStyle(0, label.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont()));
for (const wxString& str : diff_set) {
int pos = label.First(str);
if (pos == wxNOT_FOUND)
continue;
text->SetStyle(pos, pos + (int)str.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont().Bold()));
}
grid_sizer->Add(text, 1, wxALL | wxEXPAND, border);
};
add_value(old_value, old_new_diff_set);
add_value(new_value, new_old_diff_set, true);
sizer->Add(grid_sizer, 1, wxEXPAND);
auto dlg_btns = new DialogButtons(this, {"OK"});
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(dlg_btns , 0, wxEXPAND);
SetSizer(topSizer);
topSizer->SetSizeHints(this);
wxGetApp().UpdateDlgDarkUI(this);
}
static PresetCollection* get_preset_collection(Preset::Type type, PresetBundle* preset_bundle = nullptr) {
if (!preset_bundle)
preset_bundle = wxGetApp().preset_bundle;
return type == Preset::Type::TYPE_PRINTER ? &preset_bundle->printers :
type == Preset::Type::TYPE_FILAMENT ? &preset_bundle->filaments :
type == Preset::Type::TYPE_SLA_MATERIAL ? &preset_bundle->sla_materials :
type == Preset::Type::TYPE_PRINT ? &preset_bundle->prints :
type == Preset::Type::TYPE_SLA_PRINT ? &preset_bundle->sla_prints :
nullptr;
}
//------------------------------------------
// DiffPresetDialog
//------------------------------------------
static std::string get_selection(PresetComboBox* preset_combo)
{
return into_u8(preset_combo->GetString(preset_combo->GetSelection()));
}
void DiffPresetDialog::create_presets_sizer()
{
m_presets_sizer = new wxBoxSizer(wxVERTICAL);
for (auto new_type : { Preset::TYPE_PRINTER, Preset::TYPE_FILAMENT, Preset::TYPE_SLA_MATERIAL, Preset::TYPE_PRINT, Preset::TYPE_SLA_PRINT })
{
const PresetCollection* collection = get_preset_collection(new_type);
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
PresetComboBox* presets_left;
PresetComboBox* presets_right;
ScalableButton* equal_bmp = new ScalableButton(this, wxID_ANY, "equal");
auto add_preset_combobox = [collection, sizer, new_type, this](PresetComboBox** cb_, PresetBundle* preset_bundle) {
*cb_ = new PresetComboBox(this, new_type, wxSize(em_unit() * 35, -1), preset_bundle);
PresetComboBox* cb = (*cb_);
cb->set_selection_changed_function([this, new_type, preset_bundle, cb](int selection) {
if (m_view_type == Preset::TYPE_INVALID) {
std::string preset_name = Preset::remove_suffix_modified(cb->GetString(selection).ToUTF8().data());
update_compatibility(preset_name, new_type, preset_bundle);
}
update_tree();
});
if (collection->get_selected_idx() != (size_t)-1)
cb->update(collection->get_selected_preset().name);
sizer->Add(cb, 1);
cb->Show(new_type == Preset::TYPE_PRINTER);
};
add_preset_combobox(&presets_left, m_preset_bundle_left.get());
sizer->Add(equal_bmp, 0, wxRIGHT | wxLEFT | wxALIGN_CENTER_VERTICAL, 5);
add_preset_combobox(&presets_right, m_preset_bundle_right.get());
m_presets_sizer->Add(sizer, 1, wxTOP, 5);
equal_bmp->Show(new_type == Preset::TYPE_PRINTER);
m_preset_combos.push_back({ presets_left, equal_bmp, presets_right });
equal_bmp->Bind(wxEVT_BUTTON, [presets_left, presets_right, this](wxEvent&) {
std::string preset_name = get_selection(presets_left);
presets_right->update(preset_name);
if (m_view_type == Preset::TYPE_INVALID)
update_compatibility(Preset::remove_suffix_modified(preset_name), presets_right->get_type(), m_preset_bundle_right.get());
update_tree();
});
}
}
void DiffPresetDialog::create_show_all_presets_chb()
{
m_show_all_presets = new wxCheckBox(this, wxID_ANY, _L("Show all presets (including incompatible)"));
m_show_all_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
bool show_all = m_show_all_presets->GetValue();
for (auto preset_combos : m_preset_combos) {
if (preset_combos.presets_left->get_type() == Preset::TYPE_PRINTER)
continue;
preset_combos.presets_left->show_all(show_all);
preset_combos.presets_right->show_all(show_all);
}
if (m_view_type == Preset::TYPE_INVALID)
update_tree();
});
}
void DiffPresetDialog::create_info_lines()
{
const wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold();
m_top_info_line = new wxStaticText(this, wxID_ANY, _L("Select presets to compare"));
m_top_info_line->SetFont(font);
m_bottom_info_line = new wxStaticText(this, wxID_ANY, "");
m_bottom_info_line->SetFont(font);
}
void DiffPresetDialog::create_tree()
{
m_tree = new DiffViewCtrl(this, wxSize(em_unit() * 65, em_unit() * 40));
m_tree->AppendToggleColumn_(L"\u2714", DiffModel::colToggle, wxLinux ? 9 : 6);
m_tree->AppendBmpTextColumn("", DiffModel::colIconText, 35);
m_tree->AppendBmpTextColumn(_L("Left Preset Value"), DiffModel::colOldValue, 15);
m_tree->AppendBmpTextColumn(_L("Right Preset Value"),DiffModel::colNewValue, 15);
m_tree->Hide();
m_tree->GetColumn(DiffModel::colToggle)->SetHidden(true);
}
std::array<Preset::Type, 3> DiffPresetDialog::types_list() const
{
return PresetBundle::types_list(m_pr_technology);
}
void DiffPresetDialog::create_buttons()
{
m_buttons = new wxBoxSizer(wxHORIZONTAL);
auto show_in_bottom_info = [this](const wxString& ext_line, wxEvent* e = nullptr) {
m_bottom_info_line->SetLabel(ext_line);
m_bottom_info_line->Show(true);
Layout();
if (e) e->Skip();
};
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
// Transfer
auto transfer_btn = dlg_btns->GetOK();
transfer_btn->SetLabel(L("Transfer"));
transfer_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { button_event(Action::Transfer);});
auto enable_transfer = [this](const Preset::Type& type) {
const Preset& main_edited_preset = get_preset_collection(type, wxGetApp().preset_bundle)->get_edited_preset();
if (main_edited_preset.is_dirty)
return main_edited_preset.name == get_right_preset_name(type);
return true;
};
transfer_btn->Bind(wxEVT_UPDATE_UI, [this, enable_transfer, show_in_bottom_info, transfer_btn](wxUpdateUIEvent& evt) {
bool enable = m_tree->has_selection();
if (enable) {
if (m_view_type == Preset::TYPE_INVALID) {
for (const Preset::Type& type : types_list())
if (!enable_transfer(type)) {
enable = false;
break;
}
}
else
enable = enable_transfer(m_view_type);
if (!enable && transfer_btn->IsShown()) {
show_in_bottom_info(_L("You can only transfer to current active profile because it has been modified."));
}
}
evt.Enable(enable);
});
transfer_btn->Bind(wxEVT_ENTER_WINDOW, [show_in_bottom_info](wxMouseEvent& e) {
show_in_bottom_info(_L("Transfer the selected options from left preset to the right.\n"
"Note: New modified presets will be selected in settings tabs after close this dialog."), &e); });
// Cancel
auto cancel_btn = dlg_btns->GetCANCEL();
cancel_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { button_event(Action::Discard);});
for (Button* btn : { transfer_btn, cancel_btn }) {
btn->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { update_bottom_info(); Layout(); e.Skip(); });
}
m_buttons->Add(dlg_btns, 1, wxEXPAND);
m_buttons->Show(false);
}
void DiffPresetDialog::create_edit_sizer()
{
// Add check box for the edit mode
m_use_for_transfer = new wxCheckBox(this, wxID_ANY, _L("Transfer values from left to right"));
m_use_for_transfer->SetToolTip(_L("If enabled, this dialog can be used for transfer selected values from left to right preset."));
m_use_for_transfer->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
bool use = m_use_for_transfer->GetValue();
m_tree->GetColumn(DiffModel::colToggle)->SetHidden(!use);
if (m_tree->IsShown()) {
m_buttons->Show(use);
Fit();
Refresh();
}
else
this->Layout();
});
// Add Buttons
create_buttons();
// Create and fill edit sizer
m_edit_sizer = new wxBoxSizer(wxHORIZONTAL);
m_edit_sizer->Add(m_use_for_transfer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 10);
m_edit_sizer->AddSpacer(em_unit() * 10);
m_edit_sizer->Add(m_buttons, 1, wxLEFT, 5);
m_edit_sizer->Show(false);
}
void DiffPresetDialog::complete_dialog_creation()
{
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
int border = 10;
topSizer->Add(m_top_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2 * border);
topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(m_show_all_presets, 0, wxEXPAND | wxALL, border);
topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border);
topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border);
topSizer->Add(m_edit_sizer, 0, wxEXPAND);
this->SetMinSize(wxSize(80 * em_unit(), 30 * em_unit()));
this->SetSizer(topSizer);
topSizer->SetSizeHints(this);
wxGetApp().UpdateDlgDarkUI(this);
}
DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe)
: DPIDialog(mainframe, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
m_pr_technology(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology())
{
#if defined(__WXMSW__)
// ys_FIXME! temporary workaround for correct font scaling
// Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts,
// From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT
this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
#endif // __WXMSW__
// Init bundles
assert(wxGetApp().preset_bundle);
m_preset_bundle_left = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
m_preset_bundle_right = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
// Create UI items
SetBackgroundColour(*wxWHITE);
create_info_lines();
create_presets_sizer();
create_show_all_presets_chb();
create_tree();
create_edit_sizer();
complete_dialog_creation();
}
void DiffPresetDialog::update_controls_visibility(Preset::Type type /* = Preset::TYPE_INVALID*/)
{
for (auto preset_combos : m_preset_combos) {
Preset::Type cb_type = preset_combos.presets_left->get_type();
bool show = type != Preset::TYPE_INVALID ? type == cb_type :
cb_type == Preset::TYPE_PRINTER ? true :
m_pr_technology == ptFFF ? cb_type == Preset::TYPE_PRINT || cb_type == Preset::TYPE_FILAMENT :
cb_type == Preset::TYPE_SLA_PRINT || cb_type == Preset::TYPE_SLA_MATERIAL;
preset_combos.presets_left->Show(show);
preset_combos.equal_bmp->Show(show);
preset_combos.presets_right->Show(show);
if (show) {
preset_combos.presets_left->update_from_bundle();
preset_combos.presets_right->update_from_bundle();
}
}
m_show_all_presets->Show(type != Preset::TYPE_PRINTER);
}
void DiffPresetDialog::update_bundles_from_app()
{
*m_preset_bundle_left = *wxGetApp().preset_bundle;
*m_preset_bundle_right = *wxGetApp().preset_bundle;
m_pr_technology = m_preset_bundle_left.get()->printers.get_edited_preset().printer_technology();
}
void DiffPresetDialog::show(Preset::Type type /* = Preset::TYPE_INVALID*/)
{
this->SetTitle(_L("Compare presets"));
m_view_type = type;
update_bundles_from_app();
update_controls_visibility(type);
if (type == Preset::TYPE_INVALID)
Fit();
update_tree();
wxGetApp().UpdateDlgDarkUI(this);
// if this dialog is shown it have to be Hide and show again to be placed on the very Top of windows
if (IsShown())
Hide();
Show();
}
void DiffPresetDialog::update_presets(Preset::Type type)
{
update_bundles_from_app();
update_controls_visibility(type);
if (type == Preset::TYPE_INVALID)
for (auto preset_combos : m_preset_combos) {
if (preset_combos.presets_left->get_type() == Preset::TYPE_PRINTER) {
preset_combos.presets_left->update_from_bundle ();
preset_combos.presets_right->update_from_bundle();
break;
}
}
else
for (auto preset_combos : m_preset_combos) {
if (preset_combos.presets_left->get_type() == type) {
preset_combos.presets_left->update();
preset_combos.presets_right->update();
break;
}
}
update_tree();
}
void DiffPresetDialog::update_bottom_info(wxString bottom_info)
{
const bool show_bottom_info = !m_tree->IsShown();
if (show_bottom_info)
m_bottom_info_line->SetLabel(bottom_info);
m_bottom_info_line->Show(show_bottom_info);
}
void DiffPresetDialog::update_tree()
{
Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher();
searcher.sort_options_by_key();
m_tree->Clear();
wxString bottom_info = "";
bool show_tree = false;
for (auto preset_combos : m_preset_combos)
{
if (!preset_combos.presets_left->IsShown())
continue;
Preset::Type type = preset_combos.presets_left->get_type();
const PresetCollection* presets = get_preset_collection(type);
const Preset* left_preset = presets->find_preset(get_selection(preset_combos.presets_left));
const Preset* right_preset = presets->find_preset(get_selection(preset_combos.presets_right));
if (!left_preset || !right_preset) {
bottom_info = "One of the presets does not exist";
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question"));
preset_combos.equal_bmp->SetToolTip(bottom_info);
continue;
}
const DynamicPrintConfig& left_config = left_preset->config;
const PrinterTechnology& left_pt = left_preset->printer_technology();
const DynamicPrintConfig& right_congig = right_preset->config;
if (left_pt != right_preset->printer_technology()) {
bottom_info = "Compared presets has different printer technology";
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question"));
preset_combos.equal_bmp->SetToolTip(bottom_info);
continue;
}
// Collect dirty options.
const bool deep_compare = (type == Preset::TYPE_PRINTER ||
type == Preset::TYPE_FILAMENT || type == Preset::TYPE_SLA_MATERIAL);
auto dirty_options = type == Preset::TYPE_PRINTER && left_pt == ptFFF &&
left_config.opt<ConfigOptionStrings>("extruder_colour")->values.size() < right_congig.opt<ConfigOptionStrings>("extruder_colour")->values.size() ?
presets->dirty_options(right_preset, left_preset, deep_compare) :
presets->dirty_options(left_preset, right_preset, deep_compare);
if (dirty_options.empty()) {
//bottom_info = _L("Presets are the same");
bottom_info = wxEmptyString;
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "equal"));
preset_combos.equal_bmp->SetToolTip(bottom_info);
continue;
}
show_tree = true;
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "not_equal"));
/*preset_combos.equal_bmp->SetToolTip(_L("Presets are different.\n"
"Click this button to select the same preset for the right and left preset."));*/
preset_combos.equal_bmp->SetToolTip(wxEmptyString);
m_tree->model->AddPreset(type, "\"" + from_u8(left_preset->name) + "\" vs \"" + from_u8(right_preset->name) + "\"", left_pt);
const std::map<wxString, std::string>& category_icon_map = wxGetApp().get_tab(type)->get_category_icon_map();
auto get_category_icon = [&category_icon_map](const wxString& key) {
auto it = category_icon_map.find(key);
return it != category_icon_map.end() ? it->second : std::string();
};
// process changes of extruders count
if (type == Preset::TYPE_PRINTER && left_pt == ptFFF &&
left_config.opt<ConfigOptionStrings>("extruder_colour")->values.size() != right_congig.opt<ConfigOptionStrings>("extruder_colour")->values.size()) {
wxString local_label = _L("Extruder count");
wxString left_val = from_u8((boost::format("%1%") % left_config.opt<ConfigOptionStrings>("extruder_colour")->values.size()).str());
wxString right_val = from_u8((boost::format("%1%") % right_congig.opt<ConfigOptionStrings>("extruder_colour")->values.size()).str());
m_tree->Append("extruders_count", type, "General", "Capabilities", local_label, left_val, right_val,
get_category_icon("Basic information"));
}
for (const std::string& opt_key : dirty_options) {
wxString left_val = get_string_value(opt_key, left_config);
wxString right_val = get_string_value(opt_key, right_congig);
const std::string lookup_key = get_pure_opt_key(opt_key);
Search::Option option = searcher.get_option(lookup_key, type);
if (get_pure_opt_key(option.opt_key()) != lookup_key)
option = searcher.get_option(opt_key, get_full_label(opt_key, left_config), type);
if (get_pure_opt_key(option.opt_key()) != lookup_key) {
// When the found option is not the requested one.
// This can happen for dirty_options such as:
// "default_print_profile", "printer_model", "printer_settings_id",
// because they do not exist in the searcher.
continue;
}
m_tree->Append(opt_key, type, option.category_local, option.group_local, option.label_local,
left_val, right_val, get_category_icon(option.category));
}
}
bool tree_was_shown = m_tree->IsShown();
m_tree->Show(show_tree);
bool can_transfer_options = m_view_type == Preset::TYPE_INVALID || get_left_preset_name(m_view_type) != get_right_preset_name(m_view_type);
m_edit_sizer->Show(show_tree && can_transfer_options);
m_buttons->Show(m_edit_sizer->IsShown(size_t(0)) && m_use_for_transfer->GetValue());
update_bottom_info(bottom_info);
if (tree_was_shown == m_tree->IsShown())
Layout();
else {
Fit();
Refresh();
}
// Revert sort of searcher back
searcher.sort_options_by_label();
}
void DiffPresetDialog::on_dpi_changed(const wxRect&)
{
int em = em_unit();
const wxSize& size = wxSize(80 * em, 30 * em);
SetMinSize(size);
for (auto preset_combos : m_preset_combos) {
preset_combos.presets_left->msw_rescale();
preset_combos.equal_bmp->msw_rescale();
preset_combos.presets_right->msw_rescale();
}
m_tree->Rescale(em);
Fit();
Refresh();
}
void DiffPresetDialog::on_sys_color_changed()
{
#ifdef _WIN32
wxGetApp().UpdateAllStaticTextDarkUI(this);
wxGetApp().UpdateDarkUI(m_show_all_presets);
wxGetApp().UpdateDVCDarkUI(m_tree);
#endif
for (auto preset_combos : m_preset_combos) {
preset_combos.presets_left->msw_rescale();
preset_combos.equal_bmp->msw_rescale();
preset_combos.presets_right->msw_rescale();
}
// msw_rescale updates just icons, so use it
m_tree->Rescale();
Refresh();
}
void DiffPresetDialog::update_compatibility(const std::string& preset_name, Preset::Type type, PresetBundle* preset_bundle)
{
PresetCollection* presets = get_preset_collection(type, preset_bundle);
bool print_tab = type == Preset::TYPE_PRINT || type == Preset::TYPE_SLA_PRINT;
bool printer_tab = type == Preset::TYPE_PRINTER;
bool technology_changed = false;
if (printer_tab) {
const Preset& new_printer_preset = *presets->find_preset(preset_name, true);
PrinterTechnology old_printer_technology = presets->get_selected_preset().printer_technology();
PrinterTechnology new_printer_technology = new_printer_preset.printer_technology();
technology_changed = old_printer_technology != new_printer_technology;
}
// select preset
presets->select_preset_by_name(preset_name, false);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
// The following method should not discard changes of current print or filament presets on change of a printer profile,
// if they are compatible with the current printer.
auto update_compatible_type = [](bool technology_changed, bool on_page, bool show_incompatible_presets) {
return technology_changed ? PresetSelectCompatibleType::Always :
on_page ? PresetSelectCompatibleType::Never :
show_incompatible_presets ? PresetSelectCompatibleType::OnlyIfWasCompatible : PresetSelectCompatibleType::Always;
};
if (print_tab || printer_tab)
preset_bundle->update_compatible(
update_compatible_type(technology_changed, print_tab, true),
update_compatible_type(technology_changed, false, true));
bool is_left_presets = preset_bundle == m_preset_bundle_left.get();
PrinterTechnology pr_tech = preset_bundle->printers.get_selected_preset().printer_technology();
// update preset comboboxes
for (auto preset_combos : m_preset_combos)
{
PresetComboBox* cb = is_left_presets ? preset_combos.presets_left : preset_combos.presets_right;
Preset::Type presets_type = cb->get_type();
if ((print_tab && (
(pr_tech == ptFFF && presets_type == Preset::TYPE_FILAMENT) ||
(pr_tech == ptSLA && presets_type == Preset::TYPE_SLA_MATERIAL) )) ||
(printer_tab && (
(pr_tech == ptFFF && (presets_type == Preset::TYPE_PRINT || presets_type == Preset::TYPE_FILAMENT) ) ||
(pr_tech == ptSLA && (presets_type == Preset::TYPE_SLA_PRINT || presets_type == Preset::TYPE_SLA_MATERIAL) )) ))
cb->update();
}
if (technology_changed &&
m_preset_bundle_left.get()->printers.get_selected_preset().printer_technology() ==
m_preset_bundle_right.get()->printers.get_selected_preset().printer_technology())
{
m_pr_technology = m_preset_bundle_left.get()->printers.get_edited_preset().printer_technology();
update_controls_visibility();
}
}
void DiffPresetDialog::button_event(Action act)
{
Hide();
if (act == Action::Transfer)
wxPostEvent(this, SimpleEvent(EVT_DIFF_DIALOG_TRANSFER));
}
std::string DiffPresetDialog::get_left_preset_name(Preset::Type type)
{
PresetComboBox* cb = std::find_if(m_preset_combos.begin(), m_preset_combos.end(), [type](const DiffPresets& p) {
return p.presets_left->get_type() == type;
})->presets_left;
return Preset::remove_suffix_modified(get_selection(cb));
}
std::string DiffPresetDialog::get_right_preset_name(Preset::Type type)
{
PresetComboBox* cb = std::find_if(m_preset_combos.begin(), m_preset_combos.end(), [type](const DiffPresets& p) {
return p.presets_right->get_type() == type;
})->presets_right;
return Preset::remove_suffix_modified(get_selection(cb));
}
}
} // namespace Slic3r::GUI