OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::GUI::IMEXModesCtrl Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/slic3r/GUI/IMEXModesCtrl.hpp>

Inheritance diagram for Slic3r::GUI::IMEXModesCtrl:
Collaboration diagram for Slic3r::GUI::IMEXModesCtrl:

Classes

struct  RoleStyle
struct  Row
struct  RowSnapshot

Public Types

using ParentConfigLookup = std::function<const DynamicPrintConfig*()>

Public Member Functions

void set_parent_config_lookup (ParentConfigLookup f)
 IMEXModesCtrl (wxWindow *parent, int n_cols, int n_rows, int layout=0)
void set_grid_size (int n_cols, int n_rows, int layout=-1)
void load_from_config (const DynamicPrintConfig &cfg)
std::tuple< std::vector< std::string >, std::vector< std::string >, std::vector< std::string > > get_mode_data () const
bool matches_config (const DynamicPrintConfig &cfg) const
void set_applicable (bool applicable)
bool Show (bool show=true) override

Static Public Member Functions

static int parse_layout (ImexToolLayout layout)

Public Attributes

std::function< void()> on_change
std::function< void(const std::vector< std::string > &)> on_mode_removed

Private Member Functions

std::string unique_mode_name (const std::vector< std::string > &also_taken, const std::string &base="Mode") const
bool role_offered (ImexRole role) const
bool role_allowed_on_tile (ImexRole role, int tool_idx, bool other_primary, int primary_gantry) const
std::vector< wxString > suggested_mode_names (const ::ComboBox *skip=nullptr) const
void apply_btn (wxButton *btn, int tool_idx, std::optional< ImexRole > role)
RowSnapshot snapshot_row (const Row &r) const
bool row_differs_from_parent (int row_idx) const
bool row_has_parent_counterpart (int row_idx) const
void add_row (const std::string &name="", const std::string &active_tools="", const std::string &gcode="", bool is_primary=false)
std::vector< std::string > remove_row (wxPanel *panel)
void clear_rows ()
std::string active_tools_string (const Row &r) const
bool fix_row_name (Row &r)
void notify ()
void refresh_reset_buttons ()
void reset_row_to_parent (wxPanel *panel)
void rebuild_info_and_header ()

Static Private Member Functions

static std::map< int, ImexRole > roles_for_mode (const std::string &active_tools)
static RoleStyle role_style (std::optional< ImexRole > role)
static std::optional< ImexRole > next_tile_role (std::optional< ImexRole > current, const std::function< bool(ImexRole)> &allowed)
static wxString role_hint (ImexRole role)

Private Attributes

bool m_applicable = true
wxBoxSizer * m_outer
wxBoxSizer * m_rows_sizer
wxPanel * m_info_panel = nullptr
wxPanel * m_hdr_panel = nullptr
std::vector< Row > m_rows
int m_n_cols
int m_n_rows
int m_layout {0}
bool m_clearing_rows {false}
ParentConfigLookup m_parent_lookup

Detailed Description


Class Documentation

◆ Slic3r::GUI::IMEXModesCtrl::RoleStyle

struct Slic3r::GUI::IMEXModesCtrl::RoleStyle
Class Members
wxColour bg
wxColour fg
wxString label

◆ Slic3r::GUI::IMEXModesCtrl::Row

struct Slic3r::GUI::IMEXModesCtrl::Row
Collaboration diagram for Slic3r::GUI::IMEXModesCtrl::Row:
Class Members
map< int, ImexRole > all_tool_roles
vector< optional< ImexRole > > btn_roles
vector< int > btn_tool_idx
vector< wxButton * > btns
string focus_name
wxTextCtrl * gcode
bool is_primary {false}
::ComboBox * name
string orig_name
wxPanel * panel
ScalableButton * reset_btn {nullptr}
bool reset_dirty_cached {false}

◆ Slic3r::GUI::IMEXModesCtrl::RowSnapshot

struct Slic3r::GUI::IMEXModesCtrl::RowSnapshot
Collaboration diagram for Slic3r::GUI::IMEXModesCtrl::RowSnapshot:
Class Members
string gcode
string name
string tools

Member Typedef Documentation

◆ ParentConfigLookup

Constructor & Destructor Documentation

◆ IMEXModesCtrl()

Slic3r::GUI::IMEXModesCtrl::IMEXModesCtrl ( wxWindow * parent,
int n_cols,
int n_rows,
int layout = 0 )
48 : wxPanel(parent, wxID_ANY), m_n_cols(std::max(1, n_cols)), m_n_rows(std::max(1, n_rows)), m_layout(layout)
49{
50 // Set explicitly: GTK otherwise gives child wxPanels a slightly lighter "widget bg",
51 // which shows as a light box around the chromeless ScalableButtons. The page's own
52 // color, as OG_CustomCtrl takes it, because the dark-mode walk only remaps palette
53 // colors: the app's window default is not one, so it never matched the page and
54 // froze in whichever theme the editor was last built in. Sub-panels inherit this color.
55 SetBackgroundColour(parent->GetBackgroundColour());
56
57 m_outer = new wxBoxSizer(wxVERTICAL);
58 m_rows_sizer = new wxBoxSizer(wxVERTICAL);
59
60 // Info panel (instructions + legend) and the column-header row. Both depend on the grid
61 // shape, so their CONTENTS are (re)built by rebuild_info_and_header(), which set_grid_size()
62 // calls again whenever the grid changes. The panels themselves are stable so m_outer's
63 // ordering never has to be rearranged.
64 m_info_panel = new wxPanel(this, wxID_ANY);
65 m_info_panel->SetBackgroundColour(GetBackgroundColour());
66 m_hdr_panel = new wxPanel(this, wxID_ANY);
67 m_hdr_panel->SetBackgroundColour(GetBackgroundColour());
69
70 // Add Mode is built by rebuild_info_and_header() into the info panel, above the rows:
71 // at the bottom it moved down the page every time a mode was added, so its position
72 // depended on how many modes you already had.
73 m_outer->Add(m_info_panel, 0, wxEXPAND | wxBOTTOM, FromDIP(4));
74 m_outer->Add(m_hdr_panel, 0, wxEXPAND | wxBOTTOM, FromDIP(2));
75 m_outer->Add(m_rows_sizer, 0, wxEXPAND);
76 m_outer->AddSpacer(FromDIP(4));
77 // SetSizer(), not SetSizerAndFit(): no row exists yet, so there is nothing to fit to --
78 // rows arrive from add_row() / load_from_config() / set_grid_size(). Deliberately NOT
79 // followed by m_outer->SetSizeHints(this) once they do: this panel owns a sizer, so
80 // wxWindow::GetBestSize() bypasses the best-size cache and re-runs m_outer->CalcMin()
81 // on every parent layout, and that is what the enclosing sizer already reserves space
82 // for via GetEffectiveMinSize(). An explicitly set min size takes PRIORITY over the best
83 // size in that call, so a size hint would pin the reserved height to the row count that
84 // happened to be on screen when it ran -- adding a mode after that would clip the bottom
85 // row, which is the very failure the hint is meant to avoid.
86 SetSizer(m_outer);
87}
int m_layout
Definition IMEXModesCtrl.hpp:262
void rebuild_info_and_header()
Definition IMEXModesCtrl.cpp:115
wxPanel * m_info_panel
Definition IMEXModesCtrl.hpp:258
int m_n_cols
Definition IMEXModesCtrl.hpp:261
wxBoxSizer * m_outer
Definition IMEXModesCtrl.hpp:256
wxPanel * m_hdr_panel
Definition IMEXModesCtrl.hpp:259
wxBoxSizer * m_rows_sizer
Definition IMEXModesCtrl.hpp:257
int m_n_rows
Definition IMEXModesCtrl.hpp:261

References m_hdr_panel, m_info_panel, m_layout, m_n_cols, m_n_rows, m_outer, m_rows_sizer, and rebuild_info_and_header().

Here is the call graph for this function:

Member Function Documentation

◆ active_tools_string()

std::string Slic3r::GUI::IMEXModesCtrl::active_tools_string ( const Row & r) const
private
834 {
835 // Serialize from all_tool_roles (not just visible buttons) so assignments
836 // for off-screen tools are preserved across grid size changes. Inactive tools are
837 // simply absent from the map, so there is nothing to filter out here.
838 //
839 // The letters come from imex_role_letter(), the same table parse_imex_active_tools()
840 // reads back — this used to be a second, independent int → letter switch, which is
841 // exactly where the editor and the slicer could come to disagree about a mode string.
842 std::string s;
843 for (const auto& [idx, role] : r.all_tool_roles) {
844 if (!s.empty()) s += ',';
845 s += std::to_string(idx) + ':' + imex_role_letter(role);
846 }
847 return s;
848}
char imex_role_letter(ImexRole role)
Definition IMEXHelpers.cpp:308

