]> git.saurik.com Git - wxWidgets.git/blob - src/msw/treectrl.cpp
c5ce30eada57f8defd701c2753f88a8f8a6fe175
[wxWidgets.git] / src / msw / treectrl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/treectrl.cpp
3 // Purpose: wxTreeCtrl
4 // Author: Julian Smart
5 // Modified by: Vadim Zeitlin to be less MSW-specific on 10.10.98
6 // Created: 1997
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "treectrl.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_TREECTRL
32
33 // include <commctrl.h> "properly"
34 #include "wx/msw/wrapcctl.h"
35
36 #include "wx/msw/private.h"
37 #include "wx/msw/missing.h"
38
39 // Set this to 1 to be _absolutely_ sure that repainting will work for all
40 // comctl32.dll versions
41 #define wxUSE_COMCTL32_SAFELY 0
42
43 #include "wx/app.h"
44 #include "wx/log.h"
45 #include "wx/dynarray.h"
46 #include "wx/imaglist.h"
47 #include "wx/settings.h"
48 #include "wx/msw/treectrl.h"
49 #include "wx/msw/dragimag.h"
50
51 // macros to hide the cast ugliness
52 // --------------------------------
53
54 // ptr is the real item id, i.e. wxTreeItemId::m_pItem
55 #define HITEM_PTR(ptr) (HTREEITEM)(ptr)
56
57 // item here is a wxTreeItemId
58 #define HITEM(item) HITEM_PTR((item).m_pItem)
59
60 // the native control doesn't support multiple selections under MSW and we
61 // have 2 ways to emulate them: either using TVS_CHECKBOXES style and let
62 // checkboxes be the selection status (checked == selected) or by really
63 // emulating everything, i.e. intercepting mouse and key events &c. The first
64 // approach is much easier but doesn't work with comctl32.dll < 4.71 and also
65 // looks quite ugly.
66 #define wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE 0
67
68 // ----------------------------------------------------------------------------
69 // private functions
70 // ----------------------------------------------------------------------------
71
72 // wrapper for TreeView_HitTest
73 static HTREEITEM GetItemFromPoint(HWND hwndTV, int x, int y)
74 {
75 TV_HITTESTINFO tvht;
76 tvht.pt.x = x;
77 tvht.pt.y = y;
78
79 return (HTREEITEM)TreeView_HitTest(hwndTV, &tvht);
80 }
81
82 #if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
83
84 // wrappers for TreeView_GetItem/TreeView_SetItem
85 static bool IsItemSelected(HWND hwndTV, HTREEITEM hItem)
86 {
87
88 TV_ITEM tvi;
89 tvi.mask = TVIF_STATE | TVIF_HANDLE;
90 tvi.stateMask = TVIS_SELECTED;
91 tvi.hItem = hItem;
92
93 if ( !TreeView_GetItem(hwndTV, &tvi) )
94 {
95 wxLogLastError(wxT("TreeView_GetItem"));
96 }
97
98 return (tvi.state & TVIS_SELECTED) != 0;
99 }
100
101 static bool SelectItem(HWND hwndTV, HTREEITEM hItem, bool select = true)
102 {
103 TV_ITEM tvi;
104 tvi.mask = TVIF_STATE | TVIF_HANDLE;
105 tvi.stateMask = TVIS_SELECTED;
106 tvi.state = select ? TVIS_SELECTED : 0;
107 tvi.hItem = hItem;
108
109 if ( TreeView_SetItem(hwndTV, &tvi) == -1 )
110 {
111 wxLogLastError(wxT("TreeView_SetItem"));
112 return false;
113 }
114
115 return true;
116 }
117
118 static inline void UnselectItem(HWND hwndTV, HTREEITEM htItem)
119 {
120 SelectItem(hwndTV, htItem, false);
121 }
122
123 static inline void ToggleItemSelection(HWND hwndTV, HTREEITEM htItem)
124 {
125 SelectItem(hwndTV, htItem, !IsItemSelected(hwndTV, htItem));
126 }
127
128 // helper function which selects all items in a range and, optionally,
129 // unselects all others
130 static void SelectRange(HWND hwndTV,
131 HTREEITEM htFirst,
132 HTREEITEM htLast,
133 bool unselectOthers = true)
134 {
135 // find the first (or last) item and select it
136 bool cont = true;
137 HTREEITEM htItem = (HTREEITEM)TreeView_GetRoot(hwndTV);
138 while ( htItem && cont )
139 {
140 if ( (htItem == htFirst) || (htItem == htLast) )
141 {
142 if ( !IsItemSelected(hwndTV, htItem) )
143 {
144 SelectItem(hwndTV, htItem);
145 }
146
147 cont = false;
148 }
149 else
150 {
151 if ( unselectOthers && IsItemSelected(hwndTV, htItem) )
152 {
153 UnselectItem(hwndTV, htItem);
154 }
155 }
156
157 htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
158 }
159
160 // select the items in range
161 cont = htFirst != htLast;
162 while ( htItem && cont )
163 {
164 if ( !IsItemSelected(hwndTV, htItem) )
165 {
166 SelectItem(hwndTV, htItem);
167 }
168
169 cont = (htItem != htFirst) && (htItem != htLast);
170
171 htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
172 }
173
174 // unselect the rest
175 if ( unselectOthers )
176 {
177 while ( htItem )
178 {
179 if ( IsItemSelected(hwndTV, htItem) )
180 {
181 UnselectItem(hwndTV, htItem);
182 }
183
184 htItem = (HTREEITEM)TreeView_GetNextVisible(hwndTV, htItem);
185 }
186 }
187
188 // seems to be necessary - otherwise the just selected items don't always
189 // appear as selected
190 UpdateWindow(hwndTV);
191 }
192
193 // helper function which tricks the standard control into changing the focused
194 // item without changing anything else (if someone knows why Microsoft doesn't
195 // allow to do it by just setting TVIS_FOCUSED flag, please tell me!)
196 static void SetFocus(HWND hwndTV, HTREEITEM htItem)
197 {
198 // the current focus
199 HTREEITEM htFocus = (HTREEITEM)TreeView_GetSelection(hwndTV);
200
201 if ( htItem )
202 {
203 // set the focus
204 if ( htItem != htFocus )
205 {
206 // remember the selection state of the item
207 bool wasSelected = IsItemSelected(hwndTV, htItem);
208
209 if ( htFocus && IsItemSelected(hwndTV, htFocus) )
210 {
211 // prevent the tree from unselecting the old focus which it
212 // would do by default (TreeView_SelectItem unselects the
213 // focused item)
214 TreeView_SelectItem(hwndTV, 0);
215 SelectItem(hwndTV, htFocus);
216 }
217
218 TreeView_SelectItem(hwndTV, htItem);
219
220 if ( !wasSelected )
221 {
222 // need to clear the selection which TreeView_SelectItem() gave
223 // us
224 UnselectItem(hwndTV, htItem);
225 }
226 //else: was selected, still selected - ok
227 }
228 //else: nothing to do, focus already there
229 }
230 else
231 {
232 if ( htFocus )
233 {
234 bool wasFocusSelected = IsItemSelected(hwndTV, htFocus);
235
236 // just clear the focus
237 TreeView_SelectItem(hwndTV, 0);
238
239 if ( wasFocusSelected )
240 {
241 // restore the selection state
242 SelectItem(hwndTV, htFocus);
243 }
244 }
245 //else: nothing to do, no focus already
246 }
247 }
248
249 #endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
250
251 // ----------------------------------------------------------------------------
252 // private classes
253 // ----------------------------------------------------------------------------
254
255 // a convenient wrapper around TV_ITEM struct which adds a ctor
256 #ifdef __VISUALC__
257 #pragma warning( disable : 4097 ) // inheriting from typedef
258 #endif
259
260 struct wxTreeViewItem : public TV_ITEM
261 {
262 wxTreeViewItem(const wxTreeItemId& item, // the item handle
263 UINT mask_, // fields which are valid
264 UINT stateMask_ = 0) // for TVIF_STATE only
265 {
266 wxZeroMemory(*this);
267
268 // hItem member is always valid
269 mask = mask_ | TVIF_HANDLE;
270 stateMask = stateMask_;
271 hItem = HITEM(item);
272 }
273 };
274
275 // wxVirutalNode is used in place of a single root when 'hidden' root is
276 // specified.
277 class wxVirtualNode : public wxTreeViewItem
278 {
279 public:
280 wxVirtualNode(wxTreeItemData *data)
281 : wxTreeViewItem(TVI_ROOT, 0)
282 {
283 m_data = data;
284 }
285
286 ~wxVirtualNode()
287 {
288 delete m_data;
289 }
290
291 wxTreeItemData *GetData() const { return m_data; }
292 void SetData(wxTreeItemData *data) { delete m_data; m_data = data; }
293
294 private:
295 wxTreeItemData *m_data;
296
297 DECLARE_NO_COPY_CLASS(wxVirtualNode)
298 };
299
300 #ifdef __VISUALC__
301 #pragma warning( default : 4097 )
302 #endif
303
304 // a macro to get the virtual root, returns NULL if none
305 #define GET_VIRTUAL_ROOT() ((wxVirtualNode *)m_pVirtualRoot)
306
307 // returns true if the item is the virtual root
308 #define IS_VIRTUAL_ROOT(item) (HITEM(item) == TVI_ROOT)
309
310 // a class which encapsulates the tree traversal logic: it vists all (unless
311 // OnVisit() returns false) items under the given one
312 class wxTreeTraversal
313 {
314 public:
315 wxTreeTraversal(const wxTreeCtrl *tree)
316 {
317 m_tree = tree;
318 }
319
320 // do traverse the tree: visit all items (recursively by default) under the
321 // given one; return true if all items were traversed or false if the
322 // traversal was aborted because OnVisit returned false
323 bool DoTraverse(const wxTreeItemId& root, bool recursively = true);
324
325 // override this function to do whatever is needed for each item, return
326 // false to stop traversing
327 virtual bool OnVisit(const wxTreeItemId& item) = 0;
328
329 protected:
330 const wxTreeCtrl *GetTree() const { return m_tree; }
331
332 private:
333 bool Traverse(const wxTreeItemId& root, bool recursively);
334
335 const wxTreeCtrl *m_tree;
336
337 DECLARE_NO_COPY_CLASS(wxTreeTraversal)
338 };
339
340 // internal class for getting the selected items
341 class TraverseSelections : public wxTreeTraversal
342 {
343 public:
344 TraverseSelections(const wxTreeCtrl *tree,
345 wxArrayTreeItemIds& selections)
346 : wxTreeTraversal(tree), m_selections(selections)
347 {
348 m_selections.Empty();
349
350 DoTraverse(tree->GetRootItem());
351 }
352
353 virtual bool OnVisit(const wxTreeItemId& item)
354 {
355 // can't visit a virtual node.
356 if ( (GetTree()->GetRootItem() == item) && (GetTree()->GetWindowStyle() & wxTR_HIDE_ROOT))
357 {
358 return true;
359 }
360
361 #if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
362 if ( GetTree()->IsItemChecked(item) )
363 #else
364 if ( ::IsItemSelected(GetHwndOf(GetTree()), HITEM(item)) )
365 #endif
366 {
367 m_selections.Add(item);
368 }
369
370 return true;
371 }
372
373 size_t GetCount() const { return m_selections.GetCount(); }
374
375 private:
376 wxArrayTreeItemIds& m_selections;
377
378 DECLARE_NO_COPY_CLASS(TraverseSelections)
379 };
380
381 // internal class for counting tree items
382 class TraverseCounter : public wxTreeTraversal
383 {
384 public:
385 TraverseCounter(const wxTreeCtrl *tree,
386 const wxTreeItemId& root,
387 bool recursively)
388 : wxTreeTraversal(tree)
389 {
390 m_count = 0;
391
392 DoTraverse(root, recursively);
393 }
394
395 virtual bool OnVisit(const wxTreeItemId& WXUNUSED(item))
396 {
397 m_count++;
398
399 return true;
400 }
401
402 size_t GetCount() const { return m_count; }
403
404 private:
405 size_t m_count;
406
407 DECLARE_NO_COPY_CLASS(TraverseCounter)
408 };
409
410 // ----------------------------------------------------------------------------
411 // This class is needed for support of different images: the Win32 common
412 // control natively supports only 2 images (the normal one and another for the
413 // selected state). We wish to provide support for 2 more of them for folder
414 // items (i.e. those which have children): for expanded state and for expanded
415 // selected state. For this we use this structure to store the additional items
416 // images.
417 //
418 // There is only one problem with this: when we retrieve the item's data, we
419 // don't know whether we get a pointer to wxTreeItemData or
420 // wxTreeItemIndirectData. So we always set the item id to an invalid value
421 // in this class and the code using the client data checks for it and retrieves
422 // the real client data in this case.
423 // ----------------------------------------------------------------------------
424
425 class wxTreeItemIndirectData : public wxTreeItemData
426 {
427 public:
428 // ctor associates this data with the item and the real item data becomes
429 // available through our GetData() method
430 wxTreeItemIndirectData(wxTreeCtrl *tree, const wxTreeItemId& item)
431 {
432 for ( size_t n = 0; n < WXSIZEOF(m_images); n++ )
433 {
434 m_images[n] = -1;
435 }
436
437 // save the old data
438 m_data = tree->GetItemData(item);
439
440 // and set ourselves as the new one
441 tree->SetIndirectItemData(item, this);
442
443 // we must have the invalid value for the item
444 m_pItem = 0l;
445 }
446
447 // dtor deletes the associated data as well
448 virtual ~wxTreeItemIndirectData() { delete m_data; }
449
450 // accessors
451 // get the real data associated with the item
452 wxTreeItemData *GetData() const { return m_data; }
453 // change it
454 void SetData(wxTreeItemData *data) { m_data = data; }
455
456 // do we have such image?
457 bool HasImage(wxTreeItemIcon which) const { return m_images[which] != -1; }
458 // get image
459 int GetImage(wxTreeItemIcon which) const { return m_images[which]; }
460 // change it
461 void SetImage(int image, wxTreeItemIcon which) { m_images[which] = image; }
462
463 private:
464 // all the images associated with the item
465 int m_images[wxTreeItemIcon_Max];
466
467 // the real client data
468 wxTreeItemData *m_data;
469
470 DECLARE_NO_COPY_CLASS(wxTreeItemIndirectData)
471 };
472
473 // ----------------------------------------------------------------------------
474 // wxWin macros
475 // ----------------------------------------------------------------------------
476
477 #if wxUSE_EXTENDED_RTTI
478 WX_DEFINE_FLAGS( wxTreeCtrlStyle )
479
480 wxBEGIN_FLAGS( wxTreeCtrlStyle )
481 // new style border flags, we put them first to
482 // use them for streaming out
483 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
484 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
485 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
486 wxFLAGS_MEMBER(wxBORDER_RAISED)
487 wxFLAGS_MEMBER(wxBORDER_STATIC)
488 wxFLAGS_MEMBER(wxBORDER_NONE)
489
490 // old style border flags
491 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
492 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
493 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
494 wxFLAGS_MEMBER(wxRAISED_BORDER)
495 wxFLAGS_MEMBER(wxSTATIC_BORDER)
496 wxFLAGS_MEMBER(wxBORDER)
497
498 // standard window styles
499 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
500 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
501 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
502 wxFLAGS_MEMBER(wxWANTS_CHARS)
503 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
504 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
505 wxFLAGS_MEMBER(wxVSCROLL)
506 wxFLAGS_MEMBER(wxHSCROLL)
507
508 wxFLAGS_MEMBER(wxTR_EDIT_LABELS)
509 wxFLAGS_MEMBER(wxTR_NO_BUTTONS)
510 wxFLAGS_MEMBER(wxTR_HAS_BUTTONS)
511 wxFLAGS_MEMBER(wxTR_TWIST_BUTTONS)
512 wxFLAGS_MEMBER(wxTR_NO_LINES)
513 wxFLAGS_MEMBER(wxTR_FULL_ROW_HIGHLIGHT)
514 wxFLAGS_MEMBER(wxTR_LINES_AT_ROOT)
515 wxFLAGS_MEMBER(wxTR_HIDE_ROOT)
516 wxFLAGS_MEMBER(wxTR_ROW_LINES)
517 wxFLAGS_MEMBER(wxTR_HAS_VARIABLE_ROW_HEIGHT)
518 wxFLAGS_MEMBER(wxTR_SINGLE)
519 wxFLAGS_MEMBER(wxTR_MULTIPLE)
520 wxFLAGS_MEMBER(wxTR_EXTENDED)
521 wxFLAGS_MEMBER(wxTR_DEFAULT_STYLE)
522
523 wxEND_FLAGS( wxTreeCtrlStyle )
524
525 IMPLEMENT_DYNAMIC_CLASS_XTI(wxTreeCtrl, wxControl,"wx/treectrl.h")
526
527 wxBEGIN_PROPERTIES_TABLE(wxTreeCtrl)
528 wxEVENT_PROPERTY( TextUpdated , wxEVT_COMMAND_TEXT_UPDATED , wxCommandEvent )
529 wxEVENT_RANGE_PROPERTY( TreeEvent , wxEVT_COMMAND_TREE_BEGIN_DRAG , wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK , wxTreeEvent )
530 wxPROPERTY_FLAGS( WindowStyle , wxTreeCtrlStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
531 wxEND_PROPERTIES_TABLE()
532
533 wxBEGIN_HANDLERS_TABLE(wxTreeCtrl)
534 wxEND_HANDLERS_TABLE()
535
536 wxCONSTRUCTOR_5( wxTreeCtrl , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size , long , WindowStyle )
537 #else
538 IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxControl)
539 #endif
540
541 // ----------------------------------------------------------------------------
542 // constants
543 // ----------------------------------------------------------------------------
544
545 // indices in gs_expandEvents table below
546 enum
547 {
548 IDX_COLLAPSE,
549 IDX_EXPAND,
550 IDX_WHAT_MAX
551 };
552
553 enum
554 {
555 IDX_DONE,
556 IDX_DOING,
557 IDX_HOW_MAX
558 };
559
560 // handy table for sending events - it has to be initialized during run-time
561 // now so can't be const any more
562 static /* const */ wxEventType gs_expandEvents[IDX_WHAT_MAX][IDX_HOW_MAX];
563
564 /*
565 but logically it's a const table with the following entries:
566 =
567 {
568 { wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxEVT_COMMAND_TREE_ITEM_COLLAPSING },
569 { wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxEVT_COMMAND_TREE_ITEM_EXPANDING }
570 };
571 */
572
573 // ============================================================================
574 // implementation
575 // ============================================================================
576
577 // ----------------------------------------------------------------------------
578 // tree traversal
579 // ----------------------------------------------------------------------------
580
581 bool wxTreeTraversal::DoTraverse(const wxTreeItemId& root, bool recursively)
582 {
583 if ( !OnVisit(root) )
584 return false;
585
586 return Traverse(root, recursively);
587 }
588
589 bool wxTreeTraversal::Traverse(const wxTreeItemId& root, bool recursively)
590 {
591 wxTreeItemIdValue cookie;
592 wxTreeItemId child = m_tree->GetFirstChild(root, cookie);
593 while ( child.IsOk() )
594 {
595 // depth first traversal
596 if ( recursively && !Traverse(child, true) )
597 return false;
598
599 if ( !OnVisit(child) )
600 return false;
601
602 child = m_tree->GetNextChild(root, cookie);
603 }
604
605 return true;
606 }
607
608 // ----------------------------------------------------------------------------
609 // construction and destruction
610 // ----------------------------------------------------------------------------
611
612 void wxTreeCtrl::Init()
613 {
614 m_imageListNormal = NULL;
615 m_imageListState = NULL;
616 m_ownsImageListNormal = m_ownsImageListState = false;
617 m_textCtrl = NULL;
618 m_hasAnyAttr = false;
619 m_dragImage = NULL;
620 m_pVirtualRoot = NULL;
621
622 // initialize the global array of events now as it can't be done statically
623 // with the wxEVT_XXX values being allocated during run-time only
624 gs_expandEvents[IDX_COLLAPSE][IDX_DONE] = wxEVT_COMMAND_TREE_ITEM_COLLAPSED;
625 gs_expandEvents[IDX_COLLAPSE][IDX_DOING] = wxEVT_COMMAND_TREE_ITEM_COLLAPSING;
626 gs_expandEvents[IDX_EXPAND][IDX_DONE] = wxEVT_COMMAND_TREE_ITEM_EXPANDED;
627 gs_expandEvents[IDX_EXPAND][IDX_DOING] = wxEVT_COMMAND_TREE_ITEM_EXPANDING;
628 }
629
630 bool wxTreeCtrl::Create(wxWindow *parent,
631 wxWindowID id,
632 const wxPoint& pos,
633 const wxSize& size,
634 long style,
635 const wxValidator& validator,
636 const wxString& name)
637 {
638 Init();
639
640 if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
641 style |= wxBORDER_SUNKEN;
642
643 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
644 return false;
645
646 DWORD exStyle = 0;
647 DWORD wstyle = MSWGetStyle(m_windowStyle, & exStyle);
648 wstyle |= WS_TABSTOP | TVS_SHOWSELALWAYS;
649
650 if ((m_windowStyle & wxTR_NO_LINES) == 0)
651 wstyle |= TVS_HASLINES;
652 if ( m_windowStyle & wxTR_HAS_BUTTONS )
653 wstyle |= TVS_HASBUTTONS;
654
655 if ( m_windowStyle & wxTR_EDIT_LABELS )
656 wstyle |= TVS_EDITLABELS;
657
658 if ( m_windowStyle & wxTR_LINES_AT_ROOT )
659 wstyle |= TVS_LINESATROOT;
660
661 if ( m_windowStyle & wxTR_FULL_ROW_HIGHLIGHT )
662 {
663 if ( wxApp::GetComCtl32Version() >= 471 )
664 wstyle |= TVS_FULLROWSELECT;
665 }
666
667 // using TVS_CHECKBOXES for emulation of a multiselection tree control
668 // doesn't work without the new enough headers
669 #if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE && \
670 !defined( __GNUWIN32_OLD__ ) && \
671 !defined( __BORLANDC__ ) && \
672 !defined( __WATCOMC__ ) && \
673 (!defined(__VISUALC__) || (__VISUALC__ > 1010))
674
675 // we emulate the multiple selection tree controls by using checkboxes: set
676 // up the image list we need for this if we do have multiple selections
677 if ( m_windowStyle & wxTR_MULTIPLE )
678 wstyle |= TVS_CHECKBOXES;
679 #endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
680
681 #if !defined(__WXWINCE__) && defined(TVS_INFOTIP)
682 // Need so that TVN_GETINFOTIP messages will be sent
683 wstyle |= TVS_INFOTIP;
684 #endif
685
686 // Create the tree control.
687 if ( !MSWCreateControl(WC_TREEVIEW, wstyle, pos, size) )
688 return false;
689
690 #if wxUSE_COMCTL32_SAFELY
691 wxWindow::SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
692 wxWindow::SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
693 #elif 1
694 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
695 SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
696 #else
697 // This works around a bug in the Windows tree control whereby for some versions
698 // of comctrl32, setting any colour actually draws the background in black.
699 // This will initialise the background to the system colour.
700 // THIS FIX NOW REVERTED since it caused problems on _other_ systems.
701 // Assume the user has an updated comctl32.dll.
702 ::SendMessage(GetHwnd(), TVM_SETBKCOLOR, 0,-1);
703 wxWindow::SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
704 SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
705 #endif
706
707
708 // VZ: this is some experimental code which may be used to get the
709 // TVS_CHECKBOXES style functionality for comctl32.dll < 4.71.
710 // AFAIK, the standard DLL does about the same thing anyhow.
711 #if 0
712 if ( m_windowStyle & wxTR_MULTIPLE )
713 {
714 wxBitmap bmp;
715
716 // create the DC compatible with the current screen
717 HDC hdcMem = CreateCompatibleDC(NULL);
718
719 // create a mono bitmap of the standard size
720 int x = ::GetSystemMetrics(SM_CXMENUCHECK);
721 int y = ::GetSystemMetrics(SM_CYMENUCHECK);
722 wxImageList imagelistCheckboxes(x, y, false, 2);
723 HBITMAP hbmpCheck = CreateBitmap(x, y, // bitmap size
724 1, // # of color planes
725 1, // # bits needed for one pixel
726 0); // array containing colour data
727 SelectObject(hdcMem, hbmpCheck);
728
729 // then draw a check mark into it
730 RECT rect = { 0, 0, x, y };
731 if ( !::DrawFrameControl(hdcMem, &rect,
732 DFC_BUTTON,
733 DFCS_BUTTONCHECK | DFCS_CHECKED) )
734 {
735 wxLogLastError(wxT("DrawFrameControl(check)"));
736 }
737
738 bmp.SetHBITMAP((WXHBITMAP)hbmpCheck);
739 imagelistCheckboxes.Add(bmp);
740
741 if ( !::DrawFrameControl(hdcMem, &rect,
742 DFC_BUTTON,
743 DFCS_BUTTONCHECK) )
744 {
745 wxLogLastError(wxT("DrawFrameControl(uncheck)"));
746 }
747
748 bmp.SetHBITMAP((WXHBITMAP)hbmpCheck);
749 imagelistCheckboxes.Add(bmp);
750
751 // clean up
752 ::DeleteDC(hdcMem);
753
754 // set the imagelist
755 SetStateImageList(&imagelistCheckboxes);
756 }
757 #endif // 0
758
759 wxSetCCUnicodeFormat(GetHwnd());
760
761 return true;
762 }
763
764 wxTreeCtrl::~wxTreeCtrl()
765 {
766 // delete any attributes
767 if ( m_hasAnyAttr )
768 {
769 WX_CLEAR_HASH_MAP(wxMapTreeAttr, m_attrs);
770
771 // prevent TVN_DELETEITEM handler from deleting the attributes again!
772 m_hasAnyAttr = false;
773 }
774
775 DeleteTextCtrl();
776
777 // delete user data to prevent memory leaks
778 // also deletes hidden root node storage.
779 DeleteAllItems();
780
781 if (m_ownsImageListNormal) delete m_imageListNormal;
782 if (m_ownsImageListState) delete m_imageListState;
783 }
784
785 // ----------------------------------------------------------------------------
786 // accessors
787 // ----------------------------------------------------------------------------
788
789 /* static */ wxVisualAttributes
790 wxTreeCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
791 {
792 wxVisualAttributes attrs = GetCompositeControlsDefaultAttributes(variant);
793
794 // common controls have their own default font
795 attrs.font = wxGetCCDefaultFont();
796
797 return attrs;
798 }
799
800
801 // simple wrappers which add error checking in debug mode
802
803 bool wxTreeCtrl::DoGetItem(wxTreeViewItem* tvItem) const
804 {
805 wxCHECK_MSG( tvItem->hItem != TVI_ROOT, false,
806 _T("can't retrieve virtual root item") );
807
808 if ( !TreeView_GetItem(GetHwnd(), tvItem) )
809 {
810 wxLogLastError(wxT("TreeView_GetItem"));
811
812 return false;
813 }
814
815 return true;
816 }
817
818 void wxTreeCtrl::DoSetItem(wxTreeViewItem* tvItem)
819 {
820 if ( TreeView_SetItem(GetHwnd(), tvItem) == -1 )
821 {
822 wxLogLastError(wxT("TreeView_SetItem"));
823 }
824 }
825
826 size_t wxTreeCtrl::GetCount() const
827 {
828 return (size_t)TreeView_GetCount(GetHwnd());
829 }
830
831 unsigned int wxTreeCtrl::GetIndent() const
832 {
833 return TreeView_GetIndent(GetHwnd());
834 }
835
836 void wxTreeCtrl::SetIndent(unsigned int indent)
837 {
838 TreeView_SetIndent(GetHwnd(), indent);
839 }
840
841 wxImageList *wxTreeCtrl::GetImageList() const
842 {
843 return m_imageListNormal;
844 }
845
846 wxImageList *wxTreeCtrl::GetStateImageList() const
847 {
848 return m_imageListState;
849 }
850
851 void wxTreeCtrl::SetAnyImageList(wxImageList *imageList, int which)
852 {
853 // no error return
854 TreeView_SetImageList(GetHwnd(),
855 imageList ? imageList->GetHIMAGELIST() : 0,
856 which);
857 }
858
859 void wxTreeCtrl::SetImageList(wxImageList *imageList)
860 {
861 if (m_ownsImageListNormal)
862 delete m_imageListNormal;
863
864 SetAnyImageList(m_imageListNormal = imageList, TVSIL_NORMAL);
865 m_ownsImageListNormal = false;
866 }
867
868 void wxTreeCtrl::SetStateImageList(wxImageList *imageList)
869 {
870 if (m_ownsImageListState) delete m_imageListState;
871 SetAnyImageList(m_imageListState = imageList, TVSIL_STATE);
872 m_ownsImageListState = false;
873 }
874
875 void wxTreeCtrl::AssignImageList(wxImageList *imageList)
876 {
877 SetImageList(imageList);
878 m_ownsImageListNormal = true;
879 }
880
881 void wxTreeCtrl::AssignStateImageList(wxImageList *imageList)
882 {
883 SetStateImageList(imageList);
884 m_ownsImageListState = true;
885 }
886
887 size_t wxTreeCtrl::GetChildrenCount(const wxTreeItemId& item,
888 bool recursively) const
889 {
890 wxCHECK_MSG( item.IsOk(), 0u, wxT("invalid tree item") );
891
892 TraverseCounter counter(this, item, recursively);
893 return counter.GetCount() - 1;
894 }
895
896 // ----------------------------------------------------------------------------
897 // control colours
898 // ----------------------------------------------------------------------------
899
900 bool wxTreeCtrl::SetBackgroundColour(const wxColour &colour)
901 {
902 #if !wxUSE_COMCTL32_SAFELY
903 if ( !wxWindowBase::SetBackgroundColour(colour) )
904 return false;
905
906 ::SendMessage(GetHwnd(), TVM_SETBKCOLOR, 0, colour.GetPixel());
907 #endif
908
909 return true;
910 }
911
912 bool wxTreeCtrl::SetForegroundColour(const wxColour &colour)
913 {
914 #if !wxUSE_COMCTL32_SAFELY
915 if ( !wxWindowBase::SetForegroundColour(colour) )
916 return false;
917
918 ::SendMessage(GetHwnd(), TVM_SETTEXTCOLOR, 0, colour.GetPixel());
919 #endif
920
921 return true;
922 }
923
924 // ----------------------------------------------------------------------------
925 // Item access
926 // ----------------------------------------------------------------------------
927
928 wxString wxTreeCtrl::GetItemText(const wxTreeItemId& item) const
929 {
930 wxCHECK_MSG( item.IsOk(), wxEmptyString, wxT("invalid tree item") );
931
932 wxChar buf[512]; // the size is arbitrary...
933
934 wxTreeViewItem tvItem(item, TVIF_TEXT);
935 tvItem.pszText = buf;
936 tvItem.cchTextMax = WXSIZEOF(buf);
937 if ( !DoGetItem(&tvItem) )
938 {
939 // don't return some garbage which was on stack, but an empty string
940 buf[0] = wxT('\0');
941 }
942
943 return wxString(buf);
944 }
945
946 void wxTreeCtrl::SetItemText(const wxTreeItemId& item, const wxString& text)
947 {
948 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
949
950 if ( IS_VIRTUAL_ROOT(item) )
951 return;
952
953 wxTreeViewItem tvItem(item, TVIF_TEXT);
954 tvItem.pszText = (wxChar *)text.c_str(); // conversion is ok
955 DoSetItem(&tvItem);
956
957 // when setting the text of the item being edited, the text control should
958 // be updated to reflect the new text as well, otherwise calling
959 // SetItemText() in the OnBeginLabelEdit() handler doesn't have any effect
960 //
961 // don't use GetEditControl() here because m_textCtrl is not set yet
962 HWND hwndEdit = TreeView_GetEditControl(GetHwnd());
963 if ( hwndEdit )
964 {
965 if ( item == m_idEdited )
966 {
967 ::SetWindowText(hwndEdit, text);
968 }
969 }
970 }
971
972 int wxTreeCtrl::DoGetItemImageFromData(const wxTreeItemId& item,
973 wxTreeItemIcon which) const
974 {
975 wxTreeViewItem tvItem(item, TVIF_PARAM);
976 if ( !DoGetItem(&tvItem) )
977 {
978 return -1;
979 }
980
981 return ((wxTreeItemIndirectData *)tvItem.lParam)->GetImage(which);
982 }
983
984 void wxTreeCtrl::DoSetItemImageFromData(const wxTreeItemId& item,
985 int image,
986 wxTreeItemIcon which) const
987 {
988 wxTreeViewItem tvItem(item, TVIF_PARAM);
989 if ( !DoGetItem(&tvItem) )
990 {
991 return;
992 }
993
994 wxTreeItemIndirectData *data = ((wxTreeItemIndirectData *)tvItem.lParam);
995
996 data->SetImage(image, which);
997
998 // make sure that we have selected images as well
999 if ( which == wxTreeItemIcon_Normal &&
1000 !data->HasImage(wxTreeItemIcon_Selected) )
1001 {
1002 data->SetImage(image, wxTreeItemIcon_Selected);
1003 }
1004
1005 if ( which == wxTreeItemIcon_Expanded &&
1006 !data->HasImage(wxTreeItemIcon_SelectedExpanded) )
1007 {
1008 data->SetImage(image, wxTreeItemIcon_SelectedExpanded);
1009 }
1010 }
1011
1012 void wxTreeCtrl::DoSetItemImages(const wxTreeItemId& item,
1013 int image,
1014 int imageSel)
1015 {
1016 wxTreeViewItem tvItem(item, TVIF_IMAGE | TVIF_SELECTEDIMAGE);
1017 tvItem.iSelectedImage = imageSel;
1018 tvItem.iImage = image;
1019 DoSetItem(&tvItem);
1020 }
1021
1022 int wxTreeCtrl::GetItemImage(const wxTreeItemId& item,
1023 wxTreeItemIcon which) const
1024 {
1025 wxCHECK_MSG( item.IsOk(), -1, wxT("invalid tree item") );
1026
1027 if ( (HITEM(item) == TVI_ROOT) && (m_windowStyle & wxTR_HIDE_ROOT) )
1028 {
1029 // TODO: Maybe a hidden root can still provide images?
1030 return -1;
1031 }
1032
1033 if ( HasIndirectData(item) )
1034 {
1035 return DoGetItemImageFromData(item, which);
1036 }
1037
1038 UINT mask;
1039 switch ( which )
1040 {
1041 default:
1042 wxFAIL_MSG( wxT("unknown tree item image type") );
1043
1044 case wxTreeItemIcon_Normal:
1045 mask = TVIF_IMAGE;
1046 break;
1047
1048 case wxTreeItemIcon_Selected:
1049 mask = TVIF_SELECTEDIMAGE;
1050 break;
1051
1052 case wxTreeItemIcon_Expanded:
1053 case wxTreeItemIcon_SelectedExpanded:
1054 return -1;
1055 }
1056
1057 wxTreeViewItem tvItem(item, mask);
1058 DoGetItem(&tvItem);
1059
1060 return mask == TVIF_IMAGE ? tvItem.iImage : tvItem.iSelectedImage;
1061 }
1062
1063 void wxTreeCtrl::SetItemImage(const wxTreeItemId& item, int image,
1064 wxTreeItemIcon which)
1065 {
1066 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1067
1068 if ( IS_VIRTUAL_ROOT(item) )
1069 {
1070 // TODO: Maybe a hidden root can still store images?
1071 return;
1072 }
1073
1074 int imageNormal,
1075 imageSel;
1076
1077 switch ( which )
1078 {
1079 default:
1080 wxFAIL_MSG( wxT("unknown tree item image type") );
1081 // fall through
1082
1083 case wxTreeItemIcon_Normal:
1084 {
1085 const int imageNormalOld = GetItemImage(item);
1086 const int imageSelOld =
1087 GetItemImage(item, wxTreeItemIcon_Selected);
1088
1089 // always set the normal image
1090 imageNormal = image;
1091
1092 // if the selected and normal images were the same, they should
1093 // be the same after the update, otherwise leave the selected
1094 // image as it was
1095 imageSel = imageNormalOld == imageSelOld ? image : imageSelOld;
1096 }
1097 break;
1098
1099 case wxTreeItemIcon_Selected:
1100 imageNormal = GetItemImage(item);
1101 imageSel = image;
1102 break;
1103
1104 case wxTreeItemIcon_Expanded:
1105 case wxTreeItemIcon_SelectedExpanded:
1106 if ( !HasIndirectData(item) )
1107 {
1108 // we need to get the old images first, because after we create
1109 // the wxTreeItemIndirectData GetItemXXXImage() will use it to
1110 // get the images
1111 imageNormal = GetItemImage(item);
1112 imageSel = GetItemImage(item, wxTreeItemIcon_Selected);
1113
1114 // if it doesn't have it yet, add it
1115 wxTreeItemIndirectData *data = new
1116 wxTreeItemIndirectData(this, item);
1117
1118 // copy the data to the new location
1119 data->SetImage(imageNormal, wxTreeItemIcon_Normal);
1120 data->SetImage(imageSel, wxTreeItemIcon_Selected);
1121 }
1122
1123 DoSetItemImageFromData(item, image, which);
1124
1125 // reset the normal/selected images because we won't use them any
1126 // more - now they're stored inside the indirect data
1127 imageNormal =
1128 imageSel = I_IMAGECALLBACK;
1129 break;
1130 }
1131
1132 // NB: at least in version 5.00.0518.9 of comctl32.dll we need to always
1133 // change both normal and selected image - otherwise the change simply
1134 // doesn't take place!
1135 DoSetItemImages(item, imageNormal, imageSel);
1136 }
1137
1138 wxTreeItemData *wxTreeCtrl::GetItemData(const wxTreeItemId& item) const
1139 {
1140 wxCHECK_MSG( item.IsOk(), NULL, wxT("invalid tree item") );
1141
1142 wxTreeViewItem tvItem(item, TVIF_PARAM);
1143
1144 // Hidden root may have data.
1145 if ( IS_VIRTUAL_ROOT(item) )
1146 {
1147 return GET_VIRTUAL_ROOT()->GetData();
1148 }
1149
1150 // Visible node.
1151 if ( !DoGetItem(&tvItem) )
1152 {
1153 return NULL;
1154 }
1155
1156 wxTreeItemData *data = (wxTreeItemData *)tvItem.lParam;
1157 if ( IsDataIndirect(data) )
1158 {
1159 data = ((wxTreeItemIndirectData *)data)->GetData();
1160 }
1161
1162 return data;
1163 }
1164
1165 void wxTreeCtrl::SetItemData(const wxTreeItemId& item, wxTreeItemData *data)
1166 {
1167 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1168
1169 if ( IS_VIRTUAL_ROOT(item) )
1170 {
1171 GET_VIRTUAL_ROOT()->SetData(data);
1172 }
1173
1174 // first, associate this piece of data with this item
1175 if ( data )
1176 {
1177 data->SetId(item);
1178 }
1179
1180 wxTreeViewItem tvItem(item, TVIF_PARAM);
1181
1182 if ( HasIndirectData(item) )
1183 {
1184 if ( DoGetItem(&tvItem) )
1185 {
1186 ((wxTreeItemIndirectData *)tvItem.lParam)->SetData(data);
1187 }
1188 else
1189 {
1190 wxFAIL_MSG( wxT("failed to change tree items data") );
1191 }
1192 }
1193 else
1194 {
1195 tvItem.lParam = (LPARAM)data;
1196 DoSetItem(&tvItem);
1197 }
1198 }
1199
1200 void wxTreeCtrl::SetIndirectItemData(const wxTreeItemId& item,
1201 wxTreeItemIndirectData *data)
1202 {
1203 // this should never happen because it's unnecessary and will probably lead
1204 // to crash too because the code elsewhere supposes that the pointer the
1205 // wxTreeItemIndirectData has is a real wxItemData and not
1206 // wxTreeItemIndirectData as well
1207 wxASSERT_MSG( !HasIndirectData(item), wxT("setting indirect data twice?") );
1208
1209 SetItemData(item, data);
1210 }
1211
1212 bool wxTreeCtrl::HasIndirectData(const wxTreeItemId& item) const
1213 {
1214 // query the item itself
1215 wxTreeViewItem tvItem(item, TVIF_PARAM);
1216 if ( !DoGetItem(&tvItem) )
1217 {
1218 return false;
1219 }
1220
1221 wxTreeItemData *data = (wxTreeItemData *)tvItem.lParam;
1222
1223 return data && IsDataIndirect(data);
1224 }
1225
1226 void wxTreeCtrl::SetItemHasChildren(const wxTreeItemId& item, bool has)
1227 {
1228 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1229
1230 if ( IS_VIRTUAL_ROOT(item) )
1231 return;
1232
1233 wxTreeViewItem tvItem(item, TVIF_CHILDREN);
1234 tvItem.cChildren = (int)has;
1235 DoSetItem(&tvItem);
1236 }
1237
1238 void wxTreeCtrl::SetItemBold(const wxTreeItemId& item, bool bold)
1239 {
1240 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1241
1242 if ( IS_VIRTUAL_ROOT(item) )
1243 return;
1244
1245 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_BOLD);
1246 tvItem.state = bold ? TVIS_BOLD : 0;
1247 DoSetItem(&tvItem);
1248 }
1249
1250 void wxTreeCtrl::SetItemDropHighlight(const wxTreeItemId& item, bool highlight)
1251 {
1252 if ( IS_VIRTUAL_ROOT(item) )
1253 return;
1254
1255 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_DROPHILITED);
1256 tvItem.state = highlight ? TVIS_DROPHILITED : 0;
1257 DoSetItem(&tvItem);
1258 }
1259
1260 void wxTreeCtrl::RefreshItem(const wxTreeItemId& item)
1261 {
1262 if ( IS_VIRTUAL_ROOT(item) )
1263 return;
1264
1265 wxRect rect;
1266 if ( GetBoundingRect(item, rect) )
1267 {
1268 RefreshRect(rect);
1269 }
1270 }
1271
1272 wxColour wxTreeCtrl::GetItemTextColour(const wxTreeItemId& item) const
1273 {
1274 wxCHECK_MSG( item.IsOk(), wxNullColour, wxT("invalid tree item") );
1275
1276 wxMapTreeAttr::const_iterator it = m_attrs.find(item.m_pItem);
1277 return it == m_attrs.end() ? wxNullColour : it->second->GetTextColour();
1278 }
1279
1280 wxColour wxTreeCtrl::GetItemBackgroundColour(const wxTreeItemId& item) const
1281 {
1282 wxCHECK_MSG( item.IsOk(), wxNullColour, wxT("invalid tree item") );
1283
1284 wxMapTreeAttr::const_iterator it = m_attrs.find(item.m_pItem);
1285 return it == m_attrs.end() ? wxNullColour : it->second->GetBackgroundColour();
1286 }
1287
1288 wxFont wxTreeCtrl::GetItemFont(const wxTreeItemId& item) const
1289 {
1290 wxCHECK_MSG( item.IsOk(), wxNullFont, wxT("invalid tree item") );
1291
1292 wxMapTreeAttr::const_iterator it = m_attrs.find(item.m_pItem);
1293 return it == m_attrs.end() ? wxNullFont : it->second->GetFont();
1294 }
1295
1296 void wxTreeCtrl::SetItemTextColour(const wxTreeItemId& item,
1297 const wxColour& col)
1298 {
1299 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1300
1301 wxTreeItemAttr *attr;
1302 wxMapTreeAttr::iterator it = m_attrs.find(item.m_pItem);
1303 if ( it == m_attrs.end() )
1304 {
1305 m_hasAnyAttr = true;
1306
1307 m_attrs[item.m_pItem] =
1308 attr = new wxTreeItemAttr;
1309 }
1310 else
1311 {
1312 attr = it->second;
1313 }
1314
1315 attr->SetTextColour(col);
1316
1317 RefreshItem(item);
1318 }
1319
1320 void wxTreeCtrl::SetItemBackgroundColour(const wxTreeItemId& item,
1321 const wxColour& col)
1322 {
1323 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1324
1325 wxTreeItemAttr *attr;
1326 wxMapTreeAttr::iterator it = m_attrs.find(item.m_pItem);
1327 if ( it == m_attrs.end() )
1328 {
1329 m_hasAnyAttr = true;
1330
1331 m_attrs[item.m_pItem] =
1332 attr = new wxTreeItemAttr;
1333 }
1334 else // already in the hash
1335 {
1336 attr = it->second;
1337 }
1338
1339 attr->SetBackgroundColour(col);
1340
1341 RefreshItem(item);
1342 }
1343
1344 void wxTreeCtrl::SetItemFont(const wxTreeItemId& item, const wxFont& font)
1345 {
1346 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1347
1348 wxTreeItemAttr *attr;
1349 wxMapTreeAttr::iterator it = m_attrs.find(item.m_pItem);
1350 if ( it == m_attrs.end() )
1351 {
1352 m_hasAnyAttr = true;
1353
1354 m_attrs[item.m_pItem] =
1355 attr = new wxTreeItemAttr;
1356 }
1357 else // already in the hash
1358 {
1359 attr = it->second;
1360 }
1361
1362 attr->SetFont(font);
1363
1364 RefreshItem(item);
1365 }
1366
1367 // ----------------------------------------------------------------------------
1368 // Item status
1369 // ----------------------------------------------------------------------------
1370
1371 bool wxTreeCtrl::IsVisible(const wxTreeItemId& item) const
1372 {
1373 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1374
1375 if ( item == wxTreeItemId(TVI_ROOT) )
1376 {
1377 // virtual (hidden) root is never visible
1378 return false;
1379 }
1380
1381 // Bug in Gnu-Win32 headers, so don't use the macro TreeView_GetItemRect
1382 RECT rect;
1383
1384 // this ugliness comes directly from MSDN - it *is* the correct way to pass
1385 // the HTREEITEM with TVM_GETITEMRECT
1386 *(HTREEITEM *)&rect = HITEM(item);
1387
1388 // true means to get rect for just the text, not the whole line
1389 if ( !::SendMessage(GetHwnd(), TVM_GETITEMRECT, true, (LPARAM)&rect) )
1390 {
1391 // if TVM_GETITEMRECT returned false, then the item is definitely not
1392 // visible (because its parent is not expanded)
1393 return false;
1394 }
1395
1396 // however if it returned true, the item might still be outside the
1397 // currently visible part of the tree, test for it (notice that partly
1398 // visible means visible here)
1399 return rect.bottom > 0 && rect.top < GetClientSize().y;
1400 }
1401
1402 bool wxTreeCtrl::ItemHasChildren(const wxTreeItemId& item) const
1403 {
1404 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1405
1406 wxTreeViewItem tvItem(item, TVIF_CHILDREN);
1407 DoGetItem(&tvItem);
1408
1409 return tvItem.cChildren != 0;
1410 }
1411
1412 bool wxTreeCtrl::IsExpanded(const wxTreeItemId& item) const
1413 {
1414 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1415
1416 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_EXPANDED);
1417 DoGetItem(&tvItem);
1418
1419 return (tvItem.state & TVIS_EXPANDED) != 0;
1420 }
1421
1422 bool wxTreeCtrl::IsSelected(const wxTreeItemId& item) const
1423 {
1424 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1425
1426 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_SELECTED);
1427 DoGetItem(&tvItem);
1428
1429 return (tvItem.state & TVIS_SELECTED) != 0;
1430 }
1431
1432 bool wxTreeCtrl::IsBold(const wxTreeItemId& item) const
1433 {
1434 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1435
1436 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_BOLD);
1437 DoGetItem(&tvItem);
1438
1439 return (tvItem.state & TVIS_BOLD) != 0;
1440 }
1441
1442 // ----------------------------------------------------------------------------
1443 // navigation
1444 // ----------------------------------------------------------------------------
1445
1446 wxTreeItemId wxTreeCtrl::GetRootItem() const
1447 {
1448 // Root may be real (visible) or virtual (hidden).
1449 if ( GET_VIRTUAL_ROOT() )
1450 return TVI_ROOT;
1451
1452 return wxTreeItemId(TreeView_GetRoot(GetHwnd()));
1453 }
1454
1455 wxTreeItemId wxTreeCtrl::GetSelection() const
1456 {
1457 wxCHECK_MSG( !(m_windowStyle & wxTR_MULTIPLE), wxTreeItemId(),
1458 wxT("this only works with single selection controls") );
1459
1460 return wxTreeItemId(TreeView_GetSelection(GetHwnd()));
1461 }
1462
1463 wxTreeItemId wxTreeCtrl::GetItemParent(const wxTreeItemId& item) const
1464 {
1465 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1466
1467 HTREEITEM hItem;
1468
1469 if ( IS_VIRTUAL_ROOT(item) )
1470 {
1471 // no parent for the virtual root
1472 hItem = 0;
1473 }
1474 else // normal item
1475 {
1476 hItem = TreeView_GetParent(GetHwnd(), HITEM(item));
1477 if ( !hItem && HasFlag(wxTR_HIDE_ROOT) )
1478 {
1479 // the top level items should have the virtual root as their parent
1480 hItem = TVI_ROOT;
1481 }
1482 }
1483
1484 return wxTreeItemId(hItem);
1485 }
1486
1487 wxTreeItemId wxTreeCtrl::GetFirstChild(const wxTreeItemId& item,
1488 wxTreeItemIdValue& cookie) const
1489 {
1490 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1491
1492 // remember the last child returned in 'cookie'
1493 cookie = TreeView_GetChild(GetHwnd(), HITEM(item));
1494
1495 return wxTreeItemId(cookie);
1496 }
1497
1498 wxTreeItemId wxTreeCtrl::GetNextChild(const wxTreeItemId& WXUNUSED(item),
1499 wxTreeItemIdValue& cookie) const
1500 {
1501 wxTreeItemId fromCookie(cookie);
1502
1503 HTREEITEM hitem = HITEM(fromCookie);
1504
1505 hitem = TreeView_GetNextSibling(GetHwnd(), hitem);
1506
1507 wxTreeItemId item(hitem);
1508
1509 cookie = item.m_pItem;
1510
1511 return item;
1512 }
1513
1514 #if WXWIN_COMPATIBILITY_2_4
1515
1516 wxTreeItemId wxTreeCtrl::GetFirstChild(const wxTreeItemId& item,
1517 long& cookie) const
1518 {
1519 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1520
1521 cookie = (long)TreeView_GetChild(GetHwnd(), HITEM(item));
1522
1523 return wxTreeItemId((void *)cookie);
1524 }
1525
1526 wxTreeItemId wxTreeCtrl::GetNextChild(const wxTreeItemId& WXUNUSED(item),
1527 long& cookie) const
1528 {
1529 wxTreeItemId fromCookie((void *)cookie);
1530
1531 HTREEITEM hitem = HITEM(fromCookie);
1532
1533 hitem = TreeView_GetNextSibling(GetHwnd(), hitem);
1534
1535 wxTreeItemId item(hitem);
1536
1537 cookie = (long)item.m_pItem;
1538
1539 return item;
1540 }
1541
1542 #endif // WXWIN_COMPATIBILITY_2_4
1543
1544 wxTreeItemId wxTreeCtrl::GetLastChild(const wxTreeItemId& item) const
1545 {
1546 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1547
1548 // can this be done more efficiently?
1549 wxTreeItemIdValue cookie;
1550
1551 wxTreeItemId childLast,
1552 child = GetFirstChild(item, cookie);
1553 while ( child.IsOk() )
1554 {
1555 childLast = child;
1556 child = GetNextChild(item, cookie);
1557 }
1558
1559 return childLast;
1560 }
1561
1562 wxTreeItemId wxTreeCtrl::GetNextSibling(const wxTreeItemId& item) const
1563 {
1564 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1565 return wxTreeItemId(TreeView_GetNextSibling(GetHwnd(), HITEM(item)));
1566 }
1567
1568 wxTreeItemId wxTreeCtrl::GetPrevSibling(const wxTreeItemId& item) const
1569 {
1570 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1571 return wxTreeItemId(TreeView_GetPrevSibling(GetHwnd(), HITEM(item)));
1572 }
1573
1574 wxTreeItemId wxTreeCtrl::GetFirstVisibleItem() const
1575 {
1576 return wxTreeItemId(TreeView_GetFirstVisible(GetHwnd()));
1577 }
1578
1579 wxTreeItemId wxTreeCtrl::GetNextVisible(const wxTreeItemId& item) const
1580 {
1581 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1582 wxASSERT_MSG( IsVisible(item), wxT("The item you call GetNextVisible() for must be visible itself!"));
1583
1584 return wxTreeItemId(TreeView_GetNextVisible(GetHwnd(), HITEM(item)));
1585 }
1586
1587 wxTreeItemId wxTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const
1588 {
1589 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1590 wxASSERT_MSG( IsVisible(item), wxT("The item you call GetPrevVisible() for must be visible itself!"));
1591
1592 return wxTreeItemId(TreeView_GetPrevVisible(GetHwnd(), HITEM(item)));
1593 }
1594
1595 // ----------------------------------------------------------------------------
1596 // multiple selections emulation
1597 // ----------------------------------------------------------------------------
1598
1599 bool wxTreeCtrl::IsItemChecked(const wxTreeItemId& item) const
1600 {
1601 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1602
1603 // receive the desired information.
1604 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
1605 DoGetItem(&tvItem);
1606
1607 // state image indices are 1 based
1608 return ((tvItem.state >> 12) - 1) == 1;
1609 }
1610
1611 void wxTreeCtrl::SetItemCheck(const wxTreeItemId& item, bool check)
1612 {
1613 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1614
1615 // receive the desired information.
1616 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
1617
1618 DoGetItem(&tvItem);
1619
1620 // state images are one-based
1621 tvItem.state = (check ? 2 : 1) << 12;
1622
1623 DoSetItem(&tvItem);
1624 }
1625
1626 size_t wxTreeCtrl::GetSelections(wxArrayTreeItemIds& selections) const
1627 {
1628 TraverseSelections selector(this, selections);
1629
1630 return selector.GetCount();
1631 }
1632
1633 // ----------------------------------------------------------------------------
1634 // Usual operations
1635 // ----------------------------------------------------------------------------
1636
1637 wxTreeItemId wxTreeCtrl::DoInsertItem(const wxTreeItemId& parent,
1638 wxTreeItemId hInsertAfter,
1639 const wxString& text,
1640 int image, int selectedImage,
1641 wxTreeItemData *data)
1642 {
1643 wxCHECK_MSG( parent.IsOk() || !TreeView_GetRoot(GetHwnd()),
1644 wxTreeItemId(),
1645 _T("can't have more than one root in the tree") );
1646
1647 TV_INSERTSTRUCT tvIns;
1648 tvIns.hParent = HITEM(parent);
1649 tvIns.hInsertAfter = HITEM(hInsertAfter);
1650
1651 // this is how we insert the item as the first child: supply a NULL
1652 // hInsertAfter
1653 if ( !tvIns.hInsertAfter )
1654 {
1655 tvIns.hInsertAfter = TVI_FIRST;
1656 }
1657
1658 UINT mask = 0;
1659 if ( !text.empty() )
1660 {
1661 mask |= TVIF_TEXT;
1662 tvIns.item.pszText = (wxChar *)text.c_str(); // cast is ok
1663 }
1664 else
1665 {
1666 tvIns.item.pszText = NULL;
1667 tvIns.item.cchTextMax = 0;
1668 }
1669
1670 if ( image != -1 )
1671 {
1672 mask |= TVIF_IMAGE;
1673 tvIns.item.iImage = image;
1674
1675 if ( selectedImage == -1 )
1676 {
1677 // take the same image for selected icon if not specified
1678 selectedImage = image;
1679 }
1680 }
1681
1682 if ( selectedImage != -1 )
1683 {
1684 mask |= TVIF_SELECTEDIMAGE;
1685 tvIns.item.iSelectedImage = selectedImage;
1686 }
1687
1688 if ( data != NULL )
1689 {
1690 mask |= TVIF_PARAM;
1691 tvIns.item.lParam = (LPARAM)data;
1692 }
1693
1694 tvIns.item.mask = mask;
1695
1696 HTREEITEM id = (HTREEITEM) TreeView_InsertItem(GetHwnd(), &tvIns);
1697 if ( id == 0 )
1698 {
1699 wxLogLastError(wxT("TreeView_InsertItem"));
1700 }
1701
1702 if ( data != NULL )
1703 {
1704 // associate the application tree item with Win32 tree item handle
1705 data->SetId(id);
1706 }
1707
1708 return wxTreeItemId(id);
1709 }
1710
1711 // for compatibility only
1712 #if WXWIN_COMPATIBILITY_2_4
1713
1714 wxTreeItemId wxTreeCtrl::InsertItem(const wxTreeItemId& parent,
1715 const wxString& text,
1716 int image, int selImage,
1717 long insertAfter)
1718 {
1719 return DoInsertItem(parent, wxTreeItemId((void *)insertAfter), text,
1720 image, selImage, NULL);
1721 }
1722
1723 wxImageList *wxTreeCtrl::GetImageList(int) const
1724 {
1725 return GetImageList();
1726 }
1727
1728 void wxTreeCtrl::SetImageList(wxImageList *imageList, int)
1729 {
1730 SetImageList(imageList);
1731 }
1732
1733 int wxTreeCtrl::GetItemSelectedImage(const wxTreeItemId& item) const
1734 {
1735 return GetItemImage(item, wxTreeItemIcon_Selected);
1736 }
1737
1738 void wxTreeCtrl::SetItemSelectedImage(const wxTreeItemId& item, int image)
1739 {
1740 SetItemImage(item, image, wxTreeItemIcon_Selected);
1741 }
1742
1743 #endif // WXWIN_COMPATIBILITY_2_4
1744
1745 wxTreeItemId wxTreeCtrl::AddRoot(const wxString& text,
1746 int image, int selectedImage,
1747 wxTreeItemData *data)
1748 {
1749
1750 if ( m_windowStyle & wxTR_HIDE_ROOT )
1751 {
1752 // create a virtual root item, the parent for all the others
1753 m_pVirtualRoot = new wxVirtualNode(data);
1754
1755 return TVI_ROOT;
1756 }
1757
1758 return DoInsertItem(wxTreeItemId(), wxTreeItemId(),
1759 text, image, selectedImage, data);
1760 }
1761
1762 wxTreeItemId wxTreeCtrl::PrependItem(const wxTreeItemId& parent,
1763 const wxString& text,
1764 int image, int selectedImage,
1765 wxTreeItemData *data)
1766 {
1767 return DoInsertItem(parent, TVI_FIRST,
1768 text, image, selectedImage, data);
1769 }
1770
1771 wxTreeItemId wxTreeCtrl::InsertItem(const wxTreeItemId& parent,
1772 const wxTreeItemId& idPrevious,
1773 const wxString& text,
1774 int image, int selectedImage,
1775 wxTreeItemData *data)
1776 {
1777 return DoInsertItem(parent, idPrevious, text, image, selectedImage, data);
1778 }
1779
1780 wxTreeItemId wxTreeCtrl::InsertItem(const wxTreeItemId& parent,
1781 size_t index,
1782 const wxString& text,
1783 int image, int selectedImage,
1784 wxTreeItemData *data)
1785 {
1786 // find the item from index
1787 wxTreeItemIdValue cookie;
1788 wxTreeItemId idPrev, idCur = GetFirstChild(parent, cookie);
1789 while ( index != 0 && idCur.IsOk() )
1790 {
1791 index--;
1792
1793 idPrev = idCur;
1794 idCur = GetNextChild(parent, cookie);
1795 }
1796
1797 // assert, not check: if the index is invalid, we will append the item
1798 // to the end
1799 wxASSERT_MSG( index == 0, _T("bad index in wxTreeCtrl::InsertItem") );
1800
1801 return DoInsertItem(parent, idPrev, text, image, selectedImage, data);
1802 }
1803
1804 wxTreeItemId wxTreeCtrl::AppendItem(const wxTreeItemId& parent,
1805 const wxString& text,
1806 int image, int selectedImage,
1807 wxTreeItemData *data)
1808 {
1809 return DoInsertItem(parent, TVI_LAST,
1810 text, image, selectedImage, data);
1811 }
1812
1813 void wxTreeCtrl::Delete(const wxTreeItemId& item)
1814 {
1815 if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
1816 {
1817 wxLogLastError(wxT("TreeView_DeleteItem"));
1818 }
1819 }
1820
1821 // delete all children (but don't delete the item itself)
1822 void wxTreeCtrl::DeleteChildren(const wxTreeItemId& item)
1823 {
1824 wxTreeItemIdValue cookie;
1825
1826 wxArrayTreeItemIds children;
1827 wxTreeItemId child = GetFirstChild(item, cookie);
1828 while ( child.IsOk() )
1829 {
1830 children.Add(child);
1831
1832 child = GetNextChild(item, cookie);
1833 }
1834
1835 size_t nCount = children.Count();
1836 for ( size_t n = 0; n < nCount; n++ )
1837 {
1838 if ( !TreeView_DeleteItem(GetHwnd(), HITEM_PTR(children[n])) )
1839 {
1840 wxLogLastError(wxT("TreeView_DeleteItem"));
1841 }
1842 }
1843 }
1844
1845 void wxTreeCtrl::DeleteAllItems()
1846 {
1847 // delete the "virtual" root item.
1848 if ( GET_VIRTUAL_ROOT() )
1849 {
1850 delete GET_VIRTUAL_ROOT();
1851 m_pVirtualRoot = NULL;
1852 }
1853
1854 // and all the real items
1855
1856 if ( !TreeView_DeleteAllItems(GetHwnd()) )
1857 {
1858 wxLogLastError(wxT("TreeView_DeleteAllItems"));
1859 }
1860 }
1861
1862 void wxTreeCtrl::DoExpand(const wxTreeItemId& item, int flag)
1863 {
1864 wxASSERT_MSG( flag == TVE_COLLAPSE ||
1865 flag == (TVE_COLLAPSE | TVE_COLLAPSERESET) ||
1866 flag == TVE_EXPAND ||
1867 flag == TVE_TOGGLE,
1868 wxT("Unknown flag in wxTreeCtrl::DoExpand") );
1869
1870 // A hidden root can be neither expanded nor collapsed.
1871 wxCHECK_RET( !(m_windowStyle & wxTR_HIDE_ROOT) || (HITEM(item) != TVI_ROOT),
1872 wxT("Can't expand/collapse hidden root node!") )
1873
1874 // TreeView_Expand doesn't send TVN_ITEMEXPAND(ING) messages, so we must
1875 // emulate them. This behaviour has changed slightly with comctl32.dll
1876 // v 4.70 - now it does send them but only the first time. To maintain
1877 // compatible behaviour and also in order to not have surprises with the
1878 // future versions, don't rely on this and still do everything ourselves.
1879 // To avoid that the messages be sent twice when the item is expanded for
1880 // the first time we must clear TVIS_EXPANDEDONCE style manually.
1881
1882 wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_EXPANDEDONCE);
1883 tvItem.state = 0;
1884 DoSetItem(&tvItem);
1885
1886 if ( TreeView_Expand(GetHwnd(), HITEM(item), flag) != 0 )
1887 {
1888 wxTreeEvent event(wxEVT_NULL, m_windowId);
1889 event.m_item = item;
1890 event.SetEventObject(this);
1891
1892 // note that the {EXPAND|COLLAPS}ING event is sent by TreeView_Expand()
1893 // itself
1894 event.SetEventType(gs_expandEvents[IsExpanded(item) ? IDX_EXPAND
1895 : IDX_COLLAPSE]
1896 [IDX_DONE]);
1897
1898 (void)GetEventHandler()->ProcessEvent(event);
1899 }
1900 //else: change didn't took place, so do nothing at all
1901 }
1902
1903 void wxTreeCtrl::Expand(const wxTreeItemId& item)
1904 {
1905 DoExpand(item, TVE_EXPAND);
1906 }
1907
1908 void wxTreeCtrl::Collapse(const wxTreeItemId& item)
1909 {
1910 DoExpand(item, TVE_COLLAPSE);
1911 }
1912
1913 void wxTreeCtrl::CollapseAndReset(const wxTreeItemId& item)
1914 {
1915 DoExpand(item, TVE_COLLAPSE | TVE_COLLAPSERESET);
1916 }
1917
1918 void wxTreeCtrl::Toggle(const wxTreeItemId& item)
1919 {
1920 DoExpand(item, TVE_TOGGLE);
1921 }
1922
1923 #if WXWIN_COMPATIBILITY_2_4
1924
1925 void wxTreeCtrl::ExpandItem(const wxTreeItemId& item, int action)
1926 {
1927 DoExpand(item, action);
1928 }
1929
1930 #endif
1931
1932 void wxTreeCtrl::Unselect()
1933 {
1934 wxASSERT_MSG( !(m_windowStyle & wxTR_MULTIPLE),
1935 wxT("doesn't make sense, may be you want UnselectAll()?") );
1936
1937 // just remove the selection
1938 SelectItem(wxTreeItemId());
1939 }
1940
1941 void wxTreeCtrl::UnselectAll()
1942 {
1943 if ( m_windowStyle & wxTR_MULTIPLE )
1944 {
1945 wxArrayTreeItemIds selections;
1946 size_t count = GetSelections(selections);
1947 for ( size_t n = 0; n < count; n++ )
1948 {
1949 #if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
1950 SetItemCheck(HITEM_PTR(selections[n]), false);
1951 #else // !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
1952 ::UnselectItem(GetHwnd(), HITEM_PTR(selections[n]));
1953 #endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE/!wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
1954 }
1955
1956 m_htSelStart.Unset();
1957 }
1958 else
1959 {
1960 // just remove the selection
1961 Unselect();
1962 }
1963 }
1964
1965 void wxTreeCtrl::SelectItem(const wxTreeItemId& item, bool select)
1966 {
1967 if ( m_windowStyle & wxTR_MULTIPLE )
1968 {
1969 #if wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
1970 // selecting the item means checking it
1971 SetItemCheck(item, select);
1972 #else // !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
1973 ::SelectItem(GetHwnd(), HITEM(item), select);
1974 #endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE/!wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
1975 }
1976 else
1977 {
1978 wxASSERT_MSG( select,
1979 _T("SelectItem(false) works only for multiselect") );
1980
1981 // inspite of the docs (MSDN Jan 99 edition), we don't seem to receive
1982 // the notification from the control (i.e. TVN_SELCHANG{ED|ING}), so
1983 // send them ourselves
1984
1985 wxTreeEvent event(wxEVT_NULL, m_windowId);
1986 event.m_item = item;
1987 event.SetEventObject(this);
1988
1989 event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGING);
1990 if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
1991 {
1992 if ( !TreeView_SelectItem(GetHwnd(), HITEM(item)) )
1993 {
1994 wxLogLastError(wxT("TreeView_SelectItem"));
1995 }
1996 else // ok
1997 {
1998 event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGED);
1999 (void)GetEventHandler()->ProcessEvent(event);
2000 }
2001 }
2002 //else: program vetoed the change
2003 }
2004 }
2005
2006 void wxTreeCtrl::UnselectItem(const wxTreeItemId& item)
2007 {
2008 SelectItem(item, false);
2009 }
2010
2011 void wxTreeCtrl::ToggleItemSelection(const wxTreeItemId& item)
2012 {
2013 SelectItem(item, !IsSelected(item));
2014 }
2015
2016 void wxTreeCtrl::EnsureVisible(const wxTreeItemId& item)
2017 {
2018 // no error return
2019 TreeView_EnsureVisible(GetHwnd(), HITEM(item));
2020 }
2021
2022 void wxTreeCtrl::ScrollTo(const wxTreeItemId& item)
2023 {
2024 if ( !TreeView_SelectSetFirstVisible(GetHwnd(), HITEM(item)) )
2025 {
2026 wxLogLastError(wxT("TreeView_SelectSetFirstVisible"));
2027 }
2028 }
2029
2030 wxTextCtrl *wxTreeCtrl::GetEditControl() const
2031 {
2032 return m_textCtrl;
2033 }
2034
2035 void wxTreeCtrl::DeleteTextCtrl()
2036 {
2037 if ( m_textCtrl )
2038 {
2039 // the HWND corresponding to this control is deleted by the tree
2040 // control itself and we don't know when exactly this happens, so check
2041 // if the window still exists before calling UnsubclassWin()
2042 if ( !::IsWindow(GetHwndOf(m_textCtrl)) )
2043 {
2044 m_textCtrl->SetHWND(0);
2045 }
2046
2047 m_textCtrl->UnsubclassWin();
2048 m_textCtrl->SetHWND(0);
2049 delete m_textCtrl;
2050 m_textCtrl = NULL;
2051
2052 m_idEdited.Unset();
2053 }
2054 }
2055
2056 wxTextCtrl* wxTreeCtrl::EditLabel(const wxTreeItemId& item,
2057 wxClassInfo* textControlClass)
2058 {
2059 wxASSERT( textControlClass->IsKindOf(CLASSINFO(wxTextCtrl)) );
2060
2061 DeleteTextCtrl();
2062
2063 m_idEdited = item;
2064 m_textCtrl = (wxTextCtrl *)textControlClass->CreateObject();
2065 HWND hWnd = (HWND) TreeView_EditLabel(GetHwnd(), HITEM(item));
2066
2067 // this is not an error - the TVN_BEGINLABELEDIT handler might have
2068 // returned false
2069 if ( !hWnd )
2070 {
2071 delete m_textCtrl;
2072 m_textCtrl = NULL;
2073 return NULL;
2074 }
2075
2076 // textctrl is subclassed in MSWOnNotify
2077 return m_textCtrl;
2078 }
2079
2080 // End label editing, optionally cancelling the edit
2081 void wxTreeCtrl::DoEndEditLabel(bool discardChanges)
2082 {
2083 TreeView_EndEditLabelNow(GetHwnd(), discardChanges);
2084
2085 DeleteTextCtrl();
2086 }
2087
2088 wxTreeItemId wxTreeCtrl::HitTest(const wxPoint& point, int& flags)
2089 {
2090 TV_HITTESTINFO hitTestInfo;
2091 hitTestInfo.pt.x = (int)point.x;
2092 hitTestInfo.pt.y = (int)point.y;
2093
2094 TreeView_HitTest(GetHwnd(), &hitTestInfo);
2095
2096 flags = 0;
2097
2098 // avoid repetition
2099 #define TRANSLATE_FLAG(flag) if ( hitTestInfo.flags & TVHT_##flag ) \
2100 flags |= wxTREE_HITTEST_##flag
2101
2102 TRANSLATE_FLAG(ABOVE);
2103 TRANSLATE_FLAG(BELOW);
2104 TRANSLATE_FLAG(NOWHERE);
2105 TRANSLATE_FLAG(ONITEMBUTTON);
2106 TRANSLATE_FLAG(ONITEMICON);
2107 TRANSLATE_FLAG(ONITEMINDENT);
2108 TRANSLATE_FLAG(ONITEMLABEL);
2109 TRANSLATE_FLAG(ONITEMRIGHT);
2110 TRANSLATE_FLAG(ONITEMSTATEICON);
2111 TRANSLATE_FLAG(TOLEFT);
2112 TRANSLATE_FLAG(TORIGHT);
2113
2114 #undef TRANSLATE_FLAG
2115
2116 return wxTreeItemId(hitTestInfo.hItem);
2117 }
2118
2119 bool wxTreeCtrl::GetBoundingRect(const wxTreeItemId& item,
2120 wxRect& rect,
2121 bool textOnly) const
2122 {
2123 RECT rc;
2124
2125 // Virtual root items have no bounding rectangle
2126 if ( IS_VIRTUAL_ROOT(item) )
2127 {
2128 return false;
2129 }
2130
2131 if ( TreeView_GetItemRect(GetHwnd(), HITEM(item),
2132 &rc, textOnly) )
2133 {
2134 rect = wxRect(wxPoint(rc.left, rc.top), wxPoint(rc.right, rc.bottom));
2135
2136 return true;
2137 }
2138 else
2139 {
2140 // couldn't retrieve rect: for example, item isn't visible
2141 return false;
2142 }
2143 }
2144
2145 // ----------------------------------------------------------------------------
2146 // sorting stuff
2147 // ----------------------------------------------------------------------------
2148
2149 // this is just a tiny namespace which is friend to wxTreeCtrl and so can use
2150 // functions such as IsDataIndirect()
2151 class wxTreeSortHelper
2152 {
2153 public:
2154 static int CALLBACK Compare(LPARAM data1, LPARAM data2, LPARAM tree);
2155
2156 private:
2157 static wxTreeItemId GetIdFromData(wxTreeCtrl *tree, LPARAM item)
2158 {
2159 wxTreeItemData *data = (wxTreeItemData *)item;
2160 if ( tree->IsDataIndirect(data) )
2161 {
2162 data = ((wxTreeItemIndirectData *)data)->GetData();
2163 }
2164
2165 return data->GetId();
2166 }
2167 };
2168
2169 int CALLBACK wxTreeSortHelper::Compare(LPARAM pItem1,
2170 LPARAM pItem2,
2171 LPARAM htree)
2172 {
2173 wxCHECK_MSG( pItem1 && pItem2, 0,
2174 wxT("sorting tree without data doesn't make sense") );
2175
2176 wxTreeCtrl *tree = (wxTreeCtrl *)htree;
2177
2178 return tree->OnCompareItems(GetIdFromData(tree, pItem1),
2179 GetIdFromData(tree, pItem2));
2180 }
2181
2182 int wxTreeCtrl::OnCompareItems(const wxTreeItemId& item1,
2183 const wxTreeItemId& item2)
2184 {
2185 return wxStrcmp(GetItemText(item1), GetItemText(item2));
2186 }
2187
2188 void wxTreeCtrl::SortChildren(const wxTreeItemId& item)
2189 {
2190 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
2191
2192 // rely on the fact that TreeView_SortChildren does the same thing as our
2193 // default behaviour, i.e. sorts items alphabetically and so call it
2194 // directly if we're not in derived class (much more efficient!)
2195 if ( GetClassInfo() == CLASSINFO(wxTreeCtrl) )
2196 {
2197 TreeView_SortChildren(GetHwnd(), HITEM(item), 0);
2198 }
2199 else
2200 {
2201 TV_SORTCB tvSort;
2202 tvSort.hParent = HITEM(item);
2203 tvSort.lpfnCompare = wxTreeSortHelper::Compare;
2204 tvSort.lParam = (LPARAM)this;
2205 TreeView_SortChildrenCB(GetHwnd(), &tvSort, 0 /* reserved */);
2206 }
2207 }
2208
2209 // ----------------------------------------------------------------------------
2210 // implementation
2211 // ----------------------------------------------------------------------------
2212
2213 bool wxTreeCtrl::MSWCommand(WXUINT cmd, WXWORD id)
2214 {
2215 if ( cmd == EN_UPDATE )
2216 {
2217 wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, id);
2218 event.SetEventObject( this );
2219 ProcessCommand(event);
2220 }
2221 else if ( cmd == EN_KILLFOCUS )
2222 {
2223 wxCommandEvent event(wxEVT_KILL_FOCUS, id);
2224 event.SetEventObject( this );
2225 ProcessCommand(event);
2226 }
2227 else
2228 {
2229 // nothing done
2230 return false;
2231 }
2232
2233 // command processed
2234 return true;
2235 }
2236
2237 // we hook into WndProc to process WM_MOUSEMOVE/WM_BUTTONUP messages - as we
2238 // only do it during dragging, minimize wxWin overhead (this is important for
2239 // WM_MOUSEMOVE as they're a lot of them) by catching Windows messages directly
2240 // instead of passing by wxWin events
2241 WXLRESULT wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
2242 {
2243 bool processed = false;
2244 WXLRESULT rc = 0;
2245 bool isMultiple = HasFlag(wxTR_MULTIPLE);
2246
2247 if ( nMsg == WM_CONTEXTMENU )
2248 {
2249 wxTreeEvent event( wxEVT_COMMAND_TREE_ITEM_MENU, GetId() );
2250
2251 // can't use GetSelection() here as it would assert in multiselect mode
2252 event.m_item = wxTreeItemId(TreeView_GetSelection(GetHwnd()));
2253 event.SetEventObject( this );
2254
2255 if ( GetEventHandler()->ProcessEvent(event) )
2256 processed = true;
2257 //else: continue with generating wxEVT_CONTEXT_MENU in base class code
2258 }
2259 else if ( (nMsg >= WM_MOUSEFIRST) && (nMsg <= WM_MOUSELAST) )
2260 {
2261 // we only process mouse messages here and these parameters have the
2262 // same meaning for all of them
2263 int x = GET_X_LPARAM(lParam),
2264 y = GET_Y_LPARAM(lParam);
2265 HTREEITEM htItem = GetItemFromPoint(GetHwnd(), x, y);
2266
2267 switch ( nMsg )
2268 {
2269 case WM_RBUTTONDOWN:
2270 // if the item we are about to right click on is not already
2271 // selected or if we click outside of any item, remove the
2272 // entire previous selection
2273 if ( !htItem || !::IsItemSelected(GetHwnd(), htItem) )
2274 {
2275 UnselectAll();
2276 }
2277
2278 // select item and set the focus to the
2279 // newly selected item
2280 ::SelectItem(GetHwnd(), htItem);
2281 ::SetFocus(GetHwnd(), htItem);
2282 break;
2283
2284 #if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
2285 case WM_LBUTTONDOWN:
2286 if ( htItem && isMultiple )
2287 {
2288 if ( wParam & MK_CONTROL )
2289 {
2290 SetFocus();
2291
2292 // toggle selected state
2293 ::ToggleItemSelection(GetHwnd(), htItem);
2294
2295 ::SetFocus(GetHwnd(), htItem);
2296
2297 // reset on any click without Shift
2298 m_htSelStart.Unset();
2299
2300 processed = true;
2301 }
2302 else if ( wParam & MK_SHIFT )
2303 {
2304 // this selects all items between the starting one and
2305 // the current
2306
2307 if ( !m_htSelStart )
2308 {
2309 // take the focused item
2310 m_htSelStart = TreeView_GetSelection(GetHwnd());
2311 }
2312
2313 SelectRange(GetHwnd(), HITEM(m_htSelStart), htItem,
2314 !(wParam & MK_CONTROL));
2315
2316 ::SetFocus(GetHwnd(), htItem);
2317
2318 processed = true;
2319 }
2320 else // normal click
2321 {
2322 // avoid doing anything if we click on the only
2323 // currently selected item
2324
2325 wxArrayTreeItemIds selections;
2326 size_t count = GetSelections(selections);
2327 if ( count == 0 ||
2328 count > 1 ||
2329 HITEM_PTR(selections[0]) != htItem )
2330 {
2331 // clear the previously selected items, if the
2332 // user clicked outside of the present selection.
2333 // otherwise, perform the deselection on mouse-up.
2334 // this allows multiple drag and drop to work.
2335
2336 if (IsItemSelected(GetHwnd(), htItem))
2337 {
2338 ::SetFocus(GetHwnd(), htItem);
2339 }
2340 else
2341 {
2342 UnselectAll();
2343
2344 // prevent the click from starting in-place editing
2345 // which should only happen if we click on the
2346 // already selected item (and nothing else is
2347 // selected)
2348
2349 TreeView_SelectItem(GetHwnd(), 0);
2350 ::SelectItem(GetHwnd(), htItem);
2351 }
2352 }
2353
2354 // reset on any click without Shift
2355 m_htSelStart.Unset();
2356 }
2357 }
2358 break;
2359 #endif // wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
2360
2361 case WM_MOUSEMOVE:
2362 if ( m_dragImage )
2363 {
2364 m_dragImage->Move(wxPoint(x, y));
2365 if ( htItem )
2366 {
2367 // highlight the item as target (hiding drag image is
2368 // necessary - otherwise the display will be corrupted)
2369 m_dragImage->Hide();
2370 TreeView_SelectDropTarget(GetHwnd(), htItem);
2371 m_dragImage->Show();
2372 }
2373 }
2374 break;
2375
2376 case WM_LBUTTONUP:
2377
2378 // facilitates multiple drag-and-drop
2379 if (htItem && isMultiple)
2380 {
2381 wxArrayTreeItemIds selections;
2382 size_t count = GetSelections(selections);
2383
2384 if (count > 1 &&
2385 !(wParam & MK_CONTROL) &&
2386 !(wParam & MK_SHIFT))
2387 {
2388 UnselectAll();
2389 TreeView_SelectItem(GetHwnd(), htItem);
2390 }
2391 }
2392
2393 // fall through
2394
2395 case WM_RBUTTONUP:
2396 if ( m_dragImage )
2397 {
2398 m_dragImage->EndDrag();
2399 delete m_dragImage;
2400 m_dragImage = NULL;
2401
2402 // generate the drag end event
2403 wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG, m_windowId);
2404
2405 event.m_item = htItem;
2406 event.m_pointDrag = wxPoint(x, y);
2407 event.SetEventObject(this);
2408
2409 (void)GetEventHandler()->ProcessEvent(event);
2410
2411 // if we don't do it, the tree seems to think that 2 items
2412 // are selected simultaneously which is quite weird
2413 TreeView_SelectDropTarget(GetHwnd(), 0);
2414 }
2415 break;
2416 }
2417 }
2418 #if !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
2419 else if ( (nMsg == WM_SETFOCUS || nMsg == WM_KILLFOCUS) && isMultiple )
2420 {
2421 // the tree control greys out the selected item when it loses focus and
2422 // paints it as selected again when it regains it, but it won't do it
2423 // for the other items itself - help it
2424 wxArrayTreeItemIds selections;
2425 size_t count = GetSelections(selections);
2426 RECT rect;
2427 for ( size_t n = 0; n < count; n++ )
2428 {
2429 // TreeView_GetItemRect() will return false if item is not visible,
2430 // which may happen perfectly well
2431 if ( TreeView_GetItemRect(GetHwnd(), HITEM_PTR(selections[n]),
2432 &rect, TRUE) )
2433 {
2434 ::InvalidateRect(GetHwnd(), &rect, FALSE);
2435 }
2436 }
2437 }
2438 else if ( nMsg == WM_KEYDOWN && isMultiple )
2439 {
2440 bool bCtrl = wxIsCtrlDown(),
2441 bShift = wxIsShiftDown();
2442
2443 HTREEITEM htSel = (HTREEITEM)TreeView_GetSelection(GetHwnd());
2444 switch ( wParam )
2445 {
2446 case VK_SPACE:
2447 if ( bCtrl )
2448 {
2449 ::ToggleItemSelection(GetHwnd(), htSel);
2450 }
2451 else
2452 {
2453 UnselectAll();
2454
2455 ::SelectItem(GetHwnd(), htSel);
2456 }
2457
2458 processed = true;
2459 break;
2460
2461 case VK_UP:
2462 case VK_DOWN:
2463 if ( !bCtrl && !bShift )
2464 {
2465 // no modifiers, just clear selection and then let the default
2466 // processing to take place
2467 UnselectAll();
2468 }
2469 else if ( htSel )
2470 {
2471 (void)wxControl::MSWWindowProc(nMsg, wParam, lParam);
2472
2473 HTREEITEM htNext = (HTREEITEM)
2474 TreeView_GetNextItem
2475 (
2476 GetHwnd(),
2477 htSel,
2478 wParam == VK_UP ? TVGN_PREVIOUSVISIBLE
2479 : TVGN_NEXTVISIBLE
2480 );
2481
2482 if ( !htNext )
2483 {
2484 // at the top/bottom
2485 htNext = htSel;
2486 }
2487
2488 if ( bShift )
2489 {
2490 if ( !m_htSelStart )
2491 m_htSelStart = htSel;
2492
2493 SelectRange(GetHwnd(), HITEM(m_htSelStart), htNext);
2494 }
2495 else // bCtrl
2496 {
2497 // without changing selection
2498 ::SetFocus(GetHwnd(), htNext);
2499 }
2500
2501 processed = true;
2502 }
2503 break;
2504
2505 case VK_HOME:
2506 case VK_END:
2507 case VK_PRIOR:
2508 case VK_NEXT:
2509 // TODO: handle Shift/Ctrl with these keys
2510 if ( !bCtrl && !bShift )
2511 {
2512 UnselectAll();
2513
2514 m_htSelStart.Unset();
2515 }
2516 }
2517 }
2518 #endif // !wxUSE_CHECKBOXES_IN_MULTI_SEL_TREE
2519 else if ( nMsg == WM_COMMAND )
2520 {
2521 // if we receive a EN_KILLFOCUS command from the in-place edit control
2522 // used for label editing, make sure to end editing
2523 WORD id, cmd;
2524 WXHWND hwnd;
2525 UnpackCommand(wParam, lParam, &id, &hwnd, &cmd);
2526
2527 if ( cmd == EN_KILLFOCUS )
2528 {
2529 if ( m_textCtrl && m_textCtrl->GetHandle() == hwnd )
2530 {
2531 DoEndEditLabel();
2532
2533 processed = true;
2534 }
2535 }
2536 }
2537
2538 if ( !processed )
2539 rc = wxControl::MSWWindowProc(nMsg, wParam, lParam);
2540
2541 return rc;
2542 }
2543
2544 WXLRESULT
2545 wxTreeCtrl::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
2546 {
2547 // default WM_RBUTTONDOWN handler enters modal loop inside DefWindowProc()
2548 // waiting for WM_RBUTTONUP and then sends the resulting WM_CONTEXTMENU to
2549 // the parent window, not us, which completely breaks everything so simply
2550 // don't let it see this message at all
2551 if ( nMsg == WM_RBUTTONDOWN )
2552 return 0;
2553
2554 // but because of the above we don't get NM_RCLICK which is normally
2555 // generated by tree window proc when the modal loop mentioned above ends
2556 // because the mouse is released -- synthesize it ourselves instead
2557 if ( nMsg == WM_RBUTTONUP )
2558 {
2559 NMHDR hdr;
2560 hdr.hwndFrom = GetHwnd();
2561 hdr.idFrom = GetId();
2562 hdr.code = NM_RCLICK;
2563
2564 WXLPARAM rc;
2565 MSWOnNotify(GetId(), (LPARAM)&hdr, &rc);
2566
2567 // continue as usual
2568 }
2569
2570 if ( nMsg == WM_CHAR )
2571 {
2572 // also don't let the control process Space and Return keys because it
2573 // doesn't do anything useful with them anyhow but always beeps
2574 // annoyingly when it receives them and there is no way to turn it off
2575 // simply if you just process TREEITEM_ACTIVATED event to which Space
2576 // and Enter presses are mapped in your code
2577 if ( wParam == VK_SPACE || wParam == VK_RETURN )
2578 return 0;
2579 }
2580
2581 return wxControl::MSWDefWindowProc(nMsg, wParam, lParam);
2582 }
2583
2584 // process WM_NOTIFY Windows message
2585 bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
2586 {
2587 wxTreeEvent event(wxEVT_NULL, m_windowId);
2588 wxEventType eventType = wxEVT_NULL;
2589 NMHDR *hdr = (NMHDR *)lParam;
2590
2591 switch ( hdr->code )
2592 {
2593 case TVN_BEGINDRAG:
2594 eventType = wxEVT_COMMAND_TREE_BEGIN_DRAG;
2595 // fall through
2596
2597 case TVN_BEGINRDRAG:
2598 {
2599 if ( eventType == wxEVT_NULL )
2600 eventType = wxEVT_COMMAND_TREE_BEGIN_RDRAG;
2601 //else: left drag, already set above
2602
2603 NM_TREEVIEW *tv = (NM_TREEVIEW *)lParam;
2604
2605 event.m_item = tv->itemNew.hItem;
2606 event.m_pointDrag = wxPoint(tv->ptDrag.x, tv->ptDrag.y);
2607
2608 // don't allow dragging by default: the user code must
2609 // explicitly say that it wants to allow it to avoid breaking
2610 // the old apps
2611 event.Veto();
2612 }
2613 break;
2614
2615 case TVN_BEGINLABELEDIT:
2616 {
2617 eventType = wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT;
2618 TV_DISPINFO *info = (TV_DISPINFO *)lParam;
2619
2620 // although the user event handler may still veto it, it is
2621 // important to set it now so that calls to SetItemText() from
2622 // the event handler would change the text controls contents
2623 m_idEdited =
2624 event.m_item = info->item.hItem;
2625 event.m_label = info->item.pszText;
2626 event.m_editCancelled = false;
2627 }
2628 break;
2629
2630 case TVN_DELETEITEM:
2631 {
2632 eventType = wxEVT_COMMAND_TREE_DELETE_ITEM;
2633 NM_TREEVIEW *tv = (NM_TREEVIEW *)lParam;
2634
2635 event.m_item = tv->itemOld.hItem;
2636
2637 if ( m_hasAnyAttr )
2638 {
2639 wxMapTreeAttr::iterator it = m_attrs.find(tv->itemOld.hItem);
2640 if ( it != m_attrs.end() )
2641 {
2642 delete it->second;
2643 m_attrs.erase(it);
2644 }
2645 }
2646 }
2647 break;
2648
2649 case TVN_ENDLABELEDIT:
2650 {
2651 eventType = wxEVT_COMMAND_TREE_END_LABEL_EDIT;
2652 TV_DISPINFO *info = (TV_DISPINFO *)lParam;
2653
2654 event.m_item = info->item.hItem;
2655 event.m_label = info->item.pszText;
2656 event.m_editCancelled = info->item.pszText == NULL;
2657 break;
2658 }
2659
2660 #ifndef __WXWINCE__
2661 // These *must* not be removed or TVN_GETINFOTIP will
2662 // not be processed each time the mouse is moved
2663 // and the tooltip will only ever update once.
2664 case TTN_NEEDTEXTA:
2665 case TTN_NEEDTEXTW:
2666 {
2667 *result = 0;
2668
2669 break;
2670 }
2671
2672 #ifdef TVN_GETINFOTIP
2673 case TVN_GETINFOTIP:
2674 {
2675 eventType = wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP;
2676 NMTVGETINFOTIP *info = (NMTVGETINFOTIP*)lParam;
2677
2678 // Which item are we trying to get a tooltip for?
2679 event.m_item = info->hItem;
2680
2681 break;
2682 }
2683 #endif
2684 #endif
2685
2686 case TVN_GETDISPINFO:
2687 eventType = wxEVT_COMMAND_TREE_GET_INFO;
2688 // fall through
2689
2690 case TVN_SETDISPINFO:
2691 {
2692 if ( eventType == wxEVT_NULL )
2693 eventType = wxEVT_COMMAND_TREE_SET_INFO;
2694 //else: get, already set above
2695
2696 TV_DISPINFO *info = (TV_DISPINFO *)lParam;
2697
2698 event.m_item = info->item.hItem;
2699 break;
2700 }
2701
2702 case TVN_ITEMEXPANDING:
2703 case TVN_ITEMEXPANDED:
2704 {
2705 NM_TREEVIEW* tv = (NM_TREEVIEW*)lParam;
2706
2707 int what;
2708 switch ( tv->action )
2709 {
2710 default:
2711 wxLogDebug(wxT("unexpected code %d in TVN_ITEMEXPAND message"), tv->action);
2712 // fall through
2713
2714 case TVE_EXPAND:
2715 what = IDX_EXPAND;
2716 break;
2717
2718 case TVE_COLLAPSE:
2719 what = IDX_COLLAPSE;
2720 break;
2721 }
2722
2723 int how = hdr->code == TVN_ITEMEXPANDING ? IDX_DOING
2724 : IDX_DONE;
2725
2726 eventType = gs_expandEvents[what][how];
2727
2728 event.m_item = tv->itemNew.hItem;
2729 }
2730 break;
2731
2732 case TVN_KEYDOWN:
2733 {
2734 eventType = wxEVT_COMMAND_TREE_KEY_DOWN;
2735 TV_KEYDOWN *info = (TV_KEYDOWN *)lParam;
2736
2737 // fabricate the lParam and wParam parameters sufficiently
2738 // similar to the ones from a "real" WM_KEYDOWN so that
2739 // CreateKeyEvent() works correctly
2740 WXLPARAM lParam =
2741 (::GetKeyState(VK_MENU) < 0 ? KF_ALTDOWN : 0) << 16;
2742
2743 WXWPARAM wParam = info->wVKey;
2744
2745 int keyCode = wxCharCodeMSWToWX(info->wVKey);
2746 if ( !keyCode )
2747 {
2748 // wxCharCodeMSWToWX() returns 0 to indicate that this is a
2749 // simple ASCII key
2750 keyCode = wParam;
2751 }
2752
2753 event.m_evtKey = CreateKeyEvent(wxEVT_KEY_DOWN,
2754 keyCode,
2755 lParam,
2756 wParam);
2757
2758 // a separate event for Space/Return
2759 if ( !wxIsCtrlDown() && !wxIsShiftDown() &&
2760 ((info->wVKey == VK_SPACE) || (info->wVKey == VK_RETURN)) )
2761 {
2762 wxTreeEvent event2(wxEVT_COMMAND_TREE_ITEM_ACTIVATED,
2763 m_windowId);
2764 event2.SetEventObject(this);
2765 if ( !(GetWindowStyle() & wxTR_MULTIPLE) )
2766 {
2767 event2.m_item = GetSelection();
2768 }
2769 //else: don't know how to get it
2770
2771 (void)GetEventHandler()->ProcessEvent(event2);
2772 }
2773 }
2774 break;
2775
2776 // NB: MSLU is broken and sends TVN_SELCHANGEDA instead of
2777 // TVN_SELCHANGEDW in Unicode mode under Win98. Therefore
2778 // we have to handle both messages:
2779 case TVN_SELCHANGEDA:
2780 case TVN_SELCHANGEDW:
2781 eventType = wxEVT_COMMAND_TREE_SEL_CHANGED;
2782 // fall through
2783
2784 case TVN_SELCHANGINGA:
2785 case TVN_SELCHANGINGW:
2786 {
2787 if ( eventType == wxEVT_NULL )
2788 eventType = wxEVT_COMMAND_TREE_SEL_CHANGING;
2789 //else: already set above
2790
2791 if (hdr->code == TVN_SELCHANGINGW ||
2792 hdr->code == TVN_SELCHANGEDW)
2793 {
2794 NM_TREEVIEWW* tv = (NM_TREEVIEWW *)lParam;
2795 event.m_item = tv->itemNew.hItem;
2796 event.m_itemOld = tv->itemOld.hItem;
2797 }
2798 else
2799 {
2800 NM_TREEVIEWA* tv = (NM_TREEVIEWA *)lParam;
2801 event.m_item = tv->itemNew.hItem;
2802 event.m_itemOld = tv->itemOld.hItem;
2803 }
2804 }
2805 break;
2806
2807 // instead of explicitly checking for _WIN32_IE, check if the
2808 // required symbols are available in the headers
2809 #if defined(CDDS_PREPAINT) && !wxUSE_COMCTL32_SAFELY
2810 case NM_CUSTOMDRAW:
2811 {
2812 LPNMTVCUSTOMDRAW lptvcd = (LPNMTVCUSTOMDRAW)lParam;
2813 NMCUSTOMDRAW& nmcd = lptvcd->nmcd;
2814 switch ( nmcd.dwDrawStage )
2815 {
2816 case CDDS_PREPAINT:
2817 // if we've got any items with non standard attributes,
2818 // notify us before painting each item
2819 *result = m_hasAnyAttr ? CDRF_NOTIFYITEMDRAW
2820 : CDRF_DODEFAULT;
2821 break;
2822
2823 case CDDS_ITEMPREPAINT:
2824 {
2825 wxMapTreeAttr::iterator
2826 it = m_attrs.find((void *)nmcd.dwItemSpec);
2827
2828 if ( it == m_attrs.end() )
2829 {
2830 // nothing to do for this item
2831 *result = CDRF_DODEFAULT;
2832 break;
2833 }
2834
2835 wxTreeItemAttr * const attr = it->second;
2836
2837 // selection colours should override ours,
2838 // otherwise it is too confusing ot the user
2839 if ( !(nmcd.uItemState & CDIS_SELECTED) )
2840 {
2841 wxColour colBack;
2842 if ( attr->HasBackgroundColour() )
2843 {
2844 colBack = attr->GetBackgroundColour();
2845 lptvcd->clrTextBk = wxColourToRGB(colBack);
2846 }
2847 }
2848
2849 // but we still want to keep the special foreground
2850 // colour when we don't have focus (we can't keep
2851 // it when we do, it would usually be unreadable on
2852 // the almost inverted bg colour...)
2853 if ( !(nmcd.uItemState & CDIS_SELECTED) ||
2854 FindFocus() != this )
2855 {
2856 wxColour colText;
2857 if ( attr->HasTextColour() )
2858 {
2859 colText = attr->GetTextColour();
2860 lptvcd->clrText = wxColourToRGB(colText);
2861 }
2862 }
2863
2864 if ( attr->HasFont() )
2865 {
2866 HFONT hFont = GetHfontOf(attr->GetFont());
2867
2868 ::SelectObject(nmcd.hdc, hFont);
2869
2870 *result = CDRF_NEWFONT;
2871 }
2872 else // no specific font
2873 {
2874 *result = CDRF_DODEFAULT;
2875 }
2876 }
2877 break;
2878
2879 default:
2880 *result = CDRF_DODEFAULT;
2881 }
2882 }
2883
2884 // we always process it
2885 return true;
2886 #endif // have owner drawn support in headers
2887
2888 case NM_CLICK:
2889 {
2890 DWORD pos = GetMessagePos();
2891 POINT point;
2892 point.x = LOWORD(pos);
2893 point.y = HIWORD(pos);
2894 ::MapWindowPoints(HWND_DESKTOP, GetHwnd(), &point, 1);
2895 int flags = 0;
2896 wxTreeItemId item = HitTest(wxPoint(point.x, point.y), flags);
2897 if (flags & wxTREE_HITTEST_ONITEMSTATEICON)
2898 {
2899 event.m_item = item;
2900 eventType = wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK;
2901 }
2902 break;
2903 }
2904
2905 case NM_DBLCLK:
2906 case NM_RCLICK:
2907 {
2908 TV_HITTESTINFO tvhti;
2909 ::GetCursorPos(&tvhti.pt);
2910 ::ScreenToClient(GetHwnd(), &tvhti.pt);
2911 if ( TreeView_HitTest(GetHwnd(), &tvhti) )
2912 {
2913 if ( tvhti.flags & TVHT_ONITEM )
2914 {
2915 event.m_item = tvhti.hItem;
2916 eventType = (int)hdr->code == NM_DBLCLK
2917 ? wxEVT_COMMAND_TREE_ITEM_ACTIVATED
2918 : wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK;
2919
2920 event.m_pointDrag.x = tvhti.pt.x;
2921 event.m_pointDrag.y = tvhti.pt.y;
2922 }
2923
2924 break;
2925 }
2926 }
2927 // fall through
2928
2929 default:
2930 return wxControl::MSWOnNotify(idCtrl, lParam, result);
2931 }
2932
2933 event.SetEventObject(this);
2934 event.SetEventType(eventType);
2935
2936 bool processed = GetEventHandler()->ProcessEvent(event);
2937
2938 // post processing
2939 switch ( hdr->code )
2940 {
2941 case NM_DBLCLK:
2942 // we translate NM_DBLCLK into ACTIVATED event, so don't interpret
2943 // the return code of this event handler as the return value for
2944 // NM_DBLCLK - otherwise, double clicking the item to toggle its
2945 // expanded status would never work
2946 *result = false;
2947 break;
2948
2949 case TVN_BEGINDRAG:
2950 case TVN_BEGINRDRAG:
2951 if ( event.IsAllowed() )
2952 {
2953 // normally this is impossible because the m_dragImage is
2954 // deleted once the drag operation is over
2955 wxASSERT_MSG( !m_dragImage, _T("starting to drag once again?") );
2956
2957 m_dragImage = new wxDragImage(*this, event.m_item);
2958 m_dragImage->BeginDrag(wxPoint(0,0), this);
2959 m_dragImage->Show();
2960 }
2961 break;
2962
2963 case TVN_DELETEITEM:
2964 {
2965 // NB: we might process this message using wxWidgets event
2966 // tables, but due to overhead of wxWin event system we
2967 // prefer to do it here ourself (otherwise deleting a tree
2968 // with many items is just too slow)
2969 NM_TREEVIEW* tv = (NM_TREEVIEW *)lParam;
2970
2971 wxTreeItemId item = event.m_item;
2972 if ( HasIndirectData(item) )
2973 {
2974 wxTreeItemIndirectData *data = (wxTreeItemIndirectData *)
2975 tv->itemOld.lParam;
2976 delete data; // can't be NULL here
2977 }
2978 else
2979 {
2980 wxTreeItemData *data = (wxTreeItemData *)tv->itemOld.lParam;
2981 delete data; // may be NULL, ok
2982 }
2983
2984 processed = true; // Make sure we don't get called twice
2985 }
2986 break;
2987
2988 case TVN_BEGINLABELEDIT:
2989 // return true to cancel label editing
2990 *result = !event.IsAllowed();
2991
2992 // set ES_WANTRETURN ( like we do in BeginLabelEdit )
2993 if ( event.IsAllowed() )
2994 {
2995 HWND hText = TreeView_GetEditControl(GetHwnd());
2996 if(hText != NULL)
2997 {
2998 // MBN: if m_textCtrl already has an HWND, it is a stale
2999 // pointer from a previous edit (because the user
3000 // didn't modify the label before dismissing the control,
3001 // and TVN_ENDLABELEDIT was not sent), so delete it
3002 if(m_textCtrl && m_textCtrl->GetHWND() != 0)
3003 DeleteTextCtrl();
3004 if(!m_textCtrl)
3005 m_textCtrl = new wxTextCtrl();
3006 m_textCtrl->SetParent(this);
3007 m_textCtrl->SetHWND((WXHWND)hText);
3008 m_textCtrl->SubclassWin((WXHWND)hText);
3009
3010 // set wxTE_PROCESS_ENTER style for the text control to
3011 // force it to process the Enter presses itself, otherwise
3012 // they could be stolen from it by the dialog
3013 // navigation code
3014 m_textCtrl->SetWindowStyle(m_textCtrl->GetWindowStyle()
3015 | wxTE_PROCESS_ENTER);
3016 }
3017 }
3018 else // we had set m_idEdited before
3019 {
3020 m_idEdited.Unset();
3021 }
3022 break;
3023
3024 case TVN_ENDLABELEDIT:
3025 // return true to set the label to the new string: note that we
3026 // also must pretend that we did process the message or it is going
3027 // to be passed to DefWindowProc() which will happily return false
3028 // cancelling the label change
3029 *result = event.IsAllowed();
3030 processed = true;
3031
3032 // ensure that we don't have the text ctrl which is going to be
3033 // deleted any more
3034 DeleteTextCtrl();
3035 break;
3036
3037 #ifndef __WXWINCE__
3038 #ifdef TVN_GETINFOTIP
3039 case TVN_GETINFOTIP:
3040 {
3041 // If the user permitted a tooltip change, change it
3042 if (event.IsAllowed())
3043 {
3044 SetToolTip(event.m_label);
3045 }
3046 }
3047 break;
3048 #endif
3049 #endif
3050
3051 case TVN_SELCHANGING:
3052 case TVN_ITEMEXPANDING:
3053 // return true to prevent the action from happening
3054 *result = !event.IsAllowed();
3055 break;
3056
3057 case TVN_ITEMEXPANDED:
3058 // the item is not refreshed properly after expansion when it has
3059 // an image depending on the expanded/collapsed state - bug in
3060 // comctl32.dll or our code?
3061 {
3062 NM_TREEVIEW* tv = (NM_TREEVIEW *)lParam;
3063 wxTreeItemId id(tv->itemNew.hItem);
3064
3065 int image = GetItemImage(id, wxTreeItemIcon_Expanded);
3066 if ( image != -1 )
3067 {
3068 RefreshItem(id);
3069 }
3070 }
3071 break;
3072
3073 case TVN_GETDISPINFO:
3074 // NB: so far the user can't set the image himself anyhow, so do it
3075 // anyway - but this may change later
3076 //if ( /* !processed && */ 1 )
3077 {
3078 wxTreeItemId item = event.m_item;
3079 TV_DISPINFO *info = (TV_DISPINFO *)lParam;
3080 if ( info->item.mask & TVIF_IMAGE )
3081 {
3082 info->item.iImage =
3083 DoGetItemImageFromData
3084 (
3085 item,
3086 IsExpanded(item) ? wxTreeItemIcon_Expanded
3087 : wxTreeItemIcon_Normal
3088 );
3089 }
3090 if ( info->item.mask & TVIF_SELECTEDIMAGE )
3091 {
3092 info->item.iSelectedImage =
3093 DoGetItemImageFromData
3094 (
3095 item,
3096 IsExpanded(item) ? wxTreeItemIcon_SelectedExpanded
3097 : wxTreeItemIcon_Selected
3098 );
3099 }
3100 }
3101 break;
3102
3103 //default:
3104 // for the other messages the return value is ignored and there is
3105 // nothing special to do
3106 }
3107 return processed;
3108 }
3109
3110 // ----------------------------------------------------------------------------
3111 // State control.
3112 // ----------------------------------------------------------------------------
3113
3114 // why do they define INDEXTOSTATEIMAGEMASK but not the inverse?
3115 #define STATEIMAGEMASKTOINDEX(state) (((state) & TVIS_STATEIMAGEMASK) >> 12)
3116
3117 void wxTreeCtrl::SetState(const wxTreeItemId& node, int state)
3118 {
3119 TV_ITEM tvi;
3120 tvi.hItem = (HTREEITEM)node.m_pItem;
3121 tvi.mask = TVIF_STATE;
3122 tvi.stateMask = TVIS_STATEIMAGEMASK;
3123
3124 // Select the specified state, or -1 == cycle to the next one.
3125 if ( state == -1 )
3126 {
3127 TreeView_GetItem(GetHwnd(), &tvi);
3128
3129 state = STATEIMAGEMASKTOINDEX(tvi.state) + 1;
3130 if ( state == m_imageListState->GetImageCount() )
3131 state = 1;
3132 }
3133
3134 wxCHECK_RET( state < m_imageListState->GetImageCount(),
3135 _T("wxTreeCtrl::SetState(): item index out of bounds") );
3136
3137 tvi.state = INDEXTOSTATEIMAGEMASK(state);
3138
3139 TreeView_SetItem(GetHwnd(), &tvi);
3140 }
3141
3142 int wxTreeCtrl::GetState(const wxTreeItemId& node)
3143 {
3144 TV_ITEM tvi;
3145 tvi.hItem = (HTREEITEM)node.m_pItem;
3146 tvi.mask = TVIF_STATE;
3147 tvi.stateMask = TVIS_STATEIMAGEMASK;
3148 TreeView_GetItem(GetHwnd(), &tvi);
3149
3150 return STATEIMAGEMASKTOINDEX(tvi.state);
3151 }
3152
3153 #if WXWIN_COMPATIBILITY_2_2
3154
3155 wxTreeItemId wxTreeCtrl::GetParent(const wxTreeItemId& item) const
3156 {
3157 return GetItemParent( item );
3158 }
3159
3160 #endif // WXWIN_COMPATIBILITY_2_2
3161
3162 #endif // wxUSE_TREECTRL
3163