Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bd0a926..c627f6e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,102 +1,104 @@
if(WIN32)
set(KAZV_IS_WINDOWS 1)
else()
set(KAZV_IS_WINDOWS 0)
endif()
configure_file(kazv-version.cpp.in kazv-version.cpp)
configure_file(kazv-platform.hpp.in kazv-platform.hpp)
configure_file(kazv-defs.hpp.in kazv-defs.hpp)
set(kazvprivlib_SRCS
qt-job-handler.cpp
qt-job.cpp
${CMAKE_CURRENT_BINARY_DIR}/kazv-version.cpp
matrix-sdk.cpp
kazv-session-lock-guard.cpp
matrix-room.cpp
matrix-room-list.cpp
matrix-room-timeline.cpp
matrix-room-member.cpp
matrix-room-member-list-model.cpp
matrix-event-reader.cpp
matrix-event-reader-list-model.cpp
matrix-event.cpp
matrix-event-list.cpp
matrix-link.cpp
meta-types.cpp
l10n-provider.cpp
qt-rand-adapter.cpp
helper.cpp
matrix-promise.cpp
kazv-util.cpp
matrix-sticker-pack.cpp
matrix-sticker.cpp
matrix-sticker-pack-list.cpp
matrix-sticker-pack-source.cpp
matrix-user-given-attrs-map.cpp
kazv-abstract-list-model.cpp
qt-promise-handler.cpp
device-mgmt/matrix-device.cpp
device-mgmt/matrix-device-list.cpp
kazv-config.cpp
kazv-path-config.cpp
kazv-io-manager.cpp
kazv-io-job.cpp
kazv-file.cpp
upload-job-model.cpp
kazv-markdown.cpp
shortcuts/shortcut-util.cpp
register-types.cpp
+
+ kazv-clipboard.cpp
)
ecm_qt_declare_logging_category(kazvprivlib_SRCS
HEADER kazv-log.hpp
IDENTIFIER kazvLog
CATEGORY_NAME moe.kazv.mxc.kazv
)
add_library(kazvprivlib STATIC ${kazvprivlib_SRCS})
target_link_libraries(kazvprivlib PUBLIC
libkazv::kazvall
Qt${QT_MAJOR_VERSION}::Core
Qt${QT_MAJOR_VERSION}::Network
Threads::Threads
Qt${QT_MAJOR_VERSION}::Gui
Qt${QT_MAJOR_VERSION}::Qml
Qt${QT_MAJOR_VERSION}::Quick
Qt${QT_MAJOR_VERSION}::QuickControls2
Qt${QT_MAJOR_VERSION}::Svg
Qt${QT_MAJOR_VERSION}::Concurrent
Qt${QT_MAJOR_VERSION}::Widgets
KF${KF_MAJOR_VERSION}::ConfigCore KF${KF_MAJOR_VERSION}::KIOCore
KF${KF_MAJOR_VERSION}::Notifications
${CMARK_TARGET_NAME}
)
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/device-mgmt)
set(kazv_SRCS
main.cpp
resources.qrc
l10n/translations.qrc
)
add_executable(kazv ${kazv_SRCS})
target_link_libraries(kazv
PRIVATE
kazvprivlib
)
set(kazv_INSTALL_ARGS ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
if(NOT kazv_INSTALL_ARGS)
set(kazv_INSTALL_ARGS ${KF${KF_MAJOR_VERSION}_INSTALL_TARGETS_DEFAULT_ARGS})
endif()
install(TARGETS kazv ${kazv_INSTALL_ARGS})
add_subdirectory(tests)
diff --git a/src/contents/ui/SendMessageBox.qml b/src/contents/ui/SendMessageBox.qml
index a5651e5..552ee8e 100644
--- a/src/contents/ui/SendMessageBox.qml
+++ b/src/contents/ui/SendMessageBox.qml
@@ -1,367 +1,383 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import org.kde.kirigami 2.13 as Kirigami
import moe.kazv.mxc.kazv 0.0 as MK
import '.' as Kazv
ColumnLayout {
id: sendMessageBox
property var room
property var draftRelType: ''
property var draftRelatedTo: ''
property var timeline: room.timeline()
property var members: room.members()
function getRelationPrompt(draftRelType) {
if (draftRelType === 'm.in_reply_to') {
return l10n.get('send-message-box-reply-to');
} else if (draftRelType === 'm.replace') {
return l10n.get('send-message-box-edit');
}
}
function getCancelRelationPrompt(draftRelType) {
if (draftRelType === 'm.in_reply_to') {
return l10n.get('send-message-box-remove-reply-to-action');
} else if (draftRelType === 'm.replace') {
return l10n.get('send-message-box-remove-replace-action');
}
}
function replaceDraft(newDraft) {
textArea.changeText(newDraft, /* inhibitTyping = */ true);
}
onRoomChanged: {
textArea.changeText(room.localDraft, true);
}
Popup {
id: completionPopup
objectName: 'completionPopup'
x: 1
y: -height
width: parent.width - 1
contentItem: Kazv.Completion {
id: completion
members: sendMessageBox.members
onMentionUserRequested: (userId) => {
textArea.removePartialMention();
sendMessageBox.mentionUser(userId);
}
}
}
Control {
visible: !!sendMessageBox.draftRelatedTo
Layout.fillWidth: true
background: Rectangle {
color: Kirigami.Theme.alternateBackgroundColor
}
contentItem: RowLayout {
spacing: 0
ColumnLayout {
Layout.fillWidth: true
Label {
id: replyLabel
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
text: getRelationPrompt(sendMessageBox.draftRelType)
}
Item {
id: replyItem
Layout.minimumHeight: Math.min(replyToEventView.implicitHeight, Kirigami.Units.gridUnit * 5)
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
Layout.fillWidth: true
clip: true
Kazv.EventViewWrapper {
id: replyToEventView
anchors {
top: parent.top
left: parent.left
right: parent.right
}
event: room.messageById(sendMessageBox.draftRelatedTo)
compactMode: true
}
}
}
ToolButton {
Layout.alignment: Qt.AlignTop
action: removeRelatedToAction
display: AbstractButton.IconOnly
}
}
}
RowLayout {
TextArea {
id: textArea
objectName: 'draftMessage'
property var shortcutList: ["Ctrl+Return", "Ctrl+Enter"]
property var inhibitTyping: false
placeholderText: l10n.get('send-message-box-input-placeholder')
property string filter: getFilter()
Layout.fillWidth: true
wrapMode: TextEdit.Wrap
persistentSelection: true
onTextChanged: {
room.setLocalDraft(text);
if (!inhibitTyping) {
room.setTyping(true);
}
inhibitTyping = false;
}
function changeText(newText, inhibitTyping) {
textArea.inhibitTyping = inhibitTyping;
textArea.text = newText;
}
onVisibleChanged: {
if (!visible) {
room.updateLocalDraftNow();
}
}
function getFilter() {
const atPos = text.lastIndexOf('@', cursorPosition);
if (atPos < 0) {
return '';
}
const textInBetween = text.slice(atPos, cursorPosition);
const hasSpaces = /\s/.test(textInBetween);
return hasSpaces ? '' : textInBetween;
}
function removePartialMention() {
const atPos = text.lastIndexOf('@', cursorPosition);
text = text.slice(0, atPos) + text.slice(cursorPosition);
cursorPosition = atPos;
}
onFilterChanged: {
sendMessageBox.members.filter = filter.slice(1);
if (filter && sendMessageBox.members.count > 0) {
completionPopup.open();
} else {
completionPopup.close();
}
}
// Shortcut keys for sending messages (TODO: Shortcut keys customized by the user)
Shortcut {
sequences: textArea.shortcutList
onActivated: {
sendAction.trigger()
}
}
DropArea {
anchors.fill: parent
onDropped: (drag) => {
if (drag.hasUrls) {
confirmUploadPopup.call(drag.urls);
}
}
}
+ function handlePaste() {
+ if (MK.KazvClipboard.hasUrls) {
+ confirmUploadPopup.call(MK.KazvClipboard.urls());
+ } else if (MK.KazvClipboard.hasImage) {
+ confirmUploadPopup.call([ MK.KazvClipboard.saveClipboardImage() ]);
+ } else {
+ textArea.paste();
+ }
+ }
+ Keys.onPressed: (event) => {
+ if (event.matches(StandardKey.Paste)) {
+ textArea.handlePaste();
+ event.accepted = true;
+ }
+ }
+
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
preventStealing: true
cursorShape: Qt.IBeamCursor
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton)
textAreaMenu.popup();
}
Menu {
id: textAreaMenu
MenuItem {
text: l10n.get("action-undo")
icon.name: "edit-undo"
onTriggered: textArea.undo()
enabled: textArea.canUndo
}
MenuItem {
text: l10n.get("action-redo")
icon.name: "edit-redo"
onTriggered: textArea.redo()
enabled: textArea.canRedo
}
MenuSeparator {}
MenuItem {
text: l10n.get("action-cut")
icon.name: "edit-cut"
onTriggered: textArea.cut()
enabled: !!textArea.selectedText
}
MenuItem {
text: l10n.get("action-copy")
icon.name: "edit-copy"
onTriggered: textArea.copy()
enabled: !!textArea.selectedText
}
MenuItem {
text: l10n.get("action-paste")
icon.name: "edit-paste"
- onTriggered: textArea.paste()
- enabled: textArea.canPaste
+ onTriggered: textArea.handlePaste()
+ enabled: textArea.canPaste || MK.KazvClipboard.hasImage || MK.KazvClipboard.hasUrls
}
MenuItem {
text: l10n.get("action-delete")
icon.name: "edit-delete"
onTriggered: textArea.remove(textArea.selectionStart, textArea.selectionEnd)
enabled: !!textArea.selectedText
}
MenuItem {
text: l10n.get("action-select-all")
icon.name: "edit-select-all"
onTriggered: textArea.selectAll()
}
}
}
}
ToolButton {
action: sendMediaFileAction
display: AbstractButton.IconOnly
}
ToolButton {
action: stickersAction
display: AbstractButton.IconOnly
}
ToolButton {
objectName: 'sendButton'
action: sendAction
display: AbstractButton.IconOnly
}
}
function mentionUser(userId) {
const prefix = textArea.text.slice(0, textArea.cursorPosition);
const suffix = textArea.text.slice(textArea.cursorPosition);
let insertion = userId;
// If we are not at the beginning and the prev char is not a space,
// insert a space there.
if (prefix && prefix.slice(prefix.length - 1).trim() !== '') {
insertion = ' ' + insertion;
}
let postPadding = 0;
// If we are at the end or the next char is not a space,
// insert a space there.
if (!suffix || suffix.slice(0, 1).trim() !== '') {
insertion = insertion + ' ';
} else if (suffix) {
// If there is a suffix, and space is not inserted
// this means the next character is a space,
// move the cursor to that space.
postPadding = 1;
}
textArea.text = prefix + insertion + suffix;
textArea.cursorPosition = prefix.length + insertion.length + postPadding;
}
function focusInput() {
textArea.forceActiveFocus();
}
Kirigami.Action {
id: removeRelatedToAction
icon.name: 'window-close-symbolic'
text: getCancelRelationPrompt(sendMessageBox.draftRelType)
onTriggered: {
sendMessageBox.draftRelType = '';
sendMessageBox.draftRelatedTo = '';
}
}
Kirigami.Action {
id: sendAction
icon.name: "document-send"
text: l10n.get("send-message-box-send")
onTriggered: {
room.setTyping(false);
room.sendTextMessage(textArea.text, draftRelType, draftRelatedTo);
textArea.changeText("", true);
sendMessageBox.draftRelType = '';
sendMessageBox.draftRelatedTo = '';
}
enabled: textArea.text !== ''
}
Kirigami.Action {
id: sendMediaFileAction
icon.name: "document-open-data"
text: l10n.get("send-message-box-send-file")
onTriggered: {
fileDialog.open()
}
}
property var confirmUploadPopup: ConfirmUploadPopup {
id: confirmUploadPopup
parent: applicationWindow().overlay
onUploadRequested: (url) => {
sendMessageBox.uploadFile(url);
}
}
property var fileDialog: Kazv.FileDialogAdapter {
onAccepted: {
sendMessageBox.uploadFile(fileUrl);
}
}
function uploadFile(fileUrl) {
kazvIOManager.startNewUploadJob(
matrixSdk.serverUrl, fileUrl, matrixSdk.token,
room.roomId, sdkVars.roomList, room.encrypted
);
}
property var stickerPopup: Kirigami.OverlaySheet {
id: stickerPopup
parent: applicationWindow().overlay
title: l10n.get('send-message-box-stickers-popup-title')
Kazv.StickerPicker {
Layout.preferredWidth: Math.min(Kirigami.Units.gridUnit * 40, Window.width)
stickerPackList: matrixSdk.stickerPackList()
onSendMessageRequested: eventJson => {
console.log(JSON.stringify(eventJson));
room.sendMessage(eventJson, draftRelType, draftRelatedTo);
draftRelType = '';
draftRelatedTo = '';
stickerPopup.close();
}
}
}
Kirigami.Action {
id: stickersAction
icon.name: 'smiley'
text: l10n.get('send-message-box-stickers')
onTriggered: stickerPopup.open()
}
}
diff --git a/src/kazv-clipboard.cpp b/src/kazv-clipboard.cpp
new file mode 100644
index 0000000..d0972d0
--- /dev/null
+++ b/src/kazv-clipboard.cpp
@@ -0,0 +1,78 @@
+#include "kazv-clipboard.hpp"
+
+#include <QImage>
+#include <QGuiApplication>
+#include <QDateTime>
+#include <QStringList>
+#include <QMimeDatabase>
+
+KazvClipboard::KazvClipboard(QObject *parent)
+ : QObject(parent)
+ , m_tmpDir()
+ , m_tmpFilesCount(0)
+ , m_clipboard(QGuiApplication::clipboard())
+{
+ QObject::connect(m_clipboard, &QClipboard::dataChanged,
+ this, &KazvClipboard::updateHasImage);
+ QObject::connect(m_clipboard, &QClipboard::dataChanged,
+ this, &KazvClipboard::updateHasUrls);
+ this->updateHasImage();
+ this->updateHasUrls();
+}
+
+bool KazvClipboard::hasImage()
+{
+ return m_hasImage;
+}
+
+bool KazvClipboard::hasUrls()
+{
+ return m_hasUrls;
+}
+
+void KazvClipboard::setHasImage(bool hasImage)
+{
+ m_hasImage = hasImage;
+ Q_EMIT hasImageChanged();
+}
+
+void KazvClipboard::setHasUrls(bool hasUrls)
+{
+ m_hasUrls = hasUrls;
+ Q_EMIT hasUrlsChanged();
+}
+
+void KazvClipboard::updateHasImage()
+{
+ bool hasImage = m_clipboard->mimeData()->hasImage();
+ setHasImage(hasImage);
+}
+
+void KazvClipboard::updateHasUrls()
+{
+ bool hasUrls = m_clipboard->mimeData()->hasUrls();
+ setHasUrls(hasUrls);
+}
+
+QList<QUrl> KazvClipboard::urls()
+{
+ return m_clipboard->mimeData()->urls();
+}
+
+QUrl KazvClipboard::saveClipboardImage()
+{
+ auto *clipboard = QGuiApplication::clipboard();
+ m_tmpFilesCount++;
+ auto name = QString("%1").arg(m_tmpFilesCount);
+ auto mimes = clipboard->mimeData()->formats().filter("image/");
+ QString type;
+ if (mimes.size() < 1) {
+ qDebug() << "Saving clipboard image, but no image MIME found";
+ type = "";
+ } else {
+ type = QMimeDatabase().mimeTypeForName(mimes.at(0)).preferredSuffix();
+ }
+ auto path = m_tmpDir.filePath(type.isEmpty() ? name : name.append(".").append(type));
+ clipboard->image().save(path);
+ return QUrl::fromLocalFile(path);
+}
diff --git a/src/kazv-clipboard.hpp b/src/kazv-clipboard.hpp
new file mode 100644
index 0000000..2a610b5
--- /dev/null
+++ b/src/kazv-clipboard.hpp
@@ -0,0 +1,35 @@
+#include <QString>
+#include <QtQml>
+#include <QUrl>
+#include <QTemporaryDir>
+#include <QClipboard>
+
+class KazvClipboard : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_SINGLETON
+ Q_PROPERTY(bool hasImage READ hasImage WRITE setHasImage NOTIFY hasImageChanged)
+ Q_PROPERTY(bool hasUrls READ hasUrls WRITE setHasUrls NOTIFY hasUrlsChanged)
+public:
+ explicit KazvClipboard(QObject *parent = 0);
+ bool hasImage();
+ bool hasUrls();
+ void setHasImage(bool hasImage);
+ void setHasUrls(bool hasUrls);
+public Q_SLOTS:
+ void updateHasImage();
+ void updateHasUrls();
+ QList<QUrl> urls();
+ QUrl saveClipboardImage();
+Q_SIGNALS:
+ void hasImageChanged();
+ void hasUrlsChanged();
+private:
+ bool m_hasImage;
+ bool m_hasUrls;
+ QTemporaryDir m_tmpDir;
+ int m_tmpFilesCount;
+ QClipboard *m_clipboard;
+
+};
diff --git a/src/register-types.cpp b/src/register-types.cpp
index cdfb842..9c8d411 100644
--- a/src/register-types.cpp
+++ b/src/register-types.cpp
@@ -1,64 +1,67 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2021-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <QtQml>
#include "matrix-sdk.hpp"
#include "matrix-room-list.hpp"
#include "matrix-room.hpp"
#include "matrix-room-timeline.hpp"
#include "matrix-room-member.hpp"
#include "matrix-room-member-list-model.hpp"
#include "matrix-event-reader.hpp"
#include "matrix-event-reader-list-model.hpp"
#include "matrix-event.hpp"
#include "l10n-provider.hpp"
#include "kazv-config.hpp"
#include "kazv-io-manager.hpp"
#include "shortcuts/shortcut-util.hpp"
#include "kazv-io-job.hpp"
#include "device-mgmt/matrix-device-list.hpp"
#include "device-mgmt/matrix-device.hpp"
#include "matrix-promise.hpp"
#include "kazv-util.hpp"
#include "matrix-sticker.hpp"
#include "matrix-sticker-pack.hpp"
#include "matrix-sticker-pack-list.hpp"
#include "matrix-user-given-attrs-map.hpp"
+#include "kazv-clipboard.hpp"
void registerKazvQmlTypes()
{
qmlRegisterType<MatrixSdk>("moe.kazv.mxc.kazv", 0, 0, "MatrixSdk");
qmlRegisterType<L10nProvider>("moe.kazv.mxc.kazv", 0, 0, "L10nProvider");
qmlRegisterType<KazvConfig>("moe.kazv.mxc.kazv", 0, 0, "KazvConfig");
qmlRegisterType<KazvIOBaseJob>("moe.kazv.mxc.kazv", 0, 0, "KazvIOBaseJob");
qmlRegisterType<KazvIOManager>("moe.kazv.mxc.kazv", 0, 0, "KazvIOManager");
qmlRegisterType<UploadJobModel>("moe.kazv.mxc.kazv", 0, 0, "UploadJobModel");
qmlRegisterUncreatableType<MatrixRoomList>("moe.kazv.mxc.kazv", 0, 0, "MatrixRoomList", "");
qmlRegisterUncreatableType<MatrixRoom>("moe.kazv.mxc.kazv", 0, 0, "MatrixRoom", "");
qmlRegisterUncreatableType<MatrixRoomMember>("moe.kazv.mxc.kazv", 0, 0, "MatrixRoomMember", "");
qmlRegisterUncreatableType<MatrixRoomMemberListModel>("moe.kazv.mxc.kazv", 0, 0, "MatrixRoomMemberListModel", "");
qmlRegisterUncreatableType<MatrixRoomTimeline>("moe.kazv.mxc.kazv", 0, 0, "MatrixRoomTimeline", "");
qmlRegisterUncreatableType<MatrixEvent>("moe.kazv.mxc.kazv", 0, 0, "MatrixEvent", "");
qmlRegisterUncreatableType<MatrixEventReader>("moe.kazv.mxc.kazv", 0, 0, "MatrixEventReader", "");
qmlRegisterUncreatableType<MatrixEventReaderListModel>("moe.kazv.mxc.kazv", 0, 0, "MatrixEventReaderListModel", "");
qmlRegisterUncreatableType<MatrixDeviceList>("moe.kazv.mxc.kazv", 0, 0, "MatrixDeviceList", "");
qmlRegisterUncreatableType<MatrixDevice>("moe.kazv.mxc.kazv", 0, 0, "MatrixDevice", "");
qmlRegisterUncreatableType<MatrixPromise>("moe.kazv.mxc.kazv", 0, 0, "MatrixPromise", "");
qmlRegisterUncreatableType<MatrixSticker>("moe.kazv.mxc.kazv", 0, 0, "MatrixSticker", "");
qmlRegisterUncreatableType<MatrixStickerPack>("moe.kazv.mxc.kazv", 0, 0, "MatrixStickerPack", "");
qmlRegisterUncreatableType<MatrixStickerPackList>("moe.kazv.mxc.kazv", 0, 0, "MatrixStickerPackList", "");
qmlRegisterUncreatableType<MatrixUserGivenAttrsMap>("moe.kazv.mxc.kazv", 0, 0, "MatrixUserGivenAttrsMap", "");
#if KAZV_KF_QT_MAJOR_VERSION == 5
qmlRegisterSingletonInstance<KazvUtil>("moe.kazv.mxc.kazv", 0, 0, "KazvUtil", new KazvUtil());
qmlRegisterSingletonInstance<ShortcutUtil>("moe.kazv.mxc.kazvshortcuts", 0, 0, "ShortcutUtil", new ShortcutUtil());
+ qmlRegisterSingletonInstance<KazvClipboard>("moe.kazv.mxc.kazv", 0, 0, "KazvClipboard", new KazvClipboard());
#else
qmlRegisterSingletonInstance("moe.kazv.mxc.kazv", 0, 0, "KazvUtil", new KazvUtil());
qmlRegisterSingletonInstance("moe.kazv.mxc.kazvshortcuts", 0, 0, "ShortcutUtil", new ShortcutUtil());
+ qmlRegisterSingletonInstance("moe.kazv.mxc.kazv", 0, 0, "KazvClipboard", new KazvClipboard());
#endif
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 9, 10:46 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784904
Default Alt Text
(20 KB)

Event Timeline