diff options
Diffstat (limited to 'dw/widget.cc')
-rw-r--r-- | dw/widget.cc | 1202 |
1 files changed, 1151 insertions, 51 deletions
diff --git a/dw/widget.cc b/dw/widget.cc index 7e9591ff..464459ba 100644 --- a/dw/widget.cc +++ b/dw/widget.cc @@ -17,8 +17,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - - #include "core.hh" #include "../lout/msg.h" @@ -62,6 +60,7 @@ void Widget::WidgetImgRenderer::draw (int x, int y, int width, int height) // ---------------------------------------------------------------------- +bool Widget::adjustMinWidth = true; int Widget::CLASS_ID = -1; Widget::Widget () @@ -69,8 +68,10 @@ Widget::Widget () DBG_OBJ_CREATE ("dw::core::Widget"); registerName ("dw::core::Widget", &CLASS_ID); - flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED | HAS_CONTENTS); - parent = NULL; + flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED); + parent = quasiParent = generator = container = NULL; + DBG_OBJ_SET_PTR ("container", container); + layout = NULL; allocation.x = -1; @@ -79,6 +80,8 @@ Widget::Widget () allocation.ascent = 1; allocation.descent = 0; + extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0; + style = NULL; bgColor = NULL; buttonSensitive = true; @@ -106,7 +109,7 @@ Widget::~Widget () if (parent) parent->removeChild (this); - else + else if (layout) layout->removeWidget (); DBG_OBJ_DELETE (); @@ -150,62 +153,320 @@ void Widget::setParent (Widget *parent) 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); + + notifySetParent(); +} + +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? */ - layout->queueDraw (x + allocation.x, y + allocation.y, width, height); + + 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) +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"); + + // queueResize() can be called recursively; calls are queued, so + // that actualQueueResize() is clean. + + if (queueResizeEntered ()) { + DBG_OBJ_MSG ("resize", 1, "put into queue"); + layout->queueQueueResizeList->pushUnder (new Layout::QueueResizeItem + (this, ref, extremesChanged, + fast)); + } else { + actualQueueResize (ref, extremesChanged, fast); + + DBG_IF_RTFL { + if (layout == NULL) + DBG_OBJ_MSG ("resize", 1, "layout is not set"); + else if (layout->queueQueueResizeList->size () == 0) + DBG_OBJ_MSG ("resize", 1, "queue item list is empty"); + } + + while (layout != NULL && layout->queueQueueResizeList->size () > 0) { + DBG_IF_RTFL { + DBG_OBJ_MSGF ("resize", 1, "queue item list has %d elements:", + layout->queueQueueResizeList->size ()); +#if 0 + // TODO This worked when queueQueueResizeList was a Vector; now, + // iterators should be used. + DBG_OBJ_MSG_START (); + for (int i = 0; i < layout->queueQueueResizeList->size (); i++) { + DBG_OBJ_MSGF + ("resize", 1, + "#%d: widget = %p, ref = %d, extremesChanged = %s, " + "fast = %s", + i, layout->queueQueueResizeList->get(i)->widget, + layout->queueQueueResizeList->get(i)->ref, + layout->queueQueueResizeList->get(i)->extremesChanged ? + "true" : "false", + layout->queueQueueResizeList->get(i)->fast ? + "true" : "false"); + } + DBG_OBJ_MSG_END (); + DBG_OBJ_MSG ("resize", 1, "taking #0 out of list"); +#endif + } + + Layout::QueueResizeItem *item = + layout->queueQueueResizeList->getTop (); + item->widget->actualQueueResize (item->ref, item->extremesChanged, + item->fast); + layout->queueQueueResizeList->pop (); + } + } + + DBG_OBJ_LEAVE (); +} + +void Widget::actualQueueResize (int ref, bool extremesChanged, bool fast) +{ + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER ("resize", 0, "actualQueueResize", "%d, %s, %s", + ref, extremesChanged ? "true" : "false", + fast ? "true" : "false"); + + enterQueueResize (); + Widget *widget2, *child; - //printf("The %stop-level %s %p with parentRef = %d has changed its size.\n", - // parent ? "non-" : "", getClassName(), this, parentRef); + Flags resizeFlag, extremesFlag; + + 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 (NEEDS_RESIZE); - setFlags (NEEDS_ALLOCATE); + setFlags (resizeFlag); + setFlags (ALLOCATE_QUEUED); markSizeChange (ref); if (extremesChanged) { - setFlags (EXTREMES_CHANGED); + setFlags (extremesFlag); markExtremesChange (ref); } - for (widget2 = parent, child = this; - widget2; - child = widget2, widget2 = widget2->parent) { - widget2->setFlags (NEEDS_RESIZE); - widget2->markSizeChange (child->parentRef); - widget2->setFlags (NEEDS_ALLOCATE); - - //printf (" Setting DW_NEEDS_RESIZE and NEEDS_ALLOCATE for the " - // "%stop-level %s %p with parentRef = %d\n", - // widget2->parent ? "non-" : "", widget2->getClassName(), widget2, - // widget2->parentRef); - - if (extremesChanged) { - widget2->setFlags (EXTREMES_CHANGED); - widget2->markExtremesChange (child->parentRef); + 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 enty 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 (layout) + layout->queueResize (extremesChanged); } - if (layout) - layout->queueResize (); + 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 @@ -213,6 +474,21 @@ void Widget::queueResize (int ref, bool extremesChanged) */ void Widget::sizeRequest (Requisition *requisition) { + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + 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. + } + if (needsResize ()) { /** \todo Check requisition == &(this->requisition) and do what? */ sizeRequestImpl (requisition); @@ -224,6 +500,392 @@ void Widget::sizeRequest (Requisition *requisition) 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. <button>). + + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + width = -1; + calcFinalWidth (getStyle (), viewportWidth, NULL, 0, forceValue, &width); + if (width == -1) + width = viewportWidth; + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + width = parent->getAvailWidthOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + width = quasiParent->getAvailWidthOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + + return width; +} + +/** + * Return available height including margin/border/padding + * (extraSpace?), not only the content height. + */ +int Widget::getAvailHeight (bool forceValue) +{ + // TODO Correct by ... not extremes, but ...? (Height extremes?) + + // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as + // "getAvailHeight (true)" is not used. + + DBG_OBJ_ENTER ("resize", 0, "getAvailHeight", "%s", + forceValue ? "true" : "false"); + + int height; + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + // TODO Consider nested layouts (e. g. <button>). + if (style::isAbsLength (getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (getStyle()->height)); + height = style::absLengthVal (getStyle()->height) + boxDiffHeight (); + } else if (style::isPerLength (getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (getStyle()->height)); + // Notice that here -- unlike getAvailWidth() -- + // layout->hScrollbarThickness is not considered here; + // something like canvasWidthGreater (analogue to + // canvasHeightGreater) would be complicated and lead to + // possibly contradictory self-references. + height = applyPerHeight (layout->viewportHeight, getStyle()->height); + } else { + DBG_OBJ_MSG ("resize", 1, "no specification"); + height = layout->viewportHeight; + } + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + height = parent->getAvailHeightOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + height = quasiParent->getAvailHeightOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + + return height; +} + +void Widget::correctRequisition (Requisition *requisition, + void (*splitHeightFun) (int, int *, int *)) +{ + // TODO Correct height by ... not extremes, but ...? (Height extremes?) + + DBG_OBJ_ENTER ("resize", 0, "correctRequisition", "%d * (%d + %d), ...", + requisition->width, requisition->ascent, + requisition->descent); + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + int limitMinWidth = getMinWidth (NULL, true); + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + calcFinalWidth (getStyle (), viewportWidth, NULL, limitMinWidth, false, + &requisition->width); + + // For layout->viewportHeight, see comment in getAvailHeight(). + int height = calcHeight (getStyle()->height, false, + layout->viewportHeight, NULL, false); + int minHeight = calcHeight (getStyle()->minHeight, false, + layout->viewportHeight, NULL, false); + int maxHeight = calcHeight (getStyle()->maxHeight, false, + layout->viewportHeight, NULL, false); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + parent->correctRequisitionOfChild (this, requisition, splitHeightFun); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + quasiParent->correctRequisitionOfChild (this, requisition, + splitHeightFun); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctExtremes (Extremes *extremes, bool useAdjustmentWidth) +{ + DBG_OBJ_ENTER ("resize", 0, "correctExtremes", "%d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + if (container == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + int limitMinWidth = + useAdjustmentWidth ? getMinWidth (extremes, false) : 0; + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + + int width = calcWidth (getStyle()->width, viewportWidth, NULL, + limitMinWidth, false); + int minWidth = calcWidth (getStyle()->minWidth, viewportWidth, NULL, + limitMinWidth, false); + int maxWidth = calcWidth (getStyle()->maxWidth, viewportWidth, NULL, + limitMinWidth, false); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + extremes->minWidth = extremes->maxWidth = width; + if (minWidth != -1) + extremes->minWidth = minWidth; + if (maxWidth != -1) + extremes->maxWidth = maxWidth; + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + parent->correctExtremesOfChild (this, extremes, useAdjustmentWidth); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + quasiParent->correctExtremesOfChild (this, extremes, useAdjustmentWidth); + DBG_OBJ_MSG_END (); + } + + if (extremes->maxWidth < extremes->minWidth) + extremes->maxWidth = extremes->minWidth; + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +int Widget::calcWidth (style::Length cssValue, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "calcWidth", "0x%x, %d, %p, %d", + cssValue, refWidth, refWidget, limitMinWidth); + + assert (refWidth != -1 || refWidget != NULL); + + int width; + + if (style::isAbsLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "absolute width: %dpx", + style::absLengthVal (cssValue)); + width = misc::max (style::absLengthVal (cssValue) + boxDiffWidth (), + limitMinWidth); + } else if (style::isPerLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "percentage width: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (cssValue)); + if (refWidth != -1) + width = misc::max (applyPerWidth (refWidth, cssValue), limitMinWidth); + else { + int availWidth = refWidget->getAvailWidth (forceValue); + if (availWidth != -1) { + int containerWidth = availWidth - refWidget->boxDiffWidth (); + width = misc::max (applyPerWidth (containerWidth, cssValue), + limitMinWidth); + } else + width = -1; + } + } else { + DBG_OBJ_MSG ("resize", 1, "not specified"); + width = -1; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; +} + +// *finalWidth may be -1. +void Widget::calcFinalWidth (style::Style *style, int refWidth, + Widget *refWidget, int limitMinWidth, + bool forceValue, int *finalWidth) +{ + DBG_OBJ_ENTER ("resize", 0, "calcFinalWidth", "..., %d, %p, %d, [%d]", + refWidth, refWidget, limitMinWidth, *finalWidth); + + int width = calcWidth (style->width, refWidth, refWidget, limitMinWidth, + forceValue); + int minWidth = calcWidth (style->minWidth, refWidth, refWidget, + limitMinWidth, forceValue); + int maxWidth = calcWidth (style->maxWidth, refWidth, refWidget, + limitMinWidth, forceValue); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + *finalWidth = width; + if (minWidth != -1 && *finalWidth != -1 && *finalWidth < minWidth) + *finalWidth = minWidth; + if (maxWidth != -1 && *finalWidth == -1 && *finalWidth > maxWidth) + *finalWidth = maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "=> %d", *finalWidth); + DBG_OBJ_LEAVE (); +} + +int Widget::calcHeight (style::Length cssValue, bool usePercentage, + int refHeight, Widget *refWidget, bool forceValue) +{ + // TODO Search for usage of this method and check the value of + // "usePercentage"; this has to be clarified. + + DBG_OBJ_ENTER ("resize", 0, "calcHeight", "0x%x, %s, %d, %p", + cssValue, usePercentage ? "true" : "false", refHeight, + refWidget); + + assert (refHeight != -1 || refWidget != NULL); + + int height; + + if (style::isAbsLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (cssValue)); + height = + misc::max (style::absLengthVal (cssValue) + boxDiffHeight (), 0); + } else if (style::isPerLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * + style::perLengthVal_useThisOnlyForDebugging (cssValue)); + if (usePercentage) { + if (refHeight != -1) + height = misc::max (applyPerHeight (refHeight, cssValue), 0); + else { + int availHeight = refWidget->getAvailHeight (forceValue); + if (availHeight != -1) { + int containerHeight = availHeight - refWidget->boxDiffHeight (); + height = + misc::max (applyPerHeight (containerHeight, cssValue), 0); + } else + height = -1; + } + } else + height = -1; + } else { + DBG_OBJ_MSG ("resize", 1, "not specified"); + height = -1; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + return height; } /** @@ -231,15 +893,49 @@ void Widget::sizeRequest (Requisition *requisition) */ void Widget::getExtremes (Extremes *extremes) { + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + + enterGetExtremes (); + + if (extremesQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (EXTREMES_CHANGED); + unsetFlags (EXTREMES_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + if (extremesChanged ()) { + // For backward compatibility (part 1/2): + extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic = -1; + getExtremesImpl (extremes); + + // For backward compatibility (part 2/2): + if (extremes->minWidthIntrinsic == -1) + extremes->minWidthIntrinsic = extremes->minWidth; + if (extremes->maxWidthIntrinsic == -1) + extremes->maxWidthIntrinsic = extremes->maxWidth; + this->extremes = *extremes; unsetFlags (EXTREMES_CHANGED); DBG_OBJ_SET_NUM ("extremes.minWidth", extremes->minWidth); + DBG_OBJ_SET_NUM ("extremes.minWidthIntrinsic", + extremes->minWidthIntrinsic); DBG_OBJ_SET_NUM ("extremes.maxWidth", extremes->maxWidth); + DBG_OBJ_SET_NUM ("extremes.maxWidthIntrinsic", + extremes->maxWidthIntrinsic); + DBG_OBJ_SET_NUM ("extremes.adjustmentWidth", extremes->adjustmentWidth); } else *extremes = this->extremes; + + leaveGetExtremes (); + + DBG_OBJ_LEAVE (); } /** @@ -248,6 +944,33 @@ void Widget::getExtremes (Extremes *extremes) */ void Widget::sizeAllocate (Allocation *allocation) { + assert (!queueResizeEntered ()); + assert (!sizeRequestEntered ()); + assert (!getExtremesEntered ()); + assert (resizeIdleEntered ()); + + DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + DBG_OBJ_MSGF ("resize", 1, + "old allocation (%d, %d; %d * (%d + %d)); needsAllocate: %s", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent, + needsAllocate () ? "true" : "false"); + + enterSizeAllocate (); + + /*printf ("The %stop-level %s %p is allocated:\n", + parent ? "non-" : "", getClassName(), this); + printf (" old = (%d, %d, %d + (%d + %d))\n", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent); + printf (" new = (%d, %d, %d + (%d + %d))\n", + allocation->x, allocation->y, allocation->width, allocation->ascent, + allocation->descent); + printf (" NEEDS_ALLOCATE = %s\n", needsAllocate () ? "true" : "false");*/ + if (needsAllocate () || allocation->x != this->allocation.x || allocation->y != this->allocation.y || @@ -288,6 +1011,10 @@ void Widget::sizeAllocate (Allocation *allocation) } /*unsetFlags (NEEDS_RESIZE);*/ + + leaveSizeAllocate (); + + DBG_OBJ_LEAVE (); } bool Widget::buttonPress (EventButton *event) @@ -359,6 +1086,59 @@ void Widget::setStyle (style::Style *style) queueResize (0, true); else queueDraw (); + + // These should better be attributed to the style itself, and a + // script processing RTFL messages could transfer it to something + // equivalent: + + DBG_OBJ_SET_NUM ("style.margin.top", style->margin.top); + DBG_OBJ_SET_NUM ("style.margin.bottom", style->margin.bottom); + DBG_OBJ_SET_NUM ("style.margin.left", style->margin.left); + DBG_OBJ_SET_NUM ("style.margin.right", style->margin.right); + + DBG_OBJ_SET_NUM ("style.border-width.top", style->borderWidth.top); + DBG_OBJ_SET_NUM ("style.border-width.bottom", style->borderWidth.bottom); + DBG_OBJ_SET_NUM ("style.border-width.left", style->borderWidth.left); + DBG_OBJ_SET_NUM ("style.border-width.right", style->borderWidth.right); + + DBG_OBJ_SET_NUM ("style.padding.top", style->padding.top); + DBG_OBJ_SET_NUM ("style.padding.bottom", style->padding.bottom); + DBG_OBJ_SET_NUM ("style.padding.left", style->padding.left); + DBG_OBJ_SET_NUM ("style.padding.right", style->padding.right); + + DBG_OBJ_SET_NUM ("style.border-spacing (h)", style->hBorderSpacing); + DBG_OBJ_SET_NUM ("style.border-spacing (v)", style->vBorderSpacing); + + DBG_OBJ_SET_SYM ("style.display", + style->display == style::DISPLAY_BLOCK ? "block" : + style->display == style::DISPLAY_INLINE ? "inline" : + style->display == style::DISPLAY_INLINE_BLOCK ? + "inline-block" : + style->display == style::DISPLAY_LIST_ITEM ? "list-item" : + style->display == style::DISPLAY_NONE ? "none" : + style->display == style::DISPLAY_TABLE ? "table" : + style->display == style::DISPLAY_TABLE_ROW_GROUP ? + "table-row-group" : + style->display == style::DISPLAY_TABLE_HEADER_GROUP ? + "table-header-group" : + style->display == style::DISPLAY_TABLE_FOOTER_GROUP ? + "table-footer-group" : + style->display == style::DISPLAY_TABLE_ROW ? "table-row" : + style->display == style::DISPLAY_TABLE_CELL ? "table-cell" : + "???"); + + DBG_OBJ_SET_NUM ("style.width (raw)", style->width); + DBG_OBJ_SET_NUM ("style.min-width (raw)", style->minWidth); + DBG_OBJ_SET_NUM ("style.max-width (raw)", style->maxWidth); + DBG_OBJ_SET_NUM ("style.height (raw)", style->height); + DBG_OBJ_SET_NUM ("style.min-height (raw)", style->minHeight); + DBG_OBJ_SET_NUM ("style.max-height (raw)", style->maxHeight); + + if (style->backgroundColor) + DBG_OBJ_SET_COL ("style.background-color", + style->backgroundColor->getColor ()); + else + DBG_OBJ_SET_SYM ("style.background-color", "transparent"); } /** @@ -414,6 +1194,10 @@ void Widget::drawBox (View *view, style::Style *style, Rectangle *area, // does not define what here is called "reference area". To make it look // smoothly, the widget padding box is used. + // TODO Handle inverse drawing the same way as in drawWidgetBox? + // Maybe this method (drawBox) is anyway obsolete when extraSpace + // is fully supported (as in the "dillo_grows" repository). + int xPad, yPad, widthPad, heightPad; getPaddingArea (&xPad, &yPad, &widthPad, &heightPad); style::drawBackground @@ -424,7 +1208,8 @@ void Widget::drawBox (View *view, style::Style *style, Rectangle *area, - style->margin.right - style->borderWidth.right, height - style->margin.top - style->borderWidth.top - style->margin.bottom - style->borderWidth.bottom, - xPad, yPad, widthPad, heightPad, style, inverse, false); + xPad, yPad, widthPad, heightPad, style, style->backgroundColor, + inverse, false); } /** @@ -446,10 +1231,26 @@ void Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse) int xPad, yPad, widthPad, heightPad; getPaddingArea (&xPad, &yPad, &widthPad, &heightPad); + + style::Color *bgColor; + if (inverse && style->backgroundColor == NULL) { + // See style::drawBackground: for inverse drawing, we need a + // defined background color. Search through ancestors. + Widget *w = this; + while (w != NULL && w->style->backgroundColor == NULL) + w = w->parent; + + if (w != NULL && w->style->backgroundColor != NULL) + bgColor = w->style->backgroundColor; + else + bgColor = layout->getBgColor (); + } else + bgColor = style->backgroundColor; + style::drawBackground (view, layout, &canvasArea, xPad, yPad, widthPad, heightPad, xPad, yPad, widthPad, heightPad, - style, inverse, parent == NULL); + style, bgColor, inverse, parent == NULL); } /* @@ -510,6 +1311,25 @@ int Widget::getLevel () } /** + * \brief Get the level of the widget within the tree, regarting the + * generators, not the parents. + * + * The root widget has the level 0. + */ +int Widget::getGeneratorLevel () +{ + Widget *widget = this; + int level = 0; + + while (widget->getGenerator ()) { + level++; + widget = widget->getGenerator (); + } + + return level; +} + +/** * \brief Get the widget with the highest level, which is a direct ancestor of * widget1 and widget2. */ @@ -569,11 +1389,15 @@ Widget *Widget::getWidgetAtPoint (int x, int y, int level) * is such a child, it is returned. Otherwise, this widget is returned. */ childAtPoint = NULL; - it = iterator (Content::WIDGET, false); - - while (childAtPoint == NULL && it->next ()) - childAtPoint = it->getContent()->widget->getWidgetAtPoint (x, y, - level + 1); + it = iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + + while (childAtPoint == NULL && it->next ()) { + Widget *child = it->getContent()->widget; + if (child->wasAllocated ()) + childAtPoint = child->getWidgetAtPoint (x, y, level + 1); + } it->unref (); @@ -606,18 +1430,10 @@ void Widget::getPaddingArea (int *xPad, int *yPad, int *widthPad, *yPad = allocation.y + style->margin.top + style->borderWidth.top; *widthPad = allocation.width - style->margin.left - style->borderWidth.left - style->margin.right - style->borderWidth.right; - *heightPad = getHeight () - style->margin.top - style->borderWidth.top + *heightPad = getHeight () - style->margin.top - style->borderWidth.top - style->margin.bottom - style->borderWidth.bottom; } -void Widget::getExtremesImpl (Extremes *extremes) -{ - /* Simply return the requisition width */ - Requisition requisition; - sizeRequest (&requisition); - extremes->minWidth = extremes->maxWidth = requisition.width; -} - void Widget::sizeAllocateImpl (Allocation *allocation) { } @@ -630,16 +1446,280 @@ void Widget::markExtremesChange (int ref) { } -void Widget::setWidth (int width) +int Widget::applyPerWidth (int containerWidth, style::Length perWidth) +{ + return style::multiplyWithPerLength (containerWidth, perWidth) + + boxDiffWidth (); +} + +int Widget::applyPerHeight (int containerHeight, style::Length perHeight) +{ + return style::multiplyWithPerLength (containerHeight, perHeight) + + boxDiffHeight (); +} + +int Widget::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + // This is a halfway suitable implementation for all + // containers. For simplification, this will be used during the + // development; then, a differentiation could be possible. + + DBG_OBJ_ENTER ("resize", 0, "getAvailWidthOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int width; + + if (child->getStyle()->width == style::LENGTH_AUTO) { + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + width = misc::max (getAvailWidth (true) - boxDiffWidth (), 0); + else + width = -1; + } else { + // In most cases, the toplevel widget should be a container, so + // the container is non-NULL when the parent is non-NULL. Just + // in case, regard also parent. And quasiParent. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + width = -1; + child->calcFinalWidth (child->getStyle(), -1, this, 0, forceValue, + &width); + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + width = effContainer->getAvailWidthOfChild (child, forceValue); + DBG_OBJ_MSG_END (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + + return width; +} + +int Widget::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + // Again, a suitable implementation for all widgets (perhaps). + + // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as + // "getAvailHeight (true)" is not used. + + DBG_OBJ_ENTER ("resize", 0, "getAvailHeightOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int height; + + if (child->getStyle()->height == style::LENGTH_AUTO) { + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + height = misc::max (getAvailHeight (true) - boxDiffHeight (), 0); + else + height = -1; + } else { + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + if (style::isAbsLength (child->getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (child->getStyle()->height)); + height = misc::max (style::absLengthVal (child->getStyle()->height) + + child->boxDiffHeight (), 0); + } else { + assert (style::isPerLength (child->getStyle()->height)); + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (child->getStyle()->height)); + + int availHeight = getAvailHeight (forceValue); + if (availHeight == -1) + height = -1; + else + height = + misc::max (child->applyPerHeight (availHeight - + boxDiffHeight (), + child->getStyle()->height), + 0); + } + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + height = effContainer->getAvailHeightOfChild (child, forceValue); + DBG_OBJ_MSG_END (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + + return height; +} + +void Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) +{ + // Again, a suitable implementation for all widgets (perhaps). + + DBG_OBJ_ENTER ("resize", 0, "correctRequisitionOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + correctReqWidthOfChild (child, requisition); + correctReqHeightOfChild (child, requisition, splitHeightFun); + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + effContainer->correctRequisitionOfChild (child, requisition, + splitHeightFun); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctReqWidthOfChild (Widget *child, Requisition *requisition) +{ + DBG_OBJ_ENTER ("resize", 0, "correctReqWidthOfChild", "%p, %d * (%d + %d)", + child, requisition->width, requisition->ascent, + requisition->descent); + + assert (this == child->quasiParent || this == child->container); + + int limitMinWidth = child->getMinWidth (NULL, true); + child->calcFinalWidth (child->getStyle(), -1, this, limitMinWidth, false, + &requisition->width); + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctReqHeightOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)) +{ + // TODO Correct height by extremes? (Height extemes?) + + assert (this == child->quasiParent || this == child->container); + + DBG_OBJ_ENTER ("resize", 0, "correctReqHeightOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + int height = child->calcHeight (child->getStyle()->height, false, -1, this, + false); + int minHeight = child->calcHeight (child->getStyle()->minHeight, false, -1, + this, false); + int maxHeight = child->calcHeight (child->getStyle()->maxHeight, false, -1, + this, false); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctExtremesOfChild (Widget *child, Extremes *extremes, + bool useAdjustmentWidth) +{ + // See comment in correctRequisitionOfChild. + + DBG_OBJ_ENTER ("resize", 0, "correctExtremesOfChild", + "%p, %d (%d) / %d (%d)", + child, extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + int limitMinWidth = + useAdjustmentWidth ? child->getMinWidth (extremes, false) : 0; + int width = child->calcWidth (child->getStyle()->width, -1, this, + limitMinWidth, false); + int minWidth = child->calcWidth (child->getStyle()->minWidth, -1, this, + limitMinWidth, false); + int maxWidth = child->calcWidth (child->getStyle()->maxWidth, -1, this, + limitMinWidth, false); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + extremes->minWidth = extremes->maxWidth = width; + if (minWidth != -1) + extremes->minWidth = minWidth; + if (maxWidth != -1) + extremes->maxWidth = maxWidth; + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + effContainer->correctExtremesOfChild (child, extremes, + useAdjustmentWidth); + DBG_OBJ_MSG_END (); + } + + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +/** + * \brief This method is called after a widget has been set as the top of a + * widget tree. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetAsTopLevel() +{ +} + +/** + * \brief This method is called after a widget has been added to a parent. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetParent() { } -void Widget::setAscent (int ascent) +bool Widget::isBlockLevel () { + // Most widgets are not block-level. + return false; } -void Widget::setDescent (int descent) +bool Widget::isPossibleContainer () { + // In most (all?) cases identical to: + return isBlockLevel (); } bool Widget::buttonPressImpl (EventButton *event) @@ -659,7 +1739,7 @@ bool Widget::motionNotifyImpl (EventMotion *event) void Widget::enterNotifyImpl (EventCrossing *) { - core::style::Tooltip *tooltip = getStyle()->x_tooltip; + style::Tooltip *tooltip = getStyle()->x_tooltip; if (tooltip) tooltip->onEnter(); @@ -667,7 +1747,7 @@ void Widget::enterNotifyImpl (EventCrossing *) void Widget::leaveNotifyImpl (EventCrossing *) { - core::style::Tooltip *tooltip = getStyle()->x_tooltip; + style::Tooltip *tooltip = getStyle()->x_tooltip; if (tooltip) tooltip->onLeave(); @@ -679,5 +1759,25 @@ void Widget::removeChild (Widget *child) misc::assertNotReached (); } +// ---------------------------------------------------------------------- + +void splitHeightPreserveAscent (int height, int *ascent, int *descent) +{ + *descent = height - *ascent; + if (*descent < 0) { + *descent = 0; + *ascent = height; + } +} + +void splitHeightPreserveDescent (int height, int *ascent, int *descent) +{ + *ascent = height - *descent; + if (*ascent < 0) { + *ascent = 0; + *descent = height; + } +} + } // namespace core } // namespace dw |