on xenial help sign in
Merge from trunk
86b6774Michael Terry
CMakeLists.txt+2
@@ -97,6 +97,7 @@ declare_autopilot_test(shell unity8.shell ${CMAKE_SOURCE_DIR}/tests/autopilot/)
97
97
98set(SHELL_APP unity8)
98set(SHELL_APP unity8)
99set(GREETER_APP unity8-greeter)
99set(GREETER_APP unity8-greeter)
100+set(SCOPE_TOOL unity-scope-tool)
100
101
101include_directories(
102include_directories(
102 ${CMAKE_SOURCE_DIR}/include
103 ${CMAKE_SOURCE_DIR}/include
@@ -106,6 +107,7 @@ include_directories(
106# add subdirectories to build
107# add subdirectories to build
107add_subdirectory(include)
108add_subdirectory(include)
108add_subdirectory(src)
109add_subdirectory(src)
110+add_subdirectory(tools)
109add_subdirectory(qml)
111add_subdirectory(qml)
110add_subdirectory(tests)
112add_subdirectory(tests)
111add_subdirectory(plugins)
113add_subdirectory(plugins)
CODING+29
@@ -166,3 +166,32 @@ $ make tryDragHandle
166NB: Manual test or interation doesn't make sense for all components or classes,
166NB: Manual test or interation doesn't make sense for all components or classes,
167particularly the non-graphical ones. In such cases no "tryFoo" make target is
167particularly the non-graphical ones. In such cases no "tryFoo" make target is
168provided.
168provided.
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)
4 add_custom_target(autopilot-${TEST_NAME}
4 add_custom_target(autopilot-${TEST_NAME}
5 COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks autopilot run ${TEST_SUITE}
5 COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks autopilot run ${TEST_SUITE}
6 WORKING_DIRECTORY ${WORKING_DIR}
6 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
8 )
12 )
9
13
10 add_dependencies(autopilot autopilot-${TEST_NAME})
14 add_dependencies(autopilot autopilot-${TEST_NAME})
data/CMakeLists.txt+3−2
@@ -1,9 +1,10 @@
1# generate desktop file
1# generate desktop file
2configure_file(${SHELL_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop @ONLY)
2configure_file(${SHELL_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop @ONLY)
3configure_file(${GREETER_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${GREETER_APP}.desktop @ONLY)
3configure_file(${GREETER_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${GREETER_APP}.desktop @ONLY)
4+configure_file(${SCOPE_TOOL}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SCOPE_TOOL}.desktop @ONLY)
4
5
5−# install desktop file
6+# install desktop files
6−install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop
7+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop ${CMAKE_CURRENT_BINARY_DIR}/${SCOPE_TOOL}.desktop
7 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
8 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
8 )
9 )
9install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${GREETER_APP}.desktop
10install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${GREETER_APP}.desktop
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/changelog+45
@@ -1,3 +1,48 @@
1+unity8 (7.84+14.04.20131220-0ubuntu1) trusty; urgency=low
2+
3+ [ Michał Sawicz ]
4+ * Clean up root project file. Introduce include/ and qml/ and move
5+ files around to be where they fit, also adapt everything else to
6+ match.
7+
8+ [ Michael Hall ]
9+ * Update CODING to reflect the fact that only 14.04 is supported
10+ currently.
11+
12+ [ Christopher Lee ]
13+ * Make use of helpers in all tests. (LP: #1260860). (LP: #1260860)
14+
15+ [ Nic ]
16+ * Added kill time 30 to unity8 override. Added install path. (LP:
17+ #1260379)
18+
19+ [ Nick Dedekind ]
20+ * Added parser for strftime in TimeFormatter. Moved TimeFormatter to
21+ Utils plugin.
22+
23+ [ Mirco Müller ]
24+ * Support fullscreen for special-case extended snap-decision of the
25+ pin-unlock dialog.
26+
27+ [ Michael Zanetti ]
28+ * Change the default behaviour of the Lockscreen to have a variable
29+ PIN length, requiring the user to confirm with OK.
30+
31+ [ Daniel d'Andrada ]
32+ * Update CODING with instructions on how to run tests.
33+
34+ [ Dimitri John Ledkov ]
35+ * Fix cross-compilation.
36+
37+ [ Albert Astals ]
38+ * Vertical journal Comes from lp:~aacid/+junk/verticalJournal.
39+ * Add code and tests for incremental inserting/removing from the end.
40+
41+ [ Ubuntu daily release ]
42+ * Automatic snapshot from revision 603
43+
44+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 20 Dec 2013 03:23:08 +0000
45+
1unity8 (7.84+14.04.20131212-0ubuntu1) trusty; urgency=low
46unity8 (7.84+14.04.20131212-0ubuntu1) trusty; urgency=low
2
47
3 [ Michał Sawicz ]
48 [ Michał Sawicz ]
debian/control+10
@@ -60,6 +60,15 @@ Depends: qmenumodel-qml (>= 0.2.7),
60Description: Indicators client test application
60Description: Indicators client test application
61 This package contains the client application for indicators used by autopilot
61 This package contains the client application for indicators used by autopilot
62
62
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+
63Package: unity8
72Package: unity8
64Architecture: any
73Architecture: any
65Provides: indicator-renderer,
74Provides: indicator-renderer,
@@ -110,6 +119,7 @@ Depends: gir1.2-glib-2.0,
110 libqt5widgets5,
119 libqt5widgets5,
111 python-evdev,
120 python-evdev,
112 python-gi,
121 python-gi,
122+ python-mock,
113 unity8 (>= ${source:Version}),
123 unity8 (>= ${source:Version}),
114 unity8-fake-env (>= ${source:Version}),
124 unity8-fake-env (>= ${source:Version}),
115 unity8-greeter (>= ${source:Version}),
125 unity8-greeter (>= ${source:Version}),
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)
24set(QMLPLUGIN_SRC
24set(QMLPLUGIN_SRC
25 plugin.cpp
25 plugin.cpp
26 listviewwithpageheader.cpp
26 listviewwithpageheader.cpp
27+ abstractjournal.cpp
27 verticaljournal.cpp
28 verticaljournal.cpp
29+ horizontaljournal.cpp
28 )
30 )
29
31
30add_library(DashViews-qml MODULE
32add_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 @@
17
17
18#include "plugin.h"
18#include "plugin.h"
19
19
20+#include "horizontaljournal.h"
20#include "listviewwithpageheader.h"
21#include "listviewwithpageheader.h"
21#include "verticaljournal.h"
22#include "verticaljournal.h"
22
23
@@ -26,6 +27,7 @@ void DashViewsPlugin::registerTypes(const char *uri)
26{
27{
27 Q_ASSERT(uri == QLatin1String("DashViews"));
28 Q_ASSERT(uri == QLatin1String("DashViews"));
28 qmlRegisterType<QAbstractItemModel>();
29 qmlRegisterType<QAbstractItemModel>();
30+ qmlRegisterType<HorizontalJournal>(uri, 0, 1, "HorizontalJournal");
29 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
31 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
30 qmlRegisterType<VerticalJournal>(uri, 0, 1, "VerticalJournal");
32 qmlRegisterType<VerticalJournal>(uri, 0, 1, "VerticalJournal");
31}
33}
plugins/DashViews/verticaljournal.cpp+77−406
@@ -27,88 +27,16 @@
27 */
27 */
28#include "verticaljournal.h"
28#include "verticaljournal.h"
29
29
30−#include <qqmlengine.h>
31#pragma GCC diagnostic push
30#pragma GCC diagnostic push
32#pragma GCC diagnostic ignored "-pedantic"
31#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
39#include <private/qquickitem_p.h>
32#include <private/qquickitem_p.h>
40#pragma GCC diagnostic pop
33#pragma GCC diagnostic pop
41
34
42static const qreal bufferRatio = 0.5;
35static const qreal bufferRatio = 0.5;
43
36
44VerticalJournal::VerticalJournal()
37VerticalJournal::VerticalJournal()
45− : m_delegateModel(nullptr)
46− , m_asyncRequestedIndex(-1)
47− , m_columnWidth(0)
38+ : 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
63−{
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{
39{
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− }
112}
40}
113
41
114qreal VerticalJournal::columnWidth() const
42qreal VerticalJournal::columnWidth() const
@@ -133,133 +61,6 @@ void VerticalJournal::setColumnWidth(qreal columnWidth)
133 }
61 }
134}
62}
135
63
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−
263void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
64void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
264{
65{
265 *modelIndex = 0;
66 *modelIndex = 0;
@@ -268,7 +69,7 @@ void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
268 Q_FOREACH(const auto &column, m_columnVisibleItems) {
69 Q_FOREACH(const auto &column, m_columnVisibleItems) {
269 if (!column.isEmpty()) {
70 if (!column.isEmpty()) {
270 const ViewItem &item = column.last();
71 const ViewItem &item = column.last();
271− *yPos = qMin(*yPos, item.y() + item.height() + m_rowSpacing);
72+ *yPos = qMin(*yPos, item.y() + item.height() + rowSpacing());
272 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
73 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
273 } else {
74 } else {
274 *yPos = 0;
75 *yPos = 0;
@@ -287,7 +88,7 @@ void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
287 const auto &column = m_columnVisibleItems[i];
88 const auto &column = m_columnVisibleItems[i];
288 if (!column.isEmpty()) {
89 if (!column.isEmpty()) {
289 const ViewItem &item = column.first();
90 const ViewItem &item = column.first();
290− const auto itemTopPos = item.y() - m_rowSpacing;
91+ const auto itemTopPos = item.y() - rowSpacing();
291 if (itemTopPos > *yPos) {
92 if (itemTopPos > *yPos) {
292 *yPos = itemTopPos;
93 *yPos = itemTopPos;
293 *modelIndex = item.m_modelIndex - 1;
94 *modelIndex = item.m_modelIndex - 1;
@@ -309,51 +110,19 @@ void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
309 }
110 }
310}
111}
311
112
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)
345{
114{
346 bool changed = false;
115 bool changed = false;
347
116
348 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
117 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
349 QList<ViewItem> &column = m_columnVisibleItems[i];
118 QList<ViewItem> &column = m_columnVisibleItems[i];
350− while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFrom) {
119+ while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFromY) {
351− releaseItem(column.takeFirst());
120+ releaseItem(column.takeFirst().m_item);
352 changed = true;
121 changed = true;
353 }
122 }
354
123
355− while (!column.isEmpty() && column.last().y() > bufferTo) {
124+ while (!column.isEmpty() && column.last().y() > bufferToY) {
356− releaseItem(column.takeLast());
125+ releaseItem(column.takeLast().m_item);
357 changed = true;
126 changed = true;
358 }
127 }
359 }
128 }
@@ -361,53 +130,20 @@ bool VerticalJournal::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
361 return changed;
130 return changed;
362}
131}
363
132
364−QQuickItem *VerticalJournal::createItem(int modelIndex, bool asynchronous)
133+void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
365{
134{
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) {
135+ if (item->width() != m_columnWidth) {
394− qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
136+ qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
395− item->setWidth(m_columnWidth);
137+ item->setWidth(m_columnWidth);
396− }
397− positionItem(modelIndex, item);
398− return item;
399 }
138 }
400−}
401
139
402−void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
403−{
404 // Check if we add it to the bottom of existing column items
140 // Check if we add it to the bottom of existing column items
405 const QList<ViewItem> &firstColumn = m_columnVisibleItems[0];
141 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();
407 int columnToAddTo = 0;
143 int columnToAddTo = 0;
408 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
144 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
409 const QList<ViewItem> &column = m_columnVisibleItems[i];
145 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();
411 if (iY < columnToAddY) {
147 if (iY < columnToAddY) {
412 columnToAddTo = i;
148 columnToAddTo = i;
413 columnToAddY = iY;
149 columnToAddY = iY;
@@ -416,8 +152,8 @@ void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
416
152
417 const QList<ViewItem> &columnToAdd = m_columnVisibleItems[columnToAddTo];
153 const QList<ViewItem> &columnToAdd = m_columnVisibleItems[columnToAddTo];
418 if (columnToAdd.isEmpty() || columnToAdd.last().m_modelIndex < modelIndex) {
154 if (columnToAdd.isEmpty() || columnToAdd.last().m_modelIndex < modelIndex) {
419− item->setX(columnToAddTo * (m_columnWidth + m_columnSpacing));
155+ item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
420− item->setY(columnToAddY + m_rowSpacing);
156+ item->setY(columnToAddY + rowSpacing());
421
157
422 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
158 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
423 m_indexColumnMap[modelIndex] = columnToAddTo;
159 m_indexColumnMap[modelIndex] = columnToAddTo;
@@ -426,8 +162,8 @@ void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
426 columnToAddTo = m_indexColumnMap[modelIndex];
162 columnToAddTo = m_indexColumnMap[modelIndex];
427 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
163 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
428
164
429− item->setX(columnToAddTo * (m_columnWidth + m_columnSpacing));
165+ item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
430− item->setY(columnToAddY - m_rowSpacing - item->height());
166+ item->setY(columnToAddY - rowSpacing() - item->height());
431
167
432 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
168 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
433 }
169 }
@@ -439,29 +175,14 @@ void VerticalJournal::cleanupExistingItems()
439 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
175 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
440 QList<ViewItem> &column = m_columnVisibleItems[i];
176 QList<ViewItem> &column = m_columnVisibleItems[i];
441 Q_FOREACH(const ViewItem &item, column)
177 Q_FOREACH(const ViewItem &item, column)
442− releaseItem(item);
178+ releaseItem(item.m_item);
443 column.clear();
179 column.clear();
444 }
180 }
445 m_indexColumnMap.clear();
181 m_indexColumnMap.clear();
446}
182}
447
183
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−
461void VerticalJournal::calculateImplicitHeight()
184void VerticalJournal::calculateImplicitHeight()
462{
185{
463− m_implicitHeightDirty = false;
464−
465 int lastModelIndex = -1;
186 int lastModelIndex = -1;
466 qreal bottomMostY = 0;
187 qreal bottomMostY = 0;
467 Q_FOREACH(const auto &column, m_columnVisibleItems) {
188 Q_FOREACH(const auto &column, m_columnVisibleItems) {
@@ -479,131 +200,81 @@ void VerticalJournal::calculateImplicitHeight()
479 }
200 }
480}
201}
481
202
482−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
483−void VerticalJournal::itemCreated(int modelIndex, QQuickItem *item)
203+void VerticalJournal::doRelayout()
484−{
485−#else
486−void VerticalJournal::itemCreated(int modelIndex, QObject *object)
487{
204{
488− QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
205+ QList<ViewItem> allItems;
489− if (!item) {
206+ Q_FOREACH(const auto &column, m_columnVisibleItems)
490− qWarning() << "VerticalJournal::itemCreated got a non item for index" << modelIndex;
207+ allItems << column;
491− return;
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+ }
492 }
228 }
493−#endif
229+}
494− item->setParentItem(this);
230+
495− if (modelIndex == m_asyncRequestedIndex) {
231+void VerticalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
496− createItem(modelIndex, false);
232+{
497− m_implicitHeightDirty = true;
233+ Q_FOREACH(const auto &column, m_columnVisibleItems) {
498− polish();
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+ }
499 }
238 }
500}
239}
501
240
502#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
241#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)
504#else
243#else
505−void VerticalJournal::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
244+void VerticalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
506#endif
245#endif
507{
246{
508− if (reset) {
509− cleanupExistingItems();
510− } else {
511#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
247#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) {
513#else
249#else
514− Q_FOREACH(const QQmlChangeSet::Remove &remove, changeSet.removes()) {
250+ Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
515#endif
251#endif
516− for (int i = remove.count - 1; i >= 0; --i) {
252+ for (int i = remove.count - 1; i >= 0; --i) {
517− const int indexToRemove = remove.index + i;
253+ const int indexToRemove = remove.index + i;
518− // Since we only support removing from the end, indexToRemove
254+ // Since we only support removing from the end, indexToRemove
519− // must refer to the last item of one of the columns.
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)
520− bool found = false;
258+ bool found = false;
259+ int lastCreatedIndex = INT_MIN;
521− for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
260+ for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
522− QList<ViewItem> &column = m_columnVisibleItems[i];
261+ QList<ViewItem> &column = m_columnVisibleItems[i];
523− if (!column.isEmpty() && column.last().m_modelIndex == indexToRemove) {
262+ if (!column.isEmpty()) {
263+ const int lastColumnIndex = column.last().m_modelIndex;
264+ if (lastColumnIndex == indexToRemove) {
524− releaseItem(column.takeLast());
265+ releaseItem(column.takeLast().m_item);
525 found = true;
266 found = true;
526 }
267 }
268+ lastCreatedIndex = qMax(lastCreatedIndex, lastColumnIndex);
527 }
269 }
528− Q_ASSERT(found);
529 }
270 }
530− }
271+ if (!found) {
531− }
272+ if (indexToRemove < lastCreatedIndex) {
532− polish();
273+ qFatal("VerticalJournal only supports removal from the end of the model");
533−}
534−
535−
536−void VerticalJournal::relayout()
537−{
538− m_needsRelayout = true;
539− polish();
540−}
541−
542−void VerticalJournal::onHeightChanged()
543−{
544− polish();
545−}
546−
547−void VerticalJournal::updatePolish()
548−{
549− if (!model())
550− return;
551−
552− if (m_needsRelayout) {
553− m_implicitHeightDirty = true;
554−
555− QList<ViewItem> allItems;
556− Q_FOREACH(const auto &column, m_columnVisibleItems)
557− allItems << column;
558−
559− qSort(allItems);
560−
561− const int nColumns = qMax(1., floor((double)(width() + m_columnSpacing) / (m_columnWidth + m_columnSpacing)));
562− m_columnVisibleItems.resize(nColumns);
563− m_indexColumnMap.clear();
564− for (int i = 0; i < nColumns; ++i)
565− m_columnVisibleItems[i].clear();
566−
567− // If the first of allItems doesn't contain index 0 we need to drop them
568− // all since we can't consistently relayout without the first item being there
569−
570− if (!allItems.isEmpty()) {
571− if (allItems.first().m_modelIndex == 0) {
572− Q_FOREACH(const ViewItem &item, allItems)
573− positionItem(item.m_modelIndex, item.m_item);
574− } else {
274+ } else {
575− Q_FOREACH(const ViewItem &item, allItems)
275+ setImplicitHeightDirty();
576− releaseItem(item);
276+ }
577 }
277 }
578 }
278 }
579−
580− m_needsRelayout = false;
581− }
582−
583− refill();
584−
585− const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
586− const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
587− const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
588− Q_FOREACH(const auto &column, m_columnVisibleItems) {
589− Q_FOREACH(const ViewItem &item, column) {
590− QQuickItemPrivate::get(item.m_item)->setCulled(item.y() + item.height() <= from || item.y() >= to);
591− }
592 }
279 }
593−
594− if (m_implicitHeightDirty) {
595− calculateImplicitHeight();
596− }
597−}
598−
599−void VerticalJournal::componentComplete()
600−{
601− if (m_delegateModel)
602− m_delegateModel->componentComplete();
603−
604− QQuickItem::componentComplete();
605−
606− m_needsRelayout = true;
607−
608− polish();
609}
280}
plugins/DashViews/verticaljournal.h+13−91
@@ -17,17 +17,7 @@
17#ifndef VERTICALJOURNAL_H
17#ifndef VERTICALJOURNAL_H
18#define VERTICALJOURNAL_H
18#define VERTICALJOURNAL_H
19
19
20−#include <QQuickItem>
20+#include "abstractjournal.h"
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
31
21
32 /** A vertical journal is a view that creates delegates
22 /** A vertical journal is a view that creates delegates
33 * based on a model and layouts them in columns following
23 * based on a model and layouts them in columns following
@@ -60,75 +50,23 @@ class QQmlDelegateModel;
60 * +-----+
50 * +-----+
61 *
51 *
62 */
52 */
53+
63− class VerticalJournal : public QQuickItem
54+class VerticalJournal : public AbstractJournal
64{
55{
65 Q_OBJECT
56 Q_OBJECT
66
57
67− Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
68− Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
69 Q_PROPERTY(qreal columnWidth READ columnWidth WRITE setColumnWidth NOTIFY columnWidthChanged)
58 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)
80
59
81friend class VerticalJournalTest;
60friend class VerticalJournalTest;
82
61
83public:
62public:
84 VerticalJournal();
63 VerticalJournal();
85
64
86− QAbstractItemModel *model() const;
87− void setModel(QAbstractItemModel *model);
88−
89− QQmlComponent *delegate() const;
90− void setDelegate(QQmlComponent *delegate);
91−
92 qreal columnWidth() const;
65 qreal columnWidth() const;
93 void setColumnWidth(qreal columnWidth);
66 void setColumnWidth(qreal columnWidth);
94
67
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−
109Q_SIGNALS:
68Q_SIGNALS:
110− void modelChanged();
111− void delegateChanged();
112 void columnWidthChanged();
69 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();
132
70
133private:
71private:
134 class ViewItem
72 class ViewItem
@@ -144,39 +82,23 @@ private:
144 int m_modelIndex;
82 int m_modelIndex;
145 };
83 };
146
84
147− void createDelegateModel();
148− void refill();
149− void findBottomModelIndexToAdd(int *modelIndex, qreal *yPos);
85+ void findBottomModelIndexToAdd(int *modelIndex, qreal *yPos) override;
150− void findTopModelIndexToAdd(int *modelIndex, qreal *yPos);
86+ void findTopModelIndexToAdd(int *modelIndex, qreal *yPos) override;
151− bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
152− bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
87+ bool removeNonVisibleItems(qreal bufferFromY, qreal bufferToY) override;
153− QQuickItem *createItem(int modelIndex, bool asynchronous);
154− void positionItem(int modelIndex, QQuickItem *item);
88+ void positionItem(int modelIndex, QQuickItem *item) override;
155− void cleanupExistingItems();
89+ void cleanupExistingItems() override;
156− void releaseItem(const ViewItem &item);
157− void calculateImplicitHeight();
90+ void calculateImplicitHeight() override;
158−
91+ void doRelayout() override;
92+ void updateItemCulling(qreal visibleFromY, qreal visibleToY) override;
159#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
93#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
160− QQuickVisualDataModel *m_delegateModel;
94+ void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) override;
161#else
95#else
162− QQmlDelegateModel *m_delegateModel;
96+ void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) override;
163#endif
97#endif
164
98
165− // Index we are waiting because we requested it asynchronously
166− int m_asyncRequestedIndex;
167−
168 QVector<QList<ViewItem>> m_columnVisibleItems;
99 QVector<QList<ViewItem>> m_columnVisibleItems;
169 QHash<int, int> m_indexColumnMap;
100 QHash<int, int> m_indexColumnMap;
170 int m_columnWidth;
101 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;
180};
102};
181
103
182#endif
104#endif
plugins/Unity/Indicators/Messaging/qml/MessageMenuItemFactory.qml+8
@@ -60,6 +60,7 @@ Indicators.BaseMenuItem {
60
60
61 Loader {
61 Loader {
62 id: contents
62 id: contents
63+ objectName: "loader"
63 anchors.fill: parent
64 anchors.fill: parent
64
65
65 sourceComponent: loadMessage(actionsDescription);
66 sourceComponent: loadMessage(actionsDescription);
@@ -67,6 +68,7 @@ Indicators.BaseMenuItem {
67 Component {
68 Component {
68 id: simpleTextMessage
69 id: simpleTextMessage
69 SimpleTextMessage {
70 SimpleTextMessage {
71+ objectName: "simpleTextMessage"
70 // text
72 // text
71 title: menuData && menuData.label || ""
73 title: menuData && menuData.label || ""
72 time: getExtendedProperty(extendedData, "xCanonicalTime", 0)
74 time: getExtendedProperty(extendedData, "xCanonicalTime", 0)
@@ -74,6 +76,8 @@ Indicators.BaseMenuItem {
74 // icons
76 // icons
75 avatar: getExtendedProperty(extendedData, "icon", "qrc:/indicators/artwork/messaging/default_contact.png")
77 avatar: getExtendedProperty(extendedData, "icon", "qrc:/indicators/artwork/messaging/default_contact.png")
76 appIcon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "qrc:/indicators/artwork/messaging/default_app.svg")
78 appIcon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "qrc:/indicators/artwork/messaging/default_app.svg")
79+ // actions
80+ enabled: menuData && menuData.sensitive || false
77
81
78 onActivateApp: {
82 onActivateApp: {
79 menuModel.activate(menuIndex, true);
83 menuModel.activate(menuIndex, true);
@@ -91,6 +95,7 @@ Indicators.BaseMenuItem {
91 Component {
95 Component {
92 id: textMessage
96 id: textMessage
93 TextMessage {
97 TextMessage {
98+ objectName: "textMessage"
94 property var replyActionDescription: actionsDescription && actionsDescription.length > 0 ? actionsDescription[0] : undefined
99 property var replyActionDescription: actionsDescription && actionsDescription.length > 0 ? actionsDescription[0] : undefined
95
100
96 property var replyAction: QMenuModel.UnityMenuAction {
101 property var replyAction: QMenuModel.UnityMenuAction {
@@ -109,6 +114,7 @@ Indicators.BaseMenuItem {
109 appIcon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "qrc:/indicators/artwork/messaging/default_app.svg")
114 appIcon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "qrc:/indicators/artwork/messaging/default_app.svg")
110 // actions
115 // actions
111 replyEnabled: replyAction.valid && replyAction.enabled
116 replyEnabled: replyAction.valid && replyAction.enabled
117+ enabled: menuData && menuData.sensitive || false
112
118
113 onActivateApp: {
119 onActivateApp: {
114 menuModel.activate(menuIndex, true);
120 menuModel.activate(menuIndex, true);
@@ -129,6 +135,7 @@ Indicators.BaseMenuItem {
129 Component {
135 Component {
130 id: snapDecision
136 id: snapDecision
131 SnapDecision {
137 SnapDecision {
138+ objectName: "snapDecision"
132 property var activateActionDescription: actionsDescription && actionsDescription.length > 0 ? actionsDescription[0] : undefined
139 property var activateActionDescription: actionsDescription && actionsDescription.length > 0 ? actionsDescription[0] : undefined
133 property var replyActionDescription: actionsDescription && actionsDescription.length > 1 ? actionsDescription[1] : undefined
140 property var replyActionDescription: actionsDescription && actionsDescription.length > 1 ? actionsDescription[1] : undefined
134
141
@@ -156,6 +163,7 @@ Indicators.BaseMenuItem {
156 // actions
163 // actions
157 activateEnabled: activateAction.valid && activateAction.enabled
164 activateEnabled: activateAction.valid && activateAction.enabled
158 replyEnabled: replyAction.valid && replyAction.enabled
165 replyEnabled: replyAction.valid && replyAction.enabled
166+ enabled: menuData && menuData.sensitive || false
159
167
160 onActivateApp: {
168 onActivateApp: {
161 menuModel.activate(menuIndex, true);
169 menuModel.activate(menuIndex, true);
plugins/Unity/Indicators/Network/qml/AccessPoint.qml+2−2
@@ -30,7 +30,7 @@ Indicators.BaseMenuItem {
30 property bool secure: false
30 property bool secure: false
31 property bool adHoc: false
31 property bool adHoc: false
32 property int signalStrength: 0
32 property int signalStrength: 0
33− property alias text: label.text
33+ property alias label: _label.text
34
34
35 signal activate()
35 signal activate()
36
36
@@ -89,7 +89,7 @@ Indicators.BaseMenuItem {
89 }
89 }
90
90
91 Label {
91 Label {
92− id: label
92+ id: _label
93 anchors {
93 anchors {
94 left: iconSignal.right
94 left: iconSignal.right
95 leftMargin: units.gu(1)
95 leftMargin: units.gu(1)
plugins/Unity/Indicators/qml/ButtonMenuItem.qml+1−1
@@ -26,7 +26,7 @@ BaseMenuItem {
26 id: menuItem
26 id: menuItem
27 implicitHeight: units.gu(7)
27 implicitHeight: units.gu(7)
28
28
29− property alias text: button.text
29+ property alias buttonText: button.text
30
30
31 signal activate()
31 signal activate()
32
32
plugins/Unity/Indicators/qml/IndicatorPage.qmldeleted−219
@@ -1,219 +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 Lesser 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 Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Renato Araujo Oliveira Filho <renato@canonical.com>
18− * Nick Dedekind <nick.dedekind@canonical.com>
19− */
20−
21−import QtQuick 2.0
22−import Ubuntu.Components 0.1 as Components
23−import Unity.Indicators 0.1 as Indicators
24−
25−IndicatorBase {
26− id: main
27−
28− //const
29− property bool contentActive: false
30− property string title: rootActionState.title
31− property alias emptyText: emptyLabel.text
32− property alias highlightFollowsCurrentItem : mainMenu.highlightFollowsCurrentItem
33−
34− Indicators.UnityMenuModelStack {
35− id: menuStack
36− head: contentActive ? main.menuModel : null
37−
38− property var rootMenu: null
39−
40− onTailChanged: {
41− if (!tail) {
42− rootMenu = null;
43− } else if (rootMenu != tail) {
44− if (tail.get(0, "type") === rootMenuType) {
45− rootMenu = menuStack.tail.submenu(0);
46− push(rootMenu, 0);
47− } else {
48− rootMenu = null;
49− }
50− }
51− }
52− }
53−
54− Connections {
55− target: menuStack.tail
56− onRowsInserted: {
57− if (menuStack.rootMenu !== menuStack.tail && menuStack.tail.get(0, "type") === rootMenuType) {
58− menuStack.rootMenu = menuStack.tail.submenu(0);
59− menuStack.push(menuStack.rootMenu, 0);
60− }
61− }
62− }
63−
64− ListView {
65− id: mainMenu
66− model: menuStack.rootMenu
67−
68− anchors {
69− fill: parent
70− bottomMargin: Qt.inputMethod.visible ? (Qt.inputMethod.keyboardRectangle.height - main.anchors.bottomMargin) : 0
71−
72− Behavior on bottomMargin {
73− NumberAnimation {
74− duration: 175
75− easing.type: Easing.OutQuad
76− }
77− }
78− // TODO - does ever frame.
79− onBottomMarginChanged: {
80− mainMenu.positionViewAtIndex(mainMenu.currentIndex, ListView.End)
81− }
82− }
83−
84− // Ensure all delegates are cached in order to improve smoothness of scrolling
85− cacheBuffer: 10000
86−
87− // Only allow flicking if the content doesn't fit on the page
88− interactive: contentHeight > height
89−
90− property int selectedIndex: -1
91− property bool blockCurrentIndexChange: false
92− // for count = 0
93− onCountChanged: {
94− if (count == 0 && selectedIndex != -1) {
95− selectedIndex = -1;
96− }
97− }
98− // for highlight following
99− onSelectedIndexChanged: {
100− if (currentIndex != selectedIndex) {
101− var blocked = blockCurrentIndexChange;
102− blockCurrentIndexChange = true;
103−
104− currentIndex = selectedIndex;
105−
106− blockCurrentIndexChange = blocked;
107− }
108− }
109− // for item addition/removal
110− onCurrentIndexChanged: {
111− if (!blockCurrentIndexChange) {
112− if (selectedIndex != -1 && selectedIndex != currentIndex) {
113− selectedIndex = currentIndex;
114− }
115− }
116− }
117−
118− delegate: Item {
119− id: menuDelegate
120−
121− anchors {
122− left: parent.left
123− right: parent.right
124− }
125− height: loader.height
126− visible: height > 0
127−
128− Loader {
129− id: loader
130− asynchronous: false
131−
132− property int modelIndex: index
133−
134− anchors {
135− left: parent.left
136− right: parent.right
137− }
138−
139− sourceComponent: factory.load(model)
140−
141− onLoaded: {
142− if (item.hasOwnProperty("menuSelected")) {
143− item.menuSelected = mainMenu.selectedIndex == index;
144− item.selectMenu.connect(function() { mainMenu.selectedIndex = index; });
145− item.deselectMenu.connect(function() { mainMenu.selectedIndex = -1; });
146− }
147− if (item.hasOwnProperty("menuData")) {
148− item.menuData = Qt.binding(function() { return model; });
149− }
150− if (item.hasOwnProperty("menuIndex")) {
151− item.menuIndex = Qt.binding(function() { return modelIndex; });
152− }
153− }
154−
155− Binding {
156− target: item ? item : null
157− property: "objectName"
158− value: model.action
159− }
160−
161− // TODO: Fixes lp#1243146
162− // This is a workaround for a Qt bug. https://bugreports.qt-project.org/browse/QTBUG-34351
163− Connections {
164− target: mainMenu
165− onSelectedIndexChanged: {
166− if (loader.item && loader.item.hasOwnProperty("menuSelected")) {
167− loader.item.menuSelected = mainMenu.selectedIndex == index;
168− }
169− }
170− }
171− }
172− }
173− }
174−
175− MenuItemFactory {
176− id: factory
177− menuModel: mainMenu.model ? mainMenu.model : null
178− }
179−
180− Components.Label {
181− id: emptyLabel
182− visible: mainMenu.count == 0
183− anchors {
184− top: parent.top
185− left: parent.left
186− right: parent.right
187− topMargin: units.gu(2)
188− }
189− wrapMode: Text.WordWrap
190− horizontalAlignment: Text.AlignHCenter
191−
192− //style
193− color: "#e8e1d0"
194− fontSize: "medium"
195−
196− text: "Empty!"
197− }
198−
199− function start()
200− {
201− reset()
202− if (!contentActive) {
203− contentActive = true;
204− }
205− }
206−
207− function stop()
208− {
209− if (contentActive) {
210− contentActive = false;
211− }
212− }
213−
214− function reset()
215− {
216− mainMenu.selectedIndex = -1;
217− mainMenu.positionViewAtBeginning();
218− }
219−}
plugins/Unity/Indicators/qml/MenuItemFactory.qml+18−3
@@ -57,11 +57,17 @@ Item {
57 return defaultValue;
57 return defaultValue;
58 }
58 }
59
59
60+ Component {
61+ id: divMenu;
60− Component { id: divMenu; Indicators.DivMenuItem {} }
62+ Indicators.DivMenuItem {
63+ objectName: "divMenu"
64+ }
65+ }
61
66
62 Component {
67 Component {
63 id: sliderMenu;
68 id: sliderMenu;
64 Indicators.SliderMenuItem {
69 Indicators.SliderMenuItem {
70+ objectName: "sliderMenu"
65 property QtObject menuData: null
71 property QtObject menuData: null
66 property var menuModel: menuFactory.menuModel
72 property var menuModel: menuFactory.menuModel
67 property int menuIndex: -1
73 property int menuIndex: -1
@@ -106,11 +112,12 @@ Item {
106 Component {
112 Component {
107 id: buttonMenu;
113 id: buttonMenu;
108 Indicators.ButtonMenuItem {
114 Indicators.ButtonMenuItem {
115+ objectName: "buttonMenu"
109 property QtObject menuData: null
116 property QtObject menuData: null
110 property var menuModel: menuFactory.menuModel
117 property var menuModel: menuFactory.menuModel
111 property int menuIndex: -1
118 property int menuIndex: -1
112
119
113− text: menuData && menuData.label || ""
120+ buttonText: menuData && menuData.label || ""
114 enabled: menuData && menuData.sensitive || false
121 enabled: menuData && menuData.sensitive || false
115
122
116 onActivate: {
123 onActivate: {
@@ -122,6 +129,7 @@ Item {
122 Component {
129 Component {
123 id: sectionMenu;
130 id: sectionMenu;
124 Indicators.SectionMenuItem {
131 Indicators.SectionMenuItem {
132+ objectName: "sectionMenu"
125 property QtObject menuData: null
133 property QtObject menuData: null
126 property var menuIndex: undefined
134 property var menuIndex: undefined
127
135
@@ -132,6 +140,7 @@ Item {
132 Component {
140 Component {
133 id: progressMenu;
141 id: progressMenu;
134 Indicators.ProgressMenuItem {
142 Indicators.ProgressMenuItem {
143+ objectName: "progressMenu"
135 property QtObject menuData: null
144 property QtObject menuData: null
136 property int menuIndex: -1
145 property int menuIndex: -1
137
146
@@ -145,6 +154,7 @@ Item {
145 Component {
154 Component {
146 id: standardMenu;
155 id: standardMenu;
147 Indicators.StandardMenuItem {
156 Indicators.StandardMenuItem {
157+ objectName: "standardMenu"
148 property QtObject menuData: null
158 property QtObject menuData: null
149 property int menuIndex: -1
159 property int menuIndex: -1
150
160
@@ -164,6 +174,7 @@ Item {
164 Component {
174 Component {
165 id: switchMenu;
175 id: switchMenu;
166 Indicators.SwitchMenuItem {
176 Indicators.SwitchMenuItem {
177+ objectName: "switchMenu"
167 property QtObject menuData: null
178 property QtObject menuData: null
168 property int menuIndex: -1
179 property int menuIndex: -1
169
180
@@ -182,6 +193,7 @@ Item {
182 Component {
193 Component {
183 id: wifiSection;
194 id: wifiSection;
184 Indicators.SectionMenuItem {
195 Indicators.SectionMenuItem {
196+ objectName: "wifiSection"
185 property QtObject menuData: null
197 property QtObject menuData: null
186 property var menuModel: menuFactory.menuModel
198 property var menuModel: menuFactory.menuModel
187 property int menuIndex: -1
199 property int menuIndex: -1
@@ -207,6 +219,7 @@ Item {
207 Component {
219 Component {
208 id: accessPoint;
220 id: accessPoint;
209 ICNetwork.AccessPoint {
221 ICNetwork.AccessPoint {
222+ objectName: "accessPoint"
210 property QtObject menuData: null
223 property QtObject menuData: null
211 property var menuModel: menuFactory.menuModel
224 property var menuModel: menuFactory.menuModel
212 property int menuIndex: -1
225 property int menuIndex: -1
@@ -218,7 +231,7 @@ Item {
218 name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
231 name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
219 }
232 }
220
233
221− text: menuData && menuData.label || ""
234+ label: menuData && menuData.label || ""
222 enabled: menuData && menuData.sensitive || false
235 enabled: menuData && menuData.sensitive || false
223 secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
236 secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
224 adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
237 adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
@@ -248,6 +261,7 @@ Item {
248 Component {
261 Component {
249 id: messageItem
262 id: messageItem
250 ICMessaging.MessageMenuItemFactory {
263 ICMessaging.MessageMenuItemFactory {
264+ objectName: "messageItem"
251 menuModel: menuFactory.menuModel
265 menuModel: menuFactory.menuModel
252 }
266 }
253 }
267 }
@@ -255,6 +269,7 @@ Item {
255 Component {
269 Component {
256 id: groupedMessage
270 id: groupedMessage
257 ICMessaging.GroupedMessage {
271 ICMessaging.GroupedMessage {
272+ objectName: "groupedMessage"
258 property QtObject menuData: null
273 property QtObject menuData: null
259 property var menuModel: menuFactory.menuModel
274 property var menuModel: menuFactory.menuModel
260 property int menuIndex: -1
275 property int menuIndex: -1
plugins/Unity/Indicators/qml/qmldir−2
@@ -9,8 +9,6 @@ DivMenuItem 0.1 DivMenuItem.qml
9FramedMenuItem 0.1 FramedMenuItem.qml
9FramedMenuItem 0.1 FramedMenuItem.qml
10HLine 0.1 HLine.qml
10HLine 0.1 HLine.qml
11IndicatorBase 0.1 IndicatorBase.qml
11IndicatorBase 0.1 IndicatorBase.qml
12−IndicatorPage 0.1 IndicatorPage.qml
13−IndicatorWidget 0.1 IndicatorWidget.qml
14MenuActionBinding 0.1 MenuActionBinding.qml
12MenuActionBinding 0.1 MenuActionBinding.qml
15MenuItemFactory 0.1 MenuItemFactory.qml
13MenuItemFactory 0.1 MenuItemFactory.qml
16ProgressMenuItem 0.1 ProgressMenuItem.qml
14ProgressMenuItem 0.1 ProgressMenuItem.qml
plugins/Utils/qsortfilterproxymodelqml.cpp+12
@@ -68,6 +68,18 @@ QSortFilterProxyModelQML::setModel(QAbstractItemModel *itemModel)
68 }
68 }
69}
69}
70
70
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+
71QVariant
83QVariant
72QSortFilterProxyModelQML::data(int row, int role)
84QSortFilterProxyModelQML::data(int row, int role)
73{
85{
plugins/Utils/qsortfilterproxymodelqml.h+1
@@ -31,6 +31,7 @@ class QSortFilterProxyModelQML : public QSortFilterProxyModel
31public:
31public:
32 explicit QSortFilterProxyModelQML(QObject *parent = 0);
32 explicit QSortFilterProxyModelQML(QObject *parent = 0);
33
33
34+ Q_INVOKABLE QVariantMap get(int row); // Use with caution, it can be slow to query all the roles
34 Q_INVOKABLE QVariant data(int row, int role);
35 Q_INVOKABLE QVariant data(int row, int role);
35 Q_INVOKABLE int count();
36 Q_INVOKABLE int count();
36 Q_INVOKABLE int findFirst(int role, const QVariant& value) const;
37 Q_INVOKABLE int findFirst(int role, const QVariant& value) const;
po/ar.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n % 100 >= "
17"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n % 100 >= "
18"3 && n % 100 <= 10 ? 3 : n % 100 >= 11 && n % 100 <= 99 ? 4 : 5;\n"
18"3 && n % 100 <= 10 ? 3 : n % 100 >= 11 && n % 100 <= 99 ? 4 : 5;\n"
19−"X-Launchpad-Export-Date: 2013-12-06 06:30+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16863)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/ast.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-12 06:02+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16869)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/az.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/bg.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"
17"Content-Transfer-Encoding: 8bit\n"
18"Plural-Forms: nplurals=2; plural=n != 1;\n"
18"Plural-Forms: nplurals=2; plural=n != 1;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/ca.po+7−7
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-09-28 07:38+0000\n"
11+"PO-Revision-Date: 2014-01-10 07:45+0000\n"
12−"Last-Translator: David Planella <david.planella@ubuntu.com>\n"
12+"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
13"Language-Team: Catalan <ca@li.org>\n"
13"Language-Team: Catalan <ca@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2014-01-11 06:38+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16890)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
@@ -143,7 +143,7 @@ msgstr "S'està parlant..."
143
143
144#: Notifications/NotificationMenuItemFactory.qml:89
144#: Notifications/NotificationMenuItemFactory.qml:89
145msgid "Show password"
145msgid "Show password"
146−msgstr ""
146+msgstr "Mostra la contrasenya"
147
147
148#: Panel/SearchIndicator.qml:27
148#: Panel/SearchIndicator.qml:27
149msgid "Search"
149msgid "Search"
@@ -152,11 +152,11 @@ msgstr "Cerca"
152#: plugins/Unity/Launcher/launcheritem.cpp:43
152#: plugins/Unity/Launcher/launcheritem.cpp:43
153#: plugins/Unity/Launcher/launcheritem.cpp:73
153#: plugins/Unity/Launcher/launcheritem.cpp:73
154msgid "Lock to Launcher"
154msgid "Lock to Launcher"
155−msgstr ""
155+msgstr "Mantén-la al llançador"
156
156
157#: plugins/Unity/Launcher/launcheritem.cpp:73
157#: plugins/Unity/Launcher/launcheritem.cpp:73
158msgid "Unlock from Launcher"
158msgid "Unlock from Launcher"
159−msgstr ""
159+msgstr "No la mantinguis al llançador"
160
160
161#: Shell.qml:439
161#: Shell.qml:439
162#, qt-format
162#, qt-format
po/cs.po+9−7
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"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"
12"Last-Translator: Tadeáš Pařík <tadeas.parik@gmail.com>\n"
12"Last-Translator: Tadeáš Pařík <tadeas.parik@gmail.com>\n"
13"Language-Team: Czech <cs@li.org>\n"
13"Language-Team: Czech <cs@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
17"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2014-01-09 05:41+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16884)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
@@ -44,7 +44,7 @@ msgstr "Uzavřít"
44
44
45#: Components/EdgeDemo.qml:171
45#: Components/EdgeDemo.qml:171
46msgid "Swipe up again to close the settings screen"
46msgid "Swipe up again to close the settings screen"
47−msgstr ""
47+msgstr "Přetažením nahoru dojde k uzavření obrazovky nastavení"
48
48
49#: Components/EdgeDemo.qml:198
49#: Components/EdgeDemo.qml:198
50msgid "Left edge"
50msgid "Left edge"
@@ -63,6 +63,8 @@ msgid ""
63"You have now mastered the edge gestures and can start using the "
63"You have now mastered the edge gestures and can start using the "
64"phone<br><br>Tap anywhere to start"
64"phone<br><br>Tap anywhere to start"
65msgstr ""
65msgstr ""
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"
66
68
67#: Components/PageHeader.qml:364
69#: Components/PageHeader.qml:364
68msgid "Recent searches"
70msgid "Recent searches"
@@ -138,7 +140,7 @@ msgstr "Mluvte..."
138
140
139#: Notifications/NotificationMenuItemFactory.qml:89
141#: Notifications/NotificationMenuItemFactory.qml:89
140msgid "Show password"
142msgid "Show password"
141−msgstr ""
143+msgstr "Zobrazit heslo"
142
144
143#: Panel/SearchIndicator.qml:27
145#: Panel/SearchIndicator.qml:27
144msgid "Search"
146msgid "Search"
@@ -147,11 +149,11 @@ msgstr "Hledat"
147#: plugins/Unity/Launcher/launcheritem.cpp:43
149#: plugins/Unity/Launcher/launcheritem.cpp:43
148#: plugins/Unity/Launcher/launcheritem.cpp:73
150#: plugins/Unity/Launcher/launcheritem.cpp:73
149msgid "Lock to Launcher"
151msgid "Lock to Launcher"
150−msgstr ""
152+msgstr "Přidat do Launcheru"
151
153
152#: plugins/Unity/Launcher/launcheritem.cpp:73
154#: plugins/Unity/Launcher/launcheritem.cpp:73
153msgid "Unlock from Launcher"
155msgid "Unlock from Launcher"
154−msgstr ""
156+msgstr "Odebrat z Launcheru"
155
157
156#: Shell.qml:439
158#: Shell.qml:439
157#, qt-format
159#, qt-format
po/de.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/el.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/en_AU.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/en_GB.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/eo.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/es.po+3−3
@@ -9,14 +9,14 @@ msgstr ""
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"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: 2013-11-20 08:46+0000\n"
12−"Last-Translator: Adolfo Jayme Barrientos <fitoschido@gmail.com>\n"
12+"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
13"Language-Team: Spanish <es@li.org>\n"
13"Language-Team: Spanish <es@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/fa.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/fi.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/fo.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/fr.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n > 1;\n"
17"Plural-Forms: nplurals=2; plural=n > 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/gd.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : "
17"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : "
18"(n > 2 && n < 20) ? 2 : 3;\n"
18"(n > 2 && n < 20) ? 2 : 3;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/gl.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-07 06:35+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16869)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/he.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/hr.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/hu.po+32−28
@@ -8,61 +8,65 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-06-27 04:28+0000\n"
11+"PO-Revision-Date: 2014-01-07 18:02+0000\n"
12−"Last-Translator: Michał Sawicz <michal.sawicz@canonical.com>\n"
12+"Last-Translator: Gabor Kelemen <kelemeng@gnome.hu>\n"
13"Language-Team: Hungarian <hu@li.org>\n"
13"Language-Team: Hungarian <hu@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2014-01-08 05:43+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
23−msgstr ""
23+msgstr "A bemutató kihagyása"
24
24
25#: Components/EdgeDemo.qml:112
25#: Components/EdgeDemo.qml:112
26msgid "Right edge"
26msgid "Right edge"
27−msgstr ""
27+msgstr "Jobb szél"
28
28
29#: Components/EdgeDemo.qml:113
29#: Components/EdgeDemo.qml:113
30msgid "Try swiping from the right edge to unlock the phone"
30msgid "Try swiping from the right edge to unlock the phone"
31−msgstr ""
31+msgstr "A telefon feloldásához csúsztassa ujját a jobb szélről"
32
32
33#: Components/EdgeDemo.qml:144
33#: Components/EdgeDemo.qml:144
34msgid "Top edge"
34msgid "Top edge"
35−msgstr ""
35+msgstr "Felső szél"
36
36
37#: Components/EdgeDemo.qml:145
37#: Components/EdgeDemo.qml:145
38msgid "Try swiping from the top edge to access the indicators"
38msgid "Try swiping from the top edge to access the indicators"
39−msgstr ""
39+msgstr "A jelzők eléréséhez csúsztassa ujját a felső szélről"
40
40
41#: Components/EdgeDemo.qml:170
41#: Components/EdgeDemo.qml:170
42msgid "Close"
42msgid "Close"
43−msgstr ""
43+msgstr "Bezárás"
44
44
45#: Components/EdgeDemo.qml:171
45#: Components/EdgeDemo.qml:171
46msgid "Swipe up again to close the settings screen"
46msgid "Swipe up again to close the settings screen"
47−msgstr ""
47+msgstr "A beállítások bezárásához csúsztassa ujját újra felfelé"
48
48
49#: Components/EdgeDemo.qml:198
49#: Components/EdgeDemo.qml:198
50msgid "Left edge"
50msgid "Left edge"
51−msgstr ""
51+msgstr "Bal szél"
52
52
53#: Components/EdgeDemo.qml:199
53#: Components/EdgeDemo.qml:199
54msgid "Swipe from the left to reveal the launcher for quick access to apps"
54msgid "Swipe from the left to reveal the launcher for quick access to apps"
55msgstr ""
55msgstr ""
56+"Csúsztassa ujját a bal szélről az indítón található alkalmazások gyors "
57+"eléréséhez"
56
58
57#: Components/EdgeDemo.qml:226
59#: Components/EdgeDemo.qml:226
58msgid "Well done"
60msgid "Well done"
59−msgstr ""
61+msgstr "Rendben"
60
62
61#: Components/EdgeDemo.qml:227
63#: Components/EdgeDemo.qml:227
62msgid ""
64msgid ""
63"You have now mastered the edge gestures and can start using the "
65"You have now mastered the edge gestures and can start using the "
64"phone<br><br>Tap anywhere to start"
66"phone<br><br>Tap anywhere to start"
65msgstr ""
67msgstr ""
68+"Miután megismerte a szélek használatát, nincs más hátra...<br><br>Koppintson "
69+"a folytatáshoz!"
66
70
67#: Components/PageHeader.qml:364
71#: Components/PageHeader.qml:364
68msgid "Recent searches"
72msgid "Recent searches"
@@ -70,50 +74,50 @@ msgstr "Legutóbbi keresések"
70
74
71#: Dash/Apps/AppPreview.qml:135
75#: Dash/Apps/AppPreview.qml:135
72msgid "Rate this"
76msgid "Rate this"
73−msgstr ""
77+msgstr "Osztályzás"
74
78
75#. TRANSLATORS: Number of persons who rated this app/video/whatever
79#. TRANSLATORS: Number of persons who rated this app/video/whatever
76#: Dash/Previews/Header.qml:93
80#: Dash/Previews/Header.qml:93
77#, qt-format
81#, qt-format
78msgid "(%1)"
82msgid "(%1)"
79−msgstr ""
83+msgstr "(%1)"
80
84
81#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
85#. TRANSLATORS: Number of persons who wrote reviews for this app/video/whatever
82#: Dash/Previews/Header.qml:105
86#: Dash/Previews/Header.qml:105
83#, qt-format
87#, qt-format
84msgid "%1 review"
88msgid "%1 review"
85msgid_plural "%1 reviews"
89msgid_plural "%1 reviews"
86−msgstr[0] ""
90+msgstr[0] "%1 értékelés"
87−msgstr[1] ""
91+msgstr[1] "%1 értékelés"
88
92
89#: Dash/Previews/Reviews.qml:74
93#: Dash/Previews/Reviews.qml:74
90msgid "Add a review"
94msgid "Add a review"
91−msgstr ""
95+msgstr "Értékelés hozzáadása"
92
96
93#: Dash/Previews/Reviews.qml:86
97#: Dash/Previews/Reviews.qml:86
94msgid "Review"
98msgid "Review"
95−msgstr ""
99+msgstr "Értékelés"
96
100
97#: Dash/Previews/Reviews.qml:121
101#: Dash/Previews/Reviews.qml:121
98msgid "Send"
102msgid "Send"
99−msgstr ""
103+msgstr "Küldés"
100
104
101#: Dash/Previews/Reviews.qml:139
105#: Dash/Previews/Reviews.qml:139
102msgid "Comments:"
106msgid "Comments:"
103−msgstr ""
107+msgstr "Hozzászólások:"
104
108
105#: Greeter/Lockscreen.qml:149
109#: Greeter/Lockscreen.qml:149
106msgid "Emergency Call"
110msgid "Emergency Call"
107−msgstr ""
111+msgstr "Vészhívás"
108
112
109#: Greeter/PassphraseLockscreen.qml:59
113#: Greeter/PassphraseLockscreen.qml:59
110#, qt-format
114#, qt-format
111msgid "Hello %1"
115msgid "Hello %1"
112−msgstr ""
116+msgstr "Szia %1"
113
117
114#: Greeter/PassphraseLockscreen.qml:59
118#: Greeter/PassphraseLockscreen.qml:59
115msgid "Hello"
119msgid "Hello"
116−msgstr ""
120+msgstr "Szia"
117
121
118#: Hud/HudParametrizedActionsPage.qml:132
122#: Hud/HudParametrizedActionsPage.qml:132
119msgid "Confirm"
123msgid "Confirm"
@@ -137,7 +141,7 @@ msgstr "Beszéd…"
137
141
138#: Notifications/NotificationMenuItemFactory.qml:89
142#: Notifications/NotificationMenuItemFactory.qml:89
139msgid "Show password"
143msgid "Show password"
140−msgstr ""
144+msgstr "Jelszó megjelenítése"
141
145
142#: Panel/SearchIndicator.qml:27
146#: Panel/SearchIndicator.qml:27
143msgid "Search"
147msgid "Search"
@@ -146,13 +150,13 @@ msgstr "Keresés"
146#: plugins/Unity/Launcher/launcheritem.cpp:43
150#: plugins/Unity/Launcher/launcheritem.cpp:43
147#: plugins/Unity/Launcher/launcheritem.cpp:73
151#: plugins/Unity/Launcher/launcheritem.cpp:73
148msgid "Lock to Launcher"
152msgid "Lock to Launcher"
149−msgstr ""
153+msgstr "Indítóban tartás"
150
154
151#: plugins/Unity/Launcher/launcheritem.cpp:73
155#: plugins/Unity/Launcher/launcheritem.cpp:73
152msgid "Unlock from Launcher"
156msgid "Unlock from Launcher"
153−msgstr ""
157+msgstr "Eltávolítás az Indítóból"
154
158
155#: Shell.qml:439
159#: Shell.qml:439
156#, qt-format
160#, qt-format
157msgid "Please enter %1"
161msgid "Please enter %1"
158−msgstr ""
162+msgstr "Adja meg a következőt: %1"
po/hy.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/id.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/is.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/it.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/ja.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/km.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/kn.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/ko.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/ky.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/lo.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/lv.po+6−6
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"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"
12"Last-Translator: Jānis-Marks Gailis <jeanmarc.gailis@gmail.com>\n"
12"Last-Translator: Jānis-Marks Gailis <jeanmarc.gailis@gmail.com>\n"
13"Language-Team: Latvian <lv@li.org>\n"
13"Language-Team: Latvian <lv@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2014-01-12 05:52+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16890)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
@@ -32,7 +32,7 @@ msgstr ""
32
32
33#: Components/EdgeDemo.qml:144
33#: Components/EdgeDemo.qml:144
34msgid "Top edge"
34msgid "Top edge"
35−msgstr ""
35+msgstr "Augšējā mala"
36
36
37#: Components/EdgeDemo.qml:145
37#: Components/EdgeDemo.qml:145
38msgid "Try swiping from the top edge to access the indicators"
38msgid "Try swiping from the top edge to access the indicators"
@@ -40,7 +40,7 @@ msgstr ""
40
40
41#: Components/EdgeDemo.qml:170
41#: Components/EdgeDemo.qml:170
42msgid "Close"
42msgid "Close"
43−msgstr ""
43+msgstr "Aizvērt"
44
44
45#: Components/EdgeDemo.qml:171
45#: Components/EdgeDemo.qml:171
46msgid "Swipe up again to close the settings screen"
46msgid "Swipe up again to close the settings screen"
@@ -48,7 +48,7 @@ msgstr ""
48
48
49#: Components/EdgeDemo.qml:198
49#: Components/EdgeDemo.qml:198
50msgid "Left edge"
50msgid "Left edge"
51−msgstr ""
51+msgstr "Kreisā mala"
52
52
53#: Components/EdgeDemo.qml:199
53#: Components/EdgeDemo.qml:199
54msgid "Swipe from the left to reveal the launcher for quick access to apps"
54msgid "Swipe from the left to reveal the launcher for quick access to apps"
po/ml.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/my.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-12-13 06:46+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16869)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/nb.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/nl.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/pl.po+7−7
@@ -8,16 +8,16 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-13 13:44+0000\n"
11+"PO-Revision-Date: 2014-01-10 11:38+0000\n"
12−"Last-Translator: GTriderXC <gtriderxc@yahoo.com>\n"
12+"Last-Translator: Michał Sawicz <michal.sawicz@canonical.com>\n"
13"Language-Team: Polish <pl@li.org>\n"
13"Language-Team: Polish <pl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
17"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
18"|| n%100>=20) ? 1 : 2;\n"
18"|| n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2014-01-11 06:38+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16890)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
@@ -143,7 +143,7 @@ msgstr "Rozpoznanie..."
143
143
144#: Notifications/NotificationMenuItemFactory.qml:89
144#: Notifications/NotificationMenuItemFactory.qml:89
145msgid "Show password"
145msgid "Show password"
146−msgstr ""
146+msgstr "Wyświetl hasło"
147
147
148#: Panel/SearchIndicator.qml:27
148#: Panel/SearchIndicator.qml:27
149msgid "Search"
149msgid "Search"
@@ -152,11 +152,11 @@ msgstr "Szukaj"
152#: plugins/Unity/Launcher/launcheritem.cpp:43
152#: plugins/Unity/Launcher/launcheritem.cpp:43
153#: plugins/Unity/Launcher/launcheritem.cpp:73
153#: plugins/Unity/Launcher/launcheritem.cpp:73
154msgid "Lock to Launcher"
154msgid "Lock to Launcher"
155−msgstr ""
155+msgstr "Dodaj do panelu uruchamiania"
156
156
157#: plugins/Unity/Launcher/launcheritem.cpp:73
157#: plugins/Unity/Launcher/launcheritem.cpp:73
158msgid "Unlock from Launcher"
158msgid "Unlock from Launcher"
159−msgstr ""
159+msgstr "Usuń z panelu uruchamiania"
160
160
161#: Shell.qml:439
161#: Shell.qml:439
162#, qt-format
162#, qt-format
po/pt_BR.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n > 1;\n"
17"Plural-Forms: nplurals=2; plural=n > 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/ro.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=(n == 1 ? 0: (((n % 100 > 19) || ((n % 100 "
17"Plural-Forms: nplurals=3; plural=(n == 1 ? 0: (((n % 100 > 19) || ((n % 100 "
18"== 0) && (n != 0))) ? 2: 1));\n"
18"== 0) && (n != 0))) ? 2: 1));\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/ru.po+9−8
@@ -8,16 +8,16 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-01 05:55+0000\n"
11+"PO-Revision-Date: 2014-01-07 05:21+0000\n"
12−"Last-Translator: Zonov Roman <Unknown>\n"
12+"Last-Translator: Ivlev Denis <Unknown>\n"
13"Language-Team: Russian <ru@li.org>\n"
13"Language-Team: Russian <ru@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2014-01-08 05:43+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
@@ -89,7 +89,8 @@ msgstr "(%1)"
89#, qt-format
89#, qt-format
90msgid "%1 review"
90msgid "%1 review"
91msgid_plural "%1 reviews"
91msgid_plural "%1 reviews"
92−msgstr[0] "%1 отзыв"
92+msgstr[0] "%1 отзыва"
93+msgstr[1] "%1 отзывов"
93
94
94#: Dash/Previews/Reviews.qml:74
95#: Dash/Previews/Reviews.qml:74
95msgid "Add a review"
96msgid "Add a review"
@@ -142,7 +143,7 @@ msgstr "Говорите..."
142
143
143#: Notifications/NotificationMenuItemFactory.qml:89
144#: Notifications/NotificationMenuItemFactory.qml:89
144msgid "Show password"
145msgid "Show password"
145−msgstr ""
146+msgstr "Показать пароль"
146
147
147#: Panel/SearchIndicator.qml:27
148#: Panel/SearchIndicator.qml:27
148msgid "Search"
149msgid "Search"
@@ -151,11 +152,11 @@ msgstr "Поиск"
151#: plugins/Unity/Launcher/launcheritem.cpp:43
152#: plugins/Unity/Launcher/launcheritem.cpp:43
152#: plugins/Unity/Launcher/launcheritem.cpp:73
153#: plugins/Unity/Launcher/launcheritem.cpp:73
153msgid "Lock to Launcher"
154msgid "Lock to Launcher"
154−msgstr ""
155+msgstr "Закрепить на панели"
155
156
156#: plugins/Unity/Launcher/launcheritem.cpp:73
157#: plugins/Unity/Launcher/launcheritem.cpp:73
157msgid "Unlock from Launcher"
158msgid "Unlock from Launcher"
158−msgstr ""
159+msgstr "Убрать с панели"
159
160
160#: Shell.qml:439
161#: Shell.qml:439
161#, qt-format
162#, qt-format
po/shn.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=(n == 1) ? 0 : 1;\n"
17"Plural-Forms: nplurals=2; plural=(n == 1) ? 0 : 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/si.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/sl.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || "
17"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || "
18"n%100==4 ? 3 : 0);\n"
18"n%100==4 ? 3 : 0);\n"
19−"X-Launchpad-Export-Date: 2013-12-01 05:47+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/sr.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/sv.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/ta.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20"Language: ta\n"
20"Language: ta\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
po/te.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/tr.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-13 06:46+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16869)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/ug.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/uk.po+2−2
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
18"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
19+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
20−"X-Generator: Launchpad (build 16856)\n"
20+"X-Generator: Launchpad (build 16877)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
po/xh.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/zh_CN.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
po/zh_TW.po+2−2
@@ -15,8 +15,8 @@ msgstr ""
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
17"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2013-11-30 05:33+0000\n"
18+"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19−"X-Generator: Launchpad (build 16856)\n"
19+"X-Generator: Launchpad (build 16877)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
qml/Components/PageHeader.qml+14−39
@@ -22,17 +22,12 @@ import SessionManager 0.1
22import Unity 0.1
22import Unity 0.1
23
23
24Item {
24Item {
25− /*!
25+ id: root
26− \preliminary
27− The text that is shown inside the Page Header
28− \qmlproperty string text
29− */
30− property alias text: label.text
31−
32 property bool searchEntryEnabled: false
26 property bool searchEntryEnabled: false
33 property alias searchQuery: searchField.text
27 property alias searchQuery: searchField.text
34 property ListModel searchHistory
28 property ListModel searchHistory
35 property Scope scope
29 property Scope scope
30+ property alias childItem: itemContainer.children
36
31
37 height: units.gu(8.5)
32 height: units.gu(8.5)
38 implicitHeight: units.gu(8.5)
33 implicitHeight: units.gu(8.5)
@@ -83,29 +78,10 @@ Item {
83 height: childrenRect.height
78 height: childrenRect.height
84
79
85 Item {
80 Item {
86− id: textContainer
81+ id: itemContainer
87
82
88− width: header.width
83+ width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
89 height: header.height
84 height: header.height
90−
91− Label {
92− id: label
93− anchors {
94− left: parent.left
95− leftMargin: units.gu(2)
96− right: parent.right
97− verticalCenter: parent.verticalCenter
98− }
99−
100− color: Theme.palette.selected.backgroundText
101− opacity: 0.8
102− font.family: "Ubuntu"
103− font.weight: Font.Light
104− fontSize: "x-large"
105− elide: Text.ElideRight
106− style: Text.Raised
107− styleColor: "black"
108− }
109 }
85 }
110
86
111 Item {
87 Item {
@@ -116,12 +92,12 @@ Item {
116 property bool popoverShouldOpen: false
92 property bool popoverShouldOpen: false
117 property bool popoverShouldClose: false
93 property bool popoverShouldClose: false
118
94
119− property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
95+ property bool narrowMode: parent.width < units.gu(80)
120
96
121 property bool active: searchField.text != "" || searchField.activeFocus
97 property bool active: searchField.text != "" || searchField.activeFocus
122 property var popover: null
98 property var popover: null
123
99
124− anchors.right: textContainer.right
100+ anchors.right: headerContainer.right
125 height: header.height
101 height: header.height
126
102
127 state:
103 state:
@@ -142,9 +118,8 @@ Item {
142
118
143 function openPopover() {
119 function openPopover() {
144 if (searchHistory.count > 0) {
120 if (searchHistory.count > 0) {
145− searchContainer.popover = PopupUtils.open(popoverComponent, searchField,
121+ searchContainer.popover = PopupUtils.open(popoverComponent, pointerPositioner,
146 {
122 {
147− "pointerTarget": pointerPositioner,
148 "contentWidth": searchField.width,
123 "contentWidth": searchField.width,
149 "edgeMargins": units.gu(1)
124 "edgeMargins": units.gu(1)
150 }
125 }
@@ -257,13 +232,13 @@ Item {
257 states: [
232 states: [
258 State {
233 State {
259 name: "wide"
234 name: "wide"
260− AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
235+ AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
261− AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
236+ AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
262 },
237 },
263 State {
238 State {
264 name: "narrow"
239 name: "narrow"
265 PropertyChanges { target: searchField; highlighted: true }
240 PropertyChanges { target: searchField; highlighted: true }
266− AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
241+ AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
267 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
242 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
268 },
243 },
269 State {
244 State {
@@ -301,7 +276,7 @@ Item {
301 ParallelAnimation {
276 ParallelAnimation {
302 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
277 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
303 PropertyAction { target: primaryImage; property: "source" }
278 PropertyAction { target: primaryImage; property: "source" }
304− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
279+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
305 }
280 }
306 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
281 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
307 }
282 }
@@ -310,14 +285,14 @@ Item {
310 to: "inactive"
285 to: "inactive"
311 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
286 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
312 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
287 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
313− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
288+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
314 },
289 },
315 Transition {
290 Transition {
316 to: "narrowActive"
291 to: "narrowActive"
317 SequentialAnimation {
292 SequentialAnimation {
318 ParallelAnimation {
293 ParallelAnimation {
319 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
294 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
320− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
295+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
321 }
296 }
322 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
297 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
323 }
298 }
@@ -326,7 +301,7 @@ Item {
326 to: "narrowInactive"
301 to: "narrowInactive"
327 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
302 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
328 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
303 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
329− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
304+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
330 }
305 }
331 ]
306 ]
332
307
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 {
66 model: filteredScopes
66 model: filteredScopes
67 scopes: scopes
67 scopes: scopes
68 searchHistory: dash.searchHistory
68 searchHistory: dash.searchHistory
69− onMovementStarted: dashbar.startNavigation()
70− onMovementEnded: dashbar.stopNavigation()
71− onContentFlickStarted: dashbar.finishNavigation()
72− onContentEndReached: dashbar.finishNavigation()
73− onPreviewShown: dashbar.finishNavigation()
74 onScopeLoaded: {
69 onScopeLoaded: {
75 if (scopeId == dash.showScopeOnLoaded) {
70 if (scopeId == dash.showScopeOnLoaded) {
76 dash.setCurrentScope(scopeId, false, false)
71 dash.setCurrentScope(scopeId, false, false)
@@ -80,17 +75,4 @@ Showable {
80 scale: dash.contentScale
75 scale: dash.contentScale
81 clip: scale != 1.0
76 clip: scale != 1.0
82 }
77 }
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− }
96}
78}
qml/Dash/DashApps.qml−3
@@ -29,9 +29,6 @@ GenericScopeView {
29 property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
29 property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
30 property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
30 property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
31
31
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−
35 ListModel {
32 ListModel {
36 id: dummyVisibilityModifier
33 id: dummyVisibilityModifier
37
34
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 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Unity 0.1
19import Unity 0.1
20+import "../Components"
20
21
21Item {
22Item {
22 id: dashContent
23 id: dashContent
@@ -33,7 +34,6 @@ Item {
33 signal movementEnded()
34 signal movementEnded()
34 signal contentFlickStarted()
35 signal contentFlickStarted()
35 signal contentEndReached()
36 signal contentEndReached()
36− signal previewShown()
37 signal scopeLoaded(string scopeId)
37 signal scopeLoaded(string scopeId)
38 signal positionedAtBeginning()
38 signal positionedAtBeginning()
39
39
@@ -80,77 +80,138 @@ Item {
80 }
80 }
81 }
81 }
82
82
83− ListView {
83+ Item {
84− id: dashContentList
84+ id: dashContentListHolder
85− objectName: "dashContentList"
86−
87− interactive: dashContent.scopes.loaded && !currentItem.previewShown && !currentItem.moving
88−
89 anchors.fill: parent
85 anchors.fill: parent
86+
87+ ListView {
88+ id: dashContentList
89+ objectName: "dashContentList"
90+
91+ interactive: dashContent.scopes.loaded && !previewListView.onScreen && !currentItem.moving
92+
93+ anchors.fill: parent
90− model: dashContent.model
94+ model: dashContent.model
91− orientation: ListView.Horizontal
95+ orientation: ListView.Horizontal
92− boundsBehavior: Flickable.DragAndOvershootBounds
96+ boundsBehavior: Flickable.DragAndOvershootBounds
93− flickDeceleration: units.gu(625)
97+ flickDeceleration: units.gu(625)
94− maximumFlickVelocity: width * 5
98+ maximumFlickVelocity: width * 5
95− snapMode: ListView.SnapOneItem
99+ snapMode: ListView.SnapOneItem
96− highlightMoveDuration: 250
100+ highlightMoveDuration: 250
97− highlightRangeMode: ListView.StrictlyEnforceRange
101+ highlightRangeMode: ListView.StrictlyEnforceRange
98− /* FIXME: workaround rendering issue due to use of ShaderEffectSource in
102+ // TODO Investigate if we can switch to a smaller cache buffer when/if UbuntuShape gets more performant
99− UbuntuShape. While switching from the home scope to the People scope the
100− rendering would block midway.
101− */
102− cacheBuffer: 2147483647
103+ cacheBuffer: 1073741823
103− onMovementStarted: dashContent.movementStarted()
104+ onMovementStarted: dashContent.movementStarted()
104− onMovementEnded: dashContent.movementEnded()
105+ onMovementEnded: dashContent.movementEnded()
105−
106+
106− // If the number of items is less than the current index, then need to reset to another item.
107+ // If the number of items is less than the current index, then need to reset to another item.
107− onCountChanged: {
108+ onCountChanged: {
108− if (count > 0) {
109+ if (count > 0) {
109− if (currentIndex >= count) {
110+ if (currentIndex >= count) {
110− dashContent.setCurrentScopeAtIndex(count-1, true, true)
111+ dashContent.setCurrentScopeAtIndex(count-1, true, true)
111− } else if (currentIndex < 0) {
112+ } else if (currentIndex < 0) {
112− // setting currentIndex directly, cause we don't want to loose set_current_index
113+ // setting currentIndex directly, cause we don't want to loose set_current_index
113− dashContent.currentIndex = 0
114+ dashContent.currentIndex = 0
115+ }
114 }
116 }
115 }
117 }
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+ }
116 }
155 }
117
156
118− delegate:
157+ PageHeader {
119− Loader {
158+ id: pageHeader
120− width: ListView.view.width
159+ width: parent.width
121− height: ListView.view.height
160+ searchEntryEnabled: true
122− asynchronous: true
161+ searchHistory: dashContent.searchHistory
123− source: scopeMapper.map(scope.id)
162+
163+ childItem: TabBar {
164+ id: tabBar
124− objectName: scope.id + " loader"
165+ objectName: "tabbar"
125−
166+ height: units.gu(7)
126− readonly property bool previewShown: item ? item.previewShown : false
167+ width: parent.width
127− readonly property bool moving: item ? item.moving : false
168+ selectionMode: false
128−
169+ style: DashContentTabBarStyle {}
129− // these are needed for autopilot tests
170+
130− readonly property string scopeId: scope.id
171+ model: dashContentList.model
131− readonly property bool isCurrent: ListView.isCurrentItem
172+
132− readonly property bool isLoaded: status == Loader.Ready
173+ onSelectedIndexChanged: {
133−
174+ dashContentList.currentIndex = selectedIndex;
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)
141 }
175 }
176+
142 Connections {
177 Connections {
143− target: item
178+ target: dashContentList
144− ignoreUnknownSignals: true
179+ onCurrentIndexChanged: {
145− onEndReached: contentEndReached()
180+ tabBar.selectedIndex = dashContentList.currentIndex
146− onPreviewShownChanged: {
147− if (item.previewShown) {
148− dashContent.previewShown()
149− }
150 }
181 }
151 }
182 }
152
183
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+ }
154 }
196 }
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
155 }
216 }
156}
217}
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 {
27 property Scope scope
27 property Scope scope
28 property SortFilterProxyModel categories: categoryFilter
28 property SortFilterProxyModel categories: categoryFilter
29 property bool isCurrent
29 property bool isCurrent
30− property ListModel searchHistory
31 property alias moving: categoryView.moving
30 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
32
35
33 signal endReached
36 signal endReached
34 signal movementStarted
37 signal movementStarted
35 signal positionedAtBeginning
38 signal positionedAtBeginning
36
39
37− readonly property alias previewShown: previewListView.onScreen
38 property bool enableHeightBehaviorOnNextCreation: false
40 property bool enableHeightBehaviorOnNextCreation: false
41+ property var categoryView: categoryView
39
42
40 // FIXME delay the search so that daemons have time to settle, note that
43 // FIXME delay the search so that daemons have time to settle, note that
41 // removing this will break ScopeView::test_changeScope
44 // removing this will break ScopeView::test_changeScope
@@ -84,12 +87,14 @@ FocusScope {
84 target: scopeView.scope
87 target: scopeView.scope
85 property: "searchQuery"
88 property: "searchQuery"
86 value: pageHeader.searchQuery
89 value: pageHeader.searchQuery
90+ when: isCurrent
87 }
91 }
88
92
89 Binding {
93 Binding {
90 target: pageHeader
94 target: pageHeader
91 property: "searchQuery"
95 property: "searchQuery"
92 value: scopeView.scope.searchQuery
96 value: scopeView.scope.searchQuery
97+ when: isCurrent
93 }
98 }
94
99
95 Connections {
100 Connections {
@@ -119,6 +124,10 @@ FocusScope {
119 property string expandedCategoryId: ""
124 property string expandedCategoryId: ""
120 signal correctExpandedCategory();
125 signal correctExpandedCategory();
121
126
127+ onContentYChanged: pageHeader.positionRealHeader();
128+ onOriginYChanged: pageHeader.positionRealHeader();
129+ onContentHeightChanged: pageHeader.positionRealHeader();
130+
122 Behavior on contentY {
131 Behavior on contentY {
123 enabled: previewListView.open
132 enabled: previewListView.open
124 UbuntuNumberAnimation {}
133 UbuntuNumberAnimation {}
@@ -176,7 +185,7 @@ FocusScope {
176 target: rendererLoader.item
185 target: rendererLoader.item
177 onClicked: {
186 onClicked: {
178 // Prepare the preview in case activate() triggers a preview only
187 // 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);
180 previewListView.categoryId = categoryId
189 previewListView.categoryId = categoryId
181 previewListView.categoryDelegate = rendererLoader.item
190 previewListView.categoryDelegate = rendererLoader.item
182 previewListView.model = target.model;
191 previewListView.model = target.model;
@@ -197,7 +206,7 @@ FocusScope {
197 }
206 }
198 }
207 }
199 onPressAndHold: {
208 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);
201 previewListView.categoryId = categoryId
210 previewListView.categoryId = categoryId
202 previewListView.categoryDelegate = rendererLoader.item
211 previewListView.categoryDelegate = rendererLoader.item
203 previewListView.model = target.model;
212 previewListView.model = target.model;
@@ -288,14 +297,20 @@ FocusScope {
288 categoryView.expandedCategoryId = "";
297 categoryView.expandedCategoryId = "";
289 }
298 }
290 }
299 }
291− pageHeader: PageHeader {
300+ pageHeader: Item {
292− id: pageHeader
301+ implicitHeight: scopeView.tabBarHeight
293− objectName: "pageHeader"
302+ onHeightChanged: {
294− width: categoryView.width
303+ if (scopeView.pageHeader) {
295− text: scopeView.scope.name
304+ scopeView.pageHeader.height = height;
296− searchEntryEnabled: true
305+ }
306+ }
307+ onYChanged: positionRealHeader();
308+
309+ function positionRealHeader() {
297− scope: scopeView.scope
310+ if (scopeView.pageHeader) {
298− searchHistory: scopeView.searchHistory
311+ scopeView.pageHeader.y = y + parent.y;
312+ }
313+ }
299 }
314 }
300 }
315 }
301
316
@@ -346,44 +361,6 @@ FocusScope {
346 }
361 }
347 }
362 }
348
363
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−
387 Connections {
364 Connections {
388 target: scopeView.scope
365 target: scopeView.scope
389 onPreviewReady: {
366 onPreviewReady: {
@@ -402,190 +379,13 @@ FocusScope {
402 }
379 }
403 }
380 }
404
381
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−
582 // TODO: Move as InverseMouseArea to DashPreview
382 // TODO: Move as InverseMouseArea to DashPreview
583 MouseArea {
383 MouseArea {
584 objectName: "closePreviewMouseArea"
384 objectName: "closePreviewMouseArea"
585 enabled: previewListView.onScreen
385 enabled: previewListView.onScreen
586 anchors {
386 anchors {
587 fill: parent
387 fill: parent
588− topMargin: effect.bottomGapPx
388+ topMargin: openEffect.bottomGapPx
589 }
389 }
590 onClicked: {
390 onClicked: {
591 previewListView.open = false;
391 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 {
89 anchors.left: parent.left
89 anchors.left: parent.left
90 anchors.right: parent.right
90 anchors.right: parent.right
91
91
92− PageHeader {
92+ PageHeaderLabel {
93 id: header
93 id: header
94 anchors.top: parent.top
94 anchors.top: parent.top
95 anchors.left: parent.left
95 anchors.left: parent.left
qml/Notifications/Notification.qml+14−4
@@ -34,10 +34,12 @@ Item {
34 property var notification
34 property var notification
35 property color color
35 property color color
36 property bool fullscreen
36 property bool fullscreen
37+ property int maxHeight
38+ property int margins
37
39
38 fullscreen: false
40 fullscreen: false
39 objectName: "background"
41 objectName: "background"
40− implicitHeight: type != Notification.PlaceHolder ? (fullscreen ? notificationList.fullHeight : contentColumn.height + contentColumn.spacing * 4) : 0
42+ implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : contentColumn.height + contentColumn.spacing * 4) : 0
41
43
42 color: Qt.rgba(0.132, 0.117, 0.109, 0.97)
44 color: Qt.rgba(0.132, 0.117, 0.109, 0.97)
43 opacity: 0
45 opacity: 0
@@ -50,7 +52,7 @@ Item {
50
52
51 visible: !fullscreen
53 visible: !fullscreen
52 anchors.fill: parent
54 anchors.fill: parent
53− anchors.margins: notificationList.margin
55+ anchors.margins: notification.margins
54 color: parent.color
56 color: parent.color
55 opacity: parent.opacity
57 opacity: parent.opacity
56 radius: "medium"
58 radius: "medium"
@@ -104,8 +106,11 @@ Item {
104
106
105 anchors.fill: contentColumn
107 anchors.fill: contentColumn
106 objectName: "interactiveArea"
108 objectName: "interactiveArea"
109+ onClicked: {
107− enabled: notification.type == Notification.Interactive
110+ if (notification.type == Notification.Interactive) {
108− onClicked: notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
111+ notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
112+ }
113+ }
109 }
114 }
110
115
111 Column {
116 Column {
@@ -207,6 +212,11 @@ Item {
207 menuModel: unityMenuModel
212 menuModel: unityMenuModel
208 menuData: model
213 menuData: model
209 menuIndex: index
214 menuIndex: index
215+ maxHeight: notification.maxHeight
216+
217+ onLoaded: {
218+ notification.fullscreen = Qt.binding(function() { return fullscreen; });
219+ }
210 }
220 }
211 }
221 }
212 }
222 }
qml/Notifications/NotificationMenuItemFactory.qml+4−5
@@ -28,6 +28,8 @@ Loader {
28 property QtObject menuModel: null
28 property QtObject menuModel: null
29 property QtObject menuData: null
29 property QtObject menuData: null
30 property int menuIndex
30 property int menuIndex
31+ property int maxHeight
32+ readonly property bool fullscreen: menuData.type === "com.canonical.snapdecision.pinlock"
31
33
32 property var _map: {
34 property var _map: {
33 "com.canonical.snapdecision.textfield": textfield,
35 "com.canonical.snapdecision.textfield": textfield,
@@ -43,10 +45,6 @@ Loader {
43 }
45 }
44 }
46 }
45
47
46− Component.onCompleted: {
47− notification.fullscreen = menuData.type == "com.canonical.snapdecision.pinlock"
48− }
49−
50 Component {
48 Component {
51 id: textfield
49 id: textfield
52
50
@@ -68,6 +66,7 @@ Loader {
68 TextField {
66 TextField {
69 id: textfield
67 id: textfield
70
68
69+ inputMethodHints: Qt.ImhNoAutoUppercase
71 anchors.left: parent.left; anchors.right: parent.right
70 anchors.left: parent.left; anchors.right: parent.right
72 echoMode: checkBox.checked ? TextInput.Normal : TextInput.Password
71 echoMode: checkBox.checked ? TextInput.Normal : TextInput.Password
73 height: units.gu(5)
72 height: units.gu(5)
@@ -100,7 +99,7 @@ Loader {
100
99
101 Lockscreen {
100 Lockscreen {
102 anchors.left: parent.left; anchors.right: parent.right
101 anchors.left: parent.left; anchors.right: parent.right
103− height: notificationList.fullHeight
102+ height: menuFactory.maxHeight
104 placeholderText: i18n.tr("Please enter SIM PIN")
103 placeholderText: i18n.tr("Please enter SIM PIN")
105 background: shell.background
104 background: shell.background
106
105
qml/Notifications/Notifications.qml+2−1
@@ -25,7 +25,6 @@ ListView {
25 interactive: false
25 interactive: false
26
26
27 property real margin
27 property real margin
28− property real fullHeight
29
28
30 delegate: Notification {
29 delegate: Notification {
31 objectName: "notification" + index
30 objectName: "notification" + index
@@ -42,6 +41,8 @@ ListView {
42 actions: model.actions
41 actions: model.actions
43 notificationId: model.id
42 notificationId: model.id
44 notification: notificationList.model.getRaw(notificationId)
43 notification: notificationList.model.getRaw(notificationId)
44+ maxHeight: notificationList.height
45+ margins: notificationList.margin
45
46
46 // make sure there's no opacity-difference between the several
47 // make sure there's no opacity-difference between the several
47 // elements in a notification
48 // elements in a notification
qml/Panel/IndicatorItem.qml+6−12
@@ -23,10 +23,10 @@ Item {
23 id: indicatorItem
23 id: indicatorItem
24
24
25 property alias widgetSource: loader.source
25 property alias widgetSource: loader.source
26− property bool highlighted: false
27 property bool dimmed: false
26 property bool dimmed: false
28 property var indicatorProperties: undefined
27 property var indicatorProperties: undefined
29 property bool indicatorVisible: loader.item ? loader.item.enabled : false
28 property bool indicatorVisible: loader.item ? loader.item.enabled : false
29+ property string identifier
30
30
31 opacity: dimmed ? 0.4 : 1
31 opacity: dimmed ? 0.4 : 1
32 Behavior on opacity { StandardAnimation {} }
32 Behavior on opacity { StandardAnimation {} }
@@ -45,17 +45,11 @@ Item {
45 }
45 }
46 }
46 }
47 }
47 }
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
57
48
58− opacity: highlighted ? 1 : 0
49+ Binding {
59− Behavior on opacity { StandardAnimation {} }
50+ target: loader.item
51+ property: "objectName"
52+ value: identifier + "-widget"
53+ }
60 }
54 }
61}
55}
qml/Panel/IndicatorRow.qml+40−8
@@ -29,6 +29,8 @@ Item {
29 property var visibleIndicators: defined
29 property var visibleIndicators: defined
30 property int overFlowWidth: width
30 property int overFlowWidth: width
31 property bool showAll: false
31 property bool showAll: false
32+ property real currentItemOffset: 0.0
33+ property real unitProgress: 0.0
32
34
33 width: units.gu(40)
35 width: units.gu(40)
34 height: units.gu(3)
36 height: units.gu(3)
@@ -79,23 +81,24 @@ Item {
79 height: indicatorRow.height
81 height: indicatorRow.height
80 width: indicatorItem.width
82 width: indicatorItem.width
81 visible: indicatorItem.indicatorVisible
83 visible: indicatorItem.indicatorVisible
82− opacity: 1.0
84+ opacity: 1.0 * opacityMultiplier
83 y: 0
85 y: 0
84 state: "standard"
86 state: "standard"
85
87
86 property int ownIndex: index
88 property int ownIndex: index
87− property alias highlighted: indicatorItem.highlighted
89+ property bool highlighted: indicatorRow.state != "initial" ? ownIndex == indicatorRow.currentItemIndex : false
88− property alias dimmed: indicatorItem.dimmed
90+ property bool dimmed: indicatorRow.state != "initial" ? ownIndex != indicatorRow.currentItemIndex : false
89
91
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")
91 property bool overflow: row.width - itemWrapper.x > overFlowWidth
93 property bool overflow: row.width - itemWrapper.x > overFlowWidth
94+ property real opacityMultiplier: highlighted ? 1 : (1 - indicatorRow.unitProgress)
92
95
93 IndicatorItem {
96 IndicatorItem {
94 id: indicatorItem
97 id: indicatorItem
98+ identifier: model.identifier
95 height: parent.height
99 height: parent.height
96
100
97− highlighted: indicatorRow.state != "initial" ? itemWrapper.ownIndex == indicatorRow.currentItemIndex : false
101+ dimmed: itemWrapper.dimmed
98− dimmed: indicatorRow.state != "initial" ? itemWrapper.ownIndex != indicatorRow.currentItemIndex : false
99
102
100 widgetSource: model.widgetSource
103 widgetSource: model.widgetSource
101 indicatorProperties : model.indicatorProperties
104 indicatorProperties : model.indicatorProperties
@@ -135,14 +138,43 @@ Item {
135
138
136 transitions: [
139 transitions: [
137 Transition {
140 Transition {
138− StandardAnimation {
141+ UbuntuNumberAnimation {
139 target: itemWrapper
142 target: itemWrapper
140 property: "opacity"
143 property: "opacity"
141− duration: 300
144+ duration: UbuntuAnimation.BriskDuration
142 }
145 }
143 }
146 }
144 ]
147 ]
145 }
148 }
146 }
149 }
147 }
150 }
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+ }
148}
180}
qml/Panel/Indicators.qml+22−11
@@ -39,6 +39,7 @@ Showable {
39 property bool contentEnabled: true
39 property bool contentEnabled: true
40 property bool initalizeItem: true
40 property bool initalizeItem: true
41 readonly property alias content: menuContent
41 readonly property alias content: menuContent
42+ property real unitProgress: 0.0
42
43
43 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
44 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
44 // use its own values. Need to ask design about this.
45 // use its own values. Need to ask design about this.
@@ -128,23 +129,31 @@ Showable {
128 rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
129 rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
129 // get the current delegate
130 // get the current delegate
130 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
131 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
131− if (currentItem && currentItem != indicatorRow.currentItem ) {
132+ if (currentItem) {
132 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
133 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
133 distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
134 distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
135+ if (currentItem != indicatorRow.currentItem) {
134− if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
136+ 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) {
140 bufferExceeded = true;
137 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+ }
141 }
144 }
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);
142 }
152 }
143− if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null) {
153+ indicatorRow.currentItemOffset = 1 - (distanceFromRightEdge * 2);
144− indicatorRow.currentItem = currentItem;
154+ } else {
145− }
146− } else if (!currentItem) {
147 indicatorRow.setDefaultItem();
155 indicatorRow.setDefaultItem();
156+ indicatorRow.currentItemOffset = 0;
148 }
157 }
149 initalizeItem = indicatorRow.currentItem == null;
158 initalizeItem = indicatorRow.currentItem == null;
150 }
159 }
@@ -268,6 +277,7 @@ Showable {
268 height: indicators.panelHeight
277 height: indicators.panelHeight
269 indicatorsModel: indicatorsModel
278 indicatorsModel: indicatorsModel
270 state: indicators.state
279 state: indicators.state
280+ unitProgress: indicators.unitProgress
271
281
272 Connections {
282 Connections {
273 property bool enableIndexChangeSignal: true
283 property bool enableIndexChangeSignal: true
@@ -326,6 +336,7 @@ Showable {
326 if (hideAnimation.running) {
336 if (hideAnimation.running) {
327 indicators.state = "initial";
337 indicators.state = "initial";
328 initalizeItem = true;
338 initalizeItem = true;
339+ menuContent.animateNextMenuChange = false;
329 }
340 }
330 }
341 }
331 }
342 }
qml/Panel/Indicators/DefaultIndicatorPage.qml+195−2
@@ -14,12 +14,205 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
15 *
16 * Authors:
16 * Authors:
17+ * Renato Araujo Oliveira Filho <renato@canonical.com>
17 * Nick Dedekind <nick.dedekind@canonical.com>
18 * Nick Dedekind <nick.dedekind@canonical.com>
18 */
19 */
19
20
20import QtQuick 2.0
21import QtQuick 2.0
22+import Ubuntu.Components 0.1 as Components
21import Unity.Indicators 0.1 as Indicators
23import Unity.Indicators 0.1 as Indicators
22
24
23−Indicators.IndicatorPage {
25+Indicators.IndicatorBase {
26+ id: main
27+
28+ //const
29+ property bool contentActive: false
30+ property string title: rootActionState.title
31+ property alias emptyText: emptyLabel.text
32+ property alias highlightFollowsCurrentItem : mainMenu.highlightFollowsCurrentItem
33+
34+ Indicators.UnityMenuModelStack {
35+ id: menuStack
36+ head: contentActive ? main.menuModel : null
37+
38+ property var rootMenu: null
39+
40+ onTailChanged: {
41+ if (!tail) {
42+ rootMenu = null;
43+ } else if (rootMenu != tail) {
44+ if (tail.get(0, "type") === rootMenuType) {
45+ rootMenu = menuStack.tail.submenu(0);
46+ push(rootMenu, 0);
47+ } else {
48+ rootMenu = null;
49+ }
50+ }
51+ }
52+ }
53+
54+ Connections {
55+ target: menuStack.tail
56+ onRowsInserted: {
57+ if (menuStack.rootMenu !== menuStack.tail && menuStack.tail.get(0, "type") === rootMenuType) {
58+ menuStack.rootMenu = menuStack.tail.submenu(0);
59+ menuStack.push(menuStack.rootMenu, 0);
60+ }
61+ }
62+ onModelReset: {
63+ if (menuStack.rootMenu !== menuStack.tail && menuStack.tail.get(0, "type") === rootMenuType) {
64+ menuStack.rootMenu = menuStack.tail.submenu(0);
65+ menuStack.push(menuStack.rootMenu, 0);
66+ }
67+ }
68+ }
69+
70+ ListView {
71+ id: mainMenu
72+ objectName: "mainMenu"
73+ model: menuStack.rootMenu
74+
75+ anchors {
24− anchors.fill: parent
76+ fill: parent
77+ bottomMargin: Qt.inputMethod.visible ? (Qt.inputMethod.keyboardRectangle.height - main.anchors.bottomMargin) : 0
78+
79+ Behavior on bottomMargin {
80+ NumberAnimation {
81+ duration: 175
82+ easing.type: Easing.OutQuad
83+ }
84+ }
85+ // TODO - does ever frame.
86+ onBottomMarginChanged: {
87+ mainMenu.positionViewAtIndex(mainMenu.currentIndex, ListView.End)
88+ }
89+ }
90+
91+ // Ensure all delegates are cached in order to improve smoothness of scrolling
92+ cacheBuffer: 10000
93+
94+ // Only allow flicking if the content doesn't fit on the page
95+ interactive: contentHeight > height
96+
97+ property int selectedIndex: -1
98+ property bool blockCurrentIndexChange: false
99+ // for count = 0
100+ onCountChanged: {
101+ if (count == 0 && selectedIndex != -1) {
102+ selectedIndex = -1;
103+ }
104+ }
105+ // for highlight following
106+ onSelectedIndexChanged: {
107+ if (currentIndex != selectedIndex) {
108+ var blocked = blockCurrentIndexChange;
109+ blockCurrentIndexChange = true;
110+
111+ currentIndex = selectedIndex;
112+
113+ blockCurrentIndexChange = blocked;
114+ }
115+ }
116+ // for item addition/removal
117+ onCurrentIndexChanged: {
118+ if (!blockCurrentIndexChange) {
119+ if (selectedIndex != -1 && selectedIndex != currentIndex) {
120+ selectedIndex = currentIndex;
121+ }
122+ }
123+ }
124+
125+ delegate: Loader {
126+ id: loader
127+ objectName: "menuItem" + index
128+ asynchronous: false
129+ visible: height > 0
130+
131+ property int modelIndex: index
132+
133+ anchors {
134+ left: parent.left
135+ right: parent.right
136+ }
137+
138+ sourceComponent: factory.load(model)
139+
140+ onLoaded: {
141+ if (item.hasOwnProperty("menuSelected")) {
142+ item.menuSelected = mainMenu.selectedIndex == index;
143+ item.selectMenu.connect(function() { mainMenu.selectedIndex = index; });
144+ item.deselectMenu.connect(function() { mainMenu.selectedIndex = -1; });
145+ }
146+ if (item.hasOwnProperty("menuData")) {
147+ item.menuData = Qt.binding(function() { return model; });
148+ }
149+ if (item.hasOwnProperty("menuIndex")) {
150+ item.menuIndex = Qt.binding(function() { return modelIndex; });
151+ }
152+ }
153+
154+ Binding {
155+ target: item ? item : null
156+ property: "objectName"
157+ value: model.action
158+ }
159+
160+ // TODO: Fixes lp#1243146
161+ // This is a workaround for a Qt bug. https://bugreports.qt-project.org/browse/QTBUG-34351
162+ Connections {
163+ target: mainMenu
164+ onSelectedIndexChanged: {
165+ if (loader.item && loader.item.hasOwnProperty("menuSelected")) {
166+ loader.item.menuSelected = mainMenu.selectedIndex == index;
167+ }
168+ }
169+ }
170+ }
171+ }
172+
173+ Indicators.MenuItemFactory {
174+ id: factory
175+ menuModel: mainMenu.model ? mainMenu.model : null
176+ }
177+
178+ Components.Label {
179+ id: emptyLabel
180+ objectName: "emptyLabel"
181+ visible: mainMenu.count == 0
182+ anchors {
183+ top: parent.top
184+ left: parent.left
185+ right: parent.right
186+ topMargin: units.gu(2)
187+ }
188+ wrapMode: Text.WordWrap
189+ horizontalAlignment: Text.AlignHCenter
190+
191+ //style
192+ color: "#e8e1d0"
193+ fontSize: "medium"
194+
195+ text: "Empty!"
196+ }
197+
198+ function start()
199+ {
200+ reset()
201+ if (!contentActive) {
202+ contentActive = true;
203+ }
204+ }
205+
206+ function stop()
207+ {
208+ if (contentActive) {
209+ contentActive = false;
210+ }
211+ }
212+
213+ function reset()
214+ {
215+ mainMenu.selectedIndex = -1;
216+ mainMenu.positionViewAtBeginning();
217+ }
25}
218}
qml/Panel/Indicators/DefaultIndicatorWidget.qml+5−3
@@ -21,15 +21,17 @@ import QtQuick 2.0
21import Ubuntu.Components 0.1
21import Ubuntu.Components 0.1
22import Unity.Indicators 0.1 as Indicators
22import Unity.Indicators 0.1 as Indicators
23
23
24−Indicators.IndicatorWidget {
24+Indicators.IndicatorBase {
25 id: indicatorWidget
25 id: indicatorWidget
26
26
27− width: itemRow.width
27+ property int iconSize: height
28−
29 property alias leftLabel: itemLeftLabel.text
28 property alias leftLabel: itemLeftLabel.text
30 property alias rightLabel: itemRightLabel.text
29 property alias rightLabel: itemRightLabel.text
31 property var icons: undefined
30 property var icons: undefined
32
31
32+ width: itemRow.width
33+ enabled: false
34+
33 Row {
35 Row {
34 id: itemRow
36 id: itemRow
35 width: childrenRect.width
37 width: childrenRect.width
qml/Panel/MenuContent.qml+3−1
@@ -33,6 +33,7 @@ MainView {
33 property int contentReleaseInterval: 20000
33 property int contentReleaseInterval: 20000
34 property bool activeHeader: false
34 property bool activeHeader: false
35 property alias visibleIndicators: visibleIndicatorsModel.visible
35 property alias visibleIndicators: visibleIndicatorsModel.visible
36+ property bool animateNextMenuChange: false
36
37
37 width: units.gu(40)
38 width: units.gu(40)
38 height: units.gu(42)
39 height: units.gu(42)
@@ -41,11 +42,12 @@ MainView {
41 var filteredIndex = filteredIndicators.mapFromSource(index)
42 var filteredIndex = filteredIndicators.mapFromSource(index)
42
43
43 if (tabs.selectedTabIndex !== filteredIndex) {
44 if (tabs.selectedTabIndex !== filteredIndex) {
44− if (tabs.selectedTabIndex == -1) {
45+ if (tabs.selectedTabIndex == -1 || !animateNextMenuChange) {
45 tabs.tabBar.animate = false;
46 tabs.tabBar.animate = false;
46 }
47 }
47 tabs.selectedTabIndex = filteredIndex;
48 tabs.selectedTabIndex = filteredIndex;
48 tabs.tabBar.animate = true;
49 tabs.tabBar.animate = true;
50+ animateNextMenuChange = true;
49 }
51 }
50 }
52 }
51
53
qml/Panel/Panel.qml+1−1
@@ -107,7 +107,7 @@ Item {
107 pinnedMode: !fullscreenMode
107 pinnedMode: !fullscreenMode
108 overFlowWidth: search.state=="hidden" ? parent.width : parent.width - search.width
108 overFlowWidth: search.state=="hidden" ? parent.width : parent.width - search.width
109
109
110− property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
110+ unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
111 }
111 }
112
112
113 PanelSeparatorLine {
113 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+}
qml/Shell.qml−1
@@ -540,7 +540,6 @@ BasicShell {
540
540
541 model: NotificationBackend.Model
541 model: NotificationBackend.Model
542 margin: units.gu(1)
542 margin: units.gu(1)
543− fullHeight: parent.height - panel.panelHeight
544
543
545 anchors {
544 anchors {
546 top: parent.top
545 top: parent.top
src/CMakeLists.txt+11
@@ -5,10 +5,21 @@ include_directories(
5 ${UNITY-MIR_INCLUDE_DIRS}
5 ${UNITY-MIR_INCLUDE_DIRS}
6)
6)
7
7
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+
8set(COMMON_SRCS
18set(COMMON_SRCS
9 ApplicationArguments.h
19 ApplicationArguments.h
10 main.cpp
20 main.cpp
11 MouseTouchAdaptor.cpp
21 MouseTouchAdaptor.cpp
22+ ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
12 )
23 )
13add_executable(${SHELL_APP} ${COMMON_SRCS})
24add_executable(${SHELL_APP} ${COMMON_SRCS})
14add_executable(${GREETER_APP} ${COMMON_SRCS})
25add_executable(${GREETER_APP} ${COMMON_SRCS})
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 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3# Unity Autopilot Test Suite
3# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
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
7# it under the terms of the GNU General Public License as published by
@@ -17,9 +17,16 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
18#
19
19
20+from autopilot import input, introspection
20
21
21−from autopilot.introspection import CustomEmulatorBase
22
22
23−
24−class UnityEmulatorBase(CustomEmulatorBase):
23+class UnityEmulatorBase(introspection.CustomEmulatorBase):
25 """A base class for all unity emulators."""
24 """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__':
87 action_id,
87 action_id,
88 action_label,
88 action_label,
89 action_callback,
89 action_callback,
90− None,
90+ (None, ),
91 None
91 None
92 )
92 )
93
93
tests/autopilot/unity8/shell/emulators/dash.py+102−8
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3# Unity Autopilot Test Suite
3# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
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
7# it under the terms of the GNU General Public License as published by
@@ -17,13 +17,25 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
18#
19
19
20−from unity8.shell.emulators import UnityEmulatorBase
20+import logging
21
21
22+from unity8.shell import emulators
22
23
23−class Dash(UnityEmulatorBase):
24+from autopilot import logging as autopilot_logging
25+from autopilot.introspection import dbus
24
26
27+
28+logger = logging.getLogger(__name__)
29+
30+
31+class Dash(emulators.UnityEmulatorBase):
25 """An emulator that understands the Dash."""
32 """An emulator that understands the Dash."""
26
33
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+
27 def get_home_applications_grid(self):
39 def get_home_applications_grid(self):
28 get_grid = self.get_scope('home').wait_select_single(
40 get_grid = self.get_scope('home').wait_select_single(
29 "GenericFilterGrid",
41 "GenericFilterGrid",
@@ -43,9 +55,91 @@ class Dash(UnityEmulatorBase):
43 return resp_grid.select_single('Tile', text=text)
55 return resp_grid.select_single('Tile', text=text)
44
56
45 def get_scope(self, scope_name='home'):
57 def get_scope(self, scope_name='home'):
46− dash_content = self.wait_select_single(
47− 'QQuickListView',
48− objectName='dashContentList'
49− )
50 scope_id = "%s.scope" % scope_name
58 scope_id = "%s.scope" % scope_name
59+ return self.dash_content_list.select_single(
51− return dash_content.select_single('QQuickLoader', scopeId=scope_id)
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 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3# Unity Autopilot Test Suite
3# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
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
7# it under the terms of the GNU General Public License as published by
@@ -18,24 +18,21 @@
18#
18#
19
19
20from unity8.shell.emulators import UnityEmulatorBase
20from unity8.shell.emulators import UnityEmulatorBase
21−from autopilot.input import Touch
22
21
23
22
24class Greeter(UnityEmulatorBase):
23class Greeter(UnityEmulatorBase):
25−
26 """An emulator that understands the greeter screen."""
24 """An emulator that understands the greeter screen."""
27
25
28 def swipe(self):
26 def swipe(self):
29 """Swipe the greeter screen away."""
27 """Swipe the greeter screen away."""
30 self.created.wait_for(True)
28 self.created.wait_for(True)
31− touch = Touch.create()
32
29
33 rect = self.globalRect
30 rect = self.globalRect
34 start_x = rect[0] + rect[2] - 3
31 start_x = rect[0] + rect[2] - 3
35 start_y = int(rect[1] + rect[3] / 2)
32 start_y = int(rect[1] + rect[3] / 2)
36 stop_x = int(rect[0] + rect[2] * 0.2)
33 stop_x = int(rect[0] + rect[2] * 0.2)
37 stop_y = start_y
34 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)
39
36
40 self.created.wait_for(False)
37 self.created.wait_for(False)
41
38
tests/autopilot/unity8/shell/emulators/main_window.py+6
@@ -91,5 +91,11 @@ class MainWindow(object):
91 def get_pinentryField(self):
91 def get_pinentryField(self):
92 return self.app.select_single(objectName="pinentryField")
92 return self.app.select_single(objectName="pinentryField")
93
93
94+ def get_indicator(self, indicator_name):
95+ return self.app.select_single(
96+ 'DefaultIndicatorWidget',
97+ objectName=indicator_name+'-widget'
98+ )
99+
94 def get_shell_background(self):
100 def get_shell_background(self):
95 return self.app.select_single("CrossFadeImage", objectName="backgroundImage")
101 return self.app.select_single("CrossFadeImage", objectName="backgroundImage")
tests/autopilot/unity8/shell/tests/__init__.py+1
@@ -148,6 +148,7 @@ class UnityTestCase(AutopilotTestCase):
148 if model() != "Desktop":
148 if model() != "Desktop":
149 from autopilot.input import _uinput
149 from autopilot.input import _uinput
150 _uinput._touch_device = _uinput.create_touch_device()
150 _uinput._touch_device = _uinput.create_touch_device()
151+ self.addCleanup(_uinput._touch_device.close)
151 ####
152 ####
152
153
153 self.touch = Touch.create()
154 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/QMenuModel/unitymenumodel.cpp+3
@@ -143,6 +143,9 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const
143
143
144QVariantMap UnityMenuModel::rowData(int row) const
144QVariantMap UnityMenuModel::rowData(int row) const
145{
145{
146+ if (m_modelData.count() <= row) {
147+ return QVariantMap();
148+ }
146 return m_modelData[row].toMap()["rowData"].toMap();
149 return m_modelData[row].toMap()["rowData"].toMap();
147}
150}
148
151
tests/mocks/Unity/Indicators/CMakeLists.txt+2
@@ -7,12 +7,14 @@ include_directories(
7 ${CMAKE_CURRENT_SOURCE_DIR}
7 ${CMAKE_CURRENT_SOURCE_DIR}
8 ${CMAKE_CURRENT_BINARY_DIR}
8 ${CMAKE_CURRENT_BINARY_DIR}
9 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators
9 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators
10+ ${CMAKE_SOURCE_DIR}/tests/mocks/QMenuModel
10)
11)
11
12
12set(IndicatorsFakeQml_SOURCES
13set(IndicatorsFakeQml_SOURCES
13 fakeplugin.cpp
14 fakeplugin.cpp
14 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
15 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
15 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
16 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
17+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelstack.cpp
16 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/visibleindicatorsmodel.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/visibleindicatorsmodel.cpp
17)
19)
18
20
tests/mocks/Unity/Indicators/IndicatorBase.qml+2−1
@@ -26,8 +26,9 @@ Item {
26 //const
26 //const
27 property string busName
27 property string busName
28 property string actionsObjectPath
28 property string actionsObjectPath
29+ property string menuObjectPath
29 property string rootMenuType: "com.canonical.indicator.root"
30 property string rootMenuType: "com.canonical.indicator.root"
30− property string deviceMenuObjectPath
31+ property string deviceMenuObjectPath: menuObjectPath
31
32
32 property var menuModel: UnityMenuModel {}
33 property var menuModel: UnityMenuModel {}
33
34
tests/mocks/Unity/Indicators/IndicatorWidget.qmldeleted−28
@@ -1,28 +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 Lesser 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 Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Nick Dedekind <nick.dedekind@canonical.com>
18− */
19−
20−import QtQuick 2.0
21−import Unity.Indicators 0.1 as Indicators
22−
23−IndicatorBase {
24− id: indicatorWidget
25−
26− enabled: false
27− property int iconSize: height
28−}
plugins/Unity/Indicators/qml/IndicatorWidget.qml → tests/mocks/Unity/Indicators/MenuItemFactory.qmlrenamed+15−5
@@ -18,11 +18,21 @@
18 */
18 */
19
19
20import QtQuick 2.0
20import QtQuick 2.0
21−import Unity.Indicators 0.1 as Indicators
21+import Ubuntu.Components.ListItems 0.1 as ListItem
22
22
23−IndicatorBase {
23+Item {
24− id: indicatorWidget
24+ id: menuFactory
25
25
26− enabled: false
26+ property var menuModel: null
27− property int iconSize: height
27+
28+ Component {
29+ id: standardMenu;
30+ ListItem.Standard {
31+ objectName: "standardMenu"
32+ }
33+ }
34+
35+ function load(modelData) {
36+ return standardMenu;
37+ }
28}
38}
tests/mocks/Unity/Indicators/fakeplugin.cpp+2
@@ -25,6 +25,7 @@
25// local
25// local
26#include "indicators.h"
26#include "indicators.h"
27#include "menucontentactivator.h"
27#include "menucontentactivator.h"
28+#include "unitymenumodelstack.h"
28#include "visibleindicatorsmodel.h"
29#include "visibleindicatorsmodel.h"
29
30
30void IndicatorsFakePlugin::registerTypes(const char * uri)
31void IndicatorsFakePlugin::registerTypes(const char * uri)
@@ -32,6 +33,7 @@ void IndicatorsFakePlugin::registerTypes(const char * uri)
32 Q_INIT_RESOURCE(indicators_fake);
33 Q_INIT_RESOURCE(indicators_fake);
33
34
34 qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
35 qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
36+ qmlRegisterType<UnityMenuModelStack>(uri, 0, 1, "UnityMenuModelStack");
35 qmlRegisterType<VisibleIndicatorsModel>(uri, 0, 1, "VisibleIndicatorsModel");
37 qmlRegisterType<VisibleIndicatorsModel>(uri, 0, 1, "VisibleIndicatorsModel");
36
38
37 qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
39 qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
tests/mocks/Unity/Indicators/qmldir+1−1
@@ -3,7 +3,7 @@ plugin IndicatorsFakeQml
3
3
4CachedUnityMenuModel 0.1 CachedUnityMenuModel.qml
4CachedUnityMenuModel 0.1 CachedUnityMenuModel.qml
5IndicatorBase 0.1 IndicatorBase.qml
5IndicatorBase 0.1 IndicatorBase.qml
6−IndicatorWidget 0.1 IndicatorWidget.qml
7IndicatorsModel 0.1 IndicatorsModel.qml
6IndicatorsModel 0.1 IndicatorsModel.qml
8FakeMenuPage 0.1 FakeMenuPage.qml
7FakeMenuPage 0.1 FakeMenuPage.qml
8+MenuItemFactory 0.1 MenuItemFactory.qml
9RootActionState 0.1 RootActionState.qml
9RootActionState 0.1 RootActionState.qml
tests/mocks/Unity/fake_scopes.cpp+3
@@ -35,6 +35,7 @@ Scopes::Scopes(QObject *parent)
35 m_roles[Scopes::RoleScope] = "scope";
35 m_roles[Scopes::RoleScope] = "scope";
36 m_roles[Scopes::RoleId] = "id";
36 m_roles[Scopes::RoleId] = "id";
37 m_roles[Scopes::RoleVisible] = "visible";
37 m_roles[Scopes::RoleVisible] = "visible";
38+ m_roles[Scopes::RoleTitle] = "title";
38
39
39 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
40 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
40 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
41 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
110 return QVariant::fromValue(scope->id());
111 return QVariant::fromValue(scope->id());
111 } else if (role == Scopes::RoleVisible) {
112 } else if (role == Scopes::RoleVisible) {
112 return QVariant::fromValue(scope->visible());
113 return QVariant::fromValue(scope->visible());
114+ } else if (role == Scopes::RoleTitle) {
115+ return QVariant::fromValue(scope->name());
113 } else {
116 } else {
114 return QVariant();
117 return QVariant();
115 }
118 }
tests/mocks/Unity/fake_scopes.h+2−1
@@ -41,7 +41,8 @@ public:
41 enum Roles {
41 enum Roles {
42 RoleScope,
42 RoleScope,
43 RoleId,
43 RoleId,
44− RoleVisible
44+ RoleVisible,
45+ RoleTitle
45 };
46 };
46
47
47 Q_INVOKABLE int rowCount(const QModelIndex& parent = QModelIndex()) const;
48 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
40target_link_libraries(listviewwithpageheadertestsectionexternalmodelExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
40target_link_libraries(listviewwithpageheadertestsectionexternalmodelExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
41add_custom_target(testlistviewwithpageheadersectionexternalmodel ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestsectionexternalmodelExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheadersectionexternalmodel.xml,xunitxml -o -,txt)
41add_custom_target(testlistviewwithpageheadersectionexternalmodel ${CMAKE_CURRENT_BINARY_DIR}/listviewwithpageheadertestsectionexternalmodelExec -o ${CMAKE_BINARY_DIR}/testlistviewwithpageheadersectionexternalmodel.xml,xunitxml -o -,txt)
42
42
43−add_executable(verticaljournaltestExec verticaljournaltest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/verticaljournal.cpp)
43+macro(add_journal_try_test FILENAME)
44−qt5_use_modules(verticaljournaltestExec Test Core Qml)
44+ # Capitalite the first letter for the test/try name targets
45−target_link_libraries(verticaljournaltestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
45+ string(SUBSTRING ${FILENAME} 0 1 FIRST_LETTER)
46−add_custom_target(testVerticalJournal ${CMAKE_CURRENT_BINARY_DIR}/verticaljournaltestExec -o ${CMAKE_BINARY_DIR}/testVerticalJournal.xml,xunitxml -o -,txt)
46+ string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
47+ string(REGEX REPLACE "^.(.*)" "${FIRST_LETTER}\\1" CUSTOM_TARGET_NAME "${FILENAME}")
47
48
49+ add_executable(${FILENAME}testExec
50+ ${FILENAME}test.cpp
51+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
48−add_executable(verticaljournaltryExec verticaljournaltry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/verticaljournal.cpp)
52+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractjournal.cpp
53+ )
49−qt5_use_modules(verticaljournaltryExec Test Core Qml)
54+ qt5_use_modules(${FILENAME}testExec Test Core Qml)
50−target_link_libraries(verticaljournaltryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
55+ target_link_libraries(${FILENAME}testExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
51−add_custom_target(tryVerticalJournal ${CMAKE_CURRENT_BINARY_DIR}/verticaljournaltryExec -o ${CMAKE_BINARY_DIR}/testVerticalJournal.xml,xunitxml -o -,txt)
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)
52
78
53set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
79set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
54add_qml_test(. listviewwithpageheaderqml IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
80add_qml_test(. listviewwithpageheaderqml IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
@@ -57,4 +83,5 @@ add_dependencies(qmluitests testlistviewwithpageheader)
57add_dependencies(qmluitests testlistviewwithpageheadersection)
83add_dependencies(qmluitests testlistviewwithpageheadersection)
58add_dependencies(qmluitests testlistviewwithpageheadersectionexternalmodel)
84add_dependencies(qmluitests testlistviewwithpageheadersectionexternalmodel)
59add_dependencies(qmluitests testlistviewwithpageheaderqml)
85add_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+37−5
@@ -30,7 +30,7 @@
30
30
31#include "verticaljournal.h"
31#include "verticaljournal.h"
32
32
33−class QHeightModel : public QAbstractListModel {
33+class HeightModel : public QAbstractListModel {
34public:
34public:
35 QHash<int, QByteArray> roleNames() const
35 QHash<int, QByteArray> roleNames() const
36 {
36 {
@@ -127,7 +127,7 @@ private Q_SLOTS:
127 view->setResizeMode(QQuickView::SizeRootObjectToView);
127 view->setResizeMode(QQuickView::SizeRootObjectToView);
128 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
128 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
129
129
130− model = new QHeightModel();
130+ model = new HeightModel();
131 QStringList heightList;
131 QStringList heightList;
132 heightList << "100" << "50" << "125" << "10" << "40" << "70" << "200" << "110" << "160" << "20" << "20" << "65" << "80" << "200" << "300" << "130" << "400" << "300" << "500" << "10";
132 heightList << "100" << "50" << "125" << "10" << "40" << "70" << "200" << "110" << "160" << "20" << "20" << "65" << "80" << "200" << "300" << "130" << "400" << "300" << "500" << "10";
133 model->setStringList(heightList);
133 model->setStringList(heightList);
@@ -323,7 +323,7 @@ private Q_SLOTS:
323
323
324 void testChangeModel()
324 void testChangeModel()
325 {
325 {
326− QHeightModel *model2 = new QHeightModel();
326+ HeightModel *model2 = new HeightModel();
327 QStringList list2;
327 QStringList list2;
328 list2 << "100" << "50" << "25" << "25" << "50" << "50";
328 list2 << "100" << "50" << "25" << "25" << "50" << "50";
329 model2->setStringList(list2);
329 model2->setStringList(list2);
@@ -359,9 +359,41 @@ private Q_SLOTS:
359 verifyItem(vj->m_columnVisibleItems[1][1], 3, 160, 60, true);
359 verifyItem(vj->m_columnVisibleItems[1][1], 3, 160, 60, true);
360 }
360 }
361
361
362+ void testModelRemoveLastNonVisible()
363+ {
364+ model->removeLast();
365+
366+ // This is the same as checkInitialPositions but
367+ // with a different implicitHeight since there's an item less
368+ // in the model
369+ QTRY_COMPARE(vj->m_columnVisibleItems.count(), 3);
370+ QTRY_COMPARE(vj->m_columnVisibleItems[0].count(), 5);
371+ QTRY_COMPARE(vj->m_columnVisibleItems[1].count(), 7);
372+ QTRY_COMPARE(vj->m_columnVisibleItems[2].count(), 6);
373+ verifyItem(vj->m_columnVisibleItems[0][0], 0, 0, 0, true);
374+ verifyItem(vj->m_columnVisibleItems[1][0], 1, 160, 0, true);
375+ verifyItem(vj->m_columnVisibleItems[2][0], 2, 320, 0, true);
376+ verifyItem(vj->m_columnVisibleItems[1][1], 3, 160, 60, true);
377+ verifyItem(vj->m_columnVisibleItems[1][2], 4, 160, 80, true);
378+ verifyItem(vj->m_columnVisibleItems[0][1], 5, 0, 110, true);
379+ verifyItem(vj->m_columnVisibleItems[1][3], 6, 160, 130, true);
380+ verifyItem(vj->m_columnVisibleItems[2][1], 7, 320, 135, true);
381+ verifyItem(vj->m_columnVisibleItems[0][2], 8, 0, 190, true);
382+ verifyItem(vj->m_columnVisibleItems[2][2], 9, 320, 255, true);
383+ verifyItem(vj->m_columnVisibleItems[2][3], 10, 320, 285, true);
384+ verifyItem(vj->m_columnVisibleItems[2][4], 11, 320, 315, true);
385+ verifyItem(vj->m_columnVisibleItems[1][4], 12, 160, 340, true);
386+ verifyItem(vj->m_columnVisibleItems[0][3], 13, 0, 360, true);
387+ verifyItem(vj->m_columnVisibleItems[2][5], 14, 320, 390, true);
388+ verifyItem(vj->m_columnVisibleItems[1][5], 15, 160, 430, false);
389+ verifyItem(vj->m_columnVisibleItems[0][4], 16, 0, 570, false);
390+ verifyItem(vj->m_columnVisibleItems[1][6], 17, 160, 570, false);
391+ QTRY_COMPARE(vj->implicitHeight(), 970. + 1. * 970. / 18.);
392+ }
393+
362 void testModelAppendRemoveLast()
394 void testModelAppendRemoveLast()
363 {
395 {
364− QHeightModel *model2 = new QHeightModel();
396+ HeightModel *model2 = new HeightModel();
365 QStringList list2;
397 QStringList list2;
366 list2 << "100" << "50" << "25" << "25" << "50" << "50";
398 list2 << "100" << "50" << "25" << "25" << "50" << "50";
367 model2->setStringList(list2);
399 model2->setStringList(list2);
@@ -430,7 +462,7 @@ private Q_SLOTS:
430private:
462private:
431 QQuickView *view;
463 QQuickView *view;
432 VerticalJournal *vj;
464 VerticalJournal *vj;
433− QHeightModel *model;
465+ HeightModel *model;
434};
466};
435
467
436QTEST_MAIN(VerticalJournalTest)
468QTEST_MAIN(VerticalJournalTest)
tests/qmltests/CMakeLists.txt+7−3
@@ -36,9 +36,10 @@ add_qml_test(Components Revealer)
36add_qml_test(Components Showable)
36add_qml_test(Components Showable)
37add_qml_test(Components Stage)
37add_qml_test(Components Stage)
38add_qml_test(Components Tile)
38add_qml_test(Components Tile)
39−add_qml_test(Components PageHeader)
39+add_qml_test(Components PageHeaderLabel)
40+add_qml_test(Dash Card)
41+add_qml_test(Dash CardHeader)
40add_qml_test(Dash Dash)
42add_qml_test(Dash Dash)
41−add_qml_test(Dash DashBar IMPORT_PATHS ${CMAKE_CURRENT_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
42add_qml_test(Dash DashContent)
43add_qml_test(Dash DashContent)
43add_qml_test(Dash DashPreview)
44add_qml_test(Dash DashPreview)
44add_qml_test(Dash GenericPreview)
45add_qml_test(Dash GenericPreview)
@@ -47,7 +48,7 @@ add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMA
47add_qml_test(Dash/Apps RunningApplicationsGrid)
48add_qml_test(Dash/Apps RunningApplicationsGrid)
48add_qml_test(Dash/Apps AppPreview)
49add_qml_test(Dash/Apps AppPreview)
49add_qml_test(Dash/Movie MoviePreview)
50add_qml_test(Dash/Movie MoviePreview)
50−add_qml_test(Dash/Movie MusicPreview)
51+add_qml_test(Dash/Music MusicPreview)
51add_qml_test(Greeter Lockscreen ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/full")
52add_qml_test(Greeter Lockscreen ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/full")
52add_qml_test(Greeter Tablet ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/full")
53add_qml_test(Greeter Tablet ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/full")
53add_qml_test(Greeter Phone ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/single")
54add_qml_test(Greeter Phone ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/single")
@@ -63,4 +64,7 @@ add_qml_test(Panel MenuContent)
63add_qml_test(Panel Panel)
64add_qml_test(Panel Panel)
64add_qml_test(Panel SearchIndicator)
65add_qml_test(Panel SearchIndicator)
65add_qml_test(Panel/Indicators DefaultIndicatorWidget ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
66add_qml_test(Panel/Indicators DefaultIndicatorWidget ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
67+add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
68+add_qml_test(Panel/Indicators MenuItemFactory)
69+add_qml_test(Panel/Indicators MessageMenuItemFactory)
66add_qml_test(SideStage SideStage)
70add_qml_test(SideStage SideStage)
tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qmlrenamed+2−2
@@ -31,7 +31,7 @@ Item {
31 }
31 }
32
32
33 UT.UnityTestCase {
33 UT.UnityTestCase {
34− name: "PageHeaderTest"
34+ name: "PageHeaderLabelTest"
35 when: windowShown
35 when: windowShown
36
36
37 property alias searchEnabled : pageHeader.searchEntryEnabled
37 property alias searchEnabled : pageHeader.searchEntryEnabled
@@ -204,7 +204,7 @@ Item {
204 anchors.fill: parent
204 anchors.fill: parent
205 spacing: units.gu(1)
205 spacing: units.gu(1)
206
206
207− PageHeader {
207+ PageHeaderLabel {
208 id: pageHeader
208 id: pageHeader
209 anchors {
209 anchors {
210 left: parent.left
210 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 {
23 id: fakeScopeView
23 id: fakeScopeView
24
24
25 property alias backColor : back.color
25 property alias backColor : back.color
26+ property var categoryView: null
26
27
27 onEndReached: {
28 onEndReached: {
28 if (scope != undefined && shell != undefined && shell.scopeStatus != undefined) {
29 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 {
25 width: units.gu(40)
25 width: units.gu(40)
26 height: units.gu(80)
26 height: units.gu(80)
27
27
28− property ListModel searchHistory : ListModel {}
29−
30 Dash {
28 Dash {
31 id: dash
29 id: dash
32 anchors.fill: parent
30 anchors.fill: parent
@@ -83,14 +81,14 @@ Item {
83 // wait for scopes to load
81 // wait for scopes to load
84 tryCompare(scopes, "loaded", true);
82 tryCompare(scopes, "loaded", true);
85
83
86− var dashbar = findChild(dash, "dashbar");
84+ var tabbar = findChild(dash, "tabbar");
87− verify(dashbar != undefined)
85+ verify(tabbar != undefined)
88 var dashContent = findChild(dash, "dashContent");
86 var dashContent = findChild(dash, "dashContent");
89 var current_index = dashContent.currentIndex;
87 var current_index = dashContent.currentIndex;
90
88
91 dash.setCurrentScope(data.tag, true /* animate */, false /* reset */);
89 dash.setCurrentScope(data.tag, true /* animate */, false /* reset */);
92 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
90 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);
94 }
92 }
95
93
96 function test_show_scope_on_load_data() {
94 function test_show_scope_on_load_data() {
@@ -127,12 +125,12 @@ Item {
127 // wait for scopes to load
125 // wait for scopes to load
128 tryCompare(scopes, "loaded", true);
126 tryCompare(scopes, "loaded", true);
129
127
130− var dashbar = findChild(dash, "dashbar");
128+ var tabbar = findChild(dash, "tabbar");
131− verify(dashbar != undefined)
129+ verify(tabbar != undefined)
132 var dashContent = findChild(dash, "dashContent");
130 var dashContent = findChild(dash, "dashContent");
133 var current_index = dashContent.currentIndex;
131 var current_index = dashContent.currentIndex;
134
132
135− dashbar.itemSelected(data.visualIndex);
133+ tabbar.selectedIndex = data.visualIndex;
136 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
134 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
137 }
135 }
138 }
136 }
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 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import "../../../qml/Dash"
19import "../../../qml/Dash"
20+import "../../../qml/Components"
20import Ubuntu.Components 0.1
21import Ubuntu.Components 0.1
21import Unity 0.1
22import Unity 0.1
22import Unity.Test 0.1 as UT
23import Unity.Test 0.1 as UT
24+import Utils 0.1
23
25
24Item {
26Item {
25 id: shell
27 id: shell
@@ -54,11 +56,13 @@ Item {
54 id: dashContent
56 id: dashContent
55 anchors.fill: parent
57 anchors.fill: parent
56
58
59+ model: SortFilterProxyModel {
57− model: scopesModel
60+ model: scopesModel
61+ }
58 scopes : scopesModel
62 scopes : scopesModel
59
63
60 scopeMapper : scopeDelegateMapper
64 scopeMapper : scopeDelegateMapper
61− searchHistory: ListModel {}
65+ searchHistory: SearchHistoryModel {}
62 }
66 }
63
67
64 ScopeDelegateMapper {
68 ScopeDelegateMapper {
@@ -109,9 +113,10 @@ Item {
109
113
110 UT.UnityTestCase {
114 UT.UnityTestCase {
111 name: "DashContent"
115 name: "DashContent"
112− when: windowShown
116+ when: scopesModel.loaded
113
117
114 function init() {
118 function init() {
119+ scopesModel.clear();
115 scopesModel.load();
120 scopesModel.load();
116 }
121 }
117
122
@@ -268,6 +273,65 @@ Item {
268 tryCompare(scopesModel.get(2), "isActive", data.active2);
273 tryCompare(scopesModel.get(2), "isActive", data.active2);
269 }
274 }
270
275
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+
271 function checkFlickMovingAndNotInteractive()
335 function checkFlickMovingAndNotInteractive()
272 {
336 {
273 var dashContentList = findChild(dashContent, "dashContentList");
337 var dashContentList = findChild(dashContent, "dashContentList");
@@ -282,7 +346,6 @@ Item {
282 return dashContentList.currentItem.moving && !dashContentList.interactive;
346 return dashContentList.currentItem.moving && !dashContentList.interactive;
283 }
347 }
284
348
285−
286 function test_hswipe_disabled_vswipe() {
349 function test_hswipe_disabled_vswipe() {
287 var dashContentList = findChild(dashContent, "dashContentList");
350 var dashContentList = findChild(dashContent, "dashContentList");
288
351
tests/qmltests/Dash/tst_GenericScopeView.qml+57−27
@@ -41,54 +41,83 @@ Item {
41 signal mainStageFocusedApplicationChanged()
41 signal mainStageFocusedApplicationChanged()
42 }
42 }
43
43
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+
44 GenericScopeView {
62 GenericScopeView {
45 id: genericScopeView
63 id: genericScopeView
46 anchors.fill: parent
64 anchors.fill: parent
47− searchHistory: SearchHistoryModel {}
65+ previewListView: previewListView
66+ openEffect: openEffect
67+ pageHeader: pageHeader
68+ tabBarHeight: pageHeader.implicitHeight
48
69
49 UT.UnityTestCase {
70 UT.UnityTestCase {
50 name: "GenericScopeView"
71 name: "GenericScopeView"
51 when: scopes.loaded
72 when: scopes.loaded
52
73
53− function test_isCurrent() {
74+ function test_isCurrent() {
54− var pageHeader = findChild(genericScopeView, "pageHeader");
55− var previewListView = findChild(genericScopeView, "previewListView");
56 genericScopeView.isCurrent = true
75 genericScopeView.isCurrent = true
57 pageHeader.searchQuery = "test"
76 pageHeader.searchQuery = "test"
58 previewListView.open = true
77 previewListView.open = true
59 genericScopeView.isCurrent = false
78 genericScopeView.isCurrent = false
60 tryCompare(pageHeader, "searchQuery", "")
79 tryCompare(pageHeader, "searchQuery", "")
61− tryCompare(genericScopeView, "previewShown", false);
80+ tryCompare(previewListView, "open", false);
62 }
81 }
63
82
64 function test_showDash() {
83 function test_showDash() {
65− var previewListView = findChild(genericScopeView, "previewListView");
66 previewListView.open = true;
84 previewListView.open = true;
85+ tryCompare(openEffect, "live", true);
67 scopes.get(0).showDash();
86 scopes.get(0).showDash();
68− tryCompare(genericScopeView, "previewShown", false);
87+ tryCompare(previewListView, "open", false);
88+ tryCompare(openEffect, "live", true);
69 }
89 }
70
90
71 function test_hideDash() {
91 function test_hideDash() {
72− var previewListView = findChild(genericScopeView, "previewListView");
73 previewListView.open = true;
92 previewListView.open = true;
74 scopes.get(0).hideDash();
93 scopes.get(0).hideDash();
75− tryCompare(genericScopeView, "previewShown", false);
94+ tryCompare(previewListView, "open", false);
76 }
95 }
77
96
78 function openPreview() {
97 function openPreview() {
79 var categoryListView = findChild(genericScopeView, "categoryListView");
98 var categoryListView = findChild(genericScopeView, "categoryListView");
80 categoryListView.positionAtBeginning();
99 categoryListView.positionAtBeginning();
81
100
101+ tryCompareFunction(function() {
102+ var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
103+ return tile != undefined;
104+ },
105+ true);
82 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
106 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
83 mouseClick(tile, tile.width / 2, tile.height / 2);
107 mouseClick(tile, tile.width / 2, tile.height / 2);
84− var openEffect = findChild(genericScopeView, "openEffect");
108+ tryCompare(previewListView, "open", true);
85 tryCompare(openEffect, "gap", 1);
109 tryCompare(openEffect, "gap", 1);
86 }
110 }
87
111
88 function checkArrowPosition(index) {
112 function checkArrowPosition(index) {
113+ tryCompareFunction(function() {
114+ var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
115+ return tile != undefined;
116+ },
117+ true);
89 var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
118 var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
90 var tileCenter = tile.x + tile.width/2;
119 var tileCenter = tile.x + tile.width/2;
91− var pointerArrow = findChild(genericScopeView, "pointerArrow");
120+ var pointerArrow = findChild(previewListView, "pointerArrow");
92 var pointerArrowCenter = pointerArrow.x + pointerArrow.width/2;
121 var pointerArrowCenter = pointerArrow.x + pointerArrow.width/2;
93 compare(pointerArrowCenter, tileCenter, "Pointer did not move to tile");
122 compare(pointerArrowCenter, tileCenter, "Pointer did not move to tile");
94 }
123 }
@@ -97,9 +126,7 @@ Item {
97 var closePreviewMouseArea = findChild(genericScopeView, "closePreviewMouseArea");
126 var closePreviewMouseArea = findChild(genericScopeView, "closePreviewMouseArea");
98 mouseClick(closePreviewMouseArea, closePreviewMouseArea.width / 2, closePreviewMouseArea.height / 2);
127 mouseClick(closePreviewMouseArea, closePreviewMouseArea.width / 2, closePreviewMouseArea.height / 2);
99
128
100− var previewListView = findChild(genericScopeView, "previewListView");
101 tryCompare(previewListView, "open", false);
129 tryCompare(previewListView, "open", false);
102− var openEffect = findChild(genericScopeView, "openEffect");
103 tryCompare(openEffect, "gap", 0);
130 tryCompare(openEffect, "gap", 0);
104
131
105 var categoryListView = findChild(genericScopeView, "categoryListView");
132 var categoryListView = findChild(genericScopeView, "categoryListView");
@@ -108,13 +135,12 @@ Item {
108 }
135 }
109
136
110 function test_previewOpenClose() {
137 function test_previewOpenClose() {
111− var previewListView = findChild(genericScopeView, "previewListView");
112 tryCompare(previewListView, "open", false);
138 tryCompare(previewListView, "open", false);
113
139
114 openPreview();
140 openPreview();
115
141
116 // check for it opening successfully
142 // check for it opening successfully
117− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
143+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
118 tryCompareFunction(function() {
144 tryCompareFunction(function() {
119 var parts = currentPreviewItem.source.toString().split("/");
145 var parts = currentPreviewItem.source.toString().split("/");
120 var name = parts[parts.length - 1];
146 var name = parts[parts.length - 1];
@@ -138,26 +164,23 @@ Item {
138 var categoryListView = findChild(genericScopeView, "categoryListView");
164 var categoryListView = findChild(genericScopeView, "categoryListView");
139 categoryListView.positionAtBeginning();
165 categoryListView.positionAtBeginning();
140 waitForRendering(categoryListView);
166 waitForRendering(categoryListView);
141− categoryListView.flick(0, -units.gu(80));
167+ categoryListView.flick(0, -units.gu(60));
142 tryCompare(categoryListView.flicking, false);
168 tryCompare(categoryListView.flicking, false);
143
169
144 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
170 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
145 mouseClick(tile, tile.width / 2, tile.height - 1);
171 mouseClick(tile, tile.width / 2, tile.height - 1);
146− var openEffect = findChild(genericScopeView, "openEffect");
147 tryCompare(openEffect, "gap", 1);
172 tryCompare(openEffect, "gap", 1);
148
173
149− var pageHeader = findChild(genericScopeView, "pageHeader");
150 verify(openEffect.positionPx >= pageHeader.height + categoryListView.stickyHeaderHeight);
174 verify(openEffect.positionPx >= pageHeader.height + categoryListView.stickyHeaderHeight);
151 }
175 }
152
176
153 function test_previewCycle() {
177 function test_previewCycle() {
154− var previewListView = findChild(genericScopeView, "previewListView");
155 tryCompare(previewListView, "open", false);
178 tryCompare(previewListView, "open", false);
156
179
157 openPreview();
180 openPreview();
158
181
159 // wait for it to be loaded
182 // wait for it to be loaded
160− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
183+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
161 tryCompareFunction(function() {
184 tryCompareFunction(function() {
162 var parts = currentPreviewItem.source.toString().split("/");
185 var parts = currentPreviewItem.source.toString().split("/");
163 var name = parts[parts.length - 1];
186 var name = parts[parts.length - 1];
@@ -179,7 +202,7 @@ Item {
179 previewListView.height / 2);
202 previewListView.height / 2);
180
203
181 // wait for it to be loaded
204 // wait for it to be loaded
182− var nextPreviewItem = findChild(genericScopeView, "previewLoader" + i);
205+ var nextPreviewItem = findChild(previewListView, "previewLoader" + i);
183 tryCompareFunction(function() {
206 tryCompareFunction(function() {
184 var parts = nextPreviewItem.source.toString().split("/");
207 var parts = nextPreviewItem.source.toString().split("/");
185 var name = parts[parts.length - 1];
208 var name = parts[parts.length - 1];
@@ -206,11 +229,12 @@ Item {
206
229
207 function test_show_spinner() {
230 function test_show_spinner() {
208 openPreview();
231 openPreview();
209− var previewListView = findChild(genericScopeView, "previewListView");
232+ var previewLoader = findChild(previewListView, "previewLoader0");
210− var previewLoader = findChild(genericScopeView, "previewLoader0");
233+ tryCompare(previewLoader, "progress", 1.0);
234+ tryCompareFunction(function() { return previewLoader.item != undefined; }, true);
211
235
212 previewLoader.item.showProcessingAction = true;
236 previewLoader.item.showProcessingAction = true;
213− var waitingForAction = findChild(genericScopeView, "waitingForActionMouseArea");
237+ var waitingForAction = findChild(previewListView, "waitingForActionMouseArea");
214 tryCompare(waitingForAction, "enabled", true);
238 tryCompare(waitingForAction, "enabled", true);
215 previewLoader.closePreviewSpinner();
239 previewLoader.closePreviewSpinner();
216 tryCompare(waitingForAction, "enabled", false);
240 tryCompare(waitingForAction, "enabled", false);
@@ -252,12 +276,10 @@ Item {
252 tryCompareFunction(function() { return findChild(genericScopeView, "carouselDelegate") != undefined; }, true);
276 tryCompareFunction(function() { return findChild(genericScopeView, "carouselDelegate") != undefined; }, true);
253 var tile = findChild(genericScopeView, "carouselDelegate");
277 var tile = findChild(genericScopeView, "carouselDelegate");
254 mouseClick(tile, tile.width / 2, tile.height / 2);
278 mouseClick(tile, tile.width / 2, tile.height / 2);
255− var openEffect = findChild(genericScopeView, "openEffect");
256 tryCompare(openEffect, "gap", 1);
279 tryCompare(openEffect, "gap", 1);
257
280
258 // check for it opening successfully
281 // check for it opening successfully
259− var previewListView = findChild(genericScopeView, "previewListView");
260− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
282+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
261 tryCompareFunction(function() {
283 tryCompareFunction(function() {
262 var parts = currentPreviewItem.source.toString().split("/");
284 var parts = currentPreviewItem.source.toString().split("/");
263 var name = parts[parts.length - 1];
285 var name = parts[parts.length - 1];
@@ -299,6 +321,14 @@ Item {
299
321
300 categoryListView.positionAtBeginning();
322 categoryListView.positionAtBeginning();
301
323
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+
302 var header0 = findChild(genericScopeView, "dashSectionHeader0")
332 var header0 = findChild(genericScopeView, "dashSectionHeader0")
303 var category0 = findChild(genericScopeView, "dashCategory0")
333 var category0 = findChild(genericScopeView, "dashCategory0")
304 mouseClick(header0, header0.width / 2, header0.height / 2);
334 mouseClick(header0, header0.width / 2, header0.height / 2);
tests/qmltests/Notifications/tst_Notifications.qml+4−11
@@ -141,7 +141,6 @@ Row {
141 id: notifications
141 id: notifications
142
142
143 margin: units.gu(1)
143 margin: units.gu(1)
144− fullHeight: parent.height
145
144
146 anchors.fill: parent
145 anchors.fill: parent
147 model: mockModel
146 model: mockModel
@@ -226,7 +225,6 @@ Row {
226 { id: "nada_id", label: "Nada"}],
225 { id: "nada_id", label: "Nada"}],
227 summaryVisible: true,
226 summaryVisible: true,
228 bodyVisible: true,
227 bodyVisible: true,
229− interactiveAreaEnabled: false,
230 iconVisible: true,
228 iconVisible: true,
231 shapedIcon: true,
229 shapedIcon: true,
232 nonShapedIcon: false,
230 nonShapedIcon: false,
@@ -245,7 +243,6 @@ Row {
245 actions: [],
243 actions: [],
246 summaryVisible: true,
244 summaryVisible: true,
247 bodyVisible: false,
245 bodyVisible: false,
248− interactiveAreaEnabled: false,
249 iconVisible: false,
246 iconVisible: false,
250 shapedIcon: false,
247 shapedIcon: false,
251 nonShapedIcon: false,
248 nonShapedIcon: false,
@@ -283,7 +280,6 @@ Row {
283 actions: [{ id: "reply_id", label: "Dummy"}],
280 actions: [{ id: "reply_id", label: "Dummy"}],
284 summaryVisible: true,
281 summaryVisible: true,
285 bodyVisible: true,
282 bodyVisible: true,
286− interactiveAreaEnabled: true,
287 iconVisible: true,
283 iconVisible: true,
288 shapedIcon: true,
284 shapedIcon: true,
289 nonShapedIcon: false,
285 nonShapedIcon: false,
@@ -303,7 +299,6 @@ Row {
303 { id: "reject_id", label: "Reject"}],
299 { id: "reject_id", label: "Reject"}],
304 summaryVisible: true,
300 summaryVisible: true,
305 bodyVisible: true,
301 bodyVisible: true,
306− interactiveAreaEnabled: false,
307 iconVisible: true,
302 iconVisible: true,
308 shapedIcon: true,
303 shapedIcon: true,
309 nonShapedIcon: false,
304 nonShapedIcon: false,
@@ -322,7 +317,6 @@ Row {
322 actions: [],
317 actions: [],
323 summaryVisible: true,
318 summaryVisible: true,
324 bodyVisible: true,
319 bodyVisible: true,
325− interactiveAreaEnabled: false,
326 iconVisible: true,
320 iconVisible: true,
327 shapedIcon: true,
321 shapedIcon: true,
328 nonShapedIcon: false,
322 nonShapedIcon: false,
@@ -342,7 +336,6 @@ Row {
342 actions: [],
336 actions: [],
343 summaryVisible: true,
337 summaryVisible: true,
344 bodyVisible: true,
338 bodyVisible: true,
345− interactiveAreaEnabled: false,
346 iconVisible: true,
339 iconVisible: true,
347 shapedIcon: false,
340 shapedIcon: false,
348 nonShapedIcon: true,
341 nonShapedIcon: true,
@@ -396,14 +389,14 @@ Row {
396 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
389 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
397 compare(shapedIcon.visible, data.shapedIcon, "shaped-icon visibility is incorrect")
390 compare(shapedIcon.visible, data.shapedIcon, "shaped-icon visibility is incorrect")
398 compare(nonShapedIcon.visible, data.nonShapedIcon, "non-shaped-icon visibility is incorrect")
391 compare(nonShapedIcon.visible, data.nonShapedIcon, "non-shaped-icon visibility is incorrect")
399− compare(interactiveArea.enabled, data.interactiveAreaEnabled, "check for interactive area")
400
392
401− if(data.interactiveAreaEnabled) {
393+ // test input does not fall through
402− mouseClick(notification, notification.width / 2, notification.height / 2)
394+ mouseClick(notification, notification.width / 2, notification.height / 2)
395+ if(data.type == Notification.Interactive) {
403 actionSpy.wait()
396 actionSpy.wait()
404 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
397 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
405− compare(clickThroughSpy.count, 0, "click on interactive notification fell through")
406 }
398 }
399+ compare(clickThroughSpy.count, 0, "click on notification fell through")
407
400
408 compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
401 compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
409 compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
402 compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
tests/qmltests/Panel/Indicators/tst_DefaultIndicatorPage.qmlnew file+179
@@ -0,0 +1,179 @@
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.Test 0.1 as UT
20+import QMenuModel 0.1
21+import "../../../../qml/Panel/Indicators"
22+
23+Item {
24+ id: testView
25+ width: units.gu(40)
26+ height: units.gu(70)
27+
28+ DefaultIndicatorPage {
29+ id: page
30+
31+ anchors.fill: parent
32+ contentActive: true
33+
34+ busName: "test"
35+ actionsObjectPath: "test"
36+ menuObjectPath: "test"
37+
38+ rootMenuType: ""
39+ }
40+
41+ property var fullMenuData: [{
42+ "rowData": { // 1
43+ "label": "root",
44+ "sensitive": true,
45+ "isSeparator": false,
46+ "icon": "",
47+ "type": "com.canonical.indicator.root",
48+ "ext": {},
49+ "action": "",
50+ "actionState": {},
51+ "isCheck": false,
52+ "isRadio": false,
53+ "isToggled": false,
54+ },
55+ "submenu": [{
56+ "rowData": { // 1.1
57+ "label": "menu1",
58+ "sensitive": true,
59+ "isSeparator": false,
60+ "icon": "",
61+ "type": "",
62+ "ext": {},
63+ "action": "",
64+ "actionState": {},
65+ "isCheck": false,
66+ "isRadio": false,
67+ "isToggled": false,
68+ }}, {
69+ "rowData": { // 1.2
70+ "label": "menu2",
71+ "sensitive": true,
72+ "isSeparator": false,
73+ "icon": "",
74+ "type": "",
75+ "ext": {},
76+ "action": "",
77+ "actionState": {},
78+ "isCheck": false,
79+ "isRadio": false,
80+ "isToggled": false,
81+ }}, {
82+ "rowData": { // row 1.2
83+ "label": "menu3",
84+ "sensitive": true,
85+ "isSeparator": false,
86+ "icon": "",
87+ "type": "",
88+ "ext": {},
89+ "action": "",
90+ "actionState": {},
91+ "isCheck": false,
92+ "isRadio": false,
93+ "isToggled": false,
94+ }}
95+ ]
96+ }]; // end row 1
97+
98+ property var emptySubMenuData: [{
99+ "rowData": { // 1
100+ "label": "root",
101+ "sensitive": true,
102+ "isSeparator": false,
103+ "icon": "",
104+ "type": "com.canonical.indicator.root",
105+ "ext": {},
106+ "action": "",
107+ "actionState": {},
108+ "isCheck": false,
109+ "isRadio": false,
110+ "isToggled": false,
111+ },
112+ "submenu": []
113+ }]; // end row 1
114+
115+ UT.UnityTestCase {
116+ name: "DefaultIndicatorPage"
117+
118+ function init() {
119+ page.stop();
120+ var mainMenu = findChild(page, "mainMenu");
121+ verify(mainMenu.model === undefined);
122+
123+ page.rootMenuType = "com.canonical.indicator.root";
124+ page.start();
125+
126+ verify(mainMenu.model !== null);
127+ }
128+
129+ function test_reloadData() {
130+ var mainMenu = findChild(page, "mainMenu");
131+
132+ console.log("setting empty data");
133+ page.menuModel.modelData = [];
134+ tryCompare(mainMenu, "count", 0);
135+ console.log("set empty data");
136+
137+ console.log("setting full data");
138+ page.menuModel.modelData = fullMenuData;
139+ console.log("set full data");
140+ tryCompare(mainMenu, "count", 3);
141+
142+ page.menuModel.modelData = [];
143+ tryCompare(mainMenu, "count", 0);
144+
145+ page.menuModel.modelData = fullMenuData;
146+ tryCompare(mainMenu, "count", 3);
147+ }
148+
149+ function test_traverse_rootMenuType_data() {
150+ return [
151+ { tag: "Incorrect", rootMenuType: "com.canonical.indicator", expectedCount: 0},
152+ { tag: "Correct", rootMenuType: "com.canonical.indicator.root", expectedCount: 3},
153+ ]
154+ }
155+
156+ function test_traverse_rootMenuType(data) {
157+ page.rootMenuType = data.rootMenuType;
158+ page.menuModel.modelData = fullMenuData;
159+
160+ var mainMenu = findChild(page, "mainMenu");
161+ tryCompare(mainMenu, "count", data.expectedCount);
162+ }
163+
164+ function test_empty_data() {
165+ return [
166+ { tag: "EmptyNoData", modelData: [], visible: true},
167+ { tag: "EmptySubmenu", modelData: emptySubMenuData, visible: true},
168+ { tag: "NotEmpty", modelData: fullMenuData, visible: false},
169+ ]
170+ }
171+
172+ function test_empty(data) {
173+ page.menuModel.modelData = data.modelData;
174+
175+ var emptyLabel = findChild(page, "emptyLabel");
176+ tryCompare(emptyLabel, "visible", data.visible);
177+ }
178+ }
179+}
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qmlnew file+369
@@ -0,0 +1,369 @@
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.Test 0.1 as UT
20+import QMenuModel 0.1
21+import Unity.Indicators 0.1 as Indicators
22+
23+Item {
24+ id: testView
25+ width: units.gu(40)
26+ height: units.gu(70)
27+
28+ Indicators.MenuItemFactory {
29+ id: factory
30+ menuModel: UnityMenuModel {}
31+ }
32+
33+ Loader {
34+ id: loader
35+ property int modelIndex: 0
36+ property var data
37+
38+ onLoaded: {
39+ if (item.hasOwnProperty("menuData")) {
40+ item.menuData = data;
41+ }
42+ }
43+ }
44+
45+ UT.UnityTestCase {
46+ name: "MenuItemFactory"
47+
48+ property QtObject menuData: QtObject {
49+ property string label: "root"
50+ property bool sensitive: true
51+ property bool isSeparator: false
52+ property string icon: ""
53+ property string type: ""
54+ property var ext: undefined
55+ property string action: ""
56+ property var actionState: undefined
57+ property bool isCheck: false
58+ property bool isRadio: false
59+ property bool isToggled: false
60+ }
61+
62+ function init() {
63+ menuData.label = "";
64+ menuData.sensitive = true;
65+ menuData.isSeparator = false;
66+ menuData.icon = "";
67+ menuData.type = "";
68+ menuData.ext = undefined;
69+ menuData.action = "";
70+ menuData.actionState = undefined;
71+ menuData.isCheck = false;
72+ menuData.isRadio = false;
73+ menuData.isToggled = false;
74+
75+ loader.sourceComponent = null;
76+ loader.data = undefined;
77+
78+ verify(loader.item == null);
79+ }
80+
81+ function test_createTypes_data() {
82+ return [
83+ { tag: 'volume', type: "unity.widgets.systemsettings.tablet.volumecontrol", objectName: "sliderMenu" },
84+ { tag: 'switch1', type: "unity.widgets.systemsettings.tablet.switch", objectName: "switchMenu" },
85+
86+ { tag: 'button', type: "com.canonical.indicator.button", objectName: "buttonMenu" },
87+ { tag: 'separator', type: "com.canonical.indicator.div", objectName: "divMenu" },
88+ { tag: 'section', type: "com.canonical.indicator.section", objectName: "sectionMenu" },
89+ { tag: 'progress', type: "com.canonical.indicator.progress", objectName: "progressMenu" },
90+ { tag: 'slider1', type: "com.canonical.indicator.slider", objectName: "sliderMenu" },
91+ { tag: 'switch2', type: "com.canonical.indicator.switch", objectName: "switchMenu" },
92+
93+ { tag: 'messageItem', type: "com.canonical.indicator.messages.messageitem", objectName: "messageItem" },
94+ { tag: 'sourceItem', type: "com.canonical.indicator.messages.sourceitem", objectName: "groupedMessage" },
95+
96+ { tag: 'slider2', type: "com.canonical.unity.slider", objectName: "sliderMenu" },
97+ { tag: 'switch3', type: "com.canonical.unity.switch", objectName: "switchMenu" },
98+
99+ { tag: 'mediaplayer', type: "com.canonical.unity.media-player", objectName: "standardMenu" },
100+ { tag: 'playbackitem', type: "com.canonical.unity.playback-item", objectName: "standardMenu" },
101+
102+ { tag: 'wifisection', type: "unity.widgets.systemsettings.tablet.wifisection", objectName: "wifiSection" },
103+ { tag: 'accesspoint', type: "unity.widgets.systemsettings.tablet.accesspoint", objectName: "accessPoint" },
104+
105+ { tag: 'unknown', type: "", objectName: "standardMenu"}
106+ ];
107+ }
108+
109+ function test_createTypes(data) {
110+ menuData.type = data.type;
111+
112+ loader.data = menuData;
113+ loader.sourceComponent = factory.load(menuData);
114+ tryCompareFunction(function() { return loader.item != undefined; }, true);
115+ compare(loader.item.objectName, data.objectName, "Created object name does not match intended object (" + loader.item.objectName + " != " + data.objectName + ")");
116+ }
117+
118+ function test_create_radio() {
119+ skip("No radio component");
120+ menuData.isRadio = true;
121+
122+ loader.data = menuData;
123+ loader.sourceComponent = factory.load(menuData);
124+ tryCompareFunction(function() { return loader.item != undefined; }, true);
125+ compare(loader.item.objectName, "checkableMenu", "Should have created a checkable menu");
126+ }
127+
128+ function test_create_separator() {
129+ menuData.isSeparator = true;
130+
131+ loader.data = menuData;
132+ loader.sourceComponent = factory.load(menuData);
133+ tryCompareFunction(function() { return loader.item != undefined; }, true);
134+ compare(loader.item.objectName, "divMenu", "Should have created a separator menu");
135+ }
136+
137+ function test_create_sliderMenu_data() {
138+ return [
139+ {label: "testLabel1", enabled: false, minValue: 0, maxValue: 100, value: 10.5 },
140+ {label: "testLabel2", enabled: true, minValue: 0, maxValue: 100, value: 100 },
141+ ];
142+ }
143+
144+ function test_create_sliderMenu(data) {
145+ menuData.type = "com.canonical.indicator.slider"
146+ menuData.label = data.label;
147+ menuData.sensitive = data.enabled;
148+ menuData.actionState = data.value;
149+ menuData.ext = {
150+ 'minIcon': "file:///testMinIcon",
151+ 'maxIcon': "file:///testMaxIcon",
152+ 'minValue': data.minValue,
153+ 'maxValue': data.maxValue
154+ };
155+
156+ loader.data = menuData;
157+ loader.sourceComponent = factory.load(menuData);
158+ tryCompareFunction(function() { return loader.item != undefined; }, true);
159+ compare(loader.item.objectName, "sliderMenu", "Should have created a slider menu");
160+
161+ compare(loader.item.text, data.label, "Label does not match data");
162+ compare(loader.item.minIcon, "file:///testMinIcon", "MinIcon does not match data");
163+ compare(loader.item.maxIcon, "file:///testMaxIcon", "MaxIcon does not match data");
164+ compare(loader.item.minimumValue, data.minValue, "MinValue does not match data");
165+ compare(loader.item.maximumValue, data.maxValue, "MaxValue does not match data");
166+ compare(loader.item.value, data.value, "Calue does not match data");
167+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
168+ }
169+
170+ function test_create_buttonMenu_data() {
171+ return [
172+ {label: "testLabel1", enabled: false },
173+ {label: "testLabel2", enabled: true },
174+ ];
175+ }
176+
177+ function test_create_buttonMenu(data) {
178+ menuData.type = "com.canonical.indicator.button"
179+ menuData.label = data.label;
180+ menuData.sensitive = data.enabled;
181+
182+ loader.data = menuData;
183+ loader.sourceComponent = factory.load(menuData);
184+ tryCompareFunction(function() { return loader.item != undefined; }, true);
185+ compare(loader.item.objectName, "buttonMenu", "Should have created a button menu");
186+
187+ compare(loader.item.buttonText, data.label, "Label does not match data");
188+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
189+ }
190+
191+ function test_create_sectionMenu_data() {
192+ return [
193+ {label: "testLabel1" },
194+ {label: "testLabel2" },
195+ ];
196+ }
197+
198+ function test_create_sectionMenu(data) {
199+ menuData.type = "com.canonical.indicator.section"
200+ menuData.label = data.label;
201+
202+ loader.data = menuData;
203+ loader.sourceComponent = factory.load(menuData);
204+ tryCompareFunction(function() { return loader.item != undefined; }, true);
205+ compare(loader.item.objectName, "sectionMenu", "Should have created a section menu");
206+
207+ compare(loader.item.text, data.label, "Label does not match data");
208+ }
209+
210+ function test_create_progressMenu_data() {
211+ return [
212+ {label: "testLabel1", enabled: true, value: 10 },
213+ {label: "testLabel2", enabled: false, value: 55 },
214+ ];
215+ }
216+
217+ function test_create_progressMenu(data) {
218+ menuData.type = "com.canonical.indicator.progress"
219+ menuData.label = data.label;
220+ menuData.sensitive = data.enabled;
221+ menuData.actionState = data.value;
222+
223+ loader.data = menuData;
224+ loader.sourceComponent = factory.load(menuData);
225+ tryCompareFunction(function() { return loader.item != undefined; }, true);
226+ compare(loader.item.objectName, "progressMenu", "Should have created a progress menu");
227+
228+ compare(loader.item.text, data.label, "Label does not match data");
229+ compare(loader.item.value, data.value, "Value does not match data");
230+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
231+ }
232+
233+ function test_create_standardMenu_data() {
234+ return [
235+ {label: "testLabel1", enabled: true, icon: "file:///testIcon" },
236+ {label: "testLabel2", enabled: false, icon: "file:///testIcon2"},
237+ ];
238+ }
239+
240+ function test_create_standardMenu(data) {
241+ menuData.type = ""
242+ menuData.label = data.label;
243+ menuData.sensitive = data.enabled;
244+ menuData.icon = data.icon;
245+
246+ loader.data = menuData;
247+ loader.sourceComponent = factory.load(menuData);
248+ tryCompareFunction(function() { return loader.item != undefined; }, true);
249+ compare(loader.item.objectName, "standardMenu", "Should have created a standard menu");
250+
251+ compare(loader.item.text, data.label, "Label does not match data");
252+ compare(loader.item.iconSource, data.icon, "MaxIcon does not match data");
253+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
254+ }
255+
256+ function test_create_switchMenu_data() {
257+ return [
258+ {label: "testLabel1", enabled: true, checked: false },
259+ {label: "testLabel2", enabled: false, checked: true },
260+ ];
261+ }
262+
263+ function test_create_switchMenu(data) {
264+ menuData.type = "com.canonical.indicator.switch";
265+ menuData.label = data.label;
266+ menuData.sensitive = data.enabled;
267+ menuData.icon = "file:///testIcon";
268+ menuData.isToggled = data.checked;
269+
270+ loader.data = menuData;
271+ loader.sourceComponent = factory.load(menuData);
272+ tryCompareFunction(function() { return loader.item != undefined; }, true);
273+ compare(loader.item.objectName, "switchMenu", "Should have created a standard menu");
274+
275+ compare(loader.item.text, data.label, "Label does not match data");
276+ compare(loader.item.icon, "file:///testIcon", "MaxIcon does not match data");
277+ compare(loader.item.checked, data.checked, "Checked does not match data");
278+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
279+ }
280+
281+ function test_create_wifiSection_data() {
282+ return [
283+ {label: "testLabel1", busy: false },
284+ {label: "testLabel2", busy: true },
285+ ];
286+ }
287+
288+ function test_create_wifiSection(data) {
289+ menuData.type = "unity.widgets.systemsettings.tablet.wifisection";
290+ menuData.label = data.label;
291+ menuData.ext = { 'xCanonicalBusyAction': data.busy }
292+
293+ loader.data = menuData;
294+ loader.sourceComponent = factory.load(menuData);
295+ tryCompareFunction(function() { return loader.item != undefined; }, true);
296+ compare(loader.item.objectName, "wifiSection", "Should have created a wifi section menu");
297+
298+ compare(loader.item.text, data.label, "Label does not match data");
299+ compare(loader.item.busy, data.busy, "Busy does not match data");
300+ }
301+
302+ function test_create_accessPoint_data() {
303+ return [
304+ {label: "testLabel1", enabled: true, checked: false, secure: true, adHoc: false },
305+ {label: "testLabel2", enabled: false, checked: true, secure: false, adHoc: true },
306+ ];
307+ }
308+
309+ function test_create_accessPoint(data) {
310+ menuData.type = "unity.widgets.systemsettings.tablet.accesspoint";
311+ menuData.label = data.label;
312+ menuData.sensitive = data.enabled;
313+ menuData.isToggled = data.checked;
314+ menuData.ext = {
315+ 'xCanonicalWifiApStrengthAction': "action::strength",
316+ 'xCanonicalWifiApIsSecure': data.secure,
317+ 'xCanonicalWifiApIsAdhoc': data.adHoc,
318+ };
319+
320+ loader.data = menuData;
321+ loader.sourceComponent = factory.load(menuData);
322+ tryCompareFunction(function() { return loader.item != undefined; }, true);
323+ compare(loader.item.objectName, "accessPoint", "Should have created a access point menu");
324+
325+ compare(loader.item.label, data.label, "Label does not match data");
326+ compare(loader.item.strengthAction.name, "action::strength", "Strength action incorrect");
327+ compare(loader.item.secure, data.secure, "Secure does not match data");
328+ compare(loader.item.adHoc, data.adHoc, "AdHoc does not match data");
329+ compare(loader.item.checked, data.checked, "Checked does not match data");
330+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
331+ }
332+
333+ function test_create_messageItem() {
334+ menuData.type = "com.canonical.indicator.messages.messageitem";
335+
336+ loader.data = menuData;
337+ loader.sourceComponent = factory.load(menuData);
338+ tryCompareFunction(function() { return loader.item != undefined; }, true);
339+ compare(loader.item.objectName, "messageItem", "Should have created a message menu");
340+ }
341+
342+ function test_create_groupedMessage_data() {
343+ return [
344+ {label: "testLabel1", enabled: true, count: "5", appIcon: "file:///testIcon" },
345+ {label: "testLabel2", enabled: false, count: "10", appIcon: "file:///testIcon2" },
346+ ];
347+ }
348+
349+ function test_create_groupedMessage(data) {
350+ menuData.type = "com.canonical.indicator.messages.sourceitem";
351+ menuData.label = data.label;
352+ menuData.sensitive = data.enabled;
353+ menuData.icon = data.appIcon;
354+ menuData.ext = { 'icon': data.appIcon };
355+ menuData.actionState = [data.count];
356+ menuData.isToggled = true;
357+
358+ loader.data = menuData;
359+ loader.sourceComponent = factory.load(menuData);
360+ tryCompareFunction(function() { return loader.item != undefined; }, true);
361+ compare(loader.item.objectName, "groupedMessage", "Should have created a group message menu");
362+
363+ compare(loader.item.title, data.label, "Label does not match data");
364+ compare(loader.item.count, data.count, "Count does not match data");
365+ compare(loader.item.appIcon, data.appIcon, "App Icon does not match data");
366+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
367+ }
368+ }
369+}
tests/qmltests/Panel/Indicators/tst_MessageMenuItemFactory.qmlnew file+177
@@ -0,0 +1,177 @@
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.Test 0.1 as UT
20+import QMenuModel 0.1
21+import Unity.Indicators.Messaging 0.1 as Indicators
22+
23+Item {
24+ id: testView
25+ width: units.gu(40)
26+ height: units.gu(70)
27+
28+ Indicators.MessageMenuItemFactory {
29+ id: factory
30+ menuModel: UnityMenuModel {}
31+ menuIndex: 0
32+ }
33+
34+ UT.UnityTestCase {
35+ name: "MessageMenuItemFactory"
36+
37+ property QtObject menuData: QtObject {
38+ property string label: "root"
39+ property bool sensitive: true
40+ property bool isSeparator: false
41+ property string icon: ""
42+ property string type: ""
43+ property var ext: undefined
44+ property string action: ""
45+ property var actionState: undefined
46+ property bool isCheck: false
47+ property bool isRadio: false
48+ property bool isToggled: false
49+ }
50+
51+ function init() {
52+ menuData.label = "";
53+ menuData.sensitive = true;
54+ menuData.isSeparator = false;
55+ menuData.icon = "";
56+ menuData.type = "";
57+ menuData.ext = undefined;
58+ menuData.action = "";
59+ menuData.actionState = undefined;
60+ menuData.isCheck = false;
61+ menuData.isRadio = false;
62+ menuData.isToggled = false;
63+
64+ factory.menuData = null;
65+ }
66+
67+ function test_create_simpleTextmessage_data() {
68+ return [
69+ { title: "Title1", time: new Date(2013, 10, 10).getTime()*1000, message: "This is a text message 1", avatar: "file:///avatar1", appIcon: "file:///appIcon1", enabled: true},
70+ { title: "Title2", time: new Date(2014, 12, 10).getTime()*1000, message: "This is a text message 2", avatar: "file:///avatar2", appIcon: "file:///appIcon2", enabled: false},
71+ ];
72+ }
73+
74+ function test_create_simpleTextmessage(data) {
75+ menuData.type = "com.canonical.indicator.messages.messageitem";
76+ menuData.label = data.title;
77+ menuData.sensitive = data.enabled;
78+ menuData.ext = {
79+ 'xCanonicalTime': data.time,
80+ 'xCanonicalText': data.message,
81+ 'icon': data.avatar,
82+ 'xCanonicalAppIcon': data.appIcon,
83+ };
84+ factory.menuData = menuData;
85+
86+ var loader = findChild(factory, "loader");
87+ verify(loader !== undefined);
88+
89+ tryCompare(loader.item, "objectName", "simpleTextMessage");
90+ compare(loader.item.title, data.title, "Title does not match data");
91+ compare(loader.item.time, data.time, "Time does not match data");
92+ compare(loader.item.message, data.message, "Message does not match data");
93+ compare(loader.item.avatar, data.avatar, "Avatar does not match data");
94+ compare(loader.item.appIcon, data.appIcon, "App icon does not match data");
95+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
96+ }
97+
98+ function test_create_textmessage_data() {
99+ return [
100+ { title: "Title1", time: new Date(2013, 10, 10).getTime()*1000, message: "This is a text message 1", avatar: "file:///avatar1", appIcon: "file:///appIcon1", enabled: true},
101+ { title: "Title2", time: new Date(2014, 12, 10).getTime()*1000, message: "This is a text message 2", avatar: "file:///avatar2", appIcon: "file:///appIcon2", enabled: false},
102+ ];
103+ }
104+
105+ function test_create_textmessage(data) {
106+ menuData.type = "com.canonical.indicator.messages.messageitem";
107+ menuData.label = data.title;
108+ menuData.sensitive = data.enabled;
109+ menuData.ext = {
110+ 'xCanonicalTime': data.time,
111+ 'xCanonicalText': data.message,
112+ 'icon': data.avatar,
113+ 'xCanonicalAppIcon': data.appIcon,
114+ 'xCanonicalMessageActions': [{
115+ 'parameter-type': "s",
116+ 'name': "action::reply",
117+ 'label': "Reply1"
118+ }
119+ ]
120+ };
121+ factory.menuData = menuData;
122+
123+ var loader = findChild(factory, "loader");
124+ verify(loader !== undefined);
125+
126+ tryCompare(loader.item, "objectName", "textMessage");
127+ compare(loader.item.title, data.title, "Title does not match data");
128+ compare(loader.item.time, data.time, "Time does not match data");
129+ compare(loader.item.message, data.message, "Message does not match data");
130+ compare(loader.item.avatar, data.avatar, "Avatar does not match data");
131+ compare(loader.item.appIcon, data.appIcon, "App icon does not match data");
132+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
133+ }
134+
135+
136+ function test_create_snapDecision_data() {
137+ return [
138+ { title: "Title1", time: new Date(2013, 10, 10).getTime()*1000, message: "This is a text message 1", avatar: "file:///avatar1", appIcon: "file:///appIcon1", enabled: true},
139+ { title: "Title2", time: new Date(2014, 12, 10).getTime()*1000, message: "This is a text message 2", avatar: "file:///avatar2", appIcon: "file:///appIcon2", enabled: false},
140+ ];
141+ }
142+
143+ function test_create_snapDecision(data) {
144+ menuData.type = "com.canonical.indicator.messages.messageitem";
145+ menuData.label = data.title;
146+ menuData.sensitive = data.enabled;
147+ menuData.ext = {
148+ 'xCanonicalTime': data.time,
149+ 'xCanonicalText': data.message,
150+ 'icon': data.avatar,
151+ 'xCanonicalAppIcon': data.appIcon,
152+ 'xCanonicalMessageActions': [{
153+ 'name': "action::callback",
154+ 'label': "Callback1"
155+ },{
156+ 'parameter-type': "s",
157+ 'name': "action::reply",
158+ 'label': "Reply1"
159+ }
160+ ]
161+ };
162+ factory.menuData = menuData;
163+
164+ var loader = findChild(factory, "loader");
165+ verify(loader !== undefined);
166+
167+ tryCompare(loader.item, "objectName", "snapDecision");
168+ compare(loader.item.title, data.title, "Title does not match data");
169+ compare(loader.item.time, data.time, "Time does not match data");
170+ compare(loader.item.message, data.message, "Message does not match data");
171+ compare(loader.item.avatar, data.avatar, "Avatar does not match data");
172+ compare(loader.item.appIcon, data.appIcon, "App icon does not match data");
173+ compare(loader.item.enabled, data.enabled, "Enabled does not match data");
174+ }
175+
176+ }
177+}
tests/qmltests/Panel/tst_IndicatorItem.qml−11
@@ -41,17 +41,6 @@ Rectangle {
41 tryCompareFunction(function(){return indicatorItem.opacity < 1.0}, true);
41 tryCompareFunction(function(){return indicatorItem.opacity < 1.0}, true);
42 }
42 }
43
43
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−
55 function test_empty() {
44 function test_empty() {
56 compare(indicatorItem.indicatorVisible, false, "IndicatorItem should not be visible.");
45 compare(indicatorItem.indicatorVisible, false, "IndicatorItem should not be visible.");
57 indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
46 indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
tests/qmltests/Panel/tst_IndicatorRow.qml+18−4
@@ -34,6 +34,7 @@ Item {
34 {
34 {
35 indicatorRow.state = "initial";
35 indicatorRow.state = "initial";
36 indicatorRow.currentItem = null;
36 indicatorRow.currentItem = null;
37+ indicatorRow.unitProgress = 0.0;
37 }
38 }
38
39
39 PanelBackground {
40 PanelBackground {
@@ -83,14 +84,13 @@ Item {
83 function test_current_item_commit() {
84 function test_current_item_commit() {
84 init_test();
85 init_test();
85
86
86− indicatorRow.setCurrentItem(1);
87+ indicatorRow.setCurrentItem(2);
87 indicatorRow.state = "commit";
88 indicatorRow.state = "commit";
88 tryCompare(get_indicator_item_at(0), "opacity", 0.0);
89 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(1), "opacity", 0.0);
90− tryCompare(get_indicator_item_at(2), "opacity", 0.0);
91+ tryCompare(get_indicator_item_at(2), "opacity", 1.0);
91 tryCompare(get_indicator_item_at(3), "opacity", 0.0);
92 tryCompare(get_indicator_item_at(3), "opacity", 0.0);
92 tryCompare(get_indicator_item_at(4), "opacity", 0.0);
93 tryCompare(get_indicator_item_at(4), "opacity", 0.0);
93−
94 }
94 }
95 }
95 }
96
96
@@ -166,6 +166,20 @@ Item {
166 compare(get_indicator_item_at(3).highlighted, false, "Other indicators should not highlight when in locked state");
166 compare(get_indicator_item_at(3).highlighted, false, "Other indicators should not highlight when in locked state");
167 compare(get_indicator_item_at(4).highlighted, false, "Other indicators should not highlight when in locked state");
167 compare(get_indicator_item_at(4).highlighted, false, "Other indicators should not highlight when in locked state");
168 }
168 }
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+ }
169 }
183 }
170
184
171 UT.UnityTestCase {
185 UT.UnityTestCase {
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−}