diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2721bd463..94f55b4fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -181,6 +181,10 @@ add_library(neochat STATIC jobs/neochatadd3pidjob.h identityserverhelper.cpp identityserverhelper.h + enums/powerlevel.cpp + enums/powerlevel.h + models/permissionsmodel.cpp + models/permissionsmodel.h ) set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES diff --git a/src/enums/powerlevel.cpp b/src/enums/powerlevel.cpp new file mode 100644 index 000000000..fa46787b1 --- /dev/null +++ b/src/enums/powerlevel.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "powerlevel.h" + +QString PowerLevel::nameForLevel(Level level) +{ + switch (level) { + case PowerLevel::Member: + return i18n("Member"); + case PowerLevel::Moderator: + return i18n("Moderator"); + case PowerLevel::Admin: + return i18n("Admin"); + case PowerLevel::Mute: + return i18n("Mute"); + case PowerLevel::Custom: + return i18n("Custom"); + default: + return {}; + } +} + +int PowerLevel::valueForLevel(Level level) +{ + switch (level) { + case PowerLevel::Member: + return 0; + case PowerLevel::Moderator: + return 50; + case PowerLevel::Admin: + return 100; + case PowerLevel::Mute: + return -1; + default: + return {}; + } +} + +PowerLevel::Level PowerLevel::levelForValue(int value) +{ + switch (value) { + case 0: + return PowerLevel::Member; + case 50: + return PowerLevel::Moderator; + case 100: + return PowerLevel::Admin; + case -1: + return PowerLevel::Mute; + default: + return PowerLevel::Custom; + } +} + +PowerLevelModel::PowerLevelModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +bool PowerLevelModel::showMute() const +{ + return m_showMute; +} + +void PowerLevelModel::setShowMute(bool showMute) +{ + if (showMute == m_showMute) { + return; + } + m_showMute = showMute; + Q_EMIT showMuteChanged(); +} + +QVariant PowerLevelModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return {}; + } + if (index.row() >= rowCount()) { + qDebug() << "PowerLevelModel, something's wrong: index.row() >= m_rules.count()"; + return {}; + } + + const auto level = static_cast(index.row()); + if (role == NameRole) { + return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.", + "%1 (%2)", + PowerLevel::nameForLevel(level), + PowerLevel::valueForLevel(level)); + } + if (role == ValueRole) { + return PowerLevel::valueForLevel(level); + } + return {}; +} + +int PowerLevelModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return PowerLevel::NUMLevels - (m_showMute ? 0 : 1); +} + +QHash PowerLevelModel::roleNames() const +{ + return {{NameRole, "name"}, {ValueRole, "value"}}; +} diff --git a/src/enums/powerlevel.h b/src/enums/powerlevel.h new file mode 100644 index 000000000..173c969f6 --- /dev/null +++ b/src/enums/powerlevel.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include +#include + +#include + +/** + * @class PowerLevel + * + * This class is designed to define the PowerLevel enumeration. + */ +class PowerLevel : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + +public: + /** + * @brief The type of delegate that is needed for the event. + * + * @note While similar this is not the matrix event or message type. This is + * to tell a QML ListView what delegate to show for each event. So while + * similar to the spec it is not the same. + */ + enum Level { + Member, /**< A basic member. */ + Moderator, /**< A moderator with enhanced powers. */ + Admin, /**< The highest power level in the room. */ + Mute, /**< The level to remove posting privileges. */ + NUMLevels, + Custom, /**< A non-standard value. Intentionally after NUMLevels so it doesn't appear in the model. */ + }; + Q_ENUM(Level); + + /** + * @brief Return a string representation of the enum value. + */ + static QString nameForLevel(Level level); + + /** + * @brief Return the integer representation of the enum value. + */ + static int valueForLevel(Level level); + + /** + * @brief Return the enum value for the given integer power level. + */ + static Level levelForValue(int value); +}; + +/** + * @class PowerLevelModel + * + * A model visualize the allowed power levels. + */ +class PowerLevelModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(bool showMute READ showMute WRITE setShowMute NOTIFY showMuteChanged) + +public: + /** + * @brief Defines the model roles. + */ + enum Roles { + NameRole = Qt::DisplayRole, /**< The power level name. */ + ValueRole, /**< The power level value. */ + }; + Q_ENUM(Roles) + + explicit PowerLevelModel(QObject *parent = nullptr); + + [[nodiscard]] bool showMute() const; + void setShowMute(bool showMute); + + /** + * @brief Get the given role value at the given index. + * + * @sa QAbstractItemModel::data + */ + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + /** + * @brief Number of rows in the model. + * + * @sa QAbstractItemModel::rowCount + */ + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + /** + * @brief Returns a mapping from Role enum values to role names. + * + * @sa Roles, QAbstractItemModel::roleNames() + */ + [[nodiscard]] QHash roleNames() const override; + +Q_SIGNALS: + void showMuteChanged(); + +private: + bool m_showMute = true; +}; diff --git a/src/models/permissionsmodel.cpp b/src/models/permissionsmodel.cpp new file mode 100644 index 000000000..4289302f5 --- /dev/null +++ b/src/models/permissionsmodel.cpp @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "permissionsmodel.h" +#include "powerlevel.h" + +#include + +#include + +static const QStringList defaultPermissions = { + QStringLiteral("users_default"), + QStringLiteral("state_default"), + QStringLiteral("events_default"), + QStringLiteral("invite"), + QStringLiteral("kick"), + QStringLiteral("ban"), + QStringLiteral("redact"), + QStringLiteral("m.reaction"), + QStringLiteral("m.room.redaction"), + QStringLiteral("m.room.power_levels"), + QStringLiteral("m.room.name"), + QStringLiteral("m.room.avatar"), + QStringLiteral("m.room.canonical_alias"), + QStringLiteral("m.room.topic"), + QStringLiteral("m.room.encryption"), + QStringLiteral("m.room.history_visibility"), + QStringLiteral("m.room.pinned_events"), + QStringLiteral("m.room.tombstone"), + QStringLiteral("m.room.server_acl"), + QStringLiteral("m.space.child"), + QStringLiteral("m.space.parent"), +}; + +// Alternate name text for default permissions. +static const QHash defaultPermissionNames = { + {QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")}, + {QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")}, + {QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")}, + {QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")}, + {QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")}, + {QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")}, + {QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")}, + {QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")}, + {QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")}, + {QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")}, + {QStringLiteral("m.room.name"), kli18nc("Room permission type", "Change the room name")}, + {QStringLiteral("m.room.avatar"), kli18nc("Room permission type", "Change the room avatar")}, + {QStringLiteral("m.room.canonical_alias"), kli18nc("Room permission type", "Change the room canonical alias")}, + {QStringLiteral("m.room.topic"), kli18nc("Room permission type", "Change the room topic")}, + {QStringLiteral("m.room.encryption"), kli18nc("Room permission type", "Enable encryption for the room")}, + {QStringLiteral("m.room.history_visibility"), kli18nc("Room permission type", "Change the room history visibility")}, + {QStringLiteral("m.room.pinned_events"), kli18nc("Room permission type", "Set pinned events")}, + {QStringLiteral("m.room.tombstone"), kli18nc("Room permission type", "Upgrade the room")}, + {QStringLiteral("m.room.server_acl"), kli18nc("Room permission type", "Set the room server access control list (ACL)")}, + {QStringLiteral("m.space.child"), kli18nc("Room permission type", "Set the children of this space")}, + {QStringLiteral("m.space.parent"), kli18nc("Room permission type", "Set the parent space of this room")}, +}; + +// Subtitles for the default values. +static const QHash defaultSubtitles = { + {QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")}, + {QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")}, + {QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")}, +}; + +// Permissions that should use the event default. +static const QStringList eventPermissions = { + QStringLiteral("m.room.message"), + QStringLiteral("m.reaction"), + QStringLiteral("m.room.redaction"), +}; + +PermissionsModel::PermissionsModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +NeoChatRoom *PermissionsModel::room() const +{ + return m_room; +} + +void PermissionsModel::setRoom(NeoChatRoom *room) +{ + if (room == m_room) { + return; + } + m_room = room; + Q_EMIT roomChanged(); + + initializeModel(); +} + +void PermissionsModel::initializeModel() +{ + beginResetModel(); + + m_permissions.clear(); + + if (m_room == nullptr) { + endResetModel(); + return; + } + + const auto currentPowerLevelEvent = m_room->currentState().get(); + if (currentPowerLevelEvent == nullptr) { + return; + } + + m_permissions.append(defaultPermissions); + + for (const auto &event : currentPowerLevelEvent->events().keys()) { + if (!m_permissions.contains(event)) { + m_permissions += event; + } + } + + endResetModel(); +} + +QVariant PermissionsModel::data(const QModelIndex &index, int role) const +{ + if (m_room == nullptr || !index.isValid()) { + return {}; + } + if (index.row() >= rowCount()) { + qDebug() << "PushRuleModel, something's wrong: index.row() >= m_rules.count()"; + return {}; + } + + const auto permission = m_permissions.value(index.row()); + if (role == NameRole) { + if (defaultPermissionNames.keys().contains(permission)) { + return defaultPermissionNames.value(permission).toString(); + } + return permission; + } + if (role == SubtitleRole) { + if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) { + return permission; + } + if (defaultSubtitles.contains(permission)) { + return defaultSubtitles.value(permission).toString(); + } + return QString(); + } + if (role == TypeRole) { + return permission; + } + if (role == LevelRole) { + const auto level = powerLevel(permission); + if (level.has_value()) { + return *level; + } + return {}; + } + if (role == LevelNameRole) { + const auto level = powerLevel(permission); + if (level.has_value()) { + return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.", + "%1 (%2)", + PowerLevel::nameForLevel(PowerLevel::levelForValue(*level)), + *level); + } + return QString(); + } + if (role == IsDefaultValueRole) { + return permission.contains(QLatin1String("default")); + } + if (role == IsBasicPermissionRole) { + return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban") + || permission == QStringLiteral("redact"); + } + return {}; +} + +int PermissionsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_permissions.count(); +} + +QHash PermissionsModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + roles[NameRole] = "name"; + roles[SubtitleRole] = "subtitle"; + roles[TypeRole] = "type"; + roles[LevelRole] = "level"; + roles[LevelNameRole] = "levelName"; + roles[IsDefaultValueRole] = "isDefaultValue"; + roles[IsBasicPermissionRole] = "isBasicPermission"; + return roles; +} + +std::optional PermissionsModel::powerLevel(const QString &permission) const +{ + if (m_room == nullptr) { + return std::nullopt; + } + + if (const auto currentPowerLevelEvent = m_room->currentState().get()) { + if (permission == QStringLiteral("ban")) { + return currentPowerLevelEvent->ban(); + } else if (permission == QStringLiteral("kick")) { + return currentPowerLevelEvent->kick(); + } else if (permission == QStringLiteral("invite")) { + return currentPowerLevelEvent->invite(); + } else if (permission == QStringLiteral("redact")) { + return currentPowerLevelEvent->redact(); + } else if (permission == QStringLiteral("users_default")) { + return currentPowerLevelEvent->usersDefault(); + } else if (permission == QStringLiteral("state_default")) { + return currentPowerLevelEvent->stateDefault(); + } else if (permission == QStringLiteral("events_default")) { + return currentPowerLevelEvent->eventsDefault(); + } else if (eventPermissions.contains(permission)) { + return currentPowerLevelEvent->powerLevelForEvent(permission); + } else { + return currentPowerLevelEvent->powerLevelForState(permission); + } + } + return std::nullopt; +} + +void PermissionsModel::setPowerLevel(const QString &permission, const int &newPowerLevel) +{ + if (m_room == nullptr) { + return; + } + + int clampPowerLevel = std::clamp(newPowerLevel, -1, 100); + + const auto currentPowerLevel = powerLevel(permission); + if (!currentPowerLevel.has_value() || currentPowerLevel == clampPowerLevel) { + return; + } + + if (auto currentPowerLevelEvent = m_room->currentState().get()) { + auto powerLevelContent = currentPowerLevelEvent->contentJson(); + if (powerLevelContent.contains(permission)) { + powerLevelContent[permission] = clampPowerLevel; + } else { + auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject(); + eventPowerLevels[permission] = clampPowerLevel; + powerLevelContent[QLatin1String("events")] = eventPowerLevels; + } + + m_room->setState(powerLevelContent); + } +} + +#include "moc_permissionsmodel.cpp" diff --git a/src/models/permissionsmodel.h b/src/models/permissionsmodel.h new file mode 100644 index 000000000..26a6592b7 --- /dev/null +++ b/src/models/permissionsmodel.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include +#include + +#include "neochatroom.h" + +/** + * @class PermissionsModel + * + * This class defines the model for managing room permission levels. + */ +class PermissionsModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT + + /** + * @brief The room to show the permissions for + */ + Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged) + +public: + /** + * @brief Defines the model roles. + */ + enum Roles { + NameRole = Qt::DisplayRole, /**< The permission name. */ + SubtitleRole, /**< The description of the permission. */ + TypeRole, /**< The base type of the permission, normally the event type id except for ban, kick, etc. */ + LevelRole, /**< The current power level for the permission. */ + LevelNameRole, /**< The current power level for the permission as a string. */ + IsDefaultValueRole, /**< Whether the permission is a default value, e.g. for users. */ + IsBasicPermissionRole, /**< Whether the permission is one of the basic ones, e.g. kick, ban, etc. */ + }; + Q_ENUM(Roles) + + explicit PermissionsModel(QObject *parent = nullptr); + + NeoChatRoom *room() const; + void setRoom(NeoChatRoom *room); + + /** + * @brief Get the given role value at the given index. + * + * @sa QAbstractItemModel::data + */ + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + /** + * @brief Number of rows in the model. + * + * @sa QAbstractItemModel::rowCount + */ + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + /** + * @brief Returns a mapping from Role enum values to role names. + * + * @sa Roles, QAbstractItemModel::roleNames() + */ + [[nodiscard]] QHash roleNames() const override; + + /** + * @brief Return the power level required for the given permission. + */ + std::optional powerLevel(const QString &permission) const; + + /** + * @brief Set the power level required for the given permission. + */ + Q_INVOKABLE void setPowerLevel(const QString &permission, const int &newPowerLevel); + +Q_SIGNALS: + void roomChanged(); + +private: + QPointer m_room; + + QStringList m_permissions; + + void initializeModel(); +}; diff --git a/src/models/userlistmodel.cpp b/src/models/userlistmodel.cpp index 0d0903d35..fca7e6abc 100644 --- a/src/models/userlistmodel.cpp +++ b/src/models/userlistmodel.cpp @@ -7,6 +7,7 @@ #include +#include "enums/powerlevel.h" #include "neochatroom.h" using namespace Quotient; @@ -94,16 +95,10 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const auto userPl = pl->powerLevelForUser(user->id()); - switch (userPl) { - case 0: - return QStringLiteral("Member"); - case 50: - return QStringLiteral("Moderator"); - case 100: - return QStringLiteral("Admin"); - default: - return QStringLiteral("Custom"); - } + return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.", + "%1 (%2)", + PowerLevel::nameForLevel(PowerLevel::levelForValue(userPl)), + userPl); } return {}; diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 47c365a04..e3af3d78c 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -876,7 +876,7 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel qWarning() << "User is not a member of this room so power level cannot be set"; return; } - int clampPowerLevel = std::clamp(powerLevel, 0, 100); + int clampPowerLevel = std::clamp(powerLevel, -1, 100); auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson(); auto powerLevelUserOverrides = powerLevelContent["users"_ls].toObject(); @@ -898,254 +898,6 @@ int NeoChatRoom::getUserPowerLevel(const QString &userId) const return powerLevelEvent->powerLevelForUser(userId); } -int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const -{ - const auto powerLevelEvent = currentState().get(); - if (eventName == "ban"_ls) { - return powerLevelEvent->ban(); - } else if (eventName == "kick"_ls) { - return powerLevelEvent->kick(); - } else if (eventName == "invite"_ls) { - return powerLevelEvent->invite(); - } else if (eventName == "redact"_ls) { - return powerLevelEvent->redact(); - } else if (eventName == "users_default"_ls) { - return powerLevelEvent->usersDefault(); - } else if (eventName == "state_default"_ls) { - return powerLevelEvent->stateDefault(); - } else if (eventName == "events_default"_ls) { - return powerLevelEvent->eventsDefault(); - } else if (isStateEvent) { - return powerLevelEvent->powerLevelForState(eventName); - } else { - return powerLevelEvent->powerLevelForEvent(eventName); - } -} - -void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent) -{ - auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson(); - int clampPowerLevel = std::clamp(newPowerLevel, 0, 100); - int powerLevel = 0; - - if (powerLevelContent.contains(eventName)) { - powerLevel = powerLevelContent[eventName].toInt(); - - if (powerLevel != clampPowerLevel) { - powerLevelContent[eventName] = clampPowerLevel; - } - } else { - auto eventPowerLevels = powerLevelContent["events"_ls].toObject(); - - if (eventPowerLevels.contains(eventName)) { - powerLevel = eventPowerLevels[eventName].toInt(); - } else { - if (isStateEvent) { - powerLevel = powerLevelContent["state_default"_ls].toInt(); - } else { - powerLevel = powerLevelContent["events_default"_ls].toInt(); - } - } - - if (powerLevel != clampPowerLevel) { - eventPowerLevels[eventName] = clampPowerLevel; - powerLevelContent["events"_ls] = eventPowerLevels; - } - } - - setState("m.room.power_levels"_ls, {}, powerLevelContent); -} - -int NeoChatRoom::defaultUserPowerLevel() const -{ - return powerLevel("users_default"_ls); -} - -void NeoChatRoom::setDefaultUserPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("users_default"_ls, newPowerLevel); -} - -int NeoChatRoom::invitePowerLevel() const -{ - return powerLevel("invite"_ls); -} - -void NeoChatRoom::setInvitePowerLevel(const int &newPowerLevel) -{ - setPowerLevel("invite"_ls, newPowerLevel); -} - -int NeoChatRoom::kickPowerLevel() const -{ - return powerLevel("kick"_ls); -} - -void NeoChatRoom::setKickPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("kick"_ls, newPowerLevel); -} - -int NeoChatRoom::banPowerLevel() const -{ - return powerLevel("ban"_ls); -} - -void NeoChatRoom::setBanPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("ban"_ls, newPowerLevel); -} - -int NeoChatRoom::redactPowerLevel() const -{ - return powerLevel("redact"_ls); -} - -void NeoChatRoom::setRedactPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("redact"_ls, newPowerLevel); -} - -int NeoChatRoom::statePowerLevel() const -{ - return powerLevel("state_default"_ls); -} - -void NeoChatRoom::setStatePowerLevel(const int &newPowerLevel) -{ - setPowerLevel("state_default"_ls, newPowerLevel); -} - -int NeoChatRoom::defaultEventPowerLevel() const -{ - return powerLevel("events_default"_ls); -} - -void NeoChatRoom::setDefaultEventPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("events_default"_ls, newPowerLevel); -} - -int NeoChatRoom::powerLevelPowerLevel() const -{ - return powerLevel("m.room.power_levels"_ls, true); -} - -void NeoChatRoom::setPowerLevelPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.power_levels"_ls, newPowerLevel, true); -} - -int NeoChatRoom::namePowerLevel() const -{ - return powerLevel("m.room.name"_ls, true); -} - -void NeoChatRoom::setNamePowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.name"_ls, newPowerLevel, true); -} - -int NeoChatRoom::avatarPowerLevel() const -{ - return powerLevel("m.room.avatar"_ls, true); -} - -void NeoChatRoom::setAvatarPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.avatar"_ls, newPowerLevel, true); -} - -int NeoChatRoom::canonicalAliasPowerLevel() const -{ - return powerLevel("m.room.canonical_alias"_ls, true); -} - -void NeoChatRoom::setCanonicalAliasPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.canonical_alias"_ls, newPowerLevel, true); -} - -int NeoChatRoom::topicPowerLevel() const -{ - return powerLevel("m.room.topic"_ls, true); -} - -void NeoChatRoom::setTopicPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.topic"_ls, newPowerLevel, true); -} - -int NeoChatRoom::encryptionPowerLevel() const -{ - return powerLevel("m.room.encryption"_ls, true); -} - -void NeoChatRoom::setEncryptionPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.encryption"_ls, newPowerLevel, true); -} - -int NeoChatRoom::historyVisibilityPowerLevel() const -{ - return powerLevel("m.room.history_visibility"_ls, true); -} - -void NeoChatRoom::setHistoryVisibilityPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.history_visibility"_ls, newPowerLevel, true); -} - -int NeoChatRoom::pinnedEventsPowerLevel() const -{ - return powerLevel("m.room.pinned_events"_ls, true); -} - -void NeoChatRoom::setPinnedEventsPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.pinned_events"_ls, newPowerLevel, true); -} - -int NeoChatRoom::tombstonePowerLevel() const -{ - return powerLevel("m.room.tombstone"_ls, true); -} - -void NeoChatRoom::setTombstonePowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.tombstone"_ls, newPowerLevel, true); -} - -int NeoChatRoom::serverAclPowerLevel() const -{ - return powerLevel("m.room.server_acl"_ls, true); -} - -void NeoChatRoom::setServerAclPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.room.server_acl"_ls, newPowerLevel, true); -} - -int NeoChatRoom::spaceChildPowerLevel() const -{ - return powerLevel("m.space.child"_ls, true); -} - -void NeoChatRoom::setSpaceChildPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.space.child"_ls, newPowerLevel, true); -} - -int NeoChatRoom::spaceParentPowerLevel() const -{ - return powerLevel("m.space.parent"_ls, true); -} - -void NeoChatRoom::setSpaceParentPowerLevel(const int &newPowerLevel) -{ - setPowerLevel("m.space.parent"_ls, newPowerLevel, true); -} - QCoro::Task NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason) { QStringList events; diff --git a/src/neochatroom.h b/src/neochatroom.h index 3f80775e3..b0e8625b1 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -212,101 +212,6 @@ class NeoChatRoom : public Quotient::Room */ Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged) - /** - * @brief The default power level in the room for new users. - */ - Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged) - - /** - * @brief The power level required to invite users to the room. - */ - Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged) - - /** - * @brief The power level required to kick users from the room. - */ - Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged) - - /** - * @brief The power level required to ban users from the room. - */ - Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged) - - /** - * @brief The power level required to delete other user messages. - */ - Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged) - - /** - * @brief The default power level for state events that are not explicitly specified. - */ - Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged) - - /** - * @brief The default power level for event that are not explicitly specified. - */ - Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged) - - /** - * @brief The power level required to change power levels for the room. - */ - Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged) - - /** - * @brief The power level required to change the room name. - */ - Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged) - - /** - * @brief The power level required to change the room avatar. - */ - Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged) - - /** - * @brief The power level required to change the room aliases. - */ - Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged) - - /** - * @brief The power level required to change the room topic. - */ - Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged) - - /** - * @brief The power level required to encrypt the room. - */ - Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged) - - /** - * @brief The power level required to change the room history visibility. - */ - Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged) - - /** - * @brief The power level required to pin events in the room. - */ - Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged) - - /** - * @brief The power level required to upgrade the room. - */ - Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged) - - /** - * @brief The power level required to set the room server access control list (ACL). - */ - Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged) - - /** - * @brief The power level required to add children to a space. - */ - Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged) - - /** - * @brief The power level required to set the room parent space. - */ - Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged) - /** * @brief The cache for the main chat bar in the room. */ @@ -683,66 +588,6 @@ public: Q_INVOKABLE void setUserPowerLevel(const QString &userID, const int &powerLevel); - [[nodiscard]] int powerLevel(const QString &eventName, const bool &isStateEvent = false) const; - void setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent = false); - - [[nodiscard]] int defaultUserPowerLevel() const; - void setDefaultUserPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int invitePowerLevel() const; - void setInvitePowerLevel(const int &newPowerLevel); - - [[nodiscard]] int kickPowerLevel() const; - void setKickPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int banPowerLevel() const; - void setBanPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int redactPowerLevel() const; - void setRedactPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int statePowerLevel() const; - void setStatePowerLevel(const int &newPowerLevel); - - [[nodiscard]] int defaultEventPowerLevel() const; - void setDefaultEventPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int powerLevelPowerLevel() const; - void setPowerLevelPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int namePowerLevel() const; - void setNamePowerLevel(const int &newPowerLevel); - - [[nodiscard]] int avatarPowerLevel() const; - void setAvatarPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int canonicalAliasPowerLevel() const; - void setCanonicalAliasPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int topicPowerLevel() const; - void setTopicPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int encryptionPowerLevel() const; - void setEncryptionPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int historyVisibilityPowerLevel() const; - void setHistoryVisibilityPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int pinnedEventsPowerLevel() const; - void setPinnedEventsPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int tombstonePowerLevel() const; - void setTombstonePowerLevel(const int &newPowerLevel); - - [[nodiscard]] int serverAclPowerLevel() const; - void setServerAclPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int spaceChildPowerLevel() const; - void setSpaceChildPowerLevel(const int &newPowerLevel); - - [[nodiscard]] int spaceParentPowerLevel() const; - void setSpaceParentPowerLevel(const int &newPowerLevel); - ChatBarCache *mainCache() const; ChatBarCache *editCache() const; @@ -858,25 +703,6 @@ Q_SIGNALS: void defaultUrlPreviewStateChanged(); void urlPreviewEnabledChanged(); void maxRoomVersionChanged(); - void defaultUserPowerLevelChanged(); - void invitePowerLevelChanged(); - void kickPowerLevelChanged(); - void banPowerLevelChanged(); - void redactPowerLevelChanged(); - void statePowerLevelChanged(); - void defaultEventPowerLevelChanged(); - void powerLevelPowerLevelChanged(); - void namePowerLevelChanged(); - void avatarPowerLevelChanged(); - void canonicalAliasPowerLevelChanged(); - void topicPowerLevelChanged(); - void encryptionPowerLevelChanged(); - void historyVisibilityPowerLevelChanged(); - void pinnedEventsPowerLevelChanged(); - void tombstonePowerLevelChanged(); - void serverAclPowerLevelChanged(); - void spaceChildPowerLevelChanged(); - void spaceParentPowerLevelChanged(); void replyLoaded(const QString &eventId, const QString &replyId); public Q_SLOTS: diff --git a/src/qml/PowerLevelDialog.qml b/src/qml/PowerLevelDialog.qml index 67792e10d..f37b7669a 100644 --- a/src/qml/PowerLevelDialog.qml +++ b/src/qml/PowerLevelDialog.qml @@ -28,32 +28,14 @@ Kirigami.Dialog { } } - FormCard.FormCard { + ColumnLayout { FormCard.FormComboBoxDelegate { id: powerLevelComboBox text: i18n("New power level") - model: ListModel { - id: powerLevelModel - } - textRole: "text" - valueRole: "powerLevel" - - // Done this way so we can have translated strings. - Component.onCompleted: { - powerLevelModel.append({ - "text": i18n("Member (0)"), - "powerLevel": 0 - }); - powerLevelModel.append({ - "text": i18n("Moderator (50)"), - "powerLevel": 50 - }); - powerLevelModel.append({ - "text": i18n("Admin (100)"), - "powerLevel": 100 - }); - } + model: PowerLevelModel {} + textRole: "name" + valueRole: "value" } } customFooterActions: [ diff --git a/src/settings/Permissions.qml b/src/settings/Permissions.qml index 40b892684..3c25d579e 100644 --- a/src/settings/Permissions.qml +++ b/src/settings/Permissions.qml @@ -25,8 +25,12 @@ FormCard.FormCardPage { room: root.room } - property ListModel powerLevelModel: ListModel { - id: powerLevelModel + readonly property PowerLevelModel powerLevelModel: PowerLevelModel { + showMute: false + } + + readonly property PermissionsModel permissionsModel: PermissionsModel { + room: root.room } FormCard.FormHeader { @@ -40,10 +44,16 @@ FormCard.FormCardPage { sortOrder: Qt.DescendingOrder filterRowCallback: function (source_row, source_parent) { let powerLevelRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), UserListModel.PowerLevelRole); - return powerLevelRole > 0; + return powerLevelRole != 0; } } delegate: FormCard.FormTextDelegate { + id: privilegedUserDelegate + required property string userId + required property string name + required property int powerLevel + required property string powerLevelString + text: name textItem.textFormat: Text.PlainText description: userId @@ -51,37 +61,23 @@ FormCard.FormCardPage { spacing: Kirigami.Units.largeSpacing QQC2.Label { id: powerLevelLabel - visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localUser.id) <= model.powerLevel && model.userId != room.localUser.id) - text: powerLevelString + visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localUser.id) <= privilegedUserDelegate.powerLevel && privilegedUserDelegate.userId != room.localUser.id) + text: privilegedUserDelegate.powerLevelString color: Kirigami.Theme.disabledTextColor } QQC2.ComboBox { focusPolicy: Qt.NoFocus // provided by parent - model: powerLevelModel - textRole: "text" - valueRole: "powerLevel" + model: PowerLevelModel {} + textRole: "name" + valueRole: "value" visible: !powerLevelLabel.visible Component.onCompleted: { - /** - * This is very silly but the only way to populate the model with - * translated strings. Done here because the model needs to be filled - * before the first delegate sets it's current index. - */ - if (powerLevelModel.count == 0) { - powerLevelModel.append({ - "text": i18n("Member (0)"), - "powerLevel": 0 - }); - powerLevelModel.append({ - "text": i18n("Moderator (50)"), - "powerLevel": 50 - }); - powerLevelModel.append({ - "text": i18n("Admin (100)"), - "powerLevel": 100 - }); + let index = indexOfValue(privilegedUserDelegate.powerLevel) + if (index === -1) { + displayText = privilegedUserDelegate.powerLevelString; + } else { + currentIndex = index; } - currentIndex = indexOfValue(powerLevel); } onActivated: { room.setUserPowerLevel(userId, currentValue); @@ -243,37 +239,36 @@ FormCard.FormCardPage { } FormCard.FormCard { visible: room.canSendState("m.room.power_levels") - FormCard.FormComboBoxDelegate { - text: i18n("Default user power level") - description: i18n("This is power level for all new users when joining the room") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.defaultUserPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.defaultUserPowerLevel = currentValue; + Repeater { + model: KSortFilterProxyModel { + sourceModel: root.permissionsModel + filterRowCallback: function (source_row, source_parent) { + return sourceModel.data(sourceModel.index(source_row, 0, source_parent), PermissionsModel.IsDefaultValueRole); + } } - } - FormCard.FormComboBoxDelegate { - text: i18n("Default power level to set the room state") - description: i18n("This is used for all state events that do not have their own entry here") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.statePowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.statePowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Default power level to send messages") - description: i18n("This is used for all message events that do not have their own entry here") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.defaultEventPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.defaultEventPowerLevel = currentValue; + delegate: FormCard.FormComboBoxDelegate { + required property string name + required property string subtitle + required property string type + required property int level + required property string levelName + + text: name + description: subtitle + textRole: "name" + valueRole: "value" + model: root.powerLevelModel + Component.onCompleted: { + let index = indexOfValue(level) + if (index === -1) { + displayText = levelName; + } else { + currentIndex = index; + } + } + onCurrentValueChanged: if (root.room.canSendState("m.room.power_levels")) { + root.permissionsModel.setPowerLevel(type, currentValue); + } } } } @@ -284,44 +279,36 @@ FormCard.FormCardPage { } FormCard.FormCard { visible: room.canSendState("m.room.power_levels") - FormCard.FormComboBoxDelegate { - text: i18n("Invite users") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.invitePowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.invitePowerLevel = currentValue; + Repeater { + model: KSortFilterProxyModel { + sourceModel: root.permissionsModel + filterRowCallback: function (source_row, source_parent) { + return sourceModel.data(sourceModel.index(source_row, 0, source_parent), PermissionsModel.IsBasicPermissionRole); + } } - } - FormCard.FormComboBoxDelegate { - text: i18n("Kick users") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.kickPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.kickPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Ban users") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.banPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.banPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Remove message sent by other users") - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.redactPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.redactPowerLevel = currentValue; + delegate: FormCard.FormComboBoxDelegate { + required property string name + required property string subtitle + required property string type + required property int level + required property string levelName + + text: name + description: subtitle + textRole: "name" + valueRole: "value" + model: root.powerLevelModel + Component.onCompleted: { + let index = indexOfValue(level) + if (index === -1) { + displayText = levelName; + } else { + currentIndex = index; + } + } + onCurrentValueChanged: if (root.room.canSendState("m.room.power_levels")) { + root.permissionsModel.setPowerLevel(type, currentValue); + } } } } @@ -332,137 +319,91 @@ FormCard.FormCardPage { } FormCard.FormCard { visible: room.canSendState("m.room.power_levels") - FormCard.FormComboBoxDelegate { - text: i18n("Change user permissions") - description: "m.room.power_levels" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.powerLevelPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.powerLevelPowerLevel = currentValue; + Repeater { + model: KSortFilterProxyModel { + sourceModel: root.permissionsModel + filterRowCallback: function (source_row, source_parent) { + let isBasicPermissionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PermissionsModel.IsBasicPermissionRole); + let isDefaultValueRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PermissionsModel.IsDefaultValueRole); + return !isBasicPermissionRole && !isDefaultValueRole; + } + } + delegate: FormCard.FormComboBoxDelegate { + required property string name + required property string subtitle + required property string type + required property int level + required property string levelName + + text: name + description: subtitle + textRole: "name" + valueRole: "value" + model: root.powerLevelModel + Component.onCompleted: { + let index = indexOfValue(level) + if (index === -1) { + displayText = levelName; + } else { + currentIndex = index; + } + } + onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { + root.permissionsModel.setPowerLevel(type, currentValue); + } } } - FormCard.FormComboBoxDelegate { - text: i18n("Change the room name") - description: "m.room.name" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.namePowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.namePowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Change the room avatar") - description: "m.room.avatar" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.avatarPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.avatarPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Change the room canonical alias") - description: "m.room.canonical_alias" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.canonicalAliasPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.canonicalAliasPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Change the room topic") - description: "m.room.topic" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.topicPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.topicPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Enable encryption for the room") - description: "m.room.encryption" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.encryptionPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.encryptionPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Change the room history visibility") - description: "m.room.history_visibility" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.historyVisibilityPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.historyVisibilityPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Set pinned events") - description: "m.room.pinned_events" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.pinnedEventsPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.pinnedEventsPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Upgrade the room") - description: "m.room.tombstone" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.tombstonePowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.tombstonePowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Set the room server access control list (ACL)") - description: "m.room.server_acl" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.serverAclPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.serverAclPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - visible: room.isSpace - text: i18n("Set the children of this space") - description: "m.space.child" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.spaceChildPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.spaceChildPowerLevel = currentValue; - } - } - FormCard.FormComboBoxDelegate { - text: i18n("Set the parent space of this room") - description: "m.space.parent" - textRole: "text" - valueRole: "powerLevel" - model: powerLevelModel - Component.onCompleted: currentIndex = indexOfValue(room.spaceChildPowerLevel) - onCurrentValueChanged: if (room.canSendState("m.room.power_levels")) { - room.spaceParentPowerLevel = currentValue; + FormCard.AbstractFormDelegate { + Layout.fillWidth: true + + contentItem: RowLayout { + Kirigami.ActionTextField { + id: newEventAddField + + Layout.fillWidth: true + + placeholderText: i18n("Event Type…") + enabled: NotificationsManager.keywordNotificationAction !== PushRuleAction.Unknown + + rightActions: Kirigami.Action { + icon.name: "edit-clear" + visible: newEventAddField.text.length > 0 + onTriggered: { + newEventAddField.text = ""; + } + } + + onAccepted: { + root.permissionsModel.setPowerLevel(newEventAddField.text, newEventPowerLevel.currentValue); + newEventAddField.text = ""; + } + } + QQC2.ComboBox { + id: newEventPowerLevel + focusPolicy: Qt.NoFocus // provided by parent + model: root.powerLevelModel + textRole: "name" + valueRole: "value" + } + QQC2.Button { + id: addButton + + text: i18n("Add keyword") + Accessible.name: text + icon.name: "list-add" + display: QQC2.AbstractButton.IconOnly + enabled: newEventAddField.text.length > 0 + + onClicked: { + root.permissionsModel.setPowerLevel(newEventAddField.text, newEventPowerLevel.currentValue); + newEventAddField.text = ""; + } + + QQC2.ToolTip { + text: addButton.text + delay: Kirigami.Units.toolTipDelay + } + } } } }