1 /*
2  * Dillo Widget
3  *
4  * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 
21 
22 #include "image.hh"
23 #include "../lout/msg.h"
24 #include "../lout/misc.hh"
25 #include "../lout/debug.hh"
26 
27 namespace dw {
28 
29 using namespace lout;
30 
ImageMap()31 ImageMapsList::ImageMap::ImageMap ()
32 {
33    shapesAndLinks = new container::typed::List <ShapeAndLink> (true);
34    defaultLink = -1;
35 }
36 
~ImageMap()37 ImageMapsList::ImageMap::~ImageMap ()
38 {
39    delete shapesAndLinks;
40 }
41 
draw(core::View * view,core::style::Style * style,int x,int y)42 void ImageMapsList::ImageMap::draw (core::View *view,core::style::Style *style,
43                                     int x, int y)
44 {
45    container::typed::Iterator <ShapeAndLink> it;
46 
47    for (it = shapesAndLinks->iterator (); it.hasNext (); ) {
48       ShapeAndLink *shapeAndLink = it.getNext ();
49 
50       shapeAndLink->shape->draw(view, style, x, y);
51    }
52 }
53 
add(core::Shape * shape,int link)54 void ImageMapsList::ImageMap::add (core::Shape *shape, int link) {
55    ShapeAndLink *shapeAndLink = new ShapeAndLink ();
56    shapeAndLink->shape = shape;
57    shapeAndLink->link = link;
58    shapesAndLinks->append (shapeAndLink);
59 }
60 
link(int x,int y)61 int ImageMapsList::ImageMap::link (int x, int y) {
62    container::typed::Iterator <ShapeAndLink> it;
63    int link = defaultLink;
64 
65    for (it = shapesAndLinks->iterator (); it.hasNext (); ) {
66       ShapeAndLink *shapeAndLink = it.getNext ();
67 
68       if (shapeAndLink->shape->isPointWithin (x, y)) {
69          link = shapeAndLink->link;
70          break;
71       }
72    }
73 
74    return link;
75 }
76 
ImageMapsList()77 ImageMapsList::ImageMapsList ()
78 {
79    imageMaps = new container::typed::HashTable <object::Object, ImageMap>
80       (true, true);
81    currentMap = NULL;
82 }
83 
~ImageMapsList()84 ImageMapsList::~ImageMapsList ()
85 {
86    delete imageMaps;
87 }
88 
89 /**
90  * \brief Start a new map and make it the current one.
91  *
92  * This has to be called before dw::ImageMapsList::addShapeToCurrentMap.
93  * "key" is owned by the image map list, so a copy should be passed, when
94  * necessary.
95  */
startNewMap(object::Object * key)96 void ImageMapsList::startNewMap (object::Object *key)
97 {
98    currentMap = new ImageMap ();
99    imageMaps->put (key, currentMap);
100 }
101 
102 /**
103  * \brief Add a shape to the current map-
104  *
105  * "shape" is owned by the image map list, so a copy should be passed, when
106  * necessary.
107  */
addShapeToCurrentMap(core::Shape * shape,int link)108 void ImageMapsList::addShapeToCurrentMap (core::Shape *shape, int link)
109 {
110    currentMap->add (shape, link);
111 }
112 
113 /**
114  * \brief Set default link for current map-
115  */
setCurrentMapDefaultLink(int link)116 void ImageMapsList::setCurrentMapDefaultLink (int link)
117 {
118    currentMap->setDefaultLink (link);
119 }
120 
drawMap(lout::object::Object * key,core::View * view,core::style::Style * style,int x,int y)121 void ImageMapsList::drawMap (lout::object::Object *key, core::View *view,
122                              core::style::Style *style, int x, int y)
123 {
124    ImageMap *map = imageMaps->get (key);
125 
126    if (map)
127       map->draw(view, style, x, y);
128 }
129 
link(object::Object * key,int x,int y)130 int ImageMapsList::link (object::Object *key, int x, int y)
131 {
132    int link = -1;
133    ImageMap *map = imageMaps->get (key);
134 
135    if (map)
136       link = map->link (x, y);
137 
138    return link;
139 }
140 
141 // ----------------------------------------------------------------------
142 
143 int Image::CLASS_ID = -1;
144 
Image(const char * altText)145 Image::Image(const char *altText)
146 {
147    DBG_OBJ_CREATE ("dw::Image");
148    registerName ("dw::Image", &CLASS_ID);
149    this->altText = altText ? strdup (altText) : NULL;
150    altTextWidth = -1; // not yet calculated
151    buffer = NULL;
152    clicking = false;
153    currLink = -1;
154    mapList = NULL;
155    mapKey = NULL;
156    isMap = false;
157 }
158 
~Image()159 Image::~Image()
160 {
161    if (altText)
162       free(altText);
163    if (buffer)
164       buffer->unref ();
165    if (mapKey)
166       delete mapKey;
167 
168    DBG_OBJ_DELETE ();
169 }
170 
sizeRequestImpl(core::Requisition * requisition)171 void Image::sizeRequestImpl (core::Requisition *requisition)
172 {
173    if (buffer) {
174       if (getStyle ()->height == core::style::LENGTH_AUTO &&
175           core::style::isAbsLength (getStyle ()->width) &&
176           buffer->getRootWidth () > 0) {
177          // preserve aspect ratio when only width is given
178          requisition->width = core::style::absLengthVal (getStyle ()->width);
179          requisition->ascent = buffer->getRootHeight () *
180                                requisition->width / buffer->getRootWidth ();
181       } else if (getStyle ()->width == core::style::LENGTH_AUTO &&
182                  core::style::isAbsLength (getStyle ()->height) &&
183                  buffer->getRootHeight () > 0) {
184          // preserve aspect ratio when only height is given
185          requisition->ascent = core::style::absLengthVal (getStyle ()->height);
186          requisition->width = buffer->getRootWidth () *
187                                requisition->ascent / buffer->getRootHeight ();
188       } else {
189          requisition->width = buffer->getRootWidth ();
190          requisition->ascent = buffer->getRootHeight ();
191       }
192       requisition->descent = 0;
193    } else {
194       if (altText && altText[0]) {
195          if (altTextWidth == -1)
196             altTextWidth =
197                layout->textWidth (getStyle()->font, altText, strlen (altText));
198 
199          requisition->width = altTextWidth;
200          requisition->ascent = getStyle()->font->ascent;
201          requisition->descent = getStyle()->font->descent;
202       } else {
203          requisition->width = 0;
204          requisition->ascent = 0;
205          requisition->descent = 0;
206       }
207    }
208 
209    requisition->width += getStyle()->boxDiffWidth ();
210    requisition->ascent += getStyle()->boxOffsetY ();
211    requisition->descent += getStyle()->boxRestHeight ();
212 }
213 
sizeAllocateImpl(core::Allocation * allocation)214 void Image::sizeAllocateImpl (core::Allocation *allocation)
215 {
216    core::Imgbuf *oldBuffer;
217    int dx, dy;
218 
219    /* if image is moved only */
220    if (allocation->width == this->allocation.width &&
221        allocation->ascent + allocation->descent == getHeight ())
222       return;
223 
224    dx = getStyle()->boxDiffWidth ();
225    dy = getStyle()->boxDiffHeight ();
226 #if 0
227    MSG("boxDiffHeight = %d + %d, buffer=%p\n",
228        getStyle()->boxOffsetY(), getStyle()->boxRestHeight(), buffer);
229    MSG("getContentWidth() = allocation.width - style->boxDiffWidth ()"
230        " = %d - %d = %d\n",
231        this->allocation.width, getStyle()->boxDiffWidth(),
232        this->allocation.width - getStyle()->boxDiffWidth());
233    MSG("getContentHeight() = getHeight() - style->boxDiffHeight ()"
234        " = %d - %d = %d\n", this->getHeight(), getStyle()->boxDiffHeight(),
235        this->getHeight() - getStyle()->boxDiffHeight());
236 #endif
237    if (buffer &&
238        (allocation->width - dx > 0 ||
239         allocation->ascent + allocation->descent - dy > 0)) {
240       // Zero content size : simply wait...
241       // Only one dimension: naturally scale
242       oldBuffer = buffer;
243       buffer = oldBuffer->getScaledBuf (allocation->width - dx,
244                                         allocation->ascent
245                                         + allocation->descent - dy);
246       oldBuffer->unref ();
247    }
248 }
249 
enterNotifyImpl(core::EventCrossing * event)250 void Image::enterNotifyImpl (core::EventCrossing *event)
251 {
252    // BUG: this is wrong for image maps, but the cursor position is unknown.
253    currLink = getStyle()->x_link;
254 
255    if (currLink != -1) {
256       (void) layout->emitLinkEnter (this, currLink, -1, -1, -1);
257    }
258    Widget::enterNotifyImpl(event);
259 }
260 
leaveNotifyImpl(core::EventCrossing * event)261 void Image::leaveNotifyImpl (core::EventCrossing *event)
262 {
263    clicking = false;
264 
265    if (currLink != -1) {
266       currLink = -1;
267       (void) layout->emitLinkEnter (this, -1, -1, -1, -1);
268    }
269    Widget::leaveNotifyImpl(event);
270 }
271 
272 /*
273  * Return the coordinate relative to the contents.
274  * If the event occurred in the surrounding box, return the value at the
275  * edge of the contents instead.
276  */
contentX(core::MousePositionEvent * event)277 int Image::contentX (core::MousePositionEvent *event)
278 {
279    int ret = event->xWidget - getStyle()->boxOffsetX();
280 
281    ret = misc::min(getContentWidth(), misc::max(ret, 0));
282    return ret;
283 }
284 
contentY(core::MousePositionEvent * event)285 int Image::contentY (core::MousePositionEvent *event)
286 {
287    int ret = event->yWidget - getStyle()->boxOffsetY();
288 
289    ret = misc::min(getContentHeight(), misc::max(ret, 0));
290    return ret;
291 }
292 
motionNotifyImpl(core::EventMotion * event)293 bool Image::motionNotifyImpl (core::EventMotion *event)
294 {
295    if (mapList || isMap) {
296       int x = contentX(event);
297       int y = contentY(event);
298 
299       if (mapList) {
300          /* client-side image map */
301          int newLink = mapList->link (mapKey, x, y);
302          if (newLink != currLink) {
303             currLink = newLink;
304             clicking = false;
305             /* \todo Using MAP/AREA styles would probably be best */
306             setCursor(newLink == -1 ? getStyle()->cursor :
307                                       core::style::CURSOR_POINTER);
308             (void) layout->emitLinkEnter (this, newLink, -1, -1, -1);
309          }
310       } else if (isMap && currLink != -1) {
311          /* server-side image map */
312          (void) layout->emitLinkEnter (this, currLink, -1, x, y);
313       }
314    }
315    return true;
316 }
317 
buttonPressImpl(core::EventButton * event)318 bool Image::buttonPressImpl (core::EventButton *event)
319 {
320    bool ret = false;
321 
322    currLink = mapList ? mapList->link(mapKey,contentX(event),contentY(event)) :
323       getStyle()->x_link;
324    if (event->button == 3){
325       (void)layout->emitLinkPress(this, currLink, getStyle()->x_img, -1, -1,
326                                   event);
327       ret = true;
328    } else if (event->button == 1 || currLink != -1){
329       clicking = true;
330       ret = true;
331    }
332    return ret;
333 }
334 
buttonReleaseImpl(core::EventButton * event)335 bool Image::buttonReleaseImpl (core::EventButton *event)
336 {
337    currLink = mapList ? mapList->link(mapKey,contentX(event),contentY(event)) :
338       getStyle()->x_link;
339    if (clicking) {
340       int x = isMap ? contentX(event) : -1;
341       int y = isMap ? contentY(event) : -1;
342       clicking = false;
343       layout->emitLinkClick (this, currLink, getStyle()->x_img, x, y, event);
344       return true;
345    }
346    return false;
347 }
348 
draw(core::View * view,core::Rectangle * area)349 void Image::draw (core::View *view, core::Rectangle *area)
350 {
351    int dx, dy;
352    core::Rectangle content, intersection;
353 
354    drawWidgetBox (view, area, false);
355 
356    if (buffer) {
357       dx = getStyle()->boxOffsetX ();
358       dy = getStyle()->boxOffsetY ();
359       content.x = dx;
360       content.y = dy;
361       content.width = getContentWidth ();
362       content.height = getContentHeight ();
363 
364       if (area->intersectsWith (&content, &intersection))
365          view->drawImage (buffer,
366                           allocation.x + dx, allocation.y + dy,
367                           intersection.x - dx, intersection.y - dy,
368                           intersection.width, intersection.height);
369    } else {
370       core::View *clippingView;
371 
372       if (altText && altText[0]) {
373          core::View *usedView = view;
374 
375          clippingView = NULL;
376 
377          if (altTextWidth == -1)
378             altTextWidth =
379                layout->textWidth (getStyle()->font, altText, strlen (altText));
380 
381          if ((getContentWidth() < altTextWidth) ||
382              (getContentHeight() <
383               getStyle()->font->ascent + getStyle()->font->descent)) {
384             clippingView = usedView =
385                view->getClippingView (allocation.x + getStyle()->boxOffsetX (),
386                                       allocation.y + getStyle()->boxOffsetY (),
387                                       getContentWidth(),
388                                       getContentHeight());
389          }
390 
391          usedView->drawSimpleWrappedText (getStyle()->font, getStyle()->color,
392                              core::style::Color::SHADING_NORMAL,
393                              allocation.x + getStyle()->boxOffsetX (),
394                              allocation.y + getStyle()->boxOffsetY (),
395                              getContentWidth(), getContentHeight(), altText);
396 
397          if (clippingView)
398             view->mergeClippingView (clippingView);
399       }
400       if (mapKey) {
401          clippingView = view->getClippingView (allocation.x +
402                                                getStyle()->boxOffsetX (),
403                                                allocation.y +
404                                                getStyle()->boxOffsetY (),
405                                                getContentWidth(),
406                                                getContentHeight());
407          mapList->drawMap(mapKey, clippingView, getStyle(),
408                           allocation.x + getStyle()->boxOffsetX (),
409                           allocation.y + getStyle()->boxOffsetY ());
410          view->mergeClippingView (clippingView);
411       }
412    }
413 
414    /** TODO: draw selection */
415 }
416 
iterator(core::Content::Type mask,bool atEnd)417 core::Iterator *Image::iterator (core::Content::Type mask, bool atEnd)
418 {
419    //return new core::TextIterator (this, mask, atEnd, altText);
420    /** \bug Not implemented. */
421    return new core::EmptyIterator (this, mask, atEnd);
422 }
423 
setBuffer(core::Imgbuf * buffer,bool resize)424 void Image::setBuffer (core::Imgbuf *buffer, bool resize)
425 {
426    core::Imgbuf *oldBuf = this->buffer;
427 
428    if (wasAllocated () && needsResize () &&
429       getContentWidth () > 0 && getContentHeight () > 0) {
430       // Don't create a new buffer for the transition from alt text to img,
431       // and only scale when both dimensions are known.
432       this->buffer =
433          buffer->getScaledBuf (getContentWidth (), getContentHeight ());
434    } else {
435       this->buffer = buffer;
436       buffer->ref ();
437    }
438    queueResize (0, true);
439 
440    DBG_OBJ_ASSOC_CHILD (this->buffer);
441 
442    if (oldBuf)
443       oldBuf->unref ();
444 }
445 
drawRow(int row)446 void Image::drawRow (int row)
447 {
448    core::Rectangle area;
449 
450    assert (buffer != NULL);
451 
452    buffer->getRowArea (row, &area);
453    if (area.width && area.height)
454       queueDrawArea (area.x + getStyle()->boxOffsetX (),
455                      area.y + getStyle()->boxOffsetY (),
456                      area.width, area.height);
457 }
458 
finish()459 void Image::finish ()
460 {
461    // Nothing to do; images are always drawn line by line.
462 }
463 
fatal()464 void Image::fatal ()
465 {
466    // Could display an error.
467 }
468 
469 
470 /**
471  * \brief Sets image as server side image map.
472  */
setIsMap()473 void Image::setIsMap ()
474 {
475    isMap = true;
476 }
477 
478 
479 /**
480  * \brief Sets image as client side image map.
481  *
482  * "list" is not owned by the image, the caller has to free it. "key"
483  * is owned by the image, if it is used by the caller afterwards, a copy
484  * should be passed.
485  */
setUseMap(ImageMapsList * list,object::Object * key)486 void Image::setUseMap (ImageMapsList *list, object::Object *key)
487 {
488    mapList = list;
489    if (mapKey && mapKey != key)
490       delete mapKey;
491    mapKey = key;
492 }
493 
494 } // namespace dw
495