page-widget.c 41.6 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
}

Jeremie Knuesel's avatar
Jeremie Knuesel committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
static void
set_font_from_property(cairo_t* cairo, zathura_t* zathura, cairo_font_weight_t weight)
{
  if (zathura == NULL) {
    return;
  }

  /* get user font description */
  char* font = NULL;
  girara_setting_get(zathura->ui.session, "font", &font);
  if (font == NULL) {
    return;
  }

  /* use pango to extract font family and size */
  PangoFontDescription* descr = pango_font_description_from_string(font);

  const char* family = pango_font_description_get_family(descr);

  /* get font size: can be points or absolute.
   * absolute units: value = 10*PANGO_SCALE = 10 (unscaled) device units (logical pixels)
   * point units:    value = 10*PANGO_SCALE = 10 points = 10*(font dpi config / 72) device units */
  double size = pango_font_description_get_size(descr) / PANGO_SCALE;

  /* convert point size to device units */
  if (!pango_font_description_get_size_is_absolute(descr)) {
    double font_dpi = 96.0;
    if (zathura->ui.session != NULL) {
      if (gtk_widget_has_screen(zathura->ui.session->gtk.view)) {
        GdkScreen* screen = gtk_widget_get_screen(zathura->ui.session->gtk.view);
        font_dpi = gdk_screen_get_resolution(screen);
      }
    }
    size = size * font_dpi / 72;
  }

  cairo_select_font_face(cairo, family, CAIRO_FONT_SLANT_NORMAL, weight);
  cairo_set_font_size(cairo, size);

  pango_font_description_free(descr);
  g_free(font);
}

343
static void
344
zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
345
{
346 347
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
348 349 350

  switch (prop_id) {
    case PROP_PAGE:
351 352 353 354
      priv->page = g_value_get_pointer(value);
      break;
    case PROP_ZATHURA:
      priv->zathura = g_value_get_pointer(value);
355
      break;
Moritz Lipp's avatar
Moritz Lipp committed
356
    case PROP_DRAW_LINKS:
357
      priv->links.draw = g_value_get_boolean(value);
Moritz Lipp's avatar
Moritz Lipp committed
358
      /* get links */
359
      if (priv->links.draw == TRUE && priv->links.retrieved == FALSE) {
360
        priv->links.list      = zathura_page_links_get(priv->page, NULL);
361
        priv->links.retrieved = TRUE;
362
        priv->links.n         = (priv->links.list == NULL) ? 0 : girara_list_size(priv->links.list);
Moritz Lipp's avatar
Moritz Lipp committed
363 364
      }

365
      if (priv->links.retrieved == TRUE && priv->links.list != NULL) {
366
        GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
367 368 369 370
        if (link != NULL) {
          zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
          redraw_rect(pageview, &rectangle);
        }
371
        GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
372 373
      }
      break;
Moritz Lipp's avatar
Moritz Lipp committed
374
    case PROP_LINKS_OFFSET:
375
      priv->links.offset = g_value_get_int(value);
Moritz Lipp's avatar
Moritz Lipp committed
376
      break;
377
    case PROP_SEARCH_RESULTS:
378 379 380
      if (priv->search.list != NULL && priv->search.draw) {
        redraw_all_rects(pageview, priv->search.list);
        girara_list_free(priv->search.list);
381
      }
382 383
      priv->search.list = g_value_get_pointer(value);
      if (priv->search.list != NULL && priv->search.draw) {
384
        priv->links.draw = FALSE;
385
        redraw_all_rects(pageview, priv->search.list);
386
      }
387
      priv->search.current = -1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
388
      break;
389
    case PROP_SEARCH_RESULTS_CURRENT: {
390 391 392
      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);
393 394 395 396
        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
397
      if (val < 0) {
398
        priv->search.current = girara_list_size(priv->search.list);
399
      } else {
400
        priv->search.current = val;
401
        if (priv->search.draw == TRUE && val >= 0 && val < (signed) girara_list_size(priv->search.list)) {
402 403
          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
404 405
          redraw_rect(pageview, &rectangle);
        }
406 407 408
      }
      break;
    }
409
    case PROP_DRAW_SEARCH_RESULTS:
410
      priv->search.draw = g_value_get_boolean(value);
411 412 413 414 415 416 417 418 419

      /*
       * 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
420
        gtk_widget_queue_draw(GTK_WIDGET(object));
421
      }
Moritz Lipp's avatar
Moritz Lipp committed
422
      break;
Moritz Lipp's avatar
Moritz Lipp committed
423 424 425 426 427 428
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

static void
429
zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
Moritz Lipp's avatar
Moritz Lipp committed
430
{
431 432
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
Moritz Lipp's avatar
Moritz Lipp committed
433 434 435

  switch (prop_id) {
    case PROP_LINKS_NUMBER:
436
      g_value_set_int(value, priv->links.n);
437
      break;
438
    case PROP_SEARCH_RESULTS_LENGTH:
439
      g_value_set_int(value, priv->search.list == NULL ? 0 : girara_list_size(priv->search.list));
440 441
      break;
    case PROP_SEARCH_RESULTS_CURRENT:
442
      g_value_set_int(value, priv->search.list == NULL ? -1 : priv->search.current);
443
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
444
    case PROP_SEARCH_RESULTS:
445
      g_value_set_pointer(value, priv->search.list);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
446
      break;
447 448 449
    case PROP_DRAW_SEARCH_RESULTS:
      g_value_set_boolean(value, priv->search.draw);
      break;
450 451 452 453 454
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
#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

479 480 481
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
482
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
483

Sebastian Ramacher's avatar
Sebastian Ramacher committed
484
  zathura_document_t* document   = zathura_page_get_document(priv->page);
485 486 487
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

488
  if (priv->surface != NULL || priv->thumbnail != NULL) {
489 490
    cairo_save(cairo);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
491
    const unsigned int rotation = zathura_document_get_rotation(document);
492
    switch (rotation) {
493 494 495 496 497 498 499 500 501 502 503
      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;
    }

504 505
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
506 507
    }

508 509 510 511 512 513 514
    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);
515 516 517
      unsigned int pheight = (rotation % 180 ? page_width : page_height);
      unsigned int pwidth = (rotation % 180 ? page_height : page_width);

518 519 520 521
      /* 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;
522 523 524 525 526 527 528 529 530 531 532

      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);
533 534 535 536
      /* 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);
537 538
      return FALSE;
    }
539

Jeremie Knuesel's avatar
Jeremie Knuesel committed
540 541
    /* draw links */
    set_font_from_property(cairo, priv->zathura, CAIRO_FONT_WEIGHT_BOLD);
542 543 544 545

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

546
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
547
      unsigned int link_counter = 0;
548
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
549 550
      if (link != NULL) {
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
Moritz Lipp's avatar
Moritz Lipp committed
551

Moritz Lipp's avatar
Moritz Lipp committed
552
        /* draw position */
553 554
        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
555 556 557 558 559 560 561 562 563 564 565
        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_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);
      }
566
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
567
    }
568 569

    /* draw search results */
570
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
571
      int idx = 0;
572
      GIRARA_LIST_FOREACH(priv->search.list, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
573
      zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
574

Moritz Lipp's avatar
Moritz Lipp committed
575 576
      /* draw position */
      if (idx == priv->search.current) {
577 578
        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
579
      } else {
580 581
        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
582 583 584 585 586
      }
      cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                      (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
      cairo_fill(cairo);
      ++idx;
587
      GIRARA_LIST_FOREACH_END(priv->search.list, zathura_rectangle_t*, iter, rect);
588
    }
589
    /* draw selection */
590
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
591 592
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
593
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
594
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
595
      cairo_fill(cairo);
596
    }
597 598
  } else {
    /* set background color */
599
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
600
      GdkRGBA color;
601
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
602
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
603
    } else {
604 605
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
606
    }
607
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
608 609 610 611 612 613 614
    cairo_fill(cairo);

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

    /* write text */
    if (render_loading == true) {
615
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
616
        GdkRGBA color;
617
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
618
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
619
      } else {
620
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
621
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
622 623
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
624
      const char* text = _("Loading...");
625 626 627 628
      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);
629 630
      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);
