*/
#include <math.h>
+#include <string.h>
#include <gtk/gtkalignment.h>
#include <glib/gi18n.h>
#include <gtk/gtkbindings.h>
#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"
#include "ev-document-misc.h"
#include "ev-job-queue.h"
#include "ev-page-cache.h"
#include "ev-pixbuf-cache.h"
+#include "ev-tooltip.h"
#define EV_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_VIEW, EvViewClass))
#define EV_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_VIEW))
#define EV_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EV_TYPE_VIEW, EvViewClass))
+
enum {
PROP_0,
PROP_STATUS,
PROP_PRESENTATION,
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,
};
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;
+ EV_VIEW_FIND_NEXT,
+ EV_VIEW_FIND_PREV
+} EvViewFindDirection;
#define ZOOM_IN_FACTOR 1.2
#define ZOOM_OUT_FACTOR (1.0/ZOOM_IN_FACTOR)
#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;
-
- gint scroll_x;
- gint scroll_y;
-
- /* Information for middle clicking and dragging around. */
- DragInfo drag_info;
-
- /* Selection */
- EvViewSelectionMode selection_mode;
- SelectionInfo selection_info;
-
- gboolean pressed_button;
- EvViewCursor cursor;
-
- GtkAdjustment *hadjustment;
- GtkAdjustment *vadjustment;
-
- 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);
-};
+#define SCROLL_TIME 150
/*** Scrolling ***/
static void scroll_to_current_page (EvView *view,
GtkAdjustment *vadjustment);
static void add_scroll_binding_keypad (GtkBindingSet *binding_set,
guint keyval,
- GtkScrollType scroll,
- gboolean horizontal);
-static void ev_view_binding_activated (EvView *view,
- GtkScrollType scroll,
+ GdkModifierType modifiers,
+ EvScrollType scroll,
gboolean horizontal);
static void ensure_rectangle_is_visible (EvView *view,
GdkRectangle *rect);
gint *page,
gint *x_offset,
gint *y_offset);
-
+static gboolean doc_point_to_view_point (EvView *view,
+ int page,
+ EvPoint *doc_point,
+ GdkPoint *view_point);
/*** Hyperrefs ***/
-static EvLink* get_link_at_location (EvView *view,
- gdouble x,
- gdouble y);
-static void go_to_link (EvView *view,
+static EvLink * ev_view_get_link_at_location (EvView *view,
+ gdouble x,
+ gdouble y);
+static char* tip_from_link (EvView *view,
EvLink *link);
-static char* status_message_from_link (EvView *view,
- EvLink *link);
-
+static void handle_link_over_xy (EvView *view,
+ gint x,
+ gint y);
/*** GtkWidget implementation ***/
static void ev_view_size_request_continuous_dual_page (EvView *view,
GtkRequisition *requisition);
static void ev_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static void ev_view_realize (GtkWidget *widget);
-static void ev_view_unrealize (GtkWidget *widget);
static gboolean ev_view_scroll_event (GtkWidget *widget,
GdkEventScroll *event);
static gboolean ev_view_expose_event (GtkWidget *widget,
GdkEventExpose *event);
+static gboolean ev_view_popup_menu (GtkWidget *widget);
static gboolean ev_view_button_press_event (GtkWidget *widget,
GdkEventButton *event);
static gboolean ev_view_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event);
static gboolean ev_view_button_release_event (GtkWidget *widget,
GdkEventButton *event);
+static gboolean ev_view_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event);
static gboolean ev_view_leave_notify_event (GtkWidget *widget,
GdkEventCrossing *event);
+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);
GdkRectangle *page_area,
GtkBorder *border,
GdkRectangle *expose_area);
+static void draw_loading_text (EvView *view,
+ GdkRectangle *page_area,
+ GdkRectangle *expose_area);
/*** Callbacks ***/
static void find_changed_cb (EvDocument *document,
int target_width,
int target_height,
int vsb_width);
+static double zoom_for_size_fit_height (int doc_width,
+ int doc_height,
+ int target_width,
+ int target_height,
+ int vsb_height);
static double zoom_for_size_best_fit (int doc_width,
int doc_height,
int target_width,
/*** Status messages ***/
static void ev_view_set_status (EvView *view,
const char *message);
-static void update_find_status_message (EvView *view);
+static void update_find_status_message (EvView *view,
+ gboolean this_page);
static void ev_view_set_find_status (EvView *view,
const char *message);
/*** Find ***/
static void jump_to_find_result (EvView *view);
-static void jump_to_find_page (EvView *view);
+static void jump_to_find_page (EvView *view,
+ EvViewFindDirection direction,
+ gint shift);
/*** Selection ***/
static void compute_selections (EvView *view,
GdkPoint *start,
GdkPoint *stop);
static void clear_selection (EvView *view);
+static void selection_free (EvViewSelection *selection);
static char* get_selected_text (EvView *ev_view);
static void ev_view_primary_get_cb (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
static void
scroll_to_current_page (EvView *view, GtkOrientation orientation)
{
- GdkRectangle page_area;
- GtkBorder border;
+ GdkPoint view_point;
if (view->document == NULL) {
return;
}
- get_page_extents (view, view->current_page, &page_area, &border);
+ doc_point_to_view_point (view, view->current_page, &view->pending_point, &view_point);
+
+ if (orientation == GTK_ORIENTATION_VERTICAL) {
+ view->pending_point.y = 0;
+ } else {
+ view->pending_point.x = 0;
+ }
if (orientation == GTK_ORIENTATION_VERTICAL) {
if (view->continuous) {
gtk_adjustment_clamp_page (view->vadjustment,
- page_area.y - view->spacing,
- page_area.y + view->vadjustment->page_size);
+ view_point.y - view->spacing / 2,
+ view_point.y + view->vadjustment->page_size);
} else {
gtk_adjustment_set_value (view->vadjustment,
- view->vadjustment->lower);
+ CLAMP (view_point.y,
+ view->vadjustment->lower,
+ view->vadjustment->upper -
+ view->vadjustment->page_size));
}
} else {
if (view->dual_page) {
gtk_adjustment_clamp_page (view->hadjustment,
- page_area.x,
- page_area.x + view->hadjustment->page_size);
+ view_point.x,
+ view_point.x + view->hadjustment->page_size);
} else {
gtk_adjustment_set_value (view->hadjustment,
- CLAMP (view->hadjustment->value,
+ CLAMP (view_point.x,
view->hadjustment->lower,
view->hadjustment->upper -
view->hadjustment->page_size));
case SCROLL_TO_KEEP_POSITION:
factor = (adjustment->value) / adjustment->upper;
break;
- case SCROLL_TO_CURRENT_PAGE:
+ case SCROLL_TO_PAGE_POSITION:
break;
case SCROLL_TO_CENTER:
factor = (adjustment->value + adjustment->page_size * 0.5) / adjustment->upper;
new_value = CLAMP (adjustment->upper * factor + 0.5, 0, adjustment->upper - adjustment->page_size);
gtk_adjustment_set_value (adjustment, (int)new_value);
break;
- case SCROLL_TO_CURRENT_PAGE:
+ case SCROLL_TO_PAGE_POSITION:
scroll_to_current_page (view, orientation);
break;
case SCROLL_TO_CENTER:
static void
view_update_range_and_current_page (EvView *view)
{
+ gint current_page;
+
+ if (view->pending_scroll != SCROLL_TO_KEEP_POSITION)
+ return;
+
/* Presentation trumps all other modes */
if (view->presentation) {
view->start_page = view->current_page;
} else if (view->continuous) {
GdkRectangle current_area, unused, page_area;
GtkBorder border;
- gint current_page;
gboolean found = FALSE;
int i;
}
}
- current_page = ev_page_cache_get_current_page (view->page_cache);
-
- if (current_page < view->start_page || current_page > view->end_page) {
- view->current_page = view->start_page;
- ev_page_cache_set_current_page (view->page_cache, view->start_page);
- }
} else {
if (view->dual_page) {
- if (view->current_page % 2 == 0) {
+ if (view->current_page % 2 == ev_page_cache_get_dual_even_left (view->page_cache)) {
view->start_page = view->current_page;
if (view->current_page + 1 < ev_page_cache_get_n_pages (view->page_cache))
view->end_page = view->start_page + 1;
+ else
+ view->end_page = view->start_page;
} else {
- view->start_page = view->current_page - 1;
+ if (view->current_page - 1 < 0)
+ view->start_page = view->current_page;
+ else
+ view->start_page = view->current_page - 1;
view->end_page = view->current_page;
}
} else {
view->start_page = view->current_page;
view->end_page = view->current_page;
}
+
+ }
+
+ current_page = ev_page_cache_get_current_page (view->page_cache);
+
+ if (current_page < view->start_page || current_page > view->end_page) {
+ ev_page_cache_set_current_page (view->page_cache, view->start_page);
}
ev_pixbuf_cache_set_page_range (view->pixbuf_cache,
static void
add_scroll_binding_keypad (GtkBindingSet *binding_set,
guint keyval,
- GtkScrollType scroll,
+ GdkModifierType modifiers,
+ EvScrollType scroll,
gboolean horizontal)
{
guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
- gtk_binding_entry_add_signal (binding_set, keyval, 0,
+ gtk_binding_entry_add_signal (binding_set, keyval, modifiers,
"binding_activated", 2,
- GTK_TYPE_SCROLL_TYPE, scroll,
+ EV_TYPE_SCROLL_TYPE, scroll,
G_TYPE_BOOLEAN, horizontal);
- gtk_binding_entry_add_signal (binding_set, keypad_keyval, 0,
+ gtk_binding_entry_add_signal (binding_set, keypad_keyval, modifiers,
"binding_activated", 2,
- GTK_TYPE_SCROLL_TYPE, scroll,
+ EV_TYPE_SCROLL_TYPE, scroll,
G_TYPE_BOOLEAN, horizontal);
}
void
ev_view_scroll (EvView *view,
- EvScrollType scroll)
+ EvScrollType scroll,
+ gboolean horizontal)
{
GtkAdjustment *adjustment;
double value, increment;
gboolean first_page = FALSE;
gboolean last_page = FALSE;
+ if (view->presentation) {
+ switch (scroll) {
+ case EV_SCROLL_STEP_BACKWARD:
+ ev_view_previous_page (view);
+ break;
+ case EV_SCROLL_STEP_FORWARD:
+ ev_view_next_page (view);
+ break;
+ default:
+ break;
+ }
+ return;
+ }
+
/* Assign values for increment and vertical adjustment */
- adjustment = view->vadjustment;
+ adjustment = horizontal ? view->hadjustment : view->vadjustment;
increment = adjustment->page_size * 0.75;
value = adjustment->value;
/* At the top of a page, assign the upper bound limit of previous page */
} else if (value == (adjustment->lower)) {
value = adjustment->upper - adjustment->page_size;
- ev_page_cache_set_current_page (view->page_cache, view->current_page - 1);
+ ev_view_previous_page (view);
/* Jump to the top */
} else {
value = MAX (value - increment, adjustment->lower);
/* At the bottom of a page, assign the lower bound limit of next page */
} else if (value == (adjustment->upper - adjustment->page_size)) {
value = 0;
- ev_page_cache_set_current_page (view->page_cache, view->current_page + 1);
+ ev_view_next_page (view);
/* Jump to the bottom */
} else {
value = MIN (value + increment, adjustment->upper - adjustment->page_size);
}
break;
- default:
- break;
- }
-
- gtk_adjustment_set_value (adjustment, value);
-}
-
-static void
-ev_view_binding_activated (EvView *view,
- GtkScrollType scroll,
- gboolean horizontal)
-{
- GtkAdjustment *adjustment;
- double value;
-
- if (view->presentation) {
- switch (scroll) {
- case GTK_SCROLL_STEP_BACKWARD:
- ev_page_cache_prev_page (view->page_cache);
- break;
- case GTK_SCROLL_STEP_FORWARD:
- ev_page_cache_next_page (view->page_cache);
- break;
- default:
- break;
- }
- return;
- }
-
- if (horizontal) {
- adjustment = view->hadjustment;
- } else {
- adjustment = view->vadjustment;
- }
-
- value = adjustment->value;
-
- switch (scroll) {
- case GTK_SCROLL_STEP_BACKWARD:
+ case EV_SCROLL_STEP_BACKWARD:
value -= adjustment->step_increment;
break;
- case GTK_SCROLL_STEP_FORWARD:
+ case EV_SCROLL_STEP_FORWARD:
value += adjustment->step_increment;
break;
- default:
+ case EV_SCROLL_STEP_DOWN:
+ value -= adjustment->step_increment / 10;
+ break;
+ case EV_SCROLL_STEP_UP:
+ value += adjustment->step_increment / 10;
+ break;
+ default:
break;
}
value = CLAMP (value, adjustment->lower,
- adjustment->upper - adjustment->page_size);
+ adjustment->upper - adjustment->page_size);
gtk_adjustment_set_value (adjustment, value);
+
}
#define MARGIN 5
if (view->dual_page) {
ev_page_cache_get_height_to_page (view->page_cache, page,
view->rotation, zoom, NULL, &offset);
- offset += (page / 2 + 1) * view->spacing + (page / 2) * (border.top + border.bottom);
+ offset += ((page + ev_page_cache_get_dual_even_left (view->page_cache)) / 2 + 1) * view->spacing + ((page + ev_page_cache_get_dual_even_left (view->page_cache)) / 2 ) * (border.top + border.bottom);
} else {
ev_page_cache_get_height_to_page (view->page_cache, page,
view->rotation, zoom, &offset, NULL);
gint max_width;
gint x, y;
- ev_page_cache_get_max_width (view->page_cache, view->scale,
- view->rotation, &max_width);
+ ev_page_cache_get_max_width (view->page_cache, view->rotation,
+ view->scale, &max_width);
max_width = max_width + border->left + border->right;
/* Get the location of the bounding box */
if (view->dual_page) {
- x = view->spacing + (page % 2) * (max_width + view->spacing);
+ x = view->spacing + ((page % 2 == ev_page_cache_get_dual_even_left (view->page_cache)) ? 0 : 1) * (max_width + view->spacing);
x = x + MAX (0, widget->allocation.width - (max_width * 2 + view->spacing * 3)) / 2;
- if (page % 2 == 0)
+ if (page % 2 == ev_page_cache_get_dual_even_left (view->page_cache))
x = x + (max_width - width - border->left - border->right);
} else {
x = view->spacing;
GtkBorder overall_border;
gint other_page;
- other_page = page ^ 1;
+ other_page = (page % 2 == ev_page_cache_get_dual_even_left (view->page_cache)) ? page + 1: page - 1;
/* First, we get the bounding box of the two pages */
- if (other_page < ev_page_cache_get_n_pages (view->page_cache)) {
+ if (other_page < ev_page_cache_get_n_pages (view->page_cache)
+ && (0 <= other_page)) {
ev_page_cache_get_size (view->page_cache,
other_page,
view->rotation,
y = view->spacing;
/* Adjust for being the left or right page */
- if (page % 2 == 0)
+ if (page % 2 == ev_page_cache_get_dual_even_left (view->page_cache))
x = x + max_width - width;
else
x = x + (max_width + overall_border.left + overall_border.right) + view->spacing;
doc_rect->y2 = doc_rect->y1 + (double) view_rect->height / view->scale;
}
+static gboolean
+doc_point_to_view_point (EvView *view,
+ int page,
+ EvPoint *doc_point,
+ GdkPoint *view_point)
+{
+ GdkRectangle page_area;
+ GtkBorder border;
+ double x, y, view_x, view_y;
+ int width, height;
+
+ ev_page_cache_get_size (view->page_cache, page,
+ view->rotation,
+ 1.0,
+ &width, &height);
+
+ if (view->rotation == 0) {
+ x = doc_point->x;
+ y = doc_point->y;
+ } else if (view->rotation == 90) {
+ x = width - doc_point->y;
+ y = doc_point->x;
+ } else if (view->rotation == 180) {
+ x = width - doc_point->x;
+ y = height - doc_point->y;
+ } else if (view->rotation == 270) {
+ x = doc_point->y;
+ y = height - doc_point->x;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ get_page_extents (view, page, &page_area, &border);
+
+ view_x = x * view->scale;
+ view_y = y * view->scale;
+ view_point->x = view_x + page_area.x;
+ view_point->y = view_y + page_area.y;
+
+ return (view_x > 0 && view_x <= page_area.width &&
+ view_y > 0 && view_y <= page_area.height);
+}
+
static void
doc_rect_to_view_rect (EvView *view,
int page,
return FALSE;
}
+static int
+ev_view_get_width (EvView *view)
+{
+ return GTK_WIDGET (view)->allocation.width;
+}
+
+static int
+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);
return NULL;
}
-/* FIXME: standardize this sometime */
static void
-go_to_link (EvView *view, EvLink *link)
+goto_fitr_link (EvView *view, EvLink *link)
+{
+ EvPoint doc_point;
+ int page;
+ double zoom;
+
+ zoom = zoom_for_size_best_fit (ev_link_get_right (link) - ev_link_get_left (link),
+ ev_link_get_top (link) - ev_link_get_bottom (link),
+ 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);
+
+ page = ev_link_get_page (link);
+ doc_point.x = ev_link_get_left (link);
+ doc_point.y = ev_link_get_top (link);
+
+ view->current_page = page;
+ view->pending_point = doc_point;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+}
+
+static void
+goto_fitv_link (EvView *view, EvLink *link)
+{
+ EvPoint doc_point;
+ int doc_width, doc_height, page;
+ double zoom;
+
+ page = ev_link_get_page (link);
+ ev_page_cache_get_size (view->page_cache, page, 0, 1.0, &doc_width, &doc_height);
+
+ doc_point.x = ev_link_get_left (link);
+ doc_point.y = 0;
+
+ zoom = zoom_for_size_fit_height (doc_width - doc_point.x , doc_height,
+ ev_view_get_width (view),
+ ev_view_get_height (view), 0);
+
+ ev_view_set_sizing_mode (view, EV_SIZING_FREE);
+ ev_view_set_zoom (view, zoom, FALSE);
+
+ view->current_page = page;
+ view->pending_point = doc_point;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+}
+
+static void
+goto_fith_link (EvView *view, EvLink *link)
+{
+ EvPoint doc_point;
+ int doc_width, doc_height, page;
+ double zoom;
+
+ page = ev_link_get_page (link);
+ ev_page_cache_get_size (view->page_cache, page, 0, 1.0, &doc_width, &doc_height);
+
+ doc_point.x = 0;
+ doc_point.y = doc_height - ev_link_get_top (link);
+
+ zoom = zoom_for_size_fit_width (doc_width, ev_link_get_top (link),
+ ev_view_get_width (view),
+ ev_view_get_height (view), 0);
+
+ ev_view_set_sizing_mode (view, EV_SIZING_FREE);
+ ev_view_set_zoom (view, zoom, FALSE);
+
+ view->current_page = page;
+ view->pending_point = doc_point;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+}
+
+static void
+goto_fit_link (EvView *view, EvLink *link)
+{
+ double zoom;
+ int doc_width, doc_height;
+ int page;
+
+ page = ev_link_get_page (link);
+ ev_page_cache_get_size (view->page_cache, page, 0, 1.0, &doc_width, &doc_height);
+
+ zoom = zoom_for_size_best_fit (doc_width, doc_height, 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);
+
+ view->current_page = page;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+}
+
+static void
+goto_xyz_link (EvView *view, EvLink *link)
+{
+ EvPoint doc_point;
+ int height, page;
+ double zoom;
+
+ zoom = ev_link_get_zoom (link);
+ page = ev_link_get_page (link);
+ ev_page_cache_get_size (view->page_cache, page, 0, 1.0, NULL, &height);
+
+ if (zoom != 0) {
+ ev_view_set_sizing_mode (view, EV_SIZING_FREE);
+ ev_view_set_zoom (view, zoom, FALSE);
+ }
+
+ doc_point.x = ev_link_get_left (link);
+ doc_point.y = height - ev_link_get_top (link);
+
+ view->current_page = page;
+ view->pending_point = doc_point;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+}
+
+void
+ev_view_goto_link (EvView *view, EvLink *link)
{
EvLinkType type;
- const char *uri;
int page;
type = ev_link_get_link_type (link);
page = ev_link_get_page (link);
ev_page_cache_set_current_page (view->page_cache, page);
break;
+ case EV_LINK_TYPE_PAGE_FIT:
+ goto_fit_link (view, link);
+ break;
+ case EV_LINK_TYPE_PAGE_FITH:
+ goto_fith_link (view, link);
+ break;
+ case EV_LINK_TYPE_PAGE_FITV:
+ goto_fitv_link (view, link);
+ break;
+ case EV_LINK_TYPE_PAGE_FITR:
+ goto_fitr_link (view, link);
+ break;
+ case EV_LINK_TYPE_PAGE_XYZ:
+ goto_xyz_link (view, link);
+ break;
case EV_LINK_TYPE_EXTERNAL_URI:
- uri = ev_link_get_uri (link);
- gnome_vfs_url_show (uri);
+ case EV_LINK_TYPE_LAUNCH:
+ g_signal_emit (view, signals[SIGNAL_EXTERNAL_LINK], 0, link);
break;
}
}
static char *
-status_message_from_link (EvView *view, EvLink *link)
+tip_from_link (EvView *view, EvLink *link)
{
EvLinkType type;
char *msg = NULL;
msg = g_strdup (ev_link_get_title (link));
break;
case EV_LINK_TYPE_PAGE:
+ case EV_LINK_TYPE_PAGE_XYZ:
page_label = ev_page_cache_get_page_label (view->page_cache, ev_link_get_page (link));
msg = g_strdup_printf (_("Go to page %s"), page_label);
g_free (page_label);
return msg;
}
+static void
+handle_link_over_xy (EvView *view, gint x, gint y)
+{
+ EvLink *link;
+
+ link = ev_view_get_link_at_location (view, x, y);
+
+ if (view->link_tooltip == NULL) {
+ view->link_tooltip = ev_tooltip_new (GTK_WIDGET (view));
+ }
+
+ if (view->hovered_link != link) {
+ view->hovered_link = link;
+ ev_tooltip_deactivate (EV_TOOLTIP (view->link_tooltip));
+ }
+
+ if (link) {
+ char *msg = tip_from_link (view, link);
+
+ 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);
+ } else if (location_in_text (view, x + view->scroll_x, y + view->scroll_y)) {
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_IBEAM);
+ } else {
+ ev_view_set_status (view, NULL);
+ if (view->cursor == EV_VIEW_CURSOR_LINK ||
+ view->cursor == EV_VIEW_CURSOR_IBEAM)
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
+ }
+ return;
+}
/*** GtkWidget implementation ***/
EvView *view = EV_VIEW (widget);
if (view->sizing_mode == EV_SIZING_FIT_WIDTH ||
- view->sizing_mode == EV_SIZING_BEST_FIT) {
+ view->sizing_mode == EV_SIZING_BEST_FIT) {
g_signal_emit (view, signals[SIGNAL_ZOOM_INVALID], 0);
GDK_SCROLL_MASK |
GDK_KEY_PRESS_MASK |
GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK;
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
gdk_window_set_background (widget->window, &widget->style->mid [GTK_STATE_NORMAL]);
}
-static void
-ev_view_unrealize (GtkWidget *widget)
-{
- GTK_WIDGET_CLASS (ev_view_parent_class)->unrealize (widget);
-}
-
static gboolean
ev_view_scroll_event (GtkWidget *widget, GdkEventScroll *event)
{
- EvView *view = EV_VIEW (widget);
+ EvView *view = EV_VIEW (widget);
+ guint state;
- if ((event->state & GDK_CONTROL_MASK) != 0) {
+ state = event->state & gtk_accelerator_get_default_mod_mask ();
- ev_view_set_sizing_mode (view, EV_SIZING_FREE);
+ if (state == GDK_CONTROL_MASK && view->presentation == FALSE) {
+ ev_view_set_sizing_mode (view, EV_SIZING_FREE);
- if (event->direction == GDK_SCROLL_UP ||
- event->direction == GDK_SCROLL_LEFT) {
- if (ev_view_can_zoom_in (view)) {
- ev_view_zoom_in (view);
- }
- } else {
- if (ev_view_can_zoom_out (view)) {
- ev_view_zoom_out (view);
- }
- }
- return TRUE;
+ if (event->direction == GDK_SCROLL_UP ||
+ event->direction == GDK_SCROLL_LEFT) {
+ if (ev_view_can_zoom_in (view)) {
+ ev_view_zoom_in (view);
+ }
+ } else {
+ if (ev_view_can_zoom_out (view)) {
+ ev_view_zoom_out (view);
+ }
+ }
+
+ return TRUE;
}
- if ((event->state & GDK_SHIFT_MASK) != 0) {
+ /* 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)
+ event->direction = GDK_SCROLL_UP;
if (event->direction == GDK_SCROLL_DOWN)
event->direction = GDK_SCROLL_RIGHT;
+ if (event->direction == GDK_SCROLL_RIGHT)
+ event->direction = GDK_SCROLL_DOWN;
+
+ event->state &= ~GDK_SHIFT_MASK;
+ state &= ~GDK_SHIFT_MASK;
+ }
+
+ if (state == 0 && view->presentation) {
+ switch (event->direction) {
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_RIGHT:
+ ev_view_next_page (view);
+ break;
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_LEFT:
+ ev_view_previous_page (view);
+ break;
+ }
+
+ return TRUE;
}
return FALSE;
EvView *view = EV_VIEW (widget);
int i;
+ if (view->loading) {
+ draw_loading_text (view,
+ &(widget->allocation),
+ &(event->area));
+ }
+
if (view->document == NULL)
return FALSE;
return FALSE;
}
+static gboolean
+ev_view_popup_menu (GtkWidget *widget)
+{
+ gint x, y;
+ EvLink *link;
+ EvView *view = EV_VIEW (widget);
+
+ gtk_widget_get_pointer (widget, &x, &y);
+ link = ev_view_get_link_at_location (view, x, y);
+ g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, link);
+ return TRUE;
+}
+
static gboolean
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
- * scrolling changes window relative coordinates */
- view->drag_info.start.x = event->x_root;
- view->drag_info.start.y = event->y_root;
- view->drag_info.hadj = gtk_adjustment_get_value (view->hadjustment);
- view->drag_info.vadj = gtk_adjustment_get_value (view->vadjustment);
+ 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
+ * scrolling changes window relative coordinates */
+ view->drag_info.start.x = event->x_root;
+ view->drag_info.start.y = event->y_root;
+ view->drag_info.hadj = gtk_adjustment_get_value (view->hadjustment);
+ view->drag_info.vadj = gtk_adjustment_get_value (view->vadjustment);
+
+ 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)
+{
+ 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 x, y, shift = 0;
+ GtkWidget *widget = GTK_WIDGET (view);
+
+ gtk_widget_get_pointer (widget, &x, &y);
+
+ if (y > widget->allocation.height) {
+ shift = (y - widget->allocation.height) / 2;
+ } else if (y < 0) {
+ shift = y / 2;
+ }
- ev_view_set_cursor (view, EV_VIEW_CURSOR_DRAG);
+ 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;
+ if (x > widget->allocation.width) {
+ shift = (x - widget->allocation.width) / 2;
+ } else if (x < 0) {
+ shift = x / 2;
}
- return FALSE;
+ if (shift)
+ gtk_adjustment_set_value (view->hadjustment,
+ CLAMP (view->hadjustment->value + shift,
+ view->hadjustment->lower,
+ view->hadjustment->upper -
+ view->hadjustment->page_size));
+
+ return TRUE;
}
static gboolean
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);
- if (view->pressed_button == 1) {
- GdkPoint point;
+ 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) {
+
+ /* 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;
- point.x = event->x + view->scroll_x;
- point.y = event->y + view->scroll_y;
- compute_selections (view, &view->selection_info.start, &point);
+ 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
+ * mouse. */
+ if (!view->selection_update_id)
+ view->selection_update_id = g_idle_add ((GSourceFunc)selection_update_idle_cb, view);
return TRUE;
} else if (view->pressed_button == 2) {
return TRUE;
}
- } else if (view->pressed_button <= 0) {
- EvLink *link;
-
- link = get_link_at_location (view, event->x + view->scroll_x, event->y + view->scroll_y);
- if (link) {
- char *msg;
-
- msg = status_message_from_link (view, link);
- ev_view_set_status (view, msg);
- ev_view_set_cursor (view, EV_VIEW_CURSOR_LINK);
- g_free (msg);
- } else if (location_in_text (view, event->x + view->scroll_x, event->y + view->scroll_y)) {
- ev_view_set_cursor (view, EV_VIEW_CURSOR_IBEAM);
- } else {
- ev_view_set_status (view, NULL);
- if (view->cursor == EV_VIEW_CURSOR_LINK ||
- view->cursor == EV_VIEW_CURSOR_IBEAM)
- ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
- }
+ /* For the Evince 0.4.x release, we limit links to un-rotated documents
+ * only.
+ */
+ } else if (view->pressed_button <= 0 &&
+ view->rotation == 0) {
+ handle_link_over_xy (view, x, y);
return TRUE;
}
GdkEventButton *event)
{
EvView *view = EV_VIEW (widget);
+ EvLink *link;
if (view->pressed_button == 2) {
ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
}
+ if (view->document) {
+ link = ev_view_get_link_at_location (view, event->x, event->y);
+ } else {
+ link = NULL;
+ }
+
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);
- } else if (view->document) {
- EvLink *link;
-
- link = get_link_at_location (view, event->x + view->scroll_x, event->y + view->scroll_y);
- if (link) {
- go_to_link (view, link);
+
+ 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) {
+ switch (event->button) {
+ case 1:
+ ev_view_next_page (view);
+ return TRUE;
+ case 3:
+ ev_view_previous_page (view);
+ return TRUE;
}
}
+
+ return FALSE;
+}
+
+static gint
+ev_view_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ if (EV_VIEW (widget)->pixbuf_cache)
+ ev_pixbuf_cache_style_changed (EV_VIEW (widget)->pixbuf_cache);
+ gtk_widget_queue_draw (widget);
+
+ return FALSE;
+}
+
+static gint
+ev_view_focus_out (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ if (EV_VIEW (widget)->pixbuf_cache)
+ ev_pixbuf_cache_style_changed (EV_VIEW (widget)->pixbuf_cache);
+ gtk_widget_queue_draw (widget);
return FALSE;
}
view->cursor == EV_VIEW_CURSOR_IBEAM)
ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
+ if (view->link_tooltip) {
+ view->hovered_link = NULL;
+ ev_tooltip_deactivate (EV_TOOLTIP (view->link_tooltip));
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ev_view_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event)
+{
+ EvView *view = EV_VIEW (widget);
+
+ handle_link_over_xy (view, event->x, event->y);
+
return FALSE;
}
+static void
+ev_view_style_set (GtkWidget *widget,
+ GtkStyle *old_style)
+{
+ if (EV_VIEW (widget)->pixbuf_cache)
+ ev_pixbuf_cache_style_changed (EV_VIEW (widget)->pixbuf_cache);
+
+ GTK_WIDGET_CLASS (ev_view_parent_class)->style_set (widget, old_style);
+}
+
/*** Drawing ***/
static guint32
GdkRectangle view_rectangle;
guchar alpha;
- if (i == view->find_result && page == view->find_page) {
+ if (i == view->find_result && page == view->current_page) {
alpha = 0x90;
} else {
alpha = 0x20;
}
- ev_document_find_get_result (find, page,
- i, &rectangle);
+ ev_document_find_get_result (find, page, i, &rectangle);
doc_rect_to_view_rect (view, page, &rectangle, &view_rectangle);
draw_rubberband (GTK_WIDGET (view), GTK_WIDGET(view)->window,
&view_rectangle, alpha);
}
}
+static void
+draw_loading_text (EvView *view,
+ GdkRectangle *page_area,
+ GdkRectangle *expose_area)
+{
+ PangoLayout *layout;
+ PangoFontDescription *font_desc;
+ PangoRectangle logical_rect;
+ double real_scale;
+ int target_width;
+
+ const char *loading_text = _("Loading...");
+
+ layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), loading_text);
+
+ font_desc = pango_font_description_new ();
+
+
+ /* We set the font to be 10 points, get the size, and scale appropriately */
+ pango_font_description_set_size (font_desc, 10 * PANGO_SCALE);
+ pango_layout_set_font_description (layout, font_desc);
+ pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+
+ /* Make sure we fit the middle of the page */
+ target_width = MAX (page_area->width / 2, 1);
+ real_scale = ((double)target_width / (double) logical_rect.width) * (PANGO_SCALE * 10);
+ pango_font_description_set_size (font_desc, (int)real_scale);
+ pango_layout_set_font_description (layout, font_desc);
+ pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+
+ gtk_paint_layout (GTK_WIDGET (view)->style,
+ GTK_WIDGET (view)->window,
+ GTK_WIDGET_STATE (view),
+ FALSE,
+ page_area,
+ GTK_WIDGET (view),
+ NULL,
+ page_area->x + (target_width/2),
+ page_area->y + (page_area->height - logical_rect.height) / 2,
+ layout);
+
+ pango_font_description_free (font_desc);
+ g_object_unref (layout);
+}
+
static void
draw_one_page (EvView *view,
gint page,
if (current_pixbuf && view->selection_mode == EV_VIEW_SELECTION_TEXT && selection)
selection_pixbuf = ev_pixbuf_cache_get_selection_pixbuf (view->pixbuf_cache,
page,
- view->scale);
+ view->scale,
+ NULL);
if (current_pixbuf == NULL)
scaled_image = NULL;
GDK_RGB_DITHER_NORMAL,
0, 0);
g_object_unref (scaled_image);
+ } else {
+ draw_loading_text (view,
+ &real_page_area,
+ expose_area);
}
if (scaled_selection) {
g_object_unref (view->document);
view->document = NULL;
}
+
if (view->pixbuf_cache) {
g_object_unref (view->pixbuf_cache);
view->pixbuf_cache = NULL;
}
+
+ if (view->link_tooltip) {
+ 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);
case PROP_ZOOM:
ev_view_set_zoom (view, g_value_get_double (value), FALSE);
break;
+ case PROP_ROTATION:
+ ev_view_set_rotation (view, g_value_get_int (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
+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,
case PROP_ZOOM:
g_value_set_double (value, view->scale);
break;
+ 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);
}
widget_class->button_press_event = ev_view_button_press_event;
widget_class->motion_notify_event = ev_view_motion_notify_event;
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;
- widget_class->unrealize = ev_view_unrealize;
widget_class->scroll_event = ev_view_scroll_event;
+ 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;
+ widget_class->popup_menu = ev_view_popup_menu;
gtk_object_class->destroy = ev_view_destroy;
class->set_scroll_adjustments = ev_view_set_scroll_adjustments;
- class->binding_activated = ev_view_binding_activated;
+ class->binding_activated = ev_view_scroll;
widget_class->set_scroll_adjustments_signal =
g_signal_new ("set-scroll-adjustments",
NULL, NULL,
ev_marshal_VOID__VOID,
G_TYPE_NONE, 0, G_TYPE_NONE);
+ signals[SIGNAL_EXTERNAL_LINK] = g_signal_new ("external-link",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EvViewClass, external_link),
+ NULL, NULL,
+ 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,
MAX_SCALE,
1.0,
G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_ROTATION,
+ g_param_spec_double ("rotation",
+ "Rotation",
+ "Rotation",
+ 0,
+ 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);
- add_scroll_binding_keypad (binding_set, GDK_Left, GTK_SCROLL_STEP_BACKWARD, TRUE);
- add_scroll_binding_keypad (binding_set, GDK_Right, GTK_SCROLL_STEP_FORWARD, TRUE);
- add_scroll_binding_keypad (binding_set, GDK_Up, GTK_SCROLL_STEP_BACKWARD, FALSE);
- add_scroll_binding_keypad (binding_set, GDK_Down, GTK_SCROLL_STEP_FORWARD, FALSE);
+ add_scroll_binding_keypad (binding_set, GDK_Left, 0, EV_SCROLL_STEP_BACKWARD, TRUE);
+ add_scroll_binding_keypad (binding_set, GDK_Right, 0, EV_SCROLL_STEP_FORWARD, TRUE);
+ add_scroll_binding_keypad (binding_set, GDK_Left, GDK_MOD1_MASK, EV_SCROLL_STEP_DOWN, TRUE);
+ add_scroll_binding_keypad (binding_set, GDK_Right, GDK_MOD1_MASK, EV_SCROLL_STEP_UP, TRUE);
+ add_scroll_binding_keypad (binding_set, GDK_Up, 0, EV_SCROLL_STEP_BACKWARD, FALSE);
+ add_scroll_binding_keypad (binding_set, GDK_Down, 0, EV_SCROLL_STEP_FORWARD, FALSE);
+ add_scroll_binding_keypad (binding_set, GDK_Up, GDK_MOD1_MASK, EV_SCROLL_STEP_DOWN, FALSE);
+ add_scroll_binding_keypad (binding_set, GDK_Down, GDK_MOD1_MASK, EV_SCROLL_STEP_UP, FALSE);
}
static void
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;
static void
find_changed_cb (EvDocument *document, int page, EvView *view)
{
- jump_to_find_page (view);
- jump_to_find_result (view);
- update_find_status_message (view);
+ double percent;
+ int n_pages;
+ percent = ev_document_find_get_progress
+ (EV_DOCUMENT_FIND (view->document));
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+ jump_to_find_page (view, EV_VIEW_FIND_NEXT, 0);
+ jump_to_find_result (view);
+ update_find_status_message (view, percent * n_pages >= n_pages - 1 );
if (view->current_page == page)
gtk_widget_queue_draw (GTK_WIDGET (view));
}
if (view->current_page != new_page) {
view->current_page = new_page;
- view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
if (EV_IS_DOCUMENT_FIND (view->document)) {
- view->find_page = new_page;
view->find_result = 0;
- update_find_status_message (view);
+ update_find_status_message (view, TRUE);
}
}
}
{
view->page_cache = ev_page_cache_get (view->document);
g_signal_connect (view->page_cache, "page-changed", G_CALLBACK (page_changed_cb), view);
- view->pixbuf_cache = ev_pixbuf_cache_new (view->document);
+ view->pixbuf_cache = ev_pixbuf_cache_new (GTK_WIDGET (view), view->document);
g_signal_connect (view->pixbuf_cache, "job-finished", G_CALLBACK (job_finished_cb), view);
}
}
if (view->page_cache) {
- g_object_unref (view->page_cache);
view->page_cache = NULL;
}
}
+void
+ev_view_set_loading (EvView *view,
+ gboolean loading)
+{
+ view->loading = loading;
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+}
+
void
ev_view_set_document (EvView *view,
EvDocument *document)
{
g_return_if_fail (EV_IS_VIEW (view));
+ view->loading = FALSE;
+
if (document != view->document) {
clear_caches (view);
}
view->document = document;
- view->find_page = 0;
view->find_result = 0;
if (view->document) {
if (view->continuous != continuous) {
view->continuous = continuous;
- view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
}
if (view->dual_page == dual_page)
return;
- view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
view->dual_page = dual_page;
/* FIXME: if we're keeping the pixbuf cache around, we should extend the
* preload_cache_size to be 2 if dual_page is set.
return;
view->presentation = presentation;
- view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ view->pending_scroll = SCROLL_TO_PAGE_POSITION;
gtk_widget_queue_resize (GTK_WIDGET (view));
if (GTK_WIDGET_REALIZED (view)) {
ev_view_set_zoom (view, ZOOM_OUT_FACTOR, TRUE);
}
-static void
-ev_view_set_rotation (EvView *view, int rotation)
-{
- view->rotation = rotation;
-
- ev_pixbuf_cache_clear (view->pixbuf_cache);
- gtk_widget_queue_resize (GTK_WIDGET (view));
-}
-
void
ev_view_rotate_right (EvView *view)
{
ev_view_set_rotation (view, rotation);
}
+void
+ev_view_set_rotation (EvView *view, int rotation)
+{
+ view->rotation = rotation;
+
+ if (view->pixbuf_cache) {
+ ev_pixbuf_cache_clear (view->pixbuf_cache);
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+ }
+
+ if (rotation != 0)
+ clear_selection (view);
+
+ g_object_notify (G_OBJECT (view), "rotation");
+}
+
+int
+ev_view_get_rotation (EvView *view)
+{
+ return view->rotation;
+}
+
static double
zoom_for_size_fit_width (int doc_width,
int doc_height,
return scale;
}
+static double
+zoom_for_size_fit_height (int doc_width,
+ int doc_height,
+ int target_width,
+ int target_height,
+ int vsb_height)
+{
+ double scale;
+
+ scale = (double)target_height / doc_height;
+
+ if (doc_width * scale > target_width)
+ scale = (double) (target_height - vsb_height) / doc_height;
+
+ return scale;
+}
+
static double
zoom_for_size_best_fit (int doc_width,
int doc_height,
}
static void
-update_find_status_message (EvView *view)
+update_find_status_message (EvView *view, gboolean this_page)
{
char *message;
- if (view->current_page == view->find_page) {
+ if (this_page) {
int results;
results = ev_document_find_get_n_results
percent = ev_document_find_get_progress
(EV_DOCUMENT_FIND (view->document));
- if (percent >= (1.0 - 1e-10)) {
- message = g_strdup (_("Not found"));
- } else {
- message = g_strdup_printf (_("%3d%% remaining to search"),
- (int) ((1.0 - percent) * 100));
- }
-
+ message = g_strdup_printf (_("%3d%% remaining to search"),
+ (int) ((1.0 - percent) * 100));
+
}
ev_view_set_find_status (view, message);
g_free (message);
EvRectangle rect;
GdkRectangle view_rect;
int n_results;
- int page = view->find_page;
+ int page = view->current_page;
n_results = ev_document_find_get_n_results (find, page);
- if (n_results > view->find_result) {
+ if (n_results > 0 && view->find_result < n_results) {
ev_document_find_get_result
(find, page, view->find_result, &rect);
}
}
+/**
+ * jump_to_find_page
+ * @view: #EvView instance
+ * @direction: Direction to look
+ * @shift: Shift from current page
+ *
+ * Jumps to the first page that has occurences of searched word.
+ * Uses a direction where to look and a shift from current page.
+ *
+ */
static void
-jump_to_find_page (EvView *view)
+jump_to_find_page (EvView *view, EvViewFindDirection direction, gint shift)
{
int n_pages, i;
for (i = 0; i < n_pages; i++) {
int has_results;
int page;
-
- page = i + view->find_page;
+
+ if (direction == EV_VIEW_FIND_NEXT)
+ page = view->current_page + i;
+ else
+ page = view->current_page - i;
+ page += shift;
+
if (page >= n_pages) {
page = page - n_pages;
}
-
+ if (page < 0)
+ page = page + n_pages;
+
has_results = ev_document_find_page_has_results
(EV_DOCUMENT_FIND (view->document), page);
if (has_results == -1) {
- view->find_page = page;
break;
} else if (has_results == 1) {
ev_page_cache_set_current_page (view->page_cache, page);
- jump_to_find_result (view);
break;
}
}
gboolean
ev_view_can_find_next (EvView *view)
{
- int n_results = 0;
-
if (EV_IS_DOCUMENT_FIND (view->document)) {
EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
+ int i, n_pages;
- n_results = ev_document_find_get_n_results (find, view->current_page);
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+ for (i = 0; i < n_pages; i++) {
+ if (ev_document_find_get_n_results (find, i) > 0) {
+ return TRUE;
+ }
+ }
}
- return n_results > 0;
+ return FALSE;
}
void
ev_view_find_next (EvView *view)
{
- EvPageCache *page_cache;
int n_results, n_pages;
EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
- page_cache = ev_page_cache_get (view->document);
n_results = ev_document_find_get_n_results (find, view->current_page);
- n_pages = ev_page_cache_get_n_pages (page_cache);
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
view->find_result++;
if (view->find_result >= n_results) {
- view->find_result = 0;
- view->find_page++;
-
- if (view->find_page >= n_pages) {
- view->find_page = 0;
- }
- jump_to_find_page (view);
+ view->find_result = 0;
+ jump_to_find_page (view, EV_VIEW_FIND_NEXT, 1);
+ jump_to_find_result (view);
} else {
jump_to_find_result (view);
gtk_widget_queue_draw (GTK_WIDGET (view));
view->find_result--;
if (view->find_result < 0) {
- view->find_result = 0;
- view->find_page--;
-
- if (view->find_page < 0) {
- view->find_page = n_pages - 1;
- }
- jump_to_find_page (view);
+ jump_to_find_page (view, EV_VIEW_FIND_PREV, -1);
+ view->find_result = ev_document_find_get_n_results (find, view->current_page) - 1;
+ jump_to_find_result (view);
} else {
jump_to_find_result (view);
gtk_widget_queue_draw (GTK_WIDGET (view));
GList *list = NULL;
EvViewSelection *selection;
gint width, height;
+ int start_page, end_page;
g_assert (view->selection_mode == EV_VIEW_SELECTION_TEXT);
* affects. */
first = n_pages;
last = 0;
- for (i = 0; i < n_pages; i++) {
+ if (view->continuous) {
+ start_page = 0;
+ end_page = n_pages;
+ } else if (view->dual_page) {
+ start_page = view->start_page;
+ end_page = view->end_page + 1;
+ } else {
+ start_page = view->current_page;
+ end_page = view->current_page + 1;
+ }
+
+ for (i = start_page; i < end_page; i++) {
GdkRectangle page_area;
GtkBorder border;
*/
static void
merge_selection_region (EvView *view,
- GList *list)
+ GList *new_list)
{
+ GList *old_list;
+ GList *new_list_ptr, *old_list_ptr;
- /* FIXME: actually write... */
- clear_selection (view);
- gtk_widget_queue_draw (GTK_WIDGET (view));
+ /* Update the selection */
+ old_list = ev_pixbuf_cache_get_selection_list (view->pixbuf_cache);
+ 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;
+
+ while (new_list_ptr || old_list_ptr) {
+ EvViewSelection *old_sel, *new_sel;
+ int cur_page;
+ GdkRegion *region = NULL;
+
+ new_sel = (new_list_ptr) ? (new_list_ptr->data) : NULL;
+ old_sel = (old_list_ptr) ? (old_list_ptr->data) : NULL;
+
+ /* Assume that the lists are in order, and we run through them
+ * comparing them, one page at a time. We come out with the
+ * first page we see. */
+ if (new_sel && old_sel) {
+ if (new_sel->page < old_sel->page) {
+ new_list_ptr = new_list_ptr->next;
+ old_sel = NULL;
+ } else if (new_sel->page > old_sel->page) {
+ old_list_ptr = old_list_ptr->next;
+ new_sel = NULL;
+ } else {
+ new_list_ptr = new_list_ptr->next;
+ old_list_ptr = old_list_ptr->next;
+ }
+ } else if (new_sel) {
+ new_list_ptr = new_list_ptr->next;
+ } else if (old_sel) {
+ old_list_ptr = old_list_ptr->next;
+ }
+
+ g_assert (new_sel || old_sel);
+
+ /* is the page we're looking at on the screen?*/
+ cur_page = new_sel ? new_sel->page : old_sel->page;
+ if (cur_page < view->start_page || cur_page > view->end_page)
+ continue;
+
+ /* seed the cache with a new page. We are going to need the new
+ * region too. */
+ if (new_sel) {
+ GdkRegion *tmp_region = NULL;
+ ev_pixbuf_cache_get_selection_pixbuf (view->pixbuf_cache,
+ cur_page,
+ view->scale,
+ &tmp_region);
+ if (tmp_region) {
+ new_sel->covered_region = gdk_region_copy (tmp_region);
+ }
+ }
+
+ /* Now we figure out what needs redrawing */
+ if (old_sel && new_sel) {
+ if (old_sel->covered_region &&
+ new_sel->covered_region) {
+ /* We only want to redraw the areas that have
+ * changed, so we xor the old and new regions
+ * and redraw if it's different */
+ region = gdk_region_copy (old_sel->covered_region);
+ gdk_region_xor (region, new_sel->covered_region);
+ if (gdk_region_empty (region)) {
+ gdk_region_destroy (region);
+ region = NULL;
+ }
+ } else if (old_sel->covered_region) {
+ region = gdk_region_copy (old_sel->covered_region);
+ } else if (new_sel->covered_region) {
+ region = gdk_region_copy (new_sel->covered_region);
+ }
+ } else if (old_sel && !new_sel) {
+ if (old_sel->covered_region && !gdk_region_empty (old_sel->covered_region)) {
+ region = gdk_region_copy (old_sel->covered_region);
+ }
+ } else if (!old_sel && new_sel) {
+ if (new_sel->covered_region && !gdk_region_empty (new_sel->covered_region)) {
+ region = gdk_region_copy (new_sel->covered_region);
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+ /* Redraw the damaged region! */
+ if (region) {
+ GdkRectangle page_area;
+ GtkBorder border;
+
+ get_page_extents (view, cur_page, &page_area, &border);
+ gdk_region_offset (region,
+ page_area.x + border.left - view->scroll_x,
+ page_area.y + border.top - view->scroll_y);
+ gdk_window_invalidate_region (GTK_WIDGET (view)->window, region, TRUE);
+ gdk_region_destroy (region);
+ }
+ }
- view->selection_info.selections = list;
- ev_pixbuf_cache_set_selection_list (view->pixbuf_cache, list);
+ /* Free the old list, now that we're done with it. */
+ g_list_foreach (old_list, (GFunc) selection_free, NULL);
}
static void
static void
selection_free (EvViewSelection *selection)
{
+ if (selection->covered_region)
+ gdk_region_destroy (selection->covered_region);
g_free (selection);
}
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");
}
{
int n_pages, i;
+ /* Disable selection on rotated pages for the 0.4.0 series */
+ if (view->rotation != 0)
+ return;
+
clear_selection (view);
n_pages = ev_page_cache_get_n_pages (view->page_cache);
EvViewSelection *selection;
ev_page_cache_get_size (view->page_cache,
+ i,
view->rotation,
- i, 1.0, &width, &height);
+ 1.0, &width, &height);
selection = g_new0 (EvViewSelection, 1);
selection->page = i;
}
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)
{
ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
}
+gboolean
+ev_view_next_page (EvView *view)
+{
+ int page;
+
+ g_return_val_if_fail (EV_IS_VIEW (view), FALSE);
+
+ page = ev_page_cache_get_current_page (view->page_cache);
+ page = ev_view_get_dual_page (view) ? page + 2 : page + 1;
+
+ if (page < ev_page_cache_get_n_pages (view->page_cache)) {
+ ev_page_cache_set_current_page (view->page_cache, page);
+ return TRUE;
+ } else if (ev_view_get_dual_page (view) && page == ev_page_cache_get_n_pages (view->page_cache)) {
+ ev_page_cache_set_current_page (view->page_cache, page - 1);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+ev_view_previous_page (EvView *view)
+{
+ int page;
+
+ g_return_val_if_fail (EV_IS_VIEW (view), FALSE);
+
+ page = ev_page_cache_get_current_page (view->page_cache);
+ page = ev_view_get_dual_page (view) ? page - 2 : page - 1;
+
+ if (page >= 0) {
+ ev_page_cache_set_current_page (view->page_cache, page);
+ return TRUE;
+ } else if (ev_view_get_dual_page (view) && page == -1) {
+ ev_page_cache_set_current_page (view->page_cache, 0);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
/*** Enum description for usage in signal ***/
GType
return etype;
}
+GType
+ev_scroll_type_get_type (void)
+{
+ static GType etype = 0;
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ { EV_SCROLL_PAGE_FORWARD, "EV_SCROLL_PAGE_FORWARD", "scroll-page-forward" },
+ { EV_SCROLL_PAGE_BACKWARD, "EV_SCROLL_PAGE_BACKWARD", "scroll-page-backward" },
+ { EV_SCROLL_STEP_FORWARD, "EV_SCROLL_STEP_FORWARD", "scroll-step-forward" },
+ { EV_SCROLL_STEP_FORWARD, "EV_SCROLL_STEP_FORWARD", "scroll-step-forward" },
+ { EV_SCROLL_STEP_UP, "EV_SCROLL_STEP_UP", "scroll-step-up" },
+ { EV_SCROLL_STEP_DOWN, "EV_SCROLL_STEP_DOWN", "scroll-step-down" },
+ { 0, NULL, NULL }
+ };
+ etype = g_enum_register_static ("EvScrollType", values);
+ }
+ return etype;
+}