summaryrefslogtreecommitdiff
path: root/unity-shared
diff options
authorMichal Hruby <michal.mhr@gmail.com>2012-08-21 07:12:20 -0400
committerTarmac <>2012-08-21 07:12:20 -0400
commit008bf471b2dd20ffcde8d322b5ae2c5aa5aa9402 (patch)
tree6a59837b5d452506c5929bb70fd72038888a4f0f /unity-shared
parenteaadd1569af1c934cbdeedbec74137e730444bc7 (diff)
parent49585637b8ac60f98e45ba784dc44632af73c56b (diff)
Implement rendering of UnityProtocolAnnotatedIcon. Fixes: . Approved by Gord Allott, Marco Trevisan (TreviƱo).
(bzr r2598)
Diffstat (limited to 'unity-shared')
-rw-r--r--unity-shared/IconLoader.cpp264
1 files changed, 259 insertions, 5 deletions
diff --git a/unity-shared/IconLoader.cpp b/unity-shared/IconLoader.cpp
index 78c9e9ea9..7d236cb19 100644
--- a/unity-shared/IconLoader.cpp
+++ b/unity-shared/IconLoader.cpp
@@ -22,8 +22,13 @@
#include <queue>
#include <sstream>
#include <boost/algorithm/string.hpp>
+#include <unity-protocol.h>
+#include <pango/pango.h>
+#include <pango/pangocairo.h>
+#include <Nux/Nux.h>
#include <NuxCore/Logger.h>
+#include <NuxGraphics/CairoGraphics.h>
#include <UnityCore/GLibSource.h>
#include <UnityCore/GLibSignal.h>
@@ -43,6 +48,8 @@ public:
// The Handle typedef is used to explicitly indicate which integers are
// infact our opaque handles.
typedef int Handle;
+ static const int FONT_SIZE = 10;
+ static const int MIN_FONT_SIZE = 6;
Impl();
@@ -64,6 +71,8 @@ public:
void DisconnectHandle(Handle handle);
+ static void CalculateTextHeight(int* width, int* height);
+
private:
enum IconLoaderRequestType
@@ -85,9 +94,12 @@ private:
Handle handle;
Impl* impl;
GtkIconInfo* icon_info;
+ bool no_cache;
+ int helper_handle;
glib::Object<GdkPixbuf> result;
glib::Error error;
std::list<IconLoaderTask::Ptr> shadow_tasks;
+ unsigned idle_id;
IconLoaderTask(IconLoaderRequestType type_,
std::string const& data_,
@@ -98,13 +110,17 @@ private:
Impl* self_)
: type(type_), data(data_), size(size_), key(key_)
, slot(slot_), handle(handle_), impl(self_)
- , icon_info(nullptr)
+ , icon_info(nullptr), no_cache(false), helper_handle(0), idle_id(0)
{}
~IconLoaderTask()
{
if (icon_info)
::gtk_icon_info_free(icon_info);
+ if (helper_handle != 0)
+ impl->DisconnectHandle(helper_handle);
+ if (idle_id != 0)
+ g_source_remove(idle_id);
}
void InvokeSlot()
@@ -180,7 +196,21 @@ private:
glib::Error error;
glib::Object<GIcon> icon(::g_icon_new_for_string(data.c_str(), &error));
- if (G_IS_FILE_ICON(icon.RawPtr()))
+ if (icon.IsType(UNITY_PROTOCOL_TYPE_ANNOTATED_ICON))
+ {
+ UnityProtocolAnnotatedIcon *anno;
+ anno = UNITY_PROTOCOL_ANNOTATED_ICON(icon.RawPtr());
+ GIcon* base_icon = unity_protocol_annotated_icon_get_icon(anno);
+ glib::String gicon_string(g_icon_to_string(base_icon));
+
+ no_cache = true;
+ auto helper_slot = sigc::bind(sigc::mem_fun(this, &IconLoaderTask::BaseIconLoaded), glib::object_cast<UnityProtocolAnnotatedIcon>(icon));
+ helper_handle = impl->LoadFromGIconString(gicon_string.Str(),
+ size, helper_slot);
+
+ return false;
+ }
+ else if (icon.IsType(G_TYPE_FILE_ICON))
{
// [trasfer none]
GFile* file = ::g_file_icon_get_file(G_FILE_ICON(icon.RawPtr()));
@@ -191,7 +221,7 @@ private:
return ProcessURITask();
}
- else if (G_IS_ICON(icon.RawPtr()))
+ else if (icon.IsType(G_TYPE_ICON))
{
GtkIconInfo* info = ::gtk_icon_theme_lookup_by_gicon(impl->theme_, icon, size,
static_cast<GtkIconLookupFlags>(0));
@@ -241,6 +271,177 @@ private:
return false;
}
+ void CategoryIconLoaded(std::string const& base_icon_string, unsigned size,
+ glib::Object<GdkPixbuf> const& category_pixbuf,
+ glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon)
+ {
+ helper_handle = 0;
+ if (category_pixbuf)
+ {
+ // assuming the category pixbuf is smaller than result
+ gdk_pixbuf_composite(category_pixbuf, result, // src, dest
+ 0, 0, // dest_x, dest_y
+ gdk_pixbuf_get_width(category_pixbuf), // dest_w
+ gdk_pixbuf_get_height(category_pixbuf), // dest_h
+ 0.0, 0.0, // offset_x, offset_y
+ 1.0, 1.0, // scale_x, scale_y
+ GDK_INTERP_NEAREST, // interpolation
+ 255); // src_alpha
+ }
+
+ const gchar* detail_text = unity_protocol_annotated_icon_get_ribbon(anno_icon);
+ if (detail_text)
+ {
+ int icon_w = gdk_pixbuf_get_width(result);
+ int icon_h = gdk_pixbuf_get_height(result);
+
+ int max_font_height;
+ CalculateTextHeight(nullptr, &max_font_height);
+
+ max_font_height = max_font_height * 9 / 8; // let's have some padding on the stripe
+ int pixbuf_size = static_cast<int>(
+ sqrt(max_font_height*max_font_height*8));
+ if (pixbuf_size > icon_w) pixbuf_size = icon_w;
+
+ nux::CairoGraphics cairo_graphics(CAIRO_FORMAT_ARGB32,
+ pixbuf_size, pixbuf_size);
+ std::shared_ptr<cairo_t> cr(cairo_graphics.GetContext(), cairo_destroy);
+
+ glib::Object<PangoLayout> layout;
+ PangoContext* pango_context = NULL;
+ GdkScreen* screen = gdk_screen_get_default(); // not ref'ed
+ glib::String font;
+ int dpi = -1;
+
+ g_object_get(gtk_settings_get_default(), "gtk-font-name", &font, NULL);
+ g_object_get(gtk_settings_get_default(), "gtk-xft-dpi", &dpi, NULL);
+ cairo_set_font_options(cr.get(), gdk_screen_get_font_options(screen));
+ layout = pango_cairo_create_layout(cr.get());
+ std::shared_ptr<PangoFontDescription> desc(pango_font_description_from_string(font), pango_font_description_free);
+ pango_font_description_set_weight(desc.get(), PANGO_WEIGHT_BOLD);
+ int font_size = FONT_SIZE;
+ pango_font_description_set_size (desc.get(), font_size * PANGO_SCALE);
+
+ pango_layout_set_font_description(layout, desc.get());
+ pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
+
+ double size_dbl = static_cast<double>(pixbuf_size);
+ // we'll allow tiny bit of overflow since the text is rotated and there
+ // is some space left... FIXME: 10/9? / 11/10?
+ double max_text_width = sqrt(size_dbl*size_dbl / 2) * 9/8;
+
+ pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
+ pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
+
+ glib::String escaped_text(g_markup_escape_text(detail_text, -1));
+ pango_layout_set_markup(layout, escaped_text, -1);
+
+ pango_context = pango_layout_get_context(layout); // is not ref'ed
+ // FIXME: for reasons unknown, it looks better without this
+ //pango_cairo_context_set_font_options(pango_context,
+ // gdk_screen_get_font_options(screen));
+ pango_cairo_context_set_resolution(pango_context,
+ dpi == -1 ? 96.0f : dpi/(float) PANGO_SCALE);
+ pango_layout_context_changed(layout);
+
+ // find proper font size (can we do this before the rotation?)
+ int text_width, text_height;
+ pango_layout_get_pixel_size(layout, &text_width, nullptr);
+ while (text_width > max_text_width && font_size > MIN_FONT_SIZE)
+ {
+ font_size--;
+ pango_font_description_set_size (desc.get(), font_size * PANGO_SCALE);
+ pango_layout_set_font_description(layout, desc.get());
+ pango_layout_get_pixel_size(layout, &text_width, nullptr);
+ }
+ pango_layout_set_width(layout, static_cast<int>(max_text_width * PANGO_SCALE));
+
+ cairo_set_operator(cr.get(), CAIRO_OPERATOR_CLEAR);
+ cairo_paint(cr.get());
+
+ cairo_set_operator(cr.get(), CAIRO_OPERATOR_OVER);
+
+ // draw the trapezoid
+ cairo_move_to(cr.get(), 0.0, size_dbl);
+ cairo_line_to(cr.get(), size_dbl, 0.0);
+ cairo_line_to(cr.get(), size_dbl, size_dbl / 2.0);
+ cairo_line_to(cr.get(), size_dbl / 2.0, size_dbl);
+ cairo_close_path(cr.get());
+
+ // this should be #dd4814
+ cairo_set_source_rgba(cr.get(), 0.86666f, 0.28235f, 0.07843f, 1.0f);
+ cairo_fill(cr.get());
+
+ // draw the text (rotated!)
+ cairo_set_source_rgba(cr.get(), 1.0f, 1.0f, 1.0f, 1.0f);
+ cairo_move_to(cr.get(), size_dbl * 0.25, size_dbl);
+ cairo_rotate(cr.get(), -G_PI_4); // rotate by -45 degrees
+
+ pango_cairo_update_layout(cr.get(), layout);
+ pango_layout_get_pixel_size(layout, nullptr, &text_height);
+ // current point is now in the middle of the stripe, need to translate
+ // it, so that the text is centered
+ cairo_rel_move_to(cr.get(), 0.0, text_height / -2.0);
+ double diagonal = sqrt(size_dbl*size_dbl*2);
+ // x coordinate also needs to be shifted
+ cairo_rel_move_to(cr.get(), (diagonal - max_text_width) / 4, 0.0);
+ pango_cairo_show_layout(cr.get(), layout);
+
+ // FIXME: going from image_surface to pixbuf, and then to texture :(
+ glib::Object<GdkPixbuf> detail_pb(
+ gdk_pixbuf_get_from_surface(cairo_graphics.GetSurface(),
+ 0, 0,
+ cairo_graphics.GetWidth(),
+ cairo_graphics.GetHeight()));
+
+ gdk_pixbuf_composite(detail_pb, result, // src, dest
+ icon_w - pixbuf_size, // dest_x
+ icon_h - pixbuf_size, // dest_y
+ pixbuf_size, // dest_w
+ pixbuf_size, // dest_h
+ icon_w - pixbuf_size, // offset_x
+ icon_h - pixbuf_size, // offset_y
+ 1.0, 1.0, // scale_x, scale_y
+ GDK_INTERP_NEAREST, // interpolation
+ 255); // src_alpha
+ }
+
+ idle_id = g_idle_add(LoadIconComplete, this);
+ }
+
+ void BaseIconLoaded(std::string const& base_icon_string, unsigned size,
+ glib::Object<GdkPixbuf> const& base_pixbuf,
+ glib::Object<UnityProtocolAnnotatedIcon> const& anno_icon)
+ {
+ helper_handle = 0;
+ if (base_pixbuf)
+ {
+ result = gdk_pixbuf_copy(base_pixbuf);
+ // FIXME: can we composite the pixbuf in helper thread?
+ UnityProtocolCategoryType category = unity_protocol_annotated_icon_get_category(anno_icon);
+ auto helper_slot = sigc::bind(sigc::mem_fun(this, &IconLoaderTask::CategoryIconLoaded), anno_icon);
+ unsigned cat_size = size / 4;
+ // FIXME: we still don't have the category assets
+ switch (category)
+ {
+ case UNITY_PROTOCOL_CATEGORY_TYPE_MUSIC:
+ helper_handle =
+ impl->LoadFromIconName("emblem-favorite", cat_size, helper_slot);
+ break;
+ default:
+ // rest of the processing is the CategoryIconLoaded, lets invoke it
+ glib::Object<GdkPixbuf> null_pixbuf;
+ helper_slot("", cat_size, null_pixbuf);
+ break;
+ }
+ }
+ else
+ {
+ result = nullptr;
+ idle_id = g_idle_add(LoadIconComplete, this);
+ }
+ }
+
void PushSchedulerJob()
{
::g_io_scheduler_push_job (LoaderJobFunc, this, nullptr, G_PRIORITY_HIGH_IDLE, nullptr);
@@ -298,9 +499,9 @@ private:
auto task = static_cast<IconLoaderTask*>(data);
auto impl = task->impl;
- if (GDK_IS_PIXBUF(task->result.RawPtr()))
+ if (task->result.IsType(GDK_TYPE_PIXBUF))
{
- impl->cache_[task->key] = task->result;
+ if (!task->no_cache) impl->cache_[task->key] = task->result;
}
else
{
@@ -350,6 +551,13 @@ private:
private:
std::map<std::string, glib::Object<GdkPixbuf>> cache_;
+ /* FIXME: the reference counting of IconLoaderTasks with shared pointers
+ * is currently somewhat broken, and the queued_tasks_ member is what keeps
+ * it from crashing randomly.
+ * The IconLoader instance is assuming that it is the only owner of the loader
+ * tasks, but when they are being completed in a worker thread, the thread
+ * should own them as well (yet it doesn't), this could cause trouble
+ * in the future... You've been warned! */
std::map<std::string, IconLoaderTask::Ptr> queued_tasks_;
std::queue<IconLoaderTask::Ptr> tasks_;
std::map<Handle, IconLoaderTask::Ptr> task_map_;
@@ -379,6 +587,16 @@ IconLoader::Impl::Impl()
* apply immediately. */
cache_.clear();
});
+
+ // make sure the AnnotatedIcon type is registered, so we can deserialize it
+#if GLIB_CHECK_VERSION(2, 34, 0)
+ g_type_ensure(unity_protocol_annotated_icon_get_type());
+#else
+ // we need to fool the compiler cause get_type is marked as G_GNUC_CONST,
+ // which isn't exactly true
+ volatile GType proto_icon = unity_protocol_annotated_icon_get_type();
+ g_type_name(proto_icon);
+#endif
}
int IconLoader::Impl::LoadFromIconName(std::string const& icon_name,
@@ -440,6 +658,42 @@ void IconLoader::Impl::DisconnectHandle(Handle handle)
}
}
+void IconLoader::Impl::CalculateTextHeight(int* width, int* height)
+{
+ // FIXME: what about CJK?
+ const char* const SAMPLE_MAX_TEXT = "Chromium Web Browser";
+ GtkSettings* settings = gtk_settings_get_default();
+
+ nux::CairoGraphics util_cg(CAIRO_FORMAT_ARGB32, 1, 1);
+ cairo_t* cr = util_cg.GetInternalContext();
+
+ glib::String font;
+ int dpi = 0;
+ g_object_get(settings,
+ "gtk-font-name", &font,
+ "gtk-xft-dpi", &dpi,
+ NULL);
+ std::shared_ptr<PangoFontDescription> desc(pango_font_description_from_string(font), pango_font_description_free);
+ pango_font_description_set_weight(desc.get(), PANGO_WEIGHT_BOLD);
+ pango_font_description_set_size(desc.get(), FONT_SIZE * PANGO_SCALE);
+
+ glib::Object<PangoLayout> layout(pango_cairo_create_layout(cr));
+ pango_layout_set_font_description(layout, desc.get());
+ pango_layout_set_text(layout, SAMPLE_MAX_TEXT, -1);
+
+ PangoContext* cxt = pango_layout_get_context(layout);
+ GdkScreen* screen = gdk_screen_get_default();
+ pango_cairo_context_set_font_options(cxt, gdk_screen_get_font_options(screen));
+ pango_cairo_context_set_resolution(cxt, dpi / (double) PANGO_SCALE);
+ pango_layout_context_changed(layout);
+
+ PangoRectangle log_rect;
+ pango_layout_get_extents(layout, NULL, &log_rect);
+
+ if (width) *width = log_rect.width / PANGO_SCALE;
+ if (height) *height = log_rect.height / PANGO_SCALE;
+}
+
//
// Private Methods
//