Update list of wide characters
[centerim5.git] / cppconsui / ConsUICurses.cpp
blob157a8becf7dddbb6e17896f1b53733ce1d51a713
1 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
2 //
3 // This file is part of CenterIM.
4 //
5 // CenterIM is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // CenterIM is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
18 /// @file
19 /// Hidden implementation of curses specific functions.
20 ///
21 /// @ingroup cppconsui
23 #include "ConsUICurses.h"
25 // Define _XOPEN_SOURCE_EXTENDED to get wide character support.
26 #ifndef _XOPEN_SOURCE_EXTENDED
27 #define _XOPEN_SOURCE_EXTENDED
28 #endif
30 #define NCURSES_NOMACROS
31 #include <curses.h>
33 #include "gettext.h"
34 #include <algorithm>
35 #include <cassert>
36 #include <cstring>
38 namespace CppConsUI {
40 namespace Curses {
42 namespace {
44 SCREEN *screen = nullptr;
45 int screen_width = 0;
46 int screen_height = 0;
47 bool ascii_mode = false;
49 void updateScreenSize()
51 screen_width = ::getmaxx(stdscr);
52 assert(screen_width != ERR);
53 screen_height = ::getmaxy(stdscr);
54 assert(screen_height != ERR);
57 } // anonymous namespace
59 ViewPort::ViewPort(int screen_x, int screen_y, int view_x, int view_y,
60 int view_width, int view_height)
61 : screen_x_(screen_x), screen_y_(screen_y), view_x_(view_x), view_y_(view_y),
62 view_width_(view_width), view_height_(view_height)
66 int ViewPort::addString(
67 int x, int y, int w, const char *str, Error &error, int *printed)
69 assert(str != nullptr);
71 int res = 0;
72 int p = 0;
73 while (p < w && str != nullptr && *str != '\0') {
74 int out;
75 if ((res = addChar(x + p, y, UTF8::getUniChar(str), error, &out)) != 0)
76 break;
77 p += out;
78 str = UTF8::getNextChar(str);
81 if (printed != nullptr)
82 *printed = p;
84 return res;
87 int ViewPort::addString(
88 int x, int y, const char *str, Error &error, int *printed)
90 assert(str != nullptr);
92 int res = 0;
93 int p = 0;
94 while (str != nullptr && *str != '\0') {
95 int out;
96 if ((res = addChar(x + p, y, UTF8::getUniChar(str), error, &out)) != 0)
97 break;
98 p += out;
99 str = UTF8::getNextChar(str);
102 if (printed != nullptr)
103 *printed = p;
105 return res;
108 int ViewPort::addString(int x, int y, int w, const char *str, const char *end,
109 Error &error, int *printed)
111 assert(str != nullptr);
112 assert(end != nullptr);
114 int res = 0;
115 int p = 0;
116 while (p < w && str != nullptr && str < end && *str != '\0') {
117 int out;
118 if ((res = addChar(x + p, y, UTF8::getUniChar(str), error, &out)) != 0)
119 break;
120 p += out;
121 str = UTF8::findNextChar(str, end);
124 if (printed != nullptr)
125 *printed = p;
127 return res;
130 int ViewPort::addString(
131 int x, int y, const char *str, const char *end, Error &error, int *printed)
133 assert(str != nullptr);
134 assert(end != nullptr);
136 int res = 0;
137 int p = 0;
138 while (str != nullptr && str < end && *str != '\0') {
139 int out;
140 if ((res = addChar(x + p, y, UTF8::getUniChar(str), error, &out)) != 0)
141 break;
142 p += out;
143 str = UTF8::findNextChar(str, end);
146 if (printed != nullptr)
147 *printed = p;
149 return res;
152 int ViewPort::addChar(
153 int x, int y, UTF8::UniChar uc, Error &error, int *printed)
155 if (printed)
156 *printed = 0;
158 int draw_x = screen_x_ + (x - view_x_);
159 int draw_y = screen_y_ + (y - view_y_);
160 chtype ch;
162 // Filter out C1 (8-bit) control characters.
163 if (uc >= 0x7f && uc < 0xa0) {
164 if (isInViewPort(x, y, 1)) {
165 ch = '?';
166 if (::mvaddchnstr(draw_y, draw_x, &ch, 1) == ERR) {
167 error = Error(ERROR_CURSES_ADD_CHARACTER);
168 error.setFormattedString(
169 _("Adding character '?' on screen at position (x=%d, y=%d) failed."),
170 draw_x, draw_y);
171 return error.getCode();
174 if (printed != nullptr)
175 *printed = 1;
176 return 0;
179 // Handle tab characters.
180 if (uc == '\t') {
181 int w = onScreenWidth(uc);
182 for (int i = 0; i < w; ++i) {
183 if (isInViewPort(x + i, y, 1)) {
184 ch = ' ';
185 if (::mvaddchnstr(draw_y, draw_x + i, &ch, 1) == ERR) {
186 error = Error(ERROR_CURSES_ADD_CHARACTER);
187 error.setFormattedString(
188 _("Adding character ' ' on screen at position (x=%d, y=%d) "
189 "failed."),
190 draw_x, draw_y);
191 return error.getCode();
194 if (printed != nullptr)
195 ++(*printed);
197 return 0;
200 // Make control chars printable.
201 if (uc < 32)
202 uc += 0x2400;
204 // Create a wide-character string accepted by ncurses.
205 wchar_t wch[2];
206 wch[0] = uc;
207 wch[1] = '\0';
209 int w = onScreenWidth(uc);
210 if (isInViewPort(x, y, w)) {
211 cchar_t cc;
213 if (::setcchar(&cc, wch, A_NORMAL, 0, nullptr) == ERR) {
214 error = Error(ERROR_CURSES_ADD_CHARACTER);
215 error.setFormattedString(
216 _("Setting complex character from Unicode character "
217 "#%" UNICHAR_FORMAT "failed."),
218 uc);
219 return error.getCode();
221 if (::mvadd_wchnstr(draw_y, draw_x, &cc, 1) == ERR) {
222 error.setFormattedString(
223 _("Adding Unicode character #%" UNICHAR_FORMAT " on screen at position "
224 "(x=%d, y=%d) failed."),
225 uc, draw_x, draw_y);
226 return error.getCode();
229 if (printed != nullptr)
230 *printed = w;
231 return 0;
234 int ViewPort::addLineChar(int x, int y, LineChar c, Error &error)
236 if (!isInViewPort(x, y, 1))
237 return 0;
239 cchar_t cc;
240 cchar_t *ccp = nullptr;
242 if (ascii_mode) {
243 char ch = '\0';
245 switch (c) {
246 case LINE_HLINE:
247 ch = '-';
248 break;
249 case LINE_VLINE:
250 ch = '|';
251 break;
252 case LINE_LLCORNER:
253 case LINE_LRCORNER:
254 case LINE_ULCORNER:
255 case LINE_URCORNER:
256 case LINE_BTEE:
257 case LINE_LTEE:
258 case LINE_RTEE:
259 case LINE_TTEE:
260 ch = '+';
261 break;
262 case LINE_DARROW:
263 ch = 'v';
264 break;
265 case LINE_LARROW:
266 ch = '<';
267 break;
268 case LINE_RARROW:
269 ch = '>';
270 break;
271 case LINE_UARROW:
272 ch = '^';
273 break;
274 case LINE_BULLET:
275 ch = 'o';
276 break;
278 assert(ch != '\0');
280 wchar_t wch[2];
281 wch[0] = ch;
282 wch[1] = '\0';
284 if (::setcchar(&cc, wch, A_NORMAL, 0, nullptr) == ERR) {
285 error = Error(ERROR_CURSES_ADD_CHARACTER);
286 error.setFormattedString(
287 _("Setting complex character from character '%c' failed."), ch);
288 return error.getCode();
291 ccp = &cc;
293 else {
294 // Non-ASCII mode.
295 switch (c) {
296 case LINE_HLINE:
297 ccp = WACS_HLINE;
298 break;
299 case LINE_VLINE:
300 ccp = WACS_VLINE;
301 break;
302 case LINE_LLCORNER:
303 ccp = WACS_LLCORNER;
304 break;
305 case LINE_LRCORNER:
306 ccp = WACS_LRCORNER;
307 break;
308 case LINE_ULCORNER:
309 ccp = WACS_ULCORNER;
310 break;
311 case LINE_URCORNER:
312 ccp = WACS_URCORNER;
313 break;
314 case LINE_BTEE:
315 ccp = WACS_BTEE;
316 break;
317 case LINE_LTEE:
318 ccp = WACS_LTEE;
319 break;
320 case LINE_RTEE:
321 ccp = WACS_RTEE;
322 break;
323 case LINE_TTEE:
324 ccp = WACS_TTEE;
325 break;
326 case LINE_DARROW:
327 ccp = WACS_DARROW;
328 break;
329 case LINE_LARROW:
330 ccp = WACS_LARROW;
331 break;
332 case LINE_RARROW:
333 ccp = WACS_RARROW;
334 break;
335 case LINE_UARROW:
336 ccp = WACS_UARROW;
337 break;
338 case LINE_BULLET:
339 ccp = WACS_BULLET;
340 break;
344 assert(ccp != nullptr);
346 int draw_x = screen_x_ + (x - view_x_);
347 int draw_y = screen_y_ + (y - view_y_);
349 if (::mvadd_wchnstr(draw_y, draw_x, ccp, 1) == OK)
350 return 0;
352 const char *name = nullptr;
353 switch (c) {
354 case LINE_HLINE:
355 name = "HLINE";
356 break;
357 case LINE_VLINE:
358 name = "VLINE";
359 break;
360 case LINE_LLCORNER:
361 name = "LLCORNER";
362 break;
363 case LINE_LRCORNER:
364 name = "LRCORNER";
365 break;
366 case LINE_ULCORNER:
367 name = "ULCORNER";
368 break;
369 case LINE_URCORNER:
370 name = "URCORNER";
371 break;
372 case LINE_BTEE:
373 name = "BTEE";
374 break;
375 case LINE_LTEE:
376 name = "LTEE";
377 break;
378 case LINE_RTEE:
379 name = "RTEE";
380 break;
381 case LINE_TTEE:
382 name = "TTEE";
383 break;
384 case LINE_DARROW:
385 name = "DARROW";
386 break;
387 case LINE_LARROW:
388 name = "LARROW";
389 break;
390 case LINE_RARROW:
391 name = "RARROW";
392 break;
393 case LINE_UARROW:
394 name = "UARROW";
395 break;
396 case LINE_BULLET:
397 name = "BULLET";
398 break;
400 assert(name != nullptr);
402 error = Error(ERROR_CURSES_ADD_CHARACTER);
403 error.setFormattedString(
404 _("Adding line character %s on screen at position (x=%d, y=%d) failed."),
405 name, draw_x, draw_y);
406 return error.getCode();
409 int ViewPort::attrOn(int attrs, Error &error)
411 if (::attron(attrs) == OK)
412 return 0;
414 error = Error(ERROR_CURSES_ATTR);
415 error.setFormattedString(
416 _("Turning on window attributes '%#x' failed."), attrs);
417 return error.getCode();
420 int ViewPort::attrOff(int attrs, Error &error)
422 if (::attroff(attrs) == OK)
423 return 0;
425 error = Error(ERROR_CURSES_ATTR);
426 error.setFormattedString(
427 _("Turning off window attributes '%#x' failed."), attrs);
428 return error.getCode();
431 int ViewPort::changeAt(int x, int y, int n, /* attr_t */ unsigned long attr,
432 short color, Error &error)
434 for (int i = 0; i < n; ++i) {
435 if (!isInViewPort(x + i, y, 1))
436 continue;
438 int draw_x = screen_x_ + (x + i - view_x_);
439 int draw_y = screen_y_ + (y - view_y_);
440 if (::mvchgat(draw_y, draw_x, 1, attr, color, nullptr) == ERR) {
441 error = Error(ERROR_CURSES_ATTR);
442 error.setFormattedString(
443 _("Changing window attributes to '%#lx' and color pair to '%d' on "
444 "screen at position (x=%d, y=%d) failed."),
445 attr, color, draw_x, draw_y);
446 return error.getCode();
449 return 0;
452 int ViewPort::fill(int attrs, Error &error)
454 return fill(attrs, 0, 0, view_width_, view_height_, error);
457 int ViewPort::fill(int attrs, int x, int y, int w, int h, Error &error)
459 attr_t battrs;
460 short pair;
462 if (::attr_get(&battrs, &pair, nullptr) == ERR) {
463 error = Error(ERROR_CURSES_ATTR, _("Obtaining window attributes failed."));
464 return error.getCode();
467 if (attrOn(attrs, error) != 0)
468 return error.getCode();
470 for (int i = 0; i < h; ++i)
471 for (int j = 0; j < w; ++j)
472 if (addChar(x + j, y + i, ' ', error) != 0)
473 return error.getCode();
475 if (::attr_set(battrs, pair, nullptr) == ERR) {
476 error = Error(ERROR_CURSES_ATTR);
477 error.setFormattedString(
478 _("Setting window attributes to '%#lx' and color pair to '%d' failed."),
479 static_cast<unsigned long>(battrs), pair);
480 return error.getCode();
483 return 0;
486 int ViewPort::erase(Error &error)
488 return fill(0, error);
491 void ViewPort::scroll(int scroll_x, int scroll_y)
493 view_x_ += scroll_x;
494 view_y_ += scroll_y;
497 bool ViewPort::isInViewPort(int x, int y, int w)
499 // Check that the given area fits in the view port.
500 return x >= view_x_ && y >= view_y_ && x + w <= view_x_ + view_width_ &&
501 y < view_y_ + view_height_;
504 const int Color::DEFAULT = -1;
505 const int Color::BLACK = COLOR_BLACK;
506 const int Color::RED = COLOR_RED;
507 const int Color::GREEN = COLOR_GREEN;
508 const int Color::YELLOW = COLOR_YELLOW;
509 const int Color::BLUE = COLOR_BLUE;
510 const int Color::MAGENTA = COLOR_MAGENTA;
511 const int Color::CYAN = COLOR_CYAN;
512 const int Color::WHITE = COLOR_WHITE;
514 const int Attr::NORMAL = A_NORMAL;
515 const int Attr::STANDOUT = A_STANDOUT;
516 const int Attr::REVERSE = A_REVERSE;
517 const int Attr::BLINK = A_BLINK;
518 const int Attr::DIM = A_DIM;
519 const int Attr::BOLD = A_BOLD;
521 int initScreen(Error &error)
523 assert(screen == nullptr);
525 screen = ::newterm(nullptr, stdout, stdin);
526 if (screen == nullptr) {
527 error = Error(ERROR_CURSES_INITIALIZATION,
528 _("Initialization of the terminal for Curses session failed."));
529 return error.getCode();
532 if (::has_colors()) {
533 if (::start_color() == ERR) {
534 error = Error(ERROR_CURSES_INITIALIZATION,
535 _("Initialization of color support failed."));
536 goto error_out;
538 if (::use_default_colors() == ERR) {
539 error = Error(ERROR_CURSES_INITIALIZATION,
540 _("Initialization of default colors failed."));
541 goto error_out;
544 if (::curs_set(0) == ERR) {
545 error = Error(ERROR_CURSES_INITIALIZATION, _("Hiding the cursor failed."));
546 goto error_out;
548 if (::nonl() == ERR) {
549 error = Error(
550 ERROR_CURSES_INITIALIZATION, _("Disabling newline translation failed."));
551 goto error_out;
553 if (::raw() == ERR) {
554 error = Error(ERROR_CURSES_INITIALIZATION,
555 _("Placing the terminal into raw mode failed."));
556 goto error_out;
559 updateScreenSize();
561 return 0;
563 error_out:
564 // Try to destroy the already created screen.
565 ::endwin();
566 ::delscreen(screen);
567 screen = nullptr;
569 return error.getCode();
572 int finalizeScreen(Error &error)
574 assert(screen != nullptr);
576 // Note: This function can fail in three places: clear(), refresh() and
577 // endwin(). The first two are non-critical and the function proceeds even if
578 // they occur. Error in endwin() is potentially serious and should always
579 // override any error from clear() or refresh().
581 bool has_error = false;
583 // Clear the screen.
584 if (clear(error) != 0)
585 has_error = true;
586 if (refresh(error) != 0)
587 has_error = true;
589 if (::endwin() == ERR) {
590 error = Error(
591 ERROR_CURSES_FINALIZATION, _("Finalization of Curses session failed."));
592 has_error = true;
595 ::delscreen(screen);
596 screen = nullptr;
598 return has_error ? error.getCode() : 0;
601 void setAsciiMode(bool enabled)
603 ascii_mode = enabled;
606 bool getAsciiMode()
608 return ascii_mode;
611 bool initColorPair(int idx, int fg, int bg, int *res, Error &error)
613 assert(res != nullptr);
615 int color_pair_count = Curses::getColorPairCount();
616 if (idx > color_pair_count) {
617 error = Error(ERROR_CURSES_COLOR_LIMIT);
618 error.setFormattedString(
619 _("Adding of color pair '%d' (foreground=%d, background=%d) failed "
620 "because color pair limit of '%d' was exceeded."),
621 idx, fg, bg, color_pair_count);
622 return error.getCode();
625 if (::init_pair(idx, fg, bg) == ERR) {
626 error = Error(ERROR_CURSES_COLOR_INIT);
627 error.setFormattedString(
628 _("Initialization of color pair '%d' to (foreground=%d, background=%d) "
629 "failed."),
630 idx, fg, bg);
631 return error.getCode();
634 *res = COLOR_PAIR(idx);
635 return 0;
638 int getColorCount()
640 return COLORS;
643 int getColorPairCount()
645 #ifndef NCURSES_EXT_COLORS
646 // Ncurses reports more than 256 color pairs, even when compiled without
647 // ext-color.
648 return std::min(COLOR_PAIRS, 256);
649 #else
650 return COLOR_PAIRS;
651 #endif
654 int erase(Error &error)
656 if (::erase() == ERR) {
657 error = Error(ERROR_CURSES_CLEAR, _("Erasing the screen failed."));
658 return error.getCode();
660 return 0;
663 int clear(Error &error)
665 if (::clear() == ERR) {
666 error = Error(ERROR_CURSES_CLEAR, _("Clearing the screen failed."));
667 return error.getCode();
669 return 0;
672 int refresh(Error &error)
674 if (::refresh() == ERR) {
675 error = Error(ERROR_CURSES_REFRESH, _("Refreshing the screen failed."));
676 return error.getCode();
678 return 0;
681 int beep(Error &error)
683 if (::beep() == ERR) {
684 error = Error(ERROR_CURSES_BEEP, _("Producing beep alert failed."));
685 return error.getCode();
687 return 0;
690 int getWidth()
692 return screen_width;
695 int getHeight()
697 return screen_height;
700 int resizeTerm(int width, int height, Error &error)
702 if (::resizeterm(height, width) == ERR) {
703 error = Error(ERROR_CURSES_RESIZE);
704 error.setFormattedString(
705 _("Changing the Curses terminal size to (width=%d, height=%d) failed."),
706 width, height);
707 return error.getCode();
710 updateScreenSize();
712 return 0;
715 int onScreenWidth(const char *start, const char *end)
717 int width = 0;
719 if (start == nullptr)
720 return 0;
722 if (end == nullptr)
723 end = start + std::strlen(start);
725 while (start < end) {
726 width += onScreenWidth(UTF8::getUniChar(start));
727 start = UTF8::getNextChar(start);
729 return width;
732 int onScreenWidth(UTF8::UniChar uc, int w)
734 if (uc == '\t')
735 return 8 - w % 8;
736 return UTF8::isUniCharWide(uc) ? 2 : 1;
739 } // namespace Curses
741 } // namespace CppConsUI
743 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: