+ 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,
+ GdkRectangle *page_area,
+ GtkBorder *border,
+ GdkRectangle *expose_area)
+{
+ gint width, height;
+ GdkPixbuf *current_pixbuf;
+ GdkRectangle overlap;
+ GdkRectangle real_page_area;
+ EvViewSelection *selection;
+
+ g_assert (view->document);
+ if (! gdk_rectangle_intersect (page_area, expose_area, &overlap))
+ return;
+
+ selection = find_selection_for_page (view, page);
+ ev_page_cache_get_size (view->page_cache,
+ page, view->rotation,
+ view->scale,
+ &width, &height);
+ /* Render the document itself */
+ real_page_area = *page_area;
+
+ real_page_area.x += border->left;
+ real_page_area.y += border->top;
+ real_page_area.width -= (border->left + border->right);
+ real_page_area.height -= (border->top + border->bottom);
+
+ ev_document_misc_paint_one_page (GTK_WIDGET(view)->window,
+ GTK_WIDGET (view),
+ page_area, border);
+
+ if (gdk_rectangle_intersect (&real_page_area, expose_area, &overlap)) {
+ GdkPixbuf *selection_pixbuf = NULL;
+ GdkPixbuf *scaled_image;
+ GdkPixbuf *scaled_selection;
+
+ current_pixbuf = ev_pixbuf_cache_get_pixbuf (view->pixbuf_cache, page);
+
+ /* Get the selection pixbuf iff we have something to draw */
+ 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,
+ NULL);
+
+ if (current_pixbuf == NULL)
+ scaled_image = NULL;
+ else if (width == gdk_pixbuf_get_width (current_pixbuf) &&
+ height == gdk_pixbuf_get_height (current_pixbuf))
+ scaled_image = g_object_ref (current_pixbuf);
+ else
+ /* FIXME: We don't want to scale the whole area, just the right
+ * area of it */
+ scaled_image = gdk_pixbuf_scale_simple (current_pixbuf,
+ width, height,
+ GDK_INTERP_NEAREST);
+
+ if (selection_pixbuf == NULL)
+ scaled_selection = NULL;
+ else if (width == gdk_pixbuf_get_width (selection_pixbuf) &&
+ height == gdk_pixbuf_get_height (selection_pixbuf))
+ scaled_selection = g_object_ref (selection_pixbuf);
+ else
+ /* FIXME: We don't want to scale the whole area, just the right
+ * area of it */
+ scaled_selection = gdk_pixbuf_scale_simple (selection_pixbuf,
+ width, height,
+ GDK_INTERP_NEAREST);
+
+ if (scaled_image) {
+ gdk_draw_pixbuf (GTK_WIDGET(view)->window,
+ GTK_WIDGET (view)->style->fg_gc[GTK_STATE_NORMAL],
+ scaled_image,
+ overlap.x - real_page_area.x,
+ overlap.y - real_page_area.y,
+ overlap.x, overlap.y,
+ overlap.width, overlap.height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+ g_object_unref (scaled_image);
+ } else {
+ draw_loading_text (view,
+ &real_page_area,
+ expose_area);
+ }
+
+ if (scaled_selection) {
+ gdk_draw_pixbuf (GTK_WIDGET(view)->window,
+ GTK_WIDGET (view)->style->fg_gc[GTK_STATE_NORMAL],
+ scaled_selection,
+ overlap.x - real_page_area.x,
+ overlap.y - real_page_area.y,
+ overlap.x, overlap.y,
+ overlap.width, overlap.height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+ g_object_unref (scaled_selection);
+ }
+ }
+}
+
+/*** GObject functions ***/
+
+static void
+ev_view_finalize (GObject *object)
+{
+ EvView *view = EV_VIEW (object);
+
+ LOG ("Finalize");
+
+ g_free (view->status);
+ g_free (view->find_status);
+
+ clear_selection (view);
+
+ G_OBJECT_CLASS (ev_view_parent_class)->finalize (object);
+}
+
+static void
+ev_view_destroy (GtkObject *object)
+{
+ EvView *view = EV_VIEW (object);
+
+ if (view->document) {
+ 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);
+ }
+
+ ev_view_set_scroll_adjustments (view, NULL, NULL);
+
+ GTK_OBJECT_CLASS (ev_view_parent_class)->destroy (object);
+}
+
+static void
+ev_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EvView *view = EV_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTINUOUS:
+ ev_view_set_continuous (view, g_value_get_boolean (value));
+ break;
+ case PROP_DUAL_PAGE:
+ ev_view_set_dual_page (view, g_value_get_boolean (value));
+ break;
+ case PROP_FULLSCREEN:
+ ev_view_set_fullscreen (view, g_value_get_boolean (value));
+ break;
+ case PROP_PRESENTATION:
+ ev_view_set_presentation (view, g_value_get_boolean (value));
+ break;
+ case PROP_SIZING_MODE:
+ ev_view_set_sizing_mode (view, g_value_get_enum (value));
+ break;
+ 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 void
+ev_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EvView *view = EV_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_STATUS:
+ g_value_set_string (value, view->status);
+ break;
+ case PROP_FIND_STATUS:
+ g_value_set_string (value, view->status);
+ break;
+ case PROP_CONTINUOUS:
+ g_value_set_boolean (value, view->continuous);
+ break;
+ case PROP_DUAL_PAGE:
+ g_value_set_boolean (value, view->dual_page);
+ break;
+ case PROP_FULLSCREEN:
+ g_value_set_boolean (value, view->fullscreen);
+ break;
+ case PROP_PRESENTATION:
+ g_value_set_boolean (value, view->presentation);
+ break;
+ case PROP_SIZING_MODE:
+ g_value_set_enum (value, view->sizing_mode);
+ break;
+ case PROP_ZOOM:
+ g_value_set_double (value, view->scale);
+ break;
+ case PROP_ROTATION:
+ g_value_set_int (value, view->rotation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ev_view_class_init (EvViewClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+ GtkBindingSet *binding_set;
+
+ object_class->finalize = ev_view_finalize;
+ object_class->set_property = ev_view_set_property;
+ object_class->get_property = ev_view_get_property;
+
+ widget_class->expose_event = ev_view_expose_event;
+ 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->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;
+ gtk_object_class->destroy = ev_view_destroy;
+
+ class->set_scroll_adjustments = ev_view_set_scroll_adjustments;
+ class->binding_activated = ev_view_binding_activated;
+
+ widget_class->set_scroll_adjustments_signal =
+ g_signal_new ("set-scroll-adjustments",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EvViewClass, set_scroll_adjustments),
+ NULL, NULL,
+ ev_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_ADJUSTMENT,
+ GTK_TYPE_ADJUSTMENT);
+
+ signals[SIGNAL_BINDING_ACTIVATED] = g_signal_new ("binding_activated",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EvViewClass, binding_activated),
+ NULL, NULL,
+ ev_marshal_VOID__ENUM_BOOLEAN,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_SCROLL_TYPE,
+ G_TYPE_BOOLEAN);
+
+ signals[SIGNAL_ZOOM_INVALID] = g_signal_new ("zoom-invalid",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EvViewClass, zoom_invalid),
+ NULL, NULL,
+ ev_marshal_VOID__VOID,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ g_object_class_install_property (object_class,
+ PROP_STATUS,
+ g_param_spec_string ("status",
+ "Status Message",
+ "The status message",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_FIND_STATUS,
+ g_param_spec_string ("find-status",
+ "Find Status Message",
+ "The find status message",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_CONTINUOUS,
+ g_param_spec_boolean ("continuous",
+ "Continuous",
+ "Continuous scrolling mode",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_DUAL_PAGE,
+ g_param_spec_boolean ("dual-page",
+ "Dual Page",
+ "Two pages visible at once",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_FULLSCREEN,
+ g_param_spec_boolean ("fullscreen",
+ "Full Screen",
+ "Draw page in a fullscreen fashion",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_PRESENTATION,
+ g_param_spec_boolean ("presentation",
+ "Presentation",
+ "Draw page in presentation mode",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SIZING_MODE,
+ g_param_spec_enum ("sizing-mode",
+ "Sizing Mode",
+ "Sizing Mode",
+ EV_TYPE_SIZING_MODE,
+ EV_SIZING_FIT_WIDTH,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ZOOM,
+ g_param_spec_double ("zoom",
+ "Zoom factor",
+ "Zoom factor",
+ MIN_SCALE,
+ 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));
+
+ 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);
+}
+
+static void
+ev_view_init (EvView *view)
+{
+ GTK_WIDGET_SET_FLAGS (view, GTK_CAN_FOCUS);
+
+ view->spacing = 5;
+ view->scale = 1.0;
+ view->current_page = 0;
+ view->pressed_button = -1;
+ view->cursor = EV_VIEW_CURSOR_NORMAL;
+ view->drag_info.in_drag = FALSE;
+ view->selection_info.in_selection = FALSE;
+ view->selection_mode = EV_VIEW_SELECTION_TEXT;
+ view->continuous = TRUE;
+ view->dual_page = FALSE;
+ view->presentation = FALSE;
+ view->fullscreen = FALSE;
+ view->sizing_mode = EV_SIZING_FIT_WIDTH;
+ view->pending_scroll = SCROLL_TO_KEEP_POSITION;
+}
+
+/*** Callbacks ***/
+
+static void
+find_changed_cb (EvDocument *document, int page, EvView *view)
+{
+ jump_to_find_page (view, EV_VIEW_FIND_NEXT);
+ jump_to_find_result (view);
+ update_find_status_message (view);
+
+ if (view->current_page == page)
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+}
+
+static void
+job_finished_cb (EvPixbufCache *pixbuf_cache,
+ EvView *view)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+}
+
+static void
+page_changed_cb (EvPageCache *page_cache,
+ int new_page,
+ EvView *view)
+{
+ if (view->current_page != new_page) {
+
+ view->current_page = new_page;
+ view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ 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);
+ }
+ }
+}
+
+static void on_adjustment_value_changed (GtkAdjustment *adjustment,
+ EvView *view)
+{
+ int dx = 0, dy = 0;
+
+ if (! GTK_WIDGET_REALIZED (view))
+ return;
+
+ if (view->hadjustment) {
+ dx = view->scroll_x - (int) view->hadjustment->value;
+ view->scroll_x = (int) view->hadjustment->value;
+ } else {
+ view->scroll_x = 0;
+ }
+
+ if (view->vadjustment) {
+ dy = view->scroll_y - (int) view->vadjustment->value;
+ view->scroll_y = (int) view->vadjustment->value;
+ } else {
+ view->scroll_y = 0;
+ }
+
+
+ if (view->pending_resize)
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+ else
+ gdk_window_scroll (GTK_WIDGET (view)->window, dx, dy);
+
+
+ if (view->document)
+ view_update_range_and_current_page (view);
+}
+
+GtkWidget*
+ev_view_new (void)
+{
+ GtkWidget *view;
+
+ view = g_object_new (EV_TYPE_VIEW, NULL);
+
+ return view;
+}
+
+static void
+setup_caches (EvView *view)
+{
+ 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 (GTK_WIDGET (view), view->document);
+ g_signal_connect (view->pixbuf_cache, "job-finished", G_CALLBACK (job_finished_cb), view);
+}
+
+static void
+clear_caches (EvView *view)
+{
+ if (view->pixbuf_cache) {
+ g_object_unref (view->pixbuf_cache);
+ view->pixbuf_cache = NULL;
+ }
+
+ if (view->page_cache) {
+ view->page_cache = NULL;
+ }
+}
+
+void
+ev_view_set_document (EvView *view,
+ EvDocument *document)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ if (document != view->document) {
+ clear_caches (view);
+
+ if (view->document) {
+ g_signal_handlers_disconnect_by_func (view->document,
+ find_changed_cb,
+ view);
+ g_object_unref (view->document);
+ view->page_cache = NULL;
+
+ }
+
+ view->document = document;
+ view->find_page = 0;
+ view->find_result = 0;
+
+ if (view->document) {
+ g_object_ref (view->document);
+ if (EV_IS_DOCUMENT_FIND (view->document)) {
+ g_signal_connect (view->document,
+ "find_changed",
+ G_CALLBACK (find_changed_cb),
+ view);
+ }
+
+ setup_caches (view);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+ }
+}
+
+/*** Zoom and sizing mode ***/
+
+#define EPSILON 0.0000001
+void
+ev_view_set_zoom (EvView *view,
+ double factor,
+ gboolean relative)
+{
+ double scale;
+
+ if (relative)
+ scale = view->scale * factor;
+ else
+ scale = factor;
+
+ scale = CLAMP (scale, MIN_SCALE, MAX_SCALE);
+
+ if (ABS (view->scale - scale) < EPSILON)
+ return;
+
+ view->scale = scale;
+ view->pending_resize = TRUE;
+
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+
+ g_object_notify (G_OBJECT (view), "zoom");
+}
+
+double
+ev_view_get_zoom (EvView *view)
+{
+ return view->scale;
+}
+
+gboolean
+ev_view_get_continuous (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), FALSE);
+
+ return view->continuous;
+}
+
+void
+ev_view_set_continuous (EvView *view,
+ gboolean continuous)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ continuous = continuous != FALSE;
+
+ if (view->continuous != continuous) {
+ view->continuous = continuous;
+ view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+ }
+
+ g_object_notify (G_OBJECT (view), "continuous");
+}
+
+gboolean
+ev_view_get_dual_page (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), FALSE);
+
+ return view->dual_page;
+}
+
+void
+ev_view_set_dual_page (EvView *view,
+ gboolean dual_page)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ dual_page = dual_page != FALSE;
+
+ if (view->dual_page == dual_page)
+ return;
+
+ view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ 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.
+ */
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+
+ g_object_notify (G_OBJECT (view), "dual-page");
+}
+
+void
+ev_view_set_fullscreen (EvView *view,
+ gboolean fullscreen)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ fullscreen = fullscreen != FALSE;
+
+ if (view->fullscreen == fullscreen)
+ return;
+
+ view->fullscreen = fullscreen;
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+
+ g_object_notify (G_OBJECT (view), "fullscreen");
+}
+
+gboolean
+ev_view_get_fullscreen (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), FALSE);
+
+ return view->fullscreen;
+}
+
+void
+ev_view_set_presentation (EvView *view,
+ gboolean presentation)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ presentation = presentation != FALSE;
+
+ if (view->presentation == presentation)
+ return;
+
+ view->presentation = presentation;
+ view->pending_scroll = SCROLL_TO_CURRENT_PAGE;
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+
+ if (GTK_WIDGET_REALIZED (view)) {
+ if (view->presentation)
+ gdk_window_set_background (GTK_WIDGET(view)->window,
+ >K_WIDGET (view)->style->black);
+ else
+ gdk_window_set_background (GTK_WIDGET(view)->window,
+ >K_WIDGET (view)->style->mid [GTK_STATE_NORMAL]);
+ }
+
+
+ g_object_notify (G_OBJECT (view), "presentation");
+}
+
+gboolean
+ev_view_get_presentation (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), FALSE);
+
+ return view->presentation;
+}
+
+void
+ev_view_set_sizing_mode (EvView *view,
+ EvSizingMode sizing_mode)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ if (view->sizing_mode == sizing_mode)
+ return;
+
+ view->sizing_mode = sizing_mode;
+ gtk_widget_queue_resize (GTK_WIDGET (view));
+
+ g_object_notify (G_OBJECT (view), "sizing-mode");
+}
+
+EvSizingMode
+ev_view_get_sizing_mode (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), EV_SIZING_FREE);
+
+ return view->sizing_mode;
+}
+
+gboolean
+ev_view_can_zoom_in (EvView *view)
+{
+ return view->scale * ZOOM_IN_FACTOR <= MAX_SCALE;
+}
+
+gboolean
+ev_view_can_zoom_out (EvView *view)
+{
+ return view->scale * ZOOM_OUT_FACTOR >= MIN_SCALE;
+}
+
+void
+ev_view_zoom_in (EvView *view)
+{
+ g_return_if_fail (view->sizing_mode == EV_SIZING_FREE);
+
+ view->pending_scroll = SCROLL_TO_CENTER;
+ ev_view_set_zoom (view, ZOOM_IN_FACTOR, TRUE);
+}
+
+void
+ev_view_zoom_out (EvView *view)
+{
+ g_return_if_fail (view->sizing_mode == EV_SIZING_FREE);
+
+ view->pending_scroll = SCROLL_TO_CENTER;
+ ev_view_set_zoom (view, ZOOM_OUT_FACTOR, TRUE);
+}
+
+void
+ev_view_rotate_right (EvView *view)
+{
+ int rotation = view->rotation + 90;
+
+ if (rotation >= 360) {
+ rotation -= 360;
+ }
+
+ ev_view_set_rotation (view, rotation);
+}
+
+void
+ev_view_rotate_left (EvView *view)
+{
+ int rotation = view->rotation - 90;
+
+ if (rotation < 0) {
+ rotation += 360;
+ }
+
+ 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,
+ int target_width,
+ int target_height,
+ int vsb_width)
+{
+ double scale;
+
+ scale = (double)target_width / doc_width;
+
+ if (doc_height * scale > target_height)
+ scale = (double) (target_width - vsb_width) / doc_width;
+
+ return scale;
+}
+
+static double
+zoom_for_size_best_fit (int doc_width,
+ int doc_height,
+ int target_width,
+ int target_height,
+ int vsb_width,
+ int hsb_width)
+{
+ double w_scale;
+ double h_scale;
+
+ w_scale = (double)target_width / doc_width;
+ h_scale = (double)target_height / doc_height;
+
+ if (doc_height * w_scale > target_height)
+ w_scale = (double) (target_width - vsb_width) / doc_width;
+ if (doc_width * h_scale > target_width)
+ h_scale = (double) (target_height - hsb_width) / doc_height;
+
+ return MIN (w_scale, h_scale);
+}
+
+
+static void
+ev_view_zoom_for_size_presentation (EvView *view,
+ int width,
+ int height)
+{
+ int doc_width, doc_height;
+ gdouble scale;
+
+ ev_page_cache_get_size (view->page_cache,
+ view->current_page,
+ view->rotation,
+ 1.0,
+ &doc_width,
+ &doc_height);
+ scale = zoom_for_size_best_fit (doc_width, doc_height, width, height, 0, 0);
+ ev_view_set_zoom (view, scale, FALSE);
+}
+
+static void
+ev_view_zoom_for_size_continuous_and_dual_page (EvView *view,
+ int width,
+ int height,
+ int vsb_width,
+ int hsb_height)
+{
+ int doc_width, doc_height;
+ GtkBorder border;
+ gdouble scale;
+
+ ev_page_cache_get_max_width (view->page_cache,
+ view->rotation,
+ 1.0,
+ &doc_width);
+ ev_page_cache_get_max_height (view->page_cache,
+ view->rotation,
+ 1.0,
+ &doc_height);
+ compute_border (view, doc_width, doc_height, &border);
+
+ doc_width = doc_width * 2;
+ width -= (2 * (border.left + border.right) + 3 * view->spacing);
+ height -= (border.top + border.bottom + 2 * view->spacing - 1);
+
+ /* FIXME: We really need to calculate the overall height here, not the
+ * page height. We assume there's always a vertical scrollbar for
+ * now. We need to fix this. */
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH)
+ scale = zoom_for_size_fit_width (doc_width, doc_height, width - vsb_width, height, 0);
+ else if (view->sizing_mode == EV_SIZING_BEST_FIT)
+ scale = zoom_for_size_best_fit (doc_width, doc_height, width - vsb_width, height, 0, hsb_height);
+ else
+ g_assert_not_reached ();
+
+ ev_view_set_zoom (view, scale, FALSE);
+}
+
+static void
+ev_view_zoom_for_size_continuous (EvView *view,
+ int width,
+ int height,
+ int vsb_width,
+ int hsb_height)
+{
+ int doc_width, doc_height;
+ GtkBorder border;
+ gdouble scale;
+
+ ev_page_cache_get_max_width (view->page_cache,
+ view->rotation,
+ 1.0,
+ &doc_width);
+ ev_page_cache_get_max_height (view->page_cache,
+ view->rotation,
+ 1.0,
+ &doc_height);
+ compute_border (view, doc_width, doc_height, &border);
+
+ width -= (border.left + border.right + 2 * view->spacing);
+ height -= (border.top + border.bottom + 2 * view->spacing - 1);
+
+ /* FIXME: We really need to calculate the overall height here, not the
+ * page height. We assume there's always a vertical scrollbar for
+ * now. We need to fix this. */
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH)
+ scale = zoom_for_size_fit_width (doc_width, doc_height, width - vsb_width, height, 0);
+ else if (view->sizing_mode == EV_SIZING_BEST_FIT)
+ scale = zoom_for_size_best_fit (doc_width, doc_height, width - vsb_width, height, 0, hsb_height);
+ else
+ g_assert_not_reached ();
+
+ ev_view_set_zoom (view, scale, FALSE);
+}
+
+static void
+ev_view_zoom_for_size_dual_page (EvView *view,
+ int width,
+ int height,
+ int vsb_width,
+ int hsb_height)
+{
+ GtkBorder border;
+ gint doc_width, doc_height;
+ gdouble scale;
+ gint other_page;
+
+ other_page = view->current_page ^ 1;
+
+ /* Find the largest of the two. */
+ ev_page_cache_get_size (view->page_cache,
+ view->current_page,
+ view->rotation,
+ 1.0,
+ &doc_width, &doc_height);
+
+ if (other_page < ev_page_cache_get_n_pages (view->page_cache)) {
+ gint width_2, height_2;
+ ev_page_cache_get_size (view->page_cache,
+ other_page,
+ view->rotation,
+ 1.0,
+ &width_2, &height_2);
+ if (width_2 > doc_width)
+ doc_width = width_2;
+ if (height_2 > doc_height)
+ doc_height = height_2;
+ }
+ compute_border (view, doc_width, doc_height, &border);
+
+ doc_width = doc_width * 2;
+ width -= ((border.left + border.right)* 2 + 3 * view->spacing);
+ height -= (border.top + border.bottom + 2 * view->spacing);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH)
+ scale = zoom_for_size_fit_width (doc_width, doc_height, width, height, vsb_width);
+ else if (view->sizing_mode == EV_SIZING_BEST_FIT)
+ scale = zoom_for_size_best_fit (doc_width, doc_height, width, height, vsb_width, hsb_height);
+ else
+ g_assert_not_reached ();
+
+ ev_view_set_zoom (view, scale, FALSE);
+}
+
+static void
+ev_view_zoom_for_size_single_page (EvView *view,
+ int width,
+ int height,
+ int vsb_width,
+ int hsb_height)
+{
+ int doc_width, doc_height;
+ GtkBorder border;
+ gdouble scale;
+
+ ev_page_cache_get_size (view->page_cache,
+ view->current_page,
+ view->rotation,
+ 1.0,
+ &doc_width,
+ &doc_height);
+ /* Get an approximate border */
+ compute_border (view, width, height, &border);
+
+ width -= (border.left + border.right + 2 * view->spacing);
+ height -= (border.top + border.bottom + 2 * view->spacing);
+
+ if (view->sizing_mode == EV_SIZING_FIT_WIDTH)
+ scale = zoom_for_size_fit_width (doc_width, doc_height, width, height, vsb_width);
+ else if (view->sizing_mode == EV_SIZING_BEST_FIT)
+ scale = zoom_for_size_best_fit (doc_width, doc_height, width, height, vsb_width, hsb_height);
+ else
+ g_assert_not_reached ();
+
+ ev_view_set_zoom (view, scale, FALSE);
+}
+
+void
+ev_view_set_zoom_for_size (EvView *view,
+ int width,
+ int height,
+ int vsb_width,
+ int hsb_height)
+{
+ g_return_if_fail (view->sizing_mode == EV_SIZING_FIT_WIDTH ||
+ view->sizing_mode == EV_SIZING_BEST_FIT);
+ g_return_if_fail (width >= 0);
+ g_return_if_fail (height >= 0);
+
+ if (view->document == NULL)
+ return;
+
+ if (view->presentation)
+ ev_view_zoom_for_size_presentation (view, width, height);
+ else if (view->continuous && view->dual_page)
+ ev_view_zoom_for_size_continuous_and_dual_page (view, width, height, vsb_width, hsb_height);
+ else if (view->continuous)
+ ev_view_zoom_for_size_continuous (view, width, height, vsb_width, hsb_height);
+ else if (view->dual_page)
+ ev_view_zoom_for_size_dual_page (view, width, height, vsb_width, hsb_height);
+ else
+ ev_view_zoom_for_size_single_page (view, width, height, vsb_width, hsb_height);
+}
+
+/*** Status text messages ***/
+
+const char *
+ev_view_get_status (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), NULL);
+
+ return view->status;
+}
+
+static void
+ev_view_set_status (EvView *view, const char *message)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ if (message != view->status) {
+ g_free (view->status);
+ view->status = g_strdup (message);
+ g_object_notify (G_OBJECT (view), "status");
+ }
+}
+
+static void
+update_find_status_message (EvView *view)
+{
+ char *message;
+
+ if (view->current_page == view->find_page) {
+ int results;
+
+ results = ev_document_find_get_n_results
+ (EV_DOCUMENT_FIND (view->document),
+ view->current_page);
+ /* TRANS: Sometimes this could be better translated as
+ "%d hit(s) on this page". Therefore this string
+ contains plural cases. */
+ message = g_strdup_printf (ngettext ("%d found on this page",
+ "%d found on this page",
+ results),
+ results);
+ } else {
+ double percent;
+
+ 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));
+ }