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 /// TextView class implementation.
21 /// @ingroup cppconsui
25 #include "ColorScheme.h"
33 TextView::TextView(int w
, int h
, bool autoscroll
, bool scrollbar
)
34 : Widget(w
, h
), view_top_(0), autoscroll_(autoscroll
),
35 autoscroll_suspended_(false), scrollbar_(scrollbar
)
46 int TextView::draw(Curses::ViewPort area
, Error
&error
)
48 DRAW(area
.erase(error
));
50 if (screen_lines_
.size() <= static_cast<unsigned>(real_height_
)) {
52 autoscroll_suspended_
= false;
54 else if (view_top_
> screen_lines_
.size() - real_height_
) {
55 view_top_
= screen_lines_
.size() - real_height_
;
56 autoscroll_suspended_
= false;
58 else if (autoscroll_
&& !autoscroll_suspended_
)
59 view_top_
= screen_lines_
.size() - real_height_
;
62 DRAW(getAttributes(ColorScheme::PROPERTY_TEXTVIEW_TEXT
, &attrs
, error
));
63 DRAW(area
.attrOn(attrs
, error
));
65 ScreenLines::iterator i
;
67 for (i
= screen_lines_
.begin() + view_top_
, j
= 0;
68 i
!= screen_lines_
.end() && j
< real_height_
; ++i
, ++j
) {
70 if (i
->parent
->color
!= 0) {
72 ColorScheme::PROPERTY_TEXTVIEW_TEXT
, i
->parent
->color
, &attrs2
, error
));
73 DRAW(area
.attrOff(attrs
, error
));
74 DRAW(area
.attrOn(attrs2
, error
));
77 const char *p
= i
->text
;
79 for (int k
= 0; k
< i
->length
; ++k
) {
80 UTF8::UniChar uc
= UTF8::getUniChar(p
);
83 printed
= Curses::onScreenWidth(uc
, w
);
84 for (int l
= 0; l
< printed
; ++l
)
85 DRAW(area
.addChar(w
+ l
, j
, ' ', error
));
88 DRAW(area
.addChar(w
, j
, uc
, error
, &printed
));
90 p
= UTF8::getNextChar(p
);
93 if (i
->parent
->color
!= 0) {
94 DRAW(area
.attrOff(attrs2
, error
));
95 DRAW(area
.attrOn(attrs
, error
));
99 DRAW(area
.attrOff(attrs
, error
));
104 if (screen_lines_
.size() <= static_cast<unsigned>(real_height_
)) {
109 x2
= static_cast<float>(view_top_
+ real_height_
) * real_height_
/
110 screen_lines_
.size();
111 // Calculate x1 based on x2 (not based on view_top_) to avoid jittering
113 x1
= x2
- real_height_
* real_height_
/ screen_lines_
.size();
118 getAttributes(ColorScheme::PROPERTY_TEXTVIEW_SCROLLBAR
, &attrs
, error
));
119 attrs
|= Curses::Attr::REVERSE
;
120 DRAW(area
.attrOn(attrs
, error
));
122 for (int i
= x1
+ 1; i
< x2
- 1; ++i
)
123 DRAW(area
.addChar(real_width_
- 1, i
, ' ', error
));
126 // This is a special case when x1 is too close to x2, but we need to draw
127 // at least two arrows.
128 if (real_height_
- x1
< 2) {
129 // We are close to bottom position.
130 DRAW(area
.addLineChar(
131 real_width_
- 1, real_height_
- 2, Curses::LINE_UARROW
, error
));
132 DRAW(area
.addLineChar(
133 real_width_
- 1, real_height_
- 1, Curses::LINE_DARROW
, error
));
136 // We are close to top position.
137 DRAW(area
.addLineChar(real_width_
- 1, 0, Curses::LINE_UARROW
, error
));
138 DRAW(area
.addLineChar(real_width_
- 1, 1, Curses::LINE_DARROW
, error
));
142 DRAW(area
.addLineChar(
143 real_width_
- 1, x2
- 2, Curses::LINE_UARROW
, error
));
144 DRAW(area
.addLineChar(
145 real_width_
- 1, x2
- 1, Curses::LINE_DARROW
, error
));
149 // Scrollbar length is big enough to fit two arrows.
150 DRAW(area
.addLineChar(real_width_
- 1, x1
, Curses::LINE_UARROW
, error
));
152 area
.addLineChar(real_width_
- 1, x2
- 1, Curses::LINE_DARROW
, error
));
155 // Draw a dot to indicate "end of scrolling" for users.
156 if (view_top_
+ real_height_
>= screen_lines_
.size())
157 DRAW(area
.addLineChar(
158 real_width_
- 1, real_height_
- 1, Curses::LINE_BULLET
, error
));
160 DRAW(area
.addLineChar(real_width_
- 1, 0, Curses::LINE_BULLET
, error
));
162 DRAW(area
.attrOff(attrs
, error
));
167 g_snprintf(pos, sizeof(pos), "%d/%d ", view_top_, screen_lines_.size());
168 DRAW(area.addString(0, 0, pos));
174 void TextView::append(const char *text
, int color
)
176 insert(lines_
.size(), text
, color
);
179 void TextView::insert(std::size_t line_num
, const char *text
, int color
)
184 assert(line_num
<= lines_
.size());
186 const char *p
= text
;
187 const char *s
= text
;
188 std::size_t cur_line_num
= line_num
;
193 auto l
= new Line(s
, p
- s
, color
);
194 lines_
.insert(lines_
.begin() + cur_line_num
, l
);
196 s
= p
= UTF8::getNextChar(p
);
200 p
= UTF8::getNextChar(p
);
204 auto l
= new Line(s
, p
- s
, color
);
205 lines_
.insert(lines_
.begin() + cur_line_num
, l
);
209 // Update screen lines.
210 for (std::size_t i
= line_num
, advice
= 0; i
< cur_line_num
; ++i
)
211 advice
= updateScreenLines(i
, advice
);
216 void TextView::erase(std::size_t line_num
)
218 assert(line_num
< lines_
.size());
220 eraseScreenLines(line_num
, 0);
221 delete lines_
[line_num
];
222 lines_
.erase(lines_
.begin() + line_num
);
227 void TextView::erase(std::size_t start_line
, std::size_t end_line
)
229 assert(start_line
< lines_
.size());
230 assert(end_line
<= lines_
.size());
231 assert(start_line
<= end_line
);
233 std::size_t advice
= 0;
234 for (std::size_t i
= start_line
; i
< end_line
; ++i
)
235 advice
= eraseScreenLines(i
, advice
);
236 for (std::size_t i
= start_line
; i
< end_line
; ++i
)
238 lines_
.erase(lines_
.begin() + start_line
, lines_
.begin() + end_line
);
243 void TextView::clear()
245 for (Line
*line
: lines_
)
249 screen_lines_
.clear();
254 const char *TextView::getLine(std::size_t line_num
) const
256 assert(line_num
< lines_
.size());
258 return lines_
[line_num
]->text
;
261 std::size_t TextView::getLinesNumber() const
263 return lines_
.size();
266 void TextView::setAutoScroll(bool new_autoscroll
)
268 if (new_autoscroll
== autoscroll_
)
271 autoscroll_
= new_autoscroll
;
275 void TextView::setScrollBar(bool new_scrollbar
)
277 if (new_scrollbar
== scrollbar_
)
280 scrollbar_
= new_scrollbar
;
281 updateAllScreenLines();
285 TextView::Line::Line(const char *text_
, std::size_t bytes
, int color_
)
288 assert(text_
!= nullptr);
290 text
= new char[bytes
+ 1];
291 std::strncpy(text
, text_
, bytes
);
295 const char *p
= text
;
296 while (p
!= nullptr && *p
!= '\0') {
298 p
= UTF8::getNextChar(p
);
302 TextView::Line::~Line()
307 TextView::ScreenLine::ScreenLine(Line
&parent_
, const char *text_
, int length_
)
308 : parent(&parent_
), text(text_
), length(length_
)
312 void TextView::updateArea()
314 updateAllScreenLines();
317 const char *TextView::proceedLine(
318 const char *text
, int area_width
, int *res_length
) const
320 assert(text
!= nullptr);
321 assert(area_width
> 0);
322 assert(res_length
!= nullptr);
324 const char *cur
= text
;
325 const char *res
= text
;
332 while (*cur
!= '\0') {
333 prev_width
= cur_width
;
334 UTF8::UniChar uc
= UTF8::getUniChar(cur
);
335 cur_width
+= Curses::onScreenWidth(uc
, cur_width
);
338 if (prev_width
> area_width
)
341 // Possibly too long word.
342 if (cur_width
> area_width
&& *res_length
== 0) {
343 *res_length
= cur_length
- 1;
347 if (UTF8::isUniCharSpace(uc
))
350 // Found start of a word and everything before that can fit into one
352 *res_length
= cur_length
- 1;
357 cur
= UTF8::getNextChar(cur
);
361 if (*cur
== '\0' && cur_width
<= area_width
) {
362 *res_length
= cur_length
;
366 // Fix for very small area_width and characters wider that 1 cell. For
367 // example, area_width = 1 and text = "W" where W is a wide character (2 cells
368 // width) (or simply for tabs). In that case we can not draw anything but we
369 // want to skip to another character.
371 res
= UTF8::getNextChar(res
);
376 std::size_t TextView::updateScreenLines(std::size_t line_num
, std::size_t start
)
378 assert(line_num
< lines_
.size());
379 assert(start
<= screen_lines_
.size());
381 // Find where new screen lines should be placed and remove previous screen
382 // lines created for this line.
383 ScreenLines::iterator i
=
384 screen_lines_
.begin() + eraseScreenLines(line_num
, start
);
386 // Parse line into screen lines.
387 ScreenLines new_lines
;
388 const char *p
= lines_
[line_num
]->text
;
391 int realw
= real_width_
;
392 if (scrollbar_
&& realw
> 2) {
393 // Scrollbar shrinks the width of the view area.
403 p
= proceedLine(p
, realw
, &len
);
404 new_lines
.push_back(ScreenLine(*lines_
[line_num
], s
, len
));
408 if (new_lines
.empty())
409 new_lines
.push_back(ScreenLine(*lines_
[line_num
], p
, 0));
411 std::size_t res
= i
- screen_lines_
.begin() + new_lines
.size();
412 screen_lines_
.insert(i
, new_lines
.begin(), new_lines
.end());
417 void TextView::updateAllScreenLines()
419 // Delete all screen lines.
420 screen_lines_
.clear();
422 /// @todo Save and restore scroll afterwards.
423 for (std::size_t i
= 0, advice
= 0; i
< lines_
.size(); ++i
)
424 advice
= updateScreenLines(i
, advice
);
427 std::size_t TextView::eraseScreenLines(
428 std::size_t line_num
, std::size_t start
, std::size_t *deleted
)
430 assert(line_num
< lines_
.size());
431 assert(start
<= screen_lines_
.size());
433 std::size_t i
= start
;
434 bool begin_set
= false, end_set
= false;
435 // Note, the assigment to the begin variable is only to silence a compiler
436 // warning. The use of the variable is protected by the begin_set variable.
437 std::size_t begin
= 0, end
;
438 while (i
< screen_lines_
.size()) {
439 if (screen_lines_
[i
].parent
== lines_
[line_num
]) {
445 else if (begin_set
) {
454 end
= screen_lines_
.size();
456 screen_lines_
.begin() + begin
, screen_lines_
.begin() + end
);
458 if (deleted
!= nullptr)
459 *deleted
= end
- begin
;
461 else if (deleted
!= nullptr)
467 void TextView::actionScroll(int direction
)
469 if (screen_lines_
.size() <= static_cast<unsigned>(real_height_
))
472 unsigned s
= abs(direction
) * ((real_height_
+ 1) / 2);
480 if (view_top_
+ s
> screen_lines_
.size() - real_height_
)
481 view_top_
= screen_lines_
.size() - real_height_
;
486 autoscroll_suspended_
= screen_lines_
.size() > view_top_
+ real_height_
;
490 void TextView::declareBindables()
492 declareBindable("textview", "scroll-up",
493 sigc::bind(sigc::mem_fun(this, &TextView::actionScroll
), -1),
494 InputProcessor::BINDABLE_NORMAL
);
496 declareBindable("textview", "scroll-down",
497 sigc::bind(sigc::mem_fun(this, &TextView::actionScroll
), 1),
498 InputProcessor::BINDABLE_NORMAL
);
501 } // namespace CppConsUI
503 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: