diff --git a/companion/docs/mvc-improvement-plan.md b/companion/docs/mvc-improvement-plan.md new file mode 100644 index 000000000..1a67b8ecf --- /dev/null +++ b/companion/docs/mvc-improvement-plan.md @@ -0,0 +1,199 @@ +# Companion MVC Improvement Plan + +## Context + +Companion dialogs currently use direct widget manipulation with manual `setVisible()` / `setEnabled()` calls scattered through monolithic `update()` methods. The existing Auto* widget system handles bidirectional data binding well, but there is no abstraction for conditional UI state (visibility, enabled, dynamic labels). The hardest panels — TelemetrySensorPanel (~25 rules) and ModulePanel (~40+ rules, 26 mask constants) — are where this hurts most. + +This plan extends the existing Auto* / GenericPanel infrastructure incrementally. Each phase is independently shippable. + +--- + +## Phase 1: Visibility Binding Infrastructure + +**Goal:** Extend `AutoWidget` / `GenericPanel` to handle visibility and enabled state declaratively. + +**What to build** (~200-300 lines): + +Add a `WidgetBinding` utility that connects widget visibility/enabled state to a predicate: + +```cpp +// In GenericPanel or a new small header +void GenericPanel::bindVisible(QWidget *widget, std::function pred); +void GenericPanel::bindEnabled(QWidget *widget, std::function pred); +void GenericPanel::applyBindings(); // evaluate all predicates, apply results +``` + +Usage in a panel constructor: + +```cpp +bindVisible(ui->ratio, [&]{ return sensor.getMask() & SENSOR_HAS_RATIO; }); +bindVisible(ui->ratioLabel, [&]{ return sensor.getMask() & SENSOR_HAS_RATIO + && sensor.unit != UNIT_RPMS; }); +bindVisible(ui->bladesLabel, [&]{ return sensor.getMask() & SENSOR_HAS_RATIO + && sensor.unit == UNIT_RPMS; }); +bindEnabled(ui->autoOffset, [&]{ return (sensor.getMask() & SENSOR_HAS_RATIO) + && sensor.unit != UNIT_RPMS; }); +``` + +Then `update()` shrinks to: + +```cpp +void TelemetrySensorPanel::update() { + lock = true; + updateAutoWidgets(); // existing - syncs data to widgets + applyBindings(); // new - evaluates all visibility/enabled predicates + lock = false; +} +``` + +**Why this first:** Small, self-contained, non-breaking. Existing panels don't need to change — new code opts in. The predicate lambdas capture the same business rules that already exist in `getMask()`, but declared once at setup time instead of scattered through `update()`. + +--- + +## Phase 2: Pilot on Telemetry Panel + +**Goal:** Prove the pattern on a real complex panel. + +Telemetry is the right first target because: +- `getMask()` already encapsulates the business rules cleanly +- ~25 visibility rules, complex enough to be meaningful, small enough to finish +- Self-contained — doesn't cascade into other panels + +**Steps:** +1. Convert `TelemetrySensorPanel::update()` to use `bindVisible` / `bindEnabled` +2. Move data population to Auto* widgets where not already done +3. Delete the manual `setVisible()` / `setEnabled()` calls from `update()` +4. Result: `update()` body becomes essentially `updateAutoWidgets(); applyBindings();` + +**Validation:** Behavior should be identical. Test every sensor type x formula x unit combination. + +--- + +## Phase 3: Tackle ModulePanel + +**Goal:** Apply the pattern to the hardest case. + +ModulePanel has 26 mask constants and a 400-line `update()`. This is where the real payoff is — but also where edge cases in the binding system will surface: + +- **Cascading dependencies** — protocol change triggers subtype options which trigger failsafe visibility +- **Dynamic widget creation** — receiver rows created/destroyed based on protocol +- **Cross-panel effects** — module settings affect other panels + +Expect to extend the binding infrastructure. Likely additions: +- `bindText(QLabel*, std::function)` for dynamic labels +- `bindModel(QComboBox*, std::function)` for swapping combo contents +- Possibly group bindings that share an invalidation trigger + +--- + +## Phase 4: Virtualized Grid Panels (model-arena Support) + +**Goal:** Replace fixed-count widget grids with virtualized views so panels scale with per-radio object limits. + +### The Problem + +The `model-arena` branch introduces dynamic array limits that vary by radio type (e.g., H7 radios may support more logical switches, custom functions, etc.). Several panels currently allocate all widgets upfront in static grids: + +| Panel | Widgets/Row | Current Max | Total Widgets | +|-------|------------|-------------|---------------| +| LogicalSwitches | 12 | 64 | 768 | +| CustomFunctions | ~10 | 64 | ~640 | +| Telemetry sensors | ~15 | 60 | ~900 | + +If limits double on higher-end radios, widget counts double with them. Mixes and Inputs already use `QListWidget` and won't have this problem. + +### Current Architecture (LogicalSwitches) + +``` +CPN_MAX_LOGICAL_SWITCHES = 64 (compile-time constant) + -> fixed-size C arrays in header: cbFunction[64], dsbValue[64], ... + -> constructor loop creates all 768 widgets + -> updateLine(i) shows/hides widgets per row based on function type + -> no scrolling, no virtualization +``` + +### Target Architecture + +Replace the static grid with `QTableView` + a `QAbstractTableModel` adapter: + +``` +Model: LogicalSwitchTableModel (wraps LogicalSwitchData array) + -> rowCount() returns actual count from firmware capability + -> data()/setData() for each column (function, v1, v2, AND, duration, delay, persist) + -> dataChanged() signals drive view updates + +View: QTableView with custom delegates + -> only instantiates widgets for visible rows (virtualized) + -> delegates handle the conditional widget type per cell: + column 2 shows combobox OR spinbox depending on function family + +Binding: visibility masks from updateLine() become delegate logic + -> delegate's createEditor() checks function family + -> no 768 pre-allocated widgets, just ~12 × visible rows +``` + +### Why This Phase Matters Now + +1. **model-arena makes limits radio-dependent** — `getCapability(LogicalSwitches)` will return different values per board. Fixed C arrays sized to the global max waste memory for smaller radios and may not be large enough for larger ones. +2. **Widget count becomes the load-time bottleneck** — 768 widgets is already slow; 1500+ is unacceptable. Virtualization is the only real fix. +3. **The binding infrastructure from Phase 1 applies** — delegate visibility logic can use the same predicate pattern, just inside `createEditor()` / `paint()` instead of panel-level `applyBindings()`. + +### Migration Steps + +1. **LogicalSwitches first** — highest widget density, cleanest row structure, most likely to hit raised limits +2. **Write `LogicalSwitchTableModel`** — one column per field, rowCount from firmware capability +3. **Write delegates** — `LogicalSwitchDelegate::createEditor()` returns the appropriate widget type based on function family (replaces updateLine's visibility masks) +4. **Replace TableLayout with QTableView** — drop the 12 fixed arrays +5. **Repeat for CustomFunctions** — similar structure, similar migration +6. **Telemetry sensors** — already panel-per-sensor, may need a different approach (lazy panel creation rather than table virtualization) + +### Interaction with model-arena + +The `model-arena` branch changes how Companion receives object counts: +- Today: `CPN_MAX_LOGICAL_SWITCHES` is a compile-time constant shared across all radios +- After: limits come from the radio profile / firmware capability, potentially varying at runtime + +The `QAbstractTableModel` adapter naturally handles this — `rowCount()` queries the capability, and the view adjusts. No fixed-size arrays needed in the panel header. + +If arena-backed arrays can grow (user adds a logical switch beyond the previous radio's limit on a new radio), the model emits `rowsInserted()` and the view updates. This is standard Qt Model/View — no custom code needed for the dynamic count case. + +--- + +## Phase 5: Convert Remaining Medium-Complexity Panels + +**Goal:** Normalize the binding pattern across remaining panels. + +In rough priority order (complexity vs. effort): +1. **FlightModes** — GVar linked/unlinked toggling +2. **ExpoDialog / MixerDialog** — firmware capability checks +3. **FunctionSwitches** — config/group-driven enable states +4. **Hardware** — board-dependent visibility + +Simple panels (Trainer, Checklist) may not need conversion — the manual approach is fine when there are only 2-3 rules. + +--- + +## Phase 6: Extract ViewModels Where Justified + +**Goal:** Pull business rules out of panels entirely, but only where it earns its keep. + +After Phases 2-3, the panels with rules complex enough to warrant a separate ViewModel class will be clear. The candidates are likely: +- `ModulePanel` -> `ModuleViewModel` +- `TelemetrySensorPanel` -> `SensorViewModel` + +For these, the ViewModel owns the predicates and the panel just binds to them. For everything else, lambdas in the panel constructor are fine. + +--- + +## What This Plan Avoids + +- **No `QDataWidgetMapper`** — it doesn't help with the hard problem (conditional visibility) and the Auto* system already solves data binding well +- **No .ui file changes** (Phases 1-3, 5-6) — improvements are in the .cpp layer. Phase 4 replaces .ui grid layouts with QTableView but keeps dialog structure intact +- **No big-bang migration** — each phase is independently shippable and testable +- **No framework adoption** — the binding utility is ~200 lines of code, not a dependency + +## Expected Outcome + +**Maintainability** (Phases 1-3, 5-6): Adding a new sensor formula or protocol means adding a few `bindVisible` lines and the predicate logic, not hunting through a monolithic `update()` to find every `setVisible` call. The `lock` flag anti-pattern fades away naturally since bindings are evaluated in batch, not triggered by signal cascades. + +**Scalability** (Phase 4): Grid-style panels handle variable per-radio object limits without linear widget cost. Dialog load time becomes independent of max object count. This is the prerequisite for `model-arena` support in Companion — without it, raising limits on H7/H5 radios would make panels unacceptably slow to open. diff --git a/companion/src/datamodels/CMakeLists.txt b/companion/src/datamodels/CMakeLists.txt index 20208d48f..9070de77f 100644 --- a/companion/src/datamodels/CMakeLists.txt +++ b/companion/src/datamodels/CMakeLists.txt @@ -4,6 +4,7 @@ project(datamodels) set(${PROJECT_NAME}_NAMES compounditemmodels filtereditemmodels + itemmodeleventhandler ) AddHeadersSources() diff --git a/companion/src/datamodels/filtereditemmodels.h b/companion/src/datamodels/filtereditemmodels.h index 3d834ca5d..d1c1fe24d 100644 --- a/companion/src/datamodels/filtereditemmodels.h +++ b/companion/src/datamodels/filtereditemmodels.h @@ -19,8 +19,7 @@ * GNU General Public License for more details. */ -#ifndef FILTEREDITEMMODELS_H -#define FILTEREDITEMMODELS_H +#pragma once #include "compounditemmodels.h" @@ -112,5 +111,3 @@ class CurveRefFilteredFactory : public FilteredItemModelFactory static QString fidToString(const int value); }; - -#endif // FILTEREDITEMMODELS_H diff --git a/companion/src/datamodels/itemmodeleventhandler.cpp b/companion/src/datamodels/itemmodeleventhandler.cpp new file mode 100644 index 000000000..942fedbc7 --- /dev/null +++ b/companion/src/datamodels/itemmodeleventhandler.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "itemmodeleventhandler.h" + +ItemModelEventHandler::ItemModelEventHandler(FilteredItemModel * itemModel, + bool & lock, + std::function updateCallback) : + QObject(nullptr), + m_lock(lock), + m_updateCallback(std::move(updateCallback)), + m_cnt(0) +{ + connect(itemModel, &FilteredItemModel::aboutToBeUpdated, this, [this] () + { + m_lock = true; + m_cnt++; + } + ); + + connect(itemModel, &FilteredItemModel::updateComplete, this, [this] () + { + m_cnt--; + + if (m_cnt < 1) { + if (m_updateCallback) + m_updateCallback(); + m_lock = false; + } + } + ); +} diff --git a/companion/src/datamodels/itemmodeleventhandler.h b/companion/src/datamodels/itemmodeleventhandler.h new file mode 100644 index 000000000..b69642e34 --- /dev/null +++ b/companion/src/datamodels/itemmodeleventhandler.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include "filtereditemmodels.h" + +#include +#include + +class ItemModelEventHandler : public QObject +{ + Q_OBJECT + + public: + explicit ItemModelEventHandler(FilteredItemModel * filteredModel, + bool & lock, std::function callback); + virtual ~ItemModelEventHandler() = default; + + private: + bool &m_lock; + std::function m_updateCallback; + int m_cnt; +}; diff --git a/companion/src/modeledit/setup_timer.cpp b/companion/src/modeledit/setup_timer.cpp index c1ee2b2fd..2ccf5e327 100644 --- a/companion/src/modeledit/setup_timer.cpp +++ b/companion/src/modeledit/setup_timer.cpp @@ -23,52 +23,82 @@ #include "ui_setup_timer.h" #include "namevalidator.h" -TimerPanel::TimerPanel(QWidget * parent, ModelData & model, TimerData & timer, GeneralSettings & generalSettings, Firmware * firmware, - QWidget * prevFocus, FilteredItemModelFactory * panelFilteredModels, CompoundItemModelFactory * panelItemModels): +TimerPanel::TimerPanel(QWidget * parent, ModelData & model, TimerData & timer, + GeneralSettings & generalSettings, Firmware * firmware, + QWidget * prevFocus, + FilteredItemModelFactory * panelFilteredModels, + CompoundItemModelFactory * panelItemModels): ModelPanel(parent, model, generalSettings, firmware), timer(timer), - ui(new Ui::Timer), - modelsUpdateCnt(0) + ui(new Ui::Timer) { ui->setupUi(this); - connectItemModelEvents(panelFilteredModels->getItemModel(FIM_TIMERSWITCH)); - lock = true; + imEventHandler = new ItemModelEventHandler( + panelFilteredModels->getItemModel(FIM_TIMERSWITCH), + lock, + [this] { this->update(); } + ); // Name - int length = firmware->getCapability(TimersName); - if (length == 0) - ui->name->hide(); - else { - ui->name->setValidator(new NameValidator(firmware->getBoard(), this)); - ui->name->setField(timer.name, length, this); - connect(ui->name, SIGNAL(currentDataChanged()), this, SLOT(onNameChanged())); - } - - ui->value->setField(timer.val, this); - ui->value->setMaximumTime(firmware->getMaxTimerStart()); - connect(ui->value, &AutoTimeEdit::currentDataChanged, [=](const int val) { - update(); + ui->name->setValidator(new NameValidator(firmware->getBoard(), this)); + ui->name->setField(timer.name, firmware->getCapability(TimersName), this); + ui->name->setBindPostChanged([this] { + emit this->nameChanged(); }); + // Start + ui->value->setField(timer.val, this); + ui->value->setMaximumTime(firmware->getMaxTimerStart()); + ui->value->setBindPostChanged([this]{ + this->update(); + }); + + // Mode ui->mode->setModel(panelItemModels->getItemModel(AIM_TIMER_MODE)); ui->mode->setField(timer.mode, this); - connect(ui->mode, SIGNAL(currentDataChanged(int)), this, SLOT(onModeChanged(int))); + ui->mode->setBindPostChanged([this] { + this->timer.modeChanged(); + this->update(); + emit this->modeChanged(); + }); + // Switch ui->swtch->setModel(panelFilteredModels->getItemModel(FIM_TIMERSWITCH)); ui->swtch->setField(timer.swtch, this); + ui->swtch->setBindEnabled([this] { + return this->timer.mode != TimerData::TIMERMODE_OFF; + }); + // Countdown ui->countdownBeep->setModel(panelItemModels->getItemModel(AIM_TIMER_COUNTDOWNBEEP)); ui->countdownBeep->setField(timer.countdownBeep, this); - connect(ui->countdownBeep, SIGNAL(currentDataChanged(int)), this, SLOT(onCountdownBeepChanged(int))); - - ui->minuteBeep->setField(timer.minuteBeep, this); - ui->persistent->setModel(panelItemModels->getItemModel(AIM_TIMER_PERSISTENT)); - ui->persistent->setField(timer.persistent, this); + ui->countdownBeep->setBindPostChanged([this] { + this->timer.countdownBeepChanged(); + this->update(); + }); ui->countdownStart->setModel(panelItemModels->getItemModel(AIM_TIMER_COUNTDOWNSTART)); ui->countdownStart->setField(timer.countdownStart, this); + ui->countdownStart->setBindEnabled([this] { + return this->timer.countdownBeep != TimerData::COUNTDOWNBEEP_SILENT; + }); + ui->countdownStart->addBuddyWidget(ui->countdownStartLabel); + + // Minute call + ui->minuteBeep->setField(timer.minuteBeep, this); + + // Persistence + ui->persistent->setModel(panelItemModels->getItemModel(AIM_TIMER_PERSISTENT)); + ui->persistent->setField(timer.persistent, this); + + // Time type ui->showElapsed->setModel(panelItemModels->getItemModel(AIM_TIMER_SHOWELAPSED)); ui->showElapsed->setField(timer.showElapsed, this); + ui->showElapsed->setBindEnabled([this] { return this->timer.val; }); + + // Time + // do not use setField as the raw value is meaningless + ui->persistentValue->setBindText([this] { return this->timer.pvalueToString(); }); disableMouseScrolling(); QWidget::setTabOrder(prevFocus, ui->name); @@ -81,51 +111,18 @@ TimerPanel::TimerPanel(QWidget * parent, ModelData & model, TimerData & timer, G QWidget::setTabOrder(ui->persistent, ui->showElapsed); update(); - lock = false; } TimerPanel::~TimerPanel() { delete ui; + delete imEventHandler; } void TimerPanel::update() { lock = true; - - ui->name->updateValue(); - ui->mode->updateValue(); - ui->swtch->updateValue(); - ui->value->updateValue(); - ui->countdownBeep->updateValue(); - ui->minuteBeep->updateValue(); - ui->countdownStart->updateValue(); - ui->showElapsed->updateValue(); - - if (timer.mode != TimerData::TIMERMODE_OFF) - ui->swtch->setEnabled(true); - else - ui->swtch->setEnabled(false); - - if (timer.countdownBeep == TimerData::COUNTDOWNBEEP_SILENT) { - ui->countdownStartLabel->setEnabled(false); - ui->countdownStart->setEnabled(false); - } - else { - ui->countdownStartLabel->setEnabled(true); - ui->countdownStart->setEnabled(true); - } - - ui->persistent->updateValue(); - ui->persistentValue->setText(timer.pvalueToString()); - - if (timer.val) { - ui->showElapsed->setEnabled(true); - } - else { - ui->showElapsed->setEnabled(false); - } - + updateAutoWidgets(); lock = false; } @@ -133,42 +130,3 @@ QWidget * TimerPanel::getLastFocus() { return ui->persistent; } - -void TimerPanel::connectItemModelEvents(const FilteredItemModel * itemModel) -{ - connect(itemModel, &FilteredItemModel::aboutToBeUpdated, this, &TimerPanel::onItemModelAboutToBeUpdated); - connect(itemModel, &FilteredItemModel::updateComplete, this, &TimerPanel::onItemModelUpdateComplete); -} - -void TimerPanel::onItemModelAboutToBeUpdated() -{ - lock = true; - modelsUpdateCnt++; -} - -void TimerPanel::onItemModelUpdateComplete() -{ - modelsUpdateCnt--; - if (modelsUpdateCnt < 1) { - update(); - lock = false; - } -} - -void TimerPanel::onNameChanged() -{ - emit nameChanged(); -} - -void TimerPanel::onCountdownBeepChanged(int index) -{ - timer.countdownBeepChanged(); - update(); -} - -void TimerPanel::onModeChanged(int index) -{ - timer.modeChanged(); - update(); - emit modeChanged(); -} diff --git a/companion/src/modeledit/setup_timer.h b/companion/src/modeledit/setup_timer.h index 34baf6001..c6e41e8b4 100644 --- a/companion/src/modeledit/setup_timer.h +++ b/companion/src/modeledit/setup_timer.h @@ -23,7 +23,7 @@ #include "modeledit.h" #include "eeprominterface.h" -#include "filtereditemmodels.h" +#include "itemmodeleventhandler.h" constexpr char FIM_TIMERSWITCH[] {"Timer Switch"}; constexpr char MIMETYPE_TIMER[] {"application/x-companion-timer"}; @@ -44,15 +44,6 @@ class TimerPanel : public ModelPanel virtual void update(); QWidget * getLastFocus(); - public slots: - void onNameChanged(); - void onModeChanged(int index); - - private slots: - void onItemModelAboutToBeUpdated(); - void onItemModelUpdateComplete(); - void onCountdownBeepChanged(int index); - signals: void nameChanged(); void modeChanged(); @@ -60,6 +51,5 @@ class TimerPanel : public ModelPanel private: TimerData & timer; Ui::Timer * ui; - void connectItemModelEvents(const FilteredItemModel * itemModel); - int modelsUpdateCnt; + ItemModelEventHandler *imEventHandler; }; diff --git a/companion/src/modeledit/setup_timer.ui b/companion/src/modeledit/setup_timer.ui index 1cb73a7cf..0a5591e87 100644 --- a/companion/src/modeledit/setup_timer.ui +++ b/companion/src/modeledit/setup_timer.ui @@ -48,7 +48,7 @@ true - QDateTimeEdit::HourSection + QDateTimeEdit::Section::HourSection hh:mm:ss @@ -62,7 +62,7 @@ - + Countdown @@ -72,7 +72,7 @@ - + Start @@ -95,7 +95,7 @@ - + (00:00:00) @@ -104,7 +104,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -137,6 +137,11 @@ QTimeEdit
autotimeedit.h
+ + AutoLabel + QLabel +
autolabel.h
+
diff --git a/companion/src/shared/CMakeLists.txt b/companion/src/shared/CMakeLists.txt index 787b805e7..06b6f170b 100644 --- a/companion/src/shared/CMakeLists.txt +++ b/companion/src/shared/CMakeLists.txt @@ -22,6 +22,7 @@ set(${PROJECT_NAME}_NAMES scrollarea textvalidator exclusivecombogroup + widgetbindings ) AddHeadersSources() diff --git a/companion/src/shared/autobitmappedcheckbox.cpp b/companion/src/shared/autobitmappedcheckbox.cpp index f599b517e..97901d361 100644 --- a/companion/src/shared/autobitmappedcheckbox.cpp +++ b/companion/src/shared/autobitmappedcheckbox.cpp @@ -76,7 +76,7 @@ void AutoBitMappedCheckBox::onToggled(bool checked) unsigned int fieldmask = (bitmask() << shiftbits()); *m_field = (*m_field & ~fieldmask) | (val << shiftbits()); emit currentDataChanged(val); - dataChanged(); + runPostChanged(); } } @@ -101,3 +101,8 @@ unsigned int AutoBitMappedCheckBox::bitmask() mask = ~(mask << m_bits); return (unsigned int)mask; } + +void AutoBitMappedCheckBox::setAutoText(QString text) +{ + QCheckBox::setText(text); +} diff --git a/companion/src/shared/autobitmappedcheckbox.h b/companion/src/shared/autobitmappedcheckbox.h index fc5d1cce6..33a087b21 100644 --- a/companion/src/shared/autobitmappedcheckbox.h +++ b/companion/src/shared/autobitmappedcheckbox.h @@ -34,6 +34,7 @@ class AutoBitMappedCheckBox : public QCheckBox, public AutoWidget virtual ~AutoBitMappedCheckBox(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; void setField(int & field, GenericPanel * panel = nullptr, bool invert = false); void setField(unsigned int & field, GenericPanel * panel = nullptr, bool invert = false); @@ -47,6 +48,9 @@ class AutoBitMappedCheckBox : public QCheckBox, public AutoWidget protected slots: void onToggled(bool checked); + protected: + virtual void setAutoText(QString text) override; + private: int *m_field; bool m_invert; diff --git a/companion/src/shared/autobitmappedcombobox.cpp b/companion/src/shared/autobitmappedcombobox.cpp index 8cbe1c0a5..75c1e4192 100644 --- a/companion/src/shared/autobitmappedcombobox.cpp +++ b/companion/src/shared/autobitmappedcombobox.cpp @@ -78,6 +78,11 @@ void AutoBitMappedComboBox::addItem(const QString & item, int value) updateValue(); } +void AutoBitMappedComboBox::setAutoModel(QAbstractItemModel * model) +{ + AutoBitMappedComboBox::setModel(model); +} + void AutoBitMappedComboBox::setField(unsigned int & field, GenericPanel * panel) { m_field = (int *)&field; @@ -130,11 +135,13 @@ void AutoBitMappedComboBox::setBits(const unsigned int numBits, const unsigned i void AutoBitMappedComboBox::setModel(QAbstractItemModel * model) { - setLock(true); - QComboBox::setModel(model); - setLock(false); - m_hasModel = true; - updateValue(); + if (model && QComboBox::model() != model) { + setLock(true); + QComboBox::setModel(model); + setLock(false); + m_hasModel = true; + updateValue(); + } } void AutoBitMappedComboBox::setAutoIndexes() @@ -190,7 +197,7 @@ void AutoBitMappedComboBox::onCurrentIndexChanged(int index) return; emit currentDataChanged(val); - dataChanged(); + runPostChanged(); } unsigned int AutoBitMappedComboBox::shiftbits() diff --git a/companion/src/shared/autobitmappedcombobox.h b/companion/src/shared/autobitmappedcombobox.h index 5d3e0d561..c0464f3f0 100644 --- a/companion/src/shared/autobitmappedcombobox.h +++ b/companion/src/shared/autobitmappedcombobox.h @@ -41,6 +41,8 @@ class AutoBitMappedComboBox : public QComboBox, public AutoWidget virtual void insertItems(int index, const QStringList & items); // AutoWidget virtual void updateValue() override; + virtual void setAutoModel(QAbstractItemModel * model); + void setBindText(std::function fn) = delete; void clear(); diff --git a/companion/src/shared/autobitsetcheckbox.cpp b/companion/src/shared/autobitsetcheckbox.cpp index 659d3652d..fb9ac8fec 100644 --- a/companion/src/shared/autobitsetcheckbox.cpp +++ b/companion/src/shared/autobitsetcheckbox.cpp @@ -145,7 +145,7 @@ void AutoBitsetCheckBox::onToggled(bool checked) } emit currentDataChanged(checked); - dataChanged(); + runPostChanged(); } void AutoBitsetCheckBox::init() @@ -160,3 +160,8 @@ void AutoBitsetCheckBox::initField(int bitmask, bool invert, int toggleMask) m_invert = toggleMask ? false : invert; updateValue(); } + +void AutoBitsetCheckBox::setAutoText(QString text) +{ + QCheckBox::setText(text); +} diff --git a/companion/src/shared/autobitsetcheckbox.h b/companion/src/shared/autobitsetcheckbox.h index 905c1f2b4..e65bf5cd4 100644 --- a/companion/src/shared/autobitsetcheckbox.h +++ b/companion/src/shared/autobitsetcheckbox.h @@ -46,6 +46,7 @@ class AutoBitsetCheckBox: public QCheckBox, public AutoWidget virtual ~AutoBitsetCheckBox(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; void setField(int & field, int bitmask = 0, bool invert = false, int toggleMask = 0); void setField(unsigned & field, int bitmask = 0, bool invert = false, int toggleMask = 0); @@ -59,6 +60,9 @@ class AutoBitsetCheckBox: public QCheckBox, public AutoWidget protected slots: void onToggled(bool checked); + protected: + virtual void setAutoText(QString text) override; + private: int *m_field = nullptr; int m_bitmask = 0; diff --git a/companion/src/shared/autocheckbox.cpp b/companion/src/shared/autocheckbox.cpp index a47323caa..ff4babb3f 100644 --- a/companion/src/shared/autocheckbox.cpp +++ b/companion/src/shared/autocheckbox.cpp @@ -34,6 +34,16 @@ AutoCheckBox::~AutoCheckBox() { } +void AutoCheckBox::onToggled(bool checked) +{ + if (m_field && !lock()) { + const bool val = m_invert ? !checked : checked; + *m_field = val; + emit currentDataChanged(val); + runPostChanged(); + } +} + void AutoCheckBox::setField(bool & field, GenericPanel * panel, bool invert) { m_field = &field; @@ -48,14 +58,9 @@ void AutoCheckBox::setInvert(bool invert) updateValue(); } -void AutoCheckBox::onToggled(bool checked) +void AutoCheckBox::setAutoText(QString text) { - if (m_field && !lock()) { - const bool val = m_invert ? !checked : checked; - *m_field = val; - emit currentDataChanged(val); - dataChanged(); - } + QCheckBox::setText(text); } void AutoCheckBox::updateValue() diff --git a/companion/src/shared/autocheckbox.h b/companion/src/shared/autocheckbox.h index db2787dd8..3f4f54395 100644 --- a/companion/src/shared/autocheckbox.h +++ b/companion/src/shared/autocheckbox.h @@ -34,6 +34,7 @@ class AutoCheckBox : public QCheckBox, public AutoWidget virtual ~AutoCheckBox(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; void setField(bool & field, GenericPanel * panel = nullptr, bool invert = false); void setInvert(bool invert); @@ -44,6 +45,9 @@ class AutoCheckBox : public QCheckBox, public AutoWidget protected slots: void onToggled(bool checked); + protected: + virtual void setAutoText(QString text) override; + private: bool *m_field; bool m_invert; diff --git a/companion/src/shared/autocombobox.cpp b/companion/src/shared/autocombobox.cpp index 69a8badf4..fbd9439eb 100644 --- a/companion/src/shared/autocombobox.cpp +++ b/companion/src/shared/autocombobox.cpp @@ -82,6 +82,11 @@ void AutoComboBox::addItem(const QString & item, int value) } } +void AutoComboBox::setAutoModel(QAbstractItemModel * model) +{ + AutoComboBox::setModel(model); +} + void AutoComboBox::setField(unsigned int & field, GenericPanel * panel) { setFieldInit(panel); @@ -153,11 +158,13 @@ void AutoComboBox::setFieldInit(GenericPanel * panel) void AutoComboBox::setModel(QAbstractItemModel * model) { - setLock(true); - QComboBox::setModel(model); - setLock(false); - m_hasModel = true; - updateValue(); + if (model && QComboBox::model() != model) { + setLock(true); + QComboBox::setModel(model); + setLock(false); + m_hasModel = true; + updateValue(); + } } void AutoComboBox::setAutoIndexes() @@ -231,6 +238,6 @@ void AutoComboBox::onCurrentIndexChanged(int index) return; emit currentDataChanged(val); - dataChanged(); + runPostChanged(); } } diff --git a/companion/src/shared/autocombobox.h b/companion/src/shared/autocombobox.h index 0bc5ef95a..82e38cca6 100644 --- a/companion/src/shared/autocombobox.h +++ b/companion/src/shared/autocombobox.h @@ -49,6 +49,8 @@ class AutoComboBox : public QComboBox, public AutoWidget virtual void insertItems(int index, const QStringList & items); // AutoWidget virtual void updateValue() override; + virtual void setAutoModel(QAbstractItemModel * model); + void setBindText(std::function fn) = delete; void clear(); diff --git a/companion/src/shared/autodoublespinbox.cpp b/companion/src/shared/autodoublespinbox.cpp index 06dd119b4..5cd9c85fb 100644 --- a/companion/src/shared/autodoublespinbox.cpp +++ b/companion/src/shared/autodoublespinbox.cpp @@ -88,6 +88,6 @@ void AutoDoubleSpinBox::onValueChanged(double value) if (m_field && !lock()) { *m_field = round(value * multiplier() - m_offset); emit currentDataChanged(*m_field); - dataChanged(); + runPostChanged(); } } diff --git a/companion/src/shared/autodoublespinbox.h b/companion/src/shared/autodoublespinbox.h index b1f3450d5..b36b22f4c 100644 --- a/companion/src/shared/autodoublespinbox.h +++ b/companion/src/shared/autodoublespinbox.h @@ -38,6 +38,8 @@ class AutoDoubleSpinBox : public QDoubleSpinBox, public AutoWidget virtual ~AutoDoubleSpinBox(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; + void setBindText(std::function fn) = delete; void setField(int & field, GenericPanel * panel = nullptr); void setField(unsigned int & field, GenericPanel * panel = nullptr); diff --git a/companion/src/shared/autohexspinbox.cpp b/companion/src/shared/autohexspinbox.cpp index 997f0d49f..d0c7b6e6e 100644 --- a/companion/src/shared/autohexspinbox.cpp +++ b/companion/src/shared/autohexspinbox.cpp @@ -75,7 +75,7 @@ void AutoHexSpinBox::onValueChanged(int value) if (*m_field != (unsigned)value) { *m_field = (unsigned)value; emit currentDataChanged(value); - dataChanged(); + runPostChanged(); } } } diff --git a/companion/src/shared/autohexspinbox.h b/companion/src/shared/autohexspinbox.h index a59c071c7..5c60ec414 100644 --- a/companion/src/shared/autohexspinbox.h +++ b/companion/src/shared/autohexspinbox.h @@ -39,6 +39,8 @@ class AutoHexSpinBox : public QSpinBox, public AutoWidget virtual ~AutoHexSpinBox(); virtual void updateValue(); + void setBindModel(std::function fn) = delete; + void setBindText(std::function fn) = delete; void setField(unsigned int & field, const unsigned int min = 0, const unsigned int max = AUTOHEXSPINBOX_MAX_VALUE, GenericPanel * panel = nullptr); void setField(unsigned int & field, GenericPanel * panel = nullptr); diff --git a/companion/src/shared/autolabel.cpp b/companion/src/shared/autolabel.cpp index c35f77027..88c43b4af 100644 --- a/companion/src/shared/autolabel.cpp +++ b/companion/src/shared/autolabel.cpp @@ -61,8 +61,13 @@ void AutoLabel::updateValue() { setLock(true); if (m_strField) - setText(*m_strField); + QLabel::setText(*m_strField); else if (m_charField) - setText(m_charField); + QLabel::setText(m_charField); setLock(false); } + +void AutoLabel::setAutoText(QString text) +{ + QLabel::setText(text); +} diff --git a/companion/src/shared/autolabel.h b/companion/src/shared/autolabel.h index 994e1d292..98289cbc8 100644 --- a/companion/src/shared/autolabel.h +++ b/companion/src/shared/autolabel.h @@ -34,11 +34,15 @@ class AutoLabel: public QLabel, public AutoWidget virtual ~AutoLabel(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; void setField(char * field, GenericPanel * panel = nullptr); void setField(QString & field, GenericPanel * panel = nullptr); void setWidth(int numChars); + protected: + virtual void setAutoText(QString text) override; + private: char *m_charField; QString *m_strField; diff --git a/companion/src/shared/autolineedit.cpp b/companion/src/shared/autolineedit.cpp index 2aa44186c..82551b11c 100644 --- a/companion/src/shared/autolineedit.cpp +++ b/companion/src/shared/autolineedit.cpp @@ -61,9 +61,9 @@ void AutoLineEdit::updateValue() { setLock(true); if (m_strField) - setText(*m_strField); + QLineEdit::setText(*m_strField); else if (m_charField) - setText(m_charField); + QLineEdit::setText(m_charField); setLock(false); } @@ -80,5 +80,12 @@ void AutoLineEdit::onEdited() return; emit currentDataChanged(); - dataChanged(); + runPostChanged(); +} + +void AutoLineEdit::setAutoText(QString text) +{ + setLock(true); + QLineEdit::setText(text); + setLock(false); } diff --git a/companion/src/shared/autolineedit.h b/companion/src/shared/autolineedit.h index ccaaa2e22..ae8657703 100644 --- a/companion/src/shared/autolineedit.h +++ b/companion/src/shared/autolineedit.h @@ -34,6 +34,7 @@ class AutoLineEdit: public QLineEdit, public AutoWidget virtual ~AutoLineEdit(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; void setField(char * field, int len, GenericPanel * panel = nullptr); void setField(QString & field, int len = 0, GenericPanel * panel = nullptr); @@ -44,6 +45,9 @@ class AutoLineEdit: public QLineEdit, public AutoWidget protected slots: void onEdited(); + protected: + virtual void setAutoText(QString text) override; + private: char *m_charField; QString *m_strField; diff --git a/companion/src/shared/autoprecisioncombobox.cpp b/companion/src/shared/autoprecisioncombobox.cpp index c48510be1..d4d94a235 100644 --- a/companion/src/shared/autoprecisioncombobox.cpp +++ b/companion/src/shared/autoprecisioncombobox.cpp @@ -114,9 +114,9 @@ void AutoPrecisionComboBox::setValue(int value) const unsigned int val = (unsigned int)value; if (*m_field != val) { *m_field = rangecheckDecimals(val); - updateValue(); emit currentDataChanged(value); - dataChanged(); + runPostChanged(); + updateValue(); } } @@ -127,9 +127,9 @@ void AutoPrecisionComboBox::setValue(unsigned int value) if (*m_field != value) { *m_field = rangecheckDecimals(value); - updateValue(); emit currentDataChanged((int)value); - dataChanged(); + runPostChanged(); + updateValue(); } } @@ -203,7 +203,7 @@ void AutoPrecisionComboBox::onCurrentIndexChanged(int index) if (*m_field != itemData(index).toUInt()) { *m_field = itemData(index).toUInt(); emit currentDataChanged(index); - dataChanged(); + runPostChanged(); } } diff --git a/companion/src/shared/autoprecisioncombobox.h b/companion/src/shared/autoprecisioncombobox.h index 1bacfdfbc..4ba3367c6 100644 --- a/companion/src/shared/autoprecisioncombobox.h +++ b/companion/src/shared/autoprecisioncombobox.h @@ -37,6 +37,8 @@ class AutoPrecisionComboBox : public QComboBox, public AutoWidget virtual ~AutoPrecisionComboBox(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; + void setBindText(std::function fn) = delete; void setField(int & field, unsigned int minDecimals = 0, unsigned int maxDecimals = 1, bool padding = false, QString suffix = "", GenericPanel * panel = nullptr); void setField(unsigned int & field, unsigned int minDecimals = 0, unsigned int maxDecimals = 1, bool padding = false, QString suffix = "", GenericPanel * panel = nullptr); diff --git a/companion/src/shared/autoslider.cpp b/companion/src/shared/autoslider.cpp index b377236b5..fbd0f5a44 100644 --- a/companion/src/shared/autoslider.cpp +++ b/companion/src/shared/autoslider.cpp @@ -84,7 +84,7 @@ void AutoSlider::onValueChanged(int value) if (*m_field != value) { *m_field = value; emit currentDataChanged(value); - dataChanged(); + runPostChanged(); } } } diff --git a/companion/src/shared/autoslider.h b/companion/src/shared/autoslider.h index 71c208686..ee73e19e4 100644 --- a/companion/src/shared/autoslider.h +++ b/companion/src/shared/autoslider.h @@ -35,6 +35,8 @@ class AutoSlider : public QSlider, public AutoWidget virtual ~AutoSlider(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; + void setBindText(std::function fn) = delete; void setField(int & field, int min, int max, GenericPanel * panel = nullptr); void setField(unsigned int & field, int min, int max, GenericPanel * panel = nullptr); diff --git a/companion/src/shared/autospinbox.cpp b/companion/src/shared/autospinbox.cpp index 115131728..cf3d58ca2 100644 --- a/companion/src/shared/autospinbox.cpp +++ b/companion/src/shared/autospinbox.cpp @@ -66,7 +66,7 @@ void AutoSpinBox::onValueChanged(int value) if (*m_field != value) { *m_field = value; emit currentDataChanged(value); - dataChanged(); + runPostChanged(); } } } diff --git a/companion/src/shared/autospinbox.h b/companion/src/shared/autospinbox.h index 3f680f44e..d6a7c993d 100644 --- a/companion/src/shared/autospinbox.h +++ b/companion/src/shared/autospinbox.h @@ -34,6 +34,8 @@ class AutoSpinBox : public QSpinBox, public AutoWidget virtual ~AutoSpinBox(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; + void setBindText(std::function fn) = delete; void setField(int & field, GenericPanel * panel = nullptr); void setField(unsigned int & field, GenericPanel * panel = nullptr); diff --git a/companion/src/shared/autotimeedit.cpp b/companion/src/shared/autotimeedit.cpp index b9640a86b..f0494eb1f 100644 --- a/companion/src/shared/autotimeedit.cpp +++ b/companion/src/shared/autotimeedit.cpp @@ -67,6 +67,6 @@ void AutoTimeEdit::onTimeChanged(QTime time) if (*m_field != val) { *m_field = val; emit currentDataChanged(val); - dataChanged(); + runPostChanged(); } } diff --git a/companion/src/shared/autotimeedit.h b/companion/src/shared/autotimeedit.h index 633d3df04..85d7058ad 100644 --- a/companion/src/shared/autotimeedit.h +++ b/companion/src/shared/autotimeedit.h @@ -34,6 +34,8 @@ class AutoTimeEdit : public QTimeEdit, public AutoWidget virtual ~AutoTimeEdit(); virtual void updateValue() override; + void setBindModel(std::function fn) = delete; + void setBindText(std::function fn) = delete; void setField(unsigned int & field, GenericPanel * panel = nullptr); void setTimeRange(const QTime min, const QTime max); diff --git a/companion/src/shared/autowidget.cpp b/companion/src/shared/autowidget.cpp index 2a96158ad..492729707 100644 --- a/companion/src/shared/autowidget.cpp +++ b/companion/src/shared/autowidget.cpp @@ -22,14 +22,112 @@ #include "autowidget.h" #include "genericpanel.h" +#include + AutoWidget::AutoWidget(): m_panel(nullptr), - m_lock(false) + m_lock(false), + m_buddyWidgets(QList {nullptr}), + m_enabled(nullptr), + m_model(nullptr), + m_text(nullptr), + m_visible(nullptr) { } -AutoWidget::~AutoWidget() +AutoWidget::~AutoWidget() = default; + +void AutoWidget::addBuddyWidget(AutoWidget * wgt) { + if (wgt) { + m_buddyWidgets.append(wgt); + // just in case set as we want parent to control + clearBuddyBinds(wgt); + } +} + +void AutoWidget::addBuddyWidgets(QList wgts) +{ + m_buddyWidgets.append(wgts); + // just in case set as we want parent to control + for (const auto wgt: m_buddyWidgets) { + if (wgt) clearBuddyBinds(wgt); + } +} + +// Apply bindings to this AutoWidget also enabled and visible to all buddy AutoWidgets +void AutoWidget::applyBindings() +{ + if (m_enabled) { + // buddy widget has no bind so no code executed when it is updated + // so apply parent value + bool val = m_enabled(); + setAutoEnabled(val); + + for (const auto wgt: m_buddyWidgets) { + if (wgt) wgt->setAutoEnabled(val); + } + } + + if (m_visible) { + // buddy widget has no bind so no code executed when it is updated + // so apply parent value + bool val = m_visible(); + setAutoVisible(val); + + for (const auto wgt: m_buddyWidgets) { + if (wgt) wgt->setAutoVisible(val); + } + } + + if (m_text) { + // makes no sense to apply parent bind to buddies + // leave it to each buddy to apply when it is updated + setAutoText(m_text()); + } + + if (m_model) { + // makes no sense to apply parent bind to buddies + // leave it to each buddy to apply when it is updated + setAutoModel(m_model()); + } +} + +void AutoWidget::clearBindEnabled() +{ + m_enabled = nullptr; +} + +void AutoWidget::clearBindVisible() +{ + m_visible = nullptr; +} + +void AutoWidget::clearBuddyBinds(AutoWidget * wgt) +{ + if (wgt) { + wgt->clearBindEnabled(); + wgt->clearBindVisible(); + } +} + +void AutoWidget::clearBuddyWidget(AutoWidget * wgt) +{ + if (wgt) { + if (!m_buddyWidgets.removeOne(wgt)) + qDebug() << "Warning: widget not removed from list"; + } +} + +void AutoWidget::clearBuddyWidgets() +{ + m_buddyWidgets.clear(); +} + +void AutoWidget::dataChanged() +{ + if (m_panel) + emit m_panel->modified(); } bool AutoWidget::lock() @@ -37,6 +135,56 @@ bool AutoWidget::lock() return panelLock() | m_lock; } +bool AutoWidget::panelLock() +{ + return m_panel ? m_panel->lock : false; +} + +void AutoWidget::runPostChanged() +{ + dataChanged(); + + if (m_postChanged) + m_postChanged(); +} + +void AutoWidget::setAutoEnabled(bool enabled) +{ + QWidget *wgt = dynamic_cast(this); + if (wgt) wgt->setEnabled(enabled); +} + +void AutoWidget::setAutoVisible(bool visible) +{ + QWidget *wgt = dynamic_cast(this); + if (wgt) wgt->setVisible(visible); +} + +void AutoWidget::setBindEnabled(std::function pred) +{ + m_enabled = std::move(pred); +} + +void AutoWidget::setBindModel(std::function fn) +{ + m_model = std::move(fn); +} + +void AutoWidget::setBindPostChanged(std::function fn) +{ + m_postChanged = std::move(fn); +} + +void AutoWidget::setBindText(std::function fn) +{ + m_text = std::move(fn); +} + +void AutoWidget::setBindVisible(std::function pred) +{ + m_visible = std::move(pred); +} + void AutoWidget::setLock(bool lock) { m_lock = lock; @@ -46,17 +194,3 @@ void AutoWidget::setPanel(GenericPanel * panel) { m_panel = panel; } - -void AutoWidget::dataChanged() -{ - if (m_panel) - emit m_panel->modified(); -} - -bool AutoWidget::panelLock() -{ - if (m_panel) - return m_panel->lock; - else - return false; -} diff --git a/companion/src/shared/autowidget.h b/companion/src/shared/autowidget.h index a48b13092..804d9f32c 100644 --- a/companion/src/shared/autowidget.h +++ b/companion/src/shared/autowidget.h @@ -21,9 +21,17 @@ #pragma once +#include + +#include +#include +#include +#include + class GenericPanel; -// Note: cannot be a Qt object otherwise it will create a compiler ambiguity in the inheriting class +// Base class for all Auto name prefixed widgets +// Note: ths class cannot be a Qt object as it will trigger a compile time ambiguity in the inheriting class class AutoWidget { friend class GenericPanel; @@ -32,17 +40,51 @@ class AutoWidget explicit AutoWidget(); ~AutoWidget(); + // buddy Auto widgets have their bindings mirroring the parent + // example buddies are AutoLabel and AutoComboBox + void addBuddyWidget(AutoWidget * wgt); + void addBuddyWidgets(QList wgts); + void clearBuddyWidget(AutoWidget * wgt); + void clearBuddyWidgets(); + + // mark those unsupported by the base Qt widget as deleted in the child widget + // these are not marked as virtual to support deletion + void setBindEnabled(std::function pred); + void setBindModel(std::function fn); + void setBindPostChanged(std::function fn); + void setBindText(std::function fn); + void setBindVisible(std::function pred); + protected: + virtual void setAutoModel(QAbstractItemModel * model) {}; + virtual void setAutoText(QString text) {}; + virtual void updateValue() = 0; + void applyBindings(); + void clearBindEnabled(); + void clearBindVisible(); + void clearBuddyBinds(AutoWidget * wgt); bool lock(); + bool panelLock(); + void runPostChanged(); + void setAutoEnabled(bool enabled); + void setAutoVisible(bool visible); void setLock(bool lock); void setPanel(GenericPanel * panel); - void dataChanged(); - bool panelLock(); private: GenericPanel *m_panel; bool m_lock; + + QList m_buddyWidgets; + + std::function m_enabled; + std::function m_model; + std::function m_text; + std::function m_visible; + std::function m_postChanged; + + void dataChanged(); }; diff --git a/companion/src/shared/genericpanel.cpp b/companion/src/shared/genericpanel.cpp index de86f6d6a..44e30774d 100644 --- a/companion/src/shared/genericpanel.cpp +++ b/companion/src/shared/genericpanel.cpp @@ -21,6 +21,7 @@ #include "genericpanel.h" #include "autowidget.h" +#include "widgetbindings.h" #include #include @@ -39,9 +40,7 @@ GenericPanel::GenericPanel(QWidget * parent, ModelData * model, GeneralSettings { } -GenericPanel::~GenericPanel() -{ -} +GenericPanel::~GenericPanel() = default; void GenericPanel::update() { @@ -137,6 +136,20 @@ void GenericPanel::updateAutoWidgets() AutoWidget *autowdgt = dynamic_cast(wdgt); if (autowdgt) { autowdgt->updateValue(); + autowdgt->applyBindings(); } } } + +WidgetBindings *GenericPanel::bindings() +{ + if (!m_bindings) + m_bindings = std::make_unique(); + return m_bindings.get(); +} + +void GenericPanel::applyBindings() +{ + if (m_bindings) + m_bindings->applyAll(); +} diff --git a/companion/src/shared/genericpanel.h b/companion/src/shared/genericpanel.h index d0d62ba96..9e5c321b9 100644 --- a/companion/src/shared/genericpanel.h +++ b/companion/src/shared/genericpanel.h @@ -22,12 +22,14 @@ #pragma once #include +#include class ModelData; class GeneralSettings; class Firmware; class QGridLayout; class QString; +class WidgetBindings; class GenericPanel : public QWidget { @@ -62,4 +64,10 @@ class GenericPanel : public QWidget void setFocusFilter(QWidget * w); void disableMouseScrolling(); void updateAutoWidgets(); + + WidgetBindings *bindings(); + void applyBindings(); + + private: + std::unique_ptr m_bindings; }; diff --git a/companion/src/shared/widgetbindings.cpp b/companion/src/shared/widgetbindings.cpp new file mode 100644 index 000000000..fe6e64d22 --- /dev/null +++ b/companion/src/shared/widgetbindings.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "widgetbindings.h" + +void WidgetBindings::bindVisible(QWidget *widget, std::function pred) +{ + m_visible.push_back({widget, std::move(pred)}); +} + +void WidgetBindings::bindEnabled(QWidget *widget, std::function pred) +{ + m_enabled.push_back({widget, std::move(pred)}); +} + +void WidgetBindings::bindText(QLabel *label, std::function fn) +{ + m_text.push_back({label, std::move(fn)}); +} + +void WidgetBindings::applyAll() +{ + for (const auto &b : m_visible) + b.widget->setVisible(b.pred()); + + for (const auto &b : m_enabled) + b.widget->setEnabled(b.pred()); + + for (const auto &b : m_text) + b.label->setText(b.fn()); +} diff --git a/companion/src/shared/widgetbindings.h b/companion/src/shared/widgetbindings.h new file mode 100644 index 000000000..c73097510 --- /dev/null +++ b/companion/src/shared/widgetbindings.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include +#include +#include +#include + +class WidgetBindings +{ + public: + WidgetBindings() = default; + + void bindVisible(QWidget *widget, std::function pred); + void bindEnabled(QWidget *widget, std::function pred); + void bindText(QLabel *label, std::function fn); + void applyAll(); + + private: + struct VisibleBinding { QWidget *widget; std::function pred; }; + struct EnabledBinding { QWidget *widget; std::function pred; }; + struct TextBinding { QLabel *label; std::function fn; }; + + std::vector m_visible; + std::vector m_enabled; + std::vector m_text; +};