#pragma hdrstop
#endif
-#include "wx/window.h"
#include "wx/msw/private.h"
// Mingw32 is a bit mental even though this is done in winundef
#include "wx/treectrl.h"
#include "wx/settings.h"
-#ifdef __GNUWIN32__
-#ifndef wxUSE_NORLANDER_HEADERS
-#include "wx/msw/gnuwin32/extra.h"
-#endif
+#include "wx/msw/dragimag.h"
+
+#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__))
#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("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("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;
};
// wxTreeItemIndirectData. So we have to maintain a list of all items which
// have indirect data inside the listctrl itself.
// ----------------------------------------------------------------------------
+
class wxTreeItemIndirectData
{
public:
};
// ----------------------------------------------------------------------------
-// macros
+// wxWin macros
// ----------------------------------------------------------------------------
-#if !USE_SHARED_LIBRARY
- IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
-#endif
+IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
// ----------------------------------------------------------------------------
// variables
m_imageListNormal = NULL;
m_imageListState = NULL;
m_textCtrl = NULL;
+ m_hasAnyAttr = FALSE;
+ m_dragImage = NULL;
+
+ m_htSelStart = 0;
}
bool wxTreeCtrl::Create(wxWindow *parent,
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) )
wxTreeCtrl::~wxTreeCtrl()
{
+ // delete any attributes
+ if ( m_hasAnyAttr )
+ {
+ for ( wxNode *node = m_attrs.Next(); node; node = m_attrs.Next() )
+ {
+ delete (wxTreeItemAttr *)node->Data();
+ }
+
+ // prevent TVN_DELETEITEM handler from deleting the attributes again!
+ m_hasAnyAttr = FALSE;
+ }
+
DeleteTextCtrl();
// delete user data to prevent memory leaks
return counter.GetCount() - 1;
}
+// ----------------------------------------------------------------------------
+// control colours
+// ----------------------------------------------------------------------------
+
+bool wxTreeCtrl::SetBackgroundColour(const wxColour &colour)
+{
+ if ( !wxWindowBase::SetBackgroundColour(colour) )
+ return FALSE;
+
+ SendMessage(GetHwnd(), TVM_SETBKCOLOR, 0, colour.GetPixel());
+
+ return TRUE;
+}
+
+bool wxTreeCtrl::SetForegroundColour(const wxColour &colour)
+{
+ if ( !wxWindowBase::SetForegroundColour(colour) )
+ return FALSE;
+
+ SendMessage(GetHwnd(), TVM_SETTEXTCOLOR, 0, colour.GetPixel());
+
+ return TRUE;
+}
+
// ----------------------------------------------------------------------------
// Item access
// ----------------------------------------------------------------------------
DoSetItem(&tvItem);
}
+void wxTreeCtrl::SetItemTextColour(const wxTreeItemId& item,
+ const wxColour& col)
+{
+ m_hasAnyAttr = TRUE;
+
+ long id = (long)(WXHTREEITEM)item;
+ wxTreeItemAttr *attr = (wxTreeItemAttr *)m_attrs.Get(id);
+ if ( !attr )
+ {
+ attr = new wxTreeItemAttr;
+ m_attrs.Put(id, (wxObject *)attr);
+ }
+
+ attr->SetTextColour(col);
+}
+
+void wxTreeCtrl::SetItemBackgroundColour(const wxTreeItemId& item,
+ const wxColour& col)
+{
+ m_hasAnyAttr = TRUE;
+
+ long id = (long)(WXHTREEITEM)item;
+ wxTreeItemAttr *attr = (wxTreeItemAttr *)m_attrs.Get(id);
+ if ( !attr )
+ {
+ attr = new wxTreeItemAttr;
+ m_attrs.Put(id, (wxObject *)attr);
+ }
+
+ attr->SetBackgroundColour(col);
+}
+
+void wxTreeCtrl::SetItemFont(const wxTreeItemId& item, const wxFont& font)
+{
+ m_hasAnyAttr = TRUE;
+
+ long id = (long)(WXHTREEITEM)item;
+ wxTreeItemAttr *attr = (wxTreeItemAttr *)m_attrs.Get(id);
+ if ( !attr )
+ {
+ attr = new wxTreeItemAttr;
+ m_attrs.Put(id, (wxObject *)attr);
+ }
+
+ attr->SetFont(font);
+}
+
// ----------------------------------------------------------------------------
// Item status
// ----------------------------------------------------------------------------
{
// Bug in Gnu-Win32 headers, so don't use the macro TreeView_GetItemRect
RECT rect;
+
+ // this ugliness comes directly from MSDN - it *is* the correct way to pass
+ // the HTREEITEM with TVM_GETITEMRECT
+ *(WXHTREEITEM *)&rect = (WXHTREEITEM)item;
+
+ // FALSE means get item rect for the whole item, not only text
return SendMessage(GetHwnd(), TVM_GETITEMRECT, FALSE, (LPARAM)&rect) != 0;
}
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
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!"));
- 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
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");
}
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));
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");
}
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");
}
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;
}
{
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->SetHWND((WXHWND)hWnd);
m_textCtrl->SubclassWin((WXHWND)hWnd);
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;
}
+// 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)
+{
+ bool processed = FALSE;
+ long rc = 0;
+
+ bool isMultiple = (GetWindowStyle() & wxTR_MULTIPLE) != 0;
+
+ if ( (nMsg >= WM_MOUSEFIRST) && (nMsg <= WM_MOUSELAST) )
+ {
+ // 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);
+
+ switch ( nMsg )
+ {
+#if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
+ case WM_LBUTTONDOWN:
+ if ( htItem && isMultiple )
+ {
+ if ( wParam & MK_CONTROL )
+ {
+ 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);
+ }
+ }
+ }
+ 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)
{
switch ( hdr->code )
{
case NM_RCLICK:
- {
- if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
- return TRUE;
-
- TV_HITTESTINFO tvhti;
- ::GetCursorPos(&(tvhti.pt));
- ::ScreenToClient(GetHwnd(),&(tvhti.pt));
- if ( TreeView_HitTest(GetHwnd(),&tvhti) )
{
- if( tvhti.flags & TVHT_ONITEM )
+ if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
+ return TRUE;
+
+ TV_HITTESTINFO tvhti;
+ ::GetCursorPos(&(tvhti.pt));
+ ::ScreenToClient(GetHwnd(),&(tvhti.pt));
+ if ( TreeView_HitTest(GetHwnd(),&tvhti) )
{
- event.m_item = (WXHTREEITEM) tvhti.hItem;
- eventType=wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK;
+ if( tvhti.flags & TVHT_ONITEM )
+ {
+ event.m_item = (WXHTREEITEM) tvhti.hItem;
+ eventType = wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK;
+ }
}
}
break;
- }
case TVN_BEGINDRAG:
eventType = wxEVT_COMMAND_TREE_BEGIN_DRAG;
event.m_item = (WXHTREEITEM) tv->itemNew.hItem;
event.m_pointDrag = wxPoint(tv->ptDrag.x, tv->ptDrag.y);
- break;
+
+ // 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;
case TVN_BEGINLABELEDIT:
{
event.m_item = (WXHTREEITEM) info->item.hItem;
event.m_label = info->item.pszText;
- break;
}
+ break;
case TVN_DELETEITEM:
{
eventType = wxEVT_COMMAND_TREE_DELETE_ITEM;
NM_TREEVIEW *tv = (NM_TREEVIEW *)lParam;
- event.m_item = (WXHTREEITEM) tv->itemOld.hItem;
- break;
+ event.m_item = (WXHTREEITEM)tv->itemOld.hItem;
+
+ if ( m_hasAnyAttr )
+ {
+ delete (wxTreeItemAttr *)m_attrs.
+ Delete((long)tv->itemOld.hItem);
+ }
}
+ break;
case TVN_ENDLABELEDIT:
{
eventType = g_events[expand][ing];
event.m_item = (WXHTREEITEM) tv->itemNew.hItem;
- break;
}
+ break;
case TVN_KEYDOWN:
{
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;
case TVN_SELCHANGED:
eventType = wxEVT_COMMAND_TREE_SEL_CHANGED;
event.m_item = (WXHTREEITEM) tv->itemNew.hItem;
event.m_itemOld = (WXHTREEITEM) tv->itemOld.hItem;
- break;
}
+ break;
+
+#if defined(_WIN32_IE) && _WIN32_IE >= 0x300
+ case NM_CUSTOMDRAW:
+ {
+ LPNMTVCUSTOMDRAW lptvcd = (LPNMTVCUSTOMDRAW)lParam;
+ NMCUSTOMDRAW& nmcd = lptvcd->nmcd;
+ switch( nmcd.dwDrawStage )
+ {
+ case CDDS_PREPAINT:
+ // if we've got any items with non standard attributes,
+ // notify us before painting each item
+ *result = m_hasAnyAttr ? CDRF_NOTIFYITEMDRAW
+ : CDRF_DODEFAULT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ wxTreeItemAttr *attr =
+ (wxTreeItemAttr *)m_attrs.Get(nmcd.dwItemSpec);
+
+ if ( !attr )
+ {
+ // nothing to do for this item
+ return CDRF_DODEFAULT;
+ }
+
+ HFONT hFont;
+ wxColour colText, colBack;
+ if ( attr->HasFont() )
+ {
+ wxFont font = attr->GetFont();
+ hFont = (HFONT)font.GetResourceHandle();
+ }
+ else
+ {
+ hFont = 0;
+ }
+
+ if ( attr->HasTextColour() )
+ {
+ colText = attr->GetTextColour();
+ }
+ else
+ {
+ colText = GetForegroundColour();
+ }
+
+ // selection colours should override ours
+ if ( nmcd.uItemState & CDIS_SELECTED )
+ {
+ DWORD clrBk = ::GetSysColor(COLOR_HIGHLIGHT);
+ lptvcd->clrTextBk = clrBk;
+
+ // try to make the text visible
+ lptvcd->clrText = wxColourToRGB(colText);
+ lptvcd->clrText |= ~clrBk;
+ lptvcd->clrText &= 0x00ffffff;
+ }
+ else
+ {
+ if ( attr->HasBackgroundColour() )
+ {
+ colBack = attr->GetBackgroundColour();
+ }
+ else
+ {
+ colBack = GetBackgroundColour();
+ }
+
+ lptvcd->clrText = wxColourToRGB(colText);
+ lptvcd->clrTextBk = wxColourToRGB(colBack);
+ }
+
+ // note that if we wanted to set colours for
+ // individual columns (subitems), we would have
+ // returned CDRF_NOTIFYSUBITEMREDRAW from here
+ if ( hFont )
+ {
+ ::SelectObject(nmcd.hdc, hFont);
+
+ *result = CDRF_NEWFONT;
+ }
+ else
+ {
+ *result = CDRF_DODEFAULT;
+ }
+
+ return TRUE;
+ }
+
+ default:
+ *result = CDRF_DODEFAULT;
+ return TRUE;
+ }
+ }
+ break;
+#endif // _WIN32_IE >= 0x300
default:
return wxControl::MSWOnNotify(idCtrl, lParam, result);
// post processing
switch ( hdr->code )
{
+ 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