1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program 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 * This program 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 this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
25 #include <readline/history.h>
26 #include <readline/readline.h>
31 # include <sys/select.h>
36 #include "utility/string.h"
37 #include "utility/wide_string.h"
58 if (w
->runGetStringHelper(rl_line_buffer
, &done
))
65 w
->goToXY(x
, start_y
);
68 result
= w
->readKey();
69 if (!w
->FDCallbacksListEmpty())
71 w
->goToXY(x
, start_y
);
75 while (result
== ERR
);
81 auto print_char
= [](wchar_t wc
) {
87 auto print_string
= [](wchar_t *ws
, size_t len
) {
89 for (size_t i
= 0; i
< len
; ++i
)
95 char pt
= rl_line_buffer
[rl_point
];
96 rl_line_buffer
[rl_point
] = 0;
97 wchar_t pre_pos
[rl_point
+1];
98 pre_pos
[mbstowcs(pre_pos
, rl_line_buffer
, rl_point
)] = 0;
99 rl_line_buffer
[rl_point
] = pt
;
101 int pos
= wcswidth(pre_pos
, rl_point
);
105 mvwhline(w
->raw(), start_y
, start_x
, ' ', width
+1);
106 w
->goToXY(start_x
, start_y
);
107 if (size_t(pos
) <= width
)
109 print_string(pre_pos
, pos
);
111 wchar_t post_pos
[rl_end
-rl_point
+1];
112 post_pos
[mbstowcs(post_pos
, rl_line_buffer
+rl_point
, rl_end
-rl_point
)] = 0;
115 for (wchar_t *c
= post_pos
; *c
!= 0; ++c
)
134 wchar_t *mod_pre_pos
= pre_pos
;
135 while (*mod_pre_pos
!= 0)
138 int n
= wcwidth(*mod_pre_pos
);
143 if (size_t(pos
) <= width
)
146 print_string(mod_pre_pos
, pos
);
148 w
->goToXY(start_x
+pos
, start_y
);
153 rl_insert_text(base
);
162 std::ostream
&operator<<(std::ostream
&os
, Color c
)
200 std::istream
&operator>>(std::istream
&is
, Color
&c
)
206 else if (sc
== "black")
208 else if (sc
== "red")
210 else if (sc
== "green")
212 else if (sc
== "yellow")
214 else if (sc
== "blue")
216 else if (sc
== "magenta")
218 else if (sc
== "cyan")
220 else if (sc
== "white")
222 else if (sc
== "color_end")
225 is
.setstate(std::ios::failbit
);
229 std::ostream
&operator<<(std::ostream
&os
, Format f
)
242 case Format::Underline
:
245 case Format::NoUnderline
:
246 os
<< "no_underline";
248 case Format::Reverse
:
251 case Format::NoReverse
:
254 case Format::AltCharset
:
257 case Format::NoAltCharset
:
258 os
<< "no_alt_charset";
264 std::ostream
&operator<<(std::ostream
&os
, Border b
)
286 case Border::Magenta
:
299 std::istream
&operator>>(std::istream
&is
, Border
&b
)
305 else if (sb
== "black")
307 else if (sb
== "red")
309 else if (sb
== "green")
311 else if (sb
== "yellow")
313 else if (sb
== "blue")
315 else if (sb
== "magenta")
317 else if (sb
== "cyan")
319 else if (sb
== "white")
322 is
.setstate(std::ios::failbit
);
326 std::ostream
&operator<<(std::ostream
&os
, Scroll s
)
337 os
<< "scroll_page_up";
339 case Scroll::PageDown
:
340 os
<< "scroll_page_down";
352 void initScreen(GNUC_UNUSED
const char *window_title
, bool enable_colors
)
354 const int ColorsTable
[] =
356 COLOR_BLACK
, COLOR_RED
, COLOR_GREEN
, COLOR_YELLOW
,
357 COLOR_BLUE
, COLOR_MAGENTA
, COLOR_CYAN
, COLOR_WHITE
360 Xinitscr(1, const_cast<char **>(&window_title
));
364 if (has_colors() && enable_colors
)
367 use_default_colors();
373 # endif // USE_PDCURSES
375 for (int j
= 0; j
< 8; ++j
)
376 init_pair(num
++, ColorsTable
[j
], i
< 0 ? i
: ColorsTable
[i
]);
383 // disable autocompletion
384 rl_bind_key('\t', nullptr);
385 // overwrite readline callbacks
386 rl_getc_function
= rl::read_key
;
387 rl_redisplay_function
= rl::display_string
;
388 rl_startup_hook
= rl::add_base
;
397 Window::Window(size_t startx
,
401 const std::string
&title
,
410 m_window_timeout(-1),
412 m_bg_color(Color::Default
),
414 m_base_bg_color(Color::Default
),
416 m_get_string_helper(0),
419 m_underline_counter(0),
420 m_reverse_counter(0),
421 m_alt_charset_counter(0)
423 if (m_start_x
> size_t(COLS
)
424 || m_start_y
> size_t(LINES
)
425 || m_width
+m_start_x
> size_t(COLS
)
426 || m_height
+m_start_y
> size_t(LINES
))
427 FatalError("Constructed window is bigger than terminal size");
429 if (m_border
!= Border::None
)
431 m_border_window
= newpad(m_height
, m_width
);
432 wattron(m_border_window
, COLOR_PAIR(int(m_border
)));
433 box(m_border_window
, 0, 0);
439 if (!m_title
.empty())
445 m_window
= newpad(m_height
, m_width
);
451 Window::Window(const Window
&rhs
)
452 : m_window(dupwin(rhs
.m_window
))
453 , m_border_window(dupwin(rhs
.m_border_window
))
454 , m_start_x(rhs
.m_start_x
)
455 , m_start_y(rhs
.m_start_y
)
456 , m_width(rhs
.m_width
)
457 , m_height(rhs
.m_height
)
458 , m_window_timeout(rhs
.m_window_timeout
)
459 , m_color(rhs
.m_color
)
460 , m_bg_color(rhs
.m_bg_color
)
461 , m_base_color(rhs
.m_base_color
)
462 , m_base_bg_color(rhs
.m_base_bg_color
)
463 , m_border(rhs
.m_border
)
464 , m_get_string_helper(rhs
.m_get_string_helper
)
465 , m_title(rhs
.m_title
)
466 , m_color_stack(rhs
.m_color_stack
)
467 , m_input_queue(rhs
.m_input_queue
)
469 , m_bold_counter(rhs
.m_bold_counter
)
470 , m_underline_counter(rhs
.m_underline_counter
)
471 , m_reverse_counter(rhs
.m_reverse_counter
)
472 , m_alt_charset_counter(rhs
.m_alt_charset_counter
)
476 Window::Window(Window
&&rhs
)
477 : m_window(rhs
.m_window
)
478 , m_border_window(rhs
.m_border_window
)
479 , m_start_x(rhs
.m_start_x
)
480 , m_start_y(rhs
.m_start_y
)
481 , m_width(rhs
.m_width
)
482 , m_height(rhs
.m_height
)
483 , m_window_timeout(rhs
.m_window_timeout
)
484 , m_color(rhs
.m_color
)
485 , m_bg_color(rhs
.m_bg_color
)
486 , m_base_color(rhs
.m_base_color
)
487 , m_base_bg_color(rhs
.m_base_bg_color
)
488 , m_border(rhs
.m_border
)
489 , m_get_string_helper(rhs
.m_get_string_helper
)
490 , m_title(std::move(rhs
.m_title
))
491 , m_color_stack(std::move(rhs
.m_color_stack
))
492 , m_input_queue(std::move(rhs
.m_input_queue
))
493 , m_fds(std::move(rhs
.m_fds
))
494 , m_bold_counter(rhs
.m_bold_counter
)
495 , m_underline_counter(rhs
.m_underline_counter
)
496 , m_reverse_counter(rhs
.m_reverse_counter
)
497 , m_alt_charset_counter(rhs
.m_alt_charset_counter
)
500 rhs
.m_border_window
= 0;
503 Window
&Window::operator=(Window rhs
)
505 std::swap(m_window
, rhs
.m_window
);
506 std::swap(m_border_window
, rhs
.m_border_window
);
507 std::swap(m_start_x
, rhs
.m_start_x
);
508 std::swap(m_start_y
, rhs
.m_start_y
);
509 std::swap(m_width
, rhs
.m_width
);
510 std::swap(m_height
, rhs
.m_height
);
511 std::swap(m_window_timeout
, rhs
.m_window_timeout
);
512 std::swap(m_color
, rhs
.m_color
);
513 std::swap(m_bg_color
, rhs
.m_bg_color
);
514 std::swap(m_base_color
, rhs
.m_base_color
);
515 std::swap(m_base_bg_color
, rhs
.m_base_bg_color
);
516 std::swap(m_border
, rhs
.m_border
);
517 std::swap(m_get_string_helper
, rhs
.m_get_string_helper
);
518 std::swap(m_title
, rhs
.m_title
);
519 std::swap(m_color_stack
, rhs
.m_color_stack
);
520 std::swap(m_input_queue
, rhs
.m_input_queue
);
521 std::swap(m_fds
, rhs
.m_fds
);
522 std::swap(m_bold_counter
, rhs
.m_bold_counter
);
523 std::swap(m_underline_counter
, rhs
.m_underline_counter
);
524 std::swap(m_reverse_counter
, rhs
.m_reverse_counter
);
525 std::swap(m_alt_charset_counter
, rhs
.m_alt_charset_counter
);
532 delwin(m_border_window
);
535 void Window::setColor(Color fg
, Color bg
)
537 if (fg
== Color::Default
)
540 if (fg
!= Color::Default
)
541 wattron(m_window
, COLOR_PAIR(int(bg
)*8+int(fg
)));
543 wattroff(m_window
, COLOR_PAIR(int(m_color
)));
548 void Window::setBaseColor(Color fg
, Color bg
)
551 m_base_bg_color
= bg
;
554 void Window::setBorder(Border border
)
556 if (border
== Border::None
&& m_border
!= Border::None
)
558 delwin(m_border_window
);
563 recreate(m_width
, m_height
);
565 else if (border
!= Border::None
&& m_border
== Border::None
)
567 m_border_window
= newpad(m_height
, m_width
);
568 wattron(m_border_window
, COLOR_PAIR(int(border
)));
569 box(m_border_window
,0,0);
574 recreate(m_width
, m_height
);
578 wattron(m_border_window
,COLOR_PAIR(int(border
)));
579 box(m_border_window
, 0, 0);
584 void Window::setTitle(const std::string
&new_title
)
586 if (m_title
== new_title
)
590 else if (!new_title
.empty() && m_title
.empty())
594 recreate(m_width
, m_height
);
596 else if (new_title
.empty() && !m_title
.empty())
600 recreate(m_width
, m_height
);
605 void Window::recreate(size_t width
, size_t height
)
608 m_window
= newpad(height
, width
);
609 setTimeout(m_window_timeout
);
610 setColor(m_color
, m_bg_color
);
614 void Window::moveTo(size_t new_x
, size_t new_y
)
618 if (m_border
!= Border::None
)
623 if (!m_title
.empty())
627 void Window::adjustDimensions(size_t width
, size_t height
)
629 if (m_border
!= Border::None
)
631 delwin(m_border_window
);
632 m_border_window
= newpad(height
, width
);
633 wattron(m_border_window
, COLOR_PAIR(int(m_border
)));
634 box(m_border_window
, 0, 0);
638 if (!m_title
.empty())
644 void Window::resize(size_t new_width
, size_t new_height
)
646 adjustDimensions(new_width
, new_height
);
647 recreate(m_width
, m_height
);
650 void Window::showBorder() const
652 if (m_border
!= Border::None
)
655 prefresh(m_border_window
, 0, 0, getStarty(), getStartX(), m_start_y
+m_height
, m_start_x
+m_width
);
657 if (!m_title
.empty())
659 if (m_border
!= Border::None
)
660 attron(COLOR_PAIR(int(m_border
)));
662 attron(COLOR_PAIR(int(m_base_color
)));
663 mvhline(m_start_y
-1, m_start_x
, 0, m_width
);
665 mvhline(m_start_y
-2, m_start_x
, 32, m_width
); // clear title line
666 mvaddstr(m_start_y
-2, m_start_x
, m_title
.c_str());
667 attroff(COLOR_PAIR(int(m_border
)) | A_BOLD
);
672 void Window::display()
678 void Window::refresh()
680 prefresh(m_window
, 0, 0, m_start_y
, m_start_x
, m_start_y
+m_height
-1, m_start_x
+m_width
-1);
688 void Window::bold(bool bold_state
) const
690 (bold_state
? wattron
: wattroff
)(m_window
, A_BOLD
);
693 void Window::underline(bool underline_state
) const
695 (underline_state
? wattron
: wattroff
)(m_window
, A_UNDERLINE
);
698 void Window::reverse(bool reverse_state
) const
700 (reverse_state
? wattron
: wattroff
)(m_window
, A_REVERSE
);
703 void Window::altCharset(bool altcharset_state
) const
705 (altcharset_state
? wattron
: wattroff
)(m_window
, A_ALTCHARSET
);
708 void Window::setTimeout(int timeout
)
710 if (timeout
!= m_window_timeout
)
712 m_window_timeout
= timeout
;
713 wtimeout(m_window
, timeout
);
717 void Window::addFDCallback(int fd
, void (*callback
)())
719 m_fds
.push_back(std::make_pair(fd
, callback
));
722 void Window::clearFDCallbacksList()
727 bool Window::FDCallbacksListEmpty() const
729 return m_fds
.empty();
732 int Window::readKey()
735 // if there are characters in input queue, get them and
736 // return immediately.
737 if (!m_input_queue
.empty())
739 result
= m_input_queue
.front();
743 // in pdcurses polling stdin doesn't work, so we can't poll
744 // both stdin and other file descriptors in one select. the
745 // workaround is to set the timeout of select to 0, poll
746 // other file descriptors and then wait for stdin input with
747 // the given timeout. unfortunately, this results in delays
748 // since ncmpcpp doesn't see that data arrived while waiting
749 // for input from stdin, but it seems there is no better option.
753 # if !defined(USE_PDCURSES)
754 FD_SET(STDIN_FILENO
, &fdset
);
755 timeval timeout
= { m_window_timeout
/1000, (m_window_timeout
%1000)*1000 };
757 timeval timeout
= { 0, 0 };
760 int fd_max
= STDIN_FILENO
;
761 for (FDCallbacks::const_iterator it
= m_fds
.begin(); it
!= m_fds
.end(); ++it
)
763 if (it
->first
> fd_max
)
765 FD_SET(it
->first
, &fdset
);
768 if (select(fd_max
+1, &fdset
, 0, 0, m_window_timeout
< 0 ? 0 : &timeout
) > 0)
770 # if !defined(USE_PDCURSES)
771 result
= FD_ISSET(STDIN_FILENO
, &fdset
) ? wgetch(m_window
) : ERR
;
772 # endif // !USE_PDCURSES
773 for (FDCallbacks::const_iterator it
= m_fds
.begin(); it
!= m_fds
.end(); ++it
)
774 if (FD_ISSET(it
->first
, &fdset
))
777 # if !defined(USE_PDCURSES)
781 result
= wgetch(m_window
);
786 void Window::pushChar(int ch
)
788 m_input_queue
.push(ch
);
791 std::string
Window::getString(const std::string
&base
, size_t width
, bool encrypted
)
794 getyx(m_window
, rl::start_y
, rl::start_x
);
796 rl::encrypted
= encrypted
;
797 rl::base
= base
.c_str();
800 if (width
== size_t(-1))
801 rl::width
= m_width
-rl::start_x
-1;
810 mousemask(0, &oldmask
);
811 char *input
= readline(nullptr);
812 mousemask(oldmask
, nullptr);
815 if (input
!= nullptr)
826 void Window::goToXY(int x
, int y
)
828 wmove(m_window
, y
, x
);
833 return getcurx(m_window
);
838 return getcury(m_window
);
841 bool Window::hasCoords(int &x
, int &y
)
843 # ifndef USE_PDCURSES
844 return wmouse_trafo(m_window
, &y
, &x
, 0);
846 // wmouse_trafo is broken in pdcurses, use our own implementation
847 size_t u_x
= x
, u_y
= y
;
848 if (u_x
>= m_start_x
&& u_x
< m_start_x
+m_width
849 && u_y
>= m_start_y
&& u_y
< m_start_y
+m_height
)
859 bool Window::runGetStringHelper(const char *arg
, bool *done
) const
861 if (m_get_string_helper
)
863 bool continue_
= m_get_string_helper(arg
);
872 size_t Window::getWidth() const
874 if (m_border
!= Border::None
)
880 size_t Window::getHeight() const
882 size_t height
= m_height
;
883 if (m_border
!= Border::None
)
885 if (!m_title
.empty())
890 size_t Window::getStartX() const
892 if (m_border
!= Border::None
)
898 size_t Window::getStarty() const
900 size_t starty
= m_start_y
;
901 if (m_border
!= Border::None
)
903 if (!m_title
.empty())
908 const std::string
&Window::getTitle() const
913 Color
Window::getColor() const
918 Border
Window::getBorder() const
923 int Window::getTimeout() const
925 return m_window_timeout
;
928 void Window::scroll(Scroll where
)
931 scrollok(m_window
, 1);
941 wscrl(m_window
, m_width
);
943 case Scroll::PageDown
:
944 wscrl(m_window
, -m_width
);
950 scrollok(m_window
, 0);
954 Window
&Window::operator<<(Colors colors
)
956 if (colors
.fg
== Color::End
|| colors
.bg
== Color::End
)
961 m_color_stack
.push(colors
);
962 setColor(colors
.fg
, colors
.bg
);
966 Window
&Window::operator<<(Color color
)
971 while (!m_color_stack
.empty())
973 setColor(m_base_color
, m_base_bg_color
);
976 if (!m_color_stack
.empty())
978 if (!m_color_stack
.empty())
979 setColor(m_color_stack
.top().fg
, m_color_stack
.top().bg
);
981 setColor(m_base_color
, m_base_bg_color
);
985 if (m_color_stack
.empty())
988 bg
= m_color_stack
.top().bg
;
989 m_color_stack
.push(Colors(color
, bg
));
990 setColor(m_color_stack
.top().fg
, m_color_stack
.top().bg
);
995 Window
&Window::operator<<(Format format
)
1000 bold((m_bold_counter
= 0));
1001 reverse((m_reverse_counter
= 0));
1002 altCharset((m_alt_charset_counter
= 0));
1005 bold(++m_bold_counter
);
1007 case Format::NoBold
:
1008 if (--m_bold_counter
<= 0)
1009 bold((m_bold_counter
= 0));
1011 case Format::Underline
:
1012 underline(++m_underline_counter
);
1014 case Format::NoUnderline
:
1015 if (--m_underline_counter
<= 0)
1016 underline((m_underline_counter
= 0));
1018 case Format::Reverse
:
1019 reverse(++m_reverse_counter
);
1021 case Format::NoReverse
:
1022 if (--m_reverse_counter
<= 0)
1023 reverse((m_reverse_counter
= 0));
1025 case Format::AltCharset
:
1026 altCharset(++m_alt_charset_counter
);
1028 case Format::NoAltCharset
:
1029 if (--m_alt_charset_counter
<= 0)
1030 altCharset((m_alt_charset_counter
= 0));
1036 Window
&Window::operator<<(int (*f
)(WINDOW
*))
1042 Window
&Window::operator<<(XY coords
)
1044 goToXY(coords
.x
, coords
.y
);
1048 Window
&Window::operator<<(const char *s
)
1050 for (const char *c
= s
; *c
!= '\0'; ++c
)
1051 wprintw(m_window
, "%c", *c
);
1055 Window
&Window::operator<<(char c
)
1057 wprintw(m_window
, "%c", c
);
1061 Window
&Window::operator<<(const wchar_t *ws
)
1063 for (const wchar_t *wc
= ws
; *wc
!= L
'\0'; ++wc
)
1064 wprintw(m_window
, "%lc", *wc
);
1068 Window
&Window::operator<<(wchar_t wc
)
1070 wprintw(m_window
, "%lc", wc
);
1074 Window
&Window::operator<<(int i
)
1076 wprintw(m_window
, "%d", i
);
1080 Window
&Window::operator<<(double d
)
1082 wprintw(m_window
, "%f", d
);
1086 Window
&Window::operator<<(const std::string
&s
)
1088 // for some reason passing whole string at once with "%s" doesn't work
1089 // (string is cut in the middle, probably due to limitation of ncurses'
1090 // internal buffer?), so we need to pass characters in a loop.
1091 for (auto it
= s
.begin(); it
!= s
.end(); ++it
)
1092 wprintw(m_window
, "%c", *it
);
1096 Window
&Window::operator<<(const std::wstring
&ws
)
1098 for (auto it
= ws
.begin(); it
!= ws
.end(); ++it
)
1099 wprintw(m_window
, "%lc", *it
);
1103 Window
&Window::operator<<(size_t s
)
1105 wprintw(m_window
, "%zu", s
);