← Back to team overview

ubuntu-sdk-team team mailing list archive

[Merge] lp:~bzoltan/ubuntu-ui-toolkit/landing-2017-01-30 into lp:ubuntu-ui-toolkit

 

Zoltan Balogh has proposed merging lp:~bzoltan/ubuntu-ui-toolkit/landing-2017-01-30 into lp:ubuntu-ui-toolkit.

Commit message:
Landing 17.01.30

Requested reviews:
  Zoltan Balogh (bzoltan)

For more details, see:
https://code.launchpad.net/~bzoltan/ubuntu-ui-toolkit/landing-2017-01-30/+merge/315882

Landing 17.01.30
-- 
Your team Ubuntu SDK team is subscribed to branch lp:ubuntu-ui-toolkit.
=== modified file 'components.api'
--- components.api	2016-09-17 05:48:25 +0000
+++ components.api	2017-01-30 11:36:29 +0000
@@ -1416,6 +1416,7 @@
     function var cut()
     function var deselect()
     function var insert(var position, var text)
+    function var append(var text)
     function var positionAt(var x, var y)
     function var isRightToLeft(var start, var end)
     function var moveCursorSelection(var position, var mode)
@@ -1619,6 +1620,8 @@
     property bool opened
     property Item pageStack
 Ubuntu.Components.Styles.ToolbarStyle 1.3: Item
+    property color backgroundColor
+    readonly property ActionItemProperties buttons
     property Component defaultDelegate
 Ubuntu.Components.Type: Enum
     Bool

=== modified file 'debian/changelog'
--- debian/changelog	2017-01-10 06:35:08 +0000
+++ debian/changelog	2017-01-30 11:36:29 +0000
@@ -1,3 +1,51 @@
+ubuntu-ui-toolkit (1.3.2166+17.04) UNRELEASED; urgency=medium
+
+  [ Andrea Bernabei ]
+  * ListItemLayout documentation: fix typo in color usage of a code example.
+    Fixes LP: #1561861
+
+  [ Zsombor Egri ]
+  * Get tst_listitem_focus_13 back alive. Fixes LP: #1624331.
+
+  [ Arthur Mello ]
+  * Add support for interacting with Content Hub Clipboard UI via DBus calls.
+    Fixes LP: #1563440.
+
+  [ Daniel d'Andrada ]
+  * If GRID_UNIT_PX set, always use it. It should override scaling information
+    from the QPA.
+
+  [ Adnane Belmadiaf ]
+  * Expose append method in TextArea. Fixes LP: #1658121.
+
+  [ Tim Peeters ]
+  * Toolbar visual refresh to scroll the icons instead of using an overflow 
+    panel. Fixes LP: #1558018.
+  * Add missing dependency for the UITK gallery. Fixes lLP: #1640135
+
+  [ Sergio Cazzolato ]
+  * This change is done to remove the dependencies with upstart in the tests.
+    To start apps with env vars is gonna be used the launch_test_application
+    method provided by autopilot in its test base class which is a wrapper for
+    the ubuntu-app-launch tools (API). This change removes all the dependencies
+    with upstart, so all the projects which are using the initctl api and 
+    fixtures will be impacted. An update in the documentation or in the blog is 
+    gonna be done also to communicate this change to the developers.
+
+  [ Christian Dywan ]
+  * Add visualRoot property to MainWindow. Fixes LP: #1504551, LP: #1656857.
+  * Add python3-debian to control.gles and harden package-sorting.sh.
+  * Add organizationName property to MainWindow
+  * Add MainWindow to Ubuntu.Components.Labs. 
+    Fixes LP: #1573118, LP: #1587431, LP: #1647415
+
+  [ Albert Astals Cid ]
+  * Labs is at 1.0.
+  * You can't save the focus after the popup/dialog has been created, at that
+    stage the dialog has focus already, you need to do it before creating it.
+
+ -- Zoltán Balogh <zoltan@xxxxxxxxx>  Mon, 30 Jan 2017 12:49:05 +0200
+
 ubuntu-ui-toolkit (1.3.2151+17.04.20161223build1~3) zesty; urgency=medium
 
   * No change rebuild

=== modified file 'debian/control'
--- debian/control	2016-11-21 12:16:49 +0000
+++ debian/control	2017-01-30 11:36:29 +0000
@@ -353,6 +353,7 @@
 Section: devel
 Architecture: any
 Depends: qml-module-qt-labs-folderlistmodel,
+         qml-module-qtqml-models2,
          qml-module-qtquick-xmllistmodel,
          qml-module-ubuntu-components (= ${binary:Version}) | qml-module-ubuntu-components-gles,
          ubuntu-ui-toolkit-theme (= ${binary:Version}),

=== modified file 'debian/control.gles'
--- debian/control.gles	2016-11-21 12:16:49 +0000
+++ debian/control.gles	2017-01-30 11:36:29 +0000
@@ -37,6 +37,7 @@
                lsb-release,
                pep8,
                python-autopilot (>= 1.4),
+               python3-debian,
                python3-sphinx,
                python3:any,
                python:any,

=== added file 'examples/ubuntu-ui-toolkit-gallery/Toolbars.qml'
--- examples/ubuntu-ui-toolkit-gallery/Toolbars.qml	1970-01-01 00:00:00 +0000
+++ examples/ubuntu-ui-toolkit-gallery/Toolbars.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Template {
+    objectName: "toolbarTemplate"
+    id: toolbarTemplate
+
+    property list<Action> actionList:  [
+        Action {
+            iconName: "alarm-clock"
+            text: "Tick tock"
+        },
+        Action {
+            iconName: "appointment"
+            text: "Date"
+        },
+        Action {
+            iconName: "attachment"
+            text: "Attach"
+        },
+        Action {
+            iconName: "contact"
+            text: "Contact"
+        },
+        Action {
+            iconName: "like"
+            text: "Like"
+        },
+        Action {
+            iconName: "lock"
+            text: "Lock"
+        }
+    ]
+
+    property list<Action> longActionList:  [
+        Action {
+            iconName: "alarm-clock"
+            text: "Tick tock"
+            onTriggered: print("tock")
+        },
+        Action {
+            iconName: "appointment"
+            text: "Date"
+            onTriggered: print("date")
+        },
+        Action {
+            iconName: "attachment"
+            text: "Attach"
+            onTriggered: print("attach")
+        },
+        Action {
+            iconName: "contact"
+            text: "Contact"
+            onTriggered: print("contact")
+        },
+        Action {
+            iconName: "like"
+            text: "Like"
+            onTriggered: print("+1")
+        },
+        Action {
+            iconName: "lock"
+            text: "Lock"
+            onTriggered: print("lock")
+        },
+        Action {
+            iconName: "camcorder"
+            text: "Camera"
+            onTriggered: print("cam")
+        },
+        Action {
+            iconName: "location"
+            text: "Location"
+            onTriggered: print("loc")
+            enabled: false
+        },
+        Action {
+            iconName: "message"
+            text: "Message"
+            onTriggered: print("msg")
+        },
+        Action {
+            iconName: "livetv"
+            text: "Television"
+            onTriggered: print("tv")
+        },
+        Action {
+            iconName: "lock-broken"
+            text: "Unlock"
+            onTriggered: print("unlock")
+        },
+        Action {
+            iconName: "compose"
+            text: "Edit"
+            onTriggered: print("edit")
+        },
+        Action {
+            iconName: "contact-new"
+            text: "Add user"
+            onTriggered: print("useradd")
+        },
+        Action {
+            iconName: "crop"
+            text: "Crop"
+            onTriggered: print("crop")
+        },
+        Action {
+            iconName: "edit-cut"
+            text: "Cut"
+            onTriggered: print("cut")
+        },
+        Action {
+            iconName: "image-quality"
+            text: "Image quality"
+            onTriggered: print("image quality")
+        },
+        Action {
+            iconName: "import"
+            text: "Import"
+            onTriggered: print("import")
+        },
+        Action {
+            iconName: "history"
+            text: "History"
+            onTriggered: print("history")
+        },
+        Action {
+            iconName: "media-eject"
+            text: "Eject"
+            onTriggered: print("eject")
+        }
+    ]
+
+    property list<Action> deleteActionList: [
+        Action {
+            iconName: "delete"
+            text: "Delete"
+            onTriggered: print("delete")
+        }
+    ]
+
+    header: PageHeader {
+        id: h
+        title: toolbarTemplate.title
+        property bool longList: false
+        extension: Toolbar {
+            id: headerToolbar
+            anchors {
+                left: parent.left
+                right: parent.right
+                bottom: parent.bottom
+            }
+            leadingActionBar.actions: deleteActionList
+            trailingActionBar.actions: h.longList ? longActionList : actionList
+        }
+        trailingActionBar.actions: [
+            Action {
+                iconName: h.longList ? "remove" : "add"
+                text: h.longList ? "less" : "more"
+                onTriggered: {
+                    h.longList = !h.longList;
+                }
+            }
+        ]
+    }
+
+    TemplateSection {
+        title: "Toolbar"
+        className: "Toolbar"
+
+        TemplateRow {
+            width: parent.width
+            title: i18n.tr("Enabled")
+            Toolbar {
+                width: parent.width
+                trailingActionBar.actions: actionList
+                leadingActionBar.actions: deleteActionList
+            }
+        }
+        TemplateRow {
+            title: i18n.tr("Disabled")
+            Toolbar {
+                width: parent.width
+                trailingActionBar.actions: actionList
+                leadingActionBar.actions: deleteActionList
+                enabled: false
+            }
+        }
+    }
+
+    TemplateSection {
+        title: "Scrollable toolbar"
+        className: "Toolbar"
+
+        TemplateRow {
+            title: i18n.tr("Enabled")
+            Toolbar {
+                width: parent.width
+                trailingActionBar.actions: longActionList
+                leadingActionBar.actions: deleteActionList
+            }
+        }
+        TemplateRow {
+            title: i18n.tr("Disabled")
+            Toolbar {
+                width: parent.width
+                trailingActionBar.actions: longActionList
+                leadingActionBar.actions: deleteActionList
+                enabled: false
+            }
+        }
+    }
+
+    TemplateSection {
+        title: "Colored"
+        className: "ActionBarStyle"
+
+        TemplateRow {
+            title: "Green"
+            Toolbar {
+                width: parent.width
+                trailingActionBar.actions: longActionList
+                leadingActionBar.actions: deleteActionList
+                StyleHints {
+                    ignoreUnknownProperties: false
+                    backgroundColor: UbuntuColors.green
+                    buttons {
+                        foregroundColor: "white"
+                        disabledForegroundColor: UbuntuColors.silk
+                        pressedBackgroundColor: UbuntuColors.ash
+                        disabledBackgroundColor: UbuntuColors.slate
+                    }
+                }
+            }
+        }
+    }
+}

=== modified file 'examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml'
--- examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml	2016-01-20 20:15:42 +0000
+++ examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml	2017-01-30 11:36:29 +0000
@@ -143,6 +143,12 @@
         source: "Toggles.qml"
     }
     ListElement {
+        objectName: "toolbarsElement"
+        label: "Toolbar"
+        source: "Toolbars.qml"
+    }
+
+    ListElement {
         objectName: "ubuntuListViewElement"
         label: "Ubuntu ListView"
         source: "UbuntuListViews.qml"

=== modified file 'src/UbuntuToolkit/UbuntuToolkit.pro'
--- src/UbuntuToolkit/UbuntuToolkit.pro	2016-11-04 09:37:32 +0000
+++ src/UbuntuToolkit/UbuntuToolkit.pro	2017-01-30 11:36:29 +0000
@@ -52,6 +52,7 @@
     $$PWD/privates/listviewextensions_p.h \
     $$PWD/privates/splitviewhandler_p.h \
     $$PWD/privates/threelabelsslot_p.h \
+    $$PWD/privates/uccontenthub_p.h \
     $$PWD/privates/ucpagewrapper_p.h \
     $$PWD/privates/ucpagewrapper_p_p.h \
     $$PWD/privates/ucpagewrapperincubator_p.h \
@@ -106,6 +107,8 @@
     $$PWD/uclistitemstyle_p.h \
     $$PWD/ucmainviewbase_p.h \
     $$PWD/ucmainviewbase_p_p.h \
+    $$PWD/ucmainwindow_p.h \
+    $$PWD/ucmainwindow_p_p.h \
     $$PWD/ucmargins_p.h \
     $$PWD/ucmathutils_p.h \
     $$PWD/ucmouse_p.h \
@@ -163,6 +166,7 @@
     $$PWD/privates/listviewextensions.cpp \
     $$PWD/privates/splitviewhandler.cpp \
     $$PWD/privates/threelabelsslot_p.cpp \
+    $$PWD/privates/uccontenthub.cpp \
     $$PWD/privates/ucpagewrapper.cpp \
     $$PWD/privates/ucpagewrapperincubator.cpp \
     $$PWD/privates/ucscrollbarutils.cpp \
@@ -203,6 +207,7 @@
     $$PWD/uclistitemlayout.cpp \
     $$PWD/uclistitemstyle.cpp \
     $$PWD/ucmainviewbase.cpp \
+    $$PWD/ucmainwindow.cpp \
     $$PWD/ucmathutils.cpp \
     $$PWD/ucmousefilters.cpp \
     $$PWD/ucpagetreenode.cpp \

=== added file 'src/UbuntuToolkit/privates/uccontenthub.cpp'
--- src/UbuntuToolkit/privates/uccontenthub.cpp	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/privates/uccontenthub.cpp	2017-01-30 11:36:29 +0000
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Arthur Mello <arthur.mello@xxxxxxxxxxxxx>
+ */
+
+#include "privates/uccontenthub_p.h"
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QMimeData>
+#include <QtDBus/QDBusConnection>
+#include <QtDBus/QDBusInterface>
+#include <QtDBus/QDBusReply>
+#include <QtQuick/QQuickItem>
+
+Q_LOGGING_CATEGORY(ucContentHub, "ubuntu.components.UCContentHub", QtMsgType::QtWarningMsg)
+
+#define CONTENT_HUB_TRACE(params) qCDebug(ucContentHub) << params
+
+static const QString contentHubService = QStringLiteral("com.ubuntu.content.dbus.Service");
+static const QString contentHubObjectPath = QStringLiteral("/");
+static const QString contentHubInterface = QStringLiteral("com.ubuntu.content.dbus.Service");
+
+static const QString dbusService = QStringLiteral("org.freedesktop.DBus");
+static const QString dbusObjectPath = QStringLiteral("/org/freedesktop/DBus");
+static const QString dbusInterface = QStringLiteral("org.freedesktop.DBus");
+
+UT_NAMESPACE_BEGIN
+
+UCContentHub::UCContentHub(QObject *parent)
+    : QObject(parent),
+      m_dbusIface(0),
+      m_contentHubIface(0),
+      m_canPaste(false),
+      m_targetItem(0)
+{
+    m_dbusIface = new QDBusInterface(dbusService,
+                                     dbusObjectPath,
+                                     dbusInterface,
+                                     QDBusConnection::sessionBus(),
+                                     this);
+
+    m_contentHubIface = new QDBusInterface(contentHubService,
+                                           contentHubObjectPath,
+                                           contentHubInterface,
+                                           QDBusConnection::sessionBus(),
+                                           this);
+    if (m_contentHubIface->isValid()) {
+        m_contentHubIface->connection().connect(
+            contentHubService,
+            contentHubObjectPath,
+            contentHubInterface,
+            QStringLiteral("PasteSelected"),
+            this,
+            SLOT(onPasteSelected(QString, QByteArray, bool))
+        );
+
+        m_contentHubIface->connection().connect(
+            contentHubService,
+            contentHubObjectPath,
+            contentHubInterface,
+            QStringLiteral("PasteboardChanged"),
+            this,
+            SLOT(onPasteboardChanged())
+        );
+
+        m_canPaste = checkPasteFormats();
+    }
+}
+
+UCContentHub::~UCContentHub()
+{
+    if (m_dbusIface) {
+        delete m_dbusIface;
+    }
+
+    if (m_contentHubIface) {
+        delete m_contentHubIface;
+    }
+}
+
+void UCContentHub::requestPaste(QQuickItem *targetItem)
+{
+    if (!m_contentHubIface->isValid()) {
+        CONTENT_HUB_TRACE("Invalid Content Hub DBusInterface");
+        return;
+    }
+
+    m_targetItem = targetItem;
+
+    QString appProfile = getAppProfile();
+    CONTENT_HUB_TRACE("AppArmor profile:" << appProfile);
+
+    m_contentHubIface->call(QStringLiteral("RequestPasteByAppId"), appProfile);
+}
+
+bool UCContentHub::canPaste()
+{
+    return m_canPaste;
+}
+
+void UCContentHub::onPasteSelected(QString appId, QByteArray mimedata, bool pasteAsRichText)
+{
+    if (getAppProfile() != appId) {
+        return;
+    }
+
+    if (mimedata.isNull()) {
+        CONTENT_HUB_TRACE("onPasteSelected: Invalid MimeData received");
+        return;
+    }
+
+    QMimeData* deserialized = deserializeMimeData(mimedata);
+    if (deserialized->hasImage()) {
+        if (deserialized->imageData().toByteArray().isEmpty()) {
+            Q_EMIT pasteSelected(m_targetItem, deserialized->html());
+        } else {
+            Q_EMIT pasteSelected(m_targetItem, deserialized->imageData().toString());
+        }
+    } else if (deserialized->hasHtml() && pasteAsRichText) {
+        Q_EMIT pasteSelected(m_targetItem, deserialized->html());
+    } else {
+        Q_EMIT pasteSelected(m_targetItem, deserialized->text());
+    }
+}
+
+void UCContentHub::onPasteboardChanged()
+{
+    if (checkPasteFormats() != m_canPaste) {
+        m_canPaste = !m_canPaste;
+        Q_EMIT canPasteChanged();
+    }
+}
+
+QString UCContentHub::getAppProfile()
+{
+    if (!m_dbusIface->isValid()) {
+        CONTENT_HUB_TRACE("Invalid DBus DBusInterface");
+        return QString();
+    }
+
+    QDBusReply<QString> reply = m_dbusIface->call(QStringLiteral("GetConnectionAppArmorSecurityContext"), QDBusConnection::sessionBus().baseService());
+    if (reply.isValid()) {
+        return reply.value();
+    }
+
+    return QString();
+}
+
+QMimeData* UCContentHub::deserializeMimeData(const QByteArray &serializedMimeData)
+{
+    int maxFormatsCount = 16;
+
+    if (static_cast<std::size_t>(serializedMimeData.size()) < sizeof(int)) {
+        // Data is invalid
+        return nullptr;
+    }
+
+    QMimeData *mimeData = new QMimeData;
+
+    const char* const buffer = serializedMimeData.constData();
+    const int* const header = reinterpret_cast<const int*>(serializedMimeData.constData());
+
+    const int count = qMin(header[0], maxFormatsCount);
+
+    for (int i = 0; i < count; i++) {
+        const int formatOffset = header[i*4+1];
+        const int formatSize = header[i*4+2];
+        const int dataOffset = header[i*4+3];
+        const int dataSize = header[i*4+4];
+
+        if (formatOffset + formatSize <= serializedMimeData.size()
+            && dataOffset + dataSize <= serializedMimeData.size()) {
+
+            QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize);
+            QByteArray mimeDataBytes(&buffer[dataOffset], dataSize);
+
+            mimeData->setData(mimeType, mimeDataBytes);
+        }
+    }
+
+    return mimeData;
+}
+
+bool UCContentHub::checkPasteFormats()
+{
+    if (!m_contentHubIface->isValid()) {
+        CONTENT_HUB_TRACE("Invalid Content Hub DBusInterface");
+        return false;
+    }
+
+    QDBusReply<QStringList> reply = m_contentHubIface->call(QStringLiteral("PasteFormats"));
+    if (reply.isValid()) {
+        // TODO: ContentHub clipboard keeps a list of all available paste formats.
+        // Probably apps could make use of this information to check if a specific
+        // data type is available, instead of only checking if list is empty or not.
+        // (LP: #1657111)
+        return !reply.value().isEmpty();
+    } else {
+        CONTENT_HUB_TRACE("Invalid return from DBus call PasteFormats");
+    }
+
+    return false;
+}
+
+UT_NAMESPACE_END

