view pidgin/gtkthemes.c @ 23047:76ae43c5e745

*** Plucked rev 69b22e32f7ada182101e0746f02891389ceb0c52 (sadrul@pidgin.im): The text iterators need to be properly ordered here. Taking the iterators from the x/y coordinates doesn't ensure the correct order, especially when RTL languages are involved. Thanks to thefox in #pidgin for reporting the bug. *** Plucked rev 777e7e6c4fb02c0b0f0dc557554cfc83b92544ae (sadrul@pidgin.im): Small leak fix.
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sat, 21 Jun 2008 14:41:18 +0000
parents c38d72677c8a
children 02eda4bd2b22
line wrap: on
line source

/*
 * Themes for 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 "internal.h"
#include "pidgin.h"

#include "conversation.h"
#include "debug.h"
#include "prpl.h"
#include "util.h"

#include "gtkconv.h"
#include "gtkdialogs.h"
#include "gtkimhtml.h"
#include "gtkthemes.h"

GSList *smiley_themes = NULL;
struct smiley_theme *current_smiley_theme;

static void pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme);

gboolean pidgin_themes_smileys_disabled()
{
	if (!current_smiley_theme)
		return 1;

	return strcmp(current_smiley_theme->name, "none") == 0;
}

static void
pidgin_themes_destroy_smiley_theme(struct smiley_theme *theme)
{
	pidgin_themes_destroy_smiley_theme_smileys(theme);

	g_free(theme->name);
	g_free(theme->desc);
	g_free(theme->author);
	g_free(theme->icon);
	g_free(theme->path);
	g_free(theme);
}

static void pidgin_themes_remove_theme_dir(const char *theme_dir_name)
{
	GString *str = NULL;
	const char *file_name = NULL;
	GDir *theme_dir = NULL;

	if ((theme_dir = g_dir_open(theme_dir_name, 0, NULL)) != NULL) {
		if ((str = g_string_new(theme_dir_name)) != NULL) {
			while ((file_name = g_dir_read_name(theme_dir)) != NULL) {
				g_string_printf(str, "%s%s%s", theme_dir_name, G_DIR_SEPARATOR_S, file_name);
				g_unlink(str->str);
			}
			g_string_free(str, TRUE);
		}
		g_dir_close(theme_dir);
		g_rmdir(theme_dir_name);
	}
}

void pidgin_themes_remove_smiley_theme(const char *file)
{
	char *theme_dir = NULL, *last_slash = NULL;
	g_return_if_fail(NULL != file);
	
	if (!g_file_test(file, G_FILE_TEST_EXISTS)) return;
	if ((theme_dir = g_strdup(file)) == NULL) return ;

	if ((last_slash = g_strrstr(theme_dir, G_DIR_SEPARATOR_S)) != NULL) {
		GSList *iter = NULL;
		struct smiley_theme *theme = NULL, *new_theme = NULL;

		*last_slash = 0;

		/* Delete files on disk */
		pidgin_themes_remove_theme_dir(theme_dir);

		/* Find theme in themes list and remove it */
		for (iter = smiley_themes ; iter ; iter = iter->next) {
			theme = ((struct smiley_theme *)(iter->data));
			if (!strcmp(theme->path, file))
				break ;
		}
		if (iter) {
			if (theme == current_smiley_theme) {
				new_theme = ((struct smiley_theme *)(NULL == iter->next ? (smiley_themes == iter ? NULL : smiley_themes->data) : iter->next->data));
				if (new_theme)
					purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", new_theme->name);
				else
					current_smiley_theme = NULL;
			}
			smiley_themes = g_slist_delete_link(smiley_themes, iter);

			/* Destroy theme structure */
			pidgin_themes_destroy_smiley_theme(theme);
		}
	}

	g_free(theme_dir);
}

void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
{
	struct smiley_list *list;
	if (!current_smiley_theme)
		return;

	gtk_imhtml_remove_smileys(GTK_IMHTML(imhtml));
	list = current_smiley_theme->list;
	while (list) {
		char *sml = !strcmp(list->sml, "default") ? NULL : list->sml;
		GSList *icons = list->smileys;
		while (icons) {
			gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data);
			icons = icons->next;
		}
		list = list->next;
	}
}

