#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,
};
enum {
EV_VIEW_CURSOR_DRAG
} EvViewCursor;
+typedef enum {
+ EV_VIEW_FIND_NEXT,
+ EV_VIEW_FIND_PREV
+} EvViewFindDirection;
+
#define ZOOM_IN_FACTOR 1.2
#define ZOOM_OUT_FACTOR (1.0/ZOOM_IN_FACTOR)
int pressed_button;
EvViewCursor cursor;
+ GtkWidget *link_tooltip;
+ EvLink *hovered_link;
EvPageCache *page_cache;
EvPixbufCache *pixbuf_cache;
gint *page,
gint *x_offset,
gint *y_offset);
-static void ev_view_queue_draw_page (EvView *view,
- gint page);
/*** Hyperrefs ***/
static EvLink* get_link_at_location (EvView *view,
gdouble x,
gdouble y);
-static void go_to_link (EvView *view,
+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);
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,
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);
/*** Selection ***/
static void compute_selections (EvView *view,
static void
view_update_range_and_current_page (EvView *view)
{
+ if (view->pending_scroll != SCROLL_TO_KEEP_POSITION)
+ return;
+
/* Presentation trumps all other modes */
if (view->presentation) {
view->start_page = view->current_page;
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;
view->end_page = view->current_page;
if (view->presentation) {
switch (scroll) {
case GTK_SCROLL_STEP_BACKWARD:
- ev_page_cache_prev_page (view->page_cache);
+ ev_view_previous_page (view);
break;
case GTK_SCROLL_STEP_FORWARD:
- ev_page_cache_next_page (view->page_cache);
+ ev_view_next_page (view);
break;
default:
break;
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) {
doc_rect->y2 = doc_rect->y1 + (double) view_rect->height / view->scale;
}
+static void
+doc_point_to_view_point (EvView *view,
+ int page,
+ EvPoint *doc_point,
+ GdkPoint *view_point)
+{
+ GdkRectangle page_area;
+ GtkBorder border;
+ double x, 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_point->x = x * view->scale + page_area.x;
+ view_point->y = y * view->scale + page_area.y;
+}
+
static void
doc_rect_to_view_rect (EvView *view,
int page,
*page = -1;
}
-static void
-ev_view_queue_draw_page (EvView *view,
- gint page)
-{
- /* FIXME: write */
- gtk_widget_queue_draw (GTK_WIDGET (view));
-}
-
static gboolean
location_in_text (EvView *view,
gdouble x,
return NULL;
}
-/* FIXME: standardize this sometime */
static void
-go_to_link (EvView *view, EvLink *link)
+goto_xyz_link (EvView *view, EvLink *link)
+{
+ GdkPoint view_point;
+ 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);
+
+ ev_view_set_sizing_mode (view, EV_SIZING_FREE);
+ if (zoom != 0) {
+ ev_view_set_zoom (view, zoom, FALSE);
+ }
+
+ doc_point.x = ev_link_get_left (link);
+ doc_point.y = height - ev_link_get_top (link);
+ doc_point_to_view_point (view, page, &doc_point, &view_point);
+
+ gtk_adjustment_set_value (view->hadjustment, view_point.x);
+ gtk_adjustment_set_value (view->vadjustment, view_point.y);
+}
+
+void
+ev_view_goto_link (EvView *view, EvLink *link)
{
EvLinkType type;
const char *uri;
page = ev_link_get_page (link);
ev_page_cache_set_current_page (view->page_cache, page);
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);
}
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 = get_link_at_location (view, x + view->scroll_x, y + view->scroll_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);
+
+ ev_tooltip_set_position (EV_TOOLTIP (view->link_tooltip), x, y);
+ ev_tooltip_set_text (EV_TOOLTIP (view->link_tooltip), msg);
+ ev_tooltip_activate (EV_TOOLTIP (view->link_tooltip));
+ 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 ***/
GDK_SCROLL_MASK |
GDK_KEY_PRESS_MASK |
GDK_POINTER_MOTION_MASK |
+ GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK;
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
if (!view->document)
return FALSE;
- if (view->pressed_button == 1) {
+ /* For the Evince 0.4.x release, we limit selection to un-rotated
+ * documents only.
+ */
+ if (view->pressed_button == 1 &&
+ view->rotation == 0) {
view->selection_info.in_selection = TRUE;
view->motion_x = event->x + view->scroll_x;
view->motion_y = event->y + view->scroll_y;
/* Queue an idle to handle the motion. We do this because
- * handling any selection events in the motion is probably going
- * to be slower than new motion events reach us. This means that */
-
+ * 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 <= 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, event->x, event->y);
return TRUE;
}
link = get_link_at_location (view, event->x + view->scroll_x, event->y + view->scroll_y);
if (link) {
- go_to_link (view, link);
+ ev_view_goto_link (view, link);
}
}
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;
}
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,
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);
+ }
+
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);
}
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);
}
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;
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);
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;
static void
find_changed_cb (EvDocument *document, int page, EvView *view)
{
- jump_to_find_page (view);
+ jump_to_find_page (view, EV_VIEW_FIND_NEXT);
jump_to_find_result (view);
update_find_status_message (view);
}
if (view->page_cache) {
- g_object_unref (view->page_cache);
view->page_cache = NULL;
}
}
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,
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);
}
static void
-jump_to_find_page (EvView *view)
+jump_to_find_page (EvView *view, EvViewFindDirection direction)
{
int n_pages, i;
for (i = 0; i < n_pages; i++) {
int has_results;
int page;
+
+ if (direction == EV_VIEW_FIND_NEXT)
+ page = view->find_page + i;
+ else
+ page = view->find_page - i;
+
- page = i + view->find_page;
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) {
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
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);
+ jump_to_find_page (view, EV_VIEW_FIND_NEXT);
+ 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--;
+ 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);
+ 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;
{
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);
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 {
+ 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 {
+ return FALSE;
+ }
+}
+
/*** Enum description for usage in signal ***/
GType
}
return etype;
}
-