Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be116e1ba7 | ||
|
|
3396f831d4 | ||
|
|
a0f6170539 | ||
|
|
731c6f924c | ||
|
|
3011c3d885 | ||
|
|
709b2c8fd9 | ||
|
|
0cfa87e23d | ||
|
|
538ed7dd02 | ||
|
|
180a754e67 | ||
|
|
81ba5f6ee5 | ||
|
|
a15b406cff | ||
|
|
d0bc8f3d05 | ||
|
|
c83f4b4f75 | ||
|
|
0f5425e030 | ||
|
|
f381cc4623 | ||
|
|
decd528079 | ||
|
|
0c5bd57976 | ||
|
|
7362b90c42 | ||
|
|
aef6d6fc85 | ||
|
|
432e209b16 | ||
|
|
a72cac5ea3 | ||
|
|
b9152dc93c | ||
|
|
e5791970da | ||
|
|
c157625645 | ||
|
|
026c7660bc | ||
|
|
be10e66974 | ||
|
|
024fb1a97a | ||
|
|
e4c8b6b676 | ||
|
|
863508b629 | ||
|
|
ef5550bafd | ||
|
|
1cc8d915bc | ||
|
|
9a5f2e4938 | ||
|
|
a747d44cac | ||
|
|
334c13b36c | ||
|
|
aac96da2e2 | ||
|
|
12f3f72a67 | ||
|
|
62f6cfbf9a | ||
|
|
c59e3db1dd | ||
|
|
9252e0e65e | ||
|
|
80ee5e9356 | ||
|
|
be802a28c2 | ||
|
|
b2a8430fa2 | ||
|
|
db5f328539 | ||
|
|
9ac1fbd99b | ||
|
|
022951a9df | ||
|
|
47a0d30e57 | ||
|
|
faeb1964bd | ||
|
|
db8b2fd64b | ||
|
|
37c7fe380b | ||
|
|
537a1e44b1 | ||
|
|
dc9d574b58 | ||
|
|
7c807e6a25 | ||
|
|
f74c6a41ae | ||
|
|
7d5a8c87a1 | ||
|
|
ca719b835e | ||
|
|
9b5ad3a3a0 | ||
|
|
fdfbbb1b04 | ||
|
|
dd91cb91d0 | ||
|
|
290b2249c4 | ||
|
|
8b8e521c56 | ||
|
|
cba88e1af7 | ||
|
|
1661d34d7c | ||
|
|
dc3b1a3c87 | ||
|
|
f55dc19d95 | ||
|
|
6014c15b4f | ||
|
|
a5f835b1eb |
@@ -5,3 +5,4 @@ include:
|
||||
- https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-reuse.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/linux.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
|
||||
@@ -23,3 +23,6 @@ Dependencies:
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@stable'
|
||||
|
||||
Options:
|
||||
require-passing-tests-on: [ 'Linux', 'Windows' ]
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(NeoChat)
|
||||
set(PROJECT_VERSION "22.02")
|
||||
set(PROJECT_VERSION "22.04")
|
||||
|
||||
set(KF5_MIN_VERSION "5.88.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
@@ -24,7 +24,7 @@ set(KDE_COMPILERSETTINGS_LEVEL 5.84)
|
||||
include(FeatureSummary)
|
||||
include(ECMSetupVersion)
|
||||
include(KDEInstallDirs)
|
||||
include(ECMQMLModules)
|
||||
include(ECMFindQmlModule)
|
||||
include(KDEClangFormat)
|
||||
include(KDECMakeSettings)
|
||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
@@ -108,7 +108,7 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
PURPOSE "Add image editing capability to image attachments"
|
||||
)
|
||||
|
||||
find_package(QCoro5 COMPONENTS Coro QUIET)
|
||||
find_package(QCoro5 COMPONENTS Core QUIET)
|
||||
if(NOT QCoro5_FOUND)
|
||||
find_package(QCoro REQUIRED)
|
||||
endif()
|
||||
@@ -123,6 +123,8 @@ if(ANDROID)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
||||
endif()
|
||||
|
||||
ki18n_install(po)
|
||||
|
||||
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
|
||||
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
|
||||
install(FILES org.kde.neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
||||
@@ -138,5 +140,9 @@ file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
|
||||
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
|
||||
|
||||
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
||||
|
||||
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
|
||||
# CI installs dependency headers to _install and _build, which break the reuse check
|
||||
# Fixes the test by excluding this directory
|
||||
list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX [[_(install|build)/.*]])
|
||||
ecm_check_outbound_license(LICENSES GPL-3.0-only FILES ${ALL_SOURCE_FILES})
|
||||
|
||||
@@ -14,7 +14,7 @@ KConfig and KI18n.
|
||||
|
||||
## Get it
|
||||
|
||||
A stable release [is available](https://apps.kde.org/en/neochat) for download for Linux distributions.
|
||||
A stable release [is available](https://apps.kde.org/neochat) for download for Linux distributions.
|
||||
|
||||
|
||||
Along with the stable release, a Flatpak version is available for the nightly
|
||||
|
||||
@@ -120,36 +120,21 @@ ToolBar {
|
||||
room: currentRoom ?? null
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeoutTimer
|
||||
repeat: false
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
repeatTimer.stop()
|
||||
currentRoom.sendTypingNotification(false)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
repeat: true
|
||||
interval: 5000
|
||||
triggeredOnStart: true
|
||||
onTriggered: currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
|
||||
function sendMessage(event) {
|
||||
if (isCompleting) {
|
||||
if (isCompleting && completionMenu.count > 0) {
|
||||
chatBar.complete();
|
||||
|
||||
isCompleting = false;
|
||||
return;
|
||||
}
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||
inputField.insert(cursorPosition, "\n")
|
||||
} else {
|
||||
currentRoom.sendTypingNotification(false)
|
||||
chatBar.postMessage()
|
||||
}
|
||||
isCompleting = false;
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: { sendMessage(event) }
|
||||
@@ -244,8 +229,11 @@ ToolBar {
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
timeoutTimer.restart()
|
||||
if (!repeatTimer.running) {
|
||||
currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
repeatTimer.start()
|
||||
|
||||
currentRoom.cachedInput = text
|
||||
autoAppeared = false;
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
property alias source: image.source
|
||||
property string filename
|
||||
property url localPath
|
||||
property string blurhash: ""
|
||||
property int imageWidth: -1
|
||||
property int imageHeight: -1
|
||||
@@ -45,8 +45,6 @@ ApplicationWindow {
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
source: localPath
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: image.width
|
||||
|
||||
@@ -23,5 +23,6 @@ Kirigami.PlaceholderMessage {
|
||||
|
||||
QQC2.BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
running: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ LoginStep {
|
||||
}
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^\@?[a-zA-Z0-9\._=\-/]+\:[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*\.[a-zA-Z]+(:[0-9]+)?$/
|
||||
regularExpression: /^\@?[a-zA-Z0-9\._=\-/]+\:[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(\:[0-9]+)?$/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ TimelineContainer {
|
||||
innerObject: Image {
|
||||
id: img
|
||||
|
||||
|
||||
Layout.maximumWidth: imageDelegate.bubbleMaxWidth
|
||||
source: "image://mxc/" + mediaId
|
||||
Layout.maximumHeight: imageDelegate.bubbleMaxWidth / imageDelegate.info.w * imageDelegate.info.h
|
||||
Layout.preferredWidth: imageDelegate.info.w
|
||||
Layout.preferredHeight: imageDelegate.info.h
|
||||
source: model.mediaUrl
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
@@ -95,7 +97,7 @@ TimelineContainer {
|
||||
onTapped: {
|
||||
fullScreenImage.createObject(parent, {
|
||||
filename: eventId,
|
||||
localPath: currentRoom.urlToDownload(eventId),
|
||||
source: model.mediaUrl,
|
||||
blurhash: model.content.info["xyz.amorgan.blurhash"],
|
||||
imageWidth: content.info.w,
|
||||
imageHeight: content.info.h
|
||||
|
||||
@@ -10,41 +10,57 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
RowLayout {
|
||||
Control {
|
||||
x: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
|
||||
height: label.contentHeight
|
||||
width: ListView.view.width - Kirigami.Units.largeSpacing - x
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: icon
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
name: author.displayName
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||
}
|
||||
height: sectionDelegate.height + rowLayout.height
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
visible: model.showSection
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
|
||||
Label {
|
||||
id: label
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: icon.height
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
text: "<style>a {text-decoration: none;}</style><a href=\"https://matrix.to/#/" + author.id + "\" style='color: " + author.color + "'>" + currentRoom.htmlSafeMemberName(author.id) + "</a> " + display
|
||||
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
height: label.contentHeight
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: icon
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
name: author.displayName
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: label
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: icon.height
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
text: `<style>a {text-decoration: none;}</style><a href="https://matrix.to/#/${author.id}" style="color: ${author.color}">${currentRoom.htmlSafeMemberName(author.id)}</a> ${aggregateDisplay}`
|
||||
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,20 @@ QQC2.ItemDelegate {
|
||||
|
||||
readonly property int bubbleMaxWidth: Config.compactLayout && !Config.showAvatarInTimeline ? width : (Config.compactLayout ? width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, Kirigami.Units.gridUnit * 20))
|
||||
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !applicationWindow().wideScreen
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight &&
|
||||
model.author.isLocalUser &&
|
||||
!applicationWindow().wideScreen &&
|
||||
!Config.compactLayout
|
||||
|
||||
signal openExternally()
|
||||
signal replyClicked(string eventID)
|
||||
|
||||
Component.onCompleted: {
|
||||
if (model.isReply && model.reply === undefined) {
|
||||
messageEventModel.loadReply(sortedMessageEventModel.mapToSource(sortedMessageEventModel.index(model.index, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
background: null
|
||||
@@ -72,7 +81,9 @@ QQC2.ItemDelegate {
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
visible: model.showAuthor && Config.showAvatarInTimeline && !showUserMessageOnRight
|
||||
visible: model.showAuthor &&
|
||||
Config.showAvatarInTimeline &&
|
||||
(Config.compactLayout || !showUserMessageOnRight)
|
||||
name: model.author.name ?? model.author.displayName
|
||||
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||
color: model.author.color
|
||||
|
||||
@@ -55,14 +55,6 @@ TimelineContainer {
|
||||
|
||||
fillMode: VideoOutput.PreserveAspectFit
|
||||
|
||||
Component.onCompleted: {
|
||||
if (downloaded) {
|
||||
source = progressInfo.localPath
|
||||
} else {
|
||||
source = currentRoom.urlToMxcUrl(content.url)
|
||||
}
|
||||
}
|
||||
|
||||
onDurationChanged: {
|
||||
if (!duration) {
|
||||
vid.supportStreaming = false;
|
||||
|
||||
@@ -52,7 +52,7 @@ Kirigami.OverlaySheet {
|
||||
|
||||
onClicked: {
|
||||
if (avatarMediaId) {
|
||||
fullScreenImage.createObject(parent, {"filename": displayName, "localPath": room.urlToMxcUrl(avatarUrl)}).showFullScreen()
|
||||
fullScreenImage.createObject(parent, {"filename": displayName, "source": room.urlToMxcUrl(avatarUrl)}).showFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ Kirigami.Page {
|
||||
anchors.centerIn: parent
|
||||
text: i18n("Loading…")
|
||||
QQC2.BusyIndicator {
|
||||
running: loadingIndicator.visible
|
||||
running: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,13 +236,9 @@ Kirigami.ScrollablePage {
|
||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||
bold: unreadCount > 0
|
||||
label: name ?? ""
|
||||
subtitle: {
|
||||
const txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ")
|
||||
if (txt.length) {
|
||||
return txt
|
||||
}
|
||||
return " "
|
||||
}
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitle: subtitleText
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
onPressAndHold: {
|
||||
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
|
||||
configButton.visible = true
|
||||
|
||||
@@ -41,6 +41,8 @@ Kirigami.ScrollablePage {
|
||||
if(pageStack.lastItem == page) {
|
||||
pageStack.pop()
|
||||
}
|
||||
} else if (page.currentRoom.isInvite) {
|
||||
page.currentRoom.clearInvitationNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,6 +241,11 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
CollapseStateProxyModel {
|
||||
id: collapseStateProxyModel
|
||||
sourceModel: sortedMessageEventModel
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: messageListView
|
||||
visible: !invitation.visible
|
||||
@@ -251,7 +258,7 @@ Kirigami.ScrollablePage {
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
highlightMoveDuration: 500
|
||||
|
||||
model: !isLoaded ? undefined : sortedMessageEventModel
|
||||
model: !isLoaded ? undefined : collapseStateProxyModel
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
@@ -400,12 +407,6 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (currentRoom) {
|
||||
if (currentRoom.timelineSize < 20) {
|
||||
currentRoom.getPreviousContent(50);
|
||||
}
|
||||
}
|
||||
|
||||
positionViewAtBeginning();
|
||||
}
|
||||
|
||||
@@ -606,7 +607,7 @@ Kirigami.ScrollablePage {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||
selectedText: selectedText,
|
||||
author: event.author,
|
||||
message: event.message,
|
||||
message: event.display,
|
||||
eventId: event.eventId,
|
||||
formattedBody: event.formattedBody,
|
||||
source: event.source,
|
||||
|
||||
@@ -37,6 +37,13 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
function onInitiated() {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
|
||||
@@ -139,7 +139,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: avatar.source.length !== 0
|
||||
visible: avatar.source.toString().length !== 0
|
||||
icon.name: "edit-clear"
|
||||
|
||||
onClicked: avatar.source = ""
|
||||
|
||||
@@ -175,6 +175,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
Kirigami.FormLayout {
|
||||
Layout.maximumWidth: parent.width
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: "Show Avatar:"
|
||||
text: i18n("In Chat")
|
||||
|
||||
@@ -94,7 +94,15 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
id: quickEditCheckbox
|
||||
Layout.maximumWidth: parent.width
|
||||
contentItem: QQC2.Label {
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: quickEditCheckbox.indicator.width + quickEditCheckbox.spacing
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
}
|
||||
checked: Config.allowQuickEdit
|
||||
enabled: !Config.isAllowQuickEditImmutable
|
||||
onToggled: {
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix. يتيح لك إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك باستخدام بروتوكول ماتركس</p>
|
||||
<p xml:lang="az">NeoChat Mtrix müştərisidir. O, Matrix protokolundan istifadə edərək, ailənizə, dostlarınıza, iş yoldaşlarınıza mətn, səsli və görüntülü ismarıclar göndərməyə imkan verir.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p>
|
||||
@@ -82,6 +82,7 @@
|
||||
<p xml:lang="fr">NeoChat est un client Matrix. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis en utilisant le protocole Matrix.</p>
|
||||
<p xml:lang="hu">A NeoChat egy Matrix kliens. Szöveges üzeneteket, videókat ésaudio fájlokat küldhet családjának, kollégáinak és barátainak a Matrix protokoll használatával.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente de Matrix. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante le protocollo de Matrix.</p>
|
||||
<p xml:lang="id">NeoChat adalah sebuah klien Matrix. Memungkinkan Anda untuk mengirim pesan teks, file video dan audio ke keluarga, kolega dan teman Anda menggunakan protokol Matrix.</p>
|
||||
<p xml:lang="it">NeoChat è un client Matrix. Ti consente di inviare messaggi di testo, file video e audio a familiari, colleghi e amici utilizzando il protocollo Matrix.</p>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 클라이언트입니다. Matrix 프로토콜을 사용하여 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다.</p>
|
||||
<p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
|
||||
@@ -98,7 +99,7 @@
|
||||
<p xml:lang="ar">ماتريكس هو بروتوكول اتصال لامركزي ، يعيد المستخدم إلى السيطرة. يطبق نيوتشات حاليًا جزءًا كبيرًا من الميفاق باستثناء الدردشات المشفرة ودردشة الفيديو.</p>
|
||||
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
|
||||
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
|
||||
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
||||
@@ -107,6 +108,7 @@
|
||||
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions chiffrées et du chat vidéo.</p>
|
||||
<p xml:lang="hu">A Matrix egy decentralizált kommunikációs protokoll, amely a felhasználók kezébe adja az irányítást.</p>
|
||||
<p xml:lang="ia">Matrix es un protocollo de communication decentrate, ponente le usator in le controlo. Currentemente NeoChat implementa un grande parte del protocollo con le exception de conversationes cryptate e conversationes video.</p>
|
||||
<p xml:lang="id">Matrix adalah protokol komunikasi terdesentralisasi, menempatkan pengguna kembali dalam kendali. Saat ini NeoChat mengimplementasikan sebagian besar protokol dengan pengecualian obrolan terenkripsi dan obrolan video.</p>
|
||||
<p xml:lang="it">Matrix è un protocollo di comunicazione decentralizzato, che restituisce all'utente il controllo. Attualmente NeoChat implementa gran parte del protocollo ad eccezione delle chat cifrate e delle chat video.</p>
|
||||
<p xml:lang="ko">Matrix는 사용자에게 제어권을 돌려 주는 분산 통신 프로토콜입니다. NeoChat은 암호화된 대화 및 영상 통화를 제외한 프로토콜의 대부분 기능을 구현합니다.</p>
|
||||
<p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
|
||||
@@ -123,7 +125,7 @@
|
||||
<p xml:lang="ar">يعمل نيوتشات على كل من الأجهزة المحمولة وسطح المكتب مع توفير تجربة مستخدم متسقة.</p>
|
||||
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
||||
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
|
||||
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
||||
@@ -132,6 +134,7 @@
|
||||
<p xml:lang="fr">NeoChat fonctionne aussi bien sur les mobiles que sur les ordinateurs de bureau, tout en offrant une expérience utilisateur cohérente.</p>
|
||||
<p xml:lang="hu">A NeoChat mobilon és asztali számítógépen is működik, egységes felhasználói élményt nyújtva.</p>
|
||||
<p xml:lang="ia">NeoChat functiona sia sur mobile que ur scriptorio durante que forni un experientia de usator consistente.</p>
|
||||
<p xml:lang="id">NeoChat berfungsi baik di ponsel dan desktop sambil memberikan pengalaman pengguna yang konsisten.</p>
|
||||
<p xml:lang="it">NeoChat funziona sia su dispositivi mobili che desktop, fornendo un'esperienza utente coerente.</p>
|
||||
<p xml:lang="ko">NeoChat은 모바일과 데스크톱 모두에서 일관된 사용자 경험을 제공합니다.</p>
|
||||
<p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
|
||||
@@ -194,15 +197,21 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="22.04" date="2022-04-26">
|
||||
<url>https://www.plasma-mobile.org/2022/04/26/plasma-mobile-gear-22-04/</url>
|
||||
<description>
|
||||
<p>NeoChat now lets you filter and enter a room directly from KRunner (Plasma Search). Aside from that there is also various bug fixes regarding the typing notifications.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="22.02" date="2022-02-09">
|
||||
<description>
|
||||
<p>NeoChat 22.02 focus on stability and adds a few quality of life improvements</p>
|
||||
<ul>
|
||||
<li>Add support for minimizing to system tray on startup</li>
|
||||
<li>Improved internet connectivity check</li>
|
||||
<li>Add support for sharing images and files with other apps (Nextcloud, Imgur, ...)</li>
|
||||
<li>Implement adding labels for account. This allow for an easier organization when using multiple accounts.</li>
|
||||
<li>Redesign of our config dialogs to follow the new Plasma System Settings style</li>
|
||||
<ul>
|
||||
<li>Add support for minimizing to system tray on startup</li>
|
||||
<li>Improved internet connectivity check</li>
|
||||
<li>Add support for sharing images and files with other apps (Nextcloud, Imgur, ...)</li>
|
||||
<li>Implement adding labels for account. This allow for an easier organization when using multiple accounts.</li>
|
||||
<li>Redesign of our config dialogs to follow the new Plasma System Settings style</li>
|
||||
<li>Fix various others issues and small feature requests. Decreasing the total amount of open issues by 20%.</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
# SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
[Desktop Entry]
|
||||
Version=1.5
|
||||
Name=NeoChat
|
||||
Name[ar]=نيوتشات
|
||||
Name[az]=NeoChat
|
||||
@@ -15,6 +16,7 @@ Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
Name[it]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[lt]=NeoChat
|
||||
@@ -46,6 +48,7 @@ GenericName[fi]=Matrix-asiakas
|
||||
GenericName[fr]=Client « Matrix »
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[ia]=Cliente de Matrice
|
||||
GenericName[id]=Klien Matrix
|
||||
GenericName[it]=Client Matrix
|
||||
GenericName[ko]=Matrix 클라이언트
|
||||
GenericName[lt]=Matrix kliento programą
|
||||
@@ -76,6 +79,7 @@ Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
|
||||
2020
po/ar/neochat.po
Normal file
2020
po/ar/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2151
po/az/neochat.po
Normal file
2151
po/az/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2201
po/ca/neochat.po
Normal file
2201
po/ca/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2015
po/ca@valencia/neochat.po
Normal file
2015
po/ca@valencia/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1998
po/cs/neochat.po
Normal file
1998
po/cs/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2101
po/da/neochat.po
Normal file
2101
po/da/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2165
po/de/neochat.po
Normal file
2165
po/de/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2166
po/en_GB/neochat.po
Normal file
2166
po/en_GB/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2197
po/es/neochat.po
Normal file
2197
po/es/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2187
po/eu/neochat.po
Normal file
2187
po/eu/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2156
po/fi/neochat.po
Normal file
2156
po/fi/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2185
po/fr/neochat.po
Normal file
2185
po/fr/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2193
po/hu/neochat.po
Normal file
2193
po/hu/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2092
po/ia/neochat.po
Normal file
2092
po/ia/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2005
po/id/neochat.po
Normal file
2005
po/id/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2183
po/it/neochat.po
Normal file
2183
po/it/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1990
po/ja/neochat.po
Normal file
1990
po/ja/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2080
po/ko/neochat.po
Normal file
2080
po/ko/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2195
po/nl/neochat.po
Normal file
2195
po/nl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2001
po/nn/neochat.po
Normal file
2001
po/nn/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2149
po/pa/neochat.po
Normal file
2149
po/pa/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2168
po/pl/neochat.po
Normal file
2168
po/pl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2005
po/pt/neochat.po
Normal file
2005
po/pt/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2167
po/pt_BR/neochat.po
Normal file
2167
po/pt_BR/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2179
po/sk/neochat.po
Normal file
2179
po/sk/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2014
po/sl/neochat.po
Normal file
2014
po/sl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2194
po/sv/neochat.po
Normal file
2194
po/sv/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2100
po/ta/neochat.po
Normal file
2100
po/ta/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2007
po/tok/neochat.po
Normal file
2007
po/tok/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2049
po/tr/neochat.po
Normal file
2049
po/tr/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2216
po/uk/neochat.po
Normal file
2216
po/uk/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1998
po/x-test/neochat.po
Normal file
1998
po/x-test/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1994
po/zh_CN/neochat.po
Normal file
1994
po/zh_CN/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ add_executable(neochat
|
||||
blurhash.cpp
|
||||
blurhashimageprovider.cpp
|
||||
joinrulesevent.cpp
|
||||
collapsestateproxymodel.cpp
|
||||
../res.qrc
|
||||
)
|
||||
|
||||
@@ -62,7 +63,8 @@ if(NOT ANDROID)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
target_sources(neochat PRIVATE ../res_desktop.qrc)
|
||||
target_sources(neochat PRIVATE ../res_desktop.qrc runner.cpp)
|
||||
target_compile_definitions(neochat PRIVATE -DHAVE_RUNNER)
|
||||
else()
|
||||
target_sources(neochat PRIVATE ../res_android.qrc)
|
||||
endif()
|
||||
@@ -125,7 +127,7 @@ if(ANDROID)
|
||||
)
|
||||
else()
|
||||
target_link_libraries(neochat PRIVATE Qt5::Widgets KF5::KIOWidgets)
|
||||
install(FILES neochat.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
|
||||
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
|
||||
endif()
|
||||
|
||||
if(TARGET KF5::DBusAddons)
|
||||
@@ -138,3 +140,8 @@ if (TARGET KF5::KIOWidgets)
|
||||
endif()
|
||||
|
||||
install(TARGETS neochat ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
||||
endif()
|
||||
|
||||
|
||||
84
src/collapsestateproxymodel.cpp
Normal file
84
src/collapsestateproxymodel.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "collapsestateproxymodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
||||
!= QLatin1String("state") // If this is not a state, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= QLatin1String("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
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventResolvedTypeRole)
|
||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0),
|
||||
MessageEventModel::EventResolvedTypeRole) // Also show it if it's of a different type than the one before TODO improve in
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::AuthorIdRole)
|
||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::AuthorIdRole); // Also show it if it's a different author
|
||||
}
|
||||
|
||||
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString();
|
||||
if (sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventTypeRole) != QLatin1String("state") // If it's not a state event
|
||||
|| (i > 0
|
||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventResolvedTypeRole)
|
||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventResolvedTypeRole)) // or of a different type
|
||||
|| (i > 0
|
||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorIdRole)
|
||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::AuthorIdRole)) // or by a different author
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1) {
|
||||
chunks.last() += i18ncp("[user did something] n times", " %1 time", " %1 times", count);
|
||||
}
|
||||
}
|
||||
QString text = chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
23
src/collapsestateproxymodel.h
Normal file
23
src/collapsestateproxymodel.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPair>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
class CollapseStateProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1,
|
||||
};
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
};
|
||||
@@ -70,19 +70,21 @@ Controller::Controller(QObject *parent)
|
||||
Connection::setUserType<NeoChatUser>();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
TrayIcon *trayIcon = new TrayIcon(this);
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
trayIcon->show();
|
||||
connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
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, trayIcon]() {
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [this]() {
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
trayIcon->show();
|
||||
connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
trayIcon->hide();
|
||||
disconnect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ class QKeySequences;
|
||||
|
||||
class NeoChatRoom;
|
||||
class NeoChatUser;
|
||||
class TrayIcon;
|
||||
class QQuickWindow;
|
||||
|
||||
namespace QKeychain
|
||||
@@ -100,6 +101,7 @@ private:
|
||||
|
||||
QPointer<Connection> m_connection;
|
||||
bool m_busy = false;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
static QByteArray loadAccessTokenFromFile(const AccountSettings &account);
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const AccountSettings &account);
|
||||
|
||||
24
src/main.cpp
24
src/main.cpp
@@ -6,10 +6,12 @@
|
||||
#include <QFontDatabase>
|
||||
#include <QGuiApplication>
|
||||
#include <QIcon>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlNetworkAccessManagerFactory>
|
||||
#include <QQuickStyle>
|
||||
#include <QQuickWindow>
|
||||
|
||||
@@ -44,6 +46,7 @@
|
||||
#include "chatboxhelper.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "clipboard.h"
|
||||
#include "collapsestateproxymodel.h"
|
||||
#include "commandmodel.h"
|
||||
#include "controller.h"
|
||||
#include "csapi/joining.h"
|
||||
@@ -60,6 +63,7 @@
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "networkaccessmanager.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "publicroomlistmodel.h"
|
||||
#include "roomlistmodel.h"
|
||||
@@ -74,8 +78,21 @@
|
||||
#include "colorschemer.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
#include "runner.h"
|
||||
#include <QDBusConnection>
|
||||
#endif
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
|
||||
{
|
||||
QNetworkAccessManager *create(QObject *) override
|
||||
{
|
||||
return NetworkAccessManager::instance();
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef HAVE_WINDOWSYSTEM
|
||||
static void raiseWindow(QWindow *window)
|
||||
{
|
||||
@@ -187,6 +204,7 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
||||
qmlRegisterType<CustomEmojiModel>("org.kde.neochat", 1, 0, "CustomEmojiModel");
|
||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
||||
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
||||
@@ -219,6 +237,7 @@ int main(int argc, char *argv[])
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
KLocalizedString::setApplicationDomain("neochat");
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
|
||||
@@ -243,6 +262,11 @@ int main(int argc, char *argv[])
|
||||
RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]);
|
||||
}
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
Runner runner;
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_KDBUSADDONS
|
||||
KDBusService service(KDBusService::Unique);
|
||||
service.connect(&service,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include <connection.h>
|
||||
#include <csapi/rooms.h>
|
||||
#include <events/reactionevent.h>
|
||||
#include <events/redactionevent.h>
|
||||
#include <events/roomavatarevent.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "stickerevent.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlEngine> // for qmlRegisterType()
|
||||
#include <QTimeZone>
|
||||
|
||||
@@ -40,7 +42,9 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
||||
roles[AnnotationRole] = "annotation";
|
||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyRole] = "reply";
|
||||
roles[ReplyIdRole] = "replyId";
|
||||
roles[UserMarkerRole] = "userMarker";
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
roles[ShowSectionRole] = "showSection";
|
||||
@@ -49,6 +53,8 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[SourceRole] = "source";
|
||||
roles[MimeTypeRole] = "mimeType";
|
||||
roles[FormattedBodyRole] = "formattedBody";
|
||||
roles[AuthorIdRole] = "authorId";
|
||||
roles[MediaUrlRole] = "mediaUrl";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -60,20 +66,8 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
||||
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
||||
qRegisterMetaType<FileTransferInfo>();
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
if (!m_currentRoom) {
|
||||
return;
|
||||
}
|
||||
m_currentRoom->getPreviousContent(50);
|
||||
connect(this, &QAbstractListModel::rowsInserted, this, [this]() {
|
||||
if (m_currentRoom->readMarkerEventId().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto it = m_currentRoom->findInTimeline(m_currentRoom->readMarkerEventId());
|
||||
if (it == m_currentRoom->historyEdge()) {
|
||||
m_currentRoom->getPreviousContent(50);
|
||||
}
|
||||
});
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyRole});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,7 +88,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
if (room) {
|
||||
m_lastReadEventIndex = QPersistentModelIndex(QModelIndex());
|
||||
room->setDisplayed();
|
||||
if (m_currentRoom->timelineSize() < 10) {
|
||||
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
||||
room->getPreviousContent(50);
|
||||
}
|
||||
lastReadEventId = room->readMarkerEventId();
|
||||
@@ -638,19 +632,35 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return variantList;
|
||||
}
|
||||
|
||||
if (role == IsReplyRole) {
|
||||
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||
}
|
||||
|
||||
if (role == ReplyIdRole) {
|
||||
return evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
}
|
||||
|
||||
if (role == ReplyRole) {
|
||||
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);
|
||||
if (replyIt == m_currentRoom->historyEdge()) {
|
||||
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 {};
|
||||
};
|
||||
const auto &replyEvt = **replyIt;
|
||||
}
|
||||
|
||||
QString type;
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&replyEvt)) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(replyPtr)) {
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
type = "emote";
|
||||
@@ -675,29 +685,29 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
type = "message";
|
||||
}
|
||||
|
||||
} else if (is<const StickerEvent>(replyEvt)) {
|
||||
} else if (is<const StickerEvent>(*replyPtr)) {
|
||||
type = "sticker";
|
||||
} else {
|
||||
type = "other";
|
||||
}
|
||||
|
||||
QVariant content;
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&replyEvt)) {
|
||||
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>(&replyEvt)) {
|
||||
if (auto e = eventCast<const StickerEvent>(replyPtr)) {
|
||||
content = QVariant::fromValue(e->image().originalJson);
|
||||
}
|
||||
|
||||
return QVariantMap{{"eventId", replyEventId},
|
||||
{"display", m_currentRoom->eventToString(replyEvt, Qt::RichText)},
|
||||
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"content", content},
|
||||
{"type", type},
|
||||
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyEvt.senderId())), m_currentRoom, evt)}};
|
||||
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId())), m_currentRoom, evt)}};
|
||||
}
|
||||
|
||||
if (role == ShowAuthorRole) {
|
||||
@@ -759,6 +769,26 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
return res;
|
||||
}
|
||||
if (role == AuthorIdRole) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return m_currentRoom->urlToDownload(evt.id());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -847,3 +877,13 @@ QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
||||
}
|
||||
return replyResponse;
|
||||
}
|
||||
|
||||
void MessageEventModel::loadReply(const QModelIndex &index)
|
||||
{
|
||||
auto job = m_currentRoom->connection()->callApi<GetOneRoomEventJob>(m_currentRoom->id(), data(index, ReplyIdRole).toString());
|
||||
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});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ public:
|
||||
MimeTypeRole,
|
||||
FileMimetypeIcon,
|
||||
|
||||
IsReplyRole,
|
||||
ReplyRole,
|
||||
ReplyIdRole,
|
||||
|
||||
ShowAuthorRole,
|
||||
ShowSectionRole,
|
||||
@@ -42,9 +44,11 @@ public:
|
||||
|
||||
IsEditedRole,
|
||||
SourceRole,
|
||||
|
||||
MediaUrlRole,
|
||||
// For debugging
|
||||
EventResolvedTypeRole,
|
||||
AuthorIdRole,
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
@@ -64,6 +68,7 @@ public:
|
||||
Q_INVOKABLE [[nodiscard]] int eventIDToIndex(const QString &eventID) const;
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromIndex(const int baseline);
|
||||
Q_INVOKABLE void loadReply(const QModelIndex &row);
|
||||
|
||||
private Q_SLOTS:
|
||||
int refreshEvent(const QString &eventId);
|
||||
@@ -88,6 +93,8 @@ private:
|
||||
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
||||
void moveReadMarker(const QString &toEventId);
|
||||
|
||||
std::vector<event_ptr_tt<RoomEvent>> m_extraEvents;
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void fancyEffectsReasonFound(const QString &fancyEffect);
|
||||
|
||||
@@ -14,6 +14,7 @@ Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
Name[it]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[lt]=NeoChat
|
||||
@@ -37,6 +38,7 @@ Comment[ar]=عميل لماتركس، ميفاق الاتصال اللامركز
|
||||
Comment[az]=Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu
|
||||
Comment[ca]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
||||
Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
||||
Comment[cs]=Klient pro decentralizovaný komunikační protokol matrix
|
||||
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
|
||||
Comment[en_GB]=A client for matrix, the decentralised communication protocol
|
||||
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
||||
@@ -45,6 +47,7 @@ Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
|
||||
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
|
||||
Comment[hu]=Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz
|
||||
Comment[ia]=Un cliente per Matrix, le protocollo de communication decentralisate
|
||||
Comment[id]=Sebuah klien untuk matrix, protokol komunikasi terdecentralisasi
|
||||
Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato
|
||||
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
|
||||
Comment[lt]=Matrix decentralizuoto bendravimo protokolo kliento programa
|
||||
@@ -78,6 +81,7 @@ Name[fi]=Uusi viesti
|
||||
Name[fr]=Nouveau message
|
||||
Name[hu]=Új üzenet
|
||||
Name[ia]=Nove message
|
||||
Name[id]=Pesan baru
|
||||
Name[it]=Nuovo messaggio
|
||||
Name[ko]=새 메시지
|
||||
Name[lt]=Nauja žinutė
|
||||
@@ -108,6 +112,7 @@ Comment[fi]=Saapui uusi viesti
|
||||
Comment[fr]=Il y a un nouveau message
|
||||
Comment[hu]=Új üzenet érkezett
|
||||
Comment[ia]=Il ha un nove message
|
||||
Comment[id]=Ada pesan baru
|
||||
Comment[it]=È presente un nuovo messaggio
|
||||
Comment[ko]=새 메시지가 있음
|
||||
Comment[lt]=Yra nauja žinutė
|
||||
@@ -133,11 +138,14 @@ Name[ar]=دعوة جديدة
|
||||
Name[az]=Yeni dəvət
|
||||
Name[ca]=Invitació nova
|
||||
Name[ca@valencia]=Invitació nova
|
||||
Name[cs]=Nová pozvánka
|
||||
Name[de]=Neue Einladung
|
||||
Name[en_GB]=New Invitation
|
||||
Name[es]=Nueva invitación
|
||||
Name[fi]=Uusi kutsu
|
||||
Name[fr]=Nouvelle invitation
|
||||
Name[ia]=Nove invitation
|
||||
Name[id]=Undangan Baru
|
||||
Name[it]=Nuovo invito
|
||||
Name[ko]=새 초대장
|
||||
Name[nl]=Nieuwe uitnodiging
|
||||
@@ -154,11 +162,14 @@ Comment[ar]=توجد دعوة جديدة
|
||||
Comment[az]=Otağa bir yeni dəvət var
|
||||
Comment[ca]=Hi ha una invitació nova a una sala
|
||||
Comment[ca@valencia]=Hi ha una invitació nova a una sala
|
||||
Comment[cs]=Máte novou pozvánku do místnosti
|
||||
Comment[de]=Es gibt eine neue Einladung zu einem Raum
|
||||
Comment[en_GB]=There is a new invitation to a room
|
||||
Comment[es]=Hay una nueva invitación a una sala
|
||||
Comment[fi]=Uusi kutsu huoneeseen
|
||||
Comment[fr]=Il y a une nouvelle invitation dans un salon.
|
||||
Comment[ia]=Il ha un nove invitation a un sala
|
||||
Comment[id]=Ada undangan baru ke sebuah ruangan
|
||||
Comment[it]=È presente un nuovo invito a una stanza
|
||||
Comment[ko]=새로운 대화방 초대장을 받음
|
||||
Comment[nl]=Er is een nieuwe uitnodiging naar een room
|
||||
|
||||
@@ -252,6 +252,35 @@ QDateTime NeoChatRoom::lastActiveTime()
|
||||
return messageEvents().rbegin()->get()->originTimestamp();
|
||||
}
|
||||
|
||||
QString NeoChatRoom::subtitleText()
|
||||
{
|
||||
QString subtitle = this->lastEventToString().size() == 0 ? this->topic() : this->lastEventToString();
|
||||
|
||||
subtitle
|
||||
// replace blockquote, i.e. '> text'
|
||||
.replace(QRegularExpression("(\r\n\t|\n|\r\t|)> "), " ")
|
||||
// replace headings, i.e. "# text"
|
||||
.replace(QRegularExpression("(\r\n\t|\n|\r\t|)\\#{1,6} "), " ")
|
||||
// replace newlines
|
||||
.replace(QRegularExpression("(\r\n\t|\n|\r\t)"), " ")
|
||||
// replace '**text**' and '__text__'
|
||||
.replace(QRegularExpression("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1"), "\\2")
|
||||
// replace '*text*' and '_text_'
|
||||
.replace(QRegularExpression("(\\*|_)(?=\\S)([^\\r]*\\S)\\1"), "\\2")
|
||||
// replace '~~text~~'
|
||||
.replace(QRegularExpression("~~(.*)~~"), "\\1")
|
||||
// replace '~text~'
|
||||
.replace(QRegularExpression("~(.*)~"), "\\1")
|
||||
// replace '<del>text</del>'
|
||||
.replace(QRegularExpression("<del>(.*)</del>"), "\\1")
|
||||
// replace '```code```'
|
||||
.replace(QRegularExpression("```([^```]+)```"), "\\1")
|
||||
// replace '`code`'
|
||||
.replace(QRegularExpression("`([^`]+)`"), "\\1");
|
||||
|
||||
return subtitle.size() > 0 ? subtitle : QStringLiteral(" ");
|
||||
}
|
||||
|
||||
int NeoChatRoom::savedTopVisibleIndex() const
|
||||
{
|
||||
return firstDisplayedMarker() == historyEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
|
||||
@@ -311,7 +340,11 @@ QVariantMap NeoChatRoom::getUser(const QString &userID) const
|
||||
|
||||
QUrl NeoChatRoom::urlToMxcUrl(const QUrl &mxcUrl)
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
return connection()->makeMediaUrl(mxcUrl);
|
||||
#else
|
||||
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeoChatRoom::avatarMediaId() const
|
||||
@@ -396,6 +429,18 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
[this](const RoomMemberEvent &e) {
|
||||
// FIXME: Rewind to the name that was at the time of this event
|
||||
auto subjectName = this->htmlSafeMemberName(e.userId());
|
||||
if (e.membership() == MembershipType::Leave) {
|
||||
auto displayName = e.prevContent()->displayName;
|
||||
#ifdef QUOTIENT_07
|
||||
if (displayName) {
|
||||
subjectName = sanitized(*displayName).toHtmlEscaped();
|
||||
#else
|
||||
if (displayName.isEmpty()) {
|
||||
subjectName = sanitized(displayName).toHtmlEscaped();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// The below code assumes senderName output in AuthorRole
|
||||
switch (e.membership()) {
|
||||
case MembershipType::Invite:
|
||||
@@ -493,6 +538,15 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
}
|
||||
if (e.matrixType() == QLatin1String("im.vector.modular.widgets")) {
|
||||
if (e.fullJson()["unsigned"]["prev_content"].toObject().isEmpty()) {
|
||||
return i18nc("[User] added <name> widget", "added %1 widget", e.contentJson()["name"].toString());
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18nc("[User] removed <name> widget", "removed %1 widget", e.fullJson()["unsigned"]["prev_content"]["name"].toString());
|
||||
}
|
||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"].toString());
|
||||
}
|
||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
||||
},
|
||||
@@ -781,3 +835,8 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NeoChatRoom::clearInvitationNotification()
|
||||
{
|
||||
NotificationsManager::instance().clearInvitationNotification(id());
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ public:
|
||||
/// \see lastEvent
|
||||
[[nodiscard]] QDateTime lastActiveTime();
|
||||
|
||||
/// Get subtitle text for room
|
||||
///
|
||||
/// Fetches last event and removes markdown formatting
|
||||
/// \see lastEventToString
|
||||
[[nodiscard]] QString subtitleText();
|
||||
|
||||
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
@@ -121,6 +127,7 @@ public:
|
||||
|
||||
Q_INVOKABLE QString htmlSafeName() const;
|
||||
Q_INVOKABLE QString htmlSafeDisplayName() const;
|
||||
Q_INVOKABLE void clearInvitationNotification();
|
||||
|
||||
#ifndef QUOTIENT_07
|
||||
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
||||
|
||||
@@ -32,11 +32,11 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
||||
}
|
||||
|
||||
void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
const QString &roomName,
|
||||
const QString &sender,
|
||||
const QString &text,
|
||||
const QImage &icon,
|
||||
const QString &replyEventId)
|
||||
const QString &replyEventId,
|
||||
bool canReply)
|
||||
{
|
||||
if (!NeoChatConfig::self()->showNotifications()) {
|
||||
return;
|
||||
@@ -46,10 +46,10 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
img.convertFromImage(icon);
|
||||
KNotification *notification = new KNotification("message");
|
||||
|
||||
if (sender == roomName) {
|
||||
if (sender == room->displayName()) {
|
||||
notification->setTitle(sender);
|
||||
} else {
|
||||
notification->setTitle(i18n("%1 (%2)", sender, roomName));
|
||||
notification->setTitle(i18n("%1 (%2)", sender, room->displayName()));
|
||||
}
|
||||
|
||||
notification->setText(text.toHtmlEscaped());
|
||||
@@ -61,15 +61,17 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
Q_EMIT Controller::instance().showWindow();
|
||||
});
|
||||
|
||||
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
||||
replyAction->setPlaceholderText(i18n("Reply..."));
|
||||
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
||||
room->postMessage(text, room->preprocessText(text), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
||||
});
|
||||
if (canReply) {
|
||||
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
||||
replyAction->setPlaceholderText(i18n("Reply..."));
|
||||
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
||||
room->postMessage(text, room->preprocessText(text), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
||||
});
|
||||
notification->setReplyAction(std::move(replyAction));
|
||||
}
|
||||
|
||||
notification->setHint(QStringLiteral("x-kde-origin-name"), room->localUser()->id());
|
||||
|
||||
notification->setReplyAction(std::move(replyAction));
|
||||
|
||||
notification->sendEvent();
|
||||
|
||||
@@ -87,24 +89,38 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QStri
|
||||
notification->setText(i18n("%1 invited you to a room", sender));
|
||||
notification->setTitle(title);
|
||||
notification->setPixmap(img);
|
||||
notification->setFlags(KNotification::Persistent);
|
||||
notification->setDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
connect(notification, &KNotification::defaultActivated, this, [=]() {
|
||||
#if defined(HAVE_WINDOWSYSTEM) && KNOTIFICATIONS_VERSION >= QT_VERSION_CHECK(5, 90, 0)
|
||||
KWindowSystem::setCurrentXdgActivationToken(notification->xdgActivationToken());
|
||||
#endif
|
||||
notification->close();
|
||||
RoomManager::instance().enterRoom(room);
|
||||
Q_EMIT Controller::instance().showWindow();
|
||||
});
|
||||
notification->setActions({i18n("Accept Invitation"), i18n("Reject Invitation")});
|
||||
connect(notification, &KNotification::action1Activated, this, [room]() {
|
||||
connect(notification, &KNotification::action1Activated, this, [this, room, notification]() {
|
||||
room->acceptInvitation();
|
||||
notification->close();
|
||||
});
|
||||
connect(notification, &KNotification::action2Activated, this, [room]() {
|
||||
connect(notification, &KNotification::action2Activated, this, [this, room, notification]() {
|
||||
RoomManager::instance().leaveRoom(room);
|
||||
notification->close();
|
||||
});
|
||||
connect(notification, &KNotification::closed, this, [this, room]() {
|
||||
m_invitations.remove(room->id());
|
||||
});
|
||||
|
||||
notification->setHint(QStringLiteral("x-kde-origin-name"), room->localUser()->id());
|
||||
|
||||
notification->sendEvent();
|
||||
m_notifications.insert(room->id(), notification);
|
||||
m_invitations.insert(room->id(), notification);
|
||||
}
|
||||
|
||||
void NotificationsManager::clearInvitationNotification(const QString &roomId)
|
||||
{
|
||||
if (m_invitations.contains(roomId)) {
|
||||
m_invitations[roomId]->close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,14 @@ public:
|
||||
static NotificationsManager &instance();
|
||||
|
||||
Q_INVOKABLE void
|
||||
postNotification(NeoChatRoom *room, const QString &roomName, const QString &sender, const QString &text, const QImage &icon, const QString &replyEventId);
|
||||
postNotification(NeoChatRoom *room, const QString &sender, const QString &text, const QImage &icon, const QString &replyEventId, bool canReply);
|
||||
void postInviteNotification(NeoChatRoom *room, const QString &title, const QString &sender, const QImage &icon);
|
||||
|
||||
void clearInvitationNotification(const QString &roomId);
|
||||
|
||||
private:
|
||||
NotificationsManager(QObject *parent = nullptr);
|
||||
|
||||
QMultiMap<QString, KNotification *> m_notifications;
|
||||
QHash<QString, QPointer<KNotification>> m_invitations;
|
||||
};
|
||||
|
||||
61
src/plasma-runner-neochat.desktop
Normal file
61
src/plasma-runner-neochat.desktop
Normal file
@@ -0,0 +1,61 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
# SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
[Desktop Entry]
|
||||
Name=NeoChat
|
||||
Name[ar]=نيوتشات
|
||||
Name[az]=NeoChat
|
||||
Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
Name[it]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[lt]=NeoChat
|
||||
Name[nl]=NeoChat
|
||||
Name[nn]=NeoChat
|
||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||
Name[pl]=NeoChat
|
||||
Name[pt]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Comment=Find rooms in NeoChat
|
||||
Comment[ar]=اعثر على غرف في نيوتشات
|
||||
Comment[az]=NeoChat-da otaqları tapın
|
||||
Comment[ca]=Cerca sales en el NeoChat
|
||||
Comment[ca@valencia]=Busca sales en NeoChat
|
||||
Comment[en_GB]=Find rooms in NeoChat
|
||||
Comment[es]=Buscar salas en NeoChat
|
||||
Comment[fi]=Etsi huoneita NeoChatissä
|
||||
Comment[fr]=Trouver des salons dans NeoChat
|
||||
Comment[it]=Trova stanze in NeoChat
|
||||
Comment[ko]=NeoChat에서 대화방 찾기
|
||||
Comment[nl]=Rooms zoeken in NeoChat
|
||||
Comment[pt]=Procurar salas no NeoChat
|
||||
Comment[pt_BR]=Encontrar salas no NeoChat
|
||||
Comment[sl]=Najdi sobe v NeoChatu
|
||||
Comment[sv]=Sök efter rum i NeoChat
|
||||
Comment[uk]=Пошук кімнат у NeoChat
|
||||
Comment[x-test]=xxFind rooms in NeoChatxx
|
||||
X-KDE-ServiceTypes=Plasma/Runner
|
||||
Type=Service
|
||||
Icon=org.kde.neochat
|
||||
X-Plasma-API=DBus
|
||||
X-Plasma-DBusRunner-Service=org.kde.neochat
|
||||
X-Plasma-DBusRunner-Path=/RoomRunner
|
||||
X-Plasma-Request-Actions-Once=true
|
||||
X-Plasma-Runner-Min-Letter-Count=3
|
||||
@@ -171,7 +171,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
||||
refresh(room);
|
||||
});
|
||||
connect(room, &Room::addedMessages, this, [this, room] {
|
||||
refresh(room, {LastEventRole});
|
||||
refresh(room, {LastEventRole, SubtitleTextRole});
|
||||
});
|
||||
connect(room, &Room::notificationCountChanged, this, &RoomListModel::handleNotifications);
|
||||
connect(room, &Room::highlightCountChanged, this, [this, room] {
|
||||
@@ -236,11 +236,11 @@ void RoomListModel::handleNotifications()
|
||||
avatar_image = room->avatar(128);
|
||||
}
|
||||
NotificationsManager::instance().postNotification(dynamic_cast<NeoChatRoom *>(room),
|
||||
room->displayName(),
|
||||
sender->displayname(room),
|
||||
notification["event"].toObject()["content"].toObject()["body"].toString(),
|
||||
avatar_image,
|
||||
notification["event"].toObject()["event_id"].toString());
|
||||
notification["event"].toObject()["event_id"].toString(),
|
||||
true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -329,7 +329,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
NeoChatRoom *room = m_rooms.at(index.row());
|
||||
if (role == NameRole) {
|
||||
return !room->name().isEmpty() ? room->htmlSafeName() : room->htmlSafeDisplayName();
|
||||
return !room->name().isEmpty() ? room->name() : room->displayName();
|
||||
}
|
||||
if (role == DisplayNameRole) {
|
||||
return room->displayName();
|
||||
@@ -396,6 +396,15 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (role == CategoryVisibleRole) {
|
||||
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
|
||||
}
|
||||
if (role == SubtitleTextRole) {
|
||||
return room->subtitleText();
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
return room->avatar(128);
|
||||
}
|
||||
if (role == IdRole) {
|
||||
return room->id();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
@@ -426,6 +435,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
roles[JoinStateRole] = "joinState";
|
||||
roles[CurrentRoomRole] = "currentRoom";
|
||||
roles[CategoryVisibleRole] = "categoryVisible";
|
||||
roles[SubtitleTextRole] = "subtitleText";
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ public:
|
||||
JoinStateRole,
|
||||
CurrentRoomRole,
|
||||
CategoryVisibleRole,
|
||||
SubtitleTextRole,
|
||||
AvatarImageRole,
|
||||
IdRole,
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
|
||||
94
src/runner.cpp
Normal file
94
src/runner.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDBusMetaType>
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "runner.h"
|
||||
|
||||
RemoteImage Runner::serializeImage(const QImage &image)
|
||||
{
|
||||
QImage convertedImage = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
RemoteImage remoteImage{
|
||||
convertedImage.width(),
|
||||
convertedImage.height(),
|
||||
convertedImage.bytesPerLine(),
|
||||
true, // hasAlpha
|
||||
8, // bitsPerSample
|
||||
4, // channels
|
||||
QByteArray(reinterpret_cast<const char *>(convertedImage.constBits()), convertedImage.sizeInBytes()),
|
||||
};
|
||||
return remoteImage;
|
||||
}
|
||||
|
||||
Runner::Runner()
|
||||
: QObject()
|
||||
{
|
||||
qDBusRegisterMetaType<RemoteMatch>();
|
||||
qDBusRegisterMetaType<RemoteMatches>();
|
||||
qDBusRegisterMetaType<RemoteAction>();
|
||||
qDBusRegisterMetaType<RemoteActions>();
|
||||
qDBusRegisterMetaType<RemoteImage>();
|
||||
|
||||
m_model.setSourceModel(&m_sourceModel);
|
||||
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &Runner::activeConnectionChanged);
|
||||
}
|
||||
|
||||
void Runner::activeConnectionChanged()
|
||||
{
|
||||
m_sourceModel.setConnection(Controller::instance().activeConnection());
|
||||
}
|
||||
|
||||
RemoteActions Runner::Actions()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
RemoteMatches Runner::Match(const QString &searchTerm)
|
||||
{
|
||||
m_model.setFilterText(searchTerm);
|
||||
|
||||
RemoteMatches matches;
|
||||
|
||||
for (int i = 0; i < m_model.rowCount(); ++i) {
|
||||
RemoteMatch match;
|
||||
|
||||
const QString name = m_model.data(m_model.index(i, 0), RoomListModel::DisplayNameRole).toString();
|
||||
|
||||
match.iconName = QStringLiteral("org.kde.neochat");
|
||||
match.id = m_model.data(m_model.index(i, 0), RoomListModel::IdRole).toString();
|
||||
match.text = name;
|
||||
match.relevance = 1;
|
||||
const RemoteImage remoteImage = serializeImage(m_model.data(m_model.index(i, 0), RoomListModel::AvatarImageRole).value<QImage>());
|
||||
match.properties.insert(QStringLiteral("icon-data"), QVariant::fromValue(remoteImage));
|
||||
match.properties.insert(QStringLiteral("subtext"), m_model.data(m_model.index(i, 0), RoomListModel::TopicRole).toString());
|
||||
|
||||
if (name.compare(searchTerm, Qt::CaseInsensitive) == 0) {
|
||||
match.type = ExactMatch;
|
||||
} else {
|
||||
match.type = CompletionMatch;
|
||||
}
|
||||
|
||||
matches << match;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
void Runner::Run(const QString &id, const QString &actionId)
|
||||
{
|
||||
Q_UNUSED(actionId);
|
||||
|
||||
NeoChatRoom *room = qobject_cast<NeoChatRoom *>(Controller::instance().activeConnection()->room(id));
|
||||
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
RoomManager::instance().enterRoom(room);
|
||||
Q_EMIT Controller::instance().showWindow();
|
||||
}
|
||||
169
src/runner.h
Normal file
169
src/runner.h
Normal file
@@ -0,0 +1,169 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDBusContext>
|
||||
#include <QObject>
|
||||
|
||||
#include <QDBusArgument>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
#include "sortfilterroomlistmodel.h"
|
||||
|
||||
// Copied from KRunner/QueryMatch
|
||||
enum MatchType {
|
||||
NoMatch = 0, /**< Null match */
|
||||
CompletionMatch = 10, /**< Possible completion for the data of the query */
|
||||
PossibleMatch = 30, /**< Something that may match the query */
|
||||
InformationalMatch = 50, /**< A purely informational, non-runnable match,
|
||||
such as the answer to a question or calculation.
|
||||
The data of the match will be converted to a string
|
||||
and set in the search field */
|
||||
HelperMatch = 70, /**< A match that represents an action not directly related
|
||||
to activating the given search term, such as a search
|
||||
in an external tool or a command learning trigger. Helper
|
||||
matches tend to be generic to the query and should not
|
||||
be autoactivated just because the user hits "Enter"
|
||||
while typing. They must be explicitly selected to
|
||||
be activated, but unlike InformationalMatch cause
|
||||
an action to be triggered. */
|
||||
ExactMatch = 100, /**< An exact match to the query */
|
||||
};
|
||||
|
||||
struct RemoteMatch {
|
||||
// sssuda{sv}
|
||||
QString id;
|
||||
QString text;
|
||||
QString iconName;
|
||||
MatchType type = MatchType::NoMatch;
|
||||
qreal relevance = 0;
|
||||
QVariantMap properties;
|
||||
};
|
||||
|
||||
typedef QList<RemoteMatch> RemoteMatches;
|
||||
|
||||
struct RemoteAction {
|
||||
QString id;
|
||||
QString text;
|
||||
QString iconName;
|
||||
};
|
||||
|
||||
typedef QList<RemoteAction> RemoteActions;
|
||||
|
||||
struct RemoteImage {
|
||||
// iiibiiay (matching notification spec image-data attribute)
|
||||
int width;
|
||||
int height;
|
||||
int rowStride;
|
||||
bool hasAlpha;
|
||||
int bitsPerSample;
|
||||
int channels;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteMatch &match)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << match.id;
|
||||
argument << match.text;
|
||||
argument << match.iconName;
|
||||
argument << match.type;
|
||||
argument << match.relevance;
|
||||
argument << match.properties;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteMatch &match)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> match.id;
|
||||
argument >> match.text;
|
||||
argument >> match.iconName;
|
||||
uint type;
|
||||
argument >> type;
|
||||
match.type = static_cast<MatchType>(type);
|
||||
argument >> match.relevance;
|
||||
argument >> match.properties;
|
||||
argument.endStructure();
|
||||
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteAction &action)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << action.id;
|
||||
argument << action.text;
|
||||
argument << action.iconName;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteAction &action)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> action.id;
|
||||
argument >> action.text;
|
||||
argument >> action.iconName;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteImage &image)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << image.width;
|
||||
argument << image.height;
|
||||
argument << image.rowStride;
|
||||
argument << image.hasAlpha;
|
||||
argument << image.bitsPerSample;
|
||||
argument << image.channels;
|
||||
argument << image.data;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteImage &image)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> image.width;
|
||||
argument >> image.height;
|
||||
argument >> image.rowStride;
|
||||
argument >> image.hasAlpha;
|
||||
argument >> image.bitsPerSample;
|
||||
argument >> image.channels;
|
||||
argument >> image.data;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(RemoteMatch)
|
||||
Q_DECLARE_METATYPE(RemoteMatches)
|
||||
Q_DECLARE_METATYPE(RemoteAction)
|
||||
Q_DECLARE_METATYPE(RemoteActions)
|
||||
Q_DECLARE_METATYPE(RemoteImage)
|
||||
|
||||
class Runner : public QObject, protected QDBusContext
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", "org.kde.krunner1")
|
||||
public:
|
||||
Runner();
|
||||
|
||||
Q_SCRIPTABLE RemoteActions Actions();
|
||||
Q_SCRIPTABLE RemoteMatches Match(const QString &searchTerm);
|
||||
Q_SCRIPTABLE void Run(const QString &id, const QString &actionId);
|
||||
|
||||
private:
|
||||
RemoteImage serializeImage(const QImage &image);
|
||||
void activeConnectionChanged();
|
||||
|
||||
SortFilterRoomListModel m_model;
|
||||
RoomListModel m_sourceModel;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user