static void
pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme)
{
	GHashTable *already_freed;
	struct smiley_list *wer;

	already_freed = g_hash_table_new(g_direct_hash, g_direct_equal);
	for (wer = theme->list; wer != NULL; wer = theme->list) {
		while (wer->smileys) {
			GtkIMHtmlSmiley *uio = wer->smileys->data;
			if (uio->icon)
				g_object_unref(uio->icon);
			if (g_hash_table_lookup(already_freed, uio->file) == NULL) {
				g_free(uio->file);
				g_hash_table_insert(already_freed, uio->file, GINT_TO_POINTER(1));
			}
			g_free(uio->smile);
			g_free(uio);
			wer->smileys = g_slist_remove(wer->smileys, uio);
		}
		theme->list = wer->next;
		g_free(wer->sml);
		g_free(wer);
	}
	theme->list = NULL;

	g_hash_table_destroy(already_freed);
}

static void
pidgin_smiley_themes_remove_non_existing(void)
{
	static struct smiley_theme *theme = NULL;
	GSList *iter = NULL;

	if (!smiley_themes) return ;

	for (iter = smiley_themes ; iter ; iter = iter->next) {
		theme = ((struct smiley_theme *)(iter->data));
		if (!g_file_test(theme->path, G_FILE_TEST_EXISTS)) {
			if (theme == current_smiley_theme)
				current_smiley_theme = ((struct smiley_theme *)(NULL == iter->next ? NULL : iter->next->data));
			pidgin_themes_destroy_smiley_theme(theme);
			iter->data = NULL;
		}
	}
	/* Remove all elements whose data is NULL */
	smiley_themes = g_slist_remove_all(smiley_themes, NULL);

	if (!current_smiley_theme && smiley_themes) {
		struct smiley_theme *smile = g_slist_last(smiley_themes)->data;
		pidgin_themes_load_smiley_theme(smile->path, TRUE);
	}
}

void pidgin_themes_load_smiley_theme(const char *file, gboolean load)
{
	FILE *f = g_fopen(file, "r");
	char buf[256];
	char *i;
	struct smiley_theme *theme=NULL;
	struct smiley_list *list = NULL;
	GSList *lst = smiley_themes;
	char *dirname;
	gboolean new_theme = FALSE;

	if (!f)
		return;

	while (lst) {
		struct smiley_theme *thm = lst->data;
		if (!strcmp(thm->path, file)) {
			theme = thm;
			break;
		}
		lst = lst->next;
	}

	if (!theme) {
		new_theme = TRUE;
		theme = g_new0(struct smiley_theme, 1);
		theme->path = g_strdup(file);
	} else if (theme == current_smiley_theme) {
		/* Don't reload the theme if it is already loaded */
		fclose(f);
		return;
	}

	dirname = g_path_get_dirname(file);

	while (!feof(f)) {
		if (!fgets(buf, sizeof(buf), f)) {
			break;
		}

		if (buf[0] == '#' || buf[0] == '\0')
			continue;

		i = buf;
		while (isspace(*i))
			i++;

		if (*i == '[' && strchr(i, ']') && load) {
			struct smiley_list *child = g_new0(struct smiley_list, 1);
			child->sml = g_strndup(i+1, strchr(i, ']') - i - 1);
			if (theme->list)
				list->next = child;
			else
				theme->list = child;
			/* Reverse the Smiley list since it was built in reverse order for efficiency reasons */
			if (list != NULL)
				list->smileys = g_slist_reverse(list->smileys);
			list = child;
		} else if (!g_ascii_strncasecmp(i, "Name=", strlen("Name="))) {
			int len;
			g_free(theme->name);
			theme->name = g_strdup(i + strlen("Name="));
			len = strlen(theme->name);
			theme->name[len-1] = 0;
			if(len > 2 && theme->name[len-2] == '\r')
				theme->name[len-2] = 0;
		} else if (!g_ascii_strncasecmp(i, "Description=", strlen("Description="))) {
			g_free(theme->desc);
			theme->desc = g_strdup(i + strlen("Description="));
			theme->desc[strlen(theme->desc)-1] = 0;
		} else if (!g_ascii_strncasecmp(i, "Icon=", strlen("Icon="))) {
			g_free(theme->icon);
			theme->icon = g_build_filename(dirname, i + strlen("Icon="), NULL);
			theme->icon[strlen(theme->icon)-1] = 0;
		} else if (!g_ascii_strncasecmp(i, "Author=", strlen("Author="))) {
			g_free(theme->author);
			theme->author = g_strdup(i + strlen("Author="));
			theme->author[strlen(theme->author)-1] = 0;
		} else if (load && list) {
			gboolean hidden = FALSE;
			char *sfile = NULL;
			gboolean have_used_sfile = FALSE;

			if (*i == '!' && *(i + 1) == ' ') {
				hidden = TRUE;
				i = i + 2;
			}
			while  (*i) {
				char l[64];
				int li = 0;
				while (!isspace(*i) && li < sizeof(l) - 1) {
					if (*i == '\\' && *(i+1) != '\0' && *(i+1) != '\n' && *(i+1) != '\r')
						i++;
					l[li++] = *(i++);
				}
				if (!sfile) {
					l[li] = 0;
					sfile = g_build_filename(dirname, l, NULL);
				} else {
					GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
					l[li] = 0;
					smiley->file = sfile;
					smiley->smile = g_strdup(l);
					smiley->hidden = hidden;
					list->smileys = g_slist_prepend(list->smileys, smiley);
					have_used_sfile = TRUE;
				}
				while (isspace(*i))
					i++;

			}


			if (!have_used_sfile)
				g_free(sfile);
		}
	}

	/* Reverse the Smiley list since it was built in reverse order for efficiency reasons */
	if (list != NULL)
		list->smileys = g_slist_reverse(list->smileys);

	g_free(dirname);
	fclose(f);

	if (!theme->name || !theme->desc || !theme->author) {
		purple_debug_error("gtkthemes", "Invalid file format, not loading smiley theme from '%s'\n", file);

		pidgin_themes_destroy_smiley_theme(theme);
		return;
	}

	if (new_theme) {
		smiley_themes = g_slist_prepend(smiley_themes, theme);
	}

	if (load) {
		GList *cnv;

		if (current_smiley_theme)
			pidgin_themes_destroy_smiley_theme_smileys(current_smiley_theme);
		current_smiley_theme = theme;

		for (cnv = purple_get_conversations(); cnv != NULL; cnv = cnv->next) {
			PurpleConversation *conv = cnv->data;

			if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) {
				pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml);
				pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->entry);
			}
		}
	}
}

