Blame jpf/fileselect.ml

Packit bd2e5d
(***********************************************************************)
Packit bd2e5d
(*                                                                     *)
Packit bd2e5d
(*                 MLTk, Tcl/Tk interface of OCaml                     *)
Packit bd2e5d
(*                                                                     *)
Packit bd2e5d
(*    Francois Rouaix, Francois Pessaux, Jun Furuse and Pierre Weis    *)
Packit bd2e5d
(*               projet Cristal, INRIA Rocquencourt                    *)
Packit bd2e5d
(*            Jacques Garrigue, Kyoto University RIMS                  *)
Packit bd2e5d
(*                                                                     *)
Packit bd2e5d
(*  Copyright 2002 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 found in the OCaml source tree.          *)
Packit bd2e5d
(*                                                                     *)
Packit bd2e5d
(***********************************************************************)
Packit bd2e5d
Packit bd2e5d
(* $Id$ *)
Packit bd2e5d
Packit bd2e5d
(* file selection box *)
Packit bd2e5d
Packit bd2e5d
(* This file selecter works only under the OS with the full unix support.
Packit bd2e5d
   For the portability, Tk.getOpenFile and Tk.getSaveFile are recommended. *)
Packit bd2e5d
Packit bd2e5d
open StdLabels
Packit bd2e5d
open UnixLabels
Packit bd2e5d
open Str
Packit bd2e5d
open Filename
Packit bd2e5d
Packit bd2e5d
open Tk
Packit bd2e5d
open Widget
Packit bd2e5d
Packit bd2e5d
exception Not_selected
Packit bd2e5d
Packit bd2e5d
(********************************************************** Search directory *)
Packit bd2e5d
(* Default is curdir *)
Packit bd2e5d
let global_dir = ref (getcwd ())
Packit bd2e5d
Packit bd2e5d
(***************************************************** Some widgets creation *)
Packit bd2e5d
Packit bd2e5d
(* from frx_listbox.ml *)
Packit bd2e5d
let scroll_link sb lb =
Packit bd2e5d
  Listbox.configure lb ~yscrollcommand: (Scrollbar.set sb);
Packit bd2e5d
  Scrollbar.configure sb ~command: (Listbox.yview lb)
