pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
14 months ago, Gary Kramlich
2f836435c33c
closing merged branch
/* pidgin
*
* Pidgin is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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
*/
#include "gtksmiley-theme.h"
#include "internal.h"
#include "debug.h"
#include "gtkutils.h"
#include <glib/gstdio.h>
#define PIDGIN_SMILEY_THEME_MAX_LINES 1024
#define PIDGIN_SMILEY_THEME_MAX_TOKENS 1024
/**
* PidginSmileyTheme:
*
* An implementation of a smiley theme.
*/
struct _PidginSmileyTheme
{
PurpleSmileyTheme parent;
};
typedef struct
{
gchar *path;
gchar *name;
gchar *desc;
gchar *icon;
gchar *author;
GdkPixbuf *icon_pixbuf;
GHashTable *smiley_lists_map;
} PidginSmileyThemePrivate;
static gchar **probe_dirs;
static GList *smiley_themes = NULL;
typedef struct
{
gchar *name;
gchar *desc;
gchar *icon;
gchar *author;
GList *protocols;
} PidginSmileyThemeIndex;
typedef struct
{
gchar *name;
GList *smileys;
} PidginSmileyThemeIndexProtocol;
typedef struct
{
gchar *file;
gboolean hidden;
GList *shortcuts;
} PidginSmileyThemeIndexSmiley;
G_DEFINE_TYPE_WITH_PRIVATE(PidginSmileyTheme, pidgin_smiley_theme,
PURPLE_TYPE_SMILEY_THEME);
/*******************************************************************************
* Theme index parsing
******************************************************************************/
static void
pidgin_smiley_theme_index_smiley_free(PidginSmileyThemeIndexSmiley *smiley)
{
g_return_if_fail(smiley != NULL);
g_free(smiley->file);
g_list_free_full(smiley->shortcuts, g_free);
g_free(smiley);
}
static void
pidgin_smiley_theme_index_protocol_free(PidginSmileyThemeIndexProtocol *proto)
{
g_return_if_fail(proto != NULL);
g_free(proto->name);
g_list_free_full(proto->smileys, (GDestroyNotify)pidgin_smiley_theme_index_smiley_free);
g_free(proto);
}
static void
pidgin_smiley_theme_index_free(PidginSmileyThemeIndex *index)
{
g_return_if_fail(index != NULL);
g_free(index->name);
g_free(index->desc);
g_free(index->icon);
g_free(index->author);
g_list_free_full(index->protocols, (GDestroyNotify)pidgin_smiley_theme_index_protocol_free);
g_free(index);
}
static PidginSmileyThemeIndex *
pidgin_smiley_theme_index_parse(const gchar *theme_path, gboolean load_contents)
{
PidginSmileyThemeIndex *index;
PidginSmileyThemeIndexProtocol *proto = NULL;
gchar *index_path;
FILE *file;
int line_no = 0;
gboolean inv_frm = FALSE;
index_path = g_build_filename(theme_path, "theme", NULL);
file = g_fopen(index_path, "r");
if (!file) {
purple_debug_error("gtksmiley-theme",
"Failed to open index file %s", index_path);
g_free(index_path);
return NULL;
}
index = g_new0(PidginSmileyThemeIndex, 1);
while (!feof(file)) {
PidginSmileyThemeIndexSmiley *smiley;
gchar buff[1024];
gchar *line, *eqchr;
gchar **split;
int i;
if (++line_no > PIDGIN_SMILEY_THEME_MAX_LINES) {
purple_debug_warning("gtksmiley-theme", "file too big");
break;
}
if (!fgets(buff, sizeof(buff), file))
break;
/* strip comments */
if (buff[0] == '#')
continue;
g_strstrip(buff);
if (buff[0] == '\0')
continue;
if (!g_utf8_validate(buff, -1, NULL)) {
purple_debug_error("gtksmiley-theme",
"%s:%d is invalid UTF-8",
index_path, line_no);
continue;
}
line = buff;
if (line[0] == '[') {
gchar *end;
if (!load_contents)
break;
line++;
end = strchr(line, ']');
if (!end) {
inv_frm = TRUE;
break;
}
if (proto)
proto->smileys = g_list_reverse(proto->smileys);
proto = g_new0(PidginSmileyThemeIndexProtocol, 1);
proto->name = g_strndup(line, end - line);
index->protocols =
g_list_prepend(index->protocols, proto);
continue;
}
if ((eqchr = strchr(line, '='))) {
*eqchr = '\0';
if (g_ascii_strcasecmp(line, "name") == 0) {
g_free(index->name);
index->name = g_strdup(eqchr + 1);
g_strchug(index->name);
continue;
} else if (g_ascii_strcasecmp(line, "description") == 0) {
g_free(index->desc);
index->desc = g_strdup(eqchr + 1);
g_strchug(index->desc);
continue;
} else if (g_ascii_strcasecmp(line, "icon") == 0) {
g_free(index->icon);
index->icon = g_strdup(eqchr + 1);
g_strchug(index->icon);
continue;
} else if (g_ascii_strcasecmp(line, "author") == 0) {
g_free(index->author);
index->author = g_strdup(eqchr + 1);
g_strchug(index->author);
continue;
}
*eqchr = '=';
}
/* parsing section content */
if (proto == NULL) {
inv_frm = FALSE;
break;
}
smiley = g_new0(PidginSmileyThemeIndexSmiley, 1);
proto->smileys = g_list_prepend(proto->smileys, smiley);
smiley->hidden = FALSE;
if (line[0] == '!') {
smiley->hidden = TRUE;
line++;
}
split = g_strsplit_set(line, " \t",
PIDGIN_SMILEY_THEME_MAX_TOKENS);
for (i = 0; split[i]; i++) {
gchar *token = split[i];
if (token[0] == '\0')
continue;
if (i == PIDGIN_SMILEY_THEME_MAX_TOKENS - 1)
break;
if (!smiley->file) {
smiley->file = g_strdup(token);
continue;
}
smiley->shortcuts = g_list_prepend(smiley->shortcuts,
g_strdup(token));
}
g_strfreev(split);
smiley->shortcuts = g_list_reverse(smiley->shortcuts);
}
if (proto)
proto->smileys = g_list_reverse(proto->smileys);
fclose(file);
if (inv_frm) {
purple_debug_error("gtksmiley-theme", "%s:%d"
" invalid format", index_path, line_no);
pidgin_smiley_theme_index_free(index);
index = NULL;
}
g_free(index_path);
return index;
}
/*******************************************************************************
* Theme loading
******************************************************************************/
static void
pidgin_smiley_theme_load(const gchar *theme_path)
{
PidginSmileyTheme *theme;
PidginSmileyThemePrivate *priv;
PidginSmileyThemeIndex *index;
GList *it;
/* it's not super-efficient, but we don't expect huge amount of
* installed themes */
for (it = smiley_themes; it; it = g_list_next(it)) {
PidginSmileyThemePrivate *priv =
pidgin_smiley_theme_get_instance_private(it->data);
/* theme is already loaded */
if (g_strcmp0(priv->path, theme_path) == 0)
return;
}
theme = g_object_new(PIDGIN_TYPE_SMILEY_THEME, NULL);
priv = pidgin_smiley_theme_get_instance_private(theme);
priv->path = g_strdup(theme_path);
index = pidgin_smiley_theme_index_parse(theme_path, FALSE);
if (!index->name || index->name[0] == '\0') {
purple_debug_warning("gtksmiley-theme",
"incomplete theme %s", theme_path);
pidgin_smiley_theme_index_free(index);
g_object_unref(theme);
return;
}
priv->name = g_strdup(index->name);
if (index->desc && index->desc[0])
priv->desc = g_strdup(index->desc);
if (index->icon && index->icon[0])
priv->icon = g_strdup(index->icon);
if (index->author && index->author[0])
priv->author = g_strdup(index->author);
pidgin_smiley_theme_index_free(index);
smiley_themes = g_list_append(smiley_themes, theme);
}
static void
pidgin_smiley_theme_probe(void)
{
GList *it, *next;
int i;
/* remove non-existing themes */
for (it = smiley_themes; it; it = next) {
PidginSmileyTheme *theme = it->data;
PidginSmileyThemePrivate *priv =
pidgin_smiley_theme_get_instance_private(theme);
next = g_list_next(it);
if (g_file_test(priv->path, G_FILE_TEST_EXISTS))
continue;
smiley_themes = g_list_delete_link(smiley_themes, it);
g_object_unref(theme);
}
/* scan for themes */
for (i = 0; probe_dirs[i]; i++) {
GDir *dir = g_dir_open(probe_dirs[i], 0, NULL);
const gchar *theme_dir_name;
if (!dir)
continue;
while ((theme_dir_name = g_dir_read_name(dir))) {
gchar *theme_path;
/* Ignore Pidgin 2.x.y "none" theme. */
if (g_strcmp0(theme_dir_name, "none") == 0)
continue;
theme_path = g_build_filename(
probe_dirs[i], theme_dir_name, NULL);
if (g_file_test(theme_path, G_FILE_TEST_IS_DIR))
pidgin_smiley_theme_load(theme_path);
g_free(theme_path);
}
g_dir_close(dir);
}
}
/*******************************************************************************
* API implementation
******************************************************************************/
const gchar *
pidgin_smiley_theme_get_name(PidginSmileyTheme *theme)
{
PidginSmileyThemePrivate *priv = NULL;
g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
priv = pidgin_smiley_theme_get_instance_private(theme);
return priv->name;
}
const gchar *
pidgin_smiley_theme_get_description(PidginSmileyTheme *theme)
{
PidginSmileyThemePrivate *priv = NULL;
g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
priv = pidgin_smiley_theme_get_instance_private(theme);
return priv->desc;
}
GdkPixbuf *
pidgin_smiley_theme_get_icon(PidginSmileyTheme *theme)
{
PidginSmileyThemePrivate *priv = NULL;
g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
priv = pidgin_smiley_theme_get_instance_private(theme);
if (priv->icon == NULL)
return NULL;
if (!priv->icon_pixbuf) {
gchar *icon_path = g_build_filename(
priv->path, priv->icon, NULL);
priv->icon_pixbuf = pidgin_pixbuf_new_from_file(icon_path);
g_free(icon_path);
}
return priv->icon_pixbuf;
}
const gchar *
pidgin_smiley_theme_get_author(PidginSmileyTheme *theme)
{
PidginSmileyThemePrivate *priv = NULL;
g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
priv = pidgin_smiley_theme_get_instance_private(theme);
return priv->author;
}
PurpleSmileyList *
pidgin_smiley_theme_for_conv(PurpleConversation *conv)
{
PurpleAccount *acc = NULL;
PurpleSmileyTheme *theme;
const gchar *proto_name = NULL;
theme = purple_smiley_theme_get_current();
if (theme == NULL)
return NULL;
if (conv)
acc = purple_conversation_get_account(conv);
if (acc)
proto_name = purple_account_get_protocol_name(acc);
return purple_smiley_theme_get_smileys(theme, (gpointer)proto_name);
}
static void
pidgin_smiley_theme_activate_impl(PurpleSmileyTheme *theme)
{
PidginSmileyThemePrivate *priv =
pidgin_smiley_theme_get_instance_private(
PIDGIN_SMILEY_THEME(theme));
PidginSmileyThemeIndex *index;
GHashTable *smap;
GList *it, *it2, *it3;
if (priv->smiley_lists_map)
return;
priv->smiley_lists_map = smap = g_hash_table_new_full(
g_str_hash, g_str_equal, g_free, g_object_unref);
index = pidgin_smiley_theme_index_parse(priv->path, TRUE);
for (it = index->protocols; it; it = g_list_next(it)) {
PidginSmileyThemeIndexProtocol *proto_idx = it->data;
PurpleSmileyList *proto_smileys;
proto_smileys = g_hash_table_lookup(smap, proto_idx->name);
if (!proto_smileys) {
proto_smileys = purple_smiley_list_new();
g_hash_table_insert(smap,
g_strdup(proto_idx->name), proto_smileys);
}
for (it2 = proto_idx->smileys; it2; it2 = g_list_next(it2)) {
PidginSmileyThemeIndexSmiley *smiley_idx = it2->data;
gchar *smiley_path;
smiley_path = g_build_filename(
priv->path, smiley_idx->file, NULL);
if (!g_file_test(smiley_path, G_FILE_TEST_EXISTS)) {
purple_debug_warning("gtksmiley-theme",
"Smiley %s is missing", smiley_path);
g_free(smiley_path);
continue;
}
for (it3 = smiley_idx->shortcuts; it3;
it3 = g_list_next(it3))
{
PurpleSmiley *smiley;
gchar *shortcut = it3->data;
smiley = purple_smiley_new(
shortcut, smiley_path);
g_object_set_data(G_OBJECT(smiley),
"pidgin-smiley-hidden",
GINT_TO_POINTER(smiley_idx->hidden));
purple_smiley_list_add(proto_smileys, smiley);
g_object_unref(smiley);
}
g_free(smiley_path);
}
}
pidgin_smiley_theme_index_free(index);
}
static PurpleSmileyList *
pidgin_smiley_theme_get_smileys_impl(PurpleSmileyTheme *theme, gpointer ui_data)
{
PidginSmileyThemePrivate *priv =
pidgin_smiley_theme_get_instance_private(
PIDGIN_SMILEY_THEME(theme));
PurpleSmileyList *smileys = NULL;
pidgin_smiley_theme_activate_impl(theme);
if (ui_data)
smileys = g_hash_table_lookup(priv->smiley_lists_map, ui_data);
if (smileys != NULL)
return smileys;
return g_hash_table_lookup(priv->smiley_lists_map, "default");
}
GList *
pidgin_smiley_theme_get_all(void)
{
pidgin_smiley_theme_probe();
return smiley_themes;
}
void
_pidgin_smiley_theme_init(void)
{
GList *it;
const gchar *user_smileys_dir;
const gchar *theme_name;
probe_dirs = g_new0(gchar*, 3);
probe_dirs[0] = g_build_filename(
PURPLE_DATADIR, "pixmaps", "pidgin", "emotes", NULL);
user_smileys_dir = probe_dirs[1] =
g_build_filename(purple_data_dir(), "smileys", NULL);
if (!g_file_test(user_smileys_dir, G_FILE_TEST_IS_DIR)) {
if (g_mkdir(user_smileys_dir, S_IRUSR | S_IWUSR | S_IXUSR) == 0) {
purple_debug_error("gtksmiley-theme",
"Failed to create user smileys dir");
}
}
/* setting theme by name (copy-paste from gtkprefs) */
pidgin_smiley_theme_probe();
theme_name = purple_prefs_get_string(
PIDGIN_PREFS_ROOT "/smileys/theme");
for (it = smiley_themes; it; it = g_list_next(it)) {
PidginSmileyTheme *theme = it->data;
if (g_strcmp0(pidgin_smiley_theme_get_name(theme), theme_name))
continue;
purple_smiley_theme_set_current(PURPLE_SMILEY_THEME(theme));
}
}
void
_pidgin_smiley_theme_uninit(void)
{
g_strfreev(probe_dirs);
}
/*******************************************************************************
* Object stuff
******************************************************************************/
static void
pidgin_smiley_theme_finalize(GObject *obj)
{
PidginSmileyThemePrivate *priv =
pidgin_smiley_theme_get_instance_private(
PIDGIN_SMILEY_THEME(obj));
g_free(priv->path);
g_free(priv->name);
g_free(priv->desc);
g_free(priv->icon);
g_free(priv->author);
if (priv->icon_pixbuf)
g_object_unref(priv->icon_pixbuf);
if (priv->smiley_lists_map)
g_hash_table_destroy(priv->smiley_lists_map);
G_OBJECT_CLASS(pidgin_smiley_theme_parent_class)->finalize(obj);
}
static void
pidgin_smiley_theme_class_init(PidginSmileyThemeClass *klass)
{
GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
PurpleSmileyThemeClass *pst_class = PURPLE_SMILEY_THEME_CLASS(klass);
gobj_class->finalize = pidgin_smiley_theme_finalize;
pst_class->get_smileys = pidgin_smiley_theme_get_smileys_impl;
pst_class->activate = pidgin_smiley_theme_activate_impl;
}
static void
pidgin_smiley_theme_init(PidginSmileyTheme *theme)
{
}