gtk: centralize icon lookup (new icon-resolver), move mappings out of gtkutil/pixmaps, add sane fallback chain + meson hookup

This commit is contained in:
2026-02-28 23:17:27 -07:00
parent 64c2fd4e51
commit 395ccb0706
5 changed files with 586 additions and 256 deletions

View File

@@ -42,6 +42,7 @@
#include "../common/zoitechatc.h" #include "../common/zoitechatc.h"
#include "../common/typedef.h" #include "../common/typedef.h"
#include "gtkutil.h" #include "gtkutil.h"
#include "icon-resolver.h"
#include "pixmaps.h" #include "pixmaps.h"
#ifdef WIN32 #ifdef WIN32
@@ -62,124 +63,28 @@ struct file_req
int flags; /* FRF_* flags */ 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 * static GdkPixbuf *
gtkutil_menu_icon_pixbuf_new (const char *icon_name) gtkutil_menu_icon_pixbuf_new (const char *icon_name)
{ {
GdkPixbuf *pixbuf = NULL; GdkPixbuf *pixbuf = NULL;
char *resource_path; 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; return NULL;
resource_path = g_strdup_printf ("/icons/menu/light/%s.png", 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); pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL);
if (!pixbuf) else
{ pixbuf = gdk_pixbuf_new_from_file (resource_path, 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);
}
g_free (resource_path); g_free (resource_path);
return pixbuf; return pixbuf;
@@ -188,136 +93,26 @@ gtkutil_menu_icon_pixbuf_new (const char *icon_name)
const char * const char *
gtkutil_icon_name_from_stock (const char *stock_name) gtkutil_icon_name_from_stock (const char *stock_name)
{ {
static const struct return icon_resolver_icon_name_from_stock (stock_name);
{
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;
} }
static GtkWidget * static GtkWidget *
gtkutil_menu_icon_image_new (const char *icon_name, GtkIconSize size) gtkutil_menu_icon_image_new (const char *icon_name, GtkIconSize size)
{ {
GtkWidget *image = NULL; GtkWidget *image = NULL;
GdkPixbuf *pixbuf = NULL; GdkPixbuf *pixbuf;
char *resource_path;
const char *variant;
if (!icon_name || !g_str_has_prefix (icon_name, "zc-menu-"))
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-"));
}
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 width;
gint height; gint height;
tmp_size = size; pixbuf = gtkutil_menu_icon_pixbuf_new (icon_name);
if (gtk_icon_size_lookup (tmp_size, &width, &height)) if (!pixbuf)
return NULL;
image = gtk_image_new_from_pixbuf (pixbuf);
g_object_unref (pixbuf);
if (gtk_icon_size_lookup (size, &width, &height))
gtk_image_set_pixel_size (GTK_IMAGE (image), MAX (width, height)); gtk_image_set_pixel_size (GTK_IMAGE (image), MAX (width, height));
}
return image; return image;
} }
@@ -333,10 +128,10 @@ gtkutil_image_new_from_stock (const char *stock, GtkIconSize size)
icon_name = stock; icon_name = stock;
if (size == GTK_ICON_SIZE_MENU) 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) 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) if (menu_icon_name)
icon_name = menu_icon_name; icon_name = menu_icon_name;
@@ -346,6 +141,13 @@ gtkutil_image_new_from_stock (const char *stock, GtkIconSize size)
if (image) if (image)
return 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); return gtk_image_new_from_icon_name (icon_name, size);
} }

420
src/fe-gtk/icon-resolver.c Normal file
View File

@@ -0,0 +1,420 @@
#include <string.h>
#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);
}

View File

@@ -0,0 +1,89 @@
#ifndef ZOITECHAT_ICON_RESOLVER_H
#define ZOITECHAT_ICON_RESOLVER_H
#include <gtk/gtk.h>
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

View File

