page-widget.c 42.2 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
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.
Jeremie Knuesel's avatar
Jeremie Knuesel committed
320 321
   * absolute units: example: value 10*PANGO_SCALE = 10 (unscaled) device units (logical pixels)
   * point units:    example: value 10*PANGO_SCALE = 10 points = 10*(font dpi config / 72) device units */
Jeremie Knuesel's avatar
Jeremie Knuesel committed
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
  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
static zathura_device_factors_t
get_safe_device_factors(cairo_surface_t* surface)
{
502 503
  zathura_device_factors_t factors;
  cairo_surface_get_device_scale(surface, &factors.x, &factors.y);
504

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

512
  return factors;
513 514
}

515 516 517
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
518
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
519

Sebastian Ramacher's avatar
Sebastian Ramacher committed
520
  zathura_document_t* document   = zathura_page_get_document(priv->page);
521 522 523
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

524
  if (priv->surface != NULL || priv->thumbnail != NULL) {
525 526
    cairo_save(cairo);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
527
    const unsigned int rotation = zathura_document_get_rotation(document);
528
    switch (rotation) {
529 530 531 532 533 534 535 536 537 538 539
      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;
    }

540 541
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
542 543
    }

544 545 546 547 548 549 550
    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);
551 552 553
      unsigned int pheight = (rotation % 180 ? page_width : page_height);
      unsigned int pwidth = (rotation % 180 ? page_height : page_width);

554 555 556 557
      /* 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;
558 559 560 561 562 563 564 565 566 567 568

      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);
569 570 571 572
      /* 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);
573 574
      return FALSE;
    }
575

Jeremie Knuesel's avatar
Jeremie Knuesel committed
576 577
    /* draw links */
    set_font_from_property(cairo, priv->zathura, CAIRO_FONT_WEIGHT_BOLD);
578 579 580 581

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

582
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
583
      unsigned int link_counter = 0;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
584 585 586
      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
587

Sebastian Ramacher's avatar
Sebastian Ramacher committed
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
          /* 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
603
    }
604 605

    /* draw search results */
606
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
607
      int idx = 0;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
608 609
      GIRARA_LIST_FOREACH_BODY(priv->search.list, zathura_rectangle_t*, rect,
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
610

Sebastian Ramacher's avatar
Sebastian Ramacher committed
611 612 613 614 615 616 617 618 619 620 621 622 623
        /* 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;
      );
624
    }
625
    /* draw selection */
626
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
627 628
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
629
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
630
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
631
      cairo_fill(cairo);
632
    }
633 634
  } else {
    /* set background color */
635
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
636
      GdkRGBA color;
637
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
638
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
639
    } else {
640 641
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
642
    }
643
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
644 645 646 647 648 649 650
    cairo_fill(cairo);

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

    /* write text */
    if (render_loading == true) {
651
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
652
        GdkRGBA color;
653
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
654
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
655
      } else {
656
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
657
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
658 659
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
660
      const char* text = _("Loading...");
661 662 663 664
      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);
665 666
      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);
667 668 669 670 671
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
672
    zathura_render_request(priv->render_request, g_get_real_time());
673 674 675 676 677
  }
  return FALSE;
}

static void
678
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
679 680 681 682 683
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

684
/* smaller than max to be replaced by actual renders */
685
#define THUMBNAIL_INITIAL_ZOOM 0.5
686
/* small enough to make bilinear downscaling fast */
687
#define THUMBNAIL_MAX_ZOOM 0.5
688 689

static bool
690
surface_small_enough(cairo_surface_t* surface, size_t max_size, cairo_surface_t* old)
691
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
692
  if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE) {
693
    return true;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
694
  }
695 696 697

  const unsigned int width = cairo_image_surface_get_width(surface);
  const unsigned int height = cairo_image_surface_get_height(surface);
698
  const size_t new_size = width * height;
699
  if (new_size > max_size) {
700
    return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
701
  }
702 703 704 705

  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);
706
    const size_t old_size = width_old * height_old;
707
    if (new_size < old_size && new_size >= old_size * THUMBNAIL_MAX_ZOOM * THUMBNAIL_MAX_ZOOM) {
708
      return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
709
    }
710 711 712 713 714 715
  }

  return true;
}

static cairo_surface_t *
716
draw_thumbnail_image(cairo_surface_t* surface, size_t max_size)
717 718 719
{
  unsigned int width = cairo_image_surface_get_width(surface);
  unsigned int height = cairo_image_surface_get_height(surface);
720 721 722
  double scale = sqrt((double)max_size / (width * height)) * THUMBNAIL_INITIAL_ZOOM;
  if (scale > THUMBNAIL_MAX_ZOOM) {
    scale = THUMBNAIL_MAX_ZOOM;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
723
  }
724 725 726
  width = width * scale;
  height = height * scale;

727 728
  /* 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);
729 730
  const unsigned int unscaled_width = width / device.x;
  const unsigned int unscaled_height = height / device.y;
731

732
  /* create thumbnail surface, taking width and height as _unscaled_ device units */
733
  cairo_surface_t *thumbnail;
734
  thumbnail = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR, unscaled_width, unscaled_height);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
735 736 737
  if (thumbnail == NULL) {
    return NULL;
  }
738
  cairo_t *cr = cairo_create(thumbnail);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
739 740 741 742
  if (cr == NULL) {
    cairo_surface_destroy(thumbnail);
    return NULL;
  }
743 744 745 746 747 748 749 750 751 752 753

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

754
void
755
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface, bool keep_thumbnail)
756
{
757
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
758 759 760 761 762
  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;
  }
763 764
  bool new_render = (priv->surface == NULL && priv->thumbnail == NULL);

Moritz Lipp's avatar
CS  
Moritz Lipp committed
765
  if (priv->surface != NULL) {
766
    cairo_surface_destroy(priv->surface);
767
    priv->surface = NULL;
768
  }
769
  if (surface != NULL) {
770 771
    priv->surface = surface;
    cairo_surface_reference(surface);
772

773
    if (surface_small_enough(surface, thumbnail_size, priv->thumbnail)) {
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
774
      if (priv->thumbnail != NULL) {
775
        cairo_surface_destroy(priv->thumbnail);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
776
      }
777 778 779
      priv->thumbnail = surface;
      cairo_surface_reference(surface);
    } else if (new_render) {
780
      priv->thumbnail = draw_thumbnail_image(surface, thumbnail_size);
781 782 783 784
    }
  } else if (!keep_thumbnail && priv->thumbnail != NULL) {
    cairo_surface_destroy(priv->thumbnail);
    priv->thumbnail = NULL;
785
  }
786
  /* force a redraw here */
787 788 789
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
790 791
}

792
static void
793 794
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
795 796 797
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
798
  zathura_page_widget_update_surface(widget, surface, false);
799 800
}

801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
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. */
823
    zathura_page_widget_update_surface(widget, NULL, false);
824 825 826 827
  }
  priv->cached = false;
}

828
static void
829
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
830
{
831
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
832 833 834

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
835
  zathura_page_widget_update_surface(page, NULL, true);
836
}
837 838

static void
839
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
840
{
Moritz Lipp's avatar
Moritz Lipp committed
841
  /* cause the rect to be drawn */
842
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
843
  grect.x = rectangle->x1;
844
  grect.y = rectangle->y1;
845 846
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
847
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
848 849
}

850
static void
851
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
852
{
853
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
854

Sebastian Ramacher's avatar
Sebastian Ramacher committed
855
  GIRARA_LIST_FOREACH_BODY(rectangles, zathura_rectangle_t*, rect,
856 857
    zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
    redraw_rect(widget, &rectangle);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
858
  );
859 860
}

Moritz Lipp's avatar
Moritz Lipp committed
861
zathura_link_t*
862
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
863
{
Moritz Lipp's avatar
Moritz Lipp committed
864
  g_return_val_if_fail(widget != NULL, NULL);
865
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
866
  g_return_val_if_fail(priv != NULL, NULL);
867

868 869 870
  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
871 872 873
  } else {
    return NULL;
  }
874
}
Moritz Lipp's avatar
Moritz Lipp committed
875

876 877
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
878 879 880 881 882 883
{
  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);

884 885 886 887
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

888
  if (button->button == GDK_BUTTON_PRIMARY) { /* left click */
Moritz Lipp's avatar
Moritz Lipp committed
889 890
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
891 892 893 894 895 896 897
      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
898 899
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
900 901 902 903 904 905 906
      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
907 908 909
    }

    return true;
910
  } else if (gdk_event_triggers_context_menu((GdkEvent*) button) == TRUE && button->type == GDK_BUTTON_PRESS) { /* right click */
Moritz Lipp's avatar
Moritz Lipp committed
911 912
    zathura_page_widget_popup_menu(widget, button);
    return true;
913
  }
Moritz Lipp's avatar
Moritz Lipp committed
914

915 916 917 918 919 920 921 922
  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);
923 924

  if (button->type != GDK_BUTTON_RELEASE) {
925 926 927
    return false;
  }

928 929 930
  const int oldx = button->x;
  const int oldy = button->y;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
931
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
932
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
933

934 935 936 937 938 939 940 941 942 943
  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;

944
  if (button->button != GDK_BUTTON_PRIMARY) {
945 946 947
    return false;
  }

948
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
949
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
950
    /* get links */
951 952 953 954
    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
955 956
    }

957
    if (priv->links.list != NULL && priv->links.n > 0) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
958 959 960 961 962 963 964
      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
965
    }
966
  } else {
967
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
968

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
971
    const double scale = zathura_document_get_scale(document);
972 973 974 975
    tmp.x1 /= scale;
    tmp.x2 /= scale;
    tmp.y1 /= scale;
    tmp.y2 /= scale;
976

977
    char* text = zathura_page_get_text(priv->page, tmp, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
978 979 980
    if (text != NULL && *text != '\0') {
      /* emit text-selected signal */
      g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
981
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
982
    g_free(text);
983
  }
Moritz Lipp's avatar
Moritz Lipp committed
984

985 986 987 988 989 990 991
  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;
992

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

1002
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
1003 1004 1005 1006 1007 1008 1009 1010 1011
    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
1012
      GIRARA_LIST_FOREACH_BODY(priv->links.list, zathura_link_t*, link,
1013 1014 1015
        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
1016
          break;
1017
        }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1018
      );
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029

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

1030 1031 1032 1033
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
1034 1035
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
1036
    tmp.x1 = event->x;
1037
    tmp.x2 = priv->mouse.selection_basepoint.x;
1038 1039
  } else {
    tmp.x2 = event->x;
1040
    tmp.x1 = priv->mouse.selection_basepoint.x;
1041
  }
1042
  if (event->y < priv->mouse.selection_basepoint.y) {