page-widget.c 42.4 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);
}

Jeremie Knuesel's avatar
Jeremie Knuesel committed
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
static cairo_text_extents_t
get_text_extents(const char* string, zathura_t* zathura, cairo_font_weight_t weight) {
  cairo_text_extents_t text = {0,};

  if (zathura == NULL) {
    return text;
  }

  /* make dummy surface to satisfy API requirements */
  cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 0, 0);
  if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
    return text;
  }

  cairo_t* cairo = cairo_create(surface);
  if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) {
    cairo_surface_destroy(surface);
    return text;
  }

  set_font_from_property(cairo, zathura, weight);
  cairo_text_extents(cairo, string, &text);

  /* add some margin (for some reason the reported extents can be a bit short) */
  text.width += 6;
  text.height += 2;

  cairo_destroy(cairo);
  cairo_surface_destroy(surface);

  return text;
}

376
static void
377
zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
378
{
379 380
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
381

Jeremie Knuesel's avatar
Jeremie Knuesel committed
382 383
  cairo_text_extents_t text;

384 385
  switch (prop_id) {
    case PROP_PAGE:
386 387 388 389
      priv->page = g_value_get_pointer(value);
      break;
    case PROP_ZATHURA:
      priv->zathura = g_value_get_pointer(value);
390
      break;
Moritz Lipp's avatar
Moritz Lipp committed
391
    case PROP_DRAW_LINKS:
392
      priv->links.draw = g_value_get_boolean(value);
Moritz Lipp's avatar
Moritz Lipp committed
393
      /* get links */
394
      if (priv->links.draw == TRUE && priv->links.retrieved == FALSE) {
395
        priv->links.list      = zathura_page_links_get(priv->page, NULL);
396
        priv->links.retrieved = TRUE;
397
        priv->links.n         = (priv->links.list == NULL) ? 0 : girara_list_size(priv->links.list);
Moritz Lipp's avatar
Moritz Lipp committed
398 399
      }

400
      if (priv->links.retrieved == TRUE && priv->links.list != NULL) {
Jeremie Knuesel's avatar
Jeremie Knuesel committed
401 402 403
        /* get size of text that should be large enough for every link hint */
        text = get_text_extents("888", priv->zathura, CAIRO_FONT_WEIGHT_BOLD);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
404 405 406 407 408 409 410 411 412 413 414 415
        GIRARA_LIST_FOREACH_BODY(priv->links.list, zathura_link_t*, link,
          if (link != NULL) {
            /* redraw link area */
            zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
            redraw_rect(pageview, &rectangle);

            /* also redraw area for link hint */
            rectangle.x2 = rectangle.x1 + text.width;
            rectangle.y1 = rectangle.y2 - text.height;
            redraw_rect(pageview, &rectangle);
          }
        );
Moritz Lipp's avatar
Moritz Lipp committed
416 417
      }
      break;
Moritz Lipp's avatar
Moritz Lipp committed
418
    case PROP_LINKS_OFFSET:
419
      priv->links.offset = g_value_get_int(value);
Moritz Lipp's avatar
Moritz Lipp committed
420
      break;
421
    case PROP_SEARCH_RESULTS:
422 423 424
      if (priv->search.list != NULL && priv->search.draw) {
        redraw_all_rects(pageview, priv->search.list);
        girara_list_free(priv->search.list);
425
      }
426 427
      priv->search.list = g_value_get_pointer(value);
      if (priv->search.list != NULL && priv->search.draw) {
428
        priv->links.draw = FALSE;
429
        redraw_all_rects(pageview, priv->search.list);
430
      }
431
      priv->search.current = -1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
432
      break;
433
    case PROP_SEARCH_RESULTS_CURRENT: {
434 435 436
      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);
437 438 439 440
        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
441
      if (val < 0) {
442
        priv->search.current = girara_list_size(priv->search.list);
443
      } else {
444
        priv->search.current = val;
445
        if (priv->search.draw == TRUE && val >= 0 && val < (signed) girara_list_size(priv->search.list)) {
446 447
          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
448 449
          redraw_rect(pageview, &rectangle);
        }
450 451 452
      }
      break;
    }
453
    case PROP_DRAW_SEARCH_RESULTS:
454
      priv->search.draw = g_value_get_boolean(value);
455 456 457 458 459 460 461 462 463

      /*
       * 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
464
        gtk_widget_queue_draw(GTK_WIDGET(object));
465
      }
Moritz Lipp's avatar
Moritz Lipp committed
466
      break;
Moritz Lipp's avatar
Moritz Lipp committed
467 468 469 470 471 472
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

static void
473
zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
Moritz Lipp's avatar
Moritz Lipp committed
474
{
475 476
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
Moritz Lipp's avatar
Moritz Lipp committed
477 478 479

  switch (prop_id) {
    case PROP_LINKS_NUMBER:
480
      g_value_set_int(value, priv->links.n);
481
      break;
482
    case PROP_SEARCH_RESULTS_LENGTH:
483
      g_value_set_int(value, priv->search.list == NULL ? 0 : girara_list_size(priv->search.list));
484 485
      break;
    case PROP_SEARCH_RESULTS_CURRENT:
486
      g_value_set_int(value, priv->search.list == NULL ? -1 : priv->search.current);
487
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
488
    case PROP_SEARCH_RESULTS:
489
      g_value_set_pointer(value, priv->search.list);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
490
      break;
491 492 493
    case PROP_DRAW_SEARCH_RESULTS:
      g_value_set_boolean(value, priv->search.draw);
      break;
494 495 496 497 498
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

499 500 501 502
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,14,0)
static zathura_device_factors_t
get_safe_device_factors(cairo_surface_t* surface)
{
503 504
  zathura_device_factors_t factors;
  cairo_surface_get_device_scale(surface, &factors.x, &factors.y);
505

506 507 508 509 510 511
  if (fabs(factors.x) < DBL_EPSILON) {
    factors.x = 1.0;
  }
  if (fabs(factors.y) < DBL_EPSILON) {
    factors.y = 1.0;
  }
512

513
  return factors;
514 515 516 517 518 519 520 521 522
}
#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

523 524 525
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
526
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
527

Sebastian Ramacher's avatar
Sebastian Ramacher committed
528
  zathura_document_t* document   = zathura_page_get_document(priv->page);
529 530 531
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

532
  if (priv->surface != NULL || priv->thumbnail != NULL) {
533 534
    cairo_save(cairo);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
535
    const unsigned int rotation = zathura_document_get_rotation(document);
536
    switch (rotation) {
537 538 539 540 541 542 543 544 545 546 547
      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;
    }

548 549
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
550 551
    }

552 553 554 555 556 557 558
    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);
559 560 561
      unsigned int pheight = (rotation % 180 ? page_width : page_height);
      unsigned int pwidth = (rotation % 180 ? page_height : page_width);

562 563 564 565
      /* 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;
566 567 568 569 570 571 572 573 574 575 576

      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);
577 578 579 580
      /* 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);
581 582
      return FALSE;
    }
583

Jeremie Knuesel's avatar
Jeremie Knuesel committed
584 585
    /* draw links */
    set_font_from_property(cairo, priv->zathura, CAIRO_FONT_WEIGHT_BOLD);
586 587 588 589

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

590
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
591
      unsigned int link_counter = 0;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
592 593 594
      GIRARA_LIST_FOREACH_BODY(priv->links.list, zathura_link_t*, link,
        if (link != NULL) {
          zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
Moritz Lipp's avatar
Moritz Lipp committed
595

Sebastian Ramacher's avatar
Sebastian Ramacher committed
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
          /* draw position */
          const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
          cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
          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);
        }
      );
