summaryrefslogtreecommitdiffstats
path: root/otherlibs/labltk/browser/jg_multibox.ml
blob: f05524e11c3d3db1ee589a6d743253d7729764d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
(* $Id$ *)

let rec gen_list fun:f :len =
  if len = 0 then [] else f () :: gen_list fun:f len:(len - 1)

let rec make_list :len :fill =
  if len = 0 then [] else fill :: make_list len:(len - 1) :fill

(* By column version
let rec firsts :len l =
  if len = 0 then ([],l) else
  match l with
    a::l ->
      let (f,l) = firsts l len:(len - 1) in
      (a::f,l)
  | [] ->
      (l,[])

let rec split :len = function
    [] -> []
  | l ->
      let (f,r) = firsts l :len in
      let ret = split :len r in
      f :: ret

let extend l :len :fill =
  if List.length l >= len then l
  else l @ make_list :fill len:(len - List.length l)
*)

(* By row version *)

let rec first l :len =
  if len = 0 then [], l else
  match l with
    [] -> make_list :len fill:"", []
  | a::l ->
      let (l',r) = first len:(len - 1) l in a::l',r

let rec split l :len =
  if l = [] then make_list :len fill:[] else
  let (cars,r) = first l :len in
  let cdrs = split r :len in
  List.map2 cars cdrs fun:(fun a l -> a::l)
  

open Tk

class c :cols :texts ?:maxheight ?:width parent = object (self)
  val parent' = coe parent
  val length = List.length texts
  val boxes =
    let height = (List.length texts - 1) / cols + 1 in
    let height =
      match maxheight with None -> height
      | Some max -> min max height
    in
    gen_list len:cols fun:
      begin fun () ->
        Listbox.create parent :height ?:width
          highlightthickness:(`Pix 0)
          borderwidth:(`Pix 1)
      end
  val mutable current = 0
  method cols = cols
  method texts = texts
  method parent = parent'
  method boxes = boxes
  method current = current
  method recenter?:aligntop{=false} n =
    current <-
       if n < 0 then 0 else
       if n < length then n else length - 1;
    (* Activate it, to keep consistent with Up/Down.
       You have to be in Extended or Browse mode *)
    let box = List.nth boxes pos:(current mod cols)
    and index = `Num (current / cols) in
    List.iter boxes fun:
      begin fun box ->
        Listbox.selection_clear box first:(`Num 0) last:`End;
        Listbox.selection_anchor box :index;
        Listbox.activate box :index
      end;
    Focus.set box;
    if aligntop then Listbox.yview_index box :index
    else Listbox.see box :index;
    let (first,last) = Listbox.yview_get box in
    List.iter boxes fun:(Listbox.yview scroll:(`Moveto first))
  method init =
    let textl = split len:cols texts in
    List.iter2 boxes textl fun:
      begin fun box texts ->
        Jg_bind.enter_focus box;
        Listbox.insert box :texts index:`End
      end;
    pack boxes side:`Left expand:true fill:`Both;
    self#bind_mouse events:[[],`ButtonPressDetail 1]
      action:(fun _ index:n -> self#recenter n; break ());
    let current_height () =
      let (top,bottom) = Listbox.yview_get (List.hd boxes) in
      truncate ((bottom -. top) *. float (Listbox.size (List.hd boxes))
                  +. 0.99)
    in
    List.iter
      [ "Right", (fun n -> n+1);
        "Left", (fun n -> n-1);
        "Up", (fun n -> n-cols);
        "Down", (fun n -> n+cols);
        "Prior", (fun n -> n - current_height () * cols);
        "Next", (fun n -> n + current_height () * cols);
        "Home", (fun _ -> 0);
        "End", (fun _ -> List.length texts) ]
      fun:begin fun (key,f) ->
        self#bind_kbd events:[[],`KeyPressDetail key]
          action:(fun _ index:n -> self#recenter (f n); break ())
      end;
    self#recenter 0
  method bind_mouse :events :action =
    let i = ref 0 in
    List.iter boxes fun:
      begin fun box ->
        let b = !i in
        bind box :events
          action:(`Setbreakable ([`MouseX;`MouseY], fun ev ->
            let `Num n = Listbox.nearest box y:ev.ev_MouseY
            in action ev index:(n * cols + b)));
        incr i
      end
  method bind_kbd :events :action =
    let i = ref 0 in
    List.iter boxes fun:
      begin fun box ->
        let b = !i in
        bind box :events
          action:(`Setbreakable ([`Char], fun ev ->
            let `Num n = Listbox.index box index:`Active in
            action ev index:(n * cols + b)));
        incr i
      end
end

let add_scrollbar (box : c) =
  let boxes = box#boxes in
  let sb =
    Scrollbar.create (box#parent)
      command:(fun :scroll -> List.iter boxes fun:(Listbox.yview :scroll)) in
  List.iter boxes
    fun:(fun lb -> Listbox.configure lb yscrollcommand:(Scrollbar.set sb));
  pack [sb] before:(List.hd boxes) side:`Right fill:`Y;
  sb

let add_completion ?:action ?:wait (box : c) =
  let comp = new Jg_completion.timed (box#texts) ?:wait in
  box#bind_kbd events:[[], `KeyPress]
    action:(fun ev :index -> 
      (* consider only keys producing characters. The callback is called
       * even if you press Shift. *)
      if ev.ev_Char <> "" then
        box#recenter (comp#add ev.ev_Char) aligntop:true);
  match action with
    Some action ->
      box#bind_kbd events:[[], `KeyPressDetail "space"]
        action:(fun ev :index -> action (box#current));
      box#bind_kbd events:[[], `KeyPressDetail "Return"]
        action:(fun ev :index -> action (box#current));
      box#bind_mouse events:[[], `ButtonPressDetail 1]
        action:(fun ev :index ->
          box#recenter index; action (box#current); break ())
  | None -> ()