Blame browser/jg_multibox.ml

Packit bd2e5d
(*************************************************************************)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*                         OCaml LablTk library                          *)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*            Jacques Garrigue, Kyoto University RIMS                    *)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*   Copyright 1999 Institut National de Recherche en Informatique et    *)
Packit bd2e5d
(*   en Automatique and Kyoto University.  All rights reserved.          *)
Packit bd2e5d
(*   This file is distributed under the terms of the GNU Library         *)
Packit bd2e5d
(*   General Public License, with the special exception on linking       *)
Packit bd2e5d
(*   described in file ../../../LICENSE.                                 *)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*************************************************************************)
Packit bd2e5d
Packit bd2e5d
(* $Id$ *)
Packit bd2e5d
Packit bd2e5d
open StdLabels
Packit bd2e5d
Packit bd2e5d
let rec gen_list ~f:f ~len =
Packit bd2e5d
  if len = 0 then [] else f () :: gen_list ~f:f ~len:(len - 1)
Packit bd2e5d
Packit bd2e5d
let rec make_list ~len ~fill =
Packit bd2e5d
  if len = 0 then [] else fill :: make_list ~len:(len - 1) ~fill
Packit bd2e5d
Packit bd2e5d
(* By column version
Packit bd2e5d
let rec firsts ~len l =
Packit bd2e5d
  if len = 0 then ([],l) else
Packit bd2e5d
  match l with
Packit bd2e5d
    a::l ->
Packit bd2e5d
      let (f,l) = firsts l len:(len - 1) in
Packit bd2e5d
      (a::f,l)
Packit bd2e5d
  | [] ->
Packit bd2e5d
      (l,[])
Packit bd2e5d
Packit bd2e5d
let rec split ~len = function
Packit bd2e5d
    [] -> []
Packit bd2e5d
  | l ->
Packit bd2e5d
      let (f,r) = firsts l ~len in
Packit bd2e5d
      let ret = split ~len r in
Packit bd2e5d
      f :: ret
Packit bd2e5d
Packit bd2e5d
let extend l ~len ~fill =
Packit bd2e5d
  if List.length l >= len then l
Packit bd2e5d
  else l @ make_list ~fill len:(len - List.length l)
Packit bd2e5d
*)
Packit bd2e5d
Packit bd2e5d
(* By row version *)
Packit bd2e5d
Packit bd2e5d
let rec first l ~len =
Packit bd2e5d
  if len = 0 then [], l else
Packit bd2e5d
  match l with
Packit bd2e5d
    [] -> make_list ~len ~fill:"", []
Packit bd2e5d
  | a::l ->
Packit bd2e5d
      let (l',r) = first ~len:(len - 1) l in a::l',r
Packit bd2e5d
Packit bd2e5d
let rec split l ~len =
Packit bd2e5d
  if l = [] then make_list ~len ~fill:[] else
Packit bd2e5d
  let (cars,r) = first l ~len in
Packit bd2e5d
  let cdrs = split r ~len in
Packit bd2e5d
  List.map2 cars cdrs ~f:(fun a l -> a::l)
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
open Tk
Packit bd2e5d
Packit bd2e5d
class c ~cols ~texts ?maxheight ?width parent = object (self)
Packit bd2e5d
  val parent' = coe parent
Packit bd2e5d
  val length = List.length texts
Packit bd2e5d
  val boxes =
Packit bd2e5d
    let height = (List.length texts - 1) / cols + 1 in
Packit bd2e5d
    let height =
Packit bd2e5d
      match maxheight with None -> height
Packit bd2e5d
      | Some max -> min max height
Packit bd2e5d
    in
Packit bd2e5d
    gen_list ~len:cols ~f:
Packit bd2e5d
      begin fun () ->
Packit bd2e5d
        Listbox.create parent ~height ?width
Packit bd2e5d
          ~highlightthickness:0
Packit bd2e5d
          ~borderwidth:1
Packit bd2e5d
      end
Packit bd2e5d
  val mutable current = 0
Packit bd2e5d
  method cols = cols
Packit bd2e5d
  method texts = texts
Packit bd2e5d
  method parent = parent'
Packit bd2e5d
  method boxes = boxes
Packit bd2e5d
  method current = current
Packit bd2e5d
  method recenter ?(aligntop=false) n =
Packit bd2e5d
    current <-
Packit bd2e5d
       if n < 0 then 0 else
Packit bd2e5d
       if n < length then n else length - 1;
Packit bd2e5d
    (* Activate it, to keep consistent with Up/Down.
Packit bd2e5d
       You have to be in Extended or Browse mode *)
Packit bd2e5d
    let box = List.nth boxes (current mod cols)
Packit bd2e5d
    and index = `Num (current / cols) in
Packit bd2e5d
    List.iter boxes ~f:
Packit bd2e5d
      begin fun box ->
Packit bd2e5d
        Listbox.selection_clear box ~first:(`Num 0) ~last:`End;
Packit bd2e5d
        Listbox.selection_anchor box ~index;
Packit bd2e5d
        Listbox.activate box ~index
Packit bd2e5d
      end;
Packit bd2e5d
    Focus.set box;
Packit bd2e5d
    if aligntop then Listbox.yview_index box ~index
Packit bd2e5d
    else Listbox.see box ~index;
Packit bd2e5d
    let (first,last) = Listbox.yview_get box in
Packit bd2e5d
    List.iter boxes ~f:(Listbox.yview ~scroll:(`Moveto first))
