diff -Nru unity8-8.02+15.04.20150205/debian/changelog unity8-8.02+15.04.20150211/debian/changelog --- unity8-8.02+15.04.20150205/debian/changelog 2015-02-11 17:18:19.000000000 +0000 +++ unity8-8.02+15.04.20150211/debian/changelog 2015-02-11 17:18:20.000000000 +0000 @@ -1,31 +1,40 @@ -unity8 (8.02+15.04.20150205-0ubuntu1) vivid; urgency=medium - - [ Andrea Cimitan ] - * Fix continue button in wifi wizard page, adds qml-module- - qtsysteminfo as unity8 dep (LP: #1363400) - * Background needs to be specified to be visible in horizontal cards - (LP: #1411748) - - [ CI Train Bot ] - * Resync trunk - - [ Michał Sawicz ] - * Add workaround for gcc ICE. - - [ Albert Astals ] - * Implement proper updateRanges for horizontal items (i.e. Carousel, - Horizontal List) +unity8 (8.02+15.04.20150211-0ubuntu1) vivid; urgency=medium [ Michael Terry ] - * Don't accept multiple "Finish" clicks during the last step of the - wizard + * Redesign tutorial to match latest spec (just removing obsolete pages + and redesigning look, no new pages yet) + * Add new right-edge and bottom-edge screens to the first-boot edge + tutorial (LP: #1383297) + * Support switching the indicator profile on the fly, as the greeter + appears and disappears. (But don't use it yet, not until the + settings panel to control this is in place.) + * Clip the infographic to its bounding box, instead of letting it + bleed past the welcome page (visible when dragging the welcome page + to the side). + + [ Michael Zanetti ] + * Add an AccountService based launcher model to be used with split + greeter + * Launcher fixes for windowed mode + * save and restore window size and position + * Fix resizing windows when making it smaller than minSize and then + larger again. + + [ handsome_feng<445865575@qq.com> ] + * makes the header fully reveal when tapping the search icon. (LP: + #1379327) + + [ Mirco Müller ] + * Allow swipe-to-dismiss for contracted snap-decision notifications, + interactive notifications and ephemeral notifications. (LP: + #1355422, #1334855) [ Daniel d'Andrada ] - * Unify all liblightdm mocks - * Ensure the greeter password field is not covered by the keyboard - * Don't show() the lockscreen if it's already being shown + * Make indicators bar eat all events + * Improve Launcher tests, making them more reliable + * Tapping on a surface gives it active focus - -- Ubuntu daily release Thu, 05 Feb 2015 10:30:13 +0000 + -- Ubuntu daily release Wed, 11 Feb 2015 17:13:16 +0000 unity8 (8.02+15.04.20150123.3-0ubuntu1) vivid; urgency=low diff -Nru unity8-8.02+15.04.20150205/debian/control unity8-8.02+15.04.20150211/debian/control --- unity8-8.02+15.04.20150205/debian/control 2015-02-11 17:18:19.000000000 +0000 +++ unity8-8.02+15.04.20150211/debian/control 2015-02-11 17:18:20.000000000 +0000 @@ -27,7 +27,7 @@ libqmenumodel-dev (>= 0.2.9), libqt5xmlpatterns5-dev, libsystemsettings-dev, - libunity-api-dev (>= 7.94), + libunity-api-dev (>= 7.95), libusermetricsoutput1-dev, libxcb1-dev, pkg-config, diff -Nru unity8-8.02+15.04.20150205/debian/unity8.install unity8-8.02+15.04.20150211/debian/unity8.install --- unity8-8.02+15.04.20150205/debian/unity8.install 2015-02-11 17:18:19.000000000 +0000 +++ unity8-8.02+15.04.20150211/debian/unity8.install 2015-02-11 17:18:20.000000000 +0000 @@ -10,5 +10,6 @@ usr/share/unity8/Panel usr/share/unity8/Shell.qml usr/share/unity8/Stages +usr/share/unity8/Tutorial usr/share/unity8/Wizard usr/share/url-dispatcher/urls/unity8-dash.url-dispatcher diff -Nru unity8-8.02+15.04.20150205/debian/unity8-private.install unity8-8.02+15.04.20150211/debian/unity8-private.install --- unity8-8.02+15.04.20150205/debian/unity8-private.install 2015-02-11 17:18:19.000000000 +0000 +++ unity8-8.02+15.04.20150211/debian/unity8-private.install 2015-02-11 17:18:20.000000000 +0000 @@ -1,6 +1,7 @@ usr/lib/*/libunity8-private.* usr/lib/*/unity8/qml/AccountsService usr/lib/*/unity8/qml/Dash +usr/lib/*/unity8/qml/Greeter usr/lib/*/unity8/qml/LightDM usr/lib/*/unity8/qml/Lights usr/lib/*/unity8/qml/Powerd diff -Nru unity8-8.02+15.04.20150205/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp unity8-8.02+15.04.20150211/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp --- unity8-8.02+15.04.20150205/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -57,6 +57,15 @@ } } +void AccountsServiceDBusAdaptor::setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value) +{ + QDBusInterface *iface = getUserInterface(user); + if (iface != nullptr && iface->isValid()) { + // The value needs to be carefully wrapped + iface->asyncCall("Set", interface, property, QVariant::fromValue(QDBusVariant(value))); + } +} + void AccountsServiceDBusAdaptor::propertiesChangedSlot(const QString &interface, const QVariantMap &changed, const QStringList &invalid) { // Merge changed and invalidated together diff -Nru unity8-8.02+15.04.20150205/plugins/AccountsService/AccountsServiceDBusAdaptor.h unity8-8.02+15.04.20150211/plugins/AccountsService/AccountsServiceDBusAdaptor.h --- unity8-8.02+15.04.20150205/plugins/AccountsService/AccountsServiceDBusAdaptor.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/AccountsService/AccountsServiceDBusAdaptor.h 2015-02-11 17:10:20.000000000 +0000 @@ -19,6 +19,7 @@ #ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H #define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H +#include #include #include #include @@ -33,7 +34,18 @@ explicit AccountsServiceDBusAdaptor(QObject *parent = 0); Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property); + + template + inline T getUserProperty(const QString &user, const QString &interface, const QString &property) { + QVariant variant = getUserProperty(user, interface, property); + if (variant.isValid() && variant.canConvert()) { + return qdbus_cast(variant.value()); + } + return T(); + } + Q_INVOKABLE void setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value); + Q_INVOKABLE void setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value); Q_SIGNALS: void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed); diff -Nru unity8-8.02+15.04.20150205/plugins/AccountsService/com.canonical.unity.AccountsService.xml unity8-8.02+15.04.20150211/plugins/AccountsService/com.canonical.unity.AccountsService.xml --- unity8-8.02+15.04.20150205/plugins/AccountsService/com.canonical.unity.AccountsService.xml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/AccountsService/com.canonical.unity.AccountsService.xml 2015-02-11 17:10:20.000000000 +0000 @@ -14,8 +14,7 @@ - - + diff -Nru unity8-8.02+15.04.20150205/plugins/CMakeLists.txt unity8-8.02+15.04.20150211/plugins/CMakeLists.txt --- unity8-8.02+15.04.20150205/plugins/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -12,6 +12,7 @@ endmacro() add_subdirectory(AccountsService) +add_subdirectory(Greeter) add_subdirectory(LightDM) add_subdirectory(Lights) add_subdirectory(Dash) diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/CMakeLists.txt unity8-8.02+15.04.20150211/plugins/Greeter/CMakeLists.txt --- unity8-8.02+15.04.20150205/plugins/Greeter/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,6 @@ +macro(add_unity8_greeter_plugin PLUGIN VERSION PATH) + export_qmlfiles(${PLUGIN} ${PATH} DESTINATION ${SHELL_INSTALL_QML}/Greeter TARGET_PREFIX Greeter ${ARGN}) + export_qmlplugin(${PLUGIN} ${VERSION} ${PATH} DESTINATION ${SHELL_INSTALL_QML}/Greeter TARGET_PREFIX Greeter ${ARGN}) +endmacro() + +add_subdirectory(Unity) diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/CMakeLists.txt unity8-8.02+15.04.20150211/plugins/Greeter/Unity/CMakeLists.txt --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1 @@ +add_subdirectory(Launcher) diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/CMakeLists.txt unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/CMakeLists.txt --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,38 @@ +include(FindPkgConfig) +pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6) +pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5) +pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt) + +add_definitions(-DSM_BUSNAME=systemBus) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/plugins/AccountsService + ${GSETTINGS_QT_INCLUDE_DIRS} + ${libunity8-private_SOURCE_DIR} +) + +set(QMLLAUNCHERPLUGINAS_SRC + plugin.cpp + launchermodelas.cpp + launcheritem.cpp + quicklistmodel.cpp + quicklistentry.cpp + ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h + ) + +add_library(UnityLauncherAS-qml MODULE + ${QMLLAUNCHERPLUGINAS_SRC} + ) + +target_link_libraries(UnityLauncherAS-qml + unity8-private + ${GSETTINGS_QT_LDFLAGS} + ) + +qt5_use_modules(UnityLauncherAS-qml DBus Qml Gui) + +add_unity8_greeter_plugin(Unity.Launcher 0.1 Unity/Launcher TARGETS UnityLauncherAS-qml) diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launcheritem.cpp unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launcheritem.cpp --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launcheritem.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launcheritem.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,171 @@ +/* + * Copyright 2014-2015 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 . + */ + +#include "launcheritem.h" +#include "quicklistmodel.h" + +#include + +LauncherItem::LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent) : + LauncherItemInterface(parent), + m_appId(appId), + m_name(name), + m_icon(icon), + m_pinned(false), + m_running(false), + m_recent(false), + m_progress(-1), + m_count(0), + m_countVisible(false), + m_focused(false), + m_quickList(new QuickListModel(this)) +{ + QuickListEntry nameAction; + nameAction.setActionId("launch_item"); + nameAction.setText(m_name); + m_quickList->appendAction(nameAction); +} + +QString LauncherItem::appId() const +{ + return m_appId; +} + +QString LauncherItem::name() const +{ + return m_name; +} + +void LauncherItem::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + QuickListEntry entry; + entry.setActionId("launch_item"); + entry.setText(m_name); + m_quickList->updateAction(entry); + Q_EMIT nameChanged(name); + } +} + +QString LauncherItem::icon() const +{ + return m_icon; +} + +void LauncherItem::setIcon(const QString &icon) +{ + if (m_icon != icon) { + m_icon = icon; + Q_EMIT iconChanged(icon); + } +} + +bool LauncherItem::pinned() const +{ + return m_pinned; +} + +void LauncherItem::setPinned(bool pinned) +{ + if (m_pinned != pinned) { + m_pinned = pinned; + Q_EMIT pinnedChanged(pinned); + } +} + +bool LauncherItem::running() const +{ + return m_running; +} + +void LauncherItem::setRunning(bool running) +{ + if (m_running != running) { + m_running = running; + Q_EMIT runningChanged(running); + } +} + +bool LauncherItem::recent() const +{ + return m_recent; +} + +void LauncherItem::setRecent(bool recent) +{ + if (m_recent != recent) { + m_recent = recent; + Q_EMIT recentChanged(recent); + } +} + +int LauncherItem::progress() const +{ + return m_progress; +} + +void LauncherItem::setProgress(int progress) +{ + if (m_progress != progress) { + m_progress = progress; + Q_EMIT progressChanged(progress); + } +} + +int LauncherItem::count() const +{ + return m_count; +} + +void LauncherItem::setCount(int count) +{ + if (m_count != count) { + m_count = count; + Q_EMIT countChanged(count); + } +} + +bool LauncherItem::countVisible() const +{ + return m_countVisible; +} + +void LauncherItem::setCountVisible(bool countVisible) +{ + if (m_countVisible != countVisible) { + m_countVisible = countVisible; + Q_EMIT countVisibleChanged(countVisible); + } +} + +bool LauncherItem::focused() const +{ + return m_focused; +} + +void LauncherItem::setFocused(bool focused) +{ + if (m_focused != focused) { + m_focused = focused; + Q_EMIT focusedChanged(focused); + } +} + +unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const +{ + return m_quickList; +} diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launcheritem.h unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launcheritem.h --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launcheritem.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launcheritem.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2015 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 . + */ + +#ifndef LAUNCHERITEM_H +#define LAUNCHERITEM_H + +#include "quicklistmodel.h" + +#include + +class QuickListModel; + +using namespace unity::shell::launcher; + +class LauncherItem: public LauncherItemInterface +{ + Q_OBJECT +public: + LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0); + + QString appId() const override; + QString name() const override; + QString icon() const override; + bool pinned() const override; + bool running() const override; + bool recent() const override; + int progress() const override; + int count() const override; + bool countVisible() const override; + bool focused() const override; + + unity::shell::launcher::QuickListModelInterface *quickList() const override; + +private: + void setName(const QString &name); + void setIcon(const QString &icon); + void setPinned(bool pinned); + void setRunning(bool running); + void setRecent(bool recent); + void setProgress(int progress); + void setCount(int count); + void setCountVisible(bool countVisible); + void setFocused(bool focused); + +private: + QString m_appId; + QString m_name; + QString m_icon; + bool m_pinned; + bool m_running; + bool m_recent; + int m_progress; + int m_count; + bool m_countVisible; + bool m_focused; + QuickListModel *m_quickList; + + friend class LauncherModel; +}; + +#endif // LAUNCHERITEM_H diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launchermodelas.cpp unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launchermodelas.cpp --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launchermodelas.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launchermodelas.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,277 @@ +/* + * Copyright 2014-2015 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 . + */ + +#include "launchermodelas.h" +#include "launcheritem.h" +#include "AccountsServiceDBusAdaptor.h" +#include + +#include +#include +#include + +using namespace unity::shell::application; + +LauncherModel::LauncherModel(QObject *parent): + LauncherModelInterface(parent), + m_accounts(new AccountsServiceDBusAdaptor(this)), + m_onlyPinned(true) +{ + connect(m_accounts, &AccountsServiceDBusAdaptor::propertiesChanged, this, &LauncherModel::propertiesChanged); + refresh(); +} + +LauncherModel::~LauncherModel() +{ + while (!m_list.empty()) { + m_list.takeFirst()->deleteLater(); + } +} + +int LauncherModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant LauncherModel::data(const QModelIndex &index, int role) const +{ + LauncherItem *item = m_list.at(index.row()); + switch(role) { + case RoleAppId: + return item->appId(); + case RoleName: + return item->name(); + case RoleIcon: + return item->icon(); + case RolePinned: + return item->pinned(); + case RoleCount: + return item->count(); + case RoleCountVisible: + return item->countVisible(); + case RoleProgress: + return item->progress(); + case RoleFocused: + return item->focused(); + } + + return QVariant(); +} + +unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const +{ + if (index < 0 || index >= m_list.count()) { + return 0; + } + return m_list.at(index); +} + +void LauncherModel::move(int oldIndex, int newIndex) +{ + Q_UNUSED(oldIndex) + Q_UNUSED(newIndex) + qWarning() << "This is a read only implementation. Cannot move items."; +} + +void LauncherModel::pin(const QString &appId, int index) +{ + Q_UNUSED(appId) + Q_UNUSED(index) + qWarning() << "This is a read only implementation. Cannot pin items"; +} + +void LauncherModel::requestRemove(const QString &appId) +{ + Q_UNUSED(appId) + qWarning() << "This is a read only implementation. Cannot remove items"; +} + +void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex) +{ + int index = findApplication(appId); + if (index < 0) { + return; + } + + LauncherItem *item = m_list.at(index); + QuickListModel *model = qobject_cast(item->quickList()); + if (model) { + QString actionId = model->get(actionIndex).actionId(); + + // Check if this is one of the launcher actions we handle ourselves + if (actionId == "launch_item") { + QDesktopServices::openUrl(getUrlForAppId(appId)); + + // Nope, we don't know this action, let the backend forward it to the application + } else { + // TODO: forward quicklist action to app, possibly via m_dbusIface + } + } +} + +void LauncherModel::setUser(const QString &username) +{ + if (m_user != username) { + m_user = username; + refresh(); + } +} + +QString LauncherModel::getUrlForAppId(const QString &appId) const +{ + // appId is either an appId or a legacy app name. Let's find out which + if (appId.isEmpty()) { + return QString(); + } + + if (!appId.contains("_")) { + return "application:///" + appId + ".desktop"; + } + + QStringList parts = appId.split('_'); + QString package = parts.value(0); + QString app = parts.value(1, "first-listed-app"); + return "appid://" + package + "/" + app + "/current-user-version"; +} + +ApplicationManagerInterface *LauncherModel::applicationManager() const +{ + return nullptr; +} + +void LauncherModel::setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager) +{ + Q_UNUSED(appManager) + qWarning() << "This plugin syncs all its states from AccountsService. Not using ApplicationManager."; + return; +} + +bool LauncherModel::onlyPinned() const +{ + return m_onlyPinned; +} + +void LauncherModel::setOnlyPinned(bool onlyPinned) +{ + if (m_onlyPinned != onlyPinned) { + m_onlyPinned = onlyPinned; + Q_EMIT onlyPinnedChanged(); + refresh(); + } +} + +int LauncherModel::findApplication(const QString &appId) +{ + for (int i = 0; i < m_list.count(); ++i) { + LauncherItem *item = m_list.at(i); + if (item->appId() == appId) { + return i; + } + } + return -1; +} + +typedef QList VariantMapList; +void LauncherModel::refresh() +{ + QList items; + + if (m_accounts && !m_user.isEmpty()) { + items = m_accounts->getUserProperty(m_user, "com.canonical.unity.AccountsService", "LauncherItems"); + } + + // First walk through all the existing items and see if we need to remove something + QList toBeRemoved; + + Q_FOREACH (LauncherItem* item, m_list) { + bool found = false; + Q_FOREACH(const QVariant &asItem, items) { + if (asItem.toMap().value("id").toString() == item->appId()) { + // Only keep and update it if we either show unpinned or it is pinned + if (!m_onlyPinned || asItem.toMap().value("pinned").toBool()) { + found = true; + item->setName(asItem.toMap().value("name").toString()); + item->setIcon(asItem.toMap().value("icon").toString()); + item->setCount(asItem.toMap().value("count").toInt()); + item->setCountVisible(asItem.toMap().value("countVisible").toBool()); + int idx = m_list.indexOf(item); + Q_EMIT dataChanged(index(idx), index(idx), QVector() << RoleName << RoleIcon); + } + break; + } + } + if (!found) { + toBeRemoved.append(item); + } + } + + Q_FOREACH (LauncherItem* item, toBeRemoved) { + int idx = m_list.indexOf(item); + beginRemoveRows(QModelIndex(), idx, idx); + m_list.takeAt(idx)->deleteLater(); + endRemoveRows(); + } + + // Now walk through list and see if we need to add something + int skipped = 0; + for (int asIndex = 0; asIndex < items.count(); ++asIndex) { + QVariant entry = items.at(asIndex); + + if (m_onlyPinned && !entry.toMap().value("pinned").toBool()) { + // Skipping it as we only show pinned and it is not + skipped++; + continue; + } + int newPosition = asIndex - skipped; + + int itemIndex = -1; + for (int i = 0; i < m_list.count(); ++i) { + if (m_list.at(i)->appId() == entry.toMap().value("id").toString()) { + itemIndex = i; + break; + } + } + + if (itemIndex == -1) { + // Need to add it. Just add it into the addedIndex to keep same ordering as the list in AS. + LauncherItem *item = new LauncherItem(entry.toMap().value("id").toString(), + entry.toMap().value("name").toString(), + entry.toMap().value("icon").toString(), + this); + item->setPinned(true); + item->setCount(entry.toMap().value("count").toInt()); + item->setCountVisible(entry.toMap().value("countVisible").toBool()); + beginInsertRows(QModelIndex(), newPosition, newPosition); + m_list.insert(newPosition, item); + endInsertRows(); + } else if (itemIndex != newPosition) { + // The item is already there, but it is in a different place than in the settings. + // Move it to the addedIndex + beginMoveRows(QModelIndex(), itemIndex, itemIndex, QModelIndex(), newPosition); + m_list.move(itemIndex, newPosition); + endMoveRows(); + } + } +} + +void LauncherModel::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed) +{ + if (user != m_user || interface != "com.canonical.unity.AccountsService" || !changed.contains("LauncherItems")) { + return; + } + refresh(); +} diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launchermodelas.h unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launchermodelas.h --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/launchermodelas.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/launchermodelas.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,75 @@ +/* + * Copyright 2014-2015 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 . + */ + +#ifndef LAUNCHERMODEL_H +#define LAUNCHERMODEL_H + +#include +#include + +#include + +class LauncherItem; +class GSettings; +class AccountsServiceDBusAdaptor; + +using namespace unity::shell::launcher; +using namespace unity::shell::application; + +class LauncherModel: public LauncherModelInterface +{ + Q_OBJECT + +public: + LauncherModel(QObject *parent = 0); + ~LauncherModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + + Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const; + Q_INVOKABLE void move(int oldIndex, int newIndex); + Q_INVOKABLE void pin(const QString &appId, int index = -1); + Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex); + Q_INVOKABLE void setUser(const QString &username); + Q_INVOKABLE QString getUrlForAppId(const QString &appId) const; + + unity::shell::application::ApplicationManagerInterface* applicationManager() const; + void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager); + + bool onlyPinned() const override; + void setOnlyPinned(bool onlyPinned) override; + + int findApplication(const QString &appId); + +public Q_SLOTS: + void requestRemove(const QString &appId) override; + Q_INVOKABLE void refresh(); + +private Q_SLOTS: + void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed); + +private: + QString m_user; + QList m_list; + AccountsServiceDBusAdaptor *m_accounts; + bool m_onlyPinned; + + friend class LauncherModelASTest; +}; + +#endif // LAUNCHERMODEL_H diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/Launcher.qmltypes unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/Launcher.qmltypes --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/Launcher.qmltypes 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/Launcher.qmltypes 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,179 @@ +import QtQuick.tooling 1.1 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -notrelocatable Unity.Launcher 0.1 plugins' + +Module { + Component { + name: "LauncherItem" + prototype: "unity::shell::launcher::LauncherItemInterface" + exports: ["Unity.Launcher/LauncherItem 0.1"] + isCreatable: false + exportMetaObjectRevisions: [0] + } + Component { + name: "LauncherModel" + prototype: "unity::shell::launcher::LauncherModelInterface" + exports: ["Unity.Launcher/LauncherModel 0.1"] + isCreatable: false + isSingleton: true + exportMetaObjectRevisions: [0] + Method { + name: "requestRemove" + Parameter { name: "appId"; type: "string" } + } + Method { + name: "get" + type: "unity::shell::launcher::LauncherItemInterface*" + Parameter { name: "index"; type: "int" } + } + Method { + name: "move" + Parameter { name: "oldIndex"; type: "int" } + Parameter { name: "newIndex"; type: "int" } + } + Method { + name: "pin" + Parameter { name: "appId"; type: "string" } + Parameter { name: "index"; type: "int" } + } + Method { + name: "pin" + Parameter { name: "appId"; type: "string" } + } + Method { + name: "quickListActionInvoked" + Parameter { name: "appId"; type: "string" } + Parameter { name: "actionIndex"; type: "int" } + } + Method { + name: "setUser" + Parameter { name: "username"; type: "string" } + } + Method { + name: "getUrlForAppId" + type: "string" + Parameter { name: "appId"; type: "string" } + } + } + Component { + name: "QuickListModel" + prototype: "unity::shell::launcher::QuickListModelInterface" + exports: ["Unity.Launcher/QuickListModel 0.1"] + isCreatable: false + exportMetaObjectRevisions: [0] + } + Component { + name: "unity::shell::launcher::LauncherItemInterface" + prototype: "QObject" + exports: ["Unity.Launcher/LauncherItemInterface 0.1"] + isCreatable: false + exportMetaObjectRevisions: [0] + Property { name: "appId"; type: "string"; isReadonly: true } + Property { name: "name"; type: "string"; isReadonly: true } + Property { name: "icon"; type: "string"; isReadonly: true } + Property { name: "pinned"; type: "bool"; isReadonly: true } + Property { name: "running"; type: "bool"; isReadonly: true } + Property { name: "recent"; type: "bool"; isReadonly: true } + Property { name: "progress"; type: "int"; isReadonly: true } + Property { name: "count"; type: "int"; isReadonly: true } + Property { name: "countVisible"; type: "bool"; isReadonly: true } + Property { name: "focused"; type: "bool"; isReadonly: true } + Property { + name: "quickList" + type: "unity::shell::launcher::QuickListModelInterface" + isReadonly: true + isPointer: true + } + Signal { + name: "nameChanged" + Parameter { name: "name"; type: "string" } + } + Signal { + name: "iconChanged" + Parameter { name: "icon"; type: "string" } + } + Signal { + name: "pinnedChanged" + Parameter { name: "pinned"; type: "bool" } + } + Signal { + name: "runningChanged" + Parameter { name: "running"; type: "bool" } + } + Signal { + name: "recentChanged" + Parameter { name: "running"; type: "bool" } + } + Signal { + name: "progressChanged" + Parameter { name: "progress"; type: "int" } + } + Signal { + name: "countChanged" + Parameter { name: "count"; type: "int" } + } + Signal { + name: "countVisibleChanged" + Parameter { name: "countVisible"; type: "bool" } + } + Signal { + name: "focusedChanged" + Parameter { name: "focused"; type: "bool" } + } + } + Component { + name: "unity::shell::launcher::LauncherModelInterface" + prototype: "QAbstractListModel" + exports: ["Unity.Launcher/LauncherModelInterface 0.1"] + isCreatable: false + exportMetaObjectRevisions: [0] + Property { + name: "applicationManager" + type: "unity::shell::application::ApplicationManagerInterface" + isPointer: true + } + Method { + name: "move" + Parameter { name: "oldIndex"; type: "int" } + Parameter { name: "newIndex"; type: "int" } + } + Method { + name: "get" + type: "unity::shell::launcher::LauncherItemInterface*" + Parameter { name: "index"; type: "int" } + } + Method { + name: "pin" + Parameter { name: "appId"; type: "string" } + Parameter { name: "index"; type: "int" } + } + Method { + name: "pin" + Parameter { name: "appId"; type: "string" } + } + Method { + name: "requestRemove" + Parameter { name: "appId"; type: "string" } + } + Method { + name: "quickListActionInvoked" + Parameter { name: "appId"; type: "string" } + Parameter { name: "actionIndex"; type: "int" } + } + Method { + name: "setUser" + Parameter { name: "username"; type: "string" } + } + } + Component { + name: "unity::shell::launcher::QuickListModelInterface" + prototype: "QAbstractListModel" + exports: ["Unity.Launcher/QuickListInterface 0.1"] + isCreatable: false + exportMetaObjectRevisions: [0] + } +} diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/plugin.cpp unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/plugin.cpp --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/plugin.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/plugin.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2015 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 . + */ + +// Qt +#include + +// self +#include "plugin.h" + +// local +#include "launchermodelas.h" +#include "launcheritem.h" + + +using namespace unity::shell::launcher; + +static QObject* modelProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */) +{ + return new LauncherModel(); +} + +void UnityLauncherPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(uri == QLatin1String("Unity.Launcher")); + + qmlRegisterUncreatableType(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated."); + qmlRegisterUncreatableType(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated."); + qmlRegisterUncreatableType(uri, 0, 1, "QuickListInterface", "Abstract Interface. Cannot be instantiated."); + + qmlRegisterSingletonType(uri, 0, 1, "LauncherModel", modelProvider); + qmlRegisterUncreatableType(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel."); + qmlRegisterUncreatableType(uri, 0, 1, "QuickListModel", "Can't create a QuickListModel in QML. Get them from the LauncherItems."); +} diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/plugin.h unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/plugin.h --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/plugin.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/plugin.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UNITY_LAUNCHER_PLUGIN_H +#define UNITY_LAUNCHER_PLUGIN_H + +#include +#include + +class UnityLauncherPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri); +}; + +#endif diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/qmldir unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/qmldir --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/qmldir 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/qmldir 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,3 @@ +module Unity.Launcher +plugin UnityLauncherAS-qml +typeinfo Launcher.qmltypes diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistentry.cpp unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistentry.cpp --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistentry.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistentry.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,56 @@ +/* Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "quicklistentry.h" + +QuickListEntry::QuickListEntry() +{ + +} + +QString QuickListEntry::actionId() const +{ + return m_actionId; +} + +void QuickListEntry::setActionId(const QString &actionId) +{ + m_actionId = actionId; +} + +QString QuickListEntry::text() const +{ + return m_text; +} + +void QuickListEntry::setText(const QString &text) +{ + m_text = text; +} + +QString QuickListEntry::icon() const +{ + return m_icon; +} + +void QuickListEntry::setIcon(const QString &icon) +{ + m_icon = icon; +} + +bool QuickListEntry::clickable() const +{ + return !m_actionId.isEmpty(); +} diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistentry.h unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistentry.h --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistentry.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistentry.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,43 @@ +/* Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef QUICKLISTENTRY_H +#define QUICKLISTENTRY_H + +#include + +class QuickListEntry +{ +public: + QuickListEntry(); + + QString actionId() const; + void setActionId(const QString &actionId); + + QString text() const; + void setText(const QString &text); + + QString icon() const; + void setIcon(const QString &icon); + + bool clickable() const; + +private: + QString m_actionId; + QString m_text; + QString m_icon; +}; + +#endif // QUICKLISTENTRY diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistmodel.cpp unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistmodel.cpp --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistmodel.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistmodel.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,70 @@ +/* + * Copyright 2014-2015 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 . + */ + +#include "quicklistmodel.h" + +QuickListModel::QuickListModel(QObject *parent) : + QuickListModelInterface(parent) +{ + +} + +QuickListModel::~QuickListModel() +{ + +} + +void QuickListModel::appendAction(const QuickListEntry &entry) +{ + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(entry); + endInsertRows(); +} + +void QuickListModel::updateAction(const QuickListEntry &entry) +{ + for (int i = 0; i < m_list.count(); ++i) { + if (m_list.at(i).actionId() == entry.actionId()) { + m_list.replace(i, entry); + Q_EMIT dataChanged(index(i), index(i)); + return; + } + } +} + +QuickListEntry QuickListModel::get(int index) const +{ + return m_list.at(index); +} + +int QuickListModel::rowCount(const QModelIndex &index) const +{ + Q_UNUSED(index) + return m_list.count(); +} + +QVariant QuickListModel::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleLabel: + return m_list.at(index.row()).text(); + case RoleIcon: + return m_list.at(index.row()).icon(); + case RoleClickable: + return m_list.at(index.row()).clickable(); + } + return QVariant(); +} diff -Nru unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistmodel.h unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistmodel.h --- unity8-8.02+15.04.20150205/plugins/Greeter/Unity/Launcher/quicklistmodel.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Greeter/Unity/Launcher/quicklistmodel.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,55 @@ +/* + * Copyright 2014-2015 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 . + */ + +#ifndef QUICKLISTMODEL_H +#define QUICKLISTMODEL_H + +#include "quicklistentry.h" + +#include + +using namespace unity::shell::launcher; + +class QuickListModel: public QuickListModelInterface +{ + Q_OBJECT + +public: + explicit QuickListModel(QObject *parent = 0); + + ~QuickListModel(); + + void appendAction(const QuickListEntry &entry); + + /** + * @brief Update an existing action + * @param entry The new, updated entry + * + * This will only do something if entry.actionId is found in the model. + * To add a new entry, use appendAction(). + */ + void updateAction(const QuickListEntry &entry); + + QuickListEntry get(int index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + +private: + QList m_list; +}; + +#endif // QUICKLISTMODEL_H diff -Nru unity8-8.02+15.04.20150205/plugins/Ubuntu/Gestures/TouchGate.cpp unity8-8.02+15.04.20150211/plugins/Ubuntu/Gestures/TouchGate.cpp --- unity8-8.02+15.04.20150205/plugins/Ubuntu/Gestures/TouchGate.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Ubuntu/Gestures/TouchGate.cpp 2015-02-11 17:11:30.000000000 +0000 @@ -53,6 +53,8 @@ m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested; m_touchInfoMap[touchPoint.id()].ended = false; TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this); + + Q_EMIT pressed(); } goodToGo &= m_touchInfoMap.contains(touchPoint.id()) diff -Nru unity8-8.02+15.04.20150205/plugins/Ubuntu/Gestures/TouchGate.h unity8-8.02+15.04.20150211/plugins/Ubuntu/Gestures/TouchGate.h --- unity8-8.02+15.04.20150205/plugins/Ubuntu/Gestures/TouchGate.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Ubuntu/Gestures/TouchGate.h 2015-02-11 17:11:30.000000000 +0000 @@ -53,6 +53,8 @@ Q_SIGNALS: void targetItemChanged(QQuickItem *item); + void pressed(); + protected: void touchEvent(QTouchEvent *event) override; private: diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicator.cpp unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicator.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicator.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicator.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -31,24 +31,26 @@ { } -void Indicator::init(const QString &profile, const QString& busName, const QSettings& settings) +void Indicator::init(const QString& busName, const QSettings& settings) { - setId(settings.value("Indicator Service/Name").toString()); + // Save all keys we care about from the QSettings object. It's annoying + // that we can't just copy the object. + m_settings.clear(); + Q_FOREACH(const QString& key, settings.allKeys()) { + if (key.endsWith("/Position") || key.endsWith("/ObjectPath")) { + m_settings.insert(key, settings.value(key)); + } + } - QVariant pos = settings.value(profile + "/Position"); - if (!pos.isValid()) - pos = settings.value("Indicator Service/Position", QVariant::fromValue(0)); - setPosition(pos.toInt()); + setId(settings.value("Indicator Service/Name").toString()); QString actionObjectPath = settings.value("Indicator Service/ObjectPath").toString(); - QString menuObjectPath = settings.value(profile + "/ObjectPath").toString(); QVariantMap properties; properties.clear(); properties.insert("busType", 1); properties.insert("busName", busName); properties.insert("actionsObjectPath", actionObjectPath); - properties.insert("menuObjectPath", menuObjectPath); setIndicatorProperties(properties); } @@ -78,6 +80,19 @@ } } +void Indicator::setProfile(const QString& profile) +{ + QVariant pos = m_settings.value(profile + "/Position"); + if (!pos.isValid()) + pos = m_settings.value("Indicator Service/Position", QVariant::fromValue(0)); + setPosition(pos.toInt()); + + QString menuObjectPath = m_settings.value(profile + "/ObjectPath").toString(); + QVariantMap map = m_properties.toMap(); + map.insert("menuObjectPath", menuObjectPath); + setIndicatorProperties(map); +} + QVariant Indicator::indicatorProperties() const { return m_properties; diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicator.h unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicator.h --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicator.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicator.h 2015-02-11 17:12:49.000000000 +0000 @@ -35,12 +35,15 @@ Indicator(QObject *parent = 0); virtual ~Indicator(); - void init(const QString &profile, const QString& busName, const QSettings& settings); + void init(const QString& busName, const QSettings& settings); QString identifier() const; int position() const; QVariant indicatorProperties() const; +public Q_SLOTS: + void setProfile(const QString& profile); + Q_SIGNALS: void identifierChanged(const QString &identifier); void positionChanged(int position); @@ -55,6 +58,7 @@ QString m_identifier; int m_position; QVariant m_properties; + QVariantMap m_settings; }; #endif // INDICATOR_H diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmanager.cpp unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmanager.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmanager.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmanager.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -54,10 +54,9 @@ } -void IndicatorsManager::load(const QString& profile) +void IndicatorsManager::load() { unload(); - m_profile = profile; QStringList xdgLocations = shellDataDirs();//QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); @@ -216,6 +215,19 @@ } } +QString IndicatorsManager::profile() const +{ + return m_profile; +} + +void IndicatorsManager::setProfile(const QString& profile) +{ + if (m_profile != profile) { + m_profile = profile; + Q_EMIT profileChanged(m_profile); + } +} + void IndicatorsManager::startVerify(const QString& path) { QHashIterator iter(m_indicatorsData); @@ -268,7 +280,9 @@ Indicator::Ptr new_indicator(new Indicator(this)); data->m_indicator = new_indicator; QSettings settings(data->m_fileInfo.absoluteFilePath(), QSettings::IniFormat, this); - new_indicator->init(m_profile, data->m_fileInfo.fileName(), settings); + new_indicator->init(data->m_fileInfo.fileName(), settings); + new_indicator->setProfile(m_profile); + QObject::connect(this, SIGNAL(profileChanged(const QString&)), new_indicator.data(), SLOT(setProfile(const QString&))); return new_indicator; } diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmanager.h unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmanager.h --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmanager.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmanager.h 2015-02-11 17:12:49.000000000 +0000 @@ -32,13 +32,17 @@ { Q_OBJECT Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged) + Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) public: explicit IndicatorsManager(QObject* parent = 0); ~IndicatorsManager(); - Q_INVOKABLE void load(const QString& profile); + Q_INVOKABLE void load(); Q_INVOKABLE void unload(); + QString profile() const; + void setProfile(const QString& profile); + Indicator::Ptr indicator(const QString& indicator_name); QList indicators(); @@ -47,6 +51,7 @@ Q_SIGNALS: void loadedChanged(bool); + void profileChanged(const QString&); void indicatorLoaded(const QString& indicator_name); void indicatorAboutToBeUnloaded(const QString& indicator_name); diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmodel.cpp unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmodel.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmodel.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmodel.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -56,6 +56,7 @@ m_manager = new IndicatorsManager(this); QObject::connect(m_manager, SIGNAL(indicatorLoaded(const QString&)), this, SLOT(onIndicatorLoaded(const QString&))); QObject::connect(m_manager, SIGNAL(indicatorAboutToBeUnloaded(const QString&)), this, SLOT(onIndicatorAboutToBeUnloaded(const QString&))); + QObject::connect(m_manager, SIGNAL(profileChanged(const QString&)), this, SIGNAL(profileChanged())); QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged())); QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged())); @@ -81,14 +82,36 @@ } /*! + \qmlproperty IndicatorsModel::profile + The indicator profile to use for the model. + + \b Note: methods should only be called after the Component has completed. +*/ +QString IndicatorsModel::profile() const +{ + return m_manager->profile(); +} + +/*! + \qmlproperty IndicatorsModel::setProfile + Set the indicator profile to use for the model. + + \b Note: methods should only be called after the Component has completed. +*/ +void IndicatorsModel::setProfile(const QString &profile) +{ + m_manager->setProfile(profile); +} + +/*! \qmlmethod IndicatorsModel::unload() Load all indicators. */ -void IndicatorsModel::load(const QString& profile) +void IndicatorsModel::load() { m_indicators.clear(); - m_manager->load(profile); + m_manager->load(); } /*! diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmodel.h unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmodel.h --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/indicatorsmodel.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/indicatorsmodel.h 2015-02-11 17:12:49.000000000 +0000 @@ -34,17 +34,21 @@ Q_OBJECT Q_ENUMS(Roles) Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) public: IndicatorsModel(QObject *parent=0); ~IndicatorsModel(); - Q_INVOKABLE void load(const QString& profile); + Q_INVOKABLE void load(); Q_INVOKABLE void unload(); Q_INVOKABLE QVariant data(int row, int role) const; + QString profile() const; + void setProfile(const QString& profile); + /* QAbstractItemModel */ QHash roleNames() const; int columnCount(const QModelIndex &parent = QModelIndex()) const; @@ -55,6 +59,7 @@ Q_SIGNALS: void countChanged(); + void profileChanged(); void indicatorDataChanged(const QVariant& data); private Q_SLOTS: diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/modelactionrootstate.cpp unity8-8.02+15.04.20150211/plugins/Unity/Indicators/modelactionrootstate.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/modelactionrootstate.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/modelactionrootstate.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -71,7 +71,7 @@ bool ModelActionRootState::valid() const { - return m_menu && m_menu->rowCount() > 0; + return !currentState().empty(); } void ModelActionRootState::onModelRowsAdded(const QModelIndex& parent, int start, int end) @@ -121,7 +121,9 @@ m_menu->setActionStateParser(oldParser); setCurrentState(state); - } else { + } else if (!m_menu) { setCurrentState(QVariantMap()); } + // else if m_menu->rowCount() == 0, let's leave existing cache in place + // until the new menu comes in, to avoid flashing the UI empty for a moment } diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/unitymenumodelcache.cpp unity8-8.02+15.04.20150211/plugins/Unity/Indicators/unitymenumodelcache.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/unitymenumodelcache.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/unitymenumodelcache.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -46,17 +46,14 @@ QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership); QSharedPointer menuModel(model); - connect(model, &QObject::destroyed, this, [this] { - QMutableHashIterator> iter(m_registry); - while(iter.hasNext()) { - auto keyVal = iter.next(); - if (keyVal.value().isNull()) { - iter.remove(); - break; - } - } - }); - m_registry[path] = menuModel.toWeakRef(); + + // Keep a shared pointer (rather than weak pointer which would cause the + // model to be deleted when all shared pointers we give out are deleted). + // We want to keep all models cached because when we switch indicator + // profiles, we will be switching paths often. And we want to keep the + // old model around, ready to be used. Otherwise the UI might momentarily + // wait as we populate the model from DBus yet again. + m_registry[path] = menuModel; menuModel->setMenuObjectPath(path); return menuModel; diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Indicators/unitymenumodelcache.h unity8-8.02+15.04.20150211/plugins/Unity/Indicators/unitymenumodelcache.h --- unity8-8.02+15.04.20150205/plugins/Unity/Indicators/unitymenumodelcache.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Indicators/unitymenumodelcache.h 2015-02-11 17:12:49.000000000 +0000 @@ -25,7 +25,7 @@ #include #include #include -#include +#include class UnityMenuModel; @@ -43,7 +43,7 @@ Q_INVOKABLE virtual bool contains(const QByteArray& path); protected: - QHash> m_registry; + QHash> m_registry; static QPointer theCache; }; diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Launcher/asadapter.cpp unity8-8.02+15.04.20150211/plugins/Unity/Launcher/asadapter.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Launcher/asadapter.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Launcher/asadapter.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,60 @@ +/* + * Copyright 2014-2015 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 . + */ + +#include "asadapter.h" +#include "launcheritem.h" +#include "AccountsServiceDBusAdaptor.h" + +#include + +ASAdapter::ASAdapter() +{ + m_accounts = new AccountsServiceDBusAdaptor(); + m_user = qgetenv("USER"); + if (m_user.isEmpty()) { + qWarning() << "$USER not valid. Account Service integration will not work."; + } +} + +ASAdapter::~ASAdapter() +{ + m_accounts->deleteLater(); +} + +void ASAdapter::syncItems(QList m_list) +{ + if (m_accounts && !m_user.isEmpty()) { + QList items; + + Q_FOREACH(LauncherItem *item, m_list) { + items << itemToVariant(item); + } + + m_accounts->setUserPropertyAsync(m_user, "com.canonical.unity.AccountsService", "LauncherItems", QVariant::fromValue(items)); + } +} + +QVariantMap ASAdapter::itemToVariant(LauncherItem *item) const +{ + QVariantMap details; + details.insert("id", item->appId()); + details.insert("name", item->name()); + details.insert("icon", item->icon()); + details.insert("count", item->count()); + details.insert("countVisible", item->countVisible()); + details.insert("pinned", item->pinned()); + return details; +} diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Launcher/asadapter.h unity8-8.02+15.04.20150211/plugins/Unity/Launcher/asadapter.h --- unity8-8.02+15.04.20150205/plugins/Unity/Launcher/asadapter.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Launcher/asadapter.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2015 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 . + */ + +#ifndef ASADAPTER_H +#define ASADAPTER_H + +#include + +class LauncherItem; +class AccountsServiceDBusAdaptor; +class QDBusInterface; + +class ASAdapter +{ +public: + ASAdapter(); + ~ASAdapter(); + + void syncItems(QList m_list); + +private: + QVariantMap itemToVariant(LauncherItem *item) const; + +private: + AccountsServiceDBusAdaptor *m_accounts; + QString m_user; + + QDBusInterface *m_userInterface; + + friend class LauncherModelTest; +}; + +#endif diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Launcher/CMakeLists.txt unity8-8.02+15.04.20150211/plugins/Unity/Launcher/CMakeLists.txt --- unity8-8.02+15.04.20150205/plugins/Unity/Launcher/CMakeLists.txt 2015-02-05 10:28:52.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Launcher/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -1,4 +1,4 @@ -pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=5) +pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6) pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5) pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt) @@ -20,6 +20,7 @@ dbusinterface.cpp gsettings.cpp desktopfilehandler.cpp + asadapter.cpp ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Launcher/launchermodel.cpp unity8-8.02+15.04.20150211/plugins/Unity/Launcher/launchermodel.cpp --- unity8-8.02+15.04.20150205/plugins/Unity/Launcher/launchermodel.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Launcher/launchermodel.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -22,6 +22,7 @@ #include "gsettings.h" #include "desktopfilehandler.h" #include "dbusinterface.h" +#include "asadapter.h" #include @@ -34,6 +35,7 @@ LauncherModelInterface(parent), m_settings(new GSettings(this)), m_dbusIface(new DBusInterface(this)), + m_asAdapter(new ASAdapter()), m_appManager(0) { connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged); @@ -50,6 +52,8 @@ while (!m_list.empty()) { m_list.takeFirst()->deleteLater(); } + + delete m_asAdapter; } int LauncherModel::rowCount(const QModelIndex &parent) const @@ -261,6 +265,15 @@ } } +bool LauncherModel::onlyPinned() const +{ + return false; +} + +void LauncherModel::setOnlyPinned(bool onlyPinned) { + Q_UNUSED(onlyPinned); + qWarning() << "This launcher implementation does not support showing only pinned apps"; +} void LauncherModel::storeAppList() { @@ -271,6 +284,7 @@ } } m_settings->setStoredApplications(appIds); + m_asAdapter->syncItems(m_list); } void LauncherModel::unpin(const QString &appId) @@ -321,6 +335,7 @@ LauncherItem *item = m_list.at(idx); item->setCount(count); Q_EMIT dataChanged(index(idx), index(idx), QVector() << RoleCount); + m_asAdapter->syncItems(m_list); } } @@ -352,6 +367,7 @@ Q_EMIT hint(); } } + m_asAdapter->syncItems(m_list); } void LauncherModel::refresh() @@ -433,6 +449,8 @@ if (changed) { Q_EMIT hint(); } + + m_asAdapter->syncItems(m_list); } void LauncherModel::applicationAdded(const QModelIndex &parent, int row) @@ -467,6 +485,7 @@ beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); m_list.append(item); endInsertRows(); + m_asAdapter->syncItems(m_list); } } @@ -486,6 +505,7 @@ beginRemoveRows(QModelIndex(), appIndex, appIndex); m_list.takeAt(appIndex)->deleteLater(); endRemoveRows(); + m_asAdapter->syncItems(m_list); } } diff -Nru unity8-8.02+15.04.20150205/plugins/Unity/Launcher/launchermodel.h unity8-8.02+15.04.20150211/plugins/Unity/Launcher/launchermodel.h --- unity8-8.02+15.04.20150205/plugins/Unity/Launcher/launchermodel.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Unity/Launcher/launchermodel.h 2015-02-11 17:10:20.000000000 +0000 @@ -28,6 +28,7 @@ class LauncherItem; class GSettings; class DBusInterface; +class ASAdapter; using namespace unity::shell::launcher; using namespace unity::shell::application; @@ -54,6 +55,9 @@ unity::shell::application::ApplicationManagerInterface* applicationManager() const; void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager); + bool onlyPinned() const override; + void setOnlyPinned(bool onlyPinned) override; + int findApplication(const QString &appId); public Q_SLOTS: @@ -79,6 +83,7 @@ GSettings *m_settings; DBusInterface *m_dbusIface; + ASAdapter *m_asAdapter; ApplicationManagerInterface *m_appManager; diff -Nru unity8-8.02+15.04.20150205/plugins/Utils/CMakeLists.txt unity8-8.02+15.04.20150211/plugins/Utils/CMakeLists.txt --- unity8-8.02+15.04.20150205/plugins/Utils/CMakeLists.txt 2015-02-05 10:28:52.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Utils/CMakeLists.txt 2015-02-11 17:12:21.000000000 +0000 @@ -15,6 +15,7 @@ unitymenumodelpaths.cpp windowkeysfilter.cpp easingcurve.cpp + windowstatestorage.cpp plugin.cpp ) @@ -30,6 +31,6 @@ # files directly in targets. set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default) -qt5_use_modules(Utils-qml Qml Quick DBus Network Gui) +qt5_use_modules(Utils-qml Qml Quick DBus Network Gui Sql Concurrent) add_unity8_plugin(Utils 0.1 Utils TARGETS Utils-qml) diff -Nru unity8-8.02+15.04.20150205/plugins/Utils/plugin.cpp unity8-8.02+15.04.20150211/plugins/Utils/plugin.cpp --- unity8-8.02+15.04.20150205/plugins/Utils/plugin.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Utils/plugin.cpp 2015-02-11 17:12:21.000000000 +0000 @@ -33,6 +33,14 @@ #include "unitymenumodelpaths.h" #include "windowkeysfilter.h" #include "easingcurve.h" +#include "windowstatestorage.h" + +static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return new WindowStateStorage(); +} void UtilsPlugin::registerTypes(const char *uri) { @@ -46,6 +54,7 @@ qmlRegisterType(uri, 0, 1, "GDateTimeFormatter"); qmlRegisterType(uri, 0, 1, "EasingCurve"); qmlRegisterType(uri, 0, 1, "RelativeTimeFormatter"); + qmlRegisterSingletonType(uri, 0, 1, "WindowStateStorage", createWindowStateStorage); } void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri) diff -Nru unity8-8.02+15.04.20150205/plugins/Utils/windowstatestorage.cpp unity8-8.02+15.04.20150211/plugins/Utils/windowstatestorage.cpp --- unity8-8.02+15.04.20150205/plugins/Utils/windowstatestorage.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Utils/windowstatestorage.cpp 2015-02-11 17:12:21.000000000 +0000 @@ -0,0 +1,96 @@ +/* + * Copyright 2015 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 . + */ + +#include "windowstatestorage.h" + +#include +#include +#include +#include +#include +#include + +QMutex WindowStateStorage::s_mutex; + +WindowStateStorage::WindowStateStorage(QObject *parent): + QObject(parent) +{ + QString dbPath = QDir::homePath() + "/.cache/unity8/"; + m_db = QSqlDatabase::addDatabase("QSQLITE"); + QDir dir; + dir.mkpath(dbPath); + m_db.setDatabaseName(dbPath + "windowstatestorage.sqlite"); + initdb(); +} + +void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect) +{ + QString queryString = QString("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');") + .arg(windowId) + .arg(rect.x()) + .arg(rect.y()) + .arg(rect.width()) + .arg(rect.height()); + + QtConcurrent::run(executeAsyncQuery, queryString); +} + +void WindowStateStorage::executeAsyncQuery(const QString &queryString) +{ + QMutexLocker l(&s_mutex); + QSqlQuery query; + + bool ok = query.exec(queryString); + if (!ok) { + qWarning() << "Error executing query" << queryString + << "Driver error:" << query.lastError().driverText() + << "Database error:" << query.lastError().databaseText(); + } +} + +QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) +{ + QMutexLocker l(&s_mutex); + QString queryString = QString("SELECT * FROM geometry WHERE windowId = '%1';") + .arg(windowId); + QSqlQuery query; + + bool ok = query.exec(queryString); + if (!ok) { + qWarning() << "Error retrieving window state for" << windowId + << "Driver error:" << query.lastError().driverText() + << "Database error:" << query.lastError().databaseText(); + return defaultValue; + } + if (!query.first()) { + return defaultValue; + } + return QRect(query.value("x").toInt(), query.value("y").toInt(), query.value("width").toInt(), query.value("height").toInt()); +} + +void WindowStateStorage::initdb() +{ + m_db.open(); + if (!m_db.open()) { + qWarning() << "Error opening state database:" << m_db.lastError().driverText() << m_db.lastError().databaseText(); + return; + } + + if (!m_db.tables().contains("geometry")) { + QSqlQuery query; + query.exec("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"); + } +} diff -Nru unity8-8.02+15.04.20150205/plugins/Utils/windowstatestorage.h unity8-8.02+15.04.20150211/plugins/Utils/windowstatestorage.h --- unity8-8.02+15.04.20150205/plugins/Utils/windowstatestorage.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/plugins/Utils/windowstatestorage.h 2015-02-11 17:12:21.000000000 +0000 @@ -0,0 +1,38 @@ +/* + * Copyright 2015 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 . + */ + +#include +#include +#include + +class WindowStateStorage: public QObject +{ + Q_OBJECT +public: + WindowStateStorage(QObject *parent = 0); + + Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect); + Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue); + +private: + void initdb(); + + static void executeAsyncQuery(const QString &queryString); + static QMutex s_mutex; + + // NB: This is accessed from threads. Make sure to mutex it. + QSqlDatabase m_db; +}; diff -Nru unity8-8.02+15.04.20150205/po/be.po unity8-8.02+15.04.20150211/po/be.po --- unity8-8.02+15.04.20150205/po/be.po 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/po/be.po 2015-02-11 17:09:00.000000000 +0000 @@ -8,15 +8,15 @@ "Project-Id-Version: unity8\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-01-22 11:09+0100\n" -"PO-Revision-Date: 2015-02-03 22:02+0000\n" -"Last-Translator: Arsień Šachalevič \n" +"PO-Revision-Date: 2015-02-07 19:04+0000\n" +"Last-Translator: Mikhail_SaTuRn \n" "Language-Team: Belarusian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Launchpad-Export-Date: 2015-02-04 07:13+0000\n" +"X-Launchpad-Export-Date: 2015-02-08 06:11+0000\n" "X-Generator: Launchpad (build 17331)\n" #: plugins/LightDM/Greeter.cpp:130 @@ -87,7 +87,7 @@ #: qml/Components/Dialogs.qml:132 msgid "Are you sure you want to reboot?" -msgstr "Вы ўпэўненыя, што жадаеце перазагрузіць?" +msgstr "Вы ўпэўнены, што жадаеце перазагрузіць?" #: qml/Components/Dialogs.qml:154 msgid "Power" @@ -166,11 +166,11 @@ #: qml/Components/Lockscreen.qml:238 msgid "Return to Call" -msgstr "Вярнуцца да Выкліку" +msgstr "Вярнуцца да выкліку" #: qml/Components/Lockscreen.qml:238 msgid "Emergency Call" -msgstr "Терміновы Выклік" +msgstr "Терміновы выклік" #: qml/Components/Lockscreen.qml:270 msgid "OK" @@ -518,7 +518,7 @@ #: qml/Wizard/Pages/passwd-confirm.qml:50 msgid "Confirm passcode" -msgstr "Пацвердзіце код-пароль" +msgstr "Пацвердзіце код" #: qml/Wizard/Pages/passwd-confirm.qml:53 #: qml/Wizard/Pages/passwd-confirm.qml:54 diff -Nru unity8-8.02+15.04.20150205/po/oc.po unity8-8.02+15.04.20150211/po/oc.po --- unity8-8.02+15.04.20150205/po/oc.po 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/po/oc.po 2015-02-11 17:09:00.000000000 +0000 @@ -0,0 +1,521 @@ +# Occitan (post 1500) translation for unity8 +# Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 +# This file is distributed under the same license as the unity8 package. +# FIRST AUTHOR , 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: unity8\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2015-01-22 11:09+0100\n" +"PO-Revision-Date: 2015-02-05 20:35+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Occitan (post 1500) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Launchpad-Export-Date: 2015-02-07 07:31+0000\n" +"X-Generator: Launchpad (build 17331)\n" + +#: plugins/LightDM/Greeter.cpp:130 +msgid "Password: " +msgstr "Senhal : " + +#: plugins/Unity/Launcher/launcheritem.cpp:45 +#: plugins/Unity/Launcher/launcheritem.cpp:100 +msgid "Pin shortcut" +msgstr "" + +#: plugins/Unity/Launcher/launcheritem.cpp:100 +msgid "Unpin shortcut" +msgstr "" + +#: qml/Components/DelayedLockscreen.qml:47 +msgid "Device Locked" +msgstr "" + +#: qml/Components/DelayedLockscreen.qml:62 +msgid "You have been locked out due to too many failed passphrase attempts." +msgstr "" + +#: qml/Components/DelayedLockscreen.qml:63 +msgid "You have been locked out due to too many failed passcode attempts." +msgstr "" + +#: qml/Components/DelayedLockscreen.qml:72 +#, qt-format +msgid "Please wait %1 minute and then try again…" +msgid_plural "Please wait %1 minutes and then try again…" +msgstr[0] "" +msgstr[1] "" + +#: qml/Components/Dialogs.qml:86 +msgid "Log out" +msgstr "" + +#: qml/Components/Dialogs.qml:87 +msgid "Are you sure you want to log out?" +msgstr "" + +#: qml/Components/Dialogs.qml:89 qml/Components/Dialogs.qml:111 +#: qml/Components/Dialogs.qml:134 +msgid "No" +msgstr "Non" + +#: qml/Components/Dialogs.qml:95 qml/Components/Dialogs.qml:117 +#: qml/Components/Dialogs.qml:140 +msgid "Yes" +msgstr "" + +#: qml/Components/Dialogs.qml:108 +msgid "Shut down" +msgstr "" + +#: qml/Components/Dialogs.qml:109 +msgid "Are you sure you want to shut down?" +msgstr "" + +#: qml/Components/Dialogs.qml:131 +msgid "Reboot" +msgstr "" + +#: qml/Components/Dialogs.qml:132 +msgid "Are you sure you want to reboot?" +msgstr "" + +#: qml/Components/Dialogs.qml:154 +msgid "Power" +msgstr "" + +#: qml/Components/Dialogs.qml:155 +msgid "" +"Are you sure you would like\n" +"to power off?" +msgstr "" + +#: qml/Components/Dialogs.qml:157 +msgid "Power off" +msgstr "" + +#: qml/Components/Dialogs.qml:166 +msgid "Restart" +msgstr "" + +#: qml/Components/Dialogs.qml:175 +msgid "Cancel" +msgstr "" + +#: qml/Components/EdgeDemoOverlay.qml:151 +msgid "Skip intro" +msgstr "" + +#: qml/Components/EdgeDemo.qml:125 +msgid "Right edge" +msgstr "" + +#: qml/Components/EdgeDemo.qml:126 +msgid "Try swiping from the right edge to unlock the phone" +msgstr "" + +#: qml/Components/EdgeDemo.qml:157 +msgid "Top edge" +msgstr "" + +#: qml/Components/EdgeDemo.qml:158 +msgid "Try swiping from the top edge to access the indicators" +msgstr "" + +#: qml/Components/EdgeDemo.qml:183 +msgid "Close" +msgstr "" + +#: qml/Components/EdgeDemo.qml:184 +msgid "Swipe up again to close the settings screen" +msgstr "" + +#: qml/Components/EdgeDemo.qml:214 +msgid "Left edge" +msgstr "" + +#: qml/Components/EdgeDemo.qml:215 +msgid "Swipe from the left to reveal the launcher for quick access to apps" +msgstr "" + +#: qml/Components/EdgeDemo.qml:242 +msgid "Well done" +msgstr "" + +#: qml/Components/EdgeDemo.qml:243 +msgid "" +"You have now mastered the edge gestures and can start using the " +"phone

Tap on the screen to start" +msgstr "" + +#: qml/Components/Lockscreen.qml:238 +msgid "Return to Call" +msgstr "" + +#: qml/Components/Lockscreen.qml:238 +msgid "Emergency Call" +msgstr "" + +#: qml/Components/Lockscreen.qml:270 +msgid "OK" +msgstr "" + +#: qml/Dash/GenericScopeView.qml:485 qml/Dash/GenericScopeView.qml:642 +msgid "See less" +msgstr "" + +#: qml/Dash/GenericScopeView.qml:485 +msgid "See all" +msgstr "" + +#: qml/Dash/GenericScopeView.qml:547 qml/Dash/PageHeader.qml:274 +#: qml/Panel/SearchIndicator.qml:27 +msgid "Search" +msgstr "" + +#: qml/Dash/PageHeader.qml:267 +msgid "Store" +msgstr "" + +#: qml/Dash/PageHeader.qml:284 +msgid "Settings" +msgstr "" + +#: qml/Dash/PageHeader.qml:291 +msgid "Remove from Favorites" +msgstr "" + +#: qml/Dash/PageHeader.qml:291 +msgid "Add to Favorites" +msgstr "" + +#: qml/Dash/Previews/PreviewActionCombo.qml:35 +msgid "More..." +msgstr "" + +#: qml/Dash/Previews/PreviewActionCombo.qml:35 +msgid "Less..." +msgstr "" + +#: qml/Dash/Previews/PreviewRatingInput.qml:81 +msgid "Rate this" +msgstr "" + +#: qml/Dash/Previews/PreviewRatingInput.qml:126 +msgid "Add a review" +msgstr "" + +#: qml/Dash/Previews/PreviewRatingInput.qml:166 +msgid "Send" +msgstr "" + +#: qml/Dash/PullToRefreshScopeStyle.qml:55 +msgid "Pull to refresh…" +msgstr "" + +#: qml/Dash/PullToRefreshScopeStyle.qml:60 +msgid "Release to refresh…" +msgstr "" + +#: qml/Dash/ScopesList.qml:66 +msgid "Manage" +msgstr "" + +#: qml/Dash/ScopesList.qml:112 +msgid "Home" +msgstr "" + +#: qml/Dash/ScopesList.qml:113 +msgid "Also installed" +msgstr "" + +#: qml/Greeter/Greeter.qml:133 +msgid "Unlock" +msgstr "" + +#: qml/Notifications/NotificationMenuItemFactory.qml:128 +msgid "Show password" +msgstr "" + +#: qml/Panel/ActiveCallHint.qml:79 +msgid "Tap to return to call..." +msgstr "" + +#: qml/Panel/ActiveCallHint.qml:92 +msgid "Conference" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:643 +msgid "Nothing is playing" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:791 +msgid "In queue…" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:795 +msgid "Downloading" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:797 +msgid "Paused, tap to resume" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:799 +msgid "Canceled" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:801 +msgid "Finished" +msgstr "" + +#: qml/Panel/Indicators/MenuItemFactory.qml:803 +msgid "Failed, tap to retry" +msgstr "" + +#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149 +msgid "Reply" +msgstr "" + +#: qml/Panel/Indicators/ModemInfoItem.qml:105 +msgid "Unlock SIM" +msgstr "" + +#: qml/Panel/Indicators/RoamingIndication.qml:27 +msgid "Roaming" +msgstr "" + +#: qml/Shell.qml:390 +#, qt-format +msgid "Enter %1" +msgstr "" + +#: qml/Shell.qml:391 qml/Wizard/Pages/passwd-set.qml:60 +msgid "Enter passphrase" +msgstr "" + +#: qml/Shell.qml:392 +msgid "Enter passcode" +msgstr "" + +#: qml/Shell.qml:393 +#, qt-format +msgid "Sorry, incorrect %1" +msgstr "" + +#: qml/Shell.qml:394 +msgid "Sorry, incorrect passphrase" +msgstr "" + +#: qml/Shell.qml:395 +msgid "Please re-enter" +msgstr "" + +#: qml/Shell.qml:396 +msgid "Sorry, incorrect passcode" +msgstr "" + +#: qml/Shell.qml:490 qml/Wizard/Pages/passwd-confirm.qml:53 +msgid "Sorry, incorrect passphrase." +msgstr "" + +#: qml/Shell.qml:491 qml/Wizard/Pages/passwd-confirm.qml:54 +msgid "Sorry, incorrect passcode." +msgstr "" + +#: qml/Shell.qml:492 +msgid "This will be your last attempt." +msgstr "" + +#: qml/Shell.qml:494 +msgid "" +"If passphrase is entered incorrectly, your phone will conduct a factory " +"reset and all personal data will be deleted." +msgstr "" + +#: qml/Shell.qml:495 +msgid "" +"If passcode is entered incorrectly, your phone will conduct a factory reset " +"and all personal data will be deleted." +msgstr "" + +#: qml/Wizard/Page.qml:89 +msgid "Back" +msgstr "" + +#: qml/Wizard/Pages/10-welcome.qml:27 +msgid "Hi!" +msgstr "" + +#: qml/Wizard/Pages/10-welcome.qml:44 +msgid "Welcome to your Ubuntu phone." +msgstr "" + +#: qml/Wizard/Pages/10-welcome.qml:52 +msgid "Let’s get started." +msgstr "" + +#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128 +#: qml/Wizard/Pages/40-wifi.qml:197 qml/Wizard/Pages/50-location.qml:131 +#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82 +#: qml/Wizard/Pages/passwd-set.qml:90 +msgid "Continue" +msgstr "" + +#: qml/Wizard/Pages/20-sim.qml:25 +msgid "Add a SIM card and restart your device" +msgstr "" + +#: qml/Wizard/Pages/20-sim.qml:55 +msgid "Without it, you won’t be able to make calls or use text messaging." +msgstr "" + +#: qml/Wizard/Pages/20-sim.qml:69 qml/Wizard/Pages/40-wifi.qml:197 +msgid "Skip" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:39 +msgid "Lock security" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:74 +msgid "Please select how you’d like to unlock your phone." +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:97 +msgid "Swipe" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:98 +msgid "No security" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:100 +msgid "Passcode" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:101 +msgid "4 digits only" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:103 +msgid "Passphrase" +msgstr "" + +#: qml/Wizard/Pages/30-passwd-type.qml:104 +msgid "Numbers and letters" +msgstr "" + +#: qml/Wizard/Pages/40-wifi.qml:28 +msgid "Connect to Wi‑Fi" +msgstr "" + +#: qml/Wizard/Pages/40-wifi.qml:147 +msgid "Available networks…" +msgstr "" + +#: qml/Wizard/Pages/40-wifi.qml:148 +msgid "No available networks." +msgstr "" + +#: qml/Wizard/Pages/50-location.qml:27 +msgid "Location" +msgstr "" + +#: qml/Wizard/Pages/50-location.qml:62 +msgid "Let the phone detect your location:" +msgstr "" + +#: qml/Wizard/Pages/50-location.qml:69 +msgid "Using GPS only (less accurate)" +msgstr "" + +#: qml/Wizard/Pages/50-location.qml:86 +msgid "Using GPS, anonymized Wi-Fi and cellular network info (recommended)" +msgstr "" + +#. TRANSLATORS: HERE is a trademark for Nokia's location service, you probably shouldn't translate it +#: qml/Wizard/Pages/50-location.qml:103 +msgid "" +"By selecting this option you agree to the Nokia HERE terms and " +"conditions." +msgstr "" + +#: qml/Wizard/Pages/50-location.qml:112 +msgid "Not at all" +msgstr "" + +#: qml/Wizard/Pages/50-location.qml:124 +msgid "You can change your mind later in System Settings." +msgstr "" + +#: qml/Wizard/Pages/60-reporting.qml:24 +msgid "Improving your experience" +msgstr "" + +#: qml/Wizard/Pages/60-reporting.qml:36 +msgid "" +"Your phone is set up to automatically report errors to Canonical and its " +"partners, the makers of the operating system." +msgstr "" + +#: qml/Wizard/Pages/60-reporting.qml:43 +msgid "" +"This can be disabled in System Settings under Security & " +"Privacy" +msgstr "" + +#: qml/Wizard/Pages/80-finished.qml:24 +msgid "All done" +msgstr "" + +#: qml/Wizard/Pages/80-finished.qml:39 +msgid "Nice work!" +msgstr "" + +#: qml/Wizard/Pages/80-finished.qml:46 +msgid "Your phone is now ready to use." +msgstr "" + +#: qml/Wizard/Pages/80-finished.qml:53 +msgid "Finish" +msgstr "" + +#: qml/Wizard/Pages/here-terms.qml:27 +msgid "Terms & Conditions" +msgstr "" + +#: qml/Wizard/Pages/passwd-confirm.qml:49 +msgid "Confirm passphrase" +msgstr "" + +#: qml/Wizard/Pages/passwd-confirm.qml:50 +msgid "Confirm passcode" +msgstr "" + +#: qml/Wizard/Pages/passwd-confirm.qml:53 +#: qml/Wizard/Pages/passwd-confirm.qml:54 +msgid "Please try again." +msgstr "" + +#: qml/Wizard/Pages/passwd-set.qml:61 +msgid "Choose your passcode" +msgstr "" + +#: qml/Wizard/Pages/passwd-set.qml:68 +msgid "Passphrase must be 4 characters long" +msgstr "" + +#: qml/Wizard/StackButton.qml:39 +#, qt-format +msgid "〈 %1" +msgstr "" + +#: qml/Wizard/StackButton.qml:42 +#, qt-format +msgid "%1 〉" +msgstr "" diff -Nru unity8-8.02+15.04.20150205/qml/CMakeLists.txt unity8-8.02+15.04.20150211/qml/CMakeLists.txt --- unity8-8.02+15.04.20150205/qml/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/CMakeLists.txt 2015-02-11 17:11:40.000000000 +0000 @@ -9,10 +9,11 @@ Dash graphics Greeter - Panel Launcher - Stages Notifications + Panel + Stages + Tutorial Wizard ) diff -Nru unity8-8.02+15.04.20150205/qml/Components/EdgeDemoOverlay.qml unity8-8.02+15.04.20150211/qml/Components/EdgeDemoOverlay.qml --- unity8-8.02+15.04.20150205/qml/Components/EdgeDemoOverlay.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Components/EdgeDemoOverlay.qml 1970-01-01 00:00:00.000000000 +0000 @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2013 Canonical, Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import Powerd 0.1 -import QtQuick 2.0 -import QtGraphicalEffects 1.0 -import Unity.Application 0.1 -import Ubuntu.Components 0.1 - -Showable { - id: overlay - - /* - * Valid values are "left", "right", "top", "bottom", or "none". - */ - property string edge: "top" - - /* - * This is the header displayed, like "Right edge". - */ - property alias title: titleLabel.text - - /* - * This is the block of text displayed below the header. - */ - property alias text: textLabel.text - - /* - * This is the text for the skip button. - */ - property alias skipText: skipLabel.text - - /* - * This is the visible status of the skip button. - */ - property alias showSkip: skipLabel.visible - - /* - * Whether this demo is running currently. - */ - readonly property bool active: available && visible - - /* - * Whether animations are paused. - */ - property alias paused: wholeAnimation.paused - - /* - * Whether animations are running. - */ - readonly property alias running: wholeAnimation.running - - signal skip() - - function doSkip() { - d.skipOnHide = true; - hide(); - } - - function hideNow() { - overlay.visible = false; - overlay.available = false; - if (d.skipOnHide) { - overlay.skip(); - } - } - - showAnimation: StandardAnimation { - property: "opacity" - to: 1 - onRunningChanged: if (running) overlay.visible = true - } - hideAnimation: StandardAnimation { - property: "opacity" - to: 0 - duration: UbuntuAnimation.BriskDuration - onRunningChanged: if (!running) overlay.hideNow() - } - - QtObject { - id: d - property bool skipOnHide: false - property int edgeMargin: units.gu(4) - } - - Rectangle { - objectName: "backgroundShade" - - anchors.fill: parent - color: "black" - opacity: 0.8 - visible: overlay.active - - MouseArea { - objectName: "backgroundShadeMouseArea" - - anchors.fill: parent - enabled: overlay.edge == "none" && overlay.opacity == 1.0 - onClicked: overlay.doSkip() - } - } - - Item { - id: hintGroup - x: 0 - y: 0 - width: parent.width - height: parent.height - visible: overlay.active - - Column { - id: labelGroup - spacing: units.gu(3) - - anchors { - margins: d.edgeMargin - left: parent.left - top: overlay.edge == "bottom" ? undefined : parent.top - bottom: overlay.edge == "bottom" ? parent.bottom : undefined - } - - Label { - id: titleLabel - fontSize: "x-large" - width: units.gu(25) - wrapMode: Text.WordWrap - } - - Label { - id: textLabel - width: units.gu(25) - wrapMode: Text.WordWrap - } - - Label { - id: skipLabel - objectName: "skipLabel" - text: i18n.tr("Skip intro") - color: UbuntuColors.orange - fontSize: "small" - - Icon { - anchors.left: parent.right - anchors.verticalCenter: parent.verticalCenter - height: units.dp(12) - width: units.dp(12) - name: "chevron" - color: UbuntuColors.orange - } - - MouseArea { - // Make clickable area bigger than just the link because - // otherwise, the edge demo will feel hard to dismiss. - anchors.fill: parent - anchors.margins: -units.gu(5) - onClicked: overlay.doSkip() - } - } - } - - LinearGradient { - id: edgeHint - property int size: 1 - cached: false - visible: overlay.edge != "none" - gradient: Gradient { - GradientStop { - position: 0.0 - color: Qt.hsla(16.0/360.0, 0.83, 0.47, 0.4) // UbuntuColors.orange, but transparent - } - GradientStop { - position: 1.0 - color: "transparent" - } - } - anchors.fill: parent - start: { - if (overlay.edge == "right") { - return Qt.point(width, 0); - } else if (overlay.edge == "left") { - return Qt.point(0, 0); - } else if (overlay.edge == "top") { - return Qt.point(0, 0); - } else { - return Qt.point(0, height); - } - } - end: { - if (overlay.edge == "right") { - return Qt.point(width - size, 0); - } else if (overlay.edge == "left") { - return Qt.point(size, 0); - } else if (overlay.edge == "top") { - return Qt.point(0, size); - } else { - return Qt.point(0, height - size); - } - } - } - } - - SequentialAnimation { - id: wholeAnimation - objectName: "wholeAnimation" - running: overlay.active - - ParallelAnimation { - id: fadeInAnimation - - StandardAnimation { - target: labelGroup - property: { - if (overlay.edge == "right" || overlay.edge == "left") { - return "anchors.leftMargin"; - } else if (overlay.edge == "bottom") { - return "anchors.bottomMargin"; - } else { - return "anchors.topMargin"; - } - } - from: { - if (overlay.edge == "right") { - return d.edgeMargin + units.gu(3) - } else { - return d.edgeMargin - units.gu(3) - } - } - to: d.edgeMargin - duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration - } - StandardAnimation { - target: overlay - property: "opacity" - from: 0.0 - to: 1.0 - duration: UbuntuAnimation.SleepyDuration - } - } - - SequentialAnimation { - id: hintAnimation - loops: Animation.Infinite - property string prop: (overlay.edge == "left" || overlay.edge == "right") ? "x" : "y" - property double endVal: units.dp(5) * ((overlay.edge == "left" || overlay.edge == "top") ? 1 : -1) - property double maxGlow: units.dp(20) - property int duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration - - ParallelAnimation { - StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: 0; to: hintAnimation.endVal; duration: hintAnimation.duration } - StandardAnimation { target: edgeHint; property: "size"; from: 1; to: hintAnimation.maxGlow; duration: hintAnimation.duration } - } - - // Undo the above - ParallelAnimation { - StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: hintAnimation.endVal; to: 0; duration: hintAnimation.duration } - StandardAnimation { target: edgeHint; property: "size"; from: hintAnimation.maxGlow; to: 1; duration: hintAnimation.duration } - } - } - } -} diff -Nru unity8-8.02+15.04.20150205/qml/Components/EdgeDemo.qml unity8-8.02+15.04.20150211/qml/Components/EdgeDemo.qml --- unity8-8.02+15.04.20150205/qml/Components/EdgeDemo.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Components/EdgeDemo.qml 1970-01-01 00:00:00.000000000 +0000 @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2013 Canonical, Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import AccountsService 0.1 -import LightDM 0.1 as LightDM -import QtQuick 2.0 -import Ubuntu.Components 1.1 - -Item { - id: demo - - property Item greeter - property Item launcher - property Item panel - property Item stages - - property bool launcherEnabled: true - property bool stagesEnabled: true - property bool panelEnabled: true - property bool panelContentEnabled: true - property bool running: !launcherEnabled || !stagesEnabled || !panelEnabled || !panelContentEnabled - - property bool paused: false - - onPausedChanged: { - if (d.rightEdgeDemo) d.rightEdgeDemo.paused = paused - if (d.topEdgeDemo) d.topEdgeDemo.paused = paused - if (d.bottomEdgeDemo) d.bottomEdgeDemo.paused = paused - if (d.leftEdgeDemo) d.leftEdgeDemo.paused = paused - if (d.finalEdgeDemo) d.finalEdgeDemo.paused = paused - } - - function hideEdgeDemoInShell() { - AccountsService.demoEdges = false; - stopDemo(); - } - - function hideEdgeDemoInGreeter() { - // TODO: AccountsService.demoEdges = false as lightdm user - } - - function hideEdgeDemos() { - hideEdgeDemoInGreeter(); - hideEdgeDemoInShell(); - } - - function stopDemo() { - launcherEnabled = true - stagesEnabled = true - panelEnabled = true - panelContentEnabled = true - - // Use a tiny delay for these destroy() calls because if a lot is - // happening at once (like creating and being destroyed in same event - // loop, as might happen when answering a call while demo is open), - // the destroy() call will be ignored. - if (d.rightEdgeDemo) d.rightEdgeDemo.destroy(1); - if (d.topEdgeDemo) d.topEdgeDemo.destroy(1); - if (d.bottomEdgeDemo) d.bottomEdgeDemo.destroy(1); - if (d.leftEdgeDemo) d.leftEdgeDemo.destroy(1); - if (d.finalEdgeDemo) d.finalEdgeDemo.destroy(1); - } - - function startDemo() { - if (!d.overlay) { - d.overlay = Qt.createComponent("EdgeDemoOverlay.qml") - } - - launcherEnabled = false; - stagesEnabled = false; - panelEnabled = false; - panelContentEnabled = false; - - // Begin with either greeter or top, depending on which is visible - if (greeter && greeter.shown) { - startRightEdgeDemo() - } else { - startTopEdgeDemo() - } - } - - QtObject { - id: d - property Component overlay - property QtObject rightEdgeDemo - property QtObject topEdgeDemo - property QtObject bottomEdgeDemo - property QtObject leftEdgeDemo - property QtObject finalEdgeDemo - property bool showEdgeDemo: AccountsService.demoEdges - property bool showEdgeDemoInGreeter: AccountsService.demoEdges // TODO: AccountsService.demoEdges as lightdm user - - function restartDemo() { - stopDemo() - if (d.showEdgeDemo) { - startDemo() - } - } - - onShowEdgeDemoChanged: restartDemo() - } - - Connections { - target: i18n - onLanguageChanged: d.restartDemo() - } - - function startRightEdgeDemo() { - if (demo.greeter) { - d.rightEdgeDemo = d.overlay.createObject(demo.greeter, { - "edge": "right", - "title": i18n.tr("Right edge"), - "text": i18n.tr("Try swiping from the right edge to unlock the phone"), - "anchors.fill": demo.greeter, - }); - } - if (d.rightEdgeDemo) { - d.rightEdgeDemo.onSkip.connect(demo.hideEdgeDemos) - } else { - stopDemo(); - } - } - - Connections { - target: demo.greeter - - function hide() { - if (d.rightEdgeDemo && d.rightEdgeDemo.available) { - d.rightEdgeDemo.hide() - hideEdgeDemoInGreeter() - startTopEdgeDemo() - } - } - - onUnlocked: hide() - onShownChanged: if (!greeter.shown) hide() - } - - function startTopEdgeDemo() { - demo.panelEnabled = true; - if (demo.stages) { - d.topEdgeDemo = d.overlay.createObject(demo.panel, { - "edge": "top", - "title": i18n.tr("Top edge"), - "text": i18n.tr("Try swiping from the top edge to access the indicators"), - "anchors.fill": demo.panel, - }); - } - if (d.topEdgeDemo) { - d.topEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell) - } else { - stopDemo(); - } - } - - Connections { - target: demo.panel.indicators - onFullyOpenedChanged: { - if (d.topEdgeDemo && d.topEdgeDemo.available && demo.panel.indicators.fullyOpened) { - d.topEdgeDemo.hideNow() - startBottomEdgeDemo() - } - } - } - - function startBottomEdgeDemo() { - if (demo.panel.indicators) { - d.bottomEdgeDemo = d.overlay.createObject(demo.panel.indicators, { - "edge": "bottom", - "title": i18n.tr("Close"), - "text": i18n.tr("Swipe up again to close the settings screen"), - "anchors.fill": demo.panel.indicators, - }); - } - if (d.bottomEdgeDemo) { - d.bottomEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell) - } else { - stopDemo(); - } - } - - Connections { - target: demo.panel.indicators - onPartiallyOpenedChanged: { - if (d.bottomEdgeDemo && - d.bottomEdgeDemo.available && - !demo.panel.indicators.partiallyOpened && - !demo.panel.indicators.fullyOpened) { - d.bottomEdgeDemo.hideNow() - startLeftEdgeDemo() - } - } - } - - function startLeftEdgeDemo() { - demo.panelEnabled = false; - demo.launcherEnabled = true; - if (demo.stages) { - d.leftEdgeDemo = d.overlay.createObject(demo.stages, { - "edge": "left", - "title": i18n.tr("Left edge"), - "text": i18n.tr("Swipe from the left to reveal the launcher for quick access to apps"), - "anchors.fill": demo.stages, - }); - } - if (d.leftEdgeDemo) { - d.leftEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell) - } else { - stopDemo(); - } - } - - Connections { - target: demo.launcher - onStateChanged: { - if (d.leftEdgeDemo && d.leftEdgeDemo.available && launcher.state == "visible") { - d.leftEdgeDemo.hide() - launcher.hide() - startFinalEdgeDemo() - } - } - } - - function startFinalEdgeDemo() { - demo.launcherEnabled = false; - if (demo.stages) { - d.finalEdgeDemo = d.overlay.createObject(demo.stages, { - "edge": "none", - "title": i18n.tr("Well done"), - "text": i18n.tr("You have now mastered the edge gestures and can start using the phone

Tap on the screen to start"), - "anchors.fill": demo.stages, - "showSkip": false, - }); - } - if (d.finalEdgeDemo) { - d.finalEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell) - } else { - stopDemo(); - } - } -} diff -Nru unity8-8.02+15.04.20150205/qml/Dash/GenericScopeView.qml unity8-8.02+15.04.20150211/qml/Dash/GenericScopeView.qml --- unity8-8.02+15.04.20150205/qml/Dash/GenericScopeView.qml 2015-02-05 10:29:11.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Dash/GenericScopeView.qml 2015-02-11 17:10:43.000000000 +0000 @@ -608,6 +608,7 @@ onBackClicked: scopeView.backClicked() onSettingsClicked: subPageLoader.openSubPage("settings") onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite + onSearchTextFieldFocused: scopeView.showHeader() } } } diff -Nru unity8-8.02+15.04.20150205/qml/Dash/PageHeader.qml unity8-8.02+15.04.20150211/qml/Dash/PageHeader.qml --- unity8-8.02+15.04.20150205/qml/Dash/PageHeader.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Dash/PageHeader.qml 2015-02-11 17:10:43.000000000 +0000 @@ -51,6 +51,7 @@ signal storeClicked() signal settingsClicked() signal favoriteClicked() + signal searchTextFieldFocused() onScopeStyleChanged: refreshLogo() onSearchQueryChanged: { @@ -228,6 +229,7 @@ onActiveFocusChanged: { if (activeFocus) { + root.searchTextFieldFocused(); root.openSearchHistory(); } } diff -Nru unity8-8.02+15.04.20150205/qml/Greeter/GreeterContent.qml unity8-8.02+15.04.20150211/qml/Greeter/GreeterContent.qml --- unity8-8.02+15.04.20150205/qml/Greeter/GreeterContent.qml 2015-02-05 10:29:02.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Greeter/GreeterContent.qml 2015-02-11 17:13:01.000000000 +0000 @@ -121,6 +121,7 @@ objectName: "infographics" height: narrowMode ? parent.height : 0.75 * parent.height model: greeterContentLoader.infographicModel + clip: true // clip large data bubbles property string selectedUser property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : "" diff -Nru unity8-8.02+15.04.20150205/qml/Launcher/LauncherPanel.qml unity8-8.02+15.04.20150211/qml/Launcher/LauncherPanel.qml --- unity8-8.02+15.04.20150205/qml/Launcher/LauncherPanel.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Launcher/LauncherPanel.qml 2015-02-11 17:11:11.000000000 +0000 @@ -149,14 +149,14 @@ id: snapToBottomAnimation target: launcherListView property: "contentY" - to: launcherListView.originY + to: launcherListView.originY + launcherListView.topMargin } UbuntuNumberAnimation { id: snapToTopAnimation target: launcherListView property: "contentY" - to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY + to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY - launcherListView.topMargin } displaced: Transition { @@ -521,12 +521,12 @@ left: parent.left leftMargin: (quickList.item.width - units.gu(1)) / 2 - width / 2 verticalCenter: parent.verticalCenter - verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1) + verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1) * (root.inverted ? 1 : -1) } height: units.gu(1) width: units.gu(2) source: "graphics/quicklist_tooltip.png" - rotation: quickList.offset > 0 ? 0 : 180 + rotation: (quickList.offset > 0 ? 0 : 180) + (root.inverted ? 0 : 180) } InverseMouseArea { @@ -543,7 +543,9 @@ id: quickList objectName: "quickList" color: "#f5f5f5" - width: units.gu(30) + // Because we're setting left/right anchors depending on orientation, it will break the + // width setting after rotating twice. This makes sure we also re-apply width on rotation + width: root.inverted ? units.gu(30) : units.gu(30) height: quickListColumn.height visible: quickListShape.visible anchors { diff -Nru unity8-8.02+15.04.20150205/qml/Launcher/Launcher.qml unity8-8.02+15.04.20150211/qml/Launcher/Launcher.qml --- unity8-8.02+15.04.20150205/qml/Launcher/Launcher.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Launcher/Launcher.qml 2015-02-11 17:11:40.000000000 +0000 @@ -26,6 +26,7 @@ property bool autohideEnabled: false property bool available: true // can be used to disable all interactions property alias inverted: panel.inverted + property bool shadeBackground: true // can be used to disable background shade when launcher is visible property int panelWidth: units.gu(8) property int dragAreaWidth: units.gu(1) @@ -33,6 +34,10 @@ property real progress: dragArea.dragging && dragArea.touchX > panelWidth ? (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0 + readonly property bool dragging: dragArea.dragging + readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0 + readonly property real visibleWidth: panel.width + panel.x + readonly property bool shown: panel.x > -panel.width // emitted when an application is selected @@ -154,7 +159,7 @@ MouseArea { id: launcherDragArea - enabled: root.state == "visible" + enabled: root.available && root.state == "visible" anchors.fill: panel anchors.rightMargin: -units.gu(2) drag { @@ -180,7 +185,7 @@ right: parent.right bottom: parent.bottom } - enabled: root.state == "visible" + enabled: root.shadeBackground && root.state == "visible" onPressed: { root.state = "" } @@ -190,7 +195,7 @@ id: backgroundShade anchors.fill: parent color: "black" - opacity: root.state == "visible" ? 0.6 : 0 + opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } } } @@ -198,14 +203,14 @@ LauncherPanel { id: panel objectName: "launcherPanel" - enabled: root.available + enabled: root.available && root.state == "visible" width: root.panelWidth anchors { top: parent.top bottom: parent.bottom } x: -width - visible: x > -width || dragArea.status === DirectionalDragArea.Undecided + visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided model: LauncherModel property bool animate: true @@ -247,6 +252,7 @@ direction: Direction.Rightwards enabled: root.available + x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing) width: root.dragAreaWidth height: root.height @@ -260,7 +266,7 @@ // would appear right next to the user's finger out of nowhere. // Instead, we make the panel go towards the user's finger in several // steps. ie., in an animated way. - var targetPanelX = Math.min(0, touchX - panel.width) + var targetPanelX = Math.min(0, touchX - panel.width) - root.x var delta = targetPanelX - panel.x // the trick is not to go all the way (1.0) as it would cause a sudden jump panel.x += 0.4 * delta @@ -292,7 +298,7 @@ name: "visible" PropertyChanges { target: panel - x: 0 + x: -root.x // so we never go past panelWidth, even when teased by tutorial } }, State { diff -Nru unity8-8.02+15.04.20150205/qml/Notifications/Notification.qml unity8-8.02+15.04.20150211/qml/Notifications/Notification.qml --- unity8-8.02+15.04.20150205/qml/Notifications/Notification.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Notifications/Notification.qml 2015-02-11 17:11:02.000000000 +0000 @@ -40,6 +40,7 @@ property bool fullscreen: false property int maxHeight property int margins + readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision readonly property color red: "#fc4949" readonly property color green: "#3fb24f" @@ -52,7 +53,7 @@ implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97) - opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really + opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually state: { var result = ""; @@ -80,18 +81,28 @@ id: sound objectName: "sound" audioRole: MediaPlayer.alert - source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : "" + source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : "" } // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really Component.onCompleted: { - if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") { + if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") { sound.play(); } } + Behavior on x { + id: normalXBehavior + + enabled: draggable + UbuntuNumberAnimation { + duration: UbuntuAnimation.FastDuration + easing.type: Easing.OutBounce + } + } + onHintsChanged: { - if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") { + if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") { sound.play(); } } @@ -146,6 +157,12 @@ opacity: parent.opacity } + onXChanged: { + if (draggable && notification.x > 0.75 * notification.width) { + notification.notification.close() + } + } + Item { id: contents anchors.fill: fullscreen ? nonShapedBack : shapedBack @@ -169,7 +186,7 @@ actions: paths.actions menuObjectPath: paths.menuObjectPath onNameOwnerChanged: { - if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) { + if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) { notification.notification.close() } lastNameOwner = nameOwner @@ -181,6 +198,12 @@ anchors.fill: parent objectName: "interactiveArea" + + drag.target: draggable ? notification : undefined + drag.axis: Drag.XAxis + drag.minimumX: 0 + drag.maximumX: notification.width + onClicked: { if (notification.type == Notification.Interactive) { notification.notification.invokeAction(actionRepeater.itemAt(0).actionId) @@ -188,6 +211,13 @@ notificationList.currentIndex = index; } } + onReleased: { + if (notification.x < notification.width / 2) { + notification.x = 0 + } else { + notification.x = notification.width + } + } } Column { @@ -447,7 +477,7 @@ right: parent.right margins: contentSpacing } - visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible + visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible spacing: units.gu(2) layoutDirection: Qt.RightToLeft diff -Nru unity8-8.02+15.04.20150205/qml/Notifications/Notifications.qml unity8-8.02+15.04.20150211/qml/Notifications/Notifications.qml --- unity8-8.02+15.04.20150205/qml/Notifications/Notifications.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Notifications/Notifications.qml 2015-02-11 17:11:02.000000000 +0000 @@ -41,15 +41,11 @@ property bool topmostIsFullscreen: false spacing: topmostIsFullscreen ? 0 : units.gu(.5) - // FIXME: This doesn't make any sense and results in a binding loop - currentIndex: (currentIndex < 1 && count > 1) ? 1 : -1 + currentIndex: count > 1 ? 1 : -1 delegate: Notification { objectName: "notification" + index - anchors { - left: parent.left - right: parent.right - } + width: parent.width type: model.type hints: model.hints iconSource: model.icon diff -Nru unity8-8.02+15.04.20150205/qml/Panel/Panel.qml unity8-8.02+15.04.20150211/qml/Panel/Panel.qml --- unity8-8.02+15.04.20150205/qml/Panel/Panel.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Panel/Panel.qml 2015-02-11 17:10:32.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 Canonical, Ltd. + * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -90,6 +90,9 @@ right: indicators.left } saturation: 1 - indicators.unitProgress + + // Don't let input event pass trough + MouseArea { anchors.fill: parent } } Image { @@ -110,8 +113,7 @@ right: indicators.left } height: indicators.minimizedPanelHeight - enabled: callHint.visible - onClicked: callHint.showLiveCall() + onClicked: { if (callHint.visible) { callHint.showLiveCall(); } } } IndicatorsMenu { diff -Nru unity8-8.02+15.04.20150205/qml/Shell.qml unity8-8.02+15.04.20150211/qml/Shell.qml --- unity8-8.02+15.04.20150205/qml/Shell.qml 2015-02-05 10:29:33.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Shell.qml 2015-02-11 17:12:49.000000000 +0000 @@ -36,6 +36,7 @@ import "Components" import "Notifications" import "Stages" +import "Tutorial" import "Wizard" import Unity.Notifications 1.0 as NotificationBackend import Unity.Session 0.1 @@ -62,7 +63,7 @@ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock readonly property alias hasLockedApp: greeter.hasLockedApp - readonly property bool forcedUnlock: edgeDemo.running + readonly property bool forcedUnlock: tutorial.running onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide() property bool sideStageEnabled: shell.width >= units.gu(100) @@ -161,7 +162,7 @@ ScreenGrabber { id: screenGrabber - z: edgeDemo.z + 10 + z: dialogs.z + 10 enabled: Powerd.status === Powerd.On } @@ -252,14 +253,16 @@ } onApplicationAdded: { - if (greeter.shown && appId != "unity8-dash") { - greeter.startUnlock() + if (appId != "unity8-dash") { + if (greeter.shown) { + greeter.startUnlock(); + } // If this happens on first boot, we may be in edge // tutorial or wizard while receiving a call. But a call // is more important than wizard so just bail out of those. - if (edgeDemo.running) { - edgeDemo.hideEdgeDemos(); + if (tutorial.running) { + tutorial.finish(); wizard.hide(); } } @@ -287,6 +290,15 @@ source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml" : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml" + property bool interactive: tutorial.spreadEnabled + && !greeter.shown + && !lockscreen.shown + && panel.indicators.fullyClosed + && launcher.progress == 0 + && !notifications.useModal + + onInteractiveChanged: { if (interactive) { focus = true; } } + Binding { target: applicationsDisplayLoader.item property: "objectName" @@ -306,12 +318,12 @@ Binding { target: applicationsDisplayLoader.item property: "interactive" - value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal + value: applicationsDisplayLoader.interactive } Binding { target: applicationsDisplayLoader.item property: "spreadEnabled" - value: edgeDemo.stagesEnabled && !greeter.hasLockedApp + value: tutorial.spreadEnabled && !greeter.hasLockedApp } Binding { target: applicationsDisplayLoader.item @@ -644,9 +656,13 @@ LauncherModel.setUser(user); } - onTapped: launcher.tease() + onTapped: { + if (!tutorial.running) { + launcher.tease(); + } + } onDraggingChanged: { - if (dragging) { + if (dragging && !tutorial.running) { launcher.tease(); } } @@ -684,7 +700,7 @@ onStatusChanged: { if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity && - !callManager.hasCalls && !edgeDemo.running) { + !callManager.hasCalls && !tutorial.running) { // We don't want to simply call greeter.showNow() here, because // that will take too long. Qt will delay button event // handling until the greeter is done loading and may think the @@ -700,7 +716,7 @@ } function showHome() { - if (edgeDemo.running) { + if (tutorial.running) { return } @@ -742,15 +758,17 @@ anchors.fill: parent //because this draws indicator menus indicators { hides: [launcher] - available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp - contentEnabled: edgeDemo.panelContentEnabled + available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp + contentEnabled: tutorial.panelContentEnabled width: parent.width > units.gu(60) ? units.gu(40) : parent.width minimizedPanelHeight: units.gu(3) expandedPanelHeight: units.gu(7) indicatorsModel: Indicators.IndicatorsModel { - Component.onCompleted: load(indicatorProfile); + // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...) + profile: indicatorProfile + Component.onCompleted: load() } } callHint { @@ -776,8 +794,9 @@ anchors.bottom: parent.bottom width: parent.width dragAreaWidth: shell.edgeSize - available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp + available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp inverted: usageModeSettings.usageMode === "Staged" + shadeBackground: !tutorial.running onShowDashHome: showHome() onDash: showDash() @@ -790,7 +809,7 @@ if (greeter.hasLockedApp) { greeter.startUnlock() } - if (!edgeDemo.running) + if (!tutorial.running) shell.activateApplication(appId) } onShownChanged: { @@ -863,15 +882,21 @@ } } - EdgeDemo { - id: edgeDemo - objectName: "edgeDemo" - z: dialogs.z + 10 - paused: Powerd.status === Powerd.Off || wizard.active // Saves power - greeter: greeter + Tutorial { + id: tutorial + objectName: "tutorial" + active: AccountsService.demoEdges + paused: LightDM.Greeter.active launcher: launcher panel: panel stages: stages + overlay: overlay + edgeSize: shell.edgeSize + + onFinished: { + AccountsService.demoEdges = false; + active = false; // for immediate response / if AS is having problems + } } Connections { @@ -881,7 +906,7 @@ Rectangle { id: shutdownFadeOutRectangle - z: edgeDemo.z + 10 + z: screenGrabber.z + 10 enabled: false visible: false color: "black" diff -Nru unity8-8.02+15.04.20150205/qml/Stages/ApplicationWindow.qml unity8-8.02+15.04.20150211/qml/Stages/ApplicationWindow.qml --- unity8-8.02+15.04.20150205/qml/Stages/ApplicationWindow.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/ApplicationWindow.qml 2015-02-11 17:12:05.000000000 +0000 @@ -18,7 +18,7 @@ import Ubuntu.Components 1.1 import Unity.Application 0.1 -Item { +FocusScope { id: root // to be read from outside @@ -43,6 +43,7 @@ readonly property color splashColor: root.application ? root.application.splashColor : "#00000000" readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000" readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000" + readonly property url defaultScreenshot: root.application ? root.application.defaultScreenshot : "" // Whether the Application had a surface before but lost it. property bool hadSurface: sessionContainer.surfaceContainer.hadSurface @@ -72,7 +73,7 @@ Image { id: screenshotImage objectName: "screenshotImage" - source: "" + source: d.defaultScreenshot anchors.fill: parent antialiasing: !root.interactive @@ -120,9 +121,12 @@ d.surfaceInitialized = false; } } + + focus: true } StateGroup { + id: stateGroup objectName: "applicationWindowStateGroup" states: [ State { @@ -216,6 +220,17 @@ } }, Transition { + from: "splashScreen"; to: "screenshot" + SequentialAnimation { + PropertyAction { target: screenshotImage + property: "visible"; value: true } + UbuntuNumberAnimation { target: screenshotImage; property: "opacity"; + from: 0.0; to: 1.0 + duration: UbuntuAnimation.BriskDuration } + PropertyAction { target: splashLoader; property: "active"; value: false } + } + }, + Transition { from: "surface"; to: "void" SequentialAnimation { PropertyAction { target: sessionContainer.surfaceContainer; property: "visible"; value: false } diff -Nru unity8-8.02+15.04.20150205/qml/Stages/DesktopStage.qml unity8-8.02+15.04.20150211/qml/Stages/DesktopStage.qml --- unity8-8.02+15.04.20150205/qml/Stages/DesktopStage.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/DesktopStage.qml 2015-02-11 17:12:21.000000000 +0000 @@ -38,8 +38,6 @@ Connections { target: ApplicationManager onApplicationAdded: { - // Initial placement to avoid having the window decoration behind the panel - appRepeater.itemAt(ApplicationManager.count-1).y = units.gu(3) ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId) } @@ -91,6 +89,7 @@ delegate: Item { id: appDelegate z: ApplicationManager.count - index + y: units.gu(3) width: units.gu(60) height: units.gu(50) @@ -121,6 +120,7 @@ minWidth: appDelegate.minWidth minHeight: appDelegate.minHeight resizeHandleWidth: units.gu(0.5) + windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing onPressed: ApplicationManager.requestFocusApplication(model.appId) } diff -Nru unity8-8.02+15.04.20150205/qml/Stages/PhoneStage.qml unity8-8.02+15.04.20150211/qml/Stages/PhoneStage.qml --- unity8-8.02+15.04.20150205/qml/Stages/PhoneStage.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/PhoneStage.qml 2015-02-11 17:12:05.000000000 +0000 @@ -32,6 +32,21 @@ property bool spreadEnabled: true // If false, animations and right edge will be disabled property real inverseProgress: 0 // This is the progress for left edge drags, in pixels. property int orientation: Qt.PortraitOrientation + property QtObject applicationManager: ApplicationManager + property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps + property bool altTabEnabled: true + property real startScale: 1.1 + property real endScale: 0.7 + + // How far left the stage has been dragged + readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0 + + readonly property alias dragging: spreadDragArea.dragging + + // Only used by the tutorial right now, when it is teasing the right edge + property real dragAreaOverlap + + signal opened() color: "#111111" @@ -52,30 +67,30 @@ if (inverseProgress == 0 && priv.oldInverseProgress > 0) { // left edge drag released. Minimum distance is given by design. if (priv.oldInverseProgress > units.gu(22)) { - ApplicationManager.requestFocusApplication("unity8-dash"); + applicationManager.requestFocusApplication("unity8-dash"); } } priv.oldInverseProgress = inverseProgress; } Connections { - target: ApplicationManager + target: applicationManager onFocusRequested: { if (spreadView.phase > 0) { spreadView.snapTo(priv.indexOf(appId)); } else { - ApplicationManager.focusApplication(appId); + applicationManager.focusApplication(appId); } } onApplicationAdded: { if (spreadView.phase == 2) { - spreadView.snapTo(ApplicationManager.count - 1); + spreadView.snapTo(applicationManager.count - 1); } else { spreadView.phase = 0; spreadView.contentX = -spreadView.shift; - ApplicationManager.focusApplication(appId); + applicationManager.focusApplication(appId); } } @@ -90,9 +105,9 @@ } function focusTopMostApp() { - if (ApplicationManager.count > 0) { - var topmostApp = ApplicationManager.get(0); - ApplicationManager.focusApplication(topmostApp.appId); + if (applicationManager.count > 0) { + var topmostApp = applicationManager.get(0); + applicationManager.focusApplication(topmostApp.appId); } } } @@ -100,18 +115,21 @@ QtObject { id: priv - property string focusedAppId: ApplicationManager.focusedApplicationId - property var focusedApplication: ApplicationManager.findApplication(focusedAppId) + readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0 + property string focusedAppId: applicationManager.focusedApplicationId + property var focusedApplication: applicationManager.findApplication(focusedAppId) property var focusedAppDelegate: null property real oldInverseProgress: 0 - property bool animateX: true + property bool animateX: false onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0); + onFocusedAppDelegateChanged: focusedAppDelegate.focus = true; + function indexOf(appId) { - for (var i = 0; i < ApplicationManager.count; i++) { - if (ApplicationManager.get(i).appId == appId) { + for (var i = 0; i < applicationManager.count; i++) { + if (applicationManager.get(i).appId == appId) { return i; } } @@ -131,7 +149,7 @@ // This indicates when the spreadView is active. That means, all the animations // are activated and tiles need to line up for the spread. - readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized + readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp // The flickable needs to fill the screen in order to get touch events all over. // However, we don't want to the user to be able to scroll back all the way. For @@ -197,10 +215,17 @@ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation. snapAnimation.targetContentX = width * positionMarker4 + 1 - shift; snapAnimation.start(); + root.opened(); } } function snapTo(index) { - if (ApplicationManager.count <= index) { + if (!root.altTabEnabled) { + // Reset to start instead + snapAnimation.targetContentX = -shift; + snapAnimation.start(); + return; + } + if (applicationManager.count <= index) { // In case we're trying to snap to some non existing app, lets snap back to the first one index = 0; } @@ -215,10 +240,12 @@ snapAnimation.start(); } - // In case the ApplicationManager already holds an app when starting up we're missing animations + // In case the applicationManager already holds an app when starting up we're missing animations // Make sure we end up in the same state Component.onCompleted: { spreadView.contentX = -spreadView.shift + priv.animateX = true; + snapAnimation.complete(); } SequentialAnimation { @@ -235,7 +262,7 @@ ScriptAction { script: { if (spreadView.selectedIndex >= 0) { - ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId); + applicationManager.focusApplication(applicationManager.get(spreadView.selectedIndex).appId); spreadView.selectedIndex = -1; spreadView.phase = 0; @@ -250,7 +277,7 @@ // This width controls how much the spread can be flicked left/right. It's composed of: // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit) // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen - width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5 + width: Math.max(3, applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5 height: parent.height Behavior on width { enabled: spreadView.closingIndex >= 0 @@ -265,20 +292,22 @@ x: spreadView.contentX onClicked: { - spreadView.snapTo(0); + if (root.altTabEnabled) { + spreadView.snapTo(0); + } } Repeater { id: spreadRepeater objectName: "spreadRepeater" - model: ApplicationManager + model: applicationManager delegate: TransformedSpreadDelegate { id: appDelegate objectName: "appDelegate" + index startAngle: 45 endAngle: 5 - startScale: 1.1 - endScale: 0.7 + startScale: root.startScale + endScale: root.endScale startDistance: spreadView.tileDistance endDistance: units.gu(.5) width: spreadView.width @@ -286,11 +315,12 @@ selected: spreadView.selectedIndex == index otherSelected: spreadView.selectedIndex >= 0 && !selected interactive: !spreadView.interactive && spreadView.phase === 0 - && spreadView.shiftedContentX === 0 && root.interactive && index === 0 - swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running + && spreadView.shiftedContentX === 0 && root.interactive && isFocused + swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running maximizedAppTopMargin: root.maximizedAppTopMargin dropShadow: spreadView.active || (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0) + focusFirstApp: root.focusFirstApp readonly property bool isDash: model.appId == "unity8-dash" @@ -298,7 +328,7 @@ x: { // focused app is always positioned at 0 except when following left edge drag - if (index == 0) { + if (isFocused) { if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) { return root.inverseProgress; } @@ -309,10 +339,10 @@ } // Otherwise line up for the spread - return spreadView.width + (index - 1) * spreadView.tileDistance; + return spreadView.width + spreadIndex * spreadView.tileDistance; } - application: ApplicationManager.get(index) + application: applicationManager.get(index) closeable: !isDash property real behavioredIndex: index @@ -350,7 +380,7 @@ progress: { var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width; // Tile 1 needs to move directly from the beginning... - if (behavioredIndex == 1 && spreadView.phase < 2) { + if (root.focusFirstApp && behavioredIndex == 1 && spreadView.phase < 2) { tileProgress += spreadView.tileDistance / spreadView.width; } // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not @@ -365,7 +395,7 @@ // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1 animatedProgress: { - if (spreadView.phase == 0 && index < 2) { + if (spreadView.phase == 0 && index <= priv.firstSpreadIndex) { if (progress < spreadView.positionMarker1) { return progress; } else if (progress < spreadView.positionMarker1 + 0.05){ @@ -390,11 +420,11 @@ } onClicked: { - if (spreadView.phase == 2) { - if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) { + if (root.altTabEnabled && spreadView.phase == 2) { + if (applicationManager.focusedApplicationId == applicationManager.get(index).appId) { spreadView.snapTo(index); } else { - ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId); + applicationManager.requestFocusApplication(applicationManager.get(index).appId); } } } @@ -409,7 +439,7 @@ onClosed: { spreadView.closingIndex = index; - ApplicationManager.stopApplication(ApplicationManager.get(index).appId); + applicationManager.stopApplication(applicationManager.get(index).appId); } } } @@ -422,7 +452,7 @@ direction: Direction.Leftwards enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging - anchors { top: parent.top; right: parent.right; bottom: parent.bottom } + anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap } width: root.dragAreaWidth property var gesturePoints: new Array() diff -Nru unity8-8.02+15.04.20150205/qml/Stages/SessionContainer.qml unity8-8.02+15.04.20150211/qml/Stages/SessionContainer.qml --- unity8-8.02+15.04.20150205/qml/Stages/SessionContainer.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/SessionContainer.qml 2015-02-11 17:11:30.000000000 +0000 @@ -17,7 +17,7 @@ import QtQuick 2.0 import "Animations" -Item { +FocusScope { id: root objectName: "sessionContainer" property QtObject session @@ -34,8 +34,8 @@ orientation: root.orientation } - Repeater { + id: childSessionsRepeater model: root.childSessions delegate: Loader { @@ -45,9 +45,17 @@ // Only way to do recursive qml items. source: Qt.resolvedUrl("SessionContainer.qml") + z: index + + // Since a Loader is a FocusScope, propagate its focus to the loaded Item + Binding { + target: item; when: item + property: "focus"; value: focus + } + Binding { target: item; when: item - property: "interactive"; value: root.interactive + property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive } Binding { @@ -69,10 +77,6 @@ target: item; when: item property: "orientation"; value: root.orientation } - - Component.onDestruction: { - root.session.surface.forceActiveFocus(); - } } } @@ -138,5 +142,16 @@ QtObject { id: d property var animations: [] + + property var focusedChild: { + if (childSessionsRepeater.count == 0) { + return _surfaceContainer; + } else { + return childSessionsRepeater.itemAt(childSessionsRepeater.count - 1); + } + } + onFocusedChildChanged: { + focusedChild.focus = true; + } } } diff -Nru unity8-8.02+15.04.20150205/qml/Stages/SpreadDelegate.qml unity8-8.02+15.04.20150211/qml/Stages/SpreadDelegate.qml --- unity8-8.02+15.04.20150205/qml/Stages/SpreadDelegate.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/SpreadDelegate.qml 2015-02-11 17:11:30.000000000 +0000 @@ -21,7 +21,7 @@ import Ubuntu.Components 1.1 import "../Components" -Item { +FocusScope { id: root // to be read from outside @@ -65,6 +65,7 @@ ApplicationWindow { id: appWindow objectName: application ? "appWindow_" + application.appId : "appWindow_null" + focus: true anchors { fill: parent topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin diff -Nru unity8-8.02+15.04.20150205/qml/Stages/SurfaceContainer.qml unity8-8.02+15.04.20150211/qml/Stages/SurfaceContainer.qml --- unity8-8.02+15.04.20150205/qml/Stages/SurfaceContainer.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/SurfaceContainer.qml 2015-02-11 17:11:30.000000000 +0000 @@ -18,7 +18,7 @@ import Ubuntu.Components 1.1 import Ubuntu.Gestures 0.1 // For TouchGate -Item { +FocusScope { id: root objectName: "surfaceContainer" property Item surface: null @@ -26,10 +26,15 @@ property int orientation property bool interactive + signal surfacePressed() + onSurfaceChanged: { if (surface) { + // Set the surface focus *after* it is added to the scene to + // ensure an update to the scene's active focus. + surface.focus = false; surface.parent = root; - d.forceSurfaceActiveFocusIfReady(); + surface.focus = true; } else { hadSurface = true; } @@ -38,7 +43,6 @@ Binding { target: surface; property: "orientation"; value: root.orientation } Binding { target: surface; property: "z"; value: 1 } Binding { target: surface; property: "enabled"; value: root.interactive; when: surface } - Binding { target: surface; property: "focus"; value: root.interactive; when: surface } Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface } TouchGate { @@ -46,28 +50,10 @@ anchors.fill: root enabled: root.surface ? root.surface.enabled : false z: 2 - } - - Connections { - target: root.surface - // FIXME: I would rather not need to do this, but currently it doesn't get - // active focus without it and I don't know why. - // Possibly because if an item get focus=true before it has a parent, once - // it gets a parent QQuickWindow won't check its focus and update its activeFocus - // accordingly. Unlike when you focus=true after the item already has a parent. - onFocusChanged: d.forceSurfaceActiveFocusIfReady(); - onParentChanged: d.forceSurfaceActiveFocusIfReady(); - onEnabledChanged: d.forceSurfaceActiveFocusIfReady(); - } - - QtObject { - id: d - function forceSurfaceActiveFocusIfReady() { - if (root.surface !== null && - root.surface.focus && - root.surface.parent === root && - root.surface.enabled) { - root.surface.forceActiveFocus(); + onPressed: { + root.focus = true; + if (root.interactive) { + root.forceActiveFocus(); } } } diff -Nru unity8-8.02+15.04.20150205/qml/Stages/TabletStage.qml unity8-8.02+15.04.20150211/qml/Stages/TabletStage.qml --- unity8-8.02+15.04.20150205/qml/Stages/TabletStage.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/TabletStage.qml 2015-02-11 17:11:30.000000000 +0000 @@ -76,6 +76,20 @@ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : ""; appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : ""; + + // Update the QML focus accordingly + updateSpreadDelegateFocus(); + } + + function updateSpreadDelegateFocus() { + if (priv.focusedAppId) { + var focusedAppIndex = priv.indexOf(priv.focusedAppId); + if (focusedAppIndex !== -1) { + spreadRepeater.itemAt(focusedAppIndex).focus = true; + } else { + console.warn("TabletStage: Failed to find the SpreadDelegate for appID=" + priv.focusedAppId); + } + } } function indexOf(appId) { diff -Nru unity8-8.02+15.04.20150205/qml/Stages/TransformedSpreadDelegate.qml unity8-8.02+15.04.20150211/qml/Stages/TransformedSpreadDelegate.qml --- unity8-8.02+15.04.20150205/qml/Stages/TransformedSpreadDelegate.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/TransformedSpreadDelegate.qml 2015-02-11 17:12:05.000000000 +0000 @@ -46,6 +46,18 @@ property real startDistance: units.gu(5) property real endDistance: units.gu(.5) + // Whether first app is displayed entirely vs just on the spread edge + property bool focusFirstApp: true + + // Whether this delegate is displayed to the user outside of the spread + readonly property bool isFocused: focusFirstApp && index === 0 + + // Adjusted index for any spread animation calculations + readonly property int spreadIndex: index - (focusFirstApp ? 1 : 0) + + // How far left this delegate has been translated + readonly property alias xTranslate: priv.xTranslate + onSelectedChanged: { if (selected) { priv.snapshot(); @@ -63,8 +75,8 @@ Connections { target: spreadView onPhaseChanged: { - if (spreadView.phase == 1) { - if (index == 0) { + if (root.focusFirstApp && spreadView.phase == 1) { + if (index === 0) { priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width; priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4); priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4); @@ -134,7 +146,7 @@ } if (otherSelected) { - if (spreadView.phase < 2 && index == 0) { + if (spreadView.phase < 2 && root.isFocused) { return linearAnimation(selectedProgress, 0, selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress); } @@ -142,8 +154,7 @@ return selectedXTranslate; } - switch (index) { - case 0: + if (root.focusFirstApp && index === 0) { if (spreadView.phase == 0) { return Math.min(0, linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.width * .25, root.animatedProgress)); @@ -153,11 +164,10 @@ } else if (!priv.isSelected){ // phase 2 // Apply the same animation as with the rest but add spreadView.width to align it with the others. return -easingCurve.value * spreadView.width + spreadView.width; - } else if (priv.isSelected) { + } else { return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress); } - - case 1: + } else if (root.focusFirstApp && index === 1) { if (spreadView.phase == 0 && !priv.isSelected) { return linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress); @@ -170,13 +180,13 @@ if (priv.isSelected) { // Distance to left edge - var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance); + var targetTranslate = -spreadView.width - (root.spreadIndex * root.startDistance); return linearAnimation(selectedProgress, 0, selectedXTranslate, targetTranslate, root.progress); } // Fix it at the right edge... - var rightEdgeOffset = -((index - 1) * root.startDistance); + var rightEdgeOffset = -(root.spreadIndex * root.startDistance); // ...and use our easing to move them to the left. Stop a bit earlier for each tile var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress); return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset; @@ -194,8 +204,7 @@ if (priv.isSelected) { return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress); } - switch (index) { - case 0: + if (root.focusFirstApp && index === 0) { if (spreadView.phase == 0) { return Math.max(0, linearAnimation(0, spreadView.positionMarker2, 0, root.tile0SnapAngle, root.animatedProgress)); @@ -203,7 +212,7 @@ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.tile0SnapAngle, phase2startAngle, root.progress); } - case 1: + } else if (root.focusFirstApp && index === 1) { if (spreadView.phase == 0) { return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress); @@ -227,15 +236,14 @@ return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress); } - switch (index) { - case 0: + if (root.focusFirstApp && index === 0) { if (spreadView.phase == 0) { return 1; } else if (spreadView.phase == 1) { return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 1, phase2startScale, root.progress); } - case 1: + } else if (root.focusFirstApp && index === 1) { if (spreadView.phase == 0) { var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition); return linearAnimation(0, spreadView.positionMarker2, @@ -254,7 +262,7 @@ return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5), selectedOpacity, 0, root.progress); } - if (index == 0) { + if (root.isFocused) { switch (spreadView.phase) { case 0: return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress); @@ -272,16 +280,14 @@ return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress); } - switch (index) { - case 0: + if (root.focusFirstApp && index === 0) { if (spreadView.phase == 0) { return 0; } else if (spreadView.phase == 1) { return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2startTopMarginProgress, root.progress); } - break; - case 1: + } else if (root.focusFirstApp && index === 1) { if (spreadView.phase == 0) { return 0; } else if (spreadView.phase == 1) { diff -Nru unity8-8.02+15.04.20150205/qml/Stages/TransformedTabletSpreadDelegate.qml unity8-8.02+15.04.20150211/qml/Stages/TransformedTabletSpreadDelegate.qml --- unity8-8.02+15.04.20150205/qml/Stages/TransformedTabletSpreadDelegate.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/TransformedTabletSpreadDelegate.qml 2015-02-11 17:11:30.000000000 +0000 @@ -366,7 +366,7 @@ Scale { origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 } xScale: 1 - yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1 + yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1 }, Translate { x: priv.xTranslate diff -Nru unity8-8.02+15.04.20150205/qml/Stages/WindowMoveResizeArea.qml unity8-8.02+15.04.20150211/qml/Stages/WindowMoveResizeArea.qml --- unity8-8.02+15.04.20150205/qml/Stages/WindowMoveResizeArea.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Stages/WindowMoveResizeArea.qml 2015-02-11 17:12:32.000000000 +0000 @@ -18,6 +18,7 @@ import QtQuick 2.3 import Ubuntu.Components 1.1 +import Utils 0.1 MouseArea { id: root @@ -26,7 +27,8 @@ // The target item managed by this. Must be a parent or a sibling // The area will anchor to it and manage move and resize events - property var target: null + property Item target: null + property string windowId: "" property int resizeHandleWidth: 0 property int minWidth: 0 property int minHeight: 0 @@ -37,8 +39,6 @@ readonly property int windowHeight: root.height - resizeHandleWidth * 2 property var startPoint - property int startWidth - property int startHeight property bool resizeTop: false property bool resizeBottom: false @@ -47,10 +47,18 @@ } + Component.onCompleted: { + var windowState = WindowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height)) + if (windowState !== undefined) { + target.x = windowState.x + target.y = windowState.y + target.width = windowState.width + target.height = windowState.height + } + } + onPressed: { - priv.startPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x; - priv.startWidth = root.width; - priv.startHeight = root.height; + priv.startPoint = Qt.point(mouse.x, mouse.y); priv.resizeTop = mouseY < root.resizeHandleWidth; priv.resizeBottom = mouseY > (root.height - root.resizeHandleWidth); priv.resizeLeft = mouseX < root.resizeHandleWidth; @@ -58,32 +66,28 @@ } onPositionChanged: { - var currentPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x; + var currentPoint = Qt.point(mouse.x, mouse.y); var mouseDiff = Qt.point(currentPoint.x - priv.startPoint.x, currentPoint.y - priv.startPoint.y); var moveDiff = Qt.point(0, 0); var sizeDiff = Qt.point(0, 0); + var maxSizeDiff = Qt.point(root.minWidth - root.target.width, root.minHeight - root.target.height) + if (priv.resizeTop || priv.resizeBottom || priv.resizeLeft || priv.resizeRight) { if (priv.resizeTop) { - moveDiff.y += mouseDiff.y; - sizeDiff.y += -mouseDiff.y; - } else if (priv.resizeBottom) { - sizeDiff.y += mouse.y - priv.startPoint.y; - priv.startPoint.y = mouse.y + sizeDiff.y = Math.max(maxSizeDiff.y, -currentPoint.y + priv.startPoint.y) + moveDiff.y = -sizeDiff.y } - if (priv.resizeLeft) { - moveDiff.x += mouseDiff.x; - sizeDiff.x += -mouseDiff.x; - } else if (priv.resizeRight) { - sizeDiff.x += mouse.x - priv.startPoint.x; - priv.startPoint.x = mouse.x + if (priv.resizeBottom) { + sizeDiff.y = Math.max(maxSizeDiff.y, currentPoint.y - priv.startPoint.y) + priv.startPoint.y += sizeDiff.y } - if (priv.windowWidth + sizeDiff.x < root.minWidth) { - sizeDiff.x = root.minWidth - priv.windowWidth; - moveDiff.x = moveDiff.x == 0 ? 0 : -sizeDiff.x; + if (priv.resizeLeft) { + sizeDiff.x = Math.max(maxSizeDiff.x, -currentPoint.x + priv.startPoint.x) + moveDiff.x = -sizeDiff.x } - if (priv.windowHeight + sizeDiff.y < root.minHeight) { - sizeDiff.y = root.minHeight - priv.windowHeight; - moveDiff.y = moveDiff.y == 0 ? 0 : -sizeDiff.y; + if (priv.resizeRight) { + sizeDiff.x = Math.max(maxSizeDiff.x, currentPoint.x - priv.startPoint.x) + priv.startPoint.x += sizeDiff.x } target.x += moveDiff.x; @@ -94,5 +98,10 @@ target.x += mouseDiff.x; target.y += mouseDiff.y; } + + } + + Component.onDestruction: { + WindowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height)) } } diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/Arrow.qml unity8-8.02+15.04.20150211/qml/Tutorial/Arrow.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/Arrow.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/Arrow.qml 2015-02-11 17:11:40.000000000 +0000 @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 + +Item { + id: root + + property alias color: circle.color + + // Will make whole arrow darker + property real darkenBy: 0 + + property alias chevronOpacity: chevron.opacity + + //// + + Rectangle { + id: circle + anchors.fill: parent + radius: width / 2 + } + + Image { + id: chevron + anchors.centerIn: parent + source: Qt.resolvedUrl("graphics/chevron.png") + fillMode: Image.PreserveAspectFit + sourceSize.width: 152 + sourceSize.height: 152 + width: parent.width / 2 + height: parent.height / 2 + } + + Rectangle { + id: darkCircle + anchors.fill: parent + radius: width / 2 + color: "black" + opacity: root.darkenBy + } +} Binary files /tmp/H5pTQ9HpMa/unity8-8.02+15.04.20150205/qml/Tutorial/graphics/camera.png and /tmp/3Yxh5KD9LM/unity8-8.02+15.04.20150211/qml/Tutorial/graphics/camera.png differ Binary files /tmp/H5pTQ9HpMa/unity8-8.02+15.04.20150205/qml/Tutorial/graphics/chevron.png and /tmp/3Yxh5KD9LM/unity8-8.02+15.04.20150211/qml/Tutorial/graphics/chevron.png differ Binary files /tmp/H5pTQ9HpMa/unity8-8.02+15.04.20150205/qml/Tutorial/graphics/dialer.png and /tmp/3Yxh5KD9LM/unity8-8.02+15.04.20150211/qml/Tutorial/graphics/dialer.png differ Binary files /tmp/H5pTQ9HpMa/unity8-8.02+15.04.20150205/qml/Tutorial/graphics/facebook.png and /tmp/3Yxh5KD9LM/unity8-8.02+15.04.20150211/qml/Tutorial/graphics/facebook.png differ Binary files /tmp/H5pTQ9HpMa/unity8-8.02+15.04.20150205/qml/Tutorial/graphics/gallery.png and /tmp/3Yxh5KD9LM/unity8-8.02+15.04.20150211/qml/Tutorial/graphics/gallery.png differ Binary files /tmp/H5pTQ9HpMa/unity8-8.02+15.04.20150205/qml/Tutorial/graphics/tick.png and /tmp/3Yxh5KD9LM/unity8-8.02+15.04.20150211/qml/Tutorial/graphics/tick.png differ diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/Slider.qml unity8-8.02+15.04.20150211/qml/Tutorial/Slider.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/Slider.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/Slider.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 + +Item { + id: root + + // Whether this slider is short or long + property bool shortSwipe + + // How far the user has slid + property real offset + + // Set to true when slider is being used + property bool active + + // How far in percentage terms + readonly property real percent: d.slideOffset / target.x + + QtObject { + id: d + readonly property color trayColor: "#424141" + readonly property real margin: units.gu(0.5) + readonly property real arrowSize: root.height - margin * 2 + readonly property real dotSize: units.dp(1) + readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x) + readonly property real offscreenOffset: units.gu(2) + } + + implicitWidth: shortSwipe ? units.gu(15) : units.gu(27.5) + implicitHeight: units.gu(6.5) + + Rectangle { + color: d.trayColor + anchors.fill: parent + anchors.rightMargin: clipBox.width - 1 + } + + // We want to have a circular border around the target. But we can't just + // do a radius on two of a rectangle's corners. So we clip a full circle. + Item { + id: clipBox + + clip: true + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: parent.height / 2 + + Rectangle { + color: d.trayColor + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: parent.width * 2 + radius: parent.width + } + } + + Arrow { + id: target + width: d.arrowSize + height: d.arrowSize + color: "#73000000" + chevronOpacity: 0.52 + anchors.right: parent.right + anchors.rightMargin: d.margin + anchors.verticalCenter: parent.verticalCenter + } + + Row { + anchors.left: handle.horizontalCenter + anchors.right: target.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + layoutDirection: Qt.RightToLeft + spacing: d.dotSize * 2 + + Repeater { + model: parent.width / (parent.spacing + d.dotSize) + Rectangle { + anchors.verticalCenter: parent ? parent.verticalCenter : undefined + height: d.dotSize + width: height + radius: width + color: "white" + opacity: 0.2 + } + } + } + + Arrow { + id: handle + width: d.arrowSize + height: d.arrowSize + color: UbuntuColors.orange + darkenBy: root.active ? 0.5 : 0 + anchors.left: parent.left + // We use a Translate transform rather than anchors.leftMargin because + // the latter has weird performance problems on the TutorialRight page. + transform: [ + Translate { + x: d.slideOffset + } + ] + anchors.verticalCenter: parent.verticalCenter + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/Tick.qml unity8-8.02+15.04.20150211/qml/Tutorial/Tick.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/Tick.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/Tick.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 + +MouseArea { + implicitHeight: tick.height + implicitWidth: tick.width + Image { + id: tick + source: Qt.resolvedUrl("graphics/tick.png") + height: units.gu(6.5) + width: units.gu(6.5) + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialBottomFinish.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialBottomFinish.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialBottomFinish.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialBottomFinish.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 +import "." as LocalComponents + +TutorialPage { + id: root + + title: i18n.tr("This action does different things for different apps") + text: i18n.tr("Tap here to finish.") + fullTextWidth: true + + foreground { + children: [ + LocalComponents.Tick { + objectName: "tick" + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: root.textBottom + units.gu(3) + } + onClicked: root.hide() + } + ] + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialBottom.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialBottom.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialBottom.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialBottom.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 +import Ubuntu.Gestures 0.1 +import "../Components" +import "." as LocalComponents + +TutorialPage { + id: root + + property alias edgeSize: dragArea.height + + title: i18n.tr("Open special menus") + text: i18n.tr("Swipe up from the bottom edge.") + fullTextWidth: true + + SequentialAnimation { + id: teaseAnimation + paused: running && root.paused + running: !dragArea.useTouchY && slider.dragOffset === 0 + loops: Animation.Infinite + + UbuntuNumberAnimation { + target: slider + property: "teaseOffset" + to: units.gu(1) + duration: UbuntuAnimation.SleepyDuration + } + UbuntuNumberAnimation { + target: slider + property: "teaseOffset" + to: 0 + duration: UbuntuAnimation.SleepyDuration + } + } + + foreground { + children: [ + LocalComponents.Slider { + id: slider + anchors { + bottom: parent.bottom + bottomMargin: width / 2 - height / 2 + horizontalCenter: parent.horizontalCenter + } + rotation: -90 + offset: teaseOffset + dragOffset + active: dragArea.dragging + + property real teaseOffset + property real dragOffset: dragArea.useTouchY ? -dragArea.touchY : 0 + + Behavior on dragOffset { + id: offsetAnimation + UbuntuNumberAnimation {} + } + } + ] + } + + EdgeDragArea { + id: dragArea + direction: Direction.Upwards + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + + property bool useTouchY + + onDraggingChanged: { + if (!dragging) { + if (slider.percent >= 0.85) { + root.hide(); + } else if (slider.percent >= 0.15) { + root.showError(); + } + } + + // We use a separate vars here rather than just directly looking at + // 'dragging' because we want to preserve our 'slider.offset' + // value during the above percent check. Now that we made it, + // we can have 'slider.offset' go back to zero. + offsetAnimation.enabled = !dragging; + useTouchY = dragging; + } + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialContent.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialContent.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialContent.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialContent.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013,2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 + +Item { + id: root + + property Item launcher + property Item panel + property Item stages + property Item overlay + + readonly property bool launcherEnabled: !running || + (!paused && tutorialLeft.shown) + readonly property bool spreadEnabled: !running + readonly property bool panelEnabled: !running + readonly property bool panelContentEnabled: !running + readonly property alias running: d.running + + property bool paused: false + property real edgeSize + + signal finished() + + function finish() { + d.stop(); + finished(); + } + + //// + + Component.onCompleted: { + d.start(); + } + + QtObject { + id: d + + property bool running + + function stop() { + running = false; + } + + function start() { + running = true; + tutorialLeft.show(); + } + } + + TutorialLeft { + id: tutorialLeft + objectName: "tutorialLeft" + parent: root.stages + anchors.fill: parent + launcher: root.launcher + paused: !shown || root.paused + + onFinished: tutorialLeftFinish.show() + } + + TutorialLeftFinish { + id: tutorialLeftFinish + objectName: "tutorialLeftFinish" + parent: root.stages + anchors.fill: parent + textXOffset: root.launcher.panelWidth + paused: !shown || root.paused + + onFinished: { + root.launcher.hide(); + tutorialRight.show(); + } + } + + TutorialRight { + id: tutorialRight + objectName: "tutorialRight" + parent: root.stages + anchors.fill: parent + edgeSize: root.edgeSize + panel: root.panel + paused: !shown || root.paused + + onFinished: tutorialBottom.show() + } + + TutorialBottom { + id: tutorialBottom + objectName: "tutorialBottom" + parent: root.stages + anchors.fill: parent + edgeSize: root.edgeSize + paused: !shown || root.paused + + onFinished: tutorialBottomFinish.show() + } + + TutorialBottomFinish { + id: tutorialBottomFinish + objectName: "tutorialBottomFinish" + parent: root.stages + anchors.fill: parent + backgroundFadesOut: true + paused: !shown || root.paused + + onFinished: root.finish() + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialLeftFinish.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialLeftFinish.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialLeftFinish.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialLeftFinish.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 +import "." as LocalComponents + +TutorialPage { + id: root + + title: i18n.tr("These are the shortcuts to favorite apps") + text: i18n.tr("Tap here to continue.") + fullTextWidth: true + + foreground { + children: [ + LocalComponents.Tick { + objectName: "tick" + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: root.textBottom + units.gu(3) + } + onClicked: root.hide() + } + ] + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialLeft.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialLeft.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialLeft.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialLeft.qml 2015-02-11 17:11:40.000000000 +0000 @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 +import "." as LocalComponents + +TutorialPage { + id: root + + property var launcher + + title: i18n.tr("Open the launcher") + text: i18n.tr("Short swipe from the left edge.") + + textXOffset: root.launcher.x + root.launcher.visibleWidth + + Connections { + target: root.launcher + + onStateChanged: { + if (root.launcher.state === "visible") { + finishTimer.start(); + } + } + + onDash: { + finishTimer.stop(); + root.showError(); + root.launcher.hide(); + } + } + + SequentialAnimation { + id: teaseAnimation + paused: running && root.paused + running: !slider.active && root.launcher.visibleWidth === 0 && root.shown + loops: Animation.Infinite + + UbuntuNumberAnimation { + target: root.launcher + property: "x" + to: units.gu(2) + duration: UbuntuAnimation.SleepyDuration + } + UbuntuNumberAnimation { + target: root.launcher + property: "x" + to: 0 + duration: UbuntuAnimation.SleepyDuration + } + } + + Timer { + id: finishTimer + interval: 1 + onTriggered: { + root.hide(); + root.launcher.x = 0; // make sure to reset launcher before we go + } + } + + foreground { + children: [ + LocalComponents.Slider { + id: slider + anchors { + left: parent.left + top: parent.top + topMargin: root.textBottom + units.gu(3) + } + offset: root.launcher.x + root.launcher.visibleWidth + root.launcher.progress + active: root.launcher.dragging + shortSwipe: true + } + ] + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialPage.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialPage.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialPage.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialPage.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2013,2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 +import "../Components" + +Showable { + id: root + + // This is the header displayed, like "Right edge" + property alias title: titleLabel.text + + // This is the block of text displayed below the header + property alias text: textLabel.text + + // Whether animations are paused + property bool paused + + // Whether to give the text the full width that the title has + property bool fullTextWidth + + // Whether whole page (background + foreground) or just the foreground fades in + property bool backgroundFadesIn: false + + // Whether whole page (background + foreground) or just the foreground fades out + property bool backgroundFadesOut: false + + // The foreground Item, add children to it that you want to fade in + property alias foreground: foregroundExtra + + // The text label bottom, so you can position elements relative to it + readonly property real textBottom: Math.max(textLabel.y + textLabel.height, errorTextLabel.y + errorTextLabel.height) + + // The MouseArea that eats events (so you can adjust size as you will) + property alias mouseArea: mouseArea + + // X/Y offsets for text + property real textXOffset: 0 + property real textYOffset: 0 + + // Foreground text opacity + property real textOpacity: 1 + + signal finished() + + function showError() { + errorTimer.start(); + } + + //// + + visible: false + shown: false + + property real _foregroundHideOpacity + + showAnimation: StandardAnimation { + property: root.backgroundFadesIn ? "opacity" : "_foregroundHideOpacity" + from: 0 + to: 1 + duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration + onStarted: root.visible = true + } + + hideAnimation: StandardAnimation { + property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity" + to: 0 + duration: UbuntuAnimation.BriskDuration + onStopped: { + root.visible = false; + root.finished(); + } + } + + QtObject { + id: d + + readonly property real sideMargin: units.gu(5.5) + readonly property real verticalOffset: -units.gu(9) + readonly property real textXOffset: Math.max(0, root.textXOffset - sideMargin + units.gu(2)) + + property real fadeInOffset: { + if (showAnimation.running) { + var opacity = root[root.showAnimation.property] + return (1 - opacity) * units.gu(3); + } else { + return 0; + } + } + } + + Timer { + id: errorTimer + interval: 3500 + } + + MouseArea { // eat any errant presses + id: mouseArea + anchors.fill: parent + } + + Rectangle { + anchors.fill: parent + color: "black" + opacity: 0.82 + } + + Item { + id: foreground + anchors.fill: parent + opacity: root._foregroundHideOpacity + + Item { + anchors.fill: parent + opacity: root.textOpacity + + Label { + id: titleLabel + anchors { + top: parent.verticalCenter + topMargin: d.verticalOffset + root.textYOffset + left: parent.left + leftMargin: d.sideMargin + d.textXOffset + } + width: parent.width - d.sideMargin * 2 + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + font.weight: Font.Light + font.pixelSize: units.gu(3.5) + } + + Label { + id: textLabel + anchors { + top: titleLabel.bottom + topMargin: units.gu(2) + left: parent.left + leftMargin: d.sideMargin + d.textXOffset + } + width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66) + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + font.weight: Font.Light + font.pixelSize: units.gu(2.5) + } + + // We use two separate labels like this rather than just changing + // the text of the above labels because we want to know where to place + // sliders (via root.textBottom) without having that place change + // as the text changes length. + Label { + id: errorTitleLabel + objectName: "errorTitleLabel" + anchors { + top: titleLabel.top + left: titleLabel.left + } + width: titleLabel.width + horizontalAlignment: titleLabel.horizontalAlignment + wrapMode: titleLabel.wrapMode + font.weight: titleLabel.font.weight + font.pixelSize: titleLabel.font.pixelSize + opacity: 0 + text: i18n.tr("You almost got it!") + } + + Label { + id: errorTextLabel + objectName: "errorTextLabel" + anchors { + top: errorTitleLabel.bottom + topMargin: textLabel.anchors.topMargin + left: textLabel.left + } + width: textLabel.width + horizontalAlignment: textLabel.horizontalAlignment + wrapMode: textLabel.wrapMode + font.weight: textLabel.font.weight + font.pixelSize: textLabel.font.pixelSize + opacity: 0 + text: i18n.tr("Try again.") + } + } + + // A place for subclasses to add extra widgets + Item { + id: foregroundExtra + anchors.fill: parent + } + } + + states: State { + name: "errorState" + when: errorTimer.running + PropertyChanges { target: titleLabel; opacity: 0 } + PropertyChanges { target: textLabel; opacity: 0 } + PropertyChanges { target: errorTitleLabel; opacity: 1 } + PropertyChanges { target: errorTextLabel; opacity: 1 } + } + + transitions: Transition { + to: "errorState" + reversible: true + SequentialAnimation { + ParallelAnimation { + StandardAnimation { + target: titleLabel + property: "opacity" + duration: UbuntuAnimation.BriskDuration + } + StandardAnimation { + target: textLabel + property: "opacity" + duration: UbuntuAnimation.BriskDuration + } + } + ParallelAnimation { + StandardAnimation { + target: errorTitleLabel + property: "opacity" + duration: UbuntuAnimation.BriskDuration + } + StandardAnimation { + target: errorTextLabel + property: "opacity" + duration: UbuntuAnimation.BriskDuration + } + } + } + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/Tutorial.qml unity8-8.02+15.04.20150211/qml/Tutorial/Tutorial.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/Tutorial.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/Tutorial.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 + +Item { + id: root + + property alias active: loader.active + property bool paused + property real edgeSize + + property Item launcher + property Item panel + property Item stages + property Item overlay + + readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true + readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true + readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true + readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true + readonly property bool running: loader.item ? loader.item.running : false + + function finish() { + if (loader.item) { + loader.item.finish(); + } + } + + signal finished() + + Loader { + id: loader + anchors.fill: parent + source: "TutorialContent.qml" + + Binding { + target: loader.item + property: "paused" + value: root.paused + } + + Binding { + target: loader.item + property: "edgeSize" + value: root.edgeSize + } + + Binding { + target: loader.item + property: "launcher" + value: root.launcher + } + + Binding { + target: loader.item + property: "panel" + value: root.panel + } + + Binding { + target: loader.item + property: "stages" + value: root.stages + } + + Binding { + target: loader.item + property: "overlay" + value: root.overlay + } + + Connections { + target: loader.item + onFinished: root.finished() + } + } +} diff -Nru unity8-8.02+15.04.20150205/qml/Tutorial/TutorialRight.qml unity8-8.02+15.04.20150211/qml/Tutorial/TutorialRight.qml --- unity8-8.02+15.04.20150205/qml/Tutorial/TutorialRight.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/qml/Tutorial/TutorialRight.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.3 +import Ubuntu.Components 1.1 +import Ubuntu.Gestures 0.1 +import Unity.Application 0.1 +import "../Components" +import "../Stages" +import "." as LocalComponents + +TutorialPage { + id: root + + property var panel + property alias edgeSize: stage.dragAreaWidth + + title: i18n.tr("To view open apps") + text: i18n.tr("Long swipe from the right edge.") + + textOpacity: 1 - slider.percent + + SequentialAnimation { + id: teaseAnimation + paused: running && root.paused + running: !stage.dragging && stage.dragProgress === 0 + loops: Animation.Infinite + + UbuntuNumberAnimation { + target: stage + property: "x" + to: -stage.dragAreaOverlap + duration: UbuntuAnimation.SleepyDuration + } + UbuntuNumberAnimation { + target: stage + property: "x" + to: 0 + duration: UbuntuAnimation.SleepyDuration + } + } + + foreground { + children: [ + LocalComponents.Slider { + id: slider + anchors { + right: parent.right + top: parent.top + topMargin: root.textBottom + units.gu(3) + } + rotation: 180 + offset: stage.dragProgress - stage.x + active: stage.dragging + }, + + // Just assume PhoneStage for now. The tablet version of the right-edge + // tutorial is still being spec'd by the design team. + PhoneStage { + id: stage + objectName: "stage" + anchors.top: parent.top + width: parent.width + height: parent.height + applicationManager: fakeAppManager + color: "transparent" + interactive: false + altTabEnabled: false + focusFirstApp: false + startScale: 0.8 + endScale: 0.6 + dragAreaOverlap: units.gu(2) + + onOpened: { + overlay.show(); + root.textOpacity = 0; + slider.visible = false; + } + + onDraggingChanged: { + if (!dragging) { + if (!overlay.shown) { + root.showError(); + } + teaseAnimation.complete(); + } + } + }, + + Showable { + id: overlay + objectName: "overlay" + anchors.fill: parent + + opacity: 0 + shown: false + showAnimation: UbuntuNumberAnimation { property: "opacity"; to: 1 } + + Label { + anchors.top: parent.top + anchors.topMargin: root.panel.panelHeight + units.gu(2) + anchors.left: parent.left + anchors.leftMargin: units.gu(2) + anchors.right: parent.right + anchors.rightMargin: units.gu(2) + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + fontSize: "large" + text: i18n.tr("View all your running tasks.") + } + + LocalComponents.Tick { + objectName: "tick" + anchors.bottom: bottomOverlayText.top + anchors.bottomMargin: units.gu(1) + anchors.horizontalCenter: bottomOverlayText.horizontalCenter + onClicked: root.hide() + } + + Label { + id: bottomOverlayText + anchors.bottom: parent.bottom + anchors.bottomMargin: units.gu(2) + anchors.left: parent.left + anchors.leftMargin: units.gu(2) + anchors.right: parent.right + anchors.rightMargin: units.gu(2) + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + fontSize: "small" + text: i18n.tr("Tap here to continue.") + } + } + ] + } + + ListModel { + id: fakeAppManager + + readonly property string focusedApplicationId: "facebook" + + function focusApplication(appId) {} + function requestFocusApplication(appId) {} + function findApplication(appId) {return null;} + + signal applicationAdded(string appId) + signal applicationRemoved(string appId) + signal focusRequested(string appId) + + ListElement { + appId: "facebook" + fullscreen: false + name: "" + icon: "" + state: ApplicationInfoInterface.Stopped + splashTitle: "" + splashImage: "" + splashShowHeader: false + splashColor: "transparent" + splashColorHeader: "transparent" + splashColorFooter: "transparent" + defaultScreenshot: "../Tutorial/graphics/facebook.png" + } + + ListElement { + appId: "camera" + fullscreen: false + name: "" + icon: "" + state: ApplicationInfoInterface.Stopped + splashTitle: "" + splashImage: "" + splashShowHeader: false + splashColor: "transparent" + splashColorHeader: "transparent" + splashColorFooter: "transparent" + defaultScreenshot: "../Tutorial/graphics/camera.png" + } + + ListElement { + appId: "gallery" + fullscreen: false + name: "" + icon: "" + state: ApplicationInfoInterface.Stopped + splashTitle: "" + splashImage: "" + splashShowHeader: false + splashColor: "transparent" + splashColorHeader: "transparent" + splashColorFooter: "transparent" + defaultScreenshot: "../Tutorial/graphics/gallery.png" + } + + ListElement { + appId: "dialer" + fullscreen: false + name: "" + icon: "" + state: ApplicationInfoInterface.Stopped + splashTitle: "" + splashImage: "" + splashShowHeader: false + splashColor: "transparent" + splashColorHeader: "transparent" + splashColorFooter: "transparent" + defaultScreenshot: "../Tutorial/graphics/dialer.png" + } + } +} diff -Nru unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/emulators/edges_demo.py unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/emulators/edges_demo.py --- unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/emulators/edges_demo.py 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/emulators/edges_demo.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ -# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- -# -# Unity Autopilot Test Suite -# Copyright (C) 2014 Canonical -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -import logging -import time - -import ubuntuuitoolkit - -import autopilot -from autopilot import introspection - - -logger = logging.getLogger(__name__) - - -class RightEdgeDemoOverlay( - ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): - - @classmethod - def validate_dbus_object(cls, path, state): - name = introspection.get_classname_from_path(path) - if name == b'EdgeDemoOverlay': - if state['edge'][1] == 'right': - return True - return False - - @autopilot.logging.log_action(logger.info) - def swipe(self): - """Swipe to the left to complete this demo step.""" - x, y, width, height = self.globalRect - start_x = x + width - stop_x = x - start_y = stop_y = y + height // 2 - self.pointing_device.drag(start_x, start_y, stop_x, stop_y) - return self.get_root_instance().wait_select_single( - edge='top', active=True) - - -class TopEdgeDemoOverlay( - ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): - - @classmethod - def validate_dbus_object(cls, path, state): - name = introspection.get_classname_from_path(path) - if name == b'EdgeDemoOverlay': - if state['edge'][1] == 'top': - return True - return False - - @autopilot.logging.log_action(logger.info) - def swipe(self): - """Swipe to the bottom to complete this demo step.""" - x, y, width, height = self.globalRect - start_x = stop_x = x + width // 2 - start_y = y - stop_y = y + height - self.pointing_device.drag(start_x, start_y, stop_x, stop_y) - return self.get_root_instance().wait_select_single( - edge='bottom', active=True) - - -class BottomEdgeDemoOverlay( - ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): - - @classmethod - def validate_dbus_object(cls, path, state): - name = introspection.get_classname_from_path(path) - if name == b'EdgeDemoOverlay': - if state['edge'][1] == 'bottom': - return True - return False - - @autopilot.logging.log_action(logger.info) - def swipe(self): - """Swipe to the top to complete this demo step.""" - x, y, width, height = self.globalRect - start_x = stop_x = x + width // 2 - start_y = y + height - stop_y = y - self.pointing_device.drag(start_x, start_y, stop_x, stop_y) - return self.get_root_instance().wait_select_single( - edge='left', active=True) - - -class LeftEdgeDemoOverlay( - ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): - - @classmethod - def validate_dbus_object(cls, path, state): - name = introspection.get_classname_from_path(path) - if name == b'EdgeDemoOverlay': - if state['edge'][1] == 'left': - return True - return False - - @autopilot.logging.log_action(logger.info) - def swipe(self): - """Swipe to the right to complete this demo step.""" - x, y, width, height = self.globalRect - start_x = x - stop_x = x + width - start_y = stop_y = y + height // 2 - self.pointing_device.drag(start_x, start_y, stop_x, stop_y) - return self.get_root_instance().wait_select_single( - edge='none', active=True) - - -class FinalEdgeDemoOverlay( - ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): - - @classmethod - def validate_dbus_object(cls, path, state): - name = introspection.get_classname_from_path(path) - if name == b'EdgeDemoOverlay': - if state['edge'][1] == 'none': - return True - return False - - @autopilot.logging.log_action(logger.info) - def tap_to_start(self): - """Tap to finish the demo and start using the Ubuntu Touch.""" - time.sleep(1) - self.pointing_device.click_object(self) - self.shown.wait_for(False) diff -Nru unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/emulators/tutorial.py unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/emulators/tutorial.py --- unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/emulators/tutorial.py 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/emulators/tutorial.py 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,74 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# +# Unity Autopilot Test Suite +# Copyright (C) 2014 Canonical +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import logging +import time + +import ubuntuuitoolkit + +import autopilot +from autopilot import introspection + + +logger = logging.getLogger(__name__) + + +class TutorialPage( + ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): + + @classmethod + def validate_dbus_object(cls, path, state): + name = introspection.get_classname_from_path(path) + return name in (b'TutorialPage', b'TutorialLeft', + b'TutorialLeftFinish', b'TutorialRight', + b'TutorialBottom', b'TutorialBottomFinish') + + @autopilot.logging.log_action(logger.info) + def short_swipe_right(self): + self.shown.wait_for(True) + x, y, width, height = self.globalRect + start_x = x + stop_x = x + width // 3 + start_y = stop_y = y + height // 2 + self.pointing_device.drag(start_x, start_y, stop_x, stop_y) + + @autopilot.logging.log_action(logger.info) + def swipe_left(self): + self.shown.wait_for(True) + x, y, width, height = self.globalRect + start_x = width + stop_x = x + start_y = stop_y = y + height // 2 + self.pointing_device.drag(start_x, start_y, stop_x, stop_y) + + @autopilot.logging.log_action(logger.info) + def swipe_up(self): + self.shown.wait_for(True) + x, y, width, height = self.globalRect + start_y = height + stop_y = y + start_x = stop_x = x + width // 2 + self.pointing_device.drag(start_x, start_y, stop_x, stop_y) + + @autopilot.logging.log_action(logger.info) + def tap(self): + """Tap the tick button to complete this step.""" + self.shown.wait_for(True) + button = self.wait_select_single(objectName="tick") + self.pointing_device.click_object(button) diff -Nru unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/fixture_setup.py unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/fixture_setup.py --- unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/fixture_setup.py 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/fixture_setup.py 2015-02-11 17:11:40.000000000 +0000 @@ -54,7 +54,7 @@ return ld_library_path -class EdgesDemo(fixtures.Fixture): +class Tutorial(fixtures.Fixture): def __init__(self, enable): super().__init__() @@ -62,12 +62,12 @@ def setUp(self): super().setUp() - original_state = self._is_edges_demo_enabled() + original_state = self._is_tutorial_enabled() if self.enable != original_state: - self.addCleanup(self._set_edges_demo, original_state) - self._set_edges_demo(self.enable) + self.addCleanup(self._set_tutorial, original_state) + self._set_tutorial(self.enable) - def _is_edges_demo_enabled(self): + def _is_tutorial_enabled(self): command = [ 'dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.Accounts', @@ -79,7 +79,7 @@ output = subprocess.check_output(command, universal_newlines=True) return True if output.count('true') else False - def _set_edges_demo(self, value): + def _set_tutorial(self, value): value_string = 'true' if value else 'false' command = [ 'dbus-send', '--system', '--print-reply', diff -Nru unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/tests/test_edges_demo.py unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/tests/test_edges_demo.py --- unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/tests/test_edges_demo.py 2015-02-05 10:28:52.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/tests/test_edges_demo.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- -# -# Unity Autopilot Test Suite -# Copyright (C) 2014 Canonical -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -from autopilot.matchers import Eventually -from testtools.matchers import Equals - -from unity8.shell import ( - fixture_setup, - tests -) -# unused import to load the edge emulators custom proxy objects. -from unity8.shell.emulators import edges_demo # NOQA - - -class EdgesDemoTestCase(tests.UnityTestCase): - - def setUp(self): - super(EdgesDemoTestCase, self).setUp() - self._qml_mock_enabled = False - self._data_dirs_mock_enabled = False - - self.useFixture(fixture_setup.EdgesDemo(True)) - self.unity = self.launch_unity() - - def test_complete_edge_demo(self): - edge_demo = self.unity.select_single('EdgeDemo') - self.assertThat(edge_demo.running, Eventually(Equals(True))) - right_edge_overlay = self.unity.wait_select_single( - edge='right', active=True) - top_edge_overlay = right_edge_overlay.swipe() - bottom_edge_overlay = top_edge_overlay.swipe() - left_edge_overlay = bottom_edge_overlay.swipe() - final_overlay = left_edge_overlay.swipe() - final_overlay.tap_to_start() - self.assertThat(edge_demo.running, Eventually(Equals(False))) diff -Nru unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/tests/test_tutorial.py unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/tests/test_tutorial.py --- unity8-8.02+15.04.20150205/tests/autopilot/unity8/shell/tests/test_tutorial.py 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/autopilot/unity8/shell/tests/test_tutorial.py 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,57 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# +# Unity Autopilot Test Suite +# Copyright (C) 2014 Canonical +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +from autopilot.matchers import Eventually +from testtools.matchers import Equals + +from unity8.shell import ( + fixture_setup, + tests +) +# unused import to load the tutorial emulators custom proxy objects. +from unity8.shell.emulators import tutorial # NOQA + + +class TutorialTestCase(tests.UnityTestCase): + + def setUp(self): + super(TutorialTestCase, self).setUp() + self._qml_mock_enabled = False + self._data_dirs_mock_enabled = False + + self.useFixture(fixture_setup.Tutorial(True)) + self.unity = self.launch_unity() + + def test_complete_tutorial(self): + greeter = self.main_window.get_greeter() + tutorial = self.unity.select_single('Tutorial') + self.assertThat(tutorial.running, Eventually(Equals(True))) + greeter.swipe() + page = self.unity.wait_select_single(objectName='tutorialLeft') + page.short_swipe_right() + page = self.unity.wait_select_single(objectName='tutorialLeftFinish') + page.tap() + page = self.unity.wait_select_single(objectName='tutorialRight') + page.swipe_left() + page.tap() + page = self.unity.wait_select_single(objectName='tutorialBottom') + page.swipe_up() + page = self.unity.wait_select_single(objectName='tutorialBottomFinish') + page.tap() + self.assertThat(tutorial.running, Eventually(Equals(False))) diff -Nru unity8-8.02+15.04.20150205/tests/mocks/libusermetrics/UserMetrics.cpp unity8-8.02+15.04.20150211/tests/mocks/libusermetrics/UserMetrics.cpp --- unity8-8.02+15.04.20150205/tests/mocks/libusermetrics/UserMetrics.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/libusermetrics/UserMetrics.cpp 2015-02-11 17:13:01.000000000 +0000 @@ -289,6 +289,7 @@ new UserMetricsData("52km travelled", first, firstMonth, ninth, secondMonth, this)); m_fakeData.insert("single", data); + m_fakeData.insert("has-pin", data); } { @@ -304,12 +305,14 @@ new UserMetricsData("33 messages today", second, firstMonth, eighth, secondMonth, this)); m_fakeData.insert("single", data); + m_fakeData.insert("has-pin", data); } { QVariantList firstMonth; while (firstMonth.size() < 17) firstMonth.push_back(QVariant(rand())); + firstMonth[8] = QVariant(1.0); // oversized 9th day, to test clipping while (firstMonth.size() < 31) firstMonth.push_back(QVariant()); QVariantList secondMonth; @@ -319,6 +322,7 @@ new UserMetricsData("19 minutes talk time", eighth, firstMonth, second, secondMonth, this)); m_fakeData.insert("single", data); + m_fakeData.insert("has-pin", data); // Also use same data for some tablet users m_fakeData.insert("has-password", data); m_fakeData.insert("no-password", data); @@ -386,6 +390,10 @@ if (m_dataIndex == m_fakeData.constEnd() || m_dataIndex.key() != m_username) { m_dataIndex = m_fakeData.constFind(m_username); + if (m_dataIndex == m_fakeData.constEnd()) + { + m_dataIndex = m_fakeData.constFind(""); + } } loadFakeData(); diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Application/MirSurfaceItem.qml unity8-8.02+15.04.20150211/tests/mocks/Unity/Application/MirSurfaceItem.qml --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Application/MirSurfaceItem.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Application/MirSurfaceItem.qml 2015-02-11 17:11:30.000000000 +0000 @@ -44,9 +44,24 @@ } Text { + text: surfaceText.text + color: "black" + font: surfaceText.font + fontSizeMode: Text.Fit + minimumPixelSize: 10 + verticalAlignment: Text.AlignVCenter + x: surfaceText.x + y: surfaceText.y + width: surfaceText.width + height: surfaceText.height + + transform: Translate { x: -2; y: -2 } + } + Text { + id: surfaceText anchors.fill: parent text: "SURFACE" - color: "yellow" + color: root.parent && root.parent.activeFocus ? "yellow" : "blue" font.bold: true fontSizeMode: Text.Fit minimumPixelSize: 10; font.pixelSize: 200 diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -21,7 +21,8 @@ #include "indicators.h" FakeIndicatorsModel::FakeIndicatorsModel(QObject *parent) - : QAbstractListModel(parent) + : QAbstractListModel(parent), + m_profile("phone") { QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged())); QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged())); @@ -38,6 +39,17 @@ return rowCount(); } +QString FakeIndicatorsModel::profile() const +{ + return m_profile; +} + +void FakeIndicatorsModel::setProfile(const QString& profile) +{ + m_profile = profile; + Q_EMIT profileChanged(); +} + void FakeIndicatorsModel::load(const QString&) { } diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Indicators/fakeindicatorsmodel.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Indicators/fakeindicatorsmodel.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Indicators/fakeindicatorsmodel.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Indicators/fakeindicatorsmodel.h 2015-02-11 17:12:49.000000000 +0000 @@ -27,7 +27,9 @@ Q_OBJECT Q_ENUMS(Roles) Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged) + public: FakeIndicatorsModel(QObject *parent=0); @@ -42,6 +44,8 @@ Q_INVOKABLE QVariant data(int row, int role) const; + QString profile() const; + void setProfile(const QString& profile); void setModelData(const QVariant& data); QVariant modelData() const { return m_modelData; } @@ -54,11 +58,13 @@ Q_SIGNALS: void countChanged(); + void profileChanged(); void modelDataChanged(); private: int count() const; + QString m_profile; QVariant m_modelData; }; diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Launcher/CMakeLists.txt unity8-8.02+15.04.20150211/tests/mocks/Unity/Launcher/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Launcher/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Launcher/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -1,4 +1,4 @@ -pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=5) +pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Launcher/MockLauncherModel.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Launcher/MockLauncherModel.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Launcher/MockLauncherModel.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Launcher/MockLauncherModel.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -209,6 +209,16 @@ Q_UNUSED(applicationManager) } +bool MockLauncherModel::onlyPinned() const +{ + return false; +} + +void MockLauncherModel::setOnlyPinned(bool onlyPinned) +{ + Q_UNUSED(onlyPinned) +} + void MockLauncherModel::emitHint() { Q_EMIT hint(); diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Launcher/MockLauncherModel.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Launcher/MockLauncherModel.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Launcher/MockLauncherModel.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Launcher/MockLauncherModel.h 2015-02-11 17:10:20.000000000 +0000 @@ -49,6 +49,9 @@ unity::shell::application::ApplicationManagerInterface *applicationManager() const; void setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager); + bool onlyPinned() const override; + void setOnlyPinned(bool onlyPinned) override; + // For testing Q_INVOKABLE void emitHint(); diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/CMakeLists.txt unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/CMakeLists.txt 2015-02-11 17:11:02.000000000 +0000 @@ -4,7 +4,8 @@ set(MockNotificationsPlugin_SOURCES plugin.cpp - MockNotificationTypes.cpp + MockNotification.cpp + MockNotificationModel.cpp MockActionModel.cpp ) diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockActionModel.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockActionModel.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockActionModel.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockActionModel.cpp 2015-02-11 17:11:02.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. + * Copyright 2015 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,8 +25,6 @@ }; ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) { - insertAction("ok_id", "Ok"); - insertAction("cancel_id", "Cancel"); } ActionModel::~ActionModel() { @@ -66,7 +64,11 @@ return data(index(row, 0), role); } -void ActionModel::insertAction(const QString &id, const QString &label) { +void ActionModel::append(const QString &id, const QString &label) { p->ids.push_back(id); p->labels.push_back(label); } + +int ActionModel::getCount() const { + return p->labels.size(); +} diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockActionModel.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockActionModel.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockActionModel.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockActionModel.h 2015-02-11 17:11:02.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. + * Copyright 2015 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 @@ -26,6 +26,7 @@ class ActionModel : public QStringListModel { Q_OBJECT + Q_PROPERTY(int count READ getCount) public: ActionModel(QObject *parent=nullptr); @@ -42,7 +43,8 @@ }; Q_INVOKABLE QVariant data(int row, int role) const; - void insertAction(const QString &id, const QString &label); + Q_INVOKABLE void append(const QString &id, const QString &label); + int getCount() const; private: QScopedPointer p; diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotification.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotification.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotification.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotification.cpp 2015-02-11 17:11:02.000000000 +0000 @@ -0,0 +1,178 @@ +/* + * Copyright 2015 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 . + * + * Authors: + * Mirco Mueller + */ + +#include "MockNotification.h" + +#include + +struct MockNotificationPrivate { + int id; + QString summary; + QString body; + int value; + MockNotification::Type type; + QString icon; + QString secondaryIcon; + QStringList actions; + ActionModel* actionsModel; + QVariantMap hints; +}; + +MockNotification::MockNotification(QObject *parent) : QObject(parent), p(new MockNotificationPrivate()) { + p->actionsModel = new ActionModel(); +} + +MockNotification::~MockNotification() { +} + +QString MockNotification::getSummary() const { + return p->summary; +} + +void MockNotification::setSummary(const QString &summary) { + if(p->summary != summary) { + p->summary = summary; + Q_EMIT summaryChanged(p->summary); + Q_EMIT dataChanged(p->id); + } +} + +QString MockNotification::getBody() const { + return p->body; +} + +void MockNotification::setBody(const QString &body) { + if(p->body != body) { + p->body = body; + Q_EMIT bodyChanged(p->body); + Q_EMIT dataChanged(p->id); + } +} + +int MockNotification::getID() const { + return p->id; +} + +void MockNotification::setID(const int id) { + p->id = id; +} + +int MockNotification::getValue() const { + return p->value; +} + +void MockNotification::setValue(int value) { + if(p->value != value) { + p->value = value; + Q_EMIT valueChanged(p->value); + Q_EMIT dataChanged(p->id); + } +} + +QString MockNotification::getIcon() const { + return p->icon; +} + +void MockNotification::setIcon(const QString &icon) { + if (icon.startsWith(" ") || icon.size() == 0) { + p->icon = nullptr; + } + else { + p->icon = icon; + + if (icon.indexOf("/") == -1) { + p->icon.prepend("image://theme/"); + } + } + + Q_EMIT iconChanged(p->icon); + Q_EMIT dataChanged(p->id); +} + +QString MockNotification::getSecondaryIcon() const { + return p->secondaryIcon; +} + +void MockNotification::setSecondaryIcon(const QString &secondaryIcon) { + if (secondaryIcon.startsWith(" ") || secondaryIcon.size() == 0) { + p->secondaryIcon = nullptr; + } + else { + p->secondaryIcon = secondaryIcon; + + if (secondaryIcon.indexOf("/") == -1) { + p->secondaryIcon.prepend("image://theme/"); + } + } + + Q_EMIT secondaryIconChanged(p->secondaryIcon); + Q_EMIT dataChanged(p->id); +} + +MockNotification::Type MockNotification::getType() const { + return p->type; +} + +void MockNotification::setType(Type type) { + if(p->type != type) { + p->type = type; + Q_EMIT typeChanged(p->type); + } +} + +ActionModel* MockNotification::getActions() const { + return p->actionsModel; +} + +void MockNotification::setActions(const QStringList &actions) { + if(p->actions != actions) { + p->actions = actions; + Q_EMIT actionsChanged(p->actions); + + for (int i = 0; i < p->actions.size(); i += 2) { + p->actionsModel->append(p->actions[i], p->actions[i+1]); + } + } +} + +QVariantMap MockNotification::getHints() const { + return p->hints; +} + +void MockNotification::setHints(const QVariantMap& hints) { + if (p->hints != hints) { + p->hints = hints; + Q_EMIT hintsChanged(p->hints); + } +} + +void MockNotification::invokeAction(const QString &action) { + for(int i=0; iactions.size(); i++) { + if(p->actions[i] == action) { + Q_EMIT actionInvoked(action); + qDebug() << "Info: invoked action" << action; + return; + } + } + fprintf(stderr, "Error: tried to invoke action not in actionList.\n"); +} + +void MockNotification::close() { + Q_EMIT completed(p->id); +} diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotification.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotification.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotification.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotification.h 2015-02-11 17:11:02.000000000 +0000 @@ -0,0 +1,94 @@ +/* + * Copyright 2015 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 . + * + * Authors: + * Mirco Mueller + */ + +#ifndef MOCK_NOTIFICATION_H +#define MOCK_NOTIFICATION_H + +#include "MockActionModel.h" +#include +#include +#include +#include + +struct MockNotificationPrivate; + +class MockNotification : public QObject { + Q_OBJECT + Q_ENUMS(Type) + Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged) + Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged) + Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged) + Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(QString icon READ getIcon WRITE setIcon NOTIFY iconChanged) + Q_PROPERTY(QString secondaryIcon READ getSecondaryIcon WRITE setSecondaryIcon NOTIFY secondaryIconChanged) + Q_PROPERTY(Type type READ getType WRITE setType NOTIFY typeChanged) + Q_PROPERTY(QStringList rawActions WRITE setActions) + Q_PROPERTY(ActionModel* actions READ getActions NOTIFY actionsChanged) + Q_PROPERTY(QVariantMap hints READ getHints WRITE setHints NOTIFY hintsChanged) + +private: + QScopedPointer p; + +public: + enum Urgency { Low, Normal, Critical }; + enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision }; + +Q_SIGNALS: + void summaryChanged(const QString &summary); + void bodyChanged(const QString &body); + void idChanged(const int id); + void valueChanged(int value); + void iconChanged(const QString &icon); + void secondaryIconChanged(const QString &secondaryIcon); + void typeChanged(Type type); + void actionsChanged(const QStringList &actions); + void hintsChanged(const QVariantMap& hints); + + void dataChanged(int nid); + void completed(int nid); + void actionInvoked(const QString &action); + +public: + MockNotification(QObject *parent=nullptr); + virtual ~MockNotification(); + + QString getSummary() const; + void setSummary(const QString &summary); + QString getBody() const; + void setBody(const QString &body); + int getID() const; + void setID(const int id); + int getValue() const; + void setValue(int value); + QString getIcon() const; + void setIcon(const QString &icon); + QString getSecondaryIcon() const; + void setSecondaryIcon(const QString &secondaryIcon); + Type getType() const; + void setType(Type type); + ActionModel* getActions() const; + void setActions(const QStringList &actions); + QVariantMap getHints() const; + void setHints(const QVariantMap& hints); + + Q_INVOKABLE void invokeAction(const QString &action); + Q_INVOKABLE void close(); +}; + +#endif // MOCK_NOTIFICATION_H diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationModel.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationModel.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationModel.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationModel.cpp 2015-02-11 17:11:02.000000000 +0000 @@ -0,0 +1,172 @@ +/* + * Copyright 2015 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 . + * + * Authors: + * Mirco Mueller + */ + +#include "MockNotificationModel.h" +#include "MockNotification.h" + +#include + +#include +#include +#include +#include +#include +#include + +using namespace unity::shell::notifications; + +MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListModel(parent) { +} + +MockNotificationModel::~MockNotificationModel() { +} + +int MockNotificationModel::rowCount(const QModelIndex &parent) const { + return m_queue.size(); +} + +int MockNotificationModel::getCount() const { + return m_queue.size(); +} + +QVariant MockNotificationModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) + return QVariant(); + + switch(role) { + case ModelInterface::RoleType: + return QVariant(m_queue[index.row()]->getType()); + + case ModelInterface::RoleId: + return QVariant(m_queue[index.row()]->getID()); + + case ModelInterface::RoleSummary: + return QVariant(m_queue[index.row()]->getSummary()); + + case ModelInterface::RoleBody: + return QVariant(m_queue[index.row()]->getBody()); + + case ModelInterface::RoleValue: + return QVariant(m_queue[index.row()]->getValue()); + + case ModelInterface::RoleIcon: + return QVariant(m_queue[index.row()]->getIcon()); + + case ModelInterface::RoleSecondaryIcon: + return QVariant(m_queue[index.row()]->getSecondaryIcon()); + + case ModelInterface::RoleActions: + return QVariant::fromValue(m_queue[index.row()]->getActions()); + + case ModelInterface::RoleHints: + return QVariant(m_queue[index.row()]->getHints()); + + case ModelInterface::RoleNotification: + return QVariant::fromValue(m_queue[index.row()]); + + default: + return QVariant(); + } +} + +void MockNotificationModel::append(MockNotification* n) { + int location = m_queue.size(); + QModelIndex insertionPoint = QModelIndex(); + beginInsertRows(insertionPoint, location, location); + m_queue.insert(location, n); + endInsertRows(); +} + +MockNotification* MockNotificationModel::getNotification(int id) const { + for(int i=0; i < m_queue.size(); i++) { + if(m_queue[i]->getID() == id) { + return m_queue[i]; + } + } + + return nullptr; +} + +void MockNotificationModel::remove(const int id) { + for(int i = 0; i < m_queue.size(); i++) { + if(m_queue[i]->getID() == id) { + removeInternal(i); + return; + } + } +} + +void MockNotificationModel::removeSecond() { + if(m_queue.size() < 2) + return; + removeInternal(1); +} + +void MockNotificationModel::removeInternal(int loc) { + QModelIndex deletePoint = QModelIndex(); + beginRemoveRows(deletePoint, loc, loc); + m_queue.erase(m_queue.begin() + loc); + endRemoveRows(); +} + +MockNotification* MockNotificationModel::getRaw(const int notificationId) const { + for(int i = 0; i < m_queue.size(); i++) { + if(m_queue[i]->getID() == notificationId) { + MockNotification* n = m_queue[i]; + return n; + } + } + + return nullptr; +} + +int MockNotificationModel::queued() const { + return m_queue.size(); +} + +QHash MockNotificationModel::roleNames() const { + QHash roles; + + roles.insert(ModelInterface::RoleType, "type"); + roles.insert(ModelInterface::RoleUrgency, "urgency"); + roles.insert(ModelInterface::RoleId, "id"); + roles.insert(ModelInterface::RoleSummary, "summary"); + roles.insert(ModelInterface::RoleBody, "body"); + roles.insert(ModelInterface::RoleValue, "value"); + roles.insert(ModelInterface::RoleIcon, "icon"); + roles.insert(ModelInterface::RoleSecondaryIcon, "secondaryIcon"); + roles.insert(ModelInterface::RoleActions, "actions"); + roles.insert(ModelInterface::RoleHints, "hints"); + roles.insert(ModelInterface::RoleNotification, "notification"); + + return roles; +} + +void MockNotificationModel::onCompleted(int id) { + remove(id); +} + +void MockNotificationModel::onDataChanged(int id) { + for(int i = 0; i < m_queue.size(); i++) { + if(m_queue[i]->getID() == id) { + Q_EMIT dataChanged(index(i, 0), index(i, 0)); + break; + } + } +} diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationModel.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationModel.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationModel.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationModel.h 2015-02-11 17:11:02.000000000 +0000 @@ -0,0 +1,72 @@ +/* + * Copyright 2015 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 . + * + * Authors: + * Mirco Mueller + */ + +#ifndef MOCK_NOTIFICATION_MODEL_H +#define MOCK_NOTIFICATION_MODEL_H + +#include +#include +#include +#include "MockNotification.h" + +class MockNotification; + +class MockNotificationModel : public QAbstractListModel { + Q_OBJECT + Q_PROPERTY(int count READ getCount) + +public: + MockNotificationModel(QObject *parent=nullptr); + virtual ~MockNotificationModel(); + + virtual int rowCount(const QModelIndex &parent) const; + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QHash roleNames() const; + + Q_INVOKABLE void append(MockNotification* n); + MockNotification* getNotification(int id) const; + + // getRaw() is only meant to be used from QML, since QML cannot handle + // QSharedPointers... on C++-side only use getNotification() + Q_INVOKABLE MockNotification* getRaw(const int notificationId) const; + + Q_INVOKABLE int queued() const; + Q_INVOKABLE void remove(const int id); + Q_INVOKABLE void removeSecond(); + + int getCount() const; + +Q_SIGNALS: + void actionInvoked(const QString &action); + +public Q_SLOTS: + void onCompleted(int id); + +private Q_SLOTS: + void onDataChanged(int id); + +Q_SIGNALS: + void queueSizeChanged(int newSize); + +private: + QList m_queue; + void removeInternal(int loc); +}; + +#endif diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationTypes.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationTypes.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -/* - * Copyright 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 . - * - * Authors: - * Mirco Mueller - */ - -#include "MockNotificationTypes.h" - -MockNotification::MockNotification(QObject *parent) : QObject(parent) { -} - -MockNotification::~MockNotification() { -} diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationTypes.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationTypes.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/MockNotificationTypes.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/MockNotificationTypes.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -/* - * Copyright 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 . - * - * Authors: - * Mirco Mueller - */ - -#ifndef MOCK_NOTIFICATION_TYPES_H -#define MOCK_NOTIFICATION_TYPES_H - -#include - -class MockNotification : public QObject { - Q_OBJECT - Q_ENUMS(Type) - -public: - MockNotification(QObject *parent=nullptr); - virtual ~MockNotification(); - - enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision }; -}; - -#endif // MOCK_NOTIFICATION_TYPES_H diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/plugin.cpp unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/plugin.cpp --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/plugin.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/plugin.cpp 2015-02-11 17:11:02.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. + * Copyright 2015 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 @@ -19,13 +19,15 @@ #include "plugin.h" #include "MockActionModel.h" -#include "MockNotificationTypes.h" +#include "MockNotification.h" +#include "MockNotificationModel.h" #include void TestNotificationPlugin::registerTypes(const char* uri) { // @uri Unity.Notifications - qmlRegisterUncreatableType(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin"); + qmlRegisterType(uri, 1, 0, "Notification"); + qmlRegisterType(uri, 1, 0, "NotificationModel"); qmlRegisterType(uri, 1, 0, "ActionModel"); } diff -Nru unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/plugin.h unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/plugin.h --- unity8-8.02+15.04.20150205/tests/mocks/Unity/Notifications/plugin.h 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/mocks/Unity/Notifications/plugin.h 2015-02-11 17:11:02.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. + * Copyright 2015 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 @@ -17,7 +17,6 @@ * Mirco Mueller */ - #ifndef TESTNOTIFICATION_PLUGIN_H #define TESTNOTIFICATION_PLUGIN_H diff -Nru unity8-8.02+15.04.20150205/tests/plugins/CMakeLists.txt unity8-8.02+15.04.20150211/tests/plugins/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/plugins/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -1,4 +1,5 @@ add_subdirectory(AccountsService) +add_subdirectory(Greeter) add_subdirectory(LightDM) add_subdirectory(Dash) add_subdirectory(Ubuntu) diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Greeter/CMakeLists.txt unity8-8.02+15.04.20150211/tests/plugins/Greeter/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/plugins/Greeter/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Greeter/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1 @@ +add_subdirectory(Unity) diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/CMakeLists.txt unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1 @@ +add_subdirectory(Launcher) diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.cpp unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "AccountsServiceDBusAdaptor.h" +#include +#include +#include +#include + +AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent) + : QObject(parent) +{ + qDBusRegisterMetaType>(); +} + +QVariant AccountsServiceDBusAdaptor::getUserProperty(const QString &user, const QString &interface, const QString &property) +{ + Q_UNUSED(interface) + Q_UNUSED(property) // We only fake one property here (LauncherItems) + + QDBusMessage msg; + QVariant v = QVariant::fromValue(mockProperties.value(user)); + QDBusVariant dv(v); + QVariant packed = QVariant::fromValue(dv); + msg << packed; + + return packed.value().asVariant(); +} + +void AccountsServiceDBusAdaptor::simulatePropertyChange(const QString &user, const QString &property, const QVariant &value) +{ + Q_ASSERT(property == "LauncherItems"); + mockProperties[user] = value.value>(); + Q_EMIT propertiesChanged(user, "com.canonical.unity.AccountsService", QStringList() << property); +} diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.h unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.h --- unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H +#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H + +#include +#include +#include +#include +#include + +extern QHash> mockProperties; + +class AccountsServiceDBusAdaptor: public QObject +{ + Q_OBJECT + +public: + explicit AccountsServiceDBusAdaptor(QObject *parent = 0); + + Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property); + template + inline T getUserProperty(const QString &user, const QString &interface, const QString &property) { + Q_UNUSED(interface) + Q_ASSERT(property == "LauncherItems"); + T ret = mockProperties[user]; + return ret; + } + +Q_SIGNALS: + void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed); + +private: + void simulatePropertyChange(const QString &user, const QString &property, const QVariant &value); + + friend class LauncherModelASTest; +}; + +#endif diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,56 @@ +pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6) +pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher + ${libunity8-private_SOURCE_DIR} + ) + +add_definitions(-DSM_BUSNAME=sessionBus) +add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}") +add_definitions(-DLAUNCHER_TESTING) + +### LauncherModelASTest +set(testModelCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launchermodelastestExec + --parameter -o --parameter ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml + --parameter -o --parameter -,txt) +add_test(NAME launchermodelastest COMMAND ${testModelCommand}) +add_custom_target(launchermodelastest ${testModelCommand}) +add_executable(launchermodelastestExec + launchermodelastest.cpp + AccountsServiceDBusAdaptor.cpp + ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/launchermodelas.cpp + ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/launcheritem.cpp + ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/quicklistmodel.cpp + ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/quicklistentry.cpp + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h + ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h + ${LAUNCHER_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h + ) +target_link_libraries(launchermodelastestExec + unity8-private + ${GSETTINGS_QT_LDFLAGS} + ) +qt5_use_modules(launchermodelastestExec Test Core DBus Gui) + +# copy .desktop files into build directory for shadow builds +file(GLOB DESKTOP_FILES *.desktop) + +foreach(DESKTOP_FILE ${DESKTOP_FILES}) + file(COPY "${DESKTOP_FILE}" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + ) +endforeach() + +# copy .svg files into build directory for shadow builds +file(GLOB DESKTOP_FILES *.svg) + +foreach(DESKTOP_FILE ${DESKTOP_FILES}) + file(COPY "${DESKTOP_FILE}" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + ) +endforeach() diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cpp unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,169 @@ +/* + * Copyright 2014-2015 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 . + */ + +// unity-api +#include +#include + +#include "launcheritem.h" +#include "launchermodelas.h" +#include "AccountsServiceDBusAdaptor.h" + +#include + +QHash> mockProperties; + +class LauncherModelASTest : public QObject +{ + Q_OBJECT + +private: + LauncherModel *launcherModel; + +private Q_SLOTS: + + void init() { + // Prepare some fake list + QList list; + QVariantMap item; + item.insert("id", "appId1"); + item.insert("name", "Item 1"); + item.insert("icon", "fake.svg"); + item.insert("count", 0); + item.insert("countVisible", false); + item.insert("pinned", true); + list.append(item); + item["id"] = "appId2"; + item["name"] = "Item 2"; + list.append(item); + mockProperties["user1"] = list; + } + + bool isInSync(LauncherModel *model, const QList &properties) { + QList list; + // Strip unpinned if onlyPinned is set + Q_FOREACH (const QVariantMap &map, properties) { + if (!model->onlyPinned() || map.value("pinned").toBool()) { + list << map; + } + } + + bool inSync = model->rowCount() == list.count(); + for (int i = 0; inSync && i < model->rowCount(); i++) { + inSync &= model->get(i)->appId() == list.at(i).value("id").toString(); + inSync &= model->get(i)->name() == list.at(i).value("name").toString(); + inSync &= model->get(i)->icon() == list.at(i).value("icon").toString(); + inSync &= model->get(i)->count() == list.at(i).value("count").toInt(); + inSync &= model->get(i)->countVisible() == list.at(i).value("countVisible").toBool(); + } + return inSync; + } + + void testLoadASOnStartup() { + // Load up the model + LauncherModel* model = new LauncherModel(this); + + // We didn't set a user yet. model should be empty + QCOMPARE(model->rowCount(), 0); + + model->setUser("user1"); + + QCOMPARE(isInSync(model, mockProperties["user1"]), true); + model->deleteLater(); + } + + void testASChangedUpdatesModel_data() { + QTest::addColumn("modelUser"); + QTest::addColumn("changedUser"); + QTest::addColumn("inSync"); + + QTest::newRow("this user changed") << "user1" << "user1" << true; + QTest::newRow("other user changed") << "user1" << "user2" << false; + } + + void testASChangedUpdatesModel() { + QFETCH(QString, modelUser); + QFETCH(QString, changedUser); + QFETCH(bool, inSync); + + LauncherModel* model = new LauncherModel(this); + model->setUser(modelUser); + + int oldCount = mockProperties[modelUser].count(); + QCOMPARE(model->rowCount(), oldCount); + + QList newList; + QVariantMap newEntry; + newEntry.insert("id", "newappId"); + newEntry.insert("name", "New app"); + newEntry.insert("icon", "some-icon.svg"); + newEntry.insert("count", 0); + newEntry.insert("countVisible", false); + newEntry.insert("pinned", true); + newList.append(newEntry); + model->m_accounts->simulatePropertyChange(changedUser, "LauncherItems", QVariant::fromValue(newList)); + + QCOMPARE(isInSync(model, mockProperties[modelUser]), true); + QCOMPARE(isInSync(model, mockProperties[changedUser]), inSync); + + model->deleteLater(); + } + + void testUpdateCount() { + LauncherModel* model = new LauncherModel(this); + model->setUser("user1"); + + QCOMPARE(model->get(0)->countVisible(), false); + QCOMPARE(model->get(0)->count(), 0); + + QList newList = mockProperties["user1"]; + QVariantMap entry = newList.at(0); + entry["countVisible"] = true; + entry["count"] = 55; + newList[0] = entry; + model->m_accounts->simulatePropertyChange("user1", "LauncherItems", QVariant::fromValue(newList)); + + QCOMPARE(isInSync(model, mockProperties["user1"]), true); + + model->deleteLater(); + } + + void testOnlyPinned() { + LauncherModel *model = new LauncherModel(this); + model->setUser("user1"); + model->setOnlyPinned(true); + + QCOMPARE(isInSync(model, mockProperties["user1"]), true); + + // Let's unpin one item + QList newList = mockProperties["user1"]; + QVariantMap entry = newList.at(0); + entry["pinned"] = false; + newList[0] = entry; + model->m_accounts->simulatePropertyChange("user1", "LauncherItems", QVariant::fromValue(newList)); + QCOMPARE(isInSync(model, mockProperties["user1"]), true); + + // Now toggle onlyPinned and make sure the model keeps up + model->setOnlyPinned(false); + QCOMPARE(isInSync(model, mockProperties["user1"]), true); + + model->setOnlyPinned(true); + QCOMPARE(isInSync(model, mockProperties["user1"]), true); + } +}; + +QTEST_GUILESS_MAIN(LauncherModelASTest) +#include "launchermodelastest.moc" diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp unity8-8.02+15.04.20150211/tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -50,7 +50,8 @@ QVERIFY(!manager.isLoaded()); QCOMPARE(manager.indicators().count(), 0); - manager.load("test1"); + manager.setProfile("test1"); + manager.load(); QVERIFY(manager.isLoaded()); QCOMPARE(manager.indicators().count(), 4); @@ -67,7 +68,8 @@ void testPluginInterfaceProfile1() { IndicatorsManager manager; - manager.load("test1"); + manager.setProfile("test1"); + manager.load(); Indicator::Ptr indicator = manager.indicator("indicator-fake1"); QVERIFY(indicator ? true : false); @@ -90,7 +92,8 @@ void testPluginInterfaceProfile2() { IndicatorsManager manager; - manager.load("test2"); + manager.setProfile("test2"); + manager.load(); Indicator::Ptr indicator = manager.indicator("indicator-fake1"); QVERIFY(indicator ? true : false); @@ -108,12 +111,33 @@ } /* + * Test switching the indicator profile data + */ + void testPluginInterfaceProfileSwitch() + { + IndicatorsManager manager; + manager.setProfile("test1"); + manager.load(); + + Indicator::Ptr indicator = manager.indicator("indicator-fake1"); + QVERIFY(indicator ? true : false); + + QVariantMap props = indicator->indicatorProperties().toMap(); + QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test1")); + + manager.setProfile("test2"); + props = indicator->indicatorProperties().toMap(); + QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test2")); + } + + /* * Test if a new plugin object is create for each different plugin */ void testPluginInstance() { IndicatorsManager manager; - manager.load("test2"); + manager.setProfile("test2"); + manager.load(); Indicator::Ptr i0 = manager.indicator("indicator-fake1"); Indicator::Ptr i1 = manager.indicator("indicator-fake1"); @@ -133,7 +157,8 @@ void testPluginInitAndShutdown() { IndicatorsManager manager; - manager.load("test1"); + manager.setProfile("test1"); + manager.load(); QWeakPointer wp0; QWeakPointer wp1; diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp unity8-8.02+15.04.20150211/tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -47,10 +47,11 @@ void testLoad() { IndicatorsModel model; + model.setProfile("test1"); QCOMPARE(model.property("count").toInt(), 0); - model.load("test1"); + model.load(); QCOMPARE(model.property("count").toInt(), 4); @@ -67,7 +68,8 @@ { // Priority order. (2, 1, 4, 3) IndicatorsModel model; - model.load("test1"); + model.setProfile("test1"); + model.load(); // should be in order: // fake3, fake4, fake1, fake2 @@ -104,7 +106,8 @@ { // Priority order. (2, 1, 4, 3) IndicatorsModel model; - model.load("test2"); + model.setProfile("test2"); + model.load(); // should be in order: // fake3, fake4, fake1, fake2 diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp unity8-8.02+15.04.20150211/tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp 2015-02-11 17:12:49.000000000 +0000 @@ -61,7 +61,7 @@ QCOMPARE(model1->model(), model2->model()); } - void testSharedOwnership() + void testSavedData() { QSharedPointer model1(createFullModel("test1")); QSharedPointer model2(createFullModel("test1")); @@ -70,7 +70,7 @@ model1.clear(); QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true); model2.clear(); - QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), false); + QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true); } // Tests that changing cached model data does not change the model path of others diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.cpp unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "AccountsServiceDBusAdaptor.h" +#include + +AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent) + : QObject(parent) +{ +} + +QVariant AccountsServiceDBusAdaptor::getUserProperty(const QString &user, const QString &interface, const QString &property) +{ + Q_UNUSED(user) + Q_UNUSED(interface) + return m_properties.value(property); +} + +void AccountsServiceDBusAdaptor::setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value) +{ + Q_UNUSED(user) + Q_UNUSED(interface) + m_properties[property] = value; +} + +void AccountsServiceDBusAdaptor::setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value) +{ + setUserProperty(user, interface, property, value); +} diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.h unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.h --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.h 2015-02-11 17:10:20.000000000 +0000 @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014-2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H +#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H + +#include +#include +#include +#include + +class AccountsServiceDBusAdaptor: public QObject +{ + Q_OBJECT + +public: + explicit AccountsServiceDBusAdaptor(QObject *parent = 0); + + Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property); + Q_INVOKABLE void setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value); + Q_INVOKABLE void setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value); + +Q_SIGNALS: + void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed); + void maybeChanged(const QString &user); // Standard properties might have changed + +private: + void simulatePropertyChange(const QString &property, const QVariant &value); + +private: + QHash m_properties; +}; + +#endif diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/CMakeLists.txt unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/CMakeLists.txt 2015-02-11 17:10:20.000000000 +0000 @@ -1,5 +1,5 @@ pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt) -pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=5) +pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6) pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5) include_directories( @@ -23,7 +23,9 @@ add_executable(launchermodeltestExec launchermodeltest.cpp gsettings.cpp + AccountsServiceDBusAdaptor.cpp ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp + ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/asadapter.cpp ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp diff -Nru unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/launchermodeltest.cpp unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/launchermodeltest.cpp --- unity8-8.02+15.04.20150205/tests/plugins/Unity/Launcher/launchermodeltest.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/plugins/Unity/Launcher/launchermodeltest.cpp 2015-02-11 17:10:20.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2013 Canonical Ltd. + * Copyright 2013-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 @@ -25,6 +25,8 @@ #include "launchermodel.h" #include "dbusinterface.h" #include "gsettings.h" +#include "asadapter.h" +#include "AccountsServiceDBusAdaptor.h" #include #include @@ -129,6 +131,11 @@ LauncherModel *launcherModel; MockAppManager *appManager; + QList getASConfig() { + AccountsServiceDBusAdaptor *as = launcherModel->m_asAdapter->m_accounts; + return as->getUserProperty("", "", "LauncherItems").value>(); + } + private Q_SLOTS: void initTestCase() { @@ -467,6 +474,63 @@ QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon")); QCOMPARE(spy.count(), 2); } + + void testAddSyncsToAS() { + // Make sure launcher and AS are in sync when we start the test + QCOMPARE(launcherModel->rowCount(), getASConfig().count()); + + int oldCount = launcherModel->rowCount(); + appManager->addApplication(new MockApp("rel-icon")); + QCOMPARE(launcherModel->rowCount(), oldCount + 1); + QCOMPARE(launcherModel->rowCount(), getASConfig().count()); + } + + void testRemoveSyncsToAS() { + // Make sure launcher and AS are in sync when we start the test + QCOMPARE(launcherModel->rowCount(), getASConfig().count()); + + int oldCount = launcherModel->rowCount(); + appManager->stopApplication("abs-icon"); + QCOMPARE(launcherModel->rowCount(), oldCount - 1); + QCOMPARE(launcherModel->rowCount(), getASConfig().count()); + } + + void testMoveSyncsToAS() { + // Make sure launcher and AS are in sync when we start the test + QCOMPARE(launcherModel->rowCount(), getASConfig().count()); + + for (int i = 0; i < launcherModel->rowCount(); i++) { + QString launcherAppId = launcherModel->get(i)->appId(); + QString asAppId = getASConfig().at(i).value("id").toString(); + QCOMPARE(launcherAppId, asAppId); + } + + launcherModel->move(0, 1); + + for (int i = 0; i < launcherModel->rowCount(); i++) { + QString launcherAppId = launcherModel->get(i)->appId(); + QString asAppId = getASConfig().at(i).value("id").toString(); + QCOMPARE(launcherAppId, asAppId); + } + } + + void testCountChangeSyncsToAS() { + // Find the index of the abs-icon app + int index = launcherModel->findApplication("abs-icon"); + + // Make sure it's invisible and 0 at the beginning + QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), false); + QCOMPARE(getASConfig().at(index).value("count").toInt(), 0); + + // Change the count of the abs-icon app through D-Bus + QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties"); + interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", QVariant::fromValue(QDBusVariant(55))); + interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(true))); + + // Make sure it changed to visible and 55 + QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), true); + QCOMPARE(getASConfig().at(index).value("count").toInt(), 55); + } }; QTEST_GUILESS_MAIN(LauncherModelTest) diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/CMakeLists.txt unity8-8.02+15.04.20150211/tests/qmltests/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/qmltests/CMakeLists.txt 2015-02-05 10:28:52.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/CMakeLists.txt 2015-02-11 17:11:40.000000000 +0000 @@ -25,7 +25,6 @@ add_qml_test(Components Carousel) add_qml_test(Components Dialogs) add_qml_test(Components DraggingArea) -add_qml_test(Components EdgeDemoOverlay) add_qml_test(Components LazyImage) add_qml_test(Components Lockscreen) add_qml_test(Components Rating) @@ -88,6 +87,8 @@ add_qml_test(Stages SpreadDelegate ENVIRONMENT) add_qml_test(Stages SurfaceContainer ENVIRONMENT) add_qml_test(Stages SessionContainer ENVIRONMENT) +add_qml_test(Stages TabletStage) add_qml_test(Stages WindowMoveResizeArea) add_qml_test(Stages Splash) +add_qml_test(Tutorial Tutorial) add_qml_test(Wizard Wizard) diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Components/tst_EdgeDemoOverlay.qml unity8-8.02+15.04.20150211/tests/qmltests/Components/tst_EdgeDemoOverlay.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Components/tst_EdgeDemoOverlay.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Components/tst_EdgeDemoOverlay.qml 1970-01-01 00:00:00.000000000 +0000 @@ -1,123 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.0 -import QtTest 1.0 -import Unity.Test 0.1 as UT -import "../../../qml/Components" - -Item { - id: root - width: boxWidth * 3 - height: boxHeight * 2 - - property int boxWidth: 250 - property int boxHeight: 250 - - EdgeDemoOverlay { - id: top - edge: "top" - title: "Top" - text: "Displayed on top left" - anchors.left: parent.left - anchors.top: parent.top - width: boxWidth - height: boxHeight - } - - EdgeDemoOverlay { - id: right - edge: "right" - title: "Right" - text: "Displayed on top right" - anchors.right: parent.right - anchors.top: parent.top - width: boxWidth - height: boxHeight - } - - EdgeDemoOverlay { - id: left - edge: "left" - title: "Left" - text: "Displayed on bottom right" - anchors.right: parent.right - anchors.bottom: parent.bottom - width: boxWidth - height: boxHeight - available: false - } - - EdgeDemoOverlay { - id: bottom - edge: "bottom" - title: "Bottom" - text: "Displayed on bottom left" - anchors.left: parent.left - anchors.bottom: parent.bottom - width: boxWidth - height: boxHeight - } - - EdgeDemoOverlay { - id: none - edge: "none" - title: "None" - text: "Displayed on top middle" - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - width: boxWidth - height: boxHeight - } - - SignalSpy { - id: signalSpy - } - - UT.UnityTestCase { - name: "EdgeDemoOverlay" - when: windowShown - - function test_animations() { - compare(right.running, true) - - compare(left.running, false) - left.available = true - compare(left.running, true) - } - - function test_skip() { - signalSpy.target = bottom - signalSpy.signalName = "skip" - signalSpy.clear() - var bottomSkip = findChild(bottom, "skipLabel") - mousePress(bottomSkip, 1, 1) - mouseRelease(bottomSkip, 1, 1) - signalSpy.wait() - compare(bottom.available, false) - - // Test that the 'none' edge skips anywhere - signalSpy.target = none - signalSpy.clear() - var backgroundShade = findChild(none, "backgroundShadeMouseArea") - tryCompare(backgroundShade, "enabled", true) - mousePress(backgroundShade, 1, 1) - mouseRelease(backgroundShade, 1, 1) - signalSpy.wait() - compare(none.available, false) - } - } -} diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Launcher/tst_Launcher.qml unity8-8.02+15.04.20150211/tests/qmltests/Launcher/tst_Launcher.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Launcher/tst_Launcher.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Launcher/tst_Launcher.qml 2015-02-11 17:11:20.000000000 +0000 @@ -15,6 +15,7 @@ */ import QtQuick 2.0 +import QtQuick.Layouts 1.1 import QtTest 1.0 import Unity.Test 0.1 as UT import Ubuntu.Components 1.1 @@ -74,10 +75,22 @@ } } - Button { + ColumnLayout { anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) } - text: "emit hinting signal" - onClicked: LauncherModel.emitHint() + spacing: units.gu(1) + width: units.gu(20) + + Button { + text: "emit hinting signal" + onClicked: LauncherModel.emitHint() + Layout.fillWidth: true + } + + Button { + text: "rotate" + onClicked: launcherLoader.item.inverted = !launcherLoader.item.inverted + Layout.fillWidth: true + } } SignalSpy { @@ -140,6 +153,18 @@ tryCompare(panel, "x", -panel.width, 1000); } + function positionLauncherListAtBeginning() { + var listView = testCase.findChild(launcherLoader.item, "launcherListView"); + listView.contentY = -listView.topMargin; + } + function positionLauncherListAtEnd() { + var listView = testCase.findChild(launcherLoader.item, "launcherListView"); + if ((listView.contentHeight + listView.topMargin + listView.bottomMargin) > listView.height) { + listView.contentY = listView.topMargin + listView.contentHeight + - listView.height; + } + } + // Drag from the left edge of the screen rightwards and check that the launcher // appears (as if being dragged by the finger/pointer) function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() { @@ -166,22 +191,22 @@ launcherApplicationSelected("[...]dialer-app.desktop") */ function test_clickingOnAppIconCausesSignalEmission() { dragLauncherIntoView(); - launcher.lastSelectedApplication = "" + launcher.lastSelectedApplication = ""; + launcher.inverted = false; - var listView = findChild(launcher, "launcherListView"); - listView.positionViewAtEnd(); + positionLauncherListAtBeginning(); - var appIcon = findChild(launcher, "launcherDelegate0") + var appIcon = findChild(launcher, "launcherDelegate0"); - verify(appIcon != undefined) + verify(appIcon != undefined); - mouseClick(appIcon) + mouseClick(appIcon); tryCompare(launcher, "lastSelectedApplication", - "dialer-app") + appIcon.appId); // Tapping on an application icon also dismisses the launcher - waitUntilLauncherDisappears() + waitUntilLauncherDisappears(); } /* If I click on the dash icon on the launcher @@ -277,25 +302,26 @@ function test_clickFlick_data() { var listView = findChild(launcher, "launcherListView"); return [ - {tag: "unfolded top", positionViewAtBeginning: false, + {tag: "unfolded top", positionViewAtBeginning: true, clickY: listView.topMargin + units.gu(2), expectFlick: false}, - {tag: "folded top", positionViewAtBeginning: true, + {tag: "folded top", positionViewAtBeginning: false, clickY: listView.topMargin + units.gu(2), expectFlick: true}, - {tag: "unfolded bottom", positionViewAtBeginning: true, + {tag: "unfolded bottom", positionViewAtBeginning: false, clickY: listView.height - listView.topMargin - units.gu(1), expectFlick: false}, - {tag: "folded bottom", positionViewAtBeginning: false, + {tag: "folded bottom", positionViewAtBeginning: true, clickY: listView.height - listView.topMargin - units.gu(1), expectFlick: true}, ]; } function test_clickFlick(data) { + launcher.inverted = false; launcher.lastSelectedApplication = ""; dragLauncherIntoView(); var listView = findChild(launcher, "launcherListView"); @@ -305,9 +331,9 @@ // So for stability's sake we just put the listView in the position // we want to to actually start doing what this tests intends to check. if (data.positionViewAtBeginning) { - listView.positionViewAtBeginning(); + positionLauncherListAtBeginning(); } else { - listView.positionViewAtEnd(); + positionLauncherListAtEnd(); } tryCompare(listView, "flicking", false); @@ -442,9 +468,9 @@ // Position launcher to where we need it var listView = findChild(launcher, "launcherListView"); if (data.flickTo == "top") { - listView.positionViewAtEnd(); + positionLauncherListAtBeginning(); } else { - listView.positionViewAtBeginning(); + positionLauncherListAtEnd(); } // Doing longpress @@ -454,6 +480,7 @@ verify(quickList.y >= units.gu(1)); verify(quickList.y + quickList.height + units.gu(1) <= launcher.height); + compare(quickList.width, units.gu(30)); // Click somewhere in the empty space to dismiss the quicklist mouseClick(launcher, launcher.width - units.gu(1), units.gu(1)); diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Notifications/Notification.qml unity8-8.02+15.04.20150211/tests/qmltests/Notifications/Notification.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Notifications/Notification.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Notifications/Notification.qml 2015-02-11 17:11:02.000000000 +0000 @@ -0,0 +1,31 @@ +/* + * Copyright 2015 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 . + * + * Authors: + * Mirco Mueller + */ + +import Unity.Notifications 1.0 + +Notification { + nid: 0 + type: Notification.PlaceHolder + summary: "" + body: "" + icon: "" + secondaryIcon: "" + value: 0 + rawActions: [] +} diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Notifications/tst_Notifications.qml unity8-8.02+15.04.20150211/tests/qmltests/Notifications/tst_Notifications.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Notifications/tst_Notifications.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Notifications/tst_Notifications.qml 2015-02-11 17:11:02.000000000 +0000 @@ -1,17 +1,20 @@ /* - * Copyright (C) 2013 Canonical, Ltd. + * Copyright 2015 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . + * + * Authors: + * Mirco Mueller */ import QtQuick 2.0 @@ -24,146 +27,134 @@ import QtMultimedia 5.0 Item { + id: foobar + width: notificationsRect.width + interactiveControls.width height: notificationsRect.height + property int index: 0 Row { id: rootRow - Component { - id: mockNotification - - QtObject { - function invokeAction(actionId) { - mockModel.actionInvoked(actionId) - } - } - } - - ListModel { + NotificationModel { id: mockModel - dynamicRoles: true - - signal actionInvoked(string actionId) - - function getRaw(id) { - return mockNotification.createObject(mockModel) - } // add the default/PlaceHolder notification to the model Component.onCompleted: { - var n = { - type: Notification.PlaceHolder, - hints: {}, - summary: "", - body: "", - icon: "", - secondaryIcon: "", - actions: [] - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.PlaceHolder, + "hints": {}, + "summary": "", + "body": "", + "icon": "", + "secondaryIcon": "", + "rawActions": []}) + n.completed.connect(mockModel.onCompleted) append(n) } } function add2over1SnapDecisionNotification() { - var n = { - type: Notification.SnapDecision, - hints: {"x-canonical-private-affirmative-tint": "true"}, - summary: "Theatre at Ferria Stadium", - body: "at Ferria Stadium in Bilbao, Spain\n07578545317", - icon: "", - secondaryIcon: "", - actions: [{ id: "ok_id", label: "Ok"}, - { id: "snooze_id", label: "Snooze"}, - { id: "view_id", label: "View"}] - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.SnapDecision, + "hints": {"x-canonical-private-affirmative-tint": "true"}, + "summary": "Theatre at Ferria Stadium", + "body": "at Ferria Stadium in Bilbao, Spain\n07578545317", + "icon": "", + "secondaryIcon": "", + "rawActions": ["ok_id", "Ok", + "snooze_id", "Snooze", + "view_id", "View"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } function addEphemeralNotification() { - var n = { - type: Notification.Ephemeral, - summary: "Cole Raby", - body: "I did not expect it to be that late.", - icon: "../graphics/avatars/amanda.png", - secondaryIcon: "../graphics/applicationIcons/facebook.png", - actions: [] - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.Ephemeral, + "hints": {}, + "summary": "Cole Raby", + "body": "I did not expect it to be that late.", + "icon": "../graphics/avatars/amanda.png", + "secondaryIcon": "../graphics/applicationIcons/facebook.png", + "rawActions": ["reply_id", "Dummy"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } function addEphemeralNonShapedIconNotification() { - var n = { - type: Notification.Ephemeral, - hints: {"x-canonical-non-shaped-icon": "true"}, - summary: "Contacts", - body: "Synchronised contacts-database with cloud-storage.", - icon: "../graphics/applicationIcons/contacts-app.png", - secondaryIcon: "", - actions: [] - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.Ephemeral, + "hints": {"x-canonical-non-shaped-icon": "true"}, + "summary": "Contacts", + "body": "Synchronised contacts-database with cloud-storage.", + "icon": "../graphics/applicationIcons/contacts-app.png", + "secondaryIcon": "", + "rawActions": ["reply_id", "Dummy"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } function addEphemeralIconSummaryNotification() { - var n = { - type: Notification.Ephemeral, - hints: {"x-canonical-non-shaped-icon": "false"}, - summary: "Photo upload completed", - body: "", - icon: "../graphics/applicationIcons/facebook.png", - secondaryIcon: "", - actions: [] - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.Ephemeral, + "hints": {"x-canonical-non-shaped-icon": "false"}, + "summary": "Photo upload completed", + "body": "", + "icon": "../graphics/applicationIcons/facebook.png", + "secondaryIcon": "", + "rawActions": ["reply_id", "Dummy"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } function addInteractiveNotification() { - var n = { - type: Notification.Interactive, - summary: "Interactive notification", - body: "This is a notification that can be clicked", - icon: "../graphics/avatars/anna_olsson.png", - secondaryIcon: "", - actions: [{ id: "reply_id", label: "Dummy"}], - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.Interactive, + "hints": {}, + "summary": "Interactive notification", + "body": "This is a notification that can be clicked", + "icon": "../graphics/avatars/anna_olsson.png", + "secondaryIcon": "", + "rawActions": ["reply_id", "Dummy"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } function addConfirmationNotification() { - var n = { - type: Notification.Confirmation, - hints: {"x-canonical-non-shaped-icon": "true"}, - summary: "Confirmation notification", - body: "", - icon: "image://theme/audio-volume-medium", - secondaryIcon: "", - value: 50, - actions: [], - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.Confirmation, + "hints": {"x-canonical-non-shaped-icon": "true"}, + "summary": "Confirmation notification", + "body": "", + "icon": "image://theme/audio-volume-medium", + "secondaryIcon": "", + "value": 50, + "rawActions": ["reply_id", "Dummy"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } function add2ndConfirmationNotification() { - var n = { - type: Notification.Confirmation, - hints: {"x-canonical-non-shaped-icon": "true", - "x-canonical-value-bar-tint": "true"}, - summary: "Confirmation notification", - body: "High Volume", - icon: "image://theme/audio-volume-high", - secondaryIcon: "", - value: 85, - actions: [], - } - + var component = Qt.createComponent("Notification.qml") + var n = component.createObject("notification", {"nid": index++, + "type": Notification.Confirmation, + "hints": {"x-canonical-non-shaped-icon": "true", + "x-canonical-value-bar-tint": "true"}, + "summary": "Confirmation notification", + "body": "High Volume", + "icon": "image://theme/audio-volume-high", + "secondaryIcon": "", + "value": 85, + "rawActions": ["reply_id", "Dummy"]}) + n.completed.connect(mockModel.onCompleted) mockModel.append(n) } @@ -174,8 +165,9 @@ } function remove1stNotification() { - if (mockModel.count > 1) - mockModel.remove(1) + if (mockModel.count > 1) { + mockModel.removeSecond() + } } Rectangle { @@ -273,41 +265,122 @@ name: "NotificationRendererTest" when: windowShown + property list nlist: [ + Notification { + nid: 1 + type: Notification.Ephemeral + summary: "Photo upload completed" + body: "" + icon: "../graphics/applicationIcons/facebook.png" + secondaryIcon: "" + value: 0 + rawActions: [] + }, + Notification { + nid: 2 + type: Notification.Ephemeral + hints: {"x-canonical-private-affirmative-tint": "false", + "sound-file": "dummy.ogg", + "suppress-sound": "true"} + summary: "New comment successfully published" + body: "" + icon: "" + secondaryIcon: "../graphics/applicationIcons/facebook.png" + value: 0 + rawActions: [] + }, + Notification { + nid: 3 + type: Notification.Interactive + hints: {"x-canonical-private-affirmative-tint": "false", + "sound-file": "dummy.ogg"} + summary: "Interactive notification" + body: "This is a notification that can be clicked" + icon: "../graphics/avatars/amanda.png" + secondaryIcon: "" + value: 0 + rawActions: ["reply_id", "Dummy"] + }, + Notification { + nid: 4 + type: Notification.SnapDecision + hints: {"x-canonical-private-affirmative-tint": "false", + "sound-file": "dummy.ogg"} + summary: "Bro Coly" + body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." + icon: "../graphics/avatars/anna_olsson.png" + secondaryIcon: "" + value: 0 + rawActions: ["accept_id", "Accept", + "reject_id", "Reject"] + }, + Notification { + nid: 5 + type: Notification.Ephemeral + hints: {"x-canonical-private-affirmative-tint": "false", + "sound-file": "dummy.ogg"} + summary: "Cole Raby" + body: "I did not expect it to be that late." + icon: "../graphics/avatars/funky.png" + secondaryIcon: "../graphics/applicationIcons/facebook.png" + value: 0 + rawActions: [] + }, + Notification { + nid: 6 + type: Notification.Ephemeral + hints: {"x-canonical-private-affirmative-tint": "false", + "x-canonical-non-shaped-icon": "true"} + summary: "Contacts" + body: "Synchronised contacts-database with cloud-storage." + icon: "image://theme/contacts-app" + secondaryIcon: "" + value: 0 + rawActions: [] + }, + Notification { + nid: 7 + type: Notification.Confirmation + hints: {"x-canonical-non-shaped-icon": "true"} + summary: "" + body: "" + icon: "image://theme/audio-volume-medium" + secondaryIcon: "" + value: 50 + rawActions: [] + }, + Notification { + nid: 8 + type: Notification.Confirmation + hints: {"x-canonical-non-shaped-icon": "true", + "x-canonical-value-bar-tint" : "true"} + summary: "" + body: "High Volume" + icon: "image://theme/audio-volume-high" + secondaryIcon: "" + value: 85 + rawActions: [] + }, + Notification { + nid: 9 + type: Notification.SnapDecision + hints: {"x-canonical-private-affirmative-tint": "true"} + summary: "Theatre at Ferria Stadium" + body: "at Ferria Stadium in Bilbao, Spain\n07578545317" + icon: "" + secondaryIcon: "" + value: 0 + rawActions: ["ok_id", "Ok", + "snooze_id", "Snooze", + "view_id", "View"] + } + ] + function test_NotificationRenderer_data() { return [ { - tag: "2-over-1 Snap Decision with button-tint", - type: Notification.SnapDecision, - hints: {"x-canonical-private-affirmative-tint": "true"}, - summary: "Theatre at Ferria Stadium", - body: "at Ferria Stadium in Bilbao, Spain\n07578545317", - icon: "", - secondaryIcon: "", - actions: [{ id: "ok_id", label: "Ok"}, - { id: "snooze_id", label: "Snooze"}, - { id: "view_id", label: "View"}], - summaryVisible: true, - bodyVisible: true, - iconVisible: false, - centeredIconVisible: false, - shaped: false, - secondaryIconVisible: false, - buttonRowVisible: false, - buttonTinted: true, - hasSound: false, - valueVisible: false, - valueLabelVisible: false, - valueTinted: false - }, - { tag: "Ephemeral notification - icon-summary layout", - type: Notification.Ephemeral, - hints: {}, - summary: "Photo upload completed", - body: "", - icon: "../graphics/applicationIcons/facebook.png", - secondaryIcon: "", - actions: [], + n: nlist[0], summaryVisible: true, bodyVisible: false, iconVisible: true, @@ -323,15 +396,7 @@ }, { tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout", - type: Notification.Ephemeral, - hints: {"x-canonical-private-affirmative-tint": "false", - "sound-file": "dummy.ogg", - "suppress-sound": "true"}, - summary: "New comment successfully published", - body: "", - icon: "", - secondaryIcon: "../graphics/applicationIcons/facebook.png", - actions: [], + n: nlist[1], summaryVisible: true, bodyVisible: false, interactiveAreaEnabled: false, @@ -348,14 +413,7 @@ }, { tag: "Interactive notification", - type: Notification.Interactive, - hints: {"x-canonical-private-affirmative-tint": "false", - "sound-file": "dummy.ogg"}, - summary: "Interactive notification", - body: "This is a notification that can be clicked", - icon: "../graphics/avatars/amanda.png", - secondaryIcon: "", - actions: [{ id: "reply_id", label: "Dummy"}], + n: nlist[2], summaryVisible: true, bodyVisible: true, iconVisible: true, @@ -371,15 +429,7 @@ }, { tag: "Snap Decision without secondary icon and no button-tint", - type: Notification.SnapDecision, - hints: {"x-canonical-private-affirmative-tint": "false", - "sound-file": "dummy.ogg"}, - summary: "Bro Coly", - body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.", - icon: "../graphics/avatars/anna_olsson.png", - secondaryIcon: "", - actions: [{ id: "accept_id", label: "Accept"}, - { id: "reject_id", label: "Reject"}], + n: nlist[3], summaryVisible: true, bodyVisible: true, iconVisible: true, @@ -395,14 +445,7 @@ }, { tag: "Ephemeral notification", - type: Notification.Ephemeral, - hints: {"x-canonical-private-affirmative-tint": "false", - "sound-file": "dummy.ogg"}, - summary: "Cole Raby", - body: "I did not expect it to be that late.", - icon: "../graphics/avatars/funky.png", - secondaryIcon: "../graphics/applicationIcons/facebook.png", - actions: [], + n: nlist[4], summaryVisible: true, bodyVisible: true, iconVisible: true, @@ -418,14 +461,7 @@ }, { tag: "Ephemeral notification with non-shaped icon", - type: Notification.Ephemeral, - hints: {"x-canonical-private-affirmative-tint": "false", - "x-canonical-non-shaped-icon": "true"}, - summary: "Contacts", - body: "Synchronised contacts-database with cloud-storage.", - icon: "image://theme/contacts-app", - secondaryIcon: "", - actions: [], + n: nlist[5], summaryVisible: true, bodyVisible: true, iconVisible: true, @@ -441,14 +477,7 @@ }, { tag: "Confirmation notification with value", - type: Notification.Confirmation, - hints: {"x-canonical-non-shaped-icon": "true"}, - summary: "", - body: "", - icon: "image://theme/audio-volume-medium", - secondaryIcon: "", - value: 50, - actions: [], + n: nlist[6], summaryVisible: false, bodyVisible: false, iconVisible: false, @@ -464,15 +493,7 @@ }, { tag: "Confirmation notification with value, label and tint", - type: Notification.Confirmation, - hints: {"x-canonical-non-shaped-icon": "true", - "x-canonical-value-bar-tint" : "true"}, - summary: "", - body: "High Volume", - icon: "image://theme/audio-volume-high", - secondaryIcon: "", - value: 85, - actions: [], + n: nlist[7], summaryVisible: false, bodyVisible: false, iconVisible: false, @@ -485,6 +506,22 @@ valueVisible: true, valueLabelVisible: true, valueTinted: true + }, + { + tag: "2-over-1 Snap Decision with button-tint", + n: nlist[8], + summaryVisible: true, + bodyVisible: true, + iconVisible: false, + centeredIconVisible: false, + shaped: false, + secondaryIconVisible: false, + buttonRowVisible: false, + buttonTinted: true, + hasSound: false, + valueVisible: false, + valueLabelVisible: false, + valueTinted: false } ] } @@ -509,8 +546,14 @@ } function test_NotificationRenderer(data) { + // make sure the clicks on mocked notifications can be checked against by "actionSpy" (mimicking the NotificationServer component) + data.n.actionInvoked.connect(mockModel.actionInvoked) + + // hook up notification's completed-signal with model's onCompleted-slot, so that remove() (model) happens on close() (notification) + data.n.completed.connect(mockModel.onCompleted) + // populate model with some mock notifications - mockModel.append(data) + mockModel.append(data.n) // make sure the view is properly updated before going on notifications.forceLayout(); @@ -548,9 +591,9 @@ // test input does not fall through mouseClick(notification) - if(data.type == Notification.Interactive) { + if(data.n.type === Notification.Interactive) { actionSpy.wait() - compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action") + compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id for interactive action") } compare(clickThroughSpy.count, 0, "click on notification fell through") @@ -559,17 +602,19 @@ compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect") compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect") - var audioItem = findInvisibleChild(notification, "sound") - compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state") + if (data.hasSound) { + var audioItem = findInvisibleChild(notification, "sound") + compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state") + } if(data.buttonRowVisible) { var buttonCancel = findChild(buttonRow, "notify_button1") var buttonAccept = findChild(buttonRow, "notify_button0") // only test the left/cancel-button if two actions have been passed in - if (data.actions.length == 2) { + if (data.n.actions.count === 2) { tryCompareFunction(function() { mouseClick(buttonCancel); return actionSpy.signalArguments.length > 0; }, true); - compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action") + compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action") actionSpy.clear() } @@ -578,36 +623,49 @@ // click the positive/right button tryCompareFunction(function() { mouseClick(buttonAccept); return actionSpy.signalArguments.length > 0; }, true); - compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action") + compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id positive action") actionSpy.clear() - waitForRendering (notification) // check if there's a ComboButton created due to more actions being passed - if (data.actions.length > 2) { + if (data.n.actions.count > 3) { var comboButton = findChild(notification, "notify_button2") - tryCompareFunction(function() { return comboButton.expanded == false; }, true); + tryCompareFunction(function() { return comboButton.expanded === false; }, true); // click to expand - tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true); + tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === true; }, true); // try clicking on choices in expanded comboList var choiceButton1 = findChild(notification, "notify_button3") tryCompareFunction(function() { mouseClick(choiceButton1); return actionSpy.signalArguments.length > 0; }, true); - compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1") + compare(actionSpy.signalArguments[0][0], data.n.actions.data(3, ActionModel.RoleActionId), "got wrong id choice action 1") actionSpy.clear() var choiceButton2 = findChild(notification, "notify_button4") tryCompareFunction(function() { mouseClick(choiceButton2); return actionSpy.signalArguments.length > 0; }, true); - compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2") + compare(actionSpy.signalArguments[0][0], data.n.actions.data(4, ActionModel.RoleActionId), "got wrong id choice action 2") actionSpy.clear() // click to collapse - //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true); + tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true); } else { mouseClick(buttonCancel) - compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action") + compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action") } } + + // swipe-to-dismiss check + waitForRendering(notification) + var before = mockModel.count + var dragStart = notification.width * 0.25; + var dragEnd = notification.width; + var dragY = notification.height / 2; + touchFlick(notification, dragStart, dragY, dragEnd, dragY) + waitForRendering(notification) + if ((data.n.type === Notification.SnapDecision && notification.state === "expanded") || data.n.type === Notification.Confirmation) { + tryCompare(mockModel, "count", before) + } else { + tryCompare(mockModel, "count", before - 1) + } } } } diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Notifications/tst_OptionToggle.qml unity8-8.02+15.04.20150211/tests/qmltests/Notifications/tst_OptionToggle.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Notifications/tst_OptionToggle.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Notifications/tst_OptionToggle.qml 2015-02-11 17:11:02.000000000 +0000 @@ -1,17 +1,20 @@ /* - * Copyright (C) 2014 Canonical, Ltd. + * Copyright 2015 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . + * + * Authors: + * Mirco Mueller */ import QtQuick 2.0 @@ -235,8 +238,10 @@ compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect") compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect") - var audioItem = findInvisibleChild(notification, "sound") - compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state") + if (data.hasSound) { + var audioItem = findInvisibleChild(notification, "sound") + compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state") + } if(data.buttonRowVisible) { var buttonCancel = findChild(buttonRow, "notify_button1") diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Notifications/tst_SwipeToAct.qml unity8-8.02+15.04.20150211/tests/qmltests/Notifications/tst_SwipeToAct.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Notifications/tst_SwipeToAct.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Notifications/tst_SwipeToAct.qml 2015-02-11 17:11:02.000000000 +0000 @@ -218,6 +218,10 @@ // populate model with some mock notifications mockModel.append(data) + // add actions to action-model to test against + myActionModel.append("ok_id", "Ok") + myActionModel.append("cancel_id", "Cancel") + // make sure the view is properly updated before going on notifications.forceLayout(); waitForRendering(notifications); diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Panel/tst_Panel.qml unity8-8.02+15.04.20150211/tests/qmltests/Panel/tst_Panel.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Panel/tst_Panel.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Panel/tst_Panel.qml 2015-02-11 17:10:32.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 Canonical Ltd. + * Copyright 2013-2015 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,7 +41,12 @@ Layout.fillHeight: true id: itemArea - color: "blue" + color: backgroundMouseArea.pressed ? "red" : "blue" + + MouseArea { + id: backgroundMouseArea + anchors.fill: parent + } Panel { id: panel @@ -128,6 +133,12 @@ name: "Panel" when: windowShown + SignalSpy { + id: backgroundPressedSpy + target: backgroundMouseArea + signalName: "pressedChanged" + } + function init() { panel.fullscreenMode = false; callManager.foregroundCall = null; @@ -140,6 +151,9 @@ // (switches between normal and fullscreen modes are animated) var indicatorArea = findChild(panel, "indicatorArea"); tryCompare(indicatorArea, "y", 0); + + backgroundPressedSpy.clear(); + compare(backgroundPressedSpy.valid, true); } function get_indicator_item(index) { @@ -299,5 +313,22 @@ touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2); } + + /* Checks that no input reaches items behind the indicator bar. + Ie., the indicator bar should eat all input events that hit it. + */ + function test_indicatorBarEatsAllEvents() { + // Perform several taps throughout the length of the indicator bar to ensure + // that it doesn't have a "weak spot" from where taps pass through. + var numTaps = 5; + var stepLength = (panel.width / (numTaps + 1)); + var tapY = panel.indicators.minimizedPanelHeight / 2; + for (var i = 1; i <= numTaps; ++i) { + tap(panel, stepLength * i, tapY); + tryCompare(panel.indicators, "fullyClosed", true); + } + + compare(backgroundPressedSpy.count, 0); + } } } diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/ApplicationCheckBox.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/ApplicationCheckBox.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/ApplicationCheckBox.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/ApplicationCheckBox.qml 2015-02-11 17:11:30.000000000 +0000 @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import Ubuntu.Components 1.1 +import Unity.Application 0.1 + +RowLayout { + id: root + property string appId + property alias checked: checkbox.checked + + Layout.fillWidth: true + CheckBox { + id: checkbox + checked: false + activeFocusOnPress: false + onCheckedChanged: { + if (checked) { + ApplicationManager.startApplication(root.appId); + } else { + ApplicationManager.stopApplication(root.appId); + } + } + } + Label { + text: root.appId + color: "white" + anchors.verticalCenter: parent.verticalCenter + } +} diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/RecursingChildSessionControl.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/RecursingChildSessionControl.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/RecursingChildSessionControl.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/RecursingChildSessionControl.qml 2015-02-11 17:11:30.000000000 +0000 @@ -89,6 +89,7 @@ CheckBox { id: _surfaceCheckbox; checked: false; + activeFocusOnPress: false enabled: root.session onCheckedChanged: { if (checked) { @@ -117,6 +118,7 @@ Button { enabled: root.session + activeFocusOnPress: false text: removable ? "Remove" : "Release" onClicked: { if (removable) { @@ -131,6 +133,7 @@ Button { enabled: root.session !== null + activeFocusOnPress: false text: "Add Child" onClicked: { var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1)); diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_ApplicationWindow.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_ApplicationWindow.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_ApplicationWindow.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_ApplicationWindow.qml 2015-02-11 17:11:30.000000000 +0000 @@ -52,10 +52,12 @@ application: fakeApplication orientation: Qt.PortraitOrientation interactive: true + focus: true } } Loader { id: applicationWindowLoader + focus: true anchors { top: parent.top bottom: parent.bottom @@ -83,6 +85,7 @@ CheckBox { id: sessionCheckbox; checked: false + activeFocusOnPress: false onCheckedChanged: { if (applicationWindowLoader.status !== Loader.Ready) return; @@ -121,6 +124,7 @@ ListItem.ItemSelector { id: appStateSelector + activeFocusOnPress: false anchors { left: parent.left; right: parent.right } text: "Application state" model: ["Starting", @@ -146,6 +150,7 @@ Button { anchors { left: parent.left; right: parent.right } + activeFocusOnPress: false text: "Rotate device \u27F3" onClicked: { var orientation = applicationWindowLoader.item.orientation @@ -398,7 +403,7 @@ verify(stateGroup.state === "void"); } - function test_forceActiveFocusFollowsInterative() { + function test_surfaceActiveFocusFollowsAppWindowInterative() { fakeApplication.createSession(); applicationWindowLoader.item.interactive = false; applicationWindowLoader.item.interactive = true; diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_PhoneStage.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_PhoneStage.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_PhoneStage.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_PhoneStage.qml 2015-02-11 17:11:30.000000000 +0000 @@ -29,6 +29,7 @@ PhoneStage { id: phoneStage anchors { fill: parent; rightMargin: units.gu(30) } + focus: true dragAreaWidth: units.gu(2) maximizedAppTopMargin: units.gu(3) + units.dp(2) interactive: true @@ -51,6 +52,7 @@ Button { anchors { left: parent.left; right: parent.right } text: "Add App" + activeFocusOnPress: false onClicked: { testCase.addApps(); } @@ -58,6 +60,7 @@ Button { anchors { left: parent.left; right: parent.right } text: "Remove Selected" + activeFocusOnPress: false onClicked: { ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId); } @@ -65,6 +68,7 @@ Button { anchors { left: parent.left; right: parent.right } text: "Stop Selected" + activeFocusOnPress: false onClicked: { ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped); } diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_SessionContainer.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_SessionContainer.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_SessionContainer.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_SessionContainer.qml 2015-02-11 17:11:30.000000000 +0000 @@ -42,11 +42,14 @@ id: sessionContainer anchors.fill: parent orientation: Qt.PortraitOrientation + focus: true + interactive: true } } Loader { id: sessionContainerLoader + focus: true anchors { top: parent.top bottom: parent.bottom @@ -75,6 +78,7 @@ CheckBox { id: sessionCheckbox; checked: false; + activeFocusOnPress: false onCheckedChanged: { if (sessionContainerLoader.status !== Loader.Ready) return; @@ -190,8 +194,6 @@ var sessionContainer = sessionContainerLoader.item; compare(sessionContainer.childSessions.count(), 0); - sessionContainer.interactive = true; - var i; var sessions = []; // 3 sessions should cover all edge cases @@ -209,14 +211,19 @@ var a_session; while(a_session = sessions.pop()) { - a_session.surface.forceActiveFocus(); compare(a_session.surface.activeFocus, true); - var parentSession = a_session.parentSession; - sessionContainer.session.removeChildSession(a_session); - compare(a_session.surface.activeFocus, false); + ApplicationTest.removeSurface(a_session.surface); + ApplicationTest.removeSession(a_session); - compare(parentSession.surface.activeFocus, true); + if (sessions.length > 0) { + // active focus should have gone to the yongest remaining sibling + var previousSiblingSurface = sessions[sessions.length - 1].surface + tryCompare(previousSiblingSurface, "activeFocus", true); + } else { + // active focus should have gone to the parent surface + tryCompare(sessionContainer.session.surface, "activeFocus", true); + } } } diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_TabletStage.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_TabletStage.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_TabletStage.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_TabletStage.qml 2015-02-11 17:11:30.000000000 +0000 @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.0 +import QtTest 1.0 +import Ubuntu.Components 1.1 +import Ubuntu.Components.ListItems 1.0 as ListItem +import Unity.Application 0.1 +import Unity.Test 0.1 + +import "../../../qml/Stages" + +Rectangle { + id: root + color: "grey" + width: tabletStageLoader.width + controls.width + height: tabletStageLoader.height + + Loader { + id: tabletStageLoader + + x: ((root.width - controls.width) - width) / 2 + y: (root.height - height) / 2 + width: units.gu(160*0.7) + height: units.gu(100*0.7) + + focus: true + + property bool itemDestroyed: false + sourceComponent: Component { + TabletStage { + anchors.fill: parent + Component.onDestruction: { + tabletStageLoader.itemDestroyed = true; + } + dragAreaWidth: units.gu(2) + maximizedAppTopMargin: units.gu(3) + units.dp(2) + interactive: true + focus: true + } + } + } + + Rectangle { + id: controls + color: "darkgrey" + width: units.gu(30) + anchors { + top: parent.top + bottom: parent.bottom + right: parent.right + } + + Column { + anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) } + spacing: units.gu(1) + ApplicationCheckBox { + id: webbrowserCheckBox + appId: "webbrowser-app" + } + ApplicationCheckBox { + id: dialerCheckBox + appId: "dialer-app" + } + } + } + + UnityTestCase { + id: testCase + name: "TabletStage" + when: windowShown + + property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null + + function init() { + tabletStageLoader.active = true; + tryCompare(tabletStageLoader, "status", Loader.Ready); + } + + function cleanup() { + tabletStageLoader.itemDestroyed = false; + tabletStageLoader.active = false; + + tryCompare(tabletStageLoader, "status", Loader.Null); + tryCompare(tabletStageLoader, "item", null); + // Loader.status might be Loader.Null and Loader.item might be null but the Loader + // actually took place. Likely because Loader waits until the next event loop + // iteration to do its work. So to ensure the reload, we will wait until the + // Shell instance gets destroyed. + tryCompare(tabletStageLoader, "itemDestroyed", true); + + // kill all (fake) running apps + webbrowserCheckBox.checked = false; + dialerCheckBox.checked = false; + } + + function waitUntilAppSurfaceShowsUp(appId) { + var appWindow = findChild(tabletStage, "appWindow_" + appId); + verify(appWindow); + var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup"); + verify(appWindowStates); + tryCompare(appWindowStates, "state", "surface"); + } + + function test_tappingSwitchesFocusBetweenStages() { + webbrowserCheckBox.checked = true; + waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId); + var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId); + compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage); + tryCompare(webbrowserApp.session.surface, "activeFocus", true); + + dialerCheckBox.checked = true; + waitUntilAppSurfaceShowsUp(dialerCheckBox.appId); + var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId); + compare(dialerApp.stage, ApplicationInfoInterface.SideStage); + tryCompare(dialerApp.session.surface, "activeFocus", true); + tryCompare(webbrowserApp.session.surface, "activeFocus", false); + + // Tap on the main stage application and check if the focus + // has been passed to it. + + var webbrowserWindow = findChild(tabletStage, "appWindow_" + webbrowserApp.appId); + verify(webbrowserWindow); + tap(webbrowserWindow); + + tryCompare(dialerApp.session.surface, "activeFocus", false); + tryCompare(webbrowserApp.session.surface, "activeFocus", true); + + // Now tap on the side stage application and check if the focus + // has been passed back to it. + + var dialerWindow = findChild(tabletStage, "appWindow_" + dialerApp.appId); + verify(dialerWindow); + tap(dialerWindow); + + tryCompare(dialerApp.session.surface, "activeFocus", true); + tryCompare(webbrowserApp.session.surface, "activeFocus", false); + } + } + +} diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_WindowMoveResizeArea.qml unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_WindowMoveResizeArea.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Stages/tst_WindowMoveResizeArea.qml 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Stages/tst_WindowMoveResizeArea.qml 2015-02-11 17:12:32.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. + * Copyright 2014-2015 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,20 +29,46 @@ height: units.gu(60) width: units.gu(60) - Rectangle { - id: fakeWindow - height: units.gu(20) - width: units.gu(20) - color: "khaki" - WindowMoveResizeArea { - id: moveResizeArea - target: fakeWindow - resizeHandleWidth: units.gu(0.5) - minWidth: units.gu(15) - minHeight: units.gu(10) + property var fakeWindow: windowLoader.item + + Component { + id: fakeWindowComponent + + Item { + id: fakeWindow + property alias minWidth: moveResizeArea.minWidth + property alias minHeight: moveResizeArea.minHeight + x: units.gu(20) + y: units.gu(20) + height: units.gu(20) + width: units.gu(20) + + WindowMoveResizeArea { + id: moveResizeArea + target: fakeWindow + resizeHandleWidth: units.gu(0.5) + minWidth: units.gu(15) + minHeight: units.gu(10) + windowId: "test-window-id" + } + + Rectangle { + anchors.fill: moveResizeArea + color: "red" + } + + Rectangle { + anchors.fill: fakeWindow + color: "blue" + } } } + Loader { + id: windowLoader + sourceComponent: fakeWindowComponent + } + UT.UnityTestCase { when: windowShown @@ -100,8 +126,8 @@ var startDragY = initialWindowY + initialWindowHeight + 1 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10); - tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, moveResizeArea.minWidth)); - tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, moveResizeArea.minHeight)); + tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, fakeWindow.minWidth)); + tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, fakeWindow.minHeight)); compare(fakeWindow.x, initialWindowX); compare(fakeWindow.y, initialWindowY); @@ -126,13 +152,86 @@ var startDragY = initialWindowY - 1 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10); - tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, moveResizeArea.minWidth)); - tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, moveResizeArea.minHeight)); + tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, fakeWindow.minWidth)); + tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, fakeWindow.minHeight)); - var maxMoveX = initialWindowWidth - moveResizeArea.minWidth; - var maxMoveY = initialWindowHeight - moveResizeArea.minHeight; + var maxMoveX = initialWindowWidth - fakeWindow.minWidth; + var maxMoveY = initialWindowHeight - fakeWindow.minHeight; compare(fakeWindow.x, Math.min(initialWindowX + data.dx, initialWindowX + maxMoveX)); compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY)); } + + function test_saveRestorePosition() { + var initialWindowX = fakeWindow.x; + var initialWindowY = fakeWindow.y; + var initialWindowWidth = fakeWindow.width; + var initialWindowHeight = fakeWindow.height; + + var moveDelta = units.gu(5); + var startDragX = initialWindowX + fakeWindow.width / 2; + var startDragY = initialWindowY + fakeWindow.height / 2; + mouseFlick(root, startDragX, startDragY, startDragX + moveDelta, startDragY + moveDelta, true, true, units.gu(.5), 10) + + tryCompare(fakeWindow, "x", initialWindowX + moveDelta) + tryCompare(fakeWindow, "y", initialWindowX + moveDelta) + + // This will destroy the window and recreate it + windowLoader.active = false; + waitForRendering(root); + windowLoader.active = true; + + // Make sure it's again where we left it before destroying + tryCompare(fakeWindow, "x", initialWindowX + moveDelta) + tryCompare(fakeWindow, "y", initialWindowX + moveDelta) + } + + function test_saveRestoreSize() { + var initialWindowX = fakeWindow.x; + var initialWindowY = fakeWindow.y; + var initialWindowWidth = fakeWindow.width + var initialWindowHeight = fakeWindow.height + + var resizeDelta = units.gu(5) + var startDragX = initialWindowX + initialWindowWidth + 1 + var startDragY = initialWindowY + initialWindowHeight + 1 + mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10); + + tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.minWidth)); + tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.minHeight)); + + // This will destroy the window and recreate it + windowLoader.active = false; + waitForRendering(root); + windowLoader.active = true; + + // Make sure its size is again the same as before + tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.minWidth)); + tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.minHeight)); + } + + // This tests if dragging smaller than minSize and then larger again, will keep the edge sticking + // to the mouse, instead of immediately making the window grow again when switching direction + function test_resizeSmallerAndLarger_data() { + return [ + { tag: "topLeft", startX: -1, startY: -1, dx: units.gu(15), dy: units.gu(15) }, + { tag: "bottomRight", startX: fakeWindow.width + 1, startY: fakeWindow.height + 1, dx: -units.gu(15), dy: -units.gu(15) } + ] + } + + function test_resizeSmallerAndLarger(data) { + var initialWindowX = fakeWindow.x; + var initialWindowY = fakeWindow.y; + var initialWindowWidth = fakeWindow.width + var initialWindowHeight = fakeWindow.height + + var startDragX = initialWindowX + data.startX + var startDragY = initialWindowY + data.startY + mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, false, units.gu(.05), 10); + tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - Math.abs(data.dx), fakeWindow.minWidth)); + tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - Math.abs(data.dy), fakeWindow.minHeight)); + mouseFlick(root, startDragX + data.dx, startDragY + data.dy, startDragX, startDragY, false, true, units.gu(.05), 10); + tryCompare(fakeWindow, "width", initialWindowWidth); + tryCompare(fakeWindow, "height", initialWindowHeight); + } } } diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/tst_Shell.qml unity8-8.02+15.04.20150211/tests/qmltests/tst_Shell.qml --- unity8-8.02+15.04.20150205/tests/qmltests/tst_Shell.qml 2015-02-05 10:28:52.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/tst_Shell.qml 2015-02-11 17:12:49.000000000 +0000 @@ -25,6 +25,7 @@ import Ubuntu.Telephony 0.1 as Telephony import Unity.Application 0.1 import Unity.Connectivity 0.1 +import Unity.Indicators 0.1 import Unity.Notifications 1.0 import Unity.Test 0.1 as UT import Powerd 0.1 @@ -63,6 +64,8 @@ anchors.fill: parent Loader { id: shellLoader + focus: true + active: false property bool itemDestroyed: false sourceComponent: Component { @@ -88,6 +91,7 @@ anchors { left: parent.left; right: parent.right } Button { text: "Show Greeter" + activeFocusOnPress: false onClicked: { if (shellLoader.status !== Loader.Ready) return; @@ -169,6 +173,7 @@ function init() { tryCompare(shell, "enabled", true); // enabled by greeter when ready + shell.indicatorProfile = "phone"; swipeAwayGreeter(); @@ -434,11 +439,11 @@ waitUntilTransitionsEnd(appWindowStateGroup); } - function test_surfaceLosesFocusWhilePanelIsOpen() { + function test_surfaceLosesActiveFocusWhilePanelIsOpen() { var app = ApplicationManager.startApplication("dialer-app"); waitUntilAppWindowIsFullyLoaded(app); - tryCompare(app.session.surface, "focus", true); + tryCompare(app.session.surface, "activeFocus", true); // Drag the indicators panel half-open var touchX = shell.width / 2; @@ -449,7 +454,7 @@ true /* beginTouch */, false /* endTouch */); verify(indicators.partiallyOpened); - tryCompare(app.session.surface, "focus", false); + tryCompare(app.session.surface, "activeFocus", false); // And finish getting it open touchFlick(indicators, @@ -458,11 +463,21 @@ false /* beginTouch */, true /* endTouch */); tryCompare(indicators, "fullyOpened", true); - tryCompare(app.session.surface, "focus", false); + tryCompare(app.session.surface, "activeFocus", false); dragToCloseIndicatorsPanel(); - tryCompare(app.session.surface, "focus", true); + tryCompare(app.session.surface, "activeFocus", true); + } + + function test_launchedAppHasActiveFocus() { + var dialerApp = ApplicationManager.startApplication("dialer-app"); + verify(dialerApp); + waitUntilAppSurfaceShowsUp("dialer-app") + + verify(dialerApp.session.surface); + + tryCompare(dialerApp.session.surface, "activeFocus", true); } // Wait for the whole UI to settle down @@ -477,6 +492,14 @@ waitForRendering(shell) } + function waitUntilAppSurfaceShowsUp(appId) { + var appWindow = findChild(shell, "appWindow_" + appId); + verify(appWindow); + var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup"); + verify(appWindowStates); + tryCompare(appWindowStates, "state", "surface"); + } + function dragToCloseIndicatorsPanel() { var indicators = findChild(shell, "indicators"); @@ -546,6 +569,33 @@ && itemRectInShell.y + itemRectInShell.height <= shell.height; } + function test_greeterDoesNotChangeIndicatorProfile() { + var panel = findChild(shell, "panel"); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile); + + LightDM.Greeter.showGreeter(); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile); + + LightDM.Greeter.hideGreeter(); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile); + } + + function test_shellProfileChangesReachIndicators() { + var panel = findChild(shell, "panel"); + + shell.indicatorProfile = "test1"; + for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) { + var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties); + verify(properties["menuObjectPath"].substr(-5), "test1"); + } + + shell.indicatorProfile = "test2"; + for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) { + var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties); + verify(properties["menuObjectPath"].substr(-5), "test2"); + } + } + function test_focusRequestedHidesGreeter() { var greeter = findChild(shell, "greeter"); diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/tst_ShellWithPin.qml unity8-8.02+15.04.20150211/tests/qmltests/tst_ShellWithPin.qml --- unity8-8.02+15.04.20150205/tests/qmltests/tst_ShellWithPin.qml 2015-02-05 10:29:33.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/tst_ShellWithPin.qml 2015-02-11 17:12:49.000000000 +0000 @@ -232,6 +232,22 @@ tryCompare(ApplicationManager, "focusedApplicationId", app) } + function test_greeterChangesIndicatorProfile() { + skip("Not supported yet, waiting on design for new settings panel"); + + var panel = findChild(shell, "panel"); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter"); + + LightDM.Greeter.hideGreeter(); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile); + + LightDM.Greeter.showGreeter(); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter"); + + LightDM.Greeter.hideGreeter(); + tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile); + } + function test_login() { sessionSpy.clear() tryCompare(sessionSpy, "count", 0) diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/tst_TabletShell.qml unity8-8.02+15.04.20150211/tests/qmltests/tst_TabletShell.qml --- unity8-8.02+15.04.20150205/tests/qmltests/tst_TabletShell.qml 2015-02-05 10:28:52.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/tst_TabletShell.qml 2015-02-11 17:11:40.000000000 +0000 @@ -303,8 +303,8 @@ selectUser(data.user) AccountsService.demoEdges = data.demo - var edgeDemo = findChild(shell, "edgeDemo") - tryCompare(edgeDemo, "running", data.demo) + var tutorial = findChild(shell, "tutorial"); + tryCompare(tutorial, "running", data.demo); swipeFromLeftEdge(shell.width * 0.75) wait(500) // to give time to handle dash() signal from Launcher diff -Nru unity8-8.02+15.04.20150205/tests/qmltests/Tutorial/tst_Tutorial.qml unity8-8.02+15.04.20150211/tests/qmltests/Tutorial/tst_Tutorial.qml --- unity8-8.02+15.04.20150205/tests/qmltests/Tutorial/tst_Tutorial.qml 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-11 17:12:05.000000000 +0000 @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2013,2014 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.0 +import QtTest 1.0 +import AccountsService 0.1 +import LightDM 0.1 as LightDM +import Ubuntu.Components 1.1 +import Unity.Application 0.1 +import Unity.Test 0.1 as UT + +import "../../../qml" + +Item { + id: root + width: shellLoader.width + buttons.width + height: shellLoader.height + + QtObject { + id: applicationArguments + + function hasGeometry() { + return false; + } + + function width() { + return 0; + } + + function height() { + return 0; + } + } + + Component.onCompleted: { + // must set the mock mode before loading the Shell + LightDM.Greeter.mockMode = "single-pin"; + LightDM.Users.mockMode = "single-pin"; + shellLoader.active = true; + } + + Row { + spacing: 0 + anchors.fill: parent + + Loader { + id: shellLoader + + active: false + width: units.gu(40) + height: units.gu(71) + + property bool itemDestroyed: false + sourceComponent: Component { + Shell { + property string indicatorProfile: "phone" + + Component.onDestruction: { + shellLoader.itemDestroyed = true; + } + } + } + } + + Rectangle { + id: buttons + color: "white" + width: units.gu(30) + height: shellLoader.height + + Column { + anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) } + spacing: units.gu(1) + Row { + anchors { left: parent.left; right: parent.right } + Button { + text: "Restart Tutorial" + onClicked: { + if (shellLoader.status !== Loader.Ready) + return; + + AccountsService.demoEdges = false; + AccountsService.demoEdges = true; + } + } + } + } + } + } + + UT.UnityTestCase { + id: testCase + name: "Tutorial" + when: windowShown + + property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null + property real halfWidth: shell ? shell.width / 2 : 0 + property real halfHeight: shell ? shell.height / 2 : 0 + + function init() { + tryCompare(shell, "enabled", true); // enabled by greeter when ready + swipeAwayGreeter(); + AccountsService.demoEdges = false; + AccountsService.demoEdges = true; + } + + function cleanup() { + shellLoader.itemDestroyed = false; + + shellLoader.active = false; + + tryCompare(shellLoader, "status", Loader.Null); + tryCompare(shellLoader, "item", null); + // Loader.status might be Loader.Null and Loader.item might be null but the Loader + // item might still be alive. So if we set Loader.active back to true + // again right now we will get the very same Shell instance back. So no reload + // actually took place. Likely because Loader waits until the next event loop + // iteration to do its work. So to ensure the reload, we will wait until the + // Shell instance gets destroyed. + tryCompare(shellLoader, "itemDestroyed", true); + + // kill all (fake) running apps + killApps(); + + // reload our test subject to get it in a fresh state once again + shellLoader.active = true; + + tryCompare(shellLoader, "status", Loader.Ready); + removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item); + } + + function killApps() { + while (ApplicationManager.count > 1) { + var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0 + ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId); + } + compare(ApplicationManager.count, 1) + } + + function swipeAwayGreeter() { + var greeter = findChild(shell, "greeter"); + tryCompare(greeter, "showProgress", 1); + + touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight); + + // wait until the animation has finished + tryCompare(greeter, "showProgress", 0); + waitForRendering(greeter); + } + + function waitForPage(name) { + waitForRendering(findChild(shell, name)); + var page = findChild(shell, name); + tryCompare(page, "shown", true); + tryCompare(page.showAnimation, "running", false); + return page; + } + + function checkTopEdge() { + touchFlick(shell, halfWidth, 0, halfWidth, halfHeight); + + var panel = findChild(shell, "panel"); + tryCompare(panel.indicators, "fullyClosed", true); + } + + function checkLeftEdge() { + touchFlick(shell, 0, halfHeight, halfWidth, halfHeight); + + var launcher = findChild(shell, "launcher"); + tryCompare(launcher, "state", ""); + } + + function checkRightEdge() { + touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight); + + var stage = findChild(shell, "stage"); + var spreadView = findChild(stage, "spreadView"); + tryCompare(spreadView, "phase", 0); + } + + function checkBottomEdge() { + // Can't actually check effect of swipe, since dash isn't really loaded + var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader"); + tryCompare(applicationsDisplayLoader.item, "interactive", false); + } + + function checkFinished() { + tryCompare(AccountsService, "demoEdges", false); + + var tutorial = findChild(shell, "tutorial"); + tryCompare(tutorial, "running", false); + + var launcher = findChild(shell, "launcher"); + tryCompare(launcher, "shown", false); + } + + function goToPage(name) { + var page = waitForPage("tutorialLeft"); + checkTopEdge(); + checkRightEdge(); + checkBottomEdge(); + if (name === "tutorialLeft") return page; + touchFlick(shell, 0, halfHeight, halfWidth, halfHeight); + + page = waitForPage("tutorialLeftFinish"); + if (name === "tutorialLeftFinish") return page; + var tick = findChild(page, "tick"); + tap(tick); + + page = waitForPage("tutorialRight"); + checkTopEdge(); + checkLeftEdge(); + checkBottomEdge(); + if (name === "tutorialRight") return page; + touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight); + var overlay = findChild(page, "overlay"); + tryCompare(overlay, "shown", true); + var tick = findChild(page, "tick"); + tap(tick); + + var page = waitForPage("tutorialBottom"); + checkTopEdge(); + checkLeftEdge(); + checkRightEdge(); + if (name === "tutorialBottom") return page; + touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight); + + var page = waitForPage("tutorialBottomFinish"); + checkTopEdge(); + checkLeftEdge(); + checkRightEdge(); + checkBottomEdge(); + if (name === "tutorialBottomFinish") return page; + var tick = findChild(page, "tick"); + tap(tick); + + checkFinished(); + return null; + } + + function test_walkthrough() { + goToPage(null); + } + + function test_launcherShortDrag() { + // goToPage does a normal launcher pull. But here we want to test + // just barely pulling the launcher out and letting go (i.e. not + // triggering the "progress" property of Launcher). + + var left = goToPage("tutorialLeft"); + + // Make sure we don't do anything if we don't pull the launcher + // out much. + var launcher = findChild(shell, "launcher"); + touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight); + tryCompare(launcher, "state", ""); // should remain hidden + tryCompare(left, "shown", true); // and we should still be on left + + // Now drag out but not past launcher itself + touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight); + + waitForPage("tutorialLeftFinish"); + } + + function test_launcherLongDrag() { + // goToPage does a normal launcher pull. But here we want to test + // a full pull across the page. + + var left = goToPage("tutorialLeft"); + + var launcher = findChild(shell, "launcher"); + touchFlick(shell, 0, halfHeight, shell.width, halfHeight); + + var errorTextLabel = findChild(left, "errorTextLabel"); + var errorTitleLabel = findChild(left, "errorTitleLabel"); + tryCompare(launcher, "state", ""); // launcher goes away + tryCompare(left, "shown", true); // still on left page + tryCompare(errorTextLabel, "opacity", 1); // show error + tryCompare(errorTitleLabel, "opacity", 1); // show error + } + + function test_launcherDragBack() { + // goToPage does a full launcher pull. But here we test pulling + // all the way out, then dragging back into place. + + var left = goToPage("tutorialLeft"); + touchFlick(shell, 0, halfHeight, halfWidth, halfHeight, true, false); + touchFlick(shell, halfWidth, halfHeight, 0, halfHeight, false, true); + + tryCompare(left, "shown", true); // and we should still be on left + } + + function test_spread() { + // Unfortunately, most of what we want to test of the spread is + // "did it render correctly?" but that's hard to test. So instead, + // just poke and prod it a little bit to see if some of the values + // we'd expect to be correct, are so. + + var right = goToPage("tutorialRight"); + var stage = findChild(right, "stage"); + var delegate0 = findChild(right, "appDelegate0"); + + tryCompare(stage, "dragProgress", 0); + touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false); + verify(stage.dragProgress > 0); + compare(stage.dragProgress, -delegate0.xTranslate); + touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true); + tryCompare(stage, "dragProgress", 0); + + tryCompare(delegate0, "x", shell.width); + + var screenshotImage = findChild(right, "screenshotImage"); + tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png")); + tryCompare(screenshotImage, "visible", true); + } + + function test_bottomShortDrag() { + var bottom = goToPage("tutorialBottom"); + + touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8); + + var errorTextLabel = findChild(bottom, "errorTextLabel"); + var errorTitleLabel = findChild(bottom, "errorTitleLabel"); + tryCompare(bottom, "shown", true); // still on bottom page + tryCompare(errorTextLabel, "opacity", 1); // show error + tryCompare(errorTitleLabel, "opacity", 1); // show error + } + + function test_interrupted() { + goToPage("tutorialLeft"); + ApplicationManager.startApplication("dialer-app"); + checkFinished(); + } + } +} diff -Nru unity8-8.02+15.04.20150205/tests/uqmlscene/ActiveFocusLogger.cpp unity8-8.02+15.04.20150211/tests/uqmlscene/ActiveFocusLogger.cpp --- unity8-8.02+15.04.20150205/tests/uqmlscene/ActiveFocusLogger.cpp 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/uqmlscene/ActiveFocusLogger.cpp 2015-02-11 17:11:30.000000000 +0000 @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ActiveFocusLogger.h" + +#include +#include + +void ActiveFocusLogger::setWindow(QQuickWindow *window) +{ + m_window = window; + QObject::connect(window, &QQuickWindow::activeFocusItemChanged, + this, &ActiveFocusLogger::printActiveFocusInfo); +} + +void ActiveFocusLogger::printActiveFocusInfo() +{ + if (!m_window) { + return; + } + + qDebug() << "============== Active focus info START ================"; + if (m_window->activeFocusItem()) { + qDebug() << m_window->activeFocusItem(); + qDebug() << "Ancestry:"; + QQuickItem *item = m_window->activeFocusItem()->parentItem(); + while (item != nullptr) { + qDebug() << item << ", isFocusScope =" << item->isFocusScope(); + item = item->parentItem(); + } + } else { + qDebug() << "NULL"; + } + qDebug() << "============== Active focus info END ================"; +} diff -Nru unity8-8.02+15.04.20150205/tests/uqmlscene/ActiveFocusLogger.h unity8-8.02+15.04.20150211/tests/uqmlscene/ActiveFocusLogger.h --- unity8-8.02+15.04.20150205/tests/uqmlscene/ActiveFocusLogger.h 1970-01-01 00:00:00.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/uqmlscene/ActiveFocusLogger.h 2015-02-11 17:11:30.000000000 +0000 @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ACTIVE_FOCUS_LOGGER_H +#define ACTIVE_FOCUS_LOGGER_H + +#include +#include +#include + +class ActiveFocusLogger : public QObject { + Q_OBJECT + +public: + void setWindow(QQuickWindow *window); + +private Q_SLOTS: + void printActiveFocusInfo(); + +private: + QPointer m_window; +}; + +#endif // ACTIVE_FOCUS_LOGGER_H diff -Nru unity8-8.02+15.04.20150205/tests/uqmlscene/CMakeLists.txt unity8-8.02+15.04.20150211/tests/uqmlscene/CMakeLists.txt --- unity8-8.02+15.04.20150205/tests/uqmlscene/CMakeLists.txt 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/uqmlscene/CMakeLists.txt 2015-02-11 17:11:30.000000000 +0000 @@ -1,5 +1,6 @@ add_executable(uqmlscene ${shellapplication_MOC_SRCS} + ActiveFocusLogger.cpp main.cpp ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp ) diff -Nru unity8-8.02+15.04.20150205/tests/uqmlscene/main.cpp unity8-8.02+15.04.20150211/tests/uqmlscene/main.cpp --- unity8-8.02+15.04.20150205/tests/uqmlscene/main.cpp 2015-02-05 10:28:16.000000000 +0000 +++ unity8-8.02+15.04.20150211/tests/uqmlscene/main.cpp 2015-02-11 17:11:30.000000000 +0000 @@ -68,6 +68,12 @@ // UbuntuGestures lib #include +#define UQMLSCENE_DEBUG_ACTIVE_FOCUS 0 + +#if UQMLSCENE_DEBUG_ACTIVE_FOCUS + #include "ActiveFocusLogger.h" +#endif + #ifdef QML_RUNTIME_TESTING class RenderStatistics { @@ -378,6 +384,10 @@ { Options options; + #if UQMLSCENE_DEBUG_ACTIVE_FOCUS + ActiveFocusLogger activeFocusLogger; + #endif + QStringList imports; QList > bundles; for (int i = 1; i < argc; ++i) { @@ -503,6 +513,9 @@ QQuickItem *contentItem = qobject_cast(topLevel); if (contentItem) { qxView = new QQuickView(&engine, nullptr); + #if UQMLSCENE_DEBUG_ACTIVE_FOCUS + activeFocusLogger.setWindow(qxView); + #endif TouchRegistry::instance()->setParent(qxView); qxView->installEventFilter(TouchRegistry::instance()); window = qxView;