/////////////////////////////////////////////////////////////////////////////
// Name:        dirdlg.cpp
// Purpose:     wxDirDialog
// Author:      Harm van der Heijden and Robert Roebling
// Modified by:
// Created:     12/12/98
// RCS-ID:      $Id: dirdlgg.cpp,v 1.24 2001/03/06 20:09:48 VZ Exp $
// Copyright:   (c) Harm van der Heijden and Robert Roebling
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
#pragma implementation "dirdlgg.h"
#endif

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#include "wx/defs.h"

#if wxUSE_DIRDLG

#include "wx/utils.h"
#include "wx/dialog.h"
#include "wx/button.h"
#include "wx/layout.h"
#include "wx/msgdlg.h"
#include "wx/textdlg.h"
#include "wx/filefn.h"
#include "wx/cmndata.h"
#include "wx/gdicmn.h"
#include "wx/intl.h"
#include "wx/imaglist.h"
#include "wx/icon.h"
#include "wx/log.h"
#include "wx/sizer.h"
#include "wx/tokenzr.h"

#if wxUSE_STATLINE
    #include "wx/statline.h"
#endif

#include "wx/generic/dirdlgg.h"

// If compiled under Windows, this macro can cause problems
#ifdef GetFirstChild
#undef GetFirstChild
#endif

#ifndef __WXMSW__
/* XPM */
static char * icon1_xpm[] = {
/* width height ncolors chars_per_pixel */
"16 16 6 1",
/* colors */
"   s None  c None",
".  c #000000",
"+  c #c0c0c0",
"@  c #808080",
"#  c #ffff00",
"$  c #ffffff",
/* pixels */
"                ",
"   @@@@@        ",
"  @#+#+#@       ",
" @#+#+#+#@@@@@@ ",
" @$$$$$$$$$$$$@.",
" @$#+#+#+#+#+#@.",
" @$+#+#+#+#+#+@.",
" @$#+#+#+#+#+#@.",
" @$+#+#+#+#+#+@.",
" @$#+#+#+#+#+#@.",
" @$+#+#+#+#+#+@.",
" @$#+#+#+#+#+#@.",
" @@@@@@@@@@@@@@.",
"  ..............",
"                ",
"                "};

/* XPM */
static char * icon2_xpm[] = {
/* width height ncolors chars_per_pixel */
"16 16 6 1",
/* colors */
"   s None  c None",
".  c #000000",
"+  c #c0c0c0",
"@  c #808080",
"#  c #ffff00",
"$  c #ffffff",
/* pixels */
"                ",
"   @@@@@        ",
"  @$$$$$@       ",
" @$#+#+#$@@@@@@ ",
" @$+#+#+$$$$$$@.",
" @$#+#+#+#+#+#@.",
"@@@@@@@@@@@@@#@.",
"@$$$$$$$$$$@@+@.",
"@$#+#+#+#+##.@@.",
" @$#+#+#+#+#+.@.",
" @$+#+#+#+#+#.@.",
"  @$+#+#+#+##@..",
"  @@@@@@@@@@@@@.",
"   .............",
"                ",
"                "};

#endif // !wxMSW

static const int ID_DIRCTRL = 1000;
static const int ID_TEXTCTRL = 1001;
static const int ID_OK = 1002;
static const int ID_CANCEL = 1003;
static const int ID_NEW = 1004;
//static const int ID_CHECK = 1005;

//-----------------------------------------------------------------------------
// wxDirItemData
//-----------------------------------------------------------------------------

wxDirItemData::wxDirItemData(wxString& path, wxString& name)
{
    m_path = path;
    m_name = name;
    /* Insert logic to detect hidden files here
     * In UnixLand we just check whether the first char is a dot
     * For FileNameFromPath read LastDirNameInThisPath ;-) */
    // m_isHidden = (bool)(wxFileNameFromPath(*m_path)[0] == '.');
    m_isHidden = FALSE;
    m_hasSubDirs = HasSubDirs();
}

wxDirItemData::~wxDirItemData()
{
}

void wxDirItemData::SetNewDirName( wxString path )
{
    m_path = path;
    m_name = wxFileNameFromPath( path );
}

bool wxDirItemData::HasSubDirs()
{
    wxString search = m_path + wxT("/*");
    wxLogNull log;
    wxString path = wxFindFirstFile( search, wxDIR );
    return (bool)(!path.IsNull());
}

//-----------------------------------------------------------------------------
// wxDirCtrl
//-----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS(wxDirCtrl,wxTreeCtrl)

BEGIN_EVENT_TABLE(wxDirCtrl,wxTreeCtrl)
  EVT_TREE_ITEM_EXPANDING     (-1, wxDirCtrl::OnExpandItem)
  EVT_TREE_ITEM_COLLAPSED     (-1, wxDirCtrl::OnCollapseItem)
  EVT_TREE_BEGIN_LABEL_EDIT   (-1, wxDirCtrl::OnBeginEditItem)
  EVT_TREE_END_LABEL_EDIT     (-1, wxDirCtrl::OnEndEditItem)
END_EVENT_TABLE()

wxDirCtrl::wxDirCtrl(void)
{
    m_showHidden = FALSE;
}

wxDirCtrl::wxDirCtrl(wxWindow *parent,
                     const wxWindowID id,
                     const wxString &WXUNUSED(dir),
                     const wxPoint& pos,
                     const wxSize& size,
                     const long style,
                     const wxString& name )
         : wxTreeCtrl( parent, id, pos, size, style, wxDefaultValidator, name )
{
#ifndef __WXMSW__
    m_imageListNormal = new wxImageList(16, 16, TRUE);
    m_imageListNormal->Add(wxICON(icon1));
    m_imageListNormal->Add(wxICON(icon2));
    SetImageList(m_imageListNormal);
#endif // !MSW

    m_showHidden = FALSE;
    m_rootId = AddRoot( _("Sections") );
    SetItemHasChildren(m_rootId);
    Expand(m_rootId); // automatically expand first level
}

/* Quick macro. Don't worry, I'll #undef it later */
#define ADD_SECTION(a,b) \
  if (wxPathExists((a))) { m_paths.Add( (a) ); m_names.Add( (b) ); };

void wxDirCtrl::SetupSections()
{
  wxString home;

  m_paths.Clear();
  m_names.Clear();
#ifdef __WXMSW__
  // better than nothing
  ADD_SECTION(wxT("c:\\"), _("My Harddisk") )
#else
  ADD_SECTION(wxT("/"), _("The Computer") )
  wxGetHomeDir(&home);
  ADD_SECTION(home, _("My Home") )
  ADD_SECTION(wxT("/mnt"), _("Mounted Devices") )
  ADD_SECTION(wxT("/usr/local"), _("User Local") )
  ADD_SECTION(wxT("/usr"), _("User") )
  ADD_SECTION(wxT("/var"), _("Variables") )
  ADD_SECTION(wxT("/etc"), _("Etcetera") )
  ADD_SECTION(wxT("/tmp"), _("Temporary") )
#endif
}
#undef ADD_SECTION

void wxDirCtrl::CreateItems(const wxTreeItemId &parent)
{
    wxTreeItemId id;
    wxDirItemData *dir_item;

//  wxASSERT(m_paths.Count() == m_names.Count());  ?

    size_t count = m_paths.GetCount();
    for ( size_t i=0; i<count; i++)
    {
        dir_item = new wxDirItemData(m_paths[i],m_names[i]);
#ifdef __WXMSW__
        id = AppendItem( parent, m_names[i], -1, -1, dir_item);
#else
        id = AppendItem( parent, m_names[i], 0, -1, dir_item);
        SetItemImage( id, 1, wxTreeItemIcon_Expanded );
#endif
        if (dir_item->m_hasSubDirs) SetItemHasChildren(id);
    }
}

void wxDirCtrl::OnBeginEditItem(wxTreeEvent &event)
{
    // don't rename the main entry "Sections"
    if (event.GetItem() == m_rootId)
    {
        event.Veto();
        return;
    }

    // don't rename the individual sections
    if (GetParent( event.GetItem() ) == m_rootId)
    {
        event.Veto();
        return;
    }
}

