357
/* Like theme_adium_match() but also return the X part if match is like %foo{X}% */
359
theme_adium_match_with_format (const gchar **str,
363
const gchar *cur = *str;
366
if (!theme_adium_match (&cur, match)) {
371
end = strstr (cur, "}%");
376
*format = g_strndup (cur , end - cur);
381
/* List of colors used by %senderColor%. Copied from
382
* adium/Frameworks/AIUtilities\ Framework/Source/AIColorAdditions.m
384
static gchar *colors[] = {
385
"aqua", "aquamarine", "blue", "blueviolet", "brown", "burlywood", "cadetblue",
386
"chartreuse", "chocolate", "coral", "cornflowerblue", "crimson", "cyan",
387
"darkblue", "darkcyan", "darkgoldenrod", "darkgreen", "darkgrey", "darkkhaki",
388
"darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
389
"darksalmon", "darkseagreen", "darkslateblue", "darkslategrey",
390
"darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgrey",
391
"dodgerblue", "firebrick", "forestgreen", "fuchsia", "gold", "goldenrod",
392
"green", "greenyellow", "grey", "hotpink", "indianred", "indigo", "lawngreen",
393
"lightblue", "lightcoral",
394
"lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen",
395
"lightskyblue", "lightslategrey", "lightsteelblue", "lime", "limegreen",
396
"magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
397
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
398
"mediumturquoise", "mediumvioletred", "midnightblue", "navy", "olive",
399
"olivedrab", "orange", "orangered", "orchid", "palegreen", "paleturquoise",
400
"palevioletred", "peru", "pink", "plum", "powderblue", "purple", "red",
401
"rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
402
"sienna", "silver", "skyblue", "slateblue", "slategrey", "springgreen",
403
"steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
318
408
theme_adium_append_html (EmpathyThemeAdium *theme,
319
409
const gchar *func,
320
const gchar *html, gsize len,
321
411
const gchar *message,
322
412
const gchar *avatar_filename,
323
413
const gchar *name,
324
414
const gchar *contact_id,
325
415
const gchar *service_name,
326
416
const gchar *message_classes,
328
418
gboolean is_backlog)
334
424
/* Make some search-and-replace in the html code */
335
string = g_string_sized_new (len + strlen (message));
425
string = g_string_sized_new (strlen (html) + strlen (message));
336
426
g_string_append_printf (string, "%s(\"", func);
337
427
for (cur = html; *cur != '\0'; cur++) {
338
428
const gchar *replace = NULL;
339
429
gchar *dup_replace = NULL;
430
gchar *format = NULL;
341
if (theme_adium_match (&cur, "%message%")) {
343
} else if (theme_adium_match (&cur, "%messageClasses%")) {
344
replace = message_classes;
345
} else if (theme_adium_match (&cur, "%userIconPath%")) {
432
/* Those are all well known keywords that needs replacement in
433
* html files. Please keep them in the same order than the adium
434
* spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
435
if (theme_adium_match (&cur, "%userIconPath%")) {
346
436
replace = avatar_filename;
347
} else if (theme_adium_match (&cur, "%sender%")) {
349
437
} else if (theme_adium_match (&cur, "%senderScreenName%")) {
350
438
replace = contact_id;
439
} else if (theme_adium_match (&cur, "%sender%")) {
441
} else if (theme_adium_match (&cur, "%senderColor%")) {
442
/* A color derived from the user's name.
443
* FIXME: If a colon separated list of HTML colors is at
444
* Incoming/SenderColors.txt it will be used instead of
445
* the default colors.
447
if (contact_id != NULL) {
448
guint hash = g_str_hash (contact_id);
449
replace = colors[hash % G_N_ELEMENTS (colors)];
451
} else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
452
/* FIXME: The path to the status icon of the sender
453
* (available, away, etc...)
455
} else if (theme_adium_match (&cur, "%messageDirection%")) {
456
/* FIXME: The text direction of the message
457
* (either rtl or ltr)
351
459
} else if (theme_adium_match (&cur, "%senderDisplayName%")) {
352
/* %senderDisplayName% -
353
* "The serverside (remotely set) name of the sender,
354
* such as an MSN display name."
460
/* FIXME: The serverside (remotely set) name of the
461
* sender, such as an MSN display name.
356
* We don't have access to that yet so we use local
463
* We don't have access to that yet so we use
464
* local alias instead.
359
} else if (theme_adium_match (&cur, "%service%")) {
360
replace = service_name;
467
} else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
468
/* FIXME: This keyword is used to represent the
469
* highlight background color. "X" is the opacity of the
470
* background, ranges from 0 to 1 and can be any decimal
473
} else if (theme_adium_match (&cur, "%message%")) {
475
} else if (theme_adium_match (&cur, "%time%") ||
476
theme_adium_match_with_format (&cur, "%time{", &format)) {
477
/* FIXME: format is not exactly strftime.
478
* See NSDateFormatter spec:
479
* http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
482
dup_replace = empathy_time_to_string_local (timestamp,
483
format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
485
dup_replace = empathy_time_to_string_local (timestamp,
486
format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
488
replace = dup_replace;
361
489
} else if (theme_adium_match (&cur, "%shortTime%")) {
362
490
dup_replace = empathy_time_to_string_local (timestamp,
363
491
EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
364
492
replace = dup_replace;
365
} else if (theme_adium_match (&cur, "%time")) {
366
gchar *format = NULL;
368
/* Time can be in 2 formats:
369
* %time% or %time{strftime format}%
370
* Extract the time format if provided. */
373
end = strstr (cur, "}%");
378
format = g_strndup (cur, end - cur);
385
dup_replace = empathy_time_to_string_local (timestamp,
386
format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
388
dup_replace = empathy_time_to_string_local (timestamp,
389
format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
391
replace = dup_replace;
493
} else if (theme_adium_match (&cur, "%service%")) {
494
replace = service_name;
495
} else if (theme_adium_match (&cur, "%variant%")) {
496
/* FIXME: The name of the active message style variant,
497
* with all spaces replaced with an underscore.
498
* A variant named "Alternating Messages - Blue Red"
499
* will become "Alternating_Messages_-_Blue_Red".
501
} else if (theme_adium_match (&cur, "%userIcons%")) {
502
/* FIXME: mus t be "hideIcons" if use preference is set
504
replace = "showIcons";
505
} else if (theme_adium_match (&cur, "%messageClasses%")) {
506
replace = message_classes;
507
} else if (theme_adium_match (&cur, "%status%")) {
508
/* FIXME: A description of the status event. This is
509
* neither in the user's local language nor expected to
510
* be displayed; it may be useful to use a different div
511
* class to present different types of status messages.
512
* The following is a list of some of the more important
513
* status messages; your message style should be able to
514
* handle being shown a status message not in this list,
515
* as even at present the list is incomplete and is
516
* certain to become out of date in the future:
525
* contact_joined (group chats)
529
* encryption (all OTR messages use this status)
530
* purple (all IRC topic and join/part messages use this status)
531
* fileTransferStarted
532
* fileTransferCompleted
394
535
escape_and_append_len (string, cur, 1);
529
742
g_string_append (message_classes, " incoming");
744
if (empathy_message_should_highlight (msg)) {
745
g_string_append (message_classes, " mention");
747
if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
748
g_string_append (message_classes, " autoreply");
751
g_string_append (message_classes, " action");
753
/* FIXME: other classes:
754
* status - the message is a status change
755
* event - the message is a notification of something happening
756
* (for example, encryption being turned on)
757
* %status% - See %status% in theme_adium_append_html ()
532
760
/* Define javascript function to use */
533
761
if (consecutive) {
534
func = "appendNextMessage";
762
func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
536
func = "appendMessage";
764
func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
540
767
if (empathy_contact_is_user (sender)) {
543
html = priv->data->out_nextcontext_html;
544
len = priv->data->out_nextcontext_len;
547
/* Not backlog, or fallback if NextContext.html
550
html = priv->data->out_nextcontent_html;
551
len = priv->data->out_nextcontent_len;
555
/* Not consecutive, or fallback if NextContext.html and/or
556
* NextContent.html are missing */
559
html = priv->data->out_context_html;
560
len = priv->data->out_context_len;
564
html = priv->data->out_content_html;
565
len = priv->data->out_content_len;
570
/* Incoming, or fallback if outgoing files are missing */
574
html = priv->data->in_nextcontext_html;
575
len = priv->data->in_nextcontext_len;
578
/* Note backlog, or fallback if NextContext.html
581
html = priv->data->in_nextcontent_html;
582
len = priv->data->in_nextcontent_len;
586
/* Not consecutive, or fallback if NextContext.html and/or
587
* NextContent.html are missing */
590
html = priv->data->in_context_html;
591
len = priv->data->in_context_len;
595
html = priv->data->in_content_html;
596
len = priv->data->in_content_len;
602
theme_adium_append_html (theme, func, html, len, body_escaped,
603
avatar_filename, name, contact_id,
604
service_name, message_classes->str,
605
timestamp, is_backlog);
771
html = consecutive ? priv->data->out_nextcontext_html : priv->data->out_context_html;
774
html = consecutive ? priv->data->out_nextcontent_html : priv->data->out_content_html;
607
DEBUG ("Couldn't find HTML file for this message");
780
html = consecutive ? priv->data->in_nextcontext_html : priv->data->in_context_html;
783
html = consecutive ? priv->data->in_nextcontent_html : priv->data->in_content_html;
787
theme_adium_append_html (theme, func, html, body_escaped,
788
avatar_filename, name, contact_id,
789
service_name, message_classes->str,
790
timestamp, is_backlog);
610
792
/* Keep the sender of the last displayed message */
611
793
if (priv->last_contact) {
612
794
g_object_unref (priv->last_contact);
1453
adium_info_get_version (GHashTable *info)
1455
return tp_asv_get_int32 (info, "MessageViewVersion", NULL);
1458
static const gchar *
1459
adium_info_get_no_variant_name (GHashTable *info)
1461
const gchar *name = tp_asv_get_string (info, "DisplayNameForNoVariant");
1462
return name ? name : _("Normal");
1465
static const gchar *
1466
adium_info_get_default_or_first_variant (GHashTable *info)
1469
GPtrArray *variants;
1471
name = empathy_adium_info_get_default_variant (info);
1476
variants = empathy_adium_info_get_available_variants (info);
1477
g_assert (variants->len > 0);
1478
return g_ptr_array_index (variants, 0);
1482
adium_info_dup_path_for_variant (GHashTable *info,
1483
const gchar *variant)
1485
guint version = adium_info_get_version (info);
1486
const gchar *no_variant = adium_info_get_no_variant_name (info);
1488
if (version <= 2 && !tp_strdiff (variant, no_variant)) {
1489
return g_strdup ("main.css");
1492
return g_strdup_printf ("Variants/%s.css", variant);
1497
empathy_adium_info_get_default_variant (GHashTable *info)
1499
if (adium_info_get_version (info) <= 2) {
1500
return adium_info_get_no_variant_name (info);
1503
return tp_asv_get_string (info, "DefaultVariant");
1507
empathy_adium_info_get_available_variants (GHashTable *info)
1509
GPtrArray *variants;
1514
variants = tp_asv_get_boxed (info, "AvailableVariants", G_TYPE_PTR_ARRAY);
1515
if (variants != NULL) {
1519
variants = g_ptr_array_new_with_free_func (g_free);
1520
tp_asv_take_boxed (info, g_strdup ("AvailableVariants"),
1521
G_TYPE_PTR_ARRAY, variants);
1523
path = tp_asv_get_string (info, "path");
1524
dirpath = g_build_filename (path, "Contents", "Resources", "Variants", NULL);
1525
dir = g_dir_open (dirpath, 0, NULL);
1529
for (name = g_dir_read_name (dir);
1531
name = g_dir_read_name (dir)) {
1532
gchar *display_name;
1534
if (!g_str_has_suffix (name, ".css")) {
1538
display_name = g_strdup (name);
1539
strstr (display_name, ".css")[0] = '\0';
1540
g_ptr_array_add (variants, display_name);
1546
if (adium_info_get_version (info) <= 2) {
1547
g_ptr_array_add (variants,
1548
g_strdup (adium_info_get_no_variant_name (info)));
1237
1555
empathy_adium_data_get_type (void)
1271
1583
data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1272
1584
G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1273
1585
data->info = g_hash_table_ref (info);
1586
data->version = adium_info_get_version (info);
1587
data->strings_to_free = g_ptr_array_new_with_free_func (g_free);
1589
DEBUG ("Loading theme at %s", path);
1591
#define LOAD(path, var) \
1592
tmp = g_build_filename (data->basedir, path, NULL); \
1593
g_file_get_contents (tmp, &var, NULL, NULL); \
1596
#define LOAD_CONST(path, var) \
1599
LOAD (path, content); \
1600
if (content != NULL) { \
1601
g_ptr_array_add (data->strings_to_free, content); \
1275
1606
/* Load html files */
1276
file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1277
g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1280
file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1281
g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1284
file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1285
g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1288
file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1289
g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1292
file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1293
g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1296
file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1297
g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1300
file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1301
g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1304
file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1305
g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1308
file = g_build_filename (data->basedir, "Status.html", NULL);
1309
g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1312
file = g_build_filename (data->basedir, "Footer.html", NULL);
1313
g_file_get_contents (file, &footer_html, &footer_len, NULL);
1316
file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1317
if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1318
data->default_incoming_avatar_filename = file;
1323
file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1324
if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1325
data->default_outgoing_avatar_filename = file;
1330
css_path = g_build_filename (data->basedir, "main.css", NULL);
1332
/* There is 2 formats for Template.html: The old one has 4 parameters,
1333
* the new one has 5 parameters. */
1334
file = g_build_filename (data->basedir, "Template.html", NULL);
1335
if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
1336
strv = g_strsplit (template_html, "%@", -1);
1337
len = g_strv_length (strv);
1341
if (len != 5 && len != 6) {
1342
/* Either the theme has no template or it don't have the good
1343
* number of parameters. Fallback to use our own template. */
1344
g_free (template_html);
1347
file = empathy_file_lookup ("Template.html", "data");
1348
g_file_get_contents (file, &template_html, &template_len, NULL);
1350
strv = g_strsplit (template_html, "%@", -1);
1351
len = g_strv_length (strv);
1354
/* Replace %@ with the needed information in the template html. */
1355
string = g_string_sized_new (template_len);
1356
g_string_append (string, strv[i++]);
1357
g_string_append (string, data->basedir);
1358
g_string_append (string, strv[i++]);
1360
const gchar *variant;
1362
/* We include main.css by default */
1363
g_string_append_printf (string, "@import url(\"%s\");", css_path);
1364
g_string_append (string, strv[i++]);
1365
variant = tp_asv_get_string (data->info, "DefaultVariant");
1367
g_string_append (string, "Variants/");
1368
g_string_append (string, variant);
1369
g_string_append (string, ".css");
1372
/* FIXME: We should set main.css OR the variant css */
1373
g_string_append (string, css_path);
1375
g_string_append (string, strv[i++]);
1376
g_string_append (string, ""); /* We don't want header */
1377
g_string_append (string, strv[i++]);
1378
/* FIXME: We should replace adium %macros% in footer */
1380
g_string_append (string, footer_html);
1382
g_string_append (string, strv[i++]);
1383
data->template_html = g_string_free (string, FALSE);
1607
LOAD_CONST ("Content.html", data->content_html);
1608
LOAD_CONST ("Incoming/Content.html", data->in_content_html);
1609
LOAD_CONST ("Incoming/NextContent.html", data->in_nextcontent_html);
1610
LOAD_CONST ("Incoming/Context.html", data->in_context_html);
1611
LOAD_CONST ("Incoming/NextContext.html", data->in_nextcontext_html);
1612
LOAD_CONST ("Outgoing/Content.html", data->out_content_html);
1613
LOAD_CONST ("Outgoing/NextContent.html", data->out_nextcontent_html);
1614
LOAD_CONST ("Outgoing/Context.html", data->out_context_html);
1615
LOAD_CONST ("Outgoing/NextContext.html", data->out_nextcontext_html);
1616
LOAD_CONST ("Status.html", data->status_html);
1617
LOAD ("Template.html", template_html);
1618
LOAD ("Footer.html", footer_html);
1623
/* HTML fallbacks: If we have at least content OR in_content, then
1624
* everything else gets a fallback */
1626
#define FALLBACK(html, fallback) \
1627
if (html == NULL) { \
1631
/* in_nextcontent -> in_content -> content */
1632
FALLBACK (data->in_content_html, data->content_html);
1633
FALLBACK (data->in_nextcontent_html, data->in_content_html);
1635
/* context -> content */
1636
FALLBACK (data->in_context_html, data->in_content_html);
1637
FALLBACK (data->in_nextcontext_html, data->in_nextcontent_html);
1638
FALLBACK (data->out_context_html, data->out_content_html);
1639
FALLBACK (data->out_nextcontext_html, data->out_nextcontent_html);
1642
FALLBACK (data->out_content_html, data->in_content_html);
1643
FALLBACK (data->out_nextcontent_html, data->in_nextcontent_html);
1644
FALLBACK (data->out_context_html, data->in_context_html);
1645
FALLBACK (data->out_nextcontext_html, data->in_nextcontext_html);
1647
/* status -> in_content */
1648
FALLBACK (data->status_html, data->in_content_html);
1652
/* template -> empathy's template */
1653
data->custom_template = (template_html != NULL);
1654
if (template_html == NULL) {
1655
tmp = empathy_file_lookup ("Template.html", "data");
1656
g_file_get_contents (tmp, &template_html, NULL, NULL);
1660
/* Default avatar */
1661
tmp = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1662
if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1663
data->default_incoming_avatar_filename = tmp;
1667
tmp = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1668
if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1669
data->default_outgoing_avatar_filename = tmp;
1674
variant_path = adium_info_dup_path_for_variant (info,
1675
adium_info_get_default_or_first_variant (info));
1677
/* Old custom templates had only 4 parameters.
1678
* New templates have 5 parameters */
1679
if (data->version <= 2 && data->custom_template) {
1680
tmp = string_with_format (template_html,
1683
"", /* The header */
1684
footer_html ? footer_html : "",
1687
tmp = string_with_format (template_html,
1689
data->version <= 2 ? "" : "@import url( \"main.css\" );",
1691
"", /* The header */
1692
footer_html ? footer_html : "",
1695
g_ptr_array_add (data->strings_to_free, tmp);
1696
data->template_html = tmp;
1698
g_free (template_html);
1385
1699
g_free (footer_html);
1386
g_free (template_html);
1700
g_free (variant_path);