Support pasting image to upload in the composer

This commit is contained in:
miruka 2020-07-15 15:10:34 -04:00
parent 2449fd5f18
commit 2d623118b5
6 changed files with 93 additions and 11 deletions

View File

@ -16,6 +16,7 @@ from copy import deepcopy
from datetime import datetime, timedelta
from functools import partial
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import (
TYPE_CHECKING, Any, ClassVar, Dict, List, NamedTuple, Optional, Set, Tuple,
Type, Union,
@ -23,6 +24,7 @@ from typing import (
from urllib.parse import urlparse
from uuid import UUID, uuid4
import aiofiles
import cairosvg
from PIL import Image as PILImage
from pymediainfo import MediaInfo
@ -40,7 +42,7 @@ from .errors import (
from .html_markdown import HTML_PROCESSOR as HTML
from .media_cache import Media, Thumbnail
from .models.items import (
Account, Event, Member, Presence, Room, Upload, UploadStatus, ZERO_DATE,
ZERO_DATE, Account, Event, Member, Presence, Room, Upload, UploadStatus,
)
from .models.model_store import ModelStore
from .nio_callbacks import NioCallbacks
@ -557,6 +559,18 @@ class MatrixClient(nio.AsyncClient):
self.upload_tasks[uuid].cancel()
async def send_clipboard_image(self, room_id: str, image: bytes) -> None:
"""Send a clipboard image passed from QML as a `m.image` message."""
prefix = datetime.now().strftime("%Y%m%d-%H%M%S.")
with NamedTemporaryFile(prefix=prefix, suffix=".png") as temp:
async with aiofiles.open(temp.name, "wb") as file:
await file.write(image)
await self.send_file(room_id, temp.name)
async def send_file(self, room_id: str, path: Union[Path, str]) -> None:
"""Send a `m.file`, `m.image`, `m.audio` or `m.video` message."""

View File

@ -6,14 +6,22 @@
#ifndef CLIPBOARD_H
#define CLIPBOARD_H
#include <QGuiApplication>
#include <QBuffer>
#include <QByteArray>
#include <QClipboard>
#include <QGuiApplication>
#include <QIODevice>
#include <QImage>
#include <QMimeData>
#include <QObject>
class Clipboard : public QObject {
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
Q_PROPERTY(QByteArray image READ image WRITE setImage NOTIFY imageChanged)
Q_PROPERTY(bool hasImage READ hasImage NOTIFY hasImageChanged)
Q_PROPERTY(QString selection READ selection WRITE setSelection
NOTIFY selectionChanged)
@ -22,7 +30,7 @@ class Clipboard : public QObject {
public:
explicit Clipboard(QObject *parent = nullptr) : QObject(parent) {
connect(this->clipboard, &QClipboard::dataChanged,
this, &Clipboard::textChanged);
this, &Clipboard::mainClipboardChanged);
connect(this->clipboard, &QClipboard::selectionChanged,
this, &Clipboard::selectionChanged);
@ -38,6 +46,23 @@ public:
this->clipboard->setText(text, QClipboard::Clipboard);
}
QByteArray image() const {
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
this->clipboard->image(QClipboard::Clipboard).save(&buffer, "PNG");
buffer.close();
return byteArray;
}
void setImage(const QByteArray &image) const { // TODO
Q_UNUSED(image)
}
bool hasImage() const {
return this->clipboard->mimeData()->hasImage();
}
// X11 select-middle-click-paste clipboard
QString selection() const {
@ -58,10 +83,17 @@ public:
signals:
void textChanged();
void imageChanged();
void hasImageChanged();
void selectionChanged();
private:
QClipboard *clipboard = QGuiApplication::clipboard();
void mainClipboardChanged() {
this->hasImage() ? this->imageChanged() : this->textChanged();
this->hasImageChanged();
};
};
#endif

View File

@ -2,6 +2,7 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import Clipboard 0.1
TextArea {
id: textArea
@ -26,6 +27,10 @@ TextArea {
property string previousDefaultText: "" // private
property bool enableCustomImagePaste: false
signal customImagePaste()
function reset() { clear(); text = Qt.binding(() => defaultText || "") }
function insertAtCursor(text) { insert(cursorPosition, text) }
@ -85,11 +90,22 @@ TextArea {
previousDefaultText = defaultText
}
// Prevent alt/super+any key from typing text
Keys.onPressed: if (
event.modifiers & Qt.AltModifier ||
event.modifiers & Qt.MetaModifier
) event.accepted = true
Keys.onPressed: ev => {
// Prevent alt/super+any key from typing text
if (
ev.modifiers & Qt.AltModifier ||
ev.modifiers & Qt.MetaModifier
) ev.accepted = true
if (
ev.matches(StandardKey.Paste) &&
textArea.enableCustomImagePaste &&
Clipboard.hasImage
) {
ev.accepted = true
textArea.customImagePaste()
}
}
Keys.onMenuPressed: contextMenu.spawn(false)
@ -159,5 +175,9 @@ TextArea {
onLongPressed: contextMenu.spawn()
}
HTextContextMenu { id: contextMenu }
HTextContextMenu {
id: contextMenu
enableCustomImagePaste: textArea.enableCustomImagePaste
onCustomImagePaste: print("foo") || textArea.customImagePaste()
}
}

View File

@ -1,12 +1,18 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import Clipboard 0.1
HMenu {
id: menu
property Item control: parent // HTextField or HTextArea
property bool enableCustomImagePaste: false
property bool hadPersistentSelection: false // TODO: use a Qt 5.15 Binding
signal customImagePaste()
function spawn(atMousePosition=true) {
hadPersistentSelection = control.persistentSelection
control.persistentSelection = true
@ -55,10 +61,13 @@ HMenu {
}
HMenuItem {
property bool pasteImage:
menu.enableCustomImagePaste && Clipboard.hasImage
icon.name: "paste-text"
text: qsTr("Paste")
enabled: control.canPaste
onTriggered: control.paste()
enabled: control.canPaste || pasteImage
onTriggered: pasteImage ? menu.customImagePaste() : control.paste()
}
HMenuSeparator {}

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import Clipboard 0.1
import "../../.."
import "../../../Base"
@ -93,6 +94,7 @@ HTextArea {
enabled: chat.roomInfo.can_send_messages
disabledText: qsTr("You do not have permission to post in this room")
placeholderText: qsTr("Type a message...")
enableCustomImagePaste: true
backgroundColor: "transparent"
focusedBorderColor: "transparent"
@ -159,6 +161,10 @@ HTextArea {
}
}
onCustomImagePaste: py.callClientCoro(
writingUserId, "send_clipboard_image", [chat.roomId, Clipboard.image],
)
Keys.onEscapePressed: clearReplyTo()
Keys.onReturnPressed: ev => {

View File

@ -3,6 +3,7 @@
// This file creates the application, registers custom objects for QML
// and launches Window.qml (the root component).
#include <QDataStream> // must be first include to avoid clipboard.h errors
#include <QApplication>
#include <QQmlEngine>
#include <QQmlContext>