Merge lp:~larsu/ubuntu-ui-toolkit/custom-icon-lookup into lp:ubuntu-ui-toolkit

Proposed by Lars Karlitski
Status: Superseded
Proposed branch: lp:~larsu/ubuntu-ui-toolkit/custom-icon-lookup
Merge into: lp:ubuntu-ui-toolkit
Diff against target: 404 lines (+253/-64) (has conflicts)
7 files modified
debian/control (+1/-1)
modules/Ubuntu/Components/Icon10.qml (+8/-36)
modules/Ubuntu/Components/plugin/plugin.pro (+4/-0)
modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp (+224/-25)
modules/Ubuntu/Components/plugin/unitythemeiconprovider.h (+3/-0)
tests/unit_x11/tst_components/tst_components.cpp (+12/-1)
tests/unit_x11/tst_components/tst_icon.qml (+1/-1)
Text conflict in modules/Ubuntu/Components/plugin/plugin.pro
To merge this branch: bzr merge lp:~larsu/ubuntu-ui-toolkit/custom-icon-lookup
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Tim Peeters Needs Resubmitting
Albert Astals Cid (community) Needs Information
Review via email: mp+232115@code.launchpad.net

Commit message

unitythemeiconprovider: manually load icons

Previously, UnityThemeIconProvider used QIcon::fromTheme() to lookup and load icons. That function uses fdo's default icon lookup algorithm: it looks at the current theme and all parent themes for each icon in turn. We'd prefer a breadth-first search, where all icon names are looked up in the current theme before falling back to the parent themes (see lp: #1324184). This patch implements fdo's FindBestIcon() algorithm, which behaves like that.

It also now properly support rectangular icons and always returns an icon with the requested size (adjusted for preserving the aspect ratio).

Description of the change

unitythemeiconprovider: manually load icons

To post a comment you must log in.
1215. By Cris Dywan

Push Python3 dist-packages to Phone in addition to Python2.7.

Approved by Tim Peeters, PS Jenkins bot.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

This is looking good from a quick skim, could use some tests though.

Revision history for this message
Michał Sawicz (saviq) wrote :

The only problem I see with this is that because "size" does not say whether it's width or height, we'll be using non-optimal images when they are not square. But that can't really be fixed without having amended the FD.o icon theme spec.

Revision history for this message
Lars Karlitski (larsu) wrote :

Thanks for looking at this. I've removed the sizing workarounds in the Icon component (so that we can move StatusIcon users to Icon).

> The only problem I see with this is that because "size" does not say whether it's width or height, we'll be using non-optimal images when they are not square.

Right. Let's see if we run into a lot of trouble because of this. If we do, it might be time to amend the fdo spec.

1216. By Cris Dywan

Replace off white with pure white. Fixes: https://bugs.launchpad.net/bugs/1354077.

Approved by Tim Peeters, PS Jenkins bot.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1217. By Cris Dywan

Use iconSource for actions in the overflow panel. Fixes: https://bugs.launchpad.net/bugs/1354036, https://bugs.launchpad.net/bugs/1354811.

Approved by PS Jenkins bot, Tim Peeters, Nekhelesh Ramananthan.

1218. By Tim Peeters

Add header animations.

Approved by PS Jenkins bot.

1219. By Cris Dywan

Update caret visuals and position.

Approved by PS Jenkins bot, Zsombor Egri.

1220. By Florian Boucault

Fixed usage of images across the board:

* Made images as small as possible:
  - ListItemDivider6px.png
  - bubble_shadow*.png
  - header_overflow_dropshadow.png
* Removed direct references to @GU in OptionSelector.
* Button's stroke_button.png now loaded only when necessary, asynchronously and not cached.
* Removed unused ListItemDivider24px.png.

Approved by PS Jenkins bot, Zsombor Egri.

1221. By Ugo Riboni

Disable the mousearea on top of the content when using the new header, as it is useless and it interferes with other input elements in the content area. Fixes: https://bugs.launchpad.net/bugs/1358327.

Approved by PS Jenkins bot.

1222. By Florian Boucault

Page: only compile and load ToolbarItems if strictly necessary.

Approved by Zsombor Egri, PS Jenkins bot.

Revision history for this message
Albert Astals Cid (aacid) wrote :

So instead of fixing QIcon we are reimplementing it?

review: Needs Information
Revision history for this message
Tim Peeters (tpeeters) wrote :

please branch from lp:ubuntu-ui-toolkit/staging and propose the MR against that branch

review: Needs Resubmitting
1223. By Zsombor Egri

Thin divider adjustments.

Approved by Christian Dywan, PS Jenkins bot.

1225. By Lars Karlitski

Icon: remove unnecessary sizing workaround

UnityThemeIconProvider behaves correctly now for small icon sizes.

This patch also makes sure that icons are always drawn with the correct aspect
ratio. Consumers only need to supply a height, whereas they needed to set both
width and height.

In the process, the update() function was removed and is thus not called when
the width and height of the Icon changes. This causes no regressions, as Qt
doesn't reload icons with the same url anyway.

1226. By Lars Karlitski

unitythemeiconprovider: use qgetenv instead of getenv

1227. By Lars Karlitski

tst_components: set XDG_DATA_DIRS so that icons are found

1228. By Lars Karlitski

unitythemeiconprovider: don't load icons that might not be used

Only load the icon that has the best matching size, not the ones encountered
while searching for it.

1229. By Lars Karlitski

unitythemeiconprovider: support size == -1

Qt sets requestedSize to -1 if no size has been specified. Return the largest
available icon in that case.

1230. By Lars Karlitski

unitythemeiconprovider: also return the largest icon when requested size is 0

There's a test and a bug about it. Seems to be expected behavior.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1231. By Lars Karlitski

unitythemeiconprovider: use QStandardLocation

1232. By Lars Karlitski

Icon10: give source an empty fallback

1233. By Lars Karlitski

unitythemeiconprovider: typedef shared IconTheme pointer

Unmerged revisions

1233. By Lars Karlitski

unitythemeiconprovider: typedef shared IconTheme pointer

1232. By Lars Karlitski

Icon10: give source an empty fallback

1231. By Lars Karlitski

unitythemeiconprovider: use QStandardLocation

1230. By Lars Karlitski

unitythemeiconprovider: also return the largest icon when requested size is 0

There's a test and a bug about it. Seems to be expected behavior.

1229. By Lars Karlitski

unitythemeiconprovider: support size == -1

Qt sets requestedSize to -1 if no size has been specified. Return the largest
available icon in that case.

1228. By Lars Karlitski

unitythemeiconprovider: don't load icons that might not be used

Only load the icon that has the best matching size, not the ones encountered
while searching for it.

1227. By Lars Karlitski

tst_components: set XDG_DATA_DIRS so that icons are found

1226. By Lars Karlitski

unitythemeiconprovider: use qgetenv instead of getenv

1225. By Lars Karlitski

Icon: remove unnecessary sizing workaround

UnityThemeIconProvider behaves correctly now for small icon sizes.

This patch also makes sure that icons are always drawn with the correct aspect
ratio. Consumers only need to supply a height, whereas they needed to set both
width and height.

In the process, the update() function was removed and is thus not called when
the width and height of the Icon changes. This causes no regressions, as Qt
doesn't reload icons with the same url anyway.

1224. By Lars Karlitski

unitythemeiconprovider: manually load icons

Previously, UnityThemeIconProvider used QIcon::fromTheme() to lookup and load
icons. That function uses fdo's default icon lookup algorithm: it looks at the
current theme and all parent themes for each icon in turn. We'd prefer a
breadth-first search, where all icon names are looked up in the current theme
before falling back to the parent themes (see lp: #1324184). This patch
implements fdo's FindBestIcon() algorithm, which behaves like that.

It also now properly support rectangular icons and always returns an icon with
the requested size (adjusted for preserving the aspect ratio).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-08-21 04:51:26 +0000
3+++ debian/control 2014-08-27 13:55:30 +0000
4@@ -27,7 +27,7 @@
5 qtdeclarative5-doc-html,
6 qtwebkit5-doc-html,
7 qtsvg5-doc-html,
8- libqt5svg5,
9+ libqt5svg5-dev,
10 qtscript5-doc-html,
11 qtmultimedia5-doc-html,
12 unity-action-doc,
13
14=== modified file 'modules/Ubuntu/Components/Icon10.qml'
15--- modules/Ubuntu/Components/Icon10.qml 2014-07-31 14:14:56 +0000
16+++ modules/Ubuntu/Components/Icon10.qml 2014-08-27 13:55:30 +0000
17@@ -34,42 +34,14 @@
18 Image {
19 id: image
20 objectName: "image"
21- anchors.fill: parent
22-
23- /* Necessary so that icons are not loaded before a size is set. */
24- source: ""
25- sourceSize {
26- width: 0
27- height: 0
28- }
29-
30- Component.onCompleted: update()
31- onWidthChanged: update()
32- onHeightChanged: update()
33- Connections {
34- target: icon
35- ignoreUnknownSignals: true
36- onNameChanged: image.update()
37- onSourceChanged: image.update()
38- }
39-
40- function update() {
41- // only set sourceSize.width, sourceSize.height and source when
42- // icon dimensions are valid, see bug #1349769.
43- if (width > 0 && height > 0
44- && (icon.name || (icon.hasOwnProperty("source") && icon.source))) {
45- sourceSize.width = width;
46- sourceSize.height = height;
47- if (icon.hasOwnProperty("source")) {
48- source = icon.source;
49- } else {
50- source = "image://theme/%1".arg(icon.name);
51- }
52- } else {
53- source = "";
54- sourceSize.width = 0;
55- sourceSize.height = 0;
56- }
57+ anchors { top: parent.top; bottom: parent.bottom }
58+ fillMode: Image.PreserveAspectFit;
59+ sourceSize.height: height
60+ source: {
61+ if (icon.name)
62+ return "image://theme/%1".arg(icon.name);
63+ else if (icon.hasOwnProperty("source"))
64+ return icon.source;
65 }
66
67 cache: true
68
69=== modified file 'modules/Ubuntu/Components/plugin/plugin.pro'
70--- modules/Ubuntu/Components/plugin/plugin.pro 2014-08-20 13:03:13 +0000
71+++ modules/Ubuntu/Components/plugin/plugin.pro 2014-08-27 13:55:30 +0000
72@@ -5,7 +5,11 @@
73
74 TEMPLATE = lib
75 TARGET = ../UbuntuComponents
76+<<<<<<< TREE
77 QT += core-private qml qml-private quick quick-private gui-private dbus
78+=======
79+QT += core-private qml qml-private quick quick-private dbus svg
80+>>>>>>> MERGE-SOURCE
81
82 equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 2) {
83 QT += v8-private
84
85=== modified file 'modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp'
86--- modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp 2014-03-17 14:24:12 +0000
87+++ modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp 2014-08-27 13:55:30 +0000
88@@ -1,5 +1,5 @@
89 /*
90- * Copyright 2013 Canonical Ltd.
91+ * Copyright 2014 Canonical Ltd.
92 *
93 * This program is free software; you can redistribute it and/or modify
94 * it under the terms of the GNU Lesser General Public License as published by
95@@ -18,35 +18,234 @@
96
97 #include "unitythemeiconprovider.h"
98
99-#include <QIcon>
100+#include <QDir>
101+#include <QFileInfo>
102+#include <QSettings>
103+#include <QSvgRenderer>
104+#include <QPainter>
105+#include <QtDebug>
106+
107+class IconTheme
108+{
109+public:
110+ // Returns the icon theme named @name, creating it if it didn't exist yet.
111+ static QSharedPointer<IconTheme> get(const QString &name)
112+ {
113+ static QHash<QString, QSharedPointer<IconTheme> > themes;
114+
115+ QSharedPointer<IconTheme> theme = themes[name];
116+ if (theme.isNull()) {
117+ theme = QSharedPointer<IconTheme>(new IconTheme(name));
118+ themes[name] = theme;
119+ }
120+
121+ return theme;
122+ }
123+
124+ // Does a breadth-first search for an icon with any name in @names. Parent
125+ // themes are only looked at if the current theme doesn't contain any icon
126+ // in @names.
127+ QPixmap findBestIcon(const QStringList &names, int size)
128+ {
129+ Q_FOREACH(const QString &name, names) {
130+ QPixmap pixmap = lookupIcon(name, size);
131+ if (!pixmap.isNull())
132+ return pixmap;
133+ }
134+
135+ Q_FOREACH(QSharedPointer<IconTheme> theme, parents) {
136+ QPixmap pixmap = theme->findBestIcon(names, size);
137+ if (!pixmap.isNull())
138+ return pixmap;
139+ }
140+
141+ return QPixmap();
142+ }
143+
144+private:
145+ enum SizeType { Fixed, Scalable, Threshold };
146+
147+ struct Directory {
148+ QString path;
149+ SizeType sizeType;
150+ int size, minSize, maxSize, threshold;
151+ };
152+
153+ IconTheme(const QString &name): name(name)
154+ {
155+ Q_FOREACH(const QByteArray &baseDir, qgetenv("XDG_DATA_DIRS").split(':')) {
156+ QDir dir(baseDir + "/icons/" + name);
157+ if (dir.exists())
158+ baseDirs.append(dir.absolutePath());
159+ }
160+
161+ Q_FOREACH(const QString &baseDir, baseDirs) {
162+ QString filename = baseDir + "/index.theme";
163+ if (QFileInfo::exists(filename)) {
164+ QSettings settings(filename, QSettings::IniFormat);
165+
166+ Q_FOREACH(const QString &path, settings.value("Icon Theme/Directories").toStringList()) {
167+ Directory dir;
168+ dir.path = path;
169+ dir.sizeType = sizeTypeFromString(settings.value(path + "/Type", "Fixed").toString());
170+ dir.size = settings.value(path + "/Size", 32).toInt();
171+ dir.minSize = settings.value(path + "/MinSize", 0).toInt();
172+ dir.maxSize = settings.value(path + "/MaxSize", 0).toInt();
173+ dir.threshold = settings.value(path + "/Threshold", 0).toInt();
174+ directories.append(dir);
175+ }
176+
177+ Q_FOREACH(const QString &name, settings.value("Icon Theme/Inherits").toStringList())
178+ parents.append(IconTheme::get(name));
179+
180+ // there can only be one index.theme
181+ break;
182+ }
183+ }
184+ }
185+
186+ SizeType sizeTypeFromString(const QString &string)
187+ {
188+ if (string == "Fixed")
189+ return Fixed;
190+ if (string == "Scalable")
191+ return Scalable;
192+ if (string == "Threshold")
193+ return Threshold;
194+ qWarning() << "IconTheme: unknown size type '" << string << "'. Assuming 'Fixed'.";
195+ return Fixed;
196+ }
197+
198+ static QPixmap loadIcon(const QString &filename, int size)
199+ {
200+ QPixmap pixmap;
201+
202+ if (filename.endsWith(".png")) {
203+ pixmap = QPixmap(filename);
204+ if (!pixmap.isNull() && size > 0 && (pixmap.width() != size || pixmap.height() != size))
205+ pixmap = pixmap.scaled(size, size, Qt::KeepAspectRatioByExpanding);
206+ }
207+ else if (filename.endsWith(".svg")) {
208+ QSvgRenderer renderer(filename);
209+ pixmap = QPixmap(renderer.defaultSize().scaled(size, size, Qt::KeepAspectRatioByExpanding));
210+ pixmap.fill(Qt::transparent);
211+ QPainter painter(&pixmap);
212+ renderer.render(&painter);
213+ painter.end();
214+ }
215+
216+ return pixmap;
217+ }
218+
219+ QString lookupIconFile(const QString &dir, const QString &name)
220+ {
221+ QString png = QString("%1/%2.png").arg(dir).arg(name);
222+ QString svg = QString("%1/%2.svg").arg(dir).arg(name);
223+
224+ Q_FOREACH(const QString &baseDir, baseDirs) {
225+ QString filename = baseDir + "/" + png;
226+ if (QFileInfo::exists(filename))
227+ return filename;
228+
229+ filename = baseDir + "/" + svg;
230+ if (QFileInfo::exists(filename))
231+ return filename;
232+ }
233+
234+ return QString();
235+ }
236+
237+ QPixmap lookupIcon(const QString &iconName, int size)
238+ {
239+ if (size > 0)
240+ return lookupBestMatchingIcon(iconName, size);
241+ else
242+ return lookupLargestIcon(iconName);
243+ }
244+
245+ QPixmap lookupBestMatchingIcon(const QString &iconName, int size)
246+ {
247+ int minDistance = 10000;
248+ QString bestFilename;
249+
250+ Q_FOREACH(const Directory &dir, directories) {
251+ int dist = directorySizeDistance(dir, size);
252+ if (dist >= minDistance)
253+ continue;
254+
255+ QString filename = lookupIconFile(dir.path, iconName);
256+ if (!filename.isNull()) {
257+ minDistance = dist;
258+ bestFilename = filename;
259+
260+ // bail out early if we can't get a better size match
261+ if (minDistance == 0)
262+ break;
263+ }
264+ }
265+
266+ if (!bestFilename.isNull())
267+ return loadIcon(bestFilename, size);
268+
269+ return QPixmap();
270+ }
271+
272+ QPixmap lookupLargestIcon(const QString &iconName)
273+ {
274+ int maxSize = 0;
275+ QString bestFilename;
276+
277+ Q_FOREACH(const Directory &dir, directories) {
278+ int size = dir.sizeType == Scalable ? dir.maxSize : dir.size;
279+ if (size < maxSize)
280+ continue;
281+
282+ QString filename = lookupIconFile(dir.path, iconName);
283+ if (!filename.isNull()) {
284+ maxSize = size;
285+ bestFilename = filename;
286+ }
287+ }
288+
289+ if (!bestFilename.isNull())
290+ return loadIcon(bestFilename, maxSize);
291+
292+ return QPixmap();
293+ }
294+
295+ int directorySizeDistance(const Directory &dir, int size)
296+ {
297+ switch (dir.sizeType) {
298+ case Fixed:
299+ return qAbs(size - dir.size);
300+
301+ case Scalable:
302+ return qAbs(size - qBound(dir.minSize, size, dir.maxSize));
303+
304+ case Threshold:
305+ return qAbs(size - qBound(dir.size - dir.threshold, size, dir.size + dir.threshold));
306+
307+ default:
308+ return 10000;
309+ }
310+ }
311+
312+ QString name;
313+ QStringList baseDirs;
314+ QList<Directory> directories;
315+ QList<QSharedPointer<IconTheme> > parents;
316+};
317
318 UnityThemeIconProvider::UnityThemeIconProvider():
319 QQuickImageProvider(QQuickImageProvider::Pixmap)
320 {
321- QIcon::setThemeName("suru");
322+ theme = IconTheme::get("suru");
323 }
324
325-QPixmap UnityThemeIconProvider::requestPixmap(const QString &id, QSize *realSize, const QSize &requestedSize)
326+QPixmap UnityThemeIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
327 {
328- QIcon icon;
329-
330- Q_FOREACH (const QString &name, id.split(",", QString::SkipEmptyParts)) {
331- icon = QIcon::fromTheme(name);
332- if (!icon.isNull()) {
333- if (requestedSize.isValid()) {
334- *realSize =requestedSize;
335- return icon.pixmap(requestedSize);
336- } else {
337- QList<QSize> sizes = icon.availableSizes();
338- if (sizes.count() > 0 && sizes.last().isValid()) {
339- *realSize = sizes.last();
340- } else {
341- *realSize = QSize(32,32);
342- }
343- return icon.pixmap(*realSize);
344- }
345- break;
346- }
347- }
348- return QPixmap();
349+ int iconSize = qMax(requestedSize.width(), requestedSize.height());
350+ QPixmap pixmap = theme->findBestIcon(id.split(",", QString::SkipEmptyParts), iconSize);
351+ *size = pixmap.size();
352+ return pixmap;
353 }
354
355=== modified file 'modules/Ubuntu/Components/plugin/unitythemeiconprovider.h'
356--- modules/Ubuntu/Components/plugin/unitythemeiconprovider.h 2013-08-07 16:13:39 +0000
357+++ modules/Ubuntu/Components/plugin/unitythemeiconprovider.h 2014-08-27 13:55:30 +0000
358@@ -26,6 +26,9 @@
359 public:
360 UnityThemeIconProvider();
361 QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
362+
363+private:
364+ QSharedPointer<class IconTheme> theme;
365 };
366
367 #endif
368
369=== modified file 'tests/unit_x11/tst_components/tst_components.cpp'
370--- tests/unit_x11/tst_components/tst_components.cpp 2013-11-26 12:44:39 +0000
371+++ tests/unit_x11/tst_components/tst_components.cpp 2014-08-27 13:55:30 +0000
372@@ -25,7 +25,18 @@
373 Register()
374 {
375 qmlRegisterType<TabsModel>("TestObjects", 0, 1, "TabsModel");
376- }
377+
378+ savedDataDirs = qgetenv("XDG_DATA_DIRS");
379+ qputenv("XDG_DATA_DIRS", "/usr/share");
380+ }
381+
382+ ~Register()
383+ {
384+ qputenv("XDG_DATA_DIRS", savedDataDirs);
385+ }
386+
387+private:
388+ QByteArray savedDataDirs;
389 };
390
391 Register r;
392
393=== modified file 'tests/unit_x11/tst_components/tst_icon.qml'
394--- tests/unit_x11/tst_components/tst_icon.qml 2014-07-30 20:20:49 +0000
395+++ tests/unit_x11/tst_components/tst_icon.qml 2014-08-27 13:55:30 +0000
396@@ -82,7 +82,7 @@
397
398 var image = findChild(icon2, "image");
399 compare(image.source,
400- "file:///usr/share/icons/suru/actions/scalable/search.svg",
401+ "image://theme/search",
402 "Source of the image should equal icon2.source.");
403 }
404 }

Subscribers

People subscribed via source and target branches

to status/vote changes: