Merge lp:~nick-dedekind/unity/multi-instance-icon-loader into lp:unity

Proposed by Nick Dedekind
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
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.

To post a comment you must log in.
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Needs unit test.

review: Needs Fixing
Revision history for this message
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.

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
Michal Hruby (mhr3) wrote :

1136 + timer_.reset(new glib::Timeout(0, sigc::mem_fun(this, &IconLoaderImpl::Iteration), glib::Source::Priority::LOW));

Why the change to Timeout instead of Idle?

2138 + InvokeSlot();
2139 +
2140 + if (!impl_->coalesce_timeout_)
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_map_[handle] = iter->second;

Am I right in thinking these elements in the map will "leak"?

review: Needs Fixing
2827. By Nick Dedekind

Merge with trunk

2828. By Nick Dedekind

Invoke icon slot on coalesce.

2829. By Nick Dedekind

Fixed referencing issue.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> 1136 + timer_.reset(new glib::Timeout(0, sigc::mem_fun(this,
> &IconLoaderImpl::Iteration), glib::Source::Priority::LOW));
>
> Why the change to Timeout instead of Idle?
>
> 2138 + InvokeSlot();
> 2139 +
> 2140 + if (!impl_->coalesce_timeout_)
> 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_map_[handle] = iter->second;
>
> Am I right in thinking these elements in the map will "leak"?

These should be released in IconLoaderImpl::DisconnectHandle when the slot is invoked.

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

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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