/////////////////////////////////////////////////////////////////////////////
// Name:        src/common/treebase.cpp
// Purpose:     Base wxTreeCtrl classes
// Author:      Julian Smart
// Created:     01/02/97
// Modified:
// Copyright:   (c) 1998 Robert Roebling, Julian Smart et al
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// =============================================================================
// declarations
// =============================================================================

// -----------------------------------------------------------------------------
// headers
// -----------------------------------------------------------------------------

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

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if wxUSE_TREECTRL

#include "wx/treectrl.h"
#include "wx/imaglist.h"

extern WXDLLEXPORT_DATA(const char) wxTreeCtrlNameStr[] = "treeCtrl";

// ----------------------------------------------------------------------------
// events
// ----------------------------------------------------------------------------

wxDEFINE_EVENT( wxEVT_TREE_BEGIN_DRAG, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_BEGIN_RDRAG, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_BEGIN_LABEL_EDIT, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_END_LABEL_EDIT, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_DELETE_ITEM, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_GET_INFO, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_SET_INFO, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_EXPANDED, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_EXPANDING, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_COLLAPSED, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_COLLAPSING, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_SEL_CHANGED, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_SEL_CHANGING, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_KEY_DOWN, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_ACTIVATED, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_RIGHT_CLICK, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_MIDDLE_CLICK, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_END_DRAG, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_STATE_IMAGE_CLICK, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_GETTOOLTIP, wxTreeEvent );
wxDEFINE_EVENT( wxEVT_TREE_ITEM_MENU, wxTreeEvent );

// ----------------------------------------------------------------------------
// XTI
// ----------------------------------------------------------------------------

wxDEFINE_FLAGS( wxTreeCtrlStyle )
wxBEGIN_FLAGS( wxTreeCtrlStyle )
// new style border flags, we put them first to
// use them for streaming out
wxFLAGS_MEMBER(wxBORDER_SIMPLE)
wxFLAGS_MEMBER(wxBORDER_SUNKEN)
wxFLAGS_MEMBER(wxBORDER_DOUBLE)
wxFLAGS_MEMBER(wxBORDER_RAISED)
wxFLAGS_MEMBER(wxBORDER_STATIC)
wxFLAGS_MEMBER(wxBORDER_NONE)

// old style border flags
wxFLAGS_MEMBER(wxSIMPLE_BORDER)
wxFLAGS_MEMBER(wxSUNKEN_BORDER)
wxFLAGS_MEMBER(wxDOUBLE_BORDER)
wxFLAGS_MEMBER(wxRAISED_BORDER)
wxFLAGS_MEMBER(wxSTATIC_BORDER)
wxFLAGS_MEMBER(wxBORDER)

// standard window styles
wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
wxFLAGS_MEMBER(wxCLIP_CHILDREN)
wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
wxFLAGS_MEMBER(wxWANTS_CHARS)
wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
wxFLAGS_MEMBER(wxVSCROLL)
wxFLAGS_MEMBER(wxHSCROLL)

wxFLAGS_MEMBER(wxTR_EDIT_LABELS)
wxFLAGS_MEMBER(wxTR_NO_BUTTONS)
wxFLAGS_MEMBER(wxTR_HAS_BUTTONS)
wxFLAGS_MEMBER(wxTR_TWIST_BUTTONS)
wxFLAGS_MEMBER(wxTR_NO_LINES)
wxFLAGS_MEMBER(wxTR_FULL_ROW_HIGHLIGHT)
wxFLAGS_MEMBER(wxTR_LINES_AT_ROOT)
wxFLAGS_MEMBER(wxTR_HIDE_ROOT)
wxFLAGS_MEMBER(wxTR_ROW_LINES)
wxFLAGS_MEMBER(wxTR_HAS_VARIABLE_ROW_HEIGHT)
wxFLAGS_MEMBER(wxTR_SINGLE)
wxFLAGS_MEMBER(wxTR_MULTIPLE)
#if WXWIN_COMPATIBILITY_2_8
wxFLAGS_MEMBER(wxTR_EXTENDED)
#endif
wxFLAGS_MEMBER(wxTR_DEFAULT_STYLE)
wxEND_FLAGS( wxTreeCtrlStyle )

