aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-curses.cpp
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/interface-curses.cpp
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/interface-curses.cpp')
-rw-r--r--src/interface-curses.cpp329
1 files changed, 209 insertions, 120 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)