/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "swidget/SFileDialog.h"
#include "swidget/SIcon.h"
#include "swidget/SIconFactory.h"
#include "stoolkit/SUtil.h"
#include "stoolkit/SEncoder.h"


/**
 * Hidden  Filter        Type
 * Directories Files    Types
 * +--------+ +-------+ +---+ 
 * |        | |       | |   |
 * |        | |       | |   |
 * |        | |       | |   |
 * +--------+ +-------+ +---+ 
 * ______directory       
 * ______pathname___________       
 * OK   CD   mkdir   Cancel
 */
SFileDialog::SFileDialog (void)
{
  setFrameListener (this);
  hiddenButton = new SButton (translate ("Show Hidden"));
  hiddenButton->setButtonListener (this);
  hiddenButton->setAlignment (SD_Center);
  hiddenButton->setIcon(SIconFactory::getIcon("CircleOff"));

  homeButton = new SButton (translate ("Home"), SIconFactory::getIcon("Home"));
  homeButton->setButtonListener (this);
  homeButton->setAlignment (SD_Center);
  add (homeButton);
   
  isFocused = false;
  isCancel = true;
  showHidden = false;
  setHidden ();
  add (hiddenButton);
  yesButton = new SButton (translate ("OK"), SIconFactory::getIcon("Yes"));
  yesButton->setAlignment (SD_Center);
  yesButton->setButtonListener (this);

  cdButton = new SButton (translate ("CD to"), SIconFactory::getIcon("Folder"));
  cdButton->setAlignment (SD_Center);
  cdButton->setButtonListener (this);

  createButton = new SButton (translate ("Make Folder"),  SIconFactory::getIcon("CreateFolder"));
  createButton->setAlignment (SD_Center);
  createButton->setButtonListener (this);

  cancelButton = new SButton (translate ("Cancel"), SIconFactory::getIcon("Cancel"));
  cancelButton->setAlignment (SD_Center);
  cancelButton->setButtonListener (this);
  add (yesButton);
  add (cdButton);
  add (createButton);
  add (cancelButton);
  folderListBox = new SListBox(translate ("Folder"));
  folderListBox->setListListener (this);
  add (folderListBox);
  fileListBox = new SListBox(translate ("File"));
  fileListBox->setListListener (this);
  add (fileListBox);
  fileTypeListBox = new SListBox(translate ("File Type"));
  fileTypeListBox->setListListener (this);
  add (fileTypeListBox);


  fileTitleLabel = new SLabel (translate ("File:"));
  add (fileTitleLabel);
  fileTextEdit = new STextEdit("");
  fileTextEdit->setMultiline(false);
  fileTextEdit->setLineEndMark(false);
  fileTextEdit->addTextEditLS(this);
  add (fileTextEdit);

  folderTitleLabel = new SLabel (translate ("Folder:"));
  add (folderTitleLabel);
  folderNameLabel = new SLabel ("");
  add (folderNameLabel);

  filterTitleLabel  = new SLabel (translate ("Filter:"));
  add (filterTitleLabel);
  fileFilter = "*";
  filterTextEdit = new STextEdit(fileFilter);
  filterTextEdit->addTextEditLS(this);

  add (filterTextEdit);
  filterTextEdit->setMultiline(false);
  filterTextEdit->setLineEndMark(false);
  fileTypeButton = new SButton (
     translate ("Best File Type"), SIconFactory::getIcon("Best"));
  fileTypeButton->setButtonListener (this);
  add (fileTypeButton);
  fileTypeLabel = new SLabel ("[]");
  add (fileTypeLabel);
  textDialog = new STextDialog();
  textDialog->setModal (this);
  recalc();
}

bool
SFileDialog::setFileType (const SString& _fileType)
{
  bool exists = true;
  if (!fileTypeListBox->selectText (_fileType))
  {
    fileTypeVector.append (_fileType);
    fileTypeVector.sort ();
    fileTypeListBox->setText(fileTypeVector);
    exists = false;
  }
  fileTypeListBox->selectText (_fileType);
  fileTypeChanged (_fileType);
  return exists;
}

