page-widget.c 40.1 KB
Newer Older
1 2 3 4
/* See LICENSE file for license and copyright information */

#include <girara/utils.h>
#include <girara/settings.h>
5
#include <girara/datastructures.h>
6 7 8
#include <girara/session.h>
#include <string.h>
#include <glib/gi18n.h>
9
#include <math.h>
10

Moritz Lipp's avatar
Moritz Lipp committed
11
#include "links.h"
12
#include "page-widget.h"
Moritz Lipp's avatar
Moritz Lipp committed
13
#include "page.h"
14 15 16
#include "render.h"
#include "utils.h"
#include "shortcuts.h"
17
#include "zathura.h"
18

19
G_DEFINE_TYPE(ZathuraPage, zathura_page_widget, GTK_TYPE_DRAWING_AREA)
20

21
typedef struct zathura_page_widget_private_s {
22 23
  zathura_page_t* page; /**< Page object */
  zathura_t* zathura; /**< Zathura object */
24
  cairo_surface_t* surface; /**< Cairo surface */
25
  cairo_surface_t* thumbnail; /**< Cairo surface */
26
  ZathuraRenderRequest* render_request; /* Request object */
27
  bool cached; /**< Cached state */
28 29 30

  struct {
    girara_list_t* list; /**< List of links on the page */
31 32
    gboolean retrieved; /**< True if we already tried to retrieve the list of links */
    gboolean draw; /**< True if links should be drawn */
33 34 35 36 37 38 39
    unsigned int offset; /**< Offset to the links */
    unsigned int n; /**< Number */
  } links;

  struct {
    girara_list_t* list; /**< A list if there are search results that should be drawn */
    int current; /**< The index of the current search result */
40
    gboolean draw; /**< Draw search results */
41 42 43 44
  } search;

  struct {
    girara_list_t* list; /**< List of images on the page */
45
    gboolean retrieved; /**< True if we already tried to retrieve the list of images */
46 47 48
    zathura_image_t* current; /**< Image data of selected image */
  } images;

49
  struct {
50 51 52 53 54
    zathura_rectangle_t selection; /**< Region selected with the mouse */
    struct {
      int x; /**< X coordinate */
      int y; /**< Y coordinate */
    } selection_basepoint;
55
    gboolean over_link;
56
  } mouse;
57
} zathura_page_widget_private_t;
58

59
#define ZATHURA_PAGE_GET_PRIVATE(obj) \
60 61
  (G_TYPE_INSTANCE_GET_PRIVATE((obj), ZATHURA_TYPE_PAGE, \
                               zathura_page_widget_private_t))
62

63
static gboolean zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo);
64
static void zathura_page_widget_finalize(GObject* object);
65
static void zathura_page_widget_dispose(GObject* object);
66 67 68 69 70
static void zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec);
static void zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec);
static void zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation);
static void redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle);
static void redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles);
Moritz Lipp's avatar
Moritz Lipp committed
71
static void zathura_page_widget_popup_menu(GtkWidget* widget, GdkEventButton* event);
72
static gboolean cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button);
73 74
static gboolean cb_zathura_page_widget_button_release_event(GtkWidget* widget, GdkEventButton* button);
static gboolean cb_zathura_page_widget_motion_notify(GtkWidget* widget, GdkEventMotion* event);
75
static gboolean cb_zathura_page_widget_leave_notify(GtkWidget* widget, GdkEventCrossing* event);
Moritz Lipp's avatar
Moritz Lipp committed
76 77
static gboolean cb_zathura_page_widget_popup_menu(GtkWidget* widget);
static void cb_menu_image_copy(GtkMenuItem* item, ZathuraPage* page);
Moritz Lipp's avatar
Moritz Lipp committed
78
static void cb_menu_image_save(GtkMenuItem* item, ZathuraPage* page);
79
static void cb_update_surface(ZathuraRenderRequest* request, cairo_surface_t* surface, void* data);
80 81
static void cb_cache_added(ZathuraRenderRequest* request, void* data);
static void cb_cache_invalidated(ZathuraRenderRequest* request, void* data);
82 83
static bool surface_small_enough(cairo_surface_t* surface, size_t max_size, cairo_surface_t* old);
static cairo_surface_t *draw_thumbnail_image(cairo_surface_t* surface, size_t max_size);
84

