/////////////////////////////////////////////////////////////////////////////
// Name:        explorertreectrl.cpp
// Purpose:     wxExplorerTreeCtrl
// Author:      Julian Smart, Francesco Montorsi
// Modified by:
// Created:     2006-11-28
// RCS-ID:      $Id: explorertreectrl.cpp,v 1.5 2007/01/14 10:36:38 frm Exp $
// Copyright:   (c) Julian Smart, Francesco Montorsi
// Licence:     wxWidgets license
/////////////////////////////////////////////////////////////////////////////

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

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#include "wx/cshelp.h"
#include "wx/imaglist.h"
#include "wx/dir.h"
#include "wx/filename.h"
#include "wx/menu.h"
#include "wx/msgdlg.h"

#include "wx/explorertreectrl.h"


// ----------------------------------------------------------------------------
// bitmaps
// ----------------------------------------------------------------------------

// Include XPM icons
#include "bitmaps/openfolder.xpm"
#include "bitmaps/closedfolder.xpm"

#include "bitmaps/checked.xpm"
#include "bitmaps/unchecked.xpm"



// ----------------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------------

enum wxExplorerTreeCtrlContextMenu
{
    wxETCCM_CHECK,
    wxETCCM_UNCHECK
};

IMPLEMENT_CLASS(wxExplorerTreeEvent, wxTreeEvent)
DEFINE_EVENT_TYPE(wxEVT_EXPLORER_TREE_CHECK)


// ----------------------------------------------------------------------------
// wxExplorerTreeCtrl
// ----------------------------------------------------------------------------

IMPLEMENT_CLASS(wxExplorerTreeCtrl, wxTreeCtrl)
BEGIN_EVENT_TABLE(wxExplorerTreeCtrl, wxTreeCtrl)
    EVT_TREE_ITEM_EXPANDED(wxID_ANY, wxExplorerTreeCtrl::OnTreeItemExpanded)
    EVT_TREE_ITEM_COLLAPSED(wxID_ANY, wxExplorerTreeCtrl::OnTreeItemCollapsed)
    EVT_TREE_ITEM_MENU(wxID_ANY, wxExplorerTreeCtrl::OnTreeItemMenu)

    EVT_MOUSE_EVENTS(wxExplorerTreeCtrl::OnMouseEvent)
    EVT_MENU(wxID_ANY, wxExplorerTreeCtrl::OnMenuContext)

    EVT_CHAR(wxExplorerTreeCtrl::OnKeyDown)
END_EVENT_TABLE()

bool wxExplorerTreeCtrl::Create(wxWindow* parent, wxWindowID id, 
                                 const wxPoint& pt,
                                 const wxSize& sz, long style)
{
    if (!wxTreeCtrl::Create(parent, id, pt, sz, style))
        return false;

    m_menuDirContext = new wxMenu();
    m_menuFileContext = new wxMenu();

    m_menuDirContext->Append(wxETCCM_CHECK, wxT("Check recursively"));
    m_menuDirContext->Append(wxETCCM_UNCHECK, wxT("Uncheck recursively"));

    m_menuFileContext->Append(wxETCCM_CHECK, wxT("Check"));
    m_menuFileContext->Append(wxETCCM_UNCHECK, wxT("Uncheck"));

    LoadIcons();
    return true;
}

wxExplorerTreeCtrl::~wxExplorerTreeCtrl()
{
    wxDELETE(m_menuDirContext);
    wxDELETE(m_menuFileContext);
}

void wxExplorerTreeCtrl::LoadIcons()
{
    wxImageList *il = new wxImageList(16, 16, true);

    // be sure to add the icons respecting the values of the
    // wxExplorerTreeCtrlImageIndex enum !
    il->Add(wxIcon(closedfolder_xpm));
    il->Add(wxIcon(openfolder_xpm));
    il->Add(wxIcon(checked_xpm));
    il->Add(wxIcon(unchecked_xpm));

    AssignImageList(il);
}