const SStringVector&
SFileDialog::getFileTypes() const
{
  return fileTypeVector;
}

void
SFileDialog::setApplicationImage (const SImage& image)
{
  getComponentWindow()->setApplicationImage (image);
  textDialog->setApplicationImage (image);
}

void
SFileDialog::fileTypeChanged (const SString& _filetype)
{
  //SString a="[";
  SString a;
  a.append(_filetype);
  //a.append("]");
  fileTypeLabel->setText (a);
  fileTypeText = _filetype;
}

void
SFileDialog::rereadDir()
{
  filterChanged ();
  /* get directories and files */
  while (!currentDirectory.exists() || !currentDirectory.readable())
  {
    currentDirectory.cd ("..");
    if (currentDirectory.getUnixName() == "/") break;
  }
  SStringVector files = currentDirectory.list("*");
  SStringVector combedFiles;
  unsigned int i;
  if (showHidden)
  {
     combedFiles = files;
  }
  else
  {
    for (i=0; i<files.size(); i++)
    {
      SString s = files[i];
      if (s.size() == 0 || s[0] == '.') continue;
      if (fileFilter.size()==0 || s.match (fileFilter))
      {
        combedFiles.append (s);
      }
    }
  }
  combedFiles.sort();
  fileListBox->setText(combedFiles);

  SStringVector dirs = currentDirectory.list(SDir::SE_DIR);
  SStringVector combedDirs;
  if (showHidden)
  {
     combedDirs = dirs;
  }
  else
  {
    for (i=0; i<dirs.size(); i++)
    {
      SString s = dirs[i];
      if (s.size() == 0 || s[0] == '.') continue;
      combedDirs.append (s);
    }
  }
  combedDirs.sort();
  if (currentDirectory.getUnixName () != "/")
  {
    combedDirs.insert (0, "..");
  }
  folderListBox->setText(combedDirs);
  folderNameLabel->setText (currentDirectory.getUnixName ());
  // This bug has been with us for some time now :)
  fileListBox->selectText (fileTextEdit->getText());
}

void
SFileDialog::setFileTypes(const SStringVector& v)
{
  fileTypeVector = v;
  fileTypeVector.sort();
  fileTypeListBox->setText(fileTypeVector);
}


SFileDialog::~SFileDialog ()
{
}


/**
 * recalcualte the geometry
 * it is based upon 300x200 layout.
 * preferredSize will be calculated here.
 */
