+
+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));
+ }
+
+ }
+ ev_view_set_find_status (view, message);
+ g_free (message);
+}
+
+const char *
+ev_view_get_find_status (EvView *view)
+{
+ g_return_val_if_fail (EV_IS_VIEW (view), NULL);
+
+ return view->find_status;
+}
+
+static void
+ev_view_set_find_status (EvView *view, const char *message)
+{
+ g_return_if_fail (EV_IS_VIEW (view));
+
+ g_free (view->find_status);
+ view->find_status = g_strdup (message);
+ g_object_notify (G_OBJECT (view), "find-status");
+}
+
+/*** Find ***/
+
+static void
+jump_to_find_result (EvView *view)
+{
+ EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
+ EvRectangle rect;
+ GdkRectangle view_rect;
+ int n_results;
+ int page = view->find_page;
+
+ n_results = ev_document_find_get_n_results (find, page);
+
+ if (n_results > view->find_result) {
+ ev_document_find_get_result
+ (find, page, view->find_result, &rect);
+
+ doc_rect_to_view_rect (view, page, &rect, &view_rect);
+ ensure_rectangle_is_visible (view, &view_rect);
+ }
+}
+
+static void
+jump_to_find_page (EvView *view)
+{
+ int n_pages, i;
+
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+
+ for (i = 0; i < n_pages; i++) {
+ int has_results;
+ int page;
+
+ page = i + view->find_page;
+ if (page >= n_pages) {
+ 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);
+
+ n_results = ev_document_find_get_n_results (find, view->current_page);
+ }
+
+ return n_results > 0;
+}
+
+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);
+
+ 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);
+ } else {
+ jump_to_find_result (view);
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+ }
+}
+
+void
+ev_view_find_previous (EvView *view)
+{
+ int n_results, n_pages;
+ EvDocumentFind *find = EV_DOCUMENT_FIND (view->document);
+ EvPageCache *page_cache;
+
+ 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);
+
+ 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);
+ } else {
+ jump_to_find_result (view);
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+ }
+}
+
+/*** Selections ***/
+
+/* compute_new_selection_rect/text calculates the area currently selected by
+ * view_rect. each handles a different mode;
+ */
+static GList *
+compute_new_selection_rect (EvView *view,
+ GdkPoint *start,
+ GdkPoint *stop)
+{
+ GdkRectangle view_rect;
+ int n_pages, i;
+ GList *list = NULL;
+
+ g_assert (view->selection_mode == EV_VIEW_SELECTION_RECTANGLE);
+
+ view_rect.x = MIN (start->x, stop->x);
+ view_rect.y = MIN (start->y, stop->y);
+ view_rect.width = MAX (start->x, stop->x) - view_rect.x;
+ view_rect.width = MAX (start->y, stop->y) - view_rect.y;
+
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+
+ for (i = 0; i < n_pages; i++) {
+ GdkRectangle page_area;
+ GtkBorder border;
+
+ if (get_page_extents (view, i, &page_area, &border)) {
+ GdkRectangle overlap;
+
+ if (gdk_rectangle_intersect (&page_area, &view_rect, &overlap)) {
+ EvViewSelection *selection;
+
+ selection = g_new0 (EvViewSelection, 1);
+ selection->page = i;
+ view_rect_to_doc_rect (view, &overlap, &page_area,
+ &(selection->rect));
+
+ list = g_list_append (list, selection);
+ }
+ }
+ }
+
+ return list;
+}
+
+static gboolean
+gdk_rectangle_point_in (GdkRectangle *rectangle,
+ GdkPoint *point)
+{
+ return rectangle->x <= point->x &&
+ rectangle->y <= point->y &&
+ point->x < rectangle->x + rectangle->width &&
+ point->y < rectangle->y + rectangle->height;
+}
+
+static GList *
+compute_new_selection_text (EvView *view,
+ GdkPoint *start,
+ GdkPoint *stop)
+{
+ int n_pages, i, first, last;
+ GList *list = NULL;
+ EvViewSelection *selection;
+ gint width, height;
+
+ g_assert (view->selection_mode == EV_VIEW_SELECTION_TEXT);
+
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+
+ /* First figure out the range of pages the selection
+ * affects. */
+ first = n_pages;
+ last = 0;
+ for (i = 0; i < n_pages; i++) {
+ GdkRectangle page_area;
+ GtkBorder border;
+
+ get_page_extents (view, i, &page_area, &border);
+ if (gdk_rectangle_point_in (&page_area, start) ||
+ gdk_rectangle_point_in (&page_area, stop)) {
+ if (first == n_pages)
+ first = i;
+ last = i;
+ }
+
+ }
+
+
+
+ /* Now create a list of EvViewSelection's for the affected
+ * pages. This could be an empty list, a list of just one
+ * page or a number of pages.*/
+ for (i = first; i < last + 1; i++) {
+ GdkRectangle page_area;
+ GtkBorder border;
+ GdkPoint *point;
+
+ ev_page_cache_get_size (view->page_cache, i,
+ view->rotation,
+ 1.0, &width, &height);
+
+ selection = g_new0 (EvViewSelection, 1);
+ selection->page = i;
+ selection->rect.x1 = selection->rect.y1 = 0;
+ selection->rect.x2 = width;
+ selection->rect.y2 = height;
+
+ get_page_extents (view, i, &page_area, &border);
+
+ if (gdk_rectangle_point_in (&page_area, start))
+ point = start;
+ else
+ point = stop;
+
+ if (i == first)
+ view_point_to_doc_point (view, point, &page_area,
+ &selection->rect.x1,
+ &selection->rect.y1);
+
+ /* If the selection is contained within just one page,
+ * make sure we don't write 'start' into both points
+ * in selection->rect. */
+ if (first == last)
+ point = stop;
+
+ if (i == last)
+ view_point_to_doc_point (view, point, &page_area,
+ &selection->rect.x2,
+ &selection->rect.y2);
+
+ list = g_list_append (list, selection);
+ }
+
+ return list;
+}
+
+/* This function takes the newly calculated list, and figures out which regions
+ * have changed. It then queues a redraw approporiately.
+ */
+static void
+merge_selection_region (EvView *view,
+ GList *list)
+{
+
+ /* FIXME: actually write... */
+ clear_selection (view);
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+
+ view->selection_info.selections = list;
+ ev_pixbuf_cache_set_selection_list (view->pixbuf_cache, list);
+}
+
+static void
+compute_selections (EvView *view,
+ GdkPoint *start,
+ GdkPoint *stop)
+{
+ GList *list;
+
+ if (view->selection_mode == EV_VIEW_SELECTION_RECTANGLE)
+ list = compute_new_selection_rect (view, start, stop);
+ else
+ list = compute_new_selection_text (view, start, stop);
+ merge_selection_region (view, list);
+}
+
+/* Free's the selection. It's up to the caller to queue redraws if needed.
+ */
+static void
+selection_free (EvViewSelection *selection)
+{
+ g_free (selection);
+}
+
+static void
+clear_selection (EvView *view)
+{
+ g_list_foreach (view->selection_info.selections, (GFunc)selection_free, NULL);
+ view->selection_info.selections = NULL;
+ view->selection_info.in_selection = FALSE;
+}
+
+
+void
+ev_view_select_all (EvView *view)
+{
+ int n_pages, i;
+
+ clear_selection (view);
+
+ n_pages = ev_page_cache_get_n_pages (view->page_cache);
+ for (i = 0; i < n_pages; i++) {
+ int width, height;
+ EvViewSelection *selection;
+
+ ev_page_cache_get_size (view->page_cache,
+ view->rotation,
+ i, 1.0, &width, &height);
+
+ selection = g_new0 (EvViewSelection, 1);
+ selection->page = i;
+ selection->rect.x1 = selection->rect.y1 = 0;
+ selection->rect.x2 = width;
+ selection->rect.y2 = height;
+
+ view->selection_info.selections = g_list_append (view->selection_info.selections, selection);
+ }
+
+ ev_pixbuf_cache_set_selection_list (view->pixbuf_cache, view->selection_info.selections);
+ gtk_widget_queue_draw (GTK_WIDGET (view));
+}
+
+static char *
+get_selected_text (EvView *ev_view)
+{
+ GString *text;
+ GList *l;
+
+ text = g_string_new (NULL);
+
+ ev_document_doc_mutex_lock ();
+
+ for (l = ev_view->selection_info.selections; l != NULL; l = l->next) {
+ EvViewSelection *selection = (EvViewSelection *)l->data;
+ char *tmp;
+
+ tmp = ev_document_get_text (ev_view->document,
+ selection->page,
+ &selection->rect);
+ g_string_append (text, tmp);
+ g_free (tmp);
+ }
+
+ ev_document_doc_mutex_unlock ();
+
+ return g_string_free (text, FALSE);
+}
+
+void
+ev_view_copy (EvView *ev_view)
+{
+ GtkClipboard *clipboard;
+ char *text;
+
+ if (!ev_document_can_get_text (ev_view->document)) {
+ return;
+ }
+
+ text = get_selected_text (ev_view);
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (ev_view),
+ GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text (clipboard, text, -1);
+ g_free (text);
+}
+
+static void
+ev_view_primary_get_cb (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer data)
+{
+ EvView *ev_view = EV_VIEW (data);
+ char *text;
+
+ if (!ev_document_can_get_text (ev_view->document)) {
+ return;
+ }
+
+ text = get_selected_text (ev_view);
+ gtk_selection_data_set_text (selection_data, text, -1);
+ g_free (text);
+}
+
+static void
+ev_view_primary_clear_cb (GtkClipboard *clipboard,
+ gpointer data)
+{
+ EvView *view = EV_VIEW (data);
+
+ clear_selection (view);
+}
+
+static void
+ev_view_update_primary_selection (EvView *ev_view)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (ev_view),
+ GDK_SELECTION_PRIMARY);
+
+ if (ev_view->selection_info.selections) {
+ if (!gtk_clipboard_set_with_owner (clipboard,
+ targets,
+ G_N_ELEMENTS (targets),
+ ev_view_primary_get_cb,
+ ev_view_primary_clear_cb,
+ G_OBJECT (ev_view)))
+ ev_view_primary_clear_cb (clipboard, ev_view);
+ } else {
+ if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (ev_view))
+ gtk_clipboard_clear (clipboard);
+ }
+}
+
+/*** Cursor operations ***/
+
+static GdkCursor *
+ev_view_create_invisible_cursor(void)
+{
+ GdkBitmap *empty;
+ GdkColor black = { 0, 0, 0, 0 };
+ static char bits[] = { 0x00 };
+
+ empty = gdk_bitmap_create_from_data (NULL, bits, 1, 1);
+
+ return gdk_cursor_new_from_pixmap (empty, empty, &black, &black, 0, 0);
+}
+
+static void
+ev_view_set_cursor (EvView *view, EvViewCursor new_cursor)
+{
+ GdkCursor *cursor = NULL;
+ GdkDisplay *display;
+ GtkWidget *widget;
+
+ if (view->cursor == new_cursor) {
+ return;
+ }
+
+ widget = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ display = gtk_widget_get_display (widget);
+ view->cursor = new_cursor;
+
+ switch (new_cursor) {
+ case EV_VIEW_CURSOR_NORMAL:
+ gdk_window_set_cursor (widget->window, NULL);
+ break;
+ case EV_VIEW_CURSOR_IBEAM:
+ cursor = gdk_cursor_new_for_display (display, GDK_XTERM);
+ break;
+ case EV_VIEW_CURSOR_LINK:
+ cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
+ break;
+ case EV_VIEW_CURSOR_WAIT:
+ cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+ break;
+ case EV_VIEW_CURSOR_HIDDEN:
+ cursor = ev_view_create_invisible_cursor ();
+ break;
+ case EV_VIEW_CURSOR_DRAG:
+ cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
+ break;
+ }
+
+ if (cursor) {
+ gdk_window_set_cursor (widget->window, cursor);
+ gdk_cursor_unref (cursor);
+ gdk_flush();
+ }
+}
+
+void
+ev_view_hide_cursor (EvView *view)
+{
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_HIDDEN);
+}
+
+void
+ev_view_show_cursor (EvView *view)
+{
+ ev_view_set_cursor (view, EV_VIEW_CURSOR_NORMAL);
+}
+
+/*** Enum description for usage in signal ***/
+
+GType
+ev_sizing_mode_get_type (void)
+{
+ static GType etype = 0;
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ { EV_SIZING_FIT_WIDTH, "EV_SIZING_FIT_WIDTH", "fit-width" },
+ { EV_SIZING_BEST_FIT, "EV_SIZING_BEST_FIT", "best-fit" },
+ { EV_SIZING_FREE, "EV_SIZING_FREE", "free" },
+ { 0, NULL, NULL }
+ };
+ etype = g_enum_register_static ("EvSizingMode", values);
+ }
+ return etype;
+}
+