=== added file 'src/UbuntuToolkit/privates/uccontenthub_p.h'
--- src/UbuntuToolkit/privates/uccontenthub_p.h	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/privates/uccontenthub_p.h	2017-01-30 11:36:29 +0000
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Arthur Mello <arthur.mello@xxxxxxxxxxxxx>
+ */
+
+#ifndef UCCONTENTHUB_P_H
+#define UCCONTENTHUB_P_H
+
+#include <QtCore/QObject>
+
+#include <UbuntuToolkit/ubuntutoolkitglobal.h>
+
+class QMimeData;
+class QDBusInterface;
+class QQuickItem;
+
+UT_NAMESPACE_BEGIN
+
+class UBUNTUTOOLKIT_EXPORT UCContentHub : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged)
+
+public:
+    UCContentHub(QObject* parent = 0);
+    ~UCContentHub();
+
+    Q_INVOKABLE void requestPaste(QQuickItem *targetItem);
+
+    bool canPaste();
+    QString getAppProfile();
+    QMimeData* deserializeMimeData(const QByteArray &serializedMimeData);
+
+Q_SIGNALS:
+    void pasteSelected(QQuickItem *targetItem, const QString &data);
+    void canPasteChanged();
+
+public Q_SLOTS:
+    void onPasteSelected(QString appId, QByteArray mimedata, bool pasteAsRichText);
+    void onPasteboardChanged();
+
+private:
+    bool checkPasteFormats();
+
+    QDBusInterface *m_dbusIface;
+    QDBusInterface *m_contentHubIface;
+
+    bool m_canPaste;
+    QQuickItem *m_targetItem;
+};
+
+UT_NAMESPACE_END
+
+#endif  // UCCONTENTHUB_P_H

=== modified file 'src/UbuntuToolkit/quickutils.cpp'
--- src/UbuntuToolkit/quickutils.cpp	2016-11-11 06:57:56 +0000
+++ src/UbuntuToolkit/quickutils.cpp	2017-01-30 11:36:29 +0000
@@ -29,6 +29,8 @@
 #include <QtQuick/private/qquicktextedit_p.h>
 #include <QtSystemInfo/QInputInfoManager>
 
+#include <UbuntuToolkit/private/ucmainwindow_p.h>
+
 UT_NAMESPACE_BEGIN
 
 QuickUtils *QuickUtils::m_instance = nullptr;
@@ -170,11 +172,18 @@
  * \internal
  * Returns the root item of a given item. In case there is a QQuickWindow (Window)
  * found in the hierarchy, the function will return the contentItem of the window.