void wxDirCtrl::OnEndEditItem(wxTreeEvent &event)
{
    if ((event.GetLabel().IsEmpty()) ||
        (event.GetLabel() == _(".")) ||
        (event.GetLabel() == _("..")) ||
        (event.GetLabel().First( wxT("/") ) != wxNOT_FOUND))
    {
        wxMessageDialog dialog(this, _("Illegal directory name."), _("Error"), wxOK | wxICON_ERROR );
        dialog.ShowModal();
        event.Veto();
        return;
    }

    wxTreeItemId id = event.GetItem();
    wxDirItemData *data = (wxDirItemData*)GetItemData( id );
    wxASSERT( data );

    wxString new_name( wxPathOnly( data->m_path ) );
    new_name += wxT("/");
    new_name += event.GetLabel();

    wxLogNull log;

    if (wxFileExists(new_name))
    {
        wxMessageDialog dialog(this, _("File name exists already."), _("Error"), wxOK | wxICON_ERROR );
        dialog.ShowModal();
        event.Veto();
    }

    if (wxRenameFile(data->m_path,new_name))
    {
        data->SetNewDirName( new_name );
    }
    else
    {
        wxMessageDialog dialog(this, _("Operation not permitted."), _("Error"), wxOK | wxICON_ERROR );
        dialog.ShowModal();
        event.Veto();
    }
}

void wxDirCtrl::OnExpandItem(wxTreeEvent &event)
{
    if (event.GetItem() == m_rootId)
    {
        SetupSections();
        CreateItems(m_rootId);
        return;
    }

    // This may take a longish time. Go to busy cursor
    wxBeginBusyCursor();

    wxDirItemData *data = (wxDirItemData *)GetItemData(event.GetItem());
    wxASSERT(data);

    wxString search,path,filename;

    m_paths.Clear();
    m_names.Clear();
#ifdef __WXMSW__
    search = data->m_path + _T("\\*.*");
#else
    search = data->m_path + _T("/*");
#endif
    for (path = wxFindFirstFile( search, wxDIR ); !path.IsNull();
       path=wxFindNextFile() )
    {
        filename = wxFileNameFromPath( path );
        /* Don't add "." and ".." to the tree. I think wxFindNextFile
         * also checks this, but I don't quite understand what happens
         * there. Also wxFindNextFile seems to swallow hidden dirs */
        if ( (filename != _T(".")) && (filename != _T("..")) )
        {
            m_paths.Add(path);
            m_names.Add(filename);
        }
    }

    CreateItems( event.GetItem() );
    SortChildren( event.GetItem() );

    wxEndBusyCursor();
}

void wxDirCtrl::OnCollapseItem(wxTreeEvent &event )
{
    wxTreeItemId child, parent = event.GetItem();
    long cookie;
    /* Workaround because DeleteChildren has disapeared (why?) and
     * CollapseAndReset doesn't work as advertised (deletes parent too) */
    child = GetFirstChild(parent, cookie);
    while (child.IsOk())
    {
        Delete(child);
        /* Not GetNextChild below, because the cookie mechanism can't
         * handle disappearing children! */
        child = GetFirstChild(parent, cookie);
    }
}

//-----------------------------------------------------------------------------
// wxDirDialog
//-----------------------------------------------------------------------------


IMPLEMENT_DYNAMIC_CLASS( wxDirDialog, wxDialog )

BEGIN_EVENT_TABLE( wxDirDialog, wxDialog )
  EVT_TREE_KEY_DOWN        (ID_DIRCTRL,   wxDirDialog::OnTreeKeyDown)
  EVT_TREE_SEL_CHANGED     (ID_DIRCTRL,   wxDirDialog::OnTreeSelected)
  EVT_SIZE                 (              wxDirDialog::OnSize)
  EVT_BUTTON               (ID_OK,        wxDirDialog::OnOK)
  EVT_BUTTON               (ID_CANCEL,    wxDirDialog::OnCancel)
  EVT_BUTTON               (ID_NEW,       wxDirDialog::OnNew)
  EVT_TEXT_ENTER           (ID_TEXTCTRL,  wxDirDialog::OnOK)
  //  EVT_CHECKBOX             (ID_CHECK,     wxDirDialog::OnCheck)
END_EVENT_TABLE()

