added C-Space to scroll down by the whole window (with a hack to skip gaps); fixed...
[k8zathura.git] / src / zathura.c
blobf694e33f50e1e75fa4ba915727a7434a75ac4f34
1 /* See LICENSE file for license and copyright information */
2 #ifndef _BSD_SOURCE
3 # define _BSD_SOURCE
4 #endif
5 #if defined(_XOPEN_SOURCE) && _XOPEN_SOURCE < 500
6 # undef _XOPEN_SOURCE
7 #endif
8 #ifndef _XOPEN_SOURCE
9 # define _XOPEN_SOURCE 500
10 #endif
12 #ifndef _DEFAULT_SOURCE
13 # define _DEFAULT_SOURCE
14 #endif
16 #include <libgen.h>
17 #include <limits.h>
18 #include <math.h>
19 #include <regex.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <time.h>
23 #include <unistd.h>
24 #include <sys/time.h>
26 #include <poppler/glib/poppler.h>
27 #include <cairo.h>
29 #include <glib/gstdio.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
34 /* macros */
35 #define NORETURN __attribute__((noreturn))
36 #define LENGTH(x) (sizeof(x)/sizeof((x)[0]))
37 #define CLEAN(m) (m&~(GDK_MOD5_MASK|GDK_MOD2_MASK|GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK|GDK_LEAVE_NOTIFY_MASK))
39 #define MTLOCK_PDFOBJ() do { g_static_mutex_lock(&(Zathura.Lock.pdf_obj_lock)); } while (0)
40 #define MTUNLOCK_PDFOBJ() do { g_static_mutex_unlock(&(Zathura.Lock.pdf_obj_lock)); } while (0)
42 #define MTLOCK_PDFLIB() do { g_static_mutex_lock(&(Zathura.Lock.pdflib_lock)); } while (0)
43 #define MTUNLOCK_PDFLIB() do { g_static_mutex_unlock(&(Zathura.Lock.pdflib_lock)); } while (0)
45 #define MTLOCK_SEARCH() do { g_static_mutex_lock(&(Zathura.Lock.search_lock)); } while (0)
46 #define MTUNLOCK_SEARCH() do { g_static_mutex_unlock(&(Zathura.Lock.search_lock)); } while (0)
48 #define MTLOCK_SELECT() do { g_static_mutex_lock(&(Zathura.Lock.select_lock)); } while (0)
49 #define MTUNLOCK_SELECT() do { g_static_mutex_unlock(&(Zathura.Lock.select_lock)); } while (0)
52 // just remove the #if-#define-#endif block if support for poppler versions
53 // before 0.15 is dropped
54 #if !POPPLER_CHECK_VERSION(0,15,0)
55 # define poppler_page_get_selected_text poppler_page_get_text
56 #endif
58 static const char *main_group_name = "//main//";
59 static const char *lastcheck_key_name = "lastcheck";
62 //**************************************************************************
64 // surface cache
66 //**************************************************************************
68 // maximum surfaces in page surface cache
69 #define SURF_CACHE_SIZE (16)
71 typedef struct PageSurfCache_t {
72 cairo_surface_t *surf;
73 int page_id;
74 double scale;
75 struct PageSurfCache_t *prev;
76 struct PageSurfCache_t *next;
77 } PageSurfCache;
80 PageSurfCache *surfCacheHead = NULL;
81 PageSurfCache *surfCacheTail = NULL;
84 //==========================================================================
86 // surfCacheInit
88 // initialise page cache, if necessary
90 //==========================================================================
91 static void surfCacheInit (void) {
92 if (surfCacheHead) return;
93 surfCacheHead = (PageSurfCache *)g_malloc0(sizeof(PageSurfCache));
94 memset(surfCacheHead, 0, sizeof(PageSurfCache));
95 surfCacheHead->page_id = -1;
96 surfCacheHead->scale = 0;
97 PageSurfCache *last = surfCacheHead;
98 for (int f = 1; f < SURF_CACHE_SIZE; ++f) {
99 PageSurfCache *c = (PageSurfCache *)g_malloc0(sizeof(PageSurfCache));
100 memset(c, 0, sizeof(PageSurfCache));
101 c->page_id = -1;
102 c->scale = 0;
103 last->next = c;
104 c->prev = last;
105 last = c;
107 surfCacheTail = last;
111 //==========================================================================
113 // surfCacheDestroy
115 //==========================================================================
116 static void surfCacheDestroy (void) {
117 while (surfCacheHead) {
118 PageSurfCache *c = surfCacheHead;
119 surfCacheHead = c->next;
120 if (c->surf) cairo_surface_destroy(c->surf);
121 g_free(c);
123 surfCacheHead = surfCacheTail = NULL;
127 //==========================================================================
129 // surfCacheRemove
131 //==========================================================================
132 static void surfCacheRemove (PageSurfCache *c) {
133 if (!c) return;
134 if (c->prev) c->prev->next = c->next; else surfCacheHead = c->next;
135 if (c->next) c->next->prev = c->prev; else surfCacheTail = c->prev;
136 c->prev = c->next = NULL;
140 //==========================================================================
142 // surfCacheToFront
144 //==========================================================================
145 static void surfCacheToFront (PageSurfCache *c) {
146 if (!c || c == surfCacheHead) return; // nothing to do
147 surfCacheRemove(c);
148 // make first
149 c->next = surfCacheHead;
150 surfCacheHead->prev = c;
151 surfCacheHead = c;
155 //==========================================================================
157 // surfCacheGet
159 // this either returns an existing cached item, or creates a new
160 // one, of evicts and clears the old one
161 // the item is also moved to the front of the list
162 // destroys the surface if it is invalid (i.e. check the `surf`
163 // field in the returned struct)
165 //==========================================================================
166 static PageSurfCache *surfCacheGet (int pgid, const double scale) {
167 surfCacheInit();
168 for (PageSurfCache *c = surfCacheHead; c; c = c->next) {
169 if (c->page_id == pgid) {
170 surfCacheToFront(c);
171 if (c->scale != scale) {
172 if (c->surf) cairo_surface_destroy(c->surf);
173 c->surf = NULL;
174 c->scale = scale;
176 return c;
179 // not found, use tail
180 PageSurfCache *res = surfCacheTail;
181 surfCacheToFront(res);
182 if (res->surf) cairo_surface_destroy(res->surf);
183 res->surf = NULL;
184 res->page_id = pgid;
185 res->scale = scale;
186 return res;
190 /* enums */
191 enum {
192 NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, HIGHLIGHT,
193 DELETE_LAST_WORD, DELETE_LAST_CHAR, DEFAULT, ERROR, WARNING, NEXT_GROUP,
194 PREVIOUS_GROUP, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, ZOOM_SPECIFIC, FORWARD,
195 BACKWARD, ADJUST_BESTFIT, ADJUST_WIDTH, ADJUST_NONE, CONTINUOUS, DELETE_LAST,
196 ADD_MARKER, EVAL_MARKER, EXPAND, COLLAPSE, SELECT, GOTO_DEFAULT, GOTO_LABELS,
197 GOTO_OFFSET, HALF_UP, HALF_DOWN, FULL_UP, FULL_DOWN, FULL_WINDOW_DOWN,
198 NEXT_CHAR, PREVIOUS_CHAR, DELETE_TO_LINE_START, APPEND_FILEPATH, NO_SEARCH,
200 WINDOW_UP, WINDOW_DOWN,
202 CENTER_LEFT_TOP, CENTER_CENTER, CENTER_RIGHT_BOTTOM,
206 /* define modes */
207 #define ALL (1<<0)
208 #define FULLSCREEN (1<<1)
209 #define INDEX (1<<2)
210 #define NORMAL (1<<3)
213 /* typedefs */
214 typedef struct CElement {
215 char *value;
216 char *description;
217 struct CElement *next;
218 } CompletionElement;
221 typedef struct CGroup {
222 char *value;
223 CompletionElement *elements;
224 struct CGroup *next;
225 } CompletionGroup;
229 typedef struct {
230 CompletionGroup *groups;
231 } Completion;
234 typedef struct {
235 char *command;
236 char *description;
237 int command_id;
238 gboolean is_group;
239 GtkWidget *row;
240 } CompletionRow;
243 typedef struct {
244 int n;
245 void *data;
246 } Argument;
249 typedef struct {
250 char *name;
251 int argument;
252 } ArgumentName;
255 typedef struct {
256 int mask;
257 int key;
258 void (*function) (Argument *);
259 int mode;
260 Argument argument;
261 } Shortcut;
264 typedef struct {
265 char *name;
266 int mode;
267 char *display;
268 } ModeName;
271 typedef struct {
272 char *name;
273 void (*function) (Argument *);
274 } ShortcutName;
277 typedef struct {
278 int mask;
279 int key;
280 void (*function) (Argument *);
281 Argument argument;
282 } InputbarShortcut;
285 typedef struct {
286 int direction;
287 void (*function) (Argument *);
288 Argument argument;
289 } MouseScrollEvent;
292 typedef struct {
293 char *command;
294 char *abbr;
295 gboolean (*function) (int, char **);
296 Completion* (*completion) (char *);
297 char *description;
298 } Command;
301 typedef struct {
302 char *regex;
303 void (*function) (char *, Argument *);
304 Argument argument;
305 } BufferCommand;
308 typedef struct {
309 char identifier;
310 gboolean (*function) (char *, Argument *);
311 int always;
312 Argument argument;
313 } SpecialCommand;
316 typedef struct SCList {
317 Shortcut element;
318 struct SCList *next;
319 } ShortcutList;
322 typedef struct {
323 char *identifier;
324 int key;
325 } GDKKey;
328 typedef struct {
329 PopplerPage *page;
330 int id;
331 char *label;
332 double width;
333 double height;
334 } Page;
337 typedef struct {
338 char *name;
339 void *variable;
340 char type;
341 gboolean render;
342 gboolean reinit;
343 char *description;
344 } Setting;
347 typedef struct {
348 int id;
349 int page;
350 } Marker;
353 typedef struct {
354 char *id;
355 int page;
356 } Bookmark;
359 /* zathura */
360 static struct {
361 struct {
362 GtkWidget *window;
363 GtkBox *box;
364 GtkBox *continuous;
365 GtkScrolledWindow *view;
366 GtkViewport *viewport;
367 GtkWidget *statusbar;
368 GtkBox *statusbar_entries;
369 GtkEntry *inputbar;
370 GtkWidget *index;
371 GtkWidget *information;
372 GtkWidget *drawing_area;
373 GtkWidget *document;
374 GdkNativeWindow embed;
375 } UI;
377 struct {
378 GdkColor default_fg;
379 GdkColor default_bg;
380 GdkColor inputbar_fg;
381 GdkColor inputbar_bg;
382 GdkColor statusbar_fg;
383 GdkColor statusbar_bg;
384 GdkColor completion_fg;
385 GdkColor completion_bg;
386 GdkColor completion_g_bg;
387 GdkColor completion_g_fg;
388 GdkColor completion_hl_fg;
389 GdkColor completion_hl_bg;
390 GdkColor notification_e_fg;
391 GdkColor notification_e_bg;
392 GdkColor notification_w_fg;
393 GdkColor notification_w_bg;
394 GdkColor recolor_darkcolor;
395 GdkColor recolor_lightcolor;
396 GdkColor search_highlight;
397 GdkColor select_text;
398 PangoFontDescription *font;
399 } Style;
401 struct {
402 GtkLabel *status_text;
403 GtkLabel *status_buffer;
404 GtkLabel *status_state;
405 GString *buffer;
406 GList *history;
407 int mode;
408 int viewing_mode;
409 gboolean recolor;
410 gboolean enable_labelmode;
411 int goto_mode;
412 int adjust_mode;
413 gboolean show_index;
414 gboolean show_statusbar;
415 gboolean show_inputbar;
416 int xcenter_mode;
417 } Global;
419 struct {
420 ShortcutList *sclist;
421 } Bindings;
423 struct {
424 gdouble x;
425 gdouble y;
426 } SelectPoint;
428 struct {
429 char *filename;
430 char *pages;
431 int scroll_percentage;
432 } State;
434 struct {
435 Marker *markers;
436 int number_of_markers;
437 int last;
438 } Marker;
440 struct {
441 GFileMonitor *monitor;
442 GFile *file;
443 } FileMonitor;
445 struct {
446 GKeyFile *data;
447 char *file;
448 Bookmark *bookmarks;
449 int number_of_bookmarks;
450 } Bookmarks;
452 struct {
453 PopplerDocument *document;
454 char *file;
455 char *password;
456 Page **pages;
457 int page_number;
458 double page_yskip_t; // [0..1]
459 int page_yskip_pix; // in screen pixels; for a gap
460 int number_of_pages;
461 int scale;
462 int rotate;
463 int page_gap; // usually 8
464 //TODO: cache total height and other things?
465 } PDF;
467 struct {
468 GStaticMutex pdflib_lock;
469 GStaticMutex pdf_obj_lock;
470 GStaticMutex search_lock;
471 GStaticMutex select_lock;
472 } Lock;
474 struct {
475 GList *results;
476 int page;
477 gboolean draw;
478 gchar *query;
479 } Search;
481 struct {
482 GThread *search_thread;
483 gboolean search_thread_running;
484 GThread *inotify_thread;
485 } Thread;
487 struct {
488 guint inputbar_activate;
489 guint inputbar_key_press_event;
490 } Handler;
492 struct {
493 gchar *config_dir;
494 gchar *data_dir;
495 } Config;
497 struct {
498 gchar *file;
499 } StdinSupport;
500 } Zathura;
503 /* function declarations */
504 void init_look (void);
505 void init_directories (void);
506 void init_bookmarks (void);
507 void init_keylist (void);
508 void init_settings (void);
509 void init_zathura (void);
510 void add_marker (int);
511 void build_index (GtkTreeModel *, GtkTreeIter *, PopplerIndexIter *);
512 void change_mode (int);
513 void calculate_screen_offset (GtkWidget *widget, int scrx, int scry, double *offset_x, double *offset_y, int *out_page_id);
514 void close_file (gboolean);
515 void enter_password (void);
516 void highlight_result (int, PopplerRectangle *);
517 cairo_surface_t *create_page_surface (int page_id);
518 void draw (int page_id);
519 void render_view (GtkWidget *widget, gboolean fullclear);
520 void eval_marker (int);
521 void notify (int, const char *);
522 gboolean open_file (char *, char *);
523 gboolean open_stdin (gchar *);
524 void open_uri (char *);
525 void out_of_memory (void) NORETURN;
526 void update_status (void);
527 void read_bookmarks_file (void);
528 void write_bookmarks_file (void);
529 void free_bookmarks (void);
530 void read_configuration_file (const char *);
531 void read_configuration (void);
532 void recalc_rectangle (int, PopplerRectangle *);
533 void set_completion_row_color (GtkBox *, int, int);
534 void set_page (int);
535 void set_page_keep_ofs (int page);
536 void switch_view (GtkWidget *);
537 GtkEventBox *create_completion_row (GtkBox *, char *, char *, gboolean);
538 gchar *fix_path (const gchar *);
539 gchar *path_from_env (const gchar *);
540 gchar *get_home_dir (void);
542 Completion *completion_init (void);
543 CompletionGroup *completion_group_create (char*);
544 void completion_add_group (Completion *, CompletionGroup *);
545 void completion_free (Completion *);
546 void completion_group_add_element (CompletionGroup *, char *, char *);
548 /* thread declaration */
549 void *search (void *);
551 /* shortcut declarations */
552 void sc_abort (Argument *);
553 void sc_adjust_window (Argument *);
554 void sc_xcenter_window (Argument *);
555 //void sc_ycenter_window (Argument *);
556 void sc_change_buffer (Argument *);
557 void sc_change_mode (Argument *);
558 void sc_focus_inputbar (Argument *);
559 void sc_follow (Argument *);
560 void sc_navigate (Argument *);
561 void sc_recolor (Argument *);
562 void sc_reload (Argument *);
563 void sc_rotate (Argument *);
564 void sc_scroll (Argument *);
565 void sc_search (Argument *);
566 void sc_switch_goto_mode (Argument *);
567 void sc_navigate_index (Argument *);
568 void sc_toggle_index (Argument *);
569 void sc_toggle_inputbar (Argument *);
570 void sc_toggle_fullscreen (Argument *);
571 void sc_toggle_statusbar (Argument *);
572 void sc_quit (Argument *);
573 void sc_zoom (Argument *);
575 /* inputbar shortcut declarations */
576 void isc_abort (Argument *);
577 void isc_command_history (Argument *);
578 void isc_completion (Argument *);
579 void isc_string_manipulation (Argument *);
581 /* command declarations */
582 gboolean cmd_bookmark (int, char **);
583 gboolean cmd_open_bookmark (int, char **);
584 gboolean cmd_close (int, char **);
585 gboolean cmd_delete_bookmark (int, char **);
586 gboolean cmd_export (int, char **);
587 gboolean cmd_info (int, char **);
588 gboolean cmd_map (int, char **);
589 gboolean cmd_open (int, char **);
590 gboolean cmd_print (int, char **);
591 gboolean cmd_rotate (int, char **);
592 gboolean cmd_set (int, char **);
593 gboolean cmd_quit (int, char **);
594 gboolean cmd_save (int, char **);
595 gboolean cmd_savef (int, char **);
597 /* completion commands */
598 Completion *cc_bookmark (char *);
599 Completion *cc_export (char *);
600 Completion *cc_open (char *);
601 Completion *cc_print (char *);
602 Completion *cc_set (char *);
604 /* buffer command declarations */
605 void bcmd_evalmarker (char *, Argument *);
606 void bcmd_goto (char *, Argument *);
607 void bcmd_scroll (char *, Argument *);
608 void bcmd_setmarker (char *, Argument *);
609 void bcmd_zoom (char *, Argument *);
611 /* special command delcarations */
612 gboolean scmd_search (gchar *, Argument *);
614 /* callback declarations */
615 gboolean cb_destroy (GtkWidget *, gpointer);
616 gboolean cb_draw (GtkWidget *, GdkEventExpose *, gpointer);
617 gboolean cb_index_row_activated (GtkTreeView *, GtkTreePath *, GtkTreeViewColumn *, gpointer);
618 gboolean cb_inputbar_kb_pressed (GtkWidget *, GdkEventKey *, gpointer);
619 gboolean cb_inputbar_activate (GtkEntry *, gpointer);
620 gboolean cb_inputbar_form_activate (GtkEntry *, gpointer);
621 gboolean cb_inputbar_password_activate (GtkEntry *, gpointer);
622 gboolean cb_view_kb_pressed (GtkWidget *, GdkEventKey *, gpointer);
623 gboolean cb_view_resized (GtkWidget *, GtkAllocation *, gpointer);
624 gboolean cb_view_button_pressed (GtkWidget *, GdkEventButton *, gpointer);
625 gboolean cb_view_button_release (GtkWidget *, GdkEventButton *, gpointer);
626 gboolean cb_view_motion_notify (GtkWidget *, GdkEventMotion *, gpointer);
627 gboolean cb_view_scrolled (GtkWidget *, GdkEventScroll *, gpointer);
628 gboolean cb_watch_file (GFileMonitor *, GFile *, GFile *, GFileMonitorEvent, gpointer);
631 /* configuration */
632 #include "config.h"
635 //==========================================================================
637 // safe_realloc
639 //==========================================================================
640 static void *safe_realloc (void **ptr, size_t nmemb, size_t size) {
641 static const size_t limit = ~((size_t)0u);
642 void *tmp = NULL;
643 /* Check for overflow. */
644 if (nmemb > limit/size) goto failure;
645 tmp = realloc(*ptr, nmemb*size);
646 /* Check for out of memory. */
647 if (!tmp) goto failure;
648 *ptr = tmp;
649 return *ptr;
650 /* Error handling. */
651 failure:
652 free(*ptr);
653 *ptr = NULL;
654 return NULL;
658 //==========================================================================
660 // sys_time
662 //==========================================================================
663 static double sys_time (void) {
664 struct timeval tp;
665 struct timezone tzp;
666 gettimeofday(&tp, &tzp);
667 return (double)(tp.tv_sec)+(double)tp.tv_usec/1000000.0;
671 //==========================================================================
673 // init_look
675 //==========================================================================
676 void init_look (void) {
677 /* parse */
678 gdk_color_parse(default_fgcolor, &(Zathura.Style.default_fg));
679 gdk_color_parse(default_bgcolor, &(Zathura.Style.default_bg));
680 gdk_color_parse(inputbar_fgcolor, &(Zathura.Style.inputbar_fg));
681 gdk_color_parse(inputbar_bgcolor, &(Zathura.Style.inputbar_bg));
682 gdk_color_parse(statusbar_fgcolor, &(Zathura.Style.statusbar_fg));
683 gdk_color_parse(statusbar_bgcolor, &(Zathura.Style.statusbar_bg));
684 gdk_color_parse(completion_fgcolor, &(Zathura.Style.completion_fg));
685 gdk_color_parse(completion_bgcolor, &(Zathura.Style.completion_bg));
686 gdk_color_parse(completion_g_fgcolor, &(Zathura.Style.completion_g_fg));
687 gdk_color_parse(completion_g_fgcolor, &(Zathura.Style.completion_g_fg));
688 gdk_color_parse(completion_hl_fgcolor, &(Zathura.Style.completion_hl_fg));
689 gdk_color_parse(completion_hl_bgcolor, &(Zathura.Style.completion_hl_bg));
690 gdk_color_parse(notification_e_fgcolor, &(Zathura.Style.notification_e_fg));
691 gdk_color_parse(notification_e_bgcolor, &(Zathura.Style.notification_e_bg));
692 gdk_color_parse(notification_w_fgcolor, &(Zathura.Style.notification_w_fg));
693 gdk_color_parse(notification_w_bgcolor, &(Zathura.Style.notification_w_bg));
694 gdk_color_parse(recolor_darkcolor, &(Zathura.Style.recolor_darkcolor));
695 gdk_color_parse(recolor_lightcolor, &(Zathura.Style.recolor_lightcolor));
696 gdk_color_parse(search_highlight, &(Zathura.Style.search_highlight));
697 gdk_color_parse(select_text, &(Zathura.Style.select_text));
699 pango_font_description_free(Zathura.Style.font);
700 Zathura.Style.font = pango_font_description_from_string(font);
702 /* window and viewport */
703 gtk_widget_modify_bg(GTK_WIDGET(Zathura.UI.window), GTK_STATE_NORMAL, &(Zathura.Style.default_bg));
704 gtk_widget_modify_bg(GTK_WIDGET(Zathura.UI.viewport), GTK_STATE_NORMAL, &(Zathura.Style.default_bg));
706 /* drawing area */
707 gtk_widget_modify_bg(GTK_WIDGET(Zathura.UI.drawing_area), GTK_STATE_NORMAL, &(Zathura.Style.default_bg));
709 /* statusbar */
710 gtk_widget_modify_bg(GTK_WIDGET(Zathura.UI.statusbar), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_bg));
712 gtk_widget_modify_fg(GTK_WIDGET(Zathura.Global.status_text), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_fg));
713 gtk_widget_modify_fg(GTK_WIDGET(Zathura.Global.status_state), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_fg));
714 gtk_widget_modify_fg(GTK_WIDGET(Zathura.Global.status_buffer), GTK_STATE_NORMAL, &(Zathura.Style.statusbar_fg));
716 gtk_widget_modify_font(GTK_WIDGET(Zathura.Global.status_text), Zathura.Style.font);
717 gtk_widget_modify_font(GTK_WIDGET(Zathura.Global.status_state), Zathura.Style.font);
718 gtk_widget_modify_font(GTK_WIDGET(Zathura.Global.status_buffer), Zathura.Style.font);
720 /* inputbar */
721 gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_bg));
722 gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_fg));
723 gtk_widget_modify_font(GTK_WIDGET(Zathura.UI.inputbar), Zathura.Style.font);
725 g_object_set(G_OBJECT(Zathura.UI.inputbar), "has-frame", FALSE, NULL);
727 /* scrollbars */
728 if (show_scrollbars) {
729 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.UI.view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
730 } else {
731 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.UI.view), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
734 /* inputbar */
735 if (Zathura.Global.show_inputbar) {
736 gtk_widget_show(GTK_WIDGET(Zathura.UI.inputbar));
737 } else {
738 gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar));
741 /* statusbar */
742 if (Zathura.Global.show_statusbar) {
743 gtk_widget_show(GTK_WIDGET(Zathura.UI.statusbar));
744 } else {
745 gtk_widget_hide(GTK_WIDGET(Zathura.UI.statusbar));
750 //==========================================================================
752 // fix_path
754 //==========================================================================
755 gchar *fix_path (const gchar *path) {
756 if (!path) return NULL;
757 if (path[0] == '~') {
758 gchar *home_path = get_home_dir();
759 gchar *res = g_build_filename(home_path, path+1, NULL);
760 g_free(home_path);
761 return res;
763 return g_strdup(path);
767 //==========================================================================
769 // path_from_env
771 //==========================================================================
772 gchar *path_from_env (const gchar *var) {
773 gchar *env = fix_path(g_getenv(var)), *res;
774 if (!env) return NULL;
775 res = g_build_filename(env, "zathura", NULL);
776 g_free(env);
777 return res;
781 //==========================================================================
783 // get_home_dir
785 //==========================================================================
786 gchar *get_home_dir (void) {
787 const gchar *homedir = g_getenv("HOME");
788 return g_strdup(homedir ? homedir : g_get_home_dir());
792 //==========================================================================
794 // init_directories
796 //==========================================================================
797 void init_directories (void) {
798 /* setup directories */
799 if (!Zathura.Config.config_dir) {
800 #ifndef ZATHURA_NO_XDG
801 gchar *env = path_from_env("XDG_CONFIG_HOME");
802 if (env) Zathura.Config.config_dir = env;
803 else
804 #endif
805 Zathura.Config.config_dir = fix_path(CONFIG_DIR);
807 if (!Zathura.Config.data_dir) {
808 #ifndef ZATHURA_NO_XDG
809 gchar *env = path_from_env("XDG_DATA_HOME");
810 if (env) Zathura.Config.data_dir = env;
811 else
812 #endif
813 Zathura.Config.data_dir = fix_path(DATA_DIR);
816 /* create zathura (config/data) directory */
817 g_mkdir_with_parents(Zathura.Config.config_dir, 0771);
818 g_mkdir_with_parents(Zathura.Config.data_dir, 0771);
822 //==========================================================================
824 // init_bookmarks
826 //==========================================================================
827 void init_bookmarks (void) {
828 /* init variables */
829 Zathura.Bookmarks.number_of_bookmarks = 0;
830 Zathura.Bookmarks.bookmarks = NULL;
832 Zathura.Bookmarks.file = g_build_filename(Zathura.Config.data_dir, BOOKMARK_FILE, NULL);
833 //fprintf(stderr, "CFG: %s\n", Zathura.Bookmarks.file);
834 read_bookmarks_file();
838 //==========================================================================
840 // is_reserved_bm_name
842 //==========================================================================
843 static gboolean is_reserved_bm_name (const char *bm_name) {
844 for (int i = 0; i < BM_MAX; ++i) if (strcmp(bm_reserved_names[i], bm_name) == 0) return TRUE;
845 return FALSE;
849 //==========================================================================
851 // init_keylist
853 //==========================================================================
854 void init_keylist (void) {
855 ShortcutList *e = NULL, *p = NULL;
856 for (int i = 0; i < LENGTH(shortcuts); ++i) {
857 e = malloc(sizeof(ShortcutList));
858 if (!e) out_of_memory();
859 e->element = shortcuts[i];
860 e->next = NULL;
861 if (!Zathura.Bindings.sclist) Zathura.Bindings.sclist = e;
862 if (p) p->next = e;
863 p = e;
868 //==========================================================================
870 // init_settings
872 //==========================================================================
873 void init_settings (void) {
874 Zathura.State.filename = g_strdup((char*) default_text);
875 Zathura.Global.adjust_mode = adjust_open;
876 Zathura.Global.xcenter_mode = xcenter_open;
878 gtk_window_set_default_size(GTK_WINDOW(Zathura.UI.window), default_width, default_height);
879 if (default_maximize) gtk_window_maximize(GTK_WINDOW(Zathura.UI.window));
883 //==========================================================================
885 // init_zathura
887 //==========================================================================
888 void init_zathura (void) {
889 /* init mutexes */
890 g_static_mutex_init(&(Zathura.Lock.pdflib_lock));
891 g_static_mutex_init(&(Zathura.Lock.search_lock));
892 g_static_mutex_init(&(Zathura.Lock.pdf_obj_lock));
893 g_static_mutex_init(&(Zathura.Lock.select_lock));
895 /* other */
896 Zathura.Global.mode = NORMAL;
897 Zathura.Global.viewing_mode = NORMAL;
898 Zathura.Global.recolor = 0;
899 Zathura.Global.goto_mode = GOTO_MODE;
900 Zathura.Global.show_index = FALSE;
901 Zathura.Global.show_inputbar = TRUE;
902 Zathura.Global.show_statusbar = TRUE;
904 Zathura.State.pages = g_strdup("");
905 Zathura.State.scroll_percentage = 0;
907 Zathura.Marker.markers = NULL;
908 Zathura.Marker.number_of_markers = 0;
909 Zathura.Marker.last = -1;
911 Zathura.Search.results = NULL;
912 Zathura.Search.page = 0;
913 Zathura.Search.draw = FALSE;
914 Zathura.Search.query = NULL;
916 Zathura.FileMonitor.monitor = NULL;
917 Zathura.FileMonitor.file = NULL;
919 Zathura.StdinSupport.file = NULL;
921 /* window */
922 if (Zathura.UI.embed) {
923 Zathura.UI.window = gtk_plug_new(Zathura.UI.embed);
924 } else {
925 Zathura.UI.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
928 /* UI */
929 Zathura.UI.box = GTK_BOX(gtk_vbox_new(FALSE, 0));
930 Zathura.UI.continuous = GTK_BOX(gtk_vbox_new(FALSE, 0));
931 Zathura.UI.view = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
932 Zathura.UI.viewport = GTK_VIEWPORT(gtk_viewport_new(NULL, NULL));
933 Zathura.UI.drawing_area = gtk_drawing_area_new();
934 Zathura.UI.statusbar = gtk_event_box_new();
935 Zathura.UI.statusbar_entries = GTK_BOX(gtk_hbox_new(FALSE, 0));
936 Zathura.UI.inputbar = GTK_ENTRY(gtk_entry_new());
937 Zathura.UI.document = gtk_event_box_new();
939 /* window */
940 gtk_window_set_title(GTK_WINDOW(Zathura.UI.window), "zathura");
941 GdkGeometry hints = { 1, 1 };
942 gtk_window_set_geometry_hints(GTK_WINDOW(Zathura.UI.window), NULL, &hints, GDK_HINT_MIN_SIZE);
943 g_signal_connect(G_OBJECT(Zathura.UI.window), "destroy", G_CALLBACK(cb_destroy), NULL);
945 /* box */
946 gtk_box_set_spacing(Zathura.UI.box, 0);
947 gtk_container_add(GTK_CONTAINER(Zathura.UI.window), GTK_WIDGET(Zathura.UI.box));
949 /* continuous */
950 gtk_box_set_spacing(Zathura.UI.continuous, 5);
952 /* events */
953 gtk_container_add(GTK_CONTAINER(Zathura.UI.document), GTK_WIDGET(Zathura.UI.drawing_area));
954 gtk_widget_add_events(GTK_WIDGET(Zathura.UI.document), GDK_POINTER_MOTION_MASK|GDK_POINTER_MOTION_HINT_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK);
956 g_signal_connect(G_OBJECT(Zathura.UI.document), "button-press-event", G_CALLBACK(cb_view_button_pressed), NULL);
957 g_signal_connect(G_OBJECT(Zathura.UI.document), "button-release-event", G_CALLBACK(cb_view_button_release), NULL);
958 g_signal_connect(G_OBJECT(Zathura.UI.document), "motion-notify-event", G_CALLBACK(cb_view_motion_notify), NULL);
959 gtk_widget_show(Zathura.UI.document);
961 /* view */
962 g_signal_connect(G_OBJECT(Zathura.UI.view), "key-press-event", G_CALLBACK(cb_view_kb_pressed), NULL);
963 g_signal_connect(G_OBJECT(Zathura.UI.view), "size-allocate", G_CALLBACK(cb_view_resized), NULL);
964 g_signal_connect(G_OBJECT(Zathura.UI.view), "scroll-event", G_CALLBACK(cb_view_scrolled), NULL);
965 gtk_container_add(GTK_CONTAINER(Zathura.UI.view), GTK_WIDGET(Zathura.UI.viewport));
966 gtk_viewport_set_shadow_type(Zathura.UI.viewport, GTK_SHADOW_NONE);
968 /* drawing area */
969 gtk_widget_show(Zathura.UI.drawing_area);
970 g_signal_connect(G_OBJECT(Zathura.UI.drawing_area), "expose-event", G_CALLBACK(cb_draw), NULL);
972 /* statusbar */
973 Zathura.Global.status_text = GTK_LABEL(gtk_label_new(NULL));
974 Zathura.Global.status_state = GTK_LABEL(gtk_label_new(NULL));
975 Zathura.Global.status_buffer = GTK_LABEL(gtk_label_new(NULL));
977 gtk_misc_set_alignment(GTK_MISC(Zathura.Global.status_text), 0.0, 0.0);
978 gtk_misc_set_alignment(GTK_MISC(Zathura.Global.status_state), 1.0, 0.0);
979 gtk_misc_set_alignment(GTK_MISC(Zathura.Global.status_buffer), 1.0, 0.0);
981 gtk_misc_set_padding(GTK_MISC(Zathura.Global.status_text), 2.0, 4.0);
982 gtk_misc_set_padding(GTK_MISC(Zathura.Global.status_state), 2.0, 4.0);
983 gtk_misc_set_padding(GTK_MISC(Zathura.Global.status_buffer), 2.0, 4.0);
985 gtk_label_set_use_markup(Zathura.Global.status_text, TRUE);
986 gtk_label_set_use_markup(Zathura.Global.status_state, TRUE);
987 gtk_label_set_use_markup(Zathura.Global.status_buffer, TRUE);
989 gtk_box_pack_start(Zathura.UI.statusbar_entries, GTK_WIDGET(Zathura.Global.status_text), TRUE, TRUE, 2);
990 gtk_box_pack_start(Zathura.UI.statusbar_entries, GTK_WIDGET(Zathura.Global.status_buffer), FALSE, FALSE, 2);
991 gtk_box_pack_start(Zathura.UI.statusbar_entries, GTK_WIDGET(Zathura.Global.status_state), FALSE, FALSE, 2);
993 gtk_container_add(GTK_CONTAINER(Zathura.UI.statusbar), GTK_WIDGET(Zathura.UI.statusbar_entries));
995 /* inputbar */
996 gtk_entry_set_inner_border(Zathura.UI.inputbar, NULL);
997 gtk_entry_set_has_frame( Zathura.UI.inputbar, FALSE);
998 gtk_editable_set_editable( GTK_EDITABLE(Zathura.UI.inputbar), TRUE);
1000 Zathura.Handler.inputbar_key_press_event = g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "key-press-event", G_CALLBACK(cb_inputbar_kb_pressed), NULL);
1001 Zathura.Handler.inputbar_activate = g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "activate", G_CALLBACK(cb_inputbar_activate), NULL);
1003 /* packing */
1004 gtk_box_pack_start(Zathura.UI.box, GTK_WIDGET(Zathura.UI.view), TRUE, TRUE, 0);
1005 gtk_box_pack_start(Zathura.UI.box, GTK_WIDGET(Zathura.UI.statusbar), FALSE, FALSE, 0);
1006 gtk_box_pack_end(Zathura.UI.box, GTK_WIDGET(Zathura.UI.inputbar), FALSE, FALSE, 0);
1010 //==========================================================================
1012 // add_marker
1014 //==========================================================================
1015 void add_marker (int id) {
1016 if (id < 0x30 || id > 0x7A) return;
1017 /* current information */
1018 int page_number = Zathura.PDF.page_number;
1019 /* search if entry already exists */
1020 for (int i = 0; i < Zathura.Marker.number_of_markers; ++i) {
1021 if (Zathura.Marker.markers[i].id == id) {
1022 Zathura.Marker.markers[i].page = page_number;
1023 Zathura.Marker.last = page_number;
1024 return;
1027 /* add new marker */
1028 int marker_index = Zathura.Marker.number_of_markers++;
1029 Zathura.Marker.markers = safe_realloc((void**)&Zathura.Marker.markers, Zathura.Marker.number_of_markers, sizeof(Marker));
1030 if (!Zathura.Marker.markers) out_of_memory();
1031 Zathura.Marker.markers[marker_index].id = id;
1032 Zathura.Marker.markers[marker_index].page = page_number;
1033 Zathura.Marker.last = page_number;
1037 //==========================================================================
1039 // build_index
1041 //==========================================================================
1042 void build_index (GtkTreeModel *model, GtkTreeIter *parent, PopplerIndexIter *index_iter) {
1043 do {
1044 GtkTreeIter tree_iter;
1045 PopplerIndexIter *child;
1046 PopplerAction *action;
1047 gchar *markup;
1049 action = poppler_index_iter_get_action(index_iter);
1050 if (!action) continue;
1052 markup = g_markup_escape_text (action->any.title, -1);
1054 gtk_tree_store_append(GTK_TREE_STORE(model), &tree_iter, parent);
1055 gtk_tree_store_set(GTK_TREE_STORE(model), &tree_iter, 0, markup, 1, action, -1);
1056 g_object_weak_ref(G_OBJECT(model), (GWeakNotify) poppler_action_free, action);
1057 g_free(markup);
1059 child = poppler_index_iter_get_child(index_iter);
1060 if (child) build_index(model, &tree_iter, child);
1061 poppler_index_iter_free(child);
1062 } while (poppler_index_iter_next(index_iter));
1066 //==========================================================================
1068 // get_page_screen_height
1070 //==========================================================================
1071 int get_page_screen_height (int page_id) {
1072 if (!Zathura.PDF.document || Zathura.PDF.number_of_pages < 1) return 0;
1073 if (page_id < 0 || page_id >= Zathura.PDF.number_of_pages) return 0;
1074 const double scale = ((double)Zathura.PDF.scale/100.0);
1075 Page *current_page = Zathura.PDF.pages[page_id];
1076 return (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*scale+Zathura.PDF.page_yskip_pix;
1080 //==========================================================================
1082 // get_current_page_screen_ytop
1084 //==========================================================================
1085 int get_current_page_screen_ytop (void) {
1086 if (!Zathura.PDF.document || Zathura.PDF.number_of_pages < 1) return 0;
1087 const int page_id = Zathura.PDF.page_number;
1088 if (page_id < 0) return 0;
1089 const double scale = ((double)Zathura.PDF.scale/100.0);
1090 if (page_id >= Zathura.PDF.number_of_pages) {
1091 // beyond the last page, show the whole last page
1092 if (!Zathura.UI.view) return 0;
1093 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
1094 int window_y = gtk_adjustment_get_page_size(adjustment);
1095 Page *current_page = Zathura.PDF.pages[Zathura.PDF.number_of_pages-1];
1096 const int scheight = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*scale+Zathura.PDF.page_gap;
1097 return (scheight >= window_y ? scheight-window_y : 0);
1098 } else {
1099 Page *current_page = Zathura.PDF.pages[Zathura.PDF.page_number];
1100 const double height = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width);
1101 return -(height*Zathura.PDF.page_yskip_t*scale+Zathura.PDF.page_yskip_pix);
1106 //==========================================================================
1108 // calculate_screen_offset
1110 // calculates screen page offset (and id) in the widget
1112 //==========================================================================
1113 void calculate_screen_offset (GtkWidget *widget, int scrx, int scry, double *offset_x, double *offset_y, int *out_page_id) {
1114 const double scale = ((double)Zathura.PDF.scale/100.0);
1115 int ypos = get_current_page_screen_ytop();
1116 int page_id = Zathura.PDF.page_number;
1118 int window_x, window_y;
1119 gdk_drawable_get_size(widget->window, &window_x, &window_y);
1121 //fprintf(stderr, "=== scr:(%d,%d); ypos=%d; page_id=%d; win:(%d,%d) ===\n", scrx, scry, ypos, page_id, window_x, window_y);
1122 while (ypos < window_y && page_id < Zathura.PDF.number_of_pages) {
1123 Page *current_page = Zathura.PDF.pages[page_id];
1124 const int scheight = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*scale;
1125 if (scry >= ypos && scry < ypos+scheight) {
1126 // this page!
1127 const int scwidth = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->width : current_page->height)*scale;
1128 if (offset_x) {
1129 if (window_x > scwidth) {
1130 switch (Zathura.Global.xcenter_mode) {
1131 case CENTER_LEFT_TOP: *offset_x = 0; break;
1132 case CENTER_RIGHT_BOTTOM: *offset_x = window_x-scwidth; break;
1133 default: *offset_x = (window_x-scwidth)/2; break;
1135 } else {
1136 *offset_x = 0;
1139 if (offset_y) *offset_y = ypos;
1140 if (out_page_id) *out_page_id = page_id;
1141 //fprintf(stderr, " RES: pos=(%g,%g); page_id=%d\n", *offset_x, *offset_y, *out_page_id);
1142 return;
1144 ypos += scheight+Zathura.PDF.page_gap;
1145 page_id += 1;
1148 if (out_page_id) *out_page_id = -1; // mark as invalid
1149 if (offset_x) *offset_x = 0;
1150 if (offset_y) *offset_y = 0;
1152 /* old and wrong code
1153 double width, height;
1154 const double scale = ((double)Zathura.PDF.scale/100.0);
1155 int window_x, window_y;
1157 Page *current_page = Zathura.PDF.pages[Zathura.PDF.page_number];
1158 const double page_width = current_page->width;
1159 const double page_height = current_page->height;
1161 if (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180) {
1162 width = page_width*scale;
1163 height = page_height*scale;
1164 } else {
1165 width = page_height*scale;
1166 height = page_width*scale;
1169 gdk_drawable_get_size(widget->window, &window_x, &window_y);
1171 *offset_x = (window_x > width ? (window_x-width)/2 : 0);
1172 *offset_y = (window_y > height ? (window_y-height)/2 : 0);
1177 //==========================================================================
1179 // calc_current_document_screen_offset
1181 //==========================================================================
1182 int calc_current_document_screen_offset (void) {
1183 if (!Zathura.PDF.document || Zathura.PDF.number_of_pages == 0) return 0;
1185 int dest_page_id = Zathura.PDF.page_number;
1186 if (dest_page_id < 0) dest_page_id = 1;
1187 if (dest_page_id >= Zathura.PDF.number_of_pages) dest_page_id = Zathura.PDF.number_of_pages-1;
1189 //fprintf(stderr, "curr_page_id=%d; t=%g; px=%d\n", dest_page_id, Zathura.PDF.page_yskip_t, Zathura.PDF.page_yskip_pix);
1191 const double scale = ((double)Zathura.PDF.scale/100.0);
1192 Page *current_page;
1194 int curryofs = 0;
1195 for (int page_id = 0; page_id < dest_page_id; ++page_id) {
1196 current_page = Zathura.PDF.pages[page_id];
1197 const int scheight = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*scale;
1198 curryofs += scheight+Zathura.PDF.page_gap;
1201 current_page = Zathura.PDF.pages[dest_page_id];
1202 curryofs += ((Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*Zathura.PDF.page_yskip_t*scale+Zathura.PDF.page_yskip_pix);
1204 //fprintf(stderr, "curr_page_id=%d; t=%g; px=%d; curryofs=%d\n", dest_page_id, Zathura.PDF.page_yskip_t, Zathura.PDF.page_yskip_pix, curryofs);
1205 return curryofs;
1209 //==========================================================================
1211 // calc_document_bottom_pos
1213 //==========================================================================
1214 void calc_document_bottom_pos (int *out_page_id, double *out_page_yskip_t, int *out_page_yskip_pix) {
1215 int tmppgid = 0, tmppgofs = 0;
1216 double tmpt = 0;
1217 if (!out_page_id) out_page_id = &tmppgid;
1218 if (!out_page_yskip_t) out_page_yskip_t = &tmpt;
1219 if (!out_page_yskip_pix) out_page_yskip_pix = &tmppgofs;
1221 if (!Zathura.PDF.document || Zathura.PDF.number_of_pages < 1) {
1222 *out_page_id = 0;
1223 *out_page_yskip_t = 0;
1224 *out_page_yskip_pix = 0;
1225 return;
1228 const int page_id = Zathura.PDF.number_of_pages-1;
1230 *out_page_id = page_id;
1231 *out_page_yskip_t = 0;
1232 *out_page_yskip_pix = 0;
1234 if (!Zathura.UI.view) return;
1236 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
1237 int window_y = gtk_adjustment_get_page_size(adjustment);
1238 Page *current_page = Zathura.PDF.pages[page_id];
1239 const double scale = ((double)Zathura.PDF.scale/100.0);
1240 const int scheight = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*scale;
1241 if (scheight > window_y) {
1242 *out_page_yskip_pix = 0;
1243 int yskip = scheight-window_y;
1244 *out_page_yskip_t = (double)yskip/(double)scheight;
1249 //==========================================================================
1251 // convert_screen_document_offset_to_pos
1253 //==========================================================================
1254 void convert_screen_document_offset_to_pos (int docofs, int *out_page_id, double *out_page_yskip_t, int *out_page_yskip_pix) {
1255 int tmppgid = 0, tmppgofs = 0;
1256 double tmpt = 0;
1257 if (!out_page_id) out_page_id = &tmppgid;
1258 if (!out_page_yskip_t) out_page_yskip_t = &tmpt;
1259 if (!out_page_yskip_pix) out_page_yskip_pix = &tmppgofs;
1261 if (!Zathura.PDF.document || Zathura.PDF.number_of_pages < 1) {
1262 *out_page_id = 0;
1263 *out_page_yskip_t = 0;
1264 *out_page_yskip_pix = 0;
1265 return;
1268 if (docofs < 0) docofs = 0;
1270 const double scale = ((double)Zathura.PDF.scale/100.0);
1272 //fprintf(stderr, "docofs=%d\n", docofs);
1274 int curryofs = 0;
1275 for (int page_id = 0; page_id < Zathura.PDF.number_of_pages; ++page_id) {
1276 Page *current_page = Zathura.PDF.pages[page_id];
1277 const double height = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width);
1278 const int scheight = height*scale;
1279 const int pgend = curryofs+scheight;
1280 if (docofs >= curryofs && docofs < pgend+Zathura.PDF.page_gap) {
1281 *out_page_id = page_id;
1282 if (docofs < pgend) {
1283 // some part of the page is visible
1284 *out_page_yskip_t = (double)(docofs-curryofs)/(double)scheight;
1285 *out_page_yskip_pix = 0;
1286 } else {
1287 // no page visible, we are inside a gap
1288 *out_page_yskip_t = 1;
1289 *out_page_yskip_pix = docofs-pgend;
1291 // if this is the last page, don't allow to scroll it completely out of view
1292 if (Zathura.UI.view && page_id == Zathura.PDF.number_of_pages-1) {
1293 int pid, yofs;
1294 double t;
1295 calc_document_bottom_pos(&pid, &t, &yofs);
1296 if (*out_page_yskip_t > t) {
1297 *out_page_yskip_t = t;
1298 if (*out_page_yskip_pix > yofs) *out_page_yskip_pix = yofs;
1301 return;
1303 curryofs = pgend+Zathura.PDF.page_gap;
1306 // beyond the last page, show the whole last page
1307 calc_document_bottom_pos(out_page_id, out_page_yskip_t, out_page_yskip_pix);
1311 //==========================================================================
1313 // scroll_up_pixels_no_draw
1315 //==========================================================================
1316 void scroll_up_pixels_no_draw (int count) {
1317 if (!count) return;
1318 int currypos = calc_current_document_screen_offset();
1319 currypos -= count;
1320 convert_screen_document_offset_to_pos(currypos, &Zathura.PDF.page_number, &Zathura.PDF.page_yskip_t, &Zathura.PDF.page_yskip_pix);
1324 //==========================================================================
1326 // scroll_down_pixels_no_draw
1328 //==========================================================================
1329 void scroll_down_pixels_no_draw (int count) {
1330 if (!count) return;
1331 int currypos = calc_current_document_screen_offset();
1332 currypos += count;
1333 convert_screen_document_offset_to_pos(currypos, &Zathura.PDF.page_number, &Zathura.PDF.page_yskip_t, &Zathura.PDF.page_yskip_pix);
1337 //==========================================================================
1339 // create_page_surface
1341 //==========================================================================
1342 cairo_surface_t *create_page_surface (int page_id) {
1343 if (!Zathura.PDF.document || page_id < 0 || page_id >= Zathura.PDF.number_of_pages) return NULL;
1345 const double scale = ((double)Zathura.PDF.scale/100.0);
1347 PageSurfCache *cc = surfCacheGet(page_id, scale);
1348 // create new surface if necessary
1349 if (!cc->surf) {
1350 //fprintf(stderr, "creating new page surface for page #%d\n", page_id);
1351 int rotate = Zathura.PDF.rotate;
1352 Page *current_page = Zathura.PDF.pages[page_id];
1353 cairo_surface_t *surf = NULL;
1354 const double page_width = current_page->width;
1355 const double page_height = current_page->height;
1357 double width, height;
1358 if (rotate == 0 || rotate == 180) {
1359 width = page_width*scale;
1360 height = page_height*scale;
1361 } else {
1362 width = page_height*scale;
1363 height = page_width*scale;
1366 surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
1368 cairo_t *cairo = cairo_create(surf);
1370 cairo_save(cairo);
1371 cairo_set_source_rgb(cairo, 1, 1, 1);
1372 cairo_rectangle(cairo, 0, 0, width, height);
1373 cairo_fill(cairo);
1374 cairo_restore(cairo);
1375 cairo_save(cairo);
1377 switch (rotate) {
1378 case 90: cairo_translate(cairo, width, 0); break;
1379 case 180: cairo_translate(cairo, width, height); break;
1380 case 270: cairo_translate(cairo, 0, height); break;
1381 default: cairo_translate(cairo, 0, 0); break;
1384 if (scale != 1.0) cairo_scale(cairo, scale, scale);
1385 if (rotate != 0) cairo_rotate(cairo, rotate*G_PI/180.0);
1387 MTLOCK_PDFLIB();
1388 poppler_page_render(current_page->page, cairo);
1389 MTUNLOCK_PDFLIB();
1391 cairo_restore(cairo);
1392 cairo_destroy(cairo);
1394 if (Zathura.Global.recolor) {
1395 unsigned char *image = cairo_image_surface_get_data(surf);
1396 int width = cairo_image_surface_get_width(surf);
1397 int height = cairo_image_surface_get_height(surf);
1398 int rowstride = cairo_image_surface_get_stride(surf);
1400 /* recolor code based on qimageblitz library flatten() function
1401 (http://sourceforge.net/projects/qimageblitz/) */
1403 int r1 = Zathura.Style.recolor_darkcolor.red/257;
1404 int g1 = Zathura.Style.recolor_darkcolor.green/257;
1405 int b1 = Zathura.Style.recolor_darkcolor.blue/257;
1406 int r2 = Zathura.Style.recolor_lightcolor.red/257;
1407 int g2 = Zathura.Style.recolor_lightcolor.green/257;
1408 int b2 = Zathura.Style.recolor_lightcolor.blue/257;
1410 int min = 0x00;
1411 int max = 0xFF;
1412 int mean;
1414 float sr = ((float)r2-r1)/(max-min);
1415 float sg = ((float)g2-g1)/(max-min);
1416 float sb = ((float)b2-b1)/(max-min);
1418 for (int y = 0; y < height; ++y) {
1419 unsigned char *data = image+y*rowstride;
1420 for (int x = 0; x < width; ++x) {
1421 mean = (data[0]+data[1]+data[2])/3;
1422 data[2] = sr*(mean-min)+r1+0.5;
1423 data[1] = sg*(mean-min)+g1+0.5;
1424 data[0] = sb*(mean-min)+b1+0.5;
1425 data += 4;
1430 cc->surf = surf;
1431 } else {
1432 //fprintf(stderr, "reusing new page surface for page #%d\n", page_id);
1435 return cc->surf;
1439 //==========================================================================
1441 // draw
1443 //==========================================================================
1444 void draw (int page_id) {
1445 cairo_surface_t *surf = create_page_surface(page_id);
1446 if (!surf) return;
1448 int w = cairo_image_surface_get_width(surf);
1449 int h = cairo_image_surface_get_height(surf);
1451 gtk_widget_set_size_request(Zathura.UI.drawing_area, w, h);
1452 gtk_widget_queue_draw(Zathura.UI.drawing_area);
1456 //==========================================================================
1458 // change_mode
1460 //==========================================================================
1461 void change_mode (int mode) {
1462 char *mode_text = 0;
1463 for (unsigned int i = 0; i != LENGTH(mode_names); ++i) {
1464 if (mode_names[i].mode == mode) {
1465 mode_text = mode_names[i].display;
1466 break;
1469 if (!mode_text) {
1470 switch(mode) {
1471 case ADD_MARKER: mode_text = ""; break;
1472 case EVAL_MARKER: mode_text = ""; break;
1473 default: mode_text = ""; mode = NORMAL; break;
1476 Zathura.Global.mode = mode;
1477 notify(DEFAULT, mode_text);
1481 //==========================================================================
1483 // close_file
1485 //==========================================================================
1486 void close_file (gboolean keep_monitor) {
1487 if (!Zathura.PDF.document) return;
1489 /* clean up pages */
1490 for (int i = 0; i < Zathura.PDF.number_of_pages; ++i) {
1491 Page *current_page = Zathura.PDF.pages[i];
1492 g_object_unref(current_page->page);
1493 if (current_page->label) g_free(current_page->label);
1494 free(current_page);
1497 /* save bookmarks */
1498 if (Zathura.Bookmarks.data) {
1499 read_bookmarks_file();
1500 if (save_position) {
1501 /* set current page */
1502 g_key_file_set_integer(Zathura.Bookmarks.data, Zathura.PDF.file, bm_reserved_names[BM_PAGE_ENTRY], Zathura.PDF.page_number);
1503 /* set page offset */
1504 g_key_file_set_double(Zathura.Bookmarks.data, Zathura.PDF.file, bm_reserved_names[BM_PAGE_OFFSET], Zathura.PDF.page_yskip_t+Zathura.PDF.page_yskip_pix);
1506 if (save_zoom_level) {
1507 /* set zoom level */
1508 g_key_file_set_integer(Zathura.Bookmarks.data, Zathura.PDF.file, bm_reserved_names[BM_PAGE_SCALE], Zathura.PDF.scale);
1510 write_bookmarks_file();
1511 free_bookmarks();
1514 /* inotify */
1515 if (!keep_monitor) {
1516 g_object_unref(Zathura.FileMonitor.monitor);
1517 Zathura.FileMonitor.monitor = NULL;
1518 if (Zathura.FileMonitor.file) {
1519 g_object_unref(Zathura.FileMonitor.file);
1520 Zathura.FileMonitor.file = NULL;
1524 /* reset values */
1525 g_free(Zathura.PDF.pages);
1526 g_object_unref(Zathura.PDF.document);
1527 g_free(Zathura.State.pages);
1528 gtk_window_set_title(GTK_WINDOW(Zathura.UI.window), "zathura");
1530 Zathura.State.pages = g_strdup("");
1531 g_free(Zathura.State.filename);
1532 Zathura.State.filename = g_strdup((char *)default_text);
1534 MTLOCK_PDFOBJ();
1535 Zathura.PDF.document = NULL;
1537 if (!keep_monitor) {
1538 g_free(Zathura.PDF.file);
1539 g_free(Zathura.PDF.password);
1540 Zathura.PDF.file = NULL;
1541 Zathura.PDF.password = NULL;
1542 Zathura.PDF.page_number = 0;
1543 Zathura.PDF.scale = 0;
1544 Zathura.PDF.rotate = 0;
1546 Zathura.PDF.number_of_pages = 0;
1547 Zathura.PDF.page_yskip_t = 0;
1548 Zathura.PDF.page_yskip_pix = 0;
1549 Zathura.PDF.page_gap = 8;
1550 MTUNLOCK_PDFOBJ();
1552 surfCacheDestroy();
1554 /* destroy index */
1555 if (Zathura.UI.index) {
1556 gtk_widget_destroy(Zathura.UI.index);
1557 Zathura.UI.index = NULL;
1560 /* destroy information */
1561 if (Zathura.UI.information) {
1562 gtk_widget_destroy(Zathura.UI.information);
1563 Zathura.UI.information = NULL;
1566 /* free markers */
1567 if (Zathura.Marker.markers) free(Zathura.Marker.markers);
1568 Zathura.Marker.number_of_markers = 0;
1569 Zathura.Marker.last = -1;
1571 update_status();
1575 //==========================================================================
1577 // enter_password
1579 //==========================================================================
1580 void enter_password (void) {
1581 /* replace default inputbar handler */
1582 g_signal_handler_disconnect((gpointer) Zathura.UI.inputbar, Zathura.Handler.inputbar_activate);
1583 Zathura.Handler.inputbar_activate = g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "activate", G_CALLBACK(cb_inputbar_password_activate), NULL);
1585 Argument argument;
1586 argument.data = "Enter password: ";
1587 sc_focus_inputbar(&argument);
1591 //==========================================================================
1593 // eval_marker
1595 //==========================================================================
1596 void eval_marker (int id) {
1597 /* go to last marker */
1598 if (id == 0x27) {
1599 int current_page = Zathura.PDF.page_number;
1600 set_page(Zathura.Marker.last);
1601 Zathura.Marker.last = current_page;
1602 return;
1604 /* search markers */
1605 for (int i = 0; i < Zathura.Marker.number_of_markers; ++i) {
1606 if (Zathura.Marker.markers[i].id == id) {
1607 set_page(Zathura.Marker.markers[i].page);
1608 return;
1614 //==========================================================================
1616 // highlight_result
1618 //==========================================================================
1619 void highlight_result (int page_id, PopplerRectangle *rectangle) {
1620 PopplerRectangle *trect = poppler_rectangle_copy(rectangle);
1621 cairo_t *cairo = cairo_create(create_page_surface(page_id));
1622 cairo_set_source_rgba(cairo, Zathura.Style.search_highlight.red, Zathura.Style.search_highlight.green, Zathura.Style.search_highlight.blue, transparency);
1624 recalc_rectangle(page_id, trect);
1625 cairo_rectangle(cairo, trect->x1, trect->y1, trect->x2-trect->x1, trect->y2-trect->y1);
1626 poppler_rectangle_free(trect);
1627 cairo_fill(cairo);
1628 cairo_destroy(cairo);
1632 //==========================================================================
1634 // notify
1636 //==========================================================================
1637 void notify (int level, const char *message) {
1638 switch(level) {
1639 case ERROR:
1640 gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_e_bg));
1641 gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_e_fg));
1642 break;
1643 case WARNING:
1644 gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_w_bg));
1645 gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.notification_w_fg));
1646 break;
1647 default:
1648 gtk_widget_modify_base(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_bg));
1649 gtk_widget_modify_text(GTK_WIDGET(Zathura.UI.inputbar), GTK_STATE_NORMAL, &(Zathura.Style.inputbar_fg));
1650 break;
1652 if (message) gtk_entry_set_text(Zathura.UI.inputbar, message);
1656 //==========================================================================
1658 // open_file
1660 //==========================================================================
1661 gboolean open_file (char *path, char *password) {
1662 MTLOCK_PDFOBJ();
1664 /* specify path max */
1665 size_t pm;
1666 #ifdef PATH_MAX
1667 pm = PATH_MAX;
1668 #else
1669 pm = pathconf(path, _PC_PATH_MAX);
1670 if (pm <= 0) pm = 4096;
1671 #endif
1673 char *rpath = NULL;
1674 if (path[0] == '~') {
1675 gchar *home_path = get_home_dir();
1676 rpath = g_build_filename(home_path, path+1, NULL);
1677 g_free(home_path);
1678 } else {
1679 rpath = g_strdup(path);
1682 /* get filename */
1683 char *file = (char *)g_malloc0(sizeof(char)*pm);
1684 if (!file) out_of_memory();
1686 if (!realpath(rpath, file)) {
1687 notify(ERROR, "File does not exist");
1688 g_free(file);
1689 g_free(rpath);
1690 MTUNLOCK_PDFOBJ();
1691 return FALSE;
1693 g_free(rpath);
1695 /* check if file exists */
1696 if (!g_file_test(file, G_FILE_TEST_IS_REGULAR)) {
1697 notify(ERROR, "File does not exist");
1698 g_free(file);
1699 MTUNLOCK_PDFOBJ();
1700 return FALSE;
1703 /* close old file */
1704 MTUNLOCK_PDFOBJ();
1705 close_file(FALSE);
1706 MTLOCK_PDFOBJ();
1708 /* format path */
1709 GError *error = NULL;
1710 char *file_uri = g_filename_to_uri(file, NULL, &error);
1711 if (!file_uri) {
1712 if (file) g_free(file);
1713 char *message = g_strdup_printf("Can not open file: %s", error->message);
1714 notify(ERROR, message);
1715 g_free(message);
1716 g_error_free(error);
1717 MTUNLOCK_PDFOBJ();
1718 return FALSE;
1721 /* open file */
1722 MTLOCK_PDFLIB();
1723 Zathura.PDF.document = poppler_document_new_from_file(file_uri, password, &error);
1724 MTUNLOCK_PDFLIB();
1726 if (!Zathura.PDF.document) {
1727 if (error->code == 1) {
1728 g_free(file_uri);
1729 g_error_free(error);
1730 Zathura.PDF.file = file;
1731 MTUNLOCK_PDFOBJ();
1732 enter_password();
1733 return FALSE;
1734 } else {
1735 char *message = g_strdup_printf("Can not open file: %s", error->message);
1736 notify(ERROR, message);
1737 g_free(file_uri);
1738 g_free(message);
1739 g_error_free(error);
1740 MTUNLOCK_PDFOBJ();
1741 return FALSE;
1745 /* save password */
1746 g_free(Zathura.PDF.password);
1747 Zathura.PDF.password = (password ? g_strdup(password) : NULL);
1749 /* inotify */
1750 if (!Zathura.FileMonitor.monitor) {
1751 GFile *file = g_file_new_for_uri(file_uri);
1752 if (file) {
1753 Zathura.FileMonitor.monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
1754 if (Zathura.FileMonitor.monitor) g_signal_connect(G_OBJECT(Zathura.FileMonitor.monitor), "changed", G_CALLBACK(cb_watch_file), NULL);
1755 Zathura.FileMonitor.file = file;
1759 g_free(file_uri);
1761 MTLOCK_PDFLIB();
1762 Zathura.PDF.number_of_pages = poppler_document_get_n_pages(Zathura.PDF.document);
1763 MTUNLOCK_PDFLIB();
1764 g_free(Zathura.PDF.file);
1765 Zathura.PDF.file = file;
1766 Zathura.PDF.scale = 100;
1767 Zathura.PDF.rotate = 0;
1768 Zathura.PDF.page_gap = 8;
1769 if (Zathura.State.filename) g_free(Zathura.State.filename);
1770 Zathura.State.filename = g_markup_escape_text(file, -1);
1771 Zathura.PDF.pages = g_malloc(Zathura.PDF.number_of_pages*sizeof(Page *));
1772 surfCacheDestroy();
1774 if (!Zathura.PDF.pages) out_of_memory();
1776 /* get pages and check label mode */
1777 MTLOCK_PDFLIB();
1778 Zathura.Global.enable_labelmode = FALSE;
1780 char *errmsg = NULL;
1781 printf("loading %d pages...\n", Zathura.PDF.number_of_pages);
1782 for (int i = 0; i < Zathura.PDF.number_of_pages; ++i) {
1783 //printf(" ...page #%d\n", i+1);
1784 Zathura.PDF.pages[i] = malloc(sizeof(Page));
1785 if (!Zathura.PDF.pages[i]) out_of_memory();
1787 Zathura.PDF.pages[i]->id = i+1;
1788 Zathura.PDF.pages[i]->page = poppler_document_get_page(Zathura.PDF.document, i);
1789 if (!Zathura.PDF.pages[i]->page) {
1790 fprintf(stderr, "WARNING! can't load page #%d of %d...\n", i+1, Zathura.PDF.number_of_pages);
1791 // abort here, it seems that we're out of memory (or poppler failed)
1792 free(Zathura.PDF.pages[i]);
1793 Zathura.PDF.pages[i] = NULL;
1794 errmsg = g_strdup_printf("Aborted loading on page #%d of %d due to errors", i+1, Zathura.PDF.number_of_pages);
1795 Zathura.PDF.number_of_pages = i;
1796 break;
1798 //printf(" ...page #%d: %p\n", i+1, Zathura.PDF.pages[i]->page);
1799 g_object_get(G_OBJECT(Zathura.PDF.pages[i]->page), "label", &(Zathura.PDF.pages[i]->label), NULL);
1801 /* check if it is necessary to use the label mode */
1802 int label_int = atoi(Zathura.PDF.pages[i]->label);
1803 if (label_int == 0 || label_int != i+1) Zathura.Global.enable_labelmode = TRUE;
1805 poppler_page_get_size(Zathura.PDF.pages[i]->page, &Zathura.PDF.pages[i]->width, &Zathura.PDF.pages[i]->height);
1806 if (Zathura.PDF.pages[i]->width < 1) Zathura.PDF.pages[i]->width = 1;
1807 if (Zathura.PDF.pages[i]->height < 1) Zathura.PDF.pages[i]->height = 1;
1808 //fprintf(stderr, "PAGE #%d: size=(%gx%g)\n", i, Zathura.PDF.pages[i]->width, Zathura.PDF.pages[i]->height);
1810 MTUNLOCK_PDFLIB();
1812 /* set correct goto mode */
1813 if (!Zathura.Global.enable_labelmode && GOTO_MODE == GOTO_LABELS) Zathura.Global.goto_mode = GOTO_DEFAULT;
1815 /* start page */
1816 int start_page = 0;
1817 Zathura.PDF.page_yskip_t = 0;
1818 Zathura.PDF.page_yskip_pix = 0;
1820 /* bookmarks */
1821 if (Zathura.Bookmarks.data && g_key_file_has_group(Zathura.Bookmarks.data, file)) {
1822 /* get last opened page */
1823 if (save_position && g_key_file_has_key(Zathura.Bookmarks.data, file, bm_reserved_names[BM_PAGE_ENTRY], NULL)) {
1824 start_page = g_key_file_get_integer(Zathura.Bookmarks.data, file, bm_reserved_names[BM_PAGE_ENTRY], NULL);
1826 /* get page offset */
1827 if (save_position && g_key_file_has_key(Zathura.Bookmarks.data, file, bm_reserved_names[BM_PAGE_OFFSET], NULL)) {
1828 double ofs = g_key_file_get_double(Zathura.Bookmarks.data, file, bm_reserved_names[BM_PAGE_OFFSET], NULL);
1829 if (ofs < 0) ofs = 0;
1830 int px = trunc(ofs);
1831 ofs -= px;
1832 if (px > Zathura.PDF.page_gap) px = Zathura.PDF.page_gap;
1833 Zathura.PDF.page_yskip_t = ofs;
1834 Zathura.PDF.page_yskip_pix = px;
1836 /* get zoom level */
1837 if (save_zoom_level && g_key_file_has_key(Zathura.Bookmarks.data, file, bm_reserved_names[BM_PAGE_SCALE], NULL)) {
1838 Zathura.PDF.scale = g_key_file_get_integer(Zathura.Bookmarks.data, file, bm_reserved_names[BM_PAGE_SCALE], NULL);
1839 Zathura.Global.adjust_mode = ADJUST_NONE;
1841 if (Zathura.PDF.scale > zoom_max) Zathura.PDF.scale = zoom_max;
1842 if (Zathura.PDF.scale < zoom_min) Zathura.PDF.scale = zoom_min;
1844 /* open and read bookmark file */
1845 gsize number_of_keys = 0;
1846 char **keys = g_key_file_get_keys(Zathura.Bookmarks.data, file, &number_of_keys, NULL);
1848 for (gsize i = 0; i < number_of_keys; ++i) {
1849 if (!is_reserved_bm_name(keys[i])) {
1850 Zathura.Bookmarks.bookmarks = safe_realloc((void **)&Zathura.Bookmarks.bookmarks, Zathura.Bookmarks.number_of_bookmarks+1, sizeof(Bookmark));
1851 if (!Zathura.Bookmarks.bookmarks) out_of_memory();
1853 Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].id = g_strdup(keys[i]);
1854 Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].page = g_key_file_get_integer(Zathura.Bookmarks.data, file, keys[i], NULL);
1856 ++Zathura.Bookmarks.number_of_bookmarks;
1860 g_strfreev(keys);
1863 /* set window title */
1864 gtk_window_set_title(GTK_WINDOW(Zathura.UI.window), basename(file));
1866 /* show document */
1867 set_page_keep_ofs(start_page);
1868 update_status();
1870 MTUNLOCK_PDFOBJ();
1871 isc_abort(NULL);
1873 if (errmsg) {
1874 notify(WARNING, errmsg);
1875 g_free(errmsg);
1878 return TRUE;
1882 //==========================================================================
1884 // open_stdin
1886 //==========================================================================
1887 gboolean open_stdin (gchar *password) {
1888 GError *error = NULL;
1889 gchar *file = NULL;
1890 gint handle = g_file_open_tmp("zathura.stdin.XXXXXX.pdf", &file, &error);
1891 if (handle == -1) {
1892 gchar *message = g_strdup_printf("Can not create temporary file: %s", error->message);
1893 notify(ERROR, message);
1894 g_free(message);
1895 g_error_free(error);
1896 return FALSE;
1899 // read from stdin and dump to temporary file
1900 int stdinfno = fileno(stdin);
1901 if (stdinfno == -1) {
1902 gchar *message = g_strdup_printf("Can not read from stdin.");
1903 notify(ERROR, message);
1904 g_free(message);
1905 close(handle);
1906 g_unlink(file);
1907 g_free(file);
1908 return FALSE;
1912 char buffer[BUFSIZ];
1913 ssize_t count = 0;
1914 while ((count = read(stdinfno, buffer, BUFSIZ)) > 0) {
1915 if (write(handle, buffer, count) != count) {
1916 gchar *message = g_strdup_printf("Can not write to temporary file: %s", file);
1917 notify(ERROR, message);
1918 g_free(message);
1919 close(handle);
1920 g_unlink(file);
1921 g_free(file);
1922 return FALSE;
1925 close(handle);
1927 if (count != 0) {
1928 gchar *message = g_strdup_printf("Can not read from stdin.");
1929 notify(ERROR, message);
1930 g_free(message);
1931 g_unlink(file);
1932 g_free(file);
1933 return FALSE;
1936 /* update data */
1937 if (Zathura.StdinSupport.file) g_unlink(Zathura.StdinSupport.file);
1938 g_free(Zathura.StdinSupport.file);
1939 Zathura.StdinSupport.file = file;
1941 return open_file(Zathura.StdinSupport.file, password);
1945 //==========================================================================
1947 // open_uri
1949 //==========================================================================
1950 void open_uri (char *uri) {
1951 char *escaped_uri = g_shell_quote(uri);
1952 char *uri_cmd = g_strdup_printf(uri_command, escaped_uri);
1953 system(uri_cmd);
1954 g_free(uri_cmd);
1955 g_free(escaped_uri);
1959 //==========================================================================
1961 // out_of_memory
1963 //==========================================================================
1964 void out_of_memory (void) {
1965 printf("error: out of memory\n");
1966 *(char*)0 = 0;
1967 exit(-1);
1971 //==========================================================================
1973 // update_status
1975 //==========================================================================
1976 void update_status (void) {
1977 /* update text */
1978 gtk_label_set_markup((GtkLabel *)Zathura.Global.status_text, Zathura.State.filename);
1979 /* update pages */
1980 if (Zathura.PDF.document && Zathura.PDF.pages) {
1981 int page = Zathura.PDF.page_number;
1982 g_free(Zathura.State.pages);
1983 Zathura.State.pages = g_strdup_printf("[%i/%i]", page+1, Zathura.PDF.number_of_pages);
1985 /* update state */
1986 char *zoom_level = (Zathura.PDF.scale != 0 ? g_strdup_printf("%d%%", Zathura.PDF.scale) : g_strdup(""));
1987 char *goto_mode = (Zathura.Global.goto_mode == GOTO_LABELS ? "L" : (Zathura.Global.goto_mode == GOTO_OFFSET ? "O" : "D"));
1988 char *status_text = g_strdup_printf("%s [%s] %s (%d%%)", zoom_level, goto_mode, Zathura.State.pages, Zathura.State.scroll_percentage);
1989 gtk_label_set_markup((GtkLabel *)Zathura.Global.status_state, status_text);
1990 g_free(status_text);
1991 g_free(zoom_level);
1995 //==========================================================================
1997 // read_bookmarks_file
1999 // this also removes groups that has no corresponding file
2001 //==========================================================================
2002 void read_bookmarks_file (void) {
2003 /* free it at first */
2004 if (Zathura.Bookmarks.data) g_key_file_free(Zathura.Bookmarks.data);
2005 /* create or open existing bookmark file */
2006 Zathura.Bookmarks.data = g_key_file_new();
2007 if (!g_file_test(Zathura.Bookmarks.file, G_FILE_TEST_IS_REGULAR)) {
2008 /* file does not exist */
2009 gchar *s = g_strdup_printf("# Zathura bookmarks\n\n[%s]\n%s=0\n", main_group_name, lastcheck_key_name);
2010 g_file_set_contents(Zathura.Bookmarks.file, s, -1, NULL);
2011 g_free(s);
2013 GError *error = NULL;
2014 if (!g_key_file_load_from_file(Zathura.Bookmarks.data, Zathura.Bookmarks.file, G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error)) {
2015 gchar *message = g_strdup_printf("Could not load bookmark file: %s", error->message);
2016 notify(ERROR, message);
2017 g_free(message);
2022 //==========================================================================
2024 // write_bookmarks_file
2026 //==========================================================================
2027 void write_bookmarks_file (void) {
2028 if (!Zathura.Bookmarks.data) return; /* nothing to do */
2030 /* scan groups, and remove groups for which we don't have a file anymore */
2031 GError *err = NULL;
2032 double lastcheck = 0;
2033 if (g_key_file_has_key(Zathura.Bookmarks.data, main_group_name, lastcheck_key_name, &err)) {
2034 g_key_file_get_double(Zathura.Bookmarks.data, main_group_name, lastcheck_key_name, &err);
2036 if (err) g_error_free(err);
2037 const double currtime = sys_time();
2038 /* check once a hour */
2039 if (lastcheck+60*60 <= currtime /*|| true*/) {
2040 gchar **groups = g_key_file_get_groups(Zathura.Bookmarks.data, NULL);
2041 if (groups) {
2042 for (gchar **grp = groups; *grp; ++grp) {
2043 gchar *gname = *grp;
2044 if (strcmp(gname, main_group_name) != 0 && access(gname, R_OK) != 0) {
2045 //fprintf(stderr, "***<%s>\n", gname);
2046 err = NULL;
2047 g_key_file_remove_group(Zathura.Bookmarks.data, gname, &err);
2048 if (err) g_error_free(err);
2051 g_strfreev(groups);
2053 /* save check time */
2054 g_key_file_set_double(Zathura.Bookmarks.data, main_group_name, lastcheck_key_name, currtime);
2057 /* save bookmarks */
2058 for (int i = 0; i < Zathura.Bookmarks.number_of_bookmarks; ++i) {
2059 g_key_file_set_integer(Zathura.Bookmarks.data, Zathura.PDF.file, Zathura.Bookmarks.bookmarks[i].id, Zathura.Bookmarks.bookmarks[i].page);
2062 /* convert file and save it */
2063 gchar *bookmarks = g_key_file_to_data(Zathura.Bookmarks.data, NULL, NULL);
2064 g_file_set_contents(Zathura.Bookmarks.file, bookmarks, -1, NULL);
2065 g_free(bookmarks);
2069 //==========================================================================
2071 // free_bookmarks
2073 //==========================================================================
2074 void free_bookmarks (void) {
2075 for (int i = 0; i < Zathura.Bookmarks.number_of_bookmarks; ++i) g_free(Zathura.Bookmarks.bookmarks[i].id);
2076 free(Zathura.Bookmarks.bookmarks);
2077 Zathura.Bookmarks.bookmarks = NULL;
2078 Zathura.Bookmarks.number_of_bookmarks = 0;
2082 //==========================================================================
2084 // read_configuration_file
2086 //==========================================================================
2087 void read_configuration_file (const char *rcfile) {
2088 if (!rcfile) return;
2089 if (!g_file_test(rcfile, G_FILE_TEST_IS_REGULAR)) return;
2090 char *content = NULL;
2091 if (g_file_get_contents(rcfile, &content, NULL, NULL)) {
2092 gchar **lines = g_strsplit(content, "\n", -1);
2093 int n = g_strv_length(lines)-1;
2094 for (int i = 0; i <= n; ++i) {
2095 if (!strlen(lines[i])) continue;
2096 gchar **pre_tokens = g_strsplit_set(lines[i], "\t ", -1);
2097 int pre_length = g_strv_length(pre_tokens);
2098 gchar** tokens = g_malloc0(sizeof(gchar*)*(pre_length+1));
2099 gchar** tokp = tokens;
2100 int length = 0;
2101 for (int f = 0; f != pre_length; ++f) {
2102 if (strlen(pre_tokens[f])) {
2103 *tokp++ = pre_tokens[f];
2104 ++length;
2107 if (!strcmp(tokens[0], "set")) cmd_set(length-1, tokens+1);
2108 else if (!strcmp(tokens[0], "map")) cmd_map(length-1, tokens+1);
2109 g_free(tokens);
2111 g_strfreev(lines);
2112 g_free(content);
2117 //==========================================================================
2119 // read_configuration
2121 //==========================================================================
2122 void read_configuration (void) {
2123 char *zathurarc = g_build_filename(Zathura.Config.config_dir, ZATHURA_RC, NULL);
2124 read_configuration_file(GLOBAL_RC);
2125 read_configuration_file(zathurarc);
2126 g_free(zathurarc);
2130 //==========================================================================
2132 // recalc_rectangle
2134 //==========================================================================
2135 void recalc_rectangle (int page_id, PopplerRectangle* rectangle) {
2136 double x1 = rectangle->x1;
2137 double x2 = rectangle->x2;
2138 double y1 = rectangle->y1;
2139 double y2 = rectangle->y2;
2140 Page *current_page = Zathura.PDF.pages[page_id];
2141 const double page_width = current_page->width;
2142 const double page_height = current_page->height;
2144 double scale = ((double) Zathura.PDF.scale/100.0);
2145 int rotate = Zathura.PDF.rotate;
2146 switch (rotate) {
2147 case 90:
2148 rectangle->x1 = y2*scale;
2149 rectangle->y1 = x1*scale;
2150 rectangle->x2 = y1*scale;
2151 rectangle->y2 = x2*scale;
2152 break;
2153 case 180:
2154 rectangle->x1 = (page_width-x2)*scale;
2155 rectangle->y1 = y2*scale;
2156 rectangle->x2 = (page_width-x1)*scale;
2157 rectangle->y2 = y1*scale;
2158 break;
2159 case 270:
2160 rectangle->x1 = (page_height-y1)*scale;
2161 rectangle->y1 = (page_width-x2)*scale;
2162 rectangle->x2 = (page_height-y2)*scale;
2163 rectangle->y2 = (page_width-x1)*scale;
2164 break;
2165 default:
2166 rectangle->x1 = x1*scale;
2167 rectangle->y1 = (page_height-y1)*scale;
2168 rectangle->x2 = x2*scale;
2169 rectangle->y2 = (page_height-y2)*scale;
2170 break;
2175 //==========================================================================
2177 // create_completion_row
2179 //==========================================================================
2180 GtkEventBox *create_completion_row (GtkBox *results, char *command, char *description, gboolean group) {
2181 GtkBox *col = GTK_BOX(gtk_hbox_new(FALSE, 0));
2182 GtkEventBox *row = GTK_EVENT_BOX(gtk_event_box_new());
2183 GtkLabel *show_command = GTK_LABEL(gtk_label_new(NULL));
2184 GtkLabel *show_description = GTK_LABEL(gtk_label_new(NULL));
2185 gtk_misc_set_alignment(GTK_MISC(show_command), 0.0, 0.0);
2186 gtk_misc_set_alignment(GTK_MISC(show_description), 0.0, 0.0);
2187 if (group) {
2188 gtk_misc_set_padding(GTK_MISC(show_command), 2.0, 4.0);
2189 gtk_misc_set_padding(GTK_MISC(show_description), 2.0, 4.0);
2190 } else {
2191 gtk_misc_set_padding(GTK_MISC(show_command), 1.0, 1.0);
2192 gtk_misc_set_padding(GTK_MISC(show_description), 1.0, 1.0);
2194 gtk_label_set_use_markup(show_command, TRUE);
2195 gtk_label_set_use_markup(show_description, TRUE);
2196 gchar *c = g_markup_printf_escaped(FORMAT_COMMAND, (command ? command : ""));
2197 gchar *d = g_markup_printf_escaped(FORMAT_DESCRIPTION, (description ? description : ""));
2198 gtk_label_set_markup(show_command, c);
2199 gtk_label_set_markup(show_description, d);
2200 g_free(c);
2201 g_free(d);
2202 if (group) {
2203 gtk_widget_modify_fg(GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(Zathura.Style.completion_g_fg));
2204 gtk_widget_modify_fg(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(Zathura.Style.completion_g_fg));
2205 gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_g_bg));
2206 } else {
2207 gtk_widget_modify_fg(GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg));
2208 gtk_widget_modify_fg(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg));
2209 gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_bg));
2211 gtk_widget_modify_font(GTK_WIDGET(show_command), Zathura.Style.font);
2212 gtk_widget_modify_font(GTK_WIDGET(show_description), Zathura.Style.font);
2213 gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_command), TRUE, TRUE, 2);
2214 gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_description), FALSE, FALSE, 2);
2215 gtk_container_add(GTK_CONTAINER(row), GTK_WIDGET(col));
2216 gtk_box_pack_start(results, GTK_WIDGET(row), FALSE, FALSE, 0);
2217 return row;
2221 //==========================================================================
2223 // set_completion_row_color
2225 //==========================================================================
2226 void set_completion_row_color (GtkBox *results, int mode, int id) {
2227 GtkEventBox *row = (GtkEventBox *)g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(results)), id);
2228 if (row) {
2229 GtkBox *col = (GtkBox *)g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(row)), 0);
2230 GtkLabel *cmd = (GtkLabel *)g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(col)), 0);
2231 GtkLabel *cdesc = (GtkLabel *)g_list_nth_data(gtk_container_get_children(GTK_CONTAINER(col)), 1);
2232 if (mode == NORMAL) {
2233 gtk_widget_modify_fg(GTK_WIDGET(cmd), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg));
2234 gtk_widget_modify_fg(GTK_WIDGET(cdesc), GTK_STATE_NORMAL, &(Zathura.Style.completion_fg));
2235 gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_bg));
2236 } else {
2237 gtk_widget_modify_fg(GTK_WIDGET(cmd), GTK_STATE_NORMAL, &(Zathura.Style.completion_hl_fg));
2238 gtk_widget_modify_fg(GTK_WIDGET(cdesc), GTK_STATE_NORMAL, &(Zathura.Style.completion_hl_fg));
2239 gtk_widget_modify_bg(GTK_WIDGET(row), GTK_STATE_NORMAL, &(Zathura.Style.completion_hl_bg));
2245 //==========================================================================
2247 // set_page_keep_ofs
2249 // used in file open function to set the page, but don't reset the offsets
2251 //==========================================================================
2252 void set_page_keep_ofs (int page) {
2253 if (page >= Zathura.PDF.number_of_pages || page < 0) {
2254 notify(WARNING, "Could not open page");
2255 return;
2257 Zathura.PDF.page_number = page;
2258 Zathura.Search.draw = FALSE;
2259 switch_view(Zathura.UI.document);
2260 draw(page);
2264 //==========================================================================
2266 // set_page
2268 //==========================================================================
2269 void set_page (int page) {
2270 if (page >= Zathura.PDF.number_of_pages || page < 0) {
2271 notify(WARNING, "Could not open page");
2272 return;
2274 Zathura.PDF.page_number = page;
2275 Zathura.PDF.page_yskip_t = 0;
2276 Zathura.PDF.page_yskip_pix = 0;
2277 Zathura.Search.draw = FALSE;
2278 Argument argument;
2279 argument.n = TOP;
2280 switch_view(Zathura.UI.document);
2281 draw(page);
2282 sc_scroll(&argument);
2286 //==========================================================================
2288 // switch_view
2290 //==========================================================================
2291 void switch_view (GtkWidget *widget) {
2292 GtkWidget *child = gtk_bin_get_child(GTK_BIN(Zathura.UI.viewport));
2293 if (child == widget) return;
2294 if (child) {
2295 g_object_ref(child);
2296 gtk_container_remove(GTK_CONTAINER(Zathura.UI.viewport), child);
2298 gtk_container_add(GTK_CONTAINER(Zathura.UI.viewport), GTK_WIDGET(widget));
2302 //==========================================================================
2304 // completion_init
2306 //==========================================================================
2307 Completion *completion_init (void) {
2308 Completion *completion = malloc(sizeof(Completion));
2309 if (!completion) out_of_memory();
2310 completion->groups = NULL;
2311 return completion;
2315 //==========================================================================
2317 // completion_group_create
2319 //==========================================================================
2320 CompletionGroup *completion_group_create (char *name) {
2321 CompletionGroup *group = malloc(sizeof(CompletionGroup));
2322 if (!group) out_of_memory();
2323 group->value = (name ? g_strdup(name) : NULL);
2324 group->elements = NULL;
2325 group->next = NULL;
2326 return group;
2330 //==========================================================================
2332 // completion_add_group
2334 //==========================================================================
2335 void completion_add_group (Completion *completion, CompletionGroup *group) {
2336 CompletionGroup *cg = completion->groups;
2337 while (cg && cg->next) cg = cg->next;
2338 if (cg) cg->next = group; else completion->groups = group;
2342 //==========================================================================
2344 // completion_free
2346 //==========================================================================
2347 void completion_free (Completion *completion) {
2348 CompletionGroup *group = completion->groups;
2349 while (group) {
2350 CompletionGroup *ng;
2351 CompletionElement *element = group->elements;
2352 while (element) {
2353 CompletionElement *ne = element->next;
2354 g_free(element->value);
2355 g_free(element->description);
2356 free(element);
2357 element = ne;
2359 ng = group->next;
2360 g_free(group->value);
2361 free(group);
2362 group = ng;
2364 free(completion);
2368 //==========================================================================
2370 // completion_group_add_element
2372 //==========================================================================
2373 void completion_group_add_element (CompletionGroup *group, char *name, char *description) {
2374 CompletionElement *el = group->elements;
2375 while (el && el->next) el = el->next;
2376 CompletionElement *new_element = malloc(sizeof(CompletionElement));
2377 if (!new_element) out_of_memory();
2378 new_element->value = (name ? g_strdup(name) : NULL);
2379 new_element->description = (description ? g_strdup(description) : NULL);
2380 new_element->next = NULL;
2381 if (el) el->next = new_element; else group->elements = new_element;
2385 //==========================================================================
2387 // search
2389 // thread implementation
2391 //==========================================================================
2392 void *search (void *parameter) {
2393 Argument *argument = (Argument *)parameter;
2394 static char *search_item;
2395 static int direction;
2396 static int next_page = 0;
2397 gchar *old_query = NULL;
2398 GList *results = NULL;
2399 if (argument->n != NO_SEARCH) {
2400 /* search document */
2401 if (argument->n) direction = (argument->n == BACKWARD ? -1 : 1);
2402 if (argument->data) {
2403 if (search_item) g_free(search_item);
2404 search_item = g_strdup((char *)argument->data);
2406 g_free(argument->data);
2407 g_free(argument);
2409 MTLOCK_PDFOBJ();
2410 if (!Zathura.PDF.document || !search_item || !strlen(search_item)) {
2411 MTUNLOCK_PDFOBJ();
2412 MTLOCK_SEARCH();
2413 Zathura.Thread.search_thread_running = FALSE;
2414 MTUNLOCK_SEARCH();
2415 g_thread_exit(NULL);
2418 old_query = Zathura.Search.query;
2420 /* delete old results */
2421 if (Zathura.Search.results) {
2422 g_list_free(Zathura.Search.results);
2423 Zathura.Search.results = NULL;
2426 Zathura.Search.query = g_strdup(search_item);
2428 int number_of_pages = Zathura.PDF.number_of_pages;
2429 int page_number = Zathura.PDF.page_number;
2431 MTUNLOCK_PDFOBJ();
2433 int page_counter = (g_strcmp0(old_query,search_item) == 0 ? 1 : 0);
2434 for( ; page_counter <= number_of_pages; ++page_counter) {
2435 MTLOCK_SEARCH();
2436 if (Zathura.Thread.search_thread_running == FALSE) {
2437 MTUNLOCK_SEARCH();
2438 g_free(old_query);
2439 g_thread_exit(NULL);
2441 MTUNLOCK_SEARCH();
2443 next_page = (number_of_pages+page_number+page_counter*direction)%number_of_pages;
2445 MTLOCK_PDFLIB();
2446 PopplerPage *page = poppler_document_get_page(Zathura.PDF.document, next_page);
2447 MTUNLOCK_PDFLIB();
2449 if (!page) {
2450 g_free(old_query);
2451 g_thread_exit(NULL);
2454 MTLOCK_PDFLIB();
2455 results = poppler_page_find_text(page, search_item);
2456 MTUNLOCK_PDFLIB();
2458 g_object_unref(page);
2460 if (results) break;
2462 } else {
2463 Zathura.Search.draw = TRUE;
2464 g_free(argument->data);
2465 g_free(argument);
2468 /* draw results */
2469 if (results) {
2470 gdk_threads_enter();
2472 set_page(next_page);
2474 if (Zathura.Search.results) g_list_free(Zathura.Search.results);
2476 Zathura.Search.results = results;
2477 Zathura.Search.page = next_page;
2478 Zathura.Search.draw = TRUE;
2479 Zathura.Search.query = g_strdup(search_item);
2481 gdk_threads_leave();
2484 MTLOCK_SEARCH();
2485 Zathura.Thread.search_thread_running = FALSE;
2486 MTUNLOCK_SEARCH();
2488 g_free(old_query);
2489 g_thread_exit(NULL);
2490 return NULL;
2494 //==========================================================================
2496 // sc_abort
2498 // shortcut implementation
2500 //==========================================================================
2501 void sc_abort (Argument *argument) {
2502 /* Clear buffer */
2503 if (Zathura.Global.buffer) {
2504 g_string_free(Zathura.Global.buffer, TRUE);
2505 Zathura.Global.buffer = NULL;
2506 gtk_label_set_markup((GtkLabel *)Zathura.Global.status_buffer, "");
2509 if (!Zathura.Global.show_inputbar) gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar));
2511 /* Set back to normal mode */
2512 change_mode(NORMAL);
2513 switch_view(Zathura.UI.document);
2517 //==========================================================================
2519 // sc_adjust_window
2521 //==========================================================================
2522 void sc_adjust_window (Argument *argument) {
2523 //fprintf(stderr, "RESIZED!\n");
2524 surfCacheDestroy(); // why not?
2526 if (!Zathura.PDF.document) return;
2527 Zathura.Global.adjust_mode = argument->n;
2528 GtkAdjustment *adjustment;
2529 double view_size;
2530 if (argument->n == ADJUST_BESTFIT) {
2531 adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
2532 } else if (argument->n == ADJUST_WIDTH) {
2533 adjustment = gtk_scrolled_window_get_hadjustment(Zathura.UI.view);
2534 } else {
2535 return;
2537 view_size = gtk_adjustment_get_page_size(adjustment);
2539 Page *current_page = Zathura.PDF.pages[Zathura.PDF.page_number];
2540 double page_width = current_page->width;
2541 double page_height = current_page->height;
2543 if (Zathura.PDF.rotate == 90 || Zathura.PDF.rotate == 270) {
2544 double swap = page_width;
2545 page_width = page_height;
2546 page_height = swap;
2549 if (argument->n == ADJUST_BESTFIT) {
2550 // for best fit, calculate average page height
2551 if (Zathura.PDF.number_of_pages > 0) {
2552 double total = 0;
2553 for (int f = 0; f < Zathura.PDF.number_of_pages; ++f) {
2554 //allwdt += (Zathura.PDF.rotate == 90 || Zathura.PDF.rotate == 270 ? current_page->height : current_page->width);
2555 Page *cpg = Zathura.PDF.pages[f];
2556 total += (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? cpg->height : cpg->width);
2558 total /= Zathura.PDF.number_of_pages;
2559 Zathura.PDF.scale = (view_size/total)*100;
2561 } else {
2562 Zathura.PDF.scale = (view_size/page_width)*100;
2565 draw(Zathura.PDF.page_number);
2566 update_status();
2570 //==========================================================================
2572 // sc_xcenter_window
2574 //==========================================================================
2575 void sc_xcenter_window (Argument *argument) {
2576 if (!Zathura.PDF.document) return;
2577 Zathura.Global.xcenter_mode = argument->n;
2578 draw(Zathura.PDF.page_number);
2579 update_status();
2583 //==========================================================================
2585 // sc_change_buffer
2587 //==========================================================================
2588 void sc_change_buffer (Argument *argument) {
2589 if (!Zathura.Global.buffer) return;
2590 int buffer_length = Zathura.Global.buffer->len;
2591 if (argument->n == DELETE_LAST) {
2592 if (buffer_length-1 == 0) {
2593 g_string_free(Zathura.Global.buffer, TRUE);
2594 Zathura.Global.buffer = NULL;
2595 gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, "");
2596 } else {
2597 GString *temp = g_string_new_len(Zathura.Global.buffer->str, buffer_length-1);
2598 g_string_free(Zathura.Global.buffer, TRUE);
2599 Zathura.Global.buffer = temp;
2600 gtk_label_set_markup((GtkLabel *)Zathura.Global.status_buffer, Zathura.Global.buffer->str);
2606 //==========================================================================
2608 // sc_change_mode
2610 //==========================================================================
2611 void sc_change_mode (Argument *argument) {
2612 if (argument) change_mode(argument->n);
2616 //==========================================================================
2618 // sc_focus_inputbar
2620 //==========================================================================
2621 void sc_focus_inputbar (Argument *argument) {
2622 if (!(GTK_WIDGET_VISIBLE(GTK_WIDGET(Zathura.UI.inputbar)))) gtk_widget_show(GTK_WIDGET(Zathura.UI.inputbar));
2623 if (argument->data) {
2624 char *data;
2625 if (argument->n == APPEND_FILEPATH) {
2626 data = g_strdup_printf("%s%s", (const char *)argument->data, Zathura.PDF.file);
2627 } else {
2628 data = g_strdup((const char *)argument->data);
2630 notify(DEFAULT, data);
2631 g_free(data);
2632 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.inputbar));
2633 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1);
2638 //==========================================================================
2640 // sc_follow
2642 //==========================================================================
2643 void sc_follow (Argument *argument) {
2644 if (!Zathura.PDF.document) return;
2645 Page *current_page = Zathura.PDF.pages[Zathura.PDF.page_number];
2646 int link_id = 1;
2648 MTLOCK_PDFLIB();
2649 GList *link_list = poppler_page_get_link_mapping(current_page->page);
2650 MTUNLOCK_PDFLIB();
2651 link_list = g_list_reverse(link_list);
2653 if (g_list_length(link_list) <= 0) return;
2655 for (GList *links = link_list; links; links = g_list_next(links)) {
2656 PopplerLinkMapping *link_mapping = (PopplerLinkMapping *)links->data;
2657 PopplerRectangle *link_rectangle = &link_mapping->area;
2658 PopplerAction *action = link_mapping->action;
2660 /* only handle URI and internal links */
2661 if (action->type == POPPLER_ACTION_URI || action->type == POPPLER_ACTION_GOTO_DEST) {
2662 highlight_result(Zathura.PDF.page_number, link_rectangle);
2663 /* draw text */
2664 recalc_rectangle(Zathura.PDF.page_number, link_rectangle);
2665 cairo_t *cairo = cairo_create(create_page_surface(Zathura.PDF.page_number));
2666 cairo_select_font_face(cairo, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
2667 cairo_set_font_size(cairo, 10);
2668 cairo_move_to(cairo, link_rectangle->x1+1, link_rectangle->y1-1);
2669 char *link_number = g_strdup_printf("%i", link_id++);
2670 cairo_show_text(cairo, link_number);
2671 cairo_destroy(cairo);
2672 g_free(link_number);
2676 gtk_widget_queue_draw(Zathura.UI.drawing_area);
2677 poppler_page_free_link_mapping(link_list);
2679 /* replace default inputbar handler */
2680 g_signal_handler_disconnect((gpointer) Zathura.UI.inputbar, Zathura.Handler.inputbar_activate);
2681 Zathura.Handler.inputbar_activate = g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "activate", G_CALLBACK(cb_inputbar_form_activate), NULL);
2683 argument->data = "Follow hint: ";
2684 sc_focus_inputbar(argument);
2688 //==========================================================================
2690 // sc_navigate
2692 //==========================================================================
2693 void sc_navigate (Argument *argument) {
2694 if (!Zathura.PDF.document) return;
2696 int number_of_pages = Zathura.PDF.number_of_pages;
2697 int new_page = Zathura.PDF.page_number;
2699 if (argument->n == NEXT) {
2700 new_page = (scroll_wrap ? (new_page+1)%number_of_pages : new_page+1);
2701 } else if (argument->n == PREVIOUS) {
2702 new_page = (scroll_wrap ? (new_page+number_of_pages-1)%number_of_pages : new_page-1);
2703 if (new_page < 0 && Zathura.PDF.page_yskip_t > 0) new_page = 0;
2705 if (!scroll_wrap && (new_page < 0 || new_page >= number_of_pages)) return;
2707 set_page(new_page);
2708 update_status();
2712 //==========================================================================
2714 // sc_recolor
2716 //==========================================================================
2717 void sc_recolor (Argument *argument) {
2718 Zathura.Global.recolor = !Zathura.Global.recolor;
2719 draw(Zathura.PDF.page_number);
2723 //==========================================================================
2725 // sc_reload
2727 //==========================================================================
2728 void sc_reload (Argument *argument) {
2729 draw(Zathura.PDF.page_number);
2731 GtkAdjustment *vadjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
2732 GtkAdjustment *hadjustment = gtk_scrolled_window_get_hadjustment(Zathura.UI.view);
2734 /* save old information */
2735 MTLOCK_PDFOBJ();
2736 char *path = (Zathura.PDF.file ? strdup(Zathura.PDF.file) : NULL);
2737 char *password = (Zathura.PDF.password ? strdup(Zathura.PDF.password) : NULL);
2738 int scale = Zathura.PDF.scale;
2739 int page = Zathura.PDF.page_number;
2740 int rotate = Zathura.PDF.rotate;
2741 gdouble va = gtk_adjustment_get_value(vadjustment);
2742 gdouble ha = gtk_adjustment_get_value(hadjustment);
2743 MTUNLOCK_PDFOBJ();
2745 /* reopen and restore settings */
2746 close_file(TRUE);
2747 open_file(path, password);
2749 MTLOCK_PDFOBJ();
2750 Zathura.PDF.scale = scale;
2751 Zathura.PDF.rotate = rotate;
2752 va = ha = 0;
2753 gtk_adjustment_set_value(vadjustment, va);
2754 gtk_adjustment_set_value(hadjustment, ha);
2755 MTUNLOCK_PDFOBJ();
2757 if (Zathura.PDF.number_of_pages != 0) {
2758 if (page >= Zathura.PDF.number_of_pages-1) page = Zathura.PDF.number_of_pages-1;
2759 Zathura.PDF.page_number = page;
2760 draw(Zathura.PDF.page_number);
2763 if (path) free(path);
2764 if (password) free(password);
2768 //==========================================================================
2770 // sc_rotate
2772 //==========================================================================
2773 void sc_rotate (Argument *argument) {
2774 Zathura.PDF.rotate = (Zathura.PDF.rotate+90)%360;
2775 Zathura.Search.draw = TRUE;
2776 draw(Zathura.PDF.page_number);
2780 //==========================================================================
2782 // sc_scroll
2784 //==========================================================================
2785 void sc_scroll (Argument *argument) {
2786 GtkAdjustment *adjustment;
2788 if (argument->n == LEFT || argument->n == RIGHT) {
2789 adjustment = gtk_scrolled_window_get_hadjustment(Zathura.UI.view);
2790 } else {
2791 adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
2794 if (argument->n == WINDOW_UP || argument->n == WINDOW_DOWN ||
2795 argument->n == UP || argument->n == DOWN || argument->n == FULL_WINDOW_DOWN)
2797 adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
2798 int window_y = gtk_adjustment_get_page_size(adjustment);
2799 int pgdelta = window_y/6;
2800 if (pgdelta < 2) return; // are you nuts?
2801 int delta =
2802 argument->n == WINDOW_UP || argument->n == WINDOW_DOWN ? window_y-pgdelta :
2803 argument->n == FULL_WINDOW_DOWN ? window_y :
2805 int prevypos = calc_current_document_screen_offset();
2806 int st_page = Zathura.PDF.page_number;
2807 if (argument->n == WINDOW_UP || argument->n == UP) {
2808 scroll_up_pixels_no_draw(delta);
2809 } else {
2810 scroll_down_pixels_no_draw(delta);
2811 // hack for full-page scroll, why not?
2812 // if we're moved past the page into the gap, skip the gap too
2813 if (Zathura.PDF.page_yskip_t >= 1.0f && Zathura.PDF.page_number+1 < Zathura.PDF.number_of_pages) {
2814 Zathura.PDF.page_number += 1;
2815 Zathura.PDF.page_yskip_t = 0;
2816 Zathura.PDF.page_yskip_pix = 0;
2819 // scroll?
2820 if (smooth_scrolling > 0) {
2821 // cache destination surfaces
2822 int top_page = Zathura.PDF.page_number;
2823 if (st_page < top_page) {
2824 // we're scrolling down, cache all pages down
2825 while (st_page < top_page) {
2826 create_page_surface(st_page);
2827 st_page += 1;
2829 // cache all potentially visible pages too
2830 int ypos = 0;
2831 while (ypos < window_y) {
2832 create_page_surface(top_page);
2833 int hgt = get_page_screen_height(top_page);
2834 if (hgt < 1) break;
2835 ypos += hgt;
2836 top_page += 1;
2838 // and one more, because why not?
2839 create_page_surface(top_page);
2840 } else {
2841 // we're scrolling up
2842 create_page_surface(top_page);
2844 const int currypos = calc_current_document_screen_offset();
2845 const int delta_smooth = (smooth_scrolling > 666 ? 666 : (int)smooth_scrolling);
2846 while (currypos != prevypos) {
2847 if (prevypos < currypos) {
2848 if ((prevypos += delta_smooth) > currypos) prevypos = currypos;
2849 } else {
2850 if ((prevypos -= delta_smooth) < currypos) prevypos = currypos;
2852 convert_screen_document_offset_to_pos(prevypos, &Zathura.PDF.page_number, &Zathura.PDF.page_yskip_t, &Zathura.PDF.page_yskip_pix);
2853 render_view(Zathura.UI.drawing_area, FALSE); // no full clear
2855 } else {
2856 int top_page = Zathura.PDF.page_number;
2857 // setup new page
2858 if (top_page >= 0 && top_page < Zathura.PDF.number_of_pages) {
2859 Zathura.Search.draw = TRUE;
2860 draw(top_page);
2861 gtk_adjustment_set_value(adjustment, 0);
2862 update_status();
2864 //gtk_adjustment_set_value(adjustment, 0);
2866 update_status();
2867 return;
2871 //gdouble view_size = gtk_adjustment_get_page_size(adjustment);
2872 gdouble value = gtk_adjustment_get_value(adjustment);
2873 //gdouble max = gtk_adjustment_get_upper(adjustment)-view_size;
2874 //static gboolean ss = FALSE;
2875 if (value != 0) {
2876 value = 0;
2877 gtk_adjustment_set_value(adjustment, 0);
2880 if ((argument->n == UP || argument->n == HALF_UP || argument->n == FULL_UP) && value == 0) {
2881 int old_page = Zathura.PDF.page_number;
2882 Argument arg;
2883 arg.n = PREVIOUS;
2884 sc_navigate(&arg);
2885 if (scroll_wrap || Zathura.PDF.page_number < old_page) {
2886 arg.n = BOTTOM;
2887 //ss = TRUE;
2888 sc_scroll(&arg);
2890 return;
2891 } else if ((argument->n == DOWN || argument->n == HALF_DOWN || argument->n == FULL_DOWN) /*&& value == max*/) {
2892 Argument arg;
2893 arg.n = NEXT;
2894 //ss = TRUE;
2895 sc_navigate(&arg);
2896 return;
2900 gdouble new_value;
2901 switch (argument->n) {
2902 case FULL_UP: new_value = (value-view_size < 0 ? 0 : value-view_size); break;
2903 case FULL_DOWN: new_value = (value+view_size > max ? max : value+view_size); break;
2904 case HALF_UP: new_value = (value-view_size/2 < 0 ? 0 : value-view_size/2); break;
2905 case HALF_DOWN: new_value = (value+view_size/2 > max ? max : value+view_size/2); break;
2906 case LEFT: case UP: new_value = (value-scroll_step < 0 ? 0 : value-scroll_step); break;
2907 case TOP: new_value = 0; break;
2908 case BOTTOM: new_value = max; break;
2909 default: new_value = (value+scroll_step > max ? max : value+scroll_step); break;
2912 if (!(argument->n == LEFT || argument->n == RIGHT)) Zathura.State.scroll_percentage = (max == 0 ? 0 : new_value*100/max);
2914 if (smooth_scrolling && !ss) {
2915 gdouble i;
2916 if (new_value > value) {
2917 for (i = value; i+smooth_scrolling < new_value; i += smooth_scrolling) gtk_adjustment_set_value(adjustment, i);
2918 } else {
2919 for (i = value; i+smooth_scrolling > new_value; i -= smooth_scrolling) gtk_adjustment_set_value(adjustment, i);
2923 gtk_adjustment_set_value(adjustment, new_value);
2924 ss = FALSE;
2927 update_status();
2931 //==========================================================================
2933 // sc_search
2935 //==========================================================================
2936 void sc_search (Argument *argument) {
2937 MTLOCK_SEARCH();
2938 if (Zathura.Thread.search_thread_running) {
2939 Zathura.Thread.search_thread_running = FALSE;
2940 MTUNLOCK_SEARCH();
2941 gdk_threads_leave();
2942 g_thread_join(Zathura.Thread.search_thread);
2943 gdk_threads_enter();
2944 MTLOCK_SEARCH();
2947 Argument *newarg = g_malloc0(sizeof(Argument));
2948 newarg->n = argument->n;
2949 newarg->data = (argument->data ? g_strdup(argument->data) : NULL);
2950 Zathura.Thread.search_thread_running = TRUE;
2951 Zathura.Thread.search_thread = g_thread_create(search, (gpointer) newarg, TRUE, NULL);
2952 MTUNLOCK_SEARCH();
2956 //==========================================================================
2958 // sc_switch_goto_mode
2960 //==========================================================================
2961 void sc_switch_goto_mode (Argument *argument) {
2962 switch (Zathura.Global.goto_mode) {
2963 case GOTO_LABELS: Zathura.Global.goto_mode = GOTO_OFFSET; break;
2964 case GOTO_OFFSET: Zathura.Global.goto_mode = GOTO_DEFAULT; break;
2965 default:
2966 if (Zathura.Global.enable_labelmode) Zathura.Global.goto_mode = GOTO_LABELS; else Zathura.Global.goto_mode = GOTO_OFFSET;
2967 break;
2969 update_status();
2973 //==========================================================================
2975 // cb_index_row_activated
2977 //==========================================================================
2978 gboolean cb_index_row_activated (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) {
2979 GtkTreeModel *model;
2980 GtkTreeIter iter;
2982 g_object_get(treeview, "model", &model, NULL);
2984 if (gtk_tree_model_get_iter(model, &iter, path)) {
2985 PopplerAction *action;
2986 PopplerDest *destination;
2988 gtk_tree_model_get(model, &iter, 1, &action, -1);
2989 if (!action) return TRUE;
2991 if (action->type == POPPLER_ACTION_GOTO_DEST) {
2992 destination = action->goto_dest.dest;
2993 int page_number = destination->page_num;
2994 if (action->goto_dest.dest->type == POPPLER_DEST_NAMED) {
2995 PopplerDest *d = poppler_document_find_dest(Zathura.PDF.document, action->goto_dest.dest->named_dest);
2996 if (d) {
2997 page_number = d->page_num;
2998 poppler_dest_free(d);
3001 set_page(page_number-1);
3002 update_status();
3003 Zathura.Global.show_index = FALSE;
3004 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.document));
3008 Zathura.Global.mode = NORMAL;
3009 g_object_unref(model);
3011 return TRUE;
3015 //==========================================================================
3017 // sc_navigate_index
3019 //==========================================================================
3020 void sc_navigate_index (Argument *argument) {
3021 if (!Zathura.UI.index) return;
3023 GtkTreeView *treeview = gtk_container_get_children(GTK_CONTAINER(Zathura.UI.index))->data;
3024 GtkTreePath *path;
3026 gtk_tree_view_get_cursor(treeview, &path, NULL);
3027 if (!path) return;
3029 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
3030 GtkTreeIter iter;
3031 GtkTreeIter child_iter;
3033 gboolean is_valid_path = TRUE;
3035 switch (argument->n) {
3036 case UP:
3037 if (!gtk_tree_path_prev(path)) {
3038 is_valid_path = (gtk_tree_path_get_depth(path) > 1 && gtk_tree_path_up(path));
3039 } else {
3040 /* row above */
3041 while (gtk_tree_view_row_expanded(treeview, path)) {
3042 gtk_tree_model_get_iter(model, &iter, path);
3043 /* select last child */
3044 gtk_tree_model_iter_nth_child(model, &child_iter, &iter, gtk_tree_model_iter_n_children(model, &iter)-1);
3045 gtk_tree_path_free(path);
3046 path = gtk_tree_model_get_path(model, &child_iter);
3049 break;
3050 case COLLAPSE:
3051 if (!gtk_tree_view_collapse_row(treeview, path) && gtk_tree_path_get_depth(path) > 1) {
3052 gtk_tree_path_up(path);
3053 gtk_tree_view_collapse_row(treeview, path);
3055 break;
3056 case DOWN:
3057 if (gtk_tree_view_row_expanded(treeview, path)) {
3058 gtk_tree_path_down(path);
3059 } else {
3060 do {
3061 gtk_tree_model_get_iter(model, &iter, path);
3062 if (gtk_tree_model_iter_next(model, &iter)) {
3063 path = gtk_tree_model_get_path(model, &iter);
3064 break;
3066 } while ((is_valid_path = (gtk_tree_path_get_depth(path) > 1)) && gtk_tree_path_up(path));
3068 break;
3069 case EXPAND:
3070 if (gtk_tree_view_expand_row(treeview, path, FALSE)) gtk_tree_path_down(path);
3071 break;
3072 case SELECT:
3073 cb_index_row_activated(treeview, path, NULL, NULL);
3074 return;
3077 if (is_valid_path) gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
3079 gtk_tree_path_free(path);
3083 //==========================================================================
3085 // sc_toggle_index
3087 //==========================================================================
3088 void sc_toggle_index (Argument *argument) {
3089 if (!Zathura.PDF.document) return;
3091 GtkWidget *treeview;
3092 GtkTreeModel *model;
3093 GtkCellRenderer *renderer;
3094 PopplerIndexIter *iter;
3096 if (!Zathura.UI.index) {
3097 Zathura.UI.index = gtk_scrolled_window_new (NULL, NULL);
3098 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(Zathura.UI.index), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3100 if ((iter = poppler_index_iter_new(Zathura.PDF.document))) {
3101 model = GTK_TREE_MODEL(gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER));
3102 MTLOCK_PDFLIB();
3103 build_index(model, NULL, iter);
3104 MTUNLOCK_PDFLIB();
3105 poppler_index_iter_free(iter);
3106 } else {
3107 notify(WARNING, "This document does not contain any index");
3108 Zathura.UI.index = NULL;
3109 return;
3112 treeview = gtk_tree_view_new_with_model (model);
3113 g_object_unref(model);
3114 renderer = gtk_cell_renderer_text_new();
3115 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW (treeview), 0, "Title", renderer, "markup", 0, NULL);
3116 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
3117 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3118 g_object_set(G_OBJECT(gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), 0)), "expand", TRUE, NULL);
3120 gtk_tree_view_set_cursor(GTK_TREE_VIEW(treeview), gtk_tree_path_new_first(), NULL, FALSE);
3121 g_signal_connect(G_OBJECT(treeview), "row-activated", G_CALLBACK(cb_index_row_activated), NULL);
3123 gtk_container_add (GTK_CONTAINER (Zathura.UI.index), treeview);
3124 gtk_widget_show (treeview);
3125 gtk_widget_show(Zathura.UI.index);
3128 if (!Zathura.Global.show_index) {
3129 switch_view(Zathura.UI.index);
3130 Zathura.Global.mode = INDEX;
3131 } else {
3132 switch_view(Zathura.UI.document);
3133 Zathura.Global.mode = NORMAL;
3136 Zathura.Global.show_index = !Zathura.Global.show_index;
3140 //==========================================================================
3142 // sc_toggle_inputbar
3144 //==========================================================================
3145 void sc_toggle_inputbar (Argument *argument) {
3146 if (GTK_WIDGET_VISIBLE(GTK_WIDGET(Zathura.UI.inputbar))) {
3147 gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar));
3148 } else {
3149 gtk_widget_show(GTK_WIDGET(Zathura.UI.inputbar));
3154 //==========================================================================
3156 // sc_toggle_fullscreen
3158 //==========================================================================
3159 void sc_toggle_fullscreen (Argument *argument) {
3160 static gboolean fs = TRUE;
3162 if (fs) {
3163 gtk_window_fullscreen(GTK_WINDOW(Zathura.UI.window));
3164 gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar));
3165 gtk_widget_hide(GTK_WIDGET(Zathura.UI.statusbar));
3167 Argument arg;
3168 arg.n = ADJUST_BESTFIT;
3169 sc_adjust_window(&arg);
3171 Zathura.Global.mode = FULLSCREEN;
3172 fs = FALSE;
3173 } else {
3174 gtk_window_unfullscreen(GTK_WINDOW(Zathura.UI.window));
3175 gtk_widget_show(GTK_WIDGET(Zathura.UI.inputbar));
3176 gtk_widget_show(GTK_WIDGET(Zathura.UI.statusbar));
3178 Zathura.Global.mode = NORMAL;
3179 fs = TRUE;
3181 isc_abort(NULL);
3185 //==========================================================================
3187 // sc_toggle_statusbar
3189 //==========================================================================
3190 void sc_toggle_statusbar (Argument *argument) {
3191 if (GTK_WIDGET_VISIBLE(GTK_WIDGET(Zathura.UI.statusbar))) {
3192 gtk_widget_hide(GTK_WIDGET(Zathura.UI.statusbar));
3193 } else {
3194 gtk_widget_show(GTK_WIDGET(Zathura.UI.statusbar));
3199 //==========================================================================
3201 // sc_quit
3203 //==========================================================================
3204 void sc_quit (Argument *argument) {
3205 cb_destroy(NULL, NULL);
3209 //==========================================================================
3211 // sc_zoom
3213 //==========================================================================
3214 void sc_zoom (Argument *argument) {
3215 bcmd_zoom(NULL, argument);
3219 //**************************************************************************
3221 // inputbar shortcut declarations
3223 //**************************************************************************
3225 //==========================================================================
3227 // isc_abort
3229 //==========================================================================
3230 void isc_abort (Argument *argument) {
3231 Argument arg = { HIDE };
3232 isc_completion(&arg);
3234 notify(DEFAULT, "");
3235 change_mode(NORMAL);
3236 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view));
3238 if (!Zathura.Global.show_inputbar) gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar));
3240 /* replace default inputbar handler */
3241 g_signal_handler_disconnect((gpointer) Zathura.UI.inputbar, Zathura.Handler.inputbar_activate);
3242 Zathura.Handler.inputbar_activate = g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "activate", G_CALLBACK(cb_inputbar_activate), NULL);
3243 sc_abort(NULL);
3247 //==========================================================================
3249 // isc_command_history
3251 //==========================================================================
3252 void isc_command_history (Argument *argument) {
3253 static int current = 0;
3254 int length = g_list_length(Zathura.Global.history);
3255 if (length > 0) {
3256 current = (length+current+(argument->n == NEXT ? 1 : -1))%length;
3257 gchar *command = (gchar *)g_list_nth_data(Zathura.Global.history, current);
3258 notify(DEFAULT, command);
3259 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.inputbar));
3260 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1);
3265 //==========================================================================
3267 // isc_completion
3269 //==========================================================================
3270 void isc_completion (Argument *argument) {
3271 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 1, -1);
3272 gchar *tmp_string = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 0, 1);
3273 gchar identifier = tmp_string[0];
3274 int length = strlen(input);
3276 if (!input || !tmp_string) {
3277 if (input) g_free(input);
3278 if (tmp_string) g_free(tmp_string);
3279 return;
3282 /* get current information*/
3283 char *first_space = strstr(input, " ");
3284 char *current_command;
3285 char *current_parameter;
3286 int current_command_length;
3288 if (!first_space) {
3289 current_command = g_strdup(input);
3290 current_command_length = length;
3291 current_parameter = NULL;
3292 } else {
3293 int offset = abs(input-first_space);
3294 current_command = g_strndup(input, offset);
3295 current_command_length = strlen(current_command);
3296 current_parameter = input+offset+1;
3299 /* if the identifier does not match the command sign and
3300 * the completion should not be hidden, leave this function */
3301 if (identifier != ':' && argument->n != HIDE) {
3302 if (current_command) g_free(current_command);
3303 if (input) g_free(input);
3304 if (tmp_string) g_free(tmp_string);
3305 return;
3308 /* static elements */
3309 static GtkBox *results = NULL;
3310 static CompletionRow *rows = NULL;
3312 static int current_item = 0;
3313 static int n_items = 0;
3315 static char *previous_command = NULL;
3316 static char *previous_parameter = NULL;
3317 static int previous_id = 0;
3318 static int previous_length = 0;
3320 static gboolean command_mode = TRUE;
3322 /* delete old list iff
3323 * the completion should be hidden
3324 * the current command differs from the previous one
3325 * the current parameter differs from the previous one
3327 if (argument->n == HIDE || previous_length != length ||
3328 (current_parameter && previous_parameter && strcmp(current_parameter, previous_parameter) != 0) ||
3329 (current_command && previous_command && strcmp(current_command, previous_command) != 0)) {
3330 if (results) gtk_widget_destroy(GTK_WIDGET(results));
3331 results = NULL;
3332 if (rows) {
3333 for (int i = 0; i != n_items; ++i) {
3334 g_free(rows[i].command);
3335 g_free(rows[i].description);
3337 free(rows);
3339 rows = NULL;
3340 current_item = 0;
3341 n_items = 0;
3342 command_mode = TRUE;
3343 if (argument->n == HIDE) {
3344 if (current_command) g_free(current_command);
3345 if (input) g_free(input);
3346 if (tmp_string) g_free(tmp_string);
3347 return;
3351 /* create new list iff
3352 * there is no current list
3353 * the current command differs from the previous one
3354 * the current parameter differs from the previous one
3356 if (!results) {
3357 results = GTK_BOX(gtk_vbox_new(FALSE, 0));
3358 /* create list based on parameters iff
3359 * there is a current parameter given
3360 * there is an old list with commands
3361 * the current command does not differ from the previous one
3362 * the current command has an completion function
3364 if (strchr(input, ' ')) {
3365 gboolean search_matching_command = FALSE;
3366 for (int i = 0; i < LENGTH(commands); ++i) {
3367 int abbr_length = (commands[i].abbr ? strlen(commands[i].abbr) : 0);
3368 int cmd_length = (commands[i].command ? strlen(commands[i].command) : 0);
3369 if ((current_command_length <= cmd_length && !strncmp(current_command, commands[i].command, current_command_length)) ||
3370 (current_command_length <= abbr_length && !strncmp(current_command, commands[i].abbr, current_command_length))) {
3371 if (commands[i].completion) {
3372 previous_command = current_command;
3373 previous_id = i;
3374 search_matching_command = TRUE;
3375 } else {
3376 if (current_command) g_free(current_command);
3377 if (input) g_free(input);
3378 if (tmp_string) g_free(tmp_string);
3379 return;
3384 if (!search_matching_command) {
3385 if (current_command) g_free(current_command);
3386 if (input) g_free(input);
3387 if (tmp_string) g_free(tmp_string);
3388 return;
3391 Completion *result = commands[previous_id].completion(current_parameter);
3393 if (!result || !result->groups) {
3394 if (current_command) g_free(current_command);
3395 if (input) g_free(input);
3396 if (tmp_string) g_free(tmp_string);
3397 return;
3400 command_mode = FALSE;
3401 CompletionGroup *group = NULL;
3402 CompletionElement *element = NULL;
3404 rows = malloc(sizeof(CompletionRow));
3405 if (!rows) out_of_memory();
3407 for (group = result->groups; group != NULL; group = group->next) {
3408 int group_elements = 0;
3410 for(element = group->elements; element != NULL; element = element->next)
3412 if(element->value)
3414 if(group->value && !group_elements)
3416 rows = safe_realloc((void**)&rows, n_items+1, sizeof(CompletionRow));
3417 if(!rows)
3418 out_of_memory();
3419 rows[n_items].command = g_strdup(group->value);
3420 rows[n_items].description = NULL;
3421 rows[n_items].command_id = -1;
3422 rows[n_items].is_group = TRUE;
3423 rows[n_items++].row = GTK_WIDGET(create_completion_row(results, group->value, NULL, TRUE));
3426 rows = safe_realloc((void**)&rows, n_items+1, sizeof(CompletionRow));
3427 if(!rows)
3428 out_of_memory();
3429 rows[n_items].command = g_strdup(element->value);
3430 rows[n_items].description = element->description ? g_strdup(element->description) : NULL;
3431 rows[n_items].command_id = previous_id;
3432 rows[n_items].is_group = FALSE;
3433 rows[n_items++].row = GTK_WIDGET(create_completion_row(results, element->value, element->description, FALSE));
3434 group_elements++;
3439 /* clean up */
3440 completion_free(result);
3442 /* create list based on commands */
3443 else
3445 int i = 0;
3446 command_mode = TRUE;
3448 rows = malloc(LENGTH(commands)*sizeof(CompletionRow));
3449 if(!rows)
3450 out_of_memory();
3452 //printf("clen=%u\n", LENGTH(commands));
3453 for(i = 0; i < LENGTH(commands); i++)
3455 int abbr_length = commands[i].abbr ? strlen(commands[i].abbr) : 0;
3456 int cmd_length = commands[i].command ? strlen(commands[i].command) : 0;
3458 /* add command to list iff
3459 * the current command would match the command
3460 * the current command would match the abbreviation
3462 if( ((current_command_length <= cmd_length) && !strncmp(current_command, commands[i].command, current_command_length)) ||
3463 ((current_command_length <= abbr_length) && !strncmp(current_command, commands[i].abbr, current_command_length))
3466 rows[n_items].command = g_strdup(commands[i].command);
3467 rows[n_items].description = g_strdup(commands[i].description);
3468 rows[n_items].command_id = i;
3469 rows[n_items].is_group = FALSE;
3470 rows[n_items++].row = GTK_WIDGET(create_completion_row(results, commands[i].command, commands[i].description, FALSE));
3474 //printf("n_items=%u\n", n_items);
3475 rows = safe_realloc((void**)&rows, (n_items+1), sizeof(CompletionRow));
3476 if(!rows)
3477 out_of_memory();
3480 gtk_box_pack_start(Zathura.UI.box, GTK_WIDGET(results), FALSE, FALSE, 0);
3481 gtk_widget_show_all(GTK_WIDGET(Zathura.UI.window));
3483 current_item = (argument->n == NEXT) ? -1 : 0;
3486 /* update coloring iff
3487 * there is a list with items
3489 if( (results) && (n_items > 0) )
3491 set_completion_row_color(results, NORMAL, current_item);
3492 char* temp;
3493 int i = 0, next_group = 0;
3495 for(i = 0; i < n_items; i++)
3497 if(argument->n == NEXT || argument->n == NEXT_GROUP)
3498 current_item = (current_item+n_items+1)%n_items;
3499 else if(argument->n == PREVIOUS || argument->n == PREVIOUS_GROUP)
3500 current_item = (current_item+n_items-1)%n_items;
3502 if(rows[current_item].is_group)
3504 if(!command_mode && (argument->n == NEXT_GROUP || argument->n == PREVIOUS_GROUP))
3505 next_group = 1;
3506 continue;
3508 else
3510 if(!command_mode && (next_group == 0) && (argument->n == NEXT_GROUP || argument->n == PREVIOUS_GROUP))
3511 continue;
3512 break;
3516 set_completion_row_color(results, HIGHLIGHT, current_item);
3518 /* hide other items */
3519 int uh = ceil(n_completion_items/2);
3520 int lh = floor(n_completion_items/2);
3522 for(i = 0; i < n_items; i++)
3524 if((n_items > 1) && (
3525 (i >= (current_item-lh) && (i <= current_item+uh)) ||
3526 (i < n_completion_items && current_item < lh) ||
3527 (i >= (n_items-n_completion_items) && (current_item >= (n_items-uh))))
3529 gtk_widget_show(rows[i].row);
3530 else
3531 gtk_widget_hide(rows[i].row);
3534 if(command_mode)
3535 temp = g_strconcat(":", rows[current_item].command, (n_items == 1) ? " " : NULL, NULL);
3536 else
3537 temp = g_strconcat(":", previous_command, " ", rows[current_item].command, NULL);
3539 gtk_entry_set_text(Zathura.UI.inputbar, temp);
3540 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1);
3541 g_free(temp);
3543 previous_command = g_strdup((command_mode) ? rows[current_item].command : current_command);
3544 previous_parameter = g_strdup((command_mode) ? current_parameter : rows[current_item].command);
3545 previous_length = strlen(previous_command)+((command_mode) ? (length-current_command_length) : (strlen(previous_parameter)+1));
3546 previous_id = rows[current_item].command_id;
3549 if(current_command)
3550 g_free(current_command);
3551 if(input)
3552 g_free(input);
3553 if(tmp_string)
3554 g_free(tmp_string);
3558 //==========================================================================
3560 // isc_string_manipulation
3562 //==========================================================================
3563 void isc_string_manipulation (Argument *argument)
3565 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 0, -1);
3566 int length = strlen(input);
3567 int pos = gtk_editable_get_position(GTK_EDITABLE(Zathura.UI.inputbar));
3568 int i;
3570 switch (argument->n) {
3571 case DELETE_LAST_WORD:
3572 i = pos-1;
3574 if(!pos)
3575 return;
3577 /* remove trailing spaces */
3578 for(; i >= 0 && input[i] == ' '; i--);
3580 /* find the beginning of the word */
3581 while((i > 0) && (input[i] != ' ') && (input[i] != '/'))
3582 i--;
3584 gtk_editable_delete_text(GTK_EDITABLE(Zathura.UI.inputbar), i, pos);
3585 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), i);
3586 break;
3587 case DELETE_LAST_CHAR:
3588 if((length-1) <= 0)
3589 isc_abort(NULL);
3591 gtk_editable_delete_text(GTK_EDITABLE(Zathura.UI.inputbar), pos-1, pos);
3592 break;
3593 case DELETE_TO_LINE_START:
3594 gtk_editable_delete_text(GTK_EDITABLE(Zathura.UI.inputbar), 1, pos);
3595 break;
3596 case NEXT_CHAR:
3597 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), pos+1);
3598 break;
3599 case PREVIOUS_CHAR:
3600 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), (pos == 0) ? 0 : pos-1);
3601 break;
3602 default: /* unreachable */
3603 break;
3608 //**************************************************************************
3610 // command implementation
3612 //**************************************************************************
3614 //==========================================================================
3616 // cmd_bookmark
3618 //==========================================================================
3619 gboolean cmd_bookmark (int argc, char **argv) {
3620 if(!Zathura.PDF.document || argc < 1)
3621 return TRUE;
3623 /* get id */
3624 int i;
3625 GString *id = g_string_new("");
3627 for(i = 0; i < argc; i++)
3629 if(i != 0)
3630 id = g_string_append_c(id, ' ');
3632 id = g_string_append(id, argv[i]);
3635 if(strlen(id->str) == 0)
3637 notify(WARNING, "Can't set bookmark: bookmark name is empty");
3638 g_string_free(id, TRUE);
3639 return FALSE;
3642 if(is_reserved_bm_name(id->str))
3644 notify(WARNING, "Can't set bookmark: reserved bookmark name");
3645 g_string_free(id, TRUE);
3646 return FALSE;
3649 /* reload the bookmark file */
3650 read_bookmarks_file();
3652 /* check for existing bookmark to overwrite */
3653 for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++)
3655 if(!strcmp(id->str, Zathura.Bookmarks.bookmarks[i].id))
3657 Zathura.Bookmarks.bookmarks[i].page = Zathura.PDF.page_number;
3658 g_string_free(id, TRUE);
3659 return TRUE;
3663 /* add new bookmark */
3664 Zathura.Bookmarks.bookmarks = safe_realloc((void**)&Zathura.Bookmarks.bookmarks,
3665 Zathura.Bookmarks.number_of_bookmarks+1, sizeof(Bookmark));
3666 if(!Zathura.Bookmarks.bookmarks)
3667 out_of_memory();
3669 Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].id = g_strdup(id->str);
3670 Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks].page = Zathura.PDF.page_number;
3671 Zathura.Bookmarks.number_of_bookmarks++;
3673 /* write the bookmark file */
3674 write_bookmarks_file();
3676 g_string_free(id, TRUE);
3677 return TRUE;
3681 //==========================================================================
3683 // cmd_open_bookmark
3685 //==========================================================================
3686 gboolean cmd_open_bookmark (int argc, char **argv)
3688 if(!Zathura.PDF.document || argc < 1)
3689 return TRUE;
3691 /* get id */
3692 int i;
3693 GString *id = g_string_new("");
3695 for(i = 0; i < argc; i++)
3697 if(i != 0)
3698 id = g_string_append_c(id, ' ');
3700 id = g_string_append(id, argv[i]);
3703 /* find bookmark */
3704 for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++)
3706 if(!strcmp(id->str, Zathura.Bookmarks.bookmarks[i].id))
3708 set_page(Zathura.Bookmarks.bookmarks[i].page);
3709 g_string_free(id, TRUE);
3710 return TRUE;
3714 notify(WARNING, "No matching bookmark found");
3715 g_string_free(id, TRUE);
3716 return FALSE;
3720 //==========================================================================
3722 // cmd_close
3724 //==========================================================================
3725 gboolean cmd_close (int argc, char **argv)
3727 close_file(FALSE);
3729 return TRUE;
3733 //==========================================================================
3735 // cmd_delete_bookmark
3737 //==========================================================================
3738 gboolean cmd_delete_bookmark (int argc, char **argv)
3740 if(!Zathura.PDF.document || argc < 1)
3741 return TRUE;
3743 /* get id */
3744 int i;
3745 GString *id = g_string_new("");
3747 for(i = 0; i < argc; i++)
3749 if(i != 0)
3750 id = g_string_append_c(id, ' ');
3752 id = g_string_append(id, argv[i]);
3755 /* reload bookmark file */
3756 read_bookmarks_file();
3758 /* check for bookmark to delete */
3759 for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++)
3761 if(!strcmp(id->str, Zathura.Bookmarks.bookmarks[i].id))
3763 /* update key file */
3764 g_key_file_remove_key(Zathura.Bookmarks.data, Zathura.PDF.file, Zathura.Bookmarks.bookmarks[i].id, NULL);
3766 g_free(Zathura.Bookmarks.bookmarks[i].id);
3767 /* update bookmarks */
3768 Zathura.Bookmarks.bookmarks[i].id = Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks-1].id;
3769 Zathura.Bookmarks.bookmarks[i].page = Zathura.Bookmarks.bookmarks[Zathura.Bookmarks.number_of_bookmarks-1].page;
3770 Zathura.Bookmarks.bookmarks = safe_realloc((void**)&Zathura.Bookmarks.bookmarks,
3771 Zathura.Bookmarks.number_of_bookmarks, sizeof(Bookmark));
3772 if(!Zathura.Bookmarks.bookmarks)
3773 out_of_memory();
3775 Zathura.Bookmarks.number_of_bookmarks--;
3776 g_string_free(id, TRUE);
3778 /* write bookmark file */
3779 write_bookmarks_file();
3781 return TRUE;
3785 g_string_free(id, TRUE);
3786 return TRUE;
3790 //==========================================================================
3792 // cmd_export
3794 //==========================================================================
3795 gboolean cmd_export (int argc, char **argv)
3797 if(argc == 0 || !Zathura.PDF.document)
3798 return TRUE;
3800 if(argc < 2)
3802 notify(WARNING, "No export path specified");
3803 return FALSE;
3806 /* export images */
3807 if(!strcmp(argv[0], "images"))
3809 int page_number;
3810 for(page_number = 0; page_number < Zathura.PDF.number_of_pages; page_number++)
3812 GList *image_list;
3813 GList *images;
3814 cairo_surface_t *image;
3816 MTLOCK_PDFLIB();
3817 image_list = poppler_page_get_image_mapping(Zathura.PDF.pages[page_number]->page);
3818 MTUNLOCK_PDFLIB();
3820 if(!g_list_length(image_list))
3822 notify(WARNING, "This document does not contain any images");
3823 return FALSE;
3826 for(images = image_list; images; images = g_list_next(images))
3828 PopplerImageMapping *image_mapping;
3829 gint image_id;
3830 char* file;
3831 char* filename;
3833 image_mapping = (PopplerImageMapping*) images->data;
3834 image_id = image_mapping->image_id;
3836 MTLOCK_PDFLIB();
3837 image = poppler_page_get_image(Zathura.PDF.pages[page_number]->page, image_id);
3838 MTUNLOCK_PDFLIB();
3840 if(!image)
3841 continue;
3843 filename = g_strdup_printf("%s_p%i_i%i.png", Zathura.PDF.file, page_number+1, image_id);
3845 if(argv[1][0] == '~')
3847 gchar* home_path = get_home_dir();
3848 file = g_strdup_printf("%s%s%s", home_path, argv[1]+1, filename);
3849 g_free(home_path);
3851 else
3852 file = g_strdup_printf("%s%s", argv[1], filename);
3854 cairo_surface_write_to_png(image, file);
3856 g_free(filename);
3857 g_free(file);
3861 else if(!strcmp(argv[0], "attachments"))
3863 MTLOCK_PDFLIB();
3864 if(!poppler_document_has_attachments(Zathura.PDF.document))
3866 notify(WARNING, "PDF file has no attachments");
3867 MTUNLOCK_PDFLIB();
3868 return FALSE;
3871 GList *attachment_list = poppler_document_get_attachments(Zathura.PDF.document);
3872 MTUNLOCK_PDFLIB();
3874 GList *attachments;
3875 char *file;
3877 for(attachments = attachment_list; attachments; attachments = g_list_next(attachments))
3879 PopplerAttachment *attachment = (PopplerAttachment*) attachments->data;
3881 if(argv[1][0] == '~')
3883 gchar* home_path = get_home_dir();
3884 file = g_strdup_printf("%s%s%s", home_path, argv[1]+1, attachment->name);
3885 g_free(home_path);
3887 else
3888 file = g_strdup_printf("%s%s", argv[1], attachment->name);
3890 MTLOCK_PDFLIB();
3891 poppler_attachment_save(attachment, file, NULL);
3892 MTUNLOCK_PDFLIB();
3894 g_free(file);
3898 return TRUE;
3902 //==========================================================================
3904 // cmd_info
3906 //==========================================================================
3907 gboolean cmd_info (int argc, char **argv)
3909 if(!Zathura.PDF.document)
3910 return TRUE;
3912 static gboolean visible = FALSE;
3914 if(!Zathura.UI.information)
3916 GtkListStore *list;
3917 GtkTreeIter iter;
3918 GtkCellRenderer *renderer;
3919 GtkTreeSelection *selection;
3921 list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
3923 /* read document information */
3924 gchar *title, *author;
3925 gchar *subject, *keywords;
3926 gchar *creator, *producer;
3927 GTime creation_date, modification_date;
3929 g_object_get(Zathura.PDF.document,
3930 "title", &title,
3931 "author", &author,
3932 "subject", &subject,
3933 "keywords", &keywords,
3934 "creator", &creator,
3935 "producer", &producer,
3936 "creation-date", &creation_date,
3937 "mod-date", &modification_date,
3938 NULL);
3940 /* append information to list */
3941 gtk_list_store_append(list, &iter);
3942 gtk_list_store_set(list, &iter, 0, "Author", 1, author ? author : "", -1);
3943 gtk_list_store_append(list, &iter);
3944 gtk_list_store_set(list, &iter, 0, "Title", 1, title ? title : "", -1);
3945 gtk_list_store_append(list, &iter);
3946 gtk_list_store_set(list, &iter, 0, "Subject", 1, subject ? subject : "", -1);
3947 gtk_list_store_append(list, &iter);
3948 gtk_list_store_set(list, &iter, 0, "Keywords", 1, keywords ? keywords : "", -1);
3949 gtk_list_store_append(list, &iter);
3950 gtk_list_store_set(list, &iter, 0, "Creator", 1, creator ? creator : "", -1);
3951 gtk_list_store_append(list, &iter);
3952 gtk_list_store_set(list, &iter, 0, "Producer", 1, producer ? producer : "", -1);
3954 Zathura.UI.information = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list));
3955 renderer = gtk_cell_renderer_text_new();
3957 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(Zathura.UI.information), -1,
3958 "Name", renderer, "text", 0, NULL);
3959 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(Zathura.UI.information), -1,
3960 "Value", renderer, "text", 1, NULL);
3962 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(Zathura.UI.information));
3963 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
3965 gtk_widget_show_all(Zathura.UI.information);
3968 if(!visible)
3969 switch_view(Zathura.UI.information);
3970 else
3971 switch_view(Zathura.UI.document);
3973 visible = !visible;
3975 return FALSE;
3979 //==========================================================================
3981 // cmd_map
3983 //==========================================================================
3984 gboolean cmd_map (int argc, char **argv)
3986 if(argc < 2)
3987 return TRUE;
3989 char* ks = argv[0];
3991 /* search for the right shortcut function */
3992 int sc_id = -1;
3994 int sc_c;
3995 for(sc_c = 0; sc_c < LENGTH(shortcut_names); sc_c++)
3997 if(!strcmp(argv[1], shortcut_names[sc_c].name))
3999 sc_id = sc_c;
4000 break;
4004 if(sc_id == -1)
4006 notify(WARNING, "No such shortcut function exists");
4007 return FALSE;
4010 /* parse modifier and key */
4011 int mask = 0;
4012 int key = 0;
4013 int keyl = strlen(ks);
4014 int mode = NORMAL;
4016 // single key (e.g.: g)
4017 if(keyl == 1)
4018 key = ks[0];
4020 // modifier and key (e.g.: <S-g>
4021 // special key or modifier and key/special key (e.g.: <S-g>, <Space>)
4023 else if(keyl >= 3 && ks[0] == '<' && ks[keyl-1] == '>')
4025 char* specialkey = NULL;
4027 /* check for modifier */
4028 if(keyl >= 5 && ks[2] == '-')
4030 /* evaluate modifier */
4031 switch(ks[1])
4033 case 'S':
4034 mask = GDK_SHIFT_MASK;
4035 break;
4036 case 'C':
4037 mask = GDK_CONTROL_MASK;
4038 break;
4041 /* no valid modifier */
4042 if(!mask)
4044 notify(WARNING, "No valid modifier given.");
4045 return FALSE;
4048 /* modifier and special key */
4049 if(keyl > 5)
4050 specialkey = g_strndup(ks+3, keyl-4);
4051 else
4052 key = ks[3];
4054 else
4055 specialkey = ks;
4057 /* search special key */
4058 int g_c;
4059 for(g_c = 0; specialkey && g_c < LENGTH(gdk_keys); g_c++)
4061 if(!strcmp(specialkey, gdk_keys[g_c].identifier))
4063 key = gdk_keys[g_c].key;
4064 break;
4068 if(specialkey)
4069 g_free(specialkey);
4072 if(!key)
4074 notify(WARNING, "No valid key binding given.");
4075 return FALSE;
4078 /* parse argument */
4079 Argument arg = {0, 0};
4081 if(argc >= 3)
4083 int arg_id = -1;
4085 /* compare argument with given argument names... */
4086 int arg_c;
4087 for(arg_c = 0; arg_c < LENGTH(argument_names); arg_c++)
4089 if(!strcmp(argv[2], argument_names[arg_c].name))
4091 arg_id = argument_names[arg_c].argument;
4092 break;
4096 /* if not, save it do .data */
4097 if(arg_id == -1)
4098 arg.data = argv[2];
4099 else
4100 arg.n = arg_id;
4103 /* parse mode */
4104 if(argc >= 4)
4106 int mode_c;
4107 for(mode_c = 0; mode_c < LENGTH(mode_names); mode_c++)
4109 if(!strcmp(argv[3], mode_names[mode_c].name))
4111 mode = mode_names[mode_c].mode;
4112 break;
4117 /* search for existing binding to overwrite it */
4118 ShortcutList* sc = Zathura.Bindings.sclist;
4119 while(sc && sc->next != NULL)
4121 if(sc->element.key == key && sc->element.mask == mask
4122 && sc->element.mode == mode)
4124 sc->element.function = shortcut_names[sc_id].function;
4125 sc->element.argument = arg;
4126 return TRUE;
4129 sc = sc->next;
4132 /* create new entry */
4133 ShortcutList* entry = malloc(sizeof(ShortcutList));
4134 if(!entry)
4135 out_of_memory();
4137 entry->element.mask = mask;
4138 entry->element.key = key;
4139 entry->element.function = shortcut_names[sc_id].function;
4140 entry->element.mode = mode;
4141 entry->element.argument = arg;
4142 entry->next = NULL;
4144 /* append to list */
4145 if(!Zathura.Bindings.sclist)
4146 Zathura.Bindings.sclist = entry;
4148 if(sc)
4149 sc->next = entry;
4151 return TRUE;
4155 //==========================================================================
4157 // cmd_open
4159 //==========================================================================
4160 gboolean cmd_open (int argc, char **argv)
4162 if(argc == 0 || strlen(argv[0]) == 0)
4163 return TRUE;
4165 /* assembly the arguments back to one string */
4166 int i = 0;
4167 GString *filepath = g_string_new("");
4168 for(i = 0; i < argc; i++)
4170 if(i != 0)
4171 filepath = g_string_append_c(filepath, ' ');
4173 filepath = g_string_append(filepath, argv[i]);
4176 gboolean res = open_file(filepath->str, NULL);
4177 g_string_free(filepath, TRUE);
4178 return res;
4182 //==========================================================================
4184 // cmd_print
4186 //==========================================================================
4187 gboolean cmd_print (int argc, char **argv)
4189 if(!Zathura.PDF.document)
4190 return TRUE;
4192 if(argc == 0)
4194 notify(WARNING, "No printer specified");
4195 return FALSE;
4198 char* printer = argv[0];
4199 char* sites = (argc >= 2) ? g_strdup(argv[1]) : g_strdup_printf("1-%i", Zathura.PDF.number_of_pages);
4200 GString *addit = g_string_new("");
4202 int i;
4203 for(i = 2; i < argc; i++)
4205 if(i != 0)
4206 addit = g_string_append_c(addit, ' ');
4208 addit = g_string_append(addit, argv[i]);
4211 char* escaped_filename = g_shell_quote(Zathura.PDF.file);
4212 char* command = g_strdup_printf(print_command, printer, sites, addit->str, escaped_filename);
4213 system(command);
4215 g_free(sites);
4216 g_free(escaped_filename);
4217 g_free(command);
4218 g_string_free(addit, TRUE);
4220 return TRUE;
4224 //==========================================================================
4226 // cmd_rotate
4228 //==========================================================================
4229 gboolean cmd_rotate (int argc, char **argv)
4231 return TRUE;
4235 //==========================================================================
4237 // cmd_set
4239 //==========================================================================
4240 gboolean cmd_set (int argc, char **argv)
4242 if(argc <= 0)
4243 return FALSE;
4245 int i;
4246 for(i = 0; i < LENGTH(settings); i++)
4248 if(!strcmp(argv[0], settings[i].name))
4250 /* check var type */
4251 if(settings[i].type == 'b')
4253 gboolean *x = (gboolean*) (settings[i].variable);
4254 *x = !(*x);
4256 if(argv[1])
4258 if(!strcmp(argv[1], "false") || !strcmp(argv[1], "0"))
4259 *x = FALSE;
4260 else
4261 *x = TRUE;
4264 else if(settings[i].type == 'i')
4266 if(argc != 2)
4267 return FALSE;
4269 int *x = (int*) (settings[i].variable);
4271 int id = -1;
4272 int arg_c;
4273 for(arg_c = 0; arg_c < LENGTH(argument_names); arg_c++)
4275 if(!strcmp(argv[1], argument_names[arg_c].name))
4277 id = argument_names[arg_c].argument;
4278 break;
4282 if(id == -1)
4283 id = atoi(argv[1]);
4285 *x = id;
4287 else if(settings[i].type == 'f')
4289 if(argc != 2)
4290 return FALSE;
4292 float *x = (float*) (settings[i].variable);
4293 if(argv[1])
4294 *x = atof(argv[1]);
4296 else if(settings[i].type == 's')
4298 if(argc < 2)
4299 return FALSE;
4301 /* assembly the arguments back to one string */
4302 int j;
4303 GString *s = g_string_new("");
4304 for(j = 1; j < argc; j++)
4306 if(j != 1)
4307 s = g_string_append_c(s, ' ');
4309 s = g_string_append(s, argv[j]);
4312 char **x = (char**) settings[i].variable;
4313 *x = s->str;
4315 else if(settings[i].type == 'c')
4317 if(argc != 2)
4318 return FALSE;
4320 char *x = (char*) (settings[i].variable);
4321 if(argv[1])
4322 *x = argv[1][0];
4325 /* re-init */
4326 if(settings[i].reinit)
4327 init_look();
4329 /* render */
4330 if(settings[i].render)
4332 if(!Zathura.PDF.document)
4333 return FALSE;
4335 draw(Zathura.PDF.page_number);
4340 update_status();
4341 return TRUE;
4345 //==========================================================================
4347 // cmd_quit
4349 //==========================================================================
4350 gboolean cmd_quit (int argc, char **argv)
4352 cb_destroy(NULL, NULL);
4353 return TRUE;
4357 //==========================================================================
4359 // save_file
4361 //==========================================================================
4362 gboolean save_file (int argc, char **argv, gboolean overwrite)
4364 if(argc == 0 || !Zathura.PDF.document)
4365 return TRUE;
4367 gchar* file_path = NULL;
4369 if(argv[0][0] == '~')
4371 gchar* home_path = get_home_dir();
4372 file_path = g_build_filename(home_path, argv[0]+1, NULL);
4373 g_free(home_path);
4375 else
4376 file_path = g_strdup(argv[0]);
4378 if (!overwrite && g_file_test(file_path, G_FILE_TEST_EXISTS))
4380 char* message = g_strdup_printf("File already exists: %s. Use :write! to overwrite it.", file_path);
4381 notify(ERROR, message);
4382 g_free(message);
4383 g_free(file_path);
4384 return FALSE;
4387 char* path = NULL;
4388 if (file_path[0] == '/')
4389 path = g_strdup_printf("file://%s", file_path);
4390 else
4392 char* cur = g_get_current_dir();
4393 path = g_strdup_printf("file://%s/%s", cur, file_path);
4394 g_free(cur);
4396 g_free(file_path);
4398 MTLOCK_PDFLIB();
4399 /* format path */
4400 GError* error = NULL;
4401 if (!poppler_document_save(Zathura.PDF.document, path, &error))
4403 g_free(path);
4404 char* message = g_strdup_printf("Can not write file: %s", error->message);
4405 notify(ERROR, message);
4406 g_free(message);
4407 g_error_free(error);
4408 MTUNLOCK_PDFLIB();
4409 return FALSE;
4412 MTUNLOCK_PDFLIB();
4413 g_free(path);
4415 return TRUE;
4419 //==========================================================================
4421 // cmd_save
4423 //==========================================================================
4424 gboolean cmd_save (int argc, char **argv)
4426 return save_file(argc, argv, FALSE);
4430 //==========================================================================
4432 // cmd_savef
4434 //==========================================================================
4435 gboolean cmd_savef (int argc, char **argv)
4437 return save_file(argc, argv, TRUE);
4441 //**************************************************************************
4443 // completion command implementation
4445 //**************************************************************************
4447 //==========================================================================
4449 // cc_bookmark
4451 //==========================================================================
4452 Completion *cc_bookmark (char *input) {
4453 Completion* completion = completion_init();
4454 CompletionGroup* group = completion_group_create(NULL);
4456 completion_add_group(completion, group);
4458 int i = 0;
4459 int input_length = input ? strlen(input) : 0;
4461 for(i = 0; i < Zathura.Bookmarks.number_of_bookmarks; i++)
4463 if( (input_length <= strlen(Zathura.Bookmarks.bookmarks[i].id)) &&
4464 !strncmp(input, Zathura.Bookmarks.bookmarks[i].id, input_length) )
4466 completion_group_add_element(group, Zathura.Bookmarks.bookmarks[i].id, g_strdup_printf("Page %d", Zathura.Bookmarks.bookmarks[i].page));
4470 return completion;
4474 //==========================================================================
4476 // cc_export
4478 //==========================================================================
4479 Completion *cc_export (char *input)
4481 Completion* completion = completion_init();
4482 CompletionGroup* group = completion_group_create(NULL);
4484 completion_add_group(completion, group);
4486 completion_group_add_element(group, "images", "Export images");
4487 completion_group_add_element(group, "attachments", "Export attachments");
4489 return completion;
4493 //==========================================================================
4495 // cc_open
4497 //==========================================================================
4498 Completion *cc_open (char *input)
4500 Completion* completion = completion_init();
4501 CompletionGroup* group = completion_group_create(NULL);
4503 completion_add_group(completion, group);
4505 /* ~ */
4506 if(input && input[0] == '~')
4508 gchar* home_path = get_home_dir();
4509 char *file = g_strdup_printf(":open %s/%s", home_path, input+1);
4510 g_free(home_path);
4511 gtk_entry_set_text(Zathura.UI.inputbar, file);
4512 gtk_editable_set_position(GTK_EDITABLE(Zathura.UI.inputbar), -1);
4513 g_free(file);
4514 completion_free(completion);
4515 return NULL;
4518 /* read dir */
4519 char* path = g_strdup("/");
4520 char* file = g_strdup("");
4521 int file_length = 0;
4523 /* parse input string */
4524 if(input && strlen(input) > 0)
4526 char* dinput = g_strdup(input);
4527 char* binput = g_strdup(input);
4528 char* path_temp = dirname(dinput);
4529 char* file_temp = basename(binput);
4530 char last_char = input[strlen(input)-1];
4532 if( !strcmp(path_temp, "/") && !strcmp(file_temp, "/") )
4534 g_free(file);
4535 file = g_strdup("");
4537 else if( !strcmp(path_temp, "/") && strcmp(file_temp, "/") && last_char != '/')
4539 g_free(file);
4540 file = g_strdup(file_temp);
4542 else if( !strcmp(path_temp, "/") && strcmp(file_temp, "/") && last_char == '/')
4544 g_free(path);
4545 path = g_strdup_printf("/%s/", file_temp);
4547 else if(last_char == '/')
4549 g_free(path);
4550 path = g_strdup(input);
4552 else
4554 g_free(path);
4555 g_free(file);
4556 path = g_strdup_printf("%s/", path_temp);
4557 file = g_strdup(file_temp);
4560 g_free(dinput);
4561 g_free(binput);
4564 file_length = strlen(file);
4566 /* open directory */
4567 GDir* dir = g_dir_open(path, 0, NULL);
4568 if(!dir)
4570 g_free(path);
4571 g_free(file);
4572 completion_free(completion);
4573 return NULL;
4576 /* create element list */
4577 char* name = NULL;
4579 while((name = (char*) g_dir_read_name(dir)) != NULL)
4581 char* d_name = g_filename_display_name(name);
4582 int d_length = strlen(d_name);
4584 if( ((file_length <= d_length) && !strncmp(file, d_name, file_length)) ||
4585 (file_length == 0) )
4587 char* d = g_strdup_printf("%s%s", path, d_name);
4588 if(g_file_test(d, G_FILE_TEST_IS_DIR))
4590 gchar *subdir = d;
4591 d = g_strdup_printf("%s/", subdir);
4592 g_free(subdir);
4594 completion_group_add_element(group, d, NULL);
4595 g_free(d);
4597 g_free(d_name);
4600 g_dir_close(dir);
4601 g_free(file);
4602 g_free(path);
4604 return completion;
4608 //==========================================================================
4610 // cc_print
4612 //==========================================================================
4613 Completion *cc_print (char *input)
4615 Completion* completion = completion_init();
4616 CompletionGroup* group = completion_group_create(NULL);
4618 completion_add_group(completion, group);
4620 int input_length = input ? strlen(input) : 0;
4622 /* read printers */
4623 char *current_line = NULL, current_char;
4624 int count = 0;
4625 FILE *fp;
4627 fp = popen(list_printer_command, "r");
4629 if(!fp)
4631 completion_free(completion);
4632 return NULL;
4635 while((current_char = fgetc(fp)) != EOF)
4637 if(!current_line)
4638 current_line = malloc(sizeof(char));
4639 if(!current_line)
4640 out_of_memory();
4642 current_line = safe_realloc((void**)&current_line, count+1, sizeof(char));
4643 if(!current_line)
4644 out_of_memory();
4646 if(current_char != '\n')
4647 current_line[count++] = current_char;
4648 else
4650 current_line[count] = '\0';
4651 int line_length = strlen(current_line);
4653 if( (input_length <= line_length) ||
4654 (!strncmp(input, current_line, input_length)) )
4656 completion_group_add_element(group, current_line, NULL);
4659 free(current_line);
4660 current_line = NULL;
4661 count = 0;
4665 pclose(fp);
4667 return completion;
4671 //==========================================================================
4673 // cc_set
4675 //==========================================================================
4676 Completion *cc_set (char *input)
4678 Completion* completion = completion_init();
4679 CompletionGroup* group = completion_group_create(NULL);
4681 completion_add_group(completion, group);
4683 int i = 0;
4684 int input_length = input ? strlen(input) : 0;
4686 for(i = 0; i < LENGTH(settings); i++)
4688 if( (input_length <= strlen(settings[i].name)) &&
4689 !strncmp(input, settings[i].name, input_length) )
4691 completion_group_add_element(group, settings[i].name, settings[i].description);
4695 return completion;
4699 //**************************************************************************
4701 // buffer command implementation
4703 //**************************************************************************
4705 //==========================================================================
4707 // bcmd_goto
4709 //==========================================================================
4710 void bcmd_goto (char* buffer, Argument* argument)
4712 if(!Zathura.PDF.document)
4713 return;
4715 int b_length = strlen(buffer);
4716 if(b_length < 1)
4717 return;
4719 if(!strcmp(buffer, "gg"))
4720 set_page(0);
4721 else if(!strcmp(buffer, "G"))
4722 set_page(Zathura.PDF.number_of_pages-1);
4723 else
4725 char* id = g_strndup(buffer, b_length-1);
4726 int pid = atoi(id);
4728 if(Zathura.Global.goto_mode == GOTO_LABELS)
4730 int i;
4731 for(i = 0; i < Zathura.PDF.number_of_pages; i++)
4732 if(!strcmp(id, Zathura.PDF.pages[i]->label))
4733 pid = Zathura.PDF.pages[i]->id;
4735 set_page(pid-1);
4736 g_free(id);
4739 update_status();
4743 //==========================================================================
4745 // try_goto
4747 //==========================================================================
4748 gboolean try_goto (const char *buffer)
4750 char* endptr = NULL;
4751 long page_number = strtol(buffer, &endptr, 10)-1;
4752 if(*endptr)
4753 /* conversion error */
4754 return FALSE;
4755 else
4757 /* behave like vim: <= 1 => first line, >= #lines => last line */
4758 page_number = MAX(0, MIN(Zathura.PDF.number_of_pages-1, page_number));
4759 set_page(page_number);
4760 update_status();
4761 return TRUE;
4766 //==========================================================================
4768 // bcmd_scroll
4770 //==========================================================================
4772 void bcmd_scroll (char *buffer, Argument *argument)
4774 int b_length = strlen(buffer);
4775 if(b_length < 1)
4776 return;
4778 int percentage = atoi(g_strndup(buffer, b_length-1));
4779 percentage = (percentage < 0) ? 0 : ((percentage > 100) ? 100 : percentage);
4781 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(Zathura.UI.view);
4783 gdouble view_size = gtk_adjustment_get_page_size(adjustment);
4784 gdouble max = gtk_adjustment_get_upper(adjustment)-view_size;
4785 gdouble nvalue = (percentage*max)/100;
4787 percentage = 0;
4788 nvalue = 0;
4790 Zathura.State.scroll_percentage = percentage;
4791 gtk_adjustment_set_value(adjustment, nvalue);
4792 update_status();
4797 //==========================================================================
4799 // bcmd_zoom
4801 //==========================================================================
4802 void bcmd_zoom (char *buffer, Argument *argument)
4804 Zathura.Global.adjust_mode = ADJUST_NONE;
4806 if(argument->n == ZOOM_IN)
4808 if((Zathura.PDF.scale+zoom_step) <= zoom_max)
4809 Zathura.PDF.scale += zoom_step;
4810 else
4811 Zathura.PDF.scale = zoom_max;
4813 else if(argument->n == ZOOM_OUT)
4815 if((Zathura.PDF.scale-zoom_step) >= zoom_min)
4816 Zathura.PDF.scale -= zoom_step;
4817 else
4818 Zathura.PDF.scale = zoom_min;
4820 else if(argument->n == ZOOM_SPECIFIC)
4822 int b_length = strlen(buffer);
4823 if(b_length < 1)
4824 return;
4826 int value = atoi(g_strndup(buffer, b_length-1));
4827 if(value <= zoom_min)
4828 Zathura.PDF.scale = zoom_min;
4829 else if(value >= zoom_max)
4830 Zathura.PDF.scale = zoom_max;
4831 else
4832 Zathura.PDF.scale = value;
4834 else
4835 Zathura.PDF.scale = 100;
4837 Zathura.Search.draw = TRUE;
4838 draw(Zathura.PDF.page_number);
4839 update_status();
4843 //**************************************************************************
4845 // special command implementation
4847 //**************************************************************************
4849 //==========================================================================
4851 // scmd_search
4853 //==========================================================================
4854 gboolean scmd_search (gchar *input, Argument *argument)
4856 if(!input || !strlen(input))
4857 return TRUE;
4859 argument->data = input;
4860 sc_search(argument);
4862 return TRUE;
4866 //**************************************************************************
4868 // callback implementation
4870 //**************************************************************************
4872 //==========================================================================
4874 // cb_destroy
4876 //==========================================================================
4877 gboolean cb_destroy (GtkWidget *widget, gpointer data) {
4878 pango_font_description_free(Zathura.Style.font);
4880 if(Zathura.PDF.document)
4881 close_file(FALSE);
4883 /* clean up bookmarks */
4884 g_free(Zathura.Bookmarks.file);
4885 if (Zathura.Bookmarks.data)
4886 g_key_file_free(Zathura.Bookmarks.data);
4888 /* destroy mutexes */
4889 g_static_mutex_free(&(Zathura.Lock.pdflib_lock));
4890 g_static_mutex_free(&(Zathura.Lock.search_lock));
4891 g_static_mutex_free(&(Zathura.Lock.pdf_obj_lock));
4892 g_static_mutex_free(&(Zathura.Lock.select_lock));
4894 /* inotify */
4895 if(Zathura.FileMonitor.monitor)
4896 g_object_unref(Zathura.FileMonitor.monitor);
4897 if(Zathura.FileMonitor.file)
4898 g_object_unref(Zathura.FileMonitor.file);
4900 g_list_free(Zathura.Global.history);
4902 /* clean shortcut list */
4903 ShortcutList* sc = Zathura.Bindings.sclist;
4905 while(sc)
4907 ShortcutList* ne = sc->next;
4908 free(sc);
4909 sc = ne;
4912 g_free(Zathura.State.filename);
4913 g_free(Zathura.State.pages);
4915 g_free(Zathura.Config.config_dir);
4916 g_free(Zathura.Config.data_dir);
4917 if (Zathura.StdinSupport.file)
4918 g_unlink(Zathura.StdinSupport.file);
4919 g_free(Zathura.StdinSupport.file);
4921 gtk_main_quit();
4923 return TRUE;
4927 //==========================================================================
4929 // render_view
4931 // if we don't want a full clear, we'll only clear sides and gaps
4933 //==========================================================================
4934 void render_view (GtkWidget *widget, gboolean fullclear) {
4935 if (!widget || !widget->window) return;
4937 if (!Zathura.PDF.document) {
4938 gdk_window_clear(widget->window);
4939 return;
4942 int page_id = Zathura.PDF.page_number;
4943 if (page_id < 0 || page_id > Zathura.PDF.number_of_pages) {
4944 gdk_window_clear(widget->window);
4945 return;
4948 if (fullclear) gdk_window_clear(widget->window);
4950 int window_x, window_y;
4951 gdk_drawable_get_size(widget->window, &window_x, &window_y);
4953 const double scale = ((double)Zathura.PDF.scale/100.0);
4955 int yofs = get_current_page_screen_ytop();
4957 while (yofs < window_y && page_id < Zathura.PDF.number_of_pages) {
4958 Page *current_page = Zathura.PDF.pages[page_id];
4959 const int width = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->width : current_page->height)*scale;
4960 const int height = (Zathura.PDF.rotate == 0 || Zathura.PDF.rotate == 180 ? current_page->height : current_page->width)*scale;
4962 int offset_x;
4964 if (window_x > width) {
4965 switch (Zathura.Global.xcenter_mode) {
4966 case CENTER_LEFT_TOP: offset_x = 0; break;
4967 case CENTER_RIGHT_BOTTOM: offset_x = window_x-width; break;
4968 default: offset_x = (window_x-width)/2; break;
4970 } else {
4971 offset_x = 0;
4974 if (Zathura.Search.draw) {
4975 GList *list;
4976 for (list = Zathura.Search.results; list && list->data; list = g_list_next(list)) {
4977 highlight_result(Zathura.Search.page, (PopplerRectangle *)list->data);
4979 Zathura.Search.draw = FALSE;
4982 cairo_t *cairo = gdk_cairo_create(widget->window);
4983 cairo_surface_t *surf = create_page_surface(page_id);
4984 cairo_set_source_surface(cairo, surf, offset_x, yofs);
4985 cairo_paint(cairo);
4986 cairo_destroy(cairo);
4988 // clear sides and gap
4989 if (!fullclear) {
4990 // left part
4991 if (offset_x > 0) gdk_window_clear_area(widget->window, 0, yofs, offset_x, height);
4992 // right part
4993 if (offset_x+width < window_x) gdk_window_clear_area(widget->window, offset_x+width, yofs, window_x-(offset_x+width), height);
4994 // gap
4995 if (Zathura.PDF.page_gap > 0) gdk_window_clear_area(widget->window, 0, yofs+height, window_x, Zathura.PDF.page_gap);
4998 yofs += height+Zathura.PDF.page_gap;
4999 page_id += 1;
5004 //==========================================================================
5006 // cb_draw
5008 //==========================================================================
5009 gboolean cb_draw (GtkWidget *widget, GdkEventExpose *expose, gpointer data) {
5010 render_view(widget, FALSE);
5011 return TRUE;
5015 //==========================================================================
5017 // cb_inputbar_kb_pressed
5019 //==========================================================================
5020 gboolean cb_inputbar_kb_pressed (GtkWidget *widget, GdkEventKey *event, gpointer data)
5022 int i;
5024 /* inputbar shortcuts */
5025 for(i = 0; i < LENGTH(inputbar_shortcuts); i++)
5027 if(event->keyval == inputbar_shortcuts[i].key &&
5028 (((event->state&inputbar_shortcuts[i].mask) == inputbar_shortcuts[i].mask)
5029 || inputbar_shortcuts[i].mask == 0))
5031 inputbar_shortcuts[i].function(&(inputbar_shortcuts[i].argument));
5032 return TRUE;
5036 /* special commands */
5037 char* identifier_string = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 0, 1);
5038 char identifier = identifier_string[0];
5040 for(i = 0; i < LENGTH(special_commands); i++)
5042 if((identifier == special_commands[i].identifier) &&
5043 (special_commands[i].always == 1))
5045 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(Zathura.UI.inputbar), 1, -1);
5046 guint new_utf_char = gdk_keyval_to_unicode(event->keyval);
5048 if(new_utf_char != 0)
5050 gchar* newchar = g_malloc0(6*sizeof(gchar));
5051 if(newchar == NULL)
5053 g_free(input);
5054 continue;
5057 gint len = g_unichar_to_utf8(new_utf_char, newchar);
5058 newchar[len] = 0;
5059 gchar* tmp = g_strconcat(input, newchar, NULL);
5061 g_free(input);
5062 g_free(newchar);
5064 input = tmp;
5067 // FIXME
5068 if((special_commands[i].function == scmd_search) && (event->keyval == GDK_Return))
5070 Argument argument = { NO_SEARCH, NULL };
5071 scmd_search(input, &argument);
5073 else
5075 special_commands[i].function(input, &(special_commands[i].argument));
5078 g_free(identifier_string);
5079 g_free(input);
5080 return FALSE;
5084 g_free(identifier_string);
5086 return FALSE;
5090 //==========================================================================
5092 // cb_inputbar_activate
5094 //==========================================================================
5095 gboolean cb_inputbar_activate (GtkEntry *entry, gpointer data)
5097 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(entry), 1, -1);
5098 gchar **tokens = g_strsplit(input, " ", -1);
5099 g_free(input);
5101 gchar *command = tokens[0];
5102 int length = g_strv_length(tokens);
5103 int i = 0;
5104 gboolean retv = FALSE;
5105 gboolean succ = FALSE;
5107 /* no input */
5108 if(length < 1)
5110 isc_abort(NULL);
5111 g_strfreev(tokens);
5112 return FALSE;
5115 /* append input to the command history */
5116 Zathura.Global.history = g_list_append(Zathura.Global.history, g_strdup(gtk_entry_get_text(entry)));
5118 /* special commands */
5119 char identifier = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, 1)[0];
5120 for(i = 0; i < LENGTH(special_commands); i++)
5122 if(identifier == special_commands[i].identifier)
5124 /* special commands that are evaluated every key change are not
5125 * called here */
5126 if(special_commands[i].always == 1)
5128 isc_abort(NULL);
5129 g_strfreev(tokens);
5130 return TRUE;
5133 retv = special_commands[i].function(input, &(special_commands[i].argument));
5134 if(retv) isc_abort(NULL);
5135 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view));
5136 g_strfreev(tokens);
5137 return TRUE;
5141 /* search commands */
5142 for(i = 0; i < LENGTH(commands); i++)
5144 if((g_strcmp0(command, commands[i].command) == 0) ||
5145 (g_strcmp0(command, commands[i].abbr) == 0))
5147 retv = commands[i].function(length-1, tokens+1);
5148 succ = TRUE;
5149 break;
5153 if(retv)
5154 isc_abort(NULL);
5156 if(!succ) {
5157 /* it maybe a goto command */
5158 if(!try_goto(command))
5159 notify(ERROR, "Unknown command.");
5162 Argument arg = { HIDE };
5163 isc_completion(&arg);
5164 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view));
5166 g_strfreev(tokens);
5167 return TRUE;
5171 //==========================================================================
5173 // cb_inputbar_form_activate
5175 //==========================================================================
5176 gboolean cb_inputbar_form_activate (GtkEntry *entry, gpointer data)
5178 if(!Zathura.PDF.document)
5179 return TRUE;
5181 Page* current_page = Zathura.PDF.pages[Zathura.PDF.page_number];
5182 int number_of_links = 0, link_id = 1, new_page_id = Zathura.PDF.page_number;
5184 MTLOCK_PDFLIB();
5185 GList *link_list = poppler_page_get_link_mapping(current_page->page);
5186 MTUNLOCK_PDFLIB();
5187 link_list = g_list_reverse(link_list);
5189 if((number_of_links = g_list_length(link_list)) <= 0)
5190 return FALSE;
5192 /* parse entry */
5193 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(entry), 1, -1);
5194 gchar *token = input+strlen("Follow hint: ")-1;
5195 if(!token)
5196 return FALSE;
5198 int li = atoi(token);
5199 if(li <= 0 || li > number_of_links)
5201 set_page(Zathura.PDF.page_number);
5202 isc_abort(NULL);
5203 notify(WARNING, "Invalid hint");
5204 return TRUE;
5207 /* compare entry */
5208 GList *links;
5209 for(links = link_list; links; links = g_list_next(links))
5211 PopplerLinkMapping *link_mapping = (PopplerLinkMapping*) links->data;
5212 PopplerAction *action = link_mapping->action;
5214 /* only handle URI and internal links */
5215 if(action->type == POPPLER_ACTION_URI)
5217 if(li == link_id)
5218 open_uri(action->uri.uri);
5220 else if(action->type == POPPLER_ACTION_GOTO_DEST)
5222 if(li == link_id)
5224 if(action->goto_dest.dest->type == POPPLER_DEST_NAMED)
5226 PopplerDest* destination = poppler_document_find_dest(Zathura.PDF.document, action->goto_dest.dest->named_dest);
5227 if(destination)
5229 new_page_id = destination->page_num-1;
5230 poppler_dest_free(destination);
5233 else
5234 new_page_id = action->goto_dest.dest->page_num-1;
5237 else
5238 continue;
5240 link_id++;
5243 poppler_page_free_link_mapping(link_list);
5245 /* reset all */
5246 set_page(new_page_id);
5247 isc_abort(NULL);
5249 return TRUE;
5253 //==========================================================================
5255 // cb_inputbar_password_activate
5257 //==========================================================================
5258 gboolean cb_inputbar_password_activate (GtkEntry *entry, gpointer data)
5260 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(entry), 1, -1);
5261 gchar *token = input+strlen("Enter password: ")-1;
5262 if(!token)
5263 return FALSE;
5265 if(!open_file(Zathura.PDF.file, token))
5267 enter_password();
5268 return TRUE;
5271 /* replace default inputbar handler */
5272 g_signal_handler_disconnect((gpointer) Zathura.UI.inputbar, Zathura.Handler.inputbar_activate);
5273 Zathura.Handler.inputbar_activate = g_signal_connect(G_OBJECT(Zathura.UI.inputbar), "activate", G_CALLBACK(cb_inputbar_activate), NULL);
5275 isc_abort(NULL);
5277 return TRUE;
5281 //==========================================================================
5283 // cb_view_kb_pressed
5285 //==========================================================================
5286 gboolean cb_view_kb_pressed (GtkWidget *widget, GdkEventKey *event, gpointer data)
5288 //fprintf(stderr, "KEY=0x%04X\n", event->keyval);
5289 ShortcutList* sc = Zathura.Bindings.sclist;
5290 while(sc)
5293 event->keyval == sc->element.key
5294 && (CLEAN(event->state) == sc->element.mask || (sc->element.key >= 0x21
5295 && sc->element.key <= 0x7E && CLEAN(event->state) == GDK_SHIFT_MASK))
5296 && (Zathura.Global.mode&sc->element.mode || sc->element.mode == ALL)
5297 && sc->element.function
5300 if(!(Zathura.Global.buffer && strlen(Zathura.Global.buffer->str)) || (sc->element.mask == GDK_CONTROL_MASK) ||
5301 (sc->element.key <= 0x21 || sc->element.key >= 0x7E)
5304 sc->element.function(&(sc->element.argument));
5305 return TRUE;
5309 sc = sc->next;
5312 if(Zathura.Global.mode == ADD_MARKER)
5314 add_marker(event->keyval);
5315 change_mode(NORMAL);
5316 return TRUE;
5318 else if(Zathura.Global.mode == EVAL_MARKER)
5320 eval_marker(event->keyval);
5321 change_mode(NORMAL);
5322 return TRUE;
5325 /* append only numbers and characters to buffer */
5326 if( (event->keyval >= 0x21) && (event->keyval <= 0x7E))
5328 if(!Zathura.Global.buffer)
5329 Zathura.Global.buffer = g_string_new("");
5331 Zathura.Global.buffer = g_string_append_c(Zathura.Global.buffer, event->keyval);
5332 gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, Zathura.Global.buffer->str);
5335 /* search buffer commands */
5336 if(Zathura.Global.buffer)
5338 int i;
5339 for(i = 0; i < LENGTH(buffer_commands); i++)
5341 regex_t regex;
5342 int status;
5344 regcomp(&regex, buffer_commands[i].regex, REG_EXTENDED);
5345 status = regexec(&regex, Zathura.Global.buffer->str, (size_t) 0, NULL, 0);
5346 regfree(&regex);
5348 if(status == 0)
5350 buffer_commands[i].function(Zathura.Global.buffer->str, &(buffer_commands[i].argument));
5351 g_string_free(Zathura.Global.buffer, TRUE);
5352 Zathura.Global.buffer = NULL;
5353 gtk_label_set_markup((GtkLabel*) Zathura.Global.status_buffer, "");
5355 return TRUE;
5360 return FALSE;
5364 //==========================================================================
5366 // cb_view_resized
5368 //==========================================================================
5369 gboolean cb_view_resized (GtkWidget *widget, GtkAllocation *allocation, gpointer data)
5371 Argument arg;
5372 arg.n = Zathura.Global.adjust_mode;
5373 sc_adjust_window(&arg);
5375 return TRUE;
5379 //==========================================================================
5381 // cb_view_button_pressed
5383 //==========================================================================
5384 gboolean cb_view_button_pressed (GtkWidget *widget, GdkEventButton *event, gpointer data)
5386 if(!Zathura.PDF.document)
5387 return FALSE;
5389 /* clean page */
5390 draw(Zathura.PDF.page_number);
5391 MTLOCK_SELECT();
5392 Zathura.SelectPoint.x = event->x;
5393 Zathura.SelectPoint.y = event->y;
5394 MTUNLOCK_SELECT();
5396 return TRUE;
5400 //==========================================================================
5402 // cb_view_button_release
5404 //==========================================================================
5405 gboolean cb_view_button_release (GtkWidget *widget, GdkEventButton *event, gpointer data) {
5406 if (!Zathura.PDF.document) return FALSE;
5408 double offset_x = 0, offset_y = 0;
5409 int page_id = -1;
5410 PopplerRectangle rectangle;
5412 /* build selection rectangle */
5413 rectangle.x1 = event->x;
5414 rectangle.y1 = event->y;
5416 MTLOCK_SELECT();
5417 rectangle.x2 = Zathura.SelectPoint.x;
5418 rectangle.y2 = Zathura.SelectPoint.y;
5419 MTUNLOCK_SELECT();
5421 /* calculate offset */
5422 calculate_screen_offset(widget, event->x, event->y, &offset_x, &offset_y, &page_id);
5423 if (page_id < 0) return FALSE; // not on any page
5425 /* draw selection rectangle */
5426 /* first, evict page surface, because we need it recreated; this is a hack */
5427 surfCacheGet(page_id, -666.666); // specifying invalid scale will destroy the surface, if there was any
5428 cairo_t *cairo = cairo_create(create_page_surface(page_id));
5429 cairo_set_source_rgba(cairo, Zathura.Style.select_text.red, Zathura.Style.select_text.green, Zathura.Style.select_text.blue, transparency);
5430 cairo_rectangle(cairo, rectangle.x1-offset_x, rectangle.y1-offset_y, (rectangle.x2-rectangle.x1), (rectangle.y2-rectangle.y1));
5431 cairo_fill(cairo);
5432 cairo_destroy(cairo);
5433 gtk_widget_queue_draw(Zathura.UI.drawing_area);
5435 /* resize selection rectangle to document page */
5436 Page *current_page = Zathura.PDF.pages[page_id];
5437 const double page_width = current_page->width;
5438 const double page_height = current_page->height;
5439 const double scale = ((double)Zathura.PDF.scale/100.0);
5441 rectangle.x1 = (rectangle.x1-offset_x)/scale;
5442 rectangle.y1 = (rectangle.y1-offset_y)/scale;
5443 rectangle.x2 = (rectangle.x2-offset_x)/scale;
5444 rectangle.y2 = (rectangle.y2-offset_y)/scale;
5446 /* rotation */
5447 int rotate = Zathura.PDF.rotate;
5448 double x1 = rectangle.x1;
5449 double x2 = rectangle.x2;
5450 double y1 = rectangle.y1;
5451 double y2 = rectangle.y2;
5453 switch (rotate) {
5454 case 90:
5455 rectangle.x1 = y1;
5456 rectangle.y1 = page_height-x2;
5457 rectangle.x2 = y2;
5458 rectangle.y2 = page_height-x1;
5459 break;
5460 case 180:
5461 rectangle.x1 = (page_height-y1);
5462 rectangle.y1 = (page_width-x2);
5463 rectangle.x2 = (page_height-y2);
5464 rectangle.y2 = (page_width-x1);
5465 break;
5466 case 270:
5467 rectangle.x1 = page_width-y2;
5468 rectangle.y1 = x1;
5469 rectangle.x2 = page_width-y1;
5470 rectangle.y2 = x2;
5471 break;
5474 /* reset points of the rectangle so that p1 is in the top-left corner
5475 * and p2 is in the bottom right corner */
5476 if (rectangle.x1 > rectangle.x2) {
5477 const double d = rectangle.x1;
5478 rectangle.x1 = rectangle.x2;
5479 rectangle.x2 = d;
5481 if (rectangle.y2 > rectangle.y1) {
5482 const double d = rectangle.y1;
5483 rectangle.y1 = rectangle.y2;
5484 rectangle.y2 = d;
5487 #if !POPPLER_CHECK_VERSION(0,15,0)
5488 /* adapt y coordinates */
5489 rectangle.y1 = page_height-rectangle.y1;
5490 rectangle.y2 = page_height-rectangle.y2;
5491 #endif
5493 /* get selected text */
5494 MTLOCK_PDFLIB();
5495 char *selected_text = poppler_page_get_selected_text(Zathura.PDF.pages[page_id]->page, SELECTION_STYLE, &rectangle);
5496 if (selected_text) {
5497 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), selected_text, -1);
5498 g_free(selected_text);
5500 MTUNLOCK_PDFLIB();
5502 return TRUE;
5506 //==========================================================================
5508 // cb_view_motion_notify
5510 //==========================================================================
5511 gboolean cb_view_motion_notify (GtkWidget *widget, GdkEventMotion *event, gpointer data)
5513 return TRUE;
5517 //==========================================================================
5519 // cb_view_scrolled
5521 //==========================================================================
5522 gboolean cb_view_scrolled (GtkWidget *widget, GdkEventScroll *event, gpointer data)
5524 int i;
5525 for(i = 0; i < LENGTH(mouse_scroll_events); i++)
5527 if(event->direction == mouse_scroll_events[i].direction)
5529 mouse_scroll_events[i].function(&(mouse_scroll_events[i].argument));
5530 return TRUE;
5534 return FALSE;
5538 //==========================================================================
5540 // cb_watch_file
5542 //==========================================================================
5543 gboolean cb_watch_file (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event, gpointer data) {
5544 if(event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
5545 return FALSE;
5547 sc_reload(NULL);
5549 return TRUE;
5554 //**************************************************************************
5556 // main function
5558 //**************************************************************************
5560 int main (int argc, char *argv[]) {
5561 /* embed */
5562 Zathura.UI.embed = 0;
5564 Zathura.Config.config_dir = 0;
5565 Zathura.Config.data_dir = 0;
5567 char* config_dir = 0;
5568 char* data_dir = 0;
5569 GOptionEntry entries[] =
5571 { "reparent", 'e', 0, G_OPTION_ARG_INT, &Zathura.UI.embed, "Reparents to window specified by xid", "xid" },
5572 { "config-dir", 'c', 0, G_OPTION_ARG_FILENAME, &config_dir, "Path to the config directory", "path" },
5573 { "data-dir", 'd', 0, G_OPTION_ARG_FILENAME, &data_dir, "Path to the data directory", "path" },
5574 { NULL }
5577 GOptionContext* context = g_option_context_new(" [file] [password]");
5578 g_option_context_add_main_entries(context, entries, NULL);
5580 GError* error = NULL;
5581 if(!g_option_context_parse(context, &argc, &argv, &error))
5583 printf("Error parsing command line arguments: %s\n", error->message);
5584 g_option_context_free(context);
5585 g_error_free(error);
5586 return 1;
5588 g_option_context_free(context);
5590 if (config_dir)
5591 Zathura.Config.config_dir = g_strdup(config_dir);
5592 if (data_dir)
5593 Zathura.Config.data_dir = g_strdup(data_dir);
5595 g_thread_init(NULL);
5596 gdk_threads_init();
5598 gtk_init(&argc, &argv);
5600 init_zathura();
5601 init_directories();
5602 init_keylist();
5603 read_configuration();
5604 init_settings();
5605 init_bookmarks();
5606 init_look();
5608 if(argc > 1)
5610 char* password = (argc == 3) ? argv[2] : NULL;
5611 if (strcmp(argv[1], "-") == 0)
5612 open_stdin(password);
5613 else
5614 open_file(argv[1], password);
5617 switch_view(Zathura.UI.document);
5618 update_status();
5620 gtk_widget_show_all(GTK_WIDGET(Zathura.UI.window));
5621 gtk_widget_grab_focus(GTK_WIDGET(Zathura.UI.view));
5623 if(!Zathura.Global.show_inputbar)
5624 gtk_widget_hide(GTK_WIDGET(Zathura.UI.inputbar));
5626 if(!Zathura.Global.show_statusbar)
5627 gtk_widget_hide(GTK_WIDGET(Zathura.UI.statusbar));
5629 gdk_threads_enter();
5630 gtk_main();
5631 gdk_threads_leave();
5633 return 0;