1# <20190707.1312.32>
2
3
4# These should use the "quick" option, I think...
5proc CmdToright {} {
6  global glob
7  NewPwd right $glob(left,pwd)
8  UpdateWindow_ right 1
9}
10
11proc CmdToleft {} {
12  global glob
13  NewPwd left $glob(right,pwd)
14  UpdateWindow_ left 1
15}
16
17proc CmdSwapWindows {} {
18  global glob
19  set tmpright $glob(left,pwd)
20  set glob(forceupdate) 1
21  CmdToleft
22  NewPwd right $tmpright
23  UpdateWindow right
24  set glob(forceupdate) 0
25}
26
27proc BatchReceiveVFS { inst } {
28  global glob
29  if {[IsVFS $glob($inst,pwd)]} {
30    PopInfo [_ "You can only issue a receive request to a non-VFS directory"]
31    return
32  }
33  set olddir $glob([Opposite $inst],pwd)
34  foreach itemblock $glob(batchlist) {
35    set item [lindex $itemblock 0]
36    if {![IsVFS $item]} {
37      PopWarn [_ "Can't parse %s as VFS file" $item]
38    } else {
39      NewPwd [Opposite $inst] $VFStok/
40      set r [Try { VFSgetFile $item  \
41		       "$glob($inst,pwd)/[file tail $item]" \
42		       [lindex $itemblock 1] 0 } "" 1]
43    }
44  }
45  set glob(batchlist) {}
46  set glob(forceupdate) 1
47  NewPwd [Opposite $inst] $olddir
48  UpdateWindow both
49  set glob(forceupdate) 0
50}
51
52proc SandDsame {} {
53  smart_dialog .apop[incr ::uni] . [_ "Oops That doesn't work"] \
54	[list [_ "Please set different source and destination directories!"]]\
55	0 1 [_ "OK"]
56}
57
58proc typeMatch {ttype tfile ftype ffile} {
59  set fType [expr {[string index $ftype end] == "d" ? "d" : "f"}]
60  if {$fType == $ttype} {
61    return 1
62  }
63  if {$ttype == "f"} {
64    set rt "directory:"
65    set tt "     file:"
66  } else {
67    set rt "     file:"
68    set tt "directory:"
69  }
70  # Lets not allow replacing files with dirs or dirs with files
71  PopWarn [_ "If target exist it must be the same (file or directory) as current.\
72               \n%s %s\
73               \n%s %s" $rt $ffile $tt $tfile]
74  return 0
75}
76
77
78# So here are a few observations:
79# 1) fr always does recursive copies, set up the 'cp' command to do this
80# 2) some copy code will not copy a dirs contents unless we have a trailing /
81# 3) other copy code does not care.
82# 4) if the destination ends with a dir the data (files and dirs) go there.
83# 5) if the destination ends with an undefined dir, (i.e. on the destination) one is created
84# 6) the "file tail" command will eliminate a trailing / and return
85#    the resulting tail
86# 7) if the destination dir exists, it is used as is, i.e. existing files that
87#    are not in the source are preserved.
88# 8) For files, the new file replaces the old. If it is desired that this only
89#    happen if the file differs, see rsync copy.
90# 9) The MSW xcopy wanting to assume "/*" is defeated by appending tail to the
91#    destination.
92# 10) By definition when a user says copy/move he is refering to the opposite
93#     pane. Both 'file copy' and cp, given a source dir and a destination
94#     dir do different things depending on that dirs existance cp: (create if not
95#     and make a mirror image, or, if it exists, copy to within). 'file copy'
96#     on the other hand refuses to do the copy. This is NOT what we want and thus
97#     we have written 'frFileCopy' using 'file copy' and 'glob' to get the
98#     desired outcome.
99# 11) In addition neither can be forced to delete the collision dir.
100#
101# So,
102# A) always end dir copy sources with /
103# B) to preserver the trash/recycle bin delete files using delete code,
104#    not cp code.
105# C) always append source "tail" to the destination. This may be altered by
106#    move/copy as code (which is required for local copies depending on
107#    existance of that dir on local dir to dir copies.
108# D) VFS put/get may also need work in this regard.
109
110# CopyCore
111# fromList  a list of files to copy each entry-> {file type size}
112# topwd     the directory to copy them to
113# inst      the pane (left or right)
114# as        1 if we are to ask for new name(s)
115# resume    1 if possible restart, else 0
116# option    "cp" for copy "hardlink" for hard linking instead
117
118proc CopyCore {fromList topwd inst {as 0} resume option} {
119  global config glob
120  # This is intended to be the core copy code. If "as" is true
121  # it asks for a destination. It does all the permission tests
122  # if overlaying files and permission has been requested.
123  # The fromlist should be a list of lists (a sublist for each file)
124  # The first entry should be the file name, the second its type &
125  # The third entry its size (used with VFS)
126  # Other code should assemble the fromlist.
127  # todir is a dir
128  # inst should be the 'from' panel as we may need to modify the 'to' panel
129
130  # first take care of the "as"
131  set copyList {}
132  foreach ent $fromList {
133    lassign $ent file type size
134    if {$type == "fl"} {
135      # size unknown on VFS links
136      set size -1
137    }
138    set ::ssdTmp [URL norm $topwd/[file tail $file]]
139    set typeD [expr {[string index $type end] == "d"}]
140    if {$as} {
141      set query [expr {$typeD ? [_ "directory"] : [_ "file"]}]
142
143      set r [smart_dialog .copy[incr ::uni] "." [_  "Copy As..."] \
144		 [list [_  "Please enter new name for destination %s" $query]] \
145		 1 4 \
146		 [list \
147		      [list {} [list -textvariable ::ssdTmp -width 70]]\
148		      [_ OK] [_ "Skip This File"] [_ "Cancel all copies"]]\
149		 [buildDialogConfig]
150	    ]
151      # 0,1 use result 2 skip this file -1,3 cancel the whole thing.
152      if {$r == -1 || $r == 3} {return}
153      if {$r == 2} {continue}
154      # Make sure it is not a copy into itself..
155    }
156    if {$file == $::ssdTmp} {
157      SandDsame
158      return 0
159    }
160    # Or a recursive self copy...
161    if {$typeD && [string first $file/ $::ssdTmp] == 0} {
162      PopError [_ "Can't %s recursively into same directory (%s -> %s)"\
163		    $option $file $::ssdTmp]
164      return 0
165    }
166    set tType [fileType $::ssdTmp]
167    # tType may have any of three values d=>directory f=>file 0=>does not exist
168    if {$tType != 0 && ![typeMatch $tType $::ssdTmp $type $file]} {
169      return
170    }
171    # pass the tType here as it indicates existance
172    lappend copyList [list $file $typeD $size $::ssdTmp $tType]
173  }
174  # frputs copyList fromList
175  # now we need to check permissions...
176  # do we need to??
177  # We don't delete on copy of dirs...
178  # set dirA $config(ask,dir_delete)
179  set fileA $config(ask,file_delete)
180  # Build a list of asks
181  set testThese {}
182  set doThese {}
183  set dirCount 0
184  set fileCount 0
185  foreach ent $copyList {
186    lassign $ent file typeD size dest dType
187    #set this [string index $type end]
188    frputs file typeD size dest dType
189    if {!$typeD && $fileA && $dType != 0} {
190      frputs ent
191      lappend testThese $ent
192      incr fileCount
193    } else {
194      lappend doThese $ent
195    }
196  }
197  # At this point we have two lists:
198  # doThese, the files to copy unconditionally and
199  # testThese, the files for which we need permission to copy
200
201  # This keeps track of global yes/no from the ok to delete query
202  # 3 global yes
203  # 4 global no
204  # Other vaues -> unknown
205
206  # deleteQuery returns as follows:
207  # -1 s/he Xed out the window (taken to mean cancel all copies)
208  # 0 yes to this file/dir
209  # 1 no  to this file/dir
210  # 2 same a -1, cancel all copies..
211  # 3 yes to all files or dirs (depending on if a file or dir question)
212  # 4 no  to all files or dirs Cancels copy of of files or dirs in this list
213
214  set globalYNfile 0
215  set globalYNdir 0
216  set ty "file"
217  set query [_ "file"]
218
219  foreach ent $testThese {
220    # we only test files, not dirs...
221    lassign $ent file typeD size dest dType
222    # frputs file type size dest globalYNfile globalYNdir
223    switch -exact [set globalYN$ty] {
224      4 {continue}
225      0 -
226      1 {set globalYN$ty [deleteQuery $query $dest  $fileCount [_ "Copy"]]}
227    }
228    switch -exact [set globalYN$ty] {
229      1 -
230      4 {continue}
231      2 {return 0}
232    }
233    # Here we have the ok, lets do it.
234    lappend doThese $ent
235  }
236  # Here is where we can go async. All we do is to pass the list
237  # and option to an action function, either just a call (not async)
238  # or via "after" for async.
239
240  # if {$glob(async)} {
241  #   after 1 [list copyAction $doThese $resume $option]
242  #   return 1
243  # } else {
244  #   return [copyAction $doThese $resume $option]
245  # }
246  Try {copyAction $doThese $resume $option} "" 1 $glob(async)
247}
248proc copyAction {doThese resume option} {
249  global glob config
250
251
252  # All tests are done. All that is left is to do the copy
253  # We divide this up in ways that make it easy...
254  # further from is either a file or a dir
255  # There are these copies:
256  # 0 & 1 from & to local file system (even is file, odd dir)
257  # 2 & 3 from local to VFS
258  # 4 & 5 from VFS to local
259  # 6 & 7 from VFS to VFS (either within the same VFS or not)
260  # all that sounds like a switch is in order
261
262  # set olddir $glob($inst,pwd)
263  foreach ent $doThese {
264    lassign $ent ffile typeD size dest tType
265    if {[CheckAbort [_ "Copy"]]} {
266      UpdateIf $dest $dest/x
267      return 0
268    }
269    set sw [expr {($typeD) +\
270		      2 * [IsVFS $dest] + \
271		      4 * [IsVFS $ffile] }]
272    if {$option == "hardlink"} {
273      if {$sw > 1 && $sw < 6 ||\
274	      $sw > 5 && [string first $VFStok $dest] != 0} {
275	PopWarn [_ "Hard link not possible across file\
276                     systems\n%s to\n%s" $ffile $dest]
277	return 0
278      } elseif {$sw < 2} {
279	# here we run down links (i.e. we always dereference on copy)
280	set ffile [file dir [file norm $ffile/x]]
281      }
282    }
283    if {$typeD} {
284      set ffile $ffile/
285    }
286    if {$tType == "f"} {
287      # We are either async or not, don't let it go another level...
288      deleteFile $dest $tType
289    }
290    frputs sw ffile dest
291    switch -exact $sw {
292      0 {
293	# This is the file copy or hardlink
294	# configure the command...
295	set cpcmd $config(cmd,$option)
296	#set to $topwd
297	if {$cpcmd == {}} {
298	  if {$option == "cp"} {
299	    set cpcmd "frFileCopy"
300	  } else {
301	    set cpcmd "file link -hard %2@s"
302	    set to $tof
303	  }
304	} else {
305	  set cpcmd [list exec {*}[subst {*}$::stOps $cpcmd]]
306	  # if {$glob(async)} {
307	  #   append cpcmd " %b &"
308	  # }
309	}
310	# frputs "pre frECF:  " ffile topwd
311	frECF [list {*}$cpcmd]\
312	    [list $ffile $dest]
313	# [list -b $glob(async)]
314
315      }
316      1 {
317	# This is local to local dir copy
318	Log [_ "%s (d) %s >> %s" $option $ffile $dest]
319
320	set cpcmd $config(cmd,$option)
321	if {$option == "cp"} {
322	  lassign $config(cmd,dircp) cpcmd dirFlag
323
324	  # Most (all?) of the copy commands in windows blightly assume
325	  # that you really ment to put "\*" on the end of source directory
326	  # If the user would really like to back that assumption out
327	  # we allow him to code an option in the config(cmd,dircp)
328	  # (second entry in the list) to tell us what s/he really
329	  # wants. To work this wonder we just add the final dir in
330	  # the source to the destination...
331	  # Uh. that is the default with the new copy code so
332	  # negate the test...
333	  if {![string match {*-\**} $dirFlag]} {
334	    # oops! that must be part of the command
335	    set cpcmd [list {*}$cpcmd {*}$dirFlag]
336	    # To restore the 'normal' windows type behavior..
337	    # only if the tail is the same, else it is a copy 'as'
338	    if {[file tail $ffile] == [file tail $dest]} {
339	      set dest [file norm $dest/..]
340	    }
341	  }
342	  if {$cpcmd == {}} {
343	    if {$option == "cp"} {
344	      set cpcmd "frFileCopy"
345	    } else {
346	      set cpcmd  "recHardLink %b -L"
347	    }
348	  } else {
349	    set cpcmd [list exec {*}[subst {*}$::stOps $cpcmd]]
350	    # if {$glob(async)} {
351	    #   append cpcmd " %b &"
352	    # }
353	  }
354	}
355	frputs ffile dest
356	frECF [list {*}$cpcmd]\
357	    [list $ffile $dest]
358	# [list -b $glob(async)]
359      }
360      2 {
361	# This is local to VFS file copy
362	Try {VFSputFile $dest $ffile $size } "" 1
363      }
364      3 {
365	# This is local to VFS dir copy
366	Try { CopyToVFSRecursive $ffile $dest} "" 1
367      }
368      4 {
369	# This VFS to local file copy
370	frECF [list VFSgetFile] \
371	    [list $ffile\
372		 $dest \
373		 $size \
374		 $resume ]\
375	    [list -default { "%n"}]
376	#	 -back $glob(async)]
377      }
378      5 {
379	# This is VFS to local dir copy
380	Try {CopyFromVFSRecursive "$ffile" "$dest" $resume } "" 1
381	# NewPwd $inst $olddir
382      }
383      7 -
384      6 {
385	# This is VFS to VFS file copy
386	if {$option != "cp"} {
387	  CantDoThat
388	}
389	# If the two VFS's are the same, try the internal copy
390	set VFSsourceTok $VFStok
391	IsVFS $dest
392	set eh {}
393	if {$VFSsourceTok == $VFStok && \
394		[catch {VFScopy $VFStok $ffile $dest} eh] == 0} {
395	  continue
396	  # frputs eh
397	  if {![string match {*does not support*} $eh] && $eh != {}} {
398	    PopError $eh
399	    continue
400	  }
401	}
402	# so that failed, try this
403	if {$sw == 6} {
404	  # Its a file...
405	  Try {VFSputFile $dest [set tmp [MoveToTmp $ffile $type $size]] $size} "" 1
406	  file delete -force $tmp
407	} else {
408	  # A dir copy
409	  set tail [file tail $dest]
410	  if {![Try {CopyFromVFSRecursive $ffile [makeTmp]/$tail $resume} "" 1]} {
411	    Try {CopyToVFSRecursive [set tmp [makeTmp]/$tail/] $dest} "" 1
412	    file delete -force $tmp
413	  }
414	}
415      }
416    }
417  }
418  UpdateIf $dest $dest/x
419  return 1
420}
421
422# # This function tests for existance of the file. This is simple for
423# # local files, but a bit complicated for VFS files. We use 'inst' to
424# # select the opposite in case we need to set up a new list.
425# #
426# proc fileExists {file inst} {
427#   global glob
428#   # do the simple stuff first...
429#   if {![IsVFS $file]} {
430#     return [file exists $file]
431#   }
432#   # Lets assume the most difficult...
433#   # find the dir it is listed in..
434#   set dir [URL norm $file/..]
435#   set pan [Opposite $inst]
436#   set savedPwd $glob($pan,pwd)
437#   # short cut possible?
438#   if {$dir != $savedPwd} {
439#     NewPwd $pan $dir
440#     UpdateWindow $pan
441#   }
442#   set ret [lsearch -exact -index 1  $glob($pan,filelist) [file tail $VFSpath]]
443#   if {$glob($pan,pwd) != $savedPwd} {
444#     NewPwd $pan $savedPwd
445#     UpdateWindow $pan
446#   }
447#   return [expr {$ret == -1 ? 0 : 1}]
448# }
449
450proc CmdCopy {{resume 0} {cmd cp} {as 0}} {
451  global glob
452  CmdCopy_ $glob(listbox,$glob(selected)).file glob($glob(selected),filelist) \
453      $glob($glob(selected),pwd) $glob(selected) $as $resume $cmd
454  # frputs
455  ForceUpdate both
456}
457proc CmdHardlnk {{resume 0}} {
458  CmdCopy $resume hardlink
459}
460
461proc CmdHardlnkAs {} {
462  CmdCopy 0 hardlink 1
463}
464
465proc CmdCopyAs {} {
466  CmdCopy 0 cp 1
467}
468
469proc CmdCopy_ { listb_name filelist_var frompwd  inst as resume option} {
470  global config glob
471  upvar $filelist_var filelist
472  set selList [$listb_name curselection]
473  set fileList {}
474  foreach sel $selList {
475    lassign [lindex $filelist $sel] s file type size
476    set ffile  [DNtoDirTail [URL norm $frompwd/$file]]
477    lappend fileList [list $ffile $type $size]
478  }
479  CopyCore $fileList $glob([Opposite $inst],pwd) $inst $as $resume $option
480}
481
482
483proc SoftLink { src dst } {
484  global config
485  if {$config(create_relative_links)} {
486    set srcdir [file dirname $src]
487    set dstdir [file dirname $dst]
488    set srcfile [file tail $src]
489    set dstfile [file tail $dst]
490    set dstlist [split $dstdir /]
491    set srclist [split $srcdir /]
492    set dstlen [llength $dstlist]
493    set srclen [llength $srclist]
494    # Count how many directories are the same in the source and destination paths
495    set index 0
496    while {([lindex $srclist $index] == [lindex $dstlist $index]) && \
497	       ($index < $srclen) && ($index < $dstlen)} {
498      incr index
499    }
500    # Build relative link
501    set link {}
502    for {set dstindex $index} {$dstindex < $dstlen} {incr dstindex} {
503      append link ../
504    }
505    for {set srcindex $index} {$srcindex < $srclen} {incr srcindex} {
506      append link [lindex $srclist $srcindex]/
507    }
508    set from $link$srcfile
509    cd $dstdir
510  } else {
511    set from $src
512  }
513  # Log [_ "%s : file -s %s" \
514  # 	   "$src $dst $srcdir $srcfile $dstdir $dstfile" "$dst $from"]
515  # for MSW the tcl file command seems to put garbage in the link...
516  # We create junctions as we know how to delete them...
517  # Tcl bug c8d4f01a54d7ecf819f4881fb02467ad14fc7b0c
518  set cmd "file link -symbolic"
519  if {$::MSW && [set nc [auto_execok mklink]] !={} } {
520    set cmd "exec $nc /j "
521  }
522  frECF $cmd [list $dst $from]
523  # if {[Try {{*}$cmd  $dst $from} "" 0] == 1 } {
524  #   Try {file link  $dst $from} "" 1
525  # }
526}
527
528proc CmdSoftLink {} {
529  global glob
530  if { $glob(left,pwd) == $glob(right,pwd) } {
531    SandDsame
532    return {}
533  }
534  # Well, with VFS we actually can, but we need them both to be
535  # on the same remote
536  if {([IsVFS $glob(left,pwd)] && [set lVFStok $VFStok] != {}) ||\
537	  [IsVFS $glob(right,pwd)]} {
538    if {$lVFStok == $VFStok} {
539    # missing code here...
540    # It is OK, it is handled below..
541    } else {
542      frputs "softlink " l r rVFStok lVFStok  glob(left,pwd) glob(right,pwd)
543      CantDoThat
544      return
545    }
546  }
547
548  CmdSoftLink_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd)
549  CmdSoftLink_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd)
550  UpdateWindow both
551}
552
553proc CmdSoftLink_ { listb_name filelist_var frompwd topwd } {
554  # For speed, (hopefully) we'll call by reference...
555  global config glob
556  upvar $filelist_var filelist
557
558  foreach sel [$listb_name curselection] {
559    if {[CheckAbort [_ "SoftLink"]]} {return}
560    set elem [lindex $filelist $sel]
561    set file [lindex $elem 1]
562    set realName [DNtoDirTail [URL norm $frompwd/$file]]
563    switch [lindex $elem 2] {
564      n   -
565      d   -
566      l   -
567      wl  -
568      wld -
569      ld  {
570        Log [_ "Softlinking %s to %s" "$realName" $topwd]
571        SoftLink $realName $topwd/$file
572      }
573      fn -
574      fd -
575      fl -
576      fld {
577	Log [_ "Softlinking %s to %s" "$realName" $topwd]
578	IsVFS $topwd/$file
579	set opt [expr {$config(create_relative_links) ? "r" : "a"}]
580	Try {VFSlink $realName $VFSpath $opt} "" 1
581	set glob(forceupdate) 1
582      }
583      default {
584	CantDoThat
585	return
586      }
587    }
588  }
589}
590
591proc CmdSoftLinkAs {} {
592  global glob
593  CmdSoftLinkAs_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd)
594  CmdSoftLinkAs_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd)
595  UpdateWindow both
596}
597
598proc CmdSoftLinkAs_ { listb_name filelist_var frompwd topwd } {
599  # For speed, (hopefully) we'll call by reference...
600  global config glob
601  upvar $filelist_var filelist
602
603  foreach sel [$listb_name curselection] {
604    if {[CheckAbort [_ "SoftLinkAs"]]} {return}
605    set elem [lindex $filelist $sel]
606    set file [lindex $elem 1]
607    set realName [DNtoDirTail [URL norm $frompwd/$file]]
608    set destfile [simple_smart_dialog "." [_ "Soft-Link As..."] \
609		      [_ "Please enter new name for destination link"] \
610		      $topwd/$file ]
611    if {([IsVFS $frompwd] && [set fVFStok $VFStok] !={}) || [IsVFS $destfile]} {
612      # Well, with SVFS we actually can, but we need them both to be
613      # on the same remote & SVFS (but I said that)
614      if {$fVFStok == $VFStok } {
615      } else {
616	CantDoThat
617	return
618      }
619    }
620    if {$destfile != "" } {
621      Log [_ "Softlinking %s to %s" "$realName" $destfile]
622      switch [lindex $elem 2] {
623	n   -
624	d   -
625	l   -
626	wl  -
627	wld -
628	ld  {
629          SoftLink $realName $destfile
630        }
631	fn -
632	fd -
633	fl -
634	fld {
635	  Log [_ "Softlinking %s to %s" "$realName" $destfile]
636	  # This sets up VFSadd to be VFSadd
637	  IsVFS $destfile
638	  set opt [expr {$config(create_relative_links) ? "r" : "a"}]
639	  Try {VFSlink $realName \
640		   "$VFSpath" \
641		   "$opt"} "" 1
642	  set glob(forceupdate) 1
643	}
644	default {CantDoThat}
645      }
646    }
647  }
648}
649
650# This handles all deletes. For locals we use the configured delete
651# We do the async option with override on the third parm
652
653proc deleteFile {args} {
654  global config glob
655  # if we are passed a list, the args processing will have listed it again
656  # so, if the length is 1, that sublist is what we want
657  if {[llength $args] == 1} {
658    set args [lindex $args 0]
659  }
660  foreach {newfile tType} $args {
661    frputs  tType newfile args
662    # tType should be f for files d for dirs
663    switch -exact -- $tType[IsVFS $newfile] {
664      f0 -
665      d0 {
666	if {[catch {expr {[file exists $newfile] || \
667			      [file type $newfile] == "link"}} r] == 0 && $r} {
668	  if {$config(cmd,rm) != {}} {
669	    # assume a recycle thing
670	    Log [_ "Using \"%s\" to delete %s" $config(cmd,rm) $newfile ]
671	    Try { {*}$config(cmd,rm) $newfile} "-sPossibly open file:"
672	  } else {
673	    Log [_ "Deleting %s" $newfile]
674	    Try {file delete -force -- $newfile} "-sPossibly open file:"
675	  }
676	}
677      }
678      f1 {
679	Try { VFSdelete $newfile} "-sPossibly open file:"
680      }
681      d1 {
682	Try { DeleteVFSRecursive $newfile} "-sPossibly open file:"
683      }
684      00  -
685      01  {return}
686      default {
687	return -code error "deleteFile called with $tType not in {0 d f}"
688      }
689    }
690  }
691}
692
693# Here we implement a cache of filelists indexed by dir
694#
695proc getFileListFor {dir} {
696  upvar #0 glob(left,pwd)  lpwd
697  upvar #0 glob(right,pwd) rpwd
698  global glob
699  global fileListCache fileListExpir
700
701  if {$dir == $lpwd} {
702    return $glob(left,filelist)
703  } elseif {$dir == $rpwd} {
704    return $glob(right,filelist)
705  } elseif {[info exists fileListCache($dir)] && $fileListCache($dir,time) > [clock seconds] + 10} {
706    return $fileListCache($dir)
707  } else {
708    catch {after cancel $fileListExpir}
709    set fileListCache($dir,time) [clock seconds]
710    set fileListExpir [after 10000 {array unset ::fileListCache}]
711    set r [catch {adHockGetDirList $dir} list]
712    if {$r == 0} {
713      return [set fileListCache($dir) $list]
714    }
715    return {}
716  }
717}
718#
719# This routine attempts to determine if the given file exists
720# and if so, its type. Returns are:
721# "d" exists and is directory
722# "f" exists and is file
723# "0" does not exist
724
725proc fileType {file} {
726  global glob
727  upvar #0 glob(left,pwd)  lpwd
728  upvar #0 glob(right,pwd) rpwd
729
730  if {![IsVFS $file]} {
731    set dir [file dir $file]
732    if {[file isdir  $file]} {return "d"}
733    if {[file isfile $file]} {return "f"}
734    return 0
735  }
736
737  set dir [URL dir $file]
738  set sObj [dirToDNexact $file]
739  set sObj [expr {$sObj == {} ? [file tail $file] : $sObj}]
740
741  set ent [lsearch -exact -inline -index 1 [getFileListFor $dir] $sObj]
742  if {$ent == {}} {return 0}
743  return [expr {[string index [lindex $ent 2] end] == "d" ? "d" : "f"}]
744}
745
746# Move and rename are, at heart, the same thing. Especially if we
747# bring moveas into the mix. Here are the three cases:
748#
749# 1.) move   just a simple move, target must differ from source
750#            (seed will be Move}
751# 2.) moveas the same as rename except for the seed in the prompt
752# 3.) rename just a different seed in the prompt
753#
754
755proc CmdMove {{seed {Move}}} {
756  global config glob
757  upvar #0 glob(selected)             inst
758  upvar #0 glob(listbox,$inst)        listb_name
759  upvar #0 glob($inst,filelist)       filelist
760  upvar #0 glob($inst,pwd)            frompwd
761  upvar #0 glob([Opposite $inst],pwd) topwd
762
763  set selList [$listb_name.file curselection]
764  # This keeps track of global yes/no from the ok to delete query
765  # Move does not delete dirs, but it may delete files...
766  # 2 global yes
767  # 3 global no
768  # Other vaues -> unknown
769  set globalYNfile 0
770  set globalYNdir 0
771
772  # A note on async. We need to grab all the selected file names
773  # before we return. For multi-step operations, we need to
774  # serialize the steps...
775  set moveFiles {}
776  foreach sel  $selList {
777    if {[CheckAbort [_ "Move"]]} {return}
778    set elem [lindex $filelist $sel]
779    lassign $elem duh file type size
780    set realName   [DNtoDirTail [URL norm $frompwd/$file]]
781    set toRealName [DNtoDirTail [URL norm $topwd/$file]]
782
783    set tof $topwd/$file
784    if {$seed != "Move"} {
785      # a rename or move to a new name (moveas)
786
787      set newname [simple_smart_dialog "." \
788		       [_ "%s" $seed] \
789		       [_ "Please enter new name."] \
790		       [expr {$seed == "Rename" ? $realName : $toRealName}]]
791      if {$newname == ""} {continue}
792    } else {
793      set newname $toRealName
794    }
795    # The rules, what is and what is not ok.
796    # Rename or Move to the same name is NOT ok.
797    #     this may happen even if its a simple move.
798    # Then there is the MSW alias issue (i.e. same except for case).
799    # We want to allow this to effectively change the case of a name.
800    if {$newname == $realName} {
801      SandDsame
802      return
803    }
804    set tType [fileType $newname]
805    frputs tType newname
806    if {$tType == "f" &&\
807	    (!$::MSW || ![string equal -nocase $newname $realName])} {
808      # We have an implied delete
809      # set up the permission code...
810      set dT {}
811      set ty "file"
812      set ford [expr {$config(ask,file_delete) ? [_ "file"] : {}}]
813      # frputs tType type
814      if {![typeMatch $tType $newname $type $realName]} {
815	return
816      }
817    }
818    # We don't check ownership on VFS files. (We don't usually know
819    # who the owner is in this case.) Of course CheckWhoOwns doesn't
820    # know how to approach VFS (yet??)
821
822    if {([IsVFS $realName] || [CheckWhoOwns $realName $seed]) && \
823	    ($tType == "d" || \
824		 [IsVFS $newname] ||\
825		 [CheckWhoOwns $newname overwrite])} {
826
827      # This keeps track of global yes/no from the ok to delete query
828      # 2 global yes
829      # 3 global no
830      # Other vaues -> unknown
831      # deleteQuery returns 0 -> Yes, 1 -> No, 2-> abort,
832      #                     3 ->Yes to all, 4 ->No to all
833      # Only ask about existent files
834      # Since we are doing this one at a time and we don't know if there
835      # is more than one existant file, the length of selList is a conserative
836      # proxy for the number of files (i.e. always more than or =)
837      if {$tType == "f"} {
838	switch -exact [set globalYN$ty] {
839	  4 {continue}
840	  0 -
841	  1 {set globalYN$ty [deleteQuery $ford $newname [llength $selList] [_ $seed]]}
842	}
843	switch -exact [set globalYN$ty] {
844	  1 -
845	  4 {continue}
846	  2 {return 0}
847	}
848      }
849
850    } else {
851      continue
852    }
853
854
855    # Ok, things are looking serious. We have a few issues left
856    # 1) If we are doing a moveas and it is a directory, well
857    #    there is no command to do that. For the local case
858    #    rename does the trick.
859    #    For all others we need to do a copyas followed by a
860    #    delete.
861
862
863    # Here is where we start doing things.... first the locals
864    lappend UpD $realName $realName/x $newname $newname/x
865    if {![IsVFS $newname] && ![IsVFS $realName]} {
866
867      # If we have an implied delete, make it explicit
868      # and use the configured rm command
869      if {$tType == "f"} {
870	deleteFile $newname $tType
871      }
872      Log [_ "Moving %s to %s" $realName $newname]
873      # Note in passing, "file rename" moves the file if
874      # required (man page says a different directory, hope
875      # it means a different volume)
876      Try { file rename -force -- $realName $newname }
877      continue
878    }
879    # Its remote (on one or both sides)
880    # collect what we need so we can do the whole thing as one big async
881    #
882    lappend moveFiles [list $realName $newname $type $tType $size]
883  }
884  # At this time we have all the info we need in the moveFiles list
885  # lets do this either async or not as requested.
886  if {$moveFiles != {}} {
887    Try {moveDoIt $moveFiles} $glob(async)
888  }
889    UpdateIf {*}$UpD
890}
891
892proc moveDoIt {moveFiles } {
893  foreach ent $moveFiles {
894    frputs ent moveFiles
895    if {[CheckAbort [_ "Move"]]} {break}
896    lassign $ent realName newname type tType size
897    lappend UpL $realName $realName/x $newname $newname/x
898
899    if {[set rf [IsVFS $realName]]} {
900      set VFStokf $VFStok
901    }
902    if {[IsVFS $newname] &&\
903	    $rf &&\
904	    $VFStokf == $VFStok && \
905	    [VFSsupports $newname "rename"]} {
906      #  both VFS... and the same and rename supported...
907      frputs realName VFSpath newname
908      Try { VFSrename $realName $VFSpath\
909		[expr {[string index $type end] == "d"}]} "" 1
910      continue
911    }
912    # Ok rename is out, we need to do a move and a delete.
913    # one or both VFS but if both, not the same (or rename not supported)
914    # six cases:
915    # VFS to VFS file        VFStoVFSf
916    # VFS to VFS directory   VFStoVFSd
917    # local to VFS file      loctoVFSf
918    # local to VFS directory loctoVFSd
919    # VFS to local file      VFStolocf
920    # VFS to local directory VFStolocd
921    #
922    # Also, if dest exists we need to deleteit
923
924    if {$tType == "f"} {
925      deleteFile $newname $tType
926    }
927    set resume 0
928    set sType [expr {[string index $type end] == "d" ? "d" : "f"}]
929    set whichCase [expr {$rf ? "VFS" : "loc"}]to[expr {[IsVFS $newname] ? \
930							   "VFS" : "loc"}]$sType
931    set tmpLoc {}
932    Log [_ "Move %s to %s" $realName $newname]
933
934    switch -exact $whichCase {
935
936      VFStoVFSf {
937	# VFS to VFS, copy to local then to dest.
938	# This VFS to local file copy
939	if {[set tmpLoc [MoveToTmp $realName $type $size]] == 0} {
940	  incr er
941	} elseif {[Try {VFSputFile $newname $tmpLoc $size } "" 1] != 0} {
942	  incr er
943	}
944      }
945      VFStoVFSd {
946	set tmpLoc [makeTmp]/[file tail $realName]
947	if {[Try {CopyFromVFSRecursive "$realName" "$tmpLoc" $resume }] != 0} {
948	  Try {file delete -force -- $tmpLoc} "" 1
949	} elseif {[Try {CopyToVFSRecursive $tmpLoc $newname}] != 0} {
950	  incr er
951	}
952      }
953      loctoVFSf {
954	if {[Try {VFSputFile $newname $realName $size }] != 0} {
955	  incr er
956	}
957      }
958      loctoVFSd {
959	if {[Try {CopyToVFSRecursive $realName $newname}] != 0} {
960	  incr er
961	}
962      }
963      VFStolocf {
964	# This VFS to local file copy
965	if {$type == "fl"} {set size -1}
966	if {[Try {frECF [list VFSgetFile] \
967		      [list $realName\
968			   $newname \
969			   $size \
970			   $resume]\
971		    } "" 1] != 0} {
972	  incr er
973	}
974      }
975      VFStolocd	{
976	# This is VFS to local dir copy
977	if {[Try {CopyFromVFSRecursive "$realName" "$newname" $resume}] != 0} {
978	  incr er
979	}
980      }
981      default {return -code error "Impossible file move $whichCase"}
982    }
983    # only do the delete if no er
984    if {![info exists er]} {
985      Log [_ "Deleting %s" $realName]
986      if {[IsVFS $realName]} {
987	if {$sType == "d"} {
988	  Try {DeleteVFSRecursive $realName}
989	} else {
990	  Try {VFSdelete $realName} "" 1
991	}
992      } else {
993	Try {file delete -force -- $realName}
994      }
995    }
996    if {$tmpLoc != {}} {
997      Try {file delete -force -- $tmpLoc}
998    }
999  }
1000  UpdateIf {*}$UpL
1001}
1002
1003
1004proc CmdMoveAs {} {
1005  CmdMove 1
1006}
1007
1008
1009
1010proc CmdDelete {} {
1011  global glob
1012  CmdDelete_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd) left
1013  CmdDelete_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd) right
1014  UpdateWindow both
1015  set glob(forceupdate) 0
1016}
1017# deleteQuery asks if it is ok to delete something. It puts up a window
1018# with the message: "OK to delete 'ford' 'elem'" and button answers:
1019# 'Yes' 0, 'No' 1, Abort 'op' 2, Yes to All 3, No to All 4.
1020# The last two buttons will be displayed only if 'num' is greater than 1
1021# 'ford' was set up to read directory or file. X deletion will return 2.
1022
1023proc deleteQuery {ford elem num op} {
1024  if {$ford == ""} {return 0}
1025  set ret [smart_dialog .apop[incr ::uni] . [_ "Sure?"] \
1026	      [list [_ "OK to delete %s ?" \
1027			 "$ford $elem"]]\
1028	      0 [expr {$num > 1 ? 5 : 3}] \
1029	      [list [_ "Yes"] [_ "No"] [_ "Abort %s" $op] \
1030		   [_ "Yes to All"] [_ "No to All"]]]
1031  if {$ret == -1} {return 2}
1032  return $ret
1033}
1034
1035proc CmdDelete_ { listb_name filelist_var frompwd topwd inst } {
1036  # For speed, (hopefully) we'll call by reference...
1037  global config glob
1038  upvar $filelist_var filelist
1039
1040  set selList [$listb_name curselection]
1041  # This keeps track of global yes/no from the ok to delete query
1042  # 3 global yes
1043  # 4 global no
1044  # Other vaues -> unknown (0 yes, 1 no)
1045  # we ask seperatly for files and dirs.
1046  set dglbYN 0
1047  set glbYN 0
1048  # We want to allow async, but need to pull all the info from the
1049  # dirlist before we do, thus we build a work list and then do
1050  # the work with Try, possibly asynchronously
1051  set delList {}
1052  foreach sel $selList {
1053    if {[CheckAbort [_ "Delete"]]} return
1054    set elem [lindex $filelist $sel]
1055    set type [lindex $elem 2]
1056    set ford [switch $type {
1057      d -
1058      fd {if {$config(ask,dir_delete)} {expr {{directory tree}}}}
1059      default {if {$config(ask,file_delete)} {expr {{file}}}}
1060    }]
1061    set dT [expr {$ford != "file" ? {d} :{}}]
1062
1063    set ff [DNtoDirTail [URL norm $frompwd/[lindex $elem 1]]]
1064    switch -exact [set ${dT}glbYN] {
1065      4  {continue}
1066      0 -
1067      1 {set ${dT}glbYN [deleteQuery $ford $ff [llength $selList] [_ "Delete"]]}
1068    }
1069    switch -exact [set ${dT}glbYN] {
1070      1 -
1071      4 {continue}
1072      2 {return}
1073    }
1074    set tType [expr {[string index $type end] == "d" ? "d" : "f"}]
1075    switch $type {
1076      l   -
1077      wl  -
1078      ld  -
1079      wld -
1080       n   -
1081      d {
1082	if {[CheckWhoOwns $ff delete]} {
1083	  lappend delList $ff $tType
1084	}
1085      }
1086      fn  -
1087      fld -
1088      fd  -
1089      fl  {
1090	Log [_ "Deleting %s" $ff]
1091	lappend delList  $ff $tType
1092      }
1093      default CantDoThat
1094    }
1095  }
1096  if {$delList == {}} {return}
1097  Try {
1098    deleteFile $delList {;}
1099    UpdateIf $ff
1100  } "-sPossibly open file:" $::glob(async)
1101}
1102# This command is nearly the same as View  It uses the windows
1103# file 'assoc' and 'ftype' functions to figure out how
1104# to 'open' a file.  (We use 'open' as windows means it.)
1105
1106
1107proc CmdOpen {} {
1108#  puts "CmdWinOpen"
1109  global glob
1110  CmdView_ $glob(listbox,left).file glob(left,filelist) \
1111      $glob(left,pwd) $glob(right,pwd) left open
1112  CmdView_ $glob(listbox,right).file glob(right,filelist) \
1113      $glob(right,pwd) $glob(left,pwd) right open
1114}
1115
1116# This command allows the help file to be compressed, .gz now, .zip someday.
1117
1118proc ViewHelp {seed} {
1119  frputs "ViewHelp  " seed
1120  if {[file exists $seed]} {
1121    ViewText $seed
1122  } elseif {[file exists $seed.gz]} {
1123    ViewText $seed.gz
1124  } else {
1125    PopError "Can't find $seed"
1126  }
1127}
1128
1129proc CmdView {} {
1130#  puts "CmdView"
1131  global glob
1132  CmdView_ $glob(listbox,left).file glob(left,filelist) \
1133      $glob(left,pwd) $glob(right,pwd) left
1134  CmdView_ $glob(listbox,right).file glob(right,filelist) \
1135      $glob(right,pwd) $glob(left,pwd) right
1136}
1137
1138proc CmdView_ { listb_name filelist_var frompwd topwd inst {extList view}} {
1139  # For speed, (hopefully) we'll call by reference...
1140  #  puts "CmdView_"
1141  global glob config env
1142  upvar $filelist_var filelist
1143  set filenamelist {}
1144  set filenameorg {}
1145  foreach sel [$listb_name curselection] {
1146    if {[CheckAbort [_ "View"]]} return
1147    set fileelem [lindex $filelist $sel]
1148    set file1 [DNtoDirTail [URL join $frompwd [lindex $fileelem 1]]]
1149    frputs file1 frompwd
1150    # for windows lnk files we pass both the translated version and the file
1151    set type [lindex $fileelem 2]
1152    switch  $type {
1153      l   -
1154      n   {
1155	lappend filenamelist $file1
1156	lappend filenameorg  $file1
1157      }
1158      wl  {
1159	lappend filenamelist [TranslateLnk [wLinkName $inst $fileelem] \
1160				      [lindex $glob($inst,df) 1]]
1161	lappend filenameorg $file1
1162      }
1163      wld {
1164	set newdir [TranslateLnk [wLinkName $inst $fileelem] \
1165			[lindex $glob($inst,df) 1]]
1166	if {$newdir != {}} {
1167	  NewPwd $inst $newdir
1168	  UpdateWindow $inst
1169	}
1170      }
1171      fd  -
1172      fld -
1173      ld  -
1174      d   {
1175	NewPwd $inst $file1
1176	UpdateWindow $inst
1177	return
1178      }
1179      fn  -
1180      fl  {
1181	set file [MoveToTmp $file1 $type [lindex $fileelem 3]]
1182	lappend filenamelist $file
1183	lappend filenameorg  $file1
1184      }
1185      default CantDoThat
1186    }
1187  }
1188  if {$filenamelist != {}} {
1189    ViewAny $filenamelist $extList $filenameorg
1190  }
1191}
1192
1193proc makeTmp {} {
1194  global glob
1195  set r 0
1196  if { ! [file exists $glob(tmpdir)] } {
1197    set r [Try { file mkdir $glob(tmpdir) } "" 1]
1198    if {$r} {
1199      return -code error "Failed to create $glob(tmpdir)."
1200    }
1201  }
1202  return $glob(tmpdir)
1203}
1204
1205proc MoveToTmp {file type size args} {
1206  global glob
1207
1208  set dest [makeTmp]/[file tail $file]
1209  if {$args != {}} {
1210    set inc {}
1211    while {[file exists $dest$inc]} {set inc [expr {$inc == {} ? "0" : [incr inc]}]}
1212    set dest $dest$inc
1213  } else {
1214    file delete -force $dest
1215  }
1216  #set size [lindex $fileelem 3]
1217  if {$type == "fl"} {set size -1}
1218  set rr [frECF [list VFSgetFile %b $size 0]\
1219	      [list $file $dest]]
1220  lassign $rr r val cm
1221
1222  if {$r == 0} {
1223    set glob(havedoneftp) 1
1224    #	                  puts "$glob(tmpdir)/[lindex $fileelem 1]"
1225    #return [regsub -all {\ }\
1226	#	    "$glob(tmpdir)/[lindex $fileelem 1]" {\\ }]
1227    return $dest
1228  }
1229  PopError "Command $cm \ncaused error: $val"
1230  return 0
1231}
1232
1233
1234
1235# proc CmdViewAsText {} {
1236#   global glob
1237#   CmdViewAsText_ $glob(listbox,left).file glob(left,filelist) \
1238#       $glob(left,pwd) $glob(right,pwd)
1239#   CmdViewAsText_ $glob(listbox,right).file glob(right,filelist) \
1240#       $glob(right,pwd) $glob(left,pwd)
1241# }listb_name filelist_var frompwd topwd
1242
1243proc CmdViewAsText { } {
1244  global glob
1245  set inst $glob(selected)
1246  set listb_name $glob(listbox,$inst).file
1247  set filelist $glob($inst,filelist)
1248  set frompwd $glob($inst,pwd)
1249  # For speed, (hopefully) we'll call by reference...
1250  # upvar $filelist_var filelist
1251  # set inst $glob(select_cur_lr)
1252  foreach sel [$listb_name curselection] {
1253    lassign [lindex $filelist $sel] dum file type size
1254    set ffile [DNtoDirTail [URL norm $frompwd/$file]]
1255    switch $type {
1256      wl  { ViewText [TranslateLnk [wLinkName $inst [lindex $filelist $sel]] \
1257		      [lindex $glob($inst,df) 1]]
1258      }
1259      l   -
1260      n   { ViewText $frompwd/$file }
1261      ld  -
1262      wld -
1263      d   { PopInfo [_ "Can't view directory %s in the text viewer" \
1264			 "$ffile"] }
1265      fn  -
1266      fl  { ViewText [MoveToTmp $ffile $type $size] $ffile}
1267      default CantDoThat
1268    }
1269  }
1270}
1271
1272proc CmdCheckSize {} {
1273  global glob
1274  CmdCheckSize_ $glob(listbox,left).file glob(left,filelist)\
1275      $glob(left,pwd) $glob(right,pwd) left
1276  CmdCheckSize_ $glob(listbox,right).file glob(right,filelist) \
1277      $glob(right,pwd) $glob(left,pwd) right
1278}
1279
1280proc CmdCheckSize_ { listb_name filelist_var frompwd topwd inst } {
1281  # For speed, (hopefully) we'll call by reference...
1282  global glob config env
1283  upvar $filelist_var filelist
1284  set filenamelist {}
1285  foreach sel [$listb_name curselection] {
1286    if {[CheckAbort [_ "View"]]} return
1287    set fileelem [lindex $filelist $sel]
1288    switch [lindex $fileelem 2] {
1289      d   -
1290      ld  -
1291      wld -
1292      wl  -
1293      l   -
1294      n   { lappend filenamelist [DNtoDirTail [URL norm $frompwd/[lindex $fileelem 1]]] }
1295      default CantDoThat
1296    }
1297  }
1298  if {$filenamelist != {}} {
1299    set pr [lindex $config(cmd,du) 0]
1300    frECF [list exec {*}[subst {*}$::stOps $config(cmd,du)]] $filenamelist \
1301	     [list -post \
1302		  [list postOptions [list ViewString [_ "Output from %s" $pr]] nop ]]
1303   }
1304}
1305
1306
1307proc CmdWhatIs {} {
1308  global glob
1309  CmdWhatIs_ $glob(listbox,left).file glob(left,filelist)\
1310      $glob(left,pwd) $glob(right,pwd)
1311  CmdWhatIs_ $glob(listbox,right).file glob(right,filelist)\
1312      $glob(right,pwd) $glob(left,pwd)
1313}
1314
1315proc CmdWhatIs_ { listb_name filelist_var frompwd topwd } {
1316  # For speed, (hopefully) we'll call by reference...
1317  upvar $filelist_var filelist
1318  set files {}
1319  foreach sel [$listb_name curselection] {
1320    lassign [lindex $filelist $sel] dum file type
1321    switch $type {
1322      l   -
1323      wl  -
1324      wld -
1325      n   -
1326      ld  -
1327      d   {lappend files [DNtoDirTail [URL norm $frompwd/$file]]
1328          }
1329      default CantDoThat
1330    }
1331  }
1332  if {$files != {}} {
1333    frECF "exec file %b"  $files\
1334	[list -post [list postOptions [list ViewString [_ "Output from file"]] nop ]]
1335  }
1336}
1337	# Try { PopInfo [exec file "$frompwd/$file"] } "" 1
1338
1339proc CmdEdit {} {
1340  global glob
1341  CmdEdit_ $glob(listbox,left).file glob(left,filelist) \
1342      $glob(left,pwd) $glob(right,pwd)
1343  CmdEdit_ $glob(listbox,right).file glob(right,filelist) \
1344      $glob(right,pwd) $glob(left,pwd)
1345}
1346
1347proc CmdEdit_ { listb_name filelist_var frompwd topwd } {
1348  global env config glob
1349  # For speed, (hopefully) we'll call by reference...
1350  upvar $filelist_var filelist
1351  set filenamelist {}
1352  set inst $glob(select_cur_lr)
1353
1354  foreach sel [$listb_name curselection] {
1355    set elem [lindex $filelist $sel]
1356    lassign $elem dum file swt
1357    # set swt [lindex $elem 2]
1358    switch -glob $swt {
1359      wl* {
1360	set ffile [TranslateLnk [wLinkName $inst $elem] \
1361		       [lindex $glob($inst,df) 1]]
1362      }
1363      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
1364    }
1365    switch  $swt {
1366      l   -
1367      wl  -
1368      n   { lappend filenamelist  $ffile }
1369      ld  -
1370      wld -
1371      d   { PopInfo [_ "Can't edit directory %s in the text editor" "$ffile"] }
1372      default CantDoThat
1373    }
1374  }
1375  if {$filenamelist != {}} {
1376    Log "exec $config(editor) $filenamelist"
1377
1378    set rr [frECF [list exec {*}[subst {*}$::stOps $config(editor)]] $filenamelist]
1379  }
1380}
1381
1382proc DoUsrButton {which} {
1383  global config glob
1384  upvar #0 glob(selected)             inst
1385  upvar #0 glob(listbox,$inst)        listb_name
1386  upvar #0 glob($inst,filelist)       filelist
1387  upvar #0 glob($inst,pwd)            pwd
1388  # get the config info
1389  set command {}
1390  set name $which
1391  foreach f {dirs nomultiple viewout prior numparms} {
1392    set $f 0
1393  }
1394  foreach {key value} $config(userButton,$which) {
1395    # we look for "n" first to grandfather the nomultiple "n" abbreviation
1396    switch -glob $key {
1397      n   -
1398      no* {set nomultiple $value}
1399      nu* {set numparms   $value}
1400      l* {set name       $value}
1401      c* {set command    $value}
1402      d* {set dirs       $value}
1403      v* {set viewout    $value}
1404      p* {set prior      $value}
1405    }
1406  }
1407  # These make sense?
1408  foreach f {dirs nomultiple viewout prior} {
1409    if {[string is boolean [set $f]]} {continue}
1410    PopWarn "Value for $f ($value) is not boolean."
1411    return
1412  }
1413  if {$nomultiple && ($prior || $numparms !=0)} {
1414    PopWarn "It is inconsistent to assert both \"nomultiple\" and \"prior\" or \"numparms\"."
1415  }
1416  if {$nomultiple} {
1417    set numparms "== 1"
1418  }
1419  if {$numparms == 0} {
1420    set numparms "*"
1421  }
1422  if {[string is integer $numparms]} {
1423    set numparms "==$numparms"
1424  }
1425  # get the current selected file list...
1426  set fileList {}
1427  # if doing prior, collect that first to maintain order
1428  set opwd $glob([Opposite $inst],pwd)
1429  if {$prior && [set pinst $glob(select_pry_lr)] != {}} {
1430    set opwd $glob($pinst,pwd)
1431    foreach sel $glob(select_pry_s) {
1432      set elem [lindex $glob($pinst,filelist) $sel]
1433      lassign $elem du file type
1434      if {[string match "wl*" $type]} {
1435	set file [TranslateLnk [wLinkName $pinst $elem] \
1436		       [lindex $glob($pinst,df) 1]]
1437      }
1438      lappend fileList [list [DNtoDirTail [URL norm $opwd/$file]] $type]
1439    }
1440  }
1441
1442  foreach sel [$listb_name.file curselection] {
1443    set elem [lindex $filelist $sel]
1444    lassign $elem duh file type
1445    if {[string match "wl*" $type]} {
1446      set file [TranslateLnk [wLinkName $inst $elem] \
1447		    [lindex $glob($inst,df) 1]]
1448    }
1449    lappend fileList [list [DNtoDirTail [URL norm $pwd/$file]] $type]
1450  }
1451  # Ok we now hav a list of all the files.
1452  # At the moment we will not allow VFS files and
1453  # while checking that, verify that any  dirs are allowed.
1454  set newFlist {}
1455  # test for proper number of parameters * | < n | > n | == n|
1456  if {$numparms == "*" || [eval expr [llength $fileList] $numparms]} {
1457  } else {
1458    PopWarn "[llength $fileList] parms does not pass $numparms test"
1459    return
1460  }
1461  foreach ent $fileList {
1462    lassign $ent file type
1463    if {!$dirs && [string index $type end] == "d"} {
1464      PopWarn "The file \"$file\" is a directory which is not allowed."
1465      return
1466    }
1467    if {[IsVFS $file]} {
1468      PpWarn "The file \"$file\" is a VFS file which is not allowed."
1469      return
1470    }
1471    lappend newFlist $file
1472  }
1473  # Ok, lets do it...
1474  if {$viewout} {
1475    set post [list -post \
1476	      [list postOptions \
1477		   [list ViewString "$name output: " ]\
1478		   [list PopInfo "No output" ]]]
1479  } else {
1480    set post {}
1481  }
1482  frECF [list exec {*}[subst {*}$::stOps $command]] $newFlist {*}$post \
1483      [list -error {}]
1484
1485}
1486
1487
1488proc CmdQEdit {} {
1489  global glob
1490  CmdQEdit_ $glob(listbox,left).file glob(left,filelist) \
1491      $glob(left,pwd) $glob(right,pwd)
1492  CmdQEdit_ $glob(listbox,right).file glob(right,filelist) \
1493      $glob(right,pwd) $glob(left,pwd)
1494}
1495
1496proc CmdQEdit_ { listb_name filelist_var frompwd topwd } {
1497  # For speed, (hopefully) we'll call by reference...
1498  global glob
1499  upvar $filelist_var filelist
1500  set inst $glob(select_cur_lr)
1501
1502  foreach sel [$listb_name curselection] {
1503    if {[CheckAbort [_ "Q-Edit"]]} return
1504    set elem [lindex $filelist $sel]
1505    lassign $elem dum file swt
1506    # set swt [lindex $elem 2]
1507    switch -glob $swt {
1508      wl* {
1509	set ffile [TranslateLnk [wLinkName $inst $elem] \
1510		       [lindex $glob($inst,df) 1]]
1511      }
1512      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
1513    }
1514    switch $swt {
1515      wl  -
1516      l   -
1517      n   {
1518            set r [Try {EditText "$ffile" ""} \
1519		       [_ "Error editing %s" $ffile] 1]
1520            if {$r != 0} { catch { destroy .toplevel_$glob(toplevelidx) } }
1521          }
1522      ld  -
1523      d   { PopInfo [_ "Can't edit directory %s in the text editor" "$ffile"] }
1524      default CantDoThat
1525    }
1526  }
1527}
1528
1529
1530proc CmdRename {} {
1531  CmdMove Rename
1532  return
1533}
1534
1535proc CmdUnArc {} {
1536  CmdUnArcPack unarc
1537}
1538proc CmdUnPack {} {
1539  CmdUnArcPack unpack
1540}
1541
1542proc CmdUnArcPack {which} {
1543  global glob
1544  CmdUnArcPack_ $glob(listbox,left).file glob(left,filelist) \
1545      $glob(left,pwd) $glob(right,pwd) $which
1546  CmdUnArcPack_ $glob(listbox,right).file glob(right,filelist)\
1547      $glob(right,pwd) $glob(left,pwd) $which
1548  UpdateWindow both
1549}
1550
1551proc CmdUnArcPack_ { listb_name filelist_var frompwd topwd which} {
1552  upvar $filelist_var filelist
1553  global glob
1554  set inst $glob(select_cur_lr)
1555  foreach sel [$listb_name curselection] {
1556    if {[CheckAbort $which]} return
1557    set elem [lindex $filelist $sel]
1558    lassign $elem dum file swt
1559    # set swt [lindex $elem 2]
1560    switch -glob $swt {
1561      wl* {
1562	set ffile [TranslateLnk [wLinkName $inst $elem] \
1563		       [lindex $glob($inst,df) 1]]
1564      }
1565      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
1566    }
1567    switch $swt {
1568      ld  -
1569      wld -
1570      d   { PopError [_ "Can't %s directory %s..." $which $file] }
1571      l   -
1572      wl  -
1573      n   { Log [_ "Which %s to %s" $ffile $topwd]
1574            UnArcPackAny $ffile $topwd $which
1575          }
1576      default CantDoThat
1577    }
1578  }
1579}
1580
1581
1582proc CmdArc {} {
1583  global glob
1584  CmdArc_ $glob(listbox,left).file glob(left,filelist) \
1585      $glob(left,pwd) $glob(right,pwd)
1586  CmdArc_ $glob(listbox,right).file glob(right,filelist) \
1587      $glob(right,pwd) $glob(left,pwd)
1588  UpdateWindow both
1589}
1590
1591proc CmdArc_ { listb_name filelist_var frompwd topwd } {
1592  global config glob
1593  upvar $filelist_var filelist
1594  set inst $glob(select_cur_lr)
1595
1596  foreach sel [$listb_name curselection] {
1597    if {[CheckAbort [_ "Archive"]]} return
1598    set elem [lindex $filelist $sel]
1599    lassign $elem dum file swt
1600    # set swt [lindex $elem 2]
1601    switch -glob $swt {
1602      wl* {
1603	set ffile [TranslateLnk [wLinkName $inst $elem] \
1604		       [lindex $glob($inst,df) 1]]
1605
1606      }
1607      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
1608    }
1609    set tail [file tail $ffile]
1610    set dir [file dirname $ffile]
1611    switch $swt {
1612      wl  -
1613      l   -
1614      n   {
1615        Log [_ "Packing %s" $ffile]
1616#        Try { eval exec [format $config(cmd,pack) \
1617	    #	    {$ffile}] } "" 1  $glob(async)
1618	set rr [frECF [list exec {*}[subst {*}$::stOps $config(cmd,pack)]] \
1619			 [list $ffile] \
1620			 [list -b $glob(async)]]
1621	lassign $rr r out
1622	if {$r != 0} {
1623	  PopError $out
1624	}
1625      }
1626      wld -
1627      ld  -
1628      d   {
1629        Log [_ "Archiving %s" $ffile]
1630	cd $dir
1631        if {$config(cmd,archive) == "tar+gz {%s}"} {
1632	  frECF [list exec tar cf - %a | gzip > %a] \
1633	      [list $tail $tail.tar.gz] \
1634	      [list -back $glob(async)]
1635
1636          # Try { cd $dir; exec tar cf - $tail | \
1637	  # 	    gzip > $tail.tar.gz } "" 1 $glob(async)
1638        } else {
1639	  frECF [list exec {*}[subst {*}$::stOps $config(cmd,archive)]]\
1640		   [list $ffile]\
1641		   [list -back $glob(async)]
1642	}
1643      }
1644      default CantDoThat
1645    }
1646  }
1647}
1648
1649proc CmdPrint {} {
1650  global glob
1651  CmdPrint_ $glob(listbox,left).file glob(left,filelist) \
1652      $glob(left,pwd) $glob(right,pwd)
1653  CmdPrint_ $glob(listbox,right).file glob(right,filelist) \
1654      $glob(right,pwd) $glob(left,pwd)
1655}
1656
1657proc CmdPrint_ { listb_name filelist_var frompwd topwd } {
1658  global config glob
1659  upvar $filelist_var filelist
1660  set inst $glob(select_cur_lr)
1661
1662  foreach sel [$listb_name curselection] {
1663    if {[CheckAbort [_ "Print"]]} return
1664    set elem [lindex $filelist $sel]
1665    lassign $elem dum file swt
1666    # set swt [lindex $elem 2]
1667    switch -glob $swt {
1668      wl* {
1669	set ffile [TranslateLnk [wLinkName $inst $elem] \
1670		       [lindex $glob($inst,df) 1]]
1671      }
1672      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
1673    }
1674    switch $swt {
1675      wl  -
1676      l   -
1677      n   {
1678	Log [_ "Printing %s" $ffile]
1679        frECF [list exec {*}[subst {*}$::stOps $config(cmd,print)]]\
1680	    [list $ffile]\
1681	    [list -back $glob(async)]
1682      }
1683      ld  -
1684      wld -
1685      d   { PopError [_ "Can't print directories!!"] }
1686      default CantDoThat
1687    }
1688  }
1689}
1690
1691proc CmdMakeSameDir {} {
1692  global glob
1693  if { $glob(left,pwd) == $glob(right,pwd) } {
1694    SandDsame
1695    return {}
1696  }
1697  CmdMakeSameDir_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd)
1698  CmdMakeSameDir_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd)
1699  UpdateWindow both
1700}
1701
1702proc CmdMakeDir {} {
1703  global config glob
1704  set inst {}
1705   if {[$glob(listbox,left).file curselection] != {}} {
1706    set inst left
1707  } elseif {[$glob(listbox,right).file curselection] != {}} {
1708    set inst right
1709  }
1710  if {[info exists glob(whichdir)]} {
1711    if {$inst == {} ||\
1712	    [$glob(win,$glob(whichdir)).entry_dir get] != \
1713	    [dirToDN $glob($glob(whichdir),pwd)]} {
1714      set inst $glob(whichdir)
1715    }
1716  }
1717  if {$inst == {}} {
1718    PopWarn [_ "\"MkDir\" pushed with nothing selected and display directory \
1719                unchanged.\n Please do one of these so filerunner\
1720                knows where the directory is desired."]
1721    return
1722  }
1723
1724  if {[set newdir [$glob(win,$inst).entry_dir get]] == \
1725	  [dirToDN $glob($inst,pwd)]} {
1726    set newdir [simple_smart_dialog "." [_ "Directory name?"] \
1727		    [_ "Please enter the name of the new directory.\
1728                        Another way of creating directories is to enter\
1729                        the name the new directory in one\
1730                        of the directory entries and then pressing the\
1731                        MkDir button"] $newdir]
1732    if {$newdir == ""} return
1733  }
1734  set newdir [DNtoDir $newdir]
1735  Log [_ "Creating directory %s" $newdir]
1736  if {[IsVFS $newdir]} {
1737    Try { VFSmkdir $newdir } "" 1
1738    set glob(forceupdate) 1
1739  } else {
1740    Try { file mkdir $newdir } "" 1
1741  }
1742  UpdateWindow both
1743  set glob(forceupdate) 0
1744}
1745
1746proc CmdSelect {} {
1747  global glob
1748  if { [info exists glob(whichdir)] } {
1749    set inst $glob(whichdir)
1750    set pat [DNtoDir [$glob(win,$inst).entry_dir get]]
1751  } else {
1752    PopInfo [_ "Please enter a selection pattern in one of the\
1753                directory entries and then press the Select button"]
1754    return
1755  }
1756  setDisplayDir $inst
1757  # $glob(win,$inst).entry_dir delete 0 end
1758  # $glob(win,$inst).entry_dir insert end $glob(${inst},pwd)
1759  set pat [file tail $pat]
1760  set i 0
1761  foreach elem $glob($inst,filelist) {
1762    if {[string match $pat [lindex $elem 1]]} {
1763      $glob(listbox,$inst).file selection set $i
1764    }
1765    incr i
1766  }
1767  propagateSelection $glob(listbox,$inst).file
1768  UpdateStat
1769}
1770
1771proc CmdCSelect {} {
1772  global glob
1773  set cmd [simple_smart_dialog "." [_ "Contents-select"] \
1774	       [_ "Make sure you have selected the files you want to\
1775                   search in, then please edit this command to do a\
1776                   contents-select."] "grep -i "]
1777  if { $cmd == "" } return
1778  CmdCSelect_ $glob(listbox,left).file glob(left,filelist) \
1779      $glob(left,pwd) $glob(right,pwd) $cmd
1780  CmdCSelect_ $glob(listbox,right).file glob(right,filelist) \
1781      $glob(right,pwd) $glob(left,pwd) $cmd
1782  UpdateStat
1783}
1784
1785proc CmdCSelect_ { listb_name filelist_var frompwd topwd cmd } {
1786  upvar $filelist_var filelist
1787
1788  foreach sel [$listb_name curselection] {
1789    set elem [lindex $filelist $sel]
1790    switch [lindex $elem 2] {
1791      l   -
1792      wl  -
1793      n   {
1794	set r [catch {
1795	  eval exec $cmd {
1796	    [DNtoDirTail [URL norm $frompwd/[lindex $elem 1]]]
1797	  }
1798	} outp]
1799	if { $r != 0 } {
1800	  $listb_name selection clear $sel
1801	}
1802      }
1803      default CantDoThat
1804    }
1805  }
1806}
1807
1808# This searches the run list for 'what'
1809# and returns either -1 (not found) or a list
1810# of indexes to the found entry
1811proc runCmdListSearch {clist  what} {
1812  set level -1
1813  foreach en $clist {
1814    incr level
1815    if {[llength $en] != 2} {
1816      if {[lindex $en 0 0] == $what} {
1817	return $level
1818      }
1819    } else {
1820      set rt [runCmdListSearch [lindex $en 1] $what]
1821      if {$rt != -1} {
1822	return [concat $level 1 $rt]
1823      }
1824    }
1825  }
1826  return -1
1827}
1828
1829proc CmdRunCmd {args} {
1830  frputs "Run command called to run $args "
1831  global glob config
1832  set glob(runGlob,run) {}
1833  set thisone {}
1834  # Old call sequence had 0/1 followed by name...
1835  foreach thisone $args {
1836    if {[string is integer $thisone]} {
1837      set thisone {}
1838      continue
1839    }
1840    break
1841  }
1842  if {$thisone != {} } {
1843    # This search is complicated by the tree structure of the runlist.
1844    set indx [runCmdListSearch $config(runlist) $thisone]
1845    if {$indx == -1} {
1846      PopError [_ "Run command: could not find \"%s\" in the runlist."\
1847		    $thisone]
1848      return
1849    }
1850    set glob(runGlob,run) [lindex $config(runlist) {*}$indx]
1851    after idle [list DoProtCmd CmdRunCmd_]
1852    return
1853  }
1854  # none of that, put up a menu and bail...
1855
1856  # This is an convential menu window on which we force a tearoff.
1857  # This allows us to have checkboxes that don't unpost the menu.
1858  # Given that FixTearoff fixes most of the problems with
1859  # tearoff windows, all we are left with is positioning it
1860  # somewhere near the mouse pointer.
1861
1862  # Read Config will eliminate the menu so that we can rebuild
1863  # it with a possibly new runlist. Otherwise, we keep the same
1864  # menu for the duration.
1865
1866  # Unlike other tearoffs we only allow one on the screen at a time.
1867
1868  set w [expr {[info exists glob(runMenu)] ? $glob(runMenu) : {}}]
1869  if {![winfo exists $w]} {
1870    # We need to build a new menu
1871    set w .menu_[incr glob(toplevelidx)]
1872    set glob(runMenu) $w
1873
1874    destroy $w
1875
1876    menu $w\
1877	-tearoff 1 \
1878	-title "Run menu"\
1879	-borderwidth 1 \
1880	-relief solid \
1881	-tearoffcommand "FixTearoff GrabTearoff"
1882
1883    $w add checkbutton\
1884	-label [_ "<Edit entry>"] \
1885	-variable glob(runEdit)
1886
1887    $w add checkbutton \
1888	-label [_ "<Delete menu on select>"] \
1889	-variable glob(runDeleteMenu)
1890
1891    $w add separator
1892
1893     buildRunListMenu
1894  }
1895  # this covers the case where it was but s/he destroyed it
1896  set glob(runDeleteMenu) 1
1897  set glob(runEdit) 0
1898  if {![info exists glob(runMenuTearoff)] || \
1899	  ![winfo exists $glob(runMenuTearoff)]} {
1900    $w invoke 0
1901  }
1902
1903  if {[llength $config(runlist)] == 0} {
1904    set glob(runEdit) 0
1905    wm with $glob(runMenuTearoff)
1906    cmdrunEdit $glob(runGlob,run)
1907    return
1908  }
1909  # Now we have a menu which is in a tearoff frame
1910  # Lets put it near the mouse...
1911
1912  set hm [winfo reqheight $w]
1913  lassign [winfo pointerxy .] msx msy
1914
1915  # If the mouse is in the lower half of the screen, put the bottom of the
1916  # window where the mouse is, otherwise, put the top there
1917
1918  set yloc [expr {$msy < [winfo screenheight .] / 2 ? $msy : $msy - $hm}]
1919
1920  update
1921  # we should have a new tearoff window
1922  # Lets get its true name...
1923  if {[info exists glob(runMenuTearoff)]} {
1924    set wto $glob(runMenuTearoff)
1925  } else {
1926    error "Can not find tearoff window from $w"
1927  }
1928  # wm with $w
1929  wm geo $wto +${msx}+$yloc
1930  wm deicon $wto
1931
1932  frputs "Run menu building  " w wm hm ww wh "[wm geo $w] [wm geo $w]  "
1933  return
1934}
1935
1936# This code snipit adds the runlist entries to the menu after first
1937# deleting what is there...
1938
1939proc buildRunListMenu {} {
1940  global config glob
1941  set w $glob(runMenu)
1942  $w delete 4 end
1943
1944  buildCasMenu {} \
1945      [runListMenuBuild $config(runlist)] \
1946      $w \
1947      runListButton\
1948      [list -tearoffcommand FixTearoff ]
1949
1950  $w add command \
1951      -label [_ "<Add a new program>"] \
1952      -command  "runListButton 1"
1953}
1954
1955
1956
1957# We pass this proc to FixTearoff which will add the two parms, the
1958# origional window and the name of the new tearoff.  We need the
1959# ladder to fix a few things...
1960
1961proc GrabTearoff {menu tearoff} {
1962  set ::glob(runMenuTearoff) $tearoff
1963}
1964
1965# The following is driven by glob(runGlob,run) as to what and how
1966# to run the program
1967
1968proc CmdRunCmd_ {} {
1969  global glob config
1970  # default the pwd to left (changes if selection made in right)
1971  set frompwd $glob(left,pwd)
1972  set fl {}
1973  foreach inst {left right} {
1974    foreach sel [$glob(listbox,$inst).file curselection] {
1975      set elem [lindex $glob($inst,filelist) $sel]
1976      switch [lindex $elem 2] {
1977	wl  {
1978	  lappend fl [TranslateLnk [wLinkName $inst $elem] \
1979			  [lindex $glob($inst,df) 1]]
1980	}
1981	d   -
1982	ld  -
1983	l   -
1984	n   { lappend fl [lindex $elem 1] }
1985	default CantDoThat
1986      }
1987      set frompwd $glob($inst,pwd)
1988    }
1989  }
1990  set options [lassign $glob(runGlob,run) namecmd sep]
1991  lassign $namecmd name cmd
1992
1993  if {"last+current" in $options && $glob(select_pry_lr) != {} } {
1994    set secdir $glob($glob(select_pry_lr),pwd)
1995    if { $secdir == $frompwd } {
1996      set secdir ""
1997    }
1998    foreach sel $glob(select_pry_s) {
1999      lappend fl \
2000	  [DNtoDirTail\
2001	       [URL norm ${secdir}/[lindex $glob($glob(select_pry_lr),filelist) $sel 1]]
2002    }
2003  }
2004  # set cmd [FixFormatString $cmd]
2005  # puts "run $cmd sep = $sep"
2006  if {"fullname" in $options} {
2007    set fll $fl
2008    set fl {}
2009    foreach file $fll {
2010      # if we already have a full name, join will just pass the file
2011      lappend fl [file join $frompwd $file]
2012    }
2013  }
2014  # we want to use frECF here.
2015  # This fixes the run string to accept more than one file at the same loc
2016  # and, with the change to -default, takes care the the file seperator issue
2017  # We replace the first spec (one of:%s|%n|%m|%q|%r) with that %b
2018
2019  # First count the specs, keep location of the last one.
2020  set start 0
2021  set count 0
2022  set here [string length $cmd]
2023  while {[set loc [string first "%" $cmd $start]] != -1} {
2024    if {[string index $cmd $loc-1] == {\\}} {
2025      set start [incr loc]
2026      continue
2027    }
2028    set start [incr loc 2]
2029    if {[string index $cmd $loc-1] in {s n q r} } {
2030      incr count
2031      set here $loc
2032    }
2033  }
2034
2035  set ncmd [string range $cmd 0 $here-1]
2036  set spec %[expr {$count > 0 ? [string index $cmd $here-1] : "s"}]
2037  if {[set remains [expr [llength $fl] - $count]] > 0} {
2038    if {$count == 0} {
2039      append ncmd " $spec"
2040      incr remains -1
2041    }
2042    append ncmd [string repeat "${sep}$spec" $remains]
2043    append ncmd [string range $cmd $here end]
2044    set cmd $ncmd
2045  }
2046  set async [expr {"async" in $options}]
2047  if {!$async} {
2048    set op [list -post \
2049		[list postOptions \
2050		     [list ViewString [_ "Output from %s %s"\
2051					   $name \
2052					   [string index $cmd end]]]\
2053		     nop]]
2054  } else {
2055    set op "-nop"
2056  }
2057  LogSilent [_ "Running command: %s" $cmd]
2058  cd $frompwd
2059  frECF [list exec {*}[subst {*}$::stOps $cmd] {*}[expr {$async ? "&" : {}}]]\
2060      $fl \
2061      [list -default "${sep}$spec" -back $async]\
2062      $op
2063
2064  UpdateWindow both
2065}
2066
2067
2068proc runListMenuBuild {list} {
2069  set runMenu {}
2070  while {[llength $list] > 0} {
2071    set list [lassign $list this]
2072    set more [lassign $this name ent]
2073    if {[string index $name 0] != "-"} {
2074      # simple entry
2075      lappend runMenu [list [lindex $name 0]]
2076    } else {
2077      # a sub list
2078      lappend runMenu [list $name [runListMenuBuild $ent]]
2079    }
2080  }
2081  frputs "Raw run menu  " runMenu
2082  return $runMenu
2083}
2084
2085# [list {label} {runstring} {{ }} fullname async]
2086proc cmdrunEdit {what} {
2087  global glob config
2088  set opts [lassign $what namecmd glob(runGlob,file)]
2089  lassign $namecmd glob(runGlob,display) glob(runGlob,run)
2090  set oldDisplayName $glob(runGlob,display)
2091
2092  set glob(runGlob,full) [expr {"fullname" in $opts}]
2093  set glob(async) [expr {"async" in $opts}]
2094  set glob(runGlob,duo) [expr {"last+current" in $opts}]
2095  # default to add to run list
2096  set glob(runGlob,add) [expr {! $glob(runEdit) ? 1 : 0}]
2097  set glob(runNow) 0
2098
2099  set sdr [smart_dialog .run[incr ::uni] \
2100	       . \
2101	       [_ "Run Dialog"] \
2102	       [list [_ "Edit the 'command  & ops' line.\
2103                         \n      Ctrl-A takes you to the start of the line.\
2104                         \n      Tab does command complete.\
2105                         \n      Mouse button 3 brings up a browser.\
2106                         \n      This command is run through the FileRunner\
2107                                  Exec Call Formater (FRECF)\
2108                         \n      so all format commands\
2109                                  that FRECF supports are available.\
2110                         \n      See Help menu->Tips: 'Configure options for commands II'\
2111                         \n      A '%s' in this string will be appended with\
2112                         \n      sufficient copys for the given file list\
2113                         \n      with the requested file seperator between them\
2114                         \n\n'file separator' specifies\
2115			 what string is used to separate files (should there\
2116                         \n      be more than one). Default is space ({ }).\
2117                         \n'display name' if supplied will be the name displayed\
2118                         in the run list\
2119                         \n'full file names' will pass the full name rather than\
2120                         the working directory relative name.\
2121                         \n'Last + current' will pass both the last selection\
2122                          and the current selection.\
2123                         \nAn edit operation, unless canceled, will save the\
2124                         result.\
2125                         \n\"Add to run list\" will, in this case, also add the\
2126                         same entry to the end of the list.\
2127                         \n" "%s, %n, %q or %r" ]] \
2128	       0 \
2129	       [expr {$glob(runEdit) ? 11 : 10}] \
2130	       [list [list [_ "command & opts"] \
2131			  "-textvariable glob(runGlob,run) -width 80"] \
2132		    [list [_ "file separator"] \
2133			 "-textvariable glob(runGlob,file)"]\
2134		    [list [_ "display name"] \
2135			 "-textvariable glob(runGlob,display)"] \
2136		    [list [_ "Async"] "-variable glob(async)"]\
2137		    [list [_ "full file\nnames"] \
2138			 "-variable glob(runGlob,full)"]\
2139		    [list [_ "Last +\ncurrent"] \
2140			 "-variable glob(runGlob,duo)"]\
2141		    [list [_ "Add to\nrun list"] \
2142			 "-variable glob(runGlob,add)"]\
2143		    [list [_ "Run \nnow?"] \
2144			 "-variable glob(runNow)"]\
2145		    [list [_ "OK"]] \
2146		    [list [_ "Cancel"]]\
2147		    [list [_ "Delete from\nrun list"]]\
2148		    ]\
2149	       [list -height 16 scroll 15 \
2150		    bind [list .run.0 <Tab> \
2151			      "Complete .run.0\
2152                               .run.text ; break"]\
2153		    bind [list .run.0 <3> \
2154			      "CompleteWithBrowse .run.0"]\
2155		   ]]
2156  if {$sdr == 9 || $sdr == -1 || $glob(runGlob,run) == "" } {
2157    return {}
2158  }
2159
2160  if {$sdr == 10} {
2161    # This is the delete option...
2162    set config(runlist) [ldelete $config(runlist) $glob(runIndex)]
2163    buildRunListMenu
2164    return
2165  }
2166
2167  if {$glob(runGlob,file) == {}} {
2168    set glob(runGlob,file) " "
2169  }
2170  if {$glob(runGlob,display) == {}} {
2171    set glob(runGlob,display) [lindex $glob(runGlob,run) 0]
2172  }
2173  set new {}
2174  if {$glob(runGlob,full)} {lappend new "fullname"}
2175  if {$glob(async)}        {lappend new "async"}
2176  if {$glob(runGlob,duo)}  {lappend new "last+current"}
2177  set new [list [list $glob(runGlob,display) $glob(runGlob,run)] \
2178	       $glob(runGlob,file) {*}$new]
2179  if {$glob(runEdit)} {
2180    lset config(runlist) {*}$glob(runIndex) $new
2181  }
2182  if {$glob(runGlob,add)} {
2183    # $glob(runMenu) insert [$glob(runMenu) index end] command \
2184    # 	-label $glob(runGlob,display)\
2185    # 	-command [list runListButton 2 {} [llength $config(runlist)]]
2186    lappend config(runlist) $new
2187  }
2188  if {$glob(runGlob,add) || $glob(runEdit) >= 0} {
2189    buildRunListMenu
2190    SaveConfig
2191  }
2192  set glob(runGlob,run) $new
2193  if {$glob(runNow)} {
2194    DoProtCmd CmdRunCmd_
2195  }
2196  return
2197}
2198
2199# The run menu sends us here. The parms are:
2200#
2201# val this will be 1 if s/he wants to add to the run list
2202#                  2 if loc is the index into the runlist
2203#                    of the program to run
2204# indx is the display name (we don't use this here)
2205# val is a list of indexies as per 2 above
2206
2207# In aaddition we use glob(runEdit) and glob(runDeleteMenu)
2208
2209proc runListButton {val {indx {0}} {loc {}}} {
2210  global glob config
2211  if {$glob(runDeleteMenu) && [winfo exists $glob(runMenuTearoff)]} {
2212    wm with $glob(runMenuTearoff)
2213  }
2214  if {$val < 1} {
2215    return
2216  }
2217  if {$val == 1} {
2218    set glob(runEdit) 0
2219    if {[cmdrunEdit {}] == {}} {
2220      return
2221    }
2222  }
2223  set glob(runGlob,run) [lindex $config(runlist) {*}$loc]
2224  if {$glob(runEdit) } {
2225    set glob(runIndex) $loc
2226    cmdrunEdit $glob(runGlob,run)
2227    return
2228  }
2229  DoProtCmd CmdRunCmd_
2230  return
2231}
2232
2233proc CmdForEach {} {
2234  global glob
2235  CmdForEach_ $glob(listbox,left).file glob(left,filelist) \
2236      $glob(left,pwd) $glob(right,pwd)
2237  CmdForEach_ $glob(listbox,right).file glob(right,filelist) \
2238      $glob(right,pwd) $glob(left,pwd)
2239}
2240
2241proc CmdForEach_ { listb_name filelist_var frompwd topwd } {
2242  global glob config
2243  upvar $filelist_var filelist
2244  set inst $glob(select_cur_lr)
2245
2246  set fl {}
2247  foreach sel [$listb_name curselection] {
2248    set elem [lindex $filelist $sel]
2249    switch [lindex $elem 2] {
2250      wl {
2251	lappend fl [TranslateLnk [wLinkName $inst $elem] \
2252		       [lindex $glob($inst,df) 1]]
2253      }
2254      d   -
2255      ld  -
2256      l   -
2257      n   { lappend fl [lindex $elem 1] }
2258      default CantDoThat
2259    }
2260  }
2261  if { $fl == "" } return
2262  if {$glob(async)} {
2263    PopError [_ "This command does not support asynchronous execution"]
2264    return
2265  }
2266  if {![info exists glob(foreach,cmd)] || $glob(foreach,cmd) == {}} {
2267    set glob(foreach,cmd) {echo '%s'}
2268  }
2269  set glob(foreach,cmd) \
2270      [simple_smart_dialog "." [_ "Foreach"] \
2271	   [_ "Enter command to run on each of the selected files.\
2272               The file will show up in the '%s'. A second '%s'\
2273               (if coded) will be replaced by the opposite panel's\
2274               working directory. You can use '%s' for the first\
2275               '%s' to reorder these two. You can use pipes\
2276               and other bourne-shell syntax elements since the\
2277               commands will each run in a separate bourne shell\
2278               in the working directory of the selected file." %s %s %2@s %s]\
2279	   $glob(foreach,cmd) ]
2280  if { $glob(foreach,cmd) == "" } return
2281
2282  set output {}
2283  foreach k $fl {
2284    if {[CheckAbort [_ "ForEach"]]} return
2285    set pcount [expr {[stringCount {%} $glob(foreach,cmd)] - \
2286			  (2 * [stringCount {%%} $glob(foreach,cmd)])}]
2287    set k [list $k]
2288    if {$pcount > 1} {
2289      lappend k $topwd
2290    }
2291    Log [_ "Running %s ..." [list exec [subst {*}$::stOps $glob(foreach,cmd)] $k]]
2292
2293    set rr [frECF [list exec {*}[subst {*}$::stOps $glob(foreach,cmd)]] $k]
2294
2295
2296    append output [lindex $rr 1]
2297    append output "\n"
2298  }
2299  ViewString [_ "Output from commands"] output
2300  UpdateWindow both
2301}
2302
2303proc CmdRecurseCommand { inst } {
2304  global glob config
2305
2306  set dir $glob($inst,pwd)
2307
2308  if { [IsVFS $dir] } {
2309    CantDoThat
2310    return
2311  }
2312  CmdFind $inst ok
2313  return
2314}
2315
2316proc CmdDiff {} {
2317  global glob
2318  CmdDiff_ $glob(listbox,left).file glob(left,filelist) \
2319      $glob(left,pwd) $glob(right,pwd) left
2320
2321  CmdDiff_ $glob(listbox,right).file glob(right,filelist) \
2322      $glob(right,pwd) $glob(left,pwd) right
2323}
2324
2325proc CmdDiff_ { listb_name filelist_var frompwd topwd inst } {
2326    global glob
2327    upvar $filelist_var filelist
2328    global config
2329
2330    set null 1
2331    set file1 ""
2332    set file2 ""
2333    foreach sel [$listb_name curselection] {
2334      set null 0
2335      set elem [lindex $filelist $sel]
2336      lassign $elem du ffile swt size
2337      switch -glob $swt {
2338	wl* {
2339	  set ffile [TranslateLnk [wLinkName $inst $elem] \
2340		       [lindex $glob($inst,df) 1]]
2341	}
2342	default {set ffile [DNtoDirTail [URL norm $frompwd/$ffile]]}
2343      }
2344      switch $swt {
2345	ld  -
2346	fld -
2347	d   -
2348	fd  -
2349	l   -
2350	fl  -
2351	fn  -
2352	n   {
2353	  if {$file1 == ""} {
2354	    set file1 $ffile
2355	    if {[IsVFS $file1]} {
2356	      set file1 [MoveToTmp $file1 $swt $size]
2357	    }
2358	  } else {
2359	    if { $file2 != "" } {
2360	      PopError [_ "Please select one or two\
2361                                    files or directories for diffing."]
2362	      return
2363	    }
2364	    set file2 $ffile
2365	    if {[IsVFS $file2]} {
2366	      set file2 [MoveToTmp $file2 $swt $size 1]
2367	    }
2368	    # But which was first?
2369	    if {$inst == $glob(select_pry_lr) &&
2370		$sel == $glob(select_pry_s)} { } else {
2371		  set tmp $file1
2372		  set file1 $file2
2373		  set file2 $tmp
2374		    }
2375		}
2376	    }
2377	default {
2378	  CantDoThat
2379	  return
2380	}
2381      }
2382    }
2383
2384  if {$null} return
2385
2386  if {$file2 == "" && $glob(select_pry_lr) != {}} {
2387    set sel $glob(select_pry_s)
2388    if {[llength $sel] == 1} {
2389      set elem2 [lindex $glob($glob(select_pry_lr),filelist) $sel]
2390      lassign $elem2 du file2t type2 size2
2391      set file2  [DNtoDirTail [URL norm $glob($glob(select_pry_lr),pwd)/$file2t]]
2392      if {[IsVFS $glob($glob(select_pry_lr),pwd)]} {
2393	set file2 [MoveToTmp $file2 $type2 $size2 1]
2394	# CantDoThat
2395	# return
2396      }
2397      switch -glob [lindex $elem 2] {
2398	wl* {
2399	  set file2 [TranslateLnk [wLinkName $glob(select_pry_lr) $elem] \
2400			 [lindex $glob($inst,df) 1]]
2401	}
2402      }
2403    } else {
2404	  PopError [_ "Please select one or two\
2405                         files or directories for diffing."]
2406	  return
2407	}
2408    }
2409  if {! ($file2 == "")} {
2410    set tmp $file1
2411    set file1 $file2
2412    set file2 $tmp
2413  }
2414  frECF [list exec {*}[subst {*}$::stOps $config(cmd,diff)]] [list $file1 $file2] \
2415	[list -post \
2416	     [list postOptions \
2417		  [list ViewString [_ "Diffing %s and %s %s" \
2418					$file1 $file2\
2419					[string index $config(cmd,diff) end]]]\
2420		  [list PopInfo \
2421		       [_ "No difference between\n\n%s\n\nand\n\n%s" $file1 $file2]]]]\
2422	[list -error {} -background $glob(async)]
2423
2424
2425}
2426
2427proc CmdCreateEmptyFile { inst } {
2428  global glob config
2429  set start_entry $glob($inst,pwd)
2430  set newfile [simple_smart_dialog "." [_ "Create New File"] \
2431		   [_ "Please enter the name of the new file."] \
2432		   $start_entry ]
2433
2434  if {$newfile != ""} {
2435    Log [_ "Creating new file %s" $newfile]
2436    if {[IsVFS $newfile]} {
2437      # This is a bit complicated in that we really do want to
2438      # do a "touch" and not just blindly create a new file.
2439      # Its simple if it does not exist. If it does, well we
2440      # don't want to bring it here and send it back just to
2441      # change its modify time...
2442      # Try a touch command...
2443      set r [catch {VFScommand $VFStok [list touch $VFSpath]} out]
2444      if {$r != 0} {
2445	PopError "VFS command touch failed: $out"
2446      }
2447    } else {
2448      if {$config(cmd,touch) == {}} {
2449	if {![DoWeHavePackage fileutil touch]} {
2450	  PopWarn "config(cmd,touch) is 'nil' and tcl 'fileutil' not found.
2451                  \nWe quit!"
2452	  return
2453	}
2454	Try {::fileutil::touch "$newfile"} "" 1
2455      } else {
2456	Try { exec $config(cmd,touch) "$newfile" } "" 1
2457      }
2458    }
2459    ForceUpdate
2460  }
2461}
2462
2463proc DeleteVFSRecursive {dir} {
2464  set me "DeleteVFSRecursive"
2465  global glob config
2466  set r [IsVFS $dir]
2467  if {[VFSrmdirEmpty $dir]} {
2468    VFSrmdir $dir
2469    return
2470  }
2471
2472  Log [_ "$me: Entering %s" $dir]
2473  # NewPwd $inst $dir
2474  # UpdateWindow $inst
2475  foreach elem [getFileListFor $dir] {
2476    if {[CheckAbort [_ "Delete"]]} {return}
2477    set file [lindex $elem 1]
2478    switch [lindex $elem 2] {
2479      fn  -
2480      fld -
2481      fl  {
2482        Log [_ "$me Deleting %s" $dir/$file]
2483        VFSdelete [URL norm $dir/$file]
2484      }
2485	fd {
2486
2487	  if { $file != "." } {
2488	      if { $file != ".." } {
2489		DeleteVFSRecursive [DNtoDirTail [URL norm $dir/$file]]
2490	      }
2491	  }
2492      }
2493      default {
2494        error [_ "Unexpected file type in $me"]
2495      }
2496    }
2497  }
2498  Log [_ "$me Deleting %s" $dir]
2499  VFSrmdir $dir
2500}
2501
2502proc CopyFromVFSRecursive { fromdir todir resume } {
2503  set me "CopyFromVFSRecursive"
2504  global glob config
2505  if {[CheckAbort [_ $me]]} return
2506  IsVFS $fromdir
2507  frputs fromdir todir
2508  if {[VFSRcopyOk $fromdir] & 1} {
2509    if {[file tail $fromdir] == [file tail $todir] ||\
2510	     ![VFSsupports $fromdir nocopyas]} {
2511      VFSgetFile $fromdir $todir  0
2512      return
2513    }
2514  }
2515  set dir [file tail $fromdir]
2516  Log [_ "$me: Creating %s" $todir/$dir]
2517  file mkdir "$todir"
2518  Log [_ "$me: Entering %s" $fromdir]
2519  # NewPwd $inst $fromdir
2520  # UpdateWindow $inst
2521  foreach elem [getFileListFor $fromdir] {
2522    if {[CheckAbort [_ $me]]} return
2523    lassign $elem duh file type size
2524    set tof $todir/$file
2525    switch $type {
2526      fld -
2527      fl {
2528        Log [_ "$me Skipping %s - link" $fromdir/$file]
2529      }
2530      fd {
2531	if {$file ni {. ..}} {
2532	  CopyFromVFSRecursive "$fromdir/$file" \
2533	      "$tof" $resume
2534	}
2535      }
2536      fn {
2537        Log [_ "$me: Copying %s -> %s bytes)" \
2538             $fromdir/$file \
2539             "$todir/$dir/$file $size"]
2540        VFSgetFile "$fromdir/$file" \
2541	    "$todir/$file" "$size" $resume
2542      }
2543      default {
2544        error [_ "Unexpected file type in $me"]
2545      }
2546    }
2547  }
2548}
2549
2550proc CopyToVFSRecursive { fromdir todir} {
2551  set me "CopyToVFSRecursive"
2552  global glob config
2553  if {[CheckAbort [_ $me]]} return
2554  frputs fromdir todir
2555  IsVFS $todir
2556  if {[VFSRcopyOk $todir] & 2} {
2557    if {[file tail $fromdir] == [file tail $todir] ||\
2558	    ![VFSsupports $todir nocopyas]} {
2559      # begin exp
2560      if {0} {
2561	frputs fromdir todir
2562	if {[VFSisDir $todir] == {}} {
2563	  # we know it should be a dir, but it does not exist...
2564	  VFSmkdir $todir
2565	}
2566	VFSputFile [URL dir $todir] $fromdir 0
2567	return
2568      }
2569
2570      VFSputFile $todir $fromdir 0
2571      return
2572    }
2573  }
2574  Log [_ "$me: Creating %s" $todir]
2575  # VFSmkdir "[URL norm $todir/$dir]"
2576  if {[fileType $todir] != "d"} {
2577    VFSmkdir $todir
2578  }
2579  Log [_ "$me: Entering %s" $fromdir]
2580  # NewPwd $inst $fromdir
2581  # UpdateWindow $inst
2582  foreach elem [getFileListFor $fromdir] {
2583    if {[CheckAbort [_ $me]]} return
2584    lassign $elem duh file type size
2585    # set file [lindex $elem 1]
2586    set tof [URL norm $todir/$file]
2587    switch $type {
2588      ld -
2589      l {
2590        Log [_ "$me Skipping %s - link" $fromdir/$file]
2591      }
2592      d {
2593	if {$file ni {. ..}} {
2594	  CopyToVFSRecursive "$fromdir/$file"\
2595	      $tof
2596	}
2597      }
2598      n {
2599        Log [_ "$me: Copying %s -> %s %s bytes)"\
2600		 "$fromdir/$file" $tof $size]
2601        VFSputFile $tof "$fromdir/$file" $size
2602      }
2603      default {
2604        error [_ "Unexpected file type in $me"]
2605      }
2606    }
2607  }
2608}
2609
2610# I may be dense, but I have not found a simple way to get 'file copy'
2611# to recursivly copy directories if the destination directory exists.
2612# It seems to do fine if it does not.
2613# Here is an 'file copy' replacement that does the same thing as 'xcopy'
2614# and 'cp'.
2615
2616proc frFileCopy {from to} {
2617  # First the simple...
2618  set tail [file tail $from]
2619  if {$tail in {. ..}} {return}
2620  set nextTo $to/$tail
2621  if {![file exists $nextTo] || ![file isdir $from]} {
2622    file copy -force $from $to
2623    return
2624  }
2625  foreach file [glob -nocomplain -directory $from .* *.*] {
2626   frFileCopy $file $nextTo
2627  }
2628  return
2629}
2630
2631proc FindDialog {inst pat} {
2632  # we use a special version of viewstring where we provide the call
2633  # to the string search...
2634  #set result [join [lsort $result] "\n"]
2635  set result ""
2636  return [ViewString \
2637	      [_ "Find %s in %s Click on file to display in panel" \
2638		   $pat "[pwd]/*" ] \
2639	      result SearchConfig [list FindConfig $inst]]
2640}
2641
2642proc FindConfig {inst w title filename result} {
2643  global glob
2644  upvar 2 $result string
2645  centerWin $w
2646  textSearch $w.text $title  "+buildViewConfig" \
2647      {} \
2648      [list \
2649	   {Sort on tail} "findSort $w.text t" \
2650	   {Sort on full name} "findSort $w.text f" \
2651	   {Save As...} "SaveToFile $w.text [list $glob($inst,pwd)/] 1" \
2652	   [_ Quit]  "destroy $w"]
2653  bind $w.text <1> "GotoFind \"$glob($inst,pwd)\" \"\[findGetLine  $w.text %x %y]\" $inst;break"
2654  #puts "$string"
2655}
2656
2657proc findGetLine {w x y} {
2658  lassign [split [$w index @$x,$y] .] line
2659  return [$w get $line.0 $line.end]
2660}
2661proc findSort {w opt} {
2662  set files [split [$w get 0.0 end] \n]
2663  $w delete 0.0 end
2664  $w mark set insert 0.0
2665  set sort {}
2666  foreach file $files {
2667    if {$file != {} } {
2668      if {$opt == "t"} {
2669	lappend sort [file tail $file]$file
2670      } else {
2671	lappend sort $file
2672      }
2673    }
2674  }
2675  #puts "$sort"
2676  set sindex [lsort -indices $sort]
2677  foreach idx $sindex {
2678    $w insert insert "[lindex $files $idx]\n"
2679  }
2680  # Lets not ask about saving this file...
2681  $w edit reset
2682  $w edit modified 0
2683}
2684
2685proc GotoFind { dir file inst } {
2686  global glob config
2687  frputs "gfind [pwd]  " inst dir file
2688  if {[string index $file 0] == " "|| $file == {}} {return 0}
2689  set file [URL norm $dir/$file]
2690  NewPwd $inst [URL dirname $file]
2691  UpdateWindow $inst
2692  set name [file tail $file]
2693  if {$name == {}} {return 1}
2694  # use search here? need ordinal number
2695  if {[set j [lsearch -exact -index 1 $glob($inst,filelist) $name]] != -1} {
2696    $glob(listbox,$inst).file selection clear 0 end
2697    $glob(listbox,$inst).file selection set $j
2698    $glob(listbox,$inst).file see $j
2699    propagateSelection $glob(listbox,$inst).file
2700    return 1
2701  }
2702  # File not found. Could it be hidden?
2703  if {$config(fileshow,all) == 0} {
2704    if {!$::MSW} {
2705      set h [expr {[string index $name 0] == "."}]
2706    } else {
2707      set h [file attributes $file -hidden]
2708    }
2709    if {$h} {
2710      PopError [_ "File %s is a \"hidden\" file which are\
2711               \nnot displayed if \"Show All File\" configuration is false." $file]
2712      return 1
2713    }
2714  }
2715  PopError [_ "File %s can not be found." $file]
2716}
2717
2718# fileutilFindAsyncHandler is an async function to set up a call to
2719# ::fileutil::find. It will provide a compare function based on the
2720# cmds and will log results to 'logFun'
2721#
2722proc fileutilFindAsyncHandler {w cmds logFun} {
2723  # first remove the >2 stuff
2724  if {[lindex $cmds end-1] == ">2"} {set cmds [lreplace $cmds end-1 end]}
2725  set ::findStop$w 0
2726  set parms  [lindex $logFun 1 ]
2727  frputs "fileutilFindAsync  "
2728  set r [catch {::realfind::realFind [list [pwd]] \
2729    "$cmds" [linsert $logFun end "::findStop$w" "data"]} what]
2730  if {$r !=0} {
2731    #puts "rtn $what"
2732    findData $parms $w d data " ${what}\n"
2733    findData $parms $w d data " $::errorInfo"
2734  } else {
2735    #puts "rtn 0 $what"
2736    foreach line $what {
2737      findData $parms $w d data " $line"
2738    }
2739  }
2740  findData $parms $w d eof
2741  unset ::findStop$w
2742
2743}
2744
2745proc CmdFind { inst {ext -1}} {
2746  global glob config
2747  set vfs [IsVFS $glob($inst,pwd)]
2748  catch "destroy .apop"
2749  if {![info exists glob(searchstring)]} {
2750    set glob(find,string) ""
2751    set glob(find,options) {}
2752    set glob(find,expressions) {}
2753    set glob(find,regexp) 0
2754    set glob(find,casefold) 0
2755  }
2756  if {$ext != -1 && ![info exists glob(foreach,all)]} {
2757    set glob(foreach,all) 0
2758    set glob(foreach,cmd) {}
2759  }
2760  set smart_buts \
2761      [list \
2762	   [list [_ "name or regex pattern"] {-textvariable glob(find,string)}]\
2763	   [list [_ "extra options (-L,-P,-D,-O)"] {-textvariable glob(find,options)}]\
2764	   [list [_ "extra expressions and tests"] {-textvariable glob(find,expressions)}]\
2765	   [list [_ "regexp"] {-variable glob(find,regexp)}]\
2766	   [list [_ "casefold"] {-variable glob(find,casefold)}]\
2767	   [list {} [list -text [_ "\nEnter <command> to run on each file\
2768                     or all files as a group\
2769                     (option <all files>). The file(s) will show up in the '%s'\
2770                      (or at the end of the command if %s is not coded).\ 
2771                     \nThe commands are run directly by the tcl exec command.\
2772                     You can use pipes and other tcl exec constructs.\
2773                     You can also enter a shell as the first part of the\
2774                     command to pickup shell syntax elements."  \
2775				     "%s" "%s" ] \
2776			 -wraplength [expr {60 * [font measure $config(gui,ListBoxFont) {0}]}]\
2777			 -justify left]\
2778		]\
2779	   [list {} {-textvariable glob(foreach,cmd)}]\
2780	   [list [_ "all files"] {-variable glob(foreach,all)}]\
2781	   [_ OK] [_ Cancel]]
2782
2783  if {$vfs} {
2784    set smart_buts [lreplace $smart_buts 1 4]
2785  }
2786  if {$ext == -1} {
2787    set title [_ "Find..."]
2788    set startMes [list {} [_ "Please enter substring of filename\
2789                        to search for in %s and below." $glob($inst,pwd)]]
2790    set smart_buts [lreplace $smart_buts end-4 end-2]
2791  } else {
2792    set title [_ "Run Command Recursively"]
2793    set startMes [list [_ "This command will run:\
2794                 \n\n find %s <options>\
2795                 \n\n(with <options> selected below) to\
2796                 recurse into the current directory (%s).\
2797                 \nThe below entered <command> will then be run on all files\
2798                 from this find (either singly or as a group).\n\
2799                 \nSee also: manpage for the find command.\
2800                 \n\nUse tab to go to next entry.\
2801                 Return or the OK button starts execution."\
2802			    $glob($inst,pwd) $glob($inst,pwd)]]
2803  }
2804  set butCount [llength $smart_buts]
2805
2806  set r [smart_dialog .dbl_entry_dialog[incr ::uni] "."  [_ "Find..."]\
2807	     $startMes \
2808	     -1 $butCount\
2809	     $smart_buts [buildDialogConfig] \
2810	     [list bind [list .dbl_entry_dialog.1 <Return> \
2811			     "event generate .dbl_entry_dialog.0 <Tab>"]]]
2812
2813  if {$r == -1 || $r == ($butCount - 1)} {return}
2814
2815  #----------------------------------- vfs code -------------------------
2816  if {$vfs} {
2817    if {$glob(find,string) == {}} {return}
2818    set r [catch {VFScd $glob($inst,pwd)} out]
2819    if {$r != 0} {
2820      PopError $out
2821      return
2822    }
2823    LogStatusOnly [_ "Searching, please wait..."]
2824    set r [catch {VFSsearch $VFStok $glob(find,string)} out]
2825    LogStatusOnly [_ "Searching, please wait...done"]
2826    if {$r} {
2827      PopError [_ "VFS search error: %s" $out]
2828      return
2829    }
2830    ViewString [_ "VFS search results"] out
2831    return
2832  }
2833  #----------------------------------------end of vfs code ------------------
2834  set r [catch {cd $glob($inst,pwd)} out]
2835  if {$r} {
2836    PopError "$out"
2837    return
2838  }
2839  if {$ext != -1} {
2840    set ext [list $glob(foreach,all) $glob(foreach,cmd)]
2841  }
2842  if {$glob(find,expressions) == {} && $glob(find,string) == {}} {return}
2843  set pcall {}
2844  if {$glob(find,string) != {}} {
2845    # build the proper pattern call out name, iname regex iregex
2846    switch -exact $glob(find,regexp)$glob(find,casefold) {
2847      00 {set pcall "-name"}
2848      01 {set pcall "-iname"}
2849      10 {set pcall "-regex"}
2850      11 {set pcall "-iregex"}
2851    }
2852  }
2853  set pat {}
2854  if {$glob(find,string) != {}} {
2855    if {$glob(find,regexp)} {
2856      set pat  .*$glob(find,string).*
2857    } else {
2858      set pat  *$glob(find,string)*
2859    }
2860  }
2861  # Use the frECF to get this right...
2862  set cmds [slList {*}[subst {*}$::stOps $config(cmd,find)] \
2863		{*}$glob(find,options) [pwd]/ $pcall %n \
2864      		{*}$glob(find,expressions) -print 2> \
2865		[expr {$::MSW ? "nul" : "/dev/null"}]]
2866  set cmds [frECF $cmds [list  $pat] -f 1]
2867  frputs "parts of find  " cmds glob(find,string) pat
2868  set w [FindDialog $inst "$pat $glob(find,expressions)"]
2869  set logFun [list findlog [list findData [list [list $ext [list [pwd]]]] $w]]
2870  if {$config(cmd,find) == {}} {
2871    set cmds [slList {*}$glob(find,options) $pcall \
2872		  $pat {*}$glob(find,expressions)]
2873
2874    set geo [buildStop $w \
2875		 [_ "Searching, %s for %s. Please wait..." "[pwd]/*" "$pat" ] \
2876		 "set ::findStop$w" 1]
2877    Try {fileutilFindAsyncHandler $w "$cmds" \
2878	     {findData { $ext [list [pwd]]} $w}} -a
2879  } else {
2880    # We have a race here as 'buildStop' needs to do an update which
2881    # means that calling after we call pipeoExec allows the pipo command
2882    # to send us stuff before we are ready.  So,
2883    # Here we set a dummy call back to 'update' which should never be
2884    # called and then after the pipeo call we update the call back
2885    # function to what it ought to be (for which we need the fid that
2886    # pipeo returns.
2887    set geo [buildStop $w \
2888		 [_ "Searching, %s for %s. Please wait..." "[pwd]/*" "$pat" ] \
2889		 update 1]
2890    lassign [pipeoExec $cmds r $logFun] r fid
2891    if {$r != 0} {
2892      destroy $w
2893      error [_ "Bad find call: %s \n %s" $fid $cmds]
2894    }
2895    stopReSetCallBack $w "pipeoAbort $fid"
2896  }
2897  if {[winfo exists $w]} {wm geo $w $geo}
2898}
2899
2900proc findData {parms w fid tag {arg {}}} {
2901  global config glob
2902  lassign $parms ext pwd
2903  frputs "[incr ::xx] " parms ext pwd fid tag arg
2904  set loc [$w.text index end-1chars]
2905  lassign [split $loc .] line char
2906  if {$tag == "data" } {
2907    if {$ext == "com"} {
2908      $w.text insert insert ${arg}
2909    } else {
2910      if {[string index $arg end] != "\n" } {
2911	StopProgress $w $arg
2912      } else {
2913	if {[string compare -length [string length $pwd] $pwd ${arg}] == 0} {
2914	  $w.text insert insert  [string range ${arg} [string length $pwd]+1 end]
2915	} else {
2916	  $w.text insert insert ${arg}
2917	}
2918      }
2919      # This might be excessive, but, at least on my box, it works well.
2920      intelWinSize $config(geometry,textviewer) $w.text increase 1 location bound
2921
2922      $w.text see insert
2923    }
2924    return
2925  }
2926  # must be end of file. If no data, kill the window and use a popup...
2927  LogStatusOnly [_ "...done"]
2928  set loc [$w.text index end-1chars]
2929  if {[$w.text index insert] == 1.0} {
2930    destroy $w
2931    if {$ext == "com"} {
2932      PopInfo [_ "No output"]
2933    } else {
2934      PopInfo [_ "No files found"]
2935    }
2936    return
2937  }
2938  # At this point we have a list of found files
2939  # If ext is -1 we just remove the stop button and
2940  # sort the files
2941  # Otherwise, ext will be 1 if we are to send all files
2942  # to the command and 0 if we are to send them one at a time
2943  # In either case we use pipeo an give a running result
2944  # But first lets set up a more appropiate window size
2945
2946  # This location for the following two lines is much more relaxed, but on long searches
2947  # it scrunches up the window.
2948
2949  # lassign [intelWinSize  "75x5" $config(geometry,textviewer) [$w.text get 0.0 end]] width height
2950  # wm geo $w [getGeo g${width}x$height $w -win $w.text]
2951  frputs "Phase 2?  " ext
2952  if {$ext == -1} {
2953     findSort $w.text f
2954  } else {
2955    if {[lindex $ext 0] == "com"} {
2956      incr ::[set w]_flag
2957      return
2958    }
2959    lassign $ext all cmd
2960    # if {[string first {%s} $cmd] == -1} {
2961    #   append cmd " %s"
2962    # }
2963    # get the file, remove any error lines...
2964    set files {}
2965    foreach fl [split [$w.text get 0.0 end] \n] {
2966      if {$fl == {} || [string index $fl 0] == " "} {continue}
2967      lappend files $fl
2968    }
2969    frputs "phase 2  " files
2970    $w.text delete 0.0 end
2971    set Log [list findlog [list findData [list [list com [pwd]]] $w]]
2972    # foreach fl $files {
2973    # 	lappend fll [FixFileNameO [file native $fl] 3 { }]
2974    # }
2975    foreach fl $files {
2976      if {$all == 1} {
2977	set fl $files
2978	# set fl [join $fll " "]
2979      } else {
2980	set fl [list $fl]
2981      }
2982      # set realCmd  "[ReSpaceString {} [format $cmd [braceToQuote $fl]]]\n"
2983      set realCmd [frECF [list {*}[subst {*}$::stOps $cmd]] $fl -f 1]
2984      frputs realCmd
2985      # set realCmd [subst -noc -nov $realCmd]
2986      StopProgress $w $realCmd
2987      frputs realCmd  fl
2988      lassign [pipeoExec $realCmd r $Log] r fid
2989      if {$r != 0} {
2990	destroy $w
2991	error [_ "Bad find call: %s \n %s" $fid  $cmd]
2992      }
2993      stopReSetCallBack $w "pipeoAbort $fid"
2994      # wait for completion...
2995      vwait ::[set w]_flag
2996      if {$all == 1} {break}
2997    }
2998    catch "unset ::[set w]_flag"
2999  }
3000  StopButRemove $w
3001
3002}
3003
3004proc findlog {fid tag} {Log "Find log: $tag"}
3005
3006proc CmdChmod {} {
3007  global glob
3008  CmdChmod_ $glob(listbox,left).file glob(left,filelist) \
3009      $glob(left,pwd) $glob(right,pwd) left
3010  CmdChmod_ $glob(listbox,right).file glob(right,filelist) \
3011      $glob(right,pwd) $glob(left,pwd) right
3012}
3013
3014proc CmdChmod_ { listb_name filelist_var frompwd topwd inst } {
3015  # For speed, (hopefully) we'll call by reference...
3016  global config glob
3017  upvar $filelist_var filelist
3018  set fl {}
3019
3020
3021  foreach sel [$listb_name curselection] {
3022    if {[CheckAbort [_ "Chmod"]]} return
3023    set elem [lindex $filelist $sel]
3024    lassign $elem {*}$glob(fListEl)
3025    set file [DNtoDirTail [URL norm $frompwd/$file]]
3026    set localMode [file attributes $file -permissions]
3027    lassign [split $usergroup /] user group
3028     switch $type {
3029      l   -
3030      ld  -
3031      n   -
3032      d   {
3033	incr ftpFlag 0
3034	if {$::MSW} {
3035	  PopInfo [_ "So sorry, can't do chmod on windows files."]
3036	  return
3037	}
3038     }
3039      default {
3040	# Must be an nVFS, maybe we can do it... up to the handler and remote
3041	incr ftpFlag
3042      }
3043    }
3044    lappend fl $file
3045  }
3046  # Note to me, we present the flags to the last file in the list
3047  # but apply the result to all files...
3048  if {$fl != ""} {
3049    set rarg [ChmodDialog $file $localMode $user $group]
3050    lassign $rarg arg ownarg
3051    if {$arg != ""} {
3052      if { $ftpFlag } {
3053	# for ftp we will use a seperate call for each file...
3054	foreach file $fl {
3055	  set r [catch {VFSchmod $fl $arg} m ]
3056	  if { $r != 0 && $r != "" } {
3057	    PopError [_ "VFS chmod returns: %s" $m]
3058	    break
3059	  }
3060	}
3061      } else {
3062	# we need to do this to get the quotes right...
3063	frECF [list exec {*}[subst {*}$::stOps $config(cmd,chmod)] {*}$arg]\
3064	    $fl\
3065	    [list -back $glob(async)]
3066      }
3067      ForceUpdate $inst
3068    }
3069    if {$ownarg != ""} {
3070      if { $ftpFlag } {
3071	# for ftp we will use a seperate call for each file...
3072	foreach file $fl {
3073	  set r [catch {VFSchown $fl $ownarg} m]
3074	  if { $r != "0" && $r != "" } {
3075	    PopError [_ "VFS chown returns: %s" $m]
3076	    break
3077	  }
3078	}
3079      } else {
3080	frECF [list exec {*}[subst {*}$::stOps $config(cmd,chown)] {*}$ownarg]\
3081	    $fl\
3082	    [list -back $glob(async)]
3083
3084     }
3085      ForceUpdate $inst
3086    }
3087
3088  }
3089}
3090
3091proc CmdGetHttp { inst } {
3092  global glob config
3093
3094  if {[IsVFS $glob($inst,pwd)]} {
3095    PopInfo [_ "You can only download HTTP files to a non-VFS directory"]
3096    return
3097  }
3098
3099  if {![info exists glob(http,lasturl)]} {
3100    set glob(http,lasturl) {}
3101  }
3102  set URL $glob(http,lasturl)
3103
3104  while { 1 } {
3105    set URL [simple_smart_dialog "." [_ "Get HTTP File"] \
3106		 [_ "Please enter HTTP URL to download"] $URL ]
3107    if {$URL == ""} {
3108      return
3109    }
3110
3111    if {[string range $URL 0 6] != "http://" } {
3112      set URL "http://$URL"
3113    }
3114    set glob(http,lasturl) $URL
3115
3116    if {![IsVFS $URL] || $VFSpro ni {http https}} {
3117      PopError [_ "Could not parse %s as an HTTP(s) URL" $URL]
3118      continue
3119    }
3120    if {$VFSadd == {}} {
3121      append URL /
3122    }
3123    break
3124  }
3125
3126  set filename [file tail $URL]
3127  if {[string range $URL end end] == "/" && [file ext $filename] == {}} {
3128    append filename .html
3129  }
3130  set filename [simple_smart_dialog "." [_ "Get HTTP File"] \
3131		    [_ "Please edit filename to save to.\n(URL: %s)" $URL] \
3132		    $filename]
3133  if {$filename == ""} {
3134    return
3135  }
3136  cd $glob($inst,pwd)
3137  HTTP_Get $URL $filename
3138  UpdateWindow both
3139}
3140
3141