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