Compare commits

..

40 Commits

Author SHA1 Message Date
Justin Zobel
ae155805e9 CI - Flatpak - Update Runtime/SDK to 6.7 2024-04-07 22:49:04 +00:00
snow flurry
70bff21632 Render custom emoji icons in the completion pane 2024-04-07 19:40:02 +00:00
James Graham
f58c390a47 Re-add requirement for having devtools active for the show message source action
Re-add requirement for having devtools active for the show message source action

BUG: 485140
2024-04-07 08:40:14 +00:00
l10n daemon script
089a9abcb4 GIT_SILENT Sync po/docbooks with svn 2024-04-07 01:23:56 +00:00
Joshua Goins
bf1c76d0a6 Force the choose room dialog's search dialog to be focused
This makes it possible to open the share dialog and start typing to find
the room immediately.
2024-04-06 15:40:03 -04:00
Joshua Goins
879da627b1 Fix the share dialog not showing up
Seems to be a leftover from the refactor to use modules.
2024-04-06 15:39:36 -04:00
James Graham
9b93eb44d5 Show a verified icon for verified devices rather than a verify option 2024-04-06 14:19:38 +00:00
l10n daemon script
b30220eca9 GIT_SILENT Sync po/docbooks with svn 2024-04-06 01:23:47 +00:00
l10n daemon script
d270d4e5e1 GIT_SILENT Sync po/docbooks with svn 2024-04-05 01:21:52 +00:00
l10n daemon script
21da6cb0f4 GIT_SILENT Sync po/docbooks with svn 2024-04-04 01:24:44 +00:00
l10n daemon script
6ac75df935 GIT_SILENT Sync po/docbooks with svn 2024-04-03 01:24:31 +00:00
l10n daemon script
f29781349c SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-04-03 01:14:45 +00:00
Tobias Fella
bb776d5c2b Only ask for URL opening confirmation for QR codes
BUG: 484870
2024-04-02 19:47:07 +02:00
James Graham
6cfab9e3ea Tree Model 2 Electric Boogaloo
This draws heavily on what @carlschwan did in network/neochat!1579 but I found it easier to start again and grab the bits as I needed them plus some other copying from what I did in the Space tree model.

From my current limited testing this seems to work nicely try and break it.
2024-04-02 14:44:20 +00:00
l10n daemon script
6373186c15 GIT_SILENT Sync po/docbooks with svn 2024-04-02 01:18:19 +00:00
Nate Graham
e342de3bc1 Make ConfirmUrlDialog HIG-compliant
* "URL" is an acronym; make it all caps
* Use descriptive context-appropriate buttons instead of "Yes" and "No"
* Use KUIT markup for styling the link
2024-04-01 11:32:27 -06:00
Tobias Fella
4cd7b69ea5 Fix QML warning 2024-04-01 15:48:42 +02:00
Tobias Fella
988e8529da Remove leftover signal handler 2024-04-01 15:43:31 +02:00
James Graham
6a32d1e961 Create component for showing a maximize version of a code snippet 2024-04-01 11:20:10 +00:00
James Graham
0552c798fb Create qml module for devtools 2024-04-01 10:58:29 +00:00
l10n daemon script
a53ad41879 GIT_SILENT Sync po/docbooks with svn 2024-04-01 01:17:50 +00:00
Tobias Fella
92351edcd0 Fix location delegates
- Mark OSMLocationPlugina as singleton in cmake
- Use this plugin for the LocationChooser
2024-03-31 20:57:58 +02:00
James Graham
878eb48cb0 Shut qt up about models passed to QML
Shutup qt about below

```
SortFilterRoomListModel is neither a QObject, nor default- and copy-constructible, nor uncreatable. You should not use it as a QML type.
SortFilterRoomTreeModel is neither a QObject, nor default- and copy-constructible, nor uncreatable. You should not use it as a QML type.
SortFilterSpaceListModel is neither a QObject, nor default- and copy-constructible, nor uncreatable. You should not use it as a QML type.
```
2024-03-31 17:49:35 +00:00
James Graham
053ca6bed8 Move the various room models into RoomManager
Move the various room models into RoomManager. This means the same room models are always used and is a base from which further logic can be moved from QML to cpp.
2024-03-31 12:56:27 +00:00
Tobias Fella
78ae14ab2f Stay in DM tab when selecting a DM 2024-03-31 14:31:22 +02:00
l10n daemon script
5fdc2ad765 GIT_SILENT Sync po/docbooks with svn 2024-03-31 01:30:45 +00:00
Tobias Fella
b75dbe8d5c Rework roommanager for improved stability
Fixes #645

- Active space handling is moved from QML to RoomManager
- Active tab in SpaceDrawer (space / no space / DM) is unified in a single variable
- RoomList & RoomPage loading is simplified: We're always pushing a RoomPage now; if there is no room, a placeholder is shown
- SpaceHomePage is moved into RoomPage; This replaces the entire push/replace room/spacehome logic
- If the current room is a space, the space home is shown, otherwise the timeline
- The concept of "previous room" is removed entirely. If we're leaving the active room, the placeholder room page is shown
- When clicking on a space in the list, the space room list is switched and the space home page is shown

In short, these changes should (after some initial regressions) lead to a less crashy NeoChat :)
2024-03-31 00:22:23 +01:00
James Graham
eaf4663c84 RoomManger connection
RoomManger should just get it's connection from Controller, no need to involve QML
2024-03-30 19:48:34 +00:00
James Graham
64b8cd5bcc Space Search
Allow to refine searches to spaces only in the main exlore function.
Show which rooms are spaces in the search page.

Closes #577
2024-03-30 19:37:46 +00:00
James Graham
482d61ee47 Fix marking messages as read when the window is thin
Make sure that messages are not marked as read when going back to the roomlist after entering a room when neochat is thin and only showing a single page

