Rewrite to use termo
Also get rid of some silliness that I'm only able to see now.
This commit is contained in:
parent
a3348d888b
commit
b352a0fc8d
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "termo"]
|
||||
path = termo
|
||||
url = git://github.com/pjanouch/termo.git
|
@ -8,6 +8,9 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
|
||||
"${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-missing-field-initializers")
|
||||
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
|
||||
|
||||
# Build options
|
||||
option (USE_SYSTEM_TERMO "Don't compile our own termo, use the system one" OFF)
|
||||
|
||||
# Version
|
||||
set (project_VERSION_MAJOR "0")
|
||||
set (project_VERSION_MINOR "1")
|
||||
@ -21,13 +24,27 @@ set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
||||
include (CheckFunctionExists)
|
||||
CHECK_FUNCTION_EXISTS ("wcwidth" HAVE_WCWIDTH)
|
||||
|
||||
# Dependecies
|
||||
# Dependencies
|
||||
find_package (ZLIB REQUIRED)
|
||||
|
||||
find_package (PkgConfig REQUIRED)
|
||||
pkg_check_modules (dependencies REQUIRED ncursesw glib-2.0 gio-2.0 pango)
|
||||
|
||||
include_directories (${ZLIB_INCLUDE_DIRS} ${dependencies_INCLUDE_DIRS})
|
||||
if (USE_SYSTEM_TERMO)
|
||||
find_package (Termo REQUIRED)
|
||||
else (USE_SYSTEM_TERMO)
|
||||
add_subdirectory (termo EXCLUDE_FROM_ALL)
|
||||
# We don't have many good choices when we don't want to install it and want
|
||||
# to support older versions of CMake; this is a relatively clean approach
|
||||
# (other possibilities: setting a variable in the parent scope, using a
|
||||
# cache variable, writing a special config file with build paths in it and
|
||||
# including it here, or setting a custom property on the targets).
|
||||
get_directory_property (Termo_INCLUDE_DIRS
|
||||
DIRECTORY termo INCLUDE_DIRECTORIES)
|
||||
set (Termo_LIBRARIES termo-static)
|
||||
endif (USE_SYSTEM_TERMO)
|
||||
|
||||
include_directories (${ZLIB_INCLUDE_DIRS}
|
||||
${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
|
||||
|
||||
# Localization
|
||||
find_package (Gettext REQUIRED)
|
||||
@ -76,7 +93,8 @@ set (project_common_headers
|
||||
src/utils.h)
|
||||
|
||||
# Project libraries
|
||||
set (project_common_libraries ${ZLIB_LIBRARIES} ${dependencies_LIBRARIES})
|
||||
set (project_common_libraries
|
||||
${ZLIB_LIBRARIES} ${dependencies_LIBRARIES} termo-static)
|
||||
|
||||
# Create a common project library so that source files are only compiled once
|
||||
if (${CMAKE_VERSION} VERSION_GREATER "2.8.7")
|
||||
|
746
src/sdtui.c
746
src/sdtui.c
@ -31,7 +31,6 @@
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <pango/pango.h>
|
||||
#include <ncurses.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <unistd.h>
|
||||
@ -39,70 +38,16 @@
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <termo.h> // input
|
||||
#include <ncurses.h> // output
|
||||
|
||||
#include "config.h"
|
||||
#include "stardict.h"
|
||||
|
||||
|
||||
#define CTRL_KEY(x) ((x) - 'A' + 1)
|
||||
|
||||
#define KEY_CTRL_A CTRL_KEY ('A') //!< Ctrl-A (SOH)
|
||||
#define KEY_CTRL_B CTRL_KEY ('B') //!< Ctrl-B (STX)
|
||||
#define KEY_CTRL_E CTRL_KEY ('E') //!< Ctrl-E (ENQ)
|
||||
#define KEY_CTRL_F CTRL_KEY ('F') //!< Ctrl-F (ACK)
|
||||
#define KEY_CTRL_H CTRL_KEY ('H') //!< Ctrl-H (BS)
|
||||
#define KEY_CTRL_K CTRL_KEY ('K') //!< Ctrl-K (VT)
|
||||
#define KEY_CTRL_L CTRL_KEY ('L') //!< Ctrl-L (FF)
|
||||
#define KEY_CTRL_N CTRL_KEY ('N') //!< Ctrl-N (SO)
|
||||
#define KEY_CTRL_P CTRL_KEY ('P') //!< Ctrl-P (DLE)
|
||||
#define KEY_CTRL_T CTRL_KEY ('T') //!< Ctrl-T (DC4)
|
||||
#define KEY_CTRL_U CTRL_KEY ('U') //!< Ctrl-U (NAK)
|
||||
#define KEY_CTRL_W CTRL_KEY ('W') //!< Ctrl-W (ETB)
|
||||
|
||||
#define KEY_RETURN 13 //!< Enter
|
||||
#define KEY_ESCAPE 27 //!< Esc
|
||||
|
||||
typedef enum {
|
||||
TERMINAL_UNKNOWN, //!< No extra handling
|
||||
TERMINAL_XTERM, //!< xterm and VTE extra keycodes
|
||||
TERMINAL_RXVT //!< rxvt extra keycodes
|
||||
} TerminalType; //!< Type of the terminal
|
||||
|
||||
typedef enum {
|
||||
KEY_NOT_RECOGNISED, //!< Not recognised
|
||||
|
||||
KEY_CTRL_UP, //!< Ctrl + Up arrow
|
||||
KEY_CTRL_DOWN, //!< Ctrl + Down arrow
|
||||
KEY_CTRL_LEFT, //!< Ctrl + Left arrow
|
||||
KEY_CTRL_RIGHT, //!< Ctrl + Right arrow
|
||||
|
||||
KEY_ALT_UP, //!< Alt + Up arrow
|
||||
KEY_ALT_DOWN, //!< Alt + Down arrow
|
||||
KEY_ALT_LEFT, //!< Alt + Left arrow
|
||||
KEY_ALT_RIGHT //!< Alt + Right arrow
|
||||
} ExtraKeyCode; //!< Translated key codes above KEY_MAX
|
||||
|
||||
// --- Utilities ---------------------------------------------------------------
|
||||
|
||||
static int
|
||||
poll_restart (struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
int ret;
|
||||
do
|
||||
ret = poll (fds, nfds, timeout);
|
||||
while (ret == -1 && errno == EINTR);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Wrapper for curses event data. */
|
||||
typedef struct curses_event CursesEvent;
|
||||
|
||||
struct curses_event
|
||||
{
|
||||
wint_t code;
|
||||
guint is_char : 1;
|
||||
MEVENT mouse;
|
||||
};
|
||||
|
||||
static size_t
|
||||
unichar_width (gunichar ch)
|
||||
{
|
||||
@ -122,58 +67,6 @@ is_character_in_locale (wchar_t c)
|
||||
return wcstombs (NULL, s, 0) != (size_t) -1;
|
||||
}
|
||||
|
||||
/** Translate key codes above KEY_MAX returned from ncurses into something
|
||||
* meaningful, based on the terminal type. The values have been obtained
|
||||
* experimentally. Some keycodes make ncurses return KEY_ESCAPE, even
|
||||
* depending on actual terminal settings, thus this is not reliable at all.
|
||||
* xterm/VTE seems to behave nicely, though.
|
||||
*/
|
||||
static guint
|
||||
translate_extra_keycode (wchar_t code, TerminalType terminal)
|
||||
{
|
||||
switch (terminal)
|
||||
{
|
||||
case TERMINAL_XTERM:
|
||||
switch (code)
|
||||
{
|
||||
case 565: return KEY_CTRL_UP;
|
||||
case 524: return KEY_CTRL_DOWN;
|
||||
case 544: return KEY_CTRL_LEFT;
|
||||
case 559: return KEY_CTRL_RIGHT;
|
||||
|
||||
case 563: return KEY_ALT_UP;
|
||||
case 522: return KEY_ALT_DOWN;
|
||||
case 542: return KEY_ALT_LEFT;
|
||||
case 557: return KEY_ALT_RIGHT;
|
||||
}
|
||||
break;
|
||||
case TERMINAL_RXVT:
|
||||
switch (code)
|
||||
{
|
||||
case 521: return KEY_CTRL_UP;
|
||||
case 514: return KEY_CTRL_DOWN;
|
||||
}
|
||||
break;
|
||||
case TERMINAL_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
return KEY_NOT_RECOGNISED;
|
||||
}
|
||||
|
||||
/** Get the type of the terminal based on the TERM environment variable. */
|
||||
static TerminalType
|
||||
get_terminal_type (void)
|
||||
{
|
||||
const gchar *term = g_getenv ("TERM");
|
||||
if (!term) return TERMINAL_UNKNOWN;
|
||||
|
||||
gchar term_copy[strcspn (term, "-") + 1];
|
||||
g_strlcpy (term_copy, term, sizeof term_copy);
|
||||
if (!strcmp (term_copy, "xterm")) return TERMINAL_XTERM;
|
||||
if (!strcmp (term_copy, "rxvt")) return TERMINAL_RXVT;
|
||||
return TERMINAL_UNKNOWN;
|
||||
}
|
||||
|
||||
// --- Application -------------------------------------------------------------
|
||||
|
||||
/** Data relating to one entry within the dictionary. */
|
||||
@ -190,9 +83,10 @@ struct view_entry
|
||||
|
||||
struct application
|
||||
{
|
||||
TerminalType terminal_type; //!< Type of the terminal
|
||||
GMainLoop * loop; //!< Main loop
|
||||
termo_t * tk; //!< termo handle
|
||||
guint tk_timeout; //!< termo timeout
|
||||
GIConv utf8_to_wchar; //!< utf-8 -> wchar_t conversion
|
||||
GIConv wchar_to_utf8; //!< wchar_t -> utf-8 conversion
|
||||
|
||||
StardictDict * dict; //!< The current dictionary
|
||||
guint show_help : 1; //!< Whether help can be shown
|
||||
@ -312,6 +206,10 @@ app_reload_view (Application *self)
|
||||
static void
|
||||
app_init (Application *self, const gchar *filename)
|
||||
{
|
||||
self->loop = g_main_loop_new (NULL, FALSE);
|
||||
self->tk = NULL;
|
||||
self->tk_timeout = 0;
|
||||
|
||||
GError *error = NULL;
|
||||
self->dict = stardict_dict_new (filename, &error);
|
||||
if (!self->dict)
|
||||
@ -320,7 +218,6 @@ app_init (Application *self, const gchar *filename)
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
self->terminal_type = get_terminal_type ();
|
||||
self->show_help = TRUE;
|
||||
|
||||
self->top_position = 0;
|
||||
@ -337,7 +234,6 @@ app_init (Application *self, const gchar *filename)
|
||||
|
||||
self->division = 0.5;
|
||||
|
||||
self->wchar_to_utf8 = g_iconv_open ("utf-8//translit", "wchar_t");
|
||||
self->utf8_to_wchar = g_iconv_open ("wchar_t//translit", "utf-8");
|
||||
|
||||
app_reload_view (self);
|
||||
@ -347,12 +243,17 @@ app_init (Application *self, const gchar *filename)
|
||||
static void
|
||||
app_destroy (Application *self)
|
||||
{
|
||||
g_main_loop_unref (self->loop);
|
||||
if (self->tk)
|
||||
termo_destroy (self->tk);
|
||||
if (self->tk_timeout)
|
||||
g_source_remove (self->tk_timeout);
|
||||
|
||||
g_object_unref (self->dict);
|
||||
g_ptr_array_free (self->entries, TRUE);
|
||||
g_free (self->search_label);
|
||||
g_array_free (self->input, TRUE);
|
||||
|
||||
g_iconv_close (self->wchar_to_utf8);
|
||||
g_iconv_close (self->utf8_to_wchar);
|
||||
}
|
||||
|
||||
@ -519,7 +420,7 @@ app_show_help (Application *self)
|
||||
{
|
||||
PROJECT_NAME " " PROJECT_VERSION,
|
||||
_("Terminal UI for StarDict dictionaries"),
|
||||
"Copyright (c) 2013, Přemysl Janouch",
|
||||
"Copyright (c) 2013 - 2014, Přemysl Janouch",
|
||||
"",
|
||||
_("Type to search")
|
||||
};
|
||||
@ -795,98 +696,126 @@ app_search_for_entry (Application *self)
|
||||
move (last_y, last_x); \
|
||||
refresh ();
|
||||
|
||||
/** Process input above KEY_MAX. */
|
||||
/** The terminal has been resized, make appropriate changes. */
|
||||
static gboolean
|
||||
app_process_extra_code (Application *self, CursesEvent *event)
|
||||
app_process_resize (Application *self)
|
||||
{
|
||||
SAVE_CURSOR
|
||||
switch (translate_extra_keycode (event->code, self->terminal_type))
|
||||
{
|
||||
case KEY_CTRL_UP:
|
||||
app_one_entry_up (self);
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
case KEY_CTRL_DOWN:
|
||||
app_one_entry_down (self);
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
app_reload_view (self);
|
||||
|
||||
case KEY_ALT_LEFT:
|
||||
self->division = (app_get_left_column_width (self) - 1.) / COLS;
|
||||
guint n_visible = app_count_view_items (self) - self->top_offset;
|
||||
if ((gint) n_visible > LINES - 1)
|
||||
n_visible = LINES - 1;
|
||||
|
||||
if (self->selected >= n_visible)
|
||||
{
|
||||
app_scroll_down (self, self->selected - n_visible + 1);
|
||||
self->selected = n_visible - 1;
|
||||
}
|
||||
|
||||
app_redraw (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/** Process mouse input. */
|
||||
static gboolean
|
||||
app_process_mouse (Application *self, termo_key_t *event)
|
||||
{
|
||||
int line, column, button;
|
||||
termo_mouse_event_t type;
|
||||
termo_interpret_mouse (self->tk, event, &type, &button, &line, &column);
|
||||
|
||||
if (type != TERMO_MOUSE_PRESS || button != 1)
|
||||
return TRUE;
|
||||
|
||||
SAVE_CURSOR
|
||||
if (line == 0)
|
||||
{
|
||||
gsize label_len = g_utf8_strlen (self->search_label, -1);
|
||||
gint pos = column - label_len;
|
||||
if (pos >= 0)
|
||||
{
|
||||
self->input_pos = MIN ((guint) pos, self->input->len);
|
||||
move (0, label_len + self->input_pos);
|
||||
refresh ();
|
||||
}
|
||||
}
|
||||
else if (line <= (int) (app_count_view_items (self) - self->top_offset))
|
||||
{
|
||||
self->selected = line - 1;
|
||||
app_redraw_view (self);
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
case KEY_ALT_RIGHT:
|
||||
self->division = (app_get_left_column_width (self) + 1.) / COLS;
|
||||
app_redraw_view (self);
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/** Process input that's not a character or is a control code. */
|
||||
// --- User input handling -----------------------------------------------------
|
||||
|
||||
/** All the actions that can be performed by the user. */
|
||||
typedef enum user_action UserAction;
|
||||
|
||||
enum user_action
|
||||
{
|
||||
USER_ACTION_NONE,
|
||||
|
||||
USER_ACTION_QUIT,
|
||||
USER_ACTION_REDRAW,
|
||||
|
||||
USER_ACTION_MOVE_SPLITTER_LEFT,
|
||||
USER_ACTION_MOVE_SPLITTER_RIGHT,
|
||||
|
||||
USER_ACTION_GOTO_ENTRY_PREVIOUS,
|
||||
USER_ACTION_GOTO_ENTRY_NEXT,
|
||||
USER_ACTION_GOTO_DEFINITION_PREVIOUS,
|
||||
USER_ACTION_GOTO_DEFINITION_NEXT,
|
||||
USER_ACTION_GOTO_PAGE_PREVIOUS,
|
||||
USER_ACTION_GOTO_PAGE_NEXT,
|
||||
|
||||
USER_ACTION_INPUT_CONFIRM,
|
||||
USER_ACTION_INPUT_HOME,
|
||||
USER_ACTION_INPUT_END,
|
||||
USER_ACTION_INPUT_LEFT,
|
||||
USER_ACTION_INPUT_RIGHT,
|
||||
USER_ACTION_INPUT_DELETE_PREVIOUS,
|
||||
USER_ACTION_INPUT_DELETE_NEXT,
|
||||
USER_ACTION_INPUT_DELETE_TO_HOME,
|
||||
USER_ACTION_INPUT_DELETE_TO_END,
|
||||
USER_ACTION_INPUT_DELETE_PREVIOUS_WORD,
|
||||
USER_ACTION_INPUT_TRANSPOSE,
|
||||
|
||||
USER_ACTION_COUNT
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gboolean
|
||||
app_process_nonchar_code (Application *self, CursesEvent *event)
|
||||
app_process_user_action (Application *self, UserAction action)
|
||||
{
|
||||
SAVE_CURSOR
|
||||
switch (event->code)
|
||||
switch (action)
|
||||
{
|
||||
case KEY_RESIZE:
|
||||
{
|
||||
app_reload_view (self);
|
||||
|
||||
guint n_visible = app_count_view_items (self) - self->top_offset;
|
||||
if ((gint) n_visible > LINES - 1)
|
||||
n_visible = LINES - 1;
|
||||
|
||||
if (self->selected >= n_visible)
|
||||
{
|
||||
app_scroll_down (self, self->selected - n_visible + 1);
|
||||
self->selected = n_visible - 1;
|
||||
}
|
||||
|
||||
app_redraw (self);
|
||||
break;
|
||||
}
|
||||
case KEY_MOUSE:
|
||||
if (!(event->mouse.bstate & BUTTON1_PRESSED))
|
||||
break;
|
||||
|
||||
if (event->mouse.y == 0)
|
||||
{
|
||||
gsize label_len = g_utf8_strlen (self->search_label, -1);
|
||||
gint pos = event->mouse.x - label_len;
|
||||
if (pos >= 0)
|
||||
{
|
||||
self->input_pos = MIN ((guint) pos, self->input->len);
|
||||
move (0, label_len + self->input_pos);
|
||||
refresh ();
|
||||
}
|
||||
}
|
||||
else if (event->mouse.y <= (int)
|
||||
(app_count_view_items (self) - self->top_offset))
|
||||
{
|
||||
self->selected = event->mouse.y - 1;
|
||||
app_redraw_view (self);
|
||||
RESTORE_CURSOR
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_ESCAPE:
|
||||
case USER_ACTION_QUIT:
|
||||
return FALSE;
|
||||
case KEY_RETURN:
|
||||
self->input_confirmed = TRUE;
|
||||
app_redraw_top (self);
|
||||
break;
|
||||
|
||||
case KEY_CTRL_L: // redraw everything
|
||||
case USER_ACTION_REDRAW:
|
||||
clear ();
|
||||
app_redraw (self);
|
||||
break;
|
||||
return TRUE;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
case KEY_UP:
|
||||
case KEY_CTRL_P: // previous
|
||||
|
||||
case USER_ACTION_MOVE_SPLITTER_LEFT:
|
||||
self->division = (app_get_left_column_width (self) - 1.) / COLS;
|
||||
app_redraw_view (self);
|
||||
RESTORE_CURSOR
|
||||
return TRUE;
|
||||
case USER_ACTION_MOVE_SPLITTER_RIGHT:
|
||||
self->division = (app_get_left_column_width (self) + 1.) / COLS;
|
||||
app_redraw_view (self);
|
||||
RESTORE_CURSOR
|
||||
return TRUE;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
case USER_ACTION_GOTO_DEFINITION_PREVIOUS:
|
||||
if (self->selected > 0)
|
||||
{
|
||||
self->selected--;
|
||||
@ -895,9 +824,8 @@ app_process_nonchar_code (Application *self, CursesEvent *event)
|
||||
else
|
||||
app_scroll_up (self, 1);
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
case KEY_CTRL_N: // next
|
||||
return TRUE;
|
||||
case USER_ACTION_GOTO_DEFINITION_NEXT:
|
||||
if ((gint) self->selected < LINES - 2 &&
|
||||
self->selected < app_count_view_items (self) - self->top_offset - 1)
|
||||
{
|
||||
@ -907,103 +835,61 @@ app_process_nonchar_code (Application *self, CursesEvent *event)
|
||||
else
|
||||
app_scroll_down (self, 1);
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
case KEY_PPAGE:
|
||||
case KEY_CTRL_B: // back
|
||||
return TRUE;
|
||||
|
||||
case USER_ACTION_GOTO_ENTRY_PREVIOUS:
|
||||
app_one_entry_up (self);
|
||||
RESTORE_CURSOR
|
||||
return TRUE;
|
||||
case USER_ACTION_GOTO_ENTRY_NEXT:
|
||||
app_one_entry_down (self);
|
||||
RESTORE_CURSOR
|
||||
return TRUE;
|
||||
|
||||
case USER_ACTION_GOTO_PAGE_PREVIOUS:
|
||||
app_scroll_up (self, LINES - 1);
|
||||
// FIXME selection
|
||||
// FIXME: selection
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
case KEY_CTRL_F: // forward
|
||||
return TRUE;
|
||||
case USER_ACTION_GOTO_PAGE_NEXT:
|
||||
app_scroll_down (self, LINES - 1);
|
||||
// FIXME selection
|
||||
// FIXME: selection
|
||||
RESTORE_CURSOR
|
||||
break;
|
||||
return TRUE;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
case KEY_HOME:
|
||||
case KEY_CTRL_A:
|
||||
|
||||
case USER_ACTION_INPUT_HOME:
|
||||
self->input_pos = 0;
|
||||
app_redraw_top (self);
|
||||
break;
|
||||
case KEY_END:
|
||||
case KEY_CTRL_E:
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_END:
|
||||
self->input_pos = self->input->len;
|
||||
app_redraw_top (self);
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_LEFT:
|
||||
if (self->input_pos > 0)
|
||||
{
|
||||
self->input_pos--;
|
||||
app_redraw_top (self);
|
||||
}
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_RIGHT:
|
||||
if (self->input_pos < self->input->len)
|
||||
{
|
||||
self->input_pos++;
|
||||
app_redraw_top (self);
|
||||
}
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
case KEY_CTRL_H:
|
||||
if (self->input_pos > 0)
|
||||
{
|
||||
g_array_remove_index (self->input, --self->input_pos);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
break;
|
||||
case KEY_DC:
|
||||
if (self->input_pos < self->input->len)
|
||||
{
|
||||
g_array_remove_index (self->input, self->input_pos);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
break;
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
case KEY_CTRL_K: // delete until the end of line
|
||||
if (self->input_pos < self->input->len)
|
||||
{
|
||||
g_array_remove_range (self->input,
|
||||
self->input_pos, self->input->len - self->input_pos);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
return TRUE;
|
||||
case KEY_CTRL_W: // delete word before cursor
|
||||
{
|
||||
if (self->input_pos == 0)
|
||||
return TRUE;
|
||||
|
||||
gint i = self->input_pos;
|
||||
while (i)
|
||||
if (g_array_index (self->input, gunichar, --i) != L' ')
|
||||
break;
|
||||
while (i--)
|
||||
if (g_array_index (self->input, gunichar, i) == L' ')
|
||||
break;
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
i++;
|
||||
g_array_remove_range (self->input, i, self->input_pos - i);
|
||||
self->input_pos = i;
|
||||
|
||||
app_search_for_entry (self);
|
||||
case USER_ACTION_INPUT_CONFIRM:
|
||||
self->input_confirmed = TRUE;
|
||||
app_redraw_top (self);
|
||||
return TRUE;
|
||||
}
|
||||
case KEY_CTRL_U: // delete everything before the cursor
|
||||
if (self->input->len != 0)
|
||||
{
|
||||
g_array_remove_range (self->input, 0, self->input_pos);
|
||||
self->input_pos = 0;
|
||||
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
return TRUE;
|
||||
case KEY_CTRL_T: // transposition
|
||||
case USER_ACTION_INPUT_TRANSPOSE:
|
||||
{
|
||||
if (!self->input_pos || self->input->len < 2)
|
||||
break;
|
||||
@ -1025,60 +911,209 @@ app_process_nonchar_code (Application *self, CursesEvent *event)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
case USER_ACTION_INPUT_DELETE_PREVIOUS:
|
||||
if (self->input_pos > 0)
|
||||
{
|
||||
g_array_remove_index (self->input, --self->input_pos);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_DELETE_NEXT:
|
||||
if (self->input_pos < self->input->len)
|
||||
{
|
||||
g_array_remove_index (self->input, self->input_pos);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_DELETE_TO_HOME:
|
||||
if (self->input->len != 0)
|
||||
{
|
||||
g_array_remove_range (self->input, 0, self->input_pos);
|
||||
self->input_pos = 0;
|
||||
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_DELETE_TO_END:
|
||||
if (self->input_pos < self->input->len)
|
||||
{
|
||||
g_array_remove_range (self->input,
|
||||
self->input_pos, self->input->len - self->input_pos);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
return TRUE;
|
||||
case USER_ACTION_INPUT_DELETE_PREVIOUS_WORD:
|
||||
{
|
||||
if (self->input_pos == 0)
|
||||
return TRUE;
|
||||
|
||||
gint i = self->input_pos;
|
||||
while (i)
|
||||
if (g_array_index (self->input, gunichar, --i) != L' ')
|
||||
break;
|
||||
while (i--)
|
||||
if (g_array_index (self->input, gunichar, i) == L' ')
|
||||
break;
|
||||
|
||||
i++;
|
||||
g_array_remove_range (self->input, i, self->input_pos - i);
|
||||
self->input_pos = i;
|
||||
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
case USER_ACTION_NONE:
|
||||
return TRUE;
|
||||
default:
|
||||
return app_process_extra_code (self, event);
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/** Process input events from ncurses. */
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gboolean
|
||||
app_process_curses_event (Application *self, CursesEvent *event)
|
||||
app_process_keysym (Application *self, termo_key_t *event)
|
||||
{
|
||||
// Characters below the space are ASCII control codes
|
||||
if (!event->is_char || event->code < L' ')
|
||||
return app_process_nonchar_code (self, event);
|
||||
UserAction action = USER_ACTION_NONE;
|
||||
typedef const UserAction ActionMap[TERMO_N_SYMS];
|
||||
|
||||
wchar_t code = event->code;
|
||||
gchar *letter = g_convert_with_iconv ((gchar *) &code, sizeof code,
|
||||
self->wchar_to_utf8, NULL, NULL, NULL);
|
||||
g_return_val_if_fail (letter != NULL, FALSE);
|
||||
|
||||
gunichar c = g_utf8_get_char (letter);
|
||||
if (g_unichar_isprint (c))
|
||||
static ActionMap actions =
|
||||
{
|
||||
self->show_help = FALSE;
|
||||
[TERMO_SYM_ESCAPE] = USER_ACTION_QUIT,
|
||||
|
||||
if (self->input_confirmed)
|
||||
{
|
||||
if (self->input->len != 0)
|
||||
g_array_remove_range (self->input, 0, self->input->len);
|
||||
self->input_pos = 0;
|
||||
self->input_confirmed = FALSE;
|
||||
}
|
||||
[TERMO_SYM_UP] = USER_ACTION_GOTO_DEFINITION_PREVIOUS,
|
||||
[TERMO_SYM_DOWN] = USER_ACTION_GOTO_DEFINITION_NEXT,
|
||||
[TERMO_SYM_PAGEUP] = USER_ACTION_GOTO_PAGE_PREVIOUS,
|
||||
[TERMO_SYM_PAGEDOWN] = USER_ACTION_GOTO_PAGE_NEXT,
|
||||
|
||||
g_array_insert_val (self->input, self->input_pos++, c);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
}
|
||||
g_free (letter);
|
||||
[TERMO_SYM_ENTER] = USER_ACTION_INPUT_CONFIRM,
|
||||
|
||||
[TERMO_SYM_HOME] = USER_ACTION_INPUT_HOME,
|
||||
[TERMO_SYM_END] = USER_ACTION_INPUT_END,
|
||||
[TERMO_SYM_LEFT] = USER_ACTION_INPUT_LEFT,
|
||||
[TERMO_SYM_RIGHT] = USER_ACTION_INPUT_RIGHT,
|
||||
|
||||
[TERMO_SYM_BACKSPACE] = USER_ACTION_INPUT_DELETE_PREVIOUS,
|
||||
// XXX: what's the difference?
|
||||
[TERMO_SYM_DELETE] = USER_ACTION_INPUT_DELETE_NEXT,
|
||||
[TERMO_SYM_DEL] = USER_ACTION_INPUT_DELETE_NEXT,
|
||||
};
|
||||
static ActionMap actions_alt =
|
||||
{
|
||||
[TERMO_SYM_LEFT] = USER_ACTION_MOVE_SPLITTER_LEFT,
|
||||
[TERMO_SYM_RIGHT] = USER_ACTION_MOVE_SPLITTER_RIGHT,
|
||||
};
|
||||
static ActionMap actions_ctrl =
|
||||
{
|
||||
[TERMO_SYM_UP] = USER_ACTION_GOTO_ENTRY_PREVIOUS,
|
||||
[TERMO_SYM_DOWN] = USER_ACTION_GOTO_ENTRY_NEXT,
|
||||
};
|
||||
|
||||
if (!event->modifiers)
|
||||
action = actions[event->code.sym];
|
||||
else if (event->modifiers == TERMO_KEYMOD_ALT)
|
||||
action = actions_alt[event->code.sym];
|
||||
else if (event->modifiers == TERMO_KEYMOD_CTRL)
|
||||
action = actions_ctrl[event->code.sym];
|
||||
|
||||
return app_process_user_action (self, action);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
app_process_ctrl_key (Application *self, termo_key_t *event)
|
||||
{
|
||||
static const UserAction actions[32] =
|
||||
{
|
||||
[CTRL_KEY ('L')] = USER_ACTION_REDRAW,
|
||||
|
||||
[CTRL_KEY ('P')] = USER_ACTION_GOTO_DEFINITION_PREVIOUS,
|
||||
[CTRL_KEY ('N')] = USER_ACTION_GOTO_DEFINITION_NEXT,
|
||||
[CTRL_KEY ('B')] = USER_ACTION_GOTO_PAGE_PREVIOUS,
|
||||
[CTRL_KEY ('F')] = USER_ACTION_GOTO_PAGE_NEXT,
|
||||
|
||||
[CTRL_KEY ('A')] = USER_ACTION_INPUT_HOME,
|
||||
[CTRL_KEY ('E')] = USER_ACTION_INPUT_END,
|
||||
|
||||
[CTRL_KEY ('H')] = USER_ACTION_INPUT_DELETE_PREVIOUS,
|
||||
[CTRL_KEY ('K')] = USER_ACTION_INPUT_DELETE_TO_END,
|
||||
[CTRL_KEY ('W')] = USER_ACTION_INPUT_DELETE_PREVIOUS_WORD,
|
||||
[CTRL_KEY ('U')] = USER_ACTION_INPUT_DELETE_TO_HOME,
|
||||
[CTRL_KEY ('T')] = USER_ACTION_INPUT_TRANSPOSE,
|
||||
};
|
||||
|
||||
gint64 i = (gint64) event->code.codepoint - 'a' + 1;
|
||||
if (i > 0 && i < (gint64) G_N_ELEMENTS (actions))
|
||||
return app_process_user_action (self, actions[i]);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
app_process_key (Application *self, termo_key_t *event)
|
||||
{
|
||||
if (event->modifiers == TERMO_KEYMOD_CTRL)
|
||||
return app_process_ctrl_key (self, event);
|
||||
if (event->modifiers)
|
||||
return TRUE;
|
||||
|
||||
gunichar c = event->code.codepoint;
|
||||
if (!g_unichar_isprint (c))
|
||||
{
|
||||
beep ();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
self->show_help = FALSE;
|
||||
if (self->input_confirmed)
|
||||
{
|
||||
if (self->input->len != 0)
|
||||
g_array_remove_range (self->input, 0, self->input->len);
|
||||
self->input_pos = 0;
|
||||
self->input_confirmed = FALSE;
|
||||
}
|
||||
|
||||
g_array_insert_val (self->input, self->input_pos++, c);
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/** Process input events from the terminal. */
|
||||
static gboolean
|
||||
app_process_termo_event (Application *self, termo_key_t *event)
|
||||
{
|
||||
switch (event->type)
|
||||
{
|
||||
case TERMO_TYPE_MOUSE:
|
||||
return app_process_mouse (self, event);
|
||||
case TERMO_TYPE_KEY:
|
||||
return app_process_key (self, event);
|
||||
case TERMO_TYPE_KEYSYM:
|
||||
return app_process_keysym (self, event);
|
||||
default:
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// --- SIGWINCH ----------------------------------------------------------------
|
||||
|
||||
static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */
|
||||
static void (*g_old_winch_handler) (int);
|
||||
|
||||
static void
|
||||
winch_handler (int signum)
|
||||
{
|
||||
/* Call the ncurses handler. */
|
||||
if (g_old_winch_handler)
|
||||
g_old_winch_handler (signum);
|
||||
|
||||
/* And wake up the poll() call. */
|
||||
(void) signum;
|
||||
write (g_winch_pipe[1], "x", 1);
|
||||
}
|
||||
|
||||
@ -1091,43 +1126,73 @@ install_winch_handler (void)
|
||||
act.sa_flags = SA_RESTART;
|
||||
sigemptyset (&act.sa_mask);
|
||||
sigaction (SIGWINCH, &act, &oldact);
|
||||
|
||||
/* Save the ncurses handler. */
|
||||
if (oldact.sa_handler != SIG_DFL
|
||||
&& oldact.sa_handler != SIG_IGN)
|
||||
g_old_winch_handler = oldact.sa_handler;
|
||||
}
|
||||
|
||||
// --- Initialisation, event handling ------------------------------------------
|
||||
|
||||
Application g_application;
|
||||
static gboolean process_stdin_input_timeout (gpointer data);
|
||||
|
||||
static gboolean
|
||||
process_stdin_input (void)
|
||||
process_stdin_input (G_GNUC_UNUSED GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition, gpointer data)
|
||||
{
|
||||
CursesEvent event;
|
||||
int sta;
|
||||
|
||||
while ((sta = get_wch (&event.code)) != ERR)
|
||||
Application *app = data;
|
||||
if (app->tk_timeout)
|
||||
{
|
||||
event.is_char = (sta == OK);
|
||||
if (sta == KEY_CODE_YES && event.code == KEY_MOUSE
|
||||
&& getmouse (&event.mouse) == ERR)
|
||||
abort ();
|
||||
if (!app_process_curses_event (&g_application, &event))
|
||||
return FALSE;
|
||||
g_source_remove (app->tk_timeout);
|
||||
app->tk_timeout = 0;
|
||||
}
|
||||
|
||||
termo_advisereadable (app->tk);
|
||||
|
||||
termo_key_t event;
|
||||
termo_result_t res;
|
||||
while ((res = termo_getkey (app->tk, &event)) == TERMO_RES_KEY)
|
||||
if (!app_process_termo_event (app, &event))
|
||||
goto quit;
|
||||
|
||||
if (res == TERMO_RES_AGAIN)
|
||||
app->tk_timeout = g_timeout_add (termo_get_waittime (app->tk),
|
||||
process_stdin_input_timeout, app);
|
||||
else if (res == TERMO_RES_ERROR || res == TERMO_RES_EOF)
|
||||
goto quit;
|
||||
|
||||
return TRUE;
|
||||
|
||||
quit:
|
||||
g_main_loop_quit (app->loop);
|
||||
return false;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_winch_input (int fd)
|
||||
process_stdin_input_timeout (gpointer data)
|
||||
{
|
||||
char c;
|
||||
Application *app = data;
|
||||
termo_key_t event;
|
||||
if (termo_getkey_force (app->tk, &event) == TERMO_RES_KEY)
|
||||
if (!app_process_termo_event (app, &event))
|
||||
g_main_loop_quit (app->loop);
|
||||
|
||||
read (fd, &c, 1);
|
||||
return process_stdin_input ();
|
||||
app->tk_timeout = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_winch_input (GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition, gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
|
||||
char c;
|
||||
read (g_io_channel_unix_get_fd (source), &c, 1);
|
||||
|
||||
// TODO: look for resizeterm() and use it if available for flicker-free
|
||||
// resize; endwin() escapes curses mode.
|
||||
endwin ();
|
||||
refresh ();
|
||||
|
||||
app_process_resize (app);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
@ -1182,49 +1247,34 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
|
||||
g_option_context_free (ctx);
|
||||
|
||||
app_init (&g_application, argv[1]);
|
||||
Application app;
|
||||
app_init (&app, argv[1]);
|
||||
|
||||
TERMO_CHECK_VERSION;
|
||||
if (!(app.tk = termo_new (STDIN_FILENO, NULL, 0)))
|
||||
abort ();
|
||||
|
||||
if (!initscr ()
|
||||
|| cbreak () == ERR
|
||||
|| noecho () == ERR
|
||||
|| nonl () == ERR)
|
||||
abort ();
|
||||
|
||||
keypad (stdscr, TRUE); /* Enable character processing. */
|
||||
nodelay (stdscr, TRUE); /* Don't block on get_wch(). */
|
||||
|
||||
mousemask (ALL_MOUSE_EVENTS, NULL); /* Register mouse events. */
|
||||
mouseinterval (0);
|
||||
|
||||
// TODO: catch SIGINT as well
|
||||
if (pipe (g_winch_pipe) == -1)
|
||||
abort ();
|
||||
install_winch_handler ();
|
||||
|
||||
app_redraw (&g_application);
|
||||
app_redraw (&app);
|
||||
|
||||
/* Message loop. */
|
||||
struct pollfd pollfd[2];
|
||||
|
||||
pollfd[0].fd = fileno (stdin);
|
||||
pollfd[0].events = POLLIN;
|
||||
pollfd[1].fd = g_winch_pipe[0];
|
||||
pollfd[1].events = POLLIN;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
if (poll_restart (pollfd, 2, -1) == -1)
|
||||
abort ();
|
||||
|
||||
if ((pollfd[0].revents & POLLIN)
|
||||
&& !process_stdin_input ())
|
||||
break;
|
||||
if ((pollfd[1].revents & POLLIN)
|
||||
&& !process_winch_input (pollfd[1].fd))
|
||||
break;
|
||||
}
|
||||
g_io_add_watch (g_io_channel_unix_new (STDIN_FILENO),
|
||||
G_IO_IN, process_stdin_input, &app);
|
||||
g_io_add_watch (g_io_channel_unix_new (g_winch_pipe[0]),
|
||||
G_IO_IN, process_winch_input, &app);
|
||||
g_main_loop_run (app.loop);
|
||||
|
||||
endwin ();
|
||||
app_destroy (&g_application);
|
||||
app_destroy (&app);
|
||||
|
||||
if (close (g_winch_pipe[0]) == -1
|
||||
|| close (g_winch_pipe[1]) == -1)
|
||||
|
1
termo
Submodule
1
termo
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 828f03a063ef5e1e9bb113614083c3f4e59d5317
|
Loading…
Reference in New Issue
Block a user