473{
476 r.panel = new wxPanel(this, wxID_ANY);
477 r.panel->SetBackgroundColour(GetBackgroundColour());
478 auto* sizer = new wxBoxSizer(wxHORIZONTAL);
479
480
481
482 auto* name_col = new wxBoxSizer(wxVERTICAL);
483
484 if (is_primary) {
486 r.name = nullptr;
488 wxDefaultPosition, FromDIP(wxSize(
kNameColPx, -1))));
489 wxFont
f = lbl->GetFont();
490 f.SetWeight(wxFONTWEIGHT_BOLD);
492 name_col->Add(lbl, 0);
493 } else {
494
495
496
497
498
500
501
502
503 r.orig_name = nm;
504
505
506
507
508 r.name = new ::ComboBox(r.panel, wxID_ANY, wxEmptyString, wxDefaultPosition,
509 FromDIP(wxSize(
kNameColPx, -1)), 0,
nullptr, 0);
511 r.name->Append(sug);
512
513
514
515
516
517
518
519
520
521 const auto on_open_click = [
this, cb = r.name](
wxMouseEvent& e) {
522 if (!cb->is_drop_down()) {
523 cb->Clear();
525 cb->Append(sug);
526 if (cb->GetCount() == 0) {
527 cb->SetFocus();
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
536
537
538
539
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
548
549 r.name->Bind(wxEVT_COMBOBOX, [this, cb = r.name](wxCommandEvent& e) {
550
551
552
553
554
555
556
557 cb->GetTextCtrl()->ChangeValue(cb->GetDropDown().GetValue());
558 notify();
559 });
560
561
562
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;
572 for (auto& row_ref : m_rows) {
573 if (row_ref.panel != panel) continue;
574
575
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
591 if (tool_roles.empty())
593 r.all_tool_roles = tool_roles;
594 wxPanel* this_panel = r.panel;
595
596
597
598
599
600
601
602
603
604
605
606
607
608
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;
617 auto it = tool_roles.find(tool_idx);
618 if (it != tool_roles.end()) role = it->second;
619
620
621
622
623 auto* btn =
new wxButton(grid_panel, wxID_ANY, wxEmptyString,
624 wxDefaultPosition, FromDIP(wxSize(24, 24)), wxBU_EXACTFIT | wxBU_AUTODRAW);
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
633
634
635
636
637
638
639
640
641
642
643
644
646 btn->Disable();
647 } else {
648 btn->Bind(wxEVT_BUTTON, [this, this_panel, btn_pos](wxCommandEvent&) {
649
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
656
657 bool other_primary = false;
658 int primary_gantry = -1;
659 for (int j = 0; j < (int)row_ref.btn_roles.size(); ++j) {
661 if (j != btn_pos) other_primary = true;
662 primary_gantry = row_ref.btn_tool_idx[j] /
m_n_cols;
663 }
664 }
665
666
667
670 });
671
672
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);
680 break;
681 }
682 });
683 }
684
685 grid_sizer->Add(btn, 0);
686 }
687 }
688 grid_panel->SetSizerAndFit(grid_sizer);
689
690
691
693
694
695
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
701
702
703 r.gcode->SetFont(wxGetApp().code_font());
704 r.gcode->SetBackgroundColour(*wxWHITE);
705 r.gcode->SetForegroundColour(wxColour("#262E30"));
706 wxGetApp().UpdateDarkUI(r.gcode);
707
708
709
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
726
727
728
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,
734 ph_btn->SetToolTip(
_L(
"Edit G-code / browse placeholders"));
735 ph_btn->Bind(wxEVT_BUTTON, [this, gcode_ctrl](wxCommandEvent&) {
736
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,
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
751
752
753
756 if (removed_cb && !removed.empty())
757 removed_cb(removed);
758 });
759 name_col->Add(rm, 0, wxALIGN_RIGHT | wxTOP, FromDIP(2));
760 }
761
762
763 wxPanel* this_panel_for_reset = r.panel;
765 r.reset_btn = new ScalableButton(r.panel, wxID_ANY, "dot", wxEmptyString,
766 wxDefaultSize, wxDefaultPosition,
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&) {
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