From 000ba23aa7a7779ee795b249140db886f338ef84 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Fri, 27 Oct 2000 23:06:26 +0000 Subject: [PATCH] Support for templates in theme files. module-formats.c files needs to be updated, meanwhile here's default.theme you can use. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@791 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- default.theme | 397 +++++++++++++++++++++++++++++++++ src/fe-common/core/printtext.c | 8 +- src/fe-common/core/themes.c | 300 +++++++++++++++++++++---- src/fe-common/core/themes.h | 6 + 4 files changed, 663 insertions(+), 48 deletions(-) create mode 100644 default.theme diff --git a/default.theme b/default.theme new file mode 100644 index 00000000..3b82f488 --- /dev/null +++ b/default.theme @@ -0,0 +1,397 @@ +# these characters are automatically replaced with specified color +replaces = { "[]<>=" = "%K$0-%n"; }; + +# %n specifies the color set in higher level, like in +# {ctcp {nick $0-} requested ... } +# if the ctcp was specified as "%g$0-" and nick was "%W$0-%n", the +# "requested" text would be green + +abstracts = { + # generic + line_start = "%B-%W!%B-%n"; + hilight = "%_$0-%_"; + + channel = "%_$0-%_"; + nick = "%_$0-%_"; + server = "%_$0-%_"; + reason = "[$0-]"; + + # channel specific messages + channick_hilight = "%C$0-%n"; + chanhost_hilight = "[%c$0-%n]"; + channick = "%c$0-%n"; + chanhost = "[$0-]"; + channelhilight = "%c$0-%n"; + ban = "%c$0-%n"; + + # messages + msgnick = "<$0-> %|"; + msgownnick = "%W$0-%n"; + msgchannel = "%K:%c$0-%n"; + msgme = "%Y$0-%n"; + privmsgnick = "[%R$0-%n] "; + privmsghost = "%K(%r$0-%K)%n"; + ownprivmsg = "[%r$0-%n] "; + ownprivmsgdest = "%K(%R$0-%K)"; + + # actions + action = "%W * $0-%n "; + ownaction = "{action $0-}"; + pvtaction = "%W (*) $0-%n "; + pvtaction_query = "{action $0-}"; + pubaction = "{action $0-}"; + + # notices + ownnotice = "[%r$0-]%n "; + ownnotice_target = "%K(%R$0-%K)"; + notice = "%K-%M$0-%K-%n "; + pubnotice_channel = "%K:%m$1"; + pvtnotice_nick = "%K(%m$0-%K)"; + servernotice = "%g!$0-%n "; + + # ctcp + ownctcp = "[%r$0-] "; + ownctcp_target = "K(%R$0-%K)"; + ctcp = "%g$0-%n"; + + # wall + ownwall = "[%W$0-] "; + ownwall_channel = "%K/%c$0-"; + + # wallops + wallop = "%W$0-%n: "; + wallop_nick = "%n$0-"; + wallop_action = "%W * $0-%n "; + + # netsplits + netsplit = "%G$0-%n"; + netjoin = "%C$0-%n"; + + # /names list + names_nickstat = "%_$0-%_"; + names_users = "[%g$0-%n]"; + names_channel = "%G$0-%n"; + + # dcc + dcc = "%g$0-%n"; + dccfile = "%_$0-%_"; + + dccownmsg = "[%r$0-%n] "; + dccownnick = "%K(%R$1%K)%n"; + dccownaction = "{action $0-}"; + dccmsg = "[%G$0-%n] "; + dccmsghost = "%K(%g$0-%K)%n"; + dccquerynick = "%G$0-%n"; + dccaction = "%W (*dcc*) $0-%n %|"; +}; + +# no color codes below +formats = { + "fe-common/core" = { + line_start = "{line_start} "; + line_start_irssi = "{line_start} {hilight Irssi:} "; + timestamp = "$[-2.0]3:$[-2.0]4 "; + servertag = "[$0] "; + daychange = "Day changed to $[-2.0]{0} $3 $2"; + talking_with = "You are now talking with {nick $0}"; + refnum_too_low = "Window number must be greater than 1"; + windowlist_header = "Ref Name Active item Server Level"; + windowlist_line = "$[3]0 %|$[20]1 $[15]2 $[15]3 $4"; + windowlist_footer = ""; + + looking_up = "Looking up {server $0}"; + connecting = "Connecting to {server $0} [$1] port {hilight $2}"; + connection_established = "Connection to {server $0} established"; + cant_connect = "Unable to connect server {server $0} port {hilight $1} {reason $2}"; + connection_lost = "Connection lost to {server $0}"; + lag_disconnected = "No PONG reply from server {server $0} in $1 seconds, disconnecting"; + disconnected = "Disconnected from {server $0} {reason $1}"; + server_quit = "Disconnecting from server {server $0}: {reason $1}"; + server_changed = "Changed to {hilight $2} server {server $1}"; + unknown_server_tag = "Unknown server tag {server $0}"; + server_list = "{server $0}: $1:$2 ($3)"; + server_lookup_list = "{server $0}: $1:$2 ($3) (connecting...)"; + server_reconnect_list = "{server $0}: $1:$2 ($3) ($5 left before reconnecting)"; + server_reconnect_removed = "Removed reconnection to server {server $0} port {hilight $1}"; + server_reconnect_not_found = "Reconnection tag {server $0} not found"; + setupserver_added = "Server {server $0} saved"; + setupserver_removed = "Server {server $0} removed"; + setupserver_not_found = "Server {server $0} not found"; + + join = "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}"; + part = "{channick $0} {chanhost $1} has left {channel $2} {reason $3}"; + kick = "{channick $0} was kicked from {channel $1} by {nick $2} {reason $3}"; + quit = "{channick $0} {chanhost $1} has quit {reason $2}"; + quit_once = "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}"; + nick_changed = "{channick $0} is now known as {channick_hilight $1}"; + invite = "{nick $0} invites you to {channel $1}"; + new_topic = "{nick $0} changed the topic of {channel $1} to: $2"; + topic_unset = "Topic unset by {nick $0} on {channel $1}"; + your_nick_changed = "You're now known as {nick $1}"; + talking_in = "You are now talking in {channel $0}"; + not_in_channels = "You are not on any channels"; + current_channel = "Current channel {channel $0}"; + chanlist_header = "You are on the following channels:"; + chanlist_line = "{channel $[-10]0} %|+$1 ($2): $3"; + chansetup_not_found = "Channel {channel $0} not found"; + chansetup_added = "Channel {channel $0} saved"; + chansetup_removed = "Channel {channel $0} removed"; + chansetup_header = "Channel IRC net Password Settings"; + chansetup_line = "{channel $[15]0} %|$[10]1 $[10]2 $3"; + chansetup_footer = ""; + + own_msg = "{msgnick $2{msgownnick $0}}$1"; + own_msg_channel = "{msgnick $3{msgownnick $0}{msgchannel $1}}$2"; + own_msg_private = "{ownprivmsg msg{ownprivmsgdest $0}}$1"; + own_msg_private_query = "{msgnick {msgownnick $2}}$1"; + pubmsg_me = "{msgnick $2{msgme $0}}$1"; + pubmsg_me_channel = "{msgnick $3{msgme $0}{msgchannel $1}}$2"; + pubmsg_hilight = "{msgnick $3$0$1}$2"; + pubmsg_hilight_channel = "{msgnick $4$0$1{msgchannel 2}}$3"; + pubmsg = "{msgnick $2$0}$1"; + pubmsg_channel = "{msgnick $3$0{msgchannel $1}}$2"; + msg_private = "{privmsgnick $0{privmsghost $1}}$2"; + msg_private_query = "{msgnick {privmsgnick $0}}$2"; + no_msgs_got = "You have not received a message from anyone yet"; + no_msgs_sent = "You have not sent a message to anyone yet"; + + query_start = "Starting query with {nick $0}"; + no_query = "No query with {nick $0}"; + query_server_changed = "Query with {nick $2} changed to server {server $1}"; + + hilight_header = "Highlights:"; + hilight_line = "$[-4]0 $1 $2 $3$4$5"; + hilight_footer = ""; + hilight_not_found = "Highlight not found: $0"; + hilight_removed = "Highlight removed: $0"; + + alias_added = "Alias $0 added"; + alias_removed = "Alias $0 removed"; + alias_not_found = "No such alias: $0"; + aliaslist_header = "Aliases:"; + aliaslist_line = "$[10]0 $1"; + aliaslist_footer = ""; + + log_opened = "Log file {hilight $0} opened"; + log_closed = "Log file {hilight $0} closed"; + log_create_failed = "Couldn't create log file {hilight $0}: $1"; + log_locked = "Log file {hilight $0} is locked, probably by another running Irssi"; + log_not_open = "Log file {hilight $0} not open"; + log_started = "Started logging to file {hilight $0}"; + log_stopped = "Stopped logging to file {hilight $0}"; + log_list_header = "Logs:"; + log_list = "$0 $1: $2 $3$4"; + log_list_footer = ""; + windowlog_file = "Window LOGFILE set to $0"; + windowlog_file_logging = "Can't change window's logfile while log is on"; + no_away_msgs = "No new messages in awaylog"; + away_msgs = "{hilight $1} new messages in awaylog:"; + + module_already_loaded = "Module {hilight $0} already loaded"; + module_load_error = "Error loading module {hilight $0}: $1"; + module_invalid = "{hilight $0} isn't Irssi module"; + module_loaded = "Loaded module {hilight $0}"; + module_unloaded = "Unloaded module {hilight $0}"; + + command_unknown = "Unknown command: $0"; + command_ambiguous = "Ambiguous command: $0"; + option_unknown = "Unknown option: $0"; + option_ambiguous = "Ambiguous option: $0"; + option_missing_arg = "Missing required argument for: $0"; + not_enough_params = "Not enough parameters given"; + not_connected = "Not connected to IRC server yet"; + not_joined = "Not joined to any channels yet"; + chan_not_found = "Not joined to such channel"; + chan_not_synced = "Channel not fully synchronized yet, try again after a while"; + not_good_idea = "Doing this is not a good idea. Add -YES if you really mean it"; + + theme_saved = "Theme saved to $0"; + theme_save_failed = "Error saving theme to $0"; + theme_not_found = "Theme {hilight $0} not found"; + window_theme_changed = "Using theme {hilight $0} in this window"; + format_title = "%:[{hilight $0}] - [{hilight $1}]%:%:"; + format_subtitle = "[{hilight $0}]"; + format_item = "$0 = $1"; + + not_toggle = "Value must be either ON, OFF or TOGGLE"; + perl_error = "Perl error: $0"; + bind_key = "$[10]0 $1 $2"; + bind_unknown_id = "Unknown bind action: $0"; + config_saved = "Saved configuration to file $0"; + config_reloaded = "Reloaded configuration"; + }; + + "fe-common/irc" = { + netsplit = "{netsplit Netsplit} {server $0} {server $1} quits: $2"; + netsplit_more = "{netsplit Netsplit} {server $0} {server $1} quits: $2 (+$3 more, use /NETSPLIT to show all of them)"; + netsplit_join = "{netjoin Netsplit} over, joins: $0"; + netsplit_join_more = "{netjoin Netsplit} over, joins: $0 (+$1 more)"; + no_netsplits = "There are no net splits"; + netsplits_header = "Nick Channel Server Splitted server"; + netsplits_line = "$[9]0 {[10]1 $[20]2 $3"; + netsplits_footer = ""; + ircnet_added = "Ircnet $0 saved"; + ircnet_removed = "Ircnet $0} removed"; + ircnet_not_found = "Ircnet $0 not found"; + ircnet_header = "Ircnets:"; + ircnet_line = "$0: $1"; + ircnet_footer = ""; + setupserver_header = "Server Port IRC Net Settings"; + setupserver_line = "%|$[!20]0 $[5]1 $[10]2 $3"; + setupserver_footer = ""; + + joinerror_toomany = "Cannot join to channel {channel $0} (You have joined to too many channels)"; + joinerror_full = "Cannot join to channel {channel $0} (Channel is full)"; + joinerror_invite = "Cannot join to channel {channel $0} (You must be invited)"; + joinerror_banned = "Cannot join to channel {channel $0} (You are banned)"; + joinerror_bad_key = "Cannot join to channel {channel $0} (Bad channel key)"; + joinerror_bad_mask = "Cannot join to channel {channel $0} (Bad channel mask)"; + joinerror_unavail = "Cannot join to channel {channel $0} (Channel is temporarily unavailable)"; + inviting = "Inviting {nick $0} to {channel $1}"; + not_invited = "You have not been invited to a channel!"; + names = "{names_users Users {names_channel $0}}$1"; + names_nick = "[{names_nickstat $0}$1] "; + endofnames = "{channel $0}: Total of {hilight $1} nicks [{hilight $2} ops, {hilight $3} voices, {hilight $4} normal]"; + channel_created = "Channel {channelhilight $0} created $1"; + topic = "Topic for {channelhilight $0}: $1"; + no_topic = "No topic set for {channelhilight $0}"; + topic_info = "Topic set by {nick $0} [$1]"; + chanmode_change = "mode/{channelhilight $0} [$1] by {nick $2}"; + server_chanmode_change = "{netsplit ServerMode}/{channelhilight $0} [$1] by {nick $2}"; + channel_mode = "mode/{channelhilight $0} [$1]"; + bantype = "Ban type changed to {channel $0}"; + no_bans = "No bans in channel {channel $0}"; + banlist = "{channel $0}: ban {ban $1}"; + banlist_long = "{channel $0}: ban {ban $1} [by {nick $2}, $3 secs ago]"; + ebanlist = "{channel $0}: ban exception {ban $1}"; + ebanlist_long = "{channel $0}: ban exception {ban $1} [by {nick $2}, $3 secs ago]"; + invitelist = "{channel $0}: invite {ban $1}"; + no_such_channel = "{channel $0}: No such channel"; + channel_synced = "Join to {channel $0} was synced in {hilight $1} secs"; + + usermode_change = "Mode change [{hilight $0}] for user {nick $1}"; + user_mode = "Your user mode is [{hilight $0}]"; + away = "You have been marked as being away"; + unaway = "You are no longer marked as being away"; + nick_away = "{nick $0} is away: $1"; + no_such_nick = "{nick $0}: No such nick/channel"; + your_nick = "Your nickname is {nick $0}"; + nick_in_use = "Nick {nick $0} is already in use"; + nick_unavailable = "Nick {nick $0} is temporarily unavailable"; + your_nick_owned = "Your nick is owned by {nick $3} [$1@$2]"; + + whois = "{nick $0} [$1@$2]%: ircname : $3"; + whowas = "{nick $0} [$1@$2]%: ircname : $3"; + whois_idle = " idle : $1 days $2 hours $3 mins $4 secs"; + whois_idle_signon = " idle : $1 days $2 hours $3 mins $4 secs [signon: $5]"; + whois_server = " server : $1 [$2]"; + whois_oper = " : {hilight IRC operator}"; + whois_registered = " : has registered this nick"; + whois_channels = " channels : $1"; + whois_away = " away : $1"; + end_of_whois = "End of WHOIS"; + end_of_whowas = "End of WHOWAS"; + whois_not_found = "There is no such nick $0"; + who = "{channelhilight $[-10]0} %|{nick $[!9]1} $[!3]2 $[!2]3 $4@$5 [{hilight $6}]"; + end_of_who = "End of /WHO list"; + + own_notice = "{ownnotice notice{ownnotice_target $0}}$1"; + own_action = "{ownaction $0}$1"; + own_ctcp = "{ownctcp ctcp{ownctcp_target $0}}$1 $2"; + own_wall = "{ownwall Wall{ownwall_channel}}$1"; + + notice_server = "{servernotice $0}$1"; + notice_public = "{notice $0{pubnotice_channel $1}$2"; + notice_public_ops = "{notice $0{pubnotice_channel @$1}$2"; + notice_private = "{notice $0{pvtnotice_nick $1}}$2"; + action_private = "{pvtaction $0}$2"; + action_private_query = "{pvtaction_query $0}$2"; + action_public = "{pubaction $0}$1"; + action_public_channel = "{pubaction $0{msgchannel $1}}$2"; + + ctcp_reply = "CTCP {hilight $0} reply from {nick $1}: $2"; + ctcp_reply_channel = "CTCP {hilight $0} reply from {nick $1} in channel {channel $3}: $2"; + ctcp_ping_reply = "CTCP {hilight PING} reply from {nick $0}: $1.$[-3.0]2 seconds"; + ctcp_requested = "{ctcp >>> {hilight $0} [$1] requested {hilight $2} from {nick $3}}"; + + online = "Users online: {hilight $0}"; + pong = "PONG received from $0: $1"; + wallops = "{wallop WALLOP{wallop_nick $0}$1"; + action_wallops = "{wallop WALLOP{wallop_action $0}}$1"; + error = "{hilight ERROR} $0"; + unknown_mode = "Unknown mode character $0"; + not_chanop = "You're not channel operator in {channel $0}"; + + silenced = "Silenced {nick $0}"; + unsilenced = "Unsilenced {nick $0}"; + silence_line = "{nick $0}: silence {ban $1}"; + + ignored = "Ignoring {hilight $1} from {nick $0}"; + unignored = "Unignored {nick $0}"; + ignore_not_found = "{nick $0} is not being ignored"; + ignore_no_ignores = "There are no ignores"; + ignore_header = "Ignorance List:"; + ignore_line = "$[-4]0 $1: $2 $3 $4"; + ignore_footer = ""; + }; + + "fe-common/irc/dcc" = { + own_dcc = "{dccownmsg dcc{dccownnick $1}}$2"; + own_dcc_action = "{dccownaction $0}$1"; + own_dcc_ctcp = "{ownctcp ctcp{ownctcp_target $0}}$1 $2"; + dcc_msg = "{dccmsg $0{dccmsghost dcc}}$1"; + action_dcc = "{dccaction $0}$1"; + own_dcc_query = "{msgnick {msgownnick $0}}$2"; + dcc_msg_query = "{msgnick {dccquerynick $0}}$1"; + dcc_ctcp = "{dcc >>> DCC CTCP received from {hilight $0}: $1}"; + dcc_chat = "{dcc DCC CHAT from {nick $0} [$1 port $2]}"; + dcc_chat_not_found = "{dcc No DCC CHAT connection open to {nick $0}}"; + dcc_chat_connected = "{dcc DCC CHAT connection with {nick $0} [$1 port $2] established}"; + dcc_chat_disconnected = "{dcc DCC lost chat to {nick $0}}"; + dcc_send = "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4 bytes]}"; + dcc_send_exists = "{dcc DCC already sending file {dccfile $0} for {nick $1}}"; + dcc_send_not_found = "{dcc DCC not sending file {dccfile $1} to {nick $0}}"; + dcc_send_file_not_found = "{dcc DCC file not found: {dccfile $0}}"; + dcc_send_connected = "{dcc DCC sending file {dccfile $0} for {nick $1} [$2 port $3]}"; + dcc_send_complete = "{dcc DCC sent file {dccfile $0} [{hilight $1}kb] for {nick $2} in {hilight $3} secs [{hilight $4kb/s}]}"; + dcc_send_aborted = "{dcc DCC aborted sending file {dccfile $0} for {nick $1}}"; + dcc_get_not_found = "{dcc DCC no file offered by {nick $0}}"; + dcc_get_connected = "{dcc DCC receiving file {dccfile $0} from {nick $1} [$2 port $3]}"; + dcc_get_complete = "{dcc DCC received file {dccfile $0} [$1kb] from {nick $2} in {hilight $3} secs [$4kb/s]}"; + dcc_get_aborted = "{dcc DCC aborted receiving file {dccfile $0} from {nick $1}}"; + dcc_unknown_ctcp = "{dcc DCC unknown ctcp {hilight $0} from {nick $1} [$2]}"; + dcc_unknown_reply = "{dcc DCC unknown reply {hilight $0} from {nick $1} [$2]}"; + dcc_unknown_type = "{dcc DCC unknown type {hilight $0}}"; + dcc_connect_error = "{dcc DCC can't connect to {hilight $0} port {hilight $1}}"; + dcc_cant_create = "{dcc DCC can't create file {dccfile $0}}"; + dcc_rejected = "{dcc DCC $0 was rejected by {nick $1} [{hilight $2}]}"; + dcc_close = "{dcc DCC $0 close for {nick $1} [{hilight $2}]}"; + dcc_list_header = "{dcc DCC connections}"; + dcc_list_line_chat = "{dcc $0 $1}"; + dcc_list_line_file = "{dcc $0 $1: $2k of $3k ($4%%) - $5kB/s - $6}"; + dcc_list_footer = ""; + }; + + "fe-common/irc/flood" = { + autoignore = "Flood detected from {nick $0}, autoignoring for {hilight $1} minutes"; + autounignore = "Removed autoignore from {nick $0}"; + }; + + "fe-common/irc/notifylist" = { + notify_join = "{nick $0} [$1@$2] [{hilight $3}] has joined to $4"; + notify_part = "{nick $0} has left $4"; + notify_away = "{nick $0} [$5] [$1@$2] [{hilight $3}] is now away: $4"; + notify_unaway = "{nick $0} [$4] [$1@$2] [{hilight $3}] is now unaway"; + notify_unidle = "{nick $0} [$5] [$1@$2] [{hilight $3}] just stopped idling"; + notify_online = "On $0: {hilight $1}"; + notify_offline = "Offline: $0"; + notify_list = "$0: $1 $2 $3"; + }; + + "fe-text" = { + lastlog_start = "{hilight Lastlog}:"; + lastlog_end = "{hilight End of Lastlog}"; + window_too_small = "Not enough room to resize this window"; + cant_hide_last = "You can't hide the last window"; + }; +}; diff --git a/src/fe-common/core/printtext.c b/src/fe-common/core/printtext.c index 0c4382cd..e95c8e23 100644 --- a/src/fe-common/core/printtext.c +++ b/src/fe-common/core/printtext.c @@ -436,7 +436,7 @@ char *output_format_get_text(const char *module, WINDOW_REC *window, va_start(va, formatnum); ret = output_format_text_args(&dest, &formats[formatnum], module_theme == NULL ? NULL : - module_theme->formats[formatnum], va); + module_theme->expanded_formats[formatnum], va); va_end(va); return ret; @@ -456,7 +456,7 @@ static char *output_format_text(TEXT_DEST_REC *dest, int formatnum, ...) va_start(va, formatnum); ret = output_format_text_args(dest, &fecommon_core_formats[formatnum], module_theme == NULL ? NULL : - module_theme->formats[formatnum], va); + module_theme->expanded_formats[formatnum], va); va_end(va); return ret; @@ -481,7 +481,7 @@ void printformat_module_args(const char *module, void *server, str = output_format_text_args(&dest, &formats[formatnum], module_theme == NULL ? NULL : - module_theme->formats[formatnum], va); + module_theme->expanded_formats[formatnum], va); if (*str != '\0') print_string(&dest, str); g_free(str); } @@ -511,7 +511,7 @@ void printformat_module_window_args(const char *module, WINDOW_REC *window, int formats = g_hash_table_lookup(default_formats, module); str = output_format_text_args(&dest, &formats[formatnum], module_theme == NULL ? NULL : - module_theme->formats[formatnum], va); + module_theme->expanded_formats[formatnum], va); if (*str != '\0') print_string(&dest, str); g_free(str); } diff --git a/src/fe-common/core/themes.c b/src/fe-common/core/themes.c index f72d107e..1f1e371d 100644 --- a/src/fe-common/core/themes.c +++ b/src/fe-common/core/themes.c @@ -24,6 +24,7 @@ #include "commands.h" #include "levels.h" #include "misc.h" +#include "special-vars.h" #include "lib-config/iconfig.h" #include "settings.h" @@ -45,21 +46,32 @@ THEME_REC *theme_create(const char *path, const char *name) rec = g_new0(THEME_REC, 1); rec->path = g_strdup(path); rec->name = g_strdup(name); - rec->modules = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + rec->abstracts = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + rec->modules = g_hash_table_new((GHashFunc) g_istr_hash, + (GCompareFunc) g_istr_equal); themes = g_slist_append(themes, rec); signal_emit("theme created", 1, rec); return rec; } +static void theme_abstract_destroy(char *key, char *value) +{ + g_free(key); + g_free(value); +} + static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec) { int n; - for (n = 0; n < rec->count; n++) - if (rec->formats[n] != NULL) - g_free(rec->formats[n]); + for (n = 0; n < rec->count; n++) { + g_free_not_null(rec->formats[n]); + g_free_not_null(rec->expanded_formats[n]); + } g_free(rec->formats); + g_free(rec->expanded_formats); g_free(rec->name); g_free(rec); @@ -70,14 +82,185 @@ void theme_destroy(THEME_REC *rec) themes = g_slist_remove(themes, rec); signal_emit("theme destroyed", 1, rec); + + g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL); + g_hash_table_destroy(rec->abstracts); g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL); g_hash_table_destroy(rec->modules); + g_slist_foreach(rec->replace_keys, (GFunc) g_free, NULL); + g_slist_free(rec->replace_keys); + g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL); + g_slist_free(rec->replace_values); + g_free(rec->path); g_free(rec->name); g_free(rec); } +static char *theme_format_expand_data(THEME_REC *theme, + const char **format, int root); + +static int theme_replace_find(THEME_REC *theme, char chr) +{ + GSList *tmp; + int index = 0; + + for (tmp = theme->replace_keys; tmp != NULL; tmp = tmp->next) { + if (strchr(tmp->data, chr) != NULL) + return index; + index++; + } + + return -1; +} + +static char *theme_replace_expand(THEME_REC *theme, int index, char chr) +{ + GSList *rec; + char data[2]; + + rec = g_slist_nth(theme->replace_values, index); + g_return_val_if_fail(rec != NULL, NULL); + + data[0] = chr; data[1] = '\0'; + return parse_special_string(rec->data, NULL, NULL, data, NULL); +} + +/* append next "item", either a character or $variable */ +static void theme_format_append_next(THEME_REC *theme, GString *str, + const char **format) +{ + int index; + char *value; + + if (**format == '$') { + /* $variable .. we'll always need to skip this, since it + may contain characters that are in replace chars. */ + const char *orig; + char *args[1] = { NULL }; + int free_ret; + + orig = *format; + (*format)++; + value = parse_special((char **) format, NULL, NULL, + args, &free_ret, NULL ); + if (free_ret) g_free(value); + (*format)++; + + /* append the variable name */ + value = g_strndup(orig, (int) (*format-orig)); + g_string_append(str, value); + g_free(value); + return; + } + + index = theme_replace_find(theme, **format); + if (index == -1) + g_string_append_c(str, **format); + else { + value = theme_replace_expand(theme, index, **format); + g_string_append(str, value); + g_free(value); + } + + (*format)++; +} + +/* expand a single {abstract ...data... } */ +static char *theme_format_expand_abstract(THEME_REC *theme, + const char **formatp) +{ + const char *p, *format; + char *abstract, *data, *ret; + int len; + + format = *formatp; + + /* get abstract name first */ + p = format; + while (*p != '\0' && *p != ' ' && + *p != '{' && *p != '}') p++; + if (*p == '\0' || p == format) + return NULL; /* error */ + + len = (int) (p-format); + abstract = g_strndup(format, len); + + /* skip the following space */ + if (*p == ' ') len++; + *formatp = format + len; + + /* get the abstract data */ + data = g_hash_table_lookup(theme->abstracts, abstract); + g_free(abstract); + if (data == NULL) { + /* unknown abstract */ + return NULL; + } + abstract = g_strdup(data); + + /* abstract may itself contain abstracts or replaces :) */ + p = data = abstract; + abstract = theme_format_expand_data(theme, &p, FALSE); + g_free(data); + + /* now we'll need to get the data part. it may contain + more abstracts, they are automatically expanded. */ + data = theme_format_expand_data(theme, formatp, FALSE); + + ret = parse_special_string(abstract, NULL, NULL, data, NULL); + g_free(abstract); + g_free(data); + return ret; +} + +/* expand the data part in {abstract data}. If root is TRUE, we're actually + expanding the original format string so we ignore all extra } chars. */ +static char *theme_format_expand_data(THEME_REC *theme, + const char **format, int root) +{ + GString *str; + char *ret, *abstract; + + str = g_string_new(NULL); + + while (**format != '\0') { + if (!root && **format == '}') { + (*format)++; + break; + } + + if (**format != '{') { + theme_format_append_next(theme, str, format); + continue; + } + + (*format)++; + if (**format == '\0' || **format == '}') + break; /* error */ + + /* get a single {...} */ + abstract = theme_format_expand_abstract(theme, format); + if (abstract != NULL) { + g_string_append(str, abstract); + g_free(abstract); + } + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +static char *theme_format_expand(THEME_REC *theme, const char *format) +{ + g_return_val_if_fail(theme != NULL, NULL); + g_return_val_if_fail(format != NULL, NULL); + + return theme_format_expand_data(theme, &format, TRUE); +} + static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module) { MODULE_THEME_REC *rec; @@ -94,18 +277,78 @@ static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *modul for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ; rec->formats = g_new0(char *, rec->count); + rec->expanded_formats = g_new0(char *, rec->count); g_hash_table_insert(theme->modules, rec->name, rec); return rec; } -static void theme_read_formats(CONFIG_REC *config, THEME_REC *theme, const char *module) +static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme) +{ + GSList *tmp; + CONFIG_NODE *node; + + node = config_node_traverse(config, "replaces", FALSE); + if (node == NULL || node->type != NODE_TYPE_BLOCK) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->key != NULL && node->value != NULL) { + theme->replace_keys = + g_slist_append(theme->replace_keys, + g_strdup(node->key)); + theme->replace_values = + g_slist_append(theme->replace_values, + g_strdup(node->value)); + } + } +} + +static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme) +{ + GSList *tmp; + CONFIG_NODE *node; + + node = config_node_traverse(config, "abstracts", FALSE); + if (node == NULL || node->type != NODE_TYPE_BLOCK) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->key != NULL && node->value != NULL && + g_hash_table_lookup(theme->abstracts, node->key) == NULL) { + g_hash_table_insert(theme->abstracts, + g_strdup(node->key), + g_strdup(node->value)); + } + } +} + +static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec, + FORMAT_REC *formats, + const char *key, const char *value) +{ + int n; + + for (n = 0; formats[n].def != NULL; n++) { + if (formats[n].tag != NULL && + g_strcasecmp(formats[n].tag, key) == 0) { + rec->formats[n] = g_strdup(value); + rec->expanded_formats[n] = + theme_format_expand(theme, value); + break; + } + } +} + +static void theme_read_formats(CONFIG_REC *config, THEME_REC *theme, + const char *module) { MODULE_THEME_REC *rec; FORMAT_REC *formats; CONFIG_NODE *node; GSList *tmp; - int n; formats = g_hash_table_lookup(default_formats, module); if (formats == NULL) return; @@ -120,15 +363,9 @@ static void theme_read_formats(CONFIG_REC *config, THEME_REC *theme, const char for (tmp = node->value; tmp != NULL; tmp = tmp->next) { node = tmp->data; - if (node->key == NULL || node->value == NULL) - continue; - - for (n = 0; formats[n].def != NULL; n++) { - if (formats[n].tag != NULL && - g_strcasecmp(formats[n].tag, node->key) == 0) { - rec->formats[n] = g_strdup(node->value); - break; - } + if (node->key != NULL && node->value != NULL) { + theme_set_format(theme, rec, formats, + node->key, node->value); } } } @@ -241,35 +478,6 @@ THEME_REC *theme_load(const char *name) return theme; } -#if 0 -/* Add all *.theme files from directory to themes */ -static void find_themes(gchar *path) -{ - DIR *dirp; - struct dirent *dp; - char *fname, *name; - int len; - - dirp = opendir(path); - if (dirp == NULL) return; - - while ((dp = readdir(dirp)) != NULL) { - len = strlen(dp->d_name); - if (len <= 6 || strcmp(dp->d_name+len-6, ".theme") != 0) - continue; - - name = g_strndup(dp->d_name, strlen(dp->d_name)-6); - if (!theme_find(name)) { - fname = g_strdup_printf("%s/%s", path, dp->d_name); - themes = g_slist_append(themes, theme_create(fname, name)); - g_free(fname); - } - g_free(name); - } - closedir(dirp); -} -#endif - typedef struct { THEME_REC *theme; CONFIG_REC *config; @@ -295,6 +503,8 @@ static void theme_read(THEME_REC *theme, const char *path) config_parse(config); theme->default_color = config_get_int(config, NULL, "default_color", 15); + theme_read_replaces(config, theme); + theme_read_abstracts(config, theme); rec.theme = theme; rec.config = config; @@ -377,8 +587,10 @@ static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value if (reset || value != NULL) { theme = theme_module_create(current_theme, rec->name); g_free_not_null(theme->formats[n]); + g_free_not_null(theme->expanded_formats[n]); theme->formats[n] = reset ? NULL : g_strdup(value); + theme->expanded_formats[n] = reset ? NULL : theme_format_expand(current_theme, value); text = reset ? formats[n].def : value; } printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_FORMAT_ITEM, formats[n].tag, text); diff --git a/src/fe-common/core/themes.h b/src/fe-common/core/themes.h index 49c29138..c5e48355 100644 --- a/src/fe-common/core/themes.h +++ b/src/fe-common/core/themes.h @@ -8,6 +8,8 @@ typedef struct { int count; char **formats; /* in same order as in module's default formats */ + char **expanded_formats; /* this contains the formats after + expanding {templates} */ } MODULE_THEME_REC; typedef struct { @@ -17,6 +19,10 @@ typedef struct { int default_color; GHashTable *modules; + GSList *replace_keys; + GSList *replace_values; + GHashTable *abstracts; + void *gui_data; } THEME_REC;