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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
|
(*************************************************************************)
(* *)
(* OCaml 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, with the special exception on linking *)
(* described in file ../../../LICENSE. *)
(* *)
(*************************************************************************)
(* $Id$ *)
(* file selection box *)
open StdLabels
open Str
open Filename
open Tk
open Useunix
(**** Memoized rexgexp *)
let (~!) = Jg_memo.fast ~f:Str.regexp
(************************************************************ Path name *)
(* Convert Windows-style directory separator '\' to caml-style '/' *)
let caml_dir path =
if Sys.os_type = "Win32" then
global_replace ~!"\\\\" "/" path
else path
let parse_filter s =
let s = caml_dir s in
(* replace // by / *)
let s = global_replace ~!"/+" "/" s in
(* replace /./ by / *)
let s = global_replace ~!"/\\./" "/" s in
(* replace hoge/../ by "" *)
let s = global_replace
~!"\\([^/]\\|[^\\./][^/]\\|[^/][^\\./]\\|[^/][^/]+\\)/\\.\\./" "" s in
(* replace hoge/..$ by *)
let s = global_replace
~!"\\([^/]\\|[^\\./][^/]\\|[^/][^\\./]\\|[^/][^/]+\\)/\\.\\.$" "" s in
(* replace ^/hoge/../ by / *)
let s = global_replace ~!"^\\(/\\.\\.\\)+/" "/" s in
if string_match ~!"^\\([^\\*?[]*[/:]\\)\\(.*\\)" s 0 then
let dirs = matched_group 1 s
and ptrn = matched_group 2 s
in
dirs, ptrn
else "", s
let rec fixpoint ~f v =
let v' = f v in
if v = v' then v else fixpoint ~f v'
let unix_regexp s =
let s = Str.global_replace ~!"[$^.+]" "\\\\\\0" s in
let s = Str.global_replace ~!"\\*" ".*" s in
let s = Str.global_replace ~!"\\?" ".?" s in
let s =
fixpoint s
~f:(Str.replace_first ~!"\\({[^,}]*\\)," "\\1\\|") in
let s =
Str.global_replace ~!"{\\([^}]*\\)}" "\\(\\1\\)" s in
let s = s ^ "$" in
Str.regexp s
let exact_match ~pat s =
Str.string_match pat s 0 && Str.match_end () = String.length s
let ls ~dir ~pattern =
let files = get_files_in_directory dir in
let regexp = unix_regexp pattern in
List.filter files ~f:(exact_match ~pat:regexp)
(********************************************* Creation *)
let load_in_path = ref false
let search_in_path ~name = Misc.find_in_path (Load_path.get_path_list ()) name
let f ~title ~action:proc ?(dir = Unix.getcwd ())
?filter:(deffilter ="*") ?file:(deffile ="")
?(multi=false) ?(sync=false) ?(usepath=true) () =
let current_pattern = ref ""
and current_dir = ref (caml_dir dir) in
let may_prefix name =
if Filename.is_relative name then concat !current_dir name else name in
let tl = Jg_toplevel.titled title in
Focus.set tl;
let new_var () = Textvariable.create ~on:tl () in
let filter_var = new_var ()
and selection_var = new_var ()
and sync_var = new_var () in
Textvariable.set filter_var deffilter;
let frm = Frame.create tl ~borderwidth:1 ~relief:`Raised in
let df = Frame.create frm in
let dfl = Frame.create df in
let dfll = Label.create dfl ~text:"Directories" in
let dflf, directory_listbox, directory_scrollbar =
Jg_box.create_with_scrollbar dfl in
let dfr = Frame.create df in
let dfrl = Label.create dfr ~text:"Files" in
let dfrf, filter_listbox, filter_scrollbar =
Jg_box.create_with_scrollbar dfr in
let cfrm = Frame.create tl ~borderwidth:1 ~relief:`Raised in
let configure ~filter =
let filter = may_prefix filter in
let dir, pattern = parse_filter filter in
let dir = if !load_in_path && usepath then "" else
(current_dir := dir; dir)
and pattern = if pattern = "" then "*" else pattern in
current_pattern := pattern;
let filter =
if !load_in_path && usepath then pattern else dir ^ pattern in
let directories = get_directories_in_files ~path:dir
(get_files_in_directory dir) in
let matched_files = (* get matched file by subshell call. *)
if !load_in_path && usepath then
List.fold_left (Load_path.get_path_list ()) ~init:[] ~f:
begin fun acc dir ->
let files = ls ~dir ~pattern in
List.merge ~cmp:compare files
(List.fold_left files ~init:acc
~f:(fun acc name -> List2.exclude name acc))
end
else
List.fold_left directories ~init:(ls ~dir ~pattern)
~f:(fun acc dir -> List2.exclude dir acc)
in
Textvariable.set filter_var filter;
Textvariable.set selection_var (dir ^ deffile);
Listbox.delete filter_listbox ~first:(`Num 0) ~last:`End;
Listbox.insert filter_listbox ~index:`End ~texts:matched_files;
Jg_box.recenter filter_listbox ~index:(`Num 0);
if !load_in_path && usepath then
Listbox.configure directory_listbox ~takefocus:false
else
begin
Listbox.configure directory_listbox ~takefocus:true;
Listbox.delete directory_listbox ~first:(`Num 0) ~last:`End;
Listbox.insert directory_listbox ~index:`End ~texts:directories;
Jg_box.recenter directory_listbox ~index:(`Num 0)
end
in
let selected_files = ref [] in (* used for synchronous mode *)
let activate l =
Grab.release tl;
destroy tl;
let l =
if !load_in_path && usepath then
List.fold_right l ~init:[] ~f:
begin fun name acc ->
if not (Filename.is_implicit name) then
may_prefix name :: acc
else try search_in_path ~name :: acc with Not_found -> acc
end
else
List.map l ~f:may_prefix
in
if sync then
begin
selected_files := l;
Textvariable.set sync_var "1"
end
else proc l
in
(* entries *)
let fl = Label.create frm ~text:"Filter" in
let sl = Label.create frm ~text:"Selection" in
let filter_entry = Jg_entry.create frm ~textvariable:filter_var
~command:(fun filter -> configure ~filter) in
let selection_entry = Jg_entry.create frm ~textvariable:selection_var
~command:(fun file -> activate [file]) in
(* and buttons *)
let set_path = Button.create dfl ~text:"Path editor" ~command:
begin fun () ->
Setpath.add_update_hook (fun () -> configure ~filter:!current_pattern);
let w = Setpath.f ~dir:!current_dir in
Grab.set w;
bind w ~events:[`Destroy] ~extend:true ~action:(fun _ -> Grab.set tl)
end in
let toggle_in_path = Checkbutton.create dfl ~text:"Use load path"
~command:
begin fun () ->
load_in_path := not !load_in_path;
if !load_in_path then
pack [set_path] ~side:`Bottom ~fill:`X ~expand:true
else
Pack.forget [set_path];
configure ~filter:(Textvariable.get filter_var)
end
and okb = Button.create cfrm ~text:"Ok" ~command:
begin fun () ->
let files =
if not multi then [] else
List.map (Listbox.curselection filter_listbox) ~f:
begin fun x ->
!current_dir ^ Listbox.get filter_listbox ~index:x
end
in
let files = if files = [] then [Textvariable.get selection_var]
else files in
activate files
end
and flb = Button.create cfrm ~text:"Filter"
~command:(fun () -> configure ~filter:(Textvariable.get filter_var))
and ccb = Button.create cfrm ~text:"Cancel"
~command:(fun () -> activate []) in
(* binding *)
bind tl ~events:[`KeyPressDetail "Escape"] ~action:(fun _ -> activate []);
Jg_box.add_completion filter_listbox
~action:(fun index -> activate [Listbox.get filter_listbox ~index]);
if multi then Listbox.configure filter_listbox ~selectmode:`Multiple else
bind filter_listbox ~events:[`ButtonPressDetail 1] ~fields:[`MouseY]
~action:(fun ev ->
let name = Listbox.get filter_listbox
~index:(Listbox.nearest filter_listbox ~y:ev.ev_MouseY) in
if !load_in_path && usepath then
try Textvariable.set selection_var (search_in_path ~name)
with Not_found -> ()
else Textvariable.set selection_var (may_prefix name));
Jg_box.add_completion directory_listbox ~action:
begin fun index ->
let filter =
may_prefix (Listbox.get directory_listbox ~index) ^
"/" ^ !current_pattern
in configure ~filter
end;
pack [frm] ~fill:`Both ~expand:true;
(* filter *)
pack [fl] ~side:`Top ~anchor:`W;
pack [filter_entry] ~side:`Top ~fill:`X;
(* directory + files *)
pack [df] ~side:`Top ~fill:`Both ~expand:true;
(* directory *)
pack [dfl] ~side:`Left ~fill:`Both ~expand:true;
pack [dfll] ~side:`Top ~anchor:`W;
if usepath then pack [toggle_in_path] ~side:`Bottom ~anchor:`W;
pack [dflf] ~side:`Top ~fill:`Both ~expand:true;
pack [directory_scrollbar] ~side:`Right ~fill:`Y;
pack [directory_listbox] ~side:`Left ~fill:`Both ~expand:true;
(* files *)
pack [dfr] ~side:`Right ~fill:`Both ~expand:true;
pack [dfrl] ~side:`Top ~anchor:`W;
pack [dfrf] ~side:`Top ~fill:`Both ~expand:true;
pack [filter_scrollbar] ~side:`Right ~fill:`Y;
pack [filter_listbox] ~side:`Left ~fill:`Both ~expand:true;
(* selection *)
pack [sl] ~before:df ~side:`Bottom ~anchor:`W;
pack [selection_entry] ~before:sl ~side:`Bottom ~fill:`X;
(* create OK, Filter and Cancel buttons *)
pack [okb; flb; ccb] ~side:`Left ~fill:`X ~expand:true;
pack [cfrm] ~before:frm ~side:`Bottom ~fill:`X;
if !load_in_path && usepath then begin
load_in_path := false;
Checkbutton.invoke toggle_in_path;
Checkbutton.select toggle_in_path
end
else configure ~filter:deffilter;
Tkwait.visibility tl;
Grab.set tl;
if sync then
begin
Tkwait.variable sync_var;
proc !selected_files
end;
()
|