wxIMPLEMENT_DYNAMIC_CLASS_XTI(wxTreeCtrl, wxControl, "wx/treectrl.h")

wxBEGIN_PROPERTIES_TABLE(wxTreeCtrl)
wxEVENT_PROPERTY( TextUpdated, wxEVT_TEXT, wxCommandEvent )
wxEVENT_RANGE_PROPERTY( TreeEvent, wxEVT_TREE_BEGIN_DRAG, \
                       wxEVT_TREE_STATE_IMAGE_CLICK, wxTreeEvent )

wxPROPERTY_FLAGS( WindowStyle, wxTreeCtrlStyle, long, SetWindowStyleFlag, \
                 GetWindowStyleFlag, wxEMPTY_PARAMETER_VALUE, 0 /*flags*/, \
                 wxT("Helpstring"), wxT("group")) // style
wxEND_PROPERTIES_TABLE()

wxEMPTY_HANDLERS_TABLE(wxTreeCtrl)

wxCONSTRUCTOR_5( wxTreeCtrl, wxWindow*, Parent, wxWindowID, Id, \
                wxPoint, Position, wxSize, Size, long, WindowStyle )

// ----------------------------------------------------------------------------
// Tree event
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS(wxTreeEvent, wxNotifyEvent)

wxTreeEvent::wxTreeEvent(wxEventType commandType,
                         wxTreeCtrlBase *tree,
                         const wxTreeItemId& item)
           : wxNotifyEvent(commandType, tree->GetId()),
             m_item(item)
{
    m_editCancelled = false;

    SetEventObject(tree);

    if ( item.IsOk() )
        SetClientObject(tree->GetItemData(item));
}

wxTreeEvent::wxTreeEvent(wxEventType commandType, int id)
           : wxNotifyEvent(commandType, id)
{
    m_itemOld = 0l;
    m_editCancelled = false;
}

wxTreeEvent::wxTreeEvent(const wxTreeEvent & event)
           : wxNotifyEvent(event)
{
    m_evtKey = event.m_evtKey;
    m_item = event.m_item;
    m_itemOld = event.m_itemOld;
    m_pointDrag = event.m_pointDrag;
    m_label = event.m_label;
    m_editCancelled = event.m_editCancelled;
}

// ----------------------------------------------------------------------------
// wxTreeCtrlBase
// ----------------------------------------------------------------------------

wxTreeCtrlBase::wxTreeCtrlBase()
{
    m_imageListNormal =
    m_imageListState = NULL;
    m_ownsImageListNormal =
    m_ownsImageListState = false;

    // arbitrary default
    m_spacing = 18;

    // quick DoGetBestSize calculation
    m_quickBestSize = true;

    Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(wxTreeCtrlBase::OnCharHook));
}

wxTreeCtrlBase::~wxTreeCtrlBase()
{
    if (m_ownsImageListNormal)
        delete m_imageListNormal;
    if (m_ownsImageListState)
        delete m_imageListState;
}

void wxTreeCtrlBase::SetItemState(const wxTreeItemId& item, int state)
{
    if ( state == wxTREE_ITEMSTATE_NEXT )
    {
        int current = GetItemState(item);
        if ( current == wxTREE_ITEMSTATE_NONE )
            return;
        state = current + 1;
        if ( m_imageListState && state >= m_imageListState->GetImageCount() )
            state = 0;
    }
    else if ( state == wxTREE_ITEMSTATE_PREV )
    {
        int current = GetItemState(item);
        if ( current == wxTREE_ITEMSTATE_NONE )
            return;
        state = current - 1;
        if ( state == -1 )
            state = m_imageListState ? m_imageListState->GetImageCount() - 1 : 0;
    }
    // else: wxTREE_ITEMSTATE_NONE depending on platform

    DoSetItemState(item, state);
}

