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 std::size_t advice
= cur_line_num
== lines_
.size() ? screen_lines_
.size() : 0;
211 for (std::size_t i
= line_num
; i
< cur_line_num
; ++i
)
212 advice
= updateScreenLines(i
, advice
);
217 void TextView::erase(std::size_t line_num
)
219 assert(line_num
< lines_
.size());
221 eraseScreenLines(line_num
, 0);
222 delete lines_
[line_num
];
223 lines_
.erase(lines_
.begin() + line_num
);
228 void TextView::erase(std::size_t start_line
, std::size_t end_line
)
230 assert(start_line
< lines_
.size());
231 assert(end_line
<= lines_
.size());
232 assert(start_line
<= end_line
);
234 std::size_t advice
= 0;
235 for (std::size_t i
= start_line
; i
< end_line
; ++i
)
236 advice
= eraseScreenLines(i
, advice
);
237 for (std::size_t i
= start_line
; i
< end_line
; ++i
)
239 lines_
.erase(lines_
.begin() + start_line
, lines_
.begin() + end_line
);
244 void TextView::clear()
246 for (Line
*line
: lines_
)
250 screen_lines_
.clear();
255 const char *TextView::getLine(std::size_t line_num
) const
257 assert(line_num
< lines_
.size());
259 return lines_
[line_num
]->text
;
262 std::size_t TextView::getLinesNumber() const
264 return lines_
.size();
267 void TextView::setAutoScroll(bool new_autoscroll
)
269 if (new_autoscroll
== autoscroll_
)
272 autoscroll_
= new_autoscroll
;
276 void TextView::setScrollBar(bool new_scrollbar
)
278 if (new_scrollbar
== scrollbar_
)
281 scrollbar_
= new_scrollbar
;
282 updateAllScreenLines();
286 TextView::Line::Line(const char *text_
, std::size_t bytes
, int color_
)
289 assert(text_
!= nullptr);
291 text
= new char[bytes
+ 1];
292 std::strncpy(text
, text_
, bytes
);
296 const char *p
= text
;
297 while (p
!= nullptr && *p
!= '\0') {
299 p
= UTF8::getNextChar(p
);
303 TextView::Line::~Line()
308 TextView::ScreenLine::ScreenLine(Line
&parent_
, const char *text_
, int length_
)
309 : parent(&parent_
), text(text_
), length(length_
)
313 void TextView::updateArea()
315 updateAllScreenLines();
318 const char *TextView::proceedLine(
319 const char *text
, int area_width
, int *res_length
) const
321 assert(text
!= nullptr);
322 assert(area_width
> 0);
323 assert(res_length
!= nullptr);
325 const char *cur
= text
;
326 const char *res
= text
;
333 while (*cur
!= '\0') {
334 prev_width
= cur_width
;
335 UTF8::UniChar uc
= UTF8::getUniChar(cur
);
336 cur_width
+= Curses::onScreenWidth(uc
, cur_width
);
339 if (prev_width
> area_width
)
342 // Possibly too long word.
343 if (cur_width
> area_width
&& *res_length
== 0) {
344 *res_length
= cur_length
- 1;
348 if (UTF8::isUniCharSpace(uc
))
351 // Found start of a word and everything before that can fit into one
353 *res_length
= cur_length
- 1;
358 cur
= UTF8::getNextChar(cur
);
362 if (*cur
== '\0' && cur_width
<= area_width
) {
363 *res_length
= cur_length
;
367 // Fix for very small area_width and characters wider that 1 cell. For
368 // example, area_width = 1 and text = "W" where W is a wide character (2 cells
369 // width) (or simply for tabs). In that case we can not draw anything but we
370 // want to skip to another character.
372 res
= UTF8::getNextChar(res
);
377 std::size_t TextView::updateScreenLines(std::size_t line_num
, std::size_t start
)
379 assert(line_num
< lines_
.size());
380 assert(start
<= screen_lines_
.size());
382 // Find where new screen lines should be placed and remove previous screen
383 // lines created for this line.
384 ScreenLines::iterator i
=
385 screen_lines_
.begin() + eraseScreenLines(line_num
, start
);
387 // Parse line into screen lines.
388 ScreenLines new_lines
;
389 const char *p
= lines_
[line_num
]->text
;
392 int realw
= real_width_
;
393 if (scrollbar_
&& realw
> 2) {
394 // Scrollbar shrinks the width of the view area.
404 p
= proceedLine(p
, realw
, &len
);
405 new_lines
.push_back(ScreenLine(*lines_
[line_num
], s
, len
));
409 if (new_lines
.empty())
410 new_lines
.push_back(ScreenLine(*lines_
[line_num
], p
, 0));
412 std::size_t res
= i
- screen_lines_
.begin() + new_lines
.size();
413 screen_lines_
.insert(i
, new_lines
.begin(), new_lines
.end());
418 void TextView::updateAllScreenLines()
420 // Delete all screen lines.
421 screen_lines_
.clear();
423 /// @todo Save and restore scroll afterwards.
424 for (std::size_t i
= 0, advice
= 0; i
< lines_
.size(); ++i
)
425 advice
= updateScreenLines(i
, advice
);
428 std::size_t TextView::eraseScreenLines(
429 std::size_t line_num
, std::size_t start
, std::size_t *deleted
)
431 assert(line_num
< lines_
.size());
432 assert(start
<= screen_lines_
.size());
434 std::size_t i
= start
;
435 bool begin_set
= false, end_set
= false;
436 // Note, the assigment to the begin variable is only to silence a compiler
437 // warning. The use of the variable is protected by the begin_set variable.
438 std::size_t begin
= 0, end
;
439 while (i
< screen_lines_
.size()) {
440 if (screen_lines_
[i
].parent
== lines_
[line_num
]) {
446 else if (begin_set
) {
455 end
= screen_lines_
.size();
457 screen_lines_
.begin() + begin
, screen_lines_
.begin() + end
);
459 if (deleted
!= nullptr)
460 *deleted
= end
- begin
;
462 else if (deleted
!= nullptr)
468 void TextView::actionScroll(int direction
)
470 if (screen_lines_
.size() <= static_cast<unsigned>(real_height_
))
473 unsigned s
= abs(direction
) * ((real_height_
+ 1) / 2);
481 if (view_top_
+ s
> screen_lines_
.size() - real_height_
)
482 view_top_
= screen_lines_
.size() - real_height_
;
487 autoscroll_suspended_
= screen_lines_
.size() > view_top_
+ real_height_
;
491 void TextView::declareBindables()
493 declareBindable("textview", "scroll-up",
494 sigc::bind(sigc::mem_fun(this, &TextView::actionScroll
), -1),
495 InputProcessor::BINDABLE_NORMAL
);
497 declareBindable("textview", "scroll-down",
498 sigc::bind(sigc::mem_fun(this, &TextView::actionScroll
), 1),
499 InputProcessor::BINDABLE_NORMAL
);
502 } // namespace CppConsUI
504 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: