Fix include name for the ncursesw library
[centerim5.git] / cppconsui / TreeView.cpp
blob3f9d8f68a092dc690ffd40f29500a2e94b8c390f
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 #include "TreeView.h"
21 #include "ColorScheme.h"
23 #include <cassert>
25 namespace CppConsUI {
27 TreeView::ToggleCollapseButton::ToggleCollapseButton(
28 int w, int h, const char *text)
29 : Button(w, h, text)
33 TreeView::ToggleCollapseButton::ToggleCollapseButton(const char *text)
34 : Button(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.
51 page_focus_ = true;
53 // Initialise the tree.
54 TreeNode root;
55 root.treeview = this;
56 root.collapsed = false;
57 root.style = STYLE_NORMAL;
58 root.widget = nullptr;
59 thetree_.set_head(root);
60 focus_node_ = thetree_.begin();
62 declareBindables();
65 TreeView::~TreeView()
67 clear();
70 int TreeView::draw(Curses::ViewPort area, Error &error)
72 area.scroll(scroll_xpos_, scroll_ypos_);
74 int attrs;
75 DRAW(
76 getAttributes(ColorScheme::PROPERTY_CONTAINER_BACKGROUND, &attrs, error));
77 DRAW(area.fill(attrs, error));
79 int height;
80 DRAW(drawNode(thetree_.begin(), &height, area, error));
82 return 0;
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();
94 ++i)
95 if (i->widget->grabFocus())
96 return true;
97 return false;
100 void TreeView::clear()
102 TheTree::pre_order_iterator root = thetree_.begin();
103 while (root.number_of_children() != 0)
104 deleteNode(++thetree_.begin(), false);
106 // Stay sane.
107 assert(children_.empty());
110 bool TreeView::isWidgetVisible(const Widget &child) const
112 if (parent_ == nullptr || !visible_)
113 return false;
115 NodeReference node = findNode(child);
116 if (!isNodeVisible(node))
117 return false;
119 return parent_->isWidgetVisible(*this);
122 bool TreeView::setFocusChild(Widget &child)
124 NodeReference node = findNode(child);
125 if (!isNodeVisible(node))
126 return false;
128 bool res = Container::setFocusChild(child);
129 focus_node_ = node;
130 return res;
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())
144 top = act;
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();
150 ++i) {
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);
169 else if (i == top) {
170 // This node is the focused node or the focused node is in a subtree of
171 // this node.
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());
181 else
182 focus_chain.append_child(parent, focus_child_);
185 if (i->collapsed || !widget->isVisible())
186 i.skip_children();
190 void TreeView::onChildMoveResize(
191 Widget &activator, const Rect &oldsize, const Rect &newsize)
193 // Sanity check.
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())
200 return;
202 updateArea();
205 void TreeView::onChildWishSizeChange(
206 Widget &activator, const Size &oldsize, const Size &newsize)
208 assert(activator.getParent() == this);
210 if (activator.getHeight() != AUTOSIZE)
211 return;
213 if (oldsize.getHeight() == newsize.getHeight())
214 return;
216 updateArea();
219 void TreeView::onChildVisible(Widget &activator, bool /*visible*/)
221 assert(activator.getParent() == this);
223 // TreeView children should get rarely hidden.
224 updateArea();
227 void TreeView::setCollapsed(NodeReference node, bool collapsed)
229 assert(node->treeview == this);
231 if (node->collapsed == collapsed)
232 return;
234 node->collapsed = collapsed;
235 fixFocus();
236 updateArea();
237 redraw();
240 void TreeView::toggleCollapsed(NodeReference node)
242 assert(node->treeview == this);
244 node->collapsed = !node->collapsed;
245 fixFocus();
246 updateArea();
247 redraw();
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);
263 updateArea();
264 return iter;
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);
275 updateArea();
276 return iter;
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);
287 updateArea();
288 return iter;
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);
299 updateArea();
300 return iter;
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.
308 if (keepchildren)
309 thetree_.flatten(node);
311 int shrink = 0;
312 if (node->widget) {
313 int h = node->widget->getHeight();
314 if (h == AUTOSIZE) {
315 h = node->widget->getWishHeight();
316 if (h == AUTOSIZE)
317 h = 1;
319 shrink += h;
322 while (thetree_.number_of_children(node) != 0) {
323 TheTree::pre_order_iterator i = thetree_.begin_leaf(node);
324 int h = i->widget->getHeight();
325 if (h == AUTOSIZE) {
326 h = i->widget->getWishHeight();
327 if (h == AUTOSIZE)
328 h = 1;
330 shrink += h;
332 // Remove the widget and instantly remove it from the tree.
333 removeWidget(*i->widget);
334 thetree_.erase(i);
337 if (node->widget != nullptr)
338 removeWidget(*node->widget);
340 thetree_.erase(node);
341 updateArea();
342 redraw();
345 void TreeView::deleteNodeChildren(NodeReference node, bool keepchildren)
347 assert(node->treeview == this);
349 SiblingIterator i;
350 while ((i = node.begin()) != node.end())
351 deleteNode(i, keepchildren);
354 TreeView::NodeReference TreeView::getSelectedNode() const
356 return focus_node_;
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)
372 return;
374 thetree_.move_before(position, node);
375 fixFocus();
376 updateArea();
377 redraw();
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)
386 return;
388 thetree_.move_after(position, node);
389 fixFocus();
390 updateArea();
391 redraw();
394 void TreeView::setNodeParent(NodeReference node, NodeReference newparent)
396 assert(node->treeview == this);
397 assert(newparent->treeview == this);
399 if (thetree_.parent(node) == newparent)
400 return;
402 thetree_.move_ontop(thetree_.append_child(newparent), node);
403 fixFocus();
404 updateArea();
405 redraw();
408 void TreeView::setNodeStyle(NodeReference node, Style s)
410 assert(node->treeview == this);
412 if (node->style == s)
413 return;
415 node->style = s;
416 updateArea();
417 redraw();
420 TreeView::Style TreeView::getNodeStyle(NodeReference node) const
422 assert(node->treeview == this);
424 return node->style;
427 void TreeView::updateArea()
429 repositionChildren(thetree_.begin(), 0, true);
431 // Make sure that the currently focused widget is visible.
432 updateScroll();
435 int TreeView::drawNode(
436 SiblingIterator node, int *out_height, Curses::ViewPort &area, Error &error)
438 assert(out_height != nullptr);
440 int top = 0;
441 *out_height = 0;
443 // Draw the node Widget first.
444 if (node->widget) {
445 if (!node->widget->isVisible())
446 return 0;
448 DRAW(drawChild(*node->widget, area, error));
450 top = node->widget->getRealTop();
451 int h = node->widget->getRealHeight();
452 assert(h != AUTOSIZE);
453 *out_height += h;
456 // If the node is collapsed or not openable then everything is done.
457 if (node->collapsed || !isNodeOpenable(node))
458 return 0;
460 int depthoffset = thetree_.depth(node) * 2;
461 SiblingIterator i;
462 int j;
464 int attrs;
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)
476 continue;
478 if (!i->widget->isVisible())
479 continue;
481 int h = i->widget->getRealHeight();
482 assert(h != AUTOSIZE);
483 if (h > 0)
484 last = i;
486 SiblingIterator end = last;
487 ++end;
488 for (i = node.begin(); i != end; ++i) {
489 if (i != last)
490 DRAW(area.addLineChar(
491 depthoffset, top + *out_height, Curses::LINE_LTEE, error));
492 else
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));
500 else
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;
506 int child_height;
507 DRAW(drawNode(i, &child_height, area, error));
508 *out_height += child_height;
509 DRAW(area.attrOn(attrs, error));
511 if (i != last)
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));
517 return 0;
520 TreeView::TreeNode TreeView::addNode(Widget &widget)
522 // Make room for this widget.
523 int h = widget.getHeight();
524 if (h == AUTOSIZE) {
525 h = widget.getWishHeight();
526 if (h == AUTOSIZE)
527 h = 1;
530 // Construct the new node.
531 TreeNode node;
532 node.treeview = this;
533 node.collapsed = false;
534 node.style = STYLE_NORMAL;
535 node.widget = &widget;
537 return node;
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
547 // widget).
549 updateFocusChain();
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()) {
558 // Move focus.
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)
569 break;
570 assert(i != thetree_.end());
571 return i;
574 bool TreeView::isNodeOpenable(SiblingIterator &node) const
576 for (SiblingIterator i = node.begin(); i != node.end(); ++i) {
577 if (!i->widget)
578 continue;
579 int h = i->widget->getHeight();
580 if (h == AUTOSIZE) {
581 h = i->widget->getWishHeight();
582 if (h == AUTOSIZE)
583 h = 1;
585 if (h > 0 && i->widget->isVisible())
586 return true;
588 return false;
591 bool TreeView::isNodeVisible(NodeReference &node) const
593 // Node is visible if all predecessors are visible and open.
594 NodeReference act = node;
595 bool first = true;
596 while (act != thetree_.begin()) {
597 if (!act->widget->isVisible() || (!first && act->collapsed))
598 return false;
599 first = false;
600 act = thetree_.parent(act);
602 return true;
605 int TreeView::repositionChildren(SiblingIterator node, int top, bool in_visible)
607 int height = 0;
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();
618 if (w == AUTOSIZE) {
619 w = widget->getWishWidth();
620 if (w == AUTOSIZE)
621 w = real_width_ - l;
623 if (w > real_width_)
624 w = real_width_;
626 // Calculate the real height.
627 int h = widget->getHeight();
628 if (h == AUTOSIZE) {
629 h = widget->getWishHeight();
630 if (h == AUTOSIZE)
631 h = 1;
634 widget->setRealSize(w, h);
636 if (in_visible && widget->isVisible())
637 height += h;
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);
647 if (!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: