mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-21 10:29:24 +00:00
Compare commits
7 Commits
4944517a0e
...
inline-ima
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c1b920b27 | |||
|
|
8150d6c48c | ||
| 05ee0dbdb5 | |||
|
|
2db3076c92 | ||
| 90daed18df | |||
|
|
65dd2c645a | ||
| 89d70a940c |
@@ -28,6 +28,7 @@ src/fe-gtk/fe-gtk.c
|
|||||||
src/fe-gtk/fkeys.c
|
src/fe-gtk/fkeys.c
|
||||||
src/fe-gtk/gtkutil.c
|
src/fe-gtk/gtkutil.c
|
||||||
src/fe-gtk/ignoregui.c
|
src/fe-gtk/ignoregui.c
|
||||||
|
src/fe-gtk/inline-image.c
|
||||||
src/fe-gtk/joind.c
|
src/fe-gtk/joind.c
|
||||||
src/fe-gtk/maingui.c
|
src/fe-gtk/maingui.c
|
||||||
src/fe-gtk/menu.c
|
src/fe-gtk/menu.c
|
||||||
|
|||||||
@@ -564,6 +564,7 @@ const struct prefs vars[] =
|
|||||||
{"net_proxy_use", P_OFFINT (hex_net_proxy_use), TYPE_INT},
|
{"net_proxy_use", P_OFFINT (hex_net_proxy_use), TYPE_INT},
|
||||||
{"net_proxy_user", P_OFFSET (hex_net_proxy_user), TYPE_STR},
|
{"net_proxy_user", P_OFFSET (hex_net_proxy_user), TYPE_STR},
|
||||||
{"net_reconnect_delay", P_OFFINT (hex_net_reconnect_delay), TYPE_INT},
|
{"net_reconnect_delay", P_OFFINT (hex_net_reconnect_delay), TYPE_INT},
|
||||||
|
{"net_remote_media", P_OFFINT (hex_net_remote_media), TYPE_BOOL},
|
||||||
{"net_throttle", P_OFFINT (hex_net_throttle), TYPE_BOOL},
|
{"net_throttle", P_OFFINT (hex_net_throttle), TYPE_BOOL},
|
||||||
|
|
||||||
{"notify_timeout", P_OFFINT (hex_notify_timeout), TYPE_INT},
|
{"notify_timeout", P_OFFINT (hex_notify_timeout), TYPE_INT},
|
||||||
@@ -776,6 +777,8 @@ load_default_config(void)
|
|||||||
prefs.hex_gui_hide_menu = 1;
|
prefs.hex_gui_hide_menu = 1;
|
||||||
#endif
|
#endif
|
||||||
prefs.hex_gui_ctrlq_quit = 1;
|
prefs.hex_gui_ctrlq_quit = 1;
|
||||||
|
/* hex_net_remote_media stays 0: loading remote media can reveal the
|
||||||
|
user's IP address, so it is strictly opt-in */
|
||||||
prefs.hex_gui_input_attr = 1;
|
prefs.hex_gui_input_attr = 1;
|
||||||
prefs.hex_gui_input_icon = 1;
|
prefs.hex_gui_input_icon = 1;
|
||||||
prefs.hex_gui_input_nick = 1;
|
prefs.hex_gui_input_nick = 1;
|
||||||
|
|||||||
@@ -315,6 +315,59 @@ resolve_parent_theme_root (const char *child_theme_root, const char *parent_name
|
|||||||
return home_local;
|
return home_local;
|
||||||
g_free (home_local);
|
g_free (home_local);
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *xdg_data_home = g_getenv ("XDG_DATA_HOME");
|
||||||
|
|
||||||
|
if (xdg_data_home && xdg_data_home[0])
|
||||||
|
{
|
||||||
|
candidate = g_build_filename (xdg_data_home, "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
return candidate;
|
||||||
|
g_free (candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate = g_build_filename (g_get_user_data_dir (), "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
return candidate;
|
||||||
|
g_free (candidate);
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
if (xdg_data_dirs && xdg_data_dirs[0])
|
||||||
|
{
|
||||||
|
char **data_dirs = g_strsplit (xdg_data_dirs, G_SEARCHPATH_SEPARATOR_S, -1);
|
||||||
|
|
||||||
|
for (i = 0; data_dirs && data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
if (!data_dirs[i][0])
|
||||||
|
continue;
|
||||||
|
candidate = g_build_filename (data_dirs[i], "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
{
|
||||||
|
g_strfreev (data_dirs);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
g_free (candidate);
|
||||||
|
}
|
||||||
|
g_strfreev (data_dirs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *const *system_data_dirs = g_get_system_data_dirs ();
|
||||||
|
|
||||||
|
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
candidate = g_build_filename (system_data_dirs[i], "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
return candidate;
|
||||||
|
g_free (candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
||||||
candidate = g_build_filename (user_dir, parent_name, NULL);
|
candidate = g_build_filename (user_dir, parent_name, NULL);
|
||||||
g_free (user_dir);
|
g_free (user_dir);
|
||||||
@@ -632,18 +685,55 @@ zoitechat_gtk3_theme_service_discover (void)
|
|||||||
|
|
||||||
g_mkdir_with_parents (user_dir, 0700);
|
g_mkdir_with_parents (user_dir, 0700);
|
||||||
|
|
||||||
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL);
|
/* Prefer the live XDG environment over GLib's process-cached copies so
|
||||||
|
* theme lookup matches what GTK resolves for the running session. */
|
||||||
|
{
|
||||||
|
const char *xdg_data_home = g_getenv ("XDG_DATA_HOME");
|
||||||
|
|
||||||
|
if (xdg_data_home && xdg_data_home[0])
|
||||||
|
user_data_themes = g_build_filename (xdg_data_home, "themes", NULL);
|
||||||
|
else
|
||||||
|
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL);
|
||||||
|
}
|
||||||
|
/* Scan the app's own themes dir first: imported themes get a symlink
|
||||||
|
* under the XDG data themes dir so GTK can resolve gtk-theme-name, and
|
||||||
|
* scanning that link first would make the canonical-root dedup keep
|
||||||
|
* the symlink path - changing the theme's path-derived id and breaking
|
||||||
|
* the saved gui_gtk3_theme reference on the next start. */
|
||||||
|
add_theme_root (user_roots, seen_user_roots, user_dir);
|
||||||
add_theme_root (user_roots, seen_user_roots, user_data_themes);
|
add_theme_root (user_roots, seen_user_roots, user_data_themes);
|
||||||
g_free (user_data_themes);
|
g_free (user_data_themes);
|
||||||
add_theme_root (user_roots, seen_user_roots, home_themes);
|
add_theme_root (user_roots, seen_user_roots, home_themes);
|
||||||
add_theme_root (user_roots, seen_user_roots, user_dir);
|
|
||||||
|
|
||||||
system_data_dirs = g_get_system_data_dirs ();
|
|
||||||
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
|
||||||
{
|
{
|
||||||
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL);
|
const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
|
||||||
add_theme_root (system_roots, seen_system_roots, system_themes);
|
|
||||||
g_free (system_themes);
|
if (xdg_data_dirs && xdg_data_dirs[0])
|
||||||
|
{
|
||||||
|
char **data_dirs = g_strsplit (xdg_data_dirs, G_SEARCHPATH_SEPARATOR_S, -1);
|
||||||
|
|
||||||
|
for (i = 0; data_dirs && data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
char *system_themes;
|
||||||
|
|
||||||
|
if (!data_dirs[i][0])
|
||||||
|
continue;
|
||||||
|
system_themes = g_build_filename (data_dirs[i], "themes", NULL);
|
||||||
|
add_theme_root (system_roots, seen_system_roots, system_themes);
|
||||||
|
g_free (system_themes);
|
||||||
|
}
|
||||||
|
g_strfreev (data_dirs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
system_data_dirs = g_get_system_data_dirs ();
|
||||||
|
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL);
|
||||||
|
add_theme_root (system_roots, seen_system_roots, system_themes);
|
||||||
|
g_free (system_themes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < system_roots->len; i++)
|
for (i = 0; i < system_roots->len; i++)
|
||||||
@@ -922,7 +1012,7 @@ static gboolean
|
|||||||
validate_theme_root_for_import (const char *theme_root, GError **error)
|
validate_theme_root_for_import (const char *theme_root, GError **error)
|
||||||
{
|
{
|
||||||
char *index_theme;
|
char *index_theme;
|
||||||
GKeyFile *keyfile;
|
GKeyFile *keyfile = NULL;
|
||||||
char *css_dir;
|
char *css_dir;
|
||||||
char *css_path;
|
char *css_path;
|
||||||
char *raw_inherits;
|
char *raw_inherits;
|
||||||
@@ -930,36 +1020,33 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
guint i;
|
guint i;
|
||||||
GError *load_error = NULL;
|
GError *load_error = NULL;
|
||||||
|
|
||||||
|
/* index.theme is optional for GTK3 CSS themes (GTK only needs
|
||||||
|
* gtk-3.x/gtk.css), but when it is present it must be well formed so
|
||||||
|
* broken archives are rejected up front. */
|
||||||
index_theme = g_build_filename (theme_root, "index.theme", NULL);
|
index_theme = g_build_filename (theme_root, "index.theme", NULL);
|
||||||
if (!g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
|
if (g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
|
||||||
{
|
{
|
||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
keyfile = g_key_file_new ();
|
||||||
"Invalid GTK3 theme at '%s': missing required index.theme at '%s'.",
|
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
||||||
theme_root, index_theme);
|
{
|
||||||
g_free (index_theme);
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||||
return FALSE;
|
"Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.",
|
||||||
}
|
theme_root, index_theme, load_error->message);
|
||||||
|
g_error_free (load_error);
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
|
g_free (index_theme);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
keyfile = g_key_file_new ();
|
if (!g_key_file_has_group (keyfile, "Desktop Entry"))
|
||||||
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
{
|
||||||
{
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
"Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
|
||||||
"Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.",
|
theme_root, index_theme);
|
||||||
theme_root, index_theme, load_error->message);
|
g_key_file_unref (keyfile);
|
||||||
g_error_free (load_error);
|
g_free (index_theme);
|
||||||
g_key_file_unref (keyfile);
|
return FALSE;
|
||||||
g_free (index_theme);
|
}
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_key_file_has_group (keyfile, "Desktop Entry"))
|
|
||||||
{
|
|
||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
|
||||||
"Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
|
|
||||||
theme_root, index_theme);
|
|
||||||
g_key_file_unref (keyfile);
|
|
||||||
g_free (index_theme);
|
|
||||||
return FALSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
||||||
@@ -968,7 +1055,8 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||||
"Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).",
|
"Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).",
|
||||||
theme_root);
|
theme_root);
|
||||||
g_key_file_unref (keyfile);
|
if (keyfile)
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
@@ -981,15 +1069,20 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
theme_root, css_path);
|
theme_root, css_path);
|
||||||
g_free (css_path);
|
g_free (css_path);
|
||||||
g_free (css_dir);
|
g_free (css_dir);
|
||||||
g_key_file_unref (keyfile);
|
if (keyfile)
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
g_free (css_path);
|
g_free (css_path);
|
||||||
g_free (css_dir);
|
g_free (css_dir);
|
||||||
|
|
||||||
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
raw_inherits = NULL;
|
||||||
g_key_file_unref (keyfile);
|
if (keyfile)
|
||||||
|
{
|
||||||
|
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
|
}
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
if (!raw_inherits)
|
if (!raw_inherits)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|||||||
@@ -381,9 +381,10 @@ test_archive_without_theme_reports_css_error (void)
|
|||||||
}
|
}
|
||||||
archive_path = g_build_filename (tmp_root, "invalid-theme.zip", NULL);
|
archive_path = g_build_filename (tmp_root, "invalid-theme.zip", NULL);
|
||||||
|
|
||||||
command = g_strdup_printf ("cd %s && zip -qr %s .", archive_root, archive_path);
|
command = g_strdup_printf ("sh -c \"cd '%s' && zip -qr '%s' .\"", archive_root, archive_path);
|
||||||
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
||||||
g_free (command);
|
g_free (command);
|
||||||
|
g_assert_true (g_file_test (archive_path, G_FILE_TEST_IS_REGULAR));
|
||||||
|
|
||||||
g_assert_false (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, &error));
|
g_assert_false (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, &error));
|
||||||
g_assert_null (imported_id);
|
g_assert_null (imported_id);
|
||||||
@@ -397,25 +398,31 @@ test_archive_without_theme_reports_css_error (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_import_rejects_theme_missing_index_theme (void)
|
test_import_accepts_theme_missing_index_theme (void)
|
||||||
{
|
{
|
||||||
char *tmp_root;
|
char *tmp_root;
|
||||||
char *src_root;
|
char *src_root;
|
||||||
char *theme_root;
|
char *theme_root;
|
||||||
char *imported_id = NULL;
|
char *imported_id = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
ZoitechatGtk3Theme *found;
|
||||||
|
|
||||||
setup_test_xdir (&tmp_root);
|
setup_test_xdir (&tmp_root);
|
||||||
src_root = g_build_filename (tmp_root, "src", NULL);
|
src_root = g_build_filename (tmp_root, "src", NULL);
|
||||||
g_mkdir_with_parents (src_root, 0700);
|
g_mkdir_with_parents (src_root, 0700);
|
||||||
theme_root = make_theme_dir (src_root, "NoIndex", FALSE, FALSE);
|
theme_root = make_theme_dir (src_root, "NoIndex", FALSE, FALSE);
|
||||||
|
|
||||||
g_assert_false (zoitechat_gtk3_theme_service_import (theme_root, &imported_id, &error));
|
/* index.theme is optional for GTK3 CSS themes: gtk-3.x/gtk.css alone
|
||||||
g_assert_null (imported_id);
|
* makes a loadable theme. */
|
||||||
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
|
g_assert_true (zoitechat_gtk3_theme_service_import (theme_root, &imported_id, &error));
|
||||||
g_assert_nonnull (g_strstr_len (error->message, -1, "missing required index.theme"));
|
g_assert_no_error (error);
|
||||||
g_assert_nonnull (g_strstr_len (error->message, -1, "NoIndex"));
|
g_assert_nonnull (imported_id);
|
||||||
g_error_free (error);
|
|
||||||
|
found = zoitechat_gtk3_theme_find_by_id (imported_id);
|
||||||
|
g_assert_nonnull (found);
|
||||||
|
g_assert_true (g_str_has_suffix (found->path, "NoIndex"));
|
||||||
|
zoitechat_gtk3_theme_free (found);
|
||||||
|
g_free (imported_id);
|
||||||
|
|
||||||
g_free (theme_root);
|
g_free (theme_root);
|
||||||
g_free (src_root);
|
g_free (src_root);
|
||||||
@@ -663,9 +670,10 @@ test_zip_import_nested_root (void)
|
|||||||
theme = make_theme_dir (nested, "Juno-ocean", TRUE, FALSE);
|
theme = make_theme_dir (nested, "Juno-ocean", TRUE, FALSE);
|
||||||
archive_path = g_build_filename (tmp_root, "themes.zip", NULL);
|
archive_path = g_build_filename (tmp_root, "themes.zip", NULL);
|
||||||
|
|
||||||
command = g_strdup_printf ("cd %s && zip -qr %s .", zip_root, archive_path);
|
command = g_strdup_printf ("sh -c \"cd '%s' && zip -qr '%s' .\"", zip_root, archive_path);
|
||||||
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
||||||
g_free (command);
|
g_free (command);
|
||||||
|
g_assert_true (g_file_test (archive_path, G_FILE_TEST_IS_REGULAR));
|
||||||
|
|
||||||
g_assert_true (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, NULL));
|
g_assert_true (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, NULL));
|
||||||
found = zoitechat_gtk3_theme_find_by_id (imported_id);
|
found = zoitechat_gtk3_theme_find_by_id (imported_id);
|
||||||
@@ -697,7 +705,7 @@ main (int argc, char **argv)
|
|||||||
g_test_add_func ("/gtk3-theme-service/zip-import-nested-root", test_zip_import_nested_root);
|
g_test_add_func ("/gtk3-theme-service/zip-import-nested-root", test_zip_import_nested_root);
|
||||||
g_test_add_func ("/gtk3-theme-service/invalid-archive-extract-error", test_invalid_archive_reports_extract_error);
|
g_test_add_func ("/gtk3-theme-service/invalid-archive-extract-error", test_invalid_archive_reports_extract_error);
|
||||||
g_test_add_func ("/gtk3-theme-service/archive-without-theme-css-error", test_archive_without_theme_reports_css_error);
|
g_test_add_func ("/gtk3-theme-service/archive-without-theme-css-error", test_archive_without_theme_reports_css_error);
|
||||||
g_test_add_func ("/gtk3-theme-service/import-missing-index-theme", test_import_rejects_theme_missing_index_theme);
|
g_test_add_func ("/gtk3-theme-service/import-missing-index-theme", test_import_accepts_theme_missing_index_theme);
|
||||||
g_test_add_func ("/gtk3-theme-service/import-missing-desktop-entry", test_import_rejects_index_without_desktop_entry);
|
g_test_add_func ("/gtk3-theme-service/import-missing-desktop-entry", test_import_rejects_index_without_desktop_entry);
|
||||||
g_test_add_func ("/gtk3-theme-service/import-unresolved-inherits", test_import_rejects_unresolved_inherits);
|
g_test_add_func ("/gtk3-theme-service/import-unresolved-inherits", test_import_rejects_unresolved_inherits);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_net_auto_reconnect;
|
unsigned int hex_net_auto_reconnect;
|
||||||
unsigned int hex_net_auto_reconnectonfail;
|
unsigned int hex_net_auto_reconnectonfail;
|
||||||
unsigned int hex_net_proxy_auth;
|
unsigned int hex_net_proxy_auth;
|
||||||
|
unsigned int hex_net_remote_media;
|
||||||
unsigned int hex_net_throttle;
|
unsigned int hex_net_throttle;
|
||||||
unsigned int hex_notify_whois_online;
|
unsigned int hex_notify_whois_online;
|
||||||
unsigned int hex_perl_warnings;
|
unsigned int hex_perl_warnings;
|
||||||
|
|||||||
@@ -307,6 +307,109 @@ chanview_set_impl (chanview *cv, int type)
|
|||||||
cv->func_focus (cv->focused);
|
cv->func_focus (cv->focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_sorted (chanview *cv, gboolean sorted)
|
||||||
|
{
|
||||||
|
cv->sorted = sorted ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_use_icons (chanview *cv, gboolean use_icons)
|
||||||
|
{
|
||||||
|
cv->use_icons = use_icons ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_trunc_len (chanview *cv, int trunc_len)
|
||||||
|
{
|
||||||
|
cv->trunc_len = trunc_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
chan *ch;
|
||||||
|
int old_pos;
|
||||||
|
} chanview_sort_entry;
|
||||||
|
|
||||||
|
/* re-sort the session rows below one parent with cb_compare, leaving
|
||||||
|
* utility rows (tag != 0) in place; mirrors chanview_insert_sorted() */
|
||||||
|
static void
|
||||||
|
chanview_resort_children (chanview *cv, GtkTreeIter *parent)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model = GTK_TREE_MODEL (cv->store);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
chanview_sort_entry *entries;
|
||||||
|
int *positions;
|
||||||
|
gint *new_order;
|
||||||
|
chan *ch;
|
||||||
|
int n, i, j, count;
|
||||||
|
|
||||||
|
n = gtk_tree_model_iter_n_children (model, parent);
|
||||||
|
if (n < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entries = g_new0 (chanview_sort_entry, n);
|
||||||
|
positions = g_new0 (int, n);
|
||||||
|
new_order = g_new0 (gint, n);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
new_order[i] = i;
|
||||||
|
if (!gtk_tree_model_iter_nth_child (model, &iter, parent, i))
|
||||||
|
continue;
|
||||||
|
gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1);
|
||||||
|
if (ch && ch->tag == 0 && ch->userdata)
|
||||||
|
{
|
||||||
|
entries[count].ch = ch;
|
||||||
|
entries[count].old_pos = i;
|
||||||
|
positions[count] = i;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stable insertion sort; tab groups stay small so this is cheap */
|
||||||
|
for (i = 1; i < count; i++)
|
||||||
|
{
|
||||||
|
chanview_sort_entry key = entries[i];
|
||||||
|
|
||||||
|
j = i - 1;
|
||||||
|
while (j >= 0 && cv->cb_compare (entries[j].ch->userdata, key.ch->userdata) > 0)
|
||||||
|
{
|
||||||
|
entries[j + 1] = entries[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
entries[j + 1] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
new_order[positions[i]] = entries[i].old_pos;
|
||||||
|
|
||||||
|
gtk_tree_store_reorder (cv->store, parent, new_order);
|
||||||
|
|
||||||
|
g_free (entries);
|
||||||
|
g_free (positions);
|
||||||
|
g_free (new_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* top-level rows keep their order (insertion never sorts them either);
|
||||||
|
* only the children within each family are sorted */
|
||||||
|
void
|
||||||
|
chanview_resort (chanview *cv)
|
||||||
|
{
|
||||||
|
GtkTreeIter iter;
|
||||||
|
|
||||||
|
if (!cv->cb_compare)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
chanview_resort_children (cv, &iter);
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ void chanview_set_callbacks (chanview *cv,
|
|||||||
gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
|
gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
|
||||||
int (*cb_compare) (void *a, void *b));
|
int (*cb_compare) (void *a, void *b));
|
||||||
void chanview_set_impl (chanview *cv, int type);
|
void chanview_set_impl (chanview *cv, int type);
|
||||||
|
void chanview_set_sorted (chanview *cv, gboolean sorted);
|
||||||
|
void chanview_set_use_icons (chanview *cv, gboolean use_icons);
|
||||||
|
void chanview_set_trunc_len (chanview *cv, int trunc_len);
|
||||||
|
void chanview_resort (chanview *cv);
|
||||||
chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
|
chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
|
||||||
int chanview_get_size (chanview *cv);
|
int chanview_get_size (chanview *cv);
|
||||||
GtkWidget *chanview_get_box (chanview *cv);
|
GtkWidget *chanview_get_box (chanview *cv);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#include "chanlist.h"
|
#include "chanlist.h"
|
||||||
#include "joind.h"
|
#include "joind.h"
|
||||||
#include "xtext.h"
|
#include "xtext.h"
|
||||||
|
#include "inline-image.h"
|
||||||
#include "theme/theme-gtk.h"
|
#include "theme/theme-gtk.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "notifygui.h"
|
#include "notifygui.h"
|
||||||
@@ -943,7 +944,13 @@ void
|
|||||||
fe_print_text (struct session *sess, char *text, time_t stamp,
|
fe_print_text (struct session *sess, char *text, time_t stamp,
|
||||||
gboolean no_activity)
|
gboolean no_activity)
|
||||||
{
|
{
|
||||||
PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.hex_text_indent, stamp);
|
/* image links become a "[view image]" placeholder with a hidden URL */
|
||||||
|
char *filtered = inline_image_filter_text (text);
|
||||||
|
|
||||||
|
PrintTextRaw (sess->res->buffer,
|
||||||
|
(unsigned char *)(filtered ? filtered : text),
|
||||||
|
prefs.hex_text_indent, stamp);
|
||||||
|
g_free (filtered);
|
||||||
|
|
||||||
if (no_activity || !sess->gui->is_tab)
|
if (no_activity || !sess->gui->is_tab)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
|||||||
<ClInclude Include="fkeys.h" />
|
<ClInclude Include="fkeys.h" />
|
||||||
<ClInclude Include="gtkutil.h" />
|
<ClInclude Include="gtkutil.h" />
|
||||||
<ClInclude Include="icon-resolver.h" />
|
<ClInclude Include="icon-resolver.h" />
|
||||||
|
<ClInclude Include="inline-image.h" />
|
||||||
<ClInclude Include="joind.h" />
|
<ClInclude Include="joind.h" />
|
||||||
<ClInclude Include="maingui.h" />
|
<ClInclude Include="maingui.h" />
|
||||||
<ClInclude Include="preferences-persistence.h" />
|
<ClInclude Include="preferences-persistence.h" />
|
||||||
@@ -102,6 +103,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
|||||||
<ClCompile Include="gtkutil.c" />
|
<ClCompile Include="gtkutil.c" />
|
||||||
<ClCompile Include="icon-resolver.c" />
|
<ClCompile Include="icon-resolver.c" />
|
||||||
<ClCompile Include="ignoregui.c" />
|
<ClCompile Include="ignoregui.c" />
|
||||||
|
<ClCompile Include="inline-image.c" />
|
||||||
<ClCompile Include="joind.c" />
|
<ClCompile Include="joind.c" />
|
||||||
<ClCompile Include="maingui.c" />
|
<ClCompile Include="maingui.c" />
|
||||||
<ClCompile Include="menu.c" />
|
<ClCompile Include="menu.c" />
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
<ClInclude Include="icon-resolver.h">
|
<ClInclude Include="icon-resolver.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="inline-image.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="joind.h">
|
<ClInclude Include="joind.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -170,6 +173,9 @@
|
|||||||
<ClCompile Include="ignoregui.c">
|
<ClCompile Include="ignoregui.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="inline-image.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="joind.c">
|
<ClCompile Include="joind.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
1667
src/fe-gtk/inline-image.c
Normal file
1667
src/fe-gtk/inline-image.c
Normal file
File diff suppressed because it is too large
Load Diff
45
src/fe-gtk/inline-image.h
Normal file
45
src/fe-gtk/inline-image.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/* ZoiteChat
|
||||||
|
* Copyright (C) 2026 ZoiteChat contributors.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZOITECHAT_INLINE_IMAGE_H
|
||||||
|
#define ZOITECHAT_INLINE_IMAGE_H
|
||||||
|
|
||||||
|
#include "xtext.h"
|
||||||
|
|
||||||
|
/* does this look like a direct http(s) link to an image file? */
|
||||||
|
gboolean inline_image_is_image_url (const char *url);
|
||||||
|
|
||||||
|
/* show the image behind 'url' inline below 'ent', or hide it again if it is
|
||||||
|
* already shown. Errors are reported to 'sess'. */
|
||||||
|
void inline_image_toggle (session *sess, GtkXText *xtext, textentry *ent,
|
||||||
|
const char *url);
|
||||||
|
|
||||||
|
/* replace image links in a message with a clickable placeholder label that
|
||||||
|
* hides the URL. Returns a newly allocated string, or NULL if the text has
|
||||||
|
* no image links (or the feature is off). */
|
||||||
|
char *inline_image_filter_text (const char *text);
|
||||||
|
|
||||||
|
/* pop up a window with a larger version of the entry's inline image */
|
||||||
|
void inline_image_show_viewer (GtkXText *xtext, textentry *ent);
|
||||||
|
|
||||||
|
/* can downloads go through the proxy configured for ZoiteChat (or is no
|
||||||
|
* proxy configured)? FALSE means loading media would have to bypass the
|
||||||
|
* user's proxy, which is never done. */
|
||||||
|
gboolean inline_image_proxy_usable (void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
#include "pixmaps.h"
|
#include "pixmaps.h"
|
||||||
#include "plugin-tray.h"
|
#include "plugin-tray.h"
|
||||||
#include "xtext.h"
|
#include "xtext.h"
|
||||||
|
#include "inline-image.h"
|
||||||
#include "sexy-spell-entry.h"
|
#include "sexy-spell-entry.h"
|
||||||
#include "gtkutil.h"
|
#include "gtkutil.h"
|
||||||
|
|
||||||
@@ -569,6 +570,18 @@ mg_create_tab_colors (void)
|
|||||||
away_list = mg_attr_list_create (&gui_palette[THEME_TOKEN_TAB_AWAY], FALSE);
|
away_list = mg_attr_list_create (&gui_palette[THEME_TOKEN_TAB_AWAY], FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PangoAttrList *
|
||||||
|
mg_attr_list_for_tab_state (int state)
|
||||||
|
{
|
||||||
|
if (state & TAB_STATE_NEW_HILIGHT)
|
||||||
|
return nickseen_list;
|
||||||
|
if (state & TAB_STATE_NEW_MSG)
|
||||||
|
return newmsg_list;
|
||||||
|
if (state & TAB_STATE_NEW_DATA)
|
||||||
|
return newdata_list;
|
||||||
|
return plain_list;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_window_urgency (GtkWidget *win, gboolean set)
|
set_window_urgency (GtkWidget *win, gboolean set)
|
||||||
{
|
{
|
||||||
@@ -714,7 +727,10 @@ mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away)
|
|||||||
{
|
{
|
||||||
if (gui->op_xpm)
|
if (gui->op_xpm)
|
||||||
{
|
{
|
||||||
if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */
|
/* no change? (the pref check makes sure a live settings
|
||||||
|
change can still remove the icon) */
|
||||||
|
if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm)) &&
|
||||||
|
prefs.hex_gui_input_icon)
|
||||||
{
|
{
|
||||||
mg_set_myself_away (gui, away);
|
mg_set_myself_away (gui, away);
|
||||||
return;
|
return;
|
||||||
@@ -3596,6 +3612,48 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
mg_create_chanmodebuttons (gui, bbox);
|
mg_create_chanmodebuttons (gui, bbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* re-apply the multi-line topic and inline mode button preferences to an
|
||||||
|
* already built topic bar (see mg_create_topicbar () for the layout) */
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_update_topicbar_layout (session_gui *gui)
|
||||||
|
{
|
||||||
|
GtkWidget *topic = gui->topic_entry;
|
||||||
|
GtkWidget *vbox = gui->topic_bar;
|
||||||
|
GtkWidget *topic_scroll;
|
||||||
|
GtkWidget *hbox;
|
||||||
|
GtkWidget *mode_hbox;
|
||||||
|
GtkWidget *desired_parent;
|
||||||
|
GtkWidget *current_parent;
|
||||||
|
gboolean wrap;
|
||||||
|
|
||||||
|
if (!topic || !GTK_IS_TEXT_VIEW (topic) || !vbox || !gui->topicbutton_box)
|
||||||
|
return;
|
||||||
|
|
||||||
|
wrap = prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline;
|
||||||
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic),
|
||||||
|
wrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
|
||||||
|
|
||||||
|
topic_scroll = gtk_widget_get_parent (topic);
|
||||||
|
hbox = topic_scroll ? gtk_widget_get_parent (topic_scroll) : NULL;
|
||||||
|
mode_hbox = gtk_widget_get_parent (gui->topicbutton_box);
|
||||||
|
|
||||||
|
if (mode_hbox && hbox)
|
||||||
|
{
|
||||||
|
desired_parent = prefs.hex_gui_mode_buttons_inline ? hbox : vbox;
|
||||||
|
current_parent = gtk_widget_get_parent (mode_hbox);
|
||||||
|
if (current_parent && current_parent != desired_parent)
|
||||||
|
{
|
||||||
|
g_object_ref (mode_hbox);
|
||||||
|
gtk_container_remove (GTK_CONTAINER (current_parent), mode_hbox);
|
||||||
|
gtk_box_pack_start (GTK_BOX (desired_parent), mode_hbox, 0, 0, 0);
|
||||||
|
g_object_unref (mode_hbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_topicbar_update_height (topic);
|
||||||
|
}
|
||||||
|
|
||||||
/* check if a word is clickable */
|
/* check if a word is clickable */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -3630,7 +3688,13 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even)
|
|||||||
{
|
{
|
||||||
if (word == NULL)
|
if (word == NULL)
|
||||||
{
|
{
|
||||||
mg_focus (sess);
|
/* a click on a shown inline image opens the viewer */
|
||||||
|
textentry *img_ent = gtk_xtext_get_clicked_image_entry (GTK_XTEXT (xtext));
|
||||||
|
|
||||||
|
if (img_ent)
|
||||||
|
inline_image_show_viewer (GTK_XTEXT (xtext), img_ent);
|
||||||
|
else
|
||||||
|
mg_focus (sess);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3642,6 +3706,20 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even)
|
|||||||
case WORD_HOST6:
|
case WORD_HOST6:
|
||||||
case WORD_HOST:
|
case WORD_HOST:
|
||||||
word[end] = 0;
|
word[end] = 0;
|
||||||
|
/* image links toggle an inline preview instead
|
||||||
|
of opening the browser */
|
||||||
|
if (word_type == WORD_URL &&
|
||||||
|
prefs.hex_net_remote_media &&
|
||||||
|
inline_image_is_image_url (word + start))
|
||||||
|
{
|
||||||
|
textentry *ent = gtk_xtext_get_clicked_entry (GTK_XTEXT (xtext));
|
||||||
|
|
||||||
|
if (ent)
|
||||||
|
{
|
||||||
|
inline_image_toggle (sess, GTK_XTEXT (xtext), ent, word + start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
fe_open_url (word + start);
|
fe_open_url (word + start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3670,7 +3748,8 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even)
|
|||||||
case WORD_HOST:
|
case WORD_HOST:
|
||||||
word[end] = 0;
|
word[end] = 0;
|
||||||
word += start;
|
word += start;
|
||||||
menu_urlmenu (even, word);
|
menu_urlmenu_entry (even, word, xtext,
|
||||||
|
gtk_xtext_get_clicked_entry (GTK_XTEXT (xtext)));
|
||||||
break;
|
break;
|
||||||
case WORD_NICK:
|
case WORD_NICK:
|
||||||
word[end] = 0;
|
word[end] = 0;
|
||||||
@@ -4004,8 +4083,12 @@ mg_create_userlist (session_gui *gui, GtkWidget *box)
|
|||||||
gtk_widget_set_margin_end (gui->namelistinfo, 0);
|
gtk_widget_set_margin_end (gui->namelistinfo, 0);
|
||||||
gtk_widget_set_hexpand (gui->namelistinfo, TRUE);
|
gtk_widget_set_hexpand (gui->namelistinfo, TRUE);
|
||||||
gtk_widget_set_halign (gui->namelistinfo, GTK_ALIGN_FILL);
|
gtk_widget_set_halign (gui->namelistinfo, GTK_ALIGN_FILL);
|
||||||
|
/* always pack it so the preference can be toggled without a
|
||||||
|
restart; visibility is controlled explicitly */
|
||||||
|
gtk_widget_set_no_show_all (gui->namelistinfo, TRUE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0);
|
||||||
if (prefs.hex_gui_ulist_count)
|
if (prefs.hex_gui_ulist_count)
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0);
|
gtk_widget_show (gui->namelistinfo);
|
||||||
|
|
||||||
gui->user_tree = ulist = userlist_create (vbox);
|
gui->user_tree = ulist = userlist_create (vbox);
|
||||||
|
|
||||||
@@ -4904,7 +4987,11 @@ mg_create_menu (session_gui *gui, GtkWidget *table, int away_state)
|
|||||||
gui->menu_item);
|
gui->menu_item);
|
||||||
gtk_widget_set_hexpand (gui->menu, TRUE);
|
gtk_widget_set_hexpand (gui->menu, TRUE);
|
||||||
gtk_widget_set_vexpand (gui->menu, FALSE);
|
gtk_widget_set_vexpand (gui->menu, FALSE);
|
||||||
gtk_widget_set_halign (gui->menu, GTK_ALIGN_START);
|
/* Fill the row: with GTK_ALIGN_START the menu bar stops after its
|
||||||
|
* last item and the rest of the row shows bare window background,
|
||||||
|
* which reads as a two-colored menu bar whenever the theme styles
|
||||||
|
* menubar and window differently. */
|
||||||
|
gtk_widget_set_halign (gui->menu, GTK_ALIGN_FILL);
|
||||||
gtk_widget_set_valign (gui->menu, GTK_ALIGN_FILL);
|
gtk_widget_set_valign (gui->menu, GTK_ALIGN_FILL);
|
||||||
gtk_grid_attach (GTK_GRID (table), gui->menu, 0, 0, 3, 1);
|
gtk_grid_attach (GTK_GRID (table), gui->menu, 0, 0, 3, 1);
|
||||||
}
|
}
|
||||||
@@ -5229,6 +5316,10 @@ mg_apply_setup (void)
|
|||||||
sess = list->data;
|
sess = list->data;
|
||||||
gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text);
|
gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text);
|
||||||
((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE;
|
((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE;
|
||||||
|
/* the attribute lists were just recreated, so re-apply the
|
||||||
|
color matching each tab's activity state */
|
||||||
|
if (sess->res->tab)
|
||||||
|
chan_set_color (sess->res->tab, mg_attr_list_for_tab_state (sess->tab_state));
|
||||||
if (!sess->gui->is_tab || !done_main)
|
if (!sess->gui->is_tab || !done_main)
|
||||||
mg_place_userlist_and_chanview (sess->gui);
|
mg_place_userlist_and_chanview (sess->gui);
|
||||||
if (sess->gui->is_tab)
|
if (sess->gui->is_tab)
|
||||||
@@ -5271,6 +5362,49 @@ fe_buttons_update (session *sess)
|
|||||||
gtk_widget_hide (sess->gui->button_box);
|
gtk_widget_hide (sess->gui->button_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tab label for a session that has no joined channel: the channel we are
|
||||||
|
* waiting to join in parentheses, or "<none>". tbuf must hold CHANLEN+6. */
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_tab_pending_name (session *sess, char *tbuf)
|
||||||
|
{
|
||||||
|
if (sess->waitchannel[0])
|
||||||
|
{
|
||||||
|
if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc)
|
||||||
|
{
|
||||||
|
/* truncate long channel names */
|
||||||
|
tbuf[0] = '(';
|
||||||
|
strcpy (tbuf + 1, sess->waitchannel);
|
||||||
|
g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0;
|
||||||
|
strcat (tbuf, "..)");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
sprintf (tbuf, "(%s)", sess->waitchannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
strcpy (tbuf, _("<none>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* re-apply the tab label truncation preference to a session's tab */
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_retrunc_tab (session *sess)
|
||||||
|
{
|
||||||
|
char tbuf[CHANLEN+6];
|
||||||
|
|
||||||
|
if (!sess->res->tab)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sess->channel[0])
|
||||||
|
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mg_tab_pending_name (sess, tbuf);
|
||||||
|
chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fe_clear_channel (session *sess)
|
fe_clear_channel (session *sess)
|
||||||
{
|
{
|
||||||
@@ -5279,22 +5413,7 @@ fe_clear_channel (session *sess)
|
|||||||
|
|
||||||
if (sess->gui->is_tab)
|
if (sess->gui->is_tab)
|
||||||
{
|
{
|
||||||
if (sess->waitchannel[0])
|
mg_tab_pending_name (sess, tbuf);
|
||||||
{
|
|
||||||
if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc)
|
|
||||||
{
|
|
||||||
/* truncate long channel names */
|
|
||||||
tbuf[0] = '(';
|
|
||||||
strcpy (tbuf + 1, sess->waitchannel);
|
|
||||||
g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0;
|
|
||||||
strcat (tbuf, "..)");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
sprintf (tbuf, "(%s)", sess->waitchannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
strcpy (tbuf, _("<none>"));
|
|
||||||
chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc);
|
chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5418,6 +5537,110 @@ fe_set_channel (session *sess)
|
|||||||
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_apply_live_prefs_to_gui (session_gui *gui, session *sess, const mg_live_prefs *changes)
|
||||||
|
{
|
||||||
|
if (changes->meters)
|
||||||
|
mg_update_meters (gui);
|
||||||
|
|
||||||
|
if (changes->ulist_columns && gui->user_tree)
|
||||||
|
userlist_rebuild_columns (gui->user_tree);
|
||||||
|
|
||||||
|
if (changes->ulist_count && gui->namelistinfo)
|
||||||
|
{
|
||||||
|
if (prefs.hex_gui_ulist_count)
|
||||||
|
gtk_widget_show (gui->namelistinfo);
|
||||||
|
else
|
||||||
|
gtk_widget_hide (gui->namelistinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes->input_box && gui->nick_box)
|
||||||
|
{
|
||||||
|
if (prefs.hex_gui_input_nick)
|
||||||
|
gtk_widget_show (gui->nick_box);
|
||||||
|
else
|
||||||
|
gtk_widget_hide (gui->nick_box);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* refresh the access icon; re-filling the user list rows can
|
||||||
|
clear it as a side effect, so restore it here too */
|
||||||
|
if ((changes->input_box || changes->ulist_rows) && sess && is_session (sess))
|
||||||
|
{
|
||||||
|
if (sess->type == SESS_DIALOG)
|
||||||
|
mg_set_access_icon (gui, NULL, sess->server->is_away);
|
||||||
|
else
|
||||||
|
mg_set_access_icon (gui, get_user_icon (sess->server, sess->me), sess->server->is_away);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes->topic_bar)
|
||||||
|
mg_update_topicbar_layout (gui);
|
||||||
|
|
||||||
|
if (changes->transparency && gui->window)
|
||||||
|
gtk_widget_set_opacity (gui->window, (prefs.hex_gui_transparency / 255.));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* apply preference changes that used to require a restart. called by
|
||||||
|
* setup.c after the new prefs have been committed. */
|
||||||
|
|
||||||
|
void
|
||||||
|
mg_apply_live_prefs (const mg_live_prefs *changes)
|
||||||
|
{
|
||||||
|
GSList *list;
|
||||||
|
session *sess;
|
||||||
|
int done_main = FALSE;
|
||||||
|
|
||||||
|
if (!changes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* the tab bar / channel tree lives in the shared tab window */
|
||||||
|
if (mg_gui && mg_gui->chanview)
|
||||||
|
{
|
||||||
|
if (changes->tab_resort)
|
||||||
|
{
|
||||||
|
chanview_set_sorted (mg_gui->chanview, prefs.hex_gui_tab_sort);
|
||||||
|
chanview_resort (mg_gui->chanview);
|
||||||
|
}
|
||||||
|
if (changes->tab_trunc)
|
||||||
|
chanview_set_trunc_len (mg_gui->chanview, prefs.hex_gui_tab_trunc);
|
||||||
|
if (changes->chanview || changes->tab_resort)
|
||||||
|
{
|
||||||
|
chanview_set_use_icons (mg_gui->chanview,
|
||||||
|
prefs.hex_gui_tab_icons && (pix_tree_channel || pix_tree_dialog ||
|
||||||
|
pix_tree_server || pix_tree_util));
|
||||||
|
/* rebuild the tab bar / tree widgets from the model */
|
||||||
|
chanview_set_impl (mg_gui->chanview, prefs.hex_gui_tab_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = sess_list;
|
||||||
|
while (list)
|
||||||
|
{
|
||||||
|
sess = list->data;
|
||||||
|
|
||||||
|
if (changes->tab_trunc)
|
||||||
|
mg_retrunc_tab (sess);
|
||||||
|
if (changes->ulist_sort)
|
||||||
|
userlist_apply_sort (sess);
|
||||||
|
if (changes->ulist_rows)
|
||||||
|
userlist_refill (sess);
|
||||||
|
|
||||||
|
if (sess->gui->is_tab)
|
||||||
|
{
|
||||||
|
/* only apply to the shared tab window once, using the
|
||||||
|
front-most session for session-dependent bits */
|
||||||
|
if (!done_main)
|
||||||
|
{
|
||||||
|
done_main = TRUE;
|
||||||
|
mg_apply_live_prefs_to_gui (sess->gui, current_tab, changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mg_apply_live_prefs_to_gui (sess->gui, sess, changes);
|
||||||
|
|
||||||
|
list = list->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fe_set_typing (session *sess, const char *nick, const char *state)
|
fe_set_typing (session *sess, const char *nick, const char *state)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, i
|
|||||||
void mg_set_title (GtkWidget *button, char *title);
|
void mg_set_title (GtkWidget *button, char *title);
|
||||||
void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
|
void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
|
||||||
void mg_apply_setup (void);
|
void mg_apply_setup (void);
|
||||||
|
|
||||||
|
/* which parts of the GUI need a live rebuild after a preferences change */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int chanview:1; /* rebuild the tab bar / channel tree */
|
||||||
|
unsigned int tab_resort:1; /* re-sort channel tabs */
|
||||||
|
unsigned int tab_trunc:1; /* re-truncate tab labels */
|
||||||
|
unsigned int ulist_columns:1; /* rebuild user list columns */
|
||||||
|
unsigned int ulist_rows:1; /* re-fill user list rows */
|
||||||
|
unsigned int ulist_sort:1; /* re-apply user list sort order */
|
||||||
|
unsigned int ulist_count:1; /* user count label visibility */
|
||||||
|
unsigned int meters:1; /* rebuild lag/throttle meters */
|
||||||
|
unsigned int input_box:1; /* nick box visibility and access icon */
|
||||||
|
unsigned int topic_bar:1; /* topic wrap mode and mode button placement */
|
||||||
|
unsigned int transparency:1; /* main window opacity */
|
||||||
|
} mg_live_prefs;
|
||||||
|
|
||||||
|
void mg_apply_live_prefs (const mg_live_prefs *changes);
|
||||||
void mg_apply_session_font_prefs (session_gui *gui);
|
void mg_apply_session_font_prefs (session_gui *gui);
|
||||||
void mg_close_sess (session *);
|
void mg_close_sess (session *);
|
||||||
void mg_tab_close (session *sess);
|
void mg_tab_close (session *sess);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#include "../common/util.h"
|
#include "../common/util.h"
|
||||||
#include "../common/text.h"
|
#include "../common/text.h"
|
||||||
#include "xtext.h"
|
#include "xtext.h"
|
||||||
|
#include "inline-image.h"
|
||||||
#include "ascii.h"
|
#include "ascii.h"
|
||||||
#include "banlist.h"
|
#include "banlist.h"
|
||||||
#include "chanlist.h"
|
#include "chanlist.h"
|
||||||
@@ -1056,8 +1057,40 @@ open_url_cb (GtkWidget *item, char *url)
|
|||||||
handle_command (current_sess, buf, FALSE);
|
handle_command (current_sess, buf, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the text entry the open URL menu was created for; the widget pointer is
|
||||||
|
nulled automatically if the widget dies while the menu is up */
|
||||||
|
static GtkXText *urlmenu_xtext = NULL;
|
||||||
|
static textentry *urlmenu_ent = NULL;
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_urlmenu_set_entry (GtkWidget *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
if (urlmenu_xtext)
|
||||||
|
g_object_remove_weak_pointer (G_OBJECT (urlmenu_xtext),
|
||||||
|
(gpointer *) &urlmenu_xtext);
|
||||||
|
urlmenu_xtext = xtext ? GTK_XTEXT (xtext) : NULL;
|
||||||
|
urlmenu_ent = ent;
|
||||||
|
if (urlmenu_xtext)
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (urlmenu_xtext),
|
||||||
|
(gpointer *) &urlmenu_xtext);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
inline_image_menu_cb (GtkWidget *item, char *url)
|
||||||
|
{
|
||||||
|
if (urlmenu_xtext == NULL || urlmenu_ent == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* the line may have scrolled out of the buffer since the menu opened */
|
||||||
|
if (!gtk_xtext_buffer_contains (urlmenu_xtext, urlmenu_ent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
inline_image_toggle (current_sess, urlmenu_xtext, urlmenu_ent, url);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_urlmenu (GdkEventButton *event, char *url)
|
menu_urlmenu_entry (GdkEventButton *event, char *url, GtkWidget *xtext,
|
||||||
|
textentry *ent)
|
||||||
{
|
{
|
||||||
GtkWidget *menu;
|
GtkWidget *menu;
|
||||||
char *tmp, *chop;
|
char *tmp, *chop;
|
||||||
@@ -1065,6 +1098,14 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
g_free (str_copy);
|
g_free (str_copy);
|
||||||
str_copy = g_strdup (url);
|
str_copy = g_strdup (url);
|
||||||
|
|
||||||
|
if (xtext == NULL || ent == NULL || !prefs.hex_net_remote_media ||
|
||||||
|
!inline_image_is_image_url (str_copy))
|
||||||
|
{
|
||||||
|
xtext = NULL;
|
||||||
|
ent = NULL;
|
||||||
|
}
|
||||||
|
menu_urlmenu_set_entry (xtext, ent);
|
||||||
|
|
||||||
menu = menu_new ();
|
menu = menu_new ();
|
||||||
/* more than 51 chars? Chop it */
|
/* more than 51 chars? Chop it */
|
||||||
if (g_utf8_strlen (str_copy, -1) >= 52)
|
if (g_utf8_strlen (str_copy, -1) >= 52)
|
||||||
@@ -1087,6 +1128,15 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
|
menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
|
||||||
else
|
else
|
||||||
menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
|
menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
|
||||||
|
if (ent)
|
||||||
|
{
|
||||||
|
const char *shown = gtk_xtext_entry_get_image_url (GTK_XTEXT (xtext), ent);
|
||||||
|
|
||||||
|
menu_quick_item_with_callback (inline_image_menu_cb,
|
||||||
|
shown && strcmp (shown, str_copy) == 0 ?
|
||||||
|
_("Hide Inline Image") : _("Show Image Inline"),
|
||||||
|
menu, str_copy);
|
||||||
|
}
|
||||||
menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
|
menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
|
||||||
/* custom ones from urlhandlers.conf */
|
/* custom ones from urlhandlers.conf */
|
||||||
menu_create (menu, urlhandler_list, str_copy, TRUE);
|
menu_create (menu, urlhandler_list, str_copy, TRUE);
|
||||||
@@ -1094,6 +1144,12 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
menu_popup (menu, event, NULL);
|
menu_popup (menu, event, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
menu_urlmenu (GdkEventButton *event, char *url)
|
||||||
|
{
|
||||||
|
menu_urlmenu_entry (event, url, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_chan_cycle (GtkWidget * menu, char *chan)
|
menu_chan_cycle (GtkWidget * menu, char *chan)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,8 +20,11 @@
|
|||||||
#ifndef ZOITECHAT_MENU_H
|
#ifndef ZOITECHAT_MENU_H
|
||||||
#define ZOITECHAT_MENU_H
|
#define ZOITECHAT_MENU_H
|
||||||
|
|
||||||
|
#include "xtext.h"
|
||||||
|
|
||||||
GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets);
|
GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets);
|
||||||
void menu_urlmenu (GdkEventButton * event, char *url);
|
void menu_urlmenu (GdkEventButton * event, char *url);
|
||||||
|
void menu_urlmenu_entry (GdkEventButton *event, char *url, GtkWidget *xtext, textentry *ent);
|
||||||
void menu_chanmenu (session *sess, GdkEventButton * event, char *chan);
|
void menu_chanmenu (session *sess, GdkEventButton * event, char *chan);
|
||||||
void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel, gboolean istree);
|
void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel, gboolean istree);
|
||||||
void menu_addconnectmenu (server *serv, GtkWidget *menu);
|
void menu_addconnectmenu (server *serv, GtkWidget *menu);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ zoitechat_gtk_sources = [
|
|||||||
'gtkutil.c',
|
'gtkutil.c',
|
||||||
'icon-resolver.c',
|
'icon-resolver.c',
|
||||||
'ignoregui.c',
|
'ignoregui.c',
|
||||||
|
'inline-image.c',
|
||||||
'joind.c',
|
'joind.c',
|
||||||
'menu.c',
|
'menu.c',
|
||||||
'maingui.c',
|
'maingui.c',
|
||||||
@@ -227,6 +228,20 @@ test('Theme Access Routing Tests', theme_access_tests,
|
|||||||
timeout: 120,
|
timeout: 120,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
theme_gtk3_settings_tests = executable('theme_gtk3_settings_tests',
|
||||||
|
[
|
||||||
|
'theme/tests/test-theme-gtk3-settings.c',
|
||||||
|
'theme/theme-gtk3.c',
|
||||||
|
],
|
||||||
|
include_directories: [config_h_include],
|
||||||
|
dependencies: [gtk_dep],
|
||||||
|
)
|
||||||
|
|
||||||
|
test('Theme GTK3 Settings Tests', theme_gtk3_settings_tests,
|
||||||
|
protocol: 'tap',
|
||||||
|
timeout: 120,
|
||||||
|
)
|
||||||
|
|
||||||
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
||||||
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
||||||
include_directories: [config_h_include],
|
include_directories: [config_h_include],
|
||||||
|
|||||||
@@ -1539,6 +1539,11 @@ tray_cleanup (void)
|
|||||||
void
|
void
|
||||||
tray_apply_setup (void)
|
tray_apply_setup (void)
|
||||||
{
|
{
|
||||||
|
/* Theme/setup events can be dispatched before the tray plugin has
|
||||||
|
* been registered; there is nothing to (de)activate yet. */
|
||||||
|
if (!ph)
|
||||||
|
return;
|
||||||
|
|
||||||
if (tray_backend_active)
|
if (tray_backend_active)
|
||||||
{
|
{
|
||||||
if (!prefs.hex_gui_tray)
|
if (!prefs.hex_gui_tray)
|
||||||
|
|||||||
@@ -673,6 +673,11 @@ static const setting network_settings[] =
|
|||||||
{ST_ENTRY, N_("Username:"), P_OFFSETNL(hex_net_proxy_user), 0, 0, sizeof prefs.hex_net_proxy_user},
|
{ST_ENTRY, N_("Username:"), P_OFFSETNL(hex_net_proxy_user), 0, 0, sizeof prefs.hex_net_proxy_user},
|
||||||
{ST_ENTRY, N_("Password:"), P_OFFSETNL(hex_net_proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.hex_net_proxy_pass},
|
{ST_ENTRY, N_("Password:"), P_OFFSETNL(hex_net_proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.hex_net_proxy_pass},
|
||||||
|
|
||||||
|
{ST_HEADER, N_("Remote Media"), 0, 0, 0, 0},
|
||||||
|
{ST_TOGGLE, N_("Allow loading remote media (inline images) when clicked"),
|
||||||
|
P_OFFINTNL(hex_net_remote_media),
|
||||||
|
N_("Loading remote media contacts the site hosting it, which can reveal your IP address and that you viewed the message. You are asked for confirmation before each new domain is contacted."), 0, 0},
|
||||||
|
|
||||||
{ST_END, 0, 0, 0, 0, 0}
|
{ST_END, 0, 0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1045,28 +1050,30 @@ setup_create_spin (GtkWidget *table, int row, const setting *set)
|
|||||||
return wid;
|
return wid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static guint setup_trans_apply_tag = 0;
|
||||||
|
/* the window opacity the dialog opened with, so cancelling can undo the
|
||||||
|
* live preview done by the slider */
|
||||||
|
static int setup_transparency_snapshot;
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
setup_apply_trans (int *tag)
|
setup_apply_trans (gpointer userdata)
|
||||||
{
|
{
|
||||||
prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
|
prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
|
||||||
gtk_widget_set_opacity (current_sess->gui->window,
|
if (current_sess)
|
||||||
(prefs.hex_gui_transparency / 255.));
|
gtk_widget_set_opacity (current_sess->gui->window,
|
||||||
|
(prefs.hex_gui_transparency / 255.));
|
||||||
|
|
||||||
*tag = 0;
|
setup_trans_apply_tag = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_hscale_cb (GtkRange *wid, const setting *set)
|
setup_hscale_cb (GtkRange *wid, const setting *set)
|
||||||
{
|
{
|
||||||
static int tag = 0;
|
|
||||||
|
|
||||||
setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));
|
setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));
|
||||||
|
|
||||||
if (tag == 0)
|
if (setup_trans_apply_tag == 0)
|
||||||
{
|
setup_trans_apply_tag = g_idle_add ((GSourceFunc) setup_apply_trans, NULL);
|
||||||
tag = g_idle_add ((GSourceFunc) setup_apply_trans, &tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1091,7 +1098,8 @@ setup_create_hscale (GtkWidget *table, int row, const setting *set)
|
|||||||
|
|
||||||
#ifndef WIN32 /* Windows always supports this */
|
#ifndef WIN32 /* Windows always supports this */
|
||||||
/* Only used for transparency currently */
|
/* Only used for transparency currently */
|
||||||
if (!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
|
if (current_sess &&
|
||||||
|
!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
|
||||||
gtk_widget_set_sensitive (wid, FALSE);
|
gtk_widget_set_sensitive (wid, FALSE);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -2293,7 +2301,9 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
ThemeChangedEvent event;
|
ThemeChangedEvent event;
|
||||||
struct zoitechatprefs old_prefs = prefs;
|
struct zoitechatprefs old_prefs = prefs;
|
||||||
int old_dark_mode = prefs.hex_gui_dark_mode;
|
int old_dark_mode = prefs.hex_gui_dark_mode;
|
||||||
|
mg_live_prefs live_changes;
|
||||||
|
|
||||||
|
memset (&live_changes, 0, sizeof (live_changes));
|
||||||
|
|
||||||
#define DIFF(a) (pr->a != prefs.a)
|
#define DIFF(a) (pr->a != prefs.a)
|
||||||
|
|
||||||
@@ -2301,40 +2311,49 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
if (DIFF (hex_gui_lang))
|
if (DIFF (hex_gui_lang))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
#endif
|
#endif
|
||||||
if (DIFF (hex_gui_compact))
|
/* server tabs are only created when a connection is opened, so
|
||||||
noapply = TRUE;
|
existing connections keep their current tabs until a restart;
|
||||||
if (DIFF (hex_gui_input_icon))
|
the channel tree indentation is still re-applied below */
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_input_nick))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_lagometer))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_mode_buttons_inline))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_icons))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_closebuttons))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_server))
|
if (DIFF (hex_gui_tab_server))
|
||||||
|
{
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_tab_small))
|
live_changes.chanview = TRUE;
|
||||||
noapply = TRUE;
|
}
|
||||||
|
|
||||||
|
if (DIFF (hex_gui_compact))
|
||||||
|
{
|
||||||
|
live_changes.chanview = TRUE;
|
||||||
|
live_changes.ulist_columns = TRUE;
|
||||||
|
}
|
||||||
|
if (DIFF (hex_gui_input_icon) || DIFF (hex_gui_input_nick))
|
||||||
|
live_changes.input_box = TRUE;
|
||||||
|
if (DIFF (hex_gui_lagometer) || DIFF (hex_gui_throttlemeter))
|
||||||
|
live_changes.meters = TRUE;
|
||||||
|
if (DIFF (hex_gui_mode_buttons_inline) || DIFF (hex_gui_topicbar_multiline))
|
||||||
|
live_changes.topic_bar = TRUE;
|
||||||
|
if (DIFF (hex_gui_tab_icons) || DIFF (hex_gui_tab_closebuttons) ||
|
||||||
|
DIFF (hex_gui_tab_small))
|
||||||
|
live_changes.chanview = TRUE;
|
||||||
if (DIFF (hex_gui_tab_sort))
|
if (DIFF (hex_gui_tab_sort))
|
||||||
noapply = TRUE;
|
live_changes.tab_resort = TRUE;
|
||||||
if (DIFF (hex_gui_tab_trunc))
|
if (DIFF (hex_gui_tab_trunc))
|
||||||
noapply = TRUE;
|
live_changes.tab_trunc = TRUE;
|
||||||
if (DIFF (hex_gui_throttlemeter))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_topicbar_multiline))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_ulist_count))
|
if (DIFF (hex_gui_ulist_count))
|
||||||
noapply = TRUE;
|
live_changes.ulist_count = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_icons))
|
if (DIFF (hex_gui_ulist_icons))
|
||||||
noapply = TRUE;
|
live_changes.ulist_rows = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_show_hosts))
|
if (DIFF (hex_gui_ulist_show_hosts))
|
||||||
noapply = TRUE;
|
{
|
||||||
|
live_changes.ulist_columns = TRUE;
|
||||||
|
live_changes.ulist_rows = TRUE; /* hostnames may be stale */
|
||||||
|
}
|
||||||
if (DIFF (hex_gui_ulist_sort))
|
if (DIFF (hex_gui_ulist_sort))
|
||||||
noapply = TRUE;
|
live_changes.ulist_sort = TRUE;
|
||||||
|
/* compare against the pre-dialog value: the slider previews the
|
||||||
|
opacity live on the current window only, so OK still needs to
|
||||||
|
apply it to the other windows */
|
||||||
|
if (pr->hex_gui_transparency != setup_transparency_snapshot)
|
||||||
|
live_changes.transparency = TRUE;
|
||||||
|
|
||||||
if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
|
if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
|
||||||
pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
|
pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
|
||||||
@@ -2378,6 +2397,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
|
|
||||||
theme_manager_dispatch_setup_apply (&event);
|
theme_manager_dispatch_setup_apply (&event);
|
||||||
|
|
||||||
|
mg_apply_live_prefs (&live_changes);
|
||||||
|
|
||||||
if (noapply)
|
if (noapply)
|
||||||
fe_message (_("Some settings were changed that require a"
|
fe_message (_("Some settings were changed that require a"
|
||||||
" restart to take full effect."), FE_MSG_WARN);
|
" restart to take full effect."), FE_MSG_WARN);
|
||||||
@@ -2410,6 +2431,9 @@ setup_ok_cb (GtkWidget *but, GtkWidget *win)
|
|||||||
if (save_result.success)
|
if (save_result.success)
|
||||||
{
|
{
|
||||||
theme_preferences_stage_commit ();
|
theme_preferences_stage_commit ();
|
||||||
|
/* the previewed values were accepted, so the close handler
|
||||||
|
must not revert them */
|
||||||
|
setup_transparency_snapshot = prefs.hex_gui_transparency;
|
||||||
gtk_widget_destroy (win);
|
gtk_widget_destroy (win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2472,6 +2496,24 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin)
|
|||||||
{
|
{
|
||||||
*swin = NULL;
|
*swin = NULL;
|
||||||
|
|
||||||
|
/* drop any pending transparency preview and undo the previewed
|
||||||
|
opacity when the dialog is cancelled; after OK the snapshot
|
||||||
|
was updated so this is a no-op */
|
||||||
|
if (setup_trans_apply_tag)
|
||||||
|
{
|
||||||
|
g_source_remove (setup_trans_apply_tag);
|
||||||
|
setup_trans_apply_tag = 0;
|
||||||
|
}
|
||||||
|
if (prefs.hex_gui_transparency != setup_transparency_snapshot)
|
||||||
|
{
|
||||||
|
mg_live_prefs revert_changes;
|
||||||
|
|
||||||
|
prefs.hex_gui_transparency = setup_transparency_snapshot;
|
||||||
|
memset (&revert_changes, 0, sizeof (revert_changes));
|
||||||
|
revert_changes.transparency = TRUE;
|
||||||
|
mg_apply_live_prefs (&revert_changes);
|
||||||
|
}
|
||||||
|
|
||||||
theme_preferences_stage_discard ();
|
theme_preferences_stage_discard ();
|
||||||
|
|
||||||
if (font_dialog)
|
if (font_dialog)
|
||||||
@@ -2493,6 +2535,7 @@ setup_open (void)
|
|||||||
memcpy (&setup_prefs, &prefs, sizeof (prefs));
|
memcpy (&setup_prefs, &prefs, sizeof (prefs));
|
||||||
|
|
||||||
color_change = FALSE;
|
color_change = FALSE;
|
||||||
|
setup_transparency_snapshot = prefs.hex_gui_transparency;
|
||||||
theme_preferences_stage_begin ();
|
theme_preferences_stage_begin ();
|
||||||
setup_window = setup_window_open ();
|
setup_window = setup_window_open ();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "../theme-access.h"
|
#include "../theme-access.h"
|
||||||
|
#include "../theme-css.h"
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
#include "../theme-runtime.h"
|
#include "../theme-runtime.h"
|
||||||
#include "../../xtext-color.h"
|
#include "../../xtext-color.h"
|
||||||
@@ -54,6 +55,13 @@ setup_apply_real (const ThemeChangedEvent *event)
|
|||||||
(void) event;
|
(void) event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gtkutil_append_font_css (GString *css, const PangoFontDescription *font_desc)
|
||||||
|
{
|
||||||
|
(void) css;
|
||||||
|
(void) font_desc;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
fe_dark_mode_is_enabled_for (unsigned int mode)
|
fe_dark_mode_is_enabled_for (unsigned int mode)
|
||||||
{
|
{
|
||||||
@@ -173,6 +181,14 @@ theme_gtk3_is_active (void)
|
|||||||
return stub_gtk3_active;
|
return stub_gtk3_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
(void) name;
|
||||||
|
(void) out_color;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
rgba_equal (const GdkRGBA *a, const GdkRGBA *b)
|
rgba_equal (const GdkRGBA *a, const GdkRGBA *b)
|
||||||
{
|
{
|
||||||
@@ -336,6 +352,79 @@ test_access_xtext_palette_widget_mapping_when_gtk3_active (void)
|
|||||||
g_object_unref (provider);
|
g_object_unref (provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_access_palette_css_does_not_feed_back_into_sampling (void)
|
||||||
|
{
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkWidget *label;
|
||||||
|
GtkCssProvider *theme1;
|
||||||
|
GtkCssProvider *theme2;
|
||||||
|
GdkScreen *screen;
|
||||||
|
XTextColor palette[2] = { 0 };
|
||||||
|
GdkRGBA old_bg;
|
||||||
|
GdkRGBA old_fg;
|
||||||
|
GdkRGBA expected;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_stubs ();
|
||||||
|
stub_gtk3_active = TRUE;
|
||||||
|
screen = gdk_screen_get_default ();
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
label = gtk_label_new ("mapped");
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), label);
|
||||||
|
gtk_widget_realize (window);
|
||||||
|
|
||||||
|
/* Old GTK3 theme active at screen level. */
|
||||||
|
theme1 = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme1,
|
||||||
|
"label { color: #112233; background-color: #445566; }"
|
||||||
|
"label:selected { color: #778899; background-color: #aabbcc; }",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme1), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
|
||||||
|
/* The app paints the sampled theme colors onto the widget, like the
|
||||||
|
* user list / channel tree / input box palette provider does. */
|
||||||
|
g_assert_true (gdk_rgba_parse (&old_bg, "#445566"));
|
||||||
|
g_assert_true (gdk_rgba_parse (&old_fg, "#112233"));
|
||||||
|
theme_css_apply_palette_widget (label, &old_bg, &old_fg, NULL);
|
||||||
|
|
||||||
|
/* The user switches to a new GTK3 theme. */
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (theme1));
|
||||||
|
theme2 = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme2,
|
||||||
|
"label { color: #00ff00; background-color: #ff0000; }"
|
||||||
|
"label:selected { color: #0000ff; background-color: #ffff00; }",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme2), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
gtk_style_context_reset_widgets (screen);
|
||||||
|
|
||||||
|
/* Re-sampling must see the new theme, not the palette colors the
|
||||||
|
* app applied for the old theme. */
|
||||||
|
theme_get_xtext_colors_for_widget (label, palette, G_N_ELEMENTS (palette));
|
||||||
|
|
||||||
|
g_assert_true (stub_last_gtk_map_valid);
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#00ff00"));
|
||||||
|
g_assert_true (rgba_close (&stub_last_gtk_map.text_foreground, &expected));
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#ff0000"));
|
||||||
|
g_assert_true (rgba_close (&stub_last_gtk_map.text_background, &expected));
|
||||||
|
|
||||||
|
/* Sampling must not permanently strip the palette class. */
|
||||||
|
g_assert_true (gtk_style_context_has_class (gtk_widget_get_style_context (label),
|
||||||
|
theme_css_palette_class_name ()));
|
||||||
|
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (theme2));
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
g_object_unref (theme1);
|
||||||
|
g_object_unref (theme2);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_access_dark_light_switch_affects_token_consumers (void)
|
test_access_dark_light_switch_affects_token_consumers (void)
|
||||||
{
|
{
|
||||||
@@ -363,6 +452,8 @@ main (int argc, char **argv)
|
|||||||
g_test_add_func ("/theme/access/xtext_palette_forwarding", test_access_xtext_palette_forwarding);
|
g_test_add_func ("/theme/access/xtext_palette_forwarding", test_access_xtext_palette_forwarding);
|
||||||
g_test_add_func ("/theme/access/xtext_palette_widget_mapping_when_gtk3_active",
|
g_test_add_func ("/theme/access/xtext_palette_widget_mapping_when_gtk3_active",
|
||||||
test_access_xtext_palette_widget_mapping_when_gtk3_active);
|
test_access_xtext_palette_widget_mapping_when_gtk3_active);
|
||||||
|
g_test_add_func ("/theme/access/palette_css_does_not_feed_back_into_sampling",
|
||||||
|
test_access_palette_css_does_not_feed_back_into_sampling);
|
||||||
g_test_add_func ("/theme/access/widget_style_forwarding", test_access_widget_style_forwarding);
|
g_test_add_func ("/theme/access/widget_style_forwarding", test_access_widget_style_forwarding);
|
||||||
g_test_add_func ("/theme/access/dark_light_switch_affects_token_consumers",
|
g_test_add_func ("/theme/access/dark_light_switch_affects_token_consumers",
|
||||||
test_access_dark_light_switch_affects_token_consumers);
|
test_access_dark_light_switch_affects_token_consumers);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "../../fe-gtk.h"
|
#include "../../fe-gtk.h"
|
||||||
|
|
||||||
#include "../theme-application.h"
|
#include "../theme-application.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../maingui.h"
|
#include "../../maingui.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
@@ -54,6 +55,19 @@ theme_css_reload_input_style (gboolean enabled, const PangoFontDescription *font
|
|||||||
css_reload_calls++;
|
css_reload_calls++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_policy_system_prefers_dark (void)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_policy_is_dark_mode_active (unsigned int mode)
|
||||||
|
{
|
||||||
|
(void) mode;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_runtime_load (void)
|
theme_runtime_load (void)
|
||||||
{
|
{
|
||||||
@@ -104,7 +118,9 @@ test_invalid_font_falls_back_to_sans_11 (void)
|
|||||||
g_assert_cmpint (pango_font_description_get_size (style->font_desc), ==, 11 * PANGO_SCALE);
|
g_assert_cmpint (pango_font_description_get_size (style->font_desc), ==, 11 * PANGO_SCALE);
|
||||||
g_assert_cmpint (message_calls, ==, 1);
|
g_assert_cmpint (message_calls, ==, 1);
|
||||||
g_assert_cmpint (css_reload_calls, ==, 1);
|
g_assert_cmpint (css_reload_calls, ==, 1);
|
||||||
g_assert_true (css_enabled);
|
/* Input boxes are themed per-widget; the app-global input CSS stays
|
||||||
|
* off so entries render natively under the active GTK3 theme. */
|
||||||
|
g_assert_false (css_enabled);
|
||||||
g_assert_nonnull (css_font_desc);
|
g_assert_nonnull (css_font_desc);
|
||||||
|
|
||||||
if (style->font_desc)
|
if (style->font_desc)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gstdio.h>
|
#include <glib/gstdio.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
#include "../theme-gtk3.h"
|
#include "../theme-gtk3.h"
|
||||||
#include "../../../common/gtk3-theme-service.h"
|
#include "../../../common/gtk3-theme-service.h"
|
||||||
@@ -39,11 +40,14 @@ static char *xdg_data_home;
|
|||||||
static char *theme_parent_root;
|
static char *theme_parent_root;
|
||||||
static char *theme_child_root;
|
static char *theme_child_root;
|
||||||
static char *theme_switch_root;
|
static char *theme_switch_root;
|
||||||
|
static char *theme_chrome_root;
|
||||||
|
|
||||||
|
static gboolean stub_system_prefers_dark;
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
theme_policy_system_prefers_dark (void)
|
theme_policy_system_prefers_dark (void)
|
||||||
{
|
{
|
||||||
return FALSE;
|
return stub_system_prefers_dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -131,6 +135,8 @@ zoitechat_gtk3_theme_find_by_id (const char *theme_id)
|
|||||||
return make_theme (theme_id, theme_child_root);
|
return make_theme (theme_id, theme_child_root);
|
||||||
if (g_strcmp0 (theme_id, "switch") == 0)
|
if (g_strcmp0 (theme_id, "switch") == 0)
|
||||||
return make_theme (theme_id, theme_switch_root);
|
return make_theme (theme_id, theme_switch_root);
|
||||||
|
if (g_strcmp0 (theme_id, "chrome") == 0)
|
||||||
|
return make_theme (theme_id, theme_chrome_root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +183,11 @@ zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root)
|
|||||||
g_ptr_array_add (chain, g_strdup (theme_switch_root));
|
g_ptr_array_add (chain, g_strdup (theme_switch_root));
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
if (g_strcmp0 (theme_root, theme_chrome_root) == 0)
|
||||||
|
{
|
||||||
|
g_ptr_array_add (chain, g_strdup (theme_chrome_root));
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
g_ptr_array_unref (chain);
|
g_ptr_array_unref (chain);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -247,6 +258,22 @@ setup_themes (void)
|
|||||||
"gtk-enable-animations=false\n"
|
"gtk-enable-animations=false\n"
|
||||||
"gtk-cursor-blink-time=444\n");
|
"gtk-cursor-blink-time=444\n");
|
||||||
|
|
||||||
|
theme_chrome_root = g_build_filename (temp_root, "chrome", NULL);
|
||||||
|
g_assert_cmpint (g_mkdir_with_parents (theme_chrome_root, 0700), ==, 0);
|
||||||
|
ensure_css_dir (theme_chrome_root, "gtk-3.24");
|
||||||
|
/* Defines named colors but no window/menubar/menu rules of its own,
|
||||||
|
* so all chrome styling must come from the backfill provider. */
|
||||||
|
{
|
||||||
|
char *css = g_build_filename (theme_chrome_root, "gtk-3.24", "gtk.css", NULL);
|
||||||
|
write_file (css,
|
||||||
|
"@define-color theme_bg_color #e8f4e8;\n"
|
||||||
|
"@define-color theme_fg_color #102010;\n"
|
||||||
|
"@define-color theme_selected_bg_color #3465a4;\n"
|
||||||
|
"@define-color theme_selected_fg_color #ffffff;\n"
|
||||||
|
"label { color: #102010; }\n");
|
||||||
|
g_free (css);
|
||||||
|
}
|
||||||
|
|
||||||
path = g_build_filename (theme_parent_root, "index.theme", NULL);
|
path = g_build_filename (theme_parent_root, "index.theme", NULL);
|
||||||
write_file (path, "[Desktop Entry]\nName=parent\n");
|
write_file (path, "[Desktop Entry]\nName=parent\n");
|
||||||
g_free (path);
|
g_free (path);
|
||||||
@@ -266,11 +293,13 @@ teardown_themes (void)
|
|||||||
g_free (theme_parent_root);
|
g_free (theme_parent_root);
|
||||||
g_free (theme_child_root);
|
g_free (theme_child_root);
|
||||||
g_free (theme_switch_root);
|
g_free (theme_switch_root);
|
||||||
|
g_free (theme_chrome_root);
|
||||||
g_free (xdg_data_home);
|
g_free (xdg_data_home);
|
||||||
g_free (temp_root);
|
g_free (temp_root);
|
||||||
theme_parent_root = NULL;
|
theme_parent_root = NULL;
|
||||||
theme_child_root = NULL;
|
theme_child_root = NULL;
|
||||||
theme_switch_root = NULL;
|
theme_switch_root = NULL;
|
||||||
|
theme_chrome_root = NULL;
|
||||||
xdg_data_home = NULL;
|
xdg_data_home = NULL;
|
||||||
temp_root = NULL;
|
temp_root = NULL;
|
||||||
}
|
}
|
||||||
@@ -343,6 +372,205 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
g_assert_false (theme_gtk3_is_active ());
|
g_assert_false (theme_gtk3_is_active ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_follow_system_variant_resolves_to_theme_variant (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An explicitly selected in-app theme must not follow the OS
|
||||||
|
* light/dark preference: the legacy FOLLOW_SYSTEM variant resolves
|
||||||
|
* to the variant the theme itself provides, so the window chrome
|
||||||
|
* (menu bar) stays on the theme even when the system prefers dark. */
|
||||||
|
stub_system_prefers_dark = TRUE;
|
||||||
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_FOLLOW_SYSTEM, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
g_assert_true (theme_gtk3_is_active ());
|
||||||
|
g_assert_cmpint (theme_gtk3_active_variant (), ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
||||||
|
g_assert_false (get_bool_setting ("gtk-application-prefer-dark-theme"));
|
||||||
|
|
||||||
|
stub_system_prefers_dark = FALSE;
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_menu_bar_follows_active_theme_colors (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkWidget *menubar;
|
||||||
|
GtkStyleContext *context;
|
||||||
|
GdkRGBA bg;
|
||||||
|
GdkRGBA expected;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The chrome theme defines no menubar rules; without the backfill
|
||||||
|
* provider the menu bar would fall through to the base (OS) theme
|
||||||
|
* and follow the OS dark/light mode instead of the selected theme. */
|
||||||
|
g_assert_true (theme_gtk3_apply ("chrome", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
menubar = gtk_menu_bar_new ();
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), menubar);
|
||||||
|
gtk_widget_show_all (window);
|
||||||
|
|
||||||
|
context = gtk_widget_get_style_context (menubar);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#e8f4e8"));
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - expected.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.green - expected.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.blue - expected.blue), <, 0.01);
|
||||||
|
|
||||||
|
/* The window background must follow the theme too, or the space
|
||||||
|
* around and beside the menu bar keeps the OS theme's colors. */
|
||||||
|
context = gtk_widget_get_style_context (window);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - expected.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.green - expected.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.blue - expected.blue), <, 0.01);
|
||||||
|
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_desktop_color_sync_does_not_leak_into_chrome (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkWidget *menubar;
|
||||||
|
GtkStyleContext *context;
|
||||||
|
GtkCssProvider *desktop_css;
|
||||||
|
GdkScreen *screen;
|
||||||
|
GdkRGBA bg;
|
||||||
|
GdkRGBA expected;
|
||||||
|
GdkRGBA injected;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KDE's GTK color-scheme sync (~/.config/gtk-3.0/gtk.css) redefines
|
||||||
|
* the standard named colors with the OS palette at USER priority.
|
||||||
|
* The switch theme defines no named colors of its own, so a naive
|
||||||
|
* @theme_bg_color reference in the chrome backfill would resolve to
|
||||||
|
* the injected OS dark palette; the backfill must use its neutral
|
||||||
|
* variant fallback instead. */
|
||||||
|
screen = gdk_screen_get_default ();
|
||||||
|
desktop_css = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (desktop_css,
|
||||||
|
"@define-color theme_bg_color #1e1e1e;\n"
|
||||||
|
"@define-color theme_fg_color #eeeeee;\n"
|
||||||
|
"menubar { background-color: #1e1e1e; }\n",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (desktop_css), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
|
||||||
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
|
||||||
|
{
|
||||||
|
GtkWidget *item;
|
||||||
|
GdkRGBA item_bg;
|
||||||
|
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
menubar = gtk_menu_bar_new ();
|
||||||
|
item = gtk_menu_item_new_with_label ("File");
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), item);
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), menubar);
|
||||||
|
gtk_widget_show_all (window);
|
||||||
|
|
||||||
|
context = gtk_widget_get_style_context (menubar);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#f6f5f4"));
|
||||||
|
g_assert_true (gdk_rgba_parse (&injected, "#1e1e1e"));
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - injected.red), >, 0.1);
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - expected.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.green - expected.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.blue - expected.blue), <, 0.01);
|
||||||
|
|
||||||
|
/* The menu bar body and the menu items must be one surface. */
|
||||||
|
context = gtk_widget_get_style_context (item);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &item_bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_cmpfloat (ABS (item_bg.red - bg.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (item_bg.green - bg.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (item_bg.blue - bg.blue), <, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (desktop_css));
|
||||||
|
g_object_unref (desktop_css);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_stale_theme_symlink_is_replaced (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
char *themes_dir;
|
||||||
|
char *link_path;
|
||||||
|
char *target;
|
||||||
|
char *active_theme_name = NULL;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!has_default_seat ())
|
||||||
|
{
|
||||||
|
g_test_message ("No default seat available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A dangling symlink left behind by a removed theme must not block
|
||||||
|
* pinning gtk-theme-name to the newly applied theme. */
|
||||||
|
themes_dir = g_build_filename (xdg_data_home, "themes", NULL);
|
||||||
|
g_assert_cmpint (g_mkdir_with_parents (themes_dir, 0700), ==, 0);
|
||||||
|
link_path = g_build_filename (themes_dir, "switch", NULL);
|
||||||
|
g_unlink (link_path);
|
||||||
|
{
|
||||||
|
GFile *link_file = g_file_new_for_path (link_path);
|
||||||
|
g_assert_true (g_file_make_symbolic_link (link_file, "/nonexistent/switch", NULL, NULL));
|
||||||
|
g_object_unref (link_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
|
||||||
|
target = g_file_read_link (link_path, NULL);
|
||||||
|
g_assert_cmpstr (target, ==, theme_switch_root);
|
||||||
|
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
||||||
|
g_assert_cmpstr (active_theme_name, ==, "switch");
|
||||||
|
|
||||||
|
g_free (active_theme_name);
|
||||||
|
g_free (target);
|
||||||
|
g_free (link_path);
|
||||||
|
g_free (themes_dir);
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
@@ -354,6 +582,14 @@ main (int argc, char **argv)
|
|||||||
|
|
||||||
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
|
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
|
||||||
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
|
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
|
||||||
|
g_test_add_func ("/theme/gtk3/follow_system_variant_resolves_to_theme_variant",
|
||||||
|
test_follow_system_variant_resolves_to_theme_variant);
|
||||||
|
g_test_add_func ("/theme/gtk3/menu_bar_follows_active_theme_colors",
|
||||||
|
test_menu_bar_follows_active_theme_colors);
|
||||||
|
g_test_add_func ("/theme/gtk3/desktop_color_sync_does_not_leak_into_chrome",
|
||||||
|
test_desktop_color_sync_does_not_leak_into_chrome);
|
||||||
|
g_test_add_func ("/theme/gtk3/stale_theme_symlink_is_replaced",
|
||||||
|
test_stale_theme_symlink_is_replaced);
|
||||||
|
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
||||||
|
|
||||||
|
|||||||
@@ -89,3 +89,9 @@ theme_gtk3_is_active (void)
|
|||||||
{
|
{
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeGtk3Variant
|
||||||
|
theme_gtk3_active_variant (void)
|
||||||
|
{
|
||||||
|
return THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
#include "../theme-gtk3.h"
|
#include "../theme-gtk3.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
|
|
||||||
@@ -84,6 +85,15 @@ gboolean theme_policy_system_prefers_dark (void)
|
|||||||
return stub_system_prefers_dark;
|
return stub_system_prefers_dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void theme_policy_init (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
(void) mode;
|
(void) mode;
|
||||||
@@ -203,6 +213,7 @@ reset_state (void)
|
|||||||
idle_add_calls = 0;
|
idle_add_calls = 0;
|
||||||
next_idle_source_id = 33;
|
next_idle_source_id = 33;
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
|
prefs.hex_gui_gtk3_theme[0] = '\0';
|
||||||
test_theme_gtk3_stub_reset ();
|
test_theme_gtk3_stub_reset ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,12 +246,14 @@ test_auto_refresh_dispatches_mode_palette_and_style_reasons (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_auto_refresh_ignores_non_auto_mode (void)
|
test_auto_refresh_ignores_locked_in_app_theme (void)
|
||||||
{
|
{
|
||||||
guint listener_id;
|
guint listener_id;
|
||||||
|
|
||||||
reset_state ();
|
reset_state ();
|
||||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme, "locked-theme", sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
||||||
stub_apply_mode_palette_changed = TRUE;
|
stub_apply_mode_palette_changed = TRUE;
|
||||||
listener_id = theme_listener_register ("auto.nonauto", auto_listener, NULL);
|
listener_id = theme_listener_register ("auto.nonauto", auto_listener, NULL);
|
||||||
theme_manager_set_idle_add_func (immediate_idle_add);
|
theme_manager_set_idle_add_func (immediate_idle_add);
|
||||||
@@ -254,6 +267,7 @@ test_auto_refresh_ignores_non_auto_mode (void)
|
|||||||
|
|
||||||
theme_manager_set_idle_add_func (NULL);
|
theme_manager_set_idle_add_func (NULL);
|
||||||
theme_listener_unregister (listener_id);
|
theme_listener_unregister (listener_id);
|
||||||
|
prefs.hex_gui_gtk3_theme[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -263,6 +277,7 @@ test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
|
|||||||
|
|
||||||
reset_state ();
|
reset_state ();
|
||||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme, "system-follower", sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL);
|
listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL);
|
||||||
theme_manager_set_idle_add_func (immediate_idle_add);
|
theme_manager_set_idle_add_func (immediate_idle_add);
|
||||||
@@ -274,6 +289,39 @@ test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
|
|||||||
g_assert_cmpint (listener_calls, ==, 0);
|
g_assert_cmpint (listener_calls, ==, 0);
|
||||||
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
|
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
|
||||||
|
|
||||||
|
theme_manager_set_idle_add_func (NULL);
|
||||||
|
theme_listener_unregister (listener_id);
|
||||||
|
prefs.hex_gui_gtk3_theme[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_auto_refresh_follows_system_theme_without_auto_mode (void)
|
||||||
|
{
|
||||||
|
guint listener_id;
|
||||||
|
|
||||||
|
reset_state ();
|
||||||
|
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||||
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
||||||
|
listener_id = theme_listener_register ("auto.system", auto_listener, NULL);
|
||||||
|
theme_manager_set_idle_add_func (immediate_idle_add);
|
||||||
|
|
||||||
|
theme_manager_refresh_auto_mode ();
|
||||||
|
|
||||||
|
/* No in-app theme is selected, so a system theme change must
|
||||||
|
* re-resolve the mapped palette even outside auto dark mode. */
|
||||||
|
g_assert_cmpint (idle_add_calls, ==, 1);
|
||||||
|
g_assert_cmpint (auto_state_calls, ==, 0);
|
||||||
|
g_assert_cmpint (listener_calls, ==, 1);
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_PALETTE));
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_WIDGET_STYLE));
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_USERLIST));
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_MODE));
|
||||||
|
|
||||||
|
/* Unchanged system state must not dispatch again. */
|
||||||
|
theme_manager_refresh_auto_mode ();
|
||||||
|
g_assert_cmpint (idle_add_calls, ==, 2);
|
||||||
|
g_assert_cmpint (listener_calls, ==, 1);
|
||||||
|
|
||||||
theme_manager_set_idle_add_func (NULL);
|
theme_manager_set_idle_add_func (NULL);
|
||||||
theme_listener_unregister (listener_id);
|
theme_listener_unregister (listener_id);
|
||||||
}
|
}
|
||||||
@@ -284,9 +332,11 @@ main (int argc, char **argv)
|
|||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
g_test_add_func ("/theme/manager/auto_refresh_dispatches_mode_palette_and_style_reasons",
|
g_test_add_func ("/theme/manager/auto_refresh_dispatches_mode_palette_and_style_reasons",
|
||||||
test_auto_refresh_dispatches_mode_palette_and_style_reasons);
|
test_auto_refresh_dispatches_mode_palette_and_style_reasons);
|
||||||
g_test_add_func ("/theme/manager/auto_refresh_ignores_non_auto_mode",
|
g_test_add_func ("/theme/manager/auto_refresh_ignores_locked_in_app_theme",
|
||||||
test_auto_refresh_ignores_non_auto_mode);
|
test_auto_refresh_ignores_locked_in_app_theme);
|
||||||
g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant",
|
g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant",
|
||||||
test_auto_refresh_reapplies_gtk3_for_follow_system_variant);
|
test_auto_refresh_reapplies_gtk3_for_follow_system_variant);
|
||||||
|
g_test_add_func ("/theme/manager/auto_refresh_follows_system_theme_without_auto_mode",
|
||||||
|
test_auto_refresh_follows_system_theme_without_auto_mode);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
|
|
||||||
@@ -77,6 +78,15 @@ gboolean theme_policy_system_prefers_dark (void)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void theme_policy_init (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
(void) mode;
|
(void) mode;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "../theme-palette.h"
|
#include "../theme-palette.h"
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ struct session *current_tab;
|
|||||||
struct session *lastact_sess;
|
struct session *lastact_sess;
|
||||||
struct zoitechatprefs prefs;
|
struct zoitechatprefs prefs;
|
||||||
|
|
||||||
|
static gboolean gtk_available;
|
||||||
static gboolean stub_policy_dark;
|
static gboolean stub_policy_dark;
|
||||||
static unsigned int stub_policy_mode;
|
static unsigned int stub_policy_mode;
|
||||||
static gboolean stub_apply_mode_result;
|
static gboolean stub_apply_mode_result;
|
||||||
@@ -79,7 +81,18 @@ void zoitechat_set_theme_post_apply_callback (zoitechat_theme_post_apply_callbac
|
|||||||
gboolean theme_policy_is_dark_mode_active (unsigned int mode)
|
gboolean theme_policy_is_dark_mode_active (unsigned int mode)
|
||||||
{
|
{
|
||||||
stub_policy_mode = mode;
|
stub_policy_mode = mode;
|
||||||
return stub_policy_dark;
|
if (mode == ZOITECHAT_DARK_MODE_AUTO)
|
||||||
|
return stub_policy_dark;
|
||||||
|
return fe_dark_mode_is_enabled_for (mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_init (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
@@ -109,11 +122,12 @@ void theme_runtime_reset_mode_colors (gboolean dark_mode)
|
|||||||
(void) dark_mode;
|
(void) dark_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active)
|
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
(void) mode;
|
(void) mode;
|
||||||
(void) dark_active;
|
|
||||||
stub_apply_mode_calls++;
|
stub_apply_mode_calls++;
|
||||||
|
if (palette_changed)
|
||||||
|
*palette_changed = TRUE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,10 +263,11 @@ test_manager_set_token_color_routes_by_mode (void)
|
|||||||
GdkRGBA color = { 0.1, 0.2, 0.3, 1.0 };
|
GdkRGBA color = { 0.1, 0.2, 0.3, 1.0 };
|
||||||
gboolean palette_changed = FALSE;
|
gboolean palette_changed = FALSE;
|
||||||
|
|
||||||
|
/* All edits target the single user palette; the mode argument is
|
||||||
|
* accepted for compatibility but no longer selects a dark palette. */
|
||||||
reset_manager_stubs ();
|
reset_manager_stubs ();
|
||||||
stub_policy_dark = FALSE;
|
stub_policy_dark = FALSE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_LIGHT);
|
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_cmpint (stub_apply_mode_calls, ==, 1);
|
g_assert_cmpint (stub_apply_mode_calls, ==, 1);
|
||||||
@@ -262,16 +277,14 @@ test_manager_set_token_color_routes_by_mode (void)
|
|||||||
reset_manager_stubs ();
|
reset_manager_stubs ();
|
||||||
stub_policy_dark = TRUE;
|
stub_policy_dark = TRUE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_DARK);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 1);
|
|
||||||
|
|
||||||
reset_manager_stubs ();
|
reset_manager_stubs ();
|
||||||
stub_policy_dark = TRUE;
|
stub_policy_dark = TRUE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_AUTO, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_AUTO, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_AUTO);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -300,9 +313,9 @@ test_manager_set_token_color_routes_setup_indexes (void)
|
|||||||
stub_policy_dark = TRUE;
|
stub_policy_dark = TRUE;
|
||||||
palette_changed = FALSE;
|
palette_changed = FALSE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, def->token, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, def->token, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 1);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_last_dark_token, ==, def->token);
|
g_assert_cmpint (stub_last_user_token, ==, def->token);
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_true (palette_changed);
|
g_assert_true (palette_changed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +358,12 @@ test_manager_window_attach_detach_idempotence (void)
|
|||||||
gulong *second_handler_ptr;
|
gulong *second_handler_ptr;
|
||||||
gulong second_handler_id;
|
gulong second_handler_id;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
g_assert_nonnull (window);
|
g_assert_nonnull (window);
|
||||||
|
|
||||||
@@ -395,5 +414,6 @@ main (int argc, char **argv)
|
|||||||
test_manager_listener_registration_dispatch_and_unregister);
|
test_manager_listener_registration_dispatch_and_unregister);
|
||||||
g_test_add_func ("/theme/manager/window_attach_detach_idempotence",
|
g_test_add_func ("/theme/manager/window_attach_detach_idempotence",
|
||||||
test_manager_window_attach_detach_idempotence);
|
test_manager_window_attach_detach_idempotence);
|
||||||
|
gtk_available = gtk_init_check (&argc, &argv);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ InputStyle *input_style;
|
|||||||
|
|
||||||
static gboolean gtk_available;
|
static gboolean gtk_available;
|
||||||
static int apply_current_calls;
|
static int apply_current_calls;
|
||||||
|
static int set_token_color_calls;
|
||||||
static char applied_theme_id[256];
|
static char applied_theme_id[256];
|
||||||
static ThemeGtk3Variant applied_variant;
|
static ThemeGtk3Variant applied_variant;
|
||||||
static gboolean removed_selected;
|
static gboolean removed_selected;
|
||||||
@@ -104,6 +105,7 @@ theme_manager_set_token_color (unsigned int dark_mode, ThemeSemanticToken token,
|
|||||||
(void)dark_mode;
|
(void)dark_mode;
|
||||||
(void)token;
|
(void)token;
|
||||||
(void)color;
|
(void)color;
|
||||||
|
set_token_color_calls++;
|
||||||
if (changed)
|
if (changed)
|
||||||
*changed = FALSE;
|
*changed = FALSE;
|
||||||
}
|
}
|
||||||
@@ -309,7 +311,7 @@ theme_gtk3_is_active (void)
|
|||||||
#include "../theme-preferences.c"
|
#include "../theme-preferences.c"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_removed_selected_theme_commits_fallback_and_applies (void)
|
test_removed_selected_theme_falls_back_to_system_default (void)
|
||||||
{
|
{
|
||||||
GtkWidget *page;
|
GtkWidget *page;
|
||||||
theme_preferences_ui *ui;
|
theme_preferences_ui *ui;
|
||||||
@@ -326,23 +328,26 @@ test_removed_selected_theme_commits_fallback_and_applies (void)
|
|||||||
g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme));
|
g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK;
|
||||||
removed_selected = FALSE;
|
removed_selected = FALSE;
|
||||||
apply_current_calls = 0;
|
|
||||||
applied_theme_id[0] = '\0';
|
|
||||||
|
|
||||||
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
|
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
|
||||||
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
||||||
g_assert_nonnull (ui);
|
g_assert_nonnull (ui);
|
||||||
|
|
||||||
|
apply_current_calls = 0;
|
||||||
|
applied_theme_id[0] = '\0';
|
||||||
|
|
||||||
g_assert_nonnull (ui->gtk3_remove);
|
g_assert_nonnull (ui->gtk3_remove);
|
||||||
gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove));
|
gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove));
|
||||||
|
|
||||||
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
|
/* Removing the active in-app theme returns to the system GTK3
|
||||||
g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
|
* theme ("None") instead of forcing another theme. */
|
||||||
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "");
|
||||||
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "");
|
||||||
|
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
|
||||||
|
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
|
||||||
g_assert_cmpint (apply_current_calls, ==, 1);
|
g_assert_cmpint (apply_current_calls, ==, 1);
|
||||||
g_assert_cmpstr (applied_theme_id, ==, "fallback-theme");
|
g_assert_cmpstr (applied_theme_id, ==, "");
|
||||||
g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
|
||||||
|
|
||||||
gtk_widget_destroy (page);
|
gtk_widget_destroy (page);
|
||||||
}
|
}
|
||||||
@@ -416,16 +421,55 @@ test_select_none_resets_theme_and_applies (void)
|
|||||||
gtk_widget_destroy (page);
|
gtk_widget_destroy (page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_select_theme_does_not_pin_palette_tokens (void)
|
||||||
|
{
|
||||||
|
GtkWidget *page;
|
||||||
|
theme_preferences_ui *ui;
|
||||||
|
struct zoitechatprefs setup_prefs;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (&setup_prefs, 0, sizeof (setup_prefs));
|
||||||
|
memset (&prefs, 0, sizeof (prefs));
|
||||||
|
removed_selected = FALSE;
|
||||||
|
apply_current_calls = 0;
|
||||||
|
set_token_color_calls = 0;
|
||||||
|
|
||||||
|
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
|
||||||
|
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
||||||
|
g_assert_nonnull (ui);
|
||||||
|
|
||||||
|
/* 0 = None, 1 = removed-theme, 2 = fallback-theme */
|
||||||
|
gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), 2);
|
||||||
|
|
||||||
|
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
|
||||||
|
g_assert_cmpint (apply_current_calls, ==, 1);
|
||||||
|
/* Selecting a GTK3 theme must leave the mapped palette tokens
|
||||||
|
* unpinned so the user list, channel tree and input box keep
|
||||||
|
* resolving their colors from the active theme instead of being
|
||||||
|
* frozen until the next restart. */
|
||||||
|
g_assert_cmpint (set_token_color_calls, ==, 0);
|
||||||
|
|
||||||
|
gtk_widget_destroy (page);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
gtk_available = gtk_init_check (&argc, &argv);
|
gtk_available = gtk_init_check (&argc, &argv);
|
||||||
g_test_add_func ("/theme/preferences/gtk3_removed_selection_applies_fallback",
|
g_test_add_func ("/theme/preferences/gtk3_removed_selection_falls_back_to_system",
|
||||||
test_removed_selected_theme_commits_fallback_and_applies);
|
test_removed_selected_theme_falls_back_to_system_default);
|
||||||
g_test_add_func ("/theme/preferences/gtk3_unset_keeps_system_default",
|
g_test_add_func ("/theme/preferences/gtk3_unset_keeps_system_default",
|
||||||
test_unset_theme_keeps_system_default_without_apply);
|
test_unset_theme_keeps_system_default_without_apply);
|
||||||
g_test_add_func ("/theme/preferences/gtk3_select_none_resets_theme",
|
g_test_add_func ("/theme/preferences/gtk3_select_none_resets_theme",
|
||||||
test_select_none_resets_theme_and_applies);
|
test_select_none_resets_theme_and_applies);
|
||||||
|
g_test_add_func ("/theme/preferences/gtk3_select_theme_does_not_pin_palette_tokens",
|
||||||
|
test_select_theme_does_not_pin_palette_tokens);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,12 +290,12 @@ test_ui_edits_persist_without_legacy_array_mutation (void)
|
|||||||
theme_runtime_save ();
|
theme_runtime_save ();
|
||||||
|
|
||||||
theme_runtime_load ();
|
theme_runtime_load ();
|
||||||
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL);
|
theme_runtime_apply_dark_mode (FALSE);
|
||||||
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &light_loaded));
|
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &light_loaded));
|
||||||
g_assert_true (gdk_rgba_parse (&light_expected, "#224466"));
|
g_assert_true (gdk_rgba_parse (&light_expected, "#224466"));
|
||||||
g_assert_true (colors_equal (&light_loaded, &light_expected));
|
g_assert_true (colors_equal (&light_loaded, &light_expected));
|
||||||
|
|
||||||
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_DARK, NULL);
|
theme_runtime_apply_dark_mode (TRUE);
|
||||||
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &dark_loaded));
|
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &dark_loaded));
|
||||||
g_assert_true (gdk_rgba_parse (&dark_expected, "#88aacc"));
|
g_assert_true (gdk_rgba_parse (&dark_expected, "#88aacc"));
|
||||||
g_assert_true (colors_equal (&dark_loaded, &dark_expected));
|
g_assert_true (colors_equal (&dark_loaded, &dark_expected));
|
||||||
|
|||||||
@@ -20,8 +20,9 @@
|
|||||||
|
|
||||||
#include "theme-access.h"
|
#include "theme-access.h"
|
||||||
|
|
||||||
#include "theme-runtime.h"
|
#include "theme-css.h"
|
||||||
#include "theme-gtk3.h"
|
#include "theme-gtk3.h"
|
||||||
|
#include "theme-runtime.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +33,8 @@ enum
|
|||||||
THEME_XTEXT_MARK_BG_INDEX = 100,
|
THEME_XTEXT_MARK_BG_INDEX = 100,
|
||||||
THEME_XTEXT_FG_INDEX = 101,
|
THEME_XTEXT_FG_INDEX = 101,
|
||||||
THEME_XTEXT_BG_INDEX = 102,
|
THEME_XTEXT_BG_INDEX = 102,
|
||||||
THEME_XTEXT_MARKER_INDEX = 103
|
THEME_XTEXT_MARKER_INDEX = 103,
|
||||||
|
THEME_XTEXT_MARKER_LEGACY_INDEX = 36
|
||||||
};
|
};
|
||||||
|
|
||||||
static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
|
static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
|
||||||
@@ -111,28 +113,95 @@ theme_access_context_get_background_color (GtkStyleContext *context, GtkStateFla
|
|||||||
gtk_style_context_restore (context);
|
gtk_style_context_restore (context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
theme_access_lookup_named_color (GtkStyleContext *context, const char *const *names, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
/* With an in-app GTK3 theme selected, prefer the colors resolved from
|
||||||
|
* the theme itself: desktop integration CSS (e.g. KDE's color-scheme
|
||||||
|
* sync) redefines the same named colors with the OS palette in the
|
||||||
|
* widget cascade, which would drag OS dark/light colors into the
|
||||||
|
* mapped palette. */
|
||||||
|
for (i = 0; names[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
if (theme_gtk3_lookup_theme_color (names[i], out_color))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; names[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
if (gtk_style_context_lookup_color (context, names[i], out_color))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_access_resolve_map_background (GtkStyleContext *context, GtkStateFlags state,
|
||||||
|
const char *const *named_fallbacks, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
GdkRGBA named;
|
||||||
|
|
||||||
|
theme_access_context_get_background_color (context, state, out_color);
|
||||||
|
if (out_color->alpha > 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Unstyled CSS nodes (e.g. the custom chat text widget) report a fully
|
||||||
|
* transparent background; fall back to the theme's named colors so the
|
||||||
|
* mapped palette still follows the active GTK3 theme. */
|
||||||
|
if (theme_access_lookup_named_color (context, named_fallbacks, &named))
|
||||||
|
*out_color = named;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map)
|
theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map)
|
||||||
{
|
{
|
||||||
|
static const char *const base_fallbacks[] = { "theme_base_color", "theme_bg_color", NULL };
|
||||||
|
static const char *const selected_bg_fallbacks[] = { "theme_selected_bg_color", NULL };
|
||||||
|
static const char *const selected_fg_fallbacks[] = { "theme_selected_fg_color", NULL };
|
||||||
GtkStyleContext *context;
|
GtkStyleContext *context;
|
||||||
|
const char *palette_class;
|
||||||
|
gboolean had_palette_class;
|
||||||
|
GdkRGBA named;
|
||||||
GdkRGBA accent;
|
GdkRGBA accent;
|
||||||
|
|
||||||
g_return_val_if_fail (out_map != NULL, FALSE);
|
g_return_val_if_fail (out_map != NULL, FALSE);
|
||||||
if (!theme_gtk3_is_active () || widget == NULL || !GTK_IS_WIDGET (widget))
|
if (widget == NULL || !GTK_IS_WIDGET (widget))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
context = gtk_widget_get_style_context (widget);
|
context = gtk_widget_get_style_context (widget);
|
||||||
if (context == NULL)
|
if (context == NULL)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
|
/* The application paints these widgets with fixed colors through the
|
||||||
|
* zoitechat-palette provider. Sampling must see the underlying GTK3
|
||||||
|
* theme, not the colors we applied earlier: otherwise every theme
|
||||||
|
* change reads back the previous palette and the user list, channel
|
||||||
|
* tree and input box keep their stale colors until a restart. */
|
||||||
|
palette_class = theme_css_palette_class_name ();
|
||||||
|
had_palette_class = gtk_style_context_has_class (context, palette_class);
|
||||||
|
if (had_palette_class)
|
||||||
|
gtk_style_context_remove_class (context, palette_class);
|
||||||
|
|
||||||
theme_access_context_get_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_foreground);
|
theme_access_context_get_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_foreground);
|
||||||
theme_access_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_background);
|
theme_access_resolve_map_background (context, GTK_STATE_FLAG_NORMAL, base_fallbacks,
|
||||||
|
&out_map->text_background);
|
||||||
theme_access_context_get_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_foreground);
|
theme_access_context_get_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_foreground);
|
||||||
theme_access_context_get_background_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_background);
|
theme_access_resolve_map_background (context, GTK_STATE_FLAG_SELECTED, selected_bg_fallbacks,
|
||||||
|
&out_map->selection_background);
|
||||||
|
if (gdk_rgba_equal (&out_map->selection_foreground, &out_map->text_foreground) &&
|
||||||
|
theme_access_lookup_named_color (context, selected_fg_fallbacks, &named))
|
||||||
|
out_map->selection_foreground = named;
|
||||||
theme_access_context_get_color (context, GTK_STATE_FLAG_LINK, &accent);
|
theme_access_context_get_color (context, GTK_STATE_FLAG_LINK, &accent);
|
||||||
if (accent.alpha <= 0.0)
|
if (accent.alpha <= 0.0)
|
||||||
accent = out_map->selection_background;
|
accent = out_map->selection_background;
|
||||||
out_map->accent = accent;
|
out_map->accent = accent;
|
||||||
|
|
||||||
|
if (had_palette_class)
|
||||||
|
gtk_style_context_add_class (context, palette_class);
|
||||||
|
|
||||||
out_map->enabled = TRUE;
|
out_map->enabled = TRUE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -223,14 +292,35 @@ theme_get_xtext_colors (XTextColor *palette, size_t palette_len)
|
|||||||
void
|
void
|
||||||
theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_t palette_len)
|
theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_t palette_len)
|
||||||
{
|
{
|
||||||
|
ThemeGtkPaletteMap gtk_map = { 0 };
|
||||||
ThemeWidgetStyleValues style_values;
|
ThemeWidgetStyleValues style_values;
|
||||||
GdkRGBA marker_color;
|
GdkRGBA marker_color;
|
||||||
|
gboolean have_marker = FALSE;
|
||||||
|
|
||||||
if (!palette)
|
if (!palette)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_get_widget_style_values_for_widget (widget, &style_values);
|
if (theme_access_get_gtk_palette_map (widget, >k_map))
|
||||||
theme_runtime_get_xtext_colors (palette, palette_len);
|
{
|
||||||
|
theme_runtime_get_widget_style_values_mapped (>k_map, &style_values);
|
||||||
|
theme_runtime_get_xtext_colors_mapped (>k_map, palette, palette_len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
theme_runtime_get_widget_style_values (&style_values);
|
||||||
|
theme_runtime_get_xtext_colors (palette, palette_len);
|
||||||
|
}
|
||||||
|
if (palette_len > THEME_XTEXT_MARKER_LEGACY_INDEX)
|
||||||
|
{
|
||||||
|
/* The marker token lives at its legacy slot before the extended
|
||||||
|
* mIRC palette overwrites it; keep the (possibly theme-mapped)
|
||||||
|
* value for the dedicated marker index below. */
|
||||||
|
marker_color.red = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].red;
|
||||||
|
marker_color.green = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].green;
|
||||||
|
marker_color.blue = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].blue;
|
||||||
|
marker_color.alpha = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].alpha;
|
||||||
|
have_marker = TRUE;
|
||||||
|
}
|
||||||
if (palette_len >= THEME_XTEXT_MIRC_COLS)
|
if (palette_len >= THEME_XTEXT_MIRC_COLS)
|
||||||
theme_access_apply_default_99_palette (palette, palette_len);
|
theme_access_apply_default_99_palette (palette, palette_len);
|
||||||
if (palette_len > THEME_XTEXT_MARK_FG_INDEX)
|
if (palette_len > THEME_XTEXT_MARK_FG_INDEX)
|
||||||
@@ -249,7 +339,7 @@ theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_
|
|||||||
}
|
}
|
||||||
if (palette_len > THEME_XTEXT_MARKER_INDEX)
|
if (palette_len > THEME_XTEXT_MARKER_INDEX)
|
||||||
{
|
{
|
||||||
if (!theme_runtime_get_color (THEME_TOKEN_MARKER, &marker_color))
|
if (!have_marker && !theme_runtime_get_color (THEME_TOKEN_MARKER, &marker_color))
|
||||||
marker_color = style_values.selection_background;
|
marker_color = style_values.selection_background;
|
||||||
palette[THEME_XTEXT_MARKER_INDEX].red = marker_color.red;
|
palette[THEME_XTEXT_MARKER_INDEX].red = marker_color.red;
|
||||||
palette[THEME_XTEXT_MARKER_INDEX].green = marker_color.green;
|
palette[THEME_XTEXT_MARKER_INDEX].green = marker_color.green;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "theme-css.h"
|
#include "theme-css.h"
|
||||||
#include "theme-runtime.h"
|
#include "theme-runtime.h"
|
||||||
#include "theme-gtk3.h"
|
#include "theme-gtk3.h"
|
||||||
|
#include "theme-policy.h"
|
||||||
#include "../maingui.h"
|
#include "../maingui.h"
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
@@ -55,10 +56,19 @@ theme_application_apply_toplevel_theme (gboolean dark)
|
|||||||
|
|
||||||
if (theme_gtk3_is_active ())
|
if (theme_gtk3_is_active ())
|
||||||
{
|
{
|
||||||
if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_DARK)
|
/* An explicitly selected in-app theme controls its own
|
||||||
prefer_dark = TRUE;
|
* variant; the OS light/dark preference must not leak into
|
||||||
else if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_LIGHT)
|
* the window chrome (menu bar, dialogs). The active variant
|
||||||
prefer_dark = FALSE;
|
* is always resolved to light or dark. */
|
||||||
|
prefer_dark = theme_gtk3_active_variant () == THEME_GTK3_VARIANT_PREFER_DARK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Following the system GTK3 theme: only request the dark
|
||||||
|
* variant when the dark-mode preference (or the system, in
|
||||||
|
* auto mode) asks for it, instead of clobbering the value
|
||||||
|
* that came from the user's GTK configuration. */
|
||||||
|
prefer_dark = theme_policy_is_dark_mode_active (prefs.hex_gui_dark_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings && g_object_class_find_property (G_OBJECT_GET_CLASS (settings),
|
if (settings && g_object_class_find_property (G_OBJECT_GET_CLASS (settings),
|
||||||
|
|||||||
@@ -29,8 +29,6 @@
|
|||||||
static const char *theme_css_selector_input = "#zoitechat-inputbox";
|
static const char *theme_css_selector_input = "#zoitechat-inputbox";
|
||||||
static const char *theme_css_selector_input_text = "#zoitechat-inputbox text";
|
static const char *theme_css_selector_input_text = "#zoitechat-inputbox text";
|
||||||
static const char *theme_css_selector_palette_class = "zoitechat-palette";
|
static const char *theme_css_selector_palette_class = "zoitechat-palette";
|
||||||
static const char *theme_css_selector_dark_class = "zoitechat-dark";
|
|
||||||
static const char *theme_css_selector_light_class = "zoitechat-light";
|
|
||||||
static const char *theme_css_palette_provider_key = "zoitechat-palette-provider";
|
static const char *theme_css_palette_provider_key = "zoitechat-palette-provider";
|
||||||
static const guint theme_css_provider_priority = GTK_STYLE_PROVIDER_PRIORITY_USER;
|
static const guint theme_css_provider_priority = GTK_STYLE_PROVIDER_PRIORITY_USER;
|
||||||
static const char *theme_css_palette_scope_selectors =
|
static const char *theme_css_palette_scope_selectors =
|
||||||
@@ -62,6 +60,12 @@ typedef struct
|
|||||||
static GtkCssProvider *theme_css_input_provider;
|
static GtkCssProvider *theme_css_input_provider;
|
||||||
static ThemeCssInputFingerprint theme_css_input_fp;
|
static ThemeCssInputFingerprint theme_css_input_fp;
|
||||||
|
|
||||||
|
const char *
|
||||||
|
theme_css_palette_class_name (void)
|
||||||
|
{
|
||||||
|
return theme_css_selector_palette_class;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_css_apply_app_provider (GtkStyleProvider *provider)
|
theme_css_apply_app_provider (GtkStyleProvider *provider)
|
||||||
{
|
{
|
||||||
@@ -362,61 +366,9 @@ theme_css_apply_palette_widget (GtkWidget *widget, const GdkRGBA *bg, const GdkR
|
|||||||
char *
|
char *
|
||||||
theme_css_build_toplevel_classes (void)
|
theme_css_build_toplevel_classes (void)
|
||||||
{
|
{
|
||||||
return g_strdup_printf (
|
/* Windows, dialogs and menus must render with the colors of the active
|
||||||
"window.%s, window.%s:backdrop, .%s {"
|
* GTK3 theme (system or in-app selected). zoitechat-dark and
|
||||||
"background-color: #202020;"
|
* zoitechat-light are marker classes only; painting fixed colors here
|
||||||
"color: #f0f0f0;"
|
* overrides every theme (issue #334). */
|
||||||
"border-color: #202020;"
|
return g_strdup ("");
|
||||||
"}"
|
|
||||||
"window.%s menubar, window.%s menubar:backdrop, window.%s menubar box, window.%s menubar box:backdrop, window.%s menuitem, window.%s menuitem:backdrop {"
|
|
||||||
"background-color: @theme_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_fg_color;"
|
|
||||||
"border-color: @theme_bg_color;"
|
|
||||||
"}"
|
|
||||||
"window.%s menuitem:hover, window.%s menuitem:selected {"
|
|
||||||
"background-color: @theme_selected_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_selected_fg_color;"
|
|
||||||
"border-color: @theme_selected_bg_color;"
|
|
||||||
"}"
|
|
||||||
"window.%s, window.%s:backdrop, .%s {"
|
|
||||||
"background-color: #f6f6f6;"
|
|
||||||
"color: #101010;"
|
|
||||||
"border-color: #f6f6f6;"
|
|
||||||
"}"
|
|
||||||
"window.%s menubar, window.%s menubar:backdrop, window.%s menubar box, window.%s menubar box:backdrop, window.%s menuitem, window.%s menuitem:backdrop {"
|
|
||||||
"background-color: @theme_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_fg_color;"
|
|
||||||
"border-color: @theme_bg_color;"
|
|
||||||
"}"
|
|
||||||
"window.%s menuitem:hover, window.%s menuitem:selected {"
|
|
||||||
"background-color: @theme_selected_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_selected_fg_color;"
|
|
||||||
"border-color: @theme_selected_bg_color;"
|
|
||||||
"}",
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
* theme_css_apply_widget_provider:
|
* theme_css_apply_widget_provider:
|
||||||
* Use for widget-local CSS providers attached to a specific widget context.
|
* Use for widget-local CSS providers attached to a specific widget context.
|
||||||
*/
|
*/
|
||||||
|
const char *theme_css_palette_class_name (void);
|
||||||
void theme_css_apply_app_provider (GtkStyleProvider *provider);
|
void theme_css_apply_app_provider (GtkStyleProvider *provider);
|
||||||
void theme_css_remove_app_provider (GtkStyleProvider *provider);
|
void theme_css_remove_app_provider (GtkStyleProvider *provider);
|
||||||
void theme_css_apply_widget_provider (GtkWidget *widget, GtkStyleProvider *provider);
|
void theme_css_apply_widget_provider (GtkWidget *widget, GtkStyleProvider *provider);
|
||||||
|
|||||||
@@ -33,11 +33,60 @@
|
|||||||
|
|
||||||
static GPtrArray *theme_gtk3_providers_base;
|
static GPtrArray *theme_gtk3_providers_base;
|
||||||
static GPtrArray *theme_gtk3_providers_variant;
|
static GPtrArray *theme_gtk3_providers_variant;
|
||||||
|
static GtkCssProvider *theme_gtk3_chrome_provider;
|
||||||
|
static GHashTable *theme_gtk3_theme_colors;
|
||||||
static GHashTable *theme_gtk3_provider_cache;
|
static GHashTable *theme_gtk3_provider_cache;
|
||||||
static gboolean theme_gtk3_active;
|
static gboolean theme_gtk3_active;
|
||||||
static char *theme_gtk3_current_id;
|
static char *theme_gtk3_current_id;
|
||||||
static ThemeGtk3Variant theme_gtk3_current_variant;
|
static ThemeGtk3Variant theme_gtk3_current_variant;
|
||||||
|
|
||||||
|
/* Menu bar and menus fall back to the base gtk-theme-name theme (the OS
|
||||||
|
* theme) for any property the selected in-app theme's CSS does not define,
|
||||||
|
* which mixes OS dark/light colors into the chrome (issue: dark menu bar on
|
||||||
|
* a light in-app theme when the OS is in dark mode). While an in-app theme
|
||||||
|
* is active, backfill those widgets with the theme's own named colors. The
|
||||||
|
* provider sits just below the theme providers, so a theme that styles its
|
||||||
|
* menus keeps full control. */
|
||||||
|
#define THEME_GTK3_CHROME_PRIORITY (GTK_STYLE_PROVIDER_PRIORITY_USER - 1)
|
||||||
|
/* Concrete color values are formatted in: desktop integration CSS (e.g.
|
||||||
|
* KDE's color-scheme sync in ~/.config/gtk-3.0/gtk.css) redefines the
|
||||||
|
* standard named colors with the OS palette at USER priority, so a
|
||||||
|
* "@theme_bg_color" reference here would resolve to the OS dark/light
|
||||||
|
* colors instead of the selected theme's. */
|
||||||
|
static const char theme_gtk3_chrome_css_template[] =
|
||||||
|
".background, .background:backdrop {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" color: %s;"
|
||||||
|
"}"
|
||||||
|
"headerbar, headerbar:backdrop, .titlebar, .titlebar:backdrop {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" background-image: none;"
|
||||||
|
" color: %s;"
|
||||||
|
"}";
|
||||||
|
|
||||||
|
/* The menu bar body and the menu items are separate CSS surfaces; desktop
|
||||||
|
* integration CSS or a theme can style one but not the other, splitting
|
||||||
|
* the menu bar row into two colors. These rules sit above the theme and
|
||||||
|
* the desktop CSS so body and items always render as one surface. */
|
||||||
|
#define THEME_GTK3_MENU_UNIFORM_PRIORITY (GTK_STYLE_PROVIDER_PRIORITY_USER + 40)
|
||||||
|
static const char theme_gtk3_menu_uniform_template[] =
|
||||||
|
"menubar, menubar:backdrop, menubar > menuitem, menubar > menuitem:backdrop,"
|
||||||
|
"menu, menu:backdrop, .menu, .menu:backdrop, menu > menuitem, menu > menuitem:backdrop {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" background-image: none;"
|
||||||
|
" color: %s;"
|
||||||
|
"}"
|
||||||
|
"menubar > menuitem:hover, menubar > menuitem:selected,"
|
||||||
|
"menu > menuitem:hover, menu > menuitem:selected,"
|
||||||
|
".menu menuitem:hover, .menu menuitem:selected {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" background-image: none;"
|
||||||
|
" color: %s;"
|
||||||
|
"}"
|
||||||
|
"menubar > menuitem:disabled, menu > menuitem:disabled {"
|
||||||
|
" color: alpha(%s, 0.5);"
|
||||||
|
"}";
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
GHashTable *defaults;
|
GHashTable *defaults;
|
||||||
@@ -49,6 +98,7 @@ typedef struct
|
|||||||
static ThemeGtk3SettingsState theme_gtk3_settings_state;
|
static ThemeGtk3SettingsState theme_gtk3_settings_state;
|
||||||
|
|
||||||
static gboolean settings_apply_property (GtkSettings *settings, const char *property_name, const char *raw_value);
|
static gboolean settings_apply_property (GtkSettings *settings, const char *property_name, const char *raw_value);
|
||||||
|
static void theme_gtk3_menu_uniform_apply (const char *bg, const char *fg, const char *sel_bg, const char *sel_fg);
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
theme_gtk3_theme_name_is_dark (const char *name)
|
theme_gtk3_theme_name_is_dark (const char *name)
|
||||||
@@ -399,6 +449,21 @@ settings_theme_link_search_path (const char *theme_root, const char *theme_name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
link_path = g_build_filename (themes_root, theme_name, NULL);
|
link_path = g_build_filename (themes_root, theme_name, NULL);
|
||||||
|
|
||||||
|
/* A leftover symlink from a removed or relocated theme would satisfy
|
||||||
|
* the EXISTS check while pointing at the wrong (or no) theme, leaving
|
||||||
|
* the base gtk-theme-name on the OS theme. Replace it. */
|
||||||
|
{
|
||||||
|
char *link_target = g_file_read_link (link_path, NULL);
|
||||||
|
if (link_target)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (link_target, theme_root) != 0 ||
|
||||||
|
!g_file_test (link_path, G_FILE_TEST_IS_DIR))
|
||||||
|
g_unlink (link_path);
|
||||||
|
g_free (link_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!g_file_test (link_path, G_FILE_TEST_EXISTS))
|
if (!g_file_test (link_path, G_FILE_TEST_EXISTS))
|
||||||
{
|
{
|
||||||
GFile *link_file = g_file_new_for_path (link_path);
|
GFile *link_file = g_file_new_for_path (link_path);
|
||||||
@@ -757,12 +822,176 @@ settings_apply_from_file (const char *theme_root, const char *css_dir)
|
|||||||
g_ptr_array_unref (settings_paths);
|
g_ptr_array_unref (settings_paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
theme_gtk3_providers_define_color (const char *name)
|
||||||
|
{
|
||||||
|
char *needle = g_strdup_printf ("@define-color %s ", name);
|
||||||
|
gboolean found = FALSE;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
for (i = 0; !found && theme_gtk3_providers_base && i < theme_gtk3_providers_base->len; i++)
|
||||||
|
{
|
||||||
|
char *dump = gtk_css_provider_to_string (g_ptr_array_index (theme_gtk3_providers_base, i));
|
||||||
|
found = dump && strstr (dump, needle) != NULL;
|
||||||
|
g_free (dump);
|
||||||
|
}
|
||||||
|
for (i = 0; !found && theme_gtk3_providers_variant && i < theme_gtk3_providers_variant->len; i++)
|
||||||
|
{
|
||||||
|
char *dump = gtk_css_provider_to_string (g_ptr_array_index (theme_gtk3_providers_variant, i));
|
||||||
|
found = dump && strstr (dump, needle) != NULL;
|
||||||
|
g_free (dump);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (needle);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve a named color from the selected theme itself. The lookup
|
||||||
|
* context layers the theme's providers over the screen cascade, so the
|
||||||
|
* theme's own @define-color wins; when the theme does not define the
|
||||||
|
* name at all, fall back to a neutral palette matching the theme's
|
||||||
|
* variant instead of whatever the desktop injected into the cascade. */
|
||||||
|
static char *
|
||||||
|
theme_gtk3_chrome_resolve_color (GtkStyleContext *lookup, const char *name, const char *fallback)
|
||||||
|
{
|
||||||
|
GdkRGBA color;
|
||||||
|
|
||||||
|
if (!theme_gtk3_providers_define_color (name) ||
|
||||||
|
!gtk_style_context_lookup_color (lookup, name, &color))
|
||||||
|
g_assert (gdk_rgba_parse (&color, fallback));
|
||||||
|
|
||||||
|
if (!theme_gtk3_theme_colors)
|
||||||
|
theme_gtk3_theme_colors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||||
|
{
|
||||||
|
GdkRGBA *stored = g_new (GdkRGBA, 1);
|
||||||
|
*stored = color;
|
||||||
|
g_hash_table_insert (theme_gtk3_theme_colors, g_strdup (name), stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gdk_rgba_to_string (&color);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
GdkRGBA *stored;
|
||||||
|
|
||||||
|
if (!theme_gtk3_active || !theme_gtk3_theme_colors || !name || !out_color)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
stored = g_hash_table_lookup (theme_gtk3_theme_colors, name);
|
||||||
|
if (!stored)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
*out_color = *stored;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_gtk3_chrome_provider_apply (gboolean prefer_dark)
|
||||||
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkStyleContext *lookup;
|
||||||
|
char *bg;
|
||||||
|
char *fg;
|
||||||
|
char *sel_bg;
|
||||||
|
char *sel_fg;
|
||||||
|
char *css;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lookup = gtk_style_context_new ();
|
||||||
|
for (i = 0; theme_gtk3_providers_base && i < theme_gtk3_providers_base->len; i++)
|
||||||
|
gtk_style_context_add_provider (lookup,
|
||||||
|
GTK_STYLE_PROVIDER (g_ptr_array_index (theme_gtk3_providers_base, i)),
|
||||||
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
for (i = 0; theme_gtk3_providers_variant && i < theme_gtk3_providers_variant->len; i++)
|
||||||
|
gtk_style_context_add_provider (lookup,
|
||||||
|
GTK_STYLE_PROVIDER (g_ptr_array_index (theme_gtk3_providers_variant, i)),
|
||||||
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
|
||||||
|
if (theme_gtk3_theme_colors)
|
||||||
|
g_hash_table_remove_all (theme_gtk3_theme_colors);
|
||||||
|
|
||||||
|
bg = theme_gtk3_chrome_resolve_color (lookup, "theme_bg_color",
|
||||||
|
prefer_dark ? "#242424" : "#f6f5f4");
|
||||||
|
fg = theme_gtk3_chrome_resolve_color (lookup, "theme_fg_color",
|
||||||
|
prefer_dark ? "#eeeeee" : "#2e3436");
|
||||||
|
sel_bg = theme_gtk3_chrome_resolve_color (lookup, "theme_selected_bg_color", "#3584e4");
|
||||||
|
sel_fg = theme_gtk3_chrome_resolve_color (lookup, "theme_selected_fg_color", "#ffffff");
|
||||||
|
/* Not used by the chrome CSS itself, but resolved and stored for the
|
||||||
|
* mapped-palette sampling fallback (chat area and friends). */
|
||||||
|
g_free (theme_gtk3_chrome_resolve_color (lookup, "theme_base_color",
|
||||||
|
prefer_dark ? "#1e1e1e" : "#ffffff"));
|
||||||
|
g_object_unref (lookup);
|
||||||
|
|
||||||
|
css = g_strdup_printf (theme_gtk3_chrome_css_template,
|
||||||
|
bg, fg,
|
||||||
|
bg, fg);
|
||||||
|
|
||||||
|
if (!theme_gtk3_chrome_provider)
|
||||||
|
theme_gtk3_chrome_provider = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme_gtk3_chrome_provider, css, -1, NULL);
|
||||||
|
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_chrome_provider),
|
||||||
|
THEME_GTK3_CHROME_PRIORITY);
|
||||||
|
|
||||||
|
theme_gtk3_menu_uniform_apply (bg, fg, sel_bg, sel_fg);
|
||||||
|
|
||||||
|
g_free (css);
|
||||||
|
g_free (sel_fg);
|
||||||
|
g_free (sel_bg);
|
||||||
|
g_free (fg);
|
||||||
|
g_free (bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkCssProvider *theme_gtk3_menu_uniform_provider;
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_gtk3_menu_uniform_apply (const char *bg, const char *fg, const char *sel_bg, const char *sel_fg)
|
||||||
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
char *css;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
css = g_strdup_printf (theme_gtk3_menu_uniform_template, bg, fg, sel_bg, sel_fg, fg);
|
||||||
|
if (!theme_gtk3_menu_uniform_provider)
|
||||||
|
theme_gtk3_menu_uniform_provider = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme_gtk3_menu_uniform_provider, css, -1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_menu_uniform_provider),
|
||||||
|
THEME_GTK3_MENU_UNIFORM_PRIORITY);
|
||||||
|
g_free (css);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_gtk3_chrome_provider_remove (void)
|
||||||
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (theme_gtk3_chrome_provider)
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_chrome_provider));
|
||||||
|
if (theme_gtk3_menu_uniform_provider)
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_menu_uniform_provider));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_gtk3_remove_provider (void)
|
theme_gtk3_remove_provider (void)
|
||||||
{
|
{
|
||||||
GdkScreen *screen = gdk_screen_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
guint i;
|
guint i;
|
||||||
|
|
||||||
|
theme_gtk3_chrome_provider_remove ();
|
||||||
if (screen && theme_gtk3_providers_variant)
|
if (screen && theme_gtk3_providers_variant)
|
||||||
{
|
{
|
||||||
for (i = 0; i < theme_gtk3_providers_variant->len; i++)
|
for (i = 0; i < theme_gtk3_providers_variant->len; i++)
|
||||||
@@ -866,6 +1095,7 @@ load_css_with_variant (ZoitechatGtk3Theme *theme, ThemeGtk3Variant variant, GErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_ptr_array_unref (chain);
|
g_ptr_array_unref (chain);
|
||||||
|
theme_gtk3_chrome_provider_apply (prefer_dark);
|
||||||
settings_apply_for_variant (variant);
|
settings_apply_for_variant (variant);
|
||||||
theme_gtk3_reset_widgets ();
|
theme_gtk3_reset_widgets ();
|
||||||
theme_gtk3_active = TRUE;
|
theme_gtk3_active = TRUE;
|
||||||
@@ -876,24 +1106,35 @@ static gboolean
|
|||||||
theme_gtk3_apply_internal (const char *theme_id, ThemeGtk3Variant variant, gboolean force_reload, GError **error)
|
theme_gtk3_apply_internal (const char *theme_id, ThemeGtk3Variant variant, gboolean force_reload, GError **error)
|
||||||
{
|
{
|
||||||
ZoitechatGtk3Theme *theme;
|
ZoitechatGtk3Theme *theme;
|
||||||
char *previous_id = g_strdup (theme_gtk3_current_id);
|
char *previous_id;
|
||||||
ThemeGtk3Variant previous_variant = theme_gtk3_current_variant;
|
ThemeGtk3Variant previous_variant;
|
||||||
gboolean had_previous = theme_gtk3_active && previous_id && previous_id[0];
|
gboolean had_previous;
|
||||||
gboolean ok;
|
gboolean ok;
|
||||||
|
|
||||||
|
theme = zoitechat_gtk3_theme_find_by_id (theme_id);
|
||||||
|
if (!theme)
|
||||||
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "GTK3 theme not found."), FALSE;
|
||||||
|
|
||||||
|
/* An explicitly selected in-app theme pins the rendering to that
|
||||||
|
* theme: it must not flip with the OS light/dark preference. Legacy
|
||||||
|
* configs still carry FOLLOW_SYSTEM (the pre-inference default), so
|
||||||
|
* resolve it to the variant the theme itself provides. */
|
||||||
|
if (variant == THEME_GTK3_VARIANT_FOLLOW_SYSTEM)
|
||||||
|
variant = theme_gtk3_infer_variant (theme);
|
||||||
|
|
||||||
if (!force_reload &&
|
if (!force_reload &&
|
||||||
theme_gtk3_active &&
|
theme_gtk3_active &&
|
||||||
g_strcmp0 (theme_gtk3_current_id, theme_id) == 0 &&
|
g_strcmp0 (theme_gtk3_current_id, theme_id) == 0 &&
|
||||||
theme_gtk3_current_variant == variant)
|
theme_gtk3_current_variant == variant)
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
theme = zoitechat_gtk3_theme_find_by_id (theme_id);
|
|
||||||
if (!theme)
|
|
||||||
{
|
{
|
||||||
g_free (previous_id);
|
zoitechat_gtk3_theme_free (theme);
|
||||||
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "GTK3 theme not found."), FALSE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previous_id = g_strdup (theme_gtk3_current_id);
|
||||||
|
previous_variant = theme_gtk3_current_variant;
|
||||||
|
had_previous = theme_gtk3_active && previous_id && previous_id[0];
|
||||||
|
|
||||||
theme_gtk3_remove_provider ();
|
theme_gtk3_remove_provider ();
|
||||||
if (force_reload)
|
if (force_reload)
|
||||||
theme_gtk3_invalidate_provider_cache ();
|
theme_gtk3_invalidate_provider_cache ();
|
||||||
@@ -961,6 +1202,12 @@ void
|
|||||||
theme_gtk3_disable (void)
|
theme_gtk3_disable (void)
|
||||||
{
|
{
|
||||||
theme_gtk3_remove_provider ();
|
theme_gtk3_remove_provider ();
|
||||||
|
g_clear_object (&theme_gtk3_chrome_provider);
|
||||||
|
g_clear_pointer (&theme_gtk3_theme_colors, g_hash_table_destroy);
|
||||||
|
/* Following the system theme: keep the menu bar body and the menu
|
||||||
|
* items on one surface too, using the system's own colors. */
|
||||||
|
theme_gtk3_menu_uniform_apply ("@theme_bg_color", "@theme_fg_color",
|
||||||
|
"@theme_selected_bg_color", "@theme_selected_fg_color");
|
||||||
g_clear_pointer (&theme_gtk3_current_id, g_free);
|
g_clear_pointer (&theme_gtk3_current_id, g_free);
|
||||||
theme_gtk3_invalidate_provider_cache ();
|
theme_gtk3_invalidate_provider_cache ();
|
||||||
g_clear_pointer (&theme_gtk3_provider_cache, g_hash_table_destroy);
|
g_clear_pointer (&theme_gtk3_provider_cache, g_hash_table_destroy);
|
||||||
@@ -990,3 +1237,13 @@ theme_gtk3_is_active (void)
|
|||||||
{
|
{
|
||||||
return theme_gtk3_active;
|
return theme_gtk3_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeGtk3Variant
|
||||||
|
theme_gtk3_active_variant (void)
|
||||||
|
{
|
||||||
|
/* Always resolved to PREFER_LIGHT or PREFER_DARK while a theme is
|
||||||
|
* active; FOLLOW_SYSTEM only when no in-app theme is applied. */
|
||||||
|
if (!theme_gtk3_active)
|
||||||
|
return THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
|
return theme_gtk3_current_variant;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#define ZOITECHAT_THEME_GTK3_H
|
#define ZOITECHAT_THEME_GTK3_H
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <gdk/gdk.h>
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
@@ -38,5 +39,7 @@ ThemeGtk3Variant theme_gtk3_variant_for_theme (const char *theme_id);
|
|||||||
void theme_gtk3_invalidate_provider_cache (void);
|
void theme_gtk3_invalidate_provider_cache (void);
|
||||||
void theme_gtk3_disable (void);
|
void theme_gtk3_disable (void);
|
||||||
gboolean theme_gtk3_is_active (void);
|
gboolean theme_gtk3_is_active (void);
|
||||||
|
ThemeGtk3Variant theme_gtk3_active_variant (void);
|
||||||
|
gboolean theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -58,10 +58,52 @@ typedef struct
|
|||||||
gboolean resolved_dark_preference;
|
gboolean resolved_dark_preference;
|
||||||
char gtk3_theme_id[sizeof prefs.hex_gui_gtk3_theme];
|
char gtk3_theme_id[sizeof prefs.hex_gui_gtk3_theme];
|
||||||
int gtk3_variant;
|
int gtk3_variant;
|
||||||
|
char *gtk_theme_name;
|
||||||
} ThemeManagerAutoRefreshCache;
|
} ThemeManagerAutoRefreshCache;
|
||||||
|
|
||||||
static ThemeManagerAutoRefreshCache theme_manager_auto_refresh_cache;
|
static ThemeManagerAutoRefreshCache theme_manager_auto_refresh_cache;
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_manager_auto_refresh_cache_reset (void)
|
||||||
|
{
|
||||||
|
g_free (theme_manager_auto_refresh_cache.gtk_theme_name);
|
||||||
|
memset (&theme_manager_auto_refresh_cache, 0, sizeof (theme_manager_auto_refresh_cache));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_manager_auto_refresh_cache_store (gboolean resolved_dark_preference, char *gtk_theme_name)
|
||||||
|
{
|
||||||
|
theme_manager_auto_refresh_cache.initialized = TRUE;
|
||||||
|
theme_manager_auto_refresh_cache.resolved_dark_preference = resolved_dark_preference;
|
||||||
|
theme_manager_auto_refresh_cache.gtk3_variant = prefs.hex_gui_gtk3_variant;
|
||||||
|
g_strlcpy (theme_manager_auto_refresh_cache.gtk3_theme_id,
|
||||||
|
prefs.hex_gui_gtk3_theme,
|
||||||
|
sizeof (theme_manager_auto_refresh_cache.gtk3_theme_id));
|
||||||
|
g_free (theme_manager_auto_refresh_cache.gtk_theme_name);
|
||||||
|
theme_manager_auto_refresh_cache.gtk_theme_name = gtk_theme_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
theme_manager_dup_gtk_theme_name (void)
|
||||||
|
{
|
||||||
|
GtkSettings *settings = gtk_settings_get_default ();
|
||||||
|
char *gtk_theme_name = NULL;
|
||||||
|
|
||||||
|
if (settings)
|
||||||
|
g_object_get (settings, "gtk-theme-name", >k_theme_name, NULL);
|
||||||
|
return gtk_theme_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record the state the application started up with so that the first queued
|
||||||
|
* auto refresh only reacts to real changes; startup already applied the
|
||||||
|
* current theme, and consumers such as the tray are not ready yet. */
|
||||||
|
static void
|
||||||
|
theme_manager_auto_refresh_cache_prime (void)
|
||||||
|
{
|
||||||
|
theme_manager_auto_refresh_cache_store (theme_policy_system_prefers_dark (),
|
||||||
|
theme_manager_dup_gtk_theme_name ());
|
||||||
|
}
|
||||||
|
|
||||||
static void theme_manager_apply_platform_window_theme (GtkWidget *window);
|
static void theme_manager_apply_platform_window_theme (GtkWidget *window);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -145,7 +187,9 @@ theme_manager_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec,
|
|||||||
gboolean color_change = FALSE;
|
gboolean color_change = FALSE;
|
||||||
gboolean should_refresh_gtk3;
|
gboolean should_refresh_gtk3;
|
||||||
gboolean gtk3_refresh;
|
gboolean gtk3_refresh;
|
||||||
|
gboolean follows_system_theme;
|
||||||
gboolean resolved_dark_preference;
|
gboolean resolved_dark_preference;
|
||||||
|
char *gtk_theme_name = NULL;
|
||||||
static gboolean in_handler = FALSE;
|
static gboolean in_handler = FALSE;
|
||||||
|
|
||||||
(void) settings;
|
(void) settings;
|
||||||
@@ -154,39 +198,47 @@ theme_manager_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec,
|
|||||||
|
|
||||||
resolved_dark_preference = theme_policy_system_prefers_dark ();
|
resolved_dark_preference = theme_policy_system_prefers_dark ();
|
||||||
gtk3_refresh = theme_manager_should_refresh_gtk3 ();
|
gtk3_refresh = theme_manager_should_refresh_gtk3 ();
|
||||||
|
follows_system_theme = prefs.hex_gui_gtk3_theme[0] == '\0';
|
||||||
should_refresh_gtk3 = gtk3_refresh || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO;
|
should_refresh_gtk3 = gtk3_refresh || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO;
|
||||||
|
|
||||||
|
gtk_theme_name = theme_manager_dup_gtk_theme_name ();
|
||||||
|
|
||||||
if (theme_manager_auto_refresh_cache.initialized &&
|
if (theme_manager_auto_refresh_cache.initialized &&
|
||||||
theme_manager_auto_refresh_cache.resolved_dark_preference == resolved_dark_preference &&
|
theme_manager_auto_refresh_cache.resolved_dark_preference == resolved_dark_preference &&
|
||||||
theme_manager_auto_refresh_cache.gtk3_variant == prefs.hex_gui_gtk3_variant &&
|
theme_manager_auto_refresh_cache.gtk3_variant == prefs.hex_gui_gtk3_variant &&
|
||||||
g_strcmp0 (theme_manager_auto_refresh_cache.gtk3_theme_id, prefs.hex_gui_gtk3_theme) == 0)
|
g_strcmp0 (theme_manager_auto_refresh_cache.gtk3_theme_id, prefs.hex_gui_gtk3_theme) == 0 &&
|
||||||
|
g_strcmp0 (theme_manager_auto_refresh_cache.gtk_theme_name, gtk_theme_name) == 0)
|
||||||
|
{
|
||||||
|
g_free (gtk_theme_name);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
theme_manager_auto_refresh_cache.initialized = TRUE;
|
theme_manager_auto_refresh_cache_store (resolved_dark_preference, gtk_theme_name);
|
||||||
theme_manager_auto_refresh_cache.resolved_dark_preference = resolved_dark_preference;
|
|
||||||
theme_manager_auto_refresh_cache.gtk3_variant = prefs.hex_gui_gtk3_variant;
|
|
||||||
g_strlcpy (theme_manager_auto_refresh_cache.gtk3_theme_id,
|
|
||||||
prefs.hex_gui_gtk3_theme,
|
|
||||||
sizeof (theme_manager_auto_refresh_cache.gtk3_theme_id));
|
|
||||||
|
|
||||||
if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO && !gtk3_refresh)
|
if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO && !gtk3_refresh && !follows_system_theme)
|
||||||
return;
|
return;
|
||||||
if (in_handler)
|
if (in_handler)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
in_handler = TRUE;
|
in_handler = TRUE;
|
||||||
|
|
||||||
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO)
|
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO || follows_system_theme)
|
||||||
{
|
{
|
||||||
fe_set_auto_dark_mode_state (resolved_dark_preference);
|
|
||||||
theme_manager_commit_preferences (prefs.hex_gui_dark_mode, &color_change);
|
theme_manager_commit_preferences (prefs.hex_gui_dark_mode, &color_change);
|
||||||
if (color_change)
|
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO)
|
||||||
theme_manager_dispatch_changed (THEME_CHANGED_REASON_PALETTE | THEME_CHANGED_REASON_WIDGET_STYLE | THEME_CHANGED_REASON_USERLIST | THEME_CHANGED_REASON_MODE);
|
fe_set_auto_dark_mode_state (resolved_dark_preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_refresh_gtk3)
|
if (should_refresh_gtk3)
|
||||||
theme_gtk3_apply_current (NULL);
|
theme_gtk3_apply_current (NULL);
|
||||||
|
|
||||||
|
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO || follows_system_theme)
|
||||||
|
{
|
||||||
|
/* The system theme feeds the mapped palette (chat area, user
|
||||||
|
* list, channel tree), so consumers must re-resolve colors. */
|
||||||
|
theme_manager_dispatch_changed (THEME_CHANGED_REASON_PALETTE | THEME_CHANGED_REASON_WIDGET_STYLE | THEME_CHANGED_REASON_USERLIST | THEME_CHANGED_REASON_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
in_handler = FALSE;
|
in_handler = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,18 +256,28 @@ theme_manager_run_auto_refresh (gpointer data)
|
|||||||
static void
|
static void
|
||||||
theme_manager_queue_auto_refresh (GtkSettings *settings, GParamSpec *pspec, gpointer data)
|
theme_manager_queue_auto_refresh (GtkSettings *settings, GParamSpec *pspec, gpointer data)
|
||||||
{
|
{
|
||||||
|
guint source_id;
|
||||||
|
|
||||||
(void) settings;
|
(void) settings;
|
||||||
(void) pspec;
|
(void) pspec;
|
||||||
|
|
||||||
if (theme_manager_auto_refresh_source != 0)
|
if (theme_manager_auto_refresh_source != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_manager_auto_refresh_source = theme_manager_idle_add_func (theme_manager_run_auto_refresh, data);
|
theme_manager_auto_refresh_source = 1;
|
||||||
|
source_id = theme_manager_idle_add_func (theme_manager_run_auto_refresh, data);
|
||||||
|
/* An injected idle-add may run the refresh synchronously and already
|
||||||
|
* have cleared the pending marker; only keep the id while pending. */
|
||||||
|
if (theme_manager_auto_refresh_source != 0)
|
||||||
|
theme_manager_auto_refresh_source = source_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_manager_init (void)
|
theme_manager_init (void)
|
||||||
{
|
{
|
||||||
|
static gboolean settings_signals_connected = FALSE;
|
||||||
|
GtkSettings *settings;
|
||||||
|
|
||||||
if (!theme_manager_listeners)
|
if (!theme_manager_listeners)
|
||||||
theme_manager_listeners = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
|
theme_manager_listeners = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
|
||||||
theme_listener_free);
|
theme_listener_free);
|
||||||
@@ -223,10 +285,25 @@ theme_manager_init (void)
|
|||||||
if (!theme_manager_setup_listener_id)
|
if (!theme_manager_setup_listener_id)
|
||||||
theme_manager_setup_listener_id = theme_listener_register ("setup.apply", theme_manager_setup_apply_listener, NULL);
|
theme_manager_setup_listener_id = theme_listener_register ("setup.apply", theme_manager_setup_apply_listener, NULL);
|
||||||
|
|
||||||
fe_set_auto_dark_mode_state (FALSE);
|
theme_policy_init ();
|
||||||
|
theme_policy_set_appearance_changed_callback (theme_manager_refresh_auto_mode);
|
||||||
|
|
||||||
|
settings = gtk_settings_get_default ();
|
||||||
|
if (settings && !settings_signals_connected)
|
||||||
|
{
|
||||||
|
g_signal_connect (settings, "notify::gtk-theme-name",
|
||||||
|
G_CALLBACK (theme_manager_queue_auto_refresh), NULL);
|
||||||
|
g_signal_connect (settings, "notify::gtk-application-prefer-dark-theme",
|
||||||
|
G_CALLBACK (theme_manager_queue_auto_refresh), NULL);
|
||||||
|
settings_signals_connected = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_set_auto_dark_mode_state (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO ?
|
||||||
|
theme_policy_system_prefers_dark () : FALSE);
|
||||||
theme_application_apply_mode (prefs.hex_gui_dark_mode, NULL);
|
theme_application_apply_mode (prefs.hex_gui_dark_mode, NULL);
|
||||||
theme_gtk3_init ();
|
theme_gtk3_init ();
|
||||||
zoitechat_set_theme_post_apply_callback (theme_manager_handle_theme_applied);
|
zoitechat_set_theme_post_apply_callback (theme_manager_handle_theme_applied);
|
||||||
|
theme_manager_auto_refresh_cache_prime ();
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
@@ -513,13 +590,9 @@ theme_manager_apply_platform_window_theme (GtkWidget *window)
|
|||||||
|
|
||||||
context = gtk_widget_get_style_context (window);
|
context = gtk_widget_get_style_context (window);
|
||||||
if (theme_gtk3_is_active ())
|
if (theme_gtk3_is_active ())
|
||||||
{
|
dark = theme_gtk3_active_variant () == THEME_GTK3_VARIANT_PREFER_DARK;
|
||||||
dark = prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_DARK;
|
|
||||||
if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_FOLLOW_SYSTEM)
|
|
||||||
dark = theme_policy_system_prefers_dark ();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
dark = theme_runtime_is_dark_active ();
|
dark = theme_policy_is_dark_mode_active (prefs.hex_gui_dark_mode);
|
||||||
if (context)
|
if (context)
|
||||||
{
|
{
|
||||||
gtk_style_context_remove_class (context, "zoitechat-dark");
|
gtk_style_context_remove_class (context, "zoitechat-dark");
|
||||||
@@ -712,4 +785,5 @@ theme_manager_set_idle_add_func (ThemeManagerIdleAddFunc idle_add_func)
|
|||||||
{
|
{
|
||||||
theme_manager_idle_add_func = idle_add_func ? idle_add_func : g_idle_add;
|
theme_manager_idle_add_func = idle_add_func ? idle_add_func : g_idle_add;
|
||||||
theme_manager_auto_refresh_source = 0;
|
theme_manager_auto_refresh_source = 0;
|
||||||
|
theme_manager_auto_refresh_cache_reset ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,379 @@
|
|||||||
|
|
||||||
#include "theme-policy.h"
|
#include "theme-policy.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "theme-gtk3.h"
|
||||||
#include "../fe-gtk.h"
|
#include "../fe-gtk.h"
|
||||||
#include "../../common/zoitechat.h"
|
#include "../../common/zoitechat.h"
|
||||||
#include "../../common/zoitechatc.h"
|
#include "../../common/zoitechatc.h"
|
||||||
|
|
||||||
|
/* org.freedesktop.appearance color-scheme values (XDG desktop portal) */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE = 0,
|
||||||
|
THEME_POLICY_COLOR_SCHEME_PREFER_DARK = 1,
|
||||||
|
THEME_POLICY_COLOR_SCHEME_PREFER_LIGHT = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
static ThemePolicyAppearanceChangedFunc policy_appearance_changed_cb;
|
||||||
|
|
||||||
|
/* GtkSettings state captured before ZoiteChat writes to it, so that values
|
||||||
|
* coming from the user's own GTK configuration are not mistaken for values
|
||||||
|
* this application set. */
|
||||||
|
static gboolean policy_initial_gtk_state_captured;
|
||||||
|
static gboolean policy_initial_prefer_dark;
|
||||||
|
static char *policy_initial_theme_name;
|
||||||
|
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
static gboolean policy_portal_watch_started;
|
||||||
|
static gboolean policy_portal_value_valid;
|
||||||
|
static guint32 policy_portal_color_scheme;
|
||||||
|
|
||||||
|
static gboolean policy_gsettings_checked;
|
||||||
|
static GSettings *policy_gsettings_interface;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_capture_initial_gtk_state (void)
|
||||||
|
{
|
||||||
|
GtkSettings *settings;
|
||||||
|
|
||||||
|
if (policy_initial_gtk_state_captured)
|
||||||
|
return;
|
||||||
|
|
||||||
|
settings = gtk_settings_get_default ();
|
||||||
|
if (!settings)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_object_get (settings,
|
||||||
|
"gtk-application-prefer-dark-theme", &policy_initial_prefer_dark,
|
||||||
|
"gtk-theme-name", &policy_initial_theme_name,
|
||||||
|
NULL);
|
||||||
|
policy_initial_gtk_state_captured = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_theme_name_is_dark (const char *name)
|
||||||
|
{
|
||||||
|
char *lower;
|
||||||
|
gboolean dark;
|
||||||
|
|
||||||
|
if (!name || !name[0])
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
lower = g_ascii_strdown (name, -1);
|
||||||
|
dark = strstr (lower, "dark") != NULL || strstr (lower, "noir") != NULL ||
|
||||||
|
strstr (lower, "black") != NULL;
|
||||||
|
g_free (lower);
|
||||||
|
return dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_notify_appearance_changed (void)
|
||||||
|
{
|
||||||
|
if (policy_appearance_changed_cb)
|
||||||
|
policy_appearance_changed_cb ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_color_scheme_from_variant (GVariant *value, guint32 *color_scheme)
|
||||||
|
{
|
||||||
|
GVariant *inner;
|
||||||
|
gboolean ok = FALSE;
|
||||||
|
|
||||||
|
if (!value || !color_scheme)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
inner = g_variant_ref (value);
|
||||||
|
while (g_variant_is_of_type (inner, G_VARIANT_TYPE_VARIANT))
|
||||||
|
{
|
||||||
|
GVariant *unwrapped = g_variant_get_variant (inner);
|
||||||
|
|
||||||
|
g_variant_unref (inner);
|
||||||
|
inner = unwrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_variant_is_of_type (inner, G_VARIANT_TYPE_UINT32))
|
||||||
|
{
|
||||||
|
*color_scheme = g_variant_get_uint32 (inner);
|
||||||
|
ok = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref (inner);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_store_color_scheme (guint32 color_scheme)
|
||||||
|
{
|
||||||
|
gboolean changed;
|
||||||
|
|
||||||
|
changed = !policy_portal_value_valid || policy_portal_color_scheme != color_scheme;
|
||||||
|
policy_portal_value_valid = TRUE;
|
||||||
|
policy_portal_color_scheme = color_scheme;
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
policy_notify_appearance_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_setting_changed_cb (GDBusConnection *connection, const char *sender_name,
|
||||||
|
const char *object_path, const char *interface_name,
|
||||||
|
const char *signal_name, GVariant *parameters,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
const char *setting_namespace = NULL;
|
||||||
|
const char *setting_key = NULL;
|
||||||
|
GVariant *value = NULL;
|
||||||
|
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
|
||||||
|
|
||||||
|
(void) connection;
|
||||||
|
(void) sender_name;
|
||||||
|
(void) object_path;
|
||||||
|
(void) interface_name;
|
||||||
|
(void) signal_name;
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
if (!parameters || !g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssv)")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_variant_get (parameters, "(&s&sv)", &setting_namespace, &setting_key, &value);
|
||||||
|
if (g_strcmp0 (setting_namespace, "org.freedesktop.appearance") == 0 &&
|
||||||
|
g_strcmp0 (setting_key, "color-scheme") == 0 &&
|
||||||
|
policy_color_scheme_from_variant (value, &color_scheme))
|
||||||
|
policy_portal_store_color_scheme (color_scheme);
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
g_variant_unref (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_read_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||||||
|
{
|
||||||
|
GVariant *reply;
|
||||||
|
GVariant *value = NULL;
|
||||||
|
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
|
||||||
|
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, NULL);
|
||||||
|
if (!reply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(v)")))
|
||||||
|
{
|
||||||
|
g_variant_get (reply, "(v)", &value);
|
||||||
|
if (policy_color_scheme_from_variant (value, &color_scheme))
|
||||||
|
policy_portal_store_color_scheme (color_scheme);
|
||||||
|
if (value)
|
||||||
|
g_variant_unref (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref (reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_bus_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||||||
|
{
|
||||||
|
GDBusConnection *connection;
|
||||||
|
|
||||||
|
(void) source;
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
connection = g_bus_get_finish (result, NULL);
|
||||||
|
if (!connection)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_dbus_connection_signal_subscribe (connection,
|
||||||
|
"org.freedesktop.portal.Desktop",
|
||||||
|
"org.freedesktop.portal.Settings",
|
||||||
|
"SettingChanged",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
NULL,
|
||||||
|
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||||
|
policy_portal_setting_changed_cb,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
g_dbus_connection_call (connection,
|
||||||
|
"org.freedesktop.portal.Desktop",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
"org.freedesktop.portal.Settings",
|
||||||
|
"Read",
|
||||||
|
g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
|
||||||
|
G_VARIANT_TYPE ("(v)"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
3000,
|
||||||
|
NULL,
|
||||||
|
policy_portal_read_ready_cb,
|
||||||
|
NULL);
|
||||||
|
g_object_unref (connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_watch_start (void)
|
||||||
|
{
|
||||||
|
if (policy_portal_watch_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
policy_portal_watch_started = TRUE;
|
||||||
|
g_bus_get (G_BUS_TYPE_SESSION, NULL, policy_portal_bus_ready_cb, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_gsettings_changed_cb (GSettings *settings, const char *key, gpointer user_data)
|
||||||
|
{
|
||||||
|
(void) settings;
|
||||||
|
(void) key;
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
policy_notify_appearance_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static GSettings *
|
||||||
|
policy_get_interface_gsettings (void)
|
||||||
|
{
|
||||||
|
GSettingsSchemaSource *source;
|
||||||
|
GSettingsSchema *schema;
|
||||||
|
gboolean has_key;
|
||||||
|
|
||||||
|
if (policy_gsettings_checked)
|
||||||
|
return policy_gsettings_interface;
|
||||||
|
|
||||||
|
policy_gsettings_checked = TRUE;
|
||||||
|
|
||||||
|
source = g_settings_schema_source_get_default ();
|
||||||
|
if (!source)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
schema = g_settings_schema_source_lookup (source, "org.gnome.desktop.interface", TRUE);
|
||||||
|
if (!schema)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Requires GLib 2.40, always satisfied by the GTK 3.22 dependency. */
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
has_key = g_settings_schema_has_key (schema, "color-scheme");
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_settings_schema_unref (schema);
|
||||||
|
if (!has_key)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
policy_gsettings_interface = g_settings_new ("org.gnome.desktop.interface");
|
||||||
|
g_signal_connect (policy_gsettings_interface, "changed::color-scheme",
|
||||||
|
G_CALLBACK (policy_gsettings_changed_cb), NULL);
|
||||||
|
return policy_gsettings_interface;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_gsettings_color_scheme (gboolean *prefers_dark)
|
||||||
|
{
|
||||||
|
GSettings *settings;
|
||||||
|
char *scheme;
|
||||||
|
gboolean explicit_preference = FALSE;
|
||||||
|
|
||||||
|
settings = policy_get_interface_gsettings ();
|
||||||
|
if (!settings)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
scheme = g_settings_get_string (settings, "color-scheme");
|
||||||
|
if (g_strcmp0 (scheme, "prefer-dark") == 0)
|
||||||
|
{
|
||||||
|
*prefers_dark = TRUE;
|
||||||
|
explicit_preference = TRUE;
|
||||||
|
}
|
||||||
|
else if (g_strcmp0 (scheme, "prefer-light") == 0)
|
||||||
|
{
|
||||||
|
*prefers_dark = FALSE;
|
||||||
|
explicit_preference = TRUE;
|
||||||
|
}
|
||||||
|
g_free (scheme);
|
||||||
|
return explicit_preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_gtk_heuristic_prefers_dark (void)
|
||||||
|
{
|
||||||
|
GtkSettings *settings;
|
||||||
|
char *theme_name = NULL;
|
||||||
|
gboolean dark;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
|
||||||
|
/* gtk-application-prefer-dark-theme is overwritten by ZoiteChat at
|
||||||
|
* runtime, so only its captured startup value (settings.ini, XSETTINGS)
|
||||||
|
* reflects the user's own configuration. */
|
||||||
|
if (policy_initial_gtk_state_captured && policy_initial_prefer_dark)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
if (theme_gtk3_is_active ())
|
||||||
|
{
|
||||||
|
/* gtk-theme-name currently holds the in-app theme override;
|
||||||
|
* fall back to the captured system theme name. */
|
||||||
|
theme_name = g_strdup (policy_initial_theme_name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settings = gtk_settings_get_default ();
|
||||||
|
if (settings)
|
||||||
|
g_object_get (settings, "gtk-theme-name", &theme_name, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
dark = policy_theme_name_is_dark (theme_name);
|
||||||
|
g_free (theme_name);
|
||||||
|
return dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !G_OS_WIN32 */
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_policy_init (void)
|
||||||
|
{
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
policy_portal_watch_start ();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
policy_appearance_changed_cb = callback;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
theme_policy_system_prefers_dark (void)
|
theme_policy_system_prefers_dark (void)
|
||||||
{
|
{
|
||||||
return FALSE;
|
#ifdef G_OS_WIN32
|
||||||
|
gboolean prefer_dark = FALSE;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
|
||||||
|
if (fe_win32_high_contrast_is_enabled ())
|
||||||
|
return FALSE;
|
||||||
|
if (fe_win32_try_get_system_dark (&prefer_dark))
|
||||||
|
return prefer_dark;
|
||||||
|
return policy_theme_name_is_dark (policy_initial_theme_name);
|
||||||
|
#else
|
||||||
|
gboolean prefers_dark = FALSE;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
policy_portal_watch_start ();
|
||||||
|
|
||||||
|
if (policy_portal_value_valid &&
|
||||||
|
policy_portal_color_scheme != THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE)
|
||||||
|
return policy_portal_color_scheme == THEME_POLICY_COLOR_SCHEME_PREFER_DARK;
|
||||||
|
|
||||||
|
if (policy_gsettings_color_scheme (&prefers_dark))
|
||||||
|
return prefers_dark;
|
||||||
|
|
||||||
|
return policy_gtk_heuristic_prefers_dark ();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
|
|
||||||
#include "../fe-gtk.h"
|
#include "../fe-gtk.h"
|
||||||
|
|
||||||
|
typedef void (*ThemePolicyAppearanceChangedFunc) (void);
|
||||||
|
|
||||||
|
void theme_policy_init (void);
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback);
|
||||||
gboolean theme_policy_system_prefers_dark (void);
|
gboolean theme_policy_system_prefers_dark (void);
|
||||||
gboolean theme_policy_is_dark_mode_active (unsigned int mode);
|
gboolean theme_policy_is_dark_mode_active (unsigned int mode);
|
||||||
gboolean theme_policy_is_app_dark_mode_active (void);
|
gboolean theme_policy_is_app_dark_mode_active (void);
|
||||||
|
|||||||
@@ -101,8 +101,16 @@ typedef struct
|
|||||||
gboolean changed;
|
gboolean changed;
|
||||||
gboolean snapshot_valid[THEME_TOKEN_COUNT];
|
gboolean snapshot_valid[THEME_TOKEN_COUNT];
|
||||||
gboolean staged_valid[THEME_TOKEN_COUNT];
|
gboolean staged_valid[THEME_TOKEN_COUNT];
|
||||||
|
/* Only explicitly edited tokens are written back to the runtime
|
||||||
|
* palette; untouched tokens keep following the GTK3 theme. */
|
||||||
|
gboolean staged_dirty[THEME_TOKEN_COUNT];
|
||||||
GdkRGBA snapshot[THEME_TOKEN_COUNT];
|
GdkRGBA snapshot[THEME_TOKEN_COUNT];
|
||||||
GdkRGBA staged[THEME_TOKEN_COUNT];
|
GdkRGBA staged[THEME_TOKEN_COUNT];
|
||||||
|
/* the GTK3 theme selection is previewed live from the combo, so
|
||||||
|
* remember what was active when the dialog opened in order to
|
||||||
|
* restore it when the dialog is cancelled */
|
||||||
|
char gtk3_theme_snapshot[sizeof ((struct zoitechatprefs *) 0)->hex_gui_gtk3_theme];
|
||||||
|
int gtk3_variant_snapshot;
|
||||||
} theme_preferences_stage_state;
|
} theme_preferences_stage_state;
|
||||||
|
|
||||||
static theme_preferences_stage_state theme_preferences_stage;
|
static theme_preferences_stage_state theme_preferences_stage;
|
||||||
@@ -147,7 +155,8 @@ theme_preferences_stage_sync_runtime_to_snapshot (void)
|
|||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
if (theme_preferences_stage.snapshot_valid[token])
|
if (theme_preferences_stage.snapshot_valid[token] &&
|
||||||
|
theme_preferences_stage.staged_dirty[token])
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
||||||
&theme_preferences_stage.snapshot[token], NULL);
|
&theme_preferences_stage.snapshot[token], NULL);
|
||||||
}
|
}
|
||||||
@@ -160,7 +169,8 @@ theme_preferences_stage_sync_runtime_to_staged (void)
|
|||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
if (theme_preferences_stage.staged_valid[token])
|
if (theme_preferences_stage.staged_valid[token] &&
|
||||||
|
theme_preferences_stage.staged_dirty[token])
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
||||||
&theme_preferences_stage.staged[token], NULL);
|
&theme_preferences_stage.staged[token], NULL);
|
||||||
}
|
}
|
||||||
@@ -179,6 +189,7 @@ theme_preferences_staged_set_color (ThemeSemanticToken token, const GdkRGBA *rgb
|
|||||||
{
|
{
|
||||||
theme_preferences_stage.staged[token] = *rgba;
|
theme_preferences_stage.staged[token] = *rgba;
|
||||||
theme_preferences_stage.staged_valid[token] = TRUE;
|
theme_preferences_stage.staged_valid[token] = TRUE;
|
||||||
|
theme_preferences_stage.staged_dirty[token] = TRUE;
|
||||||
theme_preferences_stage_recompute_changed ();
|
theme_preferences_stage_recompute_changed ();
|
||||||
if (color_change_flag)
|
if (color_change_flag)
|
||||||
*color_change_flag = theme_preferences_stage.changed;
|
*color_change_flag = theme_preferences_stage.changed;
|
||||||
@@ -198,6 +209,11 @@ theme_preferences_stage_begin (void)
|
|||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
theme_preferences_stage.active = TRUE;
|
theme_preferences_stage.active = TRUE;
|
||||||
|
|
||||||
|
g_strlcpy (theme_preferences_stage.gtk3_theme_snapshot,
|
||||||
|
prefs.hex_gui_gtk3_theme,
|
||||||
|
sizeof (theme_preferences_stage.gtk3_theme_snapshot));
|
||||||
|
theme_preferences_stage.gtk3_variant_snapshot = prefs.hex_gui_gtk3_variant;
|
||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
GdkRGBA rgba;
|
GdkRGBA rgba;
|
||||||
@@ -231,12 +247,36 @@ theme_preferences_stage_commit (void)
|
|||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* undo a live GTK3 theme preview: put the selection that was active when
|
||||||
|
* the dialog opened back and re-apply it */
|
||||||
|
static void
|
||||||
|
theme_preferences_stage_restore_gtk3 (void)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (prefs.hex_gui_gtk3_theme, theme_preferences_stage.gtk3_theme_snapshot) == 0 &&
|
||||||
|
prefs.hex_gui_gtk3_variant == theme_preferences_stage.gtk3_variant_snapshot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme,
|
||||||
|
theme_preferences_stage.gtk3_theme_snapshot,
|
||||||
|
sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
|
prefs.hex_gui_gtk3_variant = theme_preferences_stage.gtk3_variant_snapshot;
|
||||||
|
|
||||||
|
theme_gtk3_apply_current (NULL);
|
||||||
|
theme_manager_dispatch_changed (THEME_CHANGED_REASON_THEME_PACK |
|
||||||
|
THEME_CHANGED_REASON_PALETTE |
|
||||||
|
THEME_CHANGED_REASON_WIDGET_STYLE |
|
||||||
|
THEME_CHANGED_REASON_USERLIST |
|
||||||
|
THEME_CHANGED_REASON_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_preferences_stage_discard (void)
|
theme_preferences_stage_discard (void)
|
||||||
{
|
{
|
||||||
if (!theme_preferences_stage.active)
|
if (!theme_preferences_stage.active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* restore the GTK3 theme first so the color snapshot below wins */
|
||||||
|
theme_preferences_stage_restore_gtk3 ();
|
||||||
theme_preferences_stage_sync_runtime_to_snapshot ();
|
theme_preferences_stage_sync_runtime_to_snapshot ();
|
||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
}
|
}
|
||||||
@@ -841,6 +881,10 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i
|
|||||||
ThemeSemanticToken token;
|
ThemeSemanticToken token;
|
||||||
ThemeWidgetStyleValues style_values;
|
ThemeWidgetStyleValues style_values;
|
||||||
|
|
||||||
|
/* The runtime palette was just reset; drop any
|
||||||
|
* pending edits so the reset is not re-pinned. */
|
||||||
|
memset (theme_preferences_stage.staged_dirty, 0,
|
||||||
|
sizeof (theme_preferences_stage.staged_dirty));
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
GdkRGBA rgba;
|
GdkRGBA rgba;
|
||||||
@@ -855,7 +899,6 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i
|
|||||||
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_FOREGROUND] = TRUE;
|
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_FOREGROUND] = TRUE;
|
||||||
theme_preferences_stage.staged[THEME_TOKEN_TEXT_BACKGROUND] = style_values.background;
|
theme_preferences_stage.staged[THEME_TOKEN_TEXT_BACKGROUND] = style_values.background;
|
||||||
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_BACKGROUND] = TRUE;
|
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_BACKGROUND] = TRUE;
|
||||||
theme_preferences_stage_sync_runtime_to_staged ();
|
|
||||||
theme_preferences_stage_recompute_changed ();
|
theme_preferences_stage_recompute_changed ();
|
||||||
if (ui->color_change_flag)
|
if (ui->color_change_flag)
|
||||||
*ui->color_change_flag = theme_preferences_stage.changed;
|
*ui->color_change_flag = theme_preferences_stage.changed;
|
||||||
@@ -1534,6 +1577,25 @@ theme_preferences_gtk3_sync_remove_state (theme_preferences_ui *ui)
|
|||||||
gtk_widget_set_sensitive (ui->gtk3_remove, source == ZOITECHAT_GTK3_THEME_SOURCE_USER);
|
gtk_widget_set_sensitive (ui->gtk3_remove, source == ZOITECHAT_GTK3_THEME_SOURCE_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_preferences_stage_rebase_color (ThemeSemanticToken token, const GdkRGBA *rgba)
|
||||||
|
{
|
||||||
|
if (token < 0 || token >= THEME_TOKEN_COUNT || !rgba)
|
||||||
|
return;
|
||||||
|
if (!theme_preferences_stage.active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* The theme changed underneath the dialog: shift the baseline so the
|
||||||
|
* new theme-provided value is neither treated as a user edit nor
|
||||||
|
* pinned into the runtime palette. */
|
||||||
|
theme_preferences_stage.snapshot[token] = *rgba;
|
||||||
|
theme_preferences_stage.staged[token] = *rgba;
|
||||||
|
theme_preferences_stage.snapshot_valid[token] = TRUE;
|
||||||
|
theme_preferences_stage.staged_valid[token] = TRUE;
|
||||||
|
theme_preferences_stage.staged_dirty[token] = FALSE;
|
||||||
|
theme_preferences_stage_recompute_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui)
|
theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui)
|
||||||
{
|
{
|
||||||
@@ -1549,22 +1611,16 @@ theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui)
|
|||||||
|
|
||||||
theme_get_widget_style_values_for_widget (style_source, &style_values);
|
theme_get_widget_style_values_for_widget (style_source, &style_values);
|
||||||
|
|
||||||
theme_preferences_staged_set_color (THEME_TOKEN_TEXT_FOREGROUND,
|
/* Leave the mapped tokens unpinned so the user list, channel tree and
|
||||||
&style_values.foreground,
|
* input box keep resolving their colors from whatever GTK3 theme is
|
||||||
NULL,
|
* active (system or in-app selected), and only refresh what the
|
||||||
TRUE);
|
* dialog shows. Pinning theme-derived values here freezes them into
|
||||||
theme_preferences_staged_set_color (THEME_TOKEN_TEXT_BACKGROUND,
|
* the runtime palette (and colors.conf), which left those widgets
|
||||||
&style_values.background,
|
* with stale colors until the next restart. */
|
||||||
NULL,
|
theme_preferences_stage_rebase_color (THEME_TOKEN_TEXT_FOREGROUND, &style_values.foreground);
|
||||||
TRUE);
|
theme_preferences_stage_rebase_color (THEME_TOKEN_TEXT_BACKGROUND, &style_values.background);
|
||||||
theme_preferences_staged_set_color (THEME_TOKEN_SELECTION_FOREGROUND,
|
theme_preferences_stage_rebase_color (THEME_TOKEN_SELECTION_FOREGROUND, &style_values.selection_foreground);
|
||||||
&style_values.selection_foreground,
|
theme_preferences_stage_rebase_color (THEME_TOKEN_SELECTION_BACKGROUND, &style_values.selection_background);
|
||||||
NULL,
|
|
||||||
TRUE);
|
|
||||||
theme_preferences_staged_set_color (THEME_TOKEN_SELECTION_BACKGROUND,
|
|
||||||
&style_values.selection_background,
|
|
||||||
NULL,
|
|
||||||
TRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
|
|||||||
@@ -796,16 +796,12 @@ userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkListStore *
|
static void
|
||||||
userlist_create_model (session *sess)
|
userlist_model_set_sort (GtkListStore *store, session *sess)
|
||||||
{
|
{
|
||||||
GtkListStore *store;
|
|
||||||
GtkTreeIterCompareFunc cmp_func;
|
GtkTreeIterCompareFunc cmp_func;
|
||||||
GtkSortType sort_type;
|
GtkSortType sort_type;
|
||||||
|
|
||||||
store = gtk_list_store_new (6, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
|
|
||||||
G_TYPE_STRING, G_TYPE_POINTER, THEME_GTK_COLOR_TYPE);
|
|
||||||
|
|
||||||
switch (prefs.hex_gui_ulist_sort)
|
switch (prefs.hex_gui_ulist_sort)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
@@ -825,18 +821,77 @@ userlist_create_model (session *sess)
|
|||||||
sort_type = GTK_SORT_DESCENDING;
|
sort_type = GTK_SORT_DESCENDING;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* No sorting */
|
/* No sorting; rows keep their current order and new rows
|
||||||
|
go wherever they are inserted */
|
||||||
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
|
||||||
|
GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
|
||||||
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), NULL, NULL, NULL);
|
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), NULL, NULL, NULL);
|
||||||
return store;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), cmp_func, sess, NULL);
|
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), cmp_func, sess, NULL);
|
||||||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
|
||||||
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, sort_type);
|
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, sort_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkListStore *
|
||||||
|
userlist_create_model (session *sess)
|
||||||
|
{
|
||||||
|
GtkListStore *store;
|
||||||
|
|
||||||
|
store = gtk_list_store_new (6, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
|
||||||
|
G_TYPE_STRING, G_TYPE_POINTER, THEME_GTK_COLOR_TYPE);
|
||||||
|
userlist_model_set_sort (store, sess);
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
userlist_apply_sort (session *sess)
|
||||||
|
{
|
||||||
|
if (!sess->res || !sess->res->user_model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
userlist_model_set_sort (sess->res->user_model, sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove and re-insert every row, so per-row content that depends on
|
||||||
|
* preferences (mode icons vs. text prefixes, hostnames) is rebuilt */
|
||||||
|
void
|
||||||
|
userlist_refill (session *sess)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
GSList *users = NULL;
|
||||||
|
GSList *it;
|
||||||
|
struct User *user;
|
||||||
|
|
||||||
|
if (!sess->res || !sess->res->user_model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
model = GTK_TREE_MODEL (sess->res->user_model);
|
||||||
|
if (gtk_tree_model_get_iter_first (model, &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
|
||||||
|
if (user)
|
||||||
|
users = g_slist_prepend (users, user);
|
||||||
|
}
|
||||||
|
while (gtk_tree_model_iter_next (model, &iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_userlist_clear (sess);
|
||||||
|
|
||||||
|
/* fe_userlist_insert() prepends, so inserting in reverse display
|
||||||
|
order restores the on-screen order for unsorted lists; sorted
|
||||||
|
models position the rows themselves */
|
||||||
|
for (it = users; it; it = it->next)
|
||||||
|
fe_userlist_insert (sess, it->data, FALSE);
|
||||||
|
|
||||||
|
g_slist_free (users);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
userlist_add_columns (GtkTreeView * treeview)
|
userlist_add_columns (GtkTreeView * treeview)
|
||||||
{
|
{
|
||||||
@@ -906,6 +961,23 @@ userlist_add_columns (GtkTreeView * treeview)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
userlist_rebuild_columns (GtkWidget *treeview)
|
||||||
|
{
|
||||||
|
GList *columns;
|
||||||
|
GList *it;
|
||||||
|
|
||||||
|
if (!treeview)
|
||||||
|
return;
|
||||||
|
|
||||||
|
columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (treeview));
|
||||||
|
for (it = columns; it; it = it->next)
|
||||||
|
gtk_tree_view_remove_column (GTK_TREE_VIEW (treeview), it->data);
|
||||||
|
g_list_free (columns);
|
||||||
|
|
||||||
|
userlist_add_columns (GTK_TREE_VIEW (treeview));
|
||||||
|
}
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
|
userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ void userlist_set_value (GtkWidget *treeview, gfloat val);
|
|||||||
gfloat userlist_get_value (GtkWidget *treeview);
|
gfloat userlist_get_value (GtkWidget *treeview);
|
||||||
GtkWidget *userlist_create (GtkWidget *box);
|
GtkWidget *userlist_create (GtkWidget *box);
|
||||||
GtkListStore *userlist_create_model (session *sess);
|
GtkListStore *userlist_create_model (session *sess);
|
||||||
|
void userlist_apply_sort (session *sess);
|
||||||
|
void userlist_refill (session *sess);
|
||||||
|
void userlist_rebuild_columns (GtkWidget *treeview);
|
||||||
void userlist_show (session *sess);
|
void userlist_show (session *sess);
|
||||||
void userlist_select (session *sess, char *name);
|
void userlist_select (session *sess, char *name);
|
||||||
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
||||||
|
|||||||
@@ -92,8 +92,104 @@ struct textentry
|
|||||||
guchar pad1;
|
guchar pad1;
|
||||||
guchar pad2;
|
guchar pad2;
|
||||||
GList *marks; /* List of found strings */
|
GList *marks; /* List of found strings */
|
||||||
|
GdkPixbuf *image; /* inline image shown below the text, or NULL */
|
||||||
|
GdkPixbuf *image_full; /* larger version for the image viewer */
|
||||||
|
char *image_url; /* the URL the image was loaded from */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* inline image display */
|
||||||
|
#define XTEXT_IMAGE_PADDING 4
|
||||||
|
#define XTEXT_IMAGE_MAX_WIDTH 480
|
||||||
|
#define XTEXT_IMAGE_MAX_HEIGHT 360
|
||||||
|
#define XTEXT_IMAGE_FULL_MAX_WIDTH 1600
|
||||||
|
#define XTEXT_IMAGE_FULL_MAX_HEIGHT 1200
|
||||||
|
|
||||||
|
/* an inline image load that has been started but not yet completed;
|
||||||
|
ent/buf/xtext are invalidated (entry removed) when any of them dies */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
guint id;
|
||||||
|
GtkXText *xtext;
|
||||||
|
xtext_buffer *buf;
|
||||||
|
textentry *ent;
|
||||||
|
char *url;
|
||||||
|
} xtext_image_pending;
|
||||||
|
|
||||||
|
static GSList *image_pending_list = NULL;
|
||||||
|
static guint image_pending_next_id = 1;
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_xtext_image_pending_forget (gboolean (*match) (xtext_image_pending *, gconstpointer), gconstpointer data)
|
||||||
|
{
|
||||||
|
GSList *list = image_pending_list;
|
||||||
|
|
||||||
|
while (list)
|
||||||
|
{
|
||||||
|
GSList *next = list->next;
|
||||||
|
xtext_image_pending *pending = list->data;
|
||||||
|
|
||||||
|
if (match (pending, data))
|
||||||
|
{
|
||||||
|
image_pending_list = g_slist_delete_link (image_pending_list, list);
|
||||||
|
g_free (pending->url);
|
||||||
|
g_free (pending);
|
||||||
|
}
|
||||||
|
list = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gtk_xtext_image_pending_match_ent (xtext_image_pending *pending, gconstpointer ent)
|
||||||
|
{
|
||||||
|
return pending->ent == ent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gtk_xtext_image_pending_match_buf (xtext_image_pending *pending, gconstpointer buf)
|
||||||
|
{
|
||||||
|
return pending->buf == buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gtk_xtext_image_pending_match_xtext (xtext_image_pending *pending, gconstpointer xtext)
|
||||||
|
{
|
||||||
|
return pending->xtext == xtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* number of extra screen lines an entry needs for its inline image */
|
||||||
|
|
||||||
|
static int
|
||||||
|
gtk_xtext_image_rows (GtkXText *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
if (ent->image == NULL || xtext->fontsize <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (gdk_pixbuf_get_height (ent->image) + XTEXT_IMAGE_PADDING * 2 +
|
||||||
|
xtext->fontsize - 1) / xtext->fontsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* free an entry's inline image and drop any pending load aimed at it;
|
||||||
|
must be called whenever a textentry is freed */
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_xtext_ent_free_image (textentry *ent)
|
||||||
|
{
|
||||||
|
gtk_xtext_image_pending_forget (gtk_xtext_image_pending_match_ent, ent);
|
||||||
|
|
||||||
|
if (ent->image)
|
||||||
|
{
|
||||||
|
g_object_unref (ent->image);
|
||||||
|
ent->image = NULL;
|
||||||
|
}
|
||||||
|
if (ent->image_full)
|
||||||
|
{
|
||||||
|
g_object_unref (ent->image_full);
|
||||||
|
ent->image_full = NULL;
|
||||||
|
}
|
||||||
|
g_free (ent->image_url);
|
||||||
|
ent->image_url = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
WORD_CLICK,
|
WORD_CLICK,
|
||||||
@@ -213,6 +309,7 @@ static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk
|
|||||||
static char * gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, int *ret_off, int *ret_len, GSList **slp);
|
static char * gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, int *ret_off, int *ret_len, GSList **slp);
|
||||||
static gboolean gtk_xtext_word_select_char (const unsigned char *ch);
|
static gboolean gtk_xtext_word_select_char (const unsigned char *ch);
|
||||||
static gboolean gtk_xtext_get_word_select_range (GtkXText *xtext, int x, int y, textentry **ret_ent, int *ret_off, int *ret_len);
|
static gboolean gtk_xtext_get_word_select_range (GtkXText *xtext, int x, int y, textentry **ret_ent, int *ret_off, int *ret_len);
|
||||||
|
static textentry * gtk_xtext_image_hit (GtkXText *xtext, int x, int y);
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
gtk_xtext_cursor_unref (GdkCursor *cursor)
|
gtk_xtext_cursor_unref (GdkCursor *cursor)
|
||||||
@@ -1062,6 +1159,8 @@ gtk_xtext_dispose (GObject *object)
|
|||||||
{
|
{
|
||||||
GtkXText *xtext = GTK_XTEXT (object);
|
GtkXText *xtext = GTK_XTEXT (object);
|
||||||
|
|
||||||
|
gtk_xtext_image_pending_forget (gtk_xtext_image_pending_match_xtext, xtext);
|
||||||
|
g_clear_pointer (&xtext->tooltip_url, g_free);
|
||||||
gtk_xtext_cleanup (xtext);
|
gtk_xtext_cleanup (xtext);
|
||||||
|
|
||||||
if (G_OBJECT_CLASS (gtk_xtext_parent_class)->dispose)
|
if (G_OBJECT_CLASS (gtk_xtext_parent_class)->dispose)
|
||||||
@@ -1443,7 +1542,7 @@ gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline,
|
|||||||
|
|
||||||
str = ent->str + gtk_xtext_find_subline (xtext, ent, subline);
|
str = ent->str + gtk_xtext_find_subline (xtext, ent, subline);
|
||||||
if (str >= ent->str + ent->str_len)
|
if (str >= ent->str + ent->str_len)
|
||||||
return 0;
|
return ent->str_len; /* e.g. the blank rows of an inline image */
|
||||||
|
|
||||||
/* Let user select left a few pixels to grab hidden text e.g. '<' */
|
/* Let user select left a few pixels to grab hidden text e.g. '<' */
|
||||||
if (x < indent - xtext->space_width)
|
if (x < indent - xtext->space_width)
|
||||||
@@ -2143,6 +2242,7 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
|
|||||||
int len;
|
int len;
|
||||||
int out_of_bounds = 0;
|
int out_of_bounds = 0;
|
||||||
int len_to_offset = 0;
|
int len_to_offset = 0;
|
||||||
|
gboolean has_hidden;
|
||||||
|
|
||||||
ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds);
|
ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds);
|
||||||
if (ent == NULL || out_of_bounds || offset < 0 || offset >= ent->str_len)
|
if (ent == NULL || out_of_bounds || offset < 0 || offset >= ent->str_len)
|
||||||
@@ -2187,10 +2287,15 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
|
|||||||
if (ret_len)
|
if (ret_len)
|
||||||
*ret_len = len; /* Length before stripping */
|
*ret_len = len; /* Length before stripping */
|
||||||
|
|
||||||
|
/* a word with hidden text is a placeholder (e.g. an inline image link
|
||||||
|
label); its whole visible part acts as the link */
|
||||||
|
has_hidden = memchr (word, ATTR_HIDDEN, len) != NULL;
|
||||||
|
|
||||||
word = gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, slp, FALSE);
|
word = gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, slp, FALSE);
|
||||||
|
|
||||||
/* avoid turning the cursor into a hand for non-url part of the word */
|
/* avoid turning the cursor into a hand for non-url part of the word */
|
||||||
if (xtext->urlcheck_function && xtext->urlcheck_function (GTK_WIDGET (xtext), word))
|
if (!has_hidden &&
|
||||||
|
xtext->urlcheck_function && xtext->urlcheck_function (GTK_WIDGET (xtext), word))
|
||||||
{
|
{
|
||||||
int start, end;
|
int start, end;
|
||||||
url_last (&start, &end);
|
url_last (&start, &end);
|
||||||
@@ -2274,12 +2379,43 @@ gtk_xtext_unrender_hilight (GtkXText *xtext)
|
|||||||
xtext->un_hilight = FALSE;
|
xtext->un_hilight = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* show (or stop showing) the destination URL of a hovered placeholder
|
||||||
|
word as the widget's tooltip */
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_xtext_set_url_tooltip (GtkXText *xtext, const char *url, int url_len)
|
||||||
|
{
|
||||||
|
if (url == NULL)
|
||||||
|
{
|
||||||
|
if (xtext->tooltip_url)
|
||||||
|
{
|
||||||
|
g_free (xtext->tooltip_url);
|
||||||
|
xtext->tooltip_url = NULL;
|
||||||
|
gtk_widget_set_tooltip_text (GTK_WIDGET (xtext), NULL);
|
||||||
|
xtext->tooltip_stamp_set = FALSE;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xtext->tooltip_url &&
|
||||||
|
strlen (xtext->tooltip_url) == (gsize) url_len &&
|
||||||
|
memcmp (xtext->tooltip_url, url, url_len) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_free (xtext->tooltip_url);
|
||||||
|
xtext->tooltip_url = g_strndup (url, url_len);
|
||||||
|
gtk_widget_set_tooltip_text (GTK_WIDGET (xtext), xtext->tooltip_url);
|
||||||
|
xtext->tooltip_stamp_set = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
gtk_xtext_leave_notify (GtkWidget * widget, GdkEventCrossing * event)
|
gtk_xtext_leave_notify (GtkWidget * widget, GdkEventCrossing * event)
|
||||||
{
|
{
|
||||||
GtkXText *xtext = GTK_XTEXT (widget);
|
GtkXText *xtext = GTK_XTEXT (widget);
|
||||||
GdkWindow *window = gtk_widget_get_window (widget);
|
GdkWindow *window = gtk_widget_get_window (widget);
|
||||||
|
|
||||||
|
gtk_xtext_set_url_tooltip (xtext, NULL, 0);
|
||||||
|
|
||||||
if (xtext->cursor_hand)
|
if (xtext->cursor_hand)
|
||||||
{
|
{
|
||||||
gtk_xtext_unrender_hilight (xtext);
|
gtk_xtext_unrender_hilight (xtext);
|
||||||
@@ -2343,6 +2479,7 @@ gtk_xtext_get_word_adjust (GtkXText *xtext, int x, int y, textentry **word_ent,
|
|||||||
GSList *slp = NULL;
|
GSList *slp = NULL;
|
||||||
unsigned char *word;
|
unsigned char *word;
|
||||||
int word_type = 0;
|
int word_type = 0;
|
||||||
|
gboolean placeholder = FALSE;
|
||||||
|
|
||||||
word = gtk_xtext_get_word (xtext, x, y, word_ent, offset, len, &slp);
|
word = gtk_xtext_get_word (xtext, x, y, word_ent, offset, len, &slp);
|
||||||
if (word)
|
if (word)
|
||||||
@@ -2352,7 +2489,18 @@ gtk_xtext_get_word_adjust (GtkXText *xtext, int x, int y, textentry **word_ent,
|
|||||||
word_type = xtext->urlcheck_function (GTK_WIDGET (xtext), word);
|
word_type = xtext->urlcheck_function (GTK_WIDGET (xtext), word);
|
||||||
if (word_type > 0)
|
if (word_type > 0)
|
||||||
{
|
{
|
||||||
if (url_last (&laststart, &lastend))
|
/* words with hidden text are placeholders (e.g. inline image
|
||||||
|
links): hilight the whole word, not just the URL inside it */
|
||||||
|
if (memchr ((*word_ent)->str + *offset, ATTR_HIDDEN, *len))
|
||||||
|
{
|
||||||
|
/* the label hides the real destination, so show the full
|
||||||
|
URL as a tooltip while hovering */
|
||||||
|
placeholder = TRUE;
|
||||||
|
if (url_last (&laststart, &lastend) && lastend > laststart)
|
||||||
|
gtk_xtext_set_url_tooltip (xtext,
|
||||||
|
(const char *) word + laststart, lastend - laststart);
|
||||||
|
}
|
||||||
|
else if (url_last (&laststart, &lastend))
|
||||||
{
|
{
|
||||||
int cumlen, startadj = 0, endadj = 0;
|
int cumlen, startadj = 0, endadj = 0;
|
||||||
offlen_t *meta;
|
offlen_t *meta;
|
||||||
@@ -2382,6 +2530,9 @@ gtk_xtext_get_word_adjust (GtkXText *xtext, int x, int y, textentry **word_ent,
|
|||||||
}
|
}
|
||||||
g_slist_free_full (slp, g_free);
|
g_slist_free_full (slp, g_free);
|
||||||
|
|
||||||
|
if (!placeholder)
|
||||||
|
gtk_xtext_set_url_tooltip (xtext, NULL, 0);
|
||||||
|
|
||||||
return word_type;
|
return word_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2513,6 +2664,20 @@ gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
tooltip_check:
|
tooltip_check:
|
||||||
|
/* hovering an inline image? it opens the viewer when clicked */
|
||||||
|
if (gtk_xtext_image_hit (xtext, x, y))
|
||||||
|
{
|
||||||
|
if (xtext->hilight_ent) /* moving here straight from a link */
|
||||||
|
gtk_xtext_leave_notify (widget, NULL);
|
||||||
|
if (!xtext->cursor_hand)
|
||||||
|
{
|
||||||
|
gdk_window_set_cursor (window, xtext->hand_cursor);
|
||||||
|
xtext->cursor_hand = TRUE;
|
||||||
|
xtext->cursor_resize = FALSE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
if (xtext->buffer->time_stamp && xtext->buffer->indent > 0 && x >= 0 && x < xtext->stamp_width)
|
if (xtext->buffer->time_stamp && xtext->buffer->indent > 0 && x >= 0 && x < xtext->stamp_width)
|
||||||
{
|
{
|
||||||
textentry *ent = gtk_xtext_find_char (xtext, x, y, NULL, NULL);
|
textentry *ent = gtk_xtext_find_char (xtext, x, y, NULL, NULL);
|
||||||
@@ -2620,6 +2785,46 @@ gtk_xtext_unselect (GtkXText *xtext)
|
|||||||
xtext->buffer->last_ent_end = NULL;
|
xtext->buffer->last_ent_end = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* check whether the given widget coordinates hit an entry's inline image;
|
||||||
|
returns the entry if so */
|
||||||
|
|
||||||
|
static textentry *
|
||||||
|
gtk_xtext_image_hit (GtkXText *xtext, int x, int y)
|
||||||
|
{
|
||||||
|
textentry *ent;
|
||||||
|
int line, subline, image_rows, text_rows, rel_y, img_x;
|
||||||
|
|
||||||
|
if (xtext->fontsize <= 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* adjust y value for negative rounding, double to int */
|
||||||
|
if (y < 0)
|
||||||
|
y -= xtext->fontsize;
|
||||||
|
|
||||||
|
line = (y + xtext->pixel_offset) / xtext->fontsize;
|
||||||
|
ent = gtk_xtext_nth (xtext, line + (int) xtext_adj_get_value (xtext->adj),
|
||||||
|
&subline);
|
||||||
|
if (ent == NULL || ent->image == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
image_rows = gtk_xtext_image_rows (xtext, ent);
|
||||||
|
text_rows = ent->subline_count - image_rows;
|
||||||
|
if (subline < text_rows)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
img_x = xtext->buffer->indent + XTEXT_IMAGE_PADDING;
|
||||||
|
if (x < img_x || x >= img_x + gdk_pixbuf_get_width (ent->image))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
rel_y = (subline - text_rows) * xtext->fontsize +
|
||||||
|
(y + xtext->pixel_offset) - (line * xtext->fontsize) -
|
||||||
|
XTEXT_IMAGE_PADDING;
|
||||||
|
if (rel_y < 0 || rel_y >= gdk_pixbuf_get_height (ent->image))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return ent;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event)
|
gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event)
|
||||||
{
|
{
|
||||||
@@ -2692,8 +2897,15 @@ gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event)
|
|||||||
|
|
||||||
if (!gtk_xtext_is_selecting (xtext))
|
if (!gtk_xtext_is_selecting (xtext))
|
||||||
{
|
{
|
||||||
word = gtk_xtext_get_word (xtext, event_x, event_y, 0, 0, 0, 0);
|
textentry *word_ent = NULL;
|
||||||
|
|
||||||
|
word = gtk_xtext_get_word (xtext, event_x, event_y, &word_ent, 0, 0, 0);
|
||||||
|
xtext->clicked_ent = word ? word_ent : NULL;
|
||||||
|
xtext->clicked_image_ent = word ? NULL :
|
||||||
|
gtk_xtext_image_hit (xtext, event_x, event_y);
|
||||||
g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, word ? word : NULL, event);
|
g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, word ? word : NULL, event);
|
||||||
|
xtext->clicked_ent = NULL;
|
||||||
|
xtext->clicked_image_ent = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2720,14 +2932,16 @@ gtk_xtext_button_press (GtkWidget * widget, GdkEventButton * event)
|
|||||||
|
|
||||||
if (event->button == 3 || event->button == 2) /* right/middle click */
|
if (event->button == 3 || event->button == 2) /* right/middle click */
|
||||||
{
|
{
|
||||||
word = gtk_xtext_get_word (xtext, x, y, 0, 0, 0, 0);
|
word = gtk_xtext_get_word (xtext, x, y, &ent, 0, 0, 0);
|
||||||
if (word)
|
if (word)
|
||||||
{
|
{
|
||||||
|
xtext->clicked_ent = ent;
|
||||||
g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
|
g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
|
||||||
word, event);
|
word, event);
|
||||||
} else
|
} else
|
||||||
g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
|
g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
|
||||||
"", event);
|
"", event);
|
||||||
|
xtext->clicked_ent = NULL;
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4105,6 +4319,7 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line,
|
|||||||
unsigned char *str;
|
unsigned char *str;
|
||||||
int indent, taken, entline, len, y, start_subline;
|
int indent, taken, entline, len, y, start_subline;
|
||||||
int emphasis = 0;
|
int emphasis = 0;
|
||||||
|
int image_rows;
|
||||||
|
|
||||||
entline = taken = 0;
|
entline = taken = 0;
|
||||||
str = ent->str;
|
str = ent->str;
|
||||||
@@ -4175,6 +4390,56 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line,
|
|||||||
}
|
}
|
||||||
while (str < ent->str + ent->str_len);
|
while (str < ent->str + ent->str_len);
|
||||||
|
|
||||||
|
/* draw the inline image on its blank rows below the text. 'subline'
|
||||||
|
still holds the rows left to skip when the page starts inside the
|
||||||
|
image area (the text loop above only consumes text sublines). */
|
||||||
|
image_rows = gtk_xtext_image_rows (xtext, ent);
|
||||||
|
if (image_rows > 0)
|
||||||
|
{
|
||||||
|
int skip = MIN (subline, image_rows);
|
||||||
|
int rows = image_rows - skip;
|
||||||
|
int first_row_y = (xtext->fontsize * line) - xtext->pixel_offset;
|
||||||
|
int drawn = 0;
|
||||||
|
gboolean render_image = !xtext->dont_render && !xtext->skip_border_fills &&
|
||||||
|
!xtext->render_hilights_only;
|
||||||
|
|
||||||
|
while (rows > 0 && line < lines_max)
|
||||||
|
{
|
||||||
|
y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset;
|
||||||
|
if (render_image)
|
||||||
|
{
|
||||||
|
int bg_x = MAX (0, xtext->clip_x);
|
||||||
|
int bg_w = MIN (win_width + MARGIN, xtext->clip_x2) - bg_x;
|
||||||
|
|
||||||
|
if (bg_w > 0)
|
||||||
|
xtext_draw_bg (xtext, bg_x, y - xtext->font->ascent, bg_w, xtext->fontsize);
|
||||||
|
gtk_xtext_draw_sep (xtext, y - xtext->font->ascent);
|
||||||
|
}
|
||||||
|
line++;
|
||||||
|
taken++;
|
||||||
|
rows--;
|
||||||
|
drawn++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawn > 0 && render_image)
|
||||||
|
{
|
||||||
|
/* this may be the shared draw context, so the clip has to be
|
||||||
|
undone before anything else is rendered with it */
|
||||||
|
cairo_t *cr = xtext_create_context (xtext);
|
||||||
|
int img_x = xtext->buffer->indent + XTEXT_IMAGE_PADDING;
|
||||||
|
int img_y = first_row_y - (skip * xtext->fontsize) + XTEXT_IMAGE_PADDING;
|
||||||
|
|
||||||
|
cairo_save (cr);
|
||||||
|
cairo_rectangle (cr, 0, first_row_y, win_width + MARGIN,
|
||||||
|
drawn * xtext->fontsize);
|
||||||
|
cairo_clip (cr);
|
||||||
|
gdk_cairo_set_source_pixbuf (cr, ent->image, img_x, img_y);
|
||||||
|
cairo_paint (cr);
|
||||||
|
cairo_restore (cr);
|
||||||
|
cairo_destroy (cr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline));
|
gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline));
|
||||||
|
|
||||||
return taken;
|
return taken;
|
||||||
@@ -4333,6 +4598,7 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent)
|
|||||||
unsigned char *str;
|
unsigned char *str;
|
||||||
int indent, len;
|
int indent, len;
|
||||||
int win_width;
|
int win_width;
|
||||||
|
int image_rows, i;
|
||||||
|
|
||||||
g_slist_free (ent->sublines);
|
g_slist_free (ent->sublines);
|
||||||
ent->sublines = NULL;
|
ent->sublines = NULL;
|
||||||
@@ -4342,21 +4608,27 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent)
|
|||||||
if (win_width >= ent->indent + ent->str_width)
|
if (win_width >= ent->indent + ent->str_width)
|
||||||
{
|
{
|
||||||
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (ent->str_len));
|
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (ent->str_len));
|
||||||
ent->subline_count = 1;
|
|
||||||
return ent->subline_count;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
indent = ent->indent;
|
|
||||||
str = ent->str;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
len = find_next_wrap (buf->xtext, ent, str, win_width, indent);
|
indent = ent->indent;
|
||||||
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (str + len - ent->str));
|
str = ent->str;
|
||||||
indent = buf->indent;
|
|
||||||
str += len;
|
do
|
||||||
|
{
|
||||||
|
len = find_next_wrap (buf->xtext, ent, str, win_width, indent);
|
||||||
|
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (str + len - ent->str));
|
||||||
|
indent = buf->indent;
|
||||||
|
str += len;
|
||||||
|
}
|
||||||
|
while (str < ent->str + ent->str_len);
|
||||||
}
|
}
|
||||||
while (str < ent->str + ent->str_len);
|
|
||||||
|
/* extra blank rows that make room for an attached inline image;
|
||||||
|
their offset is str_len, i.e. past the end of the text */
|
||||||
|
image_rows = gtk_xtext_image_rows (buf->xtext, ent);
|
||||||
|
for (i = 0; i < image_rows; i++)
|
||||||
|
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER ((gint) ent->str_len));
|
||||||
|
|
||||||
ent->subline_count = g_slist_length (ent->sublines);
|
ent->subline_count = g_slist_length (ent->sublines);
|
||||||
return ent->subline_count;
|
return ent->subline_count;
|
||||||
@@ -4779,6 +5051,8 @@ gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent)
|
|||||||
ent->sublines = NULL;
|
ent->sublines = NULL;
|
||||||
ent->subline_count = 0;
|
ent->subline_count = 0;
|
||||||
|
|
||||||
|
gtk_xtext_ent_free_image (ent);
|
||||||
|
|
||||||
g_free (ent);
|
g_free (ent);
|
||||||
return visible;
|
return visible;
|
||||||
}
|
}
|
||||||
@@ -4916,6 +5190,7 @@ gtk_xtext_clear (xtext_buffer *buf, int lines)
|
|||||||
while (buf->text_first)
|
while (buf->text_first)
|
||||||
{
|
{
|
||||||
next = buf->text_first->next;
|
next = buf->text_first->next;
|
||||||
|
gtk_xtext_ent_free_image (buf->text_first);
|
||||||
g_free (buf->text_first);
|
g_free (buf->text_first);
|
||||||
buf->text_first = next;
|
buf->text_first = next;
|
||||||
}
|
}
|
||||||
@@ -5435,6 +5710,9 @@ gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp)
|
|||||||
ent->next = NULL;
|
ent->next = NULL;
|
||||||
ent->marks = NULL;
|
ent->marks = NULL;
|
||||||
ent->subline_count = 0;
|
ent->subline_count = 0;
|
||||||
|
ent->image = NULL;
|
||||||
|
ent->image_full = NULL;
|
||||||
|
ent->image_url = NULL;
|
||||||
|
|
||||||
if (ent->indent < MARGIN)
|
if (ent->indent < MARGIN)
|
||||||
ent->indent = MARGIN; /* 2 pixels is the left margin */
|
ent->indent = MARGIN; /* 2 pixels is the left margin */
|
||||||
@@ -5955,13 +6233,260 @@ gtk_xtext_buffer_free (xtext_buffer *buf)
|
|||||||
gtk_xtext_search_fini (buf);
|
gtk_xtext_search_fini (buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gtk_xtext_image_pending_forget (gtk_xtext_image_pending_match_buf, buf);
|
||||||
|
|
||||||
ent = buf->text_first;
|
ent = buf->text_first;
|
||||||
while (ent)
|
while (ent)
|
||||||
{
|
{
|
||||||
next = ent->next;
|
next = ent->next;
|
||||||
|
gtk_xtext_ent_free_image (ent);
|
||||||
g_free (ent);
|
g_free (ent);
|
||||||
ent = next;
|
ent = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free (buf);
|
g_free (buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================= */
|
||||||
|
/* ========== INLINE IMAGE API ============= */
|
||||||
|
/* ========================================= */
|
||||||
|
|
||||||
|
/* the entry the last word_click signal was emitted for; only valid while
|
||||||
|
handling that signal */
|
||||||
|
|
||||||
|
textentry *
|
||||||
|
gtk_xtext_get_clicked_entry (GtkXText *xtext)
|
||||||
|
{
|
||||||
|
return xtext->clicked_ent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the entry whose inline image the last word_click signal (with a NULL
|
||||||
|
word) landed on; only valid while handling that signal */
|
||||||
|
|
||||||
|
textentry *
|
||||||
|
gtk_xtext_get_clicked_image_entry (GtkXText *xtext)
|
||||||
|
{
|
||||||
|
return xtext->clicked_image_ent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check that 'ent' is still an entry of the currently shown buffer; used to
|
||||||
|
validate entry pointers that were remembered across main loop iterations */
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
gtk_xtext_buffer_contains (GtkXText *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
textentry *walk;
|
||||||
|
|
||||||
|
if (ent == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
for (walk = xtext->buffer->text_first; walk; walk = walk->next)
|
||||||
|
{
|
||||||
|
if (walk == ent)
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
gtk_xtext_entry_has_image (GtkXText *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
return ent != NULL && ent->image != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the URL of the entry's attached image, or NULL if it has none */
|
||||||
|
|
||||||
|
const char *
|
||||||
|
gtk_xtext_entry_get_image_url (GtkXText *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
return ent && ent->image ? ent->image_url : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* larger version of the entry's attached image, for the viewer */
|
||||||
|
|
||||||
|
GdkPixbuf *
|
||||||
|
gtk_xtext_entry_get_full_image (GtkXText *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
if (ent == NULL)
|
||||||
|
return NULL;
|
||||||
|
return ent->image_full ? ent->image_full : ent->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the entry's subline count changed (image attached or removed):
|
||||||
|
redo its layout, fix the scrollbar and redraw */
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_xtext_image_relayout (GtkXText *xtext, xtext_buffer *buf, textentry *ent)
|
||||||
|
{
|
||||||
|
buf->num_lines -= ent->subline_count;
|
||||||
|
buf->num_lines += gtk_xtext_lines_taken (buf, ent);
|
||||||
|
buf->pagetop_ent = NULL;
|
||||||
|
dontscroll (buf);
|
||||||
|
|
||||||
|
if (xtext->buffer == buf)
|
||||||
|
{
|
||||||
|
gtk_xtext_adjustment_set (buf, TRUE);
|
||||||
|
if (buf->scrollbar_down)
|
||||||
|
xtext_adj_set_value (xtext->adj,
|
||||||
|
xtext_adj_get_upper (xtext->adj) -
|
||||||
|
xtext_adj_get_page_size (xtext->adj));
|
||||||
|
if (gtk_widget_get_realized (GTK_WIDGET (xtext)))
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (xtext));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scale down (never up) to fit within max_width x max_height */
|
||||||
|
|
||||||
|
static GdkPixbuf *
|
||||||
|
gtk_xtext_image_scale_max (GdkPixbuf *pixbuf, int max_width, int max_height)
|
||||||
|
{
|
||||||
|
int width = gdk_pixbuf_get_width (pixbuf);
|
||||||
|
int height = gdk_pixbuf_get_height (pixbuf);
|
||||||
|
int new_width, new_height;
|
||||||
|
double scale;
|
||||||
|
|
||||||
|
if (width < 1 || height < 1)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (width <= max_width && height <= max_height)
|
||||||
|
return g_object_ref (pixbuf);
|
||||||
|
|
||||||
|
scale = MIN ((double) max_width / width, (double) max_height / height);
|
||||||
|
new_width = MAX (1, (int) (width * scale));
|
||||||
|
new_height = MAX (1, (int) (height * scale));
|
||||||
|
|
||||||
|
return gdk_pixbuf_scale_simple (pixbuf, new_width, new_height,
|
||||||
|
GDK_INTERP_BILINEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scale down (never up) to fit the usual text area */
|
||||||
|
|
||||||
|
static GdkPixbuf *
|
||||||
|
gtk_xtext_image_scale (xtext_buffer *buf, GdkPixbuf *pixbuf)
|
||||||
|
{
|
||||||
|
int max_width = XTEXT_IMAGE_MAX_WIDTH;
|
||||||
|
int avail;
|
||||||
|
|
||||||
|
avail = buf->window_width - buf->indent - MARGIN - XTEXT_IMAGE_PADDING * 2;
|
||||||
|
if (avail >= 64 && avail < max_width)
|
||||||
|
max_width = avail;
|
||||||
|
|
||||||
|
return gtk_xtext_image_scale_max (pixbuf, max_width, XTEXT_IMAGE_MAX_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* register an asynchronous image load for 'ent'. Returns a handle to pass
|
||||||
|
to gtk_xtext_image_load_finish(), or 0 if the entry already has an image
|
||||||
|
or a load underway. */
|
||||||
|
|
||||||
|
guint
|
||||||
|
gtk_xtext_image_load_begin (GtkXText *xtext, textentry *ent, const char *url)
|
||||||
|
{
|
||||||
|
xtext_image_pending *pending;
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (ent == NULL || ent->image != NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (list = image_pending_list; list; list = list->next)
|
||||||
|
{
|
||||||
|
pending = list->data;
|
||||||
|
if (pending->ent == ent)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending = g_new0 (xtext_image_pending, 1);
|
||||||
|
pending->id = image_pending_next_id++;
|
||||||
|
if (image_pending_next_id == 0) /* skip 0 on wrap, it means failure */
|
||||||
|
image_pending_next_id = 1;
|
||||||
|
pending->xtext = xtext;
|
||||||
|
pending->buf = xtext->buffer;
|
||||||
|
pending->ent = ent;
|
||||||
|
pending->url = g_strdup (url);
|
||||||
|
image_pending_list = g_slist_prepend (image_pending_list, pending);
|
||||||
|
|
||||||
|
return pending->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* complete a load started with gtk_xtext_image_load_begin(). pixbuf may be
|
||||||
|
NULL to abandon the load (e.g. download failed). Returns TRUE if the
|
||||||
|
image was attached, FALSE if it was dropped (failure, or the entry is
|
||||||
|
gone). The caller keeps ownership of the pixbuf. */
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
gtk_xtext_image_load_finish (guint handle, GdkPixbuf *pixbuf)
|
||||||
|
{
|
||||||
|
xtext_image_pending *pending = NULL;
|
||||||
|
GSList *list;
|
||||||
|
GdkPixbuf *scaled;
|
||||||
|
GtkXText *xtext;
|
||||||
|
xtext_buffer *buf;
|
||||||
|
textentry *ent;
|
||||||
|
|
||||||
|
if (handle == 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
for (list = image_pending_list; list; list = list->next)
|
||||||
|
{
|
||||||
|
xtext_image_pending *walk = list->data;
|
||||||
|
if (walk->id == handle)
|
||||||
|
{
|
||||||
|
pending = walk;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending == NULL) /* entry, buffer or widget died meanwhile */
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
xtext = pending->xtext;
|
||||||
|
buf = pending->buf;
|
||||||
|
ent = pending->ent;
|
||||||
|
image_pending_list = g_slist_remove (image_pending_list, pending);
|
||||||
|
|
||||||
|
if (pixbuf == NULL || ent->image != NULL)
|
||||||
|
{
|
||||||
|
g_free (pending->url);
|
||||||
|
g_free (pending);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
scaled = gtk_xtext_image_scale (buf, pixbuf);
|
||||||
|
if (scaled == NULL)
|
||||||
|
{
|
||||||
|
g_free (pending->url);
|
||||||
|
g_free (pending);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->image = scaled;
|
||||||
|
ent->image_full = gtk_xtext_image_scale_max (pixbuf,
|
||||||
|
XTEXT_IMAGE_FULL_MAX_WIDTH, XTEXT_IMAGE_FULL_MAX_HEIGHT);
|
||||||
|
ent->image_url = pending->url; /* transferred */
|
||||||
|
g_free (pending);
|
||||||
|
gtk_xtext_image_relayout (xtext, buf, ent);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove an entry's inline image again. Returns FALSE if it had none. */
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
gtk_xtext_image_remove (GtkXText *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
if (ent == NULL || ent->image == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_object_unref (ent->image);
|
||||||
|
ent->image = NULL;
|
||||||
|
if (ent->image_full)
|
||||||
|
{
|
||||||
|
g_object_unref (ent->image_full);
|
||||||
|
ent->image_full = NULL;
|
||||||
|
}
|
||||||
|
g_free (ent->image_url);
|
||||||
|
ent->image_url = NULL;
|
||||||
|
gtk_xtext_image_relayout (xtext, xtext->buffer, ent);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ struct _GtkXText
|
|||||||
int hilight_end;
|
int hilight_end;
|
||||||
time_t tooltip_stamp;
|
time_t tooltip_stamp;
|
||||||
unsigned int tooltip_stamp_set:1;
|
unsigned int tooltip_stamp_set:1;
|
||||||
|
char *tooltip_url; /* URL shown while hovering a placeholder */
|
||||||
|
|
||||||
guint16 fontwidth[128]; /* each char's width, only the ASCII ones */
|
guint16 fontwidth[128]; /* each char's width, only the ASCII ones */
|
||||||
|
|
||||||
@@ -254,6 +255,9 @@ struct _GtkXText
|
|||||||
unsigned int force_render:1;
|
unsigned int force_render:1;
|
||||||
unsigned int color_paste:1; /* CTRL was pressed when selection finished */
|
unsigned int color_paste:1; /* CTRL was pressed when selection finished */
|
||||||
|
|
||||||
|
textentry *clicked_ent; /* entry under the last word_click, or NULL */
|
||||||
|
textentry *clicked_image_ent; /* entry whose inline image was clicked */
|
||||||
|
|
||||||
/* settings/prefs */
|
/* settings/prefs */
|
||||||
unsigned int auto_indent:1;
|
unsigned int auto_indent:1;
|
||||||
unsigned int thinline:1;
|
unsigned int thinline:1;
|
||||||
@@ -309,4 +313,19 @@ void gtk_xtext_buffer_free (xtext_buffer *buf);
|
|||||||
void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render);
|
void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render);
|
||||||
void gtk_xtext_copy_selection (GtkXText *xtext);
|
void gtk_xtext_copy_selection (GtkXText *xtext);
|
||||||
|
|
||||||
|
/* inline image support. A textentry can have one image attached, which is
|
||||||
|
* rendered below its text. Loading is asynchronous: begin() registers the
|
||||||
|
* target entry and returns a handle, finish() attaches the downloaded pixbuf
|
||||||
|
* if the entry still exists. The handle stays valid even if the entry, its
|
||||||
|
* buffer or the widget are destroyed in the meantime. */
|
||||||
|
textentry *gtk_xtext_get_clicked_entry (GtkXText *xtext);
|
||||||
|
textentry *gtk_xtext_get_clicked_image_entry (GtkXText *xtext);
|
||||||
|
gboolean gtk_xtext_buffer_contains (GtkXText *xtext, textentry *ent);
|
||||||
|
gboolean gtk_xtext_entry_has_image (GtkXText *xtext, textentry *ent);
|
||||||
|
const char *gtk_xtext_entry_get_image_url (GtkXText *xtext, textentry *ent);
|
||||||
|
GdkPixbuf *gtk_xtext_entry_get_full_image (GtkXText *xtext, textentry *ent);
|
||||||
|
guint gtk_xtext_image_load_begin (GtkXText *xtext, textentry *ent, const char *url);
|
||||||
|
gboolean gtk_xtext_image_load_finish (guint handle, GdkPixbuf *pixbuf);
|
||||||
|
gboolean gtk_xtext_image_remove (GtkXText *xtext, textentry *ent);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
<Gtk3Immodules Include="$(DepsRoot)\lib\gtk-3.0\3.0.0\immodules\**\*" />
|
<Gtk3Immodules Include="$(DepsRoot)\lib\gtk-3.0\3.0.0\immodules\**\*" />
|
||||||
<Gtk3PrintBackends Include="$(DepsRoot)\lib\gtk-3.0\3.0.0\printbackends\**\*" />
|
<Gtk3PrintBackends Include="$(DepsRoot)\lib\gtk-3.0\3.0.0\printbackends\**\*" />
|
||||||
<GSettingsSchemas Include="$(DepsRoot)\share\glib-2.0\schemas\*" />
|
<GSettingsSchemas Include="$(DepsRoot)\share\glib-2.0\schemas\*" />
|
||||||
|
<GioModules Include="$(DepsRoot)\lib\gio\modules\**\*" />
|
||||||
<GdkPixbufLoaders Include="$(DepsRoot)\lib\gdk-pixbuf-2.0\**\loaders\**\*" />
|
<GdkPixbufLoaders Include="$(DepsRoot)\lib\gdk-pixbuf-2.0\**\loaders\**\*" />
|
||||||
<GdkPixbufLoaderCache Include="$(DepsRoot)\lib\gdk-pixbuf-2.0\**\loaders.cache" />
|
<GdkPixbufLoaderCache Include="$(DepsRoot)\lib\gdk-pixbuf-2.0\**\loaders.cache" />
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
<Copy SourceFiles="@(GdkPixbufLoaders)" DestinationFiles="@(GdkPixbufLoaders->'$(ZoiteChatRel)\lib\gdk-pixbuf-2.0\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(GdkPixbufLoaders)" DestinationFiles="@(GdkPixbufLoaders->'$(ZoiteChatRel)\lib\gdk-pixbuf-2.0\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(GdkPixbufLoaderCache)" DestinationFiles="@(GdkPixbufLoaderCache->'$(ZoiteChatRel)\lib\gdk-pixbuf-2.0\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(GdkPixbufLoaderCache)" DestinationFiles="@(GdkPixbufLoaderCache->'$(ZoiteChatRel)\lib\gdk-pixbuf-2.0\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(GSettingsSchemas)" DestinationFiles="@(GSettingsSchemas->'$(ZoiteChatRel)\share\glib-2.0\schemas\%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(GSettingsSchemas)" DestinationFiles="@(GSettingsSchemas->'$(ZoiteChatRel)\share\glib-2.0\schemas\%(Filename)%(Extension)')" />
|
||||||
|
<Copy SourceFiles="@(GioModules)" DestinationFiles="@(GioModules->'$(ZoiteChatRel)\lib\gio\modules\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(Share)" DestinationFiles="@(Share->'$(ZoiteChatRel)\share\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(Share)" DestinationFiles="@(Share->'$(ZoiteChatRel)\share\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="..\..\COPYING" DestinationFolder="$(ZoiteChatRel)\share\doc\zoitechat" />
|
<Copy SourceFiles="..\..\COPYING" DestinationFolder="$(ZoiteChatRel)\share\doc\zoitechat" />
|
||||||
<Copy SourceFiles="$(WinSparklePath)\COPYING" DestinationFolder="$(ZoiteChatRel)\share\doc\WinSparkle" />
|
<Copy SourceFiles="$(WinSparklePath)\COPYING" DestinationFolder="$(ZoiteChatRel)\share\doc\WinSparkle" />
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ Source: "tiff.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
|||||||
Source: "plugins\hcnotifications-winrt.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: libs
|
Source: "plugins\hcnotifications-winrt.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: libs
|
||||||
|
|
||||||
Source: "lib\enchant\*"; DestDir: "{app}\lib\enchant"; Flags: ignoreversion; Components: libs
|
Source: "lib\enchant\*"; DestDir: "{app}\lib\enchant"; Flags: ignoreversion; Components: libs
|
||||||
|
Source: "lib\gio\modules\*"; DestDir: "{app}\lib\gio\modules"; Flags: ignoreversion recursesubdirs; Components: libs
|
||||||
|
|
||||||
Source: "girepository-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\lua
|
Source: "girepository-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\lua
|
||||||
Source: "lua51.dll"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\lua
|
Source: "lua51.dll"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\lua
|
||||||
|
|||||||
Reference in New Issue
Block a user