Files
flenser/dw/image.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

552 lines
16 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/>.
*/
//#define DEBUG_LEVEL 1
#include "image.hh"
#include "dlib/dlib.hh"
#include "../lout/msg.h"
#include "../lout/misc.hh"
#include "../lout/debug.hh"
namespace dw {
using namespace lout;
void
ImageMapsList::ImageMap::draw( core::View *view, core::style::Style *style, int x, int y )
{
for( auto &shapeAndLink: shapesAndLinks )
{
shapeAndLink->shape->draw(view, style, x, y);
}
}
void
ImageMapsList::ImageMap::add( std::unique_ptr< core::Shape > shape, int link )
{
auto shapeAndLink = std::make_unique< ShapeAndLink >();
shapeAndLink->shape = std::move( shape );
shapeAndLink->link = link;
shapesAndLinks.push_back( std::move( shapeAndLink ) );
}
int ImageMapsList::ImageMap::link (int x, int y) {
container::typed::Iterator <ShapeAndLink> it;
int link = defaultLink;
for ( auto &shapeAndLink: shapesAndLinks )
{
if (shapeAndLink->shape->isPointWithin (x, y)) {
link = shapeAndLink->link;
break;
}
}
return link;
}
ImageMapsList::ImageMapsList ()
{
imageMaps = new container::typed::HashTable <object::Object, ImageMap>
(true, true);
currentMap = NULL;
}
ImageMapsList::~ImageMapsList ()
{
delete imageMaps;
}
/**
* \brief Start a new map and make it the current one.
*
* This has to be called before dw::ImageMapsList::addShapeToCurrentMap.
* "key" is owned by the image map list, so a copy should be passed, when
* necessary.
*/
void ImageMapsList::startNewMap (object::Object *key)
{
currentMap = new ImageMap ();
imageMaps->put (key, currentMap);
}
/**
* \brief Add a shape to the current map-
*
* "shape" is owned by the image map list, so a copy should be passed, when
* necessary.
*/
void
ImageMapsList::addShapeToCurrentMap( std::unique_ptr< core::Shape > shape, const int link )
{
currentMap->add( std::move( shape ), link );
}
/**
* \brief Set default link for current map-
*/
void ImageMapsList::setCurrentMapDefaultLink (int link)
{
currentMap->setDefaultLink (link);
}
void ImageMapsList::drawMap (lout::object::Object *key, core::View *view,
core::style::Style *style, int x, int y)
{
ImageMap *map = imageMaps->get (key);
if (map)
map->draw(view, style, x, y);
}
int ImageMapsList::link (object::Object *key, int x, int y)
{
int link = -1;
ImageMap *map = imageMaps->get (key);
if (map)
link = map->link (x, y);
return link;
}
// ----------------------------------------------------------------------
Image::Image(const std::optional< std::string_view > altText)
{
DBG_OBJ_CREATE ("dw::Image");
registerName ("dw::Image", typeid(*this));
this->altText = altText;
altTextWidth = -1; // not yet calculated
buffer = NULL;
bufWidth = bufHeight = -1;
clicking = false;
currLink = -1;
mapList = NULL;
mapKey = NULL;
isMap = false;
DBG_OBJ_SET_NUM ("bufWidth", bufWidth);
DBG_OBJ_SET_NUM ("bufHeight", bufHeight);
}
Image::~Image()
{
if (buffer)
buffer->unref ();
if (mapKey)
delete mapKey;
DBG_OBJ_DELETE ();
}
void Image::sizeRequestSimpl (core::Requisition *requisition)
{
DBG_OBJ_ENTER0 ("resize", 0, "sizeRequestImpl");
DEBUG_MSG(1, "-- Image::sizeRequestSimpl() begins\n");
DEBUG_MSG(1, "Image::sizeRequestImpl border: w=%d h=%d\n",
boxDiffWidth(), boxDiffHeight());
/* First set a naive size based on the image properties if given */
if (buffer) {
requisition->width = buffer->getRootWidth ();
requisition->ascent = buffer->getRootHeight ();
requisition->descent = 0;
} else {
if (altText && altText.value()[0]) {
if (altTextWidth == -1)
altTextWidth =
layout->textWidth (getStyle()->font, altText.value().c_str(), altText.value().size());
requisition->width = altTextWidth;
requisition->ascent = getStyle()->font->ascent;
requisition->descent = getStyle()->font->descent;
} else {
requisition->width = 0;
requisition->ascent = 0;
requisition->descent = 0;
}
}
requisition->width += boxDiffWidth ();
requisition->ascent += boxOffsetY ();
requisition->descent += boxRestHeight ();
DEBUG_MSG(1, "Image: initial requisition (with border): w=%d, h=%d\n",
requisition->width, requisition->ascent + requisition->descent);
/* Then correct the size if needed, so it fits within the available space in
* the container widget. The correctRequisition() method will take into the
* account the preferred aspect ratio. */
correctRequisition (requisition, core::splitHeightPreserveDescent, true,
true);
DEBUG_MSG(1, "Image: corrected requisition: w=%d, h=%d\n",
requisition->width, requisition->ascent + requisition->descent);
DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)",
requisition->width, requisition->ascent, requisition->descent);
DBG_OBJ_LEAVE ();
}
void Image::getExtremesSimpl (core::Extremes *extremes)
{
int contentWidth;
if (buffer)
contentWidth = buffer->getRootWidth ();
else {
if (altText && altText.value()[0]) {
if (altTextWidth == -1)
altTextWidth =
layout->textWidth (getStyle()->font, altText.value().c_str(), altText.value().size());
contentWidth = altTextWidth;
} else
contentWidth = 0;
}
int width = contentWidth + boxDiffWidth ();
// With percentage width, the image may be narrower than the buffer.
extremes->minWidth =
core::style::isPerLength (getStyle()->width) ? boxDiffWidth () : width;
// (We ignore the same effect for the maximal width.)
extremes->maxWidth = width;
extremes->minWidthIntrinsic = extremes->minWidth;
extremes->maxWidthIntrinsic = extremes->maxWidth;
correctExtremes (extremes, false);
extremes->adjustmentWidth =
std::min (extremes->minWidthIntrinsic, extremes->minWidth);
}
void Image::sizeAllocateImpl (core::Allocation *allocation)
{
DBG_OBJ_ENTER ("resize", 0, "sizeAllocateImpl", "%d, %d; %d * (%d + %d)",
allocation->x, allocation->y, allocation->width,
allocation->ascent, allocation->descent);
DEBUG_MSG(1, "Image::sizeAllocateImpl x=%d y=%d w=%d h=(%d + %d)\n",
allocation->x, allocation->y, allocation->width,
allocation->ascent, allocation->descent);
DEBUG_MSG(1, "Image::sizeAllocateImpl border: w=%d h=%d\n",
boxDiffWidth(), boxDiffHeight());
int newBufWidth = allocation->width - boxDiffWidth ();
int newBufHeight =
allocation->ascent + allocation->descent - boxDiffHeight ();
if (buffer && newBufWidth > 0 && newBufHeight > 0 &&
// Save some time when size did not change:
(newBufWidth != bufWidth || newBufHeight != bufHeight)) {
DBG_OBJ_MSG ("resize", 1, "replacing buffer");
DEBUG_MSG(1, "Image::sizeAllocateImpl new buffer size: w=%d h=%d\n",
newBufWidth, newBufHeight);
core::Imgbuf *oldBuffer = buffer;
buffer = oldBuffer->getScaledBuf (newBufWidth, newBufHeight);
oldBuffer->unref ();
bufWidth = newBufWidth;
bufHeight = newBufHeight;
DBG_OBJ_ASSOC_CHILD (this->buffer);
DBG_OBJ_SET_NUM ("bufWidth", bufWidth);
DBG_OBJ_SET_NUM ("bufHeight", bufHeight);
}
DEBUG_MSG(1, "Image::sizeAllocateImpl x=%d y=%d w=%d h=(%d + %d)\n",
allocation->x, allocation->y, allocation->width,
allocation->ascent, allocation->descent);
DBG_OBJ_LEAVE ();
}
void Image::containerSizeChangedForChildren ()
{
DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren");
// Nothing to do.
DBG_OBJ_LEAVE ();
}
void Image::enterNotifyImpl (core::EventCrossing *event)
{
// BUG: this is wrong for image maps, but the cursor position is unknown.
currLink = getStyle()->x_link;
if (currLink != -1) {
(void) layout->emitLinkEnter (this, currLink, -1, -1, -1);
}
Widget::enterNotifyImpl(event);
}
void Image::leaveNotifyImpl (core::EventCrossing *event)
{
clicking = false;
if (currLink != -1) {
currLink = -1;
(void) layout->emitLinkEnter (this, -1, -1, -1, -1);
}
Widget::leaveNotifyImpl(event);
}
/*
* Return the coordinate relative to the contents.
* If the event occurred in the surrounding box, return the value at the
* edge of the contents instead.
*/
int Image::contentX (core::MousePositionEvent *event)
{
int ret = event->xWidget - boxOffsetX();
ret = std::min(getContentWidth(), std::max(ret, 0));
return ret;
}
int Image::contentY (core::MousePositionEvent *event)
{
int ret = event->yWidget - boxOffsetY();
ret = std::min(getContentHeight(), std::max(ret, 0));
return ret;
}
bool Image::motionNotifyImpl (core::EventMotion *event)
{
if (mapList || isMap) {
int x = contentX(event);
int y = contentY(event);
if (mapList) {
/* client-side image map */
int newLink = mapList->link (mapKey, x, y);
if (newLink != currLink) {
currLink = newLink;
clicking = false;
/* \todo Using MAP/AREA styles would probably be best */
setCursor(newLink == -1 ? getStyle()->cursor :
core::style::CURSOR_POINTER);
(void) layout->emitLinkEnter (this, newLink, -1, -1, -1);
}
} else if (isMap && currLink != -1) {
/* server-side image map */
(void) layout->emitLinkEnter (this, currLink, -1, x, y);
}
}
return true;
}
bool Image::buttonPressImpl (core::EventButton *event)
{
bool ret = false;
currLink = mapList ? mapList->link(mapKey,contentX(event),contentY(event)) :
getStyle()->x_link;
if (event->button == 3){
(void)layout->emitLinkPress(this, currLink, getStyle()->x_img, -1, -1,
event);
ret = true;
} else if (event->button == 1 || currLink != -1){
clicking = true;
ret = true;
}
return ret;
}
bool Image::buttonReleaseImpl (core::EventButton *event)
{
currLink = mapList ? mapList->link(mapKey,contentX(event),contentY(event)) :
getStyle()->x_link;
if (clicking) {
int x = isMap ? contentX(event) : -1;
int y = isMap ? contentY(event) : -1;
clicking = false;
layout->emitLinkClick (this, currLink, getStyle()->x_img, x, y, event);
return true;
}
return false;
}
void Image::draw (core::View *view, core::Rectangle *area,
core::DrawingContext *context)
{
int dx, dy;
core::Rectangle content, intersection;
drawWidgetBox (view, area, false);
if (buffer) {
dx = boxOffsetX ();
dy = boxOffsetY ();
content.x = dx;
content.y = dy;
content.width = getContentWidth ();
content.height = getContentHeight ();
if (area->intersectsWith (&content, &intersection))
view->drawImage (buffer,
allocation.x + dx, allocation.y + dy,
intersection.x - dx, intersection.y - dy,
intersection.width, intersection.height);
} else {
core::View *clippingView;
if (altText && altText.value()[0]) {
core::View *usedView = view;
clippingView = NULL;
if (altTextWidth == -1)
altTextWidth =
layout->textWidth (getStyle()->font, altText.value().c_str(), altText.value().size());
if ((getContentWidth() < altTextWidth) ||
(getContentHeight() <
getStyle()->font->ascent + getStyle()->font->descent)) {
clippingView = usedView =
view->getClippingView (allocation.x + boxOffsetX (),
allocation.y + boxOffsetY (),
getContentWidth(),
getContentHeight());
}
usedView->drawSimpleWrappedText (getStyle()->font, getStyle()->color,
core::style::Color::SHADING_NORMAL,
allocation.x + boxOffsetX (),
allocation.y + boxOffsetY (),
getContentWidth(), getContentHeight(), altText.value().c_str());
if (clippingView)
view->mergeClippingView (clippingView);
}
if (mapKey) {
clippingView = view->getClippingView (allocation.x + boxOffsetX (),
allocation.y + boxOffsetY (),
getContentWidth(),
getContentHeight());
mapList->drawMap(mapKey, clippingView, getStyle(),
allocation.x + boxOffsetX (),
allocation.y + boxOffsetY ());
view->mergeClippingView (clippingView);
}
}
/** TODO: draw selection */
}
core::Iterator *Image::iterator (core::Content::Type mask, bool atEnd)
{
//return new core::TextIterator (this, mask, atEnd, altText);
/** \bug Not implemented. */
return new core::EmptyIterator (this, mask, atEnd);
}
void Image::setBuffer (core::Imgbuf *buffer, bool resize)
{
core::Imgbuf *oldBuf = this->buffer;
if (wasAllocated () && needsResize () &&
getContentWidth () > 0 && getContentHeight () > 0) {
// Don't create a new buffer for the transition from alt text to img,
// and only scale when both dimensions are known.
bufWidth = getContentWidth ();
bufHeight = getContentHeight ();
this->buffer = buffer->getScaledBuf (bufWidth, bufHeight);
} else {
this->buffer = buffer;
bufWidth = buffer->getRootWidth ();
bufHeight = buffer->getRootHeight ();
buffer->ref ();
}
queueResize (0, true);
if (bufWidth)
this->ratio = (float) bufWidth / (float) bufHeight;
DBG_OBJ_ASSOC_CHILD (this->buffer);
DBG_OBJ_SET_NUM ("bufWidth", bufWidth);
DBG_OBJ_SET_NUM ("bufHeight", bufHeight);
if (oldBuf)
oldBuf->unref ();
}
void Image::drawRow (int row)
{
core::Rectangle area;
assert (buffer != NULL);
buffer->getRowArea (row, &area);
if (area.width && area.height)
queueDrawArea (area.x + boxOffsetX (), area.y + boxOffsetY (), area.width,
area.height);
}
void Image::finish ()
{
// Nothing to do; images are always drawn line by line.
}
void Image::fatal ()
{
// Could display an error.
}
/**
* \brief Sets image as server side image map.
*/
void Image::setIsMap ()
{
isMap = true;
}
/**
* \brief Sets image as client side image map.
*
* "list" is not owned by the image, the caller has to free it. "key"
* is owned by the image, if it is used by the caller afterwards, a copy
* should be passed.
*/
void Image::setUseMap (ImageMapsList *list, object::Object *key)
{
mapList = list;
if (mapKey && mapKey != key)
delete mapKey;
mapKey = key;
}
} // namespace dw