1 (***********************************************************************)
3 (* MLTk, Tcl/Tk interface of Objective Caml *)
5 (* Francois Rouaix, Francois Pessaux, Jun Furuse and Pierre Weis *)
6 (* projet Cristal, INRIA Rocquencourt *)
7 (* Jacques Garrigue, Kyoto University RIMS *)
9 (* Copyright 2002 Institut National de Recherche en Informatique et *)
10 (* en Automatique and Kyoto University. All rights reserved. *)
11 (* This file is distributed under the terms of the GNU Library *)
12 (* General Public License, with the special exception on linking *)
13 (* described in file LICENSE found in the Objective Caml source tree. *)
15 (***********************************************************************)
17 (* $Id: fileselect.ml 6757 2005-01-28 16:13:11Z doligez $ *)
19 (* file selection box *)
21 (* This file selecter works only under the OS with the full unix support.
22 For the portability, Tk.getOpenFile and Tk.getSaveFile are recommended. *)
32 exception Not_selected
34 (********************************************************** Search directory *)
35 (* Default is curdir *)
36 let global_dir = ref (getcwd ())
38 (***************************************************** Some widgets creation *)
40 (* from frx_listbox.ml *)
41 let scroll_link sb lb =
42 Listbox.configure lb ~yscrollcommand: (Scrollbar.set sb);
43 Scrollbar.configure sb ~command: (Listbox.yview lb)
45 (* focus when enter binding *)
46 let bind_enter_focus w =
47 bind w ~events:[`Enter] ~action:(fun _ -> Focus.set w);;
49 let myentry_create p ~variable =
50 let w = Entry.create p ~relief: `Sunken ~textvariable: variable in
53 (************************************************************* Subshell call *)
58 0 -> close r; dup2 ~src:w ~dst:stdout;
59 execv ~prog:"/bin/sh" ~args:[| "/bin/sh"; "-c"; cmd |]
62 let rc = in_channel_of_descr r in
65 try Some(input_line rc) with _ -> None
71 close_in rc; (* because of finalize_channel *)
72 let _ = waitpid ~mode:[] id in answer
74 (***************************************************************** Path name *)
76 (* find directory name which doesn't contain "?*[" *)
77 let dirget = regexp "^\\([^\\*?[]*/\\)\\(.*\\)"
79 let parse_filter src =
81 let s = global_replace (regexp "/+") "/" src in
82 (* replace /./ by / *)
83 let s = global_replace (regexp "/\\./") "/" s in
84 (* replace ????/../ by "" *)
85 let s = global_replace
86 (regexp "\\([^/]\\|[^\\./][^/]\\|[^/][^\\./]\\|[^/][^/]+\\)/\\.\\./")
89 (* replace ????/..$ by "" *)
90 let s = global_replace
91 (regexp "\\([^/]\\|[^\\./][^/]\\|[^/][^\\./]\\|[^/][^/]+\\)/\\.\\.$")
94 (* replace ^/../../ by / *)
95 let s = global_replace (regexp "^\\(/\\.\\.\\)+/") "/" s in
96 if string_match dirget s 0 then
97 let dirs = matched_group 1 s
98 and ptrn = matched_group 2 s
104 subshell ("cd " ^ dir ^ ";/bin/ls -ad " ^ pattern ^" 2>/dev/null")
106 (*************************************************************** File System *)
108 let get_files_in_directory dir =
109 let dirh = opendir dir in
112 try Some(Unix.readdir dirh) with _ -> None
115 Unix.closedir dirh; l
119 List.sort ~cmp:compare (get_them [])
121 let rec get_directories_in_files path =
123 ~f:(fun x -> try (stat (path ^ x)).st_kind = S_DIR with _ -> false)
125 let remove_directories path =
127 ~f:(fun x -> try (stat (path ^ x)).st_kind <> S_DIR with _ -> false)
129 (************************* a nice interface to listbox - from frx_listbox.ml *)
131 let add_completion lb action =
132 let prefx = ref "" (* current match prefix *)
133 and maxi = ref 0 (* maximum index (doesn'y matter actually) *)
134 and current = ref 0 (* current position *)
135 and lastevent = ref 0 in
137 let rec move_forward () =
138 if Listbox.get lb ~index:(`Num !current) < !prefx then
139 if !current < !maxi then begin incr current; move_forward() end
142 let element = `Num !current in
143 (* Clean the selection *)
144 Listbox.selection_clear lb ~first:(`Num 0) ~last:`End;
145 (* Set it to our unique element *)
146 Listbox.selection_set lb ~first:element ~last:element;
147 (* Activate it, to keep consistent with Up/Down.
148 You have to be in Extended or Browse mode *)
149 Listbox.activate lb ~index:element;
150 Listbox.selection_anchor lb ~index:element;
151 Listbox.see lb ~index:element in
153 let complete time s =
154 if time - !lastevent < 500 then (* sorry, hard coded limit *)
156 else begin (* reset *)
165 bind lb ~events:[`KeyPress] ~fields:[`Char; `Time]
166 (* consider only keys producing characters. The callback is called
167 if you press Shift. *)
168 ~action:(fun ev -> if ev.ev_Char <> "" then complete ev.ev_Time ev.ev_Char);
169 (* Key specific bindings override KeyPress *)
170 bind lb ~events:[`KeyPressDetail "Return"] ~action;
171 (* Finally, we have to set focus, otherwise events dont get through *)
173 recenter() (* so that first item is selected *);
174 (* returns init_completion function *)
177 maxi := Listbox.size lb - 1;
180 (****************************************************************** Creation *)
182 let f ~title ~action:proc ~filter:deffilter ~file:deffile ~multi ~sync =
183 (* Ah ! Now I regret about the names of the widgets... *)
185 let current_pattern = ref ""
186 and current_dir = ref "" in
188 (* init_completions *)
189 let filter_init_completion = ref (fun _ -> ())
190 and directory_init_completion = ref (fun _ -> ()) in
192 let tl = Toplevel.create default_toplevel in
194 Wm.title_set tl title;
196 let filter_var = Textvariable.create ~on:tl () (* new_temporary *)
197 and selection_var = Textvariable.create ~on:tl ()
198 and sync_var = Textvariable.create ~on:tl () in
200 let frm' = Frame.create tl ~borderwidth: 1 ~relief: `Raised in
201 let frm = Frame.create frm' ~borderwidth: 8 in
202 let fl = Label.create frm ~text: "Filter" in
203 let df = Frame.create frm in
204 let dfl = Frame.create df in
205 let dfll = Label.create dfl ~text: "Directories" in
206 let dflf = Frame.create dfl in
207 let directory_listbox = Listbox.create dflf ~relief: `Sunken
208 and directory_scrollbar = Scrollbar.create dflf in
209 scroll_link directory_scrollbar directory_listbox;
210 let dfr = Frame.create df in
211 let dfrl = Label.create dfr ~text: "Files" in
212 let dfrf = Frame.create dfr in
213 let filter_listbox = Listbox.create dfrf ~relief: `Sunken in
214 let filter_scrollbar = Scrollbar.create dfrf in
215 scroll_link filter_scrollbar filter_listbox;
216 let sl = Label.create frm ~text: "Selection" in
217 let filter_entry = myentry_create frm ~variable: filter_var in
218 let selection_entry = myentry_create frm ~variable: selection_var
220 let cfrm' = Frame.create tl ~borderwidth: 1 ~relief: `Raised in
221 let cfrm = Frame.create cfrm' ~borderwidth: 8 in
222 let dumf = Frame.create cfrm in
223 let dumf2 = Frame.create cfrm in
225 let configure filter =
226 (* OLDER let curdir = getcwd () in *)
227 (* Printf.eprintf "CURDIR %s\n" curdir; *)
229 if string_match (regexp "^/.*") filter 0 then filter
231 if filter = "" then !global_dir ^ "/*"
232 else !global_dir ^ "/" ^ filter in
233 (* Printf.eprintf "FILTER %s\n" filter; *)
234 let dirname, patternname = parse_filter filter in
235 (* Printf.eprintf "DIRNAME %s PATTERNNAME %s\n" dirname patternname; *)
236 current_dir := dirname;
237 global_dir := dirname;
238 let patternname = if patternname = "" then "*" else patternname in
239 current_pattern := patternname;
240 let filter = dirname ^ patternname in
241 (* Printf.eprintf "FILTER : %s\n\n" filter; *)
242 (* flush Pervasives.stderr; *)
244 let directories = get_directories_in_files dirname
245 (get_files_in_directory dirname) in
246 (* get matched file by subshell call. *)
247 let matched_files = remove_directories dirname (ls dirname patternname)
249 Textvariable.set filter_var filter;
250 Textvariable.set selection_var (dirname ^ deffile);
251 Listbox.delete directory_listbox ~first:(`Num 0) ~last:`End;
252 Listbox.insert directory_listbox ~index:`End ~texts:directories;
253 Listbox.delete filter_listbox ~first:(`Num 0) ~last:`End;
254 Listbox.insert filter_listbox ~index:`End ~texts:matched_files;
255 !directory_init_completion directory_listbox;
256 !filter_init_completion filter_listbox
258 Unix_error (ENOENT,_,_) ->
259 (* Directory is not found (maybe) *)
263 let selected_files = ref [] in (* used for synchronous mode *)
270 Textvariable.set sync_var "1"
280 let okb = Button.create cfrm ~text: "OK" ~command:
283 List.map (Listbox.curselection filter_listbox)
284 ~f:(fun x -> !current_dir ^ (Listbox.get filter_listbox ~index:x))
286 let files = if files = [] then [Textvariable.get selection_var]
291 let flb = Button.create cfrm ~text: "Filter"
292 ~command: (fun () -> configure (Textvariable.get filter_var)) in
293 let ccb = Button.create cfrm ~text: "Cancel"
294 ~command: (fun () -> activate [] ()) in
297 bind selection_entry ~events:[`KeyPressDetail "Return"] ~breakable:true
298 ~action:(fun _ -> activate [Textvariable.get selection_var] ());
299 bind filter_entry ~events:[`KeyPressDetail "Return"]
300 ~action:(fun _ -> configure (Textvariable.get filter_var));
304 List.map (Listbox.curselection filter_listbox)
305 ~f:(fun x -> !current_dir ^ (Listbox.get filter_listbox ~index:x))
309 bind filter_listbox ~events:[`Modified([`Double], `ButtonPressDetail 1)]
310 ~breakable:true ~action;
311 if multi then Listbox.configure filter_listbox ~selectmode: `Multiple;
312 filter_init_completion := add_completion filter_listbox action;
316 configure (!current_dir ^ ((function
317 [x] -> Listbox.get directory_listbox ~index:x
318 | _ -> (* you must choose at least one directory. *)
319 Bell.ring (); raise Not_selected)
320 (Listbox.curselection directory_listbox)) ^ "/" ^ !current_pattern)
322 bind directory_listbox ~events:[`Modified([`Double], `ButtonPressDetail 1)]
323 ~breakable:true ~action;
324 Listbox.configure directory_listbox ~selectmode: `Browse;
325 directory_init_completion := add_completion directory_listbox action;
327 pack [frm'; frm] ~fill: `X;
329 pack [fl] ~side: `Top ~anchor: `W;
330 pack [filter_entry] ~side: `Top ~fill: `X;
331 (* directory + files *)
332 pack [df] ~side: `Top ~fill: `X ~ipadx: 8;
334 pack [dfl] ~side: `Left;
335 pack [dfll] ~side: `Top ~anchor: `W;
336 pack [dflf] ~side: `Top;
337 pack [coe directory_listbox; coe directory_scrollbar]
338 ~side: `Left ~fill: `Y;
340 pack [dfr] ~side: `Right;
341 pack [dfrl] ~side: `Top ~anchor: `W;
342 pack [dfrf] ~side: `Top;
343 pack [coe filter_listbox; coe filter_scrollbar] ~side: `Left ~fill: `Y;
345 pack [sl] ~side: `Top ~anchor: `W;
346 pack [selection_entry] ~side: `Top ~fill: `X;
348 (* create OK, Filter and Cancel buttons *)
349 pack [cfrm'] ~fill: `X;
350 pack [cfrm] ~fill: `X;
351 pack [okb] ~side: `Left;
352 pack [dumf] ~side: `Left ~expand: true;
353 pack [flb] ~side: `Left;
354 pack [dumf2] ~side: `Left ~expand: true;
355 pack [ccb] ~side: `Left;
359 Tkwait.visibility tl;
364 Tkwait.variable sync_var;