+ * If the root item is a \b MainWindow, the visualRoot property will be respected.
  */
 QQuickItem *QuickUtils::rootItem(QObject *object)
 {
     // make sure we have the m_rootView updated
     lookupQuickView();
+
+    UCMainWindow *mainWindow(qobject_cast<UCMainWindow*>(m_rootWindow));
+    if (mainWindow && mainWindow->visualRoot()) {
+        return mainWindow->visualRoot();
+    }
+
     if (!object) {
         return m_rootView ? m_rootView->rootObject() : (m_rootWindow ? m_rootWindow->contentItem() : 0);
     }

=== modified file 'src/UbuntuToolkit/ubuntutoolkitmodule.cpp'
--- src/UbuntuToolkit/ubuntutoolkitmodule.cpp	2016-09-29 10:19:06 +0000
+++ src/UbuntuToolkit/ubuntutoolkitmodule.cpp	2017-01-30 11:36:29 +0000
@@ -39,11 +39,13 @@
 #include "inversemouseareatype_p.h"
 #include "listener_p.h"
 #include "livetimer_p.h"
+#include "ucmainwindow_p.h"
 #include "menu_p.h"
 #include "menubar_p.h"
 #include "menugroup_p.h"
 #include "privates/appheaderbase_p.h"
 #include "privates/frame_p.h"
+#include "privates/uccontenthub_p.h"
 #include "privates/ucpagewrapper_p.h"
 #include "privates/ucscrollbarutils_p.h"
 #include "qquickclipboard_p.h"
@@ -262,6 +264,8 @@
     qmlRegisterType<UCAppHeaderBase>(privateUri, 1, 3, "AppHeaderBase");
     qmlRegisterType<Tree>(privateUri, 1, 3, "Tree");
 
+    qmlRegisterSimpleSingletonType<UCContentHub>(privateUri, 1, 3, "UCContentHub");
+
     //FIXME: move to a more generic location, i.e StyledItem or QuickUtils
     qmlRegisterSimpleSingletonType<UCScrollbarUtils>(privateUri, 1, 3, "PrivateScrollbarUtils");
 
@@ -439,6 +443,7 @@
     qmlRegisterType<SplitView>(uri, 1, 0, "SplitView");
     qmlRegisterType<SplitViewLayout>(uri, 1, 0, "SplitViewLayout");
     qmlRegisterType<ViewColumn>(uri, 1, 0, "ViewColumn");
+    qmlRegisterType<UCMainWindow>(uri, 1, 0, "MainWindow");
     qmlRegisterType<Menu>(uri, 1, 0, "Menu");
     qmlRegisterType<MenuBar>(uri, 1, 0, "MenuBar");
     qmlRegisterType<MenuGroup>(uri, 1, 0, "MenuGroup");

=== modified file 'src/UbuntuToolkit/ucapplication.cpp'
--- src/UbuntuToolkit/ucapplication.cpp	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/ucapplication.cpp	2017-01-30 11:36:29 +0000
@@ -39,6 +39,8 @@
 UCApplication::UCApplication(QObject* parent) : QObject(parent), m_context(0)
                                                                , m_inputMethod(QGuiApplication::inputMethod())
 {
+    // Unset organization by default to skip an extra folder component
+    QCoreApplication::setOrganizationName(QStringLiteral(""));
     // Make sure we receive application name changes from C++ modules
     connect(QCoreApplication::instance(), &QCoreApplication::applicationNameChanged,
             this, &UCApplication::applicationNameChanged);
@@ -68,8 +70,7 @@
        to how Unity uses it to distinguish running applications.
      */
     QCoreApplication::setApplicationName(applicationName);
-    // Unset organization to skip an extra folder component
-    QCoreApplication::setOrganizationName(QString());
+    QCoreApplication::setOrganizationName(QCoreApplication::organizationName());
     /*
        Ensure that LocalStorage and WebKit use the same location
        Docs are ambiguous: in practise applicationName is ignored by default

=== modified file 'src/UbuntuToolkit/uclistitemlayout.cpp'
--- src/UbuntuToolkit/uclistitemlayout.cpp	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/uclistitemlayout.cpp	2017-01-30 11:36:29 +0000
@@ -139,7 +139,7 @@
         ListItemLayout {
             id: layout
             title.text: "Hello..."
-            title.color: UbuntuColors.Orange
+            title.color: UbuntuColors.orange
             subtitle.text: "...world!"
 
             Rectangle {

=== modified file 'src/UbuntuToolkit/ucmainviewbase.cpp'
--- src/UbuntuToolkit/ucmainviewbase.cpp	2016-09-12 09:03:50 +0000
+++ src/UbuntuToolkit/ucmainviewbase.cpp	2017-01-30 11:36:29 +0000
@@ -145,6 +145,7 @@
 void UCMainViewBase::setApplicationName(QString applicationName)
 {
     Q_D(UCMainViewBase);
+
     if (d->m_applicationName == applicationName)
         return;
 

=== added file 'src/UbuntuToolkit/ucmainwindow.cpp'
--- src/UbuntuToolkit/ucmainwindow.cpp	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/ucmainwindow.cpp	2017-01-30 11:36:29 +0000
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2016-2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ucmainwindow_p_p.h"
+
+#include <QtCore/QCoreApplication>
+
+#include "ucactionmanager_p.h"
+#include "ucactioncontext_p.h"
+#include "ucapplication_p.h"
+#include "uctheme_p.h"
+#include "i18n_p.h"
+#include "quickutils_p.h"
+
+UT_NAMESPACE_BEGIN
+
+/*!
+  \internal
+  \qmlabstract MainWindow
+  \inqmlmodule Ubuntu.Components.Labs
+  \ingroup ubuntu
+  \brief MainWindow is an alternate window-based root Item providing \l units
+  and \l i18n as native properties, an \ actionContext and an \l applicationName.
+  Unlike \l MainView there is no built-in header.
+
+  The simplest way to use a MainWindow is to include a single \l Page object:
+  \qml
+      import QtQuick 2.4
+      import Ubuntu.Components 1.3
+      import Ubuntu.Components.Labs 1.0
+
+      MainWindow {
+          minimumWidth: units.gu(48)
+          minimumHeight: units.gu(60)
+
+          Page {
+	      anchors.fill: parent
+              header: PageHeader {
+                  title: "Simple page"
+              }
+              Button {
+                  anchors {
+                      horizontalCenter: parent.horizontalCenter
+                      top: pageHeader.bottom
+                      topMargin: units.gu(5)
+                  }
+                  width: units.gu(15)
+                  text: "Push me"
+                  onClicked: print("Click!")
+              }
+          }
+      }
+  \endqml
+  Anchors need to be set, there's no automatic fill like with \l MainView.
+
+  Do not include multiple Pages directly, but use \l AdaptivePageLayout
+  inside MainWindow to navigate between several Pages.
+
+  If the \l Page inside the MainWindow includes a Flickable, set the flickable property of
+  the PageHeader to automatically hide and show the header when the user scrolls up or down:
+  \qml
+      import QtQuick 2.4
+      import Ubuntu.Components 1.3
+      import Ubuntu.Components.Labs 1.0
+
+      MainWindow {
+          minimumWidth: units.gu(48)
+          maximumHeight: units.gu(60)
+
+          Page {
+              anchors.fill: parent
+              header: PageHeader {
+                  title: "Page with Flickable"
+                  flickable: myFlickable
+              }
+
+              Flickable {
+                  id: myFlickable
+                  anchors.fill: parent
+                  contentHeight: column.height
+
+                  Column {
+                      id: column
+                      Repeater {
+                          model: 100
+                          Label {
+                              text: "line "+index
+                          }
+                      }
+                  }
+              }
+          }
+      }
+  \endqml
+  The same header behavior is automatic when using a ListView instead of a Flickable in the above
+  example.
+
+  The examples above show how to include a single \l Page inside a MainWindow, but more
+  advanced application structures are possible using \l AdaptivePageLayout.
+**/
+UCMainWindowPrivate::UCMainWindowPrivate()
+    : m_actionContext(nullptr),
+      m_units(nullptr)
+{
+}
+
+void UCMainWindowPrivate::init()
+{
+    Q_Q(UCMainWindow);
+
+    //need to init here because the q pointer is null in constructor
+    m_actionContext = new UCPopupContext(q);
+
+    m_actionContext->setObjectName(QStringLiteral("RootContext"));
+    m_actionContext->setActive(true);
+}
+
+UCMainWindow::UCMainWindow(QWindow *parent)
+    : QQuickWindow(*(new UCMainWindowPrivate), parent)
+{
+    d_func()->init();
+
+    QObject::connect(UbuntuI18n::instance(this), SIGNAL(domainChanged()),
+                     this, SIGNAL(i18nChanged()));
+    QObject::connect(UbuntuI18n::instance(this), SIGNAL(languageChanged()),
+                     this, SIGNAL(i18nChanged()));
+}
+
+/*!
+  \qmlproperty string MainWindow::applicationName
+
+  The property holds the application's name, which must be the same as the
+  desktop file's name.
+  The name also sets the name of the QCoreApplication and defaults for data
+  and cache folders that work on the desktop and under confinement, as well as
+  the default gettext domain.
+  C++ code that writes files may use QStandardPaths::writableLocation with
+  QStandardPaths::DataLocation or QStandardPaths::CacheLocation.
+*/
+QString UCMainWindow::applicationName() const
+{
+    return d_func()->m_applicationName;
+}
+
+void UCMainWindow::setApplicationName(QString applicationName)
+{
+    Q_D(UCMainWindow);
+
+    if (d->m_applicationName == applicationName)
+        return;
+
+    d->m_applicationName = applicationName;
+
+    if (applicationName != QStringLiteral("")) {
+        UbuntuI18n::instance()->setDomain(applicationName);
+        UCApplication::instance()->setApplicationName(applicationName);
+    }
+    Q_EMIT applicationNameChanged(applicationName);
+}
+
+/*!
+  \qmlproperty string MainWindow::organizationName
+
+  The property holds the optional name of the organization. If set, data
+  folders reside in a subfolder of the organizationName. By default no
+  organizationName is set.
+*/
+QString UCMainWindow::organizationName() const
+{
+    return d_func()->m_organizationName;
+}
+
+
+void UCMainWindow::setOrganizationName(QString organizationName)
+{
+    Q_D(UCMainWindow);
+
+    if (d->m_organizationName == organizationName)
+        return;
+
+    d->m_organizationName = organizationName;
+
+    if (organizationName != QStringLiteral("")) {
+        QCoreApplication::setOrganizationName(organizationName);
+    }
+    Q_EMIT organizationNameChanged(organizationName);
+}
+
+/*!
+  \qmlproperty Units MainWindow::units
+
+  Grid units for this particular window - unlike the global context property
+  by the same name.
+*/
+UCUnits* UCMainWindow::units()
+{
+    Q_D(UCMainWindow);
+
+    if (!d->m_units) {
+        d->m_units = new UCUnits(this);
+        QObject::connect(d->m_units, SIGNAL(gridUnitChanged()),
+                         this, SIGNAL(unitsChanged()));
+    }
+    return d->m_units;
+}
+
+/*!
+  \qmlproperty Units MainWindow::i18n
+
+  The property holds its breath for documentation.
+*/
+UbuntuI18n* UCMainWindow::i18n() const
+{
+    return UbuntuI18n::instance();
+}
+
+/*!
+  \qmlproperty ActionContext MainWindow::actionContext
+  \readonly
+  \since Ubuntu.Components 1.3
+  The action context of the MainWindow.
+  */
+UCPopupContext *UCMainWindow::actionContext() const
+{
+    return d_func()->m_actionContext;
+}
+
+/*!
+  \qmlproperty Item MainWindow::visualRoot
+
+  If set, the property holds the window's visual root, as opposed to root item
+  of the scene.
+  Popups (popovers, dialogs, menus) opened with popupUtils.open() will be
+  children of the visual root, or the root item otherwise.
+  Popups shown via show() may respectively reparent to the visual root, if it
+  is set, or the root item otherwise, if reparentToRootItem is set.
+*/
+QQuickItem *UCMainWindow::visualRoot() const
+{
+    return d_func()->m_visualRoot;
+}
+
+void UCMainWindow::setVisualRoot(QQuickItem *visualRoot)
+{
+    Q_D(UCMainWindow);
+
+    if (d->m_visualRoot == visualRoot)
+        return;
+
+    d->m_visualRoot = visualRoot;
+    Q_EMIT visualRootChanged(visualRoot);
+}
+
+UT_NAMESPACE_END
+
+#include "moc_ucmainwindow_p.cpp"

=== added file 'src/UbuntuToolkit/ucmainwindow_p.h'
--- src/UbuntuToolkit/ucmainwindow_p.h	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/ucmainwindow_p.h	2017-01-30 11:36:29 +0000
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2016-2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UCMAINWINDOW_P_H
+#define UCMAINWINDOW_P_H
+
+#include <QtQuick/QQuickWindow>
+
+#include <UbuntuToolkit/private/i18n_p.h>
+#include <UbuntuToolkit/private/ucunits_p.h>
+
+UT_NAMESPACE_BEGIN
+
+class UCMainWindowPrivate;
+class UCPopupContext;
+class UCAction;
+
+class UBUNTUTOOLKIT_EXPORT UCMainWindow : public QQuickWindow
+{
+    Q_OBJECT
+    Q_PROPERTY(QString applicationName READ applicationName WRITE setApplicationName NOTIFY applicationNameChanged)
+    Q_PROPERTY(QString organizationName READ organizationName WRITE setOrganizationName NOTIFY organizationNameChanged)
+#ifndef Q_QDOC
+    Q_PROPERTY(UT_PREPEND_NAMESPACE(UCUnits)* units READ units NOTIFY unitsChanged)
+    Q_PROPERTY(UT_PREPEND_NAMESPACE(UbuntuI18n)* i18n READ i18n NOTIFY i18nChanged)
+    Q_PROPERTY(UT_PREPEND_NAMESPACE(UCPopupContext)* actionContext READ actionContext NOTIFY actionContextChanged)
+    Q_PROPERTY(UT_PREPEND_NAMESPACE(QQuickItem)* visualRoot READ visualRoot WRITE setVisualRoot NOTIFY visualRootChanged)
+#else
+    Q_PROPERTY(UCUnits* units READ units NOTIFY unitsChanged)
+    Q_PROPERTY(UbuntuI18n* i18n READ i18n NOTIFY i18nChanged)
+    Q_PROPERTY(UCPopupContext* actionContext READ actionContext NOTIFY actionContextChanged)
+    Q_PROPERTY(QQuickItem* visualRoot READ visualRoot WRITE setVisualRoot NOTIFY visualRootChanged)
+#endif
+
+public:
+    UCMainWindow(QWindow *parent = nullptr);
+
+
+    QString applicationName() const;
+    void setApplicationName(QString applicationName);
+    QString organizationName() const;
+    void setOrganizationName(QString organizationName);
+
+    UCUnits* units();
+    UbuntuI18n* i18n() const;
+
+    UCPopupContext* actionContext() const;
+
+    QQuickItem* visualRoot() const;
+    void setVisualRoot(QQuickItem*);
+
+Q_SIGNALS:
+    void applicationNameChanged(QString applicationName);
+    void organizationNameChanged(QString applicationName);
+    void i18nChanged();
+    void unitsChanged();
+#ifndef Q_QDOC
+    void actionContextChanged(UT_PREPEND_NAMESPACE(UCPopupContext)* actionContext);
+    void visualRootChanged(UT_PREPEND_NAMESPACE(QQuickItem)* visualRoot);
+#else
+    void actionContextChanged(UCPopupContext* actionContext);
+    void visualRootChanged(QQuickItem* visualRoot);
+#endif
+
+private:
+    Q_DECLARE_PRIVATE(UCMainWindow)
+};
+
+UT_NAMESPACE_END
+
+#endif // UCMAINWINDOW_P_H

=== added file 'src/UbuntuToolkit/ucmainwindow_p_p.h'
--- src/UbuntuToolkit/ucmainwindow_p_p.h	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/ucmainwindow_p_p.h	2017-01-30 11:36:29 +0000
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UCMAINWINDOW_P_P_H
+#define UCMAINWINDOW_P_P_H
+
+#include <UbuntuToolkit/private/ucmainwindow_p.h>
+
+#include <QtQml/QQmlProperty>
+
+#include <QtQuick/private/qquickwindow_p.h>
+
+UT_NAMESPACE_BEGIN
+
+class UCMainWindow;
+class UCPopupContext;
+
+class UCMainWindowPrivate : public QQuickWindowPrivate
+{
+    Q_DECLARE_PUBLIC(UCMainWindow)
+
+public:
+    UCMainWindowPrivate();
+    void init();
+
+    QString m_applicationName;
+    QString m_organizationName;
+    UCPopupContext* m_actionContext = nullptr;
+    UCUnits* m_units = nullptr;
+    QQuickItem* m_visualRoot = nullptr;
+
+};
+
+UT_NAMESPACE_END
+
+#endif // UCMAINWINDOW_P_P_H

=== modified file 'src/UbuntuToolkit/ucunits.cpp'
--- src/UbuntuToolkit/ucunits.cpp	2016-09-12 09:03:50 +0000
+++ src/UbuntuToolkit/ucunits.cpp	2017-01-30 11:36:29 +0000
@@ -97,23 +97,54 @@
  * isolated from Qt's own scaling concept.
  */
 
+UCUnits::UCUnits(QWindow *parent) :
+    QObject(parent),
+    m_devicePixelRatio(parent->devicePixelRatio())
+{
+    m_gridUnit = getenvFloat(ENV_GRID_UNIT_PX, DEFAULT_GRID_UNIT_PX * m_devicePixelRatio);
+
+    if (!qEnvironmentVariableIsSet(ENV_GRID_UNIT_PX)) {
+        QObject::connect(parent, &QWindow::screenChanged,
+                         this, &UCUnits::screenChanged);
+        m_screen = parent->screen();
+        if (m_screen)
+            QObject::connect(m_screen, &QScreen::physicalDotsPerInchChanged,
+                             this, &UCUnits::devicePixelRatioChanged);
+    }
+}
+
+void UCUnits::screenChanged(QScreen *screen)
+{
+    if (m_screen)
+        QObject::disconnect(m_screen, &QScreen::physicalDotsPerInchChanged,
+                            this, &UCUnits::devicePixelRatioChanged);
+    m_screen = screen;
+    QObject::connect(m_screen, &QScreen::physicalDotsPerInchChanged,
+                     this, &UCUnits::devicePixelRatioChanged);
+    m_devicePixelRatio = screen->devicePixelRatio();
+    setGridUnit(DEFAULT_GRID_UNIT_PX * m_devicePixelRatio);
+}
+
+void UCUnits::devicePixelRatioChanged(qreal dpi)
+{
+    m_devicePixelRatio = dpi;
+    setGridUnit(DEFAULT_GRID_UNIT_PX * m_devicePixelRatio);
+}
+
 UCUnits *UCUnits::m_units = nullptr;
 
 UCUnits::UCUnits(QObject *parent) :
     QObject(parent),
     m_devicePixelRatio(qGuiApp->devicePixelRatio())
 {
-    // If GRID_UNIT_PX set, always use it. If not, 1GU := DEFAULT_GRID_UNIT_PX * m_devicePixelRatio
-    if (qEnvironmentVariableIsSet(ENV_GRID_UNIT_PX)) {
-        m_gridUnit = getenvFloat(ENV_GRID_UNIT_PX, DEFAULT_GRID_UNIT_PX);
-    } else {
-        m_gridUnit = DEFAULT_GRID_UNIT_PX * m_devicePixelRatio;
-    }
+    m_gridUnit = getenvFloat(ENV_GRID_UNIT_PX, DEFAULT_GRID_UNIT_PX * m_devicePixelRatio);
 
-    auto nativeInterface = qGuiApp->platformNativeInterface();
-    if (nativeInterface) {
-        QObject::connect(nativeInterface, &QPlatformNativeInterface::windowPropertyChanged,
-                         this, &UCUnits::windowPropertyChanged);
+    if (!qEnvironmentVariableIsSet(ENV_GRID_UNIT_PX)) {
+        auto nativeInterface = qGuiApp->platformNativeInterface();
+        if (nativeInterface) {
+            QObject::connect(nativeInterface, &QPlatformNativeInterface::windowPropertyChanged,
+                             this, &UCUnits::windowPropertyChanged);
+        }
     }
 }
 

=== modified file 'src/UbuntuToolkit/ucunits_p.h'
--- src/UbuntuToolkit/ucunits_p.h	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/ucunits_p.h	2017-01-30 11:36:29 +0000
@@ -23,6 +23,7 @@
 #include <QtCore/QObject>
 #include <QtCore/QString>
 #include <QtCore/QUrl>
+#include <QtGui/QWindow>
 
 #include <UbuntuToolkit/ubuntutoolkitglobal.h>
 
@@ -48,6 +49,7 @@
     }
 
     explicit UCUnits(QObject *parent = 0);
+    explicit UCUnits(QWindow *parent);
     ~UCUnits();
     Q_INVOKABLE float dp(float value);
     Q_INVOKABLE float gu(float value);
@@ -68,10 +70,13 @@
 
 private Q_SLOTS:
     void windowPropertyChanged(QPlatformWindow *window, const QString &propertyName);
+    void screenChanged(QScreen *screen);
+    void devicePixelRatioChanged(qreal dpi);
 
 private:
     static UCUnits *m_units;
     float m_devicePixelRatio;
+    QScreen *m_screen;
     float m_gridUnit;
 };
 

=== modified file 'src/imports/Components/1.3/TextArea.qml'
--- src/imports/Components/1.3/TextArea.qml	2016-11-10 15:36:49 +0000
+++ src/imports/Components/1.3/TextArea.qml	2017-01-30 11:36:29 +0000
@@ -17,6 +17,7 @@
 import QtQuick 2.4
 import Ubuntu.Components 1.3 as Ubuntu
 import Ubuntu.Components.Popups 1.3
+import Ubuntu.Components.Private 1.3 as Private
 
 /*!
     \qmltype TextArea
@@ -581,6 +582,16 @@
     }
 
     /*!
+      Appends a new paragraph with text to the end of the TextArea.
+
+      In order to append without inserting a new paragraph, call TextArea.insert(TextArea.length, text) instead.
+      */
+    function append(text)
+    {
+        editor.append(text);
+    }
+
+    /*!
       Returns the text position closest to pixel position (x, y).
 
       Position 0 is before the first character, position 1 is after the first
@@ -736,6 +747,15 @@
         editor.linkActivated.connect(control.linkActivated);
     }
 
+    Connections {
+        target: Private.UCContentHub
+        onPasteSelected: {
+            if (targetItem === control) {
+                control.paste(data)
+            }
+        }
+    }
+
     // activation area on mouse click
     // the editor activates automatically when pressed in the editor control,
     // however that one can be slightly spaced to the main control area
@@ -806,6 +826,11 @@
                 control.paste("\n");
             }
             event.accepted = true;
+        } else if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)){
+            if (Private.UCContentHub.canPaste) {
+                Private.UCContentHub.requestPaste(control);
+                event.accepted = true;
+            }
         } else {
             event.accepted = false;
         }

=== modified file 'src/imports/Components/1.3/TextField.qml'
--- src/imports/Components/1.3/TextField.qml	2016-09-20 07:23:24 +0000
+++ src/imports/Components/1.3/TextField.qml	2017-01-30 11:36:29 +0000
@@ -17,6 +17,7 @@
 import QtQuick 2.4
 import Ubuntu.Components 1.3 as Ubuntu
 import Ubuntu.Components.Popups 1.3
+import Ubuntu.Components.Private 1.3 as Private
 
 /*!
     \qmltype TextField
@@ -637,7 +638,6 @@
         control.triggered(control.text)
     }
 
-
     /*!
       Copies the currently selected text to the system clipboard.
     */
@@ -834,6 +834,28 @@
 
     // internals
 
+    Connections {
+        target: Private.UCContentHub
+        onPasteSelected: {
+            if (targetItem === control) {
+                control.paste(data)
+            }
+        }
+    }
+
+    Keys.onPressed: {
+        if (readOnly)
+            return;
+        if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)){
+            if (Private.UCContentHub.canPaste) {
+                Private.UCContentHub.requestPaste(control);
+                event.accepted = true;
+            }
+        } else {
+            event.accepted = false;
+        }
+    }
+
     // Overload focus mechanics to avoid bubbling up of focus from children
     activeFocusOnPress: true
 

