* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <config.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "ev-job-queue.h"
#include "ev-page-cache.h"
#include "ev-pixbuf-cache.h"
+#include "ev-transition-animation.h"
#if !GTK_CHECK_VERSION (2, 11, 7)
#include "ev-tooltip.h"
#endif
goto_fitr_dest (EvView *view, EvLinkDest *dest)
{
EvPoint doc_point;
- double zoom;
+ gdouble zoom, left, top;
+ gboolean change_left, change_top;
+
+ left = ev_link_dest_get_left (dest, &change_left);
+ top = ev_link_dest_get_top (dest, &change_top);
- zoom = zoom_for_size_best_fit (ev_link_dest_get_right (dest) - ev_link_dest_get_left (dest),
- ev_link_dest_get_bottom (dest) - ev_link_dest_get_top (dest),
+ zoom = zoom_for_size_best_fit (ev_link_dest_get_right (dest) - left,
+ ev_link_dest_get_bottom (dest) - top,
ev_view_get_width (view),
ev_view_get_height (view), 0, 0);
ev_view_set_sizing_mode (view, EV_SIZING_FREE);
ev_view_set_zoom (view, zoom, FALSE);
- doc_point.x = ev_link_dest_get_left (dest);
- doc_point.y = ev_link_dest_get_top (dest);
+ doc_point.x = change_left ? left : 0;
+ doc_point.y = change_top ? top : 0;
view->current_page = ev_link_dest_get_page (dest);
- view->pending_point = doc_point;
+ if (change_left || change_top)
+ view->pending_point = doc_point;
view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
{
EvPoint doc_point;
int doc_width, doc_height, page;
- double zoom;
+ double zoom, left;
+ gboolean change_left;
page = ev_link_dest_get_page (dest);
ev_page_cache_get_size (view->page_cache, page, 0, 1.0, &doc_width, &doc_height);
- doc_point.x = ev_link_dest_get_left (dest);
+ left = ev_link_dest_get_left (dest, &change_left);
+ doc_point.x = change_left ? left : 0;
doc_point.y = 0;
zoom = zoom_for_size_fit_height (doc_width - doc_point.x , doc_height,
ev_view_set_zoom (view, zoom, FALSE);
view->current_page = page;
- view->pending_point = doc_point;
+ if (change_left)
+ view->pending_point = doc_point;
view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
{
EvPoint doc_point;
int doc_width, doc_height, page;
- double zoom;
+ gdouble zoom, top;
+ gboolean change_top;
page = ev_link_dest_get_page (dest);
ev_page_cache_get_size (view->page_cache, page, 0, 1.0, &doc_width, &doc_height);
+ top = ev_link_dest_get_top (dest, &change_top);
+
doc_point.x = 0;
- doc_point.y = ev_link_dest_get_top (dest);
+ doc_point.y = change_top ? top : 0;
- zoom = zoom_for_size_fit_width (doc_width, ev_link_dest_get_top (dest),
+ zoom = zoom_for_size_fit_width (doc_width, top,
ev_view_get_width (view),
ev_view_get_height (view), 0);
ev_view_set_zoom (view, zoom, FALSE);
view->current_page = page;
- view->pending_point = doc_point;
+ if (change_top)
+ view->pending_point = doc_point;
view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
{
EvPoint doc_point;
gint page;
- double zoom;
+ gdouble zoom, left, top;
+ gboolean change_zoom, change_left, change_top;
- zoom = ev_link_dest_get_zoom (dest);
+ zoom = ev_link_dest_get_zoom (dest, &change_zoom);
page = ev_link_dest_get_page (dest);
- if (zoom > 1) {
+ if (change_zoom && zoom > 1) {
ev_view_set_sizing_mode (view, EV_SIZING_FREE);
ev_view_set_zoom (view, zoom, FALSE);
}
- doc_point.x = ev_link_dest_get_left (dest);
- doc_point.y = ev_link_dest_get_top (dest);
+ left = ev_link_dest_get_left (dest, &change_left);
+ top = ev_link_dest_get_top (dest, &change_top);
+
+ doc_point.x = change_left ? left : 0;
+ doc_point.y = change_top ? top : 0;
view->current_page = page;
- view->pending_point = doc_point;
+ if (change_left || change_top)
+ view->pending_point = doc_point;
view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
ev_view_set_cursor (view, EV_VIEW_CURSOR_DRAG);
return;
}
+
+ if (view->scroll_info.autoscrolling) {
+ if (view->cursor != EV_VIEW_CURSOR_AUTOSCROLL)
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_AUTOSCROLL);
+ return;
+ }
link = ev_view_get_link_at_location (view, x, y);
} else {
if (view->cursor == EV_VIEW_CURSOR_LINK ||
view->cursor == EV_VIEW_CURSOR_IBEAM ||
- view->cursor == EV_VIEW_CURSOR_DRAG)
+ view->cursor == EV_VIEW_CURSOR_DRAG ||
+ view->cursor == EV_VIEW_CURSOR_AUTOSCROLL)
ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
}
}
}
view->jump_to_find_result = FALSE;
+
/* Shift+Wheel scrolls the in the perpendicular direction */
if (state & GDK_SHIFT_MASK) {
if (event->direction == GDK_SCROLL_UP)
event->direction = GDK_SCROLL_LEFT;
- if (event->direction == GDK_SCROLL_LEFT)
+ else if (event->direction == GDK_SCROLL_LEFT)
event->direction = GDK_SCROLL_UP;
- if (event->direction == GDK_SCROLL_DOWN)
+ else if (event->direction == GDK_SCROLL_DOWN)
event->direction = GDK_SCROLL_RIGHT;
- if (event->direction == GDK_SCROLL_RIGHT)
+ else if (event->direction == GDK_SCROLL_RIGHT)
event->direction = GDK_SCROLL_DOWN;
event->state &= ~GDK_SHIFT_MASK;
cairo_t *cr;
gint i;
+ if (view->animation) {
+ GdkRectangle page_area;
+ GtkBorder border;
+
+ if (get_page_extents (view, view->current_page, &page_area, &border)) {
+ cr = gdk_cairo_create (view->layout.bin_window);
+
+ /* normalize to x=0, y=0 */
+ cairo_translate (cr, page_area.x, page_area.y);
+ page_area.x = page_area.y = 0;
+
+ ev_transition_animation_paint (view->animation, cr, page_area);
+ cairo_destroy (cr);
+ }
+
+ return TRUE;
+ }
+
if (view->presentation) {
switch (view->presentation_state) {
case EV_PRESENTATION_END: {
view->pressed_button = event->button;
view->selection_info.in_drag = FALSE;
+
+ if (view->scroll_info.autoscrolling)
+ return TRUE;
switch (event->button) {
case 1: {
return TRUE;
case 3:
+ view->scroll_info.start_y = event->y;
return ev_view_do_popup_menu (view, event->x, event->y);
}
if (view->image_dnd_info.image) {
GdkPixbuf *pixbuf;
- pixbuf = ev_image_get_pixbuf (view->image_dnd_info.image);
+ ev_document_doc_mutex_lock ();
+ pixbuf = ev_document_images_get_image (EV_DOCUMENT_IMAGES (view->document),
+ view->image_dnd_info.image);
+ ev_document_doc_mutex_unlock ();
+
gtk_selection_data_set_pixbuf (selection_data, pixbuf);
+ g_object_unref (pixbuf);
}
break;
case TARGET_DND_URI:
if (view->image_dnd_info.image) {
+ GdkPixbuf *pixbuf;
const gchar *tmp_uri;
gchar **uris;
- tmp_uri = ev_image_save_tmp (view->image_dnd_info.image);
+ ev_document_doc_mutex_lock ();
+ pixbuf = ev_document_images_get_image (EV_DOCUMENT_IMAGES (view->document),
+ view->image_dnd_info.image);
+ ev_document_doc_mutex_unlock ();
+
+ tmp_uri = ev_image_save_tmp (view->image_dnd_info.image, pixbuf);
+ g_object_unref (pixbuf);
uris = g_new0 (gchar *, 2);
uris[0] = (gchar *)tmp_uri;
return TRUE;
}
+static gboolean
+ev_view_drag_update_momentum (EvView *view)
+{
+ int i;
+ if (!view->drag_info.in_drag)
+ return FALSE;
+
+ for (i = DRAG_HISTORY - 1; i > 0; i--) {
+ view->drag_info.buffer[i].x = view->drag_info.buffer[i-1].x;
+ view->drag_info.buffer[i].y = view->drag_info.buffer[i-1].y;
+ }
+
+ /* Momentum is a moving average of 10ms granularity over
+ * the last 100ms with each 10ms stored in buffer.
+ */
+
+ view->drag_info.momentum.x = (view->drag_info.buffer[DRAG_HISTORY - 1].x - view->drag_info.buffer[0].x);
+ view->drag_info.momentum.y = (view->drag_info.buffer[DRAG_HISTORY - 1].y - view->drag_info.buffer[0].y);
+
+ return TRUE;
+}
+
+static gboolean
+ev_view_scroll_drag_release (EvView *view)
+{
+ gdouble dhadj_value, dvadj_value;
+ gdouble oldhadjustment, oldvadjustment;
+
+ view->drag_info.momentum.x /= 1.2;
+ view->drag_info.momentum.y /= 1.2; /* Alter these constants to change "friction" */
+
+ dhadj_value = view->hadjustment->page_size *
+ (gdouble)view->drag_info.momentum.x / GTK_WIDGET (view)->allocation.width;
+ dvadj_value = view->vadjustment->page_size *
+ (gdouble)view->drag_info.momentum.y / GTK_WIDGET (view)->allocation.height;
+
+ oldhadjustment = gtk_adjustment_get_value (view->hadjustment);
+ oldvadjustment = gtk_adjustment_get_value (view->vadjustment);
+
+ if (((oldhadjustment + dhadj_value) > (view->hadjustment->upper - view->hadjustment->page_size)) ||
+ ((oldhadjustment + dhadj_value) < 0))
+ view->drag_info.momentum.x *= -0.5; /* 0.5 rather than 1 means the edges absorb some momentum */
+ if (((oldvadjustment + dvadj_value) > (view->vadjustment->upper - view->vadjustment->page_size)) ||
+ ((oldvadjustment + dvadj_value) < 0))
+ view->drag_info.momentum.y *= -0.5;
+
+ gtk_adjustment_set_value (view->hadjustment,
+ MIN (oldhadjustment + dhadj_value,
+ view->hadjustment->upper - view->hadjustment->page_size));
+ gtk_adjustment_set_value (view->vadjustment,
+ MIN (oldvadjustment + dvadj_value,
+ view->vadjustment->upper - view->vadjustment->page_size));
+
+ if (((view->drag_info.momentum.x < 1) && (view->drag_info.momentum.x > -1)) &&
+ ((view->drag_info.momentum.y < 1) && (view->drag_info.momentum.y > -1)))
+ return FALSE;
+ else
+ return TRUE;
+}
+
static gboolean
ev_view_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event)
if (!view->document)
return FALSE;
+
if (event->is_hint || event->window != view->layout.bin_window) {
gtk_widget_get_pointer (widget, &x, &y);
y = event->y;
}
+ if (view->scroll_info.autoscrolling) {
+ view->scroll_info.last_y = y;
+ return TRUE;
+ }
+
if (view->selection_info.in_drag) {
if (gtk_drag_check_threshold (widget,
view->selection_info.start.x,
} else if (view->pressed_button == 2) {
if (!view->drag_info.in_drag) {
gboolean start;
+ int i;
start = gtk_drag_check_threshold (widget,
view->drag_info.start.x,
event->x_root,
event->y_root);
view->drag_info.in_drag = start;
+ view->drag_info.drag_timeout_id = g_timeout_add (10,
+ (GSourceFunc)ev_view_drag_update_momentum, view);
+ /* Set 100 to choose how long it takes to build up momentum */
+ /* Clear out previous momentum info: */
+ for (i = 0; i < DRAG_HISTORY; i++) {
+ view->drag_info.buffer[i].x = event->x;
+ view->drag_info.buffer[i].y = event->y;
+ }
+ view->drag_info.momentum.x = 0;
+ view->drag_info.momentum.y = 0;
}
if (view->drag_info.in_drag) {
int dx, dy;
gdouble dhadj_value, dvadj_value;
+ view->drag_info.buffer[0].x = event->x;
+ view->drag_info.buffer[0].y = event->y;
+
dx = event->x_root - view->drag_info.start.x;
dy = event->y_root - view->drag_info.start.y;
EvView *view = EV_VIEW (widget);
EvLink *link = NULL;
- view->drag_info.in_drag = FALSE;
view->image_dnd_info.in_drag = FALSE;
- if (view->pressed_button == 2) {
- ev_view_handle_cursor_over_xy (view, event->x, event->y);
+ if (view->scroll_info.autoscrolling) {
+ ev_view_autoscroll_stop (view);
+ view->pressed_button = -1;
+
+ return TRUE;
+ }
+
+ if (view->drag_info.in_drag) {
+ view->drag_info.release_timeout_id =
+ g_timeout_add (20,
+ (GSourceFunc)ev_view_scroll_drag_release, view);
}
- if (view->document && view->pressed_button != 3) {
+ if (view->document && !view->drag_info.in_drag && view->pressed_button != 3) {
link = ev_view_get_link_at_location (view, event->x, event->y);
}
+
+ view->drag_info.in_drag = FALSE;
+
+ if (view->pressed_button == 2) {
+ ev_view_handle_cursor_over_xy (view, event->x, event->y);
+ }
view->pressed_button = -1;
cairo_t *cr;
gint width, height;
- /* Don't annoy users with loading messages during presentations.
- * FIXME: Temporary "workaround" for
- * http://bugzilla.gnome.org/show_bug.cgi?id=320352 */
- if (view->presentation)
- return;
-
if (!view->loading_text) {
const gchar *loading_text = _("Loading...");
PangoLayout *layout;
cairo_save (cr);
cairo_translate (cr, overlap.x, overlap.y);
-
+
if (width != page_width || height != page_height) {
cairo_pattern_set_filter (cairo_get_source (cr),
CAIRO_FILTER_FAST);
view->loading_text = NULL;
}
+ if (view->scroll_info.timeout_id) {
+ g_source_remove (view->scroll_info.timeout_id);
+ view->scroll_info.timeout_id = 0;
+ }
+
+ if (view->drag_info.drag_timeout_id) {
+ g_source_remove (view->drag_info.drag_timeout_id);
+ view->drag_info.drag_timeout_id = 0;
+ }
+
+ if (view->drag_info.release_timeout_id) {
+ g_source_remove (view->drag_info.release_timeout_id);
+ view->drag_info.release_timeout_id = 0;
+ }
+
ev_view_presentation_transition_stop (view);
ev_view_set_scroll_adjustments (GTK_LAYOUT (view), NULL, NULL);
view->pressed_button = -1;
view->cursor = EV_VIEW_CURSOR_NORMAL;
view->drag_info.in_drag = FALSE;
+ view->scroll_info.autoscrolling = FALSE;
view->selection_info.selections = NULL;
view->selection_info.in_selection = FALSE;
view->selection_info.in_drag = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (view));
}
+static void
+ev_view_change_page (EvView *view,
+ gint new_page)
+{
+ gint x, y;
+
+ view->current_page = new_page;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
+
+ if (view->presentation)
+ ev_view_presentation_transition_start (view);
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+ ev_view_handle_cursor_over_xy (view, x, y);
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+}
+
+static void
+ev_view_transition_animation_finish (EvTransitionAnimation *animation,
+ EvView *view)
+{
+ g_object_unref (view->animation);
+ view->animation = NULL;
+ ev_view_change_page (view, view->current_page);
+}
+
+static void
+ev_view_transition_animation_frame (EvTransitionAnimation *animation,
+ gdouble progress,
+ EvView *view)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+}
+
+static void
+ev_view_presentation_animation_start (EvView *view,
+ int new_page)
+{
+ EvTransitionEffect *effect = NULL;
+ cairo_surface_t *surface;
+
+ if (EV_IS_DOCUMENT_TRANSITION (view->document))
+ effect = ev_document_transition_get_effect (EV_DOCUMENT_TRANSITION (view->document),
+ view->current_page);
+ if (!effect)
+ return;
+
+ surface = ev_pixbuf_cache_get_surface (view->pixbuf_cache, view->current_page);
+ view->animation = ev_transition_animation_new (effect);
+ ev_transition_animation_set_origin_surface (view->animation, surface);
+
+ g_signal_connect (view->animation, "frame",
+ G_CALLBACK (ev_view_transition_animation_frame), view);
+ g_signal_connect (view->animation, "finished",
+ G_CALLBACK (ev_view_transition_animation_finish), view);
+}
+
static void
job_finished_cb (EvPixbufCache *pixbuf_cache,
GdkRegion *region,
EvView *view)
{
+ if (view->animation) {
+ cairo_surface_t *surface;
+
+ surface = ev_pixbuf_cache_get_surface (pixbuf_cache, view->current_page);
+ ev_transition_animation_set_dest_surface (view->animation, surface);
+ }
+
if (region) {
gdk_window_invalidate_region (view->layout.bin_window,
region, TRUE);
EvView *view)
{
if (view->current_page != new_page) {
- gint x, y;
-
- view->current_page = new_page;
- view->pending_scroll = SCROLL_TO_PAGE_POSITION;
-
if (view->presentation)
- ev_view_presentation_transition_start (view);
-
- gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
- ev_view_handle_cursor_over_xy (view, x, y);
-
- gtk_widget_queue_resize (GTK_WIDGET (view));
+ ev_view_presentation_animation_start (view, new_page);
+
+ ev_view_change_page (view, new_page);
} else {
gtk_widget_queue_draw (GTK_WIDGET (view));
}
gtk_widget_queue_draw (GTK_WIDGET (view));
}
+static gboolean
+ev_view_autoscroll_cb (EvView *view)
+{
+ gdouble speed, value;
+
+ /* If the user stops autoscrolling, autoscrolling will be
+ * set to false but the timeout will continue; stop the timeout: */
+ if (!view->scroll_info.autoscrolling) {
+ view->scroll_info.timeout_id = 0;
+ return FALSE;
+ }
+
+ if (view->scroll_info.last_y > view->scroll_info.start_y &&
+ (view->scroll_info.last_y < view->scroll_info.start_y))
+ return TRUE;
+
+ /* Replace 100 with your speed of choice: The lower the faster.
+ * Replace 3 with another speed of choice: The higher, the faster it accelerated
+ * based on the distance of the starting point from the mouse
+ * (All also effected by the timeout interval of this callback) */
+
+ if (view->scroll_info.start_y > view->scroll_info.last_y)
+ speed = -pow ((((gdouble)view->scroll_info.start_y - view->scroll_info.last_y) / 100), 3);
+ else
+ speed = pow ((((gdouble)view->scroll_info.last_y - view->scroll_info.start_y) / 100), 3);
+
+ value = gtk_adjustment_get_value (view->vadjustment);
+ value = CLAMP (value + speed, 0, view->vadjustment->upper - view->vadjustment->page_size);
+ gtk_adjustment_set_value (view->vadjustment, value);
+
+ return TRUE;
+
+}
+
+void
+ev_view_autoscroll_start (EvView *view)
+{
+ gint x, y;
+
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ if (view->scroll_info.autoscrolling)
+ return;
+
+ view->scroll_info.autoscrolling = TRUE;
+ view->scroll_info.timeout_id =
+ g_timeout_add (20, (GSourceFunc)ev_view_autoscroll_cb,
+ view);
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+ ev_view_handle_cursor_over_xy (view, x, y);
+}
+
+void
+ev_view_autoscroll_stop (EvView *view)
+{
+ gint x, y;
+
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ if (!view->scroll_info.autoscrolling)
+ return;
+
+ view->scroll_info.autoscrolling = FALSE;
+ if (view->scroll_info.timeout_id) {
+ g_source_remove (view->scroll_info.timeout_id);
+ view->scroll_info.timeout_id = 0;
+ }
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+ ev_view_handle_cursor_over_xy (view, x, y);
+}
+
void
ev_view_set_document (EvView *view,
EvDocument *document)
else
scale = factor;
- scale = CLAMP (scale, view->min_scale, view->max_scale);
+ scale = CLAMP (scale,
+ view->sizing_mode == EV_SIZING_FREE ? view->min_scale : 0,
+ view->max_scale);
if (ABS (view->scale - scale) < EPSILON)
return;
if (presentation)
ev_view_presentation_transition_start (view);
- else
+ else {
ev_view_presentation_transition_stop (view);
+ if (view->animation) {
+ /* stop any running animation */
+ ev_view_transition_animation_finish (view->animation, view);
+ }
+ }
+
if (GTK_WIDGET_REALIZED (view)) {
if (view->presentation)
gdk_window_set_background (view->layout.bin_window,
duration = ev_document_transition_get_page_duration (EV_DOCUMENT_TRANSITION (view->document),
view->current_page);
if (duration > 0) {
-#if GLIB_CHECK_VERSION (2, 13, 0)
view->trans_timeout_id =
g_timeout_add_seconds (duration,
(GSourceFunc) transition_next_page,
view);
-#else
- view->trans_timeout_id =
- g_timeout_add (duration * 1000,
- (GSourceFunc) transition_next_page,
- view);
-#endif
}
}
case EV_VIEW_CURSOR_DRAG:
cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
break;
+ case EV_VIEW_CURSOR_AUTOSCROLL:
+ cursor = gdk_cursor_new_for_display (display, GDK_DOUBLE_ARROW);
+ break;
}
if (cursor) {
view->presentation_state == EV_PRESENTATION_WHITE)) {
ev_view_reset_presentation_state (view);
return FALSE;
- }
+ }
+
+ if (view->animation) {
+ ev_view_transition_animation_finish (view->animation, view);
+ return TRUE;
+ }
ev_view_presentation_transition_stop (view);
ev_view_reset_presentation_state (view);
return FALSE;
}
+ if (view->animation) {
+ ev_view_transition_animation_finish (view->animation, view);
+ return TRUE;
+ }
+
ev_view_reset_presentation_state (view);
page = ev_page_cache_get_current_page (view->page_cache);