void
SFileDialog::recalc ()
{
  unsigned int bw = 1;
  if (yesButton->getPreferredSize().width > bw )
  {
     bw = yesButton->getPreferredSize().width;
  }
  if (cdButton->getPreferredSize().width > bw )
  {
     bw = cdButton->getPreferredSize().width;
  }
  if (createButton->getPreferredSize().width > bw )
  {
     bw = createButton->getPreferredSize().width;
  }
  if (cancelButton->getPreferredSize().width > bw )
  {
     bw = cancelButton->getPreferredSize().width;
  }
  unsigned int bh= cancelButton->getPreferredSize().height;
  int gap = (300-((int)bw * 4 + 10))/3;
  int currentx= 5;

  yesButton->setLayout (
    SLayout (
      SLocation (currentx, 195-bh),
      SLocation (currentx+(int)bw, 195),
      SLocation (0, 100),
      SLocation (0, 100)
    )
  );
  currentx = currentx + (int) bw + gap;
  cdButton->setLayout (
    SLayout (
      SLocation (currentx, 195-bh),
      SLocation (currentx+(int)bw, 195),
      SLocation (33, 100),
      SLocation (33, 100)
    )
  );
  currentx = currentx + (int) bw + gap;
  createButton->setLayout (
    SLayout (
      SLocation (currentx, 195-bh),
      SLocation (currentx+(int)bw, 195),
      SLocation (66, 100),
      SLocation (66, 100)
    )
  );
  currentx = currentx + (int) bw + gap;
  cancelButton->setLayout (
    SLayout (
      SLocation (currentx, 195-bh),
      SLocation (currentx+(int)bw, 195),
      SLocation (100, 100),
      SLocation (100, 100)
    )
  );
  SDimension hms = homeButton->getPreferredSize();
  homeButton->setLayout (
    SLayout (
      SLocation (5, 5),
      SLocation ((int)hms.width+5, (int)hms.height+5),
      SLocation (0, 0),
      SLocation (0, 0)
    )
  );
  SDimension hs = hiddenButton->getPreferredSize();
  hiddenButton->setLayout (
    SLayout (
      SLocation ((int)hms.width+10, 5),
      SLocation ((int)hms.width + hs.width+10, (int)hs.height+5),
      SLocation (0, 0),
      SLocation (0, 0)
    )
  );
  SDimension bs = fileTypeButton->getPreferredSize();
  fileTypeButton->setLayout (
    SLayout (
      SLocation (295-(int)bs.width, 5),
      SLocation (295, (int)bs.height+5),
      SLocation (100, 0),
      SLocation (100, 0)
    )
  );
  SDimension fs = filterTitleLabel->getPreferredSize();
  filterTitleLabel->setLayout (
    SLayout (
      SLocation ((int)hs.width+(int)hms.width+12, 5),
      SLocation ((int) fs.width + (int)hs.width+(int)hms.width+12, (int) fs.height+5),
      SLocation (0, 0),
      SLocation (0, 0)
    )
  );
  filterTextEdit->setLayout (
    SLayout (
      SLocation ((int) fs.width + (int)hs.width+(int)hms.width+12, 5),
      SLocation ((int) fs.width + 80 + (int)hs.width+(int)hms.width+12, 
             (int) fs.height+5),
      SLocation (0, 0),
      SLocation (0, 0)
    )
  );
 
  /* this will be the text field height */
  unsigned int tfh = fileTextEdit->getPreferredSize().height;
  unsigned int phw = fileTitleLabel->getPreferredSize().width;

  unsigned int dph = folderTitleLabel->getPreferredSize().height;
  unsigned int dpw = folderTitleLabel->getPreferredSize().width;

  folderTitleLabel->setLayout (
     SLayout (
        SLocation (5, 200-(int)bh-(int)tfh -(int) dph - 10),
        SLocation (5 + dpw, 200-(int)bh -(int)tfh - 10),
        SLocation (0, 100),
        SLocation (0, 100)
     )
  );

  folderNameLabel->setLayout (
     SLayout (
        SLocation (5+dpw, 200-(int)bh-(int)tfh -(int) dph - 10),
        SLocation (295, 200-(int)bh -(int)tfh - 10),
        SLocation (0, 100),
        SLocation (100, 100)
     )
  );

  fileTitleLabel->setLayout (
     SLayout (
        SLocation (5, 200-(int)bh-(int)tfh - 10),
        SLocation (5 + phw, 200-(int)bh - 10),
        SLocation (0, 100),
        SLocation (0, 100)
     )
  );

  fileTextEdit->setLayout (
     SLayout (
        SLocation (5+phw, 200-(int)bh-(int)tfh - 10),
        SLocation (180, 200-(int)bh - 10),
        SLocation (0, 100),
        SLocation (100, 100)
     )
  );
  fileTypeLabel->setLayout (
     SLayout (
        SLocation (180, 200-(int)bh-(int)tfh - 10),
        SLocation (295, 200-(int)bh - 10),
        SLocation (100, 100),
        SLocation (100, 100)
     )
  );

  unsigned int incr = (300-10)/3;
  folderListBox->setLayout (
    SLayout (
      SLocation (5, (int)hs.height+5+5),
      SLocation (5+incr-2, 200-(int)bh-(int)tfh -(int) dph - 10),
      SLocation (0, 0),
      SLocation (33, 100)
    )
  );

  fileListBox->setLayout (
    SLayout (
      SLocation (5+incr+1, (int)hs.height+5+5),
      SLocation (300-5-incr-1, 200-(int)bh-(int)tfh-(int) dph  - 10),
      SLocation (33, 0),
      SLocation (66, 100)
    )
  );

  fileTypeListBox->setLayout (
    SLayout (
      SLocation (300-5-incr+2, (int)hs.height+5+5),
      SLocation (300-5, 200-(int)bh-(int)tfh-(int) dph  - 10),
      SLocation (66, 0),
      SLocation (100, 100)
    )
  );
  unsigned int bestWidthBottom = bw * 4 + 10 + 15 + 20;
  /* top */
  unsigned int bestWidthTop = 5 + hms.width + 5 + hs.width 
          + 5 +fs.width + 80 + bs.width + 5 + 10;
  unsigned int bestWidth = (bestWidthTop>bestWidthBottom) ?
        bestWidthTop : bestWidthBottom;

  preferredSize = SDimension (bestWidth+10,  300);

  /* This was out layout... */
  forceLayout (SLayout (SDimension (300,200)));

  /* But this one is the one that works */
  setLayout (SLayout (preferredSize));

  setMinimumSize (preferredSize);

  resize (SDimension (preferredSize.width, preferredSize.height));

}

