diff options
author | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-12-10 22:30:12 +0100 |
---|---|---|
committer | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-12-10 22:30:12 +0100 |
commit | 429d5f88b94ff28416cbfc6420b6389fa284df97 (patch) | |
tree | fb6fdaf7731de1ef396f98b748c56f3149801c84 /dwr/graph.cc |
Import RTFL 0.1.1v0.1.1
Diffstat (limited to 'dwr/graph.cc')
-rw-r--r-- | dwr/graph.cc | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/dwr/graph.cc b/dwr/graph.cc new file mode 100644 index 0000000..d8a1b35 --- /dev/null +++ b/dwr/graph.cc @@ -0,0 +1,642 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "graph.hh" +#include "tools.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::container::typed; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int Graph::CLASS_ID = -1; + +// ---------------------------------------------------------------------- + +// TODO: GraphIterator ignores the text nodes (for references). Are +// they needed, anyway? +Graph::GraphIterator::GraphIterator (Graph *graph, Content::Type mask, + bool atEnd) : Iterator (graph, mask, atEnd) +{ + index = atEnd ? graph->allWidgets->size() : -1; + content.type = atEnd ? Content::END : Content::START; +} + +Graph::GraphIterator::GraphIterator (Graph *graph, Content::Type mask, + int index) : Iterator (graph, mask, false) +{ + this->index = index; + + if (index < 0) + content.type = Content::START; + else if (index >= graph->allWidgets->size ()) + content.type = Content::END; + else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = graph->allWidgets->get (index); + } +} + +lout::object::Object *Graph::GraphIterator::clone () +{ + return new GraphIterator ((Graph*)getWidget(), getMask(), index); +} + +int Graph::GraphIterator::compareTo (lout::object::Comparable *other) +{ + return index - ((GraphIterator*)other)->index; +} + +bool Graph::GraphIterator::next () +{ + Graph *graph = (Graph*)getWidget(); + + if (content.type == Content::END) + return false; + + // graphs only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::END; + return false; + } + + index++; + if (index >= graph->allWidgets->size ()) { + content.type = Content::END; + return false; + } else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = graph->allWidgets->get (index); + return true; + } +} + +bool Graph::GraphIterator::prev () +{ + Graph *graph = (Graph*)getWidget(); + + if (content.type == Content::START) + return false; + + // graphs only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::START; + return false; + } + + index--; + if (index < 0) { + content.type = Content::START; + return false; + } else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = graph->allWidgets->get (index); + return true; + } +} + +void Graph::GraphIterator::highlight (int start, int end, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Graph::GraphIterator::unhighlight (int direction, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Graph::GraphIterator::getAllocation (int start, int end, + Allocation *allocation) +{ + /** \bug Not implemented. */ +} + +// ---------------------------------------------------------------------- + +Graph::Node::Node (Widget *widget, Node *parent) +{ + type = WIDGET; + this->widget = widget; + this->parent = parent; + children = new List<Node> (true); +} + +Graph::Node::Node (const char *text, Node *parent) +{ + type = REF; + this->text = strdup (text); + this->parent = parent; + children = new List<Node> (true); +} + +Graph::Node::~Node () +{ + if (type == WIDGET) { + if (widget) // see Graph::removeChild + delete widget; + } else + free (text); + delete children; +} + +// ---------------------------------------------------------------------- + +Graph::Graph () +{ + DBG_OBJ_CREATE ("rtfl::dw::Graph"); + registerName ("rtfl::dw::Graph", &CLASS_ID); + + allWidgets = new Vector<Widget> (4, false); + topLevelNodes = new List<Node> (true); + refStyle = NULL; + pressedRefNode = NULL; + inDestructor = false; +} + +Graph::~Graph () +{ + inDestructor = true; + delete allWidgets; + delete topLevelNodes; + if (refStyle) + refStyle->unref (); + + DBG_OBJ_DELETE (); +} + +void Graph::sizeRequestImpl (Requisition *requisition) +{ + sizeRequest (requisition, topLevelNodes); + + requisition->width += getStyle()->boxDiffWidth (); + requisition->ascent += getStyle()->boxOffsetY (); + requisition->descent += getStyle()->boxRestHeight (); +} + +void Graph::sizeRequest (Requisition *requisition, List<Node> *list) +{ + // For simlicity, descent is set to 0. (Anyway never needed within + // RTFL.) + requisition->width = requisition->descent = 0; + requisition->ascent = max (VDIFF * (list->size () - 1), 0); + + for (lout::container::typed::Iterator<Node> it = list->iterator (); + it.hasNext (); ) { + Node *node = it.getNext (); + Requisition nodeReq; + sizeRequest (&nodeReq, node); + + requisition->width = max (requisition->width, nodeReq.width); + requisition->ascent += (nodeReq.ascent + nodeReq.descent); + } +} + +void Graph::sizeRequest (Requisition *requisition, Node *node) +{ + if (node->type == Node::WIDGET) + node->widget->sizeRequest (&node->rootReq); + else { + node->rootReq.width = + layout->textWidth (refStyle->font, node->text, strlen (node->text)) + + refStyle->boxDiffWidth (); + node->rootReq.ascent = refStyle->font->ascent + refStyle->boxOffsetY (); + node->rootReq.descent = + refStyle->font->descent + refStyle->boxRestHeight (); + } + + if (node->children->isEmpty ()) + *requisition = node->rootReq; + else { + sizeRequest (&node->childrenReq, node->children); + + requisition->ascent = + max (node->rootReq.ascent + node->rootReq.descent, + node->childrenReq.ascent + node->childrenReq.descent); + requisition->descent = 0; + requisition->width = + node->rootReq.width + HDIFF + node->childrenReq.width; + } +} + +void Graph::getExtremesImpl (Extremes *extremes) +{ + getExtremes (extremes, topLevelNodes); + + extremes->minWidth += getStyle()->boxDiffWidth (); + extremes->maxWidth += getStyle()->boxDiffWidth (); +} + +void Graph::getExtremes (Extremes *extremes, List<Node> *list) +{ + extremes->minWidth = extremes->maxWidth = 0; + + for (lout::container::typed::Iterator<Node> it = list->iterator (); + it.hasNext (); ) { + Node *node = it.getNext (); + Extremes nodeExtr; + getExtremes (&nodeExtr, node); + + extremes->minWidth = max (extremes->minWidth, nodeExtr.minWidth); + extremes->maxWidth = max (extremes->maxWidth, nodeExtr.maxWidth); + } +} + +void Graph::getExtremes (Extremes *extremes, Node *node) +{ + Extremes rootExtr; + if (node->type == Node::WIDGET) + node->widget->getExtremes (&rootExtr); + else + rootExtr.minWidth = rootExtr.maxWidth = + layout->textWidth (refStyle->font, node->text, strlen (node->text)) + + refStyle->boxDiffWidth (); + + if (node->children->isEmpty ()) + *extremes = rootExtr; + else { + Extremes childrenExtr; + getExtremes (&childrenExtr, node->children); + + extremes->minWidth = rootExtr.minWidth + HDIFF + childrenExtr.minWidth; + extremes->maxWidth = rootExtr.maxWidth + HDIFF + childrenExtr.maxWidth; + } +} + +void Graph::sizeAllocateImpl (Allocation *allocation) +{ + // Assumes that sizeRequest has been called before. Only position, + // not allocation size, is considered. + //printf ("sizeAllocate (%d, %d, ...)\n", allocation->x, allocation->y); + sizeAllocate (allocation->x + getStyle()->boxOffsetX (), + allocation->y + getStyle()->boxOffsetY (), topLevelNodes); +} + +void Graph::sizeAllocate (int x, int y, List<Node> *list) +{ + //printf (" List: sizeAllocate (%d, %d, ...)\n", x, y); + + for (lout::container::typed::Iterator<Node> it = list->iterator (); + it.hasNext (); ) { + Node *node = it.getNext (); + sizeAllocate (x, y, node); + int h = node->children->isEmpty () ? + node->rootReq.ascent + node->rootReq.descent : + max (node->rootReq.ascent + node->rootReq.descent, + node->childrenReq.ascent + node->childrenReq.descent); + y += (VDIFF + h); + } +} + +void Graph::sizeAllocate (int x, int y, Node *node) +{ + // Notice that node->childrenReq is undefined when there are no + // children. + + node->rootX = x; + node->rootY = node->children->isEmpty () ? y : + y + max ((node->childrenReq.ascent + node->childrenReq.descent + - (node->rootReq.ascent + node->rootReq.descent)) / 2, 0); + + + if (node->type == Node::WIDGET) { + Allocation rootAlloc; + rootAlloc.x = node->rootX; + rootAlloc.y = node->rootY; + rootAlloc.width = node->rootReq.width; + rootAlloc.ascent = node->rootReq.ascent; + rootAlloc.descent = node->rootReq.descent; + node->widget->sizeAllocate (&rootAlloc); + } + + if (!node->children->isEmpty ()) + sizeAllocate (x + HDIFF + node->rootReq.width, + y + max ((node->rootReq.ascent + node->rootReq.descent + - (node->childrenReq.ascent + + node->childrenReq.descent)) / 2, 0), + node->children); +} + +void Graph::draw (View *view, Rectangle *area) +{ + DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + drawWidgetBox (view, area, false); + draw (view, area, topLevelNodes); + + DBG_OBJ_LEAVE (); +} + +void Graph::draw (View *view, Rectangle *area, List<Node> *list) +{ + for (lout::container::typed::Iterator<Node> it = list->iterator (); + it.hasNext (); ) { + Node *node = it.getNext (); + + if (node->type == Node::WIDGET) { + Rectangle childArea; + if (node->widget->intersects (area, &childArea)) + node->widget->draw (view, &childArea); + } else { + drawBox (view, refStyle, area, node->rootX, node->rootY, + node->rootReq.width, + node->rootReq.ascent + node->rootReq.descent, false); + view-> drawText (refStyle->font, refStyle->color, + Color::SHADING_NORMAL, + node->rootX + refStyle->boxOffsetX (), + node->rootY + node->rootReq.ascent, + node->text, strlen (node->text)); + } + + drawArrows (view, area, node); + + draw (view, area, node->children); + } +} + +void Graph::drawArrows (View *view, Rectangle *area, Node *node) +{ + // TODO Regarding "area" could make this faster, especially for + // large graphs. + + int x1 = node->rootX + node->rootReq.width; + int y1 = node->rootY + (node->rootReq.ascent + node->rootReq.descent) / 2; + + for (lout::container::typed::Iterator<Node> it = node->children->iterator (); + it.hasNext (); ) { + Node *child = it.getNext (); + + int x2 = child->rootX; + int y2 = + child->rootY + (child->rootReq.ascent + child->rootReq.descent) / 2; + + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, x1, y1, x2, y2); + tools::drawArrowHead (view, getStyle(), x1, y1, x2, y2, AHEADLEN); + } +} + +::dw::core::Iterator *Graph::iterator (Content::Type mask, bool atEnd) +{ + return new GraphIterator (this, mask, atEnd); +} + +void Graph::removeChild (Widget *child) +{ + if (!inDestructor) { + Node *node = searchWidget (child); + assert (node != NULL); + assert (node->type == Node::WIDGET); + + // Otherwise, Node::~Node would delete the widget again: + Widget *widget = node->widget; + node->widget = NULL; + + // TODO No full implementation, only when all edges have been removed. + assert (node->parent == NULL); + assert (node->children->isEmpty ()); + + bool removed = topLevelNodes->removeRef (node); + assert (removed); + + int pos = -1; + for (int i = 0; pos == -1 && i < allWidgets->size (); i++) { + if (allWidgets->get (i) == widget) + pos = i; + } + + assert (pos != -1); + allWidgets->remove (pos); + } +} + +void Graph::setStyle (Style *style) +{ + Widget::setStyle (style); + if (refStyle == NULL && style != NULL) { + refStyle = style; + refStyle->ref (); + } +} + +Graph::Node *Graph::searchRefNode (MousePositionEvent *event, List<Node> *list) +{ + for (lout::container::typed::Iterator<Node> it = list->iterator (); + it.hasNext (); ) { + Node *node = it.getNext (); + if (event->xCanvas >= node->rootX && + event->xCanvas < node->rootX + node->rootReq.width && + event->yCanvas >= node->rootY && + event->yCanvas < node->rootY + node->rootReq.ascent + + node->rootReq.descent) + return node; + + Node *node2 = searchRefNode (event, node->children); + if (node2) + return node2; + } + + return NULL; +} + +bool Graph::buttonPressImpl (EventButton *event) +{ + if (event->button == 1) { + pressedRefNode = searchRefNode (event); + return pressedRefNode != NULL; + } else + return false; +} + +bool Graph::buttonReleaseImpl (EventButton *event) +{ + if (event->button == 1 && pressedRefNode != NULL) { + Node *newRefNode = searchRefNode (event); + if (newRefNode == pressedRefNode) { + Node *ref = pressedRefNode->reference; + scrollTo (HPOS_CENTER, VPOS_CENTER, ref->rootX + getAllocation()->x, + ref->rootY + getAllocation()->y, ref->rootReq.width, + ref->rootReq.ascent + ref->rootReq.descent); + pressedRefNode = NULL; + } + return true; + } else + return false; +} + +bool Graph::motionNotifyImpl (::dw::core::EventMotion *event) +{ + if ((event->state & BUTTON1_MASK) && pressedRefNode) { + Node *newRefNode = searchRefNode (event); + if (newRefNode != pressedRefNode) + pressedRefNode = NULL; + } + return false; +} + +void Graph::leaveNotifyImpl (EventCrossing *event) +{ + pressedRefNode = NULL; +} + +bool Graph::isAncestorOf (Node *n1, Node *n2) +{ + for (Node *node = n2; node; node = node->parent) + if (node == n1) + return true; + + return false; +} + +void Graph::addReference (Node *from, Node *to) +{ + assert (from->type == Node::WIDGET); + assert (to->type == Node::WIDGET); + //printf ("edge from %s %p to %s %p implemented as reference.\n", + // from->widget->getClassName (), from->widget, + // to->widget->getClassName (), to->widget); + + Node *ref1 = new Node ("…", from); + from->children->append (ref1); + + Node *ref2 = new Node ("…", to); + List<Node> *list = to->parent ? to->parent->children : topLevelNodes; + list->insertBefore (to, ref2); + list->detachRef (to); + ref2->children->append (to); + + ref1->reference = ref2; + ref2->reference = ref1; +} + +void Graph::addNode (Widget *widget) +{ + if (searchWidget (widget)) + fprintf (stderr, "WARNING: widget %p added twice.\n", widget); + else { + allWidgets->put (widget); + topLevelNodes->append (new Node (widget, NULL)); + widget->setParent (this); + queueResize (0, true); + } +} + +void Graph::addEdge (Widget *from, Widget *to) +{ + Node *nodeFrom, *nodeTo; + + if ((nodeFrom = searchWidget (from)) == NULL) { + fprintf (stderr, "WARNING: adding edge starting from unknown widget %p\n", + from); + return; + } + + if ((nodeTo = searchWidget (to)) == NULL) { + fprintf (stderr, "WARNING: adding edge ending in unknown widget %p\n", + to); + return; + } + + if (nodeTo->parent) { + if (nodeTo->parent == nodeFrom) + fprintf (stderr, + "WARNING: edge from widget %p to widget %p added twice.\n", + from, to); + else { + // Second node is already the end of an edge, so no new is + // added, but a reference. + addReference (nodeFrom, nodeTo); + } + } else { + // Here, nodeTo->parent is NULL. + if (isAncestorOf (nodeTo, nodeFrom)) + // Would cause a cycle otherwise. + addReference (nodeFrom, nodeTo); + else { + topLevelNodes->detachRef (nodeTo); + nodeTo->parent = nodeFrom; + nodeFrom->children->append (nodeTo); + } + } + + queueResize (0, true); +} + +void Graph::removeEdge (::dw::core::Widget *from, ::dw::core::Widget *to) +{ + Node *nodeFrom, *nodeTo; + + if ((nodeFrom = searchWidget (from)) == NULL) { + fprintf (stderr, "WARNING: adding edge starting from unknown widget %p\n", + from); + return; + } + + if ((nodeTo = searchWidget (to)) == NULL) { + fprintf (stderr, "WARNING: adding edge ending in unknown widget %p\n", + to); + return; + } + + assertNotReached (); // TODO + + queueResize (0, true); +} + +Graph::Node *Graph::searchWidget (Widget *widget, List<Node> *list) +{ + for (lout::container::typed::Iterator<Node> it = list->iterator (); + it.hasNext (); ) { + Node *node = it.getNext (); + if (node->type == Node::WIDGET && node->widget == widget) + return node; + + Node *node2 = searchWidget (widget, node->children); + if (node2) + return node2; + } + + return NULL; +} + +void Graph::setRefStyle (::dw::core::style::Style *style) +{ + if (refStyle) + refStyle->unref (); + + refStyle = style; + refStyle->ref (); +} + +} // namespace rtfl + +} // namespace dw |