//<copyright>
// 
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// original version:
// Copyright (c) 1991 Stanford University
// Copyright (c) 1991 Silicon Graphics, Inc.
// 
//</copyright>
/*
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

/*
 * FileChooser -- select a file
 */


//<file>
//
// File:        fchooser.C - implementation of class WFileChooser
//
// Created:      1 Jul 94   Michael Pichler
//
// Changed:      7 Nov 94   Michael Pichler
//
//
//</file>


/* X-attributes     default value        meaning

   caption          ""
   subcaption       "Enter filename:"
   open             "Open"               button label open
   cancel           "Cancel"             "      "     cancel
   rows             10                   no. of rows in browser
   width            16*width('m')        natural browser width
   filter           off                  file name filter
   filterPattern    ""
   filterCaption    "Filter:"
   directoryFilter  off                  directory filter
   d.filterPattern  ""
   d.filterCaption  "Directory Filter:"
   background                            background of dialog

   furthermore all attributes of Window, e.g. name
*/


#include "fchooser.h"
#include "field.h"
#include "glyphutil.h"
#include "msgbox.h"

#include "fbrowser.h"

#include <IV-look/choice.h>
#include <IV-look/kit.h>
#include <InterViews/action.h>
#include <InterViews/background.h>
#include <InterViews/event.h>
#include <InterViews/font.h>
#include <InterViews/hit.h>
#include <InterViews/input.h>
#include <InterViews/layout.h>
#include <InterViews/scrbox.h>
#include <InterViews/style.h>
#include <InterViews/target.h>
#include <OS/directory.h>
#include <OS/string.h>

#include <string.h>
#include <stdio.h>
#include <iostream.h>


/*** WFileChooserImpl ***/

class WFileChooserImpl
{
  private:
    friend class WFileChooser;

    String* name_;
    WidgetKit* kit_;
    WFileChooser* fchooser_;
    WFileBrowser* fbrowser_;
    FieldEditor31* editor_;
    FieldEditor31* filter_;
    Glyph* browserglyph_;
    String* filterstr_;
    FieldEditor31* directory_filter_;
    int* filter_map_;
    Directory* dir_;
    WFileChooserAction* action_;
    const char* selected_;
    Style* style_;
//    Action* update_;

    void init(WFileChooser*, Style*, WFileChooserAction*);
    void free();
    void build();
    void clear();
    void load();
    FieldEditor31* add_filter(
	Style*,
	const char* pattern_attribute, const char* default_pattern,
	const char* caption_attribute, const char* default_caption,
	Glyph*, FieldEditorAction31*
    );
    boolean filtered(const String&, FieldEditor31*, const String*);
    void accept_browser();
    void cancel_browser();
    void accept_editor (FieldEditor31*, char);
    void cancel_editor (FieldEditor31*, char);
    void accept_filter (FieldEditor31*, char);
    boolean chdir(const String&);
};

declareActionCallback(WFileChooserImpl)
implementActionCallback(WFileChooserImpl)

declareFieldEditorCallback31(WFileChooserImpl)
implementFieldEditorCallback31(WFileChooserImpl)


/*** WFileChooser ***/

WFileChooser::WFileChooser (
  const char* dir, WidgetKit* kit, Style* s,
  WFileChooserAction* a, const char* filter
): Dialog (nil, s)
{
  impl_ = new WFileChooserImpl;
  WFileChooserImpl& fc = *impl_;
  fc.name_ = new CopyString (dir ? dir : "");
  fc.kit_ = kit;
  fc.filterstr_ = new CopyString (filter ? filter : "");
  fc.init (this, s, a);
}


WFileChooser::~WFileChooser ()
{
  impl_->free ();
  delete impl_;
}


const char* WFileChooser::selected () const
{
  return impl_->selected_;
}


void WFileChooser::reread ()
{
  WFileChooserImpl& fc = *impl_;
  if (!fc.chdir (*fc.dir_->path ()))
  {
    cerr << "FileChooser: error: could not change to directory " << fc.dir_->path ()->string () << endl;
  }
}


void WFileChooser::rebuild ()
{
  impl_->build ();  // rebuild dialog
}


FieldEditor31* WFileChooser::editorField () const
{
  return impl_->editor_;
}


InputHandler* WFileChooser::fileBrowser () const
{
  return impl_->fbrowser_;
}


Glyph* WFileChooser::browserGlyph () const
{
  return impl_->browserglyph_;
}


