mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Don't use a modal to edit room name and topic
This commit is contained in:
parent
82cdb483a9
commit
656fcac91c
8 changed files with 187 additions and 868 deletions
|
@ -338,7 +338,6 @@ set(SRC_FILES
|
|||
src/ui/NhekoDropArea.cpp
|
||||
src/ui/NhekoGlobalObject.cpp
|
||||
src/ui/RoomSettings.cpp
|
||||
src/ui/TextField.cpp
|
||||
src/ui/Theme.cpp
|
||||
src/ui/ThemeManager.cpp
|
||||
src/ui/UIA.cpp
|
||||
|
@ -532,7 +531,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
src/ui/NhekoDropArea.h
|
||||
src/ui/NhekoGlobalObject.h
|
||||
src/ui/RoomSettings.h
|
||||
src/ui/TextField.h
|
||||
src/ui/Theme.h
|
||||
src/ui/ThemeManager.h
|
||||
src/ui/UIA.h
|
||||
|
|
|
@ -107,17 +107,58 @@ ApplicationWindow {
|
|||
hideErrorAnimation.restart();
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: roomSettings.roomName
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pixelSize: fontMetrics.font.pixelSize * 2
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
color: Nheko.colors.text
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: Text.RichText
|
||||
|
||||
TextEdit {
|
||||
id: roomName
|
||||
|
||||
property bool isNameEditingAllowed: false
|
||||
|
||||
readOnly: !isNameEditingAllowed
|
||||
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
|
||||
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
|
||||
font.pixelSize: fontMetrics.font.pixelSize * 2
|
||||
color: Nheko.colors.text
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
selectByMouse: true
|
||||
|
||||
Keys.onShortcutOverride: event.key === Qt.Key_Enter
|
||||
Keys.onPressed: {
|
||||
if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
roomSettings.changeName(roomName.text);
|
||||
roomName.isNameEditingAllowed = false;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: nameChangeButton
|
||||
visible: roomSettings.canChangeName
|
||||
anchors.leftMargin: Nheko.paddingSmall
|
||||
anchors.left: roomName.right
|
||||
anchors.verticalCenter: roomName.verticalCenter
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Change name of this room")
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
|
||||
onClicked: {
|
||||
if (roomName.isNameEditingAllowed) {
|
||||
roomSettings.changeName(roomName.text);
|
||||
roomName.isNameEditingAllowed = false;
|
||||
} else {
|
||||
roomName.isNameEditingAllowed = true;
|
||||
roomName.focus = true;
|
||||
roomName.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("%n member(s)", "", roomSettings.memberCount)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
@ -134,17 +175,10 @@ ApplicationWindow {
|
|||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
image: ":/icons/icons/ui/edit.svg"
|
||||
visible: roomSettings.canChangeNameAndTopic
|
||||
onClicked: roomSettings.openEditModal()
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: roomTopic
|
||||
property bool cut: implicitHeight > 100
|
||||
property bool showMore
|
||||
property bool showMore: false
|
||||
clip: true
|
||||
Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
@ -153,10 +187,12 @@ ApplicationWindow {
|
|||
Layout.leftMargin: Nheko.paddingLarge
|
||||
Layout.rightMargin: Nheko.paddingLarge
|
||||
|
||||
text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
|
||||
property bool isTopicEditingAllowed: false
|
||||
|
||||
readOnly: !isTopicEditingAllowed
|
||||
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
|
||||
text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic
|
||||
wrapMode: TextEdit.WordWrap
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true
|
||||
background: null
|
||||
selectByMouse: !Settings.mobileMode
|
||||
color: Nheko.colors.text
|
||||
|
@ -169,6 +205,29 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: topicChangeButton
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: roomSettings.canChangeTopic
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Change topic of this room")
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
|
||||
onClicked: {
|
||||
if (roomTopic.isTopicEditingAllowed) {
|
||||
roomSettings.changeTopic(roomTopic.text);
|
||||
roomTopic.isTopicEditingAllowed = false;
|
||||
} else {
|
||||
roomTopic.isTopicEditingAllowed = true;
|
||||
roomTopic.showMore = true;
|
||||
roomTopic.focus = true;
|
||||
//roomTopic.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
id: showMorePlaceholder
|
||||
|
|
|
@ -5,13 +5,10 @@
|
|||
|
||||
#include "RoomSettings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QImageReader>
|
||||
#include <QMimeDatabase>
|
||||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
#include <mtx/events/event_type.hpp>
|
||||
#include <mtx/responses/common.hpp>
|
||||
#include <mtx/responses/media.hpp>
|
||||
|
@ -23,153 +20,9 @@
|
|||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Utils.h"
|
||||
#include "ui/TextField.h"
|
||||
|
||||
using namespace mtx::events;
|
||||
|
||||
EditModal::EditModal(const QString &roomId, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, roomId_{roomId}
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
|
||||
QFont largeFont;
|
||||
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4);
|
||||
setMinimumWidth(conf::window::minModalWidth);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
applyBtn_ = new QPushButton(tr("Apply"), this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
cancelBtn_->setDefault(true);
|
||||
|
||||
auto btnLayout = new QHBoxLayout;
|
||||
btnLayout->addStretch(1);
|
||||
btnLayout->setSpacing(15);
|
||||
btnLayout->addWidget(cancelBtn_);
|
||||
btnLayout->addWidget(applyBtn_);
|
||||
|
||||
nameInput_ = new TextField(this);
|
||||
nameInput_->setLabel(tr("Name").toUpper());
|
||||
topicInput_ = new TextField(this);
|
||||
topicInput_->setLabel(tr("Topic").toUpper());
|
||||
|
||||
errorField_ = new QLabel(this);
|
||||
errorField_->setWordWrap(true);
|
||||
errorField_->hide();
|
||||
|
||||
layout->addWidget(nameInput_);
|
||||
layout->addWidget(topicInput_);
|
||||
layout->addLayout(btnLayout, 1);
|
||||
|
||||
auto labelLayout = new QHBoxLayout;
|
||||
labelLayout->setAlignment(Qt::AlignHCenter);
|
||||
labelLayout->addWidget(errorField_);
|
||||
layout->addLayout(labelLayout);
|
||||
|
||||
connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked);
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
|
||||
|
||||
auto window = QApplication::activeWindow();
|
||||
|
||||
if (window != nullptr) {
|
||||
auto center = window->frameGeometry().center();
|
||||
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
EditModal::topicEventSent(const QString &topic)
|
||||
{
|
||||
errorField_->hide();
|
||||
emit topicChanged(topic);
|
||||
close();
|
||||
}
|
||||
|
||||
void
|
||||
EditModal::nameEventSent(const QString &name)
|
||||
{
|
||||
errorField_->hide();
|
||||
emit nameChanged(name);
|
||||
close();
|
||||
}
|
||||
|
||||
void
|
||||
EditModal::error(const QString &msg)
|
||||
{
|
||||
errorField_->setText(msg);
|
||||
errorField_->show();
|
||||
}
|
||||
|
||||
void
|
||||
EditModal::applyClicked()
|
||||
{
|
||||
// Check if the values are changed from the originals.
|
||||
auto newName = nameInput_->text().trimmed();
|
||||
auto newTopic = topicInput_->text().trimmed();
|
||||
|
||||
errorField_->hide();
|
||||
|
||||
if (newName == initialName_ && newTopic == initialTopic_) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace mtx::events;
|
||||
auto proxy = std::make_shared<ThreadProxy>();
|
||||
connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent);
|
||||
connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent);
|
||||
connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error);
|
||||
|
||||
if (newName != initialName_ && !newName.isEmpty()) {
|
||||
state::Name body;
|
||||
body.name = newName.toStdString();
|
||||
|
||||
http::client()->send_state_event(
|
||||
roomId_.toStdString(),
|
||||
body,
|
||||
[proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit proxy->error(QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->nameEventSent(newName);
|
||||
});
|
||||
}
|
||||
|
||||
if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
|
||||
state::Topic body;
|
||||
body.topic = newTopic.toStdString();
|
||||
|
||||
http::client()->send_state_event(
|
||||
roomId_.toStdString(),
|
||||
body,
|
||||
[proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit proxy->error(QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->topicEventSent(newTopic);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
EditModal::setFields(const QString &roomName, const QString &roomTopic)
|
||||
{
|
||||
initialName_ = roomName;
|
||||
initialTopic_ = roomTopic;
|
||||
|
||||
nameInput_->setText(roomName);
|
||||
topicInput_->setText(roomTopic);
|
||||
}
|
||||
|
||||
RoomSettings::RoomSettings(QString roomid, QObject *parent)
|
||||
: QObject(parent)
|
||||
, roomid_{std::move(roomid)}
|
||||
|
@ -244,6 +97,18 @@ RoomSettings::roomTopic() const
|
|||
.replace(QLatin1String("\n"), QLatin1String("<br>"))));
|
||||
}
|
||||
|
||||
QString
|
||||
RoomSettings::plainRoomName() const
|
||||
{
|
||||
return QString::fromStdString(info_.name);
|
||||
}
|
||||
|
||||
QString
|
||||
RoomSettings::plainRoomTopic() const
|
||||
{
|
||||
return QString::fromStdString(info_.topic);
|
||||
}
|
||||
|
||||
QString
|
||||
RoomSettings::roomId() const
|
||||
{
|
||||
|
@ -340,12 +205,24 @@ RoomSettings::canChangeJoinRules() const
|
|||
}
|
||||
|
||||
bool
|
||||
RoomSettings::canChangeNameAndTopic() const
|
||||
RoomSettings::canChangeName() const
|
||||
{
|
||||
try {
|
||||
return cache::hasEnoughPowerLevel({EventType::RoomName, EventType::RoomTopic},
|
||||
roomid_.toStdString(),
|
||||
utils::localUser().toStdString());
|
||||
return cache::hasEnoughPowerLevel(
|
||||
{EventType::RoomName}, roomid_.toStdString(), utils::localUser().toStdString());
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->warn("lmdb error: {}", e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
RoomSettings::canChangeTopic() const
|
||||
{
|
||||
try {
|
||||
return cache::hasEnoughPowerLevel(
|
||||
{EventType::RoomTopic}, roomid_.toStdString(), utils::localUser().toStdString());
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->warn("lmdb error: {}", e.what());
|
||||
}
|
||||
|
@ -387,26 +264,6 @@ RoomSettings::supportsRestricted() const
|
|||
info_.version != "6" && info_.version != "7";
|
||||
}
|
||||
|
||||
void
|
||||
RoomSettings::openEditModal()
|
||||
{
|
||||
retrieveRoomInfo();
|
||||
|
||||
auto modal = new EditModal(roomid_);
|
||||
modal->setFields(QString::fromStdString(info_.name), QString::fromStdString(info_.topic));
|
||||
modal->raise();
|
||||
modal->show();
|
||||
connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) {
|
||||
info_.name = newName.toStdString();
|
||||
emit roomNameChanged();
|
||||
});
|
||||
|
||||
connect(modal, &EditModal::topicChanged, this, [this](const QString &newTopic) {
|
||||
info_.topic = newTopic.toStdString();
|
||||
emit roomTopicChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RoomSettings::changeNotifications(int currentIndex)
|
||||
{
|
||||
|
@ -502,6 +359,74 @@ RoomSettings::changeAccessRules(int index)
|
|||
updateAccessRules(roomid_.toStdString(), join_rule, guest_access);
|
||||
}
|
||||
|
||||
void
|
||||
RoomSettings::changeName(QString name)
|
||||
{
|
||||
// Check if the values are changed from the originals.
|
||||
auto newName = name.trimmed().toStdString();
|
||||
|
||||
if (newName == info_.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace mtx::events;
|
||||
auto proxy = std::make_shared<ThreadProxy>();
|
||||
connect(proxy.get(), &ThreadProxy::nameEventSent, this, [this](QString newRoomName) {
|
||||
this->info_.name = newRoomName.toStdString();
|
||||
emit roomNameChanged();
|
||||
});
|
||||
connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
|
||||
|
||||
state::Name body;
|
||||
body.name = newName;
|
||||
|
||||
http::client()->send_state_event(
|
||||
roomid_.toStdString(),
|
||||
body,
|
||||
[proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit proxy->error(QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->nameEventSent(QString::fromStdString(newName));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RoomSettings::changeTopic(QString topic)
|
||||
{
|
||||
// Check if the values are changed from the originals.
|
||||
auto newTopic = topic.trimmed().toStdString();
|
||||
|
||||
if (newTopic == info_.topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace mtx::events;
|
||||
auto proxy = std::make_shared<ThreadProxy>();
|
||||
connect(proxy.get(), &ThreadProxy::topicEventSent, this, [this](QString newRoomTopic) {
|
||||
this->info_.topic = newRoomTopic.toStdString();
|
||||
emit roomTopicChanged();
|
||||
});
|
||||
connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
|
||||
|
||||
state::Topic body;
|
||||
body.topic = newTopic;
|
||||
|
||||
http::client()->send_state_event(
|
||||
roomid_.toStdString(),
|
||||
body,
|
||||
[proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit proxy->error(QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->topicEventSent(QString::fromStdString(newTopic));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RoomSettings::updateAccessRules(const std::string &room_id,
|
||||
const mtx::events::state::JoinRules &join_rule,
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QObject>
|
||||
#include <QPushButton>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
|
@ -16,8 +14,6 @@
|
|||
|
||||
#include "CacheStructs.h"
|
||||
|
||||
class TextField;
|
||||
|
||||
/// Convenience class which connects events emmited from threads
|
||||
/// outside of main with the UI code.
|
||||
class ThreadProxy : public QObject
|
||||
|
@ -31,40 +27,6 @@ signals:
|
|||
void stopLoading();
|
||||
};
|
||||
|
||||
class EditModal : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EditModal(const QString &roomId, QWidget *parent = nullptr);
|
||||
|
||||
void setFields(const QString &roomName, const QString &roomTopic);
|
||||
|
||||
signals:
|
||||
void nameChanged(const QString &roomName);
|
||||
void topicChanged(const QString &topic);
|
||||
|
||||
private slots:
|
||||
void topicEventSent(const QString &topic);
|
||||
void nameEventSent(const QString &name);
|
||||
void error(const QString &msg);
|
||||
|
||||
void applyClicked();
|
||||
|
||||
private:
|
||||
QString roomId_;
|
||||
QString initialName_;
|
||||
QString initialTopic_;
|
||||
|
||||
QLabel *errorField_;
|
||||
|
||||
TextField *nameInput_;
|
||||
TextField *topicInput_;
|
||||
|
||||
QPushButton *applyBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
};
|
||||
|
||||
class RoomSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -72,6 +34,8 @@ class RoomSettings : public QObject
|
|||
Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT)
|
||||
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
||||
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
|
||||
Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY roomNameChanged)
|
||||
Q_PROPERTY(QString plainRoomTopic READ plainRoomTopic NOTIFY roomTopicChanged)
|
||||
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged)
|
||||
Q_PROPERTY(int memberCount READ memberCount CONSTANT)
|
||||
Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged)
|
||||
|
@ -79,7 +43,8 @@ class RoomSettings : public QObject
|
|||
Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
|
||||
Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT)
|
||||
Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT)
|
||||
Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT)
|
||||
Q_PROPERTY(bool canChangeName READ canChangeName CONSTANT)
|
||||
Q_PROPERTY(bool canChangeTopic READ canChangeTopic CONSTANT)
|
||||
Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged)
|
||||
Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT)
|
||||
Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT)
|
||||
|
@ -90,6 +55,8 @@ public:
|
|||
QString roomId() const;
|
||||
QString roomName() const;
|
||||
QString roomTopic() const;
|
||||
QString plainRoomName() const;
|
||||
QString plainRoomTopic() const;
|
||||
QString roomVersion() const;
|
||||
QString roomAvatarUrl();
|
||||
int memberCount() const;
|
||||
|
@ -98,8 +65,10 @@ public:
|
|||
bool isLoading() const;
|
||||
//! Whether the user has enough power level to send m.room.join_rules events.
|
||||
bool canChangeJoinRules() const;
|
||||
//! Whether the user has enough power level to send m.room.name & m.room.topic events.
|
||||
bool canChangeNameAndTopic() const;
|
||||
//! Whether the user has enough power level to send m.room.name.
|
||||
bool canChangeName() const;
|
||||
//! Whether the user has enough power level to send m.room.topic events.
|
||||
bool canChangeTopic() const;
|
||||
//! Whether the user has enough power level to send m.room.avatar event.
|
||||
bool canChangeAvatar() const;
|
||||
bool isEncryptionEnabled() const;
|
||||
|
@ -108,9 +77,10 @@ public:
|
|||
|
||||
Q_INVOKABLE void enableEncryption();
|
||||
Q_INVOKABLE void updateAvatar();
|
||||
Q_INVOKABLE void openEditModal();
|
||||
Q_INVOKABLE void changeAccessRules(int index);
|
||||
Q_INVOKABLE void changeNotifications(int currentIndex);
|
||||
Q_INVOKABLE void changeTopic(QString topic);
|
||||
Q_INVOKABLE void changeName(QString name);
|
||||
|
||||
signals:
|
||||
void loadingChanged();
|
||||
|
|
|
@ -1,382 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "TextField.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QEventTransition>
|
||||
#include <QFontDatabase>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QRegularExpressionValidator>
|
||||
|
||||
TextField::TextField(QWidget *parent)
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
// Get rid of the focus border on macOS.
|
||||
setAttribute(Qt::WA_MacShowFocusRect, 0);
|
||||
|
||||
QPalette pal;
|
||||
|
||||
state_machine_ = new TextFieldStateMachine(this);
|
||||
label_ = nullptr;
|
||||
label_font_size_ = 15;
|
||||
show_label_ = false;
|
||||
background_color_ = pal.color(QPalette::Window);
|
||||
is_valid_ = true;
|
||||
|
||||
setFrame(false);
|
||||
setAttribute(Qt::WA_Hover);
|
||||
setMouseTracking(true);
|
||||
setTextMargins(0, 4, 0, 6);
|
||||
|
||||
state_machine_->start();
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setBackgroundColor(const QColor &color)
|
||||
{
|
||||
background_color_ = color;
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
QColor
|
||||
TextField::backgroundColor() const
|
||||
{
|
||||
return background_color_;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setShowLabel(bool value)
|
||||
{
|
||||
if (show_label_ == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
show_label_ = value;
|
||||
|
||||
if (!label_ && value) {
|
||||
label_ = new TextFieldLabel(this);
|
||||
state_machine_->setLabel(label_);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
setContentsMargins(0, 23, 0, 0);
|
||||
} else {
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TextField::hasLabel() const
|
||||
{
|
||||
return show_label_;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setValid(bool valid)
|
||||
{
|
||||
is_valid_ = valid;
|
||||
}
|
||||
|
||||
bool
|
||||
TextField::isValid() const
|
||||
{
|
||||
QString s = text();
|
||||
int pos = 0;
|
||||
if (regexp_.pattern().isEmpty()) {
|
||||
return is_valid_;
|
||||
}
|
||||
QRegularExpressionValidator v(QRegularExpression(regexp_), 0);
|
||||
return v.validate(s, pos) == QValidator::Acceptable;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setLabelFontSize(qreal size)
|
||||
{
|
||||
label_font_size_ = size;
|
||||
|
||||
if (label_) {
|
||||
QFont font(label_->font());
|
||||
font.setPointSizeF(size);
|
||||
label_->setFont(font);
|
||||
label_->update();
|
||||
}
|
||||
}
|
||||
|
||||
qreal
|
||||
TextField::labelFontSize() const
|
||||
{
|
||||
return label_font_size_;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setLabel(const QString &label)
|
||||
{
|
||||
label_text_ = label;
|
||||
setShowLabel(true);
|
||||
label_->update();
|
||||
}
|
||||
|
||||
QString
|
||||
TextField::label() const
|
||||
{
|
||||
return label_text_;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setLabelColor(const QColor &color)
|
||||
{
|
||||
label_color_ = color;
|
||||
emit labelColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor
|
||||
TextField::labelColor() const
|
||||
{
|
||||
if (!label_color_.isValid()) {
|
||||
return QPalette().color(QPalette::Text);
|
||||
}
|
||||
|
||||
return label_color_;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setInkColor(const QColor &color)
|
||||
{
|
||||
ink_color_ = color;
|
||||
emit inkColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor
|
||||
TextField::inkColor() const
|
||||
{
|
||||
if (!ink_color_.isValid()) {
|
||||
return QPalette().color(QPalette::Text);
|
||||
}
|
||||
|
||||
return ink_color_;
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setUnderlineColor(const QColor &color)
|
||||
{
|
||||
underline_color_ = color;
|
||||
emit underlineColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
void
|
||||
TextField::setRegexp(const QRegularExpression ®exp)
|
||||
{
|
||||
regexp_ = regexp;
|
||||
}
|
||||
|
||||
QColor
|
||||
TextField::underlineColor() const
|
||||
{
|
||||
if (!underline_color_.isValid()) {
|
||||
if ((hasAcceptableInput() && isValid()) || !isModified())
|
||||
return QPalette().color(QPalette::Highlight);
|
||||
else
|
||||
return Qt::red;
|
||||
}
|
||||
|
||||
return underline_color_;
|
||||
}
|
||||
|
||||
bool
|
||||
TextField::event(QEvent *event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case QEvent::Resize:
|
||||
case QEvent::Move: {
|
||||
if (label_)
|
||||
label_->setGeometry(rect());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QLineEdit::event(event);
|
||||
}
|
||||
|
||||
void
|
||||
TextField::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QLineEdit::paintEvent(event);
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
if (text().isEmpty()) {
|
||||
painter.setOpacity(1 - state_machine_->progress());
|
||||
painter.fillRect(rect(), backgroundColor());
|
||||
}
|
||||
|
||||
const int y = height() - 1;
|
||||
const int wd = width() - 5;
|
||||
|
||||
QPen pen;
|
||||
pen.setWidth(1);
|
||||
pen.setColor(underlineColor());
|
||||
painter.setPen(pen);
|
||||
painter.setOpacity(1);
|
||||
painter.drawLine(2, y, wd, y);
|
||||
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
brush.setColor(inkColor());
|
||||
|
||||
const qreal progress = state_machine_->progress();
|
||||
|
||||
if (progress > 0) {
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(brush);
|
||||
const int w = (1 - progress) * static_cast<qreal>(wd / 2);
|
||||
painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2);
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldStateMachine::TextFieldStateMachine(TextField *parent)
|
||||
: QStateMachine(parent)
|
||||
, text_field_(parent)
|
||||
{
|
||||
normal_state_ = new QState;
|
||||
focused_state_ = new QState;
|
||||
|
||||
label_ = nullptr;
|
||||
offset_anim_ = nullptr;
|
||||
color_anim_ = nullptr;
|
||||
progress_ = 0.0;
|
||||
|
||||
addState(normal_state_);
|
||||
addState(focused_state_);
|
||||
|
||||
setInitialState(normal_state_);
|
||||
|
||||
QEventTransition *transition;
|
||||
QPropertyAnimation *animation;
|
||||
|
||||
transition = new QEventTransition(parent, QEvent::FocusIn);
|
||||
transition->setTargetState(focused_state_);
|
||||
normal_state_->addTransition(transition);
|
||||
|
||||
animation = new QPropertyAnimation(this, "progress", this);
|
||||
animation->setEasingCurve(QEasingCurve::InCubic);
|
||||
animation->setDuration(310);
|
||||
transition->addAnimation(animation);
|
||||
|
||||
transition = new QEventTransition(parent, QEvent::FocusOut);
|
||||
transition->setTargetState(normal_state_);
|
||||
focused_state_->addTransition(transition);
|
||||
|
||||
animation = new QPropertyAnimation(this, "progress", this);
|
||||
animation->setEasingCurve(QEasingCurve::OutCubic);
|
||||
animation->setDuration(310);
|
||||
transition->addAnimation(animation);
|
||||
|
||||
normal_state_->assignProperty(this, "progress", 0);
|
||||
focused_state_->assignProperty(this, "progress", 1);
|
||||
|
||||
setupProperties();
|
||||
|
||||
connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties()));
|
||||
}
|
||||
|
||||
void
|
||||
TextFieldStateMachine::setLabel(TextFieldLabel *label)
|
||||
{
|
||||
if (label_) {
|
||||
delete label_;
|
||||
}
|
||||
|
||||
if (offset_anim_) {
|
||||
removeDefaultAnimation(offset_anim_);
|
||||
delete offset_anim_;
|
||||
}
|
||||
|
||||
if (color_anim_) {
|
||||
removeDefaultAnimation(color_anim_);
|
||||
delete color_anim_;
|
||||
}
|
||||
|
||||
label_ = label;
|
||||
|
||||
if (label_) {
|
||||
offset_anim_ = new QPropertyAnimation(label_, "offset", this);
|
||||
offset_anim_->setDuration(210);
|
||||
offset_anim_->setEasingCurve(QEasingCurve::OutCubic);
|
||||
addDefaultAnimation(offset_anim_);
|
||||
|
||||
color_anim_ = new QPropertyAnimation(label_, "color", this);
|
||||
color_anim_->setDuration(210);
|
||||
addDefaultAnimation(color_anim_);
|
||||
}
|
||||
|
||||
setupProperties();
|
||||
}
|
||||
|
||||
void
|
||||
TextFieldStateMachine::setupProperties()
|
||||
{
|
||||
if (label_) {
|
||||
const int m = text_field_->textMargins().top();
|
||||
|
||||
if (text_field_->text().isEmpty()) {
|
||||
normal_state_->assignProperty(label_, "offset", QPointF(0, 26));
|
||||
} else {
|
||||
normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
|
||||
}
|
||||
|
||||
focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
|
||||
focused_state_->assignProperty(label_, "color", text_field_->inkColor());
|
||||
normal_state_->assignProperty(label_, "color", text_field_->labelColor());
|
||||
|
||||
if (0 != label_->offset().y() && !text_field_->text().isEmpty()) {
|
||||
label_->setOffset(QPointF(0, 0 - m));
|
||||
} else if (!text_field_->hasFocus() && label_->offset().y() <= 0 &&
|
||||
text_field_->text().isEmpty()) {
|
||||
label_->setOffset(QPointF(0, 26));
|
||||
}
|
||||
}
|
||||
|
||||
text_field_->update();
|
||||
}
|
||||
|
||||
TextFieldLabel::TextFieldLabel(TextField *parent)
|
||||
: QWidget(parent)
|
||||
, text_field_(parent)
|
||||
{
|
||||
x_ = 0;
|
||||
y_ = 26;
|
||||
scale_ = 1;
|
||||
color_ = parent->labelColor();
|
||||
|
||||
QFont font;
|
||||
font.setWeight(60);
|
||||
font.setLetterSpacing(QFont::PercentageSpacing, 102);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
void
|
||||
TextFieldLabel::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (!text_field_->hasLabel())
|
||||
return;
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.scale(scale_, scale_);
|
||||
painter.setPen(color_);
|
||||
painter.setOpacity(1);
|
||||
|
||||
QPointF pos(2 + x_, height() - 36 + y_);
|
||||
painter.drawText(pos.x(), pos.y(), text_field_->label());
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
#include <QLineEdit>
|
||||
#include <QPaintEvent>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QRegularExpression>
|
||||
#include <QStateMachine>
|
||||
#include <QtGlobal>
|
||||
|
||||
class TextField;
|
||||
class TextFieldLabel;
|
||||
class TextFieldStateMachine;
|
||||
|
||||
class TextField : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor NOTIFY inkColorChanged)
|
||||
Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor NOTIFY labelColorChanged)
|
||||
Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor NOTIFY
|
||||
underlineColorChanged)
|
||||
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor NOTIFY
|
||||
backgroundColorChanged)
|
||||
|
||||
public:
|
||||
explicit TextField(QWidget *parent = nullptr);
|
||||
|
||||
void setInkColor(const QColor &color);
|
||||
void setBackgroundColor(const QColor &color);
|
||||
void setLabel(const QString &label);
|
||||
void setLabelColor(const QColor &color);
|
||||
void setLabelFontSize(qreal size);
|
||||
void setShowLabel(bool value);
|
||||
void setUnderlineColor(const QColor &color);
|
||||
void setRegexp(const QRegularExpression ®exp);
|
||||
void setValid(bool valid);
|
||||
|
||||
QColor inkColor() const;
|
||||
QColor labelColor() const;
|
||||
QColor underlineColor() const;
|
||||
QColor backgroundColor() const;
|
||||
QString label() const;
|
||||
bool hasLabel() const;
|
||||
bool isValid() const;
|
||||
qreal labelFontSize() const;
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void inkColorChanged();
|
||||
void labelColorChanged();
|
||||
void underlineColorChanged();
|
||||
void backgroundColorChanged();
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
QColor ink_color_;
|
||||
QColor background_color_;
|
||||
QColor label_color_;
|
||||
QColor underline_color_;
|
||||
QString label_text_;
|
||||
TextFieldLabel *label_;
|
||||
TextFieldStateMachine *state_machine_;
|
||||
bool show_label_;
|
||||
QRegularExpression regexp_;
|
||||
bool is_valid_;
|
||||
qreal label_font_size_;
|
||||
};
|
||||
|
||||
class TextFieldLabel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(qreal scale WRITE setScale READ scale NOTIFY scaleChanged)
|
||||
Q_PROPERTY(QPointF offset WRITE setOffset READ offset NOTIFY offsetChanged)
|
||||
Q_PROPERTY(QColor color WRITE setColor READ color NOTIFY colorChanged)
|
||||
|
||||
public:
|
||||
TextFieldLabel(TextField *parent);
|
||||
|
||||
inline void setColor(const QColor &color);
|
||||
inline void setOffset(QPointF pos);
|
||||
inline void setScale(qreal scale);
|
||||
|
||||
inline QColor color() const;
|
||||
inline QPointF offset() const;
|
||||
inline qreal scale() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void scaleChanged();
|
||||
void offsetChanged();
|
||||
void colorChanged();
|
||||
|
||||
private:
|
||||
TextField *const text_field_;
|
||||
|
||||
QColor color_;
|
||||
qreal scale_;
|
||||
qreal x_;
|
||||
qreal y_;
|
||||
};
|
||||
|
||||
inline void
|
||||
TextFieldLabel::setColor(const QColor &color)
|
||||
{
|
||||
color_ = color;
|
||||
emit colorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
inline void
|
||||
TextFieldLabel::setOffset(QPointF pos)
|
||||
{
|
||||
x_ = pos.x();
|
||||
y_ = pos.y();
|
||||
emit offsetChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
inline void
|
||||
TextFieldLabel::setScale(qreal scale)
|
||||
{
|
||||
scale_ = scale;
|
||||
emit scaleChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
inline QPointF
|
||||
TextFieldLabel::offset() const
|
||||
{
|
||||
return QPointF(x_, y_);
|
||||
}
|
||||
inline qreal
|
||||
TextFieldLabel::scale() const
|
||||
{
|
||||
return scale_;
|
||||
}
|
||||
inline QColor
|
||||
TextFieldLabel::color() const
|
||||
{
|
||||
return color_;
|
||||
}
|
||||
|
||||
class TextFieldStateMachine : public QStateMachine
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(qreal progress WRITE setProgress READ progress NOTIFY progressChanged)
|
||||
|
||||
public:
|
||||
TextFieldStateMachine(TextField *parent);
|
||||
|
||||
inline void setProgress(qreal progress);
|
||||
void setLabel(TextFieldLabel *label);
|
||||
|
||||
inline qreal progress() const;
|
||||
|
||||
public slots:
|
||||
void setupProperties();
|
||||
|
||||
signals:
|
||||
void progressChanged();
|
||||
|
||||
private:
|
||||
QPropertyAnimation *color_anim_;
|
||||
QPropertyAnimation *offset_anim_;
|
||||
|
||||
QState *focused_state_;
|
||||
QState *normal_state_;
|
||||
|
||||
TextField *text_field_;
|
||||
TextFieldLabel *label_;
|
||||
|
||||
qreal progress_;
|
||||
};
|
||||
|
||||
inline void
|
||||
TextFieldStateMachine::setProgress(qreal progress)
|
||||
{
|
||||
progress_ = progress;
|
||||
emit progressChanged();
|
||||
text_field_->update();
|
||||
}
|
||||
|
||||
inline qreal
|
||||
TextFieldStateMachine::progress() const
|
||||
{
|
||||
return progress_;
|
||||
}
|
|
@ -8,54 +8,6 @@
|
|||
#include <QColor>
|
||||
#include <QPalette>
|
||||
|
||||
namespace ui {
|
||||
// Default font size.
|
||||
const int FontSize = 16;
|
||||
|
||||
// Default avatar size. Width and height.
|
||||
const int AvatarSize = 40;
|
||||
|
||||
enum class ButtonPreset
|
||||
{
|
||||
FlatPreset,
|
||||
CheckablePreset
|
||||
};
|
||||
|
||||
enum class RippleStyle
|
||||
{
|
||||
CenteredRipple,
|
||||
PositionedRipple,
|
||||
NoRipple
|
||||
};
|
||||
|
||||
enum class OverlayStyle
|
||||
{
|
||||
NoOverlay,
|
||||
TintedOverlay,
|
||||
GrayOverlay
|
||||
};
|
||||
|
||||
enum class Role
|
||||
{
|
||||
Default,
|
||||
Primary,
|
||||
Secondary
|
||||
};
|
||||
|
||||
enum class ButtonIconPlacement
|
||||
{
|
||||
LeftIcon,
|
||||
RightIcon
|
||||
};
|
||||
|
||||
enum class ProgressType
|
||||
{
|
||||
DeterminateProgress,
|
||||
IndeterminateProgress
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
class Theme : public QPalette
|
||||
{
|
||||
Q_GADGET
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QFontDatabase>
|
||||
|
||||
#include "ThemeManager.h"
|
||||
|
||||
QColor
|
||||
|
|
Loading…
Reference in a new issue