diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2025-02-15 01:32:05 +0300 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2025-02-23 04:52:39 +0300 |
commit | 428dafa568923d5632101c716fb20a3de35d27be (patch) | |
tree | 475b270fc384d5040e4711e155e47d580c52b1a3 /src/interface-curses | |
parent | 980dcdaa138a42830af4e2533b7e970d7f5fa3cf (diff) | |
download | sciteco-428dafa568923d5632101c716fb20a3de35d27be.tar.gz |
support mouse interaction with popup windows
* Curses allows scrolling with the scroll wheel at least
if mouse support is enabled via ED flags.
Gtk always supported that.
* Allow clicking on popup entries to fully autocomplete them.
Since this behavior - just like auto completions - is parser state-dependant,
I introduced a new state method (insert_completion_cb).
All the implementations are currently in cmdline.c since there is some overlap
with the process_edit_cmd_cb implementations.
* Fixed pressing undefined function keys while showing the popup.
The popup area is no longer redrawn/replaced with the Scintilla view.
Instead, continue to show the popup.
Diffstat (limited to 'src/interface-curses')
-rw-r--r-- | src/interface-curses/curses-info-popup.c | 68 | ||||
-rw-r--r-- | src/interface-curses/curses-info-popup.h | 11 | ||||
-rw-r--r-- | src/interface-curses/interface.c | 74 |
3 files changed, 142 insertions, 11 deletions
diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c index 9525391..e470879 100644 --- a/src/interface-curses/curses-info-popup.c +++ b/src/interface-curses/curses-info-popup.c @@ -200,6 +200,61 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) wmove(ctx->window, bar_y, cols-1); wattron(ctx->window, A_REVERSE); wvline(ctx->window, ' ', bar_height); +} + +/** + * Find the entry at the given character coordinates. + * + * @param ctx The popup widget to look up + * @param y The pointer's Y position, relative to the popup's window + * @param x The pointer's X position, relative to the popup's window + * @return Pointer to the entry's string under the pointer or NULL. + * This string is owned by the popup and is only valid until the + * popup is cleared. + * + * @note This must match the calculations in teco_curses_info_popup_init_pad(). + */ +const teco_string_t * +teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x) +{ + int cols = getmaxx(stdscr); /**! screen width */ + gint pad_cols; /**! entry columns */ + gint pad_colwidth; /**! width per entry column */ + + /* + * With Unicode icons enabled, we reserve 2 characters at the beginning and one + * after the filename/directory. + * Otherwise 2 characters after the entry. + */ + gint reserve = teco_ed & TECO_ED_ICONS ? 2+1 : 2; + pad_colwidth = MIN(ctx->longest + reserve, cols - 2); + + /* pad_cols = floor((cols - 2) / pad_colwidth) */ + pad_cols = (cols - 2) / pad_colwidth; + + gint cur_col = 0; + for (teco_stailq_entry_t *cur = ctx->list.first; cur != NULL; cur = cur->next) { + teco_popup_entry_t *entry = (teco_popup_entry_t *)cur; + gint cur_line = cur_col/pad_cols + 1; + + if (cur_line > ctx->pad_first_line+y) + break; + if (cur_line == ctx->pad_first_line+y && + x > (cur_col % pad_cols)*pad_colwidth && x <= ((cur_col % pad_cols)+1)*pad_colwidth) + return &entry->name; + + cur_col++; + } + + return NULL; +} + +void +teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx) +{ + gint lines = getmaxy(stdscr); + gint pad_lines = getmaxy(ctx->pad); + gint popup_lines = MIN(pad_lines + 1, lines - 1); /* progress scroll position */ ctx->pad_first_line += popup_lines - 1; @@ -211,6 +266,19 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) } void +teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta) +{ + gint lines = getmaxy(stdscr); + gint pad_lines = getmaxy(ctx->pad); + gint popup_lines = MIN(pad_lines + 1, lines - 1); + + ctx->pad_first_line = MAX(ctx->pad_first_line+delta, 0); + if (pad_lines - ctx->pad_first_line < popup_lines - 1) + /* show last page */ + ctx->pad_first_line = pad_lines - (popup_lines - 1); +} + +void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx) { if (ctx->window) diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h index 96dee5a..6f2ac9a 100644 --- a/src/interface-curses/curses-info-popup.h +++ b/src/interface-curses/curses-info-popup.h @@ -23,6 +23,7 @@ #include <curses.h> #include "list.h" +#include "string-utils.h" #include "interface.h" typedef struct { @@ -49,6 +50,10 @@ void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_ const gchar *name, gsize name_len, gboolean highlight); void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr); +const teco_string_t *teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x); +void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx); +void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta); + static inline bool teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx) { @@ -58,8 +63,10 @@ teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx) static inline void teco_curses_info_popup_noutrefresh(teco_curses_info_popup_t *ctx) { - if (ctx->window) - wnoutrefresh(ctx->window); + if (!ctx->window) + return; + redrawwin(ctx->window); + wnoutrefresh(ctx->window); } void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx); diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 9480822..3be1001 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -356,6 +356,7 @@ static struct { GQueue *input_queue; teco_curses_info_popup_t popup; + gsize popup_prefix_len; /** * GError "thrown" by teco_interface_event_loop_iter(). @@ -1480,7 +1481,7 @@ 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 (!teco_interface.cmdline_window) /* batch mode */ @@ -1489,9 +1490,21 @@ teco_interface_popup_show(void) short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + teco_interface.popup_prefix_len = prefix_len; teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg)); } +void +teco_interface_popup_scroll(void) +{ + if (!teco_interface.cmdline_window) + /* batch mode */ + return; + + teco_curses_info_popup_scroll_page(&teco_interface.popup); + teco_interface_popup_show(teco_interface.popup_prefix_len); +} + gboolean teco_interface_popup_is_shown(void) { @@ -1583,6 +1596,11 @@ teco_interface_refresh(void) teco_view_noutrefresh(teco_interface_current_view); wnoutrefresh(teco_interface.msg_window); wnoutrefresh(teco_interface.cmdline_window); + /* + * FIXME: Why do we have to redrawwin() the popup window + * to keep it on the screen? + * Perhaps something is causing a redraw of the entire Scinterm view. + */ teco_curses_info_popup_noutrefresh(&teco_interface.popup); doupdate(); } @@ -1596,20 +1614,59 @@ teco_interface_refresh(void) (BUTTON1_##X | BUTTON2_##X | BUTTON3_##X | BUTTON4_##X | BUTTON5_##X) static gboolean -teco_interface_getmouse(void) +teco_interface_getmouse(GError **error) { MEVENT event; - WINDOW *current = teco_view_get_window(teco_interface_current_view); + + if (getmouse(&event) != OK) + return TRUE; + + if (teco_curses_info_popup_is_shown(&teco_interface.popup) && + wmouse_trafo(teco_interface.popup.window, &event.y, &event.x, FALSE)) { + /* + * NOTE: Not all curses variants report the RELEASED event, + * but may also return REPORT_MOUSE_POSITION. + * So we might react to all button presses as well. + */ + if (event.bstate & (BUTTON1_RELEASED | REPORT_MOUSE_POSITION)) { + teco_machine_t *machine = &teco_cmdline.machine.parent; + const teco_string_t *insert = teco_curses_info_popup_getentry(&teco_interface.popup, event.y, event.x); + + if (insert && machine->current->insert_completion_cb) { + /* successfully clicked popup item */ + const teco_string_t insert_suffix = {insert->data + teco_interface.popup_prefix_len, + insert->len - teco_interface.popup_prefix_len}; + if (!machine->current->insert_completion_cb(machine, &insert_suffix, error)) + return FALSE; + + teco_interface_popup_clear(); + teco_interface_msg_clear(); + teco_interface_cmdline_update(&teco_cmdline); + } + + return TRUE; + } + if (event.bstate & BUTTON_NUM(4)) + teco_curses_info_popup_scroll(&teco_interface.popup, -1); + else if (event.bstate & BUTTON_NUM(5)) + teco_curses_info_popup_scroll(&teco_interface.popup, +1); + + short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg)); + + return TRUE; + } /* * 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)) + WINDOW *current = teco_view_get_window(teco_interface_current_view); + if (!wmouse_trafo(current, &event.y, &event.x, FALSE)) /* no event inside of current view */ - return FALSE; + return TRUE; /* * NOTE: There will only be one of the button bits @@ -1654,7 +1711,7 @@ teco_interface_getmouse(void) if (event.bstate & BUTTON_ALT) teco_mouse.mods |= TECO_MOUSE_ALT; - return TRUE; + return teco_cmdline_keymacro("MOUSE", -1, error); } #endif /* NCURSES_MOUSE_VERSION >= 2 */ @@ -1789,8 +1846,7 @@ teco_interface_event_loop_iter(void) #if NCURSES_MOUSE_VERSION >= 2 case KEY_MOUSE: /* ANY of the mouse events */ - if (teco_interface_getmouse() && - !teco_cmdline_keymacro("MOUSE", -1, error)) + if (!teco_interface_getmouse(error)) return; break; #endif |