631 632 633 634 635
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
636
    zathura_render_request(priv->render_request, g_get_real_time());
637 638 639 640 641
  }
  return FALSE;
}

static void
642
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
643 644 645 646 647
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

648
/* smaller than max to be replaced by actual renders */
649
#define THUMBNAIL_INITIAL_SCALE 0.5
650 651 652 653
/* small enough to make bilinear downscaling fast */
#define THUMBNAIL_MAX_SCALE 0.5

static bool
654
surface_small_enough(cairo_surface_t* surface, size_t max_size, cairo_surface_t* old)
655
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
656
  if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE) {
657
    return true;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
658
  }
659 660 661

  const unsigned int width = cairo_image_surface_get_width(surface);
  const unsigned int height = cairo_image_surface_get_height(surface);
662
  const size_t new_size = width * height;
663
  if (new_size > max_size) {
664
    return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
665
  }
666 667 668 669

  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);
670 671
    const size_t old_size = width_old * height_old;
    if (new_size < old_size && new_size >= old_size * THUMBNAIL_MAX_SCALE * THUMBNAIL_MAX_SCALE) {
672
      return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
673
    }
674 675 676 677 678 679
  }

  return true;
}

static cairo_surface_t *
680
draw_thumbnail_image(cairo_surface_t* surface, size_t max_size)
681 682 683
{
  unsigned int width = cairo_image_surface_get_width(surface);
  unsigned int height = cairo_image_surface_get_height(surface);
684
  double scale = sqrt((double)max_size / (width * height)) * THUMBNAIL_INITIAL_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
685
  if (scale > THUMBNAIL_MAX_SCALE) {
686
    scale = THUMBNAIL_MAX_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
687
  }
688 689 690
  width = width * scale;
  height = height * scale;

691 692 693 694 695 696
  /* note: this always returns 1 and 1 if Cairo too old for device scale API */
  zathura_device_factors_t device = get_safe_device_factors(surface);
  const unsigned int user_width = width / device.x;
  const unsigned int user_height = height / device.y;

  /* create thumbnail surface, taking width and height as device sizes */
697
  cairo_surface_t *thumbnail;
698
  thumbnail = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR, user_width, user_height);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
699 700 701
  if (thumbnail == NULL) {
    return NULL;
  }
702
  cairo_t *cr = cairo_create(thumbnail);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
703 704 705 706
  if (cr == NULL) {
    cairo_surface_destroy(thumbnail);
    return NULL;
  }
707 708 709 710 711 712 713 714 715 716 717

  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;
}

718
void
719
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface, bool keep_thumbnail)
720
{
721
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
722 723 724 725 726
  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;
  }
727 728
  bool new_render = (priv->surface == NULL && priv->thumbnail == NULL);

Moritz Lipp's avatar
CS  
Moritz Lipp committed
729
  if (priv->surface != NULL) {
730
    cairo_surface_destroy(priv->surface);
731
    priv->surface = NULL;
732
  }
733
  if (surface != NULL) {
734 735
    priv->surface = surface;
    cairo_surface_reference(surface);
736

737
    if (surface_small_enough(surface, thumbnail_size, priv->thumbnail)) {
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
738
      if (priv->thumbnail != NULL) {
739
        cairo_surface_destroy(priv->thumbnail);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
740
      }
741 742 743
      priv->thumbnail = surface;
      cairo_surface_reference(surface);
    } else if (new_render) {
744
      priv->thumbnail = draw_thumbnail_image(surface, thumbnail_size);
745 746 747 748
    }
  } else if (!keep_thumbnail && priv->thumbnail != NULL) {
    cairo_surface_destroy(priv->thumbnail);
    priv->thumbnail = NULL;
749
  }
750
  /* force a redraw here */
751 752 753
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
754 755
}

756
static void
757 758
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
759 760 761
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
762
  zathura_page_widget_update_surface(widget, surface, false);
763 764
}

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
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. */
787
    zathura_page_widget_update_surface(widget, NULL, false);
788 789 790 791
  }
  priv->cached = false;
}

792
static void
793
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
794
{
795
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
796 797 798

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
799
  zathura_page_widget_update_surface(page, NULL, true);
800
}
801 802