Moritz Lipp's avatar
Moritz Lipp committed
85
enum properties_e {
86
  PROP_0,
Moritz Lipp's avatar
Moritz Lipp committed
87
  PROP_PAGE,
88
  PROP_ZATHURA,
Moritz Lipp's avatar
Moritz Lipp committed
89
  PROP_DRAW_LINKS,
Moritz Lipp's avatar
Moritz Lipp committed
90 91
  PROP_LINKS_OFFSET,
  PROP_LINKS_NUMBER,
92 93
  PROP_SEARCH_RESULTS,
  PROP_SEARCH_RESULTS_LENGTH,
Moritz Lipp's avatar
Moritz Lipp committed
94
  PROP_SEARCH_RESULTS_CURRENT,
95
  PROP_DRAW_SEARCH_RESULTS,
96
  PROP_LAST_VIEW,
97 98
};

99 100 101
enum {
  TEXT_SELECTED,
  IMAGE_SELECTED,
102
  BUTTON_RELEASE,
103 104
  ENTER_LINK,
  LEAVE_LINK,
105 106 107 108 109
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

110
static void
111
zathura_page_widget_class_init(ZathuraPageClass* class)
112 113
{
  /* add private members */
114
  g_type_class_add_private(class, sizeof(zathura_page_widget_private_t));
115 116

  /* overwrite methods */
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
117
  GtkWidgetClass* widget_class       = GTK_WIDGET_CLASS(class);
118
  widget_class->draw                 = zathura_page_widget_draw;
119 120
  widget_class->size_allocate        = zathura_page_widget_size_allocate;
  widget_class->button_press_event   = cb_zathura_page_widget_button_press_event;
121
  widget_class->button_release_event = cb_zathura_page_widget_button_release_event;
122
  widget_class->motion_notify_event  = cb_zathura_page_widget_motion_notify;
123
  widget_class->leave_notify_event   = cb_zathura_page_widget_leave_notify;
Moritz Lipp's avatar
Moritz Lipp committed
124
  widget_class->popup_menu           = cb_zathura_page_widget_popup_menu;
125 126

  GObjectClass* object_class = G_OBJECT_CLASS(class);
127
  object_class->dispose      = zathura_page_widget_dispose;
128
  object_class->finalize     = zathura_page_widget_finalize;
129 130
  object_class->set_property = zathura_page_widget_set_property;
  object_class->get_property = zathura_page_widget_get_property;
131 132 133

  /* add properties */
  g_object_class_install_property(object_class, PROP_PAGE,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
134
                                  g_param_spec_pointer("page", "page", "the page to draw", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
135
  g_object_class_install_property(object_class, PROP_ZATHURA,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
136
                                  g_param_spec_pointer("zathura", "zathura", "the zathura instance", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
Moritz Lipp's avatar
Moritz Lipp committed
137
  g_object_class_install_property(object_class, PROP_DRAW_LINKS,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
138
                                  g_param_spec_boolean("draw-links", "draw-links", "Set to true if links should be drawn", FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
Moritz Lipp's avatar
Moritz Lipp committed
139
  g_object_class_install_property(object_class, PROP_LINKS_OFFSET,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
140
                                  g_param_spec_int("offset-links", "offset-links", "Offset for the link numbers", 0, INT_MAX, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
Moritz Lipp's avatar
Moritz Lipp committed
141
  g_object_class_install_property(object_class, PROP_LINKS_NUMBER,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
142
                                  g_param_spec_int("number-of-links", "number-of-links", "Number of links", 0, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
143
  g_object_class_install_property(object_class, PROP_SEARCH_RESULTS,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
144
                                  g_param_spec_pointer("search-results", "search-results", "Set to the list of search results", G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
145
  g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_CURRENT,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
146
                                  g_param_spec_int("search-current", "search-current", "The current search result", -1, INT_MAX, 0, G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
147
  g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_LENGTH,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
148
                                  g_param_spec_int("search-length", "search-length", "The number of search results", -1, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
149 150
  g_object_class_install_property(object_class, PROP_DRAW_SEARCH_RESULTS,
                                  g_param_spec_boolean("draw-search-results", "draw-search-results", "Set to true if search results should be drawn", FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

  /* add signals */
  signals[TEXT_SELECTED] = g_signal_new("text-selected",
      ZATHURA_TYPE_PAGE,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      1,
      G_TYPE_STRING);

  signals[IMAGE_SELECTED] = g_signal_new("image-selected",
      ZATHURA_TYPE_PAGE,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      1,
      G_TYPE_OBJECT);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193

  signals[ENTER_LINK] = g_signal_new("enter-link",
      ZATHURA_TYPE_PAGE,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      0);

  signals[LEAVE_LINK] = g_signal_new("leave-link",
      ZATHURA_TYPE_PAGE,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      0);
194 195 196 197 198 199 200 201 202 203 204

  signals[BUTTON_RELEASE] = g_signal_new("scaled-button-release",
      ZATHURA_TYPE_PAGE,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      1,
      G_TYPE_POINTER);
205 206 207
}

static void
208
zathura_page_widget_init(ZathuraPage* widget)
209
{
210
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
211 212 213 214 215
  priv->page                          = NULL;
  priv->surface                       = NULL;
  priv->thumbnail                     = NULL;
  priv->render_request                = NULL;
  priv->cached                        = false;
216 217 218 219 220 221 222 223 224

  priv->links.list      = NULL;
  priv->links.retrieved = false;
  priv->links.draw      = false;
  priv->links.offset    = 0;
  priv->links.n         = 0;

  priv->search.list    = NULL;
  priv->search.current = INT_MAX;
225
  priv->search.draw    = false;
226 227 228 229 230 231 232 233 234 235

  priv->images.list      = NULL;
  priv->images.retrieved = false;
  priv->images.current   = NULL;

  priv->mouse.selection.x1          = -1;
  priv->mouse.selection.y1          = -1;
  priv->mouse.selection_basepoint.x = -1;
  priv->mouse.selection_basepoint.y = -1;

236 237 238
  const unsigned int event_mask = GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK;
  gtk_widget_add_events(GTK_WIDGET(widget), event_mask);
239 240 241
}

GtkWidget*
242
zathura_page_widget_new(zathura_t* zathura, zathura_page_t* page)
243 244 245
{
  g_return_val_if_fail(page != NULL, NULL);

246 247 248 249 250 251 252 253 254
  GObject* ret = g_object_new(ZATHURA_TYPE_PAGE, "page", page, "zathura", zathura, NULL);
  if (ret == NULL) {
    return NULL;
  }

  ZathuraPage* widget = ZATHURA_PAGE(ret);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  priv->render_request = zathura_render_request_new(zathura->sync.render_thread, page);
  g_signal_connect_object(priv->render_request, "completed",
255
      G_CALLBACK(cb_update_surface), widget, 0);
256 257 258 259
  g_signal_connect_object(priv->render_request, "cache-added",
      G_CALLBACK(cb_cache_added), widget, 0);
  g_signal_connect_object(priv->render_request, "cache-invalidated",
      G_CALLBACK(cb_cache_invalidated), widget, 0);
260 261

  return GTK_WIDGET(ret);
262 263
}

264 265 266 267 268 269 270 271 272 273 274
static void
zathura_page_widget_dispose(GObject* object)
{
  ZathuraPage* widget = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);

  g_clear_object(&priv->render_request);

  G_OBJECT_CLASS(zathura_page_widget_parent_class)->dispose(object);
}

275
static void
276
zathura_page_widget_finalize(GObject* object)
277
{
278 279
  ZathuraPage* widget = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
280

Sebastian Ramacher's avatar
Sebastian Ramacher committed
281
  if (priv->surface != NULL) {
282 283
    cairo_surface_destroy(priv->surface);
  }
284

285 286 287 288
  if (priv->thumbnail != NULL) {
    cairo_surface_destroy(priv->thumbnail);
  }

289 290
  if (priv->search.list != NULL) {
    girara_list_free(priv->search.list);
291 292
  }

293 294
  if (priv->links.list != NULL) {
    girara_list_free(priv->links.list);
295 296
  }

297
  G_OBJECT_CLASS(zathura_page_widget_parent_class)->finalize(object);
298 299 300
}

static void
301
zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
302
{
303 304
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
305 306 307

  switch (prop_id) {
    case PROP_PAGE:
308 309 310 311
      priv->page = g_value_get_pointer(value);
      break;
    case PROP_ZATHURA:
      priv->zathura = g_value_get_pointer(value);
312
      break;
Moritz Lipp's avatar
Moritz Lipp committed
313
    case PROP_DRAW_LINKS:
314
      priv->links.draw = g_value_get_boolean(value);
Moritz Lipp's avatar
Moritz Lipp committed
315
      /* get links */
316
      if (priv->links.draw == TRUE && priv->links.retrieved == FALSE) {
317
        priv->links.list      = zathura_page_links_get(priv->page, NULL);
318
        priv->links.retrieved = TRUE;
319
        priv->links.n         = (priv->links.list == NULL) ? 0 : girara_list_size(priv->links.list);
Moritz Lipp's avatar
Moritz Lipp committed
320 321
      }

322
      if (priv->links.retrieved == TRUE && priv->links.list != NULL) {
323
        GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
324 325 326 327
        if (link != NULL) {
          zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
          redraw_rect(pageview, &rectangle);
        }
328
        GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
329 330
      }
      break;
Moritz Lipp's avatar
Moritz Lipp committed
331
    case PROP_LINKS_OFFSET:
332
      priv->links.offset = g_value_get_int(value);
Moritz Lipp's avatar
Moritz Lipp committed
333
      break;
334
    case PROP_SEARCH_RESULTS:
335 336 337
      if (priv->search.list != NULL && priv->search.draw) {
        redraw_all_rects(pageview, priv->search.list);
        girara_list_free(priv->search.list);
338
      }
339 340
      priv->search.list = g_value_get_pointer(value);
      if (priv->search.list != NULL && priv->search.draw) {
341
        priv->links.draw = FALSE;
342
        redraw_all_rects(pageview, priv->search.list);
343
      }
344
      priv->search.current = -1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
345
      break;
346
    case PROP_SEARCH_RESULTS_CURRENT: {
347 348 349
      g_return_if_fail(priv->search.list != NULL);
      if (priv->search.current >= 0 && priv->search.current < (signed) girara_list_size(priv->search.list)) {
        zathura_rectangle_t* rect = girara_list_nth(priv->search.list, priv->search.current);
350 351 352 353
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
        redraw_rect(pageview, &rectangle);
      }
      int val = g_value_get_int(value);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
354
      if (val < 0) {
355
        priv->search.current = girara_list_size(priv->search.list);
356
      } else {
357
        priv->search.current = val;
358
        if (priv->search.draw == TRUE && val >= 0 && val < (signed) girara_list_size(priv->search.list)) {
359 360
          zathura_rectangle_t* rect = girara_list_nth(priv->search.list, priv->search.current);
          zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
Moritz Lipp's avatar
Moritz Lipp committed
361 362
          redraw_rect(pageview, &rectangle);
        }
363 364 365
      }
      break;
    }
366
    case PROP_DRAW_SEARCH_RESULTS:
367
      priv->search.draw = g_value_get_boolean(value);
368 369 370 371 372 373 374 375 376

      /*
       * we do the following instead of only redrawing the rectangles of the
       * search results in order to avoid the rectangular margins that appear
       * around the search terms after their highlighted rectangular areas are
       * redrawn without highlighting.
       */

      if (priv->search.list != NULL && zathura_page_get_visibility(priv->page)) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
377
        gtk_widget_queue_draw(GTK_WIDGET(object));
378
      }
Moritz Lipp's avatar
Moritz Lipp committed
379
      break;
Moritz Lipp's avatar
Moritz Lipp committed
380 381 382 383 384 385
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

static void
386
zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
Moritz Lipp's avatar
Moritz Lipp committed
387
{
388 389
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
Moritz Lipp's avatar
Moritz Lipp committed
390 391 392

  switch (prop_id) {
    case PROP_LINKS_NUMBER:
393
      g_value_set_int(value, priv->links.n);
394
      break;
395
    case PROP_SEARCH_RESULTS_LENGTH:
396
      g_value_set_int(value, priv->search.list == NULL ? 0 : girara_list_size(priv->search.list));
397 398
      break;
    case PROP_SEARCH_RESULTS_CURRENT:
399
      g_value_set_int(value, priv->search.list == NULL ? -1 : priv->search.current);
400
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
401
    case PROP_SEARCH_RESULTS:
402
      g_value_set_pointer(value, priv->search.list);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
403
      break;
404 405 406
    case PROP_DRAW_SEARCH_RESULTS:
      g_value_set_boolean(value, priv->search.draw);
      break;
407 408 409 410 411
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,14,0)
static zathura_device_factors_t
get_safe_device_factors(cairo_surface_t* surface)
{
    zathura_device_factors_t factors;
    cairo_surface_get_device_scale(surface, &factors.x, &factors.y);

    if (fabs(factors.x) < DBL_EPSILON) {
        factors.x = 1.0;
    }
    if (fabs(factors.y) < DBL_EPSILON) {
        factors.y = 1.0;
    }

    return factors;
}
#else
static zathura_device_factors_t
get_safe_device_factors(cairo_surface_t* UNUSED(surface))
{
  return (zathura_device_factors_t){1.0, 1.0};
}
#endif

436 437 438
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
439
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
440

Sebastian Ramacher's avatar
Sebastian Ramacher committed
441
  zathura_document_t* document   = zathura_page_get_document(priv->page);
442 443 444
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

445
  if (priv->surface != NULL || priv->thumbnail != NULL) {
446 447
    cairo_save(cairo);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
448
    const unsigned int rotation = zathura_document_get_rotation(document);
449
    switch (rotation) {
450 451 452 453 454 455 456 457 458 459 460
      case 90:
        cairo_translate(cairo, page_width, 0);
        break;
      case 180:
        cairo_translate(cairo, page_width, page_height);
        break;
      case 270:
        cairo_translate(cairo, 0, page_height);
        break;
    }

461 462
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
463 464
    }

465 466 467 468 469 470 471
    if (priv->surface != NULL) {
      cairo_set_source_surface(cairo, priv->surface, 0, 0);
      cairo_paint(cairo);
      cairo_restore(cairo);
    } else {
      const unsigned int height = cairo_image_surface_get_height(priv->thumbnail);
      const unsigned int width = cairo_image_surface_get_width(priv->thumbnail);
472 473 474
      unsigned int pheight = (rotation % 180 ? page_width : page_height);
      unsigned int pwidth = (rotation % 180 ? page_height : page_width);

475 476 477 478
      /* note: this always returns 1 and 1 if Cairo too old for device scale API */
      zathura_device_factors_t device = get_safe_device_factors(priv->thumbnail);
      pwidth *= device.x;
      pheight *= device.y;
479 480 481 482 483 484 485 486 487 488 489

      cairo_scale(cairo, pwidth / (double)width, pheight / (double)height);
      cairo_set_source_surface(cairo, priv->thumbnail, 0, 0);
      cairo_pattern_set_extend(cairo_get_source(cairo), CAIRO_EXTEND_PAD);
      if (pwidth < width || pheight < height) {
        /* pixman bilinear downscaling is slow */
        cairo_pattern_set_filter(cairo_get_source(cairo), CAIRO_FILTER_FAST);
      }
      cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
      cairo_paint(cairo);
      cairo_restore(cairo);
490 491 492 493
      /* All but the last jobs requested here are aborted during zooming.
       * Processing and aborting smaller jobs first improves responsiveness. */
      const gint64 penalty = pwidth * pheight;
      zathura_render_request(priv->render_request, g_get_real_time() + penalty);
494 495
      return FALSE;
    }
496 497 498 499 500 501 502 503 504 505 506

    /* draw rectangles */
    char* font = NULL;
    girara_setting_get(priv->zathura->ui.session, "font", &font);

    float transparency = 0.5;
    girara_setting_get(priv->zathura->ui.session, "highlight-transparency", &transparency);

    if (font != NULL) {
      cairo_select_font_face(cairo, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    }
507

508
    g_free(font);
509

Moritz Lipp's avatar
Moritz Lipp committed
510
    /* draw links */
511
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
512
      unsigned int link_counter = 0;
513
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
514 515
      if (link != NULL) {
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
Moritz Lipp's avatar
Moritz Lipp committed
516

Moritz Lipp's avatar
Moritz Lipp committed
517
        /* draw position */
518 519
        const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
        cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
Moritz Lipp's avatar
Moritz Lipp committed
520 521 522 523 524 525 526 527 528 529 530 531
        cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                        (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
        cairo_fill(cairo);

        /* draw text */
        cairo_set_source_rgba(cairo, 0, 0, 0, 1);
        cairo_set_font_size(cairo, 10);
        cairo_move_to(cairo, rectangle.x1 + 1, rectangle.y2 - 1);
        char* link_number = g_strdup_printf("%i", priv->links.offset + ++link_counter);
        cairo_show_text(cairo, link_number);
        g_free(link_number);
      }
532
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
533
    }
534 535

    /* draw search results */
536
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
537
      int idx = 0;
538
      GIRARA_LIST_FOREACH(priv->search.list, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
539
      zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
540

Moritz Lipp's avatar
Moritz Lipp committed
541 542
      /* draw position */
      if (idx == priv->search.current) {
543 544
        const GdkRGBA color = priv->zathura->ui.colors.highlight_color_active;
        cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
Moritz Lipp's avatar
Moritz Lipp committed
545
      } else {
546 547
        const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
        cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
Moritz Lipp's avatar
Moritz Lipp committed
548 549 550 551 552
      }
      cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                      (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
      cairo_fill(cairo);
      ++idx;
553
      GIRARA_LIST_FOREACH_END(priv->search.list, zathura_rectangle_t*, iter, rect);
554
    }
555
    /* draw selection */
556
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
557 558
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
559
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
560
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
561
      cairo_fill(cairo);
562
    }
563 564
  } else {
    /* set background color */
565
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
566
      GdkRGBA color;
567
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
568
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
569
    } else {
570 571
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
572
    }
573
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
574 575 576 577 578 579 580
    cairo_fill(cairo);

    bool render_loading = true;
    girara_setting_get(priv->zathura->ui.session, "render-loading", &render_loading);

    /* write text */
    if (render_loading == true) {
581
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
582
        GdkRGBA color;
583
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
584
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
585
      } else {
586
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
587
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
588 589
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
590
      const char* text = _("Loading...");
591 592 593 594
      cairo_select_font_face(cairo, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
      cairo_set_font_size(cairo, 16.0);
      cairo_text_extents_t extents;
      cairo_text_extents(cairo, text, &extents);
595 596
      double x = page_width * 0.5 - (extents.width * 0.5 + extents.x_bearing);
      double y = page_height * 0.5 - (extents.height * 0.5 + extents.y_bearing);
597 598 599 600 601
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
602
    zathura_render_request(priv->render_request, g_get_real_time());
603 604 605 606 607
  }
  return FALSE;
}

static void
608
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
609 610 611 612 613
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

614
/* smaller than max to be replaced by actual renders */
615
#define THUMBNAIL_INITIAL_SCALE 0.5
616 617 618 619
/* small enough to make bilinear downscaling fast */
#define THUMBNAIL_MAX_SCALE 0.5

static bool
620
surface_small_enough(cairo_surface_t* surface, size_t max_size, cairo_surface_t* old)
621
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
622
  if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE) {
623
    return true;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
624
  }
625 626 627

  const unsigned int width = cairo_image_surface_get_width(surface);
  const unsigned int height = cairo_image_surface_get_height(surface);
628
  const size_t new_size = width * height;
629
  if (new_size > max_size) {
630
    return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
631
  }
632 633 634 635

  if (old != NULL) {
    const unsigned int width_old = cairo_image_surface_get_width(old);
    const unsigned int height_old = cairo_image_surface_get_height(old);
636 637
    const size_t old_size = width_old * height_old;
    if (new_size < old_size && new_size >= old_size * THUMBNAIL_MAX_SCALE * THUMBNAIL_MAX_SCALE) {
638
      return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
639
    }
640 641 642 643 644 645
  }

  return true;
}

static cairo_surface_t *
646
draw_thumbnail_image(cairo_surface_t* surface, size_t max_size)
647 648 649
{
  unsigned int width = cairo_image_surface_get_width(surface);
  unsigned int height = cairo_image_surface_get_height(surface);
650
  double scale = sqrt((double)max_size / (width * height)) * THUMBNAIL_INITIAL_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
651
  if (scale > THUMBNAIL_MAX_SCALE) {
652
    scale = THUMBNAIL_MAX_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
653
  }
654 655 656 657 658
  width = width * scale;
  height = height * scale;

  cairo_surface_t *thumbnail;
  thumbnail = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR, width, height);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
659 660 661
  if (thumbnail == NULL) {
    return NULL;
  }
662
  cairo_t *cr = cairo_create(thumbnail);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
663 664 665 666
  if (cr == NULL) {
    cairo_surface_destroy(thumbnail);
    return NULL;
  }
667 668 669 670 671 672 673 674 675 676 677

  cairo_scale(cr, scale, scale);
  cairo_set_source_surface(cr, surface, 0, 0);
  cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
  cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
  cairo_paint(cr);
  cairo_destroy(cr);

  return thumbnail;
}

678
void
679
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface, bool keep_thumbnail)
680
{
681
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
682 683 684 685 686
  int thumbnail_size = 0;
  girara_setting_get(priv->zathura->ui.session, "page-thumbnail-size", &thumbnail_size);
  if (thumbnail_size <= 0) {
    thumbnail_size = ZATHURA_PAGE_THUMBNAIL_DEFAULT_SIZE;
  }
687 688
  bool new_render = (priv->surface == NULL && priv->thumbnail == NULL);

Moritz Lipp's avatar
CS  
Moritz Lipp committed
689
  if (priv->surface != NULL) {
690
    cairo_surface_destroy(priv->surface);
691
    priv->surface = NULL;
692
  }
693
  if (surface != NULL) {
694 695
    priv->surface = surface;
    cairo_surface_reference(surface);
696

697
    if (surface_small_enough(surface, thumbnail_size, priv->thumbnail)) {
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
698
      if (priv->thumbnail != NULL) {
699
        cairo_surface_destroy(priv->thumbnail);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
700
      }
701 702 703
      priv->thumbnail = surface;
      cairo_surface_reference(surface);
    } else if (new_render) {
704
      priv->thumbnail = draw_thumbnail_image(surface, thumbnail_size);
705 706 707 708
    }
  } else if (!keep_thumbnail && priv->thumbnail != NULL) {
    cairo_surface_destroy(priv->thumbnail);
    priv->thumbnail = NULL;
709
  }
710
  /* force a redraw here */
711 712 713
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
714 715
}

716
static void
717 718
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
719 720 721
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
722
  zathura_page_widget_update_surface(widget, surface, false);
723 724
}

725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
static void
cb_cache_added(ZathuraRenderRequest* UNUSED(request), void* data)
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  priv->cached = true;
}

static void
cb_cache_invalidated(ZathuraRenderRequest* UNUSED(request), void* data)
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  if (zathura_page_widget_have_surface(widget) == true &&
      priv->cached == true &&
      zathura_page_get_visibility(priv->page) == false) {
    /* The page was in the cache but got removed and is invisible, so get rid of
     * the surface. */
747
    zathura_page_widget_update_surface(widget, NULL, false);
748 749 750 751
  }
  priv->cached = false;
}

752
static void
753
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
754
{
755
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
756 757 758

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
759
  zathura_page_widget_update_surface(page, NULL, true);
760
}
761 762

static void
763
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
764
{
Moritz Lipp's avatar
Moritz Lipp committed
765
  /* cause the rect to be drawn */
766
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
767
  grect.x = rectangle->x1;
768
  grect.y = rectangle->y1;
769 770
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
771
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
772 773
}

774
static void
775
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
776
{
777
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
778 779

  GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect)
780 781
    zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
    redraw_rect(widget, &rectangle);
782 783 784
  GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect);
}

Moritz Lipp's avatar
Moritz Lipp committed
785
zathura_link_t*
786
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
787
{
Moritz Lipp's avatar
Moritz Lipp committed
788
  g_return_val_if_fail(widget != NULL, NULL);
789
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
790
  g_return_val_if_fail(priv != NULL, NULL);
791

792 793 794
  if (priv->links.list != NULL && index >= priv->links.offset &&
      girara_list_size(priv->links.list) > index - priv->links.offset) {
    return girara_list_nth(priv->links.list, index - priv->links.offset);
Moritz Lipp's avatar
Moritz Lipp committed
795 796 797
  } else {
    return NULL;
  }
798
}
Moritz Lipp's avatar
Moritz Lipp committed
799

800 801
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
802 803 804 805 806 807
{
  g_return_val_if_fail(widget != NULL, false);
  g_return_val_if_fail(button != NULL, false);

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);

808 809 810 811
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

812
  if (button->button == GDK_BUTTON_PRIMARY) { /* left click */
Moritz Lipp's avatar
Moritz Lipp committed
813 814
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
815 816 817 818 819 820 821
      priv->mouse.selection_basepoint.x = button->x;
      priv->mouse.selection_basepoint.y = button->y;

      priv->mouse.selection.x1 = button->x;
      priv->mouse.selection.y1 = button->y;
      priv->mouse.selection.x2 = button->x;
      priv->mouse.selection.y2 = button->y;
Moritz Lipp's avatar
Moritz Lipp committed
822 823
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
824 825 826 827 828 829 830
      priv->mouse.selection_basepoint.x = -1;
      priv->mouse.selection_basepoint.y = -1;

      priv->mouse.selection.x1 = -1;
      priv->mouse.selection.y1 = -1;
      priv->mouse.selection.x2 = -1;
      priv->mouse.selection.y2 = -1;
Moritz Lipp's avatar
Moritz Lipp committed
831 832 833
    }

    return true;
834
  } else if (gdk_event_triggers_context_menu((GdkEvent*) button) == TRUE && button->type == GDK_BUTTON_PRESS) { /* right click */
Moritz Lipp's avatar
Moritz Lipp committed
835 836
    zathura_page_widget_popup_menu(widget, button);
    return true;
837
  }
Moritz Lipp's avatar
Moritz Lipp committed
838

839 840 841 842 843 844 845 846
  return false;
}

static gboolean
cb_zathura_page_widget_button_release_event(GtkWidget* widget, GdkEventButton* button)
{
  g_return_val_if_fail(widget != NULL, false);
  g_return_val_if_fail(button != NULL, false);
847 848

  if (button->type != GDK_BUTTON_RELEASE) {
849 850 851
    return false;
  }

852 853 854
  const int oldx = button->x;
  const int oldy = button->y;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
855
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
856
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
857

858 859 860 861 862 863 864 865 866 867
  const double scale = zathura_document_get_scale(document);

  button->x /= scale;
  button->y /= scale;

  g_signal_emit(ZATHURA_PAGE(widget), signals[BUTTON_RELEASE], 0, button);

  button->x = oldx;
  button->y = oldy;

868
  if (button->button != GDK_BUTTON_PRIMARY) {
869 870 871
    return false;
  }

872
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
873
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
874
    /* get links */
875 876 877 878
    if (priv->links.retrieved == false) {
      priv->links.list      = zathura_page_links_get(priv->page, NULL);
      priv->links.retrieved = true;
      priv->links.n         = (priv->links.list == NULL) ? 0 : girara_list_size(priv->links.list);
Moritz Lipp's avatar
Moritz Lipp committed
879 880
    }

881 882
    if (priv->links.list != NULL && priv->links.n > 0) {
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
883 884 885 886 887
      zathura_rectangle_t rect = recalc_rectangle(priv->page, zathura_link_get_position(link));
      if (rect.x1 <= button->x && rect.x2 >= button->x
          && rect.y1 <= button->y && rect.y2 >= button->y) {
        zathura_link_evaluate(priv->zathura, link);
      }
888
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
889
    }
890
  } else {
891
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
892

893
    zathura_rectangle_t tmp = priv->mouse.selection;
Moritz Lipp's avatar
Moritz Lipp committed
894

Sebastian Ramacher's avatar
Sebastian Ramacher committed
895
    const double scale = zathura_document_get_scale(document);
896 897 898 899
    tmp.x1 /= scale;
    tmp.x2 /= scale;
    tmp.y1 /= scale;
    tmp.y2 /= scale;
900

901
    char* text = zathura_page_get_text(priv->page, tmp, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
902 903 904
    if (text != NULL && *text != '\0') {
      /* emit text-selected signal */
      g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
905
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
906
    g_free(text);
907
  }
Moritz Lipp's avatar
Moritz Lipp committed
908

909 910 911 912 913 914 915
  priv->mouse.selection_basepoint.x = -1;
  priv->mouse.selection_basepoint.y = -1;

  priv->mouse.selection.x1 = -1;
  priv->mouse.selection.y1 = -1;
  priv->mouse.selection.x2 = -1;
  priv->mouse.selection.y2 = -1;
916

917 918 919 920 921 922 923 924
  return false;
}

static gboolean
cb_zathura_page_widget_motion_notify(GtkWidget* widget, GdkEventMotion* event)
{
  g_return_val_if_fail(widget != NULL, false);
  g_return_val_if_fail(event != NULL, false);
925

926
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
    zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
    if (priv->links.retrieved == false) {
      priv->links.list      = zathura_page_links_get(priv->page, NULL);
      priv->links.retrieved = true;
      priv->links.n         = (priv->links.list == NULL) ? 0 : girara_list_size(priv->links.list);
    }

    if (priv->links.list != NULL && priv->links.n > 0) {
      bool over_link = false;
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
        zathura_rectangle_t rect = recalc_rectangle(priv->page, zathura_link_get_position(link));
        if (rect.x1 <= event->x && rect.x2 >= event->x && rect.y1 <= event->y && rect.y2 >= event->y) {
          over_link = true;
        }
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);

      if (priv->mouse.over_link != over_link) {
        if (over_link == true) {
          g_signal_emit(ZATHURA_PAGE(widget), signals[ENTER_LINK], 0);
        } else {
          g_signal_emit(ZATHURA_PAGE(widget), signals[LEAVE_LINK], 0);
        }
        priv->mouse.over_link = over_link;
      }
    }

953 954 955 956
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
957 958
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
959
    tmp.x1 = event->x;
960
    tmp.x2 = priv->mouse.selection_basepoint.x;
961 962
  } else {
    tmp.x2 = event->x;
963
    tmp.x1 = priv->mouse.selection_basepoint.x;
964
  }
965
  if (event->y < priv->mouse.selection_basepoint.y) {
966
    tmp.y1 = event->y;
967
    tmp.y2 = priv->mouse.selection_basepoint.y;
968
  } else {
969
    tmp.y1 = priv->mouse.selection_basepoint.y;
970
    tmp.y2 = event->y;
Moritz Lipp's avatar
Moritz Lipp committed
971 972
  }

973
  redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
974
  redraw_rect(ZATHURA_PAGE(widget), &tmp);
975
  priv->mouse.selection = tmp;
976

Moritz Lipp's avatar
Moritz Lipp committed
977 978
  return false;
}
Moritz Lipp's avatar
Moritz Lipp committed
979

980 981 982 983 984 985 986 987 988 989 990 991 992
static gboolean
cb_zathura_page_widget_leave_notify(GtkWidget* widget, GdkEventCrossing* UNUSED(event))
{
  g_return_val_if_fail(widget != NULL, false);

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  if (priv->mouse.over_link == true) {
    g_signal_emit(ZATHURA_PAGE(widget), signals[LEAVE_LINK], 0);
    priv->mouse.over_link = false;
  }
  return false;
}

Moritz Lipp's avatar
Moritz Lipp committed
993 994 995 996
static void
zathura_page_widget_popup_menu(GtkWidget* widget, GdkEventButton* event)
{
  g_return_if_fail(widget != NULL);
997 998 999 1000 1001
  if (event == NULL) {
    /* do something here in the future in case we have general popups */
    return;
  }

Moritz Lipp's avatar
Moritz Lipp committed
1002 1003
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);

1004 1005 1006
  if (priv->images.retrieved == false) {
    priv->images.list      = zathura_page_images_get(priv->page, NULL);
    priv->images.retrieved = true;
Moritz Lipp's avatar
Moritz Lipp committed
1007 1008
  }

1009
  if (priv->images.list == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
1010 1011 1012