aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2015-07-15 04:36:08 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2015-07-15 05:32:53 +0200
commit8baae7579973b755b47250742d9a1a94795ae1bb (patch)
tree91f156d0d7c94a17f0b1e708801b3b5ebbe6edec /src
parentea9989266dcb64025ed90d0113ed7c052d07cc34 (diff)
downloadsciteco-8baae7579973b755b47250742d9a1a94795ae1bb.tar.gz
Curses UI: revised popup area, with borders and a scroll bar; reduce flickering
* InterfaceCurses::Popup has been turned into a proper class. This made sense since it is more complicated now and allows us to isolate popup-related code. This will also ease moving the popup code as a widget into its own file later (it seems we will need subdirs per interface anyway). * the popup is now implemented using curses pads of which pages are copied into the popup window (to implement cycling through the list of entries). This simplifies things conceptually. * instead of a trailing ellipsis, scrollbars are shown if the popup area is too small to show all entries. This looks much better and consistent with regard to Scinterm's scrollbars. Also, the planned GTK+ popup widget rewrite will have scroll bars, too for cycling through the list of entries. Therefore, the popup window will now always be the same size when cycling. This also looks better. * Borders are drawn around the popup area. This makes sense since the popup area had to be colored distinctly just to be able to discern it from the rest of the UI (esp. the Scintilla view). Now, less annoying colors may be used by default or set up in color profiles while still maintaining good visibility. Also, with the borders added, the popup area looks more consistent when it covers the entire screen. * Entries that are too long to fit on the screen (e.g. long file names) are now truncated with a bold/underline ellipsis. * Use scintilla_noutrefresh() to refresh the Scintilla view. Since popups have to be refreshed __after__ the Scintilla view, this improves performance significantly and reduces flickering when displaying large popups.
Diffstat (limited to 'src')
-rw-r--r--src/interface-curses.cpp329
-rw-r--r--src/interface-curses.h59
2 files changed, 258 insertions, 130 deletions
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
index c3a5b33..b4d83ec 100644
--- a/src/interface-curses.cpp
+++ b/src/interface-curses.cpp
@@ -256,6 +256,193 @@ InterfaceCurses::InterfaceCurses() : stdout_orig(-1), stderr_orig(-1),
}
void
+InterfaceCurses::Popup::add(PopupEntryType type,
+ const gchar *name, bool highlight)
+{
+ gchar *entry = g_strconcat(highlight ? "*" : " ", name, NIL);
+
+ longest = MAX(longest, (gint)strlen(name));
+ length++;
+
+ /*
+ * Entries are added in reverse (constant time for GSList),
+ * so they will later have to be reversed.
+ */
+ list = g_slist_prepend(list, entry);
+}
+
+void
+InterfaceCurses::Popup::init_pad(attr_t attr)
+{
+ int cols = getmaxx(stdscr); /* screen width */
+ int pad_lines; /* pad height */
+ gint pad_cols; /* entry columns */
+ gint pad_colwidth; /* width per entry column */
+
+ gint cur_col;
+
+ /* reserve 2 spaces between columns */
+ pad_colwidth = MIN(longest + 2, cols - 2);
+
+ /* pad_cols = floor((cols - 2) / pad_colwidth) */
+ pad_cols = (cols - 2) / pad_colwidth;
+ /* pad_lines = ceil(length / pad_cols) */
+ pad_lines = (length+pad_cols-1) / pad_cols;
+
+ /*
+ * Render the entire autocompletion list into a pad
+ * which can be higher than the physical screen.
+ * The pad uses two columns less than the screen since
+ * it will be drawn into the popup window which has left
+ * and right borders.
+ */
+ pad = newpad(pad_lines, cols - 2);
+
+ wbkgd(pad, ' ' | attr);
+
+ /*
+ * cur_col is the row currently written.
+ * It does not wrap but grows indefinitely.
+ * Therefore the real current row is (cur_col % popup_cols)
+ */
+ cur_col = 0;
+ for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
+ gchar *entry = (gchar *)cur->data;
+ gint cur_line = cur_col/pad_cols + 1;
+
+ wmove(pad, cur_line-1,
+ (cur_col % pad_cols)*pad_colwidth);
+
+ wattrset(pad, *entry == '*' ? A_BOLD : A_NORMAL);
+
+ if ((int)strlen(entry + 1) > cols - 2) {
+ /* truncate entry */
+ waddnstr(pad, entry + 1, cols - 2 - 3);
+ wattron(pad, A_BOLD);
+ /*
+ * A_UNDERLINE is not supported by PDCurses/win32
+ * and causes weird colors, so we better leave it away.
+ */
+#ifndef PDCURSES_WIN32
+ wattron(pad, A_UNDERLINE);
+#endif
+ waddstr(pad, "...");
+ } else {
+ waddstr(pad, entry + 1);
+ }
+
+ cur_col++;
+ }
+}
+
+void
+InterfaceCurses::Popup::show(attr_t attr)
+{
+ int lines, cols; /* screen dimensions */
+ gint pad_lines;
+ gint popup_lines;
+ gint bar_height, bar_y;
+
+ if (!length)
+ /* nothing to display */
+ return;
+
+ getmaxyx(stdscr, lines, cols);
+
+ if (window)
+ delwin(window);
+ else
+ /* reverse list only once */
+ list = g_slist_reverse(list);
+
+ if (!pad)
+ init_pad(attr);
+ pad_lines = getmaxy(pad);
+
+ /*
+ * Popup window can cover all but one screen row.
+ * Another row is reserved for the top border.
+ */
+ popup_lines = MIN(pad_lines + 1, lines - 1);
+
+ /* window covers message, scintilla and info windows */
+ window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
+
+ wbkgdset(window, ' ' | attr);
+
+ wborder(window,
+ ACS_VLINE,
+ ACS_VLINE, /* may be overwritten with scrollbar */
+ ACS_HLINE,
+ ' ', /* no bottom line */
+ ACS_ULCORNER, ACS_URCORNER,
+ ACS_VLINE, ACS_VLINE);
+
+ copywin(pad, window,
+ pad_first_line, 0,
+ 1, 1, popup_lines - 1, cols - 2, FALSE);
+
+ if (pad_lines <= popup_lines - 1)
+ /* no need for scrollbar */
+ return;
+
+ /* bar_height = ceil((popup_lines-1)/pad_lines * (popup_lines-2)) */
+ bar_height = ((popup_lines-1)*(popup_lines-2) + pad_lines-1) /
+ pad_lines;
+ /* bar_y = floor(pad_first_line/pad_lines * (popup_lines-2)) + 1 */
+ bar_y = pad_first_line*(popup_lines-2) / pad_lines + 1;
+
+ mvwvline(window, 1, cols-1, ACS_CKBOARD, popup_lines-2);
+ /*
+ * We do not use ACS_BLOCK here since it will not
+ * always be drawn as a solid block (e.g. xterm).
+ * Instead, simply draw reverse blanks.
+ */
+ wmove(window, bar_y, cols-1);
+ wattron(window, A_REVERSE);
+ wvline(window, ' ', bar_height);
+
+ /* progress scroll position */
+ pad_first_line += popup_lines - 1;
+ /* wrap on last shown page */
+ pad_first_line %= pad_lines;
+ if (pad_lines - pad_first_line < popup_lines - 1)
+ /* show last page */
+ pad_first_line = pad_lines - (popup_lines - 1);
+}
+
+void
+InterfaceCurses::Popup::clear(void)
+{
+ g_slist_free_full(list, g_free);
+ list = NULL;
+ length = 0;
+ longest = 0;
+
+ pad_first_line = 0;
+
+ if (window) {
+ delwin(window);
+ window = NULL;
+ }
+
+ if (pad) {
+ delwin(pad);
+ pad = NULL;
+ }
+}
+
+InterfaceCurses::Popup::~Popup()
+{
+ if (window)
+ delwin(window);
+ if (pad)
+ delwin(pad);
+ if (list)
+ g_slist_free_full(list, g_free);
+}
+
+void
InterfaceCurses::main_impl(int &argc, char **&argv)
{
/*
@@ -981,127 +1168,40 @@ InterfaceCurses::draw_cmdline(void)
}
void
-InterfaceCurses::popup_add_impl(PopupEntryType type,
- const gchar *name, bool highlight)
-{
- gchar *entry;
-
- if (!cmdline_window) /* batch mode */
- return;
-
- entry = g_strconcat(highlight ? "*" : " ", name, NIL);
-
- popup.longest = MAX(popup.longest, (gint)strlen(name));
- popup.length++;
-
- popup.list = g_slist_prepend(popup.list, entry);
-}
-
-void
InterfaceCurses::popup_show_impl(void)
{
- int lines, cols; /* screen dimensions */
- int popup_lines;
- gint popup_cols;
- gint popup_colwidth;
- gint cur_col;
-
short fg, bg;
- if (!cmdline_window || !popup.length)
- /* batch mode or nothing to display */
+ if (!cmdline_window)
+ /* batch mode */
return;
- getmaxyx(stdscr, lines, cols);
-
- if (popup.window)
- delwin(popup.window);
- else
- /* reverse list only once */
- popup.list = g_slist_reverse(popup.list);
-
- if (!popup.cur_list) {
- /* start from beginning of list */
- popup.cur_list = popup.list;
- popup.cur_entry = 0;
- }
-
- /* reserve 2 spaces between columns */
- popup_colwidth = popup.longest + 2;
-
- /* popup_cols = floor(cols / popup_colwidth) */
- popup_cols = MAX(cols / popup_colwidth, 1);
- /* popup_lines = ceil((popup.length-popup.cur_entry) / popup_cols) */
- popup_lines = (popup.length-popup.cur_entry+popup_cols-1) / popup_cols;
- /*
- * Popup window can cover all but one screen row.
- * If it does not fit, the list of tokens is truncated
- * and "..." is displayed.
- */
- popup_lines = MIN(popup_lines, lines - 1);
-
- /* window covers message, scintilla and info windows */
- popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
-
fg = rgb2curses(ssm(SCI_STYLEGETFORE, STYLE_CALLTIP));
bg = rgb2curses(ssm(SCI_STYLEGETBACK, STYLE_CALLTIP));
- wbkgd(popup.window, ' ' | SCI_COLOR_ATTR(fg, bg));
-
- /*
- * cur_col is the row currently written.
- * It does not wrap but grows indefinitely.
- * Therefore the real current row is (cur_col % popup_cols)
- */
- cur_col = 0;
- while (popup.cur_list) {
- gchar *entry = (gchar *)popup.cur_list->data;
- gint cur_line = cur_col/popup_cols + 1;
-
- wmove(popup.window,
- cur_line-1, (cur_col % popup_cols)*popup_colwidth);
- cur_col++;
-
- if (cur_line == popup_lines && !(cur_col % popup_cols) &&
- g_slist_next(popup.cur_list) != NULL) {
- /* truncate entries in the popup's very last column */
- (void)wattrset(popup.window, A_BOLD);
- waddstr(popup.window, "...");
- break;
- }
-
- (void)wattrset(popup.window, *entry == '*' ? A_BOLD : A_NORMAL);
- waddstr(popup.window, entry + 1);
- popup.cur_list = g_slist_next(popup.cur_list);
- popup.cur_entry++;
- }
-
- redrawwin(info_window);
- /* scintilla window is redrawn by ViewCurses::refresh() */
- redrawwin(msg_window);
+ popup.show(SCI_COLOR_ATTR(fg, bg));
}
void
InterfaceCurses::popup_clear_impl(void)
{
- g_slist_free_full(popup.list, g_free);
- popup.list = NULL;
- popup.length = 0;
- /* reserve at least 3 characters for "..." */
- popup.longest = 3;
-
- popup.cur_list = NULL;
- popup.cur_entry = 0;
-
- if (!popup.window)
- return;
-
- redrawwin(info_window);
- /* scintilla window is redrawn by ViewCurses::refresh() */
- redrawwin(msg_window);
+#ifdef __PDCURSES__
+ /*
+ * PDCurses will not redraw all windows that may be
+ * overlapped by the popup window correctly - at least
+ * not the info window.
+ * The Scintilla window is apparently always touched by
+ * scintilla_noutrefresh().
+ * Actually we would expect this to be necessary on any curses,
+ * but ncurses doesn't require this.
+ */
+ if (popup.is_shown()) {
+ touchwin(info_window);
+ touchwin(msg_window);
+ }
+#endif
- delwin(popup.window);
- popup.window = NULL;
+ popup.clear();
}
/**
@@ -1232,12 +1332,10 @@ event_loop_iter()
*/
interface.draw_info();
wnoutrefresh(interface.info_window);
- /* FIXME: this does wrefresh() internally */
- interface.current_view->refresh();
+ interface.current_view->noutrefresh();
wnoutrefresh(interface.msg_window);
wnoutrefresh(interface.cmdline_window);
- if (interface.popup.window)
- wnoutrefresh(interface.popup.window);
+ interface.popup.noutrefresh();
doupdate();
}
@@ -1252,10 +1350,9 @@ InterfaceCurses::event_loop_impl(void)
init_interactive();
/* initial refresh */
- /* FIXME: this does wrefresh() internally */
- current_view->refresh();
draw_info();
wnoutrefresh(info_window);
+ current_view->noutrefresh();
msg_clear();
wnoutrefresh(msg_window);
cmdline_update(&empty_cmdline);
@@ -1287,14 +1384,6 @@ InterfaceCurses::event_loop_impl(void)
#endif
}
-InterfaceCurses::Popup::~Popup()
-{
- if (window)
- delwin(window);
- if (list)
- g_slist_free_full(list, g_free);
-}
-
InterfaceCurses::~InterfaceCurses()
{
if (info_window)
diff --git a/src/interface-curses.h b/src/interface-curses.h
index b43069f..9bb17cd 100644
--- a/src/interface-curses.h
+++ b/src/interface-curses.h
@@ -52,6 +52,12 @@ public:
}
inline void
+ noutrefresh(void)
+ {
+ scintilla_noutrefresh(sci);
+ }
+
+ inline void
refresh(void)
{
scintilla_refresh(sci);
@@ -104,19 +110,44 @@ typedef class InterfaceCurses : public Interface<InterfaceCurses, ViewCurses> {
WINDOW *cmdline_window, *cmdline_pad;
gsize cmdline_len, cmdline_rubout_len;
- struct Popup {
- WINDOW *window;
+ class Popup {
+ WINDOW *window; /**! window showing part of pad */
+ WINDOW *pad; /**! full-height entry list */
+
GSList *list; /**! list of popup entries */
gint longest; /**! size of longest entry */
gint length; /**! total number of popup entries */
- GSList *cur_list; /**! next entry to display */
- gint cur_entry; /**! next entry to display (position) */
+ gint pad_first_line; /**! first line in pad to show */
+
+ public:
+ Popup() : window(NULL), pad(NULL),
+ list(NULL), longest(0), length(0),
+ pad_first_line(0) {}
+
+ void add(PopupEntryType type,
+ const gchar *name, bool highlight = false);
+
+ void show(attr_t attr);
+ inline bool
+ is_shown(void)
+ {
+ return window != NULL;
+ }
+
+ void clear(void);
+
+ inline void
+ noutrefresh(void)
+ {
+ if (window)
+ wnoutrefresh(window);
+ }
- Popup() : window(NULL), list(NULL),
- longest(3), length(0),
- cur_list(NULL), cur_entry(0) {}
~Popup();
+
+ private:
+ void init_pad(attr_t attr);
} popup;
public:
@@ -145,16 +176,24 @@ public:
void cmdline_update_impl(const Cmdline *cmdline);
/* implementation of Interface::popup_add() */
- void popup_add_impl(PopupEntryType type,
- const gchar *name, bool highlight = false);
+ inline void
+ popup_add_impl(PopupEntryType type,
+ const gchar *name, bool highlight = false)
+ {
+ if (cmdline_window)
+ /* interactive mode */
+ popup.add(type, name, highlight);
+ }
+
/* implementation of Interface::popup_show() */
void popup_show_impl(void);
/* implementation of Interface::popup_is_shown() */
inline bool
popup_is_shown_impl(void)
{
- return popup.window != NULL;
+ return popup.is_shown();
}
+
/* implementation of Interface::popup_clear() */
void popup_clear_impl(void);