summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLorn Potter <lorn.potter@gmail.com>2021-05-14 18:37:12 +1000
committerLorn Potter <lorn.potter@gmail.com>2021-12-08 13:39:58 +1000
commitf0be152896471aa392bb1b2b649b66feb31480cc (patch)
treeaa8a3d1c6776416c45578d75177c8eba05ee0f35
parent3b24713098abd34cf8652da815f4dcf3a22110d3 (diff)
wasm: improve clipboard support
Add support for Clipboard API Add clipboard manual test Also includes these fixes: - improve clipboard use for chrome browser - make QClipboard::setText work - html copy and paste - image copy/paste Chrome browser supports text, html and png To use the Clipboard API, apps need to be served from a secure context (https). There is a fallback in the case of non secure context (http) - Firefox requires dom.events.asyncClipboard.read, dom.events.asyncClipboard.clipboardItem and dom.events.asyncClipboard.dataTransfer to be set from about:config, in order to support the Clipboard API. Change-Id: Ie4cb1bbb1dfc77e9655090a30967632780d15dd9 Fixes: QTBUG-74504 Fixes: QTBUG-93619 Fixes: QTBUG-79365 Fixes: QTBUG-86169 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp392
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.h10
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.cpp11
-rw-r--r--tests/manual/wasm/CMakeLists.txt1
-rw-r--r--tests/manual/wasm/clipboard/CMakeLists.txt46
-rw-r--r--tests/manual/wasm/clipboard/README2
-rw-r--r--tests/manual/wasm/clipboard/clipboard.pro27
-rw-r--r--tests/manual/wasm/clipboard/data.qrc5
-rw-r--r--tests/manual/wasm/clipboard/data/qticon64.pngbin0 -> 6474 bytes
-rw-r--r--tests/manual/wasm/clipboard/main.cpp61
-rw-r--r--tests/manual/wasm/clipboard/mainwindow.cpp287
-rw-r--r--tests/manual/wasm/clipboard/mainwindow.h94
-rw-r--r--tests/manual/wasm/clipboard/mainwindow.ui222
13 files changed, 1081 insertions, 77 deletions
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 222dcff7fa..52471f7b41 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -30,103 +30,263 @@
#include "qwasmclipboard.h"
#include "qwasmwindow.h"
#include "qwasmstring.h"
+#include <private/qstdweb_p.h>
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/bind.h>
+#include <emscripten/val.h>
#include <QCoreApplication>
#include <qpa/qwindowsysteminterface.h>
+#include <QBuffer>
+#include <QString>
using namespace emscripten;
-// there has got to be a better way...
-static QString g_clipboardText;
-static QString g_clipboardFormat;
+static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr)
+{
+ QString formatString = QWasmString::toQString(format);
+ QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>());
+
+ QMimeData *mMimeData = new QMimeData;
+ mMimeData->setData(formatString, dataArray);
-static val getClipboardData()
+ QWasmClipboard::qWasmClipboardPaste(mMimeData);
+// QWasmIntegration::get()->getWasmClipboard()->isPaste = false;
+}
+
+static void qClipboardPasteResolve(emscripten::val blob)
{
- return QWasmString::fromQString(g_clipboardText);
+ // read Blob here
+
+ auto fileReader = std::make_shared<qstdweb::FileReader>();
+ auto _blob = qstdweb::Blob(blob);
+ QString formatString = QString::fromStdString(_blob.type());
+
+ fileReader->readAsArrayBuffer(_blob);
+ char *chunkBuffer = nullptr;
+ qstdweb::ArrayBuffer result = fileReader->result();
+ qstdweb::Uint8Array(result).copyTo(chunkBuffer);
+ QMimeData *mMimeData = new QMimeData;
+ mMimeData->setData(formatString, chunkBuffer);
+ QWasmClipboard::qWasmClipboardPaste(mMimeData);
}
-static val getClipboardFormat()
+static void qClipboardPromiseResolve(emscripten::val clipboardItems)
{
- return QWasmString::fromQString(g_clipboardFormat);
+ int itemsCount = clipboardItems["length"].as<int>();
+
+ for (int i = 0; i < itemsCount; i++) {
+ int typesCount = clipboardItems[i]["types"]["length"].as<int>(); // ClipboardItem
+
+ std::string mimeFormat = clipboardItems[i]["types"][0].as<std::string>();
+
+ if (mimeFormat.find(std::string("text")) != std::string::npos) {
+ // simple val object, no further processing
+
+ val navigator = val::global("navigator");
+ val textPromise = navigator["clipboard"].call<val>("readText");
+ val readTextResolve = val::global("Module")["qtClipboardTextPromiseResolve"];
+ textPromise.call<val>("then", readTextResolve);
+
+ } else {
+ // binary types require additional processing
+ for (int j = 0; j < typesCount; j++) {
+ val pasteResolve = emscripten::val::module_property("qtClipboardPasteResolve");
+ val pasteException = emscripten::val::module_property("qtClipboardPromiseException");
+
+ // get the blob
+ clipboardItems[i]
+ .call<val>("getType", clipboardItems[i]["types"][j])
+ .call<val>("then", pasteResolve)
+ .call<val>("catch", pasteException);
+ }
+ }
+ }
}
-static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr)
+static void qClipboardCopyPromiseResolve(emscripten::val something)
{
- QString formatString = QWasmString::toQString(format);
- QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>());
- QMimeData *mMimeData = new QMimeData;
- mMimeData->setData(formatString, dataArray);
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
+ qWarning() << "copy succeeeded";
}
-static void qClipboardPromiseResolve(emscripten::val something)
+
+static emscripten::val qClipboardPromiseException(emscripten::val something)
{
- pasteClipboardData(emscripten::val("text/plain"), something);
+ qWarning() << "clipboard error"
+ << QString::fromStdString(something["name"].as<std::string>())
+ << QString::fromStdString(something["message"].as<std::string>());
+ return something;
+}
+
+static void commonCopyEvent(val event)
+{
+ QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
+ if (!_mimes)
+ return;
+
+ // doing it this way seems to sanitize the text better that calling data() like down below
+ if (_mimes->hasText()) {
+ event["clipboardData"].call<void>("setData", val("text/plain")
+ , QWasmString::fromQString(_mimes->text()));
+ }
+ if (_mimes->hasHtml()) {
+ event["clipboardData"].call<void>("setData", val("text/html")
+ , QWasmString::fromQString(_mimes->html()));
+ }
+
+ for (auto mimetype : _mimes->formats()) {
+ if (mimetype.contains("text/"))
+ continue;
+ QByteArray ba = _mimes->data(mimetype);
+ if (!ba.isEmpty())
+ event["clipboardData"].call<void>("setData", QWasmString::fromQString(mimetype)
+ , val(ba.constData()));
+ }
+
+ event.call<void>("preventDefault");
+ QWasmIntegration::get()->getWasmClipboard()->m_isListener = false;
}
static void qClipboardCutTo(val event)
{
+ QWasmIntegration::get()->getWasmClipboard()->m_isListener = true;
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
- }
- event["clipboardData"].call<void>("setData", getClipboardFormat(), getClipboardData());
- event.call<void>("preventDefault");
+ QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
+ 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "X");
+ }
+
+ commonCopyEvent(event);
}
static void qClipboardCopyTo(val event)
{
+ QWasmIntegration::get()->getWasmClipboard()->m_isListener = true;
+
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
// Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
+ QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
+ 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
}
- event["clipboardData"].call<void>("setData", getClipboardFormat(), getClipboardData());
- event.call<void>("preventDefault");
+ commonCopyEvent(event);
}
-static void qClipboardPasteTo(val event)
+static void qClipboardPasteTo(val dataTransfer)
{
- bool hasClipboardApi = QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi;
- val clipdata = hasClipboardApi ? getClipboardData() :
- event["clipboardData"].call<val>("getData", val("text"));
+ QWasmIntegration::get()->getWasmClipboard()->m_isListener = true;
+ val clipboardData = dataTransfer["clipboardData"];
+ val types = clipboardData["types"];
+ int typesCount = types["length"].as<int>();
+ std::string stdMimeFormat;
+ QMimeData *mMimeData = new QMimeData;
+ for (int i = 0; i < typesCount; i++) {
+ stdMimeFormat = types[i].as<std::string>();
+ QString mimeFormat = QString::fromStdString(stdMimeFormat);
+ if (mimeFormat.contains("STRING", Qt::CaseSensitive) || mimeFormat.contains("TEXT", Qt::CaseSensitive))
+ continue;
- const QString qstr = QWasmString::toQString(clipdata);
- if (qstr.length() > 0) {
- QMimeData *mMimeData = new QMimeData;
- mMimeData->setText(qstr);
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
+ if (mimeFormat.contains("text")) {
+// also "text/plain;charset=utf-8"
+// "UTF8_STRING" "MULTIPLE"
+ val mimeData = clipboardData.call<val>("getData", val(stdMimeFormat)); // as DataTransfer
+
+ const QString qstr = QWasmString::toQString(mimeData);
+
+ if (qstr.length() > 0) {
+ if (mimeFormat.contains("text/html")) {
+ mMimeData->setHtml(qstr);
+ } else if (mimeFormat.isEmpty() || mimeFormat.contains("text/plain")) {
+ mMimeData->setText(qstr); // the type can be empty
+ } else {
+ mMimeData->setData(mimeFormat, qstr.toLocal8Bit());}
+ }
+ } else {
+ val items = clipboardData["items"];
+
+ int itemsCount = items["length"].as<int>();
+ // handle data
+ for (int i = 0; i < itemsCount; i++) {
+ val item = items[i];
+ val clipboardFile = item.call<emscripten::val>("getAsFile"); // string kind is handled above
+ if (clipboardFile.isUndefined() || item["kind"].as<std::string>() == "string" ) {
+ continue;
+ }
+ qstdweb::File file(clipboardFile);
+
+ mimeFormat = QString::fromStdString(file.type());
+ QByteArray fileContent;
+ fileContent.resize(file.size());
+
+ file.stream(fileContent.data(), [=]() {
+ if (!fileContent.isEmpty()) {
+
+ if (mimeFormat.contains("image")) {
+ QImage image;
+ image.loadFromData(fileContent, nullptr);
+ mMimeData->setImageData(image);
+ } else {
+ mMimeData->setData(mimeFormat,fileContent.data());
+ }
+ QWasmClipboard::qWasmClipboardPaste(mMimeData);
+ }
+ });
+ } // next item
+ }
}
+ QWasmClipboard::qWasmClipboardPaste(mMimeData);
+ QWasmIntegration::get()->getWasmClipboard()->m_isListener = false;
+}
+
+static void qClipboardTextPromiseResolve(emscripten::val clipdata)
+{
+ pasteClipboardData(emscripten::val("text/plain"), clipdata);
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
+ function("qtPasteClipboardData", &pasteClipboardData);
+
+ function("qtClipboardTextPromiseResolve", &qClipboardTextPromiseResolve);
function("qtClipboardPromiseResolve", &qClipboardPromiseResolve);
+
+ function("qtClipboardCopyPromiseResolve", &qClipboardCopyPromiseResolve);
+ function("qtClipboardPromiseException", &qClipboardPromiseException);
+
function("qtClipboardCutTo", &qClipboardCutTo);
function("qtClipboardCopyTo", &qClipboardCopyTo);
function("qtClipboardPasteTo", &qClipboardPasteTo);
+ function("qtClipboardPasteResolve", &qClipboardPasteResolve);
}
-QWasmClipboard::QWasmClipboard()
+QWasmClipboard::QWasmClipboard() :
+ isPaste(false),
+ m_isListener(false)
{
val clipboard = val::global("navigator")["clipboard"];
val permissions = val::global("navigator")["permissions"];
- hasClipboardApi = (!clipboard.isUndefined() && !permissions.isUndefined() && !clipboard["readText"].isUndefined());
- if (hasClipboardApi)
- initClipboardEvents();
+ val hasInstallTrigger = val::global("window")["InstallTrigger"];
+
+ hasPermissionsApi = !permissions.isUndefined();
+ hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined());
+ bool isFirefox = !hasInstallTrigger.isUndefined();
+ isSafari = !emscripten::val::global("window")["safari"].isUndefined();
+
+ // firefox has clipboard API if user sets these config tweaks:
+ // dom.events.asyncClipboard.clipboardItem true
+ // dom.events.asyncClipboard.read true
+ // dom.events.testing.asyncClipboard
+ // and permissions API, but does not currently support
+ // the clipboardRead and clipboardWrite permissions
+ if (hasClipboardApi && hasPermissionsApi && !isFirefox)
+ initClipboardPermissions();
}
QWasmClipboard::~QWasmClipboard()
{
- g_clipboardText.clear();
- g_clipboardFormat.clear();
}
-QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode)
+QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
{
if (mode != QClipboard::Clipboard)
return nullptr;
@@ -134,17 +294,18 @@ QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode)
return QPlatformClipboard::mimeData(mode);
}
-void QWasmClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
+void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
{
- if (mimeData->hasText()) {
- g_clipboardFormat = mimeData->formats().at(0);
- g_clipboardText = mimeData->text();
- } else if (mimeData->hasHtml()) {
- g_clipboardFormat = mimeData->formats().at(0);
- g_clipboardText = mimeData->html();
- }
-
QPlatformClipboard::setMimeData(mimeData, mode);
+ // handle setText/ setData programmatically
+ if (!isPaste) {
+ if (hasClipboardApi) {
+ writeToClipboardApi();
+ } else if (!m_isListener) {
+ writeToClipboard(mimeData);
+ }
+ }
+ isPaste = false;
}
bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
@@ -163,10 +324,10 @@ void QWasmClipboard::qWasmClipboardPaste(QMimeData *mData)
QWasmIntegration::get()->clipboard()->setMimeData(mData, QClipboard::Clipboard);
QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V");
+ 0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V");
}
-void QWasmClipboard::initClipboardEvents()
+void QWasmClipboard::initClipboardPermissions()
{
if (!hasClipboardApi)
return;
@@ -183,32 +344,121 @@ void QWasmClipboard::initClipboardEvents()
void QWasmClipboard::installEventHandlers(const emscripten::val &canvas)
{
- if (hasClipboardApi)
- return;
-
+ emscripten::val cContext = val::undefined();
+ emscripten::val isChromium = val::global("window")["chrome"];
+ if (!isChromium.isUndefined()) {
+ cContext = val::global("document");
+ } else {
+ cContext = canvas;
+ }
// Fallback path for browsers which do not support direct clipboard access
- canvas.call<void>("addEventListener", val("cut"),
- val::module_property("qtClipboardCutTo"));
- canvas.call<void>("addEventListener", val("copy"),
- val::module_property("qtClipboardCopyTo"));
- canvas.call<void>("addEventListener", val("paste"),
- val::module_property("qtClipboardPasteTo"));
+ cContext.call<void>("addEventListener", val("cut"),
+ val::module_property("qtClipboardCutTo"), true);
+ cContext.call<void>("addEventListener", val("copy"),
+ val::module_property("qtClipboardCopyTo"), true);
+ cContext.call<void>("addEventListener", val("paste"),
+ val::module_property("qtClipboardPasteTo"), true);
}
-void QWasmClipboard::readTextFromClipboard()
+void QWasmClipboard::writeToClipboardApi()
{
- if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
- val navigator = val::global("navigator");
- val textPromise = navigator["clipboard"].call<val>("readText");
- val readTextResolve = val::module_property("qtClipboardPromiseResolve");
- textPromise.call<val>("then", readTextResolve);
+ if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi)
+ return;
+
+ // copy event
+ // browser event handler detected ctrl c if clipboard API
+ // or Qt call from keyboard event handler
+
+ QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
+ if (!_mimes)
+ return;
+
+ emscripten::val clipboardWriteArray = emscripten::val::array();
+ QByteArray ba;
+
+ for (auto mimetype : _mimes->formats()) {
+ // we need to treat binary and text differently, as the blob method below
+ // fails for text mimetypes
+ // ignore text types
+
+ if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive))
+ continue;
+
+ if (_mimes->hasHtml()) { // prefer html over text
+ ba = _mimes->html().toLocal8Bit();
+ // force this mime
+ mimetype = "text/html";
+ } else if (mimetype.contains("text/plain")) {
+ ba = _mimes->text().toLocal8Bit();
+ } else if (mimetype.contains("image")) {
+ QImage img = qvariant_cast<QImage>( _mimes->imageData());
+ QBuffer buffer(&ba);
+ buffer.open(QIODevice::WriteOnly);
+ img.save(&buffer, "PNG");
+ mimetype = "image/png"; // chrome only allows png
+ // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."
+ // safari silently fails
+ // so we use png internally for now
+ } else {
+ // DATA
+ ba = _mimes->data(mimetype);
+ }
+ // Create file data Blob
+
+ const char *content = ba.data();
+ int dataLength = ba.length();
+ if (dataLength < 1) {
+ qDebug() << "no content found";
+ return;
+ }
+
+ emscripten::val document = emscripten::val::global("document");
+ emscripten::val window = emscripten::val::global("window");
+
+ emscripten::val fileContentView =
+ emscripten::val(emscripten::typed_memory_view(dataLength, content));
+ emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength);
+ emscripten::val fileContentCopyView =
+ emscripten::val::global("Uint8Array").new_(fileContentCopy);
+ fileContentCopyView.call<void>("set", fileContentView);
+
+ emscripten::val contentArray = emscripten::val::array();
+ contentArray.call<void>("push", fileContentCopyView);
+
+ // we have a blob, now create a ClipboardItem
+ emscripten::val type = emscripten::val::array();
+ type.set("type", val(QWasmString::fromQString(mimetype)));
+
+ emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
+
+ emscripten::val clipboardItemObject = emscripten::val::object();
+ clipboardItemObject.set(val(QWasmString::fromQString(mimetype)), contentBlob);
+
+ val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
+
+ clipboardWriteArray.call<void>("push", clipboardItemData);
+
+ // Clipboard write is only supported with one ClipboardItem at the moment
+ // but somehow this still works?
+ // break;
}
+
+ val copyResolve = emscripten::val::module_property("qtClipboardCopyPromiseResolve");
+ val copyException = emscripten::val::module_property("qtClipboardPromiseException");
+
+ val navigator = val::global("navigator");
+ navigator["clipboard"]
+ .call<val>("write", clipboardWriteArray)
+ .call<val>("then", copyResolve)
+ .call<val>("catch", copyException);
}
-void QWasmClipboard::writeTextToClipboard()
+void QWasmClipboard::writeToClipboard(const QMimeData *data)
{
- if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
- val navigator = val::global("navigator");
- navigator["clipboard"].call<void>("writeText", getClipboardData());
- }
+ // this works for firefox, chrome by generating
+ // copy event, but not safari
+ // execCommand has been deemed deprecated in the docs, but browsers do not seem
+ // interested in removing it. There is no replacement, so we use it here.
+ val document = val::global("document");
+ document.call<val>("execCommand", val("copy"));
}
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.h b/src/plugins/platforms/wasm/qwasmclipboard.h
index 3b28e2c381..9a33b79667 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.h
+++ b/src/plugins/platforms/wasm/qwasmclipboard.h
@@ -51,11 +51,15 @@ public:
bool ownsMode(QClipboard::Mode mode) const override;
static void qWasmClipboardPaste(QMimeData *mData);
- void initClipboardEvents();
+ void initClipboardPermissions();
void installEventHandlers(const emscripten::val &canvas);
bool hasClipboardApi;
- void readTextFromClipboard();
- void writeTextToClipboard();
+ bool hasPermissionsApi;
+ void writeToClipboardApi();
+ void writeToClipboard(const QMimeData *data);
+ bool isPaste;
+ bool m_isListener;
+ bool isSafari;
};
#endif // QWASMCLIPBOARD_H
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
index 5f809140f5..6d4ead60d5 100644
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
+++ b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
@@ -845,7 +845,10 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
// handlers if direct clipboard access is not available.
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi && modifiers & Qt::ControlModifier &&
(qtKey == Qt::Key_X || qtKey == Qt::Key_C || qtKey == Qt::Key_V)) {
- return 0;
+ if (qtKey == Qt::Key_V) {
+ QWasmIntegration::get()->getWasmClipboard()->isPaste = true;
+ }
+ return false;
}
bool accepted = false;
@@ -853,7 +856,8 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
if (keyType == QEvent::KeyPress &&
mods.testFlag(Qt::ControlModifier)
&& qtKey == Qt::Key_V) {
- QWasmIntegration::get()->getWasmClipboard()->readTextFromClipboard();
+ QWasmIntegration::get()->getWasmClipboard()->isPaste = true;
+ accepted = false; // continue on to event
} else {
if (keyText.isEmpty())
keyText = QString(keyEvent->key);
@@ -865,7 +869,8 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
if (keyType == QEvent::KeyPress &&
mods.testFlag(Qt::ControlModifier)
&& qtKey == Qt::Key_C) {
- QWasmIntegration::get()->getWasmClipboard()->writeTextToClipboard();
+ QWasmIntegration::get()->getWasmClipboard()->isPaste = false;
+ accepted = false; // continue on to event
}
QWasmEventDispatcher::maintainTimers();
diff --git a/tests/manual/wasm/CMakeLists.txt b/tests/manual/wasm/CMakeLists.txt
index cd594e7112..dd0d816a5d 100644
--- a/tests/manual/wasm/CMakeLists.txt
+++ b/tests/manual/wasm/CMakeLists.txt
@@ -2,4 +2,5 @@ add_subdirectory(eventloop)
if(QT_FEATURE_widgets)
add_subdirectory(cursors)
add_subdirectory(localfiles)
+add_subdirectory(clipboard)
endif()
diff --git a/tests/manual/wasm/clipboard/CMakeLists.txt b/tests/manual/wasm/clipboard/CMakeLists.txt
new file mode 100644
index 0000000000..4bc60a5edc
--- /dev/null
+++ b/tests/manual/wasm/clipboard/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Generated from clipboard.pro.
+
+#####################################################################
+## clipboard Binary:
+#####################################################################
+
+qt_internal_add_manual_test(clipboard
+ GUI
+ SOURCES
+ main.cpp
+ mainwindow.cpp mainwindow.h mainwindow.ui
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+ ENABLE_AUTOGEN_TOOLS
+ uic
+)
+# Resources:
+set(data_resource_files
+ "data/qticon64.png"
+)
+
+qt_internal_add_resource(clipboard "data"
+ PREFIX
+ "/"
+ FILES
+ ${data_resource_files}
+)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(clipboard CONDITION (QT_MAJOR_VERSION GREATER 4)
+ PUBLIC_LIBRARIES
+ Qt::Widgets
+)
+
+#### Keys ignored in scope 3:.:.:clipboard.pro:QNX:
+# target.path = "/tmp/$${TARGET}/bin"
+
+#### Keys ignored in scope 5:.:.:clipboard.pro:UNIX AND NOT ANDROID:
+# target.path = "/opt/$${TARGET}/bin"
+
+#### Keys ignored in scope 6:.:.:clipboard.pro:NOT target.path_ISEMPTY:
+# INSTALLS = "target"
diff --git a/tests/manual/wasm/clipboard/README b/tests/manual/wasm/clipboard/README
new file mode 100644
index 0000000000..91529696ca
--- /dev/null
+++ b/tests/manual/wasm/clipboard/README
@@ -0,0 +1,2 @@
+The Clipboard manual test app can be used both on desktop and in the browser
+using WebAssembly to test clipboard use between WebAssembly app and the desktop.
diff --git a/tests/manual/wasm/clipboard/clipboard.pro b/tests/manual/wasm/clipboard/clipboard.pro
new file mode 100644
index 0000000000..3286049225
--- /dev/null
+++ b/tests/manual/wasm/clipboard/clipboard.pro
@@ -0,0 +1,27 @@
+QT += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++11
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ main.cpp \
+ mainwindow.cpp
+
+HEADERS += \
+ mainwindow.h
+
+FORMS += \
+ mainwindow.ui
+
+RESOURCES += \
+ data.qrc
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
diff --git a/tests/manual/wasm/clipboard/data.qrc b/tests/manual/wasm/clipboard/data.qrc
new file mode 100644
index 0000000000..c0f33f25be
--- /dev/null
+++ b/tests/manual/wasm/clipboard/data.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>data/qticon64.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/wasm/clipboard/data/qticon64.png b/tests/manual/wasm/clipboard/data/qticon64.png
new file mode 100644
index 0000000000..76f02c6c96
--- /dev/null
+++ b/tests/manual/wasm/clipboard/data/qticon64.png
Binary files differ
diff --git a/tests/manual/wasm/clipboard/main.cpp b/tests/manual/wasm/clipboard/main.cpp
new file mode 100644
index 0000000000..cdffd01766
--- /dev/null
+++ b/tests/manual/wasm/clipboard/main.cpp
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/tests/manual/wasm/clipboard/mainwindow.cpp b/tests/manual/wasm/clipboard/mainwindow.cpp
new file mode 100644
index 0000000000..2b8a162c2d
--- /dev/null
+++ b/tests/manual/wasm/clipboard/mainwindow.cpp
@@ -0,0 +1,287 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <QClipboard>
+#include <QMimeData>
+#include <QImageReader>
+#include <QBuffer>
+#include <QRandomGenerator>
+#include <QPainter>
+#include <QKeyEvent>
+
+#ifdef Q_OS_WASM
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <emscripten/val.h>
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+#endif
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ ui->imageLabel->installEventFilter(this);
+
+ ui->imageLabel->setBackgroundRole(QPalette::Base);
+ ui->imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
+ ui->imageLabel->setScaledContents(true);
+
+ clipboard = QGuiApplication::clipboard();
+ connect(
+ clipboard, &QClipboard::dataChanged,
+ [=]() {
+ ui->textEdit_2->insertHtml("<b>Clipboard data changed:</b><br>");
+ const QMimeData *mimeData = clipboard->mimeData();
+ QByteArray ba;
+
+ for (auto mimetype : mimeData->formats()) {
+ qDebug() << Q_FUNC_INFO << mimetype;
+ ba = mimeData->data(mimetype);
+ }
+ QString sizeStr;
+
+ if (mimeData->hasImage()) {
+ qsizetype imageSize = qvariant_cast<QImage>(mimeData->imageData()).sizeInBytes();
+ sizeStr.setNum(imageSize);
+ ui->textEdit_2->insertHtml("has Image data: " + sizeStr + "<br>");
+ }
+
+ if (mimeData->hasHtml()) {
+ int size = mimeData->html().length();
+ sizeStr.setNum(size);
+ ui->textEdit_2->insertHtml("has html data: " + sizeStr + "<br>");
+ }
+ if (mimeData->hasText()) {
+ int size = mimeData->text().length();
+ sizeStr.setNum(size);
+ ui->textEdit_2->insertHtml("has text data: " + sizeStr + "<br>");
+ }
+
+ ui->textEdit_2->insertHtml(mimeData->formats().join(" | ")+ "<br>");
+
+ ui->textEdit_2->ensureCursorVisible();
+
+ const QString message = tr("Clipboard changed, %1 ")
+ .arg(mimeData->formats().join(' '));
+
+ statusBar()->showMessage(message + sizeStr);
+ }
+ );
+#ifdef Q_OS_WASM
+ val clipboard = val::global("navigator")["clipboard"];
+ bool hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined());
+ QString messageApi;
+ if (hasClipboardApi)
+ messageApi = QStringLiteral("Using Clipboard API");
+ else
+ messageApi = QStringLiteral("Using Clipboard events");
+ ui->label->setText(messageApi);
+#else
+ ui->label->setText("desktop clipboard");
+#endif
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::on_setTextButton_clicked()
+{
+ QGuiApplication::clipboard()->setText(ui->textEdit->textCursor().selectedText());
+}
+
+static QImage clipboardImage()
+{
+ if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData()) {
+ if (mimeData->hasImage()) {
+ const QImage image = qvariant_cast<QImage>(mimeData->imageData());
+ if (!image.isNull())
+ return image;
+ }
+ }
+ return QImage();
+}
+
+static QByteArray clipboardBinary()
+{
+ if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData()) {
+
+ if (mimeData->formats().contains("application/octet-stream")) {
+ const QByteArray ba = qvariant_cast<QByteArray>(mimeData->data("application/octet-stream"));
+ qDebug() << Q_FUNC_INFO << ba;
+ if (!ba.isNull())
+ return ba;
+ }
+ }
+ return QByteArray();
+}
+
+void MainWindow::on_pasteImageButton_clicked()
+{
+ const QImage newImage = clipboardImage();
+ if (newImage.isNull()) {
+ qDebug() << "No image in clipboard";
+ const QString message = tr("No image in clipboard")
+ .arg(newImage.width()).arg(newImage.height()).arg(newImage.depth());
+ statusBar()->showMessage(message);
+ } else {
+ setImage(newImage);
+ setWindowFilePath(QString());
+ const QString message = tr("Obtained image from clipboard, %1x%2, Depth: %3")
+ .arg(newImage.width()).arg(newImage.height()).arg(newImage.depth());
+ statusBar()->showMessage(message);
+ }
+}
+
+void MainWindow::setImage(const QImage &newImage)
+{
+ image = newImage;
+ ui->imageLabel->setPixmap(QPixmap::fromImage(image));
+}
+
+void MainWindow::on_pasteTextButton_clicked()
+{
+ ui->textEdit->insertPlainText(QGuiApplication::clipboard()->text());
+}
+
+void MainWindow::on_copyBinaryButton_clicked()
+{
+ QByteArray ba;
+ ba.resize(10);
+ ba[0] = 0x3c;
+ ba[1] = 0xb8;
+ ba[2] = 0x64;
+ ba[3] = 0x18;
+ ba[4] = 0xca;
+ ba[5] = 0xca;
+ ba[6] = 0x18;
+ ba[7] = 0x64;
+ ba[8] = 0xb8;
+ ba[9] = 0x3c;
+
+ QMimeData *mimeData = new QMimeData();
+ mimeData->setData("application/octet-stream", ba);
+ QGuiApplication::clipboard()->setMimeData(mimeData);
+
+ const QString message = tr("Copied binary to clipboard: " + ba + " 10 bytes");
+ statusBar()->showMessage(message);
+}
+
+void MainWindow::on_pasteBinaryButton_clicked()
+{
+ const QByteArray ba = clipboardBinary();
+ if (ba.isNull()) {
+ qDebug() << "No binary in clipboard";
+ const QString message = tr("No binary in clipboard");
+ statusBar()->showMessage(message);
+ } else {
+ setWindowFilePath(QString());
+ const QString message = tr("Obtained binary from clipboard: " + ba);
+ statusBar()->showMessage(message);
+ }
+}
+
+void MainWindow::on_comboBox_textActivated(const QString &arg1)
+{
+ QImage image(QSize(150,100), QImage::Format_RGB32);
+ QPainter painter(&image);
+ painter.fillRect(QRectF(0,0,150,100),generateRandomColor());
+ painter.fillRect(QRectF(20,30,130,40),generateRandomColor());
+ painter.setPen(QPen(generateRandomColor()));
+ painter.drawText(QRect(25,30,130,40),"Qt WebAssembly");
+
+ QByteArray ba;
+ QBuffer buffer(&ba);
+ buffer.open(QIODevice::WriteOnly);
+ image.save(&buffer, arg1.toLocal8Bit());
+
+ qDebug() << ba.mid(0,10) << ba.length();
+ qDebug() << Q_FUNC_INFO << image.sizeInBytes();
+
+ QGuiApplication::clipboard()->setImage(image);
+}
+
+QColor MainWindow::generateRandomColor()
+{
+ return QColor::fromRgb(QRandomGenerator::global()->generate());
+}
+
+bool MainWindow::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(event);
+ if (ke->key() == Qt::Key_V && ke->modifiers().testFlag(Qt::ControlModifier)) {
+ if (obj == ui->imageLabel) {
+ setImage(clipboardImage());
+ return true;
+ }
+ }
+ }
+ // standard event processing
+ return QObject::eventFilter(obj, event);
+}
+
+void MainWindow::on_pasteHtmlButton_clicked()
+{
+ ui->textEdit->insertHtml(QGuiApplication::clipboard()->mimeData()->html());
+}
+
+void MainWindow::on_clearButton_clicked()
+{
+ ui->textEdit_2->clear();
+}
+
diff --git a/tests/manual/wasm/clipboard/mainwindow.h b/tests/manual/wasm/clipboard/mainwindow.h
new file mode 100644
index 0000000000..69e21ba8a2
--- /dev/null
+++ b/tests/manual/wasm/clipboard/mainwindow.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class MainWindow; }
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+private slots:
+ void on_setTextButton_clicked();
+
+ void on_pasteImageButton_clicked();
+ void setImage(const QImage &newImage);
+ void on_pasteTextButton_clicked();
+
+
+ void on_copyBinaryButton_clicked();
+
+ void on_pasteBinaryButton_clicked();
+
+ void on_comboBox_textActivated(const QString &arg1);
+
+ void on_pasteHtmlButton_clicked();
+
+ void on_clearButton_clicked();
+
+private:
+ Ui::MainWindow *ui;
+ QImage image;
+ QClipboard *clipboard;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+ QColor generateRandomColor();
+};
+#endif // MAINWINDOW_H
diff --git a/tests/manual/wasm/clipboard/mainwindow.ui b/tests/manual/wasm/clipboard/mainwindow.ui
new file mode 100644
index 0000000000..84606598cc
--- /dev/null
+++ b/tests/manual/wasm/clipboard/mainwindow.ui
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1222</width>
+ <height>1011</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="setTextButton">
+ <property name="text">
+ <string>setText()</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pasteTextButton">
+ <property name="text">
+ <string>paste text</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pasteHtmlButton">
+ <property name="text">
+ <string>paste html</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="comboBox">
+ <property name="currentText">
+ <string>PNG</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>PNG</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>JPG</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>BMP</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NAN</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pasteImageButton">
+ <property name="text">
+ <string>paste image</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QPushButton" name="copyBinaryButton">
+ <property name="text">
+ <string>setData</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pasteBinaryButton">
+ <property name="text">
+ <string>paste data</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPushButton" name="clearButton">
+ <property name="text">
+ <string>clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextEdit" name="textEdit_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0">
+ <widget class="QTextBrowser" name="textEdit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>400</height>
+ </size>
+ </property>
+ <property name="readOnly">
+ <bool>false</bool>
+ </property>
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/data/qticon64.png&quot; /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;a name=&quot;tw-target&quot;&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'monospace'; font-weight:600;&quot;&gt;L&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-weight:600;&quot;&gt;orem&lt;/span&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-style:italic;&quot;&gt;ipsum&lt;/span&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; text-decoration: underline;&quot;&gt;dolor&lt;/span&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; vertical-align:super;&quot;&gt;sit&lt;/span&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; vertical-align:sub;&quot;&gt;amet&lt;/span&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt;, &lt;/span&gt;&lt;a href=&quot;http://localhost&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;consectetur&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; color:#7320a4;&quot;&gt;adipiscing&lt;/span&gt;&lt;span style=&quot; font-family:'monospace';&quot;&gt; elit. Som medlemmer av byrået ønsker imidlertid en eiendomsmegler. Ullamcorper største lekseforfatter. Dolor et consectetuer litt ernæring. Maecenas smile jord sitter Vulputate medlemmer og, basketball ethvert problem. Reservert lever nå propaganda. På makroen investere laoreet kan, av enhver latter. Jasmine som en TV -tegneserie.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'monospace';&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="imageLabel">
+ <property name="mouseTracking">
+ <bool>true</bool>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <property name="text">
+ <string>Paste image here</string>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1222</width>
+ <height>29</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>