1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
4 // This file is part of CenterIM.
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/>.
20 /// Container class implementation.
22 /// @ingroup cppconsui
24 #include "Container.h"
26 #include "ColorScheme.h"
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)
41 Container::~Container()
48 int Container::draw(Curses::ViewPort area
, Error
&error
)
50 if (real_width_
<= 0 || real_height_
<= 0)
53 if (area
.getViewWidth() <= 0 || area
.getViewHeight() <= 0)
56 area
.scroll(scroll_xpos_
, scroll_ypos_
);
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
));
69 Widget
*Container::getFocusWidget()
71 if (focus_child_
!= nullptr)
72 return focus_child_
->getFocusWidget();
76 void Container::cleanFocus()
78 if (focus_child_
== nullptr) {
79 // There is no widget with focus because the chain ends here.
83 // First propagate focus stealing to the widget with focus.
84 focus_child_
->cleanFocus();
85 focus_child_
= nullptr;
89 bool Container::restoreFocus()
91 if (focus_child_
!= nullptr)
92 return focus_child_
->restoreFocus();
96 bool Container::grabFocus()
98 for (Widget
*widget
: children_
)
99 if (widget
->grabFocus())
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
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());
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_
)
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_
)
164 bool res
= parent_
->setFocusChild(*this);
165 focus_child_
= &child
;
166 setInputChild(child
);
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();
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
209 if (parent_
!= nullptr) {
210 parent_
->moveFocus(direction
);
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
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())
244 if (i
== parent_iter
.end())
245 for (i
= parent_iter
.begin(); i
!= iter
; ++i
)
246 if ((*i
)->canFocus())
248 if (i
!= parent_iter
.end() && (*i
)->canFocus()) {
249 // Local focus change was successful.
252 assert((*i
)->isVisibleRecursive());
258 // Focus widget could not be changed in the local scope, give focus to any
261 focus_widget
= nullptr;
265 if (focus_widget
== nullptr) {
266 // There is no node assigned to receive focus so give focus to the first
268 FocusChain::pre_order_iterator i
= iter
;
269 while (i
!= focus_chain_
.end()) {
270 if ((*i
)->canFocus())
274 if (i
== focus_chain_
.end())
275 for (i
= ++focus_chain_
.begin(); i
!= iter
; ++i
)
276 if ((*i
)->canFocus())
279 if (i
!= focus_chain_
.end() && (*i
)->canFocus()) {
281 assert((*i
)->isVisibleRecursive());
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
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
)
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
320 if (!container
->canPageFocus())
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
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
336 cycle_begin
= parent_iter
.begin();
337 cycle_end
= parent_iter
.end();
340 // None focus cycling is allowed.
343 // Find a correct widget to focus.
345 if (direction
== FOCUS_PAGE_UP
|| direction
== FOCUS_PAGE_DOWN
) {
346 max
= container
->getRealHeight() / 2;
347 init
= focus_widget
->getRelativePosition(*container
).getY();
350 max
= init
= cur
= 0;
357 // Finally, find the previous widget which will get the focus.
359 // If no focus cycling is allowed, stop if the widget with focus is the
361 if (scope
== FOCUS_CYCLE_NONE
&& iter
== parent_iter
.begin())
364 if (iter
== cycle_begin
)
368 if (direction
== FOCUS_PAGE_UP
)
369 cur
= (*iter
)->getRelativePosition(*container
).getY();
370 } while (!(*iter
)->canFocus() || init
- cur
< max
);
376 case FOCUS_PAGE_DOWN
:
377 // Finally, find the next widget which will get the focus.
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();
389 if (iter
== cycle_end
)
392 if (direction
== FOCUS_PAGE_DOWN
)
393 cur
= (*iter
)->getRelativePosition(*container
).getY();
394 } while (!(*iter
)->canFocus() || cur
- init
< max
);
398 iter
= parent_iter
.begin();
399 while (iter
!= parent_iter
.end()) {
400 if ((*iter
)->canFocus())
404 // There is always one widget that can get the focus so this code is
409 iter
= parent_iter
.end();
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();
505 scroll_xpos_
-= std::min(x_diff
, scroll_xpos_
);
509 if (scroll_ypos_
> 0) {
510 int y_diff
= newsize
.getHeight() - oldsize
.getHeight();
512 scroll_ypos_
-= std::min(y_diff
, scroll_ypos_
);
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);
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
);
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());
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());
649 children_
.insert(position_iter
, &widget
);
653 // Need redraw if the widgets overlap.
657 void Container::updateScroll()
659 if (focus_child_
== nullptr)
662 int x
= focus_child_
->getRealLeft();
663 int y
= focus_child_
->getRealTop();
664 if (x
== UNSETPOS
|| y
== UNSETPOS
)
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
)
677 signalAbsolutePositionChange();
680 bool Container::makePointVisible(int x
, int y
)
682 bool scrolled_x
= true;
683 if (real_width_
== 0)
685 else if (x
< scroll_xpos_
)
687 else if (x
> scroll_xpos_
+ real_width_
- 1)
688 scroll_xpos_
= x
- real_width_
+ 1;
692 bool scrolled_y
= true;
693 if (real_height_
== 0)
695 else if (y
< scroll_ypos_
)
697 else if (y
> scroll_ypos_
+ real_height_
- 1)
698 scroll_ypos_
= y
- real_height_
+ 1;
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: