view dw/image.cc @ 2030:382160be8c2f

cookies comments I was going to say something in the comments about simplicity and not implementing every little bit of the rfc when we don't know of cookies that make it necessary, but then I suppose that's all implied with dillo.
author corvid <corvid@lavabit.com>
date Tue, 17 May 2011 22:48:50 +0000
parents b519799e360d
children b24e8b62bef3
line wrap: on
line source
/*
 * Dillo Widget
 *
 * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
 *
 * 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 "image.hh"
#include "../lout/msg.h"
#include "../lout/misc.hh"

namespace dw {

using namespace lout;

ImageMapsList::ImageMap::ImageMap ()
{
   shapesAndLinks = new container::typed::List <ShapeAndLink> (true);
   defaultLink = -1;
}

ImageMapsList::ImageMap::~ImageMap ()
{
   delete shapesAndLinks;
}

void ImageMapsList::ImageMap::draw (core::View *view,core::style::Style *style,
                                    int x, int y)
{
   container::typed::Iterator <ShapeAndLink> it;

   for (it = shapesAndLinks->iterator (); it.hasNext (); ) {
      ShapeAndLink *shapeAndLink = it.getNext ();

      shapeAndLink->shape->draw(view, style, x, y);
   }
}

void ImageMapsList::ImageMap::add (core::Shape *shape, int link) {
   ShapeAndLink *shapeAndLink = new ShapeAndLink ();
   shapeAndLink->shape = shape;
   shapeAndLink->link = link;
   shapesAndLinks->append (shapeAndLink);
}

int ImageMapsList::ImageMap::link (int x, int y) {
   container::typed::Iterator <ShapeAndLink> it;
   int link = defaultLink;

   for (it = shapesAndLinks->iterator (); it.hasNext (); ) {
      ShapeAndLink *shapeAndLink = it.getNext ();

      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 (core::Shape *shape, int link)
{
   currentMap->add (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;
}

// ----------------------------------------------------------------------

int Image::CLASS_ID = -1;

Image::Image(const char *altText)
{
   registerName ("dw::Image", &CLASS_ID);
   this->altText = altText ? strdup (altText) : NULL;
   altTextWidth = -1; // not yet calculated
   buffer = NULL;
   clicking = false;
   currLink = -1;
   mapList = NULL;
   mapKey = NULL;
   isMap = false;
}

Image::~Image()
{
   if (altText)
      free(altText);
   if (buffer)
      buffer->unref ();
   if (mapKey)
      delete mapKey;
}

void Image::sizeRequestImpl (core::Requisition *requisition)
{
   if (buffer) {
      if (getStyle ()->height == core::style::LENGTH_AUTO &&
          core::style::isAbsLength (getStyle ()->width) &&
          buffer->getRootWidth () > 0) {
         // preserve aspect ratio when only width is given
         requisition->width = core::style::absLengthVal (getStyle ()->width);
         requisition->ascent = buffer->getRootHeight () *
                               requisition->width / buffer->getRootWidth ();
      } else if (getStyle ()->width == core::style::LENGTH_AUTO &&
                 core::style::isAbsLength (getStyle ()->height) &&
                 buffer->getRootHeight () > 0) {
         // preserve aspect ratio when only height is given
         requisition->ascent = core::style::absLengthVal (getStyle ()->height);
         requisition->width = buffer->getRootWidth () *
                               requisition->ascent / buffer->getRootHeight ();
      } else {
         requisition->width = buffer->getRootWidth ();
         requisition->ascent = buffer->getRootHeight ();
      }
      requisition->descent = 0;
   } else {
      if (altText && altText[0]) {
         if (altTextWidth == -1)
            altTextWidth =
               layout->textWidth (getStyle()->font, altText, strlen (altText));

         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 += getStyle()->boxDiffWidth ();
   requisition->ascent += getStyle()->boxOffsetY ();
   requisition->descent += getStyle()->boxRestHeight ();
}

void Image::sizeAllocateImpl (core::Allocation *allocation)
{
   core::Imgbuf *oldBuffer;
   int dx, dy;

   /* if image is moved only */
   if (allocation->width == this->allocation.width &&
       allocation->ascent + allocation->descent == getHeight ())
      return;

   dx = getStyle()->boxDiffWidth ();
   dy = getStyle()->boxDiffHeight ();
#if 0
   MSG("boxDiffHeight = %d + %d, buffer=%p\n",
       getStyle()->boxOffsetY(), getStyle()->boxRestHeight(), buffer);
   MSG("getContentWidth() = allocation.width - style->boxDiffWidth ()"
       " = %d - %d = %d\n",
       this->allocation.width, getStyle()->boxDiffWidth(),
       this->allocation.width - getStyle()->boxDiffWidth());
   MSG("getContentHeight() = getHeight() - style->boxDiffHeight ()"
       " = %d - %d = %d\n", this->getHeight(), getStyle()->boxDiffHeight(),
       this->getHeight() - getStyle()->boxDiffHeight());
#endif
   if (buffer &&
       (allocation->width - dx > 0 ||
        allocation->ascent + allocation->descent - dy > 0)) {
      // Zero content size : simply wait...
      // Only one dimension: naturally scale
      oldBuffer = buffer;
      buffer = oldBuffer->getScaledBuf (allocation->width - dx,
                                        allocation->ascent
                                        + allocation->descent - dy);
      oldBuffer->unref ();
   }
}

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 - getStyle()->boxOffsetX();

   ret = misc::min(getContentWidth(), misc::max(ret, 0));
   return ret;
}

int Image::contentY (core::MousePositionEvent *event)
{
   int ret = event->yWidget - getStyle()->boxOffsetY();

   ret = misc::min(getContentHeight(), misc::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)
{
   int dx, dy;
   core::Rectangle content, intersection;

   drawWidgetBox (view, area, false);

   if (buffer) {
      dx = getStyle()->boxOffsetX ();
      dy = getStyle()->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[0]) {
         core::View *usedView = view;

         clippingView = NULL;

         if (altTextWidth == -1)
            altTextWidth =
               layout->textWidth (getStyle()->font, altText, strlen (altText));

         if ((getContentWidth() < altTextWidth) ||
             (getContentHeight() <
              getStyle()->font->ascent + getStyle()->font->descent)) {
            clippingView = usedView =
               view->getClippingView (allocation.x + getStyle()->boxOffsetX (),
                                      allocation.y + getStyle()->boxOffsetY (),
                                      getContentWidth(),
                                      getContentHeight());
         }

         usedView->drawText (getStyle()->font, getStyle()->color,
                             core::style::Color::SHADING_NORMAL,
                             allocation.x + getStyle()->boxOffsetX (),
                             allocation.y + getStyle()->boxOffsetY ()
                             + getStyle()->font->ascent,
                             altText, strlen(altText));

         if (clippingView)
            view->mergeClippingView (clippingView);
      }
      if (mapKey) {
         clippingView = view->getClippingView (allocation.x +
                                               getStyle()->boxOffsetX (),
                                               allocation.y +
                                               getStyle()->boxOffsetY (),
                                               getContentWidth(),
                                               getContentHeight());
         mapList->drawMap(mapKey, clippingView, getStyle(),
                          allocation.x + getStyle()->boxOffsetX (),
                          allocation.y + getStyle()->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 (resize)
      queueResize (0, true);

   if (wasAllocated () && getContentWidth () > 0 && getContentHeight () > 0) {
      // Only scale when both dimensions are known.
      this->buffer =
         buffer->getScaledBuf (getContentWidth (), getContentHeight ());
   } else {
      this->buffer = buffer;
      buffer->ref ();
   }

   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 + getStyle()->boxOffsetX (),
                     area.y + getStyle()->boxOffsetY (),
                     area.width, area.height);
}


/**
 * \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