#ifndef SETTINGSCONTROLLER_H #define SETTINGSCONTROLLER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audioconverter.h" #include "rigidentities.h" #include "rigserver.h" #include "wfviewtypes.h" #include "cluster.h" #include "prefs.h" // So we can get all of the enums #include "colorprefs.h" #include "audiodevices.h" #include "freqctrlquick.h" #include "ControllerController.h" class ShortcutOptionModel : public QAbstractListModel { Q_OBJECT public: enum Roles { TextRole = Qt::UserRole + 1, ValueRole }; Q_ENUM(Roles) explicit ShortcutOptionModel(QObject *parent = nullptr) : QAbstractListModel(parent) {} int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; void setOptions(const QVector>& options); int indexOfValue(const QString& value) const; private: QVector> m_options; }; class ShortcutSettingsModel : public QAbstractListModel { Q_OBJECT public: enum Roles { ShortcutEnabledRole = Qt::UserRole + 1, ShortcutSequenceRole, ShortcutCommandRole, ShortcutActionRole, ShortcutValueRole, ShortcutReceiverRole }; Q_ENUM(Roles) explicit ShortcutSettingsModel(QObject *parent = nullptr) : QAbstractListModel(parent) {} int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; void setShortcuts(QVector* shortcuts); void beginAppend(); void endAppend(); void beginDuplicate(int index); void endDuplicate(); void beginRemove(int index); void endRemove(); void notifyRowChanged(int row); void beginModelReset(); void endModelReset(); private: QVector* m_shortcuts = nullptr; }; class ClusterSettingsModel : public QAbstractTableModel { Q_OBJECT public: enum Column { Server = 0, Port, UserName, Password, Timeout, IsDefault, ColumnCount }; Q_ENUM(Column) enum Roles { ServerRole = Qt::UserRole + 1, PortRole, UserNameRole, PasswordRole, TimeoutRole, IsDefaultRole }; Q_ENUM(Roles) Q_INVOKABLE void setDefaultRow(int row, bool on) { if (row < 0 || row >= m_items.count()) return; bool anyChanged = false; if (!on) { // Keep one active default. Users choose another row rather than clearing all. emit dataChanged(index(row, IsDefault), index(row, IsDefault), { IsDefaultRole, Qt::CheckStateRole }); return; } else { // turning ON: this row true, all others false for (int i = 0; i < m_items.count(); ++i) { const bool want = (i == row); if (m_items[i].isdefault != want) { m_items[i].isdefault = want; anyChanged = true; } } } if (anyChanged && m_items.count() > 0) { // update default column + role for all rows (simple + reliable) emit dataChanged(index(0, IsDefault), index(m_items.count() - 1, IsDefault), { Qt::DisplayRole, Qt::EditRole, Qt::CheckStateRole, IsDefaultRole }); emit modified(); } } Q_INVOKABLE int defaultRow() const { for (int i = 0; i < m_items.count(); ++i) { if (m_items.at(i).isdefault) return i; } return m_items.isEmpty() ? -1 : 0; } explicit ClusterSettingsModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {} // ---------------- Model basics ---------------- int rowCount(const QModelIndex &parent = QModelIndex()) const override { if (parent.isValid()) return 0; return m_items.count(); } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return ColumnCount; } QHash roleNames() const override { return { { ServerRole, "server" }, { PortRole, "port" }, { UserNameRole, "userName" }, { PasswordRole, "password" }, { TimeoutRole, "timeout" }, { IsDefaultRole,"isdefault" } }; } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) return {}; const int r = index.row(); const int c = index.column(); if (r < 0 || r >= m_items.count()) return {}; const clusterSettings &it = m_items.at(r); // ---- QML role-based access (ListView/Repeater delegates) ---- switch (role) { case ServerRole: return it.server; case PortRole: return it.port; case UserNameRole: return it.userName; case PasswordRole: return it.password; case TimeoutRole: return it.timeout; case IsDefaultRole: return it.isdefault; default: break; } // ---- Optional: keep table-style column access for other views ---- if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (c) { case Server: return it.server; case Port: return it.port; case UserName: return it.userName; case Password: return it.password; case Timeout: return it.timeout; case IsDefault: return it.isdefault; default: return {}; } } if (role == Qt::CheckStateRole && c == IsDefault) return it.isdefault ? Qt::Checked : Qt::Unchecked; return {}; } bool setData(const QModelIndex &index, const QVariant &value, int role) override { if (!index.isValid()) return false; const int r = index.row(); if (r < 0 || r >= m_items.count()) return false; auto &it = m_items[r]; bool changed = false; switch (role) { case ServerRole: { const auto v = value.toString(); if (it.server != v) { it.server = v; changed = true; } break; } case PortRole: { const int v = value.toInt(); if (it.port != v) { it.port = v; changed = true; } break; } case UserNameRole: { const auto v = value.toString(); if (it.userName != v) { it.userName = v; changed = true; } break; } case PasswordRole: { const auto v = value.toString(); if (it.password != v) { it.password = v; changed = true; } break; } case TimeoutRole: { const int v = value.toInt(); if (it.timeout != v) { it.timeout = v; changed = true; } break; } case IsDefaultRole: { const bool v = value.toBool(); if (it.isdefault != v) { it.isdefault = v; changed = true; if (v) enforceSingleDefault(r); } break; } default: // fall back to your existing EditRole-by-column handling if you kept it return QAbstractTableModel::setData(index, value, role); } if (changed) { emit dataChanged(this->index(r, 0), this->index(r, ColumnCount - 1), { role, Qt::DisplayRole, Qt::EditRole }); emit modified(); } return changed; } Q_INVOKABLE bool setRoleValue(int row, int role, const QVariant &value) { if (row < 0 || row >= m_items.count()) return false; // Map role -> column so setData can reuse your existing logic int col = 0; switch (role) { case ServerRole: col = Server; break; case PortRole: col = Port; break; case UserNameRole: col = UserName; break; case PasswordRole: col = Password; break; case TimeoutRole: col = Timeout; break; case IsDefaultRole: col = IsDefault; break; default: return false; } return setData(index(row, col), value, role); } Q_INVOKABLE QVariant roleValue(int row, int role) const { if (row < 0 || row >= m_items.count()) return {}; int col = 0; switch (role) { case ServerRole: col = Server; break; case PortRole: col = Port; break; case UserNameRole: col = UserName; break; case PasswordRole: col = Password; break; case TimeoutRole: col = Timeout; break; case IsDefaultRole: col = IsDefault; break; default: return {}; } return data(index(row, col), role); } Qt::ItemFlags flags(const QModelIndex &index) const override { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; if (index.column() == IsDefault) f |= Qt::ItemIsUserCheckable; return f; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return {}; switch (section) { case Server: return "Server"; case Port: return "Port"; case UserName: return "User"; case Password: return "Password"; case Timeout: return "Timeout"; case IsDefault: return "Default"; default: return {}; } } // ---------------- QML API ---------------- Q_INVOKABLE void addEntry(const QString &server = {}, int port = 7300, const QString &userName = {}, const QString &password = {}, int timeout = 30, bool isdefault = false) { const int r = m_items.count(); beginInsertRows(QModelIndex(), r, r); const bool makeDefault = isdefault || m_items.isEmpty(); m_items.append({ server, port, userName, password, timeout, makeDefault }); endInsertRows(); if (makeDefault) enforceSingleDefault(r); emit modified(); } Q_INVOKABLE void removeEntry(int row) { if (row < 0 || row >= m_items.count()) return; beginRemoveRows(QModelIndex(), row, row); m_items.removeAt(row); endRemoveRows(); emit modified(); } Q_INVOKABLE void clearAll() { beginResetModel(); m_items.clear(); endResetModel(); emit modified(); } Q_INVOKABLE int count() const { return m_items.count(); } // ---------------- Vector / list bridging ---------------- void setFromList(const QList &list) { beginResetModel(); m_items = list; bool hasDefault = false; for (const auto &item : std::as_const(m_items)) { if (item.isdefault) { hasDefault = true; break; } } if (!hasDefault && !m_items.isEmpty()) m_items[0].isdefault = true; endResetModel(); } const QList& toList() const { return m_items; } // Convenience if you still receive std::vector void setFromVector(const std::vector &v) { beginResetModel(); m_items.clear(); m_items.reserve(static_cast(v.size())); for (const auto &it : v) m_items.append(it); endResetModel(); } private: void enforceSingleDefault(int keepRow) { bool changed = false; for (int i = 0; i < m_items.count(); ++i) { if (i == keepRow) continue; if (m_items[i].isdefault) { m_items[i].isdefault = false; changed = true; } } if (changed) { emit dataChanged(index(0, IsDefault), index(m_items.count() - 1, IsDefault), { Qt::DisplayRole, Qt::EditRole, Qt::CheckStateRole }); } } private: QList m_items; signals: void modified(); }; class ServerUsersModel : public QAbstractTableModel { Q_OBJECT public: enum Column { Username = 0, Password, UserType, ColumnCount }; Q_ENUM(Column) enum Roles { UsernameRole = Qt::UserRole + 1, PasswordRole, UserTypeRole, UserTypeNameRole }; Q_ENUM(Roles) explicit ServerUsersModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {} int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : items.count(); } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return ColumnCount; } QHash roleNames() const override { return { { UsernameRole, "username" }, { PasswordRole, "password" }, { UserTypeRole, "userType" }, { UserTypeNameRole, "userTypeName" } }; } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid() || index.row() < 0 || index.row() >= items.count()) return {}; const SERVERUSER &user = items.at(index.row()); switch (role) { case UsernameRole: return user.username; case PasswordRole: return user.password; case UserTypeRole: return user.userType; case UserTypeNameRole: return userTypeName(user.userType); default: break; } if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (index.column()) { case Username: return user.username; case Password: return user.password; case UserType: return user.userType; default: return {}; } } return {}; } bool setData(const QModelIndex &index, const QVariant &value, int role) override { if (!index.isValid() || index.row() < 0 || index.row() >= items.count()) return false; SERVERUSER &user = items[index.row()]; bool changed = false; switch (role) { case UsernameRole: { const QString v = value.toString(); if (user.username != v) { user.username = v; changed = true; } break; } case PasswordRole: { const QString v = value.toString(); if (user.password != v) { user.password = v; changed = true; } break; } case UserTypeRole: { const quint8 v = static_cast(value.toUInt()); if (user.userType != v) { user.userType = v; changed = true; } break; } default: return false; } if (changed) { emit dataChanged(this->index(index.row(), 0), this->index(index.row(), ColumnCount - 1), { role, Qt::DisplayRole, Qt::EditRole, UserTypeNameRole }); emit modified(); } return changed; } Qt::ItemFlags flags(const QModelIndex &index) const override { return index.isValid() ? Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable : Qt::NoItemFlags; } Q_INVOKABLE bool setRoleValue(int row, int role, const QVariant &value) { if (row < 0 || row >= items.count()) return false; int col = Username; switch (role) { case UsernameRole: col = Username; break; case PasswordRole: col = Password; break; case UserTypeRole: col = UserType; break; default: return false; } return setData(index(row, col), value, role); } Q_INVOKABLE void addEntry(const QString &username = {}, const QString &password = {}, int userType = 1) { const int row = items.count(); beginInsertRows(QModelIndex(), row, row); SERVERUSER user; user.username = username; user.password = password; user.userType = static_cast(userType); items.append(user); endInsertRows(); emit modified(); } Q_INVOKABLE void removeEntry(int row) { if (row < 0 || row >= items.count()) return; beginRemoveRows(QModelIndex(), row, row); items.removeAt(row); endRemoveRows(); emit modified(); } Q_INVOKABLE QString userTypeName(int userType) const { switch (userType) { case 0: return tr("Admin User"); case 1: return tr("Normal User"); case 2: return tr("Normal with no TX"); default: return tr("Unknown"); } } void setFromList(const QList &list) { beginResetModel(); items = list; endResetModel(); } const QList& toList() const { return items; } signals: void modified(); private: QList items; }; class SettingsController : public QObject { Q_OBJECT Q_PROPERTY(QQmlPropertyMap * options READ options CONSTANT) Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged) Q_PROPERTY(QVariantMap uiSpecs READ getUiSpecs NOTIFY uiSpecsChanged) Q_PROPERTY(QVariantList radioProfiles READ radioProfiles NOTIFY radioProfilesChanged) Q_PROPERTY(int currentRadioProfileIndex READ currentRadioProfileIndex NOTIFY currentRadioProfileIndexChanged) Q_PROPERTY(QVariantList shortcuts READ shortcuts NOTIFY shortcutsChanged) Q_PROPERTY(QAbstractListModel* shortcutsModel READ shortcutsModel CONSTANT) Q_PROPERTY(QAbstractListModel* shortcutAppCommandModel READ shortcutAppCommandModel CONSTANT) Q_PROPERTY(QAbstractListModel* shortcutRadioCommandModel READ shortcutRadioCommandModel CONSTANT) Q_PROPERTY(ClusterSettingsModel* clusterModel READ clusterModel CONSTANT) Q_PROPERTY(ServerUsersModel* serverUsersModel READ serverUsersModel CONSTANT) Q_PROPERTY(ControllerController* controllerController READ controllerController CONSTANT) public: explicit SettingsController(QString file, QObject *p=nullptr); ~SettingsController() override = default; QQmlPropertyMap* options() const { return m_options; } bool dirty() const { return m_dirty; } // Generic key/value API (still useful for C++ and QML writes) Q_INVOKABLE QVariant option(const QString& key) const; Q_INVOKABLE void setOption(const QString& key, const QVariant& value); Q_INVOKABLE bool selectClusterRow(int row); QVariantMap getUiSpecs() const { return uiSpecs; } QVariantList radioProfiles() const { return m_radioProfiles; } int currentRadioProfileIndex() const { return m_currentRadioProfileIndex; } QVariantList shortcuts() const; preferences* getPrefs() { return &prefs;} udpPreferences* getUdpPrefs() { return &udpPrefs;} SERVERCONFIG* getServerConfig() { return &serverConfig;} colorPrefsType getCurrentColorPreset() {return colorPreset[prefs.currentColorPresetNumber];} audioDevices* getAudioDevices() {return audioDev.get();} ClusterSettingsModel* clusterModel() const { return m_clusterModel.get(); } ServerUsersModel* serverUsersModel() const { return m_serverUsersModel.get(); } ControllerController* controllerController() const { return m_controllerController; } QAbstractListModel* shortcutsModel() const { return m_shortcutsModel.get(); } QAbstractListModel* shortcutAppCommandModel() const { return m_shortcutAppCommandModel.get(); } QAbstractListModel* shortcutRadioCommandModel() const { return m_shortcutRadioCommandModel.get(); } usbDevMap* usbDevices() { return &m_usbDevices; } QVector