183 lines
6.0 KiB
OCaml
183 lines
6.0 KiB
OCaml
(*************************************************************************)
|
|
(* *)
|
|
(* 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 -> ()
|