#include "private.h" #include #include #define HACK_FLUSH_EVENTS_DURING_LONG_CALCULATE 1 #ifdef HACK_FLUSH_EVENTS_DURING_LONG_CALCULATE static const double LONG_CALCULATE_TIMEOUT = 0.6; #include #include #endif /* value to consider as double/float error, differences smaller than * this are ignored */ static const double DOUBLE_ERROR = 0.00001; /* constant to multiply offsets. * * The smaller the value, the faster it will stop. */ static const double KINETIC_FRICTION = 1.0; /* Minimum value to consider velocity worth doing kinetic animation */ static const Evas_Coord KINETIC_VELOCITY_THRESHOLD = 20; /* How long the auto zoom animation should take, in seconds */ static const double ZOOM_AUTO_ANIMATION_DURATION = 0.5; /* Timeout in seconds to consider double-click an auto zoom */ static const double ZOOM_AUTO_TIMEOUT = 0.2; /* Padding in pixels around object being zoomed */ static const Evas_Coord ZOOM_AUTO_PADDING = 15; static const float ZOOM_AUTO_MIN = 0.3; static const float ZOOM_AUTO_MAX = 4.0; static const float ZOOM_AUTO_MIN_DIFFERENCE = 0.01; /* How long the interactive zoom animation should take, in seconds */ static const double ZOOM_STEP_ANIMATION_DURATION = 0.8; /* Threshold in pixels to change zoom step. */ static const Evas_Coord ZOOM_STEP_THRESHOLD = 50; static const float ZOOM_STEPS[] = { 0.3, 0.5, 0.67, 0.8, 0.9, 1.0, 1.1, 1.2, 1.33, 1.5, 1.7, 2.0, 2.4, 3.0, }; static const unsigned int ZOOM_STEPS_LAST = sizeof(ZOOM_STEPS) / sizeof(ZOOM_STEPS[0]); /* * Structure filled by ewk_view_single_smart_set() from * view_add(). This contains the pointers to vanilla/pristine * ewk_view_single, which will be used to implement super() behavior * in C. */ static Ewk_View_Smart_Class _parent_sc = EWK_VIEW_SMART_CLASS_INIT_NULL; typedef struct _View_Smart_Data View_Smart_Data; #define VIEW_SD_GET_OR_RETURN(o, sd, ...)\ View_Smart_Data * sd = evas_object_smart_data_get(o);\ if (!sd)\ {\ CRITICAL("no smart data for object %p [%s]",\ o, evas_object_type_get(o));\ return __VA_ARGS__;\ } #define EWK_VIEW_SD_GET_OR_RETURN(o, sd, ...)\ Ewk_View_Smart_Data * sd = evas_object_smart_data_get(o);\ if (!sd)\ {\ CRITICAL("no smart data for object %p [%s]",\ o, evas_object_type_get(o));\ return __VA_ARGS__;\ } struct point_history { Evas_Coord x, y; double timestamp; }; /* Extends Ewk_View_Smart_Data to add some members we'll need */ struct _View_Smart_Data { /* Original type must be the first, so ewk_view and ewk_view_single know * how to access it (the memory offsets will remain the same). */ Ewk_View_Smart_Data base; /* where to keep track of our animators */ struct { Ecore_Animator *pan; Ecore_Animator *zoom; Ecore_Animator *kinetic; } animator; Ecore_Idler *idler_close_window; /* context used during pan/scroll animator */ struct { /* circular array with collected samples (see _view_smart_mouse_move()) */ #define PAN_HISTORY_SIZE (20) struct point_history history[PAN_HISTORY_SIZE]; unsigned int idx; /* index in the circular buffer of the last stored value */ unsigned int count; /* total number of samples collected */ struct point_history last_move; /* used by _view_animator_pan() */ } pan; Evas_Event_Mouse_Down mouse_down_copy; /* copy of down event, used by _view_smart_mouse_up() */ struct { struct { float zoom; Evas_Coord x, y; double timestamp; int step_idx; } start; struct { Evas_Coord y; float zoom; double timestamp; int step_idx; } last, next; struct { float zoom; double time; } diff; float min_zoom; Eina_Bool interactive : 1; } zoom; /* context used during kinetic animator */ struct { /* starting point */ struct { Evas_Coord dx, dy; double timestamp; } start; /* last run */ struct { Evas_Coord dx, dy; } last; /* flags to state we ended animation in those axis */ struct { Eina_Bool x : 1; Eina_Bool y : 1; } done; } kinetic; /* general flags */ struct { Eina_Bool first_calculate : 1; Eina_Bool animated_zoom : 1; } flags; Evas_Object *context_menu; }; /*********************************************************************** * Helper functions * **********************************************************************/ /* calculates one good zoom level to fit given hit test result */ static float _view_zoom_fits_hit(View_Smart_Data *sd, const Ewk_Hit_Test *hit_test) { Evas_Coord x, y; /* hit test point */ Evas_Coord bx, by, bw, bh, bw1, bw2, bh1, bh2; /* bounding box */ Evas_Coord vx, vy, vw, vh, vw1, vw2, vh1, vh2; /* visible content */ Evas_Coord cw, ch; float zx, zy, z, old_zoom, zx1, zx2, zy1, zy2; if (!hit_test) return 0.0; old_zoom = ewk_frame_zoom_get(sd->base.main_frame); /* save some typing */ x = hit_test->x; y = hit_test->y; bx = hit_test->bounding_box.x; by = hit_test->bounding_box.y; bw = hit_test->bounding_box.w; bh = hit_test->bounding_box.h; if ((bw <= 0) || (bh <= 0)) return 0.0; ewk_frame_visible_content_geometry_get (sd->base.main_frame, EINA_FALSE, &vx, &vy, &vw, &vh); if ((vw <= 0) || (vh <= 0)) return 0.0; ewk_frame_contents_size_get(sd->base.main_frame, &cw, &ch); if ((cw <= 0) || (ch <= 0)) return 0.0; /* split width of left and right parts from hit test position */ bw1 = x - bx; bw2 = bw - bw1; /* split height of top and bottom parts from hit test position */ bh1 = y - by; bh2 = bh - bh1; /* split size of viewport as well. We'll use this as the available space */ vw1 = x - vx; vw2 = vw - vw1; vh1 = y - vy; vh2 = vh - vh1; /* check if we can add padding around those sizes, "adding" if possible * (we actually substract as adding a pad is removing available space) */ if (vw1 > ZOOM_AUTO_PADDING) vw1 -= ZOOM_AUTO_PADDING; if (vw2 > ZOOM_AUTO_PADDING) vw2 -= ZOOM_AUTO_PADDING; if (vh1 > ZOOM_AUTO_PADDING) vh1 -= ZOOM_AUTO_PADDING; if (vh2 > ZOOM_AUTO_PADDING) vh2 -= ZOOM_AUTO_PADDING; /* check individual zoom to fit each part */ zx1 = (bw1 > 0) ? (vw1 / (float)bw1) : 0.0; zx2 = (bw2 > 0) ? (vw2 / (float)bw2) : 0.0; zy1 = (bh1 > 0) ? (vh1 / (float)bh1) : 0.0; zy2 = (bh2 > 0) ? (vh2 / (float)bh2) : 0.0; if ((zx1 >= ZOOM_AUTO_MIN_DIFFERENCE) && (zx2 >= ZOOM_AUTO_MIN_DIFFERENCE)) zx = (zx1 < zx2) ? zx1 : zx2; else if (zx1 >= ZOOM_AUTO_MIN_DIFFERENCE) zx = zx1; else zx = zx2; if ((zy1 >= ZOOM_AUTO_MIN_DIFFERENCE) && (zy2 >= ZOOM_AUTO_MIN_DIFFERENCE)) zy = (zy1 < zy2) ? zy1 : zy2; else if (zy1 >= ZOOM_AUTO_MIN_DIFFERENCE) zy = zy1; else zy = zy2; if ((zx >= ZOOM_AUTO_MIN_DIFFERENCE) && (zy >= ZOOM_AUTO_MIN_DIFFERENCE)) z = (zx < zy) ? zx : zy; else if (zx >= ZOOM_AUTO_MIN_DIFFERENCE) z = zx; else z = zy; /* zoom will make contents be smaller than viewport, limit it */ if (((int)(z * old_zoom * cw) < vw) || ((int)(z * old_zoom * ch) < vh)) { float ac = cw / (float)ch; float av = vw / (float)vh; if (ac < av) z = vw / (float)cw; else z = vh / (float)ch; } #if 0 /* debug */ printf(">>> fit: center=%3d,%3d box=%3d,%3d+%3dx%3d\n" " x: %3d = %3d + %3d, %3d = %3d + %3d, %2.4f %2.4f -> %2.4f\n" " y: %3d = %3d + %3d, %3d = %3d + %3d, %2.4f %2.4f -> %2.4f\n" " final: %2.4f %2.4f (old=%0.3f, difference=%0.3f)\n" " contents: %4dx%4d -> %4dx%4d\n" "\n", x, y, bx, by, bw, bh, bw, bw1, bw2, vw, vw1, vw2, zx1, zx2, zx, bh, bh1, bh2, vh, vh1, vh2, zy1, zy2, zy, z, old_zoom * z, old_zoom, fabs(old_zoom - z), cw, ch, (int)(z * old_zoom * cw), (int)(z * old_zoom * ch)); #endif z *= old_zoom; if (z < ZOOM_AUTO_MIN) z = ZOOM_AUTO_MIN; else if (z > ZOOM_AUTO_MAX) z = ZOOM_AUTO_MAX; if (fabs(old_zoom - z) < ZOOM_AUTO_MIN_DIFFERENCE) return 0.0; return z; } /* remove flag saying that object is animating zoom */ static void _view_zoom_animated_end(void *data, Evas_Object *view __UNUSED__, void *event_info __UNUSED__) { View_Smart_Data *sd = data; sd->flags.animated_zoom = EINA_FALSE; } /* ask for pre-render when load finished */ static void _view_load_finished(void *data, Evas_Object *view, void *event_info __UNUSED__) { View_Smart_Data *sd = data; float zoom = ewk_frame_zoom_get(sd->base.main_frame); Evas_Coord x, y, w, h; ewk_frame_visible_content_geometry_get (sd->base.main_frame, EINA_TRUE, &x, &y, &w, &h); w *= 2; h *= 2; INF("load finished, pre-render %d,%d+%dx%d at %0.2f", x, y, w, h, zoom); ewk_view_pre_render_region(view, x, y, w, h, zoom); } /* stop animators, we changed page */ static void _view_uri_changed(void *data, Evas_Object *view, void *event_info __UNUSED__) { View_Smart_Data *sd = data; if (sd->animator.pan) { ecore_animator_del(sd->animator.pan); sd->animator.pan = NULL; } if (sd->animator.kinetic) { ecore_animator_del(sd->animator.kinetic); sd->animator.kinetic = NULL; } if (sd->animator.zoom) { /* inform ewk_view that we finished performing zoom animation */ ewk_view_zoom_animated_mark_stop(view); evas_object_smart_callback_call(view, "zoom,interactive,end", NULL); ecore_animator_del(sd->animator.zoom); sd->animator.zoom = NULL; } } /* ask ewk_view to pre render in the given direction. * * This uses a heuristics to create a pre-render region using the * given motion vector. */ static void _view_pan_pre_render(View_Smart_Data *sd, Evas_Coord dx, Evas_Coord dy) { float zoom = ewk_frame_zoom_get(sd->base.main_frame); double weightx, weighty; Evas_Coord x, y, w, h, px, py, pw, ph; unsigned int vx, vy; /* where are we now */ ewk_frame_visible_content_geometry_get (sd->base.main_frame, EINA_TRUE, &x, &y, &w, &h); /* get absolute value for dx and dy, remove it's precision */ vx = abs(dx) >> 4; vy = abs(dy) >> 4; if (vx == 0 && vy == 0) return; /* motion vector is not significant, don't pre-render */ else if (vx < vy) { /* moving more on Y-axis than X. */ weightx = 0.5; weighty = 1.0; } else if (vx > vy) { /* moving more on X-axis than X. */ weightx = 1.0; weighty = 0.5; } else { /* moving equally on both axis, be more conservative */ weightx = 0.6; weighty = 0.6; } /* if values were not significant, zero their extra weight */ if (vx == 0) weightx = 0.0; else if (vy == 0) weighty = 0.0; /* use single region that includes existing viewport. * * This is simple and should work. Another option is to do more * detailed analysis and possible create special cases for * vertical, horizontal and diagonal moves, with diagonal being * smarter and rendering less than we do now. */ pw = w * (1.0 + weightx); ph = h * (1.0 + weighty); px = x; py = y; if (dx > 0) px -= (pw - w); if (dy > 0) py -= (ph - h); INF("pre-render region %d,%d+%dx%d at %0.2f (viewport=%d,%d+%dx%d)", px, py, pw, ph, zoom, x, y, w, h); ewk_view_pre_render_region(sd->base.self, px, py, pw, ph, zoom); } static unsigned int _view_zoom_closest_index_find(float zoom) { unsigned int i, close_idx; float close_zoom; close_idx = 0; close_zoom = 999999.99; for (i = 0; i < ZOOM_STEPS_LAST; i++) { float cur = fabs(zoom - ZOOM_STEPS[i]); if (cur < close_zoom) { close_idx = i; close_zoom = cur; if (cur < DOUBLE_ERROR) /* close enough */ break; } } return close_idx; } /*********************************************************************** * Animators: shared timers at fixed frame rate/interval. * **********************************************************************/ /* Animator that implements the kinetic animation (momentum). * * This is started by _view_smart_mouse_up() if it is worth doing the * animation. * * Code is heavily based on els_scroller.c from Elementary. */ static Eina_Bool _view_animator_kinetic(void *data) { View_Smart_Data *sd = data; double p, dt, now = ecore_loop_time_get(); Evas_Coord x, y, sx, sy, sw, sh; dt = now - sd->kinetic.start.timestamp; /* time difference is too small, ignore it */ if (dt <= DOUBLE_ERROR) goto end; dt = dt / KINETIC_FRICTION; if (dt > 1.0) dt = 1.0; p = 1.0 - ((1.0 - dt) * (1.0 - dt)); /* query scroll area and current position */ ewk_frame_scroll_pos_get(sd->base.main_frame, &sx, &sy); ewk_frame_scroll_size_get(sd->base.main_frame, &sw, &sh); if (!sd->kinetic.done.x) { /* we're not done on x-axis yet, calculate new displacement */ Evas_Coord dx = sd->kinetic.start.dx * KINETIC_FRICTION * p; x = sd->kinetic.last.dx - dx; sd->kinetic.last.dx = dx; /* check if new displacement fit in scroll area */ if (sx + x < 0) { x = -sx; sd->kinetic.done.x = EINA_TRUE; } else if (sx + x >= sw) { x = sw - sx; sd->kinetic.done.x = EINA_TRUE; } } else x = 0; if (!sd->kinetic.done.y) { /* we're not done on y-axis yet, calculate new displacement */ Evas_Coord dy = sd->kinetic.start.dy * KINETIC_FRICTION * p; y = sd->kinetic.last.dy - dy; sd->kinetic.last.dy = dy; /* check if new displacement fit in scroll area */ if (sy + y < 0) { y = -sy; sd->kinetic.done.y = EINA_TRUE; } else if (sy + y >= sh) { y = sh - sy; sd->kinetic.done.y = EINA_TRUE; } } else y = 0; /* if there is anything to scroll, ask it */ if (x != 0 || y != 0) ewk_frame_scroll_add(sd->base.main_frame, x, y); /* if we finished our work, just stop the animator */ if (dt >= 1.0 || (sd->kinetic.done.x && sd->kinetic.done.y)) { _view_pan_pre_render(sd, sd->kinetic.start.dx, sd->kinetic.start.dy); sd->animator.kinetic = NULL; return ECORE_CALLBACK_CANCEL; } end: return ECORE_CALLBACK_RENEW; /* keep running until we finish */ } /* Animator to apply scroll/pan at fixed frame rate. * * If pointer (mouse) was moved since last run, then request main * frame to scroll. */ static Eina_Bool _view_animator_pan(void *data) { View_Smart_Data *sd = data; Evas_Coord x, y, dx, dy; double timestamp; evas_pointer_canvas_xy_get(sd->base.base.evas, &x, &y); /* webkit is swapped */ dx = sd->pan.last_move.x - x; dy = sd->pan.last_move.y - y; /* ignore this sample if it did not change */ if ((dx == 0) && (dy == 0)) goto end; timestamp = ecore_loop_time_get(); if (timestamp <= sd->pan.last_move.timestamp) /* did time went backwards?! */ goto end; /* request scroll by same displacement */ ewk_frame_scroll_add(sd->base.main_frame, dx, dy); /* save new last position */ sd->pan.last_move.x = x; sd->pan.last_move.y = y; sd->pan.last_move.timestamp = timestamp; end: return ECORE_CALLBACK_RENEW; /* keep running until something else remove */ } /* start pan/scroll animation */ static void _view_pan_start(View_Smart_Data *sd, const Evas_Event_Mouse_Down *ev) { struct point_history *p; /* not really required, but let's clean it */ memset(&sd->pan, 0, sizeof(sd->pan)); /* start history with one sample being the down position */ sd->pan.idx = 0; sd->pan.count = 1; p = sd->pan.history; p->x = ev->canvas.x; p->y = ev->canvas.y; p->timestamp = ecore_loop_time_get(); sd->pan.last_move = *p; ewk_view_pre_render_cancel(sd->base.self); /* register function to collect samples and apply scrolls at fixed interval*/ if (!sd->animator.pan) sd->animator.pan = ecore_animator_add(_view_animator_pan, sd); } /* stop pan animation and possible schedule a kinetic scrolling. * * @return @c EINA_TRUE if event was handled inside this function and * should not be used by calling function or @c EINA_FALSE if * event is unused and should be forwarded by caller. */ static Eina_Bool _view_pan_stop(View_Smart_Data *sd, const Evas_Event_Mouse_Up *ev) { double t, at; Evas_Coord ax, ay, dx, dy, vel; unsigned int i, todo, samples; /* Stop pan animator, we're done already. */ if (sd->animator.pan) { ecore_animator_del(sd->animator.pan); sd->animator.pan = NULL; } /* If we do not have enough samples, just assume it was a click */ if (sd->pan.count < 2) return EINA_FALSE; /* the following code is heavily based on els_scroller.c from Elementary. * * It will check the samples from history and get the average point. */ t = ecore_loop_time_get(); ax = ev->canvas.x; ay = ev->canvas.y; at = 0.0; i = sd->pan.idx; todo = sd->pan.count > PAN_HISTORY_SIZE ? PAN_HISTORY_SIZE : sd->pan.count; samples = 0; for (; todo > 0; todo--) { struct point_history *p = sd->pan.history + i; double dt = t - p->timestamp; if (dt > 0.2 && samples > 0) break; at += dt; ax += p->x; ay += p->y; samples++; if (i > 0) i--; else i = PAN_HISTORY_SIZE - 1; } /* time was too short, consider it a click */ if (at <= DOUBLE_ERROR) return EINA_FALSE; ax /= samples + 1; ay /= samples + 1; at *= 4.0; dx = ev->canvas.x - ax; dy = ev->canvas.y - ay; vel = sqrt((dx * dx) + (dy * dy)) / at; /* velocity was too short, consider it a click */ if (vel <= KINETIC_VELOCITY_THRESHOLD) { _view_pan_pre_render(sd, dx, dy); return EINA_FALSE; } /* it's really woth animating. setup kinetic animation context and start * animator function. */ sd->kinetic.start.timestamp = t; sd->kinetic.start.dx = dx; sd->kinetic.start.dy = dy; sd->kinetic.last.dx = 0; sd->kinetic.last.dy = 0; sd->kinetic.done.x = EINA_FALSE; sd->kinetic.done.y = EINA_FALSE; sd->animator.kinetic = ecore_animator_add(_view_animator_kinetic, sd); return EINA_TRUE; } /* Animator to collect mouse position and apply zoom at fixed frame rate. * * This animator works in two parts: * * 1. check if mouse moved up/down enough to change the zoom level * to another band. We move in bands specified in ZOOM_STEPS every * ZOOM_STEP_THRESHOLD pixels, up or down. * * 2. interpolates zoom level using weak zoom progressively until * we reach desired level (sd->zoom.next.zoom) in calculated * time slot (based on ZOOM_STEP_ANIMATION_DURATION). */ static Eina_Bool _view_animator_zoom(void *data) { View_Smart_Data *sd = data; Evas_Object *o = sd->base.self; Evas_Coord y, dy; double timestamp = ecore_loop_time_get(); int step_inc, idx; /* wait to know if we're in auto zoom or interactive zooming */ if (timestamp - sd->zoom.start.timestamp <= ZOOM_AUTO_TIMEOUT) goto end; if (!sd->zoom.interactive) { evas_object_smart_callback_call(o, "zoom,interactive,start", NULL); sd->zoom.interactive = EINA_TRUE; } evas_pointer_canvas_xy_get(sd->base.base.evas, NULL, &y); dy = y - sd->zoom.start.y; step_inc = dy / ZOOM_STEP_THRESHOLD; /* find out new step */ idx = step_inc + sd->zoom.start.step_idx; /* check for out of bounds access */ if (idx < 0) idx = 0; else if (idx >= (int)ZOOM_STEPS_LAST) idx = ZOOM_STEPS_LAST - 1; if (ZOOM_STEPS[idx] < sd->zoom.min_zoom) idx = sd->zoom.next.step_idx; /* Part 1. check if mouse moved enough to change zoom level band */ if (idx != sd->zoom.next.step_idx) { View_Zoom_Interactive data; /* new target (next) values */ sd->zoom.next.step_idx = idx; sd->zoom.next.zoom = ZOOM_STEPS[idx]; sd->zoom.next.timestamp = timestamp + ZOOM_STEP_ANIMATION_DURATION; sd->zoom.diff.zoom = sd->zoom.next.zoom - sd->zoom.last.zoom; sd->zoom.diff.time = sd->zoom.next.timestamp - sd->zoom.last.timestamp; /* we'll animated, disable smooth scaling in evas so it's faster */ ewk_view_zoom_weak_smooth_scale_set(o, EINA_FALSE); /* inform user that new level was requested */ data.x = sd->zoom.start.x; data.y = sd->zoom.start.y; data.zoom = sd->zoom.next.zoom; evas_object_smart_callback_call(o, "zoom,interactive", &data); } if (sd->zoom.next.timestamp < DOUBLE_ERROR) goto end; /* sd->zoom.next.timestamp is zero, we're stopped */ /* Part 2. interpolate values to animate zoom change */ else if (timestamp >= sd->zoom.next.timestamp) { /* we're done, enable smooth scaling so the still image looks better * and apply the final zoom level */ ewk_view_zoom_weak_smooth_scale_set(o, EINA_TRUE); ewk_view_zoom_weak_set (o, sd->zoom.next.zoom, sd->zoom.start.x, sd->zoom.start.y); sd->zoom.last = sd->zoom.next; sd->zoom.next.timestamp = 0.0; /* say we're stopped, see above */ } else if (sd->zoom.diff.time > 0.0) { /* regular intermediate animation frame, interpolate and apply */ float zoom, p; p = (timestamp - sd->zoom.last.timestamp) / sd->zoom.diff.time; zoom = sd->zoom.last.zoom + sd->zoom.diff.zoom * p; ewk_view_zoom_weak_set(o, zoom, sd->zoom.start.x, sd->zoom.start.y); } end: return ECORE_CALLBACK_RENEW; /* keep running until something else remove */ } /* start zoom animation */ static void _view_zoom_start(View_Smart_Data *sd, const Evas_Event_Mouse_Down *ev) { Evas_Object *frame = sd->base.main_frame; Evas_Coord cw, ch, vw, vh; float z, zx, zy; ewk_view_pre_render_cancel(sd->base.self); /* remember starting point so we have a reference */ sd->zoom.start.zoom = ewk_frame_zoom_get(frame); sd->zoom.start.x = ev->canvas.x; sd->zoom.start.y = ev->canvas.y; sd->zoom.start.timestamp = ecore_loop_time_get(); /* find out which zoom level band we're in, closest match */ sd->zoom.start.step_idx = _view_zoom_closest_index_find(sd->zoom.start.zoom); sd->zoom.last.y = sd->zoom.start.x; sd->zoom.last.zoom = sd->zoom.start.zoom; sd->zoom.last.timestamp = sd->zoom.start.timestamp; sd->zoom.next.y = sd->zoom.start.x; sd->zoom.next.zoom = sd->zoom.start.zoom; sd->zoom.next.timestamp = 0.0; /* this means we're stopped */ sd->zoom.diff.zoom = 0.0; sd->zoom.diff.time = 0.0; sd->zoom.interactive = EINA_FALSE; /* find out the minimum zoom level that contents is greater or equal * viewport size */ ewk_frame_visible_content_geometry_get(frame, 0, NULL, NULL, &vw, &vh); ewk_frame_contents_size_get(frame, &cw, &ch); zx = vw / (float)cw; zy = vh / (float)ch; z = (zx > zy) ? zx : zy; z *= sd->zoom.start.zoom; if (z >= ZOOM_STEPS[0]) sd->zoom.min_zoom = z; else sd->zoom.min_zoom = ZOOM_STEPS[0]; /* inform ewk_view that we'll perform an animation with zoom */ ewk_view_zoom_animated_mark_start(sd->base.self, sd->zoom.start.zoom); /* register function to query pointer position and apply new zoom */ if (!sd->animator.zoom) sd->animator.zoom = ecore_animator_add(_view_animator_zoom, sd); } /* stop zoom animation. * * @return @c EINA_TRUE if event was handled inside this function and * should not be used by calling function or @c EINA_FALSE if * event is unused and should be forwarded by caller. */ static Eina_Bool _view_zoom_stop(View_Smart_Data *sd, const Evas_Event_Mouse_Up *ev __UNUSED__) { double timestamp = ecore_loop_time_get(); Evas_Object *view = sd->base.self; Evas_Object *frame = sd->base.main_frame; const Evas_Coord x = sd->zoom.start.x, y = sd->zoom.start.y; /* Stop zoom animator, we're done already. */ if (sd->animator.zoom) { ecore_animator_del(sd->animator.zoom); sd->animator.zoom = NULL; } /* inform ewk_view that we finished performing zoom animation */ ewk_view_zoom_animated_mark_stop(view); evas_object_smart_callback_call(view, "zoom,interactive,end", NULL); /* if it was an auto zoom (zoom to fit some element) */ if (timestamp - sd->zoom.last.timestamp < ZOOM_AUTO_TIMEOUT) { Ewk_Hit_Test *hit_test = ewk_frame_hit_test_new(frame, x, y); float zoom = _view_zoom_fits_hit(sd, hit_test); ewk_frame_hit_test_free(hit_test); if (zoom > 0.0) { /* wait until animation ends (see _view_zoom_animated_end) */ sd->flags.animated_zoom = EINA_TRUE; ewk_view_zoom_animated_set (view, zoom, ZOOM_AUTO_ANIMATION_DURATION, x, y); } return EINA_TRUE; } /* otherwise, apply definitive zoom */ ewk_view_zoom_set(view, sd->zoom.next.zoom, x, y); return EINA_TRUE; } /************** Event handlers *****************/ static Eina_Bool _view_contextmenu_free(void *data) { Evas_Object *notify = data; evas_object_del(notify); return EINA_FALSE; } static void _view_contextmenu_item_selected(void *data, Evas_Object *li, void *event_info) { Ewk_Context_Menu_Item *item = data; Evas_Object *view = evas_object_data_get(li, "view"); Ewk_Context_Menu *menu = view_context_menu_get(view); ewk_context_menu_item_select(menu, item); ewk_context_menu_destroy(menu); } static void _view_contextmenu_cancel(void *data, Evas_Object *notify __UNUSED__, void *event_info __UNUSED__) { Ewk_Context_Menu *menu = data; ewk_context_menu_destroy(menu); } static void on_view_contextmenu_show(void *data __UNUSED__, Evas_Object *view, void *event_info) { Ewk_Context_Menu *menu = event_info; const Eina_List *l, *items = ewk_context_menu_item_list_get(menu); Ewk_Context_Menu_Item *item; Evas_Object *chrome = evas_object_data_get(view, "chrome"); Evas_Object *notify, *li; Elm_List_Item *it; if (eina_list_count(items) == 0) { WRN("Context Menu with no items. Aborting operation."); ewk_context_menu_destroy(menu); } notify = elm_notify_add(chrome); elm_object_style_set(notify, "ewebkit"); evas_object_size_hint_weight_set(notify, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); elm_notify_repeat_events_set(notify, EINA_FALSE); elm_notify_orient_set(notify, ELM_NOTIFY_ORIENT_BOTTOM); li = elm_list_add(view); elm_object_style_set(li, "ewebkit"); elm_list_always_select_mode_set(li, 1); evas_object_size_hint_weight_set(li, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_fill_set(li, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_size_hint_min_set(li, 200, 200); elm_notify_content_set(notify, li); evas_object_smart_callback_add(notify, "block,clicked", _view_contextmenu_cancel, menu); evas_object_data_set(li, "view", view); EINA_LIST_FOREACH(items, l, item) { it = elm_list_item_append(li, ewk_context_menu_item_title_get(item), 0, 0, _view_contextmenu_item_selected, item); elm_list_item_separator_set(it, ewk_context_menu_item_type_get(item) == EWK_SEPARATOR_TYPE); } view_context_menu_set(view, notify, menu); elm_list_go(li); evas_object_show(notify); } static void on_view_contextmenu_new(void *data, Evas_Object *view __UNUSED__, void *event_info) { DBG("menu=%p sd=%p", event_info, data); Ewk_Context_Menu *menu = event_info; ewk_context_menu_ref(menu); } static void on_view_contextmenu_freed(void *data, Evas_Object *view, void *event_info) { DBG("menu=%p view=%p", event_info, data); Ewk_Context_Menu *menu = event_info; Evas_Object *notify = view_context_menu_widget_get(view); view_context_menu_set(view, NULL, NULL); ewk_context_menu_unref(menu); ecore_idler_add(_view_contextmenu_free, notify); } /*********************************************************************** * Smart Class Methods: * * * * Implements callback interface defined by Evas with Evas_Smart_Class * * and WebKit-EFL with Ewk_View_Smart_Class, a super-set of Evas. * * * * This allow us to inherit the class to override or extend, such as * * creating a bigger smart data structure to account our animators and * * other context data and automatically apply fixed layout size when * * object size changes. **********************************************************************/ /* object constructor. receives new object to set its internal state */ static void _view_smart_add(Evas_Object *o) { /* Create our extension of Ewk_View_Smart_Data so we can have our own * context, such as the animators and extra flags. */ View_Smart_Data *sd = calloc(1, sizeof(View_Smart_Data)); evas_object_smart_data_set(o, sd); sd->flags.first_calculate = EINA_TRUE; /* call parent and let it do the whole thing */ _parent_sc.sc.add(o); /* set some common properties, such as theme and default zoom weak method */ ewk_view_theme_set(o, PACKAGE_DATA_DIR "/ewebkit.edj"); ewk_view_zoom_weak_smooth_scale_set(o, EINA_FALSE); evas_object_smart_callback_add (o, "zoom,animated,end", _view_zoom_animated_end, sd); evas_object_smart_callback_add (o, "load,finished", _view_load_finished, sd); evas_object_smart_callback_add (o, "uri,changed", _view_uri_changed, sd); evas_object_smart_callback_add (o, "contextmenu,new", on_view_contextmenu_new, sd); evas_object_smart_callback_add (o, "contextmenu,free", on_view_contextmenu_freed, sd); evas_object_smart_callback_add (o, "contextmenu,show", on_view_contextmenu_show, sd); } /* object destructor. receives object that dies when this function returns */ static void _view_smart_del(Evas_Object *o) { VIEW_SD_GET_OR_RETURN(o, sd); /* Must delete all extra fields before calling parent method, * as parent's will free View_Smart_Data. */ if (sd->animator.pan) ecore_animator_del(sd->animator.pan); if (sd->animator.zoom) ecore_animator_del(sd->animator.zoom); if (sd->animator.kinetic) ecore_animator_del(sd->animator.kinetic); if (sd->idler_close_window) ecore_idler_del(sd->idler_close_window); _parent_sc.sc.del(o); } /* calculate is called on dirty objects (evas_object_smart_changed()) * right before Evas paints the new scene. * * It is a good place to do last-minute calculations and checks, * allowing one to postpone such tasks as much as possible. * * For example, if user do for (i=0; i<1000; i++) * evas_object_resize(o, w, h), then Evas will just use the last value * when painting, however it will call Evas_Smart_Class::resize() lots * of times, as well as EVAS_CALLBACK_RESIZE. Thus if we apply * ewk_view_fixed_layout_size_set() on these callbacks, we'd apply 999 * useless values! By postponing it to calculate(), we know we'll * apply just what matters. */ static void _view_smart_calculate(Evas_Object *o) { VIEW_SD_GET_OR_RETURN(o, sd); #ifdef HACK_FLUSH_EVENTS_DURING_LONG_CALCULATE Display *dpy = ecore_x_display_get(); double before = ecore_time_get(); XSync(dpy, False); /* force processing all events */ #endif /* call parent smart calculate and let ewk_view do all its work */ _parent_sc.sc.calculate(o); #ifdef HACK_FLUSH_EVENTS_DURING_LONG_CALCULATE double now = ecore_time_get(); double elapsed = now - before; if (elapsed > LONG_CALCULATE_TIMEOUT) { WRN("calulate took too long (%0.3f of %0.3f), ignoring events.", elapsed, LONG_CALCULATE_TIMEOUT); XSync(dpy, True); /* throw away events received during this timeout */ } #endif } /* called by ewk_view when mouse down events happen. * * here we'll check if we should start a pan/scroll if user move mouse * (drag) or if we should forward this event to parent method, that * feeds it to WebKit-EFL. * * The way we do it here breaks drag&drop of elements required by some * JavaScript websites such as GMail and Yahoo! Mail. We should do it * in a way that WebKit says if element can be dragged or not, but it * is not exposed by WebKit now. */ static Eina_Bool _view_smart_mouse_down(Ewk_View_Smart_Data *esd, const Evas_Event_Mouse_Down *ev) { View_Smart_Data *sd = (View_Smart_Data *)esd; /* do not handle down when doing animated zoom */ if (sd->flags.animated_zoom) return EINA_FALSE; /* mouse down immediately cancels previous kinetic animation */ if (sd->animator.kinetic) { ecore_animator_del(sd->animator.kinetic); sd->animator.kinetic = NULL; } /* we just want to start pan on button 1 (left) and not triple click */ if (ev->button != 1) goto forward_event; if (ev->flags & EVAS_BUTTON_TRIPLE_CLICK) goto forward_event; #if 0 // at the end, this is not good usability. /* no pan or zoom if click over editable or links */ Ewk_Hit_Test *hit_test = ewk_frame_hit_test_new (esd->main_frame, ev->canvas.x, ev->canvas.y); if (hit_test) { Eina_Bool skip = (hit_test->flags.editable || (hit_test->link.url && hit_test->link.url[0])); ewk_frame_hit_test_free(hit_test); if (skip && !(ev->flags & EVAS_BUTTON_DOUBLE_CLICK)) goto forward_event; } #endif /* cancel previous animators, if any (there should be none) */ if (sd->animator.pan) { ecore_animator_del(sd->animator.pan); sd->animator.pan = NULL; } if (sd->animator.zoom) { ecore_animator_del(sd->animator.zoom); sd->animator.zoom = NULL; } /* choose if we're zooming or panning */ if (ev->flags & EVAS_BUTTON_DOUBLE_CLICK) _view_zoom_start(sd, ev); else _view_pan_start(sd, ev); /* keep a copy in the case we notice a click instead of drag to pan/scroll */ sd->mouse_down_copy = *ev; return EINA_TRUE; forward_event: if (ev->button == 3) // forward of context menu event is special return ewk_view_context_menu_forward_event(sd->base.self, ev); /* If we should forward/feed event using parent class method, then * just do it and do NOT create an animator. See _view_smart_mouse_up(). */ return _parent_sc.mouse_down(esd, ev); } /* called by ewk_view when mouse up events happen. * * This pairs with _view_smart_mouse_down(), forwarding events if * required, feeding saved mouse down event or even starting the * kinetic animation. */ static Eina_Bool _view_smart_mouse_up(Ewk_View_Smart_Data *esd, const Evas_Event_Mouse_Up *ev) { View_Smart_Data *sd = (View_Smart_Data *)esd; Eina_Bool used; /* cancel any previous kinetic animation (but should have none) */ if (sd->animator.kinetic) { ecore_animator_del(sd->animator.kinetic); sd->animator.kinetic = NULL; } /* do not handle up when doing animated zoom */ if (sd->flags.animated_zoom) return EINA_FALSE; /* if it was not doing pan or zoom (see _view_smart_mouse_down()), * then we just feed mouse_up using parent method. */ if (sd->animator.pan) used = _view_pan_stop(sd, ev); else if (sd->animator.zoom) used = _view_zoom_stop(sd, ev); else return _parent_sc.mouse_up(esd, ev); if (used) return EINA_TRUE; /* it was not used, so just feed the saved down then * mouse up event. This is required to let WebKit process the * sequence correctly. */ _parent_sc.mouse_down(esd, &sd->mouse_down_copy); return _parent_sc.mouse_up(esd, ev); } /* called by ewk_view when mouse move events happen. * * Let's just ignore mouse move events while doing pan/scrolling or zooming. */ static Eina_Bool _view_smart_mouse_move(Ewk_View_Smart_Data *esd, const Evas_Event_Mouse_Move *ev) { View_Smart_Data *sd = (View_Smart_Data *)esd; if (sd->animator.pan) { /* account sample in circular array */ struct point_history *p; unsigned int next_idx = (sd->pan.idx + 1) % PAN_HISTORY_SIZE; p = sd->pan.history + next_idx; p->x = ev->cur.canvas.x; p->y = ev->cur.canvas.y; p->timestamp = ecore_loop_time_get(); sd->pan.idx = next_idx; sd->pan.count++; return EINA_FALSE; } if (sd->animator.zoom) return EINA_FALSE; return _parent_sc.mouse_move(esd, ev); } /* called by ewk_view when something goes to console * * We just print message to stdout */ static void _view_smart_add_console_message(Ewk_View_Smart_Data *esd, const char *message, unsigned int lineNumber, const char *sourceID) { printf("BROWSER console: %s @%d: %s\n", sourceID, lineNumber, message); } enum dialog_type { DIALOG_ALERT, DIALOG_CONFIRM, DIALOG_PROMPT }; struct _dialog_data { Evas_Object *notify; Evas_Object *bt_ok, *bt_cancel; Evas_Object *entry; Eina_Bool *response; }; static void _bt_close(void *data, Evas_Object *obj, void *event_info) { struct _dialog_data *d = data; *d->response = (obj == d->bt_ok); evas_object_hide(d->notify); ecore_main_loop_quit(); } static Eina_Bool _run_dialog(Evas_Object *parent, enum dialog_type type, const char *message, const char *default_entry_value, char **entry_value) { EINA_SAFETY_ON_TRUE_RETURN_VAL((type != DIALOG_PROMPT) && (!!default_entry_value), EINA_FALSE); EINA_SAFETY_ON_TRUE_RETURN_VAL((type != DIALOG_PROMPT) && (!!entry_value), EINA_FALSE); struct _dialog_data *dialog_data = calloc(1, sizeof(*dialog_data)); Eina_Bool response = EINA_FALSE; Evas_Object *bx_v, *lb; dialog_data->notify = elm_notify_add(parent); evas_object_size_hint_weight_set(dialog_data->notify, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); elm_notify_repeat_events_set(dialog_data->notify, EINA_FALSE); bx_v = elm_box_add(parent); elm_notify_content_set(dialog_data->notify, bx_v); elm_box_horizontal_set(bx_v, 0); evas_object_size_hint_weight_set(bx_v, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_show(bx_v); lb = elm_label_add(bx_v); elm_label_label_set(lb, message); elm_box_pack_end(bx_v, lb); evas_object_show(lb); dialog_data->response = &response; if (type == DIALOG_ALERT) { dialog_data->bt_ok = elm_button_add(bx_v); elm_button_label_set(dialog_data->bt_ok, "Close"); elm_box_pack_end(bx_v, dialog_data->bt_ok); evas_object_size_hint_align_set(dialog_data->bt_ok, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(dialog_data->bt_ok, "clicked", _bt_close, dialog_data); evas_object_show(dialog_data->bt_ok); } else { if (type == DIALOG_PROMPT) { dialog_data->entry = elm_entry_add(bx_v); elm_entry_entry_set(dialog_data->entry, default_entry_value); elm_box_pack_end(bx_v, dialog_data->entry); evas_object_show(dialog_data->entry); } if (type == DIALOG_PROMPT || type == DIALOG_CONFIRM) { Evas_Object *bx_h = elm_box_add(bx_v); elm_box_horizontal_set(bx_h, 1); elm_box_pack_end(bx_v, bx_h); evas_object_size_hint_weight_set(bx_h, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(bx_h, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_show(bx_h); dialog_data->bt_cancel = elm_button_add(bx_h); elm_button_label_set(dialog_data->bt_cancel, "Cancel"); elm_box_pack_end(bx_h, dialog_data->bt_cancel); evas_object_size_hint_weight_set(dialog_data->bt_cancel, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(dialog_data->bt_cancel, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(dialog_data->bt_cancel, "clicked", _bt_close, dialog_data); evas_object_show(dialog_data->bt_cancel); dialog_data->bt_ok = elm_button_add(bx_h); elm_button_label_set(dialog_data->bt_ok, "Ok"); elm_box_pack_end(bx_h, dialog_data->bt_ok); evas_object_size_hint_weight_set(dialog_data->bt_ok, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(dialog_data->bt_ok, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(dialog_data->bt_ok, "clicked", _bt_close, dialog_data); evas_object_show(dialog_data->bt_ok); } else return EINA_FALSE; } evas_object_show(dialog_data->notify); ecore_main_loop_begin(); if ((type == DIALOG_PROMPT) && (response == EINA_TRUE)) *entry_value = strdup(elm_entry_entry_get(dialog_data->entry)); evas_object_del(dialog_data->notify); free(dialog_data); return response; } /* called by ewk_view when javascript called alert() * */ static void _view_smart_run_javascript_alert(Ewk_View_Smart_Data *esd, Evas_Object *frame, const char *message) { View_Smart_Data *sd = (View_Smart_Data *)esd; Evas_Object *view = sd->base.self; _run_dialog(view, DIALOG_ALERT, message, NULL, NULL); } /* called by ewk_view when javascript called confirm() * */ static Eina_Bool _view_smart_run_javascript_confirm(Ewk_View_Smart_Data *esd, Evas_Object *frame, const char *message) { View_Smart_Data *sd = (View_Smart_Data *)esd; Evas_Object *view = sd->base.self; return _run_dialog(view, DIALOG_CONFIRM, message, NULL, NULL); } /* called by ewk_view when javascript called confirm() * */ static Eina_Bool _view_smart_run_javascript_prompt(Ewk_View_Smart_Data *esd, Evas_Object *frame, const char *message, const char *default_value, char **value) { View_Smart_Data *sd = (View_Smart_Data *)esd; Evas_Object *view = sd->base.self; Eina_Bool confirm; confirm = _run_dialog(view, DIALOG_PROMPT, message, default_value, value); if (!confirm) *value = NULL; return EINA_TRUE; } /** * Called by webkit when a new window is requested to be created * * @param sd Smart Data of current view. * * @return a new view, owned by the window just created or @c NULL on error. */ static Evas_Object * _view_smart_window_create(Ewk_View_Smart_Data *sd __UNUSED__, Eina_Bool javascript __UNUSED__, const Ewk_Window_Features *window_features __UNUSED__) { return window_create(); } static Eina_Bool _window_close_delayed(void *data) { View_Smart_Data *sd = data; sd->idler_close_window = NULL; Evas_Object *view = sd->base.self; Evas_Object *chrome = evas_object_data_get(view, "chrome"); Browser_Window *win = evas_object_data_get(chrome, "win"); tab_close_chrome(win, chrome); return EINA_FALSE; } /** * Called by webkit when a window is requested to be closed * * @param sd Smart Data of view to be closed. */ static void _view_smart_window_close(Ewk_View_Smart_Data *esd) { View_Smart_Data *sd = (View_Smart_Data *)esd; EINA_SAFETY_ON_TRUE_RETURN(!!sd->idler_close_window); sd->idler_close_window = ecore_idler_add(_window_close_delayed, sd); } /** * Creates a new view object given the parent. * * @param parent object to use as parent. * * @return newly added Evas_Object or @c NULL on errors. */ Evas_Object * view_add(Evas_Object *parent) { static Evas_Smart *smart = NULL; Evas *canvas = evas_object_evas_get(parent); Evas_Object *view; if (!smart) { /* create ewk_view_single subclass, this is done only once! */ static Ewk_View_Smart_Class api = EWK_VIEW_SMART_CLASS_INIT_NAME_VERSION("EWK_View_Single_Demo"); /* set current and parent apis to vanilla ewk_view_single methods */ ewk_view_single_smart_set(&api); ewk_view_single_smart_set(&_parent_sc); /* override methods we want custom behavior */ api.sc.add = _view_smart_add; api.sc.del = _view_smart_del; api.sc.calculate = _view_smart_calculate; api.mouse_down = _view_smart_mouse_down; api.mouse_up = _view_smart_mouse_up; api.mouse_move = _view_smart_mouse_move; api.add_console_message = _view_smart_add_console_message; api.window_create = _view_smart_window_create; api.window_close = _view_smart_window_close; api.run_javascript_alert = _view_smart_run_javascript_alert; api.run_javascript_confirm = _view_smart_run_javascript_confirm; api.run_javascript_prompt = _view_smart_run_javascript_prompt; /* create Evas_Smart class for this new smart object type. */ smart = evas_smart_class_new(&api.sc); if (!smart) { CRITICAL("Could not create smart class"); return NULL; } } view = evas_object_smart_add(canvas, smart); if (!view) { ERR("Could not create smart object object for view"); return NULL; } return view; } void view_zoom_next_up(Evas_Object *view) { VIEW_SD_GET_OR_RETURN(view, sd); float zoom = ewk_frame_zoom_get(sd->base.main_frame); unsigned int idx = _view_zoom_closest_index_find(zoom); Evas_Coord w, h; if (sd->flags.animated_zoom || sd->animator.pan || sd->animator.zoom) return; if (idx + 1 >= ZOOM_STEPS_LAST) return; if (sd->animator.kinetic) { ecore_animator_del(sd->animator.kinetic); sd->animator.kinetic = NULL; } idx++; zoom = ZOOM_STEPS[idx]; sd->flags.animated_zoom = EINA_TRUE; ewk_frame_visible_content_geometry_get (sd->base.main_frame, EINA_FALSE, NULL, NULL, &w, &h); ewk_view_zoom_animated_set (view, zoom, ZOOM_AUTO_ANIMATION_DURATION, w / 2, h / 2); } void view_zoom_next_down(Evas_Object *view) { VIEW_SD_GET_OR_RETURN(view, sd); float zoom = ewk_frame_zoom_get(sd->base.main_frame); unsigned int idx = _view_zoom_closest_index_find(zoom); Evas_Coord w, h; if (sd->flags.animated_zoom || sd->animator.pan || sd->animator.zoom) return; if (idx == 0) return; if (sd->animator.kinetic) { ecore_animator_del(sd->animator.kinetic); sd->animator.kinetic = NULL; } idx--; zoom = ZOOM_STEPS[idx]; sd->flags.animated_zoom = EINA_TRUE; ewk_frame_visible_content_geometry_get (sd->base.main_frame, EINA_FALSE, NULL, NULL, &w, &h); ewk_view_zoom_animated_set (view, zoom, ZOOM_AUTO_ANIMATION_DURATION, w / 2, h / 2); } Eina_Bool view_context_menu_set(Evas_Object *view, Evas_Object *widget, Ewk_Context_Menu *menu) { VIEW_SD_GET_OR_RETURN(view, sd, EINA_FALSE); if (widget && sd->context_menu) { CRITICAL("Trying to overwrite existing menu"); return EINA_FALSE; } sd->context_menu = widget; if (menu) evas_object_data_set(view, "context-menu", menu); else evas_object_data_del(view, "context-menu"); return EINA_TRUE; } Evas_Object *view_context_menu_widget_get(Evas_Object *view) { VIEW_SD_GET_OR_RETURN(view, sd, NULL); return sd->context_menu; } Ewk_Context_Menu *view_context_menu_get(Evas_Object *view) { return evas_object_data_get(view, "context-menu"); }