Packit bd2e5d
Packit bd2e5d
(* focus when enter binding *)
Packit bd2e5d
let bind_enter_focus w =
Packit bd2e5d
  bind w ~events:[`Enter] ~action:(fun _ -> Focus.set w);;
Packit bd2e5d
Packit bd2e5d
let myentry_create p ~variable =
Packit bd2e5d
  let w = Entry.create p ~relief: `Sunken ~textvariable: variable in
Packit bd2e5d
  bind_enter_focus w; w
Packit bd2e5d
Packit bd2e5d
(************************************************************* Subshell call *)
Packit bd2e5d
Packit bd2e5d
let subshell cmd =
Packit bd2e5d
  let r,w = pipe () in
Packit bd2e5d
    match fork () with
Packit bd2e5d
      0 -> close r; dup2 w stdout;
Packit bd2e5d
           execv ~prog:"/bin/sh" ~args:[| "/bin/sh"; "-c"; cmd |]
Packit bd2e5d
    | id ->
Packit bd2e5d
        close w;
Packit bd2e5d
        let rc = in_channel_of_descr r in
Packit bd2e5d
        let rec it l =
Packit bd2e5d
          match
Packit bd2e5d
            try Some(input_line rc) with _ -> None
Packit bd2e5d
          with
Packit bd2e5d
            Some x -> it (x::l)
Packit bd2e5d
          | None -> List.rev l
Packit bd2e5d
        in
Packit bd2e5d
        let answer = it [] in
Packit bd2e5d
        close_in rc;  (* because of finalize_channel *)
Packit bd2e5d
        let _ = waitpid ~mode:[] id in answer
Packit bd2e5d
Packit bd2e5d
(***************************************************************** Path name *)
Packit bd2e5d
Packit bd2e5d
(* find directory name which doesn't contain "?*[" *)
Packit bd2e5d
let dirget = regexp "^\\([^\\*?[]*/\\)\\(.*\\)"
Packit bd2e5d
Packit bd2e5d
let parse_filter src =
Packit bd2e5d
  (* replace // by / *)
Packit bd2e5d
  let s = global_replace (regexp "/+") "/" src in
Packit bd2e5d
  (* replace /./ by / *)
Packit bd2e5d
  let s = global_replace (regexp "/\\./") "/" s in
Packit bd2e5d
  (* replace ????/../ by "" *)
Packit bd2e5d
  let s = global_replace
Packit bd2e5d
      (regexp "\\([^/]\\|[^\\./][^/]\\|[^/][^\\./]\\|[^/][^/]+\\)/\\.\\./")
Packit bd2e5d
      ""
Packit bd2e5d
      s in
Packit bd2e5d
  (* replace ????/..$ by "" *)
Packit bd2e5d
  let s = global_replace
Packit bd2e5d
      (regexp "\\([^/]\\|[^\\./][^/]\\|[^/][^\\./]\\|[^/][^/]+\\)/\\.\\.$")
Packit bd2e5d
      ""
Packit bd2e5d
      s in
Packit bd2e5d
  (* replace ^/../../ by / *)
Packit bd2e5d
  let s = global_replace (regexp "^\\(/\\.\\.\\)+/") "/" s in
Packit bd2e5d
  if string_match dirget s 0 then
Packit bd2e5d
    let dirs = matched_group 1 s
Packit bd2e5d
    and ptrn = matched_group 2 s
Packit bd2e5d
    in
Packit bd2e5d
      dirs, ptrn
Packit bd2e5d
  else "", s
Packit bd2e5d
Packit bd2e5d
let ls dir pattern =
Packit bd2e5d
  subshell ("cd " ^ dir ^ ";/bin/ls -ad " ^ pattern ^" 2>/dev/null")
Packit bd2e5d
Packit bd2e5d
(*************************************************************** File System *)
Packit bd2e5d
Packit bd2e5d
let get_files_in_directory dir =
Packit bd2e5d
  let dirh = opendir dir in
Packit bd2e5d
  let rec get_them l =
Packit bd2e5d
    match
Packit bd2e5d
      try Some(Unix.readdir dirh) with _ -> None
Packit bd2e5d
    with
Packit bd2e5d
    | None ->
Packit bd2e5d
        Unix.closedir dirh; l
Packit bd2e5d
    | Some x ->
Packit bd2e5d
        get_them (x::l)
Packit bd2e5d
  in
Packit bd2e5d
  List.sort ~cmp:compare (get_them [])
Packit bd2e5d
Packit bd2e5d
let rec get_directories_in_files path =
Packit bd2e5d
  List.filter
Packit bd2e5d
    ~f:(fun x -> try (stat (path ^ x)).st_kind = S_DIR with _ -> false)
Packit bd2e5d
Packit bd2e5d
let remove_directories path =
Packit bd2e5d
  List.filter
Packit bd2e5d
    ~f:(fun x -> try (stat (path ^ x)).st_kind <> S_DIR with _ -> false)
Packit bd2e5d
Packit bd2e5d
(************************* a nice interface to listbox - from frx_listbox.ml *)
Packit bd2e5d
Packit bd2e5d
let add_completion lb action =
Packit bd2e5d
  let prefx = ref ""              (* current match prefix *)
Packit bd2e5d
  and maxi = ref 0                (* maximum index (doesn'y matter actually) *)
Packit bd2e5d
  and current = ref 0              (* current position *)
Packit bd2e5d
  and lastevent = ref 0 in
Packit bd2e5d
Packit bd2e5d
  let rec move_forward () =
Packit bd2e5d
    if Listbox.get lb ~index:(`Num !current) < !prefx then
Packit bd2e5d
      if !current < !maxi then begin incr current; move_forward() end
Packit bd2e5d
Packit bd2e5d
  and recenter () =
Packit bd2e5d
    let element = `Num !current in
Packit bd2e5d
     (* Clean the selection *)
Packit bd2e5d
     Listbox.selection_clear lb ~first:(`Num 0) ~last:`End;
Packit bd2e5d
     (* Set it to our unique element *)
Packit bd2e5d
     Listbox.selection_set lb ~first:element ~last:element;
Packit bd2e5d
     (* Activate it, to keep consistent with Up/Down.
Packit bd2e5d
        You have to be in Extended or Browse mode *)
Packit bd2e5d
     Listbox.activate lb ~index:element;
Packit bd2e5d
     Listbox.selection_anchor lb ~index:element;
Packit bd2e5d
     Listbox.see lb ~index:element in
Packit bd2e5d
Packit bd2e5d
  let complete time s =
Packit bd2e5d
    if time - !lastevent < 500 then   (* sorry, hard coded limit *)
Packit bd2e5d
      prefx := !prefx ^ s
Packit bd2e5d
    else begin (* reset *)
Packit bd2e5d
      current := 0;
Packit bd2e5d
      prefx := s
Packit bd2e5d
    end;
Packit bd2e5d
    lastevent := time;
Packit bd2e5d
    move_forward();
Packit bd2e5d
    recenter() in
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
  bind lb ~events:[`KeyPress] ~fields:[`Char; `Time]
Packit bd2e5d
    (* consider only keys producing characters. The callback is called
Packit bd2e5d
       if you press Shift. *)
Packit bd2e5d
    ~action:(fun ev -> if ev.ev_Char <> "" then complete ev.ev_Time ev.ev_Char);
Packit bd2e5d
  (* Key specific bindings override KeyPress *)
Packit bd2e5d
  bind lb ~events:[`KeyPressDetail "Return"] ~action;
Packit bd2e5d
  (* Finally, we have to set focus, otherwise events dont get through *)
Packit bd2e5d
  Focus.set lb;
Packit bd2e5d
  recenter()   (* so that first item is selected *);
Packit bd2e5d
  (* returns init_completion function *)
Packit bd2e5d
  (fun lb ->
Packit bd2e5d
    prefx := "";
Packit bd2e5d
    maxi := Listbox.size lb - 1;
Packit bd2e5d
    current := 0)
Packit bd2e5d
Packit bd2e5d
(****************************************************************** Creation *)
Packit bd2e5d
Packit bd2e5d
let f ~title ~action:proc ~filter:deffilter ~file:deffile ~multi ~sync =
Packit bd2e5d
  (* Ah ! Now I regret about the names of the widgets... *)
Packit bd2e5d
Packit bd2e5d
  let current_pattern = ref ""
Packit bd2e5d
  and current_dir = ref "" in
Packit bd2e5d
Packit bd2e5d
  (* init_completions *)
Packit bd2e5d
  let filter_init_completion = ref (fun _ -> ())
Packit bd2e5d
  and directory_init_completion = ref (fun _ -> ()) in
Packit bd2e5d
Packit bd2e5d
  let tl = Toplevel.create default_toplevel in
Packit bd2e5d
  Focus.set tl;
Packit bd2e5d
  Wm.title_set tl title;
Packit bd2e5d
Packit bd2e5d
  let filter_var = Textvariable.create ~on:tl () (* new_temporary *)
Packit bd2e5d
  and selection_var = Textvariable.create ~on:tl ()
Packit bd2e5d
  and sync_var = Textvariable.create ~on:tl () in
Packit bd2e5d
Packit bd2e5d
  let frm' = Frame.create tl ~borderwidth: 1 ~relief: `Raised in
Packit bd2e5d
    let frm = Frame.create frm' ~borderwidth: 8 in
Packit bd2e5d
    let fl = Label.create  frm ~text: "Filter" in
Packit bd2e5d
    let df = Frame.create frm in
Packit bd2e5d
      let dfl = Frame.create df in
Packit bd2e5d
        let dfll = Label.create dfl ~text: "Directories" in
Packit bd2e5d
        let dflf = Frame.create dfl in
Packit bd2e5d
          let directory_listbox = Listbox.create dflf ~relief: `Sunken
Packit bd2e5d
          and directory_scrollbar = Scrollbar.create dflf in
Packit bd2e5d
            scroll_link directory_scrollbar directory_listbox;
Packit bd2e5d
      let dfr = Frame.create df in
Packit bd2e5d
        let dfrl = Label.create dfr ~text: "Files" in
Packit bd2e5d
        let dfrf = Frame.create dfr in
Packit bd2e5d
          let filter_listbox = Listbox.create dfrf ~relief: `Sunken in
Packit bd2e5d
          let filter_scrollbar = Scrollbar.create dfrf in
Packit bd2e5d
            scroll_link filter_scrollbar filter_listbox;
Packit bd2e5d
    let sl = Label.create frm ~text: "Selection" in
Packit bd2e5d
    let filter_entry = myentry_create frm ~variable: filter_var in
Packit bd2e5d
    let selection_entry = myentry_create frm ~variable: selection_var
Packit bd2e5d
    in
Packit bd2e5d
  let cfrm' = Frame.create tl ~borderwidth: 1 ~relief: `Raised in
Packit bd2e5d
    let cfrm = Frame.create cfrm' ~borderwidth: 8 in
Packit bd2e5d
    let dumf = Frame.create cfrm in
Packit bd2e5d
    let dumf2 = Frame.create cfrm in
Packit bd2e5d
Packit bd2e5d
  let configure filter =
Packit bd2e5d
    (* OLDER let curdir = getcwd () in *)
Packit bd2e5d
(* Printf.eprintf "CURDIR %s\n" curdir; *)
Packit bd2e5d
    let filter =
Packit bd2e5d
      if string_match (regexp "^/.*") filter 0 then filter
Packit bd2e5d
      else
Packit bd2e5d
        if filter = "" then !global_dir ^ "/*"
Packit bd2e5d
        else !global_dir ^ "/" ^ filter in
Packit bd2e5d
(* Printf.eprintf "FILTER %s\n" filter; *)
Packit bd2e5d
    let dirname, patternname = parse_filter filter in
Packit bd2e5d
(* Printf.eprintf "DIRNAME %s PATTERNNAME %s\n" dirname patternname; *)
Packit bd2e5d
      current_dir := dirname;
Packit bd2e5d
      global_dir := dirname;
Packit bd2e5d
    let patternname = if patternname = "" then "*" else patternname in
Packit bd2e5d
      current_pattern := patternname;
Packit bd2e5d
    let filter = dirname ^ patternname in
Packit bd2e5d
(* Printf.eprintf "FILTER : %s\n\n" filter; *)
Packit bd2e5d
(* flush Pervasives.stderr; *)
Packit bd2e5d
    try
Packit bd2e5d
      let directories = get_directories_in_files dirname
Packit bd2e5d
            (get_files_in_directory dirname) in
Packit bd2e5d
      (* get matched file by subshell call. *)
Packit bd2e5d
      let matched_files = remove_directories dirname (ls dirname patternname)
Packit bd2e5d
      in
Packit bd2e5d
        Textvariable.set filter_var filter;
Packit bd2e5d
        Textvariable.set selection_var (dirname ^ deffile);
Packit bd2e5d
        Listbox.delete directory_listbox ~first:(`Num 0) ~last:`End;
Packit bd2e5d
        Listbox.insert directory_listbox ~index:`End ~texts:directories;
Packit bd2e5d
        Listbox.delete filter_listbox ~first:(`Num 0) ~last:`End;
Packit bd2e5d
        Listbox.insert filter_listbox ~index:`End ~texts:matched_files;
Packit bd2e5d
        !directory_init_completion directory_listbox;
Packit bd2e5d
        !filter_init_completion filter_listbox
Packit bd2e5d
    with
Packit bd2e5d
      Unix_error (ENOENT,_,_) ->
Packit bd2e5d
        (* Directory is not found (maybe) *)
Packit bd2e5d
        Bell.ring ()
Packit bd2e5d
  in
Packit bd2e5d
Packit bd2e5d
  let selected_files = ref [] in (* used for synchronous mode *)
Packit bd2e5d
  let activate l () =
Packit bd2e5d
    Grab.release tl;
Packit bd2e5d
    destroy tl;
Packit bd2e5d
    if sync then
Packit bd2e5d
      begin
Packit bd2e5d
        selected_files := l;
Packit bd2e5d
        Textvariable.set sync_var "1"
Packit bd2e5d
      end
Packit bd2e5d
    else
Packit bd2e5d
      begin
Packit bd2e5d
        proc l;
Packit bd2e5d
        break ()
Packit bd2e5d
      end
Packit bd2e5d
  in
Packit bd2e5d
Packit bd2e5d
  (* and buttons *)
Packit bd2e5d
    let okb = Button.create cfrm ~text: "OK" ~command:
Packit bd2e5d
      begin fun () ->
Packit bd2e5d
        let files =
Packit bd2e5d
          List.map (Listbox.curselection filter_listbox)
Packit bd2e5d
            ~f:(fun x -> !current_dir ^ (Listbox.get filter_listbox ~index:x))
Packit bd2e5d
        in
Packit bd2e5d
        let files = if files = [] then [Textvariable.get selection_var]
Packit bd2e5d
                                  else files in
Packit bd2e5d
        activate files ()
Packit bd2e5d
      end
Packit bd2e5d
    in
Packit bd2e5d
    let flb = Button.create cfrm ~text: "Filter"
Packit bd2e5d
      ~command: (fun () -> configure (Textvariable.get filter_var)) in
Packit bd2e5d
    let ccb = Button.create cfrm ~text: "Cancel"
Packit bd2e5d
      ~command: (fun () -> activate [] ()) in
Packit bd2e5d
Packit bd2e5d
  (* binding *)
Packit bd2e5d
  bind selection_entry ~events:[`KeyPressDetail "Return"] ~breakable:true
Packit bd2e5d
    ~action:(fun _ -> activate [Textvariable.get selection_var] ());
Packit bd2e5d
  bind filter_entry ~events:[`KeyPressDetail "Return"]
Packit bd2e5d
      ~action:(fun _ -> configure (Textvariable.get filter_var));
Packit bd2e5d
Packit bd2e5d
  let action _ =
Packit bd2e5d
      let files =
Packit bd2e5d
        List.map (Listbox.curselection filter_listbox)
Packit bd2e5d
          ~f:(fun x -> !current_dir ^ (Listbox.get filter_listbox ~index:x))
Packit bd2e5d
      in
Packit bd2e5d
        activate files ()
Packit bd2e5d
  in
Packit bd2e5d
  bind filter_listbox ~events:[`Modified([`Double], `ButtonPressDetail 1)]
Packit bd2e5d
    ~breakable:true ~action;
Packit bd2e5d
  if multi then Listbox.configure filter_listbox ~selectmode: `Multiple;
Packit bd2e5d
  filter_init_completion := add_completion filter_listbox action;
Packit bd2e5d
Packit bd2e5d
  let action _ =
Packit bd2e5d
    try
Packit bd2e5d
      configure (!current_dir ^ ((function
Packit bd2e5d
          [x] -> Listbox.get directory_listbox ~index:x
Packit bd2e5d
        | _ -> (* you must choose at least one directory. *)
Packit bd2e5d
            Bell.ring (); raise Not_selected)
Packit bd2e5d
       (Listbox.curselection directory_listbox)) ^ "/" ^ !current_pattern)
Packit bd2e5d
    with _ -> () in
Packit bd2e5d
  bind directory_listbox ~events:[`Modified([`Double], `ButtonPressDetail 1)]
Packit bd2e5d
    ~breakable:true ~action;
Packit bd2e5d
  Listbox.configure directory_listbox ~selectmode: `Browse;
Packit bd2e5d
  directory_init_completion := add_completion directory_listbox action;
Packit bd2e5d
Packit bd2e5d
    pack [frm'; frm] ~fill: `X;
Packit bd2e5d
    (* filter *)
Packit bd2e5d
    pack [fl] ~side: `Top ~anchor: `W;
Packit bd2e5d
    pack [filter_entry] ~side: `Top ~fill: `X;
Packit bd2e5d
    (* directory + files *)
Packit bd2e5d
    pack [df] ~side: `Top ~fill: `X ~ipadx: 8;
Packit bd2e5d
    (* directory *)
Packit bd2e5d
    pack [dfl] ~side: `Left;
Packit bd2e5d
    pack [dfll] ~side: `Top ~anchor: `W;
Packit bd2e5d
    pack [dflf] ~side: `Top;
Packit bd2e5d
    pack [coe directory_listbox; coe directory_scrollbar]
Packit bd2e5d
                                          ~side: `Left ~fill: `Y;
Packit bd2e5d
    (* files *)
Packit bd2e5d
    pack [dfr] ~side: `Right;
Packit bd2e5d
    pack [dfrl] ~side: `Top ~anchor: `W;
Packit bd2e5d
    pack [dfrf] ~side: `Top;
Packit bd2e5d
    pack [coe filter_listbox; coe filter_scrollbar] ~side: `Left ~fill: `Y;
Packit bd2e5d
    (* selection *)
Packit bd2e5d
    pack [sl] ~side: `Top ~anchor: `W;
Packit bd2e5d
    pack [selection_entry] ~side: `Top ~fill: `X;
Packit bd2e5d
Packit bd2e5d
    (* create OK, Filter and Cancel buttons *)
Packit bd2e5d
    pack [cfrm'] ~fill: `X;
Packit bd2e5d
    pack [cfrm] ~fill: `X;
Packit bd2e5d
    pack [okb] ~side: `Left;
Packit bd2e5d
    pack [dumf] ~side: `Left ~expand: true;
Packit bd2e5d
    pack [flb] ~side: `Left;
Packit bd2e5d
    pack [dumf2] ~side: `Left ~expand: true;
Packit bd2e5d
    pack [ccb] ~side: `Left;
Packit bd2e5d
Packit bd2e5d
    configure deffilter;
Packit bd2e5d
Packit bd2e5d
    Tkwait.visibility tl;
Packit bd2e5d
    Grab.set tl;
Packit bd2e5d
Packit bd2e5d
    if sync then
Packit bd2e5d
      begin
Packit bd2e5d
        Tkwait.variable sync_var;
Packit bd2e5d
        proc !selected_files
Packit bd2e5d
      end;
Packit bd2e5d
    ()