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