+/////////////////////////////////////////////////////////////////////////////
+// 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
+// ----------------------------------------------------------------------------
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+ #pragma hdrstop
+#endif
+
+#if wxUSE_TREECTRL
+
+#include "wx/treectrl.h"
+
+#ifndef WX_PRECOMP
+ #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
+ #include "wx/msw/missing.h"
+ #include "wx/dynarray.h"
+ #include "wx/log.h"
+ #include "wx/app.h"
+ #include "wx/settings.h"
+#endif
+
+#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
+
+#include "wx/imaglist.h"
+#include "wx/msw/dragimag.h"
+
+// macros to hide the cast ugliness
+// --------------------------------
+
+// get HTREEITEM from wxTreeItemId
+#define HITEM(item) ((HTREEITEM)(((item).m_pItem)))
+
+
+// older SDKs are missing these
+#ifndef TVN_ITEMCHANGINGA
+
+#define TVN_ITEMCHANGINGA (TVN_FIRST-16)
+#define TVN_ITEMCHANGINGW (TVN_FIRST-17)
+
+typedef struct tagNMTVITEMCHANGE
+{
+ NMHDR hdr;
+ UINT uChanged;
+ HTREEITEM hItem;
+ UINT uStateNew;
+ UINT uStateOld;
+ LPARAM lParam;
+} NMTVITEMCHANGE;
+
+#endif
+
+
+// this global variable is used on vista systems for preventing unwanted
+// item state changes in the vista tree control. It is only used in
+// multi-select mode on vista systems.
+
+static HTREEITEM gs_unlockItem = NULL;
+
+class TreeItemUnlocker
+{
+public:
+ TreeItemUnlocker(HTREEITEM item) { gs_unlockItem = item; }
+ ~TreeItemUnlocker() { gs_unlockItem = NULL; }
+};
+
+
+// ----------------------------------------------------------------------------
+// private functions
+// ----------------------------------------------------------------------------
+
+// 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;
+
+ TreeItemUnlocker unlocker(hItem);
+
+ if ( !TreeView_GetItem(hwndTV, &tvi) )
+ {
+ wxLogLastError(wxT("TreeView_GetItem"));
+ }
+
+ return (tvi.state & TVIS_SELECTED) != 0;
+}
+
+static bool 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;
+
+ TreeItemUnlocker unlocker(hItem);
+
+ if ( TreeView_SetItem(hwndTV, &tvi) == -1 )
+ {
+ wxLogLastError(wxT("TreeView_SetItem"));
+ return false;
+ }
+
+ return true;
+}
+
+static inline void UnselectItem(HWND hwndTV, HTREEITEM htItem)
+{
+ SelectItem(hwndTV, htItem, false);
+}
+
+// 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!)
+//
+// returns true if the focus was changed, false if the given item was already
+// the focused one
+static bool SetFocus(HWND hwndTV, HTREEITEM htItem)
+{
+ // the current focus
+ HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(hwndTV);
+
+ if ( htItem == htFocus )
+ return false;
+
+ if ( htItem )
+ {
+ // 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 // reset focus
+ {
+ bool wasFocusSelected = IsItemSelected(hwndTV, htFocus);
+
+ // just clear the focus
+ TreeView_SelectItem(hwndTV, 0);
+
+ if ( wasFocusSelected )
+ {
+ // restore the selection state
+ SelectItem(hwndTV, htFocus);
+ }
+ }
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// 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);
+ }
+};
+
+// ----------------------------------------------------------------------------
+// This class is our userdata/lParam for the TV_ITEMs stored in the treeview.
+//
+// We need this for a couple of reasons:
+//
+// 1) 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.
+//
+// 2) This class is also needed to hold the HITEM so that we can sort
+// it correctly in the MSW sort callback.
+//
+// In addition it makes other workarounds such as this easier and helps
+// simplify the code.
+// ----------------------------------------------------------------------------
+
+class wxTreeItemParam
+{
+public:
+ wxTreeItemParam()
+ {
+ m_data = NULL;
+
+ for ( size_t n = 0; n < WXSIZEOF(m_images); n++ )
+ {
+ m_images[n] = -1;
+ }
+ }
+
+ // dtor deletes the associated data as well
+ virtual ~wxTreeItemParam() { 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, falling back to the other images if this one is not
+ // specified
+ int GetImage(wxTreeItemIcon which) const
+ {
+ int image = m_images[which];
+ if ( image == -1 )
+ {
+ switch ( which )
+ {
+ case wxTreeItemIcon_SelectedExpanded:
+ image = GetImage(wxTreeItemIcon_Expanded);
+ if ( image != -1 )
+ break;
+ //else: fall through
+
+ case wxTreeItemIcon_Selected:
+ case wxTreeItemIcon_Expanded:
+ image = GetImage(wxTreeItemIcon_Normal);
+ break;
+
+ case wxTreeItemIcon_Normal:
+ // no fallback
+ break;
+
+ default:
+ wxFAIL_MSG( _T("unsupported wxTreeItemIcon value") );
+ }
+ }
+
+ return image;
+ }
+ // change the given image
+ void SetImage(int image, wxTreeItemIcon which) { m_images[which] = image; }
+
+ // get item
+ const wxTreeItemId& GetItem() const { return m_item; }
+ // set item
+ void SetItem(const wxTreeItemId& item) { m_item = item; }
+
+protected:
+ // all the images associated with the item
+ int m_images[wxTreeItemIcon_Max];
+
+ // item for sort callbacks
+ wxTreeItemId m_item;
+
+ // the real client data
+ wxTreeItemData *m_data;
+
+ DECLARE_NO_COPY_CLASS(wxTreeItemParam)
+};
+
+// wxVirutalNode is used in place of a single root when 'hidden' root is
+// specified.
+class wxVirtualNode : public wxTreeViewItem
+{
+public:
+ wxVirtualNode(wxTreeItemParam *param)
+ : wxTreeViewItem(TVI_ROOT, 0)
+ {
+ m_param = param;
+ }
+
+ ~wxVirtualNode()
+ {
+ delete m_param;
+ }
+
+ wxTreeItemParam *GetParam() const { return m_param; }
+ void SetParam(wxTreeItemParam *param) { delete m_param; m_param = param; }
+
+private:
+ wxTreeItemParam *m_param;
+
+ DECLARE_NO_COPY_CLASS(wxVirtualNode)
+};
+
+#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;
+ }
+
+ // give it a virtual dtor: not really needed as the class is never used
+ // polymorphically and not even allocated on heap at all, but this is safer
+ // (in case it ever is) and silences the compiler warnings for now
+ virtual ~wxTreeTraversal() { }
+
+ // 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;
+
+ DECLARE_NO_COPY_CLASS(wxTreeTraversal)
+};
+
+// 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();
+
+ if (tree->GetCount() > 0)
+ DoTraverse(tree->GetRootItem());
+ }
+
+ virtual bool OnVisit(const wxTreeItemId& item)
+ {
+ const wxTreeCtrl * const tree = GetTree();
+
+ // can't visit a virtual node.
+ if ( (tree->GetRootItem() == item) && tree->HasFlag(wxTR_HIDE_ROOT) )
+ {
+ return true;
+ }
+
+ if ( ::IsItemSelected(GetHwndOf(tree), HITEM(item)) )
+ {
+ m_selections.Add(item);
+ }
+
+ return true;
+ }
+
+ size_t GetCount() const { return m_selections.GetCount(); }
+
+private:
+ wxArrayTreeItemIds& m_selections;
+
+ DECLARE_NO_COPY_CLASS(TraverseSelections)
+};
+
+// 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;
+
+ DECLARE_NO_COPY_CLASS(TraverseCounter)
+};
+
+// ----------------------------------------------------------------------------
+// wxWin macros
+// ----------------------------------------------------------------------------
+
+#if wxUSE_EXTENDED_RTTI
+WX_DEFINE_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 )
+
+IMPLEMENT_DYNAMIC_CLASS_XTI(wxTreeCtrl, wxControl,"wx/treectrl.h")
+
+wxBEGIN_PROPERTIES_TABLE(wxTreeCtrl)
+ wxEVENT_PROPERTY( TextUpdated , wxEVT_COMMAND_TEXT_UPDATED , wxCommandEvent )
+ wxEVENT_RANGE_PROPERTY( TreeEvent , wxEVT_COMMAND_TREE_BEGIN_DRAG , wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK , wxTreeEvent )
+ wxPROPERTY_FLAGS( WindowStyle , wxTreeCtrlStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
+wxEND_PROPERTIES_TABLE()
+
+wxBEGIN_HANDLERS_TABLE(wxTreeCtrl)
+wxEND_HANDLERS_TABLE()
+
+wxCONSTRUCTOR_5( wxTreeCtrl , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size , long , WindowStyle )
+#else
+IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
+#endif
+
+// ----------------------------------------------------------------------------
+// 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];
+