Compare commits
43 Commits
work/tobia
...
work/jz/fl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae155805e9 | ||
|
|
70bff21632 | ||
|
|
f58c390a47 | ||
|
|
089a9abcb4 | ||
|
|
bf1c76d0a6 | ||
|
|
879da627b1 | ||
|
|
9b93eb44d5 | ||
|
|
b30220eca9 | ||
|
|
d270d4e5e1 | ||
|
|
21da6cb0f4 | ||
|
|
6ac75df935 | ||
|
|
f29781349c | ||
|
|
bb776d5c2b | ||
|
|
6cfab9e3ea | ||
|
|
6373186c15 | ||
|
|
e342de3bc1 | ||
|
|
4cd7b69ea5 | ||
|
|
988e8529da | ||
|
|
6a32d1e961 | ||
|
|
0552c798fb | ||
|
|
a53ad41879 | ||
|
|
92351edcd0 | ||
|
|
878eb48cb0 | ||
|
|
053ca6bed8 | ||
|
|
78ae14ab2f | ||
|
|
5fdc2ad765 | ||
|
|
b75dbe8d5c | ||
|
|
eaf4663c84 | ||
|
|
64b8cd5bcc | ||
|
|
482d61ee47 | ||
|
|
276dcce95e | ||
|
|
217f9e2e02 | ||
|
|
f40a0a6f5f | ||
|
|
9bd67acc2f | ||
|
|
e87da0feb0 | ||
|
|
2608d879fa | ||
|
|
6ab61fd41f | ||
|
|
30dd6297ee | ||
|
|
ce02183f82 | ||
|
|
7c74a6cbe1 | ||
|
|
e6a11b2ad8 | ||
|
|
158942d1b5 | ||
|
|
aaa97ec029 |
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.6",
|
||||
"runtime-version": "6.7",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
|
||||
806
po/ar/neochat.po
806
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
800
po/az/neochat.po
800
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
758
po/ca/neochat.po
758
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
737
po/cs/neochat.po
737
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
754
po/da/neochat.po
754
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
795
po/de/neochat.po
795
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
802
po/el/neochat.po
802
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
729
po/eo/neochat.po
729
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
761
po/es/neochat.po
761
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
755
po/eu/neochat.po
755
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
787
po/fi/neochat.po
787
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
757
po/fr/neochat.po
757
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
781
po/hu/neochat.po
781
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
756
po/ia/neochat.po
756
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
794
po/id/neochat.po
794
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/ie/neochat.po
772
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
762
po/it/neochat.po
762
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
705
po/ja/neochat.po
705
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
752
po/ka/neochat.po
752
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
782
po/ko/neochat.po
782
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
717
po/lt/neochat.po
717
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
842
po/lv/neochat.po
842
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
760
po/nl/neochat.po
760
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
753
po/nn/neochat.po
753
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
798
po/pa/neochat.po
798
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
778
po/pl/neochat.po
778
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
794
po/pt/neochat.po
794
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
795
po/ru/neochat.po
795
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
800
po/sk/neochat.po
800
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
759
po/sl/neochat.po
759
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
792
po/sv/neochat.po
792
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
770
po/ta/neochat.po
770
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
753
po/tr/neochat.po
753
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/uk/neochat.po
772
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -172,9 +172,16 @@ add_library(neochat STATIC
|
||||
models/statekeysmodel.h
|
||||
sharehandler.cpp
|
||||
sharehandler.h
|
||||
models/roomtreeitem.cpp
|
||||
models/roomtreeitem.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
QT_QML_SINGLETON_TYPE TRUE
|
||||
)
|
||||
|
||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
|
||||
QML_FILES
|
||||
qml/main.qml
|
||||
qml/AccountMenu.qml
|
||||
@@ -205,8 +212,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/CompletionMenu.qml
|
||||
qml/PieProgressBar.qml
|
||||
qml/QuickFormatBar.qml
|
||||
qml/RoomData.qml
|
||||
qml/ServerData.qml
|
||||
qml/EmojiPicker.qml
|
||||
qml/LoginStep.qml
|
||||
qml/Login.qml
|
||||
@@ -240,7 +245,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/FileDelegateContextMenu.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ReportSheet.qml
|
||||
qml/DevtoolsPage.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RemoveSheet.qml
|
||||
qml/BanSheet.qml
|
||||
@@ -280,13 +284,13 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/RoomTreeSection.qml
|
||||
qml/DelegateContextMenu.qml
|
||||
qml/ShareDialog.qml
|
||||
qml/FeatureFlagPage.qml
|
||||
qml/AccountData.qml
|
||||
qml/StateKeys.qml
|
||||
qml/UnlockSSSSDialog.qml
|
||||
qml/QrScannerPage.qml
|
||||
qml/JoinRoomDialog.qml
|
||||
qml/ConfirmUrlDialog.qml
|
||||
qml/AccountSwitchDialog.qml
|
||||
qml/ConfirmLeaveDialog.qml
|
||||
qml/CodeMaximizeComponent.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
@@ -294,6 +298,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(timeline)
|
||||
add_subdirectory(devtools)
|
||||
|
||||
if(UNIX)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
@@ -385,7 +390,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
|
||||
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin)
|
||||
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin)
|
||||
target_link_libraries(neochat PUBLIC
|
||||
Qt::Core
|
||||
Qt::Quick
|
||||
|
||||
@@ -80,6 +80,7 @@ QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *ment
|
||||
|
||||
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
Q_ASSERT(m_room);
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(text);
|
||||
|
||||
@@ -58,7 +58,7 @@ public Q_SLOTS:
|
||||
void handleMessageEvent(ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
void checkEffects(const QString &text);
|
||||
|
||||
QString handleMentions(QString handledText, QList<Mention> *mentions);
|
||||
|
||||
@@ -318,7 +318,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount());
|
||||
}
|
||||
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionChanged(m_connection);
|
||||
}
|
||||
|
||||
void Controller::listenForNotifications()
|
||||
|
||||
@@ -129,6 +129,6 @@ Q_SIGNALS:
|
||||
void errorOccured(const QString &error, const QString &detail);
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
void activeConnectionChanged();
|
||||
void activeConnectionChanged(NeoChatConnection *connection);
|
||||
void accountsLoadingChanged();
|
||||
};
|
||||
|
||||
15
src/devtools/CMakeLists.txt
Normal file
15
src/devtools/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
qt_add_library(devtools STATIC)
|
||||
qt_add_qml_module(devtools
|
||||
URI org.kde.neochat.devtools
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools
|
||||
QML_FILES
|
||||
DevtoolsPage.qml
|
||||
AccountData.qml
|
||||
FeatureFlagPage.qml
|
||||
RoomData.qml
|
||||
ServerData.qml
|
||||
StateKeys.qml
|
||||
)
|
||||
@@ -7,7 +7,6 @@ import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.config
|
||||
|
||||
FormCard.FormCardPage {
|
||||
@@ -25,14 +25,11 @@ ColumnLayout {
|
||||
text: i18n("Room")
|
||||
textRole: "escapedDisplayName"
|
||||
valueRole: "roomId"
|
||||
displayText: roomListModel.data(roomListModel.index(currentIndex, 0), RoomListModel.DisplayNameRole)
|
||||
model: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: root.connection
|
||||
}
|
||||
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.DisplayNameRole)
|
||||
model: RoomManager.roomListModel
|
||||
currentIndex: 0
|
||||
Component.onCompleted: currentIndex = roomListModel.rowForRoom(root.room)
|
||||
onCurrentValueChanged: root.room = roomListModel.roomByAliasOrId(roomComboBox.currentValue)
|
||||
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
|
||||
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Room Id: %1", root.room.id)
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
Deprioritized, /**< The room is set as low priority. */
|
||||
Space, /**< The room is a space. */
|
||||
AddDirect, /**< So we can show the add friend delegate. */
|
||||
TypesCount, /**< Number of different types (this should always be last). */
|
||||
};
|
||||
Q_ENUM(Types);
|
||||
|
||||
|
||||
@@ -230,6 +230,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
|
||||
|
||||
qml_register_types_org_kde_neochat();
|
||||
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
|
||||
|
||||
@@ -91,7 +91,9 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
if (mediaId.isEmpty()) {
|
||||
return QVariant();
|
||||
}
|
||||
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
|
||||
if (m_room) {
|
||||
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_autoCompletionType == Emoji) {
|
||||
|
||||
@@ -118,7 +118,7 @@ private:
|
||||
QString m_text;
|
||||
QString m_fullText;
|
||||
CompletionProxyModel *m_filterModel;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
AutoCompletionType m_autoCompletionType = None;
|
||||
|
||||
void updateCompletion();
|
||||
|
||||
@@ -161,7 +161,7 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
|
||||
case Roles::ImageURL:
|
||||
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
||||
case Roles::MxcUrl:
|
||||
return data.url.mid(6);
|
||||
return m_connection->makeMediaUrl(QUrl(data.url));
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ using namespace Quotient;
|
||||
DevicesModel::DevicesModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &, const QString &deviceId) {
|
||||
const auto it = std::find_if(m_devices.begin(), m_devices.end(), [deviceId](const Quotient::Device &device) {
|
||||
return device.deviceId == deviceId;
|
||||
});
|
||||
if (it != m_devices.end()) {
|
||||
const auto index = this->index(it - m_devices.begin());
|
||||
Q_EMIT dataChanged(index, index, {Type});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DevicesModel::fetchDevices()
|
||||
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
const Quotient::RoomEvent *m_event = nullptr;
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
|
||||
@@ -124,6 +124,15 @@ void PublicRoomListModel::setShowOnlySpaces(bool showOnlySpaces)
|
||||
}
|
||||
m_showOnlySpaces = showOnlySpaces;
|
||||
Q_EMIT showOnlySpacesChanged();
|
||||
|
||||
nextBatch = QString();
|
||||
attempted = false;
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PublicRoomListModel::search(int limit)
|
||||
@@ -243,6 +252,9 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
return m_connection->room(room.roomId, JoinState::Join) != nullptr;
|
||||
}
|
||||
if (role == IsSpaceRole) {
|
||||
return room.roomType == QLatin1String("m.space");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -259,6 +271,7 @@ QHash<int, QByteArray> PublicRoomListModel::roleNames() const
|
||||
roles[AllowGuestsRole] = "allowGuests";
|
||||
roles[WorldReadableRole] = "worldReadable";
|
||||
roles[IsJoinedRole] = "isJoined";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[AliasRole] = "alias";
|
||||
|
||||
return roles;
|
||||
|
||||
@@ -69,6 +69,7 @@ public:
|
||||
AllowGuestsRole, /**< Whether the room allows guest users. */
|
||||
WorldReadableRole, /**< Whether the room events can be seen by non-members. */
|
||||
IsJoinedRole, /**< Whether the local user has joined the room. */
|
||||
IsSpaceRole, /**< Whether the room is a space. */
|
||||
};
|
||||
|
||||
explicit PublicRoomListModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include <Quotient/user.h>
|
||||
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room)
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
, m_event(event)
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
||||
};
|
||||
|
||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room);
|
||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
const NeoChatRoom *m_room;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
const Quotient::RoomMessageEvent *m_event;
|
||||
QList<Reaction> m_reactions;
|
||||
QMap<QString, QString> m_shortcodes;
|
||||
|
||||
100
src/models/roomtreeitem.cpp
Normal file
100
src/models/roomtreeitem.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "roomtreeitem.h"
|
||||
|
||||
RoomTreeItem::RoomTreeItem(TreeData data, RoomTreeItem *parent)
|
||||
: m_parentItem(parent)
|
||||
, m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
bool RoomTreeItem::operator==(const RoomTreeItem &other) const
|
||||
{
|
||||
if (std::holds_alternative<NeoChatRoomType::Types>(m_data) && std::holds_alternative<NeoChatRoomType::Types>(other.data())) {
|
||||
return std::get<NeoChatRoomType::Types>(m_data) == std::get<NeoChatRoomType::Types>(m_data);
|
||||
}
|
||||
if (std::holds_alternative<NeoChatRoom *>(m_data) && std::holds_alternative<NeoChatRoom *>(other.data())) {
|
||||
return std::get<NeoChatRoom *>(m_data)->id() == std::get<NeoChatRoom *>(m_data)->id();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RoomTreeItem *RoomTreeItem::child(int row)
|
||||
{
|
||||
return row >= 0 && row < childCount() ? m_children.at(row).get() : nullptr;
|
||||
}
|
||||
|
||||
int RoomTreeItem::childCount() const
|
||||
{
|
||||
return int(m_children.size());
|
||||
}
|
||||
|
||||
bool RoomTreeItem::insertChild(std::unique_ptr<RoomTreeItem> newChild)
|
||||
{
|
||||
if (newChild == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto it = m_children.begin(), end = m_children.end(); it != end; ++it) {
|
||||
if (*it == newChild) {
|
||||
*it = std::move(newChild);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_children.push_back(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RoomTreeItem::removeChild(int row)
|
||||
{
|
||||
if (row < 0 || row >= childCount()) {
|
||||
return false;
|
||||
}
|
||||
m_children.erase(m_children.begin() + row);
|
||||
return true;
|
||||
}
|
||||
|
||||
int RoomTreeItem::row() const
|
||||
{
|
||||
if (m_parentItem == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto it = std::find_if(m_parentItem->m_children.cbegin(), m_parentItem->m_children.cend(), [this](const std::unique_ptr<RoomTreeItem> &treeItem) {
|
||||
return treeItem.get() == this;
|
||||
});
|
||||
|
||||
if (it != m_parentItem->m_children.cend()) {
|
||||
return std::distance(m_parentItem->m_children.cbegin(), it);
|
||||
}
|
||||
Q_ASSERT(false); // should not happen
|
||||
return -1;
|
||||
}
|
||||
|
||||
RoomTreeItem *RoomTreeItem::parentItem() const
|
||||
{
|
||||
return m_parentItem;
|
||||
}
|
||||
|
||||
RoomTreeItem::TreeData RoomTreeItem::data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
std::optional<int> RoomTreeItem::rowForRoom(Quotient::Room *room) const
|
||||
{
|
||||
Q_ASSERT_X(std::holds_alternative<NeoChatRoomType::Types>(m_data), __FUNCTION__, "rowForRoom only works items for rooms not categories");
|
||||
|
||||
int i = 0;
|
||||
for (const auto &child : m_children) {
|
||||
if (std::get<NeoChatRoom *>(child->data()) == room) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
78
src/models/roomtreeitem.h
Normal file
78
src/models/roomtreeitem.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class RoomTreeItem
|
||||
*
|
||||
* This class defines an item in the space tree hierarchy model.
|
||||
*
|
||||
* @note This is separate from Quotient::Room and NeoChatRoom because we don't have
|
||||
* full room information for any room/space the user hasn't joined and we
|
||||
* don't want to create one for ever possible child in a space as that would
|
||||
* be expensive.
|
||||
*
|
||||
* @sa Quotient::Room, NeoChatRoom
|
||||
*/
|
||||
class RoomTreeItem
|
||||
{
|
||||
public:
|
||||
using TreeData = std::variant<NeoChatRoom *, NeoChatRoomType::Types>;
|
||||
|
||||
explicit RoomTreeItem(TreeData data, RoomTreeItem *parent = nullptr);
|
||||
|
||||
bool operator==(const RoomTreeItem &other) const;
|
||||
|
||||
/**
|
||||
* @brief Return the child at the given row number.
|
||||
*
|
||||
* Nullptr is returned if there is no child at the given row number.
|
||||
*/
|
||||
RoomTreeItem *child(int row);
|
||||
|
||||
/**
|
||||
* @brief The number of children this item has.
|
||||
*/
|
||||
int childCount() const;
|
||||
|
||||
/**
|
||||
* @brief Insert the given child.
|
||||
*/
|
||||
bool insertChild(std::unique_ptr<RoomTreeItem> newChild);
|
||||
|
||||
/**
|
||||
* @brief Remove the child at the given row number.
|
||||
*
|
||||
* @return True if a child was removed, false if the given row isn't valid.
|
||||
*/
|
||||
bool removeChild(int row);
|
||||
|
||||
/**
|
||||
* @brief Return this item's parent.
|
||||
*/
|
||||
RoomTreeItem *parentItem() const;
|
||||
|
||||
/**
|
||||
* @brief Return the row number for this child relative to the parent.
|
||||
*
|
||||
* @return The row value if the child has a parent, 0 otherwise.
|
||||
*/
|
||||
int row() const;
|
||||
|
||||
/**
|
||||
* @brief Return this item's data.
|
||||
*/
|
||||
TreeData data() const;
|
||||
|
||||
std::optional<int> rowForRoom(Quotient::Room *room) const;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<RoomTreeItem>> m_children;
|
||||
RoomTreeItem *m_parentItem;
|
||||
|
||||
TreeData m_data;
|
||||
};
|
||||
@@ -15,21 +15,47 @@ using namespace Quotient;
|
||||
|
||||
RoomTreeModel::RoomTreeModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_rootItem(new RoomTreeItem(nullptr))
|
||||
{
|
||||
initializeCategories();
|
||||
}
|
||||
|
||||
void RoomTreeModel::initializeCategories()
|
||||
RoomTreeItem *RoomTreeModel::getItem(const QModelIndex &index) const
|
||||
{
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
for (const auto &room : m_rooms[key]) {
|
||||
room->disconnect(this);
|
||||
if (index.isValid()) {
|
||||
RoomTreeItem *item = static_cast<RoomTreeItem *>(index.internalPointer());
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
m_rooms.clear();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
m_rooms[NeoChatRoomType::Types(i)] = {};
|
||||
return m_rootItem.get();
|
||||
}
|
||||
|
||||
void RoomTreeModel::resetModel()
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
beginResetModel();
|
||||
m_rootItem.reset();
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
m_rootItem.reset(new RoomTreeItem(nullptr));
|
||||
|
||||
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
|
||||
m_rootItem->insertChild(std::make_unique<RoomTreeItem>(NeoChatRoomType::Types(i), m_rootItem.get()));
|
||||
}
|
||||
|
||||
for (const auto &r : m_connection->allRooms()) {
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(r);
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
const auto categoryItem = m_rootItem->child(type);
|
||||
if (categoryItem->insertChild(std::make_unique<RoomTreeItem>(room, categoryItem))) {
|
||||
connectRoomSignals(room);
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void RoomTreeModel::setConnection(NeoChatConnection *connection)
|
||||
@@ -41,16 +67,13 @@ void RoomTreeModel::setConnection(NeoChatConnection *connection)
|
||||
disconnect(m_connection.get(), nullptr, this, nullptr);
|
||||
}
|
||||
m_connection = connection;
|
||||
beginResetModel();
|
||||
initializeCategories();
|
||||
endResetModel();
|
||||
|
||||
resetModel();
|
||||
|
||||
connect(connection, &Connection::newRoom, this, &RoomTreeModel::newRoom);
|
||||
connect(connection, &Connection::leftRoom, this, &RoomTreeModel::leftRoom);
|
||||
connect(connection, &Connection::aboutToDeleteRoom, this, &RoomTreeModel::leftRoom);
|
||||
|
||||
for (const auto &room : m_connection->allRooms()) {
|
||||
newRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
}
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
@@ -68,23 +91,28 @@ void RoomTreeModel::newRoom(Room *r)
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(index(type, 0), m_rooms[type].size(), m_rooms[type].size());
|
||||
m_rooms[type].append(room);
|
||||
const auto parentItem = m_rootItem->child(type);
|
||||
beginInsertRows(index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
|
||||
parentItem->insertChild(std::make_unique<RoomTreeItem>(room, parentItem));
|
||||
connectRoomSignals(room);
|
||||
endInsertRows();
|
||||
qWarning() << "adding room" << type << "new count" << parentItem->childCount();
|
||||
}
|
||||
|
||||
void RoomTreeModel::leftRoom(Room *r)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(r);
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
auto row = m_rooms[type].indexOf(room);
|
||||
if (row == -1) {
|
||||
auto index = indexForRoom(room);
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(index(type, 0), row, row);
|
||||
m_rooms[type][row]->disconnect(this);
|
||||
m_rooms[type].removeAt(row);
|
||||
|
||||
const auto parentItem = getItem(index.parent());
|
||||
Q_ASSERT(parentItem);
|
||||
|
||||
beginRemoveRows(index.parent(), index.row(), index.row());
|
||||
parentItem->removeChild(index.row());
|
||||
room->disconnect(this);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
@@ -94,30 +122,41 @@ void RoomTreeModel::moveRoom(Quotient::Room *room)
|
||||
// NeoChatRoomType::typeForRoom doesn't match it's current location. So find the room.
|
||||
NeoChatRoomType::Types oldType;
|
||||
int oldRow = -1;
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
if (m_rooms[key].contains(room)) {
|
||||
oldType = key;
|
||||
oldRow = m_rooms[key].indexOf(room);
|
||||
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
|
||||
const auto categoryItem = m_rootItem->child(i);
|
||||
const auto row = categoryItem->rowForRoom(room);
|
||||
if (row) {
|
||||
oldType = static_cast<NeoChatRoomType::Types>(i);
|
||||
oldRow = *row;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldRow == -1) {
|
||||
return;
|
||||
}
|
||||
const auto newType = NeoChatRoomType::typeForRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
auto neochatRoom = dynamic_cast<NeoChatRoom *>(room);
|
||||
const auto newType = NeoChatRoomType::typeForRoom(neochatRoom);
|
||||
if (newType == oldType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto oldParent = index(oldType, 0, {});
|
||||
auto oldParentItem = getItem(oldParent);
|
||||
Q_ASSERT(oldParentItem);
|
||||
|
||||
const auto newParent = index(newType, 0, {});
|
||||
auto newParentItem = getItem(newParent);
|
||||
Q_ASSERT(newParentItem);
|
||||
|
||||
// HACK: We're doing this as a remove then insert because moving doesn't work
|
||||
// properly with DelegateChooser for whatever reason.
|
||||
Q_ASSERT(checkIndex(index(oldRow, 0, oldParent), QAbstractItemModel::CheckIndexOption::IndexIsValid));
|
||||
beginRemoveRows(oldParent, oldRow, oldRow);
|
||||
m_rooms[oldType].removeAt(oldRow);
|
||||
const bool success = oldParentItem->removeChild(oldRow);
|
||||
Q_ASSERT(success);
|
||||
endRemoveRows();
|
||||
beginInsertRows(newParent, m_rooms[newType].size(), m_rooms[newType].size());
|
||||
m_rooms[newType].append(dynamic_cast<NeoChatRoom *>(room));
|
||||
beginInsertRows(newParent, newParentItem->childCount(), newParentItem->childCount());
|
||||
newParentItem->insertChild(std::make_unique<RoomTreeItem>(neochatRoom, newParentItem));
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@@ -151,15 +190,12 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
|
||||
|
||||
void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles)
|
||||
{
|
||||
const auto roomType = NeoChatRoomType::typeForRoom(room);
|
||||
const auto it = std::find(m_rooms[roomType].begin(), m_rooms[roomType].end(), room);
|
||||
if (it == m_rooms[roomType].end()) {
|
||||
const auto index = indexForRoom(room);
|
||||
if (!index.isValid()) {
|
||||
qCritical() << "Room" << room->id() << "not found in the room list";
|
||||
return;
|
||||
}
|
||||
const auto parentIndex = index(roomType, 0, {});
|
||||
const auto idx = index(it - m_rooms[roomType].begin(), 0, parentIndex);
|
||||
Q_EMIT dataChanged(idx, idx, roles);
|
||||
Q_EMIT dataChanged(index, index, roles);
|
||||
}
|
||||
|
||||
NeoChatConnection *RoomTreeModel::connection() const
|
||||
@@ -175,32 +211,55 @@ int RoomTreeModel::columnCount(const QModelIndex &parent) const
|
||||
|
||||
int RoomTreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
RoomTreeItem *parentItem;
|
||||
if (parent.column() > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!parent.isValid()) {
|
||||
return m_rooms.keys().size();
|
||||
parentItem = m_rootItem.get();
|
||||
} else {
|
||||
parentItem = static_cast<RoomTreeItem *>(parent.internalPointer());
|
||||
}
|
||||
if (!parent.parent().isValid()) {
|
||||
return m_rooms.values()[parent.row()].size();
|
||||
}
|
||||
return 0;
|
||||
|
||||
return parentItem->childCount();
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.internalPointer()) {
|
||||
return {};
|
||||
if (!index.isValid()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return this->index(NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(index.internalPointer())), 0, QModelIndex());
|
||||
|
||||
RoomTreeItem *childItem = static_cast<RoomTreeItem *>(index.internalPointer());
|
||||
if (!childItem) {
|
||||
return QModelIndex();
|
||||
}
|
||||
RoomTreeItem *parentItem = childItem->parentItem();
|
||||
|
||||
if (parentItem == m_rootItem.get()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!parent.isValid()) {
|
||||
return createIndex(row, column, nullptr);
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return QModelIndex();
|
||||
}
|
||||
if (row >= rowCount(parent)) {
|
||||
return {};
|
||||
|
||||
RoomTreeItem *parentItem = getItem(parent);
|
||||
if (!parentItem) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return createIndex(row, column, m_rooms[NeoChatRoomType::Types(parent.row())][row]);
|
||||
|
||||
RoomTreeItem *childItem = parentItem->child(row);
|
||||
if (childItem) {
|
||||
return createIndex(row, column, childItem);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> RoomTreeModel::roleNames() const
|
||||
@@ -235,7 +294,8 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (!index.parent().isValid()) {
|
||||
RoomTreeItem *child = getItem(index);
|
||||
if (std::holds_alternative<NeoChatRoomType::Types>(child->data())) {
|
||||
if (role == DisplayNameRole) {
|
||||
return NeoChatRoomType::typeName(index.row());
|
||||
}
|
||||
@@ -256,7 +316,8 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
return {};
|
||||
}
|
||||
const auto room = m_rooms.values()[index.parent().row()][index.row()].get();
|
||||
|
||||
const auto room = std::get<NeoChatRoom *>(child->data());
|
||||
Q_ASSERT(room);
|
||||
|
||||
if (role == DisplayNameRole) {
|
||||
@@ -338,16 +399,20 @@ QModelIndex RoomTreeModel::indexForRoom(NeoChatRoom *room) const
|
||||
|
||||
// Try and find by checking type.
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
auto row = m_rooms[type].indexOf(room);
|
||||
if (row >= 0) {
|
||||
return index(row, 0, index(type, 0));
|
||||
const auto parentItem = m_rootItem->child(type);
|
||||
const auto row = parentItem->rowForRoom(room);
|
||||
if (row) {
|
||||
return index(*row, 0, index(type, 0));
|
||||
}
|
||||
// Double check that the room isn't in the wrong category.
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
if (m_rooms[key].contains(room)) {
|
||||
return index(m_rooms[key].indexOf(room), 0, index(key, 0));
|
||||
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
|
||||
const auto parentItem = m_rootItem->child(i);
|
||||
const auto row = parentItem->rowForRoom(room);
|
||||
if (row) {
|
||||
return index(*row, 0, index(i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QPointer>
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
#include "roomtreeitem.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
@@ -82,10 +83,12 @@ Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection = nullptr;
|
||||
QMap<NeoChatRoomType::Types, QList<QPointer<NeoChatRoom>>> m_rooms;
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
std::unique_ptr<RoomTreeItem> m_rootItem;
|
||||
|
||||
void initializeCategories();
|
||||
RoomTreeItem *getItem(const QModelIndex &index) const;
|
||||
|
||||
void resetModel();
|
||||
void connectRoomSignals(NeoChatRoom *room);
|
||||
|
||||
void newRoom(Quotient::Room *room);
|
||||
|
||||
@@ -121,7 +121,7 @@ private:
|
||||
void setSearching(bool searching);
|
||||
|
||||
QString m_searchText;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
||||
Quotient::SearchJob *m_job = nullptr;
|
||||
bool m_searching = false;
|
||||
|
||||
@@ -5,17 +5,20 @@
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
SortFilterRoomListModel::SortFilterRoomListModel(QObject *parent)
|
||||
SortFilterRoomListModel::SortFilterRoomListModel(RoomListModel *sourceModel, QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &SortFilterRoomListModel::filterTextChanged, this, [this]() {
|
||||
invalidateFilter();
|
||||
});
|
||||
connect(this, &SortFilterRoomListModel::sourceModelChanged, this, [this]() {
|
||||
connect(sourceModel(), &QAbstractListModel::rowsInserted, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
connect(sourceModel(), &QAbstractListModel::rowsRemoved, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
connect(this->sourceModel(), &QAbstractListModel::rowsInserted, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
connect(this->sourceModel(), &QAbstractListModel::rowsRemoved, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/roomlistmodel.h"
|
||||
|
||||
/**
|
||||
* @class SortFilterRoomListModel
|
||||
*
|
||||
@@ -29,6 +31,7 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
@@ -36,7 +39,7 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
public:
|
||||
explicit SortFilterRoomListModel(QObject *parent = nullptr);
|
||||
explicit SortFilterRoomListModel(RoomListModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
void setFilterText(const QString &text);
|
||||
[[nodiscard]] QString filterText() const;
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
#include "roomtreemodel.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
|
||||
SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() {
|
||||
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
|
||||
@@ -21,12 +24,11 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
|
||||
|
||||
setRecursiveFilteringEnabled(true);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &SortFilterRoomTreeModel::filterTextChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(this, &SortFilterRoomTreeModel::sourceModelChanged, this, [this]() {
|
||||
sourceModel()->disconnect(this);
|
||||
connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
this->sourceModel()->disconnect(this);
|
||||
connect(this->sourceModel(), &QAbstractItemModel::rowsInserted, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(this->sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
|
||||
@@ -32,6 +32,7 @@ class SortFilterRoomTreeModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
@@ -62,7 +63,7 @@ public:
|
||||
};
|
||||
Q_ENUM(Mode)
|
||||
|
||||
explicit SortFilterRoomTreeModel(QObject *parent = nullptr);
|
||||
explicit SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
void setRoomSortOrder(RoomSortOrder sortOrder);
|
||||
|
||||
|
||||
@@ -5,22 +5,21 @@
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
SortFilterSpaceListModel::SortFilterSpaceListModel(QObject *parent)
|
||||
SortFilterSpaceListModel::SortFilterSpaceListModel(RoomListModel *sourceModel, QObject *parent)
|
||||
: QSortFilterProxyModel{parent}
|
||||
{
|
||||
setSortRole(RoomListModel::RoomIdRole);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &QAbstractProxyModel::sourceModelChanged, this, [this]() {
|
||||
connect(sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QList<int> roles) {
|
||||
if (roles.contains(RoomListModel::IsChildSpaceRole)) {
|
||||
invalidate();
|
||||
}
|
||||
countChanged();
|
||||
});
|
||||
invalidate();
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
connect(this->sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QList<int> roles) {
|
||||
if (roles.contains(RoomListModel::IsChildSpaceRole)) {
|
||||
invalidate();
|
||||
}
|
||||
Q_EMIT countChanged();
|
||||
});
|
||||
|
||||
setSortRole(RoomListModel::RoomIdRole);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/roomlistmodel.h"
|
||||
|
||||
/**
|
||||
* @class SortFilterSpaceListModel
|
||||
*
|
||||
@@ -18,6 +20,7 @@ class SortFilterSpaceListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The number of spaces in the model.
|
||||
@@ -25,7 +28,7 @@ class SortFilterSpaceListModel : public QSortFilterProxyModel
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
explicit SortFilterSpaceListModel(QObject *parent = nullptr);
|
||||
explicit SortFilterSpaceListModel(RoomListModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
Q_SIGNALS:
|
||||
void countChanged();
|
||||
|
||||
@@ -55,11 +55,14 @@ void StateKeysModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
loadState();
|
||||
|
||||
connect(room, &NeoChatRoom::changed, this, [this] {
|
||||
if (room) {
|
||||
loadState();
|
||||
});
|
||||
|
||||
connect(room, &NeoChatRoom::changed, this, [this] {
|
||||
loadState();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString StateKeysModel::eventType() const
|
||||
|
||||
@@ -76,7 +76,7 @@ Q_SIGNALS:
|
||||
void eventTypeChanged();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QString m_eventType;
|
||||
QVector<const Quotient::StateEvent *> m_stateKeys;
|
||||
void loadState();
|
||||
|
||||
@@ -22,7 +22,9 @@ QVariant StickerModel::data(const QModelIndex &index, int role) const
|
||||
const auto &row = index.row();
|
||||
const auto &image = m_images[row];
|
||||
if (role == UrlRole) {
|
||||
return m_room->connection()->makeMediaUrl(image.url);
|
||||
if (m_room) {
|
||||
return m_room->connection()->makeMediaUrl(image.url);
|
||||
}
|
||||
}
|
||||
if (role == BodyRole) {
|
||||
if (image.body) {
|
||||
@@ -108,6 +110,10 @@ void StickerModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
void StickerModel::postSticker(int index)
|
||||
{
|
||||
if (!m_room) {
|
||||
qWarning() << "No room";
|
||||
}
|
||||
|
||||
const auto &image = m_images[index];
|
||||
const auto &body = image.body ? *image.body : image.shortcode;
|
||||
QJsonObject infoJson;
|
||||
|
||||
@@ -101,6 +101,6 @@ private:
|
||||
ImagePacksModel *m_model = nullptr;
|
||||
int m_index = 0;
|
||||
QList<Quotient::ImagePackEventContent::ImagePackImage> m_images;
|
||||
NeoChatRoom *m_room;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
void reloadImages();
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"Name": "Tobias Fella",
|
||||
"Name[ca@valencia]": "Tobias Fella",
|
||||
"Name[ca]": "Tobias Fella",
|
||||
"Name[cs]": "Tobias Fella",
|
||||
"Name[de]": "Tobias Fella",
|
||||
"Name[es]": "Tobias Fella",
|
||||
"Name[eu]": "Tobias Fella",
|
||||
@@ -49,6 +50,7 @@
|
||||
"Name[ast]": "NeoChat",
|
||||
"Name[ca@valencia]": "NeoChat",
|
||||
"Name[ca]": "NeoChat",
|
||||
"Name[cs]": "NeoChat",
|
||||
"Name[de]": "NeoChat",
|
||||
"Name[es]": "NeoChat",
|
||||
"Name[eu]": "NeoChat",
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.settings
|
||||
import org.kde.neochat.devtools
|
||||
import org.kde.neochat.config
|
||||
|
||||
QQC2.Menu {
|
||||
@@ -55,7 +56,7 @@ QQC2.Menu {
|
||||
text: i18n("Open developer tools")
|
||||
icon.name: "tools"
|
||||
visible: Config.developerTools
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'DevtoolsPage.qml'), {
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage.qml'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title:window", "Developer Tools"),
|
||||
|
||||
147
src/qml/AccountSwitchDialog.qml
Normal file
147
src/qml/AccountSwitchDialog.qml
Normal file
@@ -0,0 +1,147 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.accounts
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
standardButtons: Kirigami.Dialog.NoButton
|
||||
|
||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||
title: i18nc("@title: dialog to switch between logged in accounts", "Switch Account")
|
||||
|
||||
onVisibleChanged: if (visible) {
|
||||
accountView.forceActiveFocus()
|
||||
}
|
||||
|
||||
contentItem: ListView {
|
||||
id: accountView
|
||||
property var addAccount
|
||||
|
||||
implicitHeight: contentHeight
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
footer: Delegates.RoundedItemDelegate {
|
||||
id: addDelegate
|
||||
width: parent.width
|
||||
highlighted: focus && !accountView.addAccount.pressed
|
||||
Component.onCompleted: accountView.addAccount = this
|
||||
icon {
|
||||
name: "list-add"
|
||||
width: Kirigami.Units.iconSizes.smallMedium
|
||||
height: Kirigami.Units.iconSizes.smallMedium
|
||||
}
|
||||
text: i18nc("@button: login to or register a new account.", "Add Account")
|
||||
contentItem: Delegates.SubtitleContentItem {
|
||||
itemDelegate: parent
|
||||
subtitle: i18n("Log in or create a new account")
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'WelcomePage.qml'), {}, {
|
||||
title: i18nc("@title:window", "Login")
|
||||
});
|
||||
if (switchUserButton.checked) {
|
||||
switchUserButton.checked = false;
|
||||
}
|
||||
accountView.currentIndex = Controller.activeConnectionIndex;
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
accountView.currentIndex = accountView.count - 1;
|
||||
accountView.forceActiveFocus();
|
||||
}
|
||||
Keys.onDownPressed: {
|
||||
accountView.currentIndex = 0;
|
||||
accountView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
clip: true
|
||||
model: AccountRegistry
|
||||
|
||||
keyNavigationEnabled: false
|
||||
Keys.onDownPressed: {
|
||||
if (accountView.currentIndex === accountView.count - 1) {
|
||||
accountView.addAccount.forceActiveFocus();
|
||||
accountView.currentIndex = -1;
|
||||
} else {
|
||||
accountView.incrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
if (accountView.currentIndex === 0) {
|
||||
accountView.addAccount.forceActiveFocus();
|
||||
accountView.currentIndex = -1;
|
||||
} else {
|
||||
accountView.decrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: accountView.currentItem.clicked()
|
||||
Keys.onReturnPressed: accountView.currentItem.clicked()
|
||||
|
||||
onVisibleChanged: {
|
||||
for (let i = 0; i < accountView.count; i++) {
|
||||
if (model.data(model.index(i, 0), Qt.DisplayRole) === root.connection.localUser.id) {
|
||||
accountView.currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
id: userDelegate
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
width: parent.width
|
||||
text: connection.localUser.displayName
|
||||
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
sourceSize {
|
||||
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
}
|
||||
source: userDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + userDelegate.connection.localUser.avatarMediaId) : ""
|
||||
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
|
||||
}
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: userDelegate
|
||||
subtitle: userDelegate.connection.localUser.id
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Controller.activeConnection = userDelegate.connection;
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,11 @@ Kirigami.ScrollablePage {
|
||||
required property NeoChatConnection connection
|
||||
|
||||
header: Kirigami.SearchField {
|
||||
onTextChanged: sortModel.filterText = text
|
||||
onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text
|
||||
}
|
||||
|
||||
ListView {
|
||||
model: SortFilterRoomListModel {
|
||||
id: sortModel
|
||||
sourceModel: RoomListModel {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
model: RoomManager.sortFilterRoomListModel
|
||||
delegate: RoomDelegate {
|
||||
id: roomDelegate
|
||||
onClicked: {
|
||||
@@ -36,4 +31,6 @@ Kirigami.ScrollablePage {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: Qt.callLater(() => header.forceActiveFocus())
|
||||
}
|
||||
|
||||
169
src/qml/CodeMaximizeComponent.qml
Normal file
169
src/qml/CodeMaximizeComponent.qml
Normal file
@@ -0,0 +1,169 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.syntaxhighlighting
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Components.AbstractMaximizeComponent {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The message author.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - id - The matrix ID of the author.
|
||||
* - isLocalUser - Whether the author is the local user.
|
||||
* - avatarSource - The mxc URL for the author's avatar in the current room.
|
||||
* - avatarMediaId - The media ID of the author's avatar.
|
||||
* - avatarUrl - The mxc URL for the author's avatar.
|
||||
* - displayName - The display name of the author.
|
||||
* - display - The name of the author.
|
||||
* - color - The color for the author.
|
||||
* - object - The Quotient::User object for the author.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
*/
|
||||
property var author
|
||||
|
||||
/**
|
||||
* @brief The timestamp of the message.
|
||||
*/
|
||||
property var time
|
||||
|
||||
/**
|
||||
* @brief The code text to show.
|
||||
*/
|
||||
property string codeText
|
||||
|
||||
/**
|
||||
* @brief The code language, if any.
|
||||
*/
|
||||
property string language
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action", "Copy to clipboard")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: Clipboard.saveText(root.codeText)
|
||||
}
|
||||
]
|
||||
|
||||
leading: RowLayout {
|
||||
Components.Avatar {
|
||||
id: userAvatar
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
name: root.author.name ?? root.author.displayName
|
||||
source: root.author.avatarSource
|
||||
color: root.author.color
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
QQC2.Label {
|
||||
id: userLabel
|
||||
|
||||
text: root.author.name ?? root.author.displayName
|
||||
color: root.author.color
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
QQC2.Label {
|
||||
id: dateTimeLabel
|
||||
text: root.time.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content: QQC2.ScrollView {
|
||||
id: codeScrollView
|
||||
contentWidth: root.width
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
QQC2.TextArea {
|
||||
id: codeText
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2
|
||||
|
||||
text: root.codeText
|
||||
readOnly: true
|
||||
textFormat: TextEdit.PlainText
|
||||
wrapMode: TextEdit.Wrap
|
||||
color: Kirigami.Theme.textColor
|
||||
|
||||
font.family: "monospace"
|
||||
|
||||
Kirigami.SpellCheck.enabled: false
|
||||
|
||||
onWidthChanged: lineModel.resetModel()
|
||||
onHeightChanged: lineModel.resetModel()
|
||||
|
||||
SyntaxHighlighter {
|
||||
property string definitionName: Repository.definitionForName(root.language).name
|
||||
textEdit: definitionName == "None" ? null : codeText
|
||||
definition: definitionName
|
||||
}
|
||||
ColumnLayout {
|
||||
id: lineNumberColumn
|
||||
anchors {
|
||||
top: codeText.top
|
||||
topMargin: codeText.topPadding + 1
|
||||
left: codeText.left
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
spacing: 0
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: LineModel {
|
||||
id: lineModel
|
||||
document: codeText.textDocument
|
||||
}
|
||||
delegate: QQC2.Label {
|
||||
id: label
|
||||
required property int index
|
||||
required property int docLineHeight
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: docLineHeight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: index + 1
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
|
||||
font.family: "monospace"
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
leftMargin: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing
|
||||
}
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onTapped: root.close()
|
||||
}
|
||||
|
||||
background: null
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,8 @@ QQC2.Popup {
|
||||
root.open();
|
||||
}
|
||||
|
||||
RoomListModel {
|
||||
id: roomListModel
|
||||
connection: root.connection
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
chatDocumentHandler.completionModel.roomListModel = roomListModel;
|
||||
chatDocumentHandler.completionModel.roomListModel = RoomManager.roomListModel;
|
||||
}
|
||||
|
||||
function incrementIndex() {
|
||||
|
||||
32
src/qml/ConfirmLeaveDialog.qml
Normal file
32
src/qml/ConfirmLeaveDialog.qml
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
required property NeoChatRoom room
|
||||
|
||||
width: Kirigami.Units.gridUnit * 24
|
||||
|
||||
title: i18nc("@title:dialog", "Confirm Leaving Room")
|
||||
|
||||
contentItem: FormCard.FormTextDelegate {
|
||||
text: root.room ? i18nc("Do you really want to leave <room name>?", "Do you really want to leave %1?", root.room.displayName) : ""
|
||||
}
|
||||
|
||||
customFooterActions: [
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Leave Room")
|
||||
icon.name: "arrow-left"
|
||||
onTriggered: RoomManager.leaveRoom(root.room)
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,11 +14,11 @@ Kirigami.Dialog {
|
||||
width: Kirigami.Units.gridUnit * 24
|
||||
height: Kirigami.Units.gridUnit * 8
|
||||
|
||||
title: i18nc("@title", "Open Url")
|
||||
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
||||
title: i18nc("@title", "Open URL")
|
||||
standardButtons: QQC2.DialogButtonBox.Open | QQC2.DialogButtonBox.Cancel
|
||||
|
||||
contentItem: QQC2.Label {
|
||||
text: i18nc("Do you want to open <link>", "Do you want to open <b>%1</b>?", root.link)
|
||||
text: xi18nc("@info", "Do you want to open <link>%1</link>?", root.link)
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
|
||||
@@ -84,6 +84,7 @@ Loader {
|
||||
* Some common actions shared between menus
|
||||
*/
|
||||
component ViewSourceAction: Kirigami.Action {
|
||||
visible: Config.developerTools
|
||||
text: i18n("View Source")
|
||||
icon.name: "code-context"
|
||||
onTriggered: RoomManager.viewEventSource(root.eventId)
|
||||
|
||||
@@ -32,7 +32,13 @@ SearchPage {
|
||||
/**
|
||||
* @brief Whether results should only includes spaces.
|
||||
*/
|
||||
property bool showOnlySpaces: false
|
||||
property bool showOnlySpaces: spacesOnlyButton.checked
|
||||
onShowOnlySpacesChanged: updateSearch()
|
||||
|
||||
/**
|
||||
* @brief Whetherthe button to toggle the showOnlySpaces state should be shown.
|
||||
*/
|
||||
property bool showOnlySpacesButton: true
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when a room is selected.
|
||||
@@ -47,9 +53,22 @@ SearchPage {
|
||||
|
||||
Component.onCompleted: focusSearch()
|
||||
|
||||
headerTrailing: ServerComboBox {
|
||||
id: serverComboBox
|
||||
connection: root.connection
|
||||
headerTrailing: RowLayout {
|
||||
QQC2.Button {
|
||||
id: spacesOnlyButton
|
||||
icon.name: "globe"
|
||||
display: QQC2.Button.IconOnly
|
||||
checkable: true
|
||||
text: i18nc("@action:button", "Only show spaces")
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
ServerComboBox {
|
||||
id: serverComboBox
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
model: PublicRoomListModel {
|
||||
|
||||
@@ -21,6 +21,7 @@ Delegates.RoundedItemDelegate {
|
||||
required property string topic
|
||||
required property int memberCount
|
||||
required property bool isJoined
|
||||
required property bool isSpace
|
||||
property bool justJoined: false
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ Delegates.RoundedItemDelegate {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillWidth: !spaceLabel.visible
|
||||
level: 4
|
||||
text: root.displayName
|
||||
font.bold: true
|
||||
@@ -64,6 +65,13 @@ Delegates.RoundedItemDelegate {
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
QQC2.Label {
|
||||
id: spaceLabel
|
||||
Layout.fillWidth: true
|
||||
visible: root.isSpace
|
||||
text: i18nc("@info:label A matrix space", "Space")
|
||||
color: Kirigami.Theme.linkColor
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: root.isJoined || root.justJoined
|
||||
text: i18n("Joined")
|
||||
|
||||
@@ -47,10 +47,12 @@ ApplicationWindow {
|
||||
isLive: true
|
||||
heading: NaN
|
||||
visible: !isNaN(root.latitude) && !isNaN(root.longitude)
|
||||
Component.onCompleted: mapView.map.addMapItem(this)
|
||||
}
|
||||
MapItemView {
|
||||
model: root.liveLocationModel
|
||||
delegate: LocationMapItem {}
|
||||
Component.onCompleted: mapView.map.addMapItemView(this)
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -35,52 +35,25 @@ Components.AbstractMaximizeComponent {
|
||||
|
||||
content: MapView {
|
||||
id: mapView
|
||||
map.plugin: Plugin {
|
||||
name: "osm"
|
||||
PluginParameter {
|
||||
name: "osm.useragent"
|
||||
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||
}
|
||||
PluginParameter {
|
||||
name: "osm.mapping.providersrepository.address"
|
||||
value: "https://autoconfig.kde.org/qtlocation/"
|
||||
}
|
||||
}
|
||||
map.plugin: OsmLocationPlugin.plugin
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.location = mapView.map.toCoordinate(Qt.point(mouseX, mouseY), false);
|
||||
mapView.map.addMapItem(mapView.locationMapItem);
|
||||
}
|
||||
}
|
||||
|
||||
MapQuickItem {
|
||||
id: point
|
||||
|
||||
visible: root.location
|
||||
anchorPoint.x: sourceItem.width / 2
|
||||
anchorPoint.y: sourceItem.height * 0.85
|
||||
coordinate: root.location
|
||||
autoFadeIn: false
|
||||
|
||||
sourceItem: Kirigami.Icon {
|
||||
width: height
|
||||
height: Kirigami.Units.iconSizes.huge
|
||||
source: "gps"
|
||||
isMask: true
|
||||
color: Kirigami.Theme.highlightColor
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -parent.height / 8
|
||||
width: height
|
||||
height: parent.height / 3 + 1
|
||||
source: "pin"
|
||||
isMask: true
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
}
|
||||
readonly property LocationMapItem locationMapItem: LocationMapItem {
|
||||
latitude: root.location.latitude
|
||||
longitude: root.location.longitude
|
||||
isLive: false
|
||||
heading: NaN
|
||||
asset: ""
|
||||
author: null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: mapView.map
|
||||
function onCopyrightLinkActivated() {
|
||||
|
||||
@@ -29,17 +29,22 @@ Kirigami.Page {
|
||||
map.zoomLevel: LocationHelper.zoomToFit(LocationHelper.unite(locationsModel.boundingBox, liveLocationsModel.boundingBox), mapView.width, mapView.height)
|
||||
|
||||
MapItemView {
|
||||
Component.onCompleted: mapView.map.addMapItemView(this)
|
||||
anchors.fill: parent
|
||||
|
||||
model: LocationsModel {
|
||||
id: locationsModel
|
||||
room: root.room
|
||||
}
|
||||
delegate: LocationMapItem {
|
||||
isLive: true
|
||||
isLive: false
|
||||
heading: NaN
|
||||
}
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
Component.onCompleted: mapView.map.addMapItemView(this)
|
||||
anchors.fill: parent
|
||||
model: LiveLocationsModel {
|
||||
id: liveLocationsModel
|
||||
room: root.room
|
||||
@@ -49,7 +54,7 @@ Kirigami.Page {
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
text: i18n("There are no locations shared in this room.")
|
||||
visible: mapView.mapItems.length === 0
|
||||
visible: mapView.map.mapItems.length === 0
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
Connections {
|
||||
|
||||
@@ -7,11 +7,14 @@ import QtQuick
|
||||
import QtLocation
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string userAgent: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||
property var plugin: Plugin {
|
||||
name: "osm"
|
||||
PluginParameter {
|
||||
name: "osm.useragent"
|
||||
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||
value: root.userAgent
|
||||
}
|
||||
PluginParameter {
|
||||
name: "osm.mapping.providersrepository.address"
|
||||
|
||||
@@ -38,7 +38,7 @@ Kirigami.Page {
|
||||
formats: Prison.Format.QRCode | Prison.Format.Aztec
|
||||
onResultChanged: {
|
||||
if (result.text.length > 0 && result.text != scanner.previousText) {
|
||||
RoomManager.resolveResource(result.text, "");
|
||||
RoomManager.resolveResource(result.text, "qr");
|
||||
scanner.previousText = result.text;
|
||||
}
|
||||
root.closeDialog();
|
||||
|
||||
@@ -66,6 +66,7 @@ QQC2.Dialog {
|
||||
root.close();
|
||||
}
|
||||
focusSequence: ""
|
||||
onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
@@ -81,13 +82,7 @@ QQC2.Dialog {
|
||||
highlightMoveDuration: 200
|
||||
Keys.forwardTo: searchField
|
||||
keyNavigationEnabled: true
|
||||
model: SortFilterRoomListModel {
|
||||
filterText: searchField.text
|
||||
sourceModel: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
model: RoomManager.sortFilterRoomListModel
|
||||
|
||||
delegate: RoomDelegate {
|
||||
connection: root.connection
|
||||
|
||||
@@ -115,6 +115,20 @@ QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: leaveButton
|
||||
icon.name: "arrow-left"
|
||||
text: i18nc("@action:button", "Leave this room")
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
onClicked: {
|
||||
Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog.qml').createObject(root.QQC2.ApplicationWindow.window, {
|
||||
room: root.room
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
|
||||
@@ -29,16 +29,15 @@ Kirigami.Page {
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
readonly property RoomTreeModel roomTreeModel: RoomTreeModel {
|
||||
connection: root.connection
|
||||
}
|
||||
property bool spaceChanging: true
|
||||
|
||||
readonly property bool collapsed: Config.collapsed
|
||||
|
||||
onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth
|
||||
Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth
|
||||
|
||||
|
||||
onCollapsedChanged: {
|
||||
if (collapsed) {
|
||||
sortFilterRoomTreeModel.filterText = "";
|
||||
RoomManager.sortFilterRoomTreeModel.filterText = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +86,13 @@ Kirigami.Page {
|
||||
|
||||
padding: 0
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onCurrentSpaceChanged() {
|
||||
treeView.expandRecursively();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
@@ -97,9 +103,6 @@ Kirigami.Page {
|
||||
Layout.fillHeight: true
|
||||
|
||||
connection: root.connection
|
||||
|
||||
onSelectionChanged: root.spaceChanging = true
|
||||
onSpacesUpdated: sortFilterRoomTreeModel.invalidate()
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
@@ -127,33 +130,7 @@ Kirigami.Page {
|
||||
clip: true
|
||||
reuseItems: false
|
||||
|
||||
onLayoutChanged: {
|
||||
if (sortFilterRoomTreeModel.filterTextJustChanged) {
|
||||
treeView.expandRecursively();
|
||||
sortFilterRoomTreeModel.filterTextJustChanged = false;
|
||||
}
|
||||
if (root.spaceChanging) {
|
||||
treeView.expandRecursively();
|
||||
if (spaceDrawer.showDirectChats || spaceDrawer.selectedSpaceId.length < 1) {
|
||||
const item = treeView.itemAtIndex(treeView.index(1, 0))
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
RoomManager.resolveResource(item.currentRoom.id);
|
||||
}
|
||||
root.spaceChanging = false;
|
||||
}
|
||||
}
|
||||
|
||||
model: SortFilterRoomTreeModel {
|
||||
id: sortFilterRoomTreeModel
|
||||
|
||||
property bool filterTextJustChanged: false
|
||||
|
||||
sourceModel: root.roomTreeModel
|
||||
activeSpaceId: spaceDrawer.selectedSpaceId
|
||||
mode: spaceDrawer.showDirectChats ? SortFilterRoomTreeModel.DirectChats : SortFilterRoomTreeModel.Rooms
|
||||
}
|
||||
model: RoomManager.sortFilterRoomTreeModel
|
||||
|
||||
selectionModel: ItemSelectionModel {}
|
||||
|
||||
@@ -226,7 +203,7 @@ Kirigami.Page {
|
||||
anchors.horizontalCenterOffset: (spaceDrawer.width + 1) / 2
|
||||
width: scrollView.width - Kirigami.Units.largeSpacing * 4
|
||||
visible: treeView.rows == 0
|
||||
text: if (sortFilterRoomTreeModel.filterText.length > 0) {
|
||||
text: if (RoomManager.sortFilterRoomTreeModel.filterText.length > 0) {
|
||||
return spaceDrawer.showDirectChats ? i18n("No friends found") : i18n("No rooms found");
|
||||
} else {
|
||||
return spaceDrawer.showDirectChats ? i18n("You haven't added any of your friends yet, click below to search for them.") : i18n("Join some rooms to get started");
|
||||
@@ -235,12 +212,12 @@ Kirigami.Page {
|
||||
|
||||
Kirigami.Action {
|
||||
id: exploreRoomAction
|
||||
icon.name: sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "list-add"
|
||||
text: sortFilterRoomTreeModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
||||
icon.name: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "list-add"
|
||||
text: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
||||
onTriggered: {
|
||||
let dialog = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage.qml'), {
|
||||
connection: root.connection,
|
||||
keyword: sortFilterRoomTreeModel.filterText
|
||||
keyword: RoomManager.sortFilterRoomTreeModel.filterText
|
||||
}, {
|
||||
title: i18nc("@title", "Explore Rooms")
|
||||
});
|
||||
@@ -252,8 +229,8 @@ Kirigami.Page {
|
||||
|
||||
Kirigami.Action {
|
||||
id: userSearchAction
|
||||
icon.name: sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "list-add"
|
||||
text: sortFilterRoomTreeModel.filterText.length > 0 ? i18n("Search in friend directory") : i18n("Find your friends")
|
||||
icon.name: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "list-add"
|
||||
text: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? i18n("Search in friend directory") : i18n("Find your friends")
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage.qml'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
@@ -264,7 +241,6 @@ Kirigami.Page {
|
||||
|
||||
footer: Loader {
|
||||
width: parent.width
|
||||
active: !root.collapsed
|
||||
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
|
||||
}
|
||||
|
||||
@@ -314,7 +290,6 @@ Kirigami.Page {
|
||||
Component {
|
||||
id: userInfo
|
||||
UserInfo {
|
||||
visible: !root.collapsed
|
||||
bottomEdge: false
|
||||
connection: root.connection
|
||||
}
|
||||
@@ -323,8 +298,8 @@ Kirigami.Page {
|
||||
Component {
|
||||
id: userInfoDesktop
|
||||
UserInfoDesktop {
|
||||
visible: !root.collapsed
|
||||
connection: root.connection
|
||||
collapsed: root.collapsed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,8 +311,8 @@ Kirigami.Page {
|
||||
connection: root.connection
|
||||
|
||||
onTextChanged: newText => {
|
||||
sortFilterRoomTreeModel.filterText = newText;
|
||||
sortFilterRoomTreeModel.filterTextJustChanged = true;
|
||||
RoomManager.sortFilterRoomTreeModel.filterText = newText;
|
||||
treeView.expandRecursively();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +323,7 @@ Kirigami.Page {
|
||||
connection: root.connection
|
||||
|
||||
onTextChanged: newText => {
|
||||
sortFilterRoomTreeModel.filterText = newText;
|
||||
RoomManager.sortFilterRoomTreeModel.filterText = newText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ Kirigami.Page {
|
||||
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
|
||||
property bool disableCancelShortcut: false
|
||||
|
||||
title: root.currentRoom.displayName
|
||||
title: root.currentRoom ? root.currentRoom.displayName : ""
|
||||
focus: true
|
||||
padding: 0
|
||||
|
||||
@@ -116,10 +116,11 @@ Kirigami.Page {
|
||||
Loader {
|
||||
id: timelineViewLoader
|
||||
anchors.fill: parent
|
||||
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading
|
||||
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace
|
||||
sourceComponent: TimelineView {
|
||||
id: timelineView
|
||||
currentRoom: root.currentRoom
|
||||
page: root
|
||||
timelineModel: root.timelineModel
|
||||
messageFilterModel: root.messageFilterModel
|
||||
actionsHandler: root.actionsHandler
|
||||
@@ -142,7 +143,23 @@ Kirigami.Page {
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.loading && !invitationLoader.active
|
||||
id: spaceLoader
|
||||
active: root.currentRoom && root.currentRoom.isSpace
|
||||
anchors.fill: parent
|
||||
sourceComponent: SpaceHomePage {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: !RoomManager.currentRoom
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: Kirigami.PlaceholderMessage {
|
||||
icon.name: "org.kde.neochat"
|
||||
text: i18n("Welcome to NeoChat")
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: Kirigami.LoadingPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
@@ -175,11 +192,7 @@ Kirigami.Page {
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onCurrentRoomChanged() {
|
||||
if (!RoomManager.currentRoom) {
|
||||
if (pageStack.lastItem === root) {
|
||||
pageStack.pop();
|
||||
}
|
||||
} else if (root.currentRoom.isInvite) {
|
||||
if (root.currentRoom && root.currentRoom.isInvite) {
|
||||
root.currentRoom.clearInvitationNotification();
|
||||
}
|
||||
}
|
||||
@@ -187,6 +200,10 @@ Kirigami.Page {
|
||||
function onWarning(title, message) {
|
||||
root.warning(title, message);
|
||||
}
|
||||
|
||||
function onGoToEvent(eventId) {
|
||||
(timelineViewLoader.item as TimelineView).goToEvent(eventId);
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
@@ -281,6 +298,15 @@ Kirigami.Page {
|
||||
});
|
||||
popup.open();
|
||||
}
|
||||
|
||||
function onShowMaximizedCode(author, time, codeText, language) {
|
||||
let popup = Qt.createComponent('org.kde.neochat', 'CodeMaximizeComponent.qml').createObject(QQC2.Overlay.overlay, {
|
||||
author: author,
|
||||
time: time,
|
||||
codeText: codeText,
|
||||
language: language
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user