+/////////////////////////////////////////////////////////////////////////////
+// Name: src/msw/treectrl.cpp
+// Purpose: wxTreeCtrl
+// Author: Julian Smart
+// Modified by: Vadim Zeitlin to be less MSW-specific on 10.10.98
+// Created: 1997
+// RCS-ID: $Id$
+// Copyright: (c) Julian Smart
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// ============================================================================
+// declarations
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
+
+#ifdef __GNUG__
+ #pragma implementation "treectrl.h"
+#endif
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+ #pragma hdrstop
+#endif
+
+#if wxUSE_TREECTRL
+
+#include "wx/msw/private.h"
+
+// Set this to 1 to be _absolutely_ sure that repainting will work for all
+// comctl32.dll versions
+#define wxUSE_COMCTL32_SAFELY 0
+
+// Mingw32 is a bit mental even though this is done in winundef
+#ifdef GetFirstChild
+ #undef GetFirstChild
+#endif
+
+#ifdef GetNextSibling
+ #undef GetNextSibling
+#endif
+
+#if defined(__WIN95__)
+
+#include "wx/app.h"
+#include "wx/log.h"
+#include "wx/dynarray.h"
+#include "wx/imaglist.h"
+#include "wx/settings.h"
+#include "wx/msw/treectrl.h"
+#include "wx/msw/dragimag.h"
+
+#ifdef __GNUWIN32_OLD__
+ #include "wx/msw/gnuwin32/extra.h"
+#endif
+
+#if defined(__WIN95__) && !((defined(__GNUWIN32_OLD__) || defined(__TWIN32__)) && !defined(__CYGWIN10__))
+ #include <commctrl.h>
+#endif
+
+// Bug in headers, sometimes
+#ifndef TVIS_FOCUSED
+ #define TVIS_FOCUSED 0x0001
+#endif
+
+#ifndef TV_FIRST
+ #define TV_FIRST 0x1100
+#endif
+
+#ifndef TVS_CHECKBOXES
+ #define TVS_CHECKBOXES 0x0100
+#endif
+
+#ifndef TVS_FULLROWSELECT
+ #define TVS_FULLROWSELECT 0x1000
+#endif
+
+// old headers might miss these messages (comctl32.dll 4.71+ only)
+#ifndef TVM_SETBKCOLOR
+ #define TVM_SETBKCOLOR (TV_FIRST + 29)
+ #define TVM_SETTEXTCOLOR (TV_FIRST + 30)
+#endif
+
+// a macro to hide the ugliness of nested casts
+#define HITEM(item) (HTREEITEM)(WXHTREEITEM)(item)
+
+// the native control doesn't support multiple selections under MSW and we
+// have 2 ways to emulate them: either using TVS_CHECKBOXES style and let
+// checkboxes be the selection status (checked == selected) or by really
+// emulating everything, i.e. intercepting mouse and key events &c. The first
+// approach is much easier but doesn't work with comctl32.dll < 4.71 and also
+// looks quite ugly.
+#define wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE 0
+
+// ----------------------------------------------------------------------------
+// private functions
+// ----------------------------------------------------------------------------
+
+// wrapper for TreeView_HitTest
+static HTREEITEM GetItemFromPoint(HWND hwndTV, int x, int y)
+{
+ TV_HITTESTINFO tvht;
+ tvht.pt.x = x;
+ tvht.pt.y = y;
+
+ return (HTREEITEM)TreeView_HitTest(hwndTV, &tvht);
+}
+
+#if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+
+// wrappers for TreeView_GetItem/TreeView_SetItem
+static bool IsItemSelected(HWND hwndTV, HTREEITEM hItem)
+{
+
+ TV_ITEM tvi;
+ tvi.mask = TVIF_STATE | TVIF_HANDLE;
+ tvi.stateMask = TVIS_SELECTED;
+ tvi.hItem = hItem;
+
+ if ( !TreeView_GetItem(hwndTV, &tvi) )
+ {
+ wxLogLastError(wxT("TreeView_GetItem"));
+ }
+
+ return (tvi.state & TVIS_SELECTED) != 0;
+}
+
+static void SelectItem(HWND hwndTV, HTREEITEM hItem, bool select = TRUE)
+{
+ TV_ITEM tvi;
+ tvi.mask = TVIF_STATE | TVIF_HANDLE;
+ tvi.stateMask = TVIS_SELECTED;
+ tvi.state = select ? TVIS_SELECTED : 0;
+ tvi.hItem = hItem;
+
+ if ( TreeView_SetItem(hwndTV, &tvi) == -1 )
+ {
+ wxLogLastError(wxT("TreeView_SetItem"));
+ }
+}
+
+static inline void UnselectItem(HWND hwndTV, HTREEITEM htItem)
+{
+ SelectItem(hwndTV, htItem, FALSE);
+}
+
+static inline void ToggleItemSelection(HWND hwndTV, HTREEITEM htItem)
+{
+ SelectItem(hwndTV, htItem, !IsItemSelected(hwndTV, htItem));
+}
+
+// helper function which selects all items in a range and, optionally,
+// unselects all others
+static void SelectRange(HWND hwndTV,
+ HTREEITEM htFirst,
+ HTREEITEM htLast,
+ bool unselectOthers = TRUE)
+{
+ // find the first (or last) item and select it
+ bool cont = TRUE;
+ HTREEITEM htItem = (HTREEITEM)TreeView_GetRoot(hwndTV);
+ while ( htItem && cont )
+ {
+ if ( (htItem == htFirst) || (htItem == htLast) )
+ {
+ if ( !IsItemSelected(hwndTV, htItem) )
+ {
+ SelectItem(hwndTV, htItem);
+ }
+
+ cont = FALSE;
+ }
+ else
+ {
+ if ( unselectOthers && IsItemSelected(hwndTV, htItem) )
+ {
+ UnselectItem(hwndTV, htItem);
+ }
+ }
+
+ htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
+ }
+
+ // select the items in range
+ cont = htFirst != htLast;
+ while ( htItem && cont )
+ {
+ if ( !IsItemSelected(hwndTV, htItem) )
+ {
+ SelectItem(hwndTV, htItem);
+ }
+
+ cont = (htItem != htFirst) && (htItem != htLast);
+
+ htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
+ }
+
+ // unselect the rest
+ if ( unselectOthers )
+ {
+ while ( htItem )
+ {
+ if ( IsItemSelected(hwndTV, htItem) )
+ {
+ UnselectItem(hwndTV, htItem);
+ }
+
+ htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
+ }
+ }
+
+ // seems to be necessary - otherwise the just selected items don't always
+ // appear as selected
+ UpdateWindow(hwndTV);
+}
+
+// helper function which tricks the standard control into changing the focused
+// item without changing anything else (if someone knows why Microsoft doesn't
+// allow to do it by just setting TVIS_FOCUSED flag, please tell me!)
+static void SetFocus(HWND hwndTV, HTREEITEM htItem)
+{
+ // the current focus
+ HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(hwndTV);
+
+ if ( htItem )
+ {
+ // set the focus
+ if ( htItem != htFocus )
+ {
+ // remember the selection state of the item
+ bool wasSelected = IsItemSelected(hwndTV, htItem);
+
+ if ( htFocus && IsItemSelected(hwndTV, htFocus) )
+ {
+ // prevent the tree from unselecting the old focus which it
+ // would do by default (TreeView_SelectItem unselects the
+ // focused item)
+ TreeView_SelectItem(hwndTV, 0);
+ SelectItem(hwndTV, htFocus);
+ }
+
+ TreeView_SelectItem(hwndTV, htItem);
+
+ if ( !wasSelected )
+ {
+ // need to clear the selection which TreeView_SelectItem() gave
+ // us
+ UnselectItem(hwndTV, htItem);
+ }
+ //else: was selected, still selected - ok
+ }
+ //else: nothing to do, focus already there
+ }
+ else
+ {
+ if ( htFocus )
+ {
+ bool wasFocusSelected = IsItemSelected(hwndTV, htFocus);
+
+ // just clear the focus
+ TreeView_SelectItem(hwndTV, 0);
+
+ if ( wasFocusSelected )
+ {
+ // restore the selection state
+ SelectItem(hwndTV, htFocus);
+ }
+ }
+ //else: nothing to do, no focus already
+ }
+}
+
+#endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+
+// ----------------------------------------------------------------------------
+// private classes
+// ----------------------------------------------------------------------------
+
+// a convenient wrapper around TV_ITEM struct which adds a ctor
+#ifdef __VISUALC__
+#pragma warning( disable : 4097 ) // inheriting from typedef
+#endif
+
+struct wxTreeViewItem : public TV_ITEM
+{
+ wxTreeViewItem(const wxTreeItemId& item, // the item handle
+ UINT mask_, // fields which are valid
+ UINT stateMask_ = 0) // for TVIF_STATE only
+ {
+ wxZeroMemory(*this);
+
+ // hItem member is always valid
+ mask = mask_ | TVIF_HANDLE;
+ stateMask = stateMask_;
+ hItem = HITEM(item);
+ }
+};
+
+// wxVirutalNode is used in place of a single root when 'hidden' root is
+// specified.
+class wxVirtualNode : public wxTreeViewItem
+{
+public:
+ wxVirtualNode(wxTreeItemData *data)
+ : wxTreeViewItem(TVI_ROOT, 0)
+ {
+ m_data = data;
+ }
+
+ ~wxVirtualNode()
+ {
+ delete m_data;
+ }
+
+ wxTreeItemData *GetData() const { return m_data; }
+ void SetData(wxTreeItemData *data) { delete m_data; m_data = data; }
+
+private:
+ wxTreeItemData *m_data;
+};
+
+#ifdef __VISUALC__
+#pragma warning( default : 4097 )
+#endif
+
+// a macro to get the virtual root, returns NULL if none
+#define GET_VIRTUAL_ROOT() ((wxVirtualNode *)m_pVirtualRoot)
+
+// returns TRUE if the item is the virtual root
+#define IS_VIRTUAL_ROOT(item) (HITEM(item) == TVI_ROOT)
+
+// a class which encapsulates the tree traversal logic: it vists all (unless
+// OnVisit() returns FALSE) items under the given one
+class wxTreeTraversal
+{
+public:
+ wxTreeTraversal(const wxTreeCtrl *tree)
+ {
+ m_tree = tree;
+ }
+
+ // do traverse the tree: visit all items (recursively by default) under the
+ // given one; return TRUE if all items were traversed or FALSE if the
+ // traversal was aborted because OnVisit returned FALSE
+ bool DoTraverse(const wxTreeItemId& root, bool recursively = TRUE);
+
+ // override this function to do whatever is needed for each item, return
+ // FALSE to stop traversing
+ virtual bool OnVisit(const wxTreeItemId& item) = 0;
+
+protected:
+ const wxTreeCtrl *GetTree() const { return m_tree; }
+
+private:
+ bool Traverse(const wxTreeItemId& root, bool recursively);
+
+ const wxTreeCtrl *m_tree;
+};
+
+// internal class for getting the selected items
+class TraverseSelections : public wxTreeTraversal
+{
+public:
+ TraverseSelections(const wxTreeCtrl *tree,
+ wxArrayTreeItemIds& selections)
+ : wxTreeTraversal(tree), m_selections(selections)
+ {
+ m_selections.Empty();
+
+ DoTraverse(tree->GetRootItem());
+ }
+
+ virtual bool OnVisit(const wxTreeItemId& item)
+ {
+ // can't visit a virtual node.
+ if ( (GetTree()->GetRootItem() == item) && (GetTree()->GetWindowStyle() & wxTR_HIDE_ROOT))
+ {
+ return TRUE;
+ }
+
+#if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+ if ( GetTree()->IsItemChecked(item) )
+#else
+ if ( ::IsItemSelected(GetHwndOf(GetTree()), HITEM(item)) )
+#endif
+ {
+ m_selections.Add(item);
+ }
+
+ return TRUE;
+ }
+
+ size_t GetCount() const { return m_selections.GetCount(); }
+
+private:
+ wxArrayTreeItemIds& m_selections;
+};
+
+// internal class for counting tree items
+class TraverseCounter : public wxTreeTraversal
+{
+public:
+ TraverseCounter(const wxTreeCtrl *tree,
+ const wxTreeItemId& root,
+ bool recursively)
+ : wxTreeTraversal(tree)
+ {
+ m_count = 0;
+
+ DoTraverse(root, recursively);
+ }
+
+ virtual bool OnVisit(const wxTreeItemId& WXUNUSED(item))
+ {
+ m_count++;
+
+ return TRUE;
+ }
+
+ size_t GetCount() const { return m_count; }
+
+private:
+ size_t m_count;
+};
+
+// ----------------------------------------------------------------------------
+// This class is needed for support of different images: the Win32 common
+// control natively supports only 2 images (the normal one and another for the
+// selected state). We wish to provide support for 2 more of them for folder
+// items (i.e. those which have children): for expanded state and for expanded
+// selected state. For this we use this structure to store the additional items
+// images.
+//
+// There is only one problem with this: when we retrieve the item's data, we
+// don't know whether we get a pointer to wxTreeItemData or
+// wxTreeItemIndirectData. So we always set the item id to an invalid value
+// in this class and the code using the client data checks for it and retrieves
+// the real client data in this case.
+// ----------------------------------------------------------------------------
+
+class wxTreeItemIndirectData : public wxTreeItemData
+{
+public:
+ // ctor associates this data with the item and the real item data becomes
+ // available through our GetData() method
+ wxTreeItemIndirectData(wxTreeCtrl *tree, const wxTreeItemId& item)
+ {
+ for ( size_t n = 0; n < WXSIZEOF(m_images); n++ )
+ {
+ m_images[n] = -1;
+ }
+
+ // save the old data
+ m_data = tree->GetItemData(item);
+
+ // and set ourselves as the new one
+ tree->SetIndirectItemData(item, this);
+
+ // we must have the invalid value for the item
+ m_pItem = 0l;
+ }
+
+ // dtor deletes the associated data as well
+ virtual ~wxTreeItemIndirectData() { delete m_data; }
+
+ // accessors
+ // get the real data associated with the item
+ wxTreeItemData *GetData() const { return m_data; }
+ // change it
+ void SetData(wxTreeItemData *data) { m_data = data; }
+
+ // do we have such image?
+ bool HasImage(wxTreeItemIcon which) const { return m_images[which] != -1; }
+ // get image
+ int GetImage(wxTreeItemIcon which) const { return m_images[which]; }
+ // change it
+ void SetImage(int image, wxTreeItemIcon which) { m_images[which] = image; }
+
+private:
+ // all the images associated with the item
+ int m_images[wxTreeItemIcon_Max];
+
+ // the real client data
+ wxTreeItemData *m_data;
+};
+
+// ----------------------------------------------------------------------------
+// wxWin macros
+// ----------------------------------------------------------------------------
+
+IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
+
+// ----------------------------------------------------------------------------
+// constants
+// ----------------------------------------------------------------------------
+
+// indices in gs_expandEvents table below
+enum
+{
+ IDX_COLLAPSE,
+ IDX_EXPAND,
+ IDX_WHAT_MAX
+};
+
+enum
+{
+ IDX_DONE,
+ IDX_DOING,
+ IDX_HOW_MAX
+};
+
+// handy table for sending events - it has to be initialized during run-time
+// now so can't be const any more
+static /* const */ wxEventType gs_expandEvents[IDX_WHAT_MAX][IDX_HOW_MAX];
+