mirror of
https://git.pwmt.org/pwmt/zathura.git
synced 2024-12-29 15:36:00 +01:00
cb18fe8603
This patch implements a page cache that is invalidated in a LRU fashion. Pages are added to the cache as soon as they become visible. When the cache is full and a new page that isn't in the cache becomes visible, the least recently viewed page in the cache is evicted from memory and the new one takes it's place. The cache size is configurable using the page-cache-size configuration variable, with a default value of 15 pages. Very large values for the cache size are not recommended, though, as it will stress the system memory out. The old periodic page reclaiming code is no longer necessary with this patch, so I removed it. Special thanks to Ignas Anikevičius, and Sebastian Ramacher for the inspirations. Signed-off-by: Sebastian Ramacher <sebastian+dev@ramacher.at>
862 lines
30 KiB
C
862 lines
30 KiB
C
/* See LICENSE file for license and copyright information */
|
|
|
|
#include <girara/utils.h>
|
|
#include <girara/settings.h>
|
|
#include <girara/datastructures.h>
|
|
#include <girara/session.h>
|
|
#include <string.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "glib-compat.h"
|
|
#include "links.h"
|
|
#include "page-widget.h"
|
|
#include "page.h"
|
|
#include "render.h"
|
|
#include "utils.h"
|
|
#include "shortcuts.h"
|
|
#include "synctex.h"
|
|
|
|
G_DEFINE_TYPE(ZathuraPage, zathura_page_widget, GTK_TYPE_DRAWING_AREA)
|
|
|
|
typedef struct zathura_page_widget_private_s {
|
|
zathura_page_t* page; /**< Page object */
|
|
zathura_t* zathura; /**< Zathura object */
|
|
cairo_surface_t* surface; /**< Cairo surface */
|
|
bool render_requested; /**< No surface and rendering has been requested */
|
|
gint64 last_view; /**< Last time the page has been viewed */
|
|
mutex lock; /**< Lock */
|
|
|
|
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;
|
|
|
|
struct {
|
|
zathura_rectangle_t selection; /**< Region selected with the mouse */
|
|
struct {
|
|
int x; /**< X coordinate */
|
|
int y; /**< Y coordinate */
|
|
} selection_basepoint;
|
|
} mouse;
|
|
} zathura_page_widget_private_t;
|
|
|
|
#define ZATHURA_PAGE_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), ZATHURA_TYPE_PAGE, zathura_page_widget_private_t))
|
|
|
|
static gboolean zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo);
|
|
#if GTK_MAJOR_VERSION == 2
|
|
static gboolean zathura_page_widget_expose(GtkWidget* widget, GdkEventExpose* event);
|
|
#endif
|
|
static void zathura_page_widget_finalize(GObject* object);
|
|
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);
|
|
static void zathura_page_widget_popup_menu(GtkWidget* widget, GdkEventButton* event);
|
|
static gboolean cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button);
|
|
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);
|
|
static gboolean cb_zathura_page_widget_popup_menu(GtkWidget* widget);
|
|
static void cb_menu_image_copy(GtkMenuItem* item, ZathuraPage* page);
|
|
static void cb_menu_image_save(GtkMenuItem* item, ZathuraPage* page);
|
|
|
|
enum properties_e {
|
|
PROP_0,
|
|
PROP_PAGE,
|
|
PROP_ZATHURA,
|
|
PROP_DRAW_LINKS,
|
|
PROP_LINKS_OFFSET,
|
|
PROP_LINKS_NUMBER,
|
|
PROP_SEARCH_RESULTS,
|
|
PROP_SEARCH_RESULTS_LENGTH,
|
|
PROP_SEARCH_RESULTS_CURRENT,
|
|
PROP_DRAW_SEACH_RESULTS,
|
|
PROP_LAST_VIEW,
|
|
};
|
|
|
|
static void
|
|
zathura_page_widget_class_init(ZathuraPageClass* class)
|
|
{
|
|
/* add private members */
|
|
g_type_class_add_private(class, sizeof(zathura_page_widget_private_t));
|
|
|
|
/* overwrite methods */
|
|
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(class);
|
|
#if GTK_MAJOR_VERSION == 3
|
|
widget_class->draw = zathura_page_widget_draw;
|
|
#else
|
|
widget_class->expose_event = zathura_page_widget_expose;
|
|
#endif
|
|
widget_class->size_allocate = zathura_page_widget_size_allocate;
|
|
widget_class->button_press_event = cb_zathura_page_widget_button_press_event;
|
|
widget_class->button_release_event = cb_zathura_page_widget_button_release_event;
|
|
widget_class->motion_notify_event = cb_zathura_page_widget_motion_notify;
|
|
widget_class->popup_menu = cb_zathura_page_widget_popup_menu;
|
|
|
|
GObjectClass* object_class = G_OBJECT_CLASS(class);
|
|
object_class->finalize = zathura_page_widget_finalize;
|
|
object_class->set_property = zathura_page_widget_set_property;
|
|
object_class->get_property = zathura_page_widget_get_property;
|
|
|
|
/* add properties */
|
|
g_object_class_install_property(object_class, PROP_PAGE,
|
|
g_param_spec_pointer("page", "page", "the page to draw", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_ZATHURA,
|
|
g_param_spec_pointer("zathura", "zathura", "the zathura instance", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_DRAW_LINKS,
|
|
g_param_spec_boolean("draw-links", "draw-links", "Set to true if links should be drawn", FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_LINKS_OFFSET,
|
|
g_param_spec_int("offset-links", "offset-links", "Offset for the link numbers", 0, INT_MAX, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_LINKS_NUMBER,
|
|
g_param_spec_int("number-of-links", "number-of-links", "Number of links", 0, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_SEARCH_RESULTS,
|
|
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));
|
|
g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_CURRENT,
|
|
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));
|
|
g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_LENGTH,
|
|
g_param_spec_int("search-length", "search-length", "The number of search results", -1, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_DRAW_SEACH_RESULTS,
|
|
g_param_spec_boolean("draw-search-results", "draw-search-results", "Set to true if search results should be drawn", FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class, PROP_LAST_VIEW,
|
|
g_param_spec_int("last-view", "last-view", "Last time the page has been viewed", -1, INT_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
zathura_page_widget_init(ZathuraPage* widget)
|
|
{
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
priv->page = NULL;
|
|
priv->surface = NULL;
|
|
priv->render_requested = false;
|
|
priv->last_view = g_get_real_time();
|
|
|
|
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;
|
|
priv->search.draw = true;
|
|
|
|
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;
|
|
|
|
mutex_init(&(priv->lock));
|
|
|
|
/* we want mouse events */
|
|
gtk_widget_add_events(GTK_WIDGET(widget),
|
|
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
|
|
}
|
|
|
|
GtkWidget*
|
|
zathura_page_widget_new(zathura_t* zathura, zathura_page_t* page)
|
|
{
|
|
g_return_val_if_fail(page != NULL, NULL);
|
|
|
|
return g_object_new(ZATHURA_TYPE_PAGE, "page", page, "zathura", zathura, NULL);
|
|
}
|
|
|
|
static void
|
|
zathura_page_widget_finalize(GObject* object)
|
|
{
|
|
ZathuraPage* widget = ZATHURA_PAGE(object);
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
|
|
if (priv->surface != NULL) {
|
|
cairo_surface_destroy(priv->surface);
|
|
}
|
|
|
|
if (priv->search.list != NULL) {
|
|
girara_list_free(priv->search.list);
|
|
}
|
|
|
|
if (priv->links.list != NULL) {
|
|
girara_list_free(priv->links.list);
|
|
}
|
|
|
|
mutex_free(&(priv->lock));
|
|
|
|
G_OBJECT_CLASS(zathura_page_widget_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
|
|
{
|
|
ZathuraPage* pageview = ZATHURA_PAGE(object);
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PAGE:
|
|
priv->page = g_value_get_pointer(value);
|
|
break;
|
|
case PROP_ZATHURA:
|
|
priv->zathura = g_value_get_pointer(value);
|
|
break;
|
|
case PROP_DRAW_LINKS:
|
|
priv->links.draw = g_value_get_boolean(value);
|
|
/* get links */
|
|
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);
|
|
}
|
|
|
|
if (priv->links.retrieved == true && priv->links.list != NULL) {
|
|
GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
|
|
if (link != NULL) {
|
|
zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
|
|
redraw_rect(pageview, &rectangle);
|
|
}
|
|
GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
|
|
}
|
|
break;
|
|
case PROP_LINKS_OFFSET:
|
|
priv->links.offset = g_value_get_int(value);
|
|
break;
|
|
case PROP_SEARCH_RESULTS:
|
|
if (priv->search.list != NULL && priv->search.draw) {
|
|
redraw_all_rects(pageview, priv->search.list);
|
|
girara_list_free(priv->search.list);
|
|
}
|
|
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);
|
|
}
|
|
priv->search.current = -1;
|
|
break;
|
|
case PROP_SEARCH_RESULTS_CURRENT: {
|
|
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);
|
|
zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
|
|
redraw_rect(pageview, &rectangle);
|
|
}
|
|
int val = g_value_get_int(value);
|
|
if (val < 0) {
|
|
priv->search.current = girara_list_size(priv->search.list);
|
|
} else {
|
|
priv->search.current = val;
|
|
zathura_rectangle_t* rect = girara_list_nth(priv->search.list, priv->search.current);
|
|
zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
|
|
if (priv->search.draw) {
|
|
redraw_rect(pageview, &rectangle);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PROP_DRAW_SEACH_RESULTS:
|
|
priv->search.draw = g_value_get_boolean(value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
|
|
{
|
|
ZathuraPage* pageview = ZATHURA_PAGE(object);
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview);
|
|
|
|
switch (prop_id) {
|
|
case PROP_LINKS_NUMBER:
|
|
g_value_set_int(value, priv->links.n);
|
|
break;
|
|
case PROP_SEARCH_RESULTS_LENGTH:
|
|
g_value_set_int(value, priv->search.list == NULL ? 0 : girara_list_size(priv->search.list));
|
|
break;
|
|
case PROP_SEARCH_RESULTS_CURRENT:
|
|
g_value_set_int(value, priv->search.list == NULL ? -1 : priv->search.current);
|
|
break;
|
|
case PROP_SEARCH_RESULTS:
|
|
g_value_set_pointer(value, priv->search.list);
|
|
break;
|
|
case PROP_LAST_VIEW:
|
|
g_value_set_int(value, priv->last_view);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
#if GTK_MAJOR_VERSION == 2
|
|
static gboolean
|
|
zathura_page_widget_expose(GtkWidget* widget, GdkEventExpose* event)
|
|
{
|
|
cairo_t* cairo = gdk_cairo_create(gtk_widget_get_window(widget));
|
|
if (cairo == NULL) {
|
|
girara_error("Could not retrieve cairo object");
|
|
return FALSE;
|
|
}
|
|
|
|
/* set clip region */
|
|
cairo_rectangle(cairo, event->area.x, event->area.y, event->area.width, event->area.height);
|
|
cairo_clip(cairo);
|
|
|
|
const gboolean ret = zathura_page_widget_draw(widget, cairo);
|
|
cairo_destroy(cairo);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
zathura_page_widget_draw(GtkWidget* widget, cairo_t* cairo)
|
|
{
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
mutex_lock(&(priv->lock));
|
|
|
|
zathura_document_t* document = zathura_page_get_document(priv->page);
|
|
|
|
#if GTK_MAJOR_VERSION == 2
|
|
GtkAllocation allocation;
|
|
gtk_widget_get_allocation(widget, &allocation);
|
|
const unsigned int page_height = allocation.height;
|
|
const unsigned int page_width = allocation.width;
|
|
#else
|
|
const unsigned int page_height = gtk_widget_get_allocated_height(widget);
|
|
const unsigned int page_width = gtk_widget_get_allocated_width(widget);
|
|
#endif
|
|
|
|
if (priv->surface != NULL) {
|
|
cairo_save(cairo);
|
|
|
|
unsigned int rotation = zathura_document_get_rotation(document);
|
|
switch (rotation) {
|
|
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;
|
|
}
|
|
|
|
if (rotation != 0) {
|
|
cairo_rotate(cairo, rotation * G_PI / 180.0);
|
|
}
|
|
|
|
cairo_set_source_surface(cairo, priv->surface, 0, 0);
|
|
cairo_paint(cairo);
|
|
cairo_restore(cairo);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
g_free(font);
|
|
|
|
/* draw links */
|
|
if (priv->links.draw == true && priv->links.n != 0) {
|
|
unsigned int link_counter = 0;
|
|
GIRARA_LIST_FOREACH(priv->links.list, zathura_link_t*, iter, link)
|
|
if (link != NULL) {
|
|
zathura_rectangle_t rectangle = recalc_rectangle(priv->page, zathura_link_get_position(link));
|
|
|
|
/* draw position */
|
|
GdkColor color = priv->zathura->ui.colors.highlight_color;
|
|
cairo_set_source_rgba(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0, transparency);
|
|
cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
|
|
(rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
|
|
cairo_fill(cairo);
|
|
|
|
/* draw text */
|
|
cairo_set_source_rgba(cairo, 0, 0, 0, 1);
|
|
cairo_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);
|
|
}
|
|
GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
|
|
}
|
|
|
|
/* draw search results */
|
|
if (priv->search.list != NULL && priv->search.draw == true) {
|
|
int idx = 0;
|
|
GIRARA_LIST_FOREACH(priv->search.list, zathura_rectangle_t*, iter, rect)
|
|
zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
|
|
|
|
/* draw position */
|
|
if (idx == priv->search.current) {
|
|
GdkColor color = priv->zathura->ui.colors.highlight_color_active;
|
|
cairo_set_source_rgba(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0, transparency);
|
|
} else {
|
|
GdkColor color = priv->zathura->ui.colors.highlight_color;
|
|
cairo_set_source_rgba(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0, transparency);
|
|
}
|
|
cairo_rectangle(cairo, rectangle.x1, rectangle.y1,
|
|
(rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1));
|
|
cairo_fill(cairo);
|
|
++idx;
|
|
GIRARA_LIST_FOREACH_END(priv->search.list, zathura_rectangle_t*, iter, rect);
|
|
}
|
|
/* draw selection */
|
|
if (priv->mouse.selection.y2 != -1 && priv->mouse.selection.x2 != -1) {
|
|
GdkColor color = priv->zathura->ui.colors.highlight_color;
|
|
cairo_set_source_rgba(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0, transparency);
|
|
cairo_rectangle(cairo, priv->mouse.selection.x1, priv->mouse.selection.y1,
|
|
(priv->mouse.selection.x2 - priv->mouse.selection.x1), (priv->mouse.selection.y2 - priv->mouse.selection.y1));
|
|
cairo_fill(cairo);
|
|
}
|
|
} else {
|
|
/* set background color */
|
|
if (priv->zathura->global.recolor == true) {
|
|
GdkColor color = priv->zathura->ui.colors.recolor_light_color;
|
|
cairo_set_source_rgb(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0);
|
|
} else {
|
|
GdkColor color = priv->zathura->ui.colors.render_loading_bg;
|
|
cairo_set_source_rgb(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0);
|
|
}
|
|
cairo_rectangle(cairo, 0, 0, page_width, page_height);
|
|
cairo_fill(cairo);
|
|
|
|
bool render_loading = true;
|
|
girara_setting_get(priv->zathura->ui.session, "render-loading", &render_loading);
|
|
|
|
/* write text */
|
|
if (render_loading == true) {
|
|
if (priv->zathura->global.recolor == true) {
|
|
GdkColor color = priv->zathura->ui.colors.recolor_dark_color;
|
|
cairo_set_source_rgb(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0);
|
|
} else {
|
|
GdkColor color = priv->zathura->ui.colors.render_loading_fg;
|
|
cairo_set_source_rgb(cairo, color.red/65535.0, color.green/65535.0, color.blue/65535.0);
|
|
}
|
|
|
|
const char* text = _("Loading...");
|
|
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);
|
|
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);
|
|
cairo_move_to(cairo, x, y);
|
|
cairo_show_text(cairo, text);
|
|
}
|
|
|
|
/* render real page */
|
|
if (priv->render_requested == false) {
|
|
priv->render_requested = true;
|
|
render_page(priv->zathura->sync.render_thread, priv->page);
|
|
}
|
|
}
|
|
mutex_unlock(&(priv->lock));
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
zathura_page_widget_redraw_canvas(ZathuraPage* pageview)
|
|
{
|
|
GtkWidget* widget = GTK_WIDGET(pageview);
|
|
gtk_widget_queue_draw(widget);
|
|
}
|
|
|
|
void
|
|
zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface)
|
|
{
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
mutex_lock(&(priv->lock));
|
|
if (priv->surface != NULL) {
|
|
cairo_surface_finish(priv->surface);
|
|
cairo_surface_destroy(priv->surface);
|
|
}
|
|
priv->render_requested = false;
|
|
priv->surface = surface;
|
|
mutex_unlock(&(priv->lock));
|
|
/* force a redraw here */
|
|
if (priv->surface != NULL) {
|
|
zathura_page_widget_redraw_canvas(widget);
|
|
}
|
|
}
|
|
|
|
static void
|
|
zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation)
|
|
{
|
|
GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation);
|
|
zathura_page_widget_update_surface(ZATHURA_PAGE(widget), NULL);
|
|
}
|
|
|
|
static void
|
|
redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle)
|
|
{
|
|
/* cause the rect to be drawn */
|
|
GdkRectangle grect;
|
|
grect.x = rectangle->x1;
|
|
grect.y = rectangle->y1;
|
|
grect.width = (rectangle->x2 + 1) - rectangle->x1;
|
|
grect.height = (rectangle->y2 + 1) - rectangle->y1;
|
|
#if (GTK_MAJOR_VERSION == 3)
|
|
gtk_widget_queue_draw_area(GTK_WIDGET(widget), grect.x, grect.y, grect.width, grect.height);
|
|
#else
|
|
gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(widget)), &grect, TRUE);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles)
|
|
{
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
|
|
GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect)
|
|
zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect);
|
|
redraw_rect(widget, &rectangle);
|
|
GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect);
|
|
}
|
|
|
|
zathura_link_t*
|
|
zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index)
|
|
{
|
|
g_return_val_if_fail(widget != NULL, NULL);
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
g_return_val_if_fail(priv != NULL, NULL);
|
|
|
|
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);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button)
|
|
{
|
|
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);
|
|
|
|
if (girara_callback_view_button_press_event(widget, button, priv->zathura->ui.session) == true) {
|
|
return true;
|
|
}
|
|
|
|
if (button->button == 1) { /* left click */
|
|
if (button->type == GDK_BUTTON_PRESS) {
|
|
/* start the selection */
|
|
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;
|
|
} else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
|
|
/* abort the selection */
|
|
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;
|
|
}
|
|
|
|
return true;
|
|
} else if (button->button == 3) { /* right click */
|
|
zathura_page_widget_popup_menu(widget, button);
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
if (button->type != GDK_BUTTON_RELEASE || button->button != 1) {
|
|
return false;
|
|
}
|
|
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
zathura_document_t* document = zathura_page_get_document(priv->page);
|
|
|
|
if (priv->mouse.selection.y2 == -1 && priv->mouse.selection.x2 == -1 ) {
|
|
/* simple single click */
|
|
/* get links */
|
|
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) {
|
|
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 <= button->x && rect.x2 >= button->x
|
|
&& rect.y1 <= button->y && rect.y2 >= button->y) {
|
|
zathura_link_evaluate(priv->zathura, link);
|
|
}
|
|
GIRARA_LIST_FOREACH_END(priv->links.list, zathura_link_t*, iter, link);
|
|
}
|
|
} else {
|
|
redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
|
|
|
|
bool synctex = false;
|
|
girara_setting_get(priv->zathura->ui.session, "synctex", &synctex);
|
|
|
|
if (synctex == true && button->state & GDK_CONTROL_MASK) {
|
|
/* synctex backwards sync */
|
|
double scale = zathura_document_get_scale(document);
|
|
int x = button->x / scale, y = button->y / scale;
|
|
|
|
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) {
|
|
/* copy to clipboard */
|
|
gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), text, -1);
|
|
|
|
|
|
if (priv->page != NULL && document != NULL && priv->zathura != NULL) {
|
|
char* stripped_text = g_strdelimit(g_strdup(text), "\n\t\r\n", ' ');
|
|
girara_notify(priv->zathura->ui.session, GIRARA_INFO, _("Copied selected text to clipboard: %s"), stripped_text);
|
|
g_free(stripped_text);
|
|
}
|
|
}
|
|
|
|
g_free(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
zathura_rectangle_t tmp = priv->mouse.selection;
|
|
if (event->x < priv->mouse.selection_basepoint.x) {
|
|
tmp.x1 = event->x;
|
|
tmp.x2 = priv->mouse.selection_basepoint.x;
|
|
} else {
|
|
tmp.x2 = event->x;
|
|
tmp.x1 = priv->mouse.selection_basepoint.x;
|
|
}
|
|
if (event->y < priv->mouse.selection_basepoint.y) {
|
|
tmp.y1 = event->y;
|
|
tmp.y2 = priv->mouse.selection_basepoint.y;
|
|
} else {
|
|
tmp.y1 = priv->mouse.selection_basepoint.y;
|
|
tmp.y2 = event->y;
|
|
}
|
|
|
|
redraw_rect(ZATHURA_PAGE(widget), &priv->mouse.selection);
|
|
redraw_rect(ZATHURA_PAGE(widget), &tmp);
|
|
priv->mouse.selection = tmp;
|
|
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
|
|
#if GTK_MAJOR_VERSION == 3 // FIXME
|
|
return;
|
|
#endif
|
|
|
|
if (priv->images.retrieved == false) {
|
|
priv->images.list = zathura_page_images_get(priv->page, NULL);
|
|
priv->images.retrieved = true;
|
|
}
|
|
|
|
if (priv->images.list == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* search for underlaying image */
|
|
zathura_image_t* image = NULL;
|
|
GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
|
|
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;
|
|
}
|
|
GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
|
|
|
|
if (image == NULL) {
|
|
return;
|
|
}
|
|
|
|
priv->images.current = image;
|
|
|
|
/* 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[] = {
|
|
{ _("Copy image"), cb_menu_image_copy },
|
|
{ _("Save image as"), cb_menu_image_save },
|
|
};
|
|
|
|
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)
|
|
{
|
|
#if GTK_MAJOR_VERSION == 2 // FIXME
|
|
g_return_if_fail(item != NULL);
|
|
g_return_if_fail(page != NULL);
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(page);
|
|
g_return_if_fail(priv->images.current != NULL);
|
|
|
|
cairo_surface_t* surface = zathura_page_image_get_cairo(priv->page, priv->images.current, NULL);
|
|
if (surface == NULL) {
|
|
return;
|
|
}
|
|
|
|
int width = cairo_image_surface_get_width(surface);
|
|
int height = cairo_image_surface_get_height(surface);
|
|
|
|
GdkPixmap* pixmap = gdk_pixmap_new(gtk_widget_get_window(GTK_WIDGET(item)), width, height, -1);
|
|
cairo_t* cairo = gdk_cairo_create(pixmap);
|
|
|
|
cairo_set_source_surface(cairo, surface, 0, 0);
|
|
cairo_paint(cairo);
|
|
cairo_destroy(cairo);
|
|
|
|
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_drawable(NULL, pixmap, NULL, 0, 0, 0,
|
|
0, width, height);
|
|
|
|
gtk_clipboard_set_image(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), pixbuf);
|
|
gtk_clipboard_set_image(gtk_clipboard_get(GDK_SELECTION_PRIMARY), pixbuf);
|
|
|
|
/* reset */
|
|
priv->images.current = NULL;
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
g_return_if_fail(priv->images.current != NULL);
|
|
g_return_if_fail(priv->images.list != NULL);
|
|
|
|
/* generate image identifier */
|
|
unsigned int page_id = zathura_page_get_index(priv->page) + 1;
|
|
unsigned int image_id = 1;
|
|
|
|
GIRARA_LIST_FOREACH(priv->images.list, zathura_image_t*, iter, image_it)
|
|
if (image_it == priv->images.current) {
|
|
break;
|
|
}
|
|
|
|
image_id++;
|
|
GIRARA_LIST_FOREACH_END(priv->images.list, zathura_image_t*, iter, image_it);
|
|
|
|
/* 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 */
|
|
priv->images.current = NULL;
|
|
}
|
|
|
|
void
|
|
zathura_page_widget_update_view_time(ZathuraPage* widget)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_PAGE(widget) == TRUE);
|
|
zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget);
|
|
|
|
if (zathura_page_get_visibility(priv->page) == true) {
|
|
priv->last_view = g_get_real_time();
|
|
}
|
|
}
|