/* * Dillo Widget * * Copyright 2005-2007 Sebastian Geerken * * 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. * * 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 . */ #include "core.hh" #include "../lout/msg.h" #include "../lout/debug.hh" using namespace lout; using namespace lout::object; namespace dw { namespace core { // ---------------------------------------------------------------------- bool Widget::WidgetImgRenderer::readyToDraw () { return widget->wasAllocated (); } void Widget::WidgetImgRenderer::getBgArea (int *x, int *y, int *width, int *height) { widget->getPaddingArea (x, y, width, height); } void Widget::WidgetImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef) { widget->getPaddingArea (xRef, yRef, widthRef, heightRef); } style::Style *Widget::WidgetImgRenderer::getStyle () { return widget->getStyle (); } void Widget::WidgetImgRenderer::draw (int x, int y, int width, int height) { widget->queueDrawArea (x - widget->allocation.x, y - widget->allocation.y, width, height); } // ---------------------------------------------------------------------- bool Widget::adjustMinWidth = true; int Widget::CLASS_ID = -1; Widget::Widget () { DBG_OBJ_CREATE ("dw::core::Widget"); registerName ("dw::core::Widget", &CLASS_ID); DBG_OBJ_ASSOC_CHILD (&requisitionParams); DBG_OBJ_ASSOC_CHILD (&extremesParams); flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED); parent = quasiParent = generator = container = NULL; setWidgetReference (NULL); DBG_OBJ_SET_PTR ("container", container); layout = NULL; allocation.x = -1; allocation.y = -1; allocation.width = 1; allocation.ascent = 1; allocation.descent = 0; extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0; style = NULL; bgColor = NULL; buttonSensitive = true; buttonSensitiveSet = false; deleteCallbackData = NULL; deleteCallbackFunc = NULL; widgetImgRenderer = NULL; stackingContextMgr = NULL; } Widget::~Widget () { if (deleteCallbackFunc) deleteCallbackFunc (deleteCallbackData); if (widgetImgRenderer) { if (style && style->backgroundImage) style->backgroundImage->removeExternalImgRenderer (widgetImgRenderer); delete widgetImgRenderer; } if (stackingContextMgr) delete stackingContextMgr; if (style) style->unref (); if (parent) parent->removeChild (this); else if (layout) layout->removeWidget (); DBG_OBJ_DELETE (); } /** * \brief Calculates the intersection of the visible allocation * (i. e. the intersection with the visible parent allocation) and * "area" (in widget coordinates referring to "refWidget"), * returned in intersection (in widget coordinates). * * Typically used by containers when drawing their children (passing * "this" as "refWidget"). Returns whether intersection is not empty. */ bool Widget::intersects (Widget *refWidget, Rectangle *area, Rectangle *intersection) { DBG_OBJ_ENTER ("draw", 0, "intersects", "%p, [%d, %d, %d * %d]", refWidget, area->x, area->y, area->width, area->height); bool r; if (wasAllocated ()) { *intersection = *area; intersection->x += refWidget->allocation.x; intersection->y += refWidget->allocation.y; r = true; // "RefWidget" is excluded; it is assumed that "area" its already within // its allocation. for (Widget *widget = this; r && widget != refWidget; widget = widget->parent) { assert (widget != NULL); // refWidget must be ancestor. Rectangle widgetArea, newIntersection; widgetArea.x = widget->allocation.x; widgetArea.y = widget->allocation.y; widgetArea.width = widget->allocation.width; widgetArea.height = widget->getHeight (); if (intersection->intersectsWith (&widgetArea, &newIntersection)) { DBG_OBJ_MSGF ("draw", 1, "new intersection: %d, %d, %d * %d", newIntersection.x, newIntersection.y, newIntersection.width, newIntersection.height); *intersection = newIntersection; } else { DBG_OBJ_MSG ("draw", 1, "no new intersection"); r = false; } } if (r) { intersection->x -= allocation.x; intersection->y -= allocation.y; DBG_OBJ_MSGF ("draw", 1, "final intersection: %d, %d, %d * %d", intersection->x, intersection->y, intersection->width, intersection->height); } } else { r = false; DBG_OBJ_MSG ("draw", 1, "not allocated"); } if (r) DBG_OBJ_MSGF ("draw", 1, "=> true: %d, %d, %d * %d", intersection->x, intersection->y, intersection->width, intersection->height); else DBG_OBJ_MSG ("draw", 1, "=> false"); DBG_OBJ_LEAVE (); return r; } /** * See \ref dw-interrupted-drawing for details. */ void Widget::drawInterruption (View *view, Rectangle *area, DrawingContext *context) { Rectangle thisArea; if (intersects (layout->topLevel, context->getToplevelArea (), &thisArea)) draw (view, &thisArea, context); context->addWidgetProcessedAsInterruption (this); } Widget *Widget::getWidgetAtPoint (int x, int y, GettingWidgetAtPointContext *context) { // Suitable for simple widgets, without children. if (inAllocation (x, y)) return this; else return NULL; } Widget *Widget::getWidgetAtPointInterrupted (int x, int y, GettingWidgetAtPointContext *context) { Widget *widgetAtPoint = getWidgetAtPoint (x, y, context); context->addWidgetProcessedAsInterruption (this); return widgetAtPoint; } void Widget::setParent (Widget *parent) { DBG_OBJ_ENTER ("construct", 0, "setParent", "%p", parent); this->parent = parent; layout = parent->layout; if (!buttonSensitiveSet) buttonSensitive = parent->buttonSensitive; DBG_OBJ_ASSOC_PARENT (parent); //printf ("The %s %p becomes a child of the %s %p\n", // getClassName(), this, parent->getClassName(), parent); // Determine the container. Currently rather simple; will become // more complicated when absolute and fixed positions are // supported. container = NULL; for (Widget *widget = getParent (); widget != NULL && container == NULL; widget = widget->getParent()) if (widget->isPossibleContainer ()) container = widget; // If there is no possible container widget, there is // (surprisingly!) also no container (i. e. the viewport is // used). Does not occur in dillo, where the toplevel widget is a // Textblock. DBG_OBJ_SET_PTR ("container", container); // If at all, stackingContextMgr should have set *before*, see also // Widget::setStyle() and Layout::addWidget(). if (stackingContextMgr) { Widget *stackingContextWidget = parent; while (stackingContextWidget && stackingContextWidget->stackingContextMgr == NULL) stackingContextWidget = stackingContextWidget->parent; assert (stackingContextWidget); stackingContextWidget->stackingContextMgr->addChildSCWidget (this); } else stackingContextWidget = parent->stackingContextWidget; notifySetParent(); DBG_OBJ_LEAVE (); } void Widget::setQuasiParent (Widget *quasiParent) { this->quasiParent = quasiParent; // More to do? Compare with setParent(). DBG_OBJ_SET_PTR ("quasiParent", quasiParent); } void Widget::queueDrawArea (int x, int y, int width, int height) { /** \todo Maybe only the intersection? */ DBG_OBJ_ENTER ("draw", 0, "queueDrawArea", "%d, %d, %d, %d", x, y, width, height); _MSG("Widget::queueDrawArea alloc(%d %d %d %d) wid(%d %d %d %d)\n", allocation.x, allocation.y, allocation.width, allocation.ascent + allocation.descent, x, y, width, height); if (layout) layout->queueDraw (x + allocation.x, y + allocation.y, width, height); DBG_OBJ_LEAVE (); } /** * \brief This method should be called, when a widget changes its size. * * A "fast" queueResize will ignore the anchestors, and furthermore * not trigger the idle function. Used only within * viewportSizeChanged, and not available outside Layout and Widget. */ void Widget::queueResize (int ref, bool extremesChanged, bool fast) { DBG_OBJ_ENTER ("resize", 0, "queueResize", "%d, %s, %s", ref, extremesChanged ? "true" : "false", fast ? "true" : "false"); enterQueueResize (); Widget *widget2, *child; Flags resizeFlag, extremesFlag, totalFlags; if (layout) { // If RESIZE_QUEUED is set, this widget is already in the list. if (!resizeQueued ()) layout->queueResizeList->put (this); resizeFlag = RESIZE_QUEUED; extremesFlag = EXTREMES_QUEUED; } else { resizeFlag = NEEDS_RESIZE; extremesFlag = EXTREMES_CHANGED; } setFlags (resizeFlag); setFlags (ALLOCATE_QUEUED); markSizeChange (ref); totalFlags = resizeFlag; if (extremesChanged) { totalFlags = (Flags)(totalFlags | extremesFlag); setFlags (extremesFlag); markExtremesChange (ref); } if (fast) { if (parent) { // In this case, queueResize is called from top (may be a // random entry point) to bottom, so markSizeChange and // markExtremesChange have to be called explicitly for the // parent. The tests (needsResize etc.) are uses to check // whether queueResize has been called for the parent, or // whether this widget is the entry point. if (parent->needsResize () || parent->resizeQueued ()) parent->markSizeChange (parentRef); if (parent->extremesChanged () || parent->extremesQueued ()) parent->markExtremesChange (parentRef); } } else { for (widget2 = parent, child = this; widget2; child = widget2, widget2 = widget2->parent) { if (layout && !widget2->resizeQueued ()) layout->queueResizeList->put (widget2); DBG_OBJ_MSGF ("resize", 2, "setting %s and ALLOCATE_QUEUED for %p", resizeFlag == RESIZE_QUEUED ? "RESIZE_QUEUED" : "NEEDS_RESIZE", widget2); widget2->setFlags (resizeFlag); widget2->markSizeChange (child->parentRef); widget2->setFlags (ALLOCATE_QUEUED); if (extremesChanged) { widget2->setFlags (extremesFlag); widget2->markExtremesChange (child->parentRef); } if (widget2->parent && (widget2->parent->flags & totalFlags) == totalFlags) { widget2->parent->markSizeChange (widget2->parentRef); if (extremesChanged) { widget2->parent->markExtremesChange (widget2->parentRef); } break; } } if (layout) layout->queueResize (extremesChanged); } leaveQueueResize (); DBG_OBJ_LEAVE (); } void Widget::containerSizeChanged () { DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged"); // If there is a container widget (not the viewport), which has not // changed its size (which can be determined by the respective // flags: this method is called recursively), this widget will // neither change its size. Also, the recursive iteration can be // stopped, since the children of this widget will if (container == NULL || container->needsResize () || container->resizeQueued () || container->extremesChanged () || container->extremesQueued ()) { // Viewport (container == NULL) or container widget has changed // its size. if (affectedByContainerSizeChange ()) queueResizeFast (0, true); // Even if *this* widget is not affected, children may be, so // iterate over children. containerSizeChangedForChildren (); } DBG_OBJ_LEAVE (); } bool Widget::affectedByContainerSizeChange () { DBG_OBJ_ENTER0 ("resize", 0, "affectedByContainerSizeChange"); bool ret; // This standard implementation is suitable for all widgets which // call correctRequisition() and correctExtremes(), even in the way // how Textblock and Image do (see comments there). Has to be kept // in sync. if (container == NULL) { if (style::isAbsLength (getStyle()->width) && style::isAbsLength (getStyle()->height)) // Both absolute, i. e. fixed: no dependency. ret = false; else if (style::isPerLength (getStyle()->width) || style::isPerLength (getStyle()->height)) { // Any percentage: certainly dependenant. ret = true; } else // One or both is "auto": depends ... ret = (getStyle()->width == style::LENGTH_AUTO ? usesAvailWidth () : false) || (getStyle()->height == style::LENGTH_AUTO ? usesAvailHeight () : false); } else ret = container->affectsSizeChangeContainerChild (this); DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); DBG_OBJ_LEAVE (); return ret; } bool Widget::affectsSizeChangeContainerChild (Widget *child) { DBG_OBJ_ENTER ("resize", 0, "affectsSizeChangeContainerChild", "%p", child); bool ret; // From the point of view of the container. This standard // implementation should be suitable for most (if not all) // containers. if (style::isAbsLength (child->getStyle()->width) && style::isAbsLength (child->getStyle()->height)) // Both absolute, i. e. fixed: no dependency. ret = false; else if (style::isPerLength (child->getStyle()->width) || style::isPerLength (child->getStyle()->height)) { // Any percentage: certainly dependenant. ret = true; } else // One or both is "auto": depends ... ret = (child->getStyle()->width == style::LENGTH_AUTO ? child->usesAvailWidth () : false) || (child->getStyle()->height == style::LENGTH_AUTO ? child->usesAvailHeight () : false); DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); DBG_OBJ_LEAVE (); return ret; } void Widget::containerSizeChangedForChildren () { DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); // Working, but inefficient standard implementation. Iterator *it = iterator ((Content::Type)(Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), false); while (it->next ()) it->getContent()->widget->containerSizeChanged (); it->unref (); DBG_OBJ_LEAVE (); } /** * \brief Must be implemengted by a method returning true, when * getAvailWidth() is called. */ bool Widget::usesAvailWidth () { return false; } /** * \brief Must be implemengted by a method returning true, when * getAvailHeight() is called. */ bool Widget::usesAvailHeight () { return false; } /** * \brief This method is a wrapper for Widget::sizeRequestImpl(); it calls * the latter only when needed. */ void Widget::sizeRequest (Requisition *requisition, int numPos, Widget **references, int *x, int *y) { assert (!queueResizeEntered ()); DBG_OBJ_ENTER ("resize", 0, "sizeRequest", "%d, ...", numPos); enterSizeRequest (); if (resizeQueued ()) { // This method is called outside of Layout::resizeIdle. setFlags (NEEDS_RESIZE); unsetFlags (RESIZE_QUEUED); // The widget is not taken out of Layout::queueResizeList, since // other *_QUEUED flags may still be set and processed in // Layout::resizeIdle. } bool callImpl; if (needsResize ()) callImpl = true; else { // Even if RESIZE_QUEUED / NEEDS_RESIZE is not set, calling // sizeRequestImpl is necessary when the relavive positions passed here // have changed. SizeParams newParams (numPos, references, x, y); DBG_OBJ_ASSOC_CHILD (&newParams); if (newParams.isEquivalent (&requisitionParams)) callImpl = false; else { callImpl = true; requisitionParams = newParams; } } if (callImpl) { calcExtraSpace (numPos, references, x, y); /** \todo Check requisition == &(this->requisition) and do what? */ sizeRequestImpl (requisition, numPos, references, x, y); this->requisition = *requisition; unsetFlags (NEEDS_RESIZE); DBG_OBJ_SET_NUM ("requisition.width", requisition->width); DBG_OBJ_SET_NUM ("requisition.ascent", requisition->ascent); DBG_OBJ_SET_NUM ("requisition.descent", requisition->descent); } else *requisition = this->requisition; leaveSizeRequest (); DBG_OBJ_LEAVE (); } /** * \brief Used to evaluate Widget::adjustMinWidth. * * If extremes == NULL, getExtremes is called. ForceValue is the same * value passed to getAvailWidth etc.; if false, getExtremes is not * called. A value of "false" is passed for "useCorrected" in the * context of correctExtemes etc., to avoid cyclic dependencies. * */ int Widget::getMinWidth (Extremes *extremes, bool forceValue) { DBG_IF_RTFL { if (extremes) DBG_OBJ_ENTER ("resize", 0, "getMinWidth", "[%d (%d) / %d (%d)], %s", extremes->minWidth, extremes->minWidthIntrinsic, extremes->maxWidth, extremes->maxWidthIntrinsic, forceValue ? "true" : "false"); else DBG_OBJ_ENTER ("resize", 0, "getMinWidth", "(nil), %s", forceValue ? "true" : "false"); } int minWidth; if (getAdjustMinWidth ()) { Extremes extremes2; if (extremes == NULL) { if (forceValue) { getExtremes (&extremes2); extremes = &extremes2; } } // TODO Not completely clear whether this is feasable: Within // the context of getAvailWidth(false) etc., getExtremes may not // be called. We ignore the minimal width then. if (extremes) minWidth = extremes->adjustmentWidth; else minWidth = 0; } else minWidth = 0; DBG_OBJ_MSGF ("resize", 1, "=> %d", minWidth); DBG_OBJ_LEAVE (); return minWidth; } /** * Return available width including margin/border/padding * (extraSpace?), not only the content width. */ int Widget::getAvailWidth (bool forceValue) { DBG_OBJ_ENTER ("resize", 0, "getAvailWidth", "%s", forceValue ? "true" : "false"); int width; if (parent == NULL && quasiParent == NULL) { DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); DBG_OBJ_MSG_START (); // TODO Consider nested layouts (e. g.