static void
wxGetBestTreeSize(const wxTreeCtrlBase* treeCtrl, wxTreeItemId id, wxSize& size)
{
    wxRect rect;

    if ( treeCtrl->GetBoundingRect(id, rect, true /* just the item */) )
    {
        // Translate to logical position so we get the full extent
#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
        rect.x += treeCtrl->GetScrollPos(wxHORIZONTAL);
        rect.y += treeCtrl->GetScrollPos(wxVERTICAL);
#endif

        size.IncTo(wxSize(rect.GetRight(), rect.GetBottom()));
    }

    wxTreeItemIdValue cookie;
    for ( wxTreeItemId item = treeCtrl->GetFirstChild(id, cookie);
          item.IsOk();
          item = treeCtrl->GetNextChild(id, cookie) )
    {
        wxGetBestTreeSize(treeCtrl, item, size);
    }
}

wxSize wxTreeCtrlBase::DoGetBestSize() const
{
    wxSize size;

    // this doesn't really compute the total bounding rectangle of all items
    // but a not too bad guess of it which has the advantage of not having to
    // examine all (potentially hundreds or thousands) items in the control

    if (GetQuickBestSize())
    {
        for ( wxTreeItemId item = GetRootItem();
              item.IsOk();
              item = GetLastChild(item) )
        {
            wxRect rect;

            // last parameter is "true" to get only the dimensions of the text
            // label, we don't want to get the entire item width as it's determined
            // by the current size
            if ( GetBoundingRect(item, rect, true) )
            {
                if ( size.x < rect.x + rect.width )
                    size.x = rect.x + rect.width;
                if ( size.y < rect.y + rect.height )
                    size.y = rect.y + rect.height;
            }
        }
    }
    else // use precise, if potentially slow, size computation method
    {
        // iterate over all items recursively
        wxTreeItemId idRoot = GetRootItem();
        if ( idRoot.IsOk() )
            wxGetBestTreeSize(this, idRoot, size);
    }

    // need some minimal size even for empty tree
    if ( !size.x || !size.y )
        size = wxControl::DoGetBestSize();
    else
    {
        // Add border size
        size += GetWindowBorderSize();

        CacheBestSize(size);
    }

    return size;
}

void wxTreeCtrlBase::ExpandAll()
{
    if ( IsEmpty() )
        return;

    ExpandAllChildren(GetRootItem());
}

void wxTreeCtrlBase::ExpandAllChildren(const wxTreeItemId& item)
{
    Freeze();
    // expand this item first, this might result in its children being added on
    // the fly
    if ( item != GetRootItem() || !HasFlag(wxTR_HIDE_ROOT) )
        Expand(item);
    //else: expanding hidden root item is unsupported and unnecessary

    // then (recursively) expand all the children
    wxTreeItemIdValue cookie;
    for ( wxTreeItemId idCurr = GetFirstChild(item, cookie);
          idCurr.IsOk();
          idCurr = GetNextChild(item, cookie) )
    {
        ExpandAllChildren(idCurr);
    }
    Thaw();
}

void wxTreeCtrlBase::CollapseAll()
{
    if ( IsEmpty() )
        return;

    CollapseAllChildren(GetRootItem());
}

void wxTreeCtrlBase::CollapseAllChildren(const wxTreeItemId& item)
{
    Freeze();
    // first (recursively) collapse all the children
    wxTreeItemIdValue cookie;
    for ( wxTreeItemId idCurr = GetFirstChild(item, cookie);
          idCurr.IsOk();
          idCurr = GetNextChild(item, cookie) )
    {
        CollapseAllChildren(idCurr);
    }

    // then collapse this element too unless it's the hidden root which can't
    // be collapsed
    if ( item != GetRootItem() || !HasFlag(wxTR_HIDE_ROOT) )
        Collapse(item);
    Thaw();
}

bool wxTreeCtrlBase::IsEmpty() const
{
    return !GetRootItem().IsOk();
}

void wxTreeCtrlBase::OnCharHook(wxKeyEvent& event)
{
    if ( GetEditControl() )
    {
        bool discardChanges = false;
        switch ( event.GetKeyCode() )
        {
            case WXK_ESCAPE:
                discardChanges = true;
                // fall through

            case WXK_RETURN:
                EndEditLabel(GetFocusedItem(), discardChanges);

                // Do not call Skip() below.
                return;
        }
    }

    event.Skip();
}

#endif // wxUSE_TREECTRL
