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/>.
21 #include "ColorScheme.h"
27 TreeView::ToggleCollapseButton::ToggleCollapseButton(
28 int w
, int h
, const char *text
)
33 TreeView::ToggleCollapseButton::ToggleCollapseButton(const char *text
)
38 void TreeView::ToggleCollapseButton::setParent(Container
&parent
)
40 TreeView
*tree
= dynamic_cast<TreeView
*>(&parent
);
41 assert(tree
!= nullptr);
43 Button::setParent(parent
);
44 signal_activate
.connect(
45 sigc::hide(sigc::mem_fun(tree
, &TreeView::actionToggleCollapsed
)));
48 TreeView::TreeView(int w
, int h
) : Container(w
, h
)
50 // Allow fast focus changing (paging) using PageUp/PageDown keys.
53 // Initialise the tree.
56 root
.collapsed
= false;
57 root
.style
= STYLE_NORMAL
;
58 root
.widget
= nullptr;
59 thetree_
.set_head(root
);
60 focus_node_
= thetree_
.begin();
70 int TreeView::draw(Curses::ViewPort area
, Error
&error
)
72 area
.scroll(scroll_xpos_
, scroll_ypos_
);
76 getAttributes(ColorScheme::PROPERTY_CONTAINER_BACKGROUND
, &attrs
, error
));
77 DRAW(area
.fill(attrs
, error
));
80 DRAW(drawNode(thetree_
.begin(), &height
, area
, error
));
85 void TreeView::cleanFocus()
87 Container::cleanFocus();
88 focus_node_
= thetree_
.begin();
91 bool TreeView::grabFocus()
93 for (TheTree::pre_order_iterator i
= ++thetree_
.begin(); i
!= thetree_
.end();
95 if (i
->widget
->grabFocus())
100 void TreeView::clear()
102 TheTree::pre_order_iterator root
= thetree_
.begin();
103 while (root
.number_of_children() != 0)
104 deleteNode(++thetree_
.begin(), false);
107 assert(children_
.empty());
110 bool TreeView::isWidgetVisible(const Widget
&child
) const
112 if (parent_
== nullptr || !visible_
)
115 NodeReference node
= findNode(child
);
116 if (!isNodeVisible(node
))
119 return parent_
->isWidgetVisible(*this);
122 bool TreeView::setFocusChild(Widget
&child
)
124 NodeReference node
= findNode(child
);
125 if (!isNodeVisible(node
))
128 bool res
= Container::setFocusChild(child
);
133 void TreeView::getFocusChain(
134 FocusChain
&focus_chain
, FocusChain::iterator parent
)
136 // It is possible that the predecessor of focused node was just made invisible
137 // and moveFocus() is called so other widget can take the focus. In this case
138 // we find top invisible predecessor of focused node and later the focused
139 // node is placed into the focus chain when this predecessor is reached.
140 NodeReference act
= focus_node_
;
141 NodeReference top
= thetree_
.begin();
142 while (act
!= thetree_
.begin()) {
143 if (!act
->widget
->isVisible())
145 act
= thetree_
.parent(act
);
148 // The preorder iterator starts with the root so we must skip it.
149 for (TheTree::pre_order_iterator i
= ++thetree_
.begin(); i
!= thetree_
.end();
151 Widget
*widget
= i
->widget
;
152 Container
*container
= dynamic_cast<Container
*>(widget
);
154 if (container
!= nullptr && container
->isVisible()) {
155 // The widget is a container so add its widgets as well.
156 FocusChain::pre_order_iterator iter
=
157 focus_chain
.append_child(parent
, container
);
158 container
->getFocusChain(focus_chain
, iter
);
160 // If this is not a focusable widget and it has no focusable children,
161 // remove it from the chain.
162 if (focus_chain
.number_of_children(iter
) == 0)
163 focus_chain
.erase(iter
);
165 else if (widget
->canFocus() && widget
->isVisible()) {
166 // Widget can be focused.
167 focus_chain
.append_child(parent
, widget
);
170 // This node is the focused node or the focused node is in a subtree of
173 Container
*focus_cont
= dynamic_cast<Container
*>(focus_child_
);
174 if (focus_cont
!= nullptr) {
175 // The focused node is actually a Container. First add the Container,
176 // then the focused widget.
177 FocusChain::pre_order_iterator iter
=
178 focus_chain
.append_child(parent
, focus_cont
);
179 focus_chain
.append_child(iter
, focus_cont
->getFocusWidget());
182 focus_chain
.append_child(parent
, focus_child_
);
185 if (i
->collapsed
|| !widget
->isVisible())
190 void TreeView::onChildMoveResize(
191 Widget
&activator
, const Rect
&oldsize
, const Rect
&newsize
)
194 assert(newsize
.getLeft() == UNSETPOS
&& newsize
.getTop() == UNSETPOS
);
196 assert(activator
.getParent() == this);
198 // Only interested in external height changes.
199 if (oldsize
.getHeight() == newsize
.getHeight())
205 void TreeView::onChildWishSizeChange(
206 Widget
&activator
, const Size
&oldsize
, const Size
&newsize
)
208 assert(activator
.getParent() == this);
210 if (activator
.getHeight() != AUTOSIZE
)
213 if (oldsize
.getHeight() == newsize
.getHeight())
219 void TreeView::onChildVisible(Widget
&activator
, bool /*visible*/)
221 assert(activator
.getParent() == this);
223 // TreeView children should get rarely hidden.
227 void TreeView::setCollapsed(NodeReference node
, bool collapsed
)
229 assert(node
->treeview
== this);
231 if (node
->collapsed
== collapsed
)
234 node
->collapsed
= collapsed
;
240 void TreeView::toggleCollapsed(NodeReference node
)
242 assert(node
->treeview
== this);
244 node
->collapsed
= !node
->collapsed
;
250 void TreeView::actionToggleCollapsed()
252 toggleCollapsed(focus_node_
);
255 TreeView::NodeReference
TreeView::insertNode(
256 NodeReference position
, Widget
&widget
)
258 assert(position
->treeview
== this);
260 TreeNode node
= addNode(widget
);
261 NodeReference iter
= thetree_
.insert(position
, node
);
262 addWidget(widget
, UNSETPOS
, UNSETPOS
);
267 TreeView::NodeReference
TreeView::insertNodeAfter(
268 NodeReference position
, Widget
&widget
)
270 assert(position
->treeview
== this);
272 TreeNode node
= addNode(widget
);
273 NodeReference iter
= thetree_
.insert_after(position
, node
);
274 addWidget(widget
, UNSETPOS
, UNSETPOS
);
279 TreeView::NodeReference
TreeView::prependNode(
280 NodeReference parent
, Widget
&widget
)
282 assert(parent
->treeview
== this);
284 TreeNode node
= addNode(widget
);
285 NodeReference iter
= thetree_
.prepend_child(parent
, node
);
286 addWidget(widget
, UNSETPOS
, UNSETPOS
);
291 TreeView::NodeReference
TreeView::appendNode(
292 NodeReference parent
, Widget
&widget
)
294 assert(parent
->treeview
== this);
296 TreeNode node
= addNode(widget
);
297 NodeReference iter
= thetree_
.append_child(parent
, node
);
298 addWidget(widget
, UNSETPOS
, UNSETPOS
);
303 void TreeView::deleteNode(NodeReference node
, bool keepchildren
)
305 assert(node
->treeview
== this);
307 // If we want to keep child nodes we should flatten the tree.
309 thetree_
.flatten(node
);
313 int h
= node
->widget
->getHeight();
315 h
= node
->widget
->getWishHeight();
322 while (thetree_
.number_of_children(node
) != 0) {
323 TheTree::pre_order_iterator i
= thetree_
.begin_leaf(node
);
324 int h
= i
->widget
->getHeight();
326 h
= i
->widget
->getWishHeight();
332 // Remove the widget and instantly remove it from the tree.
333 removeWidget(*i
->widget
);
337 if (node
->widget
!= nullptr)
338 removeWidget(*node
->widget
);
340 thetree_
.erase(node
);
345 void TreeView::deleteNodeChildren(NodeReference node
, bool keepchildren
)
347 assert(node
->treeview
== this);
350 while ((i
= node
.begin()) != node
.end())
351 deleteNode(i
, keepchildren
);
354 TreeView::NodeReference
TreeView::getSelectedNode() const
359 int TreeView::getNodeDepth(NodeReference node
) const
361 assert(node
->treeview
== this);
363 return thetree_
.depth(node
);
366 void TreeView::moveNodeBefore(NodeReference node
, NodeReference position
)
368 assert(node
->treeview
== this);
369 assert(position
->treeview
== this);
371 if (thetree_
.previous_sibling(position
) == node
)
374 thetree_
.move_before(position
, node
);
380 void TreeView::moveNodeAfter(NodeReference node
, NodeReference position
)
382 assert(node
->treeview
== this);
383 assert(position
->treeview
== this);
385 if (thetree_
.next_sibling(position
) == node
)
388 thetree_
.move_after(position
, node
);
394 void TreeView::setNodeParent(NodeReference node
, NodeReference newparent
)
396 assert(node
->treeview
== this);
397 assert(newparent
->treeview
== this);
399 if (thetree_
.parent(node
) == newparent
)
402 thetree_
.move_ontop(thetree_
.append_child(newparent
), node
);
408 void TreeView::setNodeStyle(NodeReference node
, Style s
)
410 assert(node
->treeview
== this);
412 if (node
->style
== s
)
420 TreeView::Style
TreeView::getNodeStyle(NodeReference node
) const
422 assert(node
->treeview
== this);
427 void TreeView::updateArea()
429 repositionChildren(thetree_
.begin(), 0, true);
431 // Make sure that the currently focused widget is visible.
435 int TreeView::drawNode(
436 SiblingIterator node
, int *out_height
, Curses::ViewPort
&area
, Error
&error
)
438 assert(out_height
!= nullptr);
443 // Draw the node Widget first.
445 if (!node
->widget
->isVisible())
448 DRAW(drawChild(*node
->widget
, area
, error
));
450 top
= node
->widget
->getRealTop();
451 int h
= node
->widget
->getRealHeight();
452 assert(h
!= AUTOSIZE
);
456 // If the node is collapsed or not openable then everything is done.
457 if (node
->collapsed
|| !isNodeOpenable(node
))
460 int depthoffset
= thetree_
.depth(node
) * 2;
465 DRAW(getAttributes(ColorScheme::PROPERTY_TREEVIEW_LINE
, &attrs
, error
));
466 DRAW(area
.attrOn(attrs
, error
));
468 for (j
= 1; j
< *out_height
; ++j
)
469 DRAW(area
.addLineChar(depthoffset
, top
+ j
, Curses::LINE_VLINE
, error
));
471 // Note: It would be better to start from the end towards the beginning but
472 // for some reason it does not seem to work.
473 SiblingIterator last
= node
.begin();
474 for (i
= node
.begin(); i
!= node
.end(); ++i
) {
475 if (i
->widget
== nullptr)
478 if (!i
->widget
->isVisible())
481 int h
= i
->widget
->getRealHeight();
482 assert(h
!= AUTOSIZE
);
486 SiblingIterator end
= last
;
488 for (i
= node
.begin(); i
!= end
; ++i
) {
490 DRAW(area
.addLineChar(
491 depthoffset
, top
+ *out_height
, Curses::LINE_LTEE
, error
));
493 DRAW(area
.addLineChar(
494 depthoffset
, top
+ *out_height
, Curses::LINE_LLCORNER
, error
));
496 if (i
->style
== STYLE_NORMAL
&& isNodeOpenable(i
)) {
497 const char *c
= i
->collapsed
? "[+]" : "[-]";
498 DRAW(area
.addString(depthoffset
+ 1, top
+ *out_height
, c
, error
));
501 DRAW(area
.addLineChar(
502 depthoffset
+ 1, top
+ *out_height
, Curses::LINE_HLINE
, error
));
504 DRAW(area
.attrOff(attrs
, error
));
505 int oldh
= *out_height
;
507 DRAW(drawNode(i
, &child_height
, area
, error
));
508 *out_height
+= child_height
;
509 DRAW(area
.attrOn(attrs
, error
));
512 for (j
= oldh
+ 1; j
< *out_height
; ++j
)
513 DRAW(area
.addLineChar(depthoffset
, top
+ j
, Curses::LINE_VLINE
, error
));
515 DRAW(area
.attrOff(attrs
, error
));
520 TreeView::TreeNode
TreeView::addNode(Widget
&widget
)
522 // Make room for this widget.
523 int h
= widget
.getHeight();
525 h
= widget
.getWishHeight();
530 // Construct the new node.
532 node
.treeview
= this;
533 node
.collapsed
= false;
534 node
.style
= STYLE_NORMAL
;
535 node
.widget
= &widget
;
540 void TreeView::fixFocus()
542 // This function is called when a widget tree is reorganized (a node was moved
543 // in another position in the tree). In this case, it is possible that there
544 // can be revealed a widget that could grab the focus (if there is no focused
545 // widget yet) or it is also possible that the currently focused widget was
546 // hidden by this reorganization (then the focus has to be handled to another
551 Container
*t
= getTopContainer();
552 Widget
*focus
= t
->getFocusWidget();
553 if (focus
== nullptr) {
554 // Try to grab the focus.
555 t
->moveFocus(FOCUS_DOWN
);
557 else if (!focus
->isVisibleRecursive()) {
559 t
->moveFocus(FOCUS_DOWN
);
563 TreeView::NodeReference
TreeView::findNode(const Widget
&child
) const
565 /// @todo Speed up this algorithm.
566 TheTree::pre_order_iterator i
;
567 for (i
= thetree_
.begin(); i
!= thetree_
.end(); ++i
)
568 if (i
->widget
== &child
)
570 assert(i
!= thetree_
.end());
574 bool TreeView::isNodeOpenable(SiblingIterator
&node
) const
576 for (SiblingIterator i
= node
.begin(); i
!= node
.end(); ++i
) {
579 int h
= i
->widget
->getHeight();
581 h
= i
->widget
->getWishHeight();
585 if (h
> 0 && i
->widget
->isVisible())
591 bool TreeView::isNodeVisible(NodeReference
&node
) const
593 // Node is visible if all predecessors are visible and open.
594 NodeReference act
= node
;
596 while (act
!= thetree_
.begin()) {
597 if (!act
->widget
->isVisible() || (!first
&& act
->collapsed
))
600 act
= thetree_
.parent(act
);
605 int TreeView::repositionChildren(SiblingIterator node
, int top
, bool in_visible
)
609 // Position the node Widget first.
610 Widget
*widget
= node
->widget
;
611 if (widget
!= nullptr) {
612 int l
= thetree_
.depth(node
) * 2;
613 l
+= (node
->style
== STYLE_NORMAL
&& isNodeOpenable(node
)) ? 3 : 1;
614 widget
->setRealPosition(l
, top
);
616 // Calculate the real width.
617 int w
= widget
->getWidth();
619 w
= widget
->getWishWidth();
626 // Calculate the real height.
627 int h
= widget
->getHeight();
629 h
= widget
->getWishHeight();
634 widget
->setRealSize(w
, h
);
636 if (in_visible
&& widget
->isVisible())
640 in_visible
= in_visible
&& !node
->collapsed
&& isNodeOpenable(node
);
642 // Position child nodes.
643 int children_height
= height
;
644 for (SiblingIterator i
= node
.begin(); i
!= node
.end(); ++i
)
645 children_height
+= repositionChildren(i
, top
+ children_height
, in_visible
);
648 assert(children_height
== height
);
650 return children_height
;
653 void TreeView::actionCollapse()
655 setCollapsed(focus_node_
, true);
658 void TreeView::actionExpand()
660 setCollapsed(focus_node_
, false);
663 void TreeView::declareBindables()
665 declareBindable("treeview", "fold-subtree",
666 sigc::mem_fun(this, &TreeView::actionCollapse
),
667 InputProcessor::BINDABLE_NORMAL
);
668 declareBindable("treeview", "unfold-subtree",
669 sigc::mem_fun(this, &TreeView::actionExpand
),
670 InputProcessor::BINDABLE_NORMAL
);
673 } // namespace CppConsUI
675 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: