Merge lp:~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus into lp:ubuntu-ui-toolkit/staging

Proposed by Arthur Mello on 2017-01-04
Status: Merged
Approved by: Christian Dywan on 2017-01-26
Approved revision: 2178
Merged at revision: 2164
Proposed branch: lp:~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 842 lines (+675/-6)
14 files modified
src/UbuntuToolkit/UbuntuToolkit.pro (+2/-0)
src/UbuntuToolkit/privates/uccontenthub.cpp (+218/-0)
src/UbuntuToolkit/privates/uccontenthub_p.h (+68/-0)
src/UbuntuToolkit/ubuntutoolkitmodule.cpp (+3/-0)
src/imports/Components/1.3/TextArea.qml (+15/-0)
src/imports/Components/1.3/TextField.qml (+23/-1)
src/imports/Components/1.3/TextInputPopover.qml (+4/-3)
tests/license/checklicense.sh (+1/-1)
tests/unit/contenthub/TextAreaPaste.qml (+32/-0)
tests/unit/contenthub/TextFieldPaste.qml (+31/-0)
tests/unit/contenthub/com.ubuntu.content.MockService.xml (+17/-0)
tests/unit/contenthub/contenthub.pro (+5/-0)
tests/unit/contenthub/tst_contenthub.cpp (+254/-0)
tests/unit/unit.pro (+2/-1)
To merge this branch: bzr merge lp:~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus
Reviewer Review Type Date Requested Status
ubuntu-sdk-build-bot continuous-integration Approve on 2017-01-26
Christian Dywan Approve on 2017-01-26
Zsombor Egri (community) 2017-01-04 Needs Fixing on 2017-01-12
Review via email: mp+314072@code.launchpad.net

Commit Message

Add support for interacting with Content Hub Clipboard UI via DBus calls

Description of the Change

Add support for interacting with Content Hub Clipboard UI via DBus calls

To post a comment you must log in.
2154. By Arthur Mello on 2017-01-06

Disable check for activeFocus by now

Zsombor Egri (zsombi) wrote :

I would love to see some test cases to guard this :)

review: Needs Fixing
2155. By Arthur Mello on 2017-01-12

Respect user decision on Clipboard UI to paste data as Plain Text or Rich Text

2156. By Arthur Mello on 2017-01-16

Make sure ImageData is pasted to TextField/TextArea

2157. By Arthur Mello on 2017-01-17

Add unit tests for the UCContentHub class

Christian Dywan (kalikiana) wrote :

> // TODO: In the future check if Paste Formats list has an interesting format

Do/ can we have a bug report for this? The word "interesting" is not all that descriptive and I imagine it might warrant some discussion leading to a follow-up merge request.

> #define CONTHUB_TRACE(params) qCDebug(ucContentHub) << params

If you don't mind my being a little nit-picky here, please make that CONTENT_HUB_TRACE.

> qDebug() << "[UITK ContentHub] AppArmor profile:" << appProfile;

This should be using CONTHUB_TRACE as well.

> target: Private.UCContentHub
> onPasteSelected: {
> //FIXME if (control.activeFocus) {

What's with this?

> if (Private.UCContentHub.canPaste) {

Wondering, is this "the" way any component would be implementing pasting? If so, perhaps make it Labs rather than Private, so it can be implemented by third party components.
Although I'm happy for it to be a FIXME with a bug number.

review: Needs Fixing
2158. By Arthur Mello on 2017-01-17

Improve trace define naming
Improve TODO message and add bug number for further discussion
Make all log messages to use trace output

Arthur Mello (artmello) wrote :

Thx for reviewing. Comments bellow

> > // TODO: In the future check if Paste Formats list has an interesting
> format
>
> Do/ can we have a bug report for this? The word "interesting" is not all that
> descriptive and I imagine it might warrant some discussion leading to a
> follow-up merge request.

As suggested LP #1657111 was created to trace this. Point is that ContentHub keeps trace of all available pastes data types and UITK Components could use that to decide what paste datas would be displayed for users. But how that would work (and if it is really something could to have) needs further discussion.

> > #define CONTHUB_TRACE(params) qCDebug(ucContentHub) << params
>
> If you don't mind my being a little nit-picky here, please make that
> CONTENT_HUB_TRACE.

fixed

> > qDebug() << "[UITK ContentHub] AppArmor profile:" << appProfile;
>
> This should be using CONTHUB_TRACE as well.

fixed

> > target: Private.UCContentHub
> > onPasteSelected: {
> > //FIXME if (control.activeFocus) {
>
> What's with this?

That is an issue with current implementation. ContentHub pasteboard keeps track of the link between launched trust sessions and applications via appId. So whenever a paste is selected from a trust session, app is notified but all available TextField/TextArea get data pasted on it. For example, if we paste on messaging-app TextArea the destination TextField get same data pasted. I was trying to keep track of which component requested the data with looking for activeFocus but this does not seem to work. Suggestions on how to properly do that?

> > if (Private.UCContentHub.canPaste) {
>
> Wondering, is this "the" way any component would be implementing pasting? If
> so, perhaps make it Labs rather than Private, so it can be implemented by
> third party components.
> Although I'm happy for it to be a FIXME with a bug number.

I have no strong opinion about it. I keep it as Private since there are (or at least we need to have) some discussion going on about integration of this ContentHub clipboard UI with other components (like where/how to paste an image or a pdf file) and with apps that do not use UITK for example. I imagine things here can still change in near future. But we can totally make it Lab if you guys prefer.

Christian Dywan (kalikiana) wrote :

> I was trying to keep track of which component requested
> the data with looking for activeFocus but this does
> not seem to work. Suggestions on how to properly do that?

window.activeFocusItem might be what you're looking for? Or maybe
what you need is to check the activeFocus of the editor (instead of control)

> I imagine things here can still change in near future.
> But we can totally make it Lab if you guys prefer.

Labs is meant exactly for that: a feature that's experimental, which
may still change, before it becomes stable API. So anyone can play
with it, but without using it in a stable version just yet.

As I said, alternatively I'm also fine with leaving it for now and
filing a bug report to discuss and expose the API.

Arthur Mello (artmello) wrote :

> > I was trying to keep track of which component requested
> > the data with looking for activeFocus but this does
> > not seem to work. Suggestions on how to properly do that?
>
> window.activeFocusItem might be what you're looking for? Or maybe
> what you need is to check the activeFocus of the editor (instead of control)

Tried both suggestions but still not working. It seems that when we return from Clipboard trust session neither component has activeFocus http://paste.ubuntu.com/23816722/. Probably we should keep track of the Component that requested paste before launching trust session.

> As I said, alternatively I'm also fine with leaving it for now and
> filing a bug report to discuss and expose the API.

https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1657164 created to keep track of this

2159. By Arthur Mello on 2017-01-17

Keep track of which QQuickItem was the target for a paste command.
It seems that when returning from the trust session activeFocus is wrong

2160. By Arthur Mello on 2017-01-17

Change unit tests to fix SIGSEGV received on CI

2161. By Arthur Mello on 2017-01-17

Fix include declaration in UCContentHub to fix build issue on i386/arm64

2162. By Arthur Mello on 2017-01-17

Increase signal wait timeout from 1 to 5 seconds

2163. By Arthur Mello on 2017-01-18

Register QQuickItem* as a meta type to remove unit test fatal warning

Christian Dywan (kalikiana) wrote :

> Private.UCContentHub.requestPaste();

You forgot to add the argument to the key events of the Text* components.

Perhaps the unit test should cover that.

review: Needs Fixing
2164. By Arthur Mello on 2017-01-18

Update requestPaste call when using keyboard shortcut

2165. By Arthur Mello on 2017-01-24

Add a mock Content Service to be used by ContentHub tests

2166. By Arthur Mello on 2017-01-24

Improve unittests for ContentHub clipboard

2167. By Arthur Mello on 2017-01-24

Add Qt Company as an expected license due to self generated files on the ContentHub clipboard unittest

Christian Dywan (kalikiana) wrote :

> Component.onCompleted: textField.forceActiveFocus()

Hmmm I might've expected you to do this from the unit test (eg. QTest::keyClick(textField->window(), Qt::Key_Tab);)

And, too, you'll want to check that focus indeed there to avoid false negatives:

QTRY_COMPARE_WITH_TIMEOUT(textField->property("activeFocus").toBool(), true, 1000);

Is handling the base component good enough? If not, you might need to access the internal editor via its objectName text_input.

review: Needs Information
2168. By Arthur Mello on 2017-01-24

Merge with trunk

2169. By Arthur Mello on 2017-01-24

Move some checks about activeFocus to cpp part of contenthub test

2170. By Arthur Mello on 2017-01-24

Skip ContentHub tests that are failing on CI with OpenGL errors

2171. By Arthur Mello on 2017-01-25

Reenable Clipboard tests adding X11 support for it

2172. By Arthur Mello on 2017-01-25

Use same test timeout on all tests

2173. By Arthur Mello on 2017-01-25

Change focus check on ContentHub test

2174. By Arthur Mello on 2017-01-25

Skip failing tests

2175. By Arthur Mello on 2017-01-25

Fix property name typo

review: Needs Fixing (continuous-integration)
2176. By Arthur Mello on 2017-01-25

Do not skip tests anymore

2177. By Arthur Mello on 2017-01-26

Remove deprecated code

2178. By Arthur Mello on 2017-01-26

Try to fix issues related with running clipboard tests

Christian Dywan (kalikiana) wrote :

Sweet! Looking much better. Let's try and get this in!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/UbuntuToolkit/UbuntuToolkit.pro'
2--- src/UbuntuToolkit/UbuntuToolkit.pro 2017-01-09 15:50:34 +0000
3+++ src/UbuntuToolkit/UbuntuToolkit.pro 2017-01-26 16:03:41 +0000
4@@ -52,6 +52,7 @@
5 $$PWD/privates/listviewextensions_p.h \
6 $$PWD/privates/splitviewhandler_p.h \
7 $$PWD/privates/threelabelsslot_p.h \
8+ $$PWD/privates/uccontenthub_p.h \
9 $$PWD/privates/ucpagewrapper_p.h \
10 $$PWD/privates/ucpagewrapper_p_p.h \
11 $$PWD/privates/ucpagewrapperincubator_p.h \
12@@ -165,6 +166,7 @@
13 $$PWD/privates/listviewextensions.cpp \
14 $$PWD/privates/splitviewhandler.cpp \
15 $$PWD/privates/threelabelsslot_p.cpp \
16+ $$PWD/privates/uccontenthub.cpp \
17 $$PWD/privates/ucpagewrapper.cpp \
18 $$PWD/privates/ucpagewrapperincubator.cpp \
19 $$PWD/privates/ucscrollbarutils.cpp \
20
21=== added file 'src/UbuntuToolkit/privates/uccontenthub.cpp'
22--- src/UbuntuToolkit/privates/uccontenthub.cpp 1970-01-01 00:00:00 +0000
23+++ src/UbuntuToolkit/privates/uccontenthub.cpp 2017-01-26 16:03:41 +0000
24@@ -0,0 +1,218 @@
25+/*
26+ * Copyright 2016 Canonical Ltd.
27+ *
28+ * This program is free software; you can redistribute it and/or modify
29+ * it under the terms of the GNU Lesser General Public License as published by
30+ * the Free Software Foundation; version 3.
31+ *
32+ * This program is distributed in the hope that it will be useful,
33+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
34+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35+ * GNU Lesser General Public License for more details.
36+ *
37+ * You should have received a copy of the GNU Lesser General Public License
38+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
39+ *
40+ * Author: Arthur Mello <arthur.mello@canonical.com>
41+ */
42+
43+#include "privates/uccontenthub_p.h"
44+
45+#include <QtCore/QLoggingCategory>
46+#include <QtCore/QMimeData>
47+#include <QtDBus/QDBusConnection>
48+#include <QtDBus/QDBusInterface>
49+#include <QtDBus/QDBusReply>
50+#include <QtQuick/QQuickItem>
51+
52+Q_LOGGING_CATEGORY(ucContentHub, "ubuntu.components.UCContentHub", QtMsgType::QtWarningMsg)
53+
54+#define CONTENT_HUB_TRACE(params) qCDebug(ucContentHub) << params
55+
56+static const QString contentHubService = QStringLiteral("com.ubuntu.content.dbus.Service");
57+static const QString contentHubObjectPath = QStringLiteral("/");
58+static const QString contentHubInterface = QStringLiteral("com.ubuntu.content.dbus.Service");
59+
60+static const QString dbusService = QStringLiteral("org.freedesktop.DBus");
61+static const QString dbusObjectPath = QStringLiteral("/org/freedesktop/DBus");
62+static const QString dbusInterface = QStringLiteral("org.freedesktop.DBus");
63+
64+UT_NAMESPACE_BEGIN
65+
66+UCContentHub::UCContentHub(QObject *parent)
67+ : QObject(parent),
68+ m_dbusIface(0),
69+ m_contentHubIface(0),
70+ m_canPaste(false),
71+ m_targetItem(0)
72+{
73+ m_dbusIface = new QDBusInterface(dbusService,
74+ dbusObjectPath,
75+ dbusInterface,
76+ QDBusConnection::sessionBus(),
77+ this);
78+
79+ m_contentHubIface = new QDBusInterface(contentHubService,
80+ contentHubObjectPath,
81+ contentHubInterface,
82+ QDBusConnection::sessionBus(),
83+ this);
84+ if (m_contentHubIface->isValid()) {
85+ m_contentHubIface->connection().connect(
86+ contentHubService,
87+ contentHubObjectPath,
88+ contentHubInterface,
89+ QStringLiteral("PasteSelected"),
90+ this,
91+ SLOT(onPasteSelected(QString, QByteArray, bool))
92+ );
93+
94+ m_contentHubIface->connection().connect(
95+ contentHubService,
96+ contentHubObjectPath,
97+ contentHubInterface,
98+ QStringLiteral("PasteboardChanged"),
99+ this,
100+ SLOT(onPasteboardChanged())
101+ );
102+
103+ m_canPaste = checkPasteFormats();
104+ }
105+}
106+
107+UCContentHub::~UCContentHub()
108+{
109+ if (m_dbusIface) {
110+ delete m_dbusIface;
111+ }
112+
113+ if (m_contentHubIface) {
114+ delete m_contentHubIface;
115+ }
116+}
117+
118+void UCContentHub::requestPaste(QQuickItem *targetItem)
119+{
120+ if (!m_contentHubIface->isValid()) {
121+ CONTENT_HUB_TRACE("Invalid Content Hub DBusInterface");
122+ return;
123+ }
124+
125+ m_targetItem = targetItem;
126+
127+ QString appProfile = getAppProfile();
128+ CONTENT_HUB_TRACE("AppArmor profile:" << appProfile);
129+
130+ m_contentHubIface->call(QStringLiteral("RequestPasteByAppId"), appProfile);
131+}
132+
133+bool UCContentHub::canPaste()
134+{
135+ return m_canPaste;
136+}
137+
138+void UCContentHub::onPasteSelected(QString appId, QByteArray mimedata, bool pasteAsRichText)
139+{
140+ if (getAppProfile() != appId) {
141+ return;
142+ }
143+
144+ if (mimedata.isNull()) {
145+ CONTENT_HUB_TRACE("onPasteSelected: Invalid MimeData received");
146+ return;
147+ }
148+
149+ QMimeData* deserialized = deserializeMimeData(mimedata);
150+ if (deserialized->hasImage()) {
151+ if (deserialized->imageData().toByteArray().isEmpty()) {
152+ Q_EMIT pasteSelected(m_targetItem, deserialized->html());
153+ } else {
154+ Q_EMIT pasteSelected(m_targetItem, deserialized->imageData().toString());
155+ }
156+ } else if (deserialized->hasHtml() && pasteAsRichText) {
157+ Q_EMIT pasteSelected(m_targetItem, deserialized->html());
158+ } else {
159+ Q_EMIT pasteSelected(m_targetItem, deserialized->text());
160+ }
161+}
162+
163+void UCContentHub::onPasteboardChanged()
164+{
165+ if (checkPasteFormats() != m_canPaste) {
166+ m_canPaste = !m_canPaste;
167+ Q_EMIT canPasteChanged();
168+ }
169+}
170+
171+QString UCContentHub::getAppProfile()
172+{
173+ if (!m_dbusIface->isValid()) {
174+ CONTENT_HUB_TRACE("Invalid DBus DBusInterface");
175+ return QString();
176+ }
177+
178+ QDBusReply<QString> reply = m_dbusIface->call(QStringLiteral("GetConnectionAppArmorSecurityContext"), QDBusConnection::sessionBus().baseService());
179+ if (reply.isValid()) {
180+ return reply.value();
181+ }
182+
183+ return QString();
184+}
185+
186+QMimeData* UCContentHub::deserializeMimeData(const QByteArray &serializedMimeData)
187+{
188+ int maxFormatsCount = 16;
189+
190+ if (static_cast<std::size_t>(serializedMimeData.size()) < sizeof(int)) {
191+ // Data is invalid
192+ return nullptr;
193+ }
194+
195+ QMimeData *mimeData = new QMimeData;
196+
197+ const char* const buffer = serializedMimeData.constData();
198+ const int* const header = reinterpret_cast<const int*>(serializedMimeData.constData());
199+
200+ const int count = qMin(header[0], maxFormatsCount);
201+
202+ for (int i = 0; i < count; i++) {
203+ const int formatOffset = header[i*4+1];
204+ const int formatSize = header[i*4+2];
205+ const int dataOffset = header[i*4+3];
206+ const int dataSize = header[i*4+4];
207+
208+ if (formatOffset + formatSize <= serializedMimeData.size()
209+ && dataOffset + dataSize <= serializedMimeData.size()) {
210+
211+ QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize);
212+ QByteArray mimeDataBytes(&buffer[dataOffset], dataSize);
213+
214+ mimeData->setData(mimeType, mimeDataBytes);
215+ }
216+ }
217+
218+ return mimeData;
219+}
220+
221+bool UCContentHub::checkPasteFormats()
222+{
223+ if (!m_contentHubIface->isValid()) {
224+ CONTENT_HUB_TRACE("Invalid Content Hub DBusInterface");
225+ return false;
226+ }
227+
228+ QDBusReply<QStringList> reply = m_contentHubIface->call(QStringLiteral("PasteFormats"));
229+ if (reply.isValid()) {
230+ // TODO: ContentHub clipboard keeps a list of all available paste formats.
231+ // Probably apps could make use of this information to check if a specific
232+ // data type is available, instead of only checking if list is empty or not.
233+ // (LP: #1657111)
234+ return !reply.value().isEmpty();
235+ } else {
236+ CONTENT_HUB_TRACE("Invalid return from DBus call PasteFormats");
237+ }
238+
239+ return false;
240+}
241+
242+UT_NAMESPACE_END
243
244=== added file 'src/UbuntuToolkit/privates/uccontenthub_p.h'
245--- src/UbuntuToolkit/privates/uccontenthub_p.h 1970-01-01 00:00:00 +0000
246+++ src/UbuntuToolkit/privates/uccontenthub_p.h 2017-01-26 16:03:41 +0000
247@@ -0,0 +1,68 @@
248+/*
249+ * Copyright 2016 Canonical Ltd.
250+ *
251+ * This program is free software; you can redistribute it and/or modify
252+ * it under the terms of the GNU Lesser General Public License as published by
253+ * the Free Software Foundation; version 3.
254+ *
255+ * This program is distributed in the hope that it will be useful,
256+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
257+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
258+ * GNU Lesser General Public License for more details.
259+ *
260+ * You should have received a copy of the GNU Lesser General Public License
261+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
262+ *
263+ * Author: Arthur Mello <arthur.mello@canonical.com>
264+ */
265+
266+#ifndef UCCONTENTHUB_P_H
267+#define UCCONTENTHUB_P_H
268+
269+#include <QtCore/QObject>
270+
271+#include <UbuntuToolkit/ubuntutoolkitglobal.h>
272+
273+class QMimeData;
274+class QDBusInterface;
275+class QQuickItem;
276+
277+UT_NAMESPACE_BEGIN
278+
279+class UBUNTUTOOLKIT_EXPORT UCContentHub : public QObject
280+{
281+ Q_OBJECT
282+
283+ Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged)
284+
285+public:
286+ UCContentHub(QObject* parent = 0);
287+ ~UCContentHub();
288+
289+ Q_INVOKABLE void requestPaste(QQuickItem *targetItem);
290+
291+ bool canPaste();
292+ QString getAppProfile();
293+ QMimeData* deserializeMimeData(const QByteArray &serializedMimeData);
294+
295+Q_SIGNALS:
296+ void pasteSelected(QQuickItem *targetItem, const QString &data);
297+ void canPasteChanged();
298+
299+public Q_SLOTS:
300+ void onPasteSelected(QString appId, QByteArray mimedata, bool pasteAsRichText);
301+ void onPasteboardChanged();
302+
303+private:
304+ bool checkPasteFormats();
305+
306+ QDBusInterface *m_dbusIface;
307+ QDBusInterface *m_contentHubIface;
308+
309+ bool m_canPaste;
310+ QQuickItem *m_targetItem;
311+};
312+
313+UT_NAMESPACE_END
314+
315+#endif // UCCONTENTHUB_P_H
316
317=== modified file 'src/UbuntuToolkit/ubuntutoolkitmodule.cpp'
318--- src/UbuntuToolkit/ubuntutoolkitmodule.cpp 2017-01-09 15:50:34 +0000
319+++ src/UbuntuToolkit/ubuntutoolkitmodule.cpp 2017-01-26 16:03:41 +0000
320@@ -45,6 +45,7 @@
321 #include "menugroup_p.h"
322 #include "privates/appheaderbase_p.h"
323 #include "privates/frame_p.h"
324+#include "privates/uccontenthub_p.h"
325 #include "privates/ucpagewrapper_p.h"
326 #include "privates/ucscrollbarutils_p.h"
327 #include "qquickclipboard_p.h"
328@@ -263,6 +264,8 @@
329 qmlRegisterType<UCAppHeaderBase>(privateUri, 1, 3, "AppHeaderBase");
330 qmlRegisterType<Tree>(privateUri, 1, 3, "Tree");
331
332+ qmlRegisterSimpleSingletonType<UCContentHub>(privateUri, 1, 3, "UCContentHub");
333+
334 //FIXME: move to a more generic location, i.e StyledItem or QuickUtils
335 qmlRegisterSimpleSingletonType<UCScrollbarUtils>(privateUri, 1, 3, "PrivateScrollbarUtils");
336
337
338=== modified file 'src/imports/Components/1.3/TextArea.qml'
339--- src/imports/Components/1.3/TextArea.qml 2017-01-20 16:08:33 +0000
340+++ src/imports/Components/1.3/TextArea.qml 2017-01-26 16:03:41 +0000
341@@ -17,6 +17,7 @@
342 import QtQuick 2.4
343 import Ubuntu.Components 1.3 as Ubuntu
344 import Ubuntu.Components.Popups 1.3
345+import Ubuntu.Components.Private 1.3 as Private
346
347 /*!
348 \qmltype TextArea
349@@ -746,6 +747,15 @@
350 editor.linkActivated.connect(control.linkActivated);
351 }
352
353+ Connections {
354+ target: Private.UCContentHub
355+ onPasteSelected: {
356+ if (targetItem === control) {
357+ control.paste(data)
358+ }
359+ }
360+ }
361+
362 // activation area on mouse click
363 // the editor activates automatically when pressed in the editor control,
364 // however that one can be slightly spaced to the main control area
365@@ -816,6 +826,11 @@
366 control.paste("\n");
367 }
368 event.accepted = true;
369+ } else if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)){
370+ if (Private.UCContentHub.canPaste) {
371+ Private.UCContentHub.requestPaste(control);
372+ event.accepted = true;
373+ }
374 } else {
375 event.accepted = false;
376 }
377
378=== modified file 'src/imports/Components/1.3/TextField.qml'
379--- src/imports/Components/1.3/TextField.qml 2016-09-20 07:23:24 +0000
380+++ src/imports/Components/1.3/TextField.qml 2017-01-26 16:03:41 +0000
381@@ -17,6 +17,7 @@
382 import QtQuick 2.4
383 import Ubuntu.Components 1.3 as Ubuntu
384 import Ubuntu.Components.Popups 1.3
385+import Ubuntu.Components.Private 1.3 as Private
386
387 /*!
388 \qmltype TextField
389@@ -637,7 +638,6 @@
390 control.triggered(control.text)
391 }
392
393-
394 /*!
395 Copies the currently selected text to the system clipboard.
396 */
397@@ -834,6 +834,28 @@
398
399 // internals
400
401+ Connections {
402+ target: Private.UCContentHub
403+ onPasteSelected: {
404+ if (targetItem === control) {
405+ control.paste(data)
406+ }
407+ }
408+ }
409+
410+ Keys.onPressed: {
411+ if (readOnly)
412+ return;
413+ if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)){
414+ if (Private.UCContentHub.canPaste) {
415+ Private.UCContentHub.requestPaste(control);
416+ event.accepted = true;
417+ }
418+ } else {
419+ event.accepted = false;
420+ }
421+ }
422+
423 // Overload focus mechanics to avoid bubbling up of focus from children
424 activeFocusOnPress: true
425
426
427=== modified file 'src/imports/Components/1.3/TextInputPopover.qml'
428--- src/imports/Components/1.3/TextInputPopover.qml 2015-12-14 15:15:46 +0000
429+++ src/imports/Components/1.3/TextInputPopover.qml 2017-01-26 16:03:41 +0000
430@@ -17,6 +17,7 @@
431 import QtQuick 2.4
432 import Ubuntu.Components 1.3
433 import Ubuntu.Components.Popups 1.3
434+import Ubuntu.Components.Private 1.3 as Private
435
436 Popover {
437 id: popover
438@@ -54,12 +55,12 @@
439 }
440 },
441 Action {
442- text: i18n.dtr('ubuntu-ui-toolkit', "Paste")
443+ text: i18n.dtr('ubuntu-ui-toolkit', "Paste...")
444 iconName: "edit-paste"
445- enabled: target && target.canPaste
446+ enabled: target && Private.UCContentHub.canPaste
447 onTriggered: {
448 PopupUtils.close(popover);
449- target.paste();
450+ Private.UCContentHub.requestPaste(target);
451 }
452 }
453 ]
454
455=== modified file 'tests/license/checklicense.sh'
456--- tests/license/checklicense.sh 2015-11-09 15:13:21 +0000
457+++ tests/license/checklicense.sh 2017-01-26 16:03:41 +0000
458@@ -18,7 +18,7 @@
459
460 include_files="\.(c(c|pp|xx)?|h(h|pp|xx)?|p(l|m)|php|py(|x)|java|js|vala|qml)$"
461 exclude_dirs="(3rd_party|qrc_|moc_|_build|include)"
462-allowed_licenses="(Canonical|Android|Google|Digia)"
463+allowed_licenses="(Canonical|Android|Google|Digia|Qt Company Ltd)"
464 issues_count=`licensecheck --noconf -r * --copyright -m -c $include_files -i $exclude_dirs | egrep -v $allowed_licenses | wc -l`
465
466 if [ $issues_count -eq 0 ]; then
467
468=== added directory 'tests/unit/contenthub'
469=== added file 'tests/unit/contenthub/TextAreaPaste.qml'
470--- tests/unit/contenthub/TextAreaPaste.qml 1970-01-01 00:00:00 +0000
471+++ tests/unit/contenthub/TextAreaPaste.qml 2017-01-26 16:03:41 +0000
472@@ -0,0 +1,32 @@
473+/*
474+ * Copyright 2016 Canonical Ltd.
475+ *
476+ * This program is free software; you can redistribute it and/or modify
477+ * it under the terms of the GNU Lesser General Public License as published by
478+ * the Free Software Foundation; version 3.
479+ *
480+ * This program is distributed in the hope that it will be useful,
481+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
482+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
483+ * GNU Lesser General Public License for more details.
484+ *
485+ * You should have received a copy of the GNU Lesser General Public License
486+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
487+ */
488+
489+import QtQuick 2.4
490+import Ubuntu.Components 1.3
491+
492+Item {
493+ width: units.gu(20)
494+ height: units.gu(20)
495+
496+ TextArea {
497+ id: textArea
498+ objectName: "textArea"
499+ width: parent.width
500+ height: units.gu(10)
501+ focus: true
502+ }
503+}
504+
505
506=== added file 'tests/unit/contenthub/TextFieldPaste.qml'
507--- tests/unit/contenthub/TextFieldPaste.qml 1970-01-01 00:00:00 +0000
508+++ tests/unit/contenthub/TextFieldPaste.qml 2017-01-26 16:03:41 +0000
509@@ -0,0 +1,31 @@
510+/*
511+ * Copyright 2016 Canonical Ltd.
512+ *
513+ * This program is free software; you can redistribute it and/or modify
514+ * it under the terms of the GNU Lesser General Public License as published by
515+ * the Free Software Foundation; version 3.
516+ *
517+ * This program is distributed in the hope that it will be useful,
518+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
519+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
520+ * GNU Lesser General Public License for more details.
521+ *
522+ * You should have received a copy of the GNU Lesser General Public License
523+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
524+ */
525+
526+import QtQuick 2.4
527+import Ubuntu.Components 1.3
528+
529+Item {
530+ width: units.gu(20)
531+ height: units.gu(20)
532+
533+ TextField {
534+ id: textField
535+ objectName: "textField"
536+ width: parent.width
537+ focus: true
538+ }
539+}
540+
541
542=== added file 'tests/unit/contenthub/com.ubuntu.content.MockService.xml'
543--- tests/unit/contenthub/com.ubuntu.content.MockService.xml 1970-01-01 00:00:00 +0000
544+++ tests/unit/contenthub/com.ubuntu.content.MockService.xml 2017-01-26 16:03:41 +0000
545@@ -0,0 +1,17 @@
546+<node>
547+ <interface name="com.ubuntu.content.dbus.Service">
548+ <method name="RequestPasteByAppId">
549+ <arg name="appId" type="s" direction="in" />
550+ </method>
551+ <method name="PasteFormats">
552+ <arg name="formats" type="as" direction="out" />
553+ </method>
554+ <signal name="PasteSelected">
555+ <arg name="appId" type="s" />
556+ <arg name="mimedata" type="ay" />
557+ <arg name="outputAsHtml" type="b" />
558+ </signal>
559+ <signal name="PasteboardChanged">
560+ </signal>
561+ </interface>
562+</node>
563
564=== added file 'tests/unit/contenthub/contenthub.pro'
565--- tests/unit/contenthub/contenthub.pro 1970-01-01 00:00:00 +0000
566+++ tests/unit/contenthub/contenthub.pro 2017-01-26 16:03:41 +0000
567@@ -0,0 +1,5 @@
568+include(../test-include-x11.pri)
569+QT += dbus gui
570+SOURCES += tst_contenthub.cpp
571+OTHER_FILES += TextAreaPaste.qml TextFieldPaste.qml
572+DBUS_ADAPTORS += com.ubuntu.content.MockService.xml
573
574=== added file 'tests/unit/contenthub/tst_contenthub.cpp'
575--- tests/unit/contenthub/tst_contenthub.cpp 1970-01-01 00:00:00 +0000
576+++ tests/unit/contenthub/tst_contenthub.cpp 2017-01-26 16:03:41 +0000
577@@ -0,0 +1,254 @@
578+/*
579+ * Copyright 2016 Canonical Ltd.
580+ *
581+ * This program is free software; you can redistribute it and/or modify
582+ * it under the terms of the GNU Lesser General Public License as published by
583+ * the Free Software Foundation; version 3.
584+ *
585+ * This program is distributed in the hope that it will be useful,
586+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
587+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
588+ * GNU Lesser General Public License for more details.
589+ *
590+ * You should have received a copy of the GNU Lesser General Public License
591+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
592+ *
593+ * Author: Arthur Mello <arthur.mello@canonical.com>
594+ */
595+
596+#include "mockservice_adaptor.h"
597+
598+#include <QtCore/QDebug>
599+#include <QtCore/QMimeData>
600+#include <QtDBus/QDBusConnection>
601+#include <QtTest/QTest>
602+#include <QtTest/QSignalSpy>
603+#include <QtQuick/QQuickItem>
604+#include <UbuntuToolkit/ubuntutoolkitmodule.h>
605+#include <UbuntuToolkit/private/uccontenthub_p.h>
606+
607+#include "uctestcase.h"
608+
609+UT_USE_NAMESPACE
610+
611+class MockContentService : public QObject
612+{
613+ Q_OBJECT
614+
615+public:
616+ MockContentService() {}
617+ ~MockContentService() {}
618+
619+public Q_SLOTS:
620+ void RequestPasteByAppId(const QString &appId)
621+ {
622+ Q_UNUSED(appId);
623+ Q_EMIT PasteRequested();
624+ }
625+
626+ QStringList PasteFormats()
627+ {
628+ QStringList formats;
629+ formats << "text/plain" << "text/html" << "image/jpeg";
630+ return formats;
631+ }
632+
633+Q_SIGNALS:
634+ void PasteSelected(const QString&, const QByteArray&, bool);
635+ void PasteboardChanged();
636+
637+ void PasteRequested();
638+};
639+
640+class tst_UCContentHub : public QObject
641+{
642+ Q_OBJECT
643+
644+public:
645+ tst_UCContentHub() {}
646+
647+private:
648+ MockContentService *mockService;
649+ UCContentHub *contentHub;
650+ QSignalSpy *pasteRequestedSpy;
651+ QSignalSpy *pasteSelectedSpy;
652+
653+ const int testTimeout = 5000;
654+
655+ const QString dummyAppId = "DummyAppId";
656+
657+ const QString sampleText = "TextData";
658+ const QString sampleHtml = "<html><body><p>HtmlTest</p></body></html>";
659+
660+ // Following serialize code is the same as used by content-hub
661+ QByteArray serializeMimeData(const QMimeData &mimeData)
662+ {
663+ /*
664+ Data format:
665+ number of mime types (sizeof(int))
666+ data layout ((4 * sizeof(int)) * number of mime types)
667+ mime type string offset (sizeof(int))
668+ mime type string size (sizeof(int))
669+ data offset (sizeof(int))
670+ data size (sizeof(int))
671+ data (n bytes)
672+ */
673+
674+ const int maxFormatsCount = 16;
675+ const int maxBufferSize = 4 * 1024 * 1024; // 4 Mb
676+
677+ const QStringList formats = mimeData.formats();
678+ const int formatCount = qMin(formats.size(), maxFormatsCount);
679+ const int headerSize = sizeof(int) + (formatCount * 4 * sizeof(int));
680+ int bufferSize = headerSize;
681+
682+ for (int i = 0; i < formatCount; i++)
683+ bufferSize += formats[i].size() + mimeData.data(formats[i]).size();
684+
685+ QByteArray serializedMimeData;
686+ if (bufferSize <= maxBufferSize) {
687+ // Serialize data.
688+ serializedMimeData.resize(bufferSize);
689+ {
690+ char *buffer = serializedMimeData.data();
691+ int* header = reinterpret_cast<int*>(serializedMimeData.data());
692+ int offset = headerSize;
693+ header[0] = formatCount;
694+ for (int i = 0; i < formatCount; i++) {
695+ const QByteArray data = mimeData.data(formats[i]);
696+ const int formatOffset = offset;
697+ const int formatSize = formats[i].size();
698+ const int dataOffset = offset + formatSize;
699+ const int dataSize = data.size();
700+ memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize);
701+ memcpy(&buffer[dataOffset], data.data(), dataSize);
702+ header[i*4+1] = formatOffset;
703+ header[i*4+2] = formatSize;
704+ header[i*4+3] = dataOffset;
705+ header[i*4+4] = dataSize;
706+ offset += formatSize + dataSize;
707+ }
708+ }
709+ }
710+
711+ return serializedMimeData;
712+ }
713+
714+
715+private Q_SLOTS:
716+ void initTestCase()
717+ {
718+ mockService = new MockContentService();
719+ new ServiceAdaptor(mockService);
720+ QDBusConnection connection = QDBusConnection::sessionBus();
721+ connection.registerObject("/", mockService);
722+ connection.registerService("com.ubuntu.content.dbus.Service");
723+ pasteRequestedSpy = new QSignalSpy(mockService, SIGNAL(PasteRequested()));
724+
725+ qRegisterMetaType<QQuickItem*>();
726+ contentHub = new UCContentHub();
727+ pasteSelectedSpy = new QSignalSpy(contentHub, SIGNAL(pasteSelected(QQuickItem*, const QString&)));
728+ }
729+
730+ void cleanupTestCase()
731+ {
732+ delete pasteRequestedSpy;
733+ delete pasteSelectedSpy;
734+ delete contentHub;
735+ }
736+
737+ void cleanup()
738+ {
739+ pasteRequestedSpy->clear();
740+ pasteSelectedSpy->clear();
741+ }
742+
743+ void test_DeserializeTextMimeData()
744+ {
745+ QMimeData textMimeData;
746+ textMimeData.setText(sampleText);
747+ QMimeData *deserialized = contentHub->deserializeMimeData(serializeMimeData(textMimeData));
748+ QCOMPARE(deserialized->formats().size(), 1);
749+ QVERIFY(deserialized->hasText());
750+ QVERIFY(deserialized->text() == sampleText);
751+ }
752+
753+ void test_DeserializeHtmlMimeData()
754+ {
755+ QMimeData htmlMimeData;
756+ htmlMimeData.setHtml(sampleHtml);
757+ QMimeData *deserialized = contentHub->deserializeMimeData(serializeMimeData(htmlMimeData));
758+ QVERIFY(deserialized->hasHtml());
759+ QVERIFY(deserialized->html() == sampleHtml);
760+ }
761+
762+ void test_TextPasteSelected()
763+ {
764+ QMimeData textPaste;
765+ textPaste.setText(sampleHtml);
766+ contentHub->onPasteSelected(contentHub->getAppProfile(), serializeMimeData(textPaste), false);
767+ pasteSelectedSpy->wait(testTimeout);
768+ QCOMPARE(pasteSelectedSpy->count(), 1);
769+ QList<QVariant> args = pasteSelectedSpy->takeFirst();
770+ QVERIFY(args.at(1).toString() == textPaste.text());
771+ }
772+
773+ void test_HtmlPasteSelectedAsText()
774+ {
775+ QMimeData htmlPaste;
776+ htmlPaste.setHtml(sampleHtml);
777+ contentHub->onPasteSelected(contentHub->getAppProfile(), serializeMimeData(htmlPaste), false);
778+ pasteSelectedSpy->wait(testTimeout);
779+ QCOMPARE(pasteSelectedSpy->count(), 1);
780+ QList<QVariant> args = pasteSelectedSpy->takeFirst();
781+ QVERIFY(args.at(1).toString() == htmlPaste.text());
782+ }
783+
784+ void test_HtmlPasteSelectedAsRichText()
785+ {
786+ QMimeData htmlPaste;
787+ htmlPaste.setHtml(sampleHtml);
788+ contentHub->onPasteSelected(contentHub->getAppProfile(), serializeMimeData(htmlPaste), true);
789+ pasteSelectedSpy->wait(testTimeout);
790+ QCOMPARE(pasteSelectedSpy->count(), 1);
791+ QList<QVariant> args = pasteSelectedSpy->takeFirst();
792+ QVERIFY(args.at(1).toString() == htmlPaste.html());
793+ }
794+
795+ void test_PasteFromAnotherAppId()
796+ {
797+ QMimeData textPaste;
798+ textPaste.setText(sampleText);
799+ contentHub->onPasteSelected(dummyAppId, serializeMimeData(textPaste), false);
800+ pasteSelectedSpy->wait(testTimeout);
801+ QCOMPARE(pasteSelectedSpy->count(), 0);
802+ }
803+
804+ void test_KeyboardShortcutOnTextField()
805+ {
806+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("TextFieldPaste.qml"));
807+ testCase->rootObject()->forceActiveFocus();
808+ QQuickItem *textField = testCase->findItem<QQuickItem*>("textField");
809+ QTest::keyClick(textField->window(), Qt::Key_Tab);
810+ QTRY_COMPARE_WITH_TIMEOUT(textField->property("activeFocus").toBool(), true, testTimeout);
811+ QTest::keyClick(textField->window(), Qt::Key_V, Qt::ControlModifier|Qt::ShiftModifier);
812+ pasteRequestedSpy->wait(testTimeout);
813+ QCOMPARE(pasteRequestedSpy->count(), 1);
814+ }
815+
816+ void test_KeyboardShortcutOnTextArea()
817+ {
818+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("TextAreaPaste.qml"));
819+ testCase->rootObject()->forceActiveFocus();
820+ QQuickItem *textArea = testCase->findItem<QQuickItem*>("textArea");
821+ QTest::keyClick(textArea->window(), Qt::Key_Tab);
822+ QTRY_COMPARE_WITH_TIMEOUT(textArea->property("activeFocus").toBool(), true, testTimeout);
823+ QTest::keyClick(textArea->window(), Qt::Key_V, Qt::ControlModifier|Qt::ShiftModifier);
824+ pasteRequestedSpy->wait(testTimeout);
825+ QCOMPARE(pasteRequestedSpy->count(), 1);
826+ }
827+};
828+
829+QTEST_MAIN(tst_UCContentHub)
830+
831+#include "tst_contenthub.moc"
832
833=== modified file 'tests/unit/unit.pro'
834--- tests/unit/unit.pro 2017-01-09 15:50:34 +0000
835+++ tests/unit/unit.pro 2017-01-26 16:03:41 +0000
836@@ -48,4 +48,5 @@
837 alarms \
838 theme \
839 quickutils \
840- tree
841+ tree \
842+ contenthub

Subscribers

People subscribed via source and target branches