Allow saving current parameters as defaults
[llpp.git] / main.ml
blob4802b11a2d59944cac8bb1a1151fecc1a7cf9588
1 type under =
2 | Unone
3 | Ulinkuri of string
4 | Ulinkgoto of (int * int)
5 | Utext of facename
6 and facename = string;;
8 let dolog fmt = Printf.kprintf prerr_endline fmt;;
10 type params = angle * proportional * texcount * sliceheight
11 and pageno = int
12 and width = int
13 and height = int
14 and leftx = int
15 and opaque = string
16 and recttype = int
17 and pixmapsize = int
18 and angle = int
19 and proportional = bool
20 and interpagespace = int
21 and texcount = int
22 and sliceheight = int
23 and gen = int
24 and top = float
27 external init : Unix.file_descr -> params -> unit = "ml_init";;
28 external draw : (int * int * int * int * bool) -> string -> unit = "ml_draw";;
29 external seltext : string -> (int * int * int * int) -> int -> unit =
30 "ml_seltext";;
31 external copysel : string -> unit = "ml_copysel";;
32 external getpdimrect : int -> float array = "ml_getpdimrect";;
33 external whatsunder : string -> int -> int -> under = "ml_whatsunder";;
34 external zoomforh : int -> int -> int -> float = "ml_zoom_for_height";;
36 type mpos = int * int
37 and mstate =
38 | Msel of (mpos * mpos)
39 | Mpan of mpos
40 | Mscroll
41 | Mzoom of (int * int)
42 | Mnone
45 type textentry = string * string * onhist * onkey * ondone
46 and onkey = string -> int -> te
47 and ondone = string -> unit
48 and histcancel = unit -> unit
49 and onhist = ((histcmd -> string) * histcancel) option
50 and histcmd = HCnext | HCprev | HCfirst | HClast
51 and te =
52 | TEstop
53 | TEdone of string
54 | TEcont of string
55 | TEswitch of textentry
58 type 'a circbuf =
59 { store : 'a array
60 ; mutable rc : int
61 ; mutable wc : int
62 ; mutable len : int
66 let cbnew n v =
67 { store = Array.create n v
68 ; rc = 0
69 ; wc = 0
70 ; len = 0
74 let cbcap b = Array.length b.store;;
76 let cbput b v =
77 let cap = cbcap b in
78 b.store.(b.wc) <- v;
79 b.wc <- (b.wc + 1) mod cap;
80 b.rc <- b.wc;
81 b.len <- min (b.len + 1) cap;
84 let cbempty b = b.len = 0;;
86 let cbgetg b circular dir =
87 if cbempty b
88 then b.store.(0)
89 else
90 let rc = b.rc + dir in
91 let rc =
92 if circular
93 then (
94 if rc = -1
95 then b.len-1
96 else (
97 if rc = b.len
98 then 0
99 else rc
102 else max 0 (min rc (b.len-1))
104 b.rc <- rc;
105 b.store.(rc);
108 let cbget b = cbgetg b false;;
109 let cbgetc b = cbgetg b true;;
111 let cbpeek b =
112 let rc = b.wc - b.len in
113 let rc = if rc < 0 then cbcap b + rc else rc in
114 b.store.(rc);
117 let cbdecr b = b.len <- b.len - 1;;
119 type layout =
120 { pageno : int
121 ; pagedimno : int
122 ; pagew : int
123 ; pageh : int
124 ; pagedispy : int
125 ; pagey : int
126 ; pagevh : int
127 ; pagex : int
131 type conf =
132 { mutable scrollw : int
133 ; mutable scrollh : int
134 ; mutable icase : bool
135 ; mutable preload : bool
136 ; mutable pagebias : int
137 ; mutable verbose : bool
138 ; mutable scrollstep : int
139 ; mutable maxhfit : bool
140 ; mutable crophack : bool
141 ; mutable autoscrollstep : int
142 ; mutable showall : bool
143 ; mutable hlinks : bool
144 ; mutable underinfo : bool
145 ; mutable interpagespace : interpagespace
146 ; mutable zoom : float
147 ; mutable presentation : bool
148 ; mutable angle : angle
149 ; mutable winw : int
150 ; mutable winh : int
151 ; mutable savebmarks : bool
152 ; mutable proportional : proportional
153 ; mutable memlimit : int
154 ; mutable texcount : texcount
155 ; mutable sliceheight : sliceheight
156 ; mutable thumbw : width
157 ; mutable jumpback : bool
158 ; mutable bgcolor : float * float * float
159 ; mutable bedefault : bool
163 type outline = string * int * int * float;;
164 type outlines =
165 | Oarray of outline array
166 | Olist of outline list
167 | Onarrow of string * outline array * outline array
170 type rect = float * float * float * float * float * float * float * float;;
172 type pagemapkey = pageno * width * angle * proportional * gen;;
174 type anchor = pageno * top;;
176 let emptyanchor = (0, 0.0);;
177 let initialanchor = (-1, nan);;
179 type mode =
180 | Birdseye of (conf * leftx * pageno * pageno * anchor)
181 | Outline of (bool * int * int * outline array * string * int * mode)
182 | Items of (int * int * item array * string * int * mode)
183 | Textentry of (textentry * onleave)
184 | View
185 and onleave = leavetextentrystatus -> unit
186 and leavetextentrystatus = | Cancel | Confirm
187 and item = string * int * action
188 and action =
189 | Noaction
190 | Action of (int -> int -> string -> int -> mode)
193 let isbirdseye = function Birdseye _ -> true | _ -> false;;
194 let istextentry = function Textentry _ -> true | _ -> false;;
196 type state =
197 { mutable csock : Unix.file_descr
198 ; mutable ssock : Unix.file_descr
199 ; mutable w : int
200 ; mutable x : int
201 ; mutable y : int
202 ; mutable anchor : anchor
203 ; mutable maxy : int
204 ; mutable layout : layout list
205 ; pagemap : (pagemapkey, (opaque * pixmapsize)) Hashtbl.t
206 ; mutable pdims : (pageno * width * height * leftx) list
207 ; mutable pagecount : int
208 ; pagecache : string circbuf
209 ; mutable rendering : bool
210 ; mutable mstate : mstate
211 ; mutable searchpattern : string
212 ; mutable rects : (pageno * recttype * rect) list
213 ; mutable rects1 : (pageno * recttype * rect) list
214 ; mutable text : string
215 ; mutable fullscreen : (width * height) option
216 ; mutable mode : mode
217 ; mutable outlines : outlines
218 ; mutable bookmarks : outline list
219 ; mutable path : string
220 ; mutable password : string
221 ; mutable invalidated : int
222 ; mutable colorscale : float
223 ; mutable memused : int
224 ; mutable gen : gen
225 ; mutable throttle : layout list option
226 ; mutable ascrollstep : int
227 ; mutable help : item array
228 ; mutable docinfo : (int * string) list
229 ; hists : hists
231 and hists =
232 { pat : string circbuf
233 ; pag : string circbuf
234 ; nav : anchor circbuf
238 let defconf =
239 { scrollw = 7
240 ; scrollh = 12
241 ; icase = true
242 ; preload = true
243 ; pagebias = 0
244 ; verbose = false
245 ; scrollstep = 24
246 ; maxhfit = true
247 ; crophack = false
248 ; autoscrollstep = 24
249 ; showall = false
250 ; hlinks = false
251 ; underinfo = false
252 ; interpagespace = 2
253 ; zoom = 1.0
254 ; presentation = false
255 ; angle = 0
256 ; winw = 900
257 ; winh = 900
258 ; savebmarks = true
259 ; proportional = true
260 ; memlimit = 32*1024*1024
261 ; texcount = 256
262 ; sliceheight = 24
263 ; thumbw = 76
264 ; jumpback = false
265 ; bgcolor = (0.5, 0.5, 0.5)
266 ; bedefault = false
270 let conf = { defconf with angle = defconf.angle };;
272 let makehelp () =
273 let strings = ("llpp version " ^ Help.version) :: "" :: Help.keys in
274 Array.of_list (List.map (fun s -> s, 0, Noaction) strings);
277 let state =
278 { csock = Unix.stdin
279 ; ssock = Unix.stdin
280 ; x = 0
281 ; y = 0
282 ; anchor = initialanchor
283 ; w = 0
284 ; layout = []
285 ; maxy = max_int
286 ; pagemap = Hashtbl.create 10
287 ; pagecache = cbnew 100 ""
288 ; pdims = []
289 ; pagecount = 0
290 ; rendering = false
291 ; mstate = Mnone
292 ; rects = []
293 ; rects1 = []
294 ; text = ""
295 ; mode = View
296 ; fullscreen = None
297 ; searchpattern = ""
298 ; outlines = Olist []
299 ; bookmarks = []
300 ; path = ""
301 ; password = ""
302 ; invalidated = 0
303 ; hists =
304 { nav = cbnew 100 (0, 0.0)
305 ; pat = cbnew 20 ""
306 ; pag = cbnew 10 ""
308 ; colorscale = 1.0
309 ; memused = 0
310 ; gen = 0
311 ; throttle = None
312 ; ascrollstep = 0
313 ; help = makehelp ()
314 ; docinfo = []
318 let vlog fmt =
319 if conf.verbose
320 then
321 Printf.kprintf prerr_endline fmt
322 else
323 Printf.kprintf ignore fmt
326 let writecmd fd s =
327 let len = String.length s in
328 let n = 4 + len in
329 let b = Buffer.create n in
330 Buffer.add_char b (Char.chr ((len lsr 24) land 0xff));
331 Buffer.add_char b (Char.chr ((len lsr 16) land 0xff));
332 Buffer.add_char b (Char.chr ((len lsr 8) land 0xff));
333 Buffer.add_char b (Char.chr ((len lsr 0) land 0xff));
334 Buffer.add_string b s;
335 let s' = Buffer.contents b in
336 let n' = Unix.write fd s' 0 n in
337 if n' != n then failwith "write failed";
340 let readcmd fd =
341 let s = "xxxx" in
342 let n = Unix.read fd s 0 4 in
343 if n != 4 then failwith "incomplete read(len)";
344 let len = 0
345 lor (Char.code s.[0] lsl 24)
346 lor (Char.code s.[1] lsl 16)
347 lor (Char.code s.[2] lsl 8)
348 lor (Char.code s.[3] lsl 0)
350 let s = String.create len in
351 let n = Unix.read fd s 0 len in
352 if n != len then failwith "incomplete read(data)";
356 let makecmd s l =
357 let b = Buffer.create 10 in
358 Buffer.add_string b s;
359 let rec combine = function
360 | [] -> b
361 | x :: xs ->
362 Buffer.add_char b ' ';
363 let s =
364 match x with
365 | `b b -> if b then "1" else "0"
366 | `s s -> s
367 | `i i -> string_of_int i
368 | `f f -> string_of_float f
369 | `I f -> string_of_int (truncate f)
371 Buffer.add_string b s;
372 combine xs;
374 combine l;
377 let wcmd s l =
378 let cmd = Buffer.contents (makecmd s l) in
379 writecmd state.csock cmd;
382 let calcips h =
383 if conf.presentation
384 then
385 let d = conf.winh - h in
386 max 0 ((d + 1) / 2)
387 else
388 conf.interpagespace
391 let calcheight () =
392 let rec f pn ph pi fh l =
393 match l with
394 | (n, _, h, _) :: rest ->
395 let ips = calcips h in
396 let fh =
397 if conf.presentation
398 then fh+ips
399 else (
400 if isbirdseye state.mode && pn = 0
401 then fh + ips
402 else fh
405 let fh = fh + ((n - pn) * (ph + pi)) in
406 f n h ips fh rest;
408 | [] ->
409 let inc =
410 if conf.presentation || (isbirdseye state.mode && pn = 0)
411 then 0
412 else -pi
414 let fh = fh + ((state.pagecount - pn) * (ph + pi)) + inc in
415 max 0 fh
417 let fh = f 0 0 0 0 state.pdims in
421 let getpageyh pageno =
422 let rec f pn ph pi y l =
423 match l with
424 | (n, _, h, _) :: rest ->
425 let ips = calcips h in
426 if n >= pageno
427 then
428 let h = if n = pageno then h else ph in
429 if conf.presentation && n = pageno
430 then
431 y + (pageno - pn) * (ph + pi) + pi, h
432 else
433 y + (pageno - pn) * (ph + pi), h
434 else
435 let y = y + (if conf.presentation then pi else 0) in
436 let y = y + (n - pn) * (ph + pi) in
437 f n h ips y rest
439 | [] ->
440 y + (pageno - pn) * (ph + pi), ph
442 f 0 0 0 0 state.pdims
445 let getpagey pageno = fst (getpageyh pageno);;
447 let layout y sh =
448 let rec f ~pageno ~pdimno ~prev ~py ~dy ~pdims ~cacheleft ~accu =
449 let ((w, h, ips, x) as curr), rest, pdimno, yinc =
450 match pdims with
451 | (pageno', w, h, x) :: rest when pageno' = pageno ->
452 let ips = calcips h in
453 let yinc =
454 if conf.presentation || (isbirdseye state.mode && pageno = 0)
455 then ips
456 else 0
458 (w, h, ips, x), rest, pdimno + 1, yinc
459 | _ ->
460 prev, pdims, pdimno, 0
462 let dy = dy + yinc in
463 let py = py + yinc in
464 if pageno = state.pagecount || cacheleft = 0 || dy >= sh
465 then
466 accu
467 else
468 let vy = y + dy in
469 if py + h <= vy - yinc
470 then
471 let py = py + h + ips in
472 let dy = max 0 (py - y) in
473 f ~pageno:(pageno+1)
474 ~pdimno
475 ~prev:curr
478 ~pdims:rest
479 ~cacheleft
480 ~accu
481 else
482 let pagey = vy - py in
483 let pagevh = h - pagey in
484 let pagevh = min (sh - dy) pagevh in
485 let off = if yinc > 0 then py - vy else 0 in
486 let py = py + h + ips in
487 let e =
488 { pageno = pageno
489 ; pagedimno = pdimno
490 ; pagew = w
491 ; pageh = h
492 ; pagedispy = dy + off
493 ; pagey = pagey + off
494 ; pagevh = pagevh - off
495 ; pagex = x
498 let accu = e :: accu in
499 f ~pageno:(pageno+1)
500 ~pdimno
501 ~prev:curr
503 ~dy:(dy+pagevh+ips)
504 ~pdims:rest
505 ~cacheleft:(cacheleft-1)
506 ~accu
508 if state.invalidated = 0
509 then (
510 let accu =
512 ~pageno:0
513 ~pdimno:~-1
514 ~prev:(0,0,0,0)
515 ~py:0
516 ~dy:0
517 ~pdims:state.pdims
518 ~cacheleft:(cbcap state.pagecache)
519 ~accu:[]
521 List.rev accu
523 else
527 let clamp incr =
528 let y = state.y + incr in
529 let y = max 0 y in
530 let y = min y (state.maxy - (if conf.maxhfit then conf.winh else 0)) in
534 let getopaque pageno =
535 try Some (Hashtbl.find state.pagemap
536 (pageno, state.w, conf.angle, conf.proportional, state.gen))
537 with Not_found -> None
540 let cache pageno opaque =
541 Hashtbl.replace state.pagemap
542 (pageno, state.w, conf.angle, conf.proportional, state.gen) opaque
545 let validopaque opaque = String.length opaque > 0;;
547 let render l =
548 match getopaque l.pageno with
549 | None when not state.rendering ->
550 state.rendering <- true;
551 cache l.pageno ("", -1);
552 wcmd "render" [`i (l.pageno + 1)
553 ;`i l.pagedimno
554 ;`i l.pagew
555 ;`i l.pageh];
556 | _ -> ()
559 let loadlayout layout =
560 let rec f all = function
561 | l :: ls ->
562 begin match getopaque l.pageno with
563 | None -> render l; f false ls
564 | Some (opaque, _) -> f (all && validopaque opaque) ls
566 | [] -> all
568 f (layout <> []) layout;
571 let findpageforopaque opaque =
572 Hashtbl.fold
573 (fun k (v, s) a -> if v = opaque then Some (k, s) else a)
574 state.pagemap None
577 let pagevisible layout n = List.exists (fun l -> l.pageno = n) layout;;
579 let preload () =
580 let oktopreload =
581 if conf.preload
582 then
583 let memleft = conf.memlimit - state.memused in
584 if memleft < 0
585 then
586 let opaque = cbpeek state.pagecache in
587 match findpageforopaque opaque with
588 | Some ((n, _, _, _, _), size) ->
589 memleft + size >= 0 && not (pagevisible state.layout n)
590 | None -> false
591 else true
592 else false
594 if oktopreload
595 then
596 let presentation = conf.presentation in
597 let interpagespace = conf.interpagespace in
598 let maxy = state.maxy in
599 conf.presentation <- false;
600 conf.interpagespace <- 0;
601 state.maxy <- calcheight ();
602 let y =
603 match state.layout with
604 | [] -> 0
605 | l :: _ -> getpagey l.pageno + l.pagey
607 let y = if y < conf.winh then 0 else y - conf.winh in
608 let pages = layout y (conf.winh*3) in
609 List.iter render pages;
610 conf.presentation <- presentation;
611 conf.interpagespace <- interpagespace;
612 state.maxy <- maxy;
615 let gotoy y =
616 let y = max 0 y in
617 let y = min state.maxy y in
618 let pages = layout y conf.winh in
619 let ready = loadlayout pages in
620 if conf.showall
621 then (
622 if ready
623 then (
624 state.y <- y;
625 state.layout <- pages;
626 state.throttle <- None;
627 Glut.postRedisplay ();
629 else (
630 state.throttle <- Some pages;
633 else (
634 state.y <- y;
635 state.layout <- pages;
636 state.throttle <- None;
637 Glut.postRedisplay ();
639 begin match state.mode with
640 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
641 if not (pagevisible pages pageno)
642 then (
643 match state.layout with
644 | [] -> ()
645 | l :: _ ->
646 state.mode <- Birdseye (conf, leftx, l.pageno, hooverpageno, anchor)
648 | _ -> ()
649 end;
650 preload ();
653 let gotoy_and_clear_text y =
654 gotoy y;
655 if not conf.verbose then state.text <- "";
658 let getanchor () =
659 match state.layout with
660 | [] -> emptyanchor
661 | l :: _ -> (l.pageno, float l.pagey /. float l.pageh)
664 let getanchory (n, top) =
665 let y, h = getpageyh n in
666 y + (truncate (top *. float h));
669 let gotoanchor anchor =
670 gotoy (getanchory anchor);
673 let addnav () =
674 cbput state.hists.nav (getanchor ());
677 let getnav () =
678 let anchor = cbgetc state.hists.nav ~-1 in
679 getanchory anchor;
682 let gotopage n top =
683 let y, h = getpageyh n in
684 gotoy_and_clear_text (y + (truncate (top *. float h)));
687 let gotopage1 n top =
688 let y = getpagey n in
689 gotoy_and_clear_text (y + top);
692 let invalidate () =
693 state.layout <- [];
694 state.pdims <- [];
695 state.rects <- [];
696 state.rects1 <- [];
697 state.invalidated <- state.invalidated + 1;
700 let scalecolor c =
701 let c = c *. state.colorscale in
702 (c, c, c);
705 let scalecolor2 (r, g, b) =
706 (r *. state.colorscale, g *. state.colorscale, b *. state.colorscale);
709 let represent () =
710 state.maxy <- calcheight ();
711 match state.mode with
712 | Birdseye (_, _, pageno, _, _) ->
713 let y, h = getpageyh pageno in
714 let top = (conf.winh - h) / 2 in
715 gotoy (max 0 (y - top))
716 | _ -> gotoanchor state.anchor
719 let pagematrix () =
720 GlMat.mode `projection;
721 GlMat.load_identity ();
722 GlMat.rotate ~x:1.0 ~angle:180.0 ();
723 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
724 GlMat.scale3 (2.0 /. float state.w, 2.0 /. float conf.winh, 1.0);
725 if state.x != 0
726 then (
727 GlMat.translate ~x:(float state.x) ();
731 let winmatrix () =
732 GlMat.mode `projection;
733 GlMat.load_identity ();
734 GlMat.rotate ~x:1.0 ~angle:180.0 ();
735 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
736 GlMat.scale3 (2.0 /. float conf.winw, 2.0 /. float conf.winh, 1.0);
739 let reshape ~w ~h =
740 if state.invalidated = 0 && state.anchor == initialanchor
741 then state.anchor <- getanchor ();
743 conf.winw <- w;
744 let w = truncate (float w *. conf.zoom) - conf.scrollw in
745 let w = max w 2 in
746 state.w <- w;
747 conf.winh <- h;
748 GlMat.mode `modelview;
749 GlMat.load_identity ();
750 GlClear.color (scalecolor 1.0);
751 GlClear.clear [`color];
753 invalidate ();
754 wcmd "geometry" [`i w; `i h];
757 let showtext c s =
758 GlDraw.color (0.0, 0.0, 0.0);
759 GlDraw.rect
760 (0.0, float (conf.winh - 18))
761 (float (conf.winw - conf.scrollw - 1), float conf.winh)
763 let font = Glut.BITMAP_8_BY_13 in
764 GlDraw.color (1.0, 1.0, 1.0);
765 GlPix.raster_pos ~x:0.0 ~y:(float (conf.winh - 5)) ();
766 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s;
769 let enttext () =
770 let len = String.length state.text in
771 match state.mode with
772 | Textentry ((prefix, text, _, _, _), _) ->
773 let s =
774 match String.length prefix with
775 | 0 | 1 ->
776 if len > 0
777 then
778 Printf.sprintf "%s%s^ [%s]" prefix text state.text
779 else
780 Printf.sprintf "%s%s^" prefix text
782 | _ ->
783 if len > 0
784 then
785 Printf.sprintf "%s: %s^ [%s]" prefix text state.text
786 else
787 Printf.sprintf "%s: %s^" prefix text
789 showtext ' ' s;
791 | _ ->
792 if len > 0 then showtext ' ' state.text
795 let showtext c s =
796 state.text <- Printf.sprintf "%c%s" c s;
797 Glut.postRedisplay ();
800 let act cmd =
801 match cmd.[0] with
802 | 'c' ->
803 state.pdims <- [];
805 | 'D' ->
806 state.rects <- state.rects1;
807 Glut.postRedisplay ()
809 | 'C' ->
810 let n = Scanf.sscanf cmd "C %u" (fun n -> n) in
811 state.pagecount <- n;
812 state.invalidated <- state.invalidated - 1;
813 if state.invalidated = 0
814 then represent ()
816 | 't' ->
817 let s = Scanf.sscanf cmd "t %n"
818 (fun n -> String.sub cmd n (String.length cmd - n))
820 Glut.setWindowTitle s
822 | 'T' ->
823 let s = Scanf.sscanf cmd "T %n"
824 (fun n -> String.sub cmd n (String.length cmd - n))
826 if istextentry state.mode
827 then (
828 state.text <- s;
829 showtext ' ' s;
831 else (
832 state.text <- s;
833 Glut.postRedisplay ();
836 | 'V' ->
837 if conf.verbose
838 then
839 let s = Scanf.sscanf cmd "V %n"
840 (fun n -> String.sub cmd n (String.length cmd - n))
842 state.text <- s;
843 showtext ' ' s;
845 | 'F' ->
846 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
847 Scanf.sscanf cmd "F %u %d %f %f %f %f %f %f %f %f"
848 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
849 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
851 let y = (getpagey pageno) + truncate y0 in
852 addnav ();
853 gotoy y;
854 state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)]
856 | 'R' ->
857 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
858 Scanf.sscanf cmd "R %u %d %f %f %f %f %f %f %f %f"
859 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
860 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
862 state.rects1 <-
863 (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
865 | 'r' ->
866 let n, w, h, r, l, s, p =
867 Scanf.sscanf cmd "r %u %u %u %d %d %u %s"
868 (fun n w h r l s p ->
869 (n-1, w, h, r, l != 0, s, p))
872 Hashtbl.replace state.pagemap (n, w, r, l, state.gen) (p, s);
873 state.memused <- state.memused + s;
875 let layout =
876 match state.throttle with
877 | None -> state.layout
878 | Some layout -> layout
881 let rec gc () =
882 if (state.memused <= conf.memlimit) || cbempty state.pagecache
883 then ()
884 else (
885 let evictedopaque = cbpeek state.pagecache in
886 match findpageforopaque evictedopaque with
887 | None -> failwith "bug in gc"
888 | Some ((evictedn, _, _, _, gen) as k, evictedsize) ->
889 if state.gen != gen || not (pagevisible layout evictedn)
890 then (
891 wcmd "free" [`s evictedopaque];
892 state.memused <- state.memused - evictedsize;
893 Hashtbl.remove state.pagemap k;
894 cbdecr state.pagecache;
895 gc ();
899 gc ();
901 cbput state.pagecache p;
902 state.rendering <- false;
904 begin match state.throttle with
905 | None ->
906 if pagevisible state.layout n
907 then gotoy state.y
908 else (
909 let allvisible = loadlayout state.layout in
910 if allvisible then preload ();
913 | Some layout ->
914 match layout with
915 | [] -> ()
916 | l :: _ ->
917 let y = getpagey l.pageno + l.pagey in
918 gotoy y
921 | 'l' ->
922 let (n, w, h, x) as pdim =
923 Scanf.sscanf cmd "l %u %u %u %u" (fun n w h x -> n, w, h, x)
925 state.pdims <- pdim :: state.pdims
927 | 'o' ->
928 let (l, n, t, h, pos) =
929 Scanf.sscanf cmd "o %u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos)
931 let s = String.sub cmd pos (String.length cmd - pos) in
932 let s =
933 let l = String.length s in
934 let b = Buffer.create (String.length s) in
935 let rec loop pc2 i =
936 if i = l
937 then ()
938 else
939 let pc2 =
940 match s.[i] with
941 | '\xa0' when pc2 -> Buffer.add_char b ' '; false
942 | '\xc2' -> true
943 | c ->
944 let c = if Char.code c land 0x80 = 0 then c else '?' in
945 Buffer.add_char b c;
946 false
948 loop pc2 (i+1)
950 loop false 0;
951 Buffer.contents b
953 let outline = (s, l, n, float t /. float h) in
954 let outlines =
955 match state.outlines with
956 | Olist outlines -> Olist (outline :: outlines)
957 | Oarray _ -> Olist [outline]
958 | Onarrow _ -> Olist [outline]
960 state.outlines <- outlines
963 | 'i' ->
964 let s = Scanf.sscanf cmd "i %n"
965 (fun n -> String.sub cmd n (String.length cmd - n))
967 let len = String.length s in
968 let rec fold accu pos =
969 let eolpos =
970 try String.index_from s pos '\n' with Not_found -> len
972 if eolpos = len
973 then List.rev accu
974 else
975 let line = String.sub s pos (eolpos - pos) in
976 fold ((1, line)::accu) (eolpos+1)
978 state.docinfo <- fold state.docinfo 0
980 | _ ->
981 dolog "unknown cmd `%S'" cmd
984 let now = Unix.gettimeofday;;
986 let idle () =
987 let rec loop delay =
988 let r, _, _ = Unix.select [state.csock] [] [] delay in
989 begin match r with
990 | [] ->
991 if state.ascrollstep > 0
992 then begin
993 let y = state.y + state.ascrollstep in
994 let y = if y >= state.maxy then 0 else y in
995 gotoy y;
996 state.text <- "";
997 end;
999 | _ ->
1000 let cmd = readcmd state.csock in
1001 act cmd;
1002 loop 0.0
1003 end;
1004 in loop 0.001
1007 let onhist cb =
1008 let rc = cb.rc in
1009 let action = function
1010 | HCprev -> cbget cb ~-1
1011 | HCnext -> cbget cb 1
1012 | HCfirst -> cbget cb ~-(cb.rc)
1013 | HClast -> cbget cb (cb.len - 1 - cb.rc)
1014 and cancel () = cb.rc <- rc
1015 in (action, cancel)
1018 let search pattern forward =
1019 if String.length pattern > 0
1020 then
1021 let pn, py =
1022 match state.layout with
1023 | [] -> 0, 0
1024 | l :: _ ->
1025 l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
1027 let cmd =
1028 let b = makecmd "search"
1029 [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0)]
1031 Buffer.add_char b ',';
1032 Buffer.add_string b pattern;
1033 Buffer.add_char b '\000';
1034 Buffer.contents b;
1036 writecmd state.csock cmd;
1039 let intentry text key =
1040 let c = Char.unsafe_chr key in
1041 match c with
1042 | '0' .. '9' ->
1043 let s = "x" in s.[0] <- c;
1044 let text = text ^ s in
1045 TEcont text
1047 | _ ->
1048 state.text <- Printf.sprintf "invalid char (%d, `%c')" key c;
1049 TEcont text
1052 let addchar s c =
1053 let b = Buffer.create (String.length s + 1) in
1054 Buffer.add_string b s;
1055 Buffer.add_char b c;
1056 Buffer.contents b;
1059 let textentry text key =
1060 let c = Char.unsafe_chr key in
1061 match c with
1062 | _ when key >= 32 && key < 127 ->
1063 let text = addchar text c in
1064 TEcont text
1066 | _ ->
1067 dolog "unhandled key %d char `%c'" key (Char.unsafe_chr key);
1068 TEcont text
1071 let reinit angle proportional =
1072 conf.angle <- angle;
1073 conf.proportional <- proportional;
1074 invalidate ();
1075 wcmd "reinit" [`i angle; `b proportional];
1078 let setzoom zoom =
1079 let zoom = max 0.01 (min 2.2 zoom) in
1080 if zoom <> conf.zoom
1081 then (
1082 if zoom <= 1.0
1083 then state.x <- 0;
1084 conf.zoom <- zoom;
1085 reshape conf.winw conf.winh;
1086 state.text <- Printf.sprintf "zoom is now %-5.1f" (zoom *. 100.0);
1090 let enterbirdseye () =
1091 let zoom = float conf.thumbw /. float conf.winw in
1092 let birdseyepageno =
1093 let rec fold candidate = function
1094 | [] -> candidate
1095 | l :: _ when l.pagey = 0 -> l.pageno
1096 | l :: rest -> fold l.pageno rest
1098 fold 0 state.layout
1100 state.mode <- Birdseye (
1101 { conf with zoom = conf.zoom }, state.x, birdseyepageno, -1, getanchor ()
1103 conf.zoom <- zoom;
1104 conf.presentation <- false;
1105 conf.interpagespace <- 10;
1106 conf.hlinks <- false;
1107 state.x <- 0;
1108 state.mstate <- Mnone;
1109 conf.showall <- false;
1110 Glut.setCursor Glut.CURSOR_INHERIT;
1111 if conf.verbose
1112 then
1113 state.text <- Printf.sprintf "birds eye mode on (zoom %3.1f%%)"
1114 (100.0*.zoom)
1115 else
1116 state.text <- ""
1118 reshape conf.winw conf.winh;
1121 let leavebirdseye (c, leftx, pageno, _, anchor) goback =
1122 state.mode <- View;
1123 conf.zoom <- c.zoom;
1124 conf.presentation <- c.presentation;
1125 conf.interpagespace <- c.interpagespace;
1126 conf.showall <- c.showall;
1127 conf.hlinks <- c.hlinks;
1128 state.x <- leftx;
1129 if conf.verbose
1130 then
1131 state.text <- Printf.sprintf "birds eye mode off (zoom %3.1f%%)"
1132 (100.0*.conf.zoom)
1134 reshape conf.winw conf.winh;
1135 state.anchor <- if goback then anchor else (pageno, 0.0);
1138 let togglebirdseye () =
1139 match state.mode with
1140 | Birdseye vals -> leavebirdseye vals true
1141 | View | Outline _ -> enterbirdseye ()
1142 | _ -> ()
1145 let upbirdseye (conf, leftx, pageno, hooverpageno, anchor) =
1146 let pageno = max 0 (pageno - 1) in
1147 let rec loop = function
1148 | [] -> gotopage1 pageno 0
1149 | l :: _ when l.pageno = pageno ->
1150 if l.pagedispy >= 0 && l.pagey = 0
1151 then Glut.postRedisplay ()
1152 else gotopage1 pageno 0
1153 | _ :: rest -> loop rest
1155 loop state.layout;
1156 state.mode <- Birdseye (conf, leftx, pageno, hooverpageno, anchor)
1159 let downbirdseye (conf, leftx, pageno, hooverpageno, anchor) =
1160 let pageno = min (state.pagecount - 1) (pageno + 1) in
1161 state.mode <- Birdseye (conf, leftx, pageno, hooverpageno, anchor);
1162 let rec loop = function
1163 | [] ->
1164 let y, h = getpageyh pageno in
1165 let dy = (y - state.y) - (conf.winh - h - conf.interpagespace) in
1166 gotoy (clamp dy)
1167 | l :: rest when l.pageno = pageno ->
1168 if l.pagevh != l.pageh
1169 then gotoy (clamp (l.pageh - l.pagevh + conf.interpagespace))
1170 else Glut.postRedisplay ()
1171 | l :: rest -> loop rest
1173 loop state.layout
1176 let optentry mode text key =
1177 let btos b = if b then "on" else "off" in
1178 let c = Char.unsafe_chr key in
1179 match c with
1180 | 's' ->
1181 let ondone s =
1182 try conf.scrollstep <- int_of_string s with exc ->
1183 state.text <- Printf.sprintf "bad integer `%s': %s"
1184 s (Printexc.to_string exc)
1186 TEswitch ("scroll step", "", None, intentry, ondone)
1188 | 'A' ->
1189 let ondone s =
1191 conf.autoscrollstep <- int_of_string s;
1192 if state.ascrollstep > 0
1193 then state.ascrollstep <- conf.autoscrollstep;
1194 with exc ->
1195 state.text <- Printf.sprintf "bad integer `%s': %s"
1196 s (Printexc.to_string exc)
1198 TEswitch ("auto scroll step", "", None, intentry, ondone)
1200 | 'Z' ->
1201 let ondone s =
1203 let zoom = float (int_of_string s) /. 100.0 in
1204 setzoom zoom
1205 with exc ->
1206 state.text <- Printf.sprintf "bad integer `%s': %s"
1207 s (Printexc.to_string exc)
1209 TEswitch ("zoom", "", None, intentry, ondone)
1211 | 't' ->
1212 let ondone s =
1214 conf.thumbw <- max 2 (min 1920 (int_of_string s));
1215 state.text <-
1216 Printf.sprintf "thumbnail width is set to %d" conf.thumbw;
1217 begin match mode with
1218 | Birdseye beye ->
1219 leavebirdseye beye false;
1220 enterbirdseye ();
1221 | _ -> ();
1223 with exc ->
1224 state.text <- Printf.sprintf "bad integer `%s': %s"
1225 s (Printexc.to_string exc)
1227 TEswitch ("thumbnail width", "", None, intentry, ondone)
1229 | 'R' ->
1230 let ondone s =
1231 match try
1232 Some (int_of_string s)
1233 with exc ->
1234 state.text <- Printf.sprintf "bad integer `%s': %s"
1235 s (Printexc.to_string exc);
1236 None
1237 with
1238 | Some angle -> reinit angle conf.proportional
1239 | None -> ()
1241 TEswitch ("rotation", "", None, intentry, ondone)
1243 | 'i' ->
1244 conf.icase <- not conf.icase;
1245 TEdone ("case insensitive search " ^ (btos conf.icase))
1247 | 'p' ->
1248 conf.preload <- not conf.preload;
1249 gotoy state.y;
1250 TEdone ("preload " ^ (btos conf.preload))
1252 | 'v' ->
1253 conf.verbose <- not conf.verbose;
1254 TEdone ("verbose " ^ (btos conf.verbose))
1256 | 'h' ->
1257 conf.maxhfit <- not conf.maxhfit;
1258 state.maxy <- state.maxy + (if conf.maxhfit then -conf.winh else conf.winh);
1259 TEdone ("maxhfit " ^ (btos conf.maxhfit))
1261 | 'c' ->
1262 conf.crophack <- not conf.crophack;
1263 TEdone ("crophack " ^ btos conf.crophack)
1265 | 'a' ->
1266 conf.showall <- not conf.showall;
1267 TEdone ("showall " ^ btos conf.showall)
1269 | 'f' ->
1270 conf.underinfo <- not conf.underinfo;
1271 TEdone ("underinfo " ^ btos conf.underinfo)
1273 | 'P' ->
1274 conf.savebmarks <- not conf.savebmarks;
1275 TEdone ("persistent bookmarks " ^ btos conf.savebmarks)
1277 | 'S' ->
1278 let ondone s =
1280 let pageno, py =
1281 match state.layout with
1282 | [] -> 0, 0
1283 | l :: _ ->
1284 l.pageno, l.pagey
1286 conf.interpagespace <- int_of_string s;
1287 state.maxy <- calcheight ();
1288 let y = getpagey pageno in
1289 gotoy (y + py)
1290 with exc ->
1291 state.text <- Printf.sprintf "bad integer `%s': %s"
1292 s (Printexc.to_string exc)
1294 TEswitch ("vertical margin", "", None, intentry, ondone)
1296 | 'l' ->
1297 reinit conf.angle (not conf.proportional);
1298 TEdone ("proprortional display " ^ btos conf.proportional)
1300 | _ ->
1301 state.text <- Printf.sprintf "bad option %d `%c'" key c;
1302 TEstop
1305 let maxoutlinerows () = (conf.winh - 31) / 16;;
1307 let enterselector allowdel outlines errmsg msg =
1308 if Array.length outlines = 0
1309 then (
1310 showtext ' ' errmsg;
1312 else (
1313 state.text <- msg;
1314 Glut.setCursor Glut.CURSOR_INHERIT;
1315 let pageno =
1316 match state.layout with
1317 | [] -> -1
1318 | {pageno=pageno} :: rest -> pageno
1320 let active =
1321 let rec loop n =
1322 if n = Array.length outlines
1323 then 0
1324 else
1325 let (_, _, outlinepageno, _) = outlines.(n) in
1326 if outlinepageno >= pageno then n else loop (n+1)
1328 loop 0
1330 state.mode <- Outline
1331 (allowdel, active, max 0 (active - maxoutlinerows () / 2), outlines, "", 0,
1332 state.mode);
1333 Glut.postRedisplay ();
1337 let enteroutlinemode () =
1338 let outlines, msg =
1339 match state.outlines with
1340 | Oarray a -> a, ""
1341 | Olist l ->
1342 let a = Array.of_list (List.rev l) in
1343 state.outlines <- Oarray a;
1344 a, ""
1345 | Onarrow (pat, a, b) ->
1346 a, "Outline was narrowed to `" ^ pat ^ "' (Ctrl-u to restore)"
1348 enterselector false outlines "Document has no outline" msg;
1351 let enterbookmarkmode () =
1352 let bookmarks = Array.of_list state.bookmarks in
1353 enterselector true bookmarks "Document has no bookmarks (yet)" "";
1356 let mode_to_string mode =
1357 let b = Buffer.create 10 in
1358 let rec f = function
1359 | Textentry (_, _) -> Buffer.add_string b "Textentry ";
1360 | View -> Buffer.add_string b "View"
1361 | Birdseye _ -> Buffer.add_string b "Birdseye"
1362 | Items _ -> Buffer.add_string b "Items"
1363 | Outline _ -> Buffer.add_string b "Outline"
1365 f mode;
1366 Buffer.contents b;
1369 let enterinfomode () =
1370 let btos = function true -> "on" | _ -> "off" in
1371 let mode = state.mode in
1372 let rec makeitems () =
1373 let intp name get set =
1374 Printf.sprintf "%-24s %d" name (get ()), 1, Action (
1375 fun active first qsearch pan ->
1376 let ondone s =
1377 let n =
1378 try int_of_string s
1379 with exn ->
1380 state.text <- Printf.sprintf "bad integer `%s': %s"
1381 s (Printexc.to_string exn);
1382 max_int;
1384 if n != max_int then set n;
1386 let te = name, "", None, intentry, ondone in
1387 state.text <- "";
1388 Textentry (
1390 fun _ ->
1391 state.mode <- Items (active, first, makeitems (), "", 0, mode)
1394 and boolp name get set =
1395 Printf.sprintf "%-24s %s" name (btos (get ())), 1, Action (
1396 fun active first qsearch pan ->
1397 let v = get () in
1398 set (not v);
1399 Items (active, first, makeitems (), qsearch, pan, mode);
1403 let items = [
1404 "Setup", 0, Noaction;
1406 boolp "presentation"
1407 (fun () -> conf.presentation)
1408 (fun v ->
1409 conf.presentation <- v;
1410 state.anchor <- getanchor ();
1411 represent ());
1413 boolp "ignore case in searches"
1414 (fun () -> conf.icase)
1415 (fun v -> conf.icase <- v);
1417 boolp "preload"
1418 (fun () -> conf.preload)
1419 (fun v -> conf.preload <- v);
1421 boolp "verbose"
1422 (fun () -> conf.verbose)
1423 (fun v -> conf.verbose <- v);
1425 boolp "max fit"
1426 (fun () -> conf.maxhfit)
1427 (fun v -> conf.maxhfit <- v);
1429 boolp "crop hack"
1430 (fun () -> conf.crophack)
1431 (fun v -> conf.crophack <- v);
1433 boolp "throttle"
1434 (fun () -> conf.showall)
1435 (fun v -> conf.showall <- v);
1437 boolp "highlight links"
1438 (fun () -> conf.hlinks)
1439 (fun v -> conf.hlinks <- v);
1441 boolp "under info"
1442 (fun () -> conf.underinfo)
1443 (fun v -> conf.underinfo <- v);
1444 boolp "persistent bookmarks"
1445 (fun () -> conf.savebmarks)
1446 (fun v -> conf.savebmarks <- v);
1448 boolp "proportional display"
1449 (fun () -> conf.proportional)
1450 (fun v -> reinit conf.angle (not conf.proportional));
1452 boolp "persistent location"
1453 (fun () -> conf.jumpback)
1454 (fun v -> conf.jumpback <- v);
1456 "", 0, Noaction;
1458 intp "vertical margin"
1459 (fun () -> conf.interpagespace)
1460 (fun n ->
1461 conf.interpagespace <- n;
1462 let pageno, py =
1463 match state.layout with
1464 | [] -> 0, 0
1465 | l :: _ ->
1466 l.pageno, l.pagey
1468 state.maxy <- calcheight ();
1469 let y = getpagey pageno in
1470 gotoy (y + py)
1473 intp "page bias"
1474 (fun () -> conf.pagebias)
1475 (fun v -> conf.pagebias <- v);
1477 intp "scroll step"
1478 (fun () -> conf.scrollstep)
1479 (fun n -> conf.scrollstep <- n);
1481 intp "auto scroll step"
1482 (fun () ->
1483 if state.ascrollstep > 0
1484 then state.ascrollstep
1485 else conf.autoscrollstep)
1486 (fun n ->
1487 if state.ascrollstep > 0
1488 then state.ascrollstep <- n
1489 else conf.autoscrollstep <- n);
1491 intp "zoom"
1492 (fun () -> truncate (conf.zoom *. 100.))
1493 (fun v -> setzoom ((float v) /. 100.));
1495 intp "rotation"
1496 (fun () -> conf.angle)
1497 (fun v -> reinit v conf.proportional);
1499 intp "scroll bar width"
1500 (fun () -> conf.scrollw)
1501 (fun v ->
1502 conf.scrollw <- v;
1503 reshape conf.winw conf.winh;
1506 intp "scroll handle height"
1507 (fun () -> conf.scrollh)
1508 (fun v -> conf.scrollh <- v;);
1510 intp "thumbnail width"
1511 (fun () -> conf.thumbw)
1512 (fun v ->
1513 conf.thumbw <- min 1920 v;
1514 match mode with
1515 | Birdseye beye ->
1516 leavebirdseye beye false;
1517 enterbirdseye ()
1518 | _ -> ()
1521 "", 0, Noaction;
1522 "Pixmap Cache", 0, Noaction;
1524 intp "size (advisory)"
1525 (fun () -> conf.memlimit)
1526 (fun v -> conf.memlimit <- v);
1527 Printf.sprintf "%-24s %d" "used" state.memused, 1, Noaction;
1529 "", 0, Noaction;
1530 "Window", 0, Noaction;
1531 Printf.sprintf "dimensions %dx%d" conf.winw conf.winh, 1, Noaction;
1533 "", 0, Noaction;
1535 Printf.sprintf "Save these parameters as defaults at exit (%s)"
1536 (btos conf.bedefault),
1538 Action (
1539 fun active first qsearch pan ->
1540 conf.bedefault <- not conf.bedefault;
1541 Items (active, first, makeitems (), qsearch, pan, mode);
1545 "", 0, Noaction;
1546 "Document", 0, Noaction;
1549 Array.of_list
1550 (items @ List.map (fun (_, s) -> (s, 1, Noaction)) state.docinfo);
1552 state.text <- "";
1553 state.mode <- Items (1, 0, makeitems (), "", 0, mode);
1554 Glut.postRedisplay ();
1557 let enterhelpmode () =
1558 state.mode <- Items (0, 0, state.help, "", 0, state.mode);
1559 Glut.postRedisplay ();
1562 let quickbookmark ?title () =
1563 match state.layout with
1564 | [] -> ()
1565 | l :: _ ->
1566 let title =
1567 match title with
1568 | None ->
1569 let sec = Unix.gettimeofday () in
1570 let tm = Unix.localtime sec in
1571 Printf.sprintf "Quick (page %d) (bookmarked at %d/%d/%d %d:%d)"
1572 (l.pageno+1)
1573 tm.Unix.tm_mday
1574 tm.Unix.tm_mon
1575 (tm.Unix.tm_year + 1900)
1576 tm.Unix.tm_hour
1577 tm.Unix.tm_min
1578 | Some title -> title
1580 state.bookmarks <-
1581 (title, 0, l.pageno, float l.pagey /. float l.pageh) :: state.bookmarks
1584 let doreshape w h =
1585 state.fullscreen <- None;
1586 Glut.reshapeWindow w h;
1589 let writeopen path password =
1590 writecmd state.csock ("open " ^ path ^ "\000" ^ state.password ^ "\000");
1591 writecmd state.csock "info";
1594 let opendoc path password =
1595 invalidate ();
1596 state.path <- path;
1597 state.password <- password;
1598 state.gen <- state.gen + 1;
1600 writeopen path password;
1601 Glut.setWindowTitle ("llpp " ^ Filename.basename path);
1602 wcmd "geometry" [`i state.w; `i conf.winh];
1605 let viewkeyboard ~key ~x ~y =
1606 let enttext te =
1607 let mode = state.mode in
1608 state.mode <- Textentry (te, fun _ -> state.mode <- mode);
1609 state.text <- "";
1610 enttext ();
1611 Glut.postRedisplay ()
1613 let c = Char.chr key in
1614 match c with
1615 | '\027' | 'q' ->
1616 exit 0
1618 | '\008' ->
1619 let y = getnav () in
1620 gotoy_and_clear_text y
1622 | 'o' ->
1623 enteroutlinemode ()
1625 | 'u' ->
1626 state.rects <- [];
1627 state.text <- "";
1628 Glut.postRedisplay ()
1630 | '/' | '?' ->
1631 let ondone isforw s =
1632 cbput state.hists.pat s;
1633 state.searchpattern <- s;
1634 search s isforw
1636 let s = String.create 1 in
1637 s.[0] <- c;
1638 enttext (s, "", Some (onhist state.hists.pat),
1639 textentry, ondone (c ='/'))
1641 | '+' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1642 let incr = if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01 in
1643 setzoom (min 2.2 (conf.zoom +. incr))
1645 | '+' ->
1646 let ondone s =
1647 let n =
1648 try int_of_string s with exc ->
1649 state.text <- Printf.sprintf "bad integer `%s': %s"
1650 s (Printexc.to_string exc);
1651 max_int
1653 if n != max_int
1654 then (
1655 conf.pagebias <- n;
1656 state.text <- "page bias is now " ^ string_of_int n;
1659 enttext ("page bias", "", None, intentry, ondone)
1661 | '-' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1662 let decr = if conf.zoom -. 0.1 < 0.1 then 0.01 else 0.1 in
1663 setzoom (max 0.01 (conf.zoom -. decr))
1665 | '-' ->
1666 let ondone msg =
1667 state.text <- msg;
1669 enttext ("option", "", None, optentry state.mode, ondone)
1671 | '0' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1672 setzoom 1.0
1674 | '1' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1675 let zoom = zoomforh conf.winw conf.winh conf.scrollw in
1676 if zoom < 1.0
1677 then setzoom zoom
1679 | '9' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1680 togglebirdseye ()
1682 | '0' .. '9' ->
1683 let ondone s =
1684 let n =
1685 try int_of_string s with exc ->
1686 state.text <- Printf.sprintf "bad integer `%s': %s"
1687 s (Printexc.to_string exc);
1690 if n >= 0
1691 then (
1692 addnav ();
1693 cbput state.hists.pag (string_of_int n);
1694 gotoy_and_clear_text (getpagey (n + conf.pagebias - 1))
1697 let pageentry text key =
1698 match Char.unsafe_chr key with
1699 | 'g' -> TEdone text
1700 | _ -> intentry text key
1702 let text = "x" in text.[0] <- c;
1703 enttext (":", text, Some (onhist state.hists.pag), pageentry, ondone)
1705 | 'b' ->
1706 conf.scrollw <- if conf.scrollw > 0 then 0 else defconf.scrollw;
1707 reshape conf.winw conf.winh;
1709 | 'l' ->
1710 conf.hlinks <- not conf.hlinks;
1711 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
1712 Glut.postRedisplay ()
1714 | 'a' ->
1715 if state.ascrollstep = 0
1716 then state.ascrollstep <- conf.autoscrollstep
1717 else (
1718 conf.autoscrollstep <- state.ascrollstep;
1719 state.ascrollstep <- 0;
1722 | 'P' ->
1723 conf.presentation <- not conf.presentation;
1724 showtext ' ' ("presentation mode " ^
1725 if conf.presentation then "on" else "off");
1726 state.anchor <- getanchor ();
1727 represent ()
1729 | 'f' ->
1730 begin match state.fullscreen with
1731 | None ->
1732 state.fullscreen <- Some (conf.winw, conf.winh);
1733 Glut.fullScreen ()
1734 | Some (w, h) ->
1735 state.fullscreen <- None;
1736 doreshape w h
1739 | 'g' ->
1740 gotoy_and_clear_text 0
1742 | 'n' ->
1743 search state.searchpattern true
1745 | 'p' | 'N' ->
1746 search state.searchpattern false
1748 | 't' ->
1749 begin match state.layout with
1750 | [] -> ()
1751 | l :: _ ->
1752 gotoy_and_clear_text (getpagey l.pageno)
1755 | ' ' ->
1756 begin match List.rev state.layout with
1757 | [] -> ()
1758 | l :: _ ->
1759 let pageno = min (l.pageno+1) (state.pagecount-1) in
1760 gotoy_and_clear_text (getpagey pageno)
1763 | '\127' ->
1764 begin match state.layout with
1765 | [] -> ()
1766 | l :: _ ->
1767 let pageno = max 0 (l.pageno-1) in
1768 gotoy_and_clear_text (getpagey pageno)
1771 | '=' ->
1772 let f (fn, ln) l =
1773 if fn = -1 then l.pageno, l.pageno else fn, l.pageno
1775 let fn, ln = List.fold_left f (-1, -1) state.layout in
1776 let s =
1777 let maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in
1778 let percent =
1779 if maxy <= 0
1780 then 100.
1781 else (100. *. (float state.y /. float maxy)) in
1782 if fn = ln
1783 then
1784 Printf.sprintf "Page %d of %d %.2f%%"
1785 (fn+1) state.pagecount percent
1786 else
1787 Printf.sprintf
1788 "Pages %d-%d of %d %.2f%%"
1789 (fn+1) (ln+1) state.pagecount percent
1791 showtext ' ' s;
1793 | 'w' ->
1794 begin match state.layout with
1795 | [] -> ()
1796 | l :: _ ->
1797 doreshape (l.pagew + conf.scrollw) l.pageh;
1798 Glut.postRedisplay ();
1801 | '\'' ->
1802 enterbookmarkmode ()
1804 | 'h' ->
1805 enterhelpmode ()
1807 | 'i' ->
1808 enterinfomode ()
1810 | 'm' ->
1811 let ondone s =
1812 match state.layout with
1813 | l :: _ ->
1814 state.bookmarks <-
1815 (s, 0, l.pageno, float l.pagey /. float l.pageh)
1816 :: state.bookmarks
1817 | _ -> ()
1819 enttext ("bookmark", "", None, textentry, ondone)
1821 | '~' ->
1822 quickbookmark ();
1823 showtext ' ' "Quick bookmark added";
1825 | 'z' ->
1826 begin match state.layout with
1827 | l :: _ ->
1828 let rect = getpdimrect l.pagedimno in
1829 let w, h =
1830 if conf.crophack
1831 then
1832 (truncate (1.8 *. (rect.(1) -. rect.(0))),
1833 truncate (1.2 *. (rect.(3) -. rect.(0))))
1834 else
1835 (truncate (rect.(1) -. rect.(0)),
1836 truncate (rect.(3) -. rect.(0)))
1838 if w != 0 && h != 0
1839 then
1840 doreshape (w + conf.scrollw) (h + conf.interpagespace)
1842 Glut.postRedisplay ();
1844 | [] -> ()
1847 | '<' | '>' ->
1848 reinit (conf.angle + (if c = '>' then 30 else -30)) conf.proportional
1850 | '[' | ']' ->
1851 state.colorscale <-
1852 max 0.0
1853 (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0);
1854 Glut.postRedisplay ()
1856 | 'k' ->
1857 begin match state.mode with
1858 | Birdseye beye -> upbirdseye beye
1859 | _ -> gotoy (clamp (-conf.scrollstep))
1862 | 'j' ->
1863 begin match state.mode with
1864 | Birdseye beye -> downbirdseye beye
1865 | _ -> gotoy (clamp conf.scrollstep)
1868 | 'r' -> opendoc state.path state.password
1870 | _ ->
1871 vlog "huh? %d %c" key (Char.chr key);
1874 let textentrykeyboard ~key ~x ~y ((c, text, opthist, onkey, ondone), onleave) =
1875 let enttext te =
1876 state.mode <- Textentry (te, onleave);
1877 state.text <- "";
1878 enttext ();
1879 Glut.postRedisplay ()
1881 match Char.unsafe_chr key with
1882 | '\008' ->
1883 let len = String.length text in
1884 if len = 0
1885 then (
1886 onleave Cancel;
1887 Glut.postRedisplay ();
1889 else (
1890 let s = String.sub text 0 (len - 1) in
1891 enttext (c, s, opthist, onkey, ondone)
1894 | '\r' | '\n' ->
1895 ondone text;
1896 onleave Confirm;
1897 Glut.postRedisplay ()
1899 | '\027' ->
1900 begin match opthist with
1901 | None -> ()
1902 | Some (_, onhistcancel) -> onhistcancel ()
1903 end;
1904 onleave Cancel;
1905 Glut.postRedisplay ()
1907 | _ ->
1908 begin match onkey text key with
1909 | TEdone text ->
1910 onleave Confirm;
1911 ondone text;
1912 Glut.postRedisplay ()
1914 | TEcont text ->
1915 enttext (c, text, opthist, onkey, ondone);
1917 | TEstop ->
1918 onleave Cancel;
1919 Glut.postRedisplay ()
1921 | TEswitch te ->
1922 state.mode <- Textentry (te, onleave);
1923 Glut.postRedisplay ()
1924 end;
1927 let birdseyekeyboard ~key ~x ~y ((_, _, pageno, _, anchor) as beye) =
1928 match key with
1929 | 27 ->
1930 leavebirdseye beye true
1932 | 12 ->
1933 let y, h = getpageyh pageno in
1934 let top = (conf.winh - h) / 2 in
1935 gotoy (max 0 (y - top))
1937 | 13 ->
1938 leavebirdseye beye false
1940 | _ ->
1941 viewkeyboard ~key ~x ~y
1944 let itemskeyboard ~key ~x ~y (active, first, items, qsearch, pan, oldmode) =
1945 let set active first qsearch =
1946 state.mode <- Items (active, first, items, qsearch, pan, oldmode)
1948 let search active pattern incr =
1949 let dosearch re =
1950 let rec loop n =
1951 if n = Array.length items || n = -1
1952 then None
1953 else
1954 let (s, _, _) = items.(n) in
1956 (try ignore (Str.search_forward re s 0); true
1957 with Not_found -> false)
1958 then Some n
1959 else loop (n + incr)
1961 loop active
1964 let re = Str.regexp_case_fold pattern in
1965 dosearch re
1966 with Failure s ->
1967 state.text <- s;
1968 None
1970 let firstof active = max 0 (active - maxoutlinerows () / 2) in
1971 match key with
1972 | 18 | 19 ->
1973 let incr = if key = 18 then -1 else 1 in
1974 let active, first =
1975 match search (active + incr) qsearch incr with
1976 | None ->
1977 state.text <- qsearch ^ " [not found]";
1978 active, first
1979 | Some active ->
1980 state.text <- qsearch;
1981 active, firstof active
1983 set active first qsearch;
1984 Glut.postRedisplay ();
1986 | 8 ->
1987 let len = String.length qsearch in
1988 if len = 0
1989 then ()
1990 else (
1991 if len = 1
1992 then (
1993 state.text <- "";
1994 set active first "";
1996 else
1997 let qsearch = String.sub qsearch 0 (len - 1) in
1998 let active, first =
1999 match search active qsearch ~-1 with
2000 | None ->
2001 state.text <- qsearch ^ " [not found]";
2002 active, first
2003 | Some active ->
2004 state.text <- qsearch;
2005 active, firstof active
2007 set active first qsearch
2009 Glut.postRedisplay ()
2011 | _ when key >= 32 && key < 127 ->
2012 let pattern = addchar qsearch (Char.chr key) in
2013 let active, first =
2014 match search active pattern 1 with
2015 | None ->
2016 state.text <- pattern ^ " [not found]";
2017 active, first
2018 | Some active ->
2019 state.text <- pattern;
2020 active, firstof active
2022 set active first pattern;
2023 Glut.postRedisplay ()
2025 | 27 ->
2026 state.text <- "";
2027 state.mode <- oldmode;
2028 Glut.postRedisplay ();
2030 | 13 ->
2031 if active < Array.length items
2032 then (
2033 match items.(active) with
2034 | _, _, Action f ->
2035 state.mode <- f active first qsearch pan
2037 | _, _, Noaction ->
2038 state.text <- "";
2039 state.mode <- oldmode
2041 Glut.postRedisplay ();
2043 | _ -> dolog "unknown key %d" key
2046 let outlinekeyboard ~key ~x ~y
2047 (allowdel, active, first, outlines, qsearch, pan, oldmode) =
2048 let narrow outlines pattern =
2049 let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in
2050 match reopt with
2051 | None -> None
2052 | Some re ->
2053 let rec fold accu n =
2054 if n = -1
2055 then accu
2056 else
2057 let (s, _, _, _) as o = outlines.(n) in
2058 let accu =
2059 if (try ignore (Str.search_forward re s 0); true
2060 with Not_found -> false)
2061 then (o :: accu)
2062 else accu
2064 fold accu (n-1)
2066 let matched = fold [] (Array.length outlines - 1) in
2067 if matched = [] then None else Some (Array.of_list matched)
2069 let search active pattern incr =
2070 let dosearch re =
2071 let rec loop n =
2072 if n = Array.length outlines || n = -1
2073 then None
2074 else
2075 let (s, _, _, _) = outlines.(n) in
2077 (try ignore (Str.search_forward re s 0); true
2078 with Not_found -> false)
2079 then Some n
2080 else loop (n + incr)
2082 loop active
2085 let re = Str.regexp_case_fold pattern in
2086 dosearch re
2087 with Failure s ->
2088 state.text <- s;
2089 None
2091 let firstof active = max 0 (active - maxoutlinerows () / 2) in
2092 match key with
2093 | 27 ->
2094 if String.length qsearch = 0
2095 then (
2096 state.text <- "";
2097 state.mode <- oldmode;
2098 Glut.postRedisplay ();
2100 else (
2101 state.text <- "";
2102 state.mode <- Outline (
2103 allowdel, active, first, outlines, "", pan, oldmode
2105 Glut.postRedisplay ();
2108 | 18 | 19 ->
2109 let incr = if key = 18 then -1 else 1 in
2110 let active, first =
2111 match search (active + incr) qsearch incr with
2112 | None ->
2113 state.text <- qsearch ^ " [not found]";
2114 active, first
2115 | Some active ->
2116 state.text <- qsearch;
2117 active, firstof active
2119 state.mode <- Outline (
2120 allowdel, active, first, outlines, qsearch, pan, oldmode
2122 Glut.postRedisplay ();
2124 | 8 ->
2125 let len = String.length qsearch in
2126 if len = 0
2127 then ()
2128 else (
2129 if len = 1
2130 then (
2131 state.text <- "";
2132 state.mode <- Outline (
2133 allowdel, active, first, outlines, "", pan, oldmode
2136 else
2137 let qsearch = String.sub qsearch 0 (len - 1) in
2138 let active, first =
2139 match search active qsearch ~-1 with
2140 | None ->
2141 state.text <- qsearch ^ " [not found]";
2142 active, first
2143 | Some active ->
2144 state.text <- qsearch;
2145 active, firstof active
2147 state.mode <- Outline (
2148 allowdel, active, first, outlines, qsearch, pan, oldmode
2151 Glut.postRedisplay ()
2153 | 13 ->
2154 if active < Array.length outlines
2155 then (
2156 let (_, _, n, t) = outlines.(active) in
2157 addnav ();
2158 gotopage n t;
2160 state.text <- "";
2161 if allowdel then state.bookmarks <- Array.to_list outlines;
2162 state.mode <- oldmode;
2163 Glut.postRedisplay ();
2165 | _ when key >= 32 && key < 127 ->
2166 let pattern = addchar qsearch (Char.chr key) in
2167 let active, first =
2168 match search active pattern 1 with
2169 | None ->
2170 state.text <- pattern ^ " [not found]";
2171 active, first
2172 | Some active ->
2173 state.text <- pattern;
2174 active, firstof active
2176 state.mode <- Outline (
2177 allowdel, active, first, outlines, pattern, pan, oldmode
2179 Glut.postRedisplay ()
2181 | 14 when not allowdel -> (* ctrl-n *)
2182 if String.length qsearch > 0
2183 then (
2184 let optoutlines = narrow outlines qsearch in
2185 begin match optoutlines with
2186 | None -> state.text <- "can't narrow"
2187 | Some outlines ->
2188 state.mode <- Outline (
2189 allowdel, 0, 0, outlines, qsearch, pan, oldmode
2191 match state.outlines with
2192 | Olist l -> ()
2193 | Oarray a ->
2194 state.outlines <- Onarrow (qsearch, outlines, a)
2195 | Onarrow (pat, a, b) ->
2196 state.outlines <- Onarrow (qsearch, outlines, b)
2197 end;
2199 Glut.postRedisplay ()
2201 | 21 when not allowdel -> (* ctrl-u *)
2202 let outline =
2203 match state.outlines with
2204 | Oarray a -> a
2205 | Olist l ->
2206 let a = Array.of_list (List.rev l) in
2207 state.outlines <- Oarray a;
2209 | Onarrow (pat, a, b) ->
2210 state.outlines <- Oarray b;
2211 state.text <- "";
2214 state.mode <- Outline (allowdel, 0, 0, outline, qsearch, pan, oldmode);
2215 Glut.postRedisplay ()
2217 | 12 ->
2218 state.mode <- Outline
2219 (allowdel, active, firstof active, outlines, qsearch, pan, oldmode);
2220 Glut.postRedisplay ()
2222 | 127 when allowdel ->
2223 let len = Array.length outlines - 1 in
2224 if len = 0
2225 then (
2226 state.mode <- View;
2227 state.bookmarks <- [];
2229 else (
2230 let bookmarks = Array.init len
2231 (fun i ->
2232 let i = if i >= active then i + 1 else i in
2233 outlines.(i)
2236 state.mode <-
2237 Outline (
2238 allowdel,
2239 min active (len-1),
2240 min first (len-1),
2241 bookmarks, qsearch,
2243 oldmode
2246 Glut.postRedisplay ()
2248 | _ -> dolog "unknown key %d" key
2251 let keyboard ~key ~x ~y =
2252 if key = 7
2253 then
2254 wcmd "interrupt" []
2255 else
2256 match state.mode with
2257 | Outline outline -> outlinekeyboard ~key ~x ~y outline
2258 | Textentry textentry -> textentrykeyboard ~key ~x ~y textentry
2259 | Birdseye birdseye -> birdseyekeyboard ~key ~x ~y birdseye
2260 | View -> viewkeyboard ~key ~x ~y
2261 | Items items -> itemskeyboard ~key ~x ~y items
2264 let birdseyespecial key x y
2265 ((conf, leftx, pageno, hooverpageno, anchor) as beye) =
2266 match key with
2267 | Glut.KEY_UP -> upbirdseye beye
2268 | Glut.KEY_DOWN -> downbirdseye beye
2270 | Glut.KEY_PAGE_UP ->
2271 begin match state.layout with
2272 | l :: _ ->
2273 if l.pagey != 0
2274 then (
2275 state.mode <- Birdseye (
2276 conf, leftx, l.pageno, hooverpageno, anchor
2278 gotopage1 l.pageno 0;
2280 else (
2281 let layout = layout (state.y-conf.winh) conf.winh in
2282 match layout with
2283 | [] -> gotoy (clamp (-conf.winh))
2284 | l :: _ ->
2285 state.mode <- Birdseye (
2286 conf, leftx, l.pageno, hooverpageno, anchor
2288 gotopage1 l.pageno 0
2291 | [] -> gotoy (clamp (-conf.winh))
2292 end;
2294 | Glut.KEY_PAGE_DOWN ->
2295 begin match List.rev state.layout with
2296 | l :: _ ->
2297 let layout = layout (state.y + conf.winh) conf.winh in
2298 begin match layout with
2299 | [] ->
2300 let incr = l.pageh - l.pagevh in
2301 if incr = 0
2302 then (
2303 state.mode <-
2304 Birdseye (
2305 conf, leftx, state.pagecount - 1, hooverpageno, anchor
2307 Glut.postRedisplay ();
2309 else gotoy (clamp (incr + conf.interpagespace*2));
2311 | l :: _ ->
2312 state.mode <-
2313 Birdseye (conf, leftx, l.pageno, hooverpageno, anchor);
2314 gotopage1 l.pageno 0;
2317 | [] -> gotoy (clamp conf.winh)
2318 end;
2320 | Glut.KEY_HOME ->
2321 state.mode <- Birdseye (conf, leftx, 0, hooverpageno, anchor);
2322 gotopage1 0 0
2324 | Glut.KEY_END ->
2325 let pageno = state.pagecount - 1 in
2326 state.mode <- Birdseye (conf, leftx, pageno, hooverpageno, anchor);
2327 if not (pagevisible state.layout pageno)
2328 then
2329 let h =
2330 match List.rev state.pdims with
2331 | [] -> conf.winh
2332 | (_, _, h, _) :: _ -> h
2334 gotoy (max 0 (getpagey pageno - (conf.winh - h - conf.interpagespace)))
2335 else Glut.postRedisplay ();
2336 | _ -> ()
2339 let setautoscrollspeed goingdown =
2340 let incr = max 1 (state.ascrollstep / 2) in
2341 let astep = max 1 (state.ascrollstep + (if goingdown then incr else -incr)) in
2342 state.ascrollstep <- astep;
2345 let special ~key ~x ~y =
2346 match state.mode with
2347 | View | (Birdseye _) when key = Glut.KEY_F9 ->
2348 togglebirdseye ()
2350 | Birdseye vals ->
2351 birdseyespecial key x y vals
2353 | View when key = Glut.KEY_F1 ->
2354 enterhelpmode ()
2356 | View ->
2357 if state.ascrollstep > 0 && (key = Glut.KEY_DOWN || key = Glut.KEY_UP)
2358 then setautoscrollspeed (key = Glut.KEY_DOWN)
2359 else
2360 let y =
2361 match key with
2362 | Glut.KEY_F3 -> search state.searchpattern true; state.y
2363 | Glut.KEY_UP -> clamp (-conf.scrollstep)
2364 | Glut.KEY_DOWN -> clamp conf.scrollstep
2365 | Glut.KEY_PAGE_UP ->
2366 if Glut.getModifiers () land Glut.active_ctrl != 0
2367 then
2368 match state.layout with
2369 | [] -> state.y
2370 | l :: _ -> state.y - l.pagey
2371 else
2372 clamp (-conf.winh)
2373 | Glut.KEY_PAGE_DOWN ->
2374 if Glut.getModifiers () land Glut.active_ctrl != 0
2375 then
2376 match List.rev state.layout with
2377 | [] -> state.y
2378 | l :: _ -> getpagey l.pageno
2379 else
2380 clamp conf.winh
2381 | Glut.KEY_HOME -> addnav (); 0
2382 | Glut.KEY_END ->
2383 addnav ();
2384 state.maxy - (if conf.maxhfit then conf.winh else 0)
2386 | Glut.KEY_RIGHT when conf.zoom > 1.0 ->
2387 state.x <- state.x - 10;
2388 state.y
2389 | Glut.KEY_LEFT when conf.zoom > 1.0 ->
2390 state.x <- state.x + 10;
2391 state.y
2393 | _ -> state.y
2395 gotoy_and_clear_text y
2397 | Textentry
2398 ((c, s, (Some (action, _) as onhist), onkey, ondone), mode) ->
2399 let s =
2400 match key with
2401 | Glut.KEY_UP -> action HCprev
2402 | Glut.KEY_DOWN -> action HCnext
2403 | Glut.KEY_HOME -> action HCfirst
2404 | Glut.KEY_END -> action HClast
2405 | _ -> state.text
2407 state.mode <- Textentry ((c, s, onhist, onkey, ondone), mode);
2408 Glut.postRedisplay ()
2410 | Textentry _ -> ()
2412 | Items (active, first, items, qsearch, pan, oldmode) ->
2413 let maxrows = maxoutlinerows () in
2414 let itemcount = Array.length items in
2415 let hasaction = function
2416 | (_, _, Noaction) -> false
2417 | _ -> true
2419 let find start incr =
2420 let rec find i =
2421 if i = -1 || i = itemcount
2422 then -1
2423 else (
2424 if hasaction items.(i)
2425 then i
2426 else find (i + incr)
2429 find start
2431 let set active first =
2432 let first = max 0 (min first (itemcount - maxrows)) in
2433 state.mode <- Items (active, first, items, qsearch, pan, oldmode)
2435 let navigate incr =
2436 let isvisible first n = n >= first && n - first <= maxrows in
2437 let active, first =
2438 let incr1 = if incr > 0 then 1 else -1 in
2439 if isvisible first active
2440 then
2441 let next =
2442 let next = active + incr in
2443 let next =
2444 if next < 0 || next >= itemcount
2445 then -1
2446 else find next incr1
2448 if next = -1 || abs (active - next) > maxrows
2449 then -1
2450 else next
2452 if next = -1
2453 then
2454 let first = first + incr in
2455 let first = max 0 (min first (itemcount - 1)) in
2456 let next =
2457 let next = active + incr in
2458 let next = max 0 (min next (itemcount - 1)) in
2459 find next ~-incr1
2461 let active = if next = -1 then active else next in
2462 active, first
2463 else
2464 let first = min next first in
2465 next, first
2466 else
2467 let first = first + incr in
2468 let first = max 0 (min first (itemcount - 1)) in
2469 let active =
2470 let next = active + incr in
2471 let next = max 0 (min next (itemcount - 1)) in
2472 let next = find next incr1 in
2473 if next = -1 || abs (active - first) > maxrows
2474 then active
2475 else next
2477 active, first
2479 set active first;
2480 Glut.postRedisplay ()
2482 begin match key with
2483 | Glut.KEY_UP -> navigate ~-1
2484 | Glut.KEY_DOWN -> navigate 1
2485 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
2486 | Glut.KEY_PAGE_DOWN -> navigate maxrows
2488 | Glut.KEY_RIGHT ->
2489 state.mode <- Items (
2490 active, first, items, qsearch, min 0 (pan - 1), oldmode
2492 Glut.postRedisplay ()
2494 | Glut.KEY_LEFT ->
2495 state.mode <- Items (
2496 active, first, items, qsearch, min 0 (pan + 1), oldmode
2498 Glut.postRedisplay ()
2500 | Glut.KEY_HOME ->
2501 let active = find 0 1 in
2502 set active 0;
2503 Glut.postRedisplay ()
2505 | Glut.KEY_END ->
2506 let first = max 0 (itemcount - maxrows) in
2507 let active = find (itemcount - 1) ~-1 in
2508 set active first;
2509 Glut.postRedisplay ()
2511 | _ -> ()
2512 end;
2514 | Outline (allowdel, active, first, outlines, qsearch, pan, oldmode) ->
2515 let maxrows = maxoutlinerows () in
2516 let calcfirst first active =
2517 if active > first
2518 then
2519 let rows = active - first in
2520 if rows > maxrows then active - maxrows else first
2521 else active
2523 let navigate incr =
2524 let active = active + incr in
2525 let active = max 0 (min active (Array.length outlines - 1)) in
2526 let first = calcfirst first active in
2527 state.mode <- Outline (
2528 allowdel, active, first, outlines, qsearch, pan, oldmode
2530 Glut.postRedisplay ()
2532 let updownlevel incr =
2533 let len = Array.length outlines in
2534 let (_, curlevel, _, _) = outlines.(active) in
2535 let rec flow i =
2536 if i = len then i-1 else if i = -1 then 0 else
2537 let (_, l, _, _) = outlines.(i) in
2538 if l != curlevel then i else flow (i+incr)
2540 let active = flow active in
2541 let first = calcfirst first active in
2542 state.mode <- Outline (
2543 allowdel, active, first, outlines, qsearch, pan, oldmode
2545 Glut.postRedisplay ()
2547 match key with
2548 | Glut.KEY_UP -> navigate ~-1
2549 | Glut.KEY_DOWN -> navigate 1
2550 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
2551 | Glut.KEY_PAGE_DOWN -> navigate maxrows
2553 | Glut.KEY_RIGHT ->
2554 if Glut.getModifiers () land Glut.active_ctrl != 0
2555 then (
2556 state.mode <- Outline (
2557 allowdel, active, first, outlines,
2558 qsearch, min 0 (pan - 1), oldmode
2560 Glut.postRedisplay ();
2562 else (
2563 if not allowdel
2564 then updownlevel 1
2567 | Glut.KEY_LEFT ->
2568 if Glut.getModifiers () land Glut.active_ctrl != 0
2569 then (
2570 state.mode <- Outline (
2571 allowdel, active, first, outlines, qsearch, pan + 1, oldmode
2573 Glut.postRedisplay ();
2575 else (
2576 if not allowdel
2577 then updownlevel ~-1
2580 | Glut.KEY_HOME ->
2581 state.mode <- Outline (
2582 allowdel, 0, 0, outlines, qsearch, pan, oldmode
2584 Glut.postRedisplay ()
2586 | Glut.KEY_END ->
2587 let active = Array.length outlines - 1 in
2588 let first = max 0 (active - maxrows) in
2589 state.mode <- Outline (
2590 allowdel, active, first, outlines, qsearch, pan, oldmode
2592 Glut.postRedisplay ()
2594 | _ -> ()
2597 let drawplaceholder l =
2598 let margin = state.x + (conf.winw - (state.w + conf.scrollw)) / 2 in
2599 GlDraw.rect
2600 (float l.pagex, float l.pagedispy)
2601 (float (l.pagew + l.pagex), float (l.pagedispy + l.pagevh))
2603 let x = float (if margin < 0 then -margin else l.pagex)
2604 and y = float (l.pagedispy + 13) in
2605 let font = Glut.BITMAP_8_BY_13 in
2606 GlDraw.color (0.0, 0.0, 0.0);
2607 GlPix.raster_pos ~x ~y ();
2608 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c))
2609 ("Loading " ^ string_of_int (l.pageno + 1));
2612 let now () = Unix.gettimeofday ();;
2614 let drawpage l =
2615 let color =
2616 match state.mode with
2617 | Textentry _ -> scalecolor 0.4
2618 | View | Outline _ | Items _ -> scalecolor 1.0
2619 | Birdseye (_, _, pageno, hooverpageno, _) ->
2620 if l.pageno = hooverpageno
2621 then scalecolor 0.9
2622 else (
2623 if l.pageno = pageno
2624 then scalecolor 1.0
2625 else scalecolor 0.8
2628 GlDraw.color color;
2629 begin match getopaque l.pageno with
2630 | Some (opaque, _) when validopaque opaque ->
2631 let a = now () in
2632 draw (l.pagedispy, l.pagew, l.pagevh, l.pagey, conf.hlinks)
2633 opaque;
2634 let b = now () in
2635 let d = b-.a in
2636 vlog "draw %d %f sec" l.pageno d;
2638 | _ ->
2639 drawplaceholder l;
2640 end;
2643 let scrollph y =
2644 let maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in
2645 let sh = (float (maxy + conf.winh) /. float conf.winh) in
2646 let sh = float conf.winh /. sh in
2647 let sh = max sh (float conf.scrollh) in
2649 let percent =
2650 if state.y = state.maxy
2651 then 1.0
2652 else float y /. float maxy
2654 let position = (float conf.winh -. sh) *. percent in
2656 let position =
2657 if position +. sh > float conf.winh
2658 then float conf.winh -. sh
2659 else position
2661 position, sh;
2664 let scrollindicator () =
2665 GlDraw.color (0.64 , 0.64, 0.64);
2666 GlDraw.rect
2667 (float (conf.winw - conf.scrollw), 0.)
2668 (float conf.winw, float conf.winh)
2670 GlDraw.color (0.0, 0.0, 0.0);
2672 let position, sh = scrollph state.y in
2673 GlDraw.rect
2674 (float (conf.winw - conf.scrollw), position)
2675 (float conf.winw, position +. sh)
2679 let showsel margin =
2680 match state.mstate with
2681 | Mnone | Mscroll _ | Mpan _ | Mzoom _ ->
2684 | Msel ((x0, y0), (x1, y1)) ->
2685 let rec loop = function
2686 | l :: ls ->
2687 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
2688 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
2689 then
2690 match getopaque l.pageno with
2691 | Some (opaque, _) when validopaque opaque ->
2692 let oy = -l.pagey + l.pagedispy in
2693 seltext opaque
2694 (x0 - margin - state.x, y0,
2695 x1 - margin - state.x, y1) oy;
2697 | _ -> ()
2698 else loop ls
2699 | [] -> ()
2701 loop state.layout
2704 let showrects () =
2705 let panx = float state.x in
2706 Gl.enable `blend;
2707 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
2708 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2709 List.iter
2710 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
2711 List.iter (fun l ->
2712 if l.pageno = pageno
2713 then (
2714 let d = float (l.pagedispy - l.pagey) in
2715 GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5;
2716 GlDraw.begins `quads;
2718 GlDraw.vertex2 (x0+.panx, y0+.d);
2719 GlDraw.vertex2 (x1+.panx, y1+.d);
2720 GlDraw.vertex2 (x2+.panx, y2+.d);
2721 GlDraw.vertex2 (x3+.panx, y3+.d);
2723 GlDraw.ends ();
2725 ) state.layout
2726 ) state.rects
2728 Gl.disable `blend;
2731 let showoutline (allowdel, active, first, outlines, qsearch, pan, oldmode) =
2732 Gl.enable `blend;
2733 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2734 GlDraw.color (0., 0., 0.) ~alpha:0.85;
2735 GlDraw.rect (0., 0.) (float conf.winw, float conf.winh);
2736 Gl.disable `blend;
2738 GlDraw.color (1., 1., 1.);
2739 let font = Glut.BITMAP_9_BY_15 in
2740 let draw_string x y s =
2741 GlPix.raster_pos ~x ~y ();
2742 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
2744 let rec loop row =
2745 if row = Array.length outlines || (row - first) * 16 > conf.winh
2746 then ()
2747 else (
2748 let (s, level, _, _) = outlines.(row) in
2749 let y = (row - first) * 16 in
2750 let x = 5 + 15*(max 0 (level+pan)) in
2751 if row = active
2752 then (
2753 Gl.enable `blend;
2754 GlDraw.polygon_mode `both `line;
2755 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2756 GlDraw.color (1., 1., 1.) ~alpha:0.9;
2757 GlDraw.rect (0., float (y + 1))
2758 (float (conf.winw - 1), float (y + 18));
2759 GlDraw.polygon_mode `both `fill;
2760 Gl.disable `blend;
2761 GlDraw.color (1., 1., 1.);
2763 let draw_string s =
2764 let l = String.length s in
2765 if pan < 0
2766 then (
2767 let pan = pan * 2 in
2768 let left = l + pan in
2769 if left > 0
2770 then
2771 let s = String.sub s (-pan) left in
2772 draw_string (float x) (float (y + 16)) s
2774 else
2775 draw_string (float (x + pan*15)) (float (y + 16)) s
2777 draw_string s;
2778 loop (row+1)
2781 loop first
2784 let showitems (active, first, items, qsearch, pan, oldmode) =
2785 Gl.enable `blend;
2786 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2787 GlDraw.color (0., 0., 0.) ~alpha:0.90;
2788 GlDraw.rect (0., 0.) (float conf.winw, float conf.winh);
2789 Gl.disable `blend;
2791 GlDraw.color (1., 1., 1.);
2792 let font = Glut.BITMAP_9_BY_15 in
2793 let draw_string x y s =
2794 GlPix.raster_pos ~x ~y ();
2795 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
2797 let rec loop row =
2798 if row = Array.length items || (row - first) * 16 > conf.winh
2799 then ()
2800 else (
2801 let (s, l, a) = items.(row) in
2802 let y = (row - first) * 16 in
2803 let x = 5 + (max 0 (l+pan))*15 in
2804 if row = active && a <> Noaction
2805 then (
2806 Gl.enable `blend;
2807 GlDraw.polygon_mode `both `line;
2808 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2809 GlDraw.color (1., 1., 1.) ~alpha:0.9;
2810 GlDraw.rect (0., float (y + 1))
2811 (float (conf.winw - 1), float (y + 18));
2812 GlDraw.polygon_mode `both `fill;
2813 Gl.disable `blend;
2814 GlDraw.color (1., 1., 1.);
2816 let draw_string s =
2817 let l = String.length s in
2818 if pan < 0
2819 then (
2820 let pan = pan * 2 in
2821 let left = l + pan in
2822 if left > 0
2823 then
2824 let s = String.sub s (-pan) left in
2825 draw_string (float x) (float (y + 16)) s
2827 else
2828 draw_string (float (x + pan*15)) (float (y + 16)) s
2830 draw_string s;
2831 loop (row+1)
2834 loop first
2837 let display () =
2838 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
2839 GlDraw.viewport margin 0 state.w conf.winh;
2840 pagematrix ();
2841 GlClear.color (scalecolor2 conf.bgcolor);
2842 GlClear.clear [`color];
2843 if conf.zoom > 1.0
2844 then (
2845 Gl.enable `scissor_test;
2846 GlMisc.scissor 0 0 (conf.winw - conf.scrollw) conf.winh;
2848 List.iter drawpage state.layout;
2849 if conf.zoom > 1.0
2850 then
2851 Gl.disable `scissor_test
2853 if state.x != 0
2854 then (
2855 let x = -.float state.x in
2856 GlMat.translate ~x ();
2858 showrects ();
2859 showsel margin;
2860 GlDraw.viewport 0 0 conf.winw conf.winh;
2861 winmatrix ();
2862 scrollindicator ();
2863 begin match state.mode with
2864 | Items items -> showitems items
2865 | Outline outline -> showoutline outline
2866 | _ -> ()
2867 end;
2868 enttext ();
2869 Glut.swapBuffers ();
2872 let getunder x y =
2873 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
2874 let x = x - margin - state.x in
2875 let rec f = function
2876 | l :: rest ->
2877 begin match getopaque l.pageno with
2878 | Some (opaque, _) when validopaque opaque ->
2879 let y = y - l.pagedispy in
2880 if y > 0
2881 then
2882 let y = l.pagey + y in
2883 let x = x - l.pagex in
2884 match whatsunder opaque x y with
2885 | Unone -> f rest
2886 | under -> under
2887 else
2888 f rest
2889 | _ ->
2890 f rest
2892 | [] -> Unone
2894 f state.layout
2897 let viewmouse button bstate x y =
2898 match button with
2899 | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP ->
2900 if Glut.getModifiers () land Glut.active_ctrl != 0
2901 then (
2902 match state.mstate with
2903 | Mzoom (oldn, i) ->
2904 if oldn = n
2905 then (
2906 if i = 2
2907 then
2908 let incr =
2909 match n with
2910 | 4 ->
2911 if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01
2912 | _ ->
2913 if conf.zoom -. 0.1 < 0.1 then -0.01 else -0.1
2915 let zoom = conf.zoom +. incr in
2916 setzoom zoom;
2917 state.mstate <- Mzoom (n, 0);
2918 else
2919 state.mstate <- Mzoom (n, i+1);
2921 else state.mstate <- Mzoom (n, 0)
2923 | _ -> state.mstate <- Mzoom (n, 0)
2925 else (
2926 if state.ascrollstep > 0
2927 then
2928 setautoscrollspeed (n=4)
2929 else
2930 let incr =
2931 if n = 3
2932 then -conf.scrollstep
2933 else conf.scrollstep
2935 let incr = incr * 2 in
2936 let y = clamp incr in
2937 gotoy_and_clear_text y
2940 | Glut.LEFT_BUTTON when Glut.getModifiers () land Glut.active_ctrl != 0 ->
2941 if bstate = Glut.DOWN
2942 then (
2943 Glut.setCursor Glut.CURSOR_CROSSHAIR;
2944 state.mstate <- Mpan (x, y)
2946 else
2947 state.mstate <- Mnone
2949 | Glut.LEFT_BUTTON when x > conf.winw - conf.scrollw ->
2950 if bstate = Glut.DOWN
2951 then
2952 let position, sh = scrollph state.y in
2953 if y > truncate position && y < truncate (position +. sh)
2954 then
2955 state.mstate <- Mscroll
2956 else
2957 let percent = float y /. float conf.winh in
2958 let desty = truncate (float (state.maxy - conf.winh) *. percent) in
2959 gotoy desty;
2960 state.mstate <- Mscroll
2961 else
2962 state.mstate <- Mnone
2964 | Glut.LEFT_BUTTON ->
2965 let dest = if bstate = Glut.DOWN then getunder x y else Unone in
2966 begin match dest with
2967 | Ulinkgoto (pageno, top) ->
2968 if pageno >= 0
2969 then (
2970 addnav ();
2971 gotopage1 pageno top;
2974 | Ulinkuri s ->
2975 print_endline s
2977 | Unone when bstate = Glut.DOWN ->
2978 Glut.setCursor Glut.CURSOR_CROSSHAIR;
2979 state.mstate <- Mpan (x, y);
2981 | Unone | Utext _ ->
2982 if bstate = Glut.DOWN
2983 then (
2984 if conf.angle mod 360 = 0
2985 then (
2986 state.mstate <- Msel ((x, y), (x, y));
2987 Glut.postRedisplay ()
2990 else (
2991 match state.mstate with
2992 | Mnone -> ()
2994 | Mzoom _ | Mscroll ->
2995 state.mstate <- Mnone
2997 | Mpan _ ->
2998 Glut.setCursor Glut.CURSOR_INHERIT;
2999 state.mstate <- Mnone
3001 | Msel ((x0, y0), (x1, y1)) ->
3002 let f l =
3003 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
3004 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
3005 then
3006 match getopaque l.pageno with
3007 | Some (opaque, _) when validopaque opaque ->
3008 copysel opaque
3009 | _ -> ()
3011 List.iter f state.layout;
3012 copysel ""; (* ugly *)
3013 Glut.setCursor Glut.CURSOR_INHERIT;
3014 state.mstate <- Mnone;
3018 | _ -> ()
3021 let birdseyemouse button bstate x y
3022 (conf, leftx, pageno, hooverpageno, anchor) =
3023 match button with
3024 | Glut.LEFT_BUTTON when bstate = Glut.UP ->
3025 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
3026 let rec loop = function
3027 | [] -> ()
3028 | l :: rest ->
3029 if y > l.pagedispy && y < l.pagedispy + l.pagevh
3030 && x > margin && x < margin + l.pagew
3031 then (
3032 leavebirdseye (conf, leftx, l.pageno, hooverpageno, anchor) false;
3034 else loop rest
3036 loop state.layout
3037 | Glut.OTHER_BUTTON _ -> viewmouse button bstate x y
3038 | _ -> ()
3041 let mouse bstate button x y =
3042 match state.mode with
3043 | View -> viewmouse button bstate x y
3044 | Birdseye beye -> birdseyemouse button bstate x y beye
3045 | Textentry _ | Outline _ | Items _ -> ()
3048 let mouse ~button ~state ~x ~y = mouse state button x y;;
3050 let motion ~x ~y =
3051 match state.mode with
3052 | Outline _ -> ()
3053 | _ ->
3054 match state.mstate with
3055 | Mzoom _ | Mnone -> ()
3057 | Mpan (x0, y0) ->
3058 let dx = x - x0
3059 and dy = y0 - y in
3060 state.mstate <- Mpan (x, y);
3061 if conf.zoom > 1.0 then state.x <- state.x + dx;
3062 let y = clamp dy in
3063 gotoy_and_clear_text y
3065 | Msel (a, _) ->
3066 state.mstate <- Msel (a, (x, y));
3067 Glut.postRedisplay ()
3069 | Mscroll ->
3070 let y = min conf.winh (max 0 y) in
3071 let percent = float y /. float conf.winh in
3072 let y = truncate (float (state.maxy - conf.winh) *. percent) in
3073 gotoy_and_clear_text y
3076 let pmotion ~x ~y =
3077 match state.mode with
3078 | Birdseye (conf, leftx, pageno, hooverpageno, anchor) ->
3079 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
3080 let rec loop = function
3081 | [] ->
3082 if hooverpageno != -1
3083 then (
3084 state.mode <- Birdseye (conf, leftx, pageno, -1, anchor);
3085 Glut.postRedisplay ();
3087 | l :: rest ->
3088 if y > l.pagedispy && y < l.pagedispy + l.pagevh
3089 && x > margin && x < margin + l.pagew
3090 then (
3091 state.mode <- Birdseye (conf, leftx, pageno, l.pageno, anchor);
3092 Glut.postRedisplay ();
3094 else loop rest
3096 loop state.layout
3098 | Outline _ -> ()
3099 | _ ->
3100 match state.mstate with
3101 | Mnone ->
3102 begin match getunder x y with
3103 | Unone -> Glut.setCursor Glut.CURSOR_INHERIT
3104 | Ulinkuri uri ->
3105 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
3106 Glut.setCursor Glut.CURSOR_INFO
3107 | Ulinkgoto (page, y) ->
3108 if conf.underinfo
3109 then showtext 'p' ("age: " ^ string_of_int page);
3110 Glut.setCursor Glut.CURSOR_INFO
3111 | Utext s ->
3112 if conf.underinfo then showtext 'f' ("ont: " ^ s);
3113 Glut.setCursor Glut.CURSOR_TEXT
3116 | Mpan _ | Msel _ | Mzoom _ | Mscroll ->
3121 module State =
3122 struct
3123 open Parser
3125 let home =
3127 match Sys.os_type with
3128 | "Win32" -> Sys.getenv "HOMEPATH"
3129 | _ -> Sys.getenv "HOME"
3130 with exn ->
3131 prerr_endline
3132 ("Can not determine home directory location: " ^
3133 Printexc.to_string exn);
3137 let color_of_string s =
3138 Scanf.sscanf s "%d/%d/%d" (fun r g b ->
3139 (float r /. 256.0, float g /. 256.0, float b /. 256.0)
3143 let config_of c attrs =
3144 let apply c k v =
3146 match k with
3147 | "scroll-bar-width" -> { c with scrollw = max 0 (int_of_string v) }
3148 | "scroll-handle-height" -> { c with scrollh = max 0 (int_of_string v) }
3149 | "case-insensitive-search" -> { c with icase = bool_of_string v }
3150 | "preload" -> { c with preload = bool_of_string v }
3151 | "page-bias" -> { c with pagebias = int_of_string v }
3152 | "scroll-step" -> { c with scrollstep = max 1 (int_of_string v) }
3153 | "auto-scroll-step" ->
3154 { c with autoscrollstep = max 0 (int_of_string v) }
3155 | "max-height-fit" -> { c with maxhfit = bool_of_string v }
3156 | "crop-hack" -> { c with crophack = bool_of_string v }
3157 | "throttle" -> { c with showall = bool_of_string v }
3158 | "highlight-links" -> { c with hlinks = bool_of_string v }
3159 | "under-cursor-info" -> { c with underinfo = bool_of_string v }
3160 | "vertical-margin" ->
3161 { c with interpagespace = max 0 (int_of_string v) }
3162 | "zoom" ->
3163 let zoom = float_of_string v /. 100. in
3164 let zoom = max 0.01 (min 2.2 zoom) in
3165 { c with zoom = zoom }
3166 | "presentation" -> { c with presentation = bool_of_string v }
3167 | "rotation-angle" -> { c with angle = int_of_string v }
3168 | "width" -> { c with winw = max 20 (int_of_string v) }
3169 | "height" -> { c with winh = max 20 (int_of_string v) }
3170 | "persistent-bookmarks" -> { c with savebmarks = bool_of_string v }
3171 | "proportional-display" -> { c with proportional = bool_of_string v }
3172 | "pixmap-cache-size" -> { c with memlimit = max 2 (int_of_string v) }
3173 | "tex-count" -> { c with texcount = max 1 (int_of_string v) }
3174 | "slice-height" -> { c with sliceheight = max 2 (int_of_string v) }
3175 | "thumbnail-width" -> { c with thumbw = max 2 (int_of_string v) }
3176 | "persistent-location" -> { c with jumpback = bool_of_string v }
3177 | "background-color" -> { c with bgcolor = color_of_string v }
3178 | _ -> c
3179 with exn ->
3180 prerr_endline ("Error processing attribute (`" ^
3181 k ^ "'=`" ^ v ^ "'): " ^ Printexc.to_string exn);
3184 let rec fold c = function
3185 | [] -> c
3186 | (k, v) :: rest ->
3187 let c = apply c k v in
3188 fold c rest
3190 fold c attrs;
3193 let fromstring f pos n v d =
3194 try f v
3195 with exn ->
3196 dolog "Error processing attribute (%S=%S) at %d\n%s"
3197 n v pos (Printexc.to_string exn)
3202 let bookmark_of attrs =
3203 let rec fold title page rely = function
3204 | ("title", v) :: rest -> fold v page rely rest
3205 | ("page", v) :: rest -> fold title v rely rest
3206 | ("rely", v) :: rest -> fold title page v rest
3207 | _ :: rest -> fold title page rely rest
3208 | [] -> title, page, rely
3210 fold "invalid" "0" "0" attrs
3213 let doc_of attrs =
3214 let rec fold path page rely pan = function
3215 | ("path", v) :: rest -> fold v page rely pan rest
3216 | ("page", v) :: rest -> fold path v rely pan rest
3217 | ("rely", v) :: rest -> fold path page v pan rest
3218 | ("pan", v) :: rest -> fold path page rely v rest
3219 | _ :: rest -> fold path page rely pan rest
3220 | [] -> path, page, rely, pan
3222 fold "" "0" "0" "0" attrs
3225 let setconf dst src =
3226 dst.scrollw <- src.scrollw;
3227 dst.scrollh <- src.scrollh;
3228 dst.icase <- src.icase;
3229 dst.preload <- src.preload;
3230 dst.pagebias <- src.pagebias;
3231 dst.verbose <- src.verbose;
3232 dst.scrollstep <- src.scrollstep;
3233 dst.maxhfit <- src.maxhfit;
3234 dst.crophack <- src.crophack;
3235 dst.autoscrollstep <- src.autoscrollstep;
3236 dst.showall <- src.showall;
3237 dst.hlinks <- src.hlinks;
3238 dst.underinfo <- src.underinfo;
3239 dst.interpagespace <- src.interpagespace;
3240 dst.zoom <- src.zoom;
3241 dst.presentation <- src.presentation;
3242 dst.angle <- src.angle;
3243 dst.winw <- src.winw;
3244 dst.winh <- src.winh;
3245 dst.savebmarks <- src.savebmarks;
3246 dst.memlimit <- src.memlimit;
3247 dst.proportional <- src.proportional;
3248 dst.texcount <- src.texcount;
3249 dst.sliceheight <- src.sliceheight;
3250 dst.thumbw <- src.thumbw;
3251 dst.jumpback <- src.jumpback;
3252 dst.bgcolor <- src.bgcolor;
3255 let unent s =
3256 let l = String.length s in
3257 let b = Buffer.create l in
3258 unent b s 0 l;
3259 Buffer.contents b;
3262 let get s =
3263 let h = Hashtbl.create 10 in
3264 let dc = { defconf with angle = defconf.angle } in
3265 let rec toplevel v t spos epos =
3266 match t with
3267 | Vdata | Vcdata | Vend -> v
3268 | Vopen ("llppconfig", attrs, closed) ->
3269 if closed
3270 then v
3271 else { v with f = llppconfig }
3272 | Vopen _ ->
3273 error "unexpected subelement at top level" s spos
3274 | Vclose tag -> error "unexpected close at top level" s spos
3276 and llppconfig v t spos epos =
3277 match t with
3278 | Vdata | Vcdata | Vend -> v
3279 | Vopen ("defaults", attrs, closed) ->
3280 let c = config_of dc attrs in
3281 setconf dc c;
3282 if closed
3283 then v
3284 else { v with f = skip "defaults" (fun () -> v) }
3286 | Vopen ("doc", attrs, closed) ->
3287 let pathent, spage, srely, span = doc_of attrs in
3288 let path = unent pathent
3289 and pageno = fromstring int_of_string spos "page" spage 0
3290 and rely = fromstring float_of_string spos "rely" srely 0.0
3291 and pan = fromstring int_of_string spos "pan" span 0 in
3292 let c = config_of dc attrs in
3293 let anchor = (pageno, rely) in
3294 if closed
3295 then (Hashtbl.add h path (c, [], pan, anchor); v)
3296 else { v with f = doc path pan anchor c [] }
3298 | Vopen (tag, _, closed) ->
3299 error "unexpected subelement in llppconfig" s spos
3301 | Vclose "llppconfig" -> { v with f = toplevel }
3302 | Vclose tag -> error "unexpected close in llppconfig" s spos
3304 and doc path pan anchor c bookmarks v t spos epos =
3305 match t with
3306 | Vdata | Vcdata -> v
3307 | Vend -> error "unexpected end of input in doc" s spos
3308 | Vopen ("bookmarks", attrs, closed) ->
3309 { v with f = pbookmarks path pan anchor c bookmarks }
3311 | Vopen (tag, _, _) ->
3312 error "unexpected subelement in doc" s spos
3314 | Vclose "doc" ->
3315 Hashtbl.add h path (c, List.rev bookmarks, pan, anchor);
3316 { v with f = llppconfig }
3318 | Vclose tag -> error "unexpected close in doc" s spos
3320 and pbookmarks path pan anchor c bookmarks v t spos epos =
3321 match t with
3322 | Vdata | Vcdata -> v
3323 | Vend -> error "unexpected end of input in bookmarks" s spos
3324 | Vopen ("item", attrs, closed) ->
3325 let titleent, spage, srely = bookmark_of attrs in
3326 let page = fromstring int_of_string spos "page" spage 0
3327 and rely = fromstring float_of_string spos "rely" srely 0.0 in
3328 let bookmarks = (unent titleent, 0, page, rely) :: bookmarks in
3329 if closed
3330 then { v with f = pbookmarks path pan anchor c bookmarks }
3331 else
3332 let f () = v in
3333 { v with f = skip "item" f }
3335 | Vopen _ ->
3336 error "unexpected subelement in bookmarks" s spos
3338 | Vclose "bookmarks" ->
3339 { v with f = doc path pan anchor c bookmarks }
3341 | Vclose tag -> error "unexpected close in bookmarks" s spos
3343 and skip tag f v t spos epos =
3344 match t with
3345 | Vdata | Vcdata -> v
3346 | Vend ->
3347 error ("unexpected end of input in skipped " ^ tag) s spos
3348 | Vopen (tag', _, closed) ->
3349 if closed
3350 then v
3351 else
3352 let f' () = { v with f = skip tag f } in
3353 { v with f = skip tag' f' }
3354 | Vclose ctag ->
3355 if tag = ctag
3356 then f ()
3357 else error ("unexpected close in skipped " ^ tag) s spos
3360 parse { f = toplevel; accu = () } s;
3361 h, dc;
3364 let do_load f ic =
3366 let len = in_channel_length ic in
3367 let s = String.create len in
3368 really_input ic s 0 len;
3369 f s;
3370 with
3371 | Parse_error (msg, s, pos) ->
3372 let subs = subs s pos in
3373 let s = Printf.sprintf "%s: at %d [..%s..]" msg pos subs in
3374 failwith ("parse error: " ^ s)
3376 | exn ->
3377 failwith ("config load error: " ^ Printexc.to_string exn)
3380 let path =
3381 let dir =
3383 let dir = Filename.concat home ".config" in
3384 if Sys.is_directory dir then dir else home
3385 with _ -> home
3387 Filename.concat dir "llpp.conf"
3390 let load1 f =
3391 if Sys.file_exists path
3392 then
3393 match
3394 (try Some (open_in_bin path)
3395 with exn ->
3396 prerr_endline
3397 ("Error opening configuation file `" ^ path ^ "': " ^
3398 Printexc.to_string exn);
3399 None
3401 with
3402 | Some ic ->
3403 begin try
3404 f (do_load get ic)
3405 with exn ->
3406 prerr_endline
3407 ("Error loading configuation from `" ^ path ^ "': " ^
3408 Printexc.to_string exn);
3409 end;
3410 close_in ic;
3412 | None -> ()
3413 else
3414 f (Hashtbl.create 0, defconf)
3417 let load () =
3418 let f (h, dc) =
3419 let pc, pb, px, pa =
3421 Hashtbl.find h (Filename.basename state.path)
3422 with Not_found -> dc, [], 0, (0, 0.0)
3424 setconf defconf dc;
3425 setconf conf pc;
3426 state.bookmarks <- pb;
3427 state.x <- px;
3428 if conf.jumpback
3429 then state.anchor <- pa;
3430 cbput state.hists.nav pa;
3432 load1 f
3435 let add_attrs bb always dc c =
3436 let ob s a b =
3437 if always || a != b
3438 then Printf.bprintf bb "\n %s='%b'" s a
3439 and oi s a b =
3440 if always || a != b
3441 then Printf.bprintf bb "\n %s='%d'" s a
3442 and oz s a b =
3443 if always || a <> b
3444 then Printf.bprintf bb "\n %s='%f'" s (a*.100.)
3445 and oc s a b =
3446 if always || a <> b
3447 then
3448 let r, g, b = a in
3449 let r = truncate (r *. 256.0)
3450 and g = truncate (r *. 256.0)
3451 and b = truncate (r *. 256.0) in
3452 Printf.bprintf bb "\n %s='%d/%d/%d'" s r g b
3454 let w, h =
3455 if always
3456 then dc.winw, dc.winh
3457 else
3458 match state.fullscreen with
3459 | Some wh -> wh
3460 | None -> c.winw, c.winh
3462 let zoom, presentation, interpagespace, showall=
3463 if always
3464 then dc.zoom, dc.presentation, dc.interpagespace, dc.showall
3465 else
3466 match state.mode with
3467 | Birdseye (bc, _, _, _, _) ->
3468 bc.zoom, bc.presentation, bc.interpagespace, bc.showall
3469 | _ -> c.zoom, c.presentation, c.interpagespace, c.showall
3471 oi "width" w dc.winw;
3472 oi "height" h dc.winh;
3473 oi "scroll-bar-width" c.scrollw dc.scrollw;
3474 oi "scroll-handle-height" c.scrollh dc.scrollh;
3475 ob "case-insensitive-search" c.icase dc.icase;
3476 ob "preload" c.preload dc.preload;
3477 oi "page-bias" c.pagebias dc.pagebias;
3478 oi "scroll-step" c.scrollstep dc.scrollstep;
3479 oi "auto-scroll-step" c.autoscrollstep dc.autoscrollstep;
3480 ob "max-height-fit" c.maxhfit dc.maxhfit;
3481 ob "crop-hack" c.crophack dc.crophack;
3482 ob "throttle" showall dc.showall;
3483 ob "highlight-links" c.hlinks dc.hlinks;
3484 ob "under-cursor-info" c.underinfo dc.underinfo;
3485 oi "vertical-margin" interpagespace dc.interpagespace;
3486 oz "zoom" zoom dc.zoom;
3487 ob "presentation" presentation dc.presentation;
3488 oi "rotation-angle" c.angle dc.angle;
3489 ob "persistent-bookmarks" c.savebmarks dc.savebmarks;
3490 ob "proportional-display" c.proportional dc.proportional;
3491 oi "pixmap-cache-size" c.memlimit dc.memlimit;
3492 oi "texcount" c.texcount dc.texcount;
3493 oi "slice-height" c.sliceheight dc.sliceheight;
3494 oi "thumbnail-width" c.thumbw dc.thumbw;
3495 ob "persistent-location" c.jumpback dc.jumpback;
3496 oc "background-color" c.bgcolor dc.bgcolor;
3499 let save () =
3500 let bb = Buffer.create 32768 in
3501 let f (h, dc) =
3502 let dc = if conf.bedefault then conf else dc in
3503 Buffer.add_string bb "<llppconfig>\n<defaults ";
3504 add_attrs bb true dc dc;
3505 Buffer.add_string bb "/>\n";
3507 let adddoc path pan anchor c bookmarks =
3508 if bookmarks == [] && c = dc && anchor = emptyanchor
3509 then ()
3510 else (
3511 Printf.bprintf bb "<doc path='%s'"
3512 (enent path 0 (String.length path));
3514 if anchor <> emptyanchor
3515 then (
3516 let n, y = anchor in
3517 Printf.bprintf bb " page='%d'" n;
3518 if y > 1e-6
3519 then
3520 Printf.bprintf bb " rely='%f'" y
3524 if pan != 0
3525 then Printf.bprintf bb " pan='%d'" pan;
3527 add_attrs bb false dc c;
3529 begin match bookmarks with
3530 | [] -> Buffer.add_string bb "/>\n"
3531 | _ ->
3532 Buffer.add_string bb ">\n<bookmarks>\n";
3533 List.iter (fun (title, _level, page, rely) ->
3534 Printf.bprintf bb
3535 "<item title='%s' page='%d'"
3536 (enent title 0 (String.length title))
3537 page
3539 if rely > 1e-6
3540 then
3541 Printf.bprintf bb " rely='%f'" rely
3543 Buffer.add_string bb "/>\n";
3544 ) bookmarks;
3545 Buffer.add_string bb "</bookmarks>\n</doc>\n";
3546 end;
3550 let pan =
3551 match state.mode with
3552 | Birdseye (_, pan, _, _, _) -> pan
3553 | _ -> state.x
3555 let basename = Filename.basename state.path in
3556 adddoc basename pan (getanchor ())
3557 { conf with
3558 autoscrollstep =
3559 if state.ascrollstep > 0
3560 then state.ascrollstep
3561 else conf.autoscrollstep }
3562 (if conf.savebmarks then state.bookmarks else []);
3564 Hashtbl.iter (fun path (c, bookmarks, x, y) ->
3565 if basename <> path
3566 then adddoc path x y c bookmarks
3567 ) h;
3568 Buffer.add_string bb "</llppconfig>";
3570 load1 f;
3571 if Buffer.length bb > 0
3572 then
3574 let tmp = path ^ ".tmp" in
3575 let oc = open_out_bin tmp in
3576 Buffer.output_buffer oc bb;
3577 close_out oc;
3578 Sys.rename tmp path;
3579 with exn ->
3580 prerr_endline
3581 ("error while saving configuration: " ^ Printexc.to_string exn)
3583 end;;
3585 let () =
3586 Arg.parse
3587 (Arg.align
3588 [("-p", Arg.String (fun s -> state.password <- s) , " Set password")
3589 ;("-v", Arg.Unit (fun () -> print_endline Help.version; exit 0),
3590 " Print version and exit")]
3592 (fun s -> state.path <- s)
3593 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\nOptions:")
3595 if String.length state.path = 0
3596 then (prerr_endline "file name missing"; exit 1);
3598 State.load ();
3600 let _ = Glut.init Sys.argv in
3601 let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in
3602 let () = Glut.initWindowSize conf.winw conf.winh in
3603 let _ = Glut.createWindow ("llpp " ^ Filename.basename state.path) in
3605 let csock, ssock =
3606 if Sys.os_type = "Unix"
3607 then
3608 Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
3609 else
3610 let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, 1337) in
3611 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
3612 Unix.setsockopt sock Unix.SO_REUSEADDR true;
3613 Unix.bind sock addr;
3614 Unix.listen sock 1;
3615 let csock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
3616 Unix.connect csock addr;
3617 let ssock, _ = Unix.accept sock in
3618 Unix.close sock;
3619 let opts sock =
3620 Unix.setsockopt sock Unix.TCP_NODELAY true;
3621 Unix.setsockopt_optint sock Unix.SO_LINGER None;
3623 opts ssock;
3624 opts csock;
3625 at_exit (fun () -> Unix.shutdown ssock Unix.SHUTDOWN_ALL);
3626 ssock, csock
3629 let () = Glut.displayFunc display in
3630 let () = Glut.reshapeFunc reshape in
3631 let () = Glut.keyboardFunc keyboard in
3632 let () = Glut.specialFunc special in
3633 let () = Glut.idleFunc (Some idle) in
3634 let () = Glut.mouseFunc mouse in
3635 let () = Glut.motionFunc motion in
3636 let () = Glut.passiveMotionFunc pmotion in
3638 init ssock (conf.angle, conf.proportional, conf.texcount, conf.sliceheight);
3639 state.csock <- csock;
3640 state.ssock <- ssock;
3641 state.text <- "Opening " ^ state.path;
3642 writeopen state.path state.password;
3644 at_exit State.save;
3646 let rec handlelablglutbug () =
3648 Glut.mainLoop ();
3649 with Glut.BadEnum "key in special_of_int" ->
3650 showtext '!' " LablGlut bug: special key not recognized";
3651 handlelablglutbug ()
3653 handlelablglutbug ();