/////////////////////////////////////////////////////////////////////////////
-// Name: treectrl.cpp
+// Name: src/msw/treectrl.cpp
// Purpose: wxTreeCtrl
// Author: Julian Smart
// Modified by: Vadim Zeitlin to be less MSW-specific on 10.10.98
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
+
#ifdef __GNUG__
#pragma implementation "treectrl.h"
#endif
#pragma hdrstop
#endif
-#include "wx/window.h"
+#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
#include "wx/log.h"
#include "wx/dynarray.h"
#include "wx/imaglist.h"
-#include "wx/treectrl.h"
#include "wx/settings.h"
+#include "wx/msw/treectrl.h"
+#include "wx/msw/dragimag.h"
-#ifdef __GNUWIN32__
-#ifndef wxUSE_NORLANDER_HEADERS
-#include "wx/msw/gnuwin32/extra.h"
-#endif
+#ifdef __GNUWIN32_OLD__
+ #include "wx/msw/gnuwin32/extra.h"
#endif
-#if (defined(__WIN95__) && !defined(__GNUWIN32__)) || defined(__TWIN32__) || defined(wxUSE_NORLANDER_HEADERS)
+#if defined(__WIN95__) && !((defined(__GNUWIN32_OLD__) || defined(__TWIN32__)) && !defined(__CYGWIN10__))
#include <commctrl.h>
#endif
#define TVIS_FOCUSED 0x0001
#endif
+#ifndef TV_FIRST
+ #define TV_FIRST 0x1100
+#endif
+
+#ifndef TVS_CHECKBOXES
+ #define TVS_CHECKBOXES 0x0100
+#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 )
+#pragma warning( disable : 4097 ) // inheriting from typedef
#endif
struct wxTreeViewItem : public TV_ITEM
// hItem member is always valid
mask = mask_ | TVIF_HANDLE;
stateMask = stateMask_;
- hItem = (HTREEITEM) (WXHTREEITEM) item;
+ hItem = HITEM(item);
}
};
virtual bool OnVisit(const wxTreeItemId& item)
{
+#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;
};
DoTraverse(root, recursively);
}
- virtual bool OnVisit(const wxTreeItemId& item)
+ virtual bool OnVisit(const wxTreeItemId& WXUNUSED(item))
{
m_count++;
};
// ----------------------------------------------------------------------------
-// macros
+// wxWin macros
// ----------------------------------------------------------------------------
-#if !USE_SHARED_LIBRARY
- IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
-#endif
+IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
// ----------------------------------------------------------------------------
// variables
// ----------------------------------------------------------------------------
// handy table for sending events
-static const wxEventType g_events[2][2] =
+static wxEventType g_events[2][2] =
{
{ wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxEVT_COMMAND_TREE_ITEM_COLLAPSING },
{ wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxEVT_COMMAND_TREE_ITEM_EXPANDING }
{
m_imageListNormal = NULL;
m_imageListState = NULL;
+ m_ownsImageListNormal = m_ownsImageListState = FALSE;
m_textCtrl = NULL;
m_hasAnyAttr = FALSE;
+ m_dragImage = NULL;
+ m_htSelStart = 0;
+
+ // Initialize static array of events, because with the new event system,
+ // they may not be initialized yet.
+
+ g_events[0][0] = wxEVT_COMMAND_TREE_ITEM_COLLAPSED;
+ g_events[0][1] = wxEVT_COMMAND_TREE_ITEM_COLLAPSING;
+ g_events[1][0] = wxEVT_COMMAND_TREE_ITEM_EXPANDED;
+ g_events[1][1] = wxEVT_COMMAND_TREE_ITEM_EXPANDING;
}
bool wxTreeCtrl::Create(wxWindow *parent,
return FALSE;
DWORD wstyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP |
- TVS_HASLINES | TVS_SHOWSELALWAYS;
+ TVS_SHOWSELALWAYS;
+ if ( m_windowStyle & wxCLIP_SIBLINGS )
+ wstyle |= WS_CLIPSIBLINGS;
+
+ if ((m_windowStyle & wxTR_NO_LINES) == 0)
+ wstyle |= TVS_HASLINES;
if ( m_windowStyle & wxTR_HAS_BUTTONS )
wstyle |= TVS_HASBUTTONS;
if ( m_windowStyle & wxTR_LINES_AT_ROOT )
wstyle |= TVS_LINESATROOT;
-#if !defined( __GNUWIN32__ ) && !defined( __BORLANDC__ ) && !defined( __WATCOMC__ ) && !defined(wxUSE_NORLANDER_HEADERS)
+ // using TVS_CHECKBOXES for emulation of a multiselection tree control
+ // doesn't work without the new enough headers
+#if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE && \
+ !defined( __GNUWIN32_OLD__ ) && \
+ !defined( __BORLANDC__ ) && \
+ !defined( __WATCOMC__ ) && \
+ (!defined(__VISUALC__) || (__VISUALC__ > 1010))
+
// we emulate the multiple selection tree controls by using checkboxes: set
// up the image list we need for this if we do have multiple selections
-#if !defined(__VISUALC__) || (__VISUALC__ > 1010)
if ( m_windowStyle & wxTR_MULTIPLE )
wstyle |= TVS_CHECKBOXES;
-#endif
-#endif
+#endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
// Create the tree control.
if ( !MSWCreateControl(WC_TREEVIEW, wstyle) )
return FALSE;
+#if wxUSE_COMCTL32_SAFELY
+ wxWindow::SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW));
+ wxWindow::SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
+#elif 1
SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW));
SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
+#else
+ // This works around a bug in the Windows tree control whereby for some versions
+ // of comctrl32, setting any colour actually draws the background in black.
+ // This will initialise the background to the system colour.
+ // THIS FIX NOW REVERTED since it caused problems on _other_ systems.
+ // Assume the user has an updated comctl32.dll.
+ ::SendMessage(GetHwnd(), TVM_SETBKCOLOR, 0,-1);
+ wxWindow::SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW));
+ SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
+#endif
+
// VZ: this is some experimental code which may be used to get the
// TVS_CHECKBOXES style functionality for comctl32.dll < 4.71.
// delete user data to prevent memory leaks
DeleteAllItems();
+
+ if (m_ownsImageListNormal) delete m_imageListNormal;
+ if (m_ownsImageListState) delete m_imageListState;
}
// ----------------------------------------------------------------------------
{
if ( !TreeView_GetItem(GetHwnd(), tvItem) )
{
- wxLogLastError("TreeView_GetItem");
+ wxLogLastError(wxT("TreeView_GetItem"));
return FALSE;
}
{
if ( TreeView_SetItem(GetHwnd(), tvItem) == -1 )
{
- wxLogLastError("TreeView_SetItem");
+ wxLogLastError(wxT("TreeView_SetItem"));
}
}
void wxTreeCtrl::SetImageList(wxImageList *imageList)
{
+ if (m_ownsImageListNormal) delete m_imageListNormal;
SetAnyImageList(m_imageListNormal = imageList, TVSIL_NORMAL);
+ m_ownsImageListNormal = FALSE;
}
void wxTreeCtrl::SetStateImageList(wxImageList *imageList)
{
+ if (m_ownsImageListState) delete m_imageListState;
SetAnyImageList(m_imageListState = imageList, TVSIL_STATE);
+ m_ownsImageListState = FALSE;
+}
+
+void wxTreeCtrl::AssignImageList(wxImageList *imageList)
+{
+ SetImageList(imageList);
+ m_ownsImageListNormal = TRUE;
+}
+
+void wxTreeCtrl::AssignStateImageList(wxImageList *imageList)
+{
+ SetStateImageList(imageList);
+ m_ownsImageListState = TRUE;
}
size_t wxTreeCtrl::GetChildrenCount(const wxTreeItemId& item,
return counter.GetCount() - 1;
}
+// ----------------------------------------------------------------------------
+// control colours
+// ----------------------------------------------------------------------------
+
+bool wxTreeCtrl::SetBackgroundColour(const wxColour &colour)
+{
+#if !wxUSE_COMCTL32_SAFELY
+ if ( !wxWindowBase::SetBackgroundColour(colour) )
+ return FALSE;
+
+ SendMessage(GetHwnd(), TVM_SETBKCOLOR, 0, colour.GetPixel());
+#endif
+
+ return TRUE;
+}
+
+bool wxTreeCtrl::SetForegroundColour(const wxColour &colour)
+{
+#if !wxUSE_COMCTL32_SAFELY
+ if ( !wxWindowBase::SetForegroundColour(colour) )
+ return FALSE;
+
+ SendMessage(GetHwnd(), TVM_SETTEXTCOLOR, 0, colour.GetPixel());
+#endif
+
+ return TRUE;
+}
+
// ----------------------------------------------------------------------------
// Item access
// ----------------------------------------------------------------------------
void wxTreeCtrl::SetItemData(const wxTreeItemId& item, wxTreeItemData *data)
{
+ // first, associate this piece of data with this item
+ if ( data )
+ {
+ data->SetId(item);
+ }
+
wxTreeViewItem tvItem(item, TVIF_PARAM);
if ( HasIndirectData(item) )
wxTreeItemId wxTreeCtrl::GetSelection() const
{
- wxCHECK_MSG( !(m_windowStyle & wxTR_MULTIPLE), (WXHTREEITEM)0,
+ wxCHECK_MSG( !(m_windowStyle & wxTR_MULTIPLE), (long)(WXHTREEITEM)0,
wxT("this only works with single selection controls") );
return wxTreeItemId((WXHTREEITEM) TreeView_GetSelection(GetHwnd()));
wxTreeItemId wxTreeCtrl::GetParent(const wxTreeItemId& item) const
{
- return wxTreeItemId((WXHTREEITEM) TreeView_GetParent(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item));
+ return wxTreeItemId((WXHTREEITEM) TreeView_GetParent(GetHwnd(), HITEM(item)));
}
wxTreeItemId wxTreeCtrl::GetFirstChild(const wxTreeItemId& item,
long& _cookie) const
{
// remember the last child returned in 'cookie'
- _cookie = (long)TreeView_GetChild(GetHwnd(), (HTREEITEM) (WXHTREEITEM)item);
+ _cookie = (long)TreeView_GetChild(GetHwnd(), HITEM(item));
return wxTreeItemId((WXHTREEITEM)_cookie);
}
long& _cookie) const
{
wxTreeItemId l = wxTreeItemId((WXHTREEITEM)TreeView_GetNextSibling(GetHwnd(),
- (HTREEITEM)(WXHTREEITEM)_cookie));
+ HITEM(_cookie)));
_cookie = (long)l;
return l;
wxTreeItemId wxTreeCtrl::GetNextSibling(const wxTreeItemId& item) const
{
- return wxTreeItemId((WXHTREEITEM) TreeView_GetNextSibling(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item));
+ return wxTreeItemId((WXHTREEITEM) TreeView_GetNextSibling(GetHwnd(), HITEM(item)));
}
wxTreeItemId wxTreeCtrl::GetPrevSibling(const wxTreeItemId& item) const
{
- return wxTreeItemId((WXHTREEITEM) TreeView_GetPrevSibling(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item));
+ return wxTreeItemId((WXHTREEITEM) TreeView_GetPrevSibling(GetHwnd(), HITEM(item)));
}
wxTreeItemId wxTreeCtrl::GetFirstVisibleItem() const
wxTreeItemId wxTreeCtrl::GetNextVisible(const wxTreeItemId& item) const
{
- wxASSERT_MSG( IsVisible(item), wxT("The item you call GetNextVisible() "
- "for must be visible itself!"));
+ wxASSERT_MSG( IsVisible(item), wxT("The item you call GetNextVisible() for must be visible itself!"));
- return wxTreeItemId((WXHTREEITEM) TreeView_GetNextVisible(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item));
+ return wxTreeItemId((WXHTREEITEM) TreeView_GetNextVisible(GetHwnd(), HITEM(item)));
}
wxTreeItemId wxTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const
{
- wxASSERT_MSG( IsVisible(item), wxT("The item you call GetPrevVisible() "
- "for must be visible itself!"));
+ wxASSERT_MSG( IsVisible(item), wxT("The item you call GetPrevVisible() for must be visible itself!"));
- return wxTreeItemId((WXHTREEITEM) TreeView_GetPrevVisible(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item));
+ return wxTreeItemId((WXHTREEITEM) TreeView_GetPrevVisible(GetHwnd(), HITEM(item)));
}
// ----------------------------------------------------------------------------
{
TraverseSelections selector(this, selections);
- return selections.GetCount();
+ return selector.GetCount();
}
// ----------------------------------------------------------------------------
int image, int selectedImage,
wxTreeItemData *data)
{
+ wxCHECK_MSG( parent.IsOk() || !TreeView_GetRoot(GetHwnd()),
+ wxTreeItemId(),
+ _T("can't have more than one root in the tree") );
+
TV_INSERTSTRUCT tvIns;
- tvIns.hParent = (HTREEITEM) (WXHTREEITEM)parent;
- tvIns.hInsertAfter = (HTREEITEM) (WXHTREEITEM) hInsertAfter;
+ tvIns.hParent = HITEM(parent);
+ tvIns.hInsertAfter = HITEM(hInsertAfter);
// this is how we insert the item as the first child: supply a NULL
// hInsertAfter
mask |= TVIF_TEXT;
tvIns.item.pszText = (wxChar *)text.c_str(); // cast is ok
}
+ else
+ {
+ tvIns.item.pszText = NULL;
+ tvIns.item.cchTextMax = 0;
+ }
if ( image != -1 )
{
HTREEITEM id = (HTREEITEM) TreeView_InsertItem(GetHwnd(), &tvIns);
if ( id == 0 )
{
- wxLogLastError("TreeView_InsertItem");
+ wxLogLastError(wxT("TreeView_InsertItem"));
}
if ( data != NULL )
int image, int selectedImage,
wxTreeItemData *data)
{
- return DoInsertItem(wxTreeItemId((WXHTREEITEM) 0), (WXHTREEITEM) 0,
+ return DoInsertItem(wxTreeItemId((long) (WXHTREEITEM) 0), (long)(WXHTREEITEM) 0,
text, image, selectedImage, data);
}
return DoInsertItem(parent, idPrevious, text, image, selectedImage, data);
}
+wxTreeItemId wxTreeCtrl::InsertItem(const wxTreeItemId& parent,
+ size_t index,
+ const wxString& text,
+ int image, int selectedImage,
+ wxTreeItemData *data)
+{
+ // find the item from index
+ long cookie;
+ wxTreeItemId idPrev, idCur = GetFirstChild(parent, cookie);
+ while ( index != 0 && idCur.IsOk() )
+ {
+ index--;
+
+ idPrev = idCur;
+ idCur = GetNextChild(parent, cookie);
+ }
+
+ // assert, not check: if the index is invalid, we will append the item
+ // to the end
+ wxASSERT_MSG( index == 0, _T("bad index in wxTreeCtrl::InsertItem") );
+
+ return DoInsertItem(parent, idPrev, text, image, selectedImage, data);
+}
+
wxTreeItemId wxTreeCtrl::AppendItem(const wxTreeItemId& parent,
const wxString& text,
int image, int selectedImage,
void wxTreeCtrl::Delete(const wxTreeItemId& item)
{
- if ( !TreeView_DeleteItem(GetHwnd(), (HTREEITEM)(WXHTREEITEM)item) )
+ if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
{
- wxLogLastError("TreeView_DeleteItem");
+ wxLogLastError(wxT("TreeView_DeleteItem"));
}
}
{
if ( !TreeView_DeleteItem(GetHwnd(), (HTREEITEM)children[n]) )
{
- wxLogLastError("TreeView_DeleteItem");
+ wxLogLastError(wxT("TreeView_DeleteItem"));
}
}
}
{
if ( !TreeView_DeleteAllItems(GetHwnd()) )
{
- wxLogLastError("TreeView_DeleteAllItems");
+ wxLogLastError(wxT("TreeView_DeleteAllItems"));
}
}
tvItem.state = 0;
DoSetItem(&tvItem);
- if ( TreeView_Expand(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item, flag) != 0 )
+ if ( TreeView_Expand(GetHwnd(), HITEM(item), flag) != 0 )
{
wxTreeEvent event(wxEVT_NULL, m_windowId);
event.m_item = item;
void wxTreeCtrl::Unselect()
{
- wxASSERT_MSG( !(m_windowStyle & wxTR_MULTIPLE), wxT("doesn't make sense") );
+ wxASSERT_MSG( !(m_windowStyle & wxTR_MULTIPLE),
+ wxT("doesn't make sense, may be you want UnselectAll()?") );
// just remove the selection
- SelectItem(wxTreeItemId((WXHTREEITEM) 0));
+ SelectItem(wxTreeItemId((long) (WXHTREEITEM) 0));
}
void wxTreeCtrl::UnselectAll()
size_t count = GetSelections(selections);
for ( size_t n = 0; n < count; n++ )
{
+#if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
SetItemCheck(selections[n], FALSE);
+#else // !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+ ::UnselectItem(GetHwnd(), HITEM(selections[n]));
+#endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE/!wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
}
}
else
{
if ( m_windowStyle & wxTR_MULTIPLE )
{
+#if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
// selecting the item means checking it
SetItemCheck(item);
+#else // !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+ ::SelectItem(GetHwnd(), HITEM(item));
+#endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE/!wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
}
else
{
event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGING);
if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
{
- if ( !TreeView_SelectItem(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item) )
+ if ( !TreeView_SelectItem(GetHwnd(), HITEM(item)) )
{
- wxLogLastError("TreeView_SelectItem");
+ wxLogLastError(wxT("TreeView_SelectItem"));
}
else
{
void wxTreeCtrl::EnsureVisible(const wxTreeItemId& item)
{
// no error return
- TreeView_EnsureVisible(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item);
+ TreeView_EnsureVisible(GetHwnd(), HITEM(item));
}
void wxTreeCtrl::ScrollTo(const wxTreeItemId& item)
{
- if ( !TreeView_SelectSetFirstVisible(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item) )
+ if ( !TreeView_SelectSetFirstVisible(GetHwnd(), HITEM(item)) )
{
- wxLogLastError("TreeView_SelectSetFirstVisible");
+ wxLogLastError(wxT("TreeView_SelectSetFirstVisible"));
}
}
wxTextCtrl* wxTreeCtrl::GetEditControl() const
{
+ // normally, we could try to do something like this to return something
+ // even when the editing was started by the user and not by calling
+ // EditLabel() - but as nobody has asked for this so far and there might be
+ // problems in the code below, I leave it disabled for now (VZ)
+#if 0
+ if ( !m_textCtrl )
+ {
+ HWND hwndText = TreeView_GetEditControl(GetHwnd());
+ if ( hwndText )
+ {
+ m_textCtrl = new wxTextCtrl(this, -1);
+ m_textCtrl->Hide();
+ m_textCtrl->SetHWND((WXHWND)hwndText);
+ }
+ //else: not editing label right now
+ }
+#endif // 0
+
return m_textCtrl;
}
{
if ( m_textCtrl )
{
+ // the HWND corresponding to this control is deleted by the tree
+ // control itself and we don't know when exactly this happens, so check
+ // if the window still exists before calling UnsubclassWin()
+ if ( !::IsWindow(GetHwndOf(m_textCtrl)) )
+ {
+ m_textCtrl->SetHWND(0);
+ }
+
m_textCtrl->UnsubclassWin();
m_textCtrl->SetHWND(0);
delete m_textCtrl;
{
wxASSERT( textControlClass->IsKindOf(CLASSINFO(wxTextCtrl)) );
- HWND hWnd = (HWND) TreeView_EditLabel(GetHwnd(), (HTREEITEM) (WXHTREEITEM) item);
+ DeleteTextCtrl();
+
+ HWND hWnd = (HWND) TreeView_EditLabel(GetHwnd(), HITEM(item));
// this is not an error - the TVN_BEGINLABELEDIT handler might have
// returned FALSE
return NULL;
}
- DeleteTextCtrl();
-
m_textCtrl = (wxTextCtrl *)textControlClass->CreateObject();
+ m_textCtrl->SetParent(this);
m_textCtrl->SetHWND((WXHWND)hWnd);
m_textCtrl->SubclassWin((WXHWND)hWnd);
}
// End label editing, optionally cancelling the edit
-void wxTreeCtrl::EndEditLabel(const wxTreeItemId& item, bool discardChanges)
+void wxTreeCtrl::EndEditLabel(const wxTreeItemId& WXUNUSED(item), bool discardChanges)
{
TreeView_EndEditLabelNow(GetHwnd(), discardChanges);
bool textOnly) const
{
RECT rc;
- if ( TreeView_GetItemRect(GetHwnd(), (HTREEITEM)(WXHTREEITEM)item,
+ if ( TreeView_GetItemRect(GetHwnd(), HITEM(item),
&rc, textOnly) )
{
rect = wxRect(wxPoint(rc.left, rc.top), wxPoint(rc.right, rc.bottom));
// directly if we're not in derived class (much more efficient!)
if ( GetClassInfo() == CLASSINFO(wxTreeCtrl) )
{
- TreeView_SortChildren(GetHwnd(), (HTREEITEM)(WXHTREEITEM)item, 0);
+ TreeView_SortChildren(GetHwnd(), HITEM(item), 0);
}
else
{
TV_SORTCB tvSort;
- tvSort.hParent = (HTREEITEM)(WXHTREEITEM)item;
+ tvSort.hParent = HITEM(item);
tvSort.lpfnCompare = (PFNTVCOMPARE)TreeView_CompareCallback;
tvSort.lParam = (LPARAM)this;
TreeView_SortChildrenCB(GetHwnd(), &tvSort, 0 /* reserved */);
return TRUE;
}
-// process WM_NOTIFY Windows message
-bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
+// we hook into WndProc to process WM_MOUSEMOVE/WM_BUTTONUP messages - as we
+// only do it during dragging, minimize wxWin overhead (this is important for
+// WM_MOUSEMOVE as they're a lot of them) by catching Windows messages directly
+// instead of passing by wxWin events
+long wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
- wxTreeEvent event(wxEVT_NULL, m_windowId);
- wxEventType eventType = wxEVT_NULL;
- NMHDR *hdr = (NMHDR *)lParam;
+ bool processed = FALSE;
+ long rc = 0;
- switch ( hdr->code )
+ bool isMultiple = (GetWindowStyle() & wxTR_MULTIPLE) != 0;
+
+ if ( (nMsg >= WM_MOUSEFIRST) && (nMsg <= WM_MOUSELAST) )
{
- case NM_RCLICK:
- {
- if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
- return TRUE;
+ // we only process mouse messages here and these parameters have the same
+ // meaning for all of them
+ int x = GET_X_LPARAM(lParam),
+ y = GET_Y_LPARAM(lParam);
+ HTREEITEM htItem = GetItemFromPoint(GetHwnd(), x, y);
- TV_HITTESTINFO tvhti;
- ::GetCursorPos(&(tvhti.pt));
- ::ScreenToClient(GetHwnd(),&(tvhti.pt));
- if ( TreeView_HitTest(GetHwnd(),&tvhti) )
+ switch ( nMsg )
+ {
+#if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+ case WM_LBUTTONDOWN:
+ if ( htItem && isMultiple )
{
- if( tvhti.flags & TVHT_ONITEM )
+ if ( wParam & MK_CONTROL )
{
- event.m_item = (WXHTREEITEM) tvhti.hItem;
- eventType = wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK;
+ SetFocus();
+
+ // toggle selected state
+ ToggleItemSelection(GetHwnd(), htItem);
+
+ ::SetFocus(GetHwnd(), htItem);
+
+ // reset on any click without Shift
+ m_htSelStart = 0;
+
+ processed = TRUE;
+ }
+ else if ( wParam & MK_SHIFT )
+ {
+ // this selects all items between the starting one and
+ // the current
+
+ if ( !m_htSelStart )
+ {
+ // take the focused item
+ m_htSelStart = (WXHTREEITEM)
+ TreeView_GetSelection(GetHwnd());
+ }
+
+ SelectRange(GetHwnd(), HITEM(m_htSelStart), htItem,
+ !(wParam & MK_CONTROL));
+
+ ::SetFocus(GetHwnd(), htItem);
+
+ processed = TRUE;
+ }
+ else // normal click
+ {
+ // clear the selection and then let the default handler
+ // do the job
+ UnselectAll();
+
+ // prevent the click from starting in-place editing
+ // when there was no selection in the control
+ TreeView_SelectItem(GetHwnd(), 0);
+
+ // reset on any click without Shift
+ m_htSelStart = 0;
}
}
+ break;
+#endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+
+ case WM_MOUSEMOVE:
+ if ( m_dragImage )
+ {
+ m_dragImage->Move(wxPoint(x, y));
+ if ( htItem )
+ {
+ // highlight the item as target (hiding drag image is
+ // necessary - otherwise the display will be corrupted)
+ m_dragImage->Hide();
+ TreeView_SelectDropTarget(GetHwnd(), htItem);
+ m_dragImage->Show();
+ }
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ case WM_RBUTTONUP:
+ if ( m_dragImage )
+ {
+ m_dragImage->EndDrag();
+ delete m_dragImage;
+ m_dragImage = NULL;
+
+ // generate the drag end event
+ wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG, m_windowId);
+
+ event.m_item = (WXHTREEITEM)htItem;
+ event.m_pointDrag = wxPoint(x, y);
+ event.SetEventObject(this);
+
+ (void)GetEventHandler()->ProcessEvent(event);
+
+ // if we don't do it, the tree seems to think that 2 items
+ // are selected simultaneously which is quite weird
+ TreeView_SelectDropTarget(GetHwnd(), 0);
+ }
+ break;
+ }
+ }
+#if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+ else if ( (nMsg == WM_SETFOCUS || nMsg == WM_KILLFOCUS) && isMultiple )
+ {
+ // the tree control greys out the selected item when it loses focus and
+ // paints it as selected again when it regains it, but it won't do it
+ // for the other items itself - help it
+ wxArrayTreeItemIds selections;
+ size_t count = GetSelections(selections);
+ RECT rect;
+ for ( size_t n = 0; n < count; n++ )
+ {
+ // TreeView_GetItemRect() will return FALSE if item is not visible,
+ // which may happen perfectly well
+ if ( TreeView_GetItemRect(GetHwnd(), HITEM(selections[n]),
+ &rect, TRUE) )
+ {
+ ::InvalidateRect(GetHwnd(), &rect, FALSE);
}
- break;
+ }
+ }
+ else if ( nMsg == WM_KEYDOWN && isMultiple )
+ {
+ bool bCtrl = wxIsCtrlDown(),
+ bShift = wxIsShiftDown();
+
+ // we handle.arrows and space, but not page up/down and home/end: the
+ // latter should be easy, but not the former
+
+ HTREEITEM htSel = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+ if ( !m_htSelStart )
+ {
+ m_htSelStart = (WXHTREEITEM)htSel;
+ }
+
+ if ( wParam == VK_SPACE )
+ {
+ if ( bCtrl )
+ {
+ ToggleItemSelection(GetHwnd(), htSel);
+ }
+ else
+ {
+ UnselectAll();
+
+ ::SelectItem(GetHwnd(), htSel);
+ }
+
+ processed = TRUE;
+ }
+ else if ( wParam == VK_UP || wParam == VK_DOWN )
+ {
+ if ( !bCtrl && !bShift )
+ {
+ // no modifiers, just clear selection and then let the default
+ // processing to take place
+ UnselectAll();
+ }
+ else if ( htSel )
+ {
+ (void)wxControl::MSWWindowProc(nMsg, wParam, lParam);
+
+ HTREEITEM htNext = (HTREEITEM)(wParam == VK_UP
+ ? TreeView_GetPrevVisible(GetHwnd(), htSel)
+ : TreeView_GetNextVisible(GetHwnd(), htSel));
+
+ if ( !htNext )
+ {
+ // at the top/bottom
+ htNext = htSel;
+ }
+
+ if ( bShift )
+ {
+ SelectRange(GetHwnd(), HITEM(m_htSelStart), htNext);
+ }
+ else // bCtrl
+ {
+ // without changing selection
+ ::SetFocus(GetHwnd(), htNext);
+ }
+
+ processed = TRUE;
+ }
+ }
+ }
+#endif // !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+
+ if ( !processed )
+ rc = wxControl::MSWWindowProc(nMsg, wParam, lParam);
+
+ return rc;
+}
+
+// process WM_NOTIFY Windows message
+bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
+{
+ wxTreeEvent event(wxEVT_NULL, m_windowId);
+ wxEventType eventType = wxEVT_NULL;
+ NMHDR *hdr = (NMHDR *)lParam;
+ switch ( hdr->code )
+ {
case TVN_BEGINDRAG:
eventType = wxEVT_COMMAND_TREE_BEGIN_DRAG;
// fall through
event.m_item = (WXHTREEITEM) tv->itemNew.hItem;
event.m_pointDrag = wxPoint(tv->ptDrag.x, tv->ptDrag.y);
+
+ // don't allow dragging by default: the user code must
+ // explicitly say that it wants to allow it to avoid breaking
+ // the old apps
+ event.Veto();
}
break;
break;
default:
- wxLogDebug(wxT("unexpected code %d in TVN_ITEMEXPAND "
- "message"), tv->action);
+ wxLogDebug(wxT("unexpected code %d in TVN_ITEMEXPAND message"), tv->action);
}
bool ing = ((int)hdr->code == TVN_ITEMEXPANDING);
event.m_code = wxCharCodeMSWToWX(info->wVKey);
- // a separate event for this case
- if ( info->wVKey == VK_SPACE || info->wVKey == VK_RETURN )
+ // a separate event for Space/Return
+ if ( !wxIsCtrlDown() && !wxIsShiftDown() &&
+ ((info->wVKey == VK_SPACE) || (info->wVKey == VK_RETURN)) )
{
wxTreeEvent event2(wxEVT_COMMAND_TREE_ITEM_ACTIVATED,
m_windowId);
event2.SetEventObject(this);
+ if ( !(GetWindowStyle() & wxTR_MULTIPLE) )
+ {
+ event2.m_item = GetSelection();
+ }
+ //else: don't know how to get it
- GetEventHandler()->ProcessEvent(event2);
+ (void)GetEventHandler()->ProcessEvent(event2);
}
}
break;
}
break;
-#ifdef NM_CUSTOMDRAW
+#if defined(_WIN32_IE) && _WIN32_IE >= 0x300 && !wxUSE_COMCTL32_SAFELY && !defined(__GNUWIN32__)
case NM_CUSTOMDRAW:
{
LPNMTVCUSTOMDRAW lptvcd = (LPNMTVCUSTOMDRAW)lParam;
return TRUE;
}
}
- break;
-#endif // NM_CUSTOMDRAW
+// break; // can never be reached
+#endif // _WIN32_IE >= 0x300
+
+ case NM_DBLCLK:
+ case NM_RCLICK:
+ {
+ TV_HITTESTINFO tvhti;
+ ::GetCursorPos(&tvhti.pt);
+ ::ScreenToClient(GetHwnd(), &tvhti.pt);
+ if ( TreeView_HitTest(GetHwnd(), &tvhti) )
+ {
+ if ( tvhti.flags & TVHT_ONITEM )
+ {
+ event.m_item = (WXHTREEITEM) tvhti.hItem;
+ eventType = (int)hdr->code == NM_DBLCLK
+ ? wxEVT_COMMAND_TREE_ITEM_ACTIVATED
+ : wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK;
+
+ event.m_pointDrag.x = tvhti.pt.x;
+ event.m_pointDrag.y = tvhti.pt.y;
+ }
+
+ break;
+ }
+ }
+ // fall through
default:
return wxControl::MSWOnNotify(idCtrl, lParam, result);
// post processing
switch ( hdr->code )
{
+ case NM_DBLCLK:
+ // we translate NM_DBLCLK into ACTIVATED event, so don't interpret
+ // the return code of this event handler as the return value for
+ // NM_DBLCLK - otherwise, double clicking the item to toggle its
+ // expanded status would never work
+ *result = FALSE;
+ break;
+
+ case TVN_BEGINDRAG:
+ case TVN_BEGINRDRAG:
+ if ( event.IsAllowed() )
+ {
+ // normally this is impossible because the m_dragImage is
+ // deleted once the drag operation is over
+ wxASSERT_MSG( !m_dragImage, _T("starting to drag once again?") );
+
+ m_dragImage = new wxDragImage(*this, event.m_item);
+ m_dragImage->BeginDrag(wxPoint(0, 0), this);
+ m_dragImage->Show();
+ }
+ break;
+
case TVN_DELETEITEM:
{
// NB: we might process this message using wxWindows event
delete data; // can't be NULL here
m_itemsWithIndirectData.Remove(item);
+#if 0
+ int iIndex = m_itemsWithIndirectData.Index(item);
+ wxASSERT( iIndex != wxNOT_FOUND) ;
+ m_itemsWithIndirectData.wxBaseArray::RemoveAt((size_t)iIndex);
+#endif
}
else
{
break;
case TVN_ENDLABELEDIT:
- // return TRUE to set the label to the new string
+ // return TRUE to set the label to the new string: note that we
+ // also must pretend that we did process the message or it is going
+ // to be passed to DefWindowProc() which will happily return FALSE
+ // cancelling the label change
*result = event.IsAllowed();
+ processed = TRUE;
// ensure that we don't have the text ctrl which is going to be
// deleted any more
case TVN_GETDISPINFO:
// NB: so far the user can't set the image himself anyhow, so do it
// anyway - but this may change later
- if ( /* !processed && */ 1 )
+// if ( /* !processed && */ 1 )
{
wxTreeItemId item = event.m_item;
TV_DISPINFO *info = (TV_DISPINFO *)lParam;
: wxTreeItemIcon_Selected
);
}
- }
+ }
break;
//default:
return processed;
}
-// ----------------------------------------------------------------------------
-// Tree event
-// ----------------------------------------------------------------------------
-
-IMPLEMENT_DYNAMIC_CLASS(wxTreeEvent, wxNotifyEvent)
-
-wxTreeEvent::wxTreeEvent(wxEventType commandType, int id)
- : wxNotifyEvent(commandType, id)
-{
- m_code = 0;
- m_itemOld = 0;
-}
-
#endif // __WIN95__
+#endif // wxUSE_TREECTRL