wxDirDialog::wxDirDialog(wxWindow *parent,
                         const wxString& message,
                         const wxString& defaultPath,
                         long style,
                         const wxPoint& pos)
           : wxDialog(parent, -1, message, pos, wxSize(300,300),
                      wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
    m_message = message;
    m_dialogStyle = style;
    m_parent = parent;

    m_path = defaultPath;

    wxBeginBusyCursor();

    wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );

    // 1) dir ctrl
    m_dir = new wxDirCtrl( this, ID_DIRCTRL, _T("/"),
                           wxDefaultPosition,
                           wxSize(200,200),
                           wxTR_HAS_BUTTONS |
                           wxSUNKEN_BORDER |
                           wxTR_EDIT_LABELS );
    topsizer->Add( m_dir, 1, wxTOP|wxLEFT|wxRIGHT | wxEXPAND, 10 );

    // 2) text ctrl
    m_input = new wxTextCtrl( this, ID_TEXTCTRL, m_path, wxDefaultPosition );
    topsizer->Add( m_input, 0, wxTOP|wxLEFT|wxRIGHT | wxEXPAND, 10 );

    // m_check = new wxCheckBox( this, ID_CHECK, _("Show hidden") );
    // m_check->SetValue(TRUE);

#if wxUSE_STATLINE
    // 3) static line
    topsizer->Add( new wxStaticLine( this, -1 ), 0, wxEXPAND | wxLEFT|wxRIGHT|wxTOP, 10 );
#endif

    // 4) buttons
    wxSizer* buttonsizer = new wxBoxSizer( wxHORIZONTAL );
    m_ok = new wxButton( this, ID_OK, _("OK") );
    buttonsizer->Add( m_ok, 0, wxLEFT|wxRIGHT, 10 );
    m_cancel = new wxButton( this, ID_CANCEL, _("Cancel") );
    buttonsizer->Add( m_cancel, 0, wxLEFT|wxRIGHT, 10 );
    m_new = new wxButton( this, ID_NEW, _("New...") );
    buttonsizer->Add( m_new, 0, wxLEFT|wxRIGHT, 10 );

    topsizer->Add( buttonsizer, 0, wxALL | wxCENTER, 10 );

    m_ok->SetDefault();
    m_dir->SetFocus();

    SetAutoLayout( TRUE );
    SetSizer( topsizer );

    topsizer->SetSizeHints( this );
    topsizer->Fit( this );

    Centre( wxBOTH );

    if (m_path == wxT("~"))
        wxGetHomeDir( &m_path );

    // choose the directory corresponding to defaultPath in the tree
    // VZ: using wxStringTokenizer is probably unsafe here (escaped slashes
    //     will not be processed correctly...)
    wxStringTokenizer tk(m_path, wxFILE_SEP_PATH, wxTOKEN_STRTOK);

    wxString path;

    long cookie = 0;
    // default to root dir
    wxTreeItemId item = m_dir->GetFirstChild(m_dir->GetRootItem(), cookie);
    
    if (!m_path.IsEmpty() && (m_path != wxT("/")) && (m_dir->m_paths.Count() > 1))
    {
        size_t count = m_dir->m_paths.GetCount();
        for ( size_t i=1; i<count; i++)
        {
            if (m_path.Find( m_dir->m_paths[i] ) == 0)
            {
                path = m_dir->m_paths[i];
                
                for (size_t j = 0; j < i; j++)
                   item = m_dir->GetNextChild(m_dir->GetRootItem(), cookie);
                
                wxStringTokenizer tk2(path, wxFILE_SEP_PATH, wxTOKEN_STRTOK);
                for (size_t h = 0; h < tk2.CountTokens(); h++)
                   tk.GetNextToken();
                
                break;
            }
        }
    }
    while ( tk.HasMoreTokens() && item.IsOk() )
    {
        path << wxFILE_SEP_PATH << tk.GetNextToken();

        m_dir->Expand(item);

        wxTreeItemId child = m_dir->GetFirstChild(item, cookie);
        while ( child.IsOk() )
        {
            wxDirItemData *data = (wxDirItemData*)m_dir->GetItemData(child);
            if ( data->m_path == path )
                break;

            child = m_dir->GetNextChild(item, cookie);
        }

        item = child;
    }

    if ( item.IsOk() )
    {
        m_dir->Expand(item);
        m_dir->SelectItem(item);
        m_dir->EnsureVisible(item);
    }

    wxEndBusyCursor();
}

