Rename getenvwithdef to getenvdef
[llpp.git] / listview.ml
blob51ee16a95326554469a1193990f837bea356e817
1 open Utils;;
2 open Glutils;;
3 open Config;;
5 let scrollph y maxy =
6 let sh = float (maxy + state.winh) /. float state.winh in
7 let sh = float state.winh /. sh in
8 let sh = max sh (float conf.scrollh) in
10 let percent = float y /. float maxy in
11 let position = (float state.winh -. sh) *. percent in
13 let position =
14 if position +. sh > float state.winh
15 then float state.winh -. sh
16 else position
18 position, sh;
21 let isbirdseye = function
22 | Birdseye _ -> true
23 | Textentry _ | View | LinkNav _ -> false
26 let istextentry = function
27 | Textentry _ -> true
28 | Birdseye _ | View | LinkNav _ -> false
31 let vscrollw () =
32 if state.uioh#alwaysscrolly || ((conf.scrollb land scrollbvv != 0)
33 && (state.maxy > state.winh))
34 then conf.scrollbw
35 else 0
38 let vscrollhit x =
39 if conf.leftscroll
40 then x < vscrollw ()
41 else x > state.winw - vscrollw ()
44 let firstof first active =
45 if first > active || abs (first - active) > fstate.maxrows - 1
46 then max 0 (active - (fstate.maxrows/2))
47 else first
50 let calcfirst first active =
51 if active > first
52 then
53 let rows = active - first in
54 if rows > fstate.maxrows then active - fstate.maxrows else first
55 else active
58 let enttext () =
59 let len = String.length state.text in
60 let x0 = if conf.leftscroll then vscrollw () else 0 in
61 let drawstring s =
62 let hscrollh =
63 match state.mode with
64 | Textentry _ | View | LinkNav _ ->
65 let h, _, _ = state.uioh#scrollpw in
67 | Birdseye _ -> 0
69 let rect x w =
70 filledrect
71 x (float (state.winh - (fstate.fontsize + 4) - hscrollh))
72 (x+.w) (float (state.winh - hscrollh))
75 let w = float (state.winw - 1 - vscrollw ()) in
76 if state.progress >= 0.0 && state.progress < 1.0
77 then (
78 GlDraw.color (0.3, 0.3, 0.3);
79 let w1 = w *. state.progress in
80 rect (float x0) w1;
81 GlDraw.color (0.0, 0.0, 0.0);
82 rect (float x0+.w1) (float x0+.w-.w1)
84 else (
85 GlDraw.color (0.0, 0.0, 0.0);
86 rect (float x0) w;
89 GlDraw.color (1.0, 1.0, 1.0);
90 drawstring
91 fstate.fontsize
92 (if conf.leftscroll then x0 + 2 else x0 + if len > 0 then 8 else 2)
93 (state.winh - hscrollh - 5) s;
95 let s =
96 match state.mode with
97 | Textentry ((prefix, text, _, _, _, _), _) ->
98 let s =
99 if len > 0
100 then Printf.sprintf "%s%s_ [%s]" prefix text state.text
101 else Printf.sprintf "%s%s_" prefix text
105 | Birdseye _ | View | LinkNav _ -> state.text
107 let s =
108 if state.newerrmsgs
109 then (
110 if not (istextentry state.mode) && state.uioh#eformsgs
111 then
112 let s1 = "(press 'e' to review error messasges)" in
113 if nonemptystr s then s ^ " " ^ s1 else s1
114 else s
116 else s
118 if nonemptystr s
119 then drawstring s
122 let textentrykeyboard
123 key _mask ((c, text, opthist, onkey, ondone, cancelonempty), onleave) =
124 state.text <- E.s;
125 let enttext te =
126 state.mode <- Textentry (te, onleave);
127 enttext ();
128 postRedisplay "textentrykeyboard enttext";
130 let histaction cmd =
131 match opthist with
132 | None -> ()
133 | Some (action, _) ->
134 state.mode <- Textentry (
135 (c, action cmd, opthist,
136 onkey, ondone, cancelonempty),
137 onleave);
138 postRedisplay "textentry histaction"
140 let open Keys in
141 let kt = Wsi.kc2kt key in
142 match [@warning "-4"] kt with
143 | Backspace ->
144 if emptystr text && cancelonempty
145 then (
146 onleave Cancel;
147 postRedisplay "textentrykeyboard after cancel";
149 else
150 let s = withoutlastutf8 text in
151 enttext (c, s, opthist, onkey, ondone, cancelonempty)
153 | Enter ->
154 ondone text;
155 onleave Confirm;
156 postRedisplay "textentrykeyboard after confirm"
158 | Up -> histaction HCprev
159 | Down -> histaction HCnext
160 | Home -> histaction HCfirst
161 | End -> histaction HClast
163 | Escape ->
164 if emptystr text
165 then (
166 begin match opthist with
167 | None -> ()
168 | Some (_, onhistcancel) -> onhistcancel ()
169 end;
170 onleave Cancel;
171 state.text <- E.s;
172 postRedisplay "textentrykeyboard after cancel2"
174 else enttext (c, E.s, opthist, onkey, ondone, cancelonempty)
176 | Delete -> ()
178 | Code _ | Ascii _ ->
179 begin match onkey text kt with
180 | TEdone text ->
181 ondone text;
182 onleave Confirm;
183 postRedisplay "textentrykeyboard after confirm2";
185 | TEcont text ->
186 enttext (c, text, opthist, onkey, ondone, cancelonempty);
188 | TEstop ->
189 onleave Cancel;
190 postRedisplay "textentrykeyboard after cancel3";
192 | TEswitch te ->
193 state.mode <- Textentry (te, onleave);
194 postRedisplay "textentrykeyboard switch";
196 | _ -> vlog "unhandled key"
199 class type lvsource =
200 object
201 method getitemcount : int
202 method getitem : int -> (string * int)
203 method hasaction : int -> bool
204 method exit : uioh:uioh ->
205 cancel:bool ->
206 active:int ->
207 first:int ->
208 pan:int ->
209 uioh option
210 method getactive : int
211 method getfirst : int
212 method getpan : int
213 method getminfo : (int * int) array
214 end;;
216 class virtual lvsourcebase =
217 object
218 val mutable m_active = 0
219 val mutable m_first = 0
220 val mutable m_pan = 0
221 method getactive = m_active
222 method getfirst = m_first
223 method getpan = m_pan
224 method getminfo : (int * int) array = E.a
225 end;;
227 let coe s = (s :> uioh);;
229 class listview ~zebra ~helpmode ~(source:lvsource) ~trusted ~modehash =
230 object (self)
231 val m_pan = source#getpan
232 val m_first = source#getfirst
233 val m_active = source#getactive
234 val m_qsearch = E.s
235 val m_prev_uioh = state.uioh
237 method private elemunder y =
238 if y < 0
239 then None
240 else
241 let n = y / (fstate.fontsize+1) in
242 if m_first + n < source#getitemcount
243 then (
244 if source#hasaction (m_first + n)
245 then Some (m_first + n)
246 else None
248 else None
250 method display =
251 Gl.enable `blend;
252 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
253 GlDraw.color (0., 0., 0.) ~alpha:0.85;
254 filledrect 0. 0. (float state.winw) (float state.winh);
255 GlDraw.color (1., 1., 1.);
256 Gl.enable `texture_2d;
257 let fs = fstate.fontsize in
258 let nfs = fs + 1 in
259 let hw = state.winw/3 in
260 let ww = fstate.wwidth in
261 let tabw = 17.0*.ww in
262 let itemcount = source#getitemcount in
263 let minfo = source#getminfo in
264 if conf.leftscroll
265 then (
266 GlMat.push ();
267 GlMat.translate ~x:(float conf.scrollbw) ();
269 let x0 = 0.0 and x1 = float (state.winw - conf.scrollbw - 1) in
270 let rec loop row =
271 if not ((row - m_first) > fstate.maxrows)
272 then (
273 if row >= 0 && row < itemcount
274 then (
275 let (s, level) = source#getitem row in
276 let y = (row - m_first) * nfs in
277 let x = 5.0 +. (float (level + m_pan)) *. ww in
278 if helpmode
279 then GlDraw.color
280 (let c = if row land 1 = 0 then 1.0 else 0.92 in (c,c,c));
282 if row = m_active
283 then (
284 Gl.disable `texture_2d;
285 let alpha = if source#hasaction row then 0.9 else 0.3 in
286 GlDraw.color (1., 1., 1.) ~alpha;
287 linerect (x0 +. 1.) (float (y + 1)) (x1) (float (y + fs + 3));
288 Gl.enable `texture_2d;
290 let c =
291 if zebra && row land 1 = 1
292 then 0.8
293 else 1.0
295 GlDraw.color (c,c,c);
296 let drawtabularstring s =
297 let drawstr x s =
298 let x' = truncate (x0 +. x) in
299 let s1, s2 = splitatchar s '\000' in
300 if emptystr s2
301 then drawstring1 fs x' (y+nfs) s
302 else
303 let rec e s =
304 if emptystr s
305 then s
306 else
307 let s' = withoutlastutf8 s in
308 let s = s' ^ Utf8syms.ellipsis in
309 let w = measurestr fs s in
310 if float x' +. w +. ww < float (hw + x')
311 then s
312 else e s'
314 let s1 =
315 if float x' +. ww +. measurestr fs s1 > float (hw + x')
316 then e s1
317 else s1
319 ignore (drawstring1 fs x' (y+nfs) s1);
320 drawstring1 fs (hw + x') (y+nfs) s2
322 if trusted
323 then
324 let x = if helpmode && row > 0 then x +. ww else x in
325 let s1, s2 = splitatchar s '\t' in
326 if nonemptystr s2
327 then
328 let nx = drawstr x s1 in
329 let sw = nx -. x in
330 let x = x +. (max tabw sw) in
331 drawstr x s2
332 else
333 let len = String.length s - 2 in
334 if len > 0 && s.[0] = '\xc2' && s.[1] = '\xb7'
335 then
336 let s = String.sub s 2 len in
337 let x = if not helpmode then x +. ww else x in
338 GlDraw.color (1.2, 1.2, 1.2);
339 let vinc = drawstring1 (fs+fs/4)
340 (truncate (x -. ww)) (y+nfs) s in
341 GlDraw.color (1., 1., 1.);
342 vinc +. (float fs *. 0.8)
343 else drawstr x s
344 else drawstr x s
346 ignore (drawtabularstring s);
347 loop (row+1)
351 loop m_first;
352 GlDraw.color (1.0, 1.0, 1.0) ~alpha:0.5;
353 let xadj = 5.0 in
354 let rec loop row =
355 if (row - m_first) <= fstate.maxrows
356 then
357 if row >= 0 && row < itemcount
358 then
359 let (s, level) = source#getitem row in
360 let pos0 = Ne.index s '\000' in
361 let y = (row - m_first) * nfs in
362 let x = float (level + m_pan) *. ww in
363 let (first, last) = minfo.(row) in
364 let prefix =
365 if pos0 > 0 && first > pos0
366 then String.sub s (pos0+1) (first-pos0-1)
367 else String.sub s 0 first
369 let suffix = String.sub s first (last - first) in
370 let w1 = measurestr fstate.fontsize prefix in
371 let w2 = measurestr fstate.fontsize suffix in
372 let x = x +. if conf.leftscroll then xadj else 5.0 in
373 let x = if pos0 > 0 && first > pos0 then x +. float hw else x in
374 let x0 = x +. w1
375 and y0 = float (y+2) in
376 let x1 = x0 +. w2
377 and y1 = float (y+fs+3) in
378 filledrect x0 y0 x1 y1;
379 loop (row+1)
381 Gl.disable `texture_2d;
382 if Array.length minfo > 0 then loop m_first;
383 Gl.disable `blend;
384 if conf.leftscroll
385 then GlMat.pop ()
387 method updownlevel incr =
388 let len = source#getitemcount in
389 let curlevel =
390 if m_active >= 0 && m_active < len
391 then snd (source#getitem m_active)
392 else -1
394 let rec flow i =
395 if i = len then i-1 else if i = -1 then 0 else
396 let _, l = source#getitem i in
397 if l != curlevel then i else flow (i+incr)
399 let active = flow m_active in
400 let first = calcfirst m_first active in
401 postRedisplay "outline updownlevel";
402 {< m_active = active; m_first = first >}
404 method private key1 key mask =
405 let set1 active first qsearch =
406 coe {< m_active = active; m_first = first; m_qsearch = qsearch >}
408 let search active pattern incr =
409 let active = if active = -1 then m_first else active in
410 let dosearch re =
411 let rec loop n =
412 if n >= 0 && n < source#getitemcount
413 then (
414 let s, _ = source#getitem n in
415 match Str.search_forward re s 0 with
416 | exception Not_found -> loop (n + incr)
417 | _ -> Some n
419 else None
421 loop active
423 let qpat = Str.quote pattern in
424 match Str.regexp_case_fold qpat with
425 | s -> dosearch s
426 | exception exn ->
427 dolog "regexp_case_fold for `%S' failed: %S\n" qpat @@
428 Printexc.to_string exn;
429 None
431 let itemcount = source#getitemcount in
432 let find start incr =
433 let rec find i =
434 if i = -1 || i = itemcount
435 then -1
436 else (
437 if source#hasaction i
438 then i
439 else find (i + incr)
442 find start
444 let set active first =
445 let first = bound first 0 (itemcount - fstate.maxrows) in
446 state.text <- E.s;
447 coe {< m_active = active; m_first = first; m_qsearch = E.s >}
449 let navigate incr =
450 let isvisible first n = n >= first && n - first <= fstate.maxrows in
451 let active, first =
452 let incr1 = if incr > 0 then 1 else -1 in
453 if isvisible m_first m_active
454 then
455 let next =
456 let next = m_active + incr in
457 let next =
458 if next < 0 || next >= itemcount
459 then -1
460 else find next incr1
462 if abs (m_active - next) > fstate.maxrows
463 then -1
464 else next
466 if next = -1
467 then
468 let first = m_first + incr in
469 let first = bound first 0 (itemcount - fstate.maxrows) in
470 let next =
471 let next = m_active + incr in
472 let next = bound next 0 (itemcount - 1) in
473 find next ~-incr1
475 let active =
476 if next = -1
477 then m_active
478 else (
479 if isvisible first next
480 then next
481 else m_active
484 active, first
485 else
486 let first = min next m_first in
487 let first =
488 if abs (next - first) > fstate.maxrows
489 then first + incr
490 else first
492 next, first
493 else
494 let first = m_first + incr in
495 let first = bound first 0 (itemcount - 1) in
496 let active =
497 let next = m_active + incr in
498 let next = bound next 0 (itemcount - 1) in
499 let next = find next incr1 in
500 let active =
501 if next = -1 || abs (m_active - first) > fstate.maxrows
502 then (
503 let active = if m_active = -1 then next else m_active in
504 active
506 else next
508 if isvisible first active
509 then active
510 else -1
512 active, first
514 postRedisplay "listview navigate";
515 set active first;
517 let open Keys in
518 let kt = Wsi.kc2kt key in
519 match [@warning "-4"] kt with
520 | Ascii (('r'|'s') as c) when Wsi.withctrl mask ->
521 let incr = if c = 'r' then -1 else 1 in
522 let active, first =
523 match search (m_active + incr) m_qsearch incr with
524 | None ->
525 state.text <- m_qsearch ^ " [not found]";
526 m_active, m_first
527 | Some active ->
528 state.text <- m_qsearch;
529 active, firstof m_first active
531 postRedisplay "listview ctrl-r/s";
532 set1 active first m_qsearch;
534 | Insert when Wsi.withctrl mask ->
535 if m_active >= 0 && m_active < source#getitemcount
536 then (
537 let s, _ = source#getitem m_active in
538 selstring conf.selcmd s;
540 coe self
542 | Backspace ->
543 if emptystr m_qsearch
544 then coe self
545 else (
546 let qsearch = withoutlastutf8 m_qsearch in
547 if emptystr qsearch
548 then (
549 state.text <- E.s;
550 postRedisplay "listview empty qsearch";
551 set1 m_active m_first E.s;
553 else
554 let active, first =
555 match search m_active qsearch ~-1 with
556 | None ->
557 state.text <- qsearch ^ " [not found]";
558 m_active, m_first
559 | Some active ->
560 state.text <- qsearch;
561 active, firstof m_first active
563 postRedisplay "listview backspace qsearch";
564 set1 active first qsearch
567 | Ascii _ | Code _ ->
568 let utf8 =
569 match [@warning "-8"] kt with
570 | Ascii c -> String.make 1 c
571 | Code code -> toutf8 code
573 let pattern = m_qsearch ^ utf8 in
574 let active, first =
575 match search m_active pattern 1 with
576 | None ->
577 state.text <- pattern ^ " [not found]";
578 m_active, m_first
579 | Some active ->
580 state.text <- pattern;
581 active, firstof m_first active
583 postRedisplay "listview qsearch add";
584 set1 active first pattern;
586 | Escape ->
587 state.text <- E.s;
588 if emptystr m_qsearch
589 then (
590 postRedisplay "list view escape";
591 (* XXX:
592 let mx, my = state.mpos in
593 updateunder mx my;
595 getoptdef m_prev_uioh @@
596 source#exit ~uioh:(coe self) ~cancel:true ~active:m_active
597 ~first:m_first ~pan:m_pan
599 else (
600 postRedisplay "list view kill qsearch";
601 coe {< m_qsearch = E.s >}
604 | Enter ->
605 state.text <- E.s;
606 let self = {< m_qsearch = E.s >} in
607 let opt =
608 postRedisplay "listview enter";
609 let cancel = not (m_active >= 0 && m_active < source#getitemcount) in
610 source#exit ~uioh:(coe self) ~cancel
611 ~active:m_active ~first:m_first ~pan:m_pan;
613 getoptdef m_prev_uioh opt
615 | Delete -> coe self
616 | Up -> navigate ~-1
617 | Down -> navigate 1
618 | Prior -> navigate ~-(fstate.maxrows)
619 | Next -> navigate fstate.maxrows
621 | Right ->
622 state.text <- E.s;
623 postRedisplay "listview right";
624 coe {< m_pan = m_pan - 1 >}
626 | Left ->
627 state.text <- E.s;
628 postRedisplay "listview left";
629 coe {< m_pan = m_pan + 1 >}
631 | Home ->
632 let active = find 0 1 in
633 postRedisplay "listview home";
634 set active 0;
636 | End ->
637 let first = max 0 (itemcount - fstate.maxrows) in
638 let active = find (itemcount - 1) ~-1 in
639 postRedisplay "listview end";
640 set active first;
642 | _ -> coe self
644 method key key mask =
645 match state.mode with
646 | Textentry te ->
647 textentrykeyboard key mask te;
648 coe self
649 | Birdseye _ | View | LinkNav _ -> self#key1 key mask
651 method button button down x y _ =
652 let opt =
653 match button with
654 | 1 when vscrollhit x ->
655 postRedisplay "listview scroll";
656 if down
657 then
658 let _, position, sh = self#scrollph in
659 if y > truncate position && y < truncate (position +. sh)
660 then (
661 state.mstate <- Mscrolly;
662 Some (coe self)
664 else
665 let s = float (max 0 (y - conf.scrollh)) /. float state.winh in
666 let first = truncate (s *. float source#getitemcount) in
667 let first = min source#getitemcount first in
668 Some (coe {< m_first = first; m_active = first >})
669 else (
670 state.mstate <- Mnone;
671 Some (coe self);
673 | 1 when down ->
674 begin match self#elemunder y with
675 | Some n ->
676 postRedisplay "listview click";
677 source#exit ~uioh:(coe {< m_active = n >})
678 ~cancel:false ~active:n ~first:m_first ~pan:m_pan
679 | _ ->
680 Some (coe self)
682 | n when (n == 4 || n == 5) && not down ->
683 let len = source#getitemcount in
684 let first =
685 if n = 5 && m_first + fstate.maxrows >= len
686 then m_first
687 else
688 let first = m_first + (if n == 4 then -1 else 1) in
689 bound first 0 (len - 1)
691 postRedisplay "listview wheel";
692 Some (coe {< m_first = first >})
693 | n when (n = 6 || n = 7) && not down ->
694 let inc = if n = 7 then -1 else 1 in
695 postRedisplay "listview hwheel";
696 Some (coe {< m_pan = m_pan + inc >})
697 | _ -> Some (coe self)
699 getoptdef m_prev_uioh opt
701 method multiclick _ x y = self#button 1 true x y
703 method motion _ y =
704 match state.mstate with
705 | Mscrolly ->
706 let s = float (max 0 (y - conf.scrollh)) /. float state.winh in
707 let first = truncate (s *. float source#getitemcount) in
708 let first = min source#getitemcount first in
709 postRedisplay "listview motion";
710 coe {< m_first = first; m_active = first >}
711 | Msel _
712 | Mpan _
713 | Mscrollx
714 | Mzoom _
715 | Mzoomrect _
716 | Mnone -> coe self
718 method pmotion x y =
719 if x < state.winw - conf.scrollbw
720 then
721 let n =
722 match self#elemunder y with
723 | None -> Wsi.setcursor Wsi.CURSOR_INHERIT; m_active
724 | Some n -> Wsi.setcursor Wsi.CURSOR_INFO; n
726 let o =
727 if n != m_active
728 then (postRedisplay "listview pmotion"; {< m_active = n >})
729 else self
731 coe o
732 else (
733 Wsi.setcursor Wsi.CURSOR_INHERIT;
734 coe self
737 method infochanged _ = ()
739 method scrollpw = (0, 0.0, 0.0)
740 method scrollph =
741 let nfs = fstate.fontsize + 1 in
742 let y = m_first * nfs in
743 let itemcount = source#getitemcount in
744 let maxi = max 0 (itemcount - fstate.maxrows) in
745 let maxy = maxi * nfs in
746 let p, h = scrollph y maxy in
747 conf.scrollbw, p, h
749 method modehash = modehash
750 method eformsgs = false
751 method alwaysscrolly = true
752 method scroll _ dy =
753 let self =
754 if dy != 0 then begin
755 let len = source#getitemcount in
756 let first =
757 if dy > 0 && m_first + fstate.maxrows >= len
758 then m_first
759 else
760 let first = m_first + dy / 10 in
761 bound first 0 (len - 1)
763 postRedisplay "listview wheel";
764 {< m_first = first >}
765 end else
766 self
768 coe self
770 method zoom _ _ _ = ()
771 end;;