Moritz Lipp's avatar
Moritz Lipp committed
611
    }
612 613

    /* draw search results */
614
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
615
      int idx = 0;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
616 617
      GIRARA_LIST_FOREACH_BODY(priv->search.list, zathura_rectangle_t*, rect,
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
618

Sebastian Ramacher's avatar
Sebastian Ramacher committed
619 620 621 622 623 624 625 626 627 628 629 630 631
        /* draw position */
        if (idx == priv->search.current) {
          const GdkRGBA color = priv->zathura->ui.colors.highlight_color_active;
          cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
        } else {
          const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
          cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
        }
        cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                        (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
        cairo_fill(cairo);
        ++idx;
      );
632
    }
633
    /* draw selection */
634
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
635 636
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
637
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
638
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
639
      cairo_fill(cairo);
640
    }
641 642
  } else {
    /* set background color */
643
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
644
      GdkRGBA color;
645
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
646
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
647
    } else {
648 649
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
650
    }
651
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
652 653 654 655 656 657 658
    cairo_fill(cairo);

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

    /* write text */
    if (render_loading == true) {
659
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
660
        GdkRGBA color;
661
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
662
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
663
      } else {
664
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
665
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
666 667
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
668
      const char* text = _("Loading...");
669 670 671 672
      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);
673 674
      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);