void pidgin_themes_smiley_theme_probe()
{
	GDir *dir;
	const gchar *file;
	gchar *path, *test_path;
	int l;
	char* probedirs[3];

	pidgin_smiley_themes_remove_non_existing();

	probedirs[0] = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", NULL);
	probedirs[1] = g_build_filename(purple_user_dir(), "smileys", NULL);
	probedirs[2] = 0;
	for (l=0; probedirs[l]; l++) {
		dir = g_dir_open(probedirs[l], 0, NULL);
		if (dir) {
			while ((file = g_dir_read_name(dir))) {
				test_path = g_build_filename(probedirs[l], file, NULL);
				if (g_file_test(test_path, G_FILE_TEST_IS_DIR)) {
					path = g_build_filename(probedirs[l], file, "theme", NULL);

					/* Here we check to see that the theme has proper syntax.
					 * We set the second argument to FALSE so that it doesn't load
					 * the theme yet.
					 */
					pidgin_themes_load_smiley_theme(path, FALSE);
					g_free(path);
				}
				g_free(test_path);
			}
			g_dir_close(dir);
		} else if (l == 1) {
			g_mkdir(probedirs[l], S_IRUSR | S_IWUSR | S_IXUSR);
		}
		g_free(probedirs[l]);
	}

	if (!current_smiley_theme && smiley_themes) {
		struct smiley_theme *smile = smiley_themes->data;
		pidgin_themes_load_smiley_theme(smile->path, TRUE);
	}
}

GSList *pidgin_themes_get_proto_smileys(const char *id) {
	PurplePlugin *proto;
	struct smiley_list *list, *def;

	if ((current_smiley_theme == NULL) || (current_smiley_theme->list == NULL))
		return NULL;

	def = list = current_smiley_theme->list;

	if (id == NULL)
		return def->smileys;

	proto = purple_find_prpl(id);

	while (list) {
		if (!strcmp(list->sml, "default"))
			def = list;
		else if (proto && !strcmp(proto->info->name, list->sml))
			break;

		list = list->next;
	}

	return list ? list->smileys : def->smileys;
}

void pidgin_themes_init()
{
	GSList *l;
	const char *current_theme =
		purple_prefs_get_string(PIDGIN_PREFS_ROOT "/smileys/theme");

	pidgin_themes_smiley_theme_probe();

	for (l = smiley_themes; l; l = l->next) {
		struct smiley_theme *smile = l->data;
		if (smile->name && strcmp(current_theme, smile->name) == 0) {
			pidgin_themes_load_smiley_theme(smile->path, TRUE);
			break;
		}
	}

	/* If we still don't have a smiley theme, choose the first one */
	if (!current_smiley_theme && smiley_themes) {
		struct smiley_theme *smile = smiley_themes->data;
		pidgin_themes_load_smiley_theme(smile->path, TRUE);
	}
}