Files
flenser/dw/widget.cc

2273 lines
74 KiB
C++

/*
* Dillo Widget
*
* Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
* Copyright 2023-2024 Rodrigo Arias Mallo <rodarima@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
//#define DEBUG_LEVEL 1
#include "core.hh"
#include "../lout/msg.h"
#include "../lout/debug.hh"
using namespace lout;
using namespace lout::object;
using namespace lout::misc;
namespace dw {
namespace core {
/* Used to determine which action to take when correcting the aspect ratio of a
* requisition in Widget::correctReqAspectRatio(). */
enum {
PASS_INCREASE = 0,
PASS_DECREASE = 1,
PASS_KEEP = 2
};
// ----------------------------------------------------------------------
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;
Widget::Widget ()
{
DBG_OBJ_CREATE ("dw::core::Widget");
registerName ("dw::core::Widget", typeid(*this));
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;
ratio = 0.0;
}
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_LEAVE_VAL ("true: %d, %d, %d * %d",
intersection->x, intersection->y,
intersection->width, intersection->height);
else
DBG_OBJ_LEAVE_VAL0 ("false");
return r;
}
/**
* See \ref dw-interrupted-drawing for details.
*/
void Widget::drawInterruption (View *view, Rectangle *area,
DrawingContext *context)
{
Rectangle thisArea;
if (intersects (layout->topLevel.get(), 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 ancestors, 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.push_back (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.push_back (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);
}
DBG_IF_RTFL {
if (widget2->parent)
DBG_OBJ_MSGF ("resize", 2,
"checking parent %p: (%d & %d) [= %d] == %d?",
widget2->parent, widget2->parent->flags,
totalFlags, widget2->parent->flags & totalFlags,
totalFlags);
}
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_LEAVE_VAL ("%s", boolToStr(ret));
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_LEAVE_VAL ("%s", boolToStr(ret));
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.
*
* Computes the size (Requisition) that the current widget wants. The output
* \param requisition has the final values which will be used to compute the
* widget allocation.
*/
void Widget::sizeRequest (Requisition *requisition, int numPos,
Widget **references, int *x, int *y)
{
assert (!queueResizeEntered ());
DBG_OBJ_ENTER ("resize", 0, "sizeRequest", "%d, ...", numPos);
DBG_IF_RTFL {
DBG_OBJ_MSG_START();
for(int i = 0; i < numPos; i++)
DBG_OBJ_MSGF ("resize", 1, "ref #%d: %p, %d, %d",
i, references[i], x[i], y[i]);
DBG_OBJ_MSG_END();
}
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.
}
SizeParams newRequisitionParams (numPos, references, x, y);
DBG_OBJ_ASSOC_CHILD (&newRequisitionParams);
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.
callImpl = !newRequisitionParams.isEquivalent (&requisitionParams);
}
DBG_OBJ_MSGF ("resize", 1, "callImpl = %s", boolToStr (callImpl));
requisitionParams = newRequisitionParams;
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 feasible: 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_LEAVE_VAL ("%d", minWidth);
return minWidth;
}
/**
* Return available width including margin/border/padding
* (extraSpace?), not only the content width.
*
* If the widget has a parent or a quasiParent, the width computation is
* delegated to the parent first, or the quasiParent later.
*/
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 = viewportWidth;
calcFinalWidth (getStyle (), viewportWidth, NULL, 0, forceValue, &width);
assert(width != -1);
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_LEAVE_VAL ("%d", width);
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");
if (forceValue)
height = layout->viewportHeight;
else
height = -1;
}
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_LEAVE_VAL ("%d", height);
return height;
}
/**
* Corrects a requisition to fit in the viewport.
*
* Instead of asking the parent widget, it uses the viewport dimensions to
* correct the requisition if needed. This is used for the top level widget (the
* body) which doesn't have any parent.
*/
void Widget::correctRequisitionViewport (Requisition *requisition,
void (*splitHeightFun) (int, int *, int *),
bool allowDecreaseWidth,
bool allowDecreaseHeight)
{
int limitMinWidth = getMinWidth (NULL, true);
if (!allowDecreaseWidth && limitMinWidth < requisition->width)
limitMinWidth = requisition->width;
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);
adjustHeight (&height, allowDecreaseHeight, requisition->ascent,
requisition->descent);
int minHeight = calcHeight (getStyle()->minHeight, false,
layout->viewportHeight, NULL, false);
int maxHeight = calcHeight (getStyle()->maxHeight, false,
layout->viewportHeight, NULL, false);
if (minHeight != -1 && maxHeight != -1) {
/* Prefer the maximum size for pathological cases (min > max) */
if (maxHeight < minHeight)
maxHeight = minHeight;
}
adjustHeight (&minHeight, allowDecreaseHeight, requisition->ascent,
requisition->descent);
adjustHeight (&maxHeight, allowDecreaseHeight, requisition->ascent,
requisition->descent);
// 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);
}
void Widget::correctRequisition (Requisition *requisition,
void (*splitHeightFun) (int, int *, int *),
bool allowDecreaseWidth,
bool allowDecreaseHeight)
{
// TODO Correct height by ... not extremes, but ...? (Height extremes?)
DBG_OBJ_ENTER ("resize", 0, "correctRequisition",
"%d * (%d + %d), ..., %s, %s",
requisition->width, requisition->ascent,
requisition->descent, misc::boolToStr (allowDecreaseWidth),
misc::boolToStr (allowDecreaseHeight));
if (parent == NULL && quasiParent == NULL) {
DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport");
DBG_OBJ_MSG_START ();
for (int pass = 0; pass < 3; pass++) {
correctRequisitionViewport (requisition, splitHeightFun,
allowDecreaseWidth, allowDecreaseHeight);
bool changed = correctReqAspectRatio (pass, this, requisition,
allowDecreaseWidth, allowDecreaseHeight, splitHeightFun);
/* No need to repeat more passes */
if (!changed)
break;
}
DBG_OBJ_MSG_END ();
} else if (parent) {
DBG_OBJ_MSG ("resize", 1, "delegated to parent");
DBG_OBJ_MSG_START ();
parent->correctRequisitionOfChild (this, requisition, splitHeightFun,
allowDecreaseWidth,
allowDecreaseHeight);
DBG_OBJ_MSG_END ();
} else /* if (quasiParent) */ {
DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent");
DBG_OBJ_MSG_START ();
quasiParent->correctRequisitionOfChild (this, requisition,
splitHeightFun,
allowDecreaseWidth,
allowDecreaseHeight);
DBG_OBJ_MSG_END ();
}
DBG_OBJ_LEAVE_VAL ("%d * (%d + %d)", requisition->width, requisition->ascent,
requisition->descent);
}
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_LEAVE_VAL ("%d / %d", extremes->minWidth, extremes->maxWidth);
}
/** Computes a width value in pixels from cssValue.
*
* If cssValue is absolute, the absolute value is used.
* If cssValue is relative, then it is applied to refWidth.
* Otherwise, -1 is used.
*
* In any case, the returned value is clamped so that is not smaller
* than limitMinWidth.
*
*/
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 = std::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 = std::max (applyPerWidth (refWidth, cssValue), limitMinWidth);
else {
int availWidth = refWidget->getAvailWidth (forceValue);
if (availWidth != -1) {
int containerWidth = availWidth - refWidget->boxDiffWidth ();
width = std::max (applyPerWidth (containerWidth, cssValue),
limitMinWidth);
} else
width = -1;
}
} else {
DBG_OBJ_MSG ("resize", 1, "not specified");
width = -1;
}
DBG_OBJ_LEAVE_VAL ("%d", width);
return width;
}
/**
* Computes the final width if possible and constraints it by min-width and
* max-width.
*
* This function performs a very particular computation. It will try to find the
* fixed width of the style provided by taking the refWidth or refWidget as the
* reference to expand relative values.
*
* The value of *finalWidth is used to initialized the first value of width, so
* it can be used to initialize a width when the style sets the width property
* to auto.
*
* If both the initial *finalWidth and the style with are -1, the value will be
* left as is, even if min-width and max-width could constraint the size. For the
* width to be constrained, either the initial *finalWidth or the computed width
* should return an absolute value.
*
* \post If *finalWidth != -1 the computed *finalWidth value is guarantee not to
* 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 w = *finalWidth;
int width = calcWidth (style->width, refWidth, refWidget, limitMinWidth,
forceValue);
DBG_OBJ_MSGF ("resize", 1, "w = %d, width = %d", w, width);
if (width != -1)
w = width;
/* Only correct w if not set to auto (-1) */
if (w != -1) {
int minWidth = calcWidth (style->minWidth, refWidth, refWidget,
limitMinWidth, forceValue);
int maxWidth = calcWidth (style->maxWidth, refWidth, refWidget,
limitMinWidth, forceValue);
DBG_OBJ_MSGF ("resize", 1, "minWidth = %d, maxWidth = %d",
minWidth, maxWidth);
if (minWidth != -1 && maxWidth != -1) {
/* Prefer the maximum size for pathological cases (min > max) */
if (maxWidth < minWidth)
maxWidth = minWidth;
}
if (minWidth != -1 && w < minWidth)
w = minWidth;
if (maxWidth != -1 && w > maxWidth)
w = maxWidth;
}
/* Check postcondition: *finalWidth != -1 (implies) w != -1 */
assert(!(*finalWidth != -1 && w == -1));
*finalWidth = w;
DBG_OBJ_LEAVE_VAL ("%d", *finalWidth);
}
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 =
std::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 = std::max (applyPerHeight (refHeight, cssValue), 0);
else {
int availHeight = refWidget->getAvailHeight (forceValue);
if (availHeight != -1) {
int containerHeight = availHeight - refWidget->boxDiffHeight ();
height =
std::max (applyPerHeight (containerHeight, cssValue), 0);
} else
height = -1;
}
} else
height = -1;
} else {
DBG_OBJ_MSG ("resize", 1, "not specified");
height = -1;
}
DBG_OBJ_LEAVE_VAL ("%d", height);
return height;
}
void Widget::adjustHeight (int *height, bool allowDecreaseHeight, int ascent,
int descent)
{
if (!allowDecreaseHeight && *height != -1 && *height < ascent + descent)
*height = ascent + descent;
}
/**
* \brief Wrapper for Widget::getExtremesImpl().
*/
void Widget::getExtremes (Extremes *extremes, int numPos, Widget **references,
int *x, int *y)
{
assert (!queueResizeEntered ());
DBG_OBJ_ENTER ("resize", 0, "getExtremes", "%d, ...", numPos);
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.
}
bool callImpl;
if (extremesChanged ())
callImpl = true;
else {
// Even if EXTREMES_QUEUED / EXTREMES_CHANGED is not set, calling
// getExtremesImpl is necessary when the relavive positions passed here
// have changed.
SizeParams newParams (numPos, references, x, y);
DBG_OBJ_ASSOC_CHILD (&newParams);
if (newParams.isEquivalent (&extremesParams))
callImpl = false;
else {
callImpl = true;
extremesParams = newParams;
}
}
if (callImpl) {
// For backward compatibility (part 1/2):
extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic = -1;
getExtremesImpl (extremes, numPos, references, x, y);
// 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 ();
}
/**
* \brief Calculates dw::core::Widget::extraSpace.
*
* Delegated to dw::core::Widget::calcExtraSpaceImpl. Called both from
* dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes.
*/
void Widget::calcExtraSpace (int numPos, Widget **references, int *x, int *y)
{
DBG_OBJ_ENTER0 ("resize", 0, "calcExtraSpace");
extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0;
calcExtraSpaceImpl (numPos, references, x, y);
DBG_OBJ_SET_NUM ("extraSpace.top", extraSpace.top);
DBG_OBJ_SET_NUM ("extraSpace.bottom", extraSpace.bottom);
DBG_OBJ_SET_NUM ("extraSpace.left", extraSpace.left);
DBG_OBJ_SET_NUM ("extraSpace.right", extraSpace.right);
DBG_OBJ_LEAVE ();
}
int Widget::numSizeRequestReferences ()
{
return 0;
}
Widget *Widget::sizeRequestReference (int index)
{
misc::notImplemented ("Widget::sizeRequestReference");
return NULL;
}
int Widget::numGetExtremesReferences ()
{
return 0;
}
Widget *Widget::getExtremesReference (int index)
{
misc::notImplemented ("Widget::getExtremesReference");
return NULL;
}
/**
* \brief Wrapper for Widget::sizeAllocateImpl, calls the latter only when
* needed.
*
* Sets the allocation of the widget to \param allocation, which is the final
* size and position it will have on the canvas. This is usually called after
* the requisition size is determined in Widget::sizeRequest().
*/
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 ||
allocation->width != this->allocation.width ||
allocation->ascent != this->allocation.ascent ||
allocation->descent != this->allocation.descent) {
if (wasAllocated ()) {
layout->queueDrawExcept (
this->allocation.x,
this->allocation.y,
this->allocation.width,
this->allocation.ascent + this->allocation.descent,
allocation->x,
allocation->y,
allocation->width,
allocation->ascent + allocation->descent);
}
sizeAllocateImpl (allocation);
//DEBUG_MSG (DEBUG_ALLOC, "... to %d, %d, %d x %d x %d\n",
// widget->allocation.x, widget->allocation.y,
// widget->allocation.width, widget->allocation.ascent,
// widget->allocation.descent);
this->allocation = *allocation;
unsetFlags (NEEDS_ALLOCATE);
setFlags (WAS_ALLOCATED);
resizeDrawImpl ();
DBG_OBJ_SET_NUM ("allocation.x", this->allocation.x);
DBG_OBJ_SET_NUM ("allocation.y", this->allocation.y);
DBG_OBJ_SET_NUM ("allocation.width", this->allocation.width);
DBG_OBJ_SET_NUM ("allocation.ascent", this->allocation.ascent);
DBG_OBJ_SET_NUM ("allocation.descent", this->allocation.descent);
}
/*unsetFlags (NEEDS_RESIZE);*/
leaveSizeAllocate ();
DBG_OBJ_LEAVE ();
}
bool Widget::buttonPress (EventButton *event)
{
return buttonPressImpl (event);
}
bool Widget::buttonRelease (EventButton *event)
{
return buttonReleaseImpl (event);
}
bool Widget::motionNotify (EventMotion *event)
{
return motionNotifyImpl (event);
}
void Widget::enterNotify (EventCrossing *event)
{
enterNotifyImpl (event);
}
void Widget::leaveNotify (EventCrossing *event)
{
leaveNotifyImpl (event);
}
/**
* \brief Change the style of a widget.
*
* The old style is automatically unreferred, the new is referred. If this
* call causes the widget to change its size, dw::core::Widget::queueResize
* is called.
*/
void Widget::setStyle (style::Style *style)
{
bool sizeChanged;
if (widgetImgRenderer && this->style && this->style->backgroundImage)
this->style->backgroundImage->removeExternalImgRenderer
(widgetImgRenderer);
style->ref ();
if (this->style) {
sizeChanged = this->style->sizeDiffs (style);
this->style->unref ();
} else
sizeChanged = true;
this->style = style;
DBG_OBJ_ASSOC_CHILD (style);
if (style && style->backgroundImage) {
// Create instance of WidgetImgRenderer when needed. Until this
// widget is deleted, "widgetImgRenderer" will be kept, since it
// is not specific to the style, but only to this widget.
if (widgetImgRenderer == NULL)
widgetImgRenderer = std::make_unique< WidgetImgRenderer >( this ).release();
style->backgroundImage->putExternalImgRenderer( std::unique_ptr< WidgetImgRenderer >{ widgetImgRenderer } );
}
if (layout != NULL) {
layout->updateCursor ();
}
// After Layout::addWidget() (as toplevel widget) or Widget::setParent()
// (which also sets layout), changes of the style cannot be considered
// anymore. (Should print a warning?)
if (layout == NULL &&
StackingContextMgr::isEstablishingStackingContext (this)) {
stackingContextMgr = new StackingContextMgr (this);
DBG_OBJ_ASSOC_CHILD (stackingContextMgr);
stackingContextWidget = this;
}
if (sizeChanged)
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");
}
/**
* \brief Set the background "behind" the widget, if it is not the
* background of the parent widget, e.g. the background of a table
* row.
*/
void Widget::setBgColor (style::Color *bgColor)
{
this->bgColor = bgColor;
}
/**
* \brief Get the actual background of a widget.
*/
style::Color *Widget::getBgColor ()
{
Widget *widget = this;
while (widget != NULL) {
if (widget->style->backgroundColor)
return widget->style->backgroundColor;
if (widget->bgColor)
return widget->bgColor;
widget = widget->parent;
}
return layout->getBgColor ();
}
/**
* \brief Get the actual foreground color of a widget.
*/
style::Color *Widget::getFgColor ()
{
Widget *widget = this;
while (widget != NULL) {
if (widget->style->color)
return widget->style->color;
widget = widget->parent;
}
return NULL;
}
/**
* \brief Draw borders and background of a widget part, which allocation is
* given by (x, y, width, height) (widget coordinates).
*
* area is given in widget coordinates.
*/
void Widget::drawBox (View *view, style::Style *style, Rectangle *area,
int x, int y, int width, int height, bool inverse)
{
Rectangle canvasArea;
canvasArea.x = area->x + allocation.x;
canvasArea.y = area->y + allocation.y;
canvasArea.width = area->width;
canvasArea.height = area->height;
style::drawBorder (view, layout, &canvasArea,
allocation.x + x, allocation.y + y,
width, height, style, inverse);
// This method is used for inline elements, where the CSS 2 specification
// 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 here, in the "dillo_grows" repository).
int xPad, yPad, widthPad, heightPad;
getPaddingArea (&xPad, &yPad, &widthPad, &heightPad);
style::drawBackground
(view, layout, &canvasArea,
allocation.x + x + style->margin.left + style->borderWidth.left,
allocation.y + y + style->margin.top + style->borderWidth.top,
width - style->margin.left - style->borderWidth.left
- style->margin.right - style->borderWidth.right,
height - style->margin.top - style->borderWidth.top
- style->margin.bottom - style->borderWidth.bottom,
xPad, yPad, widthPad, heightPad, style, style->backgroundColor,
inverse, false);
}
/**
* \brief Draw borders and background of a widget.
*
* area is given in widget coordinates.
*
*/
void Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse)
{
Rectangle canvasArea;
canvasArea.x = area->x + allocation.x;
canvasArea.y = area->y + allocation.y;
canvasArea.width = area->width;
canvasArea.height = area->height;
int xMar, yMar, widthMar, heightMar;
getMarginArea (&xMar, &yMar, &widthMar, &heightMar);
style::drawBorder (view, layout, &canvasArea, xMar, yMar, widthMar,
heightMar, style, 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, bgColor, inverse, parent == NULL);
}
/*
* This function is used by some widgets, when they are selected (as a whole).
*
* \todo This could be accelerated by using clipping bitmaps. Two important
* issues:
*
* (i) There should always been a pixel in the upper-left corner of the
* *widget*, so probably two different clipping bitmaps have to be
* used (10/01 and 01/10).
*
* (ii) Should a new GC always be created?
*
* \bug Not implemented.
*/
void Widget::drawSelected (View *view, Rectangle *area)
{
}
void Widget::setButtonSensitive (bool buttonSensitive)
{
this->buttonSensitive = buttonSensitive;
buttonSensitiveSet = true;
}
/**
* \brief Get the widget at the root of the tree, this widget is part from.
*/
Widget *Widget::getTopLevel ()
{
Widget *widget = this;
while (widget->parent)
widget = widget->parent;
return widget;
}
/**
* \brief Get the level of the widget within the tree.
*
* The root widget has the level 0.
*/
int Widget::getLevel ()
{
Widget *widget = this;
int level = 0;
while (widget->parent) {
level++;
widget = widget->parent;
}
return level;
}
/**
* \brief Get the level of the widget within the tree, regarding 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.
*/
Widget *Widget::getNearestCommonAncestor (Widget *otherWidget)
{
Widget *widget1 = this, *widget2 = otherWidget;
int level1 = widget1->getLevel (), level2 = widget2->getLevel();
/* Get both widgets onto the same level.*/
while (level1 > level2) {
widget1 = widget1->parent;
level1--;
}
while (level2 > level1) {
widget2 = widget2->parent;
level2--;
}
/* Search upwards. */
while (widget1 != widget2) {
if (widget1->parent == NULL) {
MSG_WARN("widgets in different trees\n");
return NULL;
}
widget1 = widget1->parent;
widget2 = widget2->parent;
}
return widget1;
}
void Widget::scrollTo (HPosition hpos, VPosition vpos,
int x, int y, int width, int height)
{
layout->scrollTo (hpos, vpos,
x + allocation.x, y + allocation.y, width, height);
}
void Widget::getMarginArea (int *xMar, int *yMar, int *widthMar, int *heightMar)
{
*xMar = allocation.x + extraSpace.left;
*yMar = allocation.y + extraSpace.top;
*widthMar = allocation.width - (extraSpace.left + extraSpace.right);
*heightMar = getHeight () - (extraSpace.top + extraSpace.bottom);
}
void Widget::getBorderArea (int *xBor, int *yBor, int *widthBor, int *heightBor)
{
getMarginArea (xBor, yBor, widthBor, heightBor);
*xBor += style->margin.left;
*yBor += style->margin.top;
*widthBor -= style->margin.left + style->margin.right;
*heightBor -= style->margin.top + style->margin.bottom;
}
/**
* \brief Return the padding area (content plus padding).
*
* Used as "reference area" (ee comment of "style::drawBackground")
* for backgrounds.
*/
void Widget::getPaddingArea (int *xPad, int *yPad, int *widthPad,
int *heightPad)
{
getBorderArea (xPad, yPad, widthPad, heightPad);
*xPad += style->borderWidth.left;
*yPad += style->borderWidth.top;
*widthPad -= style->borderWidth.left + style->borderWidth.right;
*heightPad -= style->borderWidth.top + style->borderWidth.bottom;
}
void Widget::sizeRequestImpl (Requisition *requisition, int numPos,
Widget **references, int *x, int *y)
{
// Use the simple variant.
DBG_OBJ_ENTER0 ("resize", 0, "Widget::sizeRequestImpl");
sizeRequestSimpl (requisition);
DBG_OBJ_LEAVE ();
}
void Widget::sizeRequestSimpl (Requisition *requisition)
{
// Either variant should be implemented.
misc::notImplemented ("Widget::sizeRequestSimpl");
}
void Widget::getExtremesImpl (Extremes *extremes, int numPos,
Widget **references, int *x, int *y)
{
// Use the simple variant.
DBG_OBJ_ENTER0 ("resize", 0, "Widget::getExtremesImpl");
getExtremesSimpl (extremes);
DBG_OBJ_LEAVE ();
}
void Widget::getExtremesSimpl (Extremes *extremes)
{
// Either variant should be implemented.
misc::notImplemented ("Widget::getExtremesSimpl");
}
void Widget::sizeAllocateImpl (Allocation *allocation)
{
}
/**
* \brief The actual implementation for calculating
* dw::core::Widget::extraSpace.
*
* The implementation gets a clean value of
* dw::core::Widget::extraSpace, which is only corrected. To make sure
* all possible influences are considered, the implementation of the
* base class should be called, too.
*/
void Widget::calcExtraSpaceImpl (int numPos, Widget **references, int *x,
int *y)
{
}
void Widget::markSizeChange (int ref)
{
}
void Widget::markExtremesChange (int ref)
{
}
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 ();
}
/**
* Computes the content width available of a child widget.
*
* @param child The child widget of which the available width will be
* computed.
* @param forceValue If true, computes the width of the child with value
* "auto". Otherwise, it won't.
*
* @return The available width in pixels or -1.
*/
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");
/* We only compute when forceValue is true */
if (forceValue) {
width = std::max (getAvailWidth (true) - boxDiffWidth (), 0);
if (width != -1) {
/* Clamp to min-width and max-width if given */
int maxWidth = child->calcWidth (child->getStyle()->maxWidth,
-1, this, -1, false);
if (maxWidth != -1 && width > maxWidth)
width = maxWidth;
int minWidth = child->calcWidth (child->getStyle()->minWidth,
-1, this, -1, false);
if (minWidth != -1 && width < minWidth)
width = minWidth;
}
} 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_LEAVE_VAL ("%d", width);
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 = std::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 = std::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 =
std::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_LEAVE_VAL ("%d", height);
return height;
}
void Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition,
void (*splitHeightFun) (int, int*,
int*),
bool allowDecreaseWidth,
bool allowDecreaseHeight)
{
// Again, a suitable implementation for all widgets (perhaps).
DBG_OBJ_ENTER ("resize", 0, "correctRequisitionOfChild",
"%p, %d * (%d + %d), ..., %s, %s", child, requisition->width,
requisition->ascent, requisition->descent,
misc::boolToStr (allowDecreaseWidth),
misc::boolToStr (allowDecreaseHeight));
// See comment in Widget::getAvailWidthOfChild.
Widget *effContainer = child->quasiParent ? child->quasiParent :
(child->container ? child->container : child->parent);
if (effContainer == this) {
/* Try several passes before giving up */
for (int pass = 0; pass < 3; pass++) {
correctReqWidthOfChild (child, requisition, allowDecreaseWidth);
correctReqHeightOfChild (child, requisition, splitHeightFun,
allowDecreaseHeight);
bool changed = correctReqAspectRatio (pass, child, requisition,
allowDecreaseWidth, allowDecreaseHeight, splitHeightFun);
/* No need to repeat more passes */
if (!changed)
break;
}
} else {
DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container");
DBG_OBJ_MSG_START ();
effContainer->correctRequisitionOfChild (child, requisition,
splitHeightFun,
allowDecreaseWidth,
allowDecreaseHeight);
DBG_OBJ_MSG_END ();
}
DBG_OBJ_LEAVE_VAL ("%d * (%d + %d)", requisition->width, requisition->ascent,
requisition->descent);
}
/**
* Correct a child requisition aspect ratio if needed.
*
* After the parent widget corrects the requisition provided by the \param
* child, the preferred aspect ratio may no longer be the current ratio. This
* method tries to adjust the size of the requisition so the aspect ratio is the
* preferred aspect ratio of the child.
*
* It implements three passes: increase (0), decrease (1) or keep (2). In the
* increase pass, the size is increased to fill the aspect ratio. If after
* correcting the size, it is still not the preferred aspect ratio (maybe it
* breaks some other constraint), reducing the size will be attempted. If at the
* end, reducing the size doesn't fix the preferred aspect ratio, the size is
* kept as it is.
*
* It can be called from the parent or the child itself, as it doesn't read any
* information from the current widget.
*
* \return true if the requisition has been modified, false otherwise.
*/
bool Widget::correctReqAspectRatio (int pass, Widget *child, Requisition *requisition,
bool allowDecreaseWidth, bool allowDecreaseHeight,
void (*splitHeightFun) (int, int*, int*))
{
/* Only correct the requisition if both dimensions are set, otherwise is left
* to the child how to proceed. */
int wReq = requisition->width;
int hReq = requisition->ascent + requisition->descent;
/* Prevent division by 0 */
bool sizeSet = wReq > 0 && hReq > 0;
float ratio = child->ratio;
bool changed = false;
DEBUG_MSG(1, "Widget::correctReqAspectRatio() -- wReq=%d, hReq=%d, pass=%d\n",
wReq, hReq, pass);
DEBUG_MSG(1, "Widget::correctReqAspectRatio() -- border: w=%d, h=%d\n",
child->boxDiffWidth(), child->boxDiffHeight());
wReq -= child->boxDiffWidth();
hReq -= child->boxDiffHeight();
DEBUG_MSG(1, "Widget::correctReqAspectRatio() -- with border: wReq=%d, hReq=%d\n",
wReq, hReq);
DEBUG_MSG(1, "child=%s, preferred ratio=%f\n", child->getClassName(), ratio);
if (pass != PASS_KEEP && ratio > 0.0 && sizeSet) {
/* Compute the current ratio from the content box. */
float curRatio = (float) wReq / (float) hReq;
DEBUG_MSG(1, "curRatio=%f, preferred ratio=%f\n", curRatio, ratio);
if (curRatio < ratio) {
/* W is too small compared to H. Try to increase W or decrease H. */
if (pass == PASS_INCREASE) {
/* Increase w */
int w = (float) hReq * ratio;
DEBUG_MSG(1, "increase w: %d -> %d\n", wReq, w);
w += child->boxDiffWidth();
DEBUG_MSG(1, "increase w (with border): %d -> %d\n", wReq, w);
requisition->width = w;
changed = true;
} else if (pass == PASS_DECREASE) {
/* Decrease h */
if (allowDecreaseHeight) {
/* FIXME: This may lose cases where allowDecreaseHeight is false, and
* the requisition has increased the height first, but we could still
* reduce the corrected height above the original height, without
* making the requisition height smaller. */
int h = (float) wReq / ratio;
DEBUG_MSG(1, "decrease h: %d -> %d\n", hReq, h);
h += child->boxDiffHeight();
DEBUG_MSG(1, "decrease h (with border): %d -> %d\n", hReq, h);
splitHeightFun (h, &requisition->ascent, &requisition->descent);
changed = true;
}
}
} else if (curRatio > ratio) {
/* W is too big compared to H. Try to decrease W or increase H. */
if (pass == PASS_INCREASE) {
/* Increase h */
int h = (float) wReq / ratio;
DEBUG_MSG(1, "increase h: %d -> %d\n", hReq, h);
h += child->boxDiffHeight();
DEBUG_MSG(1, "increase h (width border): %d -> %d\n", hReq, h);
splitHeightFun (h, &requisition->ascent, &requisition->descent);
changed = true;
} else if (pass == PASS_DECREASE) {
/* Decrease w */
if (allowDecreaseWidth) {
/* FIXME: This may lose cases where allowDecreaseWidth is false, and
* the requisition has increased the width first, but we could still
* reduce the corrected width above the original width, without
* making the requisition width smaller. */
int w = (float) hReq * ratio;
DEBUG_MSG(1, "decrease w: %d -> %d\n", wReq, w);
w += child->boxDiffWidth();
DEBUG_MSG(1, "decrease w (width border): %d -> %d\n", wReq, w);
requisition->width = w;
changed = true;
}
}
} else {
/* Current ratio is the preferred one. */
}
}
DEBUG_MSG(1, "Widget::correctReqAspectRatio() -- output: wReq=%d, hReq=%d, changed=%d\n",
requisition->width, requisition->ascent + requisition->descent, changed);
return changed;
}
/** Correct a child requisition to fit the parent.
*
* The \param requisition is adjusted in width so it fits in the current widget
* (parent).
*/
void Widget::correctReqWidthOfChild (Widget *child, Requisition *requisition,
bool allowDecreaseWidth)
{
DBG_OBJ_ENTER ("resize", 0, "correctReqWidthOfChild",
"%p, %d * (%d + %d), %s",
child, requisition->width, requisition->ascent,
requisition->descent, misc::boolToStr (allowDecreaseWidth));
assert (this == child->quasiParent || this == child->container);
int limitMinWidth = child->getMinWidth (NULL, true);
if (!allowDecreaseWidth && limitMinWidth < requisition->width)
limitMinWidth = requisition->width;
child->calcFinalWidth (child->getStyle(), -1, this, limitMinWidth, true,
&requisition->width);
DBG_OBJ_LEAVE_VAL ("%d * (%d + %d)", requisition->width, requisition->ascent,
requisition->descent);
}
void Widget::correctReqHeightOfChild (Widget *child, Requisition *requisition,
void (*splitHeightFun) (int, int*, int*),
bool allowDecreaseHeight)
{
// TODO Correct height by extremes? (Height extremes?)
assert (this == child->quasiParent || this == child->container);
DBG_OBJ_ENTER ("resize", 0, "correctReqHeightOfChild",
"%p, %d * (%d + %d), ..., %s", child, requisition->width,
requisition->ascent, requisition->descent,
misc::boolToStr (allowDecreaseHeight));
int height = child->calcHeight (child->getStyle()->height, true, -1, this,
false);
adjustHeight (&height, allowDecreaseHeight, requisition->ascent,
requisition->descent);
int minHeight = child->calcHeight (child->getStyle()->minHeight, true, -1,
this, false);
int maxHeight = child->calcHeight (child->getStyle()->maxHeight, true, -1,
this, false);
if (minHeight != -1 && maxHeight != -1) {
/* Prefer the maximum size for pathological cases (min > max) */
if (maxHeight < minHeight)
maxHeight = minHeight;
}
adjustHeight (&minHeight, allowDecreaseHeight, requisition->ascent,
requisition->descent);
adjustHeight (&maxHeight, allowDecreaseHeight, requisition->ascent,
requisition->descent);
// 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_LEAVE_VAL ("%d * (%d + %d)", requisition->width, requisition->ascent,
requisition->descent);
}
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_LEAVE_VAL ("%d / %d", extremes->minWidth, extremes->maxWidth);
}
/**
* \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()
{
}
bool Widget::isBlockLevel ()
{
// Most widgets are not block-level.
return false;
}
bool Widget::isPossibleContainer ()
{
// In most (all?) cases identical to:
return isBlockLevel ();
}
bool Widget::buttonPressImpl (EventButton *event)
{
return false;
}
bool Widget::buttonReleaseImpl (EventButton *event)
{
return false;
}
bool Widget::motionNotifyImpl (EventMotion *event)
{
return false;
}
void Widget::enterNotifyImpl (EventCrossing *)
{
style::Tooltip *tooltip = getStyle()->x_tooltip;
if (tooltip)
tooltip->onEnter();
}
void Widget::leaveNotifyImpl (EventCrossing *)
{
style::Tooltip *tooltip = getStyle()->x_tooltip;
if (tooltip)
tooltip->onLeave();
}
void Widget::removeChild (Widget *child)
{
// Should be implemented.
misc::notImplemented ("Widget::removeChild");
}
// ----------------------------------------------------------------------
void splitHeightPreserveAscent (int height, int *ascent, int *descent)
{
DBG_OBJ_ENTER_S ("resize", 1, "splitHeightPreserveAscent", "%d, %d, %d",
height, *ascent, *descent);
*descent = height - *ascent;
if (*descent < 0) {
*descent = 0;
*ascent = height;
}
DBG_OBJ_LEAVE_VAL_S ("%d, %d", *ascent, *descent);
}
void splitHeightPreserveDescent (int height, int *ascent, int *descent)
{
DBG_OBJ_ENTER_S ("resize", 1, "splitHeightPreserveDescent", "%d, %d, %d",
height, *ascent, *descent);
*ascent = height - *descent;
if (*ascent < 0) {
*ascent = 0;
*descent = height;
}
DBG_OBJ_LEAVE_VAL_S ("%d, %d", *ascent, *descent);
}
} // namespace core
} // namespace dw