Compare commits
111 Commits
work/tobia
...
release/23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da2b6504ae | ||
|
|
49be98662d | ||
|
|
a2c37a93d0 | ||
|
|
d6b575f017 | ||
|
|
063d91c25b | ||
|
|
54cf9ad495 | ||
|
|
aa569e127b | ||
|
|
7e7b9728af | ||
|
|
c72cf64369 | ||
|
|
7aa68ce896 | ||
|
|
c204b36aa9 | ||
|
|
38584bc735 | ||
|
|
2e873fc49b | ||
|
|
5e898d68c2 | ||
|
|
ed9d2e1d02 | ||
|
|
07fb6bd90a | ||
|
|
11003775a1 | ||
|
|
79d8f35dca | ||
|
|
a1d5727a10 | ||
|
|
4e9c4f9752 | ||
|
|
d0e71114de | ||
|
|
a6db2aac70 | ||
|
|
a829b6c991 | ||
|
|
05a6d055b4 | ||
|
|
64f8df4dd2 | ||
|
|
8dcc231bdc | ||
|
|
5efe5cbb88 | ||
|
|
d2f8392ea2 | ||
|
|
57d79bf3fa | ||
|
|
13a573c7f6 | ||
|
|
64a1de8729 | ||
|
|
669ad9f6e2 | ||
|
|
269e29244a | ||
|
|
0b6da7b799 | ||
|
|
6eee09a560 | ||
|
|
2ad5a593b4 | ||
|
|
970ff95fd0 | ||
|
|
5a1a3fbd06 | ||
|
|
9493530b9c | ||
|
|
ec76df5e47 | ||
|
|
095c1fa4c3 | ||
|
|
41874701e6 | ||
|
|
58de466ad5 | ||
|
|
5800180836 | ||
|
|
2b2a864041 | ||
|
|
90399930ec | ||
|
|
8c4ac46a69 | ||
|
|
31951db498 | ||
|
|
fd593bb483 | ||
|
|
1243aef243 | ||
|
|
41ce874f77 | ||
|
|
4d69b70e02 | ||
|
|
b5a46b35f8 | ||
|
|
33ab3fd921 | ||
|
|
357d418cb4 | ||
|
|
76d3484a1e | ||
|
|
aef86017b2 | ||
|
|
40358cba85 | ||
|
|
3ee6877b15 | ||
|
|
e7a0d90746 | ||
|
|
484188c4e1 | ||
|
|
3e967e53bf | ||
|
|
c2f1e0de53 | ||
|
|
69a1d5477e | ||
|
|
a8f29de87a | ||
|
|
275a4f9222 | ||
|
|
95f4c5b9e1 | ||
|
|
d7412c965c | ||
|
|
a94dfde7f7 | ||
|
|
6c7e12c471 | ||
|
|
9949ba8f6d | ||
|
|
2c3ce1e88b | ||
|
|
33533a75bd | ||
|
|
8c6671265c | ||
|
|
bfa643557d | ||
|
|
2b0f6a1d4d | ||
|
|
0e8deee005 | ||
|
|
fe60d11ba3 | ||
|
|
e9d3862537 | ||
|
|
e03b2e9adf | ||
|
|
566743bc19 | ||
|
|
a3b4f05e2c | ||
|
|
977e9bbf99 | ||
|
|
ad11a509aa | ||
|
|
abbe68f9ab | ||
|
|
b7b0749cd8 | ||
|
|
f8ae06f8a7 | ||
|
|
9946ba008b | ||
|
|
376916212d | ||
|
|
1ea6ffe416 | ||
|
|
2d99ae0404 | ||
|
|
88eeed74da | ||
|
|
0032d417ac | ||
|
|
bd9fb097e3 | ||
|
|
ecd7a5edff | ||
|
|
d3f0902835 | ||
|
|
6eb258f8a3 | ||
|
|
dff908edd2 | ||
|
|
7e06606cbd | ||
|
|
f78f92cd0f | ||
|
|
c4fdfa22d9 | ||
|
|
3e5d1429f7 | ||
|
|
76b5463dac | ||
|
|
8b26a9f45f | ||
|
|
da1c664f94 | ||
|
|
498cfedfea | ||
|
|
8983129520 | ||
|
|
1c8acb2acd | ||
|
|
53fa4be4d8 | ||
|
|
bf30152e33 | ||
|
|
d86c61ac8a |
@@ -41,7 +41,6 @@
|
||||
{
|
||||
"name": "olm",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DOLM_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -103,7 +102,6 @@
|
||||
{
|
||||
"name": "libQuotient",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DBUILD_TESTING=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -119,7 +117,6 @@
|
||||
{
|
||||
"name": "cmark",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DCMARK_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -136,7 +133,6 @@
|
||||
{
|
||||
"name": "qcoro",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,4 +10,3 @@ kate.project.ctags.*
|
||||
*.user
|
||||
.flatpak-builder/
|
||||
.idea/
|
||||
cmake-build-*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
|
||||
@@ -11,7 +11,6 @@ Dependencies:
|
||||
'frameworks/kconfig': '@stable'
|
||||
'frameworks/syntax-highlighting': '@stable'
|
||||
'frameworks/kitemmodels': '@stable'
|
||||
'frameworks/kquickcharts': '@stable'
|
||||
'frameworks/knotifications': '@stable'
|
||||
'libraries/kquickimageeditor': '@stable'
|
||||
'frameworks/sonnet': '@stable'
|
||||
@@ -39,7 +38,6 @@ Dependencies:
|
||||
'frameworks/kconfig': '@latest-kf6'
|
||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||
'frameworks/kitemmodels': '@latest-kf6'
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
'libraries/kquickimageeditor': '@latest-kf6'
|
||||
'frameworks/sonnet': '@latest-kf6'
|
||||
|
||||
@@ -8,16 +8,16 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "07")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "04")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "3")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "5.105.0")
|
||||
set(KF_MIN_VERSION "5.91.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
if (ANDROID)
|
||||
set(QT_MIN_VERSION "5.15.8")
|
||||
set(QT_MIN_VERSION "5.15.10")
|
||||
endif()
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
@@ -39,7 +39,6 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
include(ECMAddAppIcon)
|
||||
include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -94,12 +93,8 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
if(QT_MAJOR_VERSION STREQUAL "6" AND NOT ANDROID AND NOT WIN32)
|
||||
set(QUOTIENT_SUFFIX "Qt6")
|
||||
endif()
|
||||
|
||||
find_package(Quotient${QUOTIENT_SUFFIX} 0.6)
|
||||
set_package_properties(Quotient${QUOTIENT_SUFFIX} PROPERTIES
|
||||
find_package(Quotient 0.6)
|
||||
set_package_properties(Quotient PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
URL "https://github.com/quotient-im/libQuotient/"
|
||||
@@ -116,7 +111,6 @@ set_package_properties(cmark PROPERTIES
|
||||
|
||||
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
||||
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
|
||||
ecm_find_qmlmodule(org.kde.quickcharts 1.0)
|
||||
|
||||
find_package(KQuickImageEditor COMPONENTS)
|
||||
set_package_properties(KQuickImageEditor PROPERTIES
|
||||
@@ -136,12 +130,13 @@ set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
|
||||
if(NOT Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
find_package(Sqlite3)
|
||||
|
||||
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
||||
cmake_policy(SET CMP0063 OLD)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
find_package(Sqlite3)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
||||
endif()
|
||||
|
||||
@@ -155,7 +150,7 @@ install(FILES org.kde.neochat.tray.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/h
|
||||
add_definitions(-DQT_NO_FOREACH)
|
||||
|
||||
add_subdirectory(src)
|
||||
if (BUILD_TESTING AND Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
||||
add_subdirectory(autotests)
|
||||
endif()
|
||||
|
||||
100
README.md
100
README.md
@@ -1,51 +1,24 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
||||
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||
SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
|
||||
# NeoChat
|
||||
|
||||
A Qt/QML based Matrix client.
|
||||
NeoChat is a client for Matrix, the decentralized communication protocol for instant
|
||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably Kirigami,
|
||||
KConfig and KI18n.
|
||||
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/projects/images/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
|
||||
## Introduction
|
||||
|
||||
NeoChat is a client for [Matrix](https://matrix.org), the decentralized communication protocol for instant
|
||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably [Kirigami](https://invent.kde.org/frameworks/kirigami)
|
||||
to provide a convergent experience across multiple platforms.
|
||||
|
||||
NeoChat also make use of other KDE Frameworks as well as [libQuotient](https://github.com/quotient-im/libQuotient), a
|
||||
Qt-based SDK for the [Matrix Protocol](https://spec.matrix.org/).
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions
|
||||
of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly
|
||||
evolving but the aim remains to provide eventual support for the entire spec.
|
||||
|
||||
Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:
|
||||
- Polls - MSC3381
|
||||
- Sticker Packs - MSC2545
|
||||
- Location Events - MSC3488
|
||||
|
||||
## Get it
|
||||
|
||||
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
|
||||
A stable release [is available](https://apps.kde.org/neochat) for download for Linux distributions.
|
||||
|
||||
In addition to the stable builds, unstable nightly builds are available for all platforms. These can be downloaded
|
||||
from the [binary factory](https://binary-factory.kde.org/). There are unstable versions for the following platforms
|
||||
in addition to stable ones:
|
||||
- Android
|
||||
- MacOS
|
||||
- Windows
|
||||
|
||||
Additionally the nightly Flatpak version can be obtained from the nightly Flatpak repo using the following commands in your terminal:
|
||||
Along with the stable release, a Flatpak version is available for the nightly
|
||||
version:
|
||||
|
||||
```
|
||||
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
@@ -53,53 +26,42 @@ flatpak remote-add --if-not-exists kdeapps --from https://distribute.kde.org/kde
|
||||
flatpak install kdeapps org.kde.neochat
|
||||
```
|
||||
|
||||
The unstable Android version can also be obtained from the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid).
|
||||
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
||||
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/NeoChat_Nightly_android-arm64/).
|
||||
|
||||
## Running
|
||||
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||
|
||||
Just start the executable in your preferred way - either from the build directory or from the installed location.
|
||||

|
||||
|
||||
## Building NeoChat
|
||||
## Features
|
||||
|
||||
The best way to build KDE apps during development is to use `kdesrc-build`. The full instructions for this can be found on
|
||||
the KDE community website's get involved section under [development](https://community.kde.org/Get_Involved/development). This
|
||||
is primarily aimed at Linux development.
|
||||
* Sending messages
|
||||
* Sending files from clipboard and filesystem
|
||||
* Reply to message (right-click on a message to access menu)
|
||||
* Start a private chat (but not encrypted)
|
||||
* Show notifications, for the moment there is only a global switch
|
||||
to disable it. We plan to implement the configuration part of the
|
||||
specification soon.
|
||||
* Autocompletion of usernames in chat
|
||||
* Emoji picker
|
||||
* Basic room setting page
|
||||
* Send and accept invitations
|
||||
* /rainbow <message> (very important)
|
||||
* /me <message>
|
||||
|
||||
For Windows and Android [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
|
||||
development environments for [Windows](https://community.kde.org/Get_Involved/development/Windows) and [Android](https://develop.kde.org/docs/packaging/android/building_applications/).
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are in the repository under [autotests](autotests) and should all pass for any contribution.
|
||||
|
||||
The project has CI setup to test new commits to the repository. All tests are expected to pass for a merge request to
|
||||
be complete.
|
||||
|
||||
Current build status
|
||||
|
||||

|
||||
|
||||
Currently the number of tests is limited, but growing. If anyone wants to help improve this, those
|
||||
contributions would be especially welcome.
|
||||
|
||||
## Contributing
|
||||
|
||||
As is the case throughout the KDE ecosystem contributions are welcome from all. The code base is managed in the
|
||||
[NeoChat repository](https://invent.kde.org/network/neochat) of the KDE Gitlab instance.
|
||||
|
||||
- [Code of Conduct](https://kde.org/code-of-conduct)
|
||||
- [Report a Bug](https://bugs.kde.org/enter_bug.cgi?format=guided&product=neochat)
|
||||
- [Feature Request](https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request)
|
||||
- [Create a Merge Request](https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request)
|
||||
- [Translation](https://community.kde.org/Get_Involved/translation)
|
||||
NeoChat is still missing a few features to become a full-featured
|
||||
Matrix client (most notably encryption support and video chat support).
|
||||
We welcome contributions in this direction.
|
||||
|
||||
## Contact
|
||||
|
||||
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org).
|
||||
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
|
||||
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/)
|
||||
library and some C++ models from [Quaternion](https://github.com/quotient-im/Quaternion/).
|
||||
|
||||
This program is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ android {
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion androidNdkVersion
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
|
||||
@@ -5,7 +5,7 @@ enable_testing()
|
||||
|
||||
ecm_add_test(
|
||||
neochatroomtest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
LINK_LIBRARIES neochat Qt::Test Quotient
|
||||
TEST_NAME neochatroomtest
|
||||
)
|
||||
|
||||
|
||||
@@ -144,5 +144,5 @@ void NeoChatRoomTest::eventTest()
|
||||
QCOMPARE(room->timelineSize(), 1);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(NeoChatRoomTest)
|
||||
QTEST_MAIN(NeoChatRoomTest)
|
||||
#include "neochatroomtest.moc"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "texthandler.h"
|
||||
|
||||
#include <qnamespace.h>
|
||||
#include <connection.h>
|
||||
#include <quotient_common.h>
|
||||
#include <syncdata.h>
|
||||
|
||||
@@ -49,8 +49,7 @@ private Q_SLOTS:
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
void receiveRichInPlainOut_data();
|
||||
void receiveRichInPlainOut();
|
||||
void recieveRichInPlainOut();
|
||||
void receivePlainStripHtml();
|
||||
void receivePlainStripMarkup();
|
||||
void receiveStripNewlines();
|
||||
@@ -60,15 +59,8 @@ private Q_SLOTS:
|
||||
void receiveRichtextIn();
|
||||
void receiveRichMxcUrl();
|
||||
void receiveRichPlainUrl();
|
||||
void receiveRichEmote();
|
||||
void receiveRichEdited_data();
|
||||
void receiveRichEdited();
|
||||
void receiveLineSeparator();
|
||||
|
||||
void linkPreviewsMatch_data();
|
||||
void linkPreviewsMatch();
|
||||
void linkPreviewsReject_data();
|
||||
void linkPreviewsReject();
|
||||
void receiveLineSeparator();
|
||||
};
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -156,44 +148,7 @@ void TextHandlerTest::initTestCase()
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1232
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "/me This is an emote.",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "This is an emote.",
|
||||
"msgtype": "m.emote"
|
||||
},
|
||||
"event_id": "$153273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1532735824654,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1231
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "tested",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$zrCiBxBnqqTn0Z5FY78qSZAszno_w8nJJXzfBULG-3E",
|
||||
"origin_server_ts": 1680948575928,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1747776,
|
||||
"m.relations": {
|
||||
"m.replace": {
|
||||
"event_id": "$UX0PlpyI7vYO32iHMuuYEP7ECMh4sX3XLGiB2SwM4mQ",
|
||||
"origin_server_ts": 1680948580992,
|
||||
"sender": "@example:example.org"
|
||||
}
|
||||
}
|
||||
"age": 1235
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -356,24 +311,15 @@ void TextHandlerTest::receiveStripReply()
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichInPlainOut_data()
|
||||
void TextHandlerTest::recieveRichInPlainOut()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("ampersand") << QStringLiteral("a & b") << QStringLiteral("a & b");
|
||||
QTest::newRow("quote") << QStringLiteral(""a and b"") << QStringLiteral("\"a and b\"");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichInPlainOut()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
const QString testInputString = QStringLiteral("a & b");
|
||||
const QString testOutputString = QStringLiteral("a & b");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receivePlainTextIn()
|
||||
@@ -495,7 +441,7 @@ void TextHandlerTest::receiveRichMxcUrl()
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().back().get()), testOutputString);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -554,45 +500,6 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||
}
|
||||
|
||||
// Test that user pill is add to an emote message.
|
||||
// N.B. The second message in the test timeline is marked as an emote.
|
||||
void TextHandlerTest::receiveRichEmote()
|
||||
{
|
||||
auto event = room->messageEvents().at(1).get();
|
||||
auto author = static_cast<NeoChatUser *>(room->user(event->senderId()));
|
||||
const QString testInputString = QStringLiteral("This is an emote.");
|
||||
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:") + author->color().name()
|
||||
+ QStringLiteral("\">@example:example.org</a> This is an emote.");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, event), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
|
||||
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
|
||||
<< QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
|
||||
QTest::newRow("blockquote") << QStringLiteral("<blockquote>Edited</blockquote>")
|
||||
<< QStringLiteral("<blockquote>Edited</blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(2).get()), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveLineSeparator()
|
||||
{
|
||||
auto text = QStringLiteral("foo\u2028bar");
|
||||
@@ -601,52 +508,5 @@ void TextHandlerTest::receiveLineSeparator()
|
||||
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsMatch_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||
|
||||
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org")});
|
||||
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org")});
|
||||
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org")});
|
||||
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
|
||||
<< QList<QUrl>({
|
||||
QUrl("https://kde.org"),
|
||||
QUrl("www.example.org"),
|
||||
});
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsMatch()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QList<QUrl>, testOutputLinks);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsReject_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||
|
||||
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
|
||||
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
|
||||
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsReject()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QList<QUrl>, testOutputLinks);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TextHandlerTest)
|
||||
QTEST_MAIN(TextHandlerTest)
|
||||
#include "texthandlertest.moc"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<name xml:lang="eu">NeoChat</name>
|
||||
<name xml:lang="fi">NeoChat</name>
|
||||
<name xml:lang="fr">NeoChat</name>
|
||||
<name xml:lang="gl">NeoChat</name>
|
||||
<name xml:lang="hu">NeoChat</name>
|
||||
<name xml:lang="ia">Neochat</name>
|
||||
<name xml:lang="id">NeoChat</name>
|
||||
@@ -57,6 +58,7 @@
|
||||
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
|
||||
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
|
||||
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
|
||||
<summary xml:lang="gl">Un cliente para Matrix, o protocolo de comunicación descentralizada</summary>
|
||||
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
|
||||
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary>
|
||||
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
|
||||
@@ -65,7 +67,7 @@
|
||||
<summary xml:lang="ka">კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის</summary>
|
||||
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
||||
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix – protokollen for desentralisert kommunikasjon</summary>
|
||||
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</summary>
|
||||
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
||||
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
@@ -74,6 +76,7 @@
|
||||
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
|
||||
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||
<summary xml:lang="ta">மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி</summary>
|
||||
<summary xml:lang="tr">Merkezi olmayan iletişim protokolü Matrix için bir istemci</summary>
|
||||
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
|
||||
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
|
||||
@@ -84,12 +87,18 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix، (ميفاق الاتصال اللامركزي للمراسلة الفورية). يتيح لك نيوتشات إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك. يستخدم أطر عمل كيدي وأبرزها Kirigami لتوفير تجربة متقاربة عبر منصات متعددة.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix, el protocol descentralitzat de comunicacions de missatgeria instantània. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics. Fa servir els Frameworks de KDE i, sobretot, el Kirigami per a proporcionar una experiència convergent a través de diverses plataformes.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix, el protocol descentralitzat de comunicacions de missatgeria instantània. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics. Utilitza els Frameworks de KDE i, sobretot, Kirigami per a proporcionar una experiència convergent a través de diverses plataformes.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a client for Matrix, the decentralised communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix, el protocolo de comunicaciones descentralizado para mensajería instantánea. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos. Usa la infraestructura de KDE y, en particular, Kirigami para proporcionar una experiencia convergente en muchas plataformas.</p>
|
||||
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
|
||||
<p xml:lang="fi">NeoChat on asiakassovellus Matrixille, hajautetulle pikaviestinyhteyskäytännölle. Sillä voi lähettää teksti-, video- ja ääniviestejä perheelle, tutuille ja ystäville. Se käyttää KDE-kehystä ja erityisesti Kirigamia tuottaakseen mukautuvan monialustaisen käyttökokemuksen.</p>
|
||||
<p xml:lang="fr">NeoChat est un client pour le protocole Matrix, un protocole décentralisé de communications pour messagerie instantané. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis. Il utilise les environnements de développement et plus précisément Kirigami pour fournir une expérience convergente sur plusieurs plate-formes. </p>
|
||||
<p xml:lang="gl">NeoChat é un cliente para Matrix, o protocolo de comunicación descentralizada para mensaxería instantánea. Podes enviar mensaxes de texto, vídeos e ficheiros de son á túa familia, colegas e amizades. Usas infraestruturas de KDE e principalmente Kirigami para proporcionar unha experiencia de uso converxente para varias plataformas.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente per Matrix, le protocollo de communication decentralisate per messager instantanee. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante. Illo usa KDE frameworks e super toto Kirigamii forni un experientia convergente trans platteforme multiple.</p>
|
||||
<p xml:lang="it">NeoChat è un client per Matrix, il protocollo di comunicazione decentralizzato per la messaggistica istantanea. Ti consente di inviare messaggi di testo, video e file audio a familiari, colleghi e amici. Utilizza i framework KDE e in particolare Kirigami per fornire un'esperienza convergente su più piattaforme.</p>
|
||||
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით. ის იყენებს KDE-ის შესაძლებლობებს და განსაკუთრებით კი Kirigami-ის, რათა პლატფორმებს შორის თავსებადობა უზრუნველყოს.</p>
|
||||
<p xml:lang="nl">NeoChat is een client voor Matrix, het gedecentraliseerde communicatieprotocol voor instant messages. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden. Het gebruik KDE frameworks en het meest opmerkelijk Kirigami om een convergente ervaring te leveren op meerdere platforms.</p>
|
||||
<p xml:lang="nn">NeoChat er ein klient for Matrix, ein protokoll for desentralisert kommunikasjon. Du kan utveksla tekst, lyd og videoar med kollegaar, vennar og familie. Programmet brukar KDE Frameworks og Kirigami for å gje ei brukarflate tilpassa ulike plattformer.</p>
|
||||
<p xml:lang="pl">NeoChat jest programem do Matriksa, protokołu rozproszonego porozumiewania się w czasie rzeczywistym. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół. Używa szkieletów KDE i głównie Kirigami, aby zapewnić spójne wrażenia na wielu platformach</p>
|
||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix. Usa as plataformas do KDE, e principalmente o Kirigami, para oferecer uma experiência convergente entre várias plataformas.</p>
|
||||
<p xml:lang="sl">Neochat je odjemalec za Matrix, decentralizirani komunikacijski protokol za takojšnje sporočanje. Omogoča vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek svoji družini, sodelavcem in prijateljem. Uporablja okvire ogrodje KDE frameworks in predvsem Kirigami za zagotavljanje konvergentne izkušnje na več platformah.</p>
|
||||
<p xml:lang="sl">Neochat je odjemalec za Matrix, decentralizirani komunikacijski protokol za takojšnje sporočanje. Omogoča vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek svoji družini, sodelavcem in prijateljem. Uporablja okvire KDE in predvsem Kirigami, da zagotavlja konvergentno izkušnjo na več platformah.</p>
|
||||
<p xml:lang="tr">NeoChat, anlık iletileşme için merkezi olmayan iletişim protokolü olan Matrix için bir istemcidir. Ailenize, iş arkadaşlarınıza ve arkadaşlarınıza metin iletiler, videolar ve ses dosyaları göndermenize olanak tanır. Birden çok platformda yakınsak bir deneyim sağlamak için KDE Frameworks ve en önemlilerinden Kirigami'yi kullanır.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт Matrix, децентралізованого протоколу спілкування для миттєвого обміну повідомленнями. За його допомогою ви можете надсилати текстові повідомлення, відео та звукові файли вашій родин, колегами та друзям. У програмі використано бібліотеки KDE, зокрема Kirigami, для надання однорідного середовища на декількох програмних та апаратних платформах.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.xx</p>
|
||||
@@ -97,67 +106,60 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
|
||||
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
|
||||
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
|
||||
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
|
||||
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
|
||||
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
|
||||
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
||||
<p xml:lang="ka">NeoChat-ი მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარგდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
||||
<p xml:lang="nl">NeoChat richt zich op het volledig bieden van alle mogelijkheden van de Matrix-specificatie. Alles in de huidige stabiele specificatie met merkbare uitzondering van VoIP, gekoppelde discussies en sommige aspecten van eind-tot-eind versleuteling worden ondersteund. Er zijn een paar andere kleinere omissies vanwege het feit dat de Matrix specificatie constant evolueert maar het doel blijft het eventueel bieden van ondersteuning van de gehele specificatie.</p>
|
||||
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
|
||||
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
||||
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
||||
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix spesifikasyonu için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme'nin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
|
||||
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
|
||||
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
|
||||
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="en-GB">Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p>
|
||||
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
|
||||
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
|
||||
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
|
||||
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
|
||||
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
|
||||
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
|
||||
<p xml:lang="nl">Vanwege de aard van de ontwikkeling van de Matrix specificatie ondersteunt NeoChat ook talloze onstabiele mogelijkheden. Dit zijn nu:</p>
|
||||
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
|
||||
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
||||
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
|
||||
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtim geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
|
||||
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
|
||||
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
|
||||
<ul>
|
||||
<li>Polls - MSC3381</li>
|
||||
<li xml:lang="ar">التصويت - MSC3381</li>
|
||||
<li xml:lang="ca">Enquestes - MSC3381</li>
|
||||
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
|
||||
<li xml:lang="es">Encuestas - MSC3381</li>
|
||||
<li xml:lang="fr">Sondages - MSC3381</li>
|
||||
<li xml:lang="ka">Polls - MSC3381</li>
|
||||
<li xml:lang="nl">Polls - MSC3381</li>
|
||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
<li xml:lang="tr">Anketler - MSC3381</li>
|
||||
<li xml:lang="uk">Опитування - MSC3381</li>
|
||||
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
|
||||
<li>Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="es">Paquetes de pegatinas - MSC2545</li>
|
||||
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
|
||||
<li xml:lang="nl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
|
||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||
<li>Location Events - MSC3488</li>
|
||||
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
|
||||
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="es">Eventos de ubicación - MSC3488</li>
|
||||
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
|
||||
<li xml:lang="nl">Locatie gebeurtenissen - MSC3488</li>
|
||||
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||
<li xml:lang="tr">Konum Etkinlikleri - MSC3488</li>
|
||||
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
|
||||
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
|
||||
<ul>>
|
||||
</ul>
|
||||
<ul xml:lang="ca">></ul>
|
||||
<ul xml:lang="ca-valencia">></ul>
|
||||
<ul xml:lang="cs">></ul>
|
||||
<ul xml:lang="en-GB">></ul>
|
||||
<ul xml:lang="es">></ul>
|
||||
<ul xml:lang="fi">></ul>
|
||||
<ul xml:lang="fr">></ul>
|
||||
<ul xml:lang="gl">></ul>
|
||||
<ul xml:lang="ia">></ul>
|
||||
<ul xml:lang="it">></ul>
|
||||
<ul xml:lang="ka">></ul>
|
||||
<ul xml:lang="nl">></ul>
|
||||
<ul xml:lang="nn">></ul>
|
||||
<ul xml:lang="pl">></ul>
|
||||
<ul xml:lang="pt-BR">></ul>
|
||||
<ul xml:lang="sl">></ul>
|
||||
<ul xml:lang="uk">></ul>
|
||||
<ul xml:lang="x-test">xx>xx</ul>
|
||||
</description>
|
||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
||||
<url type="bugtracker">https://bugs.kde.org/buglist.cgi?component=General&product=NeoChat</url>
|
||||
@@ -177,6 +179,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
|
||||
<developer_name xml:lang="fi">KDE-yhteisö</developer_name>
|
||||
<developer_name xml:lang="fr">La communauté de KDE</developer_name>
|
||||
<developer_name xml:lang="gl">A comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="hu">A KDE Közösség</developer_name>
|
||||
<developer_name xml:lang="ia">Le communitate de KDE</developer_name>
|
||||
<developer_name xml:lang="id">Komunitas KDE</developer_name>
|
||||
@@ -194,6 +197,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
<developer_name xml:lang="ta">கே.டீ.யீ. சமூகம்</developer_name>
|
||||
<developer_name xml:lang="tr">KDE Topluluğu</developer_name>
|
||||
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
|
||||
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
|
||||
@@ -217,16 +221,10 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.04.3" date="2023-07-06"/>
|
||||
<release version="23.04.2" date="2023-06-08"/>
|
||||
<release version="23.04.1" date="2023-05-11"/>
|
||||
<release version="23.04.0" date="2023-04-20">
|
||||
<artifacts>
|
||||
<artifact type="binary" platform="x86_64-windows-msvc">
|
||||
<location>https://download.kde.org/stable/release-service/23.04.0/windows/neochat-23.04.0-512-windows-cl-msvc2019-x86_64.exe</location>
|
||||
<checksum type="sha256">85e17fdd9b128bfdd171a93b11f7a95febeff0a00df9261df11f6ff67637686d</checksum>
|
||||
<size type="download">58032464</size>
|
||||
</artifact>
|
||||
</artifacts>
|
||||
</release>
|
||||
<release version="23.04.0" date="2023-04-20"/>
|
||||
<release version="23.01" date="2023-01-30">
|
||||
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||
<description>
|
||||
|
||||
@@ -15,6 +15,7 @@ Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[gl]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
@@ -52,6 +53,7 @@ GenericName[es]=Cliente para Matrix
|
||||
GenericName[eu]=Matrix bezeroa
|
||||
GenericName[fi]=Matrix-asiakas
|
||||
GenericName[fr]=Client « Matrix »
|
||||
GenericName[gl]=Cliente de Matrix
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[ia]=Cliente de Matrice
|
||||
GenericName[id]=Klien Matrix
|
||||
@@ -88,6 +90,7 @@ Comment[es]=Cliente para el protocolo Matrix
|
||||
Comment[eu]=Matrix protokolorako bezeroa
|
||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[gl]=Cliente para o protocolo Matrix
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
@@ -97,7 +100,7 @@ Comment[ka]=კლიენტი Matrix-ის პროტოკოლის
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||
Comment[nn]=Klient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
|
||||
1473
po/ar/neochat.po
1473
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1592
po/az/neochat.po
1592
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1445
po/ca/neochat.po
1445
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1460
po/cs/neochat.po
1460
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1501
po/da/neochat.po
1501
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1488
po/de/neochat.po
1488
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1501
po/el/neochat.po
1501
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1477
po/en_GB/neochat.po
1477
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1467
po/es/neochat.po
1467
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1489
po/eu/neochat.po
1489
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1491
po/fi/neochat.po
1491
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1485
po/fr/neochat.po
1485
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
2863
po/hu/neochat.po
2863
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1535
po/ia/neochat.po
1535
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1603
po/id/neochat.po
1603
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1530
po/ie/neochat.po
1530
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1479
po/it/neochat.po
1479
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1383
po/ja/neochat.po
1383
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1467
po/ka/neochat.po
1467
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1590
po/ko/neochat.po
1590
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1378
po/lt/neochat.po
1378
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1471
po/nl/neochat.po
1471
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2386
po/nn/neochat.po
2386
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1577
po/pa/neochat.po
1577
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1486
po/pl/neochat.po
1486
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1641
po/pt/neochat.po
1641
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1682
po/pt_BR/neochat.po
1682
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1493
po/ru/neochat.po
1493
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1580
po/sk/neochat.po
1580
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1471
po/sl/neochat.po
1471
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1575
po/sv/neochat.po
1575
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1467
po/ta/neochat.po
1467
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1428
po/tok/neochat.po
1428
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1465
po/tr/neochat.po
1465
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1469
po/uk/neochat.po
1469
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1449
po/zh_CN/neochat.po
1449
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1373
po/zh_TW/neochat.po
1373
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -30,9 +30,11 @@ add_library(neochat STATIC
|
||||
models/devicesmodel.cpp
|
||||
filetypesingleton.cpp
|
||||
login.cpp
|
||||
stickerevent.cpp
|
||||
models/webshortcutmodel.cpp
|
||||
blurhash.cpp
|
||||
blurhashimageprovider.cpp
|
||||
joinrulesevent.cpp
|
||||
models/collapsestateproxymodel.cpp
|
||||
urlhelper.cpp
|
||||
windowcontroller.cpp
|
||||
@@ -42,23 +44,9 @@ add_library(neochat STATIC
|
||||
models/actionsmodel.cpp
|
||||
models/serverlistmodel.cpp
|
||||
models/statemodel.cpp
|
||||
models/statefiltermodel.cpp
|
||||
filetransferpseudojob.cpp
|
||||
models/searchmodel.cpp
|
||||
texthandler.cpp
|
||||
logger.cpp
|
||||
models/stickermodel.cpp
|
||||
models/imagepacksmodel.cpp
|
||||
events/imagepackevent.cpp
|
||||
events/joinrulesevent.cpp
|
||||
events/stickerevent.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
HEADER "messageeventmodel_logging.h"
|
||||
IDENTIFIER "MessageEvent"
|
||||
CATEGORY_NAME "org.kde.neochat.messageeventmodel"
|
||||
DEFAULT_SEVERITY Info
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
@@ -72,11 +60,11 @@ target_link_libraries(neochat-app PRIVATE
|
||||
neochat
|
||||
)
|
||||
|
||||
if(Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
if(Quotient_VERSION_MINOR GREATER 6)
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_07)
|
||||
target_sources(neochat PRIVATE events/pollevent.cpp pollhandler.cpp)
|
||||
target_sources(neochat PRIVATE pollevent.cpp pollhandler.cpp)
|
||||
else()
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_VERSION=\"${Quotient${QUOTIENT_SUFFIX}_VERSION}\")
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_VERSION=\"${Quotient_VERSION}\")
|
||||
target_sources(neochat PRIVATE neochataccountregistry.cpp)
|
||||
endif()
|
||||
|
||||
@@ -106,7 +94,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
@@ -187,10 +175,6 @@ if(ANDROID)
|
||||
"window-new"
|
||||
"globe"
|
||||
"visibility"
|
||||
"home"
|
||||
"preferences-desktop-notification"
|
||||
"computer-symbolic"
|
||||
"gps"
|
||||
)
|
||||
else()
|
||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "actionshandler.h"
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <csapi/joining.h>
|
||||
#include <events/roommemberevent.h>
|
||||
|
||||
@@ -11,10 +13,13 @@
|
||||
#include <KLocalizedString>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "controller.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "roommanager.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -12,31 +12,13 @@
|
||||
class CustomEmojiModel;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsHandler
|
||||
*
|
||||
* This class handles chat messages ready for posting to a room.
|
||||
*
|
||||
* Everything that needs to be done to prepare the message for posting in a room
|
||||
* including:
|
||||
* - File handling
|
||||
* - User mentions
|
||||
* - Quick edits
|
||||
* - Chat actions
|
||||
* - Custom emojis
|
||||
*
|
||||
* @note A chat action is a message starting with /, resulting in something other
|
||||
* than a normal message being sent (e.g. /me, /join).
|
||||
*
|
||||
* @sa ActionsModel, NeoChatRoom
|
||||
*/
|
||||
/// \brief Handles user interactions with NeoChat (joining room, creating room,
|
||||
/// sending message). Account management is handled by Controller.
|
||||
class ActionsHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The room that messages will be sent to.
|
||||
*/
|
||||
/// \brief The room that messages will be sent to.
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
|
||||
@@ -5,24 +5,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Returns the pixel array of the result image given the blurhash string.
|
||||
*
|
||||
* @param blurhash a string representing the blurhash to be decoded.
|
||||
* @param width the width of the resulting image.
|
||||
* @param height the height of the resulting image.
|
||||
* @param punch the factor to improve the contrast, default = 1.
|
||||
* @param nChannels the number of channels in the resulting image array, 3 = RGB, 4 = RGBA.
|
||||
*
|
||||
* @return A pointer to memory region where pixels are stored in (H, W, C) format.
|
||||
*/
|
||||
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels);
|
||||
|
||||
/**
|
||||
* @brief Checks if the Blurhash is valid or not.
|
||||
*
|
||||
* @param blurhash a string representing the blurhash.
|
||||
*
|
||||
* @return A bool (true if it is a valid blurhash, else false).
|
||||
*/
|
||||
bool isValidBlurhash(const char *blurhash);
|
||||
|
||||
@@ -5,22 +5,9 @@
|
||||
|
||||
#include <QQuickImageProvider>
|
||||
|
||||
/**
|
||||
* @class BlurhashImageProvider
|
||||
*
|
||||
* A QQuickImageProvider for blurhashes.
|
||||
*
|
||||
* @sa QQuickImageProvider
|
||||
*/
|
||||
class BlurhashImageProvider : public QQuickImageProvider
|
||||
{
|
||||
public:
|
||||
BlurhashImageProvider();
|
||||
|
||||
/**
|
||||
* @brief Return an image for a given ID.
|
||||
*
|
||||
* @sa QQuickImageProvider::requestImage
|
||||
*/
|
||||
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||
};
|
||||
};
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <Sonnet/BackgroundChecker>
|
||||
#include <Sonnet/Settings>
|
||||
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
class SyntaxHighlighter : public QSyntaxHighlighter
|
||||
@@ -136,13 +138,12 @@ int ChatDocumentHandler::completionStartIndex() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||
const long long cursor = cursorPosition();
|
||||
#else
|
||||
const auto cursor = cursorPosition();
|
||||
#endif
|
||||
const auto &text = getText();
|
||||
|
||||
auto start = std::min(cursor, text.size()) - 1;
|
||||
while (start > -1) {
|
||||
if (text.at(start) == QLatin1Char(' ')) {
|
||||
@@ -323,35 +324,3 @@ void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||
m_room->mentions()->push_back(mention);
|
||||
}
|
||||
}
|
||||
|
||||
QColor ChatDocumentHandler::mentionColor() const
|
||||
{
|
||||
return m_mentionColor;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setMentionColor(const QColor &color)
|
||||
{
|
||||
if (m_mentionColor == color) {
|
||||
return;
|
||||
}
|
||||
m_mentionColor = color;
|
||||
m_highlighter->mentionFormat.setForeground(m_mentionColor);
|
||||
m_highlighter->rehighlight();
|
||||
Q_EMIT mentionColorChanged();
|
||||
}
|
||||
|
||||
QColor ChatDocumentHandler::errorColor() const
|
||||
{
|
||||
return m_errorColor;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setErrorColor(const QColor &color)
|
||||
{
|
||||
if (m_errorColor == color) {
|
||||
return;
|
||||
}
|
||||
m_errorColor = color;
|
||||
m_highlighter->errorFormat.setForeground(m_errorColor);
|
||||
m_highlighter->rehighlight();
|
||||
Q_EMIT errorColorChanged();
|
||||
}
|
||||
|
||||
@@ -8,54 +8,13 @@
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/userlistmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
class QTextDocument;
|
||||
class NeoChatRoom;
|
||||
class SyntaxHighlighter;
|
||||
|
||||
/**
|
||||
* @class ChatDocumentHandler
|
||||
*
|
||||
* Handle the QQuickTextDocument of a qml text item.
|
||||
*
|
||||
* The class provides functionality to highlight text in the text document as well
|
||||
* as providing completion functionality via a CompletionModel.
|
||||
*
|
||||
* The ChatDocumentHandler is also linked to a NeoChatRoom to provide functionality
|
||||
* to save the chat document text when switching between rooms.
|
||||
*
|
||||
* To get the full functionality the cursor position and text selection information
|
||||
* need to be passed in. For example:
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick 2.0
|
||||
* import QtQuick.Controls 2.15 as QQC2
|
||||
*
|
||||
* import org.kde.kirigami 2.12 as Kirigami
|
||||
* import org.kde.neochat 1.0
|
||||
*
|
||||
* QQC2.TextArea {
|
||||
* id: textField
|
||||
*
|
||||
* // Set this to a NeoChatRoom object.
|
||||
* property var room
|
||||
*
|
||||
* ChatDocumentHandler {
|
||||
* id: documentHandler
|
||||
* document: textField.textDocument
|
||||
* cursorPosition: textField.cursorPosition
|
||||
* selectionStart: textField.selectionStart
|
||||
* selectionEnd: textField.selectionEnd
|
||||
* mentionColor: Kirigami.Theme.linkColor
|
||||
* errorColor: Kirigami.Theme.negativeTextColor
|
||||
* room: textField.room
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @sa QQuickTextDocument, CompletionModel, NeoChatRoom
|
||||
*/
|
||||
class ChatDocumentHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -67,50 +26,15 @@ class ChatDocumentHandler : public QObject
|
||||
* from the correct parameters in the assigned room.
|
||||
*/
|
||||
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
|
||||
|
||||
/**
|
||||
* @brief The QQuickTextDocument that is being handled.
|
||||
*/
|
||||
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
|
||||
|
||||
/**
|
||||
* @brief The current saved cursor position.
|
||||
*/
|
||||
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
|
||||
|
||||
/**
|
||||
* @brief The start position of any currently selected text.
|
||||
*/
|
||||
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
|
||||
|
||||
/**
|
||||
* @brief The end position of any currently selected text.
|
||||
*/
|
||||
Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged)
|
||||
|
||||
/**
|
||||
* @brief The current CompletionModel.
|
||||
*
|
||||
* This is typically provided to a qml component to visualise the current
|
||||
* completion results.
|
||||
*/
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
||||
|
||||
/**
|
||||
* @brief The current room that the the text document is being handled for.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
/**
|
||||
* @brief The color to highlight user mentions.
|
||||
*/
|
||||
Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged);
|
||||
|
||||
/**
|
||||
* @brief The color to highlight spelling errors.
|
||||
*/
|
||||
Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged);
|
||||
|
||||
public:
|
||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||
|
||||
@@ -136,13 +60,6 @@ public:
|
||||
|
||||
void updateCompletions();
|
||||
CompletionModel *completionModel() const;
|
||||
|
||||
[[nodiscard]] QColor mentionColor() const;
|
||||
void setMentionColor(const QColor &color);
|
||||
|
||||
[[nodiscard]] QColor errorColor() const;
|
||||
void setErrorColor(const QColor &color);
|
||||
|
||||
Q_SIGNALS:
|
||||
void isEditChanged();
|
||||
void documentChanged();
|
||||
@@ -151,22 +68,17 @@ Q_SIGNALS:
|
||||
void completionModelChanged();
|
||||
void selectionStartChanged();
|
||||
void selectionEndChanged();
|
||||
void errorColorChanged();
|
||||
void mentionColorChanged();
|
||||
|
||||
private:
|
||||
int completionStartIndex() const;
|
||||
|
||||
bool m_isEdit = false;
|
||||
bool m_isEdit;
|
||||
|
||||
QQuickTextDocument *m_document;
|
||||
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
bool completionVisible = false;
|
||||
|
||||
QColor m_mentionColor;
|
||||
QColor m_errorColor;
|
||||
|
||||
int m_cursorPosition;
|
||||
int m_selectionStart;
|
||||
int m_selectionEnd;
|
||||
|
||||
@@ -62,10 +62,10 @@ QString Clipboard::saveImage(QString localPath) const
|
||||
void Clipboard::saveText(QString message)
|
||||
{
|
||||
static QRegularExpression re(QStringLiteral("<[^>]*>"));
|
||||
auto *mimeData = new QMimeData; // ownership is transferred to clipboard
|
||||
mimeData->setHtml(message);
|
||||
mimeData->setText(message.replace(re, QString()));
|
||||
m_clipboard->setMimeData(mimeData);
|
||||
auto *mineData = new QMimeData; // ownership is transferred to clipboard
|
||||
mineData->setHtml(message);
|
||||
mineData->setText(message.replace(re, QString()));
|
||||
m_clipboard->setMimeData(mineData);
|
||||
}
|
||||
|
||||
void Clipboard::setImage(const QUrl &url)
|
||||
|
||||
@@ -9,60 +9,23 @@ class QClipboard;
|
||||
class QImage;
|
||||
|
||||
/**
|
||||
* @class Clipboard
|
||||
*
|
||||
* Clipboard proxy
|
||||
*
|
||||
* Used to set and retrieve content from the clipboard.
|
||||
*/
|
||||
class Clipboard : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief Whether the current clipboard content is an image.
|
||||
*/
|
||||
Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged)
|
||||
|
||||
/**
|
||||
* @brief Return the current clipboard content image.
|
||||
*
|
||||
* Returns a null image if the clipboard does not contain an image or if it
|
||||
* contains an image in an unsupported image format.
|
||||
*/
|
||||
Q_PROPERTY(QImage image READ image NOTIFY imageChanged)
|
||||
|
||||
public:
|
||||
explicit Clipboard(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool hasImage() const;
|
||||
|
||||
[[nodiscard]] QImage image() const;
|
||||
|
||||
/**
|
||||
* @brief Save the current clipboard image to file.
|
||||
*
|
||||
* If the clipboard does not contain an image or if it contains an image in an
|
||||
* unsupported image format nothing happens.
|
||||
*
|
||||
* The given file path must be both valid and local or nothing happens.
|
||||
*
|
||||
* @param localPath the path to save the image. A default path for the app cache
|
||||
* will be used if available and this is empty.
|
||||
*
|
||||
* @return A QString with the path that the image was saved to. The string will
|
||||
* be empty if nothing was saved.
|
||||
*/
|
||||
Q_INVOKABLE QString saveImage(QString localPath = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Set the clipboard content to the input message.
|
||||
*/
|
||||
Q_INVOKABLE void saveText(QString message);
|
||||
|
||||
/**
|
||||
* @brief Set the clipboard content to the input image.
|
||||
*/
|
||||
Q_INVOKABLE void setImage(const QUrl &image);
|
||||
|
||||
private:
|
||||
|
||||
@@ -8,53 +8,18 @@
|
||||
class QAbstractItemModel;
|
||||
class KColorSchemeManager;
|
||||
|
||||
/**
|
||||
* @class ColorSchemer
|
||||
*
|
||||
* A class to provide a wrapper around KColorSchemeManager to make it available in
|
||||
* QML.
|
||||
*
|
||||
* @sa KColorSchemeManager
|
||||
*/
|
||||
class ColorSchemer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief A QAbstractItemModel of all available color schemes.
|
||||
*
|
||||
* @sa QAbstractItemModel
|
||||
*/
|
||||
Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT)
|
||||
|
||||
public:
|
||||
ColorSchemer(QObject *parent = nullptr);
|
||||
~ColorSchemer();
|
||||
|
||||
QAbstractItemModel *model() const;
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme identified by the provided index.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(int idx);
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme with the given name.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(const QString &name);
|
||||
|
||||
/**
|
||||
* @brief Returns the index for the scheme with the given name.
|
||||
*/
|
||||
Q_INVOKABLE int indexForScheme(const QString &name) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the name for the scheme with the given index.
|
||||
*/
|
||||
Q_INVOKABLE QString nameForIndex(int index) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -73,8 +73,24 @@ Controller::Controller(QObject *parent)
|
||||
setApplicationProxy();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
setQuitOnLastWindowClosed();
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
}
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [this]() {
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
});
|
||||
#endif
|
||||
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
@@ -137,20 +153,20 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
if (!initial.contains(connection->user()->id())) {
|
||||
initial.append(connection->user()->id());
|
||||
for (const auto &n : notifications) {
|
||||
oldNotifications += n["event"]["event_id"].toString();
|
||||
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const auto &n : notifications) {
|
||||
const auto ¬ification = n.toObject();
|
||||
const auto notification = n.toObject();
|
||||
if (notification["read"].toBool()) {
|
||||
oldNotifications.removeOne(notification["event"]["event_id"].toString());
|
||||
oldNotifications.removeOne(notification["event"].toObject()["event_id"].toString());
|
||||
continue;
|
||||
}
|
||||
if (oldNotifications.contains(notification["event"]["event_id"].toString())) {
|
||||
if (oldNotifications.contains(notification["event"].toObject()["event_id"].toString())) {
|
||||
continue;
|
||||
}
|
||||
oldNotifications += notification["event"]["event_id"].toString();
|
||||
oldNotifications += notification["event"].toObject()["event_id"].toString();
|
||||
auto room = connection->room(notification["room_id"].toString());
|
||||
|
||||
// If room exists, room is NOT active OR the application is NOT active, show notification
|
||||
@@ -158,20 +174,25 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
&& !(RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id()
|
||||
&& QGuiApplication::applicationState() == Qt::ApplicationActive)) {
|
||||
// The room might have been deleted (for example rejected invitation).
|
||||
auto sender = room->user(notification["event"]["sender"].toString());
|
||||
auto sender = room->user(notification["event"].toObject()["sender"].toString());
|
||||
|
||||
QString body;
|
||||
|
||||
if (notification["event"]["type"].toString() == "org.matrix.msc3381.poll.start") {
|
||||
body = notification["event"]["content"]["org.matrix.msc3381.poll.start"]["question"]["body"].toString();
|
||||
if (notification["event"].toObject()["type"].toString() == "org.matrix.msc3381.poll.start") {
|
||||
body = notification["event"]
|
||||
.toObject()["content"]
|
||||
.toObject()["org.matrix.msc3381.poll.start"]
|
||||
.toObject()["question"]
|
||||
.toObject()["body"]
|
||||
.toString();
|
||||
} else {
|
||||
body = notification["event"]["content"]["body"].toString();
|
||||
body = notification["event"].toObject()["content"].toObject()["body"].toString();
|
||||
}
|
||||
|
||||
if (notification["event"]["type"] == "m.room.encrypted") {
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
const auto decrypted = connection->decryptNotification(notification);
|
||||
body = decrypted["content"]["body"].toString();
|
||||
auto decrypted = connection->decryptNotification(notification);
|
||||
body = decrypted["content"].toObject()["body"].toString();
|
||||
#endif
|
||||
if (body.isEmpty()) {
|
||||
body = i18n("Encrypted Message");
|
||||
@@ -207,6 +228,38 @@ void Controller::showWindow()
|
||||
WindowController::instance().showAndRaiseWindow(QString());
|
||||
}
|
||||
|
||||
void Controller::loginWithAccessToken(const QString &serverAddr, const QString &user, const QString &token, const QString &deviceName)
|
||||
{
|
||||
if (user.isEmpty() || token.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl serverUrl(serverAddr);
|
||||
|
||||
auto conn = new Connection();
|
||||
if (serverUrl.isValid()) {
|
||||
conn->setHomeserver(serverUrl);
|
||||
}
|
||||
|
||||
connect(conn, &Connection::connected, this, [this, conn, deviceName] {
|
||||
AccountSettings account(conn->userId());
|
||||
account.setKeepLoggedIn(true);
|
||||
account.setHomeserver(conn->homeserver());
|
||||
account.setDeviceId(conn->deviceId());
|
||||
account.setDeviceName(deviceName);
|
||||
if (!saveAccessTokenToKeyChain(account, conn->accessToken())) {
|
||||
qWarning() << "Couldn't save access token";
|
||||
}
|
||||
account.sync();
|
||||
addConnection(conn);
|
||||
setActiveConnection(conn);
|
||||
});
|
||||
connect(conn, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
});
|
||||
conn->assumeIdentity(user, token, deviceName);
|
||||
}
|
||||
|
||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
{
|
||||
if (!conn) {
|
||||
@@ -249,6 +302,8 @@ void Controller::addConnection(Connection *c)
|
||||
c->setLazyLoading(true);
|
||||
|
||||
connect(c, &Connection::syncDone, this, [this, c] {
|
||||
setBusy(false);
|
||||
|
||||
Q_EMIT syncDone();
|
||||
|
||||
c->sync(30000);
|
||||
@@ -264,6 +319,8 @@ void Controller::addConnection(Connection *c)
|
||||
}
|
||||
});
|
||||
|
||||
setBusy(true);
|
||||
|
||||
c->sync();
|
||||
|
||||
Q_EMIT connectionAdded(c);
|
||||
@@ -494,24 +551,31 @@ int Controller::accountCount() const
|
||||
return AccountRegistry::instance().count();
|
||||
}
|
||||
|
||||
void Controller::setQuitOnLastWindowClosed()
|
||||
bool Controller::quitOnLastWindowClosed()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
return QGuiApplication::quitOnLastWindowClosed();
|
||||
}
|
||||
|
||||
void Controller::setQuitOnLastWindowClosed(bool value)
|
||||
{
|
||||
if (quitOnLastWindowClosed() != value) {
|
||||
QGuiApplication::setQuitOnLastWindowClosed(value);
|
||||
Q_EMIT quitOnLastWindowClosedChanged();
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Controller::busy() const
|
||||
{
|
||||
return m_busy;
|
||||
}
|
||||
|
||||
void Controller::setBusy(bool busy)
|
||||
{
|
||||
if (m_busy == busy) {
|
||||
return;
|
||||
}
|
||||
m_busy = busy;
|
||||
Q_EMIT busyChanged();
|
||||
}
|
||||
|
||||
Connection *Controller::activeConnection() const
|
||||
@@ -529,7 +593,6 @@ void Controller::setActiveConnection(Connection *connection)
|
||||
}
|
||||
if (m_connection != nullptr) {
|
||||
disconnect(m_connection, &Connection::syncError, this, nullptr);
|
||||
disconnect(m_connection, &Connection::accountDataChanged, this, nullptr);
|
||||
}
|
||||
m_connection = connection;
|
||||
if (connection != nullptr) {
|
||||
@@ -553,18 +616,12 @@ void Controller::setActiveConnection(Connection *connection)
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
connect(connection, &Connection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NeoChatConfig::self()->setActiveConnection(QString());
|
||||
}
|
||||
NeoChatConfig::self()->save();
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionIndexChanged();
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
|
||||
void Controller::saveWindowGeometry()
|
||||
@@ -667,6 +724,11 @@ bool Controller::hasWindowSystem() const
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Controller::plainText(QQuickTextDocument *document) const
|
||||
{
|
||||
return document->textDocument()->toPlainText();
|
||||
}
|
||||
|
||||
bool Controller::encryptionSupported() const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -747,38 +809,3 @@ bool Controller::isFlatpak() const
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::setActiveAccountLabel(const QString &label)
|
||||
{
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
QJsonObject json{
|
||||
{"account_label", label},
|
||||
};
|
||||
m_connection->setAccountData("org.kde.neochat.account_label", json);
|
||||
}
|
||||
|
||||
QString Controller::activeAccountLabel() const
|
||||
{
|
||||
if (!m_connection) {
|
||||
return {};
|
||||
}
|
||||
return m_connection->accountDataJson("org.kde.neochat.account_label")["account_label"].toString();
|
||||
}
|
||||
|
||||
QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection)
|
||||
{
|
||||
auto roomVersions = connection->availableRoomVersions();
|
||||
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id", v.id);
|
||||
roomVersionMap.insert("status", v.status);
|
||||
roomVersionMap.insert("isStable", v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
|
||||
185
src/controller.h
185
src/controller.h
@@ -28,198 +28,86 @@ namespace QKeychain
|
||||
class ReadPasswordJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Controller
|
||||
*
|
||||
* A singleton class designed to help manage the application.
|
||||
*
|
||||
* There are also a bunch of helper functions that currently don't fit anywhere
|
||||
* else.
|
||||
*/
|
||||
class Controller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The number of logged in accounts.
|
||||
*/
|
||||
Q_PROPERTY(int accountCount READ accountCount NOTIFY accountCountChanged)
|
||||
|
||||
/**
|
||||
* @brief The current connection for the rest of NeoChat to use.
|
||||
*/
|
||||
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
||||
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The row number in the accounts directory of the active connection.
|
||||
*/
|
||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||
|
||||
/**
|
||||
* @brief The account label for the active account.
|
||||
*
|
||||
* Account labels are a concept specific to NeoChat, allowing accounts to be
|
||||
* labelled, e.g. for "Work", "Private", etc.
|
||||
*
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString activeAccountLabel READ activeAccountLabel WRITE setActiveAccountLabel NOTIFY activeAccountLabelChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the OS NeoChat is running on supports sytem tray icons.
|
||||
*/
|
||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether KWindowSystem specific features are available.
|
||||
*/
|
||||
Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether NeoChat is currently able to connect to the server.
|
||||
*/
|
||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the ecryption support has been enabled.
|
||||
*/
|
||||
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The current minor version number of libQuotient being used.
|
||||
*
|
||||
* This is the only way to gate NeoChat features by libQuotient version in QML.
|
||||
*
|
||||
* @note No major version because libQuotient doesn't have any; All are 0.x.
|
||||
*/
|
||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||
Q_PROPERTY(int quotientMinorVersion READ quotientMinorVersion CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether NeoChat is running as a flatpak.
|
||||
*
|
||||
* This is the only way to gate NeoChat features in flatpaks in QML.
|
||||
*/
|
||||
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the status after an attempt to change the password on an account.
|
||||
*/
|
||||
enum PasswordStatus {
|
||||
Success, /**< The password was successfully changed. */
|
||||
Wrong, /**< The current password entered was wrong. */
|
||||
Other, /**< An unknown problem occurred. */
|
||||
};
|
||||
Q_ENUM(PasswordStatus);
|
||||
|
||||
static Controller &instance();
|
||||
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
void setActiveConnection(Quotient::Connection *connection);
|
||||
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
void addConnection(Quotient::Connection *c);
|
||||
|
||||
/**
|
||||
* @brief Drop a connection from the account registry.
|
||||
*/
|
||||
void dropConnection(Quotient::Connection *c);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
Q_INVOKABLE void loginWithAccessToken(const QString &, const QString &, const QString &, const QString &);
|
||||
|
||||
[[nodiscard]] QString activeAccountLabel() const;
|
||||
void setActiveAccountLabel(const QString &label);
|
||||
|
||||
/**
|
||||
* @brief Save an access token to the keychain for the given account.
|
||||
*/
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
*
|
||||
* The function emits a passwordStatus signal with a PasswordStatus value when
|
||||
* complete.
|
||||
*
|
||||
* @sa PasswordStatus, passwordStatus
|
||||
*/
|
||||
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
/**
|
||||
* @brief Change the avatar for an account.
|
||||
*/
|
||||
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
||||
|
||||
/**
|
||||
* @brief Create new room for a group chat.
|
||||
*/
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
/**
|
||||
* @brief Join a room.
|
||||
*/
|
||||
Q_INVOKABLE void joinRoom(const QString &alias);
|
||||
[[nodiscard]] static bool quitOnLastWindowClosed();
|
||||
void setQuitOnLastWindowClosed(bool value);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user.
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||
[[nodiscard]] bool busy() const;
|
||||
void setBusy(bool busy);
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
/**
|
||||
* @brief Set the background blur status of the given item.
|
||||
*/
|
||||
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
|
||||
enum PasswordStatus {
|
||||
Success,
|
||||
Wrong,
|
||||
Other,
|
||||
};
|
||||
Q_ENUM(PasswordStatus);
|
||||
|
||||
/// \brief Create new room for a group chat.
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
||||
|
||||
/// \brief Join a room.
|
||||
Q_INVOKABLE void joinRoom(const QString &alias);
|
||||
|
||||
bool isOnline() const;
|
||||
|
||||
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
|
||||
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
||||
|
||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||
|
||||
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
|
||||
Q_INVOKABLE QString plainText(QQuickTextDocument *document) const;
|
||||
bool encryptionSupported() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the QNetworkProxy for the application.
|
||||
*
|
||||
* @sa QNetworkProxy::setApplicationProxy
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
Q_INVOKABLE void setApplicationProxy();
|
||||
|
||||
int quotientMinorVersion() const;
|
||||
|
||||
bool isFlatpak() const;
|
||||
|
||||
/**
|
||||
* @brief Return a string for the input timestamp.
|
||||
*
|
||||
* The output format depends on the KFormat::DurationFormatOptions chosen.
|
||||
*
|
||||
* @sa KFormat::DurationFormatOptions
|
||||
*/
|
||||
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
|
||||
|
||||
/**
|
||||
* @brief Return a human readable string for a given input number of bytes.
|
||||
*/
|
||||
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
||||
|
||||
/**
|
||||
* @brief Force a QQuickTextDocument to refresh when images are loaded.
|
||||
*
|
||||
* HACK: This is a workaround for QTBUG 93281.
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions(Quotient::Connection *connection);
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
QPointer<Quotient::Connection> m_connection;
|
||||
bool m_busy = false;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
||||
@@ -237,9 +125,9 @@ private:
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
void showWindow();
|
||||
void setQuitOnLastWindowClosed();
|
||||
|
||||
Q_SIGNALS:
|
||||
void busyChanged();
|
||||
/// Error occurred because of user inputs
|
||||
void errorOccured(const QString &error);
|
||||
|
||||
@@ -264,7 +152,6 @@ Q_SIGNALS:
|
||||
void keyVerificationKey(const QString &sas);
|
||||
void activeConnectionIndexChanged();
|
||||
void roomAdded(NeoChatRoom *room);
|
||||
void activeAccountLabelChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void logout(Quotient::Connection *conn, bool serverSideLogout);
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
/**
|
||||
* @class EmojiTones
|
||||
*
|
||||
* This class provides a _tones variable with the available emoji tones to EmojiModel.
|
||||
*
|
||||
* @sa EmojiModel
|
||||
*/
|
||||
class EmojiTones
|
||||
{
|
||||
private:
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include <QJsonObject>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains(QStringLiteral("pack"))) {
|
||||
pack = ImagePackEventContent::Pack{
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["display_name"]),
|
||||
#ifdef QUOTIENT_07
|
||||
fromJson<Omittable<QUrl>>(json["pack"].toObject()["avatar_url"]),
|
||||
#else
|
||||
QUrl(),
|
||||
#endif
|
||||
fromJson<Omittable<QStringList>>(json["pack"].toObject()["usage"]),
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["attribution"]),
|
||||
};
|
||||
} else {
|
||||
pack = none;
|
||||
}
|
||||
|
||||
const auto &keys = json["images"].toObject().keys();
|
||||
for (const auto &k : keys) {
|
||||
Omittable<EventContent::ImageInfo> info;
|
||||
if (json["images"][k].toObject().contains(QStringLiteral("info"))) {
|
||||
#ifdef QUOTIENT_07
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k]["info"].toObject(), k);
|
||||
#else
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k].toObject(), k);
|
||||
#endif
|
||||
} else {
|
||||
info = none;
|
||||
}
|
||||
images += ImagePackImage{
|
||||
k,
|
||||
#ifdef QUOTIENT_07
|
||||
fromJson<QUrl>(json["images"][k]["url"].toString()),
|
||||
#else
|
||||
QUrl(),
|
||||
#endif
|
||||
fromJson<Omittable<QString>>(json["images"][k]["body"]),
|
||||
info,
|
||||
fromJson<Omittable<QStringList>>(json["images"][k]["usage"]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePackEventContent::fillJson(QJsonObject *o) const
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/stateevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class ImagePackEventContent
|
||||
*
|
||||
* A class to define the content of an image pack event.
|
||||
*
|
||||
* See Matrix MSC2545 for more details.
|
||||
* https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
|
||||
*
|
||||
* @sa ImagePackEvent
|
||||
*/
|
||||
class ImagePackEventContent
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the properties of an image pack.
|
||||
*/
|
||||
struct Pack {
|
||||
Quotient::Omittable<QString> displayName; /**< The display name of the pack. */
|
||||
Quotient::Omittable<QUrl> avatarUrl; /**< The source mxc URL for the pack avatar. */
|
||||
Quotient::Omittable<QStringList> usage; /**< An array of the usages for this pack. Possible usages are "emoticon" and "sticker". */
|
||||
Quotient::Omittable<QString> attribution; /**< The attribution for the pack author(s). */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Defines the properties of an image pack image.
|
||||
*/
|
||||
struct ImagePackImage {
|
||||
QString shortcode; /**< The shortcode for the image. */
|
||||
QUrl url; /**< The mxc URL for this image. */
|
||||
Quotient::Omittable<QString> body; /**< An optional text body for this image. */
|
||||
Quotient::Omittable<Quotient::EventContent::ImageInfo> info; /**< The ImageInfo object used for the info block of m.sticker events. */
|
||||
/**
|
||||
* @brief An array of the usages for this image.
|
||||
*
|
||||
* The possible values match those of the usage key of a pack object.
|
||||
*/
|
||||
Quotient::Omittable<QStringList> usage;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Return the pack properties.
|
||||
*
|
||||
* @sa Pack
|
||||
*/
|
||||
Quotient::Omittable<Pack> pack;
|
||||
|
||||
/**
|
||||
* @brief Return a vector of images in the pack.
|
||||
*
|
||||
* @sa ImagePackImage
|
||||
*/
|
||||
QVector<ImagePackEventContent::ImagePackImage> images;
|
||||
|
||||
explicit ImagePackEventContent(const QJsonObject &o);
|
||||
|
||||
/**
|
||||
* @brief The definition of how to convert the content to Json.
|
||||
*
|
||||
* This is a specialization of the standard fillJson function from libQuotient.
|
||||
*
|
||||
* @sa Quotient::converters
|
||||
*/
|
||||
void fillJson(QJsonObject *o) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ImagePackEvent
|
||||
*
|
||||
* Class to define an image pack state event.
|
||||
*
|
||||
* The event content is ImagePackEventContent.
|
||||
*
|
||||
* @sa Quotient::StateEvent, ImagePackEventContent
|
||||
*/
|
||||
#ifdef QUOTIENT_07
|
||||
class ImagePackEvent : public KeyedStateEventBase<ImagePackEvent, ImagePackEventContent>
|
||||
#else
|
||||
class ImagePackEvent : public StateEvent<ImagePackEventContent>
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef QUOTIENT_07
|
||||
QUO_EVENT(ImagePackEvent, "im.ponies.room_emotes")
|
||||
using KeyedStateEventBase::KeyedStateEventBase;
|
||||
#else
|
||||
DEFINE_EVENT_TYPEID("im.ponies.room_emotes", ImagePackEvent)
|
||||
explicit ImagePackEvent(const QJsonObject &obj)
|
||||
: StateEvent(typeId(), obj)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
REGISTER_EVENT_TYPE(ImagePackEvent)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/roomevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class PollStartEvent
|
||||
*
|
||||
* Class to define a poll start event.
|
||||
*
|
||||
* See MSC3381 for full details on polls in the matrix spec
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/polls/proposals/3381-polls.md.
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class PollStartEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollStartEvent, "org.matrix.msc3381.poll.start");
|
||||
explicit PollStartEvent(const QJsonObject &obj);
|
||||
|
||||
/**
|
||||
* @brief The maximum number of options a user can select in a poll.
|
||||
*/
|
||||
int maxSelections() const;
|
||||
|
||||
/**
|
||||
* @brief The question being asked in the poll.
|
||||
*/
|
||||
QString question() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PollResponseEvent
|
||||
*
|
||||
* Class to define a poll response event.
|
||||
*
|
||||
* See MSC3381 for full details on polls in the matrix spec
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/polls/proposals/3381-polls.md.
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class PollResponseEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollResponseEvent, "org.matrix.msc3381.poll.response");
|
||||
explicit PollResponseEvent(const QJsonObject &obj);
|
||||
explicit PollResponseEvent(const QString &pollStartEventId, QStringList responses);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PollEndEvent
|
||||
*
|
||||
* Class to define a poll end event.
|
||||
*
|
||||
* See MSC3381 for full details on polls in the matrix spec
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/polls/proposals/3381-polls.md.
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class PollEndEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollEndEvent, "org.matrix.msc3381.poll.end");
|
||||
explicit PollEndEvent(const QJsonObject &obj);
|
||||
};
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/roomevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class StickerEvent
|
||||
*
|
||||
* Class to define a sticker event.
|
||||
*
|
||||
* Sticker messages are specialised image messages that are displayed without
|
||||
* controls (e.g. no "download" link, or light-box view on click, as would be
|
||||
* displayed for for m.image events).
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class StickerEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
#ifdef QUOTIENT_07
|
||||
QUO_EVENT(StickerEvent, "m.sticker")
|
||||
#else
|
||||
DEFINE_EVENT_TYPEID("m.sticker", StickerEvent)
|
||||
#endif
|
||||
|
||||
explicit StickerEvent(const QJsonObject &obj);
|
||||
|
||||
/**
|
||||
* @brief A textual representation or associated description of the sticker image.
|
||||
*
|
||||
* This could be the alt text of the original image, or a message to accompany
|
||||
* and further describe the sticker.
|
||||
*/
|
||||
QString body() const;
|
||||
|
||||
/**
|
||||
* @brief Metadata about the image referred to in url including a thumbnail representation.
|
||||
*/
|
||||
const EventContent::ImageContent &image() const;
|
||||
|
||||
/**
|
||||
* @brief The URL to the sticker image. This must be a valid mxc:// URI.
|
||||
*/
|
||||
QUrl url() const;
|
||||
|
||||
private:
|
||||
EventContent::ImageContent m_imageContent;
|
||||
};
|
||||
REGISTER_EVENT_TYPE(StickerEvent)
|
||||
}
|
||||
@@ -25,7 +25,6 @@ void FileTransferPseudoJob::fileTransferProgress(QString id, qint64 progress, qi
|
||||
|
||||
void FileTransferPseudoJob::fileTransferCompleted(QString id, QUrl localFile)
|
||||
{
|
||||
Q_UNUSED(localFile);
|
||||
if (id != m_eventId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
#include <KJob>
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* @class FileTransferPseudoJob
|
||||
*
|
||||
* A class inherited from KJob to track a file download.
|
||||
*
|
||||
* @sa KJob
|
||||
*/
|
||||
class FileTransferPseudoJob : public KJob
|
||||
{
|
||||
public:
|
||||
@@ -22,25 +15,10 @@ public:
|
||||
};
|
||||
Q_ENUM(Operation);
|
||||
FileTransferPseudoJob(Operation operation, const QString &srcDest, const QString &path);
|
||||
|
||||
/**
|
||||
* @brief Set the current number of bytes transferred.
|
||||
*/
|
||||
void fileTransferProgress(QString id, qint64 progress, qint64 total);
|
||||
|
||||
/**
|
||||
* @brief Set the file transfer as complete.
|
||||
*/
|
||||
void fileTransferCompleted(QString id, QUrl localFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file transfer as failed.
|
||||
*/
|
||||
void fileTransferFailed(QString id, QString errorMessage = {});
|
||||
|
||||
/**
|
||||
* @brief Start the file transfer.
|
||||
*/
|
||||
void start() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -11,31 +11,11 @@
|
||||
|
||||
class FileTypeSingletonPrivate;
|
||||
|
||||
/**
|
||||
* @class FileTypeSingleton
|
||||
*
|
||||
* Provide a singleton to expose the functionality of QMimeDatabase.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
class FileTypeSingleton : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief List of supported image formats.
|
||||
*
|
||||
* Returns a list of file extensions, not MIME types.
|
||||
*/
|
||||
Q_PROPERTY(QStringList supportedImageFormats READ supportedImageFormats CONSTANT FINAL)
|
||||
|
||||
/**
|
||||
* @brief List of supported animated image formats.
|
||||
*
|
||||
* Returns a list of file extensions, not MIME types.
|
||||
*/
|
||||
Q_PROPERTY(QStringList supportedAnimatedImageFormats READ supportedAnimatedImageFormats CONSTANT FINAL)
|
||||
|
||||
QML_NAMED_ELEMENT(FileType)
|
||||
QML_SINGLETON
|
||||
|
||||
@@ -43,79 +23,26 @@ public:
|
||||
explicit FileTypeSingleton(QObject *parent = nullptr);
|
||||
~FileTypeSingleton();
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for nameOrAlias or an invalid one if none found.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
// Most of the code in this public section was copy/pasted from qmimedatabase.h
|
||||
Q_INVOKABLE QMimeType mimeTypeForName(const QString &nameOrAlias) const;
|
||||
|
||||
enum MatchMode { MatchDefault, MatchExtension, MatchContent };
|
||||
Q_ENUM(MatchMode)
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the file named fileName using mode.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, FileTypeSingleton::MatchMode mode = MatchDefault) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for fileInfo.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QFileInfo &fileInfo, FileTypeSingleton::MatchMode mode = MatchDefault) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the MIME types for the file name fileName.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QList<QMimeType> mimeTypesForFileName(const QString &fileName) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for data.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForData(const QByteArray &data) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the data in device.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForData(QIODevice *device) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for url.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForUrl(const QUrl &url) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the given fileName and device data.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the given fileName and device data.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the suffix for the file fileName, as known by the MIME database.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QString suffixForFileName(const QString &fileName) const;
|
||||
|
||||
// These return a list of file extensions, not mimetypes
|
||||
QStringList supportedImageFormats() const;
|
||||
QStringList supportedAnimatedImageFormats() const;
|
||||
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class JoinRulesEvent
|
||||
*
|
||||
* Class to define a join rule state event.
|
||||
*
|
||||
* @sa Quotient::StateEvent
|
||||
*/
|
||||
#ifdef QUOTIENT_07
|
||||
class JoinRulesEvent : public StateEvent
|
||||
#else
|
||||
@@ -36,20 +29,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The join rule for the room.
|
||||
*
|
||||
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
|
||||
* the available join rules for a room.
|
||||
*/
|
||||
QString joinRule() const;
|
||||
|
||||
/**
|
||||
* @brief The allow rule for restricted rooms.
|
||||
*
|
||||
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
|
||||
* full details on allow rules.
|
||||
*/
|
||||
QJsonArray allow() const;
|
||||
};
|
||||
REGISTER_EVENT_TYPE(JoinRulesEvent)
|
||||
@@ -3,20 +3,17 @@
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <connection.h>
|
||||
#include <csapi/content-repo.h>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
LinkPreviewer::LinkPreviewer(QObject *parent, NeoChatRoom *room, QUrl url)
|
||||
LinkPreviewer::LinkPreviewer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_currentRoom(room)
|
||||
, m_loaded(false)
|
||||
, m_url(url)
|
||||
{
|
||||
loadUrlPreview();
|
||||
}
|
||||
|
||||
bool LinkPreviewer::loaded() const
|
||||
@@ -34,7 +31,7 @@ QString LinkPreviewer::description() const
|
||||
return m_description;
|
||||
}
|
||||
|
||||
QUrl LinkPreviewer::imageSource() const
|
||||
QString LinkPreviewer::imageSource() const
|
||||
{
|
||||
return m_imageSource;
|
||||
}
|
||||
@@ -46,41 +43,22 @@ QUrl LinkPreviewer::url() const
|
||||
|
||||
void LinkPreviewer::setUrl(QUrl url)
|
||||
{
|
||||
if (url != m_url) {
|
||||
m_url = url;
|
||||
urlChanged();
|
||||
loadUrlPreview();
|
||||
}
|
||||
}
|
||||
|
||||
void LinkPreviewer::loadUrlPreview()
|
||||
{
|
||||
if (m_url.scheme() == QStringLiteral("https")) {
|
||||
if (url.scheme() == QStringLiteral("https")) {
|
||||
m_loaded = false;
|
||||
Q_EMIT loadedChanged();
|
||||
|
||||
auto conn = m_currentRoom->connection();
|
||||
m_url = url;
|
||||
Q_EMIT urlChanged();
|
||||
|
||||
auto conn = Controller::instance().activeConnection();
|
||||
|
||||
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString());
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||
connect(job, &BaseJob::success, this, [this, job]() {
|
||||
const auto json = job->jsonData();
|
||||
m_title = json["og:title"].toString().trimmed();
|
||||
m_description = json["og:description"].toString().trimmed().replace("\n", " ");
|
||||
|
||||
auto imageUrl = QUrl(json["og:image"].toString());
|
||||
if (imageUrl.isValid() && imageUrl.scheme() == QStringLiteral("mxc")) {
|
||||
#ifdef QUOTIENT_07
|
||||
m_imageSource = conn->makeMediaUrl(imageUrl);
|
||||
#else
|
||||
QUrlQuery q(imageUrl.query());
|
||||
q.addQueryItem(QStringLiteral("user_id"), conn->userId());
|
||||
imageUrl.setQuery(q);
|
||||
m_imageSource = imageUrl;
|
||||
#endif
|
||||
} else {
|
||||
m_imageSource = QUrl();
|
||||
}
|
||||
|
||||
m_imageSource = json["og:image"].toString();
|
||||
m_loaded = true;
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT descriptionChanged();
|
||||
|
||||
@@ -6,65 +6,33 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class LinkPreviewer
|
||||
*
|
||||
* A class to download the link preview info for a URL and provide a QML interface for it.
|
||||
*
|
||||
* To use set the URL property and then access the other parameters which will be
|
||||
* populated once loaded is true.
|
||||
*/
|
||||
class LinkPreviewer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/**
|
||||
* @brief The URL to get the preview for.
|
||||
*/
|
||||
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
|
||||
Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged)
|
||||
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the preview information has been loaded.
|
||||
*/
|
||||
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
|
||||
|
||||
/**
|
||||
* @brief The title of the preview.
|
||||
*/
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
|
||||
/**
|
||||
* @brief The description of the preview.
|
||||
*/
|
||||
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
|
||||
|
||||
/**
|
||||
* @brief The image source for the preview.
|
||||
*/
|
||||
Q_PROPERTY(QUrl imageSource READ imageSource NOTIFY imageSourceChanged)
|
||||
|
||||
public:
|
||||
explicit LinkPreviewer(QObject *parent = nullptr, NeoChatRoom *room = nullptr, QUrl url = {});
|
||||
explicit LinkPreviewer(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QUrl url() const;
|
||||
void setUrl(QUrl);
|
||||
[[nodiscard]] bool loaded() const;
|
||||
[[nodiscard]] QString title() const;
|
||||
[[nodiscard]] QString description() const;
|
||||
[[nodiscard]] QUrl imageSource() const;
|
||||
[[nodiscard]] QString imageSource() const;
|
||||
[[nodiscard]] QUrl url() const;
|
||||
|
||||
void setUrl(QUrl);
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_currentRoom = nullptr;
|
||||
|
||||
bool m_loaded;
|
||||
QString m_title = QString();
|
||||
QString m_description = QString();
|
||||
QUrl m_imageSource = QUrl();
|
||||
QString m_title;
|
||||
QString m_description;
|
||||
QString m_imageSource;
|
||||
QUrl m_url;
|
||||
|
||||
void loadUrlPreview();
|
||||
|
||||
Q_SIGNALS:
|
||||
void loadedChanged();
|
||||
void titleChanged();
|
||||
@@ -72,4 +40,3 @@ Q_SIGNALS:
|
||||
void imageSourceChanged();
|
||||
void urlChanged();
|
||||
};
|
||||
Q_DECLARE_METATYPE(LinkPreviewer *)
|
||||
|
||||
208
src/logger.cpp
208
src/logger.cpp
@@ -1,208 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
|
||||
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
|
||||
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMutex>
|
||||
#include <QStandardPaths>
|
||||
|
||||
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
|
||||
static QtMessageHandler oldHandler = nullptr;
|
||||
static bool e2eeDebugEnabled = false;
|
||||
|
||||
class FileDebugStream : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FileDebugStream()
|
||||
: mType(QtCriticalMsg)
|
||||
{
|
||||
open(WriteOnly);
|
||||
}
|
||||
|
||||
bool isSequential() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
qint64 readData(char *, qint64) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
qint64 readLineData(char *, qint64) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 writeData(const char *data, qint64 len) override
|
||||
{
|
||||
if (!mFileName.isEmpty()) {
|
||||
QFile outputFile(mFileName);
|
||||
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
|
||||
outputFile.write(data, len);
|
||||
outputFile.putChar('\n');
|
||||
outputFile.close();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void setFileName(const QString &fileName)
|
||||
{
|
||||
mFileName = fileName;
|
||||
}
|
||||
|
||||
void setType(QtMsgType type)
|
||||
{
|
||||
mType = type;
|
||||
}
|
||||
|
||||
private:
|
||||
QString mFileName;
|
||||
QtMsgType mType;
|
||||
};
|
||||
|
||||
class DebugPrivate
|
||||
{
|
||||
public:
|
||||
DebugPrivate()
|
||||
: origHandler(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
~DebugPrivate()
|
||||
{
|
||||
qInstallMessageHandler(origHandler);
|
||||
file.close();
|
||||
}
|
||||
|
||||
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
QByteArray buf;
|
||||
QTextStream str(&buf);
|
||||
str << QDateTime::currentDateTime().toString(Qt::ISODate) << " [";
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
str << "DEBUG";
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
str << "INFO ";
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
str << "WARN ";
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
str << "FATAL";
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
str << "CRITICAL";
|
||||
break;
|
||||
}
|
||||
str << "] " << context.category << ": ";
|
||||
if (context.file && *context.file && context.line) {
|
||||
str << context.file << ":" << context.line << ": ";
|
||||
}
|
||||
if (context.function && *context.function) {
|
||||
str << context.function << ": ";
|
||||
}
|
||||
str << message << "\n";
|
||||
str.flush();
|
||||
file.write(buf.constData(), buf.size());
|
||||
file.flush();
|
||||
|
||||
if (oldHandler && (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled)) {
|
||||
oldHandler(type, context, message);
|
||||
}
|
||||
}
|
||||
|
||||
void setName(const QString &appName)
|
||||
{
|
||||
name = appName;
|
||||
|
||||
if (file.isOpen()) {
|
||||
file.close();
|
||||
}
|
||||
auto filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + appName;
|
||||
|
||||
QFileInfo infoOld(filePath + QLatin1String(".old"));
|
||||
if (infoOld.exists()) {
|
||||
QFile fileOld(infoOld.absoluteFilePath());
|
||||
const bool success = fileOld.remove();
|
||||
if (!success) {
|
||||
qFatal("Cannot remove old log file '%s': %s", qUtf8Printable(fileOld.fileName()), qUtf8Printable(fileOld.errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfo info(filePath);
|
||||
if (info.exists()) {
|
||||
QFile file(info.absoluteFilePath());
|
||||
const QString oldName = filePath + QLatin1String(".old");
|
||||
const bool success = file.copy(oldName);
|
||||
if (!success) {
|
||||
qFatal("Cannot rename log file '%s' to '%s': %s", qUtf8Printable(file.fileName()), qUtf8Printable(oldName), qUtf8Printable(file.errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfo finfo(filePath);
|
||||
if (!finfo.absoluteDir().exists()) {
|
||||
QDir().mkpath(finfo.absolutePath());
|
||||
}
|
||||
file.setFileName(filePath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
|
||||
}
|
||||
|
||||
void setOrigHandler(QtMessageHandler origHandler_)
|
||||
{
|
||||
origHandler = origHandler_;
|
||||
}
|
||||
|
||||
QMutex mutex;
|
||||
QFile file;
|
||||
QString name;
|
||||
QtMessageHandler origHandler;
|
||||
QByteArray loggingCategory;
|
||||
};
|
||||
|
||||
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||||
{
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
case QtInfoMsg:
|
||||
case QtWarningMsg:
|
||||
case QtCriticalMsg:
|
||||
sInstance()->log(type, context, message);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
sInstance()->log(QtInfoMsg, context, message);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void filter(QLoggingCategory *category)
|
||||
{
|
||||
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
|
||||
category->setEnabled(QtDebugMsg, true);
|
||||
} else if (oldCategoryFilter) {
|
||||
oldCategoryFilter(category);
|
||||
}
|
||||
}
|
||||
|
||||
void initLogging()
|
||||
{
|
||||
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtDebugMsg).isEnabled(QtDebugMsg);
|
||||
oldCategoryFilter = QLoggingCategory::installFilter(filter);
|
||||
oldHandler = qInstallMessageHandler(messageHandler);
|
||||
sInstance->setOrigHandler(oldHandler);
|
||||
sInstance->setName("neochat.log");
|
||||
}
|
||||
|
||||
#include "logger.moc"
|
||||
@@ -1,9 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
|
||||
*/
|
||||
void initLogging();
|
||||
48
src/login.h
48
src/login.h
@@ -10,67 +10,19 @@ namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Login
|
||||
*
|
||||
* A helper class for logging into a Matrix account.
|
||||
*/
|
||||
class Login : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief Whether the home server for the account is reachable.
|
||||
*/
|
||||
Q_PROPERTY(bool homeserverReachable READ homeserverReachable NOTIFY homeserverReachableChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the connection to the home server is being tested.
|
||||
*
|
||||
* True if NeoChat is trying to resolve the home server, false if not started
|
||||
* or complete.
|
||||
*/
|
||||
Q_PROPERTY(bool testing READ testing NOTIFY testingChanged)
|
||||
|
||||
/**
|
||||
* @brief The Matrix ID of the account that is being logged into.
|
||||
*/
|
||||
Q_PROPERTY(QString matrixId READ matrixId WRITE setMatrixId NOTIFY matrixIdChanged)
|
||||
|
||||
/**
|
||||
* @brief The password entered by the user to login to the account.
|
||||
*/
|
||||
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
|
||||
|
||||
/**
|
||||
* @brief The device name to assign to this session.
|
||||
*/
|
||||
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the home server of the account supports single sign on login.
|
||||
*/
|
||||
Q_PROPERTY(bool supportsSso READ supportsSso NOTIFY loginFlowsChanged STORED false)
|
||||
|
||||
/**
|
||||
* @brief Whether the home server of the account supports password login.
|
||||
*/
|
||||
Q_PROPERTY(bool supportsPassword READ supportsPassword NOTIFY loginFlowsChanged STORED false)
|
||||
|
||||
/**
|
||||
* @brief The URL for the single sign on session.
|
||||
*/
|
||||
Q_PROPERTY(QUrl ssoUrl READ ssoUrl NOTIFY ssoUrlChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether login process is ongoing.
|
||||
*/
|
||||
Q_PROPERTY(bool isLoggingIn READ isLoggingIn NOTIFY isLoggingInChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether login has successfully completed.
|
||||
*/
|
||||
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
|
||||
|
||||
public:
|
||||
|
||||
11
src/main.cpp
11
src/main.cpp
@@ -44,15 +44,14 @@
|
||||
#include "clipboard.h"
|
||||
#include "controller.h"
|
||||
#include "filetypesingleton.h"
|
||||
#include "joinrulesevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "logger.h"
|
||||
#include "login.h"
|
||||
#include "matriximageprovider.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/imagepacksmodel.h"
|
||||
#include "models/keywordnotificationrulemodel.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
@@ -73,8 +72,6 @@
|
||||
#ifdef QUOTIENT_07
|
||||
#include "pollhandler.h"
|
||||
#endif
|
||||
#include "models/statefiltermodel.h"
|
||||
#include "models/stickermodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "urlhelper.h"
|
||||
@@ -183,8 +180,6 @@ int main(int argc, char *argv[])
|
||||
KAboutData::setApplicationData(about);
|
||||
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
|
||||
|
||||
initLogging();
|
||||
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
// Copy over the included FontConfig configuration to the
|
||||
// app's config dir:
|
||||
@@ -240,18 +235,16 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
|
||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||
qmlRegisterType<StateFilterModel>("org.kde.neochat", 1, 0, "StateFilterModel");
|
||||
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||
#ifdef QUOTIENT_07
|
||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||
#endif
|
||||
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
||||
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
|
||||
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatUser>("org.kde.neochat", 1, 0, "NeoChatUser", {});
|
||||
|
||||
qRegisterMetaType<User *>("User*");
|
||||
|
||||
@@ -98,6 +98,18 @@ void ThumbnailResponse::prepareResult()
|
||||
Q_EMIT finished();
|
||||
}
|
||||
|
||||
void ThumbnailResponse::doCancel()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
if (job) {
|
||||
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||
job->abandon();
|
||||
}
|
||||
}
|
||||
|
||||
QQuickTextureFactory *ThumbnailResponse::textureFactory() const
|
||||
{
|
||||
QReadLocker _(&lock);
|
||||
@@ -110,6 +122,11 @@ QString ThumbnailResponse::errorString() const
|
||||
return errorStr;
|
||||
}
|
||||
|
||||
void ThumbnailResponse::cancel()
|
||||
{
|
||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
return new ThumbnailResponse(id, requestedSize);
|
||||
|
||||
@@ -15,13 +15,6 @@ namespace Quotient
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class ThumbnailResponse
|
||||
*
|
||||
* A QQuickImageResponse for an mxc image.
|
||||
*
|
||||
* @sa QQuickImageResponse
|
||||
*/
|
||||
class ThumbnailResponse : public QQuickImageResponse
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -32,6 +25,7 @@ public:
|
||||
private Q_SLOTS:
|
||||
void startRequest();
|
||||
void prepareResult();
|
||||
void doCancel();
|
||||
|
||||
private:
|
||||
const QString mediaId;
|
||||
@@ -45,22 +39,11 @@ private:
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override;
|
||||
QString errorString() const override;
|
||||
void cancel() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class MatrixImageProvider
|
||||
*
|
||||
* A QQuickAsyncImageProvider for mxc images.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider
|
||||
*/
|
||||
class MatrixImageProvider : public QQuickAsyncImageProvider
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Return a job to provide the image with the given ID.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider::requestImageResponse
|
||||
*/
|
||||
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "roommanager.h"
|
||||
#include <events/roommemberevent.h>
|
||||
#include <events/roompowerlevelsevent.h>
|
||||
@@ -18,41 +19,6 @@ QStringList rainbowColors{"#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500",
|
||||
"#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff",
|
||||
"#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
||||
|
||||
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
||||
room->connection()->leaveRoom(room);
|
||||
} else {
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto leaving = room->connection()->room(text);
|
||||
if (!leaving) {
|
||||
leaving = room->connection()->roomByAlias(text);
|
||||
}
|
||||
if (leaving) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
room->connection()->leaveRoom(leaving);
|
||||
} else {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
|
||||
auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
} else {
|
||||
room->connection()->user()->rename(text, room);
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
|
||||
QVector<ActionsModel::Action> actions{
|
||||
Action{
|
||||
QStringLiteral("shrug"),
|
||||
@@ -302,7 +268,31 @@ QVector<ActionsModel::Action> actions{
|
||||
},
|
||||
Action{
|
||||
QStringLiteral("part"),
|
||||
leaveRoomLambda,
|
||||
[](const QString &text, NeoChatRoom *room) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
||||
room->connection()->leaveRoom(room);
|
||||
} else {
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto leaving = room->connection()->room(text);
|
||||
if (!leaving) {
|
||||
leaving = room->connection()->roomByAlias(text);
|
||||
}
|
||||
if (leaving) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
room->connection()->leaveRoom(leaving);
|
||||
} else {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
std::nullopt,
|
||||
kli18n("[<room alias or id>]"),
|
||||
@@ -310,7 +300,31 @@ QVector<ActionsModel::Action> actions{
|
||||
},
|
||||
Action{
|
||||
QStringLiteral("leave"),
|
||||
leaveRoomLambda,
|
||||
[](const QString &text, NeoChatRoom *room) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
||||
room->connection()->leaveRoom(room);
|
||||
} else {
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto leaving = room->connection()->room(text);
|
||||
if (!leaving) {
|
||||
leaving = room->connection()->roomByAlias(text);
|
||||
}
|
||||
if (leaving) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
room->connection()->leaveRoom(leaving);
|
||||
} else {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
std::nullopt,
|
||||
kli18n("[<room alias or id>]"),
|
||||
@@ -333,15 +347,14 @@ QVector<ActionsModel::Action> actions{
|
||||
},
|
||||
Action{
|
||||
QStringLiteral("roomnick"),
|
||||
roomNickLambda,
|
||||
false,
|
||||
std::nullopt,
|
||||
kli18n("<display name>"),
|
||||
kli18n("Changes your display name in this room"),
|
||||
},
|
||||
Action{
|
||||
QStringLiteral("myroomnick"),
|
||||
roomNickLambda,
|
||||
[](const QString &text, NeoChatRoom *room) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
} else {
|
||||
room->connection()->user()->rename(text, room);
|
||||
}
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
std::nullopt,
|
||||
kli18n("<display name>"),
|
||||
|
||||
@@ -9,42 +9,20 @@
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsModel
|
||||
*
|
||||
* This class defines a model for chat actions.
|
||||
*
|
||||
* @note A chat action is a message starting with /, resulting in something other
|
||||
* than a normal message being sent (e.g. /me, /join).
|
||||
*/
|
||||
class ActionsModel : public QAbstractListModel
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Definition of an action.
|
||||
*/
|
||||
struct Action {
|
||||
QString prefix; /**< The prefix, without '/' and space after the word. */
|
||||
/**
|
||||
* @brief The function to execute when the action is triggered.
|
||||
*/
|
||||
// The prefix, without '/' and space after the word
|
||||
QString prefix;
|
||||
std::function<QString(const QString &, NeoChatRoom *)> handle;
|
||||
/**
|
||||
* @brief Whether the action is a message type action.
|
||||
*
|
||||
* If this is true, a message action will be sent. If this is false, this
|
||||
* message does some action on the client and should not be sent as a message.
|
||||
*/
|
||||
// If this is true, this action transforms a message to a different message and it will be sent.
|
||||
// If this is false, this message does some action on the client and should not be sent as a message.
|
||||
bool messageAction;
|
||||
/**
|
||||
* @brief The new message type of a message being sent.
|
||||
*
|
||||
* For a non-message action or a message action that outputs the same type
|
||||
* as its input, it's nullopt.
|
||||
*/
|
||||
// If this action changes the message type, this is the new message type. Otherwise it's nullopt
|
||||
std::optional<Quotient::RoomMessageEvent::MsgType> messageType = std::nullopt;
|
||||
KLazyLocalizedString parameters; /**< The input parameters expected by the action. */
|
||||
KLazyLocalizedString description; /**< The description of the action. */
|
||||
KLazyLocalizedString parameters;
|
||||
KLazyLocalizedString description;
|
||||
};
|
||||
static ActionsModel &instance()
|
||||
{
|
||||
@@ -52,41 +30,18 @@ public:
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
Prefix = Qt::DisplayRole, /**< The prefix, without '/' and space after the word. */
|
||||
Description, /**< The description of the action. */
|
||||
CompletionType, /**< The completion type (always "action" for this model). */
|
||||
Parameters, /**< The input parameters expected by the action. */
|
||||
Prefix = Qt::DisplayRole,
|
||||
Description,
|
||||
CompletionType,
|
||||
Parameters,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Return a vector with all supported actions.
|
||||
*/
|
||||
QVector<Action> &allActions() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole)
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||
}
|
||||
@@ -24,8 +24,6 @@ QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
@@ -36,7 +34,6 @@ QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -50,7 +47,7 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
@@ -108,7 +105,7 @@ QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
@@ -126,46 +123,12 @@ QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
uniqueAuthors = uniqueAuthors.mid(0, 5);
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::excessAuthors(int row) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = row; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int excessAuthors;
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
excessAuthors = uniqueAuthors.count() - 5;
|
||||
} else {
|
||||
excessAuthors = 0;
|
||||
}
|
||||
QString excessAuthorsString;
|
||||
if (excessAuthors == 0) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("+ %1").arg(excessAuthors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,59 +6,26 @@
|
||||
#include "messageeventmodel.h"
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class CollapseStateProxyModel
|
||||
*
|
||||
* This model aggregates multiple sequential state events into a single entry.
|
||||
*
|
||||
* Events are only aggregated if they happened on the same day.
|
||||
*
|
||||
* @sa MessageEventModel
|
||||
*/
|
||||
class CollapseStateProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
||||
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1,
|
||||
StateEventsRole,
|
||||
AuthorListRole,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::data
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractProxyModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||
* @brief QString aggregating the text of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
|
||||
/**
|
||||
* @brief Return a list of consecutive state events starting at row.
|
||||
*
|
||||
@@ -66,14 +33,8 @@ private:
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief List of the first 5 unique authors for the aggregate state events starting at row.
|
||||
* @brief List of unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QString excessAuthors(int row) const;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include "actionsmodel.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "completionproxymodel.h"
|
||||
#include "customemojimodel.h"
|
||||
#include "emojimodel.h"
|
||||
|
||||
@@ -13,89 +13,41 @@ class UserListModel;
|
||||
class NeoChatRoom;
|
||||
class RoomListModel;
|
||||
|
||||
/**
|
||||
* @class CompletionModel
|
||||
*
|
||||
* This class defines the model for suggesting completions in chat text.
|
||||
*
|
||||
* This model is able to select the appropriate completion type for the input text
|
||||
* and present a list of options that can be presented to the user.
|
||||
*/
|
||||
class CompletionModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current text to search for completions.
|
||||
*/
|
||||
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is getting completions for.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
/**
|
||||
* @brief The current type of completion being done on the entered text.
|
||||
*
|
||||
* @sa AutoCompletionType
|
||||
*/
|
||||
Q_PROPERTY(AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged);
|
||||
|
||||
/**
|
||||
* @brief The RoomListModel to be used for room completions.
|
||||
*/
|
||||
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the different types of completion available.
|
||||
*/
|
||||
enum AutoCompletionType {
|
||||
User, /**< A user in the current room. */
|
||||
Room, /**< A matrix room. */
|
||||
Emoji, /**< An emoji. */
|
||||
Command, /**< A / command. */
|
||||
None, /**< No available completion for the current text. */
|
||||
User,
|
||||
Room,
|
||||
Emoji,
|
||||
Command,
|
||||
None,
|
||||
};
|
||||
Q_ENUM(AutoCompletionType)
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
Text = Qt::DisplayRole, /**< The main text to show. */
|
||||
Subtitle, /**< The subtitle text to show. */
|
||||
Icon, /**< The icon to show. */
|
||||
ReplacedText, /**< The text to replace the input text with for the completion. */
|
||||
Text = Qt::DisplayRole,
|
||||
Subtitle,
|
||||
Icon,
|
||||
ReplacedText,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
CompletionModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
QString text() const;
|
||||
void setText(const QString &text, const QString &fullText);
|
||||
void updateCompletion();
|
||||
|
||||
NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
@@ -104,7 +56,6 @@ public:
|
||||
void setRoomListModel(RoomListModel *roomListModel);
|
||||
|
||||
AutoCompletionType autoCompletionType() const;
|
||||
void setAutoCompletionType(AutoCompletionType autoCompletionType);
|
||||
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
@@ -119,7 +70,7 @@ private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
AutoCompletionType m_autoCompletionType = None;
|
||||
|
||||
void updateCompletion();
|
||||
void setAutoCompletionType(AutoCompletionType autoCompletionType);
|
||||
|
||||
UserListModel *m_userListModel;
|
||||
RoomListModel *m_roomListModel;
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "completionproxymodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
Q_UNUSED(sourceParent);
|
||||
@@ -43,6 +44,7 @@ int CompletionProxyModel::secondaryFilterRole() const
|
||||
void CompletionProxyModel::setSecondaryFilterRole(int role)
|
||||
{
|
||||
m_secondaryFilterRole = role;
|
||||
Q_EMIT secondaryFilterRoleChanged();
|
||||
}
|
||||
|
||||
QString CompletionProxyModel::filterText() const
|
||||
@@ -53,6 +55,7 @@ QString CompletionProxyModel::filterText() const
|
||||
void CompletionProxyModel::setFilterText(const QString &filterText)
|
||||
{
|
||||
m_filterText = filterText;
|
||||
Q_EMIT filterTextChanged();
|
||||
}
|
||||
|
||||
void CompletionProxyModel::setFullText(const QString &fullText)
|
||||
|
||||
@@ -5,71 +5,28 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class CompletionProxyModel
|
||||
*
|
||||
* A filter model to sort and filter completion results.
|
||||
*
|
||||
* This model is designed to work with multiple source models depending upon the
|
||||
* completion type.
|
||||
*
|
||||
* A model value will be shown if its primary or secondary role values start with
|
||||
* the filter text. The exception is if the full text perfectly matches
|
||||
* the primary filter role value in which case the completion ends (i.e. the filter
|
||||
* will return no results).
|
||||
*
|
||||
* @note The filter is primarily design to work with strings, therefore make sure
|
||||
* that the source model roles that are to be filtered are strings.
|
||||
*/
|
||||
class CompletionProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int secondaryFilterRole READ secondaryFilterRole WRITE setSecondaryFilterRole NOTIFY secondaryFilterRoleChanged)
|
||||
Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Wether a row should be shown or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::lessThan
|
||||
*/
|
||||
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the current secondary filter role.
|
||||
*/
|
||||
int secondaryFilterRole() const;
|
||||
|
||||
/**
|
||||
* @brief Set the secondary filter role.
|
||||
*
|
||||
* Refer to the source model for what value corresponds to what role.
|
||||
*/
|
||||
void setSecondaryFilterRole(int role);
|
||||
|
||||
/**
|
||||
* @brief Get the current text being used to filter the source model.
|
||||
*/
|
||||
QString filterText() const;
|
||||
|
||||
/**
|
||||
* @brief Set the text to be used to filter the source model.
|
||||
*/
|
||||
void setFilterText(const QString &filterText);
|
||||
|
||||
/**
|
||||
* @brief Set the full text in the chatbar after the completion start.
|
||||
*
|
||||
* This is used to automatically end the completion if the user replicated the
|
||||
* primary filter role value perfectly.
|
||||
*/
|
||||
void setFullText(const QString &fullText);
|
||||
|
||||
Q_SIGNALS:
|
||||
void secondaryFilterRoleChanged();
|
||||
void filterTextChanged();
|
||||
|
||||
private:
|
||||
int m_secondaryFilterRole = -1;
|
||||
QString m_filterText;
|
||||
|
||||
@@ -2,17 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "customemojimodel.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "controller.h"
|
||||
#include "emojimodel.h"
|
||||
|
||||
#include <connection.h>
|
||||
#include <csapi/account-data.h>
|
||||
#include <csapi/content-repo.h>
|
||||
#include <events/eventcontent.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -63,31 +58,17 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
||||
|
||||
if (running(job)) {
|
||||
connect(job, &BaseJob::success, this, [name, location, job] {
|
||||
connect(job, &BaseJob::success, this, [this, name, job] {
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"].toObject();
|
||||
|
||||
QString url;
|
||||
#ifdef QUOTIENT_07
|
||||
url = job->contentUri().toString();
|
||||
#else
|
||||
url = job->contentUri();
|
||||
#endif
|
||||
|
||||
QImage image(location.toLocalFile());
|
||||
QJsonObject imageInfo;
|
||||
imageInfo["w"] = image.width();
|
||||
imageInfo["h"] = image.height();
|
||||
imageInfo["mimetype"] = QMimeDatabase().mimeTypeForFile(location.toLocalFile()).name();
|
||||
imageInfo["size"] = image.sizeInBytes();
|
||||
|
||||
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
|
||||
{QStringLiteral("url"), url},
|
||||
{QStringLiteral("info"), imageInfo},
|
||||
{QStringLiteral("body"), location.fileName()},
|
||||
#ifdef QUOTIENT_07
|
||||
{QStringLiteral("url"), job->contentUri().toString()}
|
||||
#else
|
||||
{QStringLiteral("url"), job->contentUri()}
|
||||
#endif
|
||||
});
|
||||
|
||||
json["images"] = emojiData;
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
|
||||
});
|
||||
@@ -160,8 +141,6 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
|
||||
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
||||
case Roles::MxcUrl:
|
||||
return data.url.mid(6);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
|
||||
@@ -17,29 +17,19 @@ struct CustomEmoji {
|
||||
Q_PROPERTY(QString name MEMBER name)
|
||||
};
|
||||
|
||||
/**
|
||||
* @class CustomEmojiModel
|
||||
*
|
||||
* This class defines the model for custom user emojis.
|
||||
*
|
||||
* This is based upon the im.ponies.user_emotes spec (MSC2545).
|
||||
*/
|
||||
class CustomEmojiModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
Name = Qt::DisplayRole, /**< The name of the emoji. */
|
||||
ImageURL, /**< The URL for the custom emoji. */
|
||||
ModelData, /**< for emulating the regular emoji model's usage, otherwise the UI code would get too complicated. */
|
||||
MxcUrl = 50, /**< The mxc source URL for the custom emoji. */
|
||||
DisplayRole = 51, /**< The name of the emoji. For compatibility with EmojiModel. */
|
||||
ReplacedTextRole = 52, /**< The name of the emoji. For compatibility with EmojiModel. */
|
||||
DescriptionRole = 53, /**< Invalid, reserved. For compatibility with EmojiModel. */
|
||||
Name = Qt::DisplayRole,
|
||||
ImageURL,
|
||||
ModelData, // for emulating the regular emoji model's usage, otherwise the UI code would get too complicated
|
||||
MxcUrl = 50,
|
||||
DisplayRole = 51,
|
||||
ReplacedTextRole = 52,
|
||||
DescriptionRole = 53, // also invalid, reserved
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
@@ -49,45 +39,14 @@ public:
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Substitute any custom emojis for an image in the input text.
|
||||
*/
|
||||
Q_INVOKABLE QString preprocessText(const QString &it);
|
||||
|
||||
/**
|
||||
* @brief Return a list of custom emojis where the name contains the filter text.
|
||||
*/
|
||||
Q_INVOKABLE QVariantList filterModel(const QString &filter);
|
||||
|
||||
/**
|
||||
* @brief Add a new emoji to the model.
|
||||
*/
|
||||
Q_INVOKABLE void addEmoji(const QString &name, const QUrl &location);
|
||||
|
||||
/**
|
||||
* @brief Remove an emoji from the model.
|
||||
*/
|
||||
Q_INVOKABLE void removeEmoji(const QString &name);
|
||||
|
||||
private:
|
||||
|
||||
@@ -12,67 +12,28 @@ namespace Quotient
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class DevicesModel
|
||||
*
|
||||
* This class defines the model for managing the devices of the local user.
|
||||
*
|
||||
* A device is any session where the local user is logged into a client. This means
|
||||
* the same physical device can have multiple sessions for example if the user uses
|
||||
* multiple clients on the same machine.
|
||||
*/
|
||||
class DevicesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current connection that the model is getting its devices from.
|
||||
*/
|
||||
Q_PROPERTY(Quotient::Connection *connection READ connection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
Id, /**< The device ID. */
|
||||
DisplayName, /**< Display name set by the user for this device. */
|
||||
LastIp, /**< The IP address where this device was last seen. */
|
||||
LastTimestamp, /**< The timestamp when this devices was last seen. */
|
||||
Id,
|
||||
DisplayName,
|
||||
LastIp,
|
||||
LastTimestamp,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
DevicesModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Logout the device at the given index.
|
||||
*/
|
||||
Q_INVOKABLE void logout(int index, const QString &password);
|
||||
|
||||
/**
|
||||
* @brief Set the display name of the device at the given index.
|
||||
*/
|
||||
Q_INVOKABLE void setName(int index, const QString &name);
|
||||
|
||||
Quotient::Connection *connection() const;
|
||||
|
||||
@@ -51,30 +51,12 @@ struct Emoji {
|
||||
|
||||
Q_DECLARE_METATYPE(Emoji)
|
||||
|
||||
/**
|
||||
* @class EmojiModel
|
||||
*
|
||||
* This class defines the model for visualising a list of emojis.
|
||||
*/
|
||||
class EmojiModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief Return a list of recently used emojis.
|
||||
*/
|
||||
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
||||
|
||||
/**
|
||||
* @brief Return a list of emoji categories.
|
||||
*
|
||||
* @note No custom emoji categories will be included.
|
||||
*/
|
||||
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Return a list of emoji categories with custom emojis.
|
||||
*/
|
||||
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
|
||||
|
||||
public:
|
||||
@@ -84,92 +66,47 @@ public:
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum RoleNames {
|
||||
ShortNameRole = Qt::DisplayRole, /**< The name of the emoji. */
|
||||
UnicodeRole, /**< The unicode character of the emoji. */
|
||||
InvalidRole = 50, /**< Invalid, reserved. */
|
||||
DisplayRole = 51, /**< The display text for an emoji. */
|
||||
ReplacedTextRole = 52, /**< The text to replace the short name with (i.e. the unicode character). */
|
||||
DescriptionRole = 53, /**< The long description of an emoji. */
|
||||
ShortNameRole = Qt::DisplayRole,
|
||||
UnicodeRole,
|
||||
InvalidRole = 50,
|
||||
DisplayRole = 51,
|
||||
ReplacedTextRole = 52,
|
||||
DescriptionRole = 53,
|
||||
};
|
||||
Q_ENUM(RoleNames);
|
||||
|
||||
/**
|
||||
* @brief Defines the potential categories an emoji can be placed in.
|
||||
*/
|
||||
enum Category {
|
||||
Custom, /**< A custom user emoji. */
|
||||
Search, /**< The results of a filter. */
|
||||
SearchNoCustom, /**< The results of a filter with no custom emojis. */
|
||||
History, /**< Recently used emojis. */
|
||||
HistoryNoCustom, /**< Recently used emojis with no custom emojis. */
|
||||
Smileys, /**< Smileys & emotion emojis. */
|
||||
People, /**< People & Body emojis. */
|
||||
Nature, /**< Animals & Nature emojis. */
|
||||
Food, /**< Food & Drink emojis. */
|
||||
Activities, /**< Activities emojis. */
|
||||
Travel, /**< Travel & Places emojis. */
|
||||
Objects, /**< Objects emojis. */
|
||||
Symbols, /**< Symbols emojis. */
|
||||
Flags, /**< Flags emojis. */
|
||||
Component, /**< ??? */
|
||||
Custom,
|
||||
Search,
|
||||
SearchNoCustom,
|
||||
History,
|
||||
HistoryNoCustom,
|
||||
Smileys,
|
||||
People,
|
||||
Nature,
|
||||
Food,
|
||||
Activities,
|
||||
Travel,
|
||||
Objects,
|
||||
Symbols,
|
||||
Flags,
|
||||
Component,
|
||||
};
|
||||
Q_ENUM(Category)
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa RoleNames, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Return a filtered list of emojis.
|
||||
*
|
||||
* @note This includes custom emojis, use filterModelNoCustom to return a result
|
||||
* without custom emojis.
|
||||
*
|
||||
* @sa filterModelNoCustom
|
||||
*/
|
||||
Q_INVOKABLE QVariantList history() const;
|
||||
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
||||
|
||||
/**
|
||||
* @brief Return a filtered list of emojis without custom emojis.
|
||||
*
|
||||
* @note Use filterModel to return a result with custom emojis.
|
||||
*
|
||||
* @sa filterModel
|
||||
*/
|
||||
Q_INVOKABLE static QVariantList filterModelNoCustom(const QString &filter, bool limit = true);
|
||||
|
||||
/**
|
||||
* @brief Return a list of emojis for the given category.
|
||||
*/
|
||||
Q_INVOKABLE QVariantList emojis(Category category) const;
|
||||
|
||||
/**
|
||||
* @brief Return a list of emoji tones for the given base emoji.
|
||||
*/
|
||||
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
||||
|
||||
Q_INVOKABLE QVariantList history() const;
|
||||
QVariantList categories() const;
|
||||
QVariantList categoriesWithCustom() const;
|
||||
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "imagepacksmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ImagePacksModel::ImagePacksModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int ImagePacksModel::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
return m_events.count();
|
||||
}
|
||||
|
||||
QVariant ImagePacksModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const auto &event = m_events[index.row()];
|
||||
if (role == DisplayNameRole) {
|
||||
if (event.pack->displayName) {
|
||||
return *event.pack->displayName;
|
||||
}
|
||||
}
|
||||
if (role == AvatarUrlRole) {
|
||||
if (event.pack->avatarUrl) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(*event.pack->avatarUrl);
|
||||
#endif
|
||||
} else if (!event.images.empty()) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(event.images[0].url);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ImagePacksModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{DisplayNameRole, "displayName"},
|
||||
{AvatarUrlRole, "avatarUrl"},
|
||||
{AttributionRole, "attribution"},
|
||||
{IdRole, "id"},
|
||||
};
|
||||
}
|
||||
|
||||
NeoChatRoom *ImagePacksModel::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room) {
|
||||
disconnect(m_room, nullptr, this, nullptr);
|
||||
}
|
||||
m_room = room;
|
||||
|
||||
beginResetModel();
|
||||
m_events.clear();
|
||||
|
||||
// TODO listen to account data changing
|
||||
// TODO listen to packs changing
|
||||
if (m_room->connection()->hasAccountData("im.ponies.user_emotes"_ls)) {
|
||||
auto json = m_room->connection()->accountData("im.ponies.user_emotes"_ls)->contentJson();
|
||||
json["pack"] = QJsonObject{
|
||||
{"display_name", i18n("Own Stickers")},
|
||||
};
|
||||
const auto &content = ImagePackEventContent(json);
|
||||
if (!content.images.isEmpty()) {
|
||||
m_events += ImagePackEventContent(json);
|
||||
}
|
||||
}
|
||||
const auto &accountData = m_room->connection()->accountData("im.ponies.emote_rooms"_ls);
|
||||
if (accountData) {
|
||||
const auto &rooms = accountData->contentJson()["rooms"_ls].toObject();
|
||||
for (const auto &roomId : rooms.keys()) {
|
||||
if (roomId == m_room->id()) {
|
||||
continue;
|
||||
}
|
||||
auto packs = rooms[roomId].toObject();
|
||||
const auto &stickerRoom = m_room->connection()->room(roomId);
|
||||
for (const auto &packKey : packs.keys()) {
|
||||
#ifdef QUOTIENT_07
|
||||
const auto &pack = stickerRoom->currentState().get<ImagePackEvent>(packKey);
|
||||
if (pack) {
|
||||
const auto packContent = pack->content();
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
auto events = m_room->currentState().eventsOfType("im.ponies.room_emotes");
|
||||
for (const auto &event : events) {
|
||||
auto packContent = eventCast<const ImagePackEvent>(event)->content();
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
endResetModel();
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
bool ImagePacksModel::showStickers() const
|
||||
{
|
||||
return m_showStickers;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setShowStickers(bool showStickers)
|
||||
{
|
||||
m_showStickers = showStickers;
|
||||
Q_EMIT showStickersChanged();
|
||||
}
|
||||
|
||||
bool ImagePacksModel::showEmoticons() const
|
||||
{
|
||||
return m_showEmoticons;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setShowEmoticons(bool showEmoticons)
|
||||
{
|
||||
m_showEmoticons = showEmoticons;
|
||||
Q_EMIT showEmoticonsChanged();
|
||||
}
|
||||
QVector<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_events.size()) {
|
||||
return {};
|
||||
}
|
||||
return m_events[index].images;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "events/imagepackevent.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ImagePacksModel
|
||||
*
|
||||
* Defines the model for visualising image packs.
|
||||
*
|
||||
* See Matrix MSC2545 for more details on image packs.
|
||||
* https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
|
||||
*/
|
||||
class ImagePacksModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is being used in.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether sticker image packs should be shown.
|
||||
*/
|
||||
Q_PROPERTY(bool showStickers READ showStickers WRITE setShowStickers NOTIFY showStickersChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether emoticon image packs should be shown.
|
||||
*/
|
||||
Q_PROPERTY(bool showEmoticons READ showEmoticons WRITE setShowEmoticons NOTIFY showEmoticonsChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
DisplayNameRole = Qt::DisplayRole, /**< The display name of the image pack. */
|
||||
AvatarUrlRole, /**< The source mxc URL for the pack avatar. */
|
||||
AttributionRole, /**< The attribution for the pack author(s). */
|
||||
IdRole, /**< The ID of the image pack. */
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
explicit ImagePacksModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &index) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
[[nodiscard]] bool showStickers() const;
|
||||
void setShowStickers(bool showStickers);
|
||||
|
||||
[[nodiscard]] bool showEmoticons() const;
|
||||
void setShowEmoticons(bool showEmoticons);
|
||||
|
||||
/**
|
||||
* @brief Return a vector of the images in the pack at the given index.
|
||||
*/
|
||||
[[nodiscard]] QVector<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void showStickersChanged();
|
||||
void showEmoticonsChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QVector<Quotient::ImagePackEventContent> m_events;
|
||||
bool m_showStickers = true;
|
||||
bool m_showEmoticons = true;
|
||||
};
|
||||
@@ -7,54 +7,23 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
/**
|
||||
* @class KeywordNotificationRuleModel
|
||||
*
|
||||
* This class defines the model for managing notification push rule keywords.
|
||||
*/
|
||||
class KeywordNotificationRuleModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole, /**< The push rule keyword. */
|
||||
NameRole = Qt::DisplayRole,
|
||||
};
|
||||
|
||||
KeywordNotificationRuleModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Add a new keyword to the model.
|
||||
*/
|
||||
Q_INVOKABLE void addKeyword(const QString &keyword);
|
||||
|
||||
/**
|
||||
* @brief Remove a keyword from the model.
|
||||
*/
|
||||
Q_INVOKABLE void removeKeywordAtIndex(int index);
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "messageeventmodel_logging.h"
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include <connection.h>
|
||||
@@ -15,9 +14,9 @@
|
||||
#include <user.h>
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
#include "events/pollevent.h"
|
||||
#include "pollevent.h"
|
||||
#endif
|
||||
#include "events/stickerevent.h"
|
||||
#include "stickerevent.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
@@ -28,57 +27,54 @@
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "neochatuser.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[DelegateTypeRole] = "delegateType";
|
||||
roles[PlainText] = "plainText";
|
||||
roles[EventTypeRole] = "eventType";
|
||||
roles[MessageRole] = "message";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[TimeRole] = "time";
|
||||
roles[SectionRole] = "section";
|
||||
roles[AuthorRole] = "author";
|
||||
roles[ContentRole] = "content";
|
||||
roles[ContentTypeRole] = "contentType";
|
||||
roles[HighlightRole] = "isHighlighted";
|
||||
roles[SpecialMarksRole] = "marks";
|
||||
roles[LongOperationRole] = "progressInfo";
|
||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
||||
roles[AnnotationRole] = "annotation";
|
||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||
roles[ShowLinkPreviewRole] = "showLinkPreview";
|
||||
roles[LinkPreviewRole] = "linkPreview";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyAuthor] = "replyAuthor";
|
||||
roles[ReplyRole] = "reply";
|
||||
roles[ReplyIdRole] = "replyId";
|
||||
roles[ReplyMediaInfoRole] = "replyMediaInfo";
|
||||
roles[UserMarkerRole] = "userMarker";
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
roles[ShowSectionRole] = "showSection";
|
||||
roles[ReadMarkersRole] = "readMarkers";
|
||||
roles[ExcessReadMarkersRole] = "excessReadMarkers";
|
||||
roles[ReadMarkersStringRole] = "readMarkersString";
|
||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||
roles[ReactionRole] = "reaction";
|
||||
roles[IsEditedRole] = "isEdited";
|
||||
roles[SourceRole] = "source";
|
||||
roles[MimeTypeRole] = "mimeType";
|
||||
roles[FormattedBodyRole] = "formattedBody";
|
||||
roles[AuthorIdRole] = "authorId";
|
||||
roles[MediaUrlRole] = "mediaUrl";
|
||||
roles[VerifiedRole] = "verified";
|
||||
roles[DisplayNameForInitialsRole] = "displayNameForInitials";
|
||||
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
||||
roles[IsRedactedRole] = "isRedacted";
|
||||
roles[GenericDisplayRole] = "genericDisplay";
|
||||
roles[IsPendingRole] = "isPending";
|
||||
roles[LatitudeRole] = "latitude";
|
||||
roles[LongitudeRole] = "longitude";
|
||||
roles[AssetRole] = "asset";
|
||||
return roles;
|
||||
}
|
||||
|
||||
MessageEventModel::MessageEventModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_currentRoom(nullptr)
|
||||
{
|
||||
using namespace Quotient;
|
||||
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
||||
@@ -91,11 +87,6 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
||||
|
||||
MessageEventModel::~MessageEventModel() = default;
|
||||
|
||||
NeoChatRoom *MessageEventModel::room() const
|
||||
{
|
||||
return m_currentRoom;
|
||||
}
|
||||
|
||||
void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (room == m_currentRoom) {
|
||||
@@ -105,20 +96,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginResetModel();
|
||||
if (m_currentRoom) {
|
||||
m_currentRoom->disconnect(this);
|
||||
m_linkPreviewers.clear();
|
||||
}
|
||||
|
||||
m_currentRoom = room;
|
||||
if (room) {
|
||||
m_lastReadEventIndex = QPersistentModelIndex(QModelIndex());
|
||||
room->setDisplayed();
|
||||
|
||||
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
||||
if (auto e = &*event->viewAs<RoomMessageEvent>()) {
|
||||
createLinkPreviewerForEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
||||
room->getPreviousContent(50);
|
||||
}
|
||||
@@ -130,12 +113,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
using namespace Quotient;
|
||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &&event : events) {
|
||||
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
if (message != nullptr) {
|
||||
createLinkPreviewerForEvent(message);
|
||||
|
||||
if (NeoChatConfig::self()->showFancyEffects()) {
|
||||
if (NeoChatConfig::self()->showFancyEffects()) {
|
||||
for (auto &event : events) {
|
||||
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
if (message) {
|
||||
QString planBody = message->plainBody();
|
||||
// snowflake
|
||||
const QString snowlakeEmoji = QString::fromUtf8("\xE2\x9D\x84");
|
||||
@@ -165,26 +146,16 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
}
|
||||
}
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &event : events) {
|
||||
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
if (message) {
|
||||
createLinkPreviewerForEvent(message);
|
||||
}
|
||||
}
|
||||
if (rowCount() > 0) {
|
||||
rowBelowInserted = rowCount() - 1; // See #312
|
||||
}
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_currentRoom, &Room::addedMessages, this, [this](int lowest, int biggest) {
|
||||
if (m_initialized) {
|
||||
endInsertRows();
|
||||
}
|
||||
endInsertRows();
|
||||
if (!m_lastReadEventIndex.isValid()) {
|
||||
// no read marker, so see if we need to create one.
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -202,7 +173,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, 0, 0);
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||
@@ -249,7 +219,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
connect(m_currentRoom, &Room::changed, this, [this]() {
|
||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||
auto event = it->event();
|
||||
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole});
|
||||
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole});
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent);
|
||||
@@ -263,7 +233,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localUser()->id();
|
||||
qDebug() << "Connected to room" << room->id() << "as" << room->localUser()->id();
|
||||
} else {
|
||||
lastReadEventId.clear();
|
||||
}
|
||||
@@ -305,7 +275,6 @@ void MessageEventModel::moveReadMarker(const QString &toEventId)
|
||||
// need to be displayed.
|
||||
if (newRow > timelineBaseIndex()) {
|
||||
// The user didn't read all the messages yet.
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, newRow, newRow);
|
||||
m_lastReadEventIndex = QPersistentModelIndex(index(newRow, 0));
|
||||
endInsertRows();
|
||||
@@ -341,10 +310,11 @@ int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &
|
||||
} else {
|
||||
const auto timelineIt = m_currentRoom->findInTimeline(id);
|
||||
if (timelineIt == m_currentRoom->historyEdge()) {
|
||||
qWarning() << "Trying to refresh inexistent event:" << id;
|
||||
return -1;
|
||||
}
|
||||
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||
if (data(index(row, 0), DelegateTypeRole).toInt() == ReadMarker || data(index(row, 0), DelegateTypeRole).toInt() == Other) {
|
||||
if (data(index(row, 0), EventTypeRole).toInt() == ReadMarker || data(index(row, 0), EventTypeRole).toInt() == Other) {
|
||||
row++;
|
||||
}
|
||||
}
|
||||
@@ -378,7 +348,7 @@ QDateTime MessageEventModel::makeMessageTimestamp(const Quotient::Room::rev_iter
|
||||
};
|
||||
|
||||
// What kind of room is that?..
|
||||
qCCritical(MessageEvent) << "No valid timestamps in the room timeline!";
|
||||
qCritical() << "No valid timestamps in the room timeline!";
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -448,24 +418,12 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
|
||||
}
|
||||
}
|
||||
|
||||
static const QVariantMap emptyUser = {
|
||||
{"isLocalUser", false},
|
||||
{"id", QString()},
|
||||
{"avatarSource", QUrl()},
|
||||
{"avatarMediaId", QString()},
|
||||
{"avatarUrl", QString()},
|
||||
{"displayName", QString()},
|
||||
{"display", QString()},
|
||||
{"color", QColor()},
|
||||
{"object", QVariant()},
|
||||
};
|
||||
|
||||
inline QVariantMap userInContext(NeoChatUser *user, NeoChatRoom *room)
|
||||
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt)
|
||||
{
|
||||
Q_UNUSED(evt)
|
||||
return QVariantMap{
|
||||
{"isLocalUser", user->id() == room->localUser()->id()},
|
||||
{"id", user->id()},
|
||||
{"avatarSource", room->avatarForMember(user)},
|
||||
{"avatarMediaId", user->avatarMediaId(room)},
|
||||
{"avatarUrl", user->avatarUrl(room)},
|
||||
{"displayName", user->displayname(room)},
|
||||
@@ -475,8 +433,6 @@ inline QVariantMap userInContext(NeoChatUser *user, NeoChatRoom *room)
|
||||
};
|
||||
}
|
||||
|
||||
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
||||
|
||||
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
const auto row = idx.row();
|
||||
@@ -489,7 +445,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (m_lastReadEventIndex.row() == row) {
|
||||
switch (role) {
|
||||
case DelegateTypeRole:
|
||||
case EventTypeRole:
|
||||
return DelegateType::ReadMarker;
|
||||
case TimeRole: {
|
||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||
@@ -533,15 +489,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == PlainText) {
|
||||
if (role == MessageRole) {
|
||||
return m_currentRoom->eventToString(evt);
|
||||
}
|
||||
|
||||
if (role == SourceRole) {
|
||||
return QJsonDocument(evt.fullJson()).toJson();
|
||||
return evt.originalJson();
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
if (role == EventTypeRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
@@ -554,8 +510,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return DelegateType::Audio;
|
||||
case MessageEventType::Video:
|
||||
return DelegateType::Video;
|
||||
case MessageEventType::Location:
|
||||
return DelegateType::Location;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -592,7 +546,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == AuthorRole) {
|
||||
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
|
||||
return userInContext(author, m_currentRoom);
|
||||
return userAtEvent(author, m_currentRoom, evt);
|
||||
}
|
||||
|
||||
if (role == ContentTypeRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
const auto &contentType = e->mimeType().name();
|
||||
return contentType == "text/plain" ? QStringLiteral("text/html") : contentType;
|
||||
}
|
||||
return QStringLiteral("text/plain");
|
||||
}
|
||||
|
||||
if (role == ContentRole) {
|
||||
@@ -602,9 +564,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
if (e->msgtype() == Quotient::MessageEventType::Location) {
|
||||
return e->contentJson();
|
||||
}
|
||||
// Cannot use e.contentJson() here because some
|
||||
// EventContent classes inject values into the copy of the
|
||||
// content JSON stored in EventContent::Base
|
||||
@@ -621,6 +580,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return !m_currentRoom->isDirectChat() && m_currentRoom->isEventHighlighted(&evt);
|
||||
}
|
||||
|
||||
if (role == FileMimetypeIcon) {
|
||||
auto e = eventCast<const RoomMessageEvent>(&evt);
|
||||
if (!e || !e->hasFileContent()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return e->content()->fileInfo()->mimeType.iconName();
|
||||
}
|
||||
|
||||
if (role == MimeTypeRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
if (!e || !e->hasFileContent()) {
|
||||
@@ -686,6 +654,14 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return EventStatus::Normal;
|
||||
}
|
||||
|
||||
if (role == IsEditedRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
return !e->unsignedJson().isEmpty() && e->unsignedJson().contains("m.relations")
|
||||
&& e->unsignedJson()["m.relations"].toObject().contains("m.replace");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == EventIdRole) {
|
||||
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
||||
}
|
||||
@@ -701,25 +677,27 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
if (role == AnnotationRole) {
|
||||
if (isPending) {
|
||||
return pendingIt->annotation();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == TimeRole || role == SectionRole) {
|
||||
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||
}
|
||||
|
||||
if (role == ShowLinkPreviewRole) {
|
||||
return m_linkPreviewers.contains(evt.id());
|
||||
}
|
||||
|
||||
if (role == LinkPreviewRole) {
|
||||
if (m_linkPreviewers.contains(evt.id())) {
|
||||
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewers[evt.id()]);
|
||||
} else {
|
||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||
if (role == UserMarkerRole) {
|
||||
QVariantList variantList;
|
||||
const auto users = m_currentRoom->usersAtEventId(evt.id());
|
||||
for (User *user : users) {
|
||||
if (user == m_currentRoom->localUser()) {
|
||||
continue;
|
||||
}
|
||||
variantList.append(QVariant::fromValue(user));
|
||||
}
|
||||
}
|
||||
|
||||
if (role == MediaInfoRole) {
|
||||
return getMediaInfoForEvent(evt);
|
||||
return variantList;
|
||||
}
|
||||
|
||||
if (role == IsReplyRole) {
|
||||
@@ -730,27 +708,21 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
}
|
||||
|
||||
if (role == ReplyAuthor) {
|
||||
auto replyPtr = getReplyForEvent(evt);
|
||||
|
||||
if (replyPtr) {
|
||||
auto replyUser = static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId()));
|
||||
return userInContext(replyUser, m_currentRoom);
|
||||
} else {
|
||||
return emptyUser;
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ReplyMediaInfoRole) {
|
||||
auto replyPtr = getReplyForEvent(evt);
|
||||
if (!replyPtr) {
|
||||
return {};
|
||||
}
|
||||
return getMediaInfoForEvent(*replyPtr);
|
||||
}
|
||||
|
||||
if (role == ReplyRole) {
|
||||
auto replyPtr = getReplyForEvent(evt);
|
||||
const QString &replyEventId = evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
if (replyEventId.isEmpty()) {
|
||||
return {};
|
||||
};
|
||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||
const RoomEvent *replyPtr = replyIt != m_currentRoom->historyEdge() ? &**replyIt : nullptr;
|
||||
if (!replyPtr) {
|
||||
for (const auto &e : m_extraEvents) {
|
||||
if (e->id() == replyEventId) {
|
||||
replyPtr = e.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!replyPtr) {
|
||||
return {};
|
||||
}
|
||||
@@ -787,10 +759,23 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
type = DelegateType::Other;
|
||||
}
|
||||
|
||||
return QVariantMap{
|
||||
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"type", type},
|
||||
QVariant content;
|
||||
if (auto e = eventCast<const RoomMessageEvent>(replyPtr)) {
|
||||
// Cannot use e.contentJson() here because some
|
||||
// EventContent classes inject values into the copy of the
|
||||
// content JSON stored in EventContent::Base
|
||||
content = e->hasFileContent() ? QVariant::fromValue(e->content()->originalJson) : QVariant();
|
||||
};
|
||||
|
||||
if (auto e = eventCast<const StickerEvent>(replyPtr)) {
|
||||
content = QVariant::fromValue(e->image().originalJson);
|
||||
}
|
||||
|
||||
return QVariantMap{{"eventId", replyEventId},
|
||||
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"content", content},
|
||||
{"type", type},
|
||||
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId())), m_currentRoom, evt)}};
|
||||
}
|
||||
|
||||
if (role == ShowAuthorRole) {
|
||||
@@ -800,7 +785,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
||||
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == MessageEventModel::State
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) == MessageEventModel::State
|
||||
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
||||
}
|
||||
@@ -825,41 +810,10 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == LatitudeRole) {
|
||||
const auto geoUri = evt.contentJson()["geo_uri"_ls].toString();
|
||||
if (geoUri.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[0];
|
||||
return latitude.toFloat();
|
||||
}
|
||||
|
||||
if (role == LongitudeRole) {
|
||||
const auto geoUri = evt.contentJson()["geo_uri"_ls].toString();
|
||||
if (geoUri.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[1];
|
||||
return latitude.toFloat();
|
||||
}
|
||||
|
||||
if (role == AssetRole) {
|
||||
const auto assetType = evt.contentJson()["org.matrix.msc3488.asset"].toObject()["type"].toString();
|
||||
if (assetType.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
return assetType;
|
||||
}
|
||||
|
||||
if (role == ReadMarkersRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
auto userIds_temp = room()->userIdsAtEvent(evt.id());
|
||||
userIds_temp.remove(m_currentRoom->localUser()->id());
|
||||
|
||||
auto userIds = userIds_temp.values();
|
||||
if (userIds.count() > 5) {
|
||||
userIds = userIds.mid(0, 5);
|
||||
}
|
||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||
userIds.remove(m_currentRoom->localUser()->id());
|
||||
#else
|
||||
auto userIds = room()->usersAtEventId(evt.id());
|
||||
userIds.removeAll(m_currentRoom->localUser());
|
||||
@@ -873,28 +827,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
#else
|
||||
auto user = static_cast<NeoChatUser *>(userId);
|
||||
#endif
|
||||
users += userInContext(user, m_currentRoom);
|
||||
users += userAtEvent(user, m_currentRoom, evt);
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
if (role == ExcessReadMarkersRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||
userIds.remove(m_currentRoom->localUser()->id());
|
||||
#else
|
||||
auto userIds = room()->usersAtEventId(evt.id());
|
||||
userIds.removeAll(m_currentRoom->localUser());
|
||||
#endif
|
||||
|
||||
if (userIds.count() > 5) {
|
||||
return QStringLiteral("+ ") + QString::number(userIds.count() - 5);
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ReadMarkersStringRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||
@@ -955,7 +893,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
while (i != reactions.constEnd()) {
|
||||
QVariantList authors;
|
||||
for (auto author : i.value()) {
|
||||
authors.append(userInContext(author, m_currentRoom));
|
||||
authors.append(userAtEvent(author, m_currentRoom, evt));
|
||||
}
|
||||
bool hasLocalUser = i.value().contains(static_cast<NeoChatUser *>(m_currentRoom->localUser()));
|
||||
res.append(QVariantMap{{"reaction", i.key()}, {"count", i.value().count()}, {"authors", authors}, {"hasLocalUser", hasLocalUser}});
|
||||
@@ -968,6 +906,38 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return evt.senderId();
|
||||
}
|
||||
|
||||
if (role == MediaUrlRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
if (!e->hasFileContent()) {
|
||||
return QVariant();
|
||||
}
|
||||
if (e->content()->originalJson.contains(QStringLiteral("file")) && e->content()->originalJson["file"].toObject().contains(QStringLiteral("url"))) {
|
||||
return m_currentRoom->makeMediaUrl(e->id(), e->content()->originalJson["file"]["url"].toString());
|
||||
}
|
||||
if (e->content()->originalJson.contains(QStringLiteral("url"))) {
|
||||
return m_currentRoom->makeMediaUrl(e->id(), e->content()->originalJson["url"].toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Requires https://github.com/quotient-im/libQuotient/pull/570
|
||||
// if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||
// return m_currentRoom->makeMediaUrl(e->id(), e->url());
|
||||
// }
|
||||
#endif
|
||||
|
||||
// Construct link in the same form as urlToDownload as that function doesn't work for stickers
|
||||
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||
auto url = QUrl(m_currentRoom->connection()->homeserver().toString() + "/_matrix/media/r0/download/" + e->url().toString().remove("mxc://"));
|
||||
QUrlQuery q(url.query());
|
||||
q.addQueryItem("allow_remote", "true");
|
||||
url.setQuery(q);
|
||||
return url;
|
||||
}
|
||||
|
||||
return m_currentRoom->urlToDownload(evt.id());
|
||||
}
|
||||
|
||||
if (role == VerifiedRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
@@ -1004,13 +974,13 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == IsPendingRole) {
|
||||
return row < static_cast<int>(m_currentRoom->pendingEvents().size());
|
||||
return row < m_currentRoom->pendingEvents().size();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
||||
{
|
||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||
if (it == m_currentRoom->historyEdge()) {
|
||||
@@ -1052,7 +1022,7 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
||||
targetMessage.insert("event_id", eventId);
|
||||
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
||||
// Need to get the message from the original eventId or body will have * on the front
|
||||
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
||||
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
||||
|
||||
return targetMessage;
|
||||
@@ -1062,14 +1032,14 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
||||
return targetMessage;
|
||||
}
|
||||
|
||||
QVariant MessageEventModel::getLatestMessageFromRow(const int startRow)
|
||||
QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
||||
{
|
||||
QVariantMap replyResponse;
|
||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + startRow;
|
||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + baseline;
|
||||
|
||||
// set a cap limit of startRow + 35 messages, to prevent loading a lot of messages
|
||||
// set a cap limit of baseline + 35 messages, to prevent loading a lot of messages
|
||||
// in rooms where the user has not sent many messages
|
||||
const auto limit = timelineBottom + std::min(startRow + 35, m_currentRoom->timelineSize());
|
||||
const auto limit = timelineBottom + std::min(baseline + 35, m_currentRoom->timelineSize());
|
||||
|
||||
for (auto it = timelineBottom; it != limit; ++it) {
|
||||
auto evt = it->event();
|
||||
@@ -1091,7 +1061,7 @@ QVariant MessageEventModel::getLatestMessageFromRow(const int startRow)
|
||||
}
|
||||
replyResponse.insert("event_id", eventId);
|
||||
// Need to get the message from the original eventId or body will have * on the front
|
||||
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
||||
replyResponse.insert("message", idx.data(Qt::UserRole + 2));
|
||||
replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId())));
|
||||
replyResponse.insert("at", -it->index());
|
||||
@@ -1107,174 +1077,6 @@ void MessageEventModel::loadReply(const QModelIndex &index)
|
||||
QPersistentModelIndex persistentIndex(index);
|
||||
connect(job, &BaseJob::success, this, [this, job, persistentIndex] {
|
||||
m_extraEvents.push_back(fromJson<event_ptr_tt<RoomEvent>>(job->jsonData()));
|
||||
Q_EMIT dataChanged(persistentIndex, persistentIndex, {ReplyRole, ReplyMediaInfoRole, ReplyAuthor});
|
||||
Q_EMIT dataChanged(persistentIndex, persistentIndex, {ReplyRole});
|
||||
});
|
||||
}
|
||||
|
||||
const RoomEvent *MessageEventModel::getReplyForEvent(const RoomEvent &event) const
|
||||
{
|
||||
const QString &replyEventId = event.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
if (replyEventId.isEmpty()) {
|
||||
return {};
|
||||
};
|
||||
|
||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||
const RoomEvent *replyPtr = replyIt != m_currentRoom->historyEdge() ? &**replyIt : nullptr;
|
||||
if (!replyPtr) {
|
||||
for (const auto &e : m_extraEvents) {
|
||||
if (e->id() == replyEventId) {
|
||||
replyPtr = e.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return replyPtr;
|
||||
}
|
||||
|
||||
QVariantMap MessageEventModel::getMediaInfoForEvent(const RoomEvent &event) const
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
QString eventId = event.id();
|
||||
|
||||
// Get the file info for the event.
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
#ifdef QUOTIENT_07
|
||||
if (event.is<RoomMessageEvent>()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event);
|
||||
#else
|
||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event)) {
|
||||
#endif
|
||||
if (!roomMessageEvent->hasFileContent()) {
|
||||
return {};
|
||||
}
|
||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||
#ifdef QUOTIENT_07
|
||||
} else if (event.is<StickerEvent>()) {
|
||||
auto stickerEvent = eventCast<const StickerEvent>(&event);
|
||||
#else
|
||||
} else if (auto stickerEvent = eventCast<const StickerEvent>(&event)) {
|
||||
#endif
|
||||
fileInfo = &stickerEvent->image();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getMediaInfoFromFileInfo(fileInfo, eventId);
|
||||
}
|
||||
|
||||
QVariantMap MessageEventModel::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail) const
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
#ifdef QUOTIENT_07
|
||||
if (!fileInfo->url().isValid() || eventId.isEmpty()) {
|
||||
mediaInfo["source"] = QUrl();
|
||||
} else {
|
||||
QUrl source = m_currentRoom->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid() && source.scheme() == QStringLiteral("mxc")) {
|
||||
mediaInfo["source"] = source;
|
||||
} else {
|
||||
mediaInfo["source"] = QUrl();
|
||||
}
|
||||
}
|
||||
#else
|
||||
auto url = QUrl(m_currentRoom->connection()->homeserver().toString() + "/_matrix/media/r0/download/" + fileInfo->url.toString().remove("mxc://"));
|
||||
QUrlQuery q(url.query());
|
||||
q.addQueryItem("allow_remote", "true");
|
||||
url.setQuery(q);
|
||||
mediaInfo["source"] = url;
|
||||
#endif
|
||||
|
||||
auto mimeType = fileInfo->mimeType;
|
||||
// Add the MIME type for the media if available.
|
||||
mediaInfo["mimeType"] = mimeType.name();
|
||||
|
||||
// Add the MIME type icon if available.
|
||||
mediaInfo["mimeIcon"] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
mediaInfo["size"] = fileInfo->payloadSize;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileInfo)) {
|
||||
mediaInfo["width"] = castInfo->imageSize.width();
|
||||
mediaInfo["height"] = castInfo->imageSize.height();
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"].toUrl().scheme() == "mxc") {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"] = QUrl("image://blurhash/" + blurhash);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"] = tempInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("video"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileInfo)) {
|
||||
mediaInfo["width"] = castInfo->imageSize.width();
|
||||
mediaInfo["height"] = castInfo->imageSize.height();
|
||||
mediaInfo["duration"] = castInfo->duration;
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"].toUrl().scheme() == "mxc") {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"] = QUrl("image://blurhash/" + blurhash);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"] = tempInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("audio"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileInfo)) {
|
||||
mediaInfo["duration"] = castInfo->duration;
|
||||
}
|
||||
}
|
||||
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
void MessageEventModel::createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event)
|
||||
{
|
||||
if (m_linkPreviewers.contains(event->id())) {
|
||||
return;
|
||||
} else {
|
||||
QString text;
|
||||
if (event->hasTextContent()) {
|
||||
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
|
||||
if (textContent) {
|
||||
text = textContent->body;
|
||||
} else {
|
||||
text = event->plainBody();
|
||||
}
|
||||
} else {
|
||||
text = event->plainBody();
|
||||
}
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(text);
|
||||
|
||||
QList<QUrl> links = textHandler.getLinkPreviews();
|
||||
if (links.size() > 0) {
|
||||
m_linkPreviewers[event->id()] = new LinkPreviewer(nullptr, m_currentRoom, links.size() > 0 ? links[0] : QUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,106 +5,76 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
* @class MessageEventModel
|
||||
*
|
||||
* This class defines the model for visualising the room timeline.
|
||||
*
|
||||
* This model covers all event types in the timeline with many of the roles being
|
||||
* specific to a subset of events. This means the user needs to understand which
|
||||
* roles will return useful information for a given event type.
|
||||
*
|
||||
* @sa NeoChatRoom
|
||||
*/
|
||||
class MessageEventModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is getting its messages from.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief The type of delegate that is needed for the event.
|
||||
*
|
||||
* @note While similar this is not the matrix event or message type. This is
|
||||
* to tell a QML ListView what delegate to show for each event. So while
|
||||
* similar to the spec it is not the same.
|
||||
*/
|
||||
enum DelegateType {
|
||||
Emote, /**< A message that begins with /me. */
|
||||
Notice, /**< A notice event. */
|
||||
Image, /**< A message that is an image. */
|
||||
Audio, /**< A message that is an audio recording. */
|
||||
Video, /**< A message that is a video. */
|
||||
File, /**< A message that is a file. */
|
||||
Message, /**< A text message. */
|
||||
Sticker, /**< A message that is a sticker. */
|
||||
State, /**< A state event in the room. */
|
||||
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||
ReadMarker, /**< The local user read marker. */
|
||||
Poll, /**< The initial event for a poll. */
|
||||
Location, /**< A location event. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
Emote,
|
||||
Notice,
|
||||
Image,
|
||||
Audio,
|
||||
Video,
|
||||
File,
|
||||
Message,
|
||||
Sticker,
|
||||
State,
|
||||
Encrypted,
|
||||
ReadMarker,
|
||||
Poll,
|
||||
Other,
|
||||
};
|
||||
Q_ENUM(DelegateType);
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||
PlainText, /**< Plain text representation of the message. */
|
||||
EventIdRole, /**< The matrix event ID of the event. */
|
||||
TimeRole, /**< The timestamp for when the event was sent. */
|
||||
SectionRole, /**< The date of the event as a string. */
|
||||
AuthorRole, /**< The author of the event. */
|
||||
ContentRole, /**< The full message content. */
|
||||
HighlightRole, /**< Whether the event should be highlighted. */
|
||||
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||
LongOperationRole, /**< Progress info when downloading files. */
|
||||
FormattedBodyRole, /**< The formatted body of a rich message. */
|
||||
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||
EventTypeRole = Qt::UserRole + 1,
|
||||
MessageRole,
|
||||
EventIdRole,
|
||||
TimeRole,
|
||||
SectionRole,
|
||||
AuthorRole,
|
||||
ContentRole,
|
||||
ContentTypeRole,
|
||||
HighlightRole,
|
||||
SpecialMarksRole,
|
||||
LongOperationRole,
|
||||
AnnotationRole,
|
||||
UserMarkerRole,
|
||||
FormattedBodyRole,
|
||||
GenericDisplayRole,
|
||||
|
||||
ShowLinkPreviewRole, /**< Whether a link preview should be shown. */
|
||||
LinkPreviewRole, /**< The link preview details. */
|
||||
MimeTypeRole,
|
||||
FileMimetypeIcon,
|
||||
|
||||
MediaInfoRole, /**< The media info for the event. */
|
||||
MimeTypeRole, /**< The mime type of the message's file or media. */
|
||||
IsReplyRole,
|
||||
ReplyRole,
|
||||
ReplyIdRole,
|
||||
|
||||
IsReplyRole, /**< Is the message a reply to another event. */
|
||||
ReplyAuthor, /**< The author of the event that was replied to. */
|
||||
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
ReplyMediaInfoRole, /**< The media info of the message that was replied to. */
|
||||
ReplyRole, /**< The content data of the message that was replied to. */
|
||||
ShowAuthorRole,
|
||||
ShowSectionRole,
|
||||
|
||||
ShowAuthorRole, /**< Whether the author's name should be shown. */
|
||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||
|
||||
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
|
||||
ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */
|
||||
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||
ReactionRole, /**< List of reactions to this event. */
|
||||
SourceRole, /**< The full message source JSON. */
|
||||
ReadMarkersRole, /**< QVariantList of users at the event for read marker tracking. */
|
||||
ReadMarkersStringRole, /**< QString with the display name and mxID of the users at the event. */
|
||||
ShowReadMarkersRole, /**< bool with whether there are any other user read markers to be shown. */
|
||||
ReactionRole,
|
||||
|
||||
IsEditedRole,
|
||||
SourceRole,
|
||||
MediaUrlRole,
|
||||
// For debugging
|
||||
EventResolvedTypeRole, /**< The event type the message. */
|
||||
AuthorIdRole, /**< Matrix ID of the message author. */
|
||||
|
||||
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||
DisplayNameForInitialsRole, /**< Sender's displayname, always without the matrix id. */
|
||||
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||
LatitudeRole, /**< Latitude for a location event. */
|
||||
LongitudeRole, /**< Longitude for a location event. */
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
EventResolvedTypeRole,
|
||||
AuthorIdRole,
|
||||
VerifiedRole,
|
||||
// Sender's displayname, always without the matrix id
|
||||
DisplayNameForInitialsRole,
|
||||
// The displayname for the event's sender; for name change events, the old displayname
|
||||
AuthorDisplayNameRole,
|
||||
IsRedactedRole,
|
||||
IsPendingRole,
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
@@ -112,67 +82,20 @@ public:
|
||||
explicit MessageEventModel(QObject *parent = nullptr);
|
||||
~MessageEventModel() override;
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
[[nodiscard]] NeoChatRoom *room() const
|
||||
{
|
||||
return m_currentRoom;
|
||||
}
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the row number of the given event ID in the model.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||
|
||||
/**
|
||||
* @brief Get the last message sent by the local user.
|
||||
*
|
||||
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||
*
|
||||
* @return a QVariantMap for the event with the following parameters:
|
||||
* - eventId - The event ID.
|
||||
* - formattedBody - The message text formatted as Qt::RichText.
|
||||
* - message - The message text formatted as Qt::PlainText.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int eventIDToIndex(const QString &eventID) const;
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
||||
|
||||
/**
|
||||
* @brief Get the last message sent earlier than the given row.
|
||||
*
|
||||
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||
*
|
||||
* @return a QVariantMap for the event with the following parameters:
|
||||
* - eventId - The event ID.
|
||||
* - message - The message text formatted as Qt::PlainText.
|
||||
* - sender_id - The matrix ID of the sender.
|
||||
* - at - The QModelIndex of the message.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromRow(const int startRow);
|
||||
|
||||
/**
|
||||
* @brief Load the event that the item at the given index replied to.
|
||||
*
|
||||
* This is used to ensure that the reply data is available when the message that
|
||||
* was replied to is outside the currently loaded timeline.
|
||||
*/
|
||||
Q_INVOKABLE void loadReply(const QModelIndex &index);
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromIndex(const int baseline);
|
||||
Q_INVOKABLE void loadReply(const QModelIndex &row);
|
||||
|
||||
private Q_SLOTS:
|
||||
int refreshEvent(const QString &eventId);
|
||||
@@ -185,8 +108,6 @@ private:
|
||||
int rowBelowInserted = -1;
|
||||
bool movingEvent = false;
|
||||
|
||||
QMap<QString, LinkPreviewer *> m_linkPreviewers;
|
||||
|
||||
[[nodiscard]] int timelineBaseIndex() const;
|
||||
[[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
|
||||
[[nodiscard]] static QString renderDate(const QDateTime ×tamp);
|
||||
@@ -199,14 +120,7 @@ private:
|
||||
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
||||
void moveReadMarker(const QString &toEventId);
|
||||
|
||||
const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const;
|
||||
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent &event) const;
|
||||
QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const;
|
||||
void createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event);
|
||||
|
||||
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
|
||||
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
||||
bool m_initialized = false;
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
|
||||
@@ -41,7 +41,7 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt();
|
||||
const auto eventType = index.data(MessageEventModel::EventTypeRole).toInt();
|
||||
|
||||
if (eventType == MessageEventModel::Other) {
|
||||
return false;
|
||||
|
||||
@@ -5,21 +5,10 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class MessageFilterModel
|
||||
*
|
||||
* This model filters out any messages that should be hidden.
|
||||
*
|
||||
* Deleted messages are only hidden if the user hasn't set them to be shown.
|
||||
*/
|
||||
class MessageFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageFilterModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Custom filter function to remove hidden messages.
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
@@ -12,11 +12,6 @@ PublicRoomListModel::PublicRoomListModel(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
Quotient::Connection *PublicRoomListModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void PublicRoomListModel::setConnection(Connection *conn)
|
||||
{
|
||||
if (m_connection == conn) {
|
||||
@@ -52,11 +47,6 @@ void PublicRoomListModel::setConnection(Connection *conn)
|
||||
Q_EMIT hasMoreChanged();
|
||||
}
|
||||
|
||||
QString PublicRoomListModel::server() const
|
||||
{
|
||||
return m_server;
|
||||
}
|
||||
|
||||
void PublicRoomListModel::setServer(const QString &value)
|
||||
{
|
||||
if (m_server == value) {
|
||||
@@ -86,11 +76,6 @@ void PublicRoomListModel::setServer(const QString &value)
|
||||
Q_EMIT hasMoreChanged();
|
||||
}
|
||||
|
||||
QString PublicRoomListModel::keyword() const
|
||||
{
|
||||
return m_keyword;
|
||||
}
|
||||
|
||||
void PublicRoomListModel::setKeyword(const QString &value)
|
||||
{
|
||||
if (m_keyword == value) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user