Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/slic3r/GUI/PresetComboBoxes.cpp')
-rw-r--r--src/slic3r/GUI/PresetComboBoxes.cpp1099
1 files changed, 1099 insertions, 0 deletions
diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp
new file mode 100644
index 000000000..8dd35a591
--- /dev/null
+++ b/src/slic3r/GUI/PresetComboBoxes.cpp
@@ -0,0 +1,1099 @@
+#include "PresetComboBoxes.hpp"
+
+#include <cstddef>
+#include <vector>
+#include <string>
+#include <boost/algorithm/string.hpp>
+
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/button.h>
+#include <wx/statbox.h>
+#include <wx/colordlg.h>
+#include <wx/wupdlock.h>
+#include <wx/menu.h>
+
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/PresetBundle.hpp"
+
+#include "GUI.hpp"
+#include "GUI_App.hpp"
+#include "Plater.hpp"
+#include "MainFrame.hpp"
+#include "format.hpp"
+#include "Tab.hpp"
+#include "ConfigWizard.hpp"
+#include "../Utils/ASCIIFolding.hpp"
+#include "../Utils/FixModelByWin10.hpp"
+#include "../Utils/UndoRedo.hpp"
+#include "BitmapCache.hpp"
+#include "PhysicalPrinterDialog.hpp"
+#include "SavePresetDialog.hpp"
+
+// A workaround for a set of issues related to text fitting into gtk widgets:
+// See e.g.: https://github.com/prusa3d/PrusaSlicer/issues/4584
+#if defined(__WXGTK20__) || defined(__WXGTK3__)
+ #include <glib-2.0/glib-object.h>
+ #include <pango-1.0/pango/pango-layout.h>
+ #include <gtk/gtk.h>
+#endif
+
+using Slic3r::GUI::format_wxstr;
+
+namespace Slic3r {
+namespace GUI {
+
+#define BORDER_W 10
+
+// ---------------------------------
+// *** PresetComboBox ***
+// ---------------------------------
+
+/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina
+ * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean
+ * "please scale this to such and such" but rather
+ * "the wxImage is already sized for backing scale such and such". )
+ * Unfortunately, the constructor changes the size of wxBitmap too.
+ * Thus We need to use unscaled size value for bitmaps that we use
+ * to avoid scaled size of control items.
+ * For this purpose control drawing methods and
+ * control size calculation methods (virtual) are overridden.
+ **/
+
+PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size) :
+ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY),
+ m_type(preset_type),
+ m_last_selected(wxNOT_FOUND),
+ m_em_unit(em_unit(this)),
+ m_preset_bundle(wxGetApp().preset_bundle)
+{
+ SetFont(wxGetApp().normal_font());
+#ifdef _WIN32
+ // Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that
+ // the index of the item inside CBN_EDITCHANGE may no more be valid.
+ EnableTextChangedEvents(false);
+#endif /* _WIN32 */
+
+ switch (m_type)
+ {
+ case Preset::TYPE_PRINT: {
+ m_collection = &m_preset_bundle->prints;
+ m_main_bitmap_name = "cog";
+ break;
+ }
+ case Preset::TYPE_FILAMENT: {
+ m_collection = &m_preset_bundle->filaments;
+ m_main_bitmap_name = "spool";
+ break;
+ }
+ case Preset::TYPE_SLA_PRINT: {
+ m_collection = &m_preset_bundle->sla_prints;
+ m_main_bitmap_name = "cog";
+ break;
+ }
+ case Preset::TYPE_SLA_MATERIAL: {
+ m_collection = &m_preset_bundle->sla_materials;
+ m_main_bitmap_name = "resin";
+ break;
+ }
+ case Preset::TYPE_PRINTER: {
+ m_collection = &m_preset_bundle->printers;
+ m_main_bitmap_name = "printer";
+ break;
+ }
+ default: break;
+ }
+
+ m_bitmapCompatible = ScalableBitmap(this, "flag_green");
+ m_bitmapIncompatible = ScalableBitmap(this, "flag_red");
+
+ // parameters for an icon's drawing
+ fill_width_height();
+
+ Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent& e) {
+ if (m_suppress_change)
+ e.StopPropagation();
+ else
+ e.Skip();
+ });
+ Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent&) { m_suppress_change = false; });
+ Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent&) { m_suppress_change = true; });
+
+ Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
+ // see https://github.com/prusa3d/PrusaSlicer/issues/3889
+ // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender")
+ // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive.
+ // So, use GetSelection() from event parameter
+ auto selected_item = evt.GetSelection();
+
+ auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
+ if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX)
+ this->SetSelection(this->m_last_selected);
+ else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty())) {
+ m_last_selected = selected_item;
+ on_selection_changed(selected_item);
+ evt.StopPropagation();
+ }
+ evt.Skip();
+ });
+}
+
+PresetComboBox::~PresetComboBox()
+{
+}
+
+BitmapCache& PresetComboBox::bitmap_cache()
+{
+ static BitmapCache bmps;
+ return bmps;
+}
+
+void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type)
+{
+ this->SetClientData(item, (void*)label_item_type);
+}
+
+bool PresetComboBox::set_printer_technology(PrinterTechnology pt)
+{
+ if (printer_technology != pt) {
+ printer_technology = pt;
+ return true;
+ }
+ return false;
+}
+
+void PresetComboBox::invalidate_selection()
+{
+ m_last_selected = INT_MAX; // this value means that no one item is selected
+}
+
+void PresetComboBox::validate_selection(bool predicate/*=false*/)
+{
+ if (predicate ||
+ // just in case: mark m_last_selected as a first added element
+ m_last_selected == INT_MAX)
+ m_last_selected = GetCount() - 1;
+}
+
+void PresetComboBox::update_selection()
+{
+ /* If selected_preset_item is still equal to INT_MAX, it means that
+ * there is no presets added to the list.
+ * So, select last combobox item ("Add/Remove preset")
+ */
+ validate_selection();
+
+ SetSelection(m_last_selected);
+ SetToolTip(GetString(m_last_selected));
+
+// A workaround for a set of issues related to text fitting into gtk widgets:
+// See e.g.: https://github.com/prusa3d/PrusaSlicer/issues/4584
+#if defined(__WXGTK20__) || defined(__WXGTK3__)
+ GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_widget));
+
+ // 'cells' contains the GtkCellRendererPixBuf for the icon,
+ // 'cells->next' contains GtkCellRendererText for the text we need to ellipsize
+ if (!cells || !cells->next) return;
+
+ auto cell = static_cast<GtkCellRendererText *>(cells->next->data);
+
+ if (!cell) return;
+
+ g_object_set(G_OBJECT(cell), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+
+ // Only the list of cells must be freed, the renderer isn't ours to free
+ g_list_free(cells);
+#endif
+}
+
+void PresetComboBox::update(std::string select_preset_name)
+{
+ Freeze();
+ Clear();
+ invalidate_selection();
+
+ const std::deque<Preset>& presets = m_collection->get_presets();
+
+ std::map<wxString, std::pair<wxBitmap*, bool>> nonsys_presets;
+ std::map<wxString, wxBitmap*> incomp_presets;
+
+ wxString selected = "";
+ if (!presets.front().is_visible)
+ set_label_marker(Append(separator(L("System presets")), wxNullBitmap));
+
+ for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
+ {
+ const Preset& preset = presets[i];
+ if (!preset.is_visible || !preset.is_compatible)
+ continue;
+
+ // marker used for disable incompatible printer models for the selected physical printer
+ bool is_enabled = m_type == Preset::TYPE_PRINTER && printer_technology != ptAny ? preset.printer_technology() == printer_technology : true;
+ if (select_preset_name.empty() && is_enabled)
+ select_preset_name = preset.name;
+
+ std::string bitmap_key = "cb";
+ if (m_type == Preset::TYPE_PRINTER) {
+ bitmap_key += "_printer";
+ if (preset.printer_technology() == ptSLA)
+ bitmap_key += "_sla";
+ }
+ std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
+
+ wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default);
+ assert(bmp);
+
+ if (!is_enabled)
+ incomp_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp);
+ else if (preset.is_default || preset.is_system)
+ {
+ Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp);
+ validate_selection(preset.name == select_preset_name);
+ }
+ else
+ {
+ nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair<wxBitmap*, bool>(bmp, is_enabled));
+ if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled))
+ selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
+ }
+ if (i + 1 == m_collection->num_default_presets())
+ set_label_marker(Append(separator(L("System presets")), wxNullBitmap));
+ }
+ if (!nonsys_presets.empty())
+ {
+ set_label_marker(Append(separator(L("User presets")), wxNullBitmap));
+ for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
+ int item_id = Append(it->first, *it->second.first);
+ bool is_enabled = it->second.second;
+ if (!is_enabled)
+ set_label_marker(item_id, LABEL_ITEM_DISABLED);
+ validate_selection(it->first == selected);
+ }
+ }
+ if (!incomp_presets.empty())
+ {
+ set_label_marker(Append(separator(L("Incompatible presets")), wxNullBitmap));
+ for (std::map<wxString, wxBitmap*>::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) {
+ set_label_marker(Append(it->first, *it->second), LABEL_ITEM_DISABLED);
+ }
+ }
+
+ update_selection();
+ Thaw();
+}
+
+void PresetComboBox::edit_physical_printer()
+{
+ if (!m_preset_bundle->physical_printers.has_selection())
+ return;
+
+ PhysicalPrinterDialog dlg(this->GetParent(),this->GetString(this->GetSelection()));
+ if (dlg.ShowModal() == wxID_OK)
+ update();
+}
+
+void PresetComboBox::add_physical_printer()
+{
+ if (PhysicalPrinterDialog(this->GetParent(), wxEmptyString).ShowModal() == wxID_OK)
+ update();
+}
+
+bool PresetComboBox::del_physical_printer(const wxString& note_string/* = wxEmptyString*/)
+{
+ const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name();
+ if (printer_name.empty())
+ return false;
+
+ wxString msg;
+ if (!note_string.IsEmpty())
+ msg += note_string + "\n";
+ msg += format_wxstr(_L("Are you sure you want to delete \"%1%\" printer?"), printer_name);
+
+ if (wxMessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES)
+ return false;
+
+ m_preset_bundle->physical_printers.delete_selected_printer();
+
+ this->update();
+
+ if (dynamic_cast<PlaterPresetComboBox*>(this) != nullptr)
+ wxGetApp().get_tab(m_type)->update_preset_choice();
+ else if (dynamic_cast<TabPresetComboBox*>(this) != nullptr)
+ {
+ wxGetApp().get_tab(m_type)->update_btns_enabling();
+ wxGetApp().plater()->sidebar().update_presets(m_type);
+ }
+
+ return true;
+}
+
+void PresetComboBox::update()
+{
+ this->update(into_u8(this->GetString(this->GetSelection())));
+}
+
+void PresetComboBox::msw_rescale()
+{
+ m_em_unit = em_unit(this);
+
+ m_bitmapIncompatible.msw_rescale();
+ m_bitmapCompatible.msw_rescale();
+
+ // parameters for an icon's drawing
+ fill_width_height();
+
+ // update the control to redraw the icons
+ update();
+}
+
+void PresetComboBox::fill_width_height()
+{
+ // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
+ // set a bitmap's height to m_bitmapCompatible->GetHeight() and norm_icon_width to m_bitmapCompatible->GetWidth()
+ icon_height = m_bitmapCompatible.GetBmpHeight();
+ norm_icon_width = m_bitmapCompatible.GetBmpWidth();
+
+ /* It's supposed that standard size of an icon is 16px*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 float scale_f = (float)m_em_unit * 0.1f;
+
+ thin_icon_width = lroundf(8 * scale_f); // analogue to 8px;
+ wide_icon_width = norm_icon_width + thin_icon_width;
+
+ space_icon_width = lroundf(2 * scale_f);
+ thin_space_icon_width = 2 * space_icon_width;
+ wide_space_icon_width = 3 * space_icon_width;
+}
+
+wxString PresetComboBox::separator(const std::string& label)
+{
+ return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail());
+}
+
+wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name,
+ bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/,
+ std::string filament_rgb/* = ""*/, std::string extruder_rgb/* = ""*/)
+{
+ // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
+ // to the filament color image.
+ if (wide_icons)
+ bitmap_key += is_compatible ? ",cmpt" : ",ncmpt";
+
+ bitmap_key += is_system ? ",syst" : ",nsyst";
+ bitmap_key += ",h" + std::to_string(icon_height);
+ if (wxGetApp().dark_mode())
+ bitmap_key += ",dark";
+
+ wxBitmap* bmp = bitmap_cache().find(bitmap_key);
+ if (bmp == nullptr) {
+ // Create the bitmap with color bars.
+ std::vector<wxBitmap> bmps;
+ if (wide_icons)
+ // Paint a red flag for incompatible presets.
+ bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp());
+
+ if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty())
+ {
+ unsigned char rgb[3];
+ // Paint the color bars.
+ bitmap_cache().parse_color(filament_rgb, rgb);
+ bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb));
+ if (!is_single_bar) {
+ bitmap_cache().parse_color(extruder_rgb, rgb);
+ bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, rgb));
+ }
+ // Paint a lock at the system presets.
+ bmps.emplace_back(bitmap_cache().mkclear(space_icon_width, icon_height));
+ }
+ else
+ {
+ // Paint the color bars.
+ bmps.emplace_back(bitmap_cache().mkclear(thin_space_icon_width, icon_height));
+ bmps.emplace_back(create_scaled_bitmap(main_icon_name));
+ // Paint a lock at the system presets.
+ bmps.emplace_back(bitmap_cache().mkclear(wide_space_icon_width, icon_height));
+ }
+ bmps.emplace_back(is_system ? create_scaled_bitmap("lock_closed") : bitmap_cache().mkclear(norm_icon_width, icon_height));
+ bmp = bitmap_cache().insert(bitmap_key, bmps);
+ }
+
+ return bmp;
+}
+
+wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name,
+ bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/)
+{
+ bitmap_key += !is_enabled ? "_disabled" : "";
+ bitmap_key += is_compatible ? ",cmpt" : ",ncmpt";
+ bitmap_key += is_system ? ",syst" : ",nsyst";
+ bitmap_key += ",h" + std::to_string(icon_height);
+ if (wxGetApp().dark_mode())
+ bitmap_key += ",dark";
+
+ wxBitmap* bmp = bitmap_cache().find(bitmap_key);
+ if (bmp == nullptr) {
+ // Create the bitmap with color bars.
+ std::vector<wxBitmap> bmps;
+ bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? create_scaled_bitmap(main_icon_name, this, 16, !is_enabled) :
+ is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp());
+ // Paint a lock at the system presets.
+ bmps.emplace_back(is_system ? create_scaled_bitmap(next_icon_name, this, 16, !is_enabled) : bitmap_cache().mkclear(norm_icon_width, icon_height));
+ bmp = bitmap_cache().insert(bitmap_key, bmps);
+ }
+
+ return bmp;
+}
+
+bool PresetComboBox::is_selected_physical_printer()
+{
+ auto selected_item = this->GetSelection();
+ auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
+ return marker == LABEL_ITEM_PHYSICAL_PRINTER;
+}
+
+bool PresetComboBox::selection_is_changed_according_to_physical_printers()
+{
+ if (m_type != Preset::TYPE_PRINTER || !is_selected_physical_printer())
+ return false;
+
+ PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers;
+
+ std::string selected_string = this->GetString(this->GetSelection()).ToUTF8().data();
+
+ std::string old_printer_full_name, old_printer_preset;
+ if (physical_printers.has_selection()) {
+ old_printer_full_name = physical_printers.get_selected_full_printer_name();
+ old_printer_preset = physical_printers.get_selected_printer_preset_name();
+ }
+ else
+ old_printer_preset = m_collection->get_edited_preset().name;
+ // Select related printer preset on the Printer Settings Tab
+ physical_printers.select_printer(selected_string);
+ std::string preset_name = physical_printers.get_selected_printer_preset_name();
+
+ // if new preset wasn't selected, there is no need to call update preset selection
+ if (old_printer_preset == preset_name) {
+ // we need just to update according Plater<->Tab PresetComboBox
+ if (dynamic_cast<PlaterPresetComboBox*>(this)!=nullptr) {
+ wxGetApp().get_tab(m_type)->update_preset_choice();
+ // Synchronize config.ini with the current selections.
+ m_preset_bundle->export_selections(*wxGetApp().app_config);
+ }
+ else if (dynamic_cast<TabPresetComboBox*>(this)!=nullptr)
+ wxGetApp().sidebar().update_presets(m_type);
+
+ this->update();
+ return true;
+ }
+
+ Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER);
+ if (tab)
+ tab->select_preset(preset_name, false, old_printer_full_name);
+ return true;
+}
+
+#ifdef __APPLE__
+bool PresetComboBox::OnAddBitmap(const wxBitmap& bitmap)
+{
+ if (bitmap.IsOk())
+ {
+ // we should use scaled! size values of bitmap
+ int width = (int)bitmap.GetScaledWidth();
+ int height = (int)bitmap.GetScaledHeight();
+
+ if (m_usedImgSize.x < 0)
+ {
+ // If size not yet determined, get it from this image.
+ m_usedImgSize.x = width;
+ m_usedImgSize.y = height;
+
+ // Adjust control size to vertically fit the bitmap
+ wxWindow* ctrl = GetControl();
+ ctrl->InvalidateBestSize();
+ wxSize newSz = ctrl->GetBestSize();
+ wxSize sz = ctrl->GetSize();
+ if (newSz.y > sz.y)
+ ctrl->SetSize(sz.x, newSz.y);
+ else
+ DetermineIndent();
+ }
+
+ wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y,
+ false,
+ "you can only add images of same size");
+
+ return true;
+ }
+
+ return false;
+}
+
+void PresetComboBox::OnDrawItem(wxDC& dc,
+ const wxRect& rect,
+ int item,
+ int flags) const
+{
+ const wxBitmap& bmp = *(static_cast<wxBitmap*>(m_bitmaps[item]));
+ if (bmp.IsOk())
+ {
+ // we should use scaled! size values of bitmap
+ wxCoord w = bmp.GetScaledWidth();
+ wxCoord h = bmp.GetScaledHeight();
+
+ const int imgSpacingLeft = 4;
+
+ // Draw the image centered
+ dc.DrawBitmap(bmp,
+ rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft,
+ rect.y + (rect.height - h) / 2,
+ true);
+ }
+
+ wxString text = GetString(item);
+ if (!text.empty())
+ dc.DrawText(text,
+ rect.x + m_imgAreaWidth + 1,
+ rect.y + (rect.height - dc.GetCharHeight()) / 2);
+}
+#endif
+
+
+// ---------------------------------
+// *** PlaterPresetComboBox ***
+// ---------------------------------
+
+PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type) :
+ PresetComboBox(parent, preset_type, wxSize(15 * wxGetApp().em_unit(), -1))
+{
+ Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) {
+ auto selected_item = evt.GetSelection();
+
+ auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
+ if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) {
+ this->SetSelection(this->m_last_selected);
+ evt.StopPropagation();
+ if (marker == LABEL_ITEM_WIZARD_PRINTERS)
+ show_add_menu();
+ else
+ {
+ ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME;
+ switch (marker) {
+ case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break;
+ case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
+ default: break;
+ }
+ wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); });
+ }
+ } else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty() ) {
+ this->m_last_selected = selected_item;
+ evt.SetInt(this->m_type);
+ evt.Skip();
+ } else {
+ evt.StopPropagation();
+ }
+ });
+
+ if (m_type == Preset::TYPE_FILAMENT)
+ {
+ Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
+ const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
+ // Wide icons are shown if the currently selected preset is not compatible with the current printer,
+ // and red flag is drown in front of the selected preset.
+ bool wide_icons = selected_preset && !selected_preset->is_compatible;
+ float scale = m_em_unit*0.1f;
+
+ int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0;
+#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED)
+ shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image
+#endif
+ int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5);
+ int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x;
+ if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) {
+ // Let the combo box process the mouse click.
+ event.Skip();
+ return;
+ }
+
+ // Swallow the mouse click and open the color picker.
+
+ // get current color
+ DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
+ auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
+ wxColour clr(colors->values[m_extruder_idx]);
+ if (!clr.IsOk())
+ clr = wxColour(0,0,0); // Don't set alfa to transparence
+
+ auto data = new wxColourData();
+ data->SetChooseFull(1);
+ data->SetColour(clr);
+
+ wxColourDialog dialog(this, data);
+ dialog.CenterOnParent();
+ if (dialog.ShowModal() == wxID_OK)
+ {
+ colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
+
+ DynamicPrintConfig cfg_new = *cfg;
+ cfg_new.set_key_value("extruder_colour", colors);
+
+ wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
+ this->update();
+ wxGetApp().plater()->on_config_change(cfg_new);
+ }
+ });
+ }
+
+ edit_btn = new ScalableButton(parent, wxID_ANY, "cog");
+ edit_btn->SetToolTip(_L("Click to edit preset"));
+
+ edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent)
+ {
+ // In a case of a physical printer, for its editing open PhysicalPrinterDialog
+ if (m_type == Preset::TYPE_PRINTER/* && this->is_selected_physical_printer()*/) {
+ this->show_edit_menu();
+ return;
+ }
+
+ if (!switch_to_tab())
+ return;
+
+ /* In a case of a multi-material printing, for editing another Filament Preset
+ * it's needed to select this preset for the "Filament settings" Tab
+ */
+ if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
+ {
+ const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
+
+ // Call select_preset() only if there is new preset and not just modified
+ if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
+ {
+ const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
+ wxGetApp().get_tab(m_type)->select_preset(preset_name);
+ }
+ }
+ });
+}
+
+PlaterPresetComboBox::~PlaterPresetComboBox()
+{
+ if (edit_btn)
+ edit_btn->Destroy();
+}
+
+bool PlaterPresetComboBox::switch_to_tab()
+{
+ Tab* tab = wxGetApp().get_tab(m_type);
+ if (!tab)
+ return false;
+
+ int page_id = wxGetApp().tab_panel()->FindPage(tab);
+ if (page_id == wxNOT_FOUND)
+ return false;
+
+ wxGetApp().tab_panel()->SetSelection(page_id);
+ // Switch to Settings NotePad
+ wxGetApp().mainframe->select_tab();
+ return true;
+}
+
+void PlaterPresetComboBox::show_add_menu()
+{
+ wxMenu* menu = new wxMenu();
+
+ append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "",
+ [this](wxCommandEvent&) {
+ wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); });
+ }, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
+
+ append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "",
+ [this](wxCommandEvent&) {
+ PhysicalPrinterDialog dlg(this->GetParent(), wxEmptyString);
+ if (dlg.ShowModal() == wxID_OK)
+ update();
+ }, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
+
+ wxGetApp().plater()->PopupMenu(menu);
+}
+
+void PlaterPresetComboBox::show_edit_menu()
+{
+ wxMenu* menu = new wxMenu();
+
+ append_menu_item(menu, wxID_ANY, _L("Edit preset"), "",
+ [this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater());
+
+ if (this->is_selected_physical_printer()) {
+ append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "",
+ [this](wxCommandEvent&) { this->edit_physical_printer(); }, "cog", menu, []() { return true; }, wxGetApp().plater());
+
+ append_menu_item(menu, wxID_ANY, _L("Delete physical printer"), "",
+ [this](wxCommandEvent&) { this->del_physical_printer(); }, "cross", menu, []() { return true; }, wxGetApp().plater());
+ }
+ else
+ append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "",
+ [this](wxCommandEvent&) {
+ wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); });
+ }, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
+
+ append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "",
+ [this](wxCommandEvent&) { this->add_physical_printer(); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
+
+ wxGetApp().plater()->PopupMenu(menu);
+}
+
+// Only the compatible presets are shown.
+// If an incompatible preset is selected, it is shown as well.
+void PlaterPresetComboBox::update()
+{
+ if (m_type == Preset::TYPE_FILAMENT &&
+ (m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA ||
+ m_preset_bundle->filament_presets.size() <= (size_t)m_extruder_idx) )
+ return;
+
+ // Otherwise fill in the list from scratch.
+ this->Freeze();
+ this->Clear();
+ invalidate_selection();
+
+ const Preset* selected_filament_preset;
+ std::string extruder_color;
+ if (m_type == Preset::TYPE_FILAMENT)
+ {
+ unsigned char rgb[3];
+ extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx);
+ if (!bitmap_cache().parse_color(extruder_color, rgb))
+ // Extruder color is not defined.
+ extruder_color.clear();
+ selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
+ assert(selected_filament_preset);
+ }
+
+ bool has_selection = m_collection->get_selected_idx() != size_t(-1);
+ const Preset* selected_preset = m_type == Preset::TYPE_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr;
+ // Show wide icons if the currently selected preset is not compatible with the current printer,
+ // and draw a red flag in front of the selected preset.
+ bool wide_icons = selected_preset && !selected_preset->is_compatible;
+
+ std::map<wxString, wxBitmap*> nonsys_presets;
+
+ wxString selected_user_preset = "";
+ wxString tooltip = "";
+ const std::deque<Preset>& presets = m_collection->get_presets();
+
+ if (!presets.front().is_visible)
+ this->set_label_marker(this->Append(separator(L("System presets")), wxNullBitmap));
+
+ for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
+ {
+ const Preset& preset = presets[i];
+ bool is_selected = m_type == Preset::TYPE_FILAMENT ?
+ m_preset_bundle->filament_presets[m_extruder_idx] == preset.name :
+ // The case, when some physical printer is selected
+ m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false :
+ i == m_collection->get_selected_idx();
+
+ if (!preset.is_visible || (!preset.is_compatible && !is_selected))
+ continue;
+
+ std::string bitmap_key, filament_rgb, extruder_rgb;
+ std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
+
+ bool single_bar = false;
+ if (m_type == Preset::TYPE_FILAMENT)
+ {
+ // Assign an extruder color to the selected item if the extruder color is defined.
+ filament_rgb = is_selected ? selected_filament_preset->config.opt_string("filament_colour", 0) :
+ preset.config.opt_string("filament_colour", 0);
+ extruder_rgb = (is_selected && !extruder_color.empty()) ? extruder_color : filament_rgb;
+ single_bar = filament_rgb == extruder_rgb;
+
+ bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb;
+ }
+
+ wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name,
+ preset.is_compatible, preset.is_system || preset.is_default,
+ single_bar, filament_rgb, extruder_rgb);
+ assert(bmp);
+
+ const std::string name = preset.alias.empty() ? preset.name : preset.alias;
+ if (preset.is_default || preset.is_system) {
+ Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp);
+ validate_selection(is_selected);
+ if (is_selected)
+ tooltip = wxString::FromUTF8(preset.name.c_str());
+ }
+ else
+ {
+ nonsys_presets.emplace(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp);
+ if (is_selected) {
+ selected_user_preset = wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
+ tooltip = wxString::FromUTF8(preset.name.c_str());
+ }
+ }
+ if (i + 1 == m_collection->num_default_presets())
+ set_label_marker(Append(separator(L("System presets")), wxNullBitmap));
+ }
+ if (!nonsys_presets.empty())
+ {
+ set_label_marker(Append(separator(L("User presets")), wxNullBitmap));
+ for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
+ Append(it->first, *it->second);
+ validate_selection(it->first == selected_user_preset);
+ }
+ }
+
+ if (m_type == Preset::TYPE_PRINTER)
+ {
+ // add Physical printers, if any exists
+ if (!m_preset_bundle->physical_printers.empty()) {
+ set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap));
+ const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers;
+
+ for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) {
+ for (const std::string preset_name : it->get_preset_names()) {
+ Preset* preset = m_collection->find_preset(preset_name);
+ if (!preset)
+ continue;
+ std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
+ wxBitmap* bmp = get_bmp(main_icon_name, wide_icons, main_icon_name);
+ assert(bmp);
+
+ set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER);
+ validate_selection(ph_printers.is_selected(it, preset_name));
+ }
+ }
+ }
+ }
+
+ if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) {
+ wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni");
+ assert(bmp);
+
+ if (m_type == Preset::TYPE_FILAMENT)
+ set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS);
+ else if (m_type == Preset::TYPE_SLA_MATERIAL)
+ set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS);
+ else
+ set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS);
+ }
+
+ update_selection();
+ Thaw();
+
+ if (!tooltip.IsEmpty())
+ SetToolTip(tooltip);
+
+ // Update control min size after rescale (changed Display DPI under MSW)
+ if (GetMinWidth() != 20 * m_em_unit)
+ SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight()));
+}
+
+void PlaterPresetComboBox::msw_rescale()
+{
+ PresetComboBox::msw_rescale();
+ edit_btn->msw_rescale();
+}
+
+
+// ---------------------------------
+// *** TabPresetComboBox ***
+// ---------------------------------
+
+TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) :
+ PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1))
+{
+ Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
+ // see https://github.com/prusa3d/PrusaSlicer/issues/3889
+ // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender")
+ // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive.
+ // So, use GetSelection() from event parameter
+ auto selected_item = evt.GetSelection();
+
+ auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
+ if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) {
+ this->SetSelection(this->m_last_selected);
+ if (marker == LABEL_ITEM_WIZARD_PRINTERS)
+ wxTheApp->CallAfter([this]() {
+ wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS);
+
+ // update combobox if its parent is a PhysicalPrinterDialog
+ PhysicalPrinterDialog* parent = dynamic_cast<PhysicalPrinterDialog*>(this->GetParent());
+ if (parent != nullptr)
+ update();
+ });
+ }
+ else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) {
+ m_last_selected = selected_item;
+ on_selection_changed(selected_item);
+ }
+
+ evt.StopPropagation();
+#ifdef __WXMSW__
+ // From the Win 2004 preset combobox lose a focus after change the preset selection
+ // and that is why the up/down arrow doesn't work properly
+ // (see https://github.com/prusa3d/PrusaSlicer/issues/5531 ).
+ // So, set the focus to the combobox explicitly
+ this->SetFocus();
+#endif
+ });
+}
+
+// Update the choice UI from the list of presets.
+// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
+// If an incompatible preset is selected, it is shown as well.
+void TabPresetComboBox::update()
+{
+ Freeze();
+ Clear();
+ invalidate_selection();
+
+ const std::deque<Preset>& presets = m_collection->get_presets();
+
+ std::map<wxString, std::pair<wxBitmap*, bool>> nonsys_presets;
+ wxString selected = "";
+ if (!presets.front().is_visible)
+ set_label_marker(Append(separator(L("System presets")), wxNullBitmap));
+ size_t idx_selected = m_collection->get_selected_idx();
+
+ if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) {
+ std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name();
+ Preset* preset = m_collection->find_preset(sel_preset_name);
+ if (!preset)
+ m_preset_bundle->physical_printers.unselect_printer();
+ }
+
+ for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
+ {
+ const Preset& preset = presets[i];
+ if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected))
+ continue;
+
+ // marker used for disable incompatible printer models for the selected physical printer
+ bool is_enabled = true;
+
+ std::string bitmap_key = "tab";
+ if (m_type == Preset::TYPE_PRINTER) {
+ bitmap_key += "_printer";
+ if (preset.printer_technology() == ptSLA)
+ bitmap_key += "_sla";
+ }
+ std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
+
+ wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default);
+ assert(bmp);
+
+ if (preset.is_default || preset.is_system) {
+ int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp);
+ if (!is_enabled)
+ set_label_marker(item_id, LABEL_ITEM_DISABLED);
+ validate_selection(i == idx_selected);
+ }
+ else
+ {
+ std::pair<wxBitmap*, bool> pair(bmp, is_enabled);
+ nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair<wxBitmap*, bool>(bmp, is_enabled));
+ if (i == idx_selected)
+ selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
+ }
+ if (i + 1 == m_collection->num_default_presets())
+ set_label_marker(Append(separator(L("System presets")), wxNullBitmap));
+ }
+ if (!nonsys_presets.empty())
+ {
+ set_label_marker(Append(separator(L("User presets")), wxNullBitmap));
+ for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
+ int item_id = Append(it->first, *it->second.first);
+ bool is_enabled = it->second.second;
+ if (!is_enabled)
+ set_label_marker(item_id, LABEL_ITEM_DISABLED);
+ validate_selection(it->first == selected);
+ }
+ }
+
+ if (m_type == Preset::TYPE_PRINTER)
+ {
+ // add Physical printers, if any exists
+ if (!m_preset_bundle->physical_printers.empty()) {
+ set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap));
+ const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers;
+
+ for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) {
+ for (const std::string preset_name : it->get_preset_names()) {
+ Preset* preset = m_collection->find_preset(preset_name);
+ if (!preset)
+ continue;
+ std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
+
+ wxBitmap* bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false);
+ assert(bmp);
+
+ set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER);
+ validate_selection(ph_printers.is_selected(it, preset_name));
+ }
+ }
+ }
+
+ // add "Add/Remove printers" item
+ std::string icon_name = "edit_uni";
+ wxBitmap* bmp = get_bmp("edit_preset_list, tab,", icon_name, "");
+ assert(bmp);
+
+ set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS);
+ }
+
+ update_selection();
+ Thaw();
+}
+
+void TabPresetComboBox::msw_rescale()
+{
+ PresetComboBox::msw_rescale();
+ wxSize sz = wxSize(35 * m_em_unit, -1);
+ SetMinSize(sz);
+ SetSize(sz);
+}
+
+void TabPresetComboBox::update_dirty()
+{
+ // 1) Update the dirty flag of the current preset.
+ m_collection->update_dirty();
+
+ // 2) Update the labels.
+ wxWindowUpdateLocker noUpdates(this);
+ for (unsigned int ui_id = 0; ui_id < GetCount(); ++ui_id) {
+ auto marker = reinterpret_cast<Marker>(this->GetClientData(ui_id));
+ if (marker >= LABEL_ITEM_MARKER)
+ continue;
+
+ std::string old_label = GetString(ui_id).utf8_str().data();
+ std::string preset_name = Preset::remove_suffix_modified(old_label);
+ std::string ph_printer_name;
+
+ if (marker == LABEL_ITEM_PHYSICAL_PRINTER) {
+ ph_printer_name = PhysicalPrinter::get_short_name(preset_name);
+ preset_name = PhysicalPrinter::get_preset_name(preset_name);
+ }
+
+ const Preset* preset = m_collection->find_preset(preset_name, false);
+ if (preset) {
+ std::string new_label = preset->is_dirty ? preset->name + Preset::suffix_modified() : preset->name;
+
+ if (marker == LABEL_ITEM_PHYSICAL_PRINTER)
+ new_label = ph_printer_name + PhysicalPrinter::separator() + new_label;
+
+ if (old_label != new_label)
+ SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
+ }
+ }
+#ifdef __APPLE__
+ // wxWidgets on OSX do not upload the text of the combo box line automatically.
+ // Force it to update by re-selecting.
+ SetSelection(GetSelection());
+#endif /* __APPLE __ */
+}
+
+}} // namespace Slic3r::GUI