=== modified file 'src/imports/Components/1.3/TextInputPopover.qml'
--- src/imports/Components/1.3/TextInputPopover.qml	2015-12-14 15:15:46 +0000
+++ src/imports/Components/1.3/TextInputPopover.qml	2017-01-30 11:36:29 +0000
@@ -17,6 +17,7 @@
 import QtQuick 2.4
 import Ubuntu.Components 1.3
 import Ubuntu.Components.Popups 1.3
+import Ubuntu.Components.Private 1.3 as Private
 
 Popover {
     id: popover
@@ -54,12 +55,12 @@
             }
         },
         Action {
-            text: i18n.dtr('ubuntu-ui-toolkit', "Paste")
+            text: i18n.dtr('ubuntu-ui-toolkit', "Paste...")
             iconName: "edit-paste"
-            enabled: target && target.canPaste
+            enabled: target && Private.UCContentHub.canPaste
             onTriggered: {
                 PopupUtils.close(popover);
-                target.paste();
+                Private.UCContentHub.requestPaste(target);
             }
         }
     ]

=== modified file 'src/imports/Components/1.3/Toolbar.qml'
--- src/imports/Components/1.3/Toolbar.qml	2016-05-25 12:48:10 +0000
+++ src/imports/Components/1.3/Toolbar.qml	2017-01-30 11:36:29 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Canonical Ltd.
+ * Copyright 2016 Canonical Ltd.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
@@ -127,6 +127,17 @@
                 print("WARNING: Toolbar with more than one leading actions is not supported.");
             }
         }
+        StyleHints {
+            backgroundColor: "transparent" // background is drawn by the toolbarStyle
+            buttons {
+                foregroundColor: toolbar.__styleInstance.buttons.foregroundColor
+                pressedForegroundColor: toolbar.__styleInstance.buttons.pressedForegroundColor
+                disabledForegroundColor: toolbar.__styleInstance.buttons.disabledForegroundColor
+                backgroundColor: toolbar.__styleInstance.buttons.backgroundColor
+                pressedBackgroundColor: toolbar.__styleInstance.buttons.pressedBackgroundColor
+                disabledBackgroundColor: toolbar.__styleInstance.buttons.disabledBackgroundColor
+            }
+        }
     }
 
     /*!
@@ -150,19 +161,29 @@
     readonly property alias trailingActionBar: trailing
     ActionBar {
         id: trailing
+        styleName: "ScrollingActionBarStyle"
         anchors {
+            left: leading.right
             right: parent.right
             top: parent.top
             bottom: parent.bottom
+            leftMargin: units.gu(1)
             rightMargin: units.gu(1)
         }
-        numberOfSlots: 8
         delegate: toolbar.__styleInstance.defaultDelegate
-        Component.onCompleted: {
-            if (actions && actions.length > 8) {
-                print("WARNING: Toolbar with more than one leading actions is not supported.");
+        StyleHints {
+            backgroundColor: "transparent" // background is drawn by the toolbarStyle
+            buttons {
+                foregroundColor: toolbar.__styleInstance.buttons.foregroundColor
+                pressedForegroundColor: toolbar.__styleInstance.buttons.pressedForegroundColor
+                disabledForegroundColor: toolbar.__styleInstance.buttons.disabledForegroundColor
+                backgroundColor: toolbar.__styleInstance.buttons.backgroundColor
+                pressedBackgroundColor: toolbar.__styleInstance.buttons.pressedBackgroundColor
+                disabledBackgroundColor: toolbar.__styleInstance.buttons.disabledBackgroundColor
+            }
+            scrollButtons {
+                backgroundColor: toolbar.__styleInstance.backgroundColor // must be opaque to hide the icon buttons
             }
         }
-
     }
 }

=== modified file 'src/imports/Components/1.3/UbuntuListView.qml'
--- src/imports/Components/1.3/UbuntuListView.qml	2016-09-22 17:39:59 +0000
+++ src/imports/Components/1.3/UbuntuListView.qml	2017-01-30 11:36:29 +0000
@@ -199,10 +199,10 @@
         color: root.activeFocus
                ? theme.palette.focused.background
                : theme.palette.selected.background
-        width: root.currentItem.width
-        height: root.currentItem.height
+        width: root.currentItem ? root.currentItem.width : 0
+        height: root.currentItem ? root.currentItem.height : 0
         // FIXME: use opacity yet, until we fix the palette's disabled.background color
-        opacity: root.currentItem.enabled ? 1.0 : 0.5
+        opacity: root.currentItem && root.currentItem.enabled ? 1.0 : 0.5
     }
     highlightMoveDuration: 50
 }

=== modified file 'src/imports/Components/Popups/1.3/PopupBase.qml'
--- src/imports/Components/Popups/1.3/PopupBase.qml	2016-04-13 19:32:20 +0000
+++ src/imports/Components/Popups/1.3/PopupBase.qml	2017-01-30 11:36:29 +0000
@@ -119,6 +119,14 @@
 
     /*!
       \internal
+      The function saves the active focus for later.
+      */
+    function __setPreviousActiveFocusItem(item) {
+        stateWrapper.prevFocus = item;
+    }
+
+    /*!
+      \internal
       Foreground component excluded from InverseMouseArea
       */
     property Item __foreground
