Minor changelog updates
[pidgin-git.git] / pidgin / gtkthemes.c
blobad9f2efb033817131a9145bd22f8512eaedc7d8e
1 /*
2 * Themes for Pidgin
4 * Pidgin is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
24 #include "pidgin.h"
26 #include "conversation.h"
27 #include "debug.h"
28 #include "prpl.h"
29 #include "util.h"
31 #include "gtkconv.h"
32 #include "gtkdialogs.h"
33 #include "gtkimhtml.h"
34 #include "gtkthemes.h"
36 GSList *smiley_themes = NULL;
37 struct smiley_theme *current_smiley_theme;
39 static void pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme);
41 gboolean pidgin_themes_smileys_disabled()
43 if (!current_smiley_theme)
44 return 1;
46 return strcmp(current_smiley_theme->name, "none") == 0;
49 static void
50 pidgin_themes_destroy_smiley_theme(struct smiley_theme *theme)
52 pidgin_themes_destroy_smiley_theme_smileys(theme);
54 g_free(theme->name);
55 g_free(theme->desc);
56 g_free(theme->author);
57 g_free(theme->icon);
58 g_free(theme->path);
59 g_free(theme);
62 static void pidgin_themes_remove_theme_dir(const char *theme_dir_name)
64 GString *str = NULL;
65 const char *file_name = NULL;
66 GDir *theme_dir = NULL;
68 if ((theme_dir = g_dir_open(theme_dir_name, 0, NULL)) != NULL) {
69 if ((str = g_string_new(theme_dir_name)) != NULL) {
70 while ((file_name = g_dir_read_name(theme_dir)) != NULL) {
71 g_string_printf(str, "%s%s%s", theme_dir_name, G_DIR_SEPARATOR_S, file_name);
72 g_unlink(str->str);
74 g_string_free(str, TRUE);
76 g_dir_close(theme_dir);
77 g_rmdir(theme_dir_name);
81 void pidgin_themes_remove_smiley_theme(const char *file)
83 char *theme_dir = NULL, *last_slash = NULL;
84 g_return_if_fail(NULL != file);
86 if (!g_file_test(file, G_FILE_TEST_EXISTS)) return;
87 if ((theme_dir = g_strdup(file)) == NULL) return ;
89 if ((last_slash = g_strrstr(theme_dir, G_DIR_SEPARATOR_S)) != NULL) {
90 GSList *iter = NULL;
91 struct smiley_theme *theme = NULL, *new_theme = NULL;
93 *last_slash = 0;
95 /* Delete files on disk */
96 pidgin_themes_remove_theme_dir(theme_dir);
98 /* Find theme in themes list and remove it */
99 for (iter = smiley_themes ; iter ; iter = iter->next) {
100 theme = ((struct smiley_theme *)(iter->data));
101 if (!strcmp(theme->path, file))
102 break ;
104 if (iter) {
105 if (theme == current_smiley_theme) {
106 new_theme = ((struct smiley_theme *)(NULL == iter->next ? (smiley_themes == iter ? NULL : smiley_themes->data) : iter->next->data));
107 if (new_theme)
108 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", new_theme->name);
109 else
110 current_smiley_theme = NULL;
112 smiley_themes = g_slist_delete_link(smiley_themes, iter);
114 /* Destroy theme structure */
115 pidgin_themes_destroy_smiley_theme(theme);
119 g_free(theme_dir);
122 void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
124 struct smiley_list *list;
125 if (!current_smiley_theme)
126 return;
128 gtk_imhtml_remove_smileys(GTK_IMHTML(imhtml));
129 list = current_smiley_theme->list;
130 while (list) {
131 char *sml = !strcmp(list->sml, "default") ? NULL : list->sml;
132 GSList *icons = list->smileys;
133 while (icons) {
134 gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data);
135 icons = icons->next;
137 list = list->next;
141 static void
142 pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme)
144 GHashTable *already_freed;
145 struct smiley_list *wer;
147 already_freed = g_hash_table_new(g_direct_hash, g_direct_equal);
148 for (wer = theme->list; wer != NULL; wer = theme->list) {
149 while (wer->smileys) {
150 GtkIMHtmlSmiley *uio = wer->smileys->data;
151 if (uio->icon)
152 g_object_unref(uio->icon);
153 if (g_hash_table_lookup(already_freed, uio->file) == NULL) {
154 g_free(uio->file);
155 g_hash_table_insert(already_freed, uio->file, GINT_TO_POINTER(1));
157 g_free(uio->smile);
158 g_free(uio);
159 wer->smileys = g_slist_remove(wer->smileys, uio);
161 theme->list = wer->next;
162 g_free(wer->sml);
163 g_free(wer);
165 theme->list = NULL;
167 g_hash_table_destroy(already_freed);
170 static void
171 pidgin_smiley_themes_remove_non_existing()
173 static struct smiley_theme *theme = NULL;
174 GSList *iter = NULL;
176 if (!smiley_themes) return ;
178 for (iter = smiley_themes ; iter ; iter = iter->next) {
179 theme = ((struct smiley_theme *)(iter->data));
180 if (!g_file_test(theme->path, G_FILE_TEST_EXISTS)) {
181 if (theme == current_smiley_theme)
182 current_smiley_theme = ((struct smiley_theme *)(NULL == iter->next ? NULL : iter->next->data));
183 pidgin_themes_destroy_smiley_theme(theme);
184 iter->data = NULL;
187 /* Remove all elements whose data is NULL */
188 smiley_themes = g_slist_remove_all(smiley_themes, NULL);
190 if (!current_smiley_theme && smiley_themes) {
191 struct smiley_theme *smile = g_slist_last(smiley_themes)->data;
192 pidgin_themes_load_smiley_theme(smile->path, TRUE);
196 void pidgin_themes_load_smiley_theme(const char *file, gboolean load)
198 FILE *f = g_fopen(file, "r");
199 char buf[256];
200 char *i;
201 struct smiley_theme *theme=NULL;
202 struct smiley_list *list = NULL;
203 GSList *lst = smiley_themes;
204 char *dirname;
205 gboolean new_theme = FALSE;
207 if (!f)
208 return;
210 while (lst) {
211 struct smiley_theme *thm = lst->data;
212 if (!strcmp(thm->path, file)) {
213 theme = thm;
214 break;
216 lst = lst->next;
219 if (!theme) {
220 new_theme = TRUE;
221 theme = g_new0(struct smiley_theme, 1);
222 theme->path = g_strdup(file);
223 } else if (theme == current_smiley_theme) {
224 /* Don't reload the theme if it is already loaded */
225 fclose(f);
226 return;
229 dirname = g_path_get_dirname(file);
231 while (!feof(f)) {
232 if (!fgets(buf, sizeof(buf), f)) {
233 break;
236 if (buf[0] == '#' || buf[0] == '\0')
237 continue;
239 i = buf;
240 while (isspace(*i))
241 i++;
243 if (*i == '[' && strchr(i, ']') && load) {
244 struct smiley_list *child = g_new0(struct smiley_list, 1);
245 child->sml = g_strndup(i+1, strchr(i, ']') - i - 1);
246 if (theme->list)
247 list->next = child;
248 else
249 theme->list = child;
250 /* Reverse the Smiley list since it was built in reverse order for efficiency reasons */
251 if (list != NULL)
252 list->smileys = g_slist_reverse(list->smileys);
253 list = child;
254 } else if (!g_ascii_strncasecmp(i, "Name=", strlen("Name="))) {
255 int len;
256 g_free(theme->name);
257 theme->name = g_strdup(i + strlen("Name="));
258 len = strlen(theme->name);
259 theme->name[len-1] = 0;
260 if(len > 2 && theme->name[len-2] == '\r')
261 theme->name[len-2] = 0;
262 } else if (!g_ascii_strncasecmp(i, "Description=", strlen("Description="))) {
263 g_free(theme->desc);
264 theme->desc = g_strdup(i + strlen("Description="));
265 theme->desc[strlen(theme->desc)-1] = 0;
266 } else if (!g_ascii_strncasecmp(i, "Icon=", strlen("Icon="))) {
267 g_free(theme->icon);
268 theme->icon = g_build_filename(dirname, i + strlen("Icon="), NULL);
269 theme->icon[strlen(theme->icon)-1] = 0;
270 } else if (!g_ascii_strncasecmp(i, "Author=", strlen("Author="))) {
271 g_free(theme->author);
272 theme->author = g_strdup(i + strlen("Author="));
273 theme->author[strlen(theme->author)-1] = 0;
274 } else if (load && list) {
275 gboolean hidden = FALSE;
276 char *sfile = NULL;
277 gboolean have_used_sfile = FALSE;
279 if (*i == '!' && *(i + 1) == ' ') {
280 hidden = TRUE;
281 i = i + 2;
283 while (*i) {
284 char l[64];
285 int li = 0;
286 while (!isspace(*i) && li < sizeof(l) - 1) {
287 if (*i == '\\' && *(i+1) != '\0' && *(i+1) != '\n' && *(i+1) != '\r')
288 i++;
289 l[li++] = *(i++);
291 if (!sfile) {
292 l[li] = 0;
293 sfile = g_build_filename(dirname, l, NULL);
294 } else {
295 GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
296 l[li] = 0;
297 smiley->file = sfile;
298 smiley->smile = g_strdup(l);
299 smiley->hidden = hidden;
300 list->smileys = g_slist_prepend(list->smileys, smiley);
301 have_used_sfile = TRUE;
303 while (isspace(*i))
304 i++;
309 if (!have_used_sfile)
310 g_free(sfile);
314 /* Reverse the Smiley list since it was built in reverse order for efficiency reasons */
315 if (list != NULL)
316 list->smileys = g_slist_reverse(list->smileys);
318 g_free(dirname);
319 fclose(f);
321 if (!theme->name || !theme->desc || !theme->author) {
322 purple_debug_error("gtkthemes", "Invalid file format, not loading smiley theme from '%s'\n", file);
324 pidgin_themes_destroy_smiley_theme(theme);
325 return;
328 if (new_theme) {
329 smiley_themes = g_slist_prepend(smiley_themes, theme);
332 if (load) {
333 GList *cnv;
335 if (current_smiley_theme)
336 pidgin_themes_destroy_smiley_theme_smileys(current_smiley_theme);
337 current_smiley_theme = theme;
339 for (cnv = purple_get_conversations(); cnv != NULL; cnv = cnv->next) {
340 PurpleConversation *conv = cnv->data;
342 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) {
343 pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml);
344 pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->entry);
350 void pidgin_themes_smiley_theme_probe()
352 GDir *dir;
353 const gchar *file;
354 gchar *path, *test_path;
355 int l;
356 char* probedirs[3];
358 pidgin_smiley_themes_remove_non_existing();
360 probedirs[0] = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", NULL);
361 probedirs[1] = g_build_filename(purple_user_dir(), "smileys", NULL);
362 probedirs[2] = 0;
363 for (l=0; probedirs[l]; l++) {
364 dir = g_dir_open(probedirs[l], 0, NULL);
365 if (dir) {
366 while ((file = g_dir_read_name(dir))) {
367 test_path = g_build_filename(probedirs[l], file, NULL);
368 if (g_file_test(test_path, G_FILE_TEST_IS_DIR)) {
369 path = g_build_filename(probedirs[l], file, "theme", NULL);
371 /* Here we check to see that the theme has proper syntax.
372 * We set the second argument to FALSE so that it doesn't load
373 * the theme yet.
375 pidgin_themes_load_smiley_theme(path, FALSE);
376 g_free(path);
378 g_free(test_path);
380 g_dir_close(dir);
381 } else if (l == 1) {
382 g_mkdir(probedirs[l], S_IRUSR | S_IWUSR | S_IXUSR);
384 g_free(probedirs[l]);
387 if (!current_smiley_theme && smiley_themes) {
388 struct smiley_theme *smile = smiley_themes->data;
389 pidgin_themes_load_smiley_theme(smile->path, TRUE);
393 GSList *pidgin_themes_get_proto_smileys(const char *id) {
394 PurplePlugin *proto;
395 struct smiley_list *list, *def;
397 if ((current_smiley_theme == NULL) || (current_smiley_theme->list == NULL))
398 return NULL;
400 def = list = current_smiley_theme->list;
402 if (id == NULL)
403 return def->smileys;
405 proto = purple_find_prpl(id);
407 while (list) {
408 if (!strcmp(list->sml, "default"))
409 def = list;
410 else if (proto && !strcmp(proto->info->name, list->sml))
411 break;
413 list = list->next;
416 return list ? list->smileys : def->smileys;
419 void pidgin_themes_init()
421 GSList *l;
422 const char *current_theme =
423 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/smileys/theme");
425 pidgin_themes_smiley_theme_probe();
427 for (l = smiley_themes; l; l = l->next) {
428 struct smiley_theme *smile = l->data;
429 if (smile->name && strcmp(current_theme, smile->name) == 0) {
430 pidgin_themes_load_smiley_theme(smile->path, TRUE);
431 break;
435 /* If we still don't have a smiley theme, choose the first one */
436 if (!current_smiley_theme && smiley_themes) {
437 struct smiley_theme *smile = smiley_themes->data;
438 pidgin_themes_load_smiley_theme(smile->path, TRUE);