Reword
[llpp.git] / listview.ml
blob7d3b6272fbfd2acbe2623ac3b1c22a5f860de2fa
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 <-
135 Textentry (
136 (c, action cmd, opthist, onkey, ondone, cancelonempty), 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 (
175 enttext (c, E.s, opthist, onkey, ondone, cancelonempty)
178 | Delete -> ()
180 | Code _ | Ascii _ ->
181 begin match onkey text kt with
182 | TEdone text ->
183 ondone text;
184 onleave Confirm;
185 postRedisplay "textentrykeyboard after confirm2";
187 | TEcont text ->
188 enttext (c, text, opthist, onkey, ondone, cancelonempty);
190 | TEstop ->
191 onleave Cancel;
192 postRedisplay "textentrykeyboard after cancel3"
194 | TEswitch te ->
195 state.mode <- Textentry (te, onleave);
196 postRedisplay "textentrykeyboard switch";
198 | _ -> vlog "unhandled key"
201 class type lvsource =
202 object
203 method getitemcount : int
204 method getitem : int -> (string * int)
205 method hasaction : int -> bool
206 method exit : uioh:uioh ->
207 cancel:bool ->
208 active:int ->
209 first:int ->
210 pan:int ->
211 uioh option
212 method getactive : int
213 method getfirst : int
214 method getpan : int
215 method getminfo : (int * int) array
216 end;;
218 class virtual lvsourcebase = object
219 val mutable m_active = 0
220 val mutable m_first = 0
221 val mutable m_pan = 0
222 method getactive = m_active
223 method getfirst = m_first
224 method getpan = m_pan
225 method getminfo : (int * int) array = E.a
226 end;;
228 let coe s = (s :> uioh);;
230 class listview ~zebra ~helpmode ~(source:lvsource) ~trusted ~modehash =
231 object (self)
232 val m_pan = source#getpan
233 val m_first = source#getfirst
234 val m_active = source#getactive
235 val m_qsearch = E.s
236 val m_prev_uioh = state.uioh
238 method private elemunder y =
239 if y < 0
240 then None
241 else
242 let n = y / (fstate.fontsize+1) in
243 if m_first + n < source#getitemcount
244 then (
245 if source#hasaction (m_first + n)
246 then Some (m_first + n)
247 else None
249 else None
251 method display =
252 Gl.enable `blend;
253 GlFunc.blend_func ~src:`src_alpha ~dst:`one_minus_src_alpha;
254 GlDraw.color (0., 0., 0.) ~alpha:0.85;
255 filledrect 0. 0. (float state.winw) (float state.winh);
256 GlDraw.color (1., 1., 1.);
257 Gl.enable `texture_2d;
258 let fs = fstate.fontsize in
259 let nfs = fs + 1 in
260 let hw = state.winw/3 in
261 let ww = fstate.wwidth in
262 let tabw = 17.0*.ww in
263 let itemcount = source#getitemcount in
264 let minfo = source#getminfo in
265 if conf.leftscroll
266 then (
267 GlMat.push ();
268 GlMat.translate ~x:(float conf.scrollbw) ();
270 let x0 = 0.0 and x1 = float (state.winw - conf.scrollbw - 1) in
271 let rec loop row =
272 if not ((row - m_first) > fstate.maxrows)
273 then (
274 if row >= 0 && row < itemcount
275 then (
276 let (s, level) = source#getitem row in
277 let y = (row - m_first) * nfs in
278 let x = 5.0 +. (float (level + m_pan)) *. ww in
279 if helpmode
280 then GlDraw.color
281 (let c = if row land 1 = 0 then 1.0 else 0.92 in (c,c,c));
283 if row = m_active
284 then (
285 Gl.disable `texture_2d;
286 let alpha = if source#hasaction row then 0.9 else 0.3 in
287 GlDraw.color (1., 1., 1.) ~alpha;
288 linerect (x0 +. 1.) (float (y + 1)) (x1) (float (y + fs + 3));
289 Gl.enable `texture_2d;
291 let c =
292 if zebra && row land 1 = 1
293 then 0.8
294 else 1.0
296 GlDraw.color (c,c,c);
297 let drawtabularstring s =
298 let drawstr x s =
299 let x' = truncate (x0 +. x) in
300 let s1, s2 = splitatchar s '\000' in
301 if emptystr s2
302 then drawstring1 fs x' (y+nfs) s
303 else
304 let rec e s =
305 if emptystr s
306 then s
307 else
308 let s' = withoutlastutf8 s in
309 let s = s' ^ Utf8syms.ellipsis in
310 let w = measurestr fs s in
311 if float x' +. w +. ww < float (hw + x')
312 then s
313 else e s'
315 let s1 =
316 if float x' +. ww +. measurestr fs s1 > float (hw + x')
317 then e s1
318 else s1
320 ignore (drawstring1 fs x' (y+nfs) s1);
321 drawstring1 fs (hw + x') (y+nfs) s2
323 if trusted
324 then
325 let x = if helpmode && row > 0 then x +. ww else x in
326 let s1, s2 = splitatchar s '\t' in
327 if nonemptystr s2
328 then
329 let nx = drawstr x s1 in
330 let sw = nx -. x in
331 let x = x +. (max tabw sw) in
332 drawstr x s2
333 else
334 let len = String.length s - 2 in
335 if len > 0 && s.[0] = '\xc2' && s.[1] = '\xb7'
336 then
337 let s = String.sub s 2 len in
338 let x = if not helpmode then x +. ww else x in
339 GlDraw.color (1.2, 1.2, 1.2);
340 let vinc = drawstring1 (fs+fs/4)
341 (truncate (x -. ww)) (y+nfs) s in
342 GlDraw.color (1., 1., 1.);
343 vinc +. (float fs *. 0.8)
344 else
345 drawstr x s
346 else
347 drawstr x s
349 ignore (drawtabularstring s);
350 loop (row+1)
354 loop m_first;
355 GlDraw.color (1.0, 1.0, 1.0) ~alpha:0.5;
356 let xadj = 5.0 in
357 let rec loop row =
358 if (row - m_first) <= fstate.maxrows
359 then
360 if row >= 0 && row < itemcount
361 then (
362 let (s, level) = source#getitem row in
363 let pos0 = Ne.index s '\000' in
364 let y = (row - m_first) * nfs in
365 let x = float (level + m_pan) *. ww in
366 let (first, last) = minfo.(row) in
367 let prefix =
368 if pos0 > 0 && first > pos0
369 then String.sub s (pos0+1) (first-pos0-1)
370 else String.sub s 0 first
372 let suffix = String.sub s first (last - first) in
373 let w1 = measurestr fstate.fontsize prefix in
374 let w2 = measurestr fstate.fontsize suffix in
375 let x = x +. if conf.leftscroll then xadj else 5.0 in
376 let x = if pos0 > 0 && first > pos0 then x +. float hw else x in
377 let x0 = x +. w1
378 and y0 = float (y+2) in
379 let x1 = x0 +. w2
380 and y1 = float (y+fs+3) in
381 filledrect x0 y0 x1 y1;
382 loop (row+1)
385 Gl.disable `texture_2d;
386 if Array.length minfo > 0 then loop m_first;
387 Gl.disable `blend;
388 if conf.leftscroll
389 then GlMat.pop ()
391 method updownlevel incr =
392 let len = source#getitemcount in
393 let curlevel =
394 if m_active >= 0 && m_active < len
395 then snd (source#getitem m_active)
396 else -1
398 let rec flow i =
399 if i = len then i-1 else if i = -1 then 0 else
400 let _, l = source#getitem i in
401 if l != curlevel then i else flow (i+incr)
403 let active = flow m_active in
404 let first = calcfirst m_first active in
405 postRedisplay "outline updownlevel";
406 {< m_active = active; m_first = first >}
408 method private key1 key mask =
409 let set1 active first qsearch =
410 coe {< m_active = active; m_first = first; m_qsearch = qsearch >}
412 let search active pattern incr =
413 let active = if active = -1 then m_first else active in
414 let dosearch re =
415 let rec loop n =
416 if n >= 0 && n < source#getitemcount
417 then (
418 let s, _ = source#getitem n in
419 match Str.search_forward re s 0 with
420 | exception Not_found -> loop (n + incr)
421 | _ -> Some n
423 else None
425 loop active
427 let qpat = Str.quote pattern in
428 match Str.regexp_case_fold qpat with
429 | s -> dosearch s
430 | exception exn ->
431 dolog "regexp_case_fold for `%S' failed: %S\n" qpat @@
432 Printexc.to_string exn;
433 None
435 let itemcount = source#getitemcount in
436 let find start incr =
437 let rec find i =
438 if i = -1 || i = itemcount
439 then -1
440 else (
441 if source#hasaction i
442 then i
443 else find (i + incr)
446 find start
448 let set active first =
449 let first = bound first 0 (itemcount - fstate.maxrows) in
450 state.text <- E.s;
451 coe {< m_active = active; m_first = first; m_qsearch = E.s >}
453 let navigate incr =
454 let isvisible first n = n >= first && n - first <= fstate.maxrows in
455 let active, first =
456 let incr1 = if incr > 0 then 1 else -1 in
457 if isvisible m_first m_active
458 then
459 let next =
460 let next = m_active + incr in
461 let next =
462 if next < 0 || next >= itemcount
463 then -1
464 else find next incr1
466 if abs (m_active - next) > fstate.maxrows
467 then -1
468 else next
470 if next = -1
471 then
472 let first = m_first + incr in
473 let first = bound first 0 (itemcount - fstate.maxrows) in
474 let next =
475 let next = m_active + incr in
476 let next = bound next 0 (itemcount - 1) in
477 find next ~-incr1
479 let active =
480 if next = -1
481 then m_active
482 else (
483 if isvisible first next
484 then next
485 else m_active
488 active, first
489 else
490 let first = min next m_first in
491 let first =
492 if abs (next - first) > fstate.maxrows
493 then first + incr
494 else first
496 next, first
497 else
498 let first = m_first + incr in
499 let first = bound first 0 (itemcount - 1) in
500 let active =
501 let next = m_active + incr in
502 let next = bound next 0 (itemcount - 1) in
503 let next = find next incr1 in
504 let active =
505 if next = -1 || abs (m_active - first) > fstate.maxrows
506 then (
507 let active = if m_active = -1 then next else m_active in
508 active
510 else next
512 if isvisible first active
513 then active
514 else -1
516 active, first
518 postRedisplay "listview navigate";
519 set active first;
521 let open Keys in
522 let kt = Wsi.kc2kt key in
523 match [@warning "-4"] kt with
524 | Ascii (('r'|'s') as c) when Wsi.withctrl mask ->
525 let incr = if c = 'r' then -1 else 1 in
526 let active, first =
527 match search (m_active + incr) m_qsearch incr with
528 | None ->
529 state.text <- m_qsearch ^ " [not found]";
530 m_active, m_first
531 | Some active ->
532 state.text <- m_qsearch;
533 active, firstof m_first active
535 postRedisplay "listview ctrl-r/s";
536 set1 active first m_qsearch;
538 | Insert when Wsi.withctrl mask ->
539 if m_active >= 0 && m_active < source#getitemcount
540 then (
541 let s, _ = source#getitem m_active in
542 selstring conf.selcmd s;
544 coe self
546 | Backspace ->
547 if emptystr m_qsearch
548 then coe self
549 else (
550 let qsearch = withoutlastutf8 m_qsearch in
551 if emptystr qsearch
552 then (
553 state.text <- E.s;
554 postRedisplay "listview empty qsearch";
555 set1 m_active m_first E.s;
557 else
558 let active, first =
559 match search m_active qsearch ~-1 with
560 | None ->
561 state.text <- qsearch ^ " [not found]";
562 m_active, m_first
563 | Some active ->
564 state.text <- qsearch;
565 active, firstof m_first active
567 postRedisplay "listview backspace qsearch";
568 set1 active first qsearch
571 | Ascii _ | Code _ ->
572 let utf8 =
573 match [@warning "-8"] kt with
574 | Ascii c -> String.make 1 c
575 | Code code -> toutf8 code
577 let pattern = m_qsearch ^ utf8 in
578 let active, first =
579 match search m_active pattern 1 with
580 | None ->
581 state.text <- pattern ^ " [not found]";
582 m_active, m_first
583 | Some active ->
584 state.text <- pattern;
585 active, firstof m_first active
587 postRedisplay "listview qsearch add";
588 set1 active first pattern;
590 | Escape ->
591 state.text <- E.s;
592 if emptystr m_qsearch
593 then (
594 postRedisplay "list view escape";
595 (* XXX:
596 let mx, my = state.mpos in
597 updateunder mx my;
599 match source#exit ~uioh:(coe self) ~cancel:true ~active:m_active
600 ~first:m_first ~pan:m_pan with
601 | None -> m_prev_uioh
602 | Some uioh -> uioh
604 else (
605 postRedisplay "list view kill qsearch";
606 coe {< m_qsearch = E.s >}
609 | Enter ->
610 state.text <- E.s;
611 let self = {< m_qsearch = E.s >} in
612 let opt =
613 postRedisplay "listview enter";
614 let cancel = not (m_active >= 0 && m_active < source#getitemcount) in
615 source#exit ~uioh:(coe self) ~cancel
616 ~active:m_active ~first:m_first ~pan:m_pan;
618 begin match opt with
619 | None -> m_prev_uioh
620 | Some uioh -> uioh
623 | Delete ->
624 coe self
626 | Up -> navigate ~-1
627 | Down -> navigate 1
628 | Prior -> navigate ~-(fstate.maxrows)
629 | Next -> navigate fstate.maxrows
631 | Right ->
632 state.text <- E.s;
633 postRedisplay "listview right";
634 coe {< m_pan = m_pan - 1 >}
636 | Left ->
637 state.text <- E.s;
638 postRedisplay "listview left";
639 coe {< m_pan = m_pan + 1 >}
641 | Home ->
642 let active = find 0 1 in
643 postRedisplay "listview home";
644 set active 0;
646 | End ->
647 let first = max 0 (itemcount - fstate.maxrows) in
648 let active = find (itemcount - 1) ~-1 in
649 postRedisplay "listview end";
650 set active first;
652 | _ -> coe self
654 method key key mask =
655 match state.mode with
656 | Textentry te ->
657 textentrykeyboard key mask te;
658 coe self
659 | Birdseye _ | View | LinkNav _ -> self#key1 key mask
661 method button button down x y _ =
662 let opt =
663 match button with
664 | 1 when vscrollhit x ->
665 postRedisplay "listview scroll";
666 if down
667 then
668 let _, position, sh = self#scrollph in
669 if y > truncate position && y < truncate (position +. sh)
670 then (
671 state.mstate <- Mscrolly;
672 Some (coe self)
674 else
675 let s = float (max 0 (y - conf.scrollh)) /. float state.winh in
676 let first = truncate (s *. float source#getitemcount) in
677 let first = min source#getitemcount first in
678 Some (coe {< m_first = first; m_active = first >})
679 else (
680 state.mstate <- Mnone;
681 Some (coe self);
683 | 1 when down ->
684 begin match self#elemunder y with
685 | Some n ->
686 postRedisplay "listview click";
687 source#exit ~uioh:(coe {< m_active = n >})
688 ~cancel:false ~active:n ~first:m_first ~pan:m_pan
689 | _ ->
690 Some (coe self)
692 | n when (n == 4 || n == 5) && not down ->
693 let len = source#getitemcount in
694 let first =
695 if n = 5 && m_first + fstate.maxrows >= len
696 then
697 m_first
698 else
699 let first = m_first + (if n == 4 then -1 else 1) in
700 bound first 0 (len - 1)
702 postRedisplay "listview wheel";
703 Some (coe {< m_first = first >})
704 | n when (n = 6 || n = 7) && not down ->
705 let inc = if n = 7 then -1 else 1 in
706 postRedisplay "listview hwheel";
707 Some (coe {< m_pan = m_pan + inc >})
708 | _ ->
709 Some (coe self)
711 match opt with
712 | None -> m_prev_uioh
713 | Some uioh -> uioh
715 method multiclick _ x y = self#button 1 true x y
717 method motion _ y =
718 match state.mstate with
719 | Mscrolly ->
720 let s = float (max 0 (y - conf.scrollh)) /. float state.winh in
721 let first = truncate (s *. float source#getitemcount) in
722 let first = min source#getitemcount first in
723 postRedisplay "listview motion";
724 coe {< m_first = first; m_active = first >}
725 | Msel _
726 | Mpan _
727 | Mscrollx
728 | Mzoom _
729 | Mzoomrect _
730 | Mnone -> coe self
732 method pmotion x y =
733 if x < state.winw - conf.scrollbw
734 then
735 let n =
736 match self#elemunder y with
737 | None -> Wsi.setcursor Wsi.CURSOR_INHERIT; m_active
738 | Some n -> Wsi.setcursor Wsi.CURSOR_INFO; n
740 let o =
741 if n != m_active
742 then (postRedisplay "listview pmotion"; {< m_active = n >})
743 else self
745 coe o
746 else (
747 Wsi.setcursor Wsi.CURSOR_INHERIT;
748 coe self
751 method infochanged _ = ()
753 method scrollpw = (0, 0.0, 0.0)
754 method scrollph =
755 let nfs = fstate.fontsize + 1 in
756 let y = m_first * nfs in
757 let itemcount = source#getitemcount in
758 let maxi = max 0 (itemcount - fstate.maxrows) in
759 let maxy = maxi * nfs in
760 let p, h = scrollph y maxy in
761 conf.scrollbw, p, h
763 method modehash = modehash
764 method eformsgs = false
765 method alwaysscrolly = true
766 method scroll _ dy =
767 let self =
768 if dy != 0 then begin
769 let len = source#getitemcount in
770 let first =
771 if dy > 0 && m_first + fstate.maxrows >= len
772 then
773 m_first
774 else
775 let first = m_first + dy / 10 in
776 bound first 0 (len - 1)
778 postRedisplay "listview wheel";
779 {< m_first = first >}
780 end else
781 self
783 coe self
785 method zoom _ _ _ = ()
786 end;;