Logo Search packages:      
Sourcecode: guifications version File versions

gf_item_text.c

/*
 * Guifications - The end all, be all, toaster popup plugin
 * Copyright (C) 2003-2004 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 <util.h>
#include <xmlnode.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, gaim_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"))) {
            gaim_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) {
            gaim_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;
      GaimAccount *account;
      GaimBuddy *buddy;
      GaimConversation *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, gaim_account_get_username(account));
                        break;
                  case 'C': /* conversation title */
                        if(conv)
                              str = g_string_append(str, gaim_conversation_get_title(conv));

                        break;
                  case 'c': /* conversation name */
                        if(conv->type == GAIM_CONV_IM) {
                              GaimBuddy *buddy;

                              buddy = gaim_find_buddy(account, conv->name);
                              if(buddy)
                                    str = g_string_append(str, gaim_buddy_get_contact_alias(buddy));
                              else
                                    str = g_string_append(str, conv->name);
                        } else if(conv->type == GAIM_CONV_CHAT) {
                              GaimChat *chat;

                              chat = gaim_blist_find_chat(account, conv->name);
                              if(chat)
                                    str = g_string_append(str, gaim_chat_get_display_name(chat));
                              else
                                    str = g_string_append(str, conv->name);
                        } else {
                              str = g_string_append(str, conv->name);
                        }

                        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, gaim_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 = gaim_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 = gaim_find_buddy(account, target);

                                    if(buddy) {
                                          const gchar *alias;

                                          alias = gaim_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, gaim_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 '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 = gaim_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", buddy->evil);
                              } else {
                                    str = g_string_append(str, warning);
                              }
                        } else {
                              str = g_string_append(str, warning);
                        }

                        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_strrncpy(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 = g_strdup(text);
            new_text = gf_utf8_strrncpy(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);

            right_text = g_strdup(text);
            if(IS_EVEN(g_utf8_strlen(text, -1)))
                  right_text = gf_utf8_strrncpy(text, mid - 1);
            else
                  right_text = gf_utf8_strrncpy(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_strrncpy(text, mid - 1);
      else
            right_text = gf_utf8_strrncpy(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));
}

Generated by  Doxygen 1.6.0   Back to index