int wxDirDialog::ShowModal()
{
    m_input->SetValue( m_path );
    return wxDialog::ShowModal();
}

void wxDirDialog::OnTreeSelected( wxTreeEvent &event )
{
    wxDirItemData *data = (wxDirItemData*)m_dir->GetItemData(event.GetItem());
    if (data)
       m_input->SetValue( data->m_path );
};

void wxDirDialog::OnTreeKeyDown( wxTreeEvent &WXUNUSED(event) )
{
    wxDirItemData *data = (wxDirItemData*)m_dir->GetItemData(m_dir->GetSelection());
    if (data)
        m_input->SetValue( data->m_path );
};

void wxDirDialog::OnOK( wxCommandEvent& WXUNUSED(event) )
{
  m_path = m_input->GetValue();
  // Does the path exist? (User may have typed anything in m_input)
  if (wxPathExists(m_path)) {
    // OK, path exists, we're done.
    EndModal(wxID_OK);
    return;
  }
  // Interact with user, find out if the dir is a typo or to be created
  wxString msg( _("The directory ") );
  msg = msg + m_path;
  msg = msg + _("\ndoes not exist\nCreate it now?") ;
  wxMessageDialog dialog(this, msg, _("Directory does not exist"), wxYES_NO | wxICON_WARNING );
  if ( dialog.ShowModal() == wxID_YES ) {
    // Okay, let's make it
    wxLogNull log;
    if (wxMkdir(m_path)) {
      // The new dir was created okay.
      EndModal(wxID_OK);
      return;
    }
    else {
      // Trouble...
      msg = _("Failed to create directory ")+m_path+
  _("\n(Do you have the required permissions?)");
      wxMessageDialog errmsg(this, msg, _("Error creating directory"), wxOK | wxICON_ERROR);
      errmsg.ShowModal();
      // We still don't have a valid dir. Back to the main dialog.
    }
  }
  // User has answered NO to create dir.
}

void wxDirDialog::OnCancel( wxCommandEvent& WXUNUSED(event) )
{
  EndModal(wxID_CANCEL);
}

void wxDirDialog::OnNew( wxCommandEvent& WXUNUSED(event) )
{
    wxTreeItemId id = m_dir->GetSelection();
    if ((id == m_dir->GetRootItem()) ||
        (m_dir->GetParent(id) == m_dir->GetRootItem()))
    {
        wxMessageDialog msg(this, _("You cannot add a new directory to this section."),
                            _("Create directory"), wxOK | wxICON_INFORMATION );
        msg.ShowModal();
        return;
    }

    wxTreeItemId parent = m_dir->GetParent( id );
    wxDirItemData *data = (wxDirItemData*)m_dir->GetItemData( parent );
    wxASSERT( data );

    wxString new_name( wxT("NewName") );
    wxString path( data->m_path );
    path += wxT("/");
    path += new_name;
    if (wxFileExists(path))
    {
        // try NewName0, NewName1 etc.
        int i = 0;
        do {
            new_name = wxT("NewName");
            wxString num;
            num.Printf( wxT("%d"), i );
            new_name += num;

            path = data->m_path;
            path += wxT("/");
            path += new_name;
            i++;
        } while (wxFileExists(path));
    }

    wxLogNull log;
    if (!wxMkdir(path))
    {
        wxMessageDialog dialog(this, _("Operation not permitted."), _("Error"), wxOK | wxICON_ERROR );
        dialog.ShowModal();
        return;
    }

    wxDirItemData *new_data = new wxDirItemData( path, new_name );
    wxTreeItemId new_id = m_dir->AppendItem( parent, new_name, 0, 1, new_data );
    m_dir->EnsureVisible( new_id );
    m_dir->EditLabel( new_id );
}

/*
void wxDirDialog::OnCheck( wxCommandEvent& WXUNUSED(event) )
{
  printf("Checkbox clicked: %s\n", ( m_check->GetValue() ? "on" : "off" ) );
}
*/

#endif // wxUSE_DIRDLG
