Disable 'Z', the calculations are wrong (due to scroll bar subtraction)
[llpp.git] / main.ml
blob1391a736060d0fde30eca892cee392e5529c7d6c
1 type under =
2 | Unone
3 | Ulinkuri of string
4 | Ulinkgoto of (int * int)
5 | Utext of facename
6 and facename = string;;
8 let log fmt = Printf.kprintf prerr_endline fmt;;
9 let dolog fmt = Printf.kprintf prerr_endline fmt;;
11 external init : Unix.file_descr -> unit = "ml_init";;
12 external draw : (int * int * int * int * bool) -> string -> unit = "ml_draw";;
13 external seltext : string -> (int * int * int * int) -> int -> unit =
14 "ml_seltext";;
15 external copysel : string -> unit = "ml_copysel";;
16 external getpagewh : int -> float array = "ml_getpagewh";;
17 external whatsunder : string -> int -> int -> under = "ml_whatsunder";;
19 type mpos = int * int
20 type mstate = Msel of (mpos * mpos) | Mpan of mpos | Mnone;;
22 type 'a circbuf =
23 { store : 'a array
24 ; mutable rc : int
25 ; mutable wc : int
26 ; mutable len : int
30 type textentry = (char * string * onhist option * onkey * ondone)
31 and onkey = string -> int -> te
32 and ondone = string -> unit
33 and onhist = histcmd -> string
34 and histcmd = HCnext | HCprev | HCfirst | HClast
35 and te =
36 | TEstop
37 | TEdone of string
38 | TEcont of string
39 | TEswitch of textentry
42 let cbnew n v =
43 { store = Array.create n v
44 ; rc = 0
45 ; wc = 0
46 ; len = 0
50 let cblen b = Array.length b.store;;
52 let cbput b v =
53 let len = cblen b in
54 b.store.(b.wc) <- v;
55 b.wc <- (b.wc + 1) mod len;
56 b.len <- min (b.len + 1) len;
59 let cbpeekw b = b.store.(b.wc);;
61 let cbget b dir =
62 if b.len = 0
63 then b.store.(0)
64 else
65 let rc = b.rc + dir in
66 let rc = if rc = -1 then b.len - 1 else rc in
67 let rc = if rc = b.len then 0 else rc in
68 b.rc <- rc;
69 b.store.(rc);
72 let cbrfollowlen b =
73 b.rc <- b.len;
76 let cbclear b v =
77 b.len <- 0;
78 Array.fill b.store 0 (Array.length b.store) v;
81 type layout =
82 { pageno : int
83 ; pagedimno : int
84 ; pagew : int
85 ; pageh : int
86 ; pagedispy : int
87 ; pagey : int
88 ; pagevh : int
92 type conf =
93 { mutable scrollw : int
94 ; mutable scrollh : int
95 ; mutable icase : bool
96 ; mutable preload : bool
97 ; mutable pagebias : int
98 ; mutable verbose : bool
99 ; mutable scrollincr : int
100 ; mutable maxhfit : bool
101 ; mutable crophack : bool
102 ; mutable autoscroll : bool
103 ; mutable showall : bool
104 ; mutable hlinks : bool
105 ; mutable underinfo : bool
106 ; mutable interpagespace : int
107 ; mutable zoom : float
108 ; mutable presentation : bool
112 type outline = string * int * int * float;;
113 type outlines =
114 | Oarray of outline array
115 | Olist of outline list
116 | Onarrow of outline array * outline array
119 type rect = (float * float * float * float * float * float * float * float);;
121 type state =
122 { mutable csock : Unix.file_descr
123 ; mutable ssock : Unix.file_descr
124 ; mutable w : int
125 ; mutable h : int
126 ; mutable winw : int
127 ; mutable rotate : int
128 ; mutable x : int
129 ; mutable y : int
130 ; mutable ty : float
131 ; mutable maxy : int
132 ; mutable layout : layout list
133 ; pagemap : ((int * int * int), string) Hashtbl.t
134 ; mutable pages : (int * int * int) list
135 ; mutable pagecount : int
136 ; pagecache : string circbuf
137 ; mutable rendering : bool
138 ; mutable mstate : mstate
139 ; mutable searchpattern : string
140 ; mutable rects : (int * int * rect) list
141 ; mutable rects1 : (int * int * rect) list
142 ; mutable text : string
143 ; mutable fullscreen : (int * int) option
144 ; mutable textentry : textentry option
145 ; mutable outlines : outlines
146 ; mutable outline : (bool * int * int * outline array * string) option
147 ; mutable bookmarks : outline list
148 ; mutable path : string
149 ; mutable password : string
150 ; mutable invalidated : int
151 ; mutable colorscale : float
152 ; hists : hists
154 and hists =
155 { pat : string circbuf
156 ; pag : string circbuf
157 ; nav : float circbuf
161 let conf =
162 { scrollw = 5
163 ; scrollh = 12
164 ; icase = true
165 ; preload = true
166 ; pagebias = 0
167 ; verbose = false
168 ; scrollincr = 24
169 ; maxhfit = true
170 ; crophack = false
171 ; autoscroll = false
172 ; showall = false
173 ; hlinks = false
174 ; underinfo = false
175 ; interpagespace = 2
176 ; zoom = 1.0
177 ; presentation = false
181 let state =
182 { csock = Unix.stdin
183 ; ssock = Unix.stdin
184 ; w = 900
185 ; h = 900
186 ; winw = 900
187 ; rotate = 0
188 ; y = 0
189 ; x = 0
190 ; ty = 0.0
191 ; layout = []
192 ; maxy = max_int
193 ; pagemap = Hashtbl.create 10
194 ; pagecache = cbnew 10 ""
195 ; pages = []
196 ; pagecount = 0
197 ; rendering = false
198 ; mstate = Mnone
199 ; rects = []
200 ; rects1 = []
201 ; text = ""
202 ; fullscreen = None
203 ; textentry = None
204 ; searchpattern = ""
205 ; outlines = Olist []
206 ; outline = None
207 ; bookmarks = []
208 ; path = ""
209 ; password = ""
210 ; invalidated = 0
211 ; hists =
212 { nav = cbnew 100 0.0
213 ; pat = cbnew 20 ""
214 ; pag = cbnew 10 ""
216 ; colorscale = 1.0
220 let vlog fmt =
221 if conf.verbose
222 then
223 Printf.kprintf prerr_endline fmt
224 else
225 Printf.kprintf ignore fmt
228 let writecmd fd s =
229 let len = String.length s in
230 let n = 4 + len in
231 let b = Buffer.create n in
232 Buffer.add_char b (Char.chr ((len lsr 24) land 0xff));
233 Buffer.add_char b (Char.chr ((len lsr 16) land 0xff));
234 Buffer.add_char b (Char.chr ((len lsr 8) land 0xff));
235 Buffer.add_char b (Char.chr ((len lsr 0) land 0xff));
236 Buffer.add_string b s;
237 let s' = Buffer.contents b in
238 let n' = Unix.write fd s' 0 n in
239 if n' != n then failwith "write failed";
242 let readcmd fd =
243 let s = "xxxx" in
244 let n = Unix.read fd s 0 4 in
245 if n != 4 then failwith "incomplete read(len)";
246 let len = 0
247 lor (Char.code s.[0] lsl 24)
248 lor (Char.code s.[1] lsl 16)
249 lor (Char.code s.[2] lsl 8)
250 lor (Char.code s.[3] lsl 0)
252 let s = String.create len in
253 let n = Unix.read fd s 0 len in
254 if n != len then failwith "incomplete read(data)";
258 let yratio y =
259 if y = state.maxy
260 then 1.0
261 else float y /. float state.maxy
264 let makecmd s l =
265 let b = Buffer.create 10 in
266 Buffer.add_string b s;
267 let rec combine = function
268 | [] -> b
269 | x :: xs ->
270 Buffer.add_char b ' ';
271 let s =
272 match x with
273 | `b b -> if b then "1" else "0"
274 | `s s -> s
275 | `i i -> string_of_int i
276 | `f f -> string_of_float f
277 | `I f -> string_of_int (truncate f)
279 Buffer.add_string b s;
280 combine xs;
282 combine l;
285 let wcmd s l =
286 let cmd = Buffer.contents (makecmd s l) in
287 writecmd state.csock cmd;
290 let calcips h =
291 if conf.presentation
292 then
293 let d = state.h - h in
294 max 0 ((d + 1) / 2)
295 else
296 conf.interpagespace
299 let calcheight () =
300 let rec f pn ph pi fh l =
301 match l with
302 | (n, _, h) :: rest ->
303 let ips = calcips h in
304 let fh =
305 if conf.presentation
306 then fh+ips
307 else fh
309 let fh = fh + ((n - pn) * (ph + pi)) in
310 f n h ips fh rest
312 | [] ->
313 let inc =
314 if conf.presentation
315 then 0
316 else -pi
318 let fh = fh + ((state.pagecount - pn) * (ph + pi)) + inc in
319 max 0 fh
321 let fh = f 0 0 0 0 state.pages in
325 let getpageyh pageno =
326 let rec f pn ph pi y l =
327 match l with
328 | (n, _, h) :: rest ->
329 let ips = calcips h in
330 if n >= pageno
331 then
332 if conf.presentation && n = pageno
333 then
334 y + (pageno - pn) * (ph + pi) + pi, h
335 else
336 y + (pageno - pn) * (ph + pi), h
337 else
338 let y = y + (if conf.presentation then pi else 0) in
339 let y = y + (n - pn) * (ph + pi) in
340 f n h ips y rest
342 | [] ->
343 y + (pageno - pn) * (ph + pi), ph
345 f 0 0 0 0 state.pages
348 let getpagey pageno = fst (getpageyh pageno);;
350 let layout y sh =
351 let rec f ~pageno ~pdimno ~prev ~py ~dy ~pdims ~cacheleft ~accu =
352 let ((w, h, ips) as curr), rest, pdimno, yinc =
353 match pdims with
354 | (pageno', w, h) :: rest when pageno' = pageno ->
355 let ips = calcips h in
356 let yinc = if conf.presentation then ips else 0 in
357 (w, h, ips), rest, pdimno + 1, yinc
358 | _ ->
359 prev, pdims, pdimno, 0
361 let dy = dy + yinc in
362 let py = py + yinc in
363 if pageno = state.pagecount || cacheleft = 0 || dy >= sh
364 then
365 accu
366 else
367 let vy = y + dy in
368 if py + h <= vy - yinc
369 then
370 let py = py + h + ips in
371 let dy = max 0 (py - y) in
372 f ~pageno:(pageno+1)
373 ~pdimno
374 ~prev:curr
377 ~pdims:rest
378 ~cacheleft
379 ~accu
380 else
381 let pagey = vy - py in
382 let pagevh = h - pagey in
383 let pagevh = min (sh - dy) pagevh in
384 let off = if yinc > 0 then py - vy else 0
386 let py = py + h + ips in
387 let e =
388 { pageno = pageno
389 ; pagedimno = pdimno
390 ; pagew = w
391 ; pageh = h
392 ; pagedispy = dy + off
393 ; pagey = pagey + off
394 ; pagevh = pagevh - off
397 let accu = e :: accu in
398 f ~pageno:(pageno+1)
399 ~pdimno
400 ~prev:curr
402 ~dy:(dy+pagevh+ips)
403 ~pdims:rest
404 ~cacheleft:(cacheleft-1)
405 ~accu
407 if state.invalidated = 0
408 then (
409 let accu =
411 ~pageno:0
412 ~pdimno:~-1
413 ~prev:(0,0,0)
414 ~py:0
415 ~dy:0
416 ~pdims:state.pages
417 ~cacheleft:(cblen state.pagecache)
418 ~accu:[]
420 List.rev accu
422 else
426 let clamp incr =
427 let y = state.y + incr in
428 let y = max 0 y in
429 let y = min y (state.maxy - (if conf.maxhfit then state.h else 0)) in
433 let getopaque pageno =
434 try Some (Hashtbl.find state.pagemap (pageno + 1, state.w, state.rotate))
435 with Not_found -> None
438 let cache pageno opaque =
439 Hashtbl.replace state.pagemap (pageno + 1, state.w, state.rotate) opaque
442 let validopaque opaque = String.length opaque > 0;;
444 let render l =
445 match getopaque l.pageno with
446 | None when not state.rendering ->
447 state.rendering <- true;
448 cache l.pageno "";
449 wcmd "render" [`i (l.pageno + 1)
450 ;`i l.pagedimno
451 ;`i l.pagew
452 ;`i l.pageh];
454 | _ -> ()
457 let loadlayout layout =
458 let rec f all = function
459 | l :: ls ->
460 begin match getopaque l.pageno with
461 | None -> render l; f false ls
462 | Some opaque -> f (all && validopaque opaque) ls
464 | [] -> all
466 f (layout <> []) layout;
469 let preload () =
470 if conf.preload
471 then
472 let evictedvisible =
473 let evictedopaque = cbpeekw state.pagecache in
474 List.exists (fun l ->
475 match getopaque l.pageno with
476 | Some opaque when validopaque opaque ->
477 evictedopaque = opaque
478 | otherwise -> false
479 ) state.layout
481 if not evictedvisible
482 then
483 let rely = yratio state.y in
484 let presentation = conf.presentation in
485 let interpagespace = conf.interpagespace in
486 let maxy = state.maxy in
487 conf.presentation <- false;
488 conf.interpagespace <- 0;
489 state.maxy <- calcheight ();
490 let y = truncate (float state.maxy *. rely) in
491 let y = if y < state.h then 0 else y - state.h in
492 let pages = layout y (state.h*3) in
493 List.iter render pages;
494 conf.presentation <- presentation;
495 conf.interpagespace <- interpagespace;
496 state.maxy <- maxy;
499 let gotoy y =
500 let y = max 0 y in
501 let y = min state.maxy y in
502 let pages = layout y state.h in
503 let ready = loadlayout pages in
504 state.ty <- yratio y;
505 if conf.showall
506 then (
507 if ready
508 then (
509 state.layout <- pages;
510 state.y <- y;
511 Glut.postRedisplay ();
514 else (
515 state.layout <- pages;
516 state.y <- y;
517 Glut.postRedisplay ();
519 preload ();
522 let gotoy_and_clear_text y =
523 gotoy y;
524 if not conf.verbose then state.text <- "";
527 let addnav () =
528 cbput state.hists.nav (yratio state.y);
529 cbrfollowlen state.hists.nav;
532 let getnav () =
533 let y = cbget state.hists.nav ~-1 in
534 truncate (y *. float state.maxy)
537 let gotopage n top =
538 let y, h = getpageyh n in
539 addnav ();
540 gotoy_and_clear_text (y + (truncate (top *. float h)));
543 let gotopage1 n top =
544 let y = getpagey n in
545 addnav ();
546 gotoy_and_clear_text (y + top);
549 let invalidate () =
550 state.layout <- [];
551 state.pages <- [];
552 state.rects <- [];
553 state.rects1 <- [];
554 state.invalidated <- state.invalidated + 1;
557 let scalecolor c =
558 let c = c *. state.colorscale in
559 (c, c, c);
562 let represent () =
563 let y =
564 match state.layout with
565 | [] ->
566 let rely = yratio state.y in
567 state.maxy <- calcheight ();
568 truncate (float state.maxy *. rely)
570 | l :: _ ->
571 state.maxy <- calcheight ();
572 getpagey l.pageno
574 gotoy y
577 let pagematrix () =
578 GlMat.mode `projection;
579 GlMat.load_identity ();
580 GlMat.rotate ~x:1.0 ~angle:180.0 ();
581 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
582 GlMat.scale3 (2.0 /. float state.w, 2.0 /. float state.h, 1.0);
585 let winmatrix () =
586 GlMat.mode `projection;
587 GlMat.load_identity ();
588 GlMat.rotate ~x:1.0 ~angle:180.0 ();
589 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
590 GlMat.scale3 (2.0 /. float state.winw, 2.0 /. float state.h, 1.0);
593 let reshape ~w ~h =
594 state.winw <- w;
595 let w = truncate (float w *. conf.zoom) - conf.scrollw in
596 state.w <- w;
597 state.h <- h;
598 GlMat.mode `modelview;
599 GlMat.load_identity ();
600 GlClear.color (scalecolor 1.0);
601 GlClear.clear [`color];
603 invalidate ();
604 wcmd "geometry" [`i state.w; `i h];
607 let showtext c s =
608 GlDraw.color (0.0, 0.0, 0.0);
609 GlDraw.rect
610 (0.0, float (state.h - 18))
611 (float (state.winw - conf.scrollw - 1), float state.h)
613 let font = Glut.BITMAP_8_BY_13 in
614 GlDraw.color (1.0, 1.0, 1.0);
615 GlPix.raster_pos ~x:0.0 ~y:(float (state.h - 5)) ();
616 Glut.bitmapCharacter ~font ~c:(Char.code c);
617 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s;
620 let enttext () =
621 let len = String.length state.text in
622 match state.textentry with
623 | None ->
624 if len > 0 then showtext ' ' state.text
626 | Some (c, text, _, _, _) ->
627 let s =
628 if len > 0
629 then
630 text ^ " [" ^ state.text ^ "]"
631 else
632 text
634 showtext c s;
637 let showtext c s =
638 if true
639 then (
640 state.text <- Printf.sprintf "%c%s" c s;
641 Glut.postRedisplay ();
643 else (
644 showtext c s;
645 Glut.swapBuffers ();
649 let act cmd =
650 match cmd.[0] with
651 | 'c' ->
652 state.pages <- [];
654 | 'D' ->
655 state.rects <- state.rects1;
656 Glut.postRedisplay ()
658 | 'C' ->
659 let n = Scanf.sscanf cmd "C %d" (fun n -> n) in
660 state.pagecount <- n;
661 state.invalidated <- state.invalidated - 1;
662 if state.invalidated = 0
663 then represent ()
665 | 't' ->
666 let s = Scanf.sscanf cmd "t %n"
667 (fun n -> String.sub cmd n (String.length cmd - n))
669 Glut.setWindowTitle s
671 | 'T' ->
672 let s = Scanf.sscanf cmd "T %n"
673 (fun n -> String.sub cmd n (String.length cmd - n))
675 if state.textentry = None
676 then (
677 state.text <- s;
678 showtext ' ' s;
680 else (
681 state.text <- s;
682 Glut.postRedisplay ();
685 | 'V' ->
686 if conf.verbose
687 then
688 let s = Scanf.sscanf cmd "V %n"
689 (fun n -> String.sub cmd n (String.length cmd - n))
691 state.text <- s;
692 showtext ' ' s;
694 | 'F' ->
695 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
696 Scanf.sscanf cmd "F %d %d %f %f %f %f %f %f %f %f"
697 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
698 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
700 let y = (getpagey pageno) + truncate y0 in
701 addnav ();
702 gotoy y;
703 state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)]
705 | 'R' ->
706 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
707 Scanf.sscanf cmd "R %d %d %f %f %f %f %f %f %f %f"
708 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
709 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
711 state.rects1 <-
712 (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
714 | 'r' ->
715 let n, w, h, r, p =
716 Scanf.sscanf cmd "r %d %d %d %d %s"
717 (fun n w h r p -> (n, w, h, r, p))
719 Hashtbl.replace state.pagemap (n, w, r) p;
720 let opaque = cbpeekw state.pagecache in
721 if validopaque opaque
722 then (
723 let k =
724 Hashtbl.fold
725 (fun k v a -> if v = opaque then k else a)
726 state.pagemap (-1, -1, -1)
728 wcmd "free" [`s opaque];
729 Hashtbl.remove state.pagemap k
731 cbput state.pagecache p;
732 state.rendering <- false;
733 if conf.showall
734 then gotoy (truncate (ceil (state.ty *. float state.maxy)))
735 else (
736 let visible = List.exists (fun l -> l.pageno + 1 = n) state.layout in
737 if visible
738 then gotoy state.y
739 else (ignore (loadlayout state.layout); preload ())
742 | 'l' ->
743 let (n, w, h) as pagelayout =
744 Scanf.sscanf cmd "l %d %d %d" (fun n w h -> n, w, h)
746 state.pages <- pagelayout :: state.pages
748 | 'o' ->
749 let (l, n, t, h, pos) =
750 Scanf.sscanf cmd "o %d %d %d %d %n" (fun l n t h pos -> l, n, t, h, pos)
752 let s = String.sub cmd pos (String.length cmd - pos) in
753 let s =
754 let l = String.length s in
755 let b = Buffer.create (String.length s) in
756 let rec loop pc2 i =
757 if i = l
758 then ()
759 else
760 let pc2 =
761 match s.[i] with
762 | '\xa0' when pc2 -> Buffer.add_char b ' '; false
763 | '\xc2' -> true
764 | c ->
765 let c = if Char.code c land 0x80 = 0 then c else '?' in
766 Buffer.add_char b c;
767 false
769 loop pc2 (i+1)
771 loop false 0;
772 Buffer.contents b
774 let outline = (s, l, n, float t /. float h) in
775 let outlines =
776 match state.outlines with
777 | Olist outlines -> Olist (outline :: outlines)
778 | Oarray _ -> Olist [outline]
779 | Onarrow _ -> Olist [outline]
781 state.outlines <- outlines
783 | _ ->
784 log "unknown cmd `%S'" cmd
787 let now = Unix.gettimeofday;;
789 let idle () =
790 let rec loop delay =
791 let r, _, _ = Unix.select [state.csock] [] [] delay in
792 begin match r with
793 | [] ->
794 if conf.autoscroll
795 then begin
796 let y = state.y + conf.scrollincr in
797 let y = if y >= state.maxy then 0 else y in
798 gotoy y;
799 state.text <- "";
800 end;
802 | _ ->
803 let cmd = readcmd state.csock in
804 act cmd;
805 loop 0.0
806 end;
807 in loop 0.001
810 let onhist cb = function
811 | HCprev -> cbget cb ~-1
812 | HCnext -> cbget cb 1
813 | HCfirst -> cbget cb ~-(cb.rc)
814 | HClast -> cbget cb (cb.len - 1 - cb.rc)
817 let search pattern forward =
818 if String.length pattern > 0
819 then
820 let pn, py =
821 match state.layout with
822 | [] -> 0, 0
823 | l :: _ ->
824 l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
826 let cmd =
827 let b = makecmd "search"
828 [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0)]
830 Buffer.add_char b ',';
831 Buffer.add_string b pattern;
832 Buffer.add_char b '\000';
833 Buffer.contents b;
835 writecmd state.csock cmd;
838 let intentry text key =
839 let c = Char.unsafe_chr key in
840 match c with
841 | '0' .. '9' ->
842 let s = "x" in s.[0] <- c;
843 let text = text ^ s in
844 TEcont text
846 | _ ->
847 state.text <- Printf.sprintf "invalid char (%d, `%c')" key c;
848 TEcont text
851 let addchar s c =
852 let b = Buffer.create (String.length s + 1) in
853 Buffer.add_string b s;
854 Buffer.add_char b c;
855 Buffer.contents b;
858 let textentry text key =
859 let c = Char.unsafe_chr key in
860 match c with
861 | _ when key >= 32 && key < 127 ->
862 let text = addchar text c in
863 TEcont text
865 | _ ->
866 log "unhandled key %d char `%c'" key (Char.unsafe_chr key);
867 TEcont text
870 let rotate angle =
871 state.rotate <- angle;
872 invalidate ();
873 wcmd "rotate" [`i angle];
876 let optentry text key =
877 let btos b = if b then "on" else "off" in
878 let c = Char.unsafe_chr key in
879 match c with
880 | 's' ->
881 let ondone s =
882 try conf.scrollincr <- int_of_string s with exc ->
883 state.text <- Printf.sprintf "bad integer `%s': %s"
884 s (Printexc.to_string exc)
886 TEswitch ('#', "", None, intentry, ondone)
888 | 'R' ->
889 let ondone s =
890 match try
891 Some (int_of_string s)
892 with exc ->
893 state.text <- Printf.sprintf "bad integer `%s': %s"
894 s (Printexc.to_string exc);
895 None
896 with
897 | Some angle -> rotate angle
898 | None -> ()
900 TEswitch ('^', "", None, intentry, ondone)
902 | 'i' ->
903 conf.icase <- not conf.icase;
904 TEdone ("case insensitive search " ^ (btos conf.icase))
906 | 'p' ->
907 conf.preload <- not conf.preload;
908 gotoy state.y;
909 TEdone ("preload " ^ (btos conf.preload))
911 | 'v' ->
912 conf.verbose <- not conf.verbose;
913 TEdone ("verbose " ^ (btos conf.verbose))
915 | 'h' ->
916 conf.maxhfit <- not conf.maxhfit;
917 state.maxy <- state.maxy + (if conf.maxhfit then -state.h else state.h);
918 TEdone ("maxhfit " ^ (btos conf.maxhfit))
920 | 'c' ->
921 conf.crophack <- not conf.crophack;
922 TEdone ("crophack " ^ btos conf.crophack)
924 | 'a' ->
925 conf.showall <- not conf.showall;
926 TEdone ("showall " ^ btos conf.showall)
928 | 'f' ->
929 conf.underinfo <- not conf.underinfo;
930 TEdone ("underinfo " ^ btos conf.underinfo)
932 | 'S' ->
933 let ondone s =
935 conf.interpagespace <- int_of_string s;
936 let rely = yratio state.y in
937 state.maxy <- calcheight ();
938 gotoy (truncate (float state.maxy *. rely));
939 with exc ->
940 state.text <- Printf.sprintf "bad integer `%s': %s"
941 s (Printexc.to_string exc)
943 TEswitch ('%', "", None, intentry, ondone)
945 | _ ->
946 state.text <- Printf.sprintf "bad option %d `%c'" key c;
947 TEstop
950 let maxoutlinerows () = (state.h - 31) / 16;;
952 let enterselector allowdel outlines errmsg =
953 if Array.length outlines = 0
954 then (
955 showtext ' ' errmsg;
957 else (
958 state.text <- "";
959 Glut.setCursor Glut.CURSOR_INHERIT;
960 let pageno =
961 match state.layout with
962 | [] -> -1
963 | {pageno=pageno} :: rest -> pageno
965 let active =
966 let rec loop n =
967 if n = Array.length outlines
968 then 0
969 else
970 let (_, _, outlinepageno, _) = outlines.(n) in
971 if outlinepageno >= pageno then n else loop (n+1)
973 loop 0
975 state.outline <-
976 Some (allowdel, active,
977 max 0 ((active - maxoutlinerows () / 2)), outlines, "");
978 Glut.postRedisplay ();
982 let enteroutlinemode () =
983 let outlines =
984 match state.outlines with
985 | Oarray a -> a
986 | Olist l ->
987 let a = Array.of_list (List.rev l) in
988 state.outlines <- Oarray a;
990 | Onarrow (a, b) -> a
992 enterselector false outlines "Document has no outline";
995 let enterbookmarkmode () =
996 let bookmarks = Array.of_list state.bookmarks in
997 enterselector true bookmarks "Document has no bookmarks (yet)";
1000 let quickbookmark ?title () =
1001 match state.layout with
1002 | [] -> ()
1003 | l :: _ ->
1004 let title =
1005 match title with
1006 | None ->
1007 let sec = Unix.gettimeofday () in
1008 let tm = Unix.localtime sec in
1009 Printf.sprintf "Quick %d visited (%d/%d/%d %d:%d)"
1010 l.pageno
1011 tm.Unix.tm_mday
1012 tm.Unix.tm_mon
1013 (tm.Unix.tm_year + 1900)
1014 tm.Unix.tm_hour
1015 tm.Unix.tm_min
1016 | Some title -> title
1018 state.bookmarks <-
1019 (title, 0, l.pageno, float l.pagey /. float l.pageh) :: state.bookmarks
1022 let doreshape w h =
1023 state.fullscreen <- None;
1024 Glut.reshapeWindow w h;
1027 let opendoc path password =
1028 invalidate ();
1029 state.path <- path;
1030 state.password <- password;
1031 Hashtbl.clear state.pagemap;
1033 writecmd state.csock ("open " ^ path ^ "\000" ^ password ^ "\000");
1034 Glut.setWindowTitle ("llpp " ^ Filename.basename path);
1035 wcmd "geometry" [`i state.w; `i state.h];
1038 let viewkeyboard ~key ~x ~y =
1039 let enttext te =
1040 state.textentry <- te;
1041 state.text <- "";
1042 enttext ();
1043 Glut.postRedisplay ()
1045 match state.textentry with
1046 | None ->
1047 let c = Char.chr key in
1048 begin match c with
1049 | '\027' | 'q' ->
1050 exit 0
1052 | '\008' ->
1053 let y = getnav () in
1054 gotoy_and_clear_text y
1056 | 'o' ->
1057 enteroutlinemode ()
1059 | 'u' ->
1060 state.rects <- [];
1061 state.text <- "";
1062 Glut.postRedisplay ()
1064 | '/' | '?' ->
1065 let ondone isforw s =
1066 cbput state.hists.pat s;
1067 cbrfollowlen state.hists.pat;
1068 state.searchpattern <- s;
1069 search s isforw
1071 enttext (Some (c, "", Some (onhist state.hists.pat),
1072 textentry, ondone (c ='/')))
1074 | '+' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1075 conf.zoom <- min 2.2 (conf.zoom +. 0.1);
1076 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1077 reshape state.winw state.h
1079 | '+' ->
1080 let ondone s =
1081 let n =
1082 try int_of_string s with exc ->
1083 state.text <- Printf.sprintf "bad integer `%s': %s"
1084 s (Printexc.to_string exc);
1085 max_int
1087 if n != max_int
1088 then (
1089 conf.pagebias <- n;
1090 state.text <- "page bias is now " ^ string_of_int n;
1093 enttext (Some ('+', "", None, intentry, ondone))
1095 | '-' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1096 conf.zoom <- max 0.1 (conf.zoom -. 0.1);
1097 if conf.zoom <= 1.0 then state.x <- 0;
1098 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1099 reshape state.winw state.h;
1101 | '-' ->
1102 let ondone msg =
1103 state.text <- msg;
1105 enttext (Some ('-', "", None, optentry, ondone))
1107 | '0' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1108 state.x <- 0;
1109 conf.zoom <- 1.0;
1110 state.text <- "zoom is 100%";
1111 reshape state.winw state.h
1113 | '0' .. '9' ->
1114 let ondone s =
1115 let n =
1116 try int_of_string s with exc ->
1117 state.text <- Printf.sprintf "bad integer `%s': %s"
1118 s (Printexc.to_string exc);
1121 if n >= 0
1122 then (
1123 addnav ();
1124 cbput state.hists.pag (string_of_int n);
1125 cbrfollowlen state.hists.pag;
1126 gotoy_and_clear_text (getpagey (n + conf.pagebias - 1))
1129 let pageentry text key =
1130 match Char.unsafe_chr key with
1131 | 'g' -> TEdone text
1132 | _ -> intentry text key
1134 let text = "x" in text.[0] <- c;
1135 enttext (Some (':', text, Some (onhist state.hists.pag),
1136 pageentry, ondone))
1138 | 'b' ->
1139 conf.scrollw <- if conf.scrollw > 0 then 0 else 5;
1140 reshape state.winw state.h;
1142 | 'l' ->
1143 conf.hlinks <- not conf.hlinks;
1144 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
1145 Glut.postRedisplay ()
1147 | 'a' ->
1148 conf.autoscroll <- not conf.autoscroll
1150 | 'P' ->
1151 conf.presentation <- not conf.presentation;
1152 showtext ' ' ("presentation mode " ^
1153 if conf.presentation then "on" else "off");
1154 represent ()
1156 | 'f' ->
1157 begin match state.fullscreen with
1158 | None ->
1159 state.fullscreen <- Some (state.winw, state.h);
1160 Glut.fullScreen ()
1161 | Some (w, h) ->
1162 state.fullscreen <- None;
1163 doreshape w h
1166 | 'g' ->
1167 gotoy_and_clear_text 0
1169 | 'n' ->
1170 search state.searchpattern true
1172 | 'p' | 'N' ->
1173 search state.searchpattern false
1175 | 't' ->
1176 begin match state.layout with
1177 | [] -> ()
1178 | l :: _ ->
1179 gotoy_and_clear_text (getpagey l.pageno)
1182 | ' ' ->
1183 begin match List.rev state.layout with
1184 | [] -> ()
1185 | l :: _ ->
1186 let pageno = min (l.pageno+1) (state.pagecount-1) in
1187 gotoy_and_clear_text (getpagey pageno)
1190 | '\127' ->
1191 begin match state.layout with
1192 | [] -> ()
1193 | l :: _ ->
1194 let pageno = max 0 (l.pageno-1) in
1195 gotoy_and_clear_text (getpagey pageno)
1198 | '=' ->
1199 let f (fn, ln) l =
1200 if fn = -1 then l.pageno, l.pageno else fn, l.pageno
1202 let fn, ln = List.fold_left f (-1, -1) state.layout in
1203 let s =
1204 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1205 let percent =
1206 if maxy <= 0
1207 then 100.
1208 else (100. *. (float state.y /. float maxy)) in
1209 if fn = ln
1210 then
1211 Printf.sprintf "Page %d of %d %.2f%%"
1212 (fn+1) state.pagecount percent
1213 else
1214 Printf.sprintf
1215 "Pages %d-%d of %d %.2f%%"
1216 (fn+1) (ln+1) state.pagecount percent
1218 showtext ' ' s;
1220 | 'w' ->
1221 begin match state.layout with
1222 | [] -> ()
1223 | l :: _ ->
1224 doreshape (l.pagew + conf.scrollw) l.pageh;
1225 Glut.postRedisplay ();
1228 | '\'' ->
1229 enterbookmarkmode ()
1231 | 'm' ->
1232 let ondone s =
1233 match state.layout with
1234 | l :: _ ->
1235 state.bookmarks <-
1236 (s, 0, l.pageno, float l.pagey /. float l.pageh)
1237 :: state.bookmarks
1238 | _ -> ()
1240 enttext (Some ('~', "", None, textentry, ondone))
1242 | '~' ->
1243 quickbookmark ();
1244 showtext ' ' "Quick bookmark added";
1246 | 'z' ->
1247 begin match state.layout with
1248 | l :: _ ->
1249 let a = getpagewh l.pagedimno in
1250 let w, h =
1251 if conf.crophack
1252 then
1253 (truncate (1.8 *. (a.(1) -. a.(0))),
1254 truncate (1.2 *. (a.(3) -. a.(0))))
1255 else
1256 (truncate (a.(1) -. a.(0)),
1257 truncate (a.(3) -. a.(0)))
1259 doreshape (w + conf.scrollw) (h + conf.interpagespace);
1260 Glut.postRedisplay ();
1262 | [] -> ()
1265 | '<' | '>' ->
1266 rotate (state.rotate + (if c = '>' then 30 else -30));
1268 | '[' | ']' ->
1269 state.colorscale <-
1270 max 0.0
1271 (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0);
1272 Glut.postRedisplay ()
1274 | 'k' -> gotoy (clamp (-conf.scrollincr))
1275 | 'j' -> gotoy (clamp conf.scrollincr)
1277 | 'r' -> opendoc state.path state.password
1279 | _ ->
1280 vlog "huh? %d %c" key (Char.chr key);
1283 | Some (c, text, onhist, onkey, ondone) when key = 8 ->
1284 let len = String.length text in
1285 if len = 0
1286 then (
1287 state.textentry <- None;
1288 Glut.postRedisplay ();
1290 else (
1291 let s = String.sub text 0 (len - 1) in
1292 enttext (Some (c, s, onhist, onkey, ondone))
1295 | Some (c, text, onhist, onkey, ondone) ->
1296 begin match Char.unsafe_chr key with
1297 | '\r' | '\n' ->
1298 ondone text;
1299 state.textentry <- None;
1300 Glut.postRedisplay ()
1302 | '\027' ->
1303 state.textentry <- None;
1304 Glut.postRedisplay ()
1306 | _ ->
1307 begin match onkey text key with
1308 | TEdone text ->
1309 state.textentry <- None;
1310 ondone text;
1311 Glut.postRedisplay ()
1313 | TEcont text ->
1314 enttext (Some (c, text, onhist, onkey, ondone));
1316 | TEstop ->
1317 state.textentry <- None;
1318 Glut.postRedisplay ()
1320 | TEswitch te ->
1321 state.textentry <- Some te;
1322 Glut.postRedisplay ()
1323 end;
1324 end;
1327 let narrow outlines pattern =
1328 let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in
1329 match reopt with
1330 | None -> None
1331 | Some re ->
1332 let rec fold accu n =
1333 if n = -1
1334 then accu
1335 else
1336 let (s, _, _, _) as o = outlines.(n) in
1337 let accu =
1338 if (try ignore (Str.search_forward re s 0); true
1339 with Not_found -> false)
1340 then (o :: accu)
1341 else accu
1343 fold accu (n-1)
1345 let matched = fold [] (Array.length outlines - 1) in
1346 if matched = [] then None else Some (Array.of_list matched)
1349 let outlinekeyboard ~key ~x ~y (allowdel, active, first, outlines, qsearch) =
1350 let search active pattern incr =
1351 let dosearch re =
1352 let rec loop n =
1353 if n = Array.length outlines || n = -1
1354 then None
1355 else
1356 let (s, _, _, _) = outlines.(n) in
1358 (try ignore (Str.search_forward re s 0); true
1359 with Not_found -> false)
1360 then Some n
1361 else loop (n + incr)
1363 loop active
1366 let re = Str.regexp_case_fold pattern in
1367 dosearch re
1368 with Failure s ->
1369 state.text <- s;
1370 None
1372 let firstof active = max 0 (active - maxoutlinerows () / 2) in
1373 match key with
1374 | 27 ->
1375 if String.length qsearch = 0
1376 then (
1377 state.text <- "";
1378 state.outline <- None;
1379 Glut.postRedisplay ();
1381 else (
1382 state.text <- "";
1383 state.outline <- Some (allowdel, active, first, outlines, "");
1384 Glut.postRedisplay ();
1387 | 18 | 19 ->
1388 let incr = if key = 18 then -1 else 1 in
1389 let active, first =
1390 match search (active + incr) qsearch incr with
1391 | None ->
1392 state.text <- qsearch ^ " [not found]";
1393 active, first
1394 | Some active ->
1395 state.text <- qsearch;
1396 active, firstof active
1398 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1399 Glut.postRedisplay ();
1401 | 8 ->
1402 let len = String.length qsearch in
1403 if len = 0
1404 then ()
1405 else (
1406 if len = 1
1407 then (
1408 state.text <- "";
1409 state.outline <- Some (allowdel, active, first, outlines, "");
1411 else
1412 let qsearch = String.sub qsearch 0 (len - 1) in
1413 let active, first =
1414 match search active qsearch ~-1 with
1415 | None ->
1416 state.text <- qsearch ^ " [not found]";
1417 active, first
1418 | Some active ->
1419 state.text <- qsearch;
1420 active, firstof active
1422 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1424 Glut.postRedisplay ()
1426 | 13 ->
1427 if active < Array.length outlines
1428 then (
1429 let (_, _, n, t) = outlines.(active) in
1430 gotopage n t;
1432 state.text <- "";
1433 if allowdel then state.bookmarks <- Array.to_list outlines;
1434 state.outline <- None;
1435 Glut.postRedisplay ();
1437 | _ when key >= 32 && key < 127 ->
1438 let pattern = addchar qsearch (Char.chr key) in
1439 let active, first =
1440 match search active pattern 1 with
1441 | None ->
1442 state.text <- pattern ^ " [not found]";
1443 active, first
1444 | Some active ->
1445 state.text <- pattern;
1446 active, firstof active
1448 state.outline <- Some (allowdel, active, first, outlines, pattern);
1449 Glut.postRedisplay ()
1451 | 14 when not allowdel ->
1452 let optoutlines = narrow outlines qsearch in
1453 begin match optoutlines with
1454 | None -> state.text <- "can't narrow"
1455 | Some outlines ->
1456 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1457 match state.outlines with
1458 | Olist l -> ()
1459 | Oarray a -> state.outlines <- Onarrow (outlines, a)
1460 | Onarrow (a, b) -> state.outlines <- Onarrow (outlines, b)
1461 end;
1462 Glut.postRedisplay ()
1464 | 21 when not allowdel ->
1465 let outline =
1466 match state.outlines with
1467 | Oarray a -> a
1468 | Olist l ->
1469 let a = Array.of_list (List.rev l) in
1470 state.outlines <- Oarray a;
1472 | Onarrow (a, b) ->
1473 state.outlines <- Oarray b;
1476 state.outline <- Some (allowdel, 0, 0, outline, qsearch);
1477 Glut.postRedisplay ()
1479 | 12 ->
1480 state.outline <-
1481 Some (allowdel, active, firstof active, outlines, qsearch);
1482 Glut.postRedisplay ()
1484 | 127 when allowdel ->
1485 let len = Array.length outlines - 1 in
1486 if len = 0
1487 then (
1488 state.outline <- None;
1489 state.bookmarks <- [];
1491 else (
1492 let bookmarks = Array.init len
1493 (fun i ->
1494 let i = if i >= active then i + 1 else i in
1495 outlines.(i)
1498 state.outline <-
1499 Some (allowdel,
1500 min active (len-1),
1501 min first (len-1),
1502 bookmarks, qsearch)
1505 Glut.postRedisplay ()
1507 | _ -> log "unknown key %d" key
1510 let keyboard ~key ~x ~y =
1511 if key = 7
1512 then
1513 wcmd "interrupt" []
1514 else
1515 match state.outline with
1516 | None -> viewkeyboard ~key ~x ~y
1517 | Some outline -> outlinekeyboard ~key ~x ~y outline
1520 let special ~key ~x ~y =
1521 match state.outline with
1522 | None ->
1523 begin match state.textentry with
1524 | None ->
1525 let y =
1526 match key with
1527 | Glut.KEY_F3 -> search state.searchpattern true; state.y
1528 | Glut.KEY_UP -> clamp (-conf.scrollincr)
1529 | Glut.KEY_DOWN -> clamp conf.scrollincr
1530 | Glut.KEY_PAGE_UP ->
1531 if Glut.getModifiers () land Glut.active_ctrl != 0
1532 then
1533 match state.layout with
1534 | [] -> state.y
1535 | l :: _ -> state.y - l.pagey
1536 else
1537 clamp (-state.h)
1538 | Glut.KEY_PAGE_DOWN ->
1539 if Glut.getModifiers () land Glut.active_ctrl != 0
1540 then
1541 match List.rev state.layout with
1542 | [] -> state.y
1543 | l :: _ -> getpagey l.pageno
1544 else
1545 clamp state.h
1546 | Glut.KEY_HOME -> addnav (); 0
1547 | Glut.KEY_END ->
1548 addnav ();
1549 state.maxy - (if conf.maxhfit then state.h else 0)
1551 | Glut.KEY_RIGHT when conf.zoom > 1.0 ->
1552 state.x <- state.x - 10;
1553 state.y
1554 | Glut.KEY_LEFT when conf.zoom > 1.0 ->
1555 state.x <- state.x + 10;
1556 state.y
1558 | _ -> state.y
1560 gotoy_and_clear_text y
1562 | Some (c, s, Some onhist, onkey, ondone) ->
1563 let s =
1564 match key with
1565 | Glut.KEY_UP -> onhist HCprev
1566 | Glut.KEY_DOWN -> onhist HCnext
1567 | Glut.KEY_HOME -> onhist HCfirst
1568 | Glut.KEY_END -> onhist HClast
1569 | _ -> state.text
1571 state.textentry <- Some (c, s, Some onhist, onkey, ondone);
1572 Glut.postRedisplay ()
1574 | _ -> ()
1577 | Some (allowdel, active, first, outlines, qsearch) ->
1578 let maxrows = maxoutlinerows () in
1579 let navigate incr =
1580 let active = active + incr in
1581 let active = max 0 (min active (Array.length outlines - 1)) in
1582 let first =
1583 if active > first
1584 then
1585 let rows = active - first in
1586 if rows > maxrows then active - maxrows else first
1587 else active
1589 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1590 Glut.postRedisplay ()
1592 match key with
1593 | Glut.KEY_UP -> navigate ~-1
1594 | Glut.KEY_DOWN -> navigate 1
1595 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
1596 | Glut.KEY_PAGE_DOWN -> navigate maxrows
1598 | Glut.KEY_HOME ->
1599 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1600 Glut.postRedisplay ()
1602 | Glut.KEY_END ->
1603 let active = Array.length outlines - 1 in
1604 let first = max 0 (active - maxrows) in
1605 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1606 Glut.postRedisplay ()
1608 | _ -> ()
1611 let drawplaceholder l =
1612 GlDraw.color (scalecolor 1.0);
1613 GlDraw.rect
1614 (0.0, float l.pagedispy)
1615 (float l.pagew, float (l.pagedispy + l.pagevh))
1617 let x = 0.0
1618 and y = float (l.pagedispy + 13) in
1619 let font = Glut.BITMAP_8_BY_13 in
1620 GlDraw.color (0.0, 0.0, 0.0);
1621 GlPix.raster_pos ~x ~y ();
1622 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c))
1623 ("Loading " ^ string_of_int l.pageno);
1626 let now () = Unix.gettimeofday ();;
1628 let drawpage i l =
1629 begin match getopaque l.pageno with
1630 | Some opaque when validopaque opaque ->
1631 if state.textentry = None
1632 then GlDraw.color (scalecolor 1.0)
1633 else GlDraw.color (scalecolor 0.4);
1634 let a = now () in
1635 draw (l.pagedispy, l.pagew, l.pagevh, l.pagey, conf.hlinks)
1636 opaque;
1637 let b = now () in
1638 let d = b-.a in
1639 vlog "draw %d %f sec" l.pageno d;
1641 | _ ->
1642 drawplaceholder l;
1643 end;
1644 l.pagedispy + l.pagevh;
1647 let scrollindicator () =
1648 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1649 GlDraw.color (0.64 , 0.64, 0.64);
1650 GlDraw.rect
1651 (float (state.winw - conf.scrollw), 0.)
1652 (float state.winw, float state.h)
1654 GlDraw.color (0.0, 0.0, 0.0);
1655 let sh = (float (maxy + state.h) /. float state.h) in
1656 let sh = float state.h /. sh in
1657 let sh = max sh (float conf.scrollh) in
1659 let percent =
1660 if state.y = state.maxy
1661 then 1.0
1662 else float state.y /. float maxy
1664 let position = (float state.h -. sh) *. percent in
1666 let position =
1667 if position +. sh > float state.h
1668 then
1669 float state.h -. sh
1670 else
1671 position
1673 GlDraw.rect
1674 (float (state.winw - conf.scrollw), position)
1675 (float state.winw, position +. sh)
1679 let showsel margin =
1680 match state.mstate with
1681 | Mnone | Mpan _ ->
1684 | Msel ((x0, y0), (x1, y1)) ->
1685 let rec loop = function
1686 | l :: ls ->
1687 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1688 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1689 then
1690 match getopaque l.pageno with
1691 | Some opaque when validopaque opaque ->
1692 let oy = -l.pagey + l.pagedispy in
1693 seltext opaque
1694 (x0 - margin - state.x, y0,
1695 x1 - margin - state.x, y1) oy;
1697 | _ -> ()
1698 else loop ls
1699 | [] -> ()
1701 loop state.layout
1704 let showrects () =
1705 let panx = float state.x in
1706 Gl.enable `blend;
1707 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
1708 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1709 List.iter
1710 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
1711 List.iter (fun l ->
1712 if l.pageno = pageno
1713 then (
1714 let d = float (l.pagedispy - l.pagey) in
1715 GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5;
1716 GlDraw.begins `quads;
1718 GlDraw.vertex2 (x0+.panx, y0+.d);
1719 GlDraw.vertex2 (x1+.panx, y1+.d);
1720 GlDraw.vertex2 (x2+.panx, y2+.d);
1721 GlDraw.vertex2 (x3+.panx, y3+.d);
1723 GlDraw.ends ();
1725 ) state.layout
1726 ) state.rects
1728 Gl.disable `blend;
1731 let showoutline = function
1732 | None -> ()
1733 | Some (allowdel, active, first, outlines, qsearch) ->
1734 Gl.enable `blend;
1735 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1736 GlDraw.color (0., 0., 0.) ~alpha:0.85;
1737 GlDraw.rect (0., 0.) (float state.w, float state.h);
1738 Gl.disable `blend;
1740 GlDraw.color (1., 1., 1.);
1741 let font = Glut.BITMAP_9_BY_15 in
1742 let draw_string x y s =
1743 GlPix.raster_pos ~x ~y ();
1744 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
1746 let rec loop row =
1747 if row = Array.length outlines || (row - first) * 16 > state.h
1748 then ()
1749 else (
1750 let (s, l, _, _) = outlines.(row) in
1751 let y = (row - first) * 16 in
1752 let x = 5 + 15*l in
1753 if row = active
1754 then (
1755 Gl.enable `blend;
1756 GlDraw.polygon_mode `both `line;
1757 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1758 GlDraw.color (1., 1., 1.) ~alpha:0.9;
1759 GlDraw.rect (0., float (y + 1))
1760 (float (state.w - 1), float (y + 18));
1761 GlDraw.polygon_mode `both `fill;
1762 Gl.disable `blend;
1763 GlDraw.color (1., 1., 1.);
1765 draw_string (float x) (float (y + 16)) s;
1766 loop (row+1)
1769 loop first
1772 let display () =
1773 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1774 GlDraw.viewport margin 0 state.w state.h;
1775 pagematrix ();
1776 GlClear.color (scalecolor 0.5);
1777 GlClear.clear [`color];
1778 if state.x != 0
1779 then (
1780 let x = float state.x in
1781 GlMat.translate ~x ();
1783 if conf.zoom > 1.0
1784 then (
1785 Gl.enable `scissor_test;
1786 GlMisc.scissor 0 0 (state.winw - conf.scrollw) state.h;
1788 let _lasty = List.fold_left drawpage 0 (state.layout) in
1789 if conf.zoom > 1.0
1790 then
1791 Gl.disable `scissor_test
1793 if state.x != 0
1794 then (
1795 let x = -.float state.x in
1796 GlMat.translate ~x ();
1798 showrects ();
1799 showsel margin;
1800 GlDraw.viewport 0 0 state.winw state.h;
1801 winmatrix ();
1802 scrollindicator ();
1803 showoutline state.outline;
1804 enttext ();
1805 Glut.swapBuffers ();
1808 let getunder x y =
1809 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1810 let x = x - margin - state.x in
1811 let rec f = function
1812 | l :: rest ->
1813 begin match getopaque l.pageno with
1814 | Some opaque when validopaque opaque ->
1815 let y = y - l.pagedispy in
1816 if y > 0
1817 then
1818 let y = l.pagey + y in
1819 match whatsunder opaque x y with
1820 | Unone -> f rest
1821 | under -> under
1822 else
1823 f rest
1824 | _ ->
1825 f rest
1827 | [] -> Unone
1829 f state.layout
1832 let mouse ~button ~bstate ~x ~y =
1833 match button with
1834 | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP ->
1835 let incr =
1836 if n = 3
1837 then
1838 -conf.scrollincr
1839 else
1840 conf.scrollincr
1842 let incr = incr * 2 in
1843 let y = clamp incr in
1844 gotoy_and_clear_text y
1846 | Glut.LEFT_BUTTON when state.outline = None
1847 && Glut.getModifiers () land Glut.active_ctrl != 0 ->
1848 if bstate = Glut.DOWN
1849 then (
1850 Glut.setCursor Glut.CURSOR_CROSSHAIR;
1851 state.mstate <- Mpan (x, y)
1853 else
1854 state.mstate <- Mnone
1856 | Glut.LEFT_BUTTON when state.outline = None ->
1857 let dest = if bstate = Glut.DOWN then getunder x y else Unone in
1858 begin match dest with
1859 | Ulinkgoto (pageno, top) ->
1860 if pageno >= 0
1861 then
1862 gotopage1 pageno top
1864 | Ulinkuri s ->
1865 print_endline s
1867 | Unone when bstate = Glut.DOWN ->
1868 Glut.setCursor Glut.CURSOR_CROSSHAIR;
1869 state.mstate <- Mpan (x, y);
1871 | Unone | Utext _ ->
1872 if bstate = Glut.DOWN
1873 then (
1874 if state.rotate mod 360 = 0
1875 then (
1876 state.mstate <- Msel ((x, y), (x, y));
1877 Glut.postRedisplay ()
1880 else (
1881 match state.mstate with
1882 | Mnone -> ()
1884 | Mpan _ ->
1885 Glut.setCursor Glut.CURSOR_INHERIT;
1886 state.mstate <- Mnone
1888 | Msel ((x0, y0), (x1, y1)) ->
1889 let f l =
1890 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1891 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1892 then
1893 match getopaque l.pageno with
1894 | Some opaque when validopaque opaque ->
1895 copysel opaque
1896 | _ -> ()
1898 List.iter f state.layout;
1899 copysel ""; (* ugly *)
1900 Glut.setCursor Glut.CURSOR_INHERIT;
1901 state.mstate <- Mnone;
1905 | _ ->
1908 let mouse ~button ~state ~x ~y = mouse button state x y;;
1910 let motion ~x ~y =
1911 if state.outline = None
1912 then
1913 match state.mstate with
1914 | Mnone -> ()
1916 | Mpan (x0, y0) ->
1917 let dx = x - x0
1918 and dy = y0 - y in
1919 state.mstate <- Mpan (x, y);
1920 if conf.zoom > 1.0 then state.x <- state.x + dx;
1921 let y = clamp dy in
1922 gotoy_and_clear_text y
1924 | Msel (a, _) ->
1925 state.mstate <- Msel (a, (x, y));
1926 Glut.postRedisplay ()
1929 let pmotion ~x ~y =
1930 if state.outline = None
1931 then
1932 match state.mstate with
1933 | Mnone ->
1934 begin match getunder x y with
1935 | Unone -> Glut.setCursor Glut.CURSOR_INHERIT
1936 | Ulinkuri uri ->
1937 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
1938 Glut.setCursor Glut.CURSOR_INFO
1939 | Ulinkgoto (page, y) ->
1940 if conf.underinfo then showtext 'p' ("age: " ^ string_of_int page);
1941 Glut.setCursor Glut.CURSOR_INFO
1942 | Utext s ->
1943 if conf.underinfo then showtext 'f' ("ont: " ^ s);
1944 Glut.setCursor Glut.CURSOR_TEXT
1947 | Mpan _ | Msel _ ->
1951 let () =
1952 let statepath =
1953 let home =
1954 if Sys.os_type = "Win32"
1955 then
1956 try Sys.getenv "HOMEPATH" with Not_found -> ""
1957 else
1958 try Filename.concat (Sys.getenv "HOME") ".config" with Not_found -> ""
1960 Filename.concat home "llpp"
1962 let pstate =
1964 let ic = open_in_bin statepath in
1965 let hash = input_value ic in
1966 close_in ic;
1967 hash
1968 with exn ->
1969 if false
1970 then
1971 prerr_endline ("Error loading state " ^ Printexc.to_string exn)
1973 Hashtbl.create 1
1975 let savestate () =
1977 let w, h =
1978 match state.fullscreen with
1979 | None -> state.winw, state.h
1980 | Some wh -> wh
1982 Hashtbl.replace pstate state.path (state.bookmarks, w, h);
1983 let oc = open_out_bin statepath in
1984 output_value oc pstate
1985 with exn ->
1986 if false
1987 then
1988 prerr_endline ("Error saving state " ^ Printexc.to_string exn)
1991 let setstate () =
1993 let statebookmarks, statew, stateh = Hashtbl.find pstate state.path in
1994 state.w <- statew;
1995 state.h <- stateh;
1996 state.bookmarks <- statebookmarks;
1997 with Not_found -> ()
1998 | exn ->
1999 prerr_endline ("Error setting state " ^ Printexc.to_string exn)
2002 Arg.parse
2003 ["-p", Arg.String (fun s -> state.password <- s) , "password"]
2004 (fun s -> state.path <- s)
2005 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\noptions:")
2007 let name =
2008 if String.length state.path = 0
2009 then (prerr_endline "filename missing"; exit 1)
2010 else state.path
2013 setstate ();
2014 let _ = Glut.init Sys.argv in
2015 let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in
2016 let () = Glut.initWindowSize state.w state.h in
2017 let _ = Glut.createWindow ("llpp " ^ Filename.basename name) in
2019 let csock, ssock =
2020 if Sys.os_type = "Unix"
2021 then
2022 Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
2023 else
2024 let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, 1337) in
2025 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2026 Unix.setsockopt sock Unix.SO_REUSEADDR true;
2027 Unix.bind sock addr;
2028 Unix.listen sock 1;
2029 let csock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2030 Unix.connect csock addr;
2031 let ssock, _ = Unix.accept sock in
2032 Unix.close sock;
2033 let opts sock =
2034 Unix.setsockopt sock Unix.TCP_NODELAY true;
2035 Unix.setsockopt_optint sock Unix.SO_LINGER None;
2037 opts ssock;
2038 opts csock;
2039 at_exit (fun () -> Unix.shutdown ssock Unix.SHUTDOWN_ALL);
2040 ssock, csock
2043 let () = Glut.displayFunc display in
2044 let () = Glut.reshapeFunc reshape in
2045 let () = Glut.keyboardFunc keyboard in
2046 let () = Glut.specialFunc special in
2047 let () = Glut.idleFunc (Some idle) in
2048 let () = Glut.mouseFunc mouse in
2049 let () = Glut.motionFunc motion in
2050 let () = Glut.passiveMotionFunc pmotion in
2052 init ssock;
2053 state.csock <- csock;
2054 state.ssock <- ssock;
2055 state.text <- "Opening " ^ name;
2056 writecmd state.csock ("open " ^ state.path ^ "\000" ^ state.password ^ "\000");
2058 at_exit savestate;
2060 let rec handlelablglutbug () =
2062 Glut.mainLoop ();
2063 with Glut.BadEnum "key in special_of_int" ->
2064 showtext '!' " LablGlut bug: special key not recognized";
2065 handlelablglutbug ()
2067 handlelablglutbug ();