#
# $Id: filedialog.icn,v 1.4 2004/02/15 22:10:54 rparlett Exp $
#
# This file is in the public domain.
#
# Author: Robert Parlett (parlett@dial.pipex.com)
#

package gui
link graphics

$include "guih.icn"

$ifdef _MS_WINDOWS_NT
$define PATHCHAR "\\"
$else
$define PATHCHAR "/"
$endif

#
#
# File dialog class.  This class provides a standard file dialog.
# 
# @example
# @ d := FileDialog()
# @ d.show()
# @ write(d.get_result() | "cancelled")
#
class FileDialog : Dialog(
   init_dir,                # Initial directory name
   init_file,               # Initial file name
   res,                     # Resulting file path          
   dir,                     # TextField directory
   file,                    # TextField filename
   dlist,                   # TextList of directories            
   flist,                   # TextList of files            
   okay,                    #            
   cancel                   #              
   )

   #
   # Get the directory part of the result
   #
   method get_directory()
      return directory_name(\self.res)
   end

   #
   # Get the result, (will fail if cancel was pressed).
   #
   method get_result()
      return \self.res
   end

   #
   # Set the initial directory.
   #
   method set_directory(s)
      return self.init_dir := s
   end

   #
   # Set the initial file
   #
   method set_file(s)
      return self.init_file := s
   end

   #
   # Set the initial file/directory from a whole path.
   # 
   method set_path(s)
      self.init_dir := directory_name(s)
      self.init_file := file_name(s)
      return s
   end

   #
   # Set the result
   #
   # @p
   method set_result()
      self.res := self.get_std_dir() || file.get_contents()
   end

   #
   # Get the directory TextField contents standardized with a trailing /
   #
   # @p
   method get_std_dir()
      s := dir.get_contents()
      if (s[-1] ~== PATHCHAR) then
         s ||:= PATHCHAR

      return s
   end

   method on_okay()
      self.set_result()
      self.dispose()
   end

   method on_file()
      #
      # If return pressed in file TextField, same as okay
      #
      self.set_result()
      self.dispose()
   end

   method on_dlist()
      #
      # Clicked in the directory list; get the item clicked on.
      #
      value := dlist.get_contents()[dlist.get_selections()[1]]
      s := self.get_std_dir()
      #
      # Go to parent directory (unless at root directory)
      #
      if value == (".." || PATHCHAR) then {    
         if s ~== PATHCHAR then {
            s[-1] := ""
            while s[-1] ~== PATHCHAR do s[-1] := ""
         }
         dir.set_contents(s)
      } else
         dir.set_contents(s ||:= value)
      #
      # Update directory and file lists.
      #
      l := get_directory_list(s)
      dlist.set_contents(l[1])
      dlist.goto_pos(1, 0)
      dlist.clear_selections()
      flist.set_contents(l[2])
      flist.goto_pos(1, 0)
      flist.clear_selections()
      file.set_contents("")
   end

   method on_flist()
      #
      # Clicked in file list; set TextField
      #
      file.set_contents(flist.get_contents()[flist.get_selections()[1]])
   end

   method on_dir()
      #
      # Return pressed in directory TextField; update lists.
      #
      dir.set_contents(s := self.get_std_dir())
      l := get_directory_list(s)
      dlist.set_contents(l[1])
      dlist.goto_pos(1, 0)
      dlist.clear_selections()
      flist.set_contents(l[2])
      flist.goto_pos(1, 0)
      flist.clear_selections()
      file.set_contents("")
   end

   method init_dialog()
      self.set_focus(file)
   end
   
   method component_setup()
      local l

      #
      # Defaults if none set by caller.
      #
      if /init_dir | (init_dir == "") then {
         init_dir := chdir() | PATHCHAR
      }
      /init_file := ""

      if (init_dir[-1] ~== PATHCHAR) then init_dir ||:= PATHCHAR

      l := Label("pos=25,25", "align=l,c", "label=Directory")
      self.add(l)
      dir := TextField("pos=100,25", "size=100%-150", "align=l,c", "accel=d")
      dir.connect(self, "on_dir", ACTION_EVENT)
      dir.set_contents(init_dir)
      self.add(dir)
      l.set_linked_accel(dir)

      l := Label("pos=25,75", "align=l,c", "label=File")
      self.add(l)
      file := TextField("pos=100,75", "size=100%-150", "align=l,c", "accel=f")
      file.connect(self, "on_file", ACTION_EVENT)
      file.set_contents(init_file)
      self.add(file)
      l.set_linked_accel(file)

      l := get_directory_list(init_dir)

      dlist := TextList("pos=25,110", "size=50%-38,100%-170", "select_one", "selection_on_key_moves=f")
      dlist.connect(self, "on_dlist", SELECTION_CHANGED_EVENT)
      dlist.set_contents(l[1])
      self.add(dlist)

      flist := TextList("pos=50%+12,110", "size=50%-38,100%-170", "select_one", "selection_on_key_moves=f")
      flist.connect(self, "on_flist", SELECTION_CHANGED_EVENT)
      flist.set_contents(l[2])
      self.add(flist)

      okay := TextButton("pos=33%,100%-30", "align=c,c", "label=Okay", "accel=o")
      okay.connect(self, "on_okay", ACTION_EVENT)
      self.add(okay)

      cancel := TextButton("pos=66%,100%-30", "align=c,c", "label=Cancel", "accel=c")
      cancel.connect(self, "dispose", ACTION_EVENT)
      self.add(cancel)
   end

   initially(a[])
      self.Dialog.initially()
      #
      # Set attrib before calling set_fields, so these settings can
      # be overridden.
      #
      attrib("size=420,370", "resize=on", "label=Select File")
      set_fields(a)
end

#
# Read a directory.
#
procedure get_directory_list(s)
   local dir_list, file_list
   /fatal_error := stop
$ifdef _MS_WINDOWS_NT
   s := map(s,"/","\\")
   if (*s > 1) & (s[-1] == PATHCHAR) & (map(s,&letters,repl("a",52)) ~== "a:\\") then
      s[-1] := ""
$else
   if (*s > 1) & (s[-1] == PATHCHAR) then s[-1] := ""
$endif
 
   p := open(s) | {
       write(&errout, "get_directory_list: can't open ", image(s))
       fail
   }
   if not (s[-1] == PATHCHAR) then s ||:= PATHCHAR
   dir_list := []
   file_list := []
   while s2 := read(p) do {
      sr := stat(s||s2) | {
          write(&errout, "get_directory_list: can't stat ", image(s2))
          fail
       }
      if sr.mode[1] == "d" then
         put(dir_list, s2 || PATHCHAR)
      else
         put(file_list, s2)
   }
   close(p)

   return [sort(dir_list), sort(file_list)]
end
 
#
# Return the directory name of the file name s, including the trailing /
#
procedure directory_name(s)
   local i
   every i := find(PATHCHAR, s)
   return s[1:\i + 1] | ""
end
 
#
# Return the file name of s
#
procedure file_name(s)
   local i
   every i := find(PATHCHAR, s)
   return s[\i + 1:0] | s
end