Take layout defaults from mupdf
[llpp.git] / main.ml
blob7c9f7c5e95ae5b97e992f3a98a8619fd18057606
1 open Utils
2 open Config
3 open Glutils
4 open Uiutils
6 module U = struct
7 let dopen = '\023'
8 let cs = '\024'
9 let freepage = '\025'
10 let freetile = '\026'
11 let search = '\027'
12 let geometry = '\028'
13 let reqlayout = '\029'
14 let page = '\030'
15 let tile = '\031'
16 let trimset = '\032'
17 let settrim = '\033'
18 let sliceh = '\034'
19 let interrupt = '\035'
20 let pgscale h = truncate (float h *. conf.pgscale)
21 let nogeomcmds = function | s, [] -> emptystr s | _ -> false
22 let maxy () = !S.maxy - if conf.maxhfit then !S.winh else 0
23 let clamp incr = bound (!S.y + incr) 0 @@ maxy ()
24 let scalecolor c = let c = c *. conf.colorscale in (c, c, c)
25 let panbound x = bound x (- !S.w) !S.winw
26 let pagevisible layout n = List.exists (fun l -> l.pageno = n) layout
27 end
29 let debugrect (x0, y0, x1, y1, x2, y2, x3, y3) =
30 dolog {|rect {
31 x0,y0=(% f, % f)
32 x1,y1=(% f, % f)
33 x2,y2=(% f, % f)
34 x3,y3=(% f, % f)
35 }|} x0 y0 x1 y1 x2 y2 x3 y3
37 let hscrollh () =
38 if ((conf.scrollb land scrollbhv != 0) && (!S.w > !S.winw))
39 || !S.uioh#alwaysscrolly
40 then conf.scrollbw
41 else 0
43 let setfontsize n =
44 fstate.fontsize <- n;
45 fstate.wwidth <- Ffi.measurestr fstate.fontsize "w";
46 fstate.maxrows <- (!S.winh - fstate.fontsize - 1) / (fstate.fontsize + 1)
48 let showtext c s =
49 S.text := Printf.sprintf "%c%s" c s;
50 postRedisplay "showtext"
52 let adderrmsg src msg =
53 Buffer.add_string S.errmsgs msg;
54 S.newerrmsgs := true;
55 postRedisplay src
57 let settextfmt fmt = Printf.kprintf (fun s -> S.text := s) fmt
58 let impmsg fmt = Printf.ksprintf (fun s -> showtext '!' s) fmt
59 let adderrfmt src fmt = Printf.ksprintf (fun s -> adderrmsg src s) fmt
61 let launchpath () =
62 if emptystr conf.pathlauncher
63 then adderrmsg "path launcher" "command set"
64 else
65 let cmd = Str.global_replace Re.percent !S.path conf.pathlauncher in
66 match spawn cmd [] with
67 | exception exn ->
68 adderrfmt "spawn" "failed to execute `%s': %s" cmd @@ exntos exn
69 | _pid -> ()
71 let getopaque pageno = Hashtbl.find S.pagemap (pageno, !S.gen)
73 let pagetranslatepoint l x y =
74 let dy = y - l.pagedispy in
75 let y = dy + l.pagey in
76 let dx = x - l.pagedispx in
77 let x = dx + l.pagex in
78 (x, y)
80 let onppundermouse g x y d =
81 let rec f = function
82 | [] -> d
83 | l :: rest ->
84 match getopaque l.pageno with
85 | exception Not_found -> f rest
86 | opaque ->
87 let x0 = l.pagedispx in
88 let x1 = x0 + l.pagevw in
89 let y0 = l.pagedispy in
90 let y1 = y0 + l.pagevh in
91 if y >= y0 && y <= y1 && x >= x0 && x <= x1
92 then
93 let px, py = pagetranslatepoint l x y in
94 match g opaque l px py with
95 | Some res -> res
96 | None -> f rest
97 else f rest
99 f !S.layout
101 let getunder x y =
102 let g opaque l px py =
103 if !S.bzoom
104 then (
105 match Ffi.rectofblock opaque px py with
106 | Some [|x0;x1;y0;y1|] ->
107 let rect = (x0, y0, x1, y0, x1, y1, x0, y1) in
108 let color = (0.0, 0.0, 1.0 /. (l.pageno mod 3 |> float), 0.5) in
109 S.rects := [l.pageno, color, rect];
110 postRedisplay "getunder";
111 | _ -> ()
113 let under = Ffi.whatsunder opaque px py in
114 if under = Unone then None else Some under
116 onppundermouse g x y Unone
118 let unproject x y =
119 let g opaque l x y =
120 match Ffi.unproject opaque x y with
121 | Some (x, y) -> Some (Some (opaque, l.pageno, x, y))
122 | None -> None
124 onppundermouse g x y None
126 let pipesel opaque cmd =
127 if Ffi.hassel opaque
128 then pipef ~closew:false "pipesel"
129 (fun w ->
130 Ffi.copysel w opaque;
131 postRedisplay "pipesel"
132 ) cmd
134 let paxunder x y =
135 let g opaque l px py =
136 if Ffi.markunder opaque px py conf.paxmark
137 then
138 Some (fun () ->
139 match getopaque l.pageno with
140 | exception Not_found -> ()
141 | opaque -> pipesel opaque conf.paxcmd
143 else None
145 postRedisplay "paxunder";
146 if conf.paxmark = Mark_page
147 then
148 List.iter (fun l ->
149 match getopaque l.pageno with
150 | exception Not_found -> ()
151 | opaque -> Ffi.clearmark opaque) !S.layout;
152 S.roamf := onppundermouse g x y (fun () -> impmsg "whoopsie daisy")
154 let undertext = function
155 | Unone -> "none"
156 | Ulinkuri s -> s
157 | Utext s -> "font: " ^ s
158 | Uannotation (opaque, slinkindex) ->
159 "annotation: " ^ Ffi.getannotcontents opaque slinkindex
161 let updateunder x y =
162 match getunder x y with
163 | Unone -> Wsi.setcursor Wsi.CURSOR_INHERIT
164 | Ulinkuri uri ->
165 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
166 Wsi.setcursor Wsi.CURSOR_INFO
167 | Utext s ->
168 if conf.underinfo then showtext 'f' ("ont: " ^ s);
169 Wsi.setcursor Wsi.CURSOR_TEXT
170 | Uannotation _ ->
171 if conf.underinfo then showtext 'a' "nnotation";
172 Wsi.setcursor Wsi.CURSOR_INFO
174 let showlinktype under =
175 if conf.underinfo && under != Unone
176 then showtext ' ' @@ undertext under
178 let intentry_with_suffix text key =
179 let text =
180 match [@warning "-fragile-match"] key with
181 | Keys.Ascii ('0'..'9' as c) -> addchar text c
182 | Keys.Ascii ('k' | 'm' | 'g' | 'K' | 'M' | 'G' as c) ->
183 addchar text @@ Char.lowercase_ascii c
184 | _ ->
185 S.text := "invalid key";
186 text
188 TEcont text
190 let wcmd cmd fmt =
191 let b = Buffer.create 16 in
192 Printf.kbprintf
193 (fun b ->
194 Buffer.add_char b cmd;
195 let b = Buffer.to_bytes b in
196 Ffi.wcmd !S.ss b @@ Bytes.length b
197 ) b fmt
199 let wcmd1 cmd opaque =
200 let s = Opaque.to_string opaque in
201 let l = String.length s in
202 let b = Bytes.create (l+1) in
203 Bytes.set b l cmd;
204 Bytes.blit_string s 0 b 0 l;
205 Ffi.wcmd !S.ss b @@ l + 1
207 let layoutN ((columns, coverA, coverB), b) x y sw sh =
208 let rec fold accu n =
209 if n = Array.length b
210 then accu
211 else
212 let pdimno, dx, vy, (_, w, h, xoff) = b.(n) in
213 if (vy - y) > sh
214 && (n = coverA - 1
215 || n = !S.pagecount - coverB
216 || (n - coverA) mod columns = columns - 1)
217 then accu
218 else
219 let accu =
220 if vy + h > y
221 then
222 let pagey = max 0 (y - vy) in
223 let pagedispy = if pagey > 0 then 0 else vy - y in
224 let pagedispx, pagex =
225 let pdx =
226 if n = coverA - 1 || n = !S.pagecount - coverB
227 then x + (sw - w) / 2
228 else dx + xoff + x
230 if pdx < 0
231 then 0, -pdx
232 else pdx, 0
234 let pagevw =
235 let vw = sw - pagedispx in
236 let pw = w - pagex in
237 min vw pw
239 let pagevh = min (h - pagey) (sh - pagedispy) in
240 if pagevw > 0 && pagevh > 0
241 then
242 { pageno = n
243 ; pagecol = 0 ; pagedimno = pdimno ; pagew = w ; pageh = h
244 ; pagex ; pagey ; pagevw ; pagevh ; pagedispx ; pagedispy
245 } :: accu
246 else accu
247 else accu
249 fold accu (n+1)
251 if Array.length b = 0
252 then []
253 else List.rev (fold [] (page_of_y y))
255 let layoutS (columns, b) x y sw sh =
256 let rec fold accu n =
257 if n = Array.length b
258 then accu
259 else
260 let pdimno, px, vy, (_, pagew, pageh, xoff) = b.(n) in
261 if (vy - y) > sh
262 then accu
263 else
264 let accu =
265 if vy + pageh > y
266 then
267 let x = xoff + x in
268 let pagey = max 0 (y - vy) in
269 let pagedispy = if pagey > 0 then 0 else vy - y in
270 let pagedispx, pagex =
271 if px = 0
272 then (
273 if x < 0
274 then 0, -x
275 else x, 0
277 else (
278 let px = px - x in
279 if px < 0
280 then -px, 0
281 else 0, px
284 let pagecolw = pagew/columns in
285 let pagedispx =
286 if pagecolw < sw
287 then pagedispx + ((sw - pagecolw) / 2)
288 else pagedispx
290 let pagevw =
291 let vw = sw - pagedispx in
292 let pw = pagew - pagex in
293 min vw pw
295 let pagevw = min pagevw pagecolw in
296 let pagevh = min (pageh - pagey) (sh - pagedispy) in
297 if pagevw > 0 && pagevh > 0
298 then
299 { pageno = n/columns
300 ; pagedimno = pdimno
301 ; pagecol = n mod columns
302 ; pagew ; pageh ; pagex ; pagey ; pagedispx ; pagedispy
303 ; pagevw ; pagevh
304 } :: accu
305 else accu
306 else accu
308 fold accu (n+1)
310 List.rev (fold [] 0)
312 let layout x y sw sh =
313 if U.nogeomcmds !S.geomcmds
314 then
315 match conf.columns with
316 | Csingle b -> layoutN ((1, 0, 0), b) x y sw sh
317 | Cmulti c -> layoutN c x y sw sh
318 | Csplit s -> layoutS s x y sw sh
319 else []
321 let itertiles l f =
322 let tilex = l.pagex mod conf.tilew in
323 let tiley = l.pagey mod conf.tileh in
325 let col = l.pagex / conf.tilew in
326 let row = l.pagey / conf.tileh in
328 let rec rowloop row y0 dispy h =
329 if h != 0
330 then
331 let dh = conf.tileh - y0 in
332 let dh = min h dh in
333 let rec colloop col x0 dispx w =
334 if w != 0
335 then
336 let dw = conf.tilew - x0 in
337 let dw = min w dw in
338 f col row dispx dispy x0 y0 dw dh;
339 colloop (col+1) 0 (dispx+dw) (w-dw)
341 colloop col tilex l.pagedispx l.pagevw;
342 rowloop (row+1) 0 (dispy+dh) (h-dh)
344 if l.pagevw > 0 && l.pagevh > 0
345 then rowloop row tiley l.pagedispy l.pagevh
347 let gettileopaque l col row =
348 let key = l.pageno, !S.gen, conf.colorspace,
349 conf.angle, l.pagew, l.pageh, col, row in
350 Hashtbl.find_opt S.tilemap key
352 let puttileopaque l col row gen colorspace angle opaque size elapsed =
353 let key = l.pageno, gen, colorspace, angle, l.pagew, l.pageh, col, row in
354 Hashtbl.add S.tilemap key (opaque, size, elapsed)
356 let drawtiles l color =
357 let texe e = if conf.invert then GlTex.env (`mode e) in
358 GlDraw.color color;
359 Ffi.begintiles ();
360 let f col row x y tilex tiley w h =
361 match gettileopaque l col row with
362 | Some (opaque, _, t) ->
363 let params = x, y, w, h, tilex, tiley in
364 texe `blend;
365 Ffi.drawtile params opaque;
366 texe `modulate;
367 if conf.debug
368 then (
369 Ffi.endtiles ();
370 let s = Printf.sprintf "%d[%d,%d] %f sec" l.pageno col row t in
371 let w = Ffi.measurestr fstate.fontsize s in
372 GlDraw.color (0.0, 0.0, 0.0);
373 filledrect
374 (float (x-2))
375 (float (y-2))
376 (float (x+2) +. w)
377 (float (y + fstate.fontsize + 2));
378 GlDraw.color color;
379 drawstring fstate.fontsize x (y + fstate.fontsize - 1) s;
380 Ffi.begintiles ();
383 | None ->
384 Ffi.endtiles ();
385 let w = let lw = !S.winw - x in min lw w
386 and h = let lh = !S.winh - y in min lh h in
387 texe `blend;
388 GlDraw.color (0.8, 0.8, 0.8);
389 filledrect (float x) (float y) (float (x+w)) (float (y+h));
390 texe `modulate;
391 if w > 128 && h > fstate.fontsize + 10
392 then (
393 let c = if conf.invert then 1.0 else 0.0 in
394 GlDraw.color (c, c, c);
395 let c, r =
396 if conf.verbose
397 then (col*conf.tilew, row*conf.tileh)
398 else col, row
400 drawstringf fstate.fontsize x y "Loading %d [%d,%d]" l.pageno c r;
402 GlDraw.color color;
403 Ffi.begintiles ();
405 itertiles l f;
406 Ffi.endtiles ()
408 let tilevisible1 l x y =
409 let ax0 = l.pagex
410 and ax1 = l.pagex + l.pagevw
411 and ay0 = l.pagey
412 and ay1 = l.pagey + l.pagevh in
414 let bx0 = x
415 and by0 = y in
416 let bx1 = min (bx0 + conf.tilew) l.pagew
417 and by1 = min (by0 + conf.tileh) l.pageh in
419 let rx0 = max ax0 bx0
420 and ry0 = max ay0 by0
421 and rx1 = min ax1 bx1
422 and ry1 = min ay1 by1 in
424 let nonemptyintersection = rx1 > rx0 && ry1 > ry0 in
425 nonemptyintersection
427 let tilevisible layout n x y =
428 let rec findpageinlayout m = function
429 | l :: rest when l.pageno = n ->
430 tilevisible1 l x y || (
431 match conf.columns with
432 | Csplit (c, _) when c > m -> findpageinlayout (m+1) rest
433 | Csplit _ | Csingle _ | Cmulti _ -> false
435 | _ :: rest -> findpageinlayout 0 rest
436 | [] -> false
438 findpageinlayout 0 layout
440 let tileready l x y =
441 tilevisible1 l x y &&
442 gettileopaque l (x/conf.tilew) (y/conf.tileh) != None
444 let tilepage n p layout =
445 let rec loop = function
446 | l :: rest ->
447 if l.pageno = n
448 then
449 let f col row _ _ _ _ _ _ =
450 if !S.currently = Idle
451 then
452 match gettileopaque l col row with
453 | Some _ -> ()
454 | None ->
455 let x = col*conf.tilew
456 and y = row*conf.tileh in
457 let w =
458 let w = l.pagew - x in
459 min w conf.tilew
461 let h =
462 let h = l.pageh - y in
463 min h conf.tileh
465 wcmd U.tile "%s %d %d %d %d" (Opaque.to_string p) x y w h;
466 S.currently :=
467 Tiling (
468 l, p, conf.colorspace, conf.angle,
469 !S.gen, col, row, conf.tilew, conf.tileh
472 itertiles l f;
473 else loop rest
475 | [] -> ()
477 if U.nogeomcmds !S.geomcmds
478 then loop layout
480 let preloadlayout x y sw sh =
481 let y = if y < sh then 0 else y - sh in
482 let x = min 0 (x + sw) in
483 let h = sh*3 in
484 let w = sw*3 in
485 layout x y w h
487 let load pages =
488 let rec loop pages =
489 if !S.currently = Idle
490 then
491 match pages with
492 | l :: rest ->
493 begin match getopaque l.pageno with
494 | exception Not_found ->
495 wcmd U.page "%d %d" l.pageno l.pagedimno;
496 S.currently := Loading (l, !S.gen);
497 | opaque ->
498 tilepage l.pageno opaque pages;
499 loop rest
501 | _ -> ()
503 if U.nogeomcmds !S.geomcmds
504 then loop pages
506 let preload pages =
507 load pages;
508 if conf.preload && !S.currently = Idle
509 then load (preloadlayout !S.x !S.y !S.winw !S.winh)
511 let layoutready layout =
512 let rec fold all ls =
513 all && match ls with
514 | l :: rest ->
515 let seen = ref false in
516 let allvisible = ref true in
517 let foo col row _ _ _ _ _ _ =
518 seen := true;
519 allvisible := !allvisible &&
520 begin match gettileopaque l col row with
521 | Some _ -> true
522 | None -> false
525 itertiles l foo;
526 fold (!seen && !allvisible) rest
527 | [] -> true
529 let alltilesvisible = fold true layout in
530 alltilesvisible
532 let gotoxy x y =
533 let y = bound y 0 !S.maxy in
534 let y, layout =
535 let layout = layout x y !S.winw !S.winh in
536 postRedisplay "gotoxy ready";
537 y, layout
539 S.x := x;
540 S.y := y;
541 S.layout := layout;
542 begin match !S.mode with
543 | LinkNav ln ->
544 begin match ln with
545 | Ltexact (pageno, linkno) ->
546 let rec loop = function
547 | [] ->
548 S.lnava := Some (pageno, linkno);
549 S.mode := LinkNav (Ltgendir 0)
550 | l :: _ when l.pageno = pageno ->
551 begin match getopaque pageno with
552 | exception Not_found ->
553 S.mode := LinkNav (Ltnotready (pageno, 0))
554 | opaque ->
555 let x0, y0, x1, y1 = Ffi.getlinkrect opaque linkno in
556 if not (x0 >= l.pagex && x1 <= l.pagex + l.pagevw
557 && y0 >= l.pagey && y1 <= l.pagey + l.pagevh)
558 then S.mode := LinkNav (Ltgendir 0)
560 | _ :: rest -> loop rest
562 loop layout
563 | Ltnotready _ | Ltgendir _ -> ()
565 | Birdseye _ | Textentry _ | View -> ()
566 end;
567 begin match !S.mode with
568 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
569 if not (U.pagevisible layout pageno)
570 then (
571 match !S.layout with
572 | [] -> ()
573 | l :: _ ->
574 S.mode := Birdseye (conf, leftx, l.pageno, hooverpageno, anchor)
576 | LinkNav lt ->
577 begin match lt with
578 | Ltnotready (_, dir)
579 | Ltgendir dir ->
580 let linknav =
581 let rec loop = function
582 | [] -> lt
583 | l :: rest ->
584 match getopaque l.pageno with
585 | exception Not_found -> Ltnotready (l.pageno, dir)
586 | opaque ->
587 let link =
588 let ld =
589 if dir = 0
590 then LDfirstvisible (l.pagex, l.pagey, dir)
591 else if dir > 0 then LDfirst else LDlast
593 Ffi.findlink opaque ld
595 match link with
596 | Lnotfound -> loop rest
597 | Lfound n ->
598 showlinktype (Ffi.getlink opaque n);
599 Ltexact (l.pageno, n)
601 loop !S.layout
603 S.mode := LinkNav linknav
604 | Ltexact _ -> ()
606 | Textentry _ | View -> ()
607 end;
608 preload layout;
609 if conf.updatecurs
610 then (
611 let mx, my = !S.mpos in
612 updateunder mx my;
615 let conttiling pageno opaque =
616 tilepage pageno opaque
617 (if conf.preload
618 then preloadlayout !S.x !S.y !S.winw !S.winh
619 else !S.layout)
621 let gotoxy x y =
622 if not conf.verbose then S.text := E.s;
623 gotoxy x y
625 let getanchory (n, top, dtop) =
626 let y, h = getpageyh n in
627 if conf.presentation
628 then
629 let ips = calcips h in
630 y + truncate (top*.float h -. dtop*.float ips) + ips;
631 else y + truncate (top*.float h -. dtop*.float conf.interpagespace)
633 let addnav () = S.nav := { past = getanchor () :: !S.nav.past; future = []; }
635 let gotopage n top =
636 let y, h = getpageyh n in
637 let y = y + (truncate (top *. float h)) in
638 gotoxy !S.x y
640 let gotopage1 n top =
641 let y = getpagey n in
642 let y = y + top in
643 gotoxy !S.x y
645 let invalidate s f =
646 Glutils.redisplay := false;
647 S.layout := [];
648 S.pdims := [];
649 S.rects := [];
650 S.rects1 := [];
651 match !S.geomcmds with
652 | ps, [] when emptystr ps ->
653 f ();
654 S.geomcmds := s, [];
655 | ps, [] -> S.geomcmds := ps, [s, f];
656 | ps, (s', _) :: rest when s' = s -> S.geomcmds := ps, ((s, f) :: rest);
657 | ps, cmds -> S.geomcmds := ps, ((s, f) :: cmds)
659 let flushpages () =
660 Hashtbl.iter (fun _ opaque -> wcmd1 U.freepage opaque) S.pagemap;
661 Hashtbl.clear S.pagemap
663 let flushtiles () =
664 if not (Queue.is_empty S.tilelru)
665 then (
666 Queue.iter (fun (k, p, s) ->
667 wcmd1 U.freetile p;
668 S.memused := !S.memused - s;
669 Hashtbl.remove S.tilemap k;
670 ) S.tilelru;
671 !S.uioh#infochanged Memused;
672 Queue.clear S.tilelru;
674 load !S.layout
676 let stateh h =
677 let h = truncate (float h*.conf.zoom) in
678 let d = conf.interpagespace lsl (if conf.presentation then 1 else 0) in
679 h - d
681 let fillhelp () =
682 S.help :=
683 let sl = keystostrlist conf in
684 let rec loop accu =
685 function | [] -> accu
686 | s :: rest -> loop ((s, 0, None) :: accu) rest
687 in Help.makehelp conf.urilauncher
688 @ (("", 0, None) :: loop [] sl) |> Array.of_list
690 let titlify path =
691 if emptystr path
692 then path
693 else
694 (if emptystr !S.origin then path else !S.origin)
695 |> Filename.basename |> Ffi.mbtoutf8
697 let settitle title =
698 conf.title <- title;
699 if not !S.ignoredoctitlte
700 then Wsi.settitle @@ title ^ " - llpp"
702 let opendoc path password =
703 S.path := path;
704 S.password := password;
705 S.gen := !S.gen + 1;
706 S.docinfo := [];
707 S.outlines := [||];
709 flushpages ();
710 Ffi.setaalevel conf.aalevel;
711 Ffi.setpapercolor conf.papercolor;
712 Ffi.setdcf conf.dcf;
714 settitle @@ titlify path;
715 wcmd U.dopen "%d %d %d %d %s\000%s\000%s\000"
716 (btod conf.usedoccss) conf.rlw conf.rlh conf.rlem path password conf.css;
717 invalidate "reqlayout"
718 (fun () ->
719 wcmd U.reqlayout " %d %d %d %s\000"
720 conf.angle (FMTE.to_int conf.fitmodel)
721 (stateh !S.winh) !S.nameddest
723 fillhelp ()
725 let reload () =
726 S.anchor := getanchor ();
727 S.reload := Some (!S.x, !S.y, now ());
728 opendoc !S.path !S.password
730 let docolumns columns =
731 match columns with
732 | Csingle _ ->
733 let a = Array.make !S.pagecount (-1, -1, -1, (-1, -1, -1, -1)) in
734 let rec loop pageno pdimno pdim y ph pdims =
735 if pageno != !S.pagecount
736 then
737 let pdimno, ((_, w, h, xoff) as pdim), pdims =
738 match pdims with
739 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
740 pdimno+1, pdim, rest
741 | _ ->
742 pdimno, pdim, pdims
744 let x = max 0 (((!S.winw - w) / 2) - xoff) in
745 let y =
746 y + (if conf.presentation
747 then (if pageno = 0 then calcips h else calcips ph + calcips h)
748 else (if pageno = 0 then 0 else conf.interpagespace))
750 a.(pageno) <- (pdimno, x, y, pdim);
751 loop (pageno+1) pdimno pdim (y + h) h pdims
753 loop 0 ~-1 (-1,-1,-1,-1) 0 0 !S.pdims;
754 conf.columns <- Csingle a;
756 | Cmulti ((columns, coverA, coverB), _) ->
757 let a = Array.make !S.pagecount (-1, -1, -1, (-1, -1, -1, -1)) in
758 let rec loop pageno pdimno pdim x y rowh pdims =
759 let rec fixrow m =
760 if m >= pageno
761 then
762 let (pdimno, x, y, ((_, _, h, _) as pdim)) = a.(m) in
763 if h < rowh
764 then a.(m) <- (pdimno, x, y + (rowh - h) / 2, pdim);
765 fixrow (m+1)
767 if pageno = !S.pagecount
768 then fixrow (((pageno - 1) / columns) * columns)
769 else
770 let pdimno, ((_, w, h, xoff) as pdim), pdims =
771 match pdims with
772 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
773 pdimno+1, pdim, rest
774 | _ -> pdimno, pdim, pdims
776 let x, y, rowh' =
777 if pageno = coverA - 1 || pageno = !S.pagecount - coverB
778 then (
779 let x = (!S.winw - w) / 2 in
780 let ips =
781 if conf.presentation then calcips h else conf.interpagespace in
782 x, y + ips + rowh, h
784 else (
785 if (pageno - coverA) mod columns = 0
786 then (
787 let x = max 0 (!S.winw - !S.w) / 2 in
788 let y =
789 if conf.presentation
790 then
791 let ips = calcips h in
792 y + (if pageno = 0 then 0 else calcips rowh + ips)
793 else y + (if pageno = 0 then 0 else conf.interpagespace)
795 x, y + rowh, h
797 else x, y, max rowh h
800 let y =
801 if pageno > 1 && (pageno - coverA) mod columns = 0
802 then (
803 let y =
804 if pageno = columns && conf.presentation
805 then (
806 let ips = calcips rowh in
807 for i = 0 to pred columns
809 let (pdimno, x, y, pdim) = a.(i) in
810 a.(i) <- (pdimno, x, y+ips, pdim)
811 done;
812 y+ips;
814 else y
816 fixrow (pageno - columns);
819 else y
821 a.(pageno) <- (pdimno, x, y, pdim);
822 let x = x + w + xoff*2 + conf.interpagespace in
823 loop (pageno+1) pdimno pdim x y rowh' pdims
825 loop 0 ~-1 (-1,-1,-1,-1) 0 0 0 !S.pdims;
826 conf.columns <- Cmulti ((columns, coverA, coverB), a);
828 | Csplit (c, _) ->
829 let a = Array.make (!S.pagecount*c) (-1, -1, -1, (-1, -1, -1, -1)) in
830 let rec loop pageno pdimno pdim y pdims =
831 if pageno != !S.pagecount
832 then
833 let pdimno, ((_, w, h, _) as pdim), pdims =
834 match pdims with
835 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
836 pdimno+1, pdim, rest
837 | _ -> pdimno, pdim, pdims
839 let cw = w / c in
840 let rec loop1 n x y =
841 if n = c then y else (
842 a.(pageno*c + n) <- (pdimno, x, y, pdim);
843 loop1 (n+1) (x+cw) (y + h + conf.interpagespace)
846 let y = loop1 0 0 y in
847 loop (pageno+1) pdimno pdim y pdims
849 loop 0 ~-1 (-1,-1,-1,-1) 0 !S.pdims;
850 conf.columns <- Csplit (c, a)
852 let represent () =
853 docolumns conf.columns;
854 S.maxy := calcheight ();
855 if !S.reprf == noreprf
856 then (
857 match !S.mode with
858 | Birdseye (_, _, pageno, _, _) ->
859 let y, h = getpageyh pageno in
860 let top = (!S.winh - h) / 2 in
861 gotoxy !S.x (max 0 (y - top))
862 | Textentry _ | View | LinkNav _ ->
863 let y = getanchory !S.anchor in
864 let y = min y (!S.maxy - !S.winh) in
865 gotoxy !S.x y;
867 else (
868 !S.reprf ();
869 S.reprf := noreprf;
872 let reshape ?(firsttime=false) w h =
873 GlDraw.viewport ~x:0 ~y:0 ~w ~h;
874 if not firsttime && U.nogeomcmds !S.geomcmds
875 then S.anchor := getanchor ();
877 S.winw := w;
878 let w = truncate (float w *. conf.zoom) in
879 let w = max w 2 in
880 S.winh := h;
881 setfontsize fstate.fontsize;
882 GlMat.mode `modelview;
883 GlMat.load_identity ();
885 GlMat.mode `projection;
886 GlMat.load_identity ();
887 GlMat.rotate ~x:1.0 ~angle:180.0 ();
888 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
889 GlMat.scale3 (2.0 /. float !S.winw, 2.0 /. float !S.winh, 1.0);
891 let relx =
892 if conf.zoom <= 1.0
893 then 0.0
894 else float !S.x /. float !S.w
896 invalidate "geometry"
897 (fun () ->
898 S.w := w;
899 if not firsttime
900 then S.x := truncate (relx *. float w);
901 let w =
902 match conf.columns with
903 | Csingle _ -> w
904 | Cmulti ((c, _, _), _) -> (w - (c-1)*conf.interpagespace) / c
905 | Csplit (c, _) -> w * c
907 wcmd U.geometry "%d %d %d" w (stateh h) (FMTE.to_int conf.fitmodel)
910 let gctiles () =
911 let len = Queue.length S.tilelru in
912 let layout = lazy (if conf.preload
913 then preloadlayout !S.x !S.y !S.winw !S.winh
914 else !S.layout) in
915 let rec loop qpos =
916 if !S.memused > conf.memlimit
917 then (
918 if qpos < len
919 then
920 let (k, p, s) as lruitem = Queue.pop S.tilelru in
921 let n, gen, colorspace, angle, pagew, pageh, col, row = k in
922 let (_, pw, ph, _) = getpagedim n in
923 if gen = !S.gen
924 && colorspace = conf.colorspace
925 && angle = conf.angle
926 && pagew = pw
927 && pageh = ph
928 && (
929 let x = col*conf.tilew and y = row*conf.tileh in
930 tilevisible (Lazy.force_val layout) n x y
932 then Queue.push lruitem S.tilelru
933 else (
934 wcmd1 U.freetile p;
935 S.memused := !S.memused - s;
936 !S.uioh#infochanged Memused;
937 Hashtbl.remove S.tilemap k;
939 loop (qpos+1)
942 loop 0
944 let onpagerect pageno f =
945 let b =
946 match conf.columns with
947 | Cmulti (_, b) -> b
948 | Csingle b -> b
949 | Csplit (_, b) -> b
951 if pageno >= 0 && pageno < Array.length b
952 then
953 let (_, _, _, (_, w, h, _)) = b.(pageno) in
954 f w h
956 let gotopagexy1 pageno x y =
957 let _,w1,h1,leftx = getpagedim pageno in
958 let top = y /. (float h1) in
959 let left = x /. (float w1) in
960 let py, w, h = getpageywh pageno in
961 let wh = !S.winh in
962 let x = left *. (float w) in
963 let x = leftx + !S.x + truncate x in
964 let sx =
965 if x < 0 || x >= !S.winw
966 then !S.x - x
967 else !S.x
969 let pdy = truncate (top *. float h) in
970 let y' = py + pdy in
971 let dy = y' - !S.y in
972 let sy =
973 if x != !S.x || not (dy > 0 && dy < wh)
974 then (
975 if conf.presentation
976 then
977 if abs (py - y') > wh
978 then y'
979 else py
980 else y';
982 else !S.y
984 if !S.x != sx || !S.y != sy
985 then gotoxy sx sy
986 else gotoxy !S.x !S.y
988 let gotopagexy pageno x y =
989 match !S.mode with
990 | Birdseye _ -> gotopage pageno 0.0
991 | Textentry _ | View | LinkNav _ -> gotopagexy1 pageno x y
993 let getpassword () =
994 let passcmd = getenvdef "LLPP_ASKPASS" conf.passcmd in
995 if emptystr passcmd
996 then (adderrmsg "askpass" "ask password program not set"; E.s)
997 else getcmdoutput (adderrfmt passcmd "failed to obrain password: %s") passcmd
999 let pgoto opaque pageno x y =
1000 let pdimno = getpdimno pageno in
1001 let x, y = Ffi.project opaque pageno pdimno x y in
1002 gotopagexy pageno x y
1004 let act cmds =
1005 (* dolog1 "%S" cmds; *)
1006 let spl = splitatchar cmds ' ' in
1007 let scan s fmt f =
1008 try Scanf.sscanf s fmt f
1009 with exn ->
1010 dolog1 "error scanning %S: %s" cmds @@ exntos exn;
1011 exit 1
1013 let addoutline outline =
1014 match !S.currently with
1015 | Outlining outlines -> S.currently := Outlining (outline :: outlines)
1016 | Idle -> S.currently := Outlining [outline]
1017 | Loading _ | Tiling _ ->
1018 dolog1 "invalid outlining state";
1019 logcurrently !S.currently
1021 match spl with
1022 | "clear", "" ->
1023 S.pdims := [];
1024 !S.uioh#infochanged Pdim;
1026 | "clearrects", "" ->
1027 S.rects := !S.rects1;
1028 postRedisplay "clearrects";
1030 | "continue", args ->
1031 let n = scan args "%u" (fun n -> n) in
1032 S.pagecount := n;
1033 begin match !S.currently with
1034 | Outlining l ->
1035 S.currently := Idle;
1036 S.outlines := Array.of_list (List.rev l)
1037 | Idle | Loading _ | Tiling _ -> ()
1038 end;
1040 let cur, cmds = !S.geomcmds in
1041 if emptystr cur then error "empty geomcmd";
1043 begin match List.rev cmds with
1044 | [] ->
1045 S.geomcmds := E.s, [];
1046 represent ();
1047 | (s, f) :: rest ->
1048 f ();
1049 S.geomcmds := s, List.rev rest;
1050 end;
1051 postRedisplay "continue";
1053 | "vmsg", args ->
1054 if conf.verbose then showtext ' ' args
1056 | "emsg", args ->
1057 Buffer.add_string S.errmsgs args;
1058 Buffer.add_char S.errmsgs '\n';
1059 if not !S.newerrmsgs
1060 then (
1061 S.newerrmsgs := true;
1062 postRedisplay "error message";
1065 | "progress", args ->
1066 let progress, text =
1067 scan args "%f %n"
1068 (fun f pos -> f, String.sub args pos (String.length args - pos))
1070 S.text := text;
1071 S.progress := progress;
1072 postRedisplay "progress"
1074 | "match", args ->
1075 let pageno, n, x0, y0, x1, y1, x2, y2, x3, y3 =
1076 scan args "%u %d %f %f %f %f %f %f %f %f"
1077 (fun p n x0 y0 x1 y1 x2 y2 x3 y3 ->
1078 (p, n, x0, y0, x1, y1, x2, y2, x3, y3))
1080 if n = 0
1081 then (
1082 let y = (getpagey pageno) + truncate y0 in
1083 let x =
1084 if (!S.x < - truncate x0) || (!S.x > !S.winw - truncate x1)
1085 then !S.winw/2 - truncate (x0 /. 2. +. x1 /. 2.)
1086 else !S.x
1088 addnav ();
1089 gotoxy x y;
1091 let color = (0.0, 0.0, (if n = 0 then 1.0 else 0.5), 0.5) in
1092 S.rects1 :=
1093 (pageno, color, (x0, y0, x1, y1, x2, y2, x3, y3)) :: !S.rects1
1095 | "page", args ->
1096 let pageopaques, t = scan args "%s %f" (fun p t -> p, t) in
1097 let pageopaque = Opaque.of_string pageopaques in
1098 begin match !S.currently with
1099 | Loading (l, gen) ->
1100 vlog "page %d took %f sec" l.pageno t;
1101 Hashtbl.replace S.pagemap (l.pageno, gen) pageopaque;
1102 let preloadedpages =
1103 if conf.preload
1104 then preloadlayout !S.x !S.y !S.winw !S.winh
1105 else !S.layout
1107 let evict () =
1108 let set = List.fold_left (fun s l -> IntSet.add l.pageno s)
1109 IntSet.empty preloadedpages
1111 let evictedpages =
1112 Hashtbl.fold (fun ((pageno, _) as key) opaque accu ->
1113 if not (IntSet.mem pageno set)
1114 then (
1115 wcmd1 U.freepage opaque;
1116 key :: accu
1118 else accu
1119 ) S.pagemap []
1121 List.iter (Hashtbl.remove S.pagemap) evictedpages;
1123 evict ();
1124 S.currently := Idle;
1125 if gen = !S.gen
1126 then (
1127 tilepage l.pageno pageopaque !S.layout;
1128 load !S.layout;
1129 load preloadedpages;
1130 let visible = U.pagevisible !S.layout l.pageno in
1131 if visible
1132 then (
1133 match !S.mode with
1134 | LinkNav (Ltnotready (pageno, dir)) ->
1135 if pageno = l.pageno
1136 then (
1137 let link =
1138 let ld =
1139 if dir = 0
1140 then LDfirstvisible (l.pagex, l.pagey, dir)
1141 else if dir > 0 then LDfirst else LDlast
1143 Ffi.findlink pageopaque ld
1145 match link with
1146 | Lnotfound -> ()
1147 | Lfound n ->
1148 showlinktype (Ffi.getlink pageopaque n);
1149 S.mode := LinkNav (Ltexact (l.pageno, n))
1151 | LinkNav (Ltgendir _)
1152 | LinkNav (Ltexact _)
1153 | View
1154 | Birdseye _
1155 | Textentry _ -> ()
1158 if visible && layoutready !S.layout
1159 then postRedisplay "page";
1162 | Idle | Tiling _ | Outlining _ ->
1163 dolog1 "Inconsistent loading state";
1164 logcurrently !S.currently;
1165 exit 1
1168 | "tile" , args ->
1169 let (x, y, opaques, size, t) =
1170 scan args "%u %u %s %u %f" (fun x y p size t -> (x, y, p, size, t))
1172 let opaque = Opaque.of_string opaques in
1173 begin match !S.currently with
1174 | Tiling (l, pageopaque, cs, angle, gen, col, row, tilew, tileh) ->
1175 vlog "tile %d [%d,%d] took %f sec" l.pageno col row t;
1176 if tilew != conf.tilew || tileh != conf.tileh
1177 then (
1178 wcmd1 U.freetile opaque;
1179 S.currently := Idle;
1180 load !S.layout;
1182 else (
1183 puttileopaque l col row gen cs angle opaque size t;
1184 S.memused := !S.memused + size;
1185 !S.uioh#infochanged Memused;
1186 gctiles ();
1187 Queue.push ((l.pageno, gen, cs, angle, l.pagew, l.pageh, col, row),
1188 opaque, size) S.tilelru;
1190 S.currently := Idle;
1191 if gen = !S.gen
1192 && conf.colorspace = cs
1193 && conf.angle = angle
1194 && tilevisible !S.layout l.pageno x y
1195 then conttiling l.pageno pageopaque;
1197 preload !S.layout;
1198 if gen = !S.gen
1199 && conf.colorspace = cs
1200 && conf.angle = angle
1201 && tilevisible !S.layout l.pageno x y
1202 && layoutready !S.layout
1203 then postRedisplay "tile nothrottle";
1206 | Idle | Loading _ | Outlining _ ->
1207 dolog1 "Inconsistent tiling state";
1208 logcurrently !S.currently;
1209 exit 1
1212 | "pdim", args ->
1213 let (n, w, h, _) as pdim =
1214 scan args "%u %d %d %d" (fun n x w h -> n, w, h, x)
1216 let pdim =
1217 match conf.fitmodel with
1218 | FitWidth -> pdim
1219 | FitPage | FitProportional ->
1220 match conf.columns with
1221 | Csplit _ -> (n, w, h, 0)
1222 | Csingle _ | Cmulti _ -> pdim
1224 S.pdims := pdim :: !S.pdims;
1225 !S.uioh#infochanged Pdim
1227 | "o", args ->
1228 let (l, n, t, h, pos) =
1229 scan args "%u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos)
1231 let s = String.sub args pos (String.length args - pos) in
1232 addoutline (s, l, Oanchor (n, float t /. float h, 0.0))
1234 | "ou", args ->
1235 let (l, len, pos) = scan args "%u %u %n" (fun l len pos -> l, len, pos) in
1236 let s = String.sub args pos len in
1237 let pos2 = pos + len + 1 in
1238 let uri = String.sub args pos2 (String.length args - pos2) in
1239 addoutline (s, l, Ouri uri)
1241 | "on", args ->
1242 let (l, pos) = scan args "%u %n" (fun l pos -> l, pos) in
1243 let s = String.sub args pos (String.length args - pos) in
1244 addoutline (s, l, Onone)
1246 | "a", args ->
1247 let (n, l, t) = scan args "%u %d %d" (fun n l t -> n, l, t) in
1248 S.reprf := (fun () -> gotopagexy n (float l) (float t))
1250 | "info", args ->
1251 let s =
1252 match splitatchar args '\t' with
1253 | "Title", "" ->
1254 settitle @@ Filename.basename !S.path;
1256 | "Title", v ->
1257 settitle v;
1258 args
1259 | _, "" -> E.s
1260 | c, v ->
1261 if let len = String.length c in
1262 len > 6 && ((String.sub c (len-4) 4) = "date")
1263 then (
1264 if String.length v >= 7 && v.[0] = 'D' && v.[1] = ':'
1265 then
1266 let b = Buffer.create 10 in
1267 Printf.bprintf b "%s\t" c;
1268 let sub p l c =
1270 Buffer.add_substring b v p l;
1271 Buffer.add_char b c;
1272 with exn -> Buffer.add_string b @@ exntos exn
1274 sub 2 4 '/';
1275 sub 6 2 '/';
1276 sub 8 2 ' ';
1277 sub 10 2 ':';
1278 sub 12 2 ':';
1279 sub 14 2 ' ';
1280 Printf.bprintf b "[%s]" v;
1281 Buffer.contents b
1282 else args
1284 else args
1286 if nonemptystr s then S.docinfo := (1, s) :: !S.docinfo
1288 | "infoend", "" ->
1289 S.docinfo := List.rev !S.docinfo;
1290 !S.uioh#infochanged Docinfo
1292 | "pass", args ->
1293 if args = "fail"
1294 then adderrmsg "pass" "Wrong password";
1295 let password = getpassword () in
1296 if emptystr password
1297 then error "document is password protected"
1298 else opendoc !S.path password
1300 | _ -> error "unknown cmd `%S'" cmds
1302 let onhist cb =
1303 let rc = cb.rc in
1304 let action = function
1305 | HCprev -> cbget cb ~-1
1306 | HCnext -> cbget cb 1
1307 | HCfirst -> cbget cb ~-(cb.rc)
1308 | HClast -> cbget cb (cb.len - 1 - cb.rc)
1309 and cancel () = cb.rc <- rc
1310 in (action, cancel)
1312 let search pattern forward =
1313 match conf.columns with
1314 | Csplit _ -> impmsg "searching does not work properly in split columns mode"
1315 | Csingle _ | Cmulti _ ->
1316 if nonemptystr pattern
1317 then
1318 let pn, py =
1319 match !S.layout with
1320 | [] -> 0, 0
1321 | l :: _ -> l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
1323 S.rects1 := [];
1324 wcmd U.search "%d %d %d %d,%s\000"
1325 (btod conf.icase) pn py (btod forward) pattern
1327 let intentry text key =
1328 let text =
1329 if emptystr text && key = Keys.Ascii '-'
1330 then addchar text '-'
1331 else
1332 match [@warning "-fragile-match"] key with
1333 | Keys.Ascii ('0'..'9' as c) -> addchar text c
1334 | _ ->
1335 S.text := "invalid key";
1336 text
1338 TEcont text
1340 let linknact f s =
1341 if nonemptystr s
1342 then
1343 let rec loop off = function
1344 | [] -> ()
1345 | l :: rest ->
1346 match getopaque l.pageno with
1347 | exception Not_found -> loop off rest
1348 | opaque ->
1349 let n = Ffi.getlinkn opaque conf.hcs s off in
1350 if n <= 0
1351 then loop n rest
1352 else Ffi.getlink opaque (n-1) |> f
1354 loop 0 !S.layout
1356 let linknentry text = function [@warning "-fragile-match"]
1357 | Keys.Ascii c ->
1358 let text = addchar text c in
1359 linknact (fun under -> S.text := undertext under) text;
1360 TEcont text
1361 | key ->
1362 settextfmt "invalid key %s" @@ Keys.to_string key;
1363 TEcont text
1365 let textentry text key = match [@warning "-fragile-match"] key with
1366 | Keys.Ascii c -> TEcont (addchar text c)
1367 | Keys.Code c -> TEcont (text ^ Ffi.toutf8 c)
1368 | _ -> TEcont text
1370 let reqlayout angle fitmodel =
1371 if U.nogeomcmds !S.geomcmds
1372 then S.anchor := getanchor ();
1373 conf.angle <- angle mod 360;
1374 if conf.angle != 0
1375 then (
1376 match !S.mode with
1377 | LinkNav _ -> S.mode := View
1378 | Birdseye _ | Textentry _ | View -> ()
1380 conf.fitmodel <- fitmodel;
1381 invalidate "reqlayout"
1382 (fun () -> wcmd U.reqlayout "%d %d %d"
1383 conf.angle (FMTE.to_int conf.fitmodel) (stateh !S.winh))
1385 let settrim trimmargins trimfuzz =
1386 if U.nogeomcmds !S.geomcmds
1387 then S.anchor := getanchor ();
1388 conf.trimmargins <- trimmargins;
1389 conf.trimfuzz <- trimfuzz;
1390 let x0, y0, x1, y1 = trimfuzz in
1391 invalidate "settrim"
1392 (fun () -> wcmd U.settrim "%d %d %d %d %d"
1393 (btod conf.trimmargins) x0 y0 x1 y1);
1394 flushpages ()
1396 let setzoom zoom =
1397 let zoom = max 0.0001 zoom in
1398 if zoom <> conf.zoom
1399 then (
1400 S.prevzoom := (conf.zoom, !S.x);
1401 conf.zoom <- zoom;
1402 reshape !S.winw !S.winh;
1403 settextfmt "zoom is now %-5.2f" (zoom *. 100.0);
1406 let pivotzoom ?(vw=min !S.w !S.winw)
1407 ?(vh=min (!S.maxy - !S.y) !S.winh)
1408 ?(x=vw/2) ?(y=vh/2) zoom =
1409 let w = float !S.w /. zoom in
1410 let hw = w /. 2.0 in
1411 let ratio = float vh /. float vw in
1412 let hh = hw *. ratio in
1413 let x0 = float x -. hw +. !S.xf and y0 = float y -. hh +. !S.yf in
1414 let xf, xr = modf x0 and yf, yr = modf y0 in
1415 S.xf := xf;
1416 S.yf := yf;
1417 gotoxy (!S.x - truncate xr) (!S.y + truncate yr);
1418 setzoom zoom
1420 let pivotzoom ?vw ?vh ?x ?y zoom =
1421 if U.nogeomcmds !S.geomcmds
1422 then
1423 if zoom > 1.0
1424 then pivotzoom ?vw ?vh ?x ?y zoom
1425 else setzoom zoom
1427 let setcolumns mode columns coverA coverB =
1428 S.prevcolumns := Some (conf.columns, conf.zoom);
1429 if columns < 0
1430 then (
1431 if isbirdseye mode
1432 then impmsg "split mode doesn't work in bird's eye"
1433 else (
1434 conf.columns <- Csplit (-columns, E.a);
1435 S.x := 0;
1436 conf.zoom <- 1.0;
1439 else (
1440 if columns < 2
1441 then (
1442 conf.columns <- Csingle E.a;
1443 S.x := 0;
1444 setzoom 1.0;
1446 else (
1447 conf.columns <- Cmulti ((columns, coverA, coverB), E.a);
1448 conf.zoom <- 1.0;
1451 reshape !S.winw !S.winh
1453 let resetmstate () =
1454 S.mstate := Mnone;
1455 Wsi.setcursor Wsi.CURSOR_INHERIT
1457 let enterbirdseye () =
1458 let zoom = float conf.thumbw /. float !S.winw in
1459 let birdseyepageno =
1460 let cy = !S.winh / 2 in
1461 let fold = function
1462 | [] -> 0
1463 | l :: rest ->
1464 let rec fold best = function
1465 | [] -> best.pageno
1466 | l :: rest ->
1467 let d = cy - (l.pagedispy + l.pagevh/2)
1468 and dbest = cy - (best.pagedispy + best.pagevh/2) in
1469 if abs d < abs dbest
1470 then fold l rest
1471 else best.pageno
1472 in fold l rest
1474 fold !S.layout
1476 S.mode :=
1477 Birdseye (
1478 { conf with zoom = conf.zoom },
1479 !S.x, birdseyepageno, -1, getanchor ()
1481 resetmstate ();
1482 conf.zoom <- zoom;
1483 conf.presentation <- false;
1484 conf.interpagespace <- 10;
1485 conf.hlinks <- false;
1486 conf.fitmodel <- FitPage;
1487 S.x := 0;
1488 conf.columns <- (
1489 match conf.beyecolumns with
1490 | Some c ->
1491 conf.zoom <- 1.0;
1492 Cmulti ((c, 0, 0), E.a)
1493 | None -> Csingle E.a
1495 if conf.verbose
1496 then settextfmt "birds eye on (zoom %3.1f%%)" (100.0*.zoom);
1497 reshape !S.winw !S.winh
1499 let leavebirdseye (c, leftx, pageno, _, anchor) goback =
1500 S.mode := View;
1501 conf.zoom <- c.zoom;
1502 conf.presentation <- c.presentation;
1503 conf.interpagespace <- c.interpagespace;
1504 conf.hlinks <- c.hlinks;
1505 conf.fitmodel <- c.fitmodel;
1506 conf.beyecolumns <- (
1507 match conf.columns with
1508 | Cmulti ((c, _, _), _) -> Some c
1509 | Csingle _ -> None
1510 | Csplit _ -> error "leaving bird's eye split mode"
1512 conf.columns <- (
1513 match c.columns with
1514 | Cmulti (c, _) -> Cmulti (c, E.a)
1515 | Csingle _ -> Csingle E.a
1516 | Csplit (c, _) -> Csplit (c, E.a)
1518 if conf.verbose
1519 then settextfmt "bird's eye off (zoom %3.1f%%)" (100.0*.conf.zoom);
1520 reshape !S.winw !S.winh;
1521 S.anchor := if goback then anchor else (pageno, 0.0, 1.0);
1522 S.x := leftx
1524 let togglebirdseye () =
1525 match !S.mode with
1526 | Birdseye vals -> leavebirdseye vals true
1527 | View -> enterbirdseye ()
1528 | Textentry _ | LinkNav _ -> ()
1530 let upbirdseye incr (conf, leftx, pageno, hooverpageno, anchor) =
1531 let pageno = max 0 (pageno - incr) in
1532 let rec loop = function
1533 | [] -> gotopage1 pageno 0
1534 | l :: _ when l.pageno = pageno ->
1535 if l.pagedispy >= 0 && l.pagey = 0
1536 then postRedisplay "upbirdseye"
1537 else gotopage1 pageno 0
1538 | _ :: rest -> loop rest
1540 loop !S.layout;
1541 S.text := E.s;
1542 S.mode := Birdseye (conf, leftx, pageno, hooverpageno, anchor)
1544 let downbirdseye incr (conf, leftx, pageno, hooverpageno, anchor) =
1545 let pageno = min (!S.pagecount - 1) (pageno + incr) in
1546 S.mode := Birdseye (conf, leftx, pageno, hooverpageno, anchor);
1547 let rec loop = function
1548 | [] ->
1549 let y, h = getpageyh pageno in
1550 let dy = (y - !S.y) - (!S.winh - h - conf.interpagespace) in
1551 gotoxy !S.x (U.clamp dy)
1552 | l :: _ when l.pageno = pageno ->
1553 if l.pagevh != l.pageh
1554 then gotoxy !S.x (U.clamp (l.pageh - l.pagevh + conf.interpagespace))
1555 else postRedisplay "downbirdseye"
1556 | _ :: rest -> loop rest
1558 loop !S.layout;
1559 S.text := E.s
1561 let optentry mode _ key =
1562 match [@warning "-fragile-match"] key with
1563 | Keys.Ascii 'C' ->
1564 let ondone s =
1566 let n, a, b = multicolumns_of_string s in
1567 setcolumns mode n a b;
1568 with exn -> settextfmt "bad columns `%s': %s" s @@ exntos exn
1570 TEswitch ("columns: ", E.s, None, textentry, ondone, true)
1572 | Keys.Ascii 'Z' ->
1573 let ondone s =
1575 let zoom = float (int_of_string s) /. 100.0 in
1576 pivotzoom zoom
1577 with exn -> settextfmt "bad integer `%s': %s" s @@ exntos exn
1579 TEswitch ("zoom: ", E.s, None, intentry, ondone, true)
1581 | Keys.Ascii 'i' ->
1582 conf.icase <- not conf.icase;
1583 TEdone ("case insensitive search " ^ (onoffs conf.icase))
1585 | Keys.Ascii 'v' ->
1586 conf.verbose <- not conf.verbose;
1587 TEdone ("verbose " ^ (onoffs conf.verbose))
1589 | Keys.Ascii 'd' ->
1590 conf.debug <- not conf.debug;
1591 TEdone ("debug " ^ (onoffs conf.debug))
1593 | Keys.Ascii 'f' ->
1594 conf.underinfo <- not conf.underinfo;
1595 TEdone ("underinfo " ^ onoffs conf.underinfo)
1597 | Keys.Ascii 'T' ->
1598 settrim (not conf.trimmargins) conf.trimfuzz;
1599 TEdone ("trim margins " ^ onoffs conf.trimmargins)
1601 | Keys.Ascii 'I' ->
1602 conf.invert <- not conf.invert;
1603 TEdone ("invert colors " ^ onoffs conf.invert)
1605 | Keys.Ascii 'x' ->
1606 let ondone s =
1607 cbput !S.hists.sel s;
1608 conf.selcmd <- s;
1610 TEswitch ("selection command: ", E.s, Some (onhist !S.hists.sel),
1611 textentry, ondone, true)
1613 | Keys.Ascii 'M' ->
1614 if conf.pax == None
1615 then conf.pax <- Some 0.0
1616 else conf.pax <- None;
1617 TEdone ("PAX " ^ onoffs (conf.pax != None))
1619 | (Keys.Ascii c) ->
1620 settextfmt "bad option %d `%c'" (Char.code c) c;
1621 TEstop
1623 | _ -> TEcont !S.text
1625 class outlinelistview ~zebra ~source =
1626 let settext autonarrow s =
1627 S.text :=
1628 if autonarrow
1629 then
1630 let ss = source#statestr in
1631 if emptystr ss then "[" ^ s ^ "]" else "{" ^ ss ^ "} [" ^ s ^ "]"
1632 else s
1634 object (self)
1635 inherit listview
1636 ~zebra
1637 ~helpmode:false
1638 ~source:(source :> lvsource)
1639 ~trusted:false
1640 ~modehash:(findkeyhash conf "outline")
1641 as super
1643 val m_autonarrow = false
1645 method! key key mask =
1646 let maxrows =
1647 if emptystr !S.text
1648 then fstate.maxrows
1649 else fstate.maxrows - 2
1651 let calcfirst first active =
1652 if active > first
1653 then
1654 let rows = active - first in
1655 if rows > maxrows then active - maxrows else first
1656 else active
1658 let navigate incr =
1659 let active = m_active + incr in
1660 let active = bound active 0 (source#getitemcount - 1) in
1661 let first = calcfirst m_first active in
1662 postRedisplay "outline navigate";
1663 coe {< m_active = active; m_first = first >}
1665 let navscroll first =
1666 let active =
1667 let dist = m_active - first in
1668 if dist < 0
1669 then first
1670 else (
1671 if dist < maxrows
1672 then m_active
1673 else first + maxrows
1676 postRedisplay "outline navscroll";
1677 coe {< m_first = first; m_active = active >}
1679 let ctrl = Wsi.withctrl mask in
1680 let open Keys in
1681 match Wsi.ks2kt key with
1682 | Ascii 'a' when ctrl ->
1683 let text =
1684 if m_autonarrow
1685 then (
1686 source#denarrow;
1689 else (
1690 let pattern = source#renarrow in
1691 if nonemptystr m_qsearch
1692 then (source#narrow m_qsearch; m_qsearch)
1693 else pattern
1696 settext (not m_autonarrow) text;
1697 postRedisplay "toggle auto narrowing";
1698 coe {< m_first = 0; m_active = 0; m_autonarrow = not m_autonarrow >}
1699 | Ascii '/' when emptystr m_qsearch && not m_autonarrow ->
1700 settext true E.s;
1701 postRedisplay "toggle auto narrowing";
1702 coe {< m_first = 0; m_active = 0; m_autonarrow = true >}
1703 | Ascii 'n' when ctrl ->
1704 source#narrow m_qsearch;
1705 if not m_autonarrow
1706 then source#add_narrow_pattern m_qsearch;
1707 postRedisplay "outline ctrl-n";
1708 coe {< m_first = 0; m_active = 0 >}
1709 | Ascii 'S' when ctrl ->
1710 let active = source#calcactive (getanchor ()) in
1711 let first = firstof m_first active in
1712 postRedisplay "outline ctrl-s";
1713 coe {< m_first = first; m_active = active >}
1714 | Ascii 'u' when ctrl ->
1715 postRedisplay "outline ctrl-u";
1716 if m_autonarrow && nonemptystr m_qsearch
1717 then (
1718 ignore (source#renarrow);
1719 settext m_autonarrow E.s;
1720 coe {< m_first = 0; m_active = 0; m_qsearch = E.s >}
1722 else (
1723 source#del_narrow_pattern;
1724 let pattern = source#renarrow in
1725 let text =
1726 if emptystr pattern then E.s else "Narrowed to " ^ pattern
1728 settext m_autonarrow text;
1729 coe {< m_first = 0; m_active = 0; m_qsearch = E.s >}
1731 | Ascii 'l' when ctrl ->
1732 let first = max 0 (m_active - (fstate.maxrows / 2)) in
1733 postRedisplay "outline ctrl-l";
1734 coe {< m_first = first >}
1736 | Ascii '\t' when m_autonarrow ->
1737 if nonemptystr m_qsearch
1738 then (
1739 postRedisplay "outline list view tab";
1740 source#add_narrow_pattern m_qsearch;
1741 settext true E.s;
1742 coe {< m_qsearch = E.s >}
1744 else coe self
1745 | Escape when m_autonarrow ->
1746 if nonemptystr m_qsearch
1747 then source#add_narrow_pattern m_qsearch;
1748 super#key key mask
1749 | Enter when m_autonarrow ->
1750 if nonemptystr m_qsearch
1751 then source#add_narrow_pattern m_qsearch;
1752 super#key key mask
1753 | (Ascii _ | Code _) when m_autonarrow ->
1754 let pattern = m_qsearch ^ Ffi.toutf8 key in
1755 postRedisplay "outlinelistview autonarrow add";
1756 source#narrow pattern;
1757 settext true pattern;
1758 coe {< m_first = 0; m_active = 0; m_qsearch = pattern >}
1759 | Backspace when m_autonarrow ->
1760 if emptystr m_qsearch
1761 then coe self
1762 else
1763 let pattern = withoutlastutf8 m_qsearch in
1764 postRedisplay "outlinelistview autonarrow backspace";
1765 ignore (source#renarrow);
1766 source#narrow pattern;
1767 settext true pattern;
1768 coe {< m_first = 0; m_active = 0; m_qsearch = pattern >}
1769 | Up when ctrl -> navscroll (max 0 (m_first-1))
1770 | Down when ctrl -> navscroll (min (source#getitemcount-1) (m_first+1))
1771 | Up -> navigate ~-1
1772 | Down -> navigate 1
1773 | Prior -> navigate ~-(fstate.maxrows)
1774 | Next -> navigate fstate.maxrows
1775 | Right ->
1776 (if ctrl
1777 then (
1778 postRedisplay "outline ctrl right";
1779 {< m_pan = m_pan + 1 >}
1781 else (
1782 if Wsi.withshift mask
1783 then self#nextcurlevel 1
1784 else self#updownlevel 1
1785 )) |> coe
1786 | Left ->
1787 (if ctrl
1788 then (
1789 postRedisplay "outline ctrl left";
1790 {< m_pan = m_pan - 1 >}
1792 else (
1793 if Wsi.withshift mask
1794 then self#nextcurlevel ~-1
1795 else self#updownlevel ~-1
1796 )) |> coe
1797 | Home ->
1798 postRedisplay "outline home";
1799 coe {< m_first = 0; m_active = 0 >}
1800 | End ->
1801 let active = source#getitemcount - 1 in
1802 let first = max 0 (active - fstate.maxrows) in
1803 postRedisplay "outline end";
1804 coe {< m_active = active; m_first = first >}
1805 | Delete|Escape|Insert|Enter|Ascii _|Code _|Ctrl _|Backspace|Fn _ ->
1806 super#key key mask
1809 let genhistoutlines () =
1810 Config.gethist ()
1811 |> List.sort (fun (_, c1, _, _, _, _) (_, c2, _, _, _, _) ->
1812 compare c2.lastvisit c1.lastvisit)
1813 |> List.map (fun ((path, c, _, _, _, origin) as hist) ->
1814 let path = if nonemptystr origin then origin else path in
1815 let base = Ffi.mbtoutf8 @@ Filename.basename path in
1816 (base ^ "\000" ^ c.title, 1, Ohistory hist)
1819 let gotohist (path, c, bookmarks, x, anchor, origin) =
1820 Config.save leavebirdseye;
1821 S.anchor := anchor;
1822 S.bookmarks := bookmarks;
1823 S.origin := origin;
1824 S.x := x;
1825 setconf conf c;
1826 let x0, y0, x1, y1 = conf.trimfuzz in
1827 wcmd U.trimset "%d %d %d %d %d" (btod conf.trimmargins) x0 y0 x1 y1;
1828 Wsi.reshape c.cwinw c.cwinh;
1829 opendoc path origin;
1830 setzoom c.zoom
1832 let describe_layout layout =
1833 let d =
1834 match layout with
1835 | [] -> "Page 0"
1836 | l :: [] -> Printf.sprintf "Page %d" (l.pageno+1)
1837 | l :: rest ->
1838 let rangestr a b =
1839 if a.pageno = b.pageno then Printf.sprintf "%d" (a.pageno+1)
1840 else Printf.sprintf "%d%s%d" (a.pageno+1)
1841 (if a.pageno+1 = b.pageno then ", " else Utf8syms.ellipsis)
1842 (b.pageno+1)
1844 let rec fold s la lb = function
1845 | [] -> Printf.sprintf "%s %s" s (rangestr la lb)
1846 | l :: rest when l.pageno = succ lb.pageno -> fold s la l rest
1847 | l :: rest -> fold (s ^ " " ^ rangestr la lb ^ ",") l l rest
1849 fold "Pages" l l rest
1851 let percent =
1852 let maxy = U.maxy () in
1853 if maxy <= 0
1854 then 100.
1855 else 100. *. (float !S.y /. float maxy)
1857 Printf.sprintf "%s of %d [%.2f%%]" d !S.pagecount percent
1859 let setpresentationmode v =
1860 let n = page_of_y !S.y in
1861 S.anchor := (n, 0.0, 1.0);
1862 conf.presentation <- v;
1863 if conf.fitmodel = FitPage
1864 then reqlayout conf.angle conf.fitmodel;
1865 represent ()
1867 let infomenu =
1868 let modehash = lazy (findkeyhash conf "info") in (fun source ->
1869 S.text := E.s;
1870 new listview ~zebra:false ~helpmode:false ~source
1871 ~trusted:true ~modehash:(Lazy.force_val modehash) |> coe)
1873 let enterinfomode =
1874 let btos b = if b then Utf8syms.radical else E.s in
1875 let showextended = ref false in
1876 let showcolors = ref false in
1877 let showcommands = ref false in
1878 let showrefl = ref false in
1879 let leave mode _ = S.mode := mode in
1880 let src = object
1881 val mutable m_l = []
1882 val mutable m_a = E.a
1883 val mutable m_prev_uioh = nouioh
1884 val mutable m_prev_mode = View
1886 inherit lvsourcebase
1888 method reset prev_mode prev_uioh =
1889 m_a <- Array.of_list (List.rev m_l);
1890 m_l <- [];
1891 m_prev_mode <- prev_mode;
1892 m_prev_uioh <- prev_uioh;
1894 method int name get set =
1895 m_l <-
1896 (name, `int get, 1,
1897 Some (fun u ->
1898 let ondone s =
1899 try set (int_of_string s)
1900 with exn -> settextfmt "bad integer `%s': %s" s @@ exntos exn
1902 S.text := E.s;
1903 let te = (name ^ ": ", E.s, None, intentry, ondone, true) in
1904 S.mode := Textentry (te, leave m_prev_mode);
1906 )) :: m_l
1908 method int_with_suffix name get set =
1909 m_l <-
1910 (name, `intws get, 1,
1911 Some (fun u ->
1912 let ondone s =
1913 try set (int_of_string_with_suffix s)
1914 with exn -> settextfmt "bad integer `%s': %s" s @@ exntos exn
1916 S.text := E.s;
1917 let te = (name ^ ": ", E.s, None, intentry_with_suffix,
1918 ondone, true) in
1919 S.mode := Textentry (te, leave m_prev_mode);
1921 )) :: m_l
1923 method bool ?(offset=1) ?(btos=btos) name get set =
1924 m_l <- (name, `bool (btos, get), offset,
1925 Some (fun u -> set (not (get ())); u)) :: m_l
1927 method color name get set =
1928 m_l <-
1929 (name, `color get, 1,
1930 Some (fun u ->
1931 let invalid = (nan, nan, nan) in
1932 let ondone s =
1933 let c =
1934 try color_of_string s
1935 with exn -> settextfmt "bad color `%s': %s" s @@ exntos exn;
1936 invalid
1938 if c <> invalid
1939 then set c;
1941 let te = (name ^ ": ", E.s, None, textentry, ondone, true) in
1942 S.text := color_to_string (get ());
1943 S.mode := Textentry (te, leave m_prev_mode);
1945 )) :: m_l
1947 method string name get set =
1948 m_l <-
1949 (name, `string get, 1,
1950 Some (fun u ->
1951 let ondone s = set s in
1952 let te = (String.trim name ^ ": ", E.s, None,
1953 textentry, ondone, true) in
1954 S.mode := Textentry (te, leave m_prev_mode);
1956 )) :: m_l
1958 method colorspace name get set =
1959 m_l <-
1960 (name, `string get, 1,
1961 Some (fun _ ->
1962 let source = object
1963 inherit lvsourcebase
1965 initializer
1966 m_active <- CSTE.to_int conf.colorspace;
1967 m_first <- 0;
1969 method getitemcount =
1970 Array.length CSTE.names
1971 method getitem n =
1972 (CSTE.names.(n), 0)
1973 method exit ~uioh ~cancel ~active ~first ~pan =
1974 ignore (uioh, first, pan);
1975 if not cancel then set active;
1976 None
1977 method hasaction _ = true
1980 infomenu source
1981 )) :: m_l
1983 method paxmark name get set =
1984 m_l <-
1985 (name, `string get, 1,
1986 Some (fun _ ->
1987 let source = object
1988 inherit lvsourcebase
1990 initializer
1991 m_active <- MTE.to_int conf.paxmark;
1992 m_first <- 0;
1994 method getitemcount = Array.length MTE.names
1995 method getitem n = (MTE.names.(n), 0)
1996 method exit ~uioh ~cancel ~active ~first ~pan =
1997 ignore (uioh, first, pan);
1998 if not cancel then set active;
1999 None
2000 method hasaction _ = true
2003 infomenu source
2004 )) :: m_l
2006 method fitmodel name get set =
2007 m_l <-
2008 (name, `string get, 1,
2009 Some (fun _ ->
2010 let source = object
2011 inherit lvsourcebase
2013 initializer
2014 m_active <- FMTE.to_int conf.fitmodel;
2015 m_first <- 0;
2017 method getitemcount = Array.length FMTE.names
2018 method getitem n = (FMTE.names.(n), 0)
2019 method exit ~uioh ~cancel ~active ~first ~pan =
2020 ignore (uioh, first, pan);
2021 if not cancel then set active;
2022 None
2023 method hasaction _ = true
2026 infomenu source
2027 )) :: m_l
2029 method caption s offset =
2030 m_l <- (s, `empty, offset, None) :: m_l
2032 method caption2 s f offset =
2033 m_l <- (s, `string f, offset, None) :: m_l
2035 method getitemcount = Array.length m_a
2037 method getitem n =
2038 let tostr = function
2039 | `int f -> string_of_int (f ())
2040 | `intws f -> string_with_suffix_of_int (f ())
2041 | `string f -> f ()
2042 | `color f -> color_to_string (f ())
2043 | `bool (btos, f) -> btos (f ())
2044 | `empty -> E.s
2046 let name, t, offset, _ = m_a.(n) in
2047 ((let s = tostr t in
2048 if nonemptystr s
2049 then Printf.sprintf "%s\t%s" name s
2050 else name),
2051 offset)
2053 method exit ~uioh ~cancel ~active ~first ~pan =
2054 let uiohopt =
2055 if not cancel
2056 then (
2057 let uioh =
2058 match m_a.(active) with
2059 | _, _, _, Some f -> f uioh
2060 | _, _, _, None -> uioh
2062 Some uioh
2064 else None
2066 m_active <- active;
2067 m_first <- first;
2068 m_pan <- pan;
2069 uiohopt
2071 method hasaction n =
2072 match m_a.(n) with
2073 | _, _, _, Some _ -> true
2074 | _, _, _, None -> false
2076 initializer m_active <- 1
2079 let rec fillsrc prevmode prevuioh =
2080 let sep () = src#caption E.s 0 in
2081 let bad v exn = settextfmt "bad color `%s': %s" v @@ exntos exn in
2082 let colorp name get set =
2083 src#string name
2084 (fun () -> color_to_string (get ()))
2085 (fun v ->
2086 try set @@ color_of_string v
2087 with exn -> bad v exn
2090 let rgba name get set =
2091 src#string name
2092 (fun () -> get () |> rgba_to_string)
2093 (fun v ->
2094 try set @@ rgba_of_string v
2095 with exn -> bad v exn
2098 let oldmode = !S.mode in
2099 let birdseye = isbirdseye !S.mode in
2101 src#caption (if birdseye then "Setup (Bird's eye)" else "Setup") 0;
2103 src#bool "presentation mode"
2104 (fun () -> conf.presentation)
2105 (fun v -> setpresentationmode v);
2107 src#bool "ignore case in searches"
2108 (fun () -> conf.icase)
2109 (fun v -> conf.icase <- v);
2111 src#bool "preload"
2112 (fun () -> conf.preload)
2113 (fun v -> conf.preload <- v);
2115 src#bool "highlight links"
2116 (fun () -> conf.hlinks)
2117 (fun v -> conf.hlinks <- v);
2119 src#bool "under info"
2120 (fun () -> conf.underinfo)
2121 (fun v -> conf.underinfo <- v);
2123 src#fitmodel "fit model"
2124 (fun () -> FMTE.to_string conf.fitmodel)
2125 (fun v -> reqlayout conf.angle (FMTE.of_int v));
2127 src#bool "trim margins"
2128 (fun () -> conf.trimmargins)
2129 (fun v -> settrim v conf.trimfuzz; fillsrc prevmode prevuioh);
2131 sep ();
2132 src#int "inter-page space"
2133 (fun () -> conf.interpagespace)
2134 (fun n ->
2135 conf.interpagespace <- n;
2136 docolumns conf.columns;
2137 let pageno, py =
2138 match !S.layout with
2139 | [] -> 0, 0
2140 | l :: _ -> l.pageno, l.pagey
2142 S.maxy :=- calcheight ();
2143 gotoxy !S.x (py + getpagey pageno)
2146 src#int "page bias"
2147 (fun () -> conf.pagebias)
2148 (fun v -> conf.pagebias <- v);
2150 src#int "scroll step"
2151 (fun () -> conf.scrollstep)
2152 (fun n -> conf.scrollstep <- n);
2154 src#int "horizontal scroll step"
2155 (fun () -> conf.hscrollstep)
2156 (fun v -> conf.hscrollstep <- v);
2158 src#int "auto scroll step"
2159 (fun () ->
2160 match !S.autoscroll with
2161 | Some step -> step
2162 | _ -> conf.autoscrollstep)
2163 (fun n ->
2164 let n = boundastep !S.winh n in
2165 if !S.autoscroll <> None
2166 then S.autoscroll := Some n;
2167 conf.autoscrollstep <- n);
2169 src#int "zoom"
2170 (fun () -> truncate (conf.zoom *. 100.))
2171 (fun v -> pivotzoom ((float v) /. 100.));
2173 src#int "rotation"
2174 (fun () -> conf.angle)
2175 (fun v -> reqlayout v conf.fitmodel);
2177 src#int "scroll bar width"
2178 (fun () -> conf.scrollbw)
2179 (fun v ->
2180 conf.scrollbw <- v;
2181 reshape !S.winw !S.winh;
2184 src#int "scroll handle height"
2185 (fun () -> conf.scrollh)
2186 (fun v -> conf.scrollh <- v;);
2188 src#int "thumbnail width"
2189 (fun () -> conf.thumbw)
2190 (fun v ->
2191 conf.thumbw <- min 4096 v;
2192 match oldmode with
2193 | Birdseye beye ->
2194 leavebirdseye beye false;
2195 enterbirdseye ()
2196 | Textentry _ | View | LinkNav _ -> ()
2199 let mode = !S.mode in
2200 src#string "columns"
2201 (fun () ->
2202 match conf.columns with
2203 | Csingle _ -> "1"
2204 | Cmulti (multi, _) -> multicolumns_to_string multi
2205 | Csplit (count, _) -> "-" ^ string_of_int count
2207 (fun v ->
2208 let n, a, b = multicolumns_of_string v in
2209 setcolumns mode n a b);
2211 sep ();
2212 src#caption "Pixmap cache" 0;
2213 src#int_with_suffix "size (advisory)"
2214 (fun () -> conf.memlimit)
2215 (fun v -> conf.memlimit <- v);
2217 src#caption2 "used"
2218 (fun () ->
2219 Printf.sprintf "%s bytes, %d tiles"
2220 (string_with_suffix_of_int !S.memused)
2221 (Hashtbl.length S.tilemap)) 1;
2223 sep ();
2224 src#caption "Layout" 0;
2225 src#caption2 "Dimension"
2226 (fun () -> Printf.sprintf "%dx%d (virtual %dx%d)"
2227 !S.winw !S.winh
2228 !S.w !S.maxy)
2230 if conf.debug
2231 then src#caption2 "Position" (fun () ->
2232 Printf.sprintf "%dx%d" !S.x !S.y
2234 else src#caption2 "Position" (fun () -> describe_layout !S.layout) 1;
2236 sep ();
2237 let btos b = Utf8syms.(if b then lguillemet else rguillemet) in
2238 src#bool ~offset:0 ~btos "Extended parameters"
2239 (fun () -> !showextended)
2240 (fun v -> showextended := v; fillsrc prevmode prevuioh);
2241 if !showextended
2242 then (
2243 src#bool "update cursor"
2244 (fun () -> conf.updatecurs)
2245 (fun v -> conf.updatecurs <- v);
2246 src#bool "scroll-bar on the left"
2247 (fun () -> conf.leftscroll)
2248 (fun v -> conf.leftscroll <- v);
2249 src#bool "verbose"
2250 (fun () -> conf.verbose)
2251 (fun v -> conf.verbose <- v);
2252 src#bool "invert colors"
2253 (fun () -> conf.invert)
2254 (fun v -> conf.invert <- v);
2255 src#bool "max fit"
2256 (fun () -> conf.maxhfit)
2257 (fun v -> conf.maxhfit <- v);
2258 src#bool "pax mode"
2259 (fun () -> conf.pax != None)
2260 (fun v ->
2261 if v
2262 then conf.pax <- Some (now ())
2263 else conf.pax <- None);
2264 src#string "uri launcher"
2265 (fun () -> conf.urilauncher)
2266 (fun v -> conf.urilauncher <- v);
2267 src#string "path launcher"
2268 (fun () -> conf.pathlauncher)
2269 (fun v -> conf.pathlauncher <- v);
2270 src#string "tile size"
2271 (fun () -> Printf.sprintf "%dx%d" conf.tilew conf.tileh)
2272 (fun v ->
2274 let w, h = Scanf.sscanf v "%dx%d" (fun w h -> w, h) in
2275 conf.tilew <- max 64 w;
2276 conf.tileh <- max 64 h;
2277 flushtiles ();
2278 with exn -> settextfmt "bad tile size `%s': %s" v @@ exntos exn);
2279 src#int "texture count"
2280 (fun () -> conf.texcount)
2281 (fun v ->
2282 if Ffi.realloctexts v
2283 then conf.texcount <- v
2284 else impmsg "failed to set texture count please retry later");
2285 src#int "slice height"
2286 (fun () -> conf.sliceheight)
2287 (fun v ->
2288 conf.sliceheight <- v;
2289 wcmd U.sliceh "%d" conf.sliceheight);
2290 src#int "anti-aliasing level"
2291 (fun () -> conf.aalevel)
2292 (fun v ->
2293 conf.aalevel <- bound v 0 8;
2294 S.anchor := getanchor ();
2295 opendoc !S.path !S.password);
2296 src#string "page scroll scaling factor"
2297 (fun () -> string_of_float conf.pgscale)
2298 (fun v ->
2299 try conf.pgscale <- float_of_string v
2300 with exn ->
2301 S.text :=
2302 Printf.sprintf "bad page scroll scaling factor `%s': %s" v
2303 @@ exntos exn);
2304 src#int "ui font size"
2305 (fun () -> fstate.fontsize)
2306 (fun v -> setfontsize (bound v 5 100));
2307 src#int "hint font size"
2308 (fun () -> conf.hfsize)
2309 (fun v -> conf.hfsize <- bound v 5 100);
2310 src#string "hint chars"
2311 (fun () -> conf.hcs)
2312 (fun v ->
2314 validatehcs v;
2315 conf.hcs <- v
2316 with exn ->
2317 S.text :=
2318 Printf.sprintf "invalid hint chars %S: %s" v (exntos exn));
2319 src#string "trim fuzz"
2320 (fun () -> irect_to_string conf.trimfuzz)
2321 (fun v ->
2323 conf.trimfuzz <- irect_of_string v;
2324 if conf.trimmargins
2325 then settrim true conf.trimfuzz;
2326 with exn -> settextfmt "bad irect `%s': %s" v @@ exntos exn);
2327 src#bool ~btos "external commands"
2328 (fun () -> !showcommands)
2329 (fun v -> showcommands := v; fillsrc prevmode prevuioh);
2330 if !showcommands
2331 then (
2332 src#string " selection"
2333 (fun () -> conf.selcmd)
2334 (fun v -> conf.selcmd <- v);
2335 src#string " synctex"
2336 (fun () -> conf.stcmd)
2337 (fun v -> conf.stcmd <- v);
2338 src#string " pax"
2339 (fun () -> conf.paxcmd)
2340 (fun v -> conf.paxcmd <- v);
2341 src#string " ask password"
2342 (fun () -> conf.passcmd)
2343 (fun v -> conf.passcmd <- v);
2344 src#string " save path"
2345 (fun () -> conf.savecmd)
2346 (fun v -> conf.savecmd <- v);
2348 src#colorspace "color space"
2349 (fun () -> CSTE.to_string conf.colorspace)
2350 (fun v ->
2351 conf.colorspace <- CSTE.of_int v;
2352 wcmd U.cs "%d" v;
2353 load !S.layout);
2354 src#paxmark "pax mark method"
2355 (fun () -> MTE.to_string conf.paxmark)
2356 (fun v -> conf.paxmark <- MTE.of_int v);
2357 src#bool "mouse wheel scrolls pages"
2358 (fun () -> conf.wheelbypage)
2359 (fun v -> conf.wheelbypage <- v);
2360 src#bool "open remote links in a new instance"
2361 (fun () -> conf.riani)
2362 (fun v -> conf.riani <- v);
2363 src#bool "edit annotations inline"
2364 (fun () -> conf.annotinline)
2365 (fun v -> conf.annotinline <- v);
2366 src#bool "coarse positioning in presentation mode"
2367 (fun () -> conf.coarseprespos)
2368 (fun v -> conf.coarseprespos <- v);
2369 src#bool "use document CSS"
2370 (fun () -> conf.usedoccss)
2371 (fun v ->
2372 conf.usedoccss <- v;
2373 S.anchor := getanchor ();
2374 opendoc !S.path !S.password);
2375 src#bool ~btos "colors"
2376 (fun () -> !showcolors)
2377 (fun v -> showcolors := v; fillsrc prevmode prevuioh);
2378 if !showcolors
2379 then (
2380 colorp " background"
2381 (fun () -> conf.bgcolor)
2382 (fun v -> conf.bgcolor <- v);
2383 rgba " paper"
2384 (fun () -> conf.papercolor)
2385 (fun v ->
2386 conf.papercolor <- v;
2387 Ffi.setpapercolor conf.papercolor;
2388 flushtiles ();
2390 rgba " scrollbar"
2391 (fun () -> conf.sbarcolor)
2392 (fun v -> conf.sbarcolor <- v);
2393 rgba " scrollbar handle"
2394 (fun () -> conf.sbarhndlcolor)
2395 (fun v -> conf.sbarhndlcolor <- v);
2396 rgba " texture"
2397 (fun () -> conf.texturecolor)
2398 (fun v ->
2399 GlTex.env (`color v);
2400 conf.texturecolor <- v;
2402 src#string " scale"
2403 (fun () -> string_of_float conf.colorscale)
2404 (fun v -> conf.colorscale <- bound (float_of_string v) 0.0 1.0);
2406 src#bool ~btos "reflowable layout"
2407 (fun () -> !showrefl)
2408 (fun v -> showrefl := v; fillsrc prevmode prevuioh);
2409 if !showrefl
2410 then (
2411 src#int " width"
2412 (fun () -> conf.rlw)
2413 (fun v -> conf.rlw <- v; reload ());
2414 src#int " height"
2415 (fun () -> conf.rlh)
2416 (fun v -> conf.rlh <- v; reload ());
2417 src#int " em"
2418 (fun () -> conf.rlem)
2419 (fun v -> conf.rlem <- v; reload ());
2423 sep ();
2424 src#caption "Document" 0;
2425 List.iter (fun (_, s) -> src#caption s 1) !S.docinfo;
2426 src#caption2 "Pages" (fun () -> string_of_int !S.pagecount) 1;
2427 src#caption2 "Dimensions"
2428 (fun () -> string_of_int (List.length !S.pdims)) 1;
2429 if nonemptystr conf.css
2430 then src#caption2 "CSS" (fun () -> conf.css) 1;
2431 if conf.trimmargins
2432 then (
2433 sep ();
2434 src#caption "Trimmed margins" 0;
2435 src#caption2 "Dimensions"
2436 (fun () -> string_of_int (List.length !S.pdims)) 1;
2439 sep ();
2440 src#caption "OpenGL" 0;
2441 src#caption ("Vendor\t" ^ GlMisc.get_string `vendor) 1;
2442 src#caption ("Renderer\t" ^ GlMisc.get_string `renderer) 1;
2444 sep ();
2445 src#caption "Location" 0;
2446 if nonemptystr !S.origin
2447 then src#caption ("Origin\t" ^ Ffi.mbtoutf8 !S.origin) 1;
2448 src#caption ("Path\t" ^ Ffi.mbtoutf8 !S.path) 1;
2449 if nonemptystr conf.dcf
2450 then src#caption ("DCF\t" ^ Ffi.mbtoutf8 conf.dcf) 1;
2452 src#reset prevmode prevuioh;
2454 fun () -> (
2455 S.text := E.s;
2456 resetmstate ();
2457 let prevmode = !S.mode
2458 and prevuioh = !S.uioh in
2459 fillsrc prevmode prevuioh;
2460 let source = (src :> lvsource) in
2461 let modehash = findkeyhash conf "info" in
2462 object (self)
2463 inherit listview ~zebra:false ~helpmode:false
2464 ~source ~trusted:true ~modehash as super
2465 val mutable m_prevmemused = 0
2466 method! infochanged = function
2467 | Memused ->
2468 if m_prevmemused != !S.memused
2469 then (
2470 m_prevmemused <- !S.memused;
2471 postRedisplay "memusedchanged";
2473 | Pdim -> postRedisplay "pdimchanged"
2474 | Docinfo -> fillsrc prevmode prevuioh
2475 method! key key mask =
2476 if not (Wsi.withctrl mask)
2477 then
2478 match [@warning "-fragile-match"] Wsi.ks2kt key with
2479 | Keys.Left -> coe (self#updownlevel ~-1)
2480 | Keys.Right -> coe (self#updownlevel 1)
2481 | _ -> super#key key mask
2482 else super#key key mask
2483 end |> setuioh;
2484 postRedisplay "info";
2487 let enterhelpmode =
2488 let source = object
2489 inherit lvsourcebase
2490 method getitemcount = Array.length !S.help
2491 method getitem n =
2492 let s, l, _ = !S.help.(n) in
2493 (s, l)
2495 method exit ~uioh ~cancel ~active ~first ~pan =
2496 let optuioh =
2497 if not cancel
2498 then (
2499 match !S.help.(active) with
2500 | _, _, Some f -> Some (f uioh)
2501 | _, _, None -> Some uioh
2503 else None
2505 m_active <- active;
2506 m_first <- first;
2507 m_pan <- pan;
2508 optuioh
2510 method hasaction n =
2511 match !S.help.(n) with
2512 | _, _, Some _ -> true
2513 | _, _, None -> false
2515 initializer m_active <- -1
2517 in fun () ->
2518 let modehash = findkeyhash conf "help" in
2519 resetmstate ();
2520 new listview ~zebra:false ~helpmode:true
2521 ~source ~trusted:true ~modehash |> setuioh;
2522 postRedisplay "help"
2524 let entermsgsmode =
2525 let msgsource = object
2526 inherit lvsourcebase
2527 val mutable m_items = E.a
2529 method getitemcount = 1 + Array.length m_items
2531 method getitem n =
2532 if n = 0
2533 then "[Clear]", 0
2534 else m_items.(n-1), 0
2536 method exit ~uioh ~cancel ~active ~first ~pan =
2537 ignore uioh;
2538 if not cancel
2539 then (
2540 if active = 0
2541 then Buffer.clear S.errmsgs;
2543 m_active <- active;
2544 m_first <- first;
2545 m_pan <- pan;
2546 None
2548 method hasaction n =
2549 n = 0
2551 method reset =
2552 S.newerrmsgs := false;
2553 let l = Str.split Re.crlf (Buffer.contents S.errmsgs) in
2554 m_items <- Array.of_list l
2556 initializer m_active <- 0
2559 fun () ->
2560 S.text := E.s;
2561 resetmstate ();
2562 msgsource#reset;
2563 let source = (msgsource :> lvsource) in
2564 let modehash = findkeyhash conf "listview" in
2565 object
2566 inherit listview ~zebra:false ~helpmode:false
2567 ~source ~trusted:false ~modehash as super
2568 method! display =
2569 if !S.newerrmsgs
2570 then msgsource#reset;
2571 super#display
2572 end |> setuioh;
2573 postRedisplay "msgs"
2575 let getusertext s =
2576 let editor = getenvdef "EDITOR" E.s in
2577 if emptystr editor
2578 then E.s
2579 else
2580 let tmppath = Filename.temp_file "llpp" "note" in
2581 if nonemptystr s
2582 then (
2583 let oc = open_out tmppath in
2584 output_string oc s;
2585 close_out oc;
2587 let execstr = editor ^ " " ^ tmppath in
2588 let eret r = Printf.ksprintf (fun s -> adderrmsg "gtut:eret" s; r) in
2589 let s =
2590 match spawn execstr [] with
2591 | exception exn -> eret E.s "spawn(%S) failed: %s" execstr @@ exntos exn
2592 | pid ->
2593 match Unix.waitpid [] pid with
2594 | exception exn -> eret E.s "waitpid(%d) failed: %s" pid @@ exntos exn
2595 | (_pid, status) ->
2596 match status with
2597 | Unix.WEXITED 0 -> filecontents tmppath
2598 | Unix.WEXITED n ->
2599 eret E.s "editor process(%s) exited abnormally: %d" execstr n
2600 | Unix.WSIGNALED n ->
2601 eret E.s "editor process(%s) was killed by signal %d" execstr n
2602 | Unix.WSTOPPED n ->
2603 eret E.s "editor(%s) process was stopped by signal %d" execstr n
2605 match Unix.unlink tmppath with
2606 | exception exn -> eret s "failed to ulink %S: %s" tmppath @@ exntos exn
2607 | () -> s
2609 let enterannotmode opaque slinkindex =
2610 let msgsource = object
2611 inherit lvsourcebase
2612 val mutable m_text = E.s
2613 val mutable m_items = E.a
2615 method getitemcount = Array.length m_items
2617 method getitem n =
2618 let label, _func = m_items.(n) in
2619 label, 0
2621 method exit ~uioh ~cancel ~active ~first ~pan =
2622 ignore (uioh, first, pan);
2623 if not cancel
2624 then (
2625 let _label, func = m_items.(active) in
2626 func ()
2628 None
2630 method hasaction n = nonemptystr @@ fst m_items.(n)
2632 method reset s =
2633 let rec split accu b i =
2634 let p = b+i in
2635 if p = String.length s
2636 then (String.sub s b (p-b), fun () -> ()) :: accu
2637 else
2638 if (i > 70 && s.[p] = ' ') || s.[p] = '\r' || s.[p] = '\n'
2639 then
2640 let ss = if i = 0 then E.s else String.sub s b i in
2641 split ((ss, fun () -> ())::accu) (p+1) 0
2642 else split accu b (i+1)
2644 let cleanup () =
2645 wcmd1 U.freepage opaque;
2646 let keys =
2647 Hashtbl.fold (fun key opaque' accu ->
2648 if opaque' = opaque'
2649 then key :: accu else accu) S.pagemap []
2651 List.iter (Hashtbl.remove S.pagemap) keys;
2652 flushtiles ();
2653 gotoxy !S.x !S.y
2655 let dele () =
2656 Ffi.delannot opaque slinkindex;
2657 cleanup ();
2659 let edit inline () =
2660 let update s =
2661 if emptystr s
2662 then dele ()
2663 else (
2664 Ffi.modannot opaque slinkindex s;
2665 cleanup ();
2668 if inline
2669 then
2670 let mode = !S.mode in
2671 let te = ("annotation: ", m_text, None, textentry, update, true) in
2672 S.mode := Textentry (te, fun _ -> S.mode := mode);
2673 S.text := E.s;
2674 enttext ();
2675 else getusertext m_text |> update
2677 m_text <- s;
2678 m_items <-
2679 ( "[Copy]", fun () -> selstring conf.selcmd m_text)
2680 :: ("[Delete]", dele)
2681 :: ("[Edit]", edit conf.annotinline)
2682 :: (E.s, fun () -> ())
2683 :: split [] 0 0 |> List.rev |> Array.of_list
2685 initializer m_active <- 0
2688 S.text := E.s;
2689 let s = Ffi.getannotcontents opaque slinkindex in
2690 resetmstate ();
2691 msgsource#reset s;
2692 let source = (msgsource :> lvsource) in
2693 let modehash = findkeyhash conf "listview" in
2694 object inherit listview ~zebra:false
2695 ~helpmode:false ~source ~trusted:false ~modehash
2696 end |> setuioh;
2697 postRedisplay "enterannotmode"
2699 let gotoremote spec =
2700 let filename, dest = splitatchar spec '#' in
2701 let getpath filename =
2702 let path =
2703 if nonemptystr filename
2704 then
2705 if Filename.is_relative filename
2706 then
2707 let dir = Filename.dirname !S.path in
2708 let dir =
2709 if Filename.is_implicit dir
2710 then Filename.concat (Sys.getcwd ()) dir
2711 else dir
2713 Filename.concat dir filename
2714 else filename
2715 else E.s
2717 if Sys.file_exists path
2718 then path
2719 else E.s
2721 let path = getpath filename in
2722 if emptystr path
2723 then adderrfmt "gotoremote/getpath" "failed getpath for %S\n" filename
2724 else
2725 let dospawn lcmd =
2726 if conf.riani
2727 then
2728 let cmd = Lazy.force_val lcmd in
2729 match spawn cmd with
2730 | exception exn -> dolog "failed to execute `%s': %s" cmd @@ exntos exn
2731 | _pid -> ()
2732 else
2733 let anchor = getanchor () in
2734 let ranchor = !S.path, !S.password, anchor, !S.origin in
2735 S.origin := E.s;
2736 S.ranchors := ranchor :: !S.ranchors;
2737 opendoc path E.s;
2739 if substratis spec 0 "page="
2740 then
2741 match Scanf.sscanf spec "page=%d" (fun n -> n) with
2742 | exception exn ->
2743 adderrfmt "error parsing remote destination" "%s %s" spec @@ exntos exn
2744 | pageno ->
2745 S.anchor := (pageno, 0.0, 0.0);
2746 dospawn @@ lazy (Printf.sprintf "%s -page %d %S"
2747 !S.selfexec pageno path);
2748 else (
2749 S.nameddest := dest;
2750 dospawn @@ lazy (!S.selfexec ^ " " ^ path ^ " -dest " ^ dest)
2753 let gotounder = function
2754 | Ulinkuri s when Ffi.isexternallink s ->
2755 if substratis s 0 "file://"
2756 then gotoremote @@ String.sub s 7 (String.length s - 7)
2757 else Help.gotouri conf.urilauncher s
2758 | Ulinkuri s ->
2759 let pageno, x, y = Ffi.uritolocation s in
2760 addnav ();
2761 gotopagexy pageno x y
2762 | Utext _ | Unone -> ()
2763 | Uannotation (opaque, slinkindex) -> enterannotmode opaque slinkindex
2765 let gotooutline (_, _, kind) =
2766 match kind with
2767 | Onone -> ()
2768 | Oanchor ((pageno, y, _) as anchor) ->
2769 addnav ();
2770 gotoxy !S.x @@
2771 getanchory (if conf.presentation then (pageno, y, 1.0) else anchor)
2772 | Ouri uri -> gotounder (Ulinkuri uri)
2773 | Olaunch cmd -> error "gotounder (Ulaunch %S)" cmd
2774 | Oremote (remote, pageno) ->
2775 error "gotounder (Uremote (%S,%d) )" remote pageno
2776 | Ohistory hist -> gotohist hist
2777 | Oremotedest (path, dest) ->
2778 error "gotounder (Uremotedest (%S, %S))" path dest
2780 class outlinesoucebase fetchoutlines = object (self)
2781 inherit lvsourcebase
2782 val mutable m_items = E.a
2783 val mutable m_minfo = E.a
2784 val mutable m_orig_items = E.a
2785 val mutable m_orig_minfo = E.a
2786 val mutable m_narrow_patterns = []
2787 val mutable m_gen = -1
2789 method getitemcount = Array.length m_items
2791 method getitem n =
2792 let s, n, _ = m_items.(n) in
2793 (s, n+0)
2795 method exit ~(uioh:uioh) ~cancel ~active ~(first:int) ~pan : uioh option =
2796 ignore (uioh, first);
2797 let items, minfo =
2798 if m_narrow_patterns = []
2799 then m_orig_items, m_orig_minfo
2800 else m_items, m_minfo
2802 m_pan <- pan;
2803 if not cancel
2804 then (
2805 m_items <- items;
2806 m_minfo <- minfo;
2807 gotooutline m_items.(active);
2809 else (
2810 m_items <- items;
2811 m_minfo <- minfo;
2813 None
2815 method hasaction (_:int) = true
2817 method greetmsg =
2818 if Array.length m_items != Array.length m_orig_items
2819 then
2820 let s =
2821 match m_narrow_patterns with
2822 | one :: [] -> one
2823 | many -> String.concat Utf8syms.ellipsis (List.rev many)
2825 "Narrowed to " ^ s ^ " (ctrl-u to restore)"
2826 else E.s
2828 method statestr =
2829 match m_narrow_patterns with
2830 | [] -> E.s
2831 | one :: [] -> one
2832 | head :: _ -> Utf8syms.ellipsis ^ head
2834 method narrow pattern =
2835 match Str.regexp_case_fold pattern with
2836 | exception _ -> ()
2837 | re ->
2838 let rec loop accu minfo n =
2839 if n = -1
2840 then (
2841 m_items <- Array.of_list accu;
2842 m_minfo <- Array.of_list minfo;
2844 else
2845 let (s, _, _) as o = m_items.(n) in
2846 let accu, minfo =
2847 match Str.search_forward re s 0 with
2848 | exception Not_found -> accu, minfo
2849 | first -> o :: accu, (first, Str.match_end ()) :: minfo
2851 loop accu minfo (n-1)
2853 loop [] [] (Array.length m_items - 1)
2855 method! getminfo = m_minfo
2857 method denarrow =
2858 m_orig_items <- fetchoutlines ();
2859 m_minfo <- m_orig_minfo;
2860 m_items <- m_orig_items
2862 method add_narrow_pattern pattern =
2863 m_narrow_patterns <- pattern :: m_narrow_patterns
2865 method del_narrow_pattern =
2866 match m_narrow_patterns with
2867 | _ :: rest -> m_narrow_patterns <- rest
2868 | [] -> ()
2870 method renarrow =
2871 self#denarrow;
2872 match m_narrow_patterns with
2873 | pattern :: [] -> self#narrow pattern; pattern
2874 | list ->
2875 List.fold_left (fun accu pattern ->
2876 self#narrow pattern;
2877 pattern ^ Utf8syms.ellipsis ^ accu) E.s list
2879 method calcactive (_:anchor) = 0
2881 method reset anchor items =
2882 if !S.gen != m_gen
2883 then (
2884 m_orig_items <- items;
2885 m_items <- items;
2886 m_narrow_patterns <- [];
2887 m_minfo <- E.a;
2888 m_orig_minfo <- E.a;
2889 m_gen <- !S.gen;
2891 else (
2892 if items != m_orig_items
2893 then (
2894 m_orig_items <- items;
2895 if m_narrow_patterns == []
2896 then m_items <- items;
2899 let active = self#calcactive anchor in
2900 m_active <- active;
2901 m_first <- firstof m_first active
2904 let outlinesource fetchoutlines = object
2905 inherit outlinesoucebase fetchoutlines
2906 method! calcactive anchor =
2907 let rely = getanchory anchor in
2908 let rec loop n best bestd =
2909 if n = Array.length m_items
2910 then best
2911 else
2912 let _, _, kind = m_items.(n) in
2913 match kind with
2914 | Oanchor anchor ->
2915 let orely = getanchory anchor in
2916 let d = abs (orely - rely) in
2917 if d < bestd
2918 then loop (n+1) n d
2919 else loop (n+1) best bestd
2920 | Onone | Oremote _ | Olaunch _
2921 | Oremotedest _ | Ouri _ | Ohistory _ ->
2922 loop (n+1) best bestd
2924 loop 0 ~-1 max_int
2927 let enteroutlinemode, enterbookmarkmode, enterhistmode =
2928 let fetchoutlines sourcetype () =
2929 match sourcetype with
2930 | `bookmarks -> Array.of_list !S.bookmarks
2931 | `outlines -> !S.outlines
2932 | `history -> genhistoutlines () |> Array.of_list
2934 let so = outlinesource (fetchoutlines `outlines) in
2935 let sb = outlinesource (fetchoutlines `bookmarks) in
2936 let sh = outlinesource (fetchoutlines `history) in
2937 let mkselector sourcetype source =
2938 (fun emptymsg ->
2939 let outlines = fetchoutlines sourcetype () in
2940 if Array.length outlines = 0
2941 then showtext ' ' emptymsg
2942 else (
2943 resetmstate ();
2944 Wsi.setcursor Wsi.CURSOR_INHERIT;
2945 let anchor = getanchor () in
2946 source#reset anchor outlines;
2947 S.text := source#greetmsg;
2948 new outlinelistview ~zebra:(sourcetype=`history) ~source |> setuioh;
2949 postRedisplay "enter selector";
2953 let mkenter src errmsg s = fun () -> mkselector src s errmsg in
2954 ( mkenter `outlines "document has no outline" so
2955 , mkenter `bookmarks "document has no bookmarks (yet)" sb
2956 , mkenter `history "history is empty" sh )
2958 let addbookmark title a =
2959 let b = List.filter (fun (title', _, _) -> title <> title') !S.bookmarks in
2960 S.bookmarks := (title, 0, Oanchor a) :: b
2962 let quickbookmark ?title () =
2963 match !S.layout with
2964 | [] -> ()
2965 | l :: _ ->
2966 let title =
2967 match title with
2968 | None ->
2969 Unix.(
2970 let tm = localtime (now ()) in
2971 Printf.sprintf
2972 "Quick (page %d) (bookmarked on %02d/%02d/%d at %02d:%02d)"
2973 (l.pageno+1)
2974 tm.tm_mday (tm.tm_mon+1) (tm.tm_year+1900) tm.tm_hour tm.tm_min
2976 | Some title -> title
2978 addbookmark title (getanchor1 l)
2980 let setautoscrollspeed step goingdown =
2981 let incr = max 1 ((abs step) / 2) in
2982 let incr = if goingdown then incr else -incr in
2983 let astep = boundastep !S.winh (step + incr) in
2984 S.autoscroll := Some astep
2986 let canpan () =
2987 match conf.columns with
2988 | Csplit _ -> true
2989 | Csingle _ | Cmulti _ -> !S.x != 0 || conf.zoom > 1.0
2991 let existsinrow pageno (columns, coverA, coverB) p =
2992 let last = ((pageno - coverA) mod columns) + columns in
2993 let rec any = function
2994 | [] -> false
2995 | l :: rest ->
2996 if l.pageno = coverA - 1 || l.pageno = !S.pagecount - coverB
2997 then p l
2998 else (
2999 if not (p l)
3000 then (if l.pageno = last then false else any rest)
3001 else true
3004 any !S.layout
3006 let nextpage () =
3007 match !S.layout with
3008 | [] ->
3009 let pageno = page_of_y !S.y in
3010 gotoxy !S.x (getpagey (pageno+1))
3011 | l :: rest ->
3012 match conf.columns with
3013 | Csingle _ ->
3014 if conf.presentation && rest == [] && l.pageh > l.pagey + l.pagevh
3015 then
3016 let y = U.clamp (U.pgscale !S.winh) in
3017 gotoxy !S.x y
3018 else
3019 let pageno = min (l.pageno+1) (!S.pagecount-1) in
3020 gotoxy !S.x (getpagey pageno)
3021 | Cmulti ((c, _, _) as cl, _) ->
3022 if conf.presentation
3023 && (existsinrow l.pageno cl
3024 (fun l -> l.pageh > l.pagey + l.pagevh))
3025 then
3026 let y = U.clamp (U.pgscale !S.winh) in
3027 gotoxy !S.x y
3028 else
3029 let pageno = min (l.pageno+c) (!S.pagecount-1) in
3030 gotoxy !S.x (getpagey pageno)
3031 | Csplit (n, _) ->
3032 if l.pageno < !S.pagecount - 1 || l.pagecol < n - 1
3033 then
3034 let pagey, pageh = getpageyh l.pageno in
3035 let pagey = pagey + pageh * l.pagecol in
3036 let ips = if l.pagecol = 0 then 0 else conf.interpagespace in
3037 gotoxy !S.x (pagey + pageh + ips)
3039 let prevpage () =
3040 match !S.layout with
3041 | [] ->
3042 let pageno = page_of_y !S.y in
3043 gotoxy !S.x (getpagey (pageno-1))
3044 | l :: _ ->
3045 match conf.columns with
3046 | Csingle _ ->
3047 if conf.presentation && l.pagey != 0
3048 then gotoxy !S.x (U.clamp (U.pgscale ~-(!S.winh)))
3049 else
3050 let pageno = max 0 (l.pageno-1) in
3051 gotoxy !S.x (getpagey pageno)
3052 | Cmulti ((c, _, coverB) as cl, _) ->
3053 if conf.presentation &&
3054 (existsinrow l.pageno cl (fun l -> l.pagey != 0))
3055 then gotoxy !S.x (U.clamp (U.pgscale ~-(!S.winh)))
3056 else
3057 let decr =
3058 if l.pageno = !S.pagecount - coverB
3059 then 1
3060 else c
3062 let pageno = max 0 (l.pageno-decr) in
3063 gotoxy !S.x (getpagey pageno)
3064 | Csplit (n, _) ->
3065 let y =
3066 if l.pagecol = 0
3067 then
3068 if l.pageno = 0
3069 then l.pagey
3070 else
3071 let pageno = max 0 (l.pageno-1) in
3072 let pagey, pageh = getpageyh pageno in
3073 pagey + (n-1)*pageh
3074 else
3075 let pagey, pageh = getpageyh l.pageno in
3076 pagey + pageh * (l.pagecol-1) - conf.interpagespace
3078 gotoxy !S.x y
3080 let save () =
3081 if emptystr conf.savecmd
3082 then adderrmsg "savepath-command is empty"
3083 "don't know where to save modified document"
3084 else
3085 let savecmd = Str.global_replace Re.percent !S.path conf.savecmd in
3086 let path =
3087 getcmdoutput
3088 (adderrfmt savecmd "failed to obtain path to the saved copy: %s")
3089 savecmd
3091 if nonemptystr path
3092 then
3093 let tmp = path ^ ".tmp" in
3094 Ffi.savedoc tmp;
3095 Unix.rename tmp path
3097 let viewkeyboard key mask =
3098 let enttext te =
3099 let mode = !S.mode in
3100 S.mode := Textentry (te, fun _ -> S.mode := mode);
3101 S.text := E.s;
3102 enttext ();
3103 postRedisplay "view:enttext"
3104 and histback () =
3105 match !S.nav.past with
3106 | [] -> ()
3107 | prev :: prest ->
3108 S.nav := { past = prest ; future = getanchor () :: !S.nav.future; };
3109 gotoxy !S.x (getanchory prev)
3111 let ctrl = Wsi.withctrl mask in
3112 let open Keys in
3113 match Wsi.ks2kt key with
3114 | Ascii 'Q' -> exit 0
3115 | Ascii 'z' ->
3116 let yloc f =
3117 match List.rev !S.rects with
3118 | [] -> ()
3119 | (pageno, _, (_, y0, _, y1, _, y2, _, y3)) :: _ ->
3120 f pageno (y0, y1, y2, y3)
3121 and fsel f (y0, y1, y2, y3) = f y0 y1 |> f y2 |> f y3 |> truncate in
3122 let ondone msg = S.text := msg
3123 and zmod _ _ k =
3124 match [@warning "-fragile-match"] k with
3125 | Keys.Ascii 'z' ->
3126 let f pageno ys =
3127 let miny = fsel min ys in
3128 let hh = (fsel max ys - miny)/2 in
3129 gotopage1 pageno (miny + hh - !S.winh/2)
3131 yloc f;
3132 TEdone "center"
3133 | Keys.Ascii 't' ->
3134 let f pageno ys = gotopage1 pageno @@ fsel min ys in
3135 yloc f;
3136 TEdone "top"
3137 | Keys.Ascii 'b' ->
3138 let f pageno ys = gotopage1 pageno (fsel max ys - !S.winh) in
3139 yloc f;
3140 TEdone "bottom"
3141 | _ -> TEstop
3143 enttext (": ", E.s, None, zmod !S.mode, ondone, true)
3144 | Ascii 'W' ->
3145 if Ffi.hasunsavedchanges ()
3146 then save ()
3147 | Insert ->
3148 if conf.angle mod 360 = 0 && not (isbirdseye !S.mode)
3149 then (
3150 S.mode := (
3151 match !S.lnava with
3152 | None -> LinkNav (Ltgendir 0)
3153 | Some pn -> LinkNav (Ltexact pn)
3155 gotoxy !S.x !S.y;
3157 else impmsg "keyboard link navigation does not work under rotation"
3158 | Escape | Ascii 'q' ->
3159 begin match !S.mstate with
3160 | Mzoomrect _ ->
3161 resetmstate ();
3162 postRedisplay "kill rect";
3163 | Msel _
3164 | Mpan _
3165 | Mscrolly | Mscrollx
3166 | Mzoom _
3167 | Mnone ->
3168 begin match !S.mode with
3169 | LinkNav ln ->
3170 begin match ln with
3171 | Ltexact pl -> S.lnava := Some pl
3172 | Ltgendir _ | Ltnotready _ -> S.lnava := None
3173 end;
3174 S.mode := View;
3175 postRedisplay "esc leave linknav"
3176 | Birdseye _ | Textentry _ | View ->
3177 match !S.ranchors with
3178 | [] -> raise Quit
3179 | (path, password, anchor, origin) :: rest ->
3180 S.ranchors := rest;
3181 S.anchor := anchor;
3182 S.origin := origin;
3183 S.nameddest := E.s;
3184 opendoc path password
3185 end;
3186 end;
3187 | Ascii 'o' -> enteroutlinemode ()
3188 | Ascii 'u' ->
3189 S.rects := [];
3190 S.text := E.s;
3191 Hashtbl.iter (fun _ opaque -> Ffi.clearmark opaque) S.pagemap;
3192 postRedisplay "dehighlight";
3193 | Ascii (('/' | '?') as c) ->
3194 let ondone isforw s =
3195 cbput !S.hists.pat s;
3196 S.searchpattern := s;
3197 search s isforw
3199 enttext (String.make 1 c, E.s, Some (onhist !S.hists.pat),
3200 textentry, ondone (c = '/'), true)
3201 | Ascii '+' | Ascii '=' when ctrl ->
3202 let incr = if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01 in
3203 pivotzoom (conf.zoom +. incr)
3204 | Ascii '+' ->
3205 let ondone s =
3206 let n =
3207 try int_of_string s with exn ->
3208 S.text := Printf.sprintf "bad integer `%s': %s" s @@ exntos exn;
3209 max_int
3211 if n != max_int
3212 then (
3213 conf.pagebias <- n;
3214 S.text := "page bias is now " ^ string_of_int n;
3217 enttext ("page bias: ", E.s, None, intentry, ondone, true)
3218 | Ascii '-' when ctrl ->
3219 let decr = if conf.zoom -. 0.1 < 0.1 then 0.01 else 0.1 in
3220 pivotzoom (max 0.01 (conf.zoom -. decr))
3221 | Ascii '-' ->
3222 let ondone msg = S.text := msg in
3223 enttext ("option: ", E.s, None,
3224 optentry !S.mode, ondone, true)
3225 | Ascii '0' when ctrl ->
3226 if conf.zoom = 1.0
3227 then gotoxy 0 !S.y
3228 else setzoom 1.0
3229 | Ascii ('1'|'2' as c) when ctrl && conf.fitmodel != FitPage ->
3230 let cols =
3231 match conf.columns with
3232 | Csingle _ | Cmulti _ -> 1
3233 | Csplit (n, _) -> n
3235 let h = !S.winh -
3236 conf.interpagespace lsl (if conf.presentation then 1 else 0)
3238 let zoom = Ffi.zoomforh !S.winw h 0 cols in
3239 if zoom > 0.0 && (c = '2' || zoom < 1.0)
3240 then setzoom zoom
3241 | Ascii '3' when ctrl ->
3242 let fm =
3243 match conf.fitmodel with
3244 | FitWidth -> FitProportional
3245 | FitProportional -> FitPage
3246 | FitPage -> FitWidth
3248 S.text := "fit model: " ^ FMTE.to_string fm;
3249 reqlayout conf.angle fm
3250 | Ascii '4' when ctrl ->
3251 let zoom = Ffi.getmaxw () /. float !S.winw in
3252 if zoom > 0.0 then setzoom zoom
3253 | Fn 9 | Ascii '9' when ctrl -> togglebirdseye ()
3254 | Ascii ('0'..'9' as c) when not ctrl ->
3255 let ondone s =
3256 let n =
3257 try int_of_string s with exn ->
3258 adderrfmt "int_of_string" "`%s': %s" s @@ exntos exn;
3261 if n >= 0
3262 then (
3263 addnav ();
3264 cbput !S.hists.pag (string_of_int n);
3265 gotopage1 (n + conf.pagebias - 1) 0;
3268 let pageentry text = function [@warning "-fragile-match"]
3269 | Keys.Ascii 'g' -> TEdone text
3270 | key -> intentry text key
3272 enttext (":", String.make 1 c, Some (onhist !S.hists.pag),
3273 pageentry, ondone, true)
3274 | Ascii 'b' ->
3275 conf.scrollb <- if conf.scrollb = 0 then (scrollbvv lor scrollbhv) else 0;
3276 postRedisplay "toggle scrollbar";
3277 | Ascii 'B' ->
3278 S.bzoom := not !S.bzoom;
3279 S.rects := [];
3280 showtext ' ' ("block zoom " ^ onoffs !S.bzoom)
3281 | Ascii 'l' ->
3282 conf.hlinks <- not conf.hlinks;
3283 S.text := "highlightlinks " ^ onoffs conf.hlinks;
3284 postRedisplay "toggle highlightlinks"
3285 | Ascii 'F' ->
3286 if conf.angle mod 360 = 0
3287 then (
3288 S.glinks := true;
3289 let mode = !S.mode in
3290 let te = ("goto: ", E.s, None, linknentry, linknact gotounder, false) in
3291 S.mode := Textentry (te, (fun _ -> S.glinks := false; S.mode := mode));
3292 S.text := E.s;
3293 postRedisplay "view:linkent(F)"
3295 else impmsg "hint mode does not work under rotation"
3296 | Ascii 'y' ->
3297 S.glinks := true;
3298 let mode = !S.mode in
3299 let te = ("copy: ", E.s, None, linknentry,
3300 linknact (fun under -> selstring conf.selcmd (undertext under)),
3301 false) in
3302 S.mode := Textentry (te, (fun _ -> S.glinks := false; S.mode := mode));
3303 S.text := E.s;
3304 postRedisplay "view:linkent"
3305 | Ascii 'a' ->
3306 begin match !S.autoscroll with
3307 | Some step ->
3308 conf.autoscrollstep <- step;
3309 S.autoscroll := None
3310 | None -> S.autoscroll := Some conf.autoscrollstep
3312 | Ascii 'p' when ctrl -> launchpath ()
3313 | Ascii 'P' ->
3314 setpresentationmode (not conf.presentation);
3315 showtext ' ' ("presentation mode " ^ onoffs conf.presentation)
3316 | Ascii 'f' ->
3317 if List.mem Wsi.Fullscreen !S.winstate
3318 then Wsi.reshape conf.cwinw conf.cwinh
3319 else Wsi.fullscreen ()
3320 | Ascii ('p'|'N') -> search !S.searchpattern false
3321 | Ascii 'n' | Fn 3 -> search !S.searchpattern true
3322 | Ascii 't' ->
3323 begin match !S.layout with
3324 | [] -> ()
3325 | l :: _ -> gotoxy !S.x (getpagey l.pageno)
3327 | Ascii ' ' -> nextpage ()
3328 | Delete -> prevpage ()
3329 | Ascii '=' -> showtext ' ' (describe_layout !S.layout);
3330 | Ascii 'w' ->
3331 begin match !S.layout with
3332 | [] -> ()
3333 | l :: _ ->
3334 Wsi.reshape l.pagew l.pageh;
3335 postRedisplay "w"
3337 | Ascii '\'' -> enterbookmarkmode ()
3338 | Ascii 'i' -> enterinfomode ()
3339 | Ascii 'e' when Buffer.length S.errmsgs > 0 -> entermsgsmode ()
3340 | Ascii 'm' ->
3341 let ondone s =
3342 match !S.layout with
3343 | l :: _ when nonemptystr s -> addbookmark s @@ getanchor1 l
3344 | _ -> ()
3346 enttext ("bookmark: ", E.s, None, textentry, ondone, true)
3347 | Ascii '~' ->
3348 quickbookmark ();
3349 showtext ' ' "Quick bookmark added";
3350 | Ascii 'x' -> !S.roamf ()
3351 | Ascii ('<'|'>' as c) ->
3352 reqlayout (conf.angle + (if c = '>' then 30 else -30)) conf.fitmodel
3353 | Ascii ('['|']' as c) ->
3354 conf.colorscale <-
3355 bound (conf.colorscale +. (if c = ']' then 0.1 else -0.1)) 0.0 1.0;
3356 postRedisplay "brightness";
3357 | Ascii 'c' when !S.mode = View ->
3358 if Wsi.withalt mask
3359 then (
3360 if conf.zoom > 1.0
3361 then
3362 let m = (!S.winw - !S.w) / 2 in
3363 gotoxy m !S.y
3365 else
3366 let (c, a, b), z =
3367 match !S.prevcolumns with
3368 | None -> (1, 0, 0), 1.0
3369 | Some (columns, z) ->
3370 let cab =
3371 match columns with
3372 | Csplit (c, _) -> -c, 0, 0
3373 | Cmulti ((c, a, b), _) -> c, a, b
3374 | Csingle _ -> 1, 0, 0
3376 cab, z
3378 setcolumns View c a b;
3379 setzoom z
3380 | Down | Up when ctrl && Wsi.withshift mask ->
3381 let zoom, x = !S.prevzoom in
3382 setzoom zoom;
3383 S.x := x;
3384 | Up ->
3385 begin match !S.autoscroll with
3386 | None ->
3387 begin match !S.mode with
3388 | Birdseye beye -> upbirdseye 1 beye
3389 | Textentry _ | View | LinkNav _ ->
3390 if ctrl
3391 then gotoxy !S.x (U.clamp ~-(!S.winh/2))
3392 else (
3393 if not (Wsi.withshift mask) && conf.presentation
3394 then prevpage ()
3395 else gotoxy !S.x (U.clamp (-conf.scrollstep))
3398 | Some n -> setautoscrollspeed n false
3400 | Down ->
3401 begin match !S.autoscroll with
3402 | None ->
3403 begin match !S.mode with
3404 | Birdseye beye -> downbirdseye 1 beye
3405 | Textentry _ | View | LinkNav _ ->
3406 if ctrl
3407 then gotoxy !S.x (U.clamp (!S.winh/2))
3408 else (
3409 if not (Wsi.withshift mask) && conf.presentation
3410 then nextpage ()
3411 else gotoxy !S.x (U.clamp (conf.scrollstep))
3414 | Some n -> setautoscrollspeed n true
3416 | Ascii 'H' -> enterhistmode ()
3417 | Fn 1 when Wsi.withalt mask -> enterhistmode ()
3418 | Fn 1 -> enterhelpmode ()
3419 | Left | Right when not (Wsi.withalt mask) ->
3420 if canpan ()
3421 then
3422 let dx =
3423 if ctrl
3424 then !S.winw / 2
3425 else conf.hscrollstep
3427 let dx =
3428 let pv = Wsi.ks2kt key in
3429 if pv = Keys.Left then dx else -dx
3431 gotoxy (U.panbound (!S.x + dx)) !S.y
3432 else (
3433 S.text := E.s;
3434 postRedisplay "left/right"
3436 | Prior ->
3437 let y =
3438 if ctrl
3439 then
3440 match !S.layout with
3441 | [] -> !S.y
3442 | l :: _ -> !S.y - l.pagey
3443 else U.clamp (U.pgscale ~- !S.winh)
3445 gotoxy !S.x y
3446 | Next ->
3447 let y =
3448 if ctrl
3449 then
3450 match List.rev !S.layout with
3451 | [] -> !S.y
3452 | l :: _ -> getpagey l.pageno
3453 else U.clamp (U.pgscale !S.winh)
3455 gotoxy !S.x y
3456 | Ascii 'g' | Home ->
3457 addnav ();
3458 gotoxy 0 0
3459 | Ascii 'G' | End ->
3460 addnav ();
3461 gotoxy 0 (U.clamp !S.maxy)
3462 | Right when Wsi.withalt mask ->
3463 (match !S.nav.future with
3464 | [] -> ()
3465 | next :: frest ->
3466 S.nav := { past = getanchor () :: !S.nav.past; future = frest; };
3467 gotoxy !S.x (getanchory next)
3469 | Left when Wsi.withalt mask -> histback ()
3470 | Backspace -> histback ()
3471 | Ascii 'r' -> reload ()
3472 | Ascii 'v' when conf.debug ->
3473 S.rects := [];
3474 List.iter (fun l ->
3475 match getopaque l.pageno with
3476 | exception Not_found -> ()
3477 | opaque ->
3478 let x0, y0, x1, y1 = Ffi.pagebbox opaque in
3479 let rect = (float x0, float y0,
3480 float x1, float y0,
3481 float x1, float y1,
3482 float x0, float y1) in
3483 debugrect rect;
3484 let color = (0.0, 0.0, 1.0 /. (l.pageno mod 3 |> float), 0.5) in
3485 S.rects := (l.pageno, color, rect) :: !S.rects;
3486 ) !S.layout;
3487 postRedisplay "v";
3488 | Ascii '|' ->
3489 let mode = !S.mode in
3490 let cmd = ref E.s in
3491 let onleave = function
3492 | Cancel -> S.mode := mode
3493 | Confirm ->
3494 List.iter (fun l ->
3495 match getopaque l.pageno with
3496 | exception Not_found -> ()
3497 | opaque -> pipesel opaque !cmd) !S.layout;
3498 S.mode := mode
3500 let ondone s =
3501 cbput !S.hists.sel s;
3502 cmd := s
3504 let te =
3505 "| ", !cmd, Some (onhist !S.hists.sel), textentry, ondone, true
3507 postRedisplay "|";
3508 S.mode := Textentry (te, onleave);
3509 | (Ascii _|Fn _|Enter|Left|Right|Code _|Ctrl _) ->
3510 vlog "huh? %s" (Wsi.keyname key)
3512 let linknavkeyboard key mask linknav =
3513 let pv = Wsi.ks2kt key in
3514 let getpage pageno =
3515 let rec loop = function
3516 | [] -> None
3517 | l :: _ when l.pageno = pageno -> Some l
3518 | _ :: rest -> loop rest
3519 in loop !S.layout
3521 let doexact (pageno, n) =
3522 match getopaque pageno, getpage pageno with
3523 | opaque, Some l ->
3524 if pv = Keys.Enter
3525 then
3526 let under = Ffi.getlink opaque n in
3527 postRedisplay "link gotounder";
3528 gotounder under;
3529 S.mode := View;
3530 else
3531 let opt, dir =
3532 let open Keys in
3533 match pv with
3534 | Home -> Some (Ffi.findlink opaque LDfirst), -1
3535 | End -> Some (Ffi.findlink opaque LDlast), 1
3536 | Left -> Some (Ffi.findlink opaque (LDleft n)), -1
3537 | Right -> Some (Ffi.findlink opaque (LDright n)), 1
3538 | Up -> Some (Ffi.findlink opaque (LDup n)), -1
3539 | Down -> Some (Ffi.findlink opaque (LDdown n)), 1
3540 | Delete|Escape|Insert|Enter|Next|Prior|Ascii _
3541 | Code _|Fn _|Ctrl _|Backspace -> None, 0
3543 let pwl l dir =
3544 begin match Ffi.findpwl l.pageno dir with
3545 | Pwlnotfound -> ()
3546 | Pwl pageno ->
3547 let notfound dir =
3548 S.mode := LinkNav (Ltgendir dir);
3549 let y, h = getpageyh pageno in
3550 let y =
3551 if dir < 0
3552 then y + h - !S.winh
3553 else y
3555 gotoxy !S.x y
3557 begin match getopaque pageno, getpage pageno with
3558 | opaque, Some _ ->
3559 let link =
3560 let ld = if dir > 0 then LDfirst else LDlast in
3561 Ffi.findlink opaque ld
3563 begin match link with
3564 | Lfound m ->
3565 showlinktype (Ffi.getlink opaque m);
3566 S.mode := LinkNav (Ltexact (pageno, m));
3567 postRedisplay "linknav jpage";
3568 | Lnotfound -> notfound dir
3569 end;
3570 | _ | exception Not_found -> notfound dir
3571 end;
3572 end;
3574 begin match opt with
3575 | Some Lnotfound -> pwl l dir;
3576 | Some (Lfound m) ->
3577 if m = n
3578 then pwl l dir
3579 else (
3580 let _, y0, _, y1 = Ffi.getlinkrect opaque m in
3581 if y0 < l.pagey
3582 then gotopage1 l.pageno y0
3583 else (
3584 let d = fstate.fontsize + 1 in
3585 if y1 - l.pagey > l.pagevh - d
3586 then gotopage1 l.pageno (y1 - !S.winh + d)
3587 else postRedisplay "linknav";
3589 showlinktype (Ffi.getlink opaque m);
3590 S.mode := LinkNav (Ltexact (l.pageno, m));
3593 | None -> viewkeyboard key mask
3594 end;
3595 | _ | exception Not_found -> viewkeyboard key mask
3597 if pv = Keys.Insert
3598 then (
3599 begin match linknav with
3600 | Ltexact pa -> S.lnava := Some pa
3601 | Ltgendir _ | Ltnotready _ -> ()
3602 end;
3603 S.mode := View;
3604 postRedisplay "leave linknav"
3606 else
3607 match linknav with
3608 | Ltgendir _ | Ltnotready _ -> viewkeyboard key mask
3609 | Ltexact exact -> doexact exact
3611 let keyboard key mask =
3612 if (key = Char.code 'g' && Wsi.withctrl mask) && not (istextentry !S.mode)
3613 then wcmd U.interrupt ""
3614 else !S.uioh#key key mask |> setuioh
3616 let birdseyekeyboard key mask
3617 ((oconf, leftx, pageno, hooverpageno, anchor) as beye) =
3618 let incr =
3619 match conf.columns with
3620 | Csingle _ -> 1
3621 | Cmulti ((c, _, _), _) -> c
3622 | Csplit _ -> error "bird's eye split mode"
3624 let pgh layout = List.fold_left
3625 (fun m l -> max l.pageh m) !S.winh layout in
3626 let open Keys in
3627 match Wsi.ks2kt key with
3628 | Ascii 'l' when Wsi.withctrl mask ->
3629 let y, h = getpageyh pageno in
3630 let top = (!S.winh - h) / 2 in
3631 gotoxy !S.x (max 0 (y - top))
3632 | Enter -> leavebirdseye beye false
3633 | Escape -> leavebirdseye beye true
3634 | Up -> upbirdseye incr beye
3635 | Down -> downbirdseye incr beye
3636 | Left -> upbirdseye 1 beye
3637 | Right -> downbirdseye 1 beye
3639 | Prior ->
3640 begin match !S.layout with
3641 | l :: _ ->
3642 if l.pagey != 0
3643 then (
3644 S.mode := Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
3645 gotopage1 l.pageno 0;
3647 else (
3648 let layout = layout !S.x (!S.y - !S.winh)
3649 !S.winw
3650 (pgh !S.layout) in
3651 match layout with
3652 | [] -> gotoxy !S.x (U.clamp ~- !S.winh)
3653 | l :: _ ->
3654 S.mode := Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
3655 gotopage1 l.pageno 0
3658 | [] -> gotoxy !S.x (U.clamp ~- !S.winh)
3659 end;
3661 | Next ->
3662 begin match List.rev !S.layout with
3663 | l :: _ ->
3664 let layout = layout !S.x
3665 (!S.y + (pgh !S.layout))
3666 !S.winw !S.winh in
3667 begin match layout with
3668 | [] ->
3669 let incr = l.pageh - l.pagevh in
3670 if incr = 0
3671 then (
3672 S.mode :=
3673 Birdseye (
3674 oconf, leftx, !S.pagecount - 1, hooverpageno, anchor
3676 postRedisplay "birdseye pagedown";
3678 else gotoxy !S.x (U.clamp (incr + conf.interpagespace*2));
3680 | l :: _ ->
3681 S.mode := Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
3682 gotopage1 l.pageno 0;
3685 | [] -> gotoxy !S.x (U.clamp !S.winh)
3686 end;
3688 | Home ->
3689 S.mode := Birdseye (oconf, leftx, 0, hooverpageno, anchor);
3690 gotopage1 0 0
3692 | End ->
3693 let pageno = !S.pagecount - 1 in
3694 S.mode := Birdseye (oconf, leftx, pageno, hooverpageno, anchor);
3695 if not (U.pagevisible !S.layout pageno)
3696 then
3697 let h =
3698 match List.rev !S.pdims with
3699 | [] -> !S.winh
3700 | (_, _, h, _) :: _ -> h
3702 gotoxy
3703 !S.x
3704 (max 0 (getpagey pageno - (!S.winh - h - conf.interpagespace)))
3705 else postRedisplay "birdseye end";
3707 | Delete|Insert|Ascii _|Code _|Ctrl _|Fn _|Backspace -> viewkeyboard key mask
3709 let drawpage l =
3710 let color =
3711 match !S.mode with
3712 | Textentry _ -> U.scalecolor 0.4
3713 | LinkNav _ | View -> U.scalecolor 1.0
3714 | Birdseye (_, _, pageno, hooverpageno, _) ->
3715 if l.pageno = hooverpageno
3716 then U.scalecolor 0.9
3717 else (
3718 if l.pageno = pageno
3719 then (
3720 let c = U.scalecolor 1.0 in
3721 GlDraw.color c;
3722 GlDraw.line_width 3.0;
3723 let dispx = l.pagedispx in
3724 linerect
3725 (float (dispx-1)) (float (l.pagedispy-1))
3726 (float (dispx+l.pagevw+1))
3727 (float (l.pagedispy+l.pagevh+1));
3728 GlDraw.line_width 1.0;
3731 else U.scalecolor 0.8
3734 drawtiles l color
3736 let postdrawpage l linkindexbase =
3737 match getopaque l.pageno with
3738 | exception Not_found -> 0
3739 | opaque ->
3740 if tileready l l.pagex l.pagey
3741 then
3742 let x = l.pagedispx - l.pagex
3743 and y = l.pagedispy - l.pagey in
3744 let hlmask =
3745 match conf.columns with
3746 | Csingle _ | Cmulti _ ->
3747 (if conf.hlinks then 1 else 0)
3748 + (if !S.glinks
3749 && not (isbirdseye !S.mode) then 2 else 0)
3750 | Csplit _ -> 0
3752 let s =
3753 match !S.mode with
3754 | Textentry ((_, s, _, _, _, _), _) when !S.glinks -> s
3755 | Textentry _
3756 | Birdseye _
3757 | View
3758 | LinkNav _ -> E.s
3760 let n =
3761 Ffi.postprocess opaque hlmask x y
3762 (linkindexbase, s, conf.hfsize, conf.hcs) in
3763 if n < 0
3764 then (Glutils.redisplay := true; 0)
3765 else n
3766 else 0
3768 let scrollindicator () =
3769 let sbw, ph, sh = !S.uioh#scrollph in
3770 let sbh, pw, sw = !S.uioh#scrollpw in
3772 let x0,x1,hx0 =
3773 if conf.leftscroll
3774 then (0, sbw, sbw)
3775 else ((!S.winw - sbw), !S.winw, 0)
3778 Gl.enable `blend;
3779 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3780 let (r, g, b, alpha) = conf.sbarcolor in
3781 GlDraw.color (r, g, b) ~alpha;
3782 filledrect (float x0) 0. (float x1) (float !S.winh);
3783 filledrect
3784 (float hx0) (float (!S.winh - sbh))
3785 (float (hx0 + !S.winw)) (float !S.winh);
3786 let (r, g, b, alpha) = conf.sbarhndlcolor in
3787 GlDraw.color (r, g, b) ~alpha;
3789 filledrect (float x0) ph (float x1) (ph +. sh);
3790 let pw = pw +. float hx0 in
3791 filledrect pw (float (!S.winh - sbh)) (pw +. sw) (float !S.winh);
3792 Gl.disable `blend
3794 let showsel () =
3795 match !S.mstate with
3796 | Mnone | Mscrolly | Mscrollx | Mpan _ | Mzoom _ | Mzoomrect _ -> ()
3797 | Msel ((x0, y0), (x1, y1)) ->
3798 let identify opaque l px py = Some (opaque, l.pageno, px, py) in
3799 let o0,n0,px0,py0 =
3800 onppundermouse identify x0 y0 (Opaque.of_string E.s, -1, 0, 0) in
3801 let _o1,n1,px1,py1 =
3802 onppundermouse identify x1 y1 (Opaque.of_string E.s, -1, 0, 0) in
3803 if n0 != -1 && n0 = n1 then Ffi.seltext o0 (px0, py0, px1, py1)
3805 let showrects = function
3806 | [] -> ()
3807 | rects ->
3808 Gl.enable `blend;
3809 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
3810 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3811 List.iter
3812 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
3813 List.iter (fun l ->
3814 if l.pageno = pageno
3815 then
3816 let dx = float (l.pagedispx - l.pagex) in
3817 let dy = float (l.pagedispy - l.pagey) in
3818 let r, g, b, alpha = c in
3819 GlDraw.color (r, g, b) ~alpha;
3820 filledrect2
3821 (x0+.dx) (y0+.dy)
3822 (x1+.dx) (y1+.dy)
3823 (x3+.dx) (y3+.dy)
3824 (x2+.dx) (y2+.dy);
3825 ) !S.layout
3826 ) rects;
3827 Gl.disable `blend
3829 let display () =
3830 let sc (r, g, b) = let s = conf.colorscale in (r *. s, g *. s, b *. s) in
3831 GlDraw.color (sc conf.bgcolor);
3832 GlClear.color (sc conf.bgcolor);
3833 GlClear.clear [`color];
3834 List.iter drawpage !S.layout;
3835 let rects =
3836 match !S.mode with
3837 | LinkNav (Ltgendir _) | LinkNav (Ltnotready _)
3838 | Birdseye _
3839 | Textentry _
3840 | View -> !S.rects
3841 | LinkNav (Ltexact (pageno, linkno)) ->
3842 match getopaque pageno with
3843 | exception Not_found -> !S.rects
3844 | opaque ->
3845 let x0, y0, x1, y1 = Ffi.getlinkrect opaque linkno in
3846 let color =
3847 if conf.invert
3848 then (1.0, 1.0, 1.0, 0.5)
3849 else (0.0, 0.0, 0.5, 0.5)
3851 (pageno, color,
3852 (float x0, float y0,
3853 float x1, float y0,
3854 float x1, float y1,
3855 float x0, float y1)
3856 ) :: !S.rects
3858 showrects rects;
3859 let rec postloop linkindexbase = function
3860 | l :: rest ->
3861 let linkindexbase = linkindexbase + postdrawpage l linkindexbase in
3862 postloop linkindexbase rest
3863 | [] -> ()
3865 showsel ();
3866 postloop 0 !S.layout;
3867 !S.uioh#display;
3868 begin match !S.mstate with
3869 | Mzoomrect ((x0, y0), (x1, y1)) ->
3870 Gl.enable `blend;
3871 GlDraw.color (0.3, 0.3, 0.3) ~alpha:0.5;
3872 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3873 filledrect (float x0) (float y0) (float x1) (float y1);
3874 Gl.disable `blend;
3875 | Msel _
3876 | Mpan _
3877 | Mscrolly | Mscrollx
3878 | Mzoom _
3879 | Mnone -> ()
3880 end;
3881 enttext ();
3882 scrollindicator ();
3883 Wsi.swapb ()
3885 let display () =
3886 match !S.reload with
3887 | Some (x, y, t) ->
3888 if x != !S.x || y != !S.y || abs_float @@ now () -. t > 0.5
3889 || (!S.layout != [] && layoutready !S.layout)
3890 then (
3891 S.reload := None;
3892 display ()
3894 | None -> display ()
3896 let zoomrect x y x1 y1 =
3897 let x0 = min x x1
3898 and x1 = max x x1
3899 and y0 = min y y1 in
3900 let zoom = (float !S.w) /. float (x1 - x0) in
3901 let margin =
3902 let simple () =
3903 if !S.w < !S.winw
3904 then (!S.winw - !S.w) / 2
3905 else 0
3907 match conf.fitmodel with
3908 | FitWidth | FitProportional -> simple ()
3909 | FitPage ->
3910 match conf.columns with
3911 | Csplit _ ->
3912 onppundermouse (fun _ l _ _ -> Some l.pagedispx) x0 y0 x0
3913 | Cmulti _ | Csingle _ -> simple ()
3915 gotoxy ((!S.x + margin) - x0) (!S.y + y0);
3916 S.anchor := getanchor ();
3917 setzoom zoom;
3918 resetmstate ()
3920 let annot inline x y =
3921 match unproject x y with
3922 | Some (opaque, n, ux, uy) ->
3923 let add text =
3924 Ffi.addannot opaque ux uy text;
3925 wcmd1 U.freepage opaque;
3926 Hashtbl.remove S.pagemap (n, !S.gen);
3927 flushtiles ();
3928 gotoxy !S.x !S.y
3930 if inline
3931 then
3932 let mode = !S.mode in
3933 let te = ("annotation: ", E.s, None, textentry, add, true) in
3934 S.mode := Textentry (te, fun _ -> S.mode := mode);
3935 S.text := E.s;
3936 enttext ();
3937 postRedisplay "annot"
3938 else add @@ getusertext E.s
3939 | _ -> ()
3941 let zoomblock x y =
3942 let g opaque l px py =
3943 match Ffi.rectofblock opaque px py with
3944 | Some a ->
3945 let x0 = a.(0) -. 20. in
3946 let x1 = a.(1) +. 20. in
3947 let y0 = a.(2) -. 20. in
3948 let zoom = (float !S.w) /. (x1 -. x0) in
3949 let pagey = getpagey l.pageno in
3950 let margin = (!S.w - l.pagew)/2 in
3951 let nx = -truncate x0 - margin in
3952 gotoxy nx (pagey + truncate y0);
3953 S.anchor := getanchor ();
3954 setzoom zoom;
3955 None
3956 | None -> None
3958 match conf.columns with
3959 | Csplit _ ->
3960 impmsg "block zooming does not work properly in split columns mode"
3961 | Cmulti _ | Csingle _ -> onppundermouse g x y ()
3963 let scrollx x =
3964 let winw = !S.winw - 1 in
3965 let s = float x /. float winw in
3966 let destx = truncate (float (!S.w + winw) *. s) in
3967 gotoxy (winw - destx) !S.y;
3968 S.mstate := Mscrollx
3970 let scrolly y =
3971 let s = float y /. float !S.winh in
3972 let desty = truncate (s *. float (U.maxy ())) in
3973 gotoxy !S.x desty;
3974 S.mstate := Mscrolly
3976 let viewmulticlick clicks x y mask =
3977 let g opaque l px py =
3978 let mark =
3979 match clicks with
3980 | 2 -> Mark_word
3981 | 3 -> Mark_line
3982 | 4 -> Mark_block
3983 | _ -> Mark_page
3985 if Ffi.markunder opaque px py mark
3986 then (
3987 Some (fun () ->
3988 let dopipe cmd =
3989 match getopaque l.pageno with
3990 | exception Not_found -> ()
3991 | opaque -> pipesel opaque cmd
3993 S.roamf := (fun () -> dopipe conf.paxcmd);
3994 if not (Wsi.withctrl mask) then dopipe conf.selcmd;
3997 else None
3999 postRedisplay "viewmulticlick";
4000 onppundermouse g x y (fun () -> impmsg "nothing to select") ()
4002 let canselect () =
4003 match conf.columns with
4004 | Csplit _ -> false
4005 | Csingle _ | Cmulti _ -> conf.angle mod 360 = 0
4007 let viewmouse button down x y mask =
4008 match button with
4009 | n when (n == 4 || n == 5) && not down ->
4010 if Wsi.withctrl mask
4011 then (
4012 let incr =
4013 if n = 5
4014 then if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01
4015 else if conf.zoom -. 0.1 < 0.1 then -0.01 else -0.1
4017 let fx, fy =
4018 match !S.mstate with
4019 | Mzoom (oldn, _, pos) when n = oldn -> pos
4020 | Mzoomrect _ | Mnone | Mpan _
4021 | Msel _ | Mscrollx | Mscrolly | Mzoom _ -> (x, y)
4023 let zoom = conf.zoom -. incr in
4024 S.mstate := Mzoom (n, 0, (x, y));
4025 if false && abs (fx - x) > 5 || abs (fy - y) > 5
4026 then pivotzoom ~x ~y zoom
4027 else pivotzoom zoom
4029 else (
4030 match !S.autoscroll with
4031 | Some step -> setautoscrollspeed step (n=4)
4032 | None ->
4033 if conf.wheelbypage || conf.presentation
4034 then (
4035 if n = 4
4036 then prevpage ()
4037 else nextpage ()
4039 else
4040 let incr = if n = 4 then -conf.scrollstep else conf.scrollstep in
4041 let incr = incr * 2 in
4042 let y = U.clamp incr in
4043 gotoxy !S.x y
4046 | n when (n = 6 || n = 7) && not down && canpan () ->
4047 let x =
4048 U.panbound (!S.x + (if n = 7 then -2 else 2) * conf.hscrollstep) in
4049 gotoxy x !S.y
4051 | 1 when Wsi.withshift mask ->
4052 S.mstate := Mnone;
4053 if not down
4054 then (
4055 match unproject x y with
4056 | None -> ()
4057 | Some (_, pageno, ux, uy) ->
4058 let cmd = Printf.sprintf "%s %s %d %d %d" conf.stcmd !S.path
4059 pageno ux uy
4061 match spawn cmd [] with
4062 | exception exn ->
4063 adderrfmt "spawn" "execution of synctex command(%S) failed: %S"
4064 conf.stcmd @@ exntos exn
4065 | _pid -> ()
4068 | 1 when Wsi.withctrl mask ->
4069 if down
4070 then (
4071 Wsi.setcursor Wsi.CURSOR_FLEUR;
4072 S.mstate := Mpan (x, y)
4074 else S.mstate := Mnone
4076 | 3 ->
4077 if down
4078 then (
4079 if Wsi.withshift mask
4080 then (
4081 annot conf.annotinline x y;
4082 postRedisplay "addannot"
4084 else
4085 let p = (x, y) in
4086 Wsi.setcursor Wsi.CURSOR_CYCLE;
4087 S.mstate := Mzoomrect (p, p)
4089 else (
4090 match !S.mstate with
4091 | Mzoomrect ((x0, y0), _) ->
4092 if abs (x-x0) > 10 && abs (y - y0) > 10
4093 then zoomrect x0 y0 x y
4094 else (
4095 resetmstate ();
4096 postRedisplay "kill accidental zoom rect";
4098 | Msel _
4099 | Mpan _
4100 | Mscrolly | Mscrollx
4101 | Mzoom _
4102 | Mnone -> resetmstate ()
4105 | 1 when vscrollhit x ->
4106 if down
4107 then
4108 let _, position, sh = !S.uioh#scrollph in
4109 if y > truncate position && y < truncate (position +. sh)
4110 then S.mstate := Mscrolly
4111 else scrolly y
4112 else S.mstate := Mnone
4114 | 1 when y > !S.winh - hscrollh () ->
4115 if down
4116 then
4117 let _, position, sw = !S.uioh#scrollpw in
4118 if x > truncate position && x < truncate (position +. sw)
4119 then S.mstate := Mscrollx
4120 else scrollx x
4121 else S.mstate := Mnone
4123 | 1 when !S.bzoom -> if not down then zoomblock x y
4125 | 1 ->
4126 let dest = if down then getunder x y else Unone in
4127 begin match dest with
4128 | Ulinkuri _ -> gotounder dest
4129 | Unone when down ->
4130 Wsi.setcursor Wsi.CURSOR_FLEUR;
4131 S.mstate := Mpan (x, y);
4132 | Uannotation (opaque, slinkindex) -> enterannotmode opaque slinkindex
4133 | Unone | Utext _ ->
4134 if down
4135 then (
4136 if canselect ()
4137 then (
4138 S.mstate := Msel ((x, y), (x, y));
4139 postRedisplay "mouse select";
4142 else (
4143 match !S.mstate with
4144 | Mnone -> ()
4145 | Mzoom _ | Mscrollx | Mscrolly -> S.mstate := Mnone
4146 | Mzoomrect ((x0, y0), _) -> zoomrect x0 y0 x y
4147 | Mpan _ ->
4148 Wsi.setcursor Wsi.CURSOR_INHERIT;
4149 S.mstate := Mnone
4150 | Msel ((x0, y0), (x1, y1)) ->
4151 let rec loop = function
4152 | [] -> ()
4153 | l :: rest ->
4154 let inside =
4155 let a0 = l.pagedispy in
4156 let a1 = a0 + l.pagevh in
4157 let b0 = l.pagedispx in
4158 let b1 = b0 + l.pagevw in
4159 ((y0 >= a0 && y0 <= a1) || (y1 >= a0 && y1 <= a1))
4160 && ((x0 >= b0 && x0 <= b1) || (x1 >= b0 && x1 <= b1))
4162 if inside
4163 then
4164 match getopaque l.pageno with
4165 | exception Not_found -> ()
4166 | opaque ->
4167 let dosel cmd () =
4168 pipef ~closew:false "Msel"
4169 (fun w ->
4170 Ffi.copysel w opaque;
4171 postRedisplay "Msel") cmd
4173 dosel conf.selcmd ();
4174 S.roamf := dosel conf.paxcmd;
4175 else loop rest
4177 loop !S.layout;
4178 resetmstate ();
4181 | _ -> ()
4183 let birdseyemouse button down x y mask
4184 (conf, leftx, _, hooverpageno, anchor) =
4185 match button with
4186 | 1 when down ->
4187 let rec loop = function
4188 | [] -> ()
4189 | l :: rest ->
4190 if y > l.pagedispy && y < l.pagedispy + l.pagevh
4191 && x > l.pagedispx && x < l.pagedispx + l.pagevw
4192 then
4193 leavebirdseye (conf, leftx, l.pageno, hooverpageno, anchor) false
4194 else loop rest
4196 loop !S.layout
4197 | 3 -> ()
4198 | _ -> viewmouse button down x y mask
4200 let uioh = object
4201 method display = ()
4202 method infochanged _ = ()
4204 method key key mask =
4205 begin match !S.mode with
4206 | Textentry textentry -> textentrykeyboard key mask textentry
4207 | Birdseye birdseye -> birdseyekeyboard key mask birdseye
4208 | View -> viewkeyboard key mask
4209 | LinkNav linknav -> linknavkeyboard key mask linknav
4210 end;
4211 !S.uioh
4213 method button button bstate x y mask =
4214 begin match !S.mode with
4215 | LinkNav _ | View -> viewmouse button bstate x y mask
4216 | Birdseye beye -> birdseyemouse button bstate x y mask beye
4217 | Textentry _ -> ()
4218 end;
4219 !S.uioh
4221 method multiclick clicks x y mask =
4222 begin match !S.mode with
4223 | LinkNav _ | View -> viewmulticlick clicks x y mask
4224 | Birdseye _ | Textentry _ -> ()
4225 end;
4226 !S.uioh
4228 method motion x y =
4229 begin match !S.mode with
4230 | Textentry _ -> ()
4231 | View | Birdseye _ | LinkNav _ ->
4232 match !S.mstate with
4233 | Mzoom _ | Mnone -> ()
4234 | Mpan (x0, y0) ->
4235 let dx = x - x0
4236 and dy = y0 - y in
4237 S.mstate := Mpan (x, y);
4238 let x = if canpan () then U.panbound (!S.x + dx) else !S.x in
4239 let y = U.clamp dy in
4240 gotoxy x y
4242 | Msel (a, _) ->
4243 S.mstate := Msel (a, (x, y));
4244 postRedisplay "motion select";
4246 | Mscrolly ->
4247 let y = min !S.winh (max 0 y) in
4248 scrolly y
4250 | Mscrollx ->
4251 let x = min !S.winw (max 0 x) in
4252 scrollx x
4254 | Mzoomrect (p0, _) ->
4255 S.mstate := Mzoomrect (p0, (x, y));
4256 postRedisplay "motion zoomrect";
4257 end;
4258 !S.uioh
4260 method pmotion x y =
4261 begin match !S.mode with
4262 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
4263 let rec loop = function
4264 | [] ->
4265 if hooverpageno != -1
4266 then (
4267 S.mode := Birdseye (conf, leftx, pageno, -1, anchor);
4268 postRedisplay "pmotion birdseye no hoover";
4270 | l :: rest ->
4271 if y > l.pagedispy && y < l.pagedispy + l.pagevh
4272 && x > l.pagedispx && x < l.pagedispx + l.pagevw
4273 then (
4274 S.mode := Birdseye (conf, leftx, pageno, l.pageno, anchor);
4275 postRedisplay "pmotion birdseye hoover";
4277 else loop rest
4279 loop !S.layout
4281 | Textentry _ -> ()
4283 | LinkNav _ | View ->
4284 match !S.mstate with
4285 | Mpan _ | Msel _ | Mzoom _ | Mscrolly | Mscrollx | Mzoomrect _ -> ()
4286 | Mnone ->
4287 updateunder x y;
4288 if canselect ()
4289 then
4290 match conf.pax with
4291 | None -> ()
4292 | Some past ->
4293 let now = now () in
4294 let delta = now -. past in
4295 if delta > 0.01
4296 then paxunder x y
4297 else conf.pax <- Some now
4298 end;
4299 !S.uioh
4301 method scrollph =
4302 let maxy = U.maxy () in
4303 let p, h =
4304 if maxy = 0
4305 then 0.0, float !S.winh
4306 else scrollph !S.y maxy
4308 vscrollw (), p, h
4310 method scrollpw =
4311 let fwinw = float (!S.winw - vscrollw ()) in
4312 let sw =
4313 let sw = fwinw /. float !S.w in
4314 let sw = fwinw *. sw in
4315 max sw (float conf.scrollh)
4317 let position =
4318 let maxx = !S.w + !S.winw in
4319 let x = !S.winw - !S.x in
4320 let percent = float x /. float maxx in
4321 (fwinw -. sw) *. percent
4323 hscrollh (), position, sw
4325 method modehash =
4326 let modename =
4327 match !S.mode with
4328 | LinkNav _ -> "links"
4329 | Textentry _ -> "textentry"
4330 | Birdseye _ -> "birdseye"
4331 | View -> "view"
4333 findkeyhash conf modename
4335 method eformsgs = true
4336 method alwaysscrolly = false
4337 method scroll dx dy =
4338 let x = if canpan () then U.panbound (!S.x + dx) else !S.x in
4339 gotoxy x (U.clamp (2 * dy));
4340 !S.uioh
4341 method zoom z x y =
4342 pivotzoom ~x ~y (conf.zoom *. exp z);
4345 let ract cmds =
4346 let cl = splitatchar cmds ' ' in
4347 let scan s fmt f =
4348 try Scanf.sscanf s fmt f
4349 with exn -> adderrfmt "remote exec" "error processing '%S': %s\n"
4350 cmds @@ exntos exn
4352 let rectx s pageno (r, g, b, a) x0 y0 x1 y1 =
4353 vlog "%s page %d color (%f %f %f %f) x0,y0,x1,y1 = %f %f %f %f"
4354 s pageno r g b a x0 y0 x1 y1;
4355 onpagerect
4356 pageno
4357 (fun w h ->
4358 let _,w1,h1,_ = getpagedim pageno in
4359 let sw = float w1 /. float w
4360 and sh = float h1 /. float h in
4361 let x0s = x0 *. sw
4362 and x1s = x1 *. sw
4363 and y0s = y0 *. sh
4364 and y1s = y1 *. sh in
4365 let rect = (x0s,y0s,x1s,y0s,x1s,y1s,x0s,y1s) in
4366 let color = (r, g, b, a) in
4367 if conf.verbose then debugrect rect;
4368 S.rects := (pageno, color, rect) :: !S.rects;
4369 postRedisplay s;
4372 match cl with
4373 | "reload", "" -> reload ()
4374 | "goto", args ->
4375 scan args "%u %f %f"
4376 (fun pageno x y ->
4377 let cmd, _ = !S.geomcmds in
4378 if emptystr cmd
4379 then gotopagexy pageno x y
4380 else
4381 let f prevf () =
4382 gotopagexy pageno x y;
4383 prevf ()
4385 S.reprf := f !S.reprf
4387 | "goto1", args -> scan args "%u %f" gotopage
4388 | "gotor", args -> scan args "%S" gotoremote
4389 | "rect", args ->
4390 scan args "%u %u %f %f %f %f"
4391 (fun pageno c x0 y0 x1 y1 ->
4392 let color = (0.0, 0.0, 1.0 /. float c, 0.5) in
4393 rectx "rect" pageno color x0 y0 x1 y1;
4395 | "pgoto", args ->
4396 scan args "%u %f %f"
4397 (fun pageno x y ->
4398 let optopaque =
4399 match getopaque pageno with
4400 | exception Not_found -> Opaque.of_string E.s
4401 | opaque -> opaque
4403 pgoto optopaque pageno x y;
4404 let rec fixx = function
4405 | [] -> ()
4406 | l :: rest ->
4407 if l.pageno = pageno
4408 then gotoxy (!S.x - l.pagedispx) !S.y
4409 else fixx rest
4411 let layout =
4412 let mult =
4413 match conf.columns with
4414 | Csingle _ | Csplit _ -> 1
4415 | Cmulti ((n, _, _), _) -> n
4417 layout 0 !S.y (!S.winw * mult) !S.winh
4419 fixx layout
4421 | "activatewin", "" -> Wsi.activatewin ()
4422 | "quit", "" -> raise Quit
4423 | "keys", keys ->
4424 begin try
4425 let l = Config.keys_of_string keys in
4426 List.iter (fun (k, m) -> keyboard k m) l
4427 with exn -> adderrfmt "error processing keys" "`%S': %s\n"
4428 cmds @@ exntos exn
4430 | _ ->
4431 adderrfmt "remote command"
4432 "error processing remote command: %S\n" cmds
4434 let remote =
4435 let scratch = Bytes.create 80 in
4436 let buf = Buffer.create 80 in
4437 fun fd ->
4438 match tempfailureretry (Unix.read fd scratch 0) 80 with
4439 | exception Unix.Unix_error (Unix.EAGAIN, _, _) -> None
4440 | 0 ->
4441 Unix.close fd;
4442 if Buffer.length buf > 0
4443 then (
4444 let s = Buffer.contents buf in
4445 Buffer.clear buf;
4446 ract s;
4448 None
4449 | n ->
4450 let rec eat ppos =
4451 let nlpos =
4452 match Bytes.index_from scratch ppos '\n' with
4453 | exception Not_found -> -1
4454 | pos -> if pos >= n then -1 else pos
4456 if nlpos >= 0
4457 then (
4458 Buffer.add_subbytes buf scratch ppos (nlpos-ppos);
4459 let s = Buffer.contents buf in
4460 Buffer.clear buf;
4461 ract s;
4462 eat (nlpos+1);
4464 else (
4465 Buffer.add_subbytes buf scratch ppos (n-ppos);
4466 Some fd
4468 in eat 0
4470 let remoteopen path =
4471 try Some (Unix.openfile path [Unix.O_NONBLOCK; Unix.O_RDONLY] 0o0)
4472 with exn ->
4473 adderrfmt "remoteopen" "error opening %S: %s" path @@ exntos exn;
4474 None
4476 let () =
4477 vlogf := (fun s -> if conf.verbose then print_endline s else ignore s);
4478 let gc = ref false in
4479 let redirstderr = Unix.isatty Unix.stderr |> not |> ref in
4480 let rcmdpath = ref E.s in
4481 let dcfpath = ref E.s in
4482 let pageno = ref None in
4483 let openlast = ref false in
4484 let doreap = ref false in
4485 let csspath = ref None in
4486 S.selfexec := Sys.executable_name;
4487 let spec =
4488 [("-p", Arg.Set_string S.password, "<password> Set password");
4489 ("-f", Arg.String
4490 (fun s ->
4491 S.fontpath := s;
4492 S.selfexec := !S.selfexec ^ " -f " ^ Filename.quote s;
4493 ), "<path> Set path to the user interface font");
4494 ("-c", Arg.String
4495 (fun s ->
4496 S.selfexec := !S.selfexec ^ " -c " ^ Filename.quote s;
4497 S.confpath := s), "<path> Set path to the configuration file");
4498 ("-last", Arg.Set openlast, " Open last document");
4499 ("-page", Arg.Int (fun pageno1 -> pageno := Some (pageno1-1)),
4500 "<page-number> Jump to page");
4501 ("-dest", Arg.Set_string S.nameddest, "<dest-name> Set named destination");
4502 ("-remote", Arg.Set_string rcmdpath, "<path> Set path to the remote fifo");
4503 ("-gc", Arg.Set gc, " Collect garbage");
4504 ("-v",
4505 Arg.Unit (fun () ->
4506 Printf.printf "%s\nconfiguration file: %s\n" (Help.version ())
4507 Config.defconfpath;
4508 exit 0), " Print version and exit");
4509 ("-css", Arg.String (fun s -> csspath := Some s),
4510 "<path> Set path to the style sheet to use with EPUB/HTML");
4511 ("-origin", Arg.Set_string S.origin, "<origin> <undocumented>");
4512 ("-no-title", Arg.Set S.ignoredoctitlte, " Ignore document title");
4513 ("-dcf", Arg.Set_string dcfpath, "<path> <undocumented>");
4514 ("-flip-stderr-redirection",
4515 Arg.Unit (fun () -> redirstderr := not !redirstderr), " <undocumented>");
4518 Arg.parse (Arg.align spec) (fun s -> S.path := s)
4519 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\nOptions:");
4521 let histmode = emptystr !S.path && not !openlast in
4523 if !gc
4524 then (
4525 Config.gc ();
4526 if histmode then exit 0;
4529 if not (Config.load !openlast)
4530 then dolog "failed to load configuration";
4532 if nonemptystr !dcfpath
4533 then conf.dcf <- !dcfpath;
4535 begin match !pageno with
4536 | Some pageno -> S.anchor := (pageno, 0.0, 0.0)
4537 | None -> ()
4538 end;
4540 fillhelp ();
4541 let mu =
4542 object (self)
4543 val mutable m_clicks = 0
4544 val mutable m_click_x = 0
4545 val mutable m_click_y = 0
4546 val mutable m_lastclicktime = infinity
4548 method private cleanup =
4549 S.roamf := noroamf;
4550 Hashtbl.iter (fun _ opaque -> Ffi.clearmark opaque) S.pagemap
4551 method expose = postRedisplay "expose"
4552 method visible v =
4553 let name =
4554 match v with
4555 | Wsi.Unobscured -> "unobscured"
4556 | Wsi.PartiallyObscured -> "partiallyobscured"
4557 | Wsi.FullyObscured -> "fullyobscured"
4559 vlog "visibility change %s" name
4560 method display = display ()
4561 method map mapped = vlog "mapped %b" mapped
4562 method reshape w h =
4563 self#cleanup;
4564 reshape w h
4565 method mouse b d x y m =
4566 (* http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx *)
4567 m_click_x <- x;
4568 setuioh @@
4569 if d && canselect ()
4570 then (
4571 m_click_y <- y;
4572 if b = 1
4573 then (
4574 let t = now () in
4575 if abs x - m_click_x > 10
4576 || abs y - m_click_y > 10
4577 || abs_float (t -. m_lastclicktime) > 0.3
4578 then m_clicks <- 0;
4579 m_clicks <- m_clicks + 1;
4580 m_lastclicktime <- t;
4581 if m_clicks = 1
4582 then (
4583 self#cleanup;
4584 postRedisplay "cleanup";
4585 !S.uioh#button b d x y m
4587 else !S.uioh#multiclick m_clicks x y m
4589 else (
4590 self#cleanup;
4591 m_clicks <- 0;
4592 m_lastclicktime <- infinity;
4593 !S.uioh#button b d x y m
4596 else !S.uioh#button b d x y m
4597 method motion x y =
4598 S.mpos := (x, y);
4599 !S.uioh#motion x y |> setuioh
4600 method pmotion x y =
4601 S.mpos := (x, y);
4602 !S.uioh#pmotion x y |> setuioh
4603 method key k m =
4604 vlog "k=%#x m=%#x" k m;
4605 let mascm = m land (
4606 Wsi.altmask + Wsi.shiftmask + Wsi.ctrlmask + Wsi.metamask
4607 ) in
4608 let keyboard k m =
4609 let x = !S.x and y = !S.y in
4610 keyboard k m;
4611 if x != !S.x || y != !S.y then self#cleanup
4613 match !S.keystate with
4614 | KSnone ->
4615 let km = k, mascm in
4616 begin
4617 match
4618 let modehash = !S.uioh#modehash in
4619 try Hashtbl.find modehash km
4620 with Not_found ->
4621 try Hashtbl.find (findkeyhash conf "global") km
4622 with Not_found -> KMinsrt (k, m)
4623 with
4624 | KMinsrt (k, m) -> keyboard k m
4625 | KMinsrl l -> List.iter (fun (k, m) -> keyboard k m) l
4626 | KMmulti (l, r) -> S.keystate := KSinto (l, r)
4628 | KSinto ((k', m') :: [], insrt) when k'=k && m' land mascm = m' ->
4629 List.iter (fun (k, m) -> keyboard k m) insrt;
4630 S.keystate := KSnone
4631 | KSinto ((k', m') :: keys, insrt) when k'=k && m' land mascm = m' ->
4632 S.keystate := KSinto (keys, insrt)
4633 | KSinto _ -> S.keystate := KSnone
4634 method enter x y =
4635 S.mpos := (x, y);
4636 !S.uioh#pmotion x y |> setuioh
4637 method leave = S.mpos := (-1, -1)
4638 method winstate wsl = S.winstate := wsl
4639 method quit : 'a. 'a = raise Quit
4640 method scroll dx dy =
4641 !S.uioh#scroll dx dy |> setuioh
4642 method zoom z x y = !S.uioh#zoom z x y
4643 method opendoc path =
4644 S.mode := View;
4645 setuioh uioh;
4646 postRedisplay "opendoc";
4647 opendoc path !S.password
4650 let wsfd, winw, winh = Wsi.init mu conf.cwinw conf.cwinh in
4651 S.wsfd := wsfd;
4653 let cs, ss =
4654 match Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 with
4655 | exception exn ->
4656 dolog "socketpair failed: %s" @@ exntos exn;
4657 exit 1
4658 | (r, w) ->
4659 Unix.set_close_on_exec r;
4660 Unix.set_close_on_exec w;
4661 r, w
4664 begin match !csspath with
4665 | None -> ()
4666 | Some "" -> conf.css <- E.s
4667 | Some path ->
4668 let css = filecontents path in
4669 let l = String.length css in
4670 conf.css <-
4671 if substratis css (l-2) "\r\n"
4672 then String.sub css 0 (l-2)
4673 else (if css.[l-1] = '\n' then String.sub css 0 (l-1) else css)
4674 end;
4675 S.stderr := Ffi.init cs (
4676 conf.angle, conf.fitmodel, (conf.trimmargins, conf.trimfuzz),
4677 conf.texcount, conf.sliceheight, conf.mustoresize,
4678 conf.colorspace, !S.fontpath, !redirstderr
4680 List.iter GlArray.enable [`texture_coord; `vertex];
4681 GlTex.env (`color conf.texturecolor);
4682 S.ss := ss;
4683 reshape ~firsttime:true winw winh;
4684 setuioh uioh;
4685 if histmode
4686 then (Wsi.settitle "previously visited - llpp"; enterhistmode ())
4687 else opendoc !S.path !S.password;
4688 display ();
4689 Wsi.mapwin ();
4690 Wsi.setcursor Wsi.CURSOR_INHERIT;
4691 Sys.set_signal Sys.sighup (Sys.Signal_handle (fun _ -> reload ()));
4693 let rec reap () =
4694 match Unix.waitpid [Unix.WNOHANG] ~-1 with
4695 | exception (Unix.Unix_error (Unix.ECHILD, _, _)) -> ()
4696 | exception exn -> dolog "Unix.waitpid: %s" @@ exntos exn
4697 | 0, _ -> ()
4698 | _pid, _status -> reap ()
4700 Sys.set_signal Sys.sigchld (Sys.Signal_handle (fun _ -> doreap := true));
4702 let optrfd =
4703 ref (if nonemptystr !rcmdpath then remoteopen !rcmdpath else None)
4705 dologf := (adderrfmt "stderr" "%s\n");
4707 let fdl =
4708 let l = [!S.ss; !S.wsfd] in if !redirstderr then !S.stderr :: l else l
4710 let rec loop deadline =
4711 if !doreap
4712 then (
4713 doreap := false;
4714 reap ()
4716 let r =
4717 match !optrfd with
4718 | None -> fdl
4719 | Some fd -> fd :: fdl
4721 if !redisplay
4722 then (
4723 Glutils.redisplay := false;
4724 display ();
4726 let timeout =
4727 let now = now () in
4728 if deadline > now
4729 then (
4730 if deadline = infinity
4731 then ~-.1.0
4732 else max 0.0 (deadline -. now)
4734 else 0.0
4736 let r, _, _ =
4737 try Unix.select r [] [] timeout
4738 with Unix.Unix_error (Unix.EINTR, _, _) -> [], [], []
4740 begin match r with
4741 | [] ->
4742 let newdeadline =
4743 match !S.autoscroll with
4744 | Some step when step != 0 ->
4745 let y = !S.y + step in
4746 let fy = if conf.maxhfit then !S.winh else 0 in
4747 let y =
4748 if y < 0
4749 then !S.maxy - fy
4750 else
4751 if y >= !S.maxy - fy
4752 then 0
4753 else y
4755 gotoxy !S.x y;
4756 deadline +. 0.01
4757 | _ -> infinity
4759 loop newdeadline
4761 | l ->
4762 let rec checkfds = function
4763 | [] -> ()
4764 | fd :: rest when fd = !S.ss ->
4765 let cmd = Ffi.rcmd !S.ss in
4766 act cmd;
4767 checkfds rest
4769 | fd :: rest when fd = !S.wsfd ->
4770 Wsi.readresp fd;
4771 checkfds rest
4773 | fd :: rest when fd = !S.stderr ->
4774 let b = Bytes.create 80 in
4775 begin match Unix.read fd b 0 80 with
4776 | exception Unix.Unix_error (Unix.EINTR, _, _) -> ()
4777 | exception exn -> adderrmsg "Unix.read exn" @@ exntos exn
4778 | 0 -> ()
4779 | n -> adderrmsg "stderr" @@ Bytes.sub_string b 0 n
4780 end;
4781 checkfds rest
4783 | fd :: rest when Some fd = !optrfd ->
4784 begin match remote fd with
4785 | None -> optrfd := remoteopen !rcmdpath;
4786 | opt -> optrfd := opt
4787 end;
4788 checkfds rest
4790 | _ :: rest ->
4791 adderrmsg "mainloop" "select returned unknown descriptor";
4792 checkfds rest
4794 checkfds l;
4795 let newdeadline =
4796 match !S.autoscroll with
4797 | Some step when step != 0 ->
4798 if deadline = infinity
4799 then now () +. 0.01
4800 else deadline
4801 | _ -> infinity
4803 loop newdeadline
4804 end;
4806 match loop infinity with
4807 | exception Quit ->
4808 (match Buffer.length S.errmsgs with
4809 | 0 -> ()
4810 | n ->
4811 match Unix.write Unix.stdout (Buffer.to_bytes S.errmsgs) 0 n with
4812 | exception _ | _ -> ());
4813 Config.save leavebirdseye;
4814 if Ffi.hasunsavedchanges ()
4815 then save ()
4816 | _ -> error "umpossible - infinity reached"