diff options
Diffstat (limited to 'dwr')
-rw-r--r-- | dwr/Makefile.am | 31 | ||||
-rw-r--r-- | dwr/box.cc | 258 | ||||
-rw-r--r-- | dwr/box.hh | 110 | ||||
-rw-r--r-- | dwr/graph.cc | 642 | ||||
-rw-r--r-- | dwr/graph.hh | 126 | ||||
-rw-r--r-- | dwr/graph2.cc | 505 | ||||
-rw-r--r-- | dwr/graph2.hh | 122 | ||||
-rw-r--r-- | dwr/graph2_iterator.cc | 139 | ||||
-rw-r--r-- | dwr/hbox.cc | 123 | ||||
-rw-r--r-- | dwr/hbox.hh | 31 | ||||
-rw-r--r-- | dwr/hideable.cc | 104 | ||||
-rw-r--r-- | dwr/hideable.hh | 39 | ||||
-rw-r--r-- | dwr/label.cc | 378 | ||||
-rw-r--r-- | dwr/label.hh | 87 | ||||
-rw-r--r-- | dwr/toggle.cc | 402 | ||||
-rw-r--r-- | dwr/toggle.hh | 77 | ||||
-rw-r--r-- | dwr/tools.cc | 243 | ||||
-rw-r--r-- | dwr/tools.hh | 24 | ||||
-rw-r--r-- | dwr/vbox.cc | 164 | ||||
-rw-r--r-- | dwr/vbox.hh | 35 |
20 files changed, 3640 insertions, 0 deletions
diff --git a/dwr/Makefile.am b/dwr/Makefile.am new file mode 100644 index 0000000..afd9925 --- /dev/null +++ b/dwr/Makefile.am @@ -0,0 +1,31 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/dwr"' + +noinst_LIBRARIES = \ + libDw-rtfl.a + +libDw_rtfl_a_SOURCES = \ + box.hh \ + box.cc \ + graph.hh \ + graph.cc \ + hbox.hh \ + hbox.cc \ + hideable.hh \ + hideable.cc \ + label.hh \ + label.cc \ + toggle.hh \ + toggle.cc \ + tools.hh \ + tools.cc \ + vbox.hh \ + vbox.cc + +if USE_GRAPH2 +libDw_rtfl_a_SOURCES += \ + graph2.hh \ + graph2.cc \ + graph2_iterator.cc +endif diff --git a/dwr/box.cc b/dwr/box.cc new file mode 100644 index 0000000..2e72866 --- /dev/null +++ b/dwr/box.cc @@ -0,0 +1,258 @@ +/* + * 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 <math.h> + +#include "box.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::container::typed; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int Box::CLASS_ID = -1; + +#define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n + +const char Box::countZeroTable[256] = { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + LT(3), LT(2), LT(2), LT(1), LT(1), LT(1), LT(1), + LT(0), LT(0), LT(0), LT(0), LT(0), LT(0), LT(0), LT(0) +}; + +// ---------------------------------------------------------------------- + +Box::BoxIterator::BoxIterator (Box *box, Content::Type mask, + bool atEnd) : Iterator (box, mask, atEnd) +{ + index = atEnd ? box->children->size() : -1; + content.type = atEnd ? Content::END : Content::START; +} + +Box::BoxIterator::BoxIterator (Box *box, Content::Type mask, + int index) : Iterator (box, mask, false) +{ + this->index = index; + + if (index < 0) + content.type = Content::START; + else if (index >= box->children->size ()) + content.type = Content::END; + else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = box->children->get (index); + } +} + +lout::object::Object *Box::BoxIterator::clone () +{ + return new BoxIterator ((Box*)getWidget(), getMask(), index); +} + +int Box::BoxIterator::compareTo (lout::object::Comparable *other) +{ + return index - ((BoxIterator*)other)->index; +} + +bool Box::BoxIterator::next () +{ + Box *box = (Box*)getWidget(); + + if (content.type == Content::END) + return false; + + // boxs only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::END; + return false; + } + + index++; + if (index >= box->children->size ()) { + content.type = Content::END; + return false; + } else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = box->children->get (index); + return true; + } +} + +bool Box::BoxIterator::prev () +{ + Box *box = (Box*)getWidget(); + + if (content.type == Content::START) + return false; + + // boxs 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 = box->children->get (index); + return true; + } +} + +void Box::BoxIterator::highlight (int start, int end, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Box::BoxIterator::unhighlight (int direction, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Box::BoxIterator::getAllocation (int start, int end, + Allocation *allocation) +{ + /** \bug Not implemented. */ +} + +// ---------------------------------------------------------------------- + +Box::Box (bool stretchChildren) +{ + DBG_OBJ_CREATE ("rtfl::dw::Box"); + registerName ("rtfl::dw::Box", &CLASS_ID); + + this->stretchChildren = stretchChildren; + children = new Vector<Widget> (4, true); + inDestructor = false; +} + +Box::~Box () +{ + inDestructor = true; + delete children; + + DBG_OBJ_DELETE (); +} + +void Box::sizeRequestImplImpl (Requisition *requisition) +{ + actualSizeRequestImplImpl (requisition, 0); +} + +void Box::actualSizeRequestImplImpl (::dw::core::Requisition *requisition, + int data1) +{ + // "data1" is a value passed from a special implementation of + // sizeRequestImplImpl to accumulateSize. See VBox for an example + // on usage. Extend by "data2" ... when needed. + + requisition->width = requisition->ascent = requisition->descent = 0; + + for (int i = 0; i < children->size (); i++) { + Requisition childReq; + children->get(i)->sizeRequest (&childReq); + accumulateSize (i, children->size (), requisition, &childReq, data1); + } + + requisition->width += getStyle()->boxDiffWidth (); + requisition->ascent += getStyle()->boxOffsetY (); + requisition->descent += getStyle()->boxRestHeight (); +} + +void Box::getExtremesImplImpl (Extremes *extremes) +{ + extremes->minWidth = extremes->maxWidth = 0; + + for (int i = 0; i < children->size (); i++) { + Extremes childExtr; + children->get(i)->getExtremes (&childExtr); + accumulateExtremes (i, children->size (), extremes, &childExtr); + } + + extremes->minWidth += getStyle()->boxDiffWidth (); + extremes->maxWidth += getStyle()->boxDiffWidth (); +} + +void Box::drawImpl (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); + for (int i = 0; i < children->size (); i++) { + Widget *child = children->get (i); + Rectangle childArea; + if (child->intersects (area, &childArea)) + child->draw (view, &childArea); + } + + DBG_OBJ_LEAVE (); +} + +::dw::core::Iterator *Box::iterator (Content::Type mask, bool atEnd) +{ + return new BoxIterator (this, mask, atEnd); +} + +void Box::removeChild (Widget *child) +{ + if (!inDestructor) { + for (int i = 0; i < children->size (); i++) { + if (children->get (i) == child) { + children->remove (i); + return; + } + } + + assertNotReached (); + } +} + +void Box::addChild (Widget *widget, int newPos) +{ + if (newPos == -1) + children->put (widget); + else + children->insert (widget, newPos); + + widget->setParent (this); + queueResize (0, true); +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/box.hh b/dwr/box.hh new file mode 100644 index 0000000..2fe5b2b --- /dev/null +++ b/dwr/box.hh @@ -0,0 +1,110 @@ +#ifndef __DWR_BOX_HH__ +#define __DWR_BOX_HH__ + +#include "hideable.hh" + +namespace rtfl { + +namespace dw { + +class Box: public Hideable +{ +private: + class BoxIterator: public ::dw::core::Iterator + { + private: + int index; + + BoxIterator (Box *box, ::dw::core::Content::Type mask, int index); + + public: + BoxIterator (Box *box, ::dw::core::Content::Type mask, bool atEnd); + + lout::object::Object *clone (); + int compareTo (lout::object::Comparable *other); + + bool next (); + bool prev (); + void highlight (int start, int end, ::dw::core::HighlightLayer layer); + void unhighlight (int direction, ::dw::core::HighlightLayer layer); + void getAllocation (int start, int end, + ::dw::core::Allocation *allocation); + }; + + bool inDestructor; + static const char countZeroTable[256]; + + /** + * \brief Calculate the number of bits 0 on the left. + * + * This is similar to calculating the logarithm base 2 (which finds + * the right-most bit 1), and a lookup as described at + * <http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup> + * is used. + */ + static inline int getZeroBits (int i) + { + // TODO This should not work for 64 bit integers. + unsigned int ih, ilh, ihh; + if ((ih = i >> 16)) + return ((ihh = ih >> 8)) ? + countZeroTable[ihh] : 8 + countZeroTable[ih]; + else + return ((ilh = i >> 8)) ? + 16 + countZeroTable[ilh] : 24 + countZeroTable[i]; + } + +protected: + lout::container::typed::Vector<Widget> *children; + bool stretchChildren; + + void sizeRequestImplImpl (::dw::core::Requisition *requisition); + void actualSizeRequestImplImpl (::dw::core::Requisition *requisition, + int data1); + void getExtremesImplImpl (::dw::core::Extremes *extremes); + + void drawImpl (::dw::core::View *view, ::dw::core::Rectangle *area); + + /** + * \brief Return a * b / c, but avoid overflow for large a and b. + */ + static inline unsigned int safeATimesBDividedByC (unsigned int a, + unsigned int b, + unsigned int c) + { + // TODO This should not work for 64 bit integers. + int z = getZeroBits (a) + getZeroBits (b); + if (z >= 32) + return a * b / c; + else { + //int n = 32 - z, n1 = n / 2, n2 = (n + 1) / 2; + int n = 32 - z, n1 = 0, n2 = n; + return (a >> n1) * (b >> n2) / (c >> n); + } + } + + virtual void accumulateSize (int index, int size, + ::dw::core::Requisition *totalReq, + ::dw::core::Requisition *childReq, + int data1) = 0; + virtual void accumulateExtremes (int index, int size, + ::dw::core::Extremes *totalExtr, + ::dw::core::Extremes *childExtr) = 0; + +public: + static int CLASS_ID; + + Box (bool stretchChildren); + ~Box (); + + ::dw::core::Iterator *iterator (::dw::core::Content::Type mask, bool atEnd); + void removeChild (Widget *child); + + void addChild (::dw::core::Widget *widget, int newPos = -1); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_BOX_HH__ 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 diff --git a/dwr/graph.hh b/dwr/graph.hh new file mode 100644 index 0000000..7e0897e --- /dev/null +++ b/dwr/graph.hh @@ -0,0 +1,126 @@ +#ifndef __DWR_GRAPH_HH__ +#define __DWR_GRAPH_HH__ + +#include "dw/core.hh" +#include "lout/misc.hh" + +namespace rtfl { + +namespace dw { + +class Graph: public ::dw::core::Widget +{ +private: + class GraphIterator: public ::dw::core::Iterator + { + private: + int index; + + GraphIterator (Graph *graph, ::dw::core::Content::Type mask, int index); + + public: + GraphIterator (Graph *graph, ::dw::core::Content::Type mask, bool atEnd); + + lout::object::Object *clone (); + int compareTo (lout::object::Comparable *other); + + bool next (); + bool prev (); + void highlight (int start, int end, ::dw::core::HighlightLayer layer); + void unhighlight (int direction, ::dw::core::HighlightLayer layer); + void getAllocation (int start, int end, + ::dw::core::Allocation *allocation); + }; + + class Node: public lout::object::Object + { + public: + enum { WIDGET, REF } type; + union { + ::dw::core::Widget *widget; + struct { + char *text; + Node *reference; + }; + }; + Node *parent; + lout::container::typed::List<Node> *children; + + int rootX, rootY; + ::dw::core::Requisition rootReq, childrenReq; + + Node (::dw::core::Widget *widget, Node *parent); + Node (const char *text, Node *parent); + ~Node (); + }; + + enum { HDIFF = 50, VDIFF = 20, AHEADLEN = 10 }; + + lout::container::typed::Vector<Widget> *allWidgets; + lout::container::typed::List<Node> *topLevelNodes; + ::dw::core::style::Style *refStyle; + Node *pressedRefNode; + bool inDestructor; + + Node *searchWidget (::dw::core::Widget *widget, + lout::container::typed::List<Node> *list); + inline Node *searchWidget (::dw::core::Widget *widget) + { return searchWidget (widget, topLevelNodes); } + + void sizeRequest (::dw::core::Requisition *requisition, + lout::container::typed::List<Node> *list); + void sizeRequest (::dw::core::Requisition *requisition, Node *node); + void getExtremes (::dw::core::Extremes *extremes, + lout::container::typed::List<Node> *list); + void getExtremes (::dw::core::Extremes *extremes, Node *node); + void sizeAllocate (int x, int y, lout::container::typed::List<Node> *list); + void sizeAllocate (int x, int y, Node *node); + + void draw (::dw::core::View *view, ::dw::core::Rectangle *area, + lout::container::typed::List<Node> *list); + void drawArrows (::dw::core::View *view, ::dw::core::Rectangle *area, + Node *node); + + bool isAncestorOf (Node *n1, Node *n2); + void addReference (Node *from, Node *to); + + // TODO Not very efficient. Since there are not many ref nodes, + // they could be kept in one list. + Node *searchRefNode (::dw::core::MousePositionEvent *event) + { return searchRefNode (event, topLevelNodes); } + Node *searchRefNode (::dw::core::MousePositionEvent *event, + lout::container::typed::List<Node> *list); + +protected: + void sizeRequestImpl (::dw::core::Requisition *requisition); + void getExtremesImpl (::dw::core::Extremes *extremes); + void sizeAllocateImpl (::dw::core::Allocation *allocation); + + bool buttonPressImpl (::dw::core::EventButton *event); + bool buttonReleaseImpl (::dw::core::EventButton *event); + bool motionNotifyImpl (::dw::core::EventMotion *event); + void leaveNotifyImpl (::dw::core::EventCrossing *event); + +public: + static int CLASS_ID; + + Graph (); + ~Graph (); + + void draw (::dw::core::View *view, ::dw::core::Rectangle *area); + ::dw::core::Iterator *iterator (::dw::core::Content::Type mask, bool atEnd); + void removeChild (Widget *child); + void setStyle (::dw::core::style::Style *style); + + void addNode (::dw::core::Widget *widget); + void addEdge (::dw::core::Widget *from, ::dw::core::Widget *to); + void removeEdge (::dw::core::Widget *from, ::dw::core::Widget *to); + + void setRefStyle (::dw::core::style::Style *style); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_GRAPH_HH__ diff --git a/dwr/graph2.cc b/dwr/graph2.cc new file mode 100644 index 0000000..28f03f9 --- /dev/null +++ b/dwr/graph2.cc @@ -0,0 +1,505 @@ +/* + * RTFL + * + * Copyright 2014, 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/>. + */ + +/* ---------------------------------------------------------------------- + + This is an experimenal graph widget which replaces the widget Graph + currently used by rtfl-objview. It uses Graphviz for layouting; in + detail, pixels and points (1 point = 1/72 of an inch) are treated + as identical, for attributes "bb", "pos" etc. ("width" and "height" + are given in inch, so these values are calculated by division by + 72). + + See <http://www.graphviz.org/doc/info/attrs.html> for informations + on Graphviz attributes. + + Open issues: + + - The order of the children should be preserved. When B1, B2 and B3 + are associated to A, in this order, this order should be visible, + even at the expense of other factors. + + (My original idea was to implement this widget from scratch, + using a force directed approach with + + (i) invisible edges between the "children" (in the example + above: B1 -> B2 and B2 -> B3), which cause attraction of + the respective nodes in a similar way the visible nodes do + ("springs"); + + (ii) a homogeneous force field directed to the right and acting + on the heads of the *visible* edges, so giving the graph + layout an orientation from left to right; + + (iii) a second homogeneous force field directed to the bottom, + which acts only on the heads of the *invisible* edges. + + (i) and (iii) would lead to the desired result.) + + Update: setting "ordering" to "out" is a step into this + direction. (This seems to enforce the order of the edges, not the + nodes.) + + - Configurability: the algorithm (currently "dot") and some more + parameters (like "rankdir=LR") could be made configurable. + + ---------------------------------------------------------------------- */ + +#include "graph2.hh" +#include "tools.hh" +#include "../common/tools.hh" +#include "../lout/misc.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::container::typed; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int Graph2::CLASS_ID = -1; + +Graph2::Node::Node (Graph2 *graph, Widget *widget, int index) +{ + this->graph = graph; + this->widget = widget; + this->index = index; + + initAg (); +} + +Graph2::Node::~Node () +{ + if (widget) + delete widget; + + cleanupAg (); +} + +void Graph2::Node::initAg () +{ + assert (graph->graph); + + char buf[64]; + snprintf (buf, 64, "n%d", index); + node = agnode(graph->graph, buf, TRUE); + agsafeset (node, (char*)"shape", (char*)"rect", (char*)""); +} + +void Graph2::Node::cleanupAg () +{ + if (node) { + assert (graph->graph); + agdelete (graph->graph, node); + node = NULL; + } +} + +Graph2::Edge::Edge (Graph2 *graph, Node *from, Node *to, int index) +{ + this->graph = graph; + this->from = from; + this->to = to; + this->index = index; + + initAg (); + + numPoints = numPointsAlloc = 0; + pointX = NULL; + pointY = NULL; + pointType = NULL; + + count = 1; +} + +Graph2::Edge::~Edge () +{ + cleanupAg (); + cleanupPoints (); +} + +void Graph2::Edge::initAg () +{ + assert (graph->graph); + + char buf[64]; + snprintf (buf, 64, "e%d", index); + edge = agedge (graph->graph, from->node, to->node, (char*)buf, TRUE); +} + +void Graph2::Edge::cleanupAg () +{ + if (edge) { + assert (graph->graph); + agdelete (graph->graph, edge); + edge = NULL; + } +} + +void Graph2::Edge::cleanupPoints () +{ + if (numPointsAlloc > 0) { + delete[] pointX; + delete[] pointY; + delete[] pointType; + + pointX = NULL; + pointY = NULL; + pointType = NULL; + + numPointsAlloc = 0; + } +} + +void Graph2::Edge::setNumPoints (int numPoints) +{ + if (numPoints > numPointsAlloc) { + cleanupPoints (); + + numPointsAlloc = numPoints; + + pointX = new int[numPointsAlloc]; + pointY = new int[numPointsAlloc]; + pointType = new char[numPointsAlloc]; + } + + this->numPoints = numPoints; +} + +void Graph2::Edge::sortPoints () +{ + // If a start point exists, it is at the beginning. If an end point + // exists, it is also at the beginning or follows the start point. + // Here, it is moved at the end of the list. + + if ((numPoints >= 1 && pointType[0] == 'e') || + (numPoints >= 2 && pointType[1] == 'e')) { + int epos = pointType[0] == 'e' ? 0 : 1; + int ex = pointX[epos], ey = pointY[epos]; + + for (int i = epos; i < numPoints - 1; i++) { + pointX[i] = pointX[i + 1]; + pointY[i] = pointY[i + 1]; + pointType[i] = pointType[i + 1]; + } + + pointX[numPoints - 1] = ex; + pointY[numPoints - 1] = ey; + pointType[numPoints - 1] = 'e'; + } +} + +Graph2::Graph2 () +{ + DBG_OBJ_CREATE ("rtfl::dw:"###); + registerName ("rtfl::dw::Graph2", &CLASS_ID); + + nodes = new Vector<Node> (4, true); + edges = new Vector<Edge> (4, true); + + initAg (); +} + +Graph2::~Graph2 () +{ + inDestructor = true; + + cleanupAg (); + + delete nodes; + delete edges; + + DBG_OBJ_DELETE (); +} + +void Graph2::initAg () +{ + gvc = gvContext (); + graph = agopen ((char*)"", Agdirected, NULL); + agsafeset (graph, (char*)"rankdir", (char*)"LR", (char*)""); + agsafeset (graph, (char*)"ordering", (char*)"out", (char*)""); + + for (int i = 0; i < nodes->size (); i++) + nodes->get(i)->initAg (); + + for (int i = 0; i < edges->size (); i++) + edges->get(i)->initAg (); +} + +void Graph2::cleanupAg () +{ + for (int i = 0; i < nodes->size (); i++) + nodes->get(i)->cleanupAg (); + + for (int i = 0; i < edges->size (); i++) + edges->get(i)->cleanupAg (); + + if (graph) { + agclose (graph); + graph = NULL; + } + + if (gvc) { + gvFreeContext(gvc); + gvc = NULL; + } +} + +void Graph2::sizeRequestImpl (Requisition *requisition) +{ + for (int i = 0; i < nodes->size (); i++) { + Node *node = nodes->get (i); + + Requisition childReq; + node->widget->sizeRequest (&childReq); + + char buf[64]; + snprintf (buf, 64, "%f", (float)childReq.width / 72); + agsafeset (node->node, (char*)"width", buf, (char*)""); + snprintf (buf, 64, "%f", + (float)(childReq.ascent + childReq.descent) / 72); + agsafeset (node->node, (char*)"height", buf, (char*)""); + } + + //puts ("---------- before layouting ----------"); + //agwrite (graph, stdout); + + gvLayout (gvc, graph, "dot"); + gvRender(gvc, graph, "dot", NULL); + gvFreeLayout(gvc, graph); + + //puts ("---------- after layouting ----------"); + //agwrite (graph, stdout); + + float x, y, width, height; + sscanf (agget (graph, (char*)"bb"), "%f,%f,%f,%f", &x, &y, &width, &height); + + requisition->width = width + getStyle()->boxDiffWidth (); + requisition->ascent = height + getStyle()->boxOffsetY (); + requisition->descent = getStyle()->boxRestHeight (); +} + +void Graph2::getExtremesImpl (Extremes *extremes) +{ + assertNotReached (); +} + +void Graph2::sizeAllocateImpl (Allocation *allocation) +{ + //agwrite (graph, stdout); + + Requisition req; + sizeRequest (&req); + + for (int i = 0; i < nodes->size (); i++) { + Node *node = nodes->get (i); + + Requisition childReq; + node->widget->sizeRequest (&childReq); + + float x, y; + sscanf (agget (node->node, (char*)"pos"), "%f,%f", &x, &y); + + Allocation childAlloc; + childAlloc.x = getStyle()->boxOffsetX () + x - childReq.width / 2; + childAlloc.y = getStyle()->boxOffsetY () + + (req.ascent + req.descent - getStyle()->boxDiffHeight ()) + - y - (childReq.ascent + childReq.descent) / 2; + childAlloc.width = childReq.width; + childAlloc.ascent = childReq.ascent; + childAlloc.descent = childReq.descent; + node->widget->sizeAllocate (&childAlloc); + } + + for (int i = 0; i < edges->size (); i++) { + Edge *edge = edges->get (i); + + char *pos = agget (edge->edge, (char*)"pos"); + int lenPos = strlen (pos); + char *posBuf = new char[lenPos + 1]; + strncpy (posBuf, pos, lenPos + 1); + + int numSpaces = 0; + for (char *s = posBuf; *s; s++) + if (*s == ' ') + numSpaces++; + + edge->setNumPoints (numSpaces + 1); + + int noPoint = 0; + bool atEnd = false; + for (char *start = posBuf; !atEnd; ) { + char *end = start; + while (*end != ' ' && *end != 0) + end++; + atEnd = (*end == 0); + + *end = 0; + + char pointType, *posStart; + if ((start[0] == 's' || start[0] == 'e') && start[1] == ',') { + pointType = start[0]; + posStart = start + 2; + } else { + pointType = 0; + posStart = start; + } + + float x, y; + sscanf (posStart, "%f,%f", &x, &y); + + edge->pointX[noPoint] = getStyle()->boxOffsetX () + x; + edge->pointY[noPoint] = getStyle()->boxOffsetY () + + (req.ascent + req.descent - getStyle()->boxDiffHeight ()) - y; + edge->pointType[noPoint] = pointType; + + noPoint++; + start = end + 1; + } + + edge->sortPoints (); + + delete[] posBuf; + } +} + +void Graph2::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); + + for (int i = 0; i < nodes->size (); i++) { + Widget *widget = nodes->get(i)->widget; + Rectangle childArea; + if (widget->intersects (area, &childArea)) + widget->draw (view, &childArea); + } + + for (int i = 0; i < edges->size (); i++) { + Edge *edge = edges->get (i); + + tools::drawBSpline (view, getStyle(), 4, edge->numPoints, edge->pointX, + edge->pointY); + + for (int j = 0; j < edge->numPoints - 1; j++) { + // TODO: arrow heads should only be drawn at the first and the last + // point. In this case, the direction is correct; in other cases, the + // the points are not even part of the curve. + + if (edge->pointType[j] == 'e') + tools::drawArrowHead (view, getStyle (), + edge->pointX[j + 1], edge->pointY[j + 1], + edge->pointX[j], edge->pointY[j], + AHEADLEN); + + if (edge->pointType[j + 1] == 'e') + tools::drawArrowHead (view, getStyle (), + edge->pointX[j], edge->pointY[j], + edge->pointX[j + 1], edge->pointY[j + 1], + AHEADLEN); + } + } + + DBG_OBJ_LEAVE (); +} + +::dw::core::Iterator *Graph2::iterator (Content::Type mask, bool atEnd) +{ + return new Graph2Iterator (this, mask, atEnd); +} + +void Graph2::removeChild (Widget *child) +{ + if (!inDestructor) { + int nodeIndex = searchNodeIndex (child); + Node *node = nodes->get (nodeIndex); + + // Otherwise, Node::~Node would delete the widget again: + node->widget = NULL; + + for (int i = 0; i < edges->size (); i++) { + Edge *edge = edges->get (i); + if (edge->from == node || edge->to == node) + edges->remove (i); + } + + nodes->remove (nodeIndex); + + queueResize (0, true); + } +} + +void Graph2::addNode (Widget *widget) +{ + Node *node = new Node (this, widget, nodes->size ()); + nodes->put (node); + + widget->setParent (this); + + queueResize (0, true); +} + +void Graph2::addEdge (Widget *from, Widget *to) +{ + for (int i = 0; i < edges->size (); i++) { + Edge *edge = edges->get (i); + if (edge->from->widget == from && edge->to->widget == to) { + edge->count++; + printf ("WARNING: Edge already added the %d%s time.\n", edge->count, + rtfl::tools::numSuffix (edge->count)); + return; + } + } + + Edge *edge = + new Edge (this, searchNode (from), searchNode (to), edges->size ()); + edges->put (edge); + + queueResize (0, true); +} + +int Graph2::searchNodeIndex (Widget *widget) +{ + for (int i = 0; i < nodes->size (); i++) { + Node *node = nodes->get (i); + if (node->widget == widget) + return i; + } + + assertNotReached (); + return 0; +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/graph2.hh b/dwr/graph2.hh new file mode 100644 index 0000000..128ba32 --- /dev/null +++ b/dwr/graph2.hh @@ -0,0 +1,122 @@ +#ifndef __DWR_GRAPH2_HH__ +#define __DWR_GRAPH2_HH__ + +#include <graphviz/gvc.h> + +#include "dw/core.hh" + +namespace rtfl { + +namespace dw { + +class Graph2: public ::dw::core::Widget +{ +private: + class Graph2Iterator: public ::dw::core::Iterator + { + private: + int index; + + Graph2Iterator (Graph2 *graph, ::dw::core::Content::Type mask, int index); + + public: + Graph2Iterator (Graph2 *graph, ::dw::core::Content::Type mask, + bool atEnd); + + lout::object::Object *clone (); + int compareTo (lout::object::Comparable *other); + + bool next (); + bool prev (); + void highlight (int start, int end, ::dw::core::HighlightLayer layer); + void unhighlight (int direction, ::dw::core::HighlightLayer layer); + void getAllocation (int start, int end, + ::dw::core::Allocation *allocation); + }; + + class Node: public lout::object::Object + { + private: + Graph2 *graph; + int index; + + public: + Node (Graph2 *graph, Widget *widget, int index); + ~Node (); + + void initAg (); + void cleanupAg (); + + Widget *widget; + Agnode_t *node; + }; + + class Edge: public lout::object::Object + { + private: + Graph2 *graph; + int numPointsAlloc, index; + + void cleanupPoints (); + + public: + Edge (Graph2 *graph, Node *from, Node *to, int index); + ~Edge (); + + void initAg (); + void cleanupAg (); + + void setNumPoints (int numPoints); + void sortPoints (); + + Node *from, *to; + Agedge_t *edge; + int numPoints; + int *pointX, *pointY; + char *pointType; + + int count; + }; + + enum { AHEADLEN = 10 }; + + Agraph_t *graph; + GVC_t *gvc; + lout::container::typed::Vector<Node> *nodes; + lout::container::typed::Vector<Edge> *edges; + bool inDestructor; + + void initAg (); + void cleanupAg (); + + int searchNodeIndex (Widget *widget); + inline Node *searchNode (Widget *widget) + { return nodes->get (searchNodeIndex (widget)); } + +protected: + void sizeRequestImpl (::dw::core::Requisition *requisition); + void getExtremesImpl (::dw::core::Extremes *extremes); + void sizeAllocateImpl (::dw::core::Allocation *allocation); + +public: + static int CLASS_ID; + + Graph2 (); + ~Graph2 (); + + void draw (::dw::core::View *view, ::dw::core::Rectangle *area); + ::dw::core::Iterator *iterator (::dw::core::Content::Type mask, bool atEnd); + void removeChild (Widget *child); + + inline void setRefStyle (::dw::core::style::Style *style) + { /* No need anymore. */ } + + void addNode (Widget *widget); + void addEdge (::dw::core::Widget *from, ::dw::core::Widget *to); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_GRAPH2_HH__ diff --git a/dwr/graph2_iterator.cc b/dwr/graph2_iterator.cc new file mode 100644 index 0000000..d546d39 --- /dev/null +++ b/dwr/graph2_iterator.cc @@ -0,0 +1,139 @@ +/* + * RTFL + * + * Copyright 2014, 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 "graph2.hh" + +using namespace dw::core; + +namespace rtfl { + +namespace dw { + +Graph2::Graph2Iterator::Graph2Iterator (Graph2 *graph, Content::Type mask, + bool atEnd) : + Iterator (graph, mask, atEnd) +{ + index = atEnd ? graph->nodes->size() : -1; + content.type = atEnd ? Content::END : Content::START; +} + +Graph2::Graph2Iterator::Graph2Iterator (Graph2 *graph, Content::Type mask, + int index) : + Iterator (graph, mask, false) +{ + this->index = index; + + if (index < 0) + content.type = Content::START; + else if (index >= graph->nodes->size ()) + content.type = Content::END; + else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = graph->nodes->get(index)->widget; + } +} + +lout::object::Object *Graph2::Graph2Iterator::clone () +{ + return new Graph2Iterator ((Graph2*)getWidget(), getMask(), index); +} + +int Graph2::Graph2Iterator::compareTo (lout::object::Comparable *other) +{ + return index - ((Graph2Iterator*)other)->index; +} + +bool Graph2::Graph2Iterator::next () +{ + Graph2 *graph = (Graph2*)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->nodes->size ()) { + content.type = Content::END; + return false; + } else { + content.type = Content::WIDGET_IN_FLOW; + content.widget = graph->nodes->get(index)->widget; + return true; + } +} + +bool Graph2::Graph2Iterator::prev () +{ + Graph2 *graph = (Graph2*)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->nodes->get(index)->widget; + return true; + } +} + +void Graph2::Graph2Iterator::highlight (int start, int end, + HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Graph2::Graph2Iterator::unhighlight (int direction, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Graph2::Graph2Iterator::getAllocation (int start, int end, + Allocation *allocation) +{ + /** \bug Not implemented. */ +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/hbox.cc b/dwr/hbox.cc new file mode 100644 index 0000000..6184078 --- /dev/null +++ b/dwr/hbox.cc @@ -0,0 +1,123 @@ +/* + * 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 "hbox.hh" + +using namespace dw::core; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int HBox::CLASS_ID = -1; + +HBox::HBox (bool stretchChildren): Box (stretchChildren) +{ + DBG_OBJ_CREATE ("rtfl::dw::HBox"); + registerName ("rtfl::dw::HBox", &CLASS_ID); +} + +HBox::~HBox () +{ + DBG_OBJ_DELETE (); +} + +void HBox::sizeAllocateImpl (Allocation *allocation) +{ + Allocation allocWOBorders; + allocWOBorders.x = allocation->x + getStyle()->boxOffsetX (); + allocWOBorders.y = allocation->y + getStyle()->boxOffsetY (); + allocWOBorders.width = allocation->width - getStyle()->boxDiffWidth (); + allocWOBorders.ascent = allocation->ascent - getStyle()->boxOffsetY (); + allocWOBorders.descent = allocation->descent - getStyle()->boxRestHeight (); + + int sumReqWidth = 0; + for (int i = 0; i < children->size (); i++) { + Requisition childReq; + children->get(i)->sizeRequest (&childReq); + sumReqWidth += childReq.width; + } + + bool stretch = stretchChildren && sumReqWidth < allocWOBorders.width; + + // Cf. doc/rounding-errors.doc, with: x[i] = child requisition, + // y[i] = child allocation, a = total allocation, b = sumReqWidth. + int cumChildReqWidth = 0, cumChildAllocWidth = 0; + for (int i = 0; i < children->size (); i++) { + Widget *child = children->get (i); + + Requisition childReq; + child->sizeRequest (&childReq); + + Allocation childAlloc; + childAlloc.x = allocWOBorders.x + cumChildAllocWidth; + childAlloc.y = allocWOBorders.y; + + if (stretch) { + cumChildReqWidth += childReq.width; + childAlloc.width = sumReqWidth > 0 ? + safeATimesBDividedByC (cumChildReqWidth, allocWOBorders.width, + sumReqWidth) - cumChildAllocWidth : + 0; + childAlloc.ascent = allocWOBorders.ascent; + childAlloc.descent = allocWOBorders.descent; + } else + childAlloc.width = childReq.width; + + childAlloc.ascent = allocWOBorders.ascent; + childAlloc.descent = allocWOBorders.descent; + cumChildAllocWidth += childAlloc.width; + + child->sizeAllocate (&childAlloc); + + //printf ("hbox: %dth child at (%d, %d), %d, (%d x %d)\n", + // i, childAlloc.x, childAlloc.y, childAlloc.width, + // childAlloc.ascent, childAlloc.descent); + } +} + +void HBox::accumulateSize (int index, int size, Requisition *totalReq, + Requisition *childReq, int data1) +{ + totalReq->width += childReq->width; + totalReq->ascent = max (totalReq->ascent, childReq->ascent); + totalReq->descent = max (totalReq->descent, childReq->descent); +} + +void HBox::accumulateExtremes (int index, int size, Extremes *totalExtr, + Extremes *childExtr) +{ + totalExtr->minWidth += childExtr->minWidth; + totalExtr->maxWidth += childExtr->maxWidth; +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/hbox.hh b/dwr/hbox.hh new file mode 100644 index 0000000..79b71ff --- /dev/null +++ b/dwr/hbox.hh @@ -0,0 +1,31 @@ +#ifndef __DWR_HBOX_HH__ +#define __DWR_HBOX_HH__ + +#include "box.hh" + +namespace rtfl { + +namespace dw { + +class HBox: public Box +{ +protected: + void sizeAllocateImpl (::dw::core::Allocation *allocation); + void accumulateSize (int index, int size, ::dw::core::Requisition *totalReq, + ::dw::core::Requisition *childReq, int data1); + void accumulateExtremes (int index, int size, + ::dw::core::Extremes *totalExtr, + ::dw::core::Extremes *childExtr); + +public: + static int CLASS_ID; + + HBox (bool stretchChildren); + ~HBox (); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_HBOX_HH__ diff --git a/dwr/hideable.cc b/dwr/hideable.cc new file mode 100644 index 0000000..def90a5 --- /dev/null +++ b/dwr/hideable.cc @@ -0,0 +1,104 @@ +/* + * RTFL + * + * Copyright 2014, 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 "hideable.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int Hideable::CLASS_ID = -1; + +Hideable::Hideable () +{ + DBG_OBJ_CREATE ("rtfl::dw::Hideable"); + registerName ("rtfl::dw::Hideable", &CLASS_ID); + + hidden = drawable = false; +} + +Hideable::~Hideable () +{ + DBG_OBJ_DELETE (); +} + +void Hideable::sizeRequestImpl (Requisition *requisition) +{ + if (hidden) { + requisition->width = requisition->ascent = requisition->descent = 0; + drawable = false; + } else { + sizeRequestImplImpl (requisition); + drawable = true; + } +} + +void Hideable::getExtremesImpl (Extremes *extremes) +{ + if (hidden) + extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth = + extremes->maxWidthIntrinsic = 0; + else + getExtremesImplImpl (extremes); +} + +void Hideable::draw (View *view, Rectangle *area) +{ + DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + if (drawable) + drawImpl (view, area); + + DBG_OBJ_LEAVE (); +} + +void Hideable::show () +{ + if (hidden) { + hidden = false; + queueResize (0, true); + } +} + +void Hideable::hide () +{ + if (!hidden) { + hidden = true; + queueResize (0, true); + } +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/hideable.hh b/dwr/hideable.hh new file mode 100644 index 0000000..aabf807 --- /dev/null +++ b/dwr/hideable.hh @@ -0,0 +1,39 @@ +#ifndef __DWR_HIDEABLE_HH__ +#define __DWR_HIDEABLE_HH__ + +#include "dw/core.hh" + +namespace rtfl { + +namespace dw { + +class Hideable: public ::dw::core::Widget +{ + bool hidden, drawable; + +protected: + void sizeRequestImpl (::dw::core::Requisition *requisition); + void getExtremesImpl (::dw::core::Extremes *extremes); + + virtual void sizeRequestImplImpl (::dw::core::Requisition *requisition) = 0; + virtual void getExtremesImplImpl (::dw::core::Extremes *extremes) = 0; + virtual void drawImpl (::dw::core::View *view, ::dw::core::Rectangle *area) + = 0; + +public: + static int CLASS_ID; + + Hideable (); + ~Hideable (); + + void draw (::dw::core::View *view, ::dw::core::Rectangle *area); + + void show (); + void hide (); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_HIDEABLE_HH__ diff --git a/dwr/label.cc b/dwr/label.cc new file mode 100644 index 0000000..dfef145 --- /dev/null +++ b/dwr/label.cc @@ -0,0 +1,378 @@ +/* + * 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 "label.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int Label::CLASS_ID = -1; + +// ---------------------------------------------------------------------- + +Label::LabelIterator::LabelIterator (Label *label, Content::Type mask, + bool atEnd) : Iterator (label, mask, atEnd) +{ + index = atEnd ? label->words->size() : -1; + content.type = atEnd ? Content::END : Content::START; +} + +Label::LabelIterator::LabelIterator (Label *label, Content::Type mask, + int index) : Iterator (label, mask, false) +{ + this->index = index; + + if (index < 0) + content.type = Content::START; + else if (index >= label->words->size ()) + content.type = Content::END; + else { + content.type = Content::TEXT; + content.text = label->words->getRef(index)->text; + } +} + +lout::object::Object *Label::LabelIterator::clone() +{ + return new LabelIterator ((Label*)getWidget(), getMask(), index); +} + +int Label::LabelIterator::compareTo(lout::object::Comparable *other) +{ + return index - ((LabelIterator*)other)->index; +} + +bool Label::LabelIterator::next () +{ + Label *label = (Label*)getWidget(); + + if (content.type == Content::END) + return false; + + // labels only contain widgets: + if ((getMask() & Content::TEXT) == 0) { + content.type = Content::END; + return false; + } + + index++; + if (index >= label->words->size ()) { + content.type = Content::END; + return false; + } else { + content.type = Content::TEXT; + content.text = label->words->getRef(index)->text; + return true; + } +} + +bool Label::LabelIterator::prev () +{ + Label *label = (Label*)getWidget(); + + if (content.type == Content::START) + return false; + + // labels only contain widgets: + if ((getMask() & Content::TEXT) == 0) { + content.type = Content::START; + return false; + } + + index--; + if (index < 0) { + content.type = Content::START; + return false; + } else { + content.type = Content::TEXT; + content.text = label->words->getRef(index)->text; + return true; + } +} + +void Label::LabelIterator::highlight (int start, int end, HighlightLayer layer) +{ + /** \bug Not implemented. */ +} + +void Label::LabelIterator::unhighlight (int direction, HighlightLayer layer) +{ + /** \bug Not implemented. */ +} + +void Label::LabelIterator::getAllocation (int start, int end, + Allocation *allocation) +{ + /** \bug Not implemented. */ +} + +// ---------------------------------------------------------------------- + +Label::Label (const char *text, int link) +{ + DBG_OBJ_CREATE ("rtfl::dw::Label"); + registerName ("rtfl::dw::Label", &CLASS_ID); + + words = new SimpleVector<Word> (1); + + for (int i = 0; i < 4; i++) + styles[i] = NULL; + + selected = buttonDown = false; + this->link = link; + + setText (text); +} + +Label::~Label () +{ + clearWords (); + delete words; + + clearStyles (); + + DBG_OBJ_DELETE (); +} + +void Label::setText (const char *text) +{ + clearWords (); + + DBG_OBJ_SET_STR ("text", text); + + // Parse text for tags <i>, </i>, <b>, </b>. Very simple, no stack. + const char *start = text; + int styleIndex = 0; + while (*start) { + const char *end = start; + + while (!(*end == 0 || + strncmp (end, "<i>", 3) == 0 || strncmp (end, "<b>", 3) == 0 || + strncmp (end, "</i>", 4) == 0 || strncmp (end, "</b>", 4) == 0)) + end++; + + //printf ("start: '%s', end: '%s'\n", start, end); + + if (end > start) { + words->increase (); + Word *word = words->getLastRef (); + word->text = strndup (start, end - start); + word->styleIndex = styleIndex; + + //printf (" new word '%s' with attributes %c%c\n", word->text, + // (word->styleIndex & ITALIC) ? 'i' : '-', + // (word->styleIndex & BOLD) ? 'b' : '-'); + } + + if (*end == 0) + start = end; + else if (strncmp (end, "<i>", 3) == 0) { + start = end + 3; + styleIndex |= ITALIC; + } else if (strncmp (end, "<b>", 3) == 0) { + start = end + 3; + styleIndex |= BOLD; + } else if (strncmp (end, "</i>", 4) == 0) { + start = end + 4; + styleIndex &= ~ITALIC; + } else if (strncmp (end, "</b>", 4) == 0) { + start = end + 4; + styleIndex &= ~BOLD; + } else + assertNotReached (); + } + + queueResize (0, true); +} + +void Label::clearWords () +{ + for (int i = 0; i < words->size (); i++) + free (words->getRef(i)->text); + words->setSize (0); +} + +void Label::clearStyles () +{ + for (int i = 0; i < 4; i++) + if (styles[i]) { + styles[i]->unref (); + styles[i] = NULL; + } +} + +void Label::ensureStyles () +{ + if (getStyle () && styles[0] == NULL) { + styles[0] = getStyle (); + styles[0]->ref (); + + for (int i = 1; i < 4; i++) { + StyleAttrs styleAttrs = *getStyle (); + FontAttrs fontAttrs = *(styleAttrs.font); + fontAttrs.weight = (i & BOLD) ? 700 : 400; + fontAttrs.style = (i & ITALIC) ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL; + styleAttrs.font = Font::create (layout, &fontAttrs); + styles[i] = Style::create (&styleAttrs); + } + } +} + +void Label::sizeRequestImplImpl (Requisition *requisition) +{ + totalSize.width = totalSize.ascent = totalSize.descent = 0; + + if (getStyle ()) { + ensureStyles (); + + for (int i = 0; i < words->size (); i++) { + Word *word = words->getRef(i); + Font *font = styles[(int)word->styleIndex]->font; + + word->size.width = + layout->textWidth (font, word->text, strlen (word->text)); + word->size.ascent = font->ascent; + word->size.descent = font->descent; + + totalSize.width += word->size.width; + totalSize.ascent = max (totalSize.ascent, word->size.ascent); + totalSize.descent = max (totalSize.descent, word->size.descent); + } + } else + totalSize.width = totalSize.ascent = totalSize.descent = 0; + + requisition->width = totalSize.width + getStyle()->boxDiffWidth (); + requisition->ascent = totalSize.ascent + getStyle()->boxOffsetY (); + requisition->descent = totalSize.descent + getStyle()->boxRestHeight (); +} + +void Label::getExtremesImplImpl (Extremes *extremes) +{ + // Not used within RTFL. + assertNotReached (); +} + +void Label::drawImpl (View *view, Rectangle *area) +{ + DBG_OBJ_ENTER ("drawImpl", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + drawWidgetBox (view, area, selected); + + if (getStyle ()) { + ensureStyles (); + + // Could adhere to style::textAlign. This can be used for + // centered text: + // + // int x = getAllocation()->x + // + (getAllocation()->width - totalSize.width) / 2; + // + // Now, only left-aligned text is supported. + + int x = getAllocation()->x + getStyle()->boxOffsetX (); + + // Same for style::valign: + // + // int baseY = getAllocation()->y + // + (getHeight () - (totalSize.ascent + totalSize.descent)) / 2 + // + totalSize.ascent; + + int baseY = + getAllocation()->y + totalSize.ascent + getStyle()->boxOffsetY (); + + for (int i = 0; i < words->size (); i++) { + Word *word = words->getRef(i); + view-> drawText (styles[(int)word->styleIndex]->font, + styles[(int)word->styleIndex]->color, + selected ? Color::SHADING_INVERSE + : Color::SHADING_NORMAL, + x, baseY, word->text, strlen (word->text)); + x += word->size.width; + } + } + + DBG_OBJ_LEAVE (); +} + +void Label::leaveNotifyImpl (EventCrossing *event) +{ + buttonDown = false; +} + +bool Label::buttonPressImpl (EventButton *event) +{ + if (link != -1 && event->button == 1) { + buttonDown = true; + return true; + } else + return false; +} + +bool Label::buttonReleaseImpl (EventButton *event) +{ + if (link != -1 && event->button == 1) { + if (buttonDown) + linkEmitter.emitClick (this, link, -1, -1, -1, event); + return true; + } else + return false; +} + +Iterator *Label::iterator (Content::Type mask, bool atEnd) +{ + return new LabelIterator (this, mask, atEnd); +} + +void Label::setStyle (Style *style) +{ + Widget::setStyle (style); + clearStyles (); +} + +void Label::select () +{ + selected = true; + queueDraw (); +} + +void Label::unselect () +{ + selected = false; + queueDraw (); +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/label.hh b/dwr/label.hh new file mode 100644 index 0000000..8a069b0 --- /dev/null +++ b/dwr/label.hh @@ -0,0 +1,87 @@ +#ifndef __DWR_LABEL_HH__ +#define __DWR_LABEL_HH__ + +#include "dwr/hideable.hh" +#include "lout/misc.hh" + +namespace rtfl { + +namespace dw { + +class Label: public Hideable +{ +private: + class LabelIterator: public ::dw::core::Iterator + { + private: + int index; + + LabelIterator (Label *label, ::dw::core::Content::Type mask, int index); + + public: + LabelIterator (Label *label, ::dw::core::Content::Type mask, bool atEnd); + + lout::object::Object *clone (); + int compareTo (lout::object::Comparable *other); + + bool next (); + bool prev (); + void highlight (int start, int end, ::dw::core::HighlightLayer layer); + void unhighlight (int direction, ::dw::core::HighlightLayer layer); + void getAllocation (int start, int end, + ::dw::core::Allocation *allocation); + }; + + enum { ITALIC = 2, BOLD = 1 }; + + struct Word + { + char *text; + char styleIndex; + ::dw::core::Requisition size; + }; + + ::dw::core::style::Style *styles[4]; + lout::misc::SimpleVector<Word> *words; + ::dw::core::Requisition totalSize; + bool selected, buttonDown; + int link; + ::dw::core::Layout::LinkEmitter linkEmitter; + + void clearWords (); + void clearStyles (); + void ensureStyles (); + +protected: + void sizeRequestImplImpl (::dw::core::Requisition *requisition); + void getExtremesImplImpl (::dw::core::Extremes *extremes); + void drawImpl (::dw::core::View *view, ::dw::core::Rectangle *area); + + bool buttonPressImpl (::dw::core::EventButton *event); + bool buttonReleaseImpl (::dw::core::EventButton *event); + void leaveNotifyImpl (::dw::core::EventCrossing *event); + +public: + static int CLASS_ID; + + Label (const char *text, int link = -1); + ~Label (); + + void setText (const char *text); + inline void setLink (int link) { this->link = link; } + + ::dw::core::Iterator *iterator (::dw::core::Content::Type mask, bool atEnd); + void setStyle (::dw::core::style::Style *style); + + void select (); + void unselect (); + + inline void connectLink (::dw::core::Layout::LinkReceiver *receiver) + { linkEmitter.connectLink (receiver); } +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_LABEL_HH__ diff --git a/dwr/toggle.cc b/dwr/toggle.cc new file mode 100644 index 0000000..a400237 --- /dev/null +++ b/dwr/toggle.cc @@ -0,0 +1,402 @@ +/* + * 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 <math.h> + +#include "toggle.hh" + +using namespace dw::core; +using namespace dw::core::style; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int Toggle::CLASS_ID = -1; + +Toggle::ButtonStyle Toggle::buttonStyle = PLUS_MINUS; + +// ---------------------------------------------------------------------- + +Toggle::ToggleIterator::ToggleIterator (Toggle *toggle, Content::Type mask, + bool atEnd) : + Iterator (toggle, mask, atEnd) +{ + content.type = atEnd ? Content::END : Content::START; +} + +lout::object::Object *Toggle::ToggleIterator::clone () +{ + ToggleIterator *ti = + new ToggleIterator ((Toggle*)getWidget(), getMask(), false); + ti->content = content; + return ti; +} + +int Toggle::ToggleIterator::index () +{ + switch (content.type) { + case Content::START: + return 0; + case Content::WIDGET_IN_FLOW: + return content.widget == ((Toggle*)getWidget())->small ? 1 : 2; + case Content::END: + return 3; + default: + assertNotReached (); + return 0; + } +} + +int Toggle::ToggleIterator::compareTo (lout::object::Comparable *other) +{ + return index () - ((ToggleIterator*)other)->index (); +} + +bool Toggle::ToggleIterator::next () +{ + Toggle *toggle = (Toggle*)getWidget(); + + if (content.type == Content::END) + return false; + + // toggles only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::END; + return false; + } + + if (content.type == Content::START) { + if (toggle->small != NULL) { + content.type = Content::WIDGET_IN_FLOW; + content.widget = toggle->small; + return true; + } else if (toggle->large != NULL) { + content.type = Content::WIDGET_IN_FLOW; + content.widget = toggle->large; + return true; + } else { + content.type = Content::END; + return false; + } + } else /* if (content.type == Content::WIDGET) */ { + if (content.widget == toggle->small && toggle->large != NULL) { + content.widget = toggle->large; + return true; + } else { + content.type = Content::END; + return false; + } + } +} + +bool Toggle::ToggleIterator::prev () +{ + Toggle *toggle = (Toggle*)getWidget(); + + if (content.type == Content::START) + return false; + + // toggles only contain widgets: + if ((getMask() & Content::WIDGET_IN_FLOW) == 0) { + content.type = Content::START; + return false; + } + + if (content.type == Content::END) { + if (toggle->large != NULL) { + content.type = Content::WIDGET_IN_FLOW; + content.widget = toggle->large; + return true; + } else if (toggle->small != NULL) { + content.type = Content::WIDGET_IN_FLOW; + content.widget = toggle->small; + return true; + } else { + content.type = Content::START; + return false; + } + } else /* if (content.type == Content::WIDGET) */ { + if (content.widget == toggle->large && toggle->small != NULL) { + content.widget = toggle->small; + return true; + } else { + content.type = Content::START; + return false; + } + } +} + +void Toggle::ToggleIterator::highlight (int start, int end, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Toggle::ToggleIterator::unhighlight (int direction, HighlightLayer layer) +{ + /** todo Needs this an implementation? */ +} + +void Toggle::ToggleIterator::getAllocation (int start, int end, + Allocation *allocation) +{ + /** \bug Not implemented. */ +} + +// ---------------------------------------------------------------------- + +Toggle::Toggle (bool showLarge) +{ + DBG_OBJ_CREATE ("rtfl::dw::Toggle"); + registerName ("rtfl::dw::Toggle", &CLASS_ID); + + this->showLarge = showLarge; + small = large = NULL; + buttonDown = false; +} + +Toggle::~Toggle () +{ + if (small) + delete small; + if (large) + delete large; + + DBG_OBJ_DELETE (); +} + +void Toggle::sizeRequestImplImpl (Requisition *requisition) +{ + Widget *child = showLarge ? large : small; + Requisition childReq; + if (child) + child->sizeRequest (&childReq); + else + childReq.width = childReq.ascent = childReq.descent = 0; + + int buttonSize = calcButtonSize (); + requisition->width = + buttonSize + childReq.width + getStyle()->boxDiffWidth (); + requisition->ascent = + max (buttonSize, childReq.ascent) + getStyle()->boxOffsetY (); + requisition->descent = childReq.descent + getStyle()->boxRestHeight (); +} + + +void Toggle::getExtremesImplImpl (Extremes *extremes) +{ + Widget *child = showLarge ? large : small; + Extremes childExtr; + if (child) + child->getExtremes (&childExtr); + else + childExtr.minWidth = childExtr.maxWidth = 0; + + int buttonSize = calcButtonSize (); + extremes->minWidth = + buttonSize + childExtr.minWidth + getStyle()->boxDiffWidth (); + extremes->maxWidth = + buttonSize + childExtr.maxWidth + getStyle()->boxDiffWidth (); +} + + +void Toggle::sizeAllocateImpl (Allocation *allocation) +{ + Widget *visChild = showLarge ? large : small; + Widget *invisChild = showLarge ? small : large; + Allocation childAlloc; + int buttonSize = calcButtonSize (); + + if (visChild) { + childAlloc.x = allocation->x + buttonSize + getStyle()->boxOffsetX (); + childAlloc.y = allocation->y + getStyle()->boxOffsetY (); + childAlloc.width = + allocation->width - buttonSize - getStyle()->boxDiffWidth (); + childAlloc.ascent = allocation->ascent - getStyle()->boxOffsetY (); + childAlloc.descent = allocation->descent - getStyle()->boxRestHeight (); + visChild->sizeAllocate (&childAlloc); + } + + //printf ("toggle: visible child at (%d, %d), %d, (%d x %d)\n", + // childAlloc.x, childAlloc.y, childAlloc.width, + // childAlloc.ascent, childAlloc.descent); + + if (invisChild) { + // Zero size is used to hide a widget. + childAlloc.width = childAlloc.ascent = childAlloc.descent = 0; + invisChild->sizeAllocate (&childAlloc); + } +} + +void Toggle::drawImpl (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); + Widget *child = showLarge ? large : small; + Rectangle childArea; + if (child && child->intersects (area, &childArea)) + child->draw (view, &childArea); + + int buttonSize = calcButtonSize (); + int x0 = getAllocation()->x + getStyle()->boxOffsetX (); + int y0 = getAllocation()->y + getAllocation()->ascent - buttonSize; + + switch (buttonStyle) { + case PLUS_MINUS: + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + 1, y0 + buttonSize / 2, + x0 + buttonSize - 2, y0 + buttonSize / 2); + if (!showLarge) + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + buttonSize / 2, y0 + 1, + x0 + buttonSize / 2, y0 + buttonSize - 2); + break; + + case TRIANGLE: + if (showLarge) { + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + 1, y0 + 1, x0 + buttonSize - 2, y0 + 1); + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + 1, y0 + 1, + x0 + buttonSize / 2, y0 + buttonSize - 2); + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + buttonSize - 2, y0 + 1, + x0 + buttonSize / 2, y0 + buttonSize - 2); + } else { + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + 1, y0 + 1, x0 + 1, y0 + buttonSize - 2); + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + 1, y0 + 1, + x0 + buttonSize - 2, y0 + buttonSize / 2); + view->drawLine (getStyle()->color, Color::SHADING_NORMAL, + x0 + 1, y0 + buttonSize - 2, + x0 + buttonSize - 2, y0 + buttonSize / 2); + } + break; + } + + DBG_OBJ_LEAVE (); +} + +::dw::core::Iterator *Toggle::iterator (Content::Type mask, bool atEnd) +{ + return new ToggleIterator (this, mask, atEnd); +} + +bool Toggle::insideButton (MousePositionEvent *event) +{ + int buttonSize = calcButtonSize (); + return + event->xWidget >= getStyle()->boxOffsetX () && + event->xWidget < getStyle()->boxOffsetX () + buttonSize && + event->yWidget >= getAllocation()->ascent - buttonSize && + event->yWidget < getAllocation()->ascent; +} + +bool Toggle::buttonPressImpl (EventButton *event) +{ + if (event->button == 1 && insideButton (event)) { + buttonDown = true; + return true; + } else + return false; +} + +bool Toggle::buttonReleaseImpl (EventButton *event) +{ + if (event->button == 1 && insideButton (event)) { + if (buttonDown) { + showLarge = !showLarge; + queueResize (0, true); + } + return true; + } else + return false; +} + +bool Toggle::motionNotifyImpl (::dw::core::EventMotion *event) +{ + if ((event->state & BUTTON1_MASK) && !insideButton (event)) + buttonDown = false; + return false; +} + +void Toggle::leaveNotifyImpl (EventCrossing *event) +{ + buttonDown = false; +} + +void Toggle::removeChild (Widget *child) +{ + if (child == small) + small = NULL; + else if (child == large) + large = NULL; + else + assertNotReached (); +} + +void Toggle::setSmall (Widget *widget) +{ + if (small) + delete small; + + small = widget; + if (small) + small->setParent (this); + + if (!showLarge) + queueResize (0, true); +} + +void Toggle::setLarge (Widget *widget) +{ + if (large) + delete large; + + large = widget; + if (large) + large->setParent (this); + + if (showLarge) + queueResize (0, true); +} + +void Toggle::toggle (bool showLarge) +{ + this->showLarge = showLarge; + queueResize (0, true); +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/toggle.hh b/dwr/toggle.hh new file mode 100644 index 0000000..8962ade --- /dev/null +++ b/dwr/toggle.hh @@ -0,0 +1,77 @@ +#ifndef __DWR_TOGGLE_HH__ +#define __DWR_TOGGLE_HH__ + +#include "hideable.hh" + +namespace rtfl { + +namespace dw { + +class Toggle: public Hideable +{ +private: + class ToggleIterator: public ::dw::core::Iterator + { + private: + int index (); + + public: + ToggleIterator (Toggle *toggle, ::dw::core::Content::Type mask, + bool atEnd); + + lout::object::Object *clone (); + int compareTo (lout::object::Comparable *other); + + bool next (); + bool prev (); + void highlight (int start, int end, ::dw::core::HighlightLayer layer); + void unhighlight (int direction, ::dw::core::HighlightLayer layer); + void getAllocation (int start, int end, + ::dw::core::Allocation *allocation); + }; + + static enum ButtonStyle { PLUS_MINUS, TRIANGLE } buttonStyle; + + bool showLarge, buttonDown; + Widget *small, *large; + + bool insideButton (::dw::core::MousePositionEvent *event); + inline int calcButtonSize () + { // Always return an odd number. + int s = getStyle()->font->ascent; return (s % 2) ? s : s - 1; } + +protected: + void sizeRequestImplImpl (::dw::core::Requisition *requisition); + void getExtremesImplImpl (::dw::core::Extremes *extremes); + void sizeAllocateImpl (::dw::core::Allocation *allocation); + + void drawImpl (::dw::core::View *view, ::dw::core::Rectangle *area); + + bool buttonPressImpl (::dw::core::EventButton *event); + bool buttonReleaseImpl (::dw::core::EventButton *event); + bool motionNotifyImpl (::dw::core::EventMotion *event); + void leaveNotifyImpl (::dw::core::EventCrossing *event); + +public: + static int CLASS_ID; + + Toggle (bool showLarge); + ~Toggle (); + + ::dw::core::Iterator *iterator (::dw::core::Content::Type mask, bool atEnd); + void removeChild (Widget *child); + + inline ::dw::core::Widget *getSmall () { return small; } + inline ::dw::core::Widget *getLarge () { return large; } + void setSmall (::dw::core::Widget *widget); + void setLarge (::dw::core::Widget *widget); + + inline bool isLargeShown () { return showLarge; } + void toggle (bool showLarge); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_TOGGLE_HH__ diff --git a/dwr/tools.cc b/dwr/tools.cc new file mode 100644 index 0000000..d45ebd3 --- /dev/null +++ b/dwr/tools.cc @@ -0,0 +1,243 @@ +/* + * 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/>. + * + * ---------------------------------------------------------------------- + * + * A part of the code was written by Keith Vertanen and has been + * released to the public domain; see below. + */ + +#include "tools.hh" + +#include <math.h> + +using namespace dw::core; +using namespace dw::core::style; + +namespace rtfl { + +namespace dw { + +namespace tools { + +// Used for b-splines, see below. +struct point { + double x; + double y; + double z; +}; + +static void bspline (int n, int t, point *control, point *output, + int num_output); + +void drawArrowHead (View *view, Style *style, + int x1, int y1, int x2, int y2, int aheadlen) +{ + if (x1 != x2 || y1 != y2) { + // TODO: Use faster algorithm avoding floating point numbers. Also, + // regard that using integers could cause overflow errors. + + int l = sqrt ((double)(x2 - x1) * (double)(x2 - x1) + + (double)(y2 - y1) * (double)(y2 - y1)); + int x3 = (aheadlen * x1 + (l - aheadlen) * x2) / l; + int y3 = (aheadlen * y1 + (l - aheadlen) * y2) / l; + + int x4 = x3 - (y2 - y3) / 2; + int y4 = y3 + (x2 - x3) / 2; + view->drawLine (style->color, Color::SHADING_NORMAL, x2, y2, x4, y4); + + int x5 = x3 + (y2 - y3) / 2; + int y5 = y3 - (x2 - x3) / 2; + view->drawLine (style->color, Color::SHADING_NORMAL, x2, y2, x5, y5); + } +} + +void drawBSpline (::dw::core::View *view, ::dw::core::style::Style *style, + int degree, int numPoints, int *x, int *y) +{ + point *in = new point[numPoints]; + for (int i = 0; i < numPoints; i++) { + in[i].x = x[i]; + in[i].y = y[i]; + in[i].z = 0; + } + + int numOut = 5 * numPoints; + point *out = new point[numOut]; + + bspline(numPoints - 1, degree, in, out, numOut); + + for (int i = 0; i < numOut - 1; i++) + view->drawLine (style->color, ::dw::core::style::Color::SHADING_NORMAL, + out[i].x, out[i].y, + out[i + 1].x, out[i + 1].y); + + delete[] in; + delete[] out; +} + +/* ---------------------------------------------------------------------- + The following code was copied from + <ftp://ftp.grnet.gr/pub/lang/algorithms/c++/bspline.cpp>. + + It should be modified so that it is better adapted to our needs + (no z coordinate, no unnecessary conversion between int and + double, etc.) + ---------------------------------------------------------------------- */ + +/********************************************************************* + + Simple b-spline curve algorithm + + Copyright 1994 by Keith Vertanen (vertankd@cda.mrs.umn.edu) + + Released to the public domain (your mileage may vary) + +**********************************************************************/ + +static void compute_intervals(int *u, int n, int t); +static double blend(int k, int t, int *u, double v); +static void compute_point(int *u, int n, int t, double v, point *control, + point *output); + +void bspline(int n, int t, point *control, point *output, int num_output) + +/********************************************************************* + +Parameters: + n - the number of control points minus 1 + t - the degree of the polynomial plus 1 + control - control point array made up of point stucture + output - array in which the calculate spline points are to be put + num_output - how many points on the spline are to be calculated + +Pre-conditions: + n+2>t (no curve results if n+2<=t) + control array contains the number of points specified by n + output array is the proper size to hold num_output point structures + + +**********************************************************************/ + +{ + int *u; + double increment,interval; + point calcxyz; + int output_index; + + u=new int[n+t+1]; + compute_intervals(u, n, t); + + increment=(double) (n-t+2)/(num_output-1); // how much parameter goes up each time + interval=0; + + for (output_index=0; output_index<num_output-1; output_index++) + { + compute_point(u, n, t, interval, control, &calcxyz); + output[output_index].x = calcxyz.x; + output[output_index].y = calcxyz.y; + output[output_index].z = calcxyz.z; + interval=interval+increment; // increment our parameter + } + output[num_output-1].x=control[n].x; // put in the last point + output[num_output-1].y=control[n].y; + output[num_output-1].z=control[n].z; + + delete u; +} + +double blend(int k, int t, int *u, double v) // calculate the blending value +{ + double value; + + if (t==1) // base case for the recursion + { + if ((u[k]<=v) && (v<u[k+1])) + value=1; + else + value=0; + } + else + { + if ((u[k+t-1]==u[k]) && (u[k+t]==u[k+1])) // check for divide by zero + value = 0; + else + if (u[k+t-1]==u[k]) // if a term's denominator is zero,use just the other + value = (u[k+t] - v) / (u[k+t] - u[k+1]) * blend(k+1, t-1, u, v); + else + if (u[k+t]==u[k+1]) + value = (v - u[k]) / (u[k+t-1] - u[k]) * blend(k, t-1, u, v); + else + value = (v - u[k]) / (u[k+t-1] - u[k]) * blend(k, t-1, u, v) + + (u[k+t] - v) / (u[k+t] - u[k+1]) * blend(k+1, t-1, u, v); + } + return value; +} + +void compute_intervals(int *u, int n, int t) // figure out the knots +{ + int j; + + for (j=0; j<=n+t; j++) + { + if (j<t) + u[j]=0; + else + if ((t<=j) && (j<=n)) + u[j]=j-t+1; + else + if (j>n) + u[j]=n-t+2; // if n-t=-2 then we're screwed, everything goes to 0 + } +} + +void compute_point(int *u, int n, int t, double v, point *control, + point *output) +{ + int k; + double temp; + + // initialize the variables that will hold our outputted point + output->x=0; + output->y=0; + output->z=0; + + for (k=0; k<=n; k++) + { + temp = blend(k,t,u,v); // same blend is used for each dimension coordinate + output->x = output->x + (control[k]).x * temp; + output->y = output->y + (control[k]).y * temp; + output->z = output->z + (control[k]).z * temp; + } +} + +} // namespace tools + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/tools.hh b/dwr/tools.hh new file mode 100644 index 0000000..8e7bb2b --- /dev/null +++ b/dwr/tools.hh @@ -0,0 +1,24 @@ +#ifndef __DWR_TOOLS_HH__ +#define __DWR_TOOLS_HH__ + +#include "dw/core.hh" + +namespace rtfl { + +namespace dw { + +namespace tools { + +void drawArrowHead (::dw::core::View *view, ::dw::core::style::Style *style, + int x1, int y1, int x2, int y2, int aheadlen); + +void drawBSpline (::dw::core::View *view, ::dw::core::style::Style *style, + int degree, int numPoints, int *x, int *y); + +} // namespace tools + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_TOOLS_HH__ diff --git a/dwr/vbox.cc b/dwr/vbox.cc new file mode 100644 index 0000000..6649488 --- /dev/null +++ b/dwr/vbox.cc @@ -0,0 +1,164 @@ +/* + * 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 "vbox.hh" +#include "lout/misc.hh" + +using namespace dw::core; +using namespace lout::misc; + +namespace rtfl { + +namespace dw { + +int VBox::CLASS_ID = -1; + +VBox::VBox (bool stretchChildren): Box (stretchChildren) +{ + DBG_OBJ_CREATE ("rtfl::dw::VBox"); + registerName ("rtfl::dw::VBox", &CLASS_ID); +} + +VBox::~VBox () +{ + DBG_OBJ_DELETE (); +} + +int VBox::findFirstVisibleChild () +{ + for (int i = 0; i < children->size (); i++) { + Requisition childReq; + children->get(i)->sizeRequest (&childReq); + if (childReq.ascent > 0 || childReq.descent > 0) + return i; + } + + return 0; +} + +void VBox::sizeRequestImplImpl (Requisition *requisition) +{ + actualSizeRequestImplImpl (requisition, findFirstVisibleChild ()); +} + +void VBox::sizeAllocateImpl (Allocation *allocation) +{ + // TODO "stretchChildren" is not regarded. + + Allocation allocWOBorders; + allocWOBorders.x = allocation->x + getStyle()->boxOffsetX (); + allocWOBorders.y = allocation->y + getStyle()->boxOffsetY (); + allocWOBorders.width = allocation->width - getStyle()->boxDiffWidth (); + allocWOBorders.ascent = allocation->ascent - getStyle()->boxOffsetY (); + allocWOBorders.descent = allocation->descent - getStyle()->boxRestHeight (); + + // Unlike HBox, the ascent of the first visible widget (set as + // allocation ascent) is treated differently; the distribution is + // only done for the rest. Also, the difference is distributed to + // ascent (except first widget) and descent independently. + + int sumReqHeight = 0, firstVisibleChild = findFirstVisibleChild (); + for (int i = firstVisibleChild; i < children->size (); i++) { + Requisition childReq; + children->get(i)->sizeRequest (&childReq); + if (i > firstVisibleChild) + sumReqHeight += childReq.ascent; + sumReqHeight += childReq.descent; + } + + //bool stretch = stretchChildren && sumReqHeight < allocWOBorders.descent; + + // Cf. doc/rounding-errors.doc, with: x[i] = child requisition, + // y[i] = child allocation, a = total allocation, b = sumReqHeight. + int cumChildReqHeight = 0, cumChildAllocHeight = 0; + for (int i = 0; i < children->size (); i++) { + Widget *child = children->get (i); + + Requisition childReq; + child->sizeRequest (&childReq); + + Allocation childAlloc; + childAlloc.x = allocWOBorders.x; + childAlloc.width = allocWOBorders.width; + + if (i == firstVisibleChild) { + childAlloc.y = allocWOBorders.y; + childAlloc.ascent = allocWOBorders.ascent; + } else { + childAlloc.y = + allocWOBorders.y + allocWOBorders.ascent + cumChildAllocHeight; + cumChildReqHeight += childReq.ascent; + childAlloc.ascent = sumReqHeight > 0 ? + safeATimesBDividedByC (cumChildReqHeight, allocWOBorders.descent, + sumReqHeight) - cumChildAllocHeight : + 0; + cumChildAllocHeight += childAlloc.ascent; + } + + cumChildReqHeight += childReq.descent; + childAlloc.descent = sumReqHeight > 0 ? + safeATimesBDividedByC (cumChildReqHeight, allocWOBorders.descent, + sumReqHeight) - cumChildAllocHeight : + 0; + cumChildAllocHeight += childAlloc.descent; + + child->sizeAllocate (&childAlloc); + + //printf ("vbox: %dth child at (%d, %d), %d, (%d x %d)\n", + // i, childAlloc.x, childAlloc.y, childAlloc.width, + // childAlloc.ascent, childAlloc.descent); + + } +} + +void VBox::accumulateSize (int index, int size, Requisition *totalReq, + Requisition *childReq, int data1) +{ + int firstVisibleChild = data1; + + totalReq->width = max (totalReq->width, childReq->width); + if (index == firstVisibleChild) { + // Resulting baseline is the baseline of the first child. + totalReq->ascent = childReq->ascent; + totalReq->descent = childReq->descent; + } else + totalReq->descent += (childReq->ascent + childReq->descent); +} + + +void VBox::accumulateExtremes (int index, int size, Extremes *totalExtr, + Extremes *childExtr) +{ + totalExtr->minWidth = max (totalExtr->minWidth, childExtr->minWidth); + totalExtr->maxWidth = max (totalExtr->maxWidth, childExtr->maxWidth); +} + +} // namespace rtfl + +} // namespace dw diff --git a/dwr/vbox.hh b/dwr/vbox.hh new file mode 100644 index 0000000..b586217 --- /dev/null +++ b/dwr/vbox.hh @@ -0,0 +1,35 @@ +#ifndef __DWR_VBOX_HH__ +#define __DWR_VBOX_HH__ + +#include "box.hh" + +namespace rtfl { + +namespace dw { + +class VBox: public Box +{ +private: + int findFirstVisibleChild (); + +protected: + void sizeRequestImplImpl (::dw::core::Requisition *requisition); + void sizeAllocateImpl (::dw::core::Allocation *allocation); + void accumulateSize (int index, int size, ::dw::core::Requisition *totalReq, + ::dw::core::Requisition *childReq, int data1); + void accumulateExtremes (int index, int size, + ::dw::core::Extremes *totalExtr, + ::dw::core::Extremes *childExtr); + +public: + static int CLASS_ID; + + VBox (bool stretchChildren); + ~VBox (); +}; + +} // namespace rtfl + +} // namespace dw + +#endif // __DWR_VBOX_HH__ |