@@ -9,6 +9,7 @@ zoitechat_gtk_sources = [
'fe-gtk.c', 'fe-gtk.c',
'fkeys.c', 'fkeys.c',
'gtkutil.c', 'gtkutil.c',
'icon-resolver.c',
'ignoregui.c', 'ignoregui.c',
'joind.c', 'joind.c',
'menu.c', 'menu.c',

View File

@@ -25,6 +25,7 @@
#include "../common/zoitechat.h" #include "../common/zoitechat.h"
#include "../common/fe.h" #include "../common/fe.h"
#include "resources.h" #include "resources.h"
#include "icon-resolver.h"
#include <gio/gio.h> #include <gio/gio.h>
#include <gdk-pixbuf/gdk-pixbuf.h> #include <gdk-pixbuf/gdk-pixbuf.h>
@@ -151,25 +152,42 @@ pixmap_load_from_file (char *filename)
/* load custom icons from <config>/icons, don't mess in system folders */ /* load custom icons from <config>/icons, don't mess in system folders */
static GdkPixbuf * 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; const char *scale;
int iscale; 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); path = icon_resolver_resolve_path (role, item, GTK_ICON_SIZE_MENU, "pixmap",
pixbuf = gdk_pixbuf_new_from_file (path, 0); ICON_RESOLVER_THEME_SYSTEM, &system_icon_name);
g_free (path); if (path)
if (!pixbuf)
{ {
path = g_strdup_printf ("/icons/%s.png", filename); if (g_str_has_prefix (path, "/icons/"))
pixbuf = gdk_pixbuf_new_from_resource (path, NULL); pixbuf = gdk_pixbuf_new_from_resource (path, NULL);
else
pixbuf = gdk_pixbuf_new_from_file (path, 0);
g_free (path); 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"); scale = g_getenv ("GDK_SCALE");
if (scale) if (scale && pixbuf)
{ {
iscale = atoi (scale); iscale = atoi (scale);
if (iscale > 0) if (iscale > 0)
@@ -195,26 +213,26 @@ pixmaps_init (void)
{ {
zoitechat_register_resource(); zoitechat_register_resource();
pix_ulist_voice = load_pixmap ("ulist_voice"); pix_ulist_voice = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_VOICE, "ulist_voice");
pix_ulist_halfop = load_pixmap ("ulist_halfop"); pix_ulist_halfop = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_HALFOP, "ulist_halfop");
pix_ulist_op = load_pixmap ("ulist_op"); pix_ulist_op = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_OP, "ulist_op");
pix_ulist_owner = load_pixmap ("ulist_owner"); pix_ulist_owner = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_OWNER, "ulist_owner");
pix_ulist_founder = load_pixmap ("ulist_founder"); pix_ulist_founder = load_pixmap (ICON_RESOLVER_ROLE_USERLIST_RANK, ICON_RESOLVER_USERLIST_RANK_FOUNDER, "ulist_founder");
pix_ulist_netop = load_pixmap ("ulist_netop"); 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_normal = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_NORMAL, "tray_normal");
pix_tray_fileoffer = load_pixmap ("tray_fileoffer"); pix_tray_fileoffer = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_FILEOFFER, "tray_fileoffer");
pix_tray_highlight = load_pixmap ("tray_highlight"); pix_tray_highlight = load_pixmap (ICON_RESOLVER_ROLE_TRAY_STATE, ICON_RESOLVER_TRAY_STATE_HIGHLIGHT, "tray_highlight");
pix_tray_message = load_pixmap ("tray_message"); 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_channel = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_CHANNEL, "tree_channel");
pix_tree_dialog = load_pixmap ("tree_dialog"); pix_tree_dialog = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_DIALOG, "tree_dialog");
pix_tree_server = load_pixmap ("tree_server"); pix_tree_server = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_SERVER, "tree_server");
pix_tree_util = load_pixmap ("tree_util"); pix_tree_util = load_pixmap (ICON_RESOLVER_ROLE_TREE_TYPE, ICON_RESOLVER_TREE_TYPE_UTIL, "tree_util");
/* non-replaceable book pixmap */ /* non-replaceable book pixmap */
pix_book = gdk_pixbuf_new_from_resource ("/icons/book.png", NULL); pix_book = gdk_pixbuf_new_from_resource ("/icons/book.png", NULL);
/* used in About window, tray icon and WindowManager icon. */ /* 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);
} }