Packit bd2e5d
  method init =
Packit bd2e5d
    let textl = split ~len:cols texts in
Packit bd2e5d
    List.iter2 boxes textl ~f:
Packit bd2e5d
      begin fun box texts ->
Packit bd2e5d
        Jg_bind.enter_focus box;
Packit bd2e5d
        Listbox.insert box ~texts ~index:`End
Packit bd2e5d
      end;
Packit bd2e5d
    pack boxes ~side:`Left ~expand:true ~fill:`Both;
Packit bd2e5d
    self#bind_mouse ~events:[`ButtonPressDetail 1]
Packit bd2e5d
      ~action:(fun _ ~index:n -> self#recenter n; break ());
Packit bd2e5d
    let current_height () =
Packit bd2e5d
      let (top,bottom) = Listbox.yview_get (List.hd boxes) in
Packit bd2e5d
      truncate ((bottom -. top) *. float (Listbox.size (List.hd boxes))
Packit bd2e5d
                  +. 0.99)
Packit bd2e5d
    in
Packit bd2e5d
    List.iter
Packit bd2e5d
      [ "Right", (fun n -> n+1);
Packit bd2e5d
        "Left", (fun n -> n-1);
Packit bd2e5d
        "Up", (fun n -> n-cols);
Packit bd2e5d
        "Down", (fun n -> n+cols);
Packit bd2e5d
        "Prior", (fun n -> n - current_height () * cols);
Packit bd2e5d
        "Next", (fun n -> n + current_height () * cols);
Packit bd2e5d
        "Home", (fun _ -> 0);
Packit bd2e5d
        "End", (fun _ -> List.length texts) ]
Packit bd2e5d
      ~f:begin fun (key,f) ->
Packit bd2e5d
        self#bind_kbd ~events:[`KeyPressDetail key]
Packit bd2e5d
          ~action:(fun _ ~index:n -> self#recenter (f n); break ())
Packit bd2e5d
      end;
Packit bd2e5d
    self#recenter 0
Packit bd2e5d
  method bind_mouse ~events ~action =
Packit bd2e5d
    let i = ref 0 in
Packit bd2e5d
    List.iter boxes ~f:
Packit bd2e5d
      begin fun box ->
Packit bd2e5d
        let b = !i in
Packit bd2e5d
        bind box ~events ~breakable:true ~fields:[`MouseX;`MouseY]
Packit bd2e5d
          ~action:(fun ev ->
Packit bd2e5d
            let `Num n = Listbox.nearest box ~y:ev.ev_MouseY
Packit bd2e5d
            in action ev ~index:(n * cols + b));
Packit bd2e5d
        incr i
Packit bd2e5d
      end
Packit bd2e5d
  method bind_kbd ~events ~action =
Packit bd2e5d
    let i = ref 0 in
Packit bd2e5d
    List.iter boxes ~f:
Packit bd2e5d
      begin fun box ->
Packit bd2e5d
        let b = !i in
Packit bd2e5d
        bind box ~events ~breakable:true ~fields:[`Char]
Packit bd2e5d
          ~action:(fun ev ->
Packit bd2e5d
            let `Num n = Listbox.index box ~index:`Active in
Packit bd2e5d
            action ev ~index:(n * cols + b));
Packit bd2e5d
        incr i
Packit bd2e5d
      end
Packit bd2e5d
end
Packit bd2e5d
Packit bd2e5d
let add_scrollbar (box : c) =
Packit bd2e5d
  let boxes = box#boxes in
Packit bd2e5d
  let sb =
Packit bd2e5d
    Scrollbar.create (box#parent)
Packit bd2e5d
      ~command:(fun ~scroll -> List.iter boxes ~f:(Listbox.yview ~scroll)) in
Packit bd2e5d
  List.iter boxes
Packit bd2e5d
    ~f:(fun lb -> Listbox.configure lb ~yscrollcommand:(Scrollbar.set sb));
Packit bd2e5d
  pack [sb] ~before:(List.hd boxes) ~side:`Right ~fill:`Y;
Packit bd2e5d
  sb
Packit bd2e5d
Packit bd2e5d
let add_completion ?action ?wait (box : c) =
Packit bd2e5d
  let comp = new Jg_completion.timed (box#texts) ?wait in
Packit bd2e5d
  box#bind_kbd ~events:[`KeyPress]
Packit bd2e5d
    ~action:(fun ev ~index ->
Packit bd2e5d
      (* consider only keys producing characters. The callback is called
Packit bd2e5d
       * even if you press Shift. *)
Packit bd2e5d
      if ev.ev_Char <> "" then
Packit bd2e5d
        box#recenter (comp#add ev.ev_Char) ~aligntop:true);
Packit bd2e5d
  match action with
Packit bd2e5d
    Some action ->
Packit bd2e5d
      box#bind_kbd ~events:[`KeyPressDetail "space"]
Packit bd2e5d
        ~action:(fun ev ~index -> action (box#current));
Packit bd2e5d
      box#bind_kbd ~events:[`KeyPressDetail "Return"]
Packit bd2e5d
        ~action:(fun ev ~index -> action (box#current));
Packit bd2e5d
      box#bind_mouse ~events:[`ButtonPressDetail 1]
Packit bd2e5d
        ~action:(fun ev ~index ->
Packit bd2e5d
          box#recenter index; action (box#current); break ())
Packit bd2e5d
  | None -> ()