2014-12-09 14:57:21 +01:00
|
|
|
|
#include "ncurses_ui.hh"
|
2012-02-16 15:25:16 +01:00
|
|
|
|
|
2012-10-20 20:15:20 +02:00
|
|
|
|
#include "display_buffer.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
|
#include "event_manager.hh"
|
2014-11-12 22:27:07 +01:00
|
|
|
|
#include "keys.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
|
#include "ranges.hh"
|
2017-10-09 16:12:42 +02:00
|
|
|
|
#include "string_utils.hh"
|
2012-10-08 14:27:43 +02:00
|
|
|
|
|
2014-12-23 14:34:21 +01:00
|
|
|
|
#include <algorithm>
|
2012-02-16 15:25:16 +01:00
|
|
|
|
|
2013-06-18 21:49:56 +02:00
|
|
|
|
#define NCURSES_OPAQUE 0
|
|
|
|
|
#define NCURSES_INTERNALS
|
|
|
|
|
|
2014-04-03 02:54:49 +02:00
|
|
|
|
#include <ncurses.h>
|
|
|
|
|
|
2014-10-13 14:12:33 +02:00
|
|
|
|
#include <fcntl.h>
|
2017-01-08 23:30:15 +01:00
|
|
|
|
#include <csignal>
|
2012-10-27 14:18:52 +02:00
|
|
|
|
#include <sys/ioctl.h>
|
2014-10-13 14:12:33 +02:00
|
|
|
|
#include <termios.h>
|
|
|
|
|
#include <unistd.h>
|
2012-02-16 15:25:16 +01:00
|
|
|
|
|
2015-09-25 00:36:29 +02:00
|
|
|
|
constexpr char control(char c) { return c & 037; }
|
|
|
|
|
|
2012-02-16 15:25:16 +01:00
|
|
|
|
namespace Kakoune
|
|
|
|
|
{
|
2012-06-06 01:15:19 +02:00
|
|
|
|
|
2013-10-17 19:48:12 +02:00
|
|
|
|
using std::min;
|
|
|
|
|
using std::max;
|
|
|
|
|
|
2013-04-12 01:28:22 +02:00
|
|
|
|
struct NCursesWin : WINDOW {};
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
void NCursesUI::Window::create(const DisplayCoord& p, const DisplayCoord& s)
|
|
|
|
|
{
|
|
|
|
|
pos = p;
|
|
|
|
|
size = s;
|
|
|
|
|
win = (NCursesWin*)newpad((int)size.line, (int)size.column);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::destroy()
|
|
|
|
|
{
|
|
|
|
|
delwin(win);
|
|
|
|
|
invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::invalidate()
|
|
|
|
|
{
|
|
|
|
|
win = nullptr;
|
|
|
|
|
pos = DisplayCoord{};
|
|
|
|
|
size = DisplayCoord{};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::refresh(bool force)
|
|
|
|
|
{
|
|
|
|
|
if (not win)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (force)
|
|
|
|
|
redrawwin(win);
|
|
|
|
|
|
|
|
|
|
DisplayCoord max_pos = pos + size - DisplayCoord{1,1};
|
|
|
|
|
pnoutrefresh(win, 0, 0, (int)pos.line, (int)pos.column,
|
|
|
|
|
(int)max_pos.line, (int)max_pos.column);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::move_cursor(DisplayCoord coord)
|
|
|
|
|
{
|
|
|
|
|
wmove(win, (int)coord.line, (int)coord.column);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::add_str(StringView str)
|
|
|
|
|
{
|
|
|
|
|
waddnstr(win, str.begin(), (int)str.length());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::clear_to_end_of_line()
|
|
|
|
|
{
|
|
|
|
|
wclrtoeol(win);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::draw_line(Palette& palette,
|
|
|
|
|
const DisplayLine& line,
|
|
|
|
|
ColumnCount col_index,
|
|
|
|
|
ColumnCount max_column,
|
|
|
|
|
const Face& default_face)
|
|
|
|
|
{
|
|
|
|
|
for (const DisplayAtom& atom : line)
|
|
|
|
|
{
|
|
|
|
|
set_face(palette, atom.face, default_face);
|
|
|
|
|
|
|
|
|
|
StringView content = atom.content();
|
|
|
|
|
if (content.empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const auto remaining_columns = max_column - col_index;
|
|
|
|
|
if (content.back() == '\n' and
|
|
|
|
|
content.column_length() - 1 < remaining_columns)
|
|
|
|
|
{
|
|
|
|
|
add_str(content.substr(0, content.length()-1));
|
|
|
|
|
waddch(win, ' ');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
content = content.substr(0_col, remaining_columns);
|
|
|
|
|
add_str(content);
|
|
|
|
|
col_index += content.column_length();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::set_face(Palette& palette, Face face, const Face& default_face)
|
|
|
|
|
{
|
|
|
|
|
if (m_active_pair != -1)
|
|
|
|
|
wattroff(win, COLOR_PAIR(m_active_pair));
|
|
|
|
|
|
|
|
|
|
face = merge_faces(default_face, face);
|
|
|
|
|
|
|
|
|
|
if (face.fg != Color::Default or face.bg != Color::Default)
|
|
|
|
|
{
|
|
|
|
|
m_active_pair = palette.get_color_pair(face);
|
|
|
|
|
wattron(win, COLOR_PAIR(m_active_pair));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto set_attribute = [&](Attribute attr, int nc_attr) {
|
|
|
|
|
(face.attributes & attr) ? wattron(win, nc_attr) : wattroff(win, nc_attr);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
set_attribute(Attribute::Underline, A_UNDERLINE);
|
|
|
|
|
set_attribute(Attribute::Reverse, A_REVERSE);
|
|
|
|
|
set_attribute(Attribute::Blink, A_BLINK);
|
|
|
|
|
set_attribute(Attribute::Bold, A_BOLD);
|
|
|
|
|
set_attribute(Attribute::Dim, A_DIM);
|
|
|
|
|
#if defined(A_ITALIC)
|
|
|
|
|
set_attribute(Attribute::Italic, A_ITALIC);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::mark_dirty(LineCount pos, LineCount count)
|
|
|
|
|
{
|
|
|
|
|
wredrawln(win, (int)pos, (int)count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::set_background_color(Palette& palette, Face face)
|
|
|
|
|
{
|
|
|
|
|
wbkgdset(win, COLOR_PAIR(palette.get_color_pair(face)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int NCursesUI::Window::get_char()
|
|
|
|
|
{
|
|
|
|
|
return wgetch(win);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::Window::set_blocking(bool blocking)
|
|
|
|
|
{
|
|
|
|
|
wtimeout(win, blocking ? -1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-11 12:29:35 +02:00
|
|
|
|
constexpr int NCursesUI::default_shift_function_key;
|
|
|
|
|
|
2015-02-11 00:09:30 +01:00
|
|
|
|
static constexpr StringView assistant_cat[] =
|
2015-03-25 14:53:13 +01:00
|
|
|
|
{ R"( ___ )",
|
|
|
|
|
R"( (__ \ )",
|
|
|
|
|
R"( / / ╭)",
|
|
|
|
|
R"( .' '·. │)",
|
|
|
|
|
R"( ' ” │)",
|
|
|
|
|
R"( ╰ /\_/| │)",
|
|
|
|
|
R"( | . \ │)",
|
|
|
|
|
R"( ╰_J` | | | ╯)",
|
|
|
|
|
R"( ' \__- _/ )",
|
|
|
|
|
R"( \_\ \_\ )",
|
2015-02-09 14:33:54 +01:00
|
|
|
|
R"( )"};
|
|
|
|
|
|
2015-02-11 00:09:30 +01:00
|
|
|
|
static constexpr StringView assistant_clippy[] =
|
2015-02-05 23:40:08 +01:00
|
|
|
|
{ " ╭──╮ ",
|
|
|
|
|
" │ │ ",
|
|
|
|
|
" @ @ ╭",
|
|
|
|
|
" ││ ││ │",
|
|
|
|
|
" ││ ││ ╯",
|
|
|
|
|
" │╰─╯│ ",
|
|
|
|
|
" ╰───╯ ",
|
|
|
|
|
" " };
|
|
|
|
|
|
2017-03-08 17:45:15 +01:00
|
|
|
|
static constexpr StringView assistant_dilbert[] =
|
|
|
|
|
{ R"( დოოოოოდ )",
|
|
|
|
|
R"( | | )",
|
|
|
|
|
R"( | | ╭)",
|
|
|
|
|
R"( |-ᱛ ᱛ-| │)",
|
|
|
|
|
R"( Ͼ ∪ Ͽ │)",
|
|
|
|
|
R"( | | ╯)",
|
|
|
|
|
R"( ˏ`-.ŏ.-´ˎ )",
|
|
|
|
|
R"( @ )",
|
|
|
|
|
R"( @ )",
|
|
|
|
|
R"( )"};
|
|
|
|
|
|
2013-10-17 19:48:12 +02:00
|
|
|
|
template<typename T> T sq(T x) { return x * x; }
|
|
|
|
|
|
2015-09-09 14:40:52 +02:00
|
|
|
|
constexpr struct { unsigned char r, g, b; } builtin_colors[] = {
|
|
|
|
|
{0x00,0x00,0x00}, {0x80,0x00,0x00}, {0x00,0x80,0x00}, {0x80,0x80,0x00},
|
|
|
|
|
{0x00,0x00,0x80}, {0x80,0x00,0x80}, {0x00,0x80,0x80}, {0xc0,0xc0,0xc0},
|
|
|
|
|
{0x80,0x80,0x80}, {0xff,0x00,0x00}, {0x00,0xff,0x00}, {0xff,0xff,0x00},
|
|
|
|
|
{0x00,0x00,0xff}, {0xff,0x00,0xff}, {0x00,0xff,0xff}, {0xff,0xff,0xff},
|
|
|
|
|
{0x00,0x00,0x00}, {0x00,0x00,0x5f}, {0x00,0x00,0x87}, {0x00,0x00,0xaf},
|
|
|
|
|
{0x00,0x00,0xd7}, {0x00,0x00,0xff}, {0x00,0x5f,0x00}, {0x00,0x5f,0x5f},
|
|
|
|
|
{0x00,0x5f,0x87}, {0x00,0x5f,0xaf}, {0x00,0x5f,0xd7}, {0x00,0x5f,0xff},
|
|
|
|
|
{0x00,0x87,0x00}, {0x00,0x87,0x5f}, {0x00,0x87,0x87}, {0x00,0x87,0xaf},
|
|
|
|
|
{0x00,0x87,0xd7}, {0x00,0x87,0xff}, {0x00,0xaf,0x00}, {0x00,0xaf,0x5f},
|
|
|
|
|
{0x00,0xaf,0x87}, {0x00,0xaf,0xaf}, {0x00,0xaf,0xd7}, {0x00,0xaf,0xff},
|
|
|
|
|
{0x00,0xd7,0x00}, {0x00,0xd7,0x5f}, {0x00,0xd7,0x87}, {0x00,0xd7,0xaf},
|
|
|
|
|
{0x00,0xd7,0xd7}, {0x00,0xd7,0xff}, {0x00,0xff,0x00}, {0x00,0xff,0x5f},
|
|
|
|
|
{0x00,0xff,0x87}, {0x00,0xff,0xaf}, {0x00,0xff,0xd7}, {0x00,0xff,0xff},
|
|
|
|
|
{0x5f,0x00,0x00}, {0x5f,0x00,0x5f}, {0x5f,0x00,0x87}, {0x5f,0x00,0xaf},
|
|
|
|
|
{0x5f,0x00,0xd7}, {0x5f,0x00,0xff}, {0x5f,0x5f,0x00}, {0x5f,0x5f,0x5f},
|
|
|
|
|
{0x5f,0x5f,0x87}, {0x5f,0x5f,0xaf}, {0x5f,0x5f,0xd7}, {0x5f,0x5f,0xff},
|
|
|
|
|
{0x5f,0x87,0x00}, {0x5f,0x87,0x5f}, {0x5f,0x87,0x87}, {0x5f,0x87,0xaf},
|
|
|
|
|
{0x5f,0x87,0xd7}, {0x5f,0x87,0xff}, {0x5f,0xaf,0x00}, {0x5f,0xaf,0x5f},
|
|
|
|
|
{0x5f,0xaf,0x87}, {0x5f,0xaf,0xaf}, {0x5f,0xaf,0xd7}, {0x5f,0xaf,0xff},
|
|
|
|
|
{0x5f,0xd7,0x00}, {0x5f,0xd7,0x5f}, {0x5f,0xd7,0x87}, {0x5f,0xd7,0xaf},
|
|
|
|
|
{0x5f,0xd7,0xd7}, {0x5f,0xd7,0xff}, {0x5f,0xff,0x00}, {0x5f,0xff,0x5f},
|
|
|
|
|
{0x5f,0xff,0x87}, {0x5f,0xff,0xaf}, {0x5f,0xff,0xd7}, {0x5f,0xff,0xff},
|
|
|
|
|
{0x87,0x00,0x00}, {0x87,0x00,0x5f}, {0x87,0x00,0x87}, {0x87,0x00,0xaf},
|
|
|
|
|
{0x87,0x00,0xd7}, {0x87,0x00,0xff}, {0x87,0x5f,0x00}, {0x87,0x5f,0x5f},
|
|
|
|
|
{0x87,0x5f,0x87}, {0x87,0x5f,0xaf}, {0x87,0x5f,0xd7}, {0x87,0x5f,0xff},
|
|
|
|
|
{0x87,0x87,0x00}, {0x87,0x87,0x5f}, {0x87,0x87,0x87}, {0x87,0x87,0xaf},
|
|
|
|
|
{0x87,0x87,0xd7}, {0x87,0x87,0xff}, {0x87,0xaf,0x00}, {0x87,0xaf,0x5f},
|
|
|
|
|
{0x87,0xaf,0x87}, {0x87,0xaf,0xaf}, {0x87,0xaf,0xd7}, {0x87,0xaf,0xff},
|
|
|
|
|
{0x87,0xd7,0x00}, {0x87,0xd7,0x5f}, {0x87,0xd7,0x87}, {0x87,0xd7,0xaf},
|
|
|
|
|
{0x87,0xd7,0xd7}, {0x87,0xd7,0xff}, {0x87,0xff,0x00}, {0x87,0xff,0x5f},
|
|
|
|
|
{0x87,0xff,0x87}, {0x87,0xff,0xaf}, {0x87,0xff,0xd7}, {0x87,0xff,0xff},
|
|
|
|
|
{0xaf,0x00,0x00}, {0xaf,0x00,0x5f}, {0xaf,0x00,0x87}, {0xaf,0x00,0xaf},
|
|
|
|
|
{0xaf,0x00,0xd7}, {0xaf,0x00,0xff}, {0xaf,0x5f,0x00}, {0xaf,0x5f,0x5f},
|
|
|
|
|
{0xaf,0x5f,0x87}, {0xaf,0x5f,0xaf}, {0xaf,0x5f,0xd7}, {0xaf,0x5f,0xff},
|
|
|
|
|
{0xaf,0x87,0x00}, {0xaf,0x87,0x5f}, {0xaf,0x87,0x87}, {0xaf,0x87,0xaf},
|
|
|
|
|
{0xaf,0x87,0xd7}, {0xaf,0x87,0xff}, {0xaf,0xaf,0x00}, {0xaf,0xaf,0x5f},
|
|
|
|
|
{0xaf,0xaf,0x87}, {0xaf,0xaf,0xaf}, {0xaf,0xaf,0xd7}, {0xaf,0xaf,0xff},
|
|
|
|
|
{0xaf,0xd7,0x00}, {0xaf,0xd7,0x5f}, {0xaf,0xd7,0x87}, {0xaf,0xd7,0xaf},
|
|
|
|
|
{0xaf,0xd7,0xd7}, {0xaf,0xd7,0xff}, {0xaf,0xff,0x00}, {0xaf,0xff,0x5f},
|
|
|
|
|
{0xaf,0xff,0x87}, {0xaf,0xff,0xaf}, {0xaf,0xff,0xd7}, {0xaf,0xff,0xff},
|
|
|
|
|
{0xd7,0x00,0x00}, {0xd7,0x00,0x5f}, {0xd7,0x00,0x87}, {0xd7,0x00,0xaf},
|
|
|
|
|
{0xd7,0x00,0xd7}, {0xd7,0x00,0xff}, {0xd7,0x5f,0x00}, {0xd7,0x5f,0x5f},
|
|
|
|
|
{0xd7,0x5f,0x87}, {0xd7,0x5f,0xaf}, {0xd7,0x5f,0xd7}, {0xd7,0x5f,0xff},
|
|
|
|
|
{0xd7,0x87,0x00}, {0xd7,0x87,0x5f}, {0xd7,0x87,0x87}, {0xd7,0x87,0xaf},
|
|
|
|
|
{0xd7,0x87,0xd7}, {0xd7,0x87,0xff}, {0xd7,0xaf,0x00}, {0xd7,0xaf,0x5f},
|
|
|
|
|
{0xd7,0xaf,0x87}, {0xd7,0xaf,0xaf}, {0xd7,0xaf,0xd7}, {0xd7,0xaf,0xff},
|
|
|
|
|
{0xd7,0xd7,0x00}, {0xd7,0xd7,0x5f}, {0xd7,0xd7,0x87}, {0xd7,0xd7,0xaf},
|
|
|
|
|
{0xd7,0xd7,0xd7}, {0xd7,0xd7,0xff}, {0xd7,0xff,0x00}, {0xd7,0xff,0x5f},
|
|
|
|
|
{0xd7,0xff,0x87}, {0xd7,0xff,0xaf}, {0xd7,0xff,0xd7}, {0xd7,0xff,0xff},
|
|
|
|
|
{0xff,0x00,0x00}, {0xff,0x00,0x5f}, {0xff,0x00,0x87}, {0xff,0x00,0xaf},
|
|
|
|
|
{0xff,0x00,0xd7}, {0xff,0x00,0xff}, {0xff,0x5f,0x00}, {0xff,0x5f,0x5f},
|
|
|
|
|
{0xff,0x5f,0x87}, {0xff,0x5f,0xaf}, {0xff,0x5f,0xd7}, {0xff,0x5f,0xff},
|
|
|
|
|
{0xff,0x87,0x00}, {0xff,0x87,0x5f}, {0xff,0x87,0x87}, {0xff,0x87,0xaf},
|
|
|
|
|
{0xff,0x87,0xd7}, {0xff,0x87,0xff}, {0xff,0xaf,0x00}, {0xff,0xaf,0x5f},
|
|
|
|
|
{0xff,0xaf,0x87}, {0xff,0xaf,0xaf}, {0xff,0xaf,0xd7}, {0xff,0xaf,0xff},
|
|
|
|
|
{0xff,0xd7,0x00}, {0xff,0xd7,0x5f}, {0xff,0xd7,0x87}, {0xff,0xd7,0xaf},
|
|
|
|
|
{0xff,0xd7,0xd7}, {0xff,0xd7,0xff}, {0xff,0xff,0x00}, {0xff,0xff,0x5f},
|
|
|
|
|
{0xff,0xff,0x87}, {0xff,0xff,0xaf}, {0xff,0xff,0xd7}, {0xff,0xff,0xff},
|
|
|
|
|
{0x08,0x08,0x08}, {0x12,0x12,0x12}, {0x1c,0x1c,0x1c}, {0x26,0x26,0x26},
|
|
|
|
|
{0x30,0x30,0x30}, {0x3a,0x3a,0x3a}, {0x44,0x44,0x44}, {0x4e,0x4e,0x4e},
|
|
|
|
|
{0x58,0x58,0x58}, {0x60,0x60,0x60}, {0x66,0x66,0x66}, {0x76,0x76,0x76},
|
|
|
|
|
{0x80,0x80,0x80}, {0x8a,0x8a,0x8a}, {0x94,0x94,0x94}, {0x9e,0x9e,0x9e},
|
|
|
|
|
{0xa8,0xa8,0xa8}, {0xb2,0xb2,0xb2}, {0xbc,0xbc,0xbc}, {0xc6,0xc6,0xc6},
|
|
|
|
|
{0xd0,0xd0,0xd0}, {0xda,0xda,0xda}, {0xe4,0xe4,0xe4}, {0xee,0xee,0xee},
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
const std::initializer_list<HashMap<Kakoune::Color, int>::Item>
|
|
|
|
|
NCursesUI::Palette::default_colors = {
|
|
|
|
|
{ Color::Default, -1 },
|
|
|
|
|
{ Color::Black, 0 },
|
|
|
|
|
{ Color::Red, 1 },
|
|
|
|
|
{ Color::Green, 2 },
|
|
|
|
|
{ Color::Yellow, 3 },
|
|
|
|
|
{ Color::Blue, 4 },
|
|
|
|
|
{ Color::Magenta, 5 },
|
|
|
|
|
{ Color::Cyan, 6 },
|
|
|
|
|
{ Color::White, 7 },
|
|
|
|
|
{ Color::BrightBlack, 8 },
|
|
|
|
|
{ Color::BrightRed, 9 },
|
|
|
|
|
{ Color::BrightGreen, 10 },
|
|
|
|
|
{ Color::BrightYellow, 11 },
|
|
|
|
|
{ Color::BrightBlue, 12 },
|
|
|
|
|
{ Color::BrightMagenta, 13 },
|
|
|
|
|
{ Color::BrightCyan, 14 },
|
|
|
|
|
{ Color::BrightWhite, 15 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int NCursesUI::Palette::get_color(Color color)
|
2013-05-07 18:52:23 +02:00
|
|
|
|
{
|
2015-10-08 21:17:35 +02:00
|
|
|
|
auto it = m_colors.find(color);
|
|
|
|
|
if (it != m_colors.end())
|
2017-03-07 01:30:54 +01:00
|
|
|
|
return it->value;
|
2016-12-30 08:01:13 +01:00
|
|
|
|
else if (m_change_colors and can_change_color() and COLORS > 16)
|
2012-02-16 15:25:16 +01:00
|
|
|
|
{
|
2015-04-25 11:47:39 +02:00
|
|
|
|
kak_assert(color.color == Color::RGB);
|
2015-10-08 21:17:35 +02:00
|
|
|
|
if (m_next_color > COLORS)
|
|
|
|
|
m_next_color = 16;
|
|
|
|
|
init_color(m_next_color,
|
2013-05-07 18:52:23 +02:00
|
|
|
|
color.r * 1000 / 255,
|
|
|
|
|
color.g * 1000 / 255,
|
|
|
|
|
color.b * 1000 / 255);
|
2015-10-08 21:17:35 +02:00
|
|
|
|
m_colors[color] = m_next_color;
|
|
|
|
|
return m_next_color++;
|
2013-05-07 18:52:23 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-04-25 11:47:39 +02:00
|
|
|
|
kak_assert(color.color == Color::RGB);
|
2013-05-07 18:52:23 +02:00
|
|
|
|
int lowestDist = INT_MAX;
|
|
|
|
|
int closestCol = -1;
|
2014-07-03 14:53:51 +02:00
|
|
|
|
for (int i = 0; i < std::min(256, COLORS); ++i)
|
2013-05-07 18:52:23 +02:00
|
|
|
|
{
|
2014-07-03 14:53:51 +02:00
|
|
|
|
auto& col = builtin_colors[i];
|
2013-10-17 19:48:12 +02:00
|
|
|
|
int dist = sq(color.r - col.r)
|
|
|
|
|
+ sq(color.g - col.g)
|
|
|
|
|
+ sq(color.b - col.b);
|
2013-05-07 18:52:23 +02:00
|
|
|
|
if (dist < lowestDist)
|
|
|
|
|
{
|
|
|
|
|
lowestDist = dist;
|
2014-07-03 14:53:51 +02:00
|
|
|
|
closestCol = i;
|
2013-05-07 18:52:23 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return closestCol;
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
int NCursesUI::Palette::get_color_pair(const Face& face)
|
2012-02-16 15:25:16 +01:00
|
|
|
|
{
|
2014-07-11 01:27:04 +02:00
|
|
|
|
ColorPair colors{face.fg, face.bg};
|
2015-10-08 21:17:35 +02:00
|
|
|
|
auto it = m_colorpairs.find(colors);
|
|
|
|
|
if (it != m_colorpairs.end())
|
2017-03-07 01:30:54 +01:00
|
|
|
|
return it->value;
|
2012-02-16 15:25:16 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
2016-12-30 08:01:13 +01:00
|
|
|
|
init_pair(m_next_pair, get_color(face.fg), get_color(face.bg));
|
|
|
|
|
m_colorpairs[colors] = m_next_pair;
|
|
|
|
|
return m_next_pair++;
|
2012-09-05 00:49:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
bool NCursesUI::Palette::set_change_colors(bool change_colors)
|
2012-09-05 00:49:59 +02:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
bool reset = false;
|
|
|
|
|
if (can_change_color() and m_change_colors != change_colors)
|
2012-09-05 00:49:59 +02:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
fputs("\033]104\007", stdout); // try to reset palette
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
m_colorpairs.clear();
|
|
|
|
|
m_colors = default_colors;
|
|
|
|
|
m_next_color = 16;
|
|
|
|
|
m_next_pair = 1;
|
|
|
|
|
reset = true;
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_change_colors = change_colors;
|
|
|
|
|
return reset;
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-06-09 14:26:54 +02:00
|
|
|
|
static sig_atomic_t resize_pending = 0;
|
2018-04-29 14:27:28 +02:00
|
|
|
|
static sig_atomic_t sighup_raised = 0;
|
2014-06-09 14:26:54 +02:00
|
|
|
|
|
2018-04-29 14:27:28 +02:00
|
|
|
|
template<sig_atomic_t* signal_flag>
|
|
|
|
|
static void signal_handler(int)
|
2012-10-27 14:18:52 +02:00
|
|
|
|
{
|
2018-04-29 14:27:28 +02:00
|
|
|
|
*signal_flag = 1;
|
2012-10-27 15:02:17 +02:00
|
|
|
|
EventManager::instance().force_signal(0);
|
2012-10-27 14:18:52 +02:00
|
|
|
|
}
|
2012-09-05 00:49:59 +02:00
|
|
|
|
|
2012-09-24 19:24:27 +02:00
|
|
|
|
NCursesUI::NCursesUI()
|
2019-09-04 16:04:27 +02:00
|
|
|
|
: m_cursor{CursorMode::Buffer, {}},
|
2019-02-09 05:41:09 +01:00
|
|
|
|
m_stdin_watcher{0, FdEvents::Read,
|
2018-05-17 14:55:53 +02:00
|
|
|
|
[this](FDWatcher&, FdEvents, EventMode) {
|
2016-11-29 22:35:53 +01:00
|
|
|
|
if (not m_on_key)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
while (auto key = get_next_key())
|
|
|
|
|
m_on_key(*key);
|
2015-02-09 14:33:54 +01:00
|
|
|
|
}},
|
2019-02-09 05:41:09 +01:00
|
|
|
|
m_assistant(assistant_clippy)
|
2012-09-05 00:49:59 +02:00
|
|
|
|
{
|
|
|
|
|
initscr();
|
2014-09-20 20:35:37 +02:00
|
|
|
|
raw();
|
2012-09-05 00:49:59 +02:00
|
|
|
|
noecho();
|
|
|
|
|
nonl();
|
2017-05-29 11:02:18 +02:00
|
|
|
|
curs_set(0);
|
2012-09-05 00:49:59 +02:00
|
|
|
|
start_color();
|
|
|
|
|
use_default_colors();
|
2013-06-18 21:49:56 +02:00
|
|
|
|
set_escdelay(25);
|
2019-09-04 16:19:58 +02:00
|
|
|
|
intrflush(nullptr, false);
|
|
|
|
|
meta(nullptr, true);
|
2012-09-05 00:49:59 +02:00
|
|
|
|
|
2015-10-02 14:52:41 +02:00
|
|
|
|
enable_mouse(true);
|
2014-11-18 01:01:29 +01:00
|
|
|
|
|
2018-04-29 14:27:28 +02:00
|
|
|
|
set_signal_handler(SIGWINCH, &signal_handler<&resize_pending>);
|
|
|
|
|
set_signal_handler(SIGHUP, &signal_handler<&sighup_raised>);
|
2015-06-30 01:31:26 +02:00
|
|
|
|
|
|
|
|
|
check_resize(true);
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
redraw(false);
|
2012-09-05 00:49:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-24 19:24:27 +02:00
|
|
|
|
NCursesUI::~NCursesUI()
|
2012-09-05 00:49:59 +02:00
|
|
|
|
{
|
2015-10-02 14:52:41 +02:00
|
|
|
|
enable_mouse(false);
|
2016-11-05 12:43:25 +01:00
|
|
|
|
if (can_change_color()) // try to reset palette
|
|
|
|
|
{
|
2019-03-12 20:55:08 +01:00
|
|
|
|
fputs("\033]104\007", stdout);
|
2016-11-05 12:43:25 +01:00
|
|
|
|
fflush(stdout);
|
|
|
|
|
}
|
2012-09-05 00:49:59 +02:00
|
|
|
|
endwin();
|
2016-01-10 21:46:15 +01:00
|
|
|
|
set_signal_handler(SIGWINCH, SIG_DFL);
|
|
|
|
|
set_signal_handler(SIGCONT, SIG_DFL);
|
2012-09-05 00:49:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
void NCursesUI::redraw(bool force)
|
2015-09-11 00:39:19 +02:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.refresh(force);
|
2017-04-12 11:39:17 +02:00
|
|
|
|
|
2018-06-20 00:12:53 +02:00
|
|
|
|
if (m_menu.columns != 0 or m_menu.pos.column > m_status_len)
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.refresh(false);
|
2018-06-20 00:12:53 +02:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_info.refresh(false);
|
2017-04-12 11:39:17 +02:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
Window screen{{}, static_cast<NCursesWin*>(newscr)};
|
2017-04-12 11:39:17 +02:00
|
|
|
|
if (m_cursor.mode == CursorMode::Prompt)
|
2019-09-04 16:04:27 +02:00
|
|
|
|
screen.move_cursor({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column});
|
2017-04-12 11:39:17 +02:00
|
|
|
|
else
|
2019-09-04 16:04:27 +02:00
|
|
|
|
screen.move_cursor(m_cursor.coord + content_line_offset());
|
2017-04-12 11:39:17 +02:00
|
|
|
|
|
2012-09-30 15:18:37 +02:00
|
|
|
|
doupdate();
|
|
|
|
|
}
|
2014-04-15 20:19:44 +02:00
|
|
|
|
|
2017-04-12 11:39:17 +02:00
|
|
|
|
void NCursesUI::set_cursor(CursorMode mode, DisplayCoord coord)
|
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_cursor = Cursor{mode, coord};
|
2017-04-12 11:39:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 14:54:20 +01:00
|
|
|
|
void NCursesUI::refresh(bool force)
|
2014-04-15 20:19:44 +02:00
|
|
|
|
{
|
2016-03-07 14:54:20 +01:00
|
|
|
|
if (m_dirty or force)
|
2019-09-04 16:04:27 +02:00
|
|
|
|
redraw(force);
|
2014-04-15 20:19:44 +02:00
|
|
|
|
m_dirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 12:33:47 +02:00
|
|
|
|
static const DisplayLine empty_line = { String(" "), {} };
|
2016-02-17 14:32:05 +01:00
|
|
|
|
|
2012-10-27 14:18:52 +02:00
|
|
|
|
void NCursesUI::draw(const DisplayBuffer& display_buffer,
|
2016-02-17 14:32:05 +01:00
|
|
|
|
const Face& default_face,
|
|
|
|
|
const Face& padding_face)
|
2012-10-27 14:18:52 +02:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.set_background_color(m_palette, default_face);
|
2015-06-04 14:49:28 +02:00
|
|
|
|
|
2014-06-09 14:26:54 +02:00
|
|
|
|
check_resize();
|
|
|
|
|
|
2018-06-08 10:54:11 +02:00
|
|
|
|
const DisplayCoord dim = dimensions();
|
2018-06-03 04:06:29 +02:00
|
|
|
|
const LineCount line_offset = content_line_offset();
|
|
|
|
|
LineCount line_index = line_offset;
|
2012-10-20 20:15:20 +02:00
|
|
|
|
for (const DisplayLine& line : display_buffer.lines())
|
2012-02-16 15:25:16 +01:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.move_cursor(line_index);
|
|
|
|
|
m_window.clear_to_end_of_line();
|
|
|
|
|
m_window.draw_line(m_palette, line, 0, dim.column, default_face);
|
2012-07-12 23:19:10 +02:00
|
|
|
|
++line_index;
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.set_background_color(m_palette, padding_face);
|
|
|
|
|
m_window.set_face(m_palette, padding_face, default_face);
|
2016-04-11 22:12:27 +02:00
|
|
|
|
|
2018-06-08 10:54:11 +02:00
|
|
|
|
while (line_index < dim.line + line_offset)
|
2012-02-16 15:25:16 +01:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.move_cursor(line_index++);
|
|
|
|
|
m_window.clear_to_end_of_line();
|
|
|
|
|
m_window.add_str('~');
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-17 22:28:02 +02:00
|
|
|
|
m_dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::draw_status(const DisplayLine& status_line,
|
|
|
|
|
const DisplayLine& mode_line,
|
|
|
|
|
const Face& default_face)
|
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
const LineCount status_line_pos = m_status_on_top ? 0 : m_dimensions.line;
|
|
|
|
|
m_window.move_cursor(status_line_pos);
|
2015-06-29 23:48:26 +02:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.set_background_color(m_palette, default_face);
|
|
|
|
|
m_window.clear_to_end_of_line();
|
2015-06-29 23:48:26 +02:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.draw_line(m_palette, status_line, 0, m_dimensions.column, default_face);
|
2015-06-29 23:48:26 +02:00
|
|
|
|
|
2015-04-23 22:11:50 +02:00
|
|
|
|
const auto mode_len = mode_line.length();
|
2018-06-20 00:12:53 +02:00
|
|
|
|
m_status_len = status_line.length();
|
|
|
|
|
const auto remaining = m_dimensions.column - m_status_len;
|
2015-04-23 22:11:50 +02:00
|
|
|
|
if (mode_len < remaining)
|
2012-10-29 19:01:57 +01:00
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
ColumnCount col = m_dimensions.column - mode_len;
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.move_cursor({status_line_pos, col});
|
|
|
|
|
m_window.draw_line(m_palette, mode_line, col, m_dimensions.column, default_face);
|
2012-10-29 19:01:57 +01:00
|
|
|
|
}
|
2015-04-23 22:11:50 +02:00
|
|
|
|
else if (remaining > 2)
|
|
|
|
|
{
|
|
|
|
|
DisplayLine trimmed_mode_line = mode_line;
|
2017-06-09 16:30:13 +02:00
|
|
|
|
trimmed_mode_line.trim(mode_len + 2 - remaining, remaining - 2);
|
2018-04-29 12:33:47 +02:00
|
|
|
|
trimmed_mode_line.insert(trimmed_mode_line.begin(), { "…", {} });
|
2015-04-23 22:11:50 +02:00
|
|
|
|
kak_assert(trimmed_mode_line.length() == remaining - 1);
|
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
ColumnCount col = m_dimensions.column - remaining + 1;
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.move_cursor({status_line_pos, col});
|
|
|
|
|
m_window.draw_line(m_palette, trimmed_mode_line, col, m_dimensions.column, default_face);
|
2015-04-23 22:11:50 +02:00
|
|
|
|
}
|
2013-04-18 20:22:19 +02:00
|
|
|
|
|
2015-09-03 01:03:07 +02:00
|
|
|
|
if (m_set_title)
|
2013-04-18 20:22:19 +02:00
|
|
|
|
{
|
2016-10-04 21:13:15 +02:00
|
|
|
|
constexpr char suffix[] = " - Kakoune\007";
|
|
|
|
|
char buf[4 + 511 + 2] = "\033]2;";
|
|
|
|
|
// Fill title escape sequence buffer, removing non ascii characters
|
|
|
|
|
auto buf_it = &buf[4], buf_end = &buf[4 + 511 - (sizeof(suffix) - 2)];
|
2013-04-18 20:22:19 +02:00
|
|
|
|
for (auto& atom : mode_line)
|
2016-10-04 21:13:15 +02:00
|
|
|
|
{
|
|
|
|
|
const auto str = atom.content();
|
|
|
|
|
for (auto it = str.begin(), end = str.end();
|
|
|
|
|
it != end and buf_it != buf_end; utf8::to_next(it, end))
|
|
|
|
|
*buf_it++ = (*it >= 0x20 and *it <= 0x7e) ? *it : '?';
|
|
|
|
|
}
|
|
|
|
|
for (auto c : suffix)
|
|
|
|
|
*buf_it++ = c;
|
|
|
|
|
|
|
|
|
|
fputs(buf, stdout);
|
2016-06-20 21:56:38 +02:00
|
|
|
|
fflush(stdout);
|
2013-04-18 20:22:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-15 20:19:44 +02:00
|
|
|
|
m_dirty = true;
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-30 01:31:26 +02:00
|
|
|
|
void NCursesUI::check_resize(bool force)
|
2014-06-09 14:26:54 +02:00
|
|
|
|
{
|
2015-06-30 01:31:26 +02:00
|
|
|
|
if (not force and not resize_pending)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
resize_pending = 0;
|
|
|
|
|
|
|
|
|
|
const int fd = open("/dev/tty", O_RDWR);
|
2018-04-04 06:01:24 +02:00
|
|
|
|
if (fd < 0)
|
|
|
|
|
return;
|
2018-03-31 14:00:19 +02:00
|
|
|
|
auto close_fd = on_scope_end([fd]{ ::close(fd); });
|
2015-09-08 00:52:34 +02:00
|
|
|
|
|
2018-04-04 06:01:24 +02:00
|
|
|
|
winsize ws;
|
2018-03-31 14:00:19 +02:00
|
|
|
|
if (::ioctl(fd, TIOCGWINSZ, &ws) != 0)
|
2018-04-04 06:01:24 +02:00
|
|
|
|
return;
|
2015-06-30 01:31:26 +02:00
|
|
|
|
|
2018-03-31 14:00:19 +02:00
|
|
|
|
const bool info = (bool)m_info;
|
|
|
|
|
const bool menu = (bool)m_menu;
|
2019-09-04 16:04:27 +02:00
|
|
|
|
if (m_window) m_window.destroy();
|
2018-03-31 14:00:19 +02:00
|
|
|
|
if (info) m_info.destroy();
|
|
|
|
|
if (menu) m_menu.destroy();
|
2015-06-30 01:31:26 +02:00
|
|
|
|
|
2018-03-31 14:00:19 +02:00
|
|
|
|
resize_term(ws.ws_row, ws.ws_col);
|
2015-06-30 10:08:44 +02:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.create({0, 0}, {ws.ws_row, ws.ws_col});
|
2018-07-31 14:15:39 +02:00
|
|
|
|
kak_assert(m_window);
|
2019-09-04 16:04:27 +02:00
|
|
|
|
keypad(m_window.win, not m_builtin_key_parser);
|
2015-10-13 14:52:02 +02:00
|
|
|
|
|
2018-03-31 14:00:19 +02:00
|
|
|
|
m_dimensions = DisplayCoord{ws.ws_row-1, ws.ws_col};
|
|
|
|
|
|
|
|
|
|
if (char* csr = tigetstr((char*)"csr"))
|
|
|
|
|
putp(tparm(csr, 0, ws.ws_row));
|
|
|
|
|
|
|
|
|
|
if (menu)
|
|
|
|
|
{
|
|
|
|
|
auto items = std::move(m_menu.items);
|
|
|
|
|
menu_show(items, m_menu.anchor, m_menu.fg, m_menu.bg, m_menu.style);
|
2014-06-09 14:26:54 +02:00
|
|
|
|
}
|
2018-03-31 14:00:19 +02:00
|
|
|
|
if (info)
|
|
|
|
|
info_show(m_info.title, m_info.content, m_info.anchor, m_info.face, m_info.style);
|
2015-06-30 01:31:26 +02:00
|
|
|
|
|
2018-06-19 12:20:38 +02:00
|
|
|
|
set_resize_pending();
|
2015-09-21 23:12:23 +02:00
|
|
|
|
clearok(curscr, true);
|
2015-09-19 12:30:17 +02:00
|
|
|
|
werase(curscr);
|
2014-06-09 14:26:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-29 22:35:53 +01:00
|
|
|
|
Optional<Key> NCursesUI::get_next_key()
|
2012-10-28 09:26:54 +01:00
|
|
|
|
{
|
2018-04-29 14:27:28 +02:00
|
|
|
|
if (sighup_raised)
|
|
|
|
|
{
|
|
|
|
|
set_signal_handler(SIGWINCH, SIG_DFL);
|
|
|
|
|
set_signal_handler(SIGCONT, SIG_DFL);
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.invalidate();
|
2018-07-08 07:54:01 +02:00
|
|
|
|
m_stdin_watcher.disable();
|
2016-11-29 22:35:53 +01:00
|
|
|
|
return {};
|
2018-04-29 14:27:28 +02:00
|
|
|
|
}
|
2016-11-14 01:49:34 +01:00
|
|
|
|
|
2014-06-09 14:26:54 +02:00
|
|
|
|
check_resize();
|
|
|
|
|
|
2018-06-19 12:20:38 +02:00
|
|
|
|
if (m_resize_pending)
|
2018-06-08 10:54:11 +02:00
|
|
|
|
{
|
2018-06-19 12:20:38 +02:00
|
|
|
|
m_resize_pending = false;
|
|
|
|
|
return resize(dimensions());
|
2018-06-08 10:54:11 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.set_blocking(false);
|
|
|
|
|
const int c = m_window.get_char();
|
|
|
|
|
m_window.set_blocking(true);
|
2012-10-28 09:26:54 +01:00
|
|
|
|
|
2018-12-09 11:04:22 +01:00
|
|
|
|
if (c == ERR)
|
|
|
|
|
return {};
|
|
|
|
|
|
2015-03-22 11:08:44 +01:00
|
|
|
|
if (c == KEY_MOUSE)
|
|
|
|
|
{
|
|
|
|
|
MEVENT ev;
|
|
|
|
|
if (getmouse(&ev) == OK)
|
|
|
|
|
{
|
2019-08-19 14:16:39 +02:00
|
|
|
|
const auto mask = ev.bstate;
|
|
|
|
|
auto coord = encode_coord({ ev.y - content_line_offset(), ev.x });
|
|
|
|
|
|
|
|
|
|
Key::Modifiers mod{};
|
|
|
|
|
if (mask & BUTTON_CTRL)
|
|
|
|
|
mod |= Key::Modifiers::Control;
|
|
|
|
|
if (mask & BUTTON_ALT)
|
|
|
|
|
mod |= Key::Modifiers::Alt;
|
|
|
|
|
|
|
|
|
|
if (BUTTON_PRESS(mask, 1))
|
|
|
|
|
return Key{mod | Key::Modifiers::MousePressLeft, coord};
|
|
|
|
|
if (BUTTON_PRESS(mask, 3))
|
|
|
|
|
return Key{mod | Key::Modifiers::MousePressRight, coord};
|
|
|
|
|
if (BUTTON_RELEASE(mask, 1))
|
|
|
|
|
return Key{mod | Key::Modifiers::MouseReleaseLeft, coord};
|
|
|
|
|
if (BUTTON_RELEASE(mask, 3))
|
|
|
|
|
return Key{mod | Key::Modifiers::MouseReleaseRight, coord};
|
|
|
|
|
if (BUTTON_PRESS(mask, m_wheel_down_button))
|
|
|
|
|
return Key{mod | Key::Modifiers::Scroll, static_cast<Codepoint>(m_wheel_scroll_amount)};
|
|
|
|
|
if (BUTTON_PRESS(mask, m_wheel_up_button))
|
|
|
|
|
return Key{mod | Key::Modifiers::Scroll, static_cast<Codepoint>(-m_wheel_scroll_amount)};
|
|
|
|
|
return Key{mod | Key::Modifiers::MousePos, coord};
|
2015-03-22 11:08:44 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-30 14:38:56 +01:00
|
|
|
|
auto parse_key = [this](int c) -> Optional<Key> {
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case KEY_BACKSPACE: case 127: return {Key::Backspace};
|
|
|
|
|
case KEY_DC: return {Key::Delete};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SDC: return shift(Key::Delete);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_UP: return {Key::Up};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SR: return shift(Key::Up);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_DOWN: return {Key::Down};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SF: return shift(Key::Down);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_LEFT: return {Key::Left};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SLEFT: return shift(Key::Left);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_RIGHT: return {Key::Right};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SRIGHT: return shift(Key::Right);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_PPAGE: return {Key::PageUp};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SPREVIOUS: return shift(Key::PageUp);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_NPAGE: return {Key::PageDown};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SNEXT: return shift(Key::PageDown);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_HOME: return {Key::Home};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SHOME: return shift(Key::Home);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
case KEY_END: return {Key::End};
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_SEND: return shift(Key::End);
|
2019-07-07 18:38:46 +02:00
|
|
|
|
case KEY_IC: return {Key::Insert};
|
|
|
|
|
case KEY_SIC: return shift(Key::Insert);
|
2018-03-15 13:02:27 +01:00
|
|
|
|
case KEY_BTAB: return shift(Key::Tab);
|
2018-06-03 04:06:29 +02:00
|
|
|
|
case KEY_RESIZE: return resize(dimensions());
|
2014-09-20 20:35:37 +02:00
|
|
|
|
}
|
2017-01-30 14:38:56 +01:00
|
|
|
|
|
|
|
|
|
if (c > 0 and c < 27)
|
|
|
|
|
{
|
|
|
|
|
if (c == control('m') or c == control('j'))
|
|
|
|
|
return {Key::Return};
|
|
|
|
|
if (c == control('i'))
|
|
|
|
|
return {Key::Tab};
|
|
|
|
|
if (c == control('h'))
|
|
|
|
|
return {Key::Backspace};
|
|
|
|
|
if (c == control('z'))
|
|
|
|
|
{
|
2018-01-30 00:46:34 +01:00
|
|
|
|
bool mouse_enabled = m_mouse_enabled;
|
|
|
|
|
enable_mouse(false);
|
|
|
|
|
|
2019-08-21 10:29:25 +02:00
|
|
|
|
kill(0, SIGTSTP); // We suspend at this line
|
2018-01-30 00:46:34 +01:00
|
|
|
|
|
|
|
|
|
check_resize(true);
|
|
|
|
|
enable_mouse(mouse_enabled);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
return ctrl(Codepoint(c) - 1 + 'a');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 12; ++i)
|
|
|
|
|
{
|
2018-04-11 12:29:35 +02:00
|
|
|
|
if (c == KEY_F(i + 1))
|
2017-01-30 14:38:56 +01:00
|
|
|
|
return {Key::F1 + i};
|
2018-04-11 12:29:35 +02:00
|
|
|
|
if (c == KEY_F(m_shift_function_key + i + 1))
|
|
|
|
|
return shift(Key::F1 + i);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c >= 0 and c < 256)
|
|
|
|
|
{
|
|
|
|
|
ungetch(c);
|
|
|
|
|
struct getch_iterator
|
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
getch_iterator(Window& win) : window(win) {}
|
|
|
|
|
int operator*() { return window.get_char(); }
|
2017-01-30 14:38:56 +01:00
|
|
|
|
getch_iterator& operator++() { return *this; }
|
|
|
|
|
getch_iterator& operator++(int) { return *this; }
|
|
|
|
|
bool operator== (const getch_iterator&) const { return false; }
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
Window& window;
|
2017-01-30 14:38:56 +01:00
|
|
|
|
};
|
|
|
|
|
return Key{utf8::codepoint(getch_iterator{m_window},
|
|
|
|
|
getch_iterator{m_window})};
|
|
|
|
|
}
|
2017-02-13 15:02:09 +01:00
|
|
|
|
return {};
|
2017-01-30 14:38:56 +01:00
|
|
|
|
};
|
|
|
|
|
|
2018-12-09 11:04:22 +01:00
|
|
|
|
constexpr auto direction = make_array({Key::Up, Key::Down, Key::Right, Key::Left, Key::Home, Key::End});
|
2019-09-07 07:34:58 +02:00
|
|
|
|
constexpr auto special = make_array({Key::Insert, Key::Delete, Key::NamedKey{}, Key::PageUp, Key::PageDown, Key::Home, Key::End, Key::NamedKey{}, Key::NamedKey{},
|
|
|
|
|
Key::F1, Key::F2, Key::F3, Key::F4, Key::NamedKey{}, Key::F5, Key::F6, Key::F7, Key::F8, Key::F9, Key::F10, Key::NamedKey{}, Key::F11, Key::F12});
|
|
|
|
|
auto parse_csi = [this, &direction, &special]() -> Optional<Key> {
|
|
|
|
|
int params[16] = {};
|
|
|
|
|
int c = m_window.get_char();
|
|
|
|
|
char private_mode = 0;
|
|
|
|
|
if (c == '?' or c == '<' or c == '=' or c == '>')
|
2018-12-09 01:20:03 +01:00
|
|
|
|
{
|
2019-09-07 07:34:58 +02:00
|
|
|
|
private_mode = c;
|
|
|
|
|
c = m_window.get_char();
|
|
|
|
|
}
|
|
|
|
|
for (int count = 0; count < 16 and c >= 0x30 && c <= 0x3f; c = m_window.get_char())
|
|
|
|
|
{
|
|
|
|
|
if ('0' <= 'c' and c <= '9')
|
|
|
|
|
params[count] = params[count] * 10 + c - '0';
|
|
|
|
|
else if (c == ';')
|
|
|
|
|
++count;
|
|
|
|
|
else
|
2018-12-09 11:04:22 +01:00
|
|
|
|
return {};
|
2019-09-07 07:34:58 +02:00
|
|
|
|
}
|
|
|
|
|
if (c < 0x40 or c > 0x7e)
|
|
|
|
|
return {};
|
2018-12-09 11:04:22 +01:00
|
|
|
|
|
2019-09-07 07:34:58 +02:00
|
|
|
|
auto parse_mask = [](int mask) {
|
|
|
|
|
mask = std::max(0, mask - 1);
|
|
|
|
|
Key::Modifiers mod = Key::Modifiers::None;
|
2018-12-09 11:04:22 +01:00
|
|
|
|
if (mask & 1)
|
2019-09-07 07:34:58 +02:00
|
|
|
|
mod |= Key::Modifiers::Shift;
|
2018-12-09 11:04:22 +01:00
|
|
|
|
if (mask & 2)
|
2019-09-07 07:34:58 +02:00
|
|
|
|
mod |= Key::Modifiers::Alt;
|
2018-12-09 11:04:22 +01:00
|
|
|
|
if (mask & 4)
|
2019-09-07 07:34:58 +02:00
|
|
|
|
mod |= Key::Modifiers::Control;
|
|
|
|
|
return mod;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto mouse_button = [this](Key::Modifiers mod, Codepoint coord, bool left, bool release) {
|
|
|
|
|
auto mask = left ? 0x1 : 0x2;
|
|
|
|
|
if (not release)
|
|
|
|
|
{
|
|
|
|
|
mod |= (m_mouse_state & mask) ? Key::Modifiers::MousePos : (left ? Key::Modifiers::MousePressLeft : Key::Modifiers::MousePressRight);
|
|
|
|
|
m_mouse_state |= mask;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mod |= left ? Key::Modifiers::MouseReleaseLeft : Key::Modifiers::MouseReleaseRight;
|
|
|
|
|
m_mouse_state &= ~mask;
|
|
|
|
|
}
|
|
|
|
|
return Key{mod, coord};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (c >= 'A' and c <= 'F')
|
|
|
|
|
return Key{parse_mask(params[1]), direction[c - 'A']};
|
|
|
|
|
if (c == '~' and 2 <= params[0] and params[0] <= 24)
|
|
|
|
|
return Key{parse_mask(params[1]), special[params[0] - 2]};
|
|
|
|
|
if (c == 'Z')
|
|
|
|
|
return shift(Key::Tab);
|
|
|
|
|
if (c == 'I')
|
2018-12-09 11:04:22 +01:00
|
|
|
|
return {Key::FocusIn};
|
2019-09-07 07:34:58 +02:00
|
|
|
|
if (c == 'O')
|
2018-12-09 11:04:22 +01:00
|
|
|
|
return {Key::FocusOut};
|
2019-09-07 07:34:58 +02:00
|
|
|
|
if ((c == 'M' or c == 'm') and private_mode == '<')
|
|
|
|
|
{
|
|
|
|
|
auto coord = encode_coord({params[2] - content_line_offset() - 1, params[1] - 1});
|
|
|
|
|
Key::Modifiers mod = parse_mask(1 + ((params[0] >> 2) & 0x7));
|
|
|
|
|
switch (params[0] & 0x43)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
return mouse_button(mod, coord, true, c == 'm');
|
|
|
|
|
case 2:
|
|
|
|
|
return mouse_button(mod, coord, false, c == 'm');
|
|
|
|
|
case 64:
|
|
|
|
|
return Key{mod | Key::Modifiers::Scroll,
|
|
|
|
|
static_cast<Codepoint>(-m_wheel_scroll_amount)};
|
|
|
|
|
case 65:
|
|
|
|
|
return Key{mod | Key::Modifiers::Scroll,
|
|
|
|
|
static_cast<Codepoint>(m_wheel_scroll_amount)};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (c == 'M')
|
|
|
|
|
{
|
|
|
|
|
const Codepoint b = m_window.get_char() - 32;
|
|
|
|
|
const int x = m_window.get_char() - 32 - 1;
|
|
|
|
|
const int y = m_window.get_char() - 32 - 1;
|
|
|
|
|
auto coord = encode_coord({y - content_line_offset(), x});
|
|
|
|
|
Key::Modifiers mod = parse_mask(1 + ((b >> 2) & 0x7));
|
|
|
|
|
switch (b & 0x43)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
return mouse_button(mod, coord, true, false);
|
|
|
|
|
case 2:
|
|
|
|
|
return mouse_button(mod, coord, false, false);
|
|
|
|
|
case 3:
|
|
|
|
|
if (m_mouse_state & 0x1)
|
|
|
|
|
return mouse_button(mod, coord, true, true);
|
|
|
|
|
else if (m_mouse_state & 0x2)
|
|
|
|
|
return mouse_button(mod, coord, false, true);
|
|
|
|
|
break;
|
|
|
|
|
case 64:
|
|
|
|
|
return Key{mod | Key::Modifiers::Scroll,
|
|
|
|
|
static_cast<Codepoint>(-m_wheel_scroll_amount)};
|
|
|
|
|
case 65:
|
|
|
|
|
return Key{mod | Key::Modifiers::Scroll,
|
|
|
|
|
static_cast<Codepoint>(m_wheel_scroll_amount)};
|
|
|
|
|
}
|
|
|
|
|
return Key{Key::Modifiers::MousePos, coord};
|
|
|
|
|
}
|
2018-12-09 01:20:03 +01:00
|
|
|
|
return {};
|
|
|
|
|
};
|
|
|
|
|
|
2018-12-12 09:10:28 +01:00
|
|
|
|
if (c == 27)
|
2012-02-16 15:25:16 +01:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.set_blocking(false);
|
|
|
|
|
const int new_c = m_window.get_char();
|
2018-12-12 09:10:28 +01:00
|
|
|
|
if (new_c == '[') // potential CSI
|
2015-07-15 14:56:31 +02:00
|
|
|
|
{
|
2018-12-09 01:20:03 +01:00
|
|
|
|
if (auto key = parse_csi())
|
|
|
|
|
return key;
|
2015-07-15 14:56:31 +02:00
|
|
|
|
}
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.set_blocking(true);
|
2017-01-30 14:38:56 +01:00
|
|
|
|
|
|
|
|
|
if (auto key = parse_key(new_c))
|
|
|
|
|
return alt(*key);
|
2012-09-07 20:22:19 +02:00
|
|
|
|
else
|
2016-11-29 22:35:53 +01:00
|
|
|
|
return {Key::Escape};
|
2012-09-07 20:22:19 +02:00
|
|
|
|
}
|
2018-12-09 01:20:03 +01:00
|
|
|
|
|
2017-01-30 14:38:56 +01:00
|
|
|
|
return parse_key(c);
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2013-10-17 01:22:06 +02:00
|
|
|
|
template<typename T>
|
|
|
|
|
T div_round_up(T a, T b)
|
|
|
|
|
{
|
|
|
|
|
return (a - T(1)) / b + T(1);
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-14 19:19:33 +01:00
|
|
|
|
void NCursesUI::draw_menu()
|
|
|
|
|
{
|
2013-07-12 14:15:56 +02:00
|
|
|
|
// menu show may have not created the window if it did not fit.
|
|
|
|
|
// so be tolerant.
|
2015-09-11 00:39:19 +02:00
|
|
|
|
if (not m_menu)
|
2013-07-12 14:15:56 +02:00
|
|
|
|
return;
|
2013-03-14 19:19:33 +01:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.set_face(m_palette, m_menu.bg, {});
|
|
|
|
|
m_menu.set_background_color(m_palette, m_menu.bg);
|
2013-10-17 01:22:06 +02:00
|
|
|
|
|
2016-02-27 18:22:31 +01:00
|
|
|
|
const int item_count = (int)m_menu.items.size();
|
2018-06-19 12:20:38 +02:00
|
|
|
|
if (m_menu.columns == 0)
|
|
|
|
|
{
|
2018-06-20 12:20:11 +02:00
|
|
|
|
const auto win_width = m_menu.size.column - 4;
|
2018-06-19 12:20:38 +02:00
|
|
|
|
kak_assert(m_menu.size.line == 1);
|
|
|
|
|
ColumnCount pos = 0;
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.move_cursor({0, 0});
|
|
|
|
|
m_menu.add_str(m_menu.first_item > 0 ? "< " : " ");
|
2018-06-19 12:20:38 +02:00
|
|
|
|
|
|
|
|
|
int i = m_menu.first_item;
|
2018-06-20 12:20:11 +02:00
|
|
|
|
for (; i < item_count and pos < win_width; ++i)
|
2018-06-19 12:20:38 +02:00
|
|
|
|
{
|
|
|
|
|
const DisplayLine& item = m_menu.items[i];
|
2018-06-20 12:20:11 +02:00
|
|
|
|
const ColumnCount item_width = item.length();
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.draw_line(m_palette, item, 0, win_width - pos,
|
|
|
|
|
i == m_menu.selected_item ? m_menu.fg : m_menu.bg);
|
2018-06-20 12:20:11 +02:00
|
|
|
|
|
|
|
|
|
if (item_width > win_width - pos)
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.add_str("…");
|
2018-06-20 12:20:11 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.set_face(m_palette, m_menu.bg, {});
|
|
|
|
|
m_menu.add_str(String{" "});
|
2018-06-20 12:20:11 +02:00
|
|
|
|
}
|
|
|
|
|
pos += item_width + 1;
|
2018-06-19 12:20:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.set_face(m_palette, m_menu.bg, {});
|
2018-06-20 12:20:11 +02:00
|
|
|
|
if (pos <= win_width)
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.add_str(String{' ', win_width - pos + 1});
|
|
|
|
|
m_menu.add_str(i == item_count ? " " : ">");
|
2018-06-19 12:20:38 +02:00
|
|
|
|
m_dirty = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-27 18:22:31 +01:00
|
|
|
|
const LineCount menu_lines = div_round_up(item_count, m_menu.columns);
|
2018-06-03 01:08:02 +02:00
|
|
|
|
const LineCount win_height = m_menu.size.line;
|
2013-10-17 19:48:12 +02:00
|
|
|
|
kak_assert(win_height <= menu_lines);
|
2013-10-17 01:22:06 +02:00
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
const ColumnCount column_width = (m_menu.size.column - 1) / m_menu.columns;
|
2013-10-17 01:22:06 +02:00
|
|
|
|
|
2013-10-17 19:48:12 +02:00
|
|
|
|
const LineCount mark_height = min(div_round_up(sq(win_height), menu_lines),
|
|
|
|
|
win_height);
|
2018-07-20 19:30:47 +02:00
|
|
|
|
|
|
|
|
|
const int menu_cols = div_round_up(item_count, (int)m_menu.size.line);
|
|
|
|
|
const int first_col = m_menu.first_item / (int)m_menu.size.line;
|
|
|
|
|
|
2018-08-10 19:14:48 +02:00
|
|
|
|
const LineCount mark_line = (win_height - mark_height) * first_col / max(1, menu_cols - m_menu.columns);
|
2018-07-20 19:30:47 +02:00
|
|
|
|
|
2013-10-17 19:48:12 +02:00
|
|
|
|
for (auto line = 0_line; line < win_height; ++line)
|
2013-03-14 19:19:33 +01:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.move_cursor(line);
|
2016-02-27 18:22:31 +01:00
|
|
|
|
for (int col = 0; col < m_menu.columns; ++col)
|
2013-03-14 19:19:33 +01:00
|
|
|
|
{
|
2018-08-10 19:14:48 +02:00
|
|
|
|
int item_idx = (first_col + col) * (int)m_menu.size.line + (int)line;
|
|
|
|
|
if (item_idx >= item_count)
|
|
|
|
|
continue;
|
2013-03-14 19:19:33 +01:00
|
|
|
|
|
2016-02-27 18:22:31 +01:00
|
|
|
|
const DisplayLine& item = m_menu.items[item_idx];
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.draw_line(m_palette, item, 0, column_width,
|
|
|
|
|
item_idx == m_menu.selected_item ? m_menu.fg : m_menu.bg);
|
2016-09-22 21:36:26 +02:00
|
|
|
|
const ColumnCount pad = column_width - item.length();
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.add_str(String{' ', pad});
|
2013-03-14 19:19:33 +01:00
|
|
|
|
}
|
2013-10-17 19:48:12 +02:00
|
|
|
|
const bool is_mark = line >= mark_line and
|
|
|
|
|
line < mark_line + mark_height;
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_menu.clear_to_end_of_line();
|
|
|
|
|
m_menu.move_cursor({line, m_menu.size.column - 1});
|
|
|
|
|
m_menu.set_face(m_palette, m_menu.bg, {});
|
|
|
|
|
m_menu.add_str(is_mark ? "█" : "░");
|
2013-03-14 19:19:33 +01:00
|
|
|
|
}
|
2014-04-15 20:19:44 +02:00
|
|
|
|
m_dirty = true;
|
2013-03-14 19:19:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 04:15:02 +02:00
|
|
|
|
static LineCount height_limit(MenuStyle style)
|
|
|
|
|
{
|
|
|
|
|
switch (style)
|
|
|
|
|
{
|
|
|
|
|
case MenuStyle::Inline: return 10_line;
|
|
|
|
|
case MenuStyle::Prompt: return 10_line;
|
|
|
|
|
case MenuStyle::Search: return 3_line;
|
|
|
|
|
}
|
|
|
|
|
kak_assert(false);
|
|
|
|
|
return 0_line;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 02:25:23 +02:00
|
|
|
|
void NCursesUI::menu_show(ConstArrayView<DisplayLine> items,
|
2016-09-22 21:36:26 +02:00
|
|
|
|
DisplayCoord anchor, Face fg, Face bg,
|
2013-04-04 13:53:47 +02:00
|
|
|
|
MenuStyle style)
|
2012-08-30 21:14:28 +02:00
|
|
|
|
{
|
2018-06-03 04:06:29 +02:00
|
|
|
|
if (m_menu)
|
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.mark_dirty(m_menu.pos.line, m_menu.size.line);
|
2018-06-03 04:06:29 +02:00
|
|
|
|
m_menu.destroy();
|
|
|
|
|
m_dirty = true;
|
|
|
|
|
}
|
2012-11-21 19:01:13 +01:00
|
|
|
|
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_menu.fg = fg;
|
|
|
|
|
m_menu.bg = bg;
|
|
|
|
|
m_menu.style = style;
|
|
|
|
|
m_menu.anchor = anchor;
|
2013-04-04 13:53:47 +02:00
|
|
|
|
|
2018-03-19 19:54:19 +01:00
|
|
|
|
if (m_dimensions.column <= 2)
|
2013-05-16 21:44:58 +02:00
|
|
|
|
return;
|
2012-11-21 19:01:13 +01:00
|
|
|
|
|
2013-10-17 19:48:12 +02:00
|
|
|
|
const int item_count = items.size();
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_menu.items.clear(); // make sure it is empty
|
|
|
|
|
m_menu.items.reserve(item_count);
|
2018-03-13 04:24:03 +01:00
|
|
|
|
const auto longest = accumulate(items | transform(&DisplayLine::length),
|
2018-03-13 04:14:16 +01:00
|
|
|
|
1_col, [](auto&& lhs, auto&& rhs) { return std::max(lhs, rhs); });
|
2015-11-18 21:12:16 +01:00
|
|
|
|
|
2018-06-03 01:08:02 +02:00
|
|
|
|
const ColumnCount max_width = m_dimensions.column - 1;
|
|
|
|
|
const bool is_inline = style == MenuStyle::Inline;
|
2018-06-19 12:20:38 +02:00
|
|
|
|
const bool is_search = style == MenuStyle::Search;
|
|
|
|
|
m_menu.columns = is_search ? 0 : (is_inline ? 1 : max((int)(max_width / (longest+1)), 1));
|
2018-06-03 01:08:02 +02:00
|
|
|
|
|
2018-06-03 04:15:02 +02:00
|
|
|
|
const LineCount max_height = min(height_limit(style), max(anchor.line, m_dimensions.line - anchor.line - 1));
|
2018-06-19 12:20:38 +02:00
|
|
|
|
const LineCount height = is_search ?
|
|
|
|
|
1 : (min<LineCount>(max_height, div_round_up(item_count, m_menu.columns)));
|
2015-11-18 21:12:16 +01:00
|
|
|
|
|
2018-03-13 05:18:06 +01:00
|
|
|
|
const ColumnCount maxlen = (m_menu.columns > 1 and item_count > 1) ?
|
2018-06-03 01:08:02 +02:00
|
|
|
|
max_width / m_menu.columns - 1 : max_width;
|
2015-11-18 21:12:16 +01:00
|
|
|
|
|
2013-10-17 19:48:12 +02:00
|
|
|
|
for (auto& item : items)
|
2012-08-31 14:14:16 +02:00
|
|
|
|
{
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_menu.items.push_back(item);
|
2017-06-09 16:30:13 +02:00
|
|
|
|
m_menu.items.back().trim(0, maxlen);
|
2016-02-27 18:22:31 +01:00
|
|
|
|
kak_assert(m_menu.items.back().length() <= maxlen);
|
2012-08-31 14:14:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 04:06:29 +02:00
|
|
|
|
if (is_inline)
|
|
|
|
|
anchor.line += content_line_offset();
|
2018-03-19 19:54:19 +01:00
|
|
|
|
|
2018-02-22 12:04:04 +01:00
|
|
|
|
LineCount line = anchor.line + 1;
|
2018-06-19 12:20:38 +02:00
|
|
|
|
ColumnCount column = std::max(0_col, std::min(anchor.column, m_dimensions.column - longest - 1));
|
|
|
|
|
if (is_search)
|
|
|
|
|
{
|
|
|
|
|
line = m_status_on_top ? 0_line : m_dimensions.line;
|
|
|
|
|
column = m_dimensions.column / 2;
|
|
|
|
|
}
|
|
|
|
|
else if (not is_inline)
|
2018-02-22 12:04:04 +01:00
|
|
|
|
line = m_status_on_top ? 1_line : m_dimensions.line - height;
|
2018-03-19 19:54:19 +01:00
|
|
|
|
else if (line + height > m_dimensions.line)
|
2018-02-22 12:04:04 +01:00
|
|
|
|
line = anchor.line - height;
|
2013-10-17 19:48:12 +02:00
|
|
|
|
|
2018-06-19 12:20:38 +02:00
|
|
|
|
const auto width = is_search ? m_dimensions.column - m_dimensions.column / 2
|
|
|
|
|
: (is_inline ? min(longest+1, m_dimensions.column)
|
|
|
|
|
: m_dimensions.column);
|
2018-03-13 05:18:06 +01:00
|
|
|
|
m_menu.create({line, column}, {height, width});
|
2018-02-22 12:04:04 +01:00
|
|
|
|
m_menu.selected_item = item_count;
|
2018-06-03 01:08:02 +02:00
|
|
|
|
m_menu.first_item = 0;
|
2018-02-22 12:04:04 +01:00
|
|
|
|
|
2013-03-14 19:19:33 +01:00
|
|
|
|
draw_menu();
|
2016-03-10 00:28:23 +01:00
|
|
|
|
|
|
|
|
|
if (m_info)
|
|
|
|
|
info_show(m_info.title, m_info.content,
|
|
|
|
|
m_info.anchor, m_info.face, m_info.style);
|
2012-08-30 21:53:22 +02:00
|
|
|
|
}
|
2012-08-30 21:14:28 +02:00
|
|
|
|
|
2012-09-24 19:24:27 +02:00
|
|
|
|
void NCursesUI::menu_select(int selected)
|
2012-08-30 21:53:22 +02:00
|
|
|
|
{
|
2016-02-27 18:22:31 +01:00
|
|
|
|
const int item_count = m_menu.items.size();
|
2013-10-17 19:48:12 +02:00
|
|
|
|
if (selected < 0 or selected >= item_count)
|
2012-08-30 21:14:28 +02:00
|
|
|
|
{
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_menu.selected_item = -1;
|
2018-06-03 01:08:02 +02:00
|
|
|
|
m_menu.first_item = 0;
|
2012-08-30 21:14:28 +02:00
|
|
|
|
}
|
2018-06-19 12:20:38 +02:00
|
|
|
|
else if (m_menu.columns == 0) // Do not columnize
|
|
|
|
|
{
|
|
|
|
|
m_menu.selected_item = selected;
|
|
|
|
|
const ColumnCount width = m_menu.size.column - 3;
|
|
|
|
|
int first = 0;
|
|
|
|
|
ColumnCount item_col = 0;
|
|
|
|
|
for (int i = 0; i <= selected; ++i)
|
|
|
|
|
{
|
|
|
|
|
const ColumnCount item_width = m_menu.items[i].length() + 1;
|
|
|
|
|
if (item_col + item_width > width)
|
|
|
|
|
{
|
|
|
|
|
first = i;
|
|
|
|
|
item_col = item_width;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
item_col += item_width;
|
|
|
|
|
}
|
|
|
|
|
m_menu.first_item = first;
|
|
|
|
|
}
|
2012-09-05 19:02:06 +02:00
|
|
|
|
else
|
2013-03-14 19:19:33 +01:00
|
|
|
|
{
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_menu.selected_item = selected;
|
2018-08-10 19:14:48 +02:00
|
|
|
|
const int menu_cols = div_round_up(item_count, (int)m_menu.size.line);
|
|
|
|
|
const int first_col = m_menu.first_item / (int)m_menu.size.line;
|
|
|
|
|
const int selected_col = m_menu.selected_item / (int)m_menu.size.line;
|
|
|
|
|
if (selected_col < first_col)
|
|
|
|
|
m_menu.first_item = selected_col * (int)m_menu.size.line;
|
|
|
|
|
if (selected_col >= first_col + m_menu.columns)
|
|
|
|
|
m_menu.first_item = min(selected_col, menu_cols - m_menu.columns) * (int)m_menu.size.line;
|
2013-03-14 19:19:33 +01:00
|
|
|
|
}
|
|
|
|
|
draw_menu();
|
2012-09-05 19:02:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-24 19:24:27 +02:00
|
|
|
|
void NCursesUI::menu_hide()
|
2012-09-05 19:02:06 +02:00
|
|
|
|
{
|
2015-09-11 00:39:19 +02:00
|
|
|
|
if (not m_menu)
|
2012-09-05 19:02:06 +02:00
|
|
|
|
return;
|
2018-06-03 04:06:29 +02:00
|
|
|
|
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_menu.items.clear();
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.mark_dirty(m_menu.pos.line, m_menu.size.line);
|
2015-09-11 00:39:19 +02:00
|
|
|
|
m_menu.destroy();
|
2014-04-15 20:19:44 +02:00
|
|
|
|
m_dirty = true;
|
2017-04-04 09:36:21 +02:00
|
|
|
|
|
|
|
|
|
// Recompute info as it does not have to avoid the menu anymore
|
|
|
|
|
if (m_info)
|
|
|
|
|
info_show(m_info.title, m_info.content, m_info.anchor, m_info.face, m_info.style);
|
2012-08-30 21:14:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
static DisplayCoord compute_pos(DisplayCoord anchor, DisplayCoord size,
|
2019-09-04 16:04:27 +02:00
|
|
|
|
NCursesUI::Rect rect, NCursesUI::Rect to_avoid,
|
|
|
|
|
bool prefer_above)
|
2012-12-15 18:32:56 +01:00
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
DisplayCoord pos;
|
2014-11-10 14:28:06 +01:00
|
|
|
|
if (prefer_above)
|
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
pos = anchor - DisplayCoord{size.line};
|
2014-11-10 14:28:06 +01:00
|
|
|
|
if (pos.line < 0)
|
|
|
|
|
prefer_above = false;
|
|
|
|
|
}
|
2015-12-05 11:51:46 +01:00
|
|
|
|
auto rect_end = rect.pos + rect.size;
|
2014-11-10 14:28:06 +01:00
|
|
|
|
if (not prefer_above)
|
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
pos = anchor + DisplayCoord{1_line};
|
2015-12-05 11:51:46 +01:00
|
|
|
|
if (pos.line + size.line > rect_end.line)
|
|
|
|
|
pos.line = max(rect.pos.line, anchor.line - size.line);
|
2014-11-10 14:28:06 +01:00
|
|
|
|
}
|
2015-12-05 11:51:46 +01:00
|
|
|
|
if (pos.column + size.column > rect_end.column)
|
|
|
|
|
pos.column = max(rect.pos.column, rect_end.column - size.column);
|
2012-12-15 19:10:59 +01:00
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
if (to_avoid.size != DisplayCoord{})
|
2012-12-15 19:10:59 +01:00
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
DisplayCoord to_avoid_end = to_avoid.pos + to_avoid.size;
|
2012-12-15 19:10:59 +01:00
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
DisplayCoord end = pos + size;
|
2012-12-15 19:10:59 +01:00
|
|
|
|
|
|
|
|
|
// check intersection
|
2015-12-05 11:51:46 +01:00
|
|
|
|
if (not (end.line < to_avoid.pos.line or end.column < to_avoid.pos.column or
|
|
|
|
|
pos.line > to_avoid_end.line or pos.column > to_avoid_end.column))
|
2012-12-15 19:10:59 +01:00
|
|
|
|
{
|
2015-12-05 11:51:46 +01:00
|
|
|
|
pos.line = min(to_avoid.pos.line, anchor.line) - size.line;
|
2012-12-15 19:10:59 +01:00
|
|
|
|
// if above does not work, try below
|
|
|
|
|
if (pos.line < 0)
|
2015-12-05 11:51:46 +01:00
|
|
|
|
pos.line = max(to_avoid_end.line, anchor.line);
|
2012-12-15 19:10:59 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-15 18:32:56 +01:00
|
|
|
|
return pos;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
struct InfoBox
|
|
|
|
|
{
|
|
|
|
|
DisplayCoord size;
|
|
|
|
|
Vector<String> contents;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
InfoBox make_info_box(StringView title, StringView message, ColumnCount max_width,
|
|
|
|
|
ConstArrayView<StringView> assistant)
|
2013-10-10 23:51:16 +02:00
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
DisplayCoord assistant_size;
|
2015-02-09 14:33:54 +01:00
|
|
|
|
if (not assistant.empty())
|
2016-09-22 21:36:26 +02:00
|
|
|
|
assistant_size = { (int)assistant.size(), assistant[0].column_length() };
|
2013-10-10 23:51:16 +02:00
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
InfoBox result{};
|
2015-09-08 00:29:01 +02:00
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
const ColumnCount max_bubble_width = max_width - assistant_size.column - 6;
|
2015-09-08 00:29:01 +02:00
|
|
|
|
if (max_bubble_width < 4)
|
|
|
|
|
return result;
|
|
|
|
|
|
2015-01-09 14:57:21 +01:00
|
|
|
|
Vector<StringView> lines = wrap_lines(message, max_bubble_width);
|
2013-10-10 23:51:16 +02:00
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
|
ColumnCount bubble_width = title.column_length() + 2;
|
2013-10-10 23:51:16 +02:00
|
|
|
|
for (auto& line : lines)
|
2016-09-22 21:36:26 +02:00
|
|
|
|
bubble_width = max(bubble_width, line.column_length());
|
2013-10-10 23:51:16 +02:00
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
auto line_count = max(assistant_size.line-1, LineCount{(int)lines.size()} + 2);
|
|
|
|
|
result.size = DisplayCoord{line_count, bubble_width + assistant_size.column + 4};
|
2017-05-10 12:35:12 +02:00
|
|
|
|
const auto assistant_top_margin = (line_count - assistant_size.line+1) / 2;
|
2013-10-17 19:48:12 +02:00
|
|
|
|
for (LineCount i = 0; i < line_count; ++i)
|
2013-10-10 23:51:16 +02:00
|
|
|
|
{
|
2017-05-28 20:56:47 +02:00
|
|
|
|
String line;
|
2013-10-10 23:51:16 +02:00
|
|
|
|
constexpr Codepoint dash{L'─'};
|
2015-02-09 14:33:54 +01:00
|
|
|
|
if (not assistant.empty())
|
2017-03-14 17:06:09 +01:00
|
|
|
|
{
|
|
|
|
|
if (i >= assistant_top_margin)
|
2017-05-28 20:56:47 +02:00
|
|
|
|
line += assistant[(int)min(i - assistant_top_margin, assistant_size.line-1)];
|
2017-03-14 17:06:09 +01:00
|
|
|
|
else
|
2017-05-28 20:56:47 +02:00
|
|
|
|
line += assistant[(int)assistant_size.line-1];
|
2017-03-14 17:06:09 +01:00
|
|
|
|
}
|
2013-10-10 23:51:16 +02:00
|
|
|
|
if (i == 0)
|
|
|
|
|
{
|
|
|
|
|
if (title.empty())
|
2017-05-28 20:56:47 +02:00
|
|
|
|
line += "╭─" + String{dash, bubble_width} + "─╮";
|
2013-10-10 23:51:16 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
2016-09-22 21:36:26 +02:00
|
|
|
|
auto dash_count = bubble_width - title.column_length() - 2;
|
2013-10-17 19:48:12 +02:00
|
|
|
|
String left{dash, dash_count / 2};
|
|
|
|
|
String right{dash, dash_count - dash_count / 2};
|
2017-05-28 20:56:47 +02:00
|
|
|
|
line += "╭─" + left + "┤" + title +"├" + right +"─╮";
|
2013-10-10 23:51:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (i < lines.size() + 1)
|
|
|
|
|
{
|
2017-05-28 20:56:47 +02:00
|
|
|
|
auto& info_line = lines[(int)i - 1];
|
|
|
|
|
const ColumnCount padding = bubble_width - info_line.column_length();
|
|
|
|
|
line += "│ " + info_line + String{' ', padding} + " │";
|
2013-10-10 23:51:16 +02:00
|
|
|
|
}
|
|
|
|
|
else if (i == lines.size() + 1)
|
2017-05-28 20:56:47 +02:00
|
|
|
|
line += "╰─" + String(dash, bubble_width) + "─╯";
|
2013-10-10 23:51:16 +02:00
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
result.contents.push_back(std::move(line));
|
2013-10-10 23:51:16 +02:00
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
InfoBox make_simple_info_box(StringView contents, ColumnCount max_width)
|
|
|
|
|
{
|
|
|
|
|
InfoBox info_box{};
|
|
|
|
|
for (auto& line : wrap_lines(contents, max_width))
|
|
|
|
|
{
|
|
|
|
|
++info_box.size.line;
|
|
|
|
|
info_box.size.column = std::max(line.column_length(), info_box.size.column);
|
|
|
|
|
info_box.contents.push_back(line.str());
|
|
|
|
|
}
|
|
|
|
|
return info_box;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-30 20:39:52 +02:00
|
|
|
|
void NCursesUI::info_show(StringView title, StringView content,
|
2016-09-22 21:36:26 +02:00
|
|
|
|
DisplayCoord anchor, Face face, InfoStyle style)
|
2012-12-14 19:04:34 +01:00
|
|
|
|
{
|
2015-05-07 01:34:30 +02:00
|
|
|
|
info_hide();
|
2012-12-14 19:04:34 +01:00
|
|
|
|
|
2016-02-27 18:22:31 +01:00
|
|
|
|
m_info.title = title.str();
|
|
|
|
|
m_info.content = content.str();
|
|
|
|
|
m_info.anchor = anchor;
|
|
|
|
|
m_info.face = face;
|
|
|
|
|
m_info.style = style;
|
2015-10-13 14:58:24 +02:00
|
|
|
|
|
2018-06-03 04:06:29 +02:00
|
|
|
|
const Rect rect = {content_line_offset(), m_dimensions};
|
2018-03-18 15:23:32 +01:00
|
|
|
|
InfoBox info_box;
|
2014-11-08 18:59:38 +01:00
|
|
|
|
if (style == InfoStyle::Prompt)
|
2014-04-30 20:39:52 +02:00
|
|
|
|
{
|
2018-03-18 15:23:32 +01:00
|
|
|
|
info_box = make_info_box(m_info.title, m_info.content, m_dimensions.column, m_assistant);
|
|
|
|
|
anchor = DisplayCoord{m_status_on_top ? 0 : m_dimensions.line, m_dimensions.column-1};
|
|
|
|
|
anchor = compute_pos(anchor, info_box.size, rect, m_menu, style == InfoStyle::InlineAbove);
|
2014-04-30 20:39:52 +02:00
|
|
|
|
}
|
2017-01-04 12:24:07 +01:00
|
|
|
|
else if (style == InfoStyle::Modal)
|
2014-11-20 14:56:37 +01:00
|
|
|
|
{
|
2018-03-18 15:23:32 +01:00
|
|
|
|
info_box = make_info_box(m_info.title, m_info.content, m_dimensions.column, {});
|
|
|
|
|
auto half = [](const DisplayCoord& c) { return DisplayCoord{c.line / 2, c.column / 2}; };
|
|
|
|
|
anchor = rect.pos + half(rect.size) - half(info_box.size);
|
|
|
|
|
}
|
|
|
|
|
else if (style == InfoStyle::MenuDoc)
|
|
|
|
|
{
|
|
|
|
|
if (not m_menu)
|
|
|
|
|
return;
|
2014-11-20 14:56:37 +01:00
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
const auto right_max_width = m_dimensions.column - (m_menu.pos.column + m_menu.size.column);
|
|
|
|
|
const auto left_max_width = m_menu.pos.column;
|
|
|
|
|
const auto max_width = std::max(right_max_width, left_max_width);
|
2015-09-08 00:15:21 +02:00
|
|
|
|
if (max_width < 4)
|
|
|
|
|
return;
|
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
info_box = make_simple_info_box(m_info.content, max_width);
|
|
|
|
|
anchor.line = m_menu.pos.line;
|
|
|
|
|
if (info_box.size.column <= right_max_width or right_max_width >= left_max_width)
|
|
|
|
|
anchor.column = m_menu.pos.column + m_menu.size.column;
|
|
|
|
|
else
|
|
|
|
|
anchor.column = m_menu.pos.column - info_box.size.column;
|
2014-11-20 14:56:37 +01:00
|
|
|
|
}
|
2018-03-18 15:23:32 +01:00
|
|
|
|
else
|
2017-01-04 12:24:07 +01:00
|
|
|
|
{
|
2018-03-18 15:23:32 +01:00
|
|
|
|
const ColumnCount max_width = m_dimensions.column - anchor.column;
|
|
|
|
|
if (max_width < 4)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
info_box = make_simple_info_box(m_info.content, max_width);
|
|
|
|
|
anchor = compute_pos(anchor, info_box.size, rect, m_menu, style == InfoStyle::InlineAbove);
|
|
|
|
|
|
2018-06-03 04:06:29 +02:00
|
|
|
|
anchor.line += content_line_offset();
|
2017-01-04 12:24:07 +01:00
|
|
|
|
}
|
2012-12-14 19:04:34 +01:00
|
|
|
|
|
2015-12-05 11:51:46 +01:00
|
|
|
|
// The info box does not fit
|
2018-03-18 15:23:32 +01:00
|
|
|
|
if (anchor < rect.pos or anchor + info_box.size > rect.pos + rect.size)
|
2015-04-30 14:45:05 +02:00
|
|
|
|
return;
|
|
|
|
|
|
2018-03-18 15:23:32 +01:00
|
|
|
|
m_info.create(anchor, info_box.size);
|
2012-12-14 19:04:34 +01:00
|
|
|
|
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_info.set_background_color(m_palette, face);
|
2018-03-18 15:23:32 +01:00
|
|
|
|
for (auto line = 0_line; line < info_box.size.line; ++line)
|
2013-05-16 21:46:15 +02:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_info.move_cursor(line);
|
|
|
|
|
m_info.clear_to_end_of_line();
|
|
|
|
|
m_info.add_str(info_box.contents[(int)line]);
|
2013-05-16 21:46:15 +02:00
|
|
|
|
}
|
2014-04-15 20:19:44 +02:00
|
|
|
|
m_dirty = true;
|
2012-12-14 19:04:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NCursesUI::info_hide()
|
|
|
|
|
{
|
2015-09-11 00:39:19 +02:00
|
|
|
|
if (not m_info)
|
2012-12-14 19:04:34 +01:00
|
|
|
|
return;
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.mark_dirty(m_info.pos.line, m_info.size.line);
|
2015-09-11 00:39:19 +02:00
|
|
|
|
m_info.destroy();
|
2014-04-15 20:19:44 +02:00
|
|
|
|
m_dirty = true;
|
2012-12-14 19:04:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-29 22:35:53 +01:00
|
|
|
|
void NCursesUI::set_on_key(OnKeyCallback callback)
|
2012-10-20 20:15:20 +02:00
|
|
|
|
{
|
2016-11-29 22:35:53 +01:00
|
|
|
|
m_on_key = std::move(callback);
|
2018-06-03 04:06:29 +02:00
|
|
|
|
EventManager::instance().force_signal(0);
|
2012-10-20 20:15:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-29 22:35:53 +01:00
|
|
|
|
DisplayCoord NCursesUI::dimensions()
|
2013-01-11 19:17:21 +01:00
|
|
|
|
{
|
2018-06-19 12:20:38 +02:00
|
|
|
|
return m_dimensions;
|
2018-06-03 04:06:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LineCount NCursesUI::content_line_offset() const
|
|
|
|
|
{
|
2018-06-19 12:20:38 +02:00
|
|
|
|
return m_status_on_top ? 1 : 0;
|
2013-01-11 19:17:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-19 12:20:38 +02:00
|
|
|
|
void NCursesUI::set_resize_pending()
|
2018-06-08 10:54:11 +02:00
|
|
|
|
{
|
2018-06-19 12:20:38 +02:00
|
|
|
|
m_resize_pending = true;
|
2018-06-08 10:54:11 +02:00
|
|
|
|
EventManager::instance().force_signal(0);
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-12 01:28:22 +02:00
|
|
|
|
void NCursesUI::abort()
|
|
|
|
|
{
|
|
|
|
|
endwin();
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-02 14:52:41 +02:00
|
|
|
|
void NCursesUI::enable_mouse(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
if (enabled == m_mouse_enabled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_mouse_enabled = enabled;
|
|
|
|
|
if (enabled)
|
|
|
|
|
{
|
|
|
|
|
mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, nullptr);
|
|
|
|
|
mouseinterval(0);
|
2019-09-07 07:34:58 +02:00
|
|
|
|
// force SGR mode
|
|
|
|
|
if (m_builtin_key_parser)
|
|
|
|
|
fputs("\033[?1006h", stdout);
|
2015-10-02 14:52:41 +02:00
|
|
|
|
// force enable report focus events
|
2016-06-21 01:47:46 +02:00
|
|
|
|
fputs("\033[?1004h", stdout);
|
2019-09-07 07:34:58 +02:00
|
|
|
|
// enable mouse
|
|
|
|
|
fputs("\033[?1000h", stdout);
|
|
|
|
|
// force enable report mouse position
|
|
|
|
|
fputs("\033[?1002h", stdout);
|
2015-10-02 14:52:41 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mousemask(0, nullptr);
|
2016-06-21 01:47:46 +02:00
|
|
|
|
fputs("\033[?1002l", stdout);
|
2019-09-07 07:34:58 +02:00
|
|
|
|
fputs("\033[?1000l", stdout);
|
|
|
|
|
fputs("\033[?1004l", stdout);
|
2019-09-07 14:33:06 +02:00
|
|
|
|
fputs("\033[?1006l", stdout);
|
2015-10-02 14:52:41 +02:00
|
|
|
|
}
|
2016-06-20 21:56:38 +02:00
|
|
|
|
fflush(stdout);
|
2015-10-02 14:52:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-11 00:29:16 +01:00
|
|
|
|
void NCursesUI::set_ui_options(const Options& options)
|
|
|
|
|
{
|
2015-02-05 23:40:08 +01:00
|
|
|
|
{
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto it = options.find("ncurses_assistant"_sv);
|
2015-09-16 20:57:57 +02:00
|
|
|
|
if (it == options.end() or it->value == "clippy")
|
2015-04-06 21:05:49 +02:00
|
|
|
|
m_assistant = assistant_clippy;
|
2015-09-16 20:57:57 +02:00
|
|
|
|
else if (it->value == "cat")
|
2015-04-06 21:05:49 +02:00
|
|
|
|
m_assistant = assistant_cat;
|
2017-03-08 17:45:15 +01:00
|
|
|
|
else if (it->value == "dilbert")
|
|
|
|
|
m_assistant = assistant_dilbert;
|
2015-09-16 20:57:57 +02:00
|
|
|
|
else if (it->value == "none" or it->value == "off")
|
2015-04-06 21:05:49 +02:00
|
|
|
|
m_assistant = ConstArrayView<StringView>{};
|
2015-02-05 23:40:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto it = options.find("ncurses_status_on_top"_sv);
|
2015-09-03 01:03:07 +02:00
|
|
|
|
m_status_on_top = it != options.end() and
|
2015-09-16 20:57:57 +02:00
|
|
|
|
(it->value == "yes" or it->value == "true");
|
2015-09-03 01:03:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto it = options.find("ncurses_set_title"_sv);
|
2015-09-03 01:03:07 +02:00
|
|
|
|
m_set_title = it == options.end() or
|
2015-09-16 20:57:57 +02:00
|
|
|
|
(it->value == "yes" or it->value == "true");
|
2015-02-05 23:40:08 +01:00
|
|
|
|
}
|
2015-04-05 19:43:27 +02:00
|
|
|
|
|
2018-04-11 12:29:35 +02:00
|
|
|
|
{
|
|
|
|
|
auto it = options.find("ncurses_shift_function_key"_sv);
|
|
|
|
|
m_shift_function_key = it != options.end() ?
|
|
|
|
|
str_to_int_ifp(it->value).value_or(default_shift_function_key)
|
|
|
|
|
: default_shift_function_key;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-30 08:01:13 +01:00
|
|
|
|
{
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto it = options.find("ncurses_change_colors"_sv);
|
2019-09-04 16:04:27 +02:00
|
|
|
|
if (m_palette.set_change_colors(it == options.end() or
|
|
|
|
|
(it->value == "yes" or it->value == "true")))
|
2016-12-30 08:01:13 +01:00
|
|
|
|
{
|
2019-09-04 16:04:27 +02:00
|
|
|
|
m_window.m_active_pair = -1;
|
|
|
|
|
m_menu.m_active_pair = -1;
|
|
|
|
|
m_info.m_active_pair = -1;
|
2016-12-30 08:01:13 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-05 19:43:27 +02:00
|
|
|
|
{
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto enable_mouse_it = options.find("ncurses_enable_mouse"_sv);
|
2015-10-02 14:52:41 +02:00
|
|
|
|
enable_mouse(enable_mouse_it == options.end() or
|
|
|
|
|
enable_mouse_it->value == "yes" or
|
|
|
|
|
enable_mouse_it->value == "true");
|
|
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto wheel_up_it = options.find("ncurses_wheel_up_button"_sv);
|
2015-09-11 14:20:37 +02:00
|
|
|
|
m_wheel_up_button = wheel_up_it != options.end() ?
|
2015-09-16 20:57:57 +02:00
|
|
|
|
str_to_int_ifp(wheel_up_it->value).value_or(4) : 4;
|
2015-11-05 14:28:58 +01:00
|
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
|
auto wheel_down_it = options.find("ncurses_wheel_down_button"_sv);
|
2015-11-05 14:28:58 +01:00
|
|
|
|
m_wheel_down_button = wheel_down_it != options.end() ?
|
|
|
|
|
str_to_int_ifp(wheel_down_it->value).value_or(5) : 5;
|
2019-08-19 14:16:39 +02:00
|
|
|
|
|
|
|
|
|
auto wheel_scroll_amount_it = options.find("ncurses_wheel_scroll_amount"_sv);
|
|
|
|
|
m_wheel_scroll_amount = wheel_scroll_amount_it != options.end() ?
|
|
|
|
|
str_to_int_ifp(wheel_scroll_amount_it->value).value_or(3) : 3;
|
2015-04-05 19:43:27 +02:00
|
|
|
|
}
|
2018-12-09 11:04:22 +01:00
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto builtin_key_parser_it = options.find("ncurses_builtin_key_parser"_sv);
|
2019-09-07 07:34:58 +02:00
|
|
|
|
bool mouse_enabled = m_mouse_enabled;
|
|
|
|
|
enable_mouse(false);
|
2018-12-09 11:04:22 +01:00
|
|
|
|
m_builtin_key_parser = builtin_key_parser_it != options.end() and
|
|
|
|
|
(builtin_key_parser_it->value == "yes" or
|
|
|
|
|
builtin_key_parser_it->value == "true");
|
2019-09-04 16:04:27 +02:00
|
|
|
|
keypad(m_window.win, not m_builtin_key_parser);
|
2019-09-07 07:34:58 +02:00
|
|
|
|
enable_mouse(mouse_enabled);
|
2018-12-09 11:04:22 +01:00
|
|
|
|
}
|
2014-11-11 00:29:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2012-02-16 15:25:16 +01:00
|
|
|
|
}
|