1 /**
2  * @file    gui-preview.c
3  * @brief
4  *
5  * Copyright (C) 2009 Gummi Developers
6  * All Rights reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following
15  * conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  * OTHER DEALINGS IN THE SOFTWARE.
28  */
29 
30 #include "gui/gui-preview.h"
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include <cairo.h>
37 #include <glib.h>
38 #include <gdk/gdk.h>
39 #include <gtk/gtk.h>
40 #include <math.h>
41 #include <poppler.h>
42 
43 #ifdef WIN32
44   #include "syncTeX/synctex_parser.h"
45 #else
46   #include <synctex_parser.h>
47 #endif
48 
49 #include "configfile.h"
50 #include "constants.h"
51 #include "environment.h"
52 #include "motion.h"
53 #include "gui/gui-main.h"
54 
55 #ifdef HAVE_CONFIG_H
56 #   include "config.h"
57 #endif
58 
59 // compatibility fixes for libsynctex (>=1.16 && <=2.00):
60 #ifdef USE_SYNCTEX1
61   typedef synctex_scanner_t synctex_scanner_p;
62   typedef synctex_node_t synctex_node_p;
63   #define synctex_display_query(scanner, file, line, column, page) synctex_display_query(scanner, file, line, column)
64   #define synctex_scanner_next_result(scanner) synctex_next_result(scanner)
65 #endif
66 
67 #define page_inner(pc,i) (((pc)->pages + (i))->inner)
68 #define page_outer(pc,i) (((pc)->pages + (i))->outer)
69 
70 enum {
71     ZOOM_FIT_BOTH = 0,
72     ZOOM_FIT_WIDTH,
73     ZOOM_50,
74     ZOOM_70,
75     ZOOM_85,
76     ZOOM_100,
77     ZOOM_125,
78     ZOOM_150,
79     ZOOM_200,
80     ZOOM_300,
81     ZOOM_400,
82     N_ZOOM_SIZES
83 };
84 
85 static gfloat list_sizes[] = {-1, -1, 0.50, 0.70, 0.85, 1.0, 1.25, 1.5, 2.0,
86                               3.0, 4.0};
87 
88 extern Gummi* gummi;
89 extern GummiGui* gui;
90 
91 typedef struct {
92     gint page;
93     gint x; // of lower left corner
94     gint y; // of lower left corner
95     gint width;
96     gint height;
97     gint score;
98 } SyncNode;
99 
100 ////////////////////////////////////////////////////////////////////////////////
101 
102 // Update functions, to update cached values and gui-parameters after changes
103 static void update_scaled_size (GuPreviewGui* pc);
104 static void update_fit_scale (GuPreviewGui* pc);
105 static void update_current_page (GuPreviewGui* pc);
106 static void update_drawarea_size (GuPreviewGui *pc);
107 static void update_page_sizes (GuPreviewGui* pc);
108 static void update_prev_next_page (GuPreviewGui* pc);
109 static void update_page_input (GuPreviewGui* pc);
110 
111 // Simplicity functions for page layout
112 inline static gboolean is_continuous (GuPreviewGui* pc);
113 
114 // Functions for infos about the GUI */
115 inline static gboolean is_vscrollbar_visible (GuPreviewGui* pc);
116 inline static gboolean is_hscrollbar_visible (GuPreviewGui* pc);
117 
118 // Functions for simpler accessing of the array and struct data
119 inline static gdouble get_page_height (GuPreviewGui* pc, int page);
120 inline static gdouble get_page_width (GuPreviewGui* pc, int page);
121 
122 inline static gint get_document_margin (GuPreviewGui* pc);
123 inline static gint get_page_margin (GuPreviewGui* pc);
124 
125 // Other functions
126 static void block_handlers_current_page (GuPreviewGui* pc);
127 static void unblock_handlers_current_page (GuPreviewGui* pc);
128 static void set_fit_mode (GuPreviewGui* pc, enum GuPreviewFitModes fit_mode);
129 
130 static gboolean on_page_input_lost_focus (GtkWidget *widget, GdkEvent *event,
131                                           gpointer user_data);
132 
133 static gboolean on_button_pressed (GtkWidget* w, GdkEventButton* e, void* user);
134 
135 // Functions for layout and painting
136 static gint page_offset_x (GuPreviewGui* pc, gint page, gdouble x);
137 static gint page_offset_y (GuPreviewGui* pc, gint page, gdouble y);
138 static void paint_page (cairo_t *cr, GuPreviewGui* pc, gint page, gint x, gint y);
139 static cairo_surface_t* get_page_rendering (GuPreviewGui* pc, int page);
140 static gboolean remove_page_rendering (GuPreviewGui* pc, gint page);
141 
142 // Functions for syncronizing editor and preview via SyncTeX
143 static gboolean synctex_run_parser (GuPreviewGui* pc, GtkTextIter *sync_to, gchar* tex_file);
144 #if HAVE_POPPLER_PAGE_GET_SELECTED_TEXT
145 static void synctex_filter_results (GuPreviewGui* pc, GtkTextIter *sync_to);
146 #endif
147 static void synctex_scroll_to_node (GuPreviewGui* pc, SyncNode* node);
148 static SyncNode* synctex_one_node_found (GuPreviewGui* pc);
149 static void synctex_merge_nodes (GuPreviewGui* pc);
150 static void synctex_clear_sync_nodes (GuPreviewGui* pc);
151 
152 // Page Layout functions
153 static inline LayeredRectangle get_fov (GuPreviewGui* pc);
154 static void update_page_positions (GuPreviewGui* pc);
155 static gboolean layered_rectangle_intersect (const LayeredRectangle *src1,
156                                              const LayeredRectangle *src2,
157                                              LayeredRectangle *dest);
158 
159 static void previewgui_set_scale (GuPreviewGui* pc, gdouble scale, gdouble x, gdouble y);
160 
161 ////////////////////////////////////////////////////////////////////////////////
162 
163 
previewgui_init(GtkBuilder * builder)164 GuPreviewGui* previewgui_init (GtkBuilder * builder) {
165     g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
166 
167     GuPreviewGui* p = g_new0 (GuPreviewGui, 1);
168 
169     p->scrollw = GTK_WIDGET (gtk_builder_get_object (builder, "preview_scrollw"));
170     p->viewport = GTK_VIEWPORT (gtk_builder_get_object (builder, "preview_vport"));
171     p->drawarea = GTK_WIDGET (gtk_builder_get_object (builder, "preview_draw"));
172     p->toolbar = GTK_WIDGET (gtk_builder_get_object (builder, "preview_toolbar"));
173 
174     p->combo_sizes =
175         GTK_COMBO_BOX (gtk_builder_get_object (builder, "combo_sizes"));
176     p->page_next = GTK_WIDGET (gtk_builder_get_object (builder, "page_next"));
177     p->page_prev = GTK_WIDGET (gtk_builder_get_object (builder, "page_prev"));
178     p->page_label = GTK_WIDGET (gtk_builder_get_object (builder, "page_label"));
179     p->page_input = GTK_WIDGET (gtk_builder_get_object (builder, "page_input"));
180     p->preview_pause =
181         GTK_TOGGLE_TOOL_BUTTON (gtk_builder_get_object (builder, "preview_pause"));
182 
183     p->page_layout_single_page = GTK_RADIO_MENU_ITEM
184         (gtk_builder_get_object (builder, "page_layout_single_page"));
185     p->page_layout_one_column = GTK_RADIO_MENU_ITEM
186         (gtk_builder_get_object (builder, "page_layout_one_column"));
187     p->update_timer = 0;
188 
189     p->uri = NULL;
190     p->doc = NULL;
191     p->preview_on_idle = FALSE;
192     p->errormode = FALSE;
193 
194     p->hadj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (p->scrollw));
195     p->vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (p->scrollw));
196 
197     // Event handlers:
198     gtk_widget_add_events (p->drawarea, GDK_SCROLL_MASK
199                                       | GDK_BUTTON_PRESS_MASK
200                                       | GDK_BUTTON_MOTION_MASK);
201 
202     // Signals:
203     p->page_input_changed_handler = g_signal_connect (p->page_input,
204             "changed", G_CALLBACK (on_page_input_changed), p);
205     g_signal_connect (p->page_input,
206             "focus-out-event", G_CALLBACK(on_page_input_lost_focus), p);
207     p->combo_sizes_changed_handler = g_signal_connect (p->combo_sizes,
208             "changed", G_CALLBACK (on_combo_sizes_changed), p);
209     g_signal_connect (p->page_prev,
210             "clicked", G_CALLBACK (on_prev_page_clicked), p);
211     g_signal_connect (p->page_next,
212             "clicked", G_CALLBACK (on_next_page_clicked), p);
213 
214     p->on_resize_handler = g_signal_connect (p->scrollw, "size-allocate",
215             G_CALLBACK (on_resize), p);
216 
217     p->on_draw_handler = g_signal_connect (p->drawarea, "draw",
218             G_CALLBACK (on_draw), p);
219 
220     g_signal_connect (p->drawarea, "scroll-event",
221                       G_CALLBACK (on_scroll), p);
222     g_signal_connect (p->drawarea, "button-press-event",
223                       G_CALLBACK (on_button_pressed), p);
224     g_signal_connect (p->drawarea, "motion-notify-event",
225                       G_CALLBACK (on_motion), p);
226 
227     p->hvalue_changed_handler = g_signal_connect (p->hadj, "value-changed",
228                       G_CALLBACK (on_adj_changed), p);
229     p->vvalue_changed_handler = g_signal_connect (p->vadj, "value-changed",
230                       G_CALLBACK (on_adj_changed), p);
231     p->hchanged_handler = g_signal_connect (p->hadj, "changed",
232                       G_CALLBACK (on_adj_changed), p);
233     p->vchanged_handler = g_signal_connect (p->vadj, "changed",
234                       G_CALLBACK (on_adj_changed), p);
235 
236 
237     // The error panel is now imported from Glade. The following
238     // functions re-parent the panel widgets for use in Gummi
239     GtkWidget *holder =
240         GTK_WIDGET(gtk_builder_get_object (builder, "errorwindow"));
241     p->errorpanel =
242         GTK_WIDGET(gtk_builder_get_object (builder, "errorpanel"));
243 
244     gtk_container_remove(GTK_CONTAINER(holder), p->errorpanel);
245     g_object_unref(holder);
246 
247     // The scale to correct for the users DPI
248     gdouble screen_dpi = gdk_screen_get_resolution (
249 						 gdk_screen_get_default());
250 
251     if (screen_dpi == -1) screen_dpi = 96.0;
252 	gdouble poppler_scale = screen_dpi / 72.0;
253     slog (L_DEBUG, "Preview detected screen DPI at %.1f\n", screen_dpi);
254 
255     int i;
256     for (i=0; i < N_ZOOM_SIZES; i++) {
257         list_sizes[i] *= poppler_scale;
258     }
259 
260     if (config_value_as_str_equals ("Preview", "pagelayout", "single_page")) {
261         gtk_check_menu_item_set_active(
262                 GTK_CHECK_MENU_ITEM(p->page_layout_single_page), TRUE);
263         p->pageLayout = POPPLER_PAGE_LAYOUT_SINGLE_PAGE;
264     } else  {
265         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
266                     p->page_layout_one_column), TRUE);
267         p->pageLayout = POPPLER_PAGE_LAYOUT_ONE_COLUMN;
268     }
269 
270     if (config_get_boolean ("Compile", "pause")) {
271         gtk_toggle_tool_button_set_active (p->preview_pause, TRUE);
272     }
273 
274     slog (L_INFO, "Using libpoppler %s\n", poppler_get_version ());
275     return p;
276 }
277 
get_document_margin(GuPreviewGui * pc)278 inline static gint get_document_margin (GuPreviewGui* pc) {
279     if (pc->pageLayout == POPPLER_PAGE_LAYOUT_SINGLE_PAGE) {
280         return 0;
281     } else {
282         return DOCUMENT_MARGIN;
283     }
284 }
285 
get_page_margin(GuPreviewGui * pc)286 inline static gint get_page_margin (GuPreviewGui* pc) {
287     return PAGE_MARGIN;
288 }
289 
block_handlers_current_page(GuPreviewGui * pc)290 static void block_handlers_current_page (GuPreviewGui* pc) {
291     g_signal_handler_block(pc->hadj, pc->hvalue_changed_handler);
292     g_signal_handler_block(pc->vadj, pc->vvalue_changed_handler);
293     g_signal_handler_block(pc->hadj, pc->hchanged_handler);
294     g_signal_handler_block(pc->vadj, pc->vchanged_handler);
295 }
296 
unblock_handlers_current_page(GuPreviewGui * pc)297 static void unblock_handlers_current_page (GuPreviewGui* pc) {
298     g_signal_handler_unblock(pc->hadj, pc->hvalue_changed_handler);
299     g_signal_handler_unblock(pc->vadj, pc->vvalue_changed_handler);
300     g_signal_handler_unblock(pc->hadj, pc->hchanged_handler);
301     g_signal_handler_unblock(pc->vadj, pc->vchanged_handler);
302 }
303 
is_vscrollbar_visible(GuPreviewGui * pc)304 inline static gboolean is_vscrollbar_visible (GuPreviewGui* pc) {
305     GtkAllocation alloc1, alloc2;
306     gtk_widget_get_allocation(pc->scrollw, &alloc1);
307     gtk_widget_get_allocation(GTK_WIDGET(pc->viewport), &alloc2);
308 
309     return alloc1.width != alloc2.width;
310 }
311 
is_hscrollbar_visible(GuPreviewGui * pc)312 inline static gboolean is_hscrollbar_visible (GuPreviewGui* pc) {
313     GtkAllocation alloc1, alloc2;
314     gtk_widget_get_allocation(pc->scrollw, &alloc1);
315     gtk_widget_get_allocation(GTK_WIDGET(pc->viewport), &alloc2);
316 
317     return alloc1.height != alloc2.height;
318 }
319 
320 G_MODULE_EXPORT
previewgui_page_layout_radio_changed(GtkMenuItem * radioitem,gpointer data)321 void previewgui_page_layout_radio_changed(GtkMenuItem *radioitem, gpointer data) {
322     //L_F_DEBUG;
323 
324     if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM(radioitem))) {
325         return;
326     }
327 
328     GuPreviewGui* pc = gui->previewgui;
329 
330     PopplerPageLayout pageLayout;
331     if (gtk_check_menu_item_get_active(
332                 GTK_CHECK_MENU_ITEM(pc->page_layout_single_page))) {
333         pageLayout = POPPLER_PAGE_LAYOUT_SINGLE_PAGE;
334         config_set_string ("Preview", "pagelayout", "single_page");
335     } else {
336         pageLayout = POPPLER_PAGE_LAYOUT_ONE_COLUMN;
337         config_set_string ("Preview", "pagelayout", "one_column");
338     }
339 
340     previewgui_set_page_layout(gui->previewgui, pageLayout);
341 }
342 
previewgui_animated_scroll_step(gpointer data)343 static gboolean previewgui_animated_scroll_step(gpointer data) {
344     //L_F_DEBUG;
345     GuPreviewGui* pc = GU_PREVIEW_GUI(data);
346 
347     if (pc->ascroll_steps_left == 0) {
348 
349         return FALSE;
350     } else if (pc->ascroll_steps_left == 1) {
351 
352         block_handlers_current_page(pc);
353         previewgui_goto_xy (pc, pc->ascroll_end_x, pc->ascroll_end_y);
354         unblock_handlers_current_page(pc);
355         return FALSE;
356     } else {
357 
358         pc->ascroll_steps_left -= 1;
359 
360         gdouble r = (2.*pc->ascroll_steps_left) / ASCROLL_STEPS - 1;
361         gdouble r2 = r*r;
362 
363         gdouble rel_dist = 0.5*(ASCROLL_CONST_A * r2 * r2 * r +
364                 ASCROLL_CONST_B * r2 * r + ASCROLL_CONST_C * r) + 0.5;
365         gdouble new_x = pc->ascroll_end_x + pc->ascroll_dist_x*rel_dist;
366         gdouble new_y = pc->ascroll_end_y + pc->ascroll_dist_y*rel_dist;
367 
368         block_handlers_current_page(pc);
369         previewgui_goto_xy (pc, new_x, new_y);
370         unblock_handlers_current_page(pc);
371 
372         return TRUE;
373     }
374 }
375 
update_fit_scale(GuPreviewGui * pc)376 static void update_fit_scale(GuPreviewGui* pc) {
377 
378     if (g_active_tab->fit_mode == FIT_NUMERIC) {
379         return;
380     }
381     //L_F_DEBUG;
382 
383     GdkWindow *viewport_window;
384     gint view_width_without_bar;
385     gint view_height_without_bar;
386 
387     gdouble width_scaling;
388     gdouble height_scaling;
389     gdouble width_non_scaling;
390     gdouble height_non_scaling;
391 
392     width_scaling = pc->width_pages;
393     width_non_scaling = 2*get_document_margin(pc);
394 
395     if (is_continuous(pc)) {
396         height_scaling = pc->max_page_height;
397         height_non_scaling = 2*get_document_margin(pc);
398     } else {
399         height_scaling = get_page_height(pc, pc->current_page);
400         height_non_scaling = 2*get_document_margin(pc);
401     }
402 
403     gdouble full_height_scaling = pc->height_pages;
404     gdouble full_height_non_scaling = (pc->n_pages-1) * get_page_margin(pc) +
405         2*get_document_margin(pc);
406 
407     gint spacing;
408     GtkRequisition req;
409     gtk_widget_style_get (pc->scrollw, "scrollbar_spacing", &spacing, NULL);
410     gtk_widget_get_preferred_size (gtk_scrolled_window_get_hscrollbar(
411                 GTK_SCROLLED_WINDOW(pc->scrollw)), &req, NULL);
412 
413     gint vscrollbar_width = spacing + req.width;
414     gint hscrollbar_height = spacing + req.height;
415 
416     viewport_window = gtk_viewport_get_view_window (pc->viewport);
417     view_width_without_bar = gdk_window_get_width (viewport_window);
418     view_height_without_bar = gdk_window_get_height (viewport_window);
419 
420     if (gtk_widget_get_visible (gtk_scrolled_window_get_vscrollbar(
421                     GTK_SCROLLED_WINDOW(pc->scrollw)))) {
422         view_width_without_bar += vscrollbar_width;
423     }
424 
425     if (gtk_widget_get_visible(gtk_scrolled_window_get_hscrollbar(
426                     GTK_SCROLLED_WINDOW(pc->scrollw)))) {
427         view_height_without_bar += hscrollbar_height;
428     }
429     gint view_width_with_bar = view_width_without_bar - vscrollbar_width;
430 
431     gdouble scale_height_without_bar = (view_height_without_bar -
432             height_non_scaling) / height_scaling;
433     gdouble scale_full_height_without_bar = (view_height_without_bar -
434             full_height_non_scaling) / full_height_scaling;
435     gdouble scale_width_without_bar = (view_width_without_bar -
436             width_non_scaling)  / width_scaling;
437     gdouble scale_width_with_bar = (view_width_with_bar - width_non_scaling) /
438         width_scaling;
439     gdouble scale_both = MIN(scale_width_without_bar, scale_height_without_bar);
440     gdouble scale_both_full = MIN(scale_width_without_bar,
441             scale_full_height_without_bar);
442 
443     // When the preview window size is shrunk, in FIT_WIDTH there is a point
444     // right after the scrollbar has disappeared, where the document should
445     // must not be shrunk, because the height just fits. We catch this
446     // case here.
447 
448     gdouble scale_width = MAX(scale_width_with_bar, scale_both_full);
449 
450     // Now for the scale_both....
451     // Check if we need a bar:
452     if (scale_full_height_without_bar < scale_both) {
453         // We need a vsbar
454         scale_both = MAX(scale_both_full, MIN(scale_width_with_bar,
455                     scale_height_without_bar));
456     } else {
457         // We do not need a vsbar, everything is fine...
458     }
459 
460     gdouble scale = pc->scale;
461 
462     if (g_active_tab->fit_mode == FIT_WIDTH) {
463         scale = scale_width;
464     }
465     else if (g_active_tab->fit_mode == FIT_BOTH) {
466         scale = scale_both;
467     }
468 
469     if (scale == pc->scale) {
470         return;
471     }
472 
473     slog(L_DEBUG, "Document size wrong for fitting, changing scale from %f "
474             "to %f.\n", pc->scale, scale);
475 
476     // We do not really know where to center the scroll that might appear,
477     // passing the center of the window causes the toolbar to not be darn
478     // (don't ask me why).
479     // Passing NAN as position to center the scrolling on, causes no
480     // scrolling to happen (this is checked in previewgui_goto_xy)
481     // So this is basically a bugfix - but I could not see any unwanted side
482     // effects up till now...
483     previewgui_set_scale(pc, scale,
484         NAN,
485         NAN);
486 }
487 
is_continuous(GuPreviewGui * pc)488 inline static gboolean is_continuous(GuPreviewGui* pc) {
489 
490     if (pc->pageLayout == POPPLER_PAGE_LAYOUT_ONE_COLUMN) {
491         return TRUE;
492     } else {
493         return FALSE;
494     }
495 }
496 
page_offset_x(GuPreviewGui * pc,gint page,gdouble x)497 static gint page_offset_x (GuPreviewGui* pc, gint page, gdouble x) {
498     if (page < 0 || page >= pc->n_pages) {
499         return 0;
500     }
501 
502     return x + (pc->width_scaled - get_page_width(pc, page)*pc->scale) / 2;
503 }
504 
page_offset_y(GuPreviewGui * pc,gint page,gdouble y)505 static gint page_offset_y (GuPreviewGui* pc, gint page, gdouble y) {
506     if (page < 0 || page >= pc->n_pages) {
507         return 0;
508     }
509 
510     return y;
511 }
512 
previewgui_start_errormode(GuPreviewGui * pc,const gchar * msg)513 void previewgui_start_errormode (GuPreviewGui *pc, const gchar *msg) {
514 
515     if (pc->errormode) {
516         infoscreengui_set_message (gui->infoscreengui, msg);
517         return;
518     }
519 
520     previewgui_save_position (pc);
521 
522     infoscreengui_enable (gui->infoscreengui, msg);
523     pc->errormode = TRUE;
524 }
525 
previewgui_stop_errormode(GuPreviewGui * pc)526 void previewgui_stop_errormode (GuPreviewGui *pc) {
527 
528     if (!pc->errormode) return;
529 
530     previewgui_restore_position (pc);
531 
532     infoscreengui_disable (gui->infoscreengui);
533     pc->errormode = FALSE;
534 }
535 
on_document_compiled(gpointer data)536 gboolean on_document_compiled (gpointer data) {
537     GuPreviewGui* pc = gui->previewgui;
538     GuEditor* editor = GU_EDITOR(data);
539     GuLatex* latex = gummi_get_latex();
540 
541     // Make sure the editor still exists after compile
542     if (editor == gummi_get_active_editor()) {
543         editor_apply_errortags (editor, latex->errorlines);
544         gui_buildlog_set_text (latex->compilelog);
545 
546         if (latex->errorlines[0]) {
547             previewgui_start_errormode (pc, "compile_error");
548         } else {
549             if (!pc->uri) {
550 
551                 gchar* uri = g_filename_to_uri (editor->pdffile, NULL, NULL);
552 
553                 previewgui_set_pdffile (pc, uri);
554                 g_free(uri);
555             } else {
556                 previewgui_refresh (gui->previewgui,
557                         editor->sync_to_last_edit ?
558                         &(editor->last_edit) : NULL, editor->workfile);
559             }
560             if (pc->errormode) previewgui_stop_errormode (pc);
561         }
562     }
563     return FALSE;
564 }
565 
on_document_error(gpointer data)566 gboolean on_document_error (gpointer data) {
567     previewgui_start_errormode (gui->previewgui, (const gchar*) data);
568     return FALSE;
569 }
570 
previewgui_set_current_page(GuPreviewGui * pc,gint page)571 static void previewgui_set_current_page (GuPreviewGui* pc, gint page) {
572 
573     page = MAX(0, page);
574     page = MIN(page, pc->n_pages-1);
575 
576     // Always run the code below, in case the document has changed
577     //if (pc->current_page == page) {
578     //    return;
579     //}
580     //L_F_DEBUG;
581 
582     pc->current_page = page;
583 
584     update_page_input(pc);
585 
586 }
587 
update_page_input(GuPreviewGui * pc)588 static void update_page_input(GuPreviewGui* pc) {
589 
590     if (!gtk_widget_has_focus(pc->page_input)) {
591         gchar* num = g_strdup_printf ("%d", pc->current_page+1);
592         g_signal_handler_block(pc->page_input, pc->page_input_changed_handler);
593         gtk_entry_set_text (GTK_ENTRY(pc->page_input), num);
594         g_signal_handler_unblock(pc->page_input,pc->page_input_changed_handler);
595         g_free (num);
596     }
597 
598     update_prev_next_page(pc);
599 
600 }
601 
update_page_positions(GuPreviewGui * pc)602 static void update_page_positions(GuPreviewGui* pc) {
603     //L_F_DEBUG;
604 
605     LayeredRectangle fov = get_fov(pc);
606     int i;
607 
608     if (is_continuous(pc)) {
609         gint y = get_document_margin(pc);
610 
611         for (i=0; i<pc->n_pages; i++) {
612             page_inner(pc, i).y = y;
613             page_inner(pc, i).width = get_page_width(pc, i)*pc->scale;
614             page_inner(pc, i).x = MAX((fov.width - page_inner(pc, i).width)/2,
615                                        get_document_margin(pc));
616             page_inner(pc, i).height = get_page_height(pc, i)*pc->scale;
617             page_inner(pc, i).layer = 0;
618 
619             y += page_inner(pc, i).height + get_page_margin(pc);
620         }
621 
622         y -= get_page_margin(pc);
623         y += get_document_margin(pc);
624 
625         if (y < fov.height) {
626             gint diff = (fov.height - y) / 2;
627             for (i=0; i<pc->n_pages; i++) {
628                 page_inner(pc, i).y += diff;
629             }
630         }
631     } else {
632 
633         for (i=0; i<pc->n_pages; i++) {
634             page_inner(pc, i).height = get_page_height(pc, i)*pc->scale;
635             page_inner(pc, i).width = get_page_width(pc, i)*pc->scale;
636             page_inner(pc, i).y = MAX((fov.height - page_inner(pc, i).height)/2,
637                                        get_document_margin(pc));
638             page_inner(pc, i).x = MAX((fov.width - page_inner(pc, i).width)/2,
639                                        get_document_margin(pc));
640             page_inner(pc, i).layer = i;
641         }
642 
643     }
644 
645     for (i=0; i<pc->n_pages; i++) {
646         page_outer(pc, i).x = page_inner(pc, i).x - 1;
647         page_outer(pc, i).y = page_inner(pc, i).y - 1;
648         page_outer(pc, i).width = page_inner(pc, i).width + PAGE_SHADOW_WIDTH;
649         page_outer(pc, i).height = page_inner(pc, i).height + PAGE_SHADOW_WIDTH;
650         page_outer(pc, i).layer = page_inner(pc, i).layer;
651     }
652 }
653 
on_page_input_lost_focus(GtkWidget * widget,GdkEvent * event,gpointer user_data)654 static gboolean on_page_input_lost_focus(GtkWidget *widget, GdkEvent  *event,
655                                          gpointer   user_data) {
656     update_page_input(user_data);
657     return FALSE;
658 }
659 
update_prev_next_page(GuPreviewGui * pc)660 static void update_prev_next_page(GuPreviewGui* pc) {
661 
662     pc->next_page = pc->current_page + 1;
663     if (pc->next_page >= pc->n_pages) {
664         pc->next_page = -1;
665     }
666     pc->prev_page = pc->current_page - 1;
667     if (pc->prev_page < 0) {
668         pc->prev_page = -1;
669     }
670 
671     gtk_widget_set_sensitive(pc->page_prev, (pc->prev_page != -1));
672     gtk_widget_set_sensitive(pc->page_next, (pc->next_page != -1));
673 }
674 
update_current_page(GuPreviewGui * pc)675 static void update_current_page(GuPreviewGui* pc) {
676 
677     // Only update current page when in continuous layout...
678     if (!is_continuous(pc)) {
679         return;
680     }
681     //L_F_DEBUG;
682 
683     gdouble offset_y = MAX(get_document_margin(pc),
684             (gtk_adjustment_get_page_size(pc->vadj) - pc->height_scaled)/2 );
685 
686     // TODO: This can be simplified...
687 
688     // The page margins are just for safety...
689     gdouble view_start_y = gtk_adjustment_get_value(pc->vadj) -
690         get_page_margin(pc);
691     gdouble view_end_y   = view_start_y + gtk_adjustment_get_page_size(pc->vadj)
692         + 2*get_page_margin(pc);
693 
694     gint page;
695     for (page=0; page < pc->n_pages; page++) {
696         offset_y += get_page_height(pc, page)*pc->scale + get_page_margin(pc);
697         if (offset_y >= view_start_y) {
698             break;
699         }
700     }
701 
702     // If the first page that is painted covers at least half the screen,
703     // it is the current one, otherwise it is the one after that.
704     if (offset_y <= (view_start_y+view_end_y)/2)  {
705         page += 1;
706     }
707 
708     previewgui_set_current_page(pc, page);
709 
710 }
711 
get_page_height(GuPreviewGui * pc,int page)712 inline static gdouble get_page_height(GuPreviewGui* pc, int page) {
713     if (page < 0 || page >= pc->n_pages) {
714         return -1;
715     }
716     return (pc->pages + page)->height;
717 }
718 
get_page_width(GuPreviewGui * pc,int page)719 inline static gdouble get_page_width(GuPreviewGui* pc, int page) {
720     if (page < 0 || page >= pc->n_pages) {
721         return -1;
722     }
723     return (pc->pages + page)->width;
724 }
725 
previewgui_invalidate_renderings(GuPreviewGui * pc)726 static void previewgui_invalidate_renderings(GuPreviewGui* pc) {
727     //L_F_DEBUG;
728 
729     int i;
730     for (i = 0; i < pc->n_pages; i++) {
731         remove_page_rendering(pc, i);
732     }
733 
734     if (pc->cache_size != 0) {
735         slog(L_ERROR, "Cleared all page renderings, but cache not empty. "
736                 "Cache size is %iB.\n", pc->cache_size);
737     }
738 
739 }
740 
remove_page_rendering(GuPreviewGui * pc,gint page)741 static gboolean remove_page_rendering(GuPreviewGui* pc, gint page) {
742     if ((pc->pages + page)->rendering == NULL) {
743         return FALSE;
744     }
745     //L_F_DEBUG;
746 
747     cairo_surface_destroy((pc->pages + page)->rendering);
748     (pc->pages + page)->rendering = NULL;
749     pc->cache_size -= page_inner(pc, page).width *
750             page_inner(pc, page).height * BYTES_PER_PIXEL;
751 
752     return TRUE;
753 }
754 
update_drawarea_size(GuPreviewGui * pc)755 static void update_drawarea_size(GuPreviewGui *pc) {
756     //L_F_DEBUG;
757 
758     gint width = 1;
759     gint height = 1;
760 
761     // If the document should be fit, we set the requested size to 1 so
762     // scrollbars will not appear.
763     switch (g_active_tab->fit_mode) {
764         case FIT_NUMERIC:
765             width = pc->width_scaled + 2*get_document_margin(pc);
766             height = pc->height_scaled + 2*get_document_margin(pc);
767             break;
768         case FIT_WIDTH:
769             height = pc->height_scaled + 2*get_document_margin(pc);
770             break;
771         case FIT_BOTH:
772             if (is_continuous(pc)) {
773                 height = pc->height_scaled + 2*get_document_margin(pc);
774             }
775             break;
776     }
777 
778     gtk_widget_set_size_request (pc->drawarea, width, height);
779 
780     // The upper values probably get updated through signals, but in some cases
781     // this is too slow, so we do it here manually...
782 
783     // Minimize the number of calls to on_adjustment_changed
784     block_handlers_current_page(pc);
785 
786     gtk_adjustment_set_upper(pc->hadj,
787         (width==1) ? gtk_adjustment_get_page_size(pc->hadj) : width);
788     gtk_adjustment_set_upper(pc->vadj,
789         (height==1) ? gtk_adjustment_get_page_size(pc->vadj) : height);
790 
791     unblock_handlers_current_page(pc);
792 }
793 
update_page_sizes(GuPreviewGui * pc)794 static void update_page_sizes(GuPreviewGui* pc) {
795 
796     // recalculate document properties
797 
798     int i;
799     // calculate document height and width
800         pc->height_pages = 0;
801         for (i=0; i < pc->n_pages; i++) {
802             pc->height_pages += get_page_height(pc, i);
803         }
804 
805         pc->width_pages = 0;
806         for (i=0; i < pc->n_pages; i++) {
807             pc->width_pages = MAX(pc->width_pages, get_page_width(pc, i));
808         }
809 
810         pc->width_no_scale = pc->width_pages;
811 
812     pc->max_page_height = 0;
813     for (i=0; i < pc->n_pages; i++) {
814         pc->max_page_height = MAX(pc->max_page_height, get_page_height(pc, i));
815     }
816 
817     update_scaled_size(pc);
818     update_drawarea_size(pc);
819 
820     update_fit_scale(pc);
821 }
822 
previewgui_set_page_layout(GuPreviewGui * pc,PopplerPageLayout pageLayout)823 void previewgui_set_page_layout(GuPreviewGui* pc, PopplerPageLayout pageLayout) {
824     //L_F_DEBUG;
825 
826     if (pageLayout == POPPLER_PAGE_LAYOUT_UNSET) {
827         return;
828     }
829 
830     pc->pageLayout = pageLayout;
831 
832     update_page_sizes(pc);
833     previewgui_goto_page(pc, pc->current_page);
834 }
835 
set_fit_mode(GuPreviewGui * pc,enum GuPreviewFitModes fit_mode)836 static void set_fit_mode (GuPreviewGui* pc, enum GuPreviewFitModes fit_mode) {
837     //L_F_DEBUG;
838     update_fit_scale (pc);
839     update_page_positions (pc);
840 }
841 
update_scaled_size(GuPreviewGui * pc)842 static void update_scaled_size(GuPreviewGui* pc) {
843     //L_F_DEBUG;
844 
845     if (is_continuous(pc)) {
846         pc->height_scaled = pc->height_pages*pc->scale + (pc->n_pages-1) * get_page_margin(pc);
847     } else {
848         pc->height_scaled = get_page_height(pc, pc->current_page) * pc->scale;
849     }
850 
851 
852 
853     pc->width_scaled = pc->width_pages*pc->scale;
854 }
855 
previewgui_set_scale(GuPreviewGui * pc,gdouble scale,gdouble x,gdouble y)856 static void previewgui_set_scale(GuPreviewGui* pc, gdouble scale, gdouble x,
857     gdouble y) {
858 
859     if (pc->scale == scale) {
860         return;
861     }
862     //L_F_DEBUG;
863 
864     gdouble old_x = (gtk_adjustment_get_value(pc->hadj) + x) /
865             (pc->width_scaled + 2*get_document_margin(pc));
866     gdouble old_y = (gtk_adjustment_get_value(pc->vadj) + y) /
867             (pc->height_scaled + 2*get_document_margin(pc));
868 
869     // We have to do this before changing the scale, as otherwise the cache
870     // size would be calcualted wrong!
871     previewgui_invalidate_renderings(pc);
872 
873     pc->scale = scale;
874 
875     update_scaled_size(pc);
876     update_page_positions(pc);
877 
878     // TODO: Blocking the expose event is probably not the best way.
879     // It would be great if we could change all 3 properties (hadj, vadj & scale)
880     // at the same time.
881     // Probably blocking the expose handler causes the gray background of the
882     // window to be drawn - but at least we do not scroll to a different page
883     // anymore...
884     // Without blocking the handler, after changing the first property, e.g.
885     // vadj, a signal is emitted that causes a redraw but still contains the
886     // the not-updated hadj & scale values.
887     g_signal_handler_block (pc->drawarea, pc->on_draw_handler);
888 
889     update_drawarea_size (pc);
890 
891     if (x >= 0 && y>= 0) {
892         gdouble new_x = old_x * (pc->width_scaled + 2 * get_document_margin(pc)) -x;
893         gdouble new_y = old_y * (pc->height_scaled + 2 * get_document_margin(pc)) -y;
894 
895         previewgui_goto_xy (pc, new_x, new_y);
896     }
897     g_signal_handler_unblock (pc->drawarea, pc->on_draw_handler);
898 
899     gtk_widget_queue_draw (pc->drawarea);
900 }
901 
load_document(GuPreviewGui * pc,gboolean update)902 static void load_document(GuPreviewGui* pc, gboolean update) {
903     //L_F_DEBUG;
904 
905     previewgui_invalidate_renderings(pc);
906     g_free(pc->pages);
907 
908     pc->n_pages = poppler_document_get_n_pages (pc->doc);
909     gtk_label_set_text (GTK_LABEL (pc->page_label),
910             g_strdup_printf (_("of %d"), pc->n_pages));
911 
912     pc->pages = g_new0(GuPreviewPage, pc->n_pages);
913 
914     int i;
915     for (i=0; i < pc->n_pages; i++) {
916         PopplerPage *poppler = poppler_document_get_page(pc->doc, i);
917 
918         GuPreviewPage *page = pc->pages + i;
919         poppler_page_get_size(poppler, &(page->width), &(page->height));
920         g_object_unref(poppler);
921         poppler = NULL;
922     }
923 
924     update_page_sizes(pc);
925     update_prev_next_page(pc);
926 }
927 
previewgui_set_pdffile(GuPreviewGui * pc,const gchar * uri)928 void previewgui_set_pdffile (GuPreviewGui* pc, const gchar *uri) {
929     //L_F_DEBUG;
930     GError *error = NULL;
931 
932     previewgui_cleanup_fds (pc);
933 
934     pc->uri = g_strdup(uri);
935     pc->doc = poppler_document_new_from_file (pc->uri, NULL, &error);
936 
937     if (pc->doc == NULL) {
938         statusbar_set_message(error->message);
939         return;
940     }
941 
942     load_document(pc, FALSE);
943 
944     // This is mainly for debugging - to make sure the boxes in the preview disappear.
945     synctex_clear_sync_nodes(pc);
946 
947     // Restore scrollbar positions:
948     previewgui_restore_position (pc);
949 
950     // Restore scale and fit mode
951     if (!g_active_tab->fit_mode) {
952         const gchar* conf_zoom = config_get_string ("Preview", "zoom_mode");
953         gint new_fit, new_zoom;
954 
955         // TODO: build a dict like structure combining zoom fit strs with
956         // id (combo) so we don't have to do this verbose stuff all over the place
957         if (STR_EQU (conf_zoom, "Best Fit")) new_fit = 0, new_zoom = 0;
958         else
959         if (STR_EQU (conf_zoom, "Fit Page Width")) new_fit = 1, new_zoom = 1;
960         else {
961             new_fit = 2;
962             if (STR_EQU (conf_zoom, "50%")) new_zoom = 2;
963             else if (STR_EQU (conf_zoom, "70%")) new_zoom = 3;
964             else if (STR_EQU (conf_zoom, "85%")) new_zoom = 4;
965             else if (STR_EQU (conf_zoom, "100%")) new_zoom = 5;
966             else if (STR_EQU (conf_zoom, "125%")) new_zoom = 6;
967             else if (STR_EQU (conf_zoom, "150%")) new_zoom = 7;
968             else if (STR_EQU (conf_zoom, "200%")) new_zoom = 8;
969             else if (STR_EQU (conf_zoom, "300%")) new_zoom = 9;
970             else if (STR_EQU (conf_zoom, "400%")) new_zoom = 10;
971             else slog (L_ERROR, "should not happen\n");
972         }
973         g_active_tab->fit_mode = new_fit;
974         g_active_tab->zoom_mode = new_zoom;
975     }
976 
977     g_signal_handler_block(pc->combo_sizes, pc->combo_sizes_changed_handler);
978 
979     switch (g_active_tab->fit_mode) {
980         case FIT_BOTH:
981             set_fit_mode (pc, FIT_BOTH);
982             gtk_combo_box_set_active (pc->combo_sizes, ZOOM_FIT_BOTH);
983             break;
984         case FIT_WIDTH:
985             set_fit_mode (pc, FIT_WIDTH);
986             gtk_combo_box_set_active (pc->combo_sizes, ZOOM_FIT_WIDTH);
987             break;
988         case FIT_NUMERIC: // should compile on both gcc and clang
989             set_fit_mode (pc, FIT_NUMERIC);
990             previewgui_set_scale (pc, list_sizes[g_active_tab->zoom_mode], NAN, NAN);
991             // We pass NAN to avoid scrolling to happen. This is checked
992             // in previewgui_goto_xy() and might also have caused bug #252
993             gtk_combo_box_set_active (pc->combo_sizes, g_active_tab->zoom_mode);
994             break;
995     }
996 
997     g_signal_handler_unblock(pc->combo_sizes, pc->combo_sizes_changed_handler);
998 
999     gtk_widget_queue_draw (pc->drawarea);
1000 
1001     previewgui_goto_page (pc, 0);
1002 }
1003 
previewgui_refresh(GuPreviewGui * pc,GtkTextIter * sync_to,gchar * tex_file)1004 void previewgui_refresh (GuPreviewGui* pc, GtkTextIter *sync_to, gchar* tex_file) {
1005     //L_F_DEBUG;
1006     // We lock the mutex to prevent previewing incomplete PDF file, i.e
1007     // compiling. Also prevent PDF from changing (compiling) when previewing */
1008     if (!g_mutex_trylock (&gummi->motion->compile_mutex)) return;
1009 
1010     // This line is very important, if no pdf exist, preview will fail */
1011     if (!pc->uri || !utils_uri_path_exists (pc->uri)) goto unlock;
1012 
1013     // If no document had been loaded successfully before, force call of set_pdffile
1014     if (pc->doc == NULL) {
1015         previewgui_set_pdffile (pc, pc->uri);
1016         goto unlock;
1017     }
1018 
1019     previewgui_cleanup_fds (pc);
1020 
1021     pc->doc = poppler_document_new_from_file (pc->uri, NULL, NULL);
1022 
1023     /* release mutex and return when poppler doc is damaged or missing */
1024     if (pc->doc == NULL) goto unlock;
1025 
1026     load_document(pc, TRUE);
1027     update_page_positions(pc);
1028 
1029     if (config_get_boolean ("Compile", "synctex") &&
1030         config_get_boolean ("Preview", "autosync") &&
1031         synctex_run_parser(pc, sync_to, tex_file)) {
1032 
1033         SyncNode *node;
1034         if ((node = synctex_one_node_found(pc)) == NULL) {
1035             // See if the nodes are so close they all fit in the window
1036             // in that case we just merge them
1037             synctex_merge_nodes(pc);
1038         }
1039 
1040 #if HAVE_POPPLER_PAGE_GET_SELECTED_TEXT
1041         if ((node = synctex_one_node_found(pc)) == NULL) {
1042             // Search for words in the pdf
1043             synctex_filter_results(pc, sync_to);
1044         }
1045         // Here we could try merging again - but only with nodes which
1046         // contained the searched text
1047 #endif
1048 
1049         // If we have only one node left/selected, scroll ot it.
1050         if ((node = synctex_one_node_found(pc)) != NULL) {
1051            synctex_scroll_to_node(pc, node);
1052         }
1053 
1054     } else {
1055 
1056         // This is mainly for debugging - to make sure the boxes in the preview disappear.
1057         synctex_clear_sync_nodes(pc);
1058 
1059         if (pc->current_page >= pc->n_pages) {
1060             previewgui_goto_page (pc, pc->n_pages-1);
1061         }
1062 
1063     }
1064 
1065     gtk_widget_queue_draw (pc->drawarea);
1066 
1067 unlock:
1068     g_mutex_unlock (&gummi->motion->compile_mutex);
1069 }
1070 
synctex_run_parser(GuPreviewGui * pc,GtkTextIter * sync_to,gchar * tex_file)1071 static gboolean synctex_run_parser(GuPreviewGui* pc, GtkTextIter *sync_to, gchar* tex_file) {
1072 
1073     if (sync_to == NULL || tex_file == NULL) {
1074         return FALSE;
1075     }
1076 
1077     // sync to position...
1078     gint line = gtk_text_iter_get_line(sync_to)+1; // SyncTeX lines are 1 based, TextBuffer lines are 0 based
1079     gint column = gtk_text_iter_get_line_offset(sync_to);
1080     slog(L_DEBUG, "Syncing to %s, line %i, column %i\n", tex_file, line, column);
1081 
1082     synctex_scanner_p sync_scanner = synctex_scanner_new_with_output_file(pc->uri, C_TMPDIR, 1);
1083 
1084     synctex_clear_sync_nodes(pc);
1085 
1086     if (synctex_display_query (sync_scanner, tex_file, line, column, -1) > 0) {
1087         synctex_node_p node;
1088 
1089         // SyncTeX can return several nodes. It seems best to use the last one
1090         // as this one rarely is below (usually slightly above) the edited line
1091         while ((node = synctex_scanner_next_result(sync_scanner))) {
1092 
1093             SyncNode *sn = g_new0(SyncNode, 1);
1094 
1095             sn->page = synctex_node_page(node) - 1; // syncTeX counts from 1, but poppler from 0
1096             sn->x = synctex_node_box_visible_h(node);
1097             sn->y = synctex_node_box_visible_v(node);
1098             sn->width = synctex_node_box_visible_width(node);
1099             sn->height = synctex_node_box_visible_height(node);
1100             sn->y -= sn->height;    // We want y to be the upper value
1101 
1102             pc->sync_nodes = g_slist_append(pc->sync_nodes, sn);
1103 
1104         }
1105     }
1106 
1107     synctex_scanner_free(sync_scanner);
1108     return TRUE;
1109 }
1110 
1111 #if HAVE_POPPLER_PAGE_GET_SELECTED_TEXT
synctex_filter_results(GuPreviewGui * pc,GtkTextIter * sync_to)1112 static void synctex_filter_results(GuPreviewGui* pc, GtkTextIter *sync_to) {
1113 
1114     // First look if we even have to filter...
1115     if (g_slist_length(pc->sync_nodes) == 0) {
1116         return;
1117     }
1118 
1119     GtkTextIter wordStart = *sync_to;
1120     int i;
1121     for (i=0; i<5; i++) {
1122 
1123         gtk_text_iter_backward_word_start(&wordStart);
1124 
1125         GtkTextIter wordEnd = wordStart;
1126         gtk_text_iter_forward_word_end(&wordEnd);
1127 
1128 
1129         if (gtk_text_iter_compare(&wordStart, &wordEnd) >= 0) {
1130             break;
1131         }
1132 
1133         gchar *word = g_strconcat("\\b", gtk_text_iter_get_text(&wordStart, &wordEnd), "\\b", NULL);
1134 
1135         //gchar *pattern g_strconcat
1136 
1137         slog(L_DEBUG, "Searching for word \"%s\"\n", word);
1138 
1139         GSList *nl = pc->sync_nodes;
1140 
1141         while (nl != NULL) {
1142 
1143             SyncNode *sn =  nl->data;
1144 
1145             PopplerRectangle selection;
1146             selection.x1 = sn->x;               // lower left corner
1147             selection.y1 = sn->y + sn->height;  // lower left corner
1148             selection.x2 = sn->x + sn->width;   // upper right corner
1149             selection.y2 = sn->y;               // upper right corner
1150 
1151             PopplerPage* ppage = poppler_document_get_page(pc->doc, sn->page);
1152             gchar *node_text = poppler_page_get_selected_text(ppage,
1153                         POPPLER_SELECTION_WORD, &selection);
1154 
1155             //slog(L_DEBUG, "Node contains text\"%s\"\n", node_text);
1156 
1157             if (g_regex_match_simple(word, node_text, 0, 0)) {
1158                 sn->score += 1;
1159             }
1160 
1161             g_free(node_text);
1162             g_object_unref(ppage);
1163 
1164             nl = nl->next;
1165         }
1166 
1167         g_free(word);
1168     }
1169 }
1170 #endif
1171 
1172 
synctex_one_node_found(GuPreviewGui * pc)1173 static SyncNode* synctex_one_node_found(GuPreviewGui* pc) {
1174 
1175     if (g_slist_length(pc->sync_nodes) == 1) {
1176         SyncNode *node = g_slist_nth_data(pc->sync_nodes, 0);
1177         node->score = -1;
1178         return node;
1179     }
1180 
1181     // See if we have found a single match
1182     GSList *nl = pc->sync_nodes;
1183 
1184     gint score_max_id = -1;
1185     gint score_other = 0;
1186     gint n = 0;
1187     while (nl != NULL) {
1188         SyncNode *sn =  nl->data;
1189 
1190         if (sn->score > score_other) {
1191             score_other = sn->score;
1192             score_max_id = n;
1193         } else if (sn->score == score_other) {
1194             // If we find a second node with the same score, we forget about
1195             // the first one..
1196             score_max_id = -1;
1197         }
1198 
1199         nl = nl->next;
1200         n++;
1201     }
1202 
1203     if (score_max_id >= 0) {
1204         SyncNode *node = g_slist_nth_data(pc->sync_nodes, score_max_id);
1205         node->score = -1;
1206         return node;
1207     }
1208 
1209     return NULL;
1210 }
1211 
synctex_merge_nodes(GuPreviewGui * pc)1212 static void synctex_merge_nodes(GuPreviewGui* pc) {
1213 
1214     gint x1 = INT_MAX;   // upper left corner
1215     gint y1 = INT_MAX;   // upper left corner
1216     gint x2 = -1;   // lower right corner
1217     gint y2 = -1;   // lower right corner
1218 
1219     gint page = -1;
1220 
1221     GSList *nl = pc->sync_nodes;
1222 
1223     while (nl != NULL) {
1224 
1225         SyncNode *sn =  nl->data;
1226 
1227 
1228         slog(L_DEBUG, "Nodes (%i, %i), w=%i, h=%i, P=%i\n", sn->x, sn->y, sn->width, sn->height, sn->page);
1229 
1230         if (page == -1) {
1231             page = sn->page;
1232         } else if (page != sn->page) {
1233             return; // The Nodes are on different pages. We don't hande this for now...
1234         }
1235 
1236         x1 = MIN(x1, sn->x);
1237         y1 = MIN(y1, sn->y);
1238         x2 = MAX(x2, sn->x + sn->width);
1239         y2 = MAX(y2, sn->y + sn->height);
1240 
1241         nl = nl->next;
1242     }
1243 
1244     if ((y2-y1)*pc->scale < gtk_adjustment_get_page_size(pc->vadj)/3) {
1245         SyncNode *sn = g_new0(SyncNode, 1);
1246         sn->y = y1;
1247         sn->x = x1;
1248 
1249         sn->width = x2 - x1;
1250         sn->height = y2 - y1;
1251         sn->page = page;
1252 
1253         slog(L_DEBUG, "Merged nodes to (%i, %i), w=%i, h=%i, p=%i\n", sn->x, sn->y, sn->width, sn->height, sn->page);
1254 
1255         synctex_clear_sync_nodes(pc);
1256         pc->sync_nodes = g_slist_append(pc->sync_nodes, sn);
1257     }
1258 
1259 }
1260 
synctex_clear_sync_nodes(GuPreviewGui * pc)1261 static void synctex_clear_sync_nodes(GuPreviewGui* pc) {
1262     GSList *el = pc->sync_nodes;
1263     while (el != NULL) {
1264         SyncNode *node = el->data;
1265         g_free(node);
1266         node = NULL;
1267 
1268         el = el->next;
1269     }
1270 
1271     g_slist_free (pc->sync_nodes);
1272     pc->sync_nodes = NULL;
1273 }
1274 
synctex_scroll_to_node(GuPreviewGui * pc,SyncNode * node)1275 static void synctex_scroll_to_node (GuPreviewGui* pc, SyncNode* node) {
1276 
1277     gint adjpage_width = gtk_adjustment_get_page_size(pc->hadj);
1278     gint adjpage_height = gtk_adjustment_get_page_size(pc->vadj);
1279 
1280     gdouble node_x = MAX(get_document_margin(pc),
1281                           (adjpage_width - pc->width_scaled) / 2);
1282     gdouble node_y;
1283 
1284     if (is_continuous(pc)) {
1285         node_y = MAX(get_document_margin(pc),
1286                                (adjpage_height - pc->height_scaled) / 2);
1287 
1288         int i;
1289         for (i=0; i < node->page; i++) {
1290             node_y += get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1291         }
1292     } else {
1293         gdouble height = get_page_height(pc, pc->current_page) * pc->scale;
1294         node_y = MAX(get_document_margin(pc), (adjpage_height-height)/2);
1295     }
1296 
1297     node_y += node->y * pc->scale;
1298     node_x += node->x * pc->scale;
1299     gdouble node_height = node->height * pc->scale;
1300     gdouble node_width = node->width * pc->scale;
1301 
1302     gdouble view_x = gtk_adjustment_get_value(pc->hadj);
1303     gdouble view_width = adjpage_width;
1304     gdouble view_y = gtk_adjustment_get_value(pc->vadj);
1305     gdouble view_height = adjpage_height;
1306 
1307     slog(L_DEBUG, "node: (%f, %f), w=%f, h=%f\n", node_x, node_y, node_width,
1308             node_height);
1309     slog(L_DEBUG, "view: (%f, %f), w=%f, h=%f\n", view_x, view_y,
1310         view_width, view_height);
1311 
1312     gdouble to_y;
1313     gdouble to_x;
1314     // Positioning algorithm:
1315     // The x and y coordinates are treated separately.  For each,
1316     //  - If the node is already within the view, do not change the view.
1317     //  - Else, if the node can fit in the view, center it.
1318     //  - Else, align the view to the top/left of the view.
1319     // The functions used to change the view do bounds checking, so we
1320     // don't do that here.
1321 
1322     if (node_y > view_y && node_y + node_height < view_y + view_height) {
1323         to_y = view_y;
1324     } else if (node_height < view_height) {
1325         to_y = node_y + (node_height - view_height)/2;
1326     } else {
1327         to_y = node_y;
1328     }
1329 
1330     if (node_x > view_x && node_x + node_width < view_x + view_width) {
1331         to_x = view_x;
1332     } else if (node_width < view_width) {
1333         to_x = node_x + (node_width - view_width)/2;
1334     } else {
1335         to_x = node_x;
1336     }
1337 
1338     if (!is_continuous(pc) && pc->current_page != node->page) {
1339 
1340         previewgui_goto_page (pc, node->page);
1341         previewgui_goto_xy(pc, to_x, to_y);
1342 
1343     } else {
1344         if (config_value_as_str_equals ("Preview", "animated_scroll", "always") ||
1345             config_value_as_str_equals ("Preview", "animated_scroll", "autosync")) {
1346             previewgui_scroll_to_xy(pc, to_x, to_y);
1347         } else {
1348             previewgui_goto_xy(pc, to_x, to_y);
1349         }
1350     }
1351 
1352 }
1353 
previewgui_goto_page(GuPreviewGui * pc,int page)1354 void previewgui_goto_page (GuPreviewGui* pc, int page) {
1355     //L_F_DEBUG;
1356     page = MAX(page, 0);
1357     page = MIN(page, pc->n_pages-1);
1358 
1359     previewgui_set_current_page(pc, page);
1360 
1361     gint i;
1362     gdouble y = 0;
1363 
1364     if (!is_continuous(pc)) {
1365         update_scaled_size(pc);
1366         update_drawarea_size(pc);
1367     } else {
1368         for (i=0; i < page; i++) {
1369             y += get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1370         }
1371     }
1372 
1373     //previewgui_goto_xy(pc, page_offset_x(pc, page, 0),
1374     //                       page_offset_y(pc, page, y));
1375     // We do not want to scroll horizontally.
1376     previewgui_goto_xy(pc, gtk_adjustment_get_value(pc->hadj),
1377                            gtk_adjustment_get_value(pc->vadj));
1378 
1379     if (!is_continuous(pc)) {
1380         gtk_widget_queue_draw (pc->drawarea);
1381     }
1382 }
1383 
previewgui_scroll_to_page(GuPreviewGui * pc,int page)1384 void previewgui_scroll_to_page (GuPreviewGui* pc, int page) {
1385     //L_F_DEBUG;
1386 
1387     if (!is_continuous(pc)) {
1388         // We do not scroll in single page mode...
1389         previewgui_goto_page(pc, page);
1390         return;
1391     }
1392 
1393     page = MAX(page, 0);
1394     page = MIN(page, pc->n_pages-1);
1395 
1396     previewgui_set_current_page(pc, page);
1397 
1398     gint i;
1399     gdouble y = 0;
1400     for (i=0; i < page; i++) {
1401         y += get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1402     }
1403 
1404     //previewgui_scroll_to_xy(pc, page_offset_x(pc, page, 0),
1405     //                       page_offset_y(pc, page, y));
1406     // We do not want to scroll horizontally in single paged mode...
1407     previewgui_scroll_to_xy(pc, gtk_adjustment_get_value(pc->hadj),
1408                            page_offset_y(pc, page, y));
1409 }
1410 
previewgui_goto_xy(GuPreviewGui * pc,gdouble x,gdouble y)1411 void previewgui_goto_xy (GuPreviewGui* pc, gdouble x, gdouble y) {
1412 
1413     if (isnan(x) || isnan(y)) {
1414         return;
1415     }
1416 
1417     x = CLAMP(x, 0, gtk_adjustment_get_upper(pc->hadj) -
1418                     gtk_adjustment_get_page_size(pc->hadj));
1419     y = CLAMP(y, 0, gtk_adjustment_get_upper(pc->vadj) -
1420                     gtk_adjustment_get_page_size(pc->vadj));
1421 
1422     // Minimize the number of calls to on_adjustment_changed
1423     block_handlers_current_page(pc);
1424 
1425     gtk_adjustment_set_value(pc->hadj, x);
1426     gtk_adjustment_set_value(pc->vadj, y);
1427     previewgui_save_position (pc);
1428 
1429     unblock_handlers_current_page(pc);
1430 }
1431 
previewgui_scroll_to_xy(GuPreviewGui * pc,gdouble x,gdouble y)1432 void previewgui_scroll_to_xy (GuPreviewGui* pc, gdouble x, gdouble y) {
1433 
1434     if (isnan(x) || isnan(y)) {
1435         return;
1436     }
1437     //L_F_DEBUG;
1438 
1439     x = CLAMP(x, 0, gtk_adjustment_get_upper(pc->hadj) -
1440                gtk_adjustment_get_page_size(pc->hadj));
1441     y = CLAMP(y, 0, gtk_adjustment_get_upper(pc->vadj) -
1442                gtk_adjustment_get_page_size(pc->vadj));
1443 
1444     pc->ascroll_steps_left = ASCROLL_STEPS;
1445 
1446     pc->ascroll_end_x = x;
1447     pc->ascroll_end_y = y;
1448 
1449     pc->ascroll_dist_x = gtk_adjustment_get_value(pc->hadj) - x;
1450     pc->ascroll_dist_y = gtk_adjustment_get_value(pc->vadj) - y;
1451 
1452     g_timeout_add (1000./25., previewgui_animated_scroll_step, pc);
1453 
1454 }
1455 
previewgui_save_position(GuPreviewGui * pc)1456 void previewgui_save_position (GuPreviewGui* pc) {
1457     //L_F_DEBUG;
1458     if (g_active_tab != NULL) {
1459         g_active_tab->scroll_x = gtk_adjustment_get_value (pc->hadj);
1460         g_active_tab->scroll_y = gtk_adjustment_get_value (pc->vadj);
1461         block_handlers_current_page(pc);
1462         slog(L_DEBUG, "Preview scrollbar positions saved at x/y = %.2f/%.2f\n",
1463                        g_active_tab->scroll_x, g_active_tab->scroll_y);
1464     }
1465 }
1466 
previewgui_restore_position(GuPreviewGui * pc)1467 void previewgui_restore_position (GuPreviewGui* pc) {
1468     //L_F_DEBUG;
1469     // Restore scroll window position to value before error mode
1470     // TODO: might want to merge this with synctex funcs in future
1471     previewgui_goto_xy (pc, g_active_tab->scroll_x,
1472                             g_active_tab->scroll_y);
1473     slog(L_DEBUG, "Preview scrollbar positions restored at x/y = %.2f/%.2f\n",
1474                    g_active_tab->scroll_x, g_active_tab->scroll_y);
1475     unblock_handlers_current_page(pc);
1476 }
1477 
do_render(PopplerPage * ppage,gdouble scale,gint width,gint height)1478 static cairo_surface_t* do_render (PopplerPage* ppage, gdouble scale,
1479                                    gint width, gint height) {
1480 
1481     cairo_surface_t* r = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1482                                                 width*scale,
1483                                                 height*scale);
1484     cairo_t *c = cairo_create(r);
1485 
1486     cairo_scale (c, scale, scale);
1487     poppler_page_render(ppage, c);
1488 
1489     // TODO for what is this used?
1490     cairo_set_operator (c, CAIRO_OPERATOR_DEST_OVER);
1491     cairo_set_source_rgb (c, 1, 1, 1);
1492     cairo_paint (c);
1493     cairo_destroy (c);
1494 
1495     return r;
1496 }
1497 
get_page_rendering(GuPreviewGui * pc,int page)1498 static cairo_surface_t* get_page_rendering (GuPreviewGui* pc, int page) {
1499 
1500     GuPreviewPage *p = pc->pages + page;
1501 
1502     if (p->rendering == NULL) {
1503         PopplerPage* ppage = poppler_document_get_page(pc->doc, page);
1504         p->rendering = do_render(ppage, pc->scale, p->width, p->height);
1505         g_object_unref(ppage);
1506         pc->cache_size += page_inner(pc, page).width *
1507                 page_inner(pc, page).height * BYTES_PER_PIXEL;
1508 
1509         // Trigger the garbage collector to be run - it will exit if nothing is TBD.
1510         g_idle_add( (GSourceFunc) run_garbage_collector, pc);
1511     }
1512 
1513     return cairo_surface_reference(p->rendering);
1514 }
1515 
previewgui_reset(GuPreviewGui * pc)1516 void previewgui_reset (GuPreviewGui* pc) {
1517     //L_F_DEBUG;
1518     /* reset uri */
1519     g_free (pc->uri);
1520     pc->uri = NULL;
1521 
1522     gummi->latex->modified_since_compile = TRUE;
1523     previewgui_stop_preview (pc);
1524     motion_do_compile (gummi->motion);
1525 
1526     if (config_get_boolean ("Compile", "pause") == FALSE) {
1527         previewgui_start_preview (pc);
1528     }
1529 }
1530 
1531 
previewgui_cleanup_fds(GuPreviewGui * pc)1532 void previewgui_cleanup_fds (GuPreviewGui* pc) {
1533     //L_F_DEBUG;
1534 
1535     if (pc->doc) {
1536         g_object_unref (pc->doc);
1537         pc->doc = NULL;
1538     }
1539 }
1540 
previewgui_start_preview(GuPreviewGui * pc)1541 void previewgui_start_preview (GuPreviewGui* pc) {
1542     if (config_value_as_str_equals ("Compile", "scheme", "on_idle")) {
1543         pc->preview_on_idle = TRUE;
1544     } else {
1545         pc->update_timer = g_timeout_add_seconds (
1546                                 config_get_integer ("Compile", "timer"),
1547                                 motion_do_compile, gummi->motion);
1548     }
1549 }
1550 
previewgui_stop_preview(GuPreviewGui * pc)1551 void previewgui_stop_preview (GuPreviewGui* pc) {
1552     pc->preview_on_idle = FALSE;
1553     if (pc->update_timer != 0)
1554         g_source_remove (pc->update_timer);
1555     pc->update_timer = 0;
1556 }
1557 
1558 G_MODULE_EXPORT
on_page_input_changed(GtkEntry * entry,void * user)1559 void on_page_input_changed (GtkEntry* entry, void* user) {
1560     //L_F_DEBUG;
1561 
1562     gint newpage = atoi (gtk_entry_get_text (entry));
1563     newpage -= 1;
1564     newpage = MAX(newpage, 0);
1565     newpage = MIN(newpage, gui->previewgui->n_pages);
1566 
1567     if (config_value_as_str_equals ("Preview", "animated_scroll", "always")) {
1568         previewgui_scroll_to_page (gui->previewgui, newpage);
1569     } else {
1570         previewgui_goto_page (gui->previewgui, newpage);
1571     }
1572 
1573 }
1574 
1575 G_MODULE_EXPORT
on_next_page_clicked(GtkWidget * widget,void * user)1576 void on_next_page_clicked (GtkWidget* widget, void* user) {
1577     //L_F_DEBUG;
1578     GuPreviewGui *pc = gui->previewgui;
1579 
1580     if (config_value_as_str_equals ("Preview", "animated_scroll", "always")) {
1581         previewgui_scroll_to_page (pc, pc->next_page);
1582     } else {
1583         previewgui_goto_page (pc, pc->next_page);
1584     }
1585 }
1586 
1587 G_MODULE_EXPORT
on_prev_page_clicked(GtkWidget * widget,void * user)1588 void on_prev_page_clicked (GtkWidget* widget, void* user) {
1589     //L_F_DEBUG;
1590     GuPreviewGui *pc = gui->previewgui;
1591 
1592     if (config_value_as_str_equals ("Preview", "animated_scroll", "always")) {
1593         previewgui_scroll_to_page (pc, pc->prev_page);
1594     }
1595     else {
1596         previewgui_goto_page (pc, pc->prev_page);
1597     }
1598 }
1599 
1600 G_MODULE_EXPORT
on_preview_pause_toggled(GtkWidget * widget,void * user)1601 void on_preview_pause_toggled (GtkWidget *widget, void * user) {
1602     gboolean value =
1603         gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (widget));
1604 
1605     config_set_boolean ("Compile", "pause", value);
1606 
1607     if (value) // toggled --> PAUSE
1608         previewgui_stop_preview (gui->previewgui);
1609     else // untoggled --> RESUME
1610         previewgui_start_preview (gui->previewgui);
1611 }
1612 
1613 G_MODULE_EXPORT
on_combo_sizes_changed(GtkWidget * widget,void * user)1614 void on_combo_sizes_changed (GtkWidget* widget, void* user) {
1615     //L_F_DEBUG;
1616     gint new_zoom_mode = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
1617 
1618     switch (new_zoom_mode) {
1619         case FIT_BOTH:
1620             g_active_tab->fit_mode = FIT_BOTH;
1621             g_active_tab->zoom_mode = ZOOM_FIT_BOTH;
1622             set_fit_mode (gui->previewgui, FIT_BOTH);
1623             break;
1624         case FIT_WIDTH:
1625             g_active_tab->fit_mode = FIT_WIDTH;
1626             g_active_tab->zoom_mode = ZOOM_FIT_WIDTH;
1627             set_fit_mode (gui->previewgui, FIT_WIDTH);
1628             break;
1629         case 2 ... 10:
1630             g_active_tab->fit_mode = FIT_NUMERIC;
1631             g_active_tab->zoom_mode = new_zoom_mode;
1632             set_fit_mode (gui->previewgui, FIT_NUMERIC);
1633             previewgui_set_scale (gui->previewgui, list_sizes[new_zoom_mode],
1634                     gtk_adjustment_get_page_size (gui->previewgui->hadj) / 2,
1635                     gtk_adjustment_get_page_size (gui->previewgui->vadj) / 2);
1636             break;
1637         }
1638 }
1639 
paint_page(cairo_t * cr,GuPreviewGui * pc,gint page,gint x,gint y)1640 static void paint_page (cairo_t *cr, GuPreviewGui* pc, gint page, gint x, gint y) {
1641     if (page < 0 || page >= pc->n_pages) {
1642         return;
1643     }
1644 
1645     //slog (L_DEBUG, "printing page %i at (%i, %i)\n", page, x, y);
1646 
1647     gdouble page_width = get_page_width(pc, page) * pc->scale;
1648     gdouble page_height = get_page_height(pc, page) * pc->scale;
1649 
1650     // Paint shadow
1651     cairo_set_source_rgb (cr, 0.302, 0.302, 0.302);
1652     cairo_rectangle (cr, x + page_width , y + PAGE_SHADOW_OFFSET ,
1653                      PAGE_SHADOW_WIDTH, page_height);
1654     cairo_fill (cr);
1655     cairo_rectangle (cr, x + PAGE_SHADOW_OFFSET , y + page_height,
1656                          page_width - PAGE_SHADOW_OFFSET, PAGE_SHADOW_WIDTH);
1657     cairo_fill (cr);
1658 
1659     // Paint border around page
1660     cairo_set_line_width (cr, 0.5);
1661     cairo_set_source_rgb (cr, 0, 0, 0);
1662     cairo_rectangle (cr, x - 1, y - 1, page_width + 1, page_height + 1);
1663     cairo_stroke (cr);
1664 
1665     cairo_surface_t* rendering = get_page_rendering(pc, page);
1666 
1667     // Paint rendering
1668     cairo_set_source_surface (cr, rendering, x, y);
1669     cairo_paint (cr);
1670 
1671 
1672     GSList *nl = pc->sync_nodes;
1673     while (nl != NULL && in_debug_mode()) {
1674 
1675         SyncNode *sn =  nl->data;
1676 
1677         if (sn->page == page) {
1678             gint mark_x = sn->x * pc->scale;
1679             gint mark_y = sn->y * pc->scale;
1680             gint mark_width = sn->width * pc->scale;
1681             gint mark_height = sn->height * pc->scale;
1682 
1683             cairo_set_line_width (cr, 1);
1684             if (sn->score < 0) {
1685                 cairo_set_source_rgb (cr, 1, 0, 0); // Mark selected node red
1686             } else if (sn->score > 0) {
1687                 cairo_set_source_rgb (cr, 0, 1, 0); // Mark nodes with matches green
1688             } else {
1689                 cairo_set_source_rgb (cr, 0, 0, 1); // Mark other nodes blue
1690             }
1691             cairo_rectangle (cr, x+mark_x-1, y+mark_y-1, mark_width+2, mark_height+2);
1692             cairo_stroke (cr);
1693         }
1694 
1695         nl = nl->next;
1696     }
1697 
1698     cairo_surface_destroy(rendering);
1699 }
1700 
get_fov(GuPreviewGui * pc)1701 static inline LayeredRectangle get_fov(GuPreviewGui* pc) {
1702     //L_F_DEBUG;
1703 
1704     LayeredRectangle fov;
1705     fov.x = gtk_adjustment_get_value(pc->hadj);
1706     fov.y = gtk_adjustment_get_value(pc->vadj);
1707     fov.width = gtk_adjustment_get_page_size(pc->hadj); // TODO: Validate this is alsways correct
1708     fov.height = gtk_adjustment_get_page_size(pc->vadj);// TODO: Validate this is alsways correct
1709     if (is_continuous(pc)) {
1710         fov.layer = 0;
1711     } else {
1712         fov.layer = pc->current_page;
1713     }
1714 
1715     return fov;
1716 }
1717 
1718 /**
1719  *  Tests for the intersection of both rectangles src1 and src2.
1720  *  If dest is set and there is a intersection, it will be the intersecting,
1721  *  rectangle. If dest is set but src1 and src2 do not intersect, dest's width
1722  *  and height will be set to 0. All other values will be undefined. Dest may be
1723  *  the same as src1 or src2.
1724  *
1725  *  Set dest to NULL, if you are only interested in the boolean result.
1726  */
layered_rectangle_intersect(const LayeredRectangle * src1,const LayeredRectangle * src2,LayeredRectangle * dest)1727 static gboolean layered_rectangle_intersect (const LayeredRectangle *src1,
1728                                              const LayeredRectangle *src2,
1729                                              LayeredRectangle *dest) {
1730 
1731     if (src1 == NULL || src2 == NULL) {
1732         if (dest) {
1733             dest->width = 0;
1734             dest->height = 0;
1735         }
1736         return FALSE;
1737     }
1738 
1739     if (src1->layer == src2->layer) {
1740 
1741         gint dest_x = MAX (src1->x, src2->x);
1742         gint dest_y = MAX (src1->y, src2->y);
1743         gint dest_x2 = MIN (src1->x + src1->width, src2->x + src2->width);
1744         gint dest_y2 = MIN (src1->y + src1->height, src2->y + src2->height);
1745 
1746         if (dest_x2 > dest_x && dest_y2 > dest_y) {
1747             if (dest) {
1748                 dest->x = dest_x;
1749                 dest->y = dest_y;
1750                 dest->width = dest_x2 - dest_x;
1751                 dest->height = dest_y2 - dest_y;
1752                 dest->layer = src1->layer;
1753             }
1754             return TRUE;
1755         }
1756     }
1757 
1758     if (dest) {
1759         dest->width = 0;
1760         dest->height = 0;
1761     }
1762     return FALSE;
1763 }
1764 
run_garbage_collector(GuPreviewGui * pc)1765 gboolean run_garbage_collector (GuPreviewGui* pc) {
1766 
1767     gint max_cache_size = config_get_integer ("Preview", "cache_size") * 1024 * 1024;
1768 
1769     if (pc->cache_size < max_cache_size) {
1770         return FALSE;
1771     }
1772 
1773     LayeredRectangle fov = get_fov(pc);
1774 
1775     gint first = -1;
1776     gint last = -1;
1777 
1778     gint i;
1779     for (i=0; i < pc->n_pages; i++) {
1780         if (layered_rectangle_intersect(&fov, &(page_inner(pc, i)), NULL)) {
1781             if (first == -1) {
1782                 first = i;
1783             }
1784             last = i;
1785         }
1786     }
1787 
1788     if (first == -1) {
1789         slog (L_ERROR, "No pages are shown. Clearing whole cache.\n");
1790         previewgui_invalidate_renderings(pc);
1791     }
1792 
1793     gint n=0;
1794     gint dist = MAX(first, pc->n_pages - 1 - last);
1795     for (; dist > 0; dist--) {
1796         gint up = first - dist;
1797         if (up >= 0 && up < pc->n_pages) {
1798             if (!layered_rectangle_intersect(&fov, &(page_inner(pc, up)), NULL)) {
1799                 if (remove_page_rendering(pc, up)) {
1800                     n += 1;
1801                 }
1802             }
1803         }
1804         if (pc->cache_size < max_cache_size / 2) {
1805             break;
1806         }
1807 
1808         gint down = last + dist;
1809         if (down < pc->n_pages && down >= 0) {
1810             if (!layered_rectangle_intersect(&fov, &(page_inner(pc, down)), NULL)) {
1811                 if (remove_page_rendering(pc, down)) {
1812                     n += 1;
1813                 }
1814             }
1815         }
1816         if (pc->cache_size < max_cache_size / 2) {
1817             break;
1818         }
1819     }
1820 
1821     if (n == 0) {
1822         slog(L_DEBUG, "Could not delete any pages from cache. All pages are "
1823                 "currently visible.\n");
1824     } else {
1825         slog(L_DEBUG, "Deleted %i pages from cache.\n", n);
1826     }
1827 
1828     return FALSE;   // We only want this to run once - so always return false!
1829 }
1830 
1831 G_MODULE_EXPORT
on_draw(GtkWidget * w,cairo_t * cr,void * user)1832 gboolean on_draw (GtkWidget* w, cairo_t* cr, void* user) {
1833     GuPreviewGui* pc = GU_PREVIEW_GUI(user);
1834 
1835     if (!pc->uri || !utils_uri_path_exists (pc->uri)) {
1836         return FALSE;
1837     }
1838 
1839     gdouble page_width = gtk_adjustment_get_page_size(pc->hadj);
1840     gdouble page_height = gtk_adjustment_get_page_size(pc->vadj);
1841 
1842     gdouble offset_x = MAX(get_document_margin(pc),
1843                           (page_width - pc->width_scaled) / 2);
1844 
1845 
1846     if (is_continuous(pc)) {
1847 
1848         gdouble offset_y = MAX(get_document_margin(pc),
1849                                (page_height - pc->height_scaled) / 2);
1850 
1851         // The page margins are just for safety...
1852         gdouble view_start_y = gtk_adjustment_get_value(pc->vadj) -
1853                                get_page_margin(pc);
1854         gdouble view_end_y = view_start_y + page_height + 2*get_page_margin(pc);
1855 
1856         int i;
1857         for (i=0; i < pc->n_pages; i++) {
1858             offset_y += get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1859             if (offset_y >= view_start_y) {
1860                 break;
1861             }
1862         }
1863 
1864         // We added one offset to many...
1865         offset_y -= get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1866 
1867         for (; i < pc->n_pages; i++) {
1868 
1869             paint_page(cr, pc, i,
1870                 page_offset_x(pc, i, offset_x),
1871                 page_offset_y(pc, i, offset_y));
1872 
1873             offset_y += get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1874 
1875             if (offset_y > view_end_y) {
1876                 break;
1877             }
1878         }
1879 
1880     } else {    // "Page" Layout...
1881 
1882         gdouble height = get_page_height(pc, pc->current_page) * pc->scale;
1883         gdouble offset_y = MAX(get_document_margin(pc), (page_height-height)/2);
1884 
1885         paint_page(cr, pc, pc->current_page,
1886             page_offset_x(pc, pc->current_page, offset_x),
1887             page_offset_y(pc, pc->current_page, offset_y));
1888     }
1889 
1890     return TRUE;
1891 }
1892 
1893 G_MODULE_EXPORT
on_adj_changed(GtkAdjustment * adjustment,gpointer user)1894 void on_adj_changed (GtkAdjustment *adjustment, gpointer user) {
1895     //L_F_DEBUG;
1896     GuPreviewGui* pc = GU_PREVIEW_GUI(user);
1897 
1898     // Abort any animated scrolls that might be running...
1899     pc->ascroll_steps_left = 0;
1900 
1901     update_current_page(pc);
1902 }
1903 
draw2page(GuPreviewGui * pc,gint dx,gint dy,gint * pp,gint * px,gint * py)1904 static void draw2page (GuPreviewGui* pc, gint dx, gint dy, gint *pp, gint *px, gint *py) {
1905 
1906     *px = dx;
1907     *py = dy;
1908     *pp = 0;
1909 
1910     gint adjpage_width = gtk_adjustment_get_page_size(pc->hadj);
1911     gint adjpage_height = gtk_adjustment_get_page_size(pc->vadj);
1912 
1913     *px -= MAX(get_document_margin(pc),
1914                           (adjpage_width - pc->width_scaled) / 2);
1915 
1916     if (is_continuous(pc)) {
1917         *py -= MAX(get_document_margin(pc),
1918                                (adjpage_height - pc->height_scaled) / 2);
1919 
1920         int i;
1921         for (i=0; i < pc->n_pages-1; i++) {
1922             gint pheight = get_page_height(pc, i)*pc->scale + get_page_margin(pc);
1923             if (*py > pheight) {
1924                 *py -= pheight;
1925                 *pp += 1;
1926             }
1927         }
1928     } else {
1929         gdouble height = get_page_height(pc, pc->current_page) * pc->scale;
1930         *py -= MAX(get_document_margin(pc), (adjpage_height-height)/2);
1931         *pp += pc->current_page;
1932     }
1933 
1934     //TODO Check if we still are inside a page...
1935 }
1936 
1937 G_MODULE_EXPORT
on_button_pressed(GtkWidget * w,GdkEventButton * e,void * user)1938 gboolean on_button_pressed (GtkWidget* w, GdkEventButton* e, void* user) {
1939     GuPreviewGui* pc = GU_PREVIEW_GUI(user);
1940 
1941     if (!pc->uri || !utils_uri_path_exists (pc->uri)) return FALSE;
1942 
1943     // Check where the user clicked
1944     gint page;
1945     gint x;
1946     gint y;
1947     draw2page(pc, e->x, e->y, &page, &x, &y);
1948 
1949     if (e->state & GDK_CONTROL_MASK) {
1950 
1951 
1952         slog(L_DEBUG, "Ctrl-click to %i, %i\n", x, y);
1953 
1954         synctex_scanner_p sync_scanner = synctex_scanner_new_with_output_file(pc->uri, C_TMPDIR, 1);
1955 
1956         if(synctex_edit_query(sync_scanner, page+1, x/pc->scale, y/pc->scale)>0) {
1957             synctex_node_p node;
1958             /*
1959              * SyncTeX can return several nodes. It seems best to use the last one, as
1960              * this one rarely is below (usually slightly above) the edited line.
1961              */
1962 
1963             if ((node = synctex_scanner_next_result(sync_scanner))) {
1964 
1965                 const gchar *file = synctex_scanner_get_name(sync_scanner, synctex_node_tag(node));
1966                 gint line = synctex_node_line(node);
1967 
1968                 slog(L_DEBUG, "File \"%s\", Line %i\n", file, line);
1969 
1970                 // FIXME: Go to the editor containing the file "file"!
1971                 editor_scroll_to_line(gummi_get_active_editor(), line-1);
1972 
1973             }
1974         }
1975 
1976         synctex_scanner_free(sync_scanner);
1977 
1978     }
1979 
1980     pc->prev_x = e->x;
1981     pc->prev_y = e->y;
1982     return FALSE;
1983 }
1984 
1985 G_MODULE_EXPORT
on_motion(GtkWidget * w,GdkEventMotion * e,void * user)1986 gboolean on_motion (GtkWidget* w, GdkEventMotion* e, void* user) {
1987     GuPreviewGui* pc = GU_PREVIEW_GUI(user);
1988 
1989     if (!pc->uri || !utils_uri_path_exists (pc->uri)) return FALSE;
1990 
1991     gdouble new_x = gtk_adjustment_get_value (pc->hadj) - (e->x - pc->prev_x);
1992     gdouble new_y = gtk_adjustment_get_value (pc->vadj) - (e->y - pc->prev_y);
1993 
1994     previewgui_goto_xy(pc, new_x, new_y);
1995 
1996     return TRUE;
1997 }
1998 
1999 G_MODULE_EXPORT
on_resize(GtkWidget * w,GdkRectangle * r,void * user)2000 gboolean on_resize (GtkWidget* w, GdkRectangle* r, void* user) {
2001     //L_F_DEBUG;
2002     GuPreviewGui* pc = GU_PREVIEW_GUI(user);
2003 
2004     if (!pc->uri || !utils_uri_path_exists (pc->uri)) return FALSE;
2005 
2006     LayeredRectangle fov = get_fov(pc);
2007     gdouble x_rel = (gdouble) (fov.x + fov.width/2) / pc->width_scaled;
2008     gdouble y_rel = (gdouble) (fov.y + fov.height/2) / pc->height_scaled;
2009 
2010     update_fit_scale(pc);
2011     update_page_positions(pc);
2012 
2013     fov = get_fov(pc);
2014     previewgui_goto_xy (pc, x_rel*pc->width_scaled - fov.width/2,
2015                             y_rel*pc->height_scaled - fov.height/2);
2016 
2017     return FALSE;
2018 }
2019 
2020 G_MODULE_EXPORT
on_scroll(GtkWidget * w,GdkEventScroll * e,void * user)2021 gboolean on_scroll (GtkWidget* w, GdkEventScroll* e, void* user) {
2022     //L_F_DEBUG;
2023     GuPreviewGui* pc = GU_PREVIEW_GUI(user);
2024 
2025     if (!pc->uri || !utils_uri_path_exists (pc->uri)) return FALSE;
2026 
2027     if (GDK_CONTROL_MASK & e->state) {
2028 
2029         gdouble old_scale = pc->scale;
2030         gdouble new_scale = -1;
2031         gint    new_index = -1;
2032         int i;
2033 
2034         // we only go through the percentage entrys - the fit entrys are not
2035         // always uo to date...
2036         for (i=0; i<N_ZOOM_SIZES; i++) {
2037             if (i == ZOOM_FIT_WIDTH || i == ZOOM_FIT_BOTH) {
2038                 continue;
2039             }
2040             if (list_sizes[i] > old_scale && e->direction == GDK_SCROLL_UP) {
2041                 if (new_index == -1 || list_sizes[i] < new_scale) {
2042                     new_scale = list_sizes[i];
2043                     new_index = i;
2044                 }
2045             } else if (list_sizes[i] < old_scale &&
2046                        e->direction == GDK_SCROLL_DOWN) {
2047                 if (new_index == -1 || list_sizes[i] > new_scale) {
2048                     new_scale = list_sizes[i];
2049                     new_index = i;
2050                 }
2051             }
2052         }
2053 
2054         if (new_index != -1) {
2055 
2056             previewgui_set_scale(pc, list_sizes[new_index],
2057                 e->x - gtk_adjustment_get_value(pc->hadj),
2058                 e->y - gtk_adjustment_get_value(pc->vadj));
2059 
2060             set_fit_mode(pc, FIT_NUMERIC);
2061             g_signal_handler_block(pc->combo_sizes,
2062                                    pc->combo_sizes_changed_handler);
2063             gtk_combo_box_set_active(pc->combo_sizes, new_index);
2064             g_signal_handler_unblock(pc->combo_sizes,
2065                                      pc->combo_sizes_changed_handler);
2066 
2067         }
2068 
2069         update_current_page(pc);
2070 
2071         return TRUE;
2072 
2073     } else if (e->state & GDK_SHIFT_MASK) {
2074         // Shift+Wheel scrolls the in the perpendicular direction
2075         if (e->direction == GDK_SCROLL_UP)
2076             e->direction = GDK_SCROLL_LEFT;
2077         else if (e->direction == GDK_SCROLL_LEFT)
2078             e->direction = GDK_SCROLL_UP;
2079         else if (e->direction == GDK_SCROLL_DOWN)
2080             e->direction = GDK_SCROLL_RIGHT;
2081         else if (e->direction == GDK_SCROLL_RIGHT)
2082             e->direction = GDK_SCROLL_DOWN;
2083 
2084         e->state &= ~GDK_SHIFT_MASK;
2085     } else {
2086         // Scroll if no scroll bars visible
2087 
2088         if (!is_vscrollbar_visible(pc)) {
2089             switch (e->direction) {
2090                 case GDK_SCROLL_UP:
2091 
2092                     if (pc->prev_page != -1) {
2093                         previewgui_goto_page (pc, pc->prev_page);
2094                     }
2095 
2096                     break;
2097                 case GDK_SCROLL_DOWN:
2098 
2099                     if (pc->next_page != -1) {
2100                         previewgui_goto_page (pc, pc->next_page);
2101                     }
2102                     break;
2103 
2104                 default:
2105                     // Do nothing
2106                     break;
2107             }
2108             return TRUE;
2109         }
2110     }
2111     return FALSE;
2112 }
2113