1/*
2    Copyright (C) 2009-2010 ProFUSION embedded systems
3    Copyright (C) 2009-2010 Samsung Electronics
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19*/
20
21#include "config.h"
22#include "ewk_view.h"
23
24#include "ewk_frame.h"
25#include "ewk_logging.h"
26
27#include <Evas.h>
28#include <eina_safety_checks.h>
29#include <string.h>
30
31static Ewk_View_Smart_Class _parent_sc = EWK_VIEW_SMART_CLASS_INIT_NULL;
32
33static void _ewk_view_single_on_del(void *data, Evas *e, Evas_Object *o, void *event_info)
34{
35    Evas_Object *clip = (Evas_Object*)data;
36    evas_object_del(clip);
37}
38
39static void _ewk_view_single_smart_add(Evas_Object *o)
40{
41    Ewk_View_Smart_Data *sd;
42
43    _parent_sc.sc.add(o);
44
45    sd = (Ewk_View_Smart_Data *)evas_object_smart_data_get(o);
46
47    Evas_Object *clip = evas_object_rectangle_add(sd->base.evas);
48    evas_object_clip_set(sd->backing_store, clip);
49    evas_object_smart_member_add(clip, o);
50    evas_object_show(clip);
51
52    evas_object_event_callback_add
53        (sd->backing_store, EVAS_CALLBACK_DEL, _ewk_view_single_on_del, clip);
54}
55
56static Evas_Object *_ewk_view_single_smart_backing_store_add(Ewk_View_Smart_Data *sd)
57{
58    Evas_Object *bs = evas_object_image_add(sd->base.evas);
59    evas_object_image_alpha_set(bs, EINA_FALSE);
60    evas_object_image_smooth_scale_set(bs, sd->zoom_weak_smooth_scale);
61
62    return bs;
63}
64
65static void _ewk_view_single_smart_resize(Evas_Object *o, Evas_Coord w, Evas_Coord h)
66{
67    Ewk_View_Smart_Data *sd = (Ewk_View_Smart_Data*)evas_object_smart_data_get(o);
68    _parent_sc.sc.resize(o, w, h);
69
70    // these should be queued and processed in calculate as well!
71    evas_object_image_size_set(sd->backing_store, w, h);
72    if (sd->animated_zoom.zoom.current < 0.00001) {
73        Evas_Object *clip = evas_object_clip_get(sd->backing_store);
74        Evas_Coord x, y, cw, ch;
75        evas_object_image_fill_set(sd->backing_store, 0, 0, w, h);
76        evas_object_geometry_get(sd->backing_store, &x, &y, 0, 0);
77        evas_object_move(clip, x, y);
78        ewk_frame_contents_size_get(sd->main_frame, &cw, &ch);
79        if (w > cw)
80            w = cw;
81        if (h > ch)
82            h = ch;
83        evas_object_resize(clip, w, h);
84    }
85}
86
87static inline void _ewk_view_4b_move_region_up(uint32_t *image, size_t rows, size_t x, size_t y, size_t w, size_t h, size_t rowsize)
88{
89    uint32_t *src;
90    uint32_t *dst;
91
92    dst = image + x + y * rowsize;
93    src = dst + rows * rowsize;
94    h -= rows;
95
96    for (; h > 0; h--, dst += rowsize, src += rowsize)
97        memcpy(dst, src, w * 4);
98}
99
100static inline void _ewk_view_4b_move_region_down(uint32_t *image, size_t rows, size_t x, size_t y, size_t w, size_t h, size_t rowsize)
101{
102    uint32_t *src;
103    uint32_t *dst;
104
105    h -= rows;
106    src = image + x + (y + h - 1) * rowsize;
107    dst = src + rows * rowsize;
108
109    for (; h > 0; h--, dst -= rowsize, src -= rowsize)
110        memcpy(dst, src, w * 4);
111}
112
113static inline void _ewk_view_4b_move_line_left(uint32_t *dst, const uint32_t *src, size_t count)
114{
115    uint32_t *dst_end = dst + count;
116    /* no memcpy() as it does not allow overlapping regions */
117    /* no memmove() as it will copy to a temporary buffer */
118    /* TODO: loop unrolling, copying up to quad-words would help */
119    for (; dst < dst_end; dst++, src++)
120        *dst = *src;
121}
122
123static inline void _ewk_view_4b_move_line_right(uint32_t *dst, uint32_t *src, size_t count)
124{
125    uint32_t *dst_end = dst - count;
126    /* no memcpy() as it does not allow overlapping regions */
127    /* no memmove() as it will copy to a temporary buffer */
128    /* TODO: loop unrolling, copying up to quad-words would help */
129    for (; dst > dst_end; dst--, src--)
130        *dst = *src;
131}
132
133static inline void _ewk_view_4b_move_region_left(uint32_t *image, size_t cols, size_t x, size_t y, size_t w, size_t h, size_t rowsize)
134{
135    uint32_t *src;
136    uint32_t *dst;
137
138    dst = image + x + y * rowsize;
139    src = dst + cols;
140    w -= cols;
141
142    for (; h > 0; h--, dst += rowsize, src += rowsize)
143        _ewk_view_4b_move_line_left(dst, src, w);
144}
145
146static inline void _ewk_view_4b_move_region_right(uint32_t *image, size_t cols, size_t x, size_t y, size_t w, size_t h, size_t rowsize)
147{
148    uint32_t *src;
149    uint32_t *dst;
150
151    w -= cols;
152    src = image + (x + w - 1) + y * rowsize;
153    dst = src + cols;
154
155    for (; h > 0; h--, dst += rowsize, src += rowsize)
156        _ewk_view_4b_move_line_right(dst, src, w);
157}
158
159/* catch-all function, not as optimized as the others, but does the work. */
160static inline void _ewk_view_4b_move_region(uint32_t *image, int dx, int dy, size_t x, size_t y, size_t w, size_t h, size_t rowsize)
161{
162    uint32_t *src;
163    uint32_t *dst;
164
165    if (dy < 0) {
166        h += dy;
167        dst = image + x + y * rowsize;
168        src = dst - dy * rowsize;
169        if (dx <= 0) {
170            w += dx;
171            src -= dx;
172            for (; h > 0; h--, dst += rowsize, src += rowsize)
173                _ewk_view_4b_move_line_left(dst, src, w);
174        } else {
175            w -= dx;
176            src += w - 1;
177            dst += w + dx -1;
178            for (; h > 0; h--, dst += rowsize, src += rowsize)
179                _ewk_view_4b_move_line_right(dst, src, w);
180        }
181    } else {
182        h -= dy;
183        src = image + x + (y + h - 1) * rowsize;
184        dst = src + dy * rowsize;
185        if (dx <= 0) {
186            w += dx;
187            src -= dx;
188            for (; h > 0; h--, dst -= rowsize, src -= rowsize)
189                _ewk_view_4b_move_line_left(dst, src, w);
190        } else {
191            w -= dx;
192            src += w - 1;
193            dst += w + dx - 1;
194            for (; h > 0; h--, dst -= rowsize, src -= rowsize)
195                _ewk_view_4b_move_line_right(dst, src, w);
196        }
197    }
198}
199
200static inline void _ewk_view_single_scroll_process_single(Ewk_View_Smart_Data *sd, void *pixels, Evas_Coord ow, Evas_Coord oh, const Ewk_Scroll_Request *sr)
201{
202    Evas_Coord sx, sy, sw, sh;
203
204    DBG("%d,%d + %d,%d %+03d,%+03d, store: %p %dx%d",
205        sr->x, sr->y, sr->w, sr->h, sr->dx, sr->dy, pixels, ow, oh);
206
207    sx = sr->x;
208    sy = sr->y;
209    sw = sr->w;
210    sh = sr->h;
211
212    if (abs(sr->dx) >= sw || abs(sr->dy) >= sh) {
213        /* doubt webkit would be so stupid... */
214        DBG("full page scroll %+03d,%+03d. convert to repaint %d,%d + %dx%d",
215            sr->dx, sr->dy, sx, sy, sw, sh);
216        ewk_view_repaint_add(sd->_priv, sx, sy, sw, sh);
217        return;
218    }
219
220    if (sx < 0) {
221        sw += sx;
222        sx = 0;
223    }
224    if (sy < 0) {
225        sh += sy;
226        sy = 0;
227    }
228
229    if (sx + sw > ow)
230        sw = ow - sx;
231    if (sy + sh > oh)
232        sh = oh - sy;
233
234    if (sw < 0)
235        sw = 0;
236    if (sh < 0)
237        sh = 0;
238
239    EINA_SAFETY_ON_TRUE_RETURN(!sw || !sh);
240    if (!sr->dx) {
241        if (sr->dy < 0) {
242            DBG("scroll up: %+03d,%+03d update=%d,%d+%dx%d, "
243                "repaint=%d,%d+%dx%d",
244                sr->dx, sr->dy, sx, sy, sw, sh + sr->dy,
245                sx, sy + sh + sr->dy, sw, -sr->dy);
246
247            _ewk_view_4b_move_region_up
248                ((uint32_t*)pixels, -sr->dy, sx, sy, sw, sh, ow);
249            evas_object_image_data_update_add
250                (sd->backing_store, sx, sy, sw, sh + sr->dy);
251
252            ewk_view_repaint_add(sd->_priv, sx, sy + sh + sr->dy, sw, -sr->dy);
253        } else if (sr->dy > 0) {
254            DBG("scroll down: %+03d,%+03d update=%d,%d+%dx%d, "
255                "repaint=%d,%d+%dx%d",
256                sr->dx, sr->dy, sx, sy + sr->dy, sw, sh - sr->dy,
257                sx, sy, sw, sr->dy);
258
259            _ewk_view_4b_move_region_down
260                ((uint32_t*)pixels, sr->dy, sx, sy, sw, sh, ow);
261            evas_object_image_data_update_add
262                (sd->backing_store, sx, sy + sr->dy, sw, sh - sr->dy);
263
264            ewk_view_repaint_add(sd->_priv, sx, sy, sw, sr->dy);
265        }
266    } else if (!sr->dy) {
267        if (sr->dx < 0) {
268            DBG("scroll left: %+03d,%+03d update=%d,%d+%dx%d, "
269                "repaint=%d,%d+%dx%d",
270                sr->dx, sr->dy, sx, sy, sw + sr->dx, sh,
271                sx + sw + sr->dx, sy, -sr->dx, sh);
272
273            _ewk_view_4b_move_region_left
274                ((uint32_t*)pixels, -sr->dx, sx, sy, sw, sh, ow);
275            evas_object_image_data_update_add
276                (sd->backing_store, sx, sy, sw + sr->dx, sh);
277
278            ewk_view_repaint_add(sd->_priv, sx + sw + sr->dx, sy, -sr->dx, sh);
279        } else if (sr->dx > 0) {
280            DBG("scroll up: %+03d,%+03d update=%d,%d+%dx%d, "
281                "repaint=%d,%d+%dx%d",
282                sr->dx, sr->dy, sx + sr->dx, sy, sw - sr->dx, sh,
283                sx, sy, sr->dx, sh);
284
285            _ewk_view_4b_move_region_right
286                ((uint32_t*)pixels, sr->dx, sx, sy, sw, sh, ow);
287            evas_object_image_data_update_add
288                (sd->backing_store, sx + sr->dx, sy, sw - sr->dx, sh);
289
290            ewk_view_repaint_add(sd->_priv, sx, sy, sr->dx, sh);
291        }
292    } else {
293        Evas_Coord mx, my, mw, mh, ax, ay, aw, ah, bx, by, bw, bh;
294
295        if (sr->dx < 0) {
296            mx = sx;
297            mw = sw + sr->dx;
298            ax = mx + mw;
299            aw = -sr->dx;
300        } else {
301            ax = sx;
302            aw = sr->dx;
303            mx = ax + aw;
304            mw = sw - sr->dx;
305        }
306
307        if (sr->dy < 0) {
308            my = sy;
309            mh = sh + sr->dy;
310            by = my + mh;
311            bh = -sr->dy;
312        } else {
313            by = sy;
314            bh = sr->dy;
315            my = by + bh;
316            mh = sh - sr->dy;
317        }
318
319        ay = my;
320        ah = mh;
321        bx = sx;
322        bw = sw;
323
324        DBG("scroll diagonal: %+03d,%+03d update=%d,%d+%dx%d, "
325            "repaints: h=%d,%d+%dx%d v=%d,%d+%dx%d",
326            sr->dx, sr->dy, mx, my, mw, mh, ax, ay, aw, ah, bx, by, bw, bh);
327
328        _ewk_view_4b_move_region
329            ((uint32_t*)pixels, sr->dx, sr->dy, sx, sy, sw, sh, ow);
330
331        evas_object_image_data_update_add(sd->backing_store, mx, my, mw, mh);
332        ewk_view_repaint_add(sd->_priv, ax, ay, aw, ah);
333        ewk_view_repaint_add(sd->_priv, bx, by, bw, bh);
334    }
335}
336
337static Eina_Bool _ewk_view_single_smart_scrolls_process(Ewk_View_Smart_Data *sd)
338{
339    const Ewk_Scroll_Request *sr;
340    const Ewk_Scroll_Request *sr_end;
341    Evas_Coord ow, oh;
342    size_t count;
343    void *pixels = evas_object_image_data_get(sd->backing_store, 1);
344    evas_object_image_size_get(sd->backing_store, &ow, &oh);
345
346    sr = ewk_view_scroll_requests_get(sd->_priv, &count);
347    sr_end = sr + count;
348    for (; sr < sr_end; sr++)
349        _ewk_view_single_scroll_process_single(sd, pixels, ow, oh, sr);
350
351    evas_object_image_data_set(sd->backing_store, pixels);
352
353    return EINA_TRUE;
354}
355
356static Eina_Bool _ewk_view_single_smart_repaints_process(Ewk_View_Smart_Data *sd)
357{
358    Ewk_View_Paint_Context *ctxt;
359    Evas_Coord ow, oh;
360    void *pixels;
361    Eina_Rectangle *r;
362    const Eina_Rectangle *pr;
363    const Eina_Rectangle *pr_end;
364    Eina_Tiler *tiler;
365    Eina_Iterator *itr;
366    cairo_status_t status;
367    cairo_surface_t *surface;
368    cairo_format_t format;
369    cairo_t *cairo;
370    size_t count;
371    Eina_Bool ret = EINA_TRUE;
372
373    if (sd->animated_zoom.zoom.current < 0.00001) {
374        Evas_Object *clip = evas_object_clip_get(sd->backing_store);
375        Evas_Coord w, h, cw, ch;
376        // reset effects of zoom_weak_set()
377        evas_object_image_fill_set
378            (sd->backing_store, 0, 0, sd->view.w, sd->view.h);
379        evas_object_move(clip, sd->view.x, sd->view.y);
380
381        w = sd->view.w;
382        h = sd->view.h;
383
384        ewk_frame_contents_size_get(sd->main_frame, &cw, &ch);
385        if (w > cw)
386            w = cw;
387        if (h > ch)
388            h = ch;
389        evas_object_resize(clip, w, h);
390    }
391
392    pixels = evas_object_image_data_get(sd->backing_store, 1);
393    evas_object_image_size_get(sd->backing_store, &ow, &oh);
394
395    if (sd->bg_color.a < 255)
396        format = CAIRO_FORMAT_ARGB32;
397    else
398        format = CAIRO_FORMAT_RGB24;
399
400    surface = cairo_image_surface_create_for_data
401        ((unsigned char*)pixels, format, ow, oh, ow * 4);
402    status = cairo_surface_status(surface);
403    if (status != CAIRO_STATUS_SUCCESS) {
404        ERR("could not create surface from data %dx%d: %s",
405            ow, oh, cairo_status_to_string(status));
406        ret = EINA_FALSE;
407        goto error_cairo_surface;
408    }
409    cairo = cairo_create(surface);
410    status = cairo_status(cairo);
411    if (status != CAIRO_STATUS_SUCCESS) {
412        ERR("could not create cairo from surface %dx%d: %s",
413            ow, oh, cairo_status_to_string(status));
414        ret = EINA_FALSE;
415        goto error_cairo;
416    }
417
418    ctxt = ewk_view_paint_context_new(sd->_priv, cairo);
419    if (!ctxt) {
420        ERR("could not create paint context");
421        ret = EINA_FALSE;
422        goto error_paint_context;
423    }
424
425    tiler = eina_tiler_new(ow, oh);
426    if (!tiler) {
427        ERR("could not create tiler %dx%d", ow, oh);
428        ret = EINA_FALSE;
429        goto error_tiler;
430    }
431
432    pr = ewk_view_repaints_get(sd->_priv, &count);
433    pr_end = pr + count;
434    for (; pr < pr_end; pr++)
435        eina_tiler_rect_add(tiler, pr);
436
437    itr = eina_tiler_iterator_new(tiler);
438    if (!itr) {
439        ERR("could not get iterator for tiler");
440        ret = EINA_FALSE;
441        goto error_iterator;
442    }
443
444    int sx, sy;
445    ewk_frame_scroll_pos_get(sd->main_frame, &sx, &sy);
446
447    EINA_ITERATOR_FOREACH(itr, r) {
448        Eina_Rectangle scrolled_rect = {
449            r->x + sx, r->y + sy,
450            r->w, r->h
451        };
452
453        ewk_view_paint_context_save(ctxt);
454
455        if ((sx) || (sy))
456            ewk_view_paint_context_translate(ctxt, -sx, -sy);
457
458        ewk_view_paint_context_clip(ctxt, &scrolled_rect);
459        ewk_view_paint_context_paint_contents(ctxt, &scrolled_rect);
460
461        ewk_view_paint_context_restore(ctxt);
462        evas_object_image_data_update_add
463            (sd->backing_store, r->x, r->y, r->w, r->h);
464    }
465    eina_iterator_free(itr);
466
467error_iterator:
468    eina_tiler_free(tiler);
469error_tiler:
470    ewk_view_paint_context_free(ctxt);
471error_paint_context:
472    cairo_destroy(cairo);
473error_cairo:
474    cairo_surface_destroy(surface);
475error_cairo_surface:
476    evas_object_image_data_set(sd->backing_store, pixels); /* dec refcount */
477
478    return ret;
479}
480
481static Eina_Bool _ewk_view_single_smart_zoom_weak_set(Ewk_View_Smart_Data *sd, float zoom, Evas_Coord cx, Evas_Coord cy)
482{
483    // TODO: review
484    float scale = zoom / sd->animated_zoom.zoom.start;
485    Evas_Coord w = sd->view.w * scale;
486    Evas_Coord h = sd->view.h * scale;
487    Evas_Coord dx, dy, cw, ch;
488    Evas_Object *clip = evas_object_clip_get(sd->backing_store);
489
490    ewk_frame_contents_size_get(sd->main_frame, &cw, &ch);
491    if (sd->view.w > 0 && sd->view.h > 0) {
492        dx = (w * (sd->view.w - cx)) / sd->view.w;
493        dy = (h * (sd->view.h - cy)) / sd->view.h;
494    } else {
495        dx = 0;
496        dy = 0;
497    }
498
499    evas_object_image_fill_set(sd->backing_store, cx + dx, cy + dy, w, h);
500
501    if (sd->view.w > 0 && sd->view.h > 0) {
502        dx = ((sd->view.w - w) * cx) / sd->view.w;
503        dy = ((sd->view.h - h) * cy) / sd->view.h;
504    } else {
505        dx = 0;
506        dy = 0;
507    }
508    evas_object_move(clip, sd->view.x + dx, sd->view.y + dy);
509
510    if (cw < sd->view.w)
511        w = cw * scale;
512    if (ch < sd->view.h)
513        h = ch * scale;
514    evas_object_resize(clip, w, h);
515    return EINA_TRUE;
516}
517
518static void _ewk_view_single_smart_zoom_weak_smooth_scale_set(Ewk_View_Smart_Data *sd, Eina_Bool smooth_scale)
519{
520    evas_object_image_smooth_scale_set(sd->backing_store, smooth_scale);
521}
522
523static void _ewk_view_single_smart_bg_color_set(Ewk_View_Smart_Data *sd, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
524{
525    evas_object_image_alpha_set(sd->backing_store, a < 255);
526}
527
528/**
529 * Sets the smart class api using single backing store, enabling view
530 * to be inherited.
531 *
532 * @param api class definition to be set, all members with the
533 *        exception of Evas_Smart_Class->data may be overridden. Must
534 *        @b not be @c NULL.
535 *
536 * @note Evas_Smart_Class->data is used to implement type checking and
537 *       is not supposed to be changed/overridden. If you need extra
538 *       data for your smart class to work, just extend
539 *       Ewk_View_Smart_Class instead.
540 *
541 * @return @c EINA_TRUE on success, @c EINA_FALSE on failure (probably
542 *         version mismatch).
543 *
544 * @see ewk_view_base_smart_set()
545 */
546Eina_Bool ewk_view_single_smart_set(Ewk_View_Smart_Class *api)
547{
548    if (!ewk_view_base_smart_set(api))
549        return EINA_FALSE;
550
551    if (EINA_UNLIKELY(!_parent_sc.sc.add))
552        ewk_view_base_smart_set(&_parent_sc);
553
554    api->sc.add = _ewk_view_single_smart_add;
555    api->sc.resize = _ewk_view_single_smart_resize;
556
557    api->backing_store_add = _ewk_view_single_smart_backing_store_add;
558    api->scrolls_process = _ewk_view_single_smart_scrolls_process;
559    api->repaints_process = _ewk_view_single_smart_repaints_process;
560    api->zoom_weak_set = _ewk_view_single_smart_zoom_weak_set;
561    api->zoom_weak_smooth_scale_set = _ewk_view_single_smart_zoom_weak_smooth_scale_set;
562    api->bg_color_set = _ewk_view_single_smart_bg_color_set;
563
564    return EINA_TRUE;
565}
566
567static inline Evas_Smart *_ewk_view_single_smart_class_new(void)
568{
569    static Ewk_View_Smart_Class api = EWK_VIEW_SMART_CLASS_INIT_NAME_VERSION("Ewk_View_Single");
570    static Evas_Smart *smart = 0;
571
572    if (EINA_UNLIKELY(!smart)) {
573        ewk_view_single_smart_set(&api);
574        smart = evas_smart_class_new(&api.sc);
575    }
576
577    return smart;
578}
579
580/**
581 * Creates a new EFL WebKit View object.
582 *
583 * View objects are the recommended way to deal with EFL WebKit as it
584 * abstracts the complex pieces of the process.
585 *
586 * Each view is composed by a set of frames. The set has at least one
587 * frame, called 'main_frame'. See ewk_view_frame_main_get() and
588 * ewk_view_frame_focused_get().
589 *
590 * @param e canvas where to create the view object.
591 *
592 * @return view object or @c NULL if errors.
593 *
594 * @see ewk_view_uri_set()
595 */
596Evas_Object *ewk_view_single_add(Evas *e)
597{
598    return evas_object_smart_add(e, _ewk_view_single_smart_class_new());
599}
600