Fixes #642
2024-03-30 19:32:19 +00:00
l10n daemon script
276dcce95e GIT_SILENT Sync po/docbooks with svn 2024-03-30 01:18:07 +00:00
Tobias Fella
217f9e2e02 Set OUTPUT_DIRECTORY for qml modules
This fixes some cmake warning and might make qmlls happier
2024-03-29 20:06:26 +01:00
Tobias Fella
f40a0a6f5f Remove unused property 2024-03-29 16:43:06 +01:00
Tobias Fella
9bd67acc2f Remove leftover signal 2024-03-29 16:05:07 +01:00
James Graham
e87da0feb0 Add pagination to space hierarchy cache
Add pagination to space hierarchy cache to ensure all rooms get cached.
2024-03-29 15:03:50 +00:00
Tobias Fella
2608d879fa Add "Leave room" button to sidebar
BUG: 484425
2024-03-29 13:53:59 +01:00
Tobias Fella
6ab61fd41f Fix opening the last room active room if it's not in a space
At the moment, the saved room was effectively always overridden by the first room in the list
2024-03-29 13:29:41 +01:00
Tobias Fella
30dd6297ee Make sure we're switching out of the space home page when leaving the currently opened space 2024-03-29 11:57:06 +01:00
Tobias Fella
ce02183f82 Fix plural handling in space member strings 2024-03-29 11:56:29 +01:00
Tobias Fella
7c74a6cbe1 Improve space management strings 2024-03-29 11:56:06 +01:00
108 changed files with 15945 additions and 12100 deletions

View File

@@ -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": [

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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,14 +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
@@ -295,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)
@@ -386,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

View File

@@ -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()

View File

@@ -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();
};

View 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
)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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);

View File

@@ -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());

View File

@@ -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 {};
}

View File

@@ -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()

View File

@@ -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;

View File

@@ -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);

100
src/models/roomtreeitem.cpp Normal file
View 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
View 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;
};

View File

@@ -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 {};
}

View File

@@ -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);

View File

@@ -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);
});
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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();

View File

@@ -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",

View File

@@ -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"),

View File

@@ -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())
}

View 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
}
}
}

View File

@@ -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() {

View 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)
}
]
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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
}, {
@@ -334,8 +311,8 @@ Kirigami.Page {
connection: root.connection
onTextChanged: newText => {
sortFilterRoomTreeModel.filterText = newText;
sortFilterRoomTreeModel.filterTextJustChanged = true;
RoomManager.sortFilterRoomTreeModel.filterText = newText;
treeView.expandRecursively();
}
}
}
@@ -346,7 +323,7 @@ Kirigami.Page {
connection: root.connection
onTextChanged: newText => {
sortFilterRoomTreeModel.filterText = newText;
RoomManager.sortFilterRoomTreeModel.filterText = newText;
}
}
}

View File

