+static void
+doc_rect_to_view_rect (EvView *view,
+ int page,
+ EvRectangle *doc_rect,
+ GdkRectangle *view_rect)
+{
+ GdkRectangle page_area;
+ GtkBorder border;
+ int width, height;
+
+ get_page_extents (view, page, &page_area, &border);
+
+ width = doc_rect->x2 - doc_rect->x1;
+ height = doc_rect->y2 - doc_rect->y1;
+ view_rect->x = floor (doc_rect->x1 * view->scale) + page_area.x;
+ view_rect->y = floor (doc_rect->y1 * view->scale) + page_area.y;
+ view_rect->width = ceil (width * view->scale);
+ view_rect->height = ceil (height * view->scale);
+}
+
+static void
+find_page_at_location (EvView *view,
+ gdouble x,
+ gdouble y,
+ gint *page,
+ gint *x_offset,
+ gint *y_offset)
+{
+ int i;
+
+ if (view->document == NULL)
+ return;
+
+ g_assert (page);
+ g_assert (x_offset);
+ g_assert (y_offset);
+
+ for (i = view->start_page; i <= view->end_page; i++) {
+ GdkRectangle page_area;
+ GtkBorder border;
+
+ if (! get_page_extents (view, i, &page_area, &border))
+ continue;
+
+ if ((x >= page_area.x + border.left) &&
+ (x < page_area.x + page_area.width - border.right) &&
+ (y >= page_area.y + border.top) &&
+ (y < page_area.y + page_area.height - border.bottom)) {
+ *page = i;
+ *x_offset = x - (page_area.x + border.left);
+ *y_offset = y - (page_area.y + border.top);
+ return;
+ }
+ }
+
+ *page = -1;
+}
+
+/*** Hyperref ***/
+static EvLink *
+get_link_at_location (EvView *view,
+ gdouble x,
+ gdouble y)
+{
+ gint page = -1;
+ gint x_offset = 0, y_offset = 0;
+ GList *link_mapping;
+
+ find_page_at_location (view, x, y, &page, &x_offset, &y_offset);
+
+ if (page == -1)
+ return NULL;
+
+ link_mapping = ev_pixbuf_cache_get_link_mapping (view->pixbuf_cache, page);
+
+ if (link_mapping)
+ return ev_link_mapping_find (link_mapping, x_offset / view->scale, y_offset / view->scale);
+ else
+ return NULL;
+}
+
+/* FIXME: standardize this sometime */
+static void
+go_to_link (EvView *view, EvLink *link)
+{
+ EvLinkType type;
+ const char *uri;
+ int page;
+
+ type = ev_link_get_link_type (link);
+
+ switch (type) {
+ case EV_LINK_TYPE_TITLE:
+ break;
+ case EV_LINK_TYPE_PAGE:
+ page = ev_link_get_page (link);
+ ev_page_cache_set_current_page (view->page_cache, page);
+ break;
+ case EV_LINK_TYPE_EXTERNAL_URI:
+ uri = ev_link_get_uri (link);
+ gnome_vfs_url_show (uri);
+ break;
+ }
+}
+
+static char *
+status_message_from_link (EvView *view, EvLink *link)
+{
+ EvLinkType type;
+ char *msg = NULL;
+ char *page_label;
+
+ type = ev_link_get_link_type (link);
+
+ switch (type) {
+ case EV_LINK_TYPE_TITLE:
+ if (ev_link_get_title (link))
+ msg = g_strdup (ev_link_get_title (link));
+ break;
+ case EV_LINK_TYPE_PAGE:
+ 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);
+ break;
+ case EV_LINK_TYPE_EXTERNAL_URI:
+ msg = g_strdup (ev_link_get_uri (link));
+ break;
+ default:
+ break;
+ }
+
+ return msg;
+}
+
+
+/*** GtkWidget implementation ***/
+
+static void
+ev_view_size_request_continuous_dual_page (EvView *view,
+ GtkRequisition *requisition)
+{
+ int max_width;
+ gint n_pages;
+ GtkBorder border;
+
+ ev_page_cache_get_max_width (view->page_cache, view->scale, &max_width);
+ compute_border (view, max_width, max_width, &border);
+
+ n_pages = ev_page_cache_get_n_pages (view->page_cache) + 1;
+
+ requisition->width = (max_width + border.left + border.right) * 2 + (view->spacing * 3);
+ get_page_y_offset (view, n_pages, view->scale, &requisition->height);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH) {
+ requisition->width = 1;
+ } else if (view->sizing_mode == EV_SIZING_BEST_FIT) {
+ requisition->width = 1;
+ /* FIXME: This could actually be set on one page docs or docs
+ * with a strange aspect ratio. */
+ /* requisition->height = 1;*/
+ }
+}
+
+static void
+ev_view_size_request_continuous (EvView *view,
+ GtkRequisition *requisition)
+{
+ int max_width;
+ int n_pages;
+ GtkBorder border;
+
+
+ ev_page_cache_get_max_width (view->page_cache, view->scale, &max_width);
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+ compute_border (view, max_width, max_width, &border);
+
+ requisition->width = max_width + (view->spacing * 2) + border.left + border.right;
+ get_page_y_offset (view, n_pages, view->scale, &requisition->height);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH) {
+ requisition->width = 1;
+ } else if (view->sizing_mode == EV_SIZING_BEST_FIT) {
+ requisition->width = 1;
+ /* FIXME: This could actually be set on one page docs or docs
+ * with a strange aspect ratio. */
+ /* requisition->height = 1;*/
+ }
+}
+
+static void
+ev_view_size_request_dual_page (EvView *view,
+ GtkRequisition *requisition)
+{
+ GtkBorder border;
+ gint width, height;
+
+ /* Find the largest of the two. */
+ ev_page_cache_get_size (view->page_cache,
+ view->current_page,
+ view->scale,
+ &width, &height);
+ if (view->current_page + 1 < ev_page_cache_get_n_pages (view->page_cache)) {
+ gint width_2, height_2;
+ ev_page_cache_get_size (view->page_cache,
+ view->current_page + 1,
+ view->scale,
+ &width_2, &height_2);
+ if (width_2 > width) {
+ width = width_2;
+ height = height_2;
+ }
+ }
+ compute_border (view, width, height, &border);
+
+ requisition->width = ((width + border.left + border.right) * 2) +
+ (view->spacing * 3);
+ requisition->height = (height + border.top + border.bottom) +
+ (view->spacing * 2);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH) {
+ requisition->width = 1;
+ } else if (view->sizing_mode == EV_SIZING_BEST_FIT) {
+ requisition->width = 1;
+ requisition->height = 1;
+ }
+}
+
+static void
+ev_view_size_request_single_page (EvView *view,
+ GtkRequisition *requisition)
+{
+ GtkBorder border;
+ gint width, height;
+
+ ev_page_cache_get_size (view->page_cache,
+ view->current_page,
+ view->scale,
+ &width, &height);
+ compute_border (view, width, height, &border);
+
+ requisition->width = width + border.left + border.right + (2 * view->spacing);
+ requisition->height = height + border.top + border.bottom + (2 * view->spacing);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH) {
+ requisition->width = 1;
+ requisition->height = height + border.top + border.bottom + (2 * view->spacing);
+ } else if (view->sizing_mode == EV_SIZING_BEST_FIT) {
+ requisition->width = 1;
+ requisition->height = 1;
+ }
+}
+
+static void
+ev_view_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ EvView *view = EV_VIEW (widget);
+
+ if (view->document == NULL) {
+ requisition->width = 1;
+ requisition->height = 1;
+ return;
+ }
+
+ if (view->presentation) {
+ requisition->width = 1;
+ requisition->height = 1;
+ return;
+ }
+
+ if (view->continuous && view->dual_page)
+ ev_view_size_request_continuous_dual_page (view, requisition);
+ else if (view->continuous)
+ ev_view_size_request_continuous (view, requisition);
+ else if (view->dual_page)
+ ev_view_size_request_dual_page (view, requisition);
+ else
+ ev_view_size_request_single_page (view, requisition);
+}
+
+static void
+ev_view_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ EvView *view = EV_VIEW (widget);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH ||
+ view->sizing_mode == EV_SIZING_BEST_FIT) {
+
+ g_signal_emit (view, signals[SIGNAL_ZOOM_INVALID], 0);
+
+ ev_view_size_request (widget, &widget->requisition);
+ }
+
+ view_set_adjustment_values (view, GTK_ORIENTATION_HORIZONTAL);
+ view_set_adjustment_values (view, GTK_ORIENTATION_VERTICAL);
+
+ view->pending_scroll = SCROLL_TO_KEEP_POSITION;
+ view->pending_resize = FALSE;
+
+ if (view->document)
+ view_update_range_and_current_page (view);
+
+ GTK_WIDGET_CLASS (ev_view_parent_class)->size_allocate (widget, allocation);
+}
+
+static void
+ev_view_realize (GtkWidget *widget)
+{
+ EvView *view = EV_VIEW (widget);
+ GdkWindowAttr attributes;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.event_mask = GDK_EXPOSURE_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_LEAVE_NOTIFY_MASK;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ GDK_WA_X | GDK_WA_Y |
+ GDK_WA_COLORMAP |
+ GDK_WA_VISUAL);
+ gdk_window_set_user_data (widget->window, widget);
+ widget->style = gtk_style_attach (widget->style, widget->window);
+
+ if (view->presentation)
+ gdk_window_set_background (widget->window, &widget->style->black);
+ else
+ 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);
+
+ if ((event->state & GDK_CONTROL_MASK) != 0) {
+
+ 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->state & GDK_SHIFT_MASK) != 0) {
+ if (event->direction == GDK_SCROLL_UP)
+ event->direction = GDK_SCROLL_LEFT;
+ if (event->direction == GDK_SCROLL_DOWN)
+ event->direction = GDK_SCROLL_RIGHT;
+ }
+
+ return FALSE;
+}
+
+static EvViewSelection *
+find_selection_for_page (EvView *view,
+ gint page)
+{
+ GList *list;
+
+ for (list = view->selection_info.selections; list != NULL; list = list->next) {
+ EvViewSelection *selection;
+
+ selection = (EvViewSelection *) list->data;
+
+ if (selection->page == page)
+ return selection;
+ }
+
+ return NULL;
+}
+
+static gboolean
+ev_view_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ EvView *view = EV_VIEW (widget);
+ int i;
+
+ if (view->document == NULL)
+ return FALSE;
+
+ for (i = view->start_page; i <= view->end_page; i++) {
+ GdkRectangle page_area;
+ GtkBorder border;
+
+ if (!get_page_extents (view, i, &page_area, &border))
+ continue;
+
+ page_area.x -= view->scroll_x;
+ page_area.y -= view->scroll_y;
+
+ draw_one_page (view, i, &page_area, &border, &(event->area));
+
+ if (EV_IS_DOCUMENT_FIND (view->document))
+ highlight_find_results (view, i);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ev_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EvView *view = EV_VIEW (widget);
+
+ if (!GTK_WIDGET_HAS_FOCUS (widget)) {
+ gtk_widget_grab_focus (widget);
+ }
+
+ view->pressed_button = event->button;
+
+ switch (event->button) {
+ case 1:
+ if (view->selection_info.selections) {
+ clear_selection (view);
+ gtk_widget_queue_draw (widget);
+ }
+
+ view->selection_info.start.x = event->x;
+ view->selection_info.start.y = event->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;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ev_view_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ EvView *view = EV_VIEW (widget);
+
+ if (!view->document)
+ return FALSE;
+
+ if (view->pressed_button == 1) {
+ GdkRectangle selection;
+ view->selection_info.in_selection = TRUE;
+
+ selection.x = MIN (view->selection_info.start.x, event->x) + view->scroll_x;
+ selection.y = MIN (view->selection_info.start.y, event->y) + view->scroll_y;
+ selection.width = ABS (view->selection_info.start.x - event->x) + 1;
+ selection.height = ABS (view->selection_info.start.y - event->y) + 1;
+
+ compute_selections (view, &selection);
+
+ return TRUE;
+ } else if (view->pressed_button == 2) {
+ if (!view->drag_info.in_drag) {
+ gboolean start;
+
+ start = gtk_drag_check_threshold (widget,
+ view->drag_info.start.x,
+ view->drag_info.start.y,
+ event->x_root,
+ event->y_root);
+ view->drag_info.in_drag = start;
+ }
+
+ if (view->drag_info.in_drag) {
+ int dx, dy;
+ gdouble dhadj_value, dvadj_value;
+
+ dx = event->x_root - view->drag_info.start.x;
+ dy = event->y_root - view->drag_info.start.y;
+
+ dhadj_value = view->hadjustment->page_size *
+ (gdouble)dx / widget->allocation.width;
+ dvadj_value = view->vadjustment->page_size *
+ (gdouble)dy / widget->allocation.height;
+
+ /* clamp scrolling to visible area */
+ gtk_adjustment_set_value (view->hadjustment,
+ MIN(view->drag_info.hadj - dhadj_value,
+ view->hadjustment->upper -
+ view->hadjustment->page_size));
+ gtk_adjustment_set_value (view->vadjustment,
+ MIN(view->drag_info.vadj - dvadj_value,
+ view->vadjustment->upper -
+ view->vadjustment->page_size));
+
+ 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 {
+ ev_view_set_status (view, NULL);
+ if (view->cursor == EV_VIEW_CURSOR_LINK) {
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
+ }
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ev_view_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EvView *view = EV_VIEW (widget);
+
+ if (view->pressed_button == 2) {
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
+ }
+
+ view->pressed_button = -1;
+ view->drag_info.in_drag = FALSE;
+
+ 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);
+ }
+ }
+
+ return FALSE;
+}
+
+/*** Drawing ***/
+
+static guint32
+ev_gdk_color_to_rgb (const GdkColor *color)