SString
SFileDialog::getFileName ()
{
  SString fn = fileTextEdit->getText();
  if (fn.size()==0) return SString(fn);

  SString filePath = currentDirectory.getName();
  if (filePath != "/") filePath.append ("/"); 
  filePath.append (fn);
  return SString(filePath);
}

SString
SFileDialog::getUnixFileName ()
{
  SString fn = fileTextEdit->getText();
  if (fn.size()==0) return SString(fn);
  SString filePath = currentDirectory.getUnixName();
  if (filePath != "/") filePath.append ("/"); 
  filePath.append (fn);
  return SString(filePath);
}

SString
SFileDialog::getFileType ()
{
  return fileTypeText;
}

bool
SFileDialog::getInput (const SString& titleString, 
  bool shouldExist, bool warnExist)
{
  setTitle (titleString);
  fileFilter = "*";
  filterTextEdit->setText (fileFilter);
  rereadDir ();
  fileListBox->selectText (fileTextEdit->getText());
  center();
  isCancel = true;
  isFocused = false;
  show();
  wait();
  textDialog->hide();
  hide();
  return !isCancel;
}

/**
 * buttons call this
 */
void
SFileDialog::buttonPressed (void* source, const SAccelerator* acc)
{
  if (source == createButton)
  {
    SString before = currentDirectory.getName();
    SDir d(before);
    SString s(fileTextEdit->getText());
    unsigned int ck = 0;
    bool hasSlash = false;
    for (ck=0; ck<s.size(); ck++)
    {
       if ((int) (unsigned char) s[ck] <= ' ') break;
       if (s[ck] == '/') hasSlash = true;
    }
    if (ck==0 || ck+1 == s.size())
    {
      textDialog->getInput ("Error", 
           translate ("Will not create\nspecified folder\n"),
      STextDialog::SS_ERR);
      return;
    }
    if (hasSlash)
    {
      textDialog->getInput ("Error", 
           translate ("Folders can be created\nin current directory only."),
      STextDialog::SS_ERR);
      return;
    }
    if (d.cd (s))
    {
      textDialog->getInput ("Warning", 
           translate ("Specified folder\nalready exists.\n"),
      STextDialog::SS_WARN);
      return;
    }
    SString cdr = currentDirectory.getName();
    if (cdr != "/") cdr.append ("/");
    cdr.append (fileTextEdit->getText());
    d = SDir (cdr);
    if (!d.create())
    {
      textDialog->getInput ("Error", 
           translate ("Can not create\nspecified folder\n"),
      STextDialog::SS_ERR);
      return;
    }
    currentDirectory = d;
    rereadDir ();
    fileTextEdit->setText("");
    return;
  }
  if (source == cdButton)
  {
    SString before = currentDirectory.getName();
    SDir d(before);
    if (!d.cd (fileTextEdit->getText()))
    {
      textDialog->getInput ("Error", 
           translate ("Can not go to\nspecified folder.\n"),
       STextDialog::SS_ERR);
      return;
    }
    currentDirectory = d;
    rereadDir ();
    fileTextEdit->setText("");
    return;
  }
  if (source == cancelButton)
  {
    isCancel = true;
    hide();
    return;
  }
  if (source == yesButton)
  {
    isCancel = false;
    hide();
    return;
  }
  if (source == fileTypeButton)
  {
    setFileType("utf-8");
    return;
  }
  if (source == hiddenButton)
  {
    showHidden = !showHidden;
    setHidden();
    rereadDir();
    return;
  }
  if (source == homeButton)
  {
    currentDirectory.cd (getHome());
    rereadDir();
    return;
  }
}