bool wxExplorerTreeCtrl::Traverse(const wxString &path, const wxTreeItemId &parent)
{
    bool cont;

    wxDir dir(path);
    if (!dir.IsOpened())
        return false;

    // the name of this dir with path delimiter at the end
    wxChar sep = wxFileName::GetPathSeparator();
    wxString prefix = dir.GetName() + sep;

    // first, recurse into subdirs
    wxString dirname;
    for ( cont = dir.GetFirst(&dirname, wxEmptyString, wxDIR_DIRS|wxDIR_HIDDEN );
          cont;
          cont = cont && dir.GetNext(&dirname) )
    {
        const wxString fulldirname = prefix + dirname;

        // append this directory to the tree
        wxTreeItemId lastdir = AppendItem(parent, dirname.AfterLast(sep),
                                          wxETII_CLOSED_FOLDER, -1,
                                          new wxExplorerTreeItemData(m_arrDirs.GetCount()));
        m_arrDirs.Add(fulldirname);
        m_arrDirStatus.Add(false);

        if (!Traverse(fulldirname, lastdir))
            return false;
    }

    // now enum our own files
    wxString filename;
    cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES|wxDIR_HIDDEN);
    while ( cont )
    {
        if (m_arrFiles.GetCount() > 1000 && !m_bAlreadyWarned)
        {
            // these tree filling operation risks to be a very
            // long operation - warn the user
            int reply =
                wxMessageBox(wxT("The folder you've selected contains more than 1000 ")
                                wxT("files. Adding all the files to the tree control may ")
                                wxT("take a lot of time... should I continue?"),
                                wxT("Question"), wxYES_NO|wxICON_QUESTION);
            if (reply == wxNO)
            {
                // stop
                return false;
            }

            m_bAlreadyWarned = true;
        }

        // add the file 
        AppendItem(parent, filename.AfterLast(sep), wxETII_UNCHECKED_BOX, -1,
                   new wxExplorerTreeItemData(m_arrFiles.GetCount()));
        m_arrFiles.Add(prefix + filename);
        m_arrFileStatus.Add(false);

        // proceed
        cont = dir.GetNext(&filename);
    }

    return true;
}

void wxExplorerTreeCtrl::SetPath(const wxString &path)
{
    // remove old contents
    DeleteAllItems();
    
    m_arrFiles.Clear();
    m_arrFileStatus.Clear();
    m_arrDirs.Clear();
    m_arrDirStatus.Clear();

    m_bAlreadyWarned = false;

    // add the root of the tree control
    wxFileName realpath = wxFileName::DirName(path);
    if (realpath.GetDirs().GetCount() == 0)
        return;

    m_strCurrentPath = realpath.GetPath();
    m_strPathPrefix = realpath.GetPath().BeforeLast(wxFileName::GetPathSeparator());

    wxTreeItemId root = AddRoot(realpath.GetDirs().Last(), 
                                wxETII_CLOSED_FOLDER, -1,
                                new wxExplorerTreeItemData(m_arrDirs.GetCount()));
    m_arrDirs.Add(m_strCurrentPath);
    m_arrDirStatus.Add(false);

    // recursively traverse given path
    Traverse(path, root);
}

void wxExplorerTreeCtrl::RefreshContents()
{
    // this will delete all current items and reload them
    SetPath(m_strCurrentPath);
}

void wxExplorerTreeCtrl::DoCheck(const wxTreeItemId &item, bool check)
{
    switch (GetItemImage(item))
    {
    case wxETII_CLOSED_FOLDER:
    case wxETII_OPEN_FOLDER:
        {
            wxTreeItemIdValue cookie;
            for (wxTreeItemId i = GetFirstChild(item, cookie);
                 i.IsOk();
                 i = GetNextChild(item, cookie))
            {
                // recursively check this folder's item
                Check(i, check);
            }

            SetItemBold(item, check);
        }
        break;

    case wxETII_CHECKED_BOX:
    case wxETII_UNCHECKED_BOX:
        {
            size_t idx = GetItemData(item)->m_index;
            m_arrFileStatus[idx] = check;

            SetItemImage(item, check ? wxETII_CHECKED_BOX : wxETII_UNCHECKED_BOX);
            SetItemBold(item, check);
        }
        break;
    }
}

void wxExplorerTreeCtrl::UpdateParentStatus(const wxTreeItemId &item)
{
    wxTreeItemId parent = GetItemParent(item);
    if (!parent.IsOk())
        return;

    // obviously parent must be a folder item:
    wxASSERT(IsFolder(parent));

    // consider the "bold" status of all parent's children as up2date
    bool allbold = true;
    wxTreeItemIdValue cookie;
    for (wxTreeItemId i = GetFirstChild(parent, cookie);
         i.IsOk();
         i = GetNextChild(parent, cookie))
    {
        allbold &= IsBold(i);
    }

    size_t idx = GetItemData(parent)->m_index;
    m_arrDirStatus[idx] = allbold;
    SetItemBold(parent, allbold);

    // recursively update parents of this parent
    UpdateParentStatus(parent);
}

void wxExplorerTreeCtrl::Check(const wxTreeItemId &item, bool check)
{
    DoCheck(item, check);

    // does the folder containing "item" needs to be (un)bolded?
    UpdateParentStatus(item);
}

bool wxExplorerTreeCtrl::IsChecked(const wxTreeItemId &item, bool checked) const
{
    /*
    size_t idx = GetItemData(item)->m_index;
    if (IsFolder(item))
        return m_arrDirStatus[idx] == (int)checked;
    return m_arrFileStatus[idx] == (int)checked;

    SEEMS NOT TO WORK CORRECTLY FIXME
    */
    return RecursiveIsChecked(item, checked);
}

bool wxExplorerTreeCtrl::RecursiveIsChecked(const wxTreeItemId &item, bool checked) const
{
    // slow code which does recursively query status

    switch (GetItemImage(item))
    {
    case wxETII_UNCHECKED_BOX:
        return checked ? false : true;
    case wxETII_CHECKED_BOX:
        return checked ? true : false;

    case wxETII_OPEN_FOLDER:
    case wxETII_CLOSED_FOLDER:
        {
            wxTreeItemIdValue cookie;
            for (wxTreeItemId i = GetFirstChild(item, cookie);
                 i.IsOk();
                 i = GetNextChild(item, cookie))
            {
                if (!IsChecked(i, checked))
                    return false;
            }

            // all the items of this folder are (recursively) checked
            // (if checked==true) or unchecked (if checked==false)
            return true;
        }

    default:        // avoid dummy warnings
        return false;
    }
}

void wxExplorerTreeCtrl::Toggle(const wxTreeItemId &item)
{
    switch (GetItemImage(item))
    {
    case wxETII_UNCHECKED_BOX:
        Check(item);
        break;
    case wxETII_CHECKED_BOX:
        UnCheck(item);
        break;

        // "toggling" a folder means expanding/collapsing it

    case wxETII_OPEN_FOLDER:
        Collapse(item);
        break;
    case wxETII_CLOSED_FOLDER:
        Expand(item);
        break;
    }
}

bool wxExplorerTreeCtrl::IsFolder(const wxTreeItemId &item) const
{
    switch (GetItemImage(item))
    {
    case wxETII_OPEN_FOLDER:
    case wxETII_CLOSED_FOLDER:
        return true;

    default:
        return false;
    }
}

wxString wxExplorerTreeCtrl::GetItemFullPath(const wxTreeItemId &item,
                                             wxPathFormat format) const
{
    if (GetRootItem() == item)
        return GetItemText(GetRootItem());

    const wxTreeItemId &parent = GetItemParent(item);
    if (parent.IsOk())
    {
        // recursively concatenate the path for parent to the text of this item
        return GetItemFullPath(parent, format) + 
               wxFileName::GetPathSeparator(format) +
               GetItemText(item);
    }

    return GetItemText(item);
}

wxArrayString wxExplorerTreeCtrl::GetFilesChecked(bool checked) const
{
    wxArrayString ret;
    for (size_t i=0; i < m_arrFiles.GetCount(); i++)
        if (m_arrFileStatus[i] == (int)checked)
            ret.Add(m_arrFiles[i]);

    return ret;
}

wxArrayString wxExplorerTreeCtrl::GetDirsChecked(bool checked) const
{
    wxArrayString ret;
    for (size_t i=0; i < m_arrDirs.GetCount(); i++)
        if (m_arrDirStatus[i] == (int)checked)
            ret.Add(m_arrDirs[i]);

    return ret;
}

wxArrayString wxExplorerTreeCtrl::GetDirsRecursivelyUnChecked() const
{
    wxArrayString ret;
    wxTreeItemId root = GetRootItem();

    if (RecursiveIsChecked(root, false))
    {
        ret.Add(GetItemText(root));
        return ret;
    }

    DoGetDirsRecursivelyUnChecked(ret, root, GetItemText(root) +
                                  wxFileName::GetPathSeparator());
    return ret;
}

void wxExplorerTreeCtrl::DoGetDirsRecursivelyUnChecked(wxArrayString &ret,
                                                       const wxTreeItemId &item,
                                                       const wxString &prefix) const
{
    // differently from GetDirsChecked() here we use the IsChecked()
    // function which is slower but works recursively as we need
    wxTreeItemIdValue cookie;
    for (wxTreeItemId i = GetFirstChild(item, cookie);
         i.IsOk();
         i = GetNextChild(item, cookie))
    {
        if (!IsFolder(i))
            continue;       // skip files

        // don't use GetItemFullPath() for better performances
        wxString dir = prefix + GetItemText(i) + wxFileName::GetPathSeparator();

        if (RecursiveIsChecked(i, false))
            ret.Add(dir);
        else
            DoGetDirsRecursivelyUnChecked(ret, i, dir);
    }
}

wxTreeItemId wxExplorerTreeCtrl::FindItem(const wxString &fullpath,
                                          wxPathFormat format) const
{
    wxTreeItemId root = GetRootItem();
    wxString str = GetItemText(root);
    wxChar sep = wxFileName::GetPathSeparator(format);

    if (!fullpath.StartsWith(str))
        return wxTreeItemId();

    if (fullpath.AfterFirst(sep).IsEmpty())
        return root;

    return DoFindItem(root, fullpath.AfterFirst(sep), sep);
}

wxTreeItemId wxExplorerTreeCtrl::DoFindItem(const wxTreeItemId &item,
                                            const wxString &fullpath,
                                            const wxChar sep) const
{
    wxTreeItemIdValue cookie;
    for (wxTreeItemId i = GetFirstChild(item, cookie);
         i.IsOk();
         i = GetNextChild(item, cookie))
    {
        if (GetItemText(i) == fullpath.BeforeFirst(sep))
        {
            wxString str = fullpath.AfterFirst(sep);
            if (str.IsEmpty())
                return i;
            return DoFindItem(i, str, sep);
        }            
    }

    // build an invalid tree item ID
    return wxTreeItemId();
}



// ----------------------------------------------------------------------------
// wxExplorerTreeCtrl - event handlers
// ----------------------------------------------------------------------------

void wxExplorerTreeCtrl::OnTreeItemExpanded(wxTreeEvent& event)
{
    const wxTreeItemId &item = event.GetItem();
    if (GetItemImage(item) == wxETII_CLOSED_FOLDER)
        SetItemImage(item, wxETII_OPEN_FOLDER);

    event.Skip();       // let others handle this event, too
}

void wxExplorerTreeCtrl::OnTreeItemCollapsed(wxTreeEvent& event)
{
    const wxTreeItemId &item = event.GetItem();
    if (GetItemImage(item) == wxETII_OPEN_FOLDER)
        SetItemImage(item, wxETII_CLOSED_FOLDER);

    event.Skip();       // let others handle this event, too
}

void wxExplorerTreeCtrl::OnMouseEvent(wxMouseEvent& event)
{
    if (!event.LeftDown())
    {
        event.Skip();       // let others handle this event, too
        return;
    }

    int flags = 0;
    const wxTreeItemId &item = HitTest(wxPoint(event.GetX(), event.GetY()), flags);

    if (flags & wxTREE_HITTEST_ONITEMICON)
    {
        Toggle(item);

        // something has surely changed: send an event
        wxExplorerTreeEvent ev(wxEVT_EXPLORER_TREE_CHECK, this, item, IsChecked(item));
        if (GetEventHandler())
            GetEventHandler()->ProcessEvent(ev);
    }
    
    event.Skip();       // let others handle this event, too
}

void wxExplorerTreeCtrl::OnTreeItemMenu(wxTreeEvent& event)
{
    m_currentItem = event.GetItem();
    switch (GetItemImage(m_currentItem))
    {
    case wxETII_OPEN_FOLDER:
    case wxETII_CLOSED_FOLDER:
        PopupMenu(m_menuDirContext, event.GetPoint());
        break;

    case wxETII_UNCHECKED_BOX:
    case wxETII_CHECKED_BOX:
        PopupMenu(m_menuFileContext, event.GetPoint());
        break;
    }
}

void wxExplorerTreeCtrl::OnMenuContext(wxCommandEvent& event)
{
    bool oldstatus = IsChecked(m_currentItem);

    switch (event.GetId())
    {
    case wxETCCM_CHECK:
        Check(m_currentItem);
        break;

    case wxETCCM_UNCHECK:
        UnCheck(m_currentItem);
        break;

    default:
        event.Skip();
        return;
    }

    //  send an event if something changed...
    if (IsChecked(m_currentItem) != oldstatus)
    {
        wxExplorerTreeEvent ev(wxEVT_EXPLORER_TREE_CHECK, this, m_currentItem, !oldstatus);
        if (GetEventHandler())
            GetEventHandler()->ProcessEvent(ev);
    }
}

void wxExplorerTreeCtrl::OnKeyDown(wxKeyEvent& event)
{
    if (event.GetKeyCode() == WXK_SPACE)
    {
        wxTreeItemId item = GetSelection();
        if (item.IsOk())
        {
            Toggle(item);

            // something has surely changed: send an event
            wxExplorerTreeEvent ev(wxEVT_EXPLORER_TREE_CHECK, this, item, IsChecked(item));
            if (GetEventHandler())
                GetEventHandler()->ProcessEvent(ev);
        }
    }
    else
        event.Skip();
}