@@ -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 {

View File

@@ -82,6 +82,13 @@ Kirigami.ScrollablePage {
searchField.forceActiveFocus();
}
/**
* @brief Force the search to be updated if the model has a valid search function.
*/
function updateSearch() {
searchTimer.restart();
}
header: QQC2.Control {
padding: Kirigami.Units.largeSpacing
@@ -119,11 +126,18 @@ Kirigami.ScrollablePage {
QQC2.Button {
id: searchButton
icon.name: "search"
display: QQC2.Button.IconOnly
text: i18nc("@action:button", "Search")
onClicked: {
if (typeof model.search === 'function') {
model.search();
}
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Timer {
id: searchTimer

View File

@@ -51,7 +51,8 @@ Kirigami.Dialog {
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage.qml'), {
connection: root.room.connection,
showOnlySpaces: true
showOnlySpaces: true,
showOnlySpacesButton: false
}, {
title: i18nc("@title", "Choose Parent Space")
});
@@ -135,7 +136,8 @@ Kirigami.Dialog {
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage.qml'), {
connection: root.room.connection,
showOnlySpaces: true
showOnlySpaces: true,
showOnlySpacesButton: false
}, {
title: i18nc("@title", "Explore Rooms")
});

View File

@@ -21,20 +21,6 @@ QQC2.Control {
topPadding: 0
bottomPadding: 0
property string selectedSpaceId: RoomManager.lastSpaceId
Connections {
target: RoomManager
function onConnectionChanged() {
// We need to rebind as any previous change will have been overwritten.
selectedSpaceId = RoomManager.lastSpaceId;
}
}
property bool showDirectChats: RoomManager.directChatsActive
signal selectionChanged
signal spacesUpdated
contentItem: Loader {
id: sidebarColumn
z: 0
@@ -100,12 +86,9 @@ QQC2.Control {
source: "user-home-symbolic"
}
checked: root.selectedSpaceId === "" && root.showDirectChats === false
checked: RoomManager.currentSpace.length === 0
onClicked: {
root.showDirectChats = false;
RoomManager.directChatsActive = false;
root.selectedSpaceId = "";
RoomManager.lastSpaceId = "";
RoomManager.currentSpace = "";
root.selectionChanged();
}
@@ -119,7 +102,7 @@ QQC2.Control {
height: Kirigami.Units.iconSizes.smallMedium
text: root.connection.homeNotifications > 0 ? root.connection.homeNotifications : ""
visible: root.connection.homeNotifications > 0 && (root.selectedSpaceId !== "" || root.showDirectChats === true)
visible: root.connection.homeNotifications > 0 && (RoomManager.currentSpace.length > 0 || root.showDirectChats === true)
color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
@@ -149,12 +132,9 @@ QQC2.Control {
source: "system-users"
}
checked: root.showDirectChats === true
checked: RoomManager.currentSpace === "DM"
onClicked: {
root.showDirectChats = true;
RoomManager.directChatsActive = true;
root.selectedSpaceId = "";
RoomManager.lastSpaceId = "";
RoomManager.currentSpace = "DM";
root.selectionChanged();
}
@@ -187,17 +167,7 @@ QQC2.Control {
}
Repeater {
model: SortFilterSpaceListModel {
sourceModel: RoomListModel {
connection: root.connection
}
onLayoutChanged: root.spacesUpdated()
}
onCountChanged: {
if (!root.connection.room(root.selectedSpaceId)) {
root.selectedSpaceId = "";
}
}
model: RoomManager.sortFilterSpaceListModel
delegate: AvatarTabButton {
id: spaceDelegate
@@ -215,17 +185,10 @@ QQC2.Control {
source: avatar ? ("image://mxc/" + avatar) : ""
onSelected: {
root.showDirectChats = false;
RoomManager.directChatsActive = false;
if (!SpaceHierarchyCache.isSpaceChild(roomId, RoomManager.currentRoom.id) || root.selectedSpaceId == roomId) {
RoomManager.resolveResource(currentRoom.id);
} else {
RoomManager.lastSpaceId = currentRoom.id;
}
root.selectedSpaceId = roomId;
root.selectionChanged();
RoomManager.resolveResource(spaceDelegate.roomId);
RoomManager.currentSpace = spaceDelegate.roomId;
}
checked: root.selectedSpaceId === roomId
checked: RoomManager.currentSpace === roomId
onContextMenuRequested: root.createContextMenu(currentRoom)
QQC2.Label {
@@ -238,7 +201,7 @@ QQC2.Control {
height: Kirigami.Units.iconSizes.smallMedium
text: spaceDelegate.currentRoom.childrenNotificationCount > 0 ? spaceDelegate.currentRoom.childrenNotificationCount : ""
visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && root.selectedSpaceId != spaceDelegate.roomId
visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && RoomManager.currentSpace != spaceDelegate.roomId
color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter

View File

@@ -97,7 +97,7 @@ Item {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
text: root.memberCount + (root.topic !== "" ? i18nc("number of room members", " members - ") + root.topic : i18nc("number of room members", " members"))
text: root.topic.length > 0 ? i18ncp("number of room members", "%1 member - ", "%1 members - ", root.memberCount) + root.topic : i18ncp("number of room members", "%1 member", "%1 members", root.memberCount)
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
textFormat: Text.PlainText
@@ -106,7 +106,7 @@ Item {
}
QQC2.ToolButton {
visible: root.isSpace && root.canAddChildren
text: i18nc("@button", "Add new child")
text: i18nc("@button", "Add new room")
icon.name: "list-add"
display: QQC2.AbstractButton.IconOnly
onClicked: root.createRoom()

View File

@@ -10,152 +10,153 @@ import org.kde.kirigami as Kirigami
import org.kde.neochat
import org.kde.neochat.settings
Kirigami.Page {
ColumnLayout {
id: root
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
padding: 0
anchors.fill: parent
ColumnLayout {
id: columnLayout
anchors.fill: parent
spacing: 0
spacing: 0
Item {
id: headerItem
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
implicitHeight: headerColumn.implicitHeight
QQC2.Control {
id: headerItem
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
implicitHeight: headerColumn.implicitHeight
ColumnLayout {
id: headerColumn
anchors.centerIn: headerItem
width: sizeHelper.currentWidth
spacing: Kirigami.Units.largeSpacing
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
}
GroupChatDrawerHeader {
id: header
Layout.fillWidth: true
room: root.currentRoom
ColumnLayout {
id: headerColumn
anchors.centerIn: headerItem
width: sizeHelper.currentWidth
spacing: Kirigami.Units.largeSpacing
GroupChatDrawerHeader {
id: header
Layout.fillWidth: true
room: root.currentRoom
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
QQC2.Button {
visible: root.currentRoom.canSendState("invite")
text: i18nc("@button", "Invite user to space")
icon.name: "list-add-user"
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'InviteUserPage.qml'), {
room: root.currentRoom
}, {
title: i18nc("@title", "Invite a User")
})
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
QQC2.Button {
visible: root.currentRoom.canSendState("invite")
text: i18nc("@button", "Invite user to space")
icon.name: "list-add-user"
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'InviteUserPage.qml'), {
room: root.currentRoom
}, {
title: i18nc("@title", "Invite a User")
})
}
QQC2.Button {
visible: root.currentRoom.canSendState("m.space.child")
text: i18nc("@button", "Add new child")
icon.name: "list-add"
onClicked: _private.createRoom(root.currentRoom.id)
}
QQC2.Button {
text: i18nc("@button", "Leave the space")
icon.name: "go-previous"
onClicked: RoomManager.leaveRoom(root.currentRoom)
}
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18nc("@button", "Space settings")
icon.name: "settings-configure"
display: QQC2.AbstractButton.IconOnly
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'RoomSettings.qml'), {
room: root.currentRoom,
connection: root.currentRoom.connection
}, {
title: i18n("Room Settings")
})
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
}
QQC2.Button {
visible: root.currentRoom.canSendState("m.space.child")
text: i18nc("@button", "Add new room")
icon.name: "list-add"
onClicked: _private.createRoom(root.currentRoom.id)
}
Kirigami.SearchField {
QQC2.Button {
text: i18nc("@button", "Leave the space")
icon.name: "go-previous"
onClicked: RoomManager.leaveRoom(root.currentRoom)
}
Item {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
onTextChanged: spaceChildSortFilterModel.filterText = text
}
QQC2.Button {
text: i18nc("@button", "Space settings")
icon.name: "settings-configure"
display: QQC2.AbstractButton.IconOnly
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'RoomSettings.qml'), {
room: root.currentRoom,
connection: root.currentRoom.connection
}, {
title: i18n("Room Settings")
})
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
}
}
DelegateSizeHelper {
id: sizeHelper
startBreakpoint: Kirigami.Units.gridUnit * 46
endBreakpoint: Kirigami.Units.gridUnit * 66
startPercentWidth: 100
endPercentWidth: 85
maxWidth: Kirigami.Units.gridUnit * 60
parentWidth: columnLayout.width
Kirigami.SearchField {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
onTextChanged: spaceChildSortFilterModel.filterText = text
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
QQC2.ScrollView {
id: hierarchyScrollView
Layout.fillWidth: true
Layout.fillHeight: true
visible: !spaceChildrenModel.loading
DelegateSizeHelper {
id: sizeHelper
startBreakpoint: Kirigami.Units.gridUnit * 46
endBreakpoint: Kirigami.Units.gridUnit * 66
startPercentWidth: 100
endPercentWidth: 85
maxWidth: Kirigami.Units.gridUnit * 60
TreeView {
id: spaceTree
columnWidthProvider: function (column) {
return spaceTree.width;
}
clip: true
model: SpaceChildSortFilterModel {
id: spaceChildSortFilterModel
sourceModel: SpaceChildrenModel {
id: spaceChildrenModel
space: root.currentRoom
}
}
delegate: SpaceHierarchyDelegate {
onCreateRoom: _private.createRoom(roomId)
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: spaceChildrenModel.loading
Loader {
active: spaceChildrenModel.loading
anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder {}
}
parentWidth: root.width
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Separator {
Layout.fillWidth: true
}
QQC2.ScrollView {
id: hierarchyScrollView
Layout.fillWidth: true
Layout.fillHeight: true
visible: !spaceChildrenModel.loading
TreeView {
id: spaceTree
columnWidthProvider: function (column) {
return spaceTree.width;
}
clip: true
model: SpaceChildSortFilterModel {
id: spaceChildSortFilterModel
sourceModel: SpaceChildrenModel {
id: spaceChildrenModel
space: root.currentRoom
}
}
delegate: SpaceHierarchyDelegate {
onCreateRoom: _private.createRoom(roomId)
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
}
}
QQC2.Control {
Layout.fillWidth: true
Layout.fillHeight: true
visible: spaceChildrenModel.loading
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
}
Loader {
active: spaceChildrenModel.loading
anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder {}
}
}
QtObject {
id: _private
function createRoom(parentId) {
@@ -182,3 +183,5 @@ Kirigami.Page {
}
}
}

View File

@@ -27,6 +27,8 @@ QQC2.ScrollView {
}
property bool roomChanging: false
required property Item page
/**
* @brief The TimelineModel to use.
*
@@ -299,14 +301,14 @@ QQC2.ScrollView {
Timer {
id: markReadIfVisibleTimer
running: messageListView.allUnreadVisible() && applicationWindow().active && (root.currentRoom.timelineSize > 0 || root.currentRoom.allHistoryLoaded)
running: messageListView.allUnreadVisible() && applicationWindow().active && (root.currentRoom.timelineSize > 0 || root.currentRoom.allHistoryLoaded) && applicationWindow().pageStack.visibleItems.includes(root.page)
interval: 10000
onTriggered: root.currentRoom.markAllMessagesAsRead()
function reset() {
restart();
running = Qt.binding(function () {
return messageListView.allUnreadVisible() && applicationWindow().active && (root.currentRoom.timelineSize > 0 || root.currentRoom.allHistoryLoaded);
return messageListView.allUnreadVisible() && applicationWindow().active && (root.currentRoom.timelineSize > 0 || root.currentRoom.allHistoryLoaded) && applicationWindow().pageStack.visibleItems.includes(root.page);
});
}
}
@@ -420,4 +422,8 @@ QQC2.ScrollView {
function positionViewAtBeginning() {
messageListView.positionViewAtBeginning();
}
function goToEvent(eventId) {
messageListView.goToEvent(eventId);
}
}

View File

@@ -15,34 +15,20 @@ import org.kde.neochat.accounts
Kirigami.ApplicationWindow {
id: root
property int columnWidth: Kirigami.Units.gridUnit * 13
property RoomListPage roomListPage
property bool roomListLoaded: false
property RoomPage roomPage
property SpaceHomePage spaceHomePage
property NeoChatConnection connection: Controller.activeConnection
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 15
visible: false // Will be overridden in Component.onCompleted
wideScreen: width > columnWidth * 5
wideScreen: width > Kirigami.Units.gridUnit * 65
pageStack {
initialPage: WelcomePage {
showExisting: true
onConnectionChosen: {
pageStack.replace(roomListComponent);
roomListLoaded = true;
roomListPage = pageStack.currentItem;
RoomManager.loadInitialRoom();
}
onConnectionChosen: root.load()
}
globalToolBar.canContainHandles: true
defaultColumnWidth: roomListPage ? roomListPage.currentWidth : 0
globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar
showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0
@@ -52,7 +38,6 @@ Kirigami.ApplicationWindow {
onConnectionChanged: {
CustomEmojiModel.connection = root.connection;
MatrixImageProvider.connection = root.connection;
RoomManager.connection = root.connection;
SpaceHierarchyCache.connection = root.connection;
if (ShareHandler.text && root.connection) {
root.handleShare();
@@ -62,10 +47,7 @@ Kirigami.ApplicationWindow {
Connections {
target: LoginHelper
function onLoaded() {
pageStack.replace(roomListComponent);
roomListLoaded = true;
roomListPage = pageStack.currentItem;
RoomManager.loadInitialRoom();
root.load();
}
}
@@ -126,16 +108,6 @@ Kirigami.ApplicationWindow {
Connections {
target: RoomManager
function onPushRoom(room, event) {
root.roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage.qml'), {
connection: root.connection
});
root.roomPage.forceActiveFocus();
if (event.length > 0) {
roomPage.goToEvent(event);
}
}
function onAskJoinRoom(room) {
joinRoomDialog.createObject(applicationWindow(), {
room: room,
@@ -147,38 +119,6 @@ Kirigami.ApplicationWindow {
root.showUserDetail(user);
}
function onPushSpaceHome(room) {
root.spaceHomePage = pageStack.push(Qt.createComponent('org.kde.neochat', 'SpaceHomePage.qml'));
root.spaceHomePage.forceActiveFocus();
}
function onReplaceRoom(room, event) {
if (root.roomPage) {
pageStack.currentIndex = pageStack.depth - 1;
} else {
pageStack.pop();
root.roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage.qml'), {
connection: root.connection
});
root.spaceHomePage = null;
}
root.roomPage.forceActiveFocus();
if (event.length > 0) {
root.roomPage.goToEvent(event);
}
}
function onReplaceSpaceHome(room) {
if (root.spaceHomePage) {
pageStack.currentIndex = pageStack.depth - 1;
} else {
pageStack.pop();
root.spaceHomePage = pageStack.push(Qt.createComponent('org.kde.neochat', 'SpaceHomePage.qml'));
root.roomPage = null;
}
root.spaceHomePage.forceActiveFocus();
}
function goToEvent(event) {
if (event.length > 0) {
roomItem.goToEvent(event);
@@ -247,7 +187,6 @@ Kirigami.ApplicationWindow {
Component.onCompleted: {
CustomEmojiModel.connection = root.connection;
MatrixImageProvider.connection = root.connection;
RoomManager.connection = root.connection;
SpaceHierarchyCache.connection = root.connection;
WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout);
if (ShareHandler.text && root.connection) {
@@ -319,9 +258,7 @@ Kirigami.ApplicationWindow {
target: AccountRegistry
function onRowsRemoved() {
if (AccountRegistry.rowCount() === 0) {
RoomManager.reset();
pageStack.clear();
roomListLoaded = false;
pageStack.push(Qt.createComponent('org.kde.neochat', '.qml'));
}
}
@@ -476,7 +413,7 @@ Kirigami.ApplicationWindow {
}
}
function handleShare(): void {
const dialog = applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ChooseRoomDialog.qml", {
const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog.qml'), {
connection: root.connection
}, {
title: i18nc("@title", "Share"),
@@ -496,6 +433,15 @@ Kirigami.ApplicationWindow {
}).open();
}
function load() {
pageStack.replace(roomListComponent);
RoomManager.loadInitialRoom();
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage.qml'), {
connection: root.connection
});
roomPage.forceActiveFocus();
}
Component {
id: userDetailDialog
UserDetailDialog {}

View File

@@ -5,12 +5,13 @@
#include "roommanager.h"
#include "chatbarcache.h"
#include "controller.h"
#include "eventhandler.h"
#include "messagecomponenttype.h"
#include "models/timelinemodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
#include <KLocalizedString>
#include <QDesktopServices>
@@ -28,9 +29,12 @@
RoomManager::RoomManager(QObject *parent)
: QObject(parent)
, m_currentRoom(nullptr)
, m_lastCurrentRoom(nullptr)
, m_config(KSharedConfig::openStateConfig())
, m_roomListModel(new RoomListModel(this))
, m_sortFilterRoomListModel(new SortFilterRoomListModel(m_roomListModel, this))
, m_sortFilterSpaceListModel(new SortFilterSpaceListModel(m_roomListModel, this))
, m_roomTreeModel(new RoomTreeModel(this))
, m_sortFilterRoomTreeModel(new SortFilterRoomTreeModel(m_roomTreeModel, this))
, m_timelineModel(new TimelineModel(this))
, m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
@@ -42,6 +46,15 @@ RoomManager::RoomManager(QObject *parent)
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_timelineModel->setRoom(m_currentRoom);
});
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
setConnection(connection);
});
connect(this, &RoomManager::connectionChanged, this, [this]() {
m_roomListModel->setConnection(m_connection);
m_roomTreeModel->setConnection(m_connection);
});
connect(m_sortFilterSpaceListModel, &SortFilterSpaceListModel::layoutChanged, m_sortFilterRoomTreeModel, &SortFilterRoomTreeModel::invalidate);
}
RoomManager::~RoomManager()
@@ -59,6 +72,31 @@ NeoChatRoom *RoomManager::currentRoom() const
return m_currentRoom;
}
RoomListModel *RoomManager::roomListModel() const
{
return m_roomListModel;
}
SortFilterRoomListModel *RoomManager::sortFilterRoomListModel() const
{
return m_sortFilterRoomListModel;
}
SortFilterSpaceListModel *RoomManager::sortFilterSpaceListModel() const
{
return m_sortFilterSpaceListModel;
}
RoomTreeModel *RoomManager::roomTreeModel() const
{
return m_roomTreeModel;
}
SortFilterRoomTreeModel *RoomManager::sortFilterRoomTreeModel() const
{
return m_sortFilterRoomTreeModel;
}
TimelineModel *RoomManager::timelineModel() const
{
return m_timelineModel;
@@ -87,6 +125,11 @@ void RoomManager::resolveResource(const QString &idOrUri, const QString &action)
return;
}
if (uri.type() == Uri::NonMatrix && action == "qr"_ls) {
Q_EMIT externalUrl(uri.toUrl());
return;
}
if (uri.type() != Uri::NonMatrix) {
if (!m_connection) {
return;
@@ -99,7 +142,7 @@ void RoomManager::resolveResource(const QString &idOrUri, const QString &action)
const auto result = visitResource(m_connection, uri);
if (result == Quotient::CouldNotResolve) {
if (uri.type() == Uri::RoomAlias || uri.type() == Uri::RoomId) {
if ((uri.type() == Uri::RoomAlias || uri.type() == Uri::RoomId) && action != "no_join"_ls) {
Q_EMIT askJoinRoom(uri.primaryId());
}
} else { // Invalid cases should have been eliminated earlier
@@ -121,6 +164,14 @@ void RoomManager::maximizeMedia(int index)
Q_EMIT showMaximizedMedia(index);
}
void RoomManager::maximizeCode(const QVariantMap &author, const QDateTime &time, const QString &codeText, const QString &language)
{
if (codeText.isEmpty()) {
return;
}
Q_EMIT showMaximizedCode(author, time, codeText, language);
}
void RoomManager::requestFullScreenClose()
{
Q_EMIT closeFullScreen();
@@ -182,72 +233,17 @@ void RoomManager::loadInitialRoom()
connect(this, &RoomManager::connectionChanged, this, &RoomManager::openRoomForActiveConnection);
}
QString RoomManager::lastSpaceId() const
{
if (!m_connection) {
return {};
}
return m_lastSpaceConfig.readEntry(m_connection->userId(), QString());
}
void RoomManager::setLastSpaceId(const QString &lastSpaceId)
{
if (!m_connection) {
return;
}
const auto currentLastSpaceId = m_lastSpaceConfig.readEntry(m_connection->userId(), QString());
if (lastSpaceId == currentLastSpaceId) {
return;
}
m_lastSpaceConfig.writeEntry(m_connection->userId(), lastSpaceId);
Q_EMIT lastSpaceIdChanged();
}
bool RoomManager::directChatsActive() const
{
if (!m_connection) {
return {};
}
return m_directChatsConfig.readEntry(m_connection->userId(), bool());
}
void RoomManager::setDirectChatsActive(bool directChatsActive)
{
if (!m_connection) {
return;
}
const auto currentDirectChatsActive = m_directChatsConfig.readEntry(m_connection->userId(), bool());
if (directChatsActive == currentDirectChatsActive) {
return;
}
m_directChatsConfig.writeEntry(m_connection->userId(), directChatsActive);
Q_EMIT directChatsActiveChanged();
}
void RoomManager::openRoomForActiveConnection()
{
if (!m_connection) {
return;
m_currentRoom = nullptr;
}
// Read from last open room
QString roomId = m_lastRoomConfig.readEntry(m_connection->userId(), QString());
// TODO remove legacy check at some point.
if (roomId.isEmpty()) {
roomId = NeoChatConfig::self()->openRoom();
}
if (!roomId.isEmpty()) {
// Here we can cast because the controller has been configured to
// return NeoChatRoom instead of simple Quotient::Room
const auto room = qobject_cast<NeoChatRoom *>(m_connection->room(roomId));
if (room) {
resolveResource(room->id());
}
if (m_lastRoomConfig.readEntry(m_connection->userId(), QString()).isEmpty()) {
setCurrentRoom({});
} else {
resolveResource(m_lastRoomConfig.readEntry(m_connection->userId(), QString()));
}
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
}
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
@@ -268,10 +264,9 @@ UriResolveResult RoomManager::visitUser(User *user, const QString &action)
return Quotient::UriResolved;
}
void RoomManager::visitRoom(Room *room, const QString &eventId)
void RoomManager::visitRoom(Room *r, const QString &eventId)
{
auto neoChatRoom = qobject_cast<NeoChatRoom *>(room);
Q_ASSERT(neoChatRoom != nullptr);
auto room = qobject_cast<NeoChatRoom *>(r);
if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
m_currentRoom->editCache()->setEditId({});
@@ -280,41 +275,26 @@ void RoomManager::visitRoom(Room *room, const QString &eventId)
// We're doing these things here because it is critical that they are switched at the same time
if (m_chatDocumentHandler->document()) {
m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
m_chatDocumentHandler->setRoom(neoChatRoom);
m_chatDocumentHandler->document()->textDocument()->setPlainText(neoChatRoom->mainCache()->savedText());
neoChatRoom->mainCache()->setText(neoChatRoom->mainCache()->savedText());
} else {
m_chatDocumentHandler->setRoom(neoChatRoom);
}
}
if (m_currentRoom) {
if (m_currentRoom->id() == room->id()) {
Q_EMIT goToEvent(eventId);
} else {
m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom);
Q_EMIT currentRoomChanged();
if (neoChatRoom->isSpace()) {
m_lastSpaceConfig.writeEntry(m_connection->userId(), room->id());
Q_EMIT replaceSpaceHome(neoChatRoom);
} else {
Q_EMIT replaceRoom(neoChatRoom, eventId);
m_chatDocumentHandler->setRoom(room);
if (room) {
m_chatDocumentHandler->document()->textDocument()->setPlainText(room->mainCache()->savedText());
room->mainCache()->setText(room->mainCache()->savedText());
}
}
} else {
m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom);
Q_EMIT currentRoomChanged();
if (neoChatRoom->isSpace()) {
m_lastSpaceConfig.writeEntry(m_connection->userId(), room->id());
Q_EMIT pushSpaceHome(neoChatRoom);
} else {
Q_EMIT pushRoom(neoChatRoom, eventId);
m_chatDocumentHandler->setRoom(room);
}
}
// Save last open room
m_lastRoomConfig.writeEntry(m_connection->userId(), room->id());
if (!room) {
setCurrentRoom({});
return;
}
if (m_currentRoom && m_currentRoom->id() == room->id()) {
Q_EMIT goToEvent(eventId);
} else {
setCurrentRoom(room->id());
}
}
void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QStringList &viaServers)
@@ -351,30 +331,22 @@ void RoomManager::knockRoom(Quotient::Connection *account, const QString &roomAl
bool RoomManager::visitNonMatrix(const QUrl &url)
{
Q_EMIT externalUrl(url);
UrlHelper().openUrl(url);
return true;
}
void RoomManager::reset()
{
m_arg = QString();
m_currentRoom = nullptr;
m_lastCurrentRoom = nullptr;
Q_EMIT currentRoomChanged();
}
void RoomManager::leaveRoom(NeoChatRoom *room)
{
if (!room) {
return;
}
if (m_lastCurrentRoom && room->id() == m_lastCurrentRoom->id()) {
m_lastCurrentRoom = nullptr;
}
if (m_currentRoom && m_currentRoom->id() == room->id()) {
m_currentRoom = m_lastCurrentRoom;
m_lastCurrentRoom = nullptr;
Q_EMIT currentRoomChanged();
setCurrentRoom({});
}
if (m_currentSpaceId == room->id()) {
setCurrentSpace({});
}
room->forget();
@@ -392,11 +364,6 @@ void RoomManager::setChatDocumentHandler(ChatDocumentHandler *handler)
Q_EMIT chatDocumentHandlerChanged();
}
NeoChatConnection *RoomManager::connection() const
{
return m_connection;
}
void RoomManager::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
@@ -406,4 +373,76 @@ void RoomManager::setConnection(NeoChatConnection *connection)
Q_EMIT connectionChanged();
}
void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
{
m_currentSpaceId = spaceId;
// This need to happen before the signal so TreeView.expandRecursively() can work nicely.
m_sortFilterRoomTreeModel->setActiveSpaceId(m_currentSpaceId);
m_sortFilterRoomTreeModel->setMode(m_currentSpaceId == QLatin1String("DM") ? SortFilterRoomTreeModel::DirectChats : SortFilterRoomTreeModel::Rooms);
Q_EMIT currentSpaceChanged();
m_lastSpaceConfig.writeEntry(m_connection->userId(), spaceId);
if (!setRoom) {
return;
}
if (spaceId.length() > 3) {
resolveResource(spaceId, "no_join"_ls);
} else {
visitRoom({}, {});
}
}
void RoomManager::setCurrentRoom(const QString &roomId)
{
if (roomId.isEmpty()) {
m_currentRoom = nullptr;
} else {
m_currentRoom = dynamic_cast<NeoChatRoom *>(m_connection->room(roomId));
}
Q_EMIT currentRoomChanged();
if (m_connection) {
m_lastRoomConfig.writeEntry(m_connection->userId(), roomId);
}
if (roomId.isEmpty()) {
return;
}
if (m_currentRoom->isSpace()) {
return;
}
if (m_currentRoom->isDirectChat()) {
if (m_currentSpaceId != "DM"_ls) {
setCurrentSpace("DM"_ls, false);
}
return;
}
const auto &parentSpaces = SpaceHierarchyCache::instance().parentSpaces(roomId);
if (parentSpaces.contains(m_currentSpaceId)) {
return;
}
if (const auto &parent = m_connection->room(m_currentRoom->canonicalParent())) {
for (const auto &parentParent : SpaceHierarchyCache::instance().parentSpaces(parent->id())) {
if (SpaceHierarchyCache::instance().parentSpaces(parentParent).isEmpty()) {
setCurrentSpace(parentParent, false);
return;
}
}
setCurrentSpace(parent->id(), false);
return;
}
for (const auto &space : parentSpaces) {
if (SpaceHierarchyCache::instance().parentSpaces(space).isEmpty()) {
setCurrentSpace(space, false);
return;
}
}
setCurrentSpace({}, false);
}
QString RoomManager::currentSpace() const
{
return m_currentSpaceId;
}
#include "moc_roommanager.cpp"

View File

@@ -9,24 +9,22 @@
#include <QQmlEngine>
#include <Quotient/room.h>
#include <Quotient/uriresolver.h>
#include <KConfigGroup>
#include "chatdocumenthandler.h"
#include "enums/messagecomponenttype.h"
#include "eventhandler.h"
#include "models/mediamessagefiltermodel.h"
#include "models/messagefiltermodel.h"
#include "models/roomlistmodel.h"
#include "models/roomtreemodel.h"
#include "models/sortfilterroomlistmodel.h"
#include "models/sortfilterroomtreemodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/timelinemodel.h"
class NeoChatRoom;
class NeoChatConnection;
namespace Quotient
{
class Room;
class User;
}
using namespace Quotient;
/**
@@ -45,8 +43,6 @@ class RoomManager : public QObject, public UriResolverBase
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The current open room in NeoChat, if any.
*
@@ -54,6 +50,47 @@ class RoomManager : public QObject, public UriResolverBase
*/
Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged)
/**
* @brief The id of the space currently opened in the space drawer.
*
* If this is an empty string, the uncategorized rooms are shown.
* If it is the string "DM", the DMs are shown.
*/
Q_PROPERTY(QString currentSpace READ currentSpace WRITE setCurrentSpace NOTIFY currentSpaceChanged)
/**
* @brief The RoomListModel that should be used for linear room visualisation.
*
* The connection the model uses to get the data will be updated by this class
* so there is no need to do this manually or replace the model when the connection
* changes.
*/
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel CONSTANT)
/**
* @brief The SortFilterRoomListModel that should be used for room visualisation.
*/
Q_PROPERTY(SortFilterRoomListModel *sortFilterRoomListModel READ sortFilterRoomListModel CONSTANT)
/**
* @brief The SortFilterSpaceListModel that should be used for space visualisation.
*/
Q_PROPERTY(SortFilterSpaceListModel *sortFilterSpaceListModel READ sortFilterSpaceListModel CONSTANT)
/**
* @brief The RoomTreeModel that should be used for room visualisation.
*
* The connection the model uses to get the data will be updated by this class
* so there is no need to do this manually or replace the model when the connection
* changes.
*/
Q_PROPERTY(RoomTreeModel *roomTreeModel READ roomTreeModel CONSTANT)
/**
* @brief The SortFilterRoomTreeModel that should be used for room visualisation.
*/
Q_PROPERTY(SortFilterRoomTreeModel *sortFilterRoomTreeModel READ sortFilterRoomTreeModel CONSTANT)
/**
* @brief The TimelineModel that should be used for room message visualisation.
*
@@ -89,16 +126,6 @@ class RoomManager : public QObject, public UriResolverBase
*/
Q_PROPERTY(bool hasOpenRoom READ hasOpenRoom NOTIFY currentRoomChanged)
/**
* @brief The room ID of the last space entered.
*/
Q_PROPERTY(QString lastSpaceId READ lastSpaceId WRITE setLastSpaceId NOTIFY directChatsActiveChanged)
/**
* @brief Whether the last SpaceDrawer category selected was direct chats.
*/
Q_PROPERTY(bool directChatsActive READ directChatsActive WRITE setDirectChatsActive NOTIFY directChatsActiveChanged)
/**
* @brief The ChatDocumentHandler for the open room.
*
@@ -117,6 +144,12 @@ public:
NeoChatRoom *currentRoom() const;
RoomListModel *roomListModel() const;
SortFilterRoomListModel *sortFilterRoomListModel() const;
SortFilterSpaceListModel *sortFilterSpaceListModel() const;
RoomTreeModel *roomTreeModel() const;
SortFilterRoomTreeModel *sortFilterRoomTreeModel() const;
TimelineModel *timelineModel() const;
MessageFilterModel *messageFilterModel() const;
MediaMessageFilterModel *mediaMessageFilterModel() const;
@@ -170,6 +203,8 @@ public:
*/
Q_INVOKABLE void maximizeMedia(int index);
Q_INVOKABLE void maximizeCode(const QVariantMap &author, const QDateTime &time, const QString &codeText, const QString &language);
/**
* @brief Request that any full screen overlay currently open closes.
*/
@@ -185,11 +220,6 @@ public:
*/
Q_INVOKABLE void viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText = {});
/**
* @brief Call this when the current used connection is dropped.
*/
Q_INVOKABLE void reset();
ChatDocumentHandler *chatDocumentHandler() const;
void setChatDocumentHandler(ChatDocumentHandler *handler);
@@ -198,13 +228,14 @@ public:
*/
void setUrlArgument(const QString &arg);
QString lastSpaceId() const;
void setLastSpaceId(const QString &lastSpaceId);
QString currentSpace() const;
bool directChatsActive() const;
void setDirectChatsActive(bool directChatsActive);
NeoChatConnection *connection() const;
/**
* @brief Set the current connection
*/
void setConnection(NeoChatConnection *connection);
Q_SIGNALS:
@@ -213,59 +244,11 @@ Q_SIGNALS:
void currentRoomChanged();
/**
* @brief Push a new room page.
*
* Signal triggered when the main window pageStack should push a new page with
* the message list for the given room.
*
* @param room the room to be shown on the new page.
* @param event the event to got to if available.
*/
void pushRoom(NeoChatRoom *room, const QString &event);
/**
* @brief Replace the existing room.
*
* Signal triggered when the room displayed by the message list should be changed.
*
* @param room the room to be shown on the new page.
* @param event the event to got to if available.
*/
void replaceRoom(NeoChatRoom *room, const QString &event);
/**
* @brief Push a new space home page.
*
* Signal triggered when the main window pageStack should push a new page with
* the space home for the given space room.
*
* @param spaceRoom the space room to be shown on the new page.
*/
void pushSpaceHome(NeoChatRoom *spaceRoom);
/**
* @brief Replace the existing space home.
*
* Signal triggered when the currently displayed room page should be changed
* to the space home for the given space room.
*
* @param spaceRoom the space room to be shown on the new page.
*/
void replaceSpaceHome(NeoChatRoom *spaceRoom);
/**
* @brief Go to the specified event in the current room.
*/
void goToEvent(const QString &event);
/**
* @brief Open room in a new window.
*
* Signal triggered when a room needs to be opened in a new window.
*/
void openRoomInNewWindow(NeoChatRoom *room);
/**
* @brief Show details for the given user.
*
@@ -283,6 +266,11 @@ Q_SIGNALS:
*/
void showMaximizedMedia(int index);
/**
* @brief Request a block of code is shown maximized.
*/
void showMaximizedCode(const QVariantMap &author, const QDateTime &time, const QString &codeText, const QString &language);
/**
* @brief Request that any full screen overlay closes.
*/
@@ -331,15 +319,24 @@ Q_SIGNALS:
void connectionChanged();
void directChatsActiveChanged();
void lastSpaceIdChanged();
void externalUrl(const QUrl &url);
void currentSpaceChanged();
private:
void openRoomForActiveConnection();
/** The room currently being shown in the main view (RoomPage.qml). This can be null, if there is no room.
* If this is a space, the space home page is shown.
*/
QPointer<NeoChatRoom> m_currentRoom;
NeoChatRoom *m_lastCurrentRoom;
/** The id of the space currently opened in the space drawer. If this is empty, the uncategorized rooms are shown.
* If it is "DM", the direct messages are shown. Otherwise it's the id of a toplevel space.
*/
QString m_currentSpaceId;
QString m_arg;
KSharedConfig::Ptr m_config;
KConfigGroup m_lastRoomConfig;
@@ -347,11 +344,22 @@ private:
KConfigGroup m_directChatsConfig;
QPointer<ChatDocumentHandler> m_chatDocumentHandler;
RoomListModel *m_roomListModel;
SortFilterRoomListModel *m_sortFilterRoomListModel;
SortFilterSpaceListModel *m_sortFilterSpaceListModel;
RoomTreeModel *m_roomTreeModel;
SortFilterRoomTreeModel *m_sortFilterRoomTreeModel;
TimelineModel *m_timelineModel;
MessageFilterModel *m_messageFilterModel;
MediaMessageFilterModel *m_mediaMessageFilterModel;
NeoChatConnection *m_connection;
void setCurrentRoom(const QString &roomId);
// Space ID, "DM", or empty string
void setCurrentSpace(const QString &spaceId, bool setRoom = true);
/**
* @brief Resolve a user URI.
*

View File

@@ -9,6 +9,7 @@
#include "neochatroom.h"
#include "roomlistmodel.h"
#include "roommanager.h"
#include "sortfilterroomlistmodel.h"
#include "windowcontroller.h"
RemoteImage Runner::serializeImage(const QImage &image)
@@ -28,9 +29,9 @@ RemoteImage Runner::serializeImage(const QImage &image)
Runner::Runner()
: QObject()
, m_sourceModel(new RoomListModel(this))
, m_model(new SortFilterRoomListModel(m_sourceModel, this))
{
m_sourceModel = new RoomListModel(this);
m_model.setSourceModel(m_sourceModel);
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
m_sourceModel->setConnection(Controller::instance().activeConnection());
});
@@ -44,13 +45,13 @@ Runner::Runner()
void Runner::setRoomListModel(RoomListModel *roomListModel)
{
m_model.setSourceModel(roomListModel);
m_model->setSourceModel(roomListModel);
Q_EMIT roomListModelChanged();
}
RoomListModel *Runner::roomListModel() const
{
return dynamic_cast<RoomListModel *>(m_model.sourceModel());
return dynamic_cast<RoomListModel *>(m_model->sourceModel());
}
RemoteActions Runner::Actions()
@@ -60,22 +61,22 @@ RemoteActions Runner::Actions()
RemoteMatches Runner::Match(const QString &searchTerm)
{
m_model.setFilterText(searchTerm);
m_model->setFilterText(searchTerm);
RemoteMatches matches;
for (int i = 0; i < m_model.rowCount(); ++i) {
for (int i = 0; i < m_model->rowCount(); ++i) {
RemoteMatch match;
const QString name = m_model.data(m_model.index(i, 0), RoomListModel::DisplayNameRole).toString();
const QString name = m_model->data(m_model->index(i, 0), RoomListModel::DisplayNameRole).toString();
match.iconName = QStringLiteral("org.kde.neochat");
match.id = m_model.data(m_model.index(i, 0), RoomListModel::RoomIdRole).toString();
match.id = m_model->data(m_model->index(i, 0), RoomListModel::RoomIdRole).toString();
match.text = name;
match.relevance = 1;
const RemoteImage remoteImage = serializeImage(m_model.data(m_model.index(i, 0), RoomListModel::AvatarImageRole).value<QImage>());
const RemoteImage remoteImage = serializeImage(m_model->data(m_model->index(i, 0), RoomListModel::AvatarImageRole).value<QImage>());
match.properties.insert(QStringLiteral("icon-data"), QVariant::fromValue(remoteImage));
match.properties.insert(QStringLiteral("subtext"), m_model.data(m_model.index(i, 0), RoomListModel::TopicRole).toString());
match.properties.insert(QStringLiteral("subtext"), m_model->data(m_model->index(i, 0), RoomListModel::TopicRole).toString());
if (name.compare(searchTerm, Qt::CaseInsensitive) == 0) {
match.type = ExactMatch;

View File

@@ -203,7 +203,7 @@ Q_SIGNALS:
private:
RemoteImage serializeImage(const QImage &image);
SortFilterRoomListModel m_model;
RoomListModel *m_sourceModel;
QPointer<RoomListModel> m_sourceModel;
QPointer<SortFilterRoomListModel> m_model;
Runner();
};

View File

@@ -4,6 +4,7 @@
qt_add_library(settings STATIC)
qt_add_qml_module(settings
URI org.kde.neochat.settings
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/settings
QML_FILES
NeoChatSettings.qml
RoomSettings.qml

View File

@@ -17,6 +17,7 @@ FormCard.AbstractFormDelegate {
required property string id
required property string timestamp
required property string displayName
required property int type
property bool editDeviceName: false
property bool showVerifyButton
@@ -98,7 +99,7 @@ FormCard.AbstractFormDelegate {
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
visible: root.showVerifyButton
visible: root.showVerifyButton && root.type !== DevicesModel.Verified
action: Kirigami.Action {
id: verifyDeviceAction
text: i18n("Verify device")
@@ -112,6 +113,21 @@ FormCard.AbstractFormDelegate {
delay: Kirigami.Units.toolTipDelay
}
}
Kirigami.Icon {
visible: root.showVerifyButton && root.type === DevicesModel.Verified
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
source: "security-high"
HoverHandler {
id: verifyHover
}
QQC2.ToolTip {
text: i18nc("This device is verified", "Verified")
delay: Kirigami.Units.toolTipDelay
visible: verifyHover.hovered
}
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {

Some files were not shown because too many files have changed in this diff Show More