/**
 * STextListListener
 */
void
SFileDialog::itemSelected (void* source, const SAccelerator* acc)
{
  if (source == folderListBox->textList)
  {
     SString s = folderListBox->textList->getLastSelectedText();
     SString before = currentDirectory.getName();
//fprintf (stderr, "before %*.*s [%*.*s]\n", SSARGS (s), SSARGS(before));
     currentDirectory.cd (s);
     SString after  = currentDirectory.getName();
//fprintf (stderr, "after %*.*s [%*.*s]\n", SSARGS (s), SSARGS(after));
     if (after != before) rereadDir ();
     return;
  }
  if (source == fileListBox->textList)
  {
     SString s = fileListBox->textList->getLastSelectedText();
     if (s != fileTextEdit->getText()) {
         fileTextEdit->setText (s);
     }
     return;
  }
  if (source == fileTypeListBox->textList)
  {
    SString s = fileTypeListBox->textList->getLastSelectedText();
//fprintf (stderr, "typeselected %*.*s\n", SSARGS(s)); 
    fileTypeChanged (s);
  }
}

/**
 * STextEditLS
 */
void
SFileDialog::textEntered (void *source)
{
  if (source == filterTextEdit)
  {
    rereadDir ();
  }
  /* ok */
  if (source == fileTextEdit)
  {
    isCancel = false;
    hide();
    return;
  }
}

void
SFileDialog::textChanged (void *source)
{
  if (source == fileTextEdit)
  {
     fileListBox->selectText (fileTextEdit->getText());
  }
}

void
SFileDialog::filterChanged ()
{
  fileFilter = filterTextEdit->getText();
  /* trim what is not there */
  while (fileFilter.size()>0 && 
     (fileFilter[0]==' '||fileFilter[0]=='\t'))
  {
    fileFilter.remove (0);
  }
  while (fileFilter.size()>0 && 
      (fileFilter[fileFilter.size()-1] == ' ' 
           || fileFilter[fileFilter.size()-1] == '\t'))
  {
    fileFilter.truncate (fileFilter.size()-1);
  }
  //fprintf (stderr,"filter changed to '%*.*s'\n");
}

/**
 * set icon accoring to showHidden
 */
void
SFileDialog::setHidden()
{
  /* create icons here */
  const char * str = showHidden?"CircleOn" : "CircleOff";
  hiddenButton->setIcon (SIconFactory::getIcon(str));
}
bool
SFileDialog::close (SPanel* comp)
{
  isCancel =true;
  hide();
  textDialog->hide();
  return false;
}

void
SFileDialog::focusChanged (void *source, bool in)
{
  if (!in && source == filterTextEdit)
  {
    rereadDir ();
  }
}

/**
 * set the filename - cut off the dirpart
 */
void
SFileDialog::setFileName (const SString& _filename)
{
  SDir d;
  /* this will chop off filename */
  d.cd (_filename);
  SStringVector v(_filename, "/");
  SString fn;
  if (v.size())
  {
    fn.append (v[v.size()-1]);
  }
  currentDirectory = d;
  rereadDir ();
  fileTextEdit->setText (fn);
  fileListBox->selectText (fn);
}

void
SFileDialog::setTitleForeground (const SColor& fg)
{
  textDialog->setTitleForeground (fg);
  fileTypeButton->setForeground (fg);
  hiddenButton->setForeground (fg);
  homeButton->setForeground (fg);
  cdButton->setForeground (fg);
  yesButton->setForeground (fg);
  cancelButton->setForeground (fg);
  createButton->setForeground (fg);
  folderTitleLabel->setForeground (fg);
  fileTitleLabel->setForeground (fg);
  filterTitleLabel->setForeground (fg);

  folderListBox->setLabelForeground (fg);
  fileListBox->setLabelForeground (fg);
  fileTypeListBox->setLabelForeground (fg);
}

void 
SFileDialog::setLabelForeground (const SColor& fg)
{
  textDialog->setLabelForeground (fg);
  folderNameLabel->setForeground (fg);
  fileTypeLabel->setForeground (fg);
}

void
SFileDialog::setBackground (const SColor& bg)
{
  SFrame::setBackground (bg);
  textDialog->setBackground (bg);
  fileTextEdit->setTextBackground (SColor("white"));
  filterTextEdit->setTextBackground (SColor("white"));
}

void
SFileDialog::setForeground (const SColor& fg)
{
  SFrame::setForeground (fg);
  setLabelForeground (fg);
  setTitleForeground (fg);
}

void
SFileDialog::setSliderBackground (const SColor& bg)
{
  folderListBox->setSliderBackground (bg);
  fileListBox->setSliderBackground (bg);
  fileTypeListBox->setSliderBackground (bg);
}

void
SFileDialog::setFont (const SString& font, double fontSize)
{
  textDialog->setFont (font, fontSize);
  folderNameLabel->setFont (font, fontSize);
  fileTypeLabel->setFont (font, fontSize);
  textDialog->setFont (font, fontSize);
  fileTypeButton->setFont (font, fontSize);
  hiddenButton->setFont (font, fontSize);
  homeButton->setFont (font, fontSize);
  cdButton->setFont (font, fontSize);
  yesButton->setFont (font, fontSize);
  cancelButton->setFont (font, fontSize);
  createButton->setFont (font, fontSize);
  folderTitleLabel->setFont (font, fontSize);
  fileTitleLabel->setFont (font, fontSize);
  filterTitleLabel->setFont (font, fontSize);
  folderListBox->setFont (font, fontSize);
  fileListBox->setFont (font, fontSize);
  fileTypeListBox->setFont (font, fontSize);
  recalc();
}

void
SFileDialog::setFontSize (double fontSize)
{
  textDialog->setFontSize (fontSize);
  folderNameLabel->setFontSize (fontSize);
  fileTypeLabel->setFontSize (fontSize);
  textDialog->setFontSize (fontSize);
  fileTypeButton->setFontSize (fontSize);
  hiddenButton->setFontSize (fontSize);
  cdButton->setFontSize (fontSize);
  yesButton->setFontSize (fontSize);
  cancelButton->setFontSize (fontSize);
  createButton->setFontSize (fontSize);
  folderTitleLabel->setFontSize (fontSize);
  fileTitleLabel->setFontSize (fontSize);
  filterTitleLabel->setFontSize (fontSize);
  folderListBox->setFontSize (fontSize);
  fileListBox->setFontSize (fontSize);
  fileTypeListBox->setFontSize (fontSize);
  recalc();
}
/**
 * keyboard focus lost.
 */
void 
SFileDialog::gainedKeyboardFocus (SWindow* w)
{
  if (isFocused) return;
  isFocused = true;
  fileTextEdit->setFocus();
}
