1 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
3 // This file is part of CenterIM.
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.
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/>.
19 /// Hidden implementation of curses specific functions.
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
30 #define NCURSES_NOMACROS
44 SCREEN
*screen
= nullptr;
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);
73 while (p
< w
&& str
!= nullptr && *str
!= '\0') {
75 if ((res
= addChar(x
+ p
, y
, UTF8::getUniChar(str
), error
, &out
)) != 0)
78 str
= UTF8::getNextChar(str
);
81 if (printed
!= nullptr)
87 int ViewPort::addString(
88 int x
, int y
, const char *str
, Error
&error
, int *printed
)
90 assert(str
!= nullptr);
94 while (str
!= nullptr && *str
!= '\0') {
96 if ((res
= addChar(x
+ p
, y
, UTF8::getUniChar(str
), error
, &out
)) != 0)
99 str
= UTF8::getNextChar(str
);
102 if (printed
!= nullptr)
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);
116 while (p
< w
&& str
!= nullptr && str
< end
&& *str
!= '\0') {
118 if ((res
= addChar(x
+ p
, y
, UTF8::getUniChar(str
), error
, &out
)) != 0)
121 str
= UTF8::findNextChar(str
, end
);
124 if (printed
!= nullptr)
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);
138 while (str
!= nullptr && str
< end
&& *str
!= '\0') {
140 if ((res
= addChar(x
+ p
, y
, UTF8::getUniChar(str
), error
, &out
)) != 0)
143 str
= UTF8::findNextChar(str
, end
);
146 if (printed
!= nullptr)
152 int ViewPort::addChar(
153 int x
, int y
, UTF8::UniChar uc
, Error
&error
, int *printed
)
158 int draw_x
= screen_x_
+ (x
- view_x_
);
159 int draw_y
= screen_y_
+ (y
- view_y_
);
162 // Filter out C1 (8-bit) control characters.
163 if (uc
>= 0x7f && uc
< 0xa0) {
164 if (isInViewPort(x
, y
, 1)) {
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."),
171 return error
.getCode();
174 if (printed
!= nullptr)
179 // Handle tab characters.
181 int w
= onScreenWidth(uc
);
182 for (int i
= 0; i
< w
; ++i
) {
183 if (isInViewPort(x
+ i
, y
, 1)) {
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) "
191 return error
.getCode();
194 if (printed
!= nullptr)
200 // Make control chars printable.
204 // Create a wide-character string accepted by ncurses.
209 int w
= onScreenWidth(uc
);
210 if (isInViewPort(x
, y
, w
)) {
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."),
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."),
226 return error
.getCode();
229 if (printed
!= nullptr)
234 int ViewPort::addLineChar(int x
, int y
, LineChar c
, Error
&error
)
236 if (!isInViewPort(x
, y
, 1))
240 cchar_t
*ccp
= nullptr;
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();
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
)
352 const char *name
= nullptr;
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
)
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
)
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))
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();
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
)
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();
486 int ViewPort::erase(Error
&error
)
488 return fill(0, error
);
491 void ViewPort::scroll(int scroll_x
, int 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."));
538 if (::use_default_colors() == ERR
) {
539 error
= Error(ERROR_CURSES_INITIALIZATION
,
540 _("Initialization of default colors failed."));
544 if (::curs_set(0) == ERR
) {
545 error
= Error(ERROR_CURSES_INITIALIZATION
, _("Hiding the cursor failed."));
548 if (::nonl() == ERR
) {
550 ERROR_CURSES_INITIALIZATION
, _("Disabling newline translation failed."));
553 if (::raw() == ERR
) {
554 error
= Error(ERROR_CURSES_INITIALIZATION
,
555 _("Placing the terminal into raw mode failed."));
564 // Try to destroy the already created screen.
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;
584 if (clear(error
) != 0)
586 if (refresh(error
) != 0)
589 if (::endwin() == ERR
) {
591 ERROR_CURSES_FINALIZATION
, _("Finalization of Curses session failed."));
598 return has_error
? error
.getCode() : 0;
601 void setAsciiMode(bool enabled
)
603 ascii_mode
= enabled
;
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) "
631 return error
.getCode();
634 *res
= COLOR_PAIR(idx
);
643 int getColorPairCount()
645 #ifndef NCURSES_EXT_COLORS
646 // Ncurses reports more than 256 color pairs, even when compiled without
648 return std::min(COLOR_PAIRS
, 256);
654 int erase(Error
&error
)
656 if (::erase() == ERR
) {
657 error
= Error(ERROR_CURSES_CLEAR
, _("Erasing the screen failed."));
658 return error
.getCode();
663 int clear(Error
&error
)
665 if (::clear() == ERR
) {
666 error
= Error(ERROR_CURSES_CLEAR
, _("Clearing the screen failed."));
667 return error
.getCode();
672 int refresh(Error
&error
)
674 if (::refresh() == ERR
) {
675 error
= Error(ERROR_CURSES_REFRESH
, _("Refreshing the screen failed."));
676 return error
.getCode();
681 int beep(Error
&error
)
683 if (::beep() == ERR
) {
684 error
= Error(ERROR_CURSES_BEEP
, _("Producing beep alert failed."));
685 return error
.getCode();
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."),
707 return error
.getCode();
715 int onScreenWidth(const char *start
, const char *end
)
719 if (start
== nullptr)
723 end
= start
+ std::strlen(start
);
725 while (start
< end
) {
726 width
+= onScreenWidth(UTF8::getUniChar(start
));
727 start
= UTF8::getNextChar(start
);
732 int onScreenWidth(UTF8::UniChar uc
, int w
)
736 return UTF8::isUniCharWide(uc
) ? 2 : 1;
739 } // namespace Curses
741 } // namespace CppConsUI
743 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: