diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-07-15 04:36:08 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-07-15 05:32:53 +0200 |
commit | 8baae7579973b755b47250742d9a1a94795ae1bb (patch) | |
tree | 91f156d0d7c94a17f0b1e708801b3b5ebbe6edec /src/interface-curses.cpp | |
parent | ea9989266dcb64025ed90d0113ed7c052d07cc34 (diff) | |
download | sciteco-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.cpp | 329 |
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) |