grim/guifications2

This was compatibility code for Pidgin 1.x.y; because it was written as a check
for "not 2.0.0" this code would be compiled in for 3.0.0. It looks like the
supporting code elsewhere was removed, causing this to leave an unresolved
symbol in guifications.so, thus causing Pidgin to refuse to load. Now we load
in Pidgin 3.0.0. Whether it works or not, I have no clue.
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2008 Gary Kramlich
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "../gf_config.h"
# if HAVE_UNISTD_H
# include <unistd.h>
# endif
# if HAVE_PANGOFT2
# include <ft2build.h>
# include FT_FREETYPE_H
# include <pango/pangoft2.h>
# endif
#endif
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <pango/pango.h>
#include <string.h>
#include <time.h>
#include "gf_internal.h"
#include <account.h>
#include <conversation.h>
#include <debug.h>
#include <network.h>
#include <prpl.h>
#include <status.h>
#include <util.h>
#include <xmlnode.h>
#include <version.h>
#ifndef _WIN32
# include <gdk/gdkx.h>
#endif
#include "gf_event.h"
#include "gf_event_info.h"
#include "gf_gtk_utils.h"
#include "gf_item.h"
#include "gf_item_text.h"
#include "gf_notification.h"
#include "gf_preferences.h"
#include "gf_theme.h"
#include "gf_theme_ops.h"
struct _GfItemText {
GfItem *item;
gchar *format;
gchar *font;
gchar *color;
GfItemTextClipping clipping;
gint width;
};
#define IS_EVEN(number) ((number & 1) == 1 ? FALSE: TRUE)
/*******************************************************************************
* Subsystem
*
* If I read the mail thread correctly, a ft2 font map caches the glyphs.
* I was disregarding the font map and everything because I only needed it to
* create the context in order to create the layout, this was causing some
* major memory issues. Therefore the gf_item_text subsystem was created. All
* this does is hold our font map and context. On win32 we do not need a font
* map so all it does is keep the context around until uninit is called. With
* the caching in ft2, we should in theory be drawing anything thats not a
* format token faster, which is a good thing. Also, we aren't constantly
* getting the context and unref'n it which will save a few cycles as well.
******************************************************************************/
static PangoFontMap *map = NULL;
static PangoContext *context = NULL;
void
gf_item_text_init() {
#ifndef _WIN32
gdouble xdpi = 75, ydpi = 75;
# if GTK_CHECK_VERSION(2,2,0)
GdkDisplay *display;
GdkScreen *screen;
# endif
#endif
map = pango_ft2_font_map_new();
#ifndef _WIN32
# if GTK_CHECK_VERSION(2,2,0)
display = gdk_display_get_default();
screen = gdk_display_get_screen(display, purple_prefs_get_int(GF_PREF_ADVANCED_SCREEN));
xdpi = (double)((float)gdk_screen_get_width(screen) / (float)gdk_screen_get_width_mm(screen) * 25.4);
ydpi = (double)((float)gdk_screen_get_height(screen) / (float)gdk_screen_get_height_mm(screen) * 25.4);
# endif
pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(map), xdpi, ydpi);
#endif
context = pango_ft2_font_map_create_context(PANGO_FT2_FONT_MAP(map));
}
void
gf_item_text_uninit() {
if(map)
g_object_unref(G_OBJECT(map));
if(context)
g_object_unref(G_OBJECT(context));
}
/*******************************************************************************
* API
******************************************************************************/
void
gf_item_text_destroy(GfItemText *item_text) {
g_return_if_fail(item_text);
item_text->item = NULL;
if(item_text->format) {
g_free(item_text->format);
item_text->format = NULL;
}
if(item_text->font) {
g_free(item_text->font);
item_text->font = NULL;
}
if(item_text->color) {
g_free(item_text->color);
item_text->color = NULL;
}
item_text->clipping = GF_ITEM_TEXT_CLIPPING_UNKNOWN;
item_text->width = 0;
g_free(item_text);
item_text = NULL;
}
GfItemText *
gf_item_text_new(GfItem *item) {
GfItemText *item_text;
g_return_val_if_fail(item, NULL);
item_text = g_new0(GfItemText, 1);
item_text->item = item;
return item_text;
}
static GfItemTextClipping
text_clipping_from_string(const gchar *string) {
g_return_val_if_fail(string, GF_ITEM_TEXT_CLIPPING_UNKNOWN);
if(!g_ascii_strcasecmp(string, "truncate"))
return GF_ITEM_TEXT_CLIPPING_TRUNCATE;
if(!g_ascii_strcasecmp(string, "ellipsis-start"))
return GF_ITEM_TEXT_CLIPPING_ELLIPSIS_START;
if(!g_ascii_strcasecmp(string, "ellipsis-middle"))
return GF_ITEM_TEXT_CLIPPING_ELLIPSIS_MIDDLE;
if(!g_ascii_strcasecmp(string, "ellipsis-end"))
return GF_ITEM_TEXT_CLIPPING_ELLIPSIS_END;
else
return GF_ITEM_TEXT_CLIPPING_UNKNOWN;
}
static const gchar *
text_clipping_to_string(GfItemTextClipping clip) {
g_return_val_if_fail(clip != GF_ITEM_TEXT_CLIPPING_UNKNOWN, NULL);
switch(clip) {
case GF_ITEM_TEXT_CLIPPING_TRUNCATE:
return "truncate";
break;
case GF_ITEM_TEXT_CLIPPING_ELLIPSIS_START:
return "ellipsis-start";
break;
case GF_ITEM_TEXT_CLIPPING_ELLIPSIS_MIDDLE:
return "ellipsis-middle";
break;
case GF_ITEM_TEXT_CLIPPING_ELLIPSIS_END:
return "ellipsis-end";
break;
case GF_ITEM_TEXT_CLIPPING_UNKNOWN:
default:
return NULL;
break;
}
}
GfItemText *
gf_item_text_new_from_xmlnode(GfItem *item, xmlnode *node) {
GfItemText *item_text;
const gchar *data = NULL;
g_return_val_if_fail(item, NULL);
g_return_val_if_fail(node, NULL);
item_text = gf_item_text_new(item);
if(!(data = xmlnode_get_attrib(node, "format"))) {
purple_debug_info("Guifications", "** Error loading text item: 'No format given'\n");
gf_item_text_destroy(item_text);
return NULL;
}
item_text->format = g_strdup(data);
if((data = xmlnode_get_attrib(node, "font")))
item_text->font = g_strdup(data);
if((data = xmlnode_get_attrib(node, "color")))
item_text->color = g_strdup(data);
data = xmlnode_get_attrib(node, "clipping");
item_text->clipping = text_clipping_from_string(data);
if(item_text->clipping == GF_ITEM_TEXT_CLIPPING_UNKNOWN) {
purple_debug_info("Guifications", "** Error loading text item: "
"'Unknown clipping type'\n");
gf_item_destroy(item);
return NULL;
}
data = xmlnode_get_attrib(node, "width");
if(data)
item_text->width = atoi(data);
else
item_text->width = 0;
return item_text;
}
GfItemText *
gf_item_text_copy(GfItemText *text) {
GfItemText *new_text;
g_return_val_if_fail(text, NULL);
new_text = gf_item_text_new(text->item);
if(text->format)
new_text->format = g_strdup(text->format);
if(text->font)
new_text->font = g_strdup(text->font);
if(text->color)
new_text->color = g_strdup(text->color);
new_text->clipping = text->clipping;
new_text->width = text->width;
return new_text;
}
xmlnode *
gf_item_text_to_xmlnode(GfItemText *text) {
xmlnode *parent;
parent = xmlnode_new("text");
if(text->format)
xmlnode_set_attrib(parent, "format", text->format);
if(text->font)
xmlnode_set_attrib(parent, "font", text->font);
if(text->color)
xmlnode_set_attrib(parent, "color", text->color);
if(text->clipping != GF_ITEM_TEXT_CLIPPING_UNKNOWN)
xmlnode_set_attrib(parent, "clipping", text_clipping_to_string(text->clipping));
if(text->width >= 0) {
gchar *width = g_strdup_printf("%d", text->width);
xmlnode_set_attrib(parent, "width", width);
g_free(width);
}
return parent;
}
void
gf_item_text_set_format(GfItemText *item_text, const gchar *format) {
g_return_if_fail(item_text);
g_return_if_fail(format);
if(item_text->format)
g_free(item_text->format);
item_text->format = g_strdup(format);
}
const gchar *
gf_item_text_get_format(GfItemText *item_text) {
g_return_val_if_fail(item_text, NULL);
return item_text->format;
}
void
gf_item_text_set_font(GfItemText *item_text, const gchar *font) {
g_return_if_fail(item_text);
g_return_if_fail(font);
if(item_text->font)
g_free(item_text->font);
item_text->font = g_strdup(font);
}
const gchar *
gf_item_text_get_font(GfItemText *item_text) {
g_return_val_if_fail(item_text, NULL);
return item_text->font;
}
void
gf_item_text_set_color(GfItemText *item_text, const gchar *color) {
g_return_if_fail(item_text);
g_return_if_fail(color);
if(item_text->color)
g_free(item_text->color);
item_text->color = g_strdup(color);
}
const gchar *
gf_item_text_get_color(GfItemText *item_text) {
g_return_val_if_fail(item_text, NULL);
return item_text->color;
}
void
gf_item_text_set_clipping(GfItemText *item_text, GfItemTextClipping clipping) {
g_return_if_fail(item_text);
g_return_if_fail(clipping >= 0 || clipping < GF_ITEM_TEXT_CLIPPING_UNKNOWN);
item_text->clipping = clipping;
}
GfItemTextClipping
gf_item_text_get_clipping(GfItemText *item_text) {
g_return_val_if_fail(item_text, GF_ITEM_TEXT_CLIPPING_UNKNOWN);
return item_text->clipping;
}
void
gf_item_text_set_width(GfItemText *item_text, gint width) {
g_return_if_fail(item_text);
g_return_if_fail(width >= 0);
item_text->width = width;
}
gint
gf_item_text_get_width(GfItemText *item_text) {
g_return_val_if_fail(item_text, -1);
return item_text->width;
}
void
gf_item_text_set_item(GfItemText *item_text, GfItem *item) {
g_return_if_fail(item_text);
g_return_if_fail(item);
item_text->item = item;
}
GfItem *
gf_item_text_get_item(GfItemText *item_text) {
g_return_val_if_fail(item_text, NULL);
return item_text->item;
}
/*******************************************************************************
* Rendering stuff
******************************************************************************/
/* why did I think this was a good idea? */
static gchar *
gf_item_text_parse_format(GfItemText *item_text, GfEventInfo *info) {
GfEvent *event;
GfNotification *notification;
GfTheme *theme;
GfThemeOptions *ops;
PurpleAccount *account;
PurpleBuddy *buddy;
PurpleConversation *conv = NULL;
GString *str;
gchar *ret;
const gchar *tokens, *format, *time_format, *date_format, *warning;
const gchar *target, *message, *extra;
time_t rtime;
static char buff[80];
struct tm *ltime;
g_return_val_if_fail(item_text, NULL);
g_return_val_if_fail(info, NULL);
format = item_text->format;
notification = gf_item_get_notification(item_text->item);
theme = gf_notification_get_theme(notification);
ops = gf_theme_get_theme_options(theme);
time_format = gf_theme_options_get_time_format(ops);
date_format = gf_theme_options_get_date_format(ops);
warning = gf_theme_options_get_warning(ops);
event = gf_event_info_get_event(info);
target = gf_event_info_get_target(info);
message = gf_event_info_get_message(info);
extra = gf_event_info_get_extra(info);
str = g_string_new("");
tokens = gf_event_get_tokens(event);
time(&rtime);
ltime = localtime(&rtime);
account = gf_event_info_get_account(info);
conv = gf_event_info_get_conversation(info);
while(format && format[0]) {
if(format[0] == '\\') {
format++;
continue;
}
if(format[0] != '%') {
str = g_string_append_c(str, format[0]);
format++;
continue;
}
/* this increment is to get past the % */
format++;
if(!format[0])
break;
if(!strchr(tokens, format[0])) {
format++;
continue;
}
switch(format[0]) {
case '%': /* % */
str = g_string_append_c(str, '%');
break;
case 'a': /* account name */
str = g_string_append(str, purple_account_get_username(account));
break;
case 'C': /* conversation title */
str = g_string_append(str, conv ? purple_conversation_get_title(conv) : target);
break;
case 'c': /* conversation name */
if (conv) {
PurpleConversationType type = purple_conversation_get_type(conv);
const gchar *name = purple_conversation_get_name(conv);
if(type == PURPLE_CONV_TYPE_IM) {
PurpleBuddy *buddy;
buddy = purple_find_buddy(account, name);
if(buddy)
str = g_string_append(str, purple_buddy_get_contact_alias(buddy));
else
str = g_string_append(str, name);
} else if(type == PURPLE_CONV_TYPE_CHAT) {
PurpleChat *chat;
chat = purple_blist_find_chat(account, name);
if(chat) {
str = g_string_append(str, purple_chat_get_name(chat));
} else {
str = g_string_append(str, name);
}
} else {
str = g_string_append(str, name);
}
} else {
str = g_string_append(str, target);
}
break;
case 'D': /* date */
strftime(buff, sizeof(buff), date_format, ltime);
str = g_string_append(str, buff);
break;
case 'd': /* day 01-31 */
strftime(buff, sizeof(buff), "%d", ltime);
str = g_string_append(str, buff);
break;
case 'F': /* Chat Flags */
/* CODE ME !!!
* which of course means to add theme options to give these numbers
* text to make these make sense :)
*/
break;
case 'f': /* Chat Flag Prefixes */
/* CODE ME !!!
* which means to add theme options for the prefix, these should be 1
* char, like an ircd would send us. ie: @/+
*/
break;
case 'H': /* hour 01-23 */
strftime(buff, sizeof(buff), "%H", ltime);
str = g_string_append(str, buff);
break;
case 'h': /* hour 01-12 */
strftime(buff, sizeof(buff), "%I", ltime);
str = g_string_append(str, buff);
break;
case 'i': /* ip */
str = g_string_append(str, purple_network_get_public_ip());
break;
case 'M': /* month */
strftime(buff, sizeof(buff), "%m", ltime);
str = g_string_append(str, buff);
break;
case 'm': /* minute */
strftime(buff, sizeof(buff), "%M", ltime);
str = g_string_append(str, buff);
break;
case 'N': /* computer name */
gethostname(buff, sizeof(buff));
str = g_string_append(str, buff);
break;
case 'n': /* buddy screen name */
buddy = gf_event_info_get_buddy(info);
if(buddy) {
const gchar *alias = purple_buddy_get_contact_alias(buddy);
str = g_string_append(str, alias);
} else {
const gchar *target = gf_event_info_get_target(info);
if(target) {
const gchar *target;
target = gf_event_info_get_target(info);
buddy = purple_find_buddy(account, target);
if(buddy) {
const gchar *alias;
alias = purple_buddy_get_contact_alias(buddy);
str = g_string_append(str, alias);
} else {
str = g_string_append(str, target);
}
}
}
break;
case 'p': /* protocol name */
str = g_string_append(str, purple_account_get_protocol_id(account));
break;
case 'r': /* received message */
if(message)
str = g_string_append(str, message);
break;
case 's': /* seconds 00-59 */
strftime(buff, sizeof(buff), "%S", ltime);
str = g_string_append(str, buff);
break;
case 'T': /* Time according to the theme var */
strftime(buff, sizeof(buff), time_format, ltime);
str = g_string_append(str, buff);
break;
case 't': /* seconds since the epoc */
strftime(buff, sizeof(buff), "%s", ltime);
str = g_string_append(str, buff);
break;
case 'u': /* computer user name */
str = g_string_append(str, g_get_user_name());
break;
case 'X': /* extra info */
if(extra)
str = g_string_append(str, extra);
break;
case 'Y': /* four digit year */
strftime(buff, sizeof(buff), "%Y", ltime);
str = g_string_append(str, buff);
break;
case 'y': { /* two digit year */
/* dirty hack to avoid compiler warning */
const gchar *fmt = "%y";
strftime(buff, sizeof(buff), fmt, ltime);
str = g_string_append(str, buff);
break;
} default:
break;
}
/* this increment is to get past the formatting char */
format++;
}
ret = str->str;
g_string_free(str, FALSE);
return ret;
}
/* ugly hack to keep us working on glib 2.0 */
#if !GLIB_CHECK_VERSION(2,2,0)
static gchar *
g_utf8_strreverse(const gchar *str, gssize len) {
gchar *result;
const gchar *p;
gchar *m, *r, skip;
if (len < 0)
len = strlen(str);
result = g_new0(gchar, len + 1);
r = result + len;
p = str;
while (*p) {
skip = g_utf8_skip[*(guchar*)p];
r -= skip;
for (m = r; skip; skip--)
*m++ = *p++;
}
return result;
}
#endif
/* this will probably break.. be sure to test it!!! */
static gchar *
gf_utf8_strrndup(const gchar *text, gint n) {
gchar *rev = NULL, *tmp = NULL;
rev = g_utf8_strreverse(text, -1);
/* ewww, what's going on here? */
/* oh, I remember... */
/* tell me then? */
/* well, g_utf8_strncpy doesn't allocate, so we use strdup to allocate for us */
/* oh neat, that's pretty ugly though */
/* yeah, there's probably a better way... */
tmp = g_strdup(rev);
tmp = g_utf8_strncpy(tmp, rev, n);
g_free(rev);
rev = g_utf8_strreverse(tmp, -1);
g_free(tmp);
return rev;
}
/*******************************************************************************
* The dreaded text clipping functions
*
* Hopefully now "utf8 safe" (tm)
******************************************************************************/
static void
text_truncate(PangoLayout *layout, gint width, gint offset) {
const gchar *text;
gchar *new_text = NULL;
gint l_width = 0;
g_return_if_fail(layout);
while(1) {
pango_layout_get_pixel_size(layout, &l_width, NULL);
if(l_width + offset <= width)
break;
text = pango_layout_get_text(layout);
new_text = g_strdup(text);
new_text = g_utf8_strncpy(new_text, text, g_utf8_strlen(text, -1) - 1);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
}
static void
text_ellipsis_start(PangoLayout *layout, gint width, gint offset,
const gchar *ellipsis_text, gint ellipsis_width)
{
const gchar *text;
gchar *new_text = NULL;
gint l_width = 0;
g_return_if_fail(layout);
while(1) {
pango_layout_get_pixel_size(layout, &l_width, NULL);
if(l_width + offset + ellipsis_width <= width)
break;
text = pango_layout_get_text(layout);
new_text = gf_utf8_strrndup(text, g_utf8_strlen(text, -1) - 1);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
text = pango_layout_get_text(layout);
new_text = g_strdup_printf("%s%s", ellipsis_text, text);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
static void
text_ellipsis_middle(PangoLayout *layout, gint width, gint offset,
const gchar *ellipsis_text, gint ellipsis_width)
{
const gchar *text;
gchar *new_text = NULL, *left_text = NULL, *right_text = NULL;
gint l_width = 0, mid;
g_return_if_fail(layout);
while(1) {
pango_layout_get_pixel_size(layout, &l_width, NULL);
if(l_width + offset + ellipsis_width <= width)
break;
text = pango_layout_get_text(layout);
mid = g_utf8_strlen(text, -1) / 2;
left_text = g_strdup(text);
left_text = g_utf8_strncpy(left_text, text, mid);
if(IS_EVEN(g_utf8_strlen(text, -1)))
right_text = gf_utf8_strrndup(text, mid - 1);
else
right_text = gf_utf8_strrndup(text, mid);
new_text = g_strdup_printf("%s%s", left_text, right_text);
g_free(left_text);
g_free(right_text);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
text = pango_layout_get_text(layout);
mid = g_utf8_strlen(text, -1) / 2;
left_text = g_strdup(text);
left_text = g_utf8_strncpy(left_text, text, mid);
if(IS_EVEN(g_utf8_strlen(text, -1)))
right_text = gf_utf8_strrndup(text, mid - 1);
else
right_text = gf_utf8_strrndup(text, mid);
new_text = g_strdup_printf("%s%s%s", left_text, ellipsis_text, right_text);
g_free(left_text);
g_free(right_text);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
static void
text_ellipsis_end(PangoLayout *layout, gint width, gint offset,
const gchar *ellipsis_text, gint ellipsis_width)
{
const gchar *text;
gchar *new_text = NULL;
gint l_width = 0;
g_return_if_fail(layout);
while(1) {
pango_layout_get_pixel_size(layout, &l_width, NULL);
if(l_width + offset + ellipsis_width <= width)
break;
text = pango_layout_get_text(layout);
new_text = g_strdup(text);
new_text = g_utf8_strncpy(new_text, text, g_utf8_strlen(text, -1) - 1);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
text = pango_layout_get_text(layout);
new_text = g_strdup_printf("%s%s", text, ellipsis_text);
pango_layout_set_text(layout, new_text, -1);
g_free(new_text);
}
static void
gf_item_text_clip(GfItemText *item_text, PangoLayout *layout,
gint pixbuf_width)
{
GfNotification *notification;
GfTheme *theme;
GfThemeOptions *ops;
GfItemOffset *ioffset;
PangoLayout *ellipsis;
const gchar *ellipsis_text;
gint e_width = 0, l_width = 0, width = 0, offset = 0;
g_return_if_fail(item_text);
g_return_if_fail(layout);
notification = gf_item_get_notification(item_text->item);
theme = gf_notification_get_theme(notification);
ops = gf_theme_get_theme_options(theme);
ellipsis_text = gf_theme_options_get_ellipsis(ops);
if((ioffset = gf_item_get_horz_offset(item_text->item))) {
if(ioffset && gf_item_offset_get_is_percentage(ioffset))
offset = (pixbuf_width * gf_item_offset_get_value(ioffset)) / 100;
else
offset = gf_item_offset_get_value(ioffset);
} else {
offset = 0;
}
width = item_text->width;
if(width == 0)
width = pixbuf_width;
else
offset = 0;
ellipsis = pango_layout_copy(layout);
pango_layout_set_text(ellipsis, ellipsis_text, -1);
pango_layout_get_pixel_size(ellipsis, &e_width, NULL);
g_object_unref(G_OBJECT(ellipsis));
pango_layout_get_pixel_size(layout, &l_width, NULL);
if(l_width <= width)
return;
switch (item_text->clipping) {
case GF_ITEM_TEXT_CLIPPING_ELLIPSIS_START:
text_ellipsis_start(layout, width, offset, ellipsis_text, e_width);
break;
case GF_ITEM_TEXT_CLIPPING_ELLIPSIS_MIDDLE:
text_ellipsis_middle(layout, width, offset, ellipsis_text, e_width);
break;
case GF_ITEM_TEXT_CLIPPING_ELLIPSIS_END:
text_ellipsis_end(layout, width, offset, ellipsis_text, e_width);
break;
case GF_ITEM_TEXT_CLIPPING_TRUNCATE:
default:
text_truncate(layout, width, offset);
break;
}
}
static PangoLayout *
gf_item_text_create_layout(GfItemText *item_text, GfEventInfo *info, gint width)
{
PangoLayout *layout = NULL;
PangoFontDescription *font = NULL;
gchar *text = NULL;
g_return_val_if_fail(item_text, NULL);
g_return_val_if_fail(info, NULL);
layout = pango_layout_new(context);
pango_layout_set_width(layout, -1);
if(item_text->font) {
font = pango_font_description_from_string(item_text->font);
pango_layout_set_font_description(layout, font);
pango_font_description_free(font);
} else {
pango_layout_set_font_description(layout, gf_gtk_theme_get_font());
}
text = gf_item_text_parse_format(item_text, info);
pango_layout_set_text(layout, text, -1);
g_free(text);
gf_item_text_clip(item_text, layout, width);
return layout;
}
/* This function has cost me 5 days of trying to find some way to make gtk/pango/gdk do
* this. I'm only using this way because 2 gtk/pango developers said this is the only
* way at this time. So if you change it.. Your changes better fucking work :P
*/
static GdkPixbuf *
gf_pixbuf_new_from_ft2_bitmap(FT_Bitmap *bitmap, PangoColor *color) {
GdkPixbuf *pixbuf;
guchar *buffer, *pbuffer;
gint w, h, rowstride;
guint8 r, g, b, *alpha;
/* Grab the colors from the PangoColor and shift 8 bits because we're only in 8 bit
* mode, and a PangoColor's elements are guint16's which are 16 bits...
*/
r = color->red >> 8;
g = color->green >> 8;
b = color->blue >> 8;
/* create the new pixbuf */
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
bitmap->width, bitmap->rows);
if(!pixbuf)
return NULL;
/* clear out the pixbuf to transparent black */
gdk_pixbuf_fill(pixbuf, 0x00000000);
buffer = gdk_pixbuf_get_pixels(pixbuf);
rowstride = gdk_pixbuf_get_rowstride(pixbuf);
/* ok here's the run down...
*
* From devhelp:
* Image data in a pixbuf is stored in memory in uncompressed, packed format. Rows
* in the image are stored top to bottom, and in each row pixels are stored from
* left to right. There may be padding at the end of a row. The "rowstride" value of
* a pixbuf, as returned by gdk_pixbuf_get_rowstride(), indicates the number of
* bytes between rows.
*
* So we take the height of the FT_Bitmap (the text), and increment until we're done.
* simple enough.
*
* Then we get the alpha for the row in the FT_Bitmap
*
* Next we move left to right and draw accordingly.
*
* Repeat until we've gone through the whole FT_Bitmap.
*/
for(h = 0; h < bitmap->rows; h++) {
pbuffer = buffer + (h * rowstride);
/* get the alpha from the FT_Bitmap */
alpha = bitmap->buffer + (h * (bitmap->pitch));
for(w = 0; w < bitmap->width; w++) {
*pbuffer++ = r;
*pbuffer++ = g;
*pbuffer++ = b;
*pbuffer++ = *alpha++;
}
}
return pixbuf;
}
void
gf_item_text_render(GfItemText *item_text, GdkPixbuf *pixbuf, GfEventInfo *info)
{
GdkPixbuf *t_pixbuf = NULL;
PangoColor color;
PangoLayout *layout = NULL;
FT_Bitmap bitmap;
gint x = 0, y = 0;
gint n_width = 0, n_height = 0;
gint t_width = 0, t_height = 0;
gint l_width = 0, l_height = 0;
g_return_if_fail(item_text);
g_return_if_fail(pixbuf);
g_return_if_fail(info);
/* get the width and height of the notification pixbuf */
n_width = gdk_pixbuf_get_width(pixbuf);
n_height = gdk_pixbuf_get_height(pixbuf);
/* create the layout */
layout = gf_item_text_create_layout(item_text, info, n_width);
if(!layout)
return;
/* setup the FT_BITMAP */
pango_layout_get_pixel_size(layout, &l_width, &l_height);
bitmap.rows = l_height;
bitmap.width = l_width;
bitmap.pitch = (bitmap.width + 3) & ~3;
bitmap.buffer = g_new0(guint8, bitmap.rows * bitmap.pitch);
bitmap.num_grays = 255;
bitmap.pixel_mode = ft_pixel_mode_grays;
pango_ft2_render_layout(&bitmap, layout, 0, 0);
g_object_unref(G_OBJECT(layout));
if(!item_text->color) {
GdkColor g_color;
gf_gtk_theme_get_fg_color(&g_color);
gf_gtk_color_pango_from_gdk(&color, &g_color);
} else if(!pango_color_parse(&color, item_text->color)) {
color.red = color.green = color.blue = 0;
}
t_pixbuf = gf_pixbuf_new_from_ft2_bitmap(&bitmap, &color);
g_free(bitmap.buffer);
if(!t_pixbuf)
return;
t_width = gdk_pixbuf_get_width(t_pixbuf);
t_height = gdk_pixbuf_get_height(t_pixbuf);
gf_item_get_render_position(&x, &y, t_width, t_height, n_width, n_height,
item_text->item);
gf_gtk_pixbuf_clip_composite(t_pixbuf, x, y, pixbuf);
g_object_unref(G_OBJECT(t_pixbuf));
}