aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2025-02-02 13:17:51 +0300
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2025-02-16 02:20:39 +0300
commitb87c56799ab6f6d651e1dc6c712a625545a4ad5f (patch)
tree726a36ede8cf7a9d310c4299f5fadef6398dcda0
parent6c500d60eb7df65b0c71e9b0e0955ff581fc3f5a (diff)
downloadsciteco-b87c56799ab6f6d651e1dc6c712a625545a4ad5f.tar.gz
implemented mouse support via special ^KMOUSE and <EJ> with negative keys
* You need to set 0,64ED to enable mouse processing in Curses. It is always enabled in Gtk as it should never make the experience worse. sample.teco_ini enables mouse support, since this should be the new default. `sciteco --no-profile` won't have it enabled, though. * On curses, it requires the ncurses mouse protocol version 2, which will also be supported by PDCurses. * Similar to the Curses API, a special key macro ^KMOUSE is inserted if any of the supported mouse events has been detected. * You can then use -EJ to get the type of mouse event, which can be used with a computed goto in the command-line editing macro. Alternatively, this could have been solved with separate ^KMOUSE:PRESSED, ^KMOUSE:RELEASED etc. pseudo-key macros. * The default ^KMOUSE implementation in fnkeys.tes supports the following: * Left click: Edit command line to jump to position. * Ctrl+left click: Jump to beginning of line. * Right click: Insert position or position range (when dragging). * Double right click: insert range for word under cursor * Ctrl+right click: Insert beginning of line * Scroll wheel: scrolls (faster with shift) * Ctrl+scroll wheel: zoom (GTK-only) * Currently, there is no visual feedback when "selecting" ranges via right-click+drag. This would be tricky to do and most terminal emulators do not appear to support continuous mouse updates.
-rw-r--r--README1
-rw-r--r--configure.ac4
-rw-r--r--doc/sciteco.7.template9
-rw-r--r--lib/fnkeys.tes68
-rw-r--r--sample.teco_ini3
-rw-r--r--src/core-commands.c146
-rw-r--r--src/interface-curses/interface.c102
-rw-r--r--src/interface-gtk/interface.c140
-rw-r--r--src/interface.c3
-rw-r--r--src/interface.h22
-rw-r--r--src/sciteco.h2
11 files changed, 439 insertions, 61 deletions
diff --git a/README b/README
index 3de865d..bf0ed55 100644
--- a/README
+++ b/README
@@ -57,6 +57,7 @@ Features
by the standard library `fnkeys.tes`.
In fact, all keys with printable representation and control keys can be remapped using
key macros - and they can be context-sensitive as well!
+* Scriptable mouse support via the key macro mechanism (see also `fnkeys.tes`)
* Many TECO-11 features, like that most commands have a colon-modified form, string-building
characters, exotic match characters...
* Interactivity: Immediate searching (similar to search-as-you-type) and
diff --git a/configure.ac b/configure.ac
index 798b756..e355c24 100644
--- a/configure.ac
+++ b/configure.ac
@@ -335,6 +335,10 @@ case $INTERFACE in
AC_DEFINE(PDC_FORCE_UTF8, 1, [PDCursesMod forces use of UTF8])
])
+ AC_CHECK_FUNC([has_mouse], [
+ AC_DEFINE(PDC_NCMOUSE, 1, [PDCurses built with ncurses mouse API])
+ ])
+
# This is detectable at runtime on PDCursesMod using PDC_get_version().
# "Classic" PDCurses however does not allow runtime or compile-time checks for
# discerning e.g. WinCon from SDL.
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index 6ca81ab..97e1fd7 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -300,6 +300,15 @@ The default action is \fBnot\fP performed when
\(lq^KCLOSE\(rq has merely been masked out in the
current parser state (see below).
.TP
+.SCITECO_TOPIC ^KMOUSE
+.B ^KMOUSE
+Mouse event occurred.
+This may not be delivered unless bit 7 (64) is set in the
+\fBED\fP flags.
+You can use \fBEJ\fP with negative keys to retrieve
+the event type, mouse coordinates and other information
+about the last mouse event.
+.TP
.BI ^K x
Any other key with printable representation and all control codes
are looked up with a \(lq^K\(rq prefix.
diff --git a/lib/fnkeys.tes b/lib/fnkeys.tes
index ab78025..857c249 100644
--- a/lib/fnkeys.tes
+++ b/lib/fnkeys.tes
@@ -124,3 +124,71 @@
1U[ F9]
@[ F10]{(ESZOOMIN{-12D}}
1U[ F10]
+
+!*
+ * Mouse integration.
+ * Might be disabled unless bit 7 (64) is enabled in ED.
+ *
+ * Left click: Edit command line to jump to position.
+ * Ctrl+left click: Jump to beginning of line.
+ * Right click: Insert position or position range (when dragging).
+ * Double right click: insert range for word under cursor
+ * Ctrl+right click: Insertion beginning of line
+ * Scroll wheel: scrolls (faster with shift)
+ * Ctrl+scroll wheel: zoom (GTK-only)
+ *!
+@[MOUSE]{
+ -2EJESCHARPOSITIONFROMPOINTU.p
+ -4EJ&2"N Q.pESLINEFROMPOSITIONESPOSITIONFROMLINEU.p '
+ 1,Q.pESWORDSTARTPOSITION:U.#ws
+ 1,Q.pESWORDENDPOSITION:U.#we
+ Q.p:U.p
+
+ -EJOpressed,released,scrollup,scrolldown
+ !pressed!
+ !* left click *!
+ -3EJ-1"= Q.p-.M#c !* not reached *! '
+ { -9D
+ !* right click *!
+ -3EJ-3"= Q.p"U I(\.p ' '
+ !* middle click *!
+ -3EJ-2"= :Q~"U I(g~) ' '
+ }
+ !released!
+ { -9D
+ -3EJ-3"= Q.p"U
+ !* right click *!
+ <-A-("=1;'R> \U.o
+ Q.o-Q.p"=
+ .-1"> -2A-)"=
+ R <-A-("=1;'R>
+ \-Q.p"=
+ !* double right-click *!
+ .,ZD I\.#ws,\.#we
+ '
+ ' '
+ ZJ I)
+ |
+ Q.o-Q.p"> Q.o,Q.p U.oU.p '
+ !* right drag *!
+ .,ZD I\.o,\.p)
+ '
+ ' '
+ }
+ !scrollup!
+ -4EJ&2"=
+ -4EJ&1"=-1|-2',0ESLINESCROLL
+ |
+ ESZOOMIN
+ '
+ {-9D}
+ !scrolldown!
+ -4EJ&2"=
+ -4EJ&1"=1|2',0ESLINESCROLL
+ |
+ ESZOOMOUT
+ '
+ {-9D}
+}
+@[ MOUSE]{(M[MOUSE]}
+1U[ MOUSE]
diff --git a/sample.teco_ini b/sample.teco_ini
index a9caf26..0debdcc 100644
--- a/sample.teco_ini
+++ b/sample.teco_ini
@@ -59,6 +59,9 @@ EMQ[$SCITECOPATH]/opener.tes
!* Enable default function key macros *!
EMQ[$SCITECOPATH]/fnkeys.tes
+!* Comment out to disable mouse interaction on Curses *!
+0,64ED
+
!* Uncomment if terminal supports OSC-52 clipboards *!
!!0,256ED
diff --git a/src/core-commands.c b/src/core-commands.c
index f74b5f2..0d23adb 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -2221,37 +2221,50 @@ TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_escape,
* Without any argument ED returns the current flags.
*
* Currently, the following flags are used by \*(ST:
- * - 4: If enabled, prefer raw single-byte ANSI encoding
- * for all new buffers and registers.
- * This does not change the encoding of any existing
- * buffers and any initialized default register when set via
- * \fBED\fP, so you might want to launch \*(ST with \fB--8bit\fP.
- * - 8: Enable/disable automatic folding of case-insensitive
- * command characters during interactive key translation.
- * The case of letter keys is inverted, so one or two
- * character commands will typically be inserted upper-case,
- * but you can still press Shift to insert lower-case letters.
- * Case-insensitive Q-Register specifications are not
- * case folded.
- * This is thought to improve the readability of the command
- * line macro.
- * - 16: Enable/disable automatic translation of end of
- * line sequences to and from line feed.
- * Disabling this flag allows 8-bit clean loading and saving
- * of files.
- * - 32: Enable/Disable buffer editing hooks
- * (via execution of macro in global Q-Register \(lqED\(rq)
- * - 128: Enable/Disable enforcement of UNIX98
- * \(lq/bin/sh\(rq emulation for operating system command
- * executions
- * - 256: Enable/Disable OSC-52 clipboard support.
- * Must only be enabled if the terminal emulator is configured
- * properly.
- * - 512: Enable/Disable Unicode icons in the Curses UI.
- * This requires a capable font, like the ones provided
- * by the \(lqNerd Fonts\(rq project.
- * Changes to this flag in interactive mode may not become
- * effective immediately.
+ * .IP 4: 5
+ * If enabled, prefer raw single-byte ANSI encoding
+ * for all new buffers and registers.
+ * This does not change the encoding of any existing
+ * buffers and any initialized default register when set via
+ * \fBED\fP, so you might want to launch \*(ST with \fB--8bit\fP.
+ * .IP 8:
+ * Enable/disable automatic folding of case-insensitive
+ * command characters during interactive key translation.
+ * The case of letter keys is inverted, so one or two
+ * character commands will typically be inserted upper-case,
+ * but you can still press Shift to insert lower-case letters.
+ * Case-insensitive Q-Register specifications are not
+ * case folded.
+ * This is thought to improve the readability of the command
+ * line macro.
+ * .IP 16:
+ * Enable/disable automatic translation of end of
+ * line sequences to and from line feed.
+ * Disabling this flag allows 8-bit clean loading and saving
+ * of files.
+ * .IP 32:
+ * Enable/Disable buffer editing hooks
+ * (via execution of macro in global Q-Register \(lqED\(rq)
+ * .IP 64:
+ * .SCITECO_TOPIC mouse
+ * Enable/Disable processing and delivery of mouse events in
+ * the Curses UI.
+ * If enabled, the terminal emulator's default mouse behavior
+ * may be inhibited.
+ * .IP 128:
+ * Enable/Disable enforcement of UNIX98
+ * \(lq/bin/sh\(rq emulation for operating system command
+ * executions
+ * .IP 256:
+ * Enable/Disable OSC-52 clipboard support.
+ * Must only be enabled if the terminal emulator is configured
+ * properly.
+ * .IP 512:
+ * Enable/Disable Unicode icons in the Curses UI.
+ * This requires a capable font, like the ones provided
+ * by the \(lqNerd Fonts\(rq project.
+ * Changes to this flag in interactive mode may not become
+ * effective immediately.
*
* The features controlled thus are discribed in other sections
* of this manual.
@@ -2277,9 +2290,10 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
/*$ EJ properties
* [key]EJ -> value -- Get and set system properties
- * -EJ -> value
* value,keyEJ
* rgb,color,3EJ
+ * -EJ -> event
+ * -2EJ -> y, x
*
* This command may be used to get and set system
* properties.
@@ -2293,16 +2307,16 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* write-only or read-only.
*
* The following property keys are defined:
- * .IP 0 4
+ * .IP 0: 4
* The current user interface: 1 for Curses, 2 for GTK
* (\fBread-only\fP)
- * .IP 1
+ * .IP 1:
* The current numbfer of buffers: Also the numeric id
* of the last buffer in the ring. This is implied if
* no argument is given, so \(lqEJ\(rq returns the number
* of buffers in the ring.
* (\fBread-only\fP)
- * .IP 2
+ * .IP 2:
* The current memory limit in bytes.
* This limit helps to prevent dangerous out-of-memory
* conditions (e.g. resulting from infinite loops) by
@@ -2328,7 +2342,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* this happens you may have to clear your command-line
* first.
* Memory limiting is enabled by default.
- * .IP 3
+ * .IP 3:
* This \fBwrite-only\fP property allows redefining the
* first 16 entries of the terminal color palette \(em a
* feature required by some
@@ -2369,17 +2383,60 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* on exit the author is aware of is \fBxterm\fP(1) and
* the Linux console driver.
* You have been warned. Good luck.
- * .IP 4
+ * .IP 4:
* The column after the last horizontal movement.
* This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal
* setting \fBSCI_CHOOSECARETX\fP.
* Unless most other settings, this is on purpose not restored on rubout,
* so it "survives" command line replacements.
+ * .
+ * .IP -1:
+ * Type of the last mouse event (\fBread-only\fP).
+ * One of the following values will be returned:
+ * .RS
+ * . IP 1: 4
+ * Some button has been pressed
+ * . IP 2:
+ * Some button has been released
+ * . IP 3:
+ * Scroll up
+ * . IP 4:
+ * Scroll down
+ * .RE
+ * .IP -2:
+ * Coordinates of the mouse pointer relative to the Scintilla view
+ * at the time of the last mouse event.
+ * This is in pixels or cells depending on the UI.
+ * First the Y coordinate is pushed, followed by the X coordinate,
+ * allowing you to pass them on directly to the \fBSCI_POSITIONFROMPOINT\fP
+ * and similar Scintilla messages using the \fBES\fP command.
+ * (\fBread-only\fP)
+ * .IP -3:
+ * Number of the mouse button involved in the last mouse event, beginning with 1.
+ * Can be -1 if the button cannot be determined or is irrelevant.
+ * (\fBread-only\fP)
+ * .IP -4:
+ * Bit mask describing the key modifiers at the time of the last
+ * mouse event (\fBread-only\fP).
+ * Currently, the following flags are used:
+ * .RS
+ * . IP 1: 4
+ * Shift key
+ * . IP 2:
+ * Control key (CTRL)
+ * . IP 4:
+ * Alt key
+ * .RE
*/
static void
teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
{
enum {
+ EJ_MOUSE_MODS = -4,
+ EJ_MOUSE_BUTTON,
+ EJ_MOUSE_COORD,
+ EJ_MOUSE_TYPE,
+
EJ_USER_INTERFACE = 0,
EJ_BUFFERS,
EJ_MEMORY_LIMIT,
@@ -2442,6 +2499,21 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
* Get property
*/
switch (property) {
+ case EJ_MOUSE_TYPE:
+ teco_expressions_push(teco_mouse.type);
+ break;
+ case EJ_MOUSE_COORD:
+ /* can be passed down to @ES/POSITIONFROMPOINT// */
+ teco_expressions_push(teco_mouse.y);
+ teco_expressions_push(teco_mouse.x);
+ break;
+ case EJ_MOUSE_BUTTON:
+ teco_expressions_push(teco_mouse.button);
+ break;
+ case EJ_MOUSE_MODS:
+ teco_expressions_push(teco_mouse.mods);
+ break;
+
case EJ_USER_INTERFACE:
/*
* FIXME: Replace INTERFACE_* macros with
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index c8daab9..fe2b1bb 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -53,6 +53,7 @@
* Some macros in term.h interfere with our code.
*/
#undef lines
+#undef buttons
#endif
#include <Scintilla.h>
@@ -688,6 +689,15 @@ teco_interface_init_interactive(GError **error)
#endif
/*
+ * Disables click-detection.
+ * If we'd want to discern PRESSED and CLICKED events,
+ * we'd have to emulate the same feature on GTK.
+ */
+#if NCURSES_MOUSE_VERSION >= 2
+ mouseinterval(0);
+#endif
+
+ /*
* We always have a CTRL handler on Windows, but doing it
* here again, ensures that we have a higher precedence
* than the one installed by PDCurses.
@@ -1584,9 +1594,92 @@ teco_interface_refresh(void)
doupdate();
}
+#if NCURSES_MOUSE_VERSION >= 2
+
+#define BUTTON_NUM(X) \
+ (BUTTON##X##_PRESSED | BUTTON##X##_RELEASED | \
+ BUTTON##X##_CLICKED | BUTTON##X##_DOUBLE_CLICKED | BUTTON##X##_TRIPLE_CLICKED)
+#define BUTTON_EVENT(X) \
+ (BUTTON1_##X | BUTTON2_##X | BUTTON3_##X | BUTTON4_##X | BUTTON5_##X)
+
+static gboolean
+teco_interface_getmouse(void)
+{
+ MEVENT event;
+ WINDOW *current = teco_view_get_window(teco_interface_current_view);
+
+ /*
+ * Return mouse coordinates relative to the view.
+ * They will be in characters, but that's what SCI_POSITIONFROMPOINT
+ * expects on Scinterm anyway.
+ */
+ if (getmouse(&event) != OK ||
+ !wmouse_trafo(current, &event.y, &event.x, FALSE))
+ /* no event inside of current view */
+ return FALSE;
+
+ /*
+ * NOTE: There will only be one of the button bits
+ * set in bstate, so we don't loose information translating
+ * them to enums.
+ *
+ * At least on ncurses, we don't always get a RELEASED event.
+ * It instead sends only REPORT_MOUSE_POSITION,
+ * so make sure not to overwrite teco_mouse.button in this case.
+ */
+ if (event.bstate & BUTTON_NUM(4))
+ /* scroll up - there will be no RELEASED event */
+ teco_mouse.type = TECO_MOUSE_SCROLLUP;
+ else if (event.bstate & BUTTON_NUM(5))
+ /* scroll down - there will be no RELEASED event */
+ teco_mouse.type = TECO_MOUSE_SCROLLDOWN;
+ else if (event.bstate & BUTTON_EVENT(RELEASED))
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ else if (event.bstate & BUTTON_EVENT(PRESSED))
+ teco_mouse.type = TECO_MOUSE_PRESSED;
+ else
+ /* can also be REPORT_MOUSE_POSITION */
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+
+ teco_mouse.x = event.x;
+ teco_mouse.y = event.y;
+
+ if (event.bstate & BUTTON_NUM(1))
+ teco_mouse.button = 1;
+ else if (event.bstate & BUTTON_NUM(2))
+ teco_mouse.button = 2;
+ else if (event.bstate & BUTTON_NUM(3))
+ teco_mouse.button = 3;
+ else if (!(event.bstate & REPORT_MOUSE_POSITION))
+ teco_mouse.button = -1;
+
+ teco_mouse.mods = 0;
+ if (event.bstate & BUTTON_SHIFT)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event.bstate & BUTTON_CTRL)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ if (event.bstate & BUTTON_ALT)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return TRUE;
+}
+
+#endif /* NCURSES_MOUSE_VERSION >= 2 */
+
static gint
teco_interface_blocking_getch(void)
{
+#if NCURSES_MOUSE_VERSION >= 2
+ /*
+ * FIXME: REPORT_MOUSE_POSITION is necessary at least on
+ * ncurses, so that BUTTONX_RELEASED events are reported.
+ * It does NOT report every cursor movement, though.
+ * What does PDCurses do?
+ */
+ mousemask(teco_ed & TECO_ED_MOUSEKEY
+ ? ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION : 0, NULL);
+#endif
+
/* no special <CTRL/C> handling */
raw();
nodelay(teco_interface.input_pad, FALSE);
@@ -1697,6 +1790,15 @@ teco_interface_event_loop_iter(void)
#undef FNS
#undef FN
+#if NCURSES_MOUSE_VERSION >= 2
+ case KEY_MOUSE:
+ /* ANY of the mouse events */
+ if (teco_interface_getmouse() &&
+ !teco_cmdline_keymacro("MOUSE", -1, error))
+ return;
+ break;
+#endif
+
/*
* Control keys and keys with printable representation
*/
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 1a990e8..4619b75 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -68,8 +68,8 @@ static void teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str,
static void teco_interface_size_allocate_cb(GtkWidget *widget,
GdkRectangle *allocation,
gpointer user_data);
-static gboolean teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
- gpointer user_data);
+static gboolean teco_interface_input_cb(GtkWidget *widget, GdkEvent *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;
@@ -133,16 +133,12 @@ teco_view_new(void)
* 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);
+ 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(sci), events);
g_signal_connect(sci, SCINTILLA_NOTIFY,
@@ -240,7 +236,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);
@@ -316,6 +312,18 @@ teco_interface_init(void)
g_signal_connect(teco_interface.event_box_widget, "size-allocate",
G_CALLBACK(teco_interface_size_allocate_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");
GtkWidget *message_bar_content =
@@ -977,7 +985,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 +1113,77 @@ 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:
+ default:
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ break;
+ }
+
+ 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)
{
@@ -1274,15 +1349,16 @@ teco_interface_size_allocate_cb(GtkWidget *widget,
}
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 +1371,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,7 +1382,7 @@ 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;
}
@@ -1314,7 +1391,7 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
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));
@@ -1333,11 +1410,28 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
* the Curses UI.
*/
gdk_window_freeze_updates(top_window);
+ const teco_view_t *last_view = teco_interface_current_view;
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);
gdk_window_thaw_updates(top_window);
if (g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT)) {
@@ -1374,7 +1468,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 +1480,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.c b/src/interface.c
index a3c9935..9ec1bed 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -43,6 +43,9 @@ TECO_DEFINE_UNDO_CALL(teco_interface_ssm, unsigned int, uptr_t, sptr_t);
TECO_DEFINE_UNDO_CALL(teco_interface_info_update_qreg, const teco_qreg_t *);
TECO_DEFINE_UNDO_CALL(teco_interface_info_update_buffer, const teco_buffer_t *);
+/** Last mouse event */
+teco_mouse_t teco_mouse = {0};
+
typedef struct {
teco_string_t str;
gchar name[];
diff --git a/src/interface.h b/src/interface.h
index 12e76fb..967f14d 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -141,6 +141,28 @@ void teco_interface_popup_clear(void);
/** @pure */
gboolean teco_interface_is_interrupted(void);
+typedef struct {
+ enum {
+ TECO_MOUSE_PRESSED = 1,
+ TECO_MOUSE_RELEASED,
+ TECO_MOUSE_SCROLLUP,
+ TECO_MOUSE_SCROLLDOWN
+ } type;
+
+ guint x; /*< X-coordinate relative to view */
+ guint y; /*< Y-coordinate relative to view */
+
+ gint button; /*< number of pressed mouse button or -1 */
+
+ enum {
+ TECO_MOUSE_SHIFT = (1 << 0),
+ TECO_MOUSE_CTRL = (1 << 1),
+ TECO_MOUSE_ALT = (1 << 2)
+ } mods;
+} teco_mouse_t;
+
+extern teco_mouse_t teco_mouse;
+
/** @pure main entry point */
gboolean teco_interface_event_loop(GError **error);
diff --git a/src/sciteco.h b/src/sciteco.h
index 909821b..4868303 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -89,7 +89,7 @@ enum {
TECO_ED_AUTOCASEFOLD = (1 << 3),
TECO_ED_AUTOEOL = (1 << 4),
TECO_ED_HOOKS = (1 << 5),
- //TECO_ED_MOUSEKEY = (1 << 6),
+ TECO_ED_MOUSEKEY = (1 << 6),
TECO_ED_SHELLEMU = (1 << 7),
TECO_ED_OSC52 = (1 << 8),
TECO_ED_ICONS = (1 << 9)