@@ -177,30 +185,14 @@
     /*! \internal */
     onParentChanged: stateWrapper.rootItem = QuickUtils.rootItem(popupBase)
     Component.onCompleted: {
-        stateWrapper.saveActiveFocus();
         stateWrapper.rootItem = QuickUtils.rootItem(popupBase);
     }
 
     Item {
         id: stateWrapper
         property Item rootItem: QuickUtils.rootItem(popupBase)
-
-        property bool windowIsValid: typeof window != "undefined"
         property Item prevFocus
 
-        function saveActiveFocus() {
-            // 'window' context property is exposed to QML after component completion
-            // before rendering is complete, therefore a simple 'if (window)' check is
-            // not enough.
-            if (windowIsValid) {
-                prevFocus = window.activeFocusItem;
-                windowIsValidChanged.disconnect(saveActiveFocus);
-            } else {
-                // connect the function so we can save the original focus item
-                windowIsValidChanged.connect(saveActiveFocus);
-            }
-        }
-
         function restoreActiveFocus() {
             if (prevFocus) {
                 if (prevFocus.hasOwnProperty("requestFocus")) {

=== modified file 'src/imports/Components/Popups/1.3/popupUtils.js'
--- src/imports/Components/Popups/1.3/popupUtils.js	2016-09-15 15:53:27 +0000
+++ src/imports/Components/Popups/1.3/popupUtils.js	2017-01-30 11:36:29 +0000
@@ -32,7 +32,12 @@
   \a caller should be given when a \l ComposerSheet or \l Dialog is specified using a URL
   and opened inside a \b Window. If not, the application's root item will be the dismiss area.
 
-  Returns a popop object, which can be closed using \l close.
+  Returns a popup object, which can be closed using \l close.
+
+  Note that popups created from a file or component will be created as children
+  of the root item. If that's not a good choice, a \b MainWindow used as the
+  root item can set the \b visualRoot property an arbitrary \b Item that acts
+  as the parent of all popups.
 
   \qml
       import Ubuntu.Components 1.3
@@ -66,6 +71,8 @@
     }
 
     var popupObject;
+    // If there's an active item, save it so we can restore it later
+    var prevFocusItem = (typeof window !== "undefined") ? window.activeFocusItem : null;
     if (params !== undefined) {
         popupObject = popupComponent.createObject(rootObject, params);
     } else {
@@ -75,8 +82,11 @@
         print(popupComponent.errorString().slice(0, -1));
         print("PopupUtils.open(): Failed to create the popup object.");
         return;
-    } else if (popupObject.hasOwnProperty("caller") && caller)
+    } else if (popupObject.hasOwnProperty("caller") && caller) {
         popupObject.caller = caller;
+    } else if (popupObject.hasOwnProperty("__setPreviousActiveFocusItem")) {
+        popupObject.__setPreviousActiveFocusItem(prevFocusItem);
+    }
 
     // if caller is specified, connect its cleanup to the popup's close
     // so popups will be removed together with the caller.

=== modified file 'src/imports/Components/Styles/1.3/ToolbarStyle.qml'
--- src/imports/Components/Styles/1.3/ToolbarStyle.qml	2015-12-02 13:02:44 +0000
+++ src/imports/Components/Styles/1.3/ToolbarStyle.qml	2017-01-30 11:36:29 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Canonical Ltd.
+ * Copyright 2016 Canonical Ltd.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
@@ -25,6 +25,16 @@
   */
 Item {
     /*!
+      The color of the background of the action bar.
+     */
+    property color backgroundColor
+
+    /*!
+      Configuration of the colors of the action buttons in the action bar.
+     */
+    readonly property ActionItemProperties buttons: ActionItemProperties { }
+
+    /*!
       The default action delegate if the styled item does
       not provide a different delegate.
      */

=== modified file 'src/imports/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml'
--- src/imports/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml	2016-10-03 14:07:38 +0000
+++ src/imports/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml	2017-01-30 11:36:29 +0000
@@ -113,7 +113,11 @@
             model: listViewContainer.visibleActions
 
             highlight: Rectangle {
-                color: theme.palette.focused.background
+                color: "transparent"
+                border {
+                    color: theme.palette.selected.focus
+                    width: 2
+                }
                 visible: actionsListView.activeFocus
                 width: actionsListView.currentItem.width
                 height: actionsListView.currentItem.height

=== modified file 'src/imports/Components/Themes/Ambiance/1.3/ToolbarStyle.qml'
--- src/imports/Components/Themes/Ambiance/1.3/ToolbarStyle.qml	2016-09-19 07:24:45 +0000
+++ src/imports/Components/Themes/Ambiance/1.3/ToolbarStyle.qml	2017-01-30 11:36:29 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Canonical Ltd.
+ * Copyright 2016 Canonical Ltd.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
@@ -24,16 +24,49 @@
     // reduce toolbar height on phone in landscape orientation:
     implicitHeight: Screen.height > units.gu(50) ? units.gu(4) : units.gu(3)
 
+    backgroundColor: theme.palette.normal.background
+    buttons {
+        foregroundColor: theme.palette.normal.backgroundText
+        pressedForegroundColor: buttons.foregroundColor
+        disabledForegroundColor: theme.palette.disabled.backgroundText
+        backgroundColor: "transparent" // background is already colored
+        pressedBackgroundColor: theme.palette.highlighted.background
+        disabledBackgroundColor: buttons.backgroundColor
+    }
+
     /*!
       The default action delegate if the styled item does
       not provide a delegate.
      */
-    defaultDelegate: AbstractButton {
-        style: IconButtonStyle { }
-        objectName: action.objectName + "_button"
-        height: parent ? parent.height : undefined
+    defaultDelegate: ListItem {
         width: units.gu(4)
-        action: modelData
-        activeFocusOnTab: true
+        height: toolbarStyle.height
+        enabled: modelData.enabled
+        objectName: modelData.objectName + "_button"
+        onClicked: button.trigger()
+        AbstractButton {
+            id: button
+            anchors.fill: parent
+            style: IconButtonStyle {
+                foregroundColor: button.pressed ?
+                                     toolbarStyle.buttons.pressedForegroundColor :
+                                     button.enabled ?
+                                         toolbarStyle.buttons.foregroundColor :
+                                         toolbarStyle.buttons.disabledForegroundColor
+                backgroundColor: button.pressed ?
+                                     toolbarStyle.buttons.pressedBackgroundColor :
+                                     button.enabled ?
+                                         toolbarStyle.buttons.backgroundColor :
+                                         toolbarStyle.buttons.disabledBackgroundColor
+            }
+            action: modelData
+            activeFocusOnTab: false
+        }
+        divider.visible: false
+    }
+
+    Rectangle {
+        anchors.fill: parent
+        color: toolbarStyle.backgroundColor
     }
 }

=== modified file 'tests/autopilot/ubuntuuitoolkit/__init__.py'
--- tests/autopilot/ubuntuuitoolkit/__init__.py	2016-04-21 03:23:24 +0000
+++ tests/autopilot/ubuntuuitoolkit/__init__.py	2017-01-30 11:36:29 +0000
@@ -18,7 +18,6 @@
 
 from ubuntuuitoolkit import (
     base,
-    environment,
     fixture_setup,
     tests,
     ubuntu_scenarios
@@ -59,7 +58,6 @@
     'ActionBar',
     'check_autopilot_version',
     'CheckBox',
-    'environment',
     'fixture_setup',
     'get_keyboard',
     'get_pointing_device',

=== removed file 'tests/autopilot/ubuntuuitoolkit/environment.py'
--- tests/autopilot/ubuntuuitoolkit/environment.py	2014-08-28 19:05:23 +0000
+++ tests/autopilot/ubuntuuitoolkit/environment.py	1970-01-01 00:00:00 +0000
@@ -1,68 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-#
-# Copyright (C) 2014 Canonical Ltd.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation; version 3.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import subprocess
-
-from autopilot import logging as autopilot_logging
-
-
-logger = logging.getLogger(__name__)
-
-
-def is_initctl_env_var_set(variable, global_=False):
-    """Check True if an initctl environment variable is set.
-
-    :param variable: The name of the variable to check.
-    :param global: if True, the method will operate on the global environment
-        table. Default is False.
-    :return: True if the variable is set. False otherwise.
-
-    """
-    try:
-        get_initctl_env_var(variable, global_)
-        return True
-    except subprocess.CalledProcessError:
-        return False
-
-
-def get_initctl_env_var(variable, global_=False):
-    """Return the value of an initctl environment variable."""
-    command = ['/sbin/initctl', 'get-env', variable]
-    if global_:
-        command += ['--global']
-    output = subprocess.check_output(
-        command, stderr=subprocess.STDOUT, universal_newlines=True)
-    return output.rstrip()
-
-
-@autopilot_logging.log_action(logger.info)
-def set_initctl_env_var(variable, value, global_=False):
-    """Set the value of an initctl environment variable."""
-    command = ['/sbin/initctl', 'set-env', '%s=%s' % (variable, value)]
-    if global_:
-        command += ['--global']
-    subprocess.call(command, stderr=subprocess.STDOUT, universal_newlines=True)
-
-
-@autopilot_logging.log_action(logger.info)
-def unset_initctl_env_var(variable, global_=False):
-    """Remove an initctl environment variable."""
-    command = ['/sbin/initctl', 'unset-env', variable]
-    if global_:
-        command += ['--global']
-    subprocess.call(
-        command, stderr=subprocess.STDOUT, universal_newlines=True)

=== modified file 'tests/autopilot/ubuntuuitoolkit/fixture_setup.py'
--- tests/autopilot/ubuntuuitoolkit/fixture_setup.py	2015-07-09 21:55:03 +0000
+++ tests/autopilot/ubuntuuitoolkit/fixture_setup.py	2017-01-30 11:36:29 +0000
@@ -26,7 +26,7 @@
 from autopilot import display
 from gi.repository import Gio
 
-from ubuntuuitoolkit import base, environment
+from ubuntuuitoolkit import base
 
 
 DEFAULT_QML_FILE_CONTENTS = ("""
@@ -156,45 +156,6 @@
             url_dispatcher_file_path, shell=True)
 
 
-class InitctlEnvironmentVariable(fixtures.Fixture):
-    """Set the value of initctl environment variables."""
-
-    def __init__(self, global_=False, **kwargs):
-        super().__init__()
-        # Added one level of indirection to be able to spy the calls to
-        # environment during tests.
-        self.environment = environment
-        self.variables = kwargs
-        self.global_ = global_
-
-    def setUp(self):
-        super().setUp()
-        for variable, value in self.variables.items():
-            self._add_variable_cleanup(variable)
-            if value is None:
-                self.environment.unset_initctl_env_var(
-                    variable, global_=self.global_)
-            else:
-                self.environment.set_initctl_env_var(
-                    variable, value, global_=self.global_)
-
-    def _add_variable_cleanup(self, variable):
-        if self.environment.is_initctl_env_var_set(
-                variable, global_=self.global_):
-            original_value = self.environment.get_initctl_env_var(
-                variable, global_=self.global_)
-            self.addCleanup(
-                self.environment.set_initctl_env_var,
-                variable,
-                original_value,
-                global_=self.global_)
-        else:
-            self.addCleanup(
-                self.environment.unset_initctl_env_var,
-                variable,
-                global_=self.global_)
-
-
 class FakeHome(fixtures.Fixture):
 
     # We copy the Xauthority file to allow executions using XVFB. If it is not
@@ -210,10 +171,6 @@
         self.directory = self._make_directory_if_not_specified()
         if self.should_copy_xauthority_file:
             self._copy_xauthority_file(self.directory)
-        # We patch both environment variables so it works on the desktop and on
-        # the phone.
-        self.useFixture(
-            InitctlEnvironmentVariable(HOME=self.directory))
         self.useFixture(
             fixtures.EnvironmentVariable('HOME', newvalue=self.directory))
 

=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/__init__.py'
--- tests/autopilot/ubuntuuitoolkit/tests/__init__.py	2015-12-09 14:21:53 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/__init__.py	2017-01-30 11:36:29 +0000
@@ -16,6 +16,7 @@
 
 """Ubuntu UI Toolkit autopilot tests."""
 
+import fixtures
 import os
 import tempfile
 
@@ -106,9 +107,7 @@
         desktop_file_name = os.path.basename(
             fake_application.desktop_file_path)
         application_name, _ = os.path.splitext(desktop_file_name)
-        self.app = self.launch_upstart_application(
-            application_name,
-            emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+        self.app = self.launch_test_application(application_name)
 
     def use_local_modules(self, local_modules_path):
         env_vars = [
@@ -116,10 +115,9 @@
             'QML2_IMPORT_PATH',
             'UBUNTU_UI_TOOLKIT_THEMES_PATH'
         ]
-        kwargs = {'global_': True}
         for env in env_vars:
-            kwargs[env] = local_modules_path
-        self.useFixture(fixture_setup.InitctlEnvironmentVariable(**kwargs))
+            self.useFixture(fixtures.EnvironmentVariable(env,
+                                                         local_modules_path))
 
 
 class QMLStringAppTestCase(UbuntuUIToolkitWithFakeAppRunningTestCase):

=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/components/test_units.py'
--- tests/autopilot/ubuntuuitoolkit/tests/components/test_units.py	2015-04-14 21:02:06 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/components/test_units.py	2017-01-30 11:36:29 +0000
@@ -23,10 +23,7 @@
 
 import fixtures
 
-from ubuntuuitoolkit import (
-    fixture_setup,
-    units
-)
+from ubuntuuitoolkit import units
 
 
 logger = logging.getLogger(__name__)
@@ -52,8 +49,6 @@
     def setUp(self):
         self.useFixture(fixtures.EnvironmentVariable(
             'GRID_UNIT_PX', self.grid_unit_px))
-        self.useFixture(fixture_setup.InitctlEnvironmentVariable(
-            global_=True, GRID_UNIT_PX=self.grid_unit_px))
         super().setUp()
 
     def test_gu(self):

=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/gallery/__init__.py'
--- tests/autopilot/ubuntuuitoolkit/tests/gallery/__init__.py	2016-08-10 14:05:48 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/gallery/__init__.py	2017-01-30 11:36:29 +0000
@@ -17,7 +17,6 @@
 """Tests for the Ubuntu UI Toolkit Gallery"""
 
 import os
-import shutil
 
 from autopilot.matchers import Eventually
 from testtools.matchers import Equals

=== removed file 'tests/autopilot/ubuntuuitoolkit/tests/test_environment.py'
--- tests/autopilot/ubuntuuitoolkit/tests/test_environment.py	2014-08-29 16:12:02 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/test_environment.py	1970-01-01 00:00:00 +0000
@@ -1,89 +0,0 @@
-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
-#
-# Copyright (C) 2014 Canonical Ltd.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation; version 3.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import uuid
-
-import testtools
-
-from ubuntuuitoolkit import environment
-
-
-class InitctlEnvironmentVariableTestCase(testtools.TestCase):
-
-    def test_is_environment_variable_set_with_unset_variable(self):
-        """Test that is_initctl_env_var_set returns False for unset vars."""
-        variable = 'I do not exist {}'.format(uuid.uuid1())
-        self.assertFalse(environment.is_initctl_env_var_set(variable))
-
-    def test_is_environment_variable_set_with_set_variable(self):
-        """Test that is_initctl_env_var_set returns True for existing vars."""
-        variable = 'Test variable to set {}'.format(uuid.uuid1())
-        self.addCleanup(environment.unset_initctl_env_var, variable)
-
-        environment.set_initctl_env_var(variable, 'dummy')
-
-        self.assertTrue(environment.is_initctl_env_var_set(variable))
-
-    def test_get_environment_variable(self):
-        """Test that get_initctl_env_var returns the right value."""
-        variable = 'Test variable to get {}'.format(uuid.uuid1())
-        self.addCleanup(environment.unset_initctl_env_var, variable)
-        environment.set_initctl_env_var(variable, 'test value')
-
-        self.assertEqual(
-            'test value', environment.get_initctl_env_var(variable))
-
-    def test_unset_environment_variable(self):
-        """Test that unset_initctl_env_var removes the variable."""
-        variable = 'Test variable to unset {}'.format(uuid.uuid1())
-        environment.set_initctl_env_var(variable, 'dummy')
-
-        environment.unset_initctl_env_var(variable)
-
-        self.assertFalse(environment.is_initctl_env_var_set(variable))
-
-    def test_unset_environment_variable_with_unset_variable(self):
-        """Test that unset_initctl_env_var does nothing with unset var."""
-        variable = 'I do not exist {}'.format(uuid.uuid1())
-
-        environment.unset_initctl_env_var(variable)
-
-        self.assertFalse(environment.is_initctl_env_var_set(variable))
-
-    def test_is_global_environment_variable_set_with_unset_variable(self):
-        """Test is_initctl_env_var_set returns False for unset global vars."""
-        variable = 'I do not exist global {}'.format(uuid.uuid1())
-
-        self.assertFalse(environment.is_initctl_env_var_set(
-            variable, global_=True))
-
-    def test_get_global_environment_variable(self):
-        """Test that get_initctl_env_var returns the right global value."""
-        variable = 'Test variable to get {}'.format(uuid.uuid1())
-        self.addCleanup(
-            environment.unset_initctl_env_var, variable, global_=True)
-        environment.set_initctl_env_var(variable, 'test value', global_=True)
-
-        self.assertEqual(
-            'test value',
-            environment.get_initctl_env_var(variable, global_=True))
-
-    def test_unset_global_environment_variable(self):
-        """Test that unset_initctl_env_var removes the global variable."""
-        variable = 'Test variable to unset {}'.format(uuid.uuid1())
-
-        environment.set_initctl_env_var(variable, 'dummy', global_=True)
-        environment.unset_initctl_env_var(variable, global_=True)

=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py'
--- tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py	2015-07-09 21:55:03 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/test_fixture_setup.py	2017-01-30 11:36:29 +0000
@@ -19,18 +19,16 @@
 import tempfile
 
 from unittest import mock
-import testscenarios
 import testtools
 from autopilot import (
     display,
-    introspection,
     platform,
     testcase as autopilot_testcase
 )
 from autopilot.matchers import Eventually
 from testtools.matchers import Contains, Equals, FileExists, Not
 
-from ubuntuuitoolkit import base, environment, fixture_setup, tests
+from ubuntuuitoolkit import base, fixture_setup, tests
 
 
 class FakeApplicationTestCase(testtools.TestCase):
@@ -190,9 +188,6 @@
             url_dispatcher_protocols=['testprotocol'])
         self.useFixture(fake_application)
 
-        self.useFixture(fixture_setup.InitctlEnvironmentVariable(
-            QT_LOAD_TESTABILITY=1))
-
         self.addCleanup(
             subprocess.check_output,
             ['ubuntu-app-stop', fake_application.application_name])
@@ -202,135 +197,11 @@
 
         pid = int(subprocess.check_output(
             ['ubuntu-app-pid', fake_application.application_name]).strip())
-
-        application = introspection.get_proxy_object_for_existing_process(
-            pid=pid)
-
-        # We can select a component from the application.
-        application.select_single('Label', objectName='testLabel')
-
-
-class InitctlEnvironmentVariableTestCase(testscenarios.TestWithScenarios):
-
-    scenarios = [
-        ('global_variable', {'global_': True}),
-        ('local_variable', {'global_': False})
-    ]
-
-    def set_original_value(self, value):
-        self.addCleanup(
-            environment.unset_initctl_env_var, 'testenvvarforfixture',
-            global_=self.global_)
-        environment.set_initctl_env_var('testenvvarforfixture',
-                                        value, global_=self.global_)
-
-    def create_fixture(self, value):
-        self.initctl_env_var = fixture_setup.InitctlEnvironmentVariable(
-            testenvvarforfixture=value, global_=self.global_)
-
-    def assertValueIs(self, expected_value):
-        self.assertEqual(
-            expected_value,
-            environment.get_initctl_env_var(
-                'testenvvarforfixture', global_=self.global_))
-
-    def assertVariableIsNotSet(self):
-        self.assertFalse(
-            environment.is_initctl_env_var_set(
-                'testenvvarforfixture', global_=self.global_))
-
-    def assertTestIsSuccessful(self, expected_value, test_name):
-        result = testtools.TestResult()
-
-        class TestWithInitctlEnvVar(testtools.TestCase):
-            def setUp(inner):
-                super().setUp()
-                inner.useFixture(self.initctl_env_var)
-
-            def test_value_set(inner):
-                self.assertValueIs(expected_value)
-
-            def test_variable_not_set(inner):
-                self.assertVariableIsNotSet()
-
-        TestWithInitctlEnvVar(test_name).run(result)
-        self.assertTrue(
-            result.wasSuccessful(), 'Failed to set the environment variable.')
-
-    def test_use_initctl_environment_variable_to_set_unexisting_variable(self):
-        """Test the initctl env var fixture when the var is unset.
-
-        During the test, the new value must be in place.
-        After the test, the variable must be unset again.
-
-        """
-        self.create_fixture('test value')
-        self.assertTestIsSuccessful('test value', 'test_value_set')
-        self.assertVariableIsNotSet()
-
-    def test_use_initctl_environment_variable_to_set_existing_variable(self):
-        """Test the initctl env var fixture when the var is unset.
-
-        During the test, the new value must be in place.
-        After the test, the old value must be set again.
-
-        """
-        self.set_original_value('original test value')
-        self.create_fixture('new test value')
-        self.assertTestIsSuccessful('new test value', 'test_value_set')
-        self.assertValueIs('original test value')
-
-    def test_use_initctl_environment_variable_to_unset_existing_variable(self):
-        """Test the initctl env var fixture to unset a variable.
-
-        During the test, the variable must be unset.
-        After the test, the old value must be set again.
-
-        """
-        self.set_original_value('original test value')
-        self.create_fixture(None)
-        self.assertTestIsSuccessful(None, 'test_variable_not_set')
-        self.assertValueIs('original test value',)
-
-    def test_use_initctl_environment_variable_to_unset_nonexisting_variable(
-            self):
-        """Test the initctl env var fixture to unset a variable.
-
-        During the test, the variable must be unset.
-        After the test, the variable must remain unset.
-
-        """
-        self.create_fixture(None)
-        self.assertTestIsSuccessful(None, 'test_variable_not_set')
-        self.assertVariableIsNotSet()
+        self.assertGreater(pid, 0)
 
 
 class FakeHomeTestCase(testtools.TestCase):
 
-    def test_fake_home_fixture_patches_initctl_env_var(self):
-        original_home = environment.get_initctl_env_var('HOME')
-        fake_home = original_home + 'fake'
-        result = testtools.TestResult()
-
-        def inner_test():
-            class TestWithFakeHome(testtools.TestCase):
-                def test_it(self):
-                    fake_home_fixture = fixture_setup.FakeHome(fake_home)
-                    fake_home_fixture.should_copy_xauthority_file = False
-                    self.useFixture(fake_home_fixture)
-                    self.assertEqual(
-                        fake_home, environment.get_initctl_env_var('HOME'))
-            return TestWithFakeHome('test_it')
-
-        inner_test().run(result)
-
-        self.assertTrue(
-            result.wasSuccessful(),
-            'Failed to fake the home: {}'.format(result.errors))
-        self.assertEqual(
-            original_home,
-            environment.get_initctl_env_var('HOME'))
-
     def test_fake_home_fixture_patches_env_var(self):
         original_home = os.environ.get('HOME')
         fake_home = tempfile.gettempdir()

=== renamed file 'tests/autopilot/ubuntuuitoolkit/tests/test_launcher.window.qml' => 'tests/autopilot/ubuntuuitoolkit/tests/test_launcher.mainwindow.qml'
--- tests/autopilot/ubuntuuitoolkit/tests/test_launcher.window.qml	2016-07-12 12:05:11 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/test_launcher.mainwindow.qml	2017-01-30 11:36:29 +0000
@@ -17,23 +17,30 @@
 import QtQuick 2.4
 import QtQuick.Window 2.2 // Not Ubuntu.Test
 import Ubuntu.Components 1.3
+import Ubuntu.Components.Labs 1.3
 
-Window {
-    title: "Hello World"
+MainWindow {
+    title: i18n.tr("Hello World")
     minimumWidth: units.gu(30)
     minimumHeight: units.gu(50)
     maximumWidth: units.gu(90)
     maximumHeight: units.gu(120)
-    MainView {
+
+    Item {
+        anchors.fill: parent
         objectName: "mainView"
 
         Page {
-            title: "Launcher/Window"
+            anchors.fill: parent
+            header: PageHeader {
+                title: "Launcher/Window"
+            }
 
             Column {
+                anchors.top: parent.header.bottom
                 Label {
                     objectName: "label"
-                    text: "Lorem ipsum dolor sit amet"
+                    text: i18n.tr("Lorem ipsum dolor sit amet")
                     width: units.gu(25)
                     height: units.gu(25)
                 }

=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/test_launcher.py'
--- tests/autopilot/ubuntuuitoolkit/tests/test_launcher.py	2016-07-12 12:05:11 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/test_launcher.py	2017-01-30 11:36:29 +0000
@@ -49,6 +49,18 @@
                         Eventually(Equals("Lorem ipsum dolor sit amet")))
 
 
+class LauncherMainWindowTestCase(tests.QMLFileAppTestCase):
+    path = os.path.abspath(__file__)
+    dir_path = os.path.dirname(path)
+    test_qml_file_path = os.path.join(
+        dir_path, 'test_launcher.mainwindow.qml')
+
+    def test_window_root_item(self):
+        label = self.main_view.select_single(objectName="label")
+        self.assertThat(label.text,
+                        Eventually(Equals("Lorem ipsum dolor sit amet")))
+
+
 class LauncherQtTestTestCase(tests.QMLFileAppTestCase):
     path = os.path.abspath(__file__)
     dir_path = os.path.dirname(path)

=== added file 'tests/autopilot/ubuntuuitoolkit/tests/test_launcher.window.qml'
--- tests/autopilot/ubuntuuitoolkit/tests/test_launcher.window.qml	1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/test_launcher.window.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014-2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import QtQuick.Window 2.2 // Not Ubuntu.Test
+import Ubuntu.Components 1.3
+
+Window {
+    title: "Hello World"
+    minimumWidth: units.gu(30)
+    minimumHeight: units.gu(50)
+    maximumWidth: units.gu(90)
+    maximumHeight: units.gu(120)
+    MainView {
+        objectName: "mainView"
+
+        Page {
+            title: "Launcher/Window"
+
+            Column {
+                Label {
+                    objectName: "label"
+                    text: "Lorem ipsum dolor sit amet"
+                    width: units.gu(25)
+                    height: units.gu(25)
+                }
+            }
+        }
+    }
+}

=== modified file 'tests/autopilot/ubuntuuitoolkit/units.py'
--- tests/autopilot/ubuntuuitoolkit/units.py	2015-02-07 05:03:06 +0000
+++ tests/autopilot/ubuntuuitoolkit/units.py	2017-01-30 11:36:29 +0000
@@ -18,18 +18,12 @@
 
 import os
 
-from ubuntuuitoolkit import environment
-
-
 ENV_GRID_UNIT_PX = 'GRID_UNIT_PX'
 DEFAULT_GRID_UNIT_PX = 8
 
 
 def get_grid_unit():
     grid_unit_px = os.environ.get(ENV_GRID_UNIT_PX, None)
-    if not grid_unit_px and environment.is_initctl_env_var_set(
-            ENV_GRID_UNIT_PX):
-        grid_unit_px = environment.get_initctl_env_var(ENV_GRID_UNIT_PX)
     return float(grid_unit_px or DEFAULT_GRID_UNIT_PX)
 
 

=== modified file 'tests/license/checklicense.sh'
--- tests/license/checklicense.sh	2015-11-09 15:13:21 +0000
+++ tests/license/checklicense.sh	2017-01-30 11:36:29 +0000
@@ -18,7 +18,7 @@
 
 include_files="\.(c(c|pp|xx)?|h(h|pp|xx)?|p(l|m)|php|py(|x)|java|js|vala|qml)$"
 exclude_dirs="(3rd_party|qrc_|moc_|_build|include)"
-allowed_licenses="(Canonical|Android|Google|Digia)"
+allowed_licenses="(Canonical|Android|Google|Digia|Qt Company Ltd)"
 issues_count=`licensecheck --noconf -r * --copyright -m -c $include_files -i $exclude_dirs | egrep -v $allowed_licenses | wc -l`
 
 if [ $issues_count -eq 0 ]; then

=== modified file 'tests/packaging-sorting.sh'
--- tests/packaging-sorting.sh	2016-05-02 08:37:47 +0000
+++ tests/packaging-sorting.sh	2017-01-30 11:36:29 +0000
@@ -15,28 +15,30 @@
 #
 # Author: Timo Jyrinki <timo.jyrinki@xxxxxxxxxxxxx>
 
+# Get the current script directory (compatible with Bash and ZSH)
+SCRIPT_DIR=`dirname ${BASH_SOURCE[0]-$0}`
+SCRIPT_DIR=`cd $SCRIPT_DIR && pwd`
+
+SRC=$SCRIPT_DIR/..
 # Ensure packaging has gone through wrap-and-sort command
-
-if [ ! -f  "/usr/bin/wrap-and-sort" ] ; then
-  echo "Please install 'devscripts' package"
-  exit 1
-fi
-
-cd $(dirname $0)
 tmpdir=$(mktemp -d)
-cp -a ../debian $tmpdir
-
+cp -a $SRC/debian $tmpdir
 wrap-and-sort -a -t -d $tmpdir/debian/
-# Verify control.gles which otherwise isn't picked up
-wrap-and-sort -a -t -d $tmpdir/debian/ -f $tmpdir/debian/control.gles
+[ $? == 2 ] && exit 2
+# Note: control.gles may be moved in gles builds
+if [ -f $tmpdir/debian/control.gles ] ; then
+ # Verify control.gles which otherwise isn't picked up
+ wrap-and-sort -a -t -d $tmpdir/debian/ -f $tmpdir/debian/control.gles
+fi
+[ $? == 2 ] && exit 2
 
-diff -urN ../debian $tmpdir/debian
+diff -urN $SRC/debian $tmpdir/debian
 
 if [ $? == 1 ] ; then
  echo 
  echo 
  echo "*******************************************************"
- echo "Please run 'wrap-and-sort -a -t' to clean up packaging."
+ echo "Please run 'wrap-and-sort -a -t; wrap-and-sort -a -t -f debian/control.gles' to clean up packaging."
  echo "*******************************************************"
  echo 
  exit 1

=== added directory 'tests/unit/contenthub'
=== added file 'tests/unit/contenthub/TextAreaPaste.qml'
--- tests/unit/contenthub/TextAreaPaste.qml	1970-01-01 00:00:00 +0000
+++ tests/unit/contenthub/TextAreaPaste.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Item {
+    width: units.gu(20)
+    height: units.gu(20)
+
+    TextArea {
+        id: textArea
+        objectName: "textArea"
+        width: parent.width
+        height: units.gu(10)
+        focus: true
+    }
+}
+

=== added file 'tests/unit/contenthub/TextFieldPaste.qml'
--- tests/unit/contenthub/TextFieldPaste.qml	1970-01-01 00:00:00 +0000
+++ tests/unit/contenthub/TextFieldPaste.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Item {
+    width: units.gu(20)
+    height: units.gu(20)
+
+    TextField {
+        id: textField
+        objectName: "textField"
+        width: parent.width
+        focus: true
+    }
+}
+

=== added file 'tests/unit/contenthub/com.ubuntu.content.MockService.xml'
--- tests/unit/contenthub/com.ubuntu.content.MockService.xml	1970-01-01 00:00:00 +0000
+++ tests/unit/contenthub/com.ubuntu.content.MockService.xml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,17 @@
+<node>
+  <interface name="com.ubuntu.content.dbus.Service">
+    <method name="RequestPasteByAppId">
+      <arg name="appId" type="s" direction="in" />
+    </method>
+    <method name="PasteFormats">
+      <arg name="formats" type="as" direction="out" />
+    </method>
+    <signal name="PasteSelected">
+      <arg name="appId" type="s" />
+      <arg name="mimedata" type="ay" />
+      <arg name="outputAsHtml" type="b" />
+    </signal>
+    <signal name="PasteboardChanged">
+    </signal>
+ </interface>
+</node>

=== added file 'tests/unit/contenthub/contenthub.pro'
--- tests/unit/contenthub/contenthub.pro	1970-01-01 00:00:00 +0000
+++ tests/unit/contenthub/contenthub.pro	2017-01-30 11:36:29 +0000
@@ -0,0 +1,5 @@
+include(../test-include-x11.pri)
+QT += dbus gui
+SOURCES += tst_contenthub.cpp
+OTHER_FILES += TextAreaPaste.qml TextFieldPaste.qml
+DBUS_ADAPTORS += com.ubuntu.content.MockService.xml

=== added file 'tests/unit/contenthub/tst_contenthub.cpp'
--- tests/unit/contenthub/tst_contenthub.cpp	1970-01-01 00:00:00 +0000
+++ tests/unit/contenthub/tst_contenthub.cpp	2017-01-30 11:36:29 +0000
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Arthur Mello <arthur.mello@xxxxxxxxxxxxx>
+ */
+
+#include "mockservice_adaptor.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QMimeData>
+#include <QtDBus/QDBusConnection>
+#include <QtTest/QTest>
+#include <QtTest/QSignalSpy>
+#include <QtQuick/QQuickItem>
+#include <UbuntuToolkit/ubuntutoolkitmodule.h>
+#include <UbuntuToolkit/private/uccontenthub_p.h>
+
+#include "uctestcase.h"
+
+UT_USE_NAMESPACE
+
+class MockContentService : public QObject
+{
+    Q_OBJECT
+
+public:
+    MockContentService() {}
+    ~MockContentService() {}
+
+public Q_SLOTS:
+    void RequestPasteByAppId(const QString &appId)
+    {
+        Q_UNUSED(appId);
+        Q_EMIT PasteRequested();
+    }
+
+    QStringList PasteFormats()
+    {
+        QStringList formats;
+        formats << "text/plain" << "text/html" << "image/jpeg";
+        return formats;
+    }
+
+Q_SIGNALS:
+    void PasteSelected(const QString&, const QByteArray&, bool);
+    void PasteboardChanged();
+
+    void PasteRequested();
+};
+
+class tst_UCContentHub : public QObject
+{
+    Q_OBJECT
+
+public:
+    tst_UCContentHub() {}
+
+private:
+    MockContentService *mockService;
+    UCContentHub *contentHub;
+    QSignalSpy *pasteRequestedSpy;
+    QSignalSpy *pasteSelectedSpy;
+
+    const int testTimeout = 5000;
+
+    const QString dummyAppId = "DummyAppId";
+
+    const QString sampleText = "TextData";
+    const QString sampleHtml = "<html><body><p>HtmlTest</p></body></html>";
+
+    // Following serialize code is the same as used by content-hub
+    QByteArray serializeMimeData(const QMimeData &mimeData)
+    {
+        /*
+          Data format:
+            number of mime types      (sizeof(int))
+            data layout               ((4 * sizeof(int)) * number of mime types)
+              mime type string offset (sizeof(int))
+              mime type string size   (sizeof(int))
+              data offset             (sizeof(int))
+              data size               (sizeof(int))
+            data                      (n bytes)
+        */
+
+        const int maxFormatsCount = 16;
+        const int maxBufferSize = 4 * 1024 * 1024;  // 4 Mb
+
+        const QStringList formats = mimeData.formats();
+        const int formatCount = qMin(formats.size(), maxFormatsCount);
+        const int headerSize = sizeof(int) + (formatCount * 4 * sizeof(int));
+        int bufferSize = headerSize;
+
+        for (int i = 0; i < formatCount; i++)
+            bufferSize += formats[i].size() + mimeData.data(formats[i]).size();
+
+        QByteArray serializedMimeData;
+        if (bufferSize <= maxBufferSize) {
+            // Serialize data.
+            serializedMimeData.resize(bufferSize);
+            {
+                char *buffer = serializedMimeData.data();
+                int* header = reinterpret_cast<int*>(serializedMimeData.data());
+                int offset = headerSize;
+                header[0] = formatCount;
+                for (int i = 0; i < formatCount; i++) {
+                    const QByteArray data = mimeData.data(formats[i]);
+                    const int formatOffset = offset;
+                    const int formatSize = formats[i].size();
+                    const int dataOffset = offset + formatSize;
+                    const int dataSize = data.size();
+                    memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize);
+                    memcpy(&buffer[dataOffset], data.data(), dataSize);
+                    header[i*4+1] = formatOffset;
+                    header[i*4+2] = formatSize;
+                    header[i*4+3] = dataOffset;
+                    header[i*4+4] = dataSize;
+                    offset += formatSize + dataSize;
+                }
+            }
+        }
+
+        return serializedMimeData;
+    }
+
+
+private Q_SLOTS:
+    void initTestCase()
+    {
+        mockService = new MockContentService();
+        new ServiceAdaptor(mockService);
+        QDBusConnection connection = QDBusConnection::sessionBus();
+        connection.registerObject("/", mockService);
+        connection.registerService("com.ubuntu.content.dbus.Service");
+        pasteRequestedSpy = new QSignalSpy(mockService, SIGNAL(PasteRequested()));
+
+        qRegisterMetaType<QQuickItem*>();
+        contentHub = new UCContentHub();
+        pasteSelectedSpy = new QSignalSpy(contentHub, SIGNAL(pasteSelected(QQuickItem*, const QString&)));
+    }
+
+    void cleanupTestCase()
+    {
+        delete pasteRequestedSpy;
+        delete pasteSelectedSpy;
+        delete contentHub;
+    }
+
+    void cleanup()
+    {
+        pasteRequestedSpy->clear();
+        pasteSelectedSpy->clear();
+    }
+
+    void test_DeserializeTextMimeData()
+    {
+        QMimeData textMimeData;
+        textMimeData.setText(sampleText);
+        QMimeData *deserialized = contentHub->deserializeMimeData(serializeMimeData(textMimeData));
+        QCOMPARE(deserialized->formats().size(), 1);
+        QVERIFY(deserialized->hasText());
+        QVERIFY(deserialized->text() == sampleText);
+    }
+
+    void test_DeserializeHtmlMimeData()
+    {
+        QMimeData htmlMimeData;
+        htmlMimeData.setHtml(sampleHtml);
+        QMimeData *deserialized = contentHub->deserializeMimeData(serializeMimeData(htmlMimeData));
+        QVERIFY(deserialized->hasHtml());
+        QVERIFY(deserialized->html() == sampleHtml);
+    }
+
+    void test_TextPasteSelected()
+    {
+        QMimeData textPaste;
+        textPaste.setText(sampleHtml);
+        contentHub->onPasteSelected(contentHub->getAppProfile(), serializeMimeData(textPaste), false);
+        pasteSelectedSpy->wait(testTimeout);
+        QCOMPARE(pasteSelectedSpy->count(), 1);
+        QList<QVariant> args = pasteSelectedSpy->takeFirst();
+        QVERIFY(args.at(1).toString() == textPaste.text());
+    }
+
+    void test_HtmlPasteSelectedAsText()
+    {
+        QMimeData htmlPaste;
+        htmlPaste.setHtml(sampleHtml);
+        contentHub->onPasteSelected(contentHub->getAppProfile(), serializeMimeData(htmlPaste), false);
+        pasteSelectedSpy->wait(testTimeout);
+        QCOMPARE(pasteSelectedSpy->count(), 1);
+        QList<QVariant> args = pasteSelectedSpy->takeFirst();
+        QVERIFY(args.at(1).toString() == htmlPaste.text());
+    }
+
+    void test_HtmlPasteSelectedAsRichText()
+    {
+        QMimeData htmlPaste;
+        htmlPaste.setHtml(sampleHtml);
+        contentHub->onPasteSelected(contentHub->getAppProfile(), serializeMimeData(htmlPaste), true);
+        pasteSelectedSpy->wait(testTimeout);
+        QCOMPARE(pasteSelectedSpy->count(), 1);
+        QList<QVariant> args = pasteSelectedSpy->takeFirst();
+        QVERIFY(args.at(1).toString() == htmlPaste.html());
+    }
+
+    void test_PasteFromAnotherAppId()
+    {
+        QMimeData textPaste;
+        textPaste.setText(sampleText);
+        contentHub->onPasteSelected(dummyAppId, serializeMimeData(textPaste), false);
+        pasteSelectedSpy->wait(testTimeout);
+        QCOMPARE(pasteSelectedSpy->count(), 0);
+    }
+
+    void test_KeyboardShortcutOnTextField()
+    {
+        QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("TextFieldPaste.qml"));
+        testCase->rootObject()->forceActiveFocus();
+        QQuickItem *textField = testCase->findItem<QQuickItem*>("textField");
+        QTest::keyClick(textField->window(), Qt::Key_Tab);
+        QTRY_COMPARE_WITH_TIMEOUT(textField->property("activeFocus").toBool(), true, testTimeout);
+        QTest::keyClick(textField->window(), Qt::Key_V, Qt::ControlModifier|Qt::ShiftModifier);
+        pasteRequestedSpy->wait(testTimeout);
+        QCOMPARE(pasteRequestedSpy->count(), 1);
+    }
+    
+    void test_KeyboardShortcutOnTextArea()
+    {
+        QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("TextAreaPaste.qml"));
+        testCase->rootObject()->forceActiveFocus();
+        QQuickItem *textArea = testCase->findItem<QQuickItem*>("textArea");
+        QTest::keyClick(textArea->window(), Qt::Key_Tab);
+        QTRY_COMPARE_WITH_TIMEOUT(textArea->property("activeFocus").toBool(), true, testTimeout);
+        QTest::keyClick(textArea->window(), Qt::Key_V, Qt::ControlModifier|Qt::ShiftModifier);
+        pasteRequestedSpy->wait(testTimeout);
+        QCOMPARE(pasteRequestedSpy->count(), 1);
+    }
+};
+
+QTEST_MAIN(tst_UCContentHub)
+
+#include "tst_contenthub.moc"

=== added directory 'tests/unit/mainwindow'
=== added file 'tests/unit/mainwindow/AppName.qml'
--- tests/unit/mainwindow/AppName.qml	1970-01-01 00:00:00 +0000
+++ tests/unit/mainwindow/AppName.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013-2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.Labs 1.0
+
+MainWindow {
+    objectName: "appName"
+    applicationName: "org.gnu.wildebeest"
+
+    Label {
+        text: "Lorem ipsum dolor sit amet"
+    }
+}

=== added file 'tests/unit/mainwindow/OrganizationName.qml'
--- tests/unit/mainwindow/OrganizationName.qml	1970-01-01 00:00:00 +0000
+++ tests/unit/mainwindow/OrganizationName.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013-2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.Labs 1.0
+
+MainWindow {
+    objectName: "appName"
+    applicationName: "tv.island.pacific"
+    organizationName: "pacifist"
+
+    Label {
+        text: "Lorem ipsum dolor sit amet"
+    }
+}

=== added file 'tests/unit/mainwindow/VisualRoot.qml'
--- tests/unit/mainwindow/VisualRoot.qml	1970-01-01 00:00:00 +0000
+++ tests/unit/mainwindow/VisualRoot.qml	2017-01-30 11:36:29 +0000
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013-2017 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.Labs 1.0
+
+MainWindow {
+    objectName: "visualRoot"
+    applicationName: "org.gnu.wildebeest"
+    visualRoot: myRoot
+
+    Rectangle {
+        id: myRoot
+        objectName: "myRoot"
+        anchors.fill: parent
+
+        Label {
+            objectName: "myLabel"
+            text: "Lorem ipsum dolor sit amet"
+        }
+    }
+}

=== added file 'tests/unit/mainwindow/mainwindow.pro'
--- tests/unit/mainwindow/mainwindow.pro	1970-01-01 00:00:00 +0000
+++ tests/unit/mainwindow/mainwindow.pro	2017-01-30 11:36:29 +0000
@@ -0,0 +1,5 @@
+CONFIG += custom_qpa   # needed by test to set device pixel ratio correctly
+include(../test-include.pri)
+
+QT += gui
+SOURCES += tst_mainwindow.cpp

=== added file 'tests/unit/mainwindow/tst_mainwindow.cpp'
--- tests/unit/mainwindow/tst_mainwindow.cpp	1970-01-01 00:00:00 +0000
+++ tests/unit/mainwindow/tst_mainwindow.cpp	2017-01-30 11:36:29 +0000
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012-2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Christian Dywan <christian.dywan@xxxxxxxxxxxxx>
+ */
+
+#include <QtCore/QString>
+#include <QtCore/QTextCodec>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QProcessEnvironment>
+#include <QtCore/QDebug>
+#include <QtTest/QTest>
+#include <QtTest/QSignalSpy>
+#include <QtCore/QCoreApplication>
+#include <QtQml/QQmlEngine>
+#include <QtQuick/QQuickView>
+#include <QtQuick/QQuickItem>
+#include <QtCore/QThread>
+#include <QtCore/QFileInfo>
+#include <QtCore/QDir>
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QSettings>
+
+#include <QtQuick/QQuickItem>
+#include <QtQuick/QQuickView>
+#include <QtGui/QGuiApplication>
+#include <QtQml/QQmlEngine>
+#include <QtQml/QQmlContext>
+#include <QtQml/QQmlComponent>
+
+#include <UbuntuToolkit/ubuntutoolkitmodule.h>
+#include <UbuntuToolkit/private/quickutils_p.h>
+#include <UbuntuToolkit/private/ucapplication_p.h>
+#include <UbuntuToolkit/private/ucmainwindow_p.h>
+#include <UbuntuToolkit/private/ucunits_p.h>
+
+UT_USE_NAMESPACE
+
+class tst_MainWindow : public QObject
+{
+    Q_OBJECT
+
+public:
+    tst_MainWindow()
+    {
+    }
+
+    QQuickWindow *loadTest(const QString &document)
+    {
+        // Can't use UbuntuTestCase: We need a Window root item
+        QPointer<QQmlEngine> engine(new QQmlEngine());
+        QString modulePath(UBUNTU_QML_IMPORT_PATH);
+        if (!QDir(modulePath).exists()) {
+            qWarning("'%s' doesn't exist", qPrintable(modulePath));
+            return 0;
+        }
+        engine->addImportPath(modulePath);
+        UbuntuToolkitModule::initializeContextProperties(engine);
+        QPointer<QQmlComponent> component(new QQmlComponent(engine));
+        component->loadUrl(QUrl::fromLocalFile(document), QQmlComponent::Asynchronous);
+        while (component->isLoading())
+            QCoreApplication::processEvents();
+        QObject *toplevel(component->create());
+        if (component->errorString() != "") {
+            qWarning("%s", qPrintable(component->errorString()));
+            return 0;
+        }
+        QQuickWindow* window(qobject_cast<QQuickWindow *>(toplevel));
+        if (window)
+            engine->setIncubationController(window->incubationController());
+        else {
+            QQuickItem *rootItem = qobject_cast<QQuickItem *>(toplevel);
+            if (rootItem) {
+                QQuickView *view(new QQuickView(engine, 0));
+                window = view;
+                view->setResizeMode(QQuickView::SizeRootObjectToView);
+                view->setContent(document, component, rootItem);
+            }
+        }
+        return window;
+    }
+
+    QQuickItem *testItem(QObject *that, const QString &identifier)
+    {
+        if (that->property(identifier.toLocal8Bit()).isValid())
+            return that->property(identifier.toLocal8Bit()).value<QQuickItem*>();
+
+        QList<QQuickItem*> children = that->findChildren<QQuickItem*>(identifier);
+        return (children.count() > 0) ? children[0] : 0;
+    }
+
+private Q_SLOTS:
+
+    void initTestCase()
+    {
+    }
+
+    void cleanupTestCase()
+    {
+    }
+
+    // Note: tests/unit/mainview13 contains the UCApplication bits
+
+    void testCase_AppName()
+    {
+        QString applicationName("org.gnu.wildebeest");
+        QQuickWindow *mainWindow(loadTest("AppName.qml"));
+        QVERIFY(mainWindow);
+        QCOMPARE(applicationName, mainWindow->property("applicationName").toString());
+        QCOMPARE(applicationName, QCoreApplication::applicationName());
+        QCOMPARE(QString(""), QCoreApplication::organizationName());
+    }
+
+    void testCase_OrganizationName()
+    {
+        QString applicationName("tv.island.pacific");
+        QString organizationName("pacifist");
+        QQuickWindow *mainWindow(loadTest("OrganizationName.qml"));
+        QVERIFY(mainWindow);
+        QCOMPARE(applicationName, mainWindow->property("applicationName").toString());
+        QCOMPARE(applicationName, QCoreApplication::applicationName());
+        QCOMPARE(organizationName, mainWindow->property("organizationName").toString());
+        QCOMPARE(organizationName, QCoreApplication::organizationName());
+    }
+
+    void testCase_VisualRoot()
+    {
+        QString applicationName("tv.island.pacific");
+        QQuickWindow *mainWindow(loadTest("VisualRoot.qml"));
+        QVERIFY(mainWindow);
+        QQuickItem* myLabel(testItem(mainWindow, "myLabel"));
+        QQuickItem* visualRoot(QuickUtils::instance()->rootItem(myLabel));
+        QQuickItem* myRoot(testItem(mainWindow, "myRoot"));
+        QCOMPARE(visualRoot, myRoot);
+    }
+};
+
+QTEST_MAIN(tst_MainWindow)
+
+#include "tst_mainwindow.moc"

=== modified file 'tests/unit/unit.pro'
--- tests/unit/unit.pro	2016-09-30 05:39:57 +0000
+++ tests/unit/unit.pro	2017-01-30 11:36:29 +0000
@@ -41,10 +41,12 @@
     performance \
     mainview11 \
     mainview13 \
+    mainwindow \
     i18n \
     arguments \
     argument \
     alarms \
     theme \
     quickutils \
-    tree
+    tree \
+    contenthub

=== modified file 'tests/unit/visual/tst_focus.13.qml'
--- tests/unit/visual/tst_focus.13.qml	2016-09-19 16:40:49 +0000
+++ tests/unit/visual/tst_focus.13.qml	2017-01-30 11:36:29 +0000
@@ -414,13 +414,13 @@
                             component: dialogComponent,
                             item: null,
                             foreground_name: "dialogForeground",
-                            bug: "1569979"
+                            bug: "" // this is not buggy
                         },
                         {   tag: "Popover component",
                             component: popoverComponent,
                             item: null,
                             foreground_name: "popover_foreground",
-                            bug: "" // this is the only case without a bug
+                            bug: "" // this is not buggy
                         },
                         {   tag: "Dialog item",
                             component: null,

=== renamed file 'tests/unit/visual/FIXME-QT56_listitem_focus.13.qml' => 'tests/unit/visual/tst_listitem_focus.13.qml'
=== modified file 'tests/unit/visual/tst_popups_dialog.13.qml'
--- tests/unit/visual/tst_popups_dialog.13.qml	2016-06-15 13:46:51 +0000
+++ tests/unit/visual/tst_popups_dialog.13.qml	2017-01-30 11:36:29 +0000
@@ -47,6 +47,21 @@
             keyClick(Qt.Key_Escape);
             tryCompare(test, "dialogDestroyed", true, 500, "Dialog not destroyed");
         }
+
+        function test_focus_restore_ondismiss_dialog() {
+            pressMe.forceActiveFocus();
+
+            tryCompare(window, "activeFocusItem", pressMe);
+
+            var dlg = PopupUtils.open(dialog);
+            waitForRendering(dlg);
+
+            tryCompare(window, "activeFocusItem", dlg.button);
+
+            keyClick(Qt.Key_Escape);
+
+            tryCompare(window, "activeFocusItem", pressMe);
+        }
     }
 
     Component {
@@ -54,10 +69,13 @@
         Dialog {
             id: ahojDialog
             title: "Ahoj"
+            property alias button: closeButton
 
             Button {
+                id: closeButton
                 text: "Close"
                 onClicked: PopupUtils.close(ahojDialog)
+                focus: true
             }
         }
     }


References