X-Git-Url: https://www.fi.muni.cz/~kas/git//home/kas/public_html/git/?a=blobdiff_plain;ds=inline;f=shell%2Fev-view.c;h=c39a2657993a20c05f83a550e3ebba7ef6704aca;hb=47bf008787b6d613d0ecdc6b14c741519641c24e;hp=4b684190963623e21f591b93d92082466cb37f4e;hpb=234f9499e497c80efe070af6bf013009118474b0;p=evince.git diff --git a/shell/ev-view.c b/shell/ev-view.c index 4b684190..c39a2657 100644 --- a/shell/ev-view.c +++ b/shell/ev-view.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -29,6 +30,7 @@ #include "ev-marshal.h" #include "ev-view.h" +#include "ev-view-private.h" #include "ev-utils.h" #include "ev-selection.h" #include "ev-document-find.h" @@ -55,12 +57,14 @@ enum { PROP_SIZING_MODE, PROP_ZOOM, PROP_ROTATION, + PROP_HAS_SELECTION, }; enum { SIGNAL_BINDING_ACTIVATED, SIGNAL_ZOOM_INVALID, SIGNAL_EXTERNAL_LINK, + SIGNAL_POPUP_MENU, N_SIGNALS, }; @@ -81,15 +85,6 @@ static const GtkTargetEntry targets[] = { static guint signals[N_SIGNALS]; -typedef enum { - EV_VIEW_CURSOR_NORMAL, - EV_VIEW_CURSOR_IBEAM, - EV_VIEW_CURSOR_LINK, - EV_VIEW_CURSOR_WAIT, - EV_VIEW_CURSOR_HIDDEN, - EV_VIEW_CURSOR_DRAG -} EvViewCursor; - typedef enum { EV_VIEW_FIND_NEXT, EV_VIEW_FIND_PREV @@ -101,97 +96,7 @@ typedef enum { #define MIN_SCALE 0.05409 #define MAX_SCALE 4.0 -/* Information for middle clicking and moving around the doc */ -typedef struct { - gboolean in_drag; - GdkPoint start; - gdouble hadj; - gdouble vadj; -} DragInfo; - -/* Information for handling selection */ -typedef struct { - gboolean in_selection; - GdkPoint start; - GList *selections; -} SelectionInfo; - -typedef enum { - SCROLL_TO_KEEP_POSITION, - SCROLL_TO_CURRENT_PAGE, - SCROLL_TO_CENTER -} PendingScroll; - -struct _EvView { - GtkWidget parent_instance; - - EvDocument *document; - - char *status; - char *find_status; - - /* Scrolling */ - GtkAdjustment *hadjustment; - GtkAdjustment *vadjustment; - - gint scroll_x; - gint scroll_y; - - /* Information for middle clicking and dragging around. */ - DragInfo drag_info; - - /* Selection */ - gint motion_x; - gint motion_y; - guint selection_update_id; - - EvViewSelectionMode selection_mode; - SelectionInfo selection_info; - - int pressed_button; - EvViewCursor cursor; - GtkWidget *link_tooltip; - EvLink *hovered_link; - - EvPageCache *page_cache; - EvPixbufCache *pixbuf_cache; - - gint start_page; - gint end_page; - gint current_page; - - EvJobRender *current_job; - - int find_page; - int find_result; - int spacing; - - int rotation; - double scale; - - gboolean continuous; - gboolean dual_page; - gboolean fullscreen; - gboolean presentation; - EvSizingMode sizing_mode; - - PendingScroll pending_scroll; - gboolean pending_resize; -}; - -struct _EvViewClass { - GtkWidgetClass parent_class; - - void (*set_scroll_adjustments) (EvView *view, - GtkAdjustment *hadjustment, - GtkAdjustment *vadjustment); - void (*binding_activated) (EvView *view, - GtkScrollType scroll, - gboolean horizontal); - void (*zoom_invalid) (EvView *view); - void (*external_link) (EvView *view, - EvLink *link); -}; +#define SCROLL_TIME 150 /*** Scrolling ***/ static void scroll_to_current_page (EvView *view, @@ -245,9 +150,9 @@ static void find_page_at_location (EvView gint *y_offset); /*** Hyperrefs ***/ -static EvLink* get_link_at_location (EvView *view, - gdouble x, - gdouble y); +static EvLink * ev_view_get_link_at_location (EvView *view, + gdouble x, + gdouble y); static char* tip_from_link (EvView *view, EvLink *link); static void handle_link_over_xy (EvView *view, @@ -284,6 +189,8 @@ static gboolean ev_view_leave_notify_event (GtkWidget static void ev_view_style_set (GtkWidget *widget, GtkStyle *old_style); +static AtkObject *ev_view_get_accessible (GtkWidget *widget); + /*** Drawing ***/ static guint32 ev_gdk_color_to_rgb (const GdkColor *color); static void draw_rubberband (GtkWidget *widget, @@ -1097,15 +1004,44 @@ ev_view_get_height (EvView *view) return GTK_WIDGET (view)->allocation.height; } +static gboolean +location_in_selected_text (EvView *view, + gdouble x, + gdouble y) +{ + gint page = -1; + gint x_offset = 0, y_offset = 0; + EvViewSelection *selection; + GList *l = NULL; + + for (l = view->selection_info.selections; l != NULL; l = l->next) { + selection = (EvViewSelection *)l->data; + + find_page_at_location (view, x, y, &page, &x_offset, &y_offset); + + if (page != selection->page) + continue; + + if (selection->covered_region && + gdk_region_point_in (selection->covered_region, x_offset, y_offset)) + return TRUE; + } + + return FALSE; +} + /*** Hyperref ***/ static EvLink * -get_link_at_location (EvView *view, - gdouble x, - gdouble y) +ev_view_get_link_at_location (EvView *view, + gdouble x, + gdouble y) { gint page = -1; gint x_offset = 0, y_offset = 0; GList *link_mapping; + + x += view->scroll_x; + y += view->scroll_y; find_page_at_location (view, x, y, &page, &x_offset, &y_offset); @@ -1322,7 +1258,7 @@ handle_link_over_xy (EvView *view, gint x, gint y) { EvLink *link; - link = get_link_at_location (view, x + view->scroll_x, y + view->scroll_y); + link = ev_view_get_link_at_location (view, x, y); if (view->link_tooltip == NULL) { view->link_tooltip = ev_tooltip_new (GTK_WIDGET (view)); @@ -1336,9 +1272,13 @@ handle_link_over_xy (EvView *view, gint x, gint y) if (link) { char *msg = tip_from_link (view, link); - ev_tooltip_set_position (EV_TOOLTIP (view->link_tooltip), x, y); - ev_tooltip_set_text (EV_TOOLTIP (view->link_tooltip), msg); - ev_tooltip_activate (EV_TOOLTIP (view->link_tooltip)); + if (msg && g_utf8_validate (msg, -1, NULL)) { + EvTooltip *tooltip = EV_TOOLTIP (view->link_tooltip); + + ev_tooltip_set_position (tooltip, x, y); + ev_tooltip_set_text (tooltip, msg); + ev_tooltip_activate (tooltip); + } g_free (msg); ev_view_set_cursor (view, EV_VIEW_CURSOR_LINK); @@ -1553,6 +1493,7 @@ ev_view_realize (GtkWidget *widget) GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK; @@ -1680,22 +1621,32 @@ ev_view_button_press_event (GtkWidget *widget, GdkEventButton *event) { EvView *view = EV_VIEW (widget); - + EvLink *link; + if (!GTK_WIDGET_HAS_FOCUS (widget)) { gtk_widget_grab_focus (widget); } - + view->pressed_button = event->button; - + view->selection_info.in_drag = FALSE; + switch (event->button) { - case 1: + case 1: if (view->selection_info.selections) { - clear_selection (view); + if (location_in_selected_text (view, + event->x + view->scroll_x, + event->y + view->scroll_y)) { + view->selection_info.in_drag = TRUE; + } else { + clear_selection (view); + } + gtk_widget_queue_draw (widget); } view->selection_info.start.x = event->x + view->scroll_x; view->selection_info.start.y = event->y + view->scroll_y; + return TRUE; case 2: /* use root coordinates as reference point because @@ -1707,49 +1658,129 @@ ev_view_button_press_event (GtkWidget *widget, ev_view_set_cursor (view, EV_VIEW_CURSOR_DRAG); + return TRUE; + case 3: + link = ev_view_get_link_at_location (view, event->x, event->y); + g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, link); return TRUE; } - + return FALSE; } +static void +ev_view_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + EvView *view = EV_VIEW (widget); + + if (view->selection_info.selections && + ev_document_can_get_text (view->document)) { + gchar *text; + + text = get_selected_text (view); + + gtk_selection_data_set_text (selection_data, text, strlen (text)); + + g_free (text); + } +} static gboolean selection_update_idle_cb (EvView *view) { - GdkPoint point; - point.x = view->motion_x; - point.y = view->motion_y; - compute_selections (view, &view->selection_info.start, &point); - + compute_selections (view, &view->selection_info.start, &view->motion); view->selection_update_id = 0; return FALSE; } +static gboolean +selection_scroll_timeout_cb (EvView *view) +{ + gint y, shift = 0; + GtkWidget *widget = GTK_WIDGET (view); + + gtk_widget_get_pointer (widget, NULL, &y); + + if (y > widget->allocation.height) { + shift = (y - widget->allocation.height) / 2; + } else if (y < 0) { + shift = y / 2; + } + + if (shift) + gtk_adjustment_set_value (view->vadjustment, + CLAMP (view->vadjustment->value + shift, + view->vadjustment->lower, + view->vadjustment->upper - + view->vadjustment->page_size)); + return TRUE; +} + static gboolean ev_view_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { EvView *view = EV_VIEW (widget); + gint x, y; if (!view->document) return FALSE; + + if (event->is_hint || event->window != widget->window) { + gtk_widget_get_pointer (widget, &x, &y); + } else { + x = event->x; + y = event->y; + } + + if (view->selection_info.in_drag) { + if (gtk_drag_check_threshold (widget, + view->selection_info.start.x, + view->selection_info.start.y, + x, y)) { + GdkDragContext *context; + GtkTargetList *target_list = gtk_target_list_new (NULL, 0); + + gtk_target_list_add_text_targets (target_list, 0); + + context = gtk_drag_begin (widget, target_list, + GDK_ACTION_COPY, + 1, (GdkEvent *)event); + + view->selection_info.in_drag = FALSE; + + gtk_target_list_unref (target_list); + return TRUE; + } + } + /* For the Evince 0.4.x release, we limit selection to un-rotated * documents only. */ - if (view->pressed_button == 1 && - view->rotation == 0) { + if (view->pressed_button == 1 && view->rotation == 0) { + + /* Schedule timeout to scroll during selection and additionally + * scroll once to allow arbitrary speed. */ + if (!view->selection_scroll_id) + view->selection_scroll_id = g_timeout_add (SCROLL_TIME, (GSourceFunc)selection_scroll_timeout_cb, view); + else + selection_scroll_timeout_cb (view); + view->selection_info.in_selection = TRUE; - view->motion_x = event->x + view->scroll_x; - view->motion_y = event->y + view->scroll_y; + view->motion.x = x + view->scroll_x; + view->motion.y = y + view->scroll_y; - /* Queue an idle to handle the motion. We do this because - * handling any selection events in the motion could be slower - * than new motion events reach us. We always put it in the - * idle to make sure we catch up and don't visibly lag the + /* Queue an idle to handle the motion. We do this because + * handling any selection events in the motion could be slower + * than new motion events reach us. We always put it in the + * idle to make sure we catch up and don't visibly lag the * mouse. */ - if (! view->selection_update_id) + if (!view->selection_update_id) view->selection_update_id = g_idle_add ((GSourceFunc)selection_update_idle_cb, view); return TRUE; @@ -1794,7 +1825,7 @@ ev_view_motion_notify_event (GtkWidget *widget, */ } else if (view->pressed_button <= 0 && view->rotation == 0) { - handle_link_over_xy (view, event->x, event->y); + handle_link_over_xy (view, x, y); return TRUE; } @@ -1813,8 +1844,7 @@ ev_view_button_release_event (GtkWidget *widget, } if (view->document) { - link = get_link_at_location (view, event->x + view->scroll_x, - event->y + view->scroll_y); + link = ev_view_get_link_at_location (view, event->x, event->y); } else { link = NULL; } @@ -1822,8 +1852,24 @@ ev_view_button_release_event (GtkWidget *widget, view->pressed_button = -1; view->drag_info.in_drag = FALSE; + if (view->selection_scroll_id) { + g_source_remove (view->selection_scroll_id); + view->selection_scroll_id = 0; + } + if (view->selection_update_id) { + g_source_remove (view->selection_update_id); + view->selection_update_id = 0; + } + if (view->selection_info.selections) { ev_view_update_primary_selection (view); + + if (view->selection_info.in_drag) { + clear_selection (view); + gtk_widget_queue_draw (widget); + } + + view->selection_info.in_drag = FALSE; } else if (link) { ev_view_goto_link (view, link); } else if (view->presentation) { @@ -1901,7 +1947,6 @@ ev_view_style_set (GtkWidget *widget, GTK_WIDGET_CLASS (ev_view_parent_class)->style_set (widget, old_style); } - /*** Drawing ***/ static guint32 @@ -2166,6 +2211,16 @@ ev_view_destroy (GtkObject *object) gtk_widget_destroy (view->link_tooltip); } + if (view->selection_scroll_id) { + g_source_remove (view->selection_scroll_id); + view->selection_scroll_id = 0; + } + + if (view->selection_update_id) { + g_source_remove (view->selection_update_id); + view->selection_update_id = 0; + } + ev_view_set_scroll_adjustments (view, NULL, NULL); GTK_OBJECT_CLASS (ev_view_parent_class)->destroy (object); @@ -2207,6 +2262,37 @@ ev_view_set_property (GObject *object, } } +static AtkObject * +ev_view_get_accessible (GtkWidget *widget) +{ + static gboolean first_time = TRUE; + + if (first_time) { + AtkObjectFactory *factory; + AtkRegistry *registry; + GType derived_type; + GType derived_atk_type; + + /* + * Figure out whether accessibility is enabled by looking at the + * type of the accessible object which would be created for + * the parent type of EvView. + */ + derived_type = g_type_parent (EV_TYPE_VIEW); + + registry = atk_get_default_registry (); + factory = atk_registry_get_factory (registry, + derived_type); + derived_atk_type = atk_object_factory_get_accessible_type (factory); + if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE)) + atk_registry_set_factory_type (registry, + EV_TYPE_VIEW, + ev_view_accessible_factory_get_type ()); + first_time = FALSE; + } + return GTK_WIDGET_CLASS (ev_view_parent_class)->get_accessible (widget); +} + static void ev_view_get_property (GObject *object, guint prop_id, @@ -2244,6 +2330,10 @@ ev_view_get_property (GObject *object, case PROP_ROTATION: g_value_set_int (value, view->rotation); break; + case PROP_HAS_SELECTION: + g_value_set_boolean (value, + view->selection_info.selections != NULL); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -2267,6 +2357,7 @@ ev_view_class_init (EvViewClass *class) widget_class->button_release_event = ev_view_button_release_event; widget_class->focus_in_event = ev_view_focus_in; widget_class->focus_out_event = ev_view_focus_out; + widget_class->get_accessible = ev_view_get_accessible; widget_class->size_request = ev_view_size_request; widget_class->size_allocate = ev_view_size_allocate; widget_class->realize = ev_view_realize; @@ -2274,6 +2365,7 @@ ev_view_class_init (EvViewClass *class) widget_class->enter_notify_event = ev_view_enter_notify_event; widget_class->leave_notify_event = ev_view_leave_notify_event; widget_class->style_set = ev_view_style_set; + widget_class->drag_data_get = ev_view_drag_data_get; gtk_object_class->destroy = ev_view_destroy; class->set_scroll_adjustments = ev_view_set_scroll_adjustments; @@ -2315,6 +2407,14 @@ ev_view_class_init (EvViewClass *class) g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); + signals[SIGNAL_POPUP_MENU] = g_signal_new ("popup", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EvViewClass, popup_menu), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); g_object_class_install_property (object_class, PROP_STATUS, @@ -2389,6 +2489,13 @@ ev_view_class_init (EvViewClass *class) 360, 0, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_HAS_SELECTION, + g_param_spec_boolean ("has-selection", + "Has selection", + "The view has selections", + FALSE, + G_PARAM_READABLE)); binding_set = gtk_binding_set_by_class (class); @@ -2409,7 +2516,9 @@ ev_view_init (EvView *view) view->pressed_button = -1; view->cursor = EV_VIEW_CURSOR_NORMAL; view->drag_info.in_drag = FALSE; + view->selection_info.selections = NULL; view->selection_info.in_selection = FALSE; + view->selection_info.in_drag = FALSE; view->selection_mode = EV_VIEW_SELECTION_TEXT; view->continuous = TRUE; view->dual_page = FALSE; @@ -3441,6 +3550,7 @@ merge_selection_region (EvView *view, g_list_foreach (view->selection_info.selections, (GFunc)selection_free, NULL); view->selection_info.selections = new_list; ev_pixbuf_cache_set_selection_list (view->pixbuf_cache, new_list); + g_object_notify (G_OBJECT (view), "has-selection"); new_list_ptr = new_list; old_list_ptr = old_list; @@ -3571,6 +3681,7 @@ clear_selection (EvView *view) g_list_foreach (view->selection_info.selections, (GFunc)selection_free, NULL); view->selection_info.selections = NULL; view->selection_info.in_selection = FALSE; + g_object_notify (G_OBJECT (view), "has-selection"); } @@ -3605,9 +3716,16 @@ ev_view_select_all (EvView *view) } ev_pixbuf_cache_set_selection_list (view->pixbuf_cache, view->selection_info.selections); + g_object_notify (G_OBJECT (view), "has-selection"); gtk_widget_queue_draw (GTK_WIDGET (view)); } +gboolean +ev_view_get_has_selection (EvView *view) +{ + return view->selection_info.selections != NULL; +} + static char * get_selected_text (EvView *ev_view) {