static void
803
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
804
{
Moritz Lipp's avatar
Moritz Lipp committed
805
  /* cause the rect to be drawn */
806
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
807
  grect.x = rectangle->x1;
808
  grect.y = rectangle->y1;
809 810
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
811
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
812 813
}

814
static void
815
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
816
{
817
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
818 819

  GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect)
820 821
    zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
    redraw_rect(widget, &rectangle);
822 823 824
  GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect);
}

Moritz Lipp's avatar
Moritz Lipp committed
825
zathura_link_t*
826
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
827
{
Moritz Lipp's avatar
Moritz Lipp committed
828
  g_return_val_if_fail(widget != NULL, NULL);
829
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
830
  g_return_val_if_fail(priv != NULL, NULL);
831

832 833 834
  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
835 836 837
  } else {
    return NULL;
  }
838
}
Moritz Lipp's avatar
Moritz Lipp committed
839

840 841
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
842 843 844 845 846 847
{
  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);

848 849 850 851
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

852
  if (button->button == GDK_BUTTON_PRIMARY) { /* left click */
Moritz Lipp's avatar
Moritz Lipp committed
853 854
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
855 856 857 858 859 860 861
      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
862 863
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
864 865 866 867 868 869 870
      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
871 872 873
    }

    return true;
874
  } else if (gdk_event_triggers_context_menu((GdkEvent*) button) == TRUE && button->type == GDK_BUTTON_PRESS) { /* right click */
Moritz Lipp's avatar
Moritz Lipp committed
875 876
    zathura_page_widget_popup_menu(widget, button);
    return true;
877
  }
Moritz Lipp's avatar
Moritz Lipp committed
878

879 880 881 882 883 884 885 886
  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);
887 888

  if (button->type != GDK_BUTTON_RELEASE) {
889 890 891
    return false;
  }

892 893 894
  const int oldx = button->x;
  const int oldy = button->y;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
895
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
896
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
897

898 899 900 901 902 903 904 905 906 907
  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;

908
  if (button->button != GDK_BUTTON_PRIMARY) {
909 910 911
    return false;
  }

912
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
913
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
914
    /* get links */
915 916 917 918
    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
919 920
    }

921 922
    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
923 924 925 926 927
      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);
      }
928
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
929
    }
930
  } else {
931
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
932

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
935
    const double scale = zathura_document_get_scale(document);
936 937 938 939
    tmp.x1 /= scale;
    tmp.x2 /= scale;
    tmp.y1 /= scale;
    tmp.y2 /= scale;
940

941
    char* text = zathura_page_get_text(priv->page, tmp, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
942 943 944
    if (text != NULL && *text != '\0') {
      /* emit text-selected signal */
      g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
945
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
946
    g_free(text);
947
  }
Moritz Lipp's avatar
Moritz Lipp committed
948

949 950 951 952 953 954 955
  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;
956

957 958 959 960 961 962 963 964
  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);
965

966
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
    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;
      }
    }

993 994 995 996
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
997 998
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
999
    tmp.x1 = event->x;
1000
    tmp.x2 = priv->mouse.selection_basepoint.x;
1001 1002
  } else {
    tmp.x2 = event->x;
1003
    tmp.x1 = priv->mouse.selection_basepoint.x;
1004
  }
1005
  if (event->y < priv->mouse.selection_basepoint.y) {
1006
    tmp.y1 = event->y;
1007
    tmp.y2 = priv->mouse.selection_basepoint.y;
1008
  } else {
1009
    tmp.y1 = priv->mouse.selection_basepoint.y;
1010
    tmp.y2 = event->y;
Moritz Lipp's avatar
Moritz Lipp committed
1011 1012
  }

1013
  redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
1014
  redraw_rect(ZATHURA_PAGE(widget), &tmp);
1015
  priv->mouse.selection = tmp;
1016

Moritz Lipp's avatar
Moritz Lipp committed
1017 1018
  return false;
}
Moritz Lipp's avatar
Moritz Lipp committed
1019

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
static gboolean
cb_zathura_page_widget_leave_notify(GtkWidget* widget, GdkEventCrossing* UNUSED(event))
{
  g_return_val_if_fail(widget != NULL,