diff --git a/resources/images/score_star_outline.svg b/resources/images/score_star_outline.svg
new file mode 100644
index 0000000000..eb52be9e82
--- /dev/null
+++ b/resources/images/score_star_outline.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/slic3r/GUI/AMSMaterialsSetting.cpp b/src/slic3r/GUI/AMSMaterialsSetting.cpp
index 903524caaa..41047dffd9 100644
--- a/src/slic3r/GUI/AMSMaterialsSetting.cpp
+++ b/src/slic3r/GUI/AMSMaterialsSetting.cpp
@@ -16,6 +16,7 @@
#include
#include "slic3r/GUI/GUI_Utils.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
+#include "slic3r/GUI/Widgets/DropDown.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include
#include "slic3r/GUI/wxExtensions.hpp"
@@ -172,7 +173,26 @@ void AMSMaterialsSetting::create()
Bind(wxEVT_PAINT, &AMSMaterialsSetting::paintEvent, this);
Bind(EVT_SELECTED_COLOR, &AMSMaterialsSetting::on_picker_color, this);
- m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
+ m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
+ m_comboBox_filament->Bind(EVT_DROPDOWN_ITEM_ACTION, &AMSMaterialsSetting::on_toggle_favorite_filament, this);
+
+ // Favorites toggled in the open list are regrouped just before it opens again, so rows never move
+ // under the cursor. These handlers run ahead of ComboBox's own, which then measure and show the
+ // rebuilt rows; wxEVT_COMBOBOX_DROPDOWN would be too late, as ComboBox sends it after showing the list.
+ auto regroup_favorites = [this]() {
+ if (!m_favorites_regroup || m_comboBox_filament->is_drop_down())
+ return;
+ const FilamentInfos* info = selected_filament_info();
+ const std::string filament_id = info ? info->filament_id : std::string();
+ rebuild_filament_dropdown_items(filament_id);
+ };
+ m_comboBox_filament->Bind(wxEVT_LEFT_DOWN, [regroup_favorites](wxMouseEvent& e) { regroup_favorites(); e.Skip(); });
+ m_comboBox_filament->Bind(wxEVT_LEFT_DCLICK, [regroup_favorites](wxMouseEvent& e) { regroup_favorites(); e.Skip(); });
+ m_comboBox_filament->Bind(wxEVT_KEY_DOWN, [regroup_favorites](wxKeyEvent& e) {
+ if (e.GetKeyCode() == WXK_RETURN || e.GetKeyCode() == WXK_SPACE) // the keys that open the list
+ regroup_favorites();
+ e.Skip();
+ });
m_comboBox_cali_result->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
}
@@ -191,11 +211,9 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent)
m_sizer_filament->Add(0, 0, 0, wxEXPAND, 0);
-#ifdef __APPLE__
- m_comboBox_filament = new wxComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
-#else
m_comboBox_filament = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
-#endif
+ m_star_filled = ScalableBitmap(this, "score_star_light", 16);
+ m_star_empty = ScalableBitmap(this, "score_star_outline", 16);
m_sizer_filament->Add(m_comboBox_filament, 1, wxALIGN_CENTER, 0);
@@ -420,6 +438,7 @@ void AMSMaterialsSetting::paintEvent(wxPaintEvent &evt)
AMSMaterialsSetting::~AMSMaterialsSetting()
{
m_comboBox_filament->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
+ m_comboBox_filament->Unbind(EVT_DROPDOWN_ITEM_ACTION, &AMSMaterialsSetting::on_toggle_favorite_filament, this);
m_comboBox_cali_result->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
}
@@ -531,10 +550,9 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
std::string selected_ams_id;
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
+ const FilamentInfos* selected_info = selected_filament_info();
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
- auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
- std::string filament_id = filament_item.filament_id;
- if (it->filament_id.compare(filament_id) == 0) {
+ if (selected_info && it->filament_id.compare(selected_info->filament_id) == 0) {
selected_ams_id = it->filament_id;
break;
}
@@ -682,12 +700,13 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
ams_setting_id = "";
// the combobox item
- auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
+ const FilamentInfos* selected_info = selected_filament_info();
+ const std::string filament_id = selected_info ? selected_info->filament_id : std::string();
// check filament info (rack-gated per-nozzle blacklist evaluation)
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
const auto& fila_check_res = sCheckFilamentInfo(preset_bundle, obj, ams_id, slot_id,
- filament_item.filament_id, ams_filament_id, ams_setting_id);
+ filament_id, ams_filament_id, ams_setting_id);
if (const auto& prohibit_items = fila_check_res.get_items_by_action("prohibition"); !prohibit_items.empty()) {
wxString info_msg;
@@ -986,6 +1005,133 @@ static void _collect_filament_info(const wxString& shown_name,
query_filament_types[shown_name] = filament.config.get_filament_type();
}
+static const char* AMS_FAVORITE_FILAMENT_IDS_KEY = "ams_favorite_filament_ids";
+
+const AMSMaterialsSetting::FilamentInfos* AMSMaterialsSetting::selected_filament_info() const
+{
+ const int selection = m_comboBox_filament->GetSelection();
+ if (selection < 0 || selection >= int(m_filament_list_items.size()) || !m_filament_list_items[selection].is_filament)
+ return nullptr;
+ return &m_filament_list_items[selection].filament;
+}
+
+std::set AMSMaterialsSetting::load_favorite_filament_ids() const
+{
+ std::set favorite_ids;
+ std::vector values;
+ const std::string serialized = wxGetApp().app_config->get(AMS_FAVORITE_FILAMENT_IDS_KEY);
+ if (!serialized.empty() && Slic3r::unescape_strings_cstyle(serialized, values)) {
+ favorite_ids.insert(values.begin(), values.end());
+ }
+ return favorite_ids;
+}
+
+void AMSMaterialsSetting::save_favorite_filament_ids(const std::set& favorite_ids)
+{
+ std::vector values(favorite_ids.begin(), favorite_ids.end());
+ wxGetApp().app_config->set(AMS_FAVORITE_FILAMENT_IDS_KEY, Slic3r::escape_strings_cstyle(values));
+ wxGetApp().app_config->save();
+}
+
+void AMSMaterialsSetting::rebuild_filament_dropdown_items(const std::string& selected_filament_id)
+{
+ std::vector items;
+ m_filament_list_items.clear();
+
+ auto append_header = [&items, this](const wxString& label) {
+ DropDown::Item item;
+ item.text = label;
+ item.style = DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED;
+ items.push_back(item);
+ m_filament_list_items.push_back(FilamentListItem{});
+ };
+
+ auto append_filament = [&items, this](const wxString& name) {
+ auto info_it = map_filament_items.find(name);
+ if (info_it == map_filament_items.end())
+ return;
+
+ DropDown::Item item;
+ item.text = name;
+ items.push_back(item);
+ m_filament_list_items.push_back(FilamentListItem{info_it->second, true});
+ };
+
+ // Favorites are listed again at the top under their own header. Without any, the list is the
+ // plain filament list.
+ const std::set favorite_ids = load_favorite_filament_ids();
+ std::vector favorites;
+ for (const wxString& name : m_sorted_filament_items) {
+ auto info_it = map_filament_items.find(name);
+ if (info_it != map_filament_items.end() && favorite_ids.count(info_it->second.filament_id) > 0)
+ favorites.push_back(name);
+ }
+ if (!favorites.empty()) {
+ append_header(_L("Favorite filaments"));
+ for (const wxString& name : favorites)
+ append_filament(name);
+ append_header(_L("All filaments"));
+ }
+ for (const wxString& name : m_sorted_filament_items)
+ append_filament(name);
+
+ int selection_idx = -1;
+ if (!selected_filament_id.empty()) {
+ for (int i = 0; i < int(m_filament_list_items.size()); ++i) {
+ if (m_filament_list_items[i].is_filament && m_filament_list_items[i].filament.filament_id == selected_filament_id) {
+ selection_idx = i;
+ break;
+ }
+ }
+ }
+
+ // Clear() first: SetItems() keeps the old selection, and SetSelection() ignores an index equal to it,
+ // which would leave the previous filament's name on the combo.
+ m_comboBox_filament->Clear();
+ m_comboBox_filament->SetItems(items);
+ update_favorite_stars();
+ m_comboBox_filament->SetSelection(selection_idx);
+ m_favorites_regroup = false;
+}
+
+void AMSMaterialsSetting::update_favorite_stars()
+{
+ const std::set favorite_ids = load_favorite_filament_ids();
+ for (size_t i = 0; i < m_filament_list_items.size(); ++i) {
+ const FilamentListItem& row = m_filament_list_items[i];
+ if (row.is_filament) {
+ const bool favorite = favorite_ids.count(row.filament.filament_id) > 0;
+ m_comboBox_filament->SetItemActionBitmap(i, favorite ? m_star_filled.bmp() : m_star_empty.bmp());
+ }
+ }
+}
+
+void AMSMaterialsSetting::toggle_favorite_filament(const std::string& filament_id)
+{
+ if (filament_id.empty())
+ return;
+
+ std::set favorite_ids = load_favorite_filament_ids();
+ auto iter = favorite_ids.find(filament_id);
+ if (iter == favorite_ids.end())
+ favorite_ids.insert(filament_id);
+ else
+ favorite_ids.erase(iter);
+
+ save_favorite_filament_ids(favorite_ids);
+}
+
+void AMSMaterialsSetting::on_toggle_favorite_filament(wxCommandEvent& evt)
+{
+ const int index = evt.GetInt();
+ if (index < 0 || index >= int(m_filament_list_items.size()) || !m_filament_list_items[index].is_filament)
+ return;
+
+ toggle_favorite_filament(m_filament_list_items[index].filament.filament_id);
+ update_favorite_stars();
+ m_favorites_regroup = true;
+}
+
void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_min, wxString temp_max, wxString k, wxString n)
{
if (!obj) return;
@@ -999,13 +1145,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_input_k_val->GetTextCtrl()->SetValue(k);
m_input_n_val->GetTextCtrl()->SetValue(n);
- wxArrayString filament_items;
wxString bambu_filament_name;
- wxString hint_filament_name; // the hint type to be selected
std::unordered_map query_filament_vendors;// some information for sort
std::unordered_map query_filament_types; //
std::set filament_id_set;
+ m_sorted_filament_items.clear();
// The alias keyed map has to start empty: it is a member, so a stale alias left by an earlier
// popup (a different printer, a different nozzle) would resolve to that printer's filament_id.
map_filament_items.clear();
@@ -1035,7 +1180,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
if (alias.empty())
continue;
- filament_items.push_back(alias);
+ m_sorted_filament_items.push_back(alias);
_collect_filament_info(alias, *filament_it, query_filament_vendors, query_filament_types);
FilamentInfos filament_infos;
@@ -1044,7 +1189,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
map_filament_items[alias] = filament_infos;
if (filament_it->filament_id == ams_filament_id) {
- hint_filament_name = from_u8(alias);
bambu_filament_name = from_u8(alias);
@@ -1215,24 +1359,11 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
return name_compare != 0 ? name_compare < 0 : left < right;
};
- std::sort(filament_items.begin(), filament_items.end(), _filament_sorter);
+ std::sort(m_sorted_filament_items.begin(), m_sorted_filament_items.end(), _filament_sorter);
}
- // traverse the hint selection idx
- int selection_idx = -1;
- {
- for(int i = 0; i < filament_items.size(); i++)
- {
- if (hint_filament_name == filament_items[i])
- {
- selection_idx = i;
- break;
- }
- }
- }
-
- m_comboBox_filament->Set(filament_items);
- m_comboBox_filament->SetSelection(selection_idx);
+ rebuild_filament_dropdown_items(ams_filament_id);
+ int selection_idx = m_comboBox_filament->GetSelection();
post_select_event(selection_idx);
if (selection_idx < 0) {
@@ -1289,6 +1420,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
int* from_printer = static_cast(m_comboBox_filament->GetClientData());
m_filament_type = "";
+ const FilamentInfos* selected_info = selected_filament_info();
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
std::ostringstream stream;
@@ -1309,9 +1441,8 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
std::string nozzle_diameter_str = stream.str();
// Resolve the selection against the same list Popup() built the dropdown from, so the two
// halves of the dialog cannot disagree about which filaments this machine can use.
- const std::string selected = m_comboBox_filament->GetValue().ToStdString();
- if (!selected.empty()) {
- const std::string filament_id = map_filament_items[selected].filament_id;
+ if (selected_info) {
+ const std::string& filament_id = selected_info->filament_id;
for (Preset *it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
if (it->filament_id != filament_id)
@@ -1364,7 +1495,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
//reset cali
int cali_select_idx = -1;
- if ( !this->obj || m_filament_selection < 0) {
+ if ( !this->obj || m_filament_selection < 0 || !selected_info) {
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Disable(); // ORCA No need to change style
@@ -1383,21 +1514,9 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
ams_filament_id = "";
ams_setting_id = "";
- if (preset_bundle) {
- for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
- auto itor = map_filament_items.find(m_comboBox_filament->GetValue().ToStdString());
- if ( itor != map_filament_items.end()) {
- ams_filament_id = itor->second.filament_id;
- ams_setting_id = itor->second.setting_id;
- break;
- }
-
- if (it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
- ams_filament_id = it->filament_id;
- ams_setting_id = it->setting_id;
- break;
- }
- }
+ if (selected_info) {
+ ams_filament_id = selected_info->filament_id;
+ ams_setting_id = selected_info->setting_id;
}
wxArrayString items;
@@ -1529,6 +1648,11 @@ void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect)
m_button_reset->Rescale(); // ORCA
m_button_confirm->Rescale(); // ORCA
m_button_close->Rescale(); // ORCA
+ // An open list would keep the measurements of the old stars, so close it before replacing them.
+ m_comboBox_filament->CloseDropdown();
+ m_star_filled.msw_rescale();
+ m_star_empty.msw_rescale();
+ update_favorite_stars();
this->Refresh();
}
diff --git a/src/slic3r/GUI/AMSMaterialsSetting.hpp b/src/slic3r/GUI/AMSMaterialsSetting.hpp
index 2628e6caf4..6fa215063a 100644
--- a/src/slic3r/GUI/AMSMaterialsSetting.hpp
+++ b/src/slic3r/GUI/AMSMaterialsSetting.hpp
@@ -31,6 +31,7 @@
#include "slic3r/GUI/Widgets/StateColor.hpp"
#include
#include