Mercurial > pidgin
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)); |