aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-gtk')
-rw-r--r--src/interface-gtk/Makefile.am2
-rw-r--r--src/interface-gtk/gtk-info-popup.c57
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c8
-rw-r--r--src/interface-gtk/gtk-label.h3
-rw-r--r--src/interface-gtk/interface.c329
-rw-r--r--src/interface-gtk/view.c126
7 files changed, 398 insertions, 129 deletions
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
index 50e5311..e731a21 100644
--- a/src/interface-gtk/Makefile.am
+++ b/src/interface-gtk/Makefile.am
@@ -4,7 +4,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/contrib/rb3ptr \
AM_CFLAGS = -std=gnu11 -Wall -Wno-initializer-overrides -Wno-unused-value
noinst_LTLIBRARIES = libsciteco-interface.la
-libsciteco_interface_la_SOURCES = interface.c \
+libsciteco_interface_la_SOURCES = view.c interface.c \
gtk-info-popup.c gtk-info-popup.h \
gtk-label.c gtk-label.h
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index 4e25224..aaa0a65 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 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
@@ -47,12 +47,15 @@ struct _TecoGtkInfoPopup {
GtkAdjustment *hadjustment, *vadjustment;
GtkWidget *flow_box;
+ GdkCursor *cursor; /*< pointer/hand cursor */
GStringChunk *chunk;
teco_stailq_head_t list;
guint idle_id;
gboolean frozen;
};
+static guint teco_gtk_info_popup_clicked_signal;
+
static gboolean teco_gtk_info_popup_scroll_event(GtkWidget *widget, GdkEventScroll *event);
static void teco_gtk_info_popup_show(GtkWidget *widget);
static void teco_gtk_info_popup_vadjustment_changed(GtkAdjustment *vadjustment, GtkWidget *scrollbar);
@@ -72,6 +75,9 @@ teco_gtk_info_popup_finalize(GObject *obj_self)
while ((entry = teco_stailq_remove_head(&self->list)))
g_free(entry);
+ if (self->cursor)
+ g_object_unref(self->cursor);
+
/* chain up to parent class */
G_OBJECT_CLASS(teco_gtk_info_popup_parent_class)->finalize(obj_self);
}
@@ -82,6 +88,31 @@ teco_gtk_info_popup_class_init(TecoGtkInfoPopupClass *klass)
GTK_WIDGET_CLASS(klass)->scroll_event = teco_gtk_info_popup_scroll_event;
GTK_WIDGET_CLASS(klass)->show = teco_gtk_info_popup_show;
G_OBJECT_CLASS(klass)->finalize = teco_gtk_info_popup_finalize;
+
+ teco_gtk_info_popup_clicked_signal =
+ g_signal_new("clicked", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_ULONG);
+}
+
+static void
+teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpointer user_data)
+{
+ TecoGtkInfoPopup *popup = TECO_GTK_INFO_POPUP(user_data);
+
+ /*
+ * Find the TecoGtkLabel in the flow box child.
+ */
+ GtkWidget *hbox = gtk_bin_get_child(GTK_BIN(child));
+ g_autoptr(GList) child_list = gtk_container_get_children(GTK_CONTAINER(hbox));
+ 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));
+
+ g_signal_emit(popup, teco_gtk_info_popup_clicked_signal, 0,
+ str->data, (gulong)str->len);
}
static void
@@ -106,6 +137,8 @@ teco_gtk_info_popup_init(TecoGtkInfoPopup *self)
G_CALLBACK(teco_gtk_info_popup_vadjustment_changed), scrollbar);
self->flow_box = gtk_flow_box_new();
+ g_signal_connect(self->flow_box, "child-activated",
+ G_CALLBACK(teco_gtk_info_popup_activated_cb), self);
/* take as little height as necessary */
gtk_orientable_set_orientation(GTK_ORIENTABLE(self->flow_box),
GTK_ORIENTATION_HORIZONTAL);
@@ -311,12 +344,6 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
gtk_widget_set_halign(label, GTK_ALIGN_START);
gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
- /*
- * FIXME: This makes little sense once we've got mouse support.
- * But for the time being, it's a useful setting.
- */
- gtk_label_set_selectable(GTK_LABEL(label), TRUE);
-
switch (type) {
case TECO_POPUP_PLAIN:
gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
@@ -331,6 +358,16 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
gtk_widget_show_all(hbox);
gtk_container_add(GTK_CONTAINER(self->flow_box), hbox);
+
+ GtkWidget *flow_box_child = gtk_widget_get_parent(hbox);
+ g_assert(GTK_IS_FLOW_BOX_CHILD(flow_box_child));
+ GdkWindow *window = gtk_widget_get_window(flow_box_child);
+ g_assert(window != NULL);
+
+ if (G_UNLIKELY(!self->cursor))
+ /* we only initialize it now after guaranteed widget realization */
+ self->cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), "pointer");
+ gdk_window_set_cursor(window, self->cursor);
}
static gboolean
@@ -417,12 +454,10 @@ teco_gtk_info_popup_scroll_page(TecoGtkInfoPopup *self)
* Adjust this so only complete entries are shown.
* Effectively, this rounds down to the line height.
*/
- GList *child_list = gtk_container_get_children(GTK_CONTAINER(self->flow_box));
- if (child_list) {
+ g_autoptr(GList) child_list = gtk_container_get_children(GTK_CONTAINER(self->flow_box));
+ if (child_list)
new_value -= (gint)new_value %
gtk_widget_get_allocated_height(GTK_WIDGET(child_list->data));
- g_list_free(child_list);
- }
/* clip to the maximum possible value */
new_value = MIN(new_value, gtk_adjustment_get_upper(adj));
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
index c3a62ec..ad79b84 100644
--- a/src/interface-gtk/gtk-info-popup.h
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 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 50cd345..ef370a2 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 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
@@ -269,3 +269,9 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
gtk_label_set_text(GTK_LABEL(self), plaintext);
}
+
+const teco_string_t *
+teco_gtk_label_get_text(TecoGtkLabel *self)
+{
+ return &self->string;
+}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index bed6642..c52d073 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 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,6 +27,7 @@ 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);
void teco_gtk_label_parse_string(const gchar *str, gssize len,
PangoColor *fg, guint16 fg_alpha,
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 0dbd2ba..7f58c45 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 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
@@ -60,16 +60,17 @@
//#define DEBUG
+static gboolean teco_interface_busy_timeout_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,
gpointer user_data);
static void teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str,
gpointer user_data);
-static void teco_interface_size_allocate_cb(GtkWidget *widget,
- GdkRectangle *allocation,
+static gboolean teco_interface_input_cb(GtkWidget *widget, GdkEvent *event,
+ gpointer user_data);
+static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len,
gpointer user_data);
-static gboolean teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
- 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;
@@ -102,73 +103,6 @@ teco_bgr2rgb(guint32 bgr)
return GUINT32_SWAP_LE_BE(bgr) >> 8;
}
-/*
- * NOTE: The teco_view_t pointer is reused to directly
- * point to the ScintillaObject.
- * This saves one heap object per view.
- */
-
-static void
-teco_view_scintilla_notify(ScintillaObject *sci, gint iMessage,
- SCNotification *notify, gpointer user_data)
-{
- teco_view_process_notify((teco_view_t *)sci, notify);
-}
-
-teco_view_t *
-teco_view_new(void)
-{
- ScintillaObject *sci = SCINTILLA(scintilla_new());
- /*
- * We don't want the object to be destroyed
- * when it is removed from the vbox.
- */
- g_object_ref_sink(sci);
-
- scintilla_set_id(sci, 0);
-
- gtk_widget_set_size_request(GTK_WIDGET(sci), 500, 300);
-
- /*
- * This disables mouse and key events on this view.
- * For some strange reason, masking events on
- * the event box does NOT work.
- *
- * NOTE: Scroll events are still allowed - scrolling
- * is currently not under direct control of SciTECO
- * (i.e. it is OK the side effects of scrolling are not
- * tracked).
- */
- gtk_widget_set_can_focus(GTK_WIDGET(sci), FALSE);
- gint events = gtk_widget_get_events(GTK_WIDGET(sci));
- events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
- events &= ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
- gtk_widget_set_events(GTK_WIDGET(sci), events);
-
- g_signal_connect(sci, SCINTILLA_NOTIFY,
- G_CALLBACK(teco_view_scintilla_notify), NULL);
-
- return (teco_view_t *)sci;
-}
-
-static inline GtkWidget *
-teco_view_get_widget(teco_view_t *ctx)
-{
- return GTK_WIDGET(ctx);
-}
-
-sptr_t
-teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
-{
- return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
-}
-
-void
-teco_view_free(teco_view_t *ctx)
-{
- g_object_unref(teco_view_get_widget(ctx));
-}
-
static struct {
GtkCssProvider *css_var_provider;
@@ -198,6 +132,7 @@ static struct {
GtkIMContext *input_method;
GtkWidget *popup_widget;
+ gsize popup_prefix_len;
GtkWidget *current_view_widget;
@@ -240,7 +175,7 @@ teco_interface_init(void)
G_CALLBACK(teco_interface_window_delete_cb), NULL);
g_signal_connect(teco_interface.window, "key-press-event",
- G_CALLBACK(teco_interface_key_pressed_cb), NULL);
+ G_CALLBACK(teco_interface_input_cb), NULL);
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
@@ -313,8 +248,20 @@ teco_interface_init(void)
gtk_box_pack_start(GTK_BOX(overlay_vbox), teco_interface.event_box_widget,
TRUE, TRUE, 0);
- g_signal_connect(teco_interface.event_box_widget, "size-allocate",
- G_CALLBACK(teco_interface_size_allocate_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "realize",
+ G_CALLBACK(teco_interface_event_box_realized_cb), NULL);
+
+ gint events = gtk_widget_get_events(teco_interface.event_box_widget);
+ events |= GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_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);
teco_interface.message_bar_widget = gtk_info_bar_new();
gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
@@ -350,7 +297,7 @@ teco_interface_init(void)
/* 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 = teco_view_get_widget(teco_interface.cmdline_view);
+ GtkWidget *cmdline_widget = GTK_WIDGET(teco_interface.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);
@@ -373,6 +320,8 @@ teco_interface_init(void)
*/
teco_interface.popup_widget = teco_gtk_info_popup_new();
gtk_widget_set_name(teco_interface.popup_widget, "sciteco-info-popup");
+ g_signal_connect(teco_interface.popup_widget, "clicked",
+ G_CALLBACK(teco_interface_popup_clicked_cb), NULL);
gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), teco_interface.popup_widget);
g_signal_connect(overlay_widget, "get-child-position",
G_CALLBACK(teco_gtk_info_popup_get_position_in_overlay), NULL);
@@ -389,6 +338,17 @@ teco_interface_init(void)
teco_interface_cmdline_update(&empty_cmdline);
}
+static void
+teco_interface_set_cursor(GtkWidget *widget, const gchar *name)
+{
+ GdkWindow *window = gtk_widget_get_window(widget);
+ g_assert(window != NULL);
+ GdkDisplay *display = gdk_window_get_display(window);
+
+ g_autoptr(GdkCursor) cursor = name ? gdk_cursor_new_from_name(display, name) : NULL;
+ gdk_window_set_cursor(window, cursor);
+}
+
GOptionGroup *
teco_interface_get_options(void)
{
@@ -746,12 +706,16 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize
}
void
-teco_interface_popup_show(void)
+teco_interface_popup_show(gsize prefix_len)
{
- if (gtk_widget_get_visible(teco_interface.popup_widget))
- teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
- else
- gtk_widget_show(teco_interface.popup_widget);
+ teco_interface.popup_prefix_len = prefix_len;
+ gtk_widget_show(teco_interface.popup_widget);
+}
+
+void
+teco_interface_popup_scroll(void)
+{
+ teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
}
gboolean
@@ -876,7 +840,7 @@ teco_interface_set_css_variables(teco_view_t *view)
* 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(teco_view_get_widget(teco_interface.cmdline_view), -1, text_height);
+ gtk_widget_set_size_request(GTK_WIDGET(teco_interface.cmdline_view), -1, text_height);
}
static void
@@ -910,19 +874,12 @@ teco_interface_refresh(gboolean current_view_changed)
gtk_container_remove(GTK_CONTAINER(teco_interface.event_box_widget),
teco_interface.current_view_widget);
- teco_interface.current_view_widget = teco_view_get_widget(teco_interface_current_view);
+ teco_interface.current_view_widget = GTK_WIDGET(teco_interface_current_view);
gtk_container_add(GTK_CONTAINER(teco_interface.event_box_widget),
teco_interface.current_view_widget);
gtk_widget_show(teco_interface.current_view_widget);
}
-
- /*
- * Scintilla has been patched to avoid any automatic scrolling since that
- * has been benchmarked to be a very costly operation.
- * Instead we do it only once after every keypress.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
}
static void
@@ -977,7 +934,7 @@ teco_interface_get_ansi_key(GdkEventKey *event)
static gboolean
teco_interface_handle_key_press(GdkEventKey *event, GError **error)
{
- const teco_view_t *last_view = teco_interface_current_view;
+ g_assert(event->type == GDK_KEY_PRESS);
switch (event->keyval) {
case GDK_KEY_Escape:
@@ -1105,10 +1062,81 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
gtk_im_context_filter_keypress(teco_interface.input_method, event);
}
- teco_interface_refresh(teco_interface_current_view != last_view);
return TRUE;
}
+static gboolean
+teco_interface_handle_mouse_button(GdkEventButton *event, GError **error)
+{
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ teco_mouse.type = TECO_MOUSE_PRESSED;
+ break;
+ case GDK_BUTTON_RELEASE:
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ break;
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ default:
+ /* delivered in addition to GDK_BUTTON_PRESS */
+ return TRUE;
+ }
+
+ teco_mouse.x = event->x;
+ teco_mouse.y = event->y;
+ teco_mouse.button = event->button;
+
+ teco_mouse.mods = 0;
+ if (event->state & GDK_SHIFT_MASK)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event->state & GDK_CONTROL_MASK)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ /*
+ * NOTE: GTK returns MOD1 *without* SHIFT for ALT.
+ */
+ if ((event->state & (GDK_MOD1_MASK | GDK_SHIFT_MASK)) == GDK_MOD1_MASK)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
+static gboolean
+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;
+ break;
+ case GDK_SCROLL_DOWN:
+ teco_mouse.type = TECO_MOUSE_SCROLLDOWN;
+ break;
+ default:
+ return TRUE;
+ }
+
+ teco_mouse.x = event->x;
+ teco_mouse.y = event->y;
+ teco_mouse.button = -1;
+
+ teco_mouse.mods = 0;
+ if (event->state & GDK_SHIFT_MASK)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event->state & GDK_CONTROL_MASK)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ /*
+ * NOTE: GTK returns MOD1 *without* SHIFT for ALT.
+ */
+ if ((event->state & (GDK_MOD1_MASK | GDK_SHIFT_MASK)) == GDK_MOD1_MASK)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
gboolean
teco_interface_event_loop(GError **error)
{
@@ -1251,6 +1279,29 @@ teco_interface_cleanup(void)
*/
/**
+ * Called some time after processing an input event in order to show
+ * business.
+ *
+ * The delay avoids cursor flickering during normal typing.
+ *
+ * @fixme It would be nicer to set the cursor for the entire window,
+ * but that would apparently require another GtkEventBox, spanning everything.
+ */
+static gboolean
+teco_interface_busy_timeout_cb(gpointer user_data)
+{
+ teco_interface_set_cursor(teco_interface.event_box_widget, "wait");
+ return G_SOURCE_REMOVE;
+}
+
+static void
+teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
+{
+ /* It's only now safe to get the GdkWindow. */
+ 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.
@@ -1263,26 +1314,16 @@ teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
CARET_SLOP | CARET_EVEN, allocation->width/2);
}
-static void
-teco_interface_size_allocate_cb(GtkWidget *widget,
- GdkRectangle *allocation, gpointer user_data)
-{
- /*
- * This especially ensures that the caret is visible after startup.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
-}
-
static gboolean
-teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
+teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
static gboolean recursed = FALSE;
- g_autoptr(GError) error = NULL;
#ifdef DEBUG
- g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
- event->string, *event->string,
- event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK);
+ if (event->type == GDK_KEY_PRESS)
+ g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
+ event->key.string, *event->key.string,
+ event->key.state & GDK_SHIFT_MASK, event->key.state & GDK_CONTROL_MASK);
#endif
if (recursed) {
@@ -1295,8 +1336,9 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
* during execution, but the current implementation is
* probably easier.
*/
- if (event->state & GDK_CONTROL_MASK &&
- gdk_keyval_to_upper(event->keyval) == GDK_KEY_C)
+ if (event->type == GDK_KEY_PRESS &&
+ event->key.state & GDK_CONTROL_MASK &&
+ gdk_keyval_to_upper(event->key.keyval) == GDK_KEY_C)
/*
* Handle asynchronous interruptions if CTRL+C is pressed.
* If the execution thread is currently blocking,
@@ -1305,20 +1347,26 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
teco_interrupted = TRUE;
else
g_queue_push_tail(teco_interface.event_queue,
- gdk_event_copy((GdkEvent *)event));
+ gdk_event_copy(event));
return TRUE;
}
recursed = TRUE;
+ GSource *busy_timeout = g_timeout_source_new(500); /* ms */
+ g_source_set_callback(busy_timeout, teco_interface_busy_timeout_cb, NULL, NULL);
+ g_source_attach(busy_timeout, NULL);
+
teco_memory_start_limiting();
- g_queue_push_tail(teco_interface.event_queue, gdk_event_copy((GdkEvent *)event));
+ g_queue_push_tail(teco_interface.event_queue, gdk_event_copy(event));
GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
do {
+ g_autoptr(GError) error = NULL;
+
/*
* The event queue might be filled when pressing keys when SciTECO
* is busy executing code.
@@ -1334,10 +1382,37 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
*/
gdk_window_freeze_updates(top_window);
+ const teco_view_t *last_view = teco_interface_current_view;
+ sptr_t last_pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+
teco_interrupted = FALSE;
- teco_interface_handle_key_press(&event->key, &error);
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ teco_interface_handle_key_press(&event->key, &error);
+ break;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ teco_interface_handle_mouse_button(&event->button, &error);
+ break;
+ case GDK_SCROLL:
+ teco_interface_handle_scroll(&event->scroll, &error);
+ break;
+ default:
+ g_assert_not_reached();
+ }
teco_interrupted = FALSE;
+ teco_interface_refresh(teco_interface_current_view != last_view);
+ /*
+ * Scintilla has been patched to avoid any automatic scrolling since that
+ * has been benchmarked to be a very costly operation.
+ * Instead we do it only once after every keypress.
+ */
+ if (last_pos != teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0))
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
+
gdk_window_thaw_updates(top_window);
if (g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT)) {
@@ -1355,10 +1430,36 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
teco_memory_stop_limiting();
+ g_source_destroy(busy_timeout);
+ g_source_unref(busy_timeout);
+ teco_interface_set_cursor(teco_interface.event_box_widget, "text");
+
recursed = FALSE;
return TRUE;
}
+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};
+ teco_machine_t *machine = &teco_cmdline.machine.parent;
+
+ const teco_view_t *last_view = teco_interface_current_view;
+
+ /*
+ * NOTE: It shouldn't really be necessary to catch TECO_ERROR_QUIT here.
+ * A auto completion should never result in program termination.
+ */
+ if (machine->current->insert_completion_cb &&
+ !machine->current->insert_completion_cb(machine, &insert, NULL))
+ return;
+ teco_interface_popup_clear();
+ teco_interface_cmdline_update(&teco_cmdline);
+
+ teco_interface_refresh(teco_interface_current_view != last_view);
+}
+
static gboolean
teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer user_data)
{
@@ -1374,7 +1475,7 @@ teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer
close_event->key.window = gtk_widget_get_parent_window(widget);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_key_pressed_cb(widget, &close_event->key, NULL);
+ return teco_interface_input_cb(widget, close_event, NULL);
}
static gboolean
@@ -1386,5 +1487,5 @@ teco_interface_sigterm_handler(gpointer user_data)
g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_key_pressed_cb(teco_interface.window, &close_event->key, NULL);
+ return teco_interface_input_cb(teco_interface.window, close_event, NULL);
}
diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c
new file mode 100644
index 0000000..ef839d6
--- /dev/null
+++ b/src/interface-gtk/view.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012-2025 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
+ * the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include <Scintilla.h>
+#include <ScintillaWidget.h>
+
+#include "view.h"
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(ScintillaObject, g_object_unref)
+
+#define TECO_TYPE_VIEW teco_view_get_type()
+G_DECLARE_FINAL_TYPE(TecoView, teco_view, TECO, VIEW, ScintillaObject)
+
+struct _TecoView {
+ ScintillaObject parent_instance;
+ /** current size allocation */
+ GdkRectangle allocation;
+};
+
+G_DEFINE_TYPE(TecoView, teco_view, SCINTILLA_TYPE_OBJECT)
+
+static void
+teco_view_scintilla_notify_cb(ScintillaObject *sci, gint iMessage, SCNotification *notify)
+{
+ teco_view_process_notify((teco_view_t *)TECO_VIEW(sci), notify);
+}
+
+/**
+ * Called when the view is size allocated.
+ *
+ * This especially ensures that the caret is visible after startup and when
+ * opening files on specific lines.
+ * It's important to scroll the caret only when the size actually changes,
+ * so we do not interfere with mouse scrolling.
+ * That callback is invoked even if the size does not change, so that's why
+ * we have to store the current allocation in teco_view_t.
+ * Calling it once is unfortunately not sufficient since the window size
+ * can change during startup.
+ */
+static void
+teco_view_size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation)
+{
+ /* chain to parent class */
+ GTK_WIDGET_CLASS(teco_view_parent_class)->size_allocate(widget, allocation);
+
+ TecoView *view = TECO_VIEW(widget);
+
+ if (allocation->width == view->allocation.width && allocation->height == view->allocation.height)
+ return;
+ teco_view_ssm((teco_view_t *)view, SCI_SCROLLCARET, 0, 0);
+ memcpy(&view->allocation, allocation, sizeof(view->allocation));
+}
+
+teco_view_t *
+teco_view_new(void)
+{
+ TecoView *ctx = TECO_VIEW(g_object_new(TECO_TYPE_VIEW, NULL));
+ /*
+ * We don't want the object to be destroyed
+ * when it is removed from the vbox.
+ */
+ g_object_ref_sink(ctx);
+
+ scintilla_set_id(SCINTILLA(ctx), 0);
+
+ gtk_widget_set_size_request(GTK_WIDGET(ctx), 500, 300);
+
+ /*
+ * This disables mouse and key events on this view.
+ * For some strange reason, masking events on
+ * the event box does NOT work.
+ */
+ gtk_widget_set_can_focus(GTK_WIDGET(ctx), FALSE);
+ gint events = gtk_widget_get_events(GTK_WIDGET(ctx));
+ events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK |
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+ gtk_widget_set_events(GTK_WIDGET(ctx), events);
+
+ return (teco_view_t *)ctx;
+}
+
+static void
+teco_view_class_init(TecoViewClass *klass)
+{
+ SCINTILLA_CLASS(klass)->notify = teco_view_scintilla_notify_cb;
+ GTK_WIDGET_CLASS(klass)->size_allocate = teco_view_size_allocate_cb;
+}
+
+static void teco_view_init(TecoView *self) {}
+
+sptr_t
+teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
+{
+ return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
+}
+
+void
+teco_view_free(teco_view_t *ctx)
+{
+ g_object_unref(ctx);
+}