diff options
Diffstat (limited to 'src/interface-gtk')
-rw-r--r-- | src/interface-gtk/gtk-info-popup.c | 2 | ||||
-rw-r--r-- | src/interface-gtk/gtk-info-popup.h | 2 | ||||
-rw-r--r-- | src/interface-gtk/gtk-label.c | 2 | ||||
-rw-r--r-- | src/interface-gtk/gtk-label.h | 2 | ||||
-rw-r--r-- | src/interface-gtk/interface.c | 301 |
5 files changed, 211 insertions, 98 deletions
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c index 744900d..4e25224 100644 --- a/src/interface-gtk/gtk-info-popup.c +++ b/src/interface-gtk/gtk-info-popup.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2023 Robin Haberkorn + * Copyright (C) 2012-2024 Robin Haberkorn * * 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 diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h index de4b463..c3a62ec 100644 --- a/src/interface-gtk/gtk-info-popup.h +++ b/src/interface-gtk/gtk-info-popup.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2023 Robin Haberkorn + * Copyright (C) 2012-2024 Robin Haberkorn * * 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 diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c index c1f4867..50cd345 100644 --- a/src/interface-gtk/gtk-label.c +++ b/src/interface-gtk/gtk-label.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2023 Robin Haberkorn + * Copyright (C) 2012-2024 Robin Haberkorn * * 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 diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h index d2e2314..bed6642 100644 --- a/src/interface-gtk/gtk-label.h +++ b/src/interface-gtk/gtk-label.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2023 Robin Haberkorn + * Copyright (C) 2012-2024 Robin Haberkorn * * 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 diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index 253600a..843ad15 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2023 Robin Haberkorn + * Copyright (C) 2012-2024 Robin Haberkorn * * 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 @@ -279,7 +279,8 @@ teco_interface_init(void) "type-label"); gtk_header_bar_pack_start(GTK_HEADER_BAR(teco_interface.info_bar_widget), teco_interface.info_type_widget); - if (teco_interface.xembed_id || teco_interface.no_csd) { + if (teco_interface.xembed_id || teco_interface.no_csd || + !g_strcmp0(g_getenv("GTK_CSD"), "0")) { /* fall back to adding the info bar as an ordinary widget */ gtk_box_pack_start(GTK_BOX(vbox), teco_interface.info_bar_widget, FALSE, FALSE, 0); @@ -390,12 +391,6 @@ teco_interface_init(void) GOptionGroup * teco_interface_get_options(void) { - /* - * FIXME: On platforms where you want to disable CSD, you usually - * want to disable it always, so it should be configurable in the SciTECO - * profile. - * On the other hand, you could just install gtk3-nocsd. - */ static const GOptionEntry entries[] = { {"no-csd", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &teco_interface.no_csd, @@ -656,15 +651,46 @@ teco_interface_get_selection_by_name(const gchar *name) return gdk_atom_intern(name, FALSE); } +static void +teco_interface_clipboard_provide(GtkClipboard *clipboard, GtkSelectionData *selection, guint info, gpointer userdata) +{ + GString *str = userdata; + gtk_selection_data_set_text(selection, str->str, str->len); +} + +static void +teco_interface_clipboard_clear(GtkClipboard *clipboard, gpointer userdata) +{ + GString *str = userdata; + g_string_free(str, TRUE); +} + gboolean teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error) { + static const GtkTargetEntry target = {"UTF8_STRING", 0, 0}; GtkClipboard *clipboard = gtk_clipboard_get(teco_interface_get_selection_by_name(name)); + if (!str) { + gtk_clipboard_clear(clipboard); + return TRUE; + } + /* - * NOTE: function has compatible semantics for str_len < 0. + * NOTE: gtk_clipboard_set_text() would ignore embedded nulls, + * even though it takes a length. + * We could theoretically avoid one allocation, but don't yet have proper types + * to store string data with length in one heap object. */ - gtk_clipboard_set_text(clipboard, str, str_len); + GString *gstr = g_string_new_len(str, str_len); + if (!gtk_clipboard_set_with_data(clipboard, &target, 1, + teco_interface_clipboard_provide, + teco_interface_clipboard_clear, gstr)) { + g_string_free(gstr, TRUE); + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CLIPBOARD, + "Cannot set clipboard"); + return FALSE; + } return TRUE; } @@ -674,16 +700,28 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError { GtkClipboard *clipboard = gtk_clipboard_get(teco_interface_get_selection_by_name(name)); /* - * Could return NULL for an empty clipboard. + * gtk_clipboard_wait_for_text() does not return the text length, + * so it doesn't work with embedded nulls. + * gtk_clipboard_wait_for_contents() could also return NULL for empty clipboards. * - * FIXME: This converts to UTF8 and we loose the ability - * to get clipboard with embedded nulls. + * NOTE: This also drives the main event loop, + * which should be safe (see teco_interface_key_pressed_cb()). */ - g_autofree gchar *contents = gtk_clipboard_wait_for_text(clipboard); + GdkAtom utf8_string = gdk_atom_intern_static_string("UTF8_STRING"); + g_autoptr(GtkSelectionData) contents = gtk_clipboard_wait_for_contents(clipboard, utf8_string); + if (!contents) { + *len = 0; + if (str) + *str = NULL; + return TRUE; + } - *len = contents ? strlen(contents) : 0; - if (str) - *str = g_steal_pointer(&contents); + *len = gtk_selection_data_get_length(contents); + if (str) { + /* gtk_selection_data_get_text() does not work with embedded nulls */ + *str = memcpy(g_malloc(*len+1), gtk_selection_data_get_data(contents), *len); + (*str)[*len] = '\0'; + } return TRUE; } @@ -881,19 +919,50 @@ teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str, gpointer use { g_autoptr(GError) error = NULL; - /* - * FIXME: This is only for consistency as long as we - * do not support Unicode. - */ - for (char *p = str; *p != '\0'; p = g_utf8_next_char(p)) - if (g_utf8_get_char(p) >= 0x80) - return; - if (!teco_cmdline_keypress(str, strlen(str), &error) && g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT)) gtk_main_quit(); } +/** + * Try to find an ANSI (latin) key for a given keypress. + * + * If the given key press does not generate a key from the ANSI + * range, it tries to find one in another group. + * + * @param event Key event to look up. In case of success, + * this event structure might also be written to. + * @return The codepoint of the ANSI version or 0 if there is + * no fitting ANSI/latin key. + */ +static gchar +teco_interface_get_ansi_key(GdkEventKey *event) +{ + gunichar cp = gdk_keyval_to_unicode(event->keyval); + if (cp && cp < 0x80) + return cp; + + GdkKeymap *map = gdk_keymap_get_for_display(gdk_window_get_display(event->window)); + g_autofree GdkKeymapKey *keys = NULL; + g_autofree guint *keyvals = NULL; + gint n_entries = 0; + + gdk_keymap_get_entries_for_keycode(map, event->hardware_keycode, + &keys, &keyvals, &n_entries); + for (gint i = 0; i < n_entries; i++) { + g_assert(keys[i].keycode == event->hardware_keycode); + cp = gdk_keyval_to_unicode(keyvals[i]); + if (cp && cp < 0x80 && + gdk_keyval_is_upper(keyvals[i]) == gdk_keyval_is_upper(event->keyval)) { + event->keyval = keyvals[i]; + event->group = keys[i].group; + return cp; + } + } + + return 0; +} + static gboolean teco_interface_handle_key_press(GdkEventKey *event, GError **error) { @@ -901,19 +970,19 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) switch (event->keyval) { case GDK_KEY_Escape: - if (!teco_cmdline_keypress_c('\e', error)) + if (!teco_cmdline_keymacro_c('\e', error)) return FALSE; break; case GDK_KEY_BackSpace: - if (!teco_cmdline_keypress_c(TECO_CTL_KEY('H'), error)) + if (!teco_cmdline_keymacro_c(TECO_CTL_KEY('H'), error)) return FALSE; break; case GDK_KEY_Tab: - if (!teco_cmdline_keypress_c('\t', error)) + if (!teco_cmdline_keymacro_c('\t', error)) return FALSE; break; case GDK_KEY_Return: - if (!teco_cmdline_keypress_c('\n', error)) + if (!teco_cmdline_keymacro_c('\n', error)) return FALSE; break; @@ -922,12 +991,12 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) */ #define FN(KEY, MACRO) \ case GDK_KEY_##KEY: \ - if (!teco_cmdline_fnmacro(#MACRO, error)) \ + if (!teco_cmdline_keymacro(#MACRO, -1, error)) \ return FALSE; \ break #define FNS(KEY, MACRO) \ case GDK_KEY_##KEY: \ - if (!teco_cmdline_fnmacro(event->state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, error)) \ + if (!teco_cmdline_keymacro(event->state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, -1, error)) \ return FALSE; \ break FN(Down, DOWN); FN(Up, UP); @@ -939,8 +1008,8 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) gchar macro_name[3+1]; g_snprintf(macro_name, sizeof(macro_name), - "F%d", event->keyval - GDK_KEY_F1 + 1); - if (!teco_cmdline_fnmacro(macro_name, error)) + "F%d", event->keyval - GDK_KEY_F1 + 1); + if (!teco_cmdline_keymacro(macro_name, -1, error)) return FALSE; break; } @@ -960,33 +1029,72 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error) /* * Control keys and keys with printable representation */ - default: { - gunichar u = gdk_keyval_to_unicode(event->keyval); + default: + /* + * NOTE: Alt-Gr key-combinations are sometimes reported as + * Ctrl+Alt, so we filter those out. + */ + if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK) { + gchar c = teco_interface_get_ansi_key(event); + if (c) { + if (!teco_cmdline_keymacro_c(TECO_CTL_KEY(g_ascii_toupper(c)), error)) + return FALSE; + break; + } + } - if (u && u < 0x80 && (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK) { - /* - * NOTE: Alt-Gr key-combinations are sometimes reported as - * Ctrl+Alt, so we filter those out. - */ - if (!teco_cmdline_keypress_c(TECO_CTL_KEY(g_ascii_toupper(u)), error)) + /* + * First look up a key macro. + * Only if it's undefined, we try to automatically find an ANSI key. + * On the downside, this means we cannot define key macros for dead keys + * or keys that require some sort of input method editing. + * + * FIXME: This might be a good reason to be able to disable the + * automatic ANSIfication, as we could look up the key macro in + * teco_interface_cmdline_commit_cb(). + */ + gunichar cp = gdk_keyval_to_unicode(event->keyval); + if (cp) { + char buf[6]; + gsize len = g_unichar_to_utf8(cp, buf); + teco_keymacro_status_t rc = teco_cmdline_keymacro(buf, len, error); + if (rc == TECO_KEYMACRO_ERROR) return FALSE; - } else { - /* - * This is necessary to handle dead keys and in the future - * for inputting Asian languages. - * - * FIXME: We do not yet support preediting. - * It would be easier to forward the event to the Scintilla - * widget and use its existing IM support. - * But this breaks the event freezing and results in flickering. - */ - gtk_im_context_filter_keypress(teco_interface.input_method, event); + if (rc == TECO_KEYMACRO_SUCCESS) + break; + g_assert(rc == TECO_KEYMACRO_UNDEFINED); } - } + + /* + * If the current state is case-insensitive, it is a command name - + * which consists only of ANSI letters - we try to + * accept non-ANSI letters as well. + * This means, you don't have to change keyboard layouts + * so often. + * FIXME: This could be made to work with string-building constructs + * within Q-Register specs as well. + * Unfortunately, Q-Reg specs and string building can be nested + * indefinitely. + * This would effectively require a new keymacro_mask_cb(). + */ + if ((teco_cmdline.machine.parent.current->keymacro_mask | + teco_cmdline.machine.expectstring.machine.parent.current->keymacro_mask) & + TECO_KEYMACRO_MASK_CASEINSENSITIVE) + teco_interface_get_ansi_key(event); + + /* + * This is necessary to handle dead keys and in the future + * for inputting Asian languages. + * + * FIXME: We do not yet support preediting. + * It would be easier to forward the event to the Scintilla + * widget and use its existing IM support. + * But this breaks the event freezing and results in flickering. + */ + gtk_im_context_filter_keypress(teco_interface.input_method, event); } teco_interface_refresh(teco_interface_current_view != last_view); - return TRUE; } @@ -997,7 +1105,7 @@ teco_interface_event_loop(GError **error) g_assert(scitecoconfig_reg != NULL); g_auto(teco_string_t) scitecoconfig = {NULL, 0}; if (!scitecoconfig_reg->vtable->get_string(scitecoconfig_reg, - &scitecoconfig.data, &scitecoconfig.len, error)) + &scitecoconfig.data, &scitecoconfig.len, NULL, error)) return FALSE; if (teco_string_contains(&scitecoconfig, '\0')) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, @@ -1006,45 +1114,6 @@ teco_interface_event_loop(GError **error) } g_assert(scitecoconfig.data != NULL); -#ifdef G_OS_WIN32 - /* - * FIXME: This is necessary so that the icon themes are found in the same - * directory as sciteco.exe. - * This fails of course when $SCITECOCONFIG is changed. - * We should perhaps always use the absolute path of sciteco.exe. - * If you want to install SciTECO differently, you can still set - * $XDG_DATA_DIRS. - * - * FIXME FIXME FIXME: This is also currently broken. - */ - //g_autofree char *theme_path = g_build_filename(scitecoconfig.data, "icons"); - //gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), theme_path); -#else - /* - * Load icons for the GTK window. - * This is not necessary on Windows since the icon included - * as a resource will be used by default. - */ - static const gchar *icon_files[] = { - SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png", - SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png", - SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png" - }; - GList *icon_list = NULL; - - for (gint i = 0; i < G_N_ELEMENTS(icon_files); i++) { - GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(icon_files[i], NULL); - - /* fail silently if there's a problem with one of the icons */ - if (icon_pixbuf) - icon_list = g_list_append(icon_list, icon_pixbuf); - } - - gtk_window_set_default_icon_list(icon_list); - - g_list_free_full(icon_list, g_object_unref); -#endif - /* * Initialize the CSS variable provider and the CSS provider * for the included fallback.css. @@ -1087,6 +1156,50 @@ teco_interface_event_loop(GError **error) /* don't show popup by default */ gtk_widget_hide(teco_interface.popup_widget); +#ifdef G_OS_WIN32 + /* + * FIXME: This is necessary so that the icon themes are found in the same + * directory as sciteco.exe. + * This fails of course when $SCITECOCONFIG is changed. + * We should perhaps always use the absolute path of sciteco.exe. + * If you want to install SciTECO differently, you can still set + * $XDG_DATA_DIRS. + * + * FIXME FIXME FIXME: This is also currently broken. + */ + //g_autofree char *theme_path = g_build_filename(scitecoconfig.data, "icons"); + //gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), theme_path); +#else + /* + * Load icons for the GTK window. + * This is not necessary on Windows since the icon included + * as a resource will be used by default. + */ + static const gchar *icon_files[] = { + SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png", + SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png", + SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png" + }; + GList *icon_list = NULL; + + for (gint i = 0; i < G_N_ELEMENTS(icon_files); i++) { + GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(icon_files[i], NULL); + + /* fail silently if there's a problem with one of the icons */ + if (icon_pixbuf) + icon_list = g_list_append(icon_list, icon_pixbuf); + } + + /* + * The position of this call after gtk_widget_show() is important, so that + * tabbed and other Xembed hosts can pick up the icon. + * They also do not pick up the icon if set via gtk_window_set_default_icon_list(). + */ + gtk_window_set_icon_list(GTK_WINDOW(teco_interface.window), icon_list); + + g_list_free_full(icon_list, g_object_unref); +#endif + /* * SIGTERM emulates the "Close" key just like when * closing the window if supported by this version of glib. |