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

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

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

19
G_DEFINE_TYPE(ZathuraPage, zathura_page_widget, GTK_TYPE_DRAWING_AREA)
20

21
typedef struct zathura_page_widget_private_s {
22 23
  zathura_page_t* page; /**< Page object */
  zathura_t* zathura; /**< Zathura object */
24
  cairo_surface_t* surface; /**< Cairo surface */
25
  cairo_surface_t* thumbnail; /**< Cairo surface */
26
  ZathuraRenderRequest* render_request; /* Request object */
27
  bool cached; /**< Cached state */
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

  struct {
    girara_list_t* list; /**< List of links on the page */
    bool retrieved; /**< True if we already tried to retrieve the list of links */
    bool draw; /**< True if links should be drawn */
    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 */
    bool draw; /**< Draw search results */
  } search;

  struct {
    girara_list_t* list; /**< List of images on the page */
    bool retrieved; /**< True if we already tried to retrieve the list of images */
    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
    bool 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 117

  /* overwrite methods */
  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);
211 212
  priv->page             = NULL;
  priv->surface          = NULL;
213
  priv->thumbnail        = NULL;
214
  priv->render_request   = NULL;
215
  priv->cached           = false;
216 217 218 219 220 221 222 223 224

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

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

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

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

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

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

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

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

  return GTK_WIDGET(ret);
262 263
}

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

  g_clear_object(&priv->render_request);

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

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

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

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

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

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

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

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

  switch (prop_id) {
    case PROP_PAGE:
308 309 310 311
      priv->page = g_value_get_pointer(value);
      break;
    case PROP_ZATHURA:
      priv->zathura = g_value_get_pointer(value);
312
      break;
Moritz Lipp's avatar
Moritz Lipp committed
313
    case PROP_DRAW_LINKS:
314
      priv->links.draw = g_value_get_boolean(value);
Moritz Lipp's avatar
Moritz Lipp committed
315
      /* get links */
316 317 318 319
      if (priv->links.draw == true && 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
320 321
      }

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

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

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

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

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

412 413 414
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
415
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
416

Sebastian Ramacher's avatar
Sebastian Ramacher committed
417
  zathura_document_t* document   = zathura_page_get_document(priv->page);
418 419 420
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

421
  if (priv->surface != NULL || priv->thumbnail != NULL) {
422 423
    cairo_save(cairo);

424
    unsigned int rotation = zathura_document_get_rotation(document);
425
    switch (rotation) {
426 427 428 429 430 431 432 433 434 435 436
      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;
    }

437 438
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
439 440
    }

441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
    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);
      const unsigned int pheight = (rotation % 180 ? page_width : page_height);
      const unsigned int pwidth = (rotation % 180 ? page_height : page_width);

      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);
461 462 463 464
      /* 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);
465 466
      return FALSE;
    }
467 468 469 470 471 472 473 474 475 476 477

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

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

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

479
    g_free(font);
480

Moritz Lipp's avatar
Moritz Lipp committed
481
    /* draw links */
482
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
483
      unsigned int link_counter = 0;
484
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
485 486
      if (link != NULL) {
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
Moritz Lipp's avatar
Moritz Lipp committed
487

Moritz Lipp's avatar
Moritz Lipp committed
488
        /* draw position */
489 490
        const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
        cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
Moritz Lipp's avatar
Moritz Lipp committed
491 492 493 494 495 496 497 498 499 500 501 502
        cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                        (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
        cairo_fill(cairo);

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

    /* draw search results */
507
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
508
      int idx = 0;
509
      GIRARA_LIST_FOREACH(priv->search.list, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
510
      zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
511

Moritz Lipp's avatar
Moritz Lipp committed
512 513
      /* draw position */
      if (idx == priv->search.current) {
514 515
        const GdkRGBA color = priv->zathura->ui.colors.highlight_color_active;
        cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
Moritz Lipp's avatar
Moritz Lipp committed
516
      } else {
517 518
        const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
        cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
Moritz Lipp's avatar
Moritz Lipp committed
519 520 521 522 523
      }
      cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                      (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
      cairo_fill(cairo);
      ++idx;
524
      GIRARA_LIST_FOREACH_END(priv->search.list, zathura_rectangle_t*, iter, rect);
525
    }
526
    /* draw selection */
527
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
528 529
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
530
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
531
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
532
      cairo_fill(cairo);
533
    }
534 535
  } else {
    /* set background color */
536
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
537
      GdkRGBA color;
538
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
539
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
540
    } else {
541 542
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
543
    }
544
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
545 546 547 548 549 550 551
    cairo_fill(cairo);

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

    /* write text */
    if (render_loading == true) {
552
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
553
        GdkRGBA color;
554
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
555
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
556
      } else {
557
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
558
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
559 560
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
561
      const char* text = _("Loading...");
562 563 564 565
      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);
566 567
      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);
568 569 570 571 572
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
573
    zathura_render_request(priv->render_request, g_get_real_time());
574 575 576 577 578
  }
  return FALSE;
}

static void
579
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
580 581 582 583 584
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

585
/* smaller than max to be replaced by actual renders */
586
#define THUMBNAIL_INITIAL_SCALE 0.5
587 588 589 590
/* small enough to make bilinear downscaling fast */
#define THUMBNAIL_MAX_SCALE 0.5

static bool
591
surface_small_enough(cairo_surface_t* surface, size_t max_size, cairo_surface_t* old)
592 593 594 595 596 597
{
  if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE)
    return true;

  const unsigned int width = cairo_image_surface_get_width(surface);
  const unsigned int height = cairo_image_surface_get_height(surface);
598
  const size_t new_size = width * height;
599
  if (new_size > max_size) {
600
    return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
601
  }
602 603 604 605

  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);
606 607
    const size_t old_size = width_old * height_old;
    if (new_size < old_size && new_size >= old_size * THUMBNAIL_MAX_SCALE * THUMBNAIL_MAX_SCALE) {
608
      return false;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
609
    }
610 611 612 613 614 615
  }

  return true;
}

static cairo_surface_t *
616
draw_thumbnail_image(cairo_surface_t* surface, size_t max_size)
617 618 619
{
  unsigned int width = cairo_image_surface_get_width(surface);
  unsigned int height = cairo_image_surface_get_height(surface);
620
  double scale = sqrt((double)max_size / (width * height)) * THUMBNAIL_INITIAL_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
621
  if (scale > THUMBNAIL_MAX_SCALE) {
622
    scale = THUMBNAIL_MAX_SCALE;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
623
  }
624 625 626 627 628
  width = width * scale;
  height = height * scale;

  cairo_surface_t *thumbnail;
  thumbnail = cairo_surface_create_similar(surface, CAIRO_CONTENT_COLOR, width, height);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
629 630 631
  if (thumbnail == NULL) {
    return NULL;
  }
632
  cairo_t *cr = cairo_create(thumbnail);
Lingzhu Xiang's avatar
Lingzhu Xiang committed
633 634 635 636
  if (cr == NULL) {
    cairo_surface_destroy(thumbnail);
    return NULL;
  }
637 638 639 640 641 642 643 644 645 646 647

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

648
void
649
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface, bool keep_thumbnail)
650
{
651
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
652 653 654 655 656
  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;
  }
657 658
  bool new_render = (priv->surface == NULL && priv->thumbnail == NULL);

Moritz Lipp's avatar
CS  
Moritz Lipp committed
659
  if (priv->surface != NULL) {
660
    cairo_surface_destroy(priv->surface);
661
    priv->surface = NULL;
662
  }
663
  if (surface != NULL) {
664 665
    priv->surface = surface;
    cairo_surface_reference(surface);
666

667
    if (surface_small_enough(surface, thumbnail_size, priv->thumbnail)) {
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
668
      if (priv->thumbnail != NULL) {
669
        cairo_surface_destroy(priv->thumbnail);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
670
      }
671 672 673
      priv->thumbnail = surface;
      cairo_surface_reference(surface);
    } else if (new_render) {
674
      priv->thumbnail = draw_thumbnail_image(surface, thumbnail_size);
675 676 677 678
    }
  } else if (!keep_thumbnail && priv->thumbnail != NULL) {
    cairo_surface_destroy(priv->thumbnail);
    priv->thumbnail = NULL;
679
  }
680
  /* force a redraw here */
681 682 683
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
684 685
}

686
static void
687 688
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
689 690 691
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
692
  zathura_page_widget_update_surface(widget, surface, false);
693 694
}

695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
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. */
717
    zathura_page_widget_update_surface(widget, NULL, false);
718 719 720 721
  }
  priv->cached = false;
}

722
static void
723
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
724
{
725
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
726 727 728

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
729
  zathura_page_widget_update_surface(page, NULL, true);
730
}
731 732

static void
733
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
734
{
Moritz Lipp's avatar
Moritz Lipp committed
735
  /* cause the rect to be drawn */
736
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
737
  grect.x = rectangle->x1;
738
  grect.y = rectangle->y1;
739 740
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
741
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
742 743
}

744
static void
745
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
746
{
747
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
748 749

  GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect)
750 751
    zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
    redraw_rect(widget, &rectangle);
752 753 754
  GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect);
}

Moritz Lipp's avatar
Moritz Lipp committed
755
zathura_link_t*
756
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
757
{
Moritz Lipp's avatar
Moritz Lipp committed
758
  g_return_val_if_fail(widget != NULL, NULL);
759
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
760
  g_return_val_if_fail(priv != NULL, NULL);
761

762 763 764
  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
765 766 767
  } else {
    return NULL;
  }
768
}
Moritz Lipp's avatar
Moritz Lipp committed
769

770 771
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
772 773 774 775 776 777
{
  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);

778 779 780 781
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

Moritz Lipp's avatar
Moritz Lipp committed
782 783 784
  if (button->button == 1) { /* left click */
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
785 786 787 788 789 790 791
      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
792 793
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
794 795 796 797 798 799 800
      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
801 802 803 804 805 806
    }

    return true;
  } else if (button->button == 3) { /* right click */
    zathura_page_widget_popup_menu(widget, button);
    return true;
807
  }
Moritz Lipp's avatar
Moritz Lipp committed
808

809 810 811 812 813 814 815 816
  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);
817 818

  if (button->type != GDK_BUTTON_RELEASE) {
819 820 821
    return false;
  }

822 823 824
  const int oldx = button->x;
  const int oldy = button->y;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
825
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
826
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
827

828 829 830 831 832 833 834 835 836 837 838 839 840 841
  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;

  if (button->button != 1) {
    return false;
  }

842
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
843
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
844
    /* get links */
845 846 847 848
    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
849 850
    }

851 852
    if (priv->links.list != NULL && priv->links.n > 0) {
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
853 854 855 856 857
      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);
      }
858
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
859
    }
860
  } else {
861
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
862

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

865 866 867 868 869
    double scale = zathura_document_get_scale(document);
    tmp.x1 /= scale;
    tmp.x2 /= scale;
    tmp.y1 /= scale;
    tmp.y2 /= scale;
870

871 872 873 874 875
    char* text = zathura_page_get_text(priv->page, tmp, NULL);
    if (text != NULL) {
      if (strlen(text) > 0) {
        /* emit text-selected signal */
        g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
876
      }
877

878
      g_free(text);
879 880
    }
  }
Moritz Lipp's avatar
Moritz Lipp committed
881

882 883 884 885 886 887 888
  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;
889

890 891 892 893 894 895 896 897
  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);
898

899
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
    zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
    if (priv->links.retrieved == false) {
      priv->links.list      = zathura_page_links_get(priv->page, NULL);
      priv->links.retrieved = true;
      priv->links.n         = (priv->links.list == NULL) ? 0 : girara_list_size(priv->links.list);
    }

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

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

926 927 928 929
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
930 931
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
932
    tmp.x1 = event->x;
933
    tmp.x2 = priv->mouse.selection_basepoint.x;
934 935
  } else {
    tmp.x2 = event->x;
936
    tmp.x1 = priv->mouse.selection_basepoint.x;
937
  }
938
  if (event->y < priv->mouse.selection_basepoint.y) {
939
    tmp.y1 = event->y;
940
    tmp.y2 = priv->mouse.selection_basepoint.y;
941
  } else {
942
    tmp.y1 = priv->mouse.selection_basepoint.y;
943
    tmp.y2 = event->y;
Moritz Lipp's avatar
Moritz Lipp committed
944 945
  }

946
  redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
947
  redraw_rect(ZATHURA_PAGE(widget), &tmp);
948
  priv->mouse.selection = tmp;
949

Moritz Lipp's avatar
Moritz Lipp committed
950 951
  return false;
}
Moritz Lipp's avatar
Moritz Lipp committed
952

953 954 955 956 957 958 959 960 961 962 963 964 965
static gboolean
cb_zathura_page_widget_leave_notify(GtkWidget* widget, GdkEventCrossing* UNUSED(event))
{
  g_return_val_if_fail(widget != NULL, false);

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

Moritz Lipp's avatar
Moritz Lipp committed
966 967 968 969 970 971 972
static void
zathura_page_widget_popup_menu(GtkWidget* widget, GdkEventButton* event)
{
  g_return_if_fail(widget != NULL);
  g_return_if_fail(event != NULL);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);

973 974 975
  if (priv->images.retrieved == false) {
    priv->images.list      = zathura_page_images_get(priv->page, NULL);
    priv->images.retrieved = true;
Moritz Lipp's avatar
Moritz Lipp committed
976 977
  }

978
  if (priv->images.list == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
979 980 981 982 983
    return;
  }

  /* search for underlaying image */
  zathura_image_t* image = NULL;
984
  GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
985 986 987 988
    zathura_rectangle_t rect = recalc_rectangle(priv->page, image_it->position);
    if (rect.x1 <= event->x && rect.x2 >= event->x && rect.y1 <= event->y && rect.y2 >= event->y) {
      image = image_it;
    }
989
  GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
Moritz Lipp's avatar
Moritz Lipp committed
990 991 992 993 994

  if (image == NULL) {
    return;
  }

995
  priv->images.current = image;
Moritz Lipp's avatar
Moritz Lipp committed
996 997 998 999 1000 1001 1002 1003 1004

  /* setup menu */
  GtkWidget* menu = gtk_menu_new();

  typedef struct menu_item_s {
    char* text;
    void (*callback)(GtkMenuItem*, ZathuraPage*);
  } menu_item_t;

1005
  const menu_item_t menu_items[] = {
Moritz Lipp's avatar
Moritz Lipp committed
1006 1007
    { _("Copy image"),    cb_menu_image_copy },
    { _("Save image as"), cb_menu_image_save },
Moritz Lipp's avatar
Moritz Lipp committed
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
  };

  for (unsigned int i = 0; i < LENGTH(menu_items); i++) {
    GtkWidget* item = gtk_menu_item_new_with_label(menu_items[i].text);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    gtk_widget_show(item);
    g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(menu_items[i].callback), ZATHURA_PAGE(widget));
  }

  /* attach and popup */
  int event_button = 0;
  int event_time   = gtk_get_current_event_time();

  if (event != NULL) {
    event_button = event->button;
    event_time   = event->time;
  }

  gtk_menu_attach_to_widget(GTK_MENU(menu), widget, NULL);
  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event_button, event_time);
}

static gboolean
cb_zathura_page_widget_popup_menu(GtkWidget* widget)
{
  zathura_page_widget_popup_menu(widget, NULL);

  return TRUE;
}

static void
cb_menu_image_copy(GtkMenuItem* item, ZathuraPage* page)
{
  g_return_if_fail(item != NULL);
  g_return_if_fail(page != NULL);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(page);
1044
  g_return_if_fail(priv->images.current != NULL);
Moritz Lipp's avatar
Moritz Lipp committed
1045

1046
  cairo_surface_t* surface = zathura_page_image_get_cairo(priv->page, priv->images.current, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
1047 1048 1049 1050
  if (surface == NULL) {
    return;
  }

1051 1052
  const int width  = cairo_image_surface_get_width(surface);
  const int height = cairo_image_surface_get_height(surface);
Moritz Lipp's avatar
Moritz Lipp committed
1053

1054
  GdkPixbuf* pixbuf