Update list of wide characters
[centerim5.git] / cppconsui / TextView.cpp
blob87b1badfd8a9cab74366b552242035092b483cae
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 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);
214 redraw();
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);
225 redraw();
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)
238 delete lines_[i];
239 lines_.erase(lines_.begin() + start_line, lines_.begin() + end_line);
241 redraw();
244 void TextView::clear()
246 for (Line *line : lines_)
247 delete line;
248 lines_.clear();
250 screen_lines_.clear();
252 redraw();
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_)
270 return;
272 autoscroll_ = new_autoscroll;
273 redraw();
276 void TextView::setScrollBar(bool new_scrollbar)
278 if (new_scrollbar == scrollbar_)
279 return;
281 scrollbar_ = new_scrollbar;
282 updateAllScreenLines();
283 redraw();
286 TextView::Line::Line(const char *text_, std::size_t bytes, int color_)
287 : color(color_)
289 assert(text_ != nullptr);
291 text = new char[bytes + 1];
292 std::strncpy(text, text_, bytes);
293 text[bytes] = '\0';
295 length = 0;
296 const char *p = text;
297 while (p != nullptr && *p != '\0') {
298 ++length;
299 p = UTF8::getNextChar(p);
303 TextView::Line::~Line()
305 delete[] text;
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;
327 int prev_width = 0;
328 int cur_width = 0;
329 int cur_length = 0;
330 bool space = false;
331 *res_length = 0;
333 while (*cur != '\0') {
334 prev_width = cur_width;
335 UTF8::UniChar uc = UTF8::getUniChar(cur);
336 cur_width += Curses::onScreenWidth(uc, cur_width);
337 ++cur_length;
339 if (prev_width > area_width)
340 break;
342 // Possibly too long word.
343 if (cur_width > area_width && *res_length == 0) {
344 *res_length = cur_length - 1;
345 res = cur;
348 if (UTF8::isUniCharSpace(uc))
349 space = true;
350 else if (space) {
351 // Found start of a word and everything before that can fit into one
352 // screen line.
353 *res_length = cur_length - 1;
354 res = cur;
355 space = false;
358 cur = UTF8::getNextChar(cur);
361 // End of text.
362 if (*cur == '\0' && cur_width <= area_width) {
363 *res_length = cur_length;
364 res = cur;
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.
371 if (res == text)
372 res = UTF8::getNextChar(res);
374 return 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;
390 const char *s;
392 int realw = real_width_;
393 if (scrollbar_ && realw > 2) {
394 // Scrollbar shrinks the width of the view area.
395 realw -= 2;
398 if (realw <= 0)
399 return 0;
401 int len;
402 while (*p != '\0') {
403 s = p;
404 p = proceedLine(p, realw, &len);
405 new_lines.push_back(ScreenLine(*lines_[line_num], s, len));
408 // Empty line.
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());
415 return res;
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]) {
441 if (!begin_set) {
442 begin = i;
443 begin_set = true;
446 else if (begin_set) {
447 end = i;
448 end_set = true;
449 break;
451 ++i;
453 if (begin_set) {
454 if (!end_set)
455 end = screen_lines_.size();
456 screen_lines_.erase(
457 screen_lines_.begin() + begin, screen_lines_.begin() + end);
458 i -= end - begin;
459 if (deleted != nullptr)
460 *deleted = end - begin;
462 else if (deleted != nullptr)
463 *deleted = 0;
465 return i;
468 void TextView::actionScroll(int direction)
470 if (screen_lines_.size() <= static_cast<unsigned>(real_height_))
471 return;
473 unsigned s = abs(direction) * ((real_height_ + 1) / 2);
474 if (direction < 0) {
475 if (view_top_ < s)
476 view_top_ = 0;
477 else
478 view_top_ -= s;
480 else {
481 if (view_top_ + s > screen_lines_.size() - real_height_)
482 view_top_ = screen_lines_.size() - real_height_;
483 else
484 view_top_ += s;
487 autoscroll_suspended_ = screen_lines_.size() > view_top_ + real_height_;
488 redraw();
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: