Fixed data structure corruption in the navigation history.
[llpp.git] / main.ml
blob1c61cebb71e1b933ce4138b7f5e70a7bfe0ddd6e
1 open Utils;;
2 open Config;;
3 open Glutils;;
4 open Listview;;
6 let selfexec = ref E.s;;
7 let ignoredoctitlte = ref false;;
8 let layouth = ref ~-1;;
9 let checkerstexid = ref None;;
11 let debugrect (x0, y0, x1, y1, x2, y2, x3, y3) =
12 dolog {|rect {
13 x0,y0=(% f, % f)
14 x1,y1=(% f, % f)
15 x2,y2=(% f, % f)
16 x3,y3=(% f, % f)
17 }|} x0 y0 x1 y1 x2 y2 x3 y3;
20 let pgscale h = truncate (float h *. conf.pgscale);;
22 let hscrollh () =
23 if ((conf.scrollb land scrollbhv != 0) && (state.w > state.winw))
24 || state.uioh#alwaysscrolly
25 then conf.scrollbw
26 else 0
29 let setfontsize n =
30 fstate.fontsize <- n;
31 fstate.wwidth <- measurestr fstate.fontsize "w";
32 fstate.maxrows <- (state.winh - fstate.fontsize - 1) / (fstate.fontsize + 1);
35 let launchpath () =
36 if emptystr conf.pathlauncher
37 then dolog "%s" state.path
38 else (
39 let command =
40 Str.global_replace Utils.Re.percent state.path conf.pathlauncher in
41 match spawn command [] with
42 | _pid -> ()
43 | exception exn -> dolog "failed to execute `%s': %s" command @@ exntos exn
47 let getopaque pageno =
48 try Some (Hashtbl.find state.pagemap (pageno, state.gen))
49 with Not_found -> None
52 let pagetranslatepoint l x y =
53 let dy = y - l.pagedispy in
54 let y = dy + l.pagey in
55 let dx = x - l.pagedispx in
56 let x = dx + l.pagex in
57 (x, y);
60 let onppundermouse g x y d =
61 let rec f = function
62 | l :: rest ->
63 begin match getopaque l.pageno with
64 | Some opaque ->
65 let x0 = l.pagedispx in
66 let x1 = x0 + l.pagevw in
67 let y0 = l.pagedispy in
68 let y1 = y0 + l.pagevh in
69 if y >= y0 && y <= y1 && x >= x0 && x <= x1
70 then
71 let px, py = pagetranslatepoint l x y in
72 match g opaque l px py with
73 | Some res -> res
74 | None -> f rest
75 else f rest
76 | _ -> f rest
77 end
78 | [] -> d
80 f state.layout
83 let getunder x y =
84 let g opaque l px py =
85 if state.bzoom
86 then (
87 match Ffi.rectofblock opaque px py with
88 | Some [|x0;x1;y0;y1|] ->
89 let rect = (x0, y0, x1, y0, x1, y1, x0, y1) in
90 let color = (0.0, 0.0, 1.0 /. (l.pageno mod 3 |> float), 0.5) in
91 state.rects <- [l.pageno, color, rect];
92 postRedisplay "getunder";
93 | _ -> ()
95 let under = Ffi.whatsunder opaque px py in
96 if under = Unone then None else Some under
98 onppundermouse g x y Unone
101 let unproject x y =
102 let g opaque l x y =
103 match Ffi.unproject opaque x y with
104 | Some (x, y) -> Some (Some (opaque, l.pageno, x, y))
105 | None -> None
107 onppundermouse g x y None;
110 let showtext c s =
111 state.text <- Printf.sprintf "%c%s" c s;
112 postRedisplay "showtext";
115 let impmsg fmt = Format.ksprintf (fun s -> showtext '!' s) fmt;;
117 let pipesel opaque cmd =
118 if Ffi.hassel opaque
119 then pipef ~closew:false "pipesel"
120 (fun w ->
121 Ffi.copysel w opaque;
122 postRedisplay "pipesel"
123 ) cmd
126 let paxunder x y =
127 let g opaque l px py =
128 if Ffi.markunder opaque px py conf.paxmark
129 then
130 Some (fun () ->
131 match getopaque l.pageno with
132 | None -> ()
133 | Some opaque -> pipesel opaque conf.paxcmd
135 else None
137 postRedisplay "paxunder";
138 if conf.paxmark = Mark_page
139 then
140 List.iter (fun l ->
141 match getopaque l.pageno with
142 | None -> ()
143 | Some opaque -> Ffi.clearmark opaque) state.layout;
144 state.roam <- onppundermouse g x y (fun () -> impmsg "whoopsie daisy");
147 let undertext = function
148 | Unone -> "none"
149 | Ulinkuri s -> s
150 | Utext s -> "font: " ^ s
151 | Uannotation (opaque, slinkindex) ->
152 "annotation: " ^ Ffi.getannotcontents opaque slinkindex
155 let updateunder x y =
156 match getunder x y with
157 | Unone -> Wsi.setcursor Wsi.CURSOR_INHERIT
158 | Ulinkuri uri ->
159 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
160 Wsi.setcursor Wsi.CURSOR_INFO
161 | Utext s ->
162 if conf.underinfo then showtext 'f' ("ont: " ^ s);
163 Wsi.setcursor Wsi.CURSOR_TEXT
164 | Uannotation _ ->
165 if conf.underinfo then showtext 'a' "nnotation";
166 Wsi.setcursor Wsi.CURSOR_INFO
169 let showlinktype under =
170 if conf.underinfo && under != Unone
171 then showtext ' ' @@ undertext under
174 let intentry_with_suffix text key =
175 let text =
176 match [@warning "-4"] key with
177 | Keys.Ascii ('0'..'9' as c) -> addchar text c
178 | Keys.Ascii ('k' | 'm' | 'g' | 'K' | 'M' | 'G' as c) ->
179 addchar text @@ asciilower c
180 | _ ->
181 state.text <- "invalid key";
182 text
184 TEcont text
187 let wcmd fmt =
188 let b = Buffer.create 16 in
189 Printf.kbprintf
190 (fun b ->
191 let b = Buffer.to_bytes b in
192 Ffi.wcmd state.ss b @@ Bytes.length b
193 ) b fmt
196 let nogeomcmds = function
197 | s, [] -> emptystr s
198 | _ -> false
201 let layoutN ((columns, coverA, coverB), b) x y sw sh =
202 let rec fold accu n =
203 if n = Array.length b
204 then accu
205 else
206 let pdimno, dx, vy, (_, w, h, xoff) = b.(n) in
207 if (vy - y) > sh &&
208 (n = coverA - 1
209 || n = state.pagecount - coverB
210 || (n - coverA) mod columns = columns - 1)
211 then accu
212 else
213 let accu =
214 if vy + h > y
215 then
216 let pagey = max 0 (y - vy) in
217 let pagedispy = if pagey > 0 then 0 else vy - y in
218 let pagedispx, pagex =
219 let pdx =
220 if n = coverA - 1 || n = state.pagecount - coverB
221 then x + (sw - w) / 2
222 else dx + xoff + x
224 if pdx < 0
225 then 0, -pdx
226 else pdx, 0
228 let pagevw =
229 let vw = sw - pagedispx in
230 let pw = w - pagex in
231 min vw pw
233 let pagevh = min (h - pagey) (sh - pagedispy) in
234 if pagevw > 0 && pagevh > 0
235 then
236 { pageno = n
237 ; pagedimno = pdimno
238 ; pagew = w
239 ; pageh = h
240 ; pagex = pagex
241 ; pagey = pagey
242 ; pagevw = pagevw
243 ; pagevh = pagevh
244 ; pagedispx = pagedispx
245 ; pagedispy = pagedispy
246 ; pagecol = 0
247 } :: accu
248 else accu
249 else accu
251 fold accu (n+1)
253 if Array.length b = 0
254 then []
255 else List.rev (fold [] (page_of_y y))
258 let layoutS (columns, b) x y sw sh =
259 let rec fold accu n =
260 if n = Array.length b
261 then accu
262 else
263 let pdimno, px, vy, (_, pagew, pageh, xoff) = b.(n) in
264 if (vy - y) > sh
265 then accu
266 else
267 let accu =
268 if vy + pageh > y
269 then
270 let x = xoff + x in
271 let pagey = max 0 (y - vy) in
272 let pagedispy = if pagey > 0 then 0 else vy - y in
273 let pagedispx, pagex =
274 if px = 0
275 then (
276 if x < 0
277 then 0, -x
278 else x, 0
280 else (
281 let px = px - x in
282 if px < 0
283 then -px, 0
284 else 0, px
287 let pagecolw = pagew/columns in
288 let pagedispx =
289 if pagecolw < sw
290 then pagedispx + ((sw - pagecolw) / 2)
291 else pagedispx
293 let pagevw =
294 let vw = sw - pagedispx in
295 let pw = pagew - pagex in
296 min vw pw
298 let pagevw = min pagevw pagecolw in
299 let pagevh = min (pageh - pagey) (sh - pagedispy) in
300 if pagevw > 0 && pagevh > 0
301 then
302 { pageno = n/columns
303 ; pagedimno = pdimno
304 ; pagew = pagew
305 ; pageh = pageh
306 ; pagex = pagex
307 ; pagey = pagey
308 ; pagevw = pagevw
309 ; pagevh = pagevh
310 ; pagedispx = pagedispx
311 ; pagedispy = pagedispy
312 ; pagecol = n mod columns
313 } :: accu
314 else accu
315 else accu
317 fold accu (n+1)
319 List.rev (fold [] 0)
322 let layout x y sw sh =
323 if nogeomcmds state.geomcmds
324 then
325 match conf.columns with
326 | Csingle b -> layoutN ((1, 0, 0), b) x y sw sh
327 | Cmulti c -> layoutN c x y sw sh
328 | Csplit s -> layoutS s x y sw sh
329 else []
332 let maxy () = state.maxy - if conf.maxhfit then state.winh else 0;;
333 let clamp incr = bound (state.y + incr) 0 @@ maxy ();;
335 let itertiles l f =
336 let tilex = l.pagex mod conf.tilew in
337 let tiley = l.pagey mod conf.tileh in
339 let col = l.pagex / conf.tilew in
340 let row = l.pagey / conf.tileh in
342 let rec rowloop row y0 dispy h =
343 if h != 0
344 then
345 let dh = conf.tileh - y0 in
346 let dh = min h dh in
347 let rec colloop col x0 dispx w =
348 if w != 0
349 then
350 let dw = conf.tilew - x0 in
351 let dw = min w dw in
352 f col row dispx dispy x0 y0 dw dh;
353 colloop (col+1) 0 (dispx+dw) (w-dw)
355 colloop col tilex l.pagedispx l.pagevw;
356 rowloop (row+1) 0 (dispy+dh) (h-dh)
358 if l.pagevw > 0 && l.pagevh > 0
359 then rowloop row tiley l.pagedispy l.pagevh;
362 let gettileopaque l col row =
363 let key = l.pageno, state.gen, conf.colorspace,
364 conf.angle, l.pagew, l.pageh, col, row in
365 try Some (Hashtbl.find state.tilemap key)
366 with Not_found -> None
369 let puttileopaque l col row gen colorspace angle opaque size elapsed =
370 let key = l.pageno, gen, colorspace, angle, l.pagew, l.pageh, col, row in
371 Hashtbl.add state.tilemap key (opaque, size, elapsed)
374 let drawtiles l color =
375 GlDraw.color color;
376 Ffi.begintiles ();
377 let f col row x y tilex tiley w h =
378 match gettileopaque l col row with
379 | Some (opaque, _, t) ->
380 let params = x, y, w, h, tilex, tiley in
381 if conf.invert
382 then GlTex.env (`mode `blend);
383 Ffi.drawtile params opaque;
384 if conf.invert
385 then GlTex.env (`mode `modulate);
386 if conf.debug
387 then (
388 Ffi.endtiles ();
389 let s = Printf.sprintf "%d[%d,%d] %f sec" l.pageno col row t in
390 let w = measurestr fstate.fontsize s in
391 GlDraw.color (0.0, 0.0, 0.0);
392 filledrect
393 (float (x-2))
394 (float (y-2))
395 (float (x+2) +. w)
396 (float (y + fstate.fontsize + 2));
397 GlDraw.color color;
398 drawstring fstate.fontsize x (y + fstate.fontsize - 1) s;
399 Ffi.begintiles ();
402 | None ->
403 Ffi.endtiles ();
404 let w = let lw = state.winw - x in min lw w
405 and h = let lh = state.winh - y in min lh h
407 if conf.invert
408 then GlTex.env (`mode `blend);
409 begin match !checkerstexid with
410 | Some id ->
411 Gl.enable `texture_2d;
412 GlTex.bind_texture ~target:`texture_2d id;
413 let x0 = float x
414 and y0 = float y
415 and x1 = float (x+w)
416 and y1 = float (y+h) in
418 let tw = float w /. 16.0
419 and th = float h /. 16.0 in
420 let tx0 = float tilex /. 16.0
421 and ty0 = float tiley /. 16.0 in
422 let tx1 = tx0 +. tw
423 and ty1 = ty0 +. th in
424 Raw.sets_float Glutils.vraw ~pos:0
425 [| x0; y0; x0; y1; x1; y0; x1; y1 |];
426 Raw.sets_float Glutils.traw ~pos:0
427 [| tx0; ty0; tx0; ty1; tx1; ty0; tx1; ty1 |];
428 GlArray.vertex `two Glutils.vraw;
429 GlArray.tex_coord `two Glutils.traw;
430 GlArray.draw_arrays `triangle_strip ~first:0 ~count:4;
431 Gl.disable `texture_2d;
433 | None ->
434 GlDraw.color (1.0, 1.0, 1.0);
435 filledrect (float x) (float y) (float (x+w)) (float (y+h));
436 end;
437 if conf.invert
438 then GlTex.env (`mode `modulate);
439 if w > 128 && h > fstate.fontsize + 10
440 then (
441 let c = if conf.invert then 1.0 else 0.0 in
442 GlDraw.color (c, c, c);
443 let c, r =
444 if conf.verbose
445 then (col*conf.tilew, row*conf.tileh)
446 else col, row
448 drawstring2 fstate.fontsize x y "Loading %d [%d,%d]" l.pageno c r;
450 GlDraw.color color;
451 Ffi.begintiles ();
453 itertiles l f;
454 Ffi.endtiles ();
457 let pagevisible layout n = List.exists (fun l -> l.pageno = n) layout;;
459 let tilevisible1 l x y =
460 let ax0 = l.pagex
461 and ax1 = l.pagex + l.pagevw
462 and ay0 = l.pagey
463 and ay1 = l.pagey + l.pagevh in
465 let bx0 = x
466 and by0 = y in
467 let bx1 = min (bx0 + conf.tilew) l.pagew
468 and by1 = min (by0 + conf.tileh) l.pageh in
470 let rx0 = max ax0 bx0
471 and ry0 = max ay0 by0
472 and rx1 = min ax1 bx1
473 and ry1 = min ay1 by1 in
475 let nonemptyintersection = rx1 > rx0 && ry1 > ry0 in
476 nonemptyintersection
479 let tilevisible layout n x y =
480 let rec findpageinlayout m = function
481 | l :: rest when l.pageno = n ->
482 tilevisible1 l x y || (
483 match conf.columns with
484 | Csplit (c, _) when c > m -> findpageinlayout (m+1) rest
485 | Csplit _ | Csingle _ | Cmulti _ -> false
487 | _ :: rest -> findpageinlayout 0 rest
488 | [] -> false
490 findpageinlayout 0 layout;
493 let tileready l x y =
494 tilevisible1 l x y &&
495 gettileopaque l (x/conf.tilew) (y/conf.tileh) != None
498 let tilepage n p layout =
499 let rec loop = function
500 | l :: rest ->
501 if l.pageno = n
502 then
503 let f col row _ _ _ _ _ _ =
504 if state.currently = Idle
505 then
506 match gettileopaque l col row with
507 | Some _ -> ()
508 | None ->
509 let x = col*conf.tilew
510 and y = row*conf.tileh in
511 let w =
512 let w = l.pagew - x in
513 min w conf.tilew
515 let h =
516 let h = l.pageh - y in
517 min h conf.tileh
519 let pbo =
520 if conf.usepbo
521 then Ffi.getpbo w h conf.colorspace
522 else ~< "0"
524 wcmd "tile %s %d %d %d %d %s" (~> p) x y w h (~> pbo);
525 state.currently <-
526 Tiling (
527 l, p, conf.colorspace, conf.angle,
528 state.gen, col, row, conf.tilew, conf.tileh
531 itertiles l f;
532 else
533 loop rest
535 | [] -> ()
537 if nogeomcmds state.geomcmds
538 then loop layout;
541 let preloadlayout x y sw sh =
542 let y = if y < sh then 0 else y - sh in
543 let x = min 0 (x + sw) in
544 let h = sh*3 in
545 let w = sw*3 in
546 layout x y w h;
549 let load pages =
550 let rec loop pages =
551 if state.currently = Idle
552 then
553 match pages with
554 | l :: rest ->
555 begin match getopaque l.pageno with
556 | None ->
557 wcmd "page %d %d" l.pageno l.pagedimno;
558 state.currently <- Loading (l, state.gen);
559 | Some opaque ->
560 tilepage l.pageno opaque pages;
561 loop rest
562 end;
563 | _ -> ()
565 if nogeomcmds state.geomcmds
566 then loop pages
569 let preload pages =
570 load pages;
571 if conf.preload && state.currently = Idle
572 then load (preloadlayout state.x state.y state.winw state.winh);
575 let layoutready layout =
576 let rec fold all ls =
577 all && match ls with
578 | l :: rest ->
579 let seen = ref false in
580 let allvisible = ref true in
581 let foo col row _ _ _ _ _ _ =
582 seen := true;
583 allvisible := !allvisible &&
584 begin match gettileopaque l col row with
585 | Some _ -> true
586 | None -> false
589 itertiles l foo;
590 fold (!seen && !allvisible) rest
591 | [] -> true
593 let alltilesvisible = fold true layout in
594 alltilesvisible;
597 let gotoxy x y =
598 let y = bound y 0 state.maxy in
599 let y, layout =
600 let layout = layout x y state.winw state.winh in
601 postRedisplay "gotoxy ready";
602 y, layout
604 state.x <- x;
605 state.y <- y;
606 state.layout <- layout;
607 begin match state.mode with
608 | LinkNav ln ->
609 begin match ln with
610 | Ltexact (pageno, linkno) ->
611 let rec loop = function
612 | [] ->
613 state.lnava <- Some (pageno, linkno);
614 state.mode <- LinkNav (Ltgendir 0)
615 | l :: _ when l.pageno = pageno ->
616 begin match getopaque pageno with
617 | None -> state.mode <- LinkNav (Ltnotready (pageno, 0))
618 | Some opaque ->
619 let x0, y0, x1, y1 = Ffi.getlinkrect opaque linkno in
620 if not (x0 >= l.pagex && x1 <= l.pagex + l.pagevw
621 && y0 >= l.pagey && y1 <= l.pagey + l.pagevh)
622 then state.mode <- LinkNav (Ltgendir 0)
624 | _ :: rest -> loop rest
626 loop layout
627 | Ltnotready _ | Ltgendir _ -> ()
629 | Birdseye _ | Textentry _ | View -> ()
630 end;
631 begin match state.mode with
632 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
633 if not (pagevisible layout pageno)
634 then (
635 match state.layout with
636 | [] -> ()
637 | l :: _ ->
638 state.mode <- Birdseye (conf, leftx, l.pageno, hooverpageno, anchor)
640 | LinkNav lt ->
641 begin match lt with
642 | Ltnotready (_, dir)
643 | Ltgendir dir ->
644 let linknav =
645 let rec loop = function
646 | [] -> lt
647 | l :: rest ->
648 match getopaque l.pageno with
649 | None -> Ltnotready (l.pageno, dir)
650 | Some opaque ->
651 let link =
652 let ld =
653 if dir = 0
654 then LDfirstvisible (l.pagex, l.pagey, dir)
655 else if dir > 0 then LDfirst else LDlast
657 Ffi.findlink opaque ld
659 match link with
660 | Lnotfound -> loop rest
661 | Lfound n ->
662 showlinktype (Ffi.getlink opaque n);
663 Ltexact (l.pageno, n)
665 loop state.layout
667 state.mode <- LinkNav linknav
668 | Ltexact _ -> ()
670 | Textentry _ | View -> ()
671 end;
672 preload layout;
673 if conf.updatecurs
674 then (
675 let mx, my = state.mpos in
676 updateunder mx my;
680 let conttiling pageno opaque =
681 tilepage pageno opaque
682 (if conf.preload
683 then preloadlayout state.x state.y state.winw state.winh
684 else state.layout)
687 let gotoxy x y =
688 if not conf.verbose then state.text <- E.s;
689 gotoxy x y;
692 let getanchory (n, top, dtop) =
693 let y, h = getpageyh n in
694 if conf.presentation
695 then
696 let ips = calcips h in
697 y + truncate (top*.float h -. dtop*.float ips) + ips;
698 else y + truncate (top*.float h -. dtop*.float conf.interpagespace)
701 let addnav () =
702 state.hists.nav <-
703 { past = getanchor () :: state.hists.nav.past
704 ; future = []
708 let gotopage n top =
709 let y, h = getpageyh n in
710 let y = y + (truncate (top *. float h)) in
711 gotoxy state.x y
714 let gotopage1 n top =
715 let y = getpagey n in
716 let y = y + top in
717 gotoxy state.x y
720 let invalidate s f =
721 Glutils.redisplay := false;
722 state.layout <- [];
723 state.pdims <- [];
724 state.rects <- [];
725 state.rects1 <- [];
726 match state.geomcmds with
727 | ps, [] when emptystr ps ->
728 f ();
729 state.geomcmds <- s, [];
730 | ps, [] -> state.geomcmds <- ps, [s, f];
731 | ps, (s', _) :: rest when s' = s -> state.geomcmds <- ps, ((s, f) :: rest);
732 | ps, cmds -> state.geomcmds <- ps, ((s, f) :: cmds);
735 let flushpages () =
736 Hashtbl.iter (fun _ opaque -> wcmd "freepage %s" (~> opaque)) state.pagemap;
737 Hashtbl.clear state.pagemap;
740 let flushtiles () =
741 if not (Queue.is_empty state.tilelru)
742 then (
743 Queue.iter (fun (k, p, s) ->
744 wcmd "freetile %s" (~> p);
745 state.memused <- state.memused - s;
746 Hashtbl.remove state.tilemap k;
747 ) state.tilelru;
748 state.uioh#infochanged Memused;
749 Queue.clear state.tilelru;
751 load state.layout;
754 let stateh h =
755 let h = truncate (float h*.conf.zoom) in
756 let d = conf.interpagespace lsl (if conf.presentation then 1 else 0) in
757 h - d
760 let fillhelp () =
761 state.help <-
762 let sl = keystostrlist conf in
763 let rec loop accu =
764 function | [] -> accu
765 | s :: rest -> loop ((s, 0, Noaction) :: accu) rest
766 in Help.makehelp conf.urilauncher
767 @ (("", 0, Noaction) :: loop [] sl) |> Array.of_list
770 let opendoc path password =
771 state.path <- path;
772 state.password <- password;
773 state.gen <- state.gen + 1;
774 state.docinfo <- [];
775 state.outlines <- [||];
777 flushpages ();
778 Ffi.setaalevel conf.aalevel;
779 Ffi.setpapercolor conf.papercolor;
780 let titlepath =
781 if emptystr state.origin
782 then path
783 else state.origin
785 Wsi.settitle ("llpp " ^ mbtoutf8 (Filename.basename titlepath));
786 wcmd "open %d %d %s\000%s\000%s\000"
787 (btod conf.usedoccss) !layouth
788 path password conf.css;
789 invalidate "reqlayout"
790 (fun () ->
791 wcmd "reqlayout %d %d %d %s\000"
792 conf.angle (FMTE.to_int conf.fitmodel)
793 (stateh state.winh) state.nameddest
795 fillhelp ();
798 let reload () =
799 state.anchor <- getanchor ();
800 state.reload <- Some (state.x, state.y, now ());
801 opendoc state.path state.password;
804 let scalecolor c = let c = c *. conf.colorscale in (c, c, c);;
805 let scalecolor2 (r, g, b) =
806 (r *. conf.colorscale, g *. conf.colorscale, b *. conf.colorscale);
809 let docolumns columns =
810 match columns with
811 | Csingle _ ->
812 let a = Array.make state.pagecount (-1, -1, -1, (-1, -1, -1, -1)) in
813 let rec loop pageno pdimno pdim y ph pdims =
814 if pageno != state.pagecount
815 then
816 let pdimno, ((_, w, h, xoff) as pdim), pdims =
817 match pdims with
818 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
819 pdimno+1, pdim, rest
820 | _ ->
821 pdimno, pdim, pdims
823 let x = max 0 (((state.winw - w) / 2) - xoff) in
824 let y =
825 y + (if conf.presentation
826 then (if pageno = 0 then calcips h else calcips ph + calcips h)
827 else (if pageno = 0 then 0 else conf.interpagespace))
829 a.(pageno) <- (pdimno, x, y, pdim);
830 loop (pageno+1) pdimno pdim (y + h) h pdims
832 loop 0 ~-1 (-1,-1,-1,-1) 0 0 state.pdims;
833 conf.columns <- Csingle a;
835 | Cmulti ((columns, coverA, coverB), _) ->
836 let a = Array.make state.pagecount (-1, -1, -1, (-1, -1, -1, -1)) in
837 let rec loop pageno pdimno pdim x y rowh pdims =
838 let rec fixrow m =
839 if m = pageno then () else
840 let (pdimno, x, y, ((_, _, h, _) as pdim)) = a.(m) in
841 if h < rowh
842 then (
843 let y = y + (rowh - h) / 2 in
844 a.(m) <- (pdimno, x, y, pdim);
846 fixrow (m+1)
848 if pageno = state.pagecount
849 then fixrow (((pageno - 1) / columns) * columns)
850 else
851 let pdimno, ((_, w, h, xoff) as pdim), pdims =
852 match pdims with
853 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
854 pdimno+1, pdim, rest
855 | _ -> pdimno, pdim, pdims
857 let x, y, rowh' =
858 if pageno = coverA - 1 || pageno = state.pagecount - coverB
859 then (
860 let x = (state.winw - w) / 2 in
861 let ips =
862 if conf.presentation then calcips h else conf.interpagespace in
863 x, y + ips + rowh, h
865 else (
866 if (pageno - coverA) mod columns = 0
867 then (
868 let x = max 0 (state.winw - state.w) / 2 in
869 let y =
870 if conf.presentation
871 then
872 let ips = calcips h in
873 y + (if pageno = 0 then 0 else calcips rowh + ips)
874 else
875 y + (if pageno = 0 then 0 else conf.interpagespace)
877 x, y + rowh, h
879 else x, y, max rowh h
882 let y =
883 if pageno > 1 && (pageno - coverA) mod columns = 0
884 then (
885 let y =
886 if pageno = columns && conf.presentation
887 then (
888 let ips = calcips rowh in
889 for i = 0 to pred columns
891 let (pdimno, x, y, pdim) = a.(i) in
892 a.(i) <- (pdimno, x, y+ips, pdim)
893 done;
894 y+ips;
896 else y
898 fixrow (pageno - columns);
901 else y
903 a.(pageno) <- (pdimno, x, y, pdim);
904 let x = x + w + xoff*2 + conf.interpagespace in
905 loop (pageno+1) pdimno pdim x y rowh' pdims
907 loop 0 ~-1 (-1,-1,-1,-1) 0 0 0 state.pdims;
908 conf.columns <- Cmulti ((columns, coverA, coverB), a);
910 | Csplit (c, _) ->
911 let a = Array.make (state.pagecount*c) (-1, -1, -1, (-1, -1, -1, -1)) in
912 let rec loop pageno pdimno pdim y pdims =
913 if pageno != state.pagecount
914 then
915 let pdimno, ((_, w, h, _) as pdim), pdims =
916 match pdims with
917 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
918 pdimno+1, pdim, rest
919 | _ -> pdimno, pdim, pdims
921 let cw = w / c in
922 let rec loop1 n x y =
923 if n = c then y else (
924 a.(pageno*c + n) <- (pdimno, x, y, pdim);
925 loop1 (n+1) (x+cw) (y + h + conf.interpagespace)
928 let y = loop1 0 0 y in
929 loop (pageno+1) pdimno pdim y pdims
931 loop 0 ~-1 (-1,-1,-1,-1) 0 state.pdims;
932 conf.columns <- Csplit (c, a);
935 let represent () =
936 docolumns conf.columns;
937 state.maxy <- calcheight ();
938 if state.reprf == noreprf
939 then (
940 match state.mode with
941 | Birdseye (_, _, pageno, _, _) ->
942 let y, h = getpageyh pageno in
943 let top = (state.winh - h) / 2 in
944 gotoxy state.x (max 0 (y - top))
945 | Textentry _ | View | LinkNav _ ->
946 let y = getanchory state.anchor in
947 let y = min y (state.maxy - state.winh) in
948 gotoxy state.x y;
950 else (
951 state.reprf ();
952 state.reprf <- noreprf;
956 let reshape ?(firsttime=false) w h =
957 GlDraw.viewport ~x:0 ~y:0 ~w ~h;
958 if not firsttime && nogeomcmds state.geomcmds
959 then state.anchor <- getanchor ();
961 state.winw <- w;
962 let w = truncate (float w *. conf.zoom) in
963 let w = max w 2 in
964 state.winh <- h;
965 setfontsize fstate.fontsize;
966 GlMat.mode `modelview;
967 GlMat.load_identity ();
969 GlMat.mode `projection;
970 GlMat.load_identity ();
971 GlMat.rotate ~x:1.0 ~angle:180.0 ();
972 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
973 GlMat.scale3 (2.0 /. float state.winw, 2.0 /. float state.winh, 1.0);
975 let relx =
976 if conf.zoom <= 1.0
977 then 0.0
978 else float state.x /. float state.w
980 invalidate "geometry"
981 (fun () ->
982 state.w <- w;
983 if not firsttime
984 then state.x <- truncate (relx *. float w);
985 let w =
986 match conf.columns with
987 | Csingle _ -> w
988 | Cmulti ((c, _, _), _) -> (w - (c-1)*conf.interpagespace) / c
989 | Csplit (c, _) -> w * c
991 wcmd "geometry %d %d %d" w (stateh h) (FMTE.to_int conf.fitmodel)
995 let gctiles () =
996 let len = Queue.length state.tilelru in
997 let layout = lazy (if conf.preload
998 then preloadlayout state.x state.y state.winw state.winh
999 else state.layout) in
1000 let rec loop qpos =
1001 if state.memused > conf.memlimit
1002 then (
1003 if qpos < len
1004 then
1005 let (k, p, s) as lruitem = Queue.pop state.tilelru in
1006 let n, gen, colorspace, angle, pagew, pageh, col, row = k in
1007 let (_, pw, ph, _) = getpagedim n in
1008 if gen = state.gen
1009 && colorspace = conf.colorspace
1010 && angle = conf.angle
1011 && pagew = pw
1012 && pageh = ph
1013 && (
1014 let x = col*conf.tilew and y = row*conf.tileh in
1015 tilevisible (Lazy.force_val layout) n x y
1017 then Queue.push lruitem state.tilelru
1018 else (
1019 Ffi.freepbo p;
1020 wcmd "freetile %s" (~> p);
1021 state.memused <- state.memused - s;
1022 state.uioh#infochanged Memused;
1023 Hashtbl.remove state.tilemap k;
1025 loop (qpos+1)
1028 loop 0
1031 let onpagerect pageno f =
1032 let b =
1033 match conf.columns with
1034 | Cmulti (_, b) -> b
1035 | Csingle b -> b
1036 | Csplit (_, b) -> b
1038 if pageno >= 0 && pageno < Array.length b
1039 then
1040 let (_, _, _, (_, w, h, _)) = b.(pageno) in
1041 f w h
1044 let gotopagexy1 pageno x y =
1045 let _,w1,h1,leftx = getpagedim pageno in
1046 let top = y /. (float h1) in
1047 let left = x /. (float w1) in
1048 let py, w, h = getpageywh pageno in
1049 let wh = state.winh in
1050 let x = left *. (float w) in
1051 let x = leftx + state.x + truncate x in
1052 let sx =
1053 if x < 0 || x >= state.winw
1054 then state.x - x
1055 else state.x
1057 let pdy = truncate (top *. float h) in
1058 let y' = py + pdy in
1059 let dy = y' - state.y in
1060 let sy =
1061 if x != state.x || not (dy > 0 && dy < wh)
1062 then (
1063 if conf.presentation
1064 then
1065 if abs (py - y') > wh
1066 then y'
1067 else py
1068 else y';
1070 else state.y
1072 if state.x != sx || state.y != sy
1073 then gotoxy sx sy
1074 else gotoxy state.x state.y;
1077 let gotopagexy pageno x y =
1078 match state.mode with
1079 | Birdseye _ -> gotopage pageno 0.0
1080 | Textentry _ | View | LinkNav _ -> gotopagexy1 pageno x y
1083 let getpassword () =
1084 let passcmd = getenvdef "LLPP_ASKPASS" conf.passcmd in
1085 if emptystr passcmd
1086 then E.s
1087 else getcmdoutput (fun s ->
1088 impmsg "error getting password: %s" s;
1089 dolog "%s" s) passcmd;
1092 let pgoto opaque pageno x y =
1093 let pdimno = getpdimno pageno in
1094 let x, y = Ffi.project opaque pageno pdimno x y in
1095 gotopagexy pageno x y;
1098 let act cmds =
1099 (* dolog "%S" cmds; *)
1100 let spl = splitatchar cmds ' ' in
1101 let scan s fmt f =
1102 try Scanf.sscanf s fmt f
1103 with exn ->
1104 dolog "error processing '%S': %s" cmds @@ exntos exn;
1105 exit 1
1107 let addoutline outline =
1108 match state.currently with
1109 | Outlining outlines -> state.currently <- Outlining (outline :: outlines)
1110 | Idle -> state.currently <- Outlining [outline]
1111 | Loading _ | Tiling _ ->
1112 dolog "invalid outlining state";
1113 logcurrently state.currently
1115 match spl with
1116 | "clear", "" ->
1117 state.pdims <- [];
1118 state.uioh#infochanged Pdim;
1120 | "clearrects", "" ->
1121 state.rects <- state.rects1;
1122 postRedisplay "clearrects";
1124 | "continue", args ->
1125 let n = scan args "%u" (fun n -> n) in
1126 state.pagecount <- n;
1127 begin match state.currently with
1128 | Outlining l ->
1129 state.currently <- Idle;
1130 state.outlines <- Array.of_list (List.rev l)
1131 | Idle | Loading _ | Tiling _ -> ()
1132 end;
1134 let cur, cmds = state.geomcmds in
1135 if emptystr cur then error "empty geomcmd";
1137 begin match List.rev cmds with
1138 | [] ->
1139 state.geomcmds <- E.s, [];
1140 represent ();
1141 | (s, f) :: rest ->
1142 f ();
1143 state.geomcmds <- s, List.rev rest;
1144 end;
1145 postRedisplay "continue";
1147 | "msg", args ->
1148 showtext ' ' args
1150 | "vmsg", args ->
1151 if conf.verbose then showtext ' ' args
1153 | "emsg", args ->
1154 Buffer.add_string state.errmsgs args;
1155 Buffer.add_char state.errmsgs '\n';
1156 if not state.newerrmsgs
1157 then (
1158 state.newerrmsgs <- true;
1159 postRedisplay "error message";
1162 | "progress", args ->
1163 let progress, text =
1164 scan args "%f %n"
1165 (fun f pos -> f, String.sub args pos (String.length args - pos))
1167 state.text <- text;
1168 state.progress <- progress;
1169 postRedisplay "progress"
1171 | "firstmatch", args ->
1172 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
1173 scan args "%u %d %f %f %f %f %f %f %f %f"
1174 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
1175 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
1177 let y = (getpagey pageno) + truncate y0 in
1178 let x =
1179 if (state.x < - truncate x0) || (state.x > state.winw - truncate x1)
1180 then state.winw/2 - truncate (x0 /. 2. +. x1 /. 2.)
1181 else state.x
1183 addnav ();
1184 gotoxy x y;
1185 let color = (0.0, 0.0, 1.0 /. float c, 0.5) in
1186 state.rects1 <- [pageno, color, (x0, y0, x1, y1, x2, y2, x3, y3)]
1188 | "match", args ->
1189 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
1190 scan args "%u %d %f %f %f %f %f %f %f %f"
1191 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
1192 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
1194 let color = (0.0, 0.0, 1.0 /. float c, 0.5) in
1195 state.rects1 <-
1196 (pageno, color, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
1198 | "page", args ->
1199 let pageopaques, t = scan args "%s %f" (fun p t -> p, t) in
1200 let pageopaque = ~< pageopaques in
1201 begin match state.currently with
1202 | Loading (l, gen) ->
1203 vlog "page %d took %f sec" l.pageno t;
1204 Hashtbl.replace state.pagemap (l.pageno, gen) pageopaque;
1205 let preloadedpages =
1206 if conf.preload
1207 then preloadlayout state.x state.y state.winw state.winh
1208 else state.layout
1210 let evict () =
1211 let set = List.fold_left (fun s l -> IntSet.add l.pageno s)
1212 IntSet.empty preloadedpages
1214 let evictedpages =
1215 Hashtbl.fold (fun ((pageno, _) as key) opaque accu ->
1216 if not (IntSet.mem pageno set)
1217 then (
1218 wcmd "freepage %s" (~> opaque);
1219 key :: accu
1221 else accu
1222 ) state.pagemap []
1224 List.iter (Hashtbl.remove state.pagemap) evictedpages;
1226 evict ();
1227 state.currently <- Idle;
1228 if gen = state.gen
1229 then (
1230 tilepage l.pageno pageopaque state.layout;
1231 load state.layout;
1232 load preloadedpages;
1233 let visible = pagevisible state.layout l.pageno in
1234 if visible
1235 then (
1236 match state.mode with
1237 | LinkNav (Ltnotready (pageno, dir)) ->
1238 if pageno = l.pageno
1239 then (
1240 let link =
1241 let ld =
1242 if dir = 0
1243 then LDfirstvisible (l.pagex, l.pagey, dir)
1244 else if dir > 0 then LDfirst else LDlast
1246 Ffi.findlink pageopaque ld
1248 match link with
1249 | Lnotfound -> ()
1250 | Lfound n ->
1251 showlinktype (Ffi.getlink pageopaque n);
1252 state.mode <- LinkNav (Ltexact (l.pageno, n))
1254 | LinkNav (Ltgendir _)
1255 | LinkNav (Ltexact _)
1256 | View
1257 | Birdseye _
1258 | Textentry _ -> ()
1261 if visible && layoutready state.layout
1262 then postRedisplay "page";
1265 | Idle | Tiling _ | Outlining _ ->
1266 dolog "Inconsistent loading state";
1267 logcurrently state.currently;
1268 exit 1
1271 | "tile" , args ->
1272 let (x, y, opaques, size, t) =
1273 scan args "%u %u %s %u %f" (fun x y p size t -> (x, y, p, size, t))
1275 let opaque = ~< opaques in
1276 begin match state.currently with
1277 | Tiling (l, pageopaque, cs, angle, gen, col, row, tilew, tileh) ->
1278 vlog "tile %d [%d,%d] took %f sec" l.pageno col row t;
1280 Ffi.unmappbo opaque;
1281 if tilew != conf.tilew || tileh != conf.tileh
1282 then (
1283 wcmd "freetile %s" (~> opaque);
1284 state.currently <- Idle;
1285 load state.layout;
1287 else (
1288 puttileopaque l col row gen cs angle opaque size t;
1289 state.memused <- state.memused + size;
1290 state.uioh#infochanged Memused;
1291 gctiles ();
1292 Queue.push ((l.pageno, gen, cs, angle, l.pagew, l.pageh, col, row),
1293 opaque, size) state.tilelru;
1295 state.currently <- Idle;
1296 if gen = state.gen
1297 && conf.colorspace = cs
1298 && conf.angle = angle
1299 && tilevisible state.layout l.pageno x y
1300 then conttiling l.pageno pageopaque;
1302 preload state.layout;
1303 if gen = state.gen
1304 && conf.colorspace = cs
1305 && conf.angle = angle
1306 && tilevisible state.layout l.pageno x y
1307 && layoutready state.layout
1308 then postRedisplay "tile nothrottle";
1311 | Idle | Loading _ | Outlining _ ->
1312 dolog "Inconsistent tiling state";
1313 logcurrently state.currently;
1314 exit 1
1317 | "pdim", args ->
1318 let (n, w, h, _) as pdim =
1319 scan args "%u %u %u %u" (fun n w h x -> n, w, h, x)
1321 let pdim =
1322 match conf.fitmodel with
1323 | FitWidth -> pdim
1324 | FitPage | FitProportional ->
1325 match conf.columns with
1326 | Csplit _ -> (n, w, h, 0)
1327 | Csingle _ | Cmulti _ -> pdim
1329 state.pdims <- pdim :: state.pdims;
1330 state.uioh#infochanged Pdim
1332 | "o", args ->
1333 let (l, n, t, h, pos) =
1334 scan args "%u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos)
1336 let s = String.sub args pos (String.length args - pos) in
1337 addoutline (s, l, Oanchor (n, float t /. float h, 0.0))
1339 | "ou", args ->
1340 let (l, len, pos) = scan args "%u %u %n" (fun l len pos -> l, len, pos) in
1341 let s = String.sub args pos len in
1342 let pos2 = pos + len + 1 in
1343 let uri = String.sub args pos2 (String.length args - pos2) in
1344 addoutline (s, l, Ouri uri)
1346 | "on", args ->
1347 let (l, pos) = scan args "%u %n" (fun l pos -> l, pos) in
1348 let s = String.sub args pos (String.length args - pos) in
1349 addoutline (s, l, Onone)
1351 | "a", args ->
1352 let (n, l, t) = scan args "%u %d %d" (fun n l t -> n, l, t) in
1353 state.reprf <- (fun () -> gotopagexy n (float l) (float t))
1355 | "info", args ->
1356 let c, v = splitatchar args '\t' in
1357 let s =
1358 if nonemptystr v
1359 then
1360 if c = "Title"
1361 then (
1362 conf.title <- v;
1363 if not !ignoredoctitlte then Wsi.settitle v;
1364 args
1366 else
1367 if let len = String.length c in
1368 len > 6 && ((String.sub c (len-4) 4) = "date")
1369 then (
1370 if String.length v >= 7 && v.[0] = 'D' && v.[1] = ':'
1371 then
1372 let b = Buffer.create 10 in
1373 Printf.bprintf b "%s\t" c;
1374 let sub p l c =
1376 Buffer.add_substring b v p l;
1377 Buffer.add_char b c;
1378 with exn -> Buffer.add_string b @@ exntos exn
1380 sub 2 4 '/';
1381 sub 6 2 '/';
1382 sub 8 2 ' ';
1383 sub 10 2 ':';
1384 sub 12 2 ':';
1385 sub 14 2 ' ';
1386 Printf.bprintf b "[%s]" v;
1387 Buffer.contents b
1388 else args
1390 else args
1391 else args
1393 state.docinfo <- (1, s) :: state.docinfo
1395 | "infoend", "" ->
1396 state.docinfo <- List.rev state.docinfo;
1397 state.uioh#infochanged Docinfo
1399 | "pass", args ->
1400 if args = "fail"
1401 then Wsi.settitle "Wrong password";
1402 let password = getpassword () in
1403 if emptystr password
1404 then error "document is password protected"
1405 else opendoc state.path password
1407 | _ -> error "unknown cmd `%S'" cmds
1410 let onhist cb =
1411 let rc = cb.rc in
1412 let action = function
1413 | HCprev -> cbget cb ~-1
1414 | HCnext -> cbget cb 1
1415 | HCfirst -> cbget cb ~-(cb.rc)
1416 | HClast -> cbget cb (cb.len - 1 - cb.rc)
1417 and cancel () = cb.rc <- rc
1418 in (action, cancel)
1421 let search pattern forward =
1422 match conf.columns with
1423 | Csplit _ -> impmsg "searching does not work properly in split columns mode"
1424 | Csingle _ | Cmulti _ ->
1425 if nonemptystr pattern
1426 then
1427 let pn, py =
1428 match state.layout with
1429 | [] -> 0, 0
1430 | l :: _ -> l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
1432 wcmd "search %d %d %d %d,%s\000"
1433 (btod conf.icase) pn py (btod forward) pattern;
1436 let intentry text key =
1437 let text =
1438 if emptystr text && key = Keys.Ascii '-'
1439 then addchar text '-'
1440 else
1441 match [@warning "-4"] key with
1442 | Keys.Ascii ('0'..'9' as c) -> addchar text c
1443 | _ ->
1444 state.text <- "invalid key";
1445 text
1447 TEcont text
1450 let linknact f s =
1451 if nonemptystr s
1452 then
1453 let n =
1454 let l = String.length s in
1455 let rec loop pos n =
1456 if pos = l
1457 then n
1458 else
1459 let m = Char.code s.[pos] - (if pos = 0 && l > 1 then 96 else 97) in
1460 loop (pos+1) (n*26 + m)
1461 in loop 0 0
1463 let rec loop n = function
1464 | [] -> ()
1465 | l :: rest ->
1466 match getopaque l.pageno with
1467 | None -> loop n rest
1468 | Some opaque ->
1469 let m = Ffi.getlinkcount opaque in
1470 if n < m
1471 then
1472 let under = Ffi.getlink opaque n in
1473 f under
1474 else loop (n-m) rest
1476 loop n state.layout;
1479 let linknentry text key = match [@warning "-4"] key with
1480 | Keys.Ascii ('a' .. 'z' as c) ->
1481 let text = addchar text c in
1482 linknact (fun under -> state.text <- undertext under) text;
1483 TEcont text
1484 | _ ->
1485 state.text <- Printf.sprintf "invalid key %s" @@ Keys.to_string key;
1486 TEcont text
1489 let textentry text key = match [@warning "-4"] key with
1490 | Keys.Ascii c -> TEcont (addchar text c)
1491 | Keys.Code c -> TEcont (text ^ toutf8 c)
1492 | _ -> TEcont text
1495 let reqlayout angle fitmodel =
1496 if nogeomcmds state.geomcmds
1497 then state.anchor <- getanchor ();
1498 conf.angle <- angle mod 360;
1499 if conf.angle != 0
1500 then (
1501 match state.mode with
1502 | LinkNav _ -> state.mode <- View
1503 | Birdseye _ | Textentry _ | View -> ()
1505 conf.fitmodel <- fitmodel;
1506 invalidate "reqlayout"
1507 (fun () -> wcmd "reqlayout %d %d %d"
1508 conf.angle (FMTE.to_int conf.fitmodel) (stateh state.winh));
1511 let settrim trimmargins trimfuzz =
1512 if nogeomcmds state.geomcmds
1513 then state.anchor <- getanchor ();
1514 conf.trimmargins <- trimmargins;
1515 conf.trimfuzz <- trimfuzz;
1516 let x0, y0, x1, y1 = trimfuzz in
1517 invalidate "settrim"
1518 (fun () -> wcmd "settrim %d %d %d %d %d"
1519 (btod conf.trimmargins) x0 y0 x1 y1);
1520 flushpages ();
1523 let setzoom zoom =
1524 let zoom = max 0.0001 zoom in
1525 if zoom <> conf.zoom
1526 then (
1527 state.prevzoom <- (conf.zoom, state.x);
1528 conf.zoom <- zoom;
1529 reshape state.winw state.winh;
1530 state.text <- Printf.sprintf "zoom is now %-5.2f" (zoom *. 100.0);
1534 let pivotzoom ?(vw=min state.w state.winw)
1535 ?(vh=min (state.maxy-state.y) state.winh)
1536 ?(x=vw/2) ?(y=vh/2) zoom =
1537 let w = float state.w /. zoom in
1538 let hw = w /. 2.0 in
1539 let ratio = float vh /. float vw in
1540 let hh = hw *. ratio in
1541 let x0 = float x -. hw
1542 and y0 = float y -. hh in
1543 gotoxy (state.x - truncate x0) (state.y + truncate y0);
1544 setzoom zoom;
1547 let pivotzoom ?vw ?vh ?x ?y zoom =
1548 if nogeomcmds state.geomcmds
1549 then
1550 if zoom > 1.0
1551 then pivotzoom ?vw ?vh ?x ?y zoom
1552 else setzoom zoom
1555 let setcolumns mode columns coverA coverB =
1556 state.prevcolumns <- Some (conf.columns, conf.zoom);
1557 if columns < 0
1558 then (
1559 if isbirdseye mode
1560 then impmsg "split mode doesn't work in bird's eye"
1561 else (
1562 conf.columns <- Csplit (-columns, E.a);
1563 state.x <- 0;
1564 conf.zoom <- 1.0;
1567 else (
1568 if columns < 2
1569 then (
1570 conf.columns <- Csingle E.a;
1571 state.x <- 0;
1572 setzoom 1.0;
1574 else (
1575 conf.columns <- Cmulti ((columns, coverA, coverB), E.a);
1576 conf.zoom <- 1.0;
1579 reshape state.winw state.winh;
1582 let resetmstate () =
1583 state.mstate <- Mnone;
1584 Wsi.setcursor Wsi.CURSOR_INHERIT;
1587 let enterbirdseye () =
1588 let zoom = float conf.thumbw /. float state.winw in
1589 let birdseyepageno =
1590 let cy = state.winh / 2 in
1591 let fold = function
1592 | [] -> 0
1593 | l :: rest ->
1594 let rec fold best = function
1595 | [] -> best.pageno
1596 | l :: rest ->
1597 let d = cy - (l.pagedispy + l.pagevh/2)
1598 and dbest = cy - (best.pagedispy + best.pagevh/2) in
1599 if abs d < abs dbest
1600 then fold l rest
1601 else best.pageno
1602 in fold l rest
1604 fold state.layout
1606 state.mode <-
1607 Birdseye (
1608 { conf with zoom = conf.zoom },
1609 state.x, birdseyepageno, -1, getanchor ()
1611 resetmstate ();
1612 conf.zoom <- zoom;
1613 conf.presentation <- false;
1614 conf.interpagespace <- 10;
1615 conf.hlinks <- false;
1616 conf.fitmodel <- FitPage;
1617 state.x <- 0;
1618 conf.columns <- (
1619 match conf.beyecolumns with
1620 | Some c ->
1621 conf.zoom <- 1.0;
1622 Cmulti ((c, 0, 0), E.a)
1623 | None -> Csingle E.a
1625 if conf.verbose
1626 then state.text <- Printf.sprintf "birds eye mode on (zoom %3.1f%%)"
1627 (100.0*.zoom)
1628 else state.text <- E.s;
1629 reshape state.winw state.winh;
1632 let leavebirdseye (c, leftx, pageno, _, anchor) goback =
1633 state.mode <- View;
1634 conf.zoom <- c.zoom;
1635 conf.presentation <- c.presentation;
1636 conf.interpagespace <- c.interpagespace;
1637 conf.hlinks <- c.hlinks;
1638 conf.fitmodel <- c.fitmodel;
1639 conf.beyecolumns <- (
1640 match conf.columns with
1641 | Cmulti ((c, _, _), _) -> Some c
1642 | Csingle _ -> None
1643 | Csplit _ -> error "leaving bird's eye split mode"
1645 conf.columns <- (
1646 match c.columns with
1647 | Cmulti (c, _) -> Cmulti (c, E.a)
1648 | Csingle _ -> Csingle E.a
1649 | Csplit (c, _) -> Csplit (c, E.a)
1651 if conf.verbose
1652 then state.text <- Printf.sprintf "birds eye mode off (zoom %3.1f%%)"
1653 (100.0*.conf.zoom);
1654 reshape state.winw state.winh;
1655 state.anchor <- if goback then anchor else (pageno, 0.0, 1.0);
1656 state.x <- leftx;
1659 let togglebirdseye () =
1660 match state.mode with
1661 | Birdseye vals -> leavebirdseye vals true
1662 | View -> enterbirdseye ()
1663 | Textentry _ | LinkNav _ -> ()
1666 let upbirdseye incr (conf, leftx, pageno, hooverpageno, anchor) =
1667 let pageno = max 0 (pageno - incr) in
1668 let rec loop = function
1669 | [] -> gotopage1 pageno 0
1670 | l :: _ when l.pageno = pageno ->
1671 if l.pagedispy >= 0 && l.pagey = 0
1672 then postRedisplay "upbirdseye"
1673 else gotopage1 pageno 0
1674 | _ :: rest -> loop rest
1676 loop state.layout;
1677 state.text <- E.s;
1678 state.mode <- Birdseye (conf, leftx, pageno, hooverpageno, anchor)
1681 let downbirdseye incr (conf, leftx, pageno, hooverpageno, anchor) =
1682 let pageno = min (state.pagecount - 1) (pageno + incr) in
1683 state.mode <- Birdseye (conf, leftx, pageno, hooverpageno, anchor);
1684 let rec loop = function
1685 | [] ->
1686 let y, h = getpageyh pageno in
1687 let dy = (y - state.y) - (state.winh - h - conf.interpagespace) in
1688 gotoxy state.x (clamp dy)
1689 | l :: _ when l.pageno = pageno ->
1690 if l.pagevh != l.pageh
1691 then gotoxy state.x (clamp (l.pageh - l.pagevh + conf.interpagespace))
1692 else postRedisplay "downbirdseye"
1693 | _ :: rest -> loop rest
1695 loop state.layout;
1696 state.text <- E.s;
1699 let optentry mode _ key =
1700 let btos b = if b then "on" else "off" in
1701 match [@warning "-4"] key with
1702 | Keys.Ascii 'C' ->
1703 let ondone s =
1705 let n, a, b = multicolumns_of_string s in
1706 setcolumns mode n a b;
1707 with exn ->
1708 state.text <- Printf.sprintf "bad columns `%s': %s" s @@ exntos exn
1710 TEswitch ("columns: ", E.s, None, textentry, ondone, true)
1712 | Keys.Ascii 'Z' ->
1713 let ondone s =
1715 let zoom = float (int_of_string s) /. 100.0 in
1716 pivotzoom zoom
1717 with exn ->
1718 state.text <- Printf.sprintf "bad integer `%s': %s" s @@ exntos exn
1720 TEswitch ("zoom: ", E.s, None, intentry, ondone, true)
1722 | Keys.Ascii 'i' ->
1723 conf.icase <- not conf.icase;
1724 TEdone ("case insensitive search " ^ (btos conf.icase))
1726 | Keys.Ascii 'v' ->
1727 conf.verbose <- not conf.verbose;
1728 TEdone ("verbose " ^ (btos conf.verbose))
1730 | Keys.Ascii 'd' ->
1731 conf.debug <- not conf.debug;
1732 TEdone ("debug " ^ (btos conf.debug))
1734 | Keys.Ascii 'f' ->
1735 conf.underinfo <- not conf.underinfo;
1736 TEdone ("underinfo " ^ btos conf.underinfo)
1738 | Keys.Ascii 'T' ->
1739 settrim (not conf.trimmargins) conf.trimfuzz;
1740 TEdone ("trim margins " ^ btos conf.trimmargins)
1742 | Keys.Ascii 'I' ->
1743 conf.invert <- not conf.invert;
1744 TEdone ("invert colors " ^ btos conf.invert)
1746 | Keys.Ascii 'x' ->
1747 let ondone s =
1748 cbput state.hists.sel s;
1749 conf.selcmd <- s;
1751 TEswitch ("selection command: ", E.s, Some (onhist state.hists.sel),
1752 textentry, ondone, true)
1754 | Keys.Ascii 'M' ->
1755 if conf.pax == None
1756 then conf.pax <- Some 0.0
1757 else conf.pax <- None;
1758 TEdone ("PAX " ^ btos (conf.pax != None))
1760 | (Keys.Ascii c) ->
1761 state.text <- Printf.sprintf "bad option %d `%c'" (Char.code c) c;
1762 TEstop
1764 | _ -> TEcont state.text
1767 let adderrmsg src msg =
1768 Buffer.add_string state.errmsgs msg;
1769 state.newerrmsgs <- true;
1770 postRedisplay src
1773 let adderrfmt src fmt = Format.ksprintf (fun s -> adderrmsg src s) fmt;;
1775 class outlinelistview ~zebra ~source =
1776 let settext autonarrow s =
1777 if autonarrow
1778 then
1779 let ss = source#statestr in
1780 state.text <- if emptystr ss
1781 then "[" ^ s ^ "]"
1782 else "{" ^ ss ^ "} [" ^ s ^ "]"
1783 else state.text <- s
1785 object (self)
1786 inherit listview
1787 ~zebra
1788 ~helpmode:false
1789 ~source:(source :> lvsource)
1790 ~trusted:false
1791 ~modehash:(findkeyhash conf "outline")
1792 as super
1794 val m_autonarrow = false
1796 method! key key mask =
1797 let maxrows =
1798 if emptystr state.text
1799 then fstate.maxrows
1800 else fstate.maxrows - 2
1802 let calcfirst first active =
1803 if active > first
1804 then
1805 let rows = active - first in
1806 if rows > maxrows then active - maxrows else first
1807 else active
1809 let navigate incr =
1810 let active = m_active + incr in
1811 let active = bound active 0 (source#getitemcount - 1) in
1812 let first = calcfirst m_first active in
1813 postRedisplay "outline navigate";
1814 coe {< m_active = active; m_first = first >}
1816 let navscroll first =
1817 let active =
1818 let dist = m_active - first in
1819 if dist < 0
1820 then first
1821 else (
1822 if dist < maxrows
1823 then m_active
1824 else first + maxrows
1827 postRedisplay "outline navscroll";
1828 coe {< m_first = first; m_active = active >}
1830 let ctrl = Wsi.withctrl mask in
1831 let open Keys in
1832 match Wsi.kc2kt key with
1833 | Ascii 'a' when ctrl ->
1834 let text =
1835 if m_autonarrow
1836 then (
1837 source#denarrow;
1840 else (
1841 let pattern = source#renarrow in
1842 if nonemptystr m_qsearch
1843 then (source#narrow m_qsearch; m_qsearch)
1844 else pattern
1847 settext (not m_autonarrow) text;
1848 postRedisplay "toggle auto narrowing";
1849 coe {< m_first = 0; m_active = 0; m_autonarrow = not m_autonarrow >}
1851 | Ascii '/' when emptystr m_qsearch && not m_autonarrow ->
1852 settext true E.s;
1853 postRedisplay "toggle auto narrowing";
1854 coe {< m_first = 0; m_active = 0; m_autonarrow = true >}
1856 | Ascii 'n' when ctrl ->
1857 source#narrow m_qsearch;
1858 if not m_autonarrow
1859 then source#add_narrow_pattern m_qsearch;
1860 postRedisplay "outline ctrl-n";
1861 coe {< m_first = 0; m_active = 0 >}
1863 | Ascii 'S' when ctrl ->
1864 let active = source#calcactive (getanchor ()) in
1865 let first = firstof m_first active in
1866 postRedisplay "outline ctrl-s";
1867 coe {< m_first = first; m_active = active >}
1869 | Ascii 'u' when ctrl ->
1870 postRedisplay "outline ctrl-u";
1871 if m_autonarrow && nonemptystr m_qsearch
1872 then (
1873 ignore (source#renarrow);
1874 settext m_autonarrow E.s;
1875 coe {< m_first = 0; m_active = 0; m_qsearch = E.s >}
1877 else (
1878 source#del_narrow_pattern;
1879 let pattern = source#renarrow in
1880 let text =
1881 if emptystr pattern then E.s else "Narrowed to " ^ pattern
1883 settext m_autonarrow text;
1884 coe {< m_first = 0; m_active = 0; m_qsearch = E.s >}
1887 | Ascii 'l' when ctrl ->
1888 let first = max 0 (m_active - (fstate.maxrows / 2)) in
1889 postRedisplay "outline ctrl-l";
1890 coe {< m_first = first >}
1892 | Ascii '\t' when m_autonarrow ->
1893 if nonemptystr m_qsearch
1894 then (
1895 postRedisplay "outline list view tab";
1896 source#add_narrow_pattern m_qsearch;
1897 settext true E.s;
1898 coe {< m_qsearch = E.s >}
1900 else coe self
1902 | Escape when m_autonarrow ->
1903 if nonemptystr m_qsearch
1904 then source#add_narrow_pattern m_qsearch;
1905 super#key key mask
1907 | Enter when m_autonarrow ->
1908 if nonemptystr m_qsearch
1909 then source#add_narrow_pattern m_qsearch;
1910 super#key key mask
1912 | (Ascii _ | Code _) when m_autonarrow ->
1913 let pattern = m_qsearch ^ toutf8 key in
1914 postRedisplay "outlinelistview autonarrow add";
1915 source#narrow pattern;
1916 settext true pattern;
1917 coe {< m_first = 0; m_active = 0; m_qsearch = pattern >}
1919 | Backspace when m_autonarrow ->
1920 if emptystr m_qsearch
1921 then coe self
1922 else
1923 let pattern = withoutlastutf8 m_qsearch in
1924 postRedisplay "outlinelistview autonarrow backspace";
1925 ignore (source#renarrow);
1926 source#narrow pattern;
1927 settext true pattern;
1928 coe {< m_first = 0; m_active = 0; m_qsearch = pattern >}
1930 | Up when ctrl -> navscroll (max 0 (m_first - 1))
1932 | Down when ctrl ->
1933 navscroll (min (source#getitemcount - 1) (m_first + 1))
1935 | Up -> navigate ~-1
1936 | Down -> navigate 1
1937 | Prior -> navigate ~-(fstate.maxrows)
1938 | Next -> navigate fstate.maxrows
1940 | Right ->
1941 let o =
1942 if ctrl
1943 then (
1944 postRedisplay "outline ctrl right";
1945 {< m_pan = m_pan + 1 >}
1947 else (
1948 if Wsi.withshift mask
1949 then self#nextcurlevel 1
1950 else self#updownlevel 1
1953 coe o
1955 | Left ->
1956 let o =
1957 if ctrl
1958 then (
1959 postRedisplay "outline ctrl left";
1960 {< m_pan = m_pan - 1 >}
1962 else (
1963 if Wsi.withshift mask
1964 then self#nextcurlevel ~-1
1965 else self#updownlevel ~-1
1968 coe o
1970 | Home ->
1971 postRedisplay "outline home";
1972 coe {< m_first = 0; m_active = 0 >}
1974 | End ->
1975 let active = source#getitemcount - 1 in
1976 let first = max 0 (active - fstate.maxrows) in
1977 postRedisplay "outline end";
1978 coe {< m_active = active; m_first = first >}
1980 | Delete|Escape|Insert|Enter|Ascii _|Code _|Ctrl _|Backspace|Fn _ ->
1981 super#key key mask
1982 end;;
1984 let genhistoutlines () =
1985 Config.gethist ()
1986 |> List.sort (fun (_, c1, _, _, _, _) (_, c2, _, _, _, _) ->
1987 compare c2.lastvisit c1.lastvisit)
1988 |> List.map (fun ((path, c, _, _, _, origin) as hist) ->
1989 let path = if nonemptystr origin then origin else path in
1990 let base = mbtoutf8 @@ Filename.basename path in
1991 (base ^ "\000" ^ c.title, 1, Ohistory hist)
1995 let gotohist (path, c, bookmarks, x, anchor, origin) =
1996 Config.save leavebirdseye;
1997 state.anchor <- anchor;
1998 state.bookmarks <- bookmarks;
1999 state.origin <- origin;
2000 state.x <- x;
2001 setconf conf c;
2002 Ffi.settrimcachepath conf.trimcachepath;
2003 let x0, y0, x1, y1 = conf.trimfuzz in
2004 wcmd "trimset %d %d %d %d %d" (btod conf.trimmargins) x0 y0 x1 y1;
2005 Wsi.reshape c.cwinw c.cwinh;
2006 opendoc path origin;
2007 setzoom c.zoom;
2010 let setcheckers enabled =
2011 match !checkerstexid with
2012 | None -> if enabled then checkerstexid := Some (makecheckers ())
2013 | Some id ->
2014 if not enabled
2015 then (
2016 GlTex.delete_texture id;
2017 checkerstexid := None;
2021 let describe_layout layout =
2022 let d =
2023 match layout with
2024 | [] -> "Page 0"
2025 | l :: [] -> Printf.sprintf "Page %d" (l.pageno+1)
2026 | l :: rest ->
2027 let rangestr a b =
2028 if a.pageno = b.pageno then Printf.sprintf "%d" (a.pageno+1)
2029 else Printf.sprintf "%d%s%d" (a.pageno+1)
2030 (if a.pageno+1 = b.pageno then ", " else Utf8syms.ellipsis)
2031 (b.pageno+1)
2033 let rec fold s la lb = function
2034 | [] -> Printf.sprintf "%s %s" s (rangestr la lb)
2035 | l :: rest when l.pageno = succ lb.pageno -> fold s la l rest
2036 | l :: rest -> fold (s ^ " " ^ rangestr la lb ^ ",") l l rest
2038 fold "Pages" l l rest
2040 let percent =
2041 let maxy = maxy () in
2042 if maxy <= 0
2043 then 100.
2044 else 100. *. (float state.y /. float maxy)
2046 Printf.sprintf "%s of %d [%.2f%%]" d state.pagecount percent
2049 let setpresentationmode v =
2050 let n = page_of_y state.y in
2051 state.anchor <- (n, 0.0, 1.0);
2052 conf.presentation <- v;
2053 if conf.fitmodel = FitPage
2054 then reqlayout conf.angle conf.fitmodel;
2055 represent ();
2058 let enterinfomode =
2059 let btos b = if b then Utf8syms.radical else E.s in
2060 let showextended = ref false in
2061 let showcolors = ref false in
2062 let leave mode _ = state.mode <- mode in
2063 let src =
2064 (object
2065 val mutable m_l = []
2066 val mutable m_a = E.a
2067 val mutable m_prev_uioh = nouioh
2068 val mutable m_prev_mode = View
2070 inherit lvsourcebase
2072 method reset prev_mode prev_uioh =
2073 m_a <- Array.of_list (List.rev m_l);
2074 m_l <- [];
2075 m_prev_mode <- prev_mode;
2076 m_prev_uioh <- prev_uioh;
2078 method int name get set =
2079 m_l <-
2080 (name, `int get, 1,
2081 Action (
2082 fun u ->
2083 let ondone s =
2084 try set (int_of_string s)
2085 with exn ->
2086 state.text <- Printf.sprintf "bad integer `%s': %s"
2087 s @@ exntos exn
2089 state.text <- E.s;
2090 let te = name ^ ": ", E.s, None, intentry, ondone, true in
2091 state.mode <- Textentry (te, leave m_prev_mode);
2093 )) :: m_l
2095 method int_with_suffix name get set =
2096 m_l <-
2097 (name, `intws get, 1,
2098 Action (
2099 fun u ->
2100 let ondone s =
2101 try set (int_of_string_with_suffix s)
2102 with exn ->
2103 state.text <- Printf.sprintf "bad integer `%s': %s"
2104 s @@ exntos exn
2106 state.text <- E.s;
2107 let te =
2108 name ^ ": ", E.s, None, intentry_with_suffix, ondone, true
2110 state.mode <- Textentry (te, leave m_prev_mode);
2112 )) :: m_l
2114 method bool ?(offset=1) ?(btos=btos) name get set =
2115 m_l <-
2116 (name, `bool (btos, get), offset, Action (
2117 fun u ->
2118 let v = get () in
2119 set (not v);
2121 )) :: m_l
2123 method color name get set =
2124 m_l <-
2125 (name, `color get, 1,
2126 Action (
2127 fun u ->
2128 let invalid = (nan, nan, nan) in
2129 let ondone s =
2130 let c =
2131 try color_of_string s
2132 with exn ->
2133 state.text <- Printf.sprintf "bad color `%s': %s"
2134 s @@ exntos exn;
2135 invalid
2137 if c <> invalid
2138 then set c;
2140 let te = name ^ ": ", E.s, None, textentry, ondone, true in
2141 state.text <- color_to_string (get ());
2142 state.mode <- Textentry (te, leave m_prev_mode);
2144 )) :: m_l
2146 method string name get set =
2147 m_l <-
2148 (name, `string get, 1,
2149 Action (
2150 fun u ->
2151 let ondone s = set s in
2152 let te = name ^ ": ", E.s, None, textentry, ondone, true in
2153 state.mode <- Textentry (te, leave m_prev_mode);
2155 )) :: m_l
2157 method colorspace name get set =
2158 m_l <-
2159 (name, `string get, 1,
2160 Action (
2161 fun _ ->
2162 let source =
2163 (object
2164 inherit lvsourcebase
2166 initializer
2167 m_active <- CSTE.to_int conf.colorspace;
2168 m_first <- 0;
2170 method getitemcount =
2171 Array.length CSTE.names
2172 method getitem n =
2173 (CSTE.names.(n), 0)
2174 method exit ~uioh ~cancel ~active ~first ~pan =
2175 ignore (uioh, first, pan);
2176 if not cancel then set active;
2177 None
2178 method hasaction _ = true
2179 end)
2181 state.text <- E.s;
2182 let modehash = findkeyhash conf "info" in
2183 coe (new listview ~zebra:false ~helpmode:false
2184 ~source ~trusted:true ~modehash)
2185 )) :: m_l
2187 method paxmark name get set =
2188 m_l <-
2189 (name, `string get, 1,
2190 Action (
2191 fun _ ->
2192 let source =
2193 (object
2194 inherit lvsourcebase
2196 initializer
2197 m_active <- MTE.to_int conf.paxmark;
2198 m_first <- 0;
2200 method getitemcount = Array.length MTE.names
2201 method getitem n = (MTE.names.(n), 0)
2202 method exit ~uioh ~cancel ~active ~first ~pan =
2203 ignore (uioh, first, pan);
2204 if not cancel then set active;
2205 None
2206 method hasaction _ = true
2207 end)
2209 state.text <- E.s;
2210 let modehash = findkeyhash conf "info" in
2211 coe (new listview ~zebra:false ~helpmode:false
2212 ~source ~trusted:true ~modehash)
2213 )) :: m_l
2215 method fitmodel name get set =
2216 m_l <-
2217 (name, `string get, 1,
2218 Action (
2219 fun _ ->
2220 let source =
2221 (object
2222 inherit lvsourcebase
2224 initializer
2225 m_active <- FMTE.to_int conf.fitmodel;
2226 m_first <- 0;
2228 method getitemcount = Array.length FMTE.names
2229 method getitem n = (FMTE.names.(n), 0)
2230 method exit ~uioh ~cancel ~active ~first ~pan =
2231 ignore (uioh, first, pan);
2232 if not cancel then set active;
2233 None
2234 method hasaction _ = true
2235 end)
2237 state.text <- E.s;
2238 let modehash = findkeyhash conf "info" in
2239 coe (new listview ~zebra:false ~helpmode:false
2240 ~source ~trusted:true ~modehash)
2241 )) :: m_l
2243 method caption s offset =
2244 m_l <- (s, `empty, offset, Noaction) :: m_l
2246 method caption2 s f offset =
2247 m_l <- (s, `string f, offset, Noaction) :: m_l
2249 method getitemcount = Array.length m_a
2251 method getitem n =
2252 let tostr = function
2253 | `int f -> string_of_int (f ())
2254 | `intws f -> string_with_suffix_of_int (f ())
2255 | `string f -> f ()
2256 | `color f -> color_to_string (f ())
2257 | `bool (btos, f) -> btos (f ())
2258 | `empty -> E.s
2260 let name, t, offset, _ = m_a.(n) in
2261 ((let s = tostr t in
2262 if nonemptystr s
2263 then Printf.sprintf "%s\t%s" name s
2264 else name),
2265 offset)
2267 method exit ~uioh ~cancel ~active ~first ~pan =
2268 let uiohopt =
2269 if not cancel
2270 then (
2271 let uioh =
2272 match m_a.(active) with
2273 | _, _, _, Action f -> f uioh
2274 | _, _, _, Noaction -> uioh
2276 Some uioh
2278 else None
2280 m_active <- active;
2281 m_first <- first;
2282 m_pan <- pan;
2283 uiohopt
2285 method hasaction n =
2286 match m_a.(n) with
2287 | _, _, _, Action _ -> true
2288 | _, _, _, Noaction -> false
2290 initializer m_active <- 1
2291 end)
2293 let rec fillsrc prevmode prevuioh =
2294 let sep () = src#caption E.s 0 in
2295 let colorp name get set =
2296 src#string name
2297 (fun () -> color_to_string (get ()))
2298 (fun v ->
2299 try set @@ color_of_string v
2300 with exn ->
2301 state.text <-
2302 Printf.sprintf "bad color `%s': %s" v @@ exntos exn
2305 let rgba name get set =
2306 src#string name
2307 (fun () -> get () |> rgba_to_string)
2308 (fun v ->
2309 try set @@ rgba_of_string v
2310 with exn ->
2311 state.text <-
2312 Printf.sprintf "bad color `%s': %s" v @@ exntos exn
2315 let oldmode = state.mode in
2316 let birdseye = isbirdseye state.mode in
2318 src#caption (if birdseye then "Setup (Bird's eye)" else "Setup") 0;
2320 src#bool "presentation mode"
2321 (fun () -> conf.presentation)
2322 (fun v -> setpresentationmode v);
2324 src#bool "ignore case in searches"
2325 (fun () -> conf.icase)
2326 (fun v -> conf.icase <- v);
2328 src#bool "preload"
2329 (fun () -> conf.preload)
2330 (fun v -> conf.preload <- v);
2332 src#bool "highlight links"
2333 (fun () -> conf.hlinks)
2334 (fun v -> conf.hlinks <- v);
2336 src#bool "under info"
2337 (fun () -> conf.underinfo)
2338 (fun v -> conf.underinfo <- v);
2340 src#fitmodel "fit model"
2341 (fun () -> FMTE.to_string conf.fitmodel)
2342 (fun v -> reqlayout conf.angle (FMTE.of_int v));
2344 src#bool "trim margins"
2345 (fun () -> conf.trimmargins)
2346 (fun v -> settrim v conf.trimfuzz; fillsrc prevmode prevuioh);
2348 sep ();
2349 src#int "inter-page space"
2350 (fun () -> conf.interpagespace)
2351 (fun n ->
2352 conf.interpagespace <- n;
2353 docolumns conf.columns;
2354 let pageno, py =
2355 match state.layout with
2356 | [] -> 0, 0
2357 | l :: _ -> l.pageno, l.pagey
2359 state.maxy <- calcheight ();
2360 let y = getpagey pageno in
2361 gotoxy state.x (y + py)
2364 src#int "page bias"
2365 (fun () -> conf.pagebias)
2366 (fun v -> conf.pagebias <- v);
2368 src#int "scroll step"
2369 (fun () -> conf.scrollstep)
2370 (fun n -> conf.scrollstep <- n);
2372 src#int "horizontal scroll step"
2373 (fun () -> conf.hscrollstep)
2374 (fun v -> conf.hscrollstep <- v);
2376 src#int "auto scroll step"
2377 (fun () ->
2378 match state.autoscroll with
2379 | Some step -> step
2380 | _ -> conf.autoscrollstep)
2381 (fun n ->
2382 let n = boundastep state.winh n in
2383 if state.autoscroll <> None
2384 then state.autoscroll <- Some n;
2385 conf.autoscrollstep <- n);
2387 src#int "zoom"
2388 (fun () -> truncate (conf.zoom *. 100.))
2389 (fun v -> pivotzoom ((float v) /. 100.));
2391 src#int "rotation"
2392 (fun () -> conf.angle)
2393 (fun v -> reqlayout v conf.fitmodel);
2395 src#int "scroll bar width"
2396 (fun () -> conf.scrollbw)
2397 (fun v ->
2398 conf.scrollbw <- v;
2399 reshape state.winw state.winh;
2402 src#int "scroll handle height"
2403 (fun () -> conf.scrollh)
2404 (fun v -> conf.scrollh <- v;);
2406 src#int "thumbnail width"
2407 (fun () -> conf.thumbw)
2408 (fun v ->
2409 conf.thumbw <- min 4096 v;
2410 match oldmode with
2411 | Birdseye beye ->
2412 leavebirdseye beye false;
2413 enterbirdseye ()
2414 | Textentry _
2415 | View
2416 | LinkNav _ -> ()
2419 let mode = state.mode in
2420 src#string "columns"
2421 (fun () ->
2422 match conf.columns with
2423 | Csingle _ -> "1"
2424 | Cmulti (multi, _) -> multicolumns_to_string multi
2425 | Csplit (count, _) -> "-" ^ string_of_int count
2427 (fun v ->
2428 let n, a, b = multicolumns_of_string v in
2429 setcolumns mode n a b);
2431 sep ();
2432 src#caption "Pixmap cache" 0;
2433 src#int_with_suffix "size (advisory)"
2434 (fun () -> conf.memlimit)
2435 (fun v -> conf.memlimit <- v);
2437 src#caption2 "used"
2438 (fun () ->
2439 Printf.sprintf "%s bytes, %d tiles"
2440 (string_with_suffix_of_int state.memused)
2441 (Hashtbl.length state.tilemap)) 1;
2443 sep ();
2444 src#caption "Layout" 0;
2445 src#caption2 "Dimension"
2446 (fun () -> Printf.sprintf "%dx%d (virtual %dx%d)"
2447 state.winw state.winh
2448 state.w state.maxy)
2450 if conf.debug
2451 then src#caption2 "Position" (fun () ->
2452 Printf.sprintf "%dx%d" state.x state.y
2454 else src#caption2 "Position" (fun () -> describe_layout state.layout) 1;
2456 sep ();
2457 src#bool ~offset:0 ~btos:(fun v -> if v then "(on)" else "(off)")
2458 "Save these parameters as global defaults at exit"
2459 (fun () -> conf.bedefault)
2460 (fun v -> conf.bedefault <- v);
2462 sep ();
2463 let btos b = Utf8syms.(if b then lguillemet else rguillemet) in
2464 src#bool ~offset:0 ~btos "Extended parameters"
2465 (fun () -> !showextended)
2466 (fun v -> showextended := v; fillsrc prevmode prevuioh);
2467 if !showextended
2468 then (
2469 src#bool "checkers"
2470 (fun () -> conf.checkers)
2471 (fun v -> conf.checkers <- v; setcheckers v);
2472 src#bool "update cursor"
2473 (fun () -> conf.updatecurs)
2474 (fun v -> conf.updatecurs <- v);
2475 src#bool "scroll-bar on the left"
2476 (fun () -> conf.leftscroll)
2477 (fun v -> conf.leftscroll <- v);
2478 src#bool "verbose"
2479 (fun () -> conf.verbose)
2480 (fun v -> conf.verbose <- v);
2481 src#bool "invert colors"
2482 (fun () -> conf.invert)
2483 (fun v -> conf.invert <- v);
2484 src#bool "max fit"
2485 (fun () -> conf.maxhfit)
2486 (fun v -> conf.maxhfit <- v);
2487 src#bool "pax mode"
2488 (fun () -> conf.pax != None)
2489 (fun v ->
2490 if v
2491 then conf.pax <- Some (now ())
2492 else conf.pax <- None);
2493 src#string "uri launcher"
2494 (fun () -> conf.urilauncher)
2495 (fun v -> conf.urilauncher <- v);
2496 src#string "path launcher"
2497 (fun () -> conf.pathlauncher)
2498 (fun v -> conf.pathlauncher <- v);
2499 src#string "tile size"
2500 (fun () -> Printf.sprintf "%dx%d" conf.tilew conf.tileh)
2501 (fun v ->
2503 let w, h = Scanf.sscanf v "%dx%d" (fun w h -> w, h) in
2504 conf.tilew <- max 64 w;
2505 conf.tileh <- max 64 h;
2506 flushtiles ();
2507 with exn ->
2508 state.text <- Printf.sprintf "bad tile size `%s': %s"
2509 v @@ exntos exn
2511 src#int "texture count"
2512 (fun () -> conf.texcount)
2513 (fun v ->
2514 if Ffi.realloctexts v
2515 then conf.texcount <- v
2516 else impmsg "failed to set texture count please retry later"
2518 src#int "slice height"
2519 (fun () -> conf.sliceheight)
2520 (fun v ->
2521 conf.sliceheight <- v;
2522 wcmd "sliceh %d" conf.sliceheight;
2524 src#int "anti-aliasing level"
2525 (fun () -> conf.aalevel)
2526 (fun v ->
2527 conf.aalevel <- bound v 0 8;
2528 state.anchor <- getanchor ();
2529 opendoc state.path state.password;
2531 src#string "page scroll scaling factor"
2532 (fun () -> string_of_float conf.pgscale)
2533 (fun v ->
2534 try conf.pgscale <- float_of_string v
2535 with exn ->
2536 state.text <-
2537 Printf.sprintf "bad page scroll scaling factor `%s': %s" v
2538 @@ exntos exn
2540 src#int "ui font size"
2541 (fun () -> fstate.fontsize)
2542 (fun v -> setfontsize (bound v 5 100));
2543 src#int "hint font size"
2544 (fun () -> conf.hfsize)
2545 (fun v -> conf.hfsize <- bound v 5 100);
2546 src#string "trim fuzz"
2547 (fun () -> irect_to_string conf.trimfuzz)
2548 (fun v ->
2550 conf.trimfuzz <- irect_of_string v;
2551 if conf.trimmargins
2552 then settrim true conf.trimfuzz;
2553 with exn ->
2554 state.text <- Printf.sprintf "bad irect `%s': %s" v
2555 @@ exntos exn
2557 src#string "selection command"
2558 (fun () -> conf.selcmd)
2559 (fun v -> conf.selcmd <- v);
2560 src#string "synctex command"
2561 (fun () -> conf.stcmd)
2562 (fun v -> conf.stcmd <- v);
2563 src#string "pax command"
2564 (fun () -> conf.paxcmd)
2565 (fun v -> conf.paxcmd <- v);
2566 src#string "ask password command"
2567 (fun () -> conf.passcmd)
2568 (fun v -> conf.passcmd <- v);
2569 src#string "save path command"
2570 (fun () -> conf.savecmd)
2571 (fun v -> conf.savecmd <- v);
2572 src#colorspace "color space"
2573 (fun () -> CSTE.to_string conf.colorspace)
2574 (fun v ->
2575 conf.colorspace <- CSTE.of_int v;
2576 wcmd "cs %d" v;
2577 load state.layout;
2579 src#paxmark "pax mark method"
2580 (fun () -> MTE.to_string conf.paxmark)
2581 (fun v -> conf.paxmark <- MTE.of_int v);
2582 if Ffi.bousable ()
2583 then
2584 src#bool "use PBO"
2585 (fun () -> conf.usepbo)
2586 (fun v -> conf.usepbo <- v);
2587 src#bool "mouse wheel scrolls pages"
2588 (fun () -> conf.wheelbypage)
2589 (fun v -> conf.wheelbypage <- v);
2590 src#bool "open remote links in a new instance"
2591 (fun () -> conf.riani)
2592 (fun v -> conf.riani <- v);
2593 src#bool "edit annotations inline"
2594 (fun () -> conf.annotinline)
2595 (fun v -> conf.annotinline <- v);
2596 src#bool "coarse positioning in presentation mode"
2597 (fun () -> conf.coarseprespos)
2598 (fun v -> conf.coarseprespos <- v);
2599 src#bool "use document CSS"
2600 (fun () -> conf.usedoccss)
2601 (fun v ->
2602 conf.usedoccss <- v;
2603 state.anchor <- getanchor ();
2604 opendoc state.path state.password;
2606 src#bool ~btos "colors"
2607 (fun () -> !showcolors)
2608 (fun v -> showcolors := v; fillsrc prevmode prevuioh);
2609 if !showcolors
2610 then (
2611 colorp " background"
2612 (fun () -> conf.bgcolor)
2613 (fun v -> conf.bgcolor <- v);
2615 rgba " paper color"
2616 (fun () -> conf.papercolor)
2617 (fun v ->
2618 conf.papercolor <- v;
2619 Ffi.setpapercolor conf.papercolor;
2620 flushtiles ();
2622 rgba " scrollbar"
2623 (fun () -> conf.sbarcolor)
2624 (fun v -> conf.sbarcolor <- v);
2625 rgba " scrollbar handle"
2626 (fun () -> conf.sbarhndlcolor)
2627 (fun v -> conf.sbarhndlcolor <- v);
2628 rgba " texture color"
2629 (fun () -> conf.texturecolor)
2630 (fun v ->
2631 GlTex.env (`color v);
2632 conf.texturecolor <- v;
2637 sep ();
2638 src#caption "Document" 0;
2639 List.iter (fun (_, s) -> src#caption s 1) state.docinfo;
2640 src#caption2 "Pages" (fun () -> string_of_int state.pagecount) 1;
2641 src#caption2 "Dimensions"
2642 (fun () -> string_of_int (List.length state.pdims)) 1;
2643 if nonemptystr conf.css
2644 then src#caption2 "CSS" (fun () -> conf.css) 1;
2645 if conf.trimmargins
2646 then (
2647 sep ();
2648 src#caption "Trimmed margins" 0;
2649 src#caption2 "Dimensions"
2650 (fun () -> string_of_int (List.length state.pdims)) 1;
2653 sep ();
2654 src#caption "OpenGL" 0;
2655 src#caption (Printf.sprintf "Vendor\t%s" (GlMisc.get_string `vendor)) 1;
2656 src#caption (Printf.sprintf "Renderer\t%s" (GlMisc.get_string `renderer)) 1;
2658 sep ();
2659 src#caption "Location" 0;
2660 if nonemptystr state.origin
2661 then src#caption ("Orign\t" ^ mbtoutf8 state.origin) 1;
2662 src#caption ("Path\t" ^ mbtoutf8 state.path) 1;
2664 src#reset prevmode prevuioh;
2666 fun () -> (
2667 state.text <- E.s;
2668 resetmstate ();
2669 let prevmode = state.mode
2670 and prevuioh = state.uioh in
2671 fillsrc prevmode prevuioh;
2672 let source = (src :> lvsource) in
2673 let modehash = findkeyhash conf "info" in
2674 state.uioh <-
2675 coe (object (self)
2676 inherit listview ~zebra:false ~helpmode:false
2677 ~source ~trusted:true ~modehash as super
2678 val mutable m_prevmemused = 0
2679 method! infochanged = function
2680 | Memused ->
2681 if m_prevmemused != state.memused
2682 then (
2683 m_prevmemused <- state.memused;
2684 postRedisplay "memusedchanged";
2686 | Pdim -> postRedisplay "pdimchanged"
2687 | Docinfo -> fillsrc prevmode prevuioh
2689 method! key key mask =
2690 if not (Wsi.withctrl mask)
2691 then
2692 match [@warning "-4"] Wsi.kc2kt key with
2693 | Keys.Left -> coe (self#updownlevel ~-1)
2694 | Keys.Right -> coe (self#updownlevel 1)
2695 | _ -> super#key key mask
2696 else super#key key mask
2697 end);
2698 postRedisplay "info";
2702 let enterhelpmode =
2703 let source =
2704 (object
2705 inherit lvsourcebase
2706 method getitemcount = Array.length state.help
2707 method getitem n =
2708 let s, l, _ = state.help.(n) in
2709 (s, l)
2711 method exit ~uioh ~cancel ~active ~first ~pan =
2712 let optuioh =
2713 if not cancel
2714 then (
2715 match state.help.(active) with
2716 | _, _, Action f -> Some (f uioh)
2717 | _, _, Noaction -> Some uioh
2719 else None
2721 m_active <- active;
2722 m_first <- first;
2723 m_pan <- pan;
2724 optuioh
2726 method hasaction n =
2727 match state.help.(n) with
2728 | _, _, Action _ -> true
2729 | _, _, Noaction -> false
2731 initializer
2732 m_active <- -1
2733 end)
2735 fun () ->
2736 let modehash = findkeyhash conf "help" in
2737 resetmstate ();
2738 state.uioh <- coe (new listview
2739 ~zebra:false ~helpmode:true
2740 ~source ~trusted:true ~modehash);
2741 postRedisplay "help";
2744 let entermsgsmode =
2745 let msgsource =
2746 (object
2747 inherit lvsourcebase
2748 val mutable m_items = E.a
2750 method getitemcount = 1 + Array.length m_items
2752 method getitem n =
2753 if n = 0
2754 then "[Clear]", 0
2755 else m_items.(n-1), 0
2757 method exit ~uioh ~cancel ~active ~first ~pan =
2758 ignore uioh;
2759 if not cancel
2760 then (
2761 if active = 0
2762 then Buffer.clear state.errmsgs;
2764 m_active <- active;
2765 m_first <- first;
2766 m_pan <- pan;
2767 None
2769 method hasaction n =
2770 n = 0
2772 method reset =
2773 state.newerrmsgs <- false;
2774 let l = Str.split Utils.Re.crlf (Buffer.contents state.errmsgs) in
2775 m_items <- Array.of_list l
2777 initializer
2778 m_active <- 0
2779 end)
2780 in fun () ->
2781 state.text <- E.s;
2782 resetmstate ();
2783 msgsource#reset;
2784 let source = (msgsource :> lvsource) in
2785 let modehash = findkeyhash conf "listview" in
2786 state.uioh <-
2787 coe (object
2788 inherit listview ~zebra:false ~helpmode:false
2789 ~source ~trusted:false ~modehash as super
2790 method! display =
2791 if state.newerrmsgs
2792 then msgsource#reset;
2793 super#display
2794 end);
2795 postRedisplay "msgs";
2798 let getusertext s =
2799 let editor = getenvdef "EDITOR" E.s in
2800 if emptystr editor
2801 then E.s
2802 else
2803 let tmppath = Filename.temp_file "llpp" "note" in
2804 if nonemptystr s
2805 then (
2806 let oc = open_out tmppath in
2807 output_string oc s;
2808 close_out oc;
2810 let execstr = editor ^ " " ^ tmppath in
2811 let s =
2812 match spawn execstr [] with
2813 | exception exn ->
2814 impmsg "spawn(%S) failed: %s" execstr @@ exntos exn;
2816 | pid ->
2817 match Unix.waitpid [] pid with
2818 | exception exn ->
2819 impmsg "waitpid(%d) failed: %s" pid @@ exntos exn;
2821 | (_pid, status) ->
2822 match status with
2823 | Unix.WEXITED 0 -> filecontents tmppath
2824 | Unix.WEXITED n ->
2825 impmsg "editor process(%s) exited abnormally: %d" execstr n;
2827 | Unix.WSIGNALED n ->
2828 impmsg "editor process(%s) was killed by signal %d" execstr n;
2830 | Unix.WSTOPPED n ->
2831 impmsg "editor(%s) process was stopped by signal %d" execstr n;
2834 match Unix.unlink tmppath with
2835 | exception exn ->
2836 impmsg "failed to ulink %S: %s" tmppath @@ exntos exn;
2838 | () -> s
2841 let enterannotmode opaque slinkindex =
2842 let msgsource =
2843 (object
2844 inherit lvsourcebase
2845 val mutable m_text = E.s
2846 val mutable m_items = E.a
2848 method getitemcount = Array.length m_items
2850 method getitem n =
2851 let label, _func = m_items.(n) in
2852 label, 0
2854 method exit ~uioh ~cancel ~active ~first ~pan =
2855 ignore (uioh, first, pan);
2856 if not cancel
2857 then (
2858 let _label, func = m_items.(active) in
2859 func ()
2861 None
2863 method hasaction n = nonemptystr @@ fst m_items.(n)
2865 method reset s =
2866 let rec split accu b i =
2867 let p = b+i in
2868 if p = String.length s
2869 then (String.sub s b (p-b), fun () -> ()) :: accu
2870 else
2871 if (i > 70 && s.[p] = ' ') || s.[p] = '\r' || s.[p] = '\n'
2872 then
2873 let ss = if i = 0 then E.s else String.sub s b i in
2874 split ((ss, fun () -> ())::accu) (p+1) 0
2875 else split accu b (i+1)
2877 let cleanup () =
2878 wcmd "freepage %s" (~> opaque);
2879 let keys =
2880 Hashtbl.fold (fun key opaque' accu ->
2881 if opaque' = opaque'
2882 then key :: accu else accu) state.pagemap []
2884 List.iter (Hashtbl.remove state.pagemap) keys;
2885 flushtiles ();
2886 gotoxy state.x state.y
2888 let dele () =
2889 Ffi.delannot opaque slinkindex;
2890 cleanup ();
2892 let edit inline () =
2893 let update s =
2894 if emptystr s
2895 then dele ()
2896 else (
2897 Ffi.modannot opaque slinkindex s;
2898 cleanup ();
2901 if inline
2902 then
2903 let mode = state.mode in
2904 state.mode <-
2905 Textentry (
2906 ("annotation: ", m_text, None, textentry, update, true),
2907 fun _ -> state.mode <- mode
2909 state.text <- E.s;
2910 enttext ();
2911 else
2912 let s = getusertext m_text in
2913 update s
2915 m_text <- s;
2916 m_items <-
2917 ( "[Copy]", fun () -> selstring conf.selcmd m_text)
2918 :: ("[Delete]", dele)
2919 :: ("[Edit]", edit conf.annotinline)
2920 :: (E.s, fun () -> ())
2921 :: split [] 0 0 |> List.rev |> Array.of_list
2923 initializer
2924 m_active <- 0
2925 end)
2927 state.text <- E.s;
2928 let s = Ffi.getannotcontents opaque slinkindex in
2929 resetmstate ();
2930 msgsource#reset s;
2931 let source = (msgsource :> lvsource) in
2932 let modehash = findkeyhash conf "listview" in
2933 state.uioh <- coe (object
2934 inherit listview ~zebra:false ~helpmode:false
2935 ~source ~trusted:false ~modehash
2936 end);
2937 postRedisplay "enterannotmode";
2940 let gotoremote spec =
2941 let filename, dest = splitatchar spec '#' in
2942 let getpath filename =
2943 let path =
2944 if nonemptystr filename
2945 then
2946 if Filename.is_relative filename
2947 then
2948 let dir = Filename.dirname state.path in
2949 let dir =
2950 if Filename.is_implicit dir
2951 then Filename.concat (Sys.getcwd ()) dir
2952 else dir
2954 Filename.concat dir filename
2955 else filename
2956 else E.s
2958 if Sys.file_exists path
2959 then path
2960 else E.s
2962 let path = getpath filename in
2963 let dospawn lcmd =
2964 if conf.riani
2965 then
2966 let cmd = Lazy.force_val lcmd in
2967 match spawn cmd with
2968 | _pid -> ()
2969 | exception exn -> dolog "failed to execute `%s': %s" cmd @@ exntos exn
2970 else
2971 let anchor = getanchor () in
2972 let ranchor = state.path, state.password, anchor, state.origin in
2973 state.origin <- E.s;
2974 state.ranchors <- ranchor :: state.ranchors;
2975 opendoc path E.s;
2977 if substratis spec 0 "page="
2978 then
2979 match Scanf.sscanf spec "page=%d" (fun n -> n) with
2980 | pageno ->
2981 state.anchor <- (pageno, 0.0, 0.0);
2982 dospawn @@ lazy (Printf.sprintf "%s -page %d %S" !selfexec pageno path);
2983 | exception exn ->
2984 adderrfmt "error parsing remote destination" "page: %s" @@ exntos exn
2985 else (
2986 state.nameddest <- dest;
2987 dospawn @@ lazy (!selfexec ^ " " ^ path ^ " -dest " ^ dest)
2991 let gotounder = function
2992 | Ulinkuri s when Ffi.isexternallink s ->
2993 if substratis s 0 "file://"
2994 then gotoremote @@ String.sub s 7 (String.length s - 7)
2995 else Help.gotouri conf.urilauncher s
2996 | Ulinkuri s ->
2997 let pageno, x, y = Ffi.uritolocation s in
2998 addnav ();
2999 gotopagexy pageno x y
3000 | Utext _ | Unone -> ()
3001 | Uannotation (opaque, slinkindex) -> enterannotmode opaque slinkindex
3004 let gotooutline (_, _, kind) =
3005 match kind with
3006 | Onone -> ()
3007 | Oanchor ((pageno, y, _) as anchor) ->
3008 addnav ();
3009 gotoxy state.x @@
3010 getanchory (if conf.presentation then (pageno, y, 1.0) else anchor)
3011 | Ouri uri -> gotounder (Ulinkuri uri)
3012 | Olaunch cmd -> error "gotounder (Ulaunch %S)" cmd
3013 | Oremote (remote, pageno) ->
3014 error "gotounder (Uremote (%S,%d) )" remote pageno
3015 | Ohistory hist -> gotohist hist
3016 | Oremotedest (path, dest) ->
3017 error "gotounder (Uremotedest (%S, %S))" path dest
3020 class outlinesoucebase fetchoutlines = object (self)
3021 inherit lvsourcebase
3022 val mutable m_items = E.a
3023 val mutable m_minfo = E.a
3024 val mutable m_orig_items = E.a
3025 val mutable m_orig_minfo = E.a
3026 val mutable m_narrow_patterns = []
3027 val mutable m_gen = -1
3029 method getitemcount = Array.length m_items
3031 method getitem n =
3032 let s, n, _ = m_items.(n) in
3033 (s, n+0)
3035 method exit ~(uioh:uioh) ~cancel ~active ~(first:int) ~pan : uioh option =
3036 ignore (uioh, first);
3037 let items, minfo =
3038 if m_narrow_patterns = []
3039 then m_orig_items, m_orig_minfo
3040 else m_items, m_minfo
3042 m_pan <- pan;
3043 if not cancel
3044 then (
3045 m_items <- items;
3046 m_minfo <- minfo;
3047 gotooutline m_items.(active);
3049 else (
3050 m_items <- items;
3051 m_minfo <- minfo;
3053 None
3055 method hasaction (_:int) = true
3057 method greetmsg =
3058 if Array.length m_items != Array.length m_orig_items
3059 then
3060 let s =
3061 match m_narrow_patterns with
3062 | one :: [] -> one
3063 | many -> String.concat Utf8syms.ellipsis (List.rev many)
3065 "Narrowed to " ^ s ^ " (ctrl-u to restore)"
3066 else E.s
3068 method statestr =
3069 match m_narrow_patterns with
3070 | [] -> E.s
3071 | one :: [] -> one
3072 | head :: _ -> Utf8syms.ellipsis ^ head
3074 method narrow pattern =
3075 match Str.regexp_case_fold pattern with
3076 | exception _ -> ()
3077 | re ->
3078 let rec loop accu minfo n =
3079 if n = -1
3080 then (
3081 m_items <- Array.of_list accu;
3082 m_minfo <- Array.of_list minfo;
3084 else
3085 let (s, _, _) as o = m_items.(n) in
3086 let accu, minfo =
3087 match Str.search_forward re s 0 with
3088 | exception Not_found -> accu, minfo
3089 | first -> o :: accu, (first, Str.match_end ()) :: minfo
3091 loop accu minfo (n-1)
3093 loop [] [] (Array.length m_items - 1)
3095 method! getminfo = m_minfo
3097 method denarrow =
3098 m_orig_items <- fetchoutlines ();
3099 m_minfo <- m_orig_minfo;
3100 m_items <- m_orig_items
3102 method add_narrow_pattern pattern =
3103 m_narrow_patterns <- pattern :: m_narrow_patterns
3105 method del_narrow_pattern =
3106 match m_narrow_patterns with
3107 | _ :: rest -> m_narrow_patterns <- rest
3108 | [] -> ()
3110 method renarrow =
3111 self#denarrow;
3112 match m_narrow_patterns with
3113 | pattern :: [] -> self#narrow pattern; pattern
3114 | list ->
3115 List.fold_left (fun accu pattern ->
3116 self#narrow pattern;
3117 pattern ^ Utf8syms.ellipsis ^ accu) E.s list
3119 method calcactive (_:anchor) = 0
3121 method reset anchor items =
3122 if state.gen != m_gen
3123 then (
3124 m_orig_items <- items;
3125 m_items <- items;
3126 m_narrow_patterns <- [];
3127 m_minfo <- E.a;
3128 m_orig_minfo <- E.a;
3129 m_gen <- state.gen;
3131 else (
3132 if items != m_orig_items
3133 then (
3134 m_orig_items <- items;
3135 if m_narrow_patterns == []
3136 then m_items <- items;
3139 let active = self#calcactive anchor in
3140 m_active <- active;
3141 m_first <- firstof m_first active
3145 let outlinesource fetchoutlines =
3146 (object
3147 inherit outlinesoucebase fetchoutlines
3148 method! calcactive anchor =
3149 let rely = getanchory anchor in
3150 let rec loop n best bestd =
3151 if n = Array.length m_items
3152 then best
3153 else
3154 let _, _, kind = m_items.(n) in
3155 match kind with
3156 | Oanchor anchor ->
3157 let orely = getanchory anchor in
3158 let d = abs (orely - rely) in
3159 if d < bestd
3160 then loop (n+1) n d
3161 else loop (n+1) best bestd
3162 | Onone | Oremote _ | Olaunch _
3163 | Oremotedest _ | Ouri _ | Ohistory _ ->
3164 loop (n+1) best bestd
3166 loop 0 ~-1 max_int
3167 end)
3170 let enteroutlinemode, enterbookmarkmode, enterhistmode =
3171 let fetchoutlines sourcetype () =
3172 match sourcetype with
3173 | `bookmarks -> Array.of_list state.bookmarks
3174 | `outlines -> state.outlines
3175 | `history -> genhistoutlines () |> Array.of_list
3177 let so = outlinesource (fetchoutlines `outlines) in
3178 let sb = outlinesource (fetchoutlines `bookmarks) in
3179 let sh = outlinesource (fetchoutlines `history) in
3180 let mkselector sourcetype source =
3181 (fun errmsg ->
3182 let outlines = fetchoutlines sourcetype () in
3183 if Array.length outlines = 0
3184 then showtext ' ' errmsg
3185 else (
3186 resetmstate ();
3187 Wsi.setcursor Wsi.CURSOR_INHERIT;
3188 let anchor = getanchor () in
3189 source#reset anchor outlines;
3190 state.text <- source#greetmsg;
3191 state.uioh <-
3192 coe (new outlinelistview ~zebra:(sourcetype=`history) ~source);
3193 postRedisplay "enter selector";
3197 let mkenter sourcetype errmsg s = fun () -> mkselector sourcetype s errmsg in
3198 ( mkenter `outlines "document has no outline" so
3199 , mkenter `bookmarks "document has no bookmarks (yet)" sb
3200 , mkenter `history "history is empty" sh )
3204 let addbookmark title a =
3205 let b = List.filter (fun (title', _, _) -> title <> title') state.bookmarks in
3206 state.bookmarks <- (title, 0, Oanchor a) :: b;;
3208 let quickbookmark ?title () =
3209 match state.layout with
3210 | [] -> ()
3211 | l :: _ ->
3212 let title =
3213 match title with
3214 | None ->
3215 Unix.(
3216 let tm = localtime (now ()) in
3217 Printf.sprintf
3218 "Quick (page %d) (bookmarked on %02d/%02d/%d at %02d:%02d)"
3219 (l.pageno+1)
3220 tm.tm_mday (tm.tm_mon+1) (tm.tm_year+1900) tm.tm_hour tm.tm_min
3222 | Some title -> title
3224 addbookmark title (getanchor1 l)
3227 let setautoscrollspeed step goingdown =
3228 let incr = max 1 ((abs step) / 2) in
3229 let incr = if goingdown then incr else -incr in
3230 let astep = boundastep state.winh (step + incr) in
3231 state.autoscroll <- Some astep;
3234 let canpan () =
3235 match conf.columns with
3236 | Csplit _ -> true
3237 | Csingle _ | Cmulti _ -> state.x != 0 || conf.zoom > 1.0
3240 let panbound x = bound x (-state.w) state.winw;;
3242 let existsinrow pageno (columns, coverA, coverB) p =
3243 let last = ((pageno - coverA) mod columns) + columns in
3244 let rec any = function
3245 | [] -> false
3246 | l :: rest ->
3247 if l.pageno = coverA - 1 || l.pageno = state.pagecount - coverB
3248 then p l
3249 else (
3250 if not (p l)
3251 then (if l.pageno = last then false else any rest)
3252 else true
3255 any state.layout
3258 let nextpage () =
3259 match state.layout with
3260 | [] ->
3261 let pageno = page_of_y state.y in
3262 gotoxy state.x (getpagey (pageno+1))
3263 | l :: rest ->
3264 match conf.columns with
3265 | Csingle _ ->
3266 if conf.presentation && rest == [] && l.pageh > l.pagey + l.pagevh
3267 then
3268 let y = clamp (pgscale state.winh) in
3269 gotoxy state.x y
3270 else
3271 let pageno = min (l.pageno+1) (state.pagecount-1) in
3272 gotoxy state.x (getpagey pageno)
3273 | Cmulti ((c, _, _) as cl, _) ->
3274 if conf.presentation
3275 && (existsinrow l.pageno cl
3276 (fun l -> l.pageh > l.pagey + l.pagevh))
3277 then
3278 let y = clamp (pgscale state.winh) in
3279 gotoxy state.x y
3280 else
3281 let pageno = min (l.pageno+c) (state.pagecount-1) in
3282 gotoxy state.x (getpagey pageno)
3283 | Csplit (n, _) ->
3284 if l.pageno < state.pagecount - 1 || l.pagecol < n - 1
3285 then
3286 let pagey, pageh = getpageyh l.pageno in
3287 let pagey = pagey + pageh * l.pagecol in
3288 let ips = if l.pagecol = 0 then 0 else conf.interpagespace in
3289 gotoxy state.x (pagey + pageh + ips)
3292 let prevpage () =
3293 match state.layout with
3294 | [] ->
3295 let pageno = page_of_y state.y in
3296 gotoxy state.x (getpagey (pageno-1))
3297 | l :: _ ->
3298 match conf.columns with
3299 | Csingle _ ->
3300 if conf.presentation && l.pagey != 0
3301 then gotoxy state.x (clamp (pgscale ~-(state.winh)))
3302 else
3303 let pageno = max 0 (l.pageno-1) in
3304 gotoxy state.x (getpagey pageno)
3305 | Cmulti ((c, _, coverB) as cl, _) ->
3306 if conf.presentation &&
3307 (existsinrow l.pageno cl (fun l -> l.pagey != 0))
3308 then gotoxy state.x (clamp (pgscale ~-(state.winh)))
3309 else
3310 let decr =
3311 if l.pageno = state.pagecount - coverB
3312 then 1
3313 else c
3315 let pageno = max 0 (l.pageno-decr) in
3316 gotoxy state.x (getpagey pageno)
3317 | Csplit (n, _) ->
3318 let y =
3319 if l.pagecol = 0
3320 then
3321 if l.pageno = 0
3322 then l.pagey
3323 else
3324 let pageno = max 0 (l.pageno-1) in
3325 let pagey, pageh = getpageyh pageno in
3326 pagey + (n-1)*pageh
3327 else
3328 let pagey, pageh = getpageyh l.pageno in
3329 pagey + pageh * (l.pagecol-1) - conf.interpagespace
3331 gotoxy state.x y
3334 let save () =
3335 if emptystr conf.savecmd
3336 then adderrmsg "savepath-command is empty"
3337 "don't know where to save modified document"
3338 else
3339 let savecmd = Str.global_replace Utils.Re.percent state.path conf.savecmd in
3340 let path =
3341 getcmdoutput
3342 (fun exn ->
3343 adderrfmt savecmd "failed to produce path to the saved copy: %s" exn)
3344 savecmd
3346 if nonemptystr path
3347 then
3348 let tmp = path ^ ".tmp" in
3349 Ffi.savedoc tmp;
3350 Unix.rename tmp path;
3353 let viewkeyboard key mask =
3354 let enttext te =
3355 let mode = state.mode in
3356 state.mode <- Textentry (te, fun _ -> state.mode <- mode);
3357 state.text <- E.s;
3358 enttext ();
3359 postRedisplay "view:enttext"
3361 let ctrl = Wsi.withctrl mask in
3362 let open Keys in
3363 match Wsi.kc2kt key with
3364 | Ascii 'S' -> state.slideshow <- state.slideshow lxor 1
3366 | Ascii 'Q' -> exit 0
3368 | Ascii 'z' ->
3369 let yloc f =
3370 match List.rev state.rects with
3371 | [] -> ()
3372 | (pageno, _, (_, y0, _, y1, _, y2, _, y3)) :: _ ->
3373 f pageno (y0, y1, y2, y3)
3374 and yminmax (y0, y1, y2, y3) =
3375 let ym = min y0 y1 |> min y2 |> min y3 |> truncate in
3376 let yM = max y0 y1 |> max y2 |> max y3 |> truncate in
3377 ym, yM
3379 let ondone msg = state.text <- msg
3380 and zmod _ _ k =
3381 match [@warning "-4"] k with
3382 | Keys.Ascii 'z' ->
3383 let f pageno ys =
3384 let ym, yM = yminmax ys in
3385 let hh = (yM - ym)/2 in
3386 gotopage1 pageno (ym + hh - state.winh/2)
3388 yloc f;
3389 TEdone "center"
3390 | Keys.Ascii 't' ->
3391 let f pageno ys =
3392 let ym, _ = yminmax ys in
3393 gotopage1 pageno ym
3395 yloc f;
3396 TEdone "top"
3397 | Keys.Ascii 'b' ->
3398 let f pageno ys =
3399 let _, yM = yminmax ys in
3400 gotopage1 pageno (yM - state.winh)
3402 yloc f;
3403 TEdone "bottom"
3404 | _ -> TEstop
3406 enttext (": ", E.s, None, zmod state.mode, ondone, true)
3408 | Ascii 'W' ->
3409 if Ffi.hasunsavedchanges ()
3410 then save ()
3412 | Insert ->
3413 if conf.angle mod 360 = 0 && not (isbirdseye state.mode)
3414 then (
3415 state.mode <- (
3416 match state.lnava with
3417 | None -> LinkNav (Ltgendir 0)
3418 | Some pn -> LinkNav (Ltexact pn)
3420 gotoxy state.x state.y;
3422 else impmsg "keyboard link navigation does not work under rotation"
3424 | Escape | Ascii 'q' ->
3425 begin match state.mstate with
3426 | Mzoomrect _ ->
3427 resetmstate ();
3428 postRedisplay "kill rect";
3429 | Msel _
3430 | Mpan _
3431 | Mscrolly | Mscrollx
3432 | Mzoom _
3433 | Mnone ->
3434 begin match state.mode with
3435 | LinkNav ln ->
3436 begin match ln with
3437 | Ltexact pl -> state.lnava <- Some pl
3438 | Ltgendir _ | Ltnotready _ -> state.lnava <- None
3439 end;
3440 state.mode <- View;
3441 postRedisplay "esc leave linknav"
3442 | Birdseye _ | Textentry _ | View ->
3443 match state.ranchors with
3444 | [] -> raise Quit
3445 | (path, password, anchor, origin) :: rest ->
3446 state.ranchors <- rest;
3447 state.anchor <- anchor;
3448 state.origin <- origin;
3449 state.nameddest <- E.s;
3450 opendoc path password
3451 end;
3452 end;
3454 | Ascii 'o' -> enteroutlinemode ()
3455 | Ascii 'H' -> enterhistmode ()
3457 | Ascii 'u' ->
3458 state.rects <- [];
3459 state.text <- E.s;
3460 Hashtbl.iter (fun _ opaque ->
3461 Ffi.clearmark opaque;
3462 Hashtbl.clear state.prects) state.pagemap;
3463 postRedisplay "dehighlight";
3465 | Ascii (('/' | '?') as c) ->
3466 let ondone isforw s =
3467 cbput state.hists.pat s;
3468 state.searchpattern <- s;
3469 search s isforw
3471 let s = String.make 1 c in
3472 enttext (s, E.s, Some (onhist state.hists.pat),
3473 textentry, ondone (c = '/'), true)
3475 | Ascii '+' | Ascii '=' when ctrl ->
3476 let incr = if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01 in
3477 pivotzoom (conf.zoom +. incr)
3479 | Ascii '+' ->
3480 let ondone s =
3481 let n =
3482 try int_of_string s with exn ->
3483 state.text <-
3484 Printf.sprintf "bad integer `%s': %s" s @@ exntos exn;
3485 max_int
3487 if n != max_int
3488 then (
3489 conf.pagebias <- n;
3490 state.text <- "page bias is now " ^ string_of_int n;
3493 enttext ("page bias: ", E.s, None, intentry, ondone, true)
3495 | Ascii '-' when ctrl ->
3496 let decr = if conf.zoom -. 0.1 < 0.1 then 0.01 else 0.1 in
3497 pivotzoom (max 0.01 (conf.zoom -. decr))
3499 | Ascii '-' ->
3500 let ondone msg = state.text <- msg in
3501 enttext ("option: ", E.s, None,
3502 optentry state.mode, ondone, true)
3504 | Ascii '0' when ctrl ->
3505 if conf.zoom = 1.0
3506 then gotoxy 0 state.y
3507 else setzoom 1.0
3509 | Ascii ('1'|'2' as c) when ctrl && conf.fitmodel != FitPage ->
3510 let cols =
3511 match conf.columns with
3512 | Csingle _ | Cmulti _ -> 1
3513 | Csplit (n, _) -> n
3515 let h = state.winh -
3516 conf.interpagespace lsl (if conf.presentation then 1 else 0)
3518 let zoom = Ffi.zoomforh state.winw h 0 cols in
3519 if zoom > 0.0 && (c = '2' || zoom < 1.0)
3520 then setzoom zoom
3522 | Ascii '3' when ctrl ->
3523 let fm =
3524 match conf.fitmodel with
3525 | FitWidth -> FitProportional
3526 | FitProportional -> FitPage
3527 | FitPage -> FitWidth
3529 state.text <- "fit model: " ^ FMTE.to_string fm;
3530 reqlayout conf.angle fm
3532 | Ascii '4' when ctrl ->
3533 let zoom = Ffi.getmaxw () /. float state.winw in
3534 if zoom > 0.0 then setzoom zoom
3536 | Fn 9 | Ascii '9' when ctrl -> togglebirdseye ()
3538 | Ascii ('0'..'9' as c) when not ctrl ->
3539 let ondone s =
3540 let n =
3541 try int_of_string s with exn ->
3542 state.text <- Printf.sprintf "bad integer `%s': %s" s @@ exntos exn;
3545 if n >= 0
3546 then (
3547 addnav ();
3548 cbput state.hists.pag (string_of_int n);
3549 gotopage1 (n + conf.pagebias - 1) 0;
3552 let pageentry text = function [@warning "-4"]
3553 | Keys.Ascii 'g' -> TEdone text
3554 | key -> intentry text key
3556 let text = String.make 1 c in
3557 enttext (":", text, Some (onhist state.hists.pag),
3558 pageentry, ondone, true)
3560 | Ascii 'b' ->
3561 conf.scrollb <- if conf.scrollb = 0 then (scrollbvv lor scrollbhv) else 0;
3562 postRedisplay "toggle scrollbar";
3564 | Ascii 'B' ->
3565 state.bzoom <- not state.bzoom;
3566 state.rects <- [];
3567 showtext ' ' ("block zoom " ^ if state.bzoom then "on" else "off")
3569 | Ascii 'l' ->
3570 conf.hlinks <- not conf.hlinks;
3571 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
3572 postRedisplay "toggle highlightlinks";
3574 | Ascii 'F' ->
3575 if conf.angle mod 360 = 0
3576 then (
3577 state.glinks <- true;
3578 let mode = state.mode in
3579 state.mode <-
3580 Textentry (
3581 ("goto: ", E.s, None, linknentry, linknact gotounder, false),
3582 (fun _ ->
3583 state.glinks <- false;
3584 state.mode <- mode)
3586 state.text <- E.s;
3587 postRedisplay "view:linkent(F)"
3589 else impmsg "hint mode does not work under rotation"
3591 | Ascii 'y' ->
3592 state.glinks <- true;
3593 let mode = state.mode in
3594 state.mode <-
3595 Textentry (
3596 ("copy: ", E.s, None, linknentry,
3597 linknact (fun under ->
3598 selstring conf.selcmd (undertext under)), false),
3599 (fun _ ->
3600 state.glinks <- false;
3601 state.mode <- mode)
3603 state.text <- E.s;
3604 postRedisplay "view:linkent"
3606 | Ascii 'a' ->
3607 begin match state.autoscroll with
3608 | Some step ->
3609 conf.autoscrollstep <- step;
3610 state.autoscroll <- None
3611 | None ->
3612 state.autoscroll <- Some conf.autoscrollstep;
3613 state.slideshow <- state.slideshow land lnot 2
3616 | Ascii 'p' when ctrl ->
3617 launchpath () (* XXX where do error messages go? *)
3619 | Ascii 'P' ->
3620 setpresentationmode (not conf.presentation);
3621 showtext ' ' ("presentation mode " ^
3622 if conf.presentation then "on" else "off");
3624 | Ascii 'f' ->
3625 if List.mem Wsi.Fullscreen state.winstate
3626 then Wsi.reshape conf.cwinw conf.cwinh
3627 else Wsi.fullscreen ()
3629 | Ascii ('p'|'N') -> search state.searchpattern false
3630 | Ascii 'n' | Fn 3 -> search state.searchpattern true
3632 | Ascii 't' ->
3633 begin match state.layout with
3634 | [] -> ()
3635 | l :: _ -> gotoxy state.x (getpagey l.pageno)
3638 | Ascii ' ' -> nextpage ()
3639 | Delete -> prevpage ()
3640 | Ascii '=' -> showtext ' ' (describe_layout state.layout);
3642 | Ascii 'w' ->
3643 begin match state.layout with
3644 | [] -> ()
3645 | l :: _ ->
3646 Wsi.reshape l.pagew l.pageh;
3647 postRedisplay "w"
3650 | Ascii '\'' -> enterbookmarkmode ()
3651 | Ascii 'h' | Fn 1 -> enterhelpmode ()
3652 | Ascii 'i' -> enterinfomode ()
3653 | Ascii 'e' when Buffer.length state.errmsgs > 0 -> entermsgsmode ()
3655 | Ascii 'm' ->
3656 let ondone s =
3657 match state.layout with
3658 | l :: _ when nonemptystr s -> addbookmark s @@ getanchor1 l
3659 | _ -> ()
3661 enttext ("bookmark: ", E.s, None, textentry, ondone, true)
3663 | Ascii '~' ->
3664 quickbookmark ();
3665 showtext ' ' "Quick bookmark added";
3667 | Ascii 'x' -> state.roam ()
3669 | Ascii ('<'|'>' as c) ->
3670 reqlayout (conf.angle + (if c = '>' then 30 else -30)) conf.fitmodel
3672 | Ascii ('['|']' as c) ->
3673 conf.colorscale <-
3674 bound (conf.colorscale +. (if c = ']' then 0.1 else -0.1)) 0.0 1.0;
3675 postRedisplay "brightness";
3677 | Ascii 'c' when state.mode = View ->
3678 if Wsi.withalt mask
3679 then (
3680 if conf.zoom > 1.0
3681 then
3682 let m = (state.winw - state.w) / 2 in
3683 gotoxy m state.y
3685 else
3686 let (c, a, b), z =
3687 match state.prevcolumns with
3688 | None -> (1, 0, 0), 1.0
3689 | Some (columns, z) ->
3690 let cab =
3691 match columns with
3692 | Csplit (c, _) -> -c, 0, 0
3693 | Cmulti ((c, a, b), _) -> c, a, b
3694 | Csingle _ -> 1, 0, 0
3696 cab, z
3698 setcolumns View c a b;
3699 setzoom z
3701 | Down | Up when ctrl && Wsi.withshift mask ->
3702 let zoom, x = state.prevzoom in
3703 setzoom zoom;
3704 state.x <- x;
3706 | Up ->
3707 begin match state.autoscroll with
3708 | None ->
3709 begin match state.mode with
3710 | Birdseye beye -> upbirdseye 1 beye
3711 | Textentry _ | View | LinkNav _ ->
3712 if ctrl
3713 then gotoxy state.x (clamp ~-(state.winh/2))
3714 else (
3715 if not (Wsi.withshift mask) && conf.presentation
3716 then prevpage ()
3717 else gotoxy state.x (clamp (-conf.scrollstep))
3720 | Some n -> setautoscrollspeed n false
3723 | Down ->
3724 begin match state.autoscroll with
3725 | None ->
3726 begin match state.mode with
3727 | Birdseye beye -> downbirdseye 1 beye
3728 | Textentry _ | View | LinkNav _ ->
3729 if ctrl
3730 then gotoxy state.x (clamp (state.winh/2))
3731 else (
3732 if not (Wsi.withshift mask) && conf.presentation
3733 then nextpage ()
3734 else gotoxy state.x (clamp (conf.scrollstep))
3737 | Some n -> setautoscrollspeed n true
3740 | Left | Right when not (Wsi.withalt mask) ->
3741 if canpan ()
3742 then
3743 let dx =
3744 if ctrl
3745 then state.winw / 2
3746 else conf.hscrollstep
3748 let dx =
3749 let pv = Wsi.kc2kt key in
3750 if pv = Keys.Left then dx else -dx
3752 gotoxy (panbound (state.x + dx)) state.y
3753 else (
3754 state.text <- E.s;
3755 postRedisplay "left/right"
3758 | Prior ->
3759 let y =
3760 if ctrl
3761 then
3762 match state.layout with
3763 | [] -> state.y
3764 | l :: _ -> state.y - l.pagey
3765 else clamp (pgscale (-state.winh))
3767 gotoxy state.x y
3769 | Next ->
3770 let y =
3771 if ctrl
3772 then
3773 match List.rev state.layout with
3774 | [] -> state.y
3775 | l :: _ -> getpagey l.pageno
3776 else clamp (pgscale state.winh)
3778 gotoxy state.x y
3780 | Ascii 'g' | Home ->
3781 addnav ();
3782 gotoxy 0 0
3783 | Ascii 'G' | End ->
3784 addnav ();
3785 gotoxy 0 (clamp state.maxy)
3787 | Right when Wsi.withalt mask ->
3788 let nav = state.hists.nav in
3789 (match nav.future with
3790 | [] -> ()
3791 | next :: frest ->
3792 state.hists.nav <-
3793 { past = getanchor () :: nav.past
3794 ; future = frest
3796 gotoxy state.x (getanchory next)
3798 | Backspace | Left when Wsi.withalt mask ->
3799 let nav = state.hists.nav in
3800 (match nav.past with
3801 | [] -> ()
3802 | prev :: prest ->
3803 state.hists.nav <-
3804 { past = prest
3805 ; future = getanchor () :: nav.future
3807 gotoxy state.x (getanchory prev)
3810 | Ascii 'r' ->
3811 reload ()
3813 | Ascii 'v' when conf.debug ->
3814 state.rects <- [];
3815 List.iter (fun l ->
3816 match getopaque l.pageno with
3817 | None -> ()
3818 | Some opaque ->
3819 let x0, y0, x1, y1 = Ffi.pagebbox opaque in
3820 let rect = (float x0, float y0,
3821 float x1, float y0,
3822 float x1, float y1,
3823 float x0, float y1) in
3824 debugrect rect;
3825 let color = (0.0, 0.0, 1.0 /. (l.pageno mod 3 |> float), 0.5) in
3826 state.rects <- (l.pageno, color, rect) :: state.rects;
3827 ) state.layout;
3828 postRedisplay "v";
3830 | Ascii '|' ->
3831 let mode = state.mode in
3832 let cmd = ref E.s in
3833 let onleave = function
3834 | Cancel -> state.mode <- mode
3835 | Confirm ->
3836 List.iter (fun l ->
3837 match getopaque l.pageno with
3838 | Some opaque -> pipesel opaque !cmd
3839 | None -> ()) state.layout;
3840 state.mode <- mode
3842 let ondone s =
3843 cbput state.hists.sel s;
3844 cmd := s
3846 let te =
3847 "| ", !cmd, Some (onhist state.hists.sel), textentry, ondone, true
3849 postRedisplay "|";
3850 state.mode <- Textentry (te, onleave);
3852 | (Ascii _|Fn _|Enter|Left|Right|Code _|Ctrl _) ->
3853 vlog "huh? %s" (Wsi.keyname key)
3856 let linknavkeyboard key mask linknav =
3857 let pv = Wsi.kc2kt key in
3858 let getpage pageno =
3859 let rec loop = function
3860 | [] -> None
3861 | l :: _ when l.pageno = pageno -> Some l
3862 | _ :: rest -> loop rest
3863 in loop state.layout
3865 let doexact (pageno, n) =
3866 match getopaque pageno, getpage pageno with
3867 | Some opaque, Some l ->
3868 if pv = Keys.Enter
3869 then
3870 let under = Ffi.getlink opaque n in
3871 postRedisplay "link gotounder";
3872 gotounder under;
3873 state.mode <- View;
3874 else
3875 let opt, dir =
3876 let open Keys in
3877 match pv with
3878 | Home -> Some (Ffi.findlink opaque LDfirst), -1
3879 | End -> Some (Ffi.findlink opaque LDlast), 1
3880 | Left -> Some (Ffi.findlink opaque (LDleft n)), -1
3881 | Right -> Some (Ffi.findlink opaque (LDright n)), 1
3882 | Up -> Some (Ffi.findlink opaque (LDup n)), -1
3883 | Down -> Some (Ffi.findlink opaque (LDdown n)), 1
3884 | Delete|Escape|Insert|Enter|Next|Prior|Ascii _
3885 | Code _|Fn _|Ctrl _|Backspace -> None, 0
3887 let pwl l dir =
3888 begin match Ffi.findpwl l.pageno dir with
3889 | Pwlnotfound -> ()
3890 | Pwl pageno ->
3891 let notfound dir =
3892 state.mode <- LinkNav (Ltgendir dir);
3893 let y, h = getpageyh pageno in
3894 let y =
3895 if dir < 0
3896 then y + h - state.winh
3897 else y
3899 gotoxy state.x y
3901 begin match getopaque pageno, getpage pageno with
3902 | Some opaque, Some _ ->
3903 let link =
3904 let ld = if dir > 0 then LDfirst else LDlast in
3905 Ffi.findlink opaque ld
3907 begin match link with
3908 | Lfound m ->
3909 showlinktype (Ffi.getlink opaque m);
3910 state.mode <- LinkNav (Ltexact (pageno, m));
3911 postRedisplay "linknav jpage";
3912 | Lnotfound -> notfound dir
3913 end;
3914 | _ -> notfound dir
3915 end;
3916 end;
3918 begin match opt with
3919 | Some Lnotfound -> pwl l dir;
3920 | Some (Lfound m) ->
3921 if m = n
3922 then pwl l dir
3923 else (
3924 let _, y0, _, y1 = Ffi.getlinkrect opaque m in
3925 if y0 < l.pagey
3926 then gotopage1 l.pageno y0
3927 else (
3928 let d = fstate.fontsize + 1 in
3929 if y1 - l.pagey > l.pagevh - d
3930 then gotopage1 l.pageno (y1 - state.winh + d)
3931 else postRedisplay "linknav";
3933 showlinktype (Ffi.getlink opaque m);
3934 state.mode <- LinkNav (Ltexact (l.pageno, m));
3937 | None -> viewkeyboard key mask
3938 end;
3939 | _ -> viewkeyboard key mask
3941 if pv = Keys.Insert
3942 then (
3943 begin match linknav with
3944 | Ltexact pa -> state.lnava <- Some pa
3945 | Ltgendir _ | Ltnotready _ -> ()
3946 end;
3947 state.mode <- View;
3948 postRedisplay "leave linknav"
3950 else
3951 match linknav with
3952 | Ltgendir _ | Ltnotready _ -> viewkeyboard key mask
3953 | Ltexact exact -> doexact exact
3956 let keyboard key mask =
3957 if (key = Char.code 'g' && Wsi.withctrl mask) && not (istextentry state.mode)
3958 then wcmd "interrupt"
3959 else state.uioh <- state.uioh#key key mask
3962 let birdseyekeyboard key mask
3963 ((oconf, leftx, pageno, hooverpageno, anchor) as beye) =
3964 let incr =
3965 match conf.columns with
3966 | Csingle _ -> 1
3967 | Cmulti ((c, _, _), _) -> c
3968 | Csplit _ -> error "bird's eye split mode"
3970 let pgh layout = List.fold_left
3971 (fun m l -> max l.pageh m) state.winh layout in
3972 let open Keys in
3973 match Wsi.kc2kt key with
3974 | Ascii 'l' when Wsi.withctrl mask ->
3975 let y, h = getpageyh pageno in
3976 let top = (state.winh - h) / 2 in
3977 gotoxy state.x (max 0 (y - top))
3978 | Enter -> leavebirdseye beye false
3979 | Escape -> leavebirdseye beye true
3980 | Up -> upbirdseye incr beye
3981 | Down -> downbirdseye incr beye
3982 | Left -> upbirdseye 1 beye
3983 | Right -> downbirdseye 1 beye
3985 | Prior ->
3986 begin match state.layout with
3987 | l :: _ ->
3988 if l.pagey != 0
3989 then (
3990 state.mode <- Birdseye (
3991 oconf, leftx, l.pageno, hooverpageno, anchor
3993 gotopage1 l.pageno 0;
3995 else (
3996 let layout = layout state.x (state.y-state.winh)
3997 state.winw
3998 (pgh state.layout) in
3999 match layout with
4000 | [] -> gotoxy state.x (clamp (-state.winh))
4001 | l :: _ ->
4002 state.mode <- Birdseye (
4003 oconf, leftx, l.pageno, hooverpageno, anchor
4005 gotopage1 l.pageno 0
4008 | [] -> gotoxy state.x (clamp (-state.winh))
4009 end;
4011 | Next ->
4012 begin match List.rev state.layout with
4013 | l :: _ ->
4014 let layout = layout state.x
4015 (state.y + (pgh state.layout))
4016 state.winw state.winh in
4017 begin match layout with
4018 | [] ->
4019 let incr = l.pageh - l.pagevh in
4020 if incr = 0
4021 then (
4022 state.mode <-
4023 Birdseye (
4024 oconf, leftx, state.pagecount - 1, hooverpageno, anchor
4026 postRedisplay "birdseye pagedown";
4028 else gotoxy state.x (clamp (incr + conf.interpagespace*2));
4030 | l :: _ ->
4031 state.mode <-
4032 Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
4033 gotopage1 l.pageno 0;
4036 | [] -> gotoxy state.x (clamp state.winh)
4037 end;
4039 | Home ->
4040 state.mode <- Birdseye (oconf, leftx, 0, hooverpageno, anchor);
4041 gotopage1 0 0
4043 | End ->
4044 let pageno = state.pagecount - 1 in
4045 state.mode <- Birdseye (oconf, leftx, pageno, hooverpageno, anchor);
4046 if not (pagevisible state.layout pageno)
4047 then
4048 let h =
4049 match List.rev state.pdims with
4050 | [] -> state.winh
4051 | (_, _, h, _) :: _ -> h
4053 gotoxy
4054 state.x
4055 (max 0 (getpagey pageno - (state.winh - h - conf.interpagespace)))
4056 else postRedisplay "birdseye end";
4058 | Delete|Insert|Ascii _|Code _|Ctrl _|Fn _|Backspace -> viewkeyboard key mask
4061 let drawpage l =
4062 let color =
4063 match state.mode with
4064 | Textentry _ -> scalecolor 0.4
4065 | LinkNav _ | View -> scalecolor 1.0
4066 | Birdseye (_, _, pageno, hooverpageno, _) ->
4067 if l.pageno = hooverpageno
4068 then scalecolor 0.9
4069 else (
4070 if l.pageno = pageno
4071 then (
4072 let c = scalecolor 1.0 in
4073 GlDraw.color c;
4074 GlDraw.line_width 3.0;
4075 let dispx = l.pagedispx in
4076 linerect
4077 (float (dispx-1)) (float (l.pagedispy-1))
4078 (float (dispx+l.pagevw+1))
4079 (float (l.pagedispy+l.pagevh+1));
4080 GlDraw.line_width 1.0;
4083 else scalecolor 0.8
4086 drawtiles l color;
4089 let postdrawpage l linkindexbase =
4090 match getopaque l.pageno with
4091 | Some opaque ->
4092 if tileready l l.pagex l.pagey
4093 then
4094 let x = l.pagedispx - l.pagex
4095 and y = l.pagedispy - l.pagey in
4096 let hlmask =
4097 match conf.columns with
4098 | Csingle _ | Cmulti _ ->
4099 (if conf.hlinks then 1 else 0)
4100 + (if state.glinks
4101 && not (isbirdseye state.mode) then 2 else 0)
4102 | Csplit _ -> 0
4104 let s =
4105 match state.mode with
4106 | Textentry ((_, s, _, _, _, _), _) when state.glinks -> s
4107 | Textentry _
4108 | Birdseye _
4109 | View
4110 | LinkNav _ -> E.s
4112 Hashtbl.find_all state.prects l.pageno |>
4113 List.iter (fun vals -> Ffi.drawprect opaque x y vals);
4114 let n =
4115 Ffi.postprocess opaque hlmask x y (linkindexbase, s, conf.hfsize) in
4116 if n < 0
4117 then (Glutils.redisplay := true; 0)
4118 else n
4119 else 0
4120 | _ -> 0
4123 let scrollindicator () =
4124 let sbw, ph, sh = state.uioh#scrollph in
4125 let sbh, pw, sw = state.uioh#scrollpw in
4127 let x0,x1,hx0 =
4128 if conf.leftscroll
4129 then (0, sbw, sbw)
4130 else ((state.winw - sbw), state.winw, 0)
4133 Gl.enable `blend;
4134 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
4135 let (r, g, b, alpha) = conf.sbarcolor in
4136 GlDraw.color (r, g, b) ~alpha;
4137 filledrect (float x0) 0. (float x1) (float state.winh);
4138 filledrect
4139 (float hx0) (float (state.winh - sbh))
4140 (float (hx0 + state.winw)) (float state.winh);
4141 let (r, g, b, alpha) = conf.sbarhndlcolor in
4142 GlDraw.color (r, g, b) ~alpha;
4144 filledrect (float x0) ph (float x1) (ph +. sh);
4145 let pw = pw +. float hx0 in
4146 filledrect pw (float (state.winh - sbh)) (pw +. sw) (float state.winh);
4147 Gl.disable `blend;
4150 let showsel () =
4151 match state.mstate with
4152 | Mnone | Mscrolly | Mscrollx | Mpan _ | Mzoom _ | Mzoomrect _ -> ()
4153 | Msel ((x0, y0), (x1, y1)) ->
4154 let identify opaque l px py = Some (opaque, l.pageno, px, py) in
4155 let o0,n0,px0,py0 = onppundermouse identify x0 y0 (~< E.s, -1, 0, 0) in
4156 let _o1,n1,px1,py1 = onppundermouse identify x1 y1 (~< E.s, -1, 0, 0) in
4157 if n0 != -1 && n0 = n1 then Ffi.seltext o0 (px0, py0, px1, py1);
4160 let showrects = function
4161 | [] -> ()
4162 | rects ->
4163 Gl.enable `blend;
4164 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
4165 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
4166 List.iter
4167 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
4168 List.iter (fun l ->
4169 if l.pageno = pageno
4170 then
4171 let dx = float (l.pagedispx - l.pagex) in
4172 let dy = float (l.pagedispy - l.pagey) in
4173 let r, g, b, alpha = c in
4174 GlDraw.color (r, g, b) ~alpha;
4175 filledrect2
4176 (x0+.dx) (y0+.dy)
4177 (x1+.dx) (y1+.dy)
4178 (x3+.dx) (y3+.dy)
4179 (x2+.dx) (y2+.dy);
4180 ) state.layout
4181 ) rects;
4182 Gl.disable `blend;
4185 let display () =
4186 GlDraw.color (scalecolor2 conf.bgcolor);
4187 GlClear.color (scalecolor2 conf.bgcolor);
4188 GlClear.clear [`color];
4189 List.iter drawpage state.layout;
4190 let rects =
4191 match state.mode with
4192 | LinkNav (Ltexact (pageno, linkno)) ->
4193 begin match getopaque pageno with
4194 | Some opaque ->
4195 let x0, y0, x1, y1 = Ffi.getlinkrect opaque linkno in
4196 let color =
4197 if conf.invert
4198 then (1.0, 1.0, 1.0, 0.5)
4199 else (0.0, 0.0, 0.5, 0.5)
4201 (pageno, color,
4202 (float x0, float y0,
4203 float x1, float y0,
4204 float x1, float y1,
4205 float x0, float y1)
4206 ) :: state.rects
4207 | None -> state.rects
4209 | LinkNav (Ltgendir _) | LinkNav (Ltnotready _)
4210 | Birdseye _
4211 | Textentry _
4212 | View -> state.rects
4214 showrects rects;
4215 let rec postloop linkindexbase = function
4216 | l :: rest ->
4217 let linkindexbase = linkindexbase + postdrawpage l linkindexbase in
4218 postloop linkindexbase rest
4219 | [] -> ()
4221 showsel ();
4222 postloop 0 state.layout;
4223 state.uioh#display;
4224 begin match state.mstate with
4225 | Mzoomrect ((x0, y0), (x1, y1)) ->
4226 Gl.enable `blend;
4227 GlDraw.color (0.3, 0.3, 0.3) ~alpha:0.5;
4228 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
4229 filledrect (float x0) (float y0) (float x1) (float y1);
4230 Gl.disable `blend;
4231 | Msel _
4232 | Mpan _
4233 | Mscrolly | Mscrollx
4234 | Mzoom _
4235 | Mnone -> ()
4236 end;
4237 enttext ();
4238 scrollindicator ();
4239 Wsi.swapb ();
4242 let display () =
4243 match state.reload with
4244 | Some (x, y, t) ->
4245 if x != state.x || y != state.y || abs_float @@ now () -. t > 0.5
4246 || (state.layout != [] && layoutready state.layout)
4247 then (
4248 state.reload <- None;
4249 display ()
4251 | None -> display ()
4254 let zoomrect x y x1 y1 =
4255 let x0 = min x x1
4256 and x1 = max x x1
4257 and y0 = min y y1 in
4258 let zoom = (float state.w) /. float (x1 - x0) in
4259 let margin =
4260 let simple () =
4261 if state.w < state.winw
4262 then (state.winw - state.w) / 2
4263 else 0
4265 match conf.fitmodel with
4266 | FitWidth | FitProportional -> simple ()
4267 | FitPage ->
4268 match conf.columns with
4269 | Csplit _ ->
4270 onppundermouse (fun _ l _ _ -> Some l.pagedispx) x0 y0 x0
4271 | Cmulti _ | Csingle _ -> simple ()
4273 gotoxy ((state.x + margin) - x0) (state.y + y0);
4274 state.anchor <- getanchor ();
4275 setzoom zoom;
4276 resetmstate ();
4279 let annot inline x y =
4280 match unproject x y with
4281 | Some (opaque, n, ux, uy) ->
4282 let add text =
4283 Ffi.addannot opaque ux uy text;
4284 wcmd "freepage %s" (~> opaque);
4285 Hashtbl.remove state.pagemap (n, state.gen);
4286 flushtiles ();
4287 gotoxy state.x state.y
4289 if inline
4290 then
4291 let ondone s = add s in
4292 let mode = state.mode in
4293 state.mode <- Textentry (
4294 ("annotation: ", E.s, None, textentry, ondone, true),
4295 fun _ -> state.mode <- mode);
4296 state.text <- E.s;
4297 enttext ();
4298 postRedisplay "annot"
4299 else add @@ getusertext E.s
4300 | _ -> ()
4303 let zoomblock x y =
4304 let g opaque l px py =
4305 match Ffi.rectofblock opaque px py with
4306 | Some a ->
4307 let x0 = a.(0) -. 20. in
4308 let x1 = a.(1) +. 20. in
4309 let y0 = a.(2) -. 20. in
4310 let zoom = (float state.w) /. (x1 -. x0) in
4311 let pagey = getpagey l.pageno in
4312 let margin = (state.w - l.pagew)/2 in
4313 let nx = -truncate x0 - margin in
4314 gotoxy nx (pagey + truncate y0);
4315 state.anchor <- getanchor ();
4316 setzoom zoom;
4317 None
4318 | None -> None
4320 match conf.columns with
4321 | Csplit _ ->
4322 impmsg "block zooming does not work properly in split columns mode"
4323 | Cmulti _ | Csingle _ -> onppundermouse g x y ()
4326 let scrollx x =
4327 let winw = state.winw - 1 in
4328 let s = float x /. float winw in
4329 let destx = truncate (float (state.w + winw) *. s) in
4330 gotoxy (winw - destx) state.y;
4331 state.mstate <- Mscrollx;
4334 let scrolly y =
4335 let s = float y /. float state.winh in
4336 let desty = truncate (s *. float (maxy ())) in
4337 gotoxy state.x desty;
4338 state.mstate <- Mscrolly;
4341 let viewmulticlick clicks x y mask =
4342 let g opaque l px py =
4343 let mark =
4344 match clicks with
4345 | 2 -> Mark_word
4346 | 3 -> Mark_line
4347 | 4 -> Mark_block
4348 | _ -> Mark_page
4350 if Ffi.markunder opaque px py mark
4351 then (
4352 Some (fun () ->
4353 let dopipe cmd =
4354 match getopaque l.pageno with
4355 | None -> ()
4356 | Some opaque -> pipesel opaque cmd
4358 state.roam <- (fun () -> dopipe conf.paxcmd);
4359 if not (Wsi.withctrl mask) then dopipe conf.selcmd;
4362 else None
4364 postRedisplay "viewmulticlick";
4365 onppundermouse g x y (fun () -> impmsg "nothing to select") ();
4368 let canselect () =
4369 match conf.columns with
4370 | Csplit _ -> false
4371 | Csingle _ | Cmulti _ -> conf.angle mod 360 = 0
4374 let viewmouse button down x y mask =
4375 match button with
4376 | n when (n == 4 || n == 5) && not down ->
4377 if Wsi.withctrl mask
4378 then (
4379 let incr =
4380 if n = 5
4381 then if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01
4382 else if conf.zoom -. 0.1 < 0.1 then -0.01 else -0.1
4384 let fx, fy =
4385 match state.mstate with
4386 | Mzoom (oldn, _, pos) when n = oldn -> pos
4387 | Mzoomrect _ | Mnone | Mpan _
4388 | Msel _ | Mscrollx | Mscrolly | Mzoom _ -> (x, y)
4390 let zoom = conf.zoom -. incr in
4391 state.mstate <- Mzoom (n, 0, (x, y));
4392 if false && abs (fx - x) > 5 || abs (fy - y) > 5
4393 then pivotzoom ~x ~y zoom
4394 else pivotzoom zoom
4396 else (
4397 match state.autoscroll with
4398 | Some step -> setautoscrollspeed step (n=4)
4399 | None ->
4400 if conf.wheelbypage || conf.presentation
4401 then (
4402 if n = 4
4403 then prevpage ()
4404 else nextpage ()
4406 else
4407 let incr = if n = 4 then -conf.scrollstep else conf.scrollstep in
4408 let incr = incr * 2 in
4409 let y = clamp incr in
4410 gotoxy state.x y
4413 | n when (n = 6 || n = 7) && not down && canpan () ->
4414 let x =
4415 panbound (state.x + (if n = 7 then -2 else 2) * conf.hscrollstep) in
4416 gotoxy x state.y
4418 | 1 when Wsi.withshift mask ->
4419 state.mstate <- Mnone;
4420 if not down
4421 then (
4422 match unproject x y with
4423 | None -> ()
4424 | Some (_, pageno, ux, uy) ->
4425 let cmd = Printf.sprintf
4426 "%s %s %d %d %d"
4427 conf.stcmd state.path pageno ux uy
4429 match spawn cmd [] with
4430 | exception exn ->
4431 impmsg "execution of synctex command(%S) failed: %S"
4432 conf.stcmd @@ exntos exn
4433 | _pid -> ()
4436 | 1 when Wsi.withctrl mask ->
4437 if down
4438 then (
4439 Wsi.setcursor Wsi.CURSOR_FLEUR;
4440 state.mstate <- Mpan (x, y)
4442 else state.mstate <- Mnone
4444 | 3 ->
4445 if down
4446 then (
4447 if Wsi.withshift mask
4448 then (
4449 annot conf.annotinline x y;
4450 postRedisplay "addannot"
4452 else
4453 let p = (x, y) in
4454 Wsi.setcursor Wsi.CURSOR_CYCLE;
4455 state.mstate <- Mzoomrect (p, p)
4457 else (
4458 match state.mstate with
4459 | Mzoomrect ((x0, y0), _) ->
4460 if abs (x-x0) > 10 && abs (y - y0) > 10
4461 then zoomrect x0 y0 x y
4462 else (
4463 resetmstate ();
4464 postRedisplay "kill accidental zoom rect";
4466 | Msel _
4467 | Mpan _
4468 | Mscrolly | Mscrollx
4469 | Mzoom _
4470 | Mnone -> resetmstate ()
4473 | 1 when vscrollhit x ->
4474 if down
4475 then
4476 let _, position, sh = state.uioh#scrollph in
4477 if y > truncate position && y < truncate (position +. sh)
4478 then state.mstate <- Mscrolly
4479 else scrolly y
4480 else state.mstate <- Mnone
4482 | 1 when y > state.winh - hscrollh () ->
4483 if down
4484 then
4485 let _, position, sw = state.uioh#scrollpw in
4486 if x > truncate position && x < truncate (position +. sw)
4487 then state.mstate <- Mscrollx
4488 else scrollx x
4489 else state.mstate <- Mnone
4491 | 1 when state.bzoom -> if not down then zoomblock x y
4493 | 1 ->
4494 let dest = if down then getunder x y else Unone in
4495 begin match dest with
4496 | Ulinkuri _ -> gotounder dest
4497 | Unone when down ->
4498 Wsi.setcursor Wsi.CURSOR_FLEUR;
4499 state.mstate <- Mpan (x, y);
4500 | Uannotation (opaque, slinkindex) -> enterannotmode opaque slinkindex
4501 | Unone | Utext _ ->
4502 if down
4503 then (
4504 if canselect ()
4505 then (
4506 state.mstate <- Msel ((x, y), (x, y));
4507 postRedisplay "mouse select";
4510 else (
4511 match state.mstate with
4512 | Mnone -> ()
4513 | Mzoom _ | Mscrollx | Mscrolly -> state.mstate <- Mnone
4514 | Mzoomrect ((x0, y0), _) -> zoomrect x0 y0 x y
4515 | Mpan _ ->
4516 Wsi.setcursor Wsi.CURSOR_INHERIT;
4517 state.mstate <- Mnone
4518 | Msel ((x0, y0), (x1, y1)) ->
4519 let rec loop = function
4520 | [] -> ()
4521 | l :: rest ->
4522 let inside =
4523 let a0 = l.pagedispy in
4524 let a1 = a0 + l.pagevh in
4525 let b0 = l.pagedispx in
4526 let b1 = b0 + l.pagevw in
4527 ((y0 >= a0 && y0 <= a1) || (y1 >= a0 && y1 <= a1))
4528 && ((x0 >= b0 && x0 <= b1) || (x1 >= b0 && x1 <= b1))
4530 if inside
4531 then
4532 match getopaque l.pageno with
4533 | Some opaque ->
4534 let dosel cmd () =
4535 pipef ~closew:false "Msel"
4536 (fun w ->
4537 Ffi.copysel w opaque;
4538 postRedisplay "Msel") cmd
4540 dosel conf.selcmd ();
4541 state.roam <- dosel conf.paxcmd;
4542 | None -> ()
4543 else loop rest
4545 loop state.layout;
4546 resetmstate ();
4549 | _ -> ()
4552 let birdseyemouse button down x y mask
4553 (conf, leftx, _, hooverpageno, anchor) =
4554 match button with
4555 | 1 when down ->
4556 let rec loop = function
4557 | [] -> ()
4558 | l :: rest ->
4559 if y > l.pagedispy && y < l.pagedispy + l.pagevh
4560 && x > l.pagedispx && x < l.pagedispx + l.pagevw
4561 then
4562 leavebirdseye (conf, leftx, l.pageno, hooverpageno, anchor) false
4563 else loop rest
4565 loop state.layout
4566 | 3 -> ()
4567 | _ -> viewmouse button down x y mask
4570 let uioh = object
4571 method display = ()
4573 method key key mask =
4574 begin match state.mode with
4575 | Textentry textentry -> textentrykeyboard key mask textentry
4576 | Birdseye birdseye -> birdseyekeyboard key mask birdseye
4577 | View -> viewkeyboard key mask
4578 | LinkNav linknav -> linknavkeyboard key mask linknav
4579 end;
4580 state.uioh
4582 method button button bstate x y mask =
4583 begin match state.mode with
4584 | LinkNav _ | View -> viewmouse button bstate x y mask
4585 | Birdseye beye -> birdseyemouse button bstate x y mask beye
4586 | Textentry _ -> ()
4587 end;
4588 state.uioh
4590 method multiclick clicks x y mask =
4591 begin match state.mode with
4592 | LinkNav _ | View -> viewmulticlick clicks x y mask
4593 | Birdseye _ | Textentry _ -> ()
4594 end;
4595 state.uioh
4597 method motion x y =
4598 begin match state.mode with
4599 | Textentry _ -> ()
4600 | View | Birdseye _ | LinkNav _ ->
4601 match state.mstate with
4602 | Mzoom _ | Mnone -> ()
4603 | Mpan (x0, y0) ->
4604 let dx = x - x0
4605 and dy = y0 - y in
4606 state.mstate <- Mpan (x, y);
4607 let x = if canpan () then panbound (state.x + dx) else state.x in
4608 let y = clamp dy in
4609 gotoxy x y
4611 | Msel (a, _) ->
4612 state.mstate <- Msel (a, (x, y));
4613 postRedisplay "motion select";
4615 | Mscrolly ->
4616 let y = min state.winh (max 0 y) in
4617 scrolly y
4619 | Mscrollx ->
4620 let x = min state.winw (max 0 x) in
4621 scrollx x
4623 | Mzoomrect (p0, _) ->
4624 state.mstate <- Mzoomrect (p0, (x, y));
4625 postRedisplay "motion zoomrect";
4626 end;
4627 state.uioh
4629 method pmotion x y =
4630 begin match state.mode with
4631 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
4632 let rec loop = function
4633 | [] ->
4634 if hooverpageno != -1
4635 then (
4636 state.mode <- Birdseye (conf, leftx, pageno, -1, anchor);
4637 postRedisplay "pmotion birdseye no hoover";
4639 | l :: rest ->
4640 if y > l.pagedispy && y < l.pagedispy + l.pagevh
4641 && x > l.pagedispx && x < l.pagedispx + l.pagevw
4642 then (
4643 state.mode <- Birdseye (conf, leftx, pageno, l.pageno, anchor);
4644 postRedisplay "pmotion birdseye hoover";
4646 else loop rest
4648 loop state.layout
4650 | Textentry _ -> ()
4652 | LinkNav _ | View ->
4653 match state.mstate with
4654 | Mpan _ | Msel _ | Mzoom _ | Mscrolly | Mscrollx | Mzoomrect _ -> ()
4655 | Mnone ->
4656 updateunder x y;
4657 if canselect ()
4658 then
4659 match conf.pax with
4660 | None -> ()
4661 | Some past ->
4662 let now = now () in
4663 let delta = now -. past in
4664 if delta > 0.01
4665 then paxunder x y
4666 else conf.pax <- Some now
4667 end;
4668 state.uioh
4670 method infochanged _ = ()
4672 method scrollph =
4673 let maxy = maxy () in
4674 let p, h =
4675 if maxy = 0
4676 then 0.0, float state.winh
4677 else scrollph state.y maxy
4679 vscrollw (), p, h
4681 method scrollpw =
4682 let fwinw = float (state.winw - vscrollw ()) in
4683 let sw =
4684 let sw = fwinw /. float state.w in
4685 let sw = fwinw *. sw in
4686 max sw (float conf.scrollh)
4688 let position =
4689 let maxx = state.w + state.winw in
4690 let x = state.winw - state.x in
4691 let percent = float x /. float maxx in
4692 (fwinw -. sw) *. percent
4694 hscrollh (), position, sw
4696 method modehash =
4697 let modename =
4698 match state.mode with
4699 | LinkNav _ -> "links"
4700 | Textentry _ -> "textentry"
4701 | Birdseye _ -> "birdseye"
4702 | View -> "view"
4704 findkeyhash conf modename
4706 method eformsgs = true
4707 method alwaysscrolly = false
4708 method scroll dx dy =
4709 let x = if canpan () then panbound (state.x + dx) else state.x in
4710 gotoxy x (clamp (2 * dy));
4711 state.uioh
4712 method zoom z x y =
4713 pivotzoom ~x ~y (conf.zoom *. exp z);
4714 end;;
4716 let addrect pageno r g b a x0 y0 x1 y1 =
4717 Hashtbl.add state.prects pageno [|r; g; b; a; x0; y0; x1; y1|];
4720 let ract cmds =
4721 let cl = splitatchar cmds ' ' in
4722 let scan s fmt f =
4723 try Scanf.sscanf s fmt f
4724 with exn -> adderrfmt "remote exec" "error processing '%S': %s\n"
4725 cmds @@ exntos exn
4727 let rectx s pageno (r, g, b, a) x0 y0 x1 y1 =
4728 vlog "%s page %d color (%f %f %f %f) x0,y0,x1,y1 = %f %f %f %f"
4729 s pageno r g b a x0 y0 x1 y1;
4730 onpagerect
4731 pageno
4732 (fun w h ->
4733 let _,w1,h1,_ = getpagedim pageno in
4734 let sw = float w1 /. float w
4735 and sh = float h1 /. float h in
4736 let x0s = x0 *. sw
4737 and x1s = x1 *. sw
4738 and y0s = y0 *. sh
4739 and y1s = y1 *. sh in
4740 let rect = (x0s,y0s,x1s,y0s,x1s,y1s,x0s,y1s) in
4741 let color = (r, g, b, a) in
4742 if conf.verbose then debugrect rect;
4743 state.rects <- (pageno, color, rect) :: state.rects;
4744 postRedisplay s;
4747 match cl with
4748 | "reload", "" -> reload ()
4749 | "goto", args ->
4750 scan args "%u %f %f"
4751 (fun pageno x y ->
4752 let cmd, _ = state.geomcmds in
4753 if emptystr cmd
4754 then gotopagexy pageno x y
4755 else
4756 let f prevf () =
4757 gotopagexy pageno x y;
4758 prevf ()
4760 state.reprf <- f state.reprf
4762 | "goto1", args -> scan args "%u %f" gotopage
4763 | "gotor", args -> scan args "%S" gotoremote
4764 | "rect", args ->
4765 scan args "%u %u %f %f %f %f"
4766 (fun pageno c x0 y0 x1 y1 ->
4767 let color = (0.0, 0.0, 1.0 /. float c, 0.5) in
4768 rectx "rect" pageno color x0 y0 x1 y1;
4770 | "prect", args ->
4771 scan args "%u %f %f %f %f %f %f %f %f"
4772 (fun pageno r g b alpha x0 y0 x1 y1 ->
4773 addrect pageno r g b alpha x0 y0 x1 y1;
4774 postRedisplay "prect"
4776 | "pgoto", args ->
4777 scan args "%u %f %f"
4778 (fun pageno x y ->
4779 let optopaque =
4780 match getopaque pageno with
4781 | Some opaque -> opaque
4782 | None -> ~< E.s
4784 pgoto optopaque pageno x y;
4785 let rec fixx = function
4786 | [] -> ()
4787 | l :: rest ->
4788 if l.pageno = pageno
4789 then gotoxy (state.x - l.pagedispx) state.y
4790 else fixx rest
4792 let layout =
4793 let mult =
4794 match conf.columns with
4795 | Csingle _ | Csplit _ -> 1
4796 | Cmulti ((n, _, _), _) -> n
4798 layout 0 state.y (state.winw * mult) state.winh
4800 fixx layout
4802 | "activatewin", "" -> Wsi.activatewin ()
4803 | "quit", "" -> raise Quit
4804 | "keys", keys ->
4805 begin try
4806 let l = Config.keys_of_string keys in
4807 List.iter (fun (k, m) -> keyboard k m) l
4808 with exn -> adderrfmt "error processing keys" "`%S': %s\n"
4809 cmds @@ exntos exn
4811 | "clearrects", "" ->
4812 Hashtbl.clear state.prects;
4813 postRedisplay "clearrects"
4814 | _ ->
4815 adderrfmt "remote command"
4816 "error processing remote command: %S\n" cmds;
4819 let remote =
4820 let scratch = Bytes.create 80 in
4821 let buf = Buffer.create 80 in
4822 fun fd ->
4823 match tempfailureretry (Unix.read fd scratch 0) 80 with
4824 | exception Unix.Unix_error (Unix.EAGAIN, _, _) -> None
4825 | 0 ->
4826 Unix.close fd;
4827 if Buffer.length buf > 0
4828 then (
4829 let s = Buffer.contents buf in
4830 Buffer.clear buf;
4831 ract s;
4833 None
4834 | n ->
4835 let rec eat ppos =
4836 let nlpos =
4837 match Bytes.index_from scratch ppos '\n' with
4838 | pos -> if pos >= n then -1 else pos
4839 | exception Not_found -> -1
4841 if nlpos >= 0
4842 then (
4843 Buffer.add_subbytes buf scratch ppos (nlpos-ppos);
4844 let s = Buffer.contents buf in
4845 Buffer.clear buf;
4846 ract s;
4847 eat (nlpos+1);
4849 else (
4850 Buffer.add_subbytes buf scratch ppos (n-ppos);
4851 Some fd
4853 in eat 0
4856 let remoteopen path =
4857 try Some (Unix.openfile path [Unix.O_NONBLOCK; Unix.O_RDONLY] 0o0)
4858 with exn ->
4859 adderrfmt "remoteopen" "error opening %S: %s" path @@ exntos exn;
4860 None
4863 let () =
4864 Utils.vlogf := (fun s -> if conf.verbose then prerr_endline s else ignore s);
4865 let gcconfig = ref false in
4866 let rcmdpath = ref E.s in
4867 let pageno = ref None in
4868 let openlast = ref false in
4869 let doreap = ref false in
4870 let csspath = ref None in
4871 selfexec := Sys.executable_name;
4872 Arg.parse
4873 (Arg.align
4874 [("-p", Arg.String (fun s -> state.password <- s),
4875 "<password> Set password");
4877 ("-f", Arg.String
4878 (fun s ->
4879 Config.fontpath := s;
4880 selfexec := !selfexec ^ " -f " ^ Filename.quote s;
4882 "<path> Set path to the user interface font");
4884 ("-c", Arg.String
4885 (fun s ->
4886 selfexec := !selfexec ^ " -c " ^ Filename.quote s;
4887 Config.confpath := s),
4888 "<path> Set path to the configuration file");
4890 ("-last", Arg.Set openlast, " Open last document");
4892 ("-page", Arg.Int (fun pageno1 -> pageno := Some (pageno1-1)),
4893 "<page-number> Jump to page");
4895 ("-tcf", Arg.String (fun s -> Config.tcfpath := s),
4896 "<path> Set path to the trim cache file");
4898 ("-dest", Arg.String (fun s -> state.nameddest <- s),
4899 "<named-destination> Set named destination");
4901 ("-remote", Arg.String (fun s -> rcmdpath := s),
4902 "<path> Set path to the source of remote commands");
4904 ("-gc", Arg.Set gcconfig, " Collect config garbage");
4906 ("-v", Arg.Unit (fun () ->
4907 Printf.printf
4908 "%s\nconfiguration file: %s\n"
4909 (Help.version ())
4910 Config.defconfpath;
4911 exit 0), " Print version and exit");
4913 ("-css", Arg.String (fun s -> csspath := Some s),
4914 "<path> Set path to the style sheet to use with EPUB/HTML");
4916 ("-origin", Arg.String (fun s -> state.origin <- s),
4917 "<origin> <undocumented>");
4919 ("-no-title", Arg.Set ignoredoctitlte, " ignore document title");
4920 ("-layout-height", Arg.Set_int layouth,
4921 "<height> layout height html/epub/etc (-1, 0, N)");
4924 (fun s -> state.path <- s)
4925 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\nOptions:");
4927 let histmode = emptystr state.path && not !openlast in
4929 if not (Config.load !openlast)
4930 then dolog "failed to load configuration";
4932 begin match !pageno with
4933 | Some pageno -> state.anchor <- (pageno, 0.0, 0.0)
4934 | None -> ()
4935 end;
4937 fillhelp ();
4938 if !gcconfig
4939 then (
4940 Config.gc ();
4941 exit 0
4944 let mu =
4945 object (self)
4946 val mutable m_clicks = 0
4947 val mutable m_click_x = 0
4948 val mutable m_click_y = 0
4949 val mutable m_lastclicktime = infinity
4951 method private cleanup =
4952 state.roam <- noroam;
4953 Hashtbl.iter (fun _ opaque -> Ffi.clearmark opaque) state.pagemap
4954 method expose = postRedisplay "expose"
4955 method visible v =
4956 let name =
4957 match v with
4958 | Wsi.Unobscured -> "unobscured"
4959 | Wsi.PartiallyObscured -> "partiallyobscured"
4960 | Wsi.FullyObscured -> "fullyobscured"
4962 vlog "visibility change %s" name
4963 method display = display ()
4964 method map mapped = vlog "mapped %b" mapped
4965 method reshape w h =
4966 self#cleanup;
4967 reshape w h
4968 method mouse b d x y m =
4969 if d && canselect ()
4970 then (
4972 * http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
4974 m_click_x <- x;
4975 m_click_y <- y;
4976 if b = 1
4977 then (
4978 let t = now () in
4979 if abs x - m_click_x > 10
4980 || abs y - m_click_y > 10
4981 || abs_float (t -. m_lastclicktime) > 0.3
4982 then m_clicks <- 0;
4983 m_clicks <- m_clicks + 1;
4984 m_lastclicktime <- t;
4985 if m_clicks = 1
4986 then (
4987 self#cleanup;
4988 postRedisplay "cleanup";
4989 state.uioh <- state.uioh#button b d x y m;
4991 else state.uioh <- state.uioh#multiclick m_clicks x y m
4993 else (
4994 self#cleanup;
4995 m_clicks <- 0;
4996 m_lastclicktime <- infinity;
4997 state.uioh <- state.uioh#button b d x y m
5000 else state.uioh <- state.uioh#button b d x y m
5001 method motion x y =
5002 state.mpos <- (x, y);
5003 state.uioh <- state.uioh#motion x y
5004 method pmotion x y =
5005 state.mpos <- (x, y);
5006 state.uioh <- state.uioh#pmotion x y
5007 method key k m =
5008 vlog "k=%#x m=%#x" k m;
5009 let mascm = m land (
5010 Wsi.altmask + Wsi.shiftmask + Wsi.ctrlmask + Wsi.metamask
5011 ) in
5012 let keyboard k m =
5013 let x = state.x and y = state.y in
5014 keyboard k m;
5015 if x != state.x || y != state.y then self#cleanup
5017 match state.keystate with
5018 | KSnone ->
5019 let km = k, mascm in
5020 begin
5021 match
5022 let modehash = state.uioh#modehash in
5023 try Hashtbl.find modehash km
5024 with Not_found ->
5025 try Hashtbl.find (findkeyhash conf "global") km
5026 with Not_found -> KMinsrt (k, m)
5027 with
5028 | KMinsrt (k, m) -> keyboard k m
5029 | KMinsrl l -> List.iter (fun (k, m) -> keyboard k m) l
5030 | KMmulti (l, r) -> state.keystate <- KSinto (l, r)
5032 | KSinto ((k', m') :: [], insrt) when k'=k && m' land mascm = m' ->
5033 List.iter (fun (k, m) -> keyboard k m) insrt;
5034 state.keystate <- KSnone
5035 | KSinto ((k', m') :: keys, insrt) when k'=k && m' land mascm = m' ->
5036 state.keystate <- KSinto (keys, insrt)
5037 | KSinto _ -> state.keystate <- KSnone
5039 method enter x y =
5040 state.mpos <- (x, y);
5041 state.uioh <- state.uioh#pmotion x y
5042 method leave = state.mpos <- (-1, -1)
5043 method winstate wsl = state.winstate <- wsl
5044 method quit : 'a. 'a = raise Quit
5045 method scroll dx dy = state.uioh <- state.uioh#scroll dx dy
5046 method zoom z x y = state.uioh#zoom z x y
5047 method opendoc path =
5048 state.mode <- View;
5049 state.uioh <- uioh;
5050 postRedisplay "opendoc";
5051 opendoc path state.password
5054 if !Config.tcfpath == E.s
5055 then Config.tcfpath := conf.trimcachepath;
5056 let wsfd, winw, winh = Wsi.init mu conf.cwinw conf.cwinh platform in
5057 state.wsfd <- wsfd;
5059 if not @@ List.exists GlMisc.check_extension
5060 [ "GL_ARB_texture_rectangle"
5061 ; "GL_EXT_texture_recangle"
5062 ; "GL_NV_texture_rectangle" ]
5063 then (dolog "OpenGL does not suppport rectangular textures"; exit 1);
5065 let cs, ss =
5066 match Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 with
5067 | exception exn ->
5068 dolog "socketpair failed: %s" @@ exntos exn;
5069 exit 1
5070 | (r, w) ->
5071 cloexec r;
5072 cloexec w;
5073 r, w
5076 setcheckers conf.checkers;
5077 begin match !csspath with
5078 | None -> ()
5079 | Some "" -> conf.css <- E.s
5080 | Some path ->
5081 let css = filecontents path in
5082 let l = String.length css in
5083 conf.css <-
5084 if substratis css (l-2) "\r\n"
5085 then String.sub css 0 (l-2)
5086 else (if css.[l-1] = '\n' then String.sub css 0 (l-1) else css)
5087 end;
5088 Ffi.settrimcachepath !Config.tcfpath;
5089 conf.trimcachepath <- !Config.tcfpath;
5090 Ffi.init cs (
5091 conf.angle, conf.fitmodel, (conf.trimmargins, conf.trimfuzz),
5092 conf.texcount, conf.sliceheight, conf.mustoresize,
5093 conf.colorspace, !Config.fontpath
5095 List.iter GlArray.enable [`texture_coord; `vertex];
5096 GlTex.env (`color conf.texturecolor);
5097 state.ss <- ss;
5098 reshape ~firsttime:true winw winh;
5099 state.uioh <- uioh;
5100 if histmode
5101 then (
5102 Wsi.settitle "llpp (history)";
5103 enterhistmode ();
5105 else (
5106 state.text <- "Opening " ^ (mbtoutf8 state.path);
5107 opendoc state.path state.password;
5109 display ();
5110 Wsi.mapwin ();
5111 Wsi.setcursor Wsi.CURSOR_INHERIT;
5112 Sys.set_signal Sys.sighup (Sys.Signal_handle (fun _ -> reload ()));
5114 let rec reap () =
5115 match Unix.waitpid [Unix.WNOHANG] ~-1 with
5116 | exception (Unix.Unix_error (Unix.ECHILD, _, _)) -> ()
5117 | exception exn -> dolog "Unix.waitpid: %s" @@ exntos exn
5118 | 0, _ -> ()
5119 | _pid, _status -> reap ()
5121 Sys.set_signal Sys.sigchld (Sys.Signal_handle (fun _ -> doreap := true));
5123 let optrfd =
5124 ref (if nonemptystr !rcmdpath then remoteopen !rcmdpath else None)
5127 let rec loop deadline =
5128 if !doreap
5129 then (
5130 doreap := false;
5131 reap ()
5133 let r = [state.ss; state.wsfd] in
5134 let r =
5135 match !optrfd with
5136 | None -> r
5137 | Some fd -> fd :: r
5139 if !redisplay
5140 then (
5141 Glutils.redisplay := false;
5142 display ();
5144 let timeout =
5145 let now = now () in
5146 if deadline > now
5147 then (
5148 if deadline = infinity
5149 then ~-.1.0
5150 else max 0.0 (deadline -. now)
5152 else 0.0
5154 let r, _, _ =
5155 try Unix.select r [] [] timeout
5156 with Unix.Unix_error (Unix.EINTR, _, _) -> [], [], []
5158 begin match r with
5159 | [] ->
5160 let newdeadline =
5161 match state.autoscroll with
5162 | Some step when step != 0 ->
5163 if state.slideshow land 1 = 1
5164 then (
5165 if state.slideshow land 2 = 0
5166 then state.slideshow <- state.slideshow lor 2
5167 else if step < 0 then prevpage () else nextpage ();
5168 deadline +. (float (abs step))
5170 else
5171 let y = state.y + step in
5172 let fy = if conf.maxhfit then state.winh else 0 in
5173 let y =
5174 if y < 0
5175 then state.maxy - fy
5176 else if y >= state.maxy - fy then 0 else y
5178 gotoxy state.x y;
5179 deadline +. 0.01
5180 | _ -> infinity
5182 loop newdeadline
5184 | l ->
5185 let rec checkfds = function
5186 | [] -> ()
5187 | fd :: rest when fd = state.ss ->
5188 let cmd = Ffi.rcmd state.ss in
5189 act cmd;
5190 checkfds rest
5192 | fd :: rest when fd = state.wsfd ->
5193 Wsi.readresp fd;
5194 checkfds rest
5196 | fd :: rest when Some fd = !optrfd ->
5197 begin match remote fd with
5198 | None -> optrfd := remoteopen !rcmdpath;
5199 | opt -> optrfd := opt
5200 end;
5201 checkfds rest
5203 | _ :: rest ->
5204 dolog "select returned unknown descriptor";
5205 checkfds rest
5207 checkfds l;
5208 let newdeadline =
5209 let deadline1 =
5210 if deadline = infinity
5211 then now () +. 0.01
5212 else deadline
5214 match state.autoscroll with
5215 | Some step when step != 0 -> deadline1
5216 | _ -> infinity
5218 loop newdeadline
5219 end;
5221 match loop infinity with
5222 | exception Quit ->
5223 Config.save leavebirdseye;
5224 if Ffi.hasunsavedchanges ()
5225 then save ()
5226 | _ -> error "umpossible - infinity reached"