comparison pidgin/plugins/adiumthemes/webkit.c @ 32105:59f433824040

Separated the MessageStyle loading code from the actual rendering code.
author tdrhq@soc.pidgin.im
date Fri, 14 Aug 2009 02:51:41 +0000
parents 53735be6950a
children 285db86fcf99
comparison
equal deleted inserted replaced
32104:53735be6950a 32105:59f433824040
48 #include <gtkplugin.h> 48 #include <gtkplugin.h>
49 #include <gtkwebview.h> 49 #include <gtkwebview.h>
50 #include <smileyparser.h> 50 #include <smileyparser.h>
51 51
52 #include <libxml/xmlreader.h> 52 #include <libxml/xmlreader.h>
53
54 #include "message-style.h"
53 /* GObject data keys */ 55 /* GObject data keys */
54 #define MESSAGE_STYLE_KEY "message-style" 56 #define MESSAGE_STYLE_KEY "message-style"
55 57
56 /*
57 * I'm going to allow a different style for each PidginConversation.
58 * This way I can do two things: 1) change the theme on the fly and not
59 * change existing themes, and 2) Use a different theme for IMs and
60 * chats.
61 */
62 typedef struct _PidginMessageStyle {
63 int ref_counter;
64
65 /* current config options */
66 char *variant; /* allowed to be NULL if there are no variants */
67 char *bg_color;
68
69 /* Info.plist keys */
70 int message_view_version;
71 char *cf_bundle_name;
72 char *cf_bundle_identifier;
73 char *cf_bundle_get_info_string;
74 char *default_font_family;
75 int default_font_size;
76 gboolean shows_user_icons;
77 gboolean disable_combine_consecutive;
78 gboolean default_background_is_transparent;
79 gboolean disable_custom_background;
80 char *default_background_color;
81 gboolean allow_text_colors;
82 char *image_mask;
83
84 /* paths */
85 char *style_dir;
86 char *template_path;
87
88 /* caches */
89 char *template_html;
90 char *header_html;
91 char *footer_html;
92 char *incoming_content_html;
93 char *outgoing_content_html;
94 char *incoming_next_content_html;
95 char *outgoing_next_content_html;
96 char *status_html;
97 char *basestyle_css;
98 } PidginMessageStyle;
99
100 static GList *style_list; /**< List of PidginMessageStyles */
101 static char *cur_style_dir = NULL; 58 static char *cur_style_dir = NULL;
102 static void *handle = NULL; 59 static void *handle = NULL;
103
104 static PidginMessageStyle* pidgin_message_style_new (const char* styledir);
105 static PidginMessageStyle* pidgin_message_style_load (const char* styledir);
106 static void pidgin_message_style_unref (PidginMessageStyle *style);
107 static void pidgin_message_style_read_info_plist (PidginMessageStyle *style, const char* variant);
108 static char* pidgin_message_style_get_variant (PidginMessageStyle *style);
109 static GList* pidgin_message_style_get_variants (PidginMessageStyle *style);
110 static void pidgin_message_style_set_variant (PidginMessageStyle *style, const char *variant);
111
112 static void
113 glist_free_all_string (GList *list)
114 {
115 GList *first = list;
116 for (; list; list = g_list_next (list))
117 g_free (list->data);
118 g_list_free (first);
119 }
120 60
121 static inline char* get_absolute_path (const char *path) 61 static inline char* get_absolute_path (const char *path)
122 { 62 {
123 if (g_path_is_absolute (path)) return g_strdup (path); 63 if (g_path_is_absolute (path)) return g_strdup (path);
124 else { 64 else {
127 g_free (cwd); 67 g_free (cwd);
128 return ret; 68 return ret;
129 } 69 }
130 } 70 }
131 71
132 static PidginMessageStyle* pidgin_message_style_new (const char* styledir) 72 static void
133 { 73 glist_free_all_string (GList *list)
134 PidginMessageStyle* ret = g_new0 (PidginMessageStyle, 1); 74 {
135 GList *iter; 75 GList *first = list;
136 76 for (; list; list = g_list_next (list))
137 /* sanity check */ 77 g_free (list->data);
138 for (iter = style_list; iter; iter = g_list_next (iter)) 78 g_list_free (first);
139 g_assert (!g_str_equal (((PidginMessageStyle*)iter->data)->style_dir, styledir));
140
141 ret->ref_counter = 1;
142 ret->style_dir = g_strdup (styledir);
143
144 style_list = g_list_append (style_list, ret);
145 return ret;
146 }
147
148 /**
149 * deallocate any memory used for info.plist options
150 */
151 static void
152 pidgin_message_style_unset_info_plist (PidginMessageStyle *style)
153 {
154 style->message_view_version = 0;
155 g_free (style->cf_bundle_name);
156 style->cf_bundle_name = NULL;
157
158 g_free (style->cf_bundle_identifier);
159 style->cf_bundle_identifier = NULL;
160
161 g_free (style->cf_bundle_get_info_string);
162 style->cf_bundle_get_info_string = NULL;
163
164 g_free (style->default_font_family);
165 style->default_font_family = NULL;
166
167 style->default_font_size = 0;
168 style->shows_user_icons = TRUE;
169 style->disable_combine_consecutive = FALSE;
170 style->default_background_is_transparent = FALSE;
171 style->disable_custom_background = FALSE;
172
173 g_free (style->default_background_color);
174 style->default_background_color = NULL;
175
176 style->allow_text_colors = TRUE;
177
178 g_free (style->image_mask);
179 style->image_mask = NULL;
180 }
181
182
183 static void pidgin_message_style_unref (PidginMessageStyle *style)
184 {
185 if (!style) return;
186 g_assert (style->ref_counter > 0);
187
188 style->ref_counter--;
189 if (style->ref_counter) return;
190
191 g_free (style->style_dir);
192 g_free (style->template_path);
193
194 g_free (style->template_html);
195 g_free (style->incoming_content_html);
196 g_free (style->outgoing_content_html);
197 g_free (style->outgoing_next_content_html);
198 g_free (style->status_html);
199 g_free (style->basestyle_css);
200
201 style_list = g_list_remove (style_list, style);
202 g_free (style);
203
204 pidgin_message_style_unset_info_plist (style);
205 }
206
207 static void
208 pidgin_message_style_save_state (PidginMessageStyle *style)
209 {
210 char *prefname = g_strdup_printf ("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier);
211 char *variant = g_strdup_printf ("%s/%s", prefname, style->variant);
212
213 purple_prefs_add_none (prefname);
214 purple_prefs_add_string (variant, "");
215 purple_prefs_set_string (variant, style->variant);
216
217 g_free (prefname);
218 g_free (variant);
219 }
220
221 static void
222 pidgin_message_style_load_state (PidginMessageStyle *style)
223 {
224 char *prefname = g_strdup_printf ("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier);
225 char *variant = g_strdup_printf ("%s/%s", prefname, style->variant);
226
227 const char* value = purple_prefs_get_string (variant);
228 gboolean changed = !style->variant || !g_str_equal (style->variant, value);
229
230 g_free (style->variant);
231 style->variant = g_strdup (value);
232
233 if (changed) pidgin_message_style_read_info_plist (style, style->variant);
234
235 g_free (prefname);
236 g_free(variant);
237 } 79 }
238 80
239 static void webkit_on_webview_destroy (GtkObject* obj, gpointer data); 81 static void webkit_on_webview_destroy (GtkObject* obj, gpointer data);
240
241 static gboolean
242 parse_info_plist_key_value (xmlnode* key, gpointer destination, const char* expected)
243 {
244 xmlnode *val = key->next;
245
246 for (; val && val->type != XMLNODE_TYPE_TAG; val = val->next);
247 if (!val) return FALSE;
248
249 if (expected == NULL || g_str_equal (expected, "string")) {
250 char** dest = (char**) destination;
251 if (!g_str_equal (val->name, BAD_CAST "string")) return FALSE;
252 if (*dest) g_free (*dest);
253 *dest = xmlnode_get_data_unescaped (val);
254 } else if (g_str_equal (expected, "integer")) {
255 int* dest = (int*) destination;
256 char* value = xmlnode_get_data_unescaped (val);
257
258 if (!g_str_equal (val->name, "integer")) return FALSE;
259 *dest = atoi (value);
260 g_free (value);
261 } else if (g_str_equal (expected, "boolean")) {
262 gboolean *dest = (gboolean*) destination;
263 if (g_str_equal (val->name, BAD_CAST "true")) *dest = TRUE;
264 else if (g_str_equal (val->name, BAD_CAST "false")) *dest = FALSE;
265 else return FALSE;
266 } else return FALSE;
267
268 return TRUE;
269 }
270
271 static
272 gboolean str_for_key (const char *key, const char *found, const char *variant){
273 if (g_str_equal (key, found)) return TRUE;
274 if (!variant) return FALSE;
275 return (g_str_has_prefix (found, key)
276 && g_str_has_suffix (found, variant)
277 && strlen (found) == strlen (key) + strlen (variant) + 1);
278 }
279
280 /**
281 * Info.plist should be re-read every time the variant changes, this is because
282 * the keys that take precedence depend on the value of the current variant.
283 */
284 static void
285 pidgin_message_style_read_info_plist (PidginMessageStyle *style, const char* variant)
286 {
287 /* note that if a variant is used the option:VARIANTNAME takes precedence */
288 char *contents = g_build_filename (style->style_dir, "Contents", NULL);
289 xmlnode *plist = xmlnode_from_file (contents, "Info.plist", "Info.plist", "webkit"), *iter;
290 xmlnode *dict = xmlnode_get_child (plist, "dict");
291
292 g_assert (dict);
293 for (iter = xmlnode_get_child (dict, "key"); iter; iter = xmlnode_get_next_twin (iter)) {
294 char* key = xmlnode_get_data_unescaped (iter);
295 gboolean pr = TRUE;
296
297 if (g_str_equal ("MessageViewVersion", key))
298 pr = parse_info_plist_key_value (iter, &style->message_view_version, "integer");
299 else if (g_str_equal ("CFBundleName", key))
300 pr = parse_info_plist_key_value (iter, &style->cf_bundle_name, "string");
301 else if (g_str_equal ("CFBundleIdentifier", key))
302 pr = parse_info_plist_key_value (iter, &style->cf_bundle_identifier, "string");
303 else if (g_str_equal ("CFBundleGetInfoString", key))
304 pr = parse_info_plist_key_value (iter, &style->cf_bundle_get_info_string, "string");
305 else if (str_for_key ("DefaultFontFamily", key, variant))
306 pr = parse_info_plist_key_value (iter, &style->default_font_family, "string");
307 else if (str_for_key ("DefaultFontSize", key, variant))
308 pr = parse_info_plist_key_value (iter, &style->default_font_size, "integer");
309 else if (str_for_key ("ShowsUserIcons", key, variant))
310 pr = parse_info_plist_key_value (iter, &style->shows_user_icons, "boolean");
311 else if (str_for_key ("DisableCombineConsecutive", key, variant))
312 pr = parse_info_plist_key_value (iter, &style->disable_combine_consecutive, "boolean");
313 else if (str_for_key ("DefaultBackgroundIsTransparent", key, variant))
314 pr = parse_info_plist_key_value (iter, &style->default_background_is_transparent, "boolean");
315 else if (str_for_key ("DisableCustomBackground", key, variant))
316 pr = parse_info_plist_key_value (iter, &style->disable_custom_background, "boolean");
317 else if (str_for_key ("DefaultBackgroundColor", key, variant))
318 pr = parse_info_plist_key_value (iter, &style->default_background_color, "string");
319 else if (str_for_key ("AllowTextColors", key, variant))
320 pr = parse_info_plist_key_value (iter, &style->allow_text_colors, "string");
321 else if (str_for_key ("ImageMask", key, variant))
322 pr = parse_info_plist_key_value (iter, &style->image_mask, "string");
323
324 g_free (key);
325 if (!pr) break; /* does not make sense */
326 }
327
328 xmlnode_free (plist);
329 }
330
331 static PidginMessageStyle*
332 pidgin_message_style_load (const char* styledir)
333 {
334 /*
335 * the loading process described:
336 *
337 * First we load all the style .html files, etc.
338 * The we load any config options that have been stored for
339 * this variant.
340 * Then we load the Info.plist, for the currently decided variant.
341 * At this point, if we find that variants exist, yet
342 * we don't have a variant selected, we choose DefaultVariant
343 * and if that does not exist, we choose the first one in the
344 * directory.
345 */
346
347 /* is this style already loaded? */
348 GList *cur = style_list;
349 char *file; /* temporary variable */
350 PidginMessageStyle *style = NULL;
351
352 g_assert (styledir);
353 for (cur = style_list; cur; cur = g_list_next (cur)) {
354 style = (PidginMessageStyle*) cur->data;
355 if (g_str_equal (styledir, style->style_dir)) {
356 style->ref_counter++;
357 return style;
358 }
359 }
360
361 /* else we need to load it */
362 style = pidgin_message_style_new (styledir);
363
364 /* load all other files */
365
366 /* The template path can either come from the theme, or can
367 * be stock Template.html that comes with the plugin */
368 style->template_path = g_build_filename(styledir, "Contents", "Resources", "Template.html", NULL);
369
370 if (!g_file_test(style->template_path, G_FILE_TEST_EXISTS)) {
371 g_free (style->template_path);
372 style->template_path = g_build_filename(DATADIR, "pidgin", "webkit", "Template.html", NULL);
373 }
374
375 if (!g_file_get_contents(style->template_path, &style->template_html, NULL, NULL)) {
376 pidgin_message_style_unref (style);
377 purple_debug_error ("webkit", "Could not locate a Template.html\n");
378 return NULL;
379 }
380
381 file = g_build_filename(styledir, "Contents", "Resources", "Status.html", NULL);
382 if (!g_file_get_contents(file, &style->status_html, NULL, NULL)) {
383 purple_debug_info ("webkit", "%s could not find Resources/Status.html", styledir);
384 pidgin_message_style_unref (style);
385 g_free (file);
386 return NULL;
387 }
388 g_free (file);
389
390 file = g_build_filename(styledir, "Contents", "Resources", "main.css", NULL);
391 if (!g_file_get_contents(file, &style->basestyle_css, NULL, NULL))
392 style->basestyle_css = g_strdup ("");
393 g_free (file);
394
395 file = g_build_filename(styledir, "Contents", "Resources", "Header.html", NULL);
396 if (!g_file_get_contents(file, &style->header_html, NULL, NULL))
397 style->header_html = g_strdup ("");
398 g_free (file);
399
400 file = g_build_filename(styledir, "Contents", "Resources", "Footer.html", NULL);
401 if (!g_file_get_contents(file, &style->footer_html, NULL, NULL))
402 style->footer_html = g_strdup ("");
403 g_free (file);
404
405 file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "Content.html", NULL);
406 if (!g_file_get_contents(file, &style->incoming_content_html, NULL, NULL)) {
407 purple_debug_info ("webkit", "%s did not have a Incoming/Content.html\n", styledir);
408 pidgin_message_style_unref (style);
409 g_free (file);
410 return NULL;
411 }
412 g_free (file);
413
414
415 /* according to the spec, the following are optional files */
416 file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "NextContent.html", NULL);
417 if (!g_file_get_contents(file, &style->incoming_next_content_html, NULL, NULL)) {
418 style->incoming_next_content_html = g_strdup (style->incoming_content_html);
419 }
420 g_free (file);
421
422 file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "Content.html", NULL);
423 if (!g_file_get_contents(file, &style->outgoing_content_html, NULL, NULL)) {
424 style->outgoing_content_html = g_strdup(style->incoming_content_html);
425 }
426 g_free (file);
427
428 file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "NextContent.html", NULL);
429 if (!g_file_get_contents(file, &style->outgoing_next_content_html, NULL, NULL)) {
430 style->outgoing_next_content_html = g_strdup (style->outgoing_content_html);
431 }
432
433 pidgin_message_style_load_state (style);
434 pidgin_message_style_read_info_plist (style, style->variant);
435
436 /* non variant dependent Info.plist checks */
437 if (style->message_view_version < 3) {
438 pidgin_message_style_unref (style);
439 return NULL;
440 }
441
442 if (!style->variant)
443 {
444 GList *variants = pidgin_message_style_get_variants (style);
445
446 if (variants)
447 pidgin_message_style_set_variant (style, variants->data);
448
449 glist_free_all_string (variants);
450 pidgin_message_style_save_state (style);
451 }
452
453 return style;
454 }
455
456 static void
457 pidgin_message_style_set_variant (PidginMessageStyle *style, const char *variant)
458 {
459 /* I'm not going to test whether this variant is valid! */
460 g_free (style->variant);
461 style->variant = g_strdup (variant);
462
463 pidgin_message_style_read_info_plist (style, variant);
464 pidgin_message_style_save_state (style);
465
466 /* todo, the style has "changed". Ideally, I would like to use signals at this point. */
467 }
468
469 char* pidgin_message_style_get_variant (PidginMessageStyle *style)
470 {
471 return g_strdup (style->variant);
472 }
473
474 /**
475 * Get a list of variants supported by the style.
476 */
477 static GList*
478 pidgin_message_style_get_variants (PidginMessageStyle *style)
479 {
480 GList *ret = NULL;
481 GDir *variants;
482 const char *css_file;
483 char *css;
484 char *variant_dir;
485
486 g_assert (style->style_dir);
487 variant_dir = g_build_filename(style->style_dir, "Contents", "Resources", "Variants", NULL);
488
489 variants = g_dir_open(variant_dir, 0, NULL);
490 if (!variants) return NULL;
491
492 while ((css_file = g_dir_read_name(variants)) != NULL) {
493 if (!g_str_has_suffix (css_file, ".css"))
494 continue;
495
496 css = g_strndup (css_file, strlen (css_file) - 4);
497 ret = g_list_append(ret, css);
498 }
499
500 g_dir_close(variants);
501 g_free(variant_dir);
502
503 ret = g_list_sort (ret, (GCompareFunc)g_strcmp0);
504 return ret;
505 }
506
507 static char* pidgin_message_style_get_css (PidginMessageStyle *style)
508 {
509 if (!style->variant) {
510 return g_build_filename (style->style_dir, "Contents", "Resources", "main.css", NULL);
511 } else {
512 char *file = g_strdup_printf ("%s.css", style->variant);
513 char *ret = g_build_filename (style->style_dir, "Contents", "Resources", "Variants", file, NULL);
514 g_free (file);
515 return ret;
516 }
517 }
518 82
519 static void* webkit_plugin_get_handle () 83 static void* webkit_plugin_get_handle ()
520 { 84 {
521 if (handle) return handle; 85 if (handle) return handle;
522 else return (handle = g_malloc (1)); 86 else return (handle = g_malloc (1));