#include "wx/settings.h"
#endif
+#include "wx/dynlib.h"
#include "wx/msw/private.h"
// Set this to 1 to be _absolutely_ sure that repainting will work for all
HTREEITEM TreeItemUnlocker::ms_unlockedItem = NULL;
+// another helper class: set the variable to true during its lifetime and reset
+// it to false when it is destroyed
+//
+// it is currently always used with wxTreeCtrl::m_changingSelection
+class TempSetter
+{
+public:
+ TempSetter(bool& var) : m_var(var)
+ {
+ wxASSERT_MSG( !m_var, "variable shouldn't be already set" );
+ m_var = true;
+ }
+
+ ~TempSetter()
+ {
+ m_var = false;
+ }
+
+private:
+ bool& m_var;
+
+ wxDECLARE_NO_COPY_CLASS(TempSetter);
+};
+
// ----------------------------------------------------------------------------
// 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;
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,
+// deselects all the other ones
+//
+// returns true if the selection changed at all or false if nothing changed
+
+// flags for SelectRange()
+enum
+{
+ SR_SIMULATE = 1, // don't do anything, just return true or false
+ SR_UNSELECT_OTHERS = 2 // deselect the items not in range
+};
+
+static bool SelectRange(HWND hwndTV,
HTREEITEM htFirst,
HTREEITEM htLast,
- bool unselectOthers = true)
+ int flags)
{
// find the first (or last) item and select it
+ bool changed = false;
bool cont = true;
HTREEITEM htItem = (HTREEITEM)TreeView_GetRoot(hwndTV);
+
while ( htItem && cont )
{
if ( (htItem == htFirst) || (htItem == htLast) )
{
if ( !IsItemSelected(hwndTV, htItem) )
{
- SelectItem(hwndTV, htItem);
+ if ( !(flags & SR_SIMULATE) )
+ {
+ SelectItem(hwndTV, htItem);
+ }
+
+ changed = true;
}
cont = false;
}
- else
+ else // not first or last
{
- if ( unselectOthers && IsItemSelected(hwndTV, htItem) )
+ if ( flags & SR_UNSELECT_OTHERS )
{
- UnselectItem(hwndTV, htItem);
+ if ( IsItemSelected(hwndTV, htItem) )
+ {
+ if ( !(flags & SR_SIMULATE) )
+ UnselectItem(hwndTV, htItem);
+
+ changed = true;
+ }
}
}
{
if ( !IsItemSelected(hwndTV, htItem) )
{
- SelectItem(hwndTV, htItem);
+ if ( !(flags & SR_SIMULATE) )
+ {
+ SelectItem(hwndTV, htItem);
+ }
+
+ changed = true;
}
cont = (htItem != htFirst) && (htItem != htLast);
htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
}
- // unselect the rest
- if ( unselectOthers )
+ // optionally deselect the rest
+ if ( flags & SR_UNSELECT_OTHERS )
{
while ( htItem )
{
if ( IsItemSelected(hwndTV, htItem) )
{
- UnselectItem(hwndTV, htItem);
+ if ( !(flags & SR_SIMULATE) )
+ {
+ UnselectItem(hwndTV, htItem);
+ }
+
+ changed = true;
}
htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
// seems to be necessary - otherwise the just selected items don't always
// appear as selected
- UpdateWindow(hwndTV);
+ if ( !(flags & SR_SIMULATE) )
+ {
+ UpdateWindow(hwndTV);
+ }
+
+ return changed;
}
// helper function which tricks the standard control into changing the focused
break;
default:
- wxFAIL_MSG( _T("unsupported wxTreeItemIcon value") );
+ wxFAIL_MSG( wxT("unsupported wxTreeItemIcon value") );
}
}
// the real client data
wxTreeItemData *m_data;
- DECLARE_NO_COPY_CLASS(wxTreeItemParam)
+ wxDECLARE_NO_COPY_CLASS(wxTreeItemParam);
};
// wxVirutalNode is used in place of a single root when 'hidden' root is
private:
wxTreeItemParam *m_param;
- DECLARE_NO_COPY_CLASS(wxVirtualNode)
+ wxDECLARE_NO_COPY_CLASS(wxVirtualNode);
};
#ifdef __VISUALC__
const wxTreeCtrl *m_tree;
- DECLARE_NO_COPY_CLASS(wxTreeTraversal)
+ wxDECLARE_NO_COPY_CLASS(wxTreeTraversal);
};
// internal class for getting the selected items
private:
wxArrayTreeItemIds& m_selections;
- DECLARE_NO_COPY_CLASS(TraverseSelections)
+ wxDECLARE_NO_COPY_CLASS(TraverseSelections);
};
// internal class for counting tree items
private:
size_t m_count;
- DECLARE_NO_COPY_CLASS(TraverseCounter)
+ wxDECLARE_NO_COPY_CLASS(TraverseCounter);
};
// ----------------------------------------------------------------------------
m_dragImage = NULL;
#endif
m_pVirtualRoot = NULL;
+ m_dragStarted = false;
+ m_focusLost = true;
+ m_changingSelection = false;
+ m_triggerStateImageClick = false;
+ m_mouseUpDeselect = false;
// initialize the global array of events now as it can't be done statically
// with the wxEVT_XXX values being allocated during run-time only
DWORD wstyle = MSWGetStyle(m_windowStyle, & exStyle);
wstyle |= WS_TABSTOP | TVS_SHOWSELALWAYS;
- if ((m_windowStyle & wxTR_NO_LINES) == 0)
+ if ( !(m_windowStyle & wxTR_NO_LINES) )
wstyle |= TVS_HASLINES;
if ( m_windowStyle & wxTR_HAS_BUTTONS )
wstyle |= TVS_HASBUTTONS;
bool wxTreeCtrl::DoGetItem(wxTreeViewItem *tvItem) const
{
wxCHECK_MSG( tvItem->hItem != TVI_ROOT, false,
- _T("can't retrieve virtual root item") );
+ wxT("can't retrieve virtual root item") );
if ( !TreeView_GetItem(GetHwnd(), tvItem) )
{
return (wxTreeItemParam *)tvItem.lParam;
}
+bool wxTreeCtrl::HandleTreeEvent(wxTreeEvent& event) const
+{
+ if ( event.m_item.IsOk() )
+ {
+ event.SetClientObject(GetItemData(event.m_item));
+ }
+
+ return HandleWindowEvent(event);
+}
+
wxTreeItemData *wxTreeCtrl::GetItemData(const wxTreeItemId& item) const
{
wxTreeItemParam *data = GetItemParam(item);
{
wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
+ if ( IS_VIRTUAL_ROOT(item) )
+ {
+ wxTreeItemIdValue cookie;
+ return GetFirstChild(item, cookie).IsOk();
+ }
+
wxTreeViewItem tvItem(item, TVIF_CHILDREN);
DoGetItem(&tvItem);
wxTreeItemId wxTreeCtrl::GetSelection() const
{
- wxCHECK_MSG( !(m_windowStyle & wxTR_MULTIPLE), wxTreeItemId(),
+ wxCHECK_MSG( !HasFlag(wxTR_MULTIPLE), wxTreeItemId(),
wxT("this only works with single selection controls") );
+ return GetFocusedItem();
+}
+
+wxTreeItemId wxTreeCtrl::GetFocusedItem() const
+{
return wxTreeItemId(TreeView_GetSelection(GetHwnd()));
}
{
wxCHECK_MSG( parent.IsOk() || !TreeView_GetRoot(GetHwnd()),
wxTreeItemId(),
- _T("can't have more than one root in the tree") );
+ wxT("can't have more than one root in the tree") );
TV_INSERTSTRUCT tvIns;
tvIns.hParent = HITEM(parent);
{
if ( HasFlag(wxTR_HIDE_ROOT) )
{
- wxASSERT_MSG( !m_pVirtualRoot, _T("tree can have only a single root") );
+ wxASSERT_MSG( !m_pVirtualRoot, wxT("tree can have only a single root") );
// create a virtual root item, the parent for all the others
wxTreeItemParam *param = new wxTreeItemParam;
// 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") );
+ wxASSERT_MSG( index == 0, wxT("bad index in wxTreeCtrl::InsertItem") );
}
return DoInsertAfter(parent, idPrev, text, image, selectedImage, data);
// tree ctrl will eventually crash after item deletion
TreeItemUnlocker unlock_all;
- if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
+ if ( HasFlag(wxTR_MULTIPLE) )
+ {
+ bool selected = IsSelected(item);
+ wxTreeItemId next;
+
+ if ( selected )
+ {
+ next = TreeView_GetNextVisible(GetHwnd(), HITEM(item));
+
+ if ( !next.IsOk() )
+ {
+ next = TreeView_GetPrevVisible(GetHwnd(), HITEM(item));
+ }
+ }
+
+ {
+ TempSetter set(m_changingSelection);
+ if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
+ {
+ wxLogLastError(wxT("TreeView_DeleteItem"));
+ return;
+ }
+ }
+
+ if ( !selected )
+ {
+ return;
+ }
+
+ if ( item == m_htSelStart )
+ m_htSelStart.Unset();
+
+ if ( item == m_htClickedItem )
+ m_htClickedItem.Unset();
+
+ if ( next.IsOk() )
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this, next);
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED, this, next);
+ (void)HandleTreeEvent(changedEvent);
+ }
+ else
+ {
+ DoUnselectItem(next);
+ ClearFocusedItem();
+ }
+ }
+ }
+ else
{
- wxLogLastError(wxT("TreeView_DeleteItem"));
+ if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
+ {
+ wxLogLastError(wxT("TreeView_DeleteItem"));
+ }
}
}
size_t nCount = children.Count();
for ( size_t n = 0; n < nCount; n++ )
{
- if ( !TreeView_DeleteItem(GetHwnd(), HITEM(children[n])) )
- {
- wxLogLastError(wxT("TreeView_DeleteItem"));
- }
+ Delete(children[n]);
}
}
// unlock tree selections on vista for the duration of this call
TreeItemUnlocker unlock_all;
+ // invalidate all the items we store as they're going to become invalid
+ m_htSelStart =
+ m_htClickedItem = wxTreeItemId();
+
// delete the "virtual" root item.
if ( GET_VIRTUAL_ROOT() )
{
tvItem.state = 0;
DoSetItem(&tvItem);
- if ( TreeView_Expand(GetHwnd(), HITEM(item), flag) != 0 )
+ if ( IsExpanded(item) )
+ {
+ wxTreeEvent event(wxEVT_COMMAND_TREE_ITEM_COLLAPSING,
+ this, wxTreeItemId(item));
+
+ if ( !IsTreeEventAllowed(event) )
+ return;
+ }
+
+ if ( TreeView_Expand(GetHwnd(), HITEM(item), flag) )
{
- // note that the {EXPAND|COLLAPS}ING event is sent by TreeView_Expand()
- // itself
- wxTreeEvent event(gs_expandEvents[IsExpanded(item) ? IDX_EXPAND
- : IDX_COLLAPSE]
- [IDX_DONE],
- this, item);
- (void)HandleWindowEvent(event);
+ if ( IsExpanded(item) )
+ return;
+
+ wxTreeEvent event(wxEVT_COMMAND_TREE_ITEM_COLLAPSED, this, item);
+ (void)HandleTreeEvent(event);
}
//else: change didn't took place, so do nothing at all
}
void wxTreeCtrl::Unselect()
{
- wxASSERT_MSG( !(m_windowStyle & wxTR_MULTIPLE),
+ wxASSERT_MSG( !HasFlag(wxTR_MULTIPLE),
wxT("doesn't make sense, may be you want UnselectAll()?") );
- // just remove the selection
- SelectItem(wxTreeItemId());
+ // the current focus
+ HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+
+ if ( !htFocus )
+ {
+ return;
+ }
+
+ if ( HasFlag(wxTR_MULTIPLE) )
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, wxTreeItemId());
+ changingEvent.m_itemOld = htFocus;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ ClearFocusedItem();
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, wxTreeItemId());
+ changedEvent.m_itemOld = htFocus;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ else
+ {
+ ClearFocusedItem();
+ }
+}
+
+void wxTreeCtrl::DoUnselectAll()
+{
+ wxArrayTreeItemIds selections;
+ size_t count = GetSelections(selections);
+
+ for ( size_t n = 0; n < count; n++ )
+ {
+ DoUnselectItem(selections[n]);
+ }
+
+ m_htSelStart.Unset();
}
void wxTreeCtrl::UnselectAll()
{
- if ( m_windowStyle & wxTR_MULTIPLE )
+ if ( HasFlag(wxTR_MULTIPLE) )
{
- wxArrayTreeItemIds selections;
- size_t count = GetSelections(selections);
- for ( size_t n = 0; n < count; n++ )
+ HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+ if ( !htFocus ) return;
+
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this);
+ changingEvent.m_itemOld = htFocus;
+
+ if ( IsTreeEventAllowed(changingEvent) )
{
- ::UnselectItem(GetHwnd(), HITEM(selections[n]));
- }
+ DoUnselectAll();
- m_htSelStart.Unset();
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED, this);
+ changedEvent.m_itemOld = htFocus;
+ (void)HandleTreeEvent(changedEvent);
+ }
}
else
{
- // just remove the selection
Unselect();
}
}
+void wxTreeCtrl::DoSelectChildren(const wxTreeItemId& parent)
+{
+ DoUnselectAll();
+
+ wxTreeItemIdValue cookie;
+ wxTreeItemId child = GetFirstChild(parent, cookie);
+ while ( child.IsOk() )
+ {
+ DoSelectItem(child, true);
+ child = GetNextChild(child, cookie);
+ }
+}
+
+void wxTreeCtrl::SelectChildren(const wxTreeItemId& parent)
+{
+ wxCHECK_RET( HasFlag(wxTR_MULTIPLE),
+ "this only works with multiple selection controls" );
+
+ HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this);
+ changingEvent.m_itemOld = htFocus;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoSelectChildren(parent);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED, this);
+ changedEvent.m_itemOld = htFocus;
+ (void)HandleTreeEvent(changedEvent);
+ }
+}
+
+void wxTreeCtrl::DoSelectItem(const wxTreeItemId& item, bool select)
+{
+ TempSetter set(m_changingSelection);
+
+ ::SelectItem(GetHwnd(), HITEM(item), select);
+}
+
void wxTreeCtrl::SelectItem(const wxTreeItemId& item, bool select)
{
- wxCHECK_RET( !IsHiddenRoot(item), _T("can't select hidden root item") );
+ wxCHECK_RET( !IsHiddenRoot(item), wxT("can't select hidden root item") );
- wxASSERT_MSG( select || HasFlag(wxTR_MULTIPLE),
- _T("SelectItem(false) works only for multiselect") );
+ if ( select == IsSelected(item) )
+ {
+ // nothing to do, the item is already in the requested state
+ return;
+ }
- wxTreeEvent event(wxEVT_COMMAND_TREE_SEL_CHANGING, this, item);
- if ( !HandleWindowEvent(event) || event.IsAllowed() )
+ if ( HasFlag(wxTR_MULTIPLE) )
{
- if ( HasFlag(wxTR_MULTIPLE) )
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this, item);
+
+ if ( IsTreeEventAllowed(changingEvent) )
{
- if ( !::SelectItem(GetHwnd(), HITEM(item), select) )
+ HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+ DoSelectItem(item, select);
+
+ if ( !htFocus )
{
- wxLogLastError(wxT("TreeView_SelectItem"));
- return;
+ SetFocusedItem(item);
}
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, item);
+ (void)HandleTreeEvent(changedEvent);
}
- else // single selection
+ }
+ else // single selection
+ {
+ wxTreeItemId itemOld, itemNew;
+ if ( select )
+ {
+ itemOld = GetSelection();
+ itemNew = item;
+ }
+ else // deselecting the currently selected item
+ {
+ itemOld = item;
+ // leave itemNew invalid
+ }
+
+ // Recent versions of comctl32.dll send TVN_SELCHANG{ED,ING} events
+ // when we call TreeView_SelectItem() but apparently some old ones did
+ // not so send the events ourselves and ignore those generated by
+ // TreeView_SelectItem() if m_changingSelection is set.
+ wxTreeEvent
+ changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this, itemNew);
+ changingEvent.SetOldItem(itemOld);
+
+ if ( IsTreeEventAllowed(changingEvent) )
{
- // use TreeView_SelectItem() to deselect the previous selection
- if ( !TreeView_SelectItem(GetHwnd(), HITEM(item)) )
+ TempSetter set(m_changingSelection);
+
+ if ( !TreeView_SelectItem(GetHwnd(), HITEM(itemNew)) )
{
wxLogLastError(wxT("TreeView_SelectItem"));
- return;
}
- }
+ else // ok
+ {
+ ::SetFocus(GetHwnd(), HITEM(item));
- event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGED);
- (void)HandleWindowEvent(event);
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, itemNew);
+ changedEvent.SetOldItem(itemOld);
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ //else: program vetoed the change
}
- //else: program vetoed the change
}
void wxTreeCtrl::EnsureVisible(const wxTreeItemId& item)
{
- wxCHECK_RET( !IsHiddenRoot(item), _T("can't show hidden root item") );
+ wxCHECK_RET( !IsHiddenRoot(item), wxT("can't show hidden root item") );
// no error return
TreeView_EnsureVisible(GetHwnd(), HITEM(item));
m_textCtrl->UnsubclassWin();
m_textCtrl->SetHWND(0);
- delete m_textCtrl;
- m_textCtrl = NULL;
+ wxDELETE(m_textCtrl);
m_idEdited.Unset();
}
// returned false
if ( !hWnd )
{
- delete m_textCtrl;
- m_textCtrl = NULL;
+ wxDELETE(m_textCtrl);
return NULL;
}
}
}
+void wxTreeCtrl::ClearFocusedItem()
+{
+ TempSetter set(m_changingSelection);
+
+ if ( !TreeView_SelectItem(GetHwnd(), 0) )
+ {
+ wxLogLastError(wxT("TreeView_SelectItem"));
+ }
+}
+
+void wxTreeCtrl::SetFocusedItem(const wxTreeItemId& item)
+{
+ wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
+
+ TempSetter set(m_changingSelection);
+
+ ::SetFocus(GetHwnd(), HITEM(item));
+}
+
+void wxTreeCtrl::DoUnselectItem(const wxTreeItemId& item)
+{
+ TempSetter set(m_changingSelection);
+
+ ::UnselectItem(GetHwnd(), HITEM(item));
+}
+
+void wxTreeCtrl::DoToggleItemSelection(const wxTreeItemId& item)
+{
+ TempSetter set(m_changingSelection);
+
+ ::ToggleItemSelection(GetHwnd(), HITEM(item));
+}
+
// ----------------------------------------------------------------------------
// sorting stuff
// ----------------------------------------------------------------------------
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
-WXLRESULT wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
+bool wxTreeCtrl::MSWIsOnItem(unsigned flags) const
{
- bool processed = false;
- WXLRESULT rc = 0;
- bool isMultiple = HasFlag(wxTR_MULTIPLE);
+ unsigned mask = TVHT_ONITEM;
+ if ( HasFlag(wxTR_FULL_ROW_HIGHLIGHT) )
+ mask |= TVHT_ONITEMINDENT | TVHT_ONITEMRIGHT;
- // This message is sent after a right-click, or when the "menu" key is pressed
- if ( nMsg == WM_CONTEXTMENU )
- {
- int x = GET_X_LPARAM(lParam),
- y = GET_Y_LPARAM(lParam);
+ return (flags & mask) != 0;
+}
- // the item for which the menu should be shown
- wxTreeItemId item;
+bool wxTreeCtrl::MSWHandleSelectionKey(unsigned vkey)
+{
+ const bool bCtrl = wxIsCtrlDown();
+ const bool bShift = wxIsShiftDown();
+ const HTREEITEM htSel = (HTREEITEM)TreeView_GetSelection(GetHwnd());
- // the position where the menu should be shown in client coordinates
- // (so that it can be passed directly to PopupMenu())
- wxPoint pt;
+ switch ( vkey )
+ {
+ case VK_RETURN:
+ case VK_SPACE:
+ if ( !htSel )
+ break;
- if ( x == -1 || y == -1 )
- {
- // this means that the event was generated from keyboard (e.g. with
- // Shift-F10 or special Windows menu key)
- //
- // use the Explorer standard of putting the menu at the left edge
- // of the text, in the vertical middle of the text
- item = wxTreeItemId(TreeView_GetSelection(GetHwnd()));
- if ( item.IsOk() )
+ if ( vkey != VK_RETURN && bCtrl )
{
- // Use the bounding rectangle of only the text part
- wxRect rect;
- GetBoundingRect(item, rect, true);
- pt = wxPoint(rect.GetX(), rect.GetY() + rect.GetHeight() / 2);
- }
- }
- else // event from mouse, use mouse position
- {
- pt = ScreenToClient(wxPoint(x, y));
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htSel);
+ changingEvent.m_itemOld = htSel;
- TV_HITTESTINFO tvhti;
- tvhti.pt.x = pt.x;
- tvhti.pt.y = pt.y;
- if ( TreeView_HitTest(GetHwnd(), &tvhti) )
- item = wxTreeItemId(tvhti.hItem);
- }
-
- // create the event
- wxTreeEvent event(wxEVT_COMMAND_TREE_ITEM_MENU, this, item);
-
- event.m_pointDrag = pt;
-
- if ( HandleWindowEvent(event) )
- processed = true;
- //else: continue with generating wxEVT_CONTEXT_MENU in base class code
- }
- else 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);
-
- TV_HITTESTINFO tvht;
- tvht.pt.x = x;
- tvht.pt.y = y;
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoToggleItemSelection(wxTreeItemId(htSel));
- HTREEITEM htItem = TreeView_HitTest(GetHwnd(), &tvht);
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htSel);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ else
+ {
+ wxArrayTreeItemIds selections;
+ size_t count = GetSelections(selections);
- switch ( nMsg )
- {
- case WM_LBUTTONDOWN:
- if ( htItem && isMultiple && (tvht.flags & TVHT_ONITEM) != 0 )
+ if ( count != 1 || HITEM(selections[0]) != htSel )
{
- m_htClickedItem = (WXHTREEITEM) htItem;
- m_ptClick = wxPoint(x, y);
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htSel);
+ changingEvent.m_itemOld = htSel;
- if ( wParam & MK_CONTROL )
+ if ( IsTreeEventAllowed(changingEvent) )
{
- SetFocus();
+ DoUnselectAll();
+ DoSelectItem(wxTreeItemId(htSel));
- // toggle selected state
- ToggleItemSelection(htItem);
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htSel);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ }
+ break;
- ::SetFocus(GetHwnd(), htItem);
+ case VK_UP:
+ case VK_DOWN:
+ if ( !bCtrl && !bShift )
+ {
+ wxArrayTreeItemIds selections;
+ wxTreeItemId next;
- // reset on any click without Shift
- m_htSelStart.Unset();
+ if ( htSel )
+ {
+ next = vkey == VK_UP
+ ? TreeView_GetPrevVisible(GetHwnd(), htSel)
+ : TreeView_GetNextVisible(GetHwnd(), htSel);
+ }
+ else
+ {
+ next = GetRootItem();
- processed = true;
+ if ( IsHiddenRoot(next) )
+ next = TreeView_GetChild(GetHwnd(), HITEM(next));
+ }
+
+ if ( !next.IsOk() )
+ {
+ break;
+ }
+
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, next);
+ changingEvent.m_itemOld = htSel;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ DoSelectItem(next);
+ SetFocusedItem(next);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ else if ( htSel )
+ {
+ wxTreeItemId next = vkey == VK_UP
+ ? TreeView_GetPrevVisible(GetHwnd(), htSel)
+ : TreeView_GetNextVisible(GetHwnd(), htSel);
+
+ if ( !next.IsOk() )
+ {
+ break;
+ }
+
+ if ( !m_htSelStart )
+ {
+ m_htSelStart = htSel;
+ }
+
+ if ( bShift && SelectRange(GetHwnd(), HITEM(m_htSelStart), HITEM(next),
+ SR_UNSELECT_OTHERS | SR_SIMULATE) )
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this, next);
+ changingEvent.m_itemOld = htSel;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ SelectRange(GetHwnd(), HITEM(m_htSelStart), HITEM(next),
+ SR_UNSELECT_OTHERS);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED, this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+
+ SetFocusedItem(next);
+ }
+ break;
+
+ case VK_LEFT:
+ if ( HasChildren(htSel) && IsExpanded(htSel) )
+ {
+ Collapse(htSel);
+ }
+ else
+ {
+ wxTreeItemId next = GetItemParent(htSel);
+
+ if ( next.IsOk() && !IsHiddenRoot(next) )
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, next);
+ changingEvent.m_itemOld = htSel;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ DoSelectItem(next);
+ SetFocusedItem(next);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ }
+ break;
+
+ case VK_RIGHT:
+ if ( !IsVisible(htSel) )
+ {
+ EnsureVisible(htSel);
+ }
+
+ if ( !HasChildren(htSel) )
+ break;
+
+ if ( !IsExpanded(htSel) )
+ {
+ Expand(htSel);
+ }
+ else
+ {
+ wxTreeItemId next = TreeView_GetChild(GetHwnd(), htSel);
+
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING, this, next);
+ changingEvent.m_itemOld = htSel;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ DoSelectItem(next);
+ SetFocusedItem(next);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED, this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ break;
+
+ case VK_HOME:
+ case VK_END:
+ {
+ wxTreeItemId next = GetRootItem();
+
+ if ( IsHiddenRoot(next) )
+ {
+ next = TreeView_GetChild(GetHwnd(), HITEM(next));
+ }
+
+ if ( !next.IsOk() )
+ break;
+
+ if ( vkey == VK_END )
+ {
+ for ( ;; )
+ {
+ wxTreeItemId nextTemp = TreeView_GetNextVisible(
+ GetHwnd(), HITEM(next));
+
+ if ( !nextTemp.IsOk() )
+ break;
+
+ next = nextTemp;
}
- else if ( wParam & MK_SHIFT )
+ }
+
+ if ( htSel == HITEM(next) )
+ break;
+
+ if ( bShift )
+ {
+ if ( !m_htSelStart )
{
- // this selects all items between the starting one and
- // the current
+ m_htSelStart = htSel;
+ }
- if ( !m_htSelStart )
+ if ( SelectRange(GetHwnd(),
+ HITEM(m_htSelStart), HITEM(next),
+ SR_UNSELECT_OTHERS | SR_SIMULATE) )
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, next);
+ changingEvent.m_itemOld = htSel;
+
+ if ( IsTreeEventAllowed(changingEvent) )
{
- // take the focused item
- m_htSelStart = TreeView_GetSelection(GetHwnd());
+ SelectRange(GetHwnd(),
+ HITEM(m_htSelStart), HITEM(next),
+ SR_UNSELECT_OTHERS);
+ SetFocusedItem(next);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
}
+ }
+ }
+ else // no Shift
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, next);
+ changingEvent.m_itemOld = htSel;
- if ( m_htSelStart )
- SelectRange(GetHwnd(), HITEM(m_htSelStart), htItem,
- !(wParam & MK_CONTROL));
- else
- ::SelectItem(GetHwnd(), htItem);
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ DoSelectItem(next);
+ SetFocusedItem(next);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ }
+ break;
- ::SetFocus(GetHwnd(), htItem);
+ case VK_PRIOR:
+ case VK_NEXT:
+ if ( bCtrl )
+ {
+ wxTreeItemId firstVisible = GetFirstVisibleItem();
+ size_t visibleCount = TreeView_GetVisibleCount(GetHwnd());
+ wxTreeItemId nextAdjacent = (vkey == VK_PRIOR) ?
+ TreeView_GetPrevVisible(GetHwnd(), HITEM(firstVisible)) :
+ TreeView_GetNextVisible(GetHwnd(), HITEM(firstVisible));
- processed = true;
+ if ( !nextAdjacent )
+ {
+ break;
+ }
+
+ wxTreeItemId nextStart = firstVisible;
+
+ for ( size_t n = 1; n < visibleCount; n++ )
+ {
+ wxTreeItemId nextTemp = (vkey == VK_PRIOR) ?
+ TreeView_GetPrevVisible(GetHwnd(), HITEM(nextStart)) :
+ TreeView_GetNextVisible(GetHwnd(), HITEM(nextStart));
+
+ if ( nextTemp.IsOk() )
+ {
+ nextStart = nextTemp;
}
- else // normal click
+ else
{
- // avoid doing anything if we click on the only
- // currently selected item
+ break;
+ }
+ }
- SetFocus();
+ EnsureVisible(nextStart);
- wxArrayTreeItemIds selections;
- size_t count = GetSelections(selections);
- if ( count == 0 ||
- count > 1 ||
- HITEM(selections[0]) != htItem )
+ if ( vkey == VK_NEXT )
+ {
+ wxTreeItemId nextEnd = nextStart;
+
+ for ( size_t n = 1; n < visibleCount; n++ )
+ {
+ wxTreeItemId nextTemp =
+ TreeView_GetNextVisible(GetHwnd(), HITEM(nextEnd));
+
+ if ( nextTemp.IsOk() )
+ {
+ nextEnd = nextTemp;
+ }
+ else
{
- // clear the previously selected items, if the
- // user clicked outside of the present selection.
- // otherwise, perform the deselection on mouse-up.
- // this allows multiple drag and drop to work.
+ break;
+ }
+ }
+
+ EnsureVisible(nextEnd);
+ }
+ }
+ else // no Ctrl
+ {
+ size_t visibleCount = TreeView_GetVisibleCount(GetHwnd());
+ wxTreeItemId nextAdjacent = (vkey == VK_PRIOR) ?
+ TreeView_GetPrevVisible(GetHwnd(), htSel) :
+ TreeView_GetNextVisible(GetHwnd(), htSel);
+
+ if ( !nextAdjacent )
+ {
+ break;
+ }
+
+ wxTreeItemId next(htSel);
+
+ for ( size_t n = 1; n < visibleCount; n++ )
+ {
+ wxTreeItemId nextTemp = vkey == VK_PRIOR ?
+ TreeView_GetPrevVisible(GetHwnd(), HITEM(next)) :
+ TreeView_GetNextVisible(GetHwnd(), HITEM(next));
+
+ if ( !nextTemp.IsOk() )
+ break;
+
+ next = nextTemp;
+ }
+
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, next);
+ changingEvent.m_itemOld = htSel;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ m_htSelStart.Unset();
+ DoSelectItem(next);
+ SetFocusedItem(next);
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, next);
+ changedEvent.m_itemOld = htSel;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+bool wxTreeCtrl::MSWHandleTreeKeyDownEvent(WXWPARAM wParam, WXLPARAM lParam)
+{
+ wxTreeEvent keyEvent(wxEVT_COMMAND_TREE_KEY_DOWN, this);
+ keyEvent.m_evtKey = CreateKeyEvent(wxEVT_KEY_DOWN, wParam, lParam);
+
+ bool processed = HandleTreeEvent(keyEvent);
+
+ // generate a separate event for Space/Return
+ if ( !wxIsCtrlDown() && !wxIsShiftDown() && !wxIsAltDown() &&
+ ((wParam == VK_SPACE) || (wParam == VK_RETURN)) )
+ {
+ const HTREEITEM htSel = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+ if ( htSel )
+ {
+ wxTreeEvent activatedEvent(wxEVT_COMMAND_TREE_ITEM_ACTIVATED,
+ this, htSel);
+ (void)HandleTreeEvent(activatedEvent);
+ }
+ }
+
+ return processed;
+}
+
+// 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
+WXLRESULT
+wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
+{
+ bool processed = false;
+ WXLRESULT rc = 0;
+ bool isMultiple = HasFlag(wxTR_MULTIPLE);
+
+ if ( nMsg == WM_CONTEXTMENU )
+ {
+ int x = GET_X_LPARAM(lParam),
+ y = GET_Y_LPARAM(lParam);
+
+ // the item for which the menu should be shown
+ wxTreeItemId item;
+
+ // the position where the menu should be shown in client coordinates
+ // (so that it can be passed directly to PopupMenu())
+ wxPoint pt;
+
+ if ( x == -1 || y == -1 )
+ {
+ // this means that the event was generated from keyboard (e.g. with
+ // Shift-F10 or special Windows menu key)
+ //
+ // use the Explorer standard of putting the menu at the left edge
+ // of the text, in the vertical middle of the text
+ item = wxTreeItemId(TreeView_GetSelection(GetHwnd()));
+ if ( item.IsOk() )
+ {
+ // Use the bounding rectangle of only the text part
+ wxRect rect;
+ GetBoundingRect(item, rect, true);
+ pt = wxPoint(rect.GetX(), rect.GetY() + rect.GetHeight() / 2);
+ }
+ }
+ else // event from mouse, use mouse position
+ {
+ pt = ScreenToClient(wxPoint(x, y));
+
+ TV_HITTESTINFO tvhti;
+ tvhti.pt.x = pt.x;
+ tvhti.pt.y = pt.y;
+
+ if ( TreeView_HitTest(GetHwnd(), &tvhti) )
+ item = wxTreeItemId(tvhti.hItem);
+ }
+
+ // create the event
+ if ( item.IsOk() )
+ {
+ wxTreeEvent event(wxEVT_COMMAND_TREE_ITEM_MENU, this, item);
+
+ event.m_pointDrag = pt;
+
+ if ( HandleTreeEvent(event) )
+ processed = true;
+ //else: continue with generating wxEVT_CONTEXT_MENU in base class code
+ }
+ }
+ else 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);
+
+ TV_HITTESTINFO tvht;
+ tvht.pt.x = x;
+ tvht.pt.y = y;
+
+ HTREEITEM htOldItem = TreeView_GetSelection(GetHwnd());
+ HTREEITEM htItem = TreeView_HitTest(GetHwnd(), &tvht);
+
+ switch ( nMsg )
+ {
+ case WM_LBUTTONDOWN:
+ if ( !isMultiple )
+ break;
+
+ m_htClickedItem.Unset();
+
+ if ( !MSWIsOnItem(tvht.flags) )
+ {
+ if ( tvht.flags & TVHT_ONITEMBUTTON )
+ {
+ // either it's going to be handled by user code or
+ // we're going to use it ourselves to toggle the
+ // branch, in either case don't pass it to the base
+ // class which would generate another mouse click event
+ // for it even though it's already handled here
+ processed = true;
+ SetFocus();
- if (!IsItemSelected(GetHwnd(), htItem))
+ if ( !HandleMouseEvent(nMsg, x, y, wParam) )
+ {
+ if ( !IsExpanded(htItem) )
{
- UnselectAll();
+ Expand(htItem);
+ }
+ else
+ {
+ Collapse(htItem);
+ }
+ }
+ }
+
+ m_focusLost = false;
+ break;
+ }
+
+ processed = true;
+ SetFocus();
+ m_htClickedItem = (WXHTREEITEM) htItem;
+ m_ptClick = wxPoint(x, y);
- // prevent the click from starting in-place editing
- // which should only happen if we click on the
- // already selected item (and nothing else is
- // selected)
+ if ( wParam & MK_CONTROL )
+ {
+ if ( HandleMouseEvent(nMsg, x, y, wParam) )
+ {
+ m_htClickedItem.Unset();
+ break;
+ }
- TreeView_SelectItem(GetHwnd(), 0);
- ::SelectItem(GetHwnd(), htItem);
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htItem);
+ changingEvent.m_itemOld = htOldItem;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ // toggle selected state
+ DoToggleItemSelection(wxTreeItemId(htItem));
+
+ SetFocusedItem(wxTreeItemId(htItem));
+
+ // reset on any click without Shift
+ m_htSelStart.Unset();
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htItem);
+ changedEvent.m_itemOld = htOldItem;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ else if ( wParam & MK_SHIFT )
+ {
+ if ( HandleMouseEvent(nMsg, x, y, wParam) )
+ {
+ m_htClickedItem.Unset();
+ break;
+ }
+
+ int srFlags = 0;
+ bool willChange = true;
+
+ if ( !(wParam & MK_CONTROL) )
+ {
+ srFlags |= SR_UNSELECT_OTHERS;
+ }
+
+ if ( !m_htSelStart )
+ {
+ // take the focused item
+ m_htSelStart = htOldItem;
+ }
+ else
+ {
+ willChange = SelectRange(GetHwnd(), HITEM(m_htSelStart),
+ htItem, srFlags | SR_SIMULATE);
+ }
+
+ if ( willChange )
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htItem);
+ changingEvent.m_itemOld = htOldItem;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ // this selects all items between the starting one
+ // and the current
+ if ( m_htSelStart )
+ {
+ SelectRange(GetHwnd(), HITEM(m_htSelStart),
+ htItem, srFlags);
}
- ::SetFocus(GetHwnd(), htItem);
- processed = true;
+ else
+ {
+ DoSelectItem(wxTreeItemId(htItem));
+ }
+
+ SetFocusedItem(wxTreeItemId(htItem));
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htItem);
+ changedEvent.m_itemOld = htOldItem;
+ (void)HandleTreeEvent(changedEvent);
}
- else // click on a single selected item
+ }
+ }
+ else // normal click
+ {
+ // avoid doing anything if we click on the only
+ // currently selected item
+
+ wxArrayTreeItemIds selections;
+ size_t count = GetSelections(selections);
+
+ if ( count == 0 ||
+ count > 1 ||
+ HITEM(selections[0]) != htItem )
+ {
+ if ( HandleMouseEvent(nMsg, x, y, wParam) )
{
- // don't interfere with the default processing in
- // WM_MOUSEMOVE handler below as the default window
- // proc will start the drag itself if we let have
- // WM_LBUTTONDOWN
m_htClickedItem.Unset();
+ break;
}
- // reset on any click without Shift
- m_htSelStart.Unset();
+ // clear the previously selected items, if the user
+ // clicked outside of the present selection, otherwise,
+ // perform the deselection on mouse-up, this allows
+ // multiple drag and drop to work.
+ if ( !IsItemSelected(GetHwnd(), htItem))
+ {
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htItem);
+ changingEvent.m_itemOld = htOldItem;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ DoSelectItem(wxTreeItemId(htItem));
+ SetFocusedItem(wxTreeItemId(htItem));
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htItem);
+ changedEvent.m_itemOld = htOldItem;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
+ else
+ {
+ SetFocusedItem(wxTreeItemId(htItem));
+ m_mouseUpDeselect = true;
+ }
+ }
+ else // click on a single selected item
+ {
+ // don't interfere with the default processing in
+ // WM_MOUSEMOVE handler below as the default window
+ // proc will start the drag itself if we let have
+ // WM_LBUTTONDOWN
+ m_htClickedItem.Unset();
+
+ // prevent in-place editing from starting if focus lost
+ // since previous click
+ if ( m_focusLost )
+ {
+ ClearFocusedItem();
+ DoSelectItem(wxTreeItemId(htItem));
+ SetFocusedItem(wxTreeItemId(htItem));
+ }
+ else
+ {
+ processed = false;
+ }
+ }
+
+ // reset on any click without Shift
+ m_htSelStart.Unset();
+ }
+
+ m_focusLost = false;
+
+ // we consumed the event so we need to trigger state image
+ // click if needed
+ if ( processed )
+ {
+ int htFlags = 0;
+ wxTreeItemId item = HitTest(wxPoint(x, y), htFlags);
+
+ if ( htFlags & wxTREE_HITTEST_ONITEMSTATEICON )
+ {
+ m_triggerStateImageClick = true;
}
}
break;
case WM_RBUTTONDOWN:
+ if ( !isMultiple )
+ break;
+
+ processed = true;
+ SetFocus();
+
+ if ( HandleMouseEvent(nMsg, x, y, wParam) || !htItem )
+ {
+ break;
+ }
+
// default handler removes the highlight from the currently
// focused item when right mouse button is pressed on another
// one but keeps the remaining items highlighted, which is
- // confusing, so override this default behaviour for tree with
- // multiple selections
- if ( isMultiple )
+ // confusing, so override this default behaviour
+ if ( !IsItemSelected(GetHwnd(), htItem) )
{
- if ( !IsItemSelected(GetHwnd(), htItem) )
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htItem);
+ changingEvent.m_itemOld = htOldItem;
+
+ if ( IsTreeEventAllowed(changingEvent) )
{
- UnselectAll();
- SelectItem(htItem);
- ::SetFocus(GetHwnd(), htItem);
+ DoUnselectAll();
+ DoSelectItem(wxTreeItemId(htItem));
+ SetFocusedItem(wxTreeItemId(htItem));
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htItem);
+ changedEvent.m_itemOld = htOldItem;
+ (void)HandleTreeEvent(changedEvent);
}
-
- // fire EVT_RIGHT_DOWN
- HandleMouseEvent(nMsg, x, y, wParam);
-
- // send NM_RCLICK
- NMHDR nmhdr;
- nmhdr.hwndFrom = GetHwnd();
- nmhdr.idFrom = ::GetWindowLong(GetHwnd(), GWL_ID);
- nmhdr.code = NM_RCLICK;
- ::SendMessage(::GetParent(GetHwnd()), WM_NOTIFY,
- nmhdr.idFrom, (LPARAM)&nmhdr);
-
- // prevent tree control default processing, as we've
- // already done everything
- processed = true;
}
+
break;
case WM_MOUSEMOVE:
break;
case WM_LBUTTONUP:
-
- // facilitates multiple drag-and-drop
- if (htItem && isMultiple)
+ if ( isMultiple )
{
- wxArrayTreeItemIds selections;
- size_t count = GetSelections(selections);
-
- if (count > 1 &&
- !(wParam & MK_CONTROL) &&
- !(wParam & MK_SHIFT))
+ // deselect other items if needed
+ if ( htItem )
{
- UnselectAll();
- TreeView_SelectItem(GetHwnd(), htItem);
- ::SelectItem(GetHwnd(), htItem);
- ::SetFocus(GetHwnd(), htItem);
+ if ( m_mouseUpDeselect )
+ {
+ m_mouseUpDeselect = false;
+
+ wxTreeEvent changingEvent(wxEVT_COMMAND_TREE_SEL_CHANGING,
+ this, htItem);
+ changingEvent.m_itemOld = htOldItem;
+
+ if ( IsTreeEventAllowed(changingEvent) )
+ {
+ DoUnselectAll();
+ DoSelectItem(wxTreeItemId(htItem));
+ SetFocusedItem(wxTreeItemId(htItem));
+
+ wxTreeEvent changedEvent(wxEVT_COMMAND_TREE_SEL_CHANGED,
+ this, htItem);
+ changedEvent.m_itemOld = htOldItem;
+ (void)HandleTreeEvent(changedEvent);
+ }
+ }
}
+
m_htClickedItem.Unset();
+
+ if ( m_triggerStateImageClick )
+ {
+ if ( tvht.flags & TVHT_ONITEMSTATEICON )
+ {
+ wxTreeEvent event(wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK,
+ this, htItem);
+ (void)HandleTreeEvent(event);
+
+ m_triggerStateImageClick = false;
+ processed = true;
+ }
+ }
+
+ if ( !m_dragStarted && MSWIsOnItem(tvht.flags) )
+ {
+ processed = true;
+ }
}
// fall through
if ( m_dragImage )
{
m_dragImage->EndDrag();
- delete m_dragImage;
- m_dragImage = NULL;
+ wxDELETE(m_dragImage);
// generate the drag end event
- wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG, this, htItem);
+ wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG,
+ this, htItem);
event.m_pointDrag = wxPoint(x, y);
-
- (void)HandleWindowEvent(event);
+ (void)HandleTreeEvent(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);
}
#endif // wxUSE_DRAGIMAGE
+
+ if ( isMultiple && nMsg == WM_RBUTTONUP )
+ {
+ // send NM_RCLICK
+ NMHDR nmhdr;
+ nmhdr.hwndFrom = GetHwnd();
+ nmhdr.idFrom = ::GetWindowLong(GetHwnd(), GWL_ID);
+ nmhdr.code = NM_RCLICK;
+ ::SendMessage(::GetParent(GetHwnd()), WM_NOTIFY,
+ nmhdr.idFrom, (LPARAM)&nmhdr);
+ processed = true;
+ }
+
+ m_dragStarted = false;
+
break;
}
}
- else if ( (nMsg == WM_SETFOCUS || nMsg == WM_KILLFOCUS) && isMultiple )
+ else if ( (nMsg == WM_SETFOCUS || nMsg == WM_KILLFOCUS) )
{
- // 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++ )
+ if ( isMultiple )
{
- // TreeView_GetItemRect() will return false if item is not visible,
- // which may happen perfectly well
- if ( TreeView_GetItemRect(GetHwnd(), HITEM(selections[n]),
- &rect, TRUE) )
+ // 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++ )
{
- ::InvalidateRect(GetHwnd(), &rect, FALSE);
+ // 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);
+ }
}
}
+
+ if ( nMsg == WM_KILLFOCUS )
+ {
+ m_focusLost = true;
+ }
}
- else if ( nMsg == WM_KEYDOWN && isMultiple )
+ else if ( (nMsg == WM_KEYDOWN || nMsg == WM_SYSKEYDOWN) && isMultiple )
{
- bool bCtrl = wxIsCtrlDown(),
- bShift = wxIsShiftDown();
-
- HTREEITEM htSel = (HTREEITEM)TreeView_GetSelection(GetHwnd());
+ // normally we want to generate wxEVT_KEY_DOWN events from TVN_KEYDOWN
+ // notification but for the keys which can be used to change selection
+ // we need to do it from here so as to not apply the default behaviour
+ // if the events are handled by the user code
switch ( wParam )
{
+ case VK_RETURN:
case VK_SPACE:
- if ( bCtrl )
- {
- ToggleItemSelection(htSel);
- }
- else
- {
- UnselectAll();
-
- ::SelectItem(GetHwnd(), htSel);
- }
-
- processed = true;
- break;
-
case VK_UP:
case 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)
- TreeView_GetNextItem
- (
- GetHwnd(),
- htSel,
- wParam == VK_UP ? TVGN_PREVIOUSVISIBLE
- : TVGN_NEXTVISIBLE
- );
-
- if ( !htNext )
- {
- // at the top/bottom
- htNext = htSel;
- }
-
- if ( bShift )
- {
- if ( !m_htSelStart )
- m_htSelStart = htSel;
-
- SelectRange(GetHwnd(), HITEM(m_htSelStart), htNext);
- }
- else // bCtrl
- {
- // without changing selection
- ::SetFocus(GetHwnd(), htNext);
- }
-
- processed = true;
- }
- break;
-
+ case VK_LEFT:
+ case VK_RIGHT:
case VK_HOME:
case VK_END:
case VK_PRIOR:
case VK_NEXT:
- // TODO: handle Shift/Ctrl with these keys
- if ( !bCtrl && !bShift )
+ if ( !HandleKeyDown(wParam, lParam) &&
+ !MSWHandleTreeKeyDownEvent(wParam, lParam) )
{
- UnselectAll();
-
- m_htSelStart.Unset();
+ // use the key to update the selection if it was left
+ // unprocessed
+ MSWHandleSelectionKey(wParam);
}
+
+ // pretend that we did process it in any case as we already
+ // generated an event for it
+ processed = true;
+
+ //default: for all the other keys leave processed as false so that
+ // the tree control generates a TVN_KEYDOWN for us
}
+
}
else if ( nMsg == WM_COMMAND )
{
if ( m_dragImage )
{
m_dragImage->EndDrag();
- delete m_dragImage;
- m_dragImage = NULL;
+ wxDELETE(m_dragImage);
// if we don't do it, the tree seems to think that 2 items
// are selected simultaneously which is quite weird
case TVN_KEYDOWN:
{
- eventType = wxEVT_COMMAND_TREE_KEY_DOWN;
TV_KEYDOWN *info = (TV_KEYDOWN *)lParam;
// fabricate the lParam and wParam parameters sufficiently
// similar to the ones from a "real" WM_KEYDOWN so that
// CreateKeyEvent() works correctly
- WXLPARAM lParam = (wxIsAltDown() ? KF_ALTDOWN : 0) << 16;
-
- WXWPARAM wParam = info->wVKey;
-
- int keyCode = wxCharCodeMSWToWX(wParam);
- if ( !keyCode )
- {
- // wxCharCodeMSWToWX() returns 0 to indicate that this is a
- // simple ASCII key
- keyCode = wParam;
- }
-
- event.m_evtKey = CreateKeyEvent(wxEVT_KEY_DOWN,
- keyCode,
- lParam,
- wParam);
-
- // a separate event for Space/Return
- if ( !wxIsAnyModifierDown() &&
- ((info->wVKey == VK_SPACE) || (info->wVKey == VK_RETURN)) )
- {
- wxTreeItemId item;
- if ( !HasFlag(wxTR_MULTIPLE) )
- item = GetSelection();
-
- wxTreeEvent event2(wxEVT_COMMAND_TREE_ITEM_ACTIVATED,
- this, item);
- (void)HandleWindowEvent(event2);
- }
+ return MSWHandleTreeKeyDownEvent(
+ info->wVKey, (wxIsAltDown() ? KF_ALTDOWN : 0) << 16);
}
- break;
// Vista's tree control has introduced some problems with our
// we have to handle both messages:
case TVN_SELCHANGEDA:
case TVN_SELCHANGEDW:
- eventType = wxEVT_COMMAND_TREE_SEL_CHANGED;
+ if ( !m_changingSelection )
+ {
+ eventType = wxEVT_COMMAND_TREE_SEL_CHANGED;
+ }
// fall through
case TVN_SELCHANGINGA:
case TVN_SELCHANGINGW:
+ if ( !m_changingSelection )
{
if ( eventType == wxEVT_NULL )
eventType = wxEVT_COMMAND_TREE_SEL_CHANGING;
//
// to avoid such surprises, we force the generation of focus events
// now, before we generate the selection change ones
- SetFocus();
+ if ( !m_changingSelection )
+ SetFocus();
break;
- // instead of explicitly checking for _WIN32_IE, check if the
- // required symbols are available in the headers
+ // instead of explicitly checking for _WIN32_IE, check if the
+ // required symbols are available in the headers
#if defined(CDDS_PREPAINT) && !wxUSE_COMCTL32_SAFELY
case NM_CUSTOMDRAW:
{
// notify us before painting each item
*result = m_hasAnyAttr ? CDRF_NOTIFYITEMDRAW
: CDRF_DODEFAULT;
+
+ // windows in TreeCtrl use one-based index for item state images,
+ // 0 indexed image is not being used, we're using zero-based index,
+ // so we have to add temp image (of zero index) to state image list
+ // before we draw any item, then after items are drawn we have to
+ // delete it (in POSTPAINT notify)
+ if (m_imageListState && m_imageListState->GetImageCount() > 0)
+ {
+ typedef BOOL (wxSTDCALL *ImageList_Copy_t)
+ (HIMAGELIST, int, HIMAGELIST, int, UINT);
+ static ImageList_Copy_t s_pfnImageList_Copy = NULL;
+ static bool loaded = false;
+
+ if ( !loaded )
+ {
+ wxLoadedDLL dllComCtl32(wxT("comctl32.dll"));
+ if ( dllComCtl32.IsLoaded() )
+ wxDL_INIT_FUNC(s_pfn, ImageList_Copy, dllComCtl32);
+ }
+
+ if ( !s_pfnImageList_Copy )
+ {
+ // this code is broken with ImageList_Copy()
+ // but I don't care enough about Win95 support
+ // to write it now -- if anybody does, please
+ // do it
+ wxFAIL_MSG("TODO: implement this for Win95");
+ break;
+ }
+
+ const HIMAGELIST
+ hImageList = GetHimagelistOf(m_imageListState);
+
+ // add temporary image
+ int width, height;
+ m_imageListState->GetSize(0, width, height);
+
+ HBITMAP hbmpTemp = ::CreateBitmap(width, height, 1, 1, NULL);
+ int index = ::ImageList_Add(hImageList, hbmpTemp, hbmpTemp);
+ ::DeleteObject(hbmpTemp);
+
+ if ( index != -1 )
+ {
+ // move images to right
+ for ( int i = index; i > 0; i-- )
+ {
+ (*s_pfnImageList_Copy)(hImageList, i,
+ hImageList, i-1,
+ ILCF_MOVE);
+ }
+
+ // we must remove the image in POSTPAINT notify
+ *result |= CDRF_NOTIFYPOSTPAINT;
+ }
+ }
+ break;
+
+ case CDDS_POSTPAINT:
+ // we are deleting temp image of 0 index, which was
+ // added before items were drawn (in PREPAINT notify)
+ if (m_imageListState && m_imageListState->GetImageCount() > 0)
+ m_imageListState->Remove(0);
break;
case CDDS_ITEMPREPAINT:
point.x = LOWORD(pos);
point.y = HIWORD(pos);
::MapWindowPoints(HWND_DESKTOP, GetHwnd(), &point, 1);
- int flags = 0;
- wxTreeItemId item = HitTest(wxPoint(point.x, point.y), flags);
- if (flags & wxTREE_HITTEST_ONITEMSTATEICON)
+ int htFlags = 0;
+ wxTreeItemId item = HitTest(wxPoint(point.x, point.y), htFlags);
+
+ if ( htFlags & wxTREE_HITTEST_ONITEMSTATEICON )
{
event.m_item = item;
eventType = wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK;
}
+
break;
}
::ScreenToClient(GetHwnd(), &tvhti.pt);
if ( TreeView_HitTest(GetHwnd(), &tvhti) )
{
- if ( tvhti.flags & TVHT_ONITEM )
+ if ( MSWIsOnItem(tvhti.flags) )
{
event.m_item = tvhti.hItem;
eventType = (int)hdr->code == NM_DBLCLK
event.SetEventType(eventType);
- if ( event.m_item.IsOk() )
- event.SetClientObject(GetItemData(event.m_item));
-
- bool processed = HandleWindowEvent(event);
+ bool processed = HandleTreeEvent(event);
// 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;
+ // we translate NM_DBLCLK into ACTIVATED event and if the user
+ // handled the activation of the item we shouldn't proceed with
+ // also using the same double click for toggling the item expanded
+ // state -- but OTOH do let the user to expand/collapse the item by
+ // double clicking on it if the activation is not handled specially
+ *result = processed;
break;
case NM_RCLICK:
{
// 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?") );
+ wxASSERT_MSG( !m_dragImage, wxT("starting to drag once again?") );
m_dragImage = new wxDragImage(*this, event.m_item);
m_dragImage->BeginDrag(wxPoint(0,0), this);
m_dragImage->Show();
+
+ m_dragStarted = true;
}
#endif // wxUSE_DRAGIMAGE
break;