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

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

Moritz Lipp's avatar
Moritz Lipp committed
10
#include "links.h"
11
#include "page-widget.h"
Moritz Lipp's avatar
Moritz Lipp committed
12
#include "page.h"
13 14 15
#include "render.h"
#include "utils.h"
#include "shortcuts.h"
16
#include "synctex.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
  ZathuraRenderRequest* render_request; /* Request object */
26
  bool cached; /**< Cached state */
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

  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;

48
  struct {
49 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;
  } mouse;
55
} zathura_page_widget_private_t;
56

57
#define ZATHURA_PAGE_GET_PRIVATE(obj) \
58 59
  (G_TYPE_INSTANCE_GET_PRIVATE((obj), ZATHURA_TYPE_PAGE, \
                               zathura_page_widget_private_t))
60

61
static gboolean zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo);
62
static void zathura_page_widget_finalize(GObject* object);
63
static void zathura_page_widget_dispose(GObject* object);
64 65 66 67 68
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
69
static void zathura_page_widget_popup_menu(GtkWidget* widget, GdkEventButton* event);
70
static gboolean cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button);
71 72
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);
Moritz Lipp's avatar
Moritz Lipp committed
73 74
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
75
static void cb_menu_image_save(GtkMenuItem* item, ZathuraPage* page);
76
static void cb_update_surface(ZathuraRenderRequest* request, cairo_surface_t* surface, void* data);
77 78
static void cb_cache_added(ZathuraRenderRequest* request, void* data);
static void cb_cache_invalidated(ZathuraRenderRequest* request, void* data);
79

Moritz Lipp's avatar
Moritz Lipp committed
80
enum properties_e {
81
  PROP_0,
Moritz Lipp's avatar
Moritz Lipp committed
82
  PROP_PAGE,
83
  PROP_ZATHURA,
Moritz Lipp's avatar
Moritz Lipp committed
84
  PROP_DRAW_LINKS,
Moritz Lipp's avatar
Moritz Lipp committed
85 86
  PROP_LINKS_OFFSET,
  PROP_LINKS_NUMBER,
87 88
  PROP_SEARCH_RESULTS,
  PROP_SEARCH_RESULTS_LENGTH,
Moritz Lipp's avatar
Moritz Lipp committed
89
  PROP_SEARCH_RESULTS_CURRENT,
90
  PROP_DRAW_SEARCH_RESULTS,
91
  PROP_LAST_VIEW,
92 93
};

94 95 96 97 98 99 100 101
enum {
  TEXT_SELECTED,
  IMAGE_SELECTED,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

102
static void
103
zathura_page_widget_class_init(ZathuraPageClass* class)
104 105
{
  /* add private members */
106
  g_type_class_add_private(class, sizeof(zathura_page_widget_private_t));
107 108 109

  /* overwrite methods */
  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(class);
110
  widget_class->draw                 = zathura_page_widget_draw;
111 112
  widget_class->size_allocate        = zathura_page_widget_size_allocate;
  widget_class->button_press_event   = cb_zathura_page_widget_button_press_event;
113
  widget_class->button_release_event = cb_zathura_page_widget_button_release_event;
114
  widget_class->motion_notify_event  = cb_zathura_page_widget_motion_notify;
Moritz Lipp's avatar
Moritz Lipp committed
115
  widget_class->popup_menu           = cb_zathura_page_widget_popup_menu;
116 117

  GObjectClass* object_class = G_OBJECT_CLASS(class);
118
  object_class->dispose      = zathura_page_widget_dispose;
119
  object_class->finalize     = zathura_page_widget_finalize;
120 121
  object_class->set_property = zathura_page_widget_set_property;
  object_class->get_property = zathura_page_widget_get_property;
122 123 124

  /* add properties */
  g_object_class_install_property(object_class, PROP_PAGE,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
125
                                  g_param_spec_pointer("page", "page", "the page to draw", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
126
  g_object_class_install_property(object_class, PROP_ZATHURA,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
127
                                  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
128
  g_object_class_install_property(object_class, PROP_DRAW_LINKS,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
129
                                  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
130
  g_object_class_install_property(object_class, PROP_LINKS_OFFSET,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
131
                                  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
132
  g_object_class_install_property(object_class, PROP_LINKS_NUMBER,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
133
                                  g_param_spec_int("number-of-links", "number-of-links", "Number of links", 0, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
134
  g_object_class_install_property(object_class, PROP_SEARCH_RESULTS,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
135
                                  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));
136
  g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_CURRENT,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
137
                                  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));
138
  g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_LENGTH,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
139
                                  g_param_spec_int("search-length", "search-length", "The number of search results", -1, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
140 141
  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));
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

  /* 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);
165 166 167
}

static void
168
zathura_page_widget_init(ZathuraPage* widget)
169
{
170
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
171 172
  priv->page             = NULL;
  priv->surface          = NULL;
173
  priv->render_request   = NULL;
174
  priv->cached           = false;
175 176 177 178 179 180 181 182 183

  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;
184
  priv->search.draw    = false;
185 186 187 188 189 190 191 192 193 194

  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;

195 196
  /* we want mouse events */
  gtk_widget_add_events(GTK_WIDGET(widget),
Moritz Lipp's avatar
Moritz Lipp committed
197
                        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
198 199 200
}

GtkWidget*
201
zathura_page_widget_new(zathura_t* zathura, zathura_page_t* page)
202 203 204
{
  g_return_val_if_fail(page != NULL, NULL);

205 206 207 208 209 210 211 212 213
  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",
214
      G_CALLBACK(cb_update_surface), widget, 0);
215 216 217 218
  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);
219 220

  return GTK_WIDGET(ret);
221 222
}

223 224 225 226 227 228 229 230 231 232 233
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);
}

234
static void
235
zathura_page_widget_finalize(GObject* object)
236
{
237 238
  ZathuraPage* widget = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
239

Sebastian Ramacher's avatar
Sebastian Ramacher committed
240
  if (priv->surface != NULL) {
241 242
    cairo_surface_destroy(priv->surface);
  }
243

244 245
  if (priv->search.list != NULL) {
    girara_list_free(priv->search.list);
246 247
  }

248 249
  if (priv->links.list != NULL) {
    girara_list_free(priv->links.list);
250 251
  }

252
  G_OBJECT_CLASS(zathura_page_widget_parent_class)->finalize(object);
253 254 255
}

static void
256
zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
257
{
258 259
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
260 261 262

  switch (prop_id) {
    case PROP_PAGE:
263 264 265 266
      priv->page = g_value_get_pointer(value);
      break;
    case PROP_ZATHURA:
      priv->zathura = g_value_get_pointer(value);
267
      break;
Moritz Lipp's avatar
Moritz Lipp committed
268
    case PROP_DRAW_LINKS:
269
      priv->links.draw = g_value_get_boolean(value);
Moritz Lipp's avatar
Moritz Lipp committed
270
      /* get links */
271 272 273 274
      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
275 276
      }

277 278
      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
279 280 281 282
        if (link != NULL) {
          zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
          redraw_rect(pageview, &rectangle);
        }
283
        GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
284 285
      }
      break;
Moritz Lipp's avatar
Moritz Lipp committed
286
    case PROP_LINKS_OFFSET:
287
      priv->links.offset = g_value_get_int(value);
Moritz Lipp's avatar
Moritz Lipp committed
288
      break;
289
    case PROP_SEARCH_RESULTS:
290 291 292
      if (priv->search.list != NULL && priv->search.draw) {
        redraw_all_rects(pageview, priv->search.list);
        girara_list_free(priv->search.list);
293
      }
294 295 296 297
      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);
298
      }
299
      priv->search.current = -1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
300
      break;
301
    case PROP_SEARCH_RESULTS_CURRENT: {
302 303 304
      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);
305 306 307 308
        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
309
      if (val < 0) {
310
        priv->search.current = girara_list_size(priv->search.list);
311
      } else {
312 313
        priv->search.current = val;
        zathura_rectangle_t* rect = girara_list_nth(priv->search.list, priv->search.current);
314
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
315
        if (priv->search.draw) {
Moritz Lipp's avatar
Moritz Lipp committed
316 317
          redraw_rect(pageview, &rectangle);
        }
318 319 320
      }
      break;
    }
321
    case PROP_DRAW_SEARCH_RESULTS:
322
      priv->search.draw = g_value_get_boolean(value);
323 324 325 326 327 328 329 330 331

      /*
       * 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
332
        gtk_widget_queue_draw(GTK_WIDGET(object));
333
      }
Moritz Lipp's avatar
Moritz Lipp committed
334
      break;
Moritz Lipp's avatar
Moritz Lipp committed
335 336 337 338 339 340
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

static void
341
zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
Moritz Lipp's avatar
Moritz Lipp committed
342
{
343 344
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
Moritz Lipp's avatar
Moritz Lipp committed
345 346 347

  switch (prop_id) {
    case PROP_LINKS_NUMBER:
348
      g_value_set_int(value, priv->links.n);
349
      break;
350
    case PROP_SEARCH_RESULTS_LENGTH:
351
      g_value_set_int(value, priv->search.list == NULL ? 0 : girara_list_size(priv->search.list));
352 353
      break;
    case PROP_SEARCH_RESULTS_CURRENT:
354
      g_value_set_int(value, priv->search.list == NULL ? -1 : priv->search.current);
355
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
356
    case PROP_SEARCH_RESULTS:
357
      g_value_set_pointer(value, priv->search.list);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
358
      break;
359 360 361
    case PROP_DRAW_SEARCH_RESULTS:
      g_value_set_boolean(value, priv->search.draw);
      break;
362 363 364 365 366
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

367 368 369
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
370
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
371

Sebastian Ramacher's avatar
Sebastian Ramacher committed
372
  zathura_document_t* document   = zathura_page_get_document(priv->page);
373 374 375
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

376 377 378
  if (priv->surface != NULL) {
    cairo_save(cairo);

379
    unsigned int rotation = zathura_document_get_rotation(document);
380
    switch (rotation) {
381 382 383 384 385 386 387 388 389 390 391
      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;
    }

392 393
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
394 395 396 397 398
    }

    cairo_set_source_surface(cairo, priv->surface, 0, 0);
    cairo_paint(cairo);
    cairo_restore(cairo);
399 400 401 402 403 404 405 406 407 408 409

    /* 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);
    }
410

411
    g_free(font);
412

Moritz Lipp's avatar
Moritz Lipp committed
413
    /* draw links */
414
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
415
      unsigned int link_counter = 0;
416
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
417 418
      if (link != NULL) {
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
Moritz Lipp's avatar
Moritz Lipp committed
419

Moritz Lipp's avatar
Moritz Lipp committed
420
        /* draw position */
421 422
        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
423 424 425 426 427 428 429 430 431 432 433 434
        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);
      }
435
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
436
    }
437 438

    /* draw search results */
439
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
440
      int idx = 0;
441
      GIRARA_LIST_FOREACH(priv->search.list, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
442
      zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
443

Moritz Lipp's avatar
Moritz Lipp committed
444 445
      /* draw position */
      if (idx == priv->search.current) {
446 447
        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
448
      } else {
449 450
        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
451 452 453 454 455
      }
      cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                      (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
      cairo_fill(cairo);
      ++idx;
456
      GIRARA_LIST_FOREACH_END(priv->search.list, zathura_rectangle_t*, iter, rect);
457
    }
458
    /* draw selection */
459
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
460 461
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
462
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
463
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
464
      cairo_fill(cairo);
465
    }
466 467
  } else {
    /* set background color */
468
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
469
      GdkRGBA color;
470
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
471
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
472
    } else {
473 474
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
475
    }
476
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
477 478 479 480 481 482 483
    cairo_fill(cairo);

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

    /* write text */
    if (render_loading == true) {
484
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
485
        GdkRGBA color;
486
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
487
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
488
      } else {
489
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
490
        cairo_set_source_rgb(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0);
491 492
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
493
      const char* text = _("Loading...");
494 495 496 497
      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);
498 499
      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);
500 501 502 503 504
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
505
    zathura_render_request(priv->render_request, g_get_real_time());
506 507 508 509 510
  }
  return FALSE;
}

static void
511
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
512 513 514 515 516 517
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

void
518
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface)
519
{
520
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
CS  
Moritz Lipp committed
521
  if (priv->surface != NULL) {
522
    cairo_surface_destroy(priv->surface);
523
    priv->surface = NULL;
524
  }
525
  if (surface != NULL) {
526 527
    priv->surface = surface;
    cairo_surface_reference(surface);
528
  }
529
  /* force a redraw here */
530 531 532
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
533 534
}

535
static void
536 537
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
538 539 540 541 542 543
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
  zathura_page_widget_update_surface(widget, surface);
}

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
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. */
    zathura_page_widget_update_surface(widget, NULL);
  }
  priv->cached = false;
}

571
static void
572
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
573
{
574
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
575 576 577 578

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
  zathura_page_widget_update_surface(page, NULL);
579
}
580 581

static void
582
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
583
{
Moritz Lipp's avatar
Moritz Lipp committed
584
  /* cause the rect to be drawn */
585
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
586
  grect.x = rectangle->x1;
587
  grect.y = rectangle->y1;
588 589
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
590
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
591 592
}

593
static void
594
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
595
{
596
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
597 598

  GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
599 600
  zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
  redraw_rect(widget, &rectangle);
601 602 603
  GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect);
}

Moritz Lipp's avatar
Moritz Lipp committed
604
zathura_link_t*
605
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
606
{
Moritz Lipp's avatar
Moritz Lipp committed
607
  g_return_val_if_fail(widget != NULL, NULL);
608
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
609
  g_return_val_if_fail(priv != NULL, NULL);
610

611 612 613
  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
614 615 616
  } else {
    return NULL;
  }
617
}
Moritz Lipp's avatar
Moritz Lipp committed
618

619 620
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
621 622 623 624 625 626
{
  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);

627 628 629 630
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

Moritz Lipp's avatar
Moritz Lipp committed
631 632 633
  if (button->button == 1) { /* left click */
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
634 635 636 637 638 639 640
      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
641 642
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
643 644 645 646 647 648 649
      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
650 651 652 653 654 655
    }

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

658 659 660 661 662 663 664 665
  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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
666
  if (button->type != GDK_BUTTON_RELEASE || button->button != 1) {
667 668 669
    return false;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
670
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
671
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
672

673
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
674
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
675
    /* get links */
676 677 678 679
    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
680 681
    }

682 683
    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
684 685 686 687 688
      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);
      }
689
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
690
    }
691
  } else {
692
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
693

Moritz Lipp's avatar
Moritz Lipp committed
694 695 696 697
    bool synctex = false;
    girara_setting_get(priv->zathura->ui.session, "synctex", &synctex);

    if (synctex == true && button->state & GDK_CONTROL_MASK) {
698 699 700
      /* synctex backwards sync */
      double scale = zathura_document_get_scale(document);
      int x = button->x / scale, y = button->y / scale;
701

702 703 704 705 706 707 708 709 710 711 712 713 714
      synctex_edit(priv->zathura, priv->page, x, y);
    } else {
      zathura_rectangle_t tmp = priv->mouse.selection;

      double scale = zathura_document_get_scale(document);
      tmp.x1 /= scale;
      tmp.x2 /= scale;
      tmp.y1 /= scale;
      tmp.y2 /= scale;

      char* text = zathura_page_get_text(priv->page, tmp, NULL);
      if (text != NULL) {
        if (strlen(text) > 0) {
715 716
          /* emit text-selected signal */
          g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
717 718
        }

719 720
        g_free(text);
      }
721 722
    }
  }
Moritz Lipp's avatar
Moritz Lipp committed
723

724 725 726 727 728 729 730
  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;
731

732 733 734 735 736 737 738 739 740 741 742 743 744
  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);
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
745 746
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
747
    tmp.x1 = event->x;
748
    tmp.x2 = priv->mouse.selection_basepoint.x;
749 750
  } else {
    tmp.x2 = event->x;
751
    tmp.x1 = priv->mouse.selection_basepoint.x;
752
  }
753
  if (event->y < priv->mouse.selection_basepoint.y) {
754
    tmp.y1 = event->y;
755
    tmp.y2 = priv->mouse.selection_basepoint.y;
756
  } else {
757
    tmp.y1 = priv->mouse.selection_basepoint.y;
758
    tmp.y2 = event->y;
Moritz Lipp's avatar
Moritz Lipp committed
759 760
  }

761
  redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
762
  redraw_rect(ZATHURA_PAGE(widget), &tmp);
763
  priv->mouse.selection = tmp;
764

Moritz Lipp's avatar
Moritz Lipp committed
765 766
  return false;
}
Moritz Lipp's avatar
Moritz Lipp committed
767 768 769 770 771 772 773 774

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

775 776 777
  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
778 779
  }

780
  if (priv->images.list == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
781 782 783 784 785
    return;
  }

  /* search for underlaying image */
  zathura_image_t* image = NULL;
786
  GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
Moritz Lipp's avatar
Moritz Lipp committed
787 788 789 790
  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;
  }
791
  GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
Moritz Lipp's avatar
Moritz Lipp committed
792 793 794 795 796

  if (image == NULL) {
    return;
  }

797
  priv->images.current = image;
Moritz Lipp's avatar
Moritz Lipp committed
798 799 800 801 802 803 804 805 806 807

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

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

  menu_item_t menu_items[] = {
Moritz Lipp's avatar
Moritz Lipp committed
808 809
    { _("Copy image"),    cb_menu_image_copy },
    { _("Save image as"), cb_menu_image_save },
Moritz Lipp's avatar
Moritz Lipp committed
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
  };

  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);
846
  g_return_if_fail(priv->images.current != NULL);
Moritz Lipp's avatar
Moritz Lipp committed
847

848
  cairo_surface_t* surface = zathura_page_image_get_cairo(priv->page, priv->images.current, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
849 850 851 852
  if (surface == NULL) {
    return;
  }

853 854
  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
855

856
  GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
857 858
  g_signal_emit(page, signals[IMAGE_SELECTED], 0, pixbuf);
  g_object_unref(pixbuf);
859
  cairo_surface_destroy(surface);
Moritz Lipp's avatar
Moritz Lipp committed
860

Moritz Lipp's avatar
Moritz Lipp committed
861
  /* reset */
862
  priv->images.current = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
863
}
864

Moritz Lipp's avatar
Moritz Lipp committed
865 866 867 868 869 870
static void
cb_menu_image_save(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);
871 872
  g_return_if_fail(priv->images.current != NULL);
  g_return_if_fail(priv->images.list != NULL);
Moritz Lipp's avatar
Moritz Lipp committed
873 874 875 876 877

  /* generate image identifier */
  unsigned int page_id  = zathura_page_get_index(priv->page) + 1;
  unsigned int image_id = 1;

878
  GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
879 880 881
    if (image_it == priv->images.current) {
      break;
    }
Moritz Lipp's avatar
Moritz Lipp committed
882

883
    image_id++;
884
  GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
Moritz Lipp's avatar
Moritz Lipp committed
885 886 887 888 889 890 891 892

  /* set command */
  char* export_command = g_strdup_printf(":export image-p%d-%d ", page_id, image_id);
  girara_argument_t argument = { 0, export_command };
  sc_focus_inputbar(priv->zathura->ui.session, &argument, NULL, 0);
  g_free(export_command);

  /* reset */
893
  priv->images.current = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
894 895
}

896 897 898
void
zathura_page_widget_update_view_time(ZathuraPage* widget)
{
899
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
900 901
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);

Moritz Lipp's avatar
Moritz Lipp committed
902
  if (zathura_page_get_visibility(priv->page) == true) {
903
    zathura_render_request_update_view_time(priv->render_request);
904 905
  }
}
906 907 908 909

bool
zathura_page_widget_have_surface(ZathuraPage* widget)
{
910
  g_return_val_if_fail(ZATHURA_IS_PAGE(widget), false);
911 912 913 914
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  return priv->surface != NULL;
}

915 916 917 918 919 920
void
zathura_page_widget_abort_render_request(ZathuraPage* widget)
{
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  zathura_render_request_abort(priv->render_request);
921 922 923 924 925 926 927 928 929

  /* Make sure that if we are not cached and invisible, that there is no
   * surface.
   *
   * TODO: Maybe this should be moved somewhere else. */
  if (zathura_page_widget_have_surface(widget) == true &&
      priv->cached == false) {
    zathura_page_widget_update_surface(widget, NULL);
  }
930 931
}