From dc4a06517c775a4d0ea01048e9218bfb64c5ae65 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 26 Jun 2022 00:09:00 +0200 Subject: [PATCH] Add an option to define new power levels --- resources/qml/dialogs/PowerLevelEditor.qml | 302 ++++++++++++--------- src/PowerlevelsEditModels.cpp | 77 +++++- src/PowerlevelsEditModels.h | 23 +- 3 files changed, 273 insertions(+), 129 deletions(-) diff --git a/resources/qml/dialogs/PowerLevelEditor.qml b/resources/qml/dialogs/PowerLevelEditor.qml index 241585f9..d757f0af 100644 --- a/resources/qml/dialogs/PowerLevelEditor.qml +++ b/resources/qml/dialogs/PowerLevelEditor.qml @@ -20,13 +20,14 @@ ApplicationWindow { flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint minimumWidth: 300 minimumHeight: 400 + height: 600 title: qsTr("Permissions in %1").arg(roomSettings.roomName); -// Shortcut { -// sequence: StandardKey.Cancel -// onActivated: dbb.rejected() -// } + // Shortcut { + // sequence: StandardKey.Cancel + // onActivated: dbb.rejected() + // } ColumnLayout { anchors.margins: Nheko.paddingMedium @@ -117,6 +118,8 @@ ApplicationWindow { return qsTr("Administrator (%1)").arg(model.powerlevel) else if (editingModel.moderatorLevel == model.powerlevel) return qsTr("Moderator (%1)").arg(model.powerlevel) + else if (editingModel.defaultUserLevel == model.powerlevel) + return qsTr("User (%1)").arg(model.powerlevel) else return qsTr("Custom (%1)").arg(model.powerlevel) } @@ -144,34 +147,85 @@ ApplicationWindow { } } } - MatrixTextField { - id: typeEntry + MatrixTextField { + id: typeEntry - property int index + property int index - width: parent.width - z: 5 - visible: false + width: parent.width + z: 5 + visible: false - color: Nheko.colors.text + color: Nheko.colors.text - Keys.onPressed: { - if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { - editingModel.types.add(typeEntry.index, typeEntry.text) - typeEntry.visible = false; - typeEntry.clear(); - event.accepted = true; + Keys.onPressed: { + if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { + editingModel.types.add(typeEntry.index, typeEntry.text) + typeEntry.visible = false; + typeEntry.clear(); + event.accepted = true; + } + else if (event.matches(StandardKey.Cancel)) { + typeEntry.visible = false; + typeEntry.clear(); + event.accepted = true; + } + } + } + } + + Button { + Layout.fillWidth: true + text: qsTr("Add new role") + + onClicked: newPLLay.visible = true + + Rectangle { + id: newPLLay + + anchors.fill: parent + visible: false + color: Nheko.colors.alternateBase + + RowLayout { + spacing: Nheko.paddingMedium + anchors.fill: parent + + SpinBox { + id: newPLVal + + Layout.fillWidth: true + Layout.fillHeight: true + + editable: true + //from: -9007199254740991 + //to: 9007199254740991 + + // max qml values + from: -2000000000 + to: 2000000000 + + Keys.onPressed: { + if (event.matches(StandardKey.InsertParagraphSeparator)) { + editingModel.addRole(newPLVal.value); + newPLLay.visible = false; + } } - else if (event.matches(StandardKey.Cancel)) { - typeEntry.visible = false; - typeEntry.clear(); - event.accepted = true; + } + + Button { + text: qsTr("Add") + Layout.preferredWidth: 100 + onClicked: { + editingModel.addRole(newPLVal.value); + newPLLay.visible = false; } } } + } } - } + ColumnLayout { spacing: Nheko.paddingMedium @@ -188,16 +242,16 @@ ApplicationWindow { model: editingModel.users - Column{ - id: userEntryCompleter + Column{ + id: userEntryCompleter - property int index: 0 + property int index: 0 - visible: false + visible: false - width: parent.width - spacing: 1 - z: 5 + width: parent.width + spacing: 1 + z: 5 MatrixTextField { id: userEntry @@ -229,119 +283,119 @@ ApplicationWindow { } - Completer { - id: userCompleter + Completer { + id: userCompleter - visible: userEntry.text.length > 0 - width: parent.width - roomId: plEditorW.roomSettings.roomId - completerName: "user" - bottomToTop: false - fullWidth: true - avatarHeight: Nheko.avatarSize / 2 - avatarWidth: Nheko.avatarSize / 2 - centerRowContent: false - rowMargin: 2 - rowSpacing: 2 - } + visible: userEntry.text.length > 0 + width: parent.width + roomId: plEditorW.roomSettings.roomId + completerName: "user" + bottomToTop: false + fullWidth: true + avatarHeight: Nheko.avatarSize / 2 + avatarWidth: Nheko.avatarSize / 2 + centerRowContent: false + rowMargin: 2 + rowSpacing: 2 + } + } + + Connections { + function onCompletionSelected(id) { + console.log("selected: " + id); + editingModel.users.add(userEntryCompleter.index, id); + userEntry.clear(); + userEntryCompleter.visible = false; } - Connections { - function onCompletionSelected(id) { - console.log("selected: " + id); - editingModel.users.add(userEntryCompleter.index, id); - userEntry.clear(); - userEntryCompleter.visible = false; - } + function onCountChanged() { + if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count)) + userCompleter.currentIndex = 0; - function onCountChanged() { - if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count)) - userCompleter.currentIndex = 0; - - } - - target: userCompleter } - delegate: RowLayout { - //anchors { fill: parent; margins: 2 } - id: row + target: userCompleter + } - Avatar { - id: avatar + delegate: RowLayout { + //anchors { fill: parent; margins: 2 } + id: row - Layout.preferredHeight: Nheko.avatarSize / 2 - Layout.preferredWidth: Nheko.avatarSize / 2 - Layout.leftMargin: 2 - userid: model.mxid - url: { - if (model.isUser) - return model.avatarUrl.replace("mxc://", "image://MxcImage/") - else if (editingModel.adminLevel >= model.powerlevel) - return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + Nheko.colors.buttonText; - else if (editingModel.moderatorLevel >= model.powerlevel) - return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + Nheko.colors.buttonText; + Avatar { + id: avatar + + Layout.preferredHeight: Nheko.avatarSize / 2 + Layout.preferredWidth: Nheko.avatarSize / 2 + Layout.leftMargin: 2 + userid: model.mxid + url: { + if (model.isUser) + return model.avatarUrl.replace("mxc://", "image://MxcImage/") + else if (editingModel.adminLevel >= model.powerlevel) + return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + Nheko.colors.buttonText; + else if (editingModel.moderatorLevel >= model.powerlevel) + return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + Nheko.colors.buttonText; + else + return "image://colorimage/:/icons/icons/ui/person.svg?" + Nheko.colors.buttonText; + } + displayName: model.displayName + enabled: false + } + Column { + Layout.fillWidth: true + + Text { visible: model.isUser; text: model.displayName; color: Nheko.colors.text} + Text { visible: model.isUser; text: model.mxid; color: Nheko.colors.text} + Text { + visible: !model.isUser; + text: { + if (editingModel.adminLevel == model.powerlevel) + return qsTr("Administrator (%1)").arg(model.powerlevel) + else if (editingModel.moderatorLevel == model.powerlevel) + return qsTr("Moderator (%1)").arg(model.powerlevel) else - return "image://colorimage/:/icons/icons/ui/person.svg?" + Nheko.colors.buttonText; + return qsTr("Custom (%1)").arg(model.powerlevel) } - displayName: model.displayName - enabled: false + color: Nheko.colors.text } - Column { - Layout.fillWidth: true + } - Text { visible: model.isUser; text: model.displayName; color: Nheko.colors.text} - Text { visible: model.isUser; text: model.mxid; color: Nheko.colors.text} - Text { - visible: !model.isUser; - text: { - if (editingModel.adminLevel == model.powerlevel) - return qsTr("Administrator (%1)").arg(model.powerlevel) - else if (editingModel.moderatorLevel == model.powerlevel) - return qsTr("Moderator (%1)").arg(model.powerlevel) - else - return qsTr("Custom (%1)").arg(model.powerlevel) - } - color: Nheko.colors.text - } - } - - ImageButton { - Layout.alignment: Qt.AlignRight - Layout.rightMargin: 2 - image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" - visible: !model.isUser || model.removeable - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user") - onClicked: { - if (model.isUser) { - editingModel.users.remove(index); - } else { - userEntryCompleter.y = offset - userEntryCompleter.visible = true - userEntryCompleter.index = index; - userEntry.forceActiveFocus() - } + ImageButton { + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 2 + image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" + visible: !model.isUser || model.removeable + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user") + onClicked: { + if (model.isUser) { + editingModel.users.remove(index); + } else { + userEntryCompleter.y = offset + userEntryCompleter.visible = true + userEntryCompleter.index = index; + userEntry.forceActiveFocus() } } } } - } + } } } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - editingModel.commit(); - plEditorW.close(); - } - onRejected: plEditorW.close(); - } - } + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { + editingModel.commit(); + plEditorW.close(); + } + onRejected: plEditorW.close(); + } + +} diff --git a/src/PowerlevelsEditModels.cpp b/src/PowerlevelsEditModels.cpp index 77262a20..cea35495 100644 --- a/src/PowerlevelsEditModels.cpp +++ b/src/PowerlevelsEditModels.cpp @@ -239,6 +239,7 @@ PowerlevelsTypeListModel::remove(int row) return true; } + void PowerlevelsTypeListModel::add(int row, QString type) { @@ -261,6 +262,23 @@ PowerlevelsTypeListModel::add(int row, QString type) endInsertRows(); } +void +PowerlevelsTypeListModel::addRole(int64_t role) +{ + for (int i = 0; i < types.size(); i++) { + if (types[i].pl < role) { + beginInsertRows(QModelIndex(), i, i); + types.insert(i, Entry{"", role}); + endInsertRows(); + return; + } + } + + beginInsertRows(QModelIndex(), types.size(), types.size()); + types.push_back(Entry{"", role}); + endInsertRows(); +} + bool PowerlevelsTypeListModel::move(int from, int to) { @@ -299,10 +317,19 @@ PowerlevelsTypeListModel::moveRows(const QModelIndex &, auto pl = types.at(destinationChild > 0 ? destinationChild - 1 : 0).pl; auto sourceItem = types.takeAt(sourceRow); sourceItem.pl = pl; + + auto movedType = sourceItem.type; + if (destinationChild < sourceRow) types.insert(destinationChild, std::move(sourceItem)); else types.insert(destinationChild - 1, std::move(sourceItem)); + + if (movedType == "m.room.power_levels") + emit adminLevelChanged(); + else if (movedType == "redact") + emit moderatorLevelChanged(); + return true; } @@ -454,6 +481,23 @@ PowerlevelsUserListModel::add(int row, QString user) endInsertRows(); } +void +PowerlevelsUserListModel::addRole(int64_t role) +{ + for (int i = 0; i < users.size(); i++) { + if (users[i].pl < role) { + beginInsertRows(QModelIndex(), i, i); + users.insert(i, Entry{"", role}); + endInsertRows(); + return; + } + } + + beginInsertRows(QModelIndex(), users.size(), users.size()); + users.push_back(Entry{"", role}); + endInsertRows(); +} + bool PowerlevelsUserListModel::move(int from, int to) { @@ -492,10 +536,17 @@ PowerlevelsUserListModel::moveRows(const QModelIndex &, auto pl = users.at(destinationChild > 0 ? destinationChild - 1 : 0).pl; auto sourceItem = users.takeAt(sourceRow); sourceItem.pl = pl; + + auto movedType = sourceItem.mxid; + if (destinationChild < sourceRow) users.insert(destinationChild, std::move(sourceItem)); else users.insert(destinationChild - 1, std::move(sourceItem)); + + if (movedType == "default") + emit defaultUserLevelChanged(); + return true; } @@ -508,7 +559,20 @@ PowerlevelEditingModels::PowerlevelEditingModels(QString room_id, QObject *paren , types_(room_id.toStdString(), powerLevels_, this) , users_(room_id.toStdString(), powerLevels_, this) , room_id_(room_id.toStdString()) -{} +{ + connect(&types_, + &PowerlevelsTypeListModel::adminLevelChanged, + this, + &PowerlevelEditingModels::adminLevelChanged); + connect(&types_, + &PowerlevelsTypeListModel::moderatorLevelChanged, + this, + &PowerlevelEditingModels::moderatorLevelChanged); + connect(&users_, + &PowerlevelsUserListModel::defaultUserLevelChanged, + this, + &PowerlevelEditingModels::defaultUserLevelChanged); +} void PowerlevelEditingModels::commit() @@ -532,3 +596,14 @@ PowerlevelEditingModels::commit() } }); } + +void +PowerlevelEditingModels::addRole(int pl) +{ + for (const auto &e : types_.types) + if (pl == int(e.pl)) + return; + + types_.addRole(pl); + users_.addRole(pl); +} diff --git a/src/PowerlevelsEditModels.h b/src/PowerlevelsEditModels.h index 25ad6d5a..9aa955d2 100644 --- a/src/PowerlevelsEditModels.h +++ b/src/PowerlevelsEditModels.h @@ -15,6 +15,10 @@ class PowerlevelsTypeListModel : public QAbstractListModel { Q_OBJECT +signals: + void adminLevelChanged(); + void moderatorLevelChanged(); + public: enum Roles { @@ -36,6 +40,7 @@ public: Q_INVOKABLE bool remove(int row); Q_INVOKABLE bool move(int from, int to); Q_INVOKABLE void add(int index, QString type); + void addRole(int64_t role); bool moveRows(const QModelIndex &sourceParent, int sourceRow, @@ -50,7 +55,6 @@ public: mtx::events::state::power_level_t eventsDefault(); mtx::events::state::power_level_t stateDefault(); -private: struct Entry { ~Entry() = default; @@ -68,6 +72,9 @@ class PowerlevelsUserListModel : public QAbstractListModel { Q_OBJECT +signals: + void defaultUserLevelChanged(); + public: enum Roles { @@ -91,6 +98,7 @@ public: Q_INVOKABLE bool remove(int row); Q_INVOKABLE bool move(int from, int to); Q_INVOKABLE void add(int index, QString user); + void addRole(int64_t role); bool moveRows(const QModelIndex &sourceParent, int sourceRow, @@ -101,7 +109,6 @@ public: std::map> toUsers(); mtx::events::state::power_level_t usersDefault(); -private: struct Entry { ~Entry() = default; @@ -121,8 +128,14 @@ class PowerlevelEditingModels : public QObject Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT) Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT) - Q_PROPERTY(qlonglong adminLevel READ adminLevel CONSTANT) - Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel CONSTANT) + Q_PROPERTY(qlonglong adminLevel READ adminLevel NOTIFY adminLevelChanged) + Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel NOTIFY moderatorLevelChanged) + Q_PROPERTY(qlonglong defaultUserLevel READ defaultUserLevel NOTIFY defaultUserLevelChanged) + +signals: + void adminLevelChanged(); + void moderatorLevelChanged(); + void defaultUserLevelChanged(); public: explicit PowerlevelEditingModels(QString room_id, QObject *parent = nullptr); @@ -134,8 +147,10 @@ public: return powerLevels_.state_level(to_string(mtx::events::EventType::RoomPowerLevels)); } qlonglong moderatorLevel() const { return powerLevels_.redact; } + qlonglong defaultUserLevel() const { return powerLevels_.users_default; } Q_INVOKABLE void commit(); + Q_INVOKABLE void addRole(int pl); mtx::events::state::PowerLevels powerLevels_; PowerlevelsTypeListModel types_;