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
=== modified file 'debian/control'
--- debian/control 2014-08-21 04:51:26 +0000
+++ debian/control 2014-08-27 13:55:30 +0000
@@ -27,7 +27,7 @@
27 qtdeclarative5-doc-html,27 qtdeclarative5-doc-html,
28 qtwebkit5-doc-html,28 qtwebkit5-doc-html,
29 qtsvg5-doc-html,29 qtsvg5-doc-html,
30 libqt5svg5,30 libqt5svg5-dev,
31 qtscript5-doc-html,31 qtscript5-doc-html,
32 qtmultimedia5-doc-html,32 qtmultimedia5-doc-html,
33 unity-action-doc,33 unity-action-doc,
3434
=== modified file 'modules/Ubuntu/Components/Icon10.qml'
--- modules/Ubuntu/Components/Icon10.qml 2014-07-31 14:14:56 +0000
+++ modules/Ubuntu/Components/Icon10.qml 2014-08-27 13:55:30 +0000
@@ -34,42 +34,14 @@
34 Image {34 Image {
35 id: image35 id: image
36 objectName: "image"36 objectName: "image"
37 anchors.fill: parent37 anchors { top: parent.top; bottom: parent.bottom }
3838 fillMode: Image.PreserveAspectFit;
39 /* Necessary so that icons are not loaded before a size is set. */39 sourceSize.height: height
40 source: ""40 source: {
41 sourceSize {41 if (icon.name)
42 width: 042 return "image://theme/%1".arg(icon.name);
43 height: 043 else if (icon.hasOwnProperty("source"))
44 }44 return icon.source;
45
46 Component.onCompleted: update()
47 onWidthChanged: update()
48 onHeightChanged: update()
49 Connections {
50 target: icon
51 ignoreUnknownSignals: true
52 onNameChanged: image.update()
53 onSourceChanged: image.update()
54 }
55
56 function update() {
57 // only set sourceSize.width, sourceSize.height and source when
58 // icon dimensions are valid, see bug #1349769.
59 if (width > 0 && height > 0
60 && (icon.name || (icon.hasOwnProperty("source") && icon.source))) {
61 sourceSize.width = width;
62 sourceSize.height = height;
63 if (icon.hasOwnProperty("source")) {
64 source = icon.source;
65 } else {
66 source = "image://theme/%1".arg(icon.name);
67 }
68 } else {
69 source = "";
70 sourceSize.width = 0;
71 sourceSize.height = 0;
72 }
73 }45 }
7446
75 cache: true47 cache: true
7648
=== modified file 'modules/Ubuntu/Components/plugin/plugin.pro'
--- modules/Ubuntu/Components/plugin/plugin.pro 2014-08-20 13:03:13 +0000
+++ modules/Ubuntu/Components/plugin/plugin.pro 2014-08-27 13:55:30 +0000
@@ -5,7 +5,11 @@
55
6TEMPLATE = lib6TEMPLATE = lib
7TARGET = ../UbuntuComponents7TARGET = ../UbuntuComponents
8<<<<<<< TREE
8QT += core-private qml qml-private quick quick-private gui-private dbus9QT += core-private qml qml-private quick quick-private gui-private dbus
10=======
11QT += core-private qml qml-private quick quick-private dbus svg
12>>>>>>> MERGE-SOURCE
913
10equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 2) {14equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 2) {
11 QT += v8-private15 QT += v8-private
1216
=== modified file 'modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp'
--- modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp 2014-03-17 14:24:12 +0000
+++ modules/Ubuntu/Components/plugin/unitythemeiconprovider.cpp 2014-08-27 13:55:30 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2013 Canonical Ltd.2 * Copyright 2014 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * 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 by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -18,35 +18,234 @@
1818
19#include "unitythemeiconprovider.h"19#include "unitythemeiconprovider.h"
2020
21#include <QIcon>21#include <QDir>
22#include <QFileInfo>
23#include <QSettings>
24#include <QSvgRenderer>
25#include <QPainter>
26#include <QtDebug>
27
28class IconTheme
29{
30public:
31 // Returns the icon theme named @name, creating it if it didn't exist yet.
32 static QSharedPointer<IconTheme> get(const QString &name)
33 {
34 static QHash<QString, QSharedPointer<IconTheme> > themes;
35
36 QSharedPointer<IconTheme> theme = themes[name];
37 if (theme.isNull()) {
38 theme = QSharedPointer<IconTheme>(new IconTheme(name));
39 themes[name] = theme;
40 }
41
42 return theme;
43 }
44
45 // Does a breadth-first search for an icon with any name in @names. Parent
46 // themes are only looked at if the current theme doesn't contain any icon
47 // in @names.
48 QPixmap findBestIcon(const QStringList &names, int size)
49 {
50 Q_FOREACH(const QString &name, names) {
51 QPixmap pixmap = lookupIcon(name, size);
52 if (!pixmap.isNull())
53 return pixmap;
54 }
55
56 Q_FOREACH(QSharedPointer<IconTheme> theme, parents) {
57 QPixmap pixmap = theme->findBestIcon(names, size);
58 if (!pixmap.isNull())
59 return pixmap;
60 }
61
62 return QPixmap();
63 }
64
65private:
66 enum SizeType { Fixed, Scalable, Threshold };
67
68 struct Directory {
69 QString path;
70 SizeType sizeType;
71 int size, minSize, maxSize, threshold;
72 };
73
74 IconTheme(const QString &name): name(name)
75 {
76 Q_FOREACH(const QByteArray &baseDir, qgetenv("XDG_DATA_DIRS").split(':')) {
77 QDir dir(baseDir + "/icons/" + name);
78 if (dir.exists())
79 baseDirs.append(dir.absolutePath());
80 }
81
82 Q_FOREACH(const QString &baseDir, baseDirs) {
83 QString filename = baseDir + "/index.theme";
84 if (QFileInfo::exists(filename)) {
85 QSettings settings(filename, QSettings::IniFormat);
86
87 Q_FOREACH(const QString &path, settings.value("Icon Theme/Directories").toStringList()) {
88 Directory dir;
89 dir.path = path;
90 dir.sizeType = sizeTypeFromString(settings.value(path + "/Type", "Fixed").toString());
91 dir.size = settings.value(path + "/Size", 32).toInt();
92 dir.minSize = settings.value(path + "/MinSize", 0).toInt();
93 dir.maxSize = settings.value(path + "/MaxSize", 0).toInt();
94 dir.threshold = settings.value(path + "/Threshold", 0).toInt();
95 directories.append(dir);
96 }
97
98 Q_FOREACH(const QString &name, settings.value("Icon Theme/Inherits").toStringList())
99 parents.append(IconTheme::get(name));
100
101 // there can only be one index.theme
102 break;
103 }
104 }
105 }
106
107 SizeType sizeTypeFromString(const QString &string)
108 {
109 if (string == "Fixed")
110 return Fixed;
111 if (string == "Scalable")
112 return Scalable;
113 if (string == "Threshold")
114 return Threshold;
115 qWarning() << "IconTheme: unknown size type '" << string << "'. Assuming 'Fixed'.";
116 return Fixed;
117 }
118
119 static QPixmap loadIcon(const QString &filename, int size)
120 {
121 QPixmap pixmap;
122
123 if (filename.endsWith(".png")) {
124 pixmap = QPixmap(filename);
125 if (!pixmap.isNull() && size > 0 && (pixmap.width() != size || pixmap.height() != size))
126 pixmap = pixmap.scaled(size, size, Qt::KeepAspectRatioByExpanding);
127 }
128 else if (filename.endsWith(".svg")) {
129 QSvgRenderer renderer(filename);
130 pixmap = QPixmap(renderer.defaultSize().scaled(size, size, Qt::KeepAspectRatioByExpanding));
131 pixmap.fill(Qt::transparent);
132 QPainter painter(&pixmap);
133 renderer.render(&painter);
134 painter.end();
135 }
136
137 return pixmap;
138 }
139
140 QString lookupIconFile(const QString &dir, const QString &name)
141 {
142 QString png = QString("%1/%2.png").arg(dir).arg(name);
143 QString svg = QString("%1/%2.svg").arg(dir).arg(name);
144
145 Q_FOREACH(const QString &baseDir, baseDirs) {
146 QString filename = baseDir + "/" + png;
147 if (QFileInfo::exists(filename))
148 return filename;
149
150 filename = baseDir + "/" + svg;
151 if (QFileInfo::exists(filename))
152 return filename;
153 }
154
155 return QString();
156 }
157
158 QPixmap lookupIcon(const QString &iconName, int size)
159 {
160 if (size > 0)
161 return lookupBestMatchingIcon(iconName, size);
162 else
163 return lookupLargestIcon(iconName);
164 }
165
166 QPixmap lookupBestMatchingIcon(const QString &iconName, int size)
167 {
168 int minDistance = 10000;
169 QString bestFilename;
170
171 Q_FOREACH(const Directory &dir, directories) {
172 int dist = directorySizeDistance(dir, size);
173 if (dist >= minDistance)
174 continue;
175
176 QString filename = lookupIconFile(dir.path, iconName);
177 if (!filename.isNull()) {
178 minDistance = dist;
179 bestFilename = filename;
180
181 // bail out early if we can't get a better size match
182 if (minDistance == 0)
183 break;
184 }
185 }
186
187 if (!bestFilename.isNull())
188 return loadIcon(bestFilename, size);
189
190 return QPixmap();
191 }
192
193 QPixmap lookupLargestIcon(const QString &iconName)
194 {
195 int maxSize = 0;
196 QString bestFilename;
197
198 Q_FOREACH(const Directory &dir, directories) {
199 int size = dir.sizeType == Scalable ? dir.maxSize : dir.size;
200 if (size < maxSize)
201 continue;
202
203 QString filename = lookupIconFile(dir.path, iconName);
204 if (!filename.isNull()) {
205 maxSize = size;
206 bestFilename = filename;
207 }
208 }
209
210 if (!bestFilename.isNull())
211 return loadIcon(bestFilename, maxSize);
212
213 return QPixmap();
214 }
215
216 int directorySizeDistance(const Directory &dir, int size)
217 {
218 switch (dir.sizeType) {
219 case Fixed:
220 return qAbs(size - dir.size);
221
222 case Scalable:
223 return qAbs(size - qBound(dir.minSize, size, dir.maxSize));
224
225 case Threshold:
226 return qAbs(size - qBound(dir.size - dir.threshold, size, dir.size + dir.threshold));
227
228 default:
229 return 10000;
230 }
231 }
232
233 QString name;
234 QStringList baseDirs;
235 QList<Directory> directories;
236 QList<QSharedPointer<IconTheme> > parents;
237};
22238
23UnityThemeIconProvider::UnityThemeIconProvider():239UnityThemeIconProvider::UnityThemeIconProvider():
24 QQuickImageProvider(QQuickImageProvider::Pixmap)240 QQuickImageProvider(QQuickImageProvider::Pixmap)
25{241{
26 QIcon::setThemeName("suru");242 theme = IconTheme::get("suru");
27}243}
28244
29QPixmap UnityThemeIconProvider::requestPixmap(const QString &id, QSize *realSize, const QSize &requestedSize)245QPixmap UnityThemeIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
30{246{
31 QIcon icon;247 int iconSize = qMax(requestedSize.width(), requestedSize.height());
32248 QPixmap pixmap = theme->findBestIcon(id.split(",", QString::SkipEmptyParts), iconSize);
33 Q_FOREACH (const QString &name, id.split(",", QString::SkipEmptyParts)) {249 *size = pixmap.size();
34 icon = QIcon::fromTheme(name);250 return pixmap;
35 if (!icon.isNull()) {
36 if (requestedSize.isValid()) {
37 *realSize =requestedSize;
38 return icon.pixmap(requestedSize);
39 } else {
40 QList<QSize> sizes = icon.availableSizes();
41 if (sizes.count() > 0 && sizes.last().isValid()) {
42 *realSize = sizes.last();
43 } else {
44 *realSize = QSize(32,32);
45 }
46 return icon.pixmap(*realSize);
47 }
48 break;
49 }
50 }
51 return QPixmap();
52}251}
53252
=== modified file 'modules/Ubuntu/Components/plugin/unitythemeiconprovider.h'
--- modules/Ubuntu/Components/plugin/unitythemeiconprovider.h 2013-08-07 16:13:39 +0000
+++ modules/Ubuntu/Components/plugin/unitythemeiconprovider.h 2014-08-27 13:55:30 +0000
@@ -26,6 +26,9 @@
26public:26public:
27 UnityThemeIconProvider();27 UnityThemeIconProvider();
28 QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);28 QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
29
30private:
31 QSharedPointer<class IconTheme> theme;
29};32};
3033
31#endif34#endif
3235
=== modified file 'tests/unit_x11/tst_components/tst_components.cpp'
--- tests/unit_x11/tst_components/tst_components.cpp 2013-11-26 12:44:39 +0000
+++ tests/unit_x11/tst_components/tst_components.cpp 2014-08-27 13:55:30 +0000
@@ -25,7 +25,18 @@
25 Register()25 Register()
26 {26 {
27 qmlRegisterType<TabsModel>("TestObjects", 0, 1, "TabsModel");27 qmlRegisterType<TabsModel>("TestObjects", 0, 1, "TabsModel");
28 }28
29 savedDataDirs = qgetenv("XDG_DATA_DIRS");
30 qputenv("XDG_DATA_DIRS", "/usr/share");
31 }
32
33 ~Register()
34 {
35 qputenv("XDG_DATA_DIRS", savedDataDirs);
36 }
37
38private:
39 QByteArray savedDataDirs;
29};40};
3041
31Register r;42Register r;
3243
=== modified file 'tests/unit_x11/tst_components/tst_icon.qml'
--- tests/unit_x11/tst_components/tst_icon.qml 2014-07-30 20:20:49 +0000
+++ tests/unit_x11/tst_components/tst_icon.qml 2014-08-27 13:55:30 +0000
@@ -82,7 +82,7 @@
8282
83 var image = findChild(icon2, "image");83 var image = findChild(icon2, "image");
84 compare(image.source,84 compare(image.source,
85 "file:///usr/share/icons/suru/actions/scalable/search.svg",85 "image://theme/search",
86 "Source of the image should equal icon2.source.");86 "Source of the image should equal icon2.source.");
87 }87 }
88 }88 }

Subscribers

People subscribed via source and target branches

to status/vote changes: