From 94ba271f8067456d317b6fe236cb1132637fa422 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 29 Jun 2026 21:44:24 -0400 Subject: [PATCH 01/24] Keybindings dialog overhaul: friendly names, shortcut column, descriptions, filter, All scope, modeless, popup editor - Action column shows humanized friendly names (override map for audio speeds etc.); single Shortcut column renders the real chord - All ~127 bindings get richer descriptions, shown in Details + tooltip (tooltip also shows the raw enum) - Zebra striping; Details column stretches to fill on resize - Live filter (tokenized AND); 'All' scope (default) shows every binding - Modeless window (reuse-by-name; fixes a wxDynamicCast false-match that showed the Tip-of-the-Day dialog) - Editing via a popup editor (Edit button/double-click/Enter); Edit disabled when nothing selected; popup leads with name + description - Edit applies in place (no property-grid rebuild/flash) Reference branch for embedding into the modernized Preferences dialog. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/app-shell/KeyBindingEditDialog.cpp | 441 ++++++++++++++++--- src-ui-wx/app-shell/KeyBindingEditDialog.h | 24 + src-ui-wx/app-shell/KeyBindings.cpp | 260 +++++------ src-ui-wx/xLightsMain.cpp | 19 +- 4 files changed, 561 insertions(+), 183 deletions(-) diff --git a/src-ui-wx/app-shell/KeyBindingEditDialog.cpp b/src-ui-wx/app-shell/KeyBindingEditDialog.cpp index b43abb7fc4..e2b74c8832 100644 --- a/src-ui-wx/app-shell/KeyBindingEditDialog.cpp +++ b/src-ui-wx/app-shell/KeyBindingEditDialog.cpp @@ -17,6 +17,18 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "KeyBindingEditDialog.h" #include "KeyBindings.h" @@ -42,6 +54,137 @@ BEGIN_EVENT_TABLE(KeyBindingEditDialog,wxDialog) //*) END_EVENT_TABLE() +namespace { +// Modal editor for a single key binding. Edits native controls; ApplyTo() writes +// the result back to the live binding only when the user accepts (wxID_OK). +class KeyBindingPopupEditor : public wxDialog +{ +public: + KeyBindingPopupEditor(wxWindow* parent, const KeyBinding& b, EffectManager* em, xLightsFrame* xl) + : wxDialog(parent, wxID_ANY, _("Edit Shortcut"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + _type(b.GetType()) + { + auto* grid = new wxFlexGridSizer(0, 2, 6, 10); + grid->AddGrowableCol(1); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Key:")), 0, wxALIGN_CENTER_VERTICAL); + _key = new wxChoice(this, wxID_ANY); + _key->Append(_("(none)")); + int sel = 0; + int k = b.GetKey(); + if (k >= 'A' && k <= 'Z') k += 32; + for (const auto& it : KeyBinding::GetPossibleKeys()) { + _key->Append(KeyBinding::EncodeKey(it, false)); + _keys.push_back(it); + if (it == k) sel = (int)_keys.size(); + } + _key->SetSelection(sel); + grid->Add(_key, 1, wxEXPAND); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Modifiers:")), 0, wxALIGN_TOP | wxTOP, 4); + auto* mods = new wxBoxSizer(wxVERTICAL); +#ifdef __WXOSX__ + _ctrl = new wxCheckBox(this, wxID_ANY, L"Command ⌘"); + _alt = new wxCheckBox(this, wxID_ANY, L"Option ⌥"); + _shift = new wxCheckBox(this, wxID_ANY, L"Shift ⇧"); + _rctrl = new wxCheckBox(this, wxID_ANY, L"Control ⌃"); +#else + _ctrl = new wxCheckBox(this, wxID_ANY, _("Control")); + _alt = new wxCheckBox(this, wxID_ANY, _("Alt")); + _shift = new wxCheckBox(this, wxID_ANY, _("Shift")); + _rctrl = new wxCheckBox(this, wxID_ANY, _("macOS Ctrl")); +#endif + _ctrl->SetValue(b.RequiresControl()); + _alt->SetValue(b.RequiresAlt()); + _shift->SetValue(b.RequiresShift()); + _rctrl->SetValue(b.RequiresRawControl()); + mods->Add(_ctrl); + mods->Add(_alt); + mods->Add(_shift); + mods->Add(_rctrl); + grid->Add(mods, 1, wxEXPAND); + + if (_type == "EFFECT") { + grid->Add(new wxStaticText(this, wxID_ANY, _("Effect:")), 0, wxALIGN_CENTER_VERTICAL); + _effect = new wxChoice(this, wxID_ANY); + _effect->Append(""); + for (const auto& it : *em) { + _effect->Append(it->Name()); + if (it->Name() == b.GetEffectName()) _effect->SetSelection(_effect->GetCount() - 1); + } + if (_effect->GetSelection() == wxNOT_FOUND) _effect->SetSelection(0); + grid->Add(_effect, 1, wxEXPAND); + } + if (_type == "EFFECT" || _type == "APPLYSETTING") { + grid->Add(new wxStaticText(this, wxID_ANY, _("Effect Setting:")), 0, wxALIGN_CENTER_VERTICAL); + _setting = new wxTextCtrl(this, wxID_ANY, b.GetEffectString()); + grid->Add(_setting, 1, wxEXPAND); + } + if (_type == "PRESET") { + grid->Add(new wxStaticText(this, wxID_ANY, _("Preset:")), 0, wxALIGN_CENTER_VERTICAL); + _preset = new wxChoice(this, wxID_ANY); + _preset->Append(""); + for (const auto& it : xl->GetPresets()) { + _preset->Append(it); + if (it == b.GetEffectName()) _preset->SetSelection(_preset->GetCount() - 1); + } + if (_preset->GetSelection() == wxNOT_FOUND) _preset->SetSelection(0); + grid->Add(_preset, 1, wxEXPAND); + } + + // Header: the friendly name (prominent) and its description. The raw + // action/type isn't shown - it's not meaningful to most users. + auto* nameText = new wxStaticText(this, wxID_ANY, KeyBindingEditDialog::FriendlyName(b.GetType())); + wxFont nameFont = nameText->GetFont(); + nameFont.MakeBold(); + nameFont.SetPointSize(nameFont.GetPointSize() + 3); + nameText->SetFont(nameFont); + + auto* descText = new wxStaticText(this, wxID_ANY, b.GetTip()); + descText->Wrap(440); + + auto* top = new wxBoxSizer(wxVERTICAL); + top->Add(nameText, 0, wxLEFT | wxRIGHT | wxTOP, 14); + top->Add(descText, 0, wxLEFT | wxRIGHT | wxTOP, 6); + top->Add(new wxStaticLine(this, wxID_ANY), 0, wxEXPAND | wxALL, 12); + top->Add(grid, 1, wxEXPAND | wxLEFT | wxRIGHT, 14); + top->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 12); + SetSizerAndFit(top); + SetMinSize(GetSize()); + CenterOnParent(); + } + + void ApplyTo(KeyBinding& b) const + { + const int s = _key->GetSelection(); + if (s <= 0) { + b.SetKey(WXK_NONE); + } else { + b.SetKey(_keys[s - 1]); + } + b.SetControl(_ctrl->GetValue()); + b.SetAlt(_alt->GetValue()); + b.SetShift(_shift->GetValue()); + b.SetRawControl(_rctrl->GetValue()); + if (_effect != nullptr) b.SetEffectName(_effect->GetStringSelection().ToStdString()); + if (_preset != nullptr) b.SetEffectName(_preset->GetStringSelection().ToStdString()); + if (_setting != nullptr) b.SetEffectString(_setting->GetValue().ToStdString()); + } + +private: + std::string _type; + wxChoice* _key = nullptr; + std::vector _keys; + wxCheckBox* _ctrl = nullptr; + wxCheckBox* _alt = nullptr; + wxCheckBox* _shift = nullptr; + wxCheckBox* _rctrl = nullptr; + wxChoice* _effect = nullptr; + wxChoice* _preset = nullptr; + wxTextCtrl* _setting = nullptr; +}; +} // namespace + KeyBindingEditDialog::KeyBindingEditDialog(xLightsFrame* parent, KeyBindingMap* keyBindings, EffectManager* effectManager, wxWindowID id,const wxPoint& pos,const wxSize& size) { _xLights = parent; @@ -108,33 +251,41 @@ KeyBindingEditDialog::KeyBindingEditDialog(xLightsFrame* parent, KeyBindingMap* Panel_Properties->SetMinSize(wxSize(500, -1)); Layout(); + Choice_Scope->AppendString("All"); //Choice_Scope->AppendString("Controller"); Choice_Scope->AppendString("Layout"); Choice_Scope->AppendString("Sequencer"); Choice_Scope->AppendString("All tabs"); - Choice_Scope->SetStringSelection("Sequencer"); - - ListCtrl_Bindings->AppendColumn("Type"); - ListCtrl_Bindings->AppendColumn("Key", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); -#ifdef __WXOSX__ - ListCtrl_Bindings->AppendColumn("Command \u2318", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("Option \u2325", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("Shift \u21E7", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("Control \u2303", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); -#else - ListCtrl_Bindings->AppendColumn("Control", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("Alt", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("Shift", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("macOS Ctrl", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); -#endif + Choice_Scope->SetStringSelection("All"); + + // Live filter for the bindings list. Added here (outside the wxSmith guard) + // and stacked under the Scope row, so no .wxs change is needed. + FlexGridSizer4->Add(new wxStaticText(this, wxID_ANY, _("Filter:")), 1, wxALL | wxALIGN_CENTER_VERTICAL, 5); + _filterCtrl = new wxSearchCtrl(this, wxID_ANY); + _filterCtrl->ShowCancelButton(true); + _filterCtrl->SetDescriptiveText(_("Filter actions, shortcuts or descriptions")); + FlexGridSizer4->Add(_filterCtrl, 1, wxALL | wxEXPAND, 5); + _filterCtrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { _filter = _filterCtrl->GetValue().Lower(); LoadList(); }); + _filterCtrl->Bind(wxEVT_SEARCHCTRL_CANCEL_BTN, [this](wxCommandEvent&) { _filterCtrl->ChangeValue(""); _filter.clear(); LoadList(); }); + Layout(); + + ListCtrl_Bindings->AppendColumn("Action"); + ListCtrl_Bindings->AppendColumn("Shortcut", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); ListCtrl_Bindings->AppendColumn("Details"); + // wxListCtrl has no per-row tooltip, so track the hovered row and show + // that binding's raw type + description as the control tooltip. + ListCtrl_Bindings->Bind(wxEVT_MOTION, &KeyBindingEditDialog::OnListMouseMotion, this); + // Modeless: clean up on close instead of EndModal. + Bind(wxEVT_CLOSE_WINDOW, &KeyBindingEditDialog::OnClose, this); + SetName(WINDOW_NAME); // so the menu handler can find/reuse this instance + LoadList(); ListCtrl_Bindings->SetColumnWidth(0, wxCOL_WIDTH_AUTOSIZE); ListCtrl_Bindings->SetColumnWidth(1, wxCOL_WIDTH_AUTOSIZE); - ListCtrl_Bindings->SetColumnWidth(6, wxCOL_WIDTH_AUTOSIZE); + ListCtrl_Bindings->SetColumnWidth(2, wxCOL_WIDTH_AUTOSIZE); _propertyGrid = new xlPropertyGrid(Panel_Properties, wxID_ANY, wxDefaultPosition, wxDefaultSize, // Here are just some of the supported window styles @@ -162,6 +313,41 @@ KeyBindingEditDialog::KeyBindingEditDialog(xLightsFrame* parent, KeyBindingMap* if (targetHeight > maxHeight) targetHeight = maxHeight; } SetSize(targetWidth, targetHeight); + + // Edit... button, plus double-click / Enter on a row (ITEM_ACTIVATED), open + // the popup editor for the selected binding. The button is disabled while + // nothing is selected. + _editButton = new wxButton(this, wxID_ANY, _("Edit...")); + FlexGridSizer5->Insert(0, _editButton, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); + _editButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { DoEditSelected(); }); + ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent&) { DoEditSelected(); }); + ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_SELECTED, [this](wxListEvent& e) { e.Skip(); UpdateEditEnabled(); }); + ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_DESELECTED, [this](wxListEvent& e) { e.Skip(); UpdateEditEnabled(); }); + // Stretch the last (Details) column to fill the list width so rows/zebra + // extend full width when the window is resized. + ListCtrl_Bindings->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { + e.Skip(); + if (ListCtrl_Bindings->GetColumnCount() < 3) return; + const int total = ListCtrl_Bindings->GetClientSize().GetWidth(); + const int used = ListCtrl_Bindings->GetColumnWidth(0) + ListCtrl_Bindings->GetColumnWidth(1); + if (total - used > 120) ListCtrl_Bindings->SetColumnWidth(2, total - used); + }); + + // Editing now happens in a popup, so drop the right-hand property panel and + // rebuild the layout as a single column (scope/filter row, full-width list, + // buttons). Hiding the panel alone left its grid column behind. + Panel_Properties->Hide(); + FlexGridSizer1->Detach(FlexGridSizer4); + FlexGridSizer1->Detach(ListCtrl_Bindings); + FlexGridSizer1->Detach(FlexGridSizer5); + auto* colSizer = new wxBoxSizer(wxVERTICAL); + colSizer->Add(FlexGridSizer4, 0, wxEXPAND | wxALL, 2); + colSizer->Add(ListCtrl_Bindings, 1, wxEXPAND | wxALL, 2); + colSizer->Add(FlexGridSizer5, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5); + SetSizer(colSizer, true); + SetMinSize(wxSize(550, 400)); + SetSize(wxSize(800, 700)); + Layout(); } int KeyBindingEditDialog::GetSelectedKeyBindingIndex() const { @@ -169,6 +355,33 @@ int KeyBindingEditDialog::GetSelectedKeyBindingIndex() const { return ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } +void KeyBindingEditDialog::UpdateEditEnabled() +{ + if (_editButton != nullptr) _editButton->Enable(GetSelectedKeyBindingIndex() >= 0); +} + +void KeyBindingEditDialog::RefreshRow(long index, const KeyBinding& b) +{ + ListCtrl_Bindings->SetItem(index, 1, RenderShortcut(b)); + ListCtrl_Bindings->SetItem(index, 2, BuildDetails(b)); + RefreshDuplicateHighlights(); +} + +void KeyBindingEditDialog::DoEditSelected() +{ + int index = GetSelectedKeyBindingIndex(); + if (index < 0) return; + int id = (int)ListCtrl_Bindings->GetItemData(index); + if (id < 0) return; + + KeyBinding& b = _keyBindings->GetBinding(id); + KeyBindingPopupEditor editor(this, b, _effectManager, _xLights); + if (editor.ShowModal() == wxID_OK) { + editor.ApplyTo(b); + RefreshRow(index, b); + } +} + void KeyBindingEditDialog::SetKeyBindingProperties() { int index = GetSelectedKeyBindingIndex(); @@ -346,9 +559,12 @@ void KeyBindingEditDialog::OnControllerPropertyGridChange(wxPropertyGridEvent& e } } - LoadList(); - SelectKey(id); - SetKeyBindingProperties(); + // Update the edited row in place. Don't rebuild the list/property grid: Type + // is read-only so the property set never changes, and a rebuild re-creates + // the enum editors and visibly flashes them. The grid already shows the edit. + ListCtrl_Bindings->SetItem(index, 1, RenderShortcut(b)); + ListCtrl_Bindings->SetItem(index, 2, BuildDetails(b)); + RefreshDuplicateHighlights(); } KeyBindingEditDialog::~KeyBindingEditDialog() @@ -370,59 +586,184 @@ void KeyBindingEditDialog::LoadList() ListCtrl_Bindings->Freeze(); auto pos = ListCtrl_Bindings->GetScrollPos(wxVERTICAL); ListCtrl_Bindings->DeleteAllItems(); + const wxColour evenRow = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX); + const wxColour txt = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT); + const wxColour oddRow((evenRow.Red()*92 + txt.Red()*8) / 100, + (evenRow.Green()*92 + txt.Green()*8) / 100, + (evenRow.Blue()*92 + txt.Blue()*8) / 100); + const wxString scopeSel = Choice_Scope->GetStringSelection(); + const bool showAll = (scopeSel == "All"); + const KBSCOPE scope = EncodeScope(scopeSel); for (const auto& it : _keyBindings->GetBindings()) { - if (it.InScope(EncodeScope(Choice_Scope->GetStringSelection()))) - { - auto item = ListCtrl_Bindings->InsertItem(ListCtrl_Bindings->GetItemCount(), it.GetType()); - ListCtrl_Bindings->SetItem(item, 1, it.EncodeKey(it.GetKey(), it.RequiresShift())); - ListCtrl_Bindings->SetItem(item, 2, it.RequiresControl() ? _("Y") : _("")); - ListCtrl_Bindings->SetItem(item, 3, it.RequiresAlt() ? _("Y") : _("")); - ListCtrl_Bindings->SetItem(item, 4, it.RequiresShift() ? _("Y") : _("")); - ListCtrl_Bindings->SetItem(item, 5, it.RequiresRawControl() ? _("Y") : _("")); - if (it.GetEffectName() != "" && it.GetEffectString() != "") - { - ListCtrl_Bindings->SetItem(item, 6, it.GetEffectName() + ":" + it.GetEffectString()); - } - else if (it.GetEffectString() != "") - { - ListCtrl_Bindings->SetItem(item, 6, it.GetEffectString()); - } - else if (it.GetEffectName() != "") - { - ListCtrl_Bindings->SetItem(item, 6, it.GetEffectName()); - } - ListCtrl_Bindings->SetItemData(item, it.GetId()); - if (it.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(it)) - { - ListCtrl_Bindings->SetItemTextColour(item, *wxRED); + if (!showAll && !it.InScope(scope)) + continue; + + const wxString friendly = FriendlyName(it.GetType()); + const wxString shortcut = RenderShortcut(it); + const wxString details = BuildDetails(it); + + // Whitespace-tokenised AND filter over action / type / shortcut / details. + if (!_filter.empty()) { + const wxString hay = (friendly + " " + it.GetType() + " " + shortcut + " " + details).Lower(); + bool match = true; + wxStringTokenizer tok(_filter, " "); + while (tok.HasMoreTokens()) { + if (hay.Find(tok.GetNextToken()) == wxNOT_FOUND) { match = false; break; } } + if (!match) continue; } + + auto item = ListCtrl_Bindings->InsertItem(ListCtrl_Bindings->GetItemCount(), friendly); + ListCtrl_Bindings->SetItem(item, 1, shortcut); + ListCtrl_Bindings->SetItem(item, 2, details); + ListCtrl_Bindings->SetItemData(item, it.GetId()); + // Zebra striping using theme-aware colours (works in light and dark). + ListCtrl_Bindings->SetItemBackgroundColour(item, (item % 2 == 0) ? evenRow : oddRow); + if (it.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(it)) + { + ListCtrl_Bindings->SetItemTextColour(item, *wxRED); + } + } + if (ListCtrl_Bindings->GetItemCount() > 0 && + ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) < 0) { + ListCtrl_Bindings->SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } ListCtrl_Bindings->Thaw(); ListCtrl_Bindings->SetScrollPos(wxVERTICAL, pos); ListCtrl_Bindings->Refresh(); + UpdateEditEnabled(); +} + +wxString KeyBindingEditDialog::BuildDetails(const KeyBinding& b) const +{ + wxString details = b.GetTip(); + wxString effect; + if (b.GetEffectName() != "" && b.GetEffectString() != "") { + effect = b.GetEffectName() + ":" + b.GetEffectString(); + } else if (b.GetEffectString() != "") { + effect = b.GetEffectString(); + } else if (b.GetEffectName() != "") { + effect = b.GetEffectName(); + } + if (!effect.empty()) { + details = details.empty() ? effect : details + " (" + effect + ")"; + } + return details; +} + +// Re-colour duplicate-key rows in place (changing a key can create or resolve a +// clash on another row) without rebuilding the list. +void KeyBindingEditDialog::RefreshDuplicateHighlights() +{ + const wxColour normal = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT); + for (long i = 0; i < ListCtrl_Bindings->GetItemCount(); ++i) { + const KeyBinding& rb = _keyBindings->GetBinding((int)ListCtrl_Bindings->GetItemData(i)); + const bool dup = rb.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(rb); + ListCtrl_Bindings->SetItemTextColour(i, dup ? *wxRED : normal); + } +} + +wxString KeyBindingEditDialog::FriendlyName(const std::string& type) +{ + // Most enum names humanise cleanly (split on '_', title-case); override the + // handful that don't read well that way. + static const std::map overrides = { + { "AUDIO_FULL_SPEED", "Audio: Full Speed" }, + { "AUDIO_F_1_5_SPEED", "Audio: 1.5x Speed" }, + { "AUDIO_F_2_SPEED", "Audio: 2x Speed" }, + { "AUDIO_F_3_SPEED", "Audio: 3x Speed" }, + { "AUDIO_F_4_SPEED", "Audio: 4x Speed" }, + { "AUDIO_S_3_4_SPEED", "Audio: 3/4 Speed" }, + { "AUDIO_S_1_2_SPEED", "Audio: 1/2 Speed" }, + { "AUDIO_S_1_4_SPEED", "Audio: 1/4 Speed" }, + { "VALUECURVES_TOGGLE", "Value Curves Panel" }, + { "EXPORT_MODEL_CAD", "Export Model (CAD)" }, + { "EXPORT_LAYOUT_DXF", "Export Layout (DXF)" }, + { "FPP_CONNECT", "FPP Connect" }, + { "FOCUS_SEQUENCER", "Focus Effects Grid" }, + }; + auto o = overrides.find(type); + if (o != overrides.end()) return o->second; + + wxString out; + bool newWord = true; + for (char c : type) { + if (c == '_') { + out += ' '; + newWord = true; + } else if (newWord) { + out += (char)std::toupper((unsigned char)c); + newWord = false; + } else { + out += (char)std::tolower((unsigned char)c); + } + } + return out; +} + +wxString KeyBindingEditDialog::RenderShortcut(const KeyBinding& b) +{ + if (b.GetKey() == WXK_NONE) return "(unassigned)"; + wxString mods; +#ifdef __WXOSX__ + if (b.RequiresControl()) mods += wxUniChar(0x2318); // Command + if (b.RequiresRawControl()) mods += wxUniChar(0x2303); // Control + if (b.RequiresAlt()) mods += wxUniChar(0x2325); // Option + if (b.RequiresShift()) mods += wxUniChar(0x21E7); // Shift +#else + if (b.RequiresControl()) mods += "Ctrl+"; + if (b.RequiresRawControl()) mods += "RCtrl+"; + if (b.RequiresAlt()) mods += "Alt+"; + if (b.RequiresShift()) mods += "Shift+"; +#endif + return mods + b.EncodeKey(b.GetKey(), false); +} + +void KeyBindingEditDialog::OnListMouseMotion(wxMouseEvent& event) +{ + event.Skip(); + int flags = 0; + long item = ListCtrl_Bindings->HitTest(event.GetPosition(), flags); + if (item == _tooltipItem) return; + _tooltipItem = item; + if (item == wxNOT_FOUND) { + ListCtrl_Bindings->UnsetToolTip(); + return; + } + long id = ListCtrl_Bindings->GetItemData(item); + for (const auto& b : _keyBindings->GetBindings()) { + if ((long)b.GetId() == id) { + wxString tip = FriendlyName(b.GetType()) + " [" + wxString(b.GetType()) + "]"; + if (!b.GetTip().empty()) tip += "\n" + wxString(b.GetTip()); + ListCtrl_Bindings->SetToolTip(tip); + return; + } + } + ListCtrl_Bindings->UnsetToolTip(); } void KeyBindingEditDialog::OnButton_CancelClick(wxCommandEvent& event) { - EndDialog(wxID_CLOSE); + Close(); +} + +void KeyBindingEditDialog::OnClose(wxCloseEvent& event) +{ + Destroy(); } void KeyBindingEditDialog::OnChoice_ScopeSelect(wxCommandEvent& event) { LoadList(); - SetKeyBindingProperties(); } void KeyBindingEditDialog::OnListCtrl_BindingsItemFocused(wxListEvent& event) { - SetKeyBindingProperties(); } void KeyBindingEditDialog::OnListCtrl_BindingsItemSelect(wxListEvent& event) { - SetKeyBindingProperties(); } void KeyBindingEditDialog::OnListCtrl_BindingsKeyDown(wxListEvent& event) @@ -487,7 +828,7 @@ void KeyBindingEditDialog::OnButton_AddEffectClick(wxCommandEvent& event) int id = _keyBindings->AddKey(KeyBinding(_(""), false, _("On"), _(""), _("2020.15"), false, false, false)); LoadList(); SelectKey(id); - SetKeyBindingProperties(); + DoEditSelected(); } void KeyBindingEditDialog::OnButtonAddApplySettingClick(wxCommandEvent& event) @@ -495,7 +836,7 @@ void KeyBindingEditDialog::OnButtonAddApplySettingClick(wxCommandEvent& event) int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), _("2020.15"), false, false, false, false)); LoadList(); SelectKey(id); - SetKeyBindingProperties(); + DoEditSelected(); } void KeyBindingEditDialog::OnButtonAddPresetClick(wxCommandEvent& event) @@ -504,7 +845,7 @@ void KeyBindingEditDialog::OnButtonAddPresetClick(wxCommandEvent& event) int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), false, false, false, false)); LoadList(); SelectKey(id); - SetKeyBindingProperties(); + DoEditSelected(); } void KeyBindingEditDialog::OnButtonSaveClick(wxCommandEvent& event) diff --git a/src-ui-wx/app-shell/KeyBindingEditDialog.h b/src-ui-wx/app-shell/KeyBindingEditDialog.h index 3fc531684f..af68a664e9 100644 --- a/src-ui-wx/app-shell/KeyBindingEditDialog.h +++ b/src-ui-wx/app-shell/KeyBindingEditDialog.h @@ -22,8 +22,10 @@ //*) class KeyBindingMap; +class KeyBinding; class EffectManager; class xLightsFrame; +class wxSearchCtrl; class KeyBindingEditDialog : public wxDialog { @@ -33,11 +35,32 @@ class KeyBindingEditDialog : public wxDialog xLightsFrame* _xLights = nullptr; void LoadList(); + wxString BuildDetails(const KeyBinding& b) const; + void RefreshDuplicateHighlights(); + void DoEditSelected(); + void RefreshRow(long index, const KeyBinding& b); + void UpdateEditEnabled(); void SetKeyBindingProperties(); int GetSelectedKeyBindingIndex() const; void SelectKey(int id); + // Display helpers for the bindings list. + static wxString RenderShortcut(const KeyBinding& b); + void OnListMouseMotion(wxMouseEvent& event); + long _tooltipItem = -1; + + wxSearchCtrl* _filterCtrl = nullptr; + wxString _filter; // lower-cased; whitespace-tokenised AND match in LoadList + wxButton* _editButton = nullptr; // disabled when nothing is selected + public: + // Stable window name used to find an already-open instance (type-based + // lookup is unreliable here - wxDialog subclasses share RTTI in this build). + static constexpr const char* WINDOW_NAME = "xlKeyBindingEditDialog"; + + // Public so the popup editor can label a binding with its friendly name. + static wxString FriendlyName(const std::string& type); + KeyBindingEditDialog(xLightsFrame* parent, KeyBindingMap* keyBindings, EffectManager* effectManager, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); virtual ~KeyBindingEditDialog(); @@ -82,6 +105,7 @@ class KeyBindingEditDialog : public wxDialog //*) void OnControllerPropertyGridChange(wxPropertyGridEvent& event); + void OnClose(wxCloseEvent& event); DECLARE_EVENT_TABLE() }; diff --git a/src-ui-wx/app-shell/KeyBindings.cpp b/src-ui-wx/app-shell/KeyBindings.cpp index 334d592568..4d276d95cb 100644 --- a/src-ui-wx/app-shell/KeyBindings.cpp +++ b/src-ui-wx/app-shell/KeyBindings.cpp @@ -167,137 +167,137 @@ static std::vector> KeyBindingTypes = }; static std::vector> keyBindingTips = { - { "TIMING_ADD", "Add a timing mark." }, - { "TIMING_SPLIT", "Split a timing mark." }, - { "ZOOM_IN", "Zoom into the effects grid." }, - { "ZOOM_OUT", "Zoom out of the effects grid." }, - { "ZOOM_SEL", "Zoom so selected timeline fills the screen." }, - { "RANDOM", "Insert random effects." }, - { "RENDER_ALL", "Render all." }, - { "SAVE_CURRENT_TAB", "Save the currently selected tab." }, - { "LIGHTS_TOGGLE", "Toggle output to lights on/off." }, - { "OPEN_SEQUENCE", "Open a sequence." }, - { "CLOSE_SEQUENCE", "Close the open sequence." }, - { "NEW_SEQUENCE", "Create a new sequence." }, - { "PASTE_BY_CELL", "Put cut/copy/paste into Paste By Cell mode." }, - { "PASTE_BY_TIME", "Put cut/copy/paste into Paste By Time mode." }, - { "BACKUP", "Backup your show folder." }, - { "ALTERNATE_BACKUP", "Backup your show folder to the alternate backup location." }, - { "SELECT_SHOW_FOLDER", "Change your current show folder." }, - { "SAVEAS_SEQUENCE", "Save the current sequence to a new file." }, + { "TIMING_ADD", "Add a timing mark at the playback cursor on the active timing track." }, + { "TIMING_SPLIT", "Split the timing mark under the cursor into two at the cursor position." }, + { "ZOOM_IN", "Zoom the effects grid in for finer timing detail." }, + { "ZOOM_OUT", "Zoom the effects grid out to see more of the timeline." }, + { "ZOOM_SEL", "Zoom the timeline so the currently selected time range fills the grid." }, + { "RANDOM", "Insert randomly chosen effects into the selected cells." }, + { "RENDER_ALL", "Render every model in the sequence from scratch." }, + { "SAVE_CURRENT_TAB", "Save whichever tab is currently active (sequence, layout, or setup)." }, + { "LIGHTS_TOGGLE", "Turn live output to your controllers/lights on or off." }, + { "OPEN_SEQUENCE", "Open an existing sequence from the show folder." }, + { "CLOSE_SEQUENCE", "Close the currently open sequence (prompts to save if changed)." }, + { "NEW_SEQUENCE", "Create a new, empty sequence." }, + { "PASTE_BY_CELL", "Switch paste mode so copied effects align to timing cells rather than exact times." }, + { "PASTE_BY_TIME", "Switch paste mode so copied effects keep their original absolute times." }, + { "BACKUP", "Back up the show folder to the configured backup location." }, + { "ALTERNATE_BACKUP", "Back up the show folder to the alternate (secondary) backup location." }, + { "SELECT_SHOW_FOLDER", "Switch to a different show folder." }, + { "SAVEAS_SEQUENCE", "Save the current sequence under a new file name." }, { "SAVE_SEQUENCE", "Save the current sequence." }, - { "EFFECT_SETTINGS_TOGGLE", "Toggle display of the effect settings panel." }, - { "EFFECT_ASSIST_TOGGLE", "Toggle display of the effect assist panel." }, - { "COLOR_TOGGLE", "Toggle display of the color panel." }, - { "LAYER_SETTING_TOGGLE", "Toggle display of the layer settings panel." }, - { "LAYER_BLENDING_TOGGLE", "Toggle display of the layer blending panel." }, - { "MODEL_PREVIEW_TOGGLE", "Toggle display of the model preview panel." }, - { "HOUSE_PREVIEW_TOGGLE", "Toggle display of the house preview panel." }, - { "EFFECTS_TOGGLE", "Toggle display of the effect dropper panel." }, - { "DISPLAY_ELEMENTS_TOGGLE", "Toggle display of the display elements panel." }, - { "JUKEBOX_TOGGLE", "Toggle display of the jukebox panel." }, - { "SEQUENCE_SETTINGS", "Display the sequence settings." }, - { "LOCK_EFFECT", "Lock the selected effects." }, - { "UNLOCK_EFFECT", "Unlock the selected effects." }, - { "MARK_SPOT", "Mark the current spot in the sequencer so you can return to it." }, - { "RETURN_TO_SPOT", "Return to the previously marked spot in the sequencer." }, - { "EFFECT_DESCRIPTION", "Open the effect description dialog." }, - { "EFFECT_ALIGN_START", "Align the selected effects to have the same start times." }, - { "EFFECT_ALIGN_END", "Align the selected effects to have the same end times." }, - { "EFFECT_ALIGN_BOTH", "Align the selected effects to have the same start and end times." }, - { "INSERT_LAYER_ABOVE", "Insert a sequencing layer above the current row." }, - { "INSERT_LAYER_BELOW", "Insert a sequencing layer below the current row." }, - { "TOGGLE_ELEMENT_EXPAND", "Expand the current element." }, - { "SELECT_ALL", "Select all effects and timing marks." }, - { "SELECT_ALL_NO_TIMING", "Select all effects but not timing marks." }, - { "SHOW_PRESETS", "Show the effect presets panel." }, - { "SEARCH_TOGGLE", "Toggle display of the effect search panel." }, - { "FILTER_SEQUENCER", "Show/hide the sequencer prop filter box to jump to a prop." }, - { "PERSPECTIVES_TOGGLE", "Toggle display of the perspectives panel." }, - { "EFFECT_UPDATE", "Apply the current effect settings to all selected effects." }, - { "COLOR_UPDATE", "Apply the current colors to all selected effects." }, - { "PLAY_LOOP", "Play the selected part of the song repeatedly." }, - { "PLAY", "Play the song." }, - { "TOGGLE_PLAY", "Play/Stop playing the song." }, - { "START_OF_SONG", "Jump to the start of the song." }, - { "END_OF_SONG", "Jump to the end of the song." }, - { "STOP", "Stop sequence playback." }, - { "PAUSE", "Pause sequence playback." }, - { "EFFECT", "Insert an effect." }, - { "APPLYSETTING", "Apply setting to selected effects." }, - { "PRESET", "Insert a preset effect." }, - { "LOCK_MODEL", "Lock the selected models." }, - { "UNLOCK_MODEL", "Unlock the selected models." }, - { "GROUP_MODELS", "Create a group from the selected models." }, - { "WIRING_VIEW", "Display the wiring view for the selected model." }, - { "EXPORT_MODEL_CAD", "Export the selected model as a DXF or STL or VRML File." }, - { "EXPORT_LAYOUT_DXF", "Export the default layout as a DXF File." }, - { "NODE_LAYOUT", "Display the node layout for the selected model." }, - { "SAVE_LAYOUT", "Save the layout tab." }, - { "SELECT_ALL_MODELS", "Select all models." }, - { "MODEL_ALIGN_TOP", "Align the selected models to the top edge." }, - { "MODEL_ALIGN_BOTTOM", "Align the selected models to the bottom edge." }, - { "MODEL_ALIGN_LEFT", "Align the selected models to the left edge." }, - { "MODEL_ALIGN_RIGHT", "Align the selected models to the right edge." }, - { "MODEL_ALIGN_CENTER_VERT", "Align the selected models to be vertically centered." }, - { "MODEL_ALIGN_CENTER_HORIZ", "Align the selected models to be horizontally centered." }, - { "MODEL_ALIGN_FRONTS", "Align the selected models to the front edge." }, - { "MODEL_ALIGN_BACKS", "Align the selected models to the back edge." }, - { "MODEL_ALIGN_GROUND", "Align the selected models to the ground." }, - { "MODEL_DISTRIBUTE_HORIZ", "Distribute the selected model horizontally." }, - { "MODEL_DISTRIBUTE_VERT", "Distribute the selected models vertically." }, - { "MODEL_FLIP_HORIZ", "Flip the selected models horizontally." }, - { "MODEL_FLIP_VERT", "Flip the selected models vertically." }, - { "CANCEL_RENDER", "Cancel current rendering activity." }, - { "TOGGLE_RENDER", "Toggle background rendering." }, - { "PRESETS_TOGGLE", "Toggle display of the presets panel." }, - { "FOCUS_SEQUENCER", "Force keyboard focus to the effects gid." }, // This forces focus to the sequencer for situations where keys dont seem to work. It must be mapped to function key - { "VALUECURVES_TOGGLE", "Toggle display of the value curves droppper panel." }, - { "COLOR_DROPPER_TOGGLE", "Toggle display of the color dropper panel." }, - { "AUDIO_FULL_SPEED", "Playback audio at normal speed." }, - { "AUDIO_F_1_5_SPEED", "Playback audio at 1.5 times speed." }, - { "AUDIO_F_2_SPEED", "Playback audio at 2 times speed." }, - { "AUDIO_F_3_SPEED", "Playback audio at 3 times speed." }, - { "AUDIO_F_4_SPEED", "Playback audio at 4 times speed." }, - { "AUDIO_S_3_4_SPEED", "Playback audio at 3/4 speed." }, - { "AUDIO_S_1_2_SPEED", "Playback audio at 1/2 speed." }, - { "AUDIO_S_1_4_SPEED", "Playback audio at 1/4 speed." }, - { "PRIOR_TAG", "Jump to prior audio tag." }, - { "NEXT_TAG", "Jump to next audio tag." }, - { "PLAY_PRIOR_TAG", "Play from prior audio tag." }, - { "PLAY_NEXT_TAG", "Play from next audio tag." }, - { "MODEL_SUBMODELS", "Edit model submodels." }, - { "MODEL_FACES", "Edit model faces." }, - { "MODEL_STATES", "Edit model states." }, - { "MODEL_MODELDATA", "Edit custom model data." }, - { "MODEL_TOGGLE", "Toggle (Enable/Disable) rendering of the selected model in the sequencer" }, - { "MODEL_DISABLE", "Disable rendering of the selected model in the sequencer" }, - { "MODEL_ENABLE", "Enable rendering of the selected model in the sequencer" }, - { "EFFECT_TOGGLE", "Toggle (Enable/Disable) rendering of the selected effects in the sequencer" }, - { "EFFECT_DISABLE", "Disable rendering of the selected effects in the sequencer" }, - { "EFFECT_ENABLE", "Enable rendering of the selected effects in the sequencer" }, - { "MODEL_EFFECT_TOGGLE", "Toggle (Enable/Disable) rendering of the selected model or the effects in the sequencer" }, - { "EFFECTS_TO_TIMING", "Convert selected effects to timing marks." }, - { "SELECT_TIMING_1", "Select first timing." }, - { "SELECT_TIMING_2", "Select second timing." }, - { "SELECT_TIMING_3", "Select third timing." }, - { "SELECT_TIMING_4", "Select fourth timing." }, - { "SELECT_TIMING_5", "Select fifth timing." }, - { "SELECT_TIMING_6", "Select sixth timing." }, - { "SELECT_TIMING_7", "Select seventh timing." }, - { "SELECT_TIMING_8", "Select eighth timing." }, - { "SELECT_TIMING_9", "Select ninth timing." }, - { "SELECT_NO_TIMING", "Select no timing tracks." }, - { "INCREASE_SPEED", "Increase speed." }, - { "DECREASE_SPEED", "Decrease speed." }, - { "JUKEBOX_BTN_1", "Jukebox Button 1." }, - { "JUKEBOX_BTN_2", "Jukebox Button 2." }, - { "JUKEBOX_BTN_3", "Jukebox Button 3." }, - { "JUKEBOX_BTN_4", "Jukebox Button 4." }, - { "JUKEBOX_BTN_5", "Jukebox Button 5." }, - { "FPP_CONNECT", "Run FPP Connect" }, - { "COMMAND_PALETTE", "Open the command palette." }, - { "IMPORT_EFFECTS", "Open the Import Effects dialog." }, + { "EFFECT_SETTINGS_TOGGLE", "Show or hide the Effect Settings panel where the selected effect's options are edited." }, + { "EFFECT_ASSIST_TOGGLE", "Show or hide the Effect Assist panel (visual editor for effects that support it)." }, + { "COLOR_TOGGLE", "Show or hide the Colors panel used to set an effect's palette." }, + { "LAYER_SETTING_TOGGLE", "Show or hide the Layer Settings panel (buffer style, transform, blur, etc.)." }, + { "LAYER_BLENDING_TOGGLE", "Show or hide the Layer Blending panel that controls how stacked layers combine." }, + { "MODEL_PREVIEW_TOGGLE", "Show or hide the per-model preview panel." }, + { "HOUSE_PREVIEW_TOGGLE", "Show or hide the whole-house preview panel." }, + { "EFFECTS_TOGGLE", "Show or hide the Effects panel you drag effects from onto the grid." }, + { "DISPLAY_ELEMENTS_TOGGLE", "Show or hide the Display Elements panel for choosing which models/views appear in the grid." }, + { "JUKEBOX_TOGGLE", "Show or hide the Jukebox panel of saved effect buttons." }, + { "SEQUENCE_SETTINGS", "Open the Sequence Settings dialog (timing, media, metadata)." }, + { "LOCK_EFFECT", "Lock the selected effects so they can't be moved or edited." }, + { "UNLOCK_EFFECT", "Unlock the selected effects so they can be moved or edited again." }, + { "MARK_SPOT", "Remember the current timeline position so you can jump back to it later." }, + { "RETURN_TO_SPOT", "Jump back to the timeline position you previously marked." }, + { "EFFECT_DESCRIPTION", "Add or edit a text note/description on the selected effect." }, + { "EFFECT_ALIGN_START", "Move the selected effects so they all share the first one's start time." }, + { "EFFECT_ALIGN_END", "Move the selected effects so they all share the first one's end time." }, + { "EFFECT_ALIGN_BOTH", "Stretch the selected effects so they all share the first one's start and end times." }, + { "INSERT_LAYER_ABOVE", "Add a new, empty effect layer above the current model row." }, + { "INSERT_LAYER_BELOW", "Add a new, empty effect layer below the current model row." }, + { "TOGGLE_ELEMENT_EXPAND", "Expand or collapse the selected model row to show/hide its strands, submodels and nodes." }, + { "SELECT_ALL", "Select every effect and timing mark on the current row." }, + { "SELECT_ALL_NO_TIMING", "Select every effect on the current row, leaving timing marks unselected." }, + { "SHOW_PRESETS", "Open the Effect Presets panel of saved effect definitions." }, + { "SEARCH_TOGGLE", "Show or hide the effect search panel." }, + { "FILTER_SEQUENCER", "Jump to the sequencer's prop filter box to type a model name and narrow the visible rows." }, + { "PERSPECTIVES_TOGGLE", "Show or hide the Perspectives panel for saving/restoring panel layouts." }, + { "EFFECT_UPDATE", "Copy the current effect's settings onto all other selected effects." }, + { "COLOR_UPDATE", "Copy the current effect's colors onto all other selected effects." }, + { "PLAY_LOOP", "Play the selected time range over and over." }, + { "PLAY", "Start playback from the playback cursor." }, + { "TOGGLE_PLAY", "Start playback, or stop it if already playing." }, + { "START_OF_SONG", "Move the playback cursor to the start of the sequence." }, + { "END_OF_SONG", "Move the playback cursor to the end of the sequence." }, + { "STOP", "Stop playback and return output to idle." }, + { "PAUSE", "Pause playback, keeping the cursor where it is." }, + { "EFFECT", "Drop a specific effect (configured on this binding) into the selected cells." }, + { "APPLYSETTING", "Apply a saved setting (configured on this binding) to the selected effects." }, + { "PRESET", "Drop a saved preset effect (configured on this binding) into the selected cells." }, + { "LOCK_MODEL", "Lock the selected models so they can't be moved on the layout." }, + { "UNLOCK_MODEL", "Unlock the selected models so they can be moved again." }, + { "GROUP_MODELS", "Create a new model group containing the selected models." }, + { "WIRING_VIEW", "Open the wiring view showing node order and connections for the selected model." }, + { "EXPORT_MODEL_CAD", "Export the selected model as a CAD file (DXF, STL, or VRML)." }, + { "EXPORT_LAYOUT_DXF", "Export the entire default layout as a DXF file." }, + { "NODE_LAYOUT", "Open the node layout view showing each node's position for the selected model." }, + { "SAVE_LAYOUT", "Save changes made on the Layout tab." }, + { "SELECT_ALL_MODELS", "Select every model on the layout." }, + { "MODEL_ALIGN_TOP", "Align the selected models so their top edges match the first selected model." }, + { "MODEL_ALIGN_BOTTOM", "Align the selected models so their bottom edges match the first selected model." }, + { "MODEL_ALIGN_LEFT", "Align the selected models so their left edges match the first selected model." }, + { "MODEL_ALIGN_RIGHT", "Align the selected models so their right edges match the first selected model." }, + { "MODEL_ALIGN_CENTER_VERT", "Align the selected models to share the same vertical center line." }, + { "MODEL_ALIGN_CENTER_HORIZ", "Align the selected models to share the same horizontal center line." }, + { "MODEL_ALIGN_FRONTS", "Align the selected models' front (near) edges in 3D." }, + { "MODEL_ALIGN_BACKS", "Align the selected models' back (far) edges in 3D." }, + { "MODEL_ALIGN_GROUND", "Drop the selected models so they sit on the ground plane in 3D." }, + { "MODEL_DISTRIBUTE_HORIZ", "Space the selected models evenly left-to-right." }, + { "MODEL_DISTRIBUTE_VERT", "Space the selected models evenly top-to-bottom." }, + { "MODEL_FLIP_HORIZ", "Mirror the selected models left-to-right." }, + { "MODEL_FLIP_VERT", "Mirror the selected models top-to-bottom." }, + { "CANCEL_RENDER", "Cancel any rendering currently in progress." }, + { "TOGGLE_RENDER", "Turn automatic background rendering on or off." }, + { "PRESETS_TOGGLE", "Show or hide the presets panel." }, + { "FOCUS_SEQUENCER", "Force keyboard focus back to the effects grid when shortcuts stop responding (map this to a function key)." }, // This forces focus to the sequencer for situations where keys dont seem to work. It must be mapped to function key + { "VALUECURVES_TOGGLE", "Show or hide the Value Curves panel you drag value curves from." }, + { "COLOR_DROPPER_TOGGLE", "Show or hide the Color Dropper panel for picking colors." }, + { "AUDIO_FULL_SPEED", "Play audio back at normal (1x) speed." }, + { "AUDIO_F_1_5_SPEED", "Play audio back at 1.5x speed." }, + { "AUDIO_F_2_SPEED", "Play audio back at 2x speed." }, + { "AUDIO_F_3_SPEED", "Play audio back at 3x speed." }, + { "AUDIO_F_4_SPEED", "Play audio back at 4x speed." }, + { "AUDIO_S_3_4_SPEED", "Play audio back at 3/4 (0.75x) speed." }, + { "AUDIO_S_1_2_SPEED", "Play audio back at 1/2 (0.5x) speed." }, + { "AUDIO_S_1_4_SPEED", "Play audio back at 1/4 (0.25x) speed." }, + { "PRIOR_TAG", "Move the cursor to the previous audio tag." }, + { "NEXT_TAG", "Move the cursor to the next audio tag." }, + { "PLAY_PRIOR_TAG", "Start playback from the previous audio tag." }, + { "PLAY_NEXT_TAG", "Start playback from the next audio tag." }, + { "MODEL_SUBMODELS", "Open the submodels editor for the selected model." }, + { "MODEL_FACES", "Open the faces editor for the selected model." }, + { "MODEL_STATES", "Open the states editor for the selected model." }, + { "MODEL_MODELDATA", "Open the custom-model data grid for the selected model." }, + { "MODEL_TOGGLE", "Enable or disable rendering of the selected model in the sequence." }, + { "MODEL_DISABLE", "Disable rendering of the selected model so it produces no output." }, + { "MODEL_ENABLE", "Re-enable rendering of the selected model." }, + { "EFFECT_TOGGLE", "Enable or disable rendering of the selected effects." }, + { "EFFECT_DISABLE", "Disable the selected effects so they produce no output." }, + { "EFFECT_ENABLE", "Re-enable the selected effects." }, + { "MODEL_EFFECT_TOGGLE", "Enable/disable rendering of the selected effects, or the whole model if none are selected." }, + { "EFFECTS_TO_TIMING", "Create timing marks at the start/end of each selected effect." }, + { "SELECT_TIMING_1", "Make the 1st timing track the active one." }, + { "SELECT_TIMING_2", "Make the 2nd timing track the active one." }, + { "SELECT_TIMING_3", "Make the 3rd timing track the active one." }, + { "SELECT_TIMING_4", "Make the 4th timing track the active one." }, + { "SELECT_TIMING_5", "Make the 5th timing track the active one." }, + { "SELECT_TIMING_6", "Make the 6th timing track the active one." }, + { "SELECT_TIMING_7", "Make the 7th timing track the active one." }, + { "SELECT_TIMING_8", "Make the 8th timing track the active one." }, + { "SELECT_TIMING_9", "Make the 9th timing track the active one." }, + { "SELECT_NO_TIMING", "Deselect the active timing track." }, + { "INCREASE_SPEED", "Increase the playback speed." }, + { "DECREASE_SPEED", "Decrease the playback speed." }, + { "JUKEBOX_BTN_1", "Trigger Jukebox button 1." }, + { "JUKEBOX_BTN_2", "Trigger Jukebox button 2." }, + { "JUKEBOX_BTN_3", "Trigger Jukebox button 3." }, + { "JUKEBOX_BTN_4", "Trigger Jukebox button 4." }, + { "JUKEBOX_BTN_5", "Trigger Jukebox button 5." }, + { "FPP_CONNECT", "Open FPP Connect to upload sequences/config to your players." }, + { "COMMAND_PALETTE", "Open the command palette to search and run any command." }, + { "IMPORT_EFFECTS", "Open the Import Effects dialog to bring effects in from another sequence." }, { "ALTERNATE_PASTE", "Paste effects using the opposite of the configured 'Paste As' mode (Relative vs As Layers)." } }; diff --git a/src-ui-wx/xLightsMain.cpp b/src-ui-wx/xLightsMain.cpp index 7b6cbb351f..732a4b2d7e 100644 --- a/src-ui-wx/xLightsMain.cpp +++ b/src-ui-wx/xLightsMain.cpp @@ -8710,9 +8710,22 @@ void xLightsFrame::OnMenuItemBulkControllerUploadSelected(wxCommandEvent& event) void xLightsFrame::OnMenuItem_KeyBindingsSelected(wxCommandEvent& event) { - KeyBindingEditDialog dlg(this, &GetMainSequencer()->keyBindings, &effectManager); - - dlg.ShowModal(); + // Modeless so it can stay open while you work elsewhere. Edits apply to the + // live key-binding map immediately; Save persists to disk. Reuse an existing + // editor by window NAME, not type: wxDynamicCast can't distinguish wxDialog + // subclasses in this build (RTTI), so a type scan matched other dialogs. + for (wxWindow* w : wxTopLevelWindows) { + if (w->GetName() == KeyBindingEditDialog::WINDOW_NAME) { + w->Show(); + w->Raise(); + w->SetFocus(); + return; + } + } + auto* dlg = new KeyBindingEditDialog(this, &GetMainSequencer()->keyBindings, &effectManager); + dlg->CenterOnParent(); + dlg->Show(); + dlg->Raise(); } void xLightsFrame::OnMenuItem_ExportControllerConnectionsSelected(wxCommandEvent& event) From 3e99cdd64c45944983ea872f2053d5e57431b359 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 29 Jun 2026 22:05:20 -0400 Subject: [PATCH 02/24] Preferences: modern left-nav (treebook), SVG icons, Random Effects shuttle - Use a left-hand wxTreebook (icon beside label) on all platforms; drop the macOS native preferences window and the dead xLightsPreferencesPage class + mPreferencesEditor member. - Replace the stock/duplicated/low-res page icons with crisp, theme-aware SVG icons (one distinct icon per page) built via wxBitmapBundle::FromSVG. - Rebuild the Random Effects panel as a two-list shuttle (used vs not used) with move buttons + double-click, instead of a checkbox grid. Co-Authored-By: Claude Opus 4.8 --- .../RandomEffectsSettingsPanel.cpp | 141 +++++++++++----- .../preferences/RandomEffectsSettingsPanel.h | 26 ++- src-ui-wx/preferences/xLightsPreferences.cpp | 155 +++++------------- .../wxsmith/RandomEffectsSettingsPanel.wxs | 18 -- src-ui-wx/xLightsMain.h | 2 - 5 files changed, 160 insertions(+), 182 deletions(-) diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp index 96bd88983a..5b037af750 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing @@ -17,7 +17,10 @@ #include //*) +#include +#include #include + #include "effects/RenderableEffect.h" #include "xLightsMain.h" @@ -32,33 +35,60 @@ END_EVENT_TABLE() RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id,const wxPoint& pos,const wxSize& size) : frame(f) { //(*Initialize(RandomEffectsSettingsPanel) - wxStaticText* StaticText1; - Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); - MainSizer = new wxFlexGridSizer(0, 1, 0, 0); - StaticText1 = new wxStaticText(this, wxID_ANY, _("Select Effects for Generate Random"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - MainSizer->Add(StaticText1, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - EffectsGridSizer = new wxFlexGridSizer(0, 4, 0, 0); - MainSizer->Add(EffectsGridSizer, 1, wxALL|wxEXPAND, 0); - SetSizer(MainSizer); - MainSizer->Fit(this); - MainSizer->SetSizeHints(this); //*) - const wxArrayString &selected = frame->RandomEffectsToUse(); + // Two-list shuttle: effects on the right are used by Generate Random, + // effects on the left are not. Arrow buttons (or double-click) move the + // selection between the lists. wxLB_SORT keeps both alphabetical. + auto* mainSizer = new wxBoxSizer(wxVERTICAL); + mainSizer->Add(new wxStaticText(this, wxID_ANY, + _("Effects on the right are used by Generate Random. Move effects between the lists to include or exclude them.")), + 0, wxALL, 5); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + auto* leftCol = new wxBoxSizer(wxVERTICAL); + leftCol->Add(new wxStaticText(this, wxID_ANY, _("Not used")), 0, wxLEFT | wxBOTTOM, 2); + _availableList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(190, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); + leftCol->Add(_availableList, 1, wxEXPAND); + row->Add(leftCol, 1, wxEXPAND | wxRIGHT, 6); + + auto* btnCol = new wxBoxSizer(wxVERTICAL); + auto* btnAdd = new wxButton(this, wxID_ANY, _(">"), wxDefaultPosition, wxSize(44, -1)); + auto* btnRemove = new wxButton(this, wxID_ANY, _("<"), wxDefaultPosition, wxSize(44, -1)); + btnAdd->SetToolTip(_("Use the selected effects")); + btnRemove->SetToolTip(_("Stop using the selected effects")); + btnCol->AddStretchSpacer(); + btnCol->Add(btnAdd, 0, wxBOTTOM, 6); + btnCol->Add(btnRemove, 0); + btnCol->AddStretchSpacer(); + row->Add(btnCol, 0, wxALIGN_CENTER_VERTICAL); + + auto* rightCol = new wxBoxSizer(wxVERTICAL); + rightCol->Add(new wxStaticText(this, wxID_ANY, _("Used")), 0, wxLEFT | wxBOTTOM, 2); + _usedList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(190, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); + rightCol->Add(_usedList, 1, wxEXPAND); + row->Add(rightCol, 1, wxEXPAND | wxLEFT, 6); + + mainSizer->Add(row, 1, wxEXPAND | wxALL, 5); + SetSizer(mainSizer); + mainSizer->SetSizeHints(this); + + const wxArrayString& used = frame->RandomEffectsToUse(); for (int i = 0; i < (int)frame->GetEffectManager().size(); i++) { wxString n = frame->GetEffectManager()[i]->Name(); - bool checked = selected.Index(n) >= 0; - wxWindowID id = wxNewId(); - wxCheckBox *cb = new wxCheckBox(this, id, n, wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, n); - cb->SetValue(checked); - EffectsGridSizer->Add(cb, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - Connect(id,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&RandomEffectsSettingsPanel::OnEffectCheckBoxClick); + if (used.Index(n) >= 0) { + _usedList->Append(n); + } else { + _availableList->Append(n); + } } - EffectsGridSizer->Layout(); - MainSizer->Layout(); - MainSizer->Fit(this); - MainSizer->SetSizeHints(this); + + btnAdd->Bind(wxEVT_BUTTON, &RandomEffectsSettingsPanel::OnAdd, this); + btnRemove->Bind(wxEVT_BUTTON, &RandomEffectsSettingsPanel::OnRemove, this); + _availableList->Bind(wxEVT_LISTBOX_DOUBLECLICK, &RandomEffectsSettingsPanel::OnAvailableDClick, this); + _usedList->Bind(wxEVT_LISTBOX_DOUBLECLICK, &RandomEffectsSettingsPanel::OnUsedDClick, this); } RandomEffectsSettingsPanel::~RandomEffectsSettingsPanel() @@ -67,27 +97,64 @@ RandomEffectsSettingsPanel::~RandomEffectsSettingsPanel() //*) } +void RandomEffectsSettingsPanel::MoveSelected(wxListBox* from, wxListBox* to) +{ + wxArrayInt sel; + from->GetSelections(sel); + if (sel.IsEmpty()) return; + + // Capture the strings before deleting (indices shift on delete). + wxArrayString moving; + for (size_t i = 0; i < sel.GetCount(); ++i) { + moving.Add(from->GetString(sel[i])); + } + // GetSelections returns ascending indices; delete high-to-low so the + // remaining indices stay valid. + for (int i = (int)sel.GetCount() - 1; i >= 0; --i) { + from->Delete(sel[i]); + } + for (const auto& s : moving) { + to->Append(s); // wxLB_SORT keeps the destination alphabetical + } + ApplyIfImmediate(); +} + +void RandomEffectsSettingsPanel::ApplyIfImmediate() +{ + if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { + TransferDataFromWindow(); + } +} + +void RandomEffectsSettingsPanel::OnAdd(wxCommandEvent& event) +{ + MoveSelected(_availableList, _usedList); +} + +void RandomEffectsSettingsPanel::OnRemove(wxCommandEvent& event) +{ + MoveSelected(_usedList, _availableList); +} + +void RandomEffectsSettingsPanel::OnAvailableDClick(wxCommandEvent& event) +{ + MoveSelected(_availableList, _usedList); +} + +void RandomEffectsSettingsPanel::OnUsedDClick(wxCommandEvent& event) +{ + MoveSelected(_usedList, _availableList); +} + bool RandomEffectsSettingsPanel::TransferDataToWindow() { return true; } + bool RandomEffectsSettingsPanel::TransferDataFromWindow() { wxArrayString selected; - for (int x = 0; x < (int)EffectsGridSizer->GetItemCount(); x++) { - wxCheckBox *CheckBox1 = dynamic_cast(EffectsGridSizer->GetItem(x)->GetWindow()); - if (CheckBox1) { - wxString n = CheckBox1->GetLabel(); - bool checked = CheckBox1->IsChecked(); - if (checked) { - selected.push_back(n); - } - } + for (unsigned int i = 0; i < _usedList->GetCount(); ++i) { + selected.push_back(_usedList->GetString(i)); } frame->SetRandomEffectsToUse(selected); return true; } -void RandomEffectsSettingsPanel::OnEffectCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.h b/src-ui-wx/preferences/RandomEffectsSettingsPanel.h index 21c2ef1736..24b386e745 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.h +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing @@ -12,11 +12,12 @@ //(*Headers(RandomEffectsSettingsPanel) #include -class wxFlexGridSizer; -class wxStaticText; //*) +class wxListBox; +class wxCommandEvent; class xLightsFrame; + class RandomEffectsSettingsPanel: public wxPanel { public: @@ -25,10 +26,8 @@ class RandomEffectsSettingsPanel: public wxPanel virtual ~RandomEffectsSettingsPanel(); //(*Declarations(RandomEffectsSettingsPanel) - wxFlexGridSizer* EffectsGridSizer; - wxFlexGridSizer* MainSizer; //*) - + virtual bool TransferDataFromWindow() override; virtual bool TransferDataToWindow() override; @@ -39,10 +38,21 @@ class RandomEffectsSettingsPanel: public wxPanel private: xLightsFrame* frame; - + wxListBox* _availableList = nullptr; // effects NOT used by Generate Random + wxListBox* _usedList = nullptr; // effects used by Generate Random + + // Move the selected rows from one list to the other (both kept sorted). + void MoveSelected(wxListBox* from, wxListBox* to); + // Mirror the original checkbox behaviour: write changes back immediately + // on platforms where the preferences editor applies as-you-go. + void ApplyIfImmediate(); + //(*Handlers(RandomEffectsSettingsPanel) - void OnEffectCheckBoxClick(wxCommandEvent& event); //*) + void OnAdd(wxCommandEvent& event); + void OnRemove(wxCommandEvent& event); + void OnAvailableDClick(wxCommandEvent& event); + void OnUsedDClick(wxCommandEvent& event); DECLARE_EVENT_TABLE() }; diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index d57e48dc80..141896787f 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -9,15 +9,15 @@ **************************************************************/ #include +#include #include -#include -#include #include -#include +#include #include #include "xLightsMain.h" +#include "shared/utils/wxUtilities.h" // IsDarkMode() #include "ViewSettingsPanel.h" #include "EffectsGridSettingsPanel.h" @@ -30,79 +30,30 @@ #include "CheckSequenceSettingsPanel.h" #include "ServicesPanel.h" -#include "grid_icon.xpm" -#include "settings_panel_icon.xpm" - namespace { -// Shared description of a preferences page so the macOS native editor and the -// desktop list dialog stay in lockstep when pages are added or reordered. +// Description of a preferences page: name, left-list icon, and a factory that +// builds the panel. Pages render in a left-hand list on every platform. struct PrefPageDef { wxString name; - wxBitmapBundle nativeIcon; // larger icon for the native macOS toolbar - wxBitmapBundle listIcon; // uniform small icon for the left-hand list + wxBitmapBundle icon; std::function factory; }; -} - -class xLightsPreferencesPage : public wxPreferencesPage { -public: - xLightsPreferencesPage(const wxString &n, const wxBitmapBundle &i, std::function & f) : wxPreferencesPage(), m_icon(i), m_name(n), m_createFunction(f) { - } - - virtual wxString GetName() const override { - return m_name; - } - - virtual wxBitmapBundle GetIcon() const override { - return m_icon; - } - virtual wxWindow *CreateWindow (wxWindow *parent) override { -#ifdef __WXMSW__ - auto *scrolledWindow = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL); - scrolledWindow->SetScrollRate(10, 10); - - wxWindow *content = m_createFunction(scrolledWindow); - auto *sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(content, 1, wxEXPAND | wxALL, 2); - - scrolledWindow->SetSizer(sizer); - scrolledWindow->FitInside(); - - const wxSize screenSize = wxGetDisplaySize(); - int screenWidth = screenSize.GetWidth() * 0.90; - int screenHeight = screenSize.GetHeight() * 0.45; - - int minWidth = std::min(screenWidth, 850); - int minHeight = std::min(screenHeight, 375); - int maxHeight = std::max(screenHeight, 250); - - scrolledWindow->SetMinSize(wxSize(minWidth, minHeight)); - scrolledWindow->SetMaxSize(wxSize(screenWidth, maxHeight)); - scrolledWindow->Layout(); - - return scrolledWindow; -#else - wxWindow *w = m_createFunction(parent); -#ifdef __WXOSX__ - //need to set a minimum width or the icons get moved into a flyout menu - //which is more confusing - w->SetMinSize(wxSize(500, -1)); -#endif - return w; -#endif +// Build a crisp, theme-aware page icon from an inline SVG body. The body uses +// "%C%" wherever the ink colour should appear (strokes inherit it from the +// wrapper; filled dots set fill="%C%"). Substituting the colour keeps the icon +// legible in both light and dark mode, and SVG keeps it sharp at any DPI. +wxBitmapBundle PrefSvgIcon(const std::string& innerSvg, const std::string& ink) { + std::string svg = std::string(R"()") + innerSvg + ""; + for (size_t p = svg.find("%C%"); p != std::string::npos; p = svg.find("%C%")) { + svg.replace(p, 3, ink); } + return wxBitmapBundle::FromSVG(svg.c_str(), wxSize(24, 24)); +} -private: - wxBitmapBundle m_icon; - wxString m_name; - std::function m_createFunction; -}; - -#ifndef __WXOSX__ -// A preferences dialog with a vertical list of pages on the left selects -// the panel shown on the right (instead of tabs across the top). Used on -// Windows/Linux; macOS keeps the native preferences window. +// A preferences dialog with a vertical list of pages on the left that selects +// the panel shown on the right. Used on every platform so Preferences matches +// the rest of the xLights UI. class xlPreferencesListDialog : public wxDialog { public: xlPreferencesListDialog(wxWindow* parent, const std::vector& pages) @@ -111,14 +62,14 @@ class xlPreferencesListDialog : public wxDialog { SetExtraStyle(GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY); auto* topSizer = new wxBoxSizer(wxVERTICAL); - auto* listbook = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLB_LEFT); + auto* book = new wxTreebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); std::vector images; images.reserve(pages.size()); for (const auto& p : pages) { - images.push_back(p.listIcon); + images.push_back(p.icon); } - listbook->SetImages(images); + book->SetImages(images); const wxSize screenSize = wxGetDisplaySize(); int minWidth = std::min((int)(screenSize.GetWidth() * 0.90), 850); @@ -127,7 +78,7 @@ class xlPreferencesListDialog : public wxDialog { int idx = 0; for (const auto& p : pages) { // Wrap each panel in a scrolled window so tall panels stay usable. - auto* scrolledWindow = new wxScrolledWindow(listbook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL); + auto* scrolledWindow = new wxScrolledWindow(book, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL); scrolledWindow->SetScrollRate(10, 10); wxWindow* content = p.factory(scrolledWindow); auto* sizer = new wxBoxSizer(wxVERTICAL); @@ -136,11 +87,11 @@ class xlPreferencesListDialog : public wxDialog { scrolledWindow->FitInside(); scrolledWindow->SetMinSize(wxSize(minWidth, minHeight)); - listbook->AddPage(scrolledWindow, p.name, idx == 0, idx); + book->AddPage(scrolledWindow, p.name, idx == 0, idx); ++idx; } - topSizer->Add(listbook, 1, wxEXPAND | wxALL, 5); + topSizer->Add(book, 1, wxEXPAND | wxALL, 5); topSizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); SetSizer(topSizer); @@ -149,7 +100,7 @@ class xlPreferencesListDialog : public wxDialog { CentreOnParent(); } }; -#endif +} void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) { @@ -160,75 +111,45 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) auto ld = _lowDefinitionRender; - wxImage gridImage(GRID_ICON_64); - wxBitmap gridIcon(gridImage); - wxImage settingsImage(SETTINGS_PANEL_ICON); - wxBitmap settingIcon(settingsImage); - - const wxSize iconSize(64, 64); - const wxSize listIconSize(24, 24); - - auto scaledBundle = [](const wxImage& img, const wxSize& sz) { - return wxBitmapBundle(wxBitmap(img.Scale(sz.GetWidth(), sz.GetHeight(), wxIMAGE_QUALITY_HIGH))); - }; + // Ink colour for the page icons - light glyphs on dark mode, dark on light. + const std::string ink = IsDarkMode() ? "#E0E0E0" : "#3A3A3A"; std::vector pages; pages.push_back({ "Backup", - wxArtProvider::GetBitmapBundle(wxART_HARDDISK, wxART_BUTTON, wxSize(28, 28)), - wxArtProvider::GetBitmapBundle(wxART_HARDDISK, wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new BackupSettingsPanel(p, this)); } }); pages.push_back({ "View", - wxArtProvider::GetBitmapBundle(wxART_FULL_SCREEN, wxART_BUTTON, iconSize), - wxArtProvider::GetBitmapBundle(wxART_FULL_SCREEN, wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new ViewSettingsPanel(p, this)); } }); pages.push_back({ "Effects Grid", - wxBitmapBundle(gridIcon), - scaledBundle(gridImage, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new EffectsGridSettingsPanel(p, this)); } }); pages.push_back({ "Sequences", - wxArtProvider::GetBitmapBundle("xlART_SETTINGS", wxART_BUTTON, iconSize), - wxArtProvider::GetBitmapBundle("xlART_SETTINGS", wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new SequenceFileSettingsPanel(p, this)); } }); pages.push_back({ "Output", - wxArtProvider::GetBitmapBundle("xlART_OUTPUT_LIGHTS_ON", wxART_BUTTON, iconSize), - wxArtProvider::GetBitmapBundle("xlART_OUTPUT_LIGHTS_ON", wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new OutputSettingsPanel(p, this)); } }); pages.push_back({ "Check Sequence", - wxArtProvider::GetBitmapBundle("xlART_SETTINGS", wxART_BUTTON, iconSize), - wxArtProvider::GetBitmapBundle("xlART_SETTINGS", wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new CheckSequenceSettingsPanel(p, this)); } }); pages.push_back({ "Random Effects", - wxArtProvider::GetBitmapBundle("xlART_DICE_ICON", wxART_BUTTON, wxSize(28, 28)), - wxArtProvider::GetBitmapBundle("xlART_DICE_ICON", wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new RandomEffectsSettingsPanel(p, this)); } }); pages.push_back({ "Colors", - wxArtProvider::GetBitmapBundle("xlART_RENDER_ALL", wxART_BUTTON, iconSize), - wxArtProvider::GetBitmapBundle("xlART_RENDER_ALL", wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new ColorManagerSettingsPanel(p, this)); } }); pages.push_back({ "Other", - wxBitmapBundle(settingIcon), - scaledBundle(settingsImage, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new OtherSettingsPanel(p, this)); } }); #ifdef ENABLE_SERVICES pages.push_back({ "Services", - wxArtProvider::GetBitmapBundle("xlART_SETTINGS", wxART_BUTTON, iconSize), - wxArtProvider::GetBitmapBundle("xlART_SETTINGS", wxART_BUTTON, listIconSize), + PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new ServicesPanel(p, _serviceManager.get())); } }); #endif -#ifdef __WXOSX__ - if (!mPreferencesEditor.get()) { - mPreferencesEditor.reset(new wxPreferencesEditor("Preferences")); - for (auto& p : pages) { - std::function f = p.factory; - mPreferencesEditor->AddPage(new xLightsPreferencesPage(p.name, p.nativeIcon, f)); - } - } - mPreferencesEditor->Show(this); -#else xlPreferencesListDialog dlg(this, pages); dlg.ShowModal(); -#endif if (mRenderOnSave) { MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Render All and Save")); diff --git a/src-ui-wx/wxsmith/RandomEffectsSettingsPanel.wxs b/src-ui-wx/wxsmith/RandomEffectsSettingsPanel.wxs index 8bfc08f050..674caac198 100644 --- a/src-ui-wx/wxsmith/RandomEffectsSettingsPanel.wxs +++ b/src-ui-wx/wxsmith/RandomEffectsSettingsPanel.wxs @@ -3,23 +3,5 @@ 1 1 - - 1 - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - 4 - - wxALL|wxEXPAND - - - diff --git a/src-ui-wx/xLightsMain.h b/src-ui-wx/xLightsMain.h index 38cdec740c..43a3737397 100755 --- a/src-ui-wx/xLightsMain.h +++ b/src-ui-wx/xLightsMain.h @@ -1755,8 +1755,6 @@ private : bool SeqChanCtrlBasic; bool SeqChanCtrlColor; bool mLoopAudio = false; - - std::unique_ptr mPreferencesEditor; bool mResetToolbars = false; bool mRenderOnSave = false; bool mBackupOnSave = false; From 7ad99d152852f88e42775699a1261fe12d80e384 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 29 Jun 2026 22:45:53 -0400 Subject: [PATCH 03/24] Preferences: embed Key Bindings editor as a page; drop File-menu item Port the standalone Key Bindings dialog into a new KeyBindingsSettingsPanel (preferences page): the same filterable, scope-scoped bindings list with friendly action names, real shortcut column, details, zebra striping, and per-row tooltip, launching the modal KeyBindingPopupEditor to edit a binding. Edits apply to the live KeyBindingMap immediately and persist via keyBindings.Save() when Preferences is accepted. - Register the page in xLightsPreferences.cpp (keyboard icon). - Remove the File-menu 'Key bindings' item (MenuItem_KeyBindings, its id, Connect, handler, modeless logic) from xLightsMain.{cpp,h} and the .wxs; the separate Help-menu 'Key Bindings' cheat-sheet is unchanged. - Delete the now-unused KeyBindingEditDialog.{cpp,h,wxs} and the dead inline property-grid editing path; update .cbp/.vcxproj/.filters. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/app-shell/KeyBindingEditDialog.cpp | 854 ------------------ src-ui-wx/app-shell/KeyBindingEditDialog.h | 111 --- .../preferences/KeyBindingsSettingsPanel.cpp | 544 +++++++++++ .../preferences/KeyBindingsSettingsPanel.h | 74 ++ .../RandomEffectsSettingsPanel.cpp | 4 +- src-ui-wx/preferences/xLightsPreferences.cpp | 4 + src-ui-wx/wxsmith/KeyBindingEditDialog.wxs | 121 --- src-ui-wx/wxsmith/xLightsframe.wxs | 4 - src-ui-wx/xLightsMain.cpp | 25 - src-ui-wx/xLightsMain.h | 3 - xLights/Xlights.vcxproj | 4 +- xLights/Xlights.vcxproj.filters | 8 +- xLights/xLights.cbp | 6 +- 13 files changed, 634 insertions(+), 1128 deletions(-) delete mode 100644 src-ui-wx/app-shell/KeyBindingEditDialog.cpp delete mode 100644 src-ui-wx/app-shell/KeyBindingEditDialog.h create mode 100644 src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp create mode 100644 src-ui-wx/preferences/KeyBindingsSettingsPanel.h delete mode 100644 src-ui-wx/wxsmith/KeyBindingEditDialog.wxs diff --git a/src-ui-wx/app-shell/KeyBindingEditDialog.cpp b/src-ui-wx/app-shell/KeyBindingEditDialog.cpp deleted file mode 100644 index e2b74c8832..0000000000 --- a/src-ui-wx/app-shell/KeyBindingEditDialog.cpp +++ /dev/null @@ -1,854 +0,0 @@ - -/*************************************************************** - * This source files comes from the xLights project - * https://www.xlights.org - * https://github.com/xLightsSequencer/xLights - * See the github commit history for a record of contributing - * developers. - * Copyright claimed based on commit dates recorded in Github - * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt - **************************************************************/ - -//(*InternalHeaders(KeyBindingEditDialog) -#include -#include -//*) -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "KeyBindingEditDialog.h" -#include "KeyBindings.h" -#include "effects/EffectManager.h" -#include "effects/RenderableEffect.h" -#include "xLightsMain.h" -#include "shared/utils/xlPropertyGrid.h" - -//(*IdInit(KeyBindingEditDialog) -const long KeyBindingEditDialog::ID_STATICTEXT1 = wxNewId(); -const long KeyBindingEditDialog::ID_CHOICE1 = wxNewId(); -const long KeyBindingEditDialog::ID_LISTCTRL1 = wxNewId(); -const long KeyBindingEditDialog::ID_PANEL1 = wxNewId(); -const long KeyBindingEditDialog::ID_BUTTON1 = wxNewId(); -const long KeyBindingEditDialog::ID_BUTTON3 = wxNewId(); -const long KeyBindingEditDialog::ID_BUTTON2 = wxNewId(); -const long KeyBindingEditDialog::ID_BUTTON_SAVE = wxNewId(); -const long KeyBindingEditDialog::ID_BUTTON_CANCEL = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(KeyBindingEditDialog,wxDialog) - //(*EventTable(KeyBindingEditDialog) - //*) -END_EVENT_TABLE() - -namespace { -// Modal editor for a single key binding. Edits native controls; ApplyTo() writes -// the result back to the live binding only when the user accepts (wxID_OK). -class KeyBindingPopupEditor : public wxDialog -{ -public: - KeyBindingPopupEditor(wxWindow* parent, const KeyBinding& b, EffectManager* em, xLightsFrame* xl) - : wxDialog(parent, wxID_ANY, _("Edit Shortcut"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), - _type(b.GetType()) - { - auto* grid = new wxFlexGridSizer(0, 2, 6, 10); - grid->AddGrowableCol(1); - - grid->Add(new wxStaticText(this, wxID_ANY, _("Key:")), 0, wxALIGN_CENTER_VERTICAL); - _key = new wxChoice(this, wxID_ANY); - _key->Append(_("(none)")); - int sel = 0; - int k = b.GetKey(); - if (k >= 'A' && k <= 'Z') k += 32; - for (const auto& it : KeyBinding::GetPossibleKeys()) { - _key->Append(KeyBinding::EncodeKey(it, false)); - _keys.push_back(it); - if (it == k) sel = (int)_keys.size(); - } - _key->SetSelection(sel); - grid->Add(_key, 1, wxEXPAND); - - grid->Add(new wxStaticText(this, wxID_ANY, _("Modifiers:")), 0, wxALIGN_TOP | wxTOP, 4); - auto* mods = new wxBoxSizer(wxVERTICAL); -#ifdef __WXOSX__ - _ctrl = new wxCheckBox(this, wxID_ANY, L"Command ⌘"); - _alt = new wxCheckBox(this, wxID_ANY, L"Option ⌥"); - _shift = new wxCheckBox(this, wxID_ANY, L"Shift ⇧"); - _rctrl = new wxCheckBox(this, wxID_ANY, L"Control ⌃"); -#else - _ctrl = new wxCheckBox(this, wxID_ANY, _("Control")); - _alt = new wxCheckBox(this, wxID_ANY, _("Alt")); - _shift = new wxCheckBox(this, wxID_ANY, _("Shift")); - _rctrl = new wxCheckBox(this, wxID_ANY, _("macOS Ctrl")); -#endif - _ctrl->SetValue(b.RequiresControl()); - _alt->SetValue(b.RequiresAlt()); - _shift->SetValue(b.RequiresShift()); - _rctrl->SetValue(b.RequiresRawControl()); - mods->Add(_ctrl); - mods->Add(_alt); - mods->Add(_shift); - mods->Add(_rctrl); - grid->Add(mods, 1, wxEXPAND); - - if (_type == "EFFECT") { - grid->Add(new wxStaticText(this, wxID_ANY, _("Effect:")), 0, wxALIGN_CENTER_VERTICAL); - _effect = new wxChoice(this, wxID_ANY); - _effect->Append(""); - for (const auto& it : *em) { - _effect->Append(it->Name()); - if (it->Name() == b.GetEffectName()) _effect->SetSelection(_effect->GetCount() - 1); - } - if (_effect->GetSelection() == wxNOT_FOUND) _effect->SetSelection(0); - grid->Add(_effect, 1, wxEXPAND); - } - if (_type == "EFFECT" || _type == "APPLYSETTING") { - grid->Add(new wxStaticText(this, wxID_ANY, _("Effect Setting:")), 0, wxALIGN_CENTER_VERTICAL); - _setting = new wxTextCtrl(this, wxID_ANY, b.GetEffectString()); - grid->Add(_setting, 1, wxEXPAND); - } - if (_type == "PRESET") { - grid->Add(new wxStaticText(this, wxID_ANY, _("Preset:")), 0, wxALIGN_CENTER_VERTICAL); - _preset = new wxChoice(this, wxID_ANY); - _preset->Append(""); - for (const auto& it : xl->GetPresets()) { - _preset->Append(it); - if (it == b.GetEffectName()) _preset->SetSelection(_preset->GetCount() - 1); - } - if (_preset->GetSelection() == wxNOT_FOUND) _preset->SetSelection(0); - grid->Add(_preset, 1, wxEXPAND); - } - - // Header: the friendly name (prominent) and its description. The raw - // action/type isn't shown - it's not meaningful to most users. - auto* nameText = new wxStaticText(this, wxID_ANY, KeyBindingEditDialog::FriendlyName(b.GetType())); - wxFont nameFont = nameText->GetFont(); - nameFont.MakeBold(); - nameFont.SetPointSize(nameFont.GetPointSize() + 3); - nameText->SetFont(nameFont); - - auto* descText = new wxStaticText(this, wxID_ANY, b.GetTip()); - descText->Wrap(440); - - auto* top = new wxBoxSizer(wxVERTICAL); - top->Add(nameText, 0, wxLEFT | wxRIGHT | wxTOP, 14); - top->Add(descText, 0, wxLEFT | wxRIGHT | wxTOP, 6); - top->Add(new wxStaticLine(this, wxID_ANY), 0, wxEXPAND | wxALL, 12); - top->Add(grid, 1, wxEXPAND | wxLEFT | wxRIGHT, 14); - top->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 12); - SetSizerAndFit(top); - SetMinSize(GetSize()); - CenterOnParent(); - } - - void ApplyTo(KeyBinding& b) const - { - const int s = _key->GetSelection(); - if (s <= 0) { - b.SetKey(WXK_NONE); - } else { - b.SetKey(_keys[s - 1]); - } - b.SetControl(_ctrl->GetValue()); - b.SetAlt(_alt->GetValue()); - b.SetShift(_shift->GetValue()); - b.SetRawControl(_rctrl->GetValue()); - if (_effect != nullptr) b.SetEffectName(_effect->GetStringSelection().ToStdString()); - if (_preset != nullptr) b.SetEffectName(_preset->GetStringSelection().ToStdString()); - if (_setting != nullptr) b.SetEffectString(_setting->GetValue().ToStdString()); - } - -private: - std::string _type; - wxChoice* _key = nullptr; - std::vector _keys; - wxCheckBox* _ctrl = nullptr; - wxCheckBox* _alt = nullptr; - wxCheckBox* _shift = nullptr; - wxCheckBox* _rctrl = nullptr; - wxChoice* _effect = nullptr; - wxChoice* _preset = nullptr; - wxTextCtrl* _setting = nullptr; -}; -} // namespace - -KeyBindingEditDialog::KeyBindingEditDialog(xLightsFrame* parent, KeyBindingMap* keyBindings, EffectManager* effectManager, wxWindowID id,const wxPoint& pos,const wxSize& size) -{ - _xLights = parent; - _keyBindings = keyBindings; - _effectManager = effectManager; - - //(*Initialize(KeyBindingEditDialog) - wxFlexGridSizer* FlexGridSizer1; - wxFlexGridSizer* FlexGridSizer4; - wxFlexGridSizer* FlexGridSizer5; - - Create(parent, id, _("Edit Keybindings"), wxDefaultPosition, wxDefaultSize, wxCAPTION|wxRESIZE_BORDER|wxMAXIMIZE_BOX, _T("id")); - SetClientSize(wxDefaultSize); - Move(wxDefaultPosition); - FlexGridSizer1 = new wxFlexGridSizer(0, 2, 0, 0); - FlexGridSizer1->AddGrowableCol(0); - FlexGridSizer1->AddGrowableRow(1); - FlexGridSizer4 = new wxFlexGridSizer(0, 2, 0, 0); - FlexGridSizer4->AddGrowableCol(1); - StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Scope:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT1")); - FlexGridSizer4->Add(StaticText1, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - Choice_Scope = new wxChoice(this, ID_CHOICE1, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE1")); - FlexGridSizer4->Add(Choice_Scope, 1, wxALL|wxEXPAND, 5); - FlexGridSizer1->Add(FlexGridSizer4, 1, wxALL|wxEXPAND, 2); - FlexGridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 2); - ListCtrl_Bindings = new wxListCtrl(this, ID_LISTCTRL1, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_SINGLE_SEL, wxDefaultValidator, _T("ID_LISTCTRL1")); - FlexGridSizer1->Add(ListCtrl_Bindings, 1, wxALL|wxEXPAND, 2); - Panel_Properties = new wxPanel(this, ID_PANEL1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL1")); - FlexGridSizer3 = new wxFlexGridSizer(0, 1, 0, 0); - FlexGridSizer3->AddGrowableCol(0); - FlexGridSizer3->AddGrowableRow(0); - Panel_Properties->SetSizer(FlexGridSizer3); - FlexGridSizer3->Fit(Panel_Properties); - FlexGridSizer3->SetSizeHints(Panel_Properties); - FlexGridSizer1->Add(Panel_Properties, 1, wxALL|wxEXPAND, 2); - FlexGridSizer5 = new wxFlexGridSizer(0, 5, 0, 0); - Button_AddEffect = new wxButton(this, ID_BUTTON1, _("Add Effect"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON1")); - FlexGridSizer5->Add(Button_AddEffect, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - ButtonAddPreset = new wxButton(this, ID_BUTTON3, _("Add Preset"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON3")); - FlexGridSizer5->Add(ButtonAddPreset, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - ButtonAddApplySetting = new wxButton(this, ID_BUTTON2, _("Add Apply Setting"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON2")); - FlexGridSizer5->Add(ButtonAddApplySetting, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - ButtonSave = new wxButton(this, ID_BUTTON_SAVE, _("Save"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON_SAVE")); - FlexGridSizer5->Add(ButtonSave, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - Button_Close = new wxButton(this, ID_BUTTON_CANCEL, _("Close"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON_CANCEL")); - FlexGridSizer5->Add(Button_Close, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - FlexGridSizer1->Add(FlexGridSizer5, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - SetSizer(FlexGridSizer1); - FlexGridSizer1->Fit(this); - FlexGridSizer1->SetSizeHints(this); - - Connect(ID_CHOICE1,wxEVT_COMMAND_CHOICE_SELECTED,(wxObjectEventFunction)&KeyBindingEditDialog::OnChoice_ScopeSelect); - Connect(ID_LISTCTRL1,wxEVT_COMMAND_LIST_DELETE_ITEM,(wxObjectEventFunction)&KeyBindingEditDialog::OnListCtrl_BindingsDeleteItem); - Connect(ID_LISTCTRL1,wxEVT_COMMAND_LIST_ITEM_SELECTED,(wxObjectEventFunction)&KeyBindingEditDialog::OnListCtrl_BindingsItemSelect); - Connect(ID_LISTCTRL1,wxEVT_COMMAND_LIST_ITEM_FOCUSED,(wxObjectEventFunction)&KeyBindingEditDialog::OnListCtrl_BindingsItemFocused); - Connect(ID_LISTCTRL1,wxEVT_COMMAND_LIST_KEY_DOWN,(wxObjectEventFunction)&KeyBindingEditDialog::OnListCtrl_BindingsKeyDown); - Connect(ID_BUTTON1,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&KeyBindingEditDialog::OnButton_AddEffectClick); - Connect(ID_BUTTON3,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&KeyBindingEditDialog::OnButtonAddPresetClick); - Connect(ID_BUTTON2,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&KeyBindingEditDialog::OnButtonAddApplySettingClick); - Connect(ID_BUTTON_SAVE,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&KeyBindingEditDialog::OnButtonSaveClick); - Connect(ID_BUTTON_CANCEL,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&KeyBindingEditDialog::OnButton_CancelClick); - //*) - - Panel_Properties->SetMinSize(wxSize(500, -1)); - Layout(); - - Choice_Scope->AppendString("All"); - //Choice_Scope->AppendString("Controller"); - Choice_Scope->AppendString("Layout"); - Choice_Scope->AppendString("Sequencer"); - Choice_Scope->AppendString("All tabs"); - - Choice_Scope->SetStringSelection("All"); - - // Live filter for the bindings list. Added here (outside the wxSmith guard) - // and stacked under the Scope row, so no .wxs change is needed. - FlexGridSizer4->Add(new wxStaticText(this, wxID_ANY, _("Filter:")), 1, wxALL | wxALIGN_CENTER_VERTICAL, 5); - _filterCtrl = new wxSearchCtrl(this, wxID_ANY); - _filterCtrl->ShowCancelButton(true); - _filterCtrl->SetDescriptiveText(_("Filter actions, shortcuts or descriptions")); - FlexGridSizer4->Add(_filterCtrl, 1, wxALL | wxEXPAND, 5); - _filterCtrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { _filter = _filterCtrl->GetValue().Lower(); LoadList(); }); - _filterCtrl->Bind(wxEVT_SEARCHCTRL_CANCEL_BTN, [this](wxCommandEvent&) { _filterCtrl->ChangeValue(""); _filter.clear(); LoadList(); }); - Layout(); - - ListCtrl_Bindings->AppendColumn("Action"); - ListCtrl_Bindings->AppendColumn("Shortcut", wxLIST_FORMAT_CENTRE, wxLIST_AUTOSIZE_USEHEADER); - ListCtrl_Bindings->AppendColumn("Details"); - - // wxListCtrl has no per-row tooltip, so track the hovered row and show - // that binding's raw type + description as the control tooltip. - ListCtrl_Bindings->Bind(wxEVT_MOTION, &KeyBindingEditDialog::OnListMouseMotion, this); - // Modeless: clean up on close instead of EndModal. - Bind(wxEVT_CLOSE_WINDOW, &KeyBindingEditDialog::OnClose, this); - SetName(WINDOW_NAME); // so the menu handler can find/reuse this instance - - LoadList(); - - ListCtrl_Bindings->SetColumnWidth(0, wxCOL_WIDTH_AUTOSIZE); - ListCtrl_Bindings->SetColumnWidth(1, wxCOL_WIDTH_AUTOSIZE); - ListCtrl_Bindings->SetColumnWidth(2, wxCOL_WIDTH_AUTOSIZE); - - _propertyGrid = new xlPropertyGrid(Panel_Properties, wxID_ANY, wxDefaultPosition, wxDefaultSize, - // Here are just some of the supported window styles - //wxPG_AUTO_SORT | // Automatic sorting after items added - wxPG_SPLITTER_AUTO_CENTER | // Automatically center splitter until user manually adjusts it - // Default style - wxPG_DEFAULT_STYLE); - _propertyGrid->SetExtraStyle(wxWS_EX_PROCESS_IDLE | wxPG_EX_HELP_AS_TOOLTIPS); - FlexGridSizer3->Add(_propertyGrid, 1, wxALL | wxEXPAND, 5); - _propertyGrid->Connect(wxEVT_PG_CHANGED, (wxObjectEventFunction)&KeyBindingEditDialog::OnControllerPropertyGridChange, 0, this); - _propertyGrid->SetValidationFailureBehavior(wxPGVFBFlags::MarkCell | wxPGVFBFlags::Beep); - - // Constrain dialog size to fit within the display's client area - int targetWidth = 1200; - int targetHeight = 700; - int d = wxDisplay::GetFromWindow(this); - if (d < 0) d = 0; - wxDisplay display(d); - if (display.IsOk()) { - wxRect displayRect = display.GetClientArea(); - // Leave some margin (50 pixels) around the edges - int maxWidth = displayRect.GetWidth() - 100; - int maxHeight = displayRect.GetHeight() - 100; - if (targetWidth > maxWidth) targetWidth = maxWidth; - if (targetHeight > maxHeight) targetHeight = maxHeight; - } - SetSize(targetWidth, targetHeight); - - // Edit... button, plus double-click / Enter on a row (ITEM_ACTIVATED), open - // the popup editor for the selected binding. The button is disabled while - // nothing is selected. - _editButton = new wxButton(this, wxID_ANY, _("Edit...")); - FlexGridSizer5->Insert(0, _editButton, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); - _editButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { DoEditSelected(); }); - ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent&) { DoEditSelected(); }); - ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_SELECTED, [this](wxListEvent& e) { e.Skip(); UpdateEditEnabled(); }); - ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_DESELECTED, [this](wxListEvent& e) { e.Skip(); UpdateEditEnabled(); }); - // Stretch the last (Details) column to fill the list width so rows/zebra - // extend full width when the window is resized. - ListCtrl_Bindings->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { - e.Skip(); - if (ListCtrl_Bindings->GetColumnCount() < 3) return; - const int total = ListCtrl_Bindings->GetClientSize().GetWidth(); - const int used = ListCtrl_Bindings->GetColumnWidth(0) + ListCtrl_Bindings->GetColumnWidth(1); - if (total - used > 120) ListCtrl_Bindings->SetColumnWidth(2, total - used); - }); - - // Editing now happens in a popup, so drop the right-hand property panel and - // rebuild the layout as a single column (scope/filter row, full-width list, - // buttons). Hiding the panel alone left its grid column behind. - Panel_Properties->Hide(); - FlexGridSizer1->Detach(FlexGridSizer4); - FlexGridSizer1->Detach(ListCtrl_Bindings); - FlexGridSizer1->Detach(FlexGridSizer5); - auto* colSizer = new wxBoxSizer(wxVERTICAL); - colSizer->Add(FlexGridSizer4, 0, wxEXPAND | wxALL, 2); - colSizer->Add(ListCtrl_Bindings, 1, wxEXPAND | wxALL, 2); - colSizer->Add(FlexGridSizer5, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5); - SetSizer(colSizer, true); - SetMinSize(wxSize(550, 400)); - SetSize(wxSize(800, 700)); - Layout(); -} - -int KeyBindingEditDialog::GetSelectedKeyBindingIndex() const { - - return ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); -} - -void KeyBindingEditDialog::UpdateEditEnabled() -{ - if (_editButton != nullptr) _editButton->Enable(GetSelectedKeyBindingIndex() >= 0); -} - -void KeyBindingEditDialog::RefreshRow(long index, const KeyBinding& b) -{ - ListCtrl_Bindings->SetItem(index, 1, RenderShortcut(b)); - ListCtrl_Bindings->SetItem(index, 2, BuildDetails(b)); - RefreshDuplicateHighlights(); -} - -void KeyBindingEditDialog::DoEditSelected() -{ - int index = GetSelectedKeyBindingIndex(); - if (index < 0) return; - int id = (int)ListCtrl_Bindings->GetItemData(index); - if (id < 0) return; - - KeyBinding& b = _keyBindings->GetBinding(id); - KeyBindingPopupEditor editor(this, b, _effectManager, _xLights); - if (editor.ShowModal() == wxID_OK) { - editor.ApplyTo(b); - RefreshRow(index, b); - } -} - -void KeyBindingEditDialog::SetKeyBindingProperties() { - - int index = GetSelectedKeyBindingIndex(); - - _propertyGrid->Freeze(); - - // save property grid location - auto save = _propertyGrid->SaveEditableState(); - wxString selProp = ""; - if (_propertyGrid->GetSelection() != nullptr) { - selProp = _propertyGrid->GetSelection()->GetName(); - } - - _propertyGrid->Clear(); - - if (index < 0) { - _propertyGrid->Thaw(); - return; - } - - int id = (int)ListCtrl_Bindings->GetItemData(index); - - if (id < 0) { - _propertyGrid->Thaw(); - return; - } - - KeyBinding& b = _keyBindings->GetBinding(id); - - wxPGProperty* p = _propertyGrid->Append(new wxStringProperty("Type", "KBType", b.GetType())); - p->ChangeFlag(wxPGFlags::ReadOnly, true); - p->SetHelpString(b.GetTip()); - - int k = b.GetKey(); - if (k >= 65 && k <= 90) k += 32; - - wxPGChoices choices; - int val = 0; - choices.Add(""); - for (const auto& it : KeyBinding::GetPossibleKeys()) { - if (it == k) val = choices.GetCount(); - choices.Add(KeyBinding::EncodeKey(it, false)); - } - _propertyGrid->Append(new wxEnumProperty("Key", "KBKey", choices, val)); - - p = _propertyGrid->Append(new wxBoolProperty("Control", "KBControl", b.RequiresControl())); - p->SetEditor("CheckBox"); - - p = _propertyGrid->Append(new wxBoolProperty("Alt", "KBAlt", b.RequiresAlt())); - p->SetEditor("CheckBox"); - - p = _propertyGrid->Append(new wxBoolProperty("Shift", "KBShift", b.RequiresShift())); - p->SetEditor("CheckBox"); - - p = _propertyGrid->Append(new wxBoolProperty("RawControl", "KBRawControl", b.RequiresRawControl())); - p->SetEditor("CheckBox"); - - if (b.GetType() == "EFFECT") - { - wxPGChoices effchoices; - val = 0; - effchoices.Add(""); - for (const auto& it : *_effectManager) { - if (it->Name() == b.GetEffectName()) val = effchoices.GetCount(); - effchoices.Add(it->Name()); - } - - _propertyGrid->Append(new wxEnumProperty("Effect", "KBEffect", effchoices, val)); - } - - if (b.GetType() == "EFFECT" || b.GetType() == "APPLYSETTING") - { - _propertyGrid->Append(new wxStringProperty("Effect Setting", "KBEffectSetting", b.GetEffectString())); - } - - if (b.GetType() == "PRESET") - { - wxPGChoices presetchoices; - val = 0; - presetchoices.Add(""); - for (const auto& it : _xLights->GetPresets()) { - if (it == b.GetEffectName()) val = presetchoices.GetCount(); - presetchoices.Add(it); - } - _propertyGrid->Append(new wxEnumProperty("Preset", "KBPreset", presetchoices, val)); - } - - // restore property grid location - _propertyGrid->RestoreEditableState(save); - if (selProp != "") { - auto p = _propertyGrid->GetPropertyByName(selProp); - if (p != nullptr) _propertyGrid->EnsureVisible(p); - } - - _propertyGrid->Thaw(); - - // This has to be done when the Property editor is not frozen ... as it is ignored if called when frozen - _propertyGrid->ExpandAll(); -} - -void KeyBindingEditDialog::OnControllerPropertyGridChange(wxPropertyGridEvent& event) { - - int index = GetSelectedKeyBindingIndex(); - if (index < 0) - { - wxASSERT(false); - return; - } - - int id = (int)ListCtrl_Bindings->GetItemData(index); - if (id < 0) - { - wxASSERT(false); - return; - } - - KeyBinding& b = _keyBindings->GetBinding(id); - - wxString name = event.GetPropertyName(); - - if (name == "KBKey") { - if (event.GetValue().GetLong() == 0) { - b.SetKey(WXK_NONE); - } - else - { - auto key = KeyBinding::GetPossibleKeys()[event.GetValue().GetLong() - 1]; - b.SetKey(key); - } - } - else if (name == "KBControl") { - b.SetControl(event.GetValue().GetBool()); - } - else if (name == "KBRawControl") { - b.SetRawControl(event.GetValue().GetBool()); - } - else if (name == "KBAlt") { - b.SetAlt(event.GetValue().GetBool()); - } - else if (name == "KBShift") { - b.SetShift(event.GetValue().GetBool()); - } - else if (name == "KBEffect") { - if (event.GetValue().GetLong() == 0) { - b.SetEffectName(""); - } - else { - int i = 1; - for (const auto& it : *_effectManager) { - if (i == event.GetValue().GetLong()) { - b.SetEffectName(it->Name()); - break; - } - i++; - } - } - } - else if (name == "KBEffectSetting") { - b.SetEffectString(event.GetValue().GetString()); - } - else if (name == "KBPreset") - { - if (event.GetValue().GetLong() == 0) { - b.SetEffectName(""); - } - else { - int i = 1; - for (const auto& it : _xLights->GetPresets()) { - if (i == event.GetValue().GetLong()) { - b.SetEffectName(it); - break; - } - i++; - } - } - } - - // Update the edited row in place. Don't rebuild the list/property grid: Type - // is read-only so the property set never changes, and a rebuild re-creates - // the enum editors and visibly flashes them. The grid already shows the edit. - ListCtrl_Bindings->SetItem(index, 1, RenderShortcut(b)); - ListCtrl_Bindings->SetItem(index, 2, BuildDetails(b)); - RefreshDuplicateHighlights(); -} - -KeyBindingEditDialog::~KeyBindingEditDialog() -{ - //(*Destroy(KeyBindingEditDialog) - //*) -} - -KBSCOPE EncodeScope(std::string scope) -{ - if (scope == "Controller") return KBSCOPE::Setup; - if (scope == "Layout") return KBSCOPE::Layout; - if (scope == "Sequencer") return KBSCOPE::Sequence; - return KBSCOPE::All; -} - -void KeyBindingEditDialog::LoadList() -{ - ListCtrl_Bindings->Freeze(); - auto pos = ListCtrl_Bindings->GetScrollPos(wxVERTICAL); - ListCtrl_Bindings->DeleteAllItems(); - const wxColour evenRow = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX); - const wxColour txt = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT); - const wxColour oddRow((evenRow.Red()*92 + txt.Red()*8) / 100, - (evenRow.Green()*92 + txt.Green()*8) / 100, - (evenRow.Blue()*92 + txt.Blue()*8) / 100); - const wxString scopeSel = Choice_Scope->GetStringSelection(); - const bool showAll = (scopeSel == "All"); - const KBSCOPE scope = EncodeScope(scopeSel); - for (const auto& it : _keyBindings->GetBindings()) - { - if (!showAll && !it.InScope(scope)) - continue; - - const wxString friendly = FriendlyName(it.GetType()); - const wxString shortcut = RenderShortcut(it); - const wxString details = BuildDetails(it); - - // Whitespace-tokenised AND filter over action / type / shortcut / details. - if (!_filter.empty()) { - const wxString hay = (friendly + " " + it.GetType() + " " + shortcut + " " + details).Lower(); - bool match = true; - wxStringTokenizer tok(_filter, " "); - while (tok.HasMoreTokens()) { - if (hay.Find(tok.GetNextToken()) == wxNOT_FOUND) { match = false; break; } - } - if (!match) continue; - } - - auto item = ListCtrl_Bindings->InsertItem(ListCtrl_Bindings->GetItemCount(), friendly); - ListCtrl_Bindings->SetItem(item, 1, shortcut); - ListCtrl_Bindings->SetItem(item, 2, details); - ListCtrl_Bindings->SetItemData(item, it.GetId()); - // Zebra striping using theme-aware colours (works in light and dark). - ListCtrl_Bindings->SetItemBackgroundColour(item, (item % 2 == 0) ? evenRow : oddRow); - if (it.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(it)) - { - ListCtrl_Bindings->SetItemTextColour(item, *wxRED); - } - } - if (ListCtrl_Bindings->GetItemCount() > 0 && - ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) < 0) { - ListCtrl_Bindings->SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } - ListCtrl_Bindings->Thaw(); - ListCtrl_Bindings->SetScrollPos(wxVERTICAL, pos); - ListCtrl_Bindings->Refresh(); - UpdateEditEnabled(); -} - -wxString KeyBindingEditDialog::BuildDetails(const KeyBinding& b) const -{ - wxString details = b.GetTip(); - wxString effect; - if (b.GetEffectName() != "" && b.GetEffectString() != "") { - effect = b.GetEffectName() + ":" + b.GetEffectString(); - } else if (b.GetEffectString() != "") { - effect = b.GetEffectString(); - } else if (b.GetEffectName() != "") { - effect = b.GetEffectName(); - } - if (!effect.empty()) { - details = details.empty() ? effect : details + " (" + effect + ")"; - } - return details; -} - -// Re-colour duplicate-key rows in place (changing a key can create or resolve a -// clash on another row) without rebuilding the list. -void KeyBindingEditDialog::RefreshDuplicateHighlights() -{ - const wxColour normal = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT); - for (long i = 0; i < ListCtrl_Bindings->GetItemCount(); ++i) { - const KeyBinding& rb = _keyBindings->GetBinding((int)ListCtrl_Bindings->GetItemData(i)); - const bool dup = rb.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(rb); - ListCtrl_Bindings->SetItemTextColour(i, dup ? *wxRED : normal); - } -} - -wxString KeyBindingEditDialog::FriendlyName(const std::string& type) -{ - // Most enum names humanise cleanly (split on '_', title-case); override the - // handful that don't read well that way. - static const std::map overrides = { - { "AUDIO_FULL_SPEED", "Audio: Full Speed" }, - { "AUDIO_F_1_5_SPEED", "Audio: 1.5x Speed" }, - { "AUDIO_F_2_SPEED", "Audio: 2x Speed" }, - { "AUDIO_F_3_SPEED", "Audio: 3x Speed" }, - { "AUDIO_F_4_SPEED", "Audio: 4x Speed" }, - { "AUDIO_S_3_4_SPEED", "Audio: 3/4 Speed" }, - { "AUDIO_S_1_2_SPEED", "Audio: 1/2 Speed" }, - { "AUDIO_S_1_4_SPEED", "Audio: 1/4 Speed" }, - { "VALUECURVES_TOGGLE", "Value Curves Panel" }, - { "EXPORT_MODEL_CAD", "Export Model (CAD)" }, - { "EXPORT_LAYOUT_DXF", "Export Layout (DXF)" }, - { "FPP_CONNECT", "FPP Connect" }, - { "FOCUS_SEQUENCER", "Focus Effects Grid" }, - }; - auto o = overrides.find(type); - if (o != overrides.end()) return o->second; - - wxString out; - bool newWord = true; - for (char c : type) { - if (c == '_') { - out += ' '; - newWord = true; - } else if (newWord) { - out += (char)std::toupper((unsigned char)c); - newWord = false; - } else { - out += (char)std::tolower((unsigned char)c); - } - } - return out; -} - -wxString KeyBindingEditDialog::RenderShortcut(const KeyBinding& b) -{ - if (b.GetKey() == WXK_NONE) return "(unassigned)"; - wxString mods; -#ifdef __WXOSX__ - if (b.RequiresControl()) mods += wxUniChar(0x2318); // Command - if (b.RequiresRawControl()) mods += wxUniChar(0x2303); // Control - if (b.RequiresAlt()) mods += wxUniChar(0x2325); // Option - if (b.RequiresShift()) mods += wxUniChar(0x21E7); // Shift -#else - if (b.RequiresControl()) mods += "Ctrl+"; - if (b.RequiresRawControl()) mods += "RCtrl+"; - if (b.RequiresAlt()) mods += "Alt+"; - if (b.RequiresShift()) mods += "Shift+"; -#endif - return mods + b.EncodeKey(b.GetKey(), false); -} - -void KeyBindingEditDialog::OnListMouseMotion(wxMouseEvent& event) -{ - event.Skip(); - int flags = 0; - long item = ListCtrl_Bindings->HitTest(event.GetPosition(), flags); - if (item == _tooltipItem) return; - _tooltipItem = item; - if (item == wxNOT_FOUND) { - ListCtrl_Bindings->UnsetToolTip(); - return; - } - long id = ListCtrl_Bindings->GetItemData(item); - for (const auto& b : _keyBindings->GetBindings()) { - if ((long)b.GetId() == id) { - wxString tip = FriendlyName(b.GetType()) + " [" + wxString(b.GetType()) + "]"; - if (!b.GetTip().empty()) tip += "\n" + wxString(b.GetTip()); - ListCtrl_Bindings->SetToolTip(tip); - return; - } - } - ListCtrl_Bindings->UnsetToolTip(); -} - -void KeyBindingEditDialog::OnButton_CancelClick(wxCommandEvent& event) -{ - Close(); -} - -void KeyBindingEditDialog::OnClose(wxCloseEvent& event) -{ - Destroy(); -} - -void KeyBindingEditDialog::OnChoice_ScopeSelect(wxCommandEvent& event) -{ - LoadList(); -} - -void KeyBindingEditDialog::OnListCtrl_BindingsItemFocused(wxListEvent& event) -{ -} - -void KeyBindingEditDialog::OnListCtrl_BindingsItemSelect(wxListEvent& event) -{ -} - -void KeyBindingEditDialog::OnListCtrl_BindingsKeyDown(wxListEvent& event) -{ - if (event.GetKeyCode() == WXK_DELETE) - { - int index = GetSelectedKeyBindingIndex(); - if (index >= 0) { - int id = (int)ListCtrl_Bindings->GetItemData(index); - if (id >= 0) { - KeyBinding& b = _keyBindings->GetBinding(id); - if (b.GetType() == "EFFECT" || b.GetType() == "PRESET" || b.GetType() == "APPLYSETTING") - { - _keyBindings->DeleteKey(id); - } - else - { - b.SetKey(""); - b.SetShift(false); - b.SetAlt(false); - b.SetControl(false); - b.SetRawControl(false); - } - LoadList(); - } - } - } -} - -void KeyBindingEditDialog::OnListCtrl_BindingsDeleteItem(wxListEvent& event) -{ -} - -void KeyBindingEditDialog::SelectKey(int id) -{ - // unselect everything - int item = ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - while (item >= 0) { - ListCtrl_Bindings->SetItemState(item, 0, wxLIST_STATE_SELECTED); - item = ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - } - - // remove the focus from all items - item = ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); - if (item >= 0) { - ListCtrl_Bindings->SetItemState(item, 0, wxLIST_STATE_FOCUSED); - } - - for (int i = 0; i < ListCtrl_Bindings->GetItemCount(); i++) { - auto iid = ListCtrl_Bindings->GetItemData(i); - - if (iid == (wxUIntPtr)id) { - ListCtrl_Bindings->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - ListCtrl_Bindings->EnsureVisible(i); - break; - } - } -} - -void KeyBindingEditDialog::OnButton_AddEffectClick(wxCommandEvent& event) -{ - int id = _keyBindings->AddKey(KeyBinding(_(""), false, _("On"), _(""), _("2020.15"), false, false, false)); - LoadList(); - SelectKey(id); - DoEditSelected(); -} - -void KeyBindingEditDialog::OnButtonAddApplySettingClick(wxCommandEvent& event) -{ - int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), _("2020.15"), false, false, false, false)); - LoadList(); - SelectKey(id); - DoEditSelected(); -} - -void KeyBindingEditDialog::OnButtonAddPresetClick(wxCommandEvent& event) -{ - std::string empty; - int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), false, false, false, false)); - LoadList(); - SelectKey(id); - DoEditSelected(); -} - -void KeyBindingEditDialog::OnButtonSaveClick(wxCommandEvent& event) -{ - _keyBindings->Save(); -} diff --git a/src-ui-wx/app-shell/KeyBindingEditDialog.h b/src-ui-wx/app-shell/KeyBindingEditDialog.h deleted file mode 100644 index af68a664e9..0000000000 --- a/src-ui-wx/app-shell/KeyBindingEditDialog.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*************************************************************** - * This source files comes from the xLights project - * https://www.xlights.org - * https://github.com/xLightsSequencer/xLights - * See the github commit history for a record of contributing - * developers. - * Copyright claimed based on commit dates recorded in Github - * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt - **************************************************************/ - -//(*Headers(KeyBindingEditDialog) -#include -#include -#include -#include -#include -#include -#include -#include -//*) - -class KeyBindingMap; -class KeyBinding; -class EffectManager; -class xLightsFrame; -class wxSearchCtrl; - -class KeyBindingEditDialog : public wxDialog -{ - EffectManager* _effectManager = nullptr; - KeyBindingMap* _keyBindings = nullptr; - wxPropertyGrid* _propertyGrid = nullptr; - xLightsFrame* _xLights = nullptr; - - void LoadList(); - wxString BuildDetails(const KeyBinding& b) const; - void RefreshDuplicateHighlights(); - void DoEditSelected(); - void RefreshRow(long index, const KeyBinding& b); - void UpdateEditEnabled(); - void SetKeyBindingProperties(); - int GetSelectedKeyBindingIndex() const; - void SelectKey(int id); - - // Display helpers for the bindings list. - static wxString RenderShortcut(const KeyBinding& b); - void OnListMouseMotion(wxMouseEvent& event); - long _tooltipItem = -1; - - wxSearchCtrl* _filterCtrl = nullptr; - wxString _filter; // lower-cased; whitespace-tokenised AND match in LoadList - wxButton* _editButton = nullptr; // disabled when nothing is selected - -public: - // Stable window name used to find an already-open instance (type-based - // lookup is unreliable here - wxDialog subclasses share RTTI in this build). - static constexpr const char* WINDOW_NAME = "xlKeyBindingEditDialog"; - - // Public so the popup editor can label a binding with its friendly name. - static wxString FriendlyName(const std::string& type); - - KeyBindingEditDialog(xLightsFrame* parent, KeyBindingMap* keyBindings, EffectManager* effectManager, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); - virtual ~KeyBindingEditDialog(); - - //(*Declarations(KeyBindingEditDialog) - wxButton* ButtonAddApplySetting; - wxButton* ButtonAddPreset; - wxButton* ButtonSave; - wxButton* Button_AddEffect; - wxButton* Button_Close; - wxChoice* Choice_Scope; - wxFlexGridSizer* FlexGridSizer3; - wxListCtrl* ListCtrl_Bindings; - wxPanel* Panel_Properties; - wxStaticText* StaticText1; - //*) - -protected: - //(*Identifiers(KeyBindingEditDialog) - static const long ID_STATICTEXT1; - static const long ID_CHOICE1; - static const long ID_LISTCTRL1; - static const long ID_PANEL1; - static const long ID_BUTTON1; - static const long ID_BUTTON3; - static const long ID_BUTTON2; - static const long ID_BUTTON_SAVE; - static const long ID_BUTTON_CANCEL; - //*) - -private: - //(*Handlers(KeyBindingEditDialog) - void OnButton_CancelClick(wxCommandEvent& event); - void OnChoice_ScopeSelect(wxCommandEvent& event); - void OnListCtrl_BindingsItemFocused(wxListEvent& event); - void OnListCtrl_BindingsItemSelect(wxListEvent& event); - void OnListCtrl_BindingsKeyDown(wxListEvent& event); - void OnListCtrl_BindingsDeleteItem(wxListEvent& event); - void OnButton_AddEffectClick(wxCommandEvent& event); - void OnButtonAddApplySettingClick(wxCommandEvent& event); - void OnButtonAddPresetClick(wxCommandEvent& event); - void OnButtonSaveClick(wxCommandEvent& event); - //*) - - void OnControllerPropertyGridChange(wxPropertyGridEvent& event); - void OnClose(wxCloseEvent& event); - - DECLARE_EVENT_TABLE() -}; diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp new file mode 100644 index 0000000000..482ea5821d --- /dev/null +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp @@ -0,0 +1,544 @@ + +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "KeyBindingsSettingsPanel.h" +#include "KeyBindings.h" +#include "effects/EffectManager.h" +#include "effects/RenderableEffect.h" +#include "xLightsMain.h" +#include "sequencer/MainSequencer.h" + +namespace { +// Modal editor for a single key binding. Edits native controls; ApplyTo() writes +// the result back to the live binding only when the user accepts (wxID_OK). +class KeyBindingPopupEditor : public wxDialog +{ +public: + KeyBindingPopupEditor(wxWindow* parent, const KeyBinding& b, EffectManager* em, xLightsFrame* xl) + : wxDialog(parent, wxID_ANY, _("Edit Shortcut"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + _type(b.GetType()) + { + auto* grid = new wxFlexGridSizer(0, 2, 6, 10); + grid->AddGrowableCol(1); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Key:")), 0, wxALIGN_CENTER_VERTICAL); + _key = new wxChoice(this, wxID_ANY); + _key->Append(_("(none)")); + int sel = 0; + int k = b.GetKey(); + if (k >= 'A' && k <= 'Z') k += 32; + for (const auto& it : KeyBinding::GetPossibleKeys()) { + _key->Append(KeyBinding::EncodeKey(it, false)); + _keys.push_back(it); + if (it == k) sel = (int)_keys.size(); + } + _key->SetSelection(sel); + grid->Add(_key, 1, wxEXPAND); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Modifiers:")), 0, wxALIGN_TOP | wxTOP, 4); + auto* mods = new wxBoxSizer(wxVERTICAL); +#ifdef __WXOSX__ + _ctrl = new wxCheckBox(this, wxID_ANY, L"Command ⌘"); + _alt = new wxCheckBox(this, wxID_ANY, L"Option ⌥"); + _shift = new wxCheckBox(this, wxID_ANY, L"Shift ⇧"); + _rctrl = new wxCheckBox(this, wxID_ANY, L"Control ⌃"); +#else + _ctrl = new wxCheckBox(this, wxID_ANY, _("Control")); + _alt = new wxCheckBox(this, wxID_ANY, _("Alt")); + _shift = new wxCheckBox(this, wxID_ANY, _("Shift")); + _rctrl = new wxCheckBox(this, wxID_ANY, _("macOS Ctrl")); +#endif + _ctrl->SetValue(b.RequiresControl()); + _alt->SetValue(b.RequiresAlt()); + _shift->SetValue(b.RequiresShift()); + _rctrl->SetValue(b.RequiresRawControl()); + mods->Add(_ctrl); + mods->Add(_alt); + mods->Add(_shift); + mods->Add(_rctrl); + grid->Add(mods, 1, wxEXPAND); + + if (_type == "EFFECT") { + grid->Add(new wxStaticText(this, wxID_ANY, _("Effect:")), 0, wxALIGN_CENTER_VERTICAL); + _effect = new wxChoice(this, wxID_ANY); + _effect->Append(""); + for (const auto& it : *em) { + _effect->Append(it->Name()); + if (it->Name() == b.GetEffectName()) _effect->SetSelection(_effect->GetCount() - 1); + } + if (_effect->GetSelection() == wxNOT_FOUND) _effect->SetSelection(0); + grid->Add(_effect, 1, wxEXPAND); + } + if (_type == "EFFECT" || _type == "APPLYSETTING") { + grid->Add(new wxStaticText(this, wxID_ANY, _("Effect Setting:")), 0, wxALIGN_CENTER_VERTICAL); + _setting = new wxTextCtrl(this, wxID_ANY, b.GetEffectString()); + grid->Add(_setting, 1, wxEXPAND); + } + if (_type == "PRESET") { + grid->Add(new wxStaticText(this, wxID_ANY, _("Preset:")), 0, wxALIGN_CENTER_VERTICAL); + _preset = new wxChoice(this, wxID_ANY); + _preset->Append(""); + for (const auto& it : xl->GetPresets()) { + _preset->Append(it); + if (it == b.GetEffectName()) _preset->SetSelection(_preset->GetCount() - 1); + } + if (_preset->GetSelection() == wxNOT_FOUND) _preset->SetSelection(0); + grid->Add(_preset, 1, wxEXPAND); + } + + // Header: the friendly name (prominent) and its description. The raw + // action/type isn't shown - it's not meaningful to most users. + auto* nameText = new wxStaticText(this, wxID_ANY, KeyBindingsSettingsPanel::FriendlyName(b.GetType())); + wxFont nameFont = nameText->GetFont(); + nameFont.MakeBold(); + nameFont.SetPointSize(nameFont.GetPointSize() + 3); + nameText->SetFont(nameFont); + + auto* descText = new wxStaticText(this, wxID_ANY, b.GetTip()); + descText->Wrap(440); + + auto* top = new wxBoxSizer(wxVERTICAL); + top->Add(nameText, 0, wxLEFT | wxRIGHT | wxTOP, 14); + top->Add(descText, 0, wxLEFT | wxRIGHT | wxTOP, 6); + top->Add(new wxStaticLine(this, wxID_ANY), 0, wxEXPAND | wxALL, 12); + top->Add(grid, 1, wxEXPAND | wxLEFT | wxRIGHT, 14); + top->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 12); + SetSizerAndFit(top); + SetMinSize(GetSize()); + CenterOnParent(); + } + + void ApplyTo(KeyBinding& b) const + { + const int s = _key->GetSelection(); + if (s <= 0) { + b.SetKey(WXK_NONE); + } else { + b.SetKey(_keys[s - 1]); + } + b.SetControl(_ctrl->GetValue()); + b.SetAlt(_alt->GetValue()); + b.SetShift(_shift->GetValue()); + b.SetRawControl(_rctrl->GetValue()); + if (_effect != nullptr) b.SetEffectName(_effect->GetStringSelection().ToStdString()); + if (_preset != nullptr) b.SetEffectName(_preset->GetStringSelection().ToStdString()); + if (_setting != nullptr) b.SetEffectString(_setting->GetValue().ToStdString()); + } + +private: + std::string _type; + wxChoice* _key = nullptr; + std::vector _keys; + wxCheckBox* _ctrl = nullptr; + wxCheckBox* _alt = nullptr; + wxCheckBox* _shift = nullptr; + wxCheckBox* _rctrl = nullptr; + wxChoice* _effect = nullptr; + wxChoice* _preset = nullptr; + wxTextCtrl* _setting = nullptr; +}; +} // namespace + +KeyBindingsSettingsPanel::KeyBindingsSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindowID id, const wxPoint& pos, const wxSize& size) +{ + Create(parent, id, pos, size, wxTAB_TRAVERSAL, _T("KeyBindingsSettingsPanel")); + _xLights = f; + _keyBindings = &f->GetMainSequencer()->keyBindings; + _effectManager = &f->GetEffectManager(); + + auto* topSizer = new wxBoxSizer(wxVERTICAL); + + auto* topRow = new wxFlexGridSizer(0, 2, 0, 0); + topRow->AddGrowableCol(1); + topRow->Add(new wxStaticText(this, wxID_ANY, _("Scope:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + Choice_Scope = new wxChoice(this, wxID_ANY); + Choice_Scope->AppendString("All"); + Choice_Scope->AppendString("Layout"); + Choice_Scope->AppendString("Sequencer"); + Choice_Scope->AppendString("All tabs"); + Choice_Scope->SetStringSelection("All"); + topRow->Add(Choice_Scope, 1, wxALL | wxEXPAND, 5); + topRow->Add(new wxStaticText(this, wxID_ANY, _("Filter:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + _filterCtrl = new wxSearchCtrl(this, wxID_ANY); + _filterCtrl->ShowCancelButton(true); + _filterCtrl->SetDescriptiveText(_("Filter actions, shortcuts or descriptions")); + topRow->Add(_filterCtrl, 1, wxALL | wxEXPAND, 5); + topSizer->Add(topRow, 0, wxEXPAND); + + ListCtrl_Bindings = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 360), wxLC_REPORT | wxLC_SINGLE_SEL); + ListCtrl_Bindings->AppendColumn("Action"); + ListCtrl_Bindings->AppendColumn("Shortcut", wxLIST_FORMAT_CENTRE); + ListCtrl_Bindings->AppendColumn("Details"); + topSizer->Add(ListCtrl_Bindings, 1, wxEXPAND | wxALL, 4); + + auto* btnRow = new wxBoxSizer(wxHORIZONTAL); + _editButton = new wxButton(this, wxID_ANY, _("Edit...")); + auto* addEffect = new wxButton(this, wxID_ANY, _("Add Effect")); + auto* addPreset = new wxButton(this, wxID_ANY, _("Add Preset")); + auto* addApply = new wxButton(this, wxID_ANY, _("Add Apply Setting")); + btnRow->Add(_editButton, 0, wxRIGHT, 6); + btnRow->Add(addEffect, 0, wxRIGHT, 6); + btnRow->Add(addPreset, 0, wxRIGHT, 6); + btnRow->Add(addApply, 0); + topSizer->Add(btnRow, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 6); + + SetSizer(topSizer); + SetMinSize(wxSize(560, 420)); + + LoadList(); + ListCtrl_Bindings->SetColumnWidth(0, wxLIST_AUTOSIZE); + ListCtrl_Bindings->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); + ListCtrl_Bindings->SetColumnWidth(2, wxLIST_AUTOSIZE); + + Choice_Scope->Bind(wxEVT_CHOICE, &KeyBindingsSettingsPanel::OnChoice_ScopeSelect, this); + _filterCtrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { _filter = _filterCtrl->GetValue().Lower(); LoadList(); }); + _filterCtrl->Bind(wxEVT_SEARCHCTRL_CANCEL_BTN, [this](wxCommandEvent&) { _filterCtrl->ChangeValue(""); _filter.clear(); LoadList(); }); + ListCtrl_Bindings->Bind(wxEVT_LIST_KEY_DOWN, &KeyBindingsSettingsPanel::OnListCtrl_BindingsKeyDown, this); + ListCtrl_Bindings->Bind(wxEVT_MOTION, &KeyBindingsSettingsPanel::OnListMouseMotion, this); + ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent&) { DoEditSelected(); }); + ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_SELECTED, [this](wxListEvent& e) { e.Skip(); UpdateEditEnabled(); }); + ListCtrl_Bindings->Bind(wxEVT_LIST_ITEM_DESELECTED, [this](wxListEvent& e) { e.Skip(); UpdateEditEnabled(); }); + ListCtrl_Bindings->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { + e.Skip(); + if (ListCtrl_Bindings->GetColumnCount() < 3) return; + const int total = ListCtrl_Bindings->GetClientSize().GetWidth(); + const int used = ListCtrl_Bindings->GetColumnWidth(0) + ListCtrl_Bindings->GetColumnWidth(1); + if (total - used > 120) ListCtrl_Bindings->SetColumnWidth(2, total - used); + }); + _editButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { DoEditSelected(); }); + addEffect->Bind(wxEVT_BUTTON, &KeyBindingsSettingsPanel::OnButton_AddEffectClick, this); + addPreset->Bind(wxEVT_BUTTON, &KeyBindingsSettingsPanel::OnButtonAddPresetClick, this); + addApply->Bind(wxEVT_BUTTON, &KeyBindingsSettingsPanel::OnButtonAddApplySettingClick, this); + + UpdateEditEnabled(); +} + +int KeyBindingsSettingsPanel::GetSelectedKeyBindingIndex() const { + + return ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); +} + +void KeyBindingsSettingsPanel::UpdateEditEnabled() +{ + if (_editButton != nullptr) _editButton->Enable(GetSelectedKeyBindingIndex() >= 0); +} + +void KeyBindingsSettingsPanel::RefreshRow(long index, const KeyBinding& b) +{ + ListCtrl_Bindings->SetItem(index, 1, RenderShortcut(b)); + ListCtrl_Bindings->SetItem(index, 2, BuildDetails(b)); + RefreshDuplicateHighlights(); +} + +void KeyBindingsSettingsPanel::DoEditSelected() +{ + int index = GetSelectedKeyBindingIndex(); + if (index < 0) return; + int id = (int)ListCtrl_Bindings->GetItemData(index); + if (id < 0) return; + + KeyBinding& b = _keyBindings->GetBinding(id); + KeyBindingPopupEditor editor(this, b, _effectManager, _xLights); + if (editor.ShowModal() == wxID_OK) { + editor.ApplyTo(b); + RefreshRow(index, b); + } +} + +KeyBindingsSettingsPanel::~KeyBindingsSettingsPanel() +{ + //(*Destroy(KeyBindingsSettingsPanel) + //*) +} + +KBSCOPE EncodeScope(std::string scope) +{ + if (scope == "Controller") return KBSCOPE::Setup; + if (scope == "Layout") return KBSCOPE::Layout; + if (scope == "Sequencer") return KBSCOPE::Sequence; + return KBSCOPE::All; +} + +void KeyBindingsSettingsPanel::LoadList() +{ + ListCtrl_Bindings->Freeze(); + auto pos = ListCtrl_Bindings->GetScrollPos(wxVERTICAL); + ListCtrl_Bindings->DeleteAllItems(); + const wxColour evenRow = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX); + const wxColour txt = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT); + const wxColour oddRow((evenRow.Red()*92 + txt.Red()*8) / 100, + (evenRow.Green()*92 + txt.Green()*8) / 100, + (evenRow.Blue()*92 + txt.Blue()*8) / 100); + const wxString scopeSel = Choice_Scope->GetStringSelection(); + const bool showAll = (scopeSel == "All"); + const KBSCOPE scope = EncodeScope(scopeSel); + for (const auto& it : _keyBindings->GetBindings()) + { + if (!showAll && !it.InScope(scope)) + continue; + + const wxString friendly = FriendlyName(it.GetType()); + const wxString shortcut = RenderShortcut(it); + const wxString details = BuildDetails(it); + + // Whitespace-tokenised AND filter over action / type / shortcut / details. + if (!_filter.empty()) { + const wxString hay = (friendly + " " + it.GetType() + " " + shortcut + " " + details).Lower(); + bool match = true; + wxStringTokenizer tok(_filter, " "); + while (tok.HasMoreTokens()) { + if (hay.Find(tok.GetNextToken()) == wxNOT_FOUND) { match = false; break; } + } + if (!match) continue; + } + + auto item = ListCtrl_Bindings->InsertItem(ListCtrl_Bindings->GetItemCount(), friendly); + ListCtrl_Bindings->SetItem(item, 1, shortcut); + ListCtrl_Bindings->SetItem(item, 2, details); + ListCtrl_Bindings->SetItemData(item, it.GetId()); + // Zebra striping using theme-aware colours (works in light and dark). + ListCtrl_Bindings->SetItemBackgroundColour(item, (item % 2 == 0) ? evenRow : oddRow); + if (it.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(it)) + { + ListCtrl_Bindings->SetItemTextColour(item, *wxRED); + } + } + if (ListCtrl_Bindings->GetItemCount() > 0 && + ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) < 0) { + ListCtrl_Bindings->SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } + ListCtrl_Bindings->Thaw(); + ListCtrl_Bindings->SetScrollPos(wxVERTICAL, pos); + ListCtrl_Bindings->Refresh(); + UpdateEditEnabled(); +} + +wxString KeyBindingsSettingsPanel::BuildDetails(const KeyBinding& b) const +{ + wxString details = b.GetTip(); + wxString effect; + if (b.GetEffectName() != "" && b.GetEffectString() != "") { + effect = b.GetEffectName() + ":" + b.GetEffectString(); + } else if (b.GetEffectString() != "") { + effect = b.GetEffectString(); + } else if (b.GetEffectName() != "") { + effect = b.GetEffectName(); + } + if (!effect.empty()) { + details = details.empty() ? effect : details + " (" + effect + ")"; + } + return details; +} + +// Re-colour duplicate-key rows in place (changing a key can create or resolve a +// clash on another row) without rebuilding the list. +void KeyBindingsSettingsPanel::RefreshDuplicateHighlights() +{ + const wxColour normal = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT); + for (long i = 0; i < ListCtrl_Bindings->GetItemCount(); ++i) { + const KeyBinding& rb = _keyBindings->GetBinding((int)ListCtrl_Bindings->GetItemData(i)); + const bool dup = rb.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(rb); + ListCtrl_Bindings->SetItemTextColour(i, dup ? *wxRED : normal); + } +} + +wxString KeyBindingsSettingsPanel::FriendlyName(const std::string& type) +{ + // Most enum names humanise cleanly (split on '_', title-case); override the + // handful that don't read well that way. + static const std::map overrides = { + { "AUDIO_FULL_SPEED", "Audio: Full Speed" }, + { "AUDIO_F_1_5_SPEED", "Audio: 1.5x Speed" }, + { "AUDIO_F_2_SPEED", "Audio: 2x Speed" }, + { "AUDIO_F_3_SPEED", "Audio: 3x Speed" }, + { "AUDIO_F_4_SPEED", "Audio: 4x Speed" }, + { "AUDIO_S_3_4_SPEED", "Audio: 3/4 Speed" }, + { "AUDIO_S_1_2_SPEED", "Audio: 1/2 Speed" }, + { "AUDIO_S_1_4_SPEED", "Audio: 1/4 Speed" }, + { "VALUECURVES_TOGGLE", "Value Curves Panel" }, + { "EXPORT_MODEL_CAD", "Export Model (CAD)" }, + { "EXPORT_LAYOUT_DXF", "Export Layout (DXF)" }, + { "FPP_CONNECT", "FPP Connect" }, + { "FOCUS_SEQUENCER", "Focus Effects Grid" }, + }; + auto o = overrides.find(type); + if (o != overrides.end()) return o->second; + + wxString out; + bool newWord = true; + for (char c : type) { + if (c == '_') { + out += ' '; + newWord = true; + } else if (newWord) { + out += (char)std::toupper((unsigned char)c); + newWord = false; + } else { + out += (char)std::tolower((unsigned char)c); + } + } + return out; +} + +wxString KeyBindingsSettingsPanel::RenderShortcut(const KeyBinding& b) +{ + if (b.GetKey() == WXK_NONE) return "(unassigned)"; + wxString mods; +#ifdef __WXOSX__ + if (b.RequiresControl()) mods += wxUniChar(0x2318); // Command + if (b.RequiresRawControl()) mods += wxUniChar(0x2303); // Control + if (b.RequiresAlt()) mods += wxUniChar(0x2325); // Option + if (b.RequiresShift()) mods += wxUniChar(0x21E7); // Shift +#else + if (b.RequiresControl()) mods += "Ctrl+"; + if (b.RequiresRawControl()) mods += "RCtrl+"; + if (b.RequiresAlt()) mods += "Alt+"; + if (b.RequiresShift()) mods += "Shift+"; +#endif + return mods + b.EncodeKey(b.GetKey(), false); +} + +void KeyBindingsSettingsPanel::OnListMouseMotion(wxMouseEvent& event) +{ + event.Skip(); + int flags = 0; + long item = ListCtrl_Bindings->HitTest(event.GetPosition(), flags); + if (item == _tooltipItem) return; + _tooltipItem = item; + if (item == wxNOT_FOUND) { + ListCtrl_Bindings->UnsetToolTip(); + return; + } + long id = ListCtrl_Bindings->GetItemData(item); + for (const auto& b : _keyBindings->GetBindings()) { + if ((long)b.GetId() == id) { + wxString tip = FriendlyName(b.GetType()) + " [" + wxString(b.GetType()) + "]"; + if (!b.GetTip().empty()) tip += "\n" + wxString(b.GetTip()); + ListCtrl_Bindings->SetToolTip(tip); + return; + } + } + ListCtrl_Bindings->UnsetToolTip(); +} + +void KeyBindingsSettingsPanel::OnChoice_ScopeSelect(wxCommandEvent& event) +{ + LoadList(); +} + +void KeyBindingsSettingsPanel::OnListCtrl_BindingsKeyDown(wxListEvent& event) +{ + if (event.GetKeyCode() == WXK_DELETE) + { + int index = GetSelectedKeyBindingIndex(); + if (index >= 0) { + int id = (int)ListCtrl_Bindings->GetItemData(index); + if (id >= 0) { + KeyBinding& b = _keyBindings->GetBinding(id); + if (b.GetType() == "EFFECT" || b.GetType() == "PRESET" || b.GetType() == "APPLYSETTING") + { + _keyBindings->DeleteKey(id); + } + else + { + b.SetKey(""); + b.SetShift(false); + b.SetAlt(false); + b.SetControl(false); + b.SetRawControl(false); + } + LoadList(); + } + } + } +} + +void KeyBindingsSettingsPanel::SelectKey(int id) +{ + // unselect everything + int item = ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + while (item >= 0) { + ListCtrl_Bindings->SetItemState(item, 0, wxLIST_STATE_SELECTED); + item = ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + } + + // remove the focus from all items + item = ListCtrl_Bindings->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); + if (item >= 0) { + ListCtrl_Bindings->SetItemState(item, 0, wxLIST_STATE_FOCUSED); + } + + for (int i = 0; i < ListCtrl_Bindings->GetItemCount(); i++) { + auto iid = ListCtrl_Bindings->GetItemData(i); + + if (iid == (wxUIntPtr)id) { + ListCtrl_Bindings->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + ListCtrl_Bindings->EnsureVisible(i); + break; + } + } +} + +void KeyBindingsSettingsPanel::OnButton_AddEffectClick(wxCommandEvent& event) +{ + int id = _keyBindings->AddKey(KeyBinding(_(""), false, _("On"), _(""), _("2020.15"), false, false, false)); + LoadList(); + SelectKey(id); + DoEditSelected(); +} + +void KeyBindingsSettingsPanel::OnButtonAddApplySettingClick(wxCommandEvent& event) +{ + int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), _("2020.15"), false, false, false, false)); + LoadList(); + SelectKey(id); + DoEditSelected(); +} + +void KeyBindingsSettingsPanel::OnButtonAddPresetClick(wxCommandEvent& event) +{ + std::string empty; + int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), false, false, false, false)); + LoadList(); + SelectKey(id); + DoEditSelected(); +} + + +bool KeyBindingsSettingsPanel::TransferDataToWindow() { + LoadList(); + return true; +} + +bool KeyBindingsSettingsPanel::TransferDataFromWindow() { + if (_keyBindings != nullptr) _keyBindings->Save(); + return true; +} \ No newline at end of file diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.h b/src-ui-wx/preferences/KeyBindingsSettingsPanel.h new file mode 100644 index 0000000000..1c54233459 --- /dev/null +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.h @@ -0,0 +1,74 @@ +#pragma once + +/*************************************************************** + * This source file comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include + +#include + +class KeyBindingMap; +class KeyBinding; +class EffectManager; +class xLightsFrame; +class wxButton; +class wxChoice; +class wxListCtrl; +class wxSearchCtrl; +class wxListEvent; +class wxCommandEvent; +class wxMouseEvent; + +// Preferences page for viewing and editing keyboard shortcuts. Hosts the same +// filterable, scope-scoped bindings list the old Key Bindings dialog used, and +// launches a modal popup editor for a single binding. Edits apply to the live +// KeyBindingMap immediately; they are persisted (keyBindings.Save()) when the +// preferences dialog is accepted (TransferDataFromWindow). +class KeyBindingsSettingsPanel : public wxPanel +{ + EffectManager* _effectManager = nullptr; + KeyBindingMap* _keyBindings = nullptr; + xLightsFrame* _xLights = nullptr; + + void LoadList(); + wxString BuildDetails(const KeyBinding& b) const; + void RefreshDuplicateHighlights(); + void DoEditSelected(); + void RefreshRow(long index, const KeyBinding& b); + void UpdateEditEnabled(); + int GetSelectedKeyBindingIndex() const; + void SelectKey(int id); + + static wxString RenderShortcut(const KeyBinding& b); + void OnListMouseMotion(wxMouseEvent& event); + long _tooltipItem = -1; + + wxChoice* Choice_Scope = nullptr; + wxListCtrl* ListCtrl_Bindings = nullptr; + wxSearchCtrl* _filterCtrl = nullptr; + wxString _filter; // lower-cased; whitespace-tokenised AND match in LoadList + wxButton* _editButton = nullptr; // disabled when nothing is selected + + void OnChoice_ScopeSelect(wxCommandEvent& event); + void OnListCtrl_BindingsKeyDown(wxListEvent& event); + void OnButton_AddEffectClick(wxCommandEvent& event); + void OnButtonAddApplySettingClick(wxCommandEvent& event); + void OnButtonAddPresetClick(wxCommandEvent& event); + +public: + // Public so the popup editor can label a binding with its friendly name. + static wxString FriendlyName(const std::string& type); + + KeyBindingsSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); + virtual ~KeyBindingsSettingsPanel(); + + virtual bool TransferDataFromWindow() override; + virtual bool TransferDataToWindow() override; +}; diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp index 5b037af750..0044707f12 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp @@ -87,8 +87,8 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights btnAdd->Bind(wxEVT_BUTTON, &RandomEffectsSettingsPanel::OnAdd, this); btnRemove->Bind(wxEVT_BUTTON, &RandomEffectsSettingsPanel::OnRemove, this); - _availableList->Bind(wxEVT_LISTBOX_DOUBLECLICK, &RandomEffectsSettingsPanel::OnAvailableDClick, this); - _usedList->Bind(wxEVT_LISTBOX_DOUBLECLICK, &RandomEffectsSettingsPanel::OnUsedDClick, this); + _availableList->Bind(wxEVT_LISTBOX_DCLICK, &RandomEffectsSettingsPanel::OnAvailableDClick, this); + _usedList->Bind(wxEVT_LISTBOX_DCLICK, &RandomEffectsSettingsPanel::OnUsedDClick, this); } RandomEffectsSettingsPanel::~RandomEffectsSettingsPanel() diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index 141896787f..a8300821fa 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -29,6 +29,7 @@ #include "OtherSettingsPanel.h" #include "CheckSequenceSettingsPanel.h" #include "ServicesPanel.h" +#include "KeyBindingsSettingsPanel.h" namespace { // Description of a preferences page: name, left-list icon, and a factory that @@ -142,6 +143,9 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) pages.push_back({ "Other", PrefSvgIcon(R"()", ink), [this](wxWindow* p) { return (wxWindow*)(new OtherSettingsPanel(p, this)); } }); + pages.push_back({ "Key Bindings", + PrefSvgIcon(R"()", ink), + [this](wxWindow* p) { return (wxWindow*)(new KeyBindingsSettingsPanel(p, this)); } }); #ifdef ENABLE_SERVICES pages.push_back({ "Services", PrefSvgIcon(R"()", ink), diff --git a/src-ui-wx/wxsmith/KeyBindingEditDialog.wxs b/src-ui-wx/wxsmith/KeyBindingEditDialog.wxs deleted file mode 100644 index 4d92138368..0000000000 --- a/src-ui-wx/wxsmith/KeyBindingEditDialog.wxs +++ /dev/null @@ -1,121 +0,0 @@ - - - - Edit Keybindings - 1 - 1 - - - 2 - 0 - 1 - - - 2 - 1 - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - wxALL|wxEXPAND - 5 - - - - wxALL|wxEXPAND - 2 - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 2 - - - - - - - - - - - wxALL|wxEXPAND - 2 - - - - - - 1 - 0 - 0 - - - wxALL|wxEXPAND - 2 - - - - - 5 - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - diff --git a/src-ui-wx/wxsmith/xLightsframe.wxs b/src-ui-wx/wxsmith/xLightsframe.wxs index bdf235d135..5d00300aee 100755 --- a/src-ui-wx/wxsmith/xLightsframe.wxs +++ b/src-ui-wx/wxsmith/xLightsframe.wxs @@ -1127,10 +1127,6 @@ - - - - diff --git a/src-ui-wx/xLightsMain.cpp b/src-ui-wx/xLightsMain.cpp index 732a4b2d7e..71bdfc66b6 100644 --- a/src-ui-wx/xLightsMain.cpp +++ b/src-ui-wx/xLightsMain.cpp @@ -86,7 +86,6 @@ #include "layout/HousePreviewPanel.h" #include "setup/IPEntryDialog.h" #include "media/JukeboxPanel.h" -#include "app-shell/KeyBindingEditDialog.h" #include "layout/LayoutGroup.h" #include "layout/LayoutPanel.h" #include "sequencer/LyricUserDictDialog.h" @@ -273,7 +272,6 @@ const wxWindowID xLightsFrame::IS_SAVE_SEQ = wxNewId(); const wxWindowID xLightsFrame::ID_SAVE_AS_SEQUENCE = wxNewId(); const wxWindowID xLightsFrame::ID_CLOSE_SEQ = wxNewId(); const wxWindowID xLightsFrame::ID_SEQ_SETTINGS = wxNewId(); -const wxWindowID xLightsFrame::ID_MNU_KEYBINDINGS = wxNewId(); const wxWindowID xLightsFrame::ID_EXPORT_VIDEO = wxNewId(); const wxWindowID xLightsFrame::ID_MENUITEM2 = wxNewId(); const wxWindowID xLightsFrame::ID_MENUITEM8 = wxNewId(); @@ -1043,8 +1041,6 @@ xLightsFrame::xLightsFrame(wxWindow* parent, int ab, wxWindowID id, bool renderO MenuFile->Append(MenuItem61); Menu_Settings_Sequence = new wxMenuItem(MenuFile, ID_SEQ_SETTINGS, _("Sequence Settings"), wxEmptyString, wxITEM_NORMAL); MenuFile->Append(Menu_Settings_Sequence); - MenuItem_KeyBindings = new wxMenuItem(MenuFile, ID_MNU_KEYBINDINGS, _("Key bindings"), wxEmptyString, wxITEM_NORMAL); - MenuFile->Append(MenuItem_KeyBindings); MenuFile->AppendSeparator(); MenuItem_File_Export_Video = new wxMenuItem(MenuFile, ID_EXPORT_VIDEO, _("Export House Preview Video"), wxEmptyString, wxITEM_NORMAL); MenuFile->Append(MenuItem_File_Export_Video); @@ -1382,7 +1378,6 @@ xLightsFrame::xLightsFrame(wxWindow* parent, int ab, wxWindowID id, bool renderO Connect(ID_CLOSE_SEQ, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_Close_SequenceSelected); Connect(wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItemPreferencesSelected); Connect(ID_SEQ_SETTINGS, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenu_Settings_SequenceSelected); - Connect(ID_MNU_KEYBINDINGS, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_KeyBindingsSelected); Connect(ID_EXPORT_VIDEO, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_Export_VideoSelected); Connect(ID_MENUITEM2, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuOpenFolderSelected); Connect(ID_FILE_BACKUP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItemBackupSelected); @@ -8708,26 +8703,6 @@ void xLightsFrame::OnMenuItemBulkControllerUploadSelected(wxCommandEvent& event) dlg.ShowModal(); } -void xLightsFrame::OnMenuItem_KeyBindingsSelected(wxCommandEvent& event) -{ - // Modeless so it can stay open while you work elsewhere. Edits apply to the - // live key-binding map immediately; Save persists to disk. Reuse an existing - // editor by window NAME, not type: wxDynamicCast can't distinguish wxDialog - // subclasses in this build (RTTI), so a type scan matched other dialogs. - for (wxWindow* w : wxTopLevelWindows) { - if (w->GetName() == KeyBindingEditDialog::WINDOW_NAME) { - w->Show(); - w->Raise(); - w->SetFocus(); - return; - } - } - auto* dlg = new KeyBindingEditDialog(this, &GetMainSequencer()->keyBindings, &effectManager); - dlg->CenterOnParent(); - dlg->Show(); - dlg->Raise(); -} - void xLightsFrame::OnMenuItem_ExportControllerConnectionsSelected(wxCommandEvent& event) { wxLogNull logNo; // kludge: avoid "error 0" message from wxWidgets after new file is written diff --git a/src-ui-wx/xLightsMain.h b/src-ui-wx/xLightsMain.h index 43a3737397..272b90b454 100755 --- a/src-ui-wx/xLightsMain.h +++ b/src-ui-wx/xLightsMain.h @@ -657,7 +657,6 @@ class xLightsFrame: public xlFrame, public RenderContext, public UICallbacks void OnButtonAddControllerSerialClick(wxCommandEvent& event); void OnButtonAddControllerEthernetClick(wxCommandEvent& event); void OnButtonAddControllerNullClick(wxCommandEvent& event); - void OnMenuItem_KeyBindingsSelected(wxCommandEvent& event); void OnButton_ChangeShowFolderTemporarily(wxCommandEvent& event); void OnSysColourChanged(wxSysColourChangedEvent& event); void OnMenuItem_ExportControllerConnectionsSelected(wxCommandEvent& event); @@ -795,7 +794,6 @@ private : static const wxWindowID ID_SAVE_AS_SEQUENCE; static const wxWindowID ID_CLOSE_SEQ; static const wxWindowID ID_SEQ_SETTINGS; - static const wxWindowID ID_MNU_KEYBINDINGS; static const wxWindowID ID_EXPORT_VIDEO; static const wxWindowID ID_MENUITEM2; static const wxWindowID ID_MENUITEM8; @@ -1032,7 +1030,6 @@ private : wxMenuItem* MenuItem_Help_Isue_Tracker; wxMenuItem* MenuItem_Help_ReleaseNotes; wxMenuItem* MenuItem_ImportEffects; - wxMenuItem* MenuItem_KeyBindings; wxMenuItem* MenuItem_LogRenderState; wxMenuItem* MenuItem_LoudVol; wxMenuItem* MenuItem_MedVol; diff --git a/xLights/Xlights.vcxproj b/xLights/Xlights.vcxproj index 84fb4c2f5f..d6965a2730 100644 --- a/xLights/Xlights.vcxproj +++ b/xLights/Xlights.vcxproj @@ -629,7 +629,7 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r - + @@ -1240,7 +1240,7 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r - + diff --git a/xLights/Xlights.vcxproj.filters b/xLights/Xlights.vcxproj.filters index 9fd0e9ba7a..01d0828daa 100644 --- a/xLights/Xlights.vcxproj.filters +++ b/xLights/Xlights.vcxproj.filters @@ -685,7 +685,9 @@ Outputs - + + Preferences + Outputs @@ -2091,7 +2093,9 @@ Outputs - + + Preferences + Outputs diff --git a/xLights/xLights.cbp b/xLights/xLights.cbp index ce42526d3a..e17e494183 100644 --- a/xLights/xLights.cbp +++ b/xLights/xLights.cbp @@ -372,8 +372,8 @@ - - + + @@ -1515,7 +1515,6 @@ - @@ -1747,7 +1746,6 @@ - From 0c2360f19f7ea5d9edb5dcbef49e472e29a75e91 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 29 Jun 2026 22:52:11 -0400 Subject: [PATCH 04/24] Preferences: colourful per-page icons (white glyph on coloured tile) Replace the single-ink monochrome glyphs with macOS-Settings-style icons: a distinct rounded coloured tile per page with a white glyph. The tile carries its own background so the icon stays legible in light and dark mode without per-theme tinting. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/xLightsPreferences.cpp | 46 ++++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index a8300821fa..740ca87f4b 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -17,7 +17,6 @@ #include #include "xLightsMain.h" -#include "shared/utils/wxUtilities.h" // IsDarkMode() #include "ViewSettingsPanel.h" #include "EffectsGridSettingsPanel.h" @@ -40,15 +39,17 @@ struct PrefPageDef { std::function factory; }; -// Build a crisp, theme-aware page icon from an inline SVG body. The body uses -// "%C%" wherever the ink colour should appear (strokes inherit it from the -// wrapper; filled dots set fill="%C%"). Substituting the colour keeps the icon -// legible in both light and dark mode, and SVG keeps it sharp at any DPI. -wxBitmapBundle PrefSvgIcon(const std::string& innerSvg, const std::string& ink) { - std::string svg = std::string(R"()") + innerSvg + ""; - for (size_t p = svg.find("%C%"); p != std::string::npos; p = svg.find("%C%")) { - svg.replace(p, 3, ink); - } +// Build a crisp, colourful page icon: a rounded coloured tile with a white +// glyph (macOS-Settings style). The tile carries its own background, so the +// icon stays legible in both light and dark mode; SVG keeps it sharp at any +// DPI. The glyph body uses "%C%" for any filled dots (substituted with white). +wxBitmapBundle PrefSvgIcon(const std::string& glyph, const std::string& tile) { + // Custom raw-string delimiter so the ")" inside scale(...) etc. can't close + // the literal early. %T% = tile colour, %C% = white (filled dots). + std::string svg = std::string(R"SVG()SVG") + + glyph + R"SVG()SVG"; + for (size_t p = svg.find("%T%"); p != std::string::npos; p = svg.find("%T%")) svg.replace(p, 3, tile); + for (size_t p = svg.find("%C%"); p != std::string::npos; p = svg.find("%C%")) svg.replace(p, 3, "#FFFFFF"); return wxBitmapBundle::FromSVG(svg.c_str(), wxSize(24, 24)); } @@ -112,43 +113,40 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) auto ld = _lowDefinitionRender; - // Ink colour for the page icons - light glyphs on dark mode, dark on light. - const std::string ink = IsDarkMode() ? "#E0E0E0" : "#3A3A3A"; - std::vector pages; pages.push_back({ "Backup", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#3B82F6"), [this](wxWindow* p) { return (wxWindow*)(new BackupSettingsPanel(p, this)); } }); pages.push_back({ "View", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#14B8A6"), [this](wxWindow* p) { return (wxWindow*)(new ViewSettingsPanel(p, this)); } }); pages.push_back({ "Effects Grid", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#8B5CF6"), [this](wxWindow* p) { return (wxWindow*)(new EffectsGridSettingsPanel(p, this)); } }); pages.push_back({ "Sequences", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#6366F1"), [this](wxWindow* p) { return (wxWindow*)(new SequenceFileSettingsPanel(p, this)); } }); pages.push_back({ "Output", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#F59E0B"), [this](wxWindow* p) { return (wxWindow*)(new OutputSettingsPanel(p, this)); } }); pages.push_back({ "Check Sequence", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#22C55E"), [this](wxWindow* p) { return (wxWindow*)(new CheckSequenceSettingsPanel(p, this)); } }); pages.push_back({ "Random Effects", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#EC4899"), [this](wxWindow* p) { return (wxWindow*)(new RandomEffectsSettingsPanel(p, this)); } }); pages.push_back({ "Colors", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#F97316"), [this](wxWindow* p) { return (wxWindow*)(new ColorManagerSettingsPanel(p, this)); } }); pages.push_back({ "Other", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#64748B"), [this](wxWindow* p) { return (wxWindow*)(new OtherSettingsPanel(p, this)); } }); pages.push_back({ "Key Bindings", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#0EA5E9"), [this](wxWindow* p) { return (wxWindow*)(new KeyBindingsSettingsPanel(p, this)); } }); #ifdef ENABLE_SERVICES pages.push_back({ "Services", - PrefSvgIcon(R"()", ink), + PrefSvgIcon(R"()", "#F43F5E"), [this](wxWindow* p) { return (wxWindow*)(new ServicesPanel(p, _serviceManager.get())); } }); #endif From 3f27d4b6a4f9584ccac59c5a87d0a77624fb3cc5 Mon Sep 17 00:00:00 2001 From: heffneil Date: Tue, 30 Jun 2026 06:48:37 -0400 Subject: [PATCH 05/24] Preferences: rename Services page to AI (sparkle icon) --- src-ui-wx/preferences/xLightsPreferences.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index 740ca87f4b..334aeb9754 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -145,8 +145,8 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) PrefSvgIcon(R"()", "#0EA5E9"), [this](wxWindow* p) { return (wxWindow*)(new KeyBindingsSettingsPanel(p, this)); } }); #ifdef ENABLE_SERVICES - pages.push_back({ "Services", - PrefSvgIcon(R"()", "#F43F5E"), + pages.push_back({ "AI", + PrefSvgIcon(R"()", "#A855F7"), [this](wxWindow* p) { return (wxWindow*)(new ServicesPanel(p, _serviceManager.get())); } }); #endif From dd7c9978bbdd49ff62b1aa2eb4304f65cf20ec20 Mon Sep 17 00:00:00 2001 From: heffneil Date: Sun, 5 Jul 2026 15:50:10 -0400 Subject: [PATCH 06/24] Preferences: split video/codec settings into their own Video page Move codec, bitrate and hardware video decode/render controls out of the Other panel into a new VideoSettingsPanel, registered between Colors and Other. Render/packaging toggles (GPU render, shaders, exclude audio/video) stay in Other. Rework OtherSettingsPanel off the wxSmith event table. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/OtherSettingsPanel.cpp | 347 +++++---------- src-ui-wx/preferences/OtherSettingsPanel.h | 92 ++-- src-ui-wx/preferences/VideoSettingsPanel.cpp | 126 ++++++ src-ui-wx/preferences/VideoSettingsPanel.h | 43 ++ src-ui-wx/preferences/xLightsPreferences.cpp | 4 + src-ui-wx/wxsmith/OtherSettingsPanel.wxs | 421 +------------------ xLights/Xlights.vcxproj | 2 + xLights/Xlights.vcxproj.filters | 6 + xLights/xLights.cbp | 2 + 9 files changed, 324 insertions(+), 719 deletions(-) create mode 100644 src-ui-wx/preferences/VideoSettingsPanel.cpp create mode 100644 src-ui-wx/preferences/VideoSettingsPanel.h diff --git a/src-ui-wx/preferences/OtherSettingsPanel.cpp b/src-ui-wx/preferences/OtherSettingsPanel.cpp index ce6df4d756..3a92c536d2 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.cpp +++ b/src-ui-wx/preferences/OtherSettingsPanel.cpp @@ -11,233 +11,118 @@ #include "OtherSettingsPanel.h" #include "color/xlColourData.h" -//(*InternalHeaders(OtherSettingsPanel) #include #include -#include #include #include #include +#include #include #include #include -//*) #include #include "xLightsMain.h" - #ifdef __WXOSX__ extern "C" { extern bool isMetalComputeSupported(); } #endif -//(*IdInit(OtherSettingsPanel) -const wxWindowID OtherSettingsPanel::ID_CHECKBOX1 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHOICE4 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX7 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_STATICTEXT3 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHOICE_CODEC = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_STATICTEXT5 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_SPINCTRLDOUBLE_BITRATE = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX2 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX3 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX4 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX6 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX5 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_STATICTEXT4 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHOICE3 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX8 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_STATICTEXT2 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHOICE2 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_STATICTEXT6 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHOICE_ALIASPROMPT = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_TEXTCTRL1 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX9 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_STATICTEXT7 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CTRLPINGINTERVAL = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX10 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX11 = wxNewId(); -const wxWindowID OtherSettingsPanel::ID_CHECKBOX_CustomColorPicker = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(OtherSettingsPanel,wxPanel) - //(*EventTable(OtherSettingsPanel) - //*) -END_EVENT_TABLE() - OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindowID id, const wxPoint& pos, const wxSize& size) : - frame(f) + wxPanel(parent, id, pos, size, wxTAB_TRAVERSAL), frame(f) { - //(*Initialize(OtherSettingsPanel) - wxFlexGridSizer* FlexGridSizer1; - wxFlexGridSizer* FlexGridSizer2; - wxFlexGridSizer* FlexGridSizer3; - wxFlexGridSizer* FlexGridSizer4; - wxFlexGridSizer* FlexGridSizer5; - wxFlexGridSizer* FlexGridSizer6; - wxFlexGridSizer* FlexGridSizer7; - wxFlexGridSizer* FlexGridSizer8; - wxGridBagSizer* GridBagSizer1; - wxGridBagSizer* GridBagSizer2; - wxStaticBoxSizer* StaticBoxSizer1; - wxStaticBoxSizer* StaticBoxSizer2; - wxStaticBoxSizer* StaticBoxSizer3; - wxStaticBoxSizer* StaticBoxSizer4; - wxStaticText* StaticText1; + auto* sizer = new wxBoxSizer(wxVERTICAL); - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("wxID_ANY")); - GridBagSizer1 = new wxGridBagSizer(0, 0); - FlexGridSizer3 = new wxFlexGridSizer(0, 2, 0, 0); - HardwareVideoDecodingCheckBox = new wxCheckBox(this, ID_CHECKBOX1, _("Hardware Video Decoding"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); - HardwareVideoDecodingCheckBox->SetValue(false); - FlexGridSizer3->Add(HardwareVideoDecodingCheckBox, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - HardwareVideoRenderChoice = new wxChoice(this, ID_CHOICE4, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE4")); - HardwareVideoRenderChoice->Append(_("DirectX11")); - HardwareVideoRenderChoice->SetSelection( HardwareVideoRenderChoice->Append(_("FFmpeg Auto")) ); - HardwareVideoRenderChoice->Append(_("FFmpeg CUDA")); - HardwareVideoRenderChoice->Append(_("FFmpeg QSV")); - HardwareVideoRenderChoice->Append(_("FFmpeg Vulkan")); - HardwareVideoRenderChoice->Append(_("FFmpeg AMF")); - HardwareVideoRenderChoice->Append(_("FFmpeg DirectX11")); - FlexGridSizer3->Add(HardwareVideoRenderChoice, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(FlexGridSizer3, wxGBPosition(1, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 0); - ShaderCheckbox = new wxCheckBox(this, ID_CHECKBOX7, _("Shaders on Background Threads"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX7")); - ShaderCheckbox->SetValue(false); - GridBagSizer1->Add(ShaderCheckbox, wxGBPosition(3, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticBoxSizer2 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Video Export Settings")); - FlexGridSizer1 = new wxFlexGridSizer(0, 2, 0, 0); - FlexGridSizer1->AddGrowableCol(1); - StaticText4 = new wxStaticText(this, ID_STATICTEXT3, _("Video Codec:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT3")); - FlexGridSizer1->Add(StaticText4, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - ChoiceCodec = new wxChoice(this, ID_CHOICE_CODEC, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_CODEC")); - ChoiceCodec->Append(_("Auto")); - ChoiceCodec->SetSelection( ChoiceCodec->Append(_("H.264")) ); - ChoiceCodec->Append(_("H.265")); - ChoiceCodec->Append(_("MPEG-4")); - FlexGridSizer1->Add(ChoiceCodec, 1, wxALL|wxEXPAND, 5); - StaticText6 = new wxStaticText(this, ID_STATICTEXT5, _("Bitrate(KB/s,0=Auto):"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT5")); - FlexGridSizer1->Add(StaticText6, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - SpinCtrlDoubleBitrate = new wxSpinCtrlDouble(this, ID_SPINCTRLDOUBLE_BITRATE, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0, 90000, 0, 1000, _T("ID_SPINCTRLDOUBLE_BITRATE")); - SpinCtrlDoubleBitrate->SetValue(_T("0")); - FlexGridSizer1->Add(SpinCtrlDoubleBitrate, 1, wxALL|wxEXPAND, 5); - StaticBoxSizer2->Add(FlexGridSizer1, 1, wxALL|wxEXPAND, 0); - GridBagSizer1->Add(StaticBoxSizer2, wxGBPosition(1, 1), wxGBSpan(4, 1), wxALL|wxEXPAND, 0); - StaticBoxSizer1 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Packaging Sequences")); - GridBagSizer2 = new wxGridBagSizer(0, 0); - ExcludeVideosCheckBox = new wxCheckBox(this, ID_CHECKBOX2, _("Exclude Videos"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX2")); - ExcludeVideosCheckBox->SetValue(false); - GridBagSizer2->Add(ExcludeVideosCheckBox, wxGBPosition(0, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - ExcludeAudioCheckBox = new wxCheckBox(this, ID_CHECKBOX3, _("Exclude Audio"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX3")); - ExcludeAudioCheckBox->SetValue(false); - GridBagSizer2->Add(ExcludeAudioCheckBox, wxGBPosition(0, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticBoxSizer1->Add(GridBagSizer2, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 0); - GridBagSizer1->Add(StaticBoxSizer1, wxGBPosition(5, 1), wxGBSpan(2, 1), wxALL|wxEXPAND, 0); - CheckBox_BatchRenderPromptIssues = new wxCheckBox(this, ID_CHECKBOX4, _("Prompt issues during batch render"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX4")); - CheckBox_BatchRenderPromptIssues->SetValue(true); - GridBagSizer1->Add(CheckBox_BatchRenderPromptIssues, wxGBPosition(4, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - CheckBox_PurgeDownloadCache = new wxCheckBox(this, ID_CHECKBOX6, _("Purge download cache at startup"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX6")); - CheckBox_PurgeDownloadCache->SetValue(false); - GridBagSizer1->Add(CheckBox_PurgeDownloadCache, wxGBPosition(5, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_IgnoreVendorModelRecommendations = new wxCheckBox(this, ID_CHECKBOX5, _("Ignore vendor model recommendations"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX5")); - CheckBox_IgnoreVendorModelRecommendations->SetValue(false); - GridBagSizer1->Add(CheckBox_IgnoreVendorModelRecommendations, wxGBPosition(9, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - StaticBoxSizer3 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Tip Of The Day")); - FlexGridSizer2 = new wxFlexGridSizer(0, 2, 0, 0); - StaticText5 = new wxStaticText(this, ID_STATICTEXT4, _("Minimum Tip Level"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT4")); - FlexGridSizer2->Add(StaticText5, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - Choice_MinTipLevel = new wxChoice(this, ID_CHOICE3, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE3")); - Choice_MinTipLevel->Append(_("Off")); - Choice_MinTipLevel->SetSelection( Choice_MinTipLevel->Append(_("Beginner")) ); - Choice_MinTipLevel->Append(_("Intermediate")); - Choice_MinTipLevel->Append(_("Advanced")); - Choice_MinTipLevel->Append(_("Expert")); - FlexGridSizer2->Add(Choice_MinTipLevel, 1, wxALL|wxEXPAND, 5); - FlexGridSizer2->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - CheckBox_RecycleTips = new wxCheckBox(this, ID_CHECKBOX8, _("Recycle tips once all seen"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX8")); - CheckBox_RecycleTips->SetValue(false); - FlexGridSizer2->Add(CheckBox_RecycleTips, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - StaticBoxSizer3->Add(FlexGridSizer2, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridBagSizer1->Add(StaticBoxSizer3, wxGBPosition(7, 1), wxGBSpan(4, 1), wxALL|wxEXPAND, 0); - FlexGridSizer5 = new wxFlexGridSizer(0, 2, 0, 0); - StaticText3 = new wxStaticText(this, ID_STATICTEXT2, _("Link controller upload:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT2")); - FlexGridSizer5->Add(StaticText3, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - Choice_LinkControllerUpload = new wxChoice(this, ID_CHOICE2, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE2")); - Choice_LinkControllerUpload->SetSelection( Choice_LinkControllerUpload->Append(_("None")) ); + // Labelled fields, single column. + auto* fields = new wxFlexGridSizer(0, 2, 0, 0); + fields->AddGrowableCol(1); + + fields->Add(new wxStaticText(this, wxID_ANY, _("eMail Address:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + eMailTextControl = new wxTextCtrl(this, wxID_ANY, _("noone@nowhere.xlights.org"), wxDefaultPosition, wxDLG_UNIT(this, wxSize(180, -1))); + fields->Add(eMailTextControl, 1, wxALL | wxEXPAND, 5); + + fields->Add(new wxStaticText(this, wxID_ANY, _("Link controller upload:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + Choice_LinkControllerUpload = new wxChoice(this, wxID_ANY); + Choice_LinkControllerUpload->SetSelection(Choice_LinkControllerUpload->Append(_("None"))); Choice_LinkControllerUpload->Append(_("Inputs and Outputs")); - FlexGridSizer5->Add(Choice_LinkControllerUpload, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(FlexGridSizer5, wxGBPosition(7, 0), wxDefaultSpan, wxALL|wxEXPAND, 0); - FlexGridSizer7 = new wxFlexGridSizer(0, 2, 0, 0); - StaticText7 = new wxStaticText(this, ID_STATICTEXT6, _("Model renaming alias behavior:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT6")); - FlexGridSizer7->Add(StaticText7, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - Choice_AliasPromptBehavior = new wxChoice(this, ID_CHOICE_ALIASPROMPT, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_ALIASPROMPT")); - Choice_AliasPromptBehavior->SetSelection( Choice_AliasPromptBehavior->Append(_("Always Prompt")) ); + fields->Add(Choice_LinkControllerUpload, 1, wxALL | wxEXPAND, 5); + + fields->Add(new wxStaticText(this, wxID_ANY, _("Model renaming alias behavior:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + Choice_AliasPromptBehavior = new wxChoice(this, wxID_ANY); + Choice_AliasPromptBehavior->SetSelection(Choice_AliasPromptBehavior->Append(_("Always Prompt"))); Choice_AliasPromptBehavior->Append(_("Always Yes")); Choice_AliasPromptBehavior->Append(_("Always No")); - FlexGridSizer7->Add(Choice_AliasPromptBehavior, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(FlexGridSizer7, wxGBPosition(8, 0), wxDefaultSpan, wxALL|wxEXPAND, 0); - FlexGridSizer6 = new wxFlexGridSizer(0, 2, 0, 0); - StaticText1 = new wxStaticText(this, wxID_ANY, _("eMail Address:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - FlexGridSizer6->Add(StaticText1, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - eMailTextControl = new wxTextCtrl(this, ID_TEXTCTRL1, _("noone@nowhere.xlights.org"), wxDefaultPosition, wxDLG_UNIT(this,wxSize(180,-1)), 0, wxDefaultValidator, _T("ID_TEXTCTRL1")); - FlexGridSizer6->Add(eMailTextControl, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(FlexGridSizer6, wxGBPosition(0, 0), wxGBSpan(1, 2), wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 0); - GPURenderCheckbox = new wxCheckBox(this, ID_CHECKBOX9, _("GPU Rendering"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX9")); + fields->Add(Choice_AliasPromptBehavior, 1, wxALL | wxEXPAND, 5); + + fields->Add(new wxStaticText(this, wxID_ANY, _("Controller ping interval in seconds (0=Off):")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + CtrlPingInterval = new wxSpinCtrlDouble(this, wxID_ANY, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0, 300, 0, 10); + CtrlPingInterval->SetValue(0); + fields->Add(CtrlPingInterval, 1, wxALL | wxEXPAND, 5); + + sizer->Add(fields, 0, wxEXPAND | wxALL, 5); + + // Standalone toggles. + GPURenderCheckbox = new wxCheckBox(this, wxID_ANY, _("GPU Rendering")); GPURenderCheckbox->SetValue(true); GPURenderCheckbox->SetToolTip(_("Some effects can be rendered on the GPU if this is enabled.")); - GridBagSizer1->Add(GPURenderCheckbox, wxGBPosition(2, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - FlexGridSizer8 = new wxFlexGridSizer(0, 2, 0, 0); - FlexGridSizer8->AddGrowableCol(1); - StaticText8 = new wxStaticText(this, ID_STATICTEXT7, _("Controller ping interval in seconds (0=Off):"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT7")); - FlexGridSizer8->Add(StaticText8, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - CtrlPingInterval = new wxSpinCtrlDouble(this, ID_CTRLPINGINTERVAL, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0, 300, 0, 10, _T("ID_CTRLPINGINTERVAL")); - CtrlPingInterval->SetValue(_T("0")); - FlexGridSizer8->Add(CtrlPingInterval, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(FlexGridSizer8, wxGBPosition(10, 0), wxDefaultSpan, wxALL, 0); - StaticBoxSizer4 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Moving Head Adv - Position Zones")); - FlexGridSizer4 = new wxFlexGridSizer(0, 1, 0, 0); - CheckBox_EnablePositionZones = new wxCheckBox(this, ID_CHECKBOX10, _("Enable Position Zones"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX10")); + sizer->Add(GPURenderCheckbox, 0, wxALL, 5); + + ShaderCheckbox = new wxCheckBox(this, wxID_ANY, _("Shaders on Background Threads")); + sizer->Add(ShaderCheckbox, 0, wxALL, 5); + + CheckBox_BatchRenderPromptIssues = new wxCheckBox(this, wxID_ANY, _("Prompt issues during batch render")); + CheckBox_BatchRenderPromptIssues->SetValue(true); + sizer->Add(CheckBox_BatchRenderPromptIssues, 0, wxALL, 5); + + CheckBox_PurgeDownloadCache = new wxCheckBox(this, wxID_ANY, _("Purge download cache at startup")); + sizer->Add(CheckBox_PurgeDownloadCache, 0, wxALL, 5); + + CheckBox_IgnoreVendorModelRecommendations = new wxCheckBox(this, wxID_ANY, _("Ignore vendor model recommendations")); + sizer->Add(CheckBox_IgnoreVendorModelRecommendations, 0, wxALL, 5); + + CheckBox_UseCustomColorPicker = new wxCheckBox(this, wxID_ANY, _("Use custom color picker (experimental)")); + sizer->Add(CheckBox_UseCustomColorPicker, 0, wxALL, 5); + + // Packaging Sequences. + auto* packBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Packaging Sequences")); + ExcludeVideosCheckBox = new wxCheckBox(this, wxID_ANY, _("Exclude Videos")); + packBox->Add(ExcludeVideosCheckBox, 0, wxALL, 5); + ExcludeAudioCheckBox = new wxCheckBox(this, wxID_ANY, _("Exclude Audio")); + packBox->Add(ExcludeAudioCheckBox, 0, wxALL, 5); + sizer->Add(packBox, 0, wxEXPAND | wxALL, 5); + + // Tip Of The Day. + auto* tipBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Tip Of The Day")); + auto* tipRow = new wxBoxSizer(wxHORIZONTAL); + tipRow->Add(new wxStaticText(this, wxID_ANY, _("Minimum Tip Level")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + Choice_MinTipLevel = new wxChoice(this, wxID_ANY); + Choice_MinTipLevel->Append(_("Off")); + Choice_MinTipLevel->SetSelection(Choice_MinTipLevel->Append(_("Beginner"))); + Choice_MinTipLevel->Append(_("Intermediate")); + Choice_MinTipLevel->Append(_("Advanced")); + Choice_MinTipLevel->Append(_("Expert")); + tipRow->Add(Choice_MinTipLevel, 0, wxEXPAND); + tipBox->Add(tipRow, 0, wxALL, 5); + CheckBox_RecycleTips = new wxCheckBox(this, wxID_ANY, _("Recycle tips once all seen")); + tipBox->Add(CheckBox_RecycleTips, 0, wxALL, 5); + sizer->Add(tipBox, 0, wxEXPAND | wxALL, 5); + + // Moving Head Adv - Position Zones. + auto* zoneBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Moving Head Adv - Position Zones")); + CheckBox_EnablePositionZones = new wxCheckBox(this, wxID_ANY, _("Enable Position Zones")); CheckBox_EnablePositionZones->SetValue(true); - FlexGridSizer4->Add(CheckBox_EnablePositionZones, 1, wxALL, 5); - CheckBox_ShowZoneIndicator = new wxCheckBox(this, ID_CHECKBOX11, _("Show Zone Indicator in Preview"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX11")); - CheckBox_ShowZoneIndicator->SetValue(false); - FlexGridSizer4->Add(CheckBox_ShowZoneIndicator, 1, wxALL, 5); - StaticBoxSizer4->Add(FlexGridSizer4, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridBagSizer1->Add(StaticBoxSizer4, wxGBPosition(11, 0), wxGBSpan(2, 1), wxALL|wxEXPAND, 0); - CheckBox_UseCustomColorPicker = new wxCheckBox(this, ID_CHECKBOX_CustomColorPicker, _("Use custom color picker (experimental)"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX_CustomColorPicker")); - CheckBox_UseCustomColorPicker->SetValue(false); - GridBagSizer1->Add(CheckBox_UseCustomColorPicker, wxGBPosition(13, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - SetSizer(GridBagSizer1); + zoneBox->Add(CheckBox_EnablePositionZones, 0, wxALL, 5); + CheckBox_ShowZoneIndicator = new wxCheckBox(this, wxID_ANY, _("Show Zone Indicator in Preview")); + zoneBox->Add(CheckBox_ShowZoneIndicator, 0, wxALL, 5); + sizer->Add(zoneBox, 0, wxEXPAND | wxALL, 5); - Connect(ID_CHECKBOX1, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHOICE4, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX7, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHOICE_CODEC, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_SPINCTRLDOUBLE_BITRATE, wxEVT_SPINCTRLDOUBLE, (wxObjectEventFunction)&OtherSettingsPanel::OnSpinCtrlDoubleBitrateChange); - Connect(ID_CHECKBOX2, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX3, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX4, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX6, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX5, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHOICE3, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX8, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHOICE2, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHOICE_ALIASPROMPT, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_TEXTCTRL1, wxEVT_COMMAND_TEXT_UPDATED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_TEXTCTRL1, wxEVT_COMMAND_TEXT_ENTER, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX9, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CTRLPINGINTERVAL, wxEVT_SPINCTRLDOUBLE, (wxObjectEventFunction)&OtherSettingsPanel::OnSpinCtrlDoubleBitrateChange); - Connect(ID_CHECKBOX10, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX11, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(ID_CHECKBOX_CustomColorPicker, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&OtherSettingsPanel::OnControlChanged); - Connect(wxEVT_PAINT, (wxObjectEventFunction)&OtherSettingsPanel::OnPaint); - //*) + SetSizer(sizer); + sizer->SetSizeHints(this); #ifdef __LINUX__ - HardwareVideoDecodingCheckBox->Hide(); ShaderCheckbox->Hide(); - HardwareVideoRenderChoice->Hide(); GPURenderCheckbox->Hide(); #endif #ifdef __WXOSX__ @@ -245,40 +130,47 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind GPURenderCheckbox->Hide(); } ShaderCheckbox->Hide(); - HardwareVideoRenderChoice->Hide(); #endif #ifdef __WXMSW__ GPURenderCheckbox->Hide(); MSWDisableComposited(); #endif + eMailTextControl->Bind(wxEVT_TEXT, &OtherSettingsPanel::OnControlChanged, this); + Choice_LinkControllerUpload->Bind(wxEVT_CHOICE, &OtherSettingsPanel::OnControlChanged, this); + Choice_AliasPromptBehavior->Bind(wxEVT_CHOICE, &OtherSettingsPanel::OnControlChanged, this); + CtrlPingInterval->Bind(wxEVT_SPINCTRLDOUBLE, &OtherSettingsPanel::OnSpinCtrlDoubleChange, this); + GPURenderCheckbox->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + ShaderCheckbox->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_BatchRenderPromptIssues->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_PurgeDownloadCache->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_IgnoreVendorModelRecommendations->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_UseCustomColorPicker->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + ExcludeVideosCheckBox->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + ExcludeAudioCheckBox->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + Choice_MinTipLevel->Bind(wxEVT_CHOICE, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_RecycleTips->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_EnablePositionZones->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + CheckBox_ShowZoneIndicator->Bind(wxEVT_CHECKBOX, &OtherSettingsPanel::OnControlChanged, this); + TransferDataToWindow(); } OtherSettingsPanel::~OtherSettingsPanel() { - //(*Destroy(OtherSettingsPanel) - //*) } bool OtherSettingsPanel::TransferDataFromWindow() { frame->SetExcludeAudioFromPackagedSequences(ExcludeAudioCheckBox->IsChecked()); frame->SetExcludeVideosFromPackagedSequences(ExcludeVideosCheckBox->IsChecked()); - frame->SetHardwareVideoAccelerated(HardwareVideoDecodingCheckBox->IsChecked()); -#ifdef __WXMSW__ - frame->SetHardwareVideoRenderer(HardwareVideoRenderChoice->GetSelection()); - HardwareVideoRenderChoice->Enable(HardwareVideoDecodingCheckBox->IsChecked()); -#endif frame->SetUseGPURendering(GPURenderCheckbox->IsChecked()); frame->SetShadersOnBackgroundThreads(ShaderCheckbox->IsChecked()); frame->SetUserEMAIL(eMailTextControl->GetValue()); frame->SetRenameModelAliasPromptBehavior(Choice_AliasPromptBehavior->GetStringSelection()); - frame->SetPromptBatchRenderIssues(CheckBox_BatchRenderPromptIssues->GetValue()); - frame->SetIgnoreVendorModelRecommendations(CheckBox_IgnoreVendorModelRecommendations->GetValue()); + frame->SetPromptBatchRenderIssues(CheckBox_BatchRenderPromptIssues->GetValue()); + frame->SetIgnoreVendorModelRecommendations(CheckBox_IgnoreVendorModelRecommendations->GetValue()); frame->SetControllerPingInterval(CtrlPingInterval->GetValue()); - frame->SetPurgeDownloadCacheOnStart(CheckBox_PurgeDownloadCache->GetValue()); - frame->SetVideoExportCodec(ChoiceCodec->GetStringSelection()); - frame->SetVideoExportBitrate(SpinCtrlDoubleBitrate->GetValue()); + frame->SetPurgeDownloadCacheOnStart(CheckBox_PurgeDownloadCache->GetValue()); frame->SetMinTipLevel(Choice_MinTipLevel->GetStringSelection()); frame->SetRecycleTips(!CheckBox_RecycleTips->GetValue()); frame->SetEnablePositionZones(CheckBox_EnablePositionZones->GetValue()); @@ -290,22 +182,15 @@ bool OtherSettingsPanel::TransferDataFromWindow() { bool OtherSettingsPanel::TransferDataToWindow() { ExcludeAudioCheckBox->SetValue(frame->ExcludeAudioFromPackagedSequences()); ExcludeVideosCheckBox->SetValue(frame->ExcludeVideosFromPackagedSequences()); - HardwareVideoDecodingCheckBox->SetValue(frame->HardwareVideoAccelerated()); -#ifdef __WXMSW__ - HardwareVideoRenderChoice->SetSelection(frame->HardwareVideoRenderer()); - HardwareVideoRenderChoice->Enable(frame->HardwareVideoAccelerated()); -#endif GPURenderCheckbox->SetValue(frame->UseGPURendering()); ShaderCheckbox->SetValue(frame->ShadersOnBackgroundThreads()); eMailTextControl->ChangeValue(frame->UserEMAIL()); - Choice_LinkControllerUpload->SetStringSelection(frame->GetLinkedControllerUpload()); + Choice_LinkControllerUpload->SetStringSelection(frame->GetLinkedControllerUpload()); Choice_AliasPromptBehavior->SetStringSelection(frame->GetRenameModelAliasPromptBehavior()); - CheckBox_BatchRenderPromptIssues->SetValue(frame->GetPromptBatchRenderIssues()); - CheckBox_IgnoreVendorModelRecommendations->SetValue(frame->GetIgnoreVendorModelRecommendations()); + CheckBox_BatchRenderPromptIssues->SetValue(frame->GetPromptBatchRenderIssues()); + CheckBox_IgnoreVendorModelRecommendations->SetValue(frame->GetIgnoreVendorModelRecommendations()); CtrlPingInterval->SetValue(frame->GetControllerPingInterval()); - CheckBox_PurgeDownloadCache->SetValue(frame->GetPurgeDownloadCacheOnStart()); - ChoiceCodec->SetStringSelection(frame->GetVideoExportCodec()); - SpinCtrlDoubleBitrate->SetValue(frame->GetVideoExportBitrate()); + CheckBox_PurgeDownloadCache->SetValue(frame->GetPurgeDownloadCacheOnStart()); Choice_MinTipLevel->SetStringSelection(frame->GetMinTipLevel()); CheckBox_RecycleTips->SetValue(!frame->GetRecycleTips()); CheckBox_EnablePositionZones->SetValue(frame->GetEnablePositionZones()); @@ -319,29 +204,19 @@ bool OtherSettingsPanel::TransferDataToWindow() { CheckBox_IgnoreVendorModelRecommendations->Hide(); #endif #endif - return true; + return true; } -void OtherSettingsPanel::OnControlChanged(wxCommandEvent& event) -{ +void OtherSettingsPanel::ApplyIfImmediate() { if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { TransferDataFromWindow(); - } else { -#ifdef __WXMSW__ - frame->SetHardwareVideoRenderer(HardwareVideoRenderChoice->GetSelection()); - HardwareVideoRenderChoice->Enable(HardwareVideoDecodingCheckBox->IsChecked()); -#endif - return; } } -void OtherSettingsPanel::OnSpinCtrlDoubleBitrateChange(wxSpinDoubleEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } +void OtherSettingsPanel::OnControlChanged(wxCommandEvent& event) { + ApplyIfImmediate(); } -void OtherSettingsPanel::OnPaint(wxPaintEvent& event) -{ +void OtherSettingsPanel::OnSpinCtrlDoubleChange(wxSpinDoubleEvent& event) { + ApplyIfImmediate(); } diff --git a/src-ui-wx/preferences/OtherSettingsPanel.h b/src-ui-wx/preferences/OtherSettingsPanel.h index bc7955509e..c30121d86c 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.h +++ b/src-ui-wx/preferences/OtherSettingsPanel.h @@ -12,19 +12,17 @@ //(*Headers(OtherSettingsPanel) #include +//*) + class wxCheckBox; class wxChoice; -class wxFlexGridSizer; -class wxGridBagSizer; +class wxCommandEvent; class wxSpinCtrlDouble; -class wxSpinEvent; -class wxStaticBoxSizer; +class wxSpinDoubleEvent; class wxStaticText; class wxTextCtrl; -//*) -class wxSpinDoubleEvent; - class xLightsFrame; + class OtherSettingsPanel: public wxPanel { public: @@ -33,32 +31,6 @@ class OtherSettingsPanel: public wxPanel virtual ~OtherSettingsPanel(); //(*Declarations(OtherSettingsPanel) - wxCheckBox* CheckBox_BatchRenderPromptIssues; - wxCheckBox* CheckBox_EnablePositionZones; - wxCheckBox* CheckBox_IgnoreVendorModelRecommendations; - wxCheckBox* CheckBox_PurgeDownloadCache; - wxCheckBox* CheckBox_RecycleTips; - wxCheckBox* CheckBox_ShowZoneIndicator; - wxCheckBox* CheckBox_UseCustomColorPicker; - wxCheckBox* ExcludeAudioCheckBox; - wxCheckBox* ExcludeVideosCheckBox; - wxCheckBox* GPURenderCheckbox; - wxCheckBox* HardwareVideoDecodingCheckBox; - wxCheckBox* ShaderCheckbox; - wxChoice* ChoiceCodec; - wxChoice* Choice_AliasPromptBehavior; - wxChoice* Choice_LinkControllerUpload; - wxChoice* Choice_MinTipLevel; - wxChoice* HardwareVideoRenderChoice; - wxSpinCtrlDouble* CtrlPingInterval; - wxSpinCtrlDouble* SpinCtrlDoubleBitrate; - wxStaticText* StaticText3; - wxStaticText* StaticText4; - wxStaticText* StaticText5; - wxStaticText* StaticText6; - wxStaticText* StaticText7; - wxStaticText* StaticText8; - wxTextCtrl* eMailTextControl; //*) virtual bool TransferDataFromWindow() override; @@ -67,42 +39,34 @@ class OtherSettingsPanel: public wxPanel protected: //(*Identifiers(OtherSettingsPanel) - static const wxWindowID ID_CHECKBOX1; - static const wxWindowID ID_CHOICE4; - static const wxWindowID ID_CHECKBOX7; - static const wxWindowID ID_STATICTEXT3; - static const wxWindowID ID_CHOICE_CODEC; - static const wxWindowID ID_STATICTEXT5; - static const wxWindowID ID_SPINCTRLDOUBLE_BITRATE; - static const wxWindowID ID_CHECKBOX2; - static const wxWindowID ID_CHECKBOX3; - static const wxWindowID ID_CHECKBOX4; - static const wxWindowID ID_CHECKBOX6; - static const wxWindowID ID_CHECKBOX5; - static const wxWindowID ID_STATICTEXT4; - static const wxWindowID ID_CHOICE3; - static const wxWindowID ID_CHECKBOX8; - static const wxWindowID ID_STATICTEXT2; - static const wxWindowID ID_CHOICE2; - static const wxWindowID ID_STATICTEXT6; - static const wxWindowID ID_CHOICE_ALIASPROMPT; - static const wxWindowID ID_TEXTCTRL1; - static const wxWindowID ID_CHECKBOX9; - static const wxWindowID ID_STATICTEXT7; - static const wxWindowID ID_CTRLPINGINTERVAL; - static const wxWindowID ID_CHECKBOX10; - static const wxWindowID ID_CHECKBOX11; - static const wxWindowID ID_CHECKBOX_CustomColorPicker; //*) private: xLightsFrame *frame; + wxCheckBox* CheckBox_BatchRenderPromptIssues = nullptr; + wxCheckBox* CheckBox_EnablePositionZones = nullptr; + wxCheckBox* CheckBox_IgnoreVendorModelRecommendations = nullptr; + wxCheckBox* CheckBox_PurgeDownloadCache = nullptr; + wxCheckBox* CheckBox_RecycleTips = nullptr; + wxCheckBox* CheckBox_ShowZoneIndicator = nullptr; + wxCheckBox* CheckBox_UseCustomColorPicker = nullptr; + wxCheckBox* ExcludeAudioCheckBox = nullptr; + wxCheckBox* ExcludeVideosCheckBox = nullptr; + wxCheckBox* GPURenderCheckbox = nullptr; + wxCheckBox* ShaderCheckbox = nullptr; + wxChoice* Choice_AliasPromptBehavior = nullptr; + wxChoice* Choice_LinkControllerUpload = nullptr; + wxChoice* Choice_MinTipLevel = nullptr; + wxSpinCtrlDouble* CtrlPingInterval = nullptr; + wxTextCtrl* eMailTextControl = nullptr; + + // Write changes back immediately on platforms where the preferences + // editor applies as-you-go. + void ApplyIfImmediate(); + //(*Handlers(OtherSettingsPanel) - void OnControlChanged(wxCommandEvent& event); - void OnSpinCtrlDoubleBitrateChange(wxSpinDoubleEvent& event); - void OnPaint(wxPaintEvent& event); //*) - - DECLARE_EVENT_TABLE() + void OnControlChanged(wxCommandEvent& event); + void OnSpinCtrlDoubleChange(wxSpinDoubleEvent& event); }; diff --git a/src-ui-wx/preferences/VideoSettingsPanel.cpp b/src-ui-wx/preferences/VideoSettingsPanel.cpp new file mode 100644 index 0000000000..cab69b4fb2 --- /dev/null +++ b/src-ui-wx/preferences/VideoSettingsPanel.cpp @@ -0,0 +1,126 @@ +/*************************************************************** + * This source file comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include "VideoSettingsPanel.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xLightsMain.h" + +VideoSettingsPanel::VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindowID id, const wxPoint& pos, const wxSize& size) : + wxPanel(parent, id, pos, size, wxTAB_TRAVERSAL), frame(f) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + HardwareVideoDecodingCheckBox = new wxCheckBox(this, wxID_ANY, _("Hardware Video Decoding")); + sizer->Add(HardwareVideoDecodingCheckBox, 0, wxALL, 5); + + auto* renderRow = new wxBoxSizer(wxHORIZONTAL); + renderRow->Add(new wxStaticText(this, wxID_ANY, _("Hardware Video Renderer:")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + HardwareVideoRenderChoice = new wxChoice(this, wxID_ANY); + HardwareVideoRenderChoice->Append(_("DirectX11")); + HardwareVideoRenderChoice->SetSelection(HardwareVideoRenderChoice->Append(_("FFmpeg Auto"))); + HardwareVideoRenderChoice->Append(_("FFmpeg CUDA")); + HardwareVideoRenderChoice->Append(_("FFmpeg QSV")); + HardwareVideoRenderChoice->Append(_("FFmpeg Vulkan")); + HardwareVideoRenderChoice->Append(_("FFmpeg AMF")); + HardwareVideoRenderChoice->Append(_("FFmpeg DirectX11")); + renderRow->Add(HardwareVideoRenderChoice, 1, wxEXPAND); + sizer->Add(renderRow, 0, wxEXPAND | wxALL, 5); + + auto* exportBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Video Export Settings")); + auto* grid = new wxFlexGridSizer(0, 2, 0, 0); + grid->AddGrowableCol(1); + grid->Add(new wxStaticText(this, wxID_ANY, _("Video Codec:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + ChoiceCodec = new wxChoice(this, wxID_ANY); + ChoiceCodec->Append(_("Auto")); + ChoiceCodec->SetSelection(ChoiceCodec->Append(_("H.264"))); + ChoiceCodec->Append(_("H.265")); + ChoiceCodec->Append(_("MPEG-4")); + grid->Add(ChoiceCodec, 1, wxALL | wxEXPAND, 5); + grid->Add(new wxStaticText(this, wxID_ANY, _("Bitrate(KB/s,0=Auto):")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + SpinCtrlDoubleBitrate = new wxSpinCtrlDouble(this, wxID_ANY, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0, 90000, 0, 1000); + SpinCtrlDoubleBitrate->SetValue(0); + grid->Add(SpinCtrlDoubleBitrate, 1, wxALL | wxEXPAND, 5); + exportBox->Add(grid, 1, wxEXPAND | wxALL, 5); + sizer->Add(exportBox, 0, wxEXPAND | wxALL, 5); + + SetSizer(sizer); + sizer->SetSizeHints(this); + + // The hardware video renderer choice is only honoured on Windows; other + // platforms decode without the selectable backend (mirrors the prior panel). +#ifdef __LINUX__ + HardwareVideoDecodingCheckBox->Hide(); + HardwareVideoRenderChoice->Hide(); +#endif +#ifdef __WXOSX__ + HardwareVideoRenderChoice->Hide(); +#endif + + HardwareVideoDecodingCheckBox->Bind(wxEVT_CHECKBOX, &VideoSettingsPanel::OnControlChanged, this); + HardwareVideoRenderChoice->Bind(wxEVT_CHOICE, &VideoSettingsPanel::OnControlChanged, this); + ChoiceCodec->Bind(wxEVT_CHOICE, &VideoSettingsPanel::OnControlChanged, this); + SpinCtrlDoubleBitrate->Bind(wxEVT_SPINCTRLDOUBLE, &VideoSettingsPanel::OnBitrateChanged, this); + + TransferDataToWindow(); +} + +bool VideoSettingsPanel::TransferDataFromWindow() { + frame->SetHardwareVideoAccelerated(HardwareVideoDecodingCheckBox->IsChecked()); +#ifdef __WXMSW__ + frame->SetHardwareVideoRenderer(HardwareVideoRenderChoice->GetSelection()); + HardwareVideoRenderChoice->Enable(HardwareVideoDecodingCheckBox->IsChecked()); +#endif + frame->SetVideoExportCodec(ChoiceCodec->GetStringSelection()); + frame->SetVideoExportBitrate(SpinCtrlDoubleBitrate->GetValue()); + return true; +} + +bool VideoSettingsPanel::TransferDataToWindow() { + HardwareVideoDecodingCheckBox->SetValue(frame->HardwareVideoAccelerated()); +#ifdef __WXMSW__ + HardwareVideoRenderChoice->SetSelection(frame->HardwareVideoRenderer()); + HardwareVideoRenderChoice->Enable(frame->HardwareVideoAccelerated()); +#endif + ChoiceCodec->SetStringSelection(frame->GetVideoExportCodec()); + SpinCtrlDoubleBitrate->SetValue(frame->GetVideoExportBitrate()); + return true; +} + +void VideoSettingsPanel::ApplyIfImmediate() { + if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { + TransferDataFromWindow(); + } +} + +void VideoSettingsPanel::OnControlChanged(wxCommandEvent& event) { + if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { + TransferDataFromWindow(); + } else { +#ifdef __WXMSW__ + frame->SetHardwareVideoRenderer(HardwareVideoRenderChoice->GetSelection()); + HardwareVideoRenderChoice->Enable(HardwareVideoDecodingCheckBox->IsChecked()); +#endif + } +} + +void VideoSettingsPanel::OnBitrateChanged(wxSpinDoubleEvent& event) { + ApplyIfImmediate(); +} diff --git a/src-ui-wx/preferences/VideoSettingsPanel.h b/src-ui-wx/preferences/VideoSettingsPanel.h new file mode 100644 index 0000000000..6f8193df7a --- /dev/null +++ b/src-ui-wx/preferences/VideoSettingsPanel.h @@ -0,0 +1,43 @@ +#pragma once + +/*************************************************************** + * This source file comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include + +class wxCheckBox; +class wxChoice; +class wxCommandEvent; +class wxSpinCtrlDouble; +class wxSpinDoubleEvent; +class xLightsFrame; + +class VideoSettingsPanel : public wxPanel +{ +public: + VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); + virtual ~VideoSettingsPanel() = default; + + virtual bool TransferDataFromWindow() override; + virtual bool TransferDataToWindow() override; + +private: + xLightsFrame* frame = nullptr; + wxCheckBox* HardwareVideoDecodingCheckBox = nullptr; + wxChoice* HardwareVideoRenderChoice = nullptr; + wxChoice* ChoiceCodec = nullptr; + wxSpinCtrlDouble* SpinCtrlDoubleBitrate = nullptr; + + // Mirror the original behaviour: write changes back immediately on + // platforms where the preferences editor applies as-you-go. + void ApplyIfImmediate(); + void OnControlChanged(wxCommandEvent& event); + void OnBitrateChanged(wxSpinDoubleEvent& event); +}; diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index 334aeb9754..9dc82542e3 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -26,6 +26,7 @@ #include "RandomEffectsSettingsPanel.h" #include "ColorManagerSettingsPanel.h" #include "OtherSettingsPanel.h" +#include "VideoSettingsPanel.h" #include "CheckSequenceSettingsPanel.h" #include "ServicesPanel.h" #include "KeyBindingsSettingsPanel.h" @@ -138,6 +139,9 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) pages.push_back({ "Colors", PrefSvgIcon(R"()", "#F97316"), [this](wxWindow* p) { return (wxWindow*)(new ColorManagerSettingsPanel(p, this)); } }); + pages.push_back({ "Video", + PrefSvgIcon(R"()", "#EF4444"), + [this](wxWindow* p) { return (wxWindow*)(new VideoSettingsPanel(p, this)); } }); pages.push_back({ "Other", PrefSvgIcon(R"()", "#64748B"), [this](wxWindow* p) { return (wxWindow*)(new OtherSettingsPanel(p, this)); } }); diff --git a/src-ui-wx/wxsmith/OtherSettingsPanel.wxs b/src-ui-wx/wxsmith/OtherSettingsPanel.wxs index 5355431405..3f746c1a13 100755 --- a/src-ui-wx/wxsmith/OtherSettingsPanel.wxs +++ b/src-ui-wx/wxsmith/OtherSettingsPanel.wxs @@ -1,424 +1,7 @@ - 0 - - - - - 2 - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - DirectX11 - FFmpeg Auto - FFmpeg CUDA - FFmpeg QSV - FFmpeg Vulkan - FFmpeg AMF - FFmpeg DirectX11 - - 1 - - - wxALL|wxEXPAND - 5 - - - - 0 - 1 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - - - - - - - - 0 - 3 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - 2 - 1 - - - - - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - - Auto - H.264 - H.265 - MPEG-4 - - 1 - - - wxALL|wxEXPAND - 5 - - - - - - - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - 0 - 90000.000000 - 1000.000000 - - - wxALL|wxEXPAND - 5 - - - - wxALL|wxEXPAND - - - - 4 - 1 - 1 - wxALL|wxEXPAND - - - - - - - - - - - - - 0 - 0 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - 1 - 0 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - - - - 2 - 1 - 5 - wxALL|wxEXPAND - - - - - - 1 - - - 0 - 4 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - 0 - 5 - wxALL|wxEXPAND - 5 - - - - - - - - 0 - 9 - wxALL|wxEXPAND - 5 - - - - - - - - 2 - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - Off - Beginner - Intermediate - Advanced - Expert - - 1 - - - wxALL|wxEXPAND - 5 - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - 4 - 1 - 7 - wxALL|wxEXPAND - - - - - 2 - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - None - Inputs and Outputs - - 0 - - - wxALL|wxEXPAND - 5 - - - - 0 - 7 - wxALL|wxEXPAND - - - - - 2 - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - - Always Prompt - Always Yes - Always No - - 0 - - - wxALL|wxEXPAND - 5 - - - - 0 - 8 - wxALL|wxEXPAND - - - - - 2 - - - - - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - noone@nowhere.xlights.org - 180,-1d - - - - wxALL|wxEXPAND - 5 - - - - 2 - 0 - 0 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - - - - - - 1 - Some effects can be rendered on the GPU if this is enabled. - - - 0 - 2 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - - - 2 - 1 - - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - - 0 - 300.000000 - 10.000000 - - - wxALL|wxEXPAND - 5 - - - - 0 - 10 - wxALL - - - - - - - - 1 - - - - 1 - - - wxALL - 5 - - - - - - - - wxALL - 5 - - - - wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL - 5 - - - - 2 - 0 - 11 - wxALL|wxEXPAND - - - - - - - - 0 - 13 - wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL - 5 - - - + 1 + 1 diff --git a/xLights/Xlights.vcxproj b/xLights/Xlights.vcxproj index d6965a2730..4e984ce600 100644 --- a/xLights/Xlights.vcxproj +++ b/xLights/Xlights.vcxproj @@ -905,6 +905,7 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r + @@ -1520,6 +1521,7 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r + diff --git a/xLights/Xlights.vcxproj.filters b/xLights/Xlights.vcxproj.filters index 01d0828daa..5bb5c88a09 100644 --- a/xLights/Xlights.vcxproj.filters +++ b/xLights/Xlights.vcxproj.filters @@ -634,6 +634,9 @@ Preferences + + Preferences + Preferences @@ -2045,6 +2048,9 @@ Preferences + + Preferences + Preferences diff --git a/xLights/xLights.cbp b/xLights/xLights.cbp index e17e494183..84ca0e5fbf 100644 --- a/xLights/xLights.cbp +++ b/xLights/xLights.cbp @@ -1244,6 +1244,8 @@ + + From 77d72c9b16890782d4750ae806e437003f0c9838 Mon Sep 17 00:00:00 2001 From: heffneil Date: Sun, 5 Jul 2026 11:37:55 -0400 Subject: [PATCH 07/24] Preferences: make the dialog modeless (stays open while you work) Show() instead of ShowModal() so Preferences no longer blocks xLights. OK applies (panels save via validators in TransferDataFromWindow) and runs the post-change work (toolbar labels, ResizeMainSequencer, low-def-render reload) that previously sat after ShowModal(); Cancel/close just dismiss. Reuse an already-open instance by window name instead of stacking a second. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/xLightsPreferences.cpp | 55 +++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index 9dc82542e3..c179e4593d 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -154,24 +154,41 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) [this](wxWindow* p) { return (wxWindow*)(new ServicesPanel(p, _serviceManager.get())); } }); #endif - xlPreferencesListDialog dlg(this, pages); - dlg.ShowModal(); - - if (mRenderOnSave) { - MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Render All and Save")); - MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVEAS, _("Render All and Save As")); - MainToolBar->Realize(); - } else { - MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Save")); - MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVEAS, _("Save As")); - MainToolBar->Realize(); - } - - ResizeMainSequencer(); // just in case row height has changed - - if (ld != _lowDefinitionRender) { - // just in case the user changes the low resolution renderer - _outputModelManager.AddASAPWork(OutputModelManager::WORK_RELOAD_ALLMODELS, "Preferences Change"); - _outputModelManager.AddASAPWork(OutputModelManager::WORK_MODELS_CHANGE_REQUIRING_RERENDER, "Preferences Change"); + // Modeless so Preferences can stay open while you keep working in xLights. + // Reuse an already-open instance rather than stacking a second dialog. + for (wxWindow* w : wxTopLevelWindows) { + if (w->GetName() == "xlPreferencesDialog") { + w->Show(); + w->Raise(); + w->SetFocus(); + return; + } } + auto* dlg = new xlPreferencesListDialog(this, pages); + dlg->SetName("xlPreferencesDialog"); + // OK applies (panels save via validators in TransferDataFromWindow) and runs + // the post-change work that used to sit after ShowModal(); Cancel/close just + // dismiss. Everything captured by value/`this` so it's valid when fired. + dlg->Bind(wxEVT_BUTTON, [this, dlg, ld](wxCommandEvent&) { + if (!dlg->Validate() || !dlg->TransferDataFromWindow()) return; + if (mRenderOnSave) { + MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Render All and Save")); + MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVEAS, _("Render All and Save As")); + MainToolBar->Realize(); + } else { + MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Save")); + MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVEAS, _("Save As")); + MainToolBar->Realize(); + } + ResizeMainSequencer(); // just in case row height has changed + if (ld != _lowDefinitionRender) { + _outputModelManager.AddASAPWork(OutputModelManager::WORK_RELOAD_ALLMODELS, "Preferences Change"); + _outputModelManager.AddASAPWork(OutputModelManager::WORK_MODELS_CHANGE_REQUIRING_RERENDER, "Preferences Change"); + } + dlg->Destroy(); + }, wxID_OK); + dlg->Bind(wxEVT_BUTTON, [dlg](wxCommandEvent&) { dlg->Destroy(); }, wxID_CANCEL); + dlg->Bind(wxEVT_CLOSE_WINDOW, [dlg](wxCloseEvent&) { dlg->Destroy(); }); + dlg->Show(); + dlg->Raise(); } From 63e2751766a1c5a04852317ddc70d3d3d88d5735 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 6 Jul 2026 08:27:41 -0400 Subject: [PATCH 08/24] Preferences: widen Random Effects lists so effect names aren't clipped Bump both shuttle wxListBox controls from 190 to 260px wide. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp index 0044707f12..38e4f79bec 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp @@ -50,7 +50,7 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights auto* leftCol = new wxBoxSizer(wxVERTICAL); leftCol->Add(new wxStaticText(this, wxID_ANY, _("Not used")), 0, wxLEFT | wxBOTTOM, 2); - _availableList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(190, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); + _availableList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(260, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); leftCol->Add(_availableList, 1, wxEXPAND); row->Add(leftCol, 1, wxEXPAND | wxRIGHT, 6); @@ -67,7 +67,7 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights auto* rightCol = new wxBoxSizer(wxVERTICAL); rightCol->Add(new wxStaticText(this, wxID_ANY, _("Used")), 0, wxLEFT | wxBOTTOM, 2); - _usedList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(190, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); + _usedList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(260, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); rightCol->Add(_usedList, 1, wxEXPAND); row->Add(rightCol, 1, wxEXPAND | wxLEFT, 6); From aae2a9ce2cf7c9c5062cd7e2d978b877300f5e13 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 6 Jul 2026 09:19:02 -0400 Subject: [PATCH 09/24] Preferences: match AI page font to the rest of the dialog The AI (Services) page renders its settings in a wxPropertyGrid, whose default font is smaller/denser than the native controls on every other page, so it looked out of place. Apply the standard GUI font to the grid. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/ServicesPanel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src-ui-wx/preferences/ServicesPanel.cpp b/src-ui-wx/preferences/ServicesPanel.cpp index fd44f41c8b..503d331729 100644 --- a/src-ui-wx/preferences/ServicesPanel.cpp +++ b/src-ui-wx/preferences/ServicesPanel.cpp @@ -6,6 +6,8 @@ #include "ai/PropertyGridBuilder.h" #include "ai/aiBase.h" +#include + //(*InternalHeaders(ServicesPanel) #include #include @@ -138,6 +140,10 @@ ServicesPanel::ServicesPanel(wxWindow* parent, ServiceManager* sm, wxWindowID id Connect(ID_BUTTON_TEST, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&ServicesPanel::OnButtonTestClick); //*) servicesGrid->SetPropertyAttributeAll(wxPG_BOOL_USE_CHECKBOX, true); + // The property grid defaults to its own (smaller) font, which looks out of + // place next to the native controls on every other preferences page. Use + // the standard GUI font so this page matches the rest of the dialog. + servicesGrid->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); servicesGrid->Connect(wxEVT_PG_CHANGED, (wxObjectEventFunction)&ServicesPanel::OnPropertyGridChange, 0, this); SetupTests(); From fce2d1c18c909c2565c0174ab1dce395f052f8f2 Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 6 Jul 2026 16:48:24 -0400 Subject: [PATCH 10/24] Preferences: uniform bold section headers + Key Bindings category filter - Add StylePreferenceSectionHeaders() helper (PrefPanelUtils.h) that re-fonts wxStaticBox captions to the standard GUI font (bold), so section titles match the control-label size instead of the smaller native box-caption font. Applied to Other, Video, Colors, Backup and Sequence File pages. - Key Bindings page gains a Category dropdown (All / Effects / Presets / Apply Settings / Commands) that filters the list by binding kind; Effects shows the EFFECT-type (Wheel of Effects) bindings. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/BackupSettingsPanel.cpp | 2 ++ .../preferences/ColorManagerSettingsPanel.cpp | 3 ++ .../preferences/KeyBindingsSettingsPanel.cpp | 22 ++++++++++++ .../preferences/KeyBindingsSettingsPanel.h | 5 +++ src-ui-wx/preferences/OtherSettingsPanel.cpp | 2 ++ src-ui-wx/preferences/PrefPanelUtils.h | 35 +++++++++++++++++++ .../preferences/SequenceFileSettingsPanel.cpp | 2 ++ src-ui-wx/preferences/VideoSettingsPanel.cpp | 2 ++ xLights/Xlights.vcxproj | 1 + xLights/Xlights.vcxproj.filters | 3 ++ xLights/xLights.cbp | 1 + 11 files changed, 78 insertions(+) create mode 100644 src-ui-wx/preferences/PrefPanelUtils.h diff --git a/src-ui-wx/preferences/BackupSettingsPanel.cpp b/src-ui-wx/preferences/BackupSettingsPanel.cpp index dafa7fb631..86e0e01cb8 100644 --- a/src-ui-wx/preferences/BackupSettingsPanel.cpp +++ b/src-ui-wx/preferences/BackupSettingsPanel.cpp @@ -9,6 +9,7 @@ **************************************************************/ #include "BackupSettingsPanel.h" +#include "PrefPanelUtils.h" //(*InternalHeaders(BackupSettingsPanel) #include @@ -99,6 +100,7 @@ BackupSettingsPanel::BackupSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWi #ifdef _MSC_VER MSWDisableComposited(); #endif + StylePreferenceSectionHeaders(this); } BackupSettingsPanel::~BackupSettingsPanel() diff --git a/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp b/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp index efa1788de2..1cc098e3fe 100644 --- a/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp +++ b/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp @@ -9,6 +9,7 @@ **************************************************************/ #include "ColorManagerSettingsPanel.h" +#include "PrefPanelUtils.h" #include "shared/utils/wxUtilities.h" #include "utils/ExternalHooks.h" @@ -91,6 +92,8 @@ ColorManagerSettingsPanel::ColorManagerSettingsPanel(wxWindow* parent, xLightsFr Connect(ID_BUTTON_RESET, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&ColorManagerSettingsPanel::OnButton_ResetClick); //*) + StylePreferenceSectionHeaders(this); + #ifndef __WXMSW__ CheckBox_SuppressDarkMode->Show(false); #endif diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp index 482ea5821d..5defa98cab 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp @@ -178,6 +178,15 @@ KeyBindingsSettingsPanel::KeyBindingsSettingsPanel(wxWindow* parent, xLightsFram auto* topRow = new wxFlexGridSizer(0, 2, 0, 0); topRow->AddGrowableCol(1); + topRow->Add(new wxStaticText(this, wxID_ANY, _("Category:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + Choice_Category = new wxChoice(this, wxID_ANY); + Choice_Category->AppendString(_("All")); + Choice_Category->AppendString(_("Effects")); + Choice_Category->AppendString(_("Presets")); + Choice_Category->AppendString(_("Apply Settings")); + Choice_Category->AppendString(_("Commands")); + Choice_Category->SetStringSelection(_("All")); + topRow->Add(Choice_Category, 1, wxALL | wxEXPAND, 5); topRow->Add(new wxStaticText(this, wxID_ANY, _("Scope:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); Choice_Scope = new wxChoice(this, wxID_ANY); Choice_Scope->AppendString("All"); @@ -218,6 +227,7 @@ KeyBindingsSettingsPanel::KeyBindingsSettingsPanel(wxWindow* parent, xLightsFram ListCtrl_Bindings->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); ListCtrl_Bindings->SetColumnWidth(2, wxLIST_AUTOSIZE); + Choice_Category->Bind(wxEVT_CHOICE, [this](wxCommandEvent&) { LoadList(); }); Choice_Scope->Bind(wxEVT_CHOICE, &KeyBindingsSettingsPanel::OnChoice_ScopeSelect, this); _filterCtrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { _filter = _filterCtrl->GetValue().Lower(); LoadList(); }); _filterCtrl->Bind(wxEVT_SEARCHCTRL_CANCEL_BTN, [this](wxCommandEvent&) { _filterCtrl->ChangeValue(""); _filter.clear(); LoadList(); }); @@ -279,6 +289,14 @@ KeyBindingsSettingsPanel::~KeyBindingsSettingsPanel() //*) } +wxString KeyBindingsSettingsPanel::CategoryOf(const std::string& type) +{ + if (type == "EFFECT") return "Effects"; + if (type == "PRESET") return "Presets"; + if (type == "APPLYSETTING") return "Apply Settings"; + return "Commands"; +} + KBSCOPE EncodeScope(std::string scope) { if (scope == "Controller") return KBSCOPE::Setup; @@ -300,10 +318,14 @@ void KeyBindingsSettingsPanel::LoadList() const wxString scopeSel = Choice_Scope->GetStringSelection(); const bool showAll = (scopeSel == "All"); const KBSCOPE scope = EncodeScope(scopeSel); + const wxString categorySel = Choice_Category->GetStringSelection(); + const bool allCategories = categorySel.empty() || categorySel == "All"; for (const auto& it : _keyBindings->GetBindings()) { if (!showAll && !it.InScope(scope)) continue; + if (!allCategories && CategoryOf(it.GetType()) != categorySel) + continue; const wxString friendly = FriendlyName(it.GetType()); const wxString shortcut = RenderShortcut(it); diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.h b/src-ui-wx/preferences/KeyBindingsSettingsPanel.h index 1c54233459..ede058b4c0 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.h +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.h @@ -50,6 +50,7 @@ class KeyBindingsSettingsPanel : public wxPanel void OnListMouseMotion(wxMouseEvent& event); long _tooltipItem = -1; + wxChoice* Choice_Category = nullptr; // filters the list by binding kind wxChoice* Choice_Scope = nullptr; wxListCtrl* ListCtrl_Bindings = nullptr; wxSearchCtrl* _filterCtrl = nullptr; @@ -62,6 +63,10 @@ class KeyBindingsSettingsPanel : public wxPanel void OnButtonAddApplySettingClick(wxCommandEvent& event); void OnButtonAddPresetClick(wxCommandEvent& event); + // Broad category a binding falls into ("Effects", "Presets", + // "Apply Settings" or "Commands"), used by the Category dropdown filter. + static wxString CategoryOf(const std::string& type); + public: // Public so the popup editor can label a binding with its friendly name. static wxString FriendlyName(const std::string& type); diff --git a/src-ui-wx/preferences/OtherSettingsPanel.cpp b/src-ui-wx/preferences/OtherSettingsPanel.cpp index 3a92c536d2..cf0cd78336 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.cpp +++ b/src-ui-wx/preferences/OtherSettingsPanel.cpp @@ -9,6 +9,7 @@ **************************************************************/ #include "OtherSettingsPanel.h" +#include "PrefPanelUtils.h" #include "color/xlColourData.h" #include @@ -120,6 +121,7 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind SetSizer(sizer); sizer->SetSizeHints(this); + StylePreferenceSectionHeaders(this); #ifdef __LINUX__ ShaderCheckbox->Hide(); diff --git a/src-ui-wx/preferences/PrefPanelUtils.h b/src-ui-wx/preferences/PrefPanelUtils.h new file mode 100644 index 0000000000..54992955c3 --- /dev/null +++ b/src-ui-wx/preferences/PrefPanelUtils.h @@ -0,0 +1,35 @@ +#pragma once + +/*************************************************************** + * This source file comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include +#include + +// Section headings on the preferences pages are wxStaticBox captions. On macOS +// the native box caption uses a smaller "small system font", which looks out of +// place next to the full-size control labels on the same page. Re-font every +// wxStaticBox caption on the panel to the standard GUI font (bold) so section +// titles match the label size and read as intentional headings. Call once after +// the panel's controls have been created. +inline void StylePreferenceSectionHeaders(wxWindow* panel) { + if (panel == nullptr) return; + wxFont f = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + f.MakeBold(); + for (wxWindow* child : panel->GetChildren()) { + // wxStaticBox has its own wxWidgets RTTI, so IsKindOf is reliable here + // even in builds without C++ RTTI. + if (child != nullptr && child->IsKindOf(wxCLASSINFO(wxStaticBox))) { + child->SetFont(f); + } + } +} diff --git a/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp b/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp index 29e0dbbc3a..603ecf3bf6 100755 --- a/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp +++ b/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp @@ -9,6 +9,7 @@ **************************************************************/ #include "SequenceFileSettingsPanel.h" +#include "PrefPanelUtils.h" //(*InternalHeaders(SequenceFileSettingsPanel) #include @@ -192,6 +193,7 @@ SequenceFileSettingsPanel::SequenceFileSettingsPanel(wxWindow* parent,xLightsFra #ifdef _MSC_VER MSWDisableComposited(); #endif + StylePreferenceSectionHeaders(this); } SequenceFileSettingsPanel::~SequenceFileSettingsPanel() diff --git a/src-ui-wx/preferences/VideoSettingsPanel.cpp b/src-ui-wx/preferences/VideoSettingsPanel.cpp index cab69b4fb2..bbcfe74a80 100644 --- a/src-ui-wx/preferences/VideoSettingsPanel.cpp +++ b/src-ui-wx/preferences/VideoSettingsPanel.cpp @@ -9,6 +9,7 @@ **************************************************************/ #include "VideoSettingsPanel.h" +#include "PrefPanelUtils.h" #include #include @@ -63,6 +64,7 @@ VideoSettingsPanel::VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind SetSizer(sizer); sizer->SetSizeHints(this); + StylePreferenceSectionHeaders(this); // The hardware video renderer choice is only honoured on Windows; other // platforms decode without the selectable backend (mirrors the prior panel). diff --git a/xLights/Xlights.vcxproj b/xLights/Xlights.vcxproj index 4e984ce600..dce5d3004a 100644 --- a/xLights/Xlights.vcxproj +++ b/xLights/Xlights.vcxproj @@ -1521,6 +1521,7 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r + diff --git a/xLights/Xlights.vcxproj.filters b/xLights/Xlights.vcxproj.filters index 5bb5c88a09..600d2ef284 100644 --- a/xLights/Xlights.vcxproj.filters +++ b/xLights/Xlights.vcxproj.filters @@ -2048,6 +2048,9 @@ Preferences + + Preferences + Preferences diff --git a/xLights/xLights.cbp b/xLights/xLights.cbp index 84ca0e5fbf..982f9450ab 100644 --- a/xLights/xLights.cbp +++ b/xLights/xLights.cbp @@ -1244,6 +1244,7 @@ + From 4ab32f382c3635af31ea4aec844cb074157cdc2e Mon Sep 17 00:00:00 2001 From: heffneil Date: Mon, 6 Jul 2026 17:10:35 -0400 Subject: [PATCH 11/24] Preferences: use bold text section headers on Other/Video pages macOS ignores SetFont on a wxStaticBox caption, so the section titles kept rendering in the smaller native font. Replace the group boxes on the hand- written Other and Video pages with a bold wxStaticText header (MakePreference- SectionHeader) plus indented controls, which renders at full label size. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/BackupSettingsPanel.cpp | 2 -- .../preferences/ColorManagerSettingsPanel.cpp | 3 -- src-ui-wx/preferences/OtherSettingsPanel.cpp | 16 ++++++----- src-ui-wx/preferences/PrefPanelUtils.h | 28 +++++++------------ .../preferences/SequenceFileSettingsPanel.cpp | 2 -- src-ui-wx/preferences/VideoSettingsPanel.cpp | 6 ++-- 6 files changed, 21 insertions(+), 36 deletions(-) diff --git a/src-ui-wx/preferences/BackupSettingsPanel.cpp b/src-ui-wx/preferences/BackupSettingsPanel.cpp index 86e0e01cb8..dafa7fb631 100644 --- a/src-ui-wx/preferences/BackupSettingsPanel.cpp +++ b/src-ui-wx/preferences/BackupSettingsPanel.cpp @@ -9,7 +9,6 @@ **************************************************************/ #include "BackupSettingsPanel.h" -#include "PrefPanelUtils.h" //(*InternalHeaders(BackupSettingsPanel) #include @@ -100,7 +99,6 @@ BackupSettingsPanel::BackupSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWi #ifdef _MSC_VER MSWDisableComposited(); #endif - StylePreferenceSectionHeaders(this); } BackupSettingsPanel::~BackupSettingsPanel() diff --git a/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp b/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp index 1cc098e3fe..efa1788de2 100644 --- a/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp +++ b/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp @@ -9,7 +9,6 @@ **************************************************************/ #include "ColorManagerSettingsPanel.h" -#include "PrefPanelUtils.h" #include "shared/utils/wxUtilities.h" #include "utils/ExternalHooks.h" @@ -92,8 +91,6 @@ ColorManagerSettingsPanel::ColorManagerSettingsPanel(wxWindow* parent, xLightsFr Connect(ID_BUTTON_RESET, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&ColorManagerSettingsPanel::OnButton_ResetClick); //*) - StylePreferenceSectionHeaders(this); - #ifndef __WXMSW__ CheckBox_SuppressDarkMode->Show(false); #endif diff --git a/src-ui-wx/preferences/OtherSettingsPanel.cpp b/src-ui-wx/preferences/OtherSettingsPanel.cpp index cf0cd78336..dbcd3cb017 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.cpp +++ b/src-ui-wx/preferences/OtherSettingsPanel.cpp @@ -87,15 +87,17 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind sizer->Add(CheckBox_UseCustomColorPicker, 0, wxALL, 5); // Packaging Sequences. - auto* packBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Packaging Sequences")); + sizer->Add(MakePreferenceSectionHeader(this, _("Packaging Sequences")), 0, wxLEFT | wxTOP, 10); + auto* packBox = new wxBoxSizer(wxVERTICAL); ExcludeVideosCheckBox = new wxCheckBox(this, wxID_ANY, _("Exclude Videos")); packBox->Add(ExcludeVideosCheckBox, 0, wxALL, 5); ExcludeAudioCheckBox = new wxCheckBox(this, wxID_ANY, _("Exclude Audio")); packBox->Add(ExcludeAudioCheckBox, 0, wxALL, 5); - sizer->Add(packBox, 0, wxEXPAND | wxALL, 5); + sizer->Add(packBox, 0, wxEXPAND | wxLEFT, 16); // Tip Of The Day. - auto* tipBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Tip Of The Day")); + sizer->Add(MakePreferenceSectionHeader(this, _("Tip Of The Day")), 0, wxLEFT | wxTOP, 10); + auto* tipBox = new wxBoxSizer(wxVERTICAL); auto* tipRow = new wxBoxSizer(wxHORIZONTAL); tipRow->Add(new wxStaticText(this, wxID_ANY, _("Minimum Tip Level")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); Choice_MinTipLevel = new wxChoice(this, wxID_ANY); @@ -108,20 +110,20 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind tipBox->Add(tipRow, 0, wxALL, 5); CheckBox_RecycleTips = new wxCheckBox(this, wxID_ANY, _("Recycle tips once all seen")); tipBox->Add(CheckBox_RecycleTips, 0, wxALL, 5); - sizer->Add(tipBox, 0, wxEXPAND | wxALL, 5); + sizer->Add(tipBox, 0, wxEXPAND | wxLEFT, 16); // Moving Head Adv - Position Zones. - auto* zoneBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Moving Head Adv - Position Zones")); + sizer->Add(MakePreferenceSectionHeader(this, _("Moving Head Adv - Position Zones")), 0, wxLEFT | wxTOP, 10); + auto* zoneBox = new wxBoxSizer(wxVERTICAL); CheckBox_EnablePositionZones = new wxCheckBox(this, wxID_ANY, _("Enable Position Zones")); CheckBox_EnablePositionZones->SetValue(true); zoneBox->Add(CheckBox_EnablePositionZones, 0, wxALL, 5); CheckBox_ShowZoneIndicator = new wxCheckBox(this, wxID_ANY, _("Show Zone Indicator in Preview")); zoneBox->Add(CheckBox_ShowZoneIndicator, 0, wxALL, 5); - sizer->Add(zoneBox, 0, wxEXPAND | wxALL, 5); + sizer->Add(zoneBox, 0, wxEXPAND | wxLEFT, 16); SetSizer(sizer); sizer->SetSizeHints(this); - StylePreferenceSectionHeaders(this); #ifdef __LINUX__ ShaderCheckbox->Hide(); diff --git a/src-ui-wx/preferences/PrefPanelUtils.h b/src-ui-wx/preferences/PrefPanelUtils.h index 54992955c3..6e4bc83bc4 100644 --- a/src-ui-wx/preferences/PrefPanelUtils.h +++ b/src-ui-wx/preferences/PrefPanelUtils.h @@ -11,25 +11,17 @@ **************************************************************/ #include -#include -#include +#include #include -// Section headings on the preferences pages are wxStaticBox captions. On macOS -// the native box caption uses a smaller "small system font", which looks out of -// place next to the full-size control labels on the same page. Re-font every -// wxStaticBox caption on the panel to the standard GUI font (bold) so section -// titles match the label size and read as intentional headings. Call once after -// the panel's controls have been created. -inline void StylePreferenceSectionHeaders(wxWindow* panel) { - if (panel == nullptr) return; - wxFont f = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); +// Build a section heading for a preferences page. Uses the panel's normal +// control font made bold, so headings read at the same size as the labels +// beneath them. Preferred over a wxStaticBox caption, whose native font macOS +// renders smaller and refuses to override via SetFont. +inline wxStaticText* MakePreferenceSectionHeader(wxWindow* parent, const wxString& title) { + auto* header = new wxStaticText(parent, wxID_ANY, title); + wxFont f = header->GetFont(); f.MakeBold(); - for (wxWindow* child : panel->GetChildren()) { - // wxStaticBox has its own wxWidgets RTTI, so IsKindOf is reliable here - // even in builds without C++ RTTI. - if (child != nullptr && child->IsKindOf(wxCLASSINFO(wxStaticBox))) { - child->SetFont(f); - } - } + header->SetFont(f); + return header; } diff --git a/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp b/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp index 603ecf3bf6..29e0dbbc3a 100755 --- a/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp +++ b/src-ui-wx/preferences/SequenceFileSettingsPanel.cpp @@ -9,7 +9,6 @@ **************************************************************/ #include "SequenceFileSettingsPanel.h" -#include "PrefPanelUtils.h" //(*InternalHeaders(SequenceFileSettingsPanel) #include @@ -193,7 +192,6 @@ SequenceFileSettingsPanel::SequenceFileSettingsPanel(wxWindow* parent,xLightsFra #ifdef _MSC_VER MSWDisableComposited(); #endif - StylePreferenceSectionHeaders(this); } SequenceFileSettingsPanel::~SequenceFileSettingsPanel() diff --git a/src-ui-wx/preferences/VideoSettingsPanel.cpp b/src-ui-wx/preferences/VideoSettingsPanel.cpp index bbcfe74a80..2771f0a8cc 100644 --- a/src-ui-wx/preferences/VideoSettingsPanel.cpp +++ b/src-ui-wx/preferences/VideoSettingsPanel.cpp @@ -45,7 +45,7 @@ VideoSettingsPanel::VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind renderRow->Add(HardwareVideoRenderChoice, 1, wxEXPAND); sizer->Add(renderRow, 0, wxEXPAND | wxALL, 5); - auto* exportBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Video Export Settings")); + sizer->Add(MakePreferenceSectionHeader(this, _("Video Export Settings")), 0, wxLEFT | wxTOP, 10); auto* grid = new wxFlexGridSizer(0, 2, 0, 0); grid->AddGrowableCol(1); grid->Add(new wxStaticText(this, wxID_ANY, _("Video Codec:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); @@ -59,12 +59,10 @@ VideoSettingsPanel::VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind SpinCtrlDoubleBitrate = new wxSpinCtrlDouble(this, wxID_ANY, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0, 90000, 0, 1000); SpinCtrlDoubleBitrate->SetValue(0); grid->Add(SpinCtrlDoubleBitrate, 1, wxALL | wxEXPAND, 5); - exportBox->Add(grid, 1, wxEXPAND | wxALL, 5); - sizer->Add(exportBox, 0, wxEXPAND | wxALL, 5); + sizer->Add(grid, 0, wxEXPAND | wxLEFT, 16); SetSizer(sizer); sizer->SetSizeHints(this); - StylePreferenceSectionHeaders(this); // The hardware video renderer choice is only honoured on Windows; other // platforms decode without the selectable backend (mirrors the prior panel). From 42f22a02d26df6e759b769e6dc2f70835fe0f225 Mon Sep 17 00:00:00 2001 From: heffneil Date: Tue, 7 Jul 2026 11:36:15 -0400 Subject: [PATCH 12/24] Preferences: describe every Other-page setting; label Effects category, add Wheel-of-Effects note - Other page: add a greyed one-line description under each setting (email, controller-upload link, alias behavior, ping interval -> Status column, GPU/shaders, batch-render prompt, purge cache, vendor recommendations, custom colour picker) and an intro line under each section header. - Key Bindings: rename the Effects category to 'Effects / Wheel of Effects' and add a note that key-bound effects appear on the Wheel of Effects when double-clicking the sequencer grid. - Add MakePreferenceHint() helper. Co-Authored-By: Claude Opus 4.8 --- .../preferences/KeyBindingsSettingsPanel.cpp | 11 ++++-- src-ui-wx/preferences/OtherSettingsPanel.cpp | 35 ++++++++++++++----- src-ui-wx/preferences/PrefPanelUtils.h | 12 +++++++ 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp index 5defa98cab..c9e06b42d7 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp @@ -181,7 +181,7 @@ KeyBindingsSettingsPanel::KeyBindingsSettingsPanel(wxWindow* parent, xLightsFram topRow->Add(new wxStaticText(this, wxID_ANY, _("Category:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); Choice_Category = new wxChoice(this, wxID_ANY); Choice_Category->AppendString(_("All")); - Choice_Category->AppendString(_("Effects")); + Choice_Category->AppendString(_("Effects / Wheel of Effects")); Choice_Category->AppendString(_("Presets")); Choice_Category->AppendString(_("Apply Settings")); Choice_Category->AppendString(_("Commands")); @@ -219,6 +219,13 @@ KeyBindingsSettingsPanel::KeyBindingsSettingsPanel(wxWindow* parent, xLightsFram btnRow->Add(addApply, 0); topSizer->Add(btnRow, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 6); + auto* note = new wxStaticText(this, wxID_ANY, + _("Effects assigned a key binding also appear on the Wheel of Effects in the " + "sequencer — double-click an empty spot on the effect grid to open it.")); + note->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + note->Wrap(520); + topSizer->Add(note, 0, wxLEFT | wxRIGHT | wxBOTTOM, 8); + SetSizer(topSizer); SetMinSize(wxSize(560, 420)); @@ -291,7 +298,7 @@ KeyBindingsSettingsPanel::~KeyBindingsSettingsPanel() wxString KeyBindingsSettingsPanel::CategoryOf(const std::string& type) { - if (type == "EFFECT") return "Effects"; + if (type == "EFFECT") return "Effects / Wheel of Effects"; if (type == "PRESET") return "Presets"; if (type == "APPLYSETTING") return "Apply Settings"; return "Commands"; diff --git a/src-ui-wx/preferences/OtherSettingsPanel.cpp b/src-ui-wx/preferences/OtherSettingsPanel.cpp index dbcd3cb017..a4a5bbbd32 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.cpp +++ b/src-ui-wx/preferences/OtherSettingsPanel.cpp @@ -36,19 +36,24 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind { auto* sizer = new wxBoxSizer(wxVERTICAL); - // Labelled fields, single column. + // Labelled fields, each followed by a greyed description line. The empty + // first cell on each hint row keeps the hint aligned under the control. auto* fields = new wxFlexGridSizer(0, 2, 0, 0); fields->AddGrowableCol(1); fields->Add(new wxStaticText(this, wxID_ANY, _("eMail Address:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); eMailTextControl = new wxTextCtrl(this, wxID_ANY, _("noone@nowhere.xlights.org"), wxDefaultPosition, wxDLG_UNIT(this, wxSize(180, -1))); fields->Add(eMailTextControl, 1, wxALL | wxEXPAND, 5); + fields->Add(0, 0); + fields->Add(MakePreferenceHint(this, _("Identifies you when downloading or submitting models to the vendor database.")), 0, wxLEFT | wxBOTTOM, 5); fields->Add(new wxStaticText(this, wxID_ANY, _("Link controller upload:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); Choice_LinkControllerUpload = new wxChoice(this, wxID_ANY); Choice_LinkControllerUpload->SetSelection(Choice_LinkControllerUpload->Append(_("None"))); Choice_LinkControllerUpload->Append(_("Inputs and Outputs")); fields->Add(Choice_LinkControllerUpload, 1, wxALL | wxEXPAND, 5); + fields->Add(0, 0); + fields->Add(MakePreferenceHint(this, _("Whether uploading a controller's inputs also uploads its outputs.")), 0, wxLEFT | wxBOTTOM, 5); fields->Add(new wxStaticText(this, wxID_ANY, _("Model renaming alias behavior:")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); Choice_AliasPromptBehavior = new wxChoice(this, wxID_ANY); @@ -56,38 +61,48 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind Choice_AliasPromptBehavior->Append(_("Always Yes")); Choice_AliasPromptBehavior->Append(_("Always No")); fields->Add(Choice_AliasPromptBehavior, 1, wxALL | wxEXPAND, 5); + fields->Add(0, 0); + fields->Add(MakePreferenceHint(this, _("When you rename a model, whether to keep its old name as an alias so existing sequences still find it.")), 0, wxLEFT | wxBOTTOM, 5); fields->Add(new wxStaticText(this, wxID_ANY, _("Controller ping interval in seconds (0=Off):")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); CtrlPingInterval = new wxSpinCtrlDouble(this, wxID_ANY, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0, 300, 0, 10); CtrlPingInterval->SetValue(0); fields->Add(CtrlPingInterval, 1, wxALL | wxEXPAND, 5); + fields->Add(0, 0); + fields->Add(MakePreferenceHint(this, _("How often to check that controllers are online. Any value above 0 adds a Status column to the Controllers screen.")), 0, wxLEFT | wxBOTTOM, 5); sizer->Add(fields, 0, wxEXPAND | wxALL, 5); - // Standalone toggles. + // Standalone toggles, each with a description beneath. GPURenderCheckbox = new wxCheckBox(this, wxID_ANY, _("GPU Rendering")); GPURenderCheckbox->SetValue(true); - GPURenderCheckbox->SetToolTip(_("Some effects can be rendered on the GPU if this is enabled.")); - sizer->Add(GPURenderCheckbox, 0, wxALL, 5); + sizer->Add(GPURenderCheckbox, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Render supported effects on the GPU for better performance.")), 0, wxLEFT | wxBOTTOM, 24); ShaderCheckbox = new wxCheckBox(this, wxID_ANY, _("Shaders on Background Threads")); - sizer->Add(ShaderCheckbox, 0, wxALL, 5); + sizer->Add(ShaderCheckbox, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Render shader effects off the main thread to keep xLights responsive.")), 0, wxLEFT | wxBOTTOM, 24); CheckBox_BatchRenderPromptIssues = new wxCheckBox(this, wxID_ANY, _("Prompt issues during batch render")); CheckBox_BatchRenderPromptIssues->SetValue(true); - sizer->Add(CheckBox_BatchRenderPromptIssues, 0, wxALL, 5); + sizer->Add(CheckBox_BatchRenderPromptIssues, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Warn about problems found while batch rendering sequences.")), 0, wxLEFT | wxBOTTOM, 24); CheckBox_PurgeDownloadCache = new wxCheckBox(this, wxID_ANY, _("Purge download cache at startup")); - sizer->Add(CheckBox_PurgeDownloadCache, 0, wxALL, 5); + sizer->Add(CheckBox_PurgeDownloadCache, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Clear cached downloads each time xLights starts.")), 0, wxLEFT | wxBOTTOM, 24); CheckBox_IgnoreVendorModelRecommendations = new wxCheckBox(this, wxID_ANY, _("Ignore vendor model recommendations")); - sizer->Add(CheckBox_IgnoreVendorModelRecommendations, 0, wxALL, 5); + sizer->Add(CheckBox_IgnoreVendorModelRecommendations, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Stop warning when a model differs from the vendor's recommended setup.")), 0, wxLEFT | wxBOTTOM, 24); CheckBox_UseCustomColorPicker = new wxCheckBox(this, wxID_ANY, _("Use custom color picker (experimental)")); - sizer->Add(CheckBox_UseCustomColorPicker, 0, wxALL, 5); + sizer->Add(CheckBox_UseCustomColorPicker, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Use the built-in colour picker instead of the operating system's.")), 0, wxLEFT | wxBOTTOM, 24); // Packaging Sequences. sizer->Add(MakePreferenceSectionHeader(this, _("Packaging Sequences")), 0, wxLEFT | wxTOP, 10); + sizer->Add(MakePreferenceHint(this, _("Media to leave out when packaging a sequence to share.")), 0, wxLEFT, 16); auto* packBox = new wxBoxSizer(wxVERTICAL); ExcludeVideosCheckBox = new wxCheckBox(this, wxID_ANY, _("Exclude Videos")); packBox->Add(ExcludeVideosCheckBox, 0, wxALL, 5); @@ -97,6 +112,7 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind // Tip Of The Day. sizer->Add(MakePreferenceSectionHeader(this, _("Tip Of The Day")), 0, wxLEFT | wxTOP, 10); + sizer->Add(MakePreferenceHint(this, _("Controls the tips shown when xLights starts.")), 0, wxLEFT, 16); auto* tipBox = new wxBoxSizer(wxVERTICAL); auto* tipRow = new wxBoxSizer(wxHORIZONTAL); tipRow->Add(new wxStaticText(this, wxID_ANY, _("Minimum Tip Level")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); @@ -114,6 +130,7 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind // Moving Head Adv - Position Zones. sizer->Add(MakePreferenceSectionHeader(this, _("Moving Head Adv - Position Zones")), 0, wxLEFT | wxTOP, 10); + sizer->Add(MakePreferenceHint(this, _("Named position zones used by Moving Head Advanced effects.")), 0, wxLEFT, 16); auto* zoneBox = new wxBoxSizer(wxVERTICAL); CheckBox_EnablePositionZones = new wxCheckBox(this, wxID_ANY, _("Enable Position Zones")); CheckBox_EnablePositionZones->SetValue(true); diff --git a/src-ui-wx/preferences/PrefPanelUtils.h b/src-ui-wx/preferences/PrefPanelUtils.h index 6e4bc83bc4..546735dda5 100644 --- a/src-ui-wx/preferences/PrefPanelUtils.h +++ b/src-ui-wx/preferences/PrefPanelUtils.h @@ -12,6 +12,7 @@ #include #include +#include #include // Build a section heading for a preferences page. Uses the panel's normal @@ -25,3 +26,14 @@ inline wxStaticText* MakePreferenceSectionHeader(wxWindow* parent, const wxStrin header->SetFont(f); return header; } + +// Build a greyed, slightly smaller explanatory line for a setting, so users can +// tell what a control does without hunting for a tooltip. +inline wxStaticText* MakePreferenceHint(wxWindow* parent, const wxString& text) { + auto* hint = new wxStaticText(parent, wxID_ANY, text); + hint->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + wxFont f = hint->GetFont(); + if (f.GetPointSize() > 9) f.SetPointSize(f.GetPointSize() - 1); + hint->SetFont(f); + return hint; +} From 69a25ec72c6bac2eb654bb74bf3307d34d40778c Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 10:02:04 -0400 Subject: [PATCH 13/24] Preferences: hand-write Backup page with bold headers + per-setting descriptions Convert the Backup page off wxSmith/wxStaticBox to a hand-written vertical layout: bold text section headers (Backup Directory, Alternative Backup Directory) matching the Other page, and a greyed description under each setting. Behaviour (validation, immediate-apply, transfer) unchanged. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/BackupSettingsPanel.cpp | 142 +++++++----------- src-ui-wx/preferences/BackupSettingsPanel.h | 63 +++----- 2 files changed, 76 insertions(+), 129 deletions(-) diff --git a/src-ui-wx/preferences/BackupSettingsPanel.cpp b/src-ui-wx/preferences/BackupSettingsPanel.cpp index dafa7fb631..54000040f1 100644 --- a/src-ui-wx/preferences/BackupSettingsPanel.cpp +++ b/src-ui-wx/preferences/BackupSettingsPanel.cpp @@ -9,103 +9,84 @@ **************************************************************/ #include "BackupSettingsPanel.h" +#include "PrefPanelUtils.h" -//(*InternalHeaders(BackupSettingsPanel) #include #include +#include #include -#include #include #include #include #include -//*) #include #include "xLightsMain.h" -//(*IdInit(BackupSettingsPanel) -const long BackupSettingsPanel::ID_CHECKBOX1 = wxNewId(); -const long BackupSettingsPanel::ID_CHECKBOX2 = wxNewId(); -const long BackupSettingsPanel::ID_CHECKBOX3 = wxNewId(); -const long BackupSettingsPanel::ID_STATICTEXT1 = wxNewId(); -const long BackupSettingsPanel::ID_CHOICE1 = wxNewId(); -const long BackupSettingsPanel::ID_CHECKBOX4 = wxNewId(); -const long BackupSettingsPanel::ID_DIRPICKERCTRL1 = wxNewId(); -const long BackupSettingsPanel::ID_DIRPICKERCTRL2 = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(BackupSettingsPanel,wxPanel) - //(*EventTable(BackupSettingsPanel) - //*) -END_EVENT_TABLE() - - BackupSettingsPanel::BackupSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id) : frame(f) { - //(*Initialize(BackupSettingsPanel) - wxFlexGridSizer* FlexGridSizer1; - wxGridBagSizer* GridBagSizer1; - wxStaticBoxSizer* StaticBoxSizer1; - wxStaticBoxSizer* StaticBoxSizer2; - - Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); - GridBagSizer1 = new wxGridBagSizer(0, 0); - BackupOnSaveCheckBox = new wxCheckBox(this, ID_CHECKBOX1, _("Backup On Save"), wxDefaultPosition, wxSize(400,-1), 0, wxDefaultValidator, _T("ID_CHECKBOX1")); - BackupOnSaveCheckBox->SetValue(false); - GridBagSizer1->Add(BackupOnSaveCheckBox, wxGBPosition(0, 0), wxGBSpan(1, 2), wxALL|wxEXPAND, 5); - BackupOnLaunchCheckBox = new wxCheckBox(this, ID_CHECKBOX2, _("Backup On Launch"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX2")); - BackupOnLaunchCheckBox->SetValue(false); - GridBagSizer1->Add(BackupOnLaunchCheckBox, wxGBPosition(1, 0), wxGBSpan(1, 2), wxALL|wxEXPAND, 5); - BackupSubfoldersCheckBox = new wxCheckBox(this, ID_CHECKBOX3, _("Backup Subfolders"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX3")); - BackupSubfoldersCheckBox->SetValue(false); - GridBagSizer1->Add(BackupSubfoldersCheckBox, wxGBPosition(2, 0), wxGBSpan(1, 2), wxALL|wxEXPAND, 5); - FlexGridSizer1 = new wxFlexGridSizer(0, 2, 0, 0); - FlexGridSizer1->AddGrowableCol(1); - StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Purge Backups"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT1")); - FlexGridSizer1->Add(StaticText1, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - PurgeIntervalChoice = new wxChoice(this, ID_CHOICE1, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE1")); - PurgeIntervalChoice->SetSelection( PurgeIntervalChoice->Append(_("Never")) ); - PurgeIntervalChoice->Append(_("Older than 360 days")); - PurgeIntervalChoice->Append(_("Older than 90 days")); - PurgeIntervalChoice->Append(_("Older than 30 days")); - PurgeIntervalChoice->Append(_("Older than 7 days")); - FlexGridSizer1->Add(PurgeIntervalChoice, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(FlexGridSizer1, wxGBPosition(3, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - StaticBoxSizer1 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Backup Directory")); - CheckBox_Backup = new wxCheckBox(this, ID_CHECKBOX4, _("Use Show Folder"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX4")); - CheckBox_Backup->SetValue(false); - StaticBoxSizer1->Add(CheckBox_Backup, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - DirPickerCtrl_Backup = new wxDirPickerCtrl(this, ID_DIRPICKERCTRL1, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(400,-1), wxDIRP_DIR_MUST_EXIST|wxDIRP_USE_TEXTCTRL, wxDefaultValidator, _T("ID_DIRPICKERCTRL1")); - StaticBoxSizer1->Add(DirPickerCtrl_Backup, 1, wxALL|wxEXPAND, 5); - GridBagSizer1->Add(StaticBoxSizer1, wxGBPosition(4, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - StaticBoxSizer2 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Alternative Backup Directory")); - DirPickerCtrl_AltBackup = new wxDirPickerCtrl(this, ID_DIRPICKERCTRL2, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDIRP_DIR_MUST_EXIST|wxDIRP_USE_TEXTCTRL, wxDefaultValidator, _T("ID_DIRPICKERCTRL2")); - StaticBoxSizer2->Add(DirPickerCtrl_AltBackup, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridBagSizer1->Add(StaticBoxSizer2, wxGBPosition(5, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - SetSizer(GridBagSizer1); - GridBagSizer1->Fit(this); - GridBagSizer1->SetSizeHints(this); - - Connect(ID_CHECKBOX1,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&BackupSettingsPanel::OnBackupOnSaveCheckBoxClick); - Connect(ID_CHECKBOX2,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&BackupSettingsPanel::OnBackupOnLaunchCheckBoxClick); - Connect(ID_CHECKBOX3,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&BackupSettingsPanel::OnBackupSubfoldersCheckBoxClick); - Connect(ID_CHOICE1,wxEVT_COMMAND_CHOICE_SELECTED,(wxObjectEventFunction)&BackupSettingsPanel::OnPurgeIntervalChoiceSelect); - Connect(ID_CHECKBOX4,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&BackupSettingsPanel::OnCheckBox_BackupClick); - Connect(ID_DIRPICKERCTRL1,wxEVT_COMMAND_DIRPICKER_CHANGED,(wxObjectEventFunction)&BackupSettingsPanel::OnDirPickerCtrl_BackupDirChanged); - Connect(ID_DIRPICKERCTRL2,wxEVT_COMMAND_DIRPICKER_CHANGED,(wxObjectEventFunction)&BackupSettingsPanel::OnDirPickerCtrl_AltBackupDirChanged); - //*) - - #ifdef _MSC_VER + Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + BackupOnSaveCheckBox = new wxCheckBox(this, wxID_ANY, _("Backup On Save")); + sizer->Add(BackupOnSaveCheckBox, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Make a backup copy of the show folder every time you save.")), 0, wxLEFT | wxBOTTOM, 24); + + BackupOnLaunchCheckBox = new wxCheckBox(this, wxID_ANY, _("Backup On Launch")); + sizer->Add(BackupOnLaunchCheckBox, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Make a backup copy of the show folder each time xLights starts.")), 0, wxLEFT | wxBOTTOM, 24); + + BackupSubfoldersCheckBox = new wxCheckBox(this, wxID_ANY, _("Backup Subfolders")); + sizer->Add(BackupSubfoldersCheckBox, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Include the show folder's subfolders in the backup.")), 0, wxLEFT | wxBOTTOM, 24); + + auto* purgeRow = new wxBoxSizer(wxHORIZONTAL); + purgeRow->Add(new wxStaticText(this, wxID_ANY, _("Purge Backups")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + PurgeIntervalChoice = new wxChoice(this, wxID_ANY); + PurgeIntervalChoice->SetSelection(PurgeIntervalChoice->Append(_("Never"))); + PurgeIntervalChoice->Append(_("Older than 360 days")); + PurgeIntervalChoice->Append(_("Older than 90 days")); + PurgeIntervalChoice->Append(_("Older than 30 days")); + PurgeIntervalChoice->Append(_("Older than 7 days")); + purgeRow->Add(PurgeIntervalChoice, 0, wxEXPAND); + sizer->Add(purgeRow, 0, wxLEFT | wxTOP, 5); + sizer->Add(MakePreferenceHint(this, _("Automatically delete backups older than the chosen age.")), 0, wxLEFT | wxBOTTOM, 5); + + sizer->Add(MakePreferenceSectionHeader(this, _("Backup Directory")), 0, wxLEFT | wxTOP, 10); + sizer->Add(MakePreferenceHint(this, _("Where backups are written. Use the show folder, or untick to choose a custom location.")), 0, wxLEFT, 16); + auto* backupRow = new wxBoxSizer(wxHORIZONTAL); + CheckBox_Backup = new wxCheckBox(this, wxID_ANY, _("Use Show Folder")); + backupRow->Add(CheckBox_Backup, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + DirPickerCtrl_Backup = new wxDirPickerCtrl(this, wxID_ANY, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(400, -1), wxDIRP_DIR_MUST_EXIST | wxDIRP_USE_TEXTCTRL); + backupRow->Add(DirPickerCtrl_Backup, 1, wxEXPAND); + sizer->Add(backupRow, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 16); + + sizer->Add(MakePreferenceSectionHeader(this, _("Alternative Backup Directory")), 0, wxLEFT | wxTOP, 10); + sizer->Add(MakePreferenceHint(this, _("Optional second location backups are also copied to (e.g. an external or cloud drive).")), 0, wxLEFT, 16); + DirPickerCtrl_AltBackup = new wxDirPickerCtrl(this, wxID_ANY, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(400, -1), wxDIRP_DIR_MUST_EXIST | wxDIRP_USE_TEXTCTRL); + sizer->Add(DirPickerCtrl_AltBackup, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 16); + + SetSizer(sizer); + sizer->SetSizeHints(this); + + BackupOnSaveCheckBox->Bind(wxEVT_CHECKBOX, &BackupSettingsPanel::OnBackupOnSaveCheckBoxClick, this); + BackupOnLaunchCheckBox->Bind(wxEVT_CHECKBOX, &BackupSettingsPanel::OnBackupOnLaunchCheckBoxClick, this); + BackupSubfoldersCheckBox->Bind(wxEVT_CHECKBOX, &BackupSettingsPanel::OnBackupSubfoldersCheckBoxClick, this); + PurgeIntervalChoice->Bind(wxEVT_CHOICE, &BackupSettingsPanel::OnPurgeIntervalChoiceSelect, this); + CheckBox_Backup->Bind(wxEVT_CHECKBOX, &BackupSettingsPanel::OnCheckBox_BackupClick, this); + DirPickerCtrl_Backup->Bind(wxEVT_DIRPICKER_CHANGED, &BackupSettingsPanel::OnDirPickerCtrl_BackupDirChanged, this); + DirPickerCtrl_AltBackup->Bind(wxEVT_DIRPICKER_CHANGED, &BackupSettingsPanel::OnDirPickerCtrl_AltBackupDirChanged, this); + +#ifdef _MSC_VER MSWDisableComposited(); - #endif +#endif } BackupSettingsPanel::~BackupSettingsPanel() { - //(*Destroy(BackupSettingsPanel) - //*) } + bool BackupSettingsPanel::TransferDataToWindow() { BackupOnSaveCheckBox->SetValue(frame->BackupOnSave()); BackupOnLaunchCheckBox->SetValue(frame->BackupOnLaunch()); @@ -232,13 +213,6 @@ void BackupSettingsPanel::OnCheckBox_BackupClick(wxCommandEvent& event) } } -void BackupSettingsPanel::OnCheckBox_AltBackupClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - void BackupSettingsPanel::OnDirPickerCtrl_BackupDirChanged(wxFileDirPickerEvent& event) { if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { diff --git a/src-ui-wx/preferences/BackupSettingsPanel.h b/src-ui-wx/preferences/BackupSettingsPanel.h index baa7900184..6648278716 100644 --- a/src-ui-wx/preferences/BackupSettingsPanel.h +++ b/src-ui-wx/preferences/BackupSettingsPanel.h @@ -10,19 +10,13 @@ * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt **************************************************************/ -//(*Headers(BackupSettingsPanel) #include +#include + class wxCheckBox; class wxChoice; class wxDirPickerCtrl; -class wxFlexGridSizer; -class wxGridBagSizer; -class wxStaticBoxSizer; -class wxStaticText; -//*) - -#include - +class wxCommandEvent; class xLightsFrame; class BackupSettingsPanel: public wxPanel @@ -34,46 +28,25 @@ class BackupSettingsPanel: public wxPanel BackupSettingsPanel(wxWindow* parent, xLightsFrame *frame, wxWindowID id=wxID_ANY); virtual ~BackupSettingsPanel(); - //(*Declarations(BackupSettingsPanel) - wxCheckBox* BackupOnLaunchCheckBox; - wxCheckBox* BackupOnSaveCheckBox; - wxCheckBox* BackupSubfoldersCheckBox; - wxCheckBox* CheckBox_Backup; - wxChoice* PurgeIntervalChoice; - wxDirPickerCtrl* DirPickerCtrl_AltBackup; - wxDirPickerCtrl* DirPickerCtrl_Backup; - wxStaticText* StaticText1; - //*) - virtual bool TransferDataFromWindow() override; virtual bool TransferDataToWindow() override; - protected: - - //(*Identifiers(BackupSettingsPanel) - static const long ID_CHECKBOX1; - static const long ID_CHECKBOX2; - static const long ID_CHECKBOX3; - static const long ID_STATICTEXT1; - static const long ID_CHOICE1; - static const long ID_CHECKBOX4; - static const long ID_DIRPICKERCTRL1; - static const long ID_DIRPICKERCTRL2; - //*) - private: xLightsFrame *frame; - - //(*Handlers(BackupSettingsPanel) - void OnBackupOnSaveCheckBoxClick(wxCommandEvent& event); - void OnBackupOnLaunchCheckBoxClick(wxCommandEvent& event); - void OnBackupSubfoldersCheckBoxClick(wxCommandEvent& event); - void OnPurgeIntervalChoiceSelect(wxCommandEvent& event); - void OnCheckBox_BackupClick(wxCommandEvent& event); - void OnCheckBox_AltBackupClick(wxCommandEvent& event); - void OnDirPickerCtrl_BackupDirChanged(wxFileDirPickerEvent& event); - void OnDirPickerCtrl_AltBackupDirChanged(wxFileDirPickerEvent& event); - //*) - DECLARE_EVENT_TABLE() + wxCheckBox* BackupOnLaunchCheckBox = nullptr; + wxCheckBox* BackupOnSaveCheckBox = nullptr; + wxCheckBox* BackupSubfoldersCheckBox = nullptr; + wxCheckBox* CheckBox_Backup = nullptr; + wxChoice* PurgeIntervalChoice = nullptr; + wxDirPickerCtrl* DirPickerCtrl_AltBackup = nullptr; + wxDirPickerCtrl* DirPickerCtrl_Backup = nullptr; + + void OnBackupOnSaveCheckBoxClick(wxCommandEvent& event); + void OnBackupOnLaunchCheckBoxClick(wxCommandEvent& event); + void OnBackupSubfoldersCheckBoxClick(wxCommandEvent& event); + void OnPurgeIntervalChoiceSelect(wxCommandEvent& event); + void OnCheckBox_BackupClick(wxCommandEvent& event); + void OnDirPickerCtrl_BackupDirChanged(wxFileDirPickerEvent& event); + void OnDirPickerCtrl_AltBackupDirChanged(wxFileDirPickerEvent& event); }; From 83d0b04060cc969f3c6e99c48c6f31b0787fdd8f Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 10:53:45 -0400 Subject: [PATCH 14/24] Sequencer/Preferences: add bindable Copy Layers-SubModels to Models; sort Key Bindings alphabetically - New keyboard action COPY_MODEL_LAYERS_TO_MODELS (Sequencer scope) mirroring the row-heading menu command; dispatched via MainSequencer to a new EffectsGrid::CopyModelLayersToModelsForSelection() that resolves the target from the current selection. Registered in KeyBindingTypes with a tip and a friendly name so it appears on the Key Bindings page, unassigned by default. - Key Bindings list now shows the Action column sorted alphabetically (collect-then-stable_sort) regardless of storage order. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/app-shell/KeyBindings.cpp | 2 ++ .../preferences/KeyBindingsSettingsPanel.cpp | 32 ++++++++++++++++--- src-ui-wx/sequencer/EffectsGrid.cpp | 11 +++++++ src-ui-wx/sequencer/EffectsGrid.h | 1 + src-ui-wx/sequencer/MainSequencer.cpp | 3 ++ 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src-ui-wx/app-shell/KeyBindings.cpp b/src-ui-wx/app-shell/KeyBindings.cpp index 4d276d95cb..f9913c1c50 100644 --- a/src-ui-wx/app-shell/KeyBindings.cpp +++ b/src-ui-wx/app-shell/KeyBindings.cpp @@ -72,6 +72,7 @@ static std::vector> KeyBindingTypes = { "EFFECT_ALIGN_BOTH", KBSCOPE::Sequence }, { "INSERT_LAYER_ABOVE", KBSCOPE::Sequence }, { "INSERT_LAYER_BELOW", KBSCOPE::Sequence }, + { "COPY_MODEL_LAYERS_TO_MODELS", KBSCOPE::Sequence }, { "TOGGLE_ELEMENT_EXPAND", KBSCOPE::Sequence }, { "SELECT_ALL", KBSCOPE::Sequence }, { "SELECT_ALL_NO_TIMING", KBSCOPE::Sequence }, @@ -207,6 +208,7 @@ static std::vector> keyBindingTips = { { "EFFECT_ALIGN_BOTH", "Stretch the selected effects so they all share the first one's start and end times." }, { "INSERT_LAYER_ABOVE", "Add a new, empty effect layer above the current model row." }, { "INSERT_LAYER_BELOW", "Add a new, empty effect layer below the current model row." }, + { "COPY_MODEL_LAYERS_TO_MODELS", "Copy the selected model's layers/submodels onto one or more other models (you choose the targets)." }, { "TOGGLE_ELEMENT_EXPAND", "Expand or collapse the selected model row to show/hide its strands, submodels and nodes." }, { "SELECT_ALL", "Select every effect and timing mark on the current row." }, { "SELECT_ALL_NO_TIMING", "Select every effect on the current row, leaving timing marks unselected." }, diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp index c9e06b42d7..8da3a029f0 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -327,6 +328,17 @@ void KeyBindingsSettingsPanel::LoadList() const KBSCOPE scope = EncodeScope(scopeSel); const wxString categorySel = Choice_Category->GetStringSelection(); const bool allCategories = categorySel.empty() || categorySel == "All"; + + // Collect the visible rows first so the Action column can be shown + // alphabetically regardless of the bindings' storage order. + struct Row { + wxString friendly; + wxString shortcut; + wxString details; + long id; + const KeyBinding* binding; + }; + std::vector rows; for (const auto& it : _keyBindings->GetBindings()) { if (!showAll && !it.InScope(scope)) @@ -349,13 +361,22 @@ void KeyBindingsSettingsPanel::LoadList() if (!match) continue; } - auto item = ListCtrl_Bindings->InsertItem(ListCtrl_Bindings->GetItemCount(), friendly); - ListCtrl_Bindings->SetItem(item, 1, shortcut); - ListCtrl_Bindings->SetItem(item, 2, details); - ListCtrl_Bindings->SetItemData(item, it.GetId()); + rows.push_back({ friendly, shortcut, details, (long)it.GetId(), &it }); + } + + std::stable_sort(rows.begin(), rows.end(), [](const Row& a, const Row& b) { + return a.friendly.CmpNoCase(b.friendly) < 0; + }); + + for (const auto& r : rows) + { + auto item = ListCtrl_Bindings->InsertItem(ListCtrl_Bindings->GetItemCount(), r.friendly); + ListCtrl_Bindings->SetItem(item, 1, r.shortcut); + ListCtrl_Bindings->SetItem(item, 2, r.details); + ListCtrl_Bindings->SetItemData(item, r.id); // Zebra striping using theme-aware colours (works in light and dark). ListCtrl_Bindings->SetItemBackgroundColour(item, (item % 2 == 0) ? evenRow : oddRow); - if (it.GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(it)) + if (r.binding->GetKey() != WXK_NONE && _keyBindings->IsDuplicateKey(*r.binding)) { ListCtrl_Bindings->SetItemTextColour(item, *wxRED); } @@ -417,6 +438,7 @@ wxString KeyBindingsSettingsPanel::FriendlyName(const std::string& type) { "EXPORT_LAYOUT_DXF", "Export Layout (DXF)" }, { "FPP_CONNECT", "FPP Connect" }, { "FOCUS_SEQUENCER", "Focus Effects Grid" }, + { "COPY_MODEL_LAYERS_TO_MODELS", "Copy Layers/SubModels to Models" }, }; auto o = overrides.find(type); if (o != overrides.end()) return o->second; diff --git a/src-ui-wx/sequencer/EffectsGrid.cpp b/src-ui-wx/sequencer/EffectsGrid.cpp index 179c9277cf..1a30cf1449 100644 --- a/src-ui-wx/sequencer/EffectsGrid.cpp +++ b/src-ui-wx/sequencer/EffectsGrid.cpp @@ -8576,6 +8576,17 @@ void EffectsGrid::CopyModelEffects(int row_number, bool allLayers, bool incSubMo } } +void EffectsGrid::CopyModelLayersToModelsForSelection() { + // Mirror InsertEffectLayerAbove's row resolution so the keyboard shortcut + // acts on the same model the user has selected in the grid. + int row = mSelectedRow; + if (row == -1 && mRangeStartRow == mRangeEndRow) + row = mRangeStartRow; + if (row == -1) + return; + CopyModelEffectsToModels(row); +} + void EffectsGrid::CopyModelEffectsToModels(int row_number) { Row_Information_Struct* ri = mSequenceElements->GetVisibleRowInformation(row_number); if (ri == nullptr || ri->element == nullptr) diff --git a/src-ui-wx/sequencer/EffectsGrid.h b/src-ui-wx/sequencer/EffectsGrid.h index b770314a69..4b1463bbe9 100644 --- a/src-ui-wx/sequencer/EffectsGrid.h +++ b/src-ui-wx/sequencer/EffectsGrid.h @@ -134,6 +134,7 @@ class EffectsGrid : public GRAPHICS_BASE_CLASS void CutModelEffects(int row_number, bool allLayers); void CopyModelEffects(int row_number, bool allLayers, bool incSubModels = false); void CopyModelEffectsToModels(int row_number); + void CopyModelLayersToModelsForSelection(); void PasteModelEffects(int row_number, bool allLayers); void PasteModelEffectsWithLayers(int row_number); void PasteModelEffectsWithSubModelLayers(int row_number); diff --git a/src-ui-wx/sequencer/MainSequencer.cpp b/src-ui-wx/sequencer/MainSequencer.cpp index 71bc6be2d7..7209cc0dd2 100755 --- a/src-ui-wx/sequencer/MainSequencer.cpp +++ b/src-ui-wx/sequencer/MainSequencer.cpp @@ -824,6 +824,9 @@ bool MainSequencer::HandleSequencerKeyBinding(wxKeyEvent& event) else if (type == "INSERT_LAYER_BELOW") { PanelEffectGrid->InsertEffectLayerBelow(); } + else if (type == "COPY_MODEL_LAYERS_TO_MODELS") { + PanelEffectGrid->CopyModelLayersToModelsForSelection(); + } else if (type == "TOGGLE_ELEMENT_EXPAND") { PanelEffectGrid->ToggleExpandElement(PanelRowHeadings); } From 2242afb8a32f2cc69444b59349165e2539e20b12 Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 11:29:00 -0400 Subject: [PATCH 15/24] Preferences: alphabetize left-nav pages; stop Random Effects lists clipping names - Sort the preferences pages alphabetically by name after registration (AI included), so the left nav reads A-Z. - Give the Random Effects shuttle lists a real 260px MinSize so wxEXPAND can't shrink them below the effect-name width inside the narrow page. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp | 2 ++ src-ui-wx/preferences/xLightsPreferences.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp index 38e4f79bec..30bc003848 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp @@ -51,6 +51,7 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights auto* leftCol = new wxBoxSizer(wxVERTICAL); leftCol->Add(new wxStaticText(this, wxID_ANY, _("Not used")), 0, wxLEFT | wxBOTTOM, 2); _availableList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(260, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); + _availableList->SetMinSize(wxSize(260, 340)); // stop wxEXPAND shrinking it and clipping effect names leftCol->Add(_availableList, 1, wxEXPAND); row->Add(leftCol, 1, wxEXPAND | wxRIGHT, 6); @@ -68,6 +69,7 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights auto* rightCol = new wxBoxSizer(wxVERTICAL); rightCol->Add(new wxStaticText(this, wxID_ANY, _("Used")), 0, wxLEFT | wxBOTTOM, 2); _usedList = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(260, 340), 0, nullptr, wxLB_EXTENDED | wxLB_SORT); + _usedList->SetMinSize(wxSize(260, 340)); // stop wxEXPAND shrinking it and clipping effect names rightCol->Add(_usedList, 1, wxEXPAND); row->Add(rightCol, 1, wxEXPAND | wxLEFT, 6); diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index c179e4593d..9c4be09846 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -8,6 +8,7 @@ * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt **************************************************************/ +#include #include #include #include @@ -154,6 +155,11 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) [this](wxWindow* p) { return (wxWindow*)(new ServicesPanel(p, _serviceManager.get())); } }); #endif + // Show the left-nav pages in alphabetical order. + std::sort(pages.begin(), pages.end(), [](const PrefPageDef& a, const PrefPageDef& b) { + return a.name.CmpNoCase(b.name) < 0; + }); + // Modeless so Preferences can stay open while you keep working in xLights. // Reuse an already-open instance rather than stacking a second dialog. for (wxWindow* w : wxTopLevelWindows) { From 35f6be53d455ff2a89b34c59724afb7c28118aa2 Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 11:38:41 -0400 Subject: [PATCH 16/24] Preferences: hide Other-page descriptions with their hidden controls; force Random Effects width - Other page: the Shaders and Ignore-vendor checkboxes are hidden per-platform; hide their description labels alongside them (and Layout()) so no orphaned description line is left behind. - Random Effects: set a 620px min on the panel itself so the scrolled page can't render it narrow and clip effect names. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/OtherSettingsPanel.cpp | 16 +++++++++++++--- src-ui-wx/preferences/OtherSettingsPanel.h | 6 ++++++ .../preferences/RandomEffectsSettingsPanel.cpp | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src-ui-wx/preferences/OtherSettingsPanel.cpp b/src-ui-wx/preferences/OtherSettingsPanel.cpp index a4a5bbbd32..b10042ec4a 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.cpp +++ b/src-ui-wx/preferences/OtherSettingsPanel.cpp @@ -77,11 +77,13 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind GPURenderCheckbox = new wxCheckBox(this, wxID_ANY, _("GPU Rendering")); GPURenderCheckbox->SetValue(true); sizer->Add(GPURenderCheckbox, 0, wxLEFT | wxTOP, 5); - sizer->Add(MakePreferenceHint(this, _("Render supported effects on the GPU for better performance.")), 0, wxLEFT | wxBOTTOM, 24); + GPURenderHint = MakePreferenceHint(this, _("Render supported effects on the GPU for better performance.")); + sizer->Add(GPURenderHint, 0, wxLEFT | wxBOTTOM, 24); ShaderCheckbox = new wxCheckBox(this, wxID_ANY, _("Shaders on Background Threads")); sizer->Add(ShaderCheckbox, 0, wxLEFT | wxTOP, 5); - sizer->Add(MakePreferenceHint(this, _("Render shader effects off the main thread to keep xLights responsive.")), 0, wxLEFT | wxBOTTOM, 24); + ShaderHint = MakePreferenceHint(this, _("Render shader effects off the main thread to keep xLights responsive.")); + sizer->Add(ShaderHint, 0, wxLEFT | wxBOTTOM, 24); CheckBox_BatchRenderPromptIssues = new wxCheckBox(this, wxID_ANY, _("Prompt issues during batch render")); CheckBox_BatchRenderPromptIssues->SetValue(true); @@ -94,7 +96,8 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind CheckBox_IgnoreVendorModelRecommendations = new wxCheckBox(this, wxID_ANY, _("Ignore vendor model recommendations")); sizer->Add(CheckBox_IgnoreVendorModelRecommendations, 0, wxLEFT | wxTOP, 5); - sizer->Add(MakePreferenceHint(this, _("Stop warning when a model differs from the vendor's recommended setup.")), 0, wxLEFT | wxBOTTOM, 24); + IgnoreVendorHint = MakePreferenceHint(this, _("Stop warning when a model differs from the vendor's recommended setup.")); + sizer->Add(IgnoreVendorHint, 0, wxLEFT | wxBOTTOM, 24); CheckBox_UseCustomColorPicker = new wxCheckBox(this, wxID_ANY, _("Use custom color picker (experimental)")); sizer->Add(CheckBox_UseCustomColorPicker, 0, wxLEFT | wxTOP, 5); @@ -144,16 +147,21 @@ OtherSettingsPanel::OtherSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind #ifdef __LINUX__ ShaderCheckbox->Hide(); + ShaderHint->Hide(); GPURenderCheckbox->Hide(); + GPURenderHint->Hide(); #endif #ifdef __WXOSX__ if (!isMetalComputeSupported()) { GPURenderCheckbox->Hide(); + GPURenderHint->Hide(); } ShaderCheckbox->Hide(); + ShaderHint->Hide(); #endif #ifdef __WXMSW__ GPURenderCheckbox->Hide(); + GPURenderHint->Hide(); MSWDisableComposited(); #endif @@ -223,6 +231,8 @@ bool OtherSettingsPanel::TransferDataToWindow() { #ifndef IGNORE_VENDORS CheckBox_IgnoreVendorModelRecommendations->SetValue(false); CheckBox_IgnoreVendorModelRecommendations->Hide(); + IgnoreVendorHint->Hide(); + Layout(); #endif #endif return true; diff --git a/src-ui-wx/preferences/OtherSettingsPanel.h b/src-ui-wx/preferences/OtherSettingsPanel.h index c30121d86c..c5a9fbb59b 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.h +++ b/src-ui-wx/preferences/OtherSettingsPanel.h @@ -61,6 +61,12 @@ class OtherSettingsPanel: public wxPanel wxSpinCtrlDouble* CtrlPingInterval = nullptr; wxTextCtrl* eMailTextControl = nullptr; + // Description labels for controls that may be hidden per-platform, so + // the description can be hidden alongside its control. + wxWindow* GPURenderHint = nullptr; + wxWindow* ShaderHint = nullptr; + wxWindow* IgnoreVendorHint = nullptr; + // Write changes back immediately on platforms where the preferences // editor applies as-you-go. void ApplyIfImmediate(); diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp index 30bc003848..780114418f 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp @@ -76,6 +76,10 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights mainSizer->Add(row, 1, wxEXPAND | wxALL, 5); SetSizer(mainSizer); mainSizer->SetSizeHints(this); + // The page lives in a scrolled window that lays the panel out at its own + // minimum size, so force a width wide enough for both lists to show full + // effect names rather than clipping them with an ellipsis. + SetMinSize(wxSize(620, 420)); const wxArrayString& used = frame->RandomEffectsToUse(); for (int i = 0; i < (int)frame->GetEffectManager().size(); i++) { From 6948d6e96ae0cf3de5b7f755abd2d5f53bf17650 Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 11:45:31 -0400 Subject: [PATCH 17/24] Preferences: enforce a minimum dialog size so pages can't be narrowed into clipping wxTreebook doesn't propagate page minimum sizes, so the dialog had no effective minimum width and could be shrunk until content (Random Effects lists, etc.) clipped with an ellipsis. Set a minimum size (nav + full-width page) and grow the initial size to match. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/xLightsPreferences.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index 9c4be09846..2e59cf7a03 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -101,6 +101,16 @@ class xlPreferencesListDialog : public wxDialog { SetSizer(topSizer); topSizer->SetSizeHints(this); Fit(); + + // wxTreebook doesn't propagate its pages' minimum size to its own, so + // SetSizeHints leaves the dialog with no real minimum width and it can + // be narrowed until page content (e.g. the Random Effects lists) clips. + // Enforce a minimum wide enough for the nav plus a full-width page. + wxSize minDlg(minWidth + FromDIP(210), minHeight + FromDIP(70)); + SetMinSize(minDlg); + wxSize cur = GetSize(); + SetSize(wxSize(std::max(cur.GetWidth(), minDlg.GetWidth()), + std::max(cur.GetHeight(), minDlg.GetHeight()))); CentreOnParent(); } }; From 4ba5857c44919e52893c216449ed0bfe049ede29 Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 12:22:14 -0400 Subject: [PATCH 18/24] Preferences: rebuild Random Effects lists after layout so names aren't clipped On macOS a wxListBox populated before it reaches its final width keeps rendering items truncated to the old column width. Rebuild each list's contents in a CallAfter once the panel has its real size so the column re-measures. Co-Authored-By: Claude Opus 4.8 --- .../preferences/RandomEffectsSettingsPanel.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp index 780114418f..d0ba35606c 100644 --- a/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp +++ b/src-ui-wx/preferences/RandomEffectsSettingsPanel.cpp @@ -91,6 +91,19 @@ RandomEffectsSettingsPanel::RandomEffectsSettingsPanel(wxWindow* parent, xLights } } + // On macOS a wxListBox populated before it reaches its final width can keep + // rendering items truncated to the old (narrow) column width. Once the panel + // has its real size, rebuild each list's contents so the column re-measures. + CallAfter([this] { + for (wxListBox* lb : { _availableList, _usedList }) { + wxArrayString items; + for (unsigned int i = 0; i < lb->GetCount(); ++i) { + items.Add(lb->GetString(i)); + } + lb->Set(items); + } + }); + btnAdd->Bind(wxEVT_BUTTON, &RandomEffectsSettingsPanel::OnAdd, this); btnRemove->Bind(wxEVT_BUTTON, &RandomEffectsSettingsPanel::OnRemove, this); _availableList->Bind(wxEVT_LISTBOX_DCLICK, &RandomEffectsSettingsPanel::OnAvailableDClick, this); From 483124023f69ad17bcd938eaed71111ee633b1aa Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 14:58:37 -0400 Subject: [PATCH 19/24] Preferences: hand-write Check Sequence and Output pages with visible descriptions Convert both pages off wxSmith to a hand-written layout with a greyed description under each setting (surfacing what was previously only in tooltips). Behaviour and immediate-apply unchanged. Co-Authored-By: Claude Opus 4.8 --- .../CheckSequenceSettingsPanel.cpp | 164 +++++------------- .../preferences/CheckSequenceSettingsPanel.h | 50 ++---- src-ui-wx/preferences/OutputSettingsPanel.cpp | 133 ++++++-------- src-ui-wx/preferences/OutputSettingsPanel.h | 38 +--- 4 files changed, 114 insertions(+), 271 deletions(-) diff --git a/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp b/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp index c501ef3206..8daed26200 100644 --- a/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp +++ b/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp @@ -9,98 +9,66 @@ **************************************************************/ #include "CheckSequenceSettingsPanel.h" +#include "PrefPanelUtils.h" -//(*InternalHeaders(CheckSequenceSettingsPanel) #include -#include #include +#include #include #include -//*) #include #include "xLightsMain.h" -#include "../graphics/xlGraphicsBase.h" - -//(*IdInit(CheckSequenceSettingsPanel) -const long CheckSequenceSettingsPanel::ID_STATICTEXT1 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX1 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX2 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX3 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX4 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX5 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX6 = wxNewId(); -const long CheckSequenceSettingsPanel::ID_CHECKBOX7 = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(CheckSequenceSettingsPanel,wxPanel) - //(*EventTable(CheckSequenceSettingsPanel) - //*) -END_EVENT_TABLE() CheckSequenceSettingsPanel::CheckSequenceSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id,const wxPoint& pos,const wxSize& size) : frame(f) { - //(*Initialize(CheckSequenceSettingsPanel) - wxGridBagSizer* GridBagSizer1; - - Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); - GridBagSizer1 = new wxGridBagSizer(0, 0); - StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Disabling check sequence checks can reduce clutter in your check sequence results\nbut can also mask causes of issues such as incorrect pixels lighting up or slow rendering.\n\nPlease ensure you understand what the check sequence options do before deciding\nto disable them on someone\'s advice."), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT1")); - GridBagSizer1->Add(StaticText1, wxGBPosition(0, 0), wxDefaultSpan, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - CheckBox_DupUniv = new wxCheckBox(this, ID_CHECKBOX1, _("Disable checks on duplicate use of universe/id across controllers."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); - CheckBox_DupUniv->SetValue(false); - CheckBox_DupUniv->SetHelpText(_("If you are using unicast and not using #universe:startchannel addressing then this check can be disabled.")); - GridBagSizer1->Add(CheckBox_DupUniv, wxGBPosition(1, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_NonContigChOnPort = new wxCheckBox(this, ID_CHECKBOX2, _("Disable checks for non-contiguous channels on controller ports."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX2")); - CheckBox_NonContigChOnPort->SetValue(false); - CheckBox_NonContigChOnPort->SetHelpText(_("If you only use controllers that support virtual strings then this check can be safely disabled.")); - GridBagSizer1->Add(CheckBox_NonContigChOnPort, wxGBPosition(2, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_PreviewGroup = new wxCheckBox(this, ID_CHECKBOX3, _("Disable checks for groups containing models from different previews."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX3")); - CheckBox_PreviewGroup->SetValue(false); - CheckBox_PreviewGroup->SetHelpText(_("Adding models to groups from different previews can make them appear in other views with no obvious reason why they do.")); - GridBagSizer1->Add(CheckBox_PreviewGroup, wxGBPosition(3, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_DupNodeMG = new wxCheckBox(this, ID_CHECKBOX4, _("Disable checks for duplicate nodes in model groups."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX4")); - CheckBox_DupNodeMG->SetValue(false); - CheckBox_DupNodeMG->SetHelpText(_("Duplicate nodes in model groups can lead to pixels lighting during effects at unexpected times.")); - GridBagSizer1->Add(CheckBox_DupNodeMG, wxGBPosition(4, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_TransTime = new wxCheckBox(this, ID_CHECKBOX5, _("Disable transition time checking."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX5")); - CheckBox_TransTime->SetValue(false); - CheckBox_TransTime->SetHelpText(_("Transition times that overlap or extend beyond the duration of the effect can lead to unexpected amounts of dimming.")); - GridBagSizer1->Add(CheckBox_TransTime, wxGBPosition(5, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_CustomSizeCheck = new wxCheckBox(this, ID_CHECKBOX6, _("Disable custom model size checking."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX6")); - CheckBox_CustomSizeCheck->SetValue(false); - CheckBox_CustomSizeCheck->SetHelpText(_("Large custom models with largely empty cells generate significant rendering overhead. You may want to consider shrinking the custom model dimensions if this can done without too significantly adversely affecting appearance.")); - GridBagSizer1->Add(CheckBox_CustomSizeCheck, wxGBPosition(6, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_DisableSketch = new wxCheckBox(this, ID_CHECKBOX7, _("Disable sketch image file checking."), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX7")); - CheckBox_DisableSketch->SetValue(false); - CheckBox_DisableSketch->SetHelpText(_("Sketch effect image files are not essential to rendering.")); - GridBagSizer1->Add(CheckBox_DisableSketch, wxGBPosition(7, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - SetSizer(GridBagSizer1); - GridBagSizer1->Fit(this); - GridBagSizer1->SetSizeHints(this); - - Connect(ID_CHECKBOX1,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_DupUnivClick); - Connect(ID_CHECKBOX2,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_NonContigChOnPortClick); - Connect(ID_CHECKBOX3,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_PreviewGroupClick); - Connect(ID_CHECKBOX4,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_DupNodeMGClick); - Connect(ID_CHECKBOX5,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_TransTimeClick); - Connect(ID_CHECKBOX6,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_CustomSizeCheckClick); - Connect(ID_CHECKBOX7,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&CheckSequenceSettingsPanel::OnCheckBox_DisableSketchClick); - //*) + Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* intro = new wxStaticText(this, wxID_ANY, + _("Disabling check sequence checks can reduce clutter in your check sequence results, " + "but can also mask causes of issues such as incorrect pixels lighting up or slow " + "rendering. Please make sure you understand what each option does before disabling " + "it on someone's advice.")); + intro->Wrap(560); + sizer->Add(intro, 0, wxALL, 8); + + struct Item { + wxCheckBox** ctrl; + wxString label; + wxString hint; + }; + const Item items[] = { + { &CheckBox_DupUniv, _("Disable checks on duplicate use of universe/id across controllers."), + _("If you are using unicast and not using #universe:startchannel addressing then this check can be disabled.") }, + { &CheckBox_NonContigChOnPort, _("Disable checks for non-contiguous channels on controller ports."), + _("If you only use controllers that support virtual strings then this check can be safely disabled.") }, + { &CheckBox_PreviewGroup, _("Disable checks for groups containing models from different previews."), + _("Adding models to groups from different previews can make them appear in other views with no obvious reason why.") }, + { &CheckBox_DupNodeMG, _("Disable checks for duplicate nodes in model groups."), + _("Duplicate nodes in model groups can light pixels during effects at unexpected times.") }, + { &CheckBox_TransTime, _("Disable transition time checking."), + _("Transition times that overlap or extend beyond the effect duration can lead to unexpected dimming.") }, + { &CheckBox_CustomSizeCheck, _("Disable custom model size checking."), + _("Large custom models with mostly empty cells add significant rendering overhead; consider shrinking them.") }, + { &CheckBox_DisableSketch, _("Disable sketch image file checking."), + _("Sketch effect image files are not essential to rendering.") }, + }; + + for (const auto& it : items) { + *it.ctrl = new wxCheckBox(this, wxID_ANY, it.label); + sizer->Add(*it.ctrl, 0, wxLEFT | wxTOP, 8); + sizer->Add(MakePreferenceHint(this, it.hint), 0, wxLEFT | wxBOTTOM, 26); + (*it.ctrl)->Bind(wxEVT_CHECKBOX, &CheckSequenceSettingsPanel::OnChanged, this); + } - CheckBox_DupUniv->SetToolTip(CheckBox_DupUniv->GetHelpText()); - CheckBox_NonContigChOnPort->SetToolTip(CheckBox_NonContigChOnPort->GetHelpText()); - CheckBox_PreviewGroup->SetToolTip(CheckBox_PreviewGroup->GetHelpText()); - CheckBox_DupNodeMG->SetToolTip(CheckBox_DupNodeMG->GetHelpText()); - CheckBox_TransTime->SetToolTip(CheckBox_TransTime->GetHelpText()); - CheckBox_CustomSizeCheck->SetToolTip(CheckBox_CustomSizeCheck->GetHelpText()); - CheckBox_DisableSketch->SetToolTip(CheckBox_DisableSketch->GetHelpText()); + SetSizer(sizer); + sizer->SetSizeHints(this); } CheckSequenceSettingsPanel::~CheckSequenceSettingsPanel() { - //(*Destroy(CheckSequenceSettingsPanel) - //*) } bool CheckSequenceSettingsPanel::TransferDataToWindow() { @@ -124,49 +92,7 @@ bool CheckSequenceSettingsPanel::TransferDataFromWindow() { return true; } -void CheckSequenceSettingsPanel::OnCheckBox_DupUnivClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void CheckSequenceSettingsPanel::OnCheckBox_NonContigChOnPortClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void CheckSequenceSettingsPanel::OnCheckBox_PreviewGroupClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void CheckSequenceSettingsPanel::OnCheckBox_DupNodeMGClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void CheckSequenceSettingsPanel::OnCheckBox_TransTimeClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void CheckSequenceSettingsPanel::OnCheckBox_CustomSizeCheckClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void CheckSequenceSettingsPanel::OnCheckBox_DisableSketchClick(wxCommandEvent& event) +void CheckSequenceSettingsPanel::OnChanged(wxCommandEvent& event) { if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { TransferDataFromWindow(); diff --git a/src-ui-wx/preferences/CheckSequenceSettingsPanel.h b/src-ui-wx/preferences/CheckSequenceSettingsPanel.h index 9c01807008..6934130a78 100644 --- a/src-ui-wx/preferences/CheckSequenceSettingsPanel.h +++ b/src-ui-wx/preferences/CheckSequenceSettingsPanel.h @@ -10,14 +10,12 @@ * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt **************************************************************/ -//(*Headers(CheckSequenceSettingsPanel) #include -class wxCheckBox; -class wxGridBagSizer; -class wxStaticText; -//*) +class wxCheckBox; +class wxCommandEvent; class xLightsFrame; + class CheckSequenceSettingsPanel: public wxPanel { public: @@ -25,45 +23,19 @@ class CheckSequenceSettingsPanel: public wxPanel CheckSequenceSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize); virtual ~CheckSequenceSettingsPanel(); - //(*Declarations(CheckSequenceSettingsPanel) - wxCheckBox* CheckBox_CustomSizeCheck; - wxCheckBox* CheckBox_DisableSketch; - wxCheckBox* CheckBox_DupNodeMG; - wxCheckBox* CheckBox_DupUniv; - wxCheckBox* CheckBox_NonContigChOnPort; - wxCheckBox* CheckBox_PreviewGroup; - wxCheckBox* CheckBox_TransTime; - wxStaticText* StaticText1; - //*) - virtual bool TransferDataFromWindow() override; virtual bool TransferDataToWindow() override; - protected: - - //(*Identifiers(CheckSequenceSettingsPanel) - static const long ID_STATICTEXT1; - static const long ID_CHECKBOX1; - static const long ID_CHECKBOX2; - static const long ID_CHECKBOX3; - static const long ID_CHECKBOX4; - static const long ID_CHECKBOX5; - static const long ID_CHECKBOX6; - static const long ID_CHECKBOX7; - //*) - private: xLightsFrame *frame; - //(*Handlers(CheckSequenceSettingsPanel) - void OnCheckBox_DupUnivClick(wxCommandEvent& event); - void OnCheckBox_NonContigChOnPortClick(wxCommandEvent& event); - void OnCheckBox_PreviewGroupClick(wxCommandEvent& event); - void OnCheckBox_DupNodeMGClick(wxCommandEvent& event); - void OnCheckBox_TransTimeClick(wxCommandEvent& event); - void OnCheckBox_CustomSizeCheckClick(wxCommandEvent& event); - void OnCheckBox_DisableSketchClick(wxCommandEvent& event); - //*) + wxCheckBox* CheckBox_CustomSizeCheck = nullptr; + wxCheckBox* CheckBox_DisableSketch = nullptr; + wxCheckBox* CheckBox_DupNodeMG = nullptr; + wxCheckBox* CheckBox_DupUniv = nullptr; + wxCheckBox* CheckBox_NonContigChOnPort = nullptr; + wxCheckBox* CheckBox_PreviewGroup = nullptr; + wxCheckBox* CheckBox_TransTime = nullptr; - DECLARE_EVENT_TABLE() + void OnChanged(wxCommandEvent& event); }; diff --git a/src-ui-wx/preferences/OutputSettingsPanel.cpp b/src-ui-wx/preferences/OutputSettingsPanel.cpp index 11f2c04179..fb1862f487 100644 --- a/src-ui-wx/preferences/OutputSettingsPanel.cpp +++ b/src-ui-wx/preferences/OutputSettingsPanel.cpp @@ -9,75 +9,63 @@ **************************************************************/ #include "OutputSettingsPanel.h" +#include "PrefPanelUtils.h" -//(*InternalHeaders(OutputSettingsPanel) #include #include -#include #include +#include #include #include -//*) #include #include "xLightsMain.h" #include "utils/ip_utils.h" -//(*IdInit(OutputSettingsPanel) -const long OutputSettingsPanel::ID_CHECKBOX1 = wxNewId(); -const long OutputSettingsPanel::ID_STATICTEXT1 = wxNewId(); -const long OutputSettingsPanel::ID_CHOICE1 = wxNewId(); -const long OutputSettingsPanel::ID_CHOICE2 = wxNewId(); -const long OutputSettingsPanel::ID_CHOICE3 = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(OutputSettingsPanel,wxPanel) - //(*EventTable(OutputSettingsPanel) - //*) -END_EVENT_TABLE() - OutputSettingsPanel::OutputSettingsPanel(wxWindow* parent,xLightsFrame *f,wxWindowID id,const wxPoint& pos,const wxSize& size) : frame(f) { - //(*Initialize(OutputSettingsPanel) - wxGridBagSizer* GridBagSizer1; - wxStaticText* StaticText2; - wxStaticText* StaticText3; - - Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); - GridBagSizer1 = new wxGridBagSizer(0, 0); - FrameSyncCheckBox = new wxCheckBox(this, ID_CHECKBOX1, _("Use Frame Sync"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); - FrameSyncCheckBox->SetValue(false); - GridBagSizer1->Add(FrameSyncCheckBox, wxGBPosition(0, 0), wxGBSpan(1, 2), wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Force Local IP"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT1")); - GridBagSizer1->Add(StaticText1, wxGBPosition(1, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText2 = new wxStaticText(this, wxID_ANY, _("Duplicate Frames to Suppress"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText2, wxGBPosition(2, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText3 = new wxStaticText(this, wxID_ANY, _("xFade/xSchedule"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText3, wxGBPosition(3, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - ForceLocalIPChoice = new wxChoice(this, ID_CHOICE1, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE1")); - ForceLocalIPChoice->SetSelection( ForceLocalIPChoice->Append(wxEmptyString) ); - GridBagSizer1->Add(ForceLocalIPChoice, wxGBPosition(1, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - DuplicateSuppressChoice = new wxChoice(this, ID_CHOICE2, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE2")); - DuplicateSuppressChoice->SetSelection( DuplicateSuppressChoice->Append(_("None")) ); - DuplicateSuppressChoice->Append(_("10")); - DuplicateSuppressChoice->Append(_("20")); - DuplicateSuppressChoice->Append(_("40")); - GridBagSizer1->Add(DuplicateSuppressChoice, wxGBPosition(2, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - xFadexScheduleChoice = new wxChoice(this, ID_CHOICE3, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE3")); - xFadexScheduleChoice->SetSelection( xFadexScheduleChoice->Append(_("Disabled")) ); - xFadexScheduleChoice->Append(_("Port A")); - xFadexScheduleChoice->Append(_("Port B")); - GridBagSizer1->Add(xFadexScheduleChoice, wxGBPosition(3, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - SetSizer(GridBagSizer1); - GridBagSizer1->Fit(this); - GridBagSizer1->SetSizeHints(this); - - Connect(ID_CHECKBOX1,wxEVT_COMMAND_CHECKBOX_CLICKED,(wxObjectEventFunction)&OutputSettingsPanel::OnFrameSyncCheckBoxClick); - Connect(ID_CHOICE1,wxEVT_COMMAND_CHOICE_SELECTED,(wxObjectEventFunction)&OutputSettingsPanel::OnForceLocalIPChoiceSelect); - Connect(ID_CHOICE2,wxEVT_COMMAND_CHOICE_SELECTED,(wxObjectEventFunction)&OutputSettingsPanel::OnDuplicateSuppressChoiceSelect); - Connect(ID_CHOICE3,wxEVT_COMMAND_CHOICE_SELECTED,(wxObjectEventFunction)&OutputSettingsPanel::OnxFadexScheduleChoiceSelect); - //*) - + Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + FrameSyncCheckBox = new wxCheckBox(this, wxID_ANY, _("Use Frame Sync")); + sizer->Add(FrameSyncCheckBox, 0, wxLEFT | wxTOP, 8); + sizer->Add(MakePreferenceHint(this, _("Send an E1.31 sync packet each frame so multiple controllers stay in step.")), 0, wxLEFT | wxBOTTOM, 26); + + auto* grid = new wxFlexGridSizer(0, 2, 0, 0); + grid->AddGrowableCol(1); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Force Local IP")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + ForceLocalIPChoice = new wxChoice(this, wxID_ANY); + ForceLocalIPChoice->SetSelection(ForceLocalIPChoice->Append(wxEmptyString)); + grid->Add(ForceLocalIPChoice, 1, wxALL | wxEXPAND, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, _("Send all network output from a specific local adapter (leave blank to auto-select).")), 0, wxLEFT | wxBOTTOM, 5); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Duplicate Frames to Suppress")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + DuplicateSuppressChoice = new wxChoice(this, wxID_ANY); + DuplicateSuppressChoice->SetSelection(DuplicateSuppressChoice->Append(_("None"))); + DuplicateSuppressChoice->Append(_("10")); + DuplicateSuppressChoice->Append(_("20")); + DuplicateSuppressChoice->Append(_("40")); + grid->Add(DuplicateSuppressChoice, 1, wxALL | wxEXPAND, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, _("Stop resending unchanged frames after this many duplicates to reduce network traffic.")), 0, wxLEFT | wxBOTTOM, 5); + + grid->Add(new wxStaticText(this, wxID_ANY, _("xFade/xSchedule")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + xFadexScheduleChoice = new wxChoice(this, wxID_ANY); + xFadexScheduleChoice->SetSelection(xFadexScheduleChoice->Append(_("Disabled"))); + xFadexScheduleChoice->Append(_("Port A")); + xFadexScheduleChoice->Append(_("Port B")); + grid->Add(xFadexScheduleChoice, 1, wxALL | wxEXPAND, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, _("Share this show with xFade/xSchedule on the chosen sync port.")), 0, wxLEFT | wxBOTTOM, 5); + + sizer->Add(grid, 0, wxEXPAND | wxALL, 5); + + SetSizer(sizer); + sizer->SetSizeHints(this); + std::string localIP = frame->_outputManager.GetGlobalForceLocalIP(); auto ips = ip_utils::GetLocalIPs(); @@ -102,15 +90,15 @@ OutputSettingsPanel::OutputSettingsPanel(wxWindow* parent,xLightsFrame *f,wxWind } ForceLocalIPChoice->Set(choices); ForceLocalIPChoice->SetSelection(sel); - GridBagSizer1->Layout(); - GridBagSizer1->Fit(this); - GridBagSizer1->SetSizeHints(this); + + FrameSyncCheckBox->Bind(wxEVT_CHECKBOX, &OutputSettingsPanel::OnChanged, this); + ForceLocalIPChoice->Bind(wxEVT_CHOICE, &OutputSettingsPanel::OnChanged, this); + DuplicateSuppressChoice->Bind(wxEVT_CHOICE, &OutputSettingsPanel::OnChanged, this); + xFadexScheduleChoice->Bind(wxEVT_CHOICE, &OutputSettingsPanel::OnChanged, this); } OutputSettingsPanel::~OutputSettingsPanel() { - //(*Destroy(OutputSettingsPanel) - //*) } bool OutputSettingsPanel::TransferDataFromWindow() { @@ -153,28 +141,7 @@ bool OutputSettingsPanel::TransferDataToWindow() { return true; } -void OutputSettingsPanel::OnFrameSyncCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void OutputSettingsPanel::OnForceLocalIPChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void OutputSettingsPanel::OnDuplicateSuppressChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void OutputSettingsPanel::OnxFadexScheduleChoiceSelect(wxCommandEvent& event) +void OutputSettingsPanel::OnChanged(wxCommandEvent& event) { if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { TransferDataFromWindow(); diff --git a/src-ui-wx/preferences/OutputSettingsPanel.h b/src-ui-wx/preferences/OutputSettingsPanel.h index 90a817433a..13bd3336e1 100644 --- a/src-ui-wx/preferences/OutputSettingsPanel.h +++ b/src-ui-wx/preferences/OutputSettingsPanel.h @@ -10,15 +10,13 @@ * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt **************************************************************/ -//(*Headers(OutputSettingsPanel) #include + class wxCheckBox; class wxChoice; -class wxGridBagSizer; -class wxStaticText; -//*) - +class wxCommandEvent; class xLightsFrame; + class OutputSettingsPanel: public wxPanel { public: @@ -26,36 +24,16 @@ class OutputSettingsPanel: public wxPanel OutputSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize); virtual ~OutputSettingsPanel(); - //(*Declarations(OutputSettingsPanel) - wxCheckBox* FrameSyncCheckBox; - wxChoice* DuplicateSuppressChoice; - wxChoice* ForceLocalIPChoice; - wxChoice* xFadexScheduleChoice; - wxStaticText* StaticText1; - //*) - virtual bool TransferDataFromWindow() override; virtual bool TransferDataToWindow() override; - protected: - - //(*Identifiers(OutputSettingsPanel) - static const long ID_CHECKBOX1; - static const long ID_STATICTEXT1; - static const long ID_CHOICE1; - static const long ID_CHOICE2; - static const long ID_CHOICE3; - //*) - private: xLightsFrame *frame; - //(*Handlers(OutputSettingsPanel) - void OnFrameSyncCheckBoxClick(wxCommandEvent& event); - void OnForceLocalIPChoiceSelect(wxCommandEvent& event); - void OnDuplicateSuppressChoiceSelect(wxCommandEvent& event); - void OnxFadexScheduleChoiceSelect(wxCommandEvent& event); - //*) + wxCheckBox* FrameSyncCheckBox = nullptr; + wxChoice* DuplicateSuppressChoice = nullptr; + wxChoice* ForceLocalIPChoice = nullptr; + wxChoice* xFadexScheduleChoice = nullptr; - DECLARE_EVENT_TABLE() + void OnChanged(wxCommandEvent& event); }; From 4ae71f0415dcdcfde12cdbb04003121689cf1720 Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 15:01:36 -0400 Subject: [PATCH 20/24] Preferences: hand-write View and Effects Grid pages with visible descriptions Convert both pages off wxSmith to a hand-written layout with a greyed description under each setting. Behaviour and immediate-apply unchanged. Co-Authored-By: Claude Opus 4.8 --- .../preferences/EffectsGridSettingsPanel.cpp | 276 +++++------------- .../preferences/EffectsGridSettingsPanel.h | 77 ++--- src-ui-wx/preferences/ViewSettingsPanel.cpp | 257 +++++----------- src-ui-wx/preferences/ViewSettingsPanel.h | 72 ++--- 4 files changed, 168 insertions(+), 514 deletions(-) diff --git a/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp b/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp index cd17799561..ba4a0e2c41 100644 --- a/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp +++ b/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp @@ -9,149 +9,94 @@ **************************************************************/ #include "EffectsGridSettingsPanel.h" +#include "PrefPanelUtils.h" -//(*InternalHeaders(EffectsGridSettingsPanel) #include #include #include #include #include #include -//*) #include #include "xLightsMain.h" -//(*IdInit(EffectsGridSettingsPanel) -const wxWindowID EffectsGridSettingsPanel::ID_CHOICE1 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX1 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX2 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX7 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX3 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_STATICTEXT1 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHOICE2 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX4 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX6 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX5 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX8 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHECKBOX9 = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_STATICTEXT_PASTE_AS = wxNewId(); -const wxWindowID EffectsGridSettingsPanel::ID_CHOICE_PASTE_AS = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(EffectsGridSettingsPanel,wxPanel) - //(*EventTable(EffectsGridSettingsPanel) - //*) -END_EVENT_TABLE() - EffectsGridSettingsPanel::EffectsGridSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id,const wxPoint& pos,const wxSize& size) : frame(f) { - //(*Initialize(EffectsGridSettingsPanel) - wxFlexGridSizer* GridSizer1; - wxStaticText* StaticText5; + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("wxID_ANY")); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + // Labelled choices at the top. + auto* grid = new wxFlexGridSizer(0, 2, 0, 0); + grid->AddGrowableCol(1); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Spacing")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + GridSpacingChoice = new wxChoice(this, wxID_ANY); + GridSpacingChoice->Append(_("Extra Small")); + GridSpacingChoice->Append(_("Small")); + GridSpacingChoice->SetSelection(GridSpacingChoice->Append(_("Medium"))); + GridSpacingChoice->Append(_("Large")); + GridSpacingChoice->Append(_("Extra Large")); + grid->Add(GridSpacingChoice, 0, wxALL, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, _("Row height / effect icon size in the sequencer grid.")), 0, wxLEFT | wxBOTTOM, 5); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Double Click Mode")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + DoubleClickChoice = new wxChoice(this, wxID_ANY); + DoubleClickChoice->Append(_("Edit Text")); + DoubleClickChoice->SetSelection(DoubleClickChoice->Append(_("Play Timing"))); + grid->Add(DoubleClickChoice, 0, wxALL, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, _("What double-clicking a timing effect does — edit its text or play it.")), 0, wxLEFT | wxBOTTOM, 5); + + grid->Add(new wxStaticText(this, wxID_ANY, _("Paste As")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + PasteAsChoice = new wxChoice(this, wxID_ANY); + PasteAsChoice->SetSelection(PasteAsChoice->Append(_("Relative"))); + PasteAsChoice->Append(_("Layers")); + grid->Add(PasteAsChoice, 0, wxALL, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, _("Relative pastes at the selected position; Layers preserves the copied layer structure.")), 0, wxLEFT | wxBOTTOM, 5); + + sizer->Add(grid, 0, wxEXPAND | wxALL, 5); + + struct Toggle { + wxCheckBox** ctrl; + wxString label; + wxString hint; + bool defaultValue; + }; + const Toggle toggles[] = { + { &IconBackgroundsCheckBox, _("Effect Backgrounds"), _("Show animated gif backgrounds for most effects."), false }, + { &NodeValuesCheckBox, _("Node Values"), _("Show individual node values on the grid."), false }, + { &GroupEffectIndicator, _("Group Effect Indicator"), _("Show a bar on a model row indicating an effect is present."), true }, + { &SnapToTimingCheckBox, _("Snap to Timing Marks"), _("Snap effect edges to the nearest timing mark while dragging."), false }, + { &SmallWaveformCheckBox, _("Small Waveform"), _("Reduce the vertical size of the audio waveform."), false }, + { &TransistionMarksCheckBox, _("Display Transition Marks"), _("Show in/out transition (fade) markers on effects."), true }, + { &ColorUpdateWarnCheckBox, _("Hide Color Update Warning"), _("Don't warn when applying colours to multiple selected effects."), false }, + { &ShowAlternateTimingFormatCheckBox, _("Show Alternate Timing Format"), _("Show sequencer timing in seconds and milliseconds."), false }, + { &BellOnRenderCompletion, _("Bell on render completion or error"), _("Play a sound when rendering finishes or errors."), false }, + }; + for (const auto& t : toggles) { + *t.ctrl = new wxCheckBox(this, wxID_ANY, t.label); + (*t.ctrl)->SetValue(t.defaultValue); + sizer->Add(*t.ctrl, 0, wxLEFT | wxTOP, 8); + sizer->Add(MakePreferenceHint(this, t.hint), 0, wxLEFT | wxBOTTOM, 26); + (*t.ctrl)->Bind(wxEVT_CHECKBOX, &EffectsGridSettingsPanel::OnChanged, this); + } - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("wxID_ANY")); - GridSizer1 = new wxFlexGridSizer(0, 3, 0, 0); - GridSizer1->AddGrowableCol(2); - StaticText5 = new wxStaticText(this, wxID_ANY, _("Spacing"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridSizer1->Add(StaticText5, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSpacingChoice = new wxChoice(this, ID_CHOICE1, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE1")); - GridSpacingChoice->Append(_("Extra Small")); - GridSpacingChoice->Append(_("Small")); - GridSpacingChoice->SetSelection( GridSpacingChoice->Append(_("Medium")) ); - GridSpacingChoice->Append(_("Large")); - GridSpacingChoice->Append(_("Extra Large")); - GridSizer1->Add(GridSpacingChoice, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - IconBackgroundsCheckBox = new wxCheckBox(this, ID_CHECKBOX1, _("Effect Backgrounds"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); - IconBackgroundsCheckBox->SetValue(false); - IconBackgroundsCheckBox->SetToolTip(_("Show gif backgrounds for most effects")); - GridSizer1->Add(IconBackgroundsCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - NodeValuesCheckBox = new wxCheckBox(this, ID_CHECKBOX2, _("Node Values"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX2")); - NodeValuesCheckBox->SetValue(false); - GridSizer1->Add(NodeValuesCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GroupEffectIndicator = new wxCheckBox(this, ID_CHECKBOX7, _("Group Effect Indicator"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX7")); - GroupEffectIndicator->SetValue(true); - GroupEffectIndicator->SetToolTip(_("Show bar on model box to indicate presence of an effect")); - GridSizer1->Add(GroupEffectIndicator, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - SnapToTimingCheckBox = new wxCheckBox(this, ID_CHECKBOX3, _("Snap to Timing Marks"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX3")); - SnapToTimingCheckBox->SetValue(false); - GridSizer1->Add(SnapToTimingCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Double Click Mode"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT1")); - GridSizer1->Add(StaticText1, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - DoubleClickChoice = new wxChoice(this, ID_CHOICE2, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE2")); - DoubleClickChoice->Append(_("Edit Text")); - DoubleClickChoice->SetSelection( DoubleClickChoice->Append(_("Play Timing")) ); - GridSizer1->Add(DoubleClickChoice, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - SmallWaveformCheckBox = new wxCheckBox(this, ID_CHECKBOX4, _("Small Waveform"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX4")); - SmallWaveformCheckBox->SetValue(false); - SmallWaveformCheckBox->SetToolTip(_("Reduce the vertical size of the waveform")); - GridSizer1->Add(SmallWaveformCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - TransistionMarksCheckBox = new wxCheckBox(this, ID_CHECKBOX6, _("Display Transition Marks"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX6")); - TransistionMarksCheckBox->SetValue(true); - GridSizer1->Add(TransistionMarksCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - ColorUpdateWarnCheckBox = new wxCheckBox(this, ID_CHECKBOX5, _("Hide Color Update Warning"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX5")); - ColorUpdateWarnCheckBox->SetValue(false); - GridSizer1->Add(ColorUpdateWarnCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - ShowAlternateTimingFormatCheckBox = new wxCheckBox(this, ID_CHECKBOX8, _("Show Alternate Timing Format"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX8")); - ShowAlternateTimingFormatCheckBox->SetValue(false); - ShowAlternateTimingFormatCheckBox->SetToolTip(_("Sequencer timing will be displayed in seconds and milliseconds")); - GridSizer1->Add(ShowAlternateTimingFormatCheckBox, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - BellOnRenderCompletion = new wxCheckBox(this, ID_CHECKBOX9, _("Bell on render completion or error"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX9")); - BellOnRenderCompletion->SetValue(false); - GridSizer1->Add(BellOnRenderCompletion, 1, wxALL|wxEXPAND, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - StaticTextPasteAs = new wxStaticText(this, ID_STATICTEXT_PASTE_AS, _("Paste As"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT_PASTE_AS")); - GridSizer1->Add(StaticTextPasteAs, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - PasteAsChoice = new wxChoice(this, ID_CHOICE_PASTE_AS, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_PASTE_AS")); - PasteAsChoice->SetSelection( PasteAsChoice->Append(_("Relative")) ); - PasteAsChoice->Append(_("Layers")); - PasteAsChoice->SetToolTip(_("Relative: paste effects at the selected position. As Layers: paste preserving layer structure from copied effects.")); - GridSizer1->Add(PasteAsChoice, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - GridSizer1->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - SetSizer(GridSizer1); - GridSizer1->SetSizeHints(this); + SetSizer(sizer); + sizer->SetSizeHints(this); - Connect(ID_CHOICE1, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnGridSpacingChoiceSelect); - Connect(ID_CHECKBOX1, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnIconBackgroundsCheckBoxClick); - Connect(ID_CHECKBOX2, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnNodeValuesCheckBoxClick); - Connect(ID_CHECKBOX7, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnGroupEffectIndicatorClick); - Connect(ID_CHECKBOX3, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnSnapToTimingCheckBoxClick); - Connect(ID_CHOICE2, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnDoubleClickChoiceSelect); - Connect(ID_CHECKBOX4, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnSmallWaveformCheckBoxClick); - Connect(ID_CHECKBOX6, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnTransistionMarksCheckBoxClick); - Connect(ID_CHECKBOX5, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnColorUpdateWarnCheckBoxClick); - Connect(ID_CHECKBOX8, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnShowAlternateTimingFormatCheckBoxClick); - Connect(ID_CHECKBOX9, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnBellOnRenderCompletionClick); - Connect(ID_CHOICE_PASTE_AS, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&EffectsGridSettingsPanel::OnPasteAsChoiceSelect); - //*) + GridSpacingChoice->Bind(wxEVT_CHOICE, &EffectsGridSettingsPanel::OnChanged, this); + DoubleClickChoice->Bind(wxEVT_CHOICE, &EffectsGridSettingsPanel::OnChanged, this); + PasteAsChoice->Bind(wxEVT_CHOICE, &EffectsGridSettingsPanel::OnChanged, this); } EffectsGridSettingsPanel::~EffectsGridSettingsPanel() { - //(*Destroy(EffectsGridSettingsPanel) - //*) } - bool EffectsGridSettingsPanel::TransferDataToWindow() { NodeValuesCheckBox->SetValue(frame->GridNodeValues()); IconBackgroundsCheckBox->SetValue(frame->GridIconBackgrounds()); @@ -218,92 +163,7 @@ bool EffectsGridSettingsPanel::TransferDataFromWindow() { return true; } - -void EffectsGridSettingsPanel::OnIconBackgroundsCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnNodeValuesCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnSnapToTimingCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnSmallWaveformCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnGridSpacingChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnTransistionMarksCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnDoubleClickChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnColorUpdateWarnCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnGroupEffectIndicatorClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnAlternateTimingFormatCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnShowAlternateTimingFormatCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnBellOnRenderCompletionClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void EffectsGridSettingsPanel::OnPasteAsChoiceSelect(wxCommandEvent& event) +void EffectsGridSettingsPanel::OnChanged(wxCommandEvent& event) { if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { TransferDataFromWindow(); diff --git a/src-ui-wx/preferences/EffectsGridSettingsPanel.h b/src-ui-wx/preferences/EffectsGridSettingsPanel.h index 64d818a05f..e45ed4da92 100644 --- a/src-ui-wx/preferences/EffectsGridSettingsPanel.h +++ b/src-ui-wx/preferences/EffectsGridSettingsPanel.h @@ -10,15 +10,13 @@ * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt **************************************************************/ -//(*Headers(EffectsGridSettingsPanel) #include + class wxCheckBox; class wxChoice; -class wxFlexGridSizer; -class wxStaticText; -//*) - +class wxCommandEvent; class xLightsFrame; + class EffectsGridSettingsPanel: public wxPanel { public: @@ -26,65 +24,24 @@ class EffectsGridSettingsPanel: public wxPanel EffectsGridSettingsPanel(wxWindow* parent,xLightsFrame *f,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize); virtual ~EffectsGridSettingsPanel(); - //(*Declarations(EffectsGridSettingsPanel) - wxCheckBox* BellOnRenderCompletion; - wxCheckBox* ColorUpdateWarnCheckBox; - wxCheckBox* GroupEffectIndicator; - wxCheckBox* IconBackgroundsCheckBox; - wxCheckBox* NodeValuesCheckBox; - wxCheckBox* ShowAlternateTimingFormatCheckBox; - wxCheckBox* SmallWaveformCheckBox; - wxCheckBox* SnapToTimingCheckBox; - wxCheckBox* TransistionMarksCheckBox; - wxChoice* DoubleClickChoice; - wxChoice* GridSpacingChoice; - wxChoice* PasteAsChoice; - wxStaticText* StaticText1; - wxStaticText* StaticTextPasteAs; - //*) - virtual bool TransferDataFromWindow() override; virtual bool TransferDataToWindow() override; - protected: - - //(*Identifiers(EffectsGridSettingsPanel) - static const wxWindowID ID_CHOICE1; - static const wxWindowID ID_CHECKBOX1; - static const wxWindowID ID_CHECKBOX2; - static const wxWindowID ID_CHECKBOX7; - static const wxWindowID ID_CHECKBOX3; - static const wxWindowID ID_STATICTEXT1; - static const wxWindowID ID_CHOICE2; - static const wxWindowID ID_CHECKBOX4; - static const wxWindowID ID_CHECKBOX6; - static const wxWindowID ID_CHECKBOX5; - static const wxWindowID ID_CHECKBOX8; - static const wxWindowID ID_CHECKBOX9; - static const wxWindowID ID_STATICTEXT_PASTE_AS; - static const wxWindowID ID_CHOICE_PASTE_AS; - //*) - private: xLightsFrame *frame; - - //(*Handlers(EffectsGridSettingsPanel) - void OnIconBackgroundsCheckBoxClick(wxCommandEvent& event); - void OnNodeValuesCheckBoxClick(wxCommandEvent& event); - void OnSnapToTimingCheckBoxClick(wxCommandEvent& event); - void OnSmallWaveformCheckBoxClick(wxCommandEvent& event); - void OnGridSpacingChoiceSelect(wxCommandEvent& event); - void OnTransistionMarksCheckBoxClick(wxCommandEvent& event); - void OnDoubleClickChoiceSelect(wxCommandEvent& event); - void OnColorUpdateWarnCheckBoxClick(wxCommandEvent& event); - void OnGroupEffectIndicatorClick(wxCommandEvent& event); - void OnPaint(wxPaintEvent& event); - void OnAlternateTimingFormatCheckBoxClick(wxCommandEvent& event); - void OnShowAlternateTimingFormatCheckBoxClick(wxCommandEvent& event); - void OnBellOnRenderCompletionClick(wxCommandEvent& event); - void OnPasteAsChoiceSelect(wxCommandEvent& event); - //*) - - DECLARE_EVENT_TABLE() + wxCheckBox* BellOnRenderCompletion = nullptr; + wxCheckBox* ColorUpdateWarnCheckBox = nullptr; + wxCheckBox* GroupEffectIndicator = nullptr; + wxCheckBox* IconBackgroundsCheckBox = nullptr; + wxCheckBox* NodeValuesCheckBox = nullptr; + wxCheckBox* ShowAlternateTimingFormatCheckBox = nullptr; + wxCheckBox* SmallWaveformCheckBox = nullptr; + wxCheckBox* SnapToTimingCheckBox = nullptr; + wxCheckBox* TransistionMarksCheckBox = nullptr; + wxChoice* DoubleClickChoice = nullptr; + wxChoice* GridSpacingChoice = nullptr; + wxChoice* PasteAsChoice = nullptr; + + void OnChanged(wxCommandEvent& event); }; diff --git a/src-ui-wx/preferences/ViewSettingsPanel.cpp b/src-ui-wx/preferences/ViewSettingsPanel.cpp index 3fd1833d16..4033781d73 100755 --- a/src-ui-wx/preferences/ViewSettingsPanel.cpp +++ b/src-ui-wx/preferences/ViewSettingsPanel.cpp @@ -9,132 +9,99 @@ **************************************************************/ #include "ViewSettingsPanel.h" +#include "PrefPanelUtils.h" -//(*InternalHeaders(ViewSettingsPanel) #include #include -#include #include +#include #include #include -//*) -#include "../graphics/xlGraphicsBase.h" #include "xLightsMain.h" #include -//(*IdInit(ViewSettingsPanel) -const wxWindowID ViewSettingsPanel::ID_CHOICE3 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHOICE4 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHOICE5 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHECKBOX1 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHECKBOX2 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHECKBOX5 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHECKBOX3 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHOICE_TIMELINEZOOMING = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHECKBOX4 = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHECKBOX_ZoomMethod = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHOICE_CROSSHAIRSIZE = wxNewId(); -const wxWindowID ViewSettingsPanel::ID_CHOICE_PALETTE_SIZE = wxNewId(); -//*) - -BEGIN_EVENT_TABLE(ViewSettingsPanel, wxPanel) -//(*EventTable(ViewSettingsPanel) -//*) -END_EVENT_TABLE() - ViewSettingsPanel::ViewSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindowID id, const wxPoint& pos, const wxSize& size) : frame(f) { - //(*Initialize(ViewSettingsPanel) - wxGridBagSizer* GridBagSizer1; - wxStaticText* StaticText1; - wxStaticText* StaticText2; - wxStaticText* StaticText3; - wxStaticText* StaticText4; - wxStaticText* StaticText5; - wxStaticText* StaticText6; - Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); - GridBagSizer1 = new wxGridBagSizer(0, 0); - StaticText1 = new wxStaticText(this, wxID_ANY, _("Effect Icon Size"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText1, wxGBPosition(0, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - ToolIconSizeChoice = new wxChoice(this, ID_CHOICE3, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE3")); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* grid = new wxFlexGridSizer(0, 2, 0, 0); + grid->AddGrowableCol(1); + + auto addChoiceRow = [&](wxChoice*& choice, const wxString& label, const wxString& hint) { + grid->Add(new wxStaticText(this, wxID_ANY, label), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + choice = new wxChoice(this, wxID_ANY); + grid->Add(choice, 0, wxALL, 5); + grid->Add(0, 0); + grid->Add(MakePreferenceHint(this, hint), 0, wxLEFT | wxBOTTOM, 5); + }; + + addChoiceRow(ToolIconSizeChoice, _("Effect Icon Size"), _("Size of the effect icons in the sequencer toolbar.")); ToolIconSizeChoice->Append(_("Small")); - ToolIconSizeChoice->SetSelection( ToolIconSizeChoice->Append(_("Medium")) ); + ToolIconSizeChoice->SetSelection(ToolIconSizeChoice->Append(_("Medium"))); ToolIconSizeChoice->Append(_("Large")); ToolIconSizeChoice->Append(_("Extra Large")); - GridBagSizer1->Add(ToolIconSizeChoice, wxGBPosition(0, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText4 = new wxStaticText(this, wxID_ANY, _("Model Handle Size"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText4, wxGBPosition(1, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - ModelHandleSizeChoice = new wxChoice(this, ID_CHOICE4, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE4")); - ModelHandleSizeChoice->SetSelection( ModelHandleSizeChoice->Append(_("Normal")) ); + + addChoiceRow(ModelHandleSizeChoice, _("Model Handle Size"), _("Size of the drag handles on models in the layout preview.")); + ModelHandleSizeChoice->SetSelection(ModelHandleSizeChoice->Append(_("Normal"))); ModelHandleSizeChoice->Append(_("Large")); ModelHandleSizeChoice->Append(_("Extra Large")); ModelHandleSizeChoice->Append(_("Small")); - GridBagSizer1->Add(ModelHandleSizeChoice, wxGBPosition(1, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText5 = new wxStaticText(this, wxID_ANY, _("Effect Assist Window"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText5, wxGBPosition(2, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - EffectAssistChoice = new wxChoice(this, ID_CHOICE5, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE5")); + + addChoiceRow(EffectAssistChoice, _("Effect Assist Window"), _("When the Effect Assist panel is shown (always on/off, or auto for effects that use it).")); EffectAssistChoice->Append(_("Always On")); EffectAssistChoice->Append(_("Always Off")); - EffectAssistChoice->SetSelection( EffectAssistChoice->Append(_("Auto Toggle")) ); - GridBagSizer1->Add(EffectAssistChoice, wxGBPosition(2, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - PlayControlsCheckBox = new wxCheckBox(this, ID_CHECKBOX1, _("Show Play Controls on Preview"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); - PlayControlsCheckBox->SetValue(true); - GridBagSizer1->Add(PlayControlsCheckBox, wxGBPosition(3, 0), wxGBSpan(1, 2), wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - HousePreviewCheckBox = new wxCheckBox(this, ID_CHECKBOX2, _("Auto Show House Preview"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX2")); - HousePreviewCheckBox->SetValue(true); - GridBagSizer1->Add(HousePreviewCheckBox, wxGBPosition(4, 0), wxGBSpan(1, 2), wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - CheckBox_DisableKeyAcceleration = new wxCheckBox(this, ID_CHECKBOX5, _("Disable key acceleration when held down"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX5")); - CheckBox_DisableKeyAcceleration->SetValue(false); - GridBagSizer1->Add(CheckBox_DisableKeyAcceleration, wxGBPosition(9, 0), wxDefaultSpan, wxALL|wxEXPAND, 5); - CheckBox_BaseShowFolder = new wxCheckBox(this, ID_CHECKBOX3, _("Enable Base Show Folder Settings"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX3")); - CheckBox_BaseShowFolder->SetValue(false); - GridBagSizer1->Add(CheckBox_BaseShowFolder, wxGBPosition(5, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText6 = new wxStaticText(this, wxID_ANY, _("Timeline Zooming"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText6, wxGBPosition(6, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - Choice_TimelineZooming = new wxChoice(this, ID_CHOICE_TIMELINEZOOMING, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_TIMELINEZOOMING")); - Choice_TimelineZooming->SetSelection( Choice_TimelineZooming->Append(_("Play Marker Position")) ); + EffectAssistChoice->SetSelection(EffectAssistChoice->Append(_("Auto Toggle"))); + + addChoiceRow(Choice_TimelineZooming, _("Timeline Zooming"), _("Where the timeline zooms toward — the play marker or the mouse position.")); + Choice_TimelineZooming->SetSelection(Choice_TimelineZooming->Append(_("Play Marker Position"))); Choice_TimelineZooming->Append(_("Mouse Marker Position")); - GridBagSizer1->Add(Choice_TimelineZooming, wxGBPosition(6, 1), wxDefaultSpan, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - CheckBox_PresetPreview = new wxCheckBox(this, ID_CHECKBOX4, _("Hide Preset Previews"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX4")); - CheckBox_PresetPreview->SetValue(false); - GridBagSizer1->Add(CheckBox_PresetPreview, wxGBPosition(7, 0), wxGBSpan(1, 2), wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - CheckBox_ZoomMethod = new wxCheckBox(this, ID_CHECKBOX_ZoomMethod, _("Zoom To Cursor"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX_ZoomMethod")); - CheckBox_ZoomMethod->SetValue(true); - GridBagSizer1->Add(CheckBox_ZoomMethod, wxGBPosition(8, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText2 = new wxStaticText(this, wxID_ANY, _("Group Center Crosshair Size"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText2, wxGBPosition(10, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - CrosshairSizeChoice = new wxChoice(this, ID_CHOICE_CROSSHAIRSIZE, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_CROSSHAIRSIZE")); + + addChoiceRow(CrosshairSizeChoice, _("Group Center Crosshair Size"), _("Size of the crosshair marking a group's centre in the layout.")); CrosshairSizeChoice->Append(_("Large")); - CrosshairSizeChoice->SetSelection( CrosshairSizeChoice->Append(_("Normal")) ); + CrosshairSizeChoice->SetSelection(CrosshairSizeChoice->Append(_("Normal"))); CrosshairSizeChoice->Append(_("Small")); CrosshairSizeChoice->Append(_("Tiny")); CrosshairSizeChoice->Append(_("None")); - CrosshairSizeChoice->SetToolTip(_("Control the size of the crosshair for group centering")); - GridBagSizer1->Add(CrosshairSizeChoice, wxGBPosition(10, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - StaticText3 = new wxStaticText(this, wxID_ANY, _("Color Palette Size"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); - GridBagSizer1->Add(StaticText3, wxGBPosition(11, 0), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - Choice_PaletteSize = new wxChoice(this, ID_CHOICE_PALETTE_SIZE, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_PALETTE_SIZE")); - Choice_PaletteSize->SetSelection( Choice_PaletteSize->Append(_("Normal")) ); + + addChoiceRow(Choice_PaletteSize, _("Color Palette Size"), _("Size of the colour swatches in the Colors panel.")); + Choice_PaletteSize->SetSelection(Choice_PaletteSize->Append(_("Normal"))); Choice_PaletteSize->Append(_("Large")); - GridBagSizer1->Add(Choice_PaletteSize, wxGBPosition(11, 1), wxDefaultSpan, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); - SetSizer(GridBagSizer1); - GridBagSizer1->SetSizeHints(this); - Connect(ID_CHOICE3, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&ViewSettingsPanel::OnToolIconSizeChoiceSelect); - Connect(ID_CHOICE4, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&ViewSettingsPanel::OnModelHandleSizeChoiceSelect); - Connect(ID_CHOICE5, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&ViewSettingsPanel::OnEffectAssistChoiceSelect); - Connect(ID_CHECKBOX1, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&ViewSettingsPanel::OnPlayControlsCheckBoxClick); - Connect(ID_CHECKBOX2, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&ViewSettingsPanel::OnHousePreviewCheckBoxClick); - Connect(ID_CHECKBOX3, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&ViewSettingsPanel::OnCheckBox_BaseShowFolderClick); - Connect(ID_CHOICE_TIMELINEZOOMING, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&ViewSettingsPanel::OnChoice_TimelineZoomingSelect); - Connect(ID_CHECKBOX4, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&ViewSettingsPanel::OnPresetPreviewCheckBoxClick); - Connect(ID_CHECKBOX_ZoomMethod, wxEVT_COMMAND_CHECKBOX_CLICKED, (wxObjectEventFunction)&ViewSettingsPanel::OnCheckBox_ZoomMethodClick); - Connect(ID_CHOICE_CROSSHAIRSIZE, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&ViewSettingsPanel::OnCrosshairSizeChoiceSelect); - Connect(ID_CHOICE_PALETTE_SIZE, wxEVT_COMMAND_CHOICE_SELECTED, (wxObjectEventFunction)&ViewSettingsPanel::OnChoice_PaletteSizeSelect); - //*) + sizer->Add(grid, 0, wxEXPAND | wxALL, 5); + + struct Toggle { + wxCheckBox** ctrl; + wxString label; + wxString hint; + bool defaultValue; + }; + const Toggle toggles[] = { + { &PlayControlsCheckBox, _("Show Play Controls on Preview"), _("Overlay play/pause controls on the preview panels."), true }, + { &HousePreviewCheckBox, _("Auto Show House Preview"), _("Automatically open the House Preview when a sequence loads."), true }, + { &CheckBox_BaseShowFolder, _("Enable Base Show Folder Settings"), _("Allow settings to be inherited from a shared base show folder."), false }, + { &CheckBox_PresetPreview, _("Hide Preset Previews"), _("Don't render thumbnail previews in the preset panel."), false }, + { &CheckBox_ZoomMethod, _("Zoom To Cursor"), _("Zoom the sequencer toward the mouse cursor instead of the play position."), true }, + { &CheckBox_DisableKeyAcceleration, _("Disable key acceleration when held down"), _("Stop key repeat from accelerating when a key is held down."), false }, + }; + for (const auto& t : toggles) { + *t.ctrl = new wxCheckBox(this, wxID_ANY, t.label); + (*t.ctrl)->SetValue(t.defaultValue); + sizer->Add(*t.ctrl, 0, wxLEFT | wxTOP, 8); + sizer->Add(MakePreferenceHint(this, t.hint), 0, wxLEFT | wxBOTTOM, 26); + (*t.ctrl)->Bind(wxEVT_CHECKBOX, &ViewSettingsPanel::OnChanged, this); + } + + SetSizer(sizer); + sizer->SetSizeHints(this); + + for (wxChoice* c : { ToolIconSizeChoice, ModelHandleSizeChoice, EffectAssistChoice, + Choice_TimelineZooming, CrosshairSizeChoice, Choice_PaletteSize }) { + c->Bind(wxEVT_CHOICE, &ViewSettingsPanel::OnChanged, this); + } #ifdef _MSC_VER MSWDisableComposited(); @@ -143,8 +110,6 @@ ViewSettingsPanel::ViewSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWindow ViewSettingsPanel::~ViewSettingsPanel() { - //(*Destroy(ViewSettingsPanel) - //*) } bool ViewSettingsPanel::TransferDataToWindow() @@ -179,6 +144,7 @@ bool ViewSettingsPanel::TransferDataToWindow() Choice_TimelineZooming->SetSelection(frame->GetTimelineZooming() & 1); CheckBox_PresetPreview->SetValue(frame->HidePresetPreview()); CheckBox_DisableKeyAcceleration->SetValue(frame->IsDisableKeyAcceleration()); + CheckBox_ZoomMethod->SetValue(frame->ZoomMethodToCursor()); Choice_PaletteSize->SetStringSelection(frame->GetPaletteSizeString()); return true; @@ -215,98 +181,7 @@ bool ViewSettingsPanel::TransferDataFromWindow() return true; } -void ViewSettingsPanel::OnToolIconSizeChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnHousePreviewCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnPlayControlsCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnPresetPreviewCheckBoxClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnEffectAssistChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnModelHandleSizeChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnOpenGLRenderOrderChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnOpenGLVersionChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnCheckBox_BaseShowFolderClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnChoice_TimelineZoomingSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnCheckBox_ZoomMethodClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnCheckBox_DisableKeyAccelerationClick(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnCrosshairSizeChoiceSelect(wxCommandEvent& event) -{ - if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { - TransferDataFromWindow(); - } -} - -void ViewSettingsPanel::OnChoice_PaletteSizeSelect(wxCommandEvent& event) +void ViewSettingsPanel::OnChanged(wxCommandEvent& event) { if (wxPreferencesEditor::ShouldApplyChangesImmediately()) { TransferDataFromWindow(); diff --git a/src-ui-wx/preferences/ViewSettingsPanel.h b/src-ui-wx/preferences/ViewSettingsPanel.h index 45a6138fb6..fea057c91c 100755 --- a/src-ui-wx/preferences/ViewSettingsPanel.h +++ b/src-ui-wx/preferences/ViewSettingsPanel.h @@ -10,15 +10,13 @@ * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt **************************************************************/ -//(*Headers(ViewSettingsPanel) #include + class wxCheckBox; class wxChoice; -class wxGridBagSizer; -class wxStaticText; -//*) - +class wxCommandEvent; class xLightsFrame; + class ViewSettingsPanel: public wxPanel { public: @@ -26,60 +24,24 @@ class ViewSettingsPanel: public wxPanel ViewSettingsPanel(wxWindow* parent, xLightsFrame *f, wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize); virtual ~ViewSettingsPanel(); - //(*Declarations(ViewSettingsPanel) - wxCheckBox* CheckBox_BaseShowFolder; - wxCheckBox* CheckBox_DisableKeyAcceleration; - wxCheckBox* CheckBox_PresetPreview; - wxCheckBox* CheckBox_ZoomMethod; - wxCheckBox* HousePreviewCheckBox; - wxCheckBox* PlayControlsCheckBox; - wxChoice* Choice_PaletteSize; - wxChoice* Choice_TimelineZooming; - wxChoice* CrosshairSizeChoice; - wxChoice* EffectAssistChoice; - wxChoice* ModelHandleSizeChoice; - wxChoice* ToolIconSizeChoice; - //*) - virtual bool TransferDataFromWindow() override; virtual bool TransferDataToWindow() override; - protected: - - //(*Identifiers(ViewSettingsPanel) - static const wxWindowID ID_CHOICE3; - static const wxWindowID ID_CHOICE4; - static const wxWindowID ID_CHOICE5; - static const wxWindowID ID_CHECKBOX1; - static const wxWindowID ID_CHECKBOX2; - static const wxWindowID ID_CHECKBOX5; - static const wxWindowID ID_CHECKBOX3; - static const wxWindowID ID_CHOICE_TIMELINEZOOMING; - static const wxWindowID ID_CHECKBOX4; - static const wxWindowID ID_CHECKBOX_ZoomMethod; - static const wxWindowID ID_CHOICE_CROSSHAIRSIZE; - static const wxWindowID ID_CHOICE_PALETTE_SIZE; - //*) - private: xLightsFrame *frame; - //(*Handlers(ViewSettingsPanel) - void OnToolIconSizeChoiceSelect(wxCommandEvent& event); - void OnHousePreviewCheckBoxClick(wxCommandEvent& event); - void OnPlayControlsCheckBoxClick(wxCommandEvent& event); - void OnEffectAssistChoiceSelect(wxCommandEvent& event); - void OnModelHandleSizeChoiceSelect(wxCommandEvent& event); - void OnOpenGLRenderOrderChoiceSelect(wxCommandEvent& event); - void OnOpenGLVersionChoiceSelect(wxCommandEvent& event); - void OnCheckBox_BaseShowFolderClick(wxCommandEvent& event); - void OnChoice_TimelineZoomingSelect(wxCommandEvent& event); - void OnPresetPreviewCheckBoxClick(wxCommandEvent& event); - void OnCheckBox_ZoomMethodClick(wxCommandEvent& event); - void OnCheckBox_DisableKeyAccelerationClick(wxCommandEvent& event); - void OnCrosshairSizeChoiceSelect(wxCommandEvent& event); - void OnChoice_PaletteSizeSelect(wxCommandEvent& event); - //*) - - DECLARE_EVENT_TABLE() + wxCheckBox* CheckBox_BaseShowFolder = nullptr; + wxCheckBox* CheckBox_DisableKeyAcceleration = nullptr; + wxCheckBox* CheckBox_PresetPreview = nullptr; + wxCheckBox* CheckBox_ZoomMethod = nullptr; + wxCheckBox* HousePreviewCheckBox = nullptr; + wxCheckBox* PlayControlsCheckBox = nullptr; + wxChoice* Choice_PaletteSize = nullptr; + wxChoice* Choice_TimelineZooming = nullptr; + wxChoice* CrosshairSizeChoice = nullptr; + wxChoice* EffectAssistChoice = nullptr; + wxChoice* ModelHandleSizeChoice = nullptr; + wxChoice* ToolIconSizeChoice = nullptr; + + void OnChanged(wxCommandEvent& event); }; From 1cb9f6b80abd4efa4a95a361591734f96d01ed96 Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 15:03:02 -0400 Subject: [PATCH 21/24] Preferences: bold section headers on the Color Manager page Replace the Timing Tracks / Effect Grid / Layout Tab wxStaticBox captions with bold text headers to match the rest of the modernized Preferences pages; colour button logic is unchanged. Co-Authored-By: Claude Opus 4.8 --- .../preferences/ColorManagerSettingsPanel.cpp | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp b/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp index efa1788de2..5df6e9e5e6 100644 --- a/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp +++ b/src-ui-wx/preferences/ColorManagerSettingsPanel.cpp @@ -9,6 +9,7 @@ **************************************************************/ #include "ColorManagerSettingsPanel.h" +#include "PrefPanelUtils.h" #include "shared/utils/wxUtilities.h" #include "utils/ExternalHooks.h" @@ -46,9 +47,6 @@ ColorManagerSettingsPanel::ColorManagerSettingsPanel(wxWindow* parent, xLightsFr wxFlexGridSizer* FlexGridSizer2; wxFlexGridSizer* FlexGridSizer3; wxFlexGridSizer* FlexGridSizer7; - wxStaticBoxSizer* StaticBoxSizer1; - wxStaticBoxSizer* StaticBoxSizer2; - wxStaticBoxSizer* StaticBoxSizer3; Create(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("id")); FlexGridSizer1 = new wxFlexGridSizer(0, 1, 0, 0); @@ -58,18 +56,21 @@ ColorManagerSettingsPanel::ColorManagerSettingsPanel(wxWindow* parent, xLightsFr FlexGridSizer3->AddGrowableCol(1); FlexGridSizer3->AddGrowableCol(2); FlexGridSizer3->AddGrowableRow(0); - StaticBoxSizer1 = new wxStaticBoxSizer(wxVERTICAL, this, _("Timing Tracks")); + auto* colTiming = new wxBoxSizer(wxVERTICAL); + colTiming->Add(MakePreferenceSectionHeader(this, _("Timing Tracks")), 0, wxLEFT | wxTOP | wxBOTTOM, 4); Sizer_Timing_Tracks = new wxFlexGridSizer(0, 2, 0, 0); - StaticBoxSizer1->Add(Sizer_Timing_Tracks, 1, wxALL|wxEXPAND, 5); - FlexGridSizer3->Add(StaticBoxSizer1, 1, wxALL|wxEXPAND, 5); - StaticBoxSizer2 = new wxStaticBoxSizer(wxVERTICAL, this, _("Effect Grid")); + colTiming->Add(Sizer_Timing_Tracks, 1, wxALL|wxEXPAND, 5); + FlexGridSizer3->Add(colTiming, 1, wxALL|wxEXPAND, 5); + auto* colEffect = new wxBoxSizer(wxVERTICAL); + colEffect->Add(MakePreferenceSectionHeader(this, _("Effect Grid")), 0, wxLEFT | wxTOP | wxBOTTOM, 4); Sizer_Effect_Grid = new wxFlexGridSizer(0, 4, 0, 0); - StaticBoxSizer2->Add(Sizer_Effect_Grid, 1, wxALL|wxEXPAND, 5); - FlexGridSizer3->Add(StaticBoxSizer2, 1, wxALL|wxEXPAND, 5); - StaticBoxSizer3 = new wxStaticBoxSizer(wxVERTICAL, this, _("Layout Tab")); + colEffect->Add(Sizer_Effect_Grid, 1, wxALL|wxEXPAND, 5); + FlexGridSizer3->Add(colEffect, 1, wxALL|wxEXPAND, 5); + auto* colLayout = new wxBoxSizer(wxVERTICAL); + colLayout->Add(MakePreferenceSectionHeader(this, _("Layout Tab")), 0, wxLEFT | wxTOP | wxBOTTOM, 4); Sizer_Layout_Tab = new wxFlexGridSizer(0, 2, 0, 0); - StaticBoxSizer3->Add(Sizer_Layout_Tab, 1, wxALL|wxEXPAND, 5); - FlexGridSizer3->Add(StaticBoxSizer3, 1, wxALL|wxEXPAND, 5); + colLayout->Add(Sizer_Layout_Tab, 1, wxALL|wxEXPAND, 5); + FlexGridSizer3->Add(colLayout, 1, wxALL|wxEXPAND, 5); FlexGridSizer1->Add(FlexGridSizer3, 1, wxALL|wxEXPAND, 5); FlexGridSizer2 = new wxFlexGridSizer(0, 2, 0, 0); CheckBox_SuppressDarkMode = new wxCheckBox(this, ID_CHECKBOX1, _("Suppress Dark Mode"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); From 1b13c656d4dfcf91c05a23cc035e5fe0b50ef5fb Mon Sep 17 00:00:00 2001 From: heffneil Date: Wed, 8 Jul 2026 16:40:29 -0400 Subject: [PATCH 22/24] Preferences: fix KeyBindings.h include path for CMake (Linux/Windows) builds macOS's synchronized groups resolved the bare "KeyBindings.h"; the CMake Linux/Windows builds need the include-root-relative path used elsewhere (app-shell/KeyBindings.h), matching MainSequencer.h and EffectWheelDialog.h. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp index 8da3a029f0..49d39dfa3c 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp @@ -31,7 +31,7 @@ #include #include "KeyBindingsSettingsPanel.h" -#include "KeyBindings.h" +#include "app-shell/KeyBindings.h" #include "effects/EffectManager.h" #include "effects/RenderableEffect.h" #include "xLightsMain.h" From 2b663b501d8b0471fc53cf22e54d50e363056884 Mon Sep 17 00:00:00 2001 From: heffneil Date: Thu, 9 Jul 2026 12:00:55 -0400 Subject: [PATCH 23/24] Preferences: remove the Cancel button (changes apply live, nothing to cancel) With immediate-apply (macOS), settings take effect as they're changed, so a Cancel button that can't actually revert is misleading. Use a single OK/close button and run the same finalize (final transfer + toolbar labels, row-height resize, low-def reload) whether the dialog is closed via the button or its window close box. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/preferences/xLightsPreferences.cpp | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index 2e59cf7a03..afaeb8e891 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -96,7 +96,9 @@ class xlPreferencesListDialog : public wxDialog { } topSizer->Add(book, 1, wxEXPAND | wxALL, 5); - topSizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); + // No Cancel: changes apply live (immediate-apply on macOS) so there is + // nothing to cancel; the single button just closes the window. + topSizer->Add(CreateStdDialogButtonSizer(wxOK), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -182,11 +184,12 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) } auto* dlg = new xlPreferencesListDialog(this, pages); dlg->SetName("xlPreferencesDialog"); - // OK applies (panels save via validators in TransferDataFromWindow) and runs - // the post-change work that used to sit after ShowModal(); Cancel/close just - // dismiss. Everything captured by value/`this` so it's valid when fired. - dlg->Bind(wxEVT_BUTTON, [this, dlg, ld](wxCommandEvent&) { - if (!dlg->Validate() || !dlg->TransferDataFromWindow()) return; + // Changes already applied live (immediate-apply); on close just do a final + // transfer and the post-change work that used to sit after ShowModal(). + // Run it whether the window is closed via the button or its close box so + // nothing (toolbar labels, row-height resize, low-def reload) is skipped. + auto finalize = [this, dlg, ld]() -> bool { + if (!dlg->Validate() || !dlg->TransferDataFromWindow()) return false; if (mRenderOnSave) { MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Render All and Save")); MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVEAS, _("Render All and Save As")); @@ -201,10 +204,10 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) _outputModelManager.AddASAPWork(OutputModelManager::WORK_RELOAD_ALLMODELS, "Preferences Change"); _outputModelManager.AddASAPWork(OutputModelManager::WORK_MODELS_CHANGE_REQUIRING_RERENDER, "Preferences Change"); } - dlg->Destroy(); - }, wxID_OK); - dlg->Bind(wxEVT_BUTTON, [dlg](wxCommandEvent&) { dlg->Destroy(); }, wxID_CANCEL); - dlg->Bind(wxEVT_CLOSE_WINDOW, [dlg](wxCloseEvent&) { dlg->Destroy(); }); + return true; + }; + dlg->Bind(wxEVT_BUTTON, [dlg, finalize](wxCommandEvent&) { if (finalize()) dlg->Destroy(); }, wxID_OK); + dlg->Bind(wxEVT_CLOSE_WINDOW, [dlg, finalize](wxCloseEvent&) { finalize(); dlg->Destroy(); }); dlg->Show(); dlg->Raise(); } From 955512af99e3f1f887b91921df7b32d1a333867b Mon Sep 17 00:00:00 2001 From: heffneil Date: Thu, 9 Jul 2026 12:19:12 -0400 Subject: [PATCH 24/24] Preferences: address Copilot review - Restore OK/Cancel (removing Cancel broke discard-changes on Windows/Linux batch-apply; close/Cancel dismiss without applying). - Key Bindings: filter by Category index not localized label (fixes empty list in non-English locales); make EncodeScope static + take wxString; wrap column headers and '(unassigned)' in _(); drop unused local. - Video page: don't mutate the live renderer setting in batch mode; hide the 'Hardware Video Renderer' label with its (platform-hidden) choice. - Preferences dialog: skip an IsBeingDeleted() instance when reusing by name. - Fix header-comment and 'dont' typos. Co-Authored-By: Claude Opus 4.8 --- src-ui-wx/app-shell/KeyBindings.cpp | 4 +-- src-ui-wx/preferences/BackupSettingsPanel.cpp | 2 +- src-ui-wx/preferences/BackupSettingsPanel.h | 2 +- .../CheckSequenceSettingsPanel.cpp | 2 +- .../preferences/CheckSequenceSettingsPanel.h | 2 +- .../preferences/EffectsGridSettingsPanel.cpp | 2 +- .../preferences/EffectsGridSettingsPanel.h | 2 +- .../preferences/KeyBindingsSettingsPanel.cpp | 31 +++++++++--------- .../preferences/KeyBindingsSettingsPanel.h | 2 +- src-ui-wx/preferences/OtherSettingsPanel.cpp | 2 +- src-ui-wx/preferences/OtherSettingsPanel.h | 2 +- src-ui-wx/preferences/OutputSettingsPanel.cpp | 2 +- src-ui-wx/preferences/OutputSettingsPanel.h | 2 +- src-ui-wx/preferences/VideoSettingsPanel.cpp | 8 +++-- src-ui-wx/preferences/ViewSettingsPanel.cpp | 2 +- src-ui-wx/preferences/ViewSettingsPanel.h | 2 +- src-ui-wx/preferences/xLightsPreferences.cpp | 32 +++++++++++-------- 17 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src-ui-wx/app-shell/KeyBindings.cpp b/src-ui-wx/app-shell/KeyBindings.cpp index f9913c1c50..0cbca534a5 100644 --- a/src-ui-wx/app-shell/KeyBindings.cpp +++ b/src-ui-wx/app-shell/KeyBindings.cpp @@ -120,7 +120,7 @@ static std::vector> KeyBindingTypes = { "CANCEL_RENDER", KBSCOPE::Sequence }, { "TOGGLE_RENDER", KBSCOPE::Sequence }, { "PRESETS_TOGGLE", KBSCOPE::Sequence }, - { "FOCUS_SEQUENCER", KBSCOPE::All }, // This forces focus to the sequencer for situations where keys dont seem to work. It must be mapped to function key + { "FOCUS_SEQUENCER", KBSCOPE::All }, // This forces focus to the sequencer for situations where keys don't seem to work. It must be mapped to function key { "VALUECURVES_TOGGLE", KBSCOPE::Sequence }, { "COLOR_DROPPER_TOGGLE", KBSCOPE::Sequence }, { "AUDIO_FULL_SPEED", KBSCOPE::Sequence }, @@ -253,7 +253,7 @@ static std::vector> keyBindingTips = { { "CANCEL_RENDER", "Cancel any rendering currently in progress." }, { "TOGGLE_RENDER", "Turn automatic background rendering on or off." }, { "PRESETS_TOGGLE", "Show or hide the presets panel." }, - { "FOCUS_SEQUENCER", "Force keyboard focus back to the effects grid when shortcuts stop responding (map this to a function key)." }, // This forces focus to the sequencer for situations where keys dont seem to work. It must be mapped to function key + { "FOCUS_SEQUENCER", "Force keyboard focus back to the effects grid when shortcuts stop responding (map this to a function key)." }, // This forces focus to the sequencer for situations where keys don't seem to work. It must be mapped to function key { "VALUECURVES_TOGGLE", "Show or hide the Value Curves panel you drag value curves from." }, { "COLOR_DROPPER_TOGGLE", "Show or hide the Color Dropper panel for picking colors." }, { "AUDIO_FULL_SPEED", "Play audio back at normal (1x) speed." }, diff --git a/src-ui-wx/preferences/BackupSettingsPanel.cpp b/src-ui-wx/preferences/BackupSettingsPanel.cpp index 54000040f1..e2ccdc57e0 100644 --- a/src-ui-wx/preferences/BackupSettingsPanel.cpp +++ b/src-ui-wx/preferences/BackupSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/BackupSettingsPanel.h b/src-ui-wx/preferences/BackupSettingsPanel.h index 6648278716..349470908f 100644 --- a/src-ui-wx/preferences/BackupSettingsPanel.h +++ b/src-ui-wx/preferences/BackupSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp b/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp index 8daed26200..dc9958a0b1 100644 --- a/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp +++ b/src-ui-wx/preferences/CheckSequenceSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/CheckSequenceSettingsPanel.h b/src-ui-wx/preferences/CheckSequenceSettingsPanel.h index 6934130a78..a8781eaa29 100644 --- a/src-ui-wx/preferences/CheckSequenceSettingsPanel.h +++ b/src-ui-wx/preferences/CheckSequenceSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp b/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp index ba4a0e2c41..484734dc42 100644 --- a/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp +++ b/src-ui-wx/preferences/EffectsGridSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/EffectsGridSettingsPanel.h b/src-ui-wx/preferences/EffectsGridSettingsPanel.h index e45ed4da92..c017054620 100644 --- a/src-ui-wx/preferences/EffectsGridSettingsPanel.h +++ b/src-ui-wx/preferences/EffectsGridSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp index 49d39dfa3c..687b5185cd 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.cpp @@ -1,6 +1,6 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing @@ -204,9 +204,9 @@ KeyBindingsSettingsPanel::KeyBindingsSettingsPanel(wxWindow* parent, xLightsFram topSizer->Add(topRow, 0, wxEXPAND); ListCtrl_Bindings = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 360), wxLC_REPORT | wxLC_SINGLE_SEL); - ListCtrl_Bindings->AppendColumn("Action"); - ListCtrl_Bindings->AppendColumn("Shortcut", wxLIST_FORMAT_CENTRE); - ListCtrl_Bindings->AppendColumn("Details"); + ListCtrl_Bindings->AppendColumn(_("Action")); + ListCtrl_Bindings->AppendColumn(_("Shortcut"), wxLIST_FORMAT_CENTRE); + ListCtrl_Bindings->AppendColumn(_("Details")); topSizer->Add(ListCtrl_Bindings, 1, wxEXPAND | wxALL, 4); auto* btnRow = new wxBoxSizer(wxHORIZONTAL); @@ -297,15 +297,18 @@ KeyBindingsSettingsPanel::~KeyBindingsSettingsPanel() //*) } -wxString KeyBindingsSettingsPanel::CategoryOf(const std::string& type) +// Category dropdown index (0 = All): 1 Effects, 2 Presets, 3 Apply Settings, +// 4 Commands. Compared by index so the filter is locale-independent (the visible +// labels are translated via _()). +int KeyBindingsSettingsPanel::CategoryIndexOf(const std::string& type) { - if (type == "EFFECT") return "Effects / Wheel of Effects"; - if (type == "PRESET") return "Presets"; - if (type == "APPLYSETTING") return "Apply Settings"; - return "Commands"; + if (type == "EFFECT") return 1; + if (type == "PRESET") return 2; + if (type == "APPLYSETTING") return 3; + return 4; } -KBSCOPE EncodeScope(std::string scope) +static KBSCOPE EncodeScope(const wxString& scope) { if (scope == "Controller") return KBSCOPE::Setup; if (scope == "Layout") return KBSCOPE::Layout; @@ -326,8 +329,7 @@ void KeyBindingsSettingsPanel::LoadList() const wxString scopeSel = Choice_Scope->GetStringSelection(); const bool showAll = (scopeSel == "All"); const KBSCOPE scope = EncodeScope(scopeSel); - const wxString categorySel = Choice_Category->GetStringSelection(); - const bool allCategories = categorySel.empty() || categorySel == "All"; + const int categorySel = Choice_Category->GetSelection(); // 0 = All // Collect the visible rows first so the Action column can be shown // alphabetically regardless of the bindings' storage order. @@ -343,7 +345,7 @@ void KeyBindingsSettingsPanel::LoadList() { if (!showAll && !it.InScope(scope)) continue; - if (!allCategories && CategoryOf(it.GetType()) != categorySel) + if (categorySel > 0 && CategoryIndexOf(it.GetType()) != categorySel) continue; const wxString friendly = FriendlyName(it.GetType()); @@ -461,7 +463,7 @@ wxString KeyBindingsSettingsPanel::FriendlyName(const std::string& type) wxString KeyBindingsSettingsPanel::RenderShortcut(const KeyBinding& b) { - if (b.GetKey() == WXK_NONE) return "(unassigned)"; + if (b.GetKey() == WXK_NONE) return _("(unassigned)"); wxString mods; #ifdef __WXOSX__ if (b.RequiresControl()) mods += wxUniChar(0x2318); // Command @@ -576,7 +578,6 @@ void KeyBindingsSettingsPanel::OnButtonAddApplySettingClick(wxCommandEvent& even void KeyBindingsSettingsPanel::OnButtonAddPresetClick(wxCommandEvent& event) { - std::string empty; int id = _keyBindings->AddKey(KeyBinding(false, _(""), _(""), false, false, false, false)); LoadList(); SelectKey(id); diff --git a/src-ui-wx/preferences/KeyBindingsSettingsPanel.h b/src-ui-wx/preferences/KeyBindingsSettingsPanel.h index ede058b4c0..5967b3f418 100644 --- a/src-ui-wx/preferences/KeyBindingsSettingsPanel.h +++ b/src-ui-wx/preferences/KeyBindingsSettingsPanel.h @@ -65,7 +65,7 @@ class KeyBindingsSettingsPanel : public wxPanel // Broad category a binding falls into ("Effects", "Presets", // "Apply Settings" or "Commands"), used by the Category dropdown filter. - static wxString CategoryOf(const std::string& type); + static int CategoryIndexOf(const std::string& type); public: // Public so the popup editor can label a binding with its friendly name. diff --git a/src-ui-wx/preferences/OtherSettingsPanel.cpp b/src-ui-wx/preferences/OtherSettingsPanel.cpp index 2ec3355a7e..39114c1911 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.cpp +++ b/src-ui-wx/preferences/OtherSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/OtherSettingsPanel.h b/src-ui-wx/preferences/OtherSettingsPanel.h index 619172f2a6..02e5e5ecf2 100755 --- a/src-ui-wx/preferences/OtherSettingsPanel.h +++ b/src-ui-wx/preferences/OtherSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/OutputSettingsPanel.cpp b/src-ui-wx/preferences/OutputSettingsPanel.cpp index fb1862f487..06e5c6408a 100644 --- a/src-ui-wx/preferences/OutputSettingsPanel.cpp +++ b/src-ui-wx/preferences/OutputSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/OutputSettingsPanel.h b/src-ui-wx/preferences/OutputSettingsPanel.h index 13bd3336e1..b1ab7e3d2f 100644 --- a/src-ui-wx/preferences/OutputSettingsPanel.h +++ b/src-ui-wx/preferences/OutputSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/VideoSettingsPanel.cpp b/src-ui-wx/preferences/VideoSettingsPanel.cpp index 2771f0a8cc..df181c3e77 100644 --- a/src-ui-wx/preferences/VideoSettingsPanel.cpp +++ b/src-ui-wx/preferences/VideoSettingsPanel.cpp @@ -33,7 +33,8 @@ VideoSettingsPanel::VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind sizer->Add(HardwareVideoDecodingCheckBox, 0, wxALL, 5); auto* renderRow = new wxBoxSizer(wxHORIZONTAL); - renderRow->Add(new wxStaticText(this, wxID_ANY, _("Hardware Video Renderer:")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + auto* renderLabel = new wxStaticText(this, wxID_ANY, _("Hardware Video Renderer:")); + renderRow->Add(renderLabel, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); HardwareVideoRenderChoice = new wxChoice(this, wxID_ANY); HardwareVideoRenderChoice->Append(_("DirectX11")); HardwareVideoRenderChoice->SetSelection(HardwareVideoRenderChoice->Append(_("FFmpeg Auto"))); @@ -68,9 +69,11 @@ VideoSettingsPanel::VideoSettingsPanel(wxWindow* parent, xLightsFrame* f, wxWind // platforms decode without the selectable backend (mirrors the prior panel). #ifdef __LINUX__ HardwareVideoDecodingCheckBox->Hide(); + renderLabel->Hide(); HardwareVideoRenderChoice->Hide(); #endif #ifdef __WXOSX__ + renderLabel->Hide(); HardwareVideoRenderChoice->Hide(); #endif @@ -115,7 +118,8 @@ void VideoSettingsPanel::OnControlChanged(wxCommandEvent& event) { TransferDataFromWindow(); } else { #ifdef __WXMSW__ - frame->SetHardwareVideoRenderer(HardwareVideoRenderChoice->GetSelection()); + // Batch-apply mode: only reflect the enable/disable state; don't mutate + // the live setting until OK (TransferDataFromWindow). HardwareVideoRenderChoice->Enable(HardwareVideoDecodingCheckBox->IsChecked()); #endif } diff --git a/src-ui-wx/preferences/ViewSettingsPanel.cpp b/src-ui-wx/preferences/ViewSettingsPanel.cpp index 4033781d73..6d506c0c9c 100755 --- a/src-ui-wx/preferences/ViewSettingsPanel.cpp +++ b/src-ui-wx/preferences/ViewSettingsPanel.cpp @@ -1,5 +1,5 @@ /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/ViewSettingsPanel.h b/src-ui-wx/preferences/ViewSettingsPanel.h index fea057c91c..671950a469 100755 --- a/src-ui-wx/preferences/ViewSettingsPanel.h +++ b/src-ui-wx/preferences/ViewSettingsPanel.h @@ -1,7 +1,7 @@ #pragma once /*************************************************************** - * This source files comes from the xLights project + * This source file comes from the xLights project * https://www.xlights.org * https://github.com/xLightsSequencer/xLights * See the github commit history for a record of contributing diff --git a/src-ui-wx/preferences/xLightsPreferences.cpp b/src-ui-wx/preferences/xLightsPreferences.cpp index afaeb8e891..ff5ec0fdf7 100644 --- a/src-ui-wx/preferences/xLightsPreferences.cpp +++ b/src-ui-wx/preferences/xLightsPreferences.cpp @@ -96,9 +96,10 @@ class xlPreferencesListDialog : public wxDialog { } topSizer->Add(book, 1, wxEXPAND | wxALL, 5); - // No Cancel: changes apply live (immediate-apply on macOS) so there is - // nothing to cancel; the single button just closes the window. - topSizer->Add(CreateStdDialogButtonSizer(wxOK), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); + // OK applies; Cancel/close dismiss without applying. This matters on + // Windows/Linux where the framework batches changes until OK (macOS + // applies immediately, so Cancel there won't roll back live changes). + topSizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -174,8 +175,11 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) // Modeless so Preferences can stay open while you keep working in xLights. // Reuse an already-open instance rather than stacking a second dialog. + // Reuse an already-open instance rather than stacking a second dialog. + // Skip a window that is mid-destruction (Destroy() is deferred) so we don't + // Show()/Raise() a dying dialog. for (wxWindow* w : wxTopLevelWindows) { - if (w->GetName() == "xlPreferencesDialog") { + if (w->GetName() == "xlPreferencesDialog" && !w->IsBeingDeleted()) { w->Show(); w->Raise(); w->SetFocus(); @@ -184,12 +188,12 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) } auto* dlg = new xlPreferencesListDialog(this, pages); dlg->SetName("xlPreferencesDialog"); - // Changes already applied live (immediate-apply); on close just do a final - // transfer and the post-change work that used to sit after ShowModal(). - // Run it whether the window is closed via the button or its close box so - // nothing (toolbar labels, row-height resize, low-def reload) is skipped. - auto finalize = [this, dlg, ld]() -> bool { - if (!dlg->Validate() || !dlg->TransferDataFromWindow()) return false; + // OK applies (panels write back in TransferDataFromWindow) and runs the + // post-change work that used to sit after ShowModal(). Cancel and the close + // box dismiss without applying, so batched changes are discarded on + // Windows/Linux (on macOS they were already applied immediately). + dlg->Bind(wxEVT_BUTTON, [this, dlg, ld](wxCommandEvent&) { + if (!dlg->Validate() || !dlg->TransferDataFromWindow()) return; if (mRenderOnSave) { MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVE, _("Render All and Save")); MainToolBar->SetToolShortHelp(ID_AUITOOLBAR_SAVEAS, _("Render All and Save As")); @@ -204,10 +208,10 @@ void xLightsFrame::OnMenuItemPreferencesSelected(wxCommandEvent& event) _outputModelManager.AddASAPWork(OutputModelManager::WORK_RELOAD_ALLMODELS, "Preferences Change"); _outputModelManager.AddASAPWork(OutputModelManager::WORK_MODELS_CHANGE_REQUIRING_RERENDER, "Preferences Change"); } - return true; - }; - dlg->Bind(wxEVT_BUTTON, [dlg, finalize](wxCommandEvent&) { if (finalize()) dlg->Destroy(); }, wxID_OK); - dlg->Bind(wxEVT_CLOSE_WINDOW, [dlg, finalize](wxCloseEvent&) { finalize(); dlg->Destroy(); }); + dlg->Destroy(); + }, wxID_OK); + dlg->Bind(wxEVT_BUTTON, [dlg](wxCommandEvent&) { dlg->Destroy(); }, wxID_CANCEL); + dlg->Bind(wxEVT_CLOSE_WINDOW, [dlg](wxCloseEvent&) { dlg->Destroy(); }); dlg->Show(); dlg->Raise(); }