Update list of wide characters
[centerim5.git] / cppconsui / Container.cpp
blob26aa3de660d4182878de341808f82edd3eea1f72
1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
3 //
4 // This file is part of CenterIM.
5 //
6 // CenterIM is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // CenterIM is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
19 /// @file
20 /// Container class implementation.
21 ///
22 /// @ingroup cppconsui
24 #include "Container.h"
26 #include "ColorScheme.h"
28 #include <algorithm>
29 #include <cassert>
31 namespace CppConsUI {
33 Container::Container(int w, int h)
34 : Widget(w, h), scroll_xpos_(0), scroll_ypos_(0), border_(0),
35 focus_cycle_scope_(FOCUS_CYCLE_GLOBAL), update_focus_chain_(false),
36 page_focus_(false), focus_child_(nullptr)
38 declareBindables();
41 Container::~Container()
43 cleanFocus();
45 clear();
48 int Container::draw(Curses::ViewPort area, Error &error)
50 if (real_width_ <= 0 || real_height_ <= 0)
51 return 0;
53 if (area.getViewWidth() <= 0 || area.getViewHeight() <= 0)
54 return 0;
56 area.scroll(scroll_xpos_, scroll_ypos_);
57 int attrs;
58 DRAW(
59 getAttributes(ColorScheme::PROPERTY_CONTAINER_BACKGROUND, &attrs, error));
60 DRAW(area.fill(attrs, error));
62 for (Widget *widget : children_)
63 if (widget->isVisible())
64 DRAW(drawChild(*widget, area, error));
66 return 0;
69 Widget *Container::getFocusWidget()
71 if (focus_child_ != nullptr)
72 return focus_child_->getFocusWidget();
73 return nullptr;
76 void Container::cleanFocus()
78 if (focus_child_ == nullptr) {
79 // There is no widget with focus because the chain ends here.
80 return;
83 // First propagate focus stealing to the widget with focus.
84 focus_child_->cleanFocus();
85 focus_child_ = nullptr;
86 clearInputChild();
89 bool Container::restoreFocus()
91 if (focus_child_ != nullptr)
92 return focus_child_->restoreFocus();
93 return false;
96 bool Container::grabFocus()
98 for (Widget *widget : children_)
99 if (widget->grabFocus())
100 return true;
101 return false;
104 void Container::ungrabFocus()
106 if (focus_child_ != nullptr)
107 focus_child_->ungrabFocus();
110 void Container::setParent(Container &parent)
112 // The parent will take care about focus changing and focus chain caching from
113 // now on.
114 focus_chain_.clear();
116 Widget::setParent(parent);
119 void Container::addWidget(Widget &widget, int x, int y)
121 insertWidget(children_.size(), widget, x, y);
124 void Container::removeWidget(Widget &widget)
126 assert(widget.getParent() == this);
127 Widgets::iterator i = findWidget(widget);
128 assert(i != children_.end());
130 delete *i;
131 children_.erase(i);
134 void Container::moveWidgetBefore(Widget &widget, Widget &position)
136 moveWidget(widget, position, false);
139 void Container::moveWidgetAfter(Widget &widget, Widget &position)
141 moveWidget(widget, position, true);
144 void Container::clear()
146 while (!children_.empty())
147 removeWidget(*children_.front());
150 bool Container::isWidgetVisible(const Widget & /*widget*/) const
152 if (parent_ == nullptr || !visible_)
153 return false;
155 return parent_->isWidgetVisible(*this);
158 bool Container::setFocusChild(Widget &child)
160 // Focus cannot be set for widget without a parent.
161 if (parent_ == nullptr || !visible_)
162 return false;
164 bool res = parent_->setFocusChild(*this);
165 focus_child_ = &child;
166 setInputChild(child);
167 updateScroll();
168 return res;
171 void Container::getFocusChain(
172 FocusChain &focus_chain, FocusChain::iterator parent)
174 for (Widget *widget : children_) {
175 Container *container = dynamic_cast<Container *>(widget);
177 if (container != nullptr && container->isVisible()) {
178 // The widget is a container so add its widgets as well.
179 FocusChain::pre_order_iterator iter =
180 focus_chain.append_child(parent, container);
181 container->getFocusChain(focus_chain, iter);
183 // If this is not a focusable widget and it has no focusable children,
184 // remove it from the chain.
185 if (focus_chain.number_of_children(iter) == 0)
186 focus_chain.erase(iter);
188 else if ((widget->canFocus() && widget->isVisible()) ||
189 widget == focus_child_) {
190 // Widget can be focused or is focused already.
191 focus_chain.append_child(parent, widget);
196 void Container::updateFocusChain()
198 if (parent_ != nullptr) {
199 parent_->updateFocusChain();
200 return;
202 update_focus_chain_ = true;
205 void Container::moveFocus(FocusDirection direction)
207 // Make sure we always start at the root of the widget tree, things are a bit
208 // easier then.
209 if (parent_ != nullptr) {
210 parent_->moveFocus(direction);
211 return;
214 if (update_focus_chain_) {
215 focus_chain_.clear();
216 focus_chain_.set_head(this);
217 getFocusChain(focus_chain_, focus_chain_.begin());
218 update_focus_chain_ = false;
221 FocusChain::pre_order_iterator iter = ++focus_chain_.begin();
222 Widget *focus_widget = getFocusWidget();
224 if (focus_widget != nullptr) {
225 iter = std::find(focus_chain_.begin(), focus_chain_.end(), focus_widget);
227 // A focused widget is present but it could not be found.
228 assert(iter != focus_chain_.end());
230 Widget *widget = *iter;
231 if (!widget->isVisibleRecursive()) {
232 // Currently focused widget is no longer visible, moveFocus() was called
233 // to fix it.
235 // Try to change focus locally first.
236 FocusChain::pre_order_iterator parent_iter = focus_chain_.parent(iter);
237 iter = focus_chain_.erase(iter);
238 FocusChain::pre_order_iterator i = iter;
239 while (i != parent_iter.end()) {
240 if ((*i)->canFocus())
241 break;
242 ++i;
244 if (i == parent_iter.end())
245 for (i = parent_iter.begin(); i != iter; ++i)
246 if ((*i)->canFocus())
247 break;
248 if (i != parent_iter.end() && (*i)->canFocus()) {
249 // Local focus change was successful.
251 // Be sane.
252 assert((*i)->isVisibleRecursive());
254 (*i)->grabFocus();
255 return;
258 // Focus widget could not be changed in the local scope, give focus to any
259 // widget.
260 cleanFocus();
261 focus_widget = nullptr;
265 if (focus_widget == nullptr) {
266 // There is no node assigned to receive focus so give focus to the first
267 // widget.
268 FocusChain::pre_order_iterator i = iter;
269 while (i != focus_chain_.end()) {
270 if ((*i)->canFocus())
271 break;
272 ++i;
274 if (i == focus_chain_.end())
275 for (i = ++focus_chain_.begin(); i != iter; ++i)
276 if ((*i)->canFocus())
277 break;
279 if (i != focus_chain_.end() && (*i)->canFocus()) {
280 // Be sane.
281 assert((*i)->isVisibleRecursive());
283 (*i)->grabFocus();
286 return;
289 // At this point, focus_widget is non-nullptr and iter represents focus_widget
290 // in focus_chain_. Also the currently focused widget is visible, so if there
291 // is not any other widget that can take the focus then the focused widget can
292 // remain the same.
294 // Search for a parent that has set local or none focus cycling.
295 FocusCycleScope scope = FOCUS_CYCLE_GLOBAL;
296 FocusChain::pre_order_iterator cycle_begin, cycle_end, parent_iter;
297 parent_iter = focus_chain_.parent(iter);
298 while (parent_iter != focus_chain_.begin()) {
299 Container *c = dynamic_cast<Container *>(*parent_iter);
300 assert(c != nullptr);
301 scope = c->getFocusCycle();
302 if (scope == FOCUS_CYCLE_LOCAL || scope == FOCUS_CYCLE_NONE)
303 break;
304 parent_iter = focus_chain_.parent(parent_iter);
306 Container *container = dynamic_cast<Container *>(*parent_iter);
307 assert(container != nullptr);
309 if (direction == FOCUS_PAGE_UP || direction == FOCUS_PAGE_DOWN) {
310 // Get rid off "dummy" containers in the chain, i.e. container that has only
311 // one child. This is needed to get a correct container for paging.
312 while (parent_iter.number_of_children() == 1 &&
313 !(*parent_iter.begin())->canFocus())
314 parent_iter = parent_iter.begin();
315 container = dynamic_cast<Container *>(*parent_iter);
316 assert(container != nullptr);
318 // Stop here if focus change via paging is requested but container does not
319 // support it.
320 if (!container->canPageFocus())
321 return;
323 // Paging is requested, force no cycling.
324 scope = FOCUS_CYCLE_NONE;
327 if (scope == FOCUS_CYCLE_GLOBAL) {
328 // Global focus cycling is allowed (cycling amongst all widgets in the
329 // window).
330 cycle_begin = ++focus_chain_.begin();
331 cycle_end = focus_chain_.end();
333 else if (scope == FOCUS_CYCLE_LOCAL) {
334 // Local focus cycling is allowed (cycling amongs all widgets of the parent
335 // container).
336 cycle_begin = parent_iter.begin();
337 cycle_end = parent_iter.end();
339 else {
340 // None focus cycling is allowed.
343 // Find a correct widget to focus.
344 int max, init, cur;
345 if (direction == FOCUS_PAGE_UP || direction == FOCUS_PAGE_DOWN) {
346 max = container->getRealHeight() / 2;
347 init = focus_widget->getRelativePosition(*container).getY();
349 else
350 max = init = cur = 0;
352 switch (direction) {
353 case FOCUS_PREVIOUS:
354 case FOCUS_UP:
355 case FOCUS_LEFT:
356 case FOCUS_PAGE_UP:
357 // Finally, find the previous widget which will get the focus.
358 do {
359 // If no focus cycling is allowed, stop if the widget with focus is the
360 // first/last child.
361 if (scope == FOCUS_CYCLE_NONE && iter == parent_iter.begin())
362 goto end;
364 if (iter == cycle_begin)
365 iter = cycle_end;
366 --iter;
368 if (direction == FOCUS_PAGE_UP)
369 cur = (*iter)->getRelativePosition(*container).getY();
370 } while (!(*iter)->canFocus() || init - cur < max);
372 break;
373 case FOCUS_NEXT:
374 case FOCUS_DOWN:
375 case FOCUS_RIGHT:
376 case FOCUS_PAGE_DOWN:
377 // Finally, find the next widget which will get the focus.
378 do {
379 if (scope == FOCUS_CYCLE_NONE) {
380 // parent_iter.end() returns a sibling_iterator, it has to be converted
381 // to pre_order_iterator first...
382 FocusChain::pre_order_iterator tmp = parent_iter.end();
383 --tmp;
384 if (iter == tmp)
385 goto end;
388 ++iter;
389 if (iter == cycle_end)
390 iter = cycle_begin;
392 if (direction == FOCUS_PAGE_DOWN)
393 cur = (*iter)->getRelativePosition(*container).getY();
394 } while (!(*iter)->canFocus() || cur - init < max);
396 break;
397 case FOCUS_BEGIN:
398 iter = parent_iter.begin();
399 while (iter != parent_iter.end()) {
400 if ((*iter)->canFocus())
401 goto end;
402 ++iter;
404 // There is always one widget that can get the focus so this code is
405 // unreachable.
406 assert(0);
407 break;
408 case FOCUS_END:
409 iter = parent_iter.end();
410 --iter;
411 break;
414 end:
415 // Grab the focus.
416 (*iter)->grabFocus();
419 Point Container::getRelativePosition(
420 const Container &ref, const Widget &child) const
422 assert(child.getParent() == this);
424 int child_x = child.getRealLeft();
425 int child_y = child.getRealTop();
427 if (child_x != UNSETPOS && child_y != UNSETPOS) {
428 child_x -= scroll_xpos_;
429 child_y -= scroll_ypos_;
431 if (parent_ == nullptr || this == &ref)
432 return Point(child_x, child_y);
434 Point p = parent_->getRelativePosition(ref, *this);
435 if (p.getX() != UNSETPOS && p.getY() != UNSETPOS)
436 return Point(p.getX() + child_x, p.getY() + child_y);
438 return Point(UNSETPOS, UNSETPOS);
441 Point Container::getAbsolutePosition(const Widget &child) const
443 assert(child.getParent() == this);
445 int child_x = child.getRealLeft();
446 int child_y = child.getRealTop();
448 if (child_x != UNSETPOS && child_y != UNSETPOS) {
449 child_x -= scroll_xpos_;
450 child_y -= scroll_ypos_;
452 if (parent_ != nullptr) {
453 Point p = parent_->getAbsolutePosition(*this);
454 if (p.getX() != UNSETPOS && p.getY() != UNSETPOS)
455 return Point(p.getX() + child_x, p.getY() + child_y);
459 return Point(UNSETPOS, UNSETPOS);
462 void Container::onChildMoveResize(
463 Widget &activator, const Rect & /*oldsize*/, const Rect &newsize)
465 // Can be called only by a real child.
466 assert(activator.getParent() == this);
468 // Update child real size.
469 activator.setRealPosition(newsize.getLeft(), newsize.getTop());
470 updateChildArea(activator);
473 void Container::onChildWishSizeChange(
474 Widget &activator, const Size & /*oldsize*/, const Size & /*newsize*/)
476 // Can be called only by a real child.
477 assert(activator.getParent() == this);
479 // Update child real size.
480 updateChildArea(activator);
483 void Container::onChildVisible(Widget &activator, bool /*visible*/)
485 // Can be called only by a real child.
486 assert(activator.getParent() == this);
489 void Container::updateArea()
491 // Update all child areas.
492 for (Widget *widget : children_)
493 updateChildArea(*widget);
496 void Container::updateAreaPostRealSizeChange(
497 const Size &oldsize, const Size &newsize)
499 // If the new size is bigger than the old one then adjust the scroll in a way
500 // that the area on the left/top gets revealed.
501 bool scrolled = false;
502 if (scroll_xpos_ > 0) {
503 int x_diff = newsize.getWidth() - oldsize.getWidth();
504 if (x_diff > 0) {
505 scroll_xpos_ -= std::min(x_diff, scroll_xpos_);
506 scrolled = true;
509 if (scroll_ypos_ > 0) {
510 int y_diff = newsize.getHeight() - oldsize.getHeight();
511 if (y_diff > 0) {
512 scroll_ypos_ -= std::min(y_diff, scroll_ypos_);
513 scrolled = true;
516 if (scrolled)
517 redraw();
519 // Finish normal post-setRealSize() processing.
520 Widget::updateAreaPostRealSizeChange(oldsize, newsize);
523 void Container::updateChildArea(Widget &child)
525 int child_x = child.getRealLeft();
526 int child_y = child.getRealTop();
528 if (child_x == UNSETPOS || child_y == UNSETPOS) {
529 child.setRealSize(0, 0);
530 return;
533 int max_width = real_width_ - border_;
534 int max_height = real_height_ - border_;
536 int child_width = child.getWidth();
537 int child_height = child.getHeight();
539 if (child_width == AUTOSIZE)
540 child_width = child.getWishWidth();
541 if (child_height == AUTOSIZE)
542 child_height = child.getWishHeight();
544 // Extend the requested area to the whole parent area or shrink the requested
545 // area if necessary.
546 if (child_width == AUTOSIZE || child_width > max_width - child_x)
547 child_width = max_width - child_x;
549 if (child_height == AUTOSIZE || child_height > max_height - child_y)
550 child_height = max_height - child_y;
552 if (child_width > 0 && child_height > 0)
553 child.setRealSize(child_width, child_height);
554 else
555 child.setRealSize(0, 0);
558 int Container::drawChild(Widget &child, Curses::ViewPort area, Error &error)
560 int view_x = area.getViewLeft();
561 int view_y = area.getViewTop();
562 int view_width = area.getViewWidth();
563 int view_height = area.getViewHeight();
565 int view_x2 = view_x + view_width;
566 int view_y2 = view_y + view_height;
568 int child_x = child.getRealLeft();
569 int child_y = child.getRealTop();
570 int child_width = child.getRealWidth();
571 int child_height = child.getRealHeight();
572 int child_x2 = child_x + child_width;
573 int child_y2 = child_y + child_height;
575 int child_screen_x = area.getScreenLeft() + child_x - view_x;
576 int child_screen_y = area.getScreenTop() + child_y - view_y;
578 int child_view_x = 0;
579 int child_view_y = 0;
580 int child_view_width = child_width;
581 int child_view_height = child_height;
583 // Calculate a viewport for the child.
584 if (view_x > child_x) {
585 child_view_x = view_x - child_x;
586 child_screen_x += child_view_x;
587 if (child_view_x > child_width)
588 child_view_x = child_width;
589 child_view_width -= child_view_x;
591 if (view_y > child_y) {
592 child_view_y = view_y - child_y;
593 child_screen_y += child_view_y;
594 if (child_view_y > child_height)
595 child_view_y = child_height;
596 child_view_height -= child_view_y;
599 if (child_x2 > view_x2) {
600 child_view_width -= child_x2 - view_x2;
601 if (child_view_width < 0)
602 child_view_width = 0;
604 if (child_y2 > view_y2) {
605 child_view_height -= child_y2 - view_y2;
606 if (child_view_height < 0)
607 child_view_height = 0;
610 Curses::ViewPort child_area(child_screen_x, child_screen_y, child_view_x,
611 child_view_y, child_view_width, child_view_height);
612 return child.draw(child_area, error);
615 Widgets::iterator Container::findWidget(const Widget &widget)
617 return std::find(children_.begin(), children_.end(), &widget);
620 void Container::insertWidget(std::size_t pos, Widget &widget, int x, int y)
622 assert(pos <= children_.size());
624 widget.move(x, y);
626 // Insert a widget early into children vector so the widget can grab the focus
627 // in setParent() method if it detects that there is not any focused widget.
628 children_.insert(children_.begin() + pos, &widget);
629 widget.setParent(*this);
630 widget.setRealPosition(widget.getLeft(), widget.getTop());
631 updateChildArea(widget);
634 void Container::moveWidget(Widget &widget, Widget &position, bool after)
636 assert(widget.getParent() == this);
637 assert(position.getParent() == this);
639 // Remove widget from the children..
640 Widgets::iterator widget_iter = findWidget(widget);
641 assert(widget_iter != children_.end());
642 children_.erase(widget_iter);
644 // ..put it back into a correct position.
645 Widgets::iterator position_iter = findWidget(position);
646 assert(position_iter != children_.end());
647 if (after)
648 ++position_iter;
649 children_.insert(position_iter, &widget);
651 updateFocusChain();
653 // Need redraw if the widgets overlap.
654 redraw();
657 void Container::updateScroll()
659 if (focus_child_ == nullptr)
660 return;
662 int x = focus_child_->getRealLeft();
663 int y = focus_child_->getRealTop();
664 if (x == UNSETPOS || y == UNSETPOS)
665 return;
667 int w = focus_child_->getRealWidth();
668 int h = focus_child_->getRealHeight();
669 bool scrolled_a, scrolled_b;
670 scrolled_a = makePointVisible(x + w - 1, y + h - 1);
671 scrolled_b = makePointVisible(x, y);
673 if (!scrolled_a && !scrolled_b)
674 return;
676 redraw();
677 signalAbsolutePositionChange();
680 bool Container::makePointVisible(int x, int y)
682 bool scrolled_x = true;
683 if (real_width_ == 0)
684 scroll_xpos_ = 0;
685 else if (x < scroll_xpos_)
686 scroll_xpos_ = x;
687 else if (x > scroll_xpos_ + real_width_ - 1)
688 scroll_xpos_ = x - real_width_ + 1;
689 else
690 scrolled_x = false;
692 bool scrolled_y = true;
693 if (real_height_ == 0)
694 scroll_ypos_ = 0;
695 else if (y < scroll_ypos_)
696 scroll_ypos_ = y;
697 else if (y > scroll_ypos_ + real_height_ - 1)
698 scroll_ypos_ = y - real_height_ + 1;
699 else
700 scrolled_y = false;
702 return scrolled_x || scrolled_y;
705 void Container::declareBindables()
707 declareBindable("container", "focus-previous",
708 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
709 Container::FOCUS_PREVIOUS),
710 InputProcessor::BINDABLE_NORMAL);
711 declareBindable("container", "focus-next",
712 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
713 Container::FOCUS_NEXT),
714 InputProcessor::BINDABLE_NORMAL);
715 declareBindable("container", "focus-up",
716 sigc::bind(sigc::mem_fun(this, &Container::moveFocus), Container::FOCUS_UP),
717 InputProcessor::BINDABLE_NORMAL);
718 declareBindable("container", "focus-down",
719 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
720 Container::FOCUS_DOWN),
721 InputProcessor::BINDABLE_NORMAL);
722 declareBindable("container", "focus-left",
723 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
724 Container::FOCUS_LEFT),
725 InputProcessor::BINDABLE_NORMAL);
726 declareBindable("container", "focus-right",
727 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
728 Container::FOCUS_RIGHT),
729 InputProcessor::BINDABLE_NORMAL);
730 declareBindable("container", "focus-page-up",
731 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
732 Container::FOCUS_PAGE_UP),
733 InputProcessor::BINDABLE_NORMAL);
734 declareBindable("container", "focus-page-down",
735 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
736 Container::FOCUS_PAGE_DOWN),
737 InputProcessor::BINDABLE_NORMAL);
738 declareBindable("container", "focus-begin",
739 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
740 Container::FOCUS_BEGIN),
741 InputProcessor::BINDABLE_NORMAL);
742 declareBindable("container", "focus-end",
743 sigc::bind(sigc::mem_fun(this, &Container::moveFocus),
744 Container::FOCUS_END),
745 InputProcessor::BINDABLE_NORMAL);
748 } // namespace CppConsUI
750 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: