page-widget.c 34.5 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
    zathura_rectangle_t selection; /**< Region selected with the mouse */
    struct {
      int x; /**< X coordinate */
      int y; /**< Y coordinate */
    } selection_basepoint;
54
    bool over_link;
55
  } mouse;
56
} zathura_page_widget_private_t;
57

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

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

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

96
97
98
enum {
  TEXT_SELECTED,
  IMAGE_SELECTED,
99
100
  ENTER_LINK,
  LEAVE_LINK,
101
102
103
104
105
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

106
static void
107
zathura_page_widget_class_init(ZathuraPageClass* class)
108
109
{
  /* add private members */
110
  g_type_class_add_private(class, sizeof(zathura_page_widget_private_t));
111
112
113

  /* overwrite methods */
  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(class);
114
  widget_class->draw                 = zathura_page_widget_draw;
115
116
  widget_class->size_allocate        = zathura_page_widget_size_allocate;
  widget_class->button_press_event   = cb_zathura_page_widget_button_press_event;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
117
  widget_class->button_release_event = cb_zathura_page_widget_button_release_event;
118
  widget_class->motion_notify_event  = cb_zathura_page_widget_motion_notify;
119
  widget_class->leave_notify_event   = cb_zathura_page_widget_leave_notify;
Moritz Lipp's avatar
Moritz Lipp committed
120
  widget_class->popup_menu           = cb_zathura_page_widget_popup_menu;
121
122

  GObjectClass* object_class = G_OBJECT_CLASS(class);
123
  object_class->dispose      = zathura_page_widget_dispose;
124
  object_class->finalize     = zathura_page_widget_finalize;
125
126
  object_class->set_property = zathura_page_widget_set_property;
  object_class->get_property = zathura_page_widget_get_property;
127
128
129

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

  /* 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);
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

  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);
190
191
192
}

static void
193
zathura_page_widget_init(ZathuraPage* widget)
194
{
195
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
196
197
  priv->page             = NULL;
  priv->surface          = NULL;
198
  priv->render_request   = NULL;
199
  priv->cached           = false;
200
201
202
203
204
205
206
207
208

  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;
Marwan Tanager's avatar
Marwan Tanager committed
209
  priv->search.draw    = false;
210
211
212
213
214
215
216
217
218
219

  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;

220
221
222
  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);
223
224
225
}

GtkWidget*
226
zathura_page_widget_new(zathura_t* zathura, zathura_page_t* page)
227
228
229
{
  g_return_val_if_fail(page != NULL, NULL);

230
231
232
233
234
235
236
237
238
  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",
239
      G_CALLBACK(cb_update_surface), widget, 0);
240
241
242
243
  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);
244
245

  return GTK_WIDGET(ret);
246
247
}

248
249
250
251
252
253
254
255
256
257
258
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);
}

259
static void
260
zathura_page_widget_finalize(GObject* object)
261
{
262
263
  ZathuraPage* widget = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
264

Sebastian Ramacher's avatar
Sebastian Ramacher committed
265
  if (priv->surface != NULL) {
266
267
    cairo_surface_destroy(priv->surface);
  }
268

269
270
  if (priv->search.list != NULL) {
    girara_list_free(priv->search.list);
271
272
  }

273
274
  if (priv->links.list != NULL) {
    girara_list_free(priv->links.list);
275
276
  }

277
  G_OBJECT_CLASS(zathura_page_widget_parent_class)->finalize(object);
278
279
280
}

static void
281
zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
282
{
283
284
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
285
286
287

  switch (prop_id) {
    case PROP_PAGE:
288
289
290
291
      priv->page = g_value_get_pointer(value);
      break;
    case PROP_ZATHURA:
      priv->zathura = g_value_get_pointer(value);
292
      break;
Moritz Lipp's avatar
Moritz Lipp committed
293
    case PROP_DRAW_LINKS:
294
      priv->links.draw = g_value_get_boolean(value);
Moritz Lipp's avatar
Moritz Lipp committed
295
      /* get links */
296
297
298
299
      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
300
301
      }

302
303
      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
304
305
306
307
        if (link != NULL) {
          zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
          redraw_rect(pageview, &rectangle);
        }
308
        GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
309
310
      }
      break;
Moritz Lipp's avatar
Moritz Lipp committed
311
    case PROP_LINKS_OFFSET:
312
      priv->links.offset = g_value_get_int(value);
Moritz Lipp's avatar
Moritz Lipp committed
313
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
314
    case PROP_SEARCH_RESULTS:
315
316
317
      if (priv->search.list != NULL && priv->search.draw) {
        redraw_all_rects(pageview, priv->search.list);
        girara_list_free(priv->search.list);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
318
      }
319
320
321
322
      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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
323
      }
324
      priv->search.current = -1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
325
      break;
326
    case PROP_SEARCH_RESULTS_CURRENT: {
327
328
329
      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);
330
331
332
333
        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
334
      if (val < 0) {
335
        priv->search.current = girara_list_size(priv->search.list);
336
      } else {
337
        priv->search.current = val;
338
339
340
        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
341
342
          redraw_rect(pageview, &rectangle);
        }
343
344
345
      }
      break;
    }
Marwan Tanager's avatar
Marwan Tanager committed
346
    case PROP_DRAW_SEARCH_RESULTS:
347
      priv->search.draw = g_value_get_boolean(value);
Marwan Tanager's avatar
Marwan Tanager committed
348
349
350
351
352
353
354
355
356

      /*
       * 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
357
        gtk_widget_queue_draw(GTK_WIDGET(object));
Marwan Tanager's avatar
Marwan Tanager committed
358
      }
Moritz Lipp's avatar
Moritz Lipp committed
359
      break;
Moritz Lipp's avatar
Moritz Lipp committed
360
361
362
363
364
365
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

static void
366
zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
Moritz Lipp's avatar
Moritz Lipp committed
367
{
368
369
  ZathuraPage* pageview = ZATHURA_PAGE(object);
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
Moritz Lipp's avatar
Moritz Lipp committed
370
371
372

  switch (prop_id) {
    case PROP_LINKS_NUMBER:
373
      g_value_set_int(value, priv->links.n);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
374
      break;
375
    case PROP_SEARCH_RESULTS_LENGTH:
376
      g_value_set_int(value, priv->search.list == NULL ? 0 : girara_list_size(priv->search.list));
377
378
      break;
    case PROP_SEARCH_RESULTS_CURRENT:
379
      g_value_set_int(value, priv->search.list == NULL ? -1 : priv->search.current);
380
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
381
    case PROP_SEARCH_RESULTS:
382
      g_value_set_pointer(value, priv->search.list);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
383
      break;
Marwan Tanager's avatar
Marwan Tanager committed
384
385
386
    case PROP_DRAW_SEARCH_RESULTS:
      g_value_set_boolean(value, priv->search.draw);
      break;
387
388
389
390
391
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
  }
}

392
393
394
static gboolean
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
{
395
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
396

Sebastian Ramacher's avatar
Sebastian Ramacher committed
397
  zathura_document_t* document   = zathura_page_get_document(priv->page);
398
399
400
  const unsigned int page_height = gtk_widget_get_allocated_height(widget);
  const unsigned int page_width  = gtk_widget_get_allocated_width(widget);

401
402
403
  if (priv->surface != NULL) {
    cairo_save(cairo);

404
    unsigned int rotation = zathura_document_get_rotation(document);
405
    switch (rotation) {
406
407
408
409
410
411
412
413
414
415
416
      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;
    }

417
418
    if (rotation != 0) {
      cairo_rotate(cairo, rotation * G_PI / 180.0);
419
420
421
422
423
    }

    cairo_set_source_surface(cairo, priv->surface, 0, 0);
    cairo_paint(cairo);
    cairo_restore(cairo);
424
425
426
427
428
429
430
431
432
433
434

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
436
    g_free(font);
437

Moritz Lipp's avatar
Moritz Lipp committed
438
    /* draw links */
439
    if (priv->links.draw == true && priv->links.n != 0) {
Moritz Lipp's avatar
Moritz Lipp committed
440
      unsigned int link_counter = 0;
441
      GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
Moritz Lipp's avatar
Moritz Lipp committed
442
443
      if (link != NULL) {
        zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
Moritz Lipp's avatar
Moritz Lipp committed
444

Moritz Lipp's avatar
Moritz Lipp committed
445
        /* draw position */
446
447
        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
448
449
450
451
452
453
454
455
456
457
458
459
        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);
      }
460
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
461
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
462
463

    /* draw search results */
464
    if (priv->search.list != NULL && priv->search.draw == true) {
Moritz Lipp's avatar
Moritz Lipp committed
465
      int idx = 0;
466
      GIRARA_LIST_FOREACH(priv->search.list, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
467
      zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
468

Moritz Lipp's avatar
Moritz Lipp committed
469
470
      /* draw position */
      if (idx == priv->search.current) {
471
472
        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
473
      } else {
474
475
        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
476
477
478
479
480
      }
      cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
                      (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
      cairo_fill(cairo);
      ++idx;
481
      GIRARA_LIST_FOREACH_END(priv->search.list, zathura_rectangle_t*, iter, rect);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
482
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
483
    /* draw selection */
484
    if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
485
486
      const GdkRGBA color = priv->zathura->ui.colors.highlight_color;
      cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency);
487
      cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
Moritz Lipp's avatar
Moritz Lipp committed
488
                      (priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
489
      cairo_fill(cairo);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
490
    }
491
492
  } else {
    /* set background color */
493
    if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
494
      GdkRGBA color;
495
      zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, &color, NULL);
496
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
497
    } else {
498
499
      const GdkRGBA color = priv->zathura->ui.colors.render_loading_bg;
      cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
500
    }
501
    cairo_rectangle(cairo, 0, 0, page_width, page_height);
502
503
504
505
506
507
508
    cairo_fill(cairo);

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

    /* write text */
    if (render_loading == true) {
509
      if (zathura_renderer_recolor_enabled(priv->zathura->sync.render_thread) == true) {
510
        GdkRGBA color;
511
        zathura_renderer_get_recolor_colors(priv->zathura->sync.render_thread, NULL, &color);
512
        cairo_set_source_rgb(cairo, color.red, color.green, color.blue);
513
      } else {
514
        const GdkRGBA color = priv->zathura->ui.colors.render_loading_fg;
515
        cairo_set_source_rgb(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0);
516
517
      }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
518
      const char* text = _("Loading...");
519
520
521
522
      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);
523
524
      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);
525
526
527
528
529
      cairo_move_to(cairo, x, y);
      cairo_show_text(cairo, text);
    }

    /* render real page */
530
    zathura_render_request(priv->render_request, g_get_real_time());
531
532
533
534
535
  }
  return FALSE;
}

static void
536
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
537
538
539
540
541
542
{
  GtkWidget* widget = GTK_WIDGET(pageview);
  gtk_widget_queue_draw(widget);
}

void
543
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface)
544
{
545
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
CS    
Moritz Lipp committed
546
  if (priv->surface != NULL) {
547
    cairo_surface_destroy(priv->surface);
548
    priv->surface = NULL;
549
  }
550
  if (surface != NULL) {
551
552
    priv->surface = surface;
    cairo_surface_reference(surface);
553
  }
554
  /* force a redraw here */
555
556
557
  if (priv->surface != NULL) {
    zathura_page_widget_redraw_canvas(widget);
  }
558
559
}

560
static void
561
562
cb_update_surface(ZathuraRenderRequest* UNUSED(request),
    cairo_surface_t* surface, void* data)
563
564
565
566
567
568
{
  ZathuraPage* widget = data;
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
  zathura_page_widget_update_surface(widget, surface);
}

569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
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;
}

596
static void
597
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
598
{
599
  GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
600
601
602
603

  ZathuraPage* page = ZATHURA_PAGE(widget);
  zathura_page_widget_abort_render_request(page);
  zathura_page_widget_update_surface(page, NULL);
604
}
605
606

static void
607
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
608
{
Moritz Lipp's avatar
Moritz Lipp committed
609
  /* cause the rect to be drawn */
610
  GdkRectangle grect;
Moritz Lipp's avatar
Moritz Lipp committed
611
  grect.x = rectangle->x1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
612
  grect.y = rectangle->y1;
613
614
  grect.width  = (rectangle->x2 + 1) - rectangle->x1;
  grect.height = (rectangle->y2 + 1) - rectangle->y1;
615
  gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
616
617
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
618
static void
619
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
620
{
621
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
622
623

  GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect)
Moritz Lipp's avatar
Moritz Lipp committed
624
625
  zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
  redraw_rect(widget, &rectangle);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
626
627
628
  GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect);
}

Moritz Lipp's avatar
Moritz Lipp committed
629
zathura_link_t*
630
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
631
{
Moritz Lipp's avatar
Moritz Lipp committed
632
  g_return_val_if_fail(widget != NULL, NULL);
633
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
634
  g_return_val_if_fail(priv != NULL, NULL);
635

636
637
638
  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
639
640
641
  } else {
    return NULL;
  }
642
}
Moritz Lipp's avatar
Moritz Lipp committed
643

644
645
static gboolean
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
Moritz Lipp's avatar
Moritz Lipp committed
646
647
648
649
650
651
{
  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);

652
653
654
655
  if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
    return true;
  }

Moritz Lipp's avatar
Moritz Lipp committed
656
657
658
  if (button->button == 1) { /* left click */
    if (button->type == GDK_BUTTON_PRESS) {
      /* start the selection */
659
660
661
662
663
664
665
      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
666
667
    } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
      /* abort the selection */
668
669
670
671
672
673
674
      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
675
676
677
678
679
680
    }

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
683
684
685
686
687
688
689
690
  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
691
  if (button->type != GDK_BUTTON_RELEASE || button->button != 1) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
692
693
694
    return false;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
695
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
Moritz Lipp's avatar
Moritz Lipp committed
696
  zathura_document_t* document        = zathura_page_get_document(priv->page);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
697

698
  if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
699
    /* simple single click */
Moritz Lipp's avatar
Moritz Lipp committed
700
    /* get links */
701
702
703
704
    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
705
706
    }

707
708
    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
709
710
711
712
713
      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);
      }
714
      GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
Moritz Lipp's avatar
Moritz Lipp committed
715
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
716
  } else {
717
    redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Moritz Lipp's avatar
Moritz Lipp committed
718

Moritz Lipp's avatar
Moritz Lipp committed
719
720
721
722
    bool synctex = false;
    girara_setting_get(priv->zathura->ui.session, "synctex", &synctex);

    if (synctex == true && button->state & GDK_CONTROL_MASK) {
723
724
725
      /* synctex backwards sync */
      double scale = zathura_document_get_scale(document);
      int x = button->x / scale, y = button->y / scale;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
726

727
728
729
730
731
732
733
734
735
736
737
738
739
      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) {
740
741
          /* emit text-selected signal */
          g_signal_emit(ZATHURA_PAGE(widget), signals[TEXT_SELECTED], 0, text);
742
743
        }

744
745
        g_free(text);
      }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
746
747
    }
  }
Moritz Lipp's avatar
Moritz Lipp committed
748

749
750
751
752
753
754
755
  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;
756

Sebastian Ramacher's avatar
Sebastian Ramacher committed
757
758
759
760
761
762
763
764
  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);
765

Sebastian Ramacher's avatar
Sebastian Ramacher committed
766
  if ((event->state & GDK_BUTTON1_MASK) == 0) {
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
    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;
      }
    }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
793
794
795
796
    return false;
  }

  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
797
798
  zathura_rectangle_t tmp = priv->mouse.selection;
  if (event->x < priv->mouse.selection_basepoint.x) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
799
    tmp.x1 = event->x;
800
    tmp.x2 = priv->mouse.selection_basepoint.x;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
801
802
  } else {
    tmp.x2 = event->x;
803
    tmp.x1 = priv->mouse.selection_basepoint.x;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
804
  }
805
  if (event->y < priv->mouse.selection_basepoint.y) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
806
    tmp.y1 = event->y;
807
    tmp.y2 = priv->mouse.selection_basepoint.y;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
808
  } else {
809
    tmp.y1 = priv->mouse.selection_basepoint.y;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
810
    tmp.y2 = event->y;
Moritz Lipp's avatar
Moritz Lipp committed
811
812
  }

813
  redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
814
  redraw_rect(ZATHURA_PAGE(widget), &tmp);
815
  priv->mouse.selection = tmp;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
816

Moritz Lipp's avatar
Moritz Lipp committed
817
818
  return false;
}
Moritz Lipp's avatar
Moritz Lipp committed
819

820
821
822
823
824
825
826
827
828
829
830
831
832
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
833
834
835
836
837
838
839
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);

840
841
842
  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
843
844
  }

845
  if (priv->images.list == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
846
847
848
849
850
    return;
  }

  /* search for underlaying image */
  zathura_image_t* image = NULL;
851
  GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
Moritz Lipp's avatar
Moritz Lipp committed
852
853
854
855
  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;
  }
856
  GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
Moritz Lipp's avatar
Moritz Lipp committed
857
858
859
860
861

  if (image == NULL) {
    return;
  }

862
  priv->images.current = image;
Moritz Lipp's avatar
Moritz Lipp committed
863
864
865
866
867
868
869
870
871
872

  /* 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
873
874
    { _("Copy image"),    cb_menu_image_copy },
    { _("Save image as"), cb_menu_image_save },
Moritz Lipp's avatar
Moritz Lipp committed
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
  };

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

913
  cairo_surface_t* surface = zathura_page_image_get_cairo(priv->page, priv->images.current, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
914
915
916
917
  if (surface == NULL) {
    return;
  }

918
919
  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
920

921
  GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
922
923
  g_signal_emit(page, signals[IMAGE_SELECTED], 0, pixbuf);
  g_object_unref(pixbuf);
924
  cairo_surface_destroy(surface);
Moritz Lipp's avatar
Moritz Lipp committed
925

Moritz Lipp's avatar
Moritz Lipp committed
926
  /* reset */
927
  priv->images.current = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
928
}
929

Moritz Lipp's avatar
Moritz Lipp committed
930
931
932
933
934
935
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);
936
937
  g_return_if_fail(priv->images.current != NULL);
  g_return_if_fail(priv->images.list != NULL);
Moritz Lipp's avatar
Moritz Lipp committed
938
939
940
941
942

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

943
  GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
944
945
946
    if (image_it == priv->images.current) {
      break;
    }
Moritz Lipp's avatar
Moritz Lipp committed
947

948
    image_id++;
949
  GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
Moritz Lipp's avatar
Moritz Lipp committed
950
951
952
953
954
955
956
957

  /* 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 */
958
  priv->images.current = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
959
960
}

961
962
963
void
zathura_page_widget_update_view_time(ZathuraPage* widget)
{
964
  g_return_if_fail(ZATHURA_IS_PAGE(widget));
965
966
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);

Moritz Lipp's avatar
Moritz Lipp committed
967
  if (zathura_page_get_visibility(priv->page) == true) {
968
    zathura_render_request_update_view_time(priv->render_request);
969
970
  }
}
971
972
973
974

bool
zathura_page_widget_have_surface(ZathuraPage* widget)
{
975
  g_return_val_if_fail(ZATHURA_IS_PAGE(widget), false);
976
977
978
979
  zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
  return priv->surface != NULL;
}

980
981
982
983
984
985
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);
986
987
988
989
990
991
992
993
994

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