References active_tools_string(), Slic3r::GUI::IMEXModesCtrl::Row::all_tool_roles, and Slic3r::imex_role_letter().

Referenced by active_tools_string(), get_mode_data(), and snapshot_row().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_row()

void Slic3r::GUI::IMEXModesCtrl::add_row ( const std::string & name = "",
const std::string & active_tools = "",
const std::string & gcode = "",
bool is_primary = false )
private
473{
474 Row r;
475 r.is_primary = is_primary;
476 r.panel = new wxPanel(this, wxID_ANY);
477 r.panel->SetBackgroundColour(GetBackgroundColour());
478 auto* sizer = new wxBoxSizer(wxHORIZONTAL);
479 // The mode column is vertical: name on top, and for a deletable row the remove button
480 // beneath it. Remove used to sit next to Edit in the right-hand column, one icon apart
481 // from a button people press often -- a destructive action does not belong there.
482 auto* name_col = new wxBoxSizer(wxVERTICAL);
483
484 if (is_primary) {
485 r.orig_name = kImexPrimaryMode;
486 r.name = nullptr;
487 auto* lbl = page_label(new wxStaticText(r.panel, wxID_ANY, _L("Primary"),
488 wxDefaultPosition, FromDIP(wxSize(kNameColPx, -1))));
489 wxFont f = lbl->GetFont();
490 f.SetWeight(wxFONTWEIGHT_BOLD);
491 lbl->SetFont(f);
492 name_col->Add(lbl, 0); // the combo below carries no frame, so no inset to match
493 } else {
494 // A nameless mode cannot be selected on a plate and used to be dropped on
495 // save together with its tool roles and G-code, so a name is pre-filled
496 // here and restored below if the field is left empty. That keeps the
497 // "Add Mode → assign tools → never typed a name" path from losing work
498 // without ever showing an error.
499 const std::string nm = name.empty() ? unique_mode_name({}) : name;
500 // The name as first shown, i.e. the one the plate's mode selector offered while this
501 // row looked like this. remove_row() reports it alongside the current name, because
502 // renaming is not routed through on_mode_removed and the two then diverge.
503 r.orig_name = nm;
504 // Editable: no wxCB_READONLY, so the inner text control stays live and any name can
505 // still be typed, which the mode table needs -- it is authored for whatever hardware
506 // the user has. ComboBox derives from TextInput and paints the same border the settings
507 // fields do, so it needs no frame panel of its own.
508 r.name = new ::ComboBox(r.panel, wxID_ANY, wxEmptyString, wxDefaultPosition,
509 FromDIP(wxSize(kNameColPx, -1)), 0, nullptr, 0);
510 for (const wxString& sug : suggested_mode_names())
511 r.name->Append(sug);
512 // Rebuilt on every click that could open the list: add_row() runs before this row joins
513 // m_rows and before the rows below it exist, so a list built once there filters against
514 // only part of the table and offers names that are already taken. By then every row is
515 // present -- and this row's own name is excluded from "taken" so it stays offered. It
516 // has to be BEFORE ComboBox opens it, not in wxEVT_COMBOBOX_DROPDOWN, which ComboBox
517 // sends after sizing and showing the popup from the old items: with every name taken
518 // that showed an empty popup. Bound handlers run ahead of ComboBox's own event-table
519 // entries, so this rebuilds first and, with nothing to offer, keeps it shut. Only clicks
520 // can open it: focus lives in the inner text control, so keys never reach the combo.
521 const auto on_open_click = [this, cb = r.name](wxMouseEvent& e) {
522 if (!cb->is_drop_down()) {
523 cb->Clear();
524 for (const wxString& sug : suggested_mode_names(cb))
525 cb->Append(sug);
526 if (cb->GetCount() == 0) {
527 cb->SetFocus(); // what ComboBox's own click would have done, short of the list
528 return;
529 }
530 }
531 e.Skip();
532 };
533 r.name->Bind(wxEVT_LEFT_DOWN, on_open_click);
534 r.name->Bind(wxEVT_LEFT_DCLICK, on_open_click);
535 // Constructed empty on purpose: ComboBox hands its value to TextInput as the LABEL --
536 // the small right-aligned slot a unit like "mm" lives in -- because a read-only combo
537 // hides the text control and shows that label instead. This one is editable, so the
538 // value belongs in the text control; left in the label it renders as a second, greyed
539 // copy of the name beside the hint.
540 r.name->GetTextCtrl()->ChangeValue(from_u8(nm));
541 r.name->GetTextCtrl()->SetHint(_L("Mode name (required)"));
542 r.name->SetToolTip(_L("Name of this parallel mode, as it appears in the plate's IDEX/IQEX mode "
543 "selector. Stored in the project by name, so renaming a mode that "
544 "plates already use makes them fall back to Primary. Cannot be empty "
545 "— a blank name is replaced with a generated one."));
546 r.name->GetTextCtrl()->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { notify(); });
547 // Picking a suggestion writes it into the text control, so everything downstream keeps
548 // reading one place for the name.
549 r.name->Bind(wxEVT_COMBOBOX, [this, cb = r.name](wxCommandEvent& e) {
550 // Do NOT call SetLabel() here. It is overridden, and on an editable combo it
551 // writes the TEXT CONTROL rather than the label (ComboBox.cpp, the IsShown()
552 // branch) -- clearing it would erase the name that was just picked. The label
553 // stays empty for this control's whole life anyway: it is constructed empty and
554 // every write routes through the same override.
555 // ChangeValue is still needed: SetSelection() early-returns when the index is
556 // unchanged, so re-picking the current item would otherwise leave typed-over text.
557 cb->GetTextCtrl()->ChangeValue(cb->GetDropDown().GetValue());
558 notify();
559 });
560 // Restore a name rather than let the row reach get_mode_data() unnamed.
561 // Row is located by panel pointer (stable across add/remove) so a
562 // kill-focus delivered while the rows are being torn down is a no-op.
563 r.name->GetTextCtrl()->Bind(wxEVT_SET_FOCUS, [this, panel = r.panel](wxFocusEvent& e) {
564 e.Skip();
565 for (auto& row_ref : m_rows)
566 if (row_ref.panel == panel && row_ref.name)
567 row_ref.focus_name = into_u8(row_ref.name->GetTextCtrl()->GetValue());
568 });
569 r.name->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, panel = r.panel](wxFocusEvent& e) {
570 e.Skip();
571 if (m_clearing_rows) return; // focus-out emitted while the rows are being deleted
572 for (auto& row_ref : m_rows) {
573 if (row_ref.panel != panel) continue;
574 // Only an edit is checked. Tabbing through a hand-edited profile's duplicate
575 // leaves it alone rather than renaming the row plates resolve to.
576 const wxString value = row_ref.name ? row_ref.name->GetTextCtrl()->GetValue() : wxString();
577 if (!value.empty() && into_u8(value) == row_ref.focus_name)
578 return;
579 if (fix_row_name(row_ref))
580 notify();
581 return;
582 }
583 });
584 }
585
586 auto* grid_panel = new wxPanel(r.panel, wxID_ANY);
587 grid_panel->SetBackgroundColour(GetBackgroundColour());
588 auto* grid_sizer = new wxGridSizer(m_n_rows, m_n_cols, FromDIP(2), FromDIP(2));
589
590 auto tool_roles = roles_for_mode(active_tools);
591 if (tool_roles.empty())
592 tool_roles[0] = ImexRole::Primary; // default T0 → Primary when no assignment is stored
593 r.all_tool_roles = tool_roles; // preserve all assignments, including off-screen tools
594 wxPanel* this_panel = r.panel;
595
596 // Buttons rendered top=rear (high Y), bottom=front (low Y).
597 // Layout remapping: m_layout encodes which corner T0 is at physically.
598 // flip_x (layout 1,3): col 0 is on the right (max-X) instead of left
599 // flip_y (layout 2,3): row 0 is at the rear (max-Y) instead of front
600 // Display iterates: row from n_rows-1 down to 0 (rear→front = top→bottom).
601 // raw_row = flip_y ? (n_rows-1-row) : row
602 // raw_col = flip_x ? (n_cols-1-col) : col
603 //
604 // Render the whole grid: m_n_rows is the gantry count, so the valid tool indices are
605 // 0 .. m_n_rows*m_n_cols-1 and a window this tall only fits inside the grid at row 0.
606 // A Primary outside the grid is a data problem the zone layout already reports by
607 // producing no zones (compute_imex_zone_layout, IMEXZones.cpp).
608
609 bool flip_x = (m_layout == 1 || m_layout == 3);
610 bool flip_y = (m_layout == 2 || m_layout == 3);
611 for (int row = m_n_rows - 1; row >= 0; --row) {
612 for (int col = 0; col < m_n_cols; ++col) {
613 int raw_row = flip_y ? (m_n_rows - 1 - row) : row;
614 int raw_col = flip_x ? (m_n_cols - 1 - col) : col;
615 int tool_idx = raw_row * m_n_cols + raw_col;
616 std::optional<ImexRole> role; // nullopt == Inactive
617 auto it = tool_roles.find(tool_idx);
618 if (it != tool_roles.end()) role = it->second;
619
620 // wxBU_AUTODRAW, which wx ignores on a text button, is what GUI_App::UpdateDarkUI
621 // skips: without it every theme change ran the role colors through the palette
622 // map and left the labels gray. apply_btn() owns these colors in both themes.
623 auto* btn = new wxButton(grid_panel, wxID_ANY, wxEmptyString,
624 wxDefaultPosition, FromDIP(wxSize(24, 24)), wxBU_EXACTFIT | wxBU_AUTODRAW);
625 apply_btn(btn, tool_idx, role);
626
627 int btn_pos = (int)r.btns.size();
628 r.btns.push_back(btn);
629 r.btn_roles.push_back(role);
630 r.btn_tool_idx.push_back(tool_idx);
631
632 // Two tiles are read-only.
633 //
634 // The whole reserved `primary` row: it is the IMEX-off mode, so cycling roles there
635 // would be a no-op and confusing.
636 //
637 // And, on any row, the tile that currently HOLDS Primary. Primary is tool 0 and moves
638 // only via Tool 0 Position; letting a click demote it would leave the mode with no
639 // Primary at all, which parses to -1 and degrades the plate to an ordinary single-tool
640 // print with no zones — authored silently, in one click, with nothing in the editor
641 // showing what is wrong. The other tiles already never OFFER Primary while one is held
642 // (role_allowed_on_tile), so this closes the only remaining route to that state.
643 // A mode that reaches us WITHOUT a Primary (hand-edited preset, foreign 3MF) still has
644 // every tile live, so it can be repaired by promoting one.
645 if (is_primary || role == ImexRole::Primary) {
646 btn->Disable();
647 } else {
648 btn->Bind(wxEVT_BUTTON, [this, this_panel, btn_pos](wxCommandEvent&) {
649 // Find the row by panel pointer (stable across add/remove)
650 for (auto& row_ref : m_rows) {
651 if (row_ref.panel != this_panel) continue;
652 std::optional<ImexRole>& tile = row_ref.btn_roles[btn_pos];
653 const int tidx = row_ref.btn_tool_idx[btn_pos];
654
655 // Check if another button already holds the Primary role,
656 // and locate the primary's gantry row for Span eligibility.
657 bool other_primary = false;
658 int primary_gantry = -1;
659 for (int j = 0; j < (int)row_ref.btn_roles.size(); ++j) {
660 if (row_ref.btn_roles[j] == ImexRole::Primary) {
661 if (j != btn_pos) other_primary = true;
662 primary_gantry = row_ref.btn_tool_idx[j] / m_n_cols;
663 }
664 }
665
666 // Cycle Inactive → Primary → Copy → Mirror → Span → Inactive,
667 // skipping whatever this tile may not take right now.
668 tile = next_tile_role(tile, [&](ImexRole cand) {
669 return role_allowed_on_tile(cand, tidx, other_primary, primary_gantry);
670 });
671
672 // Keep all_tool_roles in sync so off-screen tools are preserved
673 if (tile)
674 row_ref.all_tool_roles[tidx] = *tile;
675 else
676 row_ref.all_tool_roles.erase(tidx);
677
678 apply_btn(row_ref.btns[btn_pos], row_ref.btn_tool_idx[btn_pos], tile);
679 notify();
680 break;
681 }
682 });
683 }
684
685 grid_sizer->Add(btn, 0);
686 }
687 }
688 grid_panel->SetSizerAndFit(grid_sizer);
689
690 // from_u8(): `gcode` arrives from the preset as UTF-8. Handing the raw std::string to
691 // wxString would decode it through the current locale's encoding instead.
692 auto* gcode_frame = framed_input(r.panel);
693 // wxBORDER_NONE matters off GTK: a wxTextCtrl's default border resolves to a themed or
694 // sunken edge on Windows and to NSBezelBorder on macOS, which would sit immediately inside
695 // the frame below and read as two borders.
696 r.gcode = new wxTextCtrl(gcode_frame, wxID_ANY, from_u8(gcode),
697 wxDefaultPosition, FromDIP(wxSize(220, 54)),
698 wxTE_MULTILINE | wxBORDER_NONE);
699 gcode_frame->GetSizer()->Add(r.gcode, 1, wxEXPAND | wxALL, 1);
700 // Same monospace face EditGCodeDialog gives its editor, so G-code reads the same
701 // wherever it is edited; and the same explicit-light-then-UpdateDarkUI treatment as the
702 // name field above, for the same reason.
703 r.gcode->SetFont(wxGetApp().code_font());
704 r.gcode->SetBackgroundColour(*wxWHITE);
705 r.gcode->SetForegroundColour(wxColour("#262E30")); // the palette's input text color
706 wxGetApp().UpdateDarkUI(r.gcode);
707 // Tooltip only, no SetHint(): wxTextEntry has no native placeholder for a
708 // multiline control, so wxWidgets emulates one by writing the hint into the
709 // control as gray text — indistinguishable from real G-code in this box.
710 r.gcode->SetToolTip(_L("G-code emitted once at the start of a print that uses this mode, before "
711 "the machine start G-code. This is where the printer is put into the "
712 "matching firmware mode — for example a Klipper SET_PRINT_MODE call or a "
713 "RepRapFirmware M567 — since the slicer only emits the Primary tool's "
714 "paths and the firmware drives the Copy / Mirror tools. Placeholders are "
715 "supported; use the edit button to browse them. Leave empty if the mode "
716 "needs no firmware setup."));
717 r.gcode->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { notify(); });
718
719 if (!is_primary)
720 name_col->Add(r.name, 0);
721 sizer->Add(name_col, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
722 sizer->Add(grid_panel, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
723 sizer->Add(gcode_frame, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
724
725 // Edit on top, per-row reset under it, in one column top-aligned with the G-code box.
726 // Remove is not here -- it sits under the name field, away from the button people press
727 // most. The column is top- rather than centre-justified so the edit icon keeps the same
728 // position whatever a row's height turns out to be.
729 auto* btn_col = new wxBoxSizer(wxVERTICAL);
730 wxTextCtrl* gcode_ctrl = r.gcode;
731 auto* ph_btn = new ScalableButton(r.panel, wxID_ANY, "edit", wxEmptyString,
732 wxDefaultSize, wxDefaultPosition,
733 wxBU_EXACTFIT | wxNO_BORDER, /*use_default_disabled_bitmap=*/true, kImexIconPx);
734 ph_btn->SetToolTip(_L("Edit G-code / browse placeholders"));
735 ph_btn->Bind(wxEVT_BUTTON, [this, gcode_ctrl](wxCommandEvent&) {
736 // EditGCodeDialog takes and returns UTF-8 (get_edited_gcode() is a ToUTF8()).
737 EditGCodeDialog dlg(this, "imex_mode_gcode", into_u8(gcode_ctrl->GetValue()));
738 if (dlg.ShowModal() == wxID_OK)
739 gcode_ctrl->SetValue(from_u8(dlg.get_edited_gcode()));
740 });
741 btn_col->Add(ph_btn, 0, wxBOTTOM, FromDIP(2));
742
743 if (!is_primary) {
744 auto* rm = new ScalableButton(r.panel, wxID_ANY, "delete", wxEmptyString,
745 wxDefaultSize, wxDefaultPosition,
746 wxBU_EXACTFIT | wxNO_BORDER, /*use_default_disabled_bitmap=*/true, kImexIconPx);
747 rm->SetToolTip(_L("Remove mode"));
748 rm->Bind(wxEVT_BUTTON, [this, this_panel](wxCommandEvent&) {
749 const std::vector<std::string> removed = remove_row(this_panel);
750 // Copied before notify(): on_change ends in on_value_change, which can send
751 // the tab through update() and hence load_from_config(), and that rebuilds
752 // every row. Reading the member afterwards would be reading through a
753 // handler whose owner has just been rebuilt underneath it.
754 auto removed_cb = on_mode_removed;
755 notify();
756 if (removed_cb && !removed.empty())
757 removed_cb(removed);
758 });
759 name_col->Add(rm, 0, wxALIGN_RIGHT | wxTOP, FromDIP(2));
760 }
761 // Reset only renders when this row has a counterpart in the saved preset (user-added rows
762 // beyond the saved mode count get no reset — remove covers "drop the row I just added").
763 wxPanel* this_panel_for_reset = r.panel;
764 if (row_has_parent_counterpart(static_cast<int>(m_rows.size()))) {
765 r.reset_btn = new ScalableButton(r.panel, wxID_ANY, "dot", wxEmptyString,
766 wxDefaultSize, wxDefaultPosition,
767 wxBU_EXACTFIT | wxNO_BORDER, /*use_default_disabled_bitmap=*/true, kImexIconPx);
768 r.reset_btn->SetToolTip(_L("Discard in-session edits to this mode (snap back to saved value)"));
769 r.reset_btn->Bind(wxEVT_BUTTON, [this, this_panel_for_reset](wxCommandEvent&) {
770 reset_row_to_parent(this_panel_for_reset);
771 });
772 btn_col->Add(r.reset_btn, 0, wxTOP, FromDIP(2));
773 }
774 sizer->Add(btn_col, 0, wxALIGN_TOP | wxRIGHT, FromDIP(4));
775 r.panel->SetSizerAndFit(sizer);
776
777 m_rows_sizer->Add(r.panel, 0, wxEXPAND | wxBOTTOM, FromDIP(4));
778 m_rows.push_back(std::move(r));
779 Layout();
780}
#define _L(s)
Definition OBJ.cpp:34
class WXDLLIMPEXP_FWD_CORE wxButton
Definition ProgressDialog.hpp:27
class WXDLLIMPEXP_FWD_CORE wxStaticText
Definition ProgressDialog.hpp:30
std::string unique_mode_name(const std::vector< std::string > &also_taken, const std::string &base="Mode") const
Definition IMEXModesCtrl.cpp:282
std::function< void(const std::vector< std::string > &)> on_mode_removed
Definition IMEXModesCtrl.hpp:69
static std::map< int, ImexRole > roles_for_mode(const std::string &active_tools)
Definition IMEXModesCtrl.cpp:264
bool role_allowed_on_tile(ImexRole role, int tool_idx, bool other_primary, int primary_gantry) const
Definition IMEXModesCtrl.cpp:367
void apply_btn(wxButton *btn, int tool_idx, std::optional< ImexRole > role)
Definition IMEXModesCtrl.cpp:427
bool is_primary
Definition IMEXModesCtrl.hpp:192
std::vector< std::string > remove_row(wxPanel *panel)
Definition IMEXModesCtrl.cpp:782
bool row_has_parent_counterpart(int row_idx) const
Definition IMEXModesCtrl.cpp:461
std::vector< Row > m_rows
Definition IMEXModesCtrl.hpp:260
void notify()
Definition IMEXModesCtrl.cpp:850
static std::optional< ImexRole > next_tile_role(std::optional< ImexRole > current, const std::function< bool(ImexRole)> &allowed)
Definition IMEXModesCtrl.cpp:380
void reset_row_to_parent(wxPanel *panel)
Definition IMEXModesCtrl.cpp:866
std::vector< wxString > suggested_mode_names(const ::ComboBox *skip=nullptr) const
Definition IMEXModesCtrl.cpp:398
Definition IMEXModesCtrl.hpp:175
wxMouseEvent
Definition CameraPopup.cpp:47
static std::string gcode(Type type)
Definition IMSlider.cpp:111
static wxStaticText * page_label(wxStaticText *lbl)
Definition IMEXModesCtrl.cpp:108
static constexpr int kNameColPx
Definition IMEXModesCtrl.cpp:45
static constexpr int kImexIconPx
Definition IMEXModesCtrl.cpp:41
std::string into_u8(const wxString &str)
Definition GUI.cpp:549
static wxPanel * framed_input(wxWindow *parent)
Definition IMEXModesCtrl.cpp:94
wxString from_u8(const std::string &str)
Definition GUI.cpp:544
ImexRole
Definition IMEXHelpers.hpp:345
@ Primary
Definition IMEXHelpers.hpp:345
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
constexpr const char * kImexPrimaryMode
Definition IMEXHelpers.hpp:27

References _L, ComboBox::Append(), Slic3r::f(), Slic3r::GUI::from_u8(), Slic3r::GUI::gcode(), Slic3r::GUI::IMEXModesCtrl::Row::is_primary, Slic3r::kImexPrimaryMode, Slic3r::GUI::kNameColPx, Slic3r::GUI::IMEXModesCtrl::Row::name, notify(), Slic3r::GUI::IMEXModesCtrl::Row::orig_name, Slic3r::GUI::page_label(), Slic3r::GUI::IMEXModesCtrl::Row::panel, suggested_mode_names(), unique_mode_name(), Slic3r::GUI::wxMouseEvent, and wxStaticText.

Referenced by load_from_config(), rebuild_info_and_header(), and set_grid_size().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_btn()

void Slic3r::GUI::IMEXModesCtrl::apply_btn ( wxButton * btn,
int tool_idx,
std::optional< ImexRole > role )
private
427 {
428 // Always label as T{n} — the button color already encodes the role.
429 const RoleStyle style = role_style(role);
430 btn->SetLabel(wxString::Format("T%d", tool_idx));
431 btn->SetBackgroundColour(style.bg);
432 btn->SetForegroundColour(style.fg);
433 btn->Refresh();
434}
#define style
Definition WipeTowerDialog.cpp:101
static RoleStyle role_style(std::optional< ImexRole > role)
Definition IMEXModesCtrl.cpp:321
Definition IMEXModesCtrl.hpp:151

References role_style(), style, and wxButton.

Referenced by reset_row_to_parent().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clear_rows()

void Slic3r::GUI::IMEXModesCtrl::clear_rows ( )
private
824 {
825 // Destroying a focused wxTextCtrl emits wxEVT_KILL_FOCUS, and the Name field's
826 // handler would then read m_rows while it is half-destroyed. Flag the teardown
827 // so that handler stays out of the way.
828 m_clearing_rows = true;
829 for (auto& r : m_rows) { m_rows_sizer->Detach(r.panel); r.panel->Destroy(); }
830 m_rows.clear();
831 m_clearing_rows = false;
832}
bool m_clearing_rows
Definition IMEXModesCtrl.hpp:263

References clear_rows(), m_clearing_rows, m_rows, and m_rows_sizer.

Referenced by clear_rows(), load_from_config(), and set_grid_size().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fix_row_name()

bool Slic3r::GUI::IMEXModesCtrl::fix_row_name ( Row & r)
private
298 {
299 if (r.is_primary || !r.name)
300 return false;
301 const std::string name = into_u8(r.name->GetTextCtrl()->GetValue());
302 // Case-insensitive: a row named "Primary" works, but its menu entry reads the same as the
303 // built-in Primary's.
304 const bool reserved = boost::iequals(name, kImexPrimaryMode);
305 bool taken = false;
306 for (const Row& other : m_rows)
307 if (&other != &r && !other.is_primary && other.name && into_u8(other.name->GetTextCtrl()->GetValue()) == name)
308 taken = true;
309 if (!name.empty() && !reserved && !taken)
310 return false;
311 std::string base = name;
312 // "copy 2" taken becomes "copy 3", not "copy 2 2".
313 if (const size_t sp = base.find_last_of(' '); sp != std::string::npos && sp > 0 && sp + 1 < base.size() &&
314 base.find_first_not_of("0123456789", sp + 1) == std::string::npos)
315 base.erase(sp);
316 // ChangeValue(), not SetValue(): no nested wxEVT_TEXT.
317 r.name->GetTextCtrl()->ChangeValue(from_u8(name.empty() || reserved ? unique_mode_name({}) : unique_mode_name({}, base)));
318 return true;
319}

References Slic3r::GUI::from_u8(), ComboBox::GetValue(), Slic3r::GUI::into_u8(), Slic3r::GUI::IMEXModesCtrl::Row::is_primary, Slic3r::kImexPrimaryMode, m_rows, Slic3r::GUI::IMEXModesCtrl::Row::name, and unique_mode_name().

Referenced by reset_row_to_parent().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_mode_data()

std::tuple< std::vector< std::string >, std::vector< std::string >, std::vector< std::string > > Slic3r::GUI::IMEXModesCtrl::get_mode_data ( ) const
235 {
236 std::vector<std::string> names, tools, gcodes;
237 for (auto& r : m_rows) {
238 // into_u8(), not ToStdString(): everything downstream of here -- the preset, the 3MF
239 // metadata, the plate's stored mode name -- is UTF-8, while ToStdString() encodes
240 // through wxConvLibc, which on Windows is the ANSI codepage. The two agree only on a
241 // UTF-8 locale, so mixing them turns a non-ASCII mode name into mojibake (or an empty
242 // field, once from_u8() rejects it) on the round trip through the preset.
243 std::string nm = r.is_primary ? std::string(kImexPrimaryMode) : into_u8(r.name->GetTextCtrl()->GetValue());
244 if (nm.empty()) nm = unique_mode_name(names);
245 names.push_back(nm);
246 tools.push_back(active_tools_string(r));
247 gcodes.push_back(into_u8(r.gcode->GetValue()));
248 }
249 return {names, tools, gcodes};
250}
std::string active_tools_string(const Row &r) const
Definition IMEXModesCtrl.cpp:834

References active_tools_string(), Slic3r::GUI::into_u8(), Slic3r::kImexPrimaryMode, m_rows, and unique_mode_name().

Referenced by matches_config(), and set_grid_size().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ load_from_config()

void Slic3r::GUI::IMEXModesCtrl::load_from_config ( const DynamicPrintConfig & cfg)
213 {
214 clear_rows();
215 // One ImexMode per row of imex_mode_names, with any sibling array too short for a row
216 // padded to an empty string -- exactly what the three per-index bounds checks this
217 // replaced were doing, now in one place shared with every other IMEX consumer.
218 const std::vector<ImexMode> table = imex_mode_table(cfg);
219
220 // Primary row is always first and non-deletable. Look for an existing
221 // "primary" entry in the config (present in configs saved after #8 was
222 // implemented); fall back to empty tool/gcode for older configs.
223 const ImexMode primary = find_imex_mode(cfg, kImexPrimaryMode);
224 add_row(kImexPrimaryMode, primary.active_tools, primary.gcode, /*is_primary=*/true);
225
226 for (const ImexMode& m : table) {
227 if (m.index == primary.index) continue; // already added above
228 add_row(m.name, m.active_tools, m.gcode);
229 }
231 Layout();
232}
void add_row(const std::string &name="", const std::string &active_tools="", const std::string &gcode="", bool is_primary=false)
Definition IMEXModesCtrl.cpp:469
void refresh_reset_buttons()
Definition IMEXModesCtrl.cpp:855
void clear_rows()
Definition IMEXModesCtrl.cpp:824
ImexMode find_imex_mode(const ConfigBase &cfg, const std::string &name)
Definition IMEXHelpers.cpp:665
std::vector< ImexMode > imex_mode_table(const ConfigBase &cfg)
Definition IMEXHelpers.cpp:680

References Slic3r::ImexMode::active_tools, add_row(), clear_rows(), Slic3r::find_imex_mode(), Slic3r::ImexMode::gcode, Slic3r::imex_mode_table(), Slic3r::ImexMode::index, Slic3r::kImexPrimaryMode, and refresh_reset_buttons().

Here is the call graph for this function:

◆ matches_config()

bool Slic3r::GUI::IMEXModesCtrl::matches_config ( const DynamicPrintConfig & cfg) const
252 {
253 auto [names, tools, gcodes] = get_mode_data();
254 auto cfg_strings = [&cfg](const char* key) {
255 std::vector<std::string> v;
256 if (auto* o = cfg.option<ConfigOptionStrings>(key)) v = o->values;
257 return v;
258 };
259 return names == cfg_strings("imex_mode_names")
260 && tools == cfg_strings("imex_mode_active_tools")
261 && gcodes == cfg_strings("imex_mode_gcodes");
262}
std::tuple< std::vector< std::string >, std::vector< std::string >, std::vector< std::string > > get_mode_data() const
Definition IMEXModesCtrl.cpp:235

References get_mode_data(), and Slic3r::ConfigBase::option().

Here is the call graph for this function:

◆ next_tile_role()

std::optional< ImexRole > Slic3r::GUI::IMEXModesCtrl::next_tile_role ( std::optional< ImexRole > current,
const std::function< bool(ImexRole)> & allowed )
staticprivate
381 {
382 const int n = (int)(sizeof(kImexRoleTable) / sizeof(kImexRoleTable[0]));
383 int start = 0; // no current role (Inactive) → start at the first role
384 if (current) {
385 for (int i = 0; i < n; ++i)
386 if (kImexRoleTable[i].role == *current) { start = i + 1; break; }
387 }
388 for (int i = start; i < n; ++i)
389 if (allowed(kImexRoleTable[i].role))
390 return kImexRoleTable[i].role;
391 return std::nullopt; // walked off the end → back to Inactive
392}
ImexRole role
Definition IMEXHelpers.hpp:361
constexpr ImexRoleDesc kImexRoleTable[]
Definition IMEXHelpers.hpp:364

References Slic3r::kImexRoleTable.

◆ notify()

void Slic3r::GUI::IMEXModesCtrl::notify ( )
private
850 {
852 if (on_change) on_change();
853}
std::function< void()> on_change
Definition IMEXModesCtrl.hpp:57

References notify(), on_change, and refresh_reset_buttons().

Referenced by add_row(), notify(), rebuild_info_and_header(), and reset_row_to_parent().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse_layout()

int Slic3r::GUI::IMEXModesCtrl::parse_layout ( ImexToolLayout layout)
inlinestatic
80{ return static_cast<int>(layout); }

Referenced by Slic3r::GUI::imex_grid_shape().

Here is the caller graph for this function:

◆ rebuild_info_and_header()

void Slic3r::GUI::IMEXModesCtrl::rebuild_info_and_header ( )
private
115 {
116 // --- Info panel: help button + color legend ---
117 m_info_panel->DestroyChildren();
118 auto* info_sizer = new wxBoxSizer(wxVERTICAL);
119
120 // The overview rides on the "?" button at the head of the legend rather than sitting in
121 // the panel as body text: it is read once and then only gets in the way, while the legend
122 // beside it is the part worth keeping on screen. Per-role detail is on the swatches.
123 const wxString instructions =
124 _L("Each mode names the tool heads that take part and the role each one plays. "
125 "Click a tool button to cycle its role — hover a color below for what each role does.");
126
127 // Color legend — swatches sized to the body text height so they read as
128 // matched pairs with their labels regardless of system DPI / font scale,
129 // matching the on-hover ghost tooltip swatch's visual weight.
130 // One entry per role this printer offers, in kImexRoleTable order — the same order the
131 // tiles cycle in. A role added to that table appears here with no edit of its own.
132 auto* leg_sizer = new wxBoxSizer(wxHORIZONTAL);
133 const int swatch_side = m_info_panel->GetCharHeight();
134
135 // Action row: Add Mode, then the "?" carrying the overview. Width-matched to the mode
136 // column so the button sits over the field it creates.
137 auto* act_sizer = new wxBoxSizer(wxHORIZONTAL);
138 auto* add_btn = new Button(m_info_panel, _L("Add Mode"));
139 add_btn->SetStyle(ButtonStyle::Confirm, ButtonType::Window);
140 // Width-matched to the mode column. The height has to be a real number: Button takes its
141 // min size literally, so a -1 there collapses it to a sliver rather than meaning "auto".
142 add_btn->SetMinSize(wxSize(FromDIP(130), add_btn->GetBestSize().GetHeight()));
143 add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { add_row(); notify(); });
144 act_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
145
146 auto* help_btn = new ScalableButton(m_info_panel, wxID_ANY, "icon_qusetion", wxEmptyString,
147 wxDefaultSize, wxDefaultPosition,
148 wxBU_EXACTFIT | wxNO_BORDER, /*use_default_disabled_bitmap=*/true, kImexIconPx);
149 help_btn->SetToolTip(instructions);
150 act_sizer->Add(help_btn, 0, wxALIGN_CENTER_VERTICAL);
151 info_sizer->Add(act_sizer, 0, wxBOTTOM, FromDIP(6));
152 for (const ImexRoleDesc& d : kImexRoleTable) {
153 if (!role_offered(d.role)) continue;
154 const RoleStyle style = role_style(d.role);
155 auto* swatch = new wxPanel(m_info_panel, wxID_ANY, wxDefaultPosition, wxSize(swatch_side, swatch_side));
156 swatch->SetBackgroundColour(style.bg);
157 auto* leg_label = page_label(new wxStaticText(m_info_panel, wxID_ANY, style.label));
158 const wxString hint = role_hint(d.role);
159 swatch->SetToolTip(hint);
160 leg_label->SetToolTip(hint);
161 leg_sizer->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
162 leg_sizer->Add(leg_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(16));
163 }
164 info_sizer->Add(leg_sizer, 0, wxBOTTOM, FromDIP(6));
165 m_info_panel->SetSizerAndFit(info_sizer);
166
167 // --- Column header row ---
168 // Spacers sized to align with the mode-row fields below. Name field: FromDIP(130) + FromDIP(6)
169 // gap; tool grid: n_cols*(FromDIP(24)+FromDIP(2))-FromDIP(2) + FromDIP(6) gap. grid_px tracks
170 // the live column count, so the G-code header stays aligned after a grid change.
171 m_hdr_panel->DestroyChildren();
172 auto* hdr_sizer = new wxBoxSizer(wxHORIZONTAL);
173 auto* hdr_name = page_label(new wxStaticText(m_hdr_panel, wxID_ANY, _L("Mode")));
174 auto* hdr_tools = page_label(new wxStaticText(m_hdr_panel, wxID_ANY, _L("Tools")));
175 auto* hdr_gcode = page_label(new wxStaticText(m_hdr_panel, wxID_ANY, _L("G-code")));
176 hdr_name->SetToolTip(_L("How the mode is labeled in the plate's IDEX/IQEX mode selector. "
177 "Required — a mode with no name cannot be selected."));
178 hdr_tools->SetToolTip(_L("Which tool heads take part in the mode and what role each one plays. "
179 "Click a tile to cycle its role."));
180 hdr_gcode->SetToolTip(_L("G-code run at print start to put the printer into this mode "
181 "(for example a Klipper SET_PRINT_MODE call). The slicer only emits "
182 "the Primary tool's paths; the firmware drives the others."));
183 const int grid_px = m_n_cols * FromDIP(26) - FromDIP(2); // tile 24 + 2px gap, less the trailing gap
184 const int name_col_px = FromDIP(kNameColPx + 6);
185 hdr_sizer->Add(hdr_name, 0, wxALIGN_CENTER_VERTICAL);
186 hdr_sizer->AddSpacer(std::max(0, name_col_px - hdr_name->GetBestSize().x));
187 hdr_sizer->Add(hdr_tools, 0, wxALIGN_CENTER_VERTICAL);
188 hdr_sizer->AddSpacer(std::max(0, grid_px + FromDIP(6) - hdr_tools->GetBestSize().x));
189 hdr_sizer->Add(hdr_gcode, 1, wxALIGN_CENTER_VERTICAL);
190 m_hdr_panel->SetSizerAndFit(hdr_sizer);
191}
@ Confirm
Definition Button.hpp:25
@ Window
Definition Button.hpp:32
static wxString role_hint(ImexRole role)
Definition IMEXModesCtrl.cpp:341
bool role_offered(ImexRole role) const
Definition IMEXModesCtrl.cpp:359

References _L, add_row(), Confirm, Slic3r::GUI::kImexIconPx, Slic3r::kImexRoleTable, Slic3r::GUI::kNameColPx, m_hdr_panel, m_info_panel, m_n_cols, notify(), Slic3r::GUI::page_label(), role_hint(), role_offered(), role_style(), style, Window, and wxStaticText.

Referenced by IMEXModesCtrl(), and set_grid_size().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ refresh_reset_buttons()

void Slic3r::GUI::IMEXModesCtrl::refresh_reset_buttons ( )
private
855 {
856 for (size_t i = 0; i < m_rows.size(); ++i) {
857 Row& r = m_rows[i];
858 if (!r.reset_btn || !r.reset_btn->IsEnabled()) continue;
859 const bool dirty = row_differs_from_parent(static_cast<int>(i));
860 if (dirty == r.reset_dirty_cached) continue;
861 r.reset_dirty_cached = dirty;
862 r.reset_btn->SetBitmap_(dirty ? "undo" : "dot");
863 }
864}
bool row_differs_from_parent(int row_idx) const
Definition IMEXModesCtrl.cpp:445

References m_rows, refresh_reset_buttons(), Slic3r::GUI::IMEXModesCtrl::Row::reset_btn, Slic3r::GUI::IMEXModesCtrl::Row::reset_dirty_cached, row_differs_from_parent(), and ScalableButton::SetBitmap_().

Referenced by load_from_config(), notify(), and refresh_reset_buttons().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ remove_row()

std::vector< std::string > Slic3r::GUI::IMEXModesCtrl::remove_row ( wxPanel * panel)
private
782 {
783 for (size_t i = 0; i < m_rows.size(); ++i) {
784 if (m_rows[i].panel != panel) continue;
785 if (m_rows[i].is_primary) return {}; // primary row is non-deletable
786
787 // Both names a plate can be holding for this row: the one the row was built with and
788 // the one in the field now. They diverge as soon as the row is renamed, because a
789 // rename does not rebuild the row and is deliberately not reported as a removal (the
790 // name field notifies on every keystroke). Reporting only the current name is what
791 // let "rename, then delete" leave the plate on a mode that no longer exists.
792 std::vector<std::string> removed_names;
793 const std::string current = m_rows[i].name ? into_u8(m_rows[i].name->GetTextCtrl()->GetValue())
794 : std::string();
795 for (const std::string& n : {m_rows[i].orig_name, current})
796 if (!n.empty() && std::find(removed_names.begin(), removed_names.end(), n) == removed_names.end())
797 removed_names.push_back(n);
798
799 m_rows_sizer->Detach(panel);
800 m_rows.erase(m_rows.begin() + i);
801
802 // A name a surviving row still carries is not stale: the user can rename row A to
803 // row B's old name and then delete B. Plates holding it now resolve to A, so drop it
804 // rather than reset them.
805 auto still_in_use = [this](const std::string& n) {
806 return std::any_of(m_rows.begin(), m_rows.end(), [&n](const Row& r) {
807 return r.is_primary ? n == kImexPrimaryMode
808 : r.name && into_u8(r.name->GetTextCtrl()->GetValue()) == n;
809 });
810 };
811 removed_names.erase(std::remove_if(removed_names.begin(), removed_names.end(), still_in_use),
812 removed_names.end());
813
814 Layout();
815 // Defer widget destruction so any in-flight GTK events for
816 // panel's children (including the remove button we're inside) finish
817 // processing before the wxEvtHandlers are freed.
818 wxTheApp->CallAfter([panel]() { panel->Destroy(); });
819 return removed_names;
820 }
821 return {};
822}

References Slic3r::into_u8(), m_rows, m_rows_sizer, and remove_row().

Referenced by remove_row().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ reset_row_to_parent()

void Slic3r::GUI::IMEXModesCtrl::reset_row_to_parent ( wxPanel * panel)
private
866 {
867 if (!m_parent_lookup) return;
868 const DynamicPrintConfig* parent = m_parent_lookup();
869 if (!parent) return;
870 auto* p_names = parent->option<ConfigOptionStrings>("imex_mode_names");
871 auto* p_tools = parent->option<ConfigOptionStrings>("imex_mode_active_tools");
872 auto* p_gcodes = parent->option<ConfigOptionStrings>("imex_mode_gcodes");
873 for (size_t i = 0; i < m_rows.size(); ++i) {
874 Row& r = m_rows[i];
875 if (r.panel != panel) continue;
876 if (!p_names || i >= p_names->values.size()) return;
877 if (!r.is_primary && r.name) {
878 r.name->GetTextCtrl()->ChangeValue(from_u8(p_names->values[i]));
879 // The saved name may since have been given to another row, which keeps it.
880 fix_row_name(r);
881 }
882 if (p_gcodes && i < p_gcodes->values.size())
883 r.gcode->ChangeValue(from_u8(p_gcodes->values[i]));
884 if (p_tools && i < p_tools->values.size()) {
885 // Reapply tool roles from the parent's serialized form. all_tool_roles
886 // is the source of truth for round-tripping; rebuild it then re-paint
887 // the visible buttons.
888 r.all_tool_roles = roles_for_mode(p_tools->values[i]);
889 for (size_t j = 0; j < r.btns.size(); ++j) {
890 int tidx = r.btn_tool_idx[j];
891 auto it = r.all_tool_roles.find(tidx);
892 std::optional<ImexRole> role;
893 if (it != r.all_tool_roles.end()) role = it->second;
894 r.btn_roles[j] = role;
895 apply_btn(r.btns[j], tidx, role);
896 }
897 }
898 notify();
899 return;
900 }
901}
ParentConfigLookup m_parent_lookup
Definition IMEXModesCtrl.hpp:264
bool fix_row_name(Row &r)
Definition IMEXModesCtrl.cpp:298

References Slic3r::GUI::IMEXModesCtrl::Row::all_tool_roles, apply_btn(), Slic3r::GUI::IMEXModesCtrl::Row::btn_roles, Slic3r::GUI::IMEXModesCtrl::Row::btn_tool_idx, Slic3r::GUI::IMEXModesCtrl::Row::btns, fix_row_name(), Slic3r::from_u8(), Slic3r::GUI::IMEXModesCtrl::Row::gcode, Slic3r::GUI::IMEXModesCtrl::Row::is_primary, m_parent_lookup, m_rows, Slic3r::GUI::IMEXModesCtrl::Row::name, notify(), Slic3r::ConfigBase::option(), Slic3r::GUI::IMEXModesCtrl::Row::panel, reset_row_to_parent(), roles_for_mode(), Slic3r::ConfigOptionVector< T >::size(), and Slic3r::ConfigOptionVector< T >::values.

Referenced by reset_row_to_parent().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ role_allowed_on_tile()

bool Slic3r::GUI::IMEXModesCtrl::role_allowed_on_tile ( ImexRole role,
int tool_idx,
bool other_primary,
int primary_gantry ) const
private
368 {
369 if (!role_offered(role))
370 return false;
371 // Exactly one tile drives the sliced paths.
372 if (role == ImexRole::Primary)
373 return !other_primary;
374 // Span only on a tile sharing the Primary's gantry — that is what the marker declares.
375 if (role == ImexRole::Span)
376 return primary_gantry >= 0 && (tool_idx / m_n_cols) == primary_gantry;
377 return true;
378}
@ Span
Definition IMEXHelpers.hpp:345

References m_n_cols, Slic3r::Primary, role_offered(), and Slic3r::Span.

Here is the call graph for this function:

◆ role_hint()

wxString Slic3r::GUI::IMEXModesCtrl::role_hint ( ImexRole role)
staticprivate
341 {
342 switch (role) {
344 return _L("Drives every sliced path. Always tool 0 — use Tool 0 Position above to choose "
345 "which corner of the bed it occupies. It cannot be moved from the grid.");
346 case ImexRole::Copy:
347 return _L("Follows the Primary at the firmware level, printing the same paths offset into "
348 "its own zone.");
349 case ImexRole::Mirror:
350 return _L("Follows the Primary at the firmware level, printing the same paths reflected "
351 "across the boundary between the two zones.");
352 case ImexRole::Span:
353 return _L("Marks a tool on the Primary's gantry as its multicolor partner. Required to "
354 "allow multi-color printing in paired-gantry modes.");
355 }
356 return wxEmptyString;
357}
@ Mirror
Definition IMEXHelpers.hpp:345
@ Copy
Definition IMEXHelpers.hpp:345

References _L, Slic3r::Copy, Slic3r::Mirror, Slic3r::Primary, and Slic3r::Span.

Referenced by rebuild_info_and_header().

Here is the caller graph for this function:

◆ role_offered()

bool Slic3r::GUI::IMEXModesCtrl::role_offered ( ImexRole role) const
private
359 {
360 // Span declares a within-gantry multicolor partner of the Primary, which only means
361 // something when there is a second gantry running in parallel with it.
362 if (role == ImexRole::Span)
363 return m_n_rows >= 2;
364 return true;
365}

References m_n_rows, and Slic3r::Span.

Referenced by rebuild_info_and_header(), and role_allowed_on_tile().

Here is the caller graph for this function:

◆ role_style()

IMEXModesCtrl::RoleStyle Slic3r::GUI::IMEXModesCtrl::role_style ( std::optional< ImexRole > role)
staticprivate
321 {
322 // Gray / "Inactive" is the no-role answer; every role gets an explicit case so a new
323 // one is a compile-time -Wswitch prompt rather than a tile that silently renders gray.
324 // TRN IDEX/IQEX role of a tool head that takes no part in the mode.
325 const wxString inactive = _L_CONTEXT("Inactive", "IDEX/IQEX role");
326 if (!role)
327 return { wxColour(90, 90, 90), *wxWHITE, inactive };
328 switch (*role) {
329 // TRN IDEX/IQEX role: the tool head the slicer generates paths for.
330 case ImexRole::Primary: return { wxColour(50, 160, 50), *wxWHITE, _L_CONTEXT("Primary", "IDEX/IQEX role") }; // green
331 // TRN IDEX/IQEX role: the tool head prints a copy of the Primary's paths.
332 case ImexRole::Copy: return { wxColour(60, 120, 210), *wxWHITE, _L_CONTEXT("Copy", "IDEX/IQEX role") }; // blue
333 // TRN IDEX/IQEX role: the tool head prints a mirror image of the Primary's paths.
334 case ImexRole::Mirror: return { wxColour(210, 130, 20), *wxWHITE, _L_CONTEXT("Mirror", "IDEX/IQEX role") }; // amber
335 // TRN IDEX/IQEX role: multicolor partner of the Primary on the same gantry.
336 case ImexRole::Span: return { wxColour(180, 180, 40), *wxWHITE, _L_CONTEXT("Span", "IDEX/IQEX role") }; // yellow
337 }
338 return { wxColour(90, 90, 90), *wxWHITE, inactive }; // keeps every compiler quiet
339}
#define _L_CONTEXT(s, ctx)
Definition I18N.hpp:15

References _L_CONTEXT, Slic3r::Copy, Slic3r::Mirror, Slic3r::Primary, and Slic3r::Span.

Referenced by apply_btn(), and rebuild_info_and_header().

Here is the caller graph for this function:

◆ roles_for_mode()

std::map< int, ImexRole > Slic3r::GUI::IMEXModesCtrl::roles_for_mode ( const std::string & active_tools)
staticprivate
264 {
265 std::map<int, ImexRole> roles;
266 for (const auto& [phys, role] : parse_imex_active_tools(active_tools))
267 roles[phys] = role;
268 // Promote the mode's primary, but never invent a tool that isn't in the
269 // slicer's own active-tool list: parse_imex_active_tools() drops negative and
270 // out-of-range indices, imex_primary_tool_for_mode() does not range-check the
271 // upper bound, and a junk index round-tripped back out on save would be a
272 // token the slicer ignores.
273 const int primary = imex_primary_tool_for_mode(active_tools);
274 if (primary >= 0) {
275 auto it = roles.find(primary);
276 if (it != roles.end())
277 it->second = ImexRole::Primary;
278 }
279 return roles;
280}
int imex_primary_tool_for_mode(const std::string &active_tools_for_mode)
Definition IMEXHelpers.cpp:422
std::vector< std::pair< int, ImexRole > > parse_imex_active_tools(const std::string &active_tools_for_mode)
Definition IMEXHelpers.cpp:327

References Slic3r::imex_primary_tool_for_mode(), Slic3r::parse_imex_active_tools(), and Slic3r::Primary.

Referenced by reset_row_to_parent().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ row_differs_from_parent()

bool Slic3r::GUI::IMEXModesCtrl::row_differs_from_parent ( int row_idx) const
private
445 {
446 if (!m_parent_lookup) return false;
447 const DynamicPrintConfig* parent = m_parent_lookup();
448 if (!parent) return false;
449 auto* p_names = parent->option<ConfigOptionStrings>("imex_mode_names");
450 auto* p_tools = parent->option<ConfigOptionStrings>("imex_mode_active_tools");
451 auto* p_gcodes = parent->option<ConfigOptionStrings>("imex_mode_gcodes");
452 if (!p_names || row_idx < 0 || row_idx >= (int)p_names->values.size()) return false;
453 const Row& r = m_rows[row_idx];
454 const RowSnapshot s = snapshot_row(r);
455 if (!r.is_primary && s.name != p_names->values[row_idx]) return true;
456 if (p_tools && row_idx < (int)p_tools->values.size() && s.tools != p_tools->values[row_idx]) return true;
457 if (p_gcodes && row_idx < (int)p_gcodes->values.size() && s.gcode != p_gcodes->values[row_idx]) return true;
458 return false;
459}
RowSnapshot snapshot_row(const Row &r) const
Definition IMEXModesCtrl.cpp:436
Definition IMEXModesCtrl.hpp:198

References Slic3r::GUI::IMEXModesCtrl::RowSnapshot::gcode, Slic3r::GUI::IMEXModesCtrl::Row::is_primary, m_parent_lookup, m_rows, Slic3r::GUI::IMEXModesCtrl::RowSnapshot::name, Slic3r::ConfigBase::option(), snapshot_row(), Slic3r::GUI::IMEXModesCtrl::RowSnapshot::tools, and Slic3r::ConfigOptionVector< T >::values.

Referenced by refresh_reset_buttons().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ row_has_parent_counterpart()

bool Slic3r::GUI::IMEXModesCtrl::row_has_parent_counterpart ( int row_idx) const
private
461 {
462 if (!m_parent_lookup) return false;
463 const DynamicPrintConfig* parent = m_parent_lookup();
464 if (!parent) return false;
465 auto* p_names = parent->option<ConfigOptionStrings>("imex_mode_names");
466 return p_names && row_idx >= 0 && row_idx < (int)p_names->values.size();
467}

References m_parent_lookup, Slic3r::ConfigBase::option(), and Slic3r::ConfigOptionVector< T >::values.

Here is the call graph for this function:

◆ set_applicable()

void Slic3r::GUI::IMEXModesCtrl::set_applicable ( bool applicable)
inline
109 {
110 m_applicable = applicable;
111 if (!applicable)
112 wxPanel::Show(false); // showing is left to the group, which knows the mode
113 }
bool m_applicable
Definition IMEXModesCtrl.hpp:117

References m_applicable.

◆ set_grid_size()

void Slic3r::GUI::IMEXModesCtrl::set_grid_size ( int n_cols,
int n_rows,
int layout = -1 )
193 {
194 n_cols = std::max(1, n_cols);
195 n_rows = std::max(1, n_rows);
196 if (layout < 0) layout = m_layout;
197 if (n_cols == m_n_cols && n_rows == m_n_rows && layout == m_layout) return;
198 auto [names, tools, gcodes] = get_mode_data();
199 clear_rows();
200 m_n_cols = n_cols;
201 m_n_rows = n_rows;
202 m_layout = layout;
203 // Span becomes available the moment a second gantry exists, and the header spacing tracks the
204 // column count -- so the legend and headers are rebuilt here rather than only at construction.
205 // Without this, adding a gantry gives the tiles a Span role the legend never explains until
206 // the preset is saved and the page reopened.
208 for (size_t i = 0; i < names.size(); ++i)
209 add_row(names[i], tools[i], gcodes[i], /*is_primary=*/(names[i] == kImexPrimaryMode));
210 Layout();
211}

References add_row(), clear_rows(), get_mode_data(), Slic3r::kImexPrimaryMode, m_layout, m_n_cols, m_n_rows, and rebuild_info_and_header().

Here is the call graph for this function:

◆ set_parent_config_lookup()

void Slic3r::GUI::IMEXModesCtrl::set_parent_config_lookup ( ParentConfigLookup f)
inline
75{ m_parent_lookup = std::move(f); }

References Slic3r::f(), and m_parent_lookup.

Here is the call graph for this function:

◆ Show()

bool Slic3r::GUI::IMEXModesCtrl::Show ( bool show = true)
inlineoverride
114{ return wxPanel::Show(show && m_applicable); }

References m_applicable.

◆ snapshot_row()

IMEXModesCtrl::RowSnapshot Slic3r::GUI::IMEXModesCtrl::snapshot_row ( const Row & r) const
private
436 {
437 RowSnapshot s;
438 // UTF-8 throughout -- this snapshot is compared against the preset's own values.
439 s.name = r.is_primary ? std::string(kImexPrimaryMode) : into_u8(r.name->GetTextCtrl()->GetValue());
440 s.tools = active_tools_string(r);
441 s.gcode = into_u8(r.gcode->GetValue());
442 return s;
443}
std::string name
Definition IMEXModesCtrl.hpp:198

References active_tools_string(), Slic3r::GUI::IMEXModesCtrl::Row::gcode, Slic3r::GUI::IMEXModesCtrl::RowSnapshot::gcode, ComboBox::GetValue(), Slic3r::GUI::into_u8(), Slic3r::GUI::IMEXModesCtrl::Row::is_primary, Slic3r::kImexPrimaryMode, Slic3r::GUI::IMEXModesCtrl::Row::name, Slic3r::GUI::IMEXModesCtrl::RowSnapshot::name, and Slic3r::GUI::IMEXModesCtrl::RowSnapshot::tools.

Referenced by row_differs_from_parent().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ suggested_mode_names()

std::vector< wxString > Slic3r::GUI::IMEXModesCtrl::suggested_mode_names ( const ::ComboBox * skip = nullptr) const
private
398 {
399 std::vector<std::string> pool = { "copy", "mirror" };
400 // Four carriages to drive, however they are arranged.
401 if (m_n_cols * m_n_rows >= 4)
402 for (const char* n : { "iq-copy", "iq-mirror" })
403 pool.emplace_back(n);
404 // Multicolor needs a Span partner beside the primary, so two tools on a gantry, and a
405 // second gantry to copy the pair onto. imex_resolve_routing() refuses a multicolor mode
406 // with no Span on the primary's gantry, so offering one here that it would reject is
407 // worse than not offering it at all.
408 if (m_n_cols >= 2 && m_n_rows >= 2)
409 for (const char* n : { "mc-copy", "mc-mirror" })
410 pool.emplace_back(n);
411
412 std::vector<wxString> out;
413 for (const std::string& cand : pool) {
414 bool taken = false;
415 for (const Row& r : m_rows)
416 if (!r.is_primary && r.name && r.name != skip &&
417 into_u8(r.name->GetTextCtrl()->GetValue()) == cand) {
418 taken = true;
419 break;
420 }
421 if (!taken)
422 out.push_back(from_u8(cand));
423 }
424 return out;
425}

References Slic3r::GUI::from_u8(), Slic3r::GUI::into_u8(), m_n_cols, m_n_rows, and m_rows.

Referenced by add_row().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ unique_mode_name()

std::string Slic3r::GUI::IMEXModesCtrl::unique_mode_name ( const std::vector< std::string > & also_taken,
const std::string & base = "Mode" ) const
private
282 {
283 auto is_taken = [&](const std::string& cand) {
284 if (std::find(also_taken.begin(), also_taken.end(), cand) != also_taken.end())
285 return true;
286 for (const auto& r : m_rows)
287 if (!r.is_primary && r.name && into_u8(r.name->GetTextCtrl()->GetValue()) == cand)
288 return true;
289 return false;
290 };
291 for (int n = 2; ; ++n) {
292 std::string cand = base + " " + std::to_string(n);
293 if (!is_taken(cand))
294 return cand;
295 }
296}

References Slic3r::GUI::into_u8(), and m_rows.

Referenced by add_row(), fix_row_name(), and get_mode_data().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ m_applicable

bool Slic3r::GUI::IMEXModesCtrl::m_applicable = true
private

Referenced by set_applicable(), and Show().

◆ m_clearing_rows

bool Slic3r::GUI::IMEXModesCtrl::m_clearing_rows {false}
private
263{false}; // guards handlers against teardown-time events

Referenced by clear_rows().

◆ m_hdr_panel

wxPanel* Slic3r::GUI::IMEXModesCtrl::m_hdr_panel = nullptr
private

◆ m_info_panel

wxPanel* Slic3r::GUI::IMEXModesCtrl::m_info_panel = nullptr
private

◆ m_layout

int Slic3r::GUI::IMEXModesCtrl::m_layout {0}
private
262{0}; // 0=front-left, 1=front-right, 2=rear-left, 3=rear-right

Referenced by IMEXModesCtrl(), and set_grid_size().

◆ m_n_cols

int Slic3r::GUI::IMEXModesCtrl::m_n_cols
private

◆ m_n_rows

int Slic3r::GUI::IMEXModesCtrl::m_n_rows
private

◆ m_outer

wxBoxSizer* Slic3r::GUI::IMEXModesCtrl::m_outer
private

Referenced by IMEXModesCtrl().

◆ m_parent_lookup

ParentConfigLookup Slic3r::GUI::IMEXModesCtrl::m_parent_lookup
private

◆ m_rows

◆ m_rows_sizer

wxBoxSizer* Slic3r::GUI::IMEXModesCtrl::m_rows_sizer
private

◆ on_change

std::function<void()> Slic3r::GUI::IMEXModesCtrl::on_change

Referenced by notify().

◆ on_mode_removed

std::function<void(const std::vector<std::string>&)> Slic3r::GUI::IMEXModesCtrl::on_mode_removed

The documentation for this class was generated from the following files: