Merge lp:~nick-dedekind/unity/multi-instance-icon-loader into lp:unity
- multi-instance-icon-loader
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~nick-dedekind/unity/multi-instance-icon-loader |
Merge into: | lp:unity |
Diff against target: |
2350 lines (+1138/-850) 10 files modified
dash/ResultRendererTile.cpp (+2/-2) dash/StandaloneDash.cpp (+3/-4) unity-shared/CMakeLists.txt (+1/-0) unity-shared/CoverArt.cpp (+14/-9) unity-shared/CoverArt.h (+4/-2) unity-shared/IconLoader.cpp (+112/-828) unity-shared/IconLoader.h (+16/-5) unity-shared/IconLoaderImpl.h (+119/-0) unity-shared/IconLoaderTask.cpp (+756/-0) unity-shared/IconLoaderTask.h (+111/-0) |
To merge this branch: | bzr merge lp:~nick-dedekind/unity/multi-instance-icon-loader |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Needs Fixing | |
Michal Hruby (community) | Needs Fixing | ||
Nick Dedekind (community) | Needs Fixing | ||
Review via email: mp+129125@code.launchpad.net |
Commit message
Added multi-instance Icon Loaders with management.
Description of the change
Added multi-instance Icon Loaders with management.
Icon loaders can be created as requested and deleted when the last reference is removed. This allows better control of internal caching in areas such as dash previews.
All icon loading now asynchronous.
Refactored IconLoaderTask into class.
Refactored IconLoaderTask to remove "shadow tasks" (now adds a slot to existing tasks).
Added task thread-safe ref counting for loader instance deletion during IO scheduled job processing.
Nick Dedekind (nick-dedekind) wrote : | # |
TODO:
o Refactoring to remove sync/async from single call.
o Refactor IconLoaderTask into class.
o Tasks need thread-safe ref counting for loader instance deletion during IO scheduled job processing.
Nick Dedekind (nick-dedekind) wrote : | # |
All icon loading now asynchronous.
Refactored IconLoaderTask into class.
Refactored IconLoaderTask to remove "shadow tasks" (now adds a slot to existing tasks).
Added task thread-safe ref counting for loader instance deletion during IO scheduled job processing.
Michal Hruby (mhr3) wrote : | # |
1136 + timer_.reset(new glib::Timeout(0, sigc::mem_fun(this, &IconLoaderImpl
Why the change to Timeout instead of Idle?
2138 + InvokeSlot();
2139 +
2140 + if (!impl_
2141 + {
We want to dispatch the slots in the coalesce callback (as the dispatch will often cause a QueueDraw which has higher prio than finishing other loader tasks - ie causes "finish, redraw, finish, redraw, finish, redraw" vs "finish, finish, finish, redraw").
1103 + task_slot_
Am I right in thinking these elements in the map will "leak"?
- 2827. By Nick Dedekind
-
Merge with trunk
- 2828. By Nick Dedekind
-
Invoke icon slot on coalesce.
- 2829. By Nick Dedekind
-
Fixed referencing issue.
Nick Dedekind (nick-dedekind) wrote : | # |
> 1136 + timer_.reset(new glib::Timeout(0, sigc::mem_fun(this,
> &IconLoaderImpl
>
> Why the change to Timeout instead of Idle?
>
> 2138 + InvokeSlot();
> 2139 +
> 2140 + if (!impl_
> 2141 + {
>
> We want to dispatch the slots in the coalesce callback (as the dispatch will
> often cause a QueueDraw which has higher prio than finishing other loader
> tasks - ie causes "finish, redraw, finish, redraw, finish, redraw" vs "finish,
> finish, finish, redraw").
>
Done. Reduced coalesce time to 200ms.
> 1103 + task_slot_
>
> Am I right in thinking these elements in the map will "leak"?
These should be released in IconLoaderImpl:
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:2829
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
- 2829. By Nick Dedekind
-
Fixed referencing issue.
- 2828. By Nick Dedekind
-
Invoke icon slot on coalesce.
- 2827. By Nick Dedekind
-
Merge with trunk
- 2826. By Nick Dedekind
-
IconLoader code refactor.
- 2825. By Nick Dedekind
-
Merge with trunk
- 2824. By Nick Dedekind
-
Added multi-instance icon loaders management.
- 2823. By Nick Dedekind
-
Fixed dash standalone seg-fault on close.
Preview Diff
1 | === modified file 'dash/ResultRendererTile.cpp' |
2 | --- dash/ResultRendererTile.cpp 2012-12-13 14:26:56 +0000 |
3 | +++ dash/ResultRendererTile.cpp 2013-01-30 14:41:22 +0000 |
4 | @@ -262,7 +262,7 @@ |
5 | glib::Object<GIcon> icon(g_icon_new_for_string(icon_name.c_str(), NULL)); |
6 | TextureContainer* container = row.renderer<TextureContainer*>(); |
7 | |
8 | - IconLoader::IconLoaderCallback slot = sigc::bind(sigc::mem_fun(this, &ResultRendererTile::IconLoaded), icon_hint, row); |
9 | + icon_loader::IconLoaderCallback slot = sigc::bind(sigc::mem_fun(this, &ResultRendererTile::IconLoaded), icon_hint, row); |
10 | |
11 | if (icon.IsType(G_TYPE_ICON)) |
12 | { |
13 | @@ -380,7 +380,7 @@ |
14 | else if (container) |
15 | { |
16 | // we need to load a missing icon |
17 | - IconLoader::IconLoaderCallback slot = sigc::bind(sigc::mem_fun(this, &ResultRendererTile::IconLoaded), icon_name, row); |
18 | + icon_loader::IconLoaderCallback slot = sigc::bind(sigc::mem_fun(this, &ResultRendererTile::IconLoaded), icon_name, row); |
19 | container->slot_handle = IconLoader::GetDefault().LoadFromGIconString(". GThemedIcon text-x-preview", max_width, max_height, slot); |
20 | } |
21 | } |
22 | |
23 | === modified file 'dash/StandaloneDash.cpp' |
24 | --- dash/StandaloneDash.cpp 2012-11-29 10:19:01 +0000 |
25 | +++ dash/StandaloneDash.cpp 2013-01-30 14:41:22 +0000 |
26 | @@ -83,7 +83,7 @@ |
27 | |
28 | int main(int argc, char **argv) |
29 | { |
30 | - nux::WindowThread* wt = NULL; |
31 | + std::unique_ptr<nux::WindowThread> wt; |
32 | |
33 | gtk_init (&argc, &argv); |
34 | |
35 | @@ -99,16 +99,15 @@ |
36 | unity::panel::Style panel_style; |
37 | |
38 | TestRunner *test_runner = new TestRunner (); |
39 | - wt = nux::CreateGUIThread(TEXT("Unity Dash"), |
40 | + wt.reset(nux::CreateGUIThread(TEXT("Unity Dash"), |
41 | WIDTH, HEIGHT, |
42 | 0, |
43 | &TestRunner::InitWindowThread, |
44 | - test_runner); |
45 | + test_runner)); |
46 | |
47 | nux::NuxTimerTickSource tick_source; |
48 | nux::animation::AnimationController animation_controller(tick_source); |
49 | |
50 | wt->Run (NULL); |
51 | - delete wt; |
52 | return 0; |
53 | } |
54 | |
55 | === modified file 'unity-shared/CMakeLists.txt' |
56 | --- unity-shared/CMakeLists.txt 2013-01-28 23:57:38 +0000 |
57 | +++ unity-shared/CMakeLists.txt 2013-01-30 14:41:22 +0000 |
58 | @@ -30,6 +30,7 @@ |
59 | GraphicsUtils.cpp |
60 | IMTextEntry.cpp |
61 | IconLoader.cpp |
62 | + IconLoaderTask.cpp |
63 | IconRenderer.cpp |
64 | IconTexture.cpp |
65 | IconTextureSource.cpp |
66 | |
67 | === modified file 'unity-shared/CoverArt.cpp' |
68 | --- unity-shared/CoverArt.cpp 2012-12-17 21:30:27 +0000 |
69 | +++ unity-shared/CoverArt.cpp 2013-01-30 14:41:22 +0000 |
70 | @@ -22,12 +22,11 @@ |
71 | */ |
72 | |
73 | #include "CoverArt.h" |
74 | -#include "unity-shared/IntrospectableWrappers.h" |
75 | -#include "unity-shared/CairoTexture.h" |
76 | +#include "IntrospectableWrappers.h" |
77 | +#include "CairoTexture.h" |
78 | #include <NuxCore/Logger.h> |
79 | #include <Nux/VLayout.h> |
80 | #include "DashStyle.h" |
81 | -#include "IconLoader.h" |
82 | #include "PreviewStyle.h" |
83 | |
84 | namespace unity |
85 | @@ -54,6 +53,7 @@ |
86 | , stretch_image_(false) |
87 | , waiting_(false) |
88 | , rotation_(0.0) |
89 | + , icon_loader_(IconLoader::Get("cover-art")) |
90 | { |
91 | SetupViews(); |
92 | } |
93 | @@ -63,9 +63,9 @@ |
94 | if (overlay_text_) |
95 | overlay_text_->UnReference(); |
96 | |
97 | - if (slot_handle_ > 0) |
98 | + if (icon_loader_ && slot_handle_ > 0) |
99 | { |
100 | - IconLoader::GetDefault().DisconnectHandle(slot_handle_); |
101 | + icon_loader_->DisconnectHandle(slot_handle_); |
102 | slot_handle_ = 0; |
103 | } |
104 | |
105 | @@ -89,10 +89,15 @@ |
106 | void CoverArt::SetImage(std::string const& image_hint) |
107 | { |
108 | StopWaiting(); |
109 | + if (!icon_loader_) |
110 | + { |
111 | + SetNoImageAvailable(); |
112 | + return; |
113 | + } |
114 | |
115 | if (slot_handle_ > 0) |
116 | { |
117 | - IconLoader::GetDefault().DisconnectHandle(slot_handle_); |
118 | + icon_loader_->DisconnectHandle(slot_handle_); |
119 | slot_handle_ = 0; |
120 | } |
121 | |
122 | @@ -107,7 +112,7 @@ |
123 | if (bLoadTexture) |
124 | { |
125 | StartWaiting(); |
126 | - slot_handle_ = IconLoader::GetDefault().LoadFromGIconString(image_hint, -1, -1, sigc::mem_fun(this, &CoverArt::TextureLoaded)); |
127 | + slot_handle_ = icon_loader_->LoadFromGIconString(image_hint, -1, -1, sigc::mem_fun(this, &CoverArt::TextureLoaded)); |
128 | } |
129 | else if (!image_hint.empty()) |
130 | { |
131 | @@ -115,12 +120,12 @@ |
132 | if (icon.IsType(G_TYPE_ICON)) |
133 | { |
134 | StartWaiting(); |
135 | - slot_handle_ = IconLoader::GetDefault().LoadFromGIconString(image_hint, ICON_SIZE, ICON_SIZE, sigc::mem_fun(this, &CoverArt::IconLoaded)); |
136 | + slot_handle_ = icon_loader_->LoadFromGIconString(image_hint, ICON_SIZE, ICON_SIZE, sigc::mem_fun(this, &CoverArt::IconLoaded)); |
137 | } |
138 | else |
139 | { |
140 | StartWaiting(); |
141 | - slot_handle_ = IconLoader::GetDefault().LoadFromIconName(image_hint, ICON_SIZE, ICON_SIZE, sigc::mem_fun(this, &CoverArt::IconLoaded)); |
142 | + slot_handle_ = icon_loader_->LoadFromIconName(image_hint, ICON_SIZE, ICON_SIZE, sigc::mem_fun(this, &CoverArt::IconLoaded)); |
143 | } |
144 | } |
145 | else |
146 | |
147 | === modified file 'unity-shared/CoverArt.h' |
148 | --- unity-shared/CoverArt.h 2012-12-14 12:14:34 +0000 |
149 | +++ unity-shared/CoverArt.h 2013-01-30 14:41:22 +0000 |
150 | @@ -29,8 +29,9 @@ |
151 | #include <UnityCore/ApplicationPreview.h> |
152 | #include <UnityCore/GLibSource.h> |
153 | #include <NuxCore/ObjectPtr.h> |
154 | -#include "unity-shared/StaticCairoText.h" |
155 | -#include "unity-shared/Introspectable.h" |
156 | +#include "StaticCairoText.h" |
157 | +#include "Introspectable.h" |
158 | +#include "IconLoader.h" |
159 | #include "ThumbnailGenerator.h" |
160 | |
161 | namespace unity |
162 | @@ -99,6 +100,7 @@ |
163 | |
164 | typedef std::unique_ptr<nux::AbstractPaintLayer> LayerPtr; |
165 | LayerPtr bg_layer_; |
166 | + IconLoader::Ptr icon_loader_; |
167 | }; |
168 | |
169 | } |
170 | |
171 | === modified file 'unity-shared/IconLoader.cpp' |
172 | --- unity-shared/IconLoader.cpp 2013-01-17 16:11:14 +0000 |
173 | +++ unity-shared/IconLoader.cpp 2013-01-30 14:41:22 +0000 |
174 | @@ -1,6 +1,6 @@ |
175 | // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
176 | /* |
177 | -* Copyright (C) 2010, 2011 Canonical Ltd |
178 | +* Copyright (C) 2010-2012 Canonical Ltd |
179 | * |
180 | * This program is free software: you can redistribute it and/or modify |
181 | * it under the terms of the GNU General Public License version 3 as |
182 | @@ -15,767 +15,45 @@ |
183 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
184 | * |
185 | * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> |
186 | + Nick Dedekind <nick.dedekind@canonical.com> |
187 | */ |
188 | |
189 | #include "IconLoader.h" |
190 | +#include "IconLoaderImpl.h" |
191 | +#include "IconLoaderTask.h" |
192 | #include "config.h" |
193 | |
194 | #include <queue> |
195 | #include <sstream> |
196 | -#include <boost/algorithm/string.hpp> |
197 | -#include <unity-protocol.h> |
198 | #include <pango/pango.h> |
199 | #include <pango/pangocairo.h> |
200 | |
201 | #include <Nux/Nux.h> |
202 | #include <NuxCore/Logger.h> |
203 | -#include <NuxGraphics/CairoGraphics.h> |
204 | #include <UnityCore/GLibSource.h> |
205 | #include <UnityCore/GLibSignal.h> |
206 | |
207 | #include "unity-shared/Timer.h" |
208 | |
209 | +using namespace unity::icon_loader; |
210 | + |
211 | namespace unity |
212 | { |
213 | DECLARE_LOGGER(logger, "unity.iconloader"); |
214 | namespace |
215 | { |
216 | const int MIN_ICON_SIZE = 2; |
217 | -const int RIBBON_PADDING = 2; |
218 | + |
219 | +std::map<std::string, IconLoader*> icon_loaders; |
220 | } |
221 | |
222 | -class IconLoader::Impl |
223 | +namespace icon_loader |
224 | { |
225 | -public: |
226 | - // The Handle typedef is used to explicitly indicate which integers are |
227 | - // infact our opaque handles. |
228 | - typedef int Handle; |
229 | - static const int FONT_SIZE = 8; |
230 | - static const int MIN_FONT_SIZE = 5; |
231 | - |
232 | - Impl(); |
233 | - |
234 | - Handle LoadFromIconName(std::string const& icon_name, |
235 | - int max_width, |
236 | - int max_height, |
237 | - IconLoaderCallback slot); |
238 | - |
239 | - Handle LoadFromGIconString(std::string const& gicon_string, |
240 | - int max_width, |
241 | - int max_height, |
242 | - IconLoaderCallback slot); |
243 | - |
244 | - Handle LoadFromFilename(std::string const& filename, |
245 | - int max_width, |
246 | - int max_height, |
247 | - IconLoaderCallback slot); |
248 | - |
249 | - Handle LoadFromURI(std::string const& uri, |
250 | - int max_width, |
251 | - int max_height, |
252 | - IconLoaderCallback slot); |
253 | - |
254 | - void DisconnectHandle(Handle handle); |
255 | - |
256 | - static void CalculateTextHeight(int* width, int* height); |
257 | - |
258 | -private: |
259 | - |
260 | - enum IconLoaderRequestType |
261 | - { |
262 | - REQUEST_TYPE_ICON_NAME = 0, |
263 | - REQUEST_TYPE_GICON_STRING, |
264 | - REQUEST_TYPE_URI, |
265 | - }; |
266 | - |
267 | - struct IconLoaderTask |
268 | - { |
269 | - typedef std::shared_ptr<IconLoaderTask> Ptr; |
270 | - |
271 | - IconLoaderRequestType type; |
272 | - std::string data; |
273 | - int max_width; |
274 | - int max_height; |
275 | - std::string key; |
276 | - IconLoaderCallback slot; |
277 | - Handle handle; |
278 | - Impl* impl; |
279 | - GtkIconInfo* icon_info; |
280 | - bool no_cache; |
281 | - int helper_handle; |
282 | - glib::Object<GdkPixbuf> result; |
283 | - glib::Error error; |
284 | - std::list<IconLoaderTask::Ptr> shadow_tasks; |
285 | - unsigned idle_id; |
286 | - |
287 | - IconLoaderTask(IconLoaderRequestType type_, |
288 | - std::string const& data_, |
289 | - int max_width_, |
290 | - int max_height_, |
291 | - std::string const& key_, |
292 | - IconLoaderCallback slot_, |
293 | - Handle handle_, |
294 | - Impl* self_) |
295 | - : type(type_), data(data_), max_width(max_width_) |
296 | - , max_height(max_height_), key(key_) |
297 | - , slot(slot_), handle(handle_), impl(self_) |
298 | - , icon_info(nullptr), no_cache(false), helper_handle(0), idle_id(0) |
299 | - {} |
300 | - |
301 | - ~IconLoaderTask() |
302 | - { |
303 | - if (icon_info) |
304 | - ::gtk_icon_info_free(icon_info); |
305 | - if (helper_handle != 0) |
306 | - impl->DisconnectHandle(helper_handle); |
307 | - if (idle_id != 0) |
308 | - g_source_remove(idle_id); |
309 | - } |
310 | - |
311 | - void InvokeSlot() |
312 | - { |
313 | - if (slot) |
314 | - slot(data, max_width, max_height, result); |
315 | - |
316 | - // notify shadow tasks |
317 | - for (auto shadow_task : shadow_tasks) |
318 | - { |
319 | - if (shadow_task->slot) |
320 | - shadow_task->slot(shadow_task->data, |
321 | - shadow_task->max_width, |
322 | - shadow_task->max_height, |
323 | - result); |
324 | - |
325 | - impl->task_map_.erase(shadow_task->handle); |
326 | - } |
327 | - |
328 | - shadow_tasks.clear(); |
329 | - } |
330 | - |
331 | - bool Process() |
332 | - { |
333 | - // Check the cache again, as previous tasks might have wanted the same |
334 | - if (impl->CacheLookup(key, data, max_width, max_height, slot)) |
335 | - return true; |
336 | - |
337 | - LOG_DEBUG(logger) << "Processing " << data << " at size " << max_height; |
338 | - |
339 | - // Rely on the compiler to tell us if we miss a new type |
340 | - switch (type) |
341 | - { |
342 | - case REQUEST_TYPE_ICON_NAME: |
343 | - return ProcessIconNameTask(); |
344 | - case REQUEST_TYPE_GICON_STRING: |
345 | - return ProcessGIconTask(); |
346 | - case REQUEST_TYPE_URI: |
347 | - return ProcessURITask(); |
348 | - } |
349 | - |
350 | - LOG_WARNING(logger) << "Request type " << type |
351 | - << " is not supported (" << data |
352 | - << " " << max_width << "x" << max_height << ")"; |
353 | - result = nullptr; |
354 | - InvokeSlot(); |
355 | - |
356 | - return true; |
357 | - } |
358 | - |
359 | - bool ProcessIconNameTask() |
360 | - { |
361 | - int size = max_height < 0 ? max_width : (max_width < 0 ? max_height : MIN(max_height, max_width)); |
362 | - GtkIconInfo* info = ::gtk_icon_theme_lookup_icon(impl->theme_, data.c_str(), |
363 | - size, static_cast<GtkIconLookupFlags>(0)); |
364 | - if (info) |
365 | - { |
366 | - icon_info = info; |
367 | - PushSchedulerJob(); |
368 | - |
369 | - return false; |
370 | - } |
371 | - else |
372 | - { |
373 | - LOG_WARNING(logger) << "Unable to load icon " << data |
374 | - << " at size " << size; |
375 | - } |
376 | - |
377 | - result = nullptr; |
378 | - InvokeSlot(); |
379 | - |
380 | - return true; |
381 | - } |
382 | - |
383 | - bool ProcessGIconTask() |
384 | - { |
385 | - glib::Error error; |
386 | - glib::Object<GIcon> icon(::g_icon_new_for_string(data.c_str(), &error)); |
387 | - int size = max_height < 0 ? max_width : (max_width < 0 ? max_height : MIN(max_height, max_width)); |
388 | - |
389 | - if (icon.IsType(UNITY_PROTOCOL_TYPE_ANNOTATED_ICON)) |
390 | - { |
391 | - glib::Object<UnityProtocolAnnotatedIcon> anno(glib::object_cast<UnityProtocolAnnotatedIcon>(icon)); |
392 | - GIcon* base_icon = unity_protocol_annotated_icon_get_icon(anno); |
393 | - glib::String gicon_string(g_icon_to_string(base_icon)); |
394 | - |
395 | - // ensure that annotated icons aren't cached by the IconLoader |
396 | - no_cache = true; |
397 | - |
398 | - auto helper_slot = sigc::bind(sigc::mem_fun(this, &IconLoaderTask::BaseIconLoaded), anno); |
399 | - int base_icon_width, base_icon_height; |
400 | - if (unity_protocol_annotated_icon_get_use_small_icon(anno)) |
401 | - { |
402 | - // FIXME: although this pretends to be generic, we're just making |
403 | - // sure that icons requested to have 96px will be 64 |
404 | - base_icon_width = max_width > 0 ? max_width * 2 / 3 : max_width; |
405 | - base_icon_height = max_height > 0 ? max_height * 2 / 3 : max_height; |
406 | - } |
407 | - else |
408 | - { |
409 | - base_icon_width = max_width > 0 ? max_width - RIBBON_PADDING * 2 : -1; |
410 | - base_icon_height = base_icon_width < 0 ? max_height - RIBBON_PADDING *2 : max_height; |
411 | - } |
412 | - helper_handle = impl->LoadFromGIconString(gicon_string.Str(), |
413 | - base_icon_width, |
414 | - base_icon_height, |
415 | - helper_slot); |
416 | - |
417 | - return false; |
418 | - } |
419 | - else if (icon.IsType(G_TYPE_FILE_ICON)) |
420 | - { |
421 | - // [trasfer none] |
422 | - GFile* file = ::g_file_icon_get_file(G_FILE_ICON(icon.RawPtr())); |
423 | - glib::String uri(::g_file_get_uri(file)); |
424 | - |
425 | - type = REQUEST_TYPE_URI; |
426 | - data = uri.Str(); |
427 | - |
428 | - return ProcessURITask(); |
429 | - } |
430 | - else if (icon.IsType(G_TYPE_ICON)) |
431 | - { |
432 | - GtkIconInfo* info = ::gtk_icon_theme_lookup_by_gicon(impl->theme_, icon, size, |
433 | - static_cast<GtkIconLookupFlags>(0)); |
434 | - if (info) |
435 | - { |
436 | - icon_info = info; |
437 | - PushSchedulerJob(); |
438 | - |
439 | - return false; |
440 | - } |
441 | - else |
442 | - { |
443 | - // There is some funkiness in some programs where they install |
444 | - // their icon to /usr/share/icons/hicolor/apps/, but they |
445 | - // name the Icon= key as `foo.$extension` which breaks loading |
446 | - // So we can try and work around that here. |
447 | - |
448 | - if (boost::iends_with(data, ".png") || |
449 | - boost::iends_with(data, ".xpm") || |
450 | - boost::iends_with(data, ".gif") || |
451 | - boost::iends_with(data, ".jpg")) |
452 | - { |
453 | - data = data.substr(0, data.size() - 4); |
454 | - return ProcessIconNameTask(); |
455 | - } |
456 | - else |
457 | - { |
458 | - LOG_WARNING(logger) << "Unable to load icon " << data |
459 | - << " at size " << size; |
460 | - } |
461 | - } |
462 | - } |
463 | - else |
464 | - { |
465 | - LOG_WARNING(logger) << "Unable to load icon " << data |
466 | - << " at size " << size << ": " << error; |
467 | - } |
468 | - |
469 | - InvokeSlot(); |
470 | - return true; |
471 | - } |
472 | - |
473 | - bool ProcessURITask() |
474 | - { |
475 | - PushSchedulerJob(); |
476 | - |
477 | - return false; |
478 | - } |
479 | - |
480 | - void CategoryIconLoaded(std::string const& base_icon_string, |
481 | - int max_width, int max_height, |
482 | - glib::Object<GdkPixbuf> const& category_pixbuf, |
483 | - glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon) |
484 | - { |
485 | - helper_handle = 0; |
486 | - bool has_emblem = category_pixbuf; |
487 | - |
488 | - const gchar* detail_text = unity_protocol_annotated_icon_get_ribbon(anno_icon); |
489 | - if (detail_text) |
490 | - { |
491 | - const int SHADOW_BOTTOM_PADDING = 2; |
492 | - const int SHADOW_SIDE_PADDING = 1; |
493 | - const int EMBLEM_PADDING = 2; |
494 | - int icon_w = gdk_pixbuf_get_width(result); |
495 | - int icon_h = gdk_pixbuf_get_height(result); |
496 | - |
497 | - int max_font_height; |
498 | - CalculateTextHeight(nullptr, &max_font_height); |
499 | - |
500 | - // FIXME: design wants the tags 2px wider than the original icon |
501 | - int pixbuf_width = icon_w; |
502 | - int pixbuf_height = max_font_height * 5 / 4 + SHADOW_BOTTOM_PADDING; |
503 | - if (pixbuf_height > icon_h) pixbuf_height = icon_h; |
504 | - |
505 | - nux::CairoGraphics cairo_graphics(CAIRO_FORMAT_ARGB32, |
506 | - pixbuf_width, pixbuf_height); |
507 | - std::shared_ptr<cairo_t> cr(cairo_graphics.GetContext(), cairo_destroy); |
508 | - |
509 | - glib::Object<PangoLayout> layout; |
510 | - PangoContext* pango_context = NULL; |
511 | - GdkScreen* screen = gdk_screen_get_default(); // not ref'ed |
512 | - glib::String font; |
513 | - int dpi = -1; |
514 | - |
515 | - g_object_get(gtk_settings_get_default(), "gtk-font-name", &font, NULL); |
516 | - g_object_get(gtk_settings_get_default(), "gtk-xft-dpi", &dpi, NULL); |
517 | - cairo_set_font_options(cr.get(), gdk_screen_get_font_options(screen)); |
518 | - layout = pango_cairo_create_layout(cr.get()); |
519 | - std::shared_ptr<PangoFontDescription> desc(pango_font_description_from_string(font), pango_font_description_free); |
520 | - pango_font_description_set_weight(desc.get(), PANGO_WEIGHT_BOLD); |
521 | - int font_size = FONT_SIZE; |
522 | - pango_font_description_set_size (desc.get(), font_size * PANGO_SCALE); |
523 | - |
524 | - pango_layout_set_font_description(layout, desc.get()); |
525 | - pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); |
526 | - |
527 | - double belt_w = static_cast<double>(pixbuf_width - SHADOW_SIDE_PADDING * 2); |
528 | - double belt_h = static_cast<double>(pixbuf_height - SHADOW_BOTTOM_PADDING); |
529 | - |
530 | - double max_text_width = belt_w; |
531 | - if (has_emblem) |
532 | - { |
533 | - const double CURVE_MID_X = 0.4 * (belt_h / 20.0 * 24.0); |
534 | - const int category_pb_w = gdk_pixbuf_get_width(category_pixbuf); |
535 | - max_text_width = belt_w - CURVE_MID_X - category_pb_w - EMBLEM_PADDING; |
536 | - } |
537 | - |
538 | - pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); |
539 | - pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); |
540 | - |
541 | - glib::String escaped_text(g_markup_escape_text(detail_text, -1)); |
542 | - pango_layout_set_markup(layout, escaped_text, -1); |
543 | - |
544 | - pango_context = pango_layout_get_context(layout); // is not ref'ed |
545 | - pango_cairo_context_set_font_options(pango_context, |
546 | - gdk_screen_get_font_options(screen)); |
547 | - pango_cairo_context_set_resolution(pango_context, |
548 | - dpi == -1 ? 96.0f : dpi/(float) PANGO_SCALE); |
549 | - pango_layout_context_changed(layout); |
550 | - |
551 | - // find proper font size |
552 | - int text_width, text_height; |
553 | - pango_layout_get_pixel_size(layout, &text_width, nullptr); |
554 | - while (text_width > max_text_width && font_size > MIN_FONT_SIZE) |
555 | - { |
556 | - font_size--; |
557 | - pango_font_description_set_size (desc.get(), font_size * PANGO_SCALE); |
558 | - pango_layout_set_font_description(layout, desc.get()); |
559 | - pango_layout_get_pixel_size(layout, &text_width, nullptr); |
560 | - } |
561 | - pango_layout_set_width(layout, static_cast<int>(max_text_width * PANGO_SCALE)); |
562 | - |
563 | - cairo_set_operator(cr.get(), CAIRO_OPERATOR_CLEAR); |
564 | - cairo_paint(cr.get()); |
565 | - |
566 | - cairo_set_operator(cr.get(), CAIRO_OPERATOR_OVER); |
567 | - |
568 | - // this should be #dd4814 |
569 | - const double ORANGE_R = 0.86666; |
570 | - const double ORANGE_G = 0.28235; |
571 | - const double ORANGE_B = 0.07843; |
572 | - |
573 | - // translate to make space for the shadow |
574 | - cairo_save(cr.get()); |
575 | - cairo_translate(cr.get(), 1.0, 1.0); |
576 | - |
577 | - cairo_set_source_rgba(cr.get(), ORANGE_R, ORANGE_G, ORANGE_B, 1.0); |
578 | - |
579 | - // base ribbon |
580 | - cairo_rectangle(cr.get(), 0.0, 0.0, belt_w, belt_h); |
581 | - cairo_fill_preserve(cr.get()); |
582 | - |
583 | - // hightlight on left edge |
584 | - std::shared_ptr<cairo_pattern_t> pattern( |
585 | - cairo_pattern_create_linear(0.0, 0.0, belt_w, 0.0), |
586 | - cairo_pattern_destroy); |
587 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 0.0, 1.0, 1.0, 1.0, 0.235294); |
588 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 0.02, 1.0, 1.0, 1.0, 0.0); |
589 | - // and on right one |
590 | - if (!has_emblem) |
591 | - { |
592 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 0.98, 1.0, 1.0, 1.0, 0.0); |
593 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 1.0, 1.0, 1.0, 0.235294); |
594 | - } |
595 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 1.0, 1.0, 1.0, 0.0); |
596 | - |
597 | - cairo_set_source(cr.get(), pattern.get()); |
598 | - cairo_fill(cr.get()); |
599 | - |
600 | - if (has_emblem) |
601 | - { |
602 | - // size of the emblem |
603 | - const int category_pb_w = gdk_pixbuf_get_width(category_pixbuf); |
604 | - const int category_pb_h = gdk_pixbuf_get_height(category_pixbuf); |
605 | - |
606 | - // control and end points for the two bezier curves |
607 | - const double CURVE_X_MULT = belt_h / 20.0 * 24.0; |
608 | - const double CURVE_CP1_X = 0.25 * CURVE_X_MULT; |
609 | - const double CURVE_CP2_X = 0.2521 * CURVE_X_MULT; |
610 | - const double CURVE_CP3_X = 0.36875 * CURVE_X_MULT; |
611 | - const double CURVE_CP4_X = 0.57875 * CURVE_X_MULT; |
612 | - const double CURVE_CP5_X = 0.705417 * CURVE_X_MULT; |
613 | - const double CURVE_CP6_X = 0.723333 * CURVE_X_MULT; |
614 | - const double CURVE_CP7_X = 0.952375 * CURVE_X_MULT; |
615 | - |
616 | - const double CURVE_Y1 = 0.9825; |
617 | - const double CURVE_Y2 = 0.72725; |
618 | - const double CURVE_Y3 = 0.27275; |
619 | - |
620 | - const double CURVE_START_X = belt_w - category_pb_w - CURVE_CP5_X - EMBLEM_PADDING; |
621 | - //const double CURVE_END_X = CURVE_START_X + CURVE_X_MULT; |
622 | - |
623 | - cairo_set_source_rgba(cr.get(), 1.0, 1.0, 1.0, 1.0); |
624 | - |
625 | - // paint the curved area |
626 | - cairo_move_to(cr.get(), CURVE_START_X, belt_h); |
627 | - cairo_curve_to(cr.get(), CURVE_START_X + CURVE_CP1_X, belt_h, |
628 | - CURVE_START_X + CURVE_CP2_X, CURVE_Y1 * belt_h, |
629 | - CURVE_START_X + CURVE_CP3_X, CURVE_Y2 * belt_h); |
630 | - cairo_line_to(cr.get(), CURVE_START_X + CURVE_CP4_X, CURVE_Y3 * belt_h); |
631 | - cairo_curve_to(cr.get(), CURVE_START_X + CURVE_CP5_X, 0.0, |
632 | - CURVE_START_X + CURVE_CP6_X, 0.0, |
633 | - CURVE_START_X + CURVE_CP7_X, 0.0); |
634 | - cairo_line_to(cr.get(), belt_w, 0.0); |
635 | - cairo_line_to(cr.get(), belt_w, belt_h); |
636 | - cairo_close_path(cr.get()); |
637 | - cairo_fill(cr.get()); |
638 | - |
639 | - // and the highlight |
640 | - pattern.reset(cairo_pattern_create_linear(CURVE_START_X, 0.0, belt_w, 0.0), |
641 | - cairo_pattern_destroy); |
642 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 0.0, 1.0, 1.0, 1.0, 0.0); |
643 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 0.95, 1.0, 1.0, 1.0, 0.0); |
644 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 0.0, 0.0, 0.0, 0.235294); |
645 | - cairo_set_source(cr.get(), pattern.get()); |
646 | - cairo_rectangle(cr.get(), CURVE_START_X, 0.0, belt_w - CURVE_START_X, belt_h); |
647 | - cairo_fill(cr.get()); |
648 | - |
649 | - // paint the emblem |
650 | - double category_pb_x = belt_w - category_pb_w - EMBLEM_PADDING - 1; |
651 | - gdk_cairo_set_source_pixbuf(cr.get(), category_pixbuf, |
652 | - category_pb_x, (belt_h - category_pb_h) / 2); |
653 | - cairo_paint(cr.get()); |
654 | - } |
655 | - |
656 | - // paint the text |
657 | - cairo_set_source_rgba(cr.get(), 1.0, 1.0, 1.0, 1.0); |
658 | - cairo_move_to(cr.get(), 0.0, belt_h / 2); |
659 | - pango_layout_get_pixel_size(layout, nullptr, &text_height); |
660 | - // current point is now in the middle of the stripe, need to translate |
661 | - // it, so that the text is centered |
662 | - cairo_rel_move_to(cr.get(), 0.0, text_height / -2.0); |
663 | - pango_cairo_show_layout(cr.get(), layout); |
664 | - |
665 | - // paint the shadow |
666 | - cairo_restore(cr.get()); |
667 | - |
668 | - pattern.reset(cairo_pattern_create_linear(0.0, belt_h, 0.0, belt_h + SHADOW_BOTTOM_PADDING), |
669 | - cairo_pattern_destroy); |
670 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 0.0, 0.0, 0.0, 0.0, 0.235294); |
671 | - cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 0.0, 0.0, 0.0, 0.0); |
672 | - |
673 | - cairo_set_source(cr.get(), pattern.get()); |
674 | - |
675 | - cairo_rectangle(cr.get(), 0.0, belt_h, belt_w, SHADOW_BOTTOM_PADDING); |
676 | - cairo_fill(cr.get()); |
677 | - |
678 | - cairo_set_source_rgba(cr.get(), 0.0, 0.0, 0.0, 0.1); |
679 | - cairo_rectangle(cr.get(), 0.0, 1.0, 1.0, belt_h); |
680 | - cairo_fill(cr.get()); |
681 | - |
682 | - cairo_rectangle(cr.get(), belt_w, 1.0, 1.0, belt_h); |
683 | - cairo_fill(cr.get()); |
684 | - |
685 | - // FIXME: going from image_surface to pixbuf, and then to texture :( |
686 | - glib::Object<GdkPixbuf> detail_pb( |
687 | - gdk_pixbuf_get_from_surface(cairo_graphics.GetSurface(), |
688 | - 0, 0, |
689 | - cairo_graphics.GetWidth(), |
690 | - cairo_graphics.GetHeight())); |
691 | - |
692 | - int y_pos = icon_h - pixbuf_height - max_font_height / 2; |
693 | - gdk_pixbuf_composite(detail_pb, result, // src, dest |
694 | - 0, // dest_x |
695 | - y_pos, // dest_y |
696 | - pixbuf_width, // dest_w |
697 | - pixbuf_height, // dest_h |
698 | - 0, // offset_x |
699 | - y_pos, // offset_y |
700 | - 1.0, 1.0, // scale_x, scale_y |
701 | - GDK_INTERP_NEAREST, // interpolation |
702 | - 255); // src_alpha |
703 | - } |
704 | - |
705 | - idle_id = g_idle_add(LoadIconComplete, this); |
706 | - } |
707 | - |
708 | - void BaseIconLoaded(std::string const& base_icon_string, |
709 | - int base_max_width, int base_max_height, |
710 | - glib::Object<GdkPixbuf> const& base_pixbuf, |
711 | - glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon) |
712 | - { |
713 | - helper_handle = 0; |
714 | - if (base_pixbuf) |
715 | - { |
716 | - LOG_DEBUG(logger) << "Base icon loaded: '" << base_icon_string << |
717 | - "' size: " << gdk_pixbuf_get_width(base_pixbuf) << 'x' << |
718 | - gdk_pixbuf_get_height(base_pixbuf); |
719 | - |
720 | - int result_width, result_height, dest_x, dest_y; |
721 | - if (unity_protocol_annotated_icon_get_use_small_icon(anno_icon)) |
722 | - { |
723 | - result_width = max_width > 0 ? max_width : MAX(max_height, gdk_pixbuf_get_width(base_pixbuf)); |
724 | - result_height = max_height > 0 ? max_height : MAX(max_width, gdk_pixbuf_get_height(base_pixbuf)); |
725 | - |
726 | - dest_x = (result_width - gdk_pixbuf_get_width(base_pixbuf)) / 2; |
727 | - dest_y = (result_height - gdk_pixbuf_get_height(base_pixbuf)) / 2; |
728 | - } |
729 | - else |
730 | - { |
731 | - result_width = gdk_pixbuf_get_width(base_pixbuf) + RIBBON_PADDING * 2; |
732 | - result_height = gdk_pixbuf_get_height(base_pixbuf); |
733 | - |
734 | - dest_x = RIBBON_PADDING; |
735 | - dest_y = 0; |
736 | - } |
737 | - // we need extra space for the ribbon |
738 | - result = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, |
739 | - result_width, result_height); |
740 | - gdk_pixbuf_fill(result, 0x0); |
741 | - gdk_pixbuf_copy_area(base_pixbuf, 0, 0, |
742 | - gdk_pixbuf_get_width(base_pixbuf), |
743 | - gdk_pixbuf_get_height(base_pixbuf), |
744 | - result, |
745 | - dest_x, dest_y); |
746 | - // FIXME: can we composite the pixbuf in helper thread? |
747 | - UnityProtocolCategoryType category = unity_protocol_annotated_icon_get_category(anno_icon); |
748 | - auto helper_slot = sigc::bind(sigc::mem_fun(this, &IconLoaderTask::CategoryIconLoaded), anno_icon); |
749 | - int max_font_height; |
750 | - CalculateTextHeight(nullptr, &max_font_height); |
751 | - unsigned cat_size = max_font_height * 9 / 8; |
752 | - switch (category) |
753 | - { |
754 | - case UNITY_PROTOCOL_CATEGORY_TYPE_NONE: |
755 | - // rest of the processing is the CategoryIconLoaded, lets invoke it |
756 | - helper_slot("", -1, cat_size, glib::Object<GdkPixbuf>()); |
757 | - break; |
758 | - case UNITY_PROTOCOL_CATEGORY_TYPE_APPLICATION: |
759 | - helper_handle = |
760 | - impl->LoadFromFilename(PKGDATADIR"/emblem_apps.svg", -1, cat_size, helper_slot); |
761 | - break; |
762 | - case UNITY_PROTOCOL_CATEGORY_TYPE_BOOK: |
763 | - helper_handle = |
764 | - impl->LoadFromFilename(PKGDATADIR"/emblem_books.svg", -1, cat_size, helper_slot); |
765 | - break; |
766 | - case UNITY_PROTOCOL_CATEGORY_TYPE_MUSIC: |
767 | - helper_handle = |
768 | - impl->LoadFromFilename(PKGDATADIR"/emblem_music.svg", -1, cat_size, helper_slot); |
769 | - break; |
770 | - case UNITY_PROTOCOL_CATEGORY_TYPE_MOVIE: |
771 | - helper_handle = |
772 | - impl->LoadFromFilename(PKGDATADIR"/emblem_video.svg", -1, cat_size, helper_slot); |
773 | - break; |
774 | - case UNITY_PROTOCOL_CATEGORY_TYPE_CLOTHES: |
775 | - case UNITY_PROTOCOL_CATEGORY_TYPE_SHOES: |
776 | - helper_handle = |
777 | - impl->LoadFromFilename(PKGDATADIR"/emblem_clothes.svg", -1, cat_size, helper_slot); |
778 | - break; |
779 | - case UNITY_PROTOCOL_CATEGORY_TYPE_GAMES: |
780 | - case UNITY_PROTOCOL_CATEGORY_TYPE_ELECTRONICS: |
781 | - case UNITY_PROTOCOL_CATEGORY_TYPE_COMPUTERS: |
782 | - case UNITY_PROTOCOL_CATEGORY_TYPE_OFFICE: |
783 | - case UNITY_PROTOCOL_CATEGORY_TYPE_HOME: |
784 | - case UNITY_PROTOCOL_CATEGORY_TYPE_GARDEN: |
785 | - case UNITY_PROTOCOL_CATEGORY_TYPE_PETS: |
786 | - case UNITY_PROTOCOL_CATEGORY_TYPE_TOYS: |
787 | - case UNITY_PROTOCOL_CATEGORY_TYPE_CHILDREN: |
788 | - case UNITY_PROTOCOL_CATEGORY_TYPE_BABY: |
789 | - case UNITY_PROTOCOL_CATEGORY_TYPE_WATCHES: |
790 | - case UNITY_PROTOCOL_CATEGORY_TYPE_SPORTS: |
791 | - case UNITY_PROTOCOL_CATEGORY_TYPE_OUTDOORS: |
792 | - case UNITY_PROTOCOL_CATEGORY_TYPE_GROCERY: |
793 | - case UNITY_PROTOCOL_CATEGORY_TYPE_HEALTH: |
794 | - case UNITY_PROTOCOL_CATEGORY_TYPE_BEAUTY: |
795 | - case UNITY_PROTOCOL_CATEGORY_TYPE_DIY: |
796 | - case UNITY_PROTOCOL_CATEGORY_TYPE_TOOLS: |
797 | - case UNITY_PROTOCOL_CATEGORY_TYPE_CAR: |
798 | - default: |
799 | - helper_handle = |
800 | - impl->LoadFromFilename(PKGDATADIR"/emblem_others.svg", -1, cat_size, helper_slot); |
801 | - break; |
802 | - } |
803 | - } |
804 | - else |
805 | - { |
806 | - result = nullptr; |
807 | - idle_id = g_idle_add(LoadIconComplete, this); |
808 | - } |
809 | - } |
810 | - |
811 | - void PushSchedulerJob() |
812 | - { |
813 | -#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 |
814 | - g_io_scheduler_push_job (LoaderJobFunc, this, nullptr, G_PRIORITY_HIGH_IDLE, nullptr); |
815 | -#else |
816 | - glib::Object<GTask> task(g_task_new(nullptr, nullptr, [] (GObject*, GAsyncResult*, gpointer data) { |
817 | - auto self = static_cast<IconLoaderTask*>(data); |
818 | - self->LoadIconComplete(data); |
819 | - }, this)); |
820 | - |
821 | - g_task_set_priority(task, G_PRIORITY_HIGH_IDLE); |
822 | - g_task_set_task_data(task, this, nullptr); |
823 | - |
824 | - g_task_run_in_thread(task, LoaderJobFunc); |
825 | -#endif |
826 | - } |
827 | - |
828 | - // Loading/rendering of pixbufs is done in a separate thread |
829 | -#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 |
830 | - static gboolean LoaderJobFunc(GIOSchedulerJob* job, GCancellable *canc, gpointer data) |
831 | -#else |
832 | - static void LoaderJobFunc(GTask* job, gpointer source_object, gpointer data, GCancellable* canc) |
833 | -#endif |
834 | - { |
835 | - auto task = static_cast<IconLoaderTask*>(data); |
836 | - |
837 | - // careful here this is running in non-main thread |
838 | - if (task->icon_info) |
839 | - { |
840 | - task->result = ::gtk_icon_info_load_icon(task->icon_info, &task->error); |
841 | - } |
842 | - else if (task->type == REQUEST_TYPE_URI) |
843 | - { |
844 | - glib::Object<GFile> file(::g_file_new_for_uri(task->data.c_str())); |
845 | - glib::String contents; |
846 | - gsize length = 0; |
847 | - |
848 | - if (g_file_load_contents(file, canc, &contents, &length, |
849 | - nullptr, &task->error)) |
850 | - { |
851 | - glib::Object<GInputStream> stream( |
852 | - g_memory_input_stream_new_from_data(contents.Value(), length, nullptr)); |
853 | - |
854 | - task->result = gdk_pixbuf_new_from_stream_at_scale(stream, |
855 | - task->max_width, |
856 | - task->max_height, |
857 | - TRUE, |
858 | - canc, |
859 | - &task->error); |
860 | - g_input_stream_close(stream, canc, nullptr); |
861 | - } |
862 | - } |
863 | - |
864 | -#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 |
865 | - g_io_scheduler_job_send_to_mainloop_async (job, LoadIconComplete, task, nullptr); |
866 | - return FALSE; |
867 | -#endif |
868 | - } |
869 | - |
870 | - // this will be invoked back in the thread from which push_job was called |
871 | - static gboolean LoadIconComplete(gpointer data) |
872 | - { |
873 | - auto task = static_cast<IconLoaderTask*>(data); |
874 | - auto impl = task->impl; |
875 | - |
876 | - if (task->result.IsType(GDK_TYPE_PIXBUF)) |
877 | - { |
878 | - if (!task->no_cache) impl->cache_[task->key] = task->result; |
879 | - } |
880 | - else |
881 | - { |
882 | - if (task->result) |
883 | - task->result = nullptr; |
884 | - |
885 | - LOG_WARNING(logger) << "Unable to load icon " << task->data |
886 | - << " at size " |
887 | - << task->max_width << "x" << task->max_height |
888 | - << ": " << task->error; |
889 | - } |
890 | - |
891 | - impl->finished_tasks_.push_back(task); |
892 | - |
893 | - if (!impl->coalesce_timeout_) |
894 | - { |
895 | - // we're using lower priority than the GIOSchedulerJob uses to deliver |
896 | - // results to the mainloop |
897 | - auto prio = static_cast<glib::Source::Priority>(glib::Source::Priority::DEFAULT_IDLE + 40); |
898 | - impl->coalesce_timeout_.reset(new glib::Timeout(40, prio)); |
899 | - impl->coalesce_timeout_->Run(sigc::mem_fun(impl, &Impl::CoalesceTasksCb)); |
900 | - } |
901 | - |
902 | - return FALSE; |
903 | - } |
904 | - }; |
905 | - |
906 | - Handle ReturnCachedOrQueue(std::string const& data, |
907 | - int max_width, |
908 | - int max_height, |
909 | - IconLoaderCallback slot, |
910 | - IconLoaderRequestType type); |
911 | - |
912 | - Handle QueueTask(std::string const& key, |
913 | - std::string const& data, |
914 | - int max_width, |
915 | - int max_height, |
916 | - IconLoaderCallback slot, |
917 | - IconLoaderRequestType type); |
918 | - |
919 | - std::string Hash(std::string const& data, int max_width, int max_height); |
920 | - |
921 | - bool CacheLookup(std::string const& key, |
922 | - std::string const& data, |
923 | - int max_width, |
924 | - int max_height, |
925 | - IconLoaderCallback slot); |
926 | - |
927 | - // Looping idle callback function |
928 | - bool Iteration(); |
929 | - bool CoalesceTasksCb(); |
930 | - |
931 | -private: |
932 | - std::map<std::string, glib::Object<GdkPixbuf>> cache_; |
933 | - /* FIXME: the reference counting of IconLoaderTasks with shared pointers |
934 | - * is currently somewhat broken, and the queued_tasks_ member is what keeps |
935 | - * it from crashing randomly. |
936 | - * The IconLoader instance is assuming that it is the only owner of the loader |
937 | - * tasks, but when they are being completed in a worker thread, the thread |
938 | - * should own them as well (yet it doesn't), this could cause trouble |
939 | - * in the future... You've been warned! */ |
940 | - std::map<std::string, IconLoaderTask::Ptr> queued_tasks_; |
941 | - std::queue<IconLoaderTask::Ptr> tasks_; |
942 | - std::map<Handle, IconLoaderTask::Ptr> task_map_; |
943 | - std::vector<IconLoaderTask*> finished_tasks_; |
944 | - |
945 | - bool no_load_; |
946 | - GtkIconTheme* theme_; // Not owned. |
947 | - Handle handle_counter_; |
948 | - glib::Source::UniquePtr idle_; |
949 | - glib::Source::UniquePtr coalesce_timeout_; |
950 | - glib::Signal<void, GtkIconTheme*> theme_changed_signal_; |
951 | -}; |
952 | - |
953 | - |
954 | -IconLoader::Impl::Impl() |
955 | + |
956 | +NUX_IMPLEMENT_OBJECT_TYPE(IconLoader); |
957 | + |
958 | + |
959 | +IconLoaderImpl::IconLoaderImpl() |
960 | : // Option to disable loading, if you're testing performance of other things |
961 | no_load_(::getenv("UNITY_ICON_LOADER_DISABLE")) |
962 | , theme_(::gtk_icon_theme_get_default()) |
963 | @@ -802,7 +80,11 @@ |
964 | #endif |
965 | } |
966 | |
967 | -int IconLoader::Impl::LoadFromIconName(std::string const& icon_name, |
968 | +IconLoaderImpl::~IconLoaderImpl() |
969 | +{ |
970 | +} |
971 | + |
972 | +int IconLoaderImpl::LoadFromIconName(std::string const& icon_name, |
973 | int max_width, |
974 | int max_height, |
975 | IconLoaderCallback slot) |
976 | @@ -818,11 +100,12 @@ |
977 | return LoadFromFilename(icon_name, max_width, max_height, slot); |
978 | } |
979 | |
980 | - return ReturnCachedOrQueue(icon_name, max_width, max_height, slot, |
981 | + std::string key(Hash(icon_name, max_width, max_height)); |
982 | + return QueueTask(key, icon_name, max_width, max_height, slot, |
983 | REQUEST_TYPE_ICON_NAME); |
984 | } |
985 | |
986 | -int IconLoader::Impl::LoadFromGIconString(std::string const& gicon_string, |
987 | +int IconLoaderImpl::LoadFromGIconString(std::string const& gicon_string, |
988 | int max_width, |
989 | int max_height, |
990 | IconLoaderCallback slot) |
991 | @@ -832,11 +115,12 @@ |
992 | (max_height >= 0 && max_height < MIN_ICON_SIZE))) |
993 | return 0; |
994 | |
995 | - return ReturnCachedOrQueue(gicon_string, max_width, max_height, slot, |
996 | + std::string key(Hash(gicon_string, max_width, max_height)); |
997 | + return QueueTask(key, gicon_string, max_width, max_height, slot, |
998 | REQUEST_TYPE_GICON_STRING); |
999 | } |
1000 | |
1001 | -int IconLoader::Impl::LoadFromFilename(std::string const& filename, |
1002 | +int IconLoaderImpl::LoadFromFilename(std::string const& filename, |
1003 | int max_width, |
1004 | int max_height, |
1005 | IconLoaderCallback slot) |
1006 | @@ -852,7 +136,7 @@ |
1007 | return LoadFromURI(uri.Str(), max_width, max_height, slot); |
1008 | } |
1009 | |
1010 | -int IconLoader::Impl::LoadFromURI(std::string const& uri, |
1011 | +int IconLoaderImpl::LoadFromURI(std::string const& uri, |
1012 | int max_width, |
1013 | int max_height, |
1014 | IconLoaderCallback slot) |
1015 | @@ -862,61 +146,27 @@ |
1016 | (max_height >= 0 && max_height < MIN_ICON_SIZE))) |
1017 | return 0; |
1018 | |
1019 | - return ReturnCachedOrQueue(uri, max_width, max_height, slot, |
1020 | + std::string key(Hash(uri, max_width, max_height)); |
1021 | + return QueueTask(key, uri, max_width, max_height, slot, |
1022 | REQUEST_TYPE_URI); |
1023 | } |
1024 | |
1025 | -void IconLoader::Impl::DisconnectHandle(Handle handle) |
1026 | +void IconLoaderImpl::DisconnectHandle(Handle handle) |
1027 | { |
1028 | - auto iter = task_map_.find(handle); |
1029 | + auto iter = task_slot_map_.find(handle); |
1030 | |
1031 | - if (iter != task_map_.end()) |
1032 | + if (iter != task_slot_map_.end()) |
1033 | { |
1034 | - iter->second->slot = nullptr; |
1035 | + iter->second->slots_[handle] = nullptr; |
1036 | + task_slot_map_.erase(iter); |
1037 | } |
1038 | } |
1039 | |
1040 | -void IconLoader::Impl::CalculateTextHeight(int* width, int* height) |
1041 | -{ |
1042 | - // FIXME: what about CJK? |
1043 | - const char* const SAMPLE_MAX_TEXT = "Chromium Web Browser"; |
1044 | - GtkSettings* settings = gtk_settings_get_default(); |
1045 | - |
1046 | - nux::CairoGraphics util_cg(CAIRO_FORMAT_ARGB32, 1, 1); |
1047 | - cairo_t* cr = util_cg.GetInternalContext(); |
1048 | - |
1049 | - glib::String font; |
1050 | - int dpi = 0; |
1051 | - g_object_get(settings, |
1052 | - "gtk-font-name", &font, |
1053 | - "gtk-xft-dpi", &dpi, |
1054 | - NULL); |
1055 | - std::shared_ptr<PangoFontDescription> desc(pango_font_description_from_string(font), pango_font_description_free); |
1056 | - pango_font_description_set_weight(desc.get(), PANGO_WEIGHT_BOLD); |
1057 | - pango_font_description_set_size(desc.get(), FONT_SIZE * PANGO_SCALE); |
1058 | - |
1059 | - glib::Object<PangoLayout> layout(pango_cairo_create_layout(cr)); |
1060 | - pango_layout_set_font_description(layout, desc.get()); |
1061 | - pango_layout_set_text(layout, SAMPLE_MAX_TEXT, -1); |
1062 | - |
1063 | - PangoContext* cxt = pango_layout_get_context(layout); |
1064 | - GdkScreen* screen = gdk_screen_get_default(); |
1065 | - pango_cairo_context_set_font_options(cxt, gdk_screen_get_font_options(screen)); |
1066 | - pango_cairo_context_set_resolution(cxt, dpi / (double) PANGO_SCALE); |
1067 | - pango_layout_context_changed(layout); |
1068 | - |
1069 | - PangoRectangle log_rect; |
1070 | - pango_layout_get_extents(layout, NULL, &log_rect); |
1071 | - |
1072 | - if (width) *width = log_rect.width / PANGO_SCALE; |
1073 | - if (height) *height = log_rect.height / PANGO_SCALE; |
1074 | -} |
1075 | - |
1076 | // |
1077 | // Private Methods |
1078 | // |
1079 | |
1080 | -int IconLoader::Impl::ReturnCachedOrQueue(std::string const& data, |
1081 | +int IconLoaderImpl::ReturnCachedOrQueue(std::string const& data, |
1082 | int max_width, |
1083 | int max_height, |
1084 | IconLoaderCallback slot, |
1085 | @@ -932,60 +182,59 @@ |
1086 | return result; |
1087 | } |
1088 | |
1089 | - |
1090 | -int IconLoader::Impl::QueueTask(std::string const& key, |
1091 | +int IconLoaderImpl::QueueTask(std::string const& key, |
1092 | std::string const& data, |
1093 | int max_width, |
1094 | int max_height, |
1095 | IconLoaderCallback slot, |
1096 | IconLoaderRequestType type) |
1097 | { |
1098 | - auto task = std::make_shared<IconLoaderTask>(type, data, |
1099 | - max_width, max_height, |
1100 | - key, slot, |
1101 | - ++handle_counter_, this); |
1102 | + int handle = ++handle_counter_; |
1103 | + |
1104 | auto iter = queued_tasks_.find(key); |
1105 | |
1106 | if (iter != queued_tasks_.end()) |
1107 | { |
1108 | - IconLoaderTask::Ptr const& running_task = iter->second; |
1109 | - running_task->shadow_tasks.push_back(task); |
1110 | - // do NOT push the task into the tasks queue, |
1111 | - // the parent task (which is in the queue) will handle it |
1112 | - task_map_[task->handle] = task; |
1113 | + iter->second->AddSlot(handle, slot); |
1114 | |
1115 | - LOG_DEBUG(logger) << "Appending shadow task " << data |
1116 | + LOG_DEBUG(logger) << "Appending slot for task task " << data |
1117 | << ", queue size now at " << tasks_.size(); |
1118 | |
1119 | - return task->handle; |
1120 | + task_slot_map_[handle] = iter->second; |
1121 | } |
1122 | else |
1123 | { |
1124 | + IconLoaderTask::Ptr task(new IconLoaderTask(type, data, |
1125 | + max_width, max_height, |
1126 | + key, |
1127 | + this)); |
1128 | + task->AddSlot(handle, slot); |
1129 | + |
1130 | queued_tasks_[key] = task; |
1131 | - } |
1132 | - |
1133 | - tasks_.push(task); |
1134 | - task_map_[task->handle] = task; |
1135 | - |
1136 | - LOG_DEBUG(logger) << "Pushing task " << data << " at size " |
1137 | - << max_width << "x" << max_height |
1138 | - << ", queue size now at " << tasks_.size(); |
1139 | - |
1140 | - if (!idle_) |
1141 | - { |
1142 | - idle_.reset(new glib::Idle(sigc::mem_fun(this, &Impl::Iteration), glib::Source::Priority::LOW)); |
1143 | - } |
1144 | - return task->handle; |
1145 | + tasks_.push(task); |
1146 | + |
1147 | + LOG_DEBUG(logger) << "Pushing task " << data << " at size " |
1148 | + << max_width << "x" << max_height |
1149 | + << ", queue size now at " << tasks_.size(); |
1150 | + |
1151 | + if (!timer_) |
1152 | + { |
1153 | + timer_.reset(new glib::Timeout(0, sigc::mem_fun(this, &IconLoaderImpl::Iteration), glib::Source::Priority::LOW)); |
1154 | + } |
1155 | + task_slot_map_[handle] = task; |
1156 | + } |
1157 | + |
1158 | + return handle; |
1159 | } |
1160 | |
1161 | -std::string IconLoader::Impl::Hash(std::string const& data, int max_width, int max_height) |
1162 | +std::string IconLoaderImpl::Hash(std::string const& data, int max_width, int max_height) |
1163 | { |
1164 | std::ostringstream sout; |
1165 | sout << data << ":" << max_width << "x" << max_height; |
1166 | return sout.str(); |
1167 | } |
1168 | |
1169 | -bool IconLoader::Impl::CacheLookup(std::string const& key, |
1170 | +bool IconLoaderImpl::CacheLookup(std::string const& key, |
1171 | std::string const& data, |
1172 | int max_width, |
1173 | int max_height, |
1174 | @@ -1003,24 +252,21 @@ |
1175 | return found; |
1176 | } |
1177 | |
1178 | -bool IconLoader::Impl::CoalesceTasksCb() |
1179 | +bool IconLoaderImpl::CoalesceTasksCb() |
1180 | { |
1181 | for (auto task : finished_tasks_) |
1182 | { |
1183 | task->InvokeSlot(); |
1184 | - |
1185 | - // this was all async, we need to erase the task from the task_map |
1186 | - task_map_.erase(task->handle); |
1187 | - queued_tasks_.erase(task->key); |
1188 | + |
1189 | + queued_tasks_.erase(task->key_); |
1190 | } |
1191 | - |
1192 | finished_tasks_.clear(); |
1193 | coalesce_timeout_.reset(); |
1194 | |
1195 | return false; |
1196 | } |
1197 | |
1198 | -bool IconLoader::Impl::Iteration() |
1199 | +bool IconLoaderImpl::Iteration() |
1200 | { |
1201 | static const int MAX_MICRO_SECS = 1000; |
1202 | util::Timer timer; |
1203 | @@ -1032,10 +278,18 @@ |
1204 | { |
1205 | IconLoaderTask::Ptr const& task = tasks_.front(); |
1206 | |
1207 | - if (task->Process()) |
1208 | - { |
1209 | - task_map_.erase(task->handle); |
1210 | - queued_tasks_.erase(task->key); |
1211 | + // Check cache first. |
1212 | + auto cache_iter = cache_.find(task->key_); |
1213 | + if (cache_iter != cache_.end()) |
1214 | + { |
1215 | + task->result_ = cache_iter->second; |
1216 | + task->InvokeSlot(); |
1217 | + queued_tasks_.erase(task->key_); |
1218 | + } |
1219 | + // Not in cache. process. |
1220 | + else if (task->Process()) |
1221 | + { |
1222 | + queued_tasks_.erase(task->key_); |
1223 | } |
1224 | |
1225 | tasks_.pop(); |
1226 | @@ -1048,17 +302,17 @@ |
1227 | |
1228 | if (queue_empty) |
1229 | { |
1230 | - if (task_map_.empty()) |
1231 | + if (task_slot_map_.empty()) |
1232 | handle_counter_ = 0; |
1233 | |
1234 | - idle_.reset(); |
1235 | + timer_.reset(); |
1236 | } |
1237 | |
1238 | return !queue_empty; |
1239 | } |
1240 | |
1241 | IconLoader::IconLoader() |
1242 | - : pimpl(new Impl()) |
1243 | + : pimpl(new IconLoaderImpl()) |
1244 | { |
1245 | } |
1246 | |
1247 | @@ -1109,5 +363,35 @@ |
1248 | pimpl->DisconnectHandle(handle); |
1249 | } |
1250 | |
1251 | - |
1252 | +IconLoader::Ptr IconLoader::Get(std::string const& name) |
1253 | +{ |
1254 | + auto iter = icon_loaders.find(name); |
1255 | + if (iter == icon_loaders.end()) |
1256 | + { |
1257 | + IconLoader* icon_loader = new IconLoader(); |
1258 | + // Store as a raw pointer, so as not to add a reference |
1259 | + icon_loaders[name] = icon_loader; |
1260 | + |
1261 | + icon_loader->object_destroyed.connect([icon_loader](Object*) |
1262 | + { |
1263 | + auto iter_delete = icon_loaders.begin(); |
1264 | + for (; iter_delete != icon_loaders.end(); ++iter_delete) |
1265 | + { |
1266 | + if (iter_delete->second == icon_loader) |
1267 | + { |
1268 | + icon_loaders.erase(iter_delete); |
1269 | + break; |
1270 | + } |
1271 | + } |
1272 | + }); |
1273 | + |
1274 | + IconLoader::Ptr ptr; |
1275 | + // Adopt so we don't add the reference. |
1276 | + ptr.Adopt(icon_loader); |
1277 | + return ptr; |
1278 | + } |
1279 | + return IconLoader::Ptr(iter->second); |
1280 | +} |
1281 | + |
1282 | +} |
1283 | } |
1284 | |
1285 | === modified file 'unity-shared/IconLoader.h' |
1286 | --- unity-shared/IconLoader.h 2012-09-12 15:07:07 +0000 |
1287 | +++ unity-shared/IconLoader.h 2013-01-30 14:41:22 +0000 |
1288 | @@ -1,6 +1,6 @@ |
1289 | // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
1290 | /* |
1291 | -* Copyright (C) 2010, 2011 Canonical Ltd |
1292 | +* Copyright (C) 2010-2012 Canonical Ltd |
1293 | * |
1294 | * This program is free software: you can redistribute it and/or modify |
1295 | * it under the terms of the GNU General Public License version 3 as |
1296 | @@ -15,6 +15,7 @@ |
1297 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1298 | * |
1299 | * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> |
1300 | + Nick Dedekind <nick.dedekind@canonical.com> |
1301 | */ |
1302 | |
1303 | #ifndef UNITY_ICON_LOADER_H |
1304 | @@ -25,19 +26,27 @@ |
1305 | #include <memory> |
1306 | #include <gtk/gtk.h> |
1307 | #include <UnityCore/GLibWrapper.h> |
1308 | +#include <Nux/Nux.h> |
1309 | +#include <NuxCore/Object.h> |
1310 | |
1311 | namespace unity |
1312 | { |
1313 | +namespace icon_loader |
1314 | +{ |
1315 | +class IconLoaderImpl; |
1316 | +typedef std::function<void(std::string const&, int, int, glib::Object<GdkPixbuf> const&)> IconLoaderCallback; |
1317 | |
1318 | -class IconLoader : public boost::noncopyable |
1319 | +class IconLoader : public nux::Object |
1320 | { |
1321 | public: |
1322 | - typedef std::function<void(std::string const&, int, int, glib::Object<GdkPixbuf> const&)> IconLoaderCallback; |
1323 | + typedef nux::ObjectPtr<IconLoader> Ptr; |
1324 | + NUX_DECLARE_OBJECT_TYPE(IconLoader, Object); |
1325 | |
1326 | IconLoader(); |
1327 | ~IconLoader(); |
1328 | |
1329 | static IconLoader& GetDefault(); |
1330 | + static IconLoader::Ptr Get(std::string const& name); |
1331 | |
1332 | /** |
1333 | * Each of the Load functions return an opaque handle. The sole use for |
1334 | @@ -67,10 +76,12 @@ |
1335 | void DisconnectHandle(int handle); |
1336 | |
1337 | private: |
1338 | - class Impl; |
1339 | - std::unique_ptr<Impl> pimpl; |
1340 | + std::unique_ptr<IconLoaderImpl> pimpl; |
1341 | }; |
1342 | |
1343 | } |
1344 | |
1345 | +typedef icon_loader::IconLoader IconLoader; |
1346 | +} |
1347 | + |
1348 | #endif // UNITY_ICON_LOADER_H |
1349 | |
1350 | === added file 'unity-shared/IconLoaderImpl.h' |
1351 | --- unity-shared/IconLoaderImpl.h 1970-01-01 00:00:00 +0000 |
1352 | +++ unity-shared/IconLoaderImpl.h 2013-01-30 14:41:22 +0000 |
1353 | @@ -0,0 +1,119 @@ |
1354 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
1355 | +/* |
1356 | +* Copyright (C) 2010-2012 Canonical Ltd |
1357 | +* |
1358 | +* This program is free software: you can redistribute it and/or modify |
1359 | +* it under the terms of the GNU General Public License version 3 as |
1360 | +* published by the Free Software Foundation. |
1361 | +* |
1362 | +* This program is distributed in the hope that it will be useful, |
1363 | +* but WITHOUT ANY WARRANTY; without even the implied warranty of |
1364 | +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1365 | +* GNU General Public License for more details. |
1366 | +* |
1367 | +* You should have received a copy of the GNU General Public License |
1368 | +* along with this program. If not, see <http://www.gnu.org/licenses/>. |
1369 | +* |
1370 | +* Authored by: Neil Jagdish Patel <neil.patel@canonical.com> |
1371 | + Nick Dedekind <nick.dedekind@canonical.com> |
1372 | +*/ |
1373 | + |
1374 | +#ifndef UNITY_ICON_LOADER_IMPL_H |
1375 | +#define UNITY_ICON_LOADER_IMPL_H |
1376 | + |
1377 | +#include "IconLoader.h" |
1378 | +#include "IconLoaderTask.h" |
1379 | +#include <UnityCore/GLibSource.h> |
1380 | +#include <UnityCore/GLibSignal.h> |
1381 | + |
1382 | +namespace unity |
1383 | +{ |
1384 | +namespace icon_loader |
1385 | +{ |
1386 | + |
1387 | +class IconLoaderImpl |
1388 | +{ |
1389 | +public: |
1390 | + // The Handle typedef is used to explicitly indicate which integers are |
1391 | + // infact our opaque handles. |
1392 | + |
1393 | + IconLoaderImpl(); |
1394 | + ~IconLoaderImpl(); |
1395 | + |
1396 | + Handle LoadFromIconName(std::string const& icon_name, |
1397 | + int max_width, |
1398 | + int max_height, |
1399 | + IconLoaderCallback slot); |
1400 | + |
1401 | + Handle LoadFromGIconString(std::string const& gicon_string, |
1402 | + int max_width, |
1403 | + int max_height, |
1404 | + IconLoaderCallback slot); |
1405 | + |
1406 | + Handle LoadFromFilename(std::string const& filename, |
1407 | + int max_width, |
1408 | + int max_height, |
1409 | + IconLoaderCallback slot); |
1410 | + |
1411 | + Handle LoadFromURI(std::string const& uri, |
1412 | + int max_width, |
1413 | + int max_height, |
1414 | + IconLoaderCallback slot); |
1415 | + |
1416 | + void DisconnectHandle(Handle handle); |
1417 | + |
1418 | +private: |
1419 | + |
1420 | + Handle ReturnCachedOrQueue(std::string const& data, |
1421 | + int max_width, |
1422 | + int max_height, |
1423 | + IconLoaderCallback slot, |
1424 | + IconLoaderRequestType type); |
1425 | + |
1426 | + Handle QueueTask(std::string const& key, |
1427 | + std::string const& data, |
1428 | + int max_width, |
1429 | + int max_height, |
1430 | + IconLoaderCallback slot, |
1431 | + IconLoaderRequestType type); |
1432 | + |
1433 | + std::string Hash(std::string const& data, int max_width, int max_height); |
1434 | + |
1435 | + bool CacheLookup(std::string const& key, |
1436 | + std::string const& data, |
1437 | + int max_width, |
1438 | + int max_height, |
1439 | + IconLoaderCallback slot); |
1440 | + |
1441 | + // Looping idle callback function |
1442 | + bool Iteration(); |
1443 | + bool CoalesceTasksCb(); |
1444 | + |
1445 | +private: |
1446 | + std::map<std::string, glib::Object<GdkPixbuf>> cache_; |
1447 | + /* FIXME: the reference counting of IconLoaderTasks with shared pointers |
1448 | + * is currently somewhat broken, and the queued_tasks_ member is what keeps |
1449 | + * it from crashing randomly. |
1450 | + * The IconLoader instance is assuming that it is the only owner of the loader |
1451 | + * tasks, but when they are being completed in a worker thread, the thread |
1452 | + * should own them as well (yet it doesn't), this could cause trouble |
1453 | + * in the future... You've been warned! */ |
1454 | + std::map<std::string, IconLoaderTask::Ptr> queued_tasks_; |
1455 | + std::queue<IconLoaderTask::Ptr> tasks_; |
1456 | + std::map<Handle, IconLoaderTask::Ptr> task_slot_map_; |
1457 | + std::vector<IconLoaderTask::Ptr> finished_tasks_; |
1458 | + |
1459 | + bool no_load_; |
1460 | + GtkIconTheme* theme_; // Not owned. |
1461 | + Handle handle_counter_; |
1462 | + glib::Source::UniquePtr timer_; |
1463 | + glib::Source::UniquePtr coalesce_timeout_; |
1464 | + glib::Signal<void, GtkIconTheme*> theme_changed_signal_; |
1465 | + |
1466 | + friend class IconLoaderTask; |
1467 | +}; |
1468 | + |
1469 | +} |
1470 | +} |
1471 | + |
1472 | +#endif // UNITY_ICON_LOADER_IMPL_H |
1473 | |
1474 | === added file 'unity-shared/IconLoaderTask.cpp' |
1475 | --- unity-shared/IconLoaderTask.cpp 1970-01-01 00:00:00 +0000 |
1476 | +++ unity-shared/IconLoaderTask.cpp 2013-01-30 14:41:22 +0000 |
1477 | @@ -0,0 +1,756 @@ |
1478 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
1479 | +/* |
1480 | +* Copyright (C) 2010-2012 Canonical Ltd |
1481 | +* |
1482 | +* This program is free software: you can redistribute it and/or modify |
1483 | +* it under the terms of the GNU General Public License version 3 as |
1484 | +* published by the Free Software Foundation. |
1485 | +* |
1486 | +* This program is distributed in the hope that it will be useful, |
1487 | +* but WITHOUT ANY WARRANTY; without even the implied warranty of |
1488 | +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1489 | +* GNU General Public License for more details. |
1490 | +* |
1491 | +* You should have received a copy of the GNU General Public License |
1492 | +* along with this program. If not, see <http://www.gnu.org/licenses/>. |
1493 | +* |
1494 | +* Authored by: Neil Jagdish Patel <neil.patel@canonical.com> |
1495 | + Nick Dedekind <nick.dedekind@canonical.com> |
1496 | +*/ |
1497 | + |
1498 | +#include "IconLoaderTask.h" |
1499 | +#include "IconLoaderImpl.h" |
1500 | + |
1501 | +#include <Nux/Nux.h> |
1502 | +#include <NuxCore/Logger.h> |
1503 | +#include <NuxGraphics/CairoGraphics.h> |
1504 | + |
1505 | +#include <boost/algorithm/string.hpp> |
1506 | +#include "config.h" |
1507 | + |
1508 | +namespace unity |
1509 | +{ |
1510 | +DECLARE_LOGGER(logger, "unity.iconloader.task"); |
1511 | + |
1512 | +namespace |
1513 | +{ |
1514 | +const int RIBBON_PADDING = 2; |
1515 | +const int FONT_SIZE = 8; |
1516 | +const int MIN_FONT_SIZE = 5; |
1517 | + |
1518 | +const int COALESCE_TIMEOUT = 200; |
1519 | +} |
1520 | + |
1521 | +namespace icon_loader |
1522 | +{ |
1523 | + |
1524 | +NUX_IMPLEMENT_OBJECT_TYPE(IconLoaderTask); |
1525 | + |
1526 | +IconLoaderTask::IconLoaderTask(IconLoaderRequestType type_, |
1527 | + std::string const& data_, |
1528 | + int max_width_, |
1529 | + int max_height_, |
1530 | + std::string const& key_, |
1531 | + IconLoaderImpl* self_) |
1532 | +: Object(false) |
1533 | +, type_(type_) |
1534 | +, data_(data_) |
1535 | +, max_width_(max_width_) |
1536 | +, max_height_(max_height_) |
1537 | +, key_(key_) |
1538 | +, impl_(self_) |
1539 | +, icon_info_(nullptr) |
1540 | +, no_cache_(false) |
1541 | +, helper_handle_(0) |
1542 | +, finished_(false) |
1543 | +{ |
1544 | +} |
1545 | + |
1546 | +IconLoaderTask::~IconLoaderTask() |
1547 | +{ |
1548 | + if (icon_info_) |
1549 | + ::gtk_icon_info_free(icon_info_); |
1550 | + if (helper_handle_ != 0) |
1551 | + impl_->DisconnectHandle(helper_handle_); |
1552 | +} |
1553 | + |
1554 | +void IconLoaderTask::AddSlot(Handle handle, IconLoaderCallback slot) |
1555 | +{ |
1556 | + slots_[handle] = slot; |
1557 | + if (finished_) |
1558 | + { |
1559 | + // if the icon fetching is finished, call LoadIconComplete. |
1560 | + if (!timer_complete_) |
1561 | + timer_complete_.reset(new glib::Timeout(0, sigc::mem_fun(this, &IconLoaderTask::LoadIconComplete))); |
1562 | + } |
1563 | +} |
1564 | + |
1565 | + |
1566 | +void IconLoaderTask::InvokeSlot() |
1567 | +{ |
1568 | + for(std::pair<Handle, IconLoaderCallback> slot : slots_) |
1569 | + { |
1570 | + if (slot.second) |
1571 | + slot.second(data_, max_width_, max_height_, result_); |
1572 | + impl_->DisconnectHandle(slot.first); |
1573 | + } |
1574 | + // For task caching, we clear slots so next call will not call again. |
1575 | + slots_.clear(); |
1576 | +} |
1577 | + |
1578 | +bool IconLoaderTask::Process() |
1579 | +{ |
1580 | + LOG_DEBUG(logger) << "Processing " << data_ << " at size " << max_height_; |
1581 | + |
1582 | + // Rely on the compiler to tell us if we miss a new type |
1583 | + switch (type_) |
1584 | + { |
1585 | + case REQUEST_TYPE_ICON_NAME: |
1586 | + return ProcessIconNameTask(); |
1587 | + case REQUEST_TYPE_GICON_STRING: |
1588 | + return ProcessGIconTask(); |
1589 | + case REQUEST_TYPE_URI: |
1590 | + return ProcessURITask(); |
1591 | + } |
1592 | + |
1593 | + LOG_WARNING(logger) << "Request type " << type_ |
1594 | + << " is not supported (" << data_ |
1595 | + << " " << max_width_ << "x" << max_height_ << ")"; |
1596 | + result_ = nullptr; |
1597 | + InvokeSlot(); |
1598 | + |
1599 | + return true; |
1600 | +} |
1601 | + |
1602 | +bool IconLoaderTask::ProcessIconNameTask() |
1603 | +{ |
1604 | + int size = max_height_ < 0 ? max_width_ : (max_width_ < 0 ? max_height_ : MIN(max_height_, max_width_)); |
1605 | + GtkIconInfo* info = ::gtk_icon_theme_lookup_icon(impl_->theme_, data_.c_str(), |
1606 | + size, static_cast<GtkIconLookupFlags>(0)); |
1607 | + if (info) |
1608 | + { |
1609 | + icon_info_ = info; |
1610 | + PushSchedulerJob(); |
1611 | + |
1612 | + return false; |
1613 | + } |
1614 | + else |
1615 | + { |
1616 | + LOG_WARNING(logger) << "Unable to load icon " << data_ |
1617 | + << " at size " << size; |
1618 | + } |
1619 | + |
1620 | + result_ = nullptr; |
1621 | + InvokeSlot(); |
1622 | + |
1623 | + return true; |
1624 | +} |
1625 | + |
1626 | +bool IconLoaderTask::ProcessGIconTask() |
1627 | +{ |
1628 | + glib::Error error; |
1629 | + glib::Object<GIcon> icon(::g_icon_new_for_string(data_.c_str(), &error)); |
1630 | + int size = max_height_ < 0 ? max_width_ : (max_width_ < 0 ? max_height_ : MIN(max_height_, max_width_)); |
1631 | + |
1632 | + if (icon.IsType(UNITY_PROTOCOL_TYPE_ANNOTATED_ICON)) |
1633 | + { |
1634 | + glib::Object<UnityProtocolAnnotatedIcon> anno(glib::object_cast<UnityProtocolAnnotatedIcon>(icon)); |
1635 | + GIcon* base_icon = unity_protocol_annotated_icon_get_icon(anno); |
1636 | + glib::String gicon_string(g_icon_to_string(base_icon)); |
1637 | + |
1638 | + // ensure that annotated icons aren't cached by the IconLoader |
1639 | + no_cache_ = true; |
1640 | + |
1641 | + auto helper_slot = sigc::bind(sigc::mem_fun(this, &IconLoaderTask::BaseIconLoaded), anno); |
1642 | + int base_icon_width, base_icon_height; |
1643 | + if (unity_protocol_annotated_icon_get_use_small_icon(anno)) |
1644 | + { |
1645 | + // FIXME: although this pretends to be generic, we're just making |
1646 | + // sure that icons requested to have 96px will be 64 |
1647 | + base_icon_width = max_width_ > 0 ? max_width_ * 2 / 3 : max_width_; |
1648 | + base_icon_height = max_height_ > 0 ? max_height_ * 2 / 3 : max_height_; |
1649 | + } |
1650 | + else |
1651 | + { |
1652 | + base_icon_width = max_width_ > 0 ? max_width_ - RIBBON_PADDING * 2 : -1; |
1653 | + base_icon_height = base_icon_width < 0 ? max_height_ - RIBBON_PADDING *2 : max_height_; |
1654 | + } |
1655 | + |
1656 | + // If we create a child task, we dont want to delete this task until the child is finished. |
1657 | + Reference(); |
1658 | + helper_handle_ = impl_->LoadFromGIconString(gicon_string.Str(), |
1659 | + base_icon_width, |
1660 | + base_icon_height, |
1661 | + helper_slot); |
1662 | + // no child? |
1663 | + if (helper_handle_ == 0) |
1664 | + { |
1665 | + UnReference(); |
1666 | + LOG_WARNING(logger) << "Invalid anotated icon '" << gicon_string.Str() << "'"; |
1667 | + return true; |
1668 | + } |
1669 | + return false; |
1670 | + } |
1671 | + else if (icon.IsType(G_TYPE_FILE_ICON)) |
1672 | + { |
1673 | + // [trasfer none] |
1674 | + GFile* file = ::g_file_icon_get_file(G_FILE_ICON(icon.RawPtr())); |
1675 | + glib::String uri(::g_file_get_uri(file)); |
1676 | + |
1677 | + type_ = REQUEST_TYPE_URI; |
1678 | + data_ = uri.Str(); |
1679 | + |
1680 | + return ProcessURITask(); |
1681 | + } |
1682 | + else if (icon.IsType(G_TYPE_ICON)) |
1683 | + { |
1684 | + GtkIconInfo* info = ::gtk_icon_theme_lookup_by_gicon(impl_->theme_, icon, size, |
1685 | + static_cast<GtkIconLookupFlags>(0)); |
1686 | + if (info) |
1687 | + { |
1688 | + icon_info_ = info; |
1689 | + PushSchedulerJob(); |
1690 | + |
1691 | + return false; |
1692 | + } |
1693 | + else |
1694 | + { |
1695 | + // There is some funkiness in some programs where they install |
1696 | + // their icon to /usr/share/icons/hicolor/apps/, but they |
1697 | + // name the Icon= key as `foo.$extension` which breaks loading |
1698 | + // So we can try and work around that here. |
1699 | + |
1700 | + if (boost::iends_with(data_, ".png") || |
1701 | + boost::iends_with(data_, ".xpm") || |
1702 | + boost::iends_with(data_, ".gif") || |
1703 | + boost::iends_with(data_, ".jpg")) |
1704 | + { |
1705 | + data_ = data_.substr(0, data_.size() - 4); |
1706 | + return ProcessIconNameTask(); |
1707 | + } |
1708 | + else |
1709 | + { |
1710 | + LOG_WARNING(logger) << "Unable to load icon " << data_ |
1711 | + << " at size " << size; |
1712 | + } |
1713 | + } |
1714 | + } |
1715 | + else |
1716 | + { |
1717 | + LOG_WARNING(logger) << "Unable to load icon " << data_ |
1718 | + << " at size " << size << ": " << error; |
1719 | + } |
1720 | + |
1721 | + InvokeSlot(); |
1722 | + return true; |
1723 | +} |
1724 | + |
1725 | +bool IconLoaderTask::ProcessURITask() |
1726 | +{ |
1727 | + PushSchedulerJob(); |
1728 | + |
1729 | + return false; |
1730 | +} |
1731 | + |
1732 | +void IconLoaderTask::CategoryIconLoaded(std::string const& base_icon_string, |
1733 | + int max_width, int max_height, |
1734 | + glib::Object<GdkPixbuf> const& category_pixbuf, |
1735 | + glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon) |
1736 | +{ |
1737 | + // Called reference before we added this slot to ensure we didnt delete this. |
1738 | + if (UnReference()) { return; } |
1739 | + |
1740 | + helper_handle_ = 0; |
1741 | + bool has_emblem = category_pixbuf; |
1742 | + |
1743 | + const gchar* detail_text = unity_protocol_annotated_icon_get_ribbon(anno_icon); |
1744 | + if (detail_text) |
1745 | + { |
1746 | + const int SHADOW_BOTTOM_PADDING = 2; |
1747 | + const int SHADOW_SIDE_PADDING = 1; |
1748 | + const int EMBLEM_PADDING = 2; |
1749 | + int icon_w = gdk_pixbuf_get_width(result_); |
1750 | + int icon_h = gdk_pixbuf_get_height(result_); |
1751 | + |
1752 | + int max_font_height; |
1753 | + CalculateTextHeight(nullptr, &max_font_height); |
1754 | + |
1755 | + // FIXME: design wants the tags 2px wider than the original icon |
1756 | + int pixbuf_width = icon_w; |
1757 | + int pixbuf_height = max_font_height * 5 / 4 + SHADOW_BOTTOM_PADDING; |
1758 | + if (pixbuf_height > icon_h) pixbuf_height = icon_h; |
1759 | + |
1760 | + nux::CairoGraphics cairo_graphics(CAIRO_FORMAT_ARGB32, |
1761 | + pixbuf_width, pixbuf_height); |
1762 | + std::shared_ptr<cairo_t> cr(cairo_graphics.GetContext(), cairo_destroy); |
1763 | + |
1764 | + glib::Object<PangoLayout> layout; |
1765 | + PangoContext* pango_context = NULL; |
1766 | + GdkScreen* screen = gdk_screen_get_default(); // not ref'ed |
1767 | + glib::String font; |
1768 | + int dpi = -1; |
1769 | + |
1770 | + g_object_get(gtk_settings_get_default(), "gtk-font-name", &font, NULL); |
1771 | + g_object_get(gtk_settings_get_default(), "gtk-xft-dpi", &dpi, NULL); |
1772 | + cairo_set_font_options(cr.get(), gdk_screen_get_font_options(screen)); |
1773 | + layout = pango_cairo_create_layout(cr.get()); |
1774 | + std::shared_ptr<PangoFontDescription> desc(pango_font_description_from_string(font), pango_font_description_free); |
1775 | + pango_font_description_set_weight(desc.get(), PANGO_WEIGHT_BOLD); |
1776 | + int font_size = FONT_SIZE; |
1777 | + pango_font_description_set_size (desc.get(), font_size * PANGO_SCALE); |
1778 | + |
1779 | + pango_layout_set_font_description(layout, desc.get()); |
1780 | + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); |
1781 | + |
1782 | + double belt_w = static_cast<double>(pixbuf_width - SHADOW_SIDE_PADDING * 2); |
1783 | + double belt_h = static_cast<double>(pixbuf_height - SHADOW_BOTTOM_PADDING); |
1784 | + |
1785 | + double max_text_width = belt_w; |
1786 | + if (has_emblem) |
1787 | + { |
1788 | + const double CURVE_MID_X = 0.4 * (belt_h / 20.0 * 24.0); |
1789 | + const int category_pb_w = gdk_pixbuf_get_width(category_pixbuf); |
1790 | + max_text_width = belt_w - CURVE_MID_X - category_pb_w - EMBLEM_PADDING; |
1791 | + } |
1792 | + |
1793 | + pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); |
1794 | + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); |
1795 | + |
1796 | + glib::String escaped_text(g_markup_escape_text(detail_text, -1)); |
1797 | + pango_layout_set_markup(layout, escaped_text, -1); |
1798 | + |
1799 | + pango_context = pango_layout_get_context(layout); // is not ref'ed |
1800 | + pango_cairo_context_set_font_options(pango_context, |
1801 | + gdk_screen_get_font_options(screen)); |
1802 | + pango_cairo_context_set_resolution(pango_context, |
1803 | + dpi == -1 ? 96.0f : dpi/(float) PANGO_SCALE); |
1804 | + pango_layout_context_changed(layout); |
1805 | + |
1806 | + // find proper font size |
1807 | + int text_width, text_height; |
1808 | + pango_layout_get_pixel_size(layout, &text_width, nullptr); |
1809 | + while (text_width > max_text_width && font_size > MIN_FONT_SIZE) |
1810 | + { |
1811 | + font_size--; |
1812 | + pango_font_description_set_size (desc.get(), font_size * PANGO_SCALE); |
1813 | + pango_layout_set_font_description(layout, desc.get()); |
1814 | + pango_layout_get_pixel_size(layout, &text_width, nullptr); |
1815 | + } |
1816 | + pango_layout_set_width(layout, static_cast<int>(max_text_width * PANGO_SCALE)); |
1817 | + |
1818 | + cairo_set_operator(cr.get(), CAIRO_OPERATOR_CLEAR); |
1819 | + cairo_paint(cr.get()); |
1820 | + |
1821 | + cairo_set_operator(cr.get(), CAIRO_OPERATOR_OVER); |
1822 | + |
1823 | + // this should be #dd4814 |
1824 | + const double ORANGE_R = 0.86666; |
1825 | + const double ORANGE_G = 0.28235; |
1826 | + const double ORANGE_B = 0.07843; |
1827 | + |
1828 | + // translate to make space for the shadow |
1829 | + cairo_save(cr.get()); |
1830 | + cairo_translate(cr.get(), 1.0, 1.0); |
1831 | + |
1832 | + cairo_set_source_rgba(cr.get(), ORANGE_R, ORANGE_G, ORANGE_B, 1.0); |
1833 | + |
1834 | + // base ribbon |
1835 | + cairo_rectangle(cr.get(), 0.0, 0.0, belt_w, belt_h); |
1836 | + cairo_fill_preserve(cr.get()); |
1837 | + |
1838 | + // hightlight on left edge |
1839 | + std::shared_ptr<cairo_pattern_t> pattern( |
1840 | + cairo_pattern_create_linear(0.0, 0.0, belt_w, 0.0), |
1841 | + cairo_pattern_destroy); |
1842 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 0.0, 1.0, 1.0, 1.0, 0.235294); |
1843 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 0.02, 1.0, 1.0, 1.0, 0.0); |
1844 | + // and on right one |
1845 | + if (!has_emblem) |
1846 | + { |
1847 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 0.98, 1.0, 1.0, 1.0, 0.0); |
1848 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 1.0, 1.0, 1.0, 0.235294); |
1849 | + } |
1850 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 1.0, 1.0, 1.0, 0.0); |
1851 | + |
1852 | + cairo_set_source(cr.get(), pattern.get()); |
1853 | + cairo_fill(cr.get()); |
1854 | + |
1855 | + if (has_emblem) |
1856 | + { |
1857 | + // size of the emblem |
1858 | + const int category_pb_w = gdk_pixbuf_get_width(category_pixbuf); |
1859 | + const int category_pb_h = gdk_pixbuf_get_height(category_pixbuf); |
1860 | + |
1861 | + // control and end points for the two bezier curves |
1862 | + const double CURVE_X_MULT = belt_h / 20.0 * 24.0; |
1863 | + const double CURVE_CP1_X = 0.25 * CURVE_X_MULT; |
1864 | + const double CURVE_CP2_X = 0.2521 * CURVE_X_MULT; |
1865 | + const double CURVE_CP3_X = 0.36875 * CURVE_X_MULT; |
1866 | + const double CURVE_CP4_X = 0.57875 * CURVE_X_MULT; |
1867 | + const double CURVE_CP5_X = 0.705417 * CURVE_X_MULT; |
1868 | + const double CURVE_CP6_X = 0.723333 * CURVE_X_MULT; |
1869 | + const double CURVE_CP7_X = 0.952375 * CURVE_X_MULT; |
1870 | + |
1871 | + const double CURVE_Y1 = 0.9825; |
1872 | + const double CURVE_Y2 = 0.72725; |
1873 | + const double CURVE_Y3 = 0.27275; |
1874 | + |
1875 | + const double CURVE_START_X = belt_w - category_pb_w - CURVE_CP5_X - EMBLEM_PADDING; |
1876 | + //const double CURVE_END_X = CURVE_START_X + CURVE_X_MULT; |
1877 | + |
1878 | + cairo_set_source_rgba(cr.get(), 1.0, 1.0, 1.0, 1.0); |
1879 | + |
1880 | + // paint the curved area |
1881 | + cairo_move_to(cr.get(), CURVE_START_X, belt_h); |
1882 | + cairo_curve_to(cr.get(), CURVE_START_X + CURVE_CP1_X, belt_h, |
1883 | + CURVE_START_X + CURVE_CP2_X, CURVE_Y1 * belt_h, |
1884 | + CURVE_START_X + CURVE_CP3_X, CURVE_Y2 * belt_h); |
1885 | + cairo_line_to(cr.get(), CURVE_START_X + CURVE_CP4_X, CURVE_Y3 * belt_h); |
1886 | + cairo_curve_to(cr.get(), CURVE_START_X + CURVE_CP5_X, 0.0, |
1887 | + CURVE_START_X + CURVE_CP6_X, 0.0, |
1888 | + CURVE_START_X + CURVE_CP7_X, 0.0); |
1889 | + cairo_line_to(cr.get(), belt_w, 0.0); |
1890 | + cairo_line_to(cr.get(), belt_w, belt_h); |
1891 | + cairo_close_path(cr.get()); |
1892 | + cairo_fill(cr.get()); |
1893 | + |
1894 | + // and the highlight |
1895 | + pattern.reset(cairo_pattern_create_linear(CURVE_START_X, 0.0, belt_w, 0.0), |
1896 | + cairo_pattern_destroy); |
1897 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 0.0, 1.0, 1.0, 1.0, 0.0); |
1898 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 0.95, 1.0, 1.0, 1.0, 0.0); |
1899 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 0.0, 0.0, 0.0, 0.235294); |
1900 | + cairo_set_source(cr.get(), pattern.get()); |
1901 | + cairo_rectangle(cr.get(), CURVE_START_X, 0.0, belt_w - CURVE_START_X, belt_h); |
1902 | + cairo_fill(cr.get()); |
1903 | + |
1904 | + // paint the emblem |
1905 | + double category_pb_x = belt_w - category_pb_w - EMBLEM_PADDING - 1; |
1906 | + gdk_cairo_set_source_pixbuf(cr.get(), category_pixbuf, |
1907 | + category_pb_x, (belt_h - category_pb_h) / 2); |
1908 | + cairo_paint(cr.get()); |
1909 | + } |
1910 | + |
1911 | + // paint the text |
1912 | + cairo_set_source_rgba(cr.get(), 1.0, 1.0, 1.0, 1.0); |
1913 | + cairo_move_to(cr.get(), 0.0, belt_h / 2); |
1914 | + pango_layout_get_pixel_size(layout, nullptr, &text_height); |
1915 | + // current point is now in the middle of the stripe, need to translate |
1916 | + // it, so that the text is centered |
1917 | + cairo_rel_move_to(cr.get(), 0.0, text_height / -2.0); |
1918 | + pango_cairo_show_layout(cr.get(), layout); |
1919 | + |
1920 | + // paint the shadow |
1921 | + cairo_restore(cr.get()); |
1922 | + |
1923 | + pattern.reset(cairo_pattern_create_linear(0.0, belt_h, 0.0, belt_h + SHADOW_BOTTOM_PADDING), |
1924 | + cairo_pattern_destroy); |
1925 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 0.0, 0.0, 0.0, 0.0, 0.235294); |
1926 | + cairo_pattern_add_color_stop_rgba(pattern.get(), 1.0, 0.0, 0.0, 0.0, 0.0); |
1927 | + |
1928 | + cairo_set_source(cr.get(), pattern.get()); |
1929 | + |
1930 | + cairo_rectangle(cr.get(), 0.0, belt_h, belt_w, SHADOW_BOTTOM_PADDING); |
1931 | + cairo_fill(cr.get()); |
1932 | + |
1933 | + cairo_set_source_rgba(cr.get(), 0.0, 0.0, 0.0, 0.1); |
1934 | + cairo_rectangle(cr.get(), 0.0, 1.0, 1.0, belt_h); |
1935 | + cairo_fill(cr.get()); |
1936 | + |
1937 | + cairo_rectangle(cr.get(), belt_w, 1.0, 1.0, belt_h); |
1938 | + cairo_fill(cr.get()); |
1939 | + |
1940 | + // FIXME: going from image_surface to pixbuf, and then to texture :( |
1941 | + glib::Object<GdkPixbuf> detail_pb( |
1942 | + gdk_pixbuf_get_from_surface(cairo_graphics.GetSurface(), |
1943 | + 0, 0, |
1944 | + cairo_graphics.GetWidth(), |
1945 | + cairo_graphics.GetHeight())); |
1946 | + |
1947 | + int y_pos = icon_h - pixbuf_height - max_font_height / 2; |
1948 | + gdk_pixbuf_composite(detail_pb, result_, // src, dest |
1949 | + 0, // dest_x |
1950 | + y_pos, // dest_y |
1951 | + pixbuf_width, // dest_w |
1952 | + pixbuf_height, // dest_h |
1953 | + 0, // offset_x |
1954 | + y_pos, // offset_y |
1955 | + 1.0, 1.0, // scale_x, scale_y |
1956 | + GDK_INTERP_NEAREST, // interpolation |
1957 | + 255); // src_alpha |
1958 | + } |
1959 | + |
1960 | + timer_complete_.reset(new glib::Timeout(0, sigc::mem_fun(this, &IconLoaderTask::LoadIconComplete))); |
1961 | +} |
1962 | + |
1963 | +void IconLoaderTask::BaseIconLoaded(std::string const& base_icon_string, |
1964 | + int base_max_width, int base_max_height, |
1965 | + glib::Object<GdkPixbuf> const& base_pixbuf, |
1966 | + glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon) |
1967 | +{ |
1968 | + // Called reference before we called this slot to ensure we didnt delete the task. |
1969 | + if (UnReference()) { return; } |
1970 | + |
1971 | + helper_handle_ = 0; |
1972 | + if (base_pixbuf) |
1973 | + { |
1974 | + LOG_DEBUG(logger) << "Base icon loaded: '" << base_icon_string << |
1975 | + "' size: " << gdk_pixbuf_get_width(base_pixbuf) << 'x' << |
1976 | + gdk_pixbuf_get_height(base_pixbuf); |
1977 | + |
1978 | + int result_width, result_height, dest_x, dest_y; |
1979 | + if (unity_protocol_annotated_icon_get_use_small_icon(anno_icon)) |
1980 | + { |
1981 | + result_width = max_width_ > 0 ? max_width_ : MAX(max_height_, gdk_pixbuf_get_width(base_pixbuf)); |
1982 | + result_height = max_height_ > 0 ? max_height_ : MAX(max_width_, gdk_pixbuf_get_height(base_pixbuf)); |
1983 | + |
1984 | + dest_x = (result_width - gdk_pixbuf_get_width(base_pixbuf)) / 2; |
1985 | + dest_y = (result_height - gdk_pixbuf_get_height(base_pixbuf)) / 2; |
1986 | + } |
1987 | + else |
1988 | + { |
1989 | + result_width = gdk_pixbuf_get_width(base_pixbuf) + RIBBON_PADDING * 2; |
1990 | + result_height = gdk_pixbuf_get_height(base_pixbuf); |
1991 | + |
1992 | + dest_x = RIBBON_PADDING; |
1993 | + dest_y = 0; |
1994 | + } |
1995 | + // we need extra space for the ribbon |
1996 | + result_ = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, |
1997 | + result_width, result_height); |
1998 | + gdk_pixbuf_fill(result_, 0x0); |
1999 | + gdk_pixbuf_copy_area(base_pixbuf, 0, 0, |
2000 | + gdk_pixbuf_get_width(base_pixbuf), |
2001 | + gdk_pixbuf_get_height(base_pixbuf), |
2002 | + result_, |
2003 | + dest_x, dest_y); |
2004 | + // FIXME: can we composite the pixbuf in helper thread? |
2005 | + UnityProtocolCategoryType category = unity_protocol_annotated_icon_get_category(anno_icon); |
2006 | + auto helper_slot = sigc::bind(sigc::mem_fun(this, &IconLoaderTask::CategoryIconLoaded), anno_icon); |
2007 | + int max_font_height; |
2008 | + CalculateTextHeight(nullptr, &max_font_height); |
2009 | + unsigned cat_size = max_font_height * 9 / 8; |
2010 | + |
2011 | + // If we create a child task, we dont want to delete this task until the child is finished. |
2012 | + Reference(); |
2013 | + helper_handle_ = 0; |
2014 | + |
2015 | + switch (category) |
2016 | + { |
2017 | + case UNITY_PROTOCOL_CATEGORY_TYPE_NONE: |
2018 | + // rest of the processing is the CategoryIconLoaded, lets invoke it |
2019 | + helper_slot("", -1, cat_size, glib::Object<GdkPixbuf>()); |
2020 | + return; |
2021 | + |
2022 | + case UNITY_PROTOCOL_CATEGORY_TYPE_APPLICATION: |
2023 | + helper_handle_ = |
2024 | + impl_->LoadFromFilename(PKGDATADIR"/emblem_apps.svg", -1, cat_size, helper_slot); |
2025 | + break; |
2026 | + case UNITY_PROTOCOL_CATEGORY_TYPE_BOOK: |
2027 | + helper_handle_ = |
2028 | + impl_->LoadFromFilename(PKGDATADIR"/emblem_books.svg", -1, cat_size, helper_slot); |
2029 | + break; |
2030 | + case UNITY_PROTOCOL_CATEGORY_TYPE_MUSIC: |
2031 | + helper_handle_ = |
2032 | + impl_->LoadFromFilename(PKGDATADIR"/emblem_music.svg", -1, cat_size, helper_slot); |
2033 | + break; |
2034 | + case UNITY_PROTOCOL_CATEGORY_TYPE_MOVIE: |
2035 | + helper_handle_ = |
2036 | + impl_->LoadFromFilename(PKGDATADIR"/emblem_video.svg", -1, cat_size, helper_slot); |
2037 | + break; |
2038 | + case UNITY_PROTOCOL_CATEGORY_TYPE_CLOTHES: |
2039 | + case UNITY_PROTOCOL_CATEGORY_TYPE_SHOES: |
2040 | + helper_handle_ = |
2041 | + impl_->LoadFromFilename(PKGDATADIR"/emblem_clothes.svg", -1, cat_size, helper_slot); |
2042 | + break; |
2043 | + case UNITY_PROTOCOL_CATEGORY_TYPE_GAMES: |
2044 | + case UNITY_PROTOCOL_CATEGORY_TYPE_ELECTRONICS: |
2045 | + case UNITY_PROTOCOL_CATEGORY_TYPE_COMPUTERS: |
2046 | + case UNITY_PROTOCOL_CATEGORY_TYPE_OFFICE: |
2047 | + case UNITY_PROTOCOL_CATEGORY_TYPE_HOME: |
2048 | + case UNITY_PROTOCOL_CATEGORY_TYPE_GARDEN: |
2049 | + case UNITY_PROTOCOL_CATEGORY_TYPE_PETS: |
2050 | + case UNITY_PROTOCOL_CATEGORY_TYPE_TOYS: |
2051 | + case UNITY_PROTOCOL_CATEGORY_TYPE_CHILDREN: |
2052 | + case UNITY_PROTOCOL_CATEGORY_TYPE_BABY: |
2053 | + case UNITY_PROTOCOL_CATEGORY_TYPE_WATCHES: |
2054 | + case UNITY_PROTOCOL_CATEGORY_TYPE_SPORTS: |
2055 | + case UNITY_PROTOCOL_CATEGORY_TYPE_OUTDOORS: |
2056 | + case UNITY_PROTOCOL_CATEGORY_TYPE_GROCERY: |
2057 | + case UNITY_PROTOCOL_CATEGORY_TYPE_HEALTH: |
2058 | + case UNITY_PROTOCOL_CATEGORY_TYPE_BEAUTY: |
2059 | + case UNITY_PROTOCOL_CATEGORY_TYPE_DIY: |
2060 | + case UNITY_PROTOCOL_CATEGORY_TYPE_TOOLS: |
2061 | + case UNITY_PROTOCOL_CATEGORY_TYPE_CAR: |
2062 | + default: |
2063 | + helper_handle_ = |
2064 | + impl_->LoadFromFilename(PKGDATADIR"/emblem_others.svg", -1, cat_size, helper_slot); |
2065 | + break; |
2066 | + } |
2067 | + |
2068 | + // no child? |
2069 | + if (helper_handle_ == 0) { UnReference(); } |
2070 | + } |
2071 | + else |
2072 | + { |
2073 | + result_ = nullptr; |
2074 | + timer_complete_.reset(new glib::Timeout(0, sigc::mem_fun(this, &IconLoaderTask::LoadIconComplete))); |
2075 | + } |
2076 | +} |
2077 | + |
2078 | +static gboolean ThreadLoadIconComplete(gpointer data) |
2079 | +{ |
2080 | + auto icon_task = static_cast<IconLoaderTask*>(data); |
2081 | + // IO thread finished. Can decrement reference. |
2082 | + if (icon_task->UnReference()) |
2083 | + { |
2084 | + // Task deleted. this mean that the IconLoader has been deleted while the thread was active. |
2085 | + return FALSE; |
2086 | + } |
2087 | + |
2088 | + return icon_task->LoadIconComplete(); |
2089 | +} |
2090 | + |
2091 | +// Loading/rendering of pixbufs is done in a separate thread |
2092 | +#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 |
2093 | + static gboolean LoaderJobFunc(GIOSchedulerJob* job, GCancellable *canc, gpointer data) |
2094 | +#else |
2095 | + static void LoaderJobFunc(GTask* job, gpointer source_object, gpointer data, GCancellable* canc) |
2096 | +#endif |
2097 | +{ |
2098 | + auto icon_task = static_cast<IconLoaderTask*>(data); |
2099 | + |
2100 | + // careful here this is running in non-main thread |
2101 | + if (icon_task->icon_info_) |
2102 | + { |
2103 | + icon_task->result_ = ::gtk_icon_info_load_icon(icon_task->icon_info_, &icon_task->error_); |
2104 | + } |
2105 | + else if (icon_task->type_ == REQUEST_TYPE_URI) |
2106 | + { |
2107 | + glib::Object<GFile> file(::g_file_new_for_uri(icon_task->data_.c_str())); |
2108 | + glib::String contents; |
2109 | + gsize length = 0; |
2110 | + |
2111 | + if (g_file_load_contents(file, canc, &contents, &length, |
2112 | + nullptr, &icon_task->error_)) |
2113 | + { |
2114 | + glib::Object<GInputStream> stream( |
2115 | + g_memory_input_stream_new_from_data(contents.Value(), length, nullptr)); |
2116 | + |
2117 | + icon_task->result_ = gdk_pixbuf_new_from_stream_at_scale(stream, |
2118 | + icon_task->max_width_, |
2119 | + icon_task->max_height_, |
2120 | + TRUE, |
2121 | + canc, |
2122 | + &icon_task->error_); |
2123 | + g_input_stream_close(stream, canc, nullptr); |
2124 | + } |
2125 | + } |
2126 | + |
2127 | +#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 |
2128 | + // Back to main loop. |
2129 | + g_io_scheduler_job_send_to_mainloop_async (job, ThreadLoadIconComplete, icon_task, nullptr); |
2130 | + return FALSE; |
2131 | +#endif |
2132 | +} |
2133 | + |
2134 | +void IconLoaderTask::PushSchedulerJob() |
2135 | +{ |
2136 | + // Increment reference so we dont delete the object while io thread runs. |
2137 | + Reference(); |
2138 | + |
2139 | + #if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 |
2140 | + g_io_scheduler_push_job (LoaderJobFunc, this, nullptr, G_PRIORITY_HIGH_IDLE, nullptr); |
2141 | +#else |
2142 | + glib::Object<GTask> task(g_task_new(nullptr, nullptr, [] (GObject*, GAsyncResult*, gpointer data) { |
2143 | + auto self = static_cast<IconLoaderTask*>(data); |
2144 | + // IO thread finished. Can decrement reference. |
2145 | + if (icon_task->UnReference()) |
2146 | + { |
2147 | + // Task deleted. this mean that the IconLoader has been deleted while the thread was active. |
2148 | + return; |
2149 | + } |
2150 | + self->LoadIconComplete(data); |
2151 | + }, this)); |
2152 | + |
2153 | + g_task_set_priority(task, G_PRIORITY_HIGH_IDLE); |
2154 | + g_task_set_task_data(task, this, nullptr); |
2155 | + |
2156 | + g_task_run_in_thread(task, LoaderJobFunc); |
2157 | +#endif |
2158 | +} |
2159 | + |
2160 | + |
2161 | +bool IconLoaderTask::LoadIconComplete() |
2162 | +{ |
2163 | + timer_complete_.reset(); |
2164 | + if (!finished_) |
2165 | + { |
2166 | + if (result_.IsType(GDK_TYPE_PIXBUF)) |
2167 | + { |
2168 | + if (!no_cache_) |
2169 | + impl_->cache_[key_] = result_; |
2170 | + } |
2171 | + else |
2172 | + { |
2173 | + if (result_) |
2174 | + result_ = nullptr; |
2175 | + |
2176 | + LOG_WARNING(logger) << "Unable to load icon " << data_ |
2177 | + << " at size " |
2178 | + << max_width_ << "x" << max_height_ |
2179 | + << ": " << error_; |
2180 | + } |
2181 | + impl_->finished_tasks_.push_back(IconLoaderTask::Ptr(this)); |
2182 | + finished_ = true; |
2183 | + } |
2184 | + |
2185 | + if (!impl_->coalesce_timeout_) |
2186 | + { |
2187 | + // Slow coalesce for task cache. |
2188 | + auto prio = static_cast<glib::Source::Priority>(glib::Source::Priority::DEFAULT_IDLE + 40); |
2189 | + impl_->coalesce_timeout_.reset(new glib::Timeout(COALESCE_TIMEOUT, sigc::mem_fun(impl_, &IconLoaderImpl::CoalesceTasksCb), prio)); |
2190 | + } |
2191 | + |
2192 | + return FALSE; |
2193 | +} |
2194 | + |
2195 | + |
2196 | +void IconLoaderTask::CalculateTextHeight(int* width, int* height) |
2197 | +{ |
2198 | + // FIXME: what about CJK? |
2199 | + const char* const SAMPLE_MAX_TEXT = "Chromium Web Browser"; |
2200 | + GtkSettings* settings = gtk_settings_get_default(); |
2201 | + |
2202 | + nux::CairoGraphics util_cg(CAIRO_FORMAT_ARGB32, 1, 1); |
2203 | + cairo_t* cr = util_cg.GetInternalContext(); |
2204 | + |
2205 | + glib::String font; |
2206 | + int dpi = 0; |
2207 | + g_object_get(settings, |
2208 | + "gtk-font-name", &font, |
2209 | + "gtk-xft-dpi", &dpi, |
2210 | + NULL); |
2211 | + std::shared_ptr<PangoFontDescription> desc(pango_font_description_from_string(font), pango_font_description_free); |
2212 | + pango_font_description_set_weight(desc.get(), PANGO_WEIGHT_BOLD); |
2213 | + pango_font_description_set_size(desc.get(), FONT_SIZE * PANGO_SCALE); |
2214 | + |
2215 | + glib::Object<PangoLayout> layout(pango_cairo_create_layout(cr)); |
2216 | + pango_layout_set_font_description(layout, desc.get()); |
2217 | + pango_layout_set_text(layout, SAMPLE_MAX_TEXT, -1); |
2218 | + |
2219 | + PangoContext* cxt = pango_layout_get_context(layout); |
2220 | + GdkScreen* screen = gdk_screen_get_default(); |
2221 | + pango_cairo_context_set_font_options(cxt, gdk_screen_get_font_options(screen)); |
2222 | + pango_cairo_context_set_resolution(cxt, dpi / (double) PANGO_SCALE); |
2223 | + pango_layout_context_changed(layout); |
2224 | + |
2225 | + PangoRectangle log_rect; |
2226 | + pango_layout_get_extents(layout, NULL, &log_rect); |
2227 | + |
2228 | + if (width) *width = log_rect.width / PANGO_SCALE; |
2229 | + if (height) *height = log_rect.height / PANGO_SCALE; |
2230 | +} |
2231 | + |
2232 | +} |
2233 | +} |
2234 | \ No newline at end of file |
2235 | |
2236 | === added file 'unity-shared/IconLoaderTask.h' |
2237 | --- unity-shared/IconLoaderTask.h 1970-01-01 00:00:00 +0000 |
2238 | +++ unity-shared/IconLoaderTask.h 2013-01-30 14:41:22 +0000 |
2239 | @@ -0,0 +1,111 @@ |
2240 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
2241 | +/* |
2242 | +* Copyright (C) 2010-2012 Canonical Ltd |
2243 | +* |
2244 | +* This program is free software: you can redistribute it and/or modify |
2245 | +* it under the terms of the GNU General Public License version 3 as |
2246 | +* published by the Free Software Foundation. |
2247 | +* |
2248 | +* This program is distributed in the hope that it will be useful, |
2249 | +* but WITHOUT ANY WARRANTY; without even the implied warranty of |
2250 | +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2251 | +* GNU General Public License for more details. |
2252 | +* |
2253 | +* You should have received a copy of the GNU General Public License |
2254 | +* along with this program. If not, see <http://www.gnu.org/licenses/>. |
2255 | +* |
2256 | +* Authored by: Neil Jagdish Patel <neil.patel@canonical.com> |
2257 | + Nick Dedekind <nick.dedekind@canonical.com> |
2258 | +*/ |
2259 | + |
2260 | +#ifndef UNITY_ICON_LOADER_TASK_H |
2261 | +#define UNITY_ICON_LOADER_TASK_H |
2262 | + |
2263 | +#include "IconLoader.h" |
2264 | +#include <unity-protocol.h> |
2265 | +#include <NuxCore/Object.h> |
2266 | +#include <UnityCore/GLibSource.h> |
2267 | + |
2268 | +namespace unity |
2269 | +{ |
2270 | +namespace icon_loader |
2271 | +{ |
2272 | + |
2273 | +class IconLoaderImpl; |
2274 | + |
2275 | +enum IconLoaderRequestType |
2276 | +{ |
2277 | + REQUEST_TYPE_ICON_NAME = 0, |
2278 | + REQUEST_TYPE_GICON_STRING, |
2279 | + REQUEST_TYPE_URI, |
2280 | +}; |
2281 | +typedef int Handle; |
2282 | + |
2283 | +// We use a nux::Object so we get threadsafe reference counting for cross thread destruction safety. |
2284 | +class IconLoaderTask : public nux::Object |
2285 | +{ |
2286 | +public: |
2287 | + typedef nux::ObjectPtr<IconLoaderTask> Ptr; |
2288 | + NUX_DECLARE_OBJECT_TYPE(IconLoader, Object); |
2289 | + |
2290 | + IconLoaderTask(IconLoaderRequestType type_, |
2291 | + std::string const& data_, |
2292 | + int max_width_, |
2293 | + int max_height_, |
2294 | + std::string const& key_, |
2295 | + IconLoaderImpl* self_); |
2296 | + |
2297 | + ~IconLoaderTask(); |
2298 | + |
2299 | + void InvokeSlot(); |
2300 | + |
2301 | + bool Process(); |
2302 | + |
2303 | + void AddSlot(Handle handle, IconLoaderCallback slot); |
2304 | + |
2305 | + bool LoadIconComplete(); |
2306 | + |
2307 | +private: |
2308 | + |
2309 | + bool ProcessIconNameTask(); |
2310 | + |
2311 | + bool ProcessGIconTask(); |
2312 | + |
2313 | + bool ProcessURITask(); |
2314 | + |
2315 | + void PushSchedulerJob(); |
2316 | + |
2317 | + void CategoryIconLoaded(std::string const& base_icon_string, |
2318 | + int max_width, int max_height, |
2319 | + glib::Object<GdkPixbuf> const& category_pixbuf, |
2320 | + glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon); |
2321 | + |
2322 | + void BaseIconLoaded(std::string const& base_icon_string, |
2323 | + int base_max_width, int base_max_height, |
2324 | + glib::Object<GdkPixbuf> const& base_pixbuf, |
2325 | + glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon); |
2326 | + |
2327 | + static void CalculateTextHeight(int* width, int* height); |
2328 | + |
2329 | +public: |
2330 | + IconLoaderRequestType type_; |
2331 | + std::string data_; |
2332 | + int max_width_; |
2333 | + int max_height_; |
2334 | + std::string key_; |
2335 | + std::map<Handle, IconLoaderCallback> slots_; |
2336 | + IconLoaderImpl* impl_; |
2337 | + GtkIconInfo* icon_info_; |
2338 | + bool no_cache_; |
2339 | + int helper_handle_; |
2340 | + glib::Object<GdkPixbuf> result_; |
2341 | + glib::Error error_; |
2342 | + std::list<IconLoaderTask::Ptr> shadow_tasks_; |
2343 | + glib::Source::UniquePtr timer_complete_; |
2344 | + bool finished_; |
2345 | +}; |
2346 | + |
2347 | +} |
2348 | +} |
2349 | + |
2350 | +#endif // UNITY_ICON_LOADER_TASK_H |
Needs unit test.