Fix build on systems that have a separate libintl library
[centerim5.git] / cppconsui / TextView.cpp
blobff57d2fc514690fdd81ffd258adc4b8a7e0b8ef6
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 /// TextView class implementation.
20 ///
21 /// @ingroup cppconsui
23 #include "TextView.h"
25 #include "ColorScheme.h"
27 #include <cassert>
28 #include <cstdio>
29 #include <cstring>
31 namespace CppConsUI {
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)
37 can_focus_ = true;
38 declareBindables();
41 TextView::~TextView()
43 clear();
46 int TextView::draw(Curses::ViewPort area, Error &error)
48 DRAW(area.erase(error));
50 if (screen_lines_.size() <= static_cast<unsigned>(real_height_)) {
51 view_top_ = 0;
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_;
61 int attrs;
62 DRAW(getAttributes(ColorScheme::PROPERTY_TEXTVIEW_TEXT, &attrs, error));
63 DRAW(area.attrOn(attrs, error));
65 ScreenLines::iterator i;
66 int j;
67 for (i = screen_lines_.begin() + view_top_, j = 0;
68 i != screen_lines_.end() && j < real_height_; ++i, ++j) {
69 int attrs2 = 0;
70 if (i->parent->color != 0) {
71 DRAW(getAttributes(
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;
78 int w = 0;
79 for (int k = 0; k < i->length; ++k) {
80 UTF8::UniChar uc = UTF8::getUniChar(p);
81 int printed;
82 if (uc == '\t') {
83 printed = Curses::onScreenWidth(uc, w);
84 for (int l = 0; l < printed; ++l)
85 DRAW(area.addChar(w + l, j, ' ', error));
87 else
88 DRAW(area.addChar(w, j, uc, error, &printed));
89 w += 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));
101 // Draw scrollbar.
102 if (scrollbar_) {
103 int x1, x2;
104 if (screen_lines_.size() <= static_cast<unsigned>(real_height_)) {
105 x1 = 0;
106 x2 = real_height_;
108 else {
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
112 // during rounding.
113 x1 = x2 - real_height_ * real_height_ / screen_lines_.size();
116 int attrs;
117 DRAW(
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));
125 if (x2 - x1 < 2) {
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));
135 else if (x2 < 2) {
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));
140 else {
141 // In between.
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));
148 else {
149 // Scrollbar length is big enough to fit two arrows.
150 DRAW(area.addLineChar(real_width_ - 1, x1, Curses::LINE_UARROW, error));
151 DRAW(
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));
159 if (view_top_ == 0)
160 DRAW(area.addLineChar(real_width_ - 1, 0, Curses::LINE_BULLET, error));
162 DRAW(area.attrOff(attrs, error));
166 char pos[128];
167 g_snprintf(pos, sizeof(pos), "%d/%d ", view_top_, screen_lines_.size());
168 DRAW(area.addString(0, 0, pos));
171 return 0;
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)
181 if (text == nullptr)
182 return;
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;
190 // Parse lines.
191 while (*p != '\0') {
192 if (*p == '\n') {
193 auto l = new Line(s, p - s, color);
194 lines_.insert(lines_.begin() + cur_line_num, l);
195 ++cur_line_num;
196 s = p = UTF8::getNextChar(p);
197 continue;
200 p = UTF8::getNextChar(p);
203 if (s < p) {
204 auto l = new Line(s, p - s, color);
205 lines_.insert(lines_.begin() + cur_line_num, l);
206 ++cur_line_num;
209 // Update screen lines.
210 for (std::size_t i = line_num, advice = 0; i < cur_line_num; ++i)
211 advice = updateScreenLines(i, advice);
213 redraw();
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);
224 redraw();
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)
237 delete lines_[i];
238 lines_.erase(lines_.begin() + start_line, lines_.begin() + end_line);
240 redraw();
243 void TextView::clear()
245 for (Line *line : lines_)
246 delete line;
247 lines_.clear();
249 screen_lines_.clear();
251 redraw();
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_)
269 return;
271 autoscroll_ = new_autoscroll;
272 redraw();
275 void TextView::setScrollBar(bool new_scrollbar)
277 if (new_scrollbar == scrollbar_)
278 return;
280 scrollbar_ = new_scrollbar;
281 updateAllScreenLines();
282 redraw();
285 TextView::Line::Line(const char *text_, std::size_t bytes, int color_)
286 : color(color_)
288 assert(text_ != nullptr);
290 text = new char[bytes + 1];
291 std::strncpy(text, text_, bytes);
292 text[bytes] = '\0';
294 length = 0;
295 const char *p = text;
296 while (p != nullptr && *p != '\0') {
297 ++length;
298 p = UTF8::getNextChar(p);
302 TextView::Line::~Line()
304 delete[] text;
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;
326 int prev_width = 0;
327 int cur_width = 0;
328 int cur_length = 0;
329 bool space = false;
330 *res_length = 0;
332 while (*cur != '\0') {
333 prev_width = cur_width;
334 UTF8::UniChar uc = UTF8::getUniChar(cur);
335 cur_width += Curses::onScreenWidth(uc, cur_width);
336 ++cur_length;
338 if (prev_width > area_width)
339 break;
341 // Possibly too long word.
342 if (cur_width > area_width && *res_length == 0) {
343 *res_length = cur_length - 1;
344 res = cur;
347 if (UTF8::isUniCharSpace(uc))
348 space = true;
349 else if (space) {
350 // Found start of a word and everything before that can fit into one
351 // screen line.
352 *res_length = cur_length - 1;
353 res = cur;
354 space = false;
357 cur = UTF8::getNextChar(cur);
360 // End of text.
361 if (*cur == '\0' && cur_width <= area_width) {
362 *res_length = cur_length;
363 res = cur;
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.
370 if (res == text)
371 res = UTF8::getNextChar(res);
373 return 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;
389 const char *s;
391 int realw = real_width_;
392 if (scrollbar_ && realw > 2) {
393 // Scrollbar shrinks the width of the view area.
394 realw -= 2;
397 if (realw <= 0)
398 return 0;
400 int len;
401 while (*p != '\0') {
402 s = p;
403 p = proceedLine(p, realw, &len);
404 new_lines.push_back(ScreenLine(*lines_[line_num], s, len));
407 // Empty line.
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());
414 return res;
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]) {
440 if (!begin_set) {
441 begin = i;
442 begin_set = true;
445 else if (begin_set) {
446 end = i;
447 end_set = true;
448 break;
450 ++i;
452 if (begin_set) {
453 if (!end_set)
454 end = screen_lines_.size();
455 screen_lines_.erase(
456 screen_lines_.begin() + begin, screen_lines_.begin() + end);
457 i -= end - begin;
458 if (deleted != nullptr)
459 *deleted = end - begin;
461 else if (deleted != nullptr)
462 deleted = nullptr;
464 return i;
467 void TextView::actionScroll(int direction)
469 if (screen_lines_.size() <= static_cast<unsigned>(real_height_))
470 return;
472 unsigned s = abs(direction) * ((real_height_ + 1) / 2);
473 if (direction < 0) {
474 if (view_top_ < s)
475 view_top_ = 0;
476 else
477 view_top_ -= s;
479 else {
480 if (view_top_ + s > screen_lines_.size() - real_height_)
481 view_top_ = screen_lines_.size() - real_height_;
482 else
483 view_top_ += s;
486 autoscroll_suspended_ = screen_lines_.size() > view_top_ + real_height_;
487 redraw();
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: