summaryrefslogtreecommitdiff
path: root/dw/layout.cc
diff options
context:
space:
mode:
authorRodrigo Arias Mallo <rodarima@gmail.com>2024-12-10 22:30:12 +0100
committerRodrigo Arias Mallo <rodarima@gmail.com>2024-12-10 22:30:12 +0100
commit429d5f88b94ff28416cbfc6420b6389fa284df97 (patch)
treefb6fdaf7731de1ef396f98b748c56f3149801c84 /dw/layout.cc
Import RTFL 0.1.1v0.1.1
Diffstat (limited to 'dw/layout.cc')
-rw-r--r--dw/layout.cc1445
1 files changed, 1445 insertions, 0 deletions
diff --git a/dw/layout.cc b/dw/layout.cc
new file mode 100644
index 0000000..96de6c2
--- /dev/null
+++ b/dw/layout.cc
@@ -0,0 +1,1445 @@
+/*
+ * RTFL (originally part of dillo)
+ *
+ * Copyright 2005-2007 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 "core.hh"
+
+#include "../lout/msg.h"
+#include "../lout/debug.hh"
+#include "../lout/misc.hh"
+
+using namespace lout;
+using namespace lout::container;
+using namespace lout::object;
+
+namespace dw {
+namespace core {
+
+bool Layout::LayoutImgRenderer::readyToDraw ()
+{
+ return true;
+}
+
+void Layout::LayoutImgRenderer::getBgArea (int *x, int *y, int *width,
+ int *height)
+{
+ // TODO Actually not padding area, but visible area?
+ getRefArea (x, y, width, height);
+}
+
+void Layout::LayoutImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef,
+ int *heightRef)
+{
+ *xRef = 0;
+ *yRef = 0;
+ *widthRef = misc::max (layout->viewportWidth
+ - (layout->canvasHeightGreater ?
+ layout->vScrollbarThickness : 0),
+ layout->canvasWidth);
+ *heightRef = misc::max (layout->viewportHeight
+ - layout->hScrollbarThickness,
+ layout->canvasAscent + layout->canvasDescent);
+}
+
+style::StyleImage *Layout::LayoutImgRenderer::getBackgroundImage ()
+{
+ return layout->bgImage;
+}
+
+style::BackgroundRepeat Layout::LayoutImgRenderer::getBackgroundRepeat ()
+{
+ return layout->bgRepeat;
+}
+
+style::BackgroundAttachment
+ Layout::LayoutImgRenderer::getBackgroundAttachment ()
+{
+ return layout->bgAttachment;
+}
+
+style::Length Layout::LayoutImgRenderer::getBackgroundPositionX ()
+{
+ return layout->bgPositionX;
+}
+
+style::Length Layout::LayoutImgRenderer::getBackgroundPositionY ()
+{
+ return layout->bgPositionY;
+}
+
+void Layout::LayoutImgRenderer::draw (int x, int y, int width, int height)
+{
+ layout->queueDraw (x, y, width, height);
+}
+
+// ----------------------------------------------------------------------
+
+void Layout::Receiver::resizeQueued (bool extremesChanged)
+{
+}
+
+void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent)
+{
+}
+
+// ----------------------------------------------------------------------
+
+bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo, int argc,
+ lout::object::Object **argv)
+{
+ Receiver *layoutReceiver = (Receiver*)receiver;
+
+ switch (signalNo) {
+ case CANVAS_SIZE_CHANGED:
+ layoutReceiver->canvasSizeChanged (((Integer*)argv[0])->getValue (),
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue ());
+ break;
+
+ case RESIZE_QUEUED:
+ layoutReceiver->resizeQueued (((Boolean*)argv[0])->getValue ());
+ break;
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ return false;
+}
+
+void Layout::Emitter::emitResizeQueued (bool extremesChanged)
+{
+ Boolean ec (extremesChanged);
+ Object *argv[1] = { &ec };
+ emitVoid (RESIZE_QUEUED, 1, argv);
+}
+
+void Layout::Emitter::emitCanvasSizeChanged (int width,
+ int ascent, int descent)
+{
+ Integer w (width), a (ascent), d (descent);
+ Object *argv[3] = { &w, &a, &d };
+ emitVoid (CANVAS_SIZE_CHANGED, 3, argv);
+}
+
+// ----------------------------------------------------------------------
+
+bool Layout::LinkReceiver::enter (Widget *widget, int link, int img,
+ int x, int y)
+{
+ return false;
+}
+
+bool Layout::LinkReceiver::press (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+bool Layout::LinkReceiver::release (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+bool Layout::LinkReceiver::click (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+// ----------------------------------------------------------------------
+
+bool Layout::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo, int argc,
+ lout::object::Object **argv)
+{
+ LinkReceiver *linkReceiver = (LinkReceiver*)receiver;
+
+ switch (signalNo) {
+ case ENTER:
+ return linkReceiver->enter ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue ());
+
+ case PRESS:
+ return linkReceiver->press ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue (),
+ (EventButton*)argv[5]);
+
+ case RELEASE:
+ return linkReceiver->release ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue (),
+ (EventButton*)argv[5]);
+
+ case CLICK:
+ return linkReceiver->click ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue (),
+ (EventButton*)argv[5]);
+
+ default:
+ misc::assertNotReached ();
+ }
+ return false;
+}
+
+bool Layout::LinkEmitter::emitEnter (Widget *widget, int link, int img,
+ int x, int y)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[5] = { widget, &ilink, &iimg, &ix, &iy };
+ return emitBool (ENTER, 5, argv);
+}
+
+bool Layout::LinkEmitter::emitPress (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
+ return emitBool (PRESS, 6, argv);
+}
+
+bool Layout::LinkEmitter::emitRelease (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
+ return emitBool (RELEASE, 6, argv);
+}
+
+bool Layout::LinkEmitter::emitClick (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
+ return emitBool (CLICK, 6, argv);
+}
+
+// ---------------------------------------------------------------------
+
+Layout::Anchor::~Anchor ()
+{
+ free(name);
+}
+
+// ---------------------------------------------------------------------
+
+Layout::ScrollTarget::~ScrollTarget ()
+{
+}
+
+Layout::ScrollTargetBase::ScrollTargetBase (HPosition hPos, VPosition vPos)
+{
+ this->hPos = hPos;
+ this->vPos = vPos;
+}
+
+HPosition Layout::ScrollTargetBase::getHPos ()
+{
+ return hPos;
+}
+
+VPosition Layout::ScrollTargetBase::getVPos ()
+{
+ return vPos;
+}
+
+Layout::ScrollTargetFixed::ScrollTargetFixed (HPosition hPos, VPosition vPos,
+ int x, int y, int width, int height) :
+ ScrollTargetBase (hPos, vPos)
+{
+ this->x = x;
+ this->y = y;
+ this->width = width;
+ this->height = height;
+}
+
+int Layout::ScrollTargetFixed::getX ()
+{
+ return x;
+}
+
+int Layout::ScrollTargetFixed::getY ()
+{
+ return y;
+}
+
+int Layout::ScrollTargetFixed::getWidth ()
+{
+ return width;
+}
+
+int Layout::ScrollTargetFixed::getHeight ()
+{
+ return height;
+}
+
+Layout::ScrollTargetWidget::ScrollTargetWidget (HPosition hPos, VPosition vPos,
+ Widget *widget) :
+ ScrollTargetBase (hPos, vPos)
+{
+ this->widget = widget;
+}
+
+int Layout::ScrollTargetWidget::getX ()
+{
+ return widget->allocation.x;
+}
+
+int Layout::ScrollTargetWidget::getY ()
+{
+ return widget->allocation.y;
+}
+
+int Layout::ScrollTargetWidget::getWidth ()
+{
+ return widget->allocation.width;
+}
+
+int Layout::ScrollTargetWidget::getHeight ()
+{
+ return widget->allocation.ascent + widget->allocation.descent;
+}
+
+// ----------------------------------------------------------------------
+
+Layout::Layout (Platform *platform)
+{
+ this->platform = platform;
+ view = NULL;
+ topLevel = NULL;
+ widgetAtPoint = NULL;
+
+ queueQueueResizeList = new typed::Stack<QueueResizeItem> (true);
+ queueResizeList = new typed::Vector<Widget> (4, false);
+
+ DBG_OBJ_CREATE ("dw::core::Layout");
+
+ bgColor = NULL;
+ bgImage = NULL;
+ cursor = style::CURSOR_DEFAULT;
+
+ canvasWidth = canvasAscent = canvasDescent = 0;
+
+ usesViewport = false;
+ drawAfterScrollReq = false;
+ scrollX = scrollY = 0;
+ viewportWidth = viewportHeight = 0;
+ hScrollbarThickness = vScrollbarThickness = 0;
+
+ DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth);
+ DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight);
+ DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness);
+ DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness);
+
+ requestedAnchor = NULL;
+ scrollTarget = NULL;
+ scrollIdleId = -1;
+ scrollIdleNotInterrupted = false;
+
+ anchorsTable =
+ new container::typed::HashTable <object::String, Anchor> (true, true);
+
+ resizeIdleId = -1;
+
+ textZone = new misc::ZoneAllocator (16 * 1024);
+
+ DBG_OBJ_ASSOC_CHILD (&findtextState);
+ DBG_OBJ_ASSOC_CHILD (&selectionState);
+
+ platform->setLayout (this);
+
+ selectionState.setLayout(this);
+
+ queueResizeCounter = sizeAllocateCounter = sizeRequestCounter =
+ getExtremesCounter = 0;
+
+ layoutImgRenderer = NULL;
+
+ resizeIdleCounter = queueResizeCounter = sizeAllocateCounter
+ = sizeRequestCounter = getExtremesCounter = 0;
+}
+
+Layout::~Layout ()
+{
+ widgetAtPoint = NULL;
+
+ if (layoutImgRenderer) {
+ if (bgImage)
+ bgImage->removeExternalImgRenderer (layoutImgRenderer);
+ delete layoutImgRenderer;
+ }
+
+ if (scrollIdleId != -1)
+ platform->removeIdle (scrollIdleId);
+ if (resizeIdleId != -1)
+ platform->removeIdle (resizeIdleId);
+ if (bgColor)
+ bgColor->unref ();
+ if (bgImage)
+ bgImage->unref ();
+ if (topLevel) {
+ detachWidget (topLevel);
+ Widget *w = topLevel;
+ topLevel = NULL;
+ delete w;
+ }
+
+ delete queueQueueResizeList;
+ delete queueResizeList;
+ delete platform;
+ delete view;
+ delete anchorsTable;
+ delete textZone;
+
+ if (requestedAnchor)
+ free (requestedAnchor);
+ if (scrollTarget)
+ delete scrollTarget;
+
+ DBG_OBJ_DELETE ();
+}
+
+void Layout::detachWidget (Widget *widget)
+{
+ // Called form ~Layout. Sometimes, the widgets (not only the toplevel widget)
+ // do some stuff after the layout has been deleted, so *all* widgets have to
+ // be detached, and check "layout != NULL" at relevant points.
+
+ // Could be replaced by a virtual method in Widget, like getWidgetAtPoint,
+ // if performace were really a problem.
+
+ widget->layout = NULL;
+ Iterator *it =
+ widget->iterator ((Content::Type)
+ (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT),
+ false);
+ while (it->next ())
+ detachWidget (it->getContent()->widget);
+
+ it->unref ();
+}
+
+void Layout::addWidget (Widget *widget)
+{
+ if (topLevel) {
+ MSG_WARN("widget already set\n");
+ return;
+ }
+
+ topLevel = widget;
+ widget->layout = this;
+ widget->container = NULL;
+ DBG_OBJ_SET_PTR_O (widget, "container", widget->container);
+
+ queueResizeList->clear ();
+ widget->notifySetAsTopLevel ();
+
+ findtextState.setWidget (widget);
+
+ canvasHeightGreater = false;
+ DBG_OBJ_SET_SYM ("canvasHeightGreater",
+ canvasHeightGreater ? "true" : "false");
+
+ // Do not directly call Layout::queueResize(), but
+ // Widget::queueResize(), so that all flags are set properly,
+ // queueResizeList is filled, etc.
+ topLevel->queueResize (-1, false);
+}
+
+void Layout::removeWidget ()
+{
+ /**
+ * \bug Some more attributes must be reset here.
+ */
+ topLevel = NULL;
+ queueResizeList->clear ();
+ widgetAtPoint = NULL;
+ canvasWidth = canvasAscent = canvasDescent = 0;
+ scrollX = scrollY = 0;
+
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+ if (view->usesViewport ())
+ view->setViewportSize (viewportWidth, viewportHeight, 0, 0);
+ view->queueDrawTotal ();
+
+ setAnchor (NULL);
+ updateAnchor ();
+
+ emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent);
+
+ findtextState.setWidget (NULL);
+ selectionState.reset ();
+
+ updateCursor ();
+}
+
+void Layout::setWidget (Widget *widget)
+{
+ DBG_OBJ_ASSOC_CHILD (widget);
+
+ widgetAtPoint = NULL;
+ if (topLevel) {
+ Widget *w = topLevel;
+ topLevel = NULL;
+ delete w;
+ }
+ textZone->zoneFree ();
+ addWidget (widget);
+
+ updateCursor ();
+}
+
+/**
+ * \brief Attach a view to the layout.
+ *
+ * It will become a child of the layout,
+ * and so it will be destroyed, when the layout will be destroyed.
+ */
+void Layout::attachView (View *view)
+{
+ if (this->view)
+ MSG_ERR("attachView: Multiple views for layout!\n");
+
+ DBG_OBJ_ASSOC_CHILD (view);
+
+ this->view = view;
+ platform->attachView (view);
+
+ /*
+ * The layout of the view is set later, first, we "project" the current
+ * state of the layout into the new view. A view must handle this without
+ * a layout. See also at the end of this function.
+ */
+ if (bgColor)
+ view->setBgColor (bgColor);
+ view->setCursor (cursor);
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+
+ if (view->usesViewport ()) {
+ if (usesViewport) {
+ view->scrollTo (scrollX, scrollY);
+ view->setViewportSize (viewportWidth, viewportHeight,
+ hScrollbarThickness, vScrollbarThickness);
+ hScrollbarThickness = misc::max (hScrollbarThickness,
+ view->getHScrollbarThickness ());
+ vScrollbarThickness = misc::max (vScrollbarThickness,
+ view->getVScrollbarThickness ());
+ }
+ else {
+ usesViewport = true;
+ scrollX = scrollY = 0;
+ viewportWidth = viewportHeight = 100; // random values
+ hScrollbarThickness = view->getHScrollbarThickness ();
+ vScrollbarThickness = view->getVScrollbarThickness ();
+ }
+
+ DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth);
+ DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight);
+ DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness);
+ DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness);
+ }
+
+ /*
+ * This is the last call within this function, so that it is safe for
+ * the implementation of dw::core::View::setLayout, to call methods
+ * of dw::core::Layout.
+ */
+ view->setLayout (this);
+}
+
+void Layout::detachView (View *view)
+{
+ if (this->view != view)
+ MSG_ERR("detachView: this->view: %p view %p\n", this->view, view);
+
+ view->setLayout (NULL);
+ platform->detachView (view);
+ this->view = NULL;
+ /**
+ * \todo Actually, viewportMarkerWidthDiff and
+ * viewportMarkerHeightDiff have to be recalculated here, since the
+ * effective (i.e. maximal) values may change, after the view has been
+ * detached. Same applies to the usage of viewports.
+ */
+}
+
+void Layout::scroll(ScrollCommand cmd)
+{
+ if (view->usesViewport ())
+ view->scroll(cmd);
+}
+
+/**
+ * \brief Scrolls all viewports, so that the region [x, y, width, height]
+ * is seen, according to hpos and vpos.
+ */
+void Layout::scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height)
+{
+ scrollTo0 (new ScrollTargetFixed (hpos, vpos, x, y, width, height), true);
+}
+
+void Layout::scrollToWidget (HPosition hpos, VPosition vpos, Widget *widget)
+{
+ scrollTo0 (new ScrollTargetWidget (hpos, vpos, widget), true);
+}
+
+void Layout::scrollTo0 (ScrollTarget *scrollTarget, bool scrollingInterrupted)
+{
+ if (usesViewport) {
+ _MSG("scrollTo (%d, %d, %s)\n",
+ x, y, scrollingInterrupted ? "true" : "false");
+
+ if (this->scrollTarget)
+ delete this->scrollTarget;
+
+ this->scrollTarget = scrollTarget;
+
+ if (scrollIdleId == -1) {
+ scrollIdleId = platform->addIdle (&Layout::scrollIdle);
+ scrollIdleNotInterrupted = true;
+ }
+
+ scrollIdleNotInterrupted =
+ scrollIdleNotInterrupted || !scrollingInterrupted;
+ }
+}
+
+void Layout::scrollIdle ()
+{
+ DBG_OBJ_ENTER0 ("scroll", 0, "scrollIdle");
+
+ bool xChanged = true;
+ switch (scrollTarget->getHPos ()) {
+ case HPOS_LEFT:
+ scrollX = scrollTarget->getX ();
+ break;
+ case HPOS_CENTER:
+ scrollX =
+ scrollTarget->getX () - (viewportWidth - currVScrollbarThickness()
+ - scrollTarget->getWidth ()) / 2;
+ break;
+ case HPOS_RIGHT:
+ scrollX =
+ scrollTarget->getX () - (viewportWidth - currVScrollbarThickness()
+ - scrollTarget->getWidth ());
+ break;
+ case HPOS_INTO_VIEW:
+ xChanged = calcScrollInto (scrollTarget->getX (),
+ scrollTarget->getWidth (), &scrollX,
+ viewportWidth - currVScrollbarThickness());
+ break;
+ case HPOS_NO_CHANGE:
+ xChanged = false;
+ break;
+ }
+
+ bool yChanged = true;
+ switch (scrollTarget->getVPos ()) {
+ case VPOS_TOP:
+ scrollY = scrollTarget->getY ();
+ break;
+ case VPOS_CENTER:
+ scrollY =
+ scrollTarget->getY () - (viewportHeight - currHScrollbarThickness()
+ - scrollTarget->getHeight ()) / 2;
+ break;
+ case VPOS_BOTTOM:
+ scrollY =
+ scrollTarget->getY () - (viewportHeight - currHScrollbarThickness()
+ - scrollTarget->getHeight ());
+ break;
+ case VPOS_INTO_VIEW:
+ yChanged = calcScrollInto (scrollTarget->getY (),
+ scrollTarget->getHeight (), &scrollY,
+ viewportHeight - currHScrollbarThickness());
+ break;
+ case VPOS_NO_CHANGE:
+ yChanged = false;
+ break;
+ }
+
+ DBG_OBJ_MSGF ("scroll", 1, "xChanged = %s, yChanged = %s",
+ xChanged ? "true" : "false", yChanged ? "true" : "false");
+
+ if (xChanged || yChanged) {
+ adjustScrollPos ();
+ view->scrollTo (scrollX, scrollY);
+ }
+
+ // The following code was once inside the block above ("if
+ // (xChanged || yChanged)"). I'm not sure but it looks that this
+ // should be outside, or at least setting drawAfterScrollReq in
+ // Layout::draw should consider whether the scroll position will
+ // change (but this would be rather difficult). --SG
+
+ if (drawAfterScrollReq) {
+ drawAfterScrollReq = false;
+ view->queueDrawTotal ();
+ }
+
+ delete scrollTarget;
+ scrollTarget = NULL;
+
+ scrollIdleId = -1;
+
+ DBG_OBJ_LEAVE ();
+}
+
+void Layout::adjustScrollPos ()
+{
+ scrollX = misc::min (scrollX,
+ canvasWidth - (viewportWidth - vScrollbarThickness));
+ scrollX = misc::max (scrollX, 0);
+
+ scrollY = misc::min (scrollY,
+ canvasAscent + canvasDescent - (viewportHeight - hScrollbarThickness));
+ scrollY = misc::max (scrollY, 0);
+
+ _MSG("adjustScrollPos: scrollX=%d scrollY=%d\n", scrollX, scrollY);
+}
+
+bool Layout::calcScrollInto (int requestedValue, int requestedSize,
+ int *value, int viewportSize)
+{
+ if (requestedSize > viewportSize) {
+ // The viewport size is smaller than the size of the region which will
+ // be shown. If the region is already visible, do not change the
+ // position. Otherwise, show the left/upper border, this is most likely
+ // what is needed.
+ if (*value >= requestedValue &&
+ *value + viewportSize < requestedValue + requestedSize)
+ return false;
+ else
+ requestedSize = viewportSize;
+ }
+
+ if (requestedValue < *value) {
+ *value = requestedValue;
+ return true;
+ } else if (requestedValue + requestedSize > *value + viewportSize) {
+ *value = requestedValue - viewportSize + requestedSize;
+ return true;
+ } else
+ return false;
+}
+
+void Layout::draw (View *view, Rectangle *area)
+{
+ DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d",
+ area->x, area->y, area->width, area->height);
+
+ Rectangle widgetArea, intersection, widgetDrawArea;
+
+ // First of all, draw background image. (Unlike background *color*,
+ // this is not a feature of the views.)
+ if (bgImage != NULL && bgImage->getImgbufSrc() != NULL)
+ style::drawBackgroundImage (view, bgImage, bgRepeat, bgAttachment,
+ bgPositionX, bgPositionY,
+ area->x, area->y, area->width,
+ area->height, 0, 0,
+ // Reference area: maximum of canvas size and
+ // viewport size.
+ misc::max (viewportWidth
+ - (canvasHeightGreater ?
+ vScrollbarThickness : 0),
+ canvasWidth),
+ misc::max (viewportHeight
+ - hScrollbarThickness,
+ canvasAscent + canvasDescent));
+
+ if (scrollIdleId != -1) {
+ /* scroll is pending, defer draw until after scrollIdle() */
+ drawAfterScrollReq = true;
+ DBG_OBJ_MSGF ("draw", 1, "scrollIdleId = %d => delayed", scrollIdleId);
+ } else if (topLevel) {
+ /* Draw the top level widget. */
+ widgetArea.x = topLevel->allocation.x;
+ widgetArea.y = topLevel->allocation.y;
+ widgetArea.width = topLevel->allocation.width;
+ widgetArea.height = topLevel->getHeight ();
+
+ if (area->intersectsWith (&widgetArea, &intersection)) {
+ DBG_OBJ_MSG ("draw", 1, "drawing toplevel widget");
+
+ view->startDrawing (&intersection);
+
+ /* Intersection in widget coordinates. */
+ widgetDrawArea.x = intersection.x - topLevel->allocation.x;
+ widgetDrawArea.y = intersection.y - topLevel->allocation.y;
+ widgetDrawArea.width = intersection.width;
+ widgetDrawArea.height = intersection.height;
+
+ topLevel->draw (view, &widgetDrawArea);
+
+ view->finishDrawing (&intersection);
+ } else
+ DBG_OBJ_MSG ("draw", 1, "no intersection");
+ } else
+ DBG_OBJ_MSG ("draw", 1, "no toplevel widget");
+
+ DBG_OBJ_LEAVE ();
+}
+
+int Layout::currHScrollbarThickness()
+{
+ return (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;
+}
+
+int Layout::currVScrollbarThickness()
+{
+ return (canvasAscent + canvasDescent > viewportHeight) ?
+ vScrollbarThickness : 0;
+}
+
+/**
+ * Sets the anchor to scroll to.
+ */
+void Layout::setAnchor (const char *anchor)
+{
+ _MSG("setAnchor (%s)\n", anchor);
+
+ if (requestedAnchor)
+ free (requestedAnchor);
+ requestedAnchor = anchor ? strdup (anchor) : NULL;
+ updateAnchor ();
+}
+
+/**
+ * Used, when the widget is not allocated yet.
+ */
+char *Layout::addAnchor (Widget *widget, const char* name)
+{
+ return addAnchor (widget, name, -1);
+}
+
+char *Layout::addAnchor (Widget *widget, const char* name, int y)
+{
+ String key (name);
+ if (anchorsTable->contains (&key))
+ return NULL;
+ else {
+ Anchor *anchor = new Anchor ();
+ anchor->name = strdup (name);
+ anchor->widget = widget;
+ anchor->y = y;
+
+ anchorsTable->put (new String (name), anchor);
+ updateAnchor ();
+
+ return anchor->name;
+ }
+}
+
+void Layout::changeAnchor (Widget *widget, char* name, int y)
+{
+ String key (name);
+ Anchor *anchor = anchorsTable->get (&key);
+ assert (anchor);
+ assert (anchor->widget == widget);
+ anchor->y = y;
+ updateAnchor ();
+}
+
+void Layout::removeAnchor (Widget *widget, char* name)
+{
+ String key (name);
+ anchorsTable->remove (&key);
+}
+
+void Layout::updateAnchor ()
+{
+ Anchor *anchor;
+ if (requestedAnchor) {
+ String key (requestedAnchor);
+ anchor = anchorsTable->get (&key);
+ } else
+ anchor = NULL;
+
+ if (anchor == NULL) {
+ /** \todo Copy comment from old docs. */
+ if (scrollIdleId != -1 && !scrollIdleNotInterrupted) {
+ platform->removeIdle (scrollIdleId);
+ scrollIdleId = -1;
+ }
+ } else
+ if (anchor->y != -1)
+ scrollTo0 (new ScrollTargetFixed (HPOS_NO_CHANGE, VPOS_TOP,
+ 0, anchor->y, 0, 0), false);
+}
+
+void Layout::setCursor (style::Cursor cursor)
+{
+ if (cursor != this->cursor) {
+ this->cursor = cursor;
+ view->setCursor (cursor);
+ }
+}
+
+void Layout::updateCursor ()
+{
+ if (widgetAtPoint && widgetAtPoint->style)
+ setCursor (widgetAtPoint->style->cursor);
+ else
+ setCursor (style::CURSOR_DEFAULT);
+}
+
+void Layout::setBgColor (style::Color *color)
+{
+ color->ref ();
+
+ if (bgColor)
+ bgColor->unref ();
+
+ bgColor = color;
+
+ if (view)
+ view->setBgColor (bgColor);
+}
+
+void Layout::setBgImage (style::StyleImage *bgImage,
+ style::BackgroundRepeat bgRepeat,
+ style::BackgroundAttachment bgAttachment,
+ style::Length bgPositionX, style::Length bgPositionY)
+{
+ if (layoutImgRenderer && this->bgImage)
+ this->bgImage->removeExternalImgRenderer (layoutImgRenderer);
+
+ if (bgImage)
+ bgImage->ref ();
+
+ if (this->bgImage)
+ this->bgImage->unref ();
+
+ this->bgImage = bgImage;
+ this->bgRepeat = bgRepeat;
+ this->bgAttachment = bgAttachment;
+ this->bgPositionX = bgPositionX;
+ this->bgPositionY = bgPositionY;
+
+ if (bgImage) {
+ // Create instance of LayoutImgRenderer when needed. Until this
+ // layout is deleted, "layoutImgRenderer" will be kept, since it
+ // is not specific to the style, but only to this layout.
+ if (layoutImgRenderer == NULL)
+ layoutImgRenderer = new LayoutImgRenderer (this);
+ bgImage->putExternalImgRenderer (layoutImgRenderer);
+ }
+}
+
+
+void Layout::resizeIdle ()
+{
+ DBG_OBJ_ENTER0 ("resize", 0, "resizeIdle");
+
+ enterResizeIdle ();
+
+ //static int calls = 0;
+ //printf ("Layout::resizeIdle calls = %d\n", ++calls);
+
+ assert (resizeIdleId != -1);
+
+ for (typed::Iterator <Widget> it = queueResizeList->iterator();
+ it.hasNext (); ) {
+ Widget *widget = it.getNext ();
+
+ //printf (" the %stop-level %s %p was queued (extremes changed: %s)\n",
+ // widget->parent ? "non-" : "", widget->getClassName(), widget,
+ // widget->extremesQueued () ? "yes" : "no");
+
+ if (widget->resizeQueued ()) {
+ widget->setFlags (Widget::NEEDS_RESIZE);
+ widget->unsetFlags (Widget::RESIZE_QUEUED);
+ }
+
+ if (widget->allocateQueued ()) {
+ widget->setFlags (Widget::NEEDS_ALLOCATE);
+ widget->unsetFlags (Widget::ALLOCATE_QUEUED);
+ }
+
+ if (widget->extremesQueued ()) {
+ widget->setFlags (Widget::EXTREMES_CHANGED);
+ widget->unsetFlags (Widget::EXTREMES_QUEUED);
+ }
+ }
+ queueResizeList->clear ();
+
+ // Reset already here, since in this function, queueResize() may be
+ // called again.
+ resizeIdleId = -1;
+
+ // If this method is triggered by a viewport change, we can save
+ // time when the toplevel widget is not affected (as for a toplevel
+ // image resource).
+ if (topLevel && (topLevel->needsResize () || topLevel->needsAllocate ())) {
+ Requisition requisition;
+ Allocation allocation;
+
+ topLevel->sizeRequest (&requisition);
+ DBG_OBJ_MSGF ("resize", 1, "toplevel size: %d * (%d + %d)",
+ requisition.width, requisition.ascent, requisition.descent);
+
+ // This method is triggered by Widget::queueResize, which will,
+ // in any case, set NEEDS_ALLOCATE (indirectly, as ALLOCATE_QUEUED).
+ // This assertion helps to find inconsistences. (Cases where
+ // this method is triggered by a viewport change, but the
+ // toplevel widget is not affected, are filtered out some lines
+ // above: "if (topLevel && topLevel->needsResize ())".)
+ assert (topLevel->needsAllocate ());
+
+ allocation.x = allocation.y = 0;
+ allocation.width = requisition.width;
+ allocation.ascent = requisition.ascent;
+ allocation.descent = requisition.descent;
+ topLevel->sizeAllocate (&allocation);
+
+ canvasWidth = requisition.width;
+ canvasAscent = requisition.ascent;
+ canvasDescent = requisition.descent;
+
+ emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent);
+
+ // Tell the view about the new world size.
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+ // view->queueDrawTotal (false);
+
+ if (usesViewport) {
+ int currHThickness = currHScrollbarThickness();
+ int currVThickness = currVScrollbarThickness();
+
+ if (!canvasHeightGreater &&
+ canvasAscent + canvasDescent > viewportHeight - currHThickness) {
+ canvasHeightGreater = true;
+ DBG_OBJ_SET_SYM ("canvasHeightGreater",
+ canvasHeightGreater ? "true" : "false");
+ containerSizeChanged ();
+ }
+
+ // Set viewport sizes.
+ view->setViewportSize (viewportWidth, viewportHeight,
+ currHThickness, currVThickness);
+ }
+
+ // views are redrawn via Widget::resizeDrawImpl ()
+ }
+
+ updateAnchor ();
+
+ DBG_OBJ_MSGF ("resize", 1,
+ "after resizeIdle: resizeIdleId = %d", resizeIdleId);
+ DBG_OBJ_LEAVE ();
+
+ leaveResizeIdle ();
+}
+
+void Layout::queueDraw (int x, int y, int width, int height)
+{
+ DBG_OBJ_ENTER ("draw", 0, "queueDrawArea", "%d, %d, %d, %d",
+ x, y, width, height);
+
+ Rectangle area;
+ area.x = x;
+ area.y = y;
+ area.width = width;
+ area.height = height;
+
+ if (!area.isEmpty ())
+ view->queueDraw (&area);
+
+ DBG_OBJ_LEAVE ();
+}
+
+void Layout::queueDrawExcept (int x, int y, int width, int height,
+ int ex, int ey, int ewidth, int eheight) {
+
+ if (x == ex && y == ey && width == ewidth && height == eheight)
+ return;
+
+ // queueDraw() the four rectangles within rectangle (x, y, width, height)
+ // around rectangle (ex, ey, ewidth, eheight).
+ // Some or all of these may be empty.
+
+ // upper left corner of the intersection rectangle
+ int ix1 = misc::max (x, ex);
+ int iy1 = misc::max (y, ey);
+ // lower right corner of the intersection rectangle
+ int ix2 = misc::min (x + width, ex + ewidth);
+ int iy2 = misc::min (y + height, ey + eheight);
+
+ queueDraw (x, y, width, iy1 - y);
+ queueDraw (x, iy2, width, y + height - iy2);
+ queueDraw (x, iy1, ix1 - x, iy2 - iy1);
+ queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1);
+}
+
+void Layout::queueResize (bool extremesChanged)
+{
+ DBG_OBJ_ENTER ("resize", 0, "queueResize", "%s",
+ extremesChanged ? "true" : "false");
+
+ if (resizeIdleId == -1) {
+ view->cancelQueueDraw ();
+
+ resizeIdleId = platform->addIdle (&Layout::resizeIdle);
+ DBG_OBJ_MSGF ("resize", 1, "setting resizeIdleId = %d", resizeIdleId);
+ }
+
+ emitter.emitResizeQueued (extremesChanged);
+
+ DBG_OBJ_LEAVE ();
+}
+
+
+// Views
+
+bool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed,
+ int x, int y, ButtonState state, int button)
+
+{
+ EventButton event;
+
+ moveToWidgetAtPoint (x, y, state);
+
+ event.xCanvas = x;
+ event.yCanvas = y;
+ event.state = state;
+ event.button = button;
+ event.numPressed = numPressed;
+
+ return processMouseEvent (&event, type);
+}
+
+/**
+ * \brief This function is called by a view, to delegate a motion notify
+ * event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+bool Layout::motionNotify (View *view, int x, int y, ButtonState state)
+{
+ EventButton event;
+
+ moveToWidgetAtPoint (x, y, state);
+
+ event.xCanvas = x;
+ event.yCanvas = y;
+ event.state = state;
+
+ return processMouseEvent (&event, MOTION_NOTIFY);
+}
+
+/**
+ * \brief This function is called by a view, to delegate a enter notify event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+void Layout::enterNotify (View *view, int x, int y, ButtonState state)
+{
+ Widget *lastWidget;
+ EventCrossing event;
+
+ lastWidget = widgetAtPoint;
+ moveToWidgetAtPoint (x, y, state);
+
+ if (widgetAtPoint) {
+ event.state = state;
+ event.lastWidget = lastWidget;
+ event.currentWidget = widgetAtPoint;
+ widgetAtPoint->enterNotify (&event);
+ }
+}
+
+/**
+ * \brief This function is called by a view, to delegate a leave notify event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+void Layout::leaveNotify (View *view, ButtonState state)
+{
+#if 0
+ Widget *lastWidget;
+ EventCrossing event;
+
+ lastWidget = widgetAtPoint;
+ moveOutOfView (state);
+
+ if (lastWidget) {
+ event.state = state;
+ event.lastWidget = lastWidget;
+ event.currentWidget = widgetAtPoint;
+ lastWidget->leaveNotify (&event);
+ }
+#else
+ moveOutOfView (state);
+#endif
+}
+
+/*
+ * Return the widget at position (x, y). Return NULL, if there is no widget.
+ */
+Widget *Layout::getWidgetAtPoint (int x, int y)
+{
+ _MSG ("------------------------------------------------------------\n");
+ _MSG ("widget at (%d, %d)\n", x, y);
+ if (topLevel && topLevel->wasAllocated ())
+ return topLevel->getWidgetAtPoint (x, y, 0);
+ else
+ return NULL;
+}
+
+
+/*
+ * Emit the necessary crossing events, when the mouse pointer has moved to
+ * the given widget (by mouse or scrolling).
+ */
+void Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state)
+{
+ Widget *ancestor, *w;
+ Widget **track;
+ int trackLen, i, i_a;
+ EventCrossing crossingEvent;
+
+ _MSG("moveToWidget: wap=%p nwap=%p\n",widgetAtPoint,newWidgetAtPoint);
+ if (newWidgetAtPoint != widgetAtPoint) {
+ // The mouse pointer has been moved into another widget.
+ if (newWidgetAtPoint && widgetAtPoint)
+ ancestor =
+ newWidgetAtPoint->getNearestCommonAncestor (widgetAtPoint);
+ else if (newWidgetAtPoint)
+ ancestor = newWidgetAtPoint->getTopLevel ();
+ else
+ ancestor = widgetAtPoint->getTopLevel ();
+
+ // Construct the track.
+ trackLen = 0;
+ if (widgetAtPoint)
+ // first part
+ for (w = widgetAtPoint; w != ancestor; w = w->getParent ())
+ trackLen++;
+ trackLen++; // for the ancestor
+ if (newWidgetAtPoint)
+ // second part
+ for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
+ trackLen++;
+
+ track = new Widget* [trackLen];
+ i = 0;
+ if (widgetAtPoint)
+ /* first part */
+ for (w = widgetAtPoint; w != ancestor; w = w->getParent ())
+ track[i++] = w;
+ i_a = i;
+ track[i++] = ancestor;
+ if (newWidgetAtPoint) {
+ /* second part */
+ i = trackLen - 1;
+ for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
+ track[i--] = w;
+ }
+#if 0
+ MSG("Track: %s[ ", widgetAtPoint ? "" : "nil ");
+ for (i = 0; i < trackLen; i++)
+ MSG("%s%p ", i == i_a ? ">" : "", track[i]);
+ MSG("] %s\n", newWidgetAtPoint ? "" : "nil");
+#endif
+
+ /* Send events to the widgets on the track */
+ for (i = 0; i < trackLen; i++) {
+ crossingEvent.state = state;
+ crossingEvent.currentWidget = widgetAtPoint; // ???
+ crossingEvent.lastWidget = widgetAtPoint; // ???
+ if (i < i_a) {
+ track[i]->leaveNotify (&crossingEvent);
+ } else if (i == i_a) { /* ancestor */
+ /* Don't touch ancestor unless:
+ * - moving into/from NULL,
+ * - ancestor becomes the newWidgetAtPoint */
+ if (i_a == trackLen-1 && !newWidgetAtPoint)
+ track[i]->leaveNotify (&crossingEvent);
+ else if ((i_a == 0 && !widgetAtPoint) ||
+ (i_a == trackLen-1 && newWidgetAtPoint))
+ track[i]->enterNotify (&crossingEvent);
+ } else {
+ track[i]->enterNotify (&crossingEvent);
+ }
+ }
+
+ delete[] track;
+
+ widgetAtPoint = newWidgetAtPoint;
+ updateCursor ();
+ }
+}
+
+/**
+ * \brief Common processing of press, release and motion events.
+ *
+ * This function depends on that move_to_widget_at_point()
+ * has been called before.
+ */
+bool Layout::processMouseEvent (MousePositionEvent *event,
+ ButtonEventType type)
+{
+ Widget *widget;
+
+ /*
+ * If the event is outside of the visible region of the canvas, treat it
+ * as occurring at the region's edge. Notably, this helps when selecting
+ * text.
+ */
+ if (event->xCanvas < scrollX)
+ event->xCanvas = scrollX;
+ else {
+ int maxX = scrollX + viewportWidth - currVScrollbarThickness() - 1;
+
+ if (event->xCanvas > maxX)
+ event->xCanvas = maxX;
+ }
+ if (event->yCanvas < scrollY)
+ event->yCanvas = scrollY;
+ else {
+ int maxY = misc::min(scrollY + viewportHeight -currHScrollbarThickness(),
+ canvasAscent + canvasDescent) - 1;
+
+ if (event->yCanvas > maxY)
+ event->yCanvas = maxY;
+ }
+
+ widget = getWidgetAtPoint(event->xCanvas, event->yCanvas);
+
+ for (; widget; widget = widget->getParent ()) {
+ if (widget->isButtonSensitive ()) {
+ event->xWidget = event->xCanvas - widget->getAllocation()->x;
+ event->yWidget = event->yCanvas - widget->getAllocation()->y;
+
+ switch (type) {
+ case BUTTON_PRESS:
+ return widget->buttonPress ((EventButton*)event);
+
+ case BUTTON_RELEASE:
+ return widget->buttonRelease ((EventButton*)event);
+
+ case MOTION_NOTIFY:
+ return widget->motionNotify ((EventMotion*)event);
+
+ default:
+ misc::assertNotReached ();
+ }
+ }
+ }
+ if (type == BUTTON_PRESS)
+ return emitLinkPress (NULL, -1, -1, -1, -1, (EventButton*)event);
+ else if (type == BUTTON_RELEASE)
+ return emitLinkRelease(NULL, -1, -1, -1, -1, (EventButton*)event);
+
+ return false;
+}
+
+/*
+ * This function must be called by a view, when the user has manually changed
+ * the viewport position. It is *not* called, when the layout has requested the
+ * position change.
+ */
+void Layout::scrollPosChanged (View *view, int x, int y)
+{
+ if (x != scrollX || y != scrollY) {
+ scrollX = x;
+ scrollY = y;
+
+ setAnchor (NULL);
+ updateAnchor ();
+ }
+}
+
+/*
+ * This function must be called by a viewport view, when its viewport size has
+ * changed. It is *not* called, when the layout has requested the size change.
+ */
+void Layout::viewportSizeChanged (View *view, int width, int height)
+{
+ DBG_OBJ_ENTER ("resize", 0, "viewportSizeChanged", "%p, %d, %d",
+ view, width, height);
+
+ /* If the width has become higher, we test again, whether the vertical
+ * scrollbar (so to speak) can be hidden again. */
+ if (usesViewport && width > viewportWidth) {
+ canvasHeightGreater = false;
+ DBG_OBJ_SET_SYM ("canvasHeightGreater",
+ canvasHeightGreater ? "true" : "false");
+ }
+
+ /* if size changes, redraw this view.
+ * TODO: this is a resize call (redraw/resize code needs a review). */
+ if (viewportWidth != width || viewportHeight != height) {
+ if (topLevel)
+ // similar to addWidget()
+ topLevel->queueResize (-1, false);
+ else
+ queueResize (false);
+ }
+
+ viewportWidth = width;
+ viewportHeight = height;
+
+ DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth);
+ DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight);
+
+ containerSizeChanged ();
+
+ DBG_OBJ_LEAVE ();
+}
+
+void Layout::containerSizeChanged ()
+{
+ DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged");
+
+ if (topLevel) {
+ topLevel->containerSizeChanged ();
+ queueResize (true);
+ }
+
+ DBG_OBJ_LEAVE ();
+}
+
+} // namespace core
+} // namespace dw