Cower all bases
[llpp.git] / main.ml
blob263ac06cc4ce9155115169859f183032eb0d1ba5
1 open Utils
2 open Config
3 open Uiutils
5 module U = struct
6 let dopen = '\023'
7 let cs = '\024'
8 let freepage = '\025'
9 let freetile = '\026'
10 let search = '\027'
11 let geometry = '\028'
12 let reqlayout = '\029'
13 let page = '\030'
14 let tile = '\031'
15 let trimset = '\032'
16 let settrim = '\033'
17 let sliceh = '\034'
18 let interrupt = '\035'
19 let pgscale h = truncate (float h *. conf.pgscale)
20 let nogeomcmds = function | s, [] -> emptystr s | _ -> false
21 let maxy () = !S.maxy - if conf.maxhfit then !S.winh else 0
22 let scalecolor c = let c = c *. conf.colorscale in (c, c, c)
23 let panbound x = bound x (- !S.w) !S.winw
24 let pagevisible layout n = List.exists (fun l -> l.pageno = n) layout
25 let add_to_y_and_clamp inc = bound (!S.y + inc) 0 @@ maxy ()
26 end
28 let debugrect (x0, y0, x1, y1, x2, y2, x3, y3) =
29 dolog {|rect {
30 x0,y0=(% f, % f)
31 x1,y1=(% f, % f)
32 x2,y2=(% f, % f)
33 x3,y3=(% f, % f)
34 }|} x0 y0 x1 y1 x2 y2 x3 y3
36 let hscrollh () =
37 if ((conf.scrollb land scrollbhv != 0) && (!S.w > !S.winw))
38 || !S.uioh#alwaysscrolly
39 then conf.scrollbw
40 else 0
42 let setfontsize n =
43 fstate.fontsize <- n;
44 fstate.wwidth <- Ffi.measurestr fstate.fontsize "w";
45 fstate.maxrows <- (!S.winh - fstate.fontsize - 1) / (fstate.fontsize + 1)
47 let showtext c s =
48 S.text := Printf.sprintf "%c%s" c s;
49 Glutils.postRedisplay "showtext"
51 let adderrmsg src msg =
52 Buffer.add_string S.errmsgs msg;
53 S.newerrmsgs := true;
54 Glutils.postRedisplay src
56 let settextfmt fmt = Printf.ksprintf (fun s -> S.text := s) fmt
57 let impmsg fmt = Printf.ksprintf (fun s -> showtext '!' s) fmt
58 let adderrfmt src fmt = Printf.ksprintf (fun s -> adderrmsg src s) fmt
60 let launchpath () =
61 if emptystr conf.pathlauncher
62 then adderrmsg "path launcher" "command set"
63 else
64 let n =
65 match !S.layout with
66 | l :: _ -> string_of_int l.pageno
67 | _ -> E.s
69 let cmd = Str.global_replace Re.percents !S.path conf.pathlauncher in
70 let cmd =
71 if nonemptystr n
72 then Str.global_replace Re.percentp n cmd
73 else cmd
75 match spawn cmd [] with
76 | exception exn ->
77 adderrfmt "spawn" "failed to execute `%s': %s" cmd @@ exntos exn
78 | _pid -> ()
80 let getopaque pageno = Hashtbl.find S.pagemap (pageno, !S.gen)
82 let pagetranslatepoint l x y =
83 let dy = y - l.pagedispy in
84 let y = dy + l.pagey in
85 let dx = x - l.pagedispx in
86 let x = dx + l.pagex in
87 (x, y)
89 let onppundermouse g x y d =
90 let rec f = function
91 | [] -> d
92 | l :: rest ->
93 match getopaque l.pageno with
94 | exception Not_found -> f rest
95 | opaque ->
96 let x0 = l.pagedispx in
97 let x1 = x0 + l.pagevw in
98 let y0 = l.pagedispy in
99 let y1 = y0 + l.pagevh in
100 if y >= y0 && y <= y1 && x >= x0 && x <= x1
101 then
102 let px, py = pagetranslatepoint l x y in
103 match g opaque l px py with
104 | Some res -> res
105 | None -> f rest
106 else f rest
108 f !S.layout
110 let getunder x y =
111 let g opaque l px py =
112 if !S.bzoom
113 then (
114 match Ffi.rectofblock opaque px py with
115 | Some [|x0;x1;y0;y1|] ->
116 let rect = (x0, y0, x1, y0, x1, y1, x0, y1) in
117 let color = (0.0, 0.0, 1.0 /. (l.pageno mod 3 |> float), 0.5) in
118 S.rects := [l.pageno, color, rect];
119 Glutils.postRedisplay "getunder";
120 | _ -> ()
122 let under = Ffi.whatsunder opaque px py in
123 if under = Unone then None else Some under
125 onppundermouse g x y Unone
127 let unproject x y =
128 let g opaque l x y =
129 match Ffi.unproject opaque x y with
130 | Some (x, y) -> Some (Some (opaque, l.pageno, x, y))
131 | None -> None
133 onppundermouse g x y None
135 let pipesel opaque cmd =
136 if Ffi.hassel opaque
137 then
138 pipef ~closew:false "pipesel"
139 (fun w ->
140 Ffi.copysel w opaque;
141 Glutils.postRedisplay "pipesel"
142 ) cmd
144 let paxunder x y =
145 let g opaque l px py =
146 if Ffi.markunder opaque px py conf.paxmark
147 then
148 Some (fun () ->
149 match getopaque l.pageno with
150 | exception Not_found -> ()
151 | opaque -> pipesel opaque conf.paxcmd
153 else None
155 Glutils.postRedisplay "paxunder";
156 if conf.paxmark = MarkPage
157 then
158 List.iter (fun l ->
159 match getopaque l.pageno with
160 | exception Not_found -> ()
161 | opaque -> Ffi.clearmark opaque) !S.layout;
162 S.roamf := onppundermouse g x y (fun () -> impmsg "whoopsie daisy")
164 let undertext = function
165 | Unone -> "none"
166 | Ulinkuri s -> s
167 | Utext s -> "font: " ^ s
168 | Utextannot (opaque, slinkindex) ->
169 "text annotation: " ^ Ffi.gettextannot opaque slinkindex
170 | Ufileannot (opaque, slinkindex) ->
171 "file annotation: " ^ Ffi.getfileannot opaque slinkindex
173 let updateunder x y =
174 match getunder x y with
175 | Unone -> Wsi.setcursor Wsi.CURSOR_INHERIT
176 | Ulinkuri uri ->
177 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
178 Wsi.setcursor Wsi.CURSOR_INFO
179 | Utext s ->
180 if conf.underinfo then showtext 'f' ("ont: " ^ s);
181 Wsi.setcursor Wsi.CURSOR_TEXT
182 | Utextannot _ ->
183 if conf.underinfo then showtext 't' "ext annotation";
184 Wsi.setcursor Wsi.CURSOR_INFO
185 | Ufileannot _ ->
186 if conf.underinfo then showtext 'f' "ile annotation";
187 Wsi.setcursor Wsi.CURSOR_INFO
189 let showlinktype under =
190 if conf.underinfo && under != Unone
191 then showtext ' ' @@ undertext under
193 let intentry_with_suffix text key =
194 let text =
195 match [@warning "-fragile-match"] key with
196 | Keys.Ascii ('0'..'9' as c) -> addchar text c
197 | Keys.Ascii ('k' | 'm' | 'g' | 'K' | 'M' | 'G' as c) ->
198 addchar text @@ Char.lowercase_ascii c
199 | _ ->
200 S.text := "invalid key";
201 text
203 TEcont text
205 let wcmd cmd fmt =
206 let b = Buffer.create 16 in
207 Printf.kbprintf
208 (fun b ->
209 Buffer.add_char b cmd;
210 let b = Buffer.to_bytes b in
211 Ffi.wcmd !S.ss b @@ Bytes.length b
212 ) b fmt
214 let wcmd1 cmd opaque =
215 let s = Opaque.to_string opaque in
216 let l = String.length s in
217 let b = Bytes.create (l+1) in
218 Bytes.set b l cmd;
219 Bytes.blit_string s 0 b 0 l;
220 Ffi.wcmd !S.ss b @@ l + 1
222 let layoutN ((columns, coverA, coverB), b) x y sw sh =
223 let rec fold accu n =
224 if n = Array.length b
225 then accu
226 else
227 let pdimno, dx, vy, (_, w, h, xoff) = b.(n) in
228 if (vy - y) > sh
229 && (n = coverA - 1
230 || n = !S.pagecount - coverB
231 || (n - coverA) mod columns = columns - 1)
232 then accu
233 else
234 let accu =
235 if vy + h > y
236 then
237 let pagey = max 0 (y - vy) in
238 let pagedispy = if pagey > 0 then 0 else vy - y in
239 let pagedispx, pagex =
240 let pdx =
241 if n = coverA - 1 || n = !S.pagecount - coverB
242 then x + (sw - w) / 2
243 else dx + xoff + x
245 if pdx < 0
246 then 0, -pdx
247 else pdx, 0
249 let pagevw =
250 let vw = sw - pagedispx in
251 let pw = w - pagex in
252 min vw pw
254 let pagevh = min (h - pagey) (sh - pagedispy) in
255 if pagevw > 0 && pagevh > 0
256 then
257 { pageno = n
258 ; pagecol = 0 ; pagedimno = pdimno ; pagew = w ; pageh = h
259 ; pagex ; pagey ; pagevw ; pagevh ; pagedispx ; pagedispy
260 } :: accu
261 else accu
262 else accu
264 fold accu (n+1)
266 if Array.length b = 0
267 then []
268 else List.rev (fold [] (page_of_y y))
270 let layoutS (columns, b) x y sw sh =
271 let rec fold accu n =
272 if n = Array.length b
273 then accu
274 else
275 let pdimno, px, vy, (_, pagew, pageh, xoff) = b.(n) in
276 if (vy - y) > sh
277 then accu
278 else
279 let accu =
280 if vy + pageh > y
281 then
282 let x = xoff + x in
283 let pagey = max 0 (y - vy) in
284 let pagedispy = if pagey > 0 then 0 else vy - y in
285 let pagedispx, pagex =
286 if px = 0
287 then (
288 if x < 0
289 then 0, -x
290 else x, 0
292 else (
293 let px = px - x in
294 if px < 0
295 then -px, 0
296 else 0, px
299 let pagecolw = pagew/columns in
300 let pagedispx =
301 if pagecolw < sw
302 then pagedispx + ((sw - pagecolw) / 2)
303 else pagedispx
305 let pagevw =
306 let vw = sw - pagedispx in
307 let pw = pagew - pagex in
308 min vw pw
310 let pagevw = min pagevw pagecolw in
311 let pagevh = min (pageh - pagey) (sh - pagedispy) in
312 if pagevw > 0 && pagevh > 0
313 then
314 { pageno = n/columns
315 ; pagedimno = pdimno
316 ; pagecol = n mod columns
317 ; pagew ; pageh ; pagex ; pagey ; pagedispx ; pagedispy
318 ; pagevw ; pagevh
319 } :: accu
320 else accu
321 else accu
323 fold accu (n+1)
325 List.rev (fold [] 0)
327 let layout x y sw sh =
328 if U.nogeomcmds !S.geomcmds
329 then
330 match conf.columns with
331 | Csingle b -> layoutN ((1, 0, 0), b) x y sw sh
332 | Cmulti c -> layoutN c x y sw sh
333 | Csplit s -> layoutS s x y sw sh
334 else []
336 let itertiles l f =
337 let tilex = l.pagex mod conf.tilew in
338 let tiley = l.pagey mod conf.tileh in
340 let col = l.pagex / conf.tilew in
341 let row = l.pagey / conf.tileh in
343 let rec rowloop row y0 dispy h =
344 if h != 0
345 then
346 let dh = conf.tileh - y0 in
347 let dh = min h dh in
348 let rec colloop col x0 dispx w =
349 if w != 0
350 then
351 let dw = conf.tilew - x0 in
352 let dw = min w dw in
353 f col row dispx dispy x0 y0 dw dh;
354 colloop (col+1) 0 (dispx+dw) (w-dw)
356 colloop col tilex l.pagedispx l.pagevw;
357 rowloop (row+1) 0 (dispy+dh) (h-dh)
359 if l.pagevw > 0 && l.pagevh > 0
360 then rowloop row tiley l.pagedispy l.pagevh
362 let gettileopaque l col row =
363 let key = l.pageno, !S.gen, conf.colorspace,
364 conf.angle, l.pagew, l.pageh, col, row in
365 Hashtbl.find_opt S.tilemap key
367 let puttileopaque l col row gen colorspace angle opaque size elapsed =
368 let key = l.pageno, gen, colorspace, angle, l.pagew, l.pageh, col, row in
369 Hashtbl.add S.tilemap key (opaque, size, elapsed)
371 let drawtiles l color =
372 let texe e = if conf.invert then GlTex.env (`mode e) in
373 GlDraw.color color;
374 Ffi.begintiles ();
375 let f col row x y tilex tiley w h =
376 match gettileopaque l col row with
377 | Some (opaque, _, t) ->
378 let params = x, y, w, h, tilex, tiley in
379 texe `blend;
380 Ffi.drawtile params opaque;
381 texe `modulate;
382 if conf.debug
383 then (
384 Ffi.endtiles ();
385 let s = Printf.sprintf "%d[%d,%d] %f sec" l.pageno col row t in
386 let w = Ffi.measurestr fstate.fontsize s in
387 GlDraw.color (0.0, 0.0, 0.0);
388 Glutils.filledrect
389 (float (x-2))
390 (float (y-2))
391 (float (x+2) +. w)
392 (float (y + fstate.fontsize + 2));
393 GlDraw.color color;
394 Glutils.drawstring fstate.fontsize x (y + fstate.fontsize - 1) s;
395 Ffi.begintiles ();
398 | None ->
399 Ffi.endtiles ();
400 let w = let lw = !S.winw - x in min lw w
401 and h = let lh = !S.winh - y in min lh h in
402 texe `blend;
403 let c = if conf.invert then 0.2 else 0.8 in
404 GlDraw.color (c, c, c);
405 Glutils.filledrect (float x) (float y) (float (x+w)) (float (y+h));
406 texe `modulate;
407 if w > 128 && h > fstate.fontsize + 10
408 then (
409 let c = if conf.invert then 1.0 else 0.0 in
410 GlDraw.color (c, c, c);
411 let c, r =
412 if conf.verbose
413 then (col*conf.tilew, row*conf.tileh)
414 else col, row
416 Glutils.drawstringf fstate.fontsize x y
417 "Loading %d [%d,%d]" l.pageno c r;
419 GlDraw.color color;
420 Ffi.begintiles ();
422 itertiles l f;
423 Ffi.endtiles ()
425 let tilevisible1 l x y =
426 let ax0 = l.pagex
427 and ax1 = l.pagex + l.pagevw
428 and ay0 = l.pagey
429 and ay1 = l.pagey + l.pagevh in
431 let bx0 = x
432 and by0 = y in
433 let bx1 = min (bx0 + conf.tilew) l.pagew
434 and by1 = min (by0 + conf.tileh) l.pageh in
436 let rx0 = max ax0 bx0
437 and ry0 = max ay0 by0
438 and rx1 = min ax1 bx1
439 and ry1 = min ay1 by1 in
441 let nonemptyintersection = rx1 > rx0 && ry1 > ry0 in
442 nonemptyintersection
444 let tilevisible layout n x y =
445 let rec findpageinlayout m = function
446 | l :: rest when l.pageno = n ->
447 tilevisible1 l x y || (
448 match conf.columns with
449 | Csplit (c, _) when c > m -> findpageinlayout (m+1) rest
450 | Csplit _ | Csingle _ | Cmulti _ -> false
452 | _ :: rest -> findpageinlayout 0 rest
453 | [] -> false
455 findpageinlayout 0 layout
457 let tileready l x y =
458 tilevisible1 l x y &&
459 gettileopaque l (x/conf.tilew) (y/conf.tileh) != None
461 let tilepage n p layout =
462 let rec loop = function
463 | l :: rest ->
464 if l.pageno = n
465 then
466 let f col row _ _ _ _ _ _ =
467 if !S.currently = Idle
468 then
469 match gettileopaque l col row with
470 | Some _ -> ()
471 | None ->
472 let x = col*conf.tilew
473 and y = row*conf.tileh in
474 let w =
475 let w = l.pagew - x in
476 min w conf.tilew
478 let h =
479 let h = l.pageh - y in
480 min h conf.tileh
482 wcmd U.tile "%s %d %d %d %d" (Opaque.to_string p) x y w h;
483 S.currently :=
484 Tiling (
485 l, p, conf.colorspace, conf.angle,
486 !S.gen, col, row, conf.tilew, conf.tileh
489 itertiles l f;
490 else loop rest
492 | [] -> ()
494 if U.nogeomcmds !S.geomcmds
495 then loop layout
497 let preloadlayout x y sw sh =
498 let y = if y < sh then 0 else y - sh in
499 let x = min 0 (x + sw) in
500 let h = sh*3 in
501 let w = sw*3 in
502 layout x y w h
504 let load pages =
505 let rec loop pages =
506 if !S.currently = Idle
507 then
508 match pages with
509 | l :: rest ->
510 begin match getopaque l.pageno with
511 | exception Not_found ->
512 wcmd U.page "%d %d" l.pageno l.pagedimno;
513 S.currently := Loading (l, !S.gen);
514 | opaque ->
515 tilepage l.pageno opaque pages;
516 loop rest
518 | _ -> ()
520 if U.nogeomcmds !S.geomcmds
521 then loop pages
523 let preload pages =
524 load pages;
525 if conf.preload && !S.currently = Idle
526 then load (preloadlayout !S.x !S.y !S.winw !S.winh)
528 let alltilesrendered layout =
529 let exception E in
530 let rec fold ls =
531 match ls with
532 | [] -> true
533 | l :: rest ->
534 let foo col row _ _ _ _ _ _ =
535 match gettileopaque l col row with
536 | Some _ -> ()
537 | None -> raise E
539 match itertiles l foo with
540 | () -> fold rest
541 | exception E -> false
543 fold layout
545 let gotoxy x y =
546 let y = bound y 0 !S.maxy in
547 let y, layout =
548 let layout = layout x y !S.winw !S.winh in
549 Glutils.postRedisplay "gotoxy ready";
550 y, layout
552 S.x := x;
553 S.y := y;
554 S.layout := layout;
555 begin match !S.mode with
556 | LinkNav ln ->
557 begin match ln with
558 | Ltexact (pageno, linkno) ->
559 let rec loop = function
560 | [] ->
561 S.lnava := Some (pageno, linkno);
562 S.mode := LinkNav (Ltgendir 0)
563 | l :: _ when l.pageno = pageno ->
564 begin match getopaque pageno with
565 | exception Not_found ->
566 S.mode := LinkNav (Ltnotready (pageno, 0))
567 | opaque ->
568 let x0, y0, x1, y1 = Ffi.getlinkrect opaque linkno in
569 if not (x0 >= l.pagex && x1 <= l.pagex + l.pagevw
570 && y0 >= l.pagey && y1 <= l.pagey + l.pagevh)
571 then S.mode := LinkNav (Ltgendir 0)
573 | _ :: rest -> loop rest
575 loop layout
576 | Ltnotready _ | Ltgendir _ -> ()
578 | Birdseye _ | Textentry _ | View -> ()
579 end;
580 begin match !S.mode with
581 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
582 if not (U.pagevisible layout pageno)
583 then (
584 match !S.layout with
585 | [] -> ()
586 | l :: _ ->
587 S.mode := Birdseye (conf, leftx, l.pageno, hooverpageno, anchor)
589 | LinkNav lt ->
590 begin match lt with
591 | Ltnotready (_, dir)
592 | Ltgendir dir ->
593 let linknav =
594 let rec loop = function
595 | [] -> lt
596 | l :: rest ->
597 match getopaque l.pageno with
598 | exception Not_found -> Ltnotready (l.pageno, dir)
599 | opaque ->
600 let link =
601 let ld =
602 if dir = 0
603 then LDfirstvisible (l.pagex, l.pagey, dir)
604 else if dir > 0 then LDfirst else LDlast
606 Ffi.findlink opaque ld
608 match link with
609 | Lnotfound -> loop rest
610 | Lfound n ->
611 showlinktype (Ffi.getlink opaque n);
612 Ltexact (l.pageno, n)
614 loop !S.layout
616 S.mode := LinkNav linknav
617 | Ltexact _ -> ()
619 | Textentry _ | View -> ()
620 end;
621 preload layout;
622 if conf.updatecurs
623 then (
624 let mx, my = !S.mpos in
625 updateunder mx my;
628 let conttiling pageno opaque =
629 tilepage pageno opaque
630 (if conf.preload
631 then preloadlayout !S.x !S.y !S.winw !S.winh
632 else !S.layout)
634 let gotoxy x y =
635 if not conf.verbose then S.text := E.s;
636 gotoxy x y
638 let getanchory (n, top, dtop) =
639 let y, h = getpageyh n in
640 if conf.presentation
641 then
642 let ips = calcips h in
643 y + truncate (top*.float h -. dtop*.float ips) + ips;
644 else y + truncate (top*.float h -. dtop*.float conf.interpagespace)
646 let addnav () = S.nav := { past = getanchor () :: !S.nav.past; future = []; }
648 let gotopage n top =
649 let y, h = getpageyh n in
650 let y = y + (truncate (top *. float h)) in
651 gotoxy !S.x y
653 let gotopage1 n top =
654 let y = getpagey n in
655 let y = y + top in
656 gotoxy !S.x y
658 let invalidate s f =
659 Glutils.redisplay := false;
660 S.layout := [];
661 S.pdims := [];
662 S.rects := [];
663 S.rects1 := [];
664 match !S.geomcmds with
665 | ps, [] when emptystr ps ->
666 f ();
667 S.geomcmds := s, [];
668 | ps, [] -> S.geomcmds := ps, [s, f];
669 | ps, (s', _) :: rest when s' = s -> S.geomcmds := ps, ((s, f) :: rest);
670 | ps, cmds -> S.geomcmds := ps, ((s, f) :: cmds)
672 let flushpages () =
673 Hashtbl.iter (fun _ opaque -> wcmd1 U.freepage opaque) S.pagemap;
674 Hashtbl.clear S.pagemap
676 let flushtiles () =
677 if not (Queue.is_empty S.tilelru)
678 then (
679 Queue.iter (fun (k, p, s) ->
680 wcmd1 U.freetile p;
681 S.memused := !S.memused - s;
682 Hashtbl.remove S.tilemap k;
683 ) S.tilelru;
684 !S.uioh#infochanged Memused;
685 Queue.clear S.tilelru;
687 load !S.layout
689 let stateh h =
690 let h = truncate (float h*.conf.zoom) in
691 let d = conf.interpagespace lsl (if conf.presentation then 1 else 0) in
692 h - d
694 let fillhelp () =
695 S.help :=
696 let sl = keystostrlist conf in
697 let rec loop accu =
698 function | [] -> accu
699 | s :: rest -> loop ((s, 0, None) :: accu) rest
700 in Help.makehelp conf.urilauncher
701 @ (("", 0, None) :: loop [] sl) |> Array.of_list
703 let titlify path =
704 if emptystr path
705 then path
706 else
707 (if emptystr !S.origin then path else !S.origin)
708 |> Filename.basename |> Ffi.mbtoutf8
710 let settitle title =
711 conf.title <- title;
712 if not !S.ignoredoctitlte
713 then Wsi.settitle @@ title ^ " - llpp"
715 let opendoc path mimetype password =
716 S.path := path;
717 S.mimetype := mimetype;
718 S.password := password;
719 S.gen := !S.gen + 1;
720 S.docinfo := [];
721 S.outlines := [||];
723 flushpages ();
724 Ffi.setaalevel conf.aalevel;
725 Ffi.setpapercolor conf.papercolor;
726 Ffi.setdcf conf.dcf;
728 settitle @@ titlify path;
729 wcmd U.dopen "%d %d %d %d %s\000%s\000%s\000%s\000"
730 (btod conf.usedoccss) conf.rlw conf.rlh conf.rlem
731 path mimetype password conf.css;
732 invalidate "reqlayout"
733 (fun () ->
734 wcmd U.reqlayout " %d %d %d %s\000"
735 conf.angle (FMTE.to_int conf.fitmodel)
736 (stateh !S.winh) !S.nameddest
738 fillhelp ()
740 let reload () =
741 S.anchor := getanchor ();
742 S.reload := Some (!S.x, !S.y, now ());
743 opendoc !S.path !S.mimetype !S.password
745 let docolumns columns =
746 match columns with
747 | Csingle _ ->
748 let a = Array.make !S.pagecount (-1, -1, -1, (-1, -1, -1, -1)) in
749 let rec loop pageno pdimno pdim y ph pdims =
750 if pageno != !S.pagecount
751 then
752 let pdimno, ((_, w, h, xoff) as pdim), pdims =
753 match pdims with
754 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
755 pdimno+1, pdim, rest
756 | _ ->
757 pdimno, pdim, pdims
759 let x = max 0 (((!S.winw - w) / 2) - xoff) in
760 let y =
761 y + (if conf.presentation
762 then (if pageno = 0 then calcips h else calcips ph + calcips h)
763 else (if pageno = 0 then 0 else conf.interpagespace))
765 a.(pageno) <- (pdimno, x, y, pdim);
766 loop (pageno+1) pdimno pdim (y + h) h pdims
768 loop 0 ~-1 (-1,-1,-1,-1) 0 0 !S.pdims;
769 conf.columns <- Csingle a;
771 | Cmulti ((columns, coverA, coverB), _) ->
772 let a = Array.make !S.pagecount (-1, -1, -1, (-1, -1, -1, -1)) in
773 let rec loop pageno pdimno pdim x y rowh pdims =
774 let rec fixrow m =
775 if m >= pageno
776 then
777 let (pdimno, x, y, ((_, _, h, _) as pdim)) = a.(m) in
778 if h < rowh
779 then a.(m) <- (pdimno, x, y + (rowh - h) / 2, pdim);
780 fixrow (m+1)
782 if pageno = !S.pagecount
783 then fixrow (((pageno - 1) / columns) * columns)
784 else
785 let pdimno, ((_, w, h, xoff) as pdim), pdims =
786 match pdims with
787 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
788 pdimno+1, pdim, rest
789 | _ -> pdimno, pdim, pdims
791 let x, y, rowh' =
792 if pageno = coverA - 1 || pageno = !S.pagecount - coverB
793 then (
794 let x = (!S.winw - w) / 2 in
795 let ips =
796 if conf.presentation then calcips h else conf.interpagespace in
797 x, y + ips + rowh, h
799 else (
800 if (pageno - coverA) mod columns = 0
801 then (
802 let x = max 0 (!S.winw - !S.w) / 2 in
803 let y =
804 if conf.presentation
805 then
806 let ips = calcips h in
807 y + (if pageno = 0 then 0 else calcips rowh + ips)
808 else y + (if pageno = 0 then 0 else conf.interpagespace)
810 x, y + rowh, h
812 else x, y, max rowh h
815 let y =
816 if pageno > 1 && (pageno - coverA) mod columns = 0
817 then (
818 let y =
819 if pageno = columns && conf.presentation
820 then (
821 let ips = calcips rowh in
822 for i = 0 to pred columns
824 let (pdimno, x, y, pdim) = a.(i) in
825 a.(i) <- (pdimno, x, y+ips, pdim)
826 done;
827 y+ips;
829 else y
831 fixrow (pageno - columns);
834 else y
836 a.(pageno) <- (pdimno, x, y, pdim);
837 let x = x + w + xoff*2 + conf.interpagespace in
838 loop (pageno+1) pdimno pdim x y rowh' pdims
840 loop 0 ~-1 (-1,-1,-1,-1) 0 0 0 !S.pdims;
841 conf.columns <- Cmulti ((columns, coverA, coverB), a);
843 | Csplit (c, _) ->
844 let a = Array.make (!S.pagecount*c) (-1, -1, -1, (-1, -1, -1, -1)) in
845 let rec loop pageno pdimno pdim y pdims =
846 if pageno != !S.pagecount
847 then
848 let pdimno, ((_, w, h, _) as pdim), pdims =
849 match pdims with
850 | ((pageno', _, _, _) as pdim) :: rest when pageno' = pageno ->
851 pdimno+1, pdim, rest
852 | _ -> pdimno, pdim, pdims
854 let cw = w / c in
855 let rec loop1 n x y =
856 if n = c then y else (
857 a.(pageno*c + n) <- (pdimno, x, y, pdim);
858 loop1 (n+1) (x+cw) (y + h + conf.interpagespace)
861 let y = loop1 0 0 y in
862 loop (pageno+1) pdimno pdim y pdims
864 loop 0 ~-1 (-1,-1,-1,-1) 0 !S.pdims;
865 conf.columns <- Csplit (c, a)
867 let represent () =
868 docolumns conf.columns;
869 S.maxy := calcheight ();
870 if !S.reprf == noreprf
871 then (
872 match !S.mode with
873 | Birdseye (_, _, pageno, _, _) ->
874 let y, h = getpageyh pageno in
875 let top = (!S.winh - h) / 2 in
876 gotoxy !S.x (max 0 (y - top))
877 | Textentry _ | View | LinkNav _ ->
878 let y = getanchory !S.anchor in
879 let y = min y (!S.maxy - !S.winh) in
880 gotoxy !S.x y;
882 else (
883 !S.reprf ();
884 S.reprf := noreprf;
887 let reshape ?(firsttime=false) w h =
888 GlDraw.viewport ~x:0 ~y:0 ~w ~h;
889 if not firsttime && U.nogeomcmds !S.geomcmds
890 then S.anchor := getanchor ();
892 S.winw := w;
893 let w = truncate (float w *. conf.zoom) in
894 let w = max w 2 in
895 S.winh := h;
896 setfontsize fstate.fontsize;
897 GlMat.mode `modelview;
898 GlMat.load_identity ();
900 GlMat.mode `projection;
901 GlMat.load_identity ();
902 GlMat.rotate ~x:1.0 ~angle:180.0 ();
903 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
904 GlMat.scale3 (2.0 /. float !S.winw, 2.0 /. float !S.winh, 1.0);
906 let relx =
907 if conf.zoom <= 1.0
908 then 0.0
909 else float !S.x /. float !S.w
911 invalidate "geometry"
912 (fun () ->
913 S.w := w;
914 if not firsttime
915 then S.x := truncate (relx *. float w);
916 let w =
917 match conf.columns with
918 | Csingle _ -> w
919 | Cmulti ((c, _, _), _) -> (w - (c-1)*conf.interpagespace) / c
920 | Csplit (c, _) -> w * c
922 wcmd U.geometry "%d %d %d" w (stateh h) (FMTE.to_int conf.fitmodel)
925 let gctilesnotinlayout layout =
926 let len = Queue.length S.tilelru in
927 let rec loop qpos =
928 if !S.memused > conf.memlimit
929 then (
930 if qpos < len
931 then
932 let (k, p, s) as lruitem = Queue.pop S.tilelru in
933 let n, gen, colorspace, angle, pagew, pageh, col, row = k in
934 let (_, pw, ph, _) = getpagedim n in
935 if gen = !S.gen
936 && colorspace = conf.colorspace
937 && angle = conf.angle
938 && pagew = pw
939 && pageh = ph
940 && (
941 let x = col*conf.tilew and y = row*conf.tileh in
942 tilevisible layout n x y
944 then Queue.push lruitem S.tilelru
945 else (
946 wcmd1 U.freetile p;
947 S.memused := !S.memused - s;
948 !S.uioh#infochanged Memused;
949 Hashtbl.remove S.tilemap k;
951 loop (qpos+1)
954 loop 0
956 let onpagerect pageno f =
957 let b =
958 match conf.columns with
959 | Cmulti (_, b) -> b
960 | Csingle b -> b
961 | Csplit (_, b) -> b
963 if pageno >= 0 && pageno < Array.length b
964 then
965 let (_, _, _, (_, w, h, _)) = b.(pageno) in
966 f w h
968 let gotopagexy1 pageno x y =
969 let _,w1,h1,leftx = getpagedim pageno in
970 let top = y /. (float h1) in
971 let left = x /. (float w1) in
972 let py, w, h = getpageywh pageno in
973 let wh = !S.winh in
974 let x = left *. (float w) in
975 let x = leftx + !S.x + truncate x in
976 let sx =
977 if x < 0 || x >= !S.winw
978 then !S.x - x
979 else !S.x
981 let pdy = truncate (top *. float h) in
982 let y' = py + pdy in
983 let dy = y' - !S.y in
984 let sy =
985 if x != !S.x || not (dy > 0 && dy < wh)
986 then (
987 if conf.presentation
988 then
989 if abs (py - y') > wh
990 then y'
991 else py
992 else y';
994 else !S.y
996 if !S.x != sx || !S.y != sy
997 then gotoxy sx sy
998 else gotoxy !S.x !S.y
1000 let gotopagexy pageno x y =
1001 match !S.mode with
1002 | Birdseye _ -> gotopage pageno 0.0
1003 | Textentry _ | View | LinkNav _ -> gotopagexy1 pageno x y
1005 let getpassword () =
1006 let passcmd = getenvdef "LLPP_ASKPASS" conf.passcmd in
1007 if emptystr passcmd
1008 then (adderrmsg "askpass" "ask password program not set"; E.s)
1009 else getcmdoutput (adderrfmt passcmd "failed to obrain password: %s") passcmd
1011 let pgoto opaque pageno x y =
1012 let pdimno = getpdimno pageno in
1013 let x, y = Ffi.project opaque pageno pdimno x y in
1014 gotopagexy pageno x y
1016 let act cmds =
1017 (* dolog "%S" cmds; *)
1018 let spl = splitatchar cmds ' ' in
1019 let scan s fmt f =
1020 try Scanf.sscanf s fmt f
1021 with exn ->
1022 dolog "error scanning %S: %s" cmds @@ exntos exn;
1023 exit 1
1025 let addoutline outline =
1026 match !S.currently with
1027 | Outlining outlines -> S.currently := Outlining (outline :: outlines)
1028 | Idle -> S.currently := Outlining [outline]
1029 | Loading _ | Tiling _ ->
1030 dolog "Invalid outlining state";
1031 logcurrently !S.currently
1033 match spl with
1034 | "clear", "" ->
1035 S.pdims := [];
1036 !S.uioh#infochanged Pdim;
1038 | "clearrects", "" ->
1039 S.rects := !S.rects1;
1040 Glutils.postRedisplay "clearrects";
1042 | "continue", args ->
1043 let n = scan args "%u" (fun n -> n) in
1044 S.pagecount := n;
1045 begin match !S.currently with
1046 | Outlining l ->
1047 S.currently := Idle;
1048 S.outlines := Array.of_list (List.rev l)
1049 | Idle | Loading _ | Tiling _ -> ()
1050 end;
1052 let cur, cmds = !S.geomcmds in
1053 if emptystr cur then error "empty geomcmd";
1055 begin match List.rev cmds with
1056 | [] ->
1057 S.geomcmds := E.s, [];
1058 represent ();
1059 | (s, f) :: rest ->
1060 f ();
1061 S.geomcmds := s, List.rev rest;
1062 end;
1063 Glutils.postRedisplay "continue";
1065 | "vmsg", args ->
1066 if conf.verbose then showtext ' ' args
1068 | "emsg", args ->
1069 if not !S.redirstderr
1070 then Format.eprintf "%s@." args
1071 else (
1072 Buffer.add_string S.errmsgs args;
1073 Buffer.add_char S.errmsgs '\n';
1074 if not !S.newerrmsgs
1075 then (
1076 S.newerrmsgs := true;
1077 Glutils.postRedisplay "error message";
1081 | "progress", args ->
1082 let progress, text =
1083 scan args "%f %n"
1084 (fun f pos -> f, String.sub args pos (String.length args - pos))
1086 S.text := text;
1087 S.progress := progress;
1088 Glutils.postRedisplay "progress"
1090 | "match", args ->
1091 let pageno, n, x0, y0, x1, y1, x2, y2, x3, y3 =
1092 scan args "%u %d %f %f %f %f %f %f %f %f"
1093 (fun p n x0 y0 x1 y1 x2 y2 x3 y3 ->
1094 (p, n, x0, y0, x1, y1, x2, y2, x3, y3))
1096 if n = 0
1097 then (
1098 let y = (getpagey pageno) + truncate y0 in
1099 let x =
1100 if (!S.x < - truncate x0) || (!S.x > !S.winw - truncate x1)
1101 then !S.winw/2 - truncate (x0 /. 2. +. x1 /. 2.)
1102 else !S.x
1104 addnav ();
1105 gotoxy x y;
1107 let color = (0.0, 0.0, (if n = 0 then 1.0 else 0.5), 0.5) in
1108 S.rects1 :=
1109 (pageno, color, (x0, y0, x1, y1, x2, y2, x3, y3)) :: !S.rects1
1111 | "page", args ->
1112 let pageopaques, t = scan args "%s %f" (fun p t -> p, t) in
1113 let pageopaque = Opaque.of_string pageopaques in
1114 begin match !S.currently with
1115 | Loading (l, gen) ->
1116 vlog "page %d took %f sec" l.pageno t;
1117 Hashtbl.replace S.pagemap (l.pageno, gen) pageopaque;
1118 let preloadedpages =
1119 if conf.preload
1120 then preloadlayout !S.x !S.y !S.winw !S.winh
1121 else !S.layout
1123 let evict () =
1124 let set = List.fold_left (fun s l -> IntSet.add l.pageno s)
1125 IntSet.empty preloadedpages
1127 let evictedpages =
1128 Hashtbl.fold (fun ((pageno, _) as key) opaque accu ->
1129 if not (IntSet.mem pageno set)
1130 then (
1131 wcmd1 U.freepage opaque;
1132 key :: accu
1134 else accu
1135 ) S.pagemap []
1137 List.iter (Hashtbl.remove S.pagemap) evictedpages;
1139 evict ();
1140 S.currently := Idle;
1141 if gen = !S.gen
1142 then (
1143 tilepage l.pageno pageopaque !S.layout;
1144 load !S.layout;
1145 load preloadedpages;
1146 let visible = U.pagevisible !S.layout l.pageno in
1147 if visible
1148 then (
1149 match !S.mode with
1150 | LinkNav (Ltnotready (pageno, dir)) ->
1151 if pageno = l.pageno
1152 then (
1153 let link =
1154 let ld =
1155 if dir = 0
1156 then LDfirstvisible (l.pagex, l.pagey, dir)
1157 else if dir > 0 then LDfirst else LDlast
1159 Ffi.findlink pageopaque ld
1161 match link with
1162 | Lnotfound -> ()
1163 | Lfound n ->
1164 showlinktype (Ffi.getlink pageopaque n);
1165 S.mode := LinkNav (Ltexact (l.pageno, n))
1167 | LinkNav (Ltgendir _)
1168 | LinkNav (Ltexact _)
1169 | View
1170 | Birdseye _
1171 | Textentry _ -> ()
1174 if visible && alltilesrendered !S.layout
1175 then Glutils.postRedisplay "page";
1178 | Idle | Tiling _ | Outlining _ ->
1179 dolog "Inconsistent loading state";
1180 logcurrently !S.currently;
1181 exit 1
1184 | "tile" , args ->
1186 C part is notifying us that it has finished rendering a tile
1187 valid = the tile fits current config (i.e. the settings with which
1188 the tile has been rendered match current ones)
1190 if the tile is not valid free it and issue loading/rendering commands
1191 for the current layout
1193 evict all the tiles that aren't part of preloadlayout
1194 if tile is visible post redisplay
1195 continue tiling
1197 let (x, y, opaques, size, t) =
1198 scan args "%u %u %s %u %f" (fun x y p size t -> (x, y, p, size, t))
1200 let opaque = Opaque.of_string opaques in
1201 begin match !S.currently with
1202 | Tiling (l, pageopaque, cs, angle, gen, col, row, tilew, tileh) ->
1203 vlog "tile %d [%d,%d] took %f sec" l.pageno col row t;
1204 let layout =
1205 if conf.preload && alltilesrendered !S.layout
1206 then preloadlayout !S.x !S.y !S.winw !S.winh
1207 else !S.layout
1209 if tilew != conf.tilew || tileh != conf.tileh
1210 then (
1211 wcmd1 U.freetile opaque;
1212 S.currently := Idle;
1213 load layout;
1215 else (
1216 puttileopaque l col row gen cs angle opaque size t;
1217 S.memused := !S.memused + size;
1218 !S.uioh#infochanged Memused;
1219 gctilesnotinlayout !S.layout;
1220 Queue.push ((l.pageno, gen, cs, angle, l.pagew, l.pageh, col, row),
1221 opaque, size) S.tilelru;
1223 S.currently := Idle;
1224 let visible = tilevisible layout l.pageno x y in
1225 let cont = gen = !S.gen && conf.colorspace = cs
1226 && conf.angle = angle && visible
1229 if cont
1230 then conttiling l.pageno pageopaque;
1231 preload layout;
1232 if cont
1233 then Glutils.postRedisplay "tile nothrottle";
1236 | Idle | Loading _ | Outlining _ ->
1237 dolog "Inconsistent tiling state";
1238 logcurrently !S.currently;
1239 exit 1
1242 | "pdim", args ->
1243 let (n, w, h, _) as pdim =
1244 scan args "%u %d %d %d" (fun n x w h -> n, w, h, x)
1246 let pdim =
1247 match conf.fitmodel with
1248 | FitWidth -> pdim
1249 | FitPage | FitProportional ->
1250 match conf.columns with
1251 | Csplit _ -> (n, w, h, 0)
1252 | Csingle _ | Cmulti _ -> pdim
1254 S.pdims := pdim :: !S.pdims;
1255 !S.uioh#infochanged Pdim
1257 | "o", args ->
1258 let (l, n, t, h, pos) =
1259 scan args "%u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos)
1261 let s = String.sub args pos (String.length args - pos) in
1262 addoutline (s, l, Oanchor (n, float t /. float h, 0.0))
1264 | "ou", args ->
1265 let (l, len, pos) = scan args "%u %u %n" (fun l len pos -> l, len, pos) in
1266 let s = String.sub args pos len in
1267 let pos2 = pos + len + 1 in
1268 let uri = String.sub args pos2 (String.length args - pos2) in
1269 addoutline (s, l, Ouri uri)
1271 | "on", args ->
1272 let (l, pos) = scan args "%u %n" (fun l pos -> l, pos) in
1273 let s = String.sub args pos (String.length args - pos) in
1274 addoutline (s, l, Onone)
1276 | "a", args ->
1277 let (n, l, t) = scan args "%u %d %d" (fun n l t -> n, l, t) in
1278 S.reprf := (fun () -> gotopagexy n (float l) (float t))
1280 | "info", args ->
1281 let s =
1282 match splitatchar args '\t' with
1283 | "Title", "" ->
1284 settitle @@ Filename.basename !S.path;
1286 | "Title", v ->
1287 settitle v;
1288 args
1289 | _, "" -> E.s
1290 | c, v ->
1291 if let len = String.length c in
1292 len > 6 && ((String.sub c (len-4) 4) = "date")
1293 then (
1294 if String.length v >= 7 && v.[0] = 'D' && v.[1] = ':'
1295 then
1296 let b = Buffer.create 10 in
1297 Printf.bprintf b "%s\t" c;
1298 let sub p l c =
1300 Buffer.add_substring b v p l;
1301 Buffer.add_char b c;
1302 with exn -> Buffer.add_string b @@ exntos exn
1304 sub 2 4 '/';
1305 sub 6 2 '/';
1306 sub 8 2 ' ';
1307 sub 10 2 ':';
1308 sub 12 2 ':';
1309 sub 14 2 ' ';
1310 Printf.bprintf b "[%s]" v;
1311 Buffer.contents b
1312 else args
1314 else args
1316 if nonemptystr s then S.docinfo := (1, s) :: !S.docinfo
1318 | "infoend", "" ->
1319 S.docinfo := List.rev !S.docinfo;
1320 !S.uioh#infochanged Docinfo
1322 | "pass", args ->
1323 if args = "fail"
1324 then adderrmsg "pass" "Wrong password";
1325 let password = getpassword () in
1326 if emptystr password
1327 then error "document is password protected"
1328 else opendoc !S.path !S.mimetype password
1330 | _ -> error "unknown cmd `%S'" cmds
1332 let onhist cb =
1333 let rc = cb.rc in
1334 let action = function
1335 | HCprev -> cbget cb ~-1
1336 | HCnext -> cbget cb 1
1337 | HCfirst -> cbget cb ~-(cb.rc)
1338 | HClast -> cbget cb (cb.len - 1 - cb.rc)
1339 and cancel () = cb.rc <- rc
1340 in (action, cancel)
1342 let search pattern forward =
1343 match conf.columns with
1344 | Csplit _ ->
1345 impmsg "searching while in split columns mode is not implemented"
1346 | Csingle _ | Cmulti _ ->
1347 if nonemptystr pattern
1348 then
1349 let pn, py =
1350 match !S.layout with
1351 | [] -> 0, 0
1352 | l :: _ -> l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
1354 S.rects1 := [];
1355 wcmd U.search "%d %d %d %d,%s\000"
1356 (btod conf.icase) pn py (btod forward) pattern
1358 let intentry text key =
1359 let text =
1360 if emptystr text && key = Keys.Ascii '-'
1361 then addchar text '-'
1362 else
1363 match [@warning "-fragile-match"] key with
1364 | Keys.Ascii ('0'..'9' as c) -> addchar text c
1365 | _ ->
1366 S.text := "invalid key";
1367 text
1369 TEcont text
1371 let linknact f s =
1372 if nonemptystr s
1373 then
1374 let rec loop off = function
1375 | [] -> ()
1376 | l :: rest ->
1377 match getopaque l.pageno with
1378 | exception Not_found -> loop off rest
1379 | opaque ->
1380 let n = Ffi.getlinkn opaque conf.hcs s off in
1381 if n <= 0
1382 then loop n rest
1383 else Ffi.getlink opaque (n-1) |> f
1385 loop 0 !S.layout
1387 let linknentry text = function [@warning "-fragile-match"]
1388 | Keys.Ascii c ->
1389 let text = addchar text c in
1390 linknact (fun under -> S.text := undertext under) text;
1391 TEcont text
1392 | key ->
1393 settextfmt "invalid key %s" @@ Keys.to_string key;
1394 TEcont text
1396 let textentry text key = match [@warning "-fragile-match"] key with
1397 | Keys.Ascii c -> TEcont (addchar text c)
1398 | Keys.Code c -> TEcont (text ^ Ffi.toutf8 c)
1399 | _ -> TEcont text
1401 let reqlayout angle fitmodel =
1402 if U.nogeomcmds !S.geomcmds
1403 then S.anchor := getanchor ();
1404 conf.angle <- angle mod 360;
1405 if conf.angle != 0
1406 then (
1407 match !S.mode with
1408 | LinkNav _ -> S.mode := View
1409 | Birdseye _ | Textentry _ | View -> ()
1411 conf.fitmodel <- fitmodel;
1412 invalidate "reqlayout"
1413 (fun () -> wcmd U.reqlayout "%d %d %d"
1414 conf.angle (FMTE.to_int conf.fitmodel) (stateh !S.winh))
1416 let settrim trimmargins trimfuzz =
1417 if U.nogeomcmds !S.geomcmds
1418 then S.anchor := getanchor ();
1419 conf.trimmargins <- trimmargins;
1420 conf.trimfuzz <- trimfuzz;
1421 let x0, y0, x1, y1 = trimfuzz in
1422 invalidate "settrim"
1423 (fun () -> wcmd U.settrim "%d %d %d %d %d"
1424 (btod conf.trimmargins) x0 y0 x1 y1);
1425 flushpages ()
1427 let setzoom zoom =
1428 let zoom = max 0.0001 zoom in
1429 if zoom <> conf.zoom
1430 then (
1431 S.prevzoom := (conf.zoom, !S.x);
1432 conf.zoom <- zoom;
1433 reshape !S.winw !S.winh;
1434 settextfmt "zoom is now %-5.2f" (zoom *. 100.0);
1437 let pivotzoom ?(vw=min !S.w !S.winw)
1438 ?(vh=min (!S.maxy - !S.y) !S.winh)
1439 ?(x=vw/2) ?(y=vh/2) zoom =
1440 let w = float !S.w /. zoom in
1441 let hw = w /. 2.0 in
1442 let ratio = float vh /. float vw in
1443 let hh = hw *. ratio in
1444 let x0 = float x -. hw +. !S.xf and y0 = float y -. hh +. !S.yf in
1445 let xf, xr = modf x0 and yf, yr = modf y0 in
1446 S.xf := xf;
1447 S.yf := yf;
1448 gotoxy (!S.x - truncate xr) (!S.y + truncate yr);
1449 setzoom zoom
1451 let pivotzoom ?vw ?vh ?x ?y zoom =
1452 if U.nogeomcmds !S.geomcmds
1453 then
1454 if zoom > 1.0
1455 then pivotzoom ?vw ?vh ?x ?y zoom
1456 else setzoom zoom
1458 let setcolumns mode columns coverA coverB =
1459 S.prevcolumns := Some (conf.columns, conf.zoom);
1460 if columns < 0
1461 then (
1462 if isbirdseye mode
1463 then impmsg "split mode doesn't work in bird's eye"
1464 else (
1465 conf.columns <- Csplit (-columns, E.a);
1466 S.x := 0;
1467 conf.zoom <- 1.0;
1470 else (
1471 if columns < 2
1472 then (
1473 conf.columns <- Csingle E.a;
1474 S.x := 0;
1475 setzoom 1.0;
1477 else (
1478 conf.columns <- Cmulti ((columns, coverA, coverB), E.a);
1479 conf.zoom <- 1.0;
1482 reshape !S.winw !S.winh
1484 let resetmstate () =
1485 S.mstate := Mnone;
1486 Wsi.setcursor Wsi.CURSOR_INHERIT
1488 let enterbirdseye () =
1489 let zoom = float conf.thumbw /. float !S.winw in
1490 let birdseyepageno =
1491 let cy = !S.winh / 2 in
1492 let fold = function
1493 | [] -> 0
1494 | l :: rest ->
1495 let rec fold best = function
1496 | [] -> best.pageno
1497 | l :: rest ->
1498 let d = cy - (l.pagedispy + l.pagevh/2)
1499 and dbest = cy - (best.pagedispy + best.pagevh/2) in
1500 if abs d < abs dbest
1501 then fold l rest
1502 else best.pageno
1503 in fold l rest
1505 fold !S.layout
1507 S.mode :=
1508 Birdseye (
1509 { conf with zoom = conf.zoom },
1510 !S.x, birdseyepageno, -1, getanchor ()
1512 resetmstate ();
1513 conf.zoom <- zoom;
1514 conf.presentation <- false;
1515 conf.interpagespace <- 10;
1516 conf.hlinks <- false;
1517 conf.fitmodel <- FitPage;
1518 S.x := 0;
1519 conf.columns <- (
1520 match conf.beyecolumns with
1521 | Some c ->
1522 conf.zoom <- 1.0;
1523 Cmulti ((c, 0, 0), E.a)
1524 | None -> Csingle E.a
1526 if conf.verbose
1527 then settextfmt "birds eye on (zoom %3.1f%%)" (100.0*.zoom);
1528 reshape !S.winw !S.winh
1530 let leavebirdseye (c, leftx, pageno, _, anchor) goback =
1531 S.mode := View;
1532 conf.zoom <- c.zoom;
1533 conf.presentation <- c.presentation;
1534 conf.interpagespace <- c.interpagespace;
1535 conf.hlinks <- c.hlinks;
1536 conf.fitmodel <- c.fitmodel;
1537 conf.beyecolumns <- (
1538 match conf.columns with
1539 | Cmulti ((c, _, _), _) -> Some c
1540 | Csingle _ -> None
1541 | Csplit _ -> error "leaving bird's eye split mode"
1543 conf.columns <- (
1544 match c.columns with
1545 | Cmulti (c, _) -> Cmulti (c, E.a)
1546 | Csingle _ -> Csingle E.a
1547 | Csplit (c, _) -> Csplit (c, E.a)
1549 if conf.verbose
1550 then settextfmt "bird's eye off (zoom %3.1f%%)" (100.0*.conf.zoom);
1551 reshape !S.winw !S.winh;
1552 S.anchor := if goback then anchor else (pageno, 0.0, 1.0);
1553 S.x := leftx
1555 let togglebirdseye () =
1556 match !S.mode with
1557 | Birdseye vals -> leavebirdseye vals true
1558 | View -> enterbirdseye ()
1559 | Textentry _ | LinkNav _ -> ()
1561 let upbirdseye incr (conf, leftx, pageno, hooverpageno, anchor) =
1562 let pageno = max 0 (pageno - incr) in
1563 let rec loop = function
1564 | [] -> gotopage1 pageno 0
1565 | l :: _ when l.pageno = pageno ->
1566 if l.pagedispy >= 0 && l.pagey = 0
1567 then Glutils.postRedisplay "upbirdseye"
1568 else gotopage1 pageno 0
1569 | _ :: rest -> loop rest
1571 loop !S.layout;
1572 S.text := E.s;
1573 S.mode := Birdseye (conf, leftx, pageno, hooverpageno, anchor)
1575 let downbirdseye incr (conf, leftx, pageno, hooverpageno, anchor) =
1576 let pageno = min (!S.pagecount - 1) (pageno + incr) in
1577 S.mode := Birdseye (conf, leftx, pageno, hooverpageno, anchor);
1578 let rec loop = function
1579 | [] ->
1580 let y, h = getpageyh pageno in
1581 let dy = (y - !S.y) - (!S.winh - h - conf.interpagespace) in
1582 gotoxy !S.x (U.add_to_y_and_clamp dy)
1583 | l :: _ when l.pageno = pageno ->
1584 if l.pagevh != l.pageh
1585 then
1586 let inc = l.pageh - l.pagevh + conf.interpagespace in
1587 gotoxy !S.x (U.add_to_y_and_clamp inc)
1588 else Glutils.postRedisplay "downbirdseye"
1589 | _ :: rest -> loop rest
1591 loop !S.layout;
1592 S.text := E.s
1594 let optentry mode _ key =
1595 match [@warning "-fragile-match"] key with
1596 | Keys.Ascii 'C' ->
1597 let ondone s =
1599 let n, a, b = multicolumns_of_string s in
1600 setcolumns mode n a b;
1601 with exn -> settextfmt "bad columns `%s': %s" s @@ exntos exn
1603 TEswitch ("columns: ", E.s, None, textentry, ondone, true)
1605 | Keys.Ascii 'Z' ->
1606 let ondone s =
1608 let zoom = float (int_of_string s) /. 100.0 in
1609 pivotzoom zoom
1610 with exn -> settextfmt "bad integer `%s': %s" s @@ exntos exn
1612 TEswitch ("zoom: ", E.s, None, intentry, ondone, true)
1614 | Keys.Ascii 'i' ->
1615 conf.icase <- not conf.icase;
1616 TEdone ("case insensitive search " ^ (onoffs conf.icase))
1618 | Keys.Ascii 'v' ->
1619 conf.verbose <- not conf.verbose;
1620 TEdone ("verbose " ^ (onoffs conf.verbose))
1622 | Keys.Ascii 'd' ->
1623 conf.debug <- not conf.debug;
1624 TEdone ("debug " ^ (onoffs conf.debug))
1626 | Keys.Ascii 'f' ->
1627 conf.underinfo <- not conf.underinfo;
1628 TEdone ("underinfo " ^ onoffs conf.underinfo)
1630 | Keys.Ascii 'T' ->
1631 settrim (not conf.trimmargins) conf.trimfuzz;
1632 TEdone ("trim margins " ^ onoffs conf.trimmargins)
1634 | Keys.Ascii 'I' ->
1635 conf.invert <- not conf.invert;
1636 TEdone ("invert colors " ^ onoffs conf.invert)
1638 | Keys.Ascii 'x' ->
1639 let ondone s =
1640 cbput !S.hists.sel s;
1641 conf.selcmd <- s;
1643 TEswitch ("selection command: ", E.s, Some (onhist !S.hists.sel),
1644 textentry, ondone, true)
1646 | Keys.Ascii 'M' ->
1647 if conf.pax == None
1648 then conf.pax <- Some 0.0
1649 else conf.pax <- None;
1650 TEdone ("PAX " ^ onoffs (conf.pax != None))
1652 | (Keys.Ascii c) ->
1653 settextfmt "bad option %d `%c'" (Char.code c) c;
1654 TEstop
1656 | _ -> TEcont !S.text
1658 class outlinelistview ~zebra ~source =
1659 let settext autonarrow s =
1660 S.text :=
1661 if autonarrow
1662 then
1663 let ss = source#statestr in
1664 if emptystr ss then "[" ^ s ^ "]" else "{" ^ ss ^ "} [" ^ s ^ "]"
1665 else s
1667 object (self)
1668 inherit listview
1669 ~zebra
1670 ~helpmode:false
1671 ~source:(source :> lvsource)
1672 ~trusted:false
1673 ~modehash:(findkeyhash conf "outline")
1674 as super
1676 val m_autonarrow = false
1678 method! key key mask =
1679 let maxrows =
1680 if emptystr !S.text
1681 then fstate.maxrows
1682 else fstate.maxrows - 2
1684 let calcfirst first active =
1685 if active > first
1686 then
1687 let rows = active - first in
1688 if rows > maxrows then active - maxrows else first
1689 else active
1691 let navigate incr =
1692 let active = m_active + incr in
1693 let active = bound active 0 (source#getitemcount - 1) in
1694 let first = calcfirst m_first active in
1695 Glutils.postRedisplay "outline navigate";
1696 coe {< m_active = active; m_first = first >}
1698 let navscroll first =
1699 let active =
1700 let dist = m_active - first in
1701 if dist < 0
1702 then first
1703 else (
1704 if dist < maxrows
1705 then m_active
1706 else first + maxrows
1709 Glutils.postRedisplay "outline navscroll";
1710 coe {< m_first = first; m_active = active >}
1712 let ctrl = Wsi.withctrl mask in
1713 let open Keys in
1714 match Wsi.ks2kt key with
1715 | Ascii 'a' when ctrl ->
1716 let text =
1717 if m_autonarrow
1718 then (
1719 source#denarrow;
1722 else (
1723 let pattern = source#renarrow in
1724 if nonemptystr m_qsearch
1725 then (source#narrow m_qsearch; m_qsearch)
1726 else pattern
1729 settext (not m_autonarrow) text;
1730 Glutils.postRedisplay "toggle auto narrowing";
1731 coe {< m_first = 0; m_active = 0; m_autonarrow = not m_autonarrow >}
1732 | Ascii '/' when emptystr m_qsearch && not m_autonarrow ->
1733 settext true E.s;
1734 Glutils.postRedisplay "toggle auto narrowing";
1735 coe {< m_first = 0; m_active = 0; m_autonarrow = true >}
1736 | Ascii 'n' when ctrl ->
1737 source#narrow m_qsearch;
1738 if not m_autonarrow
1739 then source#add_narrow_pattern m_qsearch;
1740 Glutils.postRedisplay "outline ctrl-n";
1741 coe {< m_first = 0; m_active = 0 >}
1742 | Ascii 'S' when ctrl ->
1743 let active = source#calcactive (getanchor ()) in
1744 let first = firstof m_first active in
1745 Glutils.postRedisplay "outline ctrl-s";
1746 coe {< m_first = first; m_active = active >}
1747 | Ascii 'u' when ctrl ->
1748 Glutils.postRedisplay "outline ctrl-u";
1749 if m_autonarrow && nonemptystr m_qsearch
1750 then (
1751 ignore (source#renarrow);
1752 settext m_autonarrow E.s;
1753 coe {< m_first = 0; m_active = 0; m_qsearch = E.s >}
1755 else (
1756 source#del_narrow_pattern;
1757 let pattern = source#renarrow in
1758 let text =
1759 if emptystr pattern then E.s else "Narrowed to " ^ pattern
1761 settext m_autonarrow text;
1762 coe {< m_first = 0; m_active = 0; m_qsearch = E.s >}
1764 | Ascii 'l' when ctrl ->
1765 let first = max 0 (m_active - (fstate.maxrows / 2)) in
1766 Glutils.postRedisplay "outline ctrl-l";
1767 coe {< m_first = first >}
1769 | Ascii '\t' when m_autonarrow ->
1770 if nonemptystr m_qsearch
1771 then (
1772 Glutils.postRedisplay "outline list view tab";
1773 source#add_narrow_pattern m_qsearch;
1774 settext true E.s;
1775 coe {< m_qsearch = E.s >}
1777 else coe self
1778 | Escape when m_autonarrow ->
1779 if nonemptystr m_qsearch
1780 then source#add_narrow_pattern m_qsearch;
1781 super#key key mask
1782 | Enter when m_autonarrow ->
1783 if nonemptystr m_qsearch
1784 then source#add_narrow_pattern m_qsearch;
1785 super#key key mask
1786 | (Ascii _ | Code _) when m_autonarrow ->
1787 let pattern = m_qsearch ^ Ffi.toutf8 key in
1788 Glutils.postRedisplay "outlinelistview autonarrow add";
1789 source#narrow pattern;
1790 settext true pattern;
1791 coe {< m_first = 0; m_active = 0; m_qsearch = pattern >}
1792 | Backspace when m_autonarrow ->
1793 if emptystr m_qsearch
1794 then coe self
1795 else
1796 let pattern = withoutlastutf8 m_qsearch in
1797 Glutils.postRedisplay "outlinelistview autonarrow backspace";
1798 ignore (source#renarrow);
1799 source#narrow pattern;
1800 settext true pattern;
1801 coe {< m_first = 0; m_active = 0; m_qsearch = pattern >}
1802 | Up when ctrl -> navscroll (max 0 (m_first-1))
1803 | Down when ctrl -> navscroll (min (source#getitemcount-1) (m_first+1))
1804 | Up -> navigate ~-1
1805 | Down -> navigate 1
1806 | Prior -> navigate ~-(fstate.maxrows)
1807 | Next -> navigate fstate.maxrows
1808 | Right ->
1809 (if ctrl
1810 then (
1811 Glutils.postRedisplay "outline ctrl right";
1812 {< m_pan = m_pan + 1 >}
1814 else (
1815 if Wsi.withshift mask
1816 then self#nextcurlevel 1
1817 else self#updownlevel 1
1818 )) |> coe
1819 | Left ->
1820 (if ctrl
1821 then (
1822 Glutils.postRedisplay "outline ctrl left";
1823 {< m_pan = m_pan - 1 >}
1825 else (
1826 if Wsi.withshift mask
1827 then self#nextcurlevel ~-1
1828 else self#updownlevel ~-1
1829 )) |> coe
1830 | Home ->
1831 Glutils.postRedisplay "outline home";
1832 coe {< m_first = 0; m_active = 0 >}
1833 | End ->
1834 let active = source#getitemcount - 1 in
1835 let first = max 0 (active - fstate.maxrows) in
1836 Glutils.postRedisplay "outline end";
1837 coe {< m_active = active; m_first = first >}
1838 | Delete|Escape|Insert|Enter|Ascii _|Code _|Ctrl _|Backspace|Fn _ ->
1839 super#key key mask
1842 let genhistoutlines () =
1843 Config.gethist ()
1844 |> List.sort (fun (_, c1, _, _, _, _) (_, c2, _, _, _, _) ->
1845 compare c2.lastvisit c1.lastvisit)
1846 |> List.map (fun ((path, c, _, _, _, origin) as hist) ->
1847 let path = if nonemptystr origin then origin else path in
1848 let base = Ffi.mbtoutf8 @@ Filename.basename path in
1849 (base ^ "\000" ^ c.title, 1, Ohistory hist)
1852 let gotohist (path, c, bookmarks, x, anchor, origin) =
1853 Config.save leavebirdseye;
1854 setconf conf c;
1855 let x0, y0, x1, y1 = conf.trimfuzz in
1856 wcmd U.trimset "%d %d %d %d %d" (btod conf.trimmargins) x0 y0 x1 y1;
1857 Wsi.reshape c.cwinw c.cwinh;
1858 opendoc path !S.mimetype origin;
1859 conf.zoom <- nan;
1860 setzoom c.zoom;
1861 S.anchor := anchor;
1862 S.bookmarks := bookmarks;
1863 S.origin := origin;
1864 S.x := x
1866 let describe_layout layout =
1867 let d =
1868 match layout with
1869 | [] -> "Page 0"
1870 | l :: [] -> Printf.sprintf "Page %d" (l.pageno+1)
1871 | l :: rest ->
1872 let rangestr a b =
1873 if a.pageno = b.pageno then Printf.sprintf "%d" (a.pageno+1)
1874 else Printf.sprintf "%d%s%d" (a.pageno+1)
1875 (if a.pageno+1 = b.pageno then ", " else Utf8syms.ellipsis)
1876 (b.pageno+1)
1878 let rec fold s la lb = function
1879 | [] -> Printf.sprintf "%s %s" s (rangestr la lb)
1880 | l :: rest when l.pageno = succ lb.pageno -> fold s la l rest
1881 | l :: rest -> fold (s ^ " " ^ rangestr la lb ^ ",") l l rest
1883 fold "Pages" l l rest
1885 let percent =
1886 let maxy = U.maxy () in
1887 if maxy <= 0
1888 then 100.
1889 else 100. *. (float !S.y /. float maxy)
1891 Printf.sprintf "%s of %d [%.2f%%]" d !S.pagecount percent
1893 let setpresentationmode v =
1894 let n = page_of_y !S.y in
1895 S.anchor := (n, 0.0, 1.0);
1896 conf.presentation <- v;
1897 if conf.fitmodel = FitPage
1898 then reqlayout conf.angle conf.fitmodel;
1899 represent ()
1901 let infomenu =
1902 let modehash = lazy (findkeyhash conf "info") in (fun source ->
1903 S.text := E.s;
1904 new listview ~zebra:false ~helpmode:false ~source
1905 ~trusted:true ~modehash:(Lazy.force_val modehash) |> coe)
1907 let enterinfomode =
1908 let btos b = if b then Utf8syms.radical else E.s in
1909 let showextended = ref false in
1910 let showcolors = ref false in
1911 let showcommands = ref false in
1912 let showrefl = ref false in
1913 let leave mode _ = S.mode := mode in
1914 let src = object
1915 val mutable m_l = []
1916 val mutable m_a = E.a
1917 val mutable m_prev_uioh = nouioh
1918 val mutable m_prev_mode = View
1920 inherit lvsourcebase
1922 method reset prev_mode prev_uioh =
1923 m_a <- Array.of_list (List.rev m_l);
1924 m_l <- [];
1925 m_prev_mode <- prev_mode;
1926 m_prev_uioh <- prev_uioh;
1928 method int name get set =
1929 m_l <-
1930 (name, `int get, 1,
1931 Some (fun u ->
1932 let ondone s =
1933 try set (int_of_string s)
1934 with exn -> settextfmt "bad integer `%s': %s" s @@ exntos exn
1936 S.text := E.s;
1937 let te = (name ^ ": ", E.s, None, intentry, ondone, true) in
1938 S.mode := Textentry (te, leave m_prev_mode);
1940 )) :: m_l
1942 method int_with_suffix name get set =
1943 m_l <-
1944 (name, `intws get, 1,
1945 Some (fun u ->
1946 let ondone s =
1947 try set (int_of_string_with_suffix s)
1948 with exn -> settextfmt "bad integer `%s': %s" s @@ exntos exn
1950 S.text := E.s;
1951 let te = (name ^ ": ", E.s, None, intentry_with_suffix,
1952 ondone, true) in
1953 S.mode := Textentry (te, leave m_prev_mode);
1955 )) :: m_l
1957 method bool ?(offset=1) ?(btos=btos) name get set =
1958 m_l <- (name, `bool (btos, get), offset,
1959 Some (fun u -> set (not (get ())); u)) :: m_l
1961 method color name get set =
1962 m_l <-
1963 (name, `color get, 1,
1964 Some (fun u ->
1965 let invalid = (nan, nan, nan) in
1966 let ondone s =
1967 let c =
1968 try color_of_string s
1969 with exn -> settextfmt "bad color `%s': %s" s @@ exntos exn;
1970 invalid
1972 if c <> invalid
1973 then set c;
1975 let te = (name ^ ": ", E.s, None, textentry, ondone, true) in
1976 S.text := color_to_string (get ());
1977 S.mode := Textentry (te, leave m_prev_mode);
1979 )) :: m_l
1981 method string name get set =
1982 m_l <-
1983 (name, `string get, 1,
1984 Some (fun u ->
1985 let ondone s = set s in
1986 let te = (String.trim name ^ ": ", E.s, None,
1987 textentry, ondone, true) in
1988 S.mode := Textentry (te, leave m_prev_mode);
1990 )) :: m_l
1992 method colorspace name get set =
1993 m_l <-
1994 (name, `string get, 1,
1995 Some (fun _ ->
1996 let source = object
1997 inherit lvsourcebase
1999 initializer
2000 m_active <- CSTE.to_int conf.colorspace;
2001 m_first <- 0;
2003 method getitemcount =
2004 Array.length CSTE.names
2005 method getitem n =
2006 (CSTE.names.(n), 0)
2007 method exit ~uioh ~cancel ~active ~first ~pan =
2008 ignore (uioh, first, pan);
2009 if not cancel then set active;
2010 None
2011 method hasaction _ = true
2014 infomenu source
2015 )) :: m_l
2017 method paxmark name get set =
2018 m_l <-
2019 (name, `string get, 1,
2020 Some (fun _ ->
2021 let source = object
2022 inherit lvsourcebase
2024 initializer
2025 m_active <- MTE.to_int conf.paxmark;
2026 m_first <- 0;
2028 method getitemcount = Array.length MTE.names
2029 method getitem n = (MTE.names.(n), 0)
2030 method exit ~uioh ~cancel ~active ~first ~pan =
2031 ignore (uioh, first, pan);
2032 if not cancel then set active;
2033 None
2034 method hasaction _ = true
2037 infomenu source
2038 )) :: m_l
2040 method fitmodel name get set =
2041 m_l <-
2042 (name, `string get, 1,
2043 Some (fun _ ->
2044 let source = object
2045 inherit lvsourcebase
2047 initializer
2048 m_active <- FMTE.to_int conf.fitmodel;
2049 m_first <- 0;
2051 method getitemcount = Array.length FMTE.names
2052 method getitem n = (FMTE.names.(n), 0)
2053 method exit ~uioh ~cancel ~active ~first ~pan =
2054 ignore (uioh, first, pan);
2055 if not cancel then set active;
2056 None
2057 method hasaction _ = true
2060 infomenu source
2061 )) :: m_l
2063 method caption s offset =
2064 m_l <- (s, `empty, offset, None) :: m_l
2066 method caption2 s f offset =
2067 m_l <- (s, `string f, offset, None) :: m_l
2069 method getitemcount = Array.length m_a
2071 method getitem n =
2072 let tostr = function
2073 | `int f -> string_of_int (f ())
2074 | `intws f -> string_with_suffix_of_int (f ())
2075 | `string f -> f ()
2076 | `color f -> color_to_string (f ())
2077 | `bool (btos, f) -> btos (f ())
2078 | `empty -> E.s
2080 let name, t, offset, _ = m_a.(n) in
2081 ((let s = tostr t in
2082 if nonemptystr s
2083 then Printf.sprintf "%s\t%s" name s
2084 else name),
2085 offset)
2087 method exit ~uioh ~cancel ~active ~first ~pan =
2088 let uiohopt =
2089 if not cancel
2090 then (
2091 let uioh =
2092 match m_a.(active) with
2093 | _, _, _, Some f -> f uioh
2094 | _, _, _, None -> uioh
2096 Some uioh
2098 else None
2100 m_active <- active;
2101 m_first <- first;
2102 m_pan <- pan;
2103 uiohopt
2105 method hasaction n =
2106 match m_a.(n) with
2107 | _, _, _, Some _ -> true
2108 | _, _, _, None -> false
2110 initializer m_active <- 1
2113 let rec fillsrc prevmode prevuioh =
2114 let sep () = src#caption E.s 0 in
2115 let bad v exn = settextfmt "bad color `%s': %s" v @@ exntos exn in
2116 let colorp name get set =
2117 src#string name
2118 (fun () -> color_to_string (get ()))
2119 (fun v ->
2120 try set @@ color_of_string v
2121 with exn -> bad v exn
2124 let rgba name get set =
2125 src#string name
2126 (fun () -> get () |> rgba_to_string)
2127 (fun v ->
2128 try set @@ rgba_of_string v
2129 with exn -> bad v exn
2132 let oldmode = !S.mode in
2133 let birdseye = isbirdseye !S.mode in
2135 src#caption (if birdseye then "Setup (Bird's eye)" else "Setup") 0;
2137 src#bool "presentation mode"
2138 (fun () -> conf.presentation)
2139 (fun v -> setpresentationmode v);
2141 src#bool "ignore case in searches"
2142 (fun () -> conf.icase)
2143 (fun v -> conf.icase <- v);
2145 src#bool "preload"
2146 (fun () -> conf.preload)
2147 (fun v -> conf.preload <- v);
2149 src#bool "highlight links"
2150 (fun () -> conf.hlinks)
2151 (fun v -> conf.hlinks <- v);
2153 src#bool "under info"
2154 (fun () -> conf.underinfo)
2155 (fun v -> conf.underinfo <- v);
2157 src#fitmodel "fit model"
2158 (fun () -> FMTE.to_string conf.fitmodel)
2159 (fun v -> reqlayout conf.angle (FMTE.of_int v));
2161 src#bool "trim margins"
2162 (fun () -> conf.trimmargins)
2163 (fun v -> settrim v conf.trimfuzz; fillsrc prevmode prevuioh);
2165 sep ();
2166 src#int "inter-page space"
2167 (fun () -> conf.interpagespace)
2168 (fun n ->
2169 conf.interpagespace <- n;
2170 docolumns conf.columns;
2171 let pageno, py =
2172 match !S.layout with
2173 | [] -> 0, 0
2174 | l :: _ -> l.pageno, l.pagey
2176 S.maxy :=- calcheight ();
2177 gotoxy !S.x (py + getpagey pageno)
2180 src#int "page bias"
2181 (fun () -> conf.pagebias)
2182 (fun v -> conf.pagebias <- v);
2184 src#int "scroll step"
2185 (fun () -> conf.scrollstep)
2186 (fun n -> conf.scrollstep <- n);
2188 src#int "horizontal scroll step"
2189 (fun () -> conf.hscrollstep)
2190 (fun v -> conf.hscrollstep <- v);
2192 src#int "auto scroll step"
2193 (fun () ->
2194 match !S.autoscroll with
2195 | Some step -> step
2196 | _ -> conf.autoscrollstep)
2197 (fun n ->
2198 let n = boundastep !S.winh n in
2199 if !S.autoscroll <> None
2200 then S.autoscroll := Some n;
2201 conf.autoscrollstep <- n);
2203 src#int "zoom"
2204 (fun () -> truncate (conf.zoom *. 100.))
2205 (fun v -> pivotzoom ((float v) /. 100.));
2207 src#int "rotation"
2208 (fun () -> conf.angle)
2209 (fun v -> reqlayout v conf.fitmodel);
2211 src#int "scroll bar width"
2212 (fun () -> conf.scrollbw)
2213 (fun v ->
2214 conf.scrollbw <- v;
2215 reshape !S.winw !S.winh;
2218 src#int "scroll handle height"
2219 (fun () -> conf.scrollh)
2220 (fun v -> conf.scrollh <- v;);
2222 src#int "thumbnail width"
2223 (fun () -> conf.thumbw)
2224 (fun v ->
2225 conf.thumbw <- min 4096 v;
2226 match oldmode with
2227 | Birdseye beye ->
2228 leavebirdseye beye false;
2229 enterbirdseye ()
2230 | Textentry _ | View | LinkNav _ -> ()
2233 let mode = !S.mode in
2234 src#string "columns"
2235 (fun () ->
2236 match conf.columns with
2237 | Csingle _ -> "1"
2238 | Cmulti (multi, _) -> multicolumns_to_string multi
2239 | Csplit (count, _) -> "-" ^ string_of_int count
2241 (fun v ->
2242 let n, a, b = multicolumns_of_string v in
2243 setcolumns mode n a b);
2245 sep ();
2246 src#caption "Pixmap cache" 0;
2247 src#int_with_suffix "size (advisory)"
2248 (fun () -> conf.memlimit)
2249 (fun v -> conf.memlimit <- v);
2251 src#caption2 "used"
2252 (fun () ->
2253 Printf.sprintf "%s bytes, %d tiles"
2254 (string_with_suffix_of_int !S.memused)
2255 (Hashtbl.length S.tilemap)) 1;
2257 sep ();
2258 src#caption "Layout" 0;
2259 src#caption2 "Dimension"
2260 (fun () -> Printf.sprintf "%dx%d (virtual %dx%d)"
2261 !S.winw !S.winh
2262 !S.w !S.maxy)
2264 if conf.debug
2265 then src#caption2 "Position" (fun () ->
2266 Printf.sprintf "%dx%d" !S.x !S.y
2268 else src#caption2 "Position" (fun () -> describe_layout !S.layout) 1;
2270 sep ();
2271 let btos b = Utf8syms.(if b then lguillemet else rguillemet) in
2272 src#bool ~offset:0 ~btos "Extended parameters"
2273 (fun () -> !showextended)
2274 (fun v -> showextended := v; fillsrc prevmode prevuioh);
2275 if !showextended
2276 then (
2277 src#bool "update cursor"
2278 (fun () -> conf.updatecurs)
2279 (fun v -> conf.updatecurs <- v);
2280 src#bool "scroll-bar on the left"
2281 (fun () -> conf.leftscroll)
2282 (fun v -> conf.leftscroll <- v);
2283 src#bool "verbose"
2284 (fun () -> conf.verbose)
2285 (fun v -> conf.verbose <- v);
2286 src#bool "invert colors"
2287 (fun () -> conf.invert)
2288 (fun v -> conf.invert <- v);
2289 src#bool "max fit"
2290 (fun () -> conf.maxhfit)
2291 (fun v -> conf.maxhfit <- v);
2292 src#bool "pax mode"
2293 (fun () -> conf.pax != None)
2294 (fun v ->
2295 if v
2296 then conf.pax <- Some (now ())
2297 else conf.pax <- None);
2298 src#string "tile size"
2299 (fun () -> Printf.sprintf "%dx%d" conf.tilew conf.tileh)
2300 (fun v ->
2302 let w, h = Scanf.sscanf v "%dx%d" (fun w h -> w, h) in
2303 conf.tilew <- max 64 w;
2304 conf.tileh <- max 64 h;
2305 flushtiles ();
2306 with exn -> settextfmt "bad tile size `%s': %s" v @@ exntos exn);
2307 src#int "texture count"
2308 (fun () -> conf.texcount)
2309 (fun v ->
2310 if Ffi.realloctexts v
2311 then conf.texcount <- v
2312 else impmsg "failed to set texture count please retry later");
2313 src#int "slice height"
2314 (fun () -> conf.sliceheight)
2315 (fun v ->
2316 conf.sliceheight <- v;
2317 wcmd U.sliceh "%d" conf.sliceheight);
2318 src#int "anti-aliasing level"
2319 (fun () -> conf.aalevel)
2320 (fun v ->
2321 conf.aalevel <- bound v 0 8;
2322 S.anchor := getanchor ();
2323 opendoc !S.path !S.mimetype !S.password);
2324 src#string "page scroll scaling factor"
2325 (fun () -> string_of_float conf.pgscale)
2326 (fun v ->
2327 try conf.pgscale <- float_of_string v
2328 with exn ->
2329 S.text :=
2330 Printf.sprintf "bad page scroll scaling factor `%s': %s" v
2331 @@ exntos exn);
2332 src#int "ui font size"
2333 (fun () -> fstate.fontsize)
2334 (fun v -> setfontsize (bound v 5 100));
2335 src#int "hint font size"
2336 (fun () -> conf.hfsize)
2337 (fun v -> conf.hfsize <- bound v 5 100);
2338 src#string "hint chars"
2339 (fun () -> conf.hcs)
2340 (fun v ->
2342 validatehcs v;
2343 conf.hcs <- v
2344 with exn ->
2345 S.text :=
2346 Printf.sprintf "invalid hint chars %S: %s" v (exntos exn));
2347 src#string "trim fuzz"
2348 (fun () -> irect_to_string conf.trimfuzz)
2349 (fun v ->
2351 conf.trimfuzz <- irect_of_string v;
2352 if conf.trimmargins
2353 then settrim true conf.trimfuzz;
2354 with exn -> settextfmt "bad irect `%s': %s" v @@ exntos exn);
2355 src#bool ~btos "external commands"
2356 (fun () -> !showcommands)
2357 (fun v -> showcommands := v; fillsrc prevmode prevuioh);
2358 if !showcommands
2359 then (
2360 src#string " uri launcher"
2361 (fun () -> conf.urilauncher)
2362 (fun v -> conf.urilauncher <- v);
2363 src#string " path launcher"
2364 (fun () -> conf.pathlauncher)
2365 (fun v -> conf.pathlauncher <- v);
2366 src#string " selection"
2367 (fun () -> conf.selcmd)
2368 (fun v -> conf.selcmd <- v);
2369 src#string " synctex"
2370 (fun () -> conf.stcmd)
2371 (fun v -> conf.stcmd <- v);
2372 src#string " pax"
2373 (fun () -> conf.paxcmd)
2374 (fun v -> conf.paxcmd <- v);
2375 src#string " ask password"
2376 (fun () -> conf.passcmd)
2377 (fun v -> conf.passcmd <- v);
2378 src#string " save path"
2379 (fun () -> conf.savecmd)
2380 (fun v -> conf.savecmd <- v);
2382 src#colorspace "color space"
2383 (fun () -> CSTE.to_string conf.colorspace)
2384 (fun v ->
2385 conf.colorspace <- CSTE.of_int v;
2386 wcmd U.cs "%d" v;
2387 load !S.layout);
2388 src#paxmark "pax mark method"
2389 (fun () -> MTE.to_string conf.paxmark)
2390 (fun v -> conf.paxmark <- MTE.of_int v);
2391 src#bool "mouse wheel scrolls pages"
2392 (fun () -> conf.wheelbypage)
2393 (fun v -> conf.wheelbypage <- v);
2394 src#bool "open remote links in a new instance"
2395 (fun () -> conf.riani)
2396 (fun v -> conf.riani <- v);
2397 src#bool "edit annotations inline"
2398 (fun () -> conf.annotinline)
2399 (fun v -> conf.annotinline <- v);
2400 src#bool "coarse positioning in presentation mode"
2401 (fun () -> conf.coarseprespos)
2402 (fun v -> conf.coarseprespos <- v);
2403 src#bool "use document CSS"
2404 (fun () -> conf.usedoccss)
2405 (fun v ->
2406 conf.usedoccss <- v;
2407 S.anchor := getanchor ();
2408 opendoc !S.path !S.mimetype !S.password);
2409 src#bool ~btos "colors"
2410 (fun () -> !showcolors)
2411 (fun v -> showcolors := v; fillsrc prevmode prevuioh);
2412 if !showcolors
2413 then (
2414 colorp " background"
2415 (fun () -> conf.bgcolor)
2416 (fun v -> conf.bgcolor <- v);
2417 rgba " paper"
2418 (fun () -> conf.papercolor)
2419 (fun v ->
2420 conf.papercolor <- v;
2421 Ffi.setpapercolor conf.papercolor;
2422 flushtiles ();
2424 rgba " scrollbar"
2425 (fun () -> conf.sbarcolor)
2426 (fun v -> conf.sbarcolor <- v);
2427 rgba " scrollbar handle"
2428 (fun () -> conf.sbarhndlcolor)
2429 (fun v -> conf.sbarhndlcolor <- v);
2430 rgba " texture"
2431 (fun () -> conf.texturecolor)
2432 (fun v ->
2433 GlTex.env (`color v);
2434 conf.texturecolor <- v;
2436 src#string " scale"
2437 (fun () -> string_of_float conf.colorscale)
2438 (fun v -> conf.colorscale <- bound (float_of_string v) 0.0 1.0);
2440 src#bool ~btos "reflowable layout"
2441 (fun () -> !showrefl)
2442 (fun v -> showrefl := v; fillsrc prevmode prevuioh);
2443 if !showrefl
2444 then (
2445 src#int " width"
2446 (fun () -> conf.rlw)
2447 (fun v -> conf.rlw <- v; reload ());
2448 src#int " height"
2449 (fun () -> conf.rlh)
2450 (fun v -> conf.rlh <- v; reload ());
2451 src#int " em"
2452 (fun () -> conf.rlem)
2453 (fun v -> conf.rlem <- v; reload ());
2457 sep ();
2458 src#caption "Document" 0;
2459 List.iter (fun (_, s) -> src#caption s 1) !S.docinfo;
2460 src#caption2 "Pages" (fun () -> string_of_int !S.pagecount) 1;
2461 src#caption2 "Dimensions"
2462 (fun () -> string_of_int (List.length !S.pdims)) 1;
2463 if nonemptystr conf.css
2464 then src#caption2 "CSS" (fun () -> conf.css) 1;
2465 if conf.trimmargins
2466 then (
2467 sep ();
2468 src#caption "Trimmed margins" 0;
2469 src#caption2 "Dimensions"
2470 (fun () -> string_of_int (List.length !S.pdims)) 1;
2473 sep ();
2474 src#caption "OpenGL" 0;
2475 src#caption ("Vendor\t" ^ GlMisc.get_string `vendor) 1;
2476 src#caption ("Renderer\t" ^ GlMisc.get_string `renderer) 1;
2478 sep ();
2479 src#caption "Location" 0;
2480 if nonemptystr !S.origin
2481 then src#caption ("Origin\t" ^ Ffi.mbtoutf8 !S.origin) 1;
2482 src#caption ("Path\t" ^ Ffi.mbtoutf8 !S.path) 1;
2483 if nonemptystr conf.dcf
2484 then src#caption ("DCF\t" ^ Ffi.mbtoutf8 conf.dcf) 1;
2486 src#reset prevmode prevuioh;
2488 fun () -> (
2489 S.text := E.s;
2490 resetmstate ();
2491 let prevmode = !S.mode
2492 and prevuioh = !S.uioh in
2493 fillsrc prevmode prevuioh;
2494 let source = (src :> lvsource) in
2495 let modehash = findkeyhash conf "info" in
2496 object (self)
2497 inherit listview ~zebra:false ~helpmode:false
2498 ~source ~trusted:true ~modehash as super
2499 val mutable m_prevmemused = 0
2500 method! infochanged = function
2501 | Memused ->
2502 if m_prevmemused != !S.memused
2503 then (
2504 m_prevmemused <- !S.memused;
2505 Glutils.postRedisplay "memusedchanged";
2507 | Pdim -> Glutils.postRedisplay "pdimchanged"
2508 | Docinfo -> fillsrc prevmode prevuioh
2509 method! key key mask =
2510 if not (Wsi.withctrl mask)
2511 then
2512 match [@warning "-fragile-match"] Wsi.ks2kt key with
2513 | Keys.Left -> coe (self#updownlevel ~-1)
2514 | Keys.Right -> coe (self#updownlevel 1)
2515 | _ -> super#key key mask
2516 else super#key key mask
2517 end |> setuioh;
2518 Glutils.postRedisplay "info";
2521 let enterhelpmode =
2522 let source = object
2523 inherit lvsourcebase
2524 method getitemcount = Array.length !S.help
2525 method getitem n =
2526 let s, l, _ = !S.help.(n) in
2527 (s, l)
2529 method exit ~uioh ~cancel ~active ~first ~pan =
2530 let optuioh =
2531 if not cancel
2532 then (
2533 match !S.help.(active) with
2534 | _, _, Some f -> Some (f uioh)
2535 | _, _, None -> Some uioh
2537 else None
2539 m_active <- active;
2540 m_first <- first;
2541 m_pan <- pan;
2542 optuioh
2544 method hasaction n =
2545 match !S.help.(n) with
2546 | _, _, Some _ -> true
2547 | _, _, None -> false
2549 initializer m_active <- -1
2551 in fun () ->
2552 let modehash = findkeyhash conf "help" in
2553 resetmstate ();
2554 new listview ~zebra:false ~helpmode:true
2555 ~source ~trusted:true ~modehash |> setuioh;
2556 Glutils.postRedisplay "help"
2558 let entermsgsmode =
2559 let msgsource = object
2560 inherit lvsourcebase
2561 val mutable m_items = E.a
2563 method getitemcount = 1 + Array.length m_items
2565 method getitem n =
2566 if n = 0
2567 then "[Clear]", 0
2568 else m_items.(n-1), 0
2570 method exit ~uioh ~cancel ~active ~first ~pan =
2571 ignore uioh;
2572 if not cancel
2573 then (
2574 if active = 0
2575 then Buffer.clear S.errmsgs;
2577 m_active <- active;
2578 m_first <- first;
2579 m_pan <- pan;
2580 None
2582 method hasaction n =
2583 n = 0
2585 method reset =
2586 S.newerrmsgs := false;
2587 let l = Str.split Re.crlf (Buffer.contents S.errmsgs) in
2588 m_items <- Array.of_list l
2590 initializer m_active <- 0
2593 fun () ->
2594 S.text := E.s;
2595 resetmstate ();
2596 msgsource#reset;
2597 let source = (msgsource :> lvsource) in
2598 let modehash = findkeyhash conf "listview" in
2599 object
2600 inherit listview ~zebra:false ~helpmode:false
2601 ~source ~trusted:false ~modehash as super
2602 method! display =
2603 if !S.newerrmsgs
2604 then msgsource#reset;
2605 super#display
2606 end |> setuioh;
2607 Glutils.postRedisplay "msgs"
2609 let getusertext s =
2610 let editor = getenvdef "EDITOR" E.s in
2611 if emptystr editor
2612 then E.s
2613 else
2614 let tmppath = Filename.temp_file "llpp" "note" in
2615 if nonemptystr s
2616 then (
2617 let oc = open_out tmppath in
2618 output_string oc s;
2619 close_out oc;
2621 let execstr = editor ^ " " ^ tmppath in
2622 let eret r = Printf.ksprintf (fun s -> adderrmsg "gtut:eret" s; r) in
2623 let s =
2624 match spawn execstr [] with
2625 | exception exn -> eret E.s "spawn(%S) failed: %s" execstr @@ exntos exn
2626 | pid ->
2627 match Unix.waitpid [] pid with
2628 | exception exn -> eret E.s "waitpid(%d) failed: %s" pid @@ exntos exn
2629 | (_pid, status) ->
2630 match status with
2631 | Unix.WEXITED 0 -> filecontents tmppath
2632 | Unix.WEXITED n ->
2633 eret E.s "editor process(%s) exited abnormally: %d" execstr n
2634 | Unix.WSIGNALED n ->
2635 eret E.s "editor process(%s) was killed by signal %d" execstr n
2636 | Unix.WSTOPPED n ->
2637 eret E.s "editor(%s) process was stopped by signal %d" execstr n
2639 match Unix.unlink tmppath with
2640 | exception exn -> eret s "failed to ulink %S: %s" tmppath @@ exntos exn
2641 | () -> s
2643 let enterannotmode opaque slinkindex =
2644 let msgsource = object
2645 inherit lvsourcebase
2646 val mutable m_text = E.s
2647 val mutable m_items = E.a
2649 method getitemcount = Array.length m_items
2651 method getitem n =
2652 let label, _func = m_items.(n) in
2653 label, 0
2655 method exit ~uioh ~cancel ~active ~first ~pan =
2656 ignore (uioh, first, pan);
2657 if not cancel
2658 then (
2659 let _label, func = m_items.(active) in
2660 func ()
2662 None
2664 method hasaction n = nonemptystr @@ fst m_items.(n)
2666 method reset s =
2667 let rec split accu b i =
2668 let p = b+i in
2669 if p = String.length s
2670 then (String.sub s b (p-b), fun () -> ()) :: accu
2671 else
2672 if (i > 70 && s.[p] = ' ') || s.[p] = '\r' || s.[p] = '\n'
2673 then
2674 let ss = if i = 0 then E.s else String.sub s b i in
2675 split ((ss, fun () -> ())::accu) (p+1) 0
2676 else split accu b (i+1)
2678 let cleanup () =
2679 wcmd1 U.freepage opaque;
2680 let keys =
2681 Hashtbl.fold (fun key opaque' accu ->
2682 if opaque' = opaque'
2683 then key :: accu else accu) S.pagemap []
2685 List.iter (Hashtbl.remove S.pagemap) keys;
2686 flushtiles ();
2687 gotoxy !S.x !S.y
2689 let dele () =
2690 Ffi.delannot opaque slinkindex;
2691 cleanup ();
2693 let edit inline () =
2694 let update s =
2695 if emptystr s
2696 then dele ()
2697 else (
2698 Ffi.modannot opaque slinkindex s;
2699 cleanup ();
2702 if inline
2703 then
2704 let mode = !S.mode in
2705 let te = ("annotation: ", m_text, None, textentry, update, true) in
2706 S.mode := Textentry (te, fun _ -> S.mode := mode);
2707 S.text := E.s;
2708 enttext ();
2709 else getusertext m_text |> update
2711 m_text <- s;
2712 m_items <-
2713 ( "[Copy]", fun () -> selstring conf.selcmd m_text)
2714 :: ("[Delete]", dele)
2715 :: ("[Edit]", edit conf.annotinline)
2716 :: (E.s, fun () -> ())
2717 :: split [] 0 0 |> List.rev |> Array.of_list
2719 initializer m_active <- 0
2722 S.text := E.s;
2723 let s = Ffi.gettextannot opaque slinkindex in
2724 resetmstate ();
2725 msgsource#reset s;
2726 let source = (msgsource :> lvsource) in
2727 let modehash = findkeyhash conf "listview" in
2728 object inherit listview ~zebra:false
2729 ~helpmode:false ~source ~trusted:false ~modehash
2730 end |> setuioh;
2731 Glutils.postRedisplay "enterannotmode"
2733 let gotoremote spec =
2734 let filename, dest = splitatchar spec '#' in
2735 let getpath filename =
2736 let path =
2737 if nonemptystr filename
2738 then
2739 if Filename.is_relative filename
2740 then
2741 let dir = Filename.dirname !S.path in
2742 let dir =
2743 if Filename.is_implicit dir
2744 then Filename.concat (Sys.getcwd ()) dir
2745 else dir
2747 Filename.concat dir filename
2748 else filename
2749 else E.s
2751 if Sys.file_exists path
2752 then path
2753 else E.s
2755 let path = getpath filename in
2756 if emptystr path
2757 then adderrfmt "gotoremote/getpath" "failed getpath for %S\n" filename
2758 else
2759 let dospawn lcmd =
2760 if conf.riani
2761 then
2762 let cmd = Lazy.force_val lcmd in
2763 match spawn cmd with
2764 | exception exn -> dolog "failed to execute `%s': %s" cmd @@ exntos exn
2765 | _pid -> ()
2766 else
2767 let anchor = getanchor () in
2768 let ranchor = !S.path, !S.mimetype, !S.password, anchor, !S.origin in
2769 S.origin := E.s;
2770 S.ranchors := ranchor :: !S.ranchors;
2771 opendoc path E.s E.s;
2773 if substratis spec 0 "page="
2774 then
2775 match Scanf.sscanf spec "page=%d" (fun n -> n) with
2776 | exception exn ->
2777 adderrfmt "error parsing remote destination" "%s %s" spec @@ exntos exn
2778 | pageno ->
2779 S.anchor := (pageno, 0.0, 0.0);
2780 dospawn @@ lazy (Printf.sprintf "%s -page %d %S"
2781 !S.selfexec pageno path);
2782 else (
2783 S.nameddest := dest;
2784 dospawn @@ lazy (!S.selfexec ^ " " ^ path ^ " -dest " ^ dest)
2787 let gotounder = function
2788 | Ulinkuri s when Ffi.isexternallink s ->
2789 if substratis s 0 "file://"
2790 then gotoremote @@ String.sub s 7 (String.length s - 7)
2791 else Help.gotouri conf.urilauncher s
2792 | Ulinkuri s ->
2793 let pageno, x, y = Ffi.uritolocation s in
2794 addnav ();
2795 gotopagexy pageno x y
2796 | Utext _ | Unone -> ()
2797 | Utextannot (opaque, slinkindex) -> enterannotmode opaque slinkindex
2798 | Ufileannot (opaque, slinkindex) ->
2799 if emptystr conf.savecmd
2800 then adderrmsg "savepath-command is empty"
2801 "don't know where to save attachment"
2802 else
2803 let filename = Ffi.getfileannot opaque slinkindex in
2804 let savecmd = Str.global_replace Re.percents filename conf.savecmd in
2805 let path =
2806 getcmdoutput
2807 (adderrfmt savecmd
2808 "failed to obtain path to the saved attachment: %s") savecmd
2810 Ffi.savefileannot opaque slinkindex path
2812 let gotooutline (_, _, kind) =
2813 match kind with
2814 | Onone -> ()
2815 | Oanchor ((pageno, y, _) as anchor) ->
2816 addnav ();
2817 gotoxy !S.x @@
2818 getanchory (if conf.presentation then (pageno, y, 1.0) else anchor)
2819 | Ouri uri -> gotounder (Ulinkuri uri)
2820 | Olaunch cmd -> error "gotounder (Ulaunch %S)" cmd
2821 | Oremote (remote, pageno) ->
2822 error "gotounder (Uremote (%S,%d) )" remote pageno
2823 | Ohistory hist -> gotohist hist
2824 | Oremotedest (path, dest) ->
2825 error "gotounder (Uremotedest (%S, %S))" path dest
2827 class outlinesoucebase fetchoutlines = object (self)
2828 inherit lvsourcebase
2829 val mutable m_items = E.a
2830 val mutable m_minfo = E.a
2831 val mutable m_orig_items = E.a
2832 val mutable m_orig_minfo = E.a
2833 val mutable m_narrow_patterns = []
2834 val mutable m_gen = -1
2836 method getitemcount = Array.length m_items
2838 method getitem n =
2839 let s, n, _ = m_items.(n) in
2840 (s, n+0)
2842 method exit ~(uioh:uioh) ~cancel ~active ~(first:int) ~pan : uioh option =
2843 ignore (uioh, first);
2844 let items, minfo =
2845 if m_narrow_patterns = []
2846 then m_orig_items, m_orig_minfo
2847 else m_items, m_minfo
2849 m_pan <- pan;
2850 if not cancel
2851 then (
2852 m_items <- items;
2853 m_minfo <- minfo;
2854 gotooutline m_items.(active);
2856 else (
2857 m_items <- items;
2858 m_minfo <- minfo;
2860 None
2862 method hasaction (_:int) = true
2864 method greetmsg =
2865 if Array.length m_items != Array.length m_orig_items
2866 then
2867 let s =
2868 match m_narrow_patterns with
2869 | one :: [] -> one
2870 | many -> String.concat Utf8syms.ellipsis (List.rev many)
2872 "Narrowed to " ^ s ^ " (ctrl-u to restore)"
2873 else E.s
2875 method statestr =
2876 match m_narrow_patterns with
2877 | [] -> E.s
2878 | one :: [] -> one
2879 | head :: _ -> Utf8syms.ellipsis ^ head
2881 method narrow pattern =
2882 match Str.regexp_case_fold pattern with
2883 | exception _ -> ()
2884 | re ->
2885 let rec loop accu minfo n =
2886 if n = -1
2887 then (
2888 m_items <- Array.of_list accu;
2889 m_minfo <- Array.of_list minfo;
2891 else
2892 let (s, _, _) as o = m_items.(n) in
2893 let accu, minfo =
2894 match Str.search_forward re s 0 with
2895 | exception Not_found -> accu, minfo
2896 | first -> o :: accu, (first, Str.match_end ()) :: minfo
2898 loop accu minfo (n-1)
2900 loop [] [] (Array.length m_items - 1)
2902 method! getminfo = m_minfo
2904 method denarrow =
2905 m_orig_items <- fetchoutlines ();
2906 m_minfo <- m_orig_minfo;
2907 m_items <- m_orig_items
2909 method add_narrow_pattern pattern =
2910 m_narrow_patterns <- pattern :: m_narrow_patterns
2912 method del_narrow_pattern =
2913 match m_narrow_patterns with
2914 | _ :: rest -> m_narrow_patterns <- rest
2915 | [] -> ()
2917 method renarrow =
2918 self#denarrow;
2919 match m_narrow_patterns with
2920 | pattern :: [] -> self#narrow pattern; pattern
2921 | list ->
2922 List.fold_left (fun accu pattern ->
2923 self#narrow pattern;
2924 pattern ^ Utf8syms.ellipsis ^ accu) E.s list
2926 method calcactive (_:anchor) = 0
2928 method reset anchor items =
2929 if !S.gen != m_gen
2930 then (
2931 m_orig_items <- items;
2932 m_items <- items;
2933 m_narrow_patterns <- [];
2934 m_minfo <- E.a;
2935 m_orig_minfo <- E.a;
2936 m_gen <- !S.gen;
2938 else (
2939 if items != m_orig_items
2940 then (
2941 m_orig_items <- items;
2942 if m_narrow_patterns == []
2943 then m_items <- items;
2946 let active = self#calcactive anchor in
2947 m_active <- active;
2948 m_first <- firstof m_first active
2951 let outlinesource fetchoutlines = object
2952 inherit outlinesoucebase fetchoutlines
2953 method! calcactive anchor =
2954 let rely = getanchory anchor in
2955 let rec loop n best bestd =
2956 if n = Array.length m_items
2957 then best
2958 else
2959 let _, _, kind = m_items.(n) in
2960 match kind with
2961 | Oanchor anchor ->
2962 let orely = getanchory anchor in
2963 let d = abs (orely - rely) in
2964 if d < bestd
2965 then loop (n+1) n d
2966 else loop (n+1) best bestd
2967 | Onone | Oremote _ | Olaunch _
2968 | Oremotedest _ | Ouri _ | Ohistory _ ->
2969 loop (n+1) best bestd
2971 loop 0 ~-1 max_int
2974 let enteroutlinemode, enterbookmarkmode, enterhistmode =
2975 let fetchoutlines sourcetype () =
2976 match sourcetype with
2977 | `bookmarks -> Array.of_list !S.bookmarks
2978 | `outlines -> !S.outlines
2979 | `history -> genhistoutlines () |> Array.of_list
2981 let so = outlinesource (fetchoutlines `outlines) in
2982 let sb = outlinesource (fetchoutlines `bookmarks) in
2983 let sh = outlinesource (fetchoutlines `history) in
2984 let mkselector sourcetype source =
2985 (fun emptymsg ->
2986 let outlines = fetchoutlines sourcetype () in
2987 if Array.length outlines = 0
2988 then showtext ' ' emptymsg
2989 else (
2990 resetmstate ();
2991 Wsi.setcursor Wsi.CURSOR_INHERIT;
2992 let anchor = getanchor () in
2993 source#reset anchor outlines;
2994 S.text := source#greetmsg;
2995 new outlinelistview ~zebra:(sourcetype=`history) ~source |> setuioh;
2996 Glutils.postRedisplay "enter selector";
3000 let mkenter src errmsg s = fun () -> mkselector src s errmsg in
3001 ( mkenter `outlines "document has no outline" so
3002 , mkenter `bookmarks "document has no bookmarks (yet)" sb
3003 , mkenter `history "history is empty" sh )
3005 let addbookmark title a =
3006 let b = List.filter (fun (title', _, _) -> title <> title') !S.bookmarks in
3007 S.bookmarks := (title, 0, Oanchor a) :: b
3009 let quickbookmark ?title () =
3010 match !S.layout with
3011 | [] -> ()
3012 | l :: _ ->
3013 let title =
3014 match title with
3015 | None ->
3016 Unix.(
3017 let tm = localtime (now ()) in
3018 Printf.sprintf
3019 "Quick (page %d) (bookmarked on %02d/%02d/%d at %02d:%02d)"
3020 (l.pageno+1)
3021 tm.tm_mday (tm.tm_mon+1) (tm.tm_year+1900) tm.tm_hour tm.tm_min
3023 | Some title -> title
3025 addbookmark title (getanchor1 l)
3027 let setautoscrollspeed step goingdown =
3028 let incr = max 1 ((abs step) / 2) in
3029 let incr = if goingdown then incr else -incr in
3030 let astep = boundastep !S.winh (step + incr) in
3031 S.autoscroll := Some astep
3033 let canpan () =
3034 match conf.columns with
3035 | Csplit _ -> true
3036 | Csingle _ | Cmulti _ -> !S.x != 0 || conf.zoom > 1.0
3038 let existsinrow pageno (columns, coverA, coverB) p =
3039 let last = ((pageno - coverA) mod columns) + columns in
3040 let rec any = function
3041 | [] -> false
3042 | l :: rest ->
3043 if l.pageno = coverA - 1 || l.pageno = !S.pagecount - coverB
3044 then p l
3045 else (
3046 if not (p l)
3047 then (if l.pageno = last then false else any rest)
3048 else true
3051 any !S.layout
3053 let nextpage () =
3054 match !S.layout with
3055 | [] ->
3056 let pageno = page_of_y !S.y in
3057 gotoxy !S.x (getpagey (pageno+1))
3058 | l :: rest ->
3059 match conf.columns with
3060 | Csingle _ ->
3061 if conf.presentation && rest == [] && l.pageh > l.pagey + l.pagevh
3062 then
3063 let y = U.add_to_y_and_clamp (U.pgscale !S.winh) in
3064 gotoxy !S.x y
3065 else
3066 let pageno = min (l.pageno+1) (!S.pagecount-1) in
3067 gotoxy !S.x (getpagey pageno)
3068 | Cmulti ((c, _, _) as cl, _) ->
3069 if conf.presentation
3070 && (existsinrow l.pageno cl
3071 (fun l -> l.pageh > l.pagey + l.pagevh))
3072 then
3073 let y = U.add_to_y_and_clamp (U.pgscale !S.winh) in
3074 gotoxy !S.x y
3075 else
3076 let pageno = min (l.pageno+c) (!S.pagecount-1) in
3077 gotoxy !S.x (getpagey pageno)
3078 | Csplit (n, _) ->
3079 if l.pageno < !S.pagecount - 1 || l.pagecol < n - 1
3080 then
3081 let pagey, pageh = getpageyh l.pageno in
3082 let pagey = pagey + pageh * l.pagecol in
3083 let ips = if l.pagecol = 0 then 0 else conf.interpagespace in
3084 gotoxy !S.x (pagey + pageh + ips)
3086 let prevpage () =
3087 match !S.layout with
3088 | [] ->
3089 let pageno = page_of_y !S.y in
3090 gotoxy !S.x (getpagey (pageno-1))
3091 | l :: _ ->
3092 match conf.columns with
3093 | Csingle _ ->
3094 if conf.presentation && l.pagey != 0
3095 then gotoxy !S.x (U.add_to_y_and_clamp (U.pgscale ~-(!S.winh)))
3096 else
3097 let pageno = max 0 (l.pageno-1) in
3098 gotoxy !S.x (getpagey pageno)
3099 | Cmulti ((c, _, coverB) as cl, _) ->
3100 if conf.presentation &&
3101 (existsinrow l.pageno cl (fun l -> l.pagey != 0))
3102 then gotoxy !S.x (U.add_to_y_and_clamp (U.pgscale ~-(!S.winh)))
3103 else
3104 let decr =
3105 if l.pageno = !S.pagecount - coverB
3106 then 1
3107 else c
3109 let pageno = max 0 (l.pageno-decr) in
3110 gotoxy !S.x (getpagey pageno)
3111 | Csplit (n, _) ->
3112 let y =
3113 if l.pagecol = 0
3114 then
3115 if l.pageno = 0
3116 then l.pagey
3117 else
3118 let pageno = max 0 (l.pageno-1) in
3119 let pagey, pageh = getpageyh pageno in
3120 pagey + (n-1)*pageh
3121 else
3122 let pagey, pageh = getpageyh l.pageno in
3123 pagey + pageh * (l.pagecol-1) - conf.interpagespace
3125 gotoxy !S.x y
3127 let save () =
3128 if emptystr conf.savecmd
3129 then adderrmsg "savepath-command is empty"
3130 "don't know where to save modified document"
3131 else
3132 let savecmd = Str.global_replace Re.percents !S.path conf.savecmd in
3133 let path =
3134 getcmdoutput
3135 (adderrfmt savecmd "failed to obtain path to the saved copy: %s")
3136 savecmd
3138 if nonemptystr path
3139 then
3140 let tmp = path ^ ".tmp" in
3141 Ffi.savedoc tmp;
3142 Unix.rename tmp path
3144 let viewkeyboard key mask =
3145 let enttext te =
3146 let mode = !S.mode in
3147 S.mode := Textentry (te, fun _ -> S.mode := mode);
3148 S.text := E.s;
3149 enttext ();
3150 Glutils.postRedisplay "view:enttext"
3151 and histback () =
3152 match !S.nav.past with
3153 | [] -> ()
3154 | prev :: prest ->
3155 S.nav := { past = prest ; future = getanchor () :: !S.nav.future; };
3156 gotoxy !S.x (getanchory prev)
3158 let ctrl = Wsi.withctrl mask in
3159 let open Keys in
3160 match Wsi.ks2kt key with
3161 | Ascii 'Q' -> exit 0
3162 | Ascii 'z' ->
3163 let yloc f =
3164 match List.rev !S.rects with
3165 | [] -> ()
3166 | (pageno, _, (_, y0, _, y1, _, y2, _, y3)) :: _ ->
3167 f pageno (y0, y1, y2, y3)
3168 and fsel f (y0, y1, y2, y3) = f y0 y1 |> f y2 |> f y3 |> truncate in
3169 let ondone msg = S.text := msg
3170 and zmod _ _ k =
3171 match [@warning "-fragile-match"] k with
3172 | Keys.Ascii 'z' ->
3173 let f pageno ys =
3174 let miny = fsel min ys in
3175 let hh = (fsel max ys - miny)/2 in
3176 gotopage1 pageno (miny + hh - !S.winh/2)
3178 yloc f;
3179 TEdone "center"
3180 | Keys.Ascii 't' ->
3181 let f pageno ys = gotopage1 pageno @@ fsel min ys in
3182 yloc f;
3183 TEdone "top"
3184 | Keys.Ascii 'b' ->
3185 let f pageno ys = gotopage1 pageno (fsel max ys - !S.winh) in
3186 yloc f;
3187 TEdone "bottom"
3188 | _ -> TEstop
3190 enttext (": ", E.s, None, zmod !S.mode, ondone, true)
3191 | Ascii 'W' ->
3192 if Ffi.hasunsavedchanges ()
3193 then save ()
3194 | Insert ->
3195 if conf.angle mod 360 = 0 && not (isbirdseye !S.mode)
3196 then (
3197 S.mode := (
3198 match !S.lnava with
3199 | None -> LinkNav (Ltgendir 0)
3200 | Some pn -> LinkNav (Ltexact pn)
3202 gotoxy !S.x !S.y;
3204 else impmsg "keyboard link navigation does not work under rotation"
3205 | Escape | Ascii 'q' ->
3206 begin match !S.mstate with
3207 | Mzoomrect _ ->
3208 resetmstate ();
3209 Glutils.postRedisplay "kill rect";
3210 | Msel _
3211 | Mpan _
3212 | Mscrolly | Mscrollx
3213 | Mzoom _
3214 | Mnone ->
3215 begin match !S.mode with
3216 | LinkNav ln ->
3217 begin match ln with
3218 | Ltexact pl -> S.lnava := Some pl
3219 | Ltgendir _ | Ltnotready _ -> S.lnava := None
3220 end;
3221 S.mode := View;
3222 Glutils.postRedisplay "esc leave linknav"
3223 | Birdseye _ | Textentry _ | View ->
3224 match !S.ranchors with
3225 | [] -> raise Quit
3226 | (path, mimetype, password, anchor, origin) :: rest ->
3227 S.ranchors := rest;
3228 S.anchor := anchor;
3229 S.origin := origin;
3230 S.nameddest := E.s;
3231 opendoc path mimetype password
3232 end;
3233 end;
3234 | Ascii 'o' -> enteroutlinemode ()
3235 | Ascii 'u' ->
3236 S.rects := [];
3237 S.text := E.s;
3238 Hashtbl.iter (fun _ opaque -> Ffi.clearmark opaque) S.pagemap;
3239 Glutils.postRedisplay "dehighlight";
3240 | Ascii (('/' | '?') as c) ->
3241 let ondone isforw s =
3242 cbput !S.hists.pat s;
3243 S.searchpattern := s;
3244 search s isforw
3246 enttext (String.make 1 c, E.s, Some (onhist !S.hists.pat),
3247 textentry, ondone (c = '/'), true)
3248 | Ascii '+' | Ascii '=' when ctrl ->
3249 let incr = if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01 in
3250 pivotzoom (conf.zoom +. incr)
3251 | Ascii '+' ->
3252 let ondone s =
3253 let n =
3254 try int_of_string s with exn ->
3255 S.text := Printf.sprintf "bad integer `%s': %s" s @@ exntos exn;
3256 max_int
3258 if n != max_int
3259 then (
3260 conf.pagebias <- n;
3261 S.text := "page bias is now " ^ string_of_int n;
3264 enttext ("page bias: ", E.s, None, intentry, ondone, true)
3265 | Ascii '-' when ctrl ->
3266 let decr = if conf.zoom -. 0.1 < 0.1 then 0.01 else 0.1 in
3267 pivotzoom (max 0.01 (conf.zoom -. decr))
3268 | Ascii '-' ->
3269 let ondone msg = S.text := msg in
3270 enttext ("option: ", E.s, None,
3271 optentry !S.mode, ondone, true)
3272 | Ascii '0' when ctrl ->
3273 if conf.zoom = 1.0
3274 then gotoxy 0 !S.y
3275 else setzoom 1.0
3276 | Ascii ('1'|'2' as c) when ctrl && conf.fitmodel != FitPage ->
3277 let cols =
3278 match conf.columns with
3279 | Csingle _ | Cmulti _ -> 1
3280 | Csplit (n, _) -> n
3282 let h = !S.winh -
3283 conf.interpagespace lsl (if conf.presentation then 1 else 0)
3285 let zoom = Ffi.zoomforh !S.winw h 0 cols in
3286 if zoom > 0.0 && (c = '2' || zoom < 1.0)
3287 then setzoom zoom
3288 | Ascii '3' when ctrl ->
3289 let fm =
3290 match conf.fitmodel with
3291 | FitWidth -> FitProportional
3292 | FitProportional -> FitPage
3293 | FitPage -> FitWidth
3295 S.text := "fit model: " ^ FMTE.to_string fm;
3296 reqlayout conf.angle fm
3297 | Ascii '4' when ctrl ->
3298 let zoom = Ffi.getmaxw () /. float !S.winw in
3299 if zoom > 0.0 then setzoom zoom
3300 | Fn 9 -> togglebirdseye ()
3301 | Ascii '9' when ctrl -> togglebirdseye ()
3302 | Ascii ('0'..'9' as c) when not ctrl ->
3303 let ondone s =
3304 let n =
3305 try int_of_string s with exn ->
3306 adderrfmt "int_of_string" "`%s': %s" s @@ exntos exn;
3309 if n >= 0
3310 then (
3311 addnav ();
3312 cbput !S.hists.pag (string_of_int n);
3313 gotopage1 (n + conf.pagebias - 1) 0;
3316 let pageentry text = function [@warning "-fragile-match"]
3317 | Keys.Ascii 'g' -> TEdone text
3318 | key -> intentry text key
3320 enttext (":", String.make 1 c, Some (onhist !S.hists.pag),
3321 pageentry, ondone, true)
3322 | Ascii 'b' ->
3323 conf.scrollb <- if conf.scrollb = 0 then (scrollbvv lor scrollbhv) else 0;
3324 Glutils.postRedisplay "toggle scrollbar";
3325 | Ascii 'B' ->
3326 S.bzoom := not !S.bzoom;
3327 S.rects := [];
3328 showtext ' ' ("block zoom " ^ onoffs !S.bzoom)
3329 | Ascii 'l' ->
3330 conf.hlinks <- not conf.hlinks;
3331 S.text := "highlightlinks " ^ onoffs conf.hlinks;
3332 Glutils.postRedisplay "toggle highlightlinks"
3333 | Ascii 'F' ->
3334 if conf.angle mod 360 = 0
3335 then (
3336 S.glinks := true;
3337 let mode = !S.mode in
3338 let te = ("goto: ", E.s, None, linknentry, linknact gotounder, false) in
3339 S.mode := Textentry (te, (fun _ -> S.glinks := false; S.mode := mode));
3340 S.text := E.s;
3341 Glutils.postRedisplay "view:linkent(F)"
3343 else impmsg "hint mode does not work under rotation"
3344 | Ascii 'y' ->
3345 S.glinks := true;
3346 let mode = !S.mode in
3347 let te = ("copy: ", E.s, None, linknentry,
3348 linknact (fun under -> selstring conf.selcmd (undertext under)),
3349 false) in
3350 S.mode := Textentry (te, (fun _ -> S.glinks := false; S.mode := mode));
3351 S.text := E.s;
3352 Glutils.postRedisplay "view:linkent"
3353 | Ascii 'a' ->
3354 begin match !S.autoscroll with
3355 | Some step ->
3356 conf.autoscrollstep <- step;
3357 S.autoscroll := None
3358 | None -> S.autoscroll := Some conf.autoscrollstep
3360 | Ascii 'p' when ctrl -> launchpath ()
3361 | Ascii 'P' ->
3362 setpresentationmode (not conf.presentation);
3363 showtext ' ' ("presentation mode " ^ onoffs conf.presentation)
3364 | Ascii 'f' ->
3365 if List.mem Wsi.Fullscreen !S.winstate
3366 then Wsi.reshape conf.cwinw conf.cwinh
3367 else Wsi.fullscreen ()
3368 | Ascii ('p'|'N') -> search !S.searchpattern false
3369 | Ascii 'n' | Fn 3 -> search !S.searchpattern true
3370 | Ascii 't' ->
3371 begin match !S.layout with
3372 | [] -> ()
3373 | l :: _ -> gotoxy !S.x (getpagey l.pageno)
3375 | Ascii ' ' -> nextpage ()
3376 | Delete -> prevpage ()
3377 | Ascii '=' -> showtext ' ' (describe_layout !S.layout);
3378 | Ascii 'w' ->
3379 begin match !S.layout with
3380 | [] -> ()
3381 | l :: _ ->
3382 Wsi.reshape l.pagew l.pageh;
3383 Glutils.postRedisplay "w"
3385 | Ascii '\'' -> enterbookmarkmode ()
3386 | Ascii 'i' -> enterinfomode ()
3387 | Ascii 'e' when Buffer.length S.errmsgs > 0 -> entermsgsmode ()
3388 | Ascii 'm' ->
3389 let ondone s =
3390 match !S.layout with
3391 | l :: _ when nonemptystr s -> addbookmark s @@ getanchor1 l
3392 | _ -> ()
3394 enttext ("bookmark: ", E.s, None, textentry, ondone, true)
3395 | Ascii '~' ->
3396 quickbookmark ();
3397 showtext ' ' "Quick bookmark added";
3398 | Ascii 'x' -> !S.roamf ()
3399 | Ascii ('<'|'>' as c) ->
3400 reqlayout (conf.angle + (if c = '>' then 30 else -30)) conf.fitmodel
3401 | Ascii ('['|']' as c) ->
3402 conf.colorscale <-
3403 bound (conf.colorscale +. (if c = ']' then 0.1 else -0.1)) 0.0 1.0;
3404 Glutils.postRedisplay "brightness";
3405 | Ascii 'c' when !S.mode = View ->
3406 if Wsi.withalt mask
3407 then (
3408 if conf.zoom > 1.0
3409 then
3410 let m = (!S.winw - !S.w) / 2 in
3411 gotoxy m !S.y
3413 else
3414 let (c, a, b), z =
3415 match !S.prevcolumns with
3416 | None -> (1, 0, 0), 1.0
3417 | Some (columns, z) ->
3418 let cab =
3419 match columns with
3420 | Csplit (c, _) -> -c, 0, 0
3421 | Cmulti ((c, a, b), _) -> c, a, b
3422 | Csingle _ -> 1, 0, 0
3424 cab, z
3426 setcolumns View c a b;
3427 setzoom z
3428 | Down | Up when ctrl && Wsi.withshift mask ->
3429 let zoom, x = !S.prevzoom in
3430 setzoom zoom;
3431 S.x := x;
3432 | Up ->
3433 begin match !S.autoscroll with
3434 | None ->
3435 begin match !S.mode with
3436 | Birdseye beye -> upbirdseye 1 beye
3437 | Textentry _ | View | LinkNav _ ->
3438 if ctrl
3439 then gotoxy !S.x (U.add_to_y_and_clamp ~-(!S.winh/2))
3440 else (
3441 if not (Wsi.withshift mask) && conf.presentation
3442 then prevpage ()
3443 else gotoxy !S.x (U.add_to_y_and_clamp (-conf.scrollstep))
3446 | Some n -> setautoscrollspeed n false
3448 | Down ->
3449 begin match !S.autoscroll with
3450 | None ->
3451 begin match !S.mode with
3452 | Birdseye beye -> downbirdseye 1 beye
3453 | Textentry _ | View | LinkNav _ ->
3454 if ctrl
3455 then gotoxy !S.x (U.add_to_y_and_clamp (!S.winh/2))
3456 else (
3457 if not (Wsi.withshift mask) && conf.presentation
3458 then nextpage ()
3459 else gotoxy !S.x (U.add_to_y_and_clamp (conf.scrollstep))
3462 | Some n -> setautoscrollspeed n true
3464 | Ascii 'H' -> enterhistmode ()
3465 | Fn 1 when Wsi.withalt mask -> enterhistmode ()
3466 | Fn 1 -> enterhelpmode ()
3467 | Left | Right when not (Wsi.withalt mask) ->
3468 if canpan ()
3469 then
3470 let dx =
3471 if ctrl
3472 then !S.winw / 2
3473 else conf.hscrollstep
3475 let dx =
3476 let pv = Wsi.ks2kt key in
3477 if pv = Keys.Left then dx else -dx
3479 gotoxy (U.panbound (!S.x + dx)) !S.y
3480 else (
3481 S.text := E.s;
3482 Glutils.postRedisplay "left/right"
3484 | Prior ->
3485 let y =
3486 if ctrl
3487 then
3488 match !S.layout with
3489 | [] -> !S.y
3490 | l :: _ -> !S.y - l.pagey
3491 else U.add_to_y_and_clamp (U.pgscale ~- !S.winh)
3493 gotoxy !S.x y
3494 | Next ->
3495 let y =
3496 if ctrl
3497 then
3498 match List.rev !S.layout with
3499 | [] -> !S.y
3500 | l :: _ -> getpagey l.pageno
3501 else U.add_to_y_and_clamp (U.pgscale !S.winh)
3503 gotoxy !S.x y
3504 | Ascii 'g' | Home ->
3505 addnav ();
3506 gotoxy 0 0
3507 | Ascii 'G' | End ->
3508 addnav ();
3509 gotoxy 0 (U.add_to_y_and_clamp !S.maxy)
3510 | Right when Wsi.withalt mask ->
3511 (match !S.nav.future with
3512 | [] -> ()
3513 | next :: frest ->
3514 S.nav := { past = getanchor () :: !S.nav.past; future = frest; };
3515 gotoxy !S.x (getanchory next)
3517 | Left when Wsi.withalt mask -> histback ()
3518 | Backspace -> histback ()
3519 | Ascii 'r' -> reload ()
3520 | Ascii 'v' when conf.debug ->
3521 S.rects := [];
3522 List.iter (fun l ->
3523 match getopaque l.pageno with
3524 | exception Not_found -> ()
3525 | opaque ->
3526 let x0, y0, x1, y1 = Ffi.pagebbox opaque in
3527 let rect = (float x0, float y0,
3528 float x1, float y0,
3529 float x1, float y1,
3530 float x0, float y1) in
3531 debugrect rect;
3532 let color = (0.0, 0.0, 1.0 /. (l.pageno mod 3 |> float), 0.5) in
3533 S.rects := (l.pageno, color, rect) :: !S.rects;
3534 ) !S.layout;
3535 Glutils.postRedisplay "v";
3536 | Ascii '|' ->
3537 let mode = !S.mode in
3538 let cmd = ref E.s in
3539 let onleave = function
3540 | Cancel -> S.mode := mode
3541 | Confirm ->
3542 List.iter (fun l ->
3543 match getopaque l.pageno with
3544 | exception Not_found -> ()
3545 | opaque -> pipesel opaque !cmd) !S.layout;
3546 S.mode := mode
3548 let ondone s =
3549 cbput !S.hists.sel s;
3550 cmd := s
3552 let te =
3553 "| ", !cmd, Some (onhist !S.hists.sel), textentry, ondone, true
3555 Glutils.postRedisplay "|";
3556 S.mode := Textentry (te, onleave);
3557 | (Ascii _|Fn _|Enter|Left|Right|Code _|Ctrl _) ->
3558 vlog "huh? %s" (Wsi.keyname key)
3560 let linknavkeyboard key mask linknav =
3561 let pv = Wsi.ks2kt key in
3562 let getpage pageno =
3563 let rec loop = function
3564 | [] -> None
3565 | l :: _ when l.pageno = pageno -> Some l
3566 | _ :: rest -> loop rest
3567 in loop !S.layout
3569 let doexact (pageno, n) =
3570 match getopaque pageno, getpage pageno with
3571 | opaque, Some l ->
3572 if pv = Keys.Enter
3573 then
3574 let under = Ffi.getlink opaque n in
3575 Glutils.postRedisplay "link gotounder";
3576 gotounder under;
3577 S.mode := View;
3578 else
3579 let opt, dir =
3580 let open Keys in
3581 match pv with
3582 | Home -> Some (Ffi.findlink opaque LDfirst), -1
3583 | End -> Some (Ffi.findlink opaque LDlast), 1
3584 | Left -> Some (Ffi.findlink opaque (LDleft n)), -1
3585 | Right -> Some (Ffi.findlink opaque (LDright n)), 1
3586 | Up -> Some (Ffi.findlink opaque (LDup n)), -1
3587 | Down -> Some (Ffi.findlink opaque (LDdown n)), 1
3588 | Delete|Escape|Insert|Enter|Next|Prior|Ascii _
3589 | Code _|Fn _|Ctrl _|Backspace -> None, 0
3591 let pwl l dir =
3592 begin match Ffi.findpwl l.pageno dir with
3593 | Pwlnotfound -> ()
3594 | Pwl pageno ->
3595 let notfound dir =
3596 S.mode := LinkNav (Ltgendir dir);
3597 let y, h = getpageyh pageno in
3598 let y =
3599 if dir < 0
3600 then y + h - !S.winh
3601 else y
3603 gotoxy !S.x y
3605 begin match getopaque pageno, getpage pageno with
3606 | opaque, Some _ ->
3607 let link =
3608 let ld = if dir > 0 then LDfirst else LDlast in
3609 Ffi.findlink opaque ld
3611 begin match link with
3612 | Lfound m ->
3613 showlinktype (Ffi.getlink opaque m);
3614 S.mode := LinkNav (Ltexact (pageno, m));
3615 Glutils.postRedisplay "linknav jpage";
3616 | Lnotfound -> notfound dir
3617 end;
3618 | _ | exception Not_found -> notfound dir
3619 end;
3620 end;
3622 begin match opt with
3623 | Some Lnotfound -> pwl l dir;
3624 | Some (Lfound m) ->
3625 if m = n
3626 then pwl l dir
3627 else (
3628 let _, y0, _, y1 = Ffi.getlinkrect opaque m in
3629 if y0 < l.pagey
3630 then gotopage1 l.pageno y0
3631 else (
3632 let d = fstate.fontsize + 1 in
3633 if y1 - l.pagey > l.pagevh - d
3634 then gotopage1 l.pageno (y1 - !S.winh + d)
3635 else Glutils.postRedisplay "linknav";
3637 showlinktype (Ffi.getlink opaque m);
3638 S.mode := LinkNav (Ltexact (l.pageno, m));
3641 | None -> viewkeyboard key mask
3642 end;
3643 | _ | exception Not_found -> viewkeyboard key mask
3645 if pv = Keys.Insert
3646 then (
3647 begin match linknav with
3648 | Ltexact pa -> S.lnava := Some pa
3649 | Ltgendir _ | Ltnotready _ -> ()
3650 end;
3651 S.mode := View;
3652 Glutils.postRedisplay "leave linknav"
3654 else
3655 match linknav with
3656 | Ltgendir _ | Ltnotready _ -> viewkeyboard key mask
3657 | Ltexact exact -> doexact exact
3659 let keyboard key mask =
3660 if (key = Char.code 'g' && Wsi.withctrl mask) && not (istextentry !S.mode)
3661 then wcmd U.interrupt ""
3662 else !S.uioh#key key mask |> setuioh
3664 let birdseyekeyboard key mask
3665 ((oconf, leftx, pageno, hooverpageno, anchor) as beye) =
3666 let incr =
3667 match conf.columns with
3668 | Csingle _ -> 1
3669 | Cmulti ((c, _, _), _) -> c
3670 | Csplit _ -> error "bird's eye split mode"
3672 let pgh layout = List.fold_left
3673 (fun m l -> max l.pageh m) !S.winh layout in
3674 let open Keys in
3675 match Wsi.ks2kt key with
3676 | Ascii 'l' when Wsi.withctrl mask ->
3677 let y, h = getpageyh pageno in
3678 let top = (!S.winh - h) / 2 in
3679 gotoxy !S.x (max 0 (y - top))
3680 | Enter -> leavebirdseye beye false
3681 | Escape -> leavebirdseye beye true
3682 | Up -> upbirdseye incr beye
3683 | Down -> downbirdseye incr beye
3684 | Left -> upbirdseye 1 beye
3685 | Right -> downbirdseye 1 beye
3687 | Prior ->
3688 begin match !S.layout with
3689 | l :: _ ->
3690 if l.pagey != 0
3691 then (
3692 S.mode := Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
3693 gotopage1 l.pageno 0;
3695 else (
3696 let layout = layout !S.x (!S.y - !S.winh)
3697 !S.winw
3698 (pgh !S.layout) in
3699 match layout with
3700 | [] -> gotoxy !S.x (U.add_to_y_and_clamp ~- !S.winh)
3701 | l :: _ ->
3702 S.mode := Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
3703 gotopage1 l.pageno 0
3706 | [] -> gotoxy !S.x (U.add_to_y_and_clamp ~- !S.winh)
3707 end;
3709 | Next ->
3710 begin match List.rev !S.layout with
3711 | l :: _ ->
3712 let layout = layout !S.x
3713 (!S.y + (pgh !S.layout))
3714 !S.winw !S.winh in
3715 begin match layout with
3716 | [] ->
3717 let incr = l.pageh - l.pagevh in
3718 if incr = 0
3719 then (
3720 S.mode :=
3721 Birdseye (
3722 oconf, leftx, !S.pagecount - 1, hooverpageno, anchor
3724 Glutils.postRedisplay "birdseye pagedown";
3726 else
3727 gotoxy !S.x (U.add_to_y_and_clamp (incr + conf.interpagespace*2));
3729 | l :: _ ->
3730 S.mode := Birdseye (oconf, leftx, l.pageno, hooverpageno, anchor);
3731 gotopage1 l.pageno 0;
3734 | [] -> gotoxy !S.x (U.add_to_y_and_clamp !S.winh)
3735 end;
3737 | Home ->
3738 S.mode := Birdseye (oconf, leftx, 0, hooverpageno, anchor);
3739 gotopage1 0 0
3741 | End ->
3742 let pageno = !S.pagecount - 1 in
3743 S.mode := Birdseye (oconf, leftx, pageno, hooverpageno, anchor);
3744 if not (U.pagevisible !S.layout pageno)
3745 then
3746 let h =
3747 match List.rev !S.pdims with
3748 | [] -> !S.winh
3749 | (_, _, h, _) :: _ -> h
3751 gotoxy
3752 !S.x
3753 (max 0 (getpagey pageno - (!S.winh - h - conf.interpagespace)))
3754 else Glutils.postRedisplay "birdseye end";
3756 | Delete|Insert|Ascii _|Code _|Ctrl _|Fn _|Backspace -> viewkeyboard key mask
3758 let drawpage l =
3759 let color =
3760 match !S.mode with
3761 | Textentry _ -> U.scalecolor 0.4
3762 | LinkNav _ | View -> U.scalecolor 1.0
3763 | Birdseye (_, _, pageno, hooverpageno, _) ->
3764 if l.pageno = hooverpageno
3765 then U.scalecolor 0.9
3766 else (
3767 if l.pageno = pageno
3768 then (
3769 let c = U.scalecolor 1.0 in
3770 GlDraw.color c;
3771 GlDraw.line_width 3.0;
3772 let dispx = l.pagedispx in
3773 Glutils.linerect
3774 (float (dispx-1)) (float (l.pagedispy-1))
3775 (float (dispx+l.pagevw+1))
3776 (float (l.pagedispy+l.pagevh+1));
3777 GlDraw.line_width 1.0;
3780 else U.scalecolor 0.8
3783 drawtiles l color
3785 let postdrawpage l linkindexbase =
3786 match getopaque l.pageno with
3787 | exception Not_found -> 0
3788 | opaque ->
3789 if tileready l l.pagex l.pagey
3790 then
3791 let x = l.pagedispx - l.pagex
3792 and y = l.pagedispy - l.pagey in
3793 let hlmask =
3794 match conf.columns with
3795 | Csingle _ | Cmulti _ ->
3796 (if conf.hlinks then 1 else 0)
3797 + (if !S.glinks
3798 && not (isbirdseye !S.mode) then 2 else 0)
3799 | Csplit _ -> 0
3801 let s =
3802 match !S.mode with
3803 | Textentry ((_, s, _, _, _, _), _) when !S.glinks -> s
3804 | Textentry _
3805 | Birdseye _
3806 | View
3807 | LinkNav _ -> E.s
3809 let n =
3810 Ffi.postprocess opaque hlmask x y
3811 (linkindexbase, s, conf.hfsize, conf.hcs) in
3812 if n < 0
3813 then (Glutils.redisplay := not @@ hasdata !S.ss; 0)
3814 else n
3815 else 0
3817 let scrollindicator () =
3818 let sbw, ph, sh = !S.uioh#scrollph in
3819 let sbh, pw, sw = !S.uioh#scrollpw in
3821 let x0,x1,hx0 =
3822 if conf.leftscroll
3823 then (0, sbw, sbw)
3824 else ((!S.winw - sbw), !S.winw, 0)
3827 Gl.enable `blend;
3828 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3829 let (r, g, b, alpha) = conf.sbarcolor in
3830 GlDraw.color (r, g, b) ~alpha;
3831 Glutils.filledrect (float x0) 0. (float x1) (float !S.winh);
3832 Glutils.filledrect
3833 (float hx0) (float (!S.winh - sbh))
3834 (float (hx0 + !S.winw)) (float !S.winh);
3835 let (r, g, b, alpha) = conf.sbarhndlcolor in
3836 GlDraw.color (r, g, b) ~alpha;
3838 Glutils.filledrect (float x0) ph (float x1) (ph +. sh);
3839 let pw = pw +. float hx0 in
3840 Glutils.filledrect pw (float (!S.winh - sbh)) (pw +. sw) (float !S.winh);
3841 Gl.disable `blend
3843 let showsel () =
3844 match !S.mstate with
3845 | Mnone | Mscrolly | Mscrollx | Mpan _ | Mzoom _ | Mzoomrect _ -> ()
3846 | Msel ((x0, y0), (x1, y1)) ->
3847 let identify opaque l px py = Some (opaque, l.pageno, px, py) in
3848 let o0,n0,px0,py0 =
3849 onppundermouse identify x0 y0 (Opaque.of_string E.s, -1, 0, 0) in
3850 let _o1,n1,px1,py1 =
3851 onppundermouse identify x1 y1 (Opaque.of_string E.s, -1, 0, 0) in
3852 if n0 != -1 && n0 = n1 then Ffi.seltext o0 (px0, py0, px1, py1)
3854 let showrects = function
3855 | [] -> ()
3856 | rects ->
3857 Gl.enable `blend;
3858 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
3859 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3860 List.iter
3861 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
3862 List.iter (fun l ->
3863 if l.pageno = pageno
3864 then
3865 let dx = float (l.pagedispx - l.pagex) in
3866 let dy = float (l.pagedispy - l.pagey) in
3867 let r, g, b, alpha = c in
3868 GlDraw.color (r, g, b) ~alpha;
3869 Glutils.filledrect2
3870 (x0+.dx) (y0+.dy)
3871 (x1+.dx) (y1+.dy)
3872 (x3+.dx) (y3+.dy)
3873 (x2+.dx) (y2+.dy);
3874 ) !S.layout
3875 ) rects;
3876 Gl.disable `blend
3878 let display () =
3879 let sc (r, g, b) = let s = conf.colorscale in (r *. s, g *. s, b *. s) in
3880 GlDraw.color (sc conf.bgcolor);
3881 GlClear.color (sc conf.bgcolor);
3882 GlClear.clear [`color];
3883 List.iter drawpage !S.layout;
3884 let rects =
3885 match !S.mode with
3886 | LinkNav (Ltgendir _) | LinkNav (Ltnotready _)
3887 | Birdseye _
3888 | Textentry _
3889 | View -> !S.rects
3890 | LinkNav (Ltexact (pageno, linkno)) ->
3891 match getopaque pageno with
3892 | exception Not_found -> !S.rects
3893 | opaque ->
3894 let x0, y0, x1, y1 = Ffi.getlinkrect opaque linkno in
3895 let color =
3896 if conf.invert
3897 then (1.0, 1.0, 1.0, 0.5)
3898 else (0.0, 0.0, 0.5, 0.5)
3900 (pageno, color,
3901 (float x0, float y0,
3902 float x1, float y0,
3903 float x1, float y1,
3904 float x0, float y1)
3905 ) :: !S.rects
3907 showrects rects;
3908 let rec postloop linkindexbase = function
3909 | l :: rest ->
3910 let linkindexbase = linkindexbase + postdrawpage l linkindexbase in
3911 postloop linkindexbase rest
3912 | [] -> ()
3914 showsel ();
3915 postloop 0 !S.layout;
3916 !S.uioh#display;
3917 begin match !S.mstate with
3918 | Mzoomrect ((x0, y0), (x1, y1)) ->
3919 Gl.enable `blend;
3920 GlDraw.color (0.3, 0.3, 0.3) ~alpha:0.5;
3921 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3922 Glutils.filledrect (float x0) (float y0) (float x1) (float y1);
3923 Gl.disable `blend;
3924 | Msel _
3925 | Mpan _
3926 | Mscrolly | Mscrollx
3927 | Mzoom _
3928 | Mnone -> ()
3929 end;
3930 enttext ();
3931 scrollindicator ();
3933 if conf.pgscale > 0.0
3934 then (
3935 let drawsep y =
3936 let x0 = 0.0 and y0 = y -. 3.0 in
3937 let x1 = float !S.winw and y1 = y +. 3.0 in
3938 Glutils.filledrect x0 y0 x1 y1;
3940 Gl.enable `blend;
3941 GlDraw.color (0.1, 0.1, 0.1) ~alpha:0.5;
3942 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
3943 (match !S.layout with
3944 | _ :: [] -> drawsep (conf.pgscale *. float !S.winh)
3945 | l -> List.iter (fun p -> drawsep (float (p.pagedispy+p.pagevh))) l
3947 Gl.disable `blend;
3949 Wsi.swapb ()
3951 let display () =
3952 match !S.reload with
3953 | Some (x, y, t) ->
3954 if x != !S.x || y != !S.y || abs_float @@ now () -. t > 0.5
3955 || (!S.layout != [] && alltilesrendered !S.layout)
3956 then (
3957 S.reload := None;
3958 display ()
3960 | None -> display ()
3962 let zoomrect x y x1 y1 =
3963 let x0 = min x x1
3964 and x1 = max x x1
3965 and y0 = min y y1 in
3966 let zoom = (float !S.w) /. float (x1 - x0) in
3967 let margin =
3968 let simple () =
3969 if !S.w < !S.winw
3970 then (!S.winw - !S.w) / 2
3971 else 0
3973 match conf.fitmodel with
3974 | FitWidth | FitProportional -> simple ()
3975 | FitPage ->
3976 match conf.columns with
3977 | Csplit _ ->
3978 onppundermouse (fun _ l _ _ -> Some l.pagedispx) x0 y0 x0
3979 | Cmulti _ | Csingle _ -> simple ()
3981 gotoxy ((!S.x + margin) - x0) (!S.y + y0);
3982 S.anchor := getanchor ();
3983 setzoom zoom;
3984 resetmstate ()
3986 let annot inline x y =
3987 match unproject x y with
3988 | Some (opaque, n, ux, uy) ->
3989 let add text =
3990 Ffi.addannot opaque ux uy text;
3991 wcmd1 U.freepage opaque;
3992 Hashtbl.remove S.pagemap (n, !S.gen);
3993 flushtiles ();
3994 gotoxy !S.x !S.y
3996 if inline
3997 then
3998 let mode = !S.mode in
3999 let te = ("annotation: ", E.s, None, textentry, add, true) in
4000 S.mode := Textentry (te, fun _ -> S.mode := mode);
4001 S.text := E.s;
4002 enttext ();
4003 Glutils.postRedisplay "annot"
4004 else add @@ getusertext E.s
4005 | _ -> ()
4007 let zoomblock x y =
4008 let g opaque l px py =
4009 match Ffi.rectofblock opaque px py with
4010 | Some a ->
4011 let x0 = a.(0) -. 20. in
4012 let x1 = a.(1) +. 20. in
4013 let y0 = a.(2) -. 20. in
4014 let zoom = (float !S.w) /. (x1 -. x0) in
4015 let pagey = getpagey l.pageno in
4016 let margin = (!S.w - l.pagew)/2 in
4017 let nx = -truncate x0 - margin in
4018 gotoxy nx (pagey + truncate y0);
4019 S.anchor := getanchor ();
4020 setzoom zoom;
4021 None
4022 | None -> None
4024 match conf.columns with
4025 | Csplit _ ->
4026 impmsg "block zooming while in split columns mode is not implemented"
4027 | Cmulti _ | Csingle _ -> onppundermouse g x y ()
4029 let scrollx x =
4030 let winw = !S.winw - 1 in
4031 let s = float x /. float winw in
4032 let destx = truncate (float (!S.w + winw) *. s) in
4033 gotoxy (winw - destx) !S.y;
4034 S.mstate := Mscrollx
4036 let scrolly y =
4037 let s = float y /. float !S.winh in
4038 let desty = truncate (s *. float (U.maxy ())) in
4039 gotoxy !S.x desty;
4040 S.mstate := Mscrolly
4042 let viewmulticlick clicks x y mask =
4043 let g opaque l px py =
4044 let mark =
4045 match clicks with
4046 | 2 -> MarkWord
4047 | 3 -> MarkLine
4048 | 4 -> MarkBlock
4049 | _ -> MarkPage
4051 if Ffi.markunder opaque px py mark
4052 then (
4053 Some (fun () ->
4054 let dopipe cmd =
4055 match getopaque l.pageno with
4056 | exception Not_found -> ()
4057 | opaque -> pipesel opaque cmd
4059 S.roamf := (fun () -> dopipe conf.paxcmd);
4060 if not (Wsi.withctrl mask) then dopipe conf.selcmd;
4063 else None
4065 Glutils.postRedisplay "viewmulticlick";
4066 onppundermouse g x y (fun () -> impmsg "nothing to select") ()
4068 let canselect () = conf.angle mod 360 = 0
4070 let viewmouse button down x y mask =
4071 match button with
4072 | n when (n == 4 || n == 5) && not (Wsi.withshift mask) && not down ->
4073 if Wsi.withctrl mask
4074 then (
4075 let incr =
4076 if n = 5
4077 then if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01
4078 else if conf.zoom -. 0.1 < 0.1 then -0.01 else -0.1
4080 let fx, fy =
4081 match !S.mstate with
4082 | Mzoom (oldn, _, pos) when n = oldn -> pos
4083 | Mzoomrect _ | Mnone | Mpan _
4084 | Msel _ | Mscrollx | Mscrolly | Mzoom _ -> (x, y)
4086 let zoom = conf.zoom -. incr in
4087 S.mstate := Mzoom (n, 0, (x, y));
4088 if false && abs (fx - x) > 5 || abs (fy - y) > 5
4089 then pivotzoom ~x ~y zoom
4090 else pivotzoom zoom
4092 else (
4093 match !S.autoscroll with
4094 | Some step -> setautoscrollspeed step (n=4)
4095 | None ->
4096 if conf.wheelbypage || conf.presentation
4097 then (
4098 if n = 4
4099 then prevpage ()
4100 else nextpage ()
4102 else
4103 let incr = if n = 4 then -conf.scrollstep else conf.scrollstep in
4104 let incr = incr * 2 in
4105 let y = U.add_to_y_and_clamp incr in
4106 gotoxy !S.x y
4109 | n when (n = 4 || n = 5 || n = 6 || n = 7) && not down && canpan () ->
4110 let x = U.panbound
4111 (!S.x + (if n = 5 || n = 7 then -2 else 2) * conf.hscrollstep)
4113 gotoxy x !S.y
4115 | 1 when Wsi.withshift mask ->
4116 S.mstate := Mnone;
4117 if not down
4118 then (
4119 match unproject x y with
4120 | None -> ()
4121 | Some (_, pageno, ux, uy) ->
4122 let cmd = Printf.sprintf "%s %s %d %d %d" conf.stcmd !S.path
4123 pageno ux uy
4125 match spawn cmd [] with
4126 | exception exn ->
4127 adderrfmt "spawn" "execution of synctex command(%S) failed: %S"
4128 conf.stcmd @@ exntos exn
4129 | _pid -> ()
4132 | 1 when Wsi.withctrl mask ->
4133 if down
4134 then (
4135 Wsi.setcursor Wsi.CURSOR_FLEUR;
4136 S.mstate := Mpan (x, y)
4138 else S.mstate := Mnone
4140 | 3 ->
4141 if down
4142 then (
4143 if Wsi.withshift mask
4144 then (
4145 annot conf.annotinline x y;
4146 Glutils.postRedisplay "addannot"
4148 else
4149 let p = (x, y) in
4150 Wsi.setcursor Wsi.CURSOR_CYCLE;
4151 S.mstate := Mzoomrect (p, p)
4153 else (
4154 match !S.mstate with
4155 | Mzoomrect ((x0, y0), _) ->
4156 if abs (x-x0) > 10 && abs (y - y0) > 10
4157 then zoomrect x0 y0 x y
4158 else (
4159 resetmstate ();
4160 Glutils.postRedisplay "kill accidental zoom rect";
4162 | Msel _
4163 | Mpan _
4164 | Mscrolly | Mscrollx
4165 | Mzoom _
4166 | Mnone -> resetmstate ()
4169 | 1 when vscrollhit x ->
4170 if down
4171 then
4172 let _, position, sh = !S.uioh#scrollph in
4173 if y > truncate position && y < truncate (position +. sh)
4174 then S.mstate := Mscrolly
4175 else scrolly y
4176 else S.mstate := Mnone
4178 | 1 when y > !S.winh - hscrollh () ->
4179 if down
4180 then
4181 let _, position, sw = !S.uioh#scrollpw in
4182 if x > truncate position && x < truncate (position +. sw)
4183 then S.mstate := Mscrollx
4184 else scrollx x
4185 else S.mstate := Mnone
4187 | 1 when !S.bzoom -> if not down then zoomblock x y
4189 | 1 ->
4190 let dest = if down then getunder x y else Unone in
4191 begin match dest with
4192 | Ulinkuri _ -> gotounder dest
4193 | Unone when down ->
4194 Wsi.setcursor Wsi.CURSOR_FLEUR;
4195 S.mstate := Mpan (x, y);
4196 | Utextannot (opaque, slinkindex) -> enterannotmode opaque slinkindex
4197 | Unone | Utext _ | Ufileannot _ ->
4198 if down
4199 then (
4200 if canselect ()
4201 then (
4202 S.mstate := Msel ((x, y), (x, y));
4203 Glutils.postRedisplay "mouse select";
4206 else (
4207 match !S.mstate with
4208 | Mnone -> ()
4209 | Mzoom _ | Mscrollx | Mscrolly -> S.mstate := Mnone
4210 | Mzoomrect ((x0, y0), _) -> zoomrect x0 y0 x y
4211 | Mpan _ ->
4212 Wsi.setcursor Wsi.CURSOR_INHERIT;
4213 S.mstate := Mnone
4214 | Msel ((x0, y0), (x1, y1)) ->
4215 let rec loop = function
4216 | [] -> ()
4217 | l :: rest ->
4218 let inside =
4219 let a0 = l.pagedispy in
4220 let a1 = a0 + l.pagevh in
4221 let b0 = l.pagedispx in
4222 let b1 = b0 + l.pagevw in
4223 ((y0 >= a0 && y0 <= a1) || (y1 >= a0 && y1 <= a1))
4224 && ((x0 >= b0 && x0 <= b1) || (x1 >= b0 && x1 <= b1))
4226 if inside
4227 then
4228 match getopaque l.pageno with
4229 | exception Not_found -> ()
4230 | opaque ->
4231 let dosel cmd () =
4232 pipef ~closew:false "Msel"
4233 (fun w ->
4234 Ffi.copysel w opaque;
4235 Glutils.postRedisplay "Msel") cmd
4237 dosel conf.selcmd ();
4238 S.roamf := dosel conf.paxcmd;
4239 else loop rest
4241 loop !S.layout;
4242 resetmstate ();
4245 | _ -> ()
4247 let birdseyemouse button down x y mask
4248 (conf, leftx, _, hooverpageno, anchor) =
4249 match button with
4250 | 1 when down ->
4251 let rec loop = function
4252 | [] -> ()
4253 | l :: rest ->
4254 if y > l.pagedispy && y < l.pagedispy + l.pagevh
4255 && x > l.pagedispx && x < l.pagedispx + l.pagevw
4256 then
4257 leavebirdseye (conf, leftx, l.pageno, hooverpageno, anchor) false
4258 else loop rest
4260 loop !S.layout
4261 | 3 -> ()
4262 | _ -> viewmouse button down x y mask
4264 let uioh = object
4265 method display = ()
4266 method infochanged _ = ()
4268 method key key mask =
4269 begin match !S.mode with
4270 | Textentry textentry -> textentrykeyboard key mask textentry
4271 | Birdseye birdseye -> birdseyekeyboard key mask birdseye
4272 | View -> viewkeyboard key mask
4273 | LinkNav linknav -> linknavkeyboard key mask linknav
4274 end;
4275 !S.uioh
4277 method button button bstate x y mask =
4278 begin match !S.mode with
4279 | LinkNav _ | View -> viewmouse button bstate x y mask
4280 | Birdseye beye -> birdseyemouse button bstate x y mask beye
4281 | Textentry _ -> ()
4282 end;
4283 !S.uioh
4285 method multiclick clicks x y mask =
4286 begin match !S.mode with
4287 | LinkNav _ | View -> viewmulticlick clicks x y mask
4288 | Birdseye _ | Textentry _ -> ()
4289 end;
4290 !S.uioh
4292 method motion x y =
4293 begin match !S.mode with
4294 | Textentry _ -> ()
4295 | View | Birdseye _ | LinkNav _ ->
4296 match !S.mstate with
4297 | Mzoom _ | Mnone -> ()
4298 | Mpan (x0, y0) ->
4299 let dx = x - x0
4300 and dy = y0 - y in
4301 S.mstate := Mpan (x, y);
4302 let x = if canpan () then U.panbound (!S.x + dx) else !S.x in
4303 let y = U.add_to_y_and_clamp dy in
4304 gotoxy x y
4306 | Msel (a, _) ->
4307 S.mstate := Msel (a, (x, y));
4308 Glutils.postRedisplay "motion select";
4310 | Mscrolly ->
4311 let y = min !S.winh (max 0 y) in
4312 scrolly y
4314 | Mscrollx ->
4315 let x = min !S.winw (max 0 x) in
4316 scrollx x
4318 | Mzoomrect (p0, _) ->
4319 S.mstate := Mzoomrect (p0, (x, y));
4320 Glutils.postRedisplay "motion zoomrect";
4321 end;
4322 !S.uioh
4324 method pmotion x y =
4325 begin match !S.mode with
4326 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
4327 let rec loop = function
4328 | [] ->
4329 if hooverpageno != -1
4330 then (
4331 S.mode := Birdseye (conf, leftx, pageno, -1, anchor);
4332 Glutils.postRedisplay "pmotion birdseye no hoover";
4334 | l :: rest ->
4335 if y > l.pagedispy && y < l.pagedispy + l.pagevh
4336 && x > l.pagedispx && x < l.pagedispx + l.pagevw
4337 then (
4338 S.mode := Birdseye (conf, leftx, pageno, l.pageno, anchor);
4339 Glutils.postRedisplay "pmotion birdseye hoover";
4341 else loop rest
4343 loop !S.layout
4345 | Textentry _ -> ()
4347 | LinkNav _ | View ->
4348 match !S.mstate with
4349 | Mpan _ | Msel _ | Mzoom _ | Mscrolly | Mscrollx | Mzoomrect _ -> ()
4350 | Mnone ->
4351 updateunder x y;
4352 if canselect ()
4353 then
4354 match conf.pax with
4355 | None -> ()
4356 | Some past ->
4357 let now = now () in
4358 let delta = now -. past in
4359 if delta > 0.01
4360 then paxunder x y
4361 else conf.pax <- Some now
4362 end;
4363 !S.uioh
4365 method scrollph =
4366 let maxy = U.maxy () in
4367 let p, h =
4368 if maxy = 0
4369 then 0.0, float !S.winh
4370 else scrollph !S.y maxy
4372 vscrollw (), p, h
4374 method scrollpw =
4375 let fwinw = float (!S.winw - vscrollw ()) in
4376 let sw =
4377 let sw = fwinw /. float !S.w in
4378 let sw = fwinw *. sw in
4379 max sw (float conf.scrollh)
4381 let position =
4382 let maxx = !S.w + !S.winw in
4383 let x = !S.winw - !S.x in
4384 let percent = float x /. float maxx in
4385 (fwinw -. sw) *. percent
4387 hscrollh (), position, sw
4389 method modehash =
4390 let modename =
4391 match !S.mode with
4392 | LinkNav _ -> "links"
4393 | Textentry _ -> "textentry"
4394 | Birdseye _ -> "birdseye"
4395 | View -> "view"
4397 findkeyhash conf modename
4399 method eformsgs = true
4400 method alwaysscrolly = false
4401 method scroll dx dy =
4402 let x = if canpan () then U.panbound (!S.x + dx) else !S.x in
4403 gotoxy x (U.add_to_y_and_clamp (2 * dy));
4404 !S.uioh
4405 method zoom z x y =
4406 pivotzoom ~x ~y (conf.zoom *. exp z);
4409 let ract cmds =
4410 let cl = splitatchar cmds ' ' in
4411 let scan s fmt f =
4412 try Scanf.sscanf s fmt f
4413 with exn -> adderrfmt "remote exec" "error processing '%S': %s\n"
4414 cmds @@ exntos exn
4416 let rectx s pageno (r, g, b, a) x0 y0 x1 y1 =
4417 vlog "%s page %d color (%f %f %f %f) x0,y0,x1,y1 = %f %f %f %f"
4418 s pageno r g b a x0 y0 x1 y1;
4419 onpagerect
4420 pageno
4421 (fun w h ->
4422 let _,w1,h1,_ = getpagedim pageno in
4423 let sw = float w1 /. float w
4424 and sh = float h1 /. float h in
4425 let x0s = x0 *. sw
4426 and x1s = x1 *. sw
4427 and y0s = y0 *. sh
4428 and y1s = y1 *. sh in
4429 let rect = (x0s,y0s,x1s,y0s,x1s,y1s,x0s,y1s) in
4430 let color = (r, g, b, a) in
4431 if conf.verbose then debugrect rect;
4432 S.rects := (pageno, color, rect) :: !S.rects;
4433 Glutils.postRedisplay s;
4436 match cl with
4437 | "reload", "" -> reload ()
4438 | "goto", args ->
4439 scan args "%u %f %f"
4440 (fun pageno x y ->
4441 let cmd, _ = !S.geomcmds in
4442 if emptystr cmd
4443 then gotopagexy pageno x y
4444 else
4445 let f prevf () =
4446 gotopagexy pageno x y;
4447 prevf ()
4449 S.reprf := f !S.reprf
4451 | "goto1", args -> scan args "%u %f" gotopage
4452 | "gotor", args -> scan args "%S" gotoremote
4453 | "rect", args ->
4454 scan args "%u %u %f %f %f %f"
4455 (fun pageno c x0 y0 x1 y1 ->
4456 let color = (0.0, 0.0, 1.0 /. float c, 0.5) in
4457 rectx "rect" pageno color x0 y0 x1 y1;
4459 | "pgoto", args ->
4460 scan args "%u %f %f"
4461 (fun pageno x y ->
4462 let optopaque =
4463 match getopaque pageno with
4464 | exception Not_found -> Opaque.of_string E.s
4465 | opaque -> opaque
4467 pgoto optopaque pageno x y;
4468 let rec fixx = function
4469 | [] -> ()
4470 | l :: rest ->
4471 if l.pageno = pageno
4472 then gotoxy (!S.x - l.pagedispx) !S.y
4473 else fixx rest
4475 let layout =
4476 let mult =
4477 match conf.columns with
4478 | Csingle _ | Csplit _ -> 1
4479 | Cmulti ((n, _, _), _) -> n
4481 layout 0 !S.y (!S.winw * mult) !S.winh
4483 fixx layout
4485 | "activatewin", "" -> Wsi.activatewin ()
4486 | "quit", "" -> raise Quit
4487 | "keys", keys ->
4488 begin try
4489 let l = Config.keys_of_string keys in
4490 List.iter (fun (k, m) -> keyboard k m) l
4491 with exn -> adderrfmt "error processing keys" "`%S': %s\n"
4492 cmds @@ exntos exn
4494 | _ ->
4495 adderrfmt "remote command"
4496 "error processing remote command: %S\n" cmds
4498 let remote =
4499 let scratch = Bytes.create 80 in
4500 let buf = Buffer.create 80 in
4501 fun fd ->
4502 match tempfailureretry (Unix.read fd scratch 0) 80 with
4503 | exception Unix.Unix_error (Unix.EAGAIN, _, _) -> None
4504 | 0 ->
4505 Unix.close fd;
4506 if Buffer.length buf > 0
4507 then (
4508 let s = Buffer.contents buf in
4509 Buffer.clear buf;
4510 ract s;
4512 None
4513 | n ->
4514 let rec eat ppos =
4515 let nlpos =
4516 match Bytes.index_from scratch ppos '\n' with
4517 | exception Not_found -> -1
4518 | pos -> if pos >= n then -1 else pos
4520 if nlpos >= 0
4521 then (
4522 Buffer.add_subbytes buf scratch ppos (nlpos-ppos);
4523 let s = Buffer.contents buf in
4524 Buffer.clear buf;
4525 ract s;
4526 eat (nlpos+1);
4528 else (
4529 Buffer.add_subbytes buf scratch ppos (n-ppos);
4530 Some fd
4532 in eat 0
4534 let remoteopen path =
4535 try Some (Unix.openfile path [Unix.O_NONBLOCK; Unix.O_RDONLY] 0o0)
4536 with exn ->
4537 adderrfmt "remoteopen" "error opening %S: %s" path @@ exntos exn;
4538 None
4540 let () =
4541 vlogf := (fun s -> if conf.verbose then print_endline s else ignore s);
4542 S.redirstderr := not @@ Unix.isatty Unix.stderr;
4543 let gc = ref false in
4544 let rcmdpath = ref E.s in
4545 let dcfpath = ref E.s in
4546 let pageno = ref None in
4547 let openlast = ref false in
4548 let doreap = ref false in
4549 let csspath = ref None in
4550 let justversion = ref false in
4551 S.selfexec := Sys.executable_name;
4552 let spec =
4553 [("-p", Arg.Set_string S.password, "<password> Set password");
4554 ("-f", Arg.String
4555 (fun s ->
4556 S.fontpath := s;
4557 S.selfexec := !S.selfexec ^ " -f " ^ Filename.quote s;
4558 ), "<path> Set path to the user interface font");
4559 ("-c", Arg.String
4560 (fun s ->
4561 S.selfexec := !S.selfexec ^ " -c " ^ Filename.quote s;
4562 S.confpath := s), "<path> Set path to the configuration file");
4563 ("-last", Arg.Set openlast, " Open last document");
4564 ("-page", Arg.Int (fun pageno1 -> pageno := Some (pageno1-1)),
4565 "<page-number> Jump to page");
4566 ("-dest", Arg.Set_string S.nameddest,
4567 "<dest-name> Set named destination");
4568 ("-remote", Arg.Set_string rcmdpath,
4569 "<path> Set path to the remote fifo");
4570 ("-gc", Arg.Set gc, " Collect garbage");
4571 ("-v", Arg.Set justversion, " Print version and exit");
4572 ("-css", Arg.String (fun s -> csspath := Some s),
4573 "<path> Set path to the style sheet to use with EPUB/HTML");
4574 ("-origin", Arg.Set_string S.origin, "<origin> <undocumented>");
4575 ("-no-title", Arg.Set S.ignoredoctitlte, " Ignore document title");
4576 ("-dcf", Arg.Set_string dcfpath, "<path> <undocumented>");
4577 ("-flip-stderr-redirection",
4578 Arg.Unit (fun () -> S.redirstderr := not !S.redirstderr),
4579 " <undocumented>");
4580 ("-mime", Arg.Set_string S.mimetype, "<mime-type> <undocumented>")
4583 Arg.parse (Arg.align spec) (fun s -> S.path := s)
4584 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\nOptions:");
4586 if !S.confpath == E.s
4587 then (
4588 let dir =
4589 let dir = Filename.concat home ".config" in
4590 if try Sys.is_directory dir with _ -> false then dir else home
4592 S.confpath := Filename.concat dir "llpp.conf"
4595 if !justversion
4596 then Printf.(
4597 printf "%s\nconfiguration file: %s\n" (Help.version ()) !S.confpath;
4598 exit 0
4601 let histmode = emptystr !S.path && not !openlast in
4603 if !gc
4604 then (
4605 Config.gc ();
4606 if histmode then exit 0;
4609 if not (Config.load !openlast)
4610 then dolog "failed to load configuration";
4612 if nonemptystr !dcfpath
4613 then conf.dcf <- !dcfpath;
4615 begin match !pageno with
4616 | Some pageno -> S.anchor := (pageno, 0.0, 0.0)
4617 | None -> ()
4618 end;
4620 fillhelp ();
4621 let mu =
4622 object (self)
4623 val mutable m_clicks = 0
4624 val mutable m_click_x = 0
4625 val mutable m_click_y = 0
4626 val mutable m_lastclicktime = infinity
4628 method private cleanup =
4629 S.roamf := noroamf;
4630 Hashtbl.iter (fun _ opaque -> Ffi.clearmark opaque) S.pagemap
4631 method expose = Glutils.postRedisplay "expose"
4632 method visible v =
4633 let name =
4634 match v with
4635 | Wsi.Unobscured -> "unobscured"
4636 | Wsi.PartiallyObscured -> "partiallyobscured"
4637 | Wsi.FullyObscured -> "fullyobscured"
4639 vlog "visibility change %s" name
4640 method display = display ()
4641 method map mapped = vlog "mapped %b" mapped
4642 method reshape w h =
4643 self#cleanup;
4644 reshape w h
4645 method mouse b d x y m =
4646 (*http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx*)
4647 m_click_x <- x;
4648 setuioh @@
4649 if d && canselect ()
4650 then (
4651 m_click_y <- y;
4652 if b = 1
4653 then (
4654 let t = now () in
4655 if abs x - m_click_x > 10
4656 || abs y - m_click_y > 10
4657 || abs_float (t -. m_lastclicktime) > 0.3
4658 then m_clicks <- 0;
4659 m_clicks <- m_clicks + 1;
4660 m_lastclicktime <- t;
4661 if m_clicks = 1
4662 then (
4663 self#cleanup;
4664 Glutils.postRedisplay "cleanup";
4665 !S.uioh#button b d x y m
4667 else !S.uioh#multiclick m_clicks x y m
4669 else (
4670 self#cleanup;
4671 m_clicks <- 0;
4672 m_lastclicktime <- infinity;
4673 !S.uioh#button b d x y m
4676 else !S.uioh#button b d x y m
4677 method motion x y =
4678 S.mpos := (x, y);
4679 !S.uioh#motion x y |> setuioh
4680 method pmotion x y =
4681 S.mpos := (x, y);
4682 !S.uioh#pmotion x y |> setuioh
4683 method key k m =
4684 vlog "k=%#x m=%#x" k m;
4685 let mascm = m land (
4686 Wsi.altmask + Wsi.shiftmask + Wsi.ctrlmask + Wsi.metamask
4687 ) in
4688 let keyboard k m =
4689 let x = !S.x and y = !S.y in
4690 keyboard k m;
4691 if x != !S.x || y != !S.y then self#cleanup
4693 match !S.keystate with
4694 | KSnone ->
4695 let km = k, mascm in
4696 begin
4697 match
4698 let modehash = !S.uioh#modehash in
4699 try Hashtbl.find modehash km
4700 with Not_found ->
4701 try Hashtbl.find (findkeyhash conf "global") km
4702 with Not_found -> KMinsrt (k, m)
4703 with
4704 | KMinsrt (k, m) -> keyboard k m
4705 | KMinsrl l -> List.iter (fun (k, m) -> keyboard k m) l
4706 | KMmulti (l, r) -> S.keystate := KSinto (l, r)
4708 | KSinto ((k', m') :: [], insrt) when k'=k && m' land mascm = m' ->
4709 List.iter (fun (k, m) -> keyboard k m) insrt;
4710 S.keystate := KSnone
4711 | KSinto ((k', m') :: keys, insrt) when k'=k && m' land mascm = m' ->
4712 S.keystate := KSinto (keys, insrt)
4713 | KSinto _ -> S.keystate := KSnone
4714 method enter x y =
4715 S.mpos := (x, y);
4716 !S.uioh#pmotion x y |> setuioh
4717 method leave = S.mpos := (-1, -1)
4718 method winstate wsl = S.winstate := wsl
4719 method quit : 'a. 'a = raise Quit
4720 method scroll dx dy =
4721 !S.uioh#scroll dx dy |> setuioh
4722 method zoom z x y = !S.uioh#zoom z x y
4723 method opendoc path =
4724 S.mode := View;
4725 setuioh uioh;
4726 Glutils.postRedisplay "opendoc";
4727 opendoc path !S.mimetype !S.password
4730 let wsfd, winw, winh = Wsi.init mu conf.cwinw conf.cwinh in
4731 S.wsfd := wsfd;
4733 let cs, ss =
4734 match Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 with
4735 | exception exn ->
4736 dolog "socketpair failed: %s" @@ exntos exn;
4737 exit 1
4738 | (r, w) ->
4739 Unix.set_close_on_exec r;
4740 Unix.set_close_on_exec w;
4741 r, w
4744 begin match !csspath with
4745 | None -> ()
4746 | Some "" -> conf.css <- E.s
4747 | Some path ->
4748 let css = filecontents path in
4749 let l = String.length css in
4750 conf.css <-
4751 if l > 1 && substratis css (l-2) "\r\n"
4752 then String.sub css 0 (l-2)
4753 else (if l > 0 && css.[l-1] = '\n' then String.sub css 0 (l-1) else css)
4754 end;
4755 S.stderr := Ffi.init cs (
4756 conf.angle, conf.fitmodel, (conf.trimmargins, conf.trimfuzz),
4757 conf.texcount, conf.sliceheight, conf.mustoresize,
4758 conf.colorspace, !S.fontpath, !S.redirstderr
4760 List.iter GlArray.enable [`texture_coord; `vertex];
4761 GlTex.env (`color conf.texturecolor);
4762 S.ss := ss;
4763 reshape ~firsttime:true winw winh;
4764 setuioh uioh;
4765 if histmode
4766 then (Wsi.settitle "previously visited - llpp"; enterhistmode ())
4767 else opendoc !S.path !S.mimetype !S.password;
4768 display ();
4769 Wsi.mapwin ();
4770 Wsi.setcursor Wsi.CURSOR_INHERIT;
4771 Sys.set_signal Sys.sighup (Sys.Signal_handle (fun _ -> reload ()));
4773 let rec reap () =
4774 match Unix.waitpid [Unix.WNOHANG] ~-1 with
4775 | exception (Unix.Unix_error (Unix.ECHILD, _, _)) -> ()
4776 | exception exn -> dolog "Unix.waitpid: %s" @@ exntos exn
4777 | 0, _ -> ()
4778 | _pid, _status -> reap ()
4780 Sys.set_signal Sys.sigchld (Sys.Signal_handle (fun _ -> doreap := true));
4782 let optrfd =
4783 ref (if nonemptystr !rcmdpath then remoteopen !rcmdpath else None)
4785 if !S.redirstderr
4786 then dologf := (adderrfmt "stderr" "%s\n");
4788 let fdl =
4789 let l = [!S.ss; !S.wsfd] in if !S.redirstderr then !S.stderr :: l else l
4791 let rec loop deadline =
4792 if !doreap
4793 then (
4794 doreap := false;
4795 reap ()
4797 let r =
4798 match !optrfd with
4799 | None -> fdl
4800 | Some fd -> fd :: fdl
4802 if !Glutils.redisplay
4803 then (
4804 Glutils.redisplay := false;
4805 display ();
4807 let timeout =
4808 let now = now () in
4809 if deadline > now
4810 then (
4811 if deadline = infinity
4812 then ~-.1.0
4813 else max 0.0 (deadline -. now)
4815 else 0.0
4817 let r, _, _ =
4818 try Unix.select r [] [] timeout
4819 with Unix.Unix_error (Unix.EINTR, _, _) -> [], [], []
4821 begin match r with
4822 | [] ->
4823 let newdeadline =
4824 match !S.autoscroll with
4825 | Some step when step != 0 ->
4826 let y = !S.y + step in
4827 let fy = if conf.maxhfit then !S.winh else 0 in
4828 let y =
4829 if y < 0
4830 then !S.maxy - fy
4831 else
4832 if y >= !S.maxy - fy
4833 then 0
4834 else y
4836 gotoxy !S.x y;
4837 deadline +. 0.01
4838 | _ -> infinity
4840 loop newdeadline
4842 | l ->
4843 let rec checkfds = function
4844 | [] -> ()
4845 | fd :: rest when fd = !S.ss ->
4846 let cmd = Ffi.rcmd !S.ss in
4847 act cmd;
4848 checkfds rest
4850 | fd :: rest when fd = !S.wsfd ->
4851 Wsi.readresp fd;
4852 checkfds rest
4854 | fd :: rest when fd = !S.stderr ->
4855 let b = Bytes.create 80 in
4856 begin match Unix.read fd b 0 80 with
4857 | exception Unix.Unix_error (Unix.EINTR, _, _) -> ()
4858 | exception exn -> adderrmsg "Unix.read exn" @@ exntos exn
4859 | 0 -> ()
4860 | n -> adderrmsg "stderr" @@ Bytes.sub_string b 0 n
4861 end;
4862 checkfds rest
4864 | fd :: rest when Some fd = !optrfd ->
4865 begin match remote fd with
4866 | None -> optrfd := remoteopen !rcmdpath;
4867 | opt -> optrfd := opt
4868 end;
4869 checkfds rest
4871 | _ :: rest ->
4872 adderrmsg "mainloop" "select returned unknown descriptor";
4873 checkfds rest
4875 checkfds l;
4876 let newdeadline =
4877 match !S.autoscroll with
4878 | Some step when step != 0 ->
4879 if deadline = infinity
4880 then now () +. 0.01
4881 else deadline
4882 | _ -> infinity
4884 loop newdeadline
4885 end;
4887 match loop infinity with
4888 | exception Quit ->
4889 (match Buffer.length S.errmsgs with
4890 | 0 -> ()
4891 | n ->
4892 match Unix.write Unix.stdout (Buffer.to_bytes S.errmsgs) 0 n with
4893 | exception _ | _ -> ());
4894 Config.save leavebirdseye;
4895 if Ffi.hasunsavedchanges ()
4896 then save ()
4897 | _ -> error "umpossible - infinity reached"