(*************************************************************************) (* *) (* Objective Caml LablTk library *) (* *) (* Jacques Garrigue, Kyoto University RIMS *) (* *) (* Copyright 1999 Institut National de Recherche en Informatique et *) (* en Automatique and Kyoto University. All rights reserved. *) (* This file is distributed under the terms of the GNU Library *) (* General Public License. *) (* *) (*************************************************************************) (* $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:0 borderwidth: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 breakable:true fields:[`MouseX;`MouseY] action:(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 breakable:true fields:[`Char] action:(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 -> ()