on xenial help sign in
merged with trunk
commita4c01dd243af6dab663766b1cc93f16931ad0be8 authorNick Dedekind <nicholas.dedekind@gmail.com> dateWed 15 Jan 2014, 10:41 (13 years ago) parent602b91209de2ed
97 files changed+5169−1340
CMakeLists.txt+2 CODING+29 cmake/modules/autopilot.cmake+5−1 data/CMakeLists.txt+3−2 data/unity-scope-tool.desktop.in+8 debian/control+12−3 debian/unity-scope-tool.install+3 plugins/DashViews/CMakeLists.txt+2 plugins/DashViews/abstractjournal.cpp+388 plugins/DashViews/abstractjournal.h+145 plugins/DashViews/horizontaljournal.cpp+253 plugins/DashViews/horizontaljournal.h+67 plugins/DashViews/plugin.cpp+2 plugins/DashViews/verticaljournal.cpp+78−420 plugins/DashViews/verticaljournal.h+13−91 plugins/Utils/qsortfilterproxymodelqml.cpp+12 plugins/Utils/qsortfilterproxymodelqml.h+1 po/ca.po+7−7 po/ckb.po+158 po/cs.po+9−7 po/da.po+158 po/en_GB.po+28−26 po/es.po+4−4 po/fo.po+5−5 po/hi.po+158 po/hu.po+6−6 po/lv.po+6−6 po/mr.po+158 po/pl.po+7−7 po/pt.po+158 po/ru.po+11−8 po/sw.po+158 po/tr.po+6−4 po/vi.po+158 qml/Components/PageHeader.qml+13−37 qml/Components/PageHeaderLabel.qml+41 qml/Dash/Card.qml+83 qml/Dash/CardHeader.qml+112 qml/Dash/Dash.qml−18 qml/Dash/DashApps.qml−3 qml/Dash/DashBar.qml−128 qml/Dash/DashContent.qml+123−62 qml/Dash/DashContentOpenEffect.qml+57 qml/Dash/DashContentTabBarStyle.qml+21 qml/Dash/GenericScopeView.qml+28−228 qml/Dash/PreviewListView.qml+212 qml/Dash/graphics/tabbarchevron@27.png qml/Hud/HudParametrizedActionsPage.qml+1−1 qml/Panel/IndicatorItem.qml+6−12 qml/Panel/IndicatorRow.qml+40−8 qml/Panel/Indicators.qml+22−11 qml/Panel/MenuContent.qml+3−1 qml/Panel/Panel.qml+1−1 qml/ScopeTool.qml+175 src/CMakeLists.txt+12−1 tests/autopilot/unity8/indicators/__init__.py tests/autopilot/unity8/indicators/tests/__init__.py tests/autopilot/unity8/indicators/tests/test_indicators.py+50 tests/autopilot/unity8/shell/emulators/__init__.py+11−4 tests/autopilot/unity8/shell/emulators/create_interactive_notification.py+1−1 tests/autopilot/unity8/shell/emulators/dash.py+102−8 tests/autopilot/unity8/shell/emulators/greeter.py+2−5 tests/autopilot/unity8/shell/emulators/main_window.py+6 tests/autopilot/unity8/shell/tests/__init__.py+1 tests/autopilot/unity8/shell/tests/test_emulators.py+120 tests/mocks/LightDM/CMakeLists.txt+1−1 tests/mocks/Unity/fake_scopes.cpp+3 tests/mocks/Unity/fake_scopes.h+2−1 tests/plugins/DashViews/CMakeLists.txt+36−9 tests/plugins/DashViews/horizontaljournaltest.cpp+465 tests/plugins/DashViews/horizontaljournaltest.qml+43 tests/plugins/DashViews/horizontaljournaltry.cpp+90 tests/plugins/DashViews/horizontaljournaltry.qml+99 tests/plugins/DashViews/verticaljournaltest.cpp+5−5 tests/plugins/LightDM/CMakeLists.txt+1 tests/qmltests/CMakeLists.txt+3−2 tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qml+2−2 tests/qmltests/Dash/CardHelpers.js+75 tests/qmltests/Dash/artwork/avatar@12.png tests/qmltests/Dash/artwork/music-player-design.png tests/qmltests/Dash/qml/FakeScopeView.qml+1 tests/qmltests/Dash/tst_Card.qml+337 tests/qmltests/Dash/tst_CardHeader.qml+138 tests/qmltests/Dash/tst_Dash.qml+6−8 tests/qmltests/Dash/tst_DashBar.qml−111 tests/qmltests/Dash/tst_DashContent.qml+67−4 tests/qmltests/Dash/tst_GenericScopeView.qml+57−27 tests/qmltests/Greeter/tst_Lockscreen.qml+1−1 tests/qmltests/Panel/tst_IndicatorItem.qml−11 tests/qmltests/Panel/tst_IndicatorRow.qml+18−4 tests/qmltests/Panel/tst_Indicators.qml+1−1 tests/utils/modules/Unity/Test/UnityTestCase.qml+2−2 tools/CMakeLists.txt+11 tools/registry-tracker.cpp+116 tools/registry-tracker.h+44 tools/scopetool.cpp+84 unity8.qmlproject−25
CMakeLists.txt+2
@@ -96,6 +96,7 @@ include(autopilot)
9696declare_autopilot_test(shell unity8.shell ${CMAKE_SOURCE_DIR}/tests/autopilot/)
9797
9898set(SHELL_APP unity8)
99+set(SCOPE_TOOL unity-scope-tool)
99100
100101include_directories(
101102 ${CMAKE_SOURCE_DIR}/include
@@ -105,6 +106,7 @@ include_directories(
105106# add subdirectories to build
106107add_subdirectory(include)
107108add_subdirectory(src)
109+add_subdirectory(tools)
108110add_subdirectory(qml)
109111add_subdirectory(tests)
110112add_subdirectory(plugins)
CODING+29
@@ -166,3 +166,32 @@ $ make tryDragHandle
166166NB: Manual test or interation doesn't make sense for all components or classes,
167167particularly the non-graphical ones. In such cases no "tryFoo" make target is
168168provided.
169+
170+NB: The tests expect the locale to be the C locale, they may fail if run
171+under a different locale, if you get failures others can not reproduce
172+prepend 'LC_ALL=C' to the 'make test' command
173+
174+Running autopilot tests
175+=======================
176+
177+Autopilot tests are run from the build directory (which will be ./builddir if you used
178+the ./build script).
179+
180+$ make autopilot
181+
182+If you want you can also run tests individually, for that you need to install first,
183+otherwise the tests will run on the system-installed version
184+
185+$ make install
186+
187+To see the list of existing autopilot tests you can do
188+
189+$ PYTHONPATH=../tests/autopilot autopilot list unity8
190+
191+And then run a single test with
192+
193+$ PYTHONPATH=../tests/autopilot autopilot run unity8.some.very.long.test.name
194+
195+or run them all with
196+
197+$ PYTHONPATH=../tests/autopilot autopilot run unity8
cmake/modules/autopilot.cmake+5−1
@@ -4,7 +4,11 @@ function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
44 add_custom_target(autopilot-${TEST_NAME}
55 COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks autopilot run ${TEST_SUITE}
66 WORKING_DIRECTORY ${WORKING_DIR}
7− DEPENDS install
7+ DEPENDS fake_install
8+ )
9+
10+ add_custom_target(fake_install
11+ COMMAND cmake --build ${CMAKE_BINARY_DIR} --target install
812 )
913
1014 add_dependencies(autopilot autopilot-${TEST_NAME})
data/CMakeLists.txt+3−2
@@ -1,7 +1,8 @@
11# generate desktop file
22configure_file(${SHELL_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop @ONLY)
3+configure_file(${SCOPE_TOOL}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SCOPE_TOOL}.desktop @ONLY)
34
4−# install desktop file
5−install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop
5+# install desktop files
6+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop ${CMAKE_CURRENT_BINARY_DIR}/${SCOPE_TOOL}.desktop
67 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
78 )
data/unity-scope-tool.desktop.innew file+8
@@ -0,0 +1,8 @@
1+[Desktop Entry]
2+Type=Application
3+Name=Unity Scope Tool
4+Comment=Developer tool for Unity scopes.
5+Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/unity-scope-tool
6+Terminal=false
7+Icon=unity-scope-tool
8+X-Ubuntu-Touch=true
debian/control+12−3
@@ -53,23 +53,31 @@ Vcs-Browser: https://bazaar.launchpad.net/~unity-team/unity8/trunk
5353Package: indicators-client
5454Architecture: amd64 armhf i386
5555Depends: qmenumodel-qml (>= 0.2.7),
56− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 0.1.46+13.10.20130829.1-0ubuntu1),
56+ qtdeclarative5-ubuntu-ui-toolkit-plugin,
5757 unity8 (= ${binary:Version}),
5858 ${misc:Depends},
5959 ${shlibs:Depends},
6060Description: Indicators client test application
6161 This package contains the client application for indicators used by autopilot
6262
63+Package: unity-scope-tool
64+Architecture: any
65+Depends: unity8 (= ${binary:Version}),
66+ ${misc:Depends},
67+ ${shlibs:Depends},
68+Description: Development tool for Unity scopes
69+ This package contains the development tool for new generation Unity scopes, based
70+ on Unity8.
71+
6372Package: unity8
6473Architecture: any
6574Provides: indicator-renderer,
6675Depends: gsettings-desktop-schemas,
67− libunity-mir1 (>= 0.2),
6876 qmenumodel-qml (>= 0.2.7),
6977 qtdeclarative5-dee-plugin,
7078 qtdeclarative5-gsettings1.0,
7179 qtdeclarative5-ubuntu-settings-components,
72− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 0.1.46+13.10.20130829.1-0ubuntu1),
80+ qtdeclarative5-ubuntu-ui-toolkit-plugin,
7381 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
7482 qtdeclarative5-xmllistmodel-plugin,
7583 unity-launcher-impl-3,
@@ -97,6 +105,7 @@ Depends: gir1.2-glib-2.0,
97105 libqt5widgets5,
98106 python-evdev,
99107 python-gi,
108+ python-mock,
100109 unity8 (>= ${source:Version}),
101110 unity8-fake-env (>= ${source:Version}),
102111 ${misc:Depends},
debian/unity-scope-tool.installnew file+3
@@ -0,0 +1,3 @@
1+usr/bin/unity-scope-tool
2+usr/share/applications/unity-scope-tool.desktop
3+usr/share/unity8/ScopeTool.qml
plugins/DashViews/CMakeLists.txt+2
@@ -24,7 +24,9 @@ remove_definitions(-DQT_NO_KEYWORDS)
2424set(QMLPLUGIN_SRC
2525 plugin.cpp
2626 listviewwithpageheader.cpp
27+ abstractjournal.cpp
2728 verticaljournal.cpp
29+ horizontaljournal.cpp
2830 )
2931
3032add_library(DashViews-qml MODULE
plugins/DashViews/abstractjournal.cppnew file+388
@@ -0,0 +1,388 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "abstractjournal.h"
18+
19+static const qreal bufferRatio = 0.5;
20+
21+AbstractJournal::AbstractJournal()
22+ : m_delegateModel(nullptr)
23+ , m_asyncRequestedIndex(-1)
24+ , m_columnSpacing(0)
25+ , m_rowSpacing(0)
26+ , m_delegateCreationBegin(0)
27+ , m_delegateCreationEnd(0)
28+ , m_delegateCreationBeginValid(false)
29+ , m_delegateCreationEndValid(false)
30+ , m_needsRelayout(false)
31+ , m_delegateValidated(false)
32+ , m_implicitHeightDirty(false)
33+{
34+ connect(this, SIGNAL(widthChanged()), this, SLOT(relayout()));
35+ connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
36+}
37+
38+QAbstractItemModel *AbstractJournal::model() const
39+{
40+ return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
41+}
42+
43+void AbstractJournal::setModel(QAbstractItemModel *model)
44+{
45+ if (model != this->model()) {
46+ if (!m_delegateModel) {
47+ createDelegateModel();
48+ } else {
49+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
50+ disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
51+ }
52+ m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
53+ connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
54+#else
55+ disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
56+ }
57+ m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
58+ connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
59+#endif
60+
61+ cleanupExistingItems();
62+
63+ Q_EMIT modelChanged();
64+ polish();
65+ }
66+}
67+
68+QQmlComponent *AbstractJournal::delegate() const
69+{
70+ return m_delegateModel ? m_delegateModel->delegate() : nullptr;
71+}
72+
73+void AbstractJournal::setDelegate(QQmlComponent *delegate)
74+{
75+ if (delegate != this->delegate()) {
76+ if (!m_delegateModel) {
77+ createDelegateModel();
78+ }
79+
80+ cleanupExistingItems();
81+
82+ m_delegateModel->setDelegate(delegate);
83+
84+ Q_EMIT delegateChanged();
85+ m_delegateValidated = false;
86+ polish();
87+ }
88+}
89+
90+qreal AbstractJournal::columnSpacing() const
91+{
92+ return m_columnSpacing;
93+}
94+
95+void AbstractJournal::setColumnSpacing(qreal columnSpacing)
96+{
97+ if (columnSpacing != m_columnSpacing) {
98+ m_columnSpacing = columnSpacing;
99+ Q_EMIT columnSpacingChanged();
100+
101+ if (isComponentComplete()) {
102+ relayout();
103+ }
104+ }
105+}
106+
107+qreal AbstractJournal::rowSpacing() const
108+{
109+ return m_rowSpacing;
110+}
111+
112+void AbstractJournal::setRowSpacing(qreal rowSpacing)
113+{
114+ if (rowSpacing != m_rowSpacing) {
115+ m_rowSpacing = rowSpacing;
116+ Q_EMIT rowSpacingChanged();
117+
118+ if (isComponentComplete()) {
119+ relayout();
120+ }
121+ }
122+}
123+
124+qreal AbstractJournal::delegateCreationBegin() const
125+{
126+ return m_delegateCreationBegin;
127+}
128+
129+void AbstractJournal::setDelegateCreationBegin(qreal begin)
130+{
131+ m_delegateCreationBeginValid = true;
132+ if (m_delegateCreationBegin == begin)
133+ return;
134+ m_delegateCreationBegin = begin;
135+ if (isComponentComplete()) {
136+ polish();
137+ }
138+ emit delegateCreationBeginChanged();
139+}
140+
141+void AbstractJournal::resetDelegateCreationBegin()
142+{
143+ m_delegateCreationBeginValid = false;
144+ if (m_delegateCreationBegin == 0)
145+ return;
146+ m_delegateCreationBegin = 0;
147+ if (isComponentComplete()) {
148+ polish();
149+ }
150+ emit delegateCreationBeginChanged();
151+}
152+
153+qreal AbstractJournal::delegateCreationEnd() const
154+{
155+ return m_delegateCreationEnd;
156+}
157+
158+void AbstractJournal::setDelegateCreationEnd(qreal end)
159+{
160+ m_delegateCreationEndValid = true;
161+ if (m_delegateCreationEnd == end)
162+ return;
163+ m_delegateCreationEnd = end;
164+ if (isComponentComplete()) {
165+ polish();
166+ }
167+ emit delegateCreationEndChanged();
168+}
169+
170+void AbstractJournal::resetDelegateCreationEnd()
171+{
172+ m_delegateCreationEndValid = false;
173+ if (m_delegateCreationEnd == 0)
174+ return;
175+ m_delegateCreationEnd = 0;
176+ if (isComponentComplete()) {
177+ polish();
178+ }
179+ emit delegateCreationEndChanged();
180+}
181+
182+void AbstractJournal::createDelegateModel()
183+{
184+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
185+ m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
186+ connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
187+#else
188+ m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
189+ connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
190+#endif
191+ if (isComponentComplete())
192+ m_delegateModel->componentComplete();
193+}
194+
195+void AbstractJournal::refill()
196+{
197+ if (!isComponentComplete()) {
198+ return;
199+ }
200+
201+ const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
202+ const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
203+ const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
204+ const qreal buffer = (to - from) * bufferRatio;
205+ const qreal bufferFrom = from - buffer;
206+ const qreal bufferTo = to + buffer;
207+
208+ bool added = addVisibleItems(from, to, false);
209+ bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
210+ added |= addVisibleItems(bufferFrom, bufferTo, true);
211+
212+ if (added || removed) {
213+ m_implicitHeightDirty = true;
214+ polish();
215+ }
216+}
217+
218+bool AbstractJournal::addVisibleItems(qreal fillFromY, qreal fillToY, bool asynchronous)
219+{
220+ if (!delegate())
221+ return false;
222+
223+ if (m_delegateModel->count() == 0)
224+ return false;
225+
226+ int modelIndex;
227+ qreal yPos;
228+ findBottomModelIndexToAdd(&modelIndex, &yPos);
229+ bool changed = false;
230+ while (modelIndex < m_delegateModel->count() && yPos <= fillToY) {
231+ if (!createItem(modelIndex, asynchronous))
232+ break;
233+
234+ changed = true;
235+ findBottomModelIndexToAdd(&modelIndex, &yPos);
236+ }
237+
238+ findTopModelIndexToAdd(&modelIndex, &yPos);
239+ while (modelIndex >= 0 && yPos > fillFromY) {
240+ if (!createItem(modelIndex, asynchronous))
241+ break;
242+
243+ changed = true;
244+ findTopModelIndexToAdd(&modelIndex, &yPos);
245+ }
246+
247+ return changed;
248+}
249+
250+QQuickItem *AbstractJournal::createItem(int modelIndex, bool asynchronous)
251+{
252+ if (asynchronous && m_asyncRequestedIndex != -1)
253+ return nullptr;
254+
255+ m_asyncRequestedIndex = -1;
256+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
257+ QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
258+#else
259+ QObject* object = m_delegateModel->object(modelIndex, asynchronous);
260+ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
261+#endif
262+ if (!item) {
263+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
264+ m_asyncRequestedIndex = modelIndex;
265+#else
266+ if (object) {
267+ m_delegateModel->release(object);
268+ if (!m_delegateValidated) {
269+ m_delegateValidated = true;
270+ QObject* delegateObj = delegate();
271+ qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
272+ }
273+ } else {
274+ m_asyncRequestedIndex = modelIndex;
275+ }
276+#endif
277+ return nullptr;
278+ } else {
279+ positionItem(modelIndex, item);
280+ return item;
281+ }
282+}
283+
284+void AbstractJournal::releaseItem(QQuickItem *item)
285+{
286+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
287+ QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
288+ if (flags & QQuickVisualModel::Destroyed) {
289+#else
290+ QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
291+ if (flags & QQmlDelegateModel::Destroyed) {
292+#endif
293+ item->setParentItem(nullptr);
294+ }
295+}
296+
297+void AbstractJournal::setImplicitHeightDirty()
298+{
299+ m_implicitHeightDirty = true;
300+}
301+
302+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
303+void AbstractJournal::itemCreated(int modelIndex, QQuickItem *item)
304+{
305+#else
306+void AbstractJournal::itemCreated(int modelIndex, QObject *object)
307+{
308+ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
309+ if (!item) {
310+ qWarning() << "AbstractJournal::itemCreated got a non item for index" << modelIndex;
311+ return;
312+ }
313+#endif
314+ item->setParentItem(this);
315+
316+ // We only need to call createItem if we are here because of an asynchronous generation
317+ // otherwise we are in this slot because createItem is creating the item sync
318+ // and thus we don't need to call createItem again, nor need to set m_implicitHeightDirty
319+ // and call polish because the sync createItem was called from addVisibleItems that
320+ // is called from refill that will already do those if an item was added
321+ if (modelIndex == m_asyncRequestedIndex) {
322+ createItem(modelIndex, false);
323+ m_implicitHeightDirty = true;
324+ polish();
325+ }
326+}
327+
328+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
329+void AbstractJournal::onModelUpdated(const QQuickChangeSet &changeSet, bool reset)
330+#else
331+void AbstractJournal::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
332+#endif
333+{
334+ if (reset) {
335+ cleanupExistingItems();
336+ } else {
337+ processModelRemoves(changeSet.removes());
338+ }
339+ polish();
340+}
341+
342+
343+void AbstractJournal::relayout()
344+{
345+ m_needsRelayout = true;
346+ polish();
347+}
348+
349+void AbstractJournal::onHeightChanged()
350+{
351+ polish();
352+}
353+
354+void AbstractJournal::updatePolish()
355+{
356+ if (!model())
357+ return;
358+
359+ if (m_needsRelayout) {
360+ doRelayout();
361+ m_needsRelayout = false;
362+ m_implicitHeightDirty = true;
363+ }
364+
365+ refill();
366+
367+ const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
368+ const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
369+ const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
370+ updateItemCulling(from, to);
371+
372+ if (m_implicitHeightDirty) {
373+ calculateImplicitHeight();
374+ m_implicitHeightDirty = false;
375+ }
376+}
377+
378+void AbstractJournal::componentComplete()
379+{
380+ if (m_delegateModel)
381+ m_delegateModel->componentComplete();
382+
383+ QQuickItem::componentComplete();
384+
385+ m_needsRelayout = true;
386+
387+ polish();
388+}
plugins/DashViews/abstractjournal.hnew file+145
@@ -0,0 +1,145 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef ABSTRACTJOURNAL_H
18+#define ABSTRACTJOURNAL_H
19+
20+#include <QQuickItem>
21+
22+class QAbstractItemModel;
23+class QQmlComponent;
24+#pragma GCC diagnostic push
25+#pragma GCC diagnostic ignored "-pedantic"
26+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
27+#include <private/qquickvisualdatamodel_p.h>
28+#else
29+#include <private/qqmldelegatemodel_p.h>
30+#include <qqmlinfo.h>
31+#endif
32+#pragma GCC diagnostic pop
33+
34+class AbstractJournal : public QQuickItem
35+{
36+ Q_OBJECT
37+
38+ Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
39+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
40+ Q_PROPERTY(qreal columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged)
41+ Q_PROPERTY(qreal rowSpacing READ rowSpacing WRITE setRowSpacing NOTIFY rowSpacingChanged)
42+ Q_PROPERTY(qreal delegateCreationBegin READ delegateCreationBegin
43+ WRITE setDelegateCreationBegin
44+ NOTIFY delegateCreationBeginChanged
45+ RESET resetDelegateCreationBegin)
46+ Q_PROPERTY(qreal delegateCreationEnd READ delegateCreationEnd
47+ WRITE setDelegateCreationEnd
48+ NOTIFY delegateCreationEndChanged
49+ RESET resetDelegateCreationEnd)
50+
51+friend class VerticalJournalTest;
52+friend class HorizontalJournalTest;
53+
54+public:
55+ AbstractJournal();
56+
57+ QAbstractItemModel *model() const;
58+ void setModel(QAbstractItemModel *model);
59+
60+ QQmlComponent *delegate() const;
61+ void setDelegate(QQmlComponent *delegate);
62+
63+ qreal columnSpacing() const;
64+ void setColumnSpacing(qreal columnSpacing);
65+
66+ qreal rowSpacing() const;
67+ void setRowSpacing(qreal rowSpacing);
68+
69+ qreal delegateCreationBegin() const;
70+ void setDelegateCreationBegin(qreal);
71+ void resetDelegateCreationBegin();
72+
73+ qreal delegateCreationEnd() const;
74+ void setDelegateCreationEnd(qreal);
75+ void resetDelegateCreationEnd();
76+
77+Q_SIGNALS:
78+ void modelChanged();
79+ void delegateChanged();
80+ void columnSpacingChanged();
81+ void rowSpacingChanged();
82+ void delegateCreationBeginChanged();
83+ void delegateCreationEndChanged();
84+
85+protected Q_SLOTS:
86+ void relayout();
87+
88+protected:
89+ void updatePolish() override;
90+ void componentComplete() override;
91+
92+ void releaseItem(QQuickItem *item);
93+ void setImplicitHeightDirty();
94+
95+private Q_SLOTS:
96+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
97+ void itemCreated(int modelIndex, QQuickItem *item);
98+ void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
99+#else
100+ void itemCreated(int modelIndex, QObject *object);
101+ void onModelUpdated(const QQmlChangeSet &changeSet, bool reset);
102+#endif
103+ void onHeightChanged();
104+
105+private:
106+ void createDelegateModel();
107+ void refill();
108+ bool addVisibleItems(qreal fillFromY, qreal fillToY, bool asynchronous);
109+ QQuickItem *createItem(int modelIndex, bool asynchronous);
110+
111+ virtual void findBottomModelIndexToAdd(int *modelIndex, qreal *yPos) = 0;
112+ virtual void findTopModelIndexToAdd(int *modelIndex, qreal *yPos) = 0;
113+ virtual void positionItem(int modelIndex, QQuickItem *item) = 0;
114+ virtual bool removeNonVisibleItems(qreal bufferFromY, qreal bufferToY) = 0;
115+ virtual void cleanupExistingItems() = 0;
116+ virtual void doRelayout() = 0;
117+ virtual void updateItemCulling(qreal visibleFromY, qreal visibleToY) = 0;
118+ virtual void calculateImplicitHeight() = 0;
119+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
120+ virtual void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) = 0;
121+#else
122+ virtual void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) = 0;
123+#endif
124+
125+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
126+ QQuickVisualDataModel *m_delegateModel;
127+#else
128+ QQmlDelegateModel *m_delegateModel;
129+#endif
130+
131+ // Index we are waiting because we requested it asynchronously
132+ int m_asyncRequestedIndex;
133+
134+ int m_columnSpacing;
135+ int m_rowSpacing;
136+ qreal m_delegateCreationBegin;
137+ qreal m_delegateCreationEnd;
138+ bool m_delegateCreationBeginValid;
139+ bool m_delegateCreationEndValid;
140+ bool m_needsRelayout;
141+ bool m_delegateValidated;
142+ bool m_implicitHeightDirty;
143+};
144+
145+#endif
plugins/DashViews/horizontaljournal.cppnew file+253
@@ -0,0 +1,253 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+/*
18+ * The implementation is centered around m_visibleItems
19+ * that a list for each of the items in the view.
20+ * m_firstVisibleIndex is the index of the first item in m_visibleItems
21+ * m_lastInRowIndexPosition is a map that contains the x position
22+ * of items that are the last ones of a row so we can reconstruct the rows
23+ * when building back
24+ */
25+
26+#include "horizontaljournal.h"
27+
28+#include <qqmlengine.h>
29+#pragma GCC diagnostic push
30+#pragma GCC diagnostic ignored "-pedantic"
31+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
32+#include <private/qquickvisualdatamodel_p.h>
33+#else
34+#include <private/qqmldelegatemodel_p.h>
35+#include <qqmlinfo.h>
36+#endif
37+#include <private/qquickitem_p.h>
38+#pragma GCC diagnostic pop
39+
40+static const qreal bufferRatio = 0.5;
41+
42+HorizontalJournal::HorizontalJournal()
43+ : m_firstVisibleIndex(-1)
44+ , m_rowHeight(0)
45+{
46+}
47+
48+qreal HorizontalJournal::rowHeight() const
49+{
50+ return m_rowHeight;
51+}
52+
53+void HorizontalJournal::setRowHeight(qreal rowHeight)
54+{
55+ if (rowHeight != m_rowHeight) {
56+ m_rowHeight = rowHeight;
57+ Q_EMIT rowHeightChanged();
58+
59+ if (isComponentComplete()) {
60+ Q_FOREACH(QQuickItem *item, m_visibleItems) {
61+ item->setHeight(rowHeight);
62+ }
63+ relayout();
64+ }
65+ }
66+}
67+
68+void HorizontalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
69+{
70+ if (m_visibleItems.isEmpty()) {
71+ *modelIndex = 0;
72+ *yPos = 0;
73+ } else {
74+ *modelIndex = m_firstVisibleIndex + m_visibleItems.count();
75+ if (m_lastInRowIndexPosition.contains(*modelIndex - 1)) {
76+ *yPos = m_visibleItems.last()->y() + m_rowHeight + rowSpacing();
77+ } else {
78+ *yPos = m_visibleItems.last()->y();
79+ }
80+ }
81+}
82+
83+void HorizontalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
84+{
85+ if (m_visibleItems.isEmpty()) {
86+ *modelIndex = -1;
87+ *yPos = INT_MIN;
88+ } else {
89+ *modelIndex = m_firstVisibleIndex - 1;
90+ if (m_lastInRowIndexPosition.contains(*modelIndex)) {
91+ *yPos = m_visibleItems.first()->y() - rowSpacing() - m_rowHeight;
92+ } else {
93+ *yPos = m_visibleItems.first()->y();
94+ }
95+ }
96+}
97+
98+bool HorizontalJournal::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
99+{
100+ bool changed = false;
101+
102+ while (!m_visibleItems.isEmpty() && m_visibleItems.first()->y() + m_rowHeight < bufferFromY) {
103+ releaseItem(m_visibleItems.takeFirst());
104+ changed = true;
105+ m_firstVisibleIndex++;
106+ }
107+
108+ while (!m_visibleItems.isEmpty() && m_visibleItems.last()->y() > bufferToY) {
109+ releaseItem(m_visibleItems.takeLast());
110+ changed = true;
111+ m_lastInRowIndexPosition.remove(m_firstVisibleIndex + m_visibleItems.count());
112+ }
113+
114+ if (m_visibleItems.isEmpty()) {
115+ m_firstVisibleIndex = -1;
116+ }
117+
118+ return changed;
119+}
120+
121+void HorizontalJournal::positionItem(int modelIndex, QQuickItem *item)
122+{
123+ if (item->height() != m_rowHeight) {
124+ qWarning() << "Item" << modelIndex << "height is not the one that the rowHeight mandates, resetting it";
125+ item->setHeight(m_rowHeight);
126+ }
127+
128+ if (m_visibleItems.isEmpty()) {
129+ Q_ASSERT(modelIndex == 0);
130+ item->setY(0);
131+ item->setX(0);
132+ m_visibleItems << item;
133+ m_firstVisibleIndex = 0;
134+ } else {
135+ // modelIndex has to be either m_firstVisibleIndex - 1 or m_firstVisibleIndex + m_visibleItems.count()
136+ if (modelIndex == m_firstVisibleIndex + m_visibleItems.count()) {
137+ QQuickItem *lastItem = m_visibleItems.last();
138+ if (lastItem->x() + lastItem->width() + columnSpacing() + item->width() <= width()) {
139+ // Fits in the row
140+ item->setY(lastItem->y());
141+ item->setX(lastItem->x() + lastItem->width() + columnSpacing());
142+ } else {
143+ // Starts a new row
144+ item->setY(lastItem->y() + m_rowHeight + rowSpacing());
145+ item->setX(0);
146+ m_lastInRowIndexPosition[modelIndex - 1] = lastItem->x();
147+ }
148+ m_visibleItems << item;
149+ } else if (modelIndex == m_firstVisibleIndex - 1) {
150+ QQuickItem *firstItem = m_visibleItems.first();
151+ if (m_lastInRowIndexPosition.contains(modelIndex)) {
152+ // It is the last item of its row, so start a new one since we're going back
153+ item->setY(firstItem->y() - rowSpacing() - m_rowHeight);
154+ item->setX(m_lastInRowIndexPosition[modelIndex]);
155+ } else {
156+ item->setY(firstItem->y());
157+ item->setX(firstItem->x() - columnSpacing() - item->width());
158+ }
159+ m_firstVisibleIndex = modelIndex;
160+ m_visibleItems.prepend(item);
161+ } else {
162+ qWarning() << "HorizontalJournal::positionItem - Got unexpected modelIndex"
163+ << modelIndex << m_firstVisibleIndex << m_visibleItems.count();
164+ }
165+ }
166+}
167+
168+void HorizontalJournal::cleanupExistingItems()
169+{
170+ // Cleanup the existing items
171+ Q_FOREACH(QQuickItem *item, m_visibleItems)
172+ releaseItem(item);
173+ m_visibleItems.clear();
174+ m_lastInRowIndexPosition.clear();
175+ m_firstVisibleIndex = -1;
176+}
177+
178+void HorizontalJournal::calculateImplicitHeight()
179+{
180+ if (m_firstVisibleIndex >= 0) {
181+ const int nIndexes = m_firstVisibleIndex + m_visibleItems.count();
182+ const double bottomMostY = m_visibleItems.last()->y() + m_rowHeight;
183+ const double averageHeight = bottomMostY / nIndexes;
184+ setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - nIndexes));
185+ } else {
186+ setImplicitHeight(0);
187+ }
188+}
189+
190+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
191+void HorizontalJournal::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
192+#else
193+void HorizontalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
194+#endif
195+{
196+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
197+ Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
198+#else
199+ Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
200+#endif
201+ for (int i = remove.count - 1; i >= 0; --i) {
202+ const int indexToRemove = remove.index + i;
203+ // We only support removing from the end so
204+ // any of the last items of a column has to be indexToRemove
205+ const int lastIndex = m_firstVisibleIndex + m_visibleItems.count() - 1;
206+ if (indexToRemove == lastIndex) {
207+ releaseItem(m_visibleItems.takeLast());
208+ m_lastInRowIndexPosition.remove(indexToRemove);
209+ } else {
210+ if (indexToRemove < lastIndex) {
211+ qFatal("HorizontalJournal only supports removal from the end of the model");
212+ } else {
213+ setImplicitHeightDirty();
214+ }
215+ }
216+ }
217+ }
218+ if (m_visibleItems.isEmpty()) {
219+ m_firstVisibleIndex = -1;
220+ }
221+}
222+
223+
224+void HorizontalJournal::doRelayout()
225+{
226+ // If m_firstVisibleIndex is not 0 we need to drop all the delegates
227+ // since we can't consistently relayout without the first item being there
228+
229+ if (m_firstVisibleIndex == 0) {
230+ int i = 0;
231+ const QList<QQuickItem*> allItems = m_visibleItems;
232+ m_visibleItems.clear();
233+ m_lastInRowIndexPosition.clear();
234+ Q_FOREACH(QQuickItem *item, allItems) {
235+ positionItem(i, item);
236+ ++i;
237+ }
238+ } else {
239+ Q_FOREACH(QQuickItem *item, m_visibleItems) {
240+ releaseItem(item);
241+ }
242+ m_visibleItems.clear();
243+ m_lastInRowIndexPosition.clear();
244+ m_firstVisibleIndex = -1;
245+ }
246+}
247+
248+void HorizontalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
249+{
250+ Q_FOREACH(QQuickItem *item, m_visibleItems) {
251+ QQuickItemPrivate::get(item)->setCulled(item->y() + m_rowHeight <= visibleFromY || item->y() >= visibleToY);
252+ }
253+}
plugins/DashViews/horizontaljournal.hnew file+67
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef HORIZONTALJOURNAL_H
18+#define HORIZONTALJOURNAL_H
19+
20+#include "abstractjournal.h"
21+
22+ /** A horizontal journal is a view that creates delegates
23+ * based on a model and layouts them one after the other
24+ * in the same row until there is no more free space for the next item
25+ * and so that item is layouted in the next row
26+ *
27+ * All delegates are forced to rowHeight if they don't have it.
28+ */
29+ class HorizontalJournal : public AbstractJournal
30+{
31+ Q_OBJECT
32+
33+ Q_PROPERTY(qreal rowHeight READ rowHeight WRITE setRowHeight NOTIFY rowHeightChanged)
34+
35+friend class HorizontalJournalTest;
36+
37+public:
38+ HorizontalJournal();
39+
40+ qreal rowHeight() const;
41+ void setRowHeight(qreal rowHeight);
42+
43+Q_SIGNALS:
44+ void rowHeightChanged();
45+
46+private:
47+ void findBottomModelIndexToAdd(int *modelIndex, qreal *yPos) override;
48+ void findTopModelIndexToAdd(int *modelIndex, qreal *yPos) override;
49+ bool removeNonVisibleItems(qreal bufferFromY, qreal bufferToY) override;
50+ void positionItem(int modelIndex, QQuickItem *item) override;
51+ void cleanupExistingItems() override;
52+ void calculateImplicitHeight() override;
53+ void doRelayout() override;
54+ void updateItemCulling(qreal visibleFromY, qreal visibleToY) override;
55+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
56+ void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) override;
57+#else
58+ void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) override;
59+#endif
60+
61+ int m_firstVisibleIndex;
62+ QList<QQuickItem*> m_visibleItems;
63+ QMap<int, double> m_lastInRowIndexPosition;
64+ int m_rowHeight;
65+};
66+
67+#endif
plugins/DashViews/plugin.cpp+2
@@ -17,6 +17,7 @@
1717
1818#include "plugin.h"
1919
20+#include "horizontaljournal.h"
2021#include "listviewwithpageheader.h"
2122#include "verticaljournal.h"
2223
@@ -26,6 +27,7 @@ void DashViewsPlugin::registerTypes(const char *uri)
2627{
2728 Q_ASSERT(uri == QLatin1String("DashViews"));
2829 qmlRegisterType<QAbstractItemModel>();
30+ qmlRegisterType<HorizontalJournal>(uri, 0, 1, "HorizontalJournal");
2931 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
3032 qmlRegisterType<VerticalJournal>(uri, 0, 1, "VerticalJournal");
3133}
plugins/DashViews/verticaljournal.cpp+78−420
@@ -27,88 +27,16 @@
2727 */
2828#include "verticaljournal.h"
2929
30−#include <qqmlengine.h>
3130#pragma GCC diagnostic push
3231#pragma GCC diagnostic ignored "-pedantic"
33−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
34−#include <private/qquickvisualdatamodel_p.h>
35−#else
36−#include <private/qqmldelegatemodel_p.h>
37−#include <qqmlinfo.h>
38−#endif
3932#include <private/qquickitem_p.h>
4033#pragma GCC diagnostic pop
4134
4235static const qreal bufferRatio = 0.5;
4336
4437VerticalJournal::VerticalJournal()
45− : m_delegateModel(nullptr)
46− , m_asyncRequestedIndex(-1)
47− , m_columnWidth(0)
48− , m_columnSpacing(0)
49− , m_rowSpacing(0)
50− , m_delegateCreationBegin(0)
51− , m_delegateCreationEnd(0)
52− , m_delegateCreationBeginValid(false)
53− , m_delegateCreationEndValid(false)
54− , m_needsRelayout(false)
55− , m_delegateValidated(false)
56− , m_implicitHeightDirty(false)
57−{
58− connect(this, SIGNAL(widthChanged()), this, SLOT(relayout()));
59− connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
60−}
61−
62−QAbstractItemModel *VerticalJournal::model() const
38+ : m_columnWidth(0)
6339{
64− return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
65−}
66−
67−void VerticalJournal::setModel(QAbstractItemModel *model)
68−{
69− if (model != this->model()) {
70− if (!m_delegateModel) {
71− createDelegateModel();
72− } else {
73−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
74− disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
75− }
76− m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
77− connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
78−#else
79− disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
80− }
81− m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
82− connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
83−#endif
84−
85− cleanupExistingItems();
86−
87− Q_EMIT modelChanged();
88− polish();
89− }
90−}
91−
92−QQmlComponent *VerticalJournal::delegate() const
93−{
94− return m_delegateModel ? m_delegateModel->delegate() : nullptr;
95−}
96−
97−void VerticalJournal::setDelegate(QQmlComponent *delegate)
98−{
99− if (delegate != this->delegate()) {
100− if (!m_delegateModel) {
101− createDelegateModel();
102− }
103−
104− cleanupExistingItems();
105−
106− m_delegateModel->setDelegate(delegate);
107−
108− Q_EMIT delegateChanged();
109− m_delegateValidated = false;
110− polish();
111− }
11240}
11341
11442qreal VerticalJournal::columnWidth() const
@@ -133,133 +61,6 @@ void VerticalJournal::setColumnWidth(qreal columnWidth)
13361 }
13462}
13563
136−qreal VerticalJournal::columnSpacing() const
137−{
138− return m_columnSpacing;
139−}
140−
141−void VerticalJournal::setColumnSpacing(qreal columnSpacing)
142−{
143− if (columnSpacing != m_columnSpacing) {
144− m_columnSpacing = columnSpacing;
145− Q_EMIT columnSpacingChanged();
146−
147− if (isComponentComplete()) {
148− relayout();
149− }
150− }
151−}
152−
153−qreal VerticalJournal::rowSpacing() const
154−{
155− return m_rowSpacing;
156−}
157−
158−void VerticalJournal::setRowSpacing(qreal rowSpacing)
159−{
160− if (rowSpacing != m_rowSpacing) {
161− m_rowSpacing = rowSpacing;
162− Q_EMIT rowSpacingChanged();
163−
164− if (isComponentComplete()) {
165− relayout();
166− }
167− }
168−}
169−
170−qreal VerticalJournal::delegateCreationBegin() const
171−{
172− return m_delegateCreationBegin;
173−}
174−
175−void VerticalJournal::setDelegateCreationBegin(qreal begin)
176−{
177− m_delegateCreationBeginValid = true;
178− if (m_delegateCreationBegin == begin)
179− return;
180− m_delegateCreationBegin = begin;
181− if (isComponentComplete()) {
182− polish();
183− }
184− emit delegateCreationBeginChanged();
185−}
186−
187−void VerticalJournal::resetDelegateCreationBegin()
188−{
189− m_delegateCreationBeginValid = false;
190− if (m_delegateCreationBegin == 0)
191− return;
192− m_delegateCreationBegin = 0;
193− if (isComponentComplete()) {
194− polish();
195− }
196− emit delegateCreationBeginChanged();
197−}
198−
199−qreal VerticalJournal::delegateCreationEnd() const
200−{
201− return m_delegateCreationEnd;
202−}
203−
204−void VerticalJournal::setDelegateCreationEnd(qreal end)
205−{
206− m_delegateCreationEndValid = true;
207− if (m_delegateCreationEnd == end)
208− return;
209− m_delegateCreationEnd = end;
210− if (isComponentComplete()) {
211− polish();
212− }
213− emit delegateCreationEndChanged();
214−}
215−
216−void VerticalJournal::resetDelegateCreationEnd()
217−{
218− m_delegateCreationEndValid = false;
219− if (m_delegateCreationEnd == 0)
220− return;
221− m_delegateCreationEnd = 0;
222− if (isComponentComplete()) {
223− polish();
224− }
225− emit delegateCreationEndChanged();
226−}
227−
228−void VerticalJournal::createDelegateModel()
229−{
230−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
231− m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
232− connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
233−#else
234− m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
235− connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
236−#endif
237− if (isComponentComplete())
238− m_delegateModel->componentComplete();
239−}
240−
241−void VerticalJournal::refill()
242−{
243− if (!isComponentComplete()) {
244− return;
245− }
246−
247− const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
248− const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
249− const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
250− const qreal buffer = (to - from) * bufferRatio;
251− const qreal bufferFrom = from - buffer;
252− const qreal bufferTo = to + buffer;
253−
254− bool added = addVisibleItems(from, to, false);
255− bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
256− added |= addVisibleItems(bufferFrom, bufferTo, true);
257−
258− if (added || removed) {
259− m_implicitHeightDirty = true;
260− }
261−}
262−
26364void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
26465{
26566 *modelIndex = 0;
@@ -268,7 +69,7 @@ void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
26869 Q_FOREACH(const auto &column, m_columnVisibleItems) {
26970 if (!column.isEmpty()) {
27071 const ViewItem &item = column.last();
271− *yPos = qMin(*yPos, item.y() + item.height() + m_rowSpacing);
72+ *yPos = qMin(*yPos, item.y() + item.height() + rowSpacing());
27273 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
27374 } else {
27475 *yPos = 0;
@@ -287,7 +88,7 @@ void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
28788 const auto &column = m_columnVisibleItems[i];
28889 if (!column.isEmpty()) {
28990 const ViewItem &item = column.first();
290− const auto itemTopPos = item.y() - m_rowSpacing;
91+ const auto itemTopPos = item.y() - rowSpacing();
29192 if (itemTopPos > *yPos) {
29293 *yPos = itemTopPos;
29394 *modelIndex = item.m_modelIndex - 1;
@@ -309,51 +110,19 @@ void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
309110 }
310111}
311112
312−bool VerticalJournal::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
313−{
314− if (!delegate())
315− return false;
316−
317− if (m_delegateModel->count() == 0)
318− return false;
319−
320− int modelIndex;
321− qreal yPos;
322− findBottomModelIndexToAdd(&modelIndex, &yPos);
323− bool changed = false;
324− while (modelIndex < m_delegateModel->count() && yPos <= fillTo) {
325− if (!createItem(modelIndex, asynchronous))
326− break;
327−
328− changed = true;
329− findBottomModelIndexToAdd(&modelIndex, &yPos);
330− }
331−
332− findTopModelIndexToAdd(&modelIndex, &yPos);
333− while (modelIndex >= 0 && yPos > fillFrom) {
334− if (!createItem(modelIndex, asynchronous))
335− break;
336−
337− changed = true;
338− findTopModelIndexToAdd(&modelIndex, &yPos);
339− }
340−
341− return changed;
342−}
343−
344−bool VerticalJournal::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
113+bool VerticalJournal::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
345114{
346115 bool changed = false;
347116
348117 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
349118 QList<ViewItem> &column = m_columnVisibleItems[i];
350− while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFrom) {
351− releaseItem(column.takeFirst());
119+ while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFromY) {
120+ releaseItem(column.takeFirst().m_item);
352121 changed = true;
353122 }
354123
355− while (!column.isEmpty() && column.last().y() > bufferTo) {
356− releaseItem(column.takeLast());
124+ while (!column.isEmpty() && column.last().y() > bufferToY) {
125+ releaseItem(column.takeLast().m_item);
357126 changed = true;
358127 }
359128 }
@@ -361,53 +130,20 @@ bool VerticalJournal::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
361130 return changed;
362131}
363132
364−QQuickItem *VerticalJournal::createItem(int modelIndex, bool asynchronous)
133+void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
365134{
366− if (asynchronous && m_asyncRequestedIndex != -1)
367− return nullptr;
368−
369− m_asyncRequestedIndex = -1;
370−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
371− QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
372−#else
373− QObject* object = m_delegateModel->object(modelIndex, asynchronous);
374− QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
375−#endif
376− if (!item) {
377−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
378− m_asyncRequestedIndex = modelIndex;
379−#else
380− if (object) {
381− m_delegateModel->release(object);
382− if (!m_delegateValidated) {
383− m_delegateValidated = true;
384− QObject* delegateObj = delegate();
385− qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
386− }
387− } else {
388− m_asyncRequestedIndex = modelIndex;
389− }
390−#endif
391− return nullptr;
392− } else {
393− if (item->width() != m_columnWidth) {
394− qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
395− item->setWidth(m_columnWidth);
396− }
397− positionItem(modelIndex, item);
398− return item;
135+ if (item->width() != m_columnWidth) {
136+ qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
137+ item->setWidth(m_columnWidth);
399138 }
400−}
401139
402−void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
403−{
404140 // Check if we add it to the bottom of existing column items
405141 const QList<ViewItem> &firstColumn = m_columnVisibleItems[0];
406− qreal columnToAddY = !firstColumn.isEmpty() ? firstColumn.last().y() + firstColumn.last().height() : -m_rowSpacing;
142+ qreal columnToAddY = !firstColumn.isEmpty() ? firstColumn.last().y() + firstColumn.last().height() : -rowSpacing();
407143 int columnToAddTo = 0;
408144 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
409145 const QList<ViewItem> &column = m_columnVisibleItems[i];
410− const qreal iY = !column.isEmpty() ? column.last().y() + column.last().height() : -m_rowSpacing;
146+ const qreal iY = !column.isEmpty() ? column.last().y() + column.last().height() : -rowSpacing();
411147 if (iY < columnToAddY) {
412148 columnToAddTo = i;
413149 columnToAddY = iY;
@@ -416,8 +152,8 @@ void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
416152
417153 const QList<ViewItem> &columnToAdd = m_columnVisibleItems[columnToAddTo];
418154 if (columnToAdd.isEmpty() || columnToAdd.last().m_modelIndex < modelIndex) {
419− item->setX(columnToAddTo * (m_columnWidth + m_columnSpacing));
420− item->setY(columnToAddY + m_rowSpacing);
155+ item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
156+ item->setY(columnToAddY + rowSpacing());
421157
422158 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
423159 m_indexColumnMap[modelIndex] = columnToAddTo;
@@ -426,8 +162,8 @@ void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
426162 columnToAddTo = m_indexColumnMap[modelIndex];
427163 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
428164
429− item->setX(columnToAddTo * (m_columnWidth + m_columnSpacing));
430− item->setY(columnToAddY - m_rowSpacing - item->height());
165+ item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
166+ item->setY(columnToAddY - rowSpacing() - item->height());
431167
432168 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
433169 }
@@ -439,29 +175,14 @@ void VerticalJournal::cleanupExistingItems()
439175 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
440176 QList<ViewItem> &column = m_columnVisibleItems[i];
441177 Q_FOREACH(const ViewItem &item, column)
442− releaseItem(item);
178+ releaseItem(item.m_item);
443179 column.clear();
444180 }
445181 m_indexColumnMap.clear();
446182}
447183
448−void VerticalJournal::releaseItem(const ViewItem &item)
449−{
450−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
451− QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
452− if (flags & QQuickVisualModel::Destroyed) {
453−#else
454− QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
455− if (flags & QQmlDelegateModel::Destroyed) {
456−#endif
457− item.m_item->setParentItem(nullptr);
458− }
459−}
460−
461184void VerticalJournal::calculateImplicitHeight()
462185{
463− m_implicitHeightDirty = false;
464−
465186 int lastModelIndex = -1;
466187 qreal bottomMostY = 0;
467188 Q_FOREACH(const auto &column, m_columnVisibleItems) {
@@ -479,144 +200,81 @@ void VerticalJournal::calculateImplicitHeight()
479200 }
480201}
481202
482−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
483−void VerticalJournal::itemCreated(int modelIndex, QQuickItem *item)
484−{
485−#else
486−void VerticalJournal::itemCreated(int modelIndex, QObject *object)
203+void VerticalJournal::doRelayout()
487204{
488− QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
489− if (!item) {
490− qWarning() << "VerticalJournal::itemCreated got a non item for index" << modelIndex;
491− return;
205+ QList<ViewItem> allItems;
206+ Q_FOREACH(const auto &column, m_columnVisibleItems)
207+ allItems << column;
208+
209+ qSort(allItems);
210+
211+ const int nColumns = qMax(1., floor((double)(width() + columnSpacing()) / (m_columnWidth + columnSpacing())));
212+ m_columnVisibleItems.resize(nColumns);
213+ m_indexColumnMap.clear();
214+ for (int i = 0; i < nColumns; ++i)
215+ m_columnVisibleItems[i].clear();
216+
217+ // If the first of allItems doesn't contain index 0 we need to drop them
218+ // all since we can't consistently relayout without the first item being there
219+
220+ if (!allItems.isEmpty()) {
221+ if (allItems.first().m_modelIndex == 0) {
222+ Q_FOREACH(const ViewItem &item, allItems)
223+ positionItem(item.m_modelIndex, item.m_item);
224+ } else {
225+ Q_FOREACH(const ViewItem &item, allItems)
226+ releaseItem(item.m_item);
227+ }
492228 }
493−#endif
494− item->setParentItem(this);
495− if (modelIndex == m_asyncRequestedIndex) {
496− createItem(modelIndex, false);
497− m_implicitHeightDirty = true;
498− polish();
229+}
230+
231+void VerticalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
232+{
233+ Q_FOREACH(const auto &column, m_columnVisibleItems) {
234+ Q_FOREACH(const ViewItem &item, column) {
235+ const bool cull = item.y() + item.height() <= visibleFromY || item.y() >= visibleToY;
236+ QQuickItemPrivate::get(item.m_item)->setCulled(cull);
237+ }
499238 }
500239}
501240
502241#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
503−void VerticalJournal::onModelUpdated(const QQuickChangeSet &changeSet, bool reset)
242+void VerticalJournal::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
504243#else
505−void VerticalJournal::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
244+void VerticalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
506245#endif
507246{
508− if (reset) {
509− cleanupExistingItems();
510− } else {
511247#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
512− Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
248+ Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
513249#else
514− Q_FOREACH(const QQmlChangeSet::Remove &remove, changeSet.removes()) {
250+ Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
515251#endif
516− for (int i = remove.count - 1; i >= 0; --i) {
517− const int indexToRemove = remove.index + i;
518− // Since we only support removing from the end, indexToRemove
519− // must refer to the last item of one of the columns or
520− // be bigger than them (because it's not in the viewport and
521− // thus we have not created a delegate for it)
522− bool found = false;
523− int lastCreatedIndex = INT_MIN;
524− for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
525− QList<ViewItem> &column = m_columnVisibleItems[i];
526− if (!column.isEmpty()) {
527− const int lastColumnIndex = column.last().m_modelIndex;
528− if (lastColumnIndex == indexToRemove) {
529− releaseItem(column.takeLast());
530− found = true;
531− }
532− lastCreatedIndex = qMax(lastCreatedIndex, lastColumnIndex);
533− }
534− }
535− if (!found) {
536− if (indexToRemove < lastCreatedIndex) {
537− qFatal("VerticalJournal only supports removal from the end of the model");
538− } else {
539− m_implicitHeightDirty = true;
252+ for (int i = remove.count - 1; i >= 0; --i) {
253+ const int indexToRemove = remove.index + i;
254+ // Since we only support removing from the end, indexToRemove
255+ // must refer to the last item of one of the columns or
256+ // be bigger than them (because it's not in the viewport and
257+ // thus we have not created a delegate for it)
258+ bool found = false;
259+ int lastCreatedIndex = INT_MIN;
260+ for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
261+ QList<ViewItem> &column = m_columnVisibleItems[i];
262+ if (!column.isEmpty()) {
263+ const int lastColumnIndex = column.last().m_modelIndex;
264+ if (lastColumnIndex == indexToRemove) {
265+ releaseItem(column.takeLast().m_item);
266+ found = true;
540267 }
268+ lastCreatedIndex = qMax(lastCreatedIndex, lastColumnIndex);
541269 }
542270 }
543− }
544− }
545− polish();
546−}
547−
548−
549−void VerticalJournal::relayout()
550−{
551− m_needsRelayout = true;
552− polish();
553−}
554−
555−void VerticalJournal::onHeightChanged()
556−{
557− polish();
558−}
559−
560−void VerticalJournal::updatePolish()
561−{
562− if (!model())
563− return;
564−
565− if (m_needsRelayout) {
566− m_implicitHeightDirty = true;
567−
568− QList<ViewItem> allItems;
569− Q_FOREACH(const auto &column, m_columnVisibleItems)
570− allItems << column;
571−
572− qSort(allItems);
573−
574− const int nColumns = qMax(1., floor((double)(width() + m_columnSpacing) / (m_columnWidth + m_columnSpacing)));
575− m_columnVisibleItems.resize(nColumns);
576− m_indexColumnMap.clear();
577− for (int i = 0; i < nColumns; ++i)
578− m_columnVisibleItems[i].clear();
579−
580− // If the first of allItems doesn't contain index 0 we need to drop them
581− // all since we can't consistently relayout without the first item being there
582−
583− if (!allItems.isEmpty()) {
584− if (allItems.first().m_modelIndex == 0) {
585− Q_FOREACH(const ViewItem &item, allItems)
586− positionItem(item.m_modelIndex, item.m_item);
587− } else {
588− Q_FOREACH(const ViewItem &item, allItems)
589− releaseItem(item);
271+ if (!found) {
272+ if (indexToRemove < lastCreatedIndex) {
273+ qFatal("VerticalJournal only supports removal from the end of the model");
274+ } else {
275+ setImplicitHeightDirty();
276+ }
590277 }
591278 }
592−
593− m_needsRelayout = false;
594− }
595−
596− refill();
597−
598− const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
599− const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
600− const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
601− Q_FOREACH(const auto &column, m_columnVisibleItems) {
602− Q_FOREACH(const ViewItem &item, column) {
603− QQuickItemPrivate::get(item.m_item)->setCulled(item.y() + item.height() <= from || item.y() >= to);
604− }
605279 }
606−
607− if (m_implicitHeightDirty) {
608− calculateImplicitHeight();
609− }
610−}
611−
612−void VerticalJournal::componentComplete()
613−{
614− if (m_delegateModel)
615− m_delegateModel->componentComplete();
616−
617− QQuickItem::componentComplete();
618−
619− m_needsRelayout = true;
620−
621− polish();
622280}
plugins/DashViews/verticaljournal.h+13−91
@@ -17,17 +17,7 @@
1717#ifndef VERTICALJOURNAL_H
1818#define VERTICALJOURNAL_H
1919
20−#include <QQuickItem>
21−
22−class QAbstractItemModel;
23−class QQmlComponent;
24−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
25−class QQuickChangeSet;
26−class QQuickVisualDataModel;
27−#else
28−class QQmlChangeSet;
29−class QQmlDelegateModel;
30−#endif
20+#include "abstractjournal.h"
3121
3222 /** A vertical journal is a view that creates delegates
3323 * based on a model and layouts them in columns following
@@ -60,75 +50,23 @@ class QQmlDelegateModel;
6050 * +-----+
6151 *
6252 */
63− class VerticalJournal : public QQuickItem
53+
54+class VerticalJournal : public AbstractJournal
6455{
6556 Q_OBJECT
6657
67− Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
68− Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
6958 Q_PROPERTY(qreal columnWidth READ columnWidth WRITE setColumnWidth NOTIFY columnWidthChanged)
70− Q_PROPERTY(qreal columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged)
71− Q_PROPERTY(qreal rowSpacing READ rowSpacing WRITE setRowSpacing NOTIFY rowSpacingChanged)
72− Q_PROPERTY(qreal delegateCreationBegin READ delegateCreationBegin
73− WRITE setDelegateCreationBegin
74− NOTIFY delegateCreationBeginChanged
75− RESET resetDelegateCreationBegin)
76− Q_PROPERTY(qreal delegateCreationEnd READ delegateCreationEnd
77− WRITE setDelegateCreationEnd
78− NOTIFY delegateCreationEndChanged
79− RESET resetDelegateCreationEnd)
8059
8160friend class VerticalJournalTest;
8261
8362public:
8463 VerticalJournal();
8564
86− QAbstractItemModel *model() const;
87− void setModel(QAbstractItemModel *model);
88−
89− QQmlComponent *delegate() const;
90− void setDelegate(QQmlComponent *delegate);
91−
9265 qreal columnWidth() const;
9366 void setColumnWidth(qreal columnWidth);
9467
95− qreal columnSpacing() const;
96− void setColumnSpacing(qreal columnSpacing);
97−
98− qreal rowSpacing() const;
99− void setRowSpacing(qreal rowSpacing);
100−
101− qreal delegateCreationBegin() const;
102− void setDelegateCreationBegin(qreal);
103− void resetDelegateCreationBegin();
104−
105− qreal delegateCreationEnd() const;
106− void setDelegateCreationEnd(qreal);
107− void resetDelegateCreationEnd();
108−
10968Q_SIGNALS:
110− void modelChanged();
111− void delegateChanged();
11269 void columnWidthChanged();
113− void columnSpacingChanged();
114− void rowSpacingChanged();
115− void delegateCreationBeginChanged();
116− void delegateCreationEndChanged();
117−
118−protected:
119− void updatePolish();
120− void componentComplete();
121−
122−private Q_SLOTS:
123−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
124− void itemCreated(int modelIndex, QQuickItem *item);
125− void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
126−#else
127− void itemCreated(int modelIndex, QObject *object);
128− void onModelUpdated(const QQmlChangeSet &changeSet, bool reset);
129−#endif
130− void relayout();
131− void onHeightChanged();
13270
13371private:
13472 class ViewItem
@@ -144,39 +82,23 @@ private:
14482 int m_modelIndex;
14583 };
14684
147− void createDelegateModel();
148− void refill();
149− void findBottomModelIndexToAdd(int *modelIndex, qreal *yPos);
150− void findTopModelIndexToAdd(int *modelIndex, qreal *yPos);
151− bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
152− bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
153− QQuickItem *createItem(int modelIndex, bool asynchronous);
154− void positionItem(int modelIndex, QQuickItem *item);
155− void cleanupExistingItems();
156− void releaseItem(const ViewItem &item);
157− void calculateImplicitHeight();
158−
85+ void findBottomModelIndexToAdd(int *modelIndex, qreal *yPos) override;
86+ void findTopModelIndexToAdd(int *modelIndex, qreal *yPos) override;
87+ bool removeNonVisibleItems(qreal bufferFromY, qreal bufferToY) override;
88+ void positionItem(int modelIndex, QQuickItem *item) override;
89+ void cleanupExistingItems() override;
90+ void calculateImplicitHeight() override;
91+ void doRelayout() override;
92+ void updateItemCulling(qreal visibleFromY, qreal visibleToY) override;
15993#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
160− QQuickVisualDataModel *m_delegateModel;
94+ void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) override;
16195#else
162− QQmlDelegateModel *m_delegateModel;
96+ void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) override;
16397#endif
16498
165− // Index we are waiting because we requested it asynchronously
166− int m_asyncRequestedIndex;
167−
16899 QVector<QList<ViewItem>> m_columnVisibleItems;
169100 QHash<int, int> m_indexColumnMap;
170101 int m_columnWidth;
171− int m_columnSpacing;
172− int m_rowSpacing;
173− qreal m_delegateCreationBegin;
174− qreal m_delegateCreationEnd;
175− bool m_delegateCreationBeginValid;
176− bool m_delegateCreationEndValid;
177− bool m_needsRelayout;
178− bool m_delegateValidated;
179− bool m_implicitHeightDirty;
180102};
181103
182104#endif
plugins/Utils/qsortfilterproxymodelqml.cpp+12
@@ -68,6 +68,18 @@ QSortFilterProxyModelQML::setModel(QAbstractItemModel *itemModel)
6868 }
6969}
7070
71+QVariantMap
72+QSortFilterProxyModelQML::get(int row)
73+{
74+ QVariantMap res;
75+ const QHash<int, QByteArray> roles = roleNames();
76+ auto it = roles.begin();
77+ for ( ; it != roles.end(); ++it) {
78+ res[*it] = data(row, it.key());
79+ }
80+ return res;
81+}
82+
7183QVariant
7284QSortFilterProxyModelQML::data(int row, int role)
7385{
plugins/Utils/qsortfilterproxymodelqml.h+1
@@ -31,6 +31,7 @@ class QSortFilterProxyModelQML : public QSortFilterProxyModel
3131public:
3232 explicit QSortFilterProxyModelQML(QObject *parent = 0);
3333
34+ Q_INVOKABLE QVariantMap get(int row); // Use with caution, it can be slow to query all the roles
3435 Q_INVOKABLE QVariant data(int row, int role);
3536 Q_INVOKABLE int count();
3637 Q_INVOKABLE int findFirst(int role, const QVariant& value) const;
po/ca.po+7−7
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-09-28 07:38+0000\n"
12−"Last-Translator: David Planella <david.planella@ubuntu.com>\n"
11+"PO-Revision-Date: 2014-01-10 07:45+0000\n"
12+"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
1313"Language-Team: Catalan <ca@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-11 06:38+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -143,7 +143,7 @@ msgstr "S'està parlant..."
143143
144144#: Notifications/NotificationMenuItemFactory.qml:89
145145msgid "Show password"
146−msgstr ""
146+msgstr "Mostra la contrasenya"
147147
148148#: Panel/SearchIndicator.qml:27
149149msgid "Search"
@@ -152,11 +152,11 @@ msgstr "Cerca"
152152#: plugins/Unity/Launcher/launcheritem.cpp:43
153153#: plugins/Unity/Launcher/launcheritem.cpp:73
154154msgid "Lock to Launcher"
155−msgstr ""
155+msgstr "Mantén-la al llançador"
156156
157157#: plugins/Unity/Launcher/launcheritem.cpp:73
158158msgid "Unlock from Launcher"
159−msgstr ""
159+msgstr "No la mantinguis al llançador"
160160
161161#: Shell.qml:439
162162#, qt-format
po/ckb.ponew file+158
@@ -0,0 +1,158 @@
1+# Kurdish (Sorani) translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-14 20:05+0000\n"
12+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+"Language-Team: Kurdish (Sorani) <ckb@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n != 1;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr ""
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr ""
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr ""
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr ""
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr ""
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr ""
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr ""
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr ""
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr ""
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr ""
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr ""
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr ""
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr ""
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr ""
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr ""
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr ""
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr ""
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr ""
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr ""
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr ""
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr ""
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr ""
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
po/cs.po+9−7
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-08 08:09+0000\n"
11+"PO-Revision-Date: 2014-01-08 10:11+0000\n"
1212"Last-Translator: Tadeáš Pařík <tadeas.parik@gmail.com>\n"
1313"Language-Team: Czech <cs@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-09 05:41+0000\n"
19+"X-Generator: Launchpad (build 16884)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -44,7 +44,7 @@ msgstr "Uzavřít"
4444
4545#: Components/EdgeDemo.qml:171
4646msgid "Swipe up again to close the settings screen"
47−msgstr ""
47+msgstr "Přetažením nahoru dojde k uzavření obrazovky nastavení"
4848
4949#: Components/EdgeDemo.qml:198
5050msgid "Left edge"
@@ -63,6 +63,8 @@ msgid ""
6363"You have now mastered the edge gestures and can start using the "
6464"phone<br><br>Tap anywhere to start"
6565msgstr ""
66+"Nyní jste si osvojili gesta potřebná k ovládání telefonu, můžete ho začít "
67+"používat <br><br>Klepnutím kdekoli začnete"
6668
6769#: Components/PageHeader.qml:364
6870msgid "Recent searches"
@@ -138,7 +140,7 @@ msgstr "Mluvte..."
138140
139141#: Notifications/NotificationMenuItemFactory.qml:89
140142msgid "Show password"
141−msgstr ""
143+msgstr "Zobrazit heslo"
142144
143145#: Panel/SearchIndicator.qml:27
144146msgid "Search"
@@ -147,11 +149,11 @@ msgstr "Hledat"
147149#: plugins/Unity/Launcher/launcheritem.cpp:43
148150#: plugins/Unity/Launcher/launcheritem.cpp:73
149151msgid "Lock to Launcher"
150−msgstr ""
152+msgstr "Přidat do Launcheru"
151153
152154#: plugins/Unity/Launcher/launcheritem.cpp:73
153155msgid "Unlock from Launcher"
154−msgstr ""
156+msgstr "Odebrat z Launcheru"
155157
156158#: Shell.qml:439
157159#, qt-format
po/da.ponew file+158
@@ -0,0 +1,158 @@
1+# Danish translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-14 20:16+0000\n"
12+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+"Language-Team: Danish <da@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n != 1;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr ""
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr ""
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr ""
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr ""
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr ""
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr ""
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr ""
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr ""
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr ""
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr ""
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr ""
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr ""
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr ""
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr ""
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr ""
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr ""
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr ""
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr ""
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr ""
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr ""
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr ""
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr ""
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
po/en_GB.po+28−26
@@ -8,61 +8,63 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-06-27 08:53+0000\n"
12−"Last-Translator: Michał Sawicz <michal.sawicz@canonical.com>\n"
11+"PO-Revision-Date: 2014-01-14 20:46+0000\n"
12+"Last-Translator: Dan Bishop <dan@danbishop.org>\n"
1313"Language-Team: English (United Kingdom) <en_GB@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
23−msgstr ""
23+msgstr "Skip intro"
2424
2525#: Components/EdgeDemo.qml:112
2626msgid "Right edge"
27−msgstr ""
27+msgstr "Right edge"
2828
2929#: Components/EdgeDemo.qml:113
3030msgid "Try swiping from the right edge to unlock the phone"
31−msgstr ""
31+msgstr "Try swiping from the right edge to unlock the phone"
3232
3333#: Components/EdgeDemo.qml:144
3434msgid "Top edge"
35−msgstr ""
35+msgstr "Top edge"
3636
3737#: Components/EdgeDemo.qml:145
3838msgid "Try swiping from the top edge to access the indicators"
39−msgstr ""
39+msgstr "Try swiping from the top edge to access the indicators"
4040
4141#: Components/EdgeDemo.qml:170
4242msgid "Close"
43−msgstr ""
43+msgstr "Close"
4444
4545#: Components/EdgeDemo.qml:171
4646msgid "Swipe up again to close the settings screen"
47−msgstr ""
47+msgstr "Swipe up again to close the settings screen"
4848
4949#: Components/EdgeDemo.qml:198
5050msgid "Left edge"
51−msgstr ""
51+msgstr "Left edge"
5252
5353#: Components/EdgeDemo.qml:199
5454msgid "Swipe from the left to reveal the launcher for quick access to apps"
55−msgstr ""
55+msgstr "Swipe from the left to reveal the launcher for quick access to apps"
5656
5757#: Components/EdgeDemo.qml:226
5858msgid "Well done"
59−msgstr ""
59+msgstr "Well done"
6060
6161#: Components/EdgeDemo.qml:227
6262msgid ""
6363"You have now mastered the edge gestures and can start using the "
6464"phone<br><br>Tap anywhere to start"
6565msgstr ""
66+"You have now mastered the edge gestures and can start using the "
67+"phone<br><br>Tap anywhere to start"
6668
6769#: Components/PageHeader.qml:364
6870msgid "Recent searches"
@@ -70,41 +72,41 @@ msgstr "Recent searches"
7072
7173#: Dash/Apps/AppPreview.qml:135
7274msgid "Rate this"
73−msgstr ""
75+msgstr "Rate this"
7476
7577#. TRANSLATORS: Number of persons who rated this app/video/whatever
7678#: Dash/Previews/Header.qml:93
7779#, qt-format
7880msgid "(%1)"
79−msgstr ""
81+msgstr "(%1)"
8082
8183#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
8284#: Dash/Previews/Header.qml:105
8385#, qt-format
8486msgid "%1 review"
8587msgid_plural "%1 reviews"
86−msgstr[0] ""
87−msgstr[1] ""
88+msgstr[0] "%1 review"
89+msgstr[1] "%1 reviews"
8890
8991#: Dash/Previews/Reviews.qml:74
9092msgid "Add a review"
91−msgstr ""
93+msgstr "Add a review"
9294
9395#: Dash/Previews/Reviews.qml:86
9496msgid "Review"
95−msgstr ""
97+msgstr "Review"
9698
9799#: Dash/Previews/Reviews.qml:121
98100msgid "Send"
99−msgstr ""
101+msgstr "Send"
100102
101103#: Dash/Previews/Reviews.qml:139
102104msgid "Comments:"
103−msgstr ""
105+msgstr "Comments:"
104106
105107#: Greeter/Lockscreen.qml:149
106108msgid "Emergency Call"
107−msgstr ""
109+msgstr "Emergency Call"
108110
109111#: Greeter/PassphraseLockscreen.qml:59
110112#, qt-format
@@ -137,7 +139,7 @@ msgstr "Speaking..."
137139
138140#: Notifications/NotificationMenuItemFactory.qml:89
139141msgid "Show password"
140−msgstr ""
142+msgstr "Show password"
141143
142144#: Panel/SearchIndicator.qml:27
143145msgid "Search"
@@ -146,11 +148,11 @@ msgstr "Search"
146148#: plugins/Unity/Launcher/launcheritem.cpp:43
147149#: plugins/Unity/Launcher/launcheritem.cpp:73
148150msgid "Lock to Launcher"
149−msgstr ""
151+msgstr "Lock to Launcher"
150152
151153#: plugins/Unity/Launcher/launcheritem.cpp:73
152154msgid "Unlock from Launcher"
153−msgstr ""
155+msgstr "Unlock from Launcher"
154156
155157#: Shell.qml:439
156158#, qt-format
po/es.po+4−4
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-11-20 08:46+0000\n"
11+"PO-Revision-Date: 2014-01-14 21:06+0000\n"
1212"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
1313"Language-Team: Spanish <es@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -113,7 +113,7 @@ msgstr "Llamada de emergencia"
113113#: Greeter/PassphraseLockscreen.qml:59
114114#, qt-format
115115msgid "Hello %1"
116−msgstr "Hola %1"
116+msgstr "Hola, %1"
117117
118118#: Greeter/PassphraseLockscreen.qml:59
119119msgid "Hello"
po/fo.po+5−5
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-01 15:13+0000\n"
11+"PO-Revision-Date: 2014-01-14 20:08+0000\n"
1212"Last-Translator: Jógvan Olsen <jeggy@jebster.net>\n"
1313"Language-Team: Faroese <fo@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -24,7 +24,7 @@ msgstr ""
2424
2525#: Components/EdgeDemo.qml:112
2626msgid "Right edge"
27−msgstr ""
27+msgstr "Høgra kant"
2828
2929#: Components/EdgeDemo.qml:113
3030msgid "Try swiping from the right edge to unlock the phone"
@@ -32,7 +32,7 @@ msgstr ""
3232
3333#: Components/EdgeDemo.qml:144
3434msgid "Top edge"
35−msgstr ""
35+msgstr "Topp kantur"
3636
3737#: Components/EdgeDemo.qml:145
3838msgid "Try swiping from the top edge to access the indicators"
po/hi.ponew file+158
@@ -0,0 +1,158 @@
1+# Hindi translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-15 01:49+0000\n"
12+"Last-Translator: Siddhanathan S. <Unknown>\n"
13+"Language-Team: Hindi <hi@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n != 1;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr "परिचय छोड़"
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr "दायाँ किनारा"
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr ""
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr "ऊपर किनारे"
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr ""
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr "बंद करें"
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr ""
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr "बायाँ किनारा"
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr ""
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr "शाबाश"
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr "पिछले खोजों"
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr "समीक्षा दे"
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr "समीक्षा"
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr "भेजें"
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr ""
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr "आपातकालीन कॉल"
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr "नमस्ते"
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr ""
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr "काम कर रहा है | कृपया प्रतीक्षा करे |"
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr "अब बात करें"
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr "अब बोल रहे हैं"
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr "खोजें"
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
po/hu.po+6−6
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2014-01-04 15:30+0000\n"
12−"Last-Translator: Péter MÁTÓ <mato.peter@gmail.com>\n"
11+"PO-Revision-Date: 2014-01-14 23:02+0000\n"
12+"Last-Translator: Kristóf Kiszel <Unknown>\n"
1313"Language-Team: Hungarian <hu@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-01-05 05:50+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -133,7 +133,7 @@ msgstr "Betöltés, kis türelmet…"
133133
134134#: Hud/Hud.qml:89
135135msgid "Speak Now..."
136−msgstr "Beszéljen most…"
136+msgstr "Most beszéljen…"
137137
138138#: Hud/Hud.qml:94
139139msgid "Speaking..."
@@ -159,4 +159,4 @@ msgstr "Eltávolítás az Indítóból"
159159#: Shell.qml:439
160160#, qt-format
161161msgid "Please enter %1"
162−msgstr ""
162+msgstr "Adja meg a következőt: %1"
po/lv.po+6−6
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-09-28 16:29+0000\n"
11+"PO-Revision-Date: 2014-01-11 16:48+0000\n"
1212"Last-Translator: Jānis-Marks Gailis <jeanmarc.gailis@gmail.com>\n"
1313"Language-Team: Latvian <lv@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-12 05:52+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -32,7 +32,7 @@ msgstr ""
3232
3333#: Components/EdgeDemo.qml:144
3434msgid "Top edge"
35−msgstr ""
35+msgstr "Augšējā mala"
3636
3737#: Components/EdgeDemo.qml:145
3838msgid "Try swiping from the top edge to access the indicators"
@@ -40,7 +40,7 @@ msgstr ""
4040
4141#: Components/EdgeDemo.qml:170
4242msgid "Close"
43−msgstr ""
43+msgstr "Aizvērt"
4444
4545#: Components/EdgeDemo.qml:171
4646msgid "Swipe up again to close the settings screen"
@@ -48,7 +48,7 @@ msgstr ""
4848
4949#: Components/EdgeDemo.qml:198
5050msgid "Left edge"
51−msgstr ""
51+msgstr "Kreisā mala"
5252
5353#: Components/EdgeDemo.qml:199
5454msgid "Swipe from the left to reveal the launcher for quick access to apps"
po/mr.ponew file+158
@@ -0,0 +1,158 @@
1+# Marathi translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-14 19:54+0000\n"
12+"Last-Translator: Rohan Deshmukh <Unknown>\n"
13+"Language-Team: Marathi <mr@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n != 1;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr "परिचय वगळा"
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr "उजवा काठ"
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr "फोन अनलॉक करण्यासाठी उजव्या काठावरुन स्वाइप करण्याचा प्रयत्न करा"
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr "वरचा काठ"
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr "इंडिकेटर वापरण्याकरिता वरचा काठावरून स्वाइप करण्याचा प्रयत्न करा"
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr "बंद करा"
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr "सेटिंग्ज स्क्रीन बंद करण्यासाठी पुन्हा एकदा स्वाइप करा"
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr "डावा काठ"
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr "लाँचर वरील अॕप्प्स त्वरित वापरण्याकरिता डाव्या बाजूने स्वाइप करा"
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr "कौशल्यपूर्वक केले"
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr ""
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr ""
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr ""
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr ""
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr ""
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr ""
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr ""
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr ""
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr ""
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr ""
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr ""
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr ""
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
po/pl.po+7−7
@@ -8,16 +8,16 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-13 13:44+0000\n"
12−"Last-Translator: GTriderXC <gtriderxc@yahoo.com>\n"
11+"PO-Revision-Date: 2014-01-10 11:38+0000\n"
12+"Last-Translator: Michał Sawicz <michal.sawicz@canonical.com>\n"
1313"Language-Team: Polish <pl@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
1818"|| n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16877)\n"
19+"X-Launchpad-Export-Date: 2014-01-11 06:38+0000\n"
20+"X-Generator: Launchpad (build 16890)\n"
2121
2222#: Components/EdgeDemoOverlay.qml:151
2323msgid "Skip intro"
@@ -143,7 +143,7 @@ msgstr "Rozpoznanie..."
143143
144144#: Notifications/NotificationMenuItemFactory.qml:89
145145msgid "Show password"
146−msgstr ""
146+msgstr "Wyświetl hasło"
147147
148148#: Panel/SearchIndicator.qml:27
149149msgid "Search"
@@ -152,11 +152,11 @@ msgstr "Szukaj"
152152#: plugins/Unity/Launcher/launcheritem.cpp:43
153153#: plugins/Unity/Launcher/launcheritem.cpp:73
154154msgid "Lock to Launcher"
155−msgstr ""
155+msgstr "Dodaj do panelu uruchamiania"
156156
157157#: plugins/Unity/Launcher/launcheritem.cpp:73
158158msgid "Unlock from Launcher"
159−msgstr ""
159+msgstr "Usuń z panelu uruchamiania"
160160
161161#: Shell.qml:439
162162#, qt-format
po/pt.ponew file+158
@@ -0,0 +1,158 @@
1+# Portuguese translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-14 19:18+0000\n"
12+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+"Language-Team: Portuguese <pt@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n != 1;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr ""
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr ""
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr ""
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr ""
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr ""
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr ""
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr ""
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr ""
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr ""
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr ""
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr ""
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr ""
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr ""
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr ""
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr ""
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr ""
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr ""
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr ""
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr ""
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr ""
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr ""
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr ""
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
po/ru.po+11−8
@@ -8,16 +8,16 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-01 05:55+0000\n"
12−"Last-Translator: Zonov Roman <Unknown>\n"
11+"PO-Revision-Date: 2014-01-14 19:17+0000\n"
12+"Last-Translator: alex <sos987@gmail.com>\n"
1313"Language-Team: Russian <ru@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
1818"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16877)\n"
19+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
20+"X-Generator: Launchpad (build 16890)\n"
2121
2222#: Components/EdgeDemoOverlay.qml:151
2323msgid "Skip intro"
@@ -69,6 +69,8 @@ msgid ""
6969"You have now mastered the edge gestures and can start using the "
7070"phone<br><br>Tap anywhere to start"
7171msgstr ""
72+"Теперь, когда вы изучили основные движения, вы можете начать использовать "
73+"телефон.<br><br>Нажмите, чтобы начать"
7274
7375#: Components/PageHeader.qml:364
7476msgid "Recent searches"
@@ -89,7 +91,8 @@ msgstr "(%1)"
8991#, qt-format
9092msgid "%1 review"
9193msgid_plural "%1 reviews"
92−msgstr[0] "%1 отзыв"
94+msgstr[0] "%1 отзыва"
95+msgstr[1] "%1 отзывов"
9396
9497#: Dash/Previews/Reviews.qml:74
9598msgid "Add a review"
@@ -142,7 +145,7 @@ msgstr "Говорите..."
142145
143146#: Notifications/NotificationMenuItemFactory.qml:89
144147msgid "Show password"
145−msgstr ""
148+msgstr "Показать пароль"
146149
147150#: Panel/SearchIndicator.qml:27
148151msgid "Search"
@@ -151,11 +154,11 @@ msgstr "Поиск"
151154#: plugins/Unity/Launcher/launcheritem.cpp:43
152155#: plugins/Unity/Launcher/launcheritem.cpp:73
153156msgid "Lock to Launcher"
154−msgstr ""
157+msgstr "Закрепить на панели"
155158
156159#: plugins/Unity/Launcher/launcheritem.cpp:73
157160msgid "Unlock from Launcher"
158−msgstr ""
161+msgstr "Убрать с панели"
159162
160163#: Shell.qml:439
161164#, qt-format
po/sw.ponew file+158
@@ -0,0 +1,158 @@
1+# Swahili translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-14 19:28+0000\n"
12+"Last-Translator: Damas Mlabwa <Unknown>\n"
13+"Language-Team: Swahili <sw@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n != 1;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr ""
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr ""
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr ""
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr ""
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr ""
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr "Funga"
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr ""
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr ""
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr ""
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr ""
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr ""
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr ""
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr ""
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr "Tuma"
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr "Maoni:"
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr ""
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr ""
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr "Thibitisha"
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr ""
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr ""
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr ""
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr "Tafuta"
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
po/tr.po+6−4
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-12-12 16:06+0000\n"
12−"Last-Translator: Arda Ünlü <aarda.uunlu@gmail.com>\n"
11+"PO-Revision-Date: 2014-01-14 19:05+0000\n"
12+"Last-Translator: Osman Coşkun <Unknown>\n"
1313"Language-Team: Turkish <tr@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
2020
2121#: Components/EdgeDemoOverlay.qml:151
2222msgid "Skip intro"
@@ -64,6 +64,8 @@ msgid ""
6464"You have now mastered the edge gestures and can start using the "
6565"phone<br><br>Tap anywhere to start"
6666msgstr ""
67+"Kenar hareketlerini öğrendiniz ve herhangi bir yere dokunarak telefonunuzu "
68+"<br><br> kullanmaya başlayabilirsiniz"
6769
6870#: Components/PageHeader.qml:364
6971msgid "Recent searches"
po/vi.ponew file+158
@@ -0,0 +1,158 @@
1+# Vietnamese translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11+"PO-Revision-Date: 2014-01-15 01:19+0000\n"
12+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+"Language-Team: Vietnamese <vi@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=1; plural=0;\n"
18+"X-Launchpad-Export-Date: 2014-01-15 06:35+0000\n"
19+"X-Generator: Launchpad (build 16890)\n"
20+
21+#: Components/EdgeDemoOverlay.qml:151
22+msgid "Skip intro"
23+msgstr ""
24+
25+#: Components/EdgeDemo.qml:112
26+msgid "Right edge"
27+msgstr ""
28+
29+#: Components/EdgeDemo.qml:113
30+msgid "Try swiping from the right edge to unlock the phone"
31+msgstr ""
32+
33+#: Components/EdgeDemo.qml:144
34+msgid "Top edge"
35+msgstr ""
36+
37+#: Components/EdgeDemo.qml:145
38+msgid "Try swiping from the top edge to access the indicators"
39+msgstr ""
40+
41+#: Components/EdgeDemo.qml:170
42+msgid "Close"
43+msgstr ""
44+
45+#: Components/EdgeDemo.qml:171
46+msgid "Swipe up again to close the settings screen"
47+msgstr ""
48+
49+#: Components/EdgeDemo.qml:198
50+msgid "Left edge"
51+msgstr ""
52+
53+#: Components/EdgeDemo.qml:199
54+msgid "Swipe from the left to reveal the launcher for quick access to apps"
55+msgstr ""
56+
57+#: Components/EdgeDemo.qml:226
58+msgid "Well done"
59+msgstr ""
60+
61+#: Components/EdgeDemo.qml:227
62+msgid ""
63+"You have now mastered the edge gestures and can start using the "
64+"phone<br><br>Tap anywhere to start"
65+msgstr ""
66+
67+#: Components/PageHeader.qml:364
68+msgid "Recent searches"
69+msgstr ""
70+
71+#: Dash/Apps/AppPreview.qml:135
72+msgid "Rate this"
73+msgstr ""
74+
75+#. TRANSLATORS: Number of persons who rated this app/video/whatever
76+#: Dash/Previews/Header.qml:93
77+#, qt-format
78+msgid "(%1)"
79+msgstr ""
80+
81+#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82+#: Dash/Previews/Header.qml:105
83+#, qt-format
84+msgid "%1 review"
85+msgid_plural "%1 reviews"
86+msgstr[0] ""
87+msgstr[1] ""
88+
89+#: Dash/Previews/Reviews.qml:74
90+msgid "Add a review"
91+msgstr ""
92+
93+#: Dash/Previews/Reviews.qml:86
94+msgid "Review"
95+msgstr ""
96+
97+#: Dash/Previews/Reviews.qml:121
98+msgid "Send"
99+msgstr ""
100+
101+#: Dash/Previews/Reviews.qml:139
102+msgid "Comments:"
103+msgstr ""
104+
105+#: Greeter/Lockscreen.qml:149
106+msgid "Emergency Call"
107+msgstr ""
108+
109+#: Greeter/PassphraseLockscreen.qml:59
110+#, qt-format
111+msgid "Hello %1"
112+msgstr ""
113+
114+#: Greeter/PassphraseLockscreen.qml:59
115+msgid "Hello"
116+msgstr ""
117+
118+#: Hud/HudParametrizedActionsPage.qml:132
119+msgid "Confirm"
120+msgstr ""
121+
122+#: Hud/Hud.qml:61 Hud/Hud.qml:99 Hud/Hud.qml:372
123+msgid "Type or say a command"
124+msgstr ""
125+
126+#: Hud/Hud.qml:84
127+msgid "Loading. Please Wait..."
128+msgstr ""
129+
130+#: Hud/Hud.qml:89
131+msgid "Speak Now..."
132+msgstr ""
133+
134+#: Hud/Hud.qml:94
135+msgid "Speaking..."
136+msgstr ""
137+
138+#: Notifications/NotificationMenuItemFactory.qml:89
139+msgid "Show password"
140+msgstr ""
141+
142+#: Panel/SearchIndicator.qml:27
143+msgid "Search"
144+msgstr ""
145+
146+#: plugins/Unity/Launcher/launcheritem.cpp:43
147+#: plugins/Unity/Launcher/launcheritem.cpp:73
148+msgid "Lock to Launcher"
149+msgstr ""
150+
151+#: plugins/Unity/Launcher/launcheritem.cpp:73
152+msgid "Unlock from Launcher"
153+msgstr ""
154+
155+#: Shell.qml:439
156+#, qt-format
157+msgid "Please enter %1"
158+msgstr ""
qml/Components/PageHeader.qml+13−37
@@ -21,17 +21,12 @@ import Ubuntu.Components.ListItems 0.1 as ListItem
2121import Unity 0.1
2222
2323Item {
24− /*!
25− \preliminary
26− The text that is shown inside the Page Header
27− \qmlproperty string text
28− */
29− property alias text: label.text
30−
24+ id: root
3125 property bool searchEntryEnabled: false
3226 property alias searchQuery: searchField.text
3327 property ListModel searchHistory
3428 property Scope scope
29+ property alias childItem: itemContainer.children
3530
3631 height: units.gu(8.5)
3732 implicitHeight: units.gu(8.5)
@@ -82,29 +77,10 @@ Item {
8277 height: childrenRect.height
8378
8479 Item {
85− id: textContainer
80+ id: itemContainer
8681
87− width: header.width
82+ width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
8883 height: header.height
89−
90− Label {
91− id: label
92− anchors {
93− left: parent.left
94− leftMargin: units.gu(2)
95− right: parent.right
96− verticalCenter: parent.verticalCenter
97− }
98−
99− color: Theme.palette.selected.backgroundText
100− opacity: 0.8
101− font.family: "Ubuntu"
102− font.weight: Font.Light
103− fontSize: "x-large"
104− elide: Text.ElideRight
105− style: Text.Raised
106− styleColor: "black"
107− }
10884 }
10985
11086 Item {
@@ -115,12 +91,12 @@ Item {
11591 property bool popoverShouldOpen: false
11692 property bool popoverShouldClose: false
11793
118− property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
94+ property bool narrowMode: parent.width < units.gu(80)
11995
12096 property bool active: searchField.text != "" || searchField.activeFocus
12197 property var popover: null
12298
123− anchors.right: textContainer.right
99+ anchors.right: headerContainer.right
124100 height: header.height
125101
126102 state:
@@ -255,13 +231,13 @@ Item {
255231 states: [
256232 State {
257233 name: "wide"
258− AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
259− AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
234+ AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
235+ AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
260236 },
261237 State {
262238 name: "narrow"
263239 PropertyChanges { target: searchField; highlighted: true }
264− AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
240+ AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
265241 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
266242 },
267243 State {
@@ -299,7 +275,7 @@ Item {
299275 ParallelAnimation {
300276 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
301277 PropertyAction { target: primaryImage; property: "source" }
302− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
278+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
303279 }
304280 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
305281 }
@@ -308,14 +284,14 @@ Item {
308284 to: "inactive"
309285 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
310286 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
311− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
287+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
312288 },
313289 Transition {
314290 to: "narrowActive"
315291 SequentialAnimation {
316292 ParallelAnimation {
317293 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
318− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
294+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
319295 }
320296 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
321297 }
@@ -324,7 +300,7 @@ Item {
324300 to: "narrowInactive"
325301 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
326302 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
327− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
303+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
328304 }
329305 ]
330306
qml/Components/PageHeaderLabel.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+PageHeader {
21+ property alias text: label.text
22+
23+ childItem: Label {
24+ id: label
25+ anchors {
26+ left: parent.left
27+ leftMargin: units.gu(2)
28+ right: parent.right
29+ verticalCenter: parent.verticalCenter
30+ }
31+
32+ color: Theme.palette.selected.backgroundText
33+ opacity: 0.8
34+ font.family: "Ubuntu"
35+ font.weight: Font.Light
36+ fontSize: "x-large"
37+ elide: Text.ElideRight
38+ style: Text.Raised
39+ styleColor: "black"
40+ }
41+}
qml/Dash/Card.qmlnew file+83
@@ -0,0 +1,83 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+Item {
21+ id: root
22+ property var template
23+ property var components
24+ property var cardData
25+
26+ width: {
27+ if (template !== undefined) {
28+ if (template["card-layout"] === "horizontal") return units.gu(38);
29+ switch (template['card-size']) {
30+ case "small": return units.gu(12);
31+ case "large": return units.gu(38);
32+ }
33+ }
34+ return units.gu(18.5);
35+ }
36+ height: childrenRect.height
37+
38+ UbuntuShape {
39+ id: artShape
40+ objectName: "artShape"
41+ width: image.fillMode === Image.PreserveAspectCrop || aspect < image.aspect ? image.width : height * image.aspect
42+ height: image.fillMode === Image.PreserveAspectCrop || aspect > image.aspect ? image.height : width / image.aspect
43+ anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
44+
45+ property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
46+
47+ image: Image {
48+ width: template && template["card-layout"] === "horizontal" ? height * artShape.aspect : root.width
49+ height: template && template["card-layout"] === "horizontal" ? header.height : width / artShape.aspect
50+ objectName: "artImage"
51+ source: cardData && cardData["art"] || ""
52+ // FIXME uncomment when having investigated / fixed the crash
53+ //sourceSize.width: width > height ? width : 0
54+ //sourceSize.height: height > width ? height : 0
55+ fillMode: components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
56+
57+ property real aspect: implicitWidth / implicitHeight
58+ }
59+ }
60+
61+ CardHeader {
62+ id: header
63+ objectName: "cardHeader"
64+ anchors {
65+ top: template && template["card-layout"] === "horizontal" ? artShape.top : artShape.bottom
66+ left: template && template["card-layout"] === "horizontal" ? artShape.right : parent.left
67+ right: parent.right
68+ }
69+
70+ mascot: cardData && cardData["mascot"] || ""
71+ title: cardData && cardData["title"] || ""
72+ subtitle: cardData && cardData["subtitle"] || ""
73+ }
74+
75+ Label {
76+ objectName: "summaryLabel"
77+ anchors { top: header.bottom; left: parent.left; right: parent.right }
78+ wrapMode: Text.Wrap
79+ maximumLineCount: 5
80+ elide: Text.ElideRight
81+ text: cardData && cardData["summary"] || ""
82+ }
83+}
qml/Dash/CardHeader.qmlnew file+112
@@ -0,0 +1,112 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+Item {
21+ id: root
22+ property alias mascot: mascotImage.source
23+ property alias title: titleLabel.text
24+ property alias subtitle: subtitleLabel.text
25+ property alias price: priceLabel.text
26+ property alias oldPrice: oldPriceLabel.text
27+ property alias altPrice: altPriceLabel.text
28+
29+ visible: mascotImage.status === Image.Ready || title || price
30+ height: row.height > 0 ? row.height + row.spacing * 2 : 0
31+
32+ Row {
33+ id: row
34+ objectName: "outerRow"
35+
36+ anchors { top: parent.top; left: parent.left; right: parent.right; margins: spacing }
37+ spacing: units.gu(1)
38+
39+ UbuntuShape {
40+ id: mascotShape
41+ objectName: "mascotShape"
42+
43+ width: units.gu(8)
44+ height: units.gu(8)
45+ visible: image.status === Image.Ready
46+
47+ image: Image {
48+ id: mascotImage
49+ sourceSize { width: mascotShape.width; height: mascotShape.height }
50+ }
51+ }
52+
53+ Column {
54+ objectName: "column"
55+ width: parent.width - x
56+
57+ Label {
58+ id: titleLabel
59+ anchors { left: parent.left; right: parent.right }
60+ elide: Text.ElideRight
61+ objectName: "titleLabel"
62+ font.weight: Font.DemiBold
63+ wrapMode: Text.Wrap
64+ maximumLineCount: 2
65+ }
66+
67+ Label {
68+ id: subtitleLabel
69+ anchors { left: parent.left; right: parent.right }
70+ elide: Text.ElideRight
71+ visible: titleLabel.text && text
72+ }
73+
74+ Row {
75+ id: prices
76+ objectName: "prices"
77+ anchors { left: parent.left; right: parent.right }
78+
79+ property int labels: {
80+ var labels = 1; // price always visible
81+ if (oldPriceLabel.text !== "") labels += 1;
82+ if (altPriceLabel.text !== "") labels += 1;
83+ return labels;
84+ }
85+ property real labelWidth: width / labels
86+
87+ Label {
88+ id: priceLabel
89+ width: parent.labelWidth
90+ elide: Text.ElideRight
91+ font.weight: Font.DemiBold
92+ color: Theme.palette.selected.foreground
93+ }
94+
95+ Label {
96+ id: oldPriceLabel
97+ objectName: "oldPriceLabel"
98+ width: parent.labelWidth
99+ elide: Text.ElideRight
100+ horizontalAlignment: parent.labels === 3 ? Text.AlignHCenter : Text.AlignRight
101+ }
102+
103+ Label {
104+ id: altPriceLabel
105+ width: parent.labelWidth
106+ elide: Text.ElideRight
107+ horizontalAlignment: Text.AlignRight
108+ }
109+ }
110+ }
111+ }
112+}
qml/Dash/Dash.qml−18
@@ -66,11 +66,6 @@ Showable {
6666 model: filteredScopes
6767 scopes: scopes
6868 searchHistory: dash.searchHistory
69− onMovementStarted: dashbar.startNavigation()
70− onMovementEnded: dashbar.stopNavigation()
71− onContentFlickStarted: dashbar.finishNavigation()
72− onContentEndReached: dashbar.finishNavigation()
73− onPreviewShown: dashbar.finishNavigation()
7469 onScopeLoaded: {
7570 if (scopeId == dash.showScopeOnLoaded) {
7671 dash.setCurrentScope(scopeId, false, false)
@@ -80,17 +75,4 @@ Showable {
8075 scale: dash.contentScale
8176 clip: scale != 1.0
8277 }
83−
84− DashBar {
85− id: dashbar
86− objectName: "dashbar"
87− anchors.bottom: parent.bottom
88− anchors.left: parent.left
89− anchors.right: parent.right
90− model: filteredScopes
91− currentIndex: dashContent.currentIndex
92− onItemSelected: dashContent.setCurrentScopeAtIndex(index, true, false)
93− opacity: dash.contentScale == 1.0 ? 1.0 : 0.0
94− Behavior on opacity { NumberAnimation { easing.type: Easing.OutQuad; duration: 150 } }
95− }
9678}
qml/Dash/DashApps.qml−3
@@ -29,9 +29,6 @@ GenericScopeView {
2929 property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
3030 property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
3131
32− //FIXME an alias to itself shouldn't be required. More than likely a Qt bug. Try removing with newer Qt.
33− property alias searchHistory: scopeView.searchHistory
34−
3532 ListModel {
3633 id: dummyVisibilityModifier
3734
qml/Dash/DashBar.qmldeleted−128
@@ -1,128 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity 0.1
20−
21−Item {
22− id: dashBar
23−
24− property var model
25− property alias currentIndex: row.currentIndex
26−
27− property int lineHeight: units.dp(2)
28− property int itemSize: units.gu(7)
29− property int iconSize: units.gu(3.5)
30−
31− signal itemSelected(int index)
32−
33− width: units.gu(40)
34− height: units.gu(6)
35−
36− function startNavigation() {
37− timeout.stop()
38− panel.open()
39− }
40−
41− function stopNavigation() {
42− timeout.restart()
43− }
44−
45− function finishNavigation() {
46− panel.close()
47− }
48−
49− Timer {
50− id: timeout
51− interval: 1500
52− running: false
53− repeat: false
54− onTriggered: finishNavigation()
55− }
56−
57− Panel {
58− id: panel
59− objectName: "panel"
60− anchors.fill: parent
61−
62− locked: true // TODO: remove this when lp bug #1179569 will be fixed
63−
64− Rectangle {
65− color: "black"
66− anchors.fill: parent
67−
68− ListView {
69− id: row
70− objectName: "row"
71− model: dashBar.model
72− orientation: ListView.Horizontal
73− width: Math.min(Math.max(dashBar.width/2, units.gu(40)), count * itemSize)
74− height: parent.height
75− anchors.horizontalCenter: parent.horizontalCenter
76− onMovingChanged: if (moving) { timeout.stop() } else { timeout.restart() }
77− interactive: visibleArea.widthRatio < 1 && panel.opened
78− highlightFollowsCurrentItem: false
79−
80− onCurrentItemChanged: {
81− highlightLine.width = currentItem.width
82− highlightLine.x = x + currentItem.x
83− }
84−
85− delegate:
86− // FIXME: Use Panel's integrated trigger() once that works again.
87− MouseArea {
88− width: itemSize
89− height: dashBar.height
90− anchors.top: parent.top
91−
92− onClicked: {
93− dashBar.itemSelected(index)
94− timeout.restart()
95− }
96−
97− Image {
98− anchors.centerIn: parent
99− source: scope.iconHint
100− sourceSize { width: iconSize; height: iconSize }
101− // opacity: index == currentIndex ? 1 : 1 // same opacity for now
102− }
103− }
104− }
105− }
106− }
107−
108− Rectangle {
109− anchors {
110− left: parent.left
111− right: parent.right
112− bottom: parent.bottom
113− }
114− height: dashBar.lineHeight
115− color: "black"
116−
117− Rectangle {
118− id: highlightLine
119− color: Theme.palette.selected.foreground
120− height: parent.height
121− anchors.bottom: parent.bottom
122− z: 1
123−
124− Behavior on x {NumberAnimation { duration: 150; easing.type: Easing.OutCubic}}
125− Behavior on width {NumberAnimation { duration: 150; easing.type: Easing.OutCubic}}
126− }
127− }
128−}
qml/Dash/DashContent.qml+123−62
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity 0.1
20+import "../Components"
2021
2122Item {
2223 id: dashContent
@@ -33,7 +34,6 @@ Item {
3334 signal movementEnded()
3435 signal contentFlickStarted()
3536 signal contentEndReached()
36− signal previewShown()
3737 signal scopeLoaded(string scopeId)
3838 signal positionedAtBeginning()
3939
@@ -80,77 +80,138 @@ Item {
8080 }
8181 }
8282
83− ListView {
84− id: dashContentList
85− objectName: "dashContentList"
86−
87− interactive: dashContent.scopes.loaded && !currentItem.previewShown && !currentItem.moving
88−
83+ Item {
84+ id: dashContentListHolder
8985 anchors.fill: parent
90− model: dashContent.model
91− orientation: ListView.Horizontal
92− boundsBehavior: Flickable.DragAndOvershootBounds
93− flickDeceleration: units.gu(625)
94− maximumFlickVelocity: width * 5
95− snapMode: ListView.SnapOneItem
96− highlightMoveDuration: 250
97− highlightRangeMode: ListView.StrictlyEnforceRange
98− /* FIXME: workaround rendering issue due to use of ShaderEffectSource in
99− UbuntuShape. While switching from the home scope to the People scope the
100− rendering would block midway.
101− */
102− cacheBuffer: 2147483647
103− onMovementStarted: dashContent.movementStarted()
104− onMovementEnded: dashContent.movementEnded()
105−
106− // If the number of items is less than the current index, then need to reset to another item.
107− onCountChanged: {
108− if (count > 0) {
109− if (currentIndex >= count) {
110− dashContent.setCurrentScopeAtIndex(count-1, true, true)
111− } else if (currentIndex < 0) {
112− // setting currentIndex directly, cause we don't want to loose set_current_index
113− dashContent.currentIndex = 0
86+
87+ ListView {
88+ id: dashContentList
89+ objectName: "dashContentList"
90+
91+ interactive: dashContent.scopes.loaded && !previewListView.onScreen && !currentItem.moving
92+
93+ anchors.fill: parent
94+ model: dashContent.model
95+ orientation: ListView.Horizontal
96+ boundsBehavior: Flickable.DragAndOvershootBounds
97+ flickDeceleration: units.gu(625)
98+ maximumFlickVelocity: width * 5
99+ snapMode: ListView.SnapOneItem
100+ highlightMoveDuration: 250
101+ highlightRangeMode: ListView.StrictlyEnforceRange
102+ // TODO Investigate if we can switch to a smaller cache buffer when/if UbuntuShape gets more performant
103+ cacheBuffer: 1073741823
104+ onMovementStarted: dashContent.movementStarted()
105+ onMovementEnded: dashContent.movementEnded()
106+
107+ // If the number of items is less than the current index, then need to reset to another item.
108+ onCountChanged: {
109+ if (count > 0) {
110+ if (currentIndex >= count) {
111+ dashContent.setCurrentScopeAtIndex(count-1, true, true)
112+ } else if (currentIndex < 0) {
113+ // setting currentIndex directly, cause we don't want to loose set_current_index
114+ dashContent.currentIndex = 0
115+ }
114116 }
115117 }
118+
119+ delegate:
120+ Loader {
121+ width: ListView.view.width
122+ height: ListView.view.height
123+ asynchronous: true
124+ source: scopeMapper.map(scope.id)
125+ objectName: scope.id + " loader"
126+
127+ readonly property bool moving: item ? item.moving : false
128+ readonly property var categoryView: item ? item.categoryView : null
129+ readonly property Scope theScope: scope
130+
131+ // these are needed for autopilot tests
132+ readonly property string scopeId: scope.id
133+ readonly property bool isCurrent: ListView.isCurrentItem
134+ readonly property bool isLoaded: status == Loader.Ready
135+
136+ onLoaded: {
137+ item.scope = Qt.binding(function() { return scope })
138+ item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
139+ item.tabBarHeight = pageHeader.implicitHeight;
140+ item.pageHeader = pageHeader;
141+ item.openEffect = openEffect;
142+ item.previewListView = previewListView;
143+ dashContentList.movementStarted.connect(item.movementStarted)
144+ dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
145+ dashContent.scopeLoaded(item.scope.id)
146+ }
147+ Connections {
148+ target: item
149+ ignoreUnknownSignals: true
150+ onEndReached: contentEndReached()
151+ }
152+
153+ Component.onDestruction: active = false
154+ }
116155 }
117156
118− delegate:
119− Loader {
120− width: ListView.view.width
121− height: ListView.view.height
122− asynchronous: true
123− source: scopeMapper.map(scope.id)
124− objectName: scope.id + " loader"
125−
126− readonly property bool previewShown: item ? item.previewShown : false
127− readonly property bool moving: item ? item.moving : false
128−
129− // these are needed for autopilot tests
130− readonly property string scopeId: scope.id
131− readonly property bool isCurrent: ListView.isCurrentItem
132− readonly property bool isLoaded: status == Loader.Ready
133−
134− onLoaded: {
135− item.scope = Qt.binding(function() { return scope })
136− item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
137− item.searchHistory = Qt.binding(function() { return dashContent.searchHistory })
138− dashContentList.movementStarted.connect(item.movementStarted)
139− dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
140− dashContent.scopeLoaded(item.scope.id)
157+ PageHeader {
158+ id: pageHeader
159+ width: parent.width
160+ searchEntryEnabled: true
161+ searchHistory: dashContent.searchHistory
162+
163+ childItem: TabBar {
164+ id: tabBar
165+ objectName: "tabbar"
166+ height: units.gu(7)
167+ width: parent.width
168+ selectionMode: false
169+ style: DashContentTabBarStyle {}
170+
171+ model: dashContentList.model
172+
173+ onSelectedIndexChanged: {
174+ dashContentList.currentIndex = selectedIndex;
141175 }
176+
142177 Connections {
143− target: item
144− ignoreUnknownSignals: true
145− onEndReached: contentEndReached()
146− onPreviewShownChanged: {
147− if (item.previewShown) {
148− dashContent.previewShown()
149− }
178+ target: dashContentList
179+ onCurrentIndexChanged: {
180+ tabBar.selectedIndex = dashContentList.currentIndex
150181 }
151182 }
152183
153− Component.onDestruction: active = false
184+ Connections {
185+ target: model
186+ onCountChanged: {
187+ if (tabBar.selectedIndex < 0 && model.count > 0)
188+ tabBar.selectedIndex = 0;
189+ }
190+ }
191+
192+ Component.onCompleted: {
193+ __styleInstance.headerTextStyle = Text.Raised
194+ __styleInstance.headerTextStyleColor = "black"
195+ }
154196 }
197+ }
198+ }
199+
200+ DashContentOpenEffect {
201+ id: openEffect
202+ anchors {
203+ fill: parent
204+ bottomMargin: -bottomOverflow
205+ }
206+ sourceItem: dashContentListHolder
207+ previewListView: previewListView
208+ }
209+
210+ PreviewListView {
211+ id: previewListView
212+ openEffect: openEffect
213+ categoryView: dashContentList.currentItem ? dashContentList.currentItem.categoryView : null
214+ scope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
215+ anchors.fill: parent
155216 }
156217}
qml/Dash/DashContentOpenEffect.qmlnew file+57
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+OpenEffect {
22+ property PreviewListView previewListView: null
23+
24+ objectName: "openEffect"
25+ anchors {
26+ fill: parent
27+ bottomMargin: -bottomOverflow
28+ }
29+
30+ enabled: gap > 0.0
31+
32+ topGapPx: (1 - gap) * positionPx
33+ topOpacity: (1 - gap * 1.2)
34+ bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
35+ bottomOverflow: units.gu(20)
36+ live: !expansionAnimation.running
37+
38+ property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
39+ property real gap: previewListView.open ? 1.0 : 0.0
40+
41+ Behavior on gap {
42+ NumberAnimation {
43+ id: expansionAnimation
44+ duration: 200
45+ easing.type: Easing.InOutQuad
46+ onRunningChanged: {
47+ if (!previewListView.open && !running) {
48+ previewListView.onScreen = false
49+ }
50+ }
51+ }
52+ }
53+ Behavior on positionPx {
54+ enabled: previewListView.open
55+ UbuntuNumberAnimation {}
56+ }
57+}
qml/Dash/DashContentTabBarStyle.qmlnew file+21
@@ -0,0 +1,21 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import Ubuntu.Components.Themes.Ambiance 0.1
18+
19+TabBarStyle {
20+ indicatorImageSource: "graphics/tabbarchevron.png"
21+}
qml/Dash/GenericScopeView.qml+28−228
@@ -27,15 +27,18 @@ FocusScope {
2727 property Scope scope
2828 property SortFilterProxyModel categories: categoryFilter
2929 property bool isCurrent
30− property ListModel searchHistory
3130 property alias moving: categoryView.moving
31+ property int tabBarHeight: 0
32+ property PageHeader pageHeader: null
33+ property OpenEffect openEffect: null
34+ property Item previewListView: null
3235
3336 signal endReached
3437 signal movementStarted
3538 signal positionedAtBeginning
3639
37− readonly property alias previewShown: previewListView.onScreen
3840 property bool enableHeightBehaviorOnNextCreation: false
41+ property var categoryView: categoryView
3942
4043 // FIXME delay the search so that daemons have time to settle, note that
4144 // removing this will break ScopeView::test_changeScope
@@ -84,12 +87,14 @@ FocusScope {
8487 target: scopeView.scope
8588 property: "searchQuery"
8689 value: pageHeader.searchQuery
90+ when: isCurrent
8791 }
8892
8993 Binding {
9094 target: pageHeader
9195 property: "searchQuery"
9296 value: scopeView.scope.searchQuery
97+ when: isCurrent
9398 }
9499
95100 Connections {
@@ -119,6 +124,10 @@ FocusScope {
119124 property string expandedCategoryId: ""
120125 signal correctExpandedCategory();
121126
127+ onContentYChanged: pageHeader.positionRealHeader();
128+ onOriginYChanged: pageHeader.positionRealHeader();
129+ onContentHeightChanged: pageHeader.positionRealHeader();
130+
122131 Behavior on contentY {
123132 enabled: previewListView.open
124133 UbuntuNumberAnimation {}
@@ -176,7 +185,7 @@ FocusScope {
176185 target: rendererLoader.item
177186 onClicked: {
178187 // Prepare the preview in case activate() triggers a preview only
179− effect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
188+ openEffect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
180189 previewListView.categoryId = categoryId
181190 previewListView.categoryDelegate = rendererLoader.item
182191 previewListView.model = target.model;
@@ -197,7 +206,7 @@ FocusScope {
197206 }
198207 }
199208 onPressAndHold: {
200− effect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
209+ openEffect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
201210 previewListView.categoryId = categoryId
202211 previewListView.categoryDelegate = rendererLoader.item
203212 previewListView.model = target.model;
@@ -288,14 +297,20 @@ FocusScope {
288297 categoryView.expandedCategoryId = "";
289298 }
290299 }
291− pageHeader: PageHeader {
292− id: pageHeader
293− objectName: "pageHeader"
294− width: categoryView.width
295− text: scopeView.scope.name
296− searchEntryEnabled: true
297− scope: scopeView.scope
298− searchHistory: scopeView.searchHistory
300+ pageHeader: Item {
301+ implicitHeight: scopeView.tabBarHeight
302+ onHeightChanged: {
303+ if (scopeView.pageHeader) {
304+ scopeView.pageHeader.height = height;
305+ }
306+ }
307+ onYChanged: positionRealHeader();
308+
309+ function positionRealHeader() {
310+ if (scopeView.pageHeader) {
311+ scopeView.pageHeader.y = y + parent.y;
312+ }
313+ }
299314 }
300315 }
301316
@@ -346,44 +361,6 @@ FocusScope {
346361 }
347362 }
348363
349− OpenEffect {
350− id: effect
351− objectName: "openEffect"
352− anchors {
353− fill: parent
354− bottomMargin: -bottomOverflow
355− }
356− sourceItem: categoryView
357−
358− enabled: gap > 0.0
359−
360− topGapPx: (1 - gap) * positionPx
361− topOpacity: (1 - gap * 1.2)
362− bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
363− bottomOverflow: units.gu(20)
364− live: !expansionAnimation.running
365−
366− property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
367− property real gap: previewListView.open ? 1.0 : 0.0
368−
369− Behavior on gap {
370− NumberAnimation {
371− id: expansionAnimation
372− duration: 200
373− easing.type: Easing.InOutQuad
374− onRunningChanged: {
375− if (!previewListView.open && !running) {
376− previewListView.onScreen = false
377− }
378− }
379− }
380− }
381− Behavior on positionPx {
382− enabled: previewListView.open
383− UbuntuNumberAnimation {}
384− }
385− }
386−
387364 Connections {
388365 target: scopeView.scope
389366 onPreviewReady: {
@@ -402,190 +379,13 @@ FocusScope {
402379 }
403380 }
404381
405− PreviewDelegateMapper {
406− id: previewDelegateMapper
407− }
408−
409− ListView {
410− id: previewListView
411− objectName: "previewListView"
412− height: effect.bottomGapPx - effect.topGapPx
413− anchors {
414− top: parent.top
415− topMargin: effect.topGapPx
416− left: parent.left
417− right: parent.right
418− }
419− orientation: ListView.Horizontal
420− highlightRangeMode: ListView.StrictlyEnforceRange
421− snapMode: ListView.SnapOneItem
422− boundsBehavior: Flickable.DragAndOvershootBounds
423− highlightMoveDuration: 250
424− flickDeceleration: units.gu(625)
425− maximumFlickVelocity: width * 5
426− cacheBuffer: 0
427−
428− // To be set before opening the preview
429− property string categoryId: ""
430− property var categoryDelegate
431−
432− // because the ListView is built asynchronous, setting the
433− // currentIndex directly won't work. We need to refresh it
434− // when the first preview is ready to be displayed.
435− property bool init: true
436−
437− onCurrentIndexChanged: {
438− var row = Math.floor(currentIndex / categoryDelegate.columns);
439− if (categoryDelegate.collapsedRowCount <= row) {
440− categoryView.expandedCategoryId = categoryId
441− }
442−
443− if (open) {
444− categoryDelegate.highlightIndex = currentIndex
445− }
446−
447− if (!init && model !== undefined) {
448− var item = model.get(currentIndex)
449− scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
450− }
451−
452− // Adjust contentY in case we need to change to it to show the next row
453− if (categoryDelegate.rows > 1) {
454− var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
455−
456− // Find new contentY and effect.postionPx
457− var newContentY = itemY - effect.positionPx - categoryDelegate.verticalSpacing;
458−
459− // Make sure the item is not covered by a header. Move the effect split down if necessary
460− var headerHeight = pageHeader.height + categoryView.stickyHeaderHeight;
461− var effectAdjust = Math.max(effect.positionPx, headerHeight);
462−
463− // Make sure we don't overscroll the listview. If yes, adjust effect position
464− if (newContentY < 0) {
465− effectAdjust += newContentY;
466− newContentY = 0;
467− }
468− if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
469− effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
470− newContentY = categoryView.contentHeight - categoryView.height;
471− }
472−
473− effect.positionPx = effectAdjust;
474− categoryView.contentY = newContentY;
475− }
476− }
477−
478− property bool open: false
479− property bool onScreen: false
480−
481− onOpenChanged: {
482− if (open) {
483− onScreen = true;
484− categoryDelegate.highlightIndex = currentIndex;
485− pageHeader.unfocus();
486− } else {
487− // Cancel any pending preview requests or actions
488− if (previewListView.currentItem.previewData !== undefined) {
489− previewListView.currentItem.previewData.cancelAction();
490− }
491− scopeView.scope.cancelActivation();
492− model = undefined;
493− categoryView.correctExpandedCategory();
494− categoryDelegate.highlightIndex = -1;
495− }
496− }
497−
498− Rectangle {
499− anchors.fill: parent
500− color: Qt.rgba(0, 0, 0, .3)
501− z: -1
502− }
503−
504− delegate: Loader {
505− id: previewLoader
506− objectName: "previewLoader" + index
507− height: previewListView.height
508− width: previewListView.width
509− asynchronous: true
510− source: previewListView.onScreen ?
511− (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
512−
513− onPreviewDataChanged: {
514− if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
515− previewLoader.opacity = 0;
516− }
517− }
518−
519− onSourceChanged: {
520− if (previewData !== undefined) {
521− fadeIn.start()
522− }
523− }
524−
525− PropertyAnimation {
526− id: fadeIn
527− target: previewLoader
528− property: "opacity"
529− from: 0.0
530− to: 1.0
531− duration: UbuntuAnimation.BriskDuration
532− }
533−
534− property var previewData
535− property bool valid: item !== null
536−
537− onLoaded: {
538− if (previewListView.onScreen && previewData !== undefined) {
539− item.previewData = Qt.binding(function() { return previewData })
540− item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
541− }
542− }
543−
544− Connections {
545− ignoreUnknownSignals: true
546− target: item
547− onClose: {
548− previewListView.open = false
549− }
550− }
551−
552− function closePreviewSpinner() {
553− if (item) {
554− item.showProcessingAction = false;
555− }
556− }
557− }
558− }
559−
560− Image {
561− objectName: "pointerArrow"
562− anchors {
563− top: previewListView.bottom
564− left: parent.left
565− leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
566− previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
567−
568− Behavior on leftMargin {
569− SmoothedAnimation {
570− duration: UbuntuAnimation.FastDuration
571− }
572− }
573− }
574− height: units.gu(1)
575− width: units.gu(2)
576− property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
577− opacity: previewListView.open ? .5 : 0
578−
579− source: "graphics/tooltip_arrow.png"
580− }
581−
582382 // TODO: Move as InverseMouseArea to DashPreview
583383 MouseArea {
584384 objectName: "closePreviewMouseArea"
585385 enabled: previewListView.onScreen
586386 anchors {
587387 fill: parent
588− topMargin: effect.bottomGapPx
388+ topMargin: openEffect.bottomGapPx
589389 }
590390 onClicked: {
591391 previewListView.open = false;
qml/Dash/PreviewListView.qmlnew file+212
@@ -0,0 +1,212 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Unity 0.1
20+import "../Components"
21+
22+Item {
23+ property OpenEffect openEffect: null
24+ property ScopeListView categoryView: null
25+ property Scope scope: null
26+
27+ property alias open: previewListView.open
28+ property alias onScreen: previewListView.onScreen
29+ property alias categoryId: previewListView.categoryId
30+ property alias categoryDelegate: previewListView.categoryDelegate
31+ property alias init: previewListView.init
32+ property alias model: previewListView.model
33+ property alias currentIndex: previewListView.currentIndex
34+ property alias currentItem: previewListView.currentItem
35+
36+ Image {
37+ objectName: "pointerArrow"
38+ anchors {
39+ top: previewListView.bottom
40+ left: parent.left
41+ leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
42+ previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
43+
44+ Behavior on leftMargin {
45+ SmoothedAnimation {
46+ duration: UbuntuAnimation.FastDuration
47+ }
48+ }
49+ }
50+ height: units.gu(1)
51+ width: units.gu(2)
52+ property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
53+ opacity: previewListView.open ? .5 : 0
54+
55+ source: "graphics/tooltip_arrow.png"
56+ }
57+
58+ ListView {
59+ id: previewListView
60+ objectName: "previewListView"
61+ height: openEffect.bottomGapPx - openEffect.topGapPx
62+ anchors {
63+ top: parent.top
64+ topMargin: openEffect.topGapPx
65+ left: parent.left
66+ right: parent.right
67+ }
68+ orientation: ListView.Horizontal
69+ highlightRangeMode: ListView.StrictlyEnforceRange
70+ snapMode: ListView.SnapOneItem
71+ boundsBehavior: Flickable.DragAndOvershootBounds
72+ highlightMoveDuration: 250
73+ flickDeceleration: units.gu(625)
74+ maximumFlickVelocity: width * 5
75+ cacheBuffer: 0
76+
77+ // To be set before opening the preview
78+ property string categoryId: ""
79+ property var categoryDelegate
80+
81+ // because the ListView is built asynchronous, setting the
82+ // currentIndex directly won't work. We need to refresh it
83+ // when the first preview is ready to be displayed.
84+ property bool init: true
85+
86+ PreviewDelegateMapper {
87+ id: previewDelegateMapper
88+ }
89+
90+ onCurrentIndexChanged: {
91+ var row = Math.floor(currentIndex / categoryDelegate.columns);
92+ if (categoryDelegate.collapsedRowCount <= row) {
93+ categoryView.expandedCategoryId = categoryId
94+ }
95+
96+ if (open) {
97+ categoryDelegate.highlightIndex = currentIndex
98+ }
99+
100+ if (!init && model !== undefined) {
101+ var item = model.get(currentIndex)
102+ scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
103+ }
104+
105+ // Adjust contentY in case we need to change to it to show the next row
106+ if (categoryDelegate.rows > 1) {
107+ var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
108+
109+ // Find new contentY and effect.postionPx
110+ var newContentY = itemY - openEffect.positionPx - categoryDelegate.verticalSpacing;
111+
112+ // Make sure the item is not covered by a header. Move the effect split down if necessary
113+ var headerHeight = pageHeader.height + categoryView.stickyHeaderHeight;
114+ var effectAdjust = Math.max(openEffect.positionPx, headerHeight);
115+
116+ // Make sure we don't overscroll the listview. If yes, adjust effect position
117+ if (newContentY < 0) {
118+ effectAdjust += newContentY;
119+ newContentY = 0;
120+ }
121+ if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
122+ effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
123+ newContentY = categoryView.contentHeight - categoryView.height;
124+ }
125+
126+ openEffect.positionPx = effectAdjust;
127+ categoryView.contentY = newContentY;
128+ }
129+ }
130+
131+ property bool open: false
132+ property bool onScreen: false
133+
134+ onOpenChanged: {
135+ if (open) {
136+ onScreen = true;
137+ categoryDelegate.highlightIndex = currentIndex;
138+ pageHeader.unfocus();
139+ } else {
140+ // Cancel any pending preview requests or actions
141+ if (previewListView.currentItem.previewData !== undefined) {
142+ previewListView.currentItem.previewData.cancelAction();
143+ }
144+ scope.cancelActivation();
145+ model = undefined;
146+ categoryView.correctExpandedCategory();
147+ categoryDelegate.highlightIndex = -1;
148+ }
149+ }
150+
151+ Rectangle {
152+ anchors.fill: parent
153+ color: Qt.rgba(0, 0, 0, .3)
154+ z: -1
155+ }
156+
157+ delegate: Loader {
158+ id: previewLoader
159+ objectName: "previewLoader" + index
160+ height: previewListView.height
161+ width: previewListView.width
162+ asynchronous: true
163+ source: previewListView.onScreen ?
164+ (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
165+
166+ onPreviewDataChanged: {
167+ if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
168+ previewLoader.opacity = 0;
169+ }
170+ }
171+
172+ onSourceChanged: {
173+ if (previewData !== undefined) {
174+ fadeIn.start()
175+ }
176+ }
177+
178+ PropertyAnimation {
179+ id: fadeIn
180+ target: previewLoader
181+ property: "opacity"
182+ from: 0.0
183+ to: 1.0
184+ duration: UbuntuAnimation.BriskDuration
185+ }
186+
187+ property var previewData
188+ property bool valid: item !== null
189+
190+ onLoaded: {
191+ if (previewListView.onScreen && previewData !== undefined) {
192+ item.previewData = Qt.binding(function() { return previewData })
193+ item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
194+ }
195+ }
196+
197+ Connections {
198+ ignoreUnknownSignals: true
199+ target: item
200+ onClose: {
201+ previewListView.open = false
202+ }
203+ }
204+
205+ function closePreviewSpinner() {
206+ if (item) {
207+ item.showProcessingAction = false;
208+ }
209+ }
210+ }
211+ }
212+}
qml/Dash/graphics/tabbarchevron@27.pngnew file

Binary file not shown.

qml/Hud/HudParametrizedActionsPage.qml+1−1
@@ -89,7 +89,7 @@ Item {
8989 anchors.left: parent.left
9090 anchors.right: parent.right
9191
92− PageHeader {
92+ PageHeaderLabel {
9393 id: header
9494 anchors.top: parent.top
9595 anchors.left: parent.left
qml/Panel/IndicatorItem.qml+6−12
@@ -23,10 +23,10 @@ Item {
2323 id: indicatorItem
2424
2525 property alias widgetSource: loader.source
26− property bool highlighted: false
2726 property bool dimmed: false
2827 property var indicatorProperties: undefined
2928 property bool indicatorVisible: loader.item ? loader.item.enabled : false
29+ property string identifier
3030
3131 opacity: dimmed ? 0.4 : 1
3232 Behavior on opacity { StandardAnimation {} }
@@ -45,17 +45,11 @@ Item {
4545 }
4646 }
4747 }
48− }
49−
50− Rectangle {
51− color: Theme.palette.selected.foreground
52− objectName: "highlight"
53− height: units.dp(2)
54− width: parent.width
55− anchors.top: parent.bottom
56− visible: highlighted
5748
58− opacity: highlighted ? 1 : 0
59− Behavior on opacity { StandardAnimation {} }
49+ Binding {
50+ target: loader.item
51+ property: "objectName"
52+ value: identifier + "-widget"
53+ }
6054 }
6155}
qml/Panel/IndicatorRow.qml+40−8
@@ -29,6 +29,8 @@ Item {
2929 property var visibleIndicators: defined
3030 property int overFlowWidth: width
3131 property bool showAll: false
32+ property real currentItemOffset: 0.0
33+ property real unitProgress: 0.0
3234
3335 width: units.gu(40)
3436 height: units.gu(3)
@@ -79,23 +81,24 @@ Item {
7981 height: indicatorRow.height
8082 width: indicatorItem.width
8183 visible: indicatorItem.indicatorVisible
82− opacity: 1.0
84+ opacity: 1.0 * opacityMultiplier
8385 y: 0
8486 state: "standard"
8587
8688 property int ownIndex: index
87− property alias highlighted: indicatorItem.highlighted
88− property alias dimmed: indicatorItem.dimmed
89+ property bool highlighted: indicatorRow.state != "initial" ? ownIndex == indicatorRow.currentItemIndex : false
90+ property bool dimmed: indicatorRow.state != "initial" ? ownIndex != indicatorRow.currentItemIndex : false
8991
90− property bool hidden: !showAll && !indicatorItem.highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")
92+ property bool hidden: !showAll && !highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")
9193 property bool overflow: row.width - itemWrapper.x > overFlowWidth
94+ property real opacityMultiplier: highlighted ? 1 : (1 - indicatorRow.unitProgress)
9295
9396 IndicatorItem {
9497 id: indicatorItem
98+ identifier: model.identifier
9599 height: parent.height
96100
97− highlighted: indicatorRow.state != "initial" ? itemWrapper.ownIndex == indicatorRow.currentItemIndex : false
98− dimmed: indicatorRow.state != "initial" ? itemWrapper.ownIndex != indicatorRow.currentItemIndex : false
101+ dimmed: itemWrapper.dimmed
99102
100103 widgetSource: model.widgetSource
101104 indicatorProperties : model.indicatorProperties
@@ -135,14 +138,43 @@ Item {
135138
136139 transitions: [
137140 Transition {
138− StandardAnimation {
141+ UbuntuNumberAnimation {
139142 target: itemWrapper
140143 property: "opacity"
141− duration: 300
144+ duration: UbuntuAnimation.BriskDuration
142145 }
143146 }
144147 ]
145148 }
146149 }
147150 }
151+
152+ Rectangle {
153+ id: highlight
154+ color: Theme.palette.selected.foreground
155+ objectName: "highlight"
156+ height: units.dp(2)
157+ anchors.top: row.bottom
158+ visible: indicatorRow.currentItem != null
159+ x: row.x + (indicatorRow.currentItem != null ? indicatorRow.currentItem.x + centerOffset : 0)
160+ width: indicatorRow.currentItem != null ? indicatorRow.currentItem.width : 0
161+
162+ property real centerOffset: {
163+ if (indicatorRow.currentItemOffset > 0.1) {
164+ return (indicatorRow.currentItemOffset - 0.1) * units.gu(0.4);
165+ } else if (indicatorRow.currentItemOffset < -0.1) {
166+ return (indicatorRow.currentItemOffset + 0.1) * units.gu(0.4);
167+ }
168+ return 0.0;
169+ }
170+
171+ Behavior on width {
172+ enabled: unitProgress > 0;
173+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
174+ }
175+ Behavior on x {
176+ enabled: unitProgress > 0;
177+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
178+ }
179+ }
148180}
qml/Panel/Indicators.qml+22−11
@@ -39,6 +39,7 @@ Showable {
3939 property bool contentEnabled: true
4040 property bool initalizeItem: true
4141 readonly property alias content: menuContent
42+ property real unitProgress: 0.0
4243
4344 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
4445 // use its own values. Need to ask design about this.
@@ -128,23 +129,31 @@ Showable {
128129 rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
129130 // get the current delegate
130131 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
131− if (currentItem && currentItem != indicatorRow.currentItem ) {
132+ if (currentItem) {
132133 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
133134 distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
134− if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
135− bufferExceeded = true;
136− } else {
137− if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
138− bufferExceeded = true;
139− } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
135+ if (currentItem != indicatorRow.currentItem) {
136+ if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
140137 bufferExceeded = true;
138+ } else {
139+ if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
140+ bufferExceeded = true;
141+ } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
142+ bufferExceeded = true;
143+ }
141144 }
145+ if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItemIndex < 0 || indicatorRow.currentItem == null) {
146+ indicatorRow.currentItem = currentItem;
147+ }
148+
149+ // need to re-init the distanceFromRightEdge for offset calculation
150+ itemCoordinates = indicatorRow.row.mapToItem(indicatorRow.currentItem, rowCoordinates.x, 0);
151+ distanceFromRightEdge = (indicatorRow.currentItem.width - itemCoordinates.x) / (indicatorRow.currentItem.width);
142152 }
143− if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null) {
144− indicatorRow.currentItem = currentItem;
145− }
146− } else if (!currentItem) {
153+ indicatorRow.currentItemOffset = 1 - (distanceFromRightEdge * 2);
154+ } else {
147155 indicatorRow.setDefaultItem();
156+ indicatorRow.currentItemOffset = 0;
148157 }
149158 initalizeItem = indicatorRow.currentItem == null;
150159 }
@@ -268,6 +277,7 @@ Showable {
268277 height: indicators.panelHeight
269278 indicatorsModel: indicatorsModel
270279 state: indicators.state
280+ unitProgress: indicators.unitProgress
271281
272282 Connections {
273283 property bool enableIndexChangeSignal: true
@@ -326,6 +336,7 @@ Showable {
326336 if (hideAnimation.running) {
327337 indicators.state = "initial";
328338 initalizeItem = true;
339+ menuContent.animateNextMenuChange = false;
329340 }
330341 }
331342 }
qml/Panel/MenuContent.qml+3−1
@@ -33,6 +33,7 @@ MainView {
3333 property int contentReleaseInterval: 20000
3434 property bool activeHeader: false
3535 property alias visibleIndicators: visibleIndicatorsModel.visible
36+ property bool animateNextMenuChange: false
3637
3738 width: units.gu(40)
3839 height: units.gu(42)
@@ -41,11 +42,12 @@ MainView {
4142 var filteredIndex = filteredIndicators.mapFromSource(index)
4243
4344 if (tabs.selectedTabIndex !== filteredIndex) {
44− if (tabs.selectedTabIndex == -1) {
45+ if (tabs.selectedTabIndex == -1 || !animateNextMenuChange) {
4546 tabs.tabBar.animate = false;
4647 }
4748 tabs.selectedTabIndex = filteredIndex;
4849 tabs.tabBar.animate = true;
50+ animateNextMenuChange = true;
4951 }
5052 }
5153
qml/Panel/Panel.qml+1−1
@@ -107,7 +107,7 @@ Item {
107107 pinnedMode: !fullscreenMode
108108 overFlowWidth: search.state=="hidden" ? parent.width : parent.width - search.width
109109
110− property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
110+ unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
111111 }
112112
113113 PanelSeparatorLine {
qml/ScopeTool.qmlnew file+175
@@ -0,0 +1,175 @@
1+/*
2+* Copyright (C) 2013 Canonical, Ltd.
3+*
4+* This program is free software; you can redistribute it and/or modify
5+* it under the terms of the GNU General Public License as published by
6+* the Free Software Foundation; version 3.
7+*
8+* This program is distributed in the hope that it will be useful,
9+* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+* GNU General Public License for more details.
12+*
13+* You should have received a copy of the GNU General Public License
14+* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Ubuntu.Components.Popups 0.1
20+import Utils 0.1
21+import Unity 0.1
22+import "Components"
23+import "Dash"
24+
25+Rectangle {
26+ id: root
27+ width: units.gu(80)
28+ height: units.gu(72)
29+ color: "#88FFFFFF"
30+
31+ // Fake shell object
32+ QtObject {
33+ id: shell
34+ }
35+
36+ // Fake greeter object
37+ QtObject {
38+ id: greeter
39+ property bool shown
40+ }
41+
42+ // Fake panel object
43+ QtObject {
44+ id: panel
45+ signal searchClicked
46+ }
47+
48+ SortFilterProxyModel {
49+ id: filteredScopes
50+ model: Scopes {
51+ id: scopes
52+ }
53+ dynamicSortFilter: true
54+
55+ filterRole: Scopes.RoleVisible
56+ filterRegExp: RegExp("^true$")
57+ }
58+
59+ Rectangle {
60+ anchors.fill: dashContent
61+ color: "#FCFCFC"
62+ }
63+
64+ DashContent {
65+ id: dashContent
66+
67+ model: filteredScopes
68+ searchHistory: SearchHistoryModel { }
69+ property Scope scope: scopes.get(currentIndex)
70+
71+ anchors {
72+ top: parent.top
73+ bottom: parent.bottom
74+ left: parent.left
75+ right: controls.left
76+ }
77+ }
78+
79+ Rectangle {
80+ id: controls
81+ color: "lightgrey"
82+ width: units.gu(40)
83+ anchors {
84+ top: parent.top
85+ bottom: parent.bottom
86+ right: parent.right
87+ }
88+
89+ MouseArea {
90+ anchors.fill: parent
91+ }
92+
93+ Column {
94+ anchors { fill: parent; margins: units.gu(1) }
95+ spacing: units.gu(1)
96+
97+ Label {
98+ text: "Search query"
99+ }
100+
101+ TextField {
102+ id: searchField
103+ anchors { left: parent.left; right: parent.right }
104+
105+ onTextChanged: dashContent.scope.searchQuery = text
106+
107+ Connections {
108+ target: dashContent.scope
109+ onSearchQueryChanged: searchField.text = dashContent.scope.searchQuery
110+ }
111+ }
112+
113+ Label {
114+ text: "Category"
115+ height: units.gu(4)
116+ verticalAlignment: Text.AlignBottom
117+ }
118+
119+ OptionSelector {
120+ id: categorySelector
121+ anchors { left: parent.left; right: parent.right }
122+ model: dashContent.scope ? dashContent.scope.categories : null
123+
124+ property Item selectedItem
125+
126+ delegate: OptionSelectorDelegate {
127+ id: categoryDelegate
128+ text: model.name
129+ property string categoryId: model.categoryId
130+ property string template: JSON.stringify(JSON.parse(model.rawRendererTemplate), null, " ");
131+
132+ onSelectedChanged: if (selected) categorySelector.selectedItem = categoryDelegate
133+ }
134+ }
135+
136+ TextArea {
137+ id: categoryJson
138+ width: parent.width
139+ autoSize: true
140+ readOnly: true
141+ text: categorySelector.selectedItem && categorySelector.selectedItem.template
142+ }
143+
144+ Button {
145+ width: parent.width
146+ text: "Override category"
147+ onClicked: {
148+ PopupUtils.open(categoryEditor)
149+ }
150+ }
151+ }
152+ }
153+
154+ Component {
155+ id: categoryEditor
156+
157+ ComposerSheet {
158+ id: sheet
159+ title: "Editing category definition"
160+
161+ TextArea {
162+ id: categoryEditorArea
163+ anchors.fill: parent
164+ wrapMode: Text.WordWrap
165+ text: categoryJson.text
166+ }
167+
168+ onCancelClicked: PopupUtils.close(sheet)
169+ onConfirmClicked: {
170+ PopupUtils.close(sheet);
171+ dashContent.scope.categories.overrideCategoryJson(categorySelector.selectedItem.categoryId, categoryEditorArea.text);
172+ }
173+ }
174+ }
175+}
src/CMakeLists.txt+12−1
@@ -5,11 +5,22 @@ include_directories(
55 ${UNITY-MIR_INCLUDE_DIRS}
66)
77
8+file(GLOB_RECURSE QML_FILES
9+ ${CMAKE_SOURCE_DIR}/qml/*
10+ ${CMAKE_SOURCE_DIR}/tests/*.qml
11+ ${CMAKE_SOURCE_DIR}/tests/*.js
12+ ${CMAKE_SOURCE_DIR}/tests/*.jpg
13+ ${CMAKE_SOURCE_DIR}/tests/*.png
14+ ${CMAKE_SOURCE_DIR}/tests/*.sci
15+ ${CMAKE_SOURCE_DIR}/tests/*.svg
16+)
17+
818add_executable(${SHELL_APP}
919 ApplicationArguments.h
1020 main.cpp
1121 MouseTouchAdaptor.cpp
12− )
22+ ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
23+)
1324
1425qt5_use_modules(${SHELL_APP} Gui Qml Quick Test)
1526pkg_check_modules(XCB REQUIRED xcb)
tests/autopilot/unity8/indicators/__init__.pynew file

No changes to the content.

tests/autopilot/unity8/indicators/tests/__init__.pynew file

No changes to the content.

tests/autopilot/unity8/indicators/tests/test_indicators.pynew file+50
@@ -0,0 +1,50 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity - Indicators Autopilot Test Suite
4+# Copyright (C) 2013 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+from __future__ import absolute_import
21+
22+from autopilot import platform
23+from testtools import skipIf
24+
25+from unity8.process_helpers import unlock_unity
26+from unity8.shell.tests import UnityTestCase
27+
28+
29+class IndicatorTestCase(UnityTestCase):
30+
31+ scenarios = [
32+ ('Bluetooth', dict(indicator_name='indicator-bluetooth')),
33+ ('Datetime', dict(indicator_name='indicator-datetime')),
34+ ('Location', dict(indicator_name='indicator-location')),
35+ ('Messaging', dict(indicator_name='indicator-messages')),
36+ ('Network', dict(indicator_name='indicator-network')),
37+ ('Power', dict(indicator_name='indicator-power')),
38+ ('Sound', dict(indicator_name='indicator-sound')),
39+ ]
40+
41+ @skipIf(platform.image_codename() == 'Desktop',
42+ "Unity8/phablet-only test!")
43+ def setUp(self):
44+ super(IndicatorTestCase, self).setUp()
45+
46+ def test_indicator_exists(self):
47+ unity_proxy = self.launch_unity()
48+ unlock_unity(unity_proxy)
49+ indicator = self.main_window.get_indicator(self.indicator_name)
50+ self.assertIsNotNone(indicator)
tests/autopilot/unity8/shell/emulators/__init__.py+11−4
@@ -1,7 +1,7 @@
11# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
22#
33# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
55#
66# This program is free software: you can redistribute it and/or modify
77# it under the terms of the GNU General Public License as published by
@@ -17,9 +17,16 @@
1717# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818#
1919
20+from autopilot import input, introspection
2021
21−from autopilot.introspection import CustomEmulatorBase
2222
23−
24−class UnityEmulatorBase(CustomEmulatorBase):
23+class UnityEmulatorBase(introspection.CustomEmulatorBase):
2524 """A base class for all unity emulators."""
25+
26+ def __init__(self, *args):
27+ super(UnityEmulatorBase, self).__init__(*args)
28+ self.pointing_device = input.Touch.create()
29+
30+
31+class UnityEmulatorException(Exception):
32+ """Exception raised when there is an error with the Unity emulators."""
tests/autopilot/unity8/shell/emulators/create_interactive_notification.py+1−1
@@ -87,7 +87,7 @@ if __name__ == '__main__':
8787 action_id,
8888 action_label,
8989 action_callback,
90− None,
90+ (None, ),
9191 None
9292 )
9393
tests/autopilot/unity8/shell/emulators/dash.py+102−8
@@ -1,7 +1,7 @@
11# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
22#
33# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
55#
66# This program is free software: you can redistribute it and/or modify
77# it under the terms of the GNU General Public License as published by
@@ -17,13 +17,25 @@
1717# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818#
1919
20−from unity8.shell.emulators import UnityEmulatorBase
20+import logging
2121
22+from unity8.shell import emulators
2223
23−class Dash(UnityEmulatorBase):
24+from autopilot import logging as autopilot_logging
25+from autopilot.introspection import dbus
2426
27+
28+logger = logging.getLogger(__name__)
29+
30+
31+class Dash(emulators.UnityEmulatorBase):
2532 """An emulator that understands the Dash."""
2633
34+ def __init__(self, *args):
35+ super(Dash, self).__init__(*args)
36+ self.dash_content_list = self.wait_select_single(
37+ 'QQuickListView', objectName='dashContentList')
38+
2739 def get_home_applications_grid(self):
2840 get_grid = self.get_scope('home').wait_select_single(
2941 "GenericFilterGrid",
@@ -43,9 +55,91 @@ class Dash(UnityEmulatorBase):
4355 return resp_grid.select_single('Tile', text=text)
4456
4557 def get_scope(self, scope_name='home'):
46− dash_content = self.wait_select_single(
47− 'QQuickListView',
48− objectName='dashContentList'
49− )
5058 scope_id = "%s.scope" % scope_name
51− return dash_content.select_single('QQuickLoader', scopeId=scope_id)
59+ return self.dash_content_list.select_single(
60+ 'QQuickLoader', scopeId=scope_id)
61+
62+ @autopilot_logging.log_action(logger.info)
63+ def open_scope(self, scope_name):
64+ """Open a dash scope.
65+
66+ :parameter scope_name: The name of the scope.
67+ :return: The scope.
68+
69+ """
70+ scope_id = "%s.scope" % scope_name
71+ scope_loader = self._get_scope_loader(scope_id)
72+ if scope_loader.isCurrent:
73+ logger.info('The scope is already open.')
74+ return self._get_scope_from_loader(scope_loader)
75+ else:
76+ return self._open_scope_scrolling(scope_loader)
77+
78+ def _get_scope_loader(self, scope_id):
79+ try:
80+ return self.dash_content_list.select_single(
81+ 'QQuickLoader', scopeId=scope_id)
82+ except dbus.StateNotFoundError:
83+ raise emulators.UnityEmulatorException(
84+ 'No scope found with id {0}'.format(scope_id))
85+
86+ def _get_scope_from_loader(self, loader):
87+ if loader.scopeId == 'applications.scope':
88+ return loader.select_single(DashApps)
89+ else:
90+ return loader.select_single(GenericScopeView)
91+
92+ def _open_scope_scrolling(self, scope_loader):
93+ scroll = self._get_scroll_direction(scope_loader)
94+
95+ while not scope_loader.isCurrent:
96+ scroll()
97+
98+ scope = self._get_scope_from_loader(scope_loader)
99+ scope.moving.wait_for(False)
100+ scope.isCurrent.wait_for(True)
101+ return scope
102+
103+ def _get_scroll_direction(self, scope_loader):
104+ current_scope_loader = self.dash_content_list.select_single(
105+ 'QQuickLoader', isCurrent=True)
106+ if scope_loader.globalRect.x < current_scope_loader.globalRect.x:
107+ return self._scroll_to_left_scope
108+ elif scope_loader.globalRect.x > current_scope_loader.globalRect.x:
109+ return self._scroll_to_right_scope
110+ else:
111+ raise emulators.UnityEmulatorException('The scope is already open')
112+
113+ @autopilot_logging.log_action(logger.info)
114+ def _scroll_to_left_scope(self):
115+ original_index = self.dash_content_list.currentIndex
116+ # Scroll on the border of the page header, because some scopes have
117+ # contents that can be scrolled horizontally.
118+ page_header = self.select_single('PageHeader')
119+ border = page_header.select_single('QQuickBorderImage')
120+ start_x = border.width / 3
121+ stop_x = border.width / 3 * 2
122+ start_y = stop_y = border.globalRect.y + border.height / 2
123+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
124+ self.dash_content_list.currentIndex.wait_for(original_index - 1)
125+
126+ @autopilot_logging.log_action(logger.info)
127+ def _scroll_to_right_scope(self):
128+ original_index = self.dash_content_list.currentIndex
129+ # Scroll on the border of the page header, because some scopes have
130+ # contents that can be scrolled horizontally.
131+ page_header = self.select_single('PageHeader')
132+ border = page_header.select_single('QQuickBorderImage')
133+ start_x = border.width / 3 * 2
134+ stop_x = border.width / 3
135+ start_y = stop_y = border.globalRect.y + border.height / 2
136+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
137+ self.dash_content_list.currentIndex.wait_for(original_index + 1)
138+
139+
140+class GenericScopeView(emulators.UnityEmulatorBase):
141+ """Autopilot emulators for generic scopes."""
142+
143+
144+class DashApps(emulators.UnityEmulatorBase):
145+ """Autopilot emulators for the applications scope."""
tests/autopilot/unity8/shell/emulators/greeter.py+2−5
@@ -1,7 +1,7 @@
11# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
22#
33# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
55#
66# This program is free software: you can redistribute it and/or modify
77# it under the terms of the GNU General Public License as published by
@@ -18,24 +18,21 @@
1818#
1919
2020from unity8.shell.emulators import UnityEmulatorBase
21−from autopilot.input import Touch
2221
2322
2423class Greeter(UnityEmulatorBase):
25−
2624 """An emulator that understands the greeter screen."""
2725
2826 def swipe(self):
2927 """Swipe the greeter screen away."""
3028 self.created.wait_for(True)
31− touch = Touch.create()
3229
3330 rect = self.globalRect
3431 start_x = rect[0] + rect[2] - 3
3532 start_y = int(rect[1] + rect[3] / 2)
3633 stop_x = int(rect[0] + rect[2] * 0.2)
3734 stop_y = start_y
38− touch.drag(start_x, start_y, stop_x, stop_y)
35+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
3936
4037 self.created.wait_for(False)
4138
tests/autopilot/unity8/shell/emulators/main_window.py+6
@@ -91,5 +91,11 @@ class MainWindow(object):
9191 def get_pinentryField(self):
9292 return self.app.select_single(objectName="pinentryField")
9393
94+ def get_indicator(self, indicator_name):
95+ return self.app.select_single(
96+ 'DefaultIndicatorWidget',
97+ objectName=indicator_name+'-widget'
98+ )
99+
94100 def get_shell_background(self):
95101 return self.app.select_single("CrossFadeImage", objectName="backgroundImage")
tests/autopilot/unity8/shell/tests/__init__.py+1
@@ -147,6 +147,7 @@ class UnityTestCase(AutopilotTestCase):
147147 if model() != "Desktop":
148148 from autopilot.input import _uinput
149149 _uinput._touch_device = _uinput.create_touch_device()
150+ self.addCleanup(_uinput._touch_device.close)
150151 ####
151152
152153 self.touch = Touch.create()
tests/autopilot/unity8/shell/tests/test_emulators.pynew file+120
@@ -0,0 +1,120 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+import mock
21+
22+from unity8 import process_helpers
23+from unity8.shell import emulators, tests
24+from unity8.shell.emulators import dash as dash_emulators
25+
26+
27+class DashEmulatorTestCase(tests.UnityTestCase):
28+
29+ scenarios = tests._get_device_emulation_scenarios()
30+
31+ def setUp(self):
32+ super(DashEmulatorTestCase, self).setUp()
33+ unity_proxy = self.launch_unity()
34+ process_helpers.unlock_unity(unity_proxy)
35+ self.dash = self.main_window.get_dash()
36+
37+ def test_open_unexisting_scope(self):
38+ scope_name = 'unexisting'
39+ with mock.patch.object(self.dash, 'pointing_device') as mock_pointer:
40+ exception = self.assertRaises(
41+ emulators.UnityEmulatorException,
42+ self.dash.open_scope, scope_name)
43+
44+ self.assertEqual(
45+ 'No scope found with id unexisting.scope', str(exception))
46+ self.assertFalse(mock_pointer.called)
47+
48+ def test_open_already_opened_scope(self):
49+ scope_id = self._get_current_scope_id()
50+ with mock.patch.object(self.dash, 'pointing_device') as mock_pointer:
51+ scope = self.dash.open_scope(self._get_scope_name_from_id(
52+ scope_id))
53+
54+ self.assertFalse(mock_pointer.called)
55+ self._assert_scope_is_opened(scope, scope_id)
56+
57+ def _assert_scope_is_opened(self, scope, scope_id):
58+ self.assertTrue(scope.isCurrent)
59+ scope_loader = scope.get_parent()
60+ self.assertEqual(scope_loader.scopeId, scope_id)
61+
62+ def _get_current_scope_id(self):
63+ scope = self.dash.dash_content_list.select_single(
64+ 'QQuickLoader', isCurrent=True)
65+ return scope.scopeId
66+
67+ def test_open_scope_to_the_right(self):
68+ leftmost_scope = self._get_scope_name_from_id(
69+ self._get_leftmost_scope_id())
70+ self.dash.open_scope(leftmost_scope)
71+
72+ scope_id = self._get_rightmost_scope_id()
73+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
74+ self._assert_scope_is_opened(scope, scope_id)
75+
76+ def _get_leftmost_scope_id(self):
77+ scope_loaders = self._get_scope_loaders()
78+ leftmost_scope_loader = scope_loaders[0]
79+ for loader in scope_loaders[1:]:
80+ if loader.globalRect.x < leftmost_scope_loader.globalRect.x:
81+ leftmost_scope_loader = loader
82+ return leftmost_scope_loader.scopeId
83+
84+ def _get_scope_loaders(self):
85+ item = self.dash.dash_content_list.get_children_by_type(
86+ 'QQuickItem')[0]
87+ return item.get_children_by_type('QQuickLoader')
88+
89+ def _get_scope_name_from_id(self, scope_id):
90+ if scope_id.endswith('.scope'):
91+ return scope_id[:-6]
92+
93+ def _get_rightmost_scope_id(self):
94+ scope_loaders = self._get_scope_loaders()
95+ rightmost_scope_loader = scope_loaders[0]
96+ for loader in scope_loaders[1:]:
97+ if loader.globalRect.x > rightmost_scope_loader.globalRect.x:
98+ rightmost_scope_loader = loader
99+ return rightmost_scope_loader.scopeId
100+
101+ def test_open_scope_to_the_left(self):
102+ rightmost_scope = self._get_scope_name_from_id(
103+ self._get_rightmost_scope_id())
104+ self.dash.open_scope(rightmost_scope)
105+
106+ scope_id = self._get_leftmost_scope_id()
107+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
108+ self._assert_scope_is_opened(scope, scope_id)
109+
110+ def test_open_generic_scope(self):
111+ scope_id = 'home.scope'
112+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
113+ self._assert_scope_is_opened(scope, scope_id)
114+ self.assertIsInstance(scope, dash_emulators.GenericScopeView)
115+
116+ def test_open_applications_scope(self):
117+ scope_id = 'applications.scope'
118+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
119+ self._assert_scope_is_opened(scope, scope_id)
120+ self.assertIsInstance(scope, dash_emulators.DashApps)
tests/mocks/LightDM/CMakeLists.txt+1−1
@@ -33,7 +33,7 @@ add_library(MockLightDM-qml MODULE
3333# installed on the system. So we make sure we link to our full fake version
3434# At run time, we can point to whichever version we happen to be using via
3535# LD_LIBRARY_PATH.
36−add_dependencies(MockLightDM-qml MockLightDM-full)
36+add_dependencies(MockLightDM-qml MockLightDM-full MockUserMetrics)
3737target_link_libraries(MockLightDM-qml
3838 -L${CMAKE_CURRENT_BINARY_DIR}/full
3939 -llightdm-qt5-2
tests/mocks/Unity/fake_scopes.cpp+3
@@ -35,6 +35,7 @@ Scopes::Scopes(QObject *parent)
3535 m_roles[Scopes::RoleScope] = "scope";
3636 m_roles[Scopes::RoleId] = "id";
3737 m_roles[Scopes::RoleVisible] = "visible";
38+ m_roles[Scopes::RoleTitle] = "title";
3839
3940 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
4041 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
@@ -110,6 +111,8 @@ QVariant Scopes::data(const QModelIndex& index, int role) const
110111 return QVariant::fromValue(scope->id());
111112 } else if (role == Scopes::RoleVisible) {
112113 return QVariant::fromValue(scope->visible());
114+ } else if (role == Scopes::RoleTitle) {
115+ return QVariant::fromValue(scope->name());
113116 } else {
114117 return QVariant();
115118 }
tests/mocks/Unity/fake_scopes.h+2−1
@@ -41,7 +41,8 @@ public:
4141 enum Roles {
4242 RoleScope,
4343 RoleId,
44− RoleVisible
44+ RoleVisible,
45+ RoleTitle
4546 };
4647
4748 Q_INVOKABLE int rowCount(const QModelIndex& parent = QModelIndex()) const;
tests/plugins/DashViews/CMakeLists.txt+36−9
@@ -40,15 +40,41 @@ qt5_use_modules(listviewwithpageheadertestsectionexternalmodelExec Test Core Qml
4040target_link_libraries(listviewwithpageheadertestsectionexternalmodelExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
4141add_custom_target(testlistviewwithpageheadersectionexternalmodel ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestsectionexternalmodelExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheadersectionexternalmodel.xml,xunitxml -o -,txt)
4242
43−add_executable(verticaljournaltestExec verticaljournaltest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/verticaljournal.cpp)
44−qt5_use_modules(verticaljournaltestExec Test Core Qml)
45−target_link_libraries(verticaljournaltestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
46−add_custom_target(testVerticalJournal ${CMAKE_CURRENT_BINARY_DIR}/verticaljournaltestExec -o ${CMAKE_BINARY_DIR}/testVerticalJournal.xml,xunitxml -o -,txt)
43+macro(add_journal_try_test FILENAME)
44+ # Capitalite the first letter for the test/try name targets
45+ string(SUBSTRING ${FILENAME} 0 1 FIRST_LETTER)
46+ string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
47+ string(REGEX REPLACE "^.(.*)" "${FIRST_LETTER}\\1" CUSTOM_TARGET_NAME "${FILENAME}")
4748
48−add_executable(verticaljournaltryExec verticaljournaltry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/verticaljournal.cpp)
49−qt5_use_modules(verticaljournaltryExec Test Core Qml)
50−target_link_libraries(verticaljournaltryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
51−add_custom_target(tryVerticalJournal ${CMAKE_CURRENT_BINARY_DIR}/verticaljournaltryExec -o ${CMAKE_BINARY_DIR}/testVerticalJournal.xml,xunitxml -o -,txt)
49+ add_executable(${FILENAME}testExec
50+ ${FILENAME}test.cpp
51+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
52+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractjournal.cpp
53+ )
54+ qt5_use_modules(${FILENAME}testExec Test Core Qml)
55+ target_link_libraries(${FILENAME}testExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
56+ add_custom_target(test${CUSTOM_TARGET_NAME}
57+ ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}testExec
58+ -o ${CMAKE_BINARY_DIR}/test${CUSTOM_TARGET_NAME}.xml,xunitxml
59+ -o -,txt
60+ DEPENDS ${FILENAME}testExec DashViews-qml
61+ )
62+
63+ add_executable(${FILENAME}tryExec
64+ ${FILENAME}try.cpp
65+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
66+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractjournal.cpp
67+ )
68+ qt5_use_modules(${FILENAME}tryExec Test Core Qml)
69+ target_link_libraries(${FILENAME}tryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
70+ add_custom_target(try${CUSTOM_TARGET_NAME}
71+ ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}tryExec
72+ DEPENDS ${FILENAME}tryExec DashViews-qml
73+ )
74+endmacro()
75+
76+add_journal_try_test(verticaljournal)
77+add_journal_try_test(horizontaljournal)
5278
5379set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
5480add_qml_test(. listviewwithpageheaderqml IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
@@ -57,4 +83,5 @@ add_dependencies(qmluitests testlistviewwithpageheader)
5783add_dependencies(qmluitests testlistviewwithpageheadersection)
5884add_dependencies(qmluitests testlistviewwithpageheadersectionexternalmodel)
5985add_dependencies(qmluitests testlistviewwithpageheaderqml)
60−add_dependencies(qmluitests testVerticalJournal)
86+add_dependencies(qmluitests testVerticaljournal)
87+add_dependencies(qmluitests testHorizontaljournal)
tests/plugins/DashViews/horizontaljournaltest.cppnew file+465
@@ -0,0 +1,465 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QQuickView>
18+#include <QtTestGui>
19+#include <QDebug>
20+#include <QGuiApplication>
21+#include <QQuickView>
22+#include <QtQml/qqml.h>
23+#include <QStringListModel>
24+#include <QQmlContext>
25+#include <QQmlEngine>
26+#pragma GCC diagnostic push
27+#pragma GCC diagnostic ignored "-pedantic"
28+#include <private/qquickitem_p.h>
29+#pragma GCC diagnostic pop
30+
31+#include "horizontaljournal.h"
32+
33+class WidthModel : public QAbstractListModel {
34+public:
35+ QHash<int, QByteArray> roleNames() const
36+ {
37+ QHash<int, QByteArray> roles;
38+ roles.insert(Qt::DisplayRole, "modelWidth");
39+ return roles;
40+ }
41+
42+ int rowCount(const QModelIndex & /*parent*/) const
43+ {
44+ return m_list.count();
45+ }
46+
47+ QVariant data(const QModelIndex &index, int /*role*/) const
48+ {
49+ return m_list[index.row()];
50+ }
51+
52+ QStringList stringList() const
53+ {
54+ return m_list;
55+ }
56+
57+ void setStringList(const QStringList &list)
58+ {
59+ beginResetModel();
60+ m_list = list;
61+ endResetModel();
62+ }
63+
64+ void addString(const QString& string)
65+ {
66+ beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
67+ m_list << string;
68+ endInsertRows();
69+ }
70+
71+ void removeLast()
72+ {
73+ beginRemoveRows(QModelIndex(), m_list.count() - 1, m_list.count() - 1);
74+ m_list.takeLast();
75+ endRemoveRows();
76+ }
77+
78+private:
79+ QStringList m_list;
80+};
81+
82+class HorizontalJournalTest : public QObject
83+{
84+ Q_OBJECT
85+
86+private:
87+ void verifyItem(const QQuickItem *item, int modelIndex, qreal x, qreal y, bool visible)
88+ {
89+ QTRY_COMPARE(item->x(), x);
90+ QTRY_COMPARE(item->y(), y);
91+ QTRY_COMPARE(item->width(), model->stringList()[modelIndex].toDouble());
92+ QTRY_COMPARE(QQuickItemPrivate::get(item)->culled, !visible);
93+ }
94+
95+ void checkInitialPositions()
96+ {
97+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
98+ QTRY_COMPARE(hj->m_visibleItems.count(), 14);
99+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
100+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
101+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
102+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
103+ verifyItem(hj->m_visibleItems[ 4], 4, 325, 0, true);
104+ verifyItem(hj->m_visibleItems[ 5], 5, 375, 0, true);
105+ verifyItem(hj->m_visibleItems[ 6], 6, 0, 160, true);
106+ verifyItem(hj->m_visibleItems[ 7], 7, 210, 160, true);
107+ verifyItem(hj->m_visibleItems[ 8], 8, 0, 320, true);
108+ verifyItem(hj->m_visibleItems[ 9], 9, 170, 320, true);
109+ verifyItem(hj->m_visibleItems[10], 10, 200, 320, true);
110+ verifyItem(hj->m_visibleItems[11], 11, 230, 320, true);
111+ verifyItem(hj->m_visibleItems[12], 12, 305, 320, true);
112+ verifyItem(hj->m_visibleItems[13], 13, 0, 480, false);
113+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 4);
114+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 375.);
115+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 7], 210.);
116+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[12], 305.);
117+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[13], 0.);
118+ QCOMPARE(hj->implicitHeight(), 900.);
119+ }
120+
121+private Q_SLOTS:
122+ void init()
123+ {
124+ view = new QQuickView();
125+ view->setResizeMode(QQuickView::SizeRootObjectToView);
126+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
127+
128+ model = new WidthModel();
129+ QStringList heightList;
130+ heightList << "100" << "50" << "125" << "10" << "40" << "70" << "200" << "110" << "160" << "20" << "20" << "65" << "80" << "200" << "300" << "130" << "400" << "300" << "500" << "10";
131+ model->setStringList(heightList);
132+
133+ view->setSource(QUrl::fromLocalFile(DASHVIEWSTEST_FOLDER "/horizontaljournaltest.qml"));
134+
135+ view->show();
136+ view->resize(470, 400);
137+ QTest::qWaitForWindowExposed(view);
138+
139+ hj = dynamic_cast<HorizontalJournal*>(view->rootObject()->findChild<QObject*>("hj"));
140+ hj->setModel(model);
141+
142+ checkInitialPositions();
143+ }
144+
145+ void cleanup()
146+ {
147+ delete view;
148+ delete model;
149+ }
150+
151+ void testWidthResize()
152+ {
153+ view->resize(629, 400);
154+ QTRY_COMPARE(hj->width(), 629.);
155+
156+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
157+ QTRY_COMPARE(hj->m_visibleItems.count(), 17);
158+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
159+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
160+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
161+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
162+ verifyItem(hj->m_visibleItems[ 4], 4, 325, 0, true);
163+ verifyItem(hj->m_visibleItems[ 5], 5, 375, 0, true);
164+ verifyItem(hj->m_visibleItems[ 6], 6, 0, 160, true);
165+ verifyItem(hj->m_visibleItems[ 7], 7, 210, 160, true);
166+ verifyItem(hj->m_visibleItems[ 8], 8, 330, 160, true);
167+ verifyItem(hj->m_visibleItems[ 9], 9, 500, 160, true);
168+ verifyItem(hj->m_visibleItems[10], 10, 530, 160, true);
169+ verifyItem(hj->m_visibleItems[11], 11, 560, 160, true);
170+ verifyItem(hj->m_visibleItems[12], 12, 0, 320, true);
171+ verifyItem(hj->m_visibleItems[13], 13, 90, 320, true);
172+ verifyItem(hj->m_visibleItems[14], 14, 300, 320, true);
173+ verifyItem(hj->m_visibleItems[15], 15, 0, 480, false);
174+ verifyItem(hj->m_visibleItems[16], 16, 140, 480, false);
175+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 4);
176+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 375.);
177+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[11], 560.);
178+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[14], 300.);
179+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[16], 140.);
180+ QCOMPARE(hj->implicitHeight(), 630. + 3. * 630. / 17.);
181+
182+ view->resize(470, 400);
183+
184+ // This is exactly the same block as the first again
185+ checkInitialPositions();
186+ }
187+
188+ void testColumnSpacing()
189+ {
190+ hj->setColumnSpacing(11);
191+
192+ QTRY_COMPARE(hj->m_needsRelayout, false);
193+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
194+ QTRY_COMPARE(hj->m_visibleItems.count(), 14);
195+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
196+ verifyItem(hj->m_visibleItems[ 1], 1, 111, 0, true);
197+ verifyItem(hj->m_visibleItems[ 2], 2, 172, 0, true);
198+ verifyItem(hj->m_visibleItems[ 3], 3, 308, 0, true);
199+ verifyItem(hj->m_visibleItems[ 4], 4, 329, 0, true);
200+ verifyItem(hj->m_visibleItems[ 5], 5, 380, 0, true);
201+ verifyItem(hj->m_visibleItems[ 6], 6, 0, 160, true);
202+ verifyItem(hj->m_visibleItems[ 7], 7, 211, 160, true);
203+ verifyItem(hj->m_visibleItems[ 8], 8, 0, 320, true);
204+ verifyItem(hj->m_visibleItems[ 9], 9, 171, 320, true);
205+ verifyItem(hj->m_visibleItems[10], 10, 202, 320, true);
206+ verifyItem(hj->m_visibleItems[11], 11, 233, 320, true);
207+ verifyItem(hj->m_visibleItems[12], 12, 309, 320, true);
208+ verifyItem(hj->m_visibleItems[13], 13, 0, 480, false);
209+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 4);
210+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 380.);
211+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 7], 211.);
212+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[12], 309.);
213+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[13], 0.);
214+ QCOMPARE(hj->implicitHeight(), 900.);
215+
216+ hj->setColumnSpacing(10);
217+ QTRY_COMPARE(hj->m_needsRelayout, false);
218+
219+ // This is exactly the same block as the first again
220+ checkInitialPositions();
221+ }
222+
223+ void testRowSpacing()
224+ {
225+ hj->setRowSpacing(11);
226+
227+ QTRY_COMPARE(hj->m_needsRelayout, false);
228+ QTRY_COMPARE(hj->m_needsRelayout, false);
229+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
230+ QTRY_COMPARE(hj->m_visibleItems.count(), 14);
231+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
232+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
233+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
234+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
235+ verifyItem(hj->m_visibleItems[ 4], 4, 325, 0, true);
236+ verifyItem(hj->m_visibleItems[ 5], 5, 375, 0, true);
237+ verifyItem(hj->m_visibleItems[ 6], 6, 0, 161, true);
238+ verifyItem(hj->m_visibleItems[ 7], 7, 210, 161, true);
239+ verifyItem(hj->m_visibleItems[ 8], 8, 0, 322, true);
240+ verifyItem(hj->m_visibleItems[ 9], 9, 170, 322, true);
241+ verifyItem(hj->m_visibleItems[10], 10, 200, 322, true);
242+ verifyItem(hj->m_visibleItems[11], 11, 230, 322, true);
243+ verifyItem(hj->m_visibleItems[12], 12, 305, 322, true);
244+ verifyItem(hj->m_visibleItems[13], 13, 0, 483, false);
245+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 4);
246+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 375.);
247+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 7], 210.);
248+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[12], 305.);
249+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[13], 0.);
250+ QCOMPARE(hj->implicitHeight(), 633. + 6. * 633. / 14.);
251+
252+ hj->setRowSpacing(10);
253+ QTRY_COMPARE(hj->m_needsRelayout, false);
254+
255+ // This is exactly the same block as the first again
256+ checkInitialPositions();
257+ }
258+
259+ void testDelegateCreationRanges()
260+ {
261+ hj->setDelegateCreationBegin(300);
262+ hj->setDelegateCreationEnd(view->height());
263+
264+ QTRY_COMPARE(hj->m_firstVisibleIndex, 6);
265+ QTRY_COMPARE(hj->m_visibleItems.count(), 7);
266+ verifyItem(hj->m_visibleItems[0], 6, 0, 160, true);
267+ verifyItem(hj->m_visibleItems[1], 7, 210, 160, true);
268+ verifyItem(hj->m_visibleItems[2], 8, 0, 320, true);
269+ verifyItem(hj->m_visibleItems[3], 9, 170, 320, true);
270+ verifyItem(hj->m_visibleItems[4], 10, 200, 320, true);
271+ verifyItem(hj->m_visibleItems[5], 11, 230, 320, true);
272+ verifyItem(hj->m_visibleItems[6], 12, 305, 320, true);
273+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 375.);
274+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 7], 210.);
275+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[12], 305.);
276+
277+ hj->resetDelegateCreationBegin();
278+ hj->resetDelegateCreationEnd();
279+
280+ // This is exactly the same block as the first again
281+ checkInitialPositions();
282+ }
283+
284+
285+ void testRowHeightChange()
286+ {
287+ hj->setRowHeight(200);
288+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
289+ QTRY_COMPARE(hj->m_visibleItems.count(), 13);
290+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
291+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
292+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
293+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
294+ verifyItem(hj->m_visibleItems[ 4], 4, 325, 0, true);
295+ verifyItem(hj->m_visibleItems[ 5], 5, 375, 0, true);
296+ verifyItem(hj->m_visibleItems[ 6], 6, 0, 210, true);
297+ verifyItem(hj->m_visibleItems[ 7], 7, 210, 210, true);
298+ verifyItem(hj->m_visibleItems[ 8], 8, 0, 420, false);
299+ verifyItem(hj->m_visibleItems[ 9], 9, 170, 420, false);
300+ verifyItem(hj->m_visibleItems[10], 10, 200, 420, false);
301+ verifyItem(hj->m_visibleItems[11], 11, 230, 420, false);
302+ verifyItem(hj->m_visibleItems[12], 12, 305, 420, false);
303+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 3);
304+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 375.);
305+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 7], 210.);
306+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[12], 305.);
307+ QCOMPARE(hj->implicitHeight(), 620. + 7. * 620. / 13.);
308+
309+ hj->setRowHeight(150);
310+
311+ // This is exactly the same block as the first again
312+ checkInitialPositions();
313+ }
314+
315+ void testChangeModel()
316+ {
317+ WidthModel *model2 = new WidthModel();
318+ QStringList list2;
319+ list2 << "100" << "50" << "125" << "25" << "50" << "50";
320+ model2->setStringList(list2);
321+ hj->setModel(model2);
322+ delete model;
323+ model = model2;
324+
325+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
326+ QTRY_COMPARE(hj->m_visibleItems.count(), 6);
327+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
328+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
329+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
330+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
331+ verifyItem(hj->m_visibleItems[ 4], 4, 340, 0, true);
332+ verifyItem(hj->m_visibleItems[ 5], 5, 400, 0, true);
333+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 0);
334+ QCOMPARE(hj->implicitHeight(), 150.);
335+ }
336+
337+ void testModelReset()
338+ {
339+ QStringList heightList;
340+ heightList << "100" << "50" << "125" << "25" << "50" << "50";
341+ model->setStringList(heightList);
342+
343+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
344+ QTRY_COMPARE(hj->m_visibleItems.count(), 6);
345+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
346+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
347+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
348+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
349+ verifyItem(hj->m_visibleItems[ 4], 4, 340, 0, true);
350+ verifyItem(hj->m_visibleItems[ 5], 5, 400, 0, true);
351+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 0);
352+ QCOMPARE(hj->implicitHeight(), 150.);
353+ }
354+
355+ void testModelRemoveLastNonVisible()
356+ {
357+ model->removeLast();
358+
359+ // This is the same than checkInitialPositions but
360+ // with a different implicitHeight since there's an item less
361+ // in the model
362+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
363+ QTRY_COMPARE(hj->m_visibleItems.count(), 14);
364+ verifyItem(hj->m_visibleItems[ 0], 0, 0, 0, true);
365+ verifyItem(hj->m_visibleItems[ 1], 1, 110, 0, true);
366+ verifyItem(hj->m_visibleItems[ 2], 2, 170, 0, true);
367+ verifyItem(hj->m_visibleItems[ 3], 3, 305, 0, true);
368+ verifyItem(hj->m_visibleItems[ 4], 4, 325, 0, true);
369+ verifyItem(hj->m_visibleItems[ 5], 5, 375, 0, true);
370+ verifyItem(hj->m_visibleItems[ 6], 6, 0, 160, true);
371+ verifyItem(hj->m_visibleItems[ 7], 7, 210, 160, true);
372+ verifyItem(hj->m_visibleItems[ 8], 8, 0, 320, true);
373+ verifyItem(hj->m_visibleItems[ 9], 9, 170, 320, true);
374+ verifyItem(hj->m_visibleItems[10], 10, 200, 320, true);
375+ verifyItem(hj->m_visibleItems[11], 11, 230, 320, true);
376+ verifyItem(hj->m_visibleItems[12], 12, 305, 320, true);
377+ verifyItem(hj->m_visibleItems[13], 13, 0, 480, false);
378+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 4);
379+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 5], 375.);
380+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[ 7], 210.);
381+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[12], 305.);
382+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[13], 0.);
383+ QTRY_COMPARE(hj->implicitHeight(), 855.);
384+ }
385+
386+ void testModelAppendRemoveLast()
387+ {
388+ WidthModel *model2 = new WidthModel();
389+ QStringList heightList;
390+ heightList << "100" << "50" << "125" << "25" << "50" << "50";
391+ model2->setStringList(heightList);
392+ hj->setModel(model2);
393+ delete model;
394+ model = model2;
395+
396+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
397+ QTRY_COMPARE(hj->m_visibleItems.count(), 6);
398+ verifyItem(hj->m_visibleItems[0], 0, 0, 0, true);
399+ verifyItem(hj->m_visibleItems[1], 1, 110, 0, true);
400+ verifyItem(hj->m_visibleItems[2], 2, 170, 0, true);
401+ verifyItem(hj->m_visibleItems[3], 3, 305, 0, true);
402+ verifyItem(hj->m_visibleItems[4], 4, 340, 0, true);
403+ verifyItem(hj->m_visibleItems[5], 5, 400, 0, true);
404+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 0);
405+ QCOMPARE(hj->implicitHeight(), 150.);
406+
407+ model2->addString("75");
408+
409+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
410+ QTRY_COMPARE(hj->m_visibleItems.count(), 7);
411+ verifyItem(hj->m_visibleItems[0], 0, 0, 0, true);
412+ verifyItem(hj->m_visibleItems[1], 1, 110, 0, true);
413+ verifyItem(hj->m_visibleItems[2], 2, 170, 0, true);
414+ verifyItem(hj->m_visibleItems[3], 3, 305, 0, true);
415+ verifyItem(hj->m_visibleItems[4], 4, 340, 0, true);
416+ verifyItem(hj->m_visibleItems[5], 5, 400, 0, true);
417+ verifyItem(hj->m_visibleItems[6], 6, 0, 160, true);
418+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 1);
419+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[5], 400.);
420+ QCOMPARE(hj->implicitHeight(), 310.);
421+
422+ model2->addString("50");
423+ model2->addString("50");
424+
425+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
426+ QTRY_COMPARE(hj->m_visibleItems.count(), 9);
427+ verifyItem(hj->m_visibleItems[0], 0, 0, 0, true);
428+ verifyItem(hj->m_visibleItems[1], 1, 110, 0, true);
429+ verifyItem(hj->m_visibleItems[2], 2, 170, 0, true);
430+ verifyItem(hj->m_visibleItems[3], 3, 305, 0, true);
431+ verifyItem(hj->m_visibleItems[4], 4, 340, 0, true);
432+ verifyItem(hj->m_visibleItems[5], 5, 400, 0, true);
433+ verifyItem(hj->m_visibleItems[6], 6, 0, 160, true);
434+ verifyItem(hj->m_visibleItems[7], 7, 85, 160, true);
435+ verifyItem(hj->m_visibleItems[8], 8, 145, 160, true);
436+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 1);
437+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[5], 400.);
438+ QCOMPARE(hj->implicitHeight(), 310.);
439+
440+ model2->removeLast();
441+
442+ QTRY_COMPARE(hj->m_firstVisibleIndex, 0);
443+ QTRY_COMPARE(hj->m_visibleItems.count(), 8);
444+ verifyItem(hj->m_visibleItems[0], 0, 0, 0, true);
445+ verifyItem(hj->m_visibleItems[1], 1, 110, 0, true);
446+ verifyItem(hj->m_visibleItems[2], 2, 170, 0, true);
447+ verifyItem(hj->m_visibleItems[3], 3, 305, 0, true);
448+ verifyItem(hj->m_visibleItems[4], 4, 340, 0, true);
449+ verifyItem(hj->m_visibleItems[5], 5, 400, 0, true);
450+ verifyItem(hj->m_visibleItems[6], 6, 0, 160, true);
451+ verifyItem(hj->m_visibleItems[7], 7, 85, 160, true);
452+ QTRY_COMPARE(hj->m_lastInRowIndexPosition.count(), 1);
453+ QTRY_COMPARE(hj->m_lastInRowIndexPosition[5], 400.);
454+ QCOMPARE(hj->implicitHeight(), 310.);
455+ }
456+
457+private:
458+ QQuickView *view;
459+ HorizontalJournal *hj;
460+ WidthModel *model;
461+};
462+
463+QTEST_MAIN(HorizontalJournalTest)
464+
465+#include "horizontaljournaltest.moc"
tests/plugins/DashViews/horizontaljournaltest.qmlnew file+43
@@ -0,0 +1,43 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.1
18+import DashViews 0.1
19+
20+Item {
21+
22+ HorizontalJournal {
23+ objectName: "hj"
24+ anchors.fill: parent
25+ rowHeight: 150
26+ columnSpacing: 10
27+ rowSpacing: 10
28+
29+ delegate: Rectangle {
30+ property real randomValue: Math.random()
31+ height: 150
32+ color: randomValue < 0.3 ? "green" : randomValue < 0.6 ? "blue" : "red";
33+ width: modelWidth
34+ border.width: 3
35+
36+ Text {
37+ text: index + "\nx: " + parent.x + "\nwidth: " + parent.width
38+ x: 10
39+ y: 10
40+ }
41+ }
42+ }
43+}
tests/plugins/DashViews/horizontaljournaltry.cppnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QQuickView>
18+#include <QDebug>
19+#include <QGuiApplication>
20+#include <QQuickView>
21+#include <QtQml/qqml.h>
22+#include <QStringListModel>
23+#include <QQmlContext>
24+#include <QQmlEngine>
25+#include <QGuiApplication>
26+
27+#include "horizontaljournal.h"
28+
29+class QWidthModela : public QStringListModel {
30+ Q_OBJECT
31+public:
32+ QHash<int, QByteArray> roleNames() const
33+ {
34+ QHash<int, QByteArray> roles;
35+ roles.insert(Qt::DisplayRole, "modelWidth");
36+ return roles;
37+ }
38+
39+public slots:
40+ void add(int height)
41+ {
42+ if (height <= 0) {
43+ height = 50 + 50. * qrand() / RAND_MAX ;
44+ }
45+ QStringList sl = stringList();
46+ sl << QString::number(height);
47+ setStringList(sl);
48+ }
49+
50+ void remove()
51+ {
52+ QStringList sl = stringList();
53+ if (!sl.isEmpty()) {
54+ sl.removeLast();
55+ setStringList(sl);
56+ }
57+ }
58+};
59+
60+int main(int argc, char *argv[])
61+{
62+ QGuiApplication a(argc, argv);
63+
64+ QQuickView *view = new QQuickView();
65+ view->setResizeMode(QQuickView::SizeRootObjectToView);
66+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
67+
68+ QWidthModela model;
69+ QStringList heightList;
70+ heightList << "100" << "50" << "125";
71+ model.setStringList(heightList);
72+
73+ QStringListModel listModel;
74+ listModel.setStringList(QStringList() << QString());
75+
76+ view->rootContext()->setContextProperty("listModel", &listModel);
77+ view->rootContext()->setContextProperty("vjModel", &model);
78+
79+ view->setSource(QUrl::fromLocalFile(DASHVIEWSTEST_FOLDER "/horizontaljournaltry.qml"));
80+
81+ view->show();
82+ view->resize(530, 400);
83+
84+ QObject::connect(view->rootObject(), SIGNAL(add(int)), &model, SLOT(add(int)));
85+ QObject::connect(view->rootObject(), SIGNAL(remove()), &model, SLOT(remove()));
86+
87+ return a.exec();
88+}
89+
90+#include "horizontaljournaltry.moc"
tests/plugins/DashViews/horizontaljournaltry.qmlnew file+99
@@ -0,0 +1,99 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.1
18+import DashViews 0.1
19+
20+Item {
21+ id: root
22+ signal add(int width)
23+ signal remove()
24+
25+ ListViewWithPageHeader {
26+ id: lvwph
27+ model: listModel
28+ height: parent.height
29+ width: parent.width - controls.width
30+
31+ delegate: HorizontalJournal {
32+ model: vjModel
33+ rowHeight: 100
34+ columnSpacing: 10
35+ rowSpacing: 10
36+ width: parent.width
37+ height: implicitHeight > 100 ? implicitHeight : 100
38+ delegateCreationBegin: lvwph.contentY
39+ delegateCreationEnd: lvwph.contentY + lvwph.height
40+
41+ delegate: Rectangle {
42+ height: 100
43+ color: "red";
44+ width: modelWidth
45+ border.width: 3
46+
47+ Text {
48+ text: index + "\nx: " + parent.x + "\nwidth: " + parent.width
49+ x: 10
50+ y: 10
51+ }
52+ }
53+ }
54+ }
55+
56+ Rectangle {
57+ id: controls
58+ height: parent.height
59+ width: 100
60+ anchors.right: parent.right
61+ color: "gray"
62+ opacity: 0.4
63+
64+ Rectangle {
65+ id: addButton
66+ height: 50
67+ width: parent.width
68+ color: "blue"
69+ Text {
70+ text: "Add"
71+ anchors.centerIn: parent
72+ }
73+ MouseArea {
74+ anchors.fill: parent
75+ onClicked: root.add(addField.text)
76+ }
77+ }
78+ TextInput {
79+ id: addField
80+ anchors.top: addButton.bottom
81+ height: 30
82+ width: parent.width
83+ }
84+ Rectangle {
85+ anchors.top: addField.bottom
86+ height: 50
87+ width: parent.width
88+ color: "red"
89+ Text {
90+ text: "Remove"
91+ anchors.centerIn: parent
92+ }
93+ MouseArea {
94+ anchors.fill: parent
95+ onClicked: root.remove()
96+ }
97+ }
98+ }
99+}
tests/plugins/DashViews/verticaljournaltest.cpp+5−5
@@ -30,7 +30,7 @@
3030
3131#include "verticaljournal.h"
3232
33−class QHeightModel : public QAbstractListModel {
33+class HeightModel : public QAbstractListModel {
3434public:
3535 QHash<int, QByteArray> roleNames() const
3636 {
@@ -127,7 +127,7 @@ private Q_SLOTS:
127127 view->setResizeMode(QQuickView::SizeRootObjectToView);
128128 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
129129
130− model = new QHeightModel();
130+ model = new HeightModel();
131131 QStringList heightList;
132132 heightList << "100" << "50" << "125" << "10" << "40" << "70" << "200" << "110" << "160" << "20" << "20" << "65" << "80" << "200" << "300" << "130" << "400" << "300" << "500" << "10";
133133 model->setStringList(heightList);
@@ -323,7 +323,7 @@ private Q_SLOTS:
323323
324324 void testChangeModel()
325325 {
326− QHeightModel *model2 = new QHeightModel();
326+ HeightModel *model2 = new HeightModel();
327327 QStringList list2;
328328 list2 << "100" << "50" << "25" << "25" << "50" << "50";
329329 model2->setStringList(list2);
@@ -393,7 +393,7 @@ private Q_SLOTS:
393393
394394 void testModelAppendRemoveLast()
395395 {
396− QHeightModel *model2 = new QHeightModel();
396+ HeightModel *model2 = new HeightModel();
397397 QStringList list2;
398398 list2 << "100" << "50" << "25" << "25" << "50" << "50";
399399 model2->setStringList(list2);
@@ -462,7 +462,7 @@ private Q_SLOTS:
462462private:
463463 QQuickView *view;
464464 VerticalJournal *vj;
465− QHeightModel *model;
465+ HeightModel *model;
466466};
467467
468468QTEST_MAIN(VerticalJournalTest)
tests/plugins/LightDM/CMakeLists.txt+1
@@ -11,6 +11,7 @@ include_directories(
1111 ${LIBLIGHTDM_INCLUDE_DIRS}
1212 )
1313
14+add_dependencies(test-lightdm-dbus MockLightDM-full)
1415target_link_libraries(test-lightdm-dbus
1516 -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full
1617 -llightdm-qt5-2
tests/qmltests/CMakeLists.txt+3−2
@@ -37,12 +37,13 @@ add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
3737 ${CMAKE_BINARY_DIR}/tests/mocks
3838 ${CMAKE_BINARY_DIR}/plugins)
3939add_qml_test(Components Tile)
40−add_qml_test(Components PageHeader)
40+add_qml_test(Components PageHeaderLabel)
4141add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
42−add_qml_test(Dash DashBar IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4342add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4443add_qml_test(Dash DashPreview)
4544add_qml_test(Dash GenericPreview)
45+add_qml_test(Dash Card)
46+add_qml_test(Dash CardHeader)
4647add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4748add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
4849 ${CMAKE_BINARY_DIR}/tests/mocks)
tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qmlrenamed+2−2
@@ -31,7 +31,7 @@ Item {
3131 }
3232
3333 UT.UnityTestCase {
34− name: "PageHeaderTest"
34+ name: "PageHeaderLabelTest"
3535 when: windowShown
3636
3737 property alias searchEnabled : pageHeader.searchEntryEnabled
@@ -204,7 +204,7 @@ Item {
204204 anchors.fill: parent
205205 spacing: units.gu(1)
206206
207− PageHeader {
207+ PageHeaderLabel {
208208 id: pageHeader
209209 anchors {
210210 left: parent.left
tests/qmltests/Dash/CardHelpers.jsnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+.pragma library
18+
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
20+
21+function tryParse(json, errorLabel) {
22+ var o = undefined;
23+ if (errorLabel !== undefined) {
24+ errorLabel.text = "";
25+ }
26+ try {
27+ o = JSON.parse(json)
28+ } catch(err) {
29+ if (errorLabel !== undefined) {
30+ errorLabel.text = err + "";
31+ } else {
32+ console.debug(err);
33+ }
34+ }
35+ return o;
36+}
37+
38+function mapData(json, layout, errorLabel) {
39+ var o = tryParse(json, errorLabel);
40+ var d = undefined;
41+
42+ if (o !== undefined) {
43+ d = Object();
44+ for (var k in components) {
45+ try {
46+ if (typeof layout[components[k]] == "object") {
47+ d[components[k]] = o[layout[components[k]]['field']];
48+ } else {
49+ d[components[k]] = o[layout[components[k]]];
50+ }
51+ } catch(err) {
52+ d[components[k]] = undefined;
53+ }
54+ }
55+ }
56+ return d;
57+}
58+
59+function update(object, overrides) {
60+ for (var k in overrides) {
61+ if (typeof object[k] == "string" && typeof overrides[k] == "object") {
62+ if (!overrides[k].hasOwnProperty('field')) overrides[k]['field'] = object[k];
63+ }
64+ if (object[k] === null) {
65+ object[k] = overrides[k];
66+ } else if (typeof object[k] == "object" && typeof overrides[k] == "string") {
67+ object[k]['field'] = overrides[k];
68+ } else if (typeof object[k] == "object") {
69+ update(object[k], overrides[k]);
70+ } else {
71+ object[k] = overrides[k];
72+ }
73+ }
74+ return object;
75+}
tests/qmltests/Dash/artwork/avatar@12.pngnew file

Binary file not shown.

tests/qmltests/Dash/artwork/music-player-design.pngnew file

Binary file not shown.

tests/qmltests/Dash/qml/FakeScopeView.qml+1
@@ -23,6 +23,7 @@ GenericScopeView {
2323 id: fakeScopeView
2424
2525 property alias backColor : back.color
26+ property var categoryView: null
2627
2728 onEndReached: {
2829 if (scope != undefined && shell != undefined && shell.scopeStatus != undefined) {
tests/qmltests/Dash/tst_Card.qmlnew file+337
@@ -0,0 +1,337 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Ubuntu.Components 0.1
20+import Unity.Test 0.1 as UT
21+import "../../../qml/Dash"
22+import "CardHelpers.js" as Helpers
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(80)
27+ height: units.gu(72)
28+ color: "#88FFFFFF"
29+
30+ property string defaultLayout: '
31+ {
32+ "schema-version": 1,
33+ "template": {
34+ "category-layout": "grid",
35+ "card-layout": "vertical",
36+ "card-size": "medium",
37+ "overlay-mode": null,
38+ "collapsed-rows": 2
39+ },
40+ "components": {
41+ "title": null,
42+ "art": {
43+ "aspect-ratio": 1.0,
44+ "fill-mode": "crop"
45+ },
46+ "subtitle": null,
47+ "mascot": null,
48+ "emblem": null,
49+ "old-price": null,
50+ "price": null,
51+ "alt-price": null,
52+ "rating": {
53+ "type": "stars",
54+ "range": [0, 5],
55+ "full": "image://theme/rating-star-full",
56+ "half": "image://theme/rating-star-half",
57+ "empty": "image://theme/rating-star-empty"
58+ },
59+ "alt-rating": null,
60+ "summary": null
61+ },
62+ "resources": {}
63+ }'
64+
65+ property string cardData: '
66+ {
67+ "art": "../../tests/qmltests/Dash/artwork/music-player-design.png",
68+ "mascot": "../../tests/qmltests/Dash/artwork/avatar.png",
69+ "title": "foo",
70+ "subtitle": "bar",
71+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
72+ }'
73+
74+ property string fullMapping: '
75+ {
76+ "title": "title",
77+ "art": "art",
78+ "subtitle": "subtitle",
79+ "mascot": "mascot",
80+ "summary": "summary"
81+ }'
82+
83+ property var cardsModel: [
84+ {
85+ "name": "Art, header, summary - vertical",
86+ "layout": { "components": JSON.parse(fullMapping) }
87+ },
88+ {
89+ "name": "Art, header, summary - vertical, small",
90+ "layout": { "template": { "card-size": "small" }, "components": JSON.parse(fullMapping) }
91+ },
92+ {
93+ "name": "Art, header, summary - vertical, large",
94+ "layout": { "template": { "card-size": "large" }, "components": JSON.parse(fullMapping) }
95+ },
96+ {
97+ "name": "Art, header, summary - vertical, wide",
98+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "aspect-ratio": 2 } }) }
99+ },
100+ {
101+ "name": "Art, title - vertical, fitted",
102+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "fill-mode": "fit" } }) }
103+ },
104+ {
105+ "name": "Art, header, summary - horizontal",
106+ "layout": { "template": { "card-layout": "horizontal" },
107+ "components": JSON.parse(fullMapping) }
108+ },
109+ ]
110+
111+ Card {
112+ id: card
113+ anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
114+
115+ template: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
116+ components: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
117+ cardData: Helpers.mapData(dataArea.text, components, dataError)
118+ }
119+
120+ Rectangle {
121+ anchors { top: parent.top; bottom: parent.bottom; right: parent.right}
122+ width: units.gu(40)
123+ color: "lightgrey"
124+
125+ Column {
126+ anchors { fill: parent; margins: units.gu(1) }
127+ spacing: units.gu(1)
128+
129+ OptionSelector {
130+ id: selector
131+ model: cardsModel
132+ delegate: OptionSelectorDelegate { text: modelData.name }
133+ onSelectedIndexChanged: updateAreas()
134+ Component.onCompleted: updateAreas()
135+
136+ function updateAreas() {
137+ var element = cardsModel[selectedIndex];
138+ if (element) {
139+ layoutArea.text = JSON.stringify(element.layout, undefined, 2) || "{}";
140+ // FIXME: don't overwrite data
141+ var data = JSON.parse(root.cardData);
142+ Helpers.update(data, element.data);
143+ dataArea.text = JSON.stringify(data, undefined, 2) || "{}";
144+ } else {
145+ layoutArea.text = "";
146+ dataArea.text = "";
147+ }
148+
149+ }
150+ }
151+
152+ TextArea {
153+ id: layoutArea
154+ anchors { left: parent.left; right: parent.right }
155+ height: units.gu(25)
156+ }
157+
158+ Label {
159+ id: layoutError
160+ anchors { left: parent.left; right: parent.right }
161+ height: units.gu(4)
162+ color: "orange"
163+ }
164+
165+ TextArea {
166+ id: dataArea
167+ anchors { left: parent.left; right: parent.right }
168+ height: units.gu(25)
169+ }
170+
171+ Label {
172+ id: dataError
173+ anchors { left: parent.left; right: parent.right }
174+ height: units.gu(4)
175+ color: "orange"
176+ }
177+ }
178+ }
179+
180+ UT.UnityTestCase {
181+ id: testCase
182+ name: "Card"
183+
184+ when: windowShown
185+
186+ property Item header: findChild(card, "cardHeader")
187+ property Item art: findChild(card, "artShape")
188+ property Item artImage: findChild(card, "artImage")
189+ property Item summary: findChild(card, "summaryLabel")
190+
191+ function initTestCase() {
192+ verify(testCase.header !== undefined, "Couldn't find header object.");
193+ }
194+
195+ function cleanup() {
196+ selector.selectedIndex = -1;
197+ }
198+
199+
200+ function test_header_binding_data() {
201+ return [
202+ { tag: "Mascot", property: "mascot", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
203+ { tag: "Title", property: "title", value: "foo", index: 0 },
204+ { tag: "Subtitle", property: "subtitle", value: "bar", index: 0 },
205+ ];
206+ }
207+
208+ function test_header_binding(data) {
209+ selector.selectedIndex = data.index;
210+ tryCompare(testCase.header, data.property, data.value);
211+ }
212+
213+ function test_card_binding_data() {
214+ return [
215+ { tag: "Art", object: artImage, property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
216+ { tag: "Summary", object: summary, property: "text", field: "summary", index: 0 },
217+ { tag: "Fit", object: art, fill: Image.PreserveAspectFit, index: 4 },
218+ ];
219+ }
220+
221+ function test_card_binding(data) {
222+ selector.selectedIndex = data.index;
223+
224+ if (data.hasOwnProperty('value')) {
225+ tryCompare(data.object, data.property, data.value);
226+ }
227+
228+ if (data.hasOwnProperty('field')) {
229+ tryCompare(data.object, data.property, card.cardData[data.field]);
230+ }
231+ }
232+
233+ function test_card_size_data() {
234+ return [
235+ { tag: "Medium", width: units.gu(18.5), index: 0 },
236+ { tag: "Small", width: units.gu(12), size: "small", index: 0 },
237+ { tag: "Large", width: units.gu(38), size: "large", index: 0 },
238+ { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
239+ { tag: "Horizontal", width: units.gu(38), index: 5 },
240+ ]
241+ }
242+
243+ function test_card_size(data) {
244+ selector.selectedIndex = data.index;
245+
246+ if (data.hasOwnProperty("size")) {
247+ card.template['card-size'] = data.size;
248+ card.templateChanged();
249+ }
250+
251+ if (data.hasOwnProperty("aspect")) {
252+ card.components['art']['aspect-ratio'] = data.aspect;
253+ card.componentsChanged();
254+ }
255+
256+ if (data.hasOwnProperty("width")) {
257+ tryCompare(card, "width", data.width);
258+ }
259+
260+ if (data.hasOwnProperty("height")) {
261+ tryCompare(card, "height", data.height);
262+ }
263+ }
264+
265+ function test_art_size_data() {
266+ return [
267+ { tag: "Medium", width: units.gu(18.5), fill: Image.PreserveAspectCrop, index: 0 },
268+ { tag: "Small", width: units.gu(12), index: 1 },
269+ { tag: "Large", width: units.gu(38), index: 2 },
270+ { tag: "Wide", height: units.gu(19), size: "large", index: 3 },
271+ { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
272+ { tag: "VerticalWidth", width: function() { return header.width }, index: 0 },
273+ { tag: "HorizontalHeight", height: function() { return header.height }, index: 5 },
274+ { tag: "HorizontalWidth", width: function() { return header.x }, index: 5 },
275+ ]
276+ }
277+
278+ function test_art_size(data) {
279+ selector.selectedIndex = data.index;
280+
281+ if (data.hasOwnProperty("size")) {
282+ card.template['card-size'] = data.size;
283+ card.templateChanged();
284+ }
285+
286+ if (data.hasOwnProperty("aspect")) {
287+ card.components['art']['aspect-ratio'] = data.aspect;
288+ card.componentsChanged();
289+ }
290+
291+ if (data.hasOwnProperty("width")) {
292+ if (typeof data.width === "function") {
293+ tryCompareFunction(function() { return art.width === data.width() }, true);
294+ } else tryCompare(art, "width", data.width);
295+ }
296+
297+ if (data.hasOwnProperty("height")) {
298+ if (typeof data.height === "function") {
299+ tryCompareFunction(function() { return art.height === data.height() }, true);
300+ } else tryCompare(art, "height", data.height);
301+ }
302+
303+ if (data.hasOwnProperty("fill")) {
304+ tryCompare(artImage, "fillMode", data.fill);
305+ }
306+ }
307+
308+ function test_art_layout_data() {
309+ return [
310+ { tag: "Vertical", left: function() { return 0 }, index: 0},
311+ { tag: "Horizontal", left: function() { return art.width }, index: 5 }
312+ ];
313+ }
314+
315+ function test_art_layout(data) {
316+ selector.selectedIndex = data.index;
317+
318+ tryCompare(testCase.header, "x", data.left());
319+ }
320+
321+ function test_header_layout_data() {
322+ return [
323+ { tag: "Vertical", top: function() { return art.y + art.height },
324+ left: function() { return art.x }, index: 0 },
325+ { tag: "Horizontal", top: function() { return art.y },
326+ left: function() { return art.x + art.width }, index: 5 }
327+ ]
328+ }
329+
330+ function test_header_layout(data) {
331+ selector.selectedIndex = data.index;
332+
333+ tryCompareFunction(function() { return testCase.header.y === data.top() }, true);
334+ tryCompareFunction(function() { return testCase.header.x === data.left() }, true);
335+ }
336+ }
337+}
tests/qmltests/Dash/tst_CardHeader.qmlnew file+138
@@ -0,0 +1,138 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Ubuntu.Components 0.1
20+import Unity.Test 0.1 as UT
21+import "../../../qml/Dash"
22+
23+
24+Rectangle {
25+ width: units.gu(40)
26+ height: units.gu(72)
27+ color: "lightgrey"
28+
29+ CardHeader {
30+ id: cardHeader
31+ anchors { left: parent.left; right: parent.right }
32+ }
33+
34+ Rectangle {
35+ anchors.fill: cardHeader
36+ color: "lightblue"
37+ opacity: 0.5
38+ }
39+
40+ UT.UnityTestCase {
41+ id: testCase
42+ name: "CardHeader"
43+
44+ when: windowShown
45+
46+ property Item mascot: findChild(cardHeader, "mascotShape")
47+ property Item titleLabel: findChild(cardHeader, "titleLabel")
48+ property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
49+ property Item prices: findChild(cardHeader, "prices")
50+ property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
51+ property Item outerRow: findChild(cardHeader, "outerRow")
52+ property Item column: findChild(cardHeader, "column")
53+
54+ function initTestCase() {
55+ verify(testCase.mascot !== undefined, "Couldn't find mascot object.");
56+ verify(testCase.titleLabel !== undefined, "Couldn't find titleLabel object.");
57+ verify(testCase.subtitleLabel !== undefined, "Couldn't find subtitleLabel object.");
58+ verify(testCase.prices !== undefined, "Couldn't find prices object.");
59+ verify(testCase.oldPriceLabel !== undefined, "Couldn't find oldPriceLabel object.");
60+ }
61+
62+ function cleanup() {
63+ cardHeader.mascot = "";
64+ cardHeader.title = "";
65+ cardHeader.subtitle = "";
66+ cardHeader.price = "";
67+ cardHeader.oldPrice = "";
68+ cardHeader.altPrice = "";
69+ }
70+
71+ function test_mascot_data() {
72+ return [
73+ { tag: "Empty", source: "", visible: false },
74+ { tag: "Invalid", source: "bad_path", visible: false },
75+ { tag: "Valid", source: Qt.resolvedUrl("artwork/avatar.png"), visible: true },
76+ ]
77+ }
78+
79+ function test_mascot(data) {
80+ cardHeader.mascot = data.source;
81+ tryCompare(testCase.mascot, "visible", data.visible);
82+ }
83+
84+ function test_labels_data() {
85+ return [
86+ { tag: "Empty", visible: false },
87+ { tag: "Title only", title: "Foo", visible: true },
88+ { tag: "Subtitle only", subtitle: "Bar", visible: false },
89+ { tag: "Both", title: "Foo", subtitle: "Bar", visible: true }
90+ ]
91+ }
92+
93+ function test_labels(data) {
94+ cardHeader.title = data.title !== undefined ? data.title : "";
95+ cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
96+ tryCompare(cardHeader, "visible", data.visible);
97+ }
98+
99+ function test_prices_data() {
100+ return [
101+ { tag: "Main", main: "$1.25", visible: true },
102+ { tag: "Alt", alt: "€1.00", visible: false },
103+ { tag: "Old", old: "€2.00", visible: false },
104+ { tag: "Main and Alt", main: "$1.25", alt: "€1.00", visible: true },
105+ { tag: "Main and Old", main: "$1.25", old: "$2.00", visible: true, oldAlign: Text.AlignRight },
106+ { tag: "Alt and Old", alt: "€1.00", old: "$2.00", visible: false },
107+ { tag: "All", main: "$1.25", alt: "€1.00", old: "$2.00", visible: true, oldAlign: Text.AlignHCenter }
108+ ]
109+ }
110+
111+ function test_prices(data) {
112+ cardHeader.price = data.main !== undefined ? data.main : "";
113+ cardHeader.oldPrice = data.old !== undefined ? data.old : "";
114+ cardHeader.altPrice = data.alt !== undefined ? data.alt : "";
115+ tryCompare(cardHeader, "visible", data.visible);
116+ if (data.hasOwnProperty("oldAlign")) {
117+ compare(testCase.oldPriceLabel.horizontalAlignment, data.oldAlign, "Old price label is aligned wrong.")
118+ }
119+ }
120+
121+ function test_dimensions_data() {
122+ return [
123+ { tag: "Column width", object: column, width: cardHeader.width - testCase.outerRow.spacing * 2 },
124+ { tag: "Column width", object: column, width: cardHeader.width - mascot.width - testCase.outerRow.spacing * 3, mascot: "artwork/avatar.png" }
125+ ]
126+ }
127+
128+ function test_dimensions(data) {
129+ if (data.hasOwnProperty("mascot")) {
130+ cardHeader.mascot = data.mascot;
131+ }
132+
133+ if (data.hasOwnProperty("width")) {
134+ tryCompare(data.object, "width", data.width);
135+ }
136+ }
137+ }
138+}
tests/qmltests/Dash/tst_Dash.qml+6−8
@@ -25,8 +25,6 @@ Item {
2525 width: units.gu(40)
2626 height: units.gu(80)
2727
28− property ListModel searchHistory : ListModel {}
29−
3028 Dash {
3129 id: dash
3230 anchors.fill: parent
@@ -83,14 +81,14 @@ Item {
8381 // wait for scopes to load
8482 tryCompare(scopes, "loaded", true);
8583
86− var dashbar = findChild(dash, "dashbar");
87− verify(dashbar != undefined)
84+ var tabbar = findChild(dash, "tabbar");
85+ verify(tabbar != undefined)
8886 var dashContent = findChild(dash, "dashContent");
8987 var current_index = dashContent.currentIndex;
9088
9189 dash.setCurrentScope(data.tag, true /* animate */, false /* reset */);
9290 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
93− compare(dashbar.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
91+ compare(tabbar.selectedIndex, data.shouldBeVisible ? data.visualIndex : current_index);
9492 }
9593
9694 function test_show_scope_on_load_data() {
@@ -127,12 +125,12 @@ Item {
127125 // wait for scopes to load
128126 tryCompare(scopes, "loaded", true);
129127
130− var dashbar = findChild(dash, "dashbar");
131− verify(dashbar != undefined)
128+ var tabbar = findChild(dash, "tabbar");
129+ verify(tabbar != undefined)
132130 var dashContent = findChild(dash, "dashContent");
133131 var current_index = dashContent.currentIndex;
134132
135− dashbar.itemSelected(data.visualIndex);
133+ tabbar.selectedIndex = data.visualIndex;
136134 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
137135 }
138136 }
tests/qmltests/Dash/tst_DashBar.qmldeleted−111
@@ -1,111 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Unity 0.1
20−import ".."
21−import "../../../qml/Dash"
22−import Ubuntu.Components 0.1
23−import Unity.Test 0.1 as UT
24−
25−Item {
26− width: units.gu(40)
27− height: units.gu(71)
28−
29− DashBar {
30− id: dashBar
31−
32− anchors {
33− left: parent.left
34− right: parent.right
35− bottom: parent.bottom
36− }
37− model: scopes
38− onItemSelected: currentIndex = index
39− }
40−
41− Scopes {
42− id: scopes
43− }
44−
45− SignalSpy {
46− id: signalSpy
47− signalName: "itemSelected"
48− target: dashBar
49− }
50−
51− UT.UnityTestCase {
52− name: "DashBar"
53− when: scopes.loaded
54−
55− property alias model: dashBar.model
56− property alias currentIndex: dashBar.currentIndex
57−
58− readonly property alias lineHeight: dashBar.lineHeight
59− readonly property alias itemSize: dashBar.itemSize
60− readonly property alias iconSize: dashBar.iconSize
61− readonly property var panel: findChild(dashBar, "panel");
62−
63− function initTestCase() {
64− currentIndex = 2
65− }
66−
67− function waitForAnimationToEnd() {
68− compare(panel.animating, true) // check the animation started
69− tryCompare(panel, "animating", false) // wait till the animation ends
70− }
71−
72− function closePanel() {
73− dashBar.finishNavigation()
74− waitForAnimationToEnd()
75− tryCompare(panel, "opened", false)
76− }
77−
78− function openPanel() {
79− dashBar.startNavigation()
80− waitForAnimationToEnd()
81− tryCompare(panel, "opened", true)
82− }
83−
84− function test_navigationAndHide() {
85− openPanel()
86− closePanel()
87− }
88−
89− function test_itemSelected() {
90− openPanel()
91−
92− var row = findChild(dashBar, "row");
93− tryCompareFunction(function(){return row.width > 0;}, true);
94− for (var i = 0; i < model.rowCount(); i++) {
95− // coordinate x in the middle of item with index 'i'
96− var x = row.x + (row.width / model.rowCount()) * i + itemSize / 2
97−
98− // FIXME workaround for a bug in SignalSpy
99− signalSpy.clear()
100−
101− // (item, x, y, button, modifiers, delay)
102− mouseClick(panel, x, row.height / 2, Qt.LeftButton, Qt.NoModifier, 0)
103− compare(signalSpy.count > 0, true, "signal itemSelected not triggered")
104− compare(signalSpy.signalArguments[0][0], i, "signal itemSelected emitted unexpected index");
105− tryCompare(dashBar, "currentIndex", i);
106− }
107−
108− closePanel()
109− }
110− }
111−}
tests/qmltests/Dash/tst_DashContent.qml+67−4
@@ -17,9 +17,11 @@
1717import QtQuick 2.0
1818import QtTest 1.0
1919import "../../../qml/Dash"
20+import "../../../qml/Components"
2021import Ubuntu.Components 0.1
2122import Unity 0.1
2223import Unity.Test 0.1 as UT
24+import Utils 0.1
2325
2426Item {
2527 id: shell
@@ -54,11 +56,13 @@ Item {
5456 id: dashContent
5557 anchors.fill: parent
5658
57− model: scopesModel
59+ model: SortFilterProxyModel {
60+ model: scopesModel
61+ }
5862 scopes : scopesModel
5963
6064 scopeMapper : scopeDelegateMapper
61− searchHistory: ListModel {}
65+ searchHistory: SearchHistoryModel {}
6266 }
6367
6468 ScopeDelegateMapper {
@@ -109,9 +113,10 @@ Item {
109113
110114 UT.UnityTestCase {
111115 name: "DashContent"
112− when: windowShown
116+ when: scopesModel.loaded
113117
114118 function init() {
119+ scopesModel.clear();
115120 scopesModel.load();
116121 }
117122
@@ -268,6 +273,65 @@ Item {
268273 tryCompare(scopesModel.get(2), "isActive", data.active2);
269274 }
270275
276+ function doFindMusicButton(parent) {
277+ for (var i = 0; i < parent.children.length; i++) {
278+ var c = parent.children[i];
279+ if (UT.Util.isInstanceOf(c, "AbstractButton") && parent.x >= 0) {
280+ for (var ii = 0; ii < c.children.length; ii++) {
281+ var cc = c.children[ii];
282+ if (UT.Util.isInstanceOf(cc, "Label") && cc.text == "Music") {
283+ return c;
284+ }
285+ }
286+ }
287+ var r = doFindMusicButton(c);
288+ if (r !== undefined) {
289+ return r;
290+ }
291+ }
292+ return undefined;
293+ }
294+
295+ function findMusicButton() {
296+ // We need to find a AbstractButton that has a Label child
297+ // with text Music and it's parent x is >= 0
298+ var tabbar = findChild(dashContent, "tabbar");
299+ return doFindMusicButton(tabbar);
300+ }
301+
302+ function test_tabBar_index_change() {
303+ tryCompare(scopesModel, "loaded", true);
304+ var tabbar = findChild(dashContent, "tabbar");
305+
306+ compare(dashContent.currentIndex, 0);
307+ tryCompare(tabbar, "selectedIndex", 0);
308+ tryCompare(tabbar, "selectionMode", false);
309+
310+ mouseClick(tabbar, units.gu(5), units.gu(5))
311+
312+ tryCompare(tabbar, "selectionMode", true);
313+ tryCompare(tabbar, "selectedIndex", 0);
314+ tryCompare(dashContent, "currentIndex", 0);
315+
316+ var button;
317+ tryCompareFunction(function() { button = findMusicButton(); return button != undefined; }, true);
318+ waitForRendering(button);
319+
320+ mouseClick(button, button.width / 2, button.height / 2)
321+
322+ tryCompare(tabbar, "selectionMode", false);
323+ tryCompare(tabbar, "selectedIndex", 1);
324+ tryCompare(dashContent, "currentIndex", 1);
325+ }
326+
327+ function test_tabBar_listens_to_index_change() {
328+ var tabbar = findChild(dashContent, "tabbar");
329+ tryCompare(dashContent, "currentIndex", 0);
330+ compare(tabbar.selectedIndex, 0);
331+ dashContent.currentIndex = 1;
332+ compare(tabbar.selectedIndex, 1);
333+ }
334+
271335 function checkFlickMovingAndNotInteractive()
272336 {
273337 var dashContentList = findChild(dashContent, "dashContentList");
@@ -282,7 +346,6 @@ Item {
282346 return dashContentList.currentItem.moving && !dashContentList.interactive;
283347 }
284348
285−
286349 function test_hswipe_disabled_vswipe() {
287350 var dashContentList = findChild(dashContent, "dashContentList");
288351
tests/qmltests/Dash/tst_GenericScopeView.qml+57−27
@@ -41,54 +41,83 @@ Item {
4141 signal mainStageFocusedApplicationChanged()
4242 }
4343
44+ PreviewListView {
45+ id: previewListView
46+ anchors.fill: parent
47+ openEffect: openEffect
48+ categoryView: genericScopeView.categoryView
49+ scope: genericScopeView.scope
50+ }
51+
52+ DashContentOpenEffect {
53+ id: openEffect
54+ previewListView: previewListView
55+ }
56+
57+ PageHeaderLabel {
58+ id: pageHeader
59+ searchHistory: SearchHistoryModel {}
60+ }
61+
4462 GenericScopeView {
4563 id: genericScopeView
4664 anchors.fill: parent
47− searchHistory: SearchHistoryModel {}
65+ previewListView: previewListView
66+ openEffect: openEffect
67+ pageHeader: pageHeader
68+ tabBarHeight: pageHeader.implicitHeight
4869
4970 UT.UnityTestCase {
5071 name: "GenericScopeView"
5172 when: scopes.loaded
5273
53− function test_isCurrent() {
54− var pageHeader = findChild(genericScopeView, "pageHeader");
55− var previewListView = findChild(genericScopeView, "previewListView");
74+ function test_isCurrent() {
5675 genericScopeView.isCurrent = true
5776 pageHeader.searchQuery = "test"
5877 previewListView.open = true
5978 genericScopeView.isCurrent = false
6079 tryCompare(pageHeader, "searchQuery", "")
61− tryCompare(genericScopeView, "previewShown", false);
80+ tryCompare(previewListView, "open", false);
6281 }
6382
6483 function test_showDash() {
65− var previewListView = findChild(genericScopeView, "previewListView");
6684 previewListView.open = true;
85+ tryCompare(openEffect, "live", true);
6786 scopes.get(0).showDash();
68− tryCompare(genericScopeView, "previewShown", false);
87+ tryCompare(previewListView, "open", false);
88+ tryCompare(openEffect, "live", true);
6989 }
7090
7191 function test_hideDash() {
72− var previewListView = findChild(genericScopeView, "previewListView");
7392 previewListView.open = true;
7493 scopes.get(0).hideDash();
75− tryCompare(genericScopeView, "previewShown", false);
94+ tryCompare(previewListView, "open", false);
7695 }
7796
7897 function openPreview() {
7998 var categoryListView = findChild(genericScopeView, "categoryListView");
8099 categoryListView.positionAtBeginning();
81100
101+ tryCompareFunction(function() {
102+ var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
103+ return tile != undefined;
104+ },
105+ true);
82106 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
83107 mouseClick(tile, tile.width / 2, tile.height / 2);
84− var openEffect = findChild(genericScopeView, "openEffect");
108+ tryCompare(previewListView, "open", true);
85109 tryCompare(openEffect, "gap", 1);
86110 }
87111
88112 function checkArrowPosition(index) {
113+ tryCompareFunction(function() {
114+ var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
115+ return tile != undefined;
116+ },
117+ true);
89118 var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
90119 var tileCenter = tile.x + tile.width/2;
91− var pointerArrow = findChild(genericScopeView, "pointerArrow");
120+ var pointerArrow = findChild(previewListView, "pointerArrow");
92121 var pointerArrowCenter = pointerArrow.x + pointerArrow.width/2;
93122 compare(pointerArrowCenter, tileCenter, "Pointer did not move to tile");
94123 }
@@ -97,9 +126,7 @@ Item {
97126 var closePreviewMouseArea = findChild(genericScopeView, "closePreviewMouseArea");
98127 mouseClick(closePreviewMouseArea, closePreviewMouseArea.width / 2, closePreviewMouseArea.height / 2);
99128
100− var previewListView = findChild(genericScopeView, "previewListView");
101129 tryCompare(previewListView, "open", false);
102− var openEffect = findChild(genericScopeView, "openEffect");
103130 tryCompare(openEffect, "gap", 0);
104131
105132 var categoryListView = findChild(genericScopeView, "categoryListView");
@@ -108,13 +135,12 @@ Item {
108135 }
109136
110137 function test_previewOpenClose() {
111− var previewListView = findChild(genericScopeView, "previewListView");
112138 tryCompare(previewListView, "open", false);
113139
114140 openPreview();
115141
116142 // check for it opening successfully
117− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
143+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
118144 tryCompareFunction(function() {
119145 var parts = currentPreviewItem.source.toString().split("/");
120146 var name = parts[parts.length - 1];
@@ -138,26 +164,23 @@ Item {
138164 var categoryListView = findChild(genericScopeView, "categoryListView");
139165 categoryListView.positionAtBeginning();
140166 waitForRendering(categoryListView);
141− categoryListView.flick(0, -units.gu(80));
167+ categoryListView.flick(0, -units.gu(60));
142168 tryCompare(categoryListView.flicking, false);
143169
144170 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
145171 mouseClick(tile, tile.width / 2, tile.height - 1);
146− var openEffect = findChild(genericScopeView, "openEffect");
147172 tryCompare(openEffect, "gap", 1);
148173
149− var pageHeader = findChild(genericScopeView, "pageHeader");
150174 verify(openEffect.positionPx >= pageHeader.height + categoryListView.stickyHeaderHeight);
151175 }
152176
153177 function test_previewCycle() {
154− var previewListView = findChild(genericScopeView, "previewListView");
155178 tryCompare(previewListView, "open", false);
156179
157180 openPreview();
158181
159182 // wait for it to be loaded
160− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
183+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
161184 tryCompareFunction(function() {
162185 var parts = currentPreviewItem.source.toString().split("/");
163186 var name = parts[parts.length - 1];
@@ -179,7 +202,7 @@ Item {
179202 previewListView.height / 2);
180203
181204 // wait for it to be loaded
182− var nextPreviewItem = findChild(genericScopeView, "previewLoader" + i);
205+ var nextPreviewItem = findChild(previewListView, "previewLoader" + i);
183206 tryCompareFunction(function() {
184207 var parts = nextPreviewItem.source.toString().split("/");
185208 var name = parts[parts.length - 1];
@@ -206,11 +229,12 @@ Item {
206229
207230 function test_show_spinner() {
208231 openPreview();
209− var previewListView = findChild(genericScopeView, "previewListView");
210− var previewLoader = findChild(genericScopeView, "previewLoader0");
232+ var previewLoader = findChild(previewListView, "previewLoader0");
233+ tryCompare(previewLoader, "progress", 1.0);
234+ tryCompareFunction(function() { return previewLoader.item != undefined; }, true);
211235
212236 previewLoader.item.showProcessingAction = true;
213− var waitingForAction = findChild(genericScopeView, "waitingForActionMouseArea");
237+ var waitingForAction = findChild(previewListView, "waitingForActionMouseArea");
214238 tryCompare(waitingForAction, "enabled", true);
215239 previewLoader.closePreviewSpinner();
216240 tryCompare(waitingForAction, "enabled", false);
@@ -252,12 +276,10 @@ Item {
252276 tryCompareFunction(function() { return findChild(genericScopeView, "carouselDelegate") != undefined; }, true);
253277 var tile = findChild(genericScopeView, "carouselDelegate");
254278 mouseClick(tile, tile.width / 2, tile.height / 2);
255− var openEffect = findChild(genericScopeView, "openEffect");
256279 tryCompare(openEffect, "gap", 1);
257280
258281 // check for it opening successfully
259− var previewListView = findChild(genericScopeView, "previewListView");
260− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
282+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
261283 tryCompareFunction(function() {
262284 var parts = currentPreviewItem.source.toString().split("/");
263285 var name = parts[parts.length - 1];
@@ -299,6 +321,14 @@ Item {
299321
300322 categoryListView.positionAtBeginning();
301323
324+ // wait for the header0 to be on its position
325+ tryCompareFunction(
326+ function() {
327+ var header0 = findChild(genericScopeView, "dashSectionHeader0")
328+ return header0.y == pageHeader.height;
329+ },
330+ true);
331+
302332 var header0 = findChild(genericScopeView, "dashSectionHeader0")
303333 var category0 = findChild(genericScopeView, "dashCategory0")
304334 mouseClick(header0, header0.width / 2, header0.height / 2);
tests/qmltests/Greeter/tst_Lockscreen.qml+1−1
@@ -134,7 +134,7 @@ Rectangle {
134134 if (data.pinPadAvailable) {
135135 compare(findChild(lockscreen, "pinPadButton8").text, "8", "Could not find number 8 on PinPad")
136136 } else {
137− compare(findChild(lockscreen, "pinPadButton8"), undefined, "Could find number 8 on PinPad even though it should be only OSK")
137+ compare(findChild(lockscreen, "pinPadButton8"), null, "Could find number 8 on PinPad even though it should be only OSK")
138138 }
139139 }
140140
tests/qmltests/Panel/tst_IndicatorItem.qml−11
@@ -41,17 +41,6 @@ Rectangle {
4141 tryCompareFunction(function(){return indicatorItem.opacity < 1.0}, true);
4242 }
4343
44− function test_highlight() {
45− var itemHighlight = findChild(indicatorItem, "highlight");
46− verify(itemHighlight != undefined);
47−
48− indicatorItem.highlighted = true;
49− compare(itemHighlight.visible, true, "Indicator should be highlighted");
50−
51− indicatorItem.highlighted = false;
52− compare(itemHighlight.visible, false, "Indicator should not be highlighted");
53− }
54−
5544 function test_empty() {
5645 compare(indicatorItem.indicatorVisible, false, "IndicatorItem should not be visible.");
5746 indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
tests/qmltests/Panel/tst_IndicatorRow.qml+18−4
@@ -34,6 +34,7 @@ Item {
3434 {
3535 indicatorRow.state = "initial";
3636 indicatorRow.currentItem = null;
37+ indicatorRow.unitProgress = 0.0;
3738 }
3839
3940 PanelBackground {
@@ -83,14 +84,13 @@ Item {
8384 function test_current_item_commit() {
8485 init_test();
8586
86− indicatorRow.setCurrentItem(1);
87+ indicatorRow.setCurrentItem(2);
8788 indicatorRow.state = "commit";
8889 tryCompare(get_indicator_item_at(0), "opacity", 0.0);
89− tryCompare(get_indicator_item_at(1), "opacity", 1.0);
90− tryCompare(get_indicator_item_at(2), "opacity", 0.0);
90+ tryCompare(get_indicator_item_at(1), "opacity", 0.0);
91+ tryCompare(get_indicator_item_at(2), "opacity", 1.0);
9192 tryCompare(get_indicator_item_at(3), "opacity", 0.0);
9293 tryCompare(get_indicator_item_at(4), "opacity", 0.0);
93−
9494 }
9595 }
9696
@@ -166,6 +166,20 @@ Item {
166166 compare(get_indicator_item_at(3).highlighted, false, "Other indicators should not highlight when in locked state");
167167 compare(get_indicator_item_at(4).highlighted, false, "Other indicators should not highlight when in locked state");
168168 }
169+
170+ function test_opacity() {
171+ init_test();
172+
173+ indicatorRow.state = "reveal";
174+ indicatorRow.unitProgress = 0.5;
175+ indicatorRow.setCurrentItem(2);
176+
177+ compare(get_indicator_item_at(0).opacity, 0.5, "Other indicator opacity should change in response to panel progress");
178+ compare(get_indicator_item_at(1).opacity, 0.5, "Other indicator opacity should change in response to panel progress");
179+ compare(get_indicator_item_at(2).opacity, 1.0, "Current indicator should not change in response to panel progress");
180+ compare(get_indicator_item_at(3).opacity, 0.5, "Other indicator opacity should change in response to panel progress");
181+ compare(get_indicator_item_at(4).opacity, 0.5, "Other indicator opacity should change in response to panel progress");
182+ }
169183 }
170184
171185 UT.UnityTestCase {
tests/qmltests/Panel/tst_Indicators.qml+1−1
@@ -175,7 +175,7 @@ Item {
175175 tryCompare(indicatorItem, "visible", data.visible);
176176
177177 var indicatorTab = findChild(indicatorTabs, data.name)
178− tryCompareFunction(function() { return indicatorTab !== undefined }, data.visible);
178+ tryCompareFunction(function() { return indicatorTab !== null }, data.visible);
179179 }
180180 }
181181}
tests/utils/modules/Unity/Test/UnityTestCase.qml+2−2
@@ -83,7 +83,7 @@ TestCase {
8383 }
8484 childs.splice(0, 1);
8585 }
86− return undefined;
86+ return null;
8787 }
8888
8989 // Find an object with the given name in the children tree of "obj"
@@ -103,7 +103,7 @@ TestCase {
103103 }
104104 childs.splice(0, 1);
105105 }
106− return undefined;
106+ return null;
107107 }
108108
109109 // Type a full string instead of keyClick letter by letter
tools/CMakeLists.txtnew file+11
@@ -0,0 +1,11 @@
1+add_executable(${SCOPE_TOOL}
2+ scopetool.cpp
3+ registry-tracker.cpp
4+ )
5+
6+qt5_use_modules(${SCOPE_TOOL} Qml Quick)
7+
8+# install binaries
9+install(TARGETS ${SCOPE_TOOL}
10+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
11+ )
tools/registry-tracker.cppnew file+116
@@ -0,0 +1,116 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Michal Hruby <michal.hruby@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+// Qt
21+#include <QtGlobal>
22+#include <QDir>
23+
24+// local
25+#include "registry-tracker.h"
26+
27+
28+RegistryTracker::RegistryTracker(QString const& scope_dir):
29+ m_scopeDir(scope_dir),
30+ m_registry(nullptr),
31+ m_endpoints_dir(QDir::temp().filePath("scope-dev-endpoints.XXXXXX"))
32+{
33+ runRegistry();
34+}
35+
36+RegistryTracker::~RegistryTracker()
37+{
38+ if (m_registry.state() != QProcess::NotRunning) {
39+ m_registry.terminate();
40+ m_registry.waitForFinished(5000);
41+ m_registry.kill();
42+ }
43+}
44+
45+#define RUNTIME_CONFIG \
46+"[Runtime]\n" \
47+"Registry.Identity = Registry\n" \
48+"Registry.ConfigFile = %1\n" \
49+"Default.Middleware = Zmq\n" \
50+"Zmq.ConfigFile = %2\n"
51+
52+#define REGISTRY_CONFIG \
53+"[Registry]\n" \
54+"Middleware = Zmq\n" \
55+"Zmq.Endpoint = ipc://%1/Registry\n" \
56+"Zmq.EndpointDir = %2\n" \
57+"Zmq.ConfigFile = %3\n" \
58+"Scope.InstallDir = %4\n" \
59+"Scoperunner.Path = %5\n"
60+
61+#define MW_CONFIG \
62+"[Zmq]\n" \
63+"EndpointDir.Public = %1\n" \
64+"EndpointDir.Private = %2\n"
65+
66+void RegistryTracker::runRegistry()
67+{
68+ QDir tmp(QDir::temp());
69+ m_runtime_config.setFileTemplate(tmp.filePath("Runtime.ini.XXXXXX"));
70+ m_registry_config.setFileTemplate(tmp.filePath("Registry.ini.XXXXXX"));
71+ m_mw_config.setFileTemplate(tmp.filePath("Zmq.ini.XXXXXX"));
72+
73+ if (!m_runtime_config.open() || !m_registry_config.open() || !m_mw_config.open() || !m_endpoints_dir.isValid()) {
74+ qWarning("Unable to open temporary files!");
75+ return;
76+ }
77+
78+ QString scopesLibdir;
79+ {
80+ QProcess pkg_config;
81+ QStringList arguments;
82+ arguments << "--variable=libdir";
83+ arguments << "libunity-scopes";
84+ pkg_config.start("pkg-config", arguments);
85+ pkg_config.waitForFinished();
86+ QByteArray libdir = pkg_config.readAllStandardOutput();
87+ scopesLibdir = QDir(QString::fromLocal8Bit(libdir)).path().trimmed();
88+ }
89+
90+ if (scopesLibdir.size() == 0) {
91+ qWarning("Unable to find libunity-scopes package config file");
92+ return;
93+ }
94+
95+ QString scopeRunnerPath = QDir(scopesLibdir).filePath("scoperunner/scoperunner");
96+
97+ QString runtime_ini = QString(RUNTIME_CONFIG).arg(m_registry_config.fileName()).arg(m_mw_config.fileName());
98+ QString registry_ini = QString(REGISTRY_CONFIG).arg(m_endpoints_dir.path()).arg(m_endpoints_dir.path()).arg(m_mw_config.fileName()).arg(m_scopeDir).arg(scopeRunnerPath);
99+ QString mw_ini = QString(MW_CONFIG).arg(m_endpoints_dir.path()).arg(m_endpoints_dir.path());
100+
101+ m_runtime_config.write(runtime_ini.toUtf8());
102+ m_registry_config.write(registry_ini.toUtf8());
103+ m_mw_config.write(mw_ini.toUtf8());
104+
105+ m_runtime_config.flush();
106+ m_registry_config.flush();
107+ m_mw_config.flush();
108+
109+ qputenv("UNITY_SCOPES_RUNTIME_PATH", m_runtime_config.fileName().toLocal8Bit());
110+
111+ QString registryBin(QDir(scopesLibdir).filePath("scoperegistry/scoperegistry"));
112+ QStringList arguments;
113+ arguments << m_runtime_config.fileName();
114+
115+ m_registry.start(registryBin, arguments);
116+}
tools/registry-tracker.hnew file+44
@@ -0,0 +1,44 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Michal Hruby <michal.hruby@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+// Qt
21+#include <QProcess>
22+#include <QTemporaryFile>
23+#include <QTemporaryDir>
24+#include <QDebug>
25+
26+
27+class RegistryTracker
28+{
29+public:
30+ RegistryTracker(QString const&);
31+ ~RegistryTracker();
32+
33+ QProcess* registry() const;
34+
35+private:
36+ void runRegistry();
37+
38+ QString m_scopeDir;
39+ QProcess m_registry;
40+ QTemporaryDir m_endpoints_dir;
41+ QTemporaryFile m_runtime_config;
42+ QTemporaryFile m_registry_config;
43+ QTemporaryFile m_mw_config;
44+};
tools/scopetool.cppnew file+84
@@ -0,0 +1,84 @@
1+/*
2+ * Copyright (C) 2012 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Gerry Boland <gerry.boland@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+// Qt
21+#include <QtQuick/QQuickView>
22+#include <QtGui/QGuiApplication>
23+#include <QtQml/QQmlEngine>
24+#include <QtQml/QQmlContext>
25+#include <QScopedPointer>
26+#include <QDebug>
27+#include <libintl.h>
28+
29+
30+// local
31+#include <paths.h>
32+#include "registry-tracker.h"
33+
34+
35+int main(int argc, char *argv[])
36+{
37+ /* Workaround Qt platform integration plugin not advertising itself
38+ as having the following capabilities:
39+ - QPlatformIntegration::ThreadedOpenGL
40+ - QPlatformIntegration::BufferQueueingOpenGL
41+ */
42+ setenv("QML_FORCE_THREADED_RENDERER", "1", 1);
43+ setenv("QML_FIXED_ANIMATION_STEP", "1", 1);
44+
45+ QGuiApplication::setApplicationName("Unity Scope Tool");
46+ QGuiApplication *application;
47+ application = new QGuiApplication(argc, argv);
48+
49+ QStringList arguments = application->arguments();
50+ QString scopesDir;
51+ if (arguments.contains("--scope-dir")) {
52+ int idx = arguments.indexOf("--scope-dir");
53+ scopesDir = arguments[idx+1];
54+ }
55+
56+ QScopedPointer<RegistryTracker> tracker;
57+ if (!scopesDir.isEmpty()) {
58+ tracker.reset(new RegistryTracker(scopesDir));
59+ }
60+
61+ resolveIconTheme();
62+
63+ bindtextdomain("unity8", translationDirectory().toUtf8().data());
64+
65+ QQuickView* view = new QQuickView();
66+ view->setResizeMode(QQuickView::SizeRootObjectToView);
67+ view->setTitle(QGuiApplication::applicationName());
68+ view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
69+
70+ QUrl source(::qmlDirectory() + "ScopeTool.qml");
71+ prependImportPaths(view->engine(), ::overrideImportPaths());
72+ appendImportPaths(view->engine(), ::fallbackImportPaths());
73+
74+ view->setSource(source);
75+
76+ view->show();
77+
78+ int result = application->exec();
79+
80+ delete view;
81+ delete application;
82+
83+ return result;
84+}
unity8.qmlprojectdeleted−25
@@ -1,25 +0,0 @@
1−/* File generated by Qt Creator, version 2.4.1 */
2−
3−import QmlProject 1.1
4−
5−Project {
6− mainFile: "Shell.qml"
7−
8− /* Include .qml, .js, and image files from current directory and subdirectories */
9− QmlFiles {
10− directory: "."
11− }
12− JavaScriptFiles {
13− directory: "."
14− }
15− ImageFiles {
16− directory: "."
17− }
18− /* List of plugin directories passed to QML runtime */
19− importPaths: [
20− "builddir/plugins",
21− "builddir/modules",
22− "builddir/tests/plugins",
23− "builddir/tests/utils/modules"
24− ]
25−}