From 395ccb07064caa8fb40a75ea0922e5b2cba13fc3 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 28 Feb 2026 23:17:27 -0700 Subject: [PATCH] gtk: centralize icon lookup (new icon-resolver), move mappings out of gtkutil/pixmaps, add sane fallback chain + meson hookup --- src/fe-gtk/gtkutil.c | 264 +++-------------------- src/fe-gtk/icon-resolver.c | 420 +++++++++++++++++++++++++++++++++++++ src/fe-gtk/icon-resolver.h | 89 ++++++++ src/fe-gtk/meson.build | 1 + src/fe-gtk/pixmaps.c | 68 +++--- 5 files changed, 586 insertions(+), 256 deletions(-) create mode 100644 src/fe-gtk/icon-resolver.c create mode 100644 src/fe-gtk/icon-resolver.h diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index b00c41b4..1b21c159 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -42,6 +42,7 @@ #include "../common/zoitechatc.h" #include "../common/typedef.h" #include "gtkutil.h" +#include "icon-resolver.h" #include "pixmaps.h" #ifdef WIN32 @@ -62,124 +63,28 @@ struct file_req int flags; /* FRF_* flags */ }; -static const char * -gtkutil_menu_custom_icon_from_stock (const char *stock_name) -{ - static const struct - { - const char *stock; - const char *custom_icon; - } icon_map[] = { - { "gtk-new", "zc-menu-new" }, - { "gtk-index", "zc-menu-network-list" }, - { "gtk-revert-to-saved", "zc-menu-load-plugin" }, - { "gtk-redo", "zc-menu-detach" }, - { "gtk-close", "zc-menu-close" }, - { "gtk-quit", "zc-menu-quit" }, - { "gtk-disconnect", "zc-menu-disconnect" }, - { "gtk-connect", "zc-menu-connect" }, - { "gtk-jump-to", "zc-menu-join" }, - { "gtk-preferences", "zc-menu-preferences" }, - { "gtk-clear", "zc-menu-clear" }, - { "gtk-copy", "zc-menu-copy" }, - { "gtk-delete", "zc-menu-delete" }, - { "gtk-add", "zc-menu-add" }, - { "gtk-remove", "zc-menu-remove" }, - { "gtk-spell-check", "zc-menu-spell-check" }, - { "gtk-save", "zc-menu-save" }, - { "gtk-save-as", "zc-menu-save-as" }, - { "gtk-refresh", "zc-menu-refresh" }, - { "gtk-justify-left", "zc-menu-search" }, - { "gtk-find", "zc-menu-find" }, - { "gtk-go-back", "zc-menu-previous" }, - { "gtk-go-forward", "zc-menu-next" }, - { "gtk-help", "zc-menu-help" }, - { "gtk-about", "zc-menu-about" }, - { "gtk-convert", "zc-menu-emoji" }, - }; - size_t i; - - if (!stock_name) - return NULL; - - for (i = 0; i < G_N_ELEMENTS (icon_map); i++) - { - if (strcmp (stock_name, icon_map[i].stock) == 0) - return icon_map[i].custom_icon; - } - - return NULL; -} - -static const char * -gtkutil_menu_custom_icon_from_icon_name (const char *icon_name) -{ - static const struct - { - const char *icon; - const char *custom_icon; - } icon_map[] = { - { "document-new", "zc-menu-new" }, - { "view-list", "zc-menu-network-list" }, - { "document-open", "zc-menu-load-plugin" }, - { "edit-redo", "zc-menu-detach" }, - { "window-close", "zc-menu-close" }, - { "application-exit", "zc-menu-quit" }, - { "network-disconnect", "zc-menu-disconnect" }, - { "network-connect", "zc-menu-connect" }, - { "go-jump", "zc-menu-join" }, - { "preferences-system", "zc-menu-preferences" }, - { "edit-clear", "zc-menu-clear" }, - { "edit-copy", "zc-menu-copy" }, - { "edit-delete", "zc-menu-delete" }, - { "list-add", "zc-menu-add" }, - { "list-remove", "zc-menu-remove" }, - { "tools-check-spelling", "zc-menu-spell-check" }, - { "document-save", "zc-menu-save" }, - { "document-save-as", "zc-menu-save-as" }, - { "view-refresh", "zc-menu-refresh" }, - { "edit-find", "zc-menu-find" }, - { "go-previous", "zc-menu-previous" }, - { "go-next", "zc-menu-next" }, - { "help-browser", "zc-menu-help" }, - { "help-about", "zc-menu-about" }, - { "face-smile", "zc-menu-emoji" }, - { "insert-emoticon", "zc-menu-emoji" }, - { "software-update-available", "zc-menu-update" }, - { "network-workgroup", "zc-menu-chanlist" }, - }; - size_t i; - - if (!icon_name) - return NULL; - - for (i = 0; i < G_N_ELEMENTS (icon_map); i++) - { - if (strcmp (icon_name, icon_map[i].icon) == 0) - return icon_map[i].custom_icon; - } - - return NULL; -} - - static GdkPixbuf * gtkutil_menu_icon_pixbuf_new (const char *icon_name) { GdkPixbuf *pixbuf = NULL; char *resource_path; + const char *system_icon_name = NULL; + int action; - if (!icon_name || !g_str_has_prefix (icon_name, "zc-menu-")) + if (!icon_name || !icon_resolver_menu_action_from_custom (icon_name, &action)) return NULL; - resource_path = g_strdup_printf ("/icons/menu/light/%s.png", icon_name + strlen ("zc-menu-")); - pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL); - if (!pixbuf) - { - g_free (resource_path); - resource_path = g_strdup_printf ("/icons/menu/light/%s.svg", icon_name + strlen ("zc-menu-")); + resource_path = icon_resolver_resolve_path (ICON_RESOLVER_ROLE_MENU_ACTION, action, + GTK_ICON_SIZE_MENU, "menu", + ICON_RESOLVER_THEME_SYSTEM, + &system_icon_name); + if (!resource_path) + return NULL; + + if (g_str_has_prefix (resource_path, "/icons/")) pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL); - } + else + pixbuf = gdk_pixbuf_new_from_file (resource_path, NULL); g_free (resource_path); return pixbuf; @@ -188,136 +93,26 @@ gtkutil_menu_icon_pixbuf_new (const char *icon_name) const char * gtkutil_icon_name_from_stock (const char *stock_name) { - static const struct - { - const char *stock; - const char *icon; - } icon_map[] = { - { "gtk-new", "document-new" }, - { "gtk-open", "document-open" }, - { "gtk-revert-to-saved", "document-open" }, - { "gtk-save", "document-save" }, - { "gtk-save-as", "document-save-as" }, - { "gtk-add", "list-add" }, - { "gtk-cancel", "dialog-cancel" }, - { "gtk-ok", "dialog-ok" }, - { "gtk-no", "dialog-cancel" }, - { "gtk-yes", "dialog-ok" }, - { "gtk-apply", "dialog-apply" }, - { "gtk-dialog-error", "dialog-error" }, - { "gtk-copy", "edit-copy" }, - { "gtk-delete", "edit-delete" }, - { "gtk-remove", "list-remove" }, - { "gtk-clear", "edit-clear" }, - { "gtk-redo", "edit-redo" }, - { "gtk-find", "edit-find" }, - { "gtk-justify-left", "edit-find" }, - { "gtk-refresh", "view-refresh" }, - { "gtk-go-back", "go-previous" }, - { "gtk-go-forward", "go-next" }, - { "gtk-index", "view-list" }, - { "gtk-jump-to", "go-jump" }, - { "gtk-media-play", "media-playback-start" }, - { "gtk-preferences", "preferences-system" }, - { "gtk-help", "help-browser" }, - { "gtk-about", "help-about" }, - { "gtk-close", "window-close" }, - { "gtk-quit", "application-exit" }, - { "gtk-connect", "network-connect" }, - { "gtk-disconnect", "network-disconnect" }, - { "gtk-network", "network-workgroup" }, - { "gtk-spell-check", "tools-check-spelling" }, - }; - size_t i; - - if (!stock_name) - return NULL; - - for (i = 0; i < G_N_ELEMENTS (icon_map); i++) - { - if (strcmp (stock_name, icon_map[i].stock) == 0) - return icon_map[i].icon; - } - - return stock_name; -} - -static const char * -gtkutil_menu_icon_theme_variant (void) -{ - GtkSettings *settings; - gboolean prefer_dark = FALSE; - char *theme_name = NULL; - char *theme_name_lower = NULL; - const char *theme_variant = "light"; - - settings = gtk_settings_get_default (); - if (settings) - { - g_object_get (G_OBJECT (settings), "gtk-application-prefer-dark-theme", &prefer_dark, NULL); - g_object_get (G_OBJECT (settings), "gtk-theme-name", &theme_name, NULL); - } - - if (theme_name) - theme_name_lower = g_ascii_strdown (theme_name, -1); - if (prefer_dark || (theme_name_lower && g_strrstr (theme_name_lower, "dark"))) - theme_variant = "dark"; - - g_free (theme_name_lower); - g_free (theme_name); - - return theme_variant; + return icon_resolver_icon_name_from_stock (stock_name); } static GtkWidget * gtkutil_menu_icon_image_new (const char *icon_name, GtkIconSize size) { GtkWidget *image = NULL; - GdkPixbuf *pixbuf = NULL; - char *resource_path; - const char *variant; + GdkPixbuf *pixbuf; + gint width; + gint height; - if (!icon_name || !g_str_has_prefix (icon_name, "zc-menu-")) + pixbuf = gtkutil_menu_icon_pixbuf_new (icon_name); + if (!pixbuf) return NULL; - variant = gtkutil_menu_icon_theme_variant (); - resource_path = g_strdup_printf ("/icons/menu/%s/%s.png", variant, icon_name + strlen ("zc-menu-")); - if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL)) - { - g_free (resource_path); - resource_path = g_strdup_printf ("/icons/menu/light/%s.png", icon_name + strlen ("zc-menu-")); - } + image = gtk_image_new_from_pixbuf (pixbuf); + g_object_unref (pixbuf); - pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL); - if (!pixbuf) - { - g_free (resource_path); - resource_path = g_strdup_printf ("/icons/menu/%s/%s.svg", variant, icon_name + strlen ("zc-menu-")); - if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL)) - { - g_free (resource_path); - resource_path = g_strdup_printf ("/icons/menu/light/%s.svg", icon_name + strlen ("zc-menu-")); - } - pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL); - } - if (pixbuf) - { - image = gtk_image_new_from_pixbuf (pixbuf); - g_object_unref (pixbuf); - } - - g_free (resource_path); - - if (image) - { - GtkIconSize tmp_size; - gint width; - gint height; - - tmp_size = size; - if (gtk_icon_size_lookup (tmp_size, &width, &height)) - gtk_image_set_pixel_size (GTK_IMAGE (image), MAX (width, height)); - } + if (gtk_icon_size_lookup (size, &width, &height)) + gtk_image_set_pixel_size (GTK_IMAGE (image), MAX (width, height)); return image; } @@ -333,10 +128,10 @@ gtkutil_image_new_from_stock (const char *stock, GtkIconSize size) icon_name = stock; if (size == GTK_ICON_SIZE_MENU) { - const char *menu_icon_name = gtkutil_menu_custom_icon_from_stock (stock); + const char *menu_icon_name = icon_resolver_menu_custom_icon_from_stock (stock); if (!menu_icon_name) - menu_icon_name = gtkutil_menu_custom_icon_from_icon_name (icon_name); + menu_icon_name = icon_resolver_menu_custom_icon_from_icon_name (icon_name); if (menu_icon_name) icon_name = menu_icon_name; @@ -346,6 +141,13 @@ gtkutil_image_new_from_stock (const char *stock, GtkIconSize size) if (image) return image; + if (icon_name && g_str_has_prefix (icon_name, "zc-menu-")) + { + const char *fallback_icon = icon_resolver_icon_name_for_menu_custom (icon_name); + if (fallback_icon) + icon_name = fallback_icon; + } + return gtk_image_new_from_icon_name (icon_name, size); } diff --git a/src/fe-gtk/icon-resolver.c b/src/fe-gtk/icon-resolver.c new file mode 100644 index 00000000..2633bea4 --- /dev/null +++ b/src/fe-gtk/icon-resolver.c @@ -0,0 +1,420 @@ +#include + +#include "fe-gtk.h" +#include "icon-resolver.h" +#include "../common/cfgfiles.h" + +typedef struct +{ + IconResolverRole role; + int item; + const char *custom_icon_name; + const char *system_icon_name; + const char *resource_name; +} IconRegistryEntry; + +typedef struct +{ + const char *stock_name; + const char *system_icon_name; +} StockIconMap; + +typedef struct +{ + const char *key; + const char *custom_icon_name; +} MenuMap; + +static const IconRegistryEntry icon_registry[] = { + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_NEW, "zc-menu-new", "document-new", "new" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_NETWORK_LIST, "zc-menu-network-list", "view-list", "network-list" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_LOAD_PLUGIN, "zc-menu-load-plugin", "document-open", "load-plugin" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_DETACH, "zc-menu-detach", "edit-redo", "detach" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_CLOSE, "zc-menu-close", "window-close", "close" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_QUIT, "zc-menu-quit", "application-exit", "quit" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_DISCONNECT, "zc-menu-disconnect", "network-disconnect", "disconnect" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_CONNECT, "zc-menu-connect", "network-connect", "connect" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_JOIN, "zc-menu-join", "go-jump", "join" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_PREFERENCES, "zc-menu-preferences", "preferences-system", "preferences" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_CLEAR, "zc-menu-clear", "edit-clear", "clear" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_COPY, "zc-menu-copy", "edit-copy", "copy" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_DELETE, "zc-menu-delete", "edit-delete", "delete" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_ADD, "zc-menu-add", "list-add", "add" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_REMOVE, "zc-menu-remove", "list-remove", "remove" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_SPELL_CHECK, "zc-menu-spell-check", "tools-check-spelling", "spell-check" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_SAVE, "zc-menu-save", "document-save", "save" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_SAVE_AS, "zc-menu-save-as", "document-save-as", "save-as" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_REFRESH, "zc-menu-refresh", "view-refresh", "refresh" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_SEARCH, "zc-menu-search", "edit-find", "search" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_FIND, "zc-menu-find", "edit-find", "find" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_PREVIOUS, "zc-menu-previous", "go-previous", "previous" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_NEXT, "zc-menu-next", "go-next", "next" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_HELP, "zc-menu-help", "help-browser", "help" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_ABOUT, "zc-menu-about", "help-about", "about" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_EMOJI, "zc-menu-emoji", "face-smile", "emoji" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_UPDATE, "zc-menu-update", "software-update-available", "update" }, + { ICON_RESOLVER_ROLE_MENU_ACTION, ICON_RESOLVER_MENU_ACTION_CHANLIST, "zc-menu-chanlist", "network-workgroup", "chanlist" }, + { ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_NORMAL, NULL, "zoitechat", "tray_normal" }, + { ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_FILEOFFER, NULL, "mail-attachment", "tray_fileoffer" }, + { ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_HIGHLIGHT, NULL, "dialog-warning", "tray_highlight" }, + { ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_MESSAGE, NULL, "mail-unread", "tray_message" }, + { ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_CHANNEL, NULL, "folder", "tree_channel" }, + { ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_DIALOG, NULL, "mail-message-new", "tree_dialog" }, + { ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_SERVER, NULL, "network-server", "tree_server" }, + { ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_UTIL, NULL, "applications-utilities", "tree_util" }, + { ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_VOICE, NULL, "emblem-ok", "ulist_voice" }, + { ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_HALFOP, NULL, "emblem-shared", "ulist_halfop" }, + { ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_OP, NULL, "emblem-default", "ulist_op" }, + { ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_OWNER, NULL, "emblem-favorite", "ulist_owner" }, + { ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_FOUNDER, NULL, "emblem-favorite", "ulist_founder" }, + { ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_NETOP, NULL, "emblem-system", "ulist_netop" } +}; + +static const StockIconMap stock_icon_map[] = { + { "gtk-new", "document-new" }, + { "gtk-open", "document-open" }, + { "gtk-revert-to-saved", "document-open" }, + { "gtk-save", "document-save" }, + { "gtk-save-as", "document-save-as" }, + { "gtk-add", "list-add" }, + { "gtk-cancel", "dialog-cancel" }, + { "gtk-ok", "dialog-ok" }, + { "gtk-no", "dialog-cancel" }, + { "gtk-yes", "dialog-ok" }, + { "gtk-apply", "dialog-apply" }, + { "gtk-dialog-error", "dialog-error" }, + { "gtk-copy", "edit-copy" }, + { "gtk-delete", "edit-delete" }, + { "gtk-remove", "list-remove" }, + { "gtk-clear", "edit-clear" }, + { "gtk-redo", "edit-redo" }, + { "gtk-find", "edit-find" }, + { "gtk-justify-left", "edit-find" }, + { "gtk-refresh", "view-refresh" }, + { "gtk-go-back", "go-previous" }, + { "gtk-go-forward", "go-next" }, + { "gtk-index", "view-list" }, + { "gtk-jump-to", "go-jump" }, + { "gtk-media-play", "media-playback-start" }, + { "gtk-preferences", "preferences-system" }, + { "gtk-help", "help-browser" }, + { "gtk-about", "help-about" }, + { "gtk-close", "window-close" }, + { "gtk-quit", "application-exit" }, + { "gtk-connect", "network-connect" }, + { "gtk-disconnect", "network-disconnect" }, + { "gtk-network", "network-workgroup" }, + { "gtk-spell-check", "tools-check-spelling" } +}; + +static const MenuMap stock_menu_map[] = { + { "gtk-new", "zc-menu-new" }, + { "gtk-index", "zc-menu-network-list" }, + { "gtk-revert-to-saved", "zc-menu-load-plugin" }, + { "gtk-redo", "zc-menu-detach" }, + { "gtk-close", "zc-menu-close" }, + { "gtk-quit", "zc-menu-quit" }, + { "gtk-disconnect", "zc-menu-disconnect" }, + { "gtk-connect", "zc-menu-connect" }, + { "gtk-jump-to", "zc-menu-join" }, + { "gtk-preferences", "zc-menu-preferences" }, + { "gtk-clear", "zc-menu-clear" }, + { "gtk-copy", "zc-menu-copy" }, + { "gtk-delete", "zc-menu-delete" }, + { "gtk-add", "zc-menu-add" }, + { "gtk-remove", "zc-menu-remove" }, + { "gtk-spell-check", "zc-menu-spell-check" }, + { "gtk-save", "zc-menu-save" }, + { "gtk-save-as", "zc-menu-save-as" }, + { "gtk-refresh", "zc-menu-refresh" }, + { "gtk-justify-left", "zc-menu-search" }, + { "gtk-find", "zc-menu-find" }, + { "gtk-go-back", "zc-menu-previous" }, + { "gtk-go-forward", "zc-menu-next" }, + { "gtk-help", "zc-menu-help" }, + { "gtk-about", "zc-menu-about" }, + { "gtk-convert", "zc-menu-emoji" } +}; + +static const MenuMap icon_menu_map[] = { + { "document-new", "zc-menu-new" }, + { "view-list", "zc-menu-network-list" }, + { "document-open", "zc-menu-load-plugin" }, + { "edit-redo", "zc-menu-detach" }, + { "window-close", "zc-menu-close" }, + { "application-exit", "zc-menu-quit" }, + { "network-disconnect", "zc-menu-disconnect" }, + { "network-connect", "zc-menu-connect" }, + { "go-jump", "zc-menu-join" }, + { "preferences-system", "zc-menu-preferences" }, + { "edit-clear", "zc-menu-clear" }, + { "edit-copy", "zc-menu-copy" }, + { "edit-delete", "zc-menu-delete" }, + { "list-add", "zc-menu-add" }, + { "list-remove", "zc-menu-remove" }, + { "tools-check-spelling", "zc-menu-spell-check" }, + { "document-save", "zc-menu-save" }, + { "document-save-as", "zc-menu-save-as" }, + { "view-refresh", "zc-menu-refresh" }, + { "edit-find", "zc-menu-find" }, + { "go-previous", "zc-menu-previous" }, + { "go-next", "zc-menu-next" }, + { "help-browser", "zc-menu-help" }, + { "help-about", "zc-menu-about" }, + { "face-smile", "zc-menu-emoji" }, + { "insert-emoticon", "zc-menu-emoji" }, + { "software-update-available", "zc-menu-update" }, + { "network-workgroup", "zc-menu-chanlist" } +}; + +static const IconRegistryEntry * +icon_registry_find (IconResolverRole role, int item) +{ + size_t i; + + for (i = 0; i < G_N_ELEMENTS (icon_registry); i++) + { + if (icon_registry[i].role == role && icon_registry[i].item == item) + return &icon_registry[i]; + } + + return NULL; +} + +static const IconRegistryEntry * +icon_registry_find_custom (const char *custom_icon_name) +{ + size_t i; + + if (!custom_icon_name) + return NULL; + + for (i = 0; i < G_N_ELEMENTS (icon_registry); i++) + { + if (icon_registry[i].custom_icon_name && strcmp (icon_registry[i].custom_icon_name, custom_icon_name) == 0) + return &icon_registry[i]; + } + + return NULL; +} + +static const char * +menu_map_lookup (const MenuMap *map, size_t map_len, const char *key) +{ + size_t i; + + if (!key) + return NULL; + + for (i = 0; i < map_len; i++) + { + if (strcmp (key, map[i].key) == 0) + return map[i].custom_icon_name; + } + + return NULL; +} + +const char * +icon_resolver_icon_name_from_stock (const char *stock_name) +{ + size_t i; + + if (!stock_name) + return NULL; + + for (i = 0; i < G_N_ELEMENTS (stock_icon_map); i++) + { + if (strcmp (stock_name, stock_icon_map[i].stock_name) == 0) + return stock_icon_map[i].system_icon_name; + } + + return stock_name; +} + +const char * +icon_resolver_menu_custom_icon_from_stock (const char *stock_name) +{ + return menu_map_lookup (stock_menu_map, G_N_ELEMENTS (stock_menu_map), stock_name); +} + +const char * +icon_resolver_menu_custom_icon_from_icon_name (const char *icon_name) +{ + return menu_map_lookup (icon_menu_map, G_N_ELEMENTS (icon_menu_map), icon_name); +} + +const char * +icon_resolver_icon_name_for_menu_custom (const char *custom_icon_name) +{ + const IconRegistryEntry *entry = icon_registry_find_custom (custom_icon_name); + + if (!entry) + return NULL; + + return entry->system_icon_name; +} + +gboolean +icon_resolver_menu_action_from_custom (const char *custom_icon_name, int *action_out) +{ + const IconRegistryEntry *entry = icon_registry_find_custom (custom_icon_name); + + if (!entry || entry->role != ICON_RESOLVER_ROLE_MENU_ACTION) + return FALSE; + + if (action_out) + *action_out = entry->item; + + return TRUE; +} + +IconResolverThemeVariant +icon_resolver_detect_theme_variant (void) +{ + GtkSettings *settings; + gboolean prefer_dark = FALSE; + char *theme_name = NULL; + char *theme_name_lower = NULL; + IconResolverThemeVariant theme_variant = ICON_RESOLVER_THEME_LIGHT; + + settings = gtk_settings_get_default (); + if (settings) + { + g_object_get (G_OBJECT (settings), "gtk-application-prefer-dark-theme", &prefer_dark, NULL); + g_object_get (G_OBJECT (settings), "gtk-theme-name", &theme_name, NULL); + } + + if (theme_name) + theme_name_lower = g_ascii_strdown (theme_name, -1); + if (prefer_dark || (theme_name_lower && g_strrstr (theme_name_lower, "dark"))) + theme_variant = ICON_RESOLVER_THEME_DARK; + + g_free (theme_name_lower); + g_free (theme_name); + + return theme_variant; +} + +static gboolean +resource_exists (const char *resource_path) +{ + return g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL); +} + +static char * +resolve_user_override (const IconRegistryEntry *entry, IconResolverThemeVariant variant) +{ + const char *variant_name = variant == ICON_RESOLVER_THEME_DARK ? "dark" : "light"; + char *path; + + path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "icons" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s.png", + get_xdir (), variant_name, entry->resource_name); + if (g_file_test (path, G_FILE_TEST_EXISTS)) + return path; + g_free (path); + + path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "icons" G_DIR_SEPARATOR_S "%s-%s.png", + get_xdir (), entry->resource_name, variant_name); + if (g_file_test (path, G_FILE_TEST_EXISTS)) + return path; + g_free (path); + + path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "icons" G_DIR_SEPARATOR_S "%s.png", + get_xdir (), entry->resource_name); + if (g_file_test (path, G_FILE_TEST_EXISTS)) + return path; + g_free (path); + + return NULL; +} + +static char * +resolve_bundled_variant (const IconRegistryEntry *entry, IconResolverThemeVariant variant) +{ + const char *variant_name = variant == ICON_RESOLVER_THEME_DARK ? "dark" : "light"; + char *path; + + if (entry->role == ICON_RESOLVER_ROLE_MENU_ACTION) + { + path = g_strdup_printf ("/icons/menu/%s/%s.png", variant_name, entry->resource_name); + if (resource_exists (path)) + return path; + g_free (path); + + path = g_strdup_printf ("/icons/menu/%s/%s.svg", variant_name, entry->resource_name); + if (resource_exists (path)) + return path; + g_free (path); + } + else + { + path = g_strdup_printf ("/icons/%s-%s.png", entry->resource_name, variant_name); + if (resource_exists (path)) + return path; + g_free (path); + } + + return NULL; +} + +static char * +resolve_bundled_neutral (const IconRegistryEntry *entry) +{ + char *path; + + if (entry->role == ICON_RESOLVER_ROLE_MENU_ACTION) + { + path = g_strdup_printf ("/icons/menu/light/%s.png", entry->resource_name); + if (resource_exists (path)) + return path; + g_free (path); + + path = g_strdup_printf ("/icons/menu/light/%s.svg", entry->resource_name); + if (resource_exists (path)) + return path; + g_free (path); + } + else + { + path = g_strdup_printf ("/icons/%s.png", entry->resource_name); + if (resource_exists (path)) + return path; + g_free (path); + } + + return NULL; +} + +char * +icon_resolver_resolve_path (IconResolverRole role, int item, GtkIconSize size, + const char *context, IconResolverThemeVariant variant, + const char **system_icon_name) +{ + const IconRegistryEntry *entry; + IconResolverThemeVariant effective_variant = variant; + char *path; + + (void)size; + (void)context; + + entry = icon_registry_find (role, item); + if (!entry) + return NULL; + + if (system_icon_name) + *system_icon_name = entry->system_icon_name; + + if (effective_variant == ICON_RESOLVER_THEME_SYSTEM) + effective_variant = icon_resolver_detect_theme_variant (); + + path = resolve_user_override (entry, effective_variant); + if (path) + return path; + + path = resolve_bundled_variant (entry, effective_variant); + if (path) + return path; + + return resolve_bundled_neutral (entry); +} diff --git a/src/fe-gtk/icon-resolver.h b/src/fe-gtk/icon-resolver.h new file mode 100644 index 00000000..067288d6 --- /dev/null +++ b/src/fe-gtk/icon-resolver.h @@ -0,0 +1,89 @@ +#ifndef ZOITECHAT_ICON_RESOLVER_H +#define ZOITECHAT_ICON_RESOLVER_H + +#include + +typedef enum +{ + ICON_RESOLVER_ROLE_MENU_ACTION, + ICON_RESOLVER_ROLE_TRAY_STATE, + ICON_RESOLVER_ROLE_TREE_TYPE, + ICON_RESOLVER_ROLE_USERLIST_RANK +} IconResolverRole; + +typedef enum +{ + ICON_RESOLVER_THEME_SYSTEM, + ICON_RESOLVER_THEME_LIGHT, + ICON_RESOLVER_THEME_DARK +} IconResolverThemeVariant; + +typedef enum +{ + ICON_RESOLVER_MENU_ACTION_NEW, + ICON_RESOLVER_MENU_ACTION_NETWORK_LIST, + ICON_RESOLVER_MENU_ACTION_LOAD_PLUGIN, + ICON_RESOLVER_MENU_ACTION_DETACH, + ICON_RESOLVER_MENU_ACTION_CLOSE, + ICON_RESOLVER_MENU_ACTION_QUIT, + ICON_RESOLVER_MENU_ACTION_DISCONNECT, + ICON_RESOLVER_MENU_ACTION_CONNECT, + ICON_RESOLVER_MENU_ACTION_JOIN, + ICON_RESOLVER_MENU_ACTION_PREFERENCES, + ICON_RESOLVER_MENU_ACTION_CLEAR, + ICON_RESOLVER_MENU_ACTION_COPY, + ICON_RESOLVER_MENU_ACTION_DELETE, + ICON_RESOLVER_MENU_ACTION_ADD, + ICON_RESOLVER_MENU_ACTION_REMOVE, + ICON_RESOLVER_MENU_ACTION_SPELL_CHECK, + ICON_RESOLVER_MENU_ACTION_SAVE, + ICON_RESOLVER_MENU_ACTION_SAVE_AS, + ICON_RESOLVER_MENU_ACTION_REFRESH, + ICON_RESOLVER_MENU_ACTION_SEARCH, + ICON_RESOLVER_MENU_ACTION_FIND, + ICON_RESOLVER_MENU_ACTION_PREVIOUS, + ICON_RESOLVER_MENU_ACTION_NEXT, + ICON_RESOLVER_MENU_ACTION_HELP, + ICON_RESOLVER_MENU_ACTION_ABOUT, + ICON_RESOLVER_MENU_ACTION_EMOJI, + ICON_RESOLVER_MENU_ACTION_UPDATE, + ICON_RESOLVER_MENU_ACTION_CHANLIST +} IconResolverMenuAction; + +typedef enum +{ + ICON_RESOLVER_TRAY_STATE_NORMAL, + ICON_RESOLVER_TRAY_STATE_FILEOFFER, + ICON_RESOLVER_TRAY_STATE_HIGHLIGHT, + ICON_RESOLVER_TRAY_STATE_MESSAGE +} IconResolverTrayState; + +typedef enum +{ + ICON_RESOLVER_TREE_TYPE_CHANNEL, + ICON_RESOLVER_TREE_TYPE_DIALOG, + ICON_RESOLVER_TREE_TYPE_SERVER, + ICON_RESOLVER_TREE_TYPE_UTIL +} IconResolverTreeType; + +typedef enum +{ + ICON_RESOLVER_USERLIST_RANK_VOICE, + ICON_RESOLVER_USERLIST_RANK_HALFOP, + ICON_RESOLVER_USERLIST_RANK_OP, + ICON_RESOLVER_USERLIST_RANK_OWNER, + ICON_RESOLVER_USERLIST_RANK_FOUNDER, + ICON_RESOLVER_USERLIST_RANK_NETOP +} IconResolverUserlistRank; + +const char *icon_resolver_icon_name_from_stock (const char *stock_name); +const char *icon_resolver_menu_custom_icon_from_stock (const char *stock_name); +const char *icon_resolver_menu_custom_icon_from_icon_name (const char *icon_name); +const char *icon_resolver_icon_name_for_menu_custom (const char *custom_icon_name); +gboolean icon_resolver_menu_action_from_custom (const char *custom_icon_name, int *action_out); +IconResolverThemeVariant icon_resolver_detect_theme_variant (void); +char *icon_resolver_resolve_path (IconResolverRole role, int item, GtkIconSize size, + const char *context, IconResolverThemeVariant variant, + const char **system_icon_name); + +#endif diff --git a/src/fe-gtk/meson.build b/src/fe-gtk/meson.build index dc4a6b5b..7019cce0 100644 --- a/src/fe-gtk/meson.build +++ b/src/fe-gtk/meson.build @@ -9,6 +9,7 @@ zoitechat_gtk_sources = [ 'fe-gtk.c', 'fkeys.c', 'gtkutil.c', + 'icon-resolver.c', 'ignoregui.c', 'joind.c', 'menu.c', diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c index 7151e65f..845a21fb 100644 --- a/src/fe-gtk/pixmaps.c +++ b/src/fe-gtk/pixmaps.c @@ -25,6 +25,7 @@ #include "../common/zoitechat.h" #include "../common/fe.h" #include "resources.h" +#include "icon-resolver.h" #include #include @@ -151,25 +152,42 @@ pixmap_load_from_file (char *filename) /* load custom icons from /icons, don't mess in system folders */ static GdkPixbuf * -load_pixmap (const char *filename) +load_pixmap (IconResolverRole role, int item, const char *fallback_name) { - GdkPixbuf *pixbuf, *scaledpixbuf; + GdkPixbuf *pixbuf = NULL; + GdkPixbuf *scaledpixbuf; const char *scale; int iscale; + char *path; + const char *system_icon_name = NULL; - gchar *path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "icons" G_DIR_SEPARATOR_S "%s.png", get_xdir (), filename); - pixbuf = gdk_pixbuf_new_from_file (path, 0); - g_free (path); - - if (!pixbuf) + path = icon_resolver_resolve_path (role, item, GTK_ICON_SIZE_MENU, "pixmap", + ICON_RESOLVER_THEME_SYSTEM, &system_icon_name); + if (path) { - path = g_strdup_printf ("/icons/%s.png", filename); - pixbuf = gdk_pixbuf_new_from_resource (path, NULL); + if (g_str_has_prefix (path, "/icons/")) + pixbuf = gdk_pixbuf_new_from_resource (path, NULL); + else + pixbuf = gdk_pixbuf_new_from_file (path, 0); g_free (path); } + if (!pixbuf && system_icon_name) + { + GtkIconTheme *theme = gtk_icon_theme_get_default (); + if (theme) + pixbuf = gtk_icon_theme_load_icon (theme, system_icon_name, 16, GTK_ICON_LOOKUP_FORCE_SIZE, NULL); + } + + if (!pixbuf && fallback_name) + { + char *resource_path = g_strdup_printf ("/icons/%s.png", fallback_name); + pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL); + g_free (resource_path); + } + scale = g_getenv ("GDK_SCALE"); - if (scale) + if (scale && pixbuf) { iscale = atoi (scale); if (iscale > 0) @@ -195,26 +213,26 @@ pixmaps_init (void) { zoitechat_register_resource(); - pix_ulist_voice = load_pixmap ("ulist_voice"); - pix_ulist_halfop = load_pixmap ("ulist_halfop"); - pix_ulist_op = load_pixmap ("ulist_op"); - pix_ulist_owner = load_pixmap ("ulist_owner"); - pix_ulist_founder = load_pixmap ("ulist_founder"); - pix_ulist_netop = load_pixmap ("ulist_netop"); + pix_ulist_voice = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_VOICE, "ulist_voice"); + pix_ulist_halfop = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_HALFOP, "ulist_halfop"); + pix_ulist_op = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_OP, "ulist_op"); + pix_ulist_owner = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_OWNER, "ulist_owner"); + pix_ulist_founder = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_FOUNDER, "ulist_founder"); + pix_ulist_netop = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_NETOP, "ulist_netop"); - pix_tray_normal = load_pixmap ("tray_normal"); - pix_tray_fileoffer = load_pixmap ("tray_fileoffer"); - pix_tray_highlight = load_pixmap ("tray_highlight"); - pix_tray_message = load_pixmap ("tray_message"); + pix_tray_normal = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_NORMAL, "tray_normal"); + pix_tray_fileoffer = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_FILEOFFER, "tray_fileoffer"); + pix_tray_highlight = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_HIGHLIGHT, "tray_highlight"); + pix_tray_message = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_MESSAGE, "tray_message"); - pix_tree_channel = load_pixmap ("tree_channel"); - pix_tree_dialog = load_pixmap ("tree_dialog"); - pix_tree_server = load_pixmap ("tree_server"); - pix_tree_util = load_pixmap ("tree_util"); + pix_tree_channel = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_CHANNEL, "tree_channel"); + pix_tree_dialog = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_DIALOG, "tree_dialog"); + pix_tree_server = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_SERVER, "tree_server"); + pix_tree_util = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_UTIL, "tree_util"); /* non-replaceable book pixmap */ pix_book = gdk_pixbuf_new_from_resource ("/icons/book.png", NULL); /* used in About window, tray icon and WindowManager icon. */ - pix_zoitechat = load_pixmap ("zoitechat"); + pix_zoitechat = gdk_pixbuf_new_from_resource ("/icons/zoitechat.png", NULL); }