zathura/page-widget.c
Marwan Tanager cc3b9aea18 More Vim-like search behavior
This patch activates the last aborted search when pressing the search shortcuts
('n' or 'N').

To avoid confusion, and to make things more predictable, I've chosen to always
reactivate an aborted search starting from the beginning (or end, in case of
'N' or '?') of the current page, as opposed to Vim which continues from the
next search term each time the search is reactivated.

Searching using '/' or '?' doesn't center the view at the current search term
like when using 'n' or 'N', so we fix this here.

Also, I managed to work around the issue of the thin rectangular margins that
show around the previously-highlighted search terms after the search is aborted
(either explicitly or as a result of following links), by redrawing the page
widget (only if it's visible) instead of redrawing the rectangles covering the
highlighted search terms.

Signed-off-by: Sebastian Ramacher <sebastian+dev@ramacher.at>
2013-06-10 14:35:19 +02:00

877 lines
31 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_SEARCH_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_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));
g_object_class_install_property(object_class, PROP_LAST_VIEW,
g_param_spec_int64("last-view", "last-view", "Last time the page has been viewed", -1, G_MAXINT64, 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 = false;
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_SEARCH_RESULTS:
priv->search.draw = g_value_get_boolean(value);
/*
* 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)) {
gtk_widget_queue_draw(GTK_WIDGET(object));
}
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_int64(value, priv->last_view);
break;
case PROP_DRAW_SEARCH_RESULTS:
g_value_set_boolean(value, priv->search.draw);
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();
}
}