aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-gtk')
-rw-r--r--src/interface-gtk/gtk-info-popup.c21
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c60
-rw-r--r--src/interface-gtk/gtk-label.h6
-rw-r--r--src/interface-gtk/interface.c461
-rw-r--r--src/interface-gtk/view.c6
6 files changed, 315 insertions, 241 deletions
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index aaa0a65..769f772 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -37,6 +37,7 @@ typedef struct {
teco_stailq_entry_t entry;
teco_popup_entry_type_t type;
+ /** entry name or empty string for the "(Unnamed)" buffer */
teco_string_t name;
gboolean highlight;
} teco_popup_entry_t;
@@ -109,10 +110,10 @@ teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpoint
GList *entry;
for (entry = child_list; entry != NULL && !TECO_IS_GTK_LABEL(entry->data); entry = g_list_next(entry));
g_assert(entry != NULL);
- const teco_string_t *str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data));
+ teco_string_t str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data));
g_signal_emit(popup, teco_gtk_info_popup_clicked_signal, 0,
- str->data, (gulong)str->len);
+ str.data, (gulong)str.len);
}
static void
@@ -249,6 +250,10 @@ teco_gtk_info_popup_new(void)
GIcon *
teco_gtk_info_popup_get_icon_for_path(const gchar *path, const gchar *fallback_name)
{
+ if (!path || !*path)
+ /* "(Unnamed)" file */
+ return g_icon_new_for_string(fallback_name, NULL);
+
GIcon *icon = NULL;
g_autoptr(GFile) file = g_file_new_for_path(path);
@@ -299,7 +304,7 @@ teco_gtk_info_popup_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type,
static void
teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type,
- const gchar *name, gssize len, gboolean highlight)
+ const gchar *name, gsize len, gboolean highlight)
{
g_return_if_fail(self != NULL);
g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self));
@@ -318,12 +323,8 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
const gchar *fallback = type == TECO_POPUP_FILE ? "text-x-generic"
: "folder";
- /*
- * `name` is not guaranteed to be null-terminated.
- */
- g_autofree gchar *path = len < 0 ? g_strdup(name) : g_strndup(name, len);
-
- g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(path, fallback);
+ /* name comes from a teco_string_t and is guaranteed to be null-terminated */
+ g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(name, fallback);
if (icon) {
gint width, height;
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
index ad79b84..3ce8e1f 100644
--- a/src/interface-gtk/gtk-info-popup.h
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 ef370a2..5052cdc 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -32,7 +32,9 @@
#include "gtk-label.h"
-#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
+#define TECO_UNNAMED_FILE "(Unnamed)"
+
+#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
struct _TecoGtkLabel {
GtkLabel parent_instance;
@@ -40,6 +42,7 @@ struct _TecoGtkLabel {
PangoColor fg, bg;
guint16 fg_alpha, bg_alpha;
+ /** text backing the label or empty string for "(Unnamed)" buffer */
teco_string_t string;
};
@@ -143,7 +146,6 @@ teco_gtk_label_add_highlight_attribs(PangoAttrList *attribs, PangoColor *fg, gui
* even in Pango v1.38.
* Perhaps, this has been fixed in later versions.
*/
-#if PANGO_VERSION_CHECK(1,38,0)
attr = pango_attr_foreground_alpha_new(fg_alpha);
attr->start_index = index;
attr->end_index = index + len;
@@ -153,7 +155,6 @@ teco_gtk_label_add_highlight_attribs(PangoAttrList *attribs, PangoColor *fg, gui
attr->start_index = index;
attr->end_index = index + len;
pango_attr_list_insert(attribs, attr);
-#endif
attr = pango_attr_foreground_new(fg->red, fg->green, fg->blue);
attr->start_index = index;
@@ -253,25 +254,52 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
teco_string_clear(&self->string);
teco_string_init(&self->string, str, len < 0 ? strlen(str) : len);
- g_autofree gchar *plaintext = NULL;
+ teco_string_t string = self->string;
+ if (!string.len) {
+ string.data = TECO_UNNAMED_FILE;
+ string.len = strlen(string.data);
+ }
- if (self->string.len > 0) {
- PangoAttrList *attribs = NULL;
+ g_autofree gchar *plaintext = NULL;
+ PangoAttrList *attribs = NULL;
- teco_gtk_label_parse_string(self->string.data, self->string.len,
- &self->fg, self->fg_alpha,
- &self->bg, self->bg_alpha,
- &attribs, &plaintext);
+ teco_gtk_label_parse_string(string.data, string.len,
+ &self->fg, self->fg_alpha,
+ &self->bg, self->bg_alpha,
+ &attribs, &plaintext);
- gtk_label_set_attributes(GTK_LABEL(self), attribs);
- pango_attr_list_unref(attribs);
- }
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ pango_attr_list_unref(attribs);
gtk_label_set_text(GTK_LABEL(self), plaintext);
}
-const teco_string_t *
+teco_string_t
teco_gtk_label_get_text(TecoGtkLabel *self)
{
- return &self->string;
+ return self->string;
+}
+
+/**
+ * Signal that a keypress is expected (after executing ^T)
+ * by printing the first character in reverse.
+ *
+ * @fixme This mimics the current Curses implementation.
+ * Perhaps better show an icon?
+ */
+void
+teco_gtk_label_highlight_getch(TecoGtkLabel *self)
+{
+ const gchar *plaintext = gtk_label_get_text(GTK_LABEL(self));
+ g_assert(plaintext != NULL);
+ if (!*plaintext || !strcmp(plaintext, "\u258C")) {
+ gtk_label_set_text(GTK_LABEL(self), "\u258C");
+ } else {
+ PangoAttrList *attribs = gtk_label_get_attributes(GTK_LABEL(self));
+ teco_gtk_label_add_highlight_attribs(attribs,
+ &self->fg, self->fg_alpha,
+ &self->bg, self->bg_alpha,
+ 0, 1);
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ }
}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index c52d073..ad39c6e 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -27,9 +27,11 @@ G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel)
GtkWidget *teco_gtk_label_new(const gchar *str, gssize len);
void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len);
-const teco_string_t *teco_gtk_label_get_text(TecoGtkLabel *self);
+teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self);
void teco_gtk_label_parse_string(const gchar *str, gssize len,
PangoColor *fg, guint16 fg_alpha,
PangoColor *bg, guint16 bg_alpha,
PangoAttrList **attribs, gchar **text);
+
+void teco_gtk_label_highlight_getch(TecoGtkLabel *self);
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 045f9d7..0e1507f 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -19,7 +19,7 @@
#include "config.h"
#endif
-#include <stdarg.h>
+#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -28,6 +28,7 @@
#include <glib/gstdio.h>
#ifdef G_OS_UNIX
+#include <unistd.h>
#include <glib-unix.h>
#endif
@@ -61,6 +62,7 @@
//#define DEBUG
static gboolean teco_interface_busy_timeout_cb(gpointer user_data);
+static gboolean teco_interface_dump_recovery_cb(gpointer user_data);
static void teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data);
static void teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
GdkRectangle *allocation,
@@ -69,33 +71,22 @@ static void teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str,
gpointer user_data);
static gboolean teco_interface_input_cb(GtkWidget *widget, GdkEvent *event,
gpointer user_data);
+static void teco_interface_scroll_cb(GtkEventControllerScroll *controller,
+ double dx, double dy, gpointer data);
static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len,
gpointer user_data);
static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event,
gpointer user_data);
static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
+static gchar teco_interface_get_ansi_key(GdkEventKey *event);
-/**
- * Interval between polling for keypresses.
- * In other words, this is the maximum latency to detect CTRL+C interruptions.
- */
-#define TECO_POLL_INTERVAL 100000 /* microseconds */
-
-#define UNNAMED_FILE "(Unnamed)"
+#define TECO_UNNAMED_FILE "(Unnamed)"
#define USER_CSS_FILE ".teco_css"
/** printf() format for CSS RGB colors given as guint32 */
#define CSS_COLOR_FORMAT "#%06" G_GINT32_MODIFIER "X"
-/** Style used for the asterisk at the beginning of the command line */
-#define STYLE_ASTERISK 16
-
-/** Indicator number used for control characters in the command line */
-#define INDIC_CONTROLCHAR (INDIC_CONTAINER+0)
-/** Indicator number used for the rubbed out part of the command line */
-#define INDIC_RUBBEDOUT (INDIC_CONTAINER+1)
-
/** Convert Scintilla-style BGR color triple to RGB. */
static inline guint32
teco_bgr2rgb(guint32 bgr)
@@ -113,9 +104,10 @@ static struct {
TECO_INFO_TYPE_BUFFER_DIRTY,
TECO_INFO_TYPE_QREG
} info_type;
+ /* current document's name or empty string for "(Unnamed)" buffer */
teco_string_t info_current;
- gboolean no_csd;
+ gboolean no_csd, detach;
gint xembed_id;
GtkWidget *info_bar_widget;
@@ -124,11 +116,11 @@ static struct {
GtkWidget *info_name_widget;
GtkWidget *event_box_widget;
+ GtkEventController *scroll_controller;
GtkWidget *message_bar_widget;
GtkWidget *message_widget;
- teco_view_t *cmdline_view;
GtkIMContext *input_method;
GtkWidget *popup_widget;
@@ -142,6 +134,26 @@ static struct {
void
teco_interface_init(void)
{
+#ifdef G_OS_UNIX
+ if (teco_interface.detach) {
+ /*
+ * NOTE: There is also daemon() on BSD/Linux,
+ * but the following should be more portable.
+ */
+ pid_t pid = fork();
+ g_assert(pid >= 0);
+ if (pid != 0)
+ /* parent process */
+ exit(EXIT_SUCCESS);
+
+ setsid();
+
+ g_freopen("/dev/null", "r", stdin);
+ g_freopen("/dev/null", "a+", stdout);
+ g_freopen("/dev/null", "a+", stderr);
+ }
+#endif
+
/*
* gtk_init() is not necessary when using gtk_get_option_group(),
* but this will open the default display.
@@ -157,10 +169,10 @@ teco_interface_init(void)
* clipboards/selections are supported on this system,
* so we register only some default ones.
*/
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
teco_interface.event_queue = g_queue_new();
@@ -190,7 +202,7 @@ teco_interface_init(void)
*/
teco_interface.info_bar_widget = gtk_header_bar_new();
gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar");
- teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0);
+ teco_interface.info_name_widget = teco_gtk_label_new("", 0);
gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER);
/* eases writing portable fallback.css that avoids CSS element names */
gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget),
@@ -230,7 +242,7 @@ teco_interface_init(void)
/*
* Overlay widget will allow overlaying the Scintilla view
* and message widgets with the info popup.
- * Therefore overlay_vbox (containing the view and popup)
+ * Therefore overlay_vbox (containing the view and message line)
* will be the main child of the overlay.
*/
GtkWidget *overlay_widget = gtk_overlay_new();
@@ -253,22 +265,30 @@ teco_interface_init(void)
gint events = gtk_widget_get_events(teco_interface.event_box_widget);
events |= GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
- GDK_SCROLL_MASK;
+ GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK;
gtk_widget_set_events(teco_interface.event_box_widget, events);
g_signal_connect(teco_interface.event_box_widget, "button-press-event",
G_CALLBACK(teco_interface_input_cb), NULL);
g_signal_connect(teco_interface.event_box_widget, "button-release-event",
G_CALLBACK(teco_interface_input_cb), NULL);
- g_signal_connect(teco_interface.event_box_widget, "scroll-event",
- G_CALLBACK(teco_interface_input_cb), NULL);
+
+ /*
+ * On some platforms only GDK_SCROLL_SMOOTH events are reported, which are hard
+ * to translate to discrete scroll events, as required by the `4EJ` API.
+ * This work is therefore delegated to a scroll controller.
+ */
+ teco_interface.scroll_controller = gtk_event_controller_scroll_new(teco_interface.event_box_widget,
+ GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
+ GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
+ g_signal_connect(teco_interface.scroll_controller, "scroll",
+ G_CALLBACK(teco_interface_scroll_cb), NULL);
teco_interface.message_bar_widget = gtk_info_bar_new();
gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
GtkWidget *message_bar_content =
gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget));
- /* NOTE: Messages are always pre-canonicalized */
- teco_interface.message_widget = gtk_label_new(NULL);
+ teco_interface.message_widget = teco_gtk_label_new(NULL, 0);
/* eases writing portable fallback.css that avoids CSS element names */
gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget),
"label");
@@ -281,23 +301,9 @@ teco_interface_init(void)
gtk_container_add(GTK_CONTAINER(overlay_widget), overlay_vbox);
gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0);
- teco_interface.cmdline_view = teco_view_new();
- teco_view_setup(teco_interface.cmdline_view);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETUNDOCOLLECTION, FALSE, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETVSCROLLBAR, FALSE, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINTYPEN, 1, SC_MARGIN_TEXT);
- teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETSTYLE, 0, STYLE_ASTERISK);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINWIDTHN, 1,
- teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTWIDTH, STYLE_ASTERISK, (sptr_t)"*"));
- teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*");
- /* only required as long as we avoid ordinary character representations */
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_CONTROLCHAR, INDIC_ROUNDBOX);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETALPHA, INDIC_CONTROLCHAR, 128);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_RUBBEDOUT, INDIC_STRIKE);
- /* we will forward key events, so the view should only react to text insertion */
- teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALLCMDKEYS, 0, 0);
-
- GtkWidget *cmdline_widget = GTK_WIDGET(teco_interface.cmdline_view);
+ teco_cmdline_init();
+
+ GtkWidget *cmdline_widget = GTK_WIDGET(teco_cmdline.view);
gtk_widget_set_name(cmdline_widget, "sciteco-cmdline");
g_signal_connect(cmdline_widget, "size-allocate",
G_CALLBACK(teco_interface_cmdline_size_allocate_cb), NULL);
@@ -332,10 +338,6 @@ teco_interface_init(void)
*/
gtk_widget_set_can_focus(teco_interface.message_widget, FALSE);
gtk_widget_set_can_focus(teco_interface.info_name_widget, FALSE);
-
- teco_cmdline_t empty_cmdline;
- memset(&empty_cmdline, 0, sizeof(empty_cmdline));
- teco_interface_cmdline_update(&empty_cmdline);
}
static void
@@ -361,6 +363,11 @@ teco_interface_get_options(void)
G_OPTION_ARG_INT, &teco_interface.xembed_id,
"Embed into an existing X11 Window.", "ID"},
#endif
+#ifdef G_OS_UNIX
+ {"detach", 'd', G_OPTION_FLAG_IN_MAIN,
+ G_OPTION_ARG_NONE, &teco_interface.detach,
+ "Detach from controlling terminal (daemonize).", NULL},
+#endif
{NULL}
};
@@ -379,7 +386,7 @@ teco_interface_get_options(void)
void teco_interface_init_color(guint color, guint32 rgb) {}
void
-teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
+teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
{
/*
* The message types are chosen such that there is a CSS class
@@ -395,21 +402,11 @@ teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
g_assert(type < G_N_ELEMENTS(type2gtk));
- gchar buf[256];
-
- /*
- * stdio_vmsg() leaves `ap` undefined and we are expected
- * to do the same and behave like vprintf().
- */
- va_list aq;
- va_copy(aq, ap);
- teco_interface_stdio_vmsg(type, fmt, ap);
- g_vsnprintf(buf, sizeof(buf), fmt, aq);
- va_end(aq);
+ teco_interface_stdio_msg(type, str, len);
gtk_info_bar_set_message_type(GTK_INFO_BAR(teco_interface.message_bar_widget),
type2gtk[type]);
- gtk_label_set_text(GTK_LABEL(teco_interface.message_widget), buf);
+ teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.message_widget), str, len);
if (type == TECO_MSG_ERROR)
gtk_widget_error_bell(teco_interface.window);
@@ -420,7 +417,100 @@ teco_interface_msg_clear(void)
{
gtk_info_bar_set_message_type(GTK_INFO_BAR(teco_interface.message_bar_widget),
GTK_MESSAGE_QUESTION);
- gtk_label_set_text(GTK_LABEL(teco_interface.message_widget), "");
+ teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.message_widget), "", 0);
+}
+
+static void
+teco_interface_getch_commit_cb(GtkIMContext *context, gchar *str, gpointer user_data)
+{
+ teco_int_t *cp = user_data;
+
+ /*
+ * FIXME: What if str contains several characters?
+ */
+ *cp = g_utf8_get_char_validated(str, -1);
+ g_assert(*cp >= 0);
+ gtk_main_quit();
+}
+
+static gboolean
+teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ teco_int_t *cp = user_data;
+
+ g_assert(event->type == GDK_KEY_PRESS);
+
+ switch (event->key.keyval) {
+ case GDK_KEY_Escape: *cp = '\e'; break;
+ case GDK_KEY_BackSpace: *cp = TECO_CTL_KEY('H'); break;
+ case GDK_KEY_Tab: *cp = '\t'; break;
+ case GDK_KEY_Return: *cp = '\n'; break;
+ default:
+ /*
+ * NOTE: Alt-Gr key-combinations are sometimes reported as
+ * Ctrl+Alt, so we filter those out.
+ */
+ if ((event->key.state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK &&
+ (*cp = teco_interface_get_ansi_key(&event->key))) {
+ *cp = TECO_CTL_KEY(g_ascii_toupper(*cp));
+ switch (*cp) {
+ case TECO_CTL_KEY('C'):
+ teco_interrupted = TRUE;
+ /* fall through */
+ case TECO_CTL_KEY('D'):
+ *cp = -1;
+ }
+ break;
+ }
+
+ gtk_im_context_filter_keypress(teco_interface.input_method, &event->key);
+ return TRUE;
+ }
+
+ gtk_main_quit();
+ return TRUE;
+}
+
+teco_int_t
+teco_interface_getch(gboolean widechar)
+{
+ if (!gtk_main_level())
+ /* batch mode */
+ return teco_interface_stdio_getch(widechar);
+
+ teco_int_t cp = -1;
+ gulong key_handler, commit_handler;
+
+ /* temporarily replace the "key-press-event" and "commit" handlers */
+ g_signal_handlers_block_by_func(teco_interface.window,
+ teco_interface_input_cb, NULL);
+ key_handler = g_signal_connect(teco_interface.window, "key-press-event",
+ G_CALLBACK(teco_interface_getch_input_cb), &cp);
+ g_signal_handlers_block_by_func(teco_interface.input_method,
+ teco_interface_cmdline_commit_cb, NULL);
+ commit_handler = g_signal_connect(teco_interface.input_method, "commit",
+ G_CALLBACK(teco_interface_getch_commit_cb), &cp);
+
+ /*
+ * Highlights the first character in the label.
+ * This mimics what the Curses UI does.
+ * Is there a better way to signal that we expect input?
+ */
+ teco_gtk_label_highlight_getch(TECO_GTK_LABEL(teco_interface.message_widget));
+
+ GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
+ gdk_window_thaw_updates(top_window);
+
+ gtk_main();
+
+ gdk_window_freeze_updates(top_window);
+
+ g_signal_handler_disconnect(teco_interface.input_method, commit_handler);
+ g_signal_handlers_unblock_by_func(teco_interface.input_method, teco_interface_cmdline_commit_cb, NULL);
+ g_signal_handler_disconnect(teco_interface.window, key_handler);
+ g_signal_handlers_unblock_by_func(teco_interface.window, teco_interface_input_cb, NULL);
+
+ return cp;
}
void
@@ -439,8 +529,13 @@ teco_interface_refresh_info(void)
gtk_style_context_remove_class(style, "dirty");
g_auto(teco_string_t) info_current_temp;
- teco_string_init(&info_current_temp,
- teco_interface.info_current.data, teco_interface.info_current.len);
+
+ if (!teco_interface.info_current.len)
+ teco_string_init(&info_current_temp, TECO_UNNAMED_FILE, strlen(TECO_UNNAMED_FILE));
+ else
+ teco_string_init(&info_current_temp,
+ teco_interface.info_current.data, teco_interface.info_current.len);
+
if (teco_interface.info_type == TECO_INFO_TYPE_BUFFER_DIRTY)
teco_string_append_c(&info_current_temp, '*');
teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.info_name_widget),
@@ -508,92 +603,18 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg)
void
teco_interface_info_update_buffer(const teco_buffer_t *buffer)
{
- const gchar *filename = buffer->filename ? : UNNAMED_FILE;
-
teco_string_clear(&teco_interface.info_current);
- teco_string_init(&teco_interface.info_current, filename, strlen(filename));
- teco_interface.info_type = buffer->dirty ? TECO_INFO_TYPE_BUFFER_DIRTY
- : TECO_INFO_TYPE_BUFFER;
-}
-
-/**
- * Insert a single character into the command line.
- *
- * @fixme
- * Control characters should be inserted verbatim since the Scintilla
- * representations of them should be preferred.
- * However, Scintilla would break the line on every CR/LF and there is
- * currently no way to prevent this.
- * Scintilla needs to be patched.
- *
- * @see teco_view_set_representations()
- * @see teco_curses_format_str()
- */
-static void
-teco_interface_cmdline_insert_c(gchar chr)
-{
- gchar buffer[3+1] = "";
-
- /*
- * NOTE: This mapping is similar to teco_view_set_representations()
- */
- switch (chr) {
- case '\e': strcpy(buffer, "$"); break;
- case '\r': strcpy(buffer, "CR"); break;
- case '\n': strcpy(buffer, "LF"); break;
- case '\t': strcpy(buffer, "TAB"); break;
- default:
- if (TECO_IS_CTL(chr)) {
- buffer[0] = '^';
- buffer[1] = TECO_CTL_ECHO(chr);
- buffer[2] = '\0';
- }
- }
-
- if (*buffer) {
- gsize len = strlen(buffer);
- teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, len, (sptr_t)buffer);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_CONTROLCHAR, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE,
- teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - len, len);
- } else {
- teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, 1, (sptr_t)&chr);
- }
-}
-
-void
-teco_interface_cmdline_update(const teco_cmdline_t *cmdline)
-{
- /*
- * We don't know if the new command line is similar to
- * the old one, so we can just as well rebuild it.
- *
- * NOTE: teco_view_ssm() already locks the GDK lock.
- */
- teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALL, 0, 0);
-
- /* format effective command line */
- for (guint i = 0; i < cmdline->effective_len; i++)
- teco_interface_cmdline_insert_c(cmdline->str.data[i]);
-
- /* cursor should be after effective command line */
- guint pos = teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_GOTOPOS, pos, 0);
-
- /* format rubbed out command line */
- for (guint i = cmdline->effective_len; i < cmdline->str.len; i++)
- teco_interface_cmdline_insert_c(cmdline->str.data[i]);
-
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_RUBBEDOUT, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE, pos,
- teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - pos);
-
- teco_view_ssm(teco_interface.cmdline_view, SCI_SCROLLCARET, 0, 0);
+ teco_string_init(&teco_interface.info_current, buffer->filename,
+ buffer->filename ? strlen(buffer->filename) : 0);
+ teco_interface.info_type = buffer->state > TECO_BUFFER_CLEAN
+ ? TECO_INFO_TYPE_BUFFER_DIRTY : TECO_INFO_TYPE_BUFFER;
}
static GdkAtom
teco_interface_get_selection_by_name(const gchar *name)
{
+ g_assert(*name != '\0');
+
/*
* We can use gdk_atom_intern() to support arbitrary X11 selection
* names. However, since we cannot find out which selections are
@@ -602,11 +623,9 @@ teco_interface_get_selection_by_name(const gchar *name)
* Checking them here avoids expensive X server roundtrips.
*/
switch (*name) {
- case '\0': return GDK_NONE;
case 'P': return GDK_SELECTION_PRIMARY;
case 'S': return GDK_SELECTION_SECONDARY;
case 'C': return GDK_SELECTION_CLIPBOARD;
- default: break;
}
return gdk_atom_intern(name, FALSE);
@@ -767,6 +786,27 @@ teco_interface_is_interrupted(void)
return teco_interrupted != FALSE;
}
+void
+teco_interface_refresh(gboolean force)
+{
+ if (!gtk_main_level()) /* batch mode */
+ return;
+
+ if (G_UNLIKELY(force))
+ gtk_widget_queue_draw(teco_interface.window);
+
+ GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
+ gdk_window_thaw_updates(top_window);
+
+ /*
+ * FIXME: Why do we need two iterations to see any updates?
+ */
+ for (gint i = 0; i < 2; i++)
+ gtk_main_iteration_do(FALSE);
+
+ gdk_window_freeze_updates(top_window);
+}
+
static void
teco_interface_set_css_variables(teco_view_t *view)
{
@@ -776,40 +816,6 @@ teco_interface_set_css_variables(teco_view_t *view)
guint32 calltip_bg_color = teco_view_ssm(view, SCI_STYLEGETBACK, STYLE_CALLTIP, 0);
/*
- * FIXME: Font and colors of Scintilla views cannot be set via CSS.
- * But some day, there will be a way to send messages to the commandline view
- * from SciTECO code via ES.
- * Configuration will then be in the hands of color schemes.
- *
- * NOTE: We don't actually know apriori how large the font_size buffer should be,
- * but luckily SCI_STYLEGETFONT with a sptr==0 will return only the size.
- * This is undocumented in the Scintilla docs.
- */
- g_autofree gchar *font_name = g_malloc(teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, 0) + 1);
- teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, (sptr_t)font_name);
-
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_DEFAULT, default_fg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_DEFAULT, default_bg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)font_name);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETSIZE, STYLE_DEFAULT,
- teco_view_ssm(view, SCI_STYLEGETSIZE, STYLE_DEFAULT, 0));
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLECLEARALL, 0, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_CALLTIP, calltip_fg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_CALLTIP, calltip_bg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETCARETFORE,
- teco_view_ssm(view, SCI_GETCARETFORE, 0, 0), 0);
- /* used for the asterisk at the beginning of the command line */
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBOLD, STYLE_ASTERISK, TRUE);
- /* used for character representations */
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_CONTROLCHAR, default_fg_color);
- /* used for the rubbed out command line */
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_RUBBEDOUT, default_fg_color);
- /* this somehow gets reset */
- teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*");
-
- guint text_height = teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTHEIGHT, 0, 0);
-
- /*
* Generates a CSS that sets some predefined color variables.
* This effectively "exports" Scintilla styles into the CSS
* world.
@@ -835,16 +841,17 @@ teco_interface_set_css_variables(teco_view_t *view)
gtk_css_provider_load_from_data(teco_interface.css_var_provider, css, -1, NULL);
/*
- * The font and size of the commandline view might have changed,
+ * The font and size and height of the command-line view might have changed,
* so we resize it.
* This cannot be done via CSS or Scintilla messages.
- * Currently, it is always exactly one line high in order to mimic the Curses UI.
*/
- gtk_widget_set_size_request(GTK_WIDGET(teco_interface.cmdline_view), -1, text_height);
+ g_assert(teco_cmdline.height > 0);
+ gtk_widget_set_size_request(GTK_WIDGET(teco_cmdline.view), -1,
+ teco_cmdline.height*teco_cmdline_ssm(SCI_TEXTHEIGHT, 0, 0));
}
static void
-teco_interface_refresh(gboolean current_view_changed)
+teco_interface_update(gboolean current_view_changed)
{
/*
* The styles configured via Scintilla might change
@@ -1116,9 +1123,6 @@ teco_interface_handle_scroll(GdkEventScroll *event, GError **error)
{
g_assert(event->type == GDK_SCROLL);
- /*
- * FIXME: Do we have to support GDK_SCROLL_SMOOTH?
- */
switch (event->direction) {
case GDK_SCROLL_UP:
teco_mouse.type = TECO_MOUSE_SCROLLUP;
@@ -1157,7 +1161,7 @@ teco_interface_event_loop(GError **error)
if (!scitecoconfig_reg->vtable->get_string(scitecoconfig_reg,
&scitecoconfig.data, &scitecoconfig.len, NULL, error))
return FALSE;
- if (teco_string_contains(&scitecoconfig, '\0')) {
+ if (teco_string_contains(scitecoconfig, '\0')) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Null-character not allowed in filenames");
return FALSE;
@@ -1195,7 +1199,7 @@ teco_interface_event_loop(GError **error)
GTK_STYLE_PROVIDER(user_css_provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
- teco_interface_refresh(TRUE);
+ teco_interface_update(TRUE);
gtk_widget_show_all(teco_interface.window);
/* don't show popup by default */
@@ -1215,7 +1219,7 @@ teco_interface_event_loop(GError **error)
* This is not necessary on Windows since the icon included
* as a resource will be used by default.
*/
- static const gchar *icon_files[] = {
+ static const gchar *const icon_files[] = {
"sciteco-48.png", "sciteco-32.png", "sciteco-16.png"
};
GList *icon_list = NULL;
@@ -1249,6 +1253,10 @@ teco_interface_event_loop(GError **error)
g_unix_signal_add(SIGTERM, teco_interface_sigterm_handler, NULL);
#endif
+ /* the interval might have been changed in the profile */
+ g_timeout_add_seconds(teco_ring_recovery_interval,
+ teco_interface_dump_recovery_cb, NULL);
+
/* don't limit while waiting for input as this might be a busy operation */
teco_memory_stop_limiting();
@@ -1274,6 +1282,8 @@ teco_interface_cleanup(void)
if (teco_interface.window)
gtk_widget_destroy(teco_interface.window);
+ if (teco_interface.scroll_controller)
+ g_object_unref(teco_interface.scroll_controller);
scintilla_release_resources();
@@ -1305,6 +1315,20 @@ teco_interface_busy_timeout_cb(gpointer user_data)
return G_SOURCE_REMOVE;
}
+static gboolean
+teco_interface_dump_recovery_cb(gpointer user_data)
+{
+ teco_ring_dump_recovery();
+
+ /*
+ * The backup interval could have changed (6EJ).
+ * New intervals will not be effective immediately, though.
+ */
+ g_timeout_add_seconds(teco_ring_recovery_interval,
+ teco_interface_dump_recovery_cb, NULL);
+ return G_SOURCE_REMOVE;
+}
+
static void
teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
{
@@ -1312,17 +1336,12 @@ teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
teco_interface_set_cursor(widget, "text");
}
-/**
- * Called when the commandline widget is resized.
- * This should ensure that the caret jumps to the middle of the command line,
- * imitating the behaviour of the current Curses command line.
- */
+/** Called when the commandline widget is resized */
static void
teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
GdkRectangle *allocation, gpointer user_data)
{
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETXCARETPOLICY,
- CARET_SLOP | CARET_EVEN, allocation->width/2);
+ teco_cmdline_resized(allocation->width);
}
static gboolean
@@ -1415,7 +1434,9 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
}
teco_interrupted = FALSE;
- teco_interface_refresh(teco_interface_current_view != last_view);
+ teco_interface_update(teco_interface_current_view != last_view);
+ /* always expand folds, even after mouse clicks */
+ teco_interface_unfold();
/*
* Scintilla has been patched to avoid any automatic scrolling since that
* has been benchmarked to be a very costly operation.
@@ -1458,10 +1479,34 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
}
static void
+teco_interface_scroll_cb(GtkEventControllerScroll *controller, double dx, double dy, gpointer data)
+{
+ /*
+ * FIXME: Using teco_interface.event_box_widget will cause crashes in
+ * teco_interface_input_cb()
+ */
+ GtkWidget *widget = teco_interface.window;
+
+ /*
+ * Emulate a GDK_SCROLL event to make use of the existing
+ * event queuing in teco_interface_input_cb().
+ */
+ g_autoptr(GdkEvent) scroll_event = gdk_event_new(GDK_SCROLL);
+ scroll_event->scroll.window = gtk_widget_get_parent_window(widget);
+ scroll_event->scroll.direction = dy > 0 ? GDK_SCROLL_DOWN : GDK_SCROLL_UP;
+ scroll_event->scroll.delta_x = dx;
+ scroll_event->scroll.delta_y = dy;
+
+ teco_interface_input_cb(widget, scroll_event, NULL);
+}
+
+static void
teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpointer user_data)
{
g_assert(len >= teco_interface.popup_prefix_len);
- const teco_string_t insert = {str+teco_interface.popup_prefix_len, len-teco_interface.popup_prefix_len};
+ /* str is an empty string for the "(Unnamed)" buffer */
+ const teco_string_t insert = {str+teco_interface.popup_prefix_len,
+ len-teco_interface.popup_prefix_len};
teco_machine_t *machine = &teco_cmdline.machine.parent;
const teco_view_t *last_view = teco_interface_current_view;
@@ -1471,12 +1516,12 @@ teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpoint
* A auto completion should never result in program termination.
*/
if (machine->current->insert_completion_cb &&
- !machine->current->insert_completion_cb(machine, &insert, NULL))
+ !machine->current->insert_completion_cb(machine, insert, NULL))
return;
teco_interface_popup_clear();
- teco_interface_cmdline_update(&teco_cmdline);
+ teco_cmdline_update();
- teco_interface_refresh(teco_interface_current_view != last_view);
+ teco_interface_update(teco_interface_current_view != last_view);
}
static gboolean
@@ -1484,7 +1529,6 @@ teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer
{
/*
* Emulate that the "close" key was pressed
- * which may then be handled by the execution thread
* which invokes the appropriate "function key macro"
* if it exists. Its default action will ensure that
* the execution thread shuts down and the main loop
@@ -1503,8 +1547,11 @@ teco_interface_sigterm_handler(gpointer user_data)
/*
* Similar to window deletion - emulate "close" key press.
*/
+ GtkWidget *widget = teco_interface.window;
+
g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
+ close_event->key.window = gtk_widget_get_parent_window(widget);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_input_cb(teco_interface.window, close_event, NULL);
+ return teco_interface_input_cb(widget, close_event, NULL);
}
diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c
index 81db3d7..3a18f33 100644
--- a/src/interface-gtk/view.c
+++ b/src/interface-gtk/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -102,12 +102,8 @@ teco_view_new(void)
gint events = gtk_widget_get_events(GTK_WIDGET(ctx));
events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK |
-#ifdef GDK_VERSION_3_18
GDK_TOUCHPAD_GESTURE_MASK |
-#endif
-#ifdef GDK_VERSION_3_22
GDK_TABLET_PAD_MASK |
-#endif
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
gtk_widget_set_events(GTK_WIDGET(ctx), events);