Files
flenser/dw/layout.cc
ADAM David Alan Martin f283391b46 Get some of the last remaining C files into C++
They still need to be fixed up, but they now build with C++.
2025-08-23 02:09:47 -04:00

1329 lines
39 KiB
C++

/*
* Dillo Widget
*
* Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
* Copyright 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/>.
*/
#include "core.hh"
#include "dlib/dlib.hh"
#include "../lout/msg.h"
#include "../lout/debug.hh"
#include "../lout/misc.hh"
using namespace lout;
using namespace lout::container;
using namespace lout::object;
namespace dw {
namespace core {
bool Layout::LayoutImgRenderer::readyToDraw ()
{
return true;
}
void Layout::LayoutImgRenderer::getBgArea (int *x, int *y, int *width,
int *height)
{
// TODO Actually not padding area, but visible area?
getRefArea (x, y, width, height);
}
void Layout::LayoutImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef,
int *heightRef)
{
*xRef = 0;
*yRef = 0;
*widthRef = std::max (layout->viewportWidth
- (layout->canvasHeightGreater ?
layout->vScrollbarThickness : 0),
layout->canvasWidth);
*heightRef = std::max (layout->viewportHeight
- layout->hScrollbarThickness,
layout->canvasAscent + layout->canvasDescent);
}
style::StyleImage *Layout::LayoutImgRenderer::getBackgroundImage ()
{
return layout->bgImage;
}
style::BackgroundRepeat Layout::LayoutImgRenderer::getBackgroundRepeat ()
{
return layout->bgRepeat;
}
style::BackgroundAttachment
Layout::LayoutImgRenderer::getBackgroundAttachment ()
{
return layout->bgAttachment;
}
style::Length Layout::LayoutImgRenderer::getBackgroundPositionX ()
{
return layout->bgPositionX;
}
style::Length Layout::LayoutImgRenderer::getBackgroundPositionY ()
{
return layout->bgPositionY;
}
void Layout::LayoutImgRenderer::draw (int x, int y, int width, int height)
{
layout->queueDraw (x, y, width, height);
}
// ----------------------------------------------------------------------
void Layout::Receiver::resizeQueued (bool extremesChanged)
{
}
void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent)
{
}
// ----------------------------------------------------------------------
bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver,
int signalNo, int argc,
lout::object::Object **argv)
{
Receiver *layoutReceiver = (Receiver*)receiver;
switch (signalNo) {
case CANVAS_SIZE_CHANGED:
layoutReceiver->canvasSizeChanged (((Integer*)argv[0])->getValue (),
((Integer*)argv[1])->getValue (),
((Integer*)argv[2])->getValue ());
break;
case RESIZE_QUEUED:
layoutReceiver->resizeQueued (((Boolean*)argv[0])->getValue ());
break;
default:
misc::assertNotReached ();
}
return false;
}
void Layout::Emitter::emitResizeQueued (bool extremesChanged)
{
Boolean ec (extremesChanged);
Object *argv[1] = { &ec };
emitVoid (RESIZE_QUEUED, 1, argv);
}
void Layout::Emitter::emitCanvasSizeChanged (int width,
int ascent, int descent)
{
Integer w (width), a (ascent), d (descent);
Object *argv[3] = { &w, &a, &d };
emitVoid (CANVAS_SIZE_CHANGED, 3, argv);
}
// ----------------------------------------------------------------------
bool Layout::LinkReceiver::enter (Widget *widget, int link, int img,
int x, int y)
{
return false;
}
bool Layout::LinkReceiver::press (Widget *widget, int link, int img,
int x, int y, EventButton *event)
{
return false;
}
bool Layout::LinkReceiver::release (Widget *widget, int link, int img,
int x, int y, EventButton *event)
{
return false;
}
bool Layout::LinkReceiver::click (Widget *widget, int link, int img,
int x, int y, EventButton *event)
{
return false;
}
// ----------------------------------------------------------------------
bool Layout::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver,
int signalNo, int argc,
lout::object::Object **argv)
{
LinkReceiver *linkReceiver = (LinkReceiver*)receiver;
switch (signalNo) {
case ENTER:
return linkReceiver->enter ((Widget*)argv[0],
((Integer*)argv[1])->getValue (),
((Integer*)argv[2])->getValue (),
((Integer*)argv[3])->getValue (),
((Integer*)argv[4])->getValue ());
case PRESS:
return linkReceiver->press ((Widget*)argv[0],
((Integer*)argv[1])->getValue (),
((Integer*)argv[2])->getValue (),
((Integer*)argv[3])->getValue (),
((Integer*)argv[4])->getValue (),
(EventButton*)argv[5]);
case RELEASE:
return linkReceiver->release ((Widget*)argv[0],
((Integer*)argv[1])->getValue (),
((Integer*)argv[2])->getValue (),
((Integer*)argv[3])->getValue (),
((Integer*)argv[4])->getValue (),
(EventButton*)argv[5]);
case CLICK:
return linkReceiver->click ((Widget*)argv[0],
((Integer*)argv[1])->getValue (),
((Integer*)argv[2])->getValue (),
((Integer*)argv[3])->getValue (),
((Integer*)argv[4])->getValue (),
(EventButton*)argv[5]);
default:
misc::assertNotReached ();
}
return false;
}
bool Layout::LinkEmitter::emitEnter (Widget *widget, int link, int img,
int x, int y)
{
Integer ilink (link), iimg (img), ix (x), iy (y);
Object *argv[5] = { widget, &ilink, &iimg, &ix, &iy };
return emitBool (ENTER, 5, argv);
}
bool Layout::LinkEmitter::emitPress (Widget *widget, int link, int img,
int x, int y, EventButton *event)
{
Integer ilink (link), iimg (img), ix (x), iy (y);
Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
return emitBool (PRESS, 6, argv);
}
bool Layout::LinkEmitter::emitRelease (Widget *widget, int link, int img,
int x, int y, EventButton *event)
{
Integer ilink (link), iimg (img), ix (x), iy (y);
Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
return emitBool (RELEASE, 6, argv);
}
bool Layout::LinkEmitter::emitClick (Widget *widget, int link, int img,
int x, int y, EventButton *event)
{
Integer ilink (link), iimg (img), ix (x), iy (y);
Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
return emitBool (CLICK, 6, argv);
}
// ---------------------------------------------------------------------
Layout::Layout (std::unique_ptr< Platform > platform, bool limit)
{
this->platform = std::move( platform );
view = NULL;
topLevel = NULL;
widgetAtPoint = NULL;
DBG_OBJ_CREATE ("dw::core::Layout");
bgColor = NULL;
bgImage = NULL;
cursor = style::CURSOR_DEFAULT;
canvasWidth = canvasAscent = canvasDescent = 0;
usesViewport = false;
drawAfterScrollReq = false;
scrollX = scrollY = 0;
viewportWidth = viewportHeight = 0;
hScrollbarThickness = vScrollbarThickness = 0;
DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth);
DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight);
DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness);
DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness);
scrollIdleId = -1;
scrollIdleNotInterrupted = false;
resizeIdleId = -1;
textZone = new misc::ZoneAllocator (16 * 1024);
DBG_OBJ_ASSOC_CHILD (&findtextState);
DBG_OBJ_ASSOC_CHILD (&selectionState);
this->platform->setLayout (this);
selectionState.setLayout(this);
queueResizeCounter = sizeAllocateCounter = sizeRequestCounter =
getExtremesCounter = 0;
layoutImgRenderer = NULL;
resizeIdleCounter = queueResizeCounter = sizeAllocateCounter
= sizeRequestCounter = getExtremesCounter = resizeCounter = 0;
resizeLimit = limit;
}
Layout::~Layout ()
{
widgetAtPoint = NULL;
if (layoutImgRenderer) {
if (bgImage)
bgImage->removeExternalImgRenderer (layoutImgRenderer);
delete layoutImgRenderer;
}
if (scrollIdleId != -1)
platform->removeIdle (scrollIdleId);
if (resizeIdleId != -1)
platform->removeIdle (resizeIdleId);
if (bgColor)
bgColor->unref ();
if (bgImage)
bgImage->unref ();
if (topLevel) {
detachWidget (topLevel.get());
topLevel.reset();
}
delete view;
delete textZone;
DBG_OBJ_DELETE ();
}
void Layout::detachWidget (Widget *widget)
{
// Called form ~Layout. Sometimes, the widgets (not only the toplevel widget)
// do some stuff after the layout has been deleted, so *all* widgets have to
// be detached, and check "layout != NULL" at relevant points.
// Could be replaced by a virtual method in Widget, like getWidgetAtPoint,
// if performance were really a problem.
widget->layout = NULL;
Iterator *it =
widget->iterator ((Content::Type)
(Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT),
false);
while (it->next ())
detachWidget (it->getContent()->widget);
it->unref ();
}
void Layout::addWidget (std::unique_ptr< Widget > widget)
{
if (topLevel) {
MSG_WARN("widget already set\n");
return;
}
// The toplevel widget always establishes a stacking context. It could
// already be set in Widget::setStyle().
if (widget->stackingContextMgr == NULL && IMPL_POS) {
widget->stackingContextMgr = new StackingContextMgr (widget.get());
DBG_OBJ_ASSOC (widget.get(), widget->stackingContextMgr);
widget->stackingContextWidget = widget.get();
}
Widget *widget_p= widget.get();
topLevel = std::move( widget );
widget_p->layout = this;
widget_p->container = NULL;
DBG_OBJ_SET_PTR_O (widget_p, "container", widget_p->container);
queueResizeList.clear ();
widget_p->notifySetAsTopLevel ();
findtextState.setWidget (widget_p);
canvasHeightGreater = false;
DBG_OBJ_SET_SYM ("canvasHeightGreater",
canvasHeightGreater ? "true" : "false");
// Do not directly call Layout::queueResize(), but
// Widget::queueResize(), so that all flags are set properly,
// queueResizeList is filled, etc.
topLevel->queueResize (-1, false);
}
void Layout::removeWidget ()
{
/**
* \bug Some more attributes must be reset here.
*/
topLevel = NULL;
queueResizeList.clear ();
widgetAtPoint = NULL;
canvasWidth = canvasAscent = canvasDescent = 0;
scrollX = scrollY = 0;
view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
if (view->usesViewport ())
view->setViewportSize (viewportWidth, viewportHeight, 0, 0);
view->queueDrawTotal ();
setAnchor (std::nullopt);
updateAnchor ();
emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent);
findtextState.setWidget (NULL);
selectionState.reset ();
updateCursor ();
}
void Layout::setWidget (std::unique_ptr< Widget > widget)
{
DBG_OBJ_ASSOC_CHILD (widget.get());
widgetAtPoint = NULL;
if (topLevel) {
topLevel.reset();
}
textZone->zoneFree ();
addWidget (std::move( widget ));
updateCursor ();
/* Reset the resizeCounter when we change the top level widget, as we are
* changing to another page */
resizeCounter = 0;
}
/**
* \brief Attach a view to the layout.
*
* It will become a child of the layout,
* and so it will be destroyed, when the layout will be destroyed.
*/
void Layout::attachView (View *view)
{
if (this->view)
MSG_ERR("attachView: Multiple views for layout!\n");
DBG_OBJ_ASSOC_CHILD (view);
this->view = view;
platform->attachView (view);
/*
* The layout of the view is set later, first, we "project" the current
* state of the layout into the new view. A view must handle this without
* a layout. See also at the end of this function.
*/
if (bgColor)
view->setBgColor (bgColor);
view->setCursor (cursor);
view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
if (view->usesViewport ()) {
if (usesViewport) {
view->scrollTo (scrollX, scrollY);
view->setViewportSize (viewportWidth, viewportHeight,
hScrollbarThickness, vScrollbarThickness);
hScrollbarThickness = std::max (hScrollbarThickness,
view->getHScrollbarThickness ());
vScrollbarThickness = std::max (vScrollbarThickness,
view->getVScrollbarThickness ());
}
else {
usesViewport = true;
scrollX = scrollY = 0;
viewportWidth = viewportHeight = 100; // random values
hScrollbarThickness = view->getHScrollbarThickness ();
vScrollbarThickness = view->getVScrollbarThickness ();
}
DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth);
DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight);
DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness);
DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness);
}
/*
* This is the last call within this function, so that it is safe for
* the implementation of dw::core::View::setLayout, to call methods
* of dw::core::Layout.
*/
view->setLayout (this);
}
void Layout::detachView (View *view)
{
if (this->view != view) {
MSG_ERR("detachView: this->view: %p view %p\n",
(void *) this->view, (void *) view);
}
view->setLayout (NULL);
platform->detachView (view);
this->view = NULL;
/**
* \todo Actually, viewportMarkerWidthDiff and
* viewportMarkerHeightDiff have to be recalculated here, since the
* effective (i.e. maximal) values may change, after the view has been
* detached. Same applies to the usage of viewports.
*/
}
void Layout::scroll(ScrollCommand cmd)
{
if (view->usesViewport ())
view->scroll(cmd);
}
/**
* \brief Scrolls all viewports, so that the region [x, y, width, height]
* is seen, according to hpos and vpos.
*/
void Layout::scrollTo (HPosition hpos, VPosition vpos,
int x, int y, int width, int height)
{
scrollTo0 (hpos, vpos, x, y, width, height, true);
}
void Layout::scrollTo0 (HPosition hpos, VPosition vpos,
int x, int y, int width, int height,
bool scrollingInterrupted)
{
if (usesViewport) {
_MSG("scrollTo (%d, %d, %s)\n",
x, y, scrollingInterrupted ? "true" : "false");
scrollTargetHpos = hpos;
scrollTargetVpos = vpos;
scrollTargetX = x;
scrollTargetY = y;
scrollTargetWidth = width;
scrollTargetHeight = height;
if (scrollIdleId == -1) {
scrollIdleId = platform->addIdle (&Layout::scrollIdle);
scrollIdleNotInterrupted = true;
}
scrollIdleNotInterrupted =
scrollIdleNotInterrupted || !scrollingInterrupted;
}
}
void Layout::scrollIdle ()
{
bool xChanged = true;
switch (scrollTargetHpos) {
case HPOS_LEFT:
scrollX = scrollTargetX;
break;
case HPOS_CENTER:
scrollX =
scrollTargetX
- (viewportWidth - currVScrollbarThickness() - scrollTargetWidth) / 2;
break;
case HPOS_RIGHT:
scrollX =
scrollTargetX
- (viewportWidth - currVScrollbarThickness() - scrollTargetWidth);
break;
case HPOS_INTO_VIEW:
xChanged = calcScrollInto (scrollTargetX, scrollTargetWidth, &scrollX,
viewportWidth - currVScrollbarThickness());
break;
case HPOS_NO_CHANGE:
xChanged = false;
break;
}
bool yChanged = true;
switch (scrollTargetVpos) {
case VPOS_TOP:
scrollY = scrollTargetY;
break;
case VPOS_CENTER:
scrollY =
scrollTargetY
- (viewportHeight - currHScrollbarThickness() - scrollTargetHeight)/2;
break;
case VPOS_BOTTOM:
scrollY =
scrollTargetY
- (viewportHeight - currHScrollbarThickness() - scrollTargetHeight);
break;
case VPOS_INTO_VIEW:
yChanged = calcScrollInto (scrollTargetY, scrollTargetHeight, &scrollY,
viewportHeight - currHScrollbarThickness());
break;
case VPOS_NO_CHANGE:
yChanged = false;
break;
}
if (xChanged || yChanged) {
adjustScrollPos ();
view->scrollTo (scrollX, scrollY);
if (drawAfterScrollReq) {
drawAfterScrollReq = false;
view->queueDrawTotal ();
}
}
scrollIdleId = -1;
}
void Layout::adjustScrollPos ()
{
scrollX = std::min (scrollX,
canvasWidth - (viewportWidth - vScrollbarThickness));
scrollX = std::max (scrollX, 0);
scrollY = std::min (scrollY,
canvasAscent + canvasDescent - (viewportHeight - hScrollbarThickness));
scrollY = std::max (scrollY, 0);
_MSG("adjustScrollPos: scrollX=%d scrollY=%d\n", scrollX, scrollY);
}
bool Layout::calcScrollInto (int requestedValue, int requestedSize,
int *value, int viewportSize)
{
if (requestedSize > viewportSize) {
// The viewport size is smaller than the size of the region which will
// be shown. If the region is already visible, do not change the
// position. Otherwise, show the left/upper border, this is most likely
// what is needed.
if (*value >= requestedValue &&
*value + viewportSize < requestedValue + requestedSize)
return false;
else
requestedSize = viewportSize;
}
if (requestedValue < *value) {
*value = requestedValue;
return true;
} else if (requestedValue + requestedSize > *value + viewportSize) {
*value = requestedValue - viewportSize + requestedSize;
return true;
} else
return false;
}
void Layout::draw (View *view, Rectangle *area)
{
DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d",
area->x, area->y, area->width, area->height);
Rectangle widgetArea, intersection, widgetDrawArea;
// First of all, draw background image. (Unlike background *color*,
// this is not a feature of the views.)
if (bgImage != NULL && bgImage->getImgbufSrc() != NULL)
style::drawBackgroundImage (view, bgImage, bgRepeat, bgAttachment,
bgPositionX, bgPositionY,
area->x, area->y, area->width,
area->height, 0, 0,
// Reference area: maximum of canvas size and
// viewport size.
std::max (viewportWidth
- (canvasHeightGreater ?
vScrollbarThickness : 0),
canvasWidth),
std::max (viewportHeight
- hScrollbarThickness,
canvasAscent + canvasDescent));
if (scrollIdleId != -1) {
/* scroll is pending, defer draw until after scrollIdle() */
drawAfterScrollReq = true;
} else if (topLevel) {
/* Draw the top level widget. */
widgetArea.x = topLevel->allocation.x;
widgetArea.y = topLevel->allocation.y;
widgetArea.width = topLevel->allocation.width;
widgetArea.height = topLevel->getHeight ();
if (area->intersectsWith (&widgetArea, &intersection)) {
view->startDrawing (&intersection);
/* Intersection in widget coordinates. */
widgetDrawArea.x = intersection.x - topLevel->allocation.x;
widgetDrawArea.y = intersection.y - topLevel->allocation.y;
widgetDrawArea.width = intersection.width;
widgetDrawArea.height = intersection.height;
DrawingContext context (&widgetArea);
topLevel->draw (view, &widgetDrawArea, &context);
view->finishDrawing (&intersection);
}
}
DBG_OBJ_LEAVE ();
}
int Layout::currHScrollbarThickness()
{
return (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;
}
int Layout::currVScrollbarThickness()
{
return (canvasAscent + canvasDescent > viewportHeight) ?
vScrollbarThickness : 0;
}
/**
* Sets the anchor to scroll to.
*/
void Layout::setAnchor (const std::optional< std::string_view > anchor)
{
_MSG("setAnchor (%s)\n", anchor.value_or("").string().c_str());
requestedAnchor = anchor;
updateAnchor ();
}
/**
* Used, when the widget is not allocated yet.
*/
std::optional< std::string >
Layout::addAnchor (Widget *widget, const std::string &name)
{
return addAnchor (widget, name, -1);
}
std::optional< std::string >
Layout::addAnchor (Widget *widget, const std::string &name, int y)
{
if (anchorsTable.contains (name))
return std::nullopt;
else {
auto anchor = std::make_unique< Anchor >();
std::string rv= anchor->name= name;
anchor->widget = widget;
anchor->y = y;
anchorsTable[ name ]= std::move( anchor );
updateAnchor ();
return rv;
}
}
void Layout::changeAnchor (Widget *widget, const std::string &name, int y)
{
Anchor *const anchor = anchorsTable.at( name ).get();
assert (anchor);
assert (anchor->widget == widget);
anchor->y = y;
updateAnchor ();
}
void Layout::removeAnchor (Widget *widget, const std::string &name)
{
anchorsTable.erase( name );
}
void Layout::updateAnchor ()
{
Anchor *anchor= nullptr;
if (requestedAnchor and anchorsTable.contains( requestedAnchor.value() ) )
{
anchor= anchorsTable[ requestedAnchor.value() ].get();
}
if (anchor == NULL) {
/** \todo Copy comment from old docs. */
if (scrollIdleId != -1 && !scrollIdleNotInterrupted) {
platform->removeIdle (scrollIdleId);
scrollIdleId = -1;
}
} else
if (anchor->y != -1)
scrollTo0 (HPOS_NO_CHANGE, VPOS_TOP, 0, anchor->y, 0, 0, false);
}
void Layout::setCursor (style::Cursor cursor)
{
if (cursor != this->cursor) {
this->cursor = cursor;
view->setCursor (cursor);
}
}
void Layout::updateCursor ()
{
if (widgetAtPoint && widgetAtPoint->style)
setCursor (widgetAtPoint->style->cursor);
else
setCursor (style::CURSOR_DEFAULT);
}
void Layout::setBgColor (style::Color *color)
{
color->ref ();
if (bgColor)
bgColor->unref ();
bgColor = color;
if (view)
view->setBgColor (bgColor);
}
void Layout::setBgImage (style::StyleImage *bgImage,
style::BackgroundRepeat bgRepeat,
style::BackgroundAttachment bgAttachment,
style::Length bgPositionX, style::Length bgPositionY)
{
if (layoutImgRenderer && this->bgImage)
this->bgImage->removeExternalImgRenderer (layoutImgRenderer);
if (bgImage)
bgImage->ref ();
if (this->bgImage)
this->bgImage->unref ();
this->bgImage = bgImage;
this->bgRepeat = bgRepeat;
this->bgAttachment = bgAttachment;
this->bgPositionX = bgPositionX;
this->bgPositionY = bgPositionY;
if (bgImage) {
// Create instance of LayoutImgRenderer when needed. Until this
// layout is deleted, "layoutImgRenderer" will be kept, since it
// is not specific to the style, but only to this layout.
if (layoutImgRenderer == NULL)
layoutImgRenderer = std::make_unique< LayoutImgRenderer >( this ).release();
bgImage->putExternalImgRenderer( std::unique_ptr< LayoutImgRenderer >{ layoutImgRenderer } );
}
}
void Layout::resizeIdle ()
{
DBG_OBJ_ENTER0 ("resize", 0, "resizeIdle");
enterResizeIdle ();
// There are two commits, 2863:b749629fbfc9 and 4645:ab70f9ce4353, the second
// reverting the former. Interrestingly, the second fixes a bug. However, it
// should still examined what happens here, and what happens the other calls
// to Layout::resizeIdle() which should be still in the queue. (See
// Layout::queueResize(), where resizeIdleId is indeed checked.)
for (int i = 0; resizeIdleId != -1; i++) {
/* Prevent infinite resize loop, if we reach this point it is very likely
* there is a bug in the layouting process */
if (resizeLimit && resizeCounter >= 1000) {
MSG_ERR("Emergency layout stop after %d iterations\n", resizeCounter);
MSG_ERR("Please file a bug report with the complete console output\n");
resizeIdleId = -1;
break;
}
/* Only allow 100 iterations before returning to redraw the screen. */
if (i >= 100) {
MSG_WARN("Stopping layout loop after %d iterations\n", resizeCounter);
break;
}
resizeCounter++;
for( auto &widget: queueResizeList ) {
if (widget->resizeQueued ()) {
widget->setFlags (Widget::NEEDS_RESIZE);
widget->unsetFlags (Widget::RESIZE_QUEUED);
}
if (widget->allocateQueued ()) {
widget->setFlags (Widget::NEEDS_ALLOCATE);
widget->unsetFlags (Widget::ALLOCATE_QUEUED);
}
if (widget->extremesQueued ()) {
widget->setFlags (Widget::EXTREMES_CHANGED);
widget->unsetFlags (Widget::EXTREMES_QUEUED);
}
}
queueResizeList.clear ();
// Reset here, since below, queueResize() may be called again.
resizeIdleId = -1;
// If this method is triggered by a viewport change, we can save
// time when the toplevel widget is not affected (as for a toplevel
// image resource).
if (topLevel &&
(topLevel->needsResize () || topLevel->needsAllocate ())) {
Requisition requisition;
Allocation allocation;
topLevel->sizeRequest (&requisition);
DBG_OBJ_MSGF ("resize", 1, "toplevel size: %d * (%d + %d)",
requisition.width, requisition.ascent,
requisition.descent);
// This method is triggered by Widget::queueResize, which will,
// in any case, set NEEDS_ALLOCATE (indirectly, as ALLOCATE_QUEUED).
// This assertion helps to find inconsistencies. (Cases where
// this method is triggered by a viewport change, but the
// toplevel widget is not affected, are filtered out some lines
// above: "if (topLevel && topLevel->needsResize ())".)
assert (topLevel->needsAllocate ());
allocation.x = allocation.y = 0;
if (usesViewport && view->getScrollbarOnLeft())
allocation.x += currVScrollbarThickness();
allocation.width = requisition.width;
allocation.ascent = requisition.ascent;
allocation.descent = requisition.descent;
topLevel->sizeAllocate (&allocation);
canvasWidth = requisition.width;
canvasAscent = requisition.ascent;
canvasDescent = requisition.descent;
emitter.emitCanvasSizeChanged (canvasWidth,
canvasAscent, canvasDescent);
// Tell the view about the new world size.
view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
if (usesViewport) {
int currHThickness = currHScrollbarThickness();
int currVThickness = currVScrollbarThickness();
if (!canvasHeightGreater &&
canvasAscent + canvasDescent > viewportHeight - currHThickness) {
canvasHeightGreater = true;
DBG_OBJ_SET_SYM ("canvasHeightGreater",
canvasHeightGreater ? "true" : "false");
containerSizeChanged ();
}
// Set viewport sizes.
view->setViewportSize (viewportWidth, viewportHeight,
currHThickness, currVThickness);
}
// views are redrawn via Widget::resizeDrawImpl ()
}
}
updateAnchor ();
DBG_OBJ_MSGF ("resize", 1,
"after resizeIdle: resizeIdleId = %d", resizeIdleId);
DBG_OBJ_LEAVE ();
leaveResizeIdle ();
}
void Layout::queueDraw (int x, int y, int width, int height)
{
Rectangle area;
area.x = x;
area.y = y;
area.width = width;
area.height = height;
if (area.isEmpty ()) return;
view->queueDraw (&area);
}
void Layout::queueDrawExcept (int x, int y, int width, int height,
int ex, int ey, int ewidth, int eheight) {
if (x == ex && y == ey && width == ewidth && height == eheight)
return;
// queueDraw() the four rectangles within rectangle (x, y, width, height)
// around rectangle (ex, ey, ewidth, eheight).
// Some or all of these may be empty.
// upper left corner of the intersection rectangle
int ix1 = std::max (x, ex);
int iy1 = std::max (y, ey);
// lower right corner of the intersection rectangle
int ix2 = std::min (x + width, ex + ewidth);
int iy2 = std::min (y + height, ey + eheight);
queueDraw (x, y, width, iy1 - y);
queueDraw (x, iy2, width, y + height - iy2);
queueDraw (x, iy1, ix1 - x, iy2 - iy1);
queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1);
}
void Layout::queueResize (bool extremesChanged)
{
DBG_OBJ_ENTER ("resize", 0, "queueResize", "%s",
extremesChanged ? "true" : "false");
if (resizeIdleId == -1) {
view->cancelQueueDraw ();
resizeIdleId = platform->addIdle (&Layout::resizeIdle);
DBG_OBJ_MSGF ("resize", 1, "setting resizeIdleId = %d", resizeIdleId);
}
emitter.emitResizeQueued (extremesChanged);
DBG_OBJ_LEAVE ();
}
// Views
bool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed,
int x, int y, ButtonState state, int button)
{
EventButton event;
moveToWidgetAtPoint (x, y, state);
event.xCanvas = x;
event.yCanvas = y;
event.state = state;
event.button = button;
event.numPressed = numPressed;
return processMouseEvent (&event, type);
}
/**
* \brief This function is called by a view, to delegate a motion notify
* event.
*
* Arguments are similar to dw::core::Layout::buttonPress.
*/
bool Layout::motionNotify (View *view, int x, int y, ButtonState state)
{
EventButton event;
moveToWidgetAtPoint (x, y, state);
event.xCanvas = x;
event.yCanvas = y;
event.state = state;
return processMouseEvent (&event, MOTION_NOTIFY);
}
/**
* \brief This function is called by a view, to delegate a enter notify event.
*
* Arguments are similar to dw::core::Layout::buttonPress.
*/
void Layout::enterNotify (View *view, int x, int y, ButtonState state)
{
Widget *lastWidget;
EventCrossing event;
lastWidget = widgetAtPoint;
moveToWidgetAtPoint (x, y, state);
if (widgetAtPoint) {
event.state = state;
event.lastWidget = lastWidget;
event.currentWidget = widgetAtPoint;
widgetAtPoint->enterNotify (&event);
}
}
/**
* \brief This function is called by a view, to delegate a leave notify event.
*
* Arguments are similar to dw::core::Layout::buttonPress.
*/
void Layout::leaveNotify (View *view, ButtonState state)
{
#if 0
Widget *lastWidget;
EventCrossing event;
lastWidget = widgetAtPoint;
moveOutOfView (state);
if (lastWidget) {
event.state = state;
event.lastWidget = lastWidget;
event.currentWidget = widgetAtPoint;
lastWidget->leaveNotify (&event);
}
#else
moveOutOfView (state);
#endif
}
/*
* Return the widget at position (x, y). Return NULL, if there is no widget.
*/
Widget *Layout::getWidgetAtPoint (int x, int y)
{
DBG_OBJ_ENTER ("events", 0, "getWidgetAtPoint", "%d, %d", x, y);
Widget *widget;
if (topLevel && topLevel->wasAllocated ()) {
GettingWidgetAtPointContext context;
widget = topLevel->getWidgetAtPoint (x, y, &context);
} else
widget = NULL;
DBG_OBJ_MSGF ("events", 0, "=> %p", widget);
DBG_OBJ_LEAVE ();
return widget;
}
/*
* Emit the necessary crossing events, when the mouse pointer has moved to
* the given widget (by mouse or scrolling).
*/
void Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state)
{
DBG_OBJ_ENTER ("events", 0, "moveToWidget", "%p, %d",
newWidgetAtPoint, state);
Widget *ancestor, *w;
Widget **track;
int trackLen, i, i_a;
EventCrossing crossingEvent;
DBG_OBJ_MSGF ("events", 1, "(old) widgetAtPoint = %p", widgetAtPoint);
if (newWidgetAtPoint != widgetAtPoint) {
// The mouse pointer has been moved into another widget.
if (newWidgetAtPoint && widgetAtPoint)
ancestor =
newWidgetAtPoint->getNearestCommonAncestor (widgetAtPoint);
else if (newWidgetAtPoint)
ancestor = newWidgetAtPoint->getTopLevel ();
else
ancestor = widgetAtPoint->getTopLevel ();
// Construct the track.
trackLen = 0;
if (widgetAtPoint)
// first part
for (w = widgetAtPoint; w != ancestor; w = w->getParent ())
trackLen++;
trackLen++; // for the ancestor
if (newWidgetAtPoint)
// second part
for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
trackLen++;
track = new Widget* [trackLen];
i = 0;
if (widgetAtPoint)
/* first part */
for (w = widgetAtPoint; w != ancestor; w = w->getParent ())
track[i++] = w;
i_a = i;
track[i++] = ancestor;
if (newWidgetAtPoint) {
/* second part */
i = trackLen - 1;
for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
track[i--] = w;
}
#if 0
MSG("Track: %s[ ", widgetAtPoint ? "" : "nil ");
for (i = 0; i < trackLen; i++)
MSG("%s%p ", i == i_a ? ">" : "", track[i]);
MSG("] %s\n", newWidgetAtPoint ? "" : "nil");
#endif
/* Send events to the widgets on the track */
for (i = 0; i < trackLen; i++) {
crossingEvent.state = state;
crossingEvent.currentWidget = widgetAtPoint; // ???
crossingEvent.lastWidget = widgetAtPoint; // ???
if (i < i_a) {
track[i]->leaveNotify (&crossingEvent);
} else if (i == i_a) { /* ancestor */
/* Don't touch ancestor unless:
* - moving into/from NULL,
* - ancestor becomes the newWidgetAtPoint */
if (i_a == trackLen-1 && !newWidgetAtPoint)
track[i]->leaveNotify (&crossingEvent);
else if ((i_a == 0 && !widgetAtPoint) ||
(i_a == trackLen-1 && newWidgetAtPoint))
track[i]->enterNotify (&crossingEvent);
} else {
track[i]->enterNotify (&crossingEvent);
}
}
delete[] track;
widgetAtPoint = newWidgetAtPoint;
updateCursor ();
}
DBG_OBJ_LEAVE ();
}
/**
* \brief Common processing of press, release and motion events.
*
* This function depends on that move_to_widget_at_point()
* has been called before.
*/
bool Layout::processMouseEvent (MousePositionEvent *event,
ButtonEventType type)
{
Widget *widget;
/*
* If the event is outside of the visible region of the canvas, treat it
* as occurring at the region's edge. Notably, this helps when selecting
* text.
*/
if (event->xCanvas < scrollX)
event->xCanvas = scrollX;
else {
int maxX = scrollX + viewportWidth - currVScrollbarThickness() - 1;
if (event->xCanvas > maxX)
event->xCanvas = maxX;
}
if (event->yCanvas < scrollY)
event->yCanvas = scrollY;
else {
int maxY = std::min(scrollY + viewportHeight -currHScrollbarThickness(),
canvasAscent + canvasDescent) - 1;
if (event->yCanvas > maxY)
event->yCanvas = maxY;
}
widget = getWidgetAtPoint(event->xCanvas, event->yCanvas);
for (; widget; widget = widget->getParent ()) {
if (widget->isButtonSensitive ()) {
event->xWidget = event->xCanvas - widget->getAllocation()->x;
event->yWidget = event->yCanvas - widget->getAllocation()->y;
switch (type) {
case BUTTON_PRESS:
return widget->buttonPress ((EventButton*)event);
case BUTTON_RELEASE:
return widget->buttonRelease ((EventButton*)event);
case MOTION_NOTIFY:
return widget->motionNotify ((EventMotion*)event);
default:
misc::assertNotReached ();
}
}
}
if (type == BUTTON_PRESS)
return emitLinkPress (NULL, -1, -1, -1, -1, (EventButton*)event);
else if (type == BUTTON_RELEASE)
return emitLinkRelease(NULL, -1, -1, -1, -1, (EventButton*)event);
return false;
}
/*
* This function must be called by a view, when the user has manually changed
* the viewport position. It is *not* called, when the layout has requested the
* position change.
*/
void Layout::scrollPosChanged (View *view, int x, int y)
{
if (x != scrollX || y != scrollY) {
scrollX = x;
scrollY = y;
setAnchor( std::nullopt );
updateAnchor ();
}
}
/*
* This function must be called by a viewport view, when its viewport size has
* changed. It is *not* called, when the layout has requested the size change.
*/
void Layout::viewportSizeChanged (View *view, int width, int height)
{
DBG_OBJ_ENTER ("resize", 0, "viewportSizeChanged", "%p, %d, %d",
view, width, height);
/* If size changes, redraw this view. */
if (viewportWidth != width || viewportHeight != height) {
canvasHeightGreater = false; // reset value here
viewportWidth = width;
viewportHeight = height;
resizeCounter = 0;
containerSizeChanged ();
DBG_OBJ_SET_SYM ("canvasHeightGreater",
canvasHeightGreater ? "true" : "false");
DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth);
DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight);
}
DBG_OBJ_LEAVE ();
}
void Layout::containerSizeChanged ()
{
DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged");
if (topLevel) {
topLevel->containerSizeChanged ();
queueResize (true);
}
DBG_OBJ_LEAVE ();
}
} // namespace core
} // namespace dw