grim/guifications2

Doing this so I can fix our tarball
org.guifications.gf2 gf_2_13beta7
2007-05-01, John Bailey
d9f3834d8f71
Doing this so I can fix our tarball
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 <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_internal.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) {
if(conv->type == PURPLE_CONV_TYPE_IM) {
PurpleBuddy *buddy;
buddy = purple_find_buddy(account, conv->name);
if(buddy)
str = g_string_append(str, purple_buddy_get_contact_alias(buddy));
else
str = g_string_append(str, conv->name);
} else if(conv->type == PURPLE_CONV_TYPE_CHAT) {
PurpleChat *chat;
chat = purple_blist_find_chat(account, conv->name);
if(chat) {
str = g_string_append(str, purple_chat_get_name(chat));
} else {
str = g_string_append(str, conv->name);
}
} else {
str = g_string_append(str, conv->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;
#if !PURPLE_VERSION_CHECK(2,0,0)
case 'W': /* warner */
if(target)
str = g_string_append(str, target);
break;
case 'w': /* warning level */
buddy = gf_event_info_get_buddy(info);
if(buddy) {
const char *prpl_id;
prpl_id = purple_account_get_protocol_id(account);
if(!g_ascii_strcasecmp(prpl_id, "prpl-toc") ||
!g_ascii_strcasecmp(prpl_id, "prpl-oscar"))
{
g_string_append_printf(str, "%d",
gf_get_warning_level(buddy));
} else {
str = g_string_append(str, warning);
}
} else {
str = g_string_append(str, warning);
}
#endif
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));
}