675 676 677 678 679
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
680
    zathura_render_request(priv->render_request, g_get_real_time());
681 682 683 684 685
  }
  return FALSE;
}

static void
686
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
687 688 689 690 691
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

692
/* smaller than max to be replaced by actual renders */
693
#define THUMBNAIL_INITIAL_SCALE 0.5
694 695 696 697
/* small enough to make bilinear downscaling fast */
#define THUMBNAIL_MAX_SCALE 0.5

static bool
698
surface_small_enough(cairo_surface_t* surface, size_t max_size, cairo_surface_t* old)
699
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
700
  if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE) {
701
    return true;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
702
  }
703 704 705

  const unsigned int width = cairo_image_surface_get_width(surface);
  const unsigned int height = cairo_image_surface_get_height(surface);
706
  const size_t new_size = width * height;
707
  if (new_size > max_size) {
708
    return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
709
  }
710 711 712 713

  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);
714 715
    const size_t old_size = width_old * height_old;
    if (new_size < old_size && new_size >= old_size * THUMBNAIL_MAX_SCALE * THUMBNAIL_MAX_SCALE) {
716
      return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
717
    }
718 719 720 721 722 723
  }

  return true;
}

static cairo_surface_t *
724
draw_thumbnail_image(cairo_surface_t* surface, size_t max_size)
725 726 727
{
  unsigned int width = cairo_image_surface_get_width(surface);
  unsigned int height = cairo_image_surface_get_height(surface);
728
  double scale = sqrt((double)max_size / (width * height)) * THUMBNAIL_INITIAL_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
729
  if (scale > THUMBNAIL_MAX_SCALE) {
730
    scale = THUMBNAIL_MAX_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
731
  }
732 733 734
  width = width * scale;
  height = height * scale;

735 736
  /* 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);
737 738
  const unsigned int unscaled_width = width / device.x;
  const unsigned int unscaled_height = height / device.y;
739

740
  /* create thumbnail surface, taking width and height as _unscaled_ device units */
741
  cairo_surface_t *thumbnail;
742
  thumbnail = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR, unscaled_width, unscaled_height);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
743 744 745
  if (thumbnail == NULL) {
    return NULL;
  }
746
  cairo_t *cr = cairo_create(thumbnail);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
747 748 749 750
  if (cr == NULL) {
    cairo_surface_destroy(thumbnail);
    return NULL;
  }
751 752 753 754 755 756 757 758 759 760 761

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

762
void
763
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface, bool keep_thumbnail)
764
{
765
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
766 767 768 769 770
  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;
  }
771 772
  bool new_render = (priv->surface == NULL && priv->thumbnail == NULL);

Moritz Lipp's avatar
CS  
Moritz Lipp committed
773
  if (priv->surface != NULL) {
774
    cairo_surface_destroy(priv->surface);
775
    priv->surface = NULL;
776
  }
777
  if (surface != NULL) {
778 779
    priv->surface = surface;
    cairo_surface_reference(surface);
780

781
    if (surface_small_enough(surface, thumbnail_size, priv->thumbnail)) {
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
782
      if (priv->thumbnail != NULL) {
783
        cairo_surface_destroy(priv->thumbnail);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
784
      }
785 786 787
      priv->thumbnail = surface;
      cairo_surface_reference(surface);
    } else if (new_render) {
788
      priv->thumbnail = draw_thumbnail_image(surface, thumbnail_size);
789 790 791 792
    }
  } else if (!keep_thumbnail && priv->thumbnail != NULL) {
    cairo_surface_destroy(priv->thumbnail);
    priv->thumbnail = NULL;
793
  }
794
  /* force a redraw here */
795 796 797
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
798 799
}

800
static void
801 802
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
803 804 805
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
806
  zathura_page_widget_update_surface(widget, surface, false);
807 808
}

809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
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. */
831
    zathura_page_widget_update_surface(widget, NULL, false);
832 833 834 835
  }
  priv->cached = false;
}

836
static void
837
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
838
{
839
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
840 841 842

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
843
  zathura_page_widget_update_surface(page, NULL, true);
844
}
845 846

static void
847
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
848
{
Moritz Lipp's avatar
Moritz Lipp committed
849
  /* cause the rect to be drawn */
850
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
851
  grect.x = rectangle->x1;
852
  grect.y = rectangle->y1;
853 854
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
855
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
856 857
}

858
static void
859
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
860
{
861
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
862

Sebastian Ramacher's avatar
Sebastian Ramacher committed
863
  GIRARA_LIST_FOREACH_BODY(rectangles, zathura_rectangle_t*, rect,
864 865
    zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
    redraw_rect(widget, &rectangle);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
866
  );
867 868
}

Moritz Lipp's avatar
Moritz Lipp committed
869
zathura_link_t*
870
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
871
{
Moritz Lipp's avatar
Moritz Lipp committed
872
  g_return_val_if_fail(widget != NULL, NULL);
873
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
874
  g_return_val_if_fail(priv != NULL, NULL);
875

876 877 878
  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
879 880 881
  } else {
    return NULL;
  }
882
}
Moritz Lipp's avatar
Moritz Lipp committed
883

884 885
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
886 887 888 889 890 891
{
  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);

892 893 894 895
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

896
  if (button->button == GDK_BUTTON_PRIMARY) { /* left click */
Moritz Lipp's avatar
Moritz Lipp committed
897 898
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
899 900 901 902 903 904 905
      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
906 907
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
908 909 910 911 912 913 914
      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
915 916 917
    }

    return true;
918
  } else if (gdk_event_triggers_context_menu((GdkEvent*) button) == TRUE && button->type == GDK_BUTTON_PRESS) { /* right click */
Moritz Lipp's avatar
Moritz Lipp committed
919 920
    zathura_page_widget_popup_menu(widget, button);
    return true;
921
  }
Moritz Lipp's avatar
Moritz Lipp committed
922

923 924 925 926 927 928 929 930
  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);
931 932

  if (button->type != GDK_BUTTON_RELEASE) {
933 934 935
    return false;
  }

936 937 938
  const int oldx = button->x;
  const int oldy = button->y;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
939
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
940
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
941

942 943 944 945 946 947 948 949 950 951
  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;

952
  if (button->button != GDK_BUTTON_PRIMARY) {
953 954 955
    return false;
  }

956
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
957
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
958
    /* get links */
959 960 961 962
    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
963 964
    }

965
    if (priv->links.list != NULL && priv->links.n > 0) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
966 967 968 969 970 971 972
      GIRARA_LIST_FOREACH_BODY(priv->links.list, zathura_link_t*, link,
        const 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);
        }
      );
Moritz Lipp's avatar
Moritz Lipp committed
973
    }
974
  } else {
975
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
976

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
979
    const double scale = zathura_document_get_scale(document);
980 981 982 983
    tmp.x1 /= scale;
    tmp.x2 /= scale;
    tmp.y1 /= scale;
    tmp.y2 /= scale;
984

985
    char* text = zathura_page_get_text(priv->page, tmp, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
986 987 988
    if (text != NULL && *text != '\0') {
      /* emit text-selected signal */
      g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
989
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
990
    g_free(text);
991
  }
Moritz Lipp's avatar
Moritz Lipp committed
992

993 994 995 996 997 998 999
  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;
1000

1001 1002 1003 1004 1005 1006 1007 1008
  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);
1009

1010
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
1011 1012 1013 1014 1015 1016 1017 1018 1019
    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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1020
      GIRARA_LIST_FOREACH_BODY(priv->links.list, zathura_link_t*, link,
1021 1022 1023
        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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1024
          break;
1025
        }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1026
      );
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037

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

1038 1039 1040 1041
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
1042 1043
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
1044
    tmp.x1 = event->x;
1045
    tmp.x2 = priv->mouse.selection_basepoint.x;
1046 1047
  } else {
    tmp.x2 = event->x;
1048
    tmp.x1 = priv->mouse.selection_basepoint.x;
1049
  }
1050
  if (event->y < priv->mouse.selection_basepoint.y) {
1051
    tmp.y1 = event->y;