void WFileChooser::dismiss (boolean accept)
{
  Dialog::dismiss (accept);  // close the dialog
  WFileChooserImpl& fc = *impl_;
  if (fc.action_)
    fc.action_->execute (this, accept);
}


/** class WFileChooserImpl **/

void WFileChooserImpl::init (
  WFileChooser* chooser, Style* s, WFileChooserAction* a
)
{
  fchooser_ = chooser;
  fbrowser_ = nil;
  editor_ = nil;
  filter_ = nil;
  browserglyph_ = nil;
  directory_filter_ = nil;
  filter_map_ = nil;

  dir_ = Directory::open (*name_);
  if (!dir_)
  {
    dir_ = Directory::current ();
    if (dir_)  // "" also opens the current dir (otherwise remove this warning!)
      cerr << "FileChooser. warning: could not read directory " << name_->string () << endl;
    else  // and what if we can't read the current directory?
      cerr << "FileChooser. error: could neither read " << name_->string () << " nor current directory." << endl;
  }

  Resource::ref (a);
  action_ = a;
  style_ = new Style (s);
  Resource::ref (style_);
  style_->alias ("FileChooser");
  style_->alias ("Dialog");

  // mpichler, 19941107: no style triggering; call rebuild if necessary
//   update_ = new ActionCallback(WFileChooserImpl) (this, &WFileChooserImpl::build);
//   style_->add_trigger_any (update_);  // rebuild dialog on any style change

  build ();  // build dialog body

} // init


void WFileChooserImpl::free ()
{
  delete name_;
  delete dir_;
  delete filter_map_;
  delete filterstr_;
  Resource::unref (action_);
  // style_->remove_trigger_any (update_);
  Resource::unref (style_);
}


void WFileChooserImpl::build ()
{
  WidgetKit& kit = *kit_;
  const LayoutKit& layout = *LayoutKit::instance();
  Style* s = style_;

  kit.push_style();
  kit.style(s);

  String caption ("");
  s->find_attribute ("caption", caption);

  String subcaption ("Enter filename:");
  s->find_attribute ("subcaption", subcaption);

  String open ("Open");
  s->find_attribute ("open", open);

  String close ("Cancel");
  s->find_attribute ("cancel", close);

  long rows = 10;
  s->find_attribute ("rows", rows);
  const Font* f = kit.font ();

  FontBoundingBox bbox;
  f->font_bbox (bbox);
  Coord height = rows * (bbox.ascent () + bbox.descent ()) + 1.0;

  Coord width;
  if (!s->find_attribute ("width", width))
    width = 16 * f->width ('m') + 3.0;

  Action* accept = new ActionCallback(WFileChooserImpl) (
    this, &WFileChooserImpl::accept_browser
  );
  Action* cancel = new ActionCallback(WFileChooserImpl) (
    this, &WFileChooserImpl::cancel_browser
  );

  if (!editor_)
  {
    const String* pathstr = dir_->path ();
    char buf [512];
    sprintf (
      buf, "%s%s",
      pathstr ? pathstr->string () : "",
      filterstr_ ? filterstr_->string () : ""
    );

    editor_ = new FieldEditor31 (
      &kit, s, buf,
      new FieldEditorCallback31(WFileChooserImpl) (
        this, &WFileChooserImpl::accept_editor, &WFileChooserImpl::cancel_editor, nil
      )
    );
  }

  fbrowser_ = new WFileBrowser (kit_, accept, cancel);

  fchooser_->remove_all_input_handlers ();
  fchooser_->append_input_handler (editor_);
  fchooser_->append_input_handler (fbrowser_);

  // fancy label allows labelStyle chiseled or raised
  Glyph* g = layout.vbox ();
  if (caption.length () > 0)
    g->append (layout.rmargin (kit.fancy_label (caption), 5.0, fil, 0.0));
  if (subcaption.length() > 0)
    g->append(layout.rmargin (kit.fancy_label (subcaption), 5.0, fil, 0.0));
  g->append (layout.vglue (5.0, 0.0, 2.0));
  g->append (editor_);
  g->append (layout.vglue(15.0, 0.0, 12.0));
  browserglyph_ = layout.hbox ( // browser with scroll bar
    layout.vcenter (
      kit.inset_frame (
        layout.margin (
          layout.natural_span (fbrowser_, width, height), 1.0
        )
      ),
      1.0
    ),
    kit.vscroll_bar (fbrowser_->adjustable())
  );
  g->append (browserglyph_);
  g->append (layout.vspace (15.0));

  // should be no need for filter field - jokers in file name field are handled
  if (s->value_is_on ("filter"))
  {
    FieldEditorAction31* action = new FieldEditorCallback31(WFileChooserImpl) (
      this, &WFileChooserImpl::accept_filter, nil, nil
    );
    filter_ = add_filter (s, "filterPattern", "", "filterCaption", "Filter:", g, action);
    if (s->value_is_on ("directoryFilter"))
      directory_filter_ = add_filter (s, "directoryFilterPattern", "",
        "directoryFilterCaption", "Directory Filter:", g, action
      );
    else
      directory_filter_ = nil;
  }
  else  // no filters
  { filter_ = nil;
    directory_filter_ = nil;
  }

  g->append (  // buttons
    layout.hbox (
      layout.hglue (10.0),
      layout.vcenter (kit.default_button (open, accept)),
      layout.hglue (10.0),  // , 0.0, 5.0),
      layout.vcenter (kit.push_button (close, cancel)),
      layout.hglue(10.0)
    )
  );

//   fchooser_->body (
//     layout.back (
//       layout.vcenter (
//         kit.outset_frame (layout.margin (g, 5.0)),
//         1.0
//       ),
//       new Target (nil, TargetPrimitiveHit)
//     )
//   );
  fchooser_->body (
    new TransparentTarget (
      new Background (
        layout.margin (g, 10.0),
        kit.background ()
      )
    )
  );

  fchooser_->focus (editor_);
  kit.pop_style ();

  load ();

} // build


void WFileChooserImpl::clear ()
{
  Browser& b = *fbrowser_;
  b.select (-1);  // remove selection
  GlyphIndex n = b.count ();
  while (n--)
  { // remove telltale state and glyph
    b.remove_selectable (n);
    b.remove (n);
  }
}


void WFileChooserImpl::load ()
{
  Directory& d = *dir_;
  WFileBrowser& b = *fbrowser_;
  WidgetKit& kit = *kit_;

  kit.push_style ();
  kit.style (style_);
  const LayoutKit& layout = *LayoutKit::instance ();

  int dircount = d.count ();
  delete filter_map_;
  int* index = new int [dircount];
  filter_map_ = index;

  // directories
  int i;
  for (i = 0;  i < dircount;  i++)
  {
    if (!d.is_directory (i))
      continue;

    const String& f = *d.name (i);
    if (filtered (f, directory_filter_, nil))
    {
      Glyph* name = layout.hbox (kit.label (f), kit.label ("/"));
      Glyph* label = new Target (
	layout.h_margin (name, 3.0, 0.0, 0.0, 15.0, fil, 0.0),
	TargetPrimitiveHit
      );
      TelltaleState* t = new TelltaleState (TelltaleState::is_enabled);
      b.append_selectable (t);
      b.append (new ChoiceItem (t, label, kit.bright_inset_frame (label)));
      *index++ = i;
    }
  } // for all directories

  // ordinary files
  for (i = 0; i < dircount; i++)
  {
    if (d.is_directory (i))
      continue;

    const String& f = *d.name (i);
    if (filtered (f, filter_, filterstr_))
    {
      Glyph* name = kit.label(f);
      Glyph* label = new Target (
        layout.h_margin (name, 3.0, 0.0, 0.0, 15.0, fil, 0.0),
        TargetPrimitiveHit
      );
      TelltaleState* t = new TelltaleState (TelltaleState::is_enabled);
      b.append_selectable (t);
      b.append(new ChoiceItem(t, label, kit.bright_inset_frame(label)));
      *index++ = i;
    }
  } // for all ordinary files

  b.refresh ();  // scroll to first

  kit.pop_style ();

} // load


FieldEditor31* WFileChooserImpl::add_filter (
  Style* s,
  const char* pattern_attribute, const char* default_pattern,
  const char* caption_attribute, const char* default_caption,
  Glyph* body, FieldEditorAction31* action
)
{
  String pattern (default_pattern);
  s->find_attribute (pattern_attribute, pattern);
  String caption (default_caption);
  s->find_attribute (caption_attribute, caption);

  WidgetKit& kit = *kit_;
  FieldEditor31* e = new FieldEditor31 (&kit, s, pattern.string (), action);
//    FieldEditor* e = DialogKit::instance()->field_editor(pattern, s, action);

  fchooser_->append_input_handler(e);
  LayoutKit& layout = *LayoutKit::instance();
  body->append(
    layout.hbox(
      layout.vcenter(kit.fancy_label(caption), 0.5),
      layout.hspace(2.0),
      layout.vcenter(e, 0.5)
    )
  );
  body->append(layout.vspace(10.0));
  return e;
}


boolean WFileChooserImpl::filtered (const String& name, FieldEditor31* fe, const String* flt)
{
  String str;
  if (fe)  // filter field
    str = fe->string ();
  else if (flt)
    str = *flt;  // filter string

  if (!str.length ())
    return true;
  else
    return Directory::match (name, str);
}


void WFileChooserImpl::accept_browser() {
    int i = int(fbrowser_->selected());
    if (i == -1) {
        accept_editor(editor_, 0);
        return;
    }
    i = filter_map_[i];
    const String& path = *dir_->path();
    const String& name = *dir_->name(i);
    int length = path.length() + name.length();
    char* tmp = new char[length + 1];
    sprintf(
        tmp, "%.*s%.*s",
        path.length(), path.string(), name.length(), name.string()
    );
    editor_->field (tmp);
    selected_ = editor_->string ();

    if (dir_->is_directory(i))
    {
      if (chdir (String (tmp, length)))
      {
        const String* pathstr = dir_->path ();
        char buf [512];

        sprintf (
          buf, "%s%s",
          pathstr ? pathstr->string () : "",
          filterstr_ ? filterstr_->string () : ""
        );
        editor_->field (buf);
        // mpichler, 19941107: do not give the focus to the field editor
        // fchooser_->focus (editor_);
        fbrowser_->focus_in ();  // select first item
      }
      else  // should generate an error message
        cerr << "FileChooser: could not change to directory " << tmp << endl;
    }
    else  // ordinary file
      fchooser_->dismiss(true);

    delete tmp;
} // accept_browser


void WFileChooserImpl::cancel_browser()
{
  selected_ = nil;
  fchooser_->dismiss (false);
}


void WFileChooserImpl::accept_editor (FieldEditor31* fe, char)
{
  // replace string by canonical form (expand ~ etc.)
  String* path = Directory::canonical (String (fe->string ()));
  const char* pathstr = path->string ();
  fe->field (pathstr);
  delete filterstr_;
  filterstr_ = nil;

  if (chdir (*path))  // directory
  { // chdir has copied the string
    delete path;
  }
  else if (strchr (pathstr, '*') || strchr (pathstr, '?'))  // joker
  {
    const char* slpos = strrchr (pathstr, '/');

    if (slpos)
    {
      filterstr_ = new CopyString (slpos + 1);

      String directory (pathstr, slpos - pathstr + 1);  // +1 to be able to open "/"
      if (chdir (directory))  // directory may have changed
      { // chdir has copied the string
        delete path;
        return;  // clear and load done inside chdir
      }
      else  // error: directory does not exist
      { // (without filter the name is assumed to be name of a file to be created)
        char buf [512];
        sprintf (buf, "failed to open directory %.*s", directory.length (), directory.string ());
        MessageBox::message (
          HgLanguage::Default, // will be supported soon!!!
          nil, buf, "error", MessageBox::Ok
        );
      }
    }
    else  // small bug: changes to current directory
      filterstr_ = new CopyString (pathstr);

    clear ();
    load ();
  }
  else  // file name
  {
    selected_ = path->string ();
    fchooser_->dismiss (true);
    // to get the plain file name?!:
    // fe->select (path->rindex ('/') + 1, path->length ());
  }
} // accept_editor


void WFileChooserImpl::cancel_editor (FieldEditor31*, char)
{
  fchooser_->dismiss (false);
}


void WFileChooserImpl::accept_filter (FieldEditor31*, char)
{
  clear ();
  load ();
}


boolean WFileChooserImpl::chdir (const String& name)
{
  Directory* d = Directory::open (name);
  if (d != nil)
  {
    dir_->close ();
    delete dir_;
    dir_ = d;
    clear ();
    load ();
    return true;
  }
  return false;
}


/** class WFileChooserAction **/

WFileChooserAction::WFileChooserAction() { }
WFileChooserAction::~WFileChooserAction() { }
void WFileChooserAction::execute(WFileChooser*, boolean) { }
