]> git.saurik.com Git - wxWidgets.git/blob - src/generic/treectlg.cpp
Fix for #15224: wxRichTextTable: Setting a cell's text colour affects subsequent...
[wxWidgets.git] / src / generic / treectlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/treectlg.cpp
3 // Purpose: generic tree control implementation
4 // Author: Robert Roebling
5 // Created: 01/02/97
6 // Modified: 22/10/98 - almost total rewrite, simpler interface (VZ)
7 // Copyright: (c) 1998 Robert Roebling and Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // =============================================================================
12 // declarations
13 // =============================================================================
14
15 // -----------------------------------------------------------------------------
16 // headers
17 // -----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_TREECTRL
27
28 #include "wx/treectrl.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/dcclient.h"
32 #include "wx/timer.h"
33 #include "wx/settings.h"
34 #include "wx/listbox.h"
35 #include "wx/textctrl.h"
36 #endif
37
38 #include "wx/generic/treectlg.h"
39 #include "wx/imaglist.h"
40
41 #include "wx/renderer.h"
42
43 #ifdef __WXMAC__
44 #include "wx/osx/private.h"
45 #endif
46
47 // -----------------------------------------------------------------------------
48 // array types
49 // -----------------------------------------------------------------------------
50
51 class WXDLLIMPEXP_FWD_CORE wxGenericTreeItem;
52
53 WX_DEFINE_ARRAY_PTR(wxGenericTreeItem *, wxArrayGenericTreeItems);
54
55 // ----------------------------------------------------------------------------
56 // constants
57 // ----------------------------------------------------------------------------
58
59 static const int NO_IMAGE = -1;
60
61 static const int PIXELS_PER_UNIT = 10;
62
63 // the margin between the item state image and the item normal image
64 static const int MARGIN_BETWEEN_STATE_AND_IMAGE = 2;
65
66 // the margin between the item image and the item text
67 static const int MARGIN_BETWEEN_IMAGE_AND_TEXT = 4;
68
69 // -----------------------------------------------------------------------------
70 // private classes
71 // -----------------------------------------------------------------------------
72
73 // timer used for enabling in-place edit
74 class WXDLLEXPORT wxTreeRenameTimer: public wxTimer
75 {
76 public:
77 // start editing the current item after half a second (if the mouse hasn't
78 // been clicked/moved)
79 enum { DELAY = 500 };
80
81 wxTreeRenameTimer( wxGenericTreeCtrl *owner );
82
83 virtual void Notify();
84
85 private:
86 wxGenericTreeCtrl *m_owner;
87
88 wxDECLARE_NO_COPY_CLASS(wxTreeRenameTimer);
89 };
90
91 // control used for in-place edit
92 class WXDLLEXPORT wxTreeTextCtrl: public wxTextCtrl
93 {
94 public:
95 wxTreeTextCtrl(wxGenericTreeCtrl *owner, wxGenericTreeItem *item);
96
97 void EndEdit( bool discardChanges );
98
99 const wxGenericTreeItem* item() const { return m_itemEdited; }
100
101 protected:
102 void OnChar( wxKeyEvent &event );
103 void OnKeyUp( wxKeyEvent &event );
104 void OnKillFocus( wxFocusEvent &event );
105
106 bool AcceptChanges();
107 void Finish( bool setfocus );
108
109 private:
110 wxGenericTreeCtrl *m_owner;
111 wxGenericTreeItem *m_itemEdited;
112 wxString m_startValue;
113 bool m_aboutToFinish;
114
115 DECLARE_EVENT_TABLE()
116 wxDECLARE_NO_COPY_CLASS(wxTreeTextCtrl);
117 };
118
119 // timer used to clear wxGenericTreeCtrl::m_findPrefix if no key was pressed
120 // for a sufficiently long time
121 class WXDLLEXPORT wxTreeFindTimer : public wxTimer
122 {
123 public:
124 // reset the current prefix after half a second of inactivity
125 enum { DELAY = 500 };
126
127 wxTreeFindTimer( wxGenericTreeCtrl *owner ) { m_owner = owner; }
128
129 virtual void Notify() { m_owner->ResetFindState(); }
130
131 private:
132 wxGenericTreeCtrl *m_owner;
133
134 wxDECLARE_NO_COPY_CLASS(wxTreeFindTimer);
135 };
136
137 // a tree item
138 class WXDLLEXPORT wxGenericTreeItem
139 {
140 public:
141 // ctors & dtor
142 wxGenericTreeItem()
143 {
144 m_data = NULL;
145 m_widthText =
146 m_heightText = -1;
147 }
148
149 wxGenericTreeItem( wxGenericTreeItem *parent,
150 const wxString& text,
151 int image,
152 int selImage,
153 wxTreeItemData *data );
154
155 ~wxGenericTreeItem();
156
157 // trivial accessors
158 wxArrayGenericTreeItems& GetChildren() { return m_children; }
159
160 const wxString& GetText() const { return m_text; }
161 int GetImage(wxTreeItemIcon which = wxTreeItemIcon_Normal) const
162 { return m_images[which]; }
163 wxTreeItemData *GetData() const { return m_data; }
164 int GetState() const { return m_state; }
165
166 // returns the current image for the item (depending on its
167 // selected/expanded/whatever state)
168 int GetCurrentImage() const;
169
170 void SetText(const wxString& text)
171 {
172 m_text = text;
173
174 ResetTextSize();
175 }
176
177 void SetImage(int image, wxTreeItemIcon which)
178 {
179 m_images[which] = image;
180 m_width = 0;
181 }
182
183 void SetData(wxTreeItemData *data) { m_data = data; }
184 void SetState(int state) { m_state = state; m_width = 0; }
185
186 void SetHasPlus(bool has = true) { m_hasPlus = has; }
187
188 void SetBold(bool bold)
189 {
190 m_isBold = bold;
191
192 ResetTextSize();
193 }
194
195 int GetX() const { return m_x; }
196 int GetY() const { return m_y; }
197
198 void SetX(int x) { m_x = x; }
199 void SetY(int y) { m_y = y; }
200
201 int GetHeight() const { return m_height; }
202 int GetWidth() const { return m_width; }
203
204 int GetTextHeight() const
205 {
206 wxASSERT_MSG( m_heightText != -1, "must call CalculateSize() first" );
207
208 return m_heightText;
209 }
210
211 int GetTextWidth() const
212 {
213 wxASSERT_MSG( m_widthText != -1, "must call CalculateSize() first" );
214
215 return m_widthText;
216 }
217
218 wxGenericTreeItem *GetParent() const { return m_parent; }
219
220 // sets the items font for the specified DC if it uses any special font or
221 // simply returns false otherwise
222 bool SetFont(wxGenericTreeCtrl *control, wxDC& dc) const
223 {
224 wxFont font;
225
226 wxTreeItemAttr * const attr = GetAttributes();
227 if ( attr && attr->HasFont() )
228 font = attr->GetFont();
229 else if ( IsBold() )
230 font = control->m_boldFont;
231 else
232 return false;
233
234 dc.SetFont(font);
235 return true;
236 }
237
238 // operations
239
240 // deletes all children notifying the treectrl about it
241 void DeleteChildren(wxGenericTreeCtrl *tree);
242
243 // get count of all children (and grand children if 'recursively')
244 size_t GetChildrenCount(bool recursively = true) const;
245
246 void Insert(wxGenericTreeItem *child, size_t index)
247 { m_children.Insert(child, index); }
248
249 // calculate and cache the item size using either the provided DC (which is
250 // supposed to have wxGenericTreeCtrl::m_normalFont selected into it!) or a
251 // wxClientDC on the control window
252 void CalculateSize(wxGenericTreeCtrl *control, wxDC& dc)
253 { DoCalculateSize(control, dc, true /* dc uses normal font */); }
254 void CalculateSize(wxGenericTreeCtrl *control);
255
256 void GetSize( int &x, int &y, const wxGenericTreeCtrl* );
257
258 void ResetSize() { m_width = 0; }
259 void ResetTextSize() { m_width = 0; m_widthText = -1; }
260 void RecursiveResetSize();
261 void RecursiveResetTextSize();
262
263 // return the item at given position (or NULL if no item), onButton is
264 // true if the point belongs to the item's button, otherwise it lies
265 // on the item's label
266 wxGenericTreeItem *HitTest( const wxPoint& point,
267 const wxGenericTreeCtrl *,
268 int &flags,
269 int level );
270
271 void Expand() { m_isCollapsed = false; }
272 void Collapse() { m_isCollapsed = true; }
273
274 void SetHilight( bool set = true ) { m_hasHilight = set; }
275
276 // status inquiries
277 bool HasChildren() const { return !m_children.IsEmpty(); }
278 bool IsSelected() const { return m_hasHilight != 0; }
279 bool IsExpanded() const { return !m_isCollapsed; }
280 bool HasPlus() const { return m_hasPlus || HasChildren(); }
281 bool IsBold() const { return m_isBold != 0; }
282
283 // attributes
284 // get them - may be NULL
285 wxTreeItemAttr *GetAttributes() const { return m_attr; }
286 // get them ensuring that the pointer is not NULL
287 wxTreeItemAttr& Attr()
288 {
289 if ( !m_attr )
290 {
291 m_attr = new wxTreeItemAttr;
292 m_ownsAttr = true;
293 }
294 return *m_attr;
295 }
296 // set them
297 void SetAttributes(wxTreeItemAttr *attr)
298 {
299 if ( m_ownsAttr ) delete m_attr;
300 m_attr = attr;
301 m_ownsAttr = false;
302 m_width = 0;
303 m_widthText = -1;
304 }
305 // set them and delete when done
306 void AssignAttributes(wxTreeItemAttr *attr)
307 {
308 SetAttributes(attr);
309 m_ownsAttr = true;
310 m_width = 0;
311 m_widthText = -1;
312 }
313
314 private:
315 // calculate the size of this item, i.e. set m_width, m_height and
316 // m_widthText and m_heightText properly
317 //
318 // if dcUsesNormalFont is true, the current dc font must be the normal tree
319 // control font
320 void DoCalculateSize(wxGenericTreeCtrl *control,
321 wxDC& dc,
322 bool dcUsesNormalFont);
323
324 // since there can be very many of these, we save size by chosing
325 // the smallest representation for the elements and by ordering
326 // the members to avoid padding.
327 wxString m_text; // label to be rendered for item
328 int m_widthText;
329 int m_heightText;
330
331 wxTreeItemData *m_data; // user-provided data
332
333 int m_state; // item state
334
335 wxArrayGenericTreeItems m_children; // list of children
336 wxGenericTreeItem *m_parent; // parent of this item
337
338 wxTreeItemAttr *m_attr; // attributes???
339
340 // tree ctrl images for the normal, selected, expanded and
341 // expanded+selected states
342 int m_images[wxTreeItemIcon_Max];
343
344 wxCoord m_x; // (virtual) offset from top
345 wxCoord m_y; // (virtual) offset from left
346 int m_width; // width of this item
347 int m_height; // height of this item
348
349 // use bitfields to save size
350 unsigned int m_isCollapsed :1;
351 unsigned int m_hasHilight :1; // same as focused
352 unsigned int m_hasPlus :1; // used for item which doesn't have
353 // children but has a [+] button
354 unsigned int m_isBold :1; // render the label in bold font
355 unsigned int m_ownsAttr :1; // delete attribute when done
356
357 wxDECLARE_NO_COPY_CLASS(wxGenericTreeItem);
358 };
359
360 // =============================================================================
361 // implementation
362 // =============================================================================
363
364 // ----------------------------------------------------------------------------
365 // private functions
366 // ----------------------------------------------------------------------------
367
368 // translate the key or mouse event flags to the type of selection we're
369 // dealing with
370 static void EventFlagsToSelType(long style,
371 bool shiftDown,
372 bool ctrlDown,
373 bool &is_multiple,
374 bool &extended_select,
375 bool &unselect_others)
376 {
377 is_multiple = (style & wxTR_MULTIPLE) != 0;
378 extended_select = shiftDown && is_multiple;
379 unselect_others = !(extended_select || (ctrlDown && is_multiple));
380 }
381
382 // check if the given item is under another one
383 static bool
384 IsDescendantOf(const wxGenericTreeItem *parent, const wxGenericTreeItem *item)
385 {
386 while ( item )
387 {
388 if ( item == parent )
389 {
390 // item is a descendant of parent
391 return true;
392 }
393
394 item = item->GetParent();
395 }
396
397 return false;
398 }
399
400 // -----------------------------------------------------------------------------
401 // wxTreeRenameTimer (internal)
402 // -----------------------------------------------------------------------------
403
404 wxTreeRenameTimer::wxTreeRenameTimer( wxGenericTreeCtrl *owner )
405 {
406 m_owner = owner;
407 }
408
409 void wxTreeRenameTimer::Notify()
410 {
411 m_owner->OnRenameTimer();
412 }
413
414 //-----------------------------------------------------------------------------
415 // wxTreeTextCtrl (internal)
416 //-----------------------------------------------------------------------------
417
418 BEGIN_EVENT_TABLE(wxTreeTextCtrl,wxTextCtrl)
419 EVT_CHAR (wxTreeTextCtrl::OnChar)
420 EVT_KEY_UP (wxTreeTextCtrl::OnKeyUp)
421 EVT_KILL_FOCUS (wxTreeTextCtrl::OnKillFocus)
422 END_EVENT_TABLE()
423
424 wxTreeTextCtrl::wxTreeTextCtrl(wxGenericTreeCtrl *owner,
425 wxGenericTreeItem *itm)
426 : m_itemEdited(itm), m_startValue(itm->GetText())
427 {
428 m_owner = owner;
429 m_aboutToFinish = false;
430
431 wxRect rect;
432 m_owner->GetBoundingRect(m_itemEdited, rect, true);
433
434 // corrects position and size for better appearance
435 #ifdef __WXMSW__
436 rect.x -= 5;
437 rect.width += 10;
438 #elif defined(__WXGTK__)
439 rect.x -= 5;
440 rect.y -= 2;
441 rect.width += 8;
442 rect.height += 4;
443 #elif defined(wxOSX_USE_CARBON) && wxOSX_USE_CARBON
444 int bestHeight = GetBestSize().y - 8;
445 if ( rect.height > bestHeight )
446 {
447 int diff = rect.height - bestHeight;
448 rect.height -= diff;
449 rect.y += diff / 2;
450 }
451 #endif // platforms
452
453 (void)Create(m_owner, wxID_ANY, m_startValue,
454 rect.GetPosition(), rect.GetSize());
455
456 SelectAll();
457 }
458
459 void wxTreeTextCtrl::EndEdit(bool discardChanges)
460 {
461 m_aboutToFinish = true;
462
463 if ( discardChanges )
464 {
465 m_owner->OnRenameCancelled(m_itemEdited);
466
467 Finish( true );
468 }
469 else
470 {
471 // Notify the owner about the changes
472 AcceptChanges();
473
474 // Even if vetoed, close the control (consistent with MSW)
475 Finish( true );
476 }
477 }
478
479 bool wxTreeTextCtrl::AcceptChanges()
480 {
481 const wxString value = GetValue();
482
483 if ( value == m_startValue )
484 {
485 // nothing changed, always accept
486 // when an item remains unchanged, the owner
487 // needs to be notified that the user decided
488 // not to change the tree item label, and that
489 // the edit has been cancelled
490
491 m_owner->OnRenameCancelled(m_itemEdited);
492 return true;
493 }
494
495 if ( !m_owner->OnRenameAccept(m_itemEdited, value) )
496 {
497 // vetoed by the user
498 return false;
499 }
500
501 // accepted, do rename the item
502 m_owner->SetItemText(m_itemEdited, value);
503
504 return true;
505 }
506
507 void wxTreeTextCtrl::Finish( bool setfocus )
508 {
509 m_owner->ResetTextControl();
510
511 wxPendingDelete.Append(this);
512
513 if (setfocus)
514 m_owner->SetFocus();
515 }
516
517 void wxTreeTextCtrl::OnChar( wxKeyEvent &event )
518 {
519 switch ( event.m_keyCode )
520 {
521 case WXK_RETURN:
522 EndEdit( false );
523 break;
524
525 case WXK_ESCAPE:
526 EndEdit( true );
527 break;
528
529 default:
530 event.Skip();
531 }
532 }
533
534 void wxTreeTextCtrl::OnKeyUp( wxKeyEvent &event )
535 {
536 if ( !m_aboutToFinish )
537 {
538 // auto-grow the textctrl:
539 wxSize parentSize = m_owner->GetSize();
540 wxPoint myPos = GetPosition();
541 wxSize mySize = GetSize();
542 int sx, sy;
543 GetTextExtent(GetValue() + wxT("M"), &sx, &sy);
544 if (myPos.x + sx > parentSize.x)
545 sx = parentSize.x - myPos.x;
546 if (mySize.x > sx)
547 sx = mySize.x;
548 SetSize(sx, wxDefaultCoord);
549 }
550
551 event.Skip();
552 }
553
554 void wxTreeTextCtrl::OnKillFocus( wxFocusEvent &event )
555 {
556 if ( !m_aboutToFinish )
557 {
558 if ( !AcceptChanges() )
559 m_owner->OnRenameCancelled( m_itemEdited );
560
561 Finish( false );
562 }
563
564 // We should let the native text control handle focus, too.
565 event.Skip();
566 }
567
568 // -----------------------------------------------------------------------------
569 // wxGenericTreeItem
570 // -----------------------------------------------------------------------------
571
572 wxGenericTreeItem::wxGenericTreeItem(wxGenericTreeItem *parent,
573 const wxString& text,
574 int image, int selImage,
575 wxTreeItemData *data)
576 : m_text(text)
577 {
578 m_images[wxTreeItemIcon_Normal] = image;
579 m_images[wxTreeItemIcon_Selected] = selImage;
580 m_images[wxTreeItemIcon_Expanded] = NO_IMAGE;
581 m_images[wxTreeItemIcon_SelectedExpanded] = NO_IMAGE;
582
583 m_data = data;
584 m_state = wxTREE_ITEMSTATE_NONE;
585 m_x = m_y = 0;
586
587 m_isCollapsed = true;
588 m_hasHilight = false;
589 m_hasPlus = false;
590 m_isBold = false;
591
592 m_parent = parent;
593
594 m_attr = NULL;
595 m_ownsAttr = false;
596
597 // We don't know the height here yet.
598 m_width = 0;
599 m_height = 0;
600
601 m_widthText = -1;
602 m_heightText = -1;
603 }
604
605 wxGenericTreeItem::~wxGenericTreeItem()
606 {
607 delete m_data;
608
609 if (m_ownsAttr) delete m_attr;
610
611 wxASSERT_MSG( m_children.IsEmpty(),
612 "must call DeleteChildren() before deleting the item" );
613 }
614
615 void wxGenericTreeItem::DeleteChildren(wxGenericTreeCtrl *tree)
616 {
617 size_t count = m_children.GetCount();
618 for ( size_t n = 0; n < count; n++ )
619 {
620 wxGenericTreeItem *child = m_children[n];
621 tree->SendDeleteEvent(child);
622
623 child->DeleteChildren(tree);
624 if ( child == tree->m_select_me )
625 tree->m_select_me = NULL;
626 delete child;
627 }
628
629 m_children.Empty();
630 }
631
632 size_t wxGenericTreeItem::GetChildrenCount(bool recursively) const
633 {
634 size_t count = m_children.GetCount();
635 if ( !recursively )
636 return count;
637
638 size_t total = count;
639 for (size_t n = 0; n < count; ++n)
640 {
641 total += m_children[n]->GetChildrenCount();
642 }
643
644 return total;
645 }
646
647 void wxGenericTreeItem::GetSize( int &x, int &y,
648 const wxGenericTreeCtrl *theButton )
649 {
650 int bottomY=m_y+theButton->GetLineHeight(this);
651 if ( y < bottomY ) y = bottomY;
652 int width = m_x + m_width;
653 if ( x < width ) x = width;
654
655 if (IsExpanded())
656 {
657 size_t count = m_children.GetCount();
658 for ( size_t n = 0; n < count; ++n )
659 {
660 m_children[n]->GetSize( x, y, theButton );
661 }
662 }
663 }
664
665 wxGenericTreeItem *wxGenericTreeItem::HitTest(const wxPoint& point,
666 const wxGenericTreeCtrl *theCtrl,
667 int &flags,
668 int level)
669 {
670 // for a hidden root node, don't evaluate it, but do evaluate children
671 if ( !(level == 0 && theCtrl->HasFlag(wxTR_HIDE_ROOT)) )
672 {
673 // evaluate the item
674 int h = theCtrl->GetLineHeight(this);
675 if ((point.y > m_y) && (point.y < m_y + h))
676 {
677 int y_mid = m_y + h/2;
678 if (point.y < y_mid )
679 flags |= wxTREE_HITTEST_ONITEMUPPERPART;
680 else
681 flags |= wxTREE_HITTEST_ONITEMLOWERPART;
682
683 int xCross = m_x - theCtrl->GetSpacing();
684 #ifdef __WXMAC__
685 // according to the drawing code the triangels are drawn
686 // at -4 , -4 from the position up to +10/+10 max
687 if ((point.x > xCross-4) && (point.x < xCross+10) &&
688 (point.y > y_mid-4) && (point.y < y_mid+10) &&
689 HasPlus() && theCtrl->HasButtons() )
690 #else
691 // 5 is the size of the plus sign
692 if ((point.x > xCross-6) && (point.x < xCross+6) &&
693 (point.y > y_mid-6) && (point.y < y_mid+6) &&
694 HasPlus() && theCtrl->HasButtons() )
695 #endif
696 {
697 flags |= wxTREE_HITTEST_ONITEMBUTTON;
698 return this;
699 }
700
701 if ((point.x >= m_x) && (point.x <= m_x+m_width))
702 {
703 int image_w = -1;
704 int image_h;
705
706 // assuming every image (normal and selected) has the same size!
707 if ( (GetImage() != NO_IMAGE) && theCtrl->m_imageListNormal )
708 {
709 theCtrl->m_imageListNormal->GetSize(GetImage(),
710 image_w, image_h);
711 }
712
713 int state_w = -1;
714 int state_h;
715
716 if ( (GetState() != wxTREE_ITEMSTATE_NONE) &&
717 theCtrl->m_imageListState )
718 {
719 theCtrl->m_imageListState->GetSize(GetState(),
720 state_w, state_h);
721 }
722
723 if ((state_w != -1) && (point.x <= m_x + state_w + 1))
724 flags |= wxTREE_HITTEST_ONITEMSTATEICON;
725 else if ((image_w != -1) &&
726 (point.x <= m_x +
727 (state_w != -1 ? state_w +
728 MARGIN_BETWEEN_STATE_AND_IMAGE
729 : 0)
730 + image_w + 1))
731 flags |= wxTREE_HITTEST_ONITEMICON;
732 else
733 flags |= wxTREE_HITTEST_ONITEMLABEL;
734
735 return this;
736 }
737
738 if (point.x < m_x)
739 flags |= wxTREE_HITTEST_ONITEMINDENT;
740 if (point.x > m_x+m_width)
741 flags |= wxTREE_HITTEST_ONITEMRIGHT;
742
743 return this;
744 }
745
746 // if children are expanded, fall through to evaluate them
747 if (m_isCollapsed) return NULL;
748 }
749
750 // evaluate children
751 size_t count = m_children.GetCount();
752 for ( size_t n = 0; n < count; n++ )
753 {
754 wxGenericTreeItem *res = m_children[n]->HitTest( point,
755 theCtrl,
756 flags,
757 level + 1 );
758 if ( res != NULL )
759 return res;
760 }
761
762 return NULL;
763 }
764
765 int wxGenericTreeItem::GetCurrentImage() const
766 {
767 int image = NO_IMAGE;
768 if ( IsExpanded() )
769 {
770 if ( IsSelected() )
771 {
772 image = GetImage(wxTreeItemIcon_SelectedExpanded);
773 }
774
775 if ( image == NO_IMAGE )
776 {
777 // we usually fall back to the normal item, but try just the
778 // expanded one (and not selected) first in this case
779 image = GetImage(wxTreeItemIcon_Expanded);
780 }
781 }
782 else // not expanded
783 {
784 if ( IsSelected() )
785 image = GetImage(wxTreeItemIcon_Selected);
786 }
787
788 // maybe it doesn't have the specific image we want,
789 // try the default one instead
790 if ( image == NO_IMAGE ) image = GetImage();
791
792 return image;
793 }
794
795 void wxGenericTreeItem::CalculateSize(wxGenericTreeCtrl* control)
796 {
797 // check if we need to do anything before creating the DC
798 if ( m_width != 0 )
799 return;
800
801 wxClientDC dc(control);
802 DoCalculateSize(control, dc, false /* normal font not used */);
803 }
804
805 void
806 wxGenericTreeItem::DoCalculateSize(wxGenericTreeCtrl* control,
807 wxDC& dc,
808 bool dcUsesNormalFont)
809 {
810 if ( m_width != 0 ) // Size known, nothing to do
811 return;
812
813 if ( m_widthText == -1 )
814 {
815 bool fontChanged;
816 if ( SetFont(control, dc) )
817 {
818 fontChanged = true;
819 }
820 else // we have no special font
821 {
822 if ( !dcUsesNormalFont )
823 {
824 // but we do need to ensure that the normal font is used: notice
825 // that this doesn't count as changing the font as we don't need
826 // to restore it
827 dc.SetFont(control->m_normalFont);
828 }
829
830 fontChanged = false;
831 }
832
833 dc.GetTextExtent( GetText(), &m_widthText, &m_heightText );
834
835 // restore normal font if the DC used it previously and we changed it
836 if ( fontChanged )
837 dc.SetFont(control->m_normalFont);
838 }
839
840 int text_h = m_heightText + 2;
841
842 int image_h = 0, image_w = 0;
843 int image = GetCurrentImage();
844 if ( image != NO_IMAGE && control->m_imageListNormal )
845 {
846 control->m_imageListNormal->GetSize(image, image_w, image_h);
847 image_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
848 }
849
850 int state_h = 0, state_w = 0;
851 int state = GetState();
852 if ( state != wxTREE_ITEMSTATE_NONE && control->m_imageListState )
853 {
854 control->m_imageListState->GetSize(state, state_w, state_h);
855 if ( image_w != 0 )
856 state_w += MARGIN_BETWEEN_STATE_AND_IMAGE;
857 else
858 state_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
859 }
860
861 int img_h = wxMax(state_h, image_h);
862 m_height = wxMax(img_h, text_h);
863
864 if (m_height < 30)
865 m_height += 2; // at least 2 pixels
866 else
867 m_height += m_height / 10; // otherwise 10% extra spacing
868
869 if (m_height > control->m_lineHeight)
870 control->m_lineHeight = m_height;
871
872 m_width = state_w + image_w + m_widthText + 2;
873 }
874
875 void wxGenericTreeItem::RecursiveResetSize()
876 {
877 m_width = 0;
878
879 const size_t count = m_children.Count();
880 for (size_t i = 0; i < count; i++ )
881 m_children[i]->RecursiveResetSize();
882 }
883
884 void wxGenericTreeItem::RecursiveResetTextSize()
885 {
886 m_width = 0;
887 m_widthText = -1;
888
889 const size_t count = m_children.Count();
890 for (size_t i = 0; i < count; i++ )
891 m_children[i]->RecursiveResetTextSize();
892 }
893
894 // -----------------------------------------------------------------------------
895 // wxGenericTreeCtrl implementation
896 // -----------------------------------------------------------------------------
897
898 IMPLEMENT_DYNAMIC_CLASS(wxGenericTreeCtrl, wxControl)
899
900 BEGIN_EVENT_TABLE(wxGenericTreeCtrl, wxTreeCtrlBase)
901 EVT_PAINT (wxGenericTreeCtrl::OnPaint)
902 EVT_SIZE (wxGenericTreeCtrl::OnSize)
903 EVT_MOUSE_EVENTS (wxGenericTreeCtrl::OnMouse)
904 EVT_KEY_DOWN (wxGenericTreeCtrl::OnKeyDown)
905 EVT_CHAR (wxGenericTreeCtrl::OnChar)
906 EVT_SET_FOCUS (wxGenericTreeCtrl::OnSetFocus)
907 EVT_KILL_FOCUS (wxGenericTreeCtrl::OnKillFocus)
908 EVT_TREE_ITEM_GETTOOLTIP(wxID_ANY, wxGenericTreeCtrl::OnGetToolTip)
909 END_EVENT_TABLE()
910
911 // -----------------------------------------------------------------------------
912 // construction/destruction
913 // -----------------------------------------------------------------------------
914
915 void wxGenericTreeCtrl::Init()
916 {
917 m_current =
918 m_key_current =
919 m_anchor =
920 m_select_me = NULL;
921 m_hasFocus = false;
922 m_dirty = false;
923
924 m_lineHeight = 10;
925 m_indent = 15;
926 m_spacing = 18;
927
928 m_hilightBrush = new wxBrush
929 (
930 wxSystemSettings::GetColour
931 (
932 wxSYS_COLOUR_HIGHLIGHT
933 ),
934 wxBRUSHSTYLE_SOLID
935 );
936
937 m_hilightUnfocusedBrush = new wxBrush
938 (
939 wxSystemSettings::GetColour
940 (
941 wxSYS_COLOUR_BTNSHADOW
942 ),
943 wxBRUSHSTYLE_SOLID
944 );
945
946 m_imageListButtons = NULL;
947 m_ownsImageListButtons = false;
948
949 m_dragCount = 0;
950 m_isDragging = false;
951 m_dropTarget = m_oldSelection = NULL;
952 m_underMouse = NULL;
953 m_textCtrl = NULL;
954
955 m_renameTimer = NULL;
956
957 m_findTimer = NULL;
958 m_findBell = 0; // default is to not ring bell at all
959
960 m_dropEffectAboveItem = false;
961
962 m_dndEffect = NoEffect;
963 m_dndEffectItem = NULL;
964
965 m_lastOnSame = false;
966
967 #if defined( __WXMAC__ )
968 m_normalFont = wxFont(wxOSX_SYSTEM_FONT_VIEWS);
969 #else
970 m_normalFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
971 #endif
972 m_boldFont = m_normalFont.Bold();
973 }
974
975 bool wxGenericTreeCtrl::Create(wxWindow *parent,
976 wxWindowID id,
977 const wxPoint& pos,
978 const wxSize& size,
979 long style,
980 const wxValidator& validator,
981 const wxString& name )
982 {
983 #ifdef __WXMAC__
984 int major, minor;
985 wxGetOsVersion(&major, &minor);
986
987 if (major < 10)
988 style |= wxTR_ROW_LINES;
989
990 if (style & wxTR_HAS_BUTTONS)
991 style |= wxTR_NO_LINES;
992 #endif // __WXMAC__
993
994 #ifdef __WXGTK20__
995 if (style & wxTR_HAS_BUTTONS)
996 style |= wxTR_NO_LINES;
997 #endif
998
999 if ( !wxControl::Create( parent, id, pos, size,
1000 style|wxHSCROLL|wxVSCROLL|wxWANTS_CHARS,
1001 validator,
1002 name ) )
1003 return false;
1004
1005 // If the tree display has no buttons, but does have
1006 // connecting lines, we can use a narrower layout.
1007 // It may not be a good idea to force this...
1008 if (!HasButtons() && !HasFlag(wxTR_NO_LINES))
1009 {
1010 m_indent= 10;
1011 m_spacing = 10;
1012 }
1013
1014 wxVisualAttributes attr = GetDefaultAttributes();
1015 SetOwnForegroundColour( attr.colFg );
1016 SetOwnBackgroundColour( attr.colBg );
1017 if (!m_hasFont)
1018 SetOwnFont(attr.font);
1019
1020 // this is a misnomer: it's called "dotted pen" but uses (default) wxSOLID
1021 // style because we apparently get performance problems when using dotted
1022 // pen for drawing in some ports -- but under MSW it seems to work fine
1023 #ifdef __WXMSW__
1024 m_dottedPen = wxPen(*wxLIGHT_GREY, 0, wxPENSTYLE_DOT);
1025 #else
1026 m_dottedPen = *wxGREY_PEN;
1027 #endif
1028
1029 SetInitialSize(size);
1030
1031 return true;
1032 }
1033
1034 wxGenericTreeCtrl::~wxGenericTreeCtrl()
1035 {
1036 delete m_hilightBrush;
1037 delete m_hilightUnfocusedBrush;
1038
1039 DeleteAllItems();
1040
1041 delete m_renameTimer;
1042 delete m_findTimer;
1043
1044 if (m_ownsImageListButtons)
1045 delete m_imageListButtons;
1046 }
1047
1048 void wxGenericTreeCtrl::EnableBellOnNoMatch( bool on )
1049 {
1050 m_findBell = on;
1051 }
1052
1053 // -----------------------------------------------------------------------------
1054 // accessors
1055 // -----------------------------------------------------------------------------
1056
1057 unsigned int wxGenericTreeCtrl::GetCount() const
1058 {
1059 if ( !m_anchor )
1060 {
1061 // the tree is empty
1062 return 0;
1063 }
1064
1065 unsigned int count = m_anchor->GetChildrenCount();
1066 if ( !HasFlag(wxTR_HIDE_ROOT) )
1067 {
1068 // take the root itself into account
1069 count++;
1070 }
1071
1072 return count;
1073 }
1074
1075 void wxGenericTreeCtrl::SetIndent(unsigned int indent)
1076 {
1077 m_indent = (unsigned short) indent;
1078 m_dirty = true;
1079 }
1080
1081 size_t
1082 wxGenericTreeCtrl::GetChildrenCount(const wxTreeItemId& item,
1083 bool recursively) const
1084 {
1085 wxCHECK_MSG( item.IsOk(), 0u, wxT("invalid tree item") );
1086
1087 return ((wxGenericTreeItem*) item.m_pItem)->GetChildrenCount(recursively);
1088 }
1089
1090 void wxGenericTreeCtrl::SetWindowStyle(const long styles)
1091 {
1092 // Do not try to expand the root node if it hasn't been created yet
1093 if (m_anchor && !HasFlag(wxTR_HIDE_ROOT) && (styles & wxTR_HIDE_ROOT))
1094 {
1095 // if we will hide the root, make sure children are visible
1096 m_anchor->SetHasPlus();
1097 m_anchor->Expand();
1098 CalculatePositions();
1099 }
1100
1101 // right now, just sets the styles. Eventually, we may
1102 // want to update the inherited styles, but right now
1103 // none of the parents has updatable styles
1104 m_windowStyle = styles;
1105 m_dirty = true;
1106 }
1107
1108 // -----------------------------------------------------------------------------
1109 // functions to work with tree items
1110 // -----------------------------------------------------------------------------
1111
1112 wxString wxGenericTreeCtrl::GetItemText(const wxTreeItemId& item) const
1113 {
1114 wxCHECK_MSG( item.IsOk(), wxEmptyString, wxT("invalid tree item") );
1115
1116 return ((wxGenericTreeItem*) item.m_pItem)->GetText();
1117 }
1118
1119 int wxGenericTreeCtrl::GetItemImage(const wxTreeItemId& item,
1120 wxTreeItemIcon which) const
1121 {
1122 wxCHECK_MSG( item.IsOk(), -1, wxT("invalid tree item") );
1123
1124 return ((wxGenericTreeItem*) item.m_pItem)->GetImage(which);
1125 }
1126
1127 wxTreeItemData *wxGenericTreeCtrl::GetItemData(const wxTreeItemId& item) const
1128 {
1129 wxCHECK_MSG( item.IsOk(), NULL, wxT("invalid tree item") );
1130
1131 return ((wxGenericTreeItem*) item.m_pItem)->GetData();
1132 }
1133
1134 int wxGenericTreeCtrl::DoGetItemState(const wxTreeItemId& item) const
1135 {
1136 wxCHECK_MSG( item.IsOk(), wxTREE_ITEMSTATE_NONE, wxT("invalid tree item") );
1137
1138 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1139 return pItem->GetState();
1140 }
1141
1142 wxColour wxGenericTreeCtrl::GetItemTextColour(const wxTreeItemId& item) const
1143 {
1144 wxCHECK_MSG( item.IsOk(), wxNullColour, wxT("invalid tree item") );
1145
1146 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1147 return pItem->Attr().GetTextColour();
1148 }
1149
1150 wxColour
1151 wxGenericTreeCtrl::GetItemBackgroundColour(const wxTreeItemId& item) const
1152 {
1153 wxCHECK_MSG( item.IsOk(), wxNullColour, wxT("invalid tree item") );
1154
1155 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1156 return pItem->Attr().GetBackgroundColour();
1157 }
1158
1159 wxFont wxGenericTreeCtrl::GetItemFont(const wxTreeItemId& item) const
1160 {
1161 wxCHECK_MSG( item.IsOk(), wxNullFont, wxT("invalid tree item") );
1162
1163 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1164 return pItem->Attr().GetFont();
1165 }
1166
1167 void
1168 wxGenericTreeCtrl::SetItemText(const wxTreeItemId& item, const wxString& text)
1169 {
1170 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1171
1172 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1173 pItem->SetText(text);
1174 pItem->CalculateSize(this);
1175 RefreshLine(pItem);
1176 }
1177
1178 void wxGenericTreeCtrl::SetItemImage(const wxTreeItemId& item,
1179 int image,
1180 wxTreeItemIcon which)
1181 {
1182 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1183
1184 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1185 pItem->SetImage(image, which);
1186 pItem->CalculateSize(this);
1187 RefreshLine(pItem);
1188 }
1189
1190 void
1191 wxGenericTreeCtrl::SetItemData(const wxTreeItemId& item, wxTreeItemData *data)
1192 {
1193 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1194
1195 if (data)
1196 data->SetId( item );
1197
1198 ((wxGenericTreeItem*) item.m_pItem)->SetData(data);
1199 }
1200
1201 void wxGenericTreeCtrl::DoSetItemState(const wxTreeItemId& item, int state)
1202 {
1203 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1204
1205 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1206 pItem->SetState(state);
1207 pItem->CalculateSize(this);
1208 RefreshLine(pItem);
1209 }
1210
1211 void wxGenericTreeCtrl::SetItemHasChildren(const wxTreeItemId& item, bool has)
1212 {
1213 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1214
1215 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1216 pItem->SetHasPlus(has);
1217 RefreshLine(pItem);
1218 }
1219
1220 void wxGenericTreeCtrl::SetItemBold(const wxTreeItemId& item, bool bold)
1221 {
1222 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1223
1224 // avoid redrawing the tree if no real change
1225 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1226 if ( pItem->IsBold() != bold )
1227 {
1228 pItem->SetBold(bold);
1229
1230 // recalculate the item size as bold and non bold fonts have different
1231 // widths
1232 pItem->CalculateSize(this);
1233 RefreshLine(pItem);
1234 }
1235 }
1236
1237 void wxGenericTreeCtrl::SetItemDropHighlight(const wxTreeItemId& item,
1238 bool highlight)
1239 {
1240 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1241
1242 wxColour fg, bg;
1243
1244 if (highlight)
1245 {
1246 bg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
1247 fg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
1248 }
1249
1250 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1251 pItem->Attr().SetTextColour(fg);
1252 pItem->Attr().SetBackgroundColour(bg);
1253 RefreshLine(pItem);
1254 }
1255
1256 void wxGenericTreeCtrl::SetItemTextColour(const wxTreeItemId& item,
1257 const wxColour& col)
1258 {
1259 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1260
1261 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1262 pItem->Attr().SetTextColour(col);
1263 RefreshLine(pItem);
1264 }
1265
1266 void wxGenericTreeCtrl::SetItemBackgroundColour(const wxTreeItemId& item,
1267 const wxColour& col)
1268 {
1269 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1270
1271 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1272 pItem->Attr().SetBackgroundColour(col);
1273 RefreshLine(pItem);
1274 }
1275
1276 void
1277 wxGenericTreeCtrl::SetItemFont(const wxTreeItemId& item, const wxFont& font)
1278 {
1279 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1280
1281 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1282 pItem->Attr().SetFont(font);
1283 pItem->ResetTextSize();
1284 pItem->CalculateSize(this);
1285 RefreshLine(pItem);
1286 }
1287
1288 bool wxGenericTreeCtrl::SetFont( const wxFont &font )
1289 {
1290 wxTreeCtrlBase::SetFont(font);
1291
1292 m_normalFont = font;
1293 m_boldFont = m_normalFont.Bold();
1294
1295 if (m_anchor)
1296 m_anchor->RecursiveResetTextSize();
1297
1298 return true;
1299 }
1300
1301
1302 // -----------------------------------------------------------------------------
1303 // item status inquiries
1304 // -----------------------------------------------------------------------------
1305
1306 bool wxGenericTreeCtrl::IsVisible(const wxTreeItemId& item) const
1307 {
1308 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1309
1310 // An item is only visible if it's not a descendant of a collapsed item
1311 wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
1312 wxGenericTreeItem* parent = pItem->GetParent();
1313 while (parent)
1314 {
1315 if (!parent->IsExpanded())
1316 return false;
1317 parent = parent->GetParent();
1318 }
1319
1320 int startX, startY;
1321 GetViewStart(& startX, & startY);
1322
1323 wxSize clientSize = GetClientSize();
1324
1325 wxRect rect;
1326 if (!GetBoundingRect(item, rect))
1327 return false;
1328 if (rect.GetWidth() == 0 || rect.GetHeight() == 0)
1329 return false;
1330 if (rect.GetBottom() < 0 || rect.GetTop() > clientSize.y)
1331 return false;
1332 if (rect.GetRight() < 0 || rect.GetLeft() > clientSize.x)
1333 return false;
1334
1335 return true;
1336 }
1337
1338 bool wxGenericTreeCtrl::ItemHasChildren(const wxTreeItemId& item) const
1339 {
1340 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1341
1342 // consider that the item does have children if it has the "+" button: it
1343 // might not have them (if it had never been expanded yet) but then it
1344 // could have them as well and it's better to err on this side rather than
1345 // disabling some operations which are restricted to the items with
1346 // children for an item which does have them
1347 return ((wxGenericTreeItem*) item.m_pItem)->HasPlus();
1348 }
1349
1350 bool wxGenericTreeCtrl::IsExpanded(const wxTreeItemId& item) const
1351 {
1352 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1353
1354 return ((wxGenericTreeItem*) item.m_pItem)->IsExpanded();
1355 }
1356
1357 bool wxGenericTreeCtrl::IsSelected(const wxTreeItemId& item) const
1358 {
1359 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1360
1361 return ((wxGenericTreeItem*) item.m_pItem)->IsSelected();
1362 }
1363
1364 bool wxGenericTreeCtrl::IsBold(const wxTreeItemId& item) const
1365 {
1366 wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
1367
1368 return ((wxGenericTreeItem*) item.m_pItem)->IsBold();
1369 }
1370
1371 // -----------------------------------------------------------------------------
1372 // navigation
1373 // -----------------------------------------------------------------------------
1374
1375 wxTreeItemId wxGenericTreeCtrl::GetItemParent(const wxTreeItemId& item) const
1376 {
1377 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1378
1379 return ((wxGenericTreeItem*) item.m_pItem)->GetParent();
1380 }
1381
1382 wxTreeItemId wxGenericTreeCtrl::GetFirstChild(const wxTreeItemId& item,
1383 wxTreeItemIdValue& cookie) const
1384 {
1385 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1386
1387 cookie = 0;
1388 return GetNextChild(item, cookie);
1389 }
1390
1391 wxTreeItemId wxGenericTreeCtrl::GetNextChild(const wxTreeItemId& item,
1392 wxTreeItemIdValue& cookie) const
1393 {
1394 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1395
1396 wxArrayGenericTreeItems&
1397 children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren();
1398
1399 // it's ok to cast cookie to size_t, we never have indices big enough to
1400 // overflow "void *"
1401 size_t *pIndex = (size_t *)&cookie;
1402 if ( *pIndex < children.GetCount() )
1403 {
1404 return children.Item((*pIndex)++);
1405 }
1406 else
1407 {
1408 // there are no more of them
1409 return wxTreeItemId();
1410 }
1411 }
1412
1413 wxTreeItemId wxGenericTreeCtrl::GetLastChild(const wxTreeItemId& item) const
1414 {
1415 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1416
1417 wxArrayGenericTreeItems&
1418 children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren();
1419 return children.IsEmpty() ? wxTreeItemId() : wxTreeItemId(children.Last());
1420 }
1421
1422 wxTreeItemId wxGenericTreeCtrl::GetNextSibling(const wxTreeItemId& item) const
1423 {
1424 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1425
1426 wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
1427 wxGenericTreeItem *parent = i->GetParent();
1428 if ( parent == NULL )
1429 {
1430 // root item doesn't have any siblings
1431 return wxTreeItemId();
1432 }
1433
1434 wxArrayGenericTreeItems& siblings = parent->GetChildren();
1435 int index = siblings.Index(i);
1436 wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent?
1437
1438 size_t n = (size_t)(index + 1);
1439 return n == siblings.GetCount() ? wxTreeItemId()
1440 : wxTreeItemId(siblings[n]);
1441 }
1442
1443 wxTreeItemId wxGenericTreeCtrl::GetPrevSibling(const wxTreeItemId& item) const
1444 {
1445 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1446
1447 wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
1448 wxGenericTreeItem *parent = i->GetParent();
1449 if ( parent == NULL )
1450 {
1451 // root item doesn't have any siblings
1452 return wxTreeItemId();
1453 }
1454
1455 wxArrayGenericTreeItems& siblings = parent->GetChildren();
1456 int index = siblings.Index(i);
1457 wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent?
1458
1459 return index == 0 ? wxTreeItemId()
1460 : wxTreeItemId(siblings[(size_t)(index - 1)]);
1461 }
1462
1463 // Only for internal use right now, but should probably be public
1464 wxTreeItemId wxGenericTreeCtrl::GetNext(const wxTreeItemId& item) const
1465 {
1466 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1467
1468 wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
1469
1470 // First see if there are any children.
1471 wxArrayGenericTreeItems& children = i->GetChildren();
1472 if (children.GetCount() > 0)
1473 {
1474 return children.Item(0);
1475 }
1476 else
1477 {
1478 // Try a sibling of this or ancestor instead
1479 wxTreeItemId p = item;
1480 wxTreeItemId toFind;
1481 do
1482 {
1483 toFind = GetNextSibling(p);
1484 p = GetItemParent(p);
1485 } while (p.IsOk() && !toFind.IsOk());
1486 return toFind;
1487 }
1488 }
1489
1490 wxTreeItemId wxGenericTreeCtrl::GetFirstVisibleItem() const
1491 {
1492 wxTreeItemId itemid = GetRootItem();
1493 if (!itemid.IsOk())
1494 return itemid;
1495
1496 do
1497 {
1498 if (IsVisible(itemid))
1499 return itemid;
1500 itemid = GetNext(itemid);
1501 } while (itemid.IsOk());
1502
1503 return wxTreeItemId();
1504 }
1505
1506 wxTreeItemId wxGenericTreeCtrl::GetNextVisible(const wxTreeItemId& item) const
1507 {
1508 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1509 wxASSERT_MSG( IsVisible(item), wxT("this item itself should be visible") );
1510
1511 wxTreeItemId id = item;
1512 if (id.IsOk())
1513 {
1514 while (id = GetNext(id), id.IsOk())
1515 {
1516 if (IsVisible(id))
1517 return id;
1518 }
1519 }
1520 return wxTreeItemId();
1521 }
1522
1523 wxTreeItemId wxGenericTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const
1524 {
1525 wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
1526 wxASSERT_MSG( IsVisible(item), wxT("this item itself should be visible") );
1527
1528 // find out the starting point
1529 wxTreeItemId prevItem = GetPrevSibling(item);
1530 if ( !prevItem.IsOk() )
1531 {
1532 prevItem = GetItemParent(item);
1533 }
1534
1535 // find the first visible item after it
1536 while ( prevItem.IsOk() && !IsVisible(prevItem) )
1537 {
1538 prevItem = GetNext(prevItem);
1539 if ( !prevItem.IsOk() || prevItem == item )
1540 {
1541 // there are no visible items before item
1542 return wxTreeItemId();
1543 }
1544 }
1545
1546 // from there we must be able to navigate until this item
1547 while ( prevItem.IsOk() )
1548 {
1549 const wxTreeItemId nextItem = GetNextVisible(prevItem);
1550 if ( !nextItem.IsOk() || nextItem == item )
1551 break;
1552
1553 prevItem = nextItem;
1554 }
1555
1556 return prevItem;
1557 }
1558
1559 // called by wxTextTreeCtrl when it marks itself for deletion
1560 void wxGenericTreeCtrl::ResetTextControl()
1561 {
1562 m_textCtrl = NULL;
1563 }
1564
1565 void wxGenericTreeCtrl::ResetFindState()
1566 {
1567 m_findPrefix.clear();
1568 if ( m_findBell )
1569 m_findBell = 1;
1570 }
1571
1572 // find the first item starting with the given prefix after the given item
1573 wxTreeItemId wxGenericTreeCtrl::FindItem(const wxTreeItemId& idParent,
1574 const wxString& prefixOrig) const
1575 {
1576 // match is case insensitive as this is more convenient to the user: having
1577 // to press Shift-letter to go to the item starting with a capital letter
1578 // would be too bothersome
1579 wxString prefix = prefixOrig.Lower();
1580
1581 // determine the starting point: we shouldn't take the current item (this
1582 // allows to switch between two items starting with the same letter just by
1583 // pressing it) but we shouldn't jump to the next one if the user is
1584 // continuing to type as otherwise he might easily skip the item he wanted
1585 wxTreeItemId itemid = idParent;
1586 if ( prefix.length() == 1 )
1587 {
1588 itemid = GetNext(itemid);
1589 }
1590
1591 // look for the item starting with the given prefix after it
1592 while ( itemid.IsOk() && !GetItemText(itemid).Lower().StartsWith(prefix) )
1593 {
1594 itemid = GetNext(itemid);
1595 }
1596
1597 // if we haven't found anything...
1598 if ( !itemid.IsOk() )
1599 {
1600 // ... wrap to the beginning
1601 itemid = GetRootItem();
1602 if ( HasFlag(wxTR_HIDE_ROOT) )
1603 {
1604 // can't select virtual root
1605 itemid = GetNext(itemid);
1606 }
1607
1608 // and try all the items (stop when we get to the one we started from)
1609 while ( itemid.IsOk() && itemid != idParent &&
1610 !GetItemText(itemid).Lower().StartsWith(prefix) )
1611 {
1612 itemid = GetNext(itemid);
1613 }
1614 // If we haven't found the item but wrapped back to the one we started
1615 // from, id.IsOk() must be false
1616 if ( itemid == idParent )
1617 {
1618 itemid = wxTreeItemId();
1619 }
1620 }
1621
1622 return itemid;
1623 }
1624
1625 // -----------------------------------------------------------------------------
1626 // operations
1627 // -----------------------------------------------------------------------------
1628
1629 wxTreeItemId wxGenericTreeCtrl::DoInsertItem(const wxTreeItemId& parentId,
1630 size_t previous,
1631 const wxString& text,
1632 int image,
1633 int selImage,
1634 wxTreeItemData *data)
1635 {
1636 wxGenericTreeItem *parent = (wxGenericTreeItem*) parentId.m_pItem;
1637 if ( !parent )
1638 {
1639 // should we give a warning here?
1640 return AddRoot(text, image, selImage, data);
1641 }
1642
1643 m_dirty = true; // do this first so stuff below doesn't cause flicker
1644
1645 wxGenericTreeItem *item =
1646 new wxGenericTreeItem( parent, text, image, selImage, data );
1647
1648 if ( data != NULL )
1649 {
1650 data->m_pItem = item;
1651 }
1652
1653 parent->Insert( item, previous == (size_t)-1 ? parent->GetChildren().size()
1654 : previous );
1655
1656 InvalidateBestSize();
1657 return item;
1658 }
1659
1660 wxTreeItemId wxGenericTreeCtrl::AddRoot(const wxString& text,
1661 int image,
1662 int selImage,
1663 wxTreeItemData *data)
1664 {
1665 wxCHECK_MSG( !m_anchor, wxTreeItemId(), "tree can have only one root" );
1666
1667 m_dirty = true; // do this first so stuff below doesn't cause flicker
1668
1669 m_anchor = new wxGenericTreeItem(NULL, text,
1670 image, selImage, data);
1671 if ( data != NULL )
1672 {
1673 data->m_pItem = m_anchor;
1674 }
1675
1676 if (HasFlag(wxTR_HIDE_ROOT))
1677 {
1678 // if root is hidden, make sure we can navigate
1679 // into children
1680 m_anchor->SetHasPlus();
1681 m_anchor->Expand();
1682 CalculatePositions();
1683 }
1684
1685 if (!HasFlag(wxTR_MULTIPLE))
1686 {
1687 m_current = m_key_current = m_anchor;
1688 m_current->SetHilight( true );
1689 }
1690
1691 InvalidateBestSize();
1692 return m_anchor;
1693 }
1694
1695 wxTreeItemId wxGenericTreeCtrl::DoInsertAfter(const wxTreeItemId& parentId,
1696 const wxTreeItemId& idPrevious,
1697 const wxString& text,
1698 int image, int selImage,
1699 wxTreeItemData *data)
1700 {
1701 wxGenericTreeItem *parent = (wxGenericTreeItem*) parentId.m_pItem;
1702 if ( !parent )
1703 {
1704 // should we give a warning here?
1705 return AddRoot(text, image, selImage, data);
1706 }
1707
1708 int index = -1;
1709 if (idPrevious.IsOk())
1710 {
1711 index = parent->GetChildren().Index(
1712 (wxGenericTreeItem*) idPrevious.m_pItem);
1713 wxASSERT_MSG( index != wxNOT_FOUND,
1714 "previous item in wxGenericTreeCtrl::InsertItem() "
1715 "is not a sibling" );
1716 }
1717
1718 return DoInsertItem(parentId, (size_t)++index, text, image, selImage, data);
1719 }
1720
1721
1722 void wxGenericTreeCtrl::SendDeleteEvent(wxGenericTreeItem *item)
1723 {
1724 wxTreeEvent event(wxEVT_TREE_DELETE_ITEM, this, item);
1725 GetEventHandler()->ProcessEvent( event );
1726 }
1727
1728 // Don't leave edit or selection on a child which is about to disappear
1729 void wxGenericTreeCtrl::ChildrenClosing(wxGenericTreeItem* item)
1730 {
1731 if ( m_textCtrl && item != m_textCtrl->item() &&
1732 IsDescendantOf(item, m_textCtrl->item()) )
1733 {
1734 m_textCtrl->EndEdit( true );
1735 }
1736
1737 if ( item != m_key_current && IsDescendantOf(item, m_key_current) )
1738 {
1739 m_key_current = NULL;
1740 }
1741
1742 if ( IsDescendantOf(item, m_select_me) )
1743 {
1744 m_select_me = item;
1745 }
1746
1747 if ( item != m_current && IsDescendantOf(item, m_current) )
1748 {
1749 m_current->SetHilight( false );
1750 m_current = NULL;
1751 m_select_me = item;
1752 }
1753 }
1754
1755 void wxGenericTreeCtrl::DeleteChildren(const wxTreeItemId& itemId)
1756 {
1757 m_dirty = true; // do this first so stuff below doesn't cause flicker
1758
1759 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
1760 ChildrenClosing(item);
1761 item->DeleteChildren(this);
1762 InvalidateBestSize();
1763 }
1764
1765 void wxGenericTreeCtrl::Delete(const wxTreeItemId& itemId)
1766 {
1767 m_dirty = true; // do this first so stuff below doesn't cause flicker
1768
1769 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
1770
1771 if (m_textCtrl != NULL && IsDescendantOf(item, m_textCtrl->item()))
1772 {
1773 // can't delete the item being edited, cancel editing it first
1774 m_textCtrl->EndEdit( true );
1775 }
1776
1777 wxGenericTreeItem *parent = item->GetParent();
1778
1779 // if the selected item will be deleted, select the parent ...
1780 wxGenericTreeItem *to_be_selected = parent;
1781 if (parent)
1782 {
1783 // .. unless there is a next sibling like wxMSW does it
1784 int pos = parent->GetChildren().Index( item );
1785 if ((int)(parent->GetChildren().GetCount()) > pos+1)
1786 to_be_selected = parent->GetChildren().Item( pos+1 );
1787 }
1788
1789 // don't keep stale pointers around!
1790 if ( IsDescendantOf(item, m_key_current) )
1791 {
1792 // Don't silently change the selection:
1793 // do it properly in idle time, so event
1794 // handlers get called.
1795
1796 // m_key_current = parent;
1797 m_key_current = NULL;
1798 }
1799
1800 // m_select_me records whether we need to select
1801 // a different item, in idle time.
1802 if ( m_select_me && IsDescendantOf(item, m_select_me) )
1803 {
1804 m_select_me = to_be_selected;
1805 }
1806
1807 if ( IsDescendantOf(item, m_current) )
1808 {
1809 // Don't silently change the selection:
1810 // do it properly in idle time, so event
1811 // handlers get called.
1812
1813 // m_current = parent;
1814 m_current = NULL;
1815 m_select_me = to_be_selected;
1816 }
1817
1818 // remove the item from the tree
1819 if ( parent )
1820 {
1821 parent->GetChildren().Remove( item ); // remove by value
1822 }
1823 else // deleting the root
1824 {
1825 // nothing will be left in the tree
1826 m_anchor = NULL;
1827 }
1828
1829 // and delete all of its children and the item itself now
1830 item->DeleteChildren(this);
1831 SendDeleteEvent(item);
1832
1833 if (item == m_select_me)
1834 m_select_me = NULL;
1835
1836 delete item;
1837
1838 InvalidateBestSize();
1839 }
1840
1841 void wxGenericTreeCtrl::DeleteAllItems()
1842 {
1843 if ( m_anchor )
1844 {
1845 Delete(m_anchor);
1846 }
1847 }
1848
1849 void wxGenericTreeCtrl::Expand(const wxTreeItemId& itemId)
1850 {
1851 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
1852
1853 wxCHECK_RET( item, wxT("invalid item in wxGenericTreeCtrl::Expand") );
1854 wxCHECK_RET( !HasFlag(wxTR_HIDE_ROOT) || itemId != GetRootItem(),
1855 wxT("can't expand hidden root") );
1856
1857 if ( !item->HasPlus() )
1858 return;
1859
1860 if ( item->IsExpanded() )
1861 return;
1862
1863 wxTreeEvent event(wxEVT_TREE_ITEM_EXPANDING, this, item);
1864
1865 if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
1866 {
1867 // cancelled by program
1868 return;
1869 }
1870
1871 item->Expand();
1872 if ( !IsFrozen() )
1873 {
1874 CalculatePositions();
1875
1876 RefreshSubtree(item);
1877 }
1878 else // frozen
1879 {
1880 m_dirty = true;
1881 }
1882
1883 event.SetEventType(wxEVT_TREE_ITEM_EXPANDED);
1884 GetEventHandler()->ProcessEvent( event );
1885 }
1886
1887 void wxGenericTreeCtrl::Collapse(const wxTreeItemId& itemId)
1888 {
1889 wxCHECK_RET( !HasFlag(wxTR_HIDE_ROOT) || itemId != GetRootItem(),
1890 wxT("can't collapse hidden root") );
1891
1892 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
1893
1894 if ( !item->IsExpanded() )
1895 return;
1896
1897 wxTreeEvent event(wxEVT_TREE_ITEM_COLLAPSING, this, item);
1898 if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
1899 {
1900 // cancelled by program
1901 return;
1902 }
1903
1904 ChildrenClosing(item);
1905 item->Collapse();
1906
1907 #if 0 // TODO why should items be collapsed recursively?
1908 wxArrayGenericTreeItems& children = item->GetChildren();
1909 size_t count = children.GetCount();
1910 for ( size_t n = 0; n < count; n++ )
1911 {
1912 Collapse(children[n]);
1913 }
1914 #endif
1915
1916 CalculatePositions();
1917
1918 RefreshSubtree(item);
1919
1920 event.SetEventType(wxEVT_TREE_ITEM_COLLAPSED);
1921 GetEventHandler()->ProcessEvent( event );
1922 }
1923
1924 void wxGenericTreeCtrl::CollapseAndReset(const wxTreeItemId& item)
1925 {
1926 Collapse(item);
1927 DeleteChildren(item);
1928 }
1929
1930 void wxGenericTreeCtrl::Toggle(const wxTreeItemId& itemId)
1931 {
1932 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
1933
1934 if (item->IsExpanded())
1935 Collapse(itemId);
1936 else
1937 Expand(itemId);
1938 }
1939
1940 void wxGenericTreeCtrl::Unselect()
1941 {
1942 if (m_current)
1943 {
1944 m_current->SetHilight( false );
1945 RefreshLine( m_current );
1946
1947 m_current = NULL;
1948 m_select_me = NULL;
1949 }
1950 }
1951
1952 void wxGenericTreeCtrl::ClearFocusedItem()
1953 {
1954 wxTreeItemId item = GetFocusedItem();
1955 if ( item.IsOk() )
1956 SelectItem(item, false);
1957 }
1958
1959 void wxGenericTreeCtrl::SetFocusedItem(const wxTreeItemId& item)
1960 {
1961 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
1962
1963 SelectItem(item, true);
1964 }
1965
1966 void wxGenericTreeCtrl::UnselectAllChildren(wxGenericTreeItem *item)
1967 {
1968 if (item->IsSelected())
1969 {
1970 item->SetHilight(false);
1971 RefreshLine(item);
1972 }
1973
1974 if (item->HasChildren())
1975 {
1976 wxArrayGenericTreeItems& children = item->GetChildren();
1977 size_t count = children.GetCount();
1978 for ( size_t n = 0; n < count; ++n )
1979 {
1980 UnselectAllChildren(children[n]);
1981 }
1982 }
1983 }
1984
1985 void wxGenericTreeCtrl::UnselectAll()
1986 {
1987 wxTreeItemId rootItem = GetRootItem();
1988
1989 // the tree might not have the root item at all
1990 if ( rootItem )
1991 {
1992 UnselectAllChildren((wxGenericTreeItem*) rootItem.m_pItem);
1993 }
1994 }
1995
1996 void wxGenericTreeCtrl::SelectChildren(const wxTreeItemId& parent)
1997 {
1998 wxCHECK_RET( HasFlag(wxTR_MULTIPLE),
1999 "this only works with multiple selection controls" );
2000
2001 UnselectAll();
2002
2003 if ( !HasChildren(parent) )
2004 return;
2005
2006
2007 wxArrayGenericTreeItems&
2008 children = ((wxGenericTreeItem*) parent.m_pItem)->GetChildren();
2009 size_t count = children.GetCount();
2010
2011 wxGenericTreeItem *
2012 item = (wxGenericTreeItem*) ((wxTreeItemId)children[0]).m_pItem;
2013 wxTreeEvent event(wxEVT_TREE_SEL_CHANGING, this, item);
2014 event.m_itemOld = m_current;
2015
2016 if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
2017 return;
2018
2019 for ( size_t n = 0; n < count; ++n )
2020 {
2021 m_current = m_key_current = children[n];
2022 m_current->SetHilight(true);
2023 RefreshSelected();
2024 }
2025
2026
2027 event.SetEventType(wxEVT_TREE_SEL_CHANGED);
2028 GetEventHandler()->ProcessEvent( event );
2029 }
2030
2031
2032 // Recursive function !
2033 // To stop we must have crt_item<last_item
2034 // Algorithm :
2035 // Tag all next children, when no more children,
2036 // Move to parent (not to tag)
2037 // Keep going... if we found last_item, we stop.
2038 bool
2039 wxGenericTreeCtrl::TagNextChildren(wxGenericTreeItem *crt_item,
2040 wxGenericTreeItem *last_item,
2041 bool select)
2042 {
2043 wxGenericTreeItem *parent = crt_item->GetParent();
2044
2045 if (parent == NULL) // This is root item
2046 return TagAllChildrenUntilLast(crt_item, last_item, select);
2047
2048 wxArrayGenericTreeItems& children = parent->GetChildren();
2049 int index = children.Index(crt_item);
2050 wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent?
2051
2052 size_t count = children.GetCount();
2053 for (size_t n=(size_t)(index+1); n<count; ++n)
2054 {
2055 if ( TagAllChildrenUntilLast(children[n], last_item, select) )
2056 return true;
2057 }
2058
2059 return TagNextChildren(parent, last_item, select);
2060 }
2061
2062 bool
2063 wxGenericTreeCtrl::TagAllChildrenUntilLast(wxGenericTreeItem *crt_item,
2064 wxGenericTreeItem *last_item,
2065 bool select)
2066 {
2067 crt_item->SetHilight(select);
2068 RefreshLine(crt_item);
2069
2070 if (crt_item==last_item)
2071 return true;
2072
2073 // We should leave the not shown children of collapsed items alone.
2074 if (crt_item->HasChildren() && crt_item->IsExpanded())
2075 {
2076 wxArrayGenericTreeItems& children = crt_item->GetChildren();
2077 size_t count = children.GetCount();
2078 for ( size_t n = 0; n < count; ++n )
2079 {
2080 if (TagAllChildrenUntilLast(children[n], last_item, select))
2081 return true;
2082 }
2083 }
2084
2085 return false;
2086 }
2087
2088 void
2089 wxGenericTreeCtrl::SelectItemRange(wxGenericTreeItem *item1,
2090 wxGenericTreeItem *item2)
2091 {
2092 m_select_me = NULL;
2093
2094 // item2 is not necessary after item1
2095 // choice first' and 'last' between item1 and item2
2096 wxGenericTreeItem *first= (item1->GetY()<item2->GetY()) ? item1 : item2;
2097 wxGenericTreeItem *last = (item1->GetY()<item2->GetY()) ? item2 : item1;
2098
2099 bool select = m_current->IsSelected();
2100
2101 if ( TagAllChildrenUntilLast(first,last,select) )
2102 return;
2103
2104 TagNextChildren(first,last,select);
2105 }
2106
2107 void wxGenericTreeCtrl::DoSelectItem(const wxTreeItemId& itemId,
2108 bool unselect_others,
2109 bool extended_select)
2110 {
2111 wxCHECK_RET( itemId.IsOk(), wxT("invalid tree item") );
2112
2113 m_select_me = NULL;
2114
2115 bool is_single=!(GetWindowStyleFlag() & wxTR_MULTIPLE);
2116 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
2117
2118 //wxCHECK_RET( ( (!unselect_others) && is_single),
2119 // wxT("this is a single selection tree") );
2120
2121 // to keep going anyhow !!!
2122 if (is_single)
2123 {
2124 if (item->IsSelected())
2125 return; // nothing to do
2126 unselect_others = true;
2127 extended_select = false;
2128 }
2129 else if ( unselect_others && item->IsSelected() )
2130 {
2131 // selection change if there is more than one item currently selected
2132 wxArrayTreeItemIds selected_items;
2133 if ( GetSelections(selected_items) == 1 )
2134 return;
2135 }
2136
2137 wxTreeEvent event(wxEVT_TREE_SEL_CHANGING, this, item);
2138 event.m_itemOld = m_current;
2139 // TODO : Here we don't send any selection mode yet !
2140
2141 if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
2142 return;
2143
2144 wxTreeItemId parent = GetItemParent( itemId );
2145 while (parent.IsOk())
2146 {
2147 if (!IsExpanded(parent))
2148 Expand( parent );
2149
2150 parent = GetItemParent( parent );
2151 }
2152
2153 // ctrl press
2154 if (unselect_others)
2155 {
2156 if (is_single) Unselect(); // to speed up thing
2157 else UnselectAll();
2158 }
2159
2160 // shift press
2161 if (extended_select)
2162 {
2163 if ( !m_current )
2164 {
2165 m_current =
2166 m_key_current = (wxGenericTreeItem*) GetRootItem().m_pItem;
2167 }
2168
2169 // don't change the mark (m_current)
2170 SelectItemRange(m_current, item);
2171 }
2172 else
2173 {
2174 bool select = true; // the default
2175
2176 // Check if we need to toggle hilight (ctrl mode)
2177 if (!unselect_others)
2178 select=!item->IsSelected();
2179
2180 m_current = m_key_current = item;
2181 m_current->SetHilight(select);
2182 RefreshLine( m_current );
2183 }
2184
2185 // This can cause idle processing to select the root
2186 // if no item is selected, so it must be after the
2187 // selection is set
2188 EnsureVisible( itemId );
2189
2190 event.SetEventType(wxEVT_TREE_SEL_CHANGED);
2191 GetEventHandler()->ProcessEvent( event );
2192 }
2193
2194 void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, bool select)
2195 {
2196 wxGenericTreeItem * const item = (wxGenericTreeItem*) itemId.m_pItem;
2197 wxCHECK_RET( item, wxT("SelectItem(): invalid tree item") );
2198
2199 if ( select )
2200 {
2201 if ( !item->IsSelected() )
2202 DoSelectItem(itemId, !HasFlag(wxTR_MULTIPLE));
2203 }
2204 else // deselect
2205 {
2206 wxTreeEvent event(wxEVT_TREE_SEL_CHANGING, this, item);
2207 if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
2208 return;
2209
2210 item->SetHilight(false);
2211 RefreshLine(item);
2212
2213 event.SetEventType(wxEVT_TREE_SEL_CHANGED);
2214 GetEventHandler()->ProcessEvent( event );
2215 }
2216 }
2217
2218 void wxGenericTreeCtrl::FillArray(wxGenericTreeItem *item,
2219 wxArrayTreeItemIds &array) const
2220 {
2221 if ( item->IsSelected() )
2222 array.Add(wxTreeItemId(item));
2223
2224 if ( item->HasChildren() )
2225 {
2226 wxArrayGenericTreeItems& children = item->GetChildren();
2227 size_t count = children.GetCount();
2228 for ( size_t n = 0; n < count; ++n )
2229 FillArray(children[n], array);
2230 }
2231 }
2232
2233 size_t wxGenericTreeCtrl::GetSelections(wxArrayTreeItemIds &array) const
2234 {
2235 array.Empty();
2236 wxTreeItemId idRoot = GetRootItem();
2237 if ( idRoot.IsOk() )
2238 {
2239 FillArray((wxGenericTreeItem*) idRoot.m_pItem, array);
2240 }
2241 //else: the tree is empty, so no selections
2242
2243 return array.GetCount();
2244 }
2245
2246 void wxGenericTreeCtrl::EnsureVisible(const wxTreeItemId& item)
2247 {
2248 wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
2249
2250 if (!item.IsOk()) return;
2251
2252 wxGenericTreeItem *gitem = (wxGenericTreeItem*) item.m_pItem;
2253
2254 // first expand all parent branches
2255 wxGenericTreeItem *parent = gitem->GetParent();
2256
2257 if ( HasFlag(wxTR_HIDE_ROOT) )
2258 {
2259 while ( parent && parent != m_anchor )
2260 {
2261 Expand(parent);
2262 parent = parent->GetParent();
2263 }
2264 }
2265 else
2266 {
2267 while ( parent )
2268 {
2269 Expand(parent);
2270 parent = parent->GetParent();
2271 }
2272 }
2273
2274 //if (parent) CalculatePositions();
2275
2276 ScrollTo(item);
2277 }
2278
2279 void wxGenericTreeCtrl::ScrollTo(const wxTreeItemId &item)
2280 {
2281 if (!item.IsOk())
2282 return;
2283
2284 // update the control before scrolling it
2285 if (m_dirty)
2286 {
2287 #if defined( __WXMSW__ )
2288 Update();
2289 #elif defined(__WXMAC__)
2290 Update();
2291 DoDirtyProcessing();
2292 #else
2293 DoDirtyProcessing();
2294 #endif
2295 }
2296
2297 wxGenericTreeItem *gitem = (wxGenericTreeItem*) item.m_pItem;
2298
2299 int itemY = gitem->GetY();
2300
2301 int start_x = 0;
2302 int start_y = 0;
2303 GetViewStart( &start_x, &start_y );
2304
2305 const int clientHeight = GetClientSize().y;
2306
2307 const int itemHeight = GetLineHeight(gitem) + 2;
2308
2309 if ( itemY + itemHeight > start_y*PIXELS_PER_UNIT + clientHeight )
2310 {
2311 // need to scroll up by enough to show this item fully
2312 itemY += itemHeight - clientHeight;
2313 #ifdef __WXOSX__
2314 // because itemY below will be divided by PIXELS_PER_UNIT it may
2315 // be rounded down, with the result of the item still only being
2316 // partially visible, so make sure we are rounding up
2317 itemY += PIXELS_PER_UNIT-1;
2318 #endif
2319 }
2320 else if ( itemY > start_y*PIXELS_PER_UNIT )
2321 {
2322 // item is already fully visible, don't do anything
2323 return;
2324 }
2325 //else: scroll down to make this item the top one displayed
2326
2327 Scroll(-1, itemY/PIXELS_PER_UNIT);
2328 }
2329
2330 // FIXME: tree sorting functions are not reentrant and not MT-safe!
2331 static wxGenericTreeCtrl *s_treeBeingSorted = NULL;
2332
2333 static int LINKAGEMODE tree_ctrl_compare_func(wxGenericTreeItem **item1,
2334 wxGenericTreeItem **item2)
2335 {
2336 wxCHECK_MSG( s_treeBeingSorted, 0,
2337 "bug in wxGenericTreeCtrl::SortChildren()" );
2338
2339 return s_treeBeingSorted->OnCompareItems(*item1, *item2);
2340 }
2341
2342 void wxGenericTreeCtrl::SortChildren(const wxTreeItemId& itemId)
2343 {
2344 wxCHECK_RET( itemId.IsOk(), wxT("invalid tree item") );
2345
2346 wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
2347
2348 wxCHECK_RET( !s_treeBeingSorted,
2349 wxT("wxGenericTreeCtrl::SortChildren is not reentrant") );
2350
2351 wxArrayGenericTreeItems& children = item->GetChildren();
2352 if ( children.GetCount() > 1 )
2353 {
2354 m_dirty = true;
2355
2356 s_treeBeingSorted = this;
2357 children.Sort(tree_ctrl_compare_func);
2358 s_treeBeingSorted = NULL;
2359 }
2360 //else: don't make the tree dirty as nothing changed
2361 }
2362
2363 void wxGenericTreeCtrl::CalculateLineHeight()
2364 {
2365 wxClientDC dc(this);
2366 m_lineHeight = (int)(dc.GetCharHeight() + 4);
2367
2368 if ( m_imageListNormal )
2369 {
2370 // Calculate a m_lineHeight value from the normal Image sizes.
2371 // May be toggle off. Then wxGenericTreeCtrl will spread when
2372 // necessary (which might look ugly).
2373 int n = m_imageListNormal->GetImageCount();
2374 for (int i = 0; i < n ; i++)
2375 {
2376 int width = 0, height = 0;
2377 m_imageListNormal->GetSize(i, width, height);
2378 if (height > m_lineHeight) m_lineHeight = height;
2379 }
2380 }
2381
2382 if ( m_imageListState )
2383 {
2384 // Calculate a m_lineHeight value from the state Image sizes.
2385 // May be toggle off. Then wxGenericTreeCtrl will spread when
2386 // necessary (which might look ugly).
2387 int n = m_imageListState->GetImageCount();
2388 for (int i = 0; i < n ; i++)
2389 {
2390 int width = 0, height = 0;
2391 m_imageListState->GetSize(i, width, height);
2392 if (height > m_lineHeight) m_lineHeight = height;
2393 }
2394 }
2395
2396 if (m_imageListButtons)
2397 {
2398 // Calculate a m_lineHeight value from the Button image sizes.
2399 // May be toggle off. Then wxGenericTreeCtrl will spread when
2400 // necessary (which might look ugly).
2401 int n = m_imageListButtons->GetImageCount();
2402 for (int i = 0; i < n ; i++)
2403 {
2404 int width = 0, height = 0;
2405 m_imageListButtons->GetSize(i, width, height);
2406 if (height > m_lineHeight) m_lineHeight = height;
2407 }
2408 }
2409
2410 if (m_lineHeight < 30)
2411 m_lineHeight += 2; // at least 2 pixels
2412 else
2413 m_lineHeight += m_lineHeight/10; // otherwise 10% extra spacing
2414 }
2415
2416 void wxGenericTreeCtrl::SetImageList(wxImageList *imageList)
2417 {
2418 if (m_ownsImageListNormal) delete m_imageListNormal;
2419 m_imageListNormal = imageList;
2420 m_ownsImageListNormal = false;
2421 m_dirty = true;
2422
2423 if (m_anchor)
2424 m_anchor->RecursiveResetSize();
2425
2426 // Don't do any drawing if we're setting the list to NULL,
2427 // since we may be in the process of deleting the tree control.
2428 if (imageList)
2429 CalculateLineHeight();
2430 }
2431
2432 void wxGenericTreeCtrl::SetStateImageList(wxImageList *imageList)
2433 {
2434 if (m_ownsImageListState) delete m_imageListState;
2435 m_imageListState = imageList;
2436 m_ownsImageListState = false;
2437 m_dirty = true;
2438
2439 if (m_anchor)
2440 m_anchor->RecursiveResetSize();
2441
2442 // Don't do any drawing if we're setting the list to NULL,
2443 // since we may be in the process of deleting the tree control.
2444 if (imageList)
2445 CalculateLineHeight();
2446 }
2447
2448 void wxGenericTreeCtrl::SetButtonsImageList(wxImageList *imageList)
2449 {
2450 if (m_ownsImageListButtons) delete m_imageListButtons;
2451 m_imageListButtons = imageList;
2452 m_ownsImageListButtons = false;
2453 m_dirty = true;
2454
2455 if (m_anchor)
2456 m_anchor->RecursiveResetSize();
2457
2458 CalculateLineHeight();
2459 }
2460
2461 void wxGenericTreeCtrl::AssignButtonsImageList(wxImageList *imageList)
2462 {
2463 SetButtonsImageList(imageList);
2464 m_ownsImageListButtons = true;
2465 }
2466
2467 // -----------------------------------------------------------------------------
2468 // helpers
2469 // -----------------------------------------------------------------------------
2470
2471 void wxGenericTreeCtrl::AdjustMyScrollbars()
2472 {
2473 if (m_anchor)
2474 {
2475 int x = 0, y = 0;
2476 m_anchor->GetSize( x, y, this );
2477 y += PIXELS_PER_UNIT+2; // one more scrollbar unit + 2 pixels
2478 x += PIXELS_PER_UNIT+2; // one more scrollbar unit + 2 pixels
2479 int x_pos = GetScrollPos( wxHORIZONTAL );
2480 int y_pos = GetScrollPos( wxVERTICAL );
2481 SetScrollbars( PIXELS_PER_UNIT, PIXELS_PER_UNIT,
2482 x/PIXELS_PER_UNIT, y/PIXELS_PER_UNIT,
2483 x_pos, y_pos );
2484 }
2485 else
2486 {
2487 SetScrollbars( 0, 0, 0, 0 );
2488 }
2489 }
2490
2491 int wxGenericTreeCtrl::GetLineHeight(wxGenericTreeItem *item) const
2492 {
2493 if (GetWindowStyleFlag() & wxTR_HAS_VARIABLE_ROW_HEIGHT)
2494 return item->GetHeight();
2495 else
2496 return m_lineHeight;
2497 }
2498
2499 void wxGenericTreeCtrl::PaintItem(wxGenericTreeItem *item, wxDC& dc)
2500 {
2501 item->SetFont(this, dc);
2502 item->CalculateSize(this, dc);
2503
2504 wxCoord text_h = item->GetTextHeight();
2505
2506 int image_h = 0, image_w = 0;
2507 int image = item->GetCurrentImage();
2508 if ( image != NO_IMAGE )
2509 {
2510 if ( m_imageListNormal )
2511 {
2512 m_imageListNormal->GetSize(image, image_w, image_h);
2513 image_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
2514 }
2515 else
2516 {
2517 image = NO_IMAGE;
2518 }
2519 }
2520
2521 int state_h = 0, state_w = 0;
2522 int state = item->GetState();
2523 if ( state != wxTREE_ITEMSTATE_NONE )
2524 {
2525 if ( m_imageListState )
2526 {
2527 m_imageListState->GetSize(state, state_w, state_h);
2528 if ( image_w != 0 )
2529 state_w += MARGIN_BETWEEN_STATE_AND_IMAGE;
2530 else
2531 state_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
2532 }
2533 else
2534 {
2535 state = wxTREE_ITEMSTATE_NONE;
2536 }
2537 }
2538
2539 int total_h = GetLineHeight(item);
2540 bool drawItemBackground = false,
2541 hasBgColour = false;
2542
2543 if ( item->IsSelected() )
2544 {
2545 dc.SetBrush(*(m_hasFocus ? m_hilightBrush : m_hilightUnfocusedBrush));
2546 drawItemBackground = true;
2547 }
2548 else
2549 {
2550 wxColour colBg;
2551 wxTreeItemAttr * const attr = item->GetAttributes();
2552 if ( attr && attr->HasBackgroundColour() )
2553 {
2554 drawItemBackground =
2555 hasBgColour = true;
2556 colBg = attr->GetBackgroundColour();
2557 }
2558 else
2559 {
2560 colBg = GetBackgroundColour();
2561 }
2562 dc.SetBrush(wxBrush(colBg, wxBRUSHSTYLE_SOLID));
2563 }
2564
2565 int offset = HasFlag(wxTR_ROW_LINES) ? 1 : 0;
2566
2567 if ( HasFlag(wxTR_FULL_ROW_HIGHLIGHT) )
2568 {
2569 int x, w, h;
2570 x=0;
2571 GetVirtualSize(&w, &h);
2572 wxRect rect( x, item->GetY()+offset, w, total_h-offset);
2573 if (!item->IsSelected())
2574 {
2575 dc.DrawRectangle(rect);
2576 }
2577 else
2578 {
2579 int flags = wxCONTROL_SELECTED;
2580 if (m_hasFocus
2581 #if defined( __WXMAC__ ) && !defined(__WXUNIVERSAL__) && wxOSX_USE_CARBON // TODO CS
2582 && IsControlActive( (ControlRef)GetHandle() )
2583 #endif
2584 )
2585 flags |= wxCONTROL_FOCUSED;
2586 if ((item == m_current) && (m_hasFocus))
2587 flags |= wxCONTROL_CURRENT;
2588
2589 wxRendererNative::Get().
2590 DrawItemSelectionRect(this, dc, rect, flags);
2591 }
2592 }
2593 else // no full row highlight
2594 {
2595 if ( item->IsSelected() &&
2596 (state != wxTREE_ITEMSTATE_NONE || image != NO_IMAGE) )
2597 {
2598 // If it's selected, and there's an state image or normal image,
2599 // then we should take care to leave the area under the image
2600 // painted in the background colour.
2601 wxRect rect( item->GetX() + state_w + image_w - 2,
2602 item->GetY() + offset,
2603 item->GetWidth() - state_w - image_w + 2,
2604 total_h - offset );
2605 #if !defined(__WXGTK20__) && !defined(__WXMAC__)
2606 dc.DrawRectangle( rect );
2607 #else
2608 rect.x -= 1;
2609 rect.width += 2;
2610
2611 int flags = wxCONTROL_SELECTED;
2612 if (m_hasFocus)
2613 flags |= wxCONTROL_FOCUSED;
2614 if ((item == m_current) && (m_hasFocus))
2615 flags |= wxCONTROL_CURRENT;
2616 wxRendererNative::Get().
2617 DrawItemSelectionRect(this, dc, rect, flags);
2618 #endif
2619 }
2620 // On GTK+ 2, drawing a 'normal' background is wrong for themes that
2621 // don't allow backgrounds to be customized. Not drawing the background,
2622 // except for custom item backgrounds, works for both kinds of theme.
2623 else if (drawItemBackground)
2624 {
2625 wxRect rect( item->GetX() + state_w + image_w - 2,
2626 item->GetY() + offset,
2627 item->GetWidth() - state_w - image_w + 2,
2628 total_h - offset );
2629 if ( hasBgColour )
2630 {
2631 dc.DrawRectangle( rect );
2632 }
2633 else // no specific background colour
2634 {
2635 rect.x -= 1;
2636 rect.width += 2;
2637
2638 int flags = wxCONTROL_SELECTED;
2639 if (m_hasFocus)
2640 flags |= wxCONTROL_FOCUSED;
2641 if ((item == m_current) && (m_hasFocus))
2642 flags |= wxCONTROL_CURRENT;
2643 wxRendererNative::Get().
2644 DrawItemSelectionRect(this, dc, rect, flags);
2645 }
2646 }
2647 }
2648
2649 if ( state != wxTREE_ITEMSTATE_NONE )
2650 {
2651 dc.SetClippingRegion( item->GetX(), item->GetY(), state_w, total_h );
2652 m_imageListState->Draw( state, dc,
2653 item->GetX(),
2654 item->GetY() +
2655 (total_h > state_h ? (total_h-state_h)/2
2656 : 0),
2657 wxIMAGELIST_DRAW_TRANSPARENT );
2658 dc.DestroyClippingRegion();
2659 }
2660
2661 if ( image != NO_IMAGE )
2662 {
2663 dc.SetClippingRegion(item->GetX() + state_w, item->GetY(),
2664 image_w, total_h);
2665 m_imageListNormal->Draw( image, dc,
2666 item->GetX() + state_w,
2667 item->GetY() +
2668 (total_h > image_h ? (total_h-image_h)/2
2669 : 0),
2670 wxIMAGELIST_DRAW_TRANSPARENT );
2671 dc.DestroyClippingRegion();
2672 }
2673
2674 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
2675 int extraH = (total_h > text_h) ? (total_h - text_h)/2 : 0;
2676 dc.DrawText( item->GetText(),
2677 (wxCoord)(state_w + image_w + item->GetX()),
2678 (wxCoord)(item->GetY() + extraH));
2679
2680 // restore normal font
2681 dc.SetFont( m_normalFont );
2682
2683 if (item == m_dndEffectItem)
2684 {
2685 dc.SetPen( *wxBLACK_PEN );
2686 // DnD visual effects
2687 switch (m_dndEffect)
2688 {
2689 case BorderEffect:
2690 {
2691 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2692 int w = item->GetWidth() + 2;
2693 int h = total_h + 2;
2694 dc.DrawRectangle( item->GetX() - 1, item->GetY() - 1, w, h);
2695 break;
2696 }
2697 case AboveEffect:
2698 {
2699 int x = item->GetX(),
2700 y = item->GetY();
2701 dc.DrawLine( x, y, x + item->GetWidth(), y);
2702 break;
2703 }
2704 case BelowEffect:
2705 {
2706 int x = item->GetX(),
2707 y = item->GetY();
2708 y += total_h - 1;
2709 dc.DrawLine( x, y, x + item->GetWidth(), y);
2710 break;
2711 }
2712 case NoEffect:
2713 break;
2714 }
2715 }
2716 }
2717
2718 void
2719 wxGenericTreeCtrl::PaintLevel(wxGenericTreeItem *item,
2720 wxDC &dc,
2721 int level,
2722 int &y)
2723 {
2724 int x = level*m_indent;
2725 if (!HasFlag(wxTR_HIDE_ROOT))
2726 {
2727 x += m_indent;
2728 }
2729 else if (level == 0)
2730 {
2731 // always expand hidden root
2732 int origY = y;
2733 wxArrayGenericTreeItems& children = item->GetChildren();
2734 int count = children.GetCount();
2735 if (count > 0)
2736 {
2737 int n = 0, oldY;
2738 do {
2739 oldY = y;
2740 PaintLevel(children[n], dc, 1, y);
2741 } while (++n < count);
2742
2743 if ( !HasFlag(wxTR_NO_LINES) && HasFlag(wxTR_LINES_AT_ROOT)
2744 && count > 0 )
2745 {
2746 // draw line down to last child
2747 origY += GetLineHeight(children[0])>>1;
2748 oldY += GetLineHeight(children[n-1])>>1;
2749 dc.DrawLine(3, origY, 3, oldY);
2750 }
2751 }
2752 return;
2753 }
2754
2755 item->SetX(x+m_spacing);
2756 item->SetY(y);
2757
2758 int h = GetLineHeight(item);
2759 int y_top = y;
2760 int y_mid = y_top + (h>>1);
2761 y += h;
2762
2763 int exposed_x = dc.LogicalToDeviceX(0);
2764 int exposed_y = dc.LogicalToDeviceY(y_top);
2765
2766 if (IsExposed(exposed_x, exposed_y, 10000, h)) // 10000 = very much
2767 {
2768 const wxPen *pen =
2769 #ifndef __WXMAC__
2770 // don't draw rect outline if we already have the
2771 // background color under Mac
2772 (item->IsSelected() && m_hasFocus) ? wxBLACK_PEN :
2773 #endif // !__WXMAC__
2774 wxTRANSPARENT_PEN;
2775
2776 wxColour colText;
2777 if ( item->IsSelected()
2778 #if defined( __WXMAC__ ) && !defined(__WXUNIVERSAL__) && wxOSX_USE_CARBON // TODO CS
2779 // On wxMac, if the tree doesn't have the focus we draw an empty
2780 // rectangle, so we want to make sure that the text is visible
2781 // against the normal background, not the highlightbackground, so
2782 // don't use the highlight text colour unless we have the focus.
2783 && m_hasFocus && IsControlActive( (ControlRef)GetHandle() )
2784 #endif
2785 )
2786 {
2787 #ifdef __WXMAC__
2788 colText = *wxWHITE;
2789 #else
2790 if (m_hasFocus)
2791 colText = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
2792 else
2793 colText = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT);
2794 #endif
2795 }
2796 else
2797 {
2798 wxTreeItemAttr *attr = item->GetAttributes();
2799 if (attr && attr->HasTextColour())
2800 colText = attr->GetTextColour();
2801 else
2802 colText = GetForegroundColour();
2803 }
2804
2805 // prepare to draw
2806 dc.SetTextForeground(colText);
2807 dc.SetPen(*pen);
2808
2809 // draw
2810 PaintItem(item, dc);
2811
2812 if (HasFlag(wxTR_ROW_LINES))
2813 {
2814 // if the background colour is white, choose a
2815 // contrasting color for the lines
2816 dc.SetPen(*((GetBackgroundColour() == *wxWHITE)
2817 ? wxMEDIUM_GREY_PEN : wxWHITE_PEN));
2818 dc.DrawLine(0, y_top, 10000, y_top);
2819 dc.DrawLine(0, y, 10000, y);
2820 }
2821
2822 // restore DC objects
2823 dc.SetBrush(*wxWHITE_BRUSH);
2824 dc.SetPen(m_dottedPen);
2825 dc.SetTextForeground(*wxBLACK);
2826
2827 if ( !HasFlag(wxTR_NO_LINES) )
2828 {
2829 // draw the horizontal line here
2830 int x_start = x;
2831 if (x > (signed)m_indent)
2832 x_start -= m_indent;
2833 else if (HasFlag(wxTR_LINES_AT_ROOT))
2834 x_start = 3;
2835 dc.DrawLine(x_start, y_mid, x + m_spacing, y_mid);
2836 }
2837
2838 // should the item show a button?
2839 if ( item->HasPlus() && HasButtons() )
2840 {
2841 if ( m_imageListButtons )
2842 {
2843 // draw the image button here
2844 int image_h = 0,
2845 image_w = 0;
2846 int image = item->IsExpanded() ? wxTreeItemIcon_Expanded
2847 : wxTreeItemIcon_Normal;
2848 if ( item->IsSelected() )
2849 image += wxTreeItemIcon_Selected - wxTreeItemIcon_Normal;
2850
2851 m_imageListButtons->GetSize(image, image_w, image_h);
2852 int xx = x - image_w/2;
2853 int yy = y_mid - image_h/2;
2854
2855 wxDCClipper clip(dc, xx, yy, image_w, image_h);
2856 m_imageListButtons->Draw(image, dc, xx, yy,
2857 wxIMAGELIST_DRAW_TRANSPARENT);
2858 }
2859 else // no custom buttons
2860 {
2861 static const int wImage = 9;
2862 static const int hImage = 9;
2863
2864 int flag = 0;
2865 if (item->IsExpanded())
2866 flag |= wxCONTROL_EXPANDED;
2867 if (item == m_underMouse)
2868 flag |= wxCONTROL_CURRENT;
2869
2870 wxRendererNative::Get().DrawTreeItemButton
2871 (
2872 this,
2873 dc,
2874 wxRect(x - wImage/2,
2875 y_mid - hImage/2,
2876 wImage, hImage),
2877 flag
2878 );
2879 }
2880 }
2881 }
2882
2883 if (item->IsExpanded())
2884 {
2885 wxArrayGenericTreeItems& children = item->GetChildren();
2886 int count = children.GetCount();
2887 if (count > 0)
2888 {
2889 int n = 0, oldY;
2890 ++level;
2891 do {
2892 oldY = y;
2893 PaintLevel(children[n], dc, level, y);
2894 } while (++n < count);
2895
2896 if (!HasFlag(wxTR_NO_LINES) && count > 0)
2897 {
2898 // draw line down to last child
2899 oldY += GetLineHeight(children[n-1])>>1;
2900 if (HasButtons()) y_mid += 5;
2901
2902 // Only draw the portion of the line that is visible, in case
2903 // it is huge
2904 wxCoord xOrigin=0, yOrigin=0, width, height;
2905 dc.GetDeviceOrigin(&xOrigin, &yOrigin);
2906 yOrigin = abs(yOrigin);
2907 GetClientSize(&width, &height);
2908
2909 // Move end points to the beginning/end of the view?
2910 if (y_mid < yOrigin)
2911 y_mid = yOrigin;
2912 if (oldY > yOrigin + height)
2913 oldY = yOrigin + height;
2914
2915 // after the adjustments if y_mid is larger than oldY then the
2916 // line isn't visible at all so don't draw anything
2917 if (y_mid < oldY)
2918 dc.DrawLine(x, y_mid, x, oldY);
2919 }
2920 }
2921 }
2922 }
2923
2924 void wxGenericTreeCtrl::DrawDropEffect(wxGenericTreeItem *item)
2925 {
2926 if ( item )
2927 {
2928 if ( item->HasPlus() )
2929 {
2930 // it's a folder, indicate it by a border
2931 DrawBorder(item);
2932 }
2933 else
2934 {
2935 // draw a line under the drop target because the item will be
2936 // dropped there
2937 DrawLine(item, !m_dropEffectAboveItem );
2938 }
2939
2940 SetCursor(*wxSTANDARD_CURSOR);
2941 }
2942 else
2943 {
2944 // can't drop here
2945 SetCursor(wxCURSOR_NO_ENTRY);
2946 }
2947 }
2948
2949 void wxGenericTreeCtrl::DrawBorder(const wxTreeItemId &item)
2950 {
2951 wxCHECK_RET( item.IsOk(), "invalid item in wxGenericTreeCtrl::DrawLine" );
2952
2953 wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
2954
2955 if (m_dndEffect == NoEffect)
2956 {
2957 m_dndEffect = BorderEffect;
2958 m_dndEffectItem = i;
2959 }
2960 else
2961 {
2962 m_dndEffect = NoEffect;
2963 m_dndEffectItem = NULL;
2964 }
2965
2966 wxRect rect( i->GetX()-1, i->GetY()-1, i->GetWidth()+2, GetLineHeight(i)+2 );
2967 CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y );
2968 RefreshRect( rect );
2969 }
2970
2971 void wxGenericTreeCtrl::DrawLine(const wxTreeItemId &item, bool below)
2972 {
2973 wxCHECK_RET( item.IsOk(), "invalid item in wxGenericTreeCtrl::DrawLine" );
2974
2975 wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
2976
2977 if (m_dndEffect == NoEffect)
2978 {
2979 if (below)
2980 m_dndEffect = BelowEffect;
2981 else
2982 m_dndEffect = AboveEffect;
2983 m_dndEffectItem = i;
2984 }
2985 else
2986 {
2987 m_dndEffect = NoEffect;
2988 m_dndEffectItem = NULL;
2989 }
2990
2991 wxRect rect( i->GetX()-1, i->GetY()-1, i->GetWidth()+2, GetLineHeight(i)+2 );
2992 CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y );
2993 RefreshRect( rect );
2994 }
2995
2996 // -----------------------------------------------------------------------------
2997 // wxWidgets callbacks
2998 // -----------------------------------------------------------------------------
2999
3000 void wxGenericTreeCtrl::OnSize( wxSizeEvent &event )
3001 {
3002 #ifdef __WXGTK__
3003 if (HasFlag( wxTR_FULL_ROW_HIGHLIGHT) && m_current)
3004 RefreshLine( m_current );
3005 #endif
3006
3007 event.Skip(true);
3008 }
3009
3010 void wxGenericTreeCtrl::OnPaint( wxPaintEvent &WXUNUSED(event) )
3011 {
3012 wxPaintDC dc(this);
3013 PrepareDC( dc );
3014
3015 if ( !m_anchor)
3016 return;
3017
3018 dc.SetFont( m_normalFont );
3019 dc.SetPen( m_dottedPen );
3020
3021 // this is now done dynamically
3022 //if(GetImageList() == NULL)
3023 // m_lineHeight = (int)(dc.GetCharHeight() + 4);
3024
3025 int y = 2;
3026 PaintLevel( m_anchor, dc, 0, y );
3027 }
3028
3029 void wxGenericTreeCtrl::OnSetFocus( wxFocusEvent &event )
3030 {
3031 m_hasFocus = true;
3032
3033 RefreshSelected();
3034
3035 event.Skip();
3036 }
3037
3038 void wxGenericTreeCtrl::OnKillFocus( wxFocusEvent &event )
3039 {
3040 m_hasFocus = false;
3041
3042 RefreshSelected();
3043
3044 event.Skip();
3045 }
3046
3047 void wxGenericTreeCtrl::OnKeyDown( wxKeyEvent &event )
3048 {
3049 // send a tree event
3050 wxTreeEvent te( wxEVT_TREE_KEY_DOWN, this);
3051 te.m_evtKey = event;
3052 if ( GetEventHandler()->ProcessEvent( te ) )
3053 return;
3054
3055 event.Skip();
3056 }
3057
3058 void wxGenericTreeCtrl::OnChar( wxKeyEvent &event )
3059 {
3060 if ( (m_current == 0) || (m_key_current == 0) )
3061 {
3062 event.Skip();
3063 return;
3064 }
3065
3066 // how should the selection work for this event?
3067 bool is_multiple, extended_select, unselect_others;
3068 EventFlagsToSelType(GetWindowStyleFlag(),
3069 event.ShiftDown(),
3070 event.CmdDown(),
3071 is_multiple, extended_select, unselect_others);
3072
3073 if (GetLayoutDirection() == wxLayout_RightToLeft)
3074 {
3075 if (event.GetKeyCode() == WXK_RIGHT)
3076 event.m_keyCode = WXK_LEFT;
3077 else if (event.GetKeyCode() == WXK_LEFT)
3078 event.m_keyCode = WXK_RIGHT;
3079 }
3080
3081 // + : Expand
3082 // - : Collaspe
3083 // * : Expand all/Collapse all
3084 // ' ' | return : activate
3085 // up : go up (not last children!)
3086 // down : go down
3087 // left : go to parent
3088 // right : open if parent and go next
3089 // home : go to root
3090 // end : go to last item without opening parents
3091 // alnum : start or continue searching for the item with this prefix
3092 int keyCode = event.GetKeyCode();
3093
3094 #ifdef __WXOSX__
3095 // Make the keys work as they do in the native control:
3096 // right => expand
3097 // left => collapse if current item is expanded
3098 if (keyCode == WXK_RIGHT)
3099 {
3100 keyCode = '+';
3101 }
3102 else if (keyCode == WXK_LEFT && IsExpanded(m_current))
3103 {
3104 keyCode = '-';
3105 }
3106 #endif // __WXOSX__
3107
3108 switch ( keyCode )
3109 {
3110 case '+':
3111 case WXK_ADD:
3112 if (m_current->HasPlus() && !IsExpanded(m_current))
3113 {
3114 Expand(m_current);
3115 }
3116 break;
3117
3118 case '*':
3119 case WXK_MULTIPLY:
3120 if ( !IsExpanded(m_current) )
3121 {
3122 // expand all
3123 ExpandAllChildren(m_current);
3124 break;
3125 }
3126 //else: fall through to Collapse() it
3127
3128 case '-':
3129 case WXK_SUBTRACT:
3130 if (IsExpanded(m_current))
3131 {
3132 Collapse(m_current);
3133 }
3134 break;
3135
3136 case WXK_MENU:
3137 {
3138 // Use the item's bounding rectangle to determine position for
3139 // the event
3140 wxRect ItemRect;
3141 GetBoundingRect(m_current, ItemRect, true);
3142
3143 wxTreeEvent
3144 eventMenu(wxEVT_TREE_ITEM_MENU, this, m_current);
3145 // Use the left edge, vertical middle
3146 eventMenu.m_pointDrag = wxPoint(ItemRect.GetX(),
3147 ItemRect.GetY() +
3148 ItemRect.GetHeight() / 2);
3149 GetEventHandler()->ProcessEvent( eventMenu );
3150 }
3151 break;
3152
3153 case ' ':
3154 case WXK_RETURN:
3155 if ( !event.HasModifiers() )
3156 {
3157 wxTreeEvent
3158 eventAct(wxEVT_TREE_ITEM_ACTIVATED, this, m_current);
3159 GetEventHandler()->ProcessEvent( eventAct );
3160 }
3161
3162 // in any case, also generate the normal key event for this key,
3163 // even if we generated the ACTIVATED event above: this is what
3164 // wxMSW does and it makes sense because you might not want to
3165 // process ACTIVATED event at all and handle Space and Return
3166 // directly (and differently) which would be impossible otherwise
3167 event.Skip();
3168 break;
3169
3170 // up goes to the previous sibling or to the last
3171 // of its children if it's expanded
3172 case WXK_UP:
3173 {
3174 wxTreeItemId prev = GetPrevSibling( m_key_current );
3175 if (!prev)
3176 {
3177 prev = GetItemParent( m_key_current );
3178 if ((prev == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT))
3179 {
3180 break; // don't go to root if it is hidden
3181 }
3182 if (prev)
3183 {
3184 wxTreeItemIdValue cookie;
3185 wxTreeItemId current = m_key_current;
3186 // TODO: Huh? If we get here, we'd better be the first
3187 // child of our parent. How else could it be?
3188 if (current == GetFirstChild( prev, cookie ))
3189 {
3190 // otherwise we return to where we came from
3191 DoSelectItem(prev,
3192 unselect_others,
3193 extended_select);
3194 m_key_current = (wxGenericTreeItem*) prev.m_pItem;
3195 break;
3196 }
3197 }
3198 }
3199 if (prev)
3200 {
3201 while ( IsExpanded(prev) && HasChildren(prev) )
3202 {
3203 wxTreeItemId child = GetLastChild(prev);
3204 if ( child )
3205 {
3206 prev = child;
3207 }
3208 }
3209
3210 DoSelectItem( prev, unselect_others, extended_select );
3211 m_key_current=(wxGenericTreeItem*) prev.m_pItem;
3212 }
3213 }
3214 break;
3215
3216 // left arrow goes to the parent
3217 case WXK_LEFT:
3218 {
3219 wxTreeItemId prev = GetItemParent( m_current );
3220 if ((prev == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT))
3221 {
3222 // don't go to root if it is hidden
3223 prev = GetPrevSibling( m_current );
3224 }
3225 if (prev)
3226 {
3227 DoSelectItem( prev, unselect_others, extended_select );
3228 }
3229 }
3230 break;
3231
3232 case WXK_RIGHT:
3233 // this works the same as the down arrow except that we
3234 // also expand the item if it wasn't expanded yet
3235 if (m_current != GetRootItem().m_pItem || !HasFlag(wxTR_HIDE_ROOT))
3236 Expand(m_current);
3237 //else: don't try to expand hidden root item (which can be the
3238 // current one when the tree is empty)
3239
3240 // fall through
3241
3242 case WXK_DOWN:
3243 {
3244 if (IsExpanded(m_key_current) && HasChildren(m_key_current))
3245 {
3246 wxTreeItemIdValue cookie;
3247 wxTreeItemId child = GetFirstChild( m_key_current, cookie );
3248 if ( !child )
3249 break;
3250
3251 DoSelectItem( child, unselect_others, extended_select );
3252 m_key_current=(wxGenericTreeItem*) child.m_pItem;
3253 }
3254 else
3255 {
3256 wxTreeItemId next = GetNextSibling( m_key_current );
3257 if (!next)
3258 {
3259 wxTreeItemId current = m_key_current;
3260 while (current.IsOk() && !next)
3261 {
3262 current = GetItemParent( current );
3263 if (current) next = GetNextSibling( current );
3264 }
3265 }
3266 if (next)
3267 {
3268 DoSelectItem( next, unselect_others, extended_select );
3269 m_key_current=(wxGenericTreeItem*) next.m_pItem;
3270 }
3271 }
3272 }
3273 break;
3274
3275 // <End> selects the last visible tree item
3276 case WXK_END:
3277 {
3278 wxTreeItemId last = GetRootItem();
3279
3280 while ( last.IsOk() && IsExpanded(last) )
3281 {
3282 wxTreeItemId lastChild = GetLastChild(last);
3283
3284 // it may happen if the item was expanded but then all of
3285 // its children have been deleted - so IsExpanded() returned
3286 // true, but GetLastChild() returned invalid item
3287 if ( !lastChild )
3288 break;
3289
3290 last = lastChild;
3291 }
3292
3293 if ( last.IsOk() )
3294 {
3295 DoSelectItem( last, unselect_others, extended_select );
3296 }
3297 }
3298 break;
3299
3300 // <Home> selects the root item
3301 case WXK_HOME:
3302 {
3303 wxTreeItemId prev = GetRootItem();
3304 if (!prev)
3305 break;
3306
3307 if ( HasFlag(wxTR_HIDE_ROOT) )
3308 {
3309 wxTreeItemIdValue cookie;
3310 prev = GetFirstChild(prev, cookie);
3311 if (!prev)
3312 break;
3313 }
3314
3315 DoSelectItem( prev, unselect_others, extended_select );
3316 }
3317 break;
3318
3319 default:
3320 // do not use wxIsalnum() here
3321 if ( !event.HasModifiers() &&
3322 ((keyCode >= '0' && keyCode <= '9') ||
3323 (keyCode >= 'a' && keyCode <= 'z') ||
3324 (keyCode >= 'A' && keyCode <= 'Z') ||
3325 (keyCode == '_')))
3326 {
3327 // find the next item starting with the given prefix
3328 wxChar ch = (wxChar)keyCode;
3329 wxTreeItemId id;
3330
3331 // if the same character is typed multiple times then go to the
3332 // next entry starting with that character instead of searching
3333 // for an item starting with multiple copies of this character,
3334 // this is more useful and is how it works under Windows.
3335 if ( m_findPrefix.length() == 1 && m_findPrefix[0] == ch )
3336 {
3337 id = FindItem(m_current, ch);
3338 }
3339 else
3340 {
3341 const wxString newPrefix(m_findPrefix + ch);
3342 id = FindItem(m_current, newPrefix);
3343 if ( id.IsOk() )
3344 m_findPrefix = newPrefix;
3345 }
3346
3347 // also start the timer to reset the current prefix if the user
3348 // doesn't press any more alnum keys soon -- we wouldn't want
3349 // to use this prefix for a new item search
3350 if ( !m_findTimer )
3351 {
3352 m_findTimer = new wxTreeFindTimer(this);
3353 }
3354
3355 // Notice that we should start the timer even if we didn't find
3356 // anything to make sure we reset the search state later.
3357 m_findTimer->Start(wxTreeFindTimer::DELAY, wxTIMER_ONE_SHOT);
3358
3359 if ( id.IsOk() )
3360 {
3361 SelectItem(id);
3362
3363 // Reset the bell flag if it had been temporarily disabled
3364 // before.
3365 if ( m_findBell )
3366 m_findBell = 1;
3367 }
3368 else // No such item
3369 {
3370 // Signal it with a bell if enabled.
3371 if ( m_findBell == 1 )
3372 {
3373 ::wxBell();
3374
3375 // Disable it for the next unsuccessful match, we only
3376 // beep once, this is usually enough and continuing to
3377 // do it would be annoying.
3378 m_findBell = -1;
3379 }
3380 }
3381 }
3382 else
3383 {
3384 event.Skip();
3385 }
3386 }
3387 }
3388
3389 wxTreeItemId
3390 wxGenericTreeCtrl::DoTreeHitTest(const wxPoint& point, int& flags) const
3391 {
3392 int w, h;
3393 GetSize(&w, &h);
3394 flags=0;
3395 if (point.x<0) flags |= wxTREE_HITTEST_TOLEFT;
3396 if (point.x>w) flags |= wxTREE_HITTEST_TORIGHT;
3397 if (point.y<0) flags |= wxTREE_HITTEST_ABOVE;
3398 if (point.y>h) flags |= wxTREE_HITTEST_BELOW;
3399 if (flags) return wxTreeItemId();
3400
3401 if (m_anchor == NULL)
3402 {
3403 flags = wxTREE_HITTEST_NOWHERE;
3404 return wxTreeItemId();
3405 }
3406
3407 wxGenericTreeItem *hit = m_anchor->HitTest(CalcUnscrolledPosition(point),
3408 this, flags, 0);
3409 if (hit == NULL)
3410 {
3411 flags = wxTREE_HITTEST_NOWHERE;
3412 return wxTreeItemId();
3413 }
3414 return hit;
3415 }
3416
3417 // get the bounding rectangle of the item (or of its label only)
3418 bool wxGenericTreeCtrl::GetBoundingRect(const wxTreeItemId& item,
3419 wxRect& rect,
3420 bool textOnly) const
3421 {
3422 wxCHECK_MSG( item.IsOk(), false,
3423 "invalid item in wxGenericTreeCtrl::GetBoundingRect" );
3424
3425 wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
3426
3427 if ( textOnly )
3428 {
3429 int image_h = 0, image_w = 0;
3430 int image = ((wxGenericTreeItem*) item.m_pItem)->GetCurrentImage();
3431 if ( image != NO_IMAGE && m_imageListNormal )
3432 {
3433 m_imageListNormal->GetSize( image, image_w, image_h );
3434 image_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
3435 }
3436
3437 int state_h = 0, state_w = 0;
3438 int state = ((wxGenericTreeItem*) item.m_pItem)->GetState();
3439 if ( state != wxTREE_ITEMSTATE_NONE && m_imageListState )
3440 {
3441 m_imageListState->GetSize( state, state_w, state_h );
3442 if ( image_w != 0 )
3443 state_w += MARGIN_BETWEEN_STATE_AND_IMAGE;
3444 else
3445 state_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
3446 }
3447
3448 rect.x = i->GetX() + state_w + image_w;
3449 rect.width = i->GetWidth() - state_w - image_w;
3450
3451 }
3452 else // the entire line
3453 {
3454 rect.x = 0;
3455 rect.width = GetClientSize().x;
3456 }
3457
3458 rect.y = i->GetY();
3459 rect.height = GetLineHeight(i);
3460
3461 // we have to return the logical coordinates, not physical ones
3462 rect.SetTopLeft(CalcScrolledPosition(rect.GetTopLeft()));
3463
3464 return true;
3465 }
3466
3467 wxTextCtrl *wxGenericTreeCtrl::EditLabel(const wxTreeItemId& item,
3468 wxClassInfo * WXUNUSED(textCtrlClass))
3469 {
3470 wxCHECK_MSG( item.IsOk(), NULL, wxT("can't edit an invalid item") );
3471
3472 wxGenericTreeItem *itemEdit = (wxGenericTreeItem *)item.m_pItem;
3473
3474 wxTreeEvent te(wxEVT_TREE_BEGIN_LABEL_EDIT, this, itemEdit);
3475 if ( GetEventHandler()->ProcessEvent( te ) && !te.IsAllowed() )
3476 {
3477 // vetoed by user
3478 return NULL;
3479 }
3480
3481 // We have to call this here because the label in
3482 // question might just have been added and no screen
3483 // update taken place.
3484 if ( m_dirty )
3485 DoDirtyProcessing();
3486
3487 // TODO: use textCtrlClass here to create the control of correct class
3488 m_textCtrl = new wxTreeTextCtrl(this, itemEdit);
3489
3490 m_textCtrl->SetFocus();
3491
3492 return m_textCtrl;
3493 }
3494
3495 // returns a pointer to the text edit control if the item is being
3496 // edited, NULL otherwise (it's assumed that no more than one item may
3497 // be edited simultaneously)
3498 wxTextCtrl* wxGenericTreeCtrl::GetEditControl() const
3499 {
3500 return m_textCtrl;
3501 }
3502
3503 void wxGenericTreeCtrl::EndEditLabel(const wxTreeItemId& WXUNUSED(item),
3504 bool discardChanges)
3505 {
3506 wxCHECK_RET( m_textCtrl, wxT("not editing label") );
3507
3508 m_textCtrl->EndEdit(discardChanges);
3509 }
3510
3511 bool wxGenericTreeCtrl::OnRenameAccept(wxGenericTreeItem *item,
3512 const wxString& value)
3513 {
3514 wxTreeEvent le(wxEVT_TREE_END_LABEL_EDIT, this, item);
3515 le.m_label = value;
3516 le.m_editCancelled = false;
3517
3518 return !GetEventHandler()->ProcessEvent( le ) || le.IsAllowed();
3519 }
3520
3521 void wxGenericTreeCtrl::OnRenameCancelled(wxGenericTreeItem *item)
3522 {
3523 // let owner know that the edit was cancelled
3524 wxTreeEvent le(wxEVT_TREE_END_LABEL_EDIT, this, item);
3525 le.m_label = wxEmptyString;
3526 le.m_editCancelled = true;
3527
3528 GetEventHandler()->ProcessEvent( le );
3529 }
3530
3531 void wxGenericTreeCtrl::OnRenameTimer()
3532 {
3533 EditLabel( m_current );
3534 }
3535
3536 void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event )
3537 {
3538 if ( !m_anchor )return;
3539
3540 wxPoint pt = CalcUnscrolledPosition(event.GetPosition());
3541
3542 // Is the mouse over a tree item button?
3543 int flags = 0;
3544 wxGenericTreeItem *thisItem = m_anchor->HitTest(pt, this, flags, 0);
3545 wxGenericTreeItem *underMouse = thisItem;
3546 #if wxUSE_TOOLTIPS
3547 bool underMouseChanged = (underMouse != m_underMouse) ;
3548 #endif // wxUSE_TOOLTIPS
3549
3550 if ((underMouse) &&
3551 (flags & wxTREE_HITTEST_ONITEMBUTTON) &&
3552 (!event.LeftIsDown()) &&
3553 (!m_isDragging) &&
3554 (!m_renameTimer || !m_renameTimer->IsRunning()))
3555 {
3556 }
3557 else
3558 {
3559 underMouse = NULL;
3560 }
3561
3562 if (underMouse != m_underMouse)
3563 {
3564 if (m_underMouse)
3565 {
3566 // unhighlight old item
3567 wxGenericTreeItem *tmp = m_underMouse;
3568 m_underMouse = NULL;
3569 RefreshLine( tmp );
3570 }
3571
3572 m_underMouse = underMouse;
3573 if (m_underMouse)
3574 RefreshLine( m_underMouse );
3575 }
3576
3577 #if wxUSE_TOOLTIPS
3578 // Determines what item we are hovering over and need a tooltip for
3579 wxTreeItemId hoverItem = thisItem;
3580
3581 // We do not want a tooltip if we are dragging, or if the rename timer is
3582 // running
3583 if ( underMouseChanged &&
3584 hoverItem.IsOk() &&
3585 !m_isDragging &&
3586 (!m_renameTimer || !m_renameTimer->IsRunning()) )
3587 {
3588 // Ask the tree control what tooltip (if any) should be shown
3589 wxTreeEvent
3590 hevent(wxEVT_TREE_ITEM_GETTOOLTIP, this, hoverItem);
3591
3592 // setting a tooltip upon leaving a view is getting the tooltip displayed
3593 // on the neighbouring view ...
3594 #ifdef __WXOSX__
3595 if ( event.Leaving() )
3596 SetToolTip(NULL);
3597 else
3598 #endif
3599 if ( GetEventHandler()->ProcessEvent(hevent) )
3600 {
3601 // If the user permitted the tooltip change, update it, otherwise
3602 // remove any old tooltip we might have.
3603 if ( hevent.IsAllowed() )
3604 SetToolTip(hevent.m_label);
3605 else
3606 SetToolTip(NULL);
3607 }
3608 }
3609 #endif
3610
3611 // we process left mouse up event (enables in-place edit), middle/right down
3612 // (pass to the user code), left dbl click (activate item) and
3613 // dragging/moving events for items drag-and-drop
3614 if ( !(event.LeftDown() ||
3615 event.LeftUp() ||
3616 event.MiddleDown() ||
3617 event.RightDown() ||
3618 event.LeftDClick() ||
3619 event.Dragging() ||
3620 ((event.Moving() || event.RightUp()) && m_isDragging)) )
3621 {
3622 event.Skip();
3623
3624 return;
3625 }
3626
3627
3628 flags = 0;
3629 wxGenericTreeItem *item = m_anchor->HitTest(pt, this, flags, 0);
3630
3631 if ( event.Dragging() && !m_isDragging )
3632 {
3633 if (m_dragCount == 0)
3634 m_dragStart = pt;
3635
3636 m_dragCount++;
3637
3638 if (m_dragCount != 3)
3639 {
3640 // wait until user drags a bit further...
3641 return;
3642 }
3643
3644 wxEventType command = event.RightIsDown()
3645 ? wxEVT_TREE_BEGIN_RDRAG
3646 : wxEVT_TREE_BEGIN_DRAG;
3647
3648 wxTreeEvent nevent(command, this, m_current);
3649 nevent.SetPoint(CalcScrolledPosition(pt));
3650
3651 // by default the dragging is not supported, the user code must
3652 // explicitly allow the event for it to take place
3653 nevent.Veto();
3654
3655 if ( GetEventHandler()->ProcessEvent(nevent) && nevent.IsAllowed() )
3656 {
3657 // we're going to drag this item
3658 m_isDragging = true;
3659
3660 // remember the old cursor because we will change it while
3661 // dragging
3662 m_oldCursor = m_cursor;
3663
3664 // in a single selection control, hide the selection temporarily
3665 if ( !(GetWindowStyleFlag() & wxTR_MULTIPLE) )
3666 {
3667 m_oldSelection = (wxGenericTreeItem*) GetSelection().m_pItem;
3668
3669 if ( m_oldSelection )
3670 {
3671 m_oldSelection->SetHilight(false);
3672 RefreshLine(m_oldSelection);
3673 }
3674 }
3675
3676 CaptureMouse();
3677 }
3678 }
3679 else if ( event.Dragging() )
3680 {
3681 if ( item != m_dropTarget )
3682 {
3683 // unhighlight the previous drop target
3684 DrawDropEffect(m_dropTarget);
3685
3686 m_dropTarget = item;
3687
3688 // highlight the current drop target if any
3689 DrawDropEffect(m_dropTarget);
3690
3691 #if defined(__WXMSW__) || defined(__WXMAC__) || defined(__WXGTK20__)
3692 Update();
3693 #else
3694 // TODO: remove this call or use wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI)
3695 // instead (needs to be tested!)
3696 wxYieldIfNeeded();
3697 #endif
3698 }
3699 }
3700 else if ( (event.LeftUp() || event.RightUp()) && m_isDragging )
3701 {
3702 ReleaseMouse();
3703
3704 // erase the highlighting
3705 DrawDropEffect(m_dropTarget);
3706
3707 if ( m_oldSelection )
3708 {
3709 m_oldSelection->SetHilight(true);
3710 RefreshLine(m_oldSelection);
3711 m_oldSelection = NULL;
3712 }
3713
3714 // generate the drag end event
3715 wxTreeEvent eventEndDrag(wxEVT_TREE_END_DRAG, this, item);
3716
3717 eventEndDrag.m_pointDrag = CalcScrolledPosition(pt);
3718
3719 (void)GetEventHandler()->ProcessEvent(eventEndDrag);
3720
3721 m_isDragging = false;
3722 m_dropTarget = NULL;
3723
3724 SetCursor(m_oldCursor);
3725
3726 #if defined( __WXMSW__ ) || defined(__WXMAC__) || defined(__WXGTK20__)
3727 Update();
3728 #else
3729 // TODO: remove this call or use wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI)
3730 // instead (needs to be tested!)
3731 wxYieldIfNeeded();
3732 #endif
3733 }
3734 else
3735 {
3736 // If we got to this point, we are not dragging or moving the mouse.
3737 // Because the code in carbon/toplevel.cpp will only set focus to the
3738 // tree if we skip for EVT_LEFT_DOWN, we MUST skip this event here for
3739 // focus to work.
3740 // We skip even if we didn't hit an item because we still should
3741 // restore focus to the tree control even if we didn't exactly hit an
3742 // item.
3743 if ( event.LeftDown() )
3744 {
3745 event.Skip();
3746 }
3747
3748 // here we process only the messages which happen on tree items
3749
3750 m_dragCount = 0;
3751
3752 if (item == NULL) return; /* we hit the blank area */
3753
3754 if ( event.RightDown() )
3755 {
3756 // If the item is already selected, do not update the selection.
3757 // Multi-selections should not be cleared if a selected item is
3758 // clicked.
3759 if (!IsSelected(item))
3760 {
3761 DoSelectItem(item, true, false);
3762 }
3763
3764 wxTreeEvent
3765 nevent(wxEVT_TREE_ITEM_RIGHT_CLICK, this, item);
3766 nevent.m_pointDrag = CalcScrolledPosition(pt);
3767 event.Skip(!GetEventHandler()->ProcessEvent(nevent));
3768
3769 // Consistent with MSW (for now), send the ITEM_MENU *after*
3770 // the RIGHT_CLICK event. TODO: This behaviour may change.
3771 wxTreeEvent nevent2(wxEVT_TREE_ITEM_MENU, this, item);
3772 nevent2.m_pointDrag = CalcScrolledPosition(pt);
3773 GetEventHandler()->ProcessEvent(nevent2);
3774 }
3775 else if ( event.MiddleDown() )
3776 {
3777 wxTreeEvent
3778 nevent(wxEVT_TREE_ITEM_MIDDLE_CLICK, this, item);
3779 nevent.m_pointDrag = CalcScrolledPosition(pt);
3780 event.Skip(!GetEventHandler()->ProcessEvent(nevent));
3781 }
3782 else if ( event.LeftUp() )
3783 {
3784 if (flags & wxTREE_HITTEST_ONITEMSTATEICON)
3785 {
3786 wxTreeEvent
3787 nevent(wxEVT_TREE_STATE_IMAGE_CLICK, this, item);
3788 GetEventHandler()->ProcessEvent(nevent);
3789 }
3790
3791 // this facilitates multiple-item drag-and-drop
3792
3793 if ( /* item && */ HasFlag(wxTR_MULTIPLE))
3794 {
3795 wxArrayTreeItemIds selections;
3796 size_t count = GetSelections(selections);
3797
3798 if (count > 1 &&
3799 !event.CmdDown() &&
3800 !event.ShiftDown())
3801 {
3802 DoSelectItem(item, true, false);
3803 }
3804 }
3805
3806 if ( m_lastOnSame )
3807 {
3808 if ( (item == m_current) &&
3809 (flags & wxTREE_HITTEST_ONITEMLABEL) &&
3810 HasFlag(wxTR_EDIT_LABELS) )
3811 {
3812 if ( m_renameTimer )
3813 {
3814 if ( m_renameTimer->IsRunning() )
3815 m_renameTimer->Stop();
3816 }
3817 else
3818 {
3819 m_renameTimer = new wxTreeRenameTimer( this );
3820 }
3821
3822 m_renameTimer->Start( wxTreeRenameTimer::DELAY, true );
3823 }
3824
3825 m_lastOnSame = false;
3826 }
3827 }
3828 else // !RightDown() && !MiddleDown() && !LeftUp()
3829 {
3830 // ==> LeftDown() || LeftDClick()
3831 if ( event.LeftDown() )
3832 {
3833 // If we click on an already selected item but do it to return
3834 // the focus to the control, it shouldn't start editing the
3835 // item label because it's too easy to start editing
3836 // accidentally (and also because nobody else does it like
3837 // this). So only set this flag, used to decide whether we
3838 // should start editing the label later, if we already have
3839 // focus.
3840 m_lastOnSame = item == m_current && HasFocus();
3841 }
3842
3843 if ( flags & wxTREE_HITTEST_ONITEMBUTTON )
3844 {
3845 // only toggle the item for a single click, double click on
3846 // the button doesn't do anything (it toggles the item twice)
3847 if ( event.LeftDown() )
3848 {
3849 Toggle( item );
3850 }
3851
3852 // don't select the item if the button was clicked
3853 return;
3854 }
3855
3856
3857 // clear the previously selected items, if the
3858 // user clicked outside of the present selection.
3859 // otherwise, perform the deselection on mouse-up.
3860 // this allows multiple drag and drop to work.
3861 // but if Cmd is down, toggle selection of the clicked item
3862 if (!IsSelected(item) || event.CmdDown())
3863 {
3864 // how should the selection work for this event?
3865 bool is_multiple, extended_select, unselect_others;
3866 EventFlagsToSelType(GetWindowStyleFlag(),
3867 event.ShiftDown(),
3868 event.CmdDown(),
3869 is_multiple,
3870 extended_select,
3871 unselect_others);
3872
3873 DoSelectItem(item, unselect_others, extended_select);
3874 }
3875
3876
3877 // For some reason, Windows isn't recognizing a left double-click,
3878 // so we need to simulate it here. Allow 200 milliseconds for now.
3879 if ( event.LeftDClick() )
3880 {
3881 // double clicking should not start editing the item label
3882 if ( m_renameTimer )
3883 m_renameTimer->Stop();
3884
3885 m_lastOnSame = false;
3886
3887 // send activate event first
3888 wxTreeEvent
3889 nevent(wxEVT_TREE_ITEM_ACTIVATED, this, item);
3890 nevent.m_pointDrag = CalcScrolledPosition(pt);
3891 if ( !GetEventHandler()->ProcessEvent( nevent ) )
3892 {
3893 // if the user code didn't process the activate event,
3894 // handle it ourselves by toggling the item when it is
3895 // double clicked
3896 if ( item->HasPlus() )
3897 {
3898 Toggle(item);
3899 }
3900 }
3901 }
3902 }
3903 }
3904 }
3905
3906 void wxGenericTreeCtrl::OnInternalIdle()
3907 {
3908 wxWindow::OnInternalIdle();
3909
3910 // Check if we need to select the root item
3911 // because nothing else has been selected.
3912 // Delaying it means that we can invoke event handlers
3913 // as required, when a first item is selected.
3914 if (!HasFlag(wxTR_MULTIPLE) && !GetSelection().IsOk())
3915 {
3916 if (m_select_me)
3917 SelectItem(m_select_me);
3918 else if (GetRootItem().IsOk())
3919 SelectItem(GetRootItem());
3920 }
3921
3922 // after all changes have been done to the tree control,
3923 // actually redraw the tree when everything is over
3924 if (m_dirty)
3925 DoDirtyProcessing();
3926 }
3927
3928 void
3929 wxGenericTreeCtrl::CalculateLevel(wxGenericTreeItem *item,
3930 wxDC &dc,
3931 int level,
3932 int &y )
3933 {
3934 int x = level*m_indent;
3935 if (!HasFlag(wxTR_HIDE_ROOT))
3936 {
3937 x += m_indent;
3938 }
3939 else if (level == 0)
3940 {
3941 // a hidden root is not evaluated, but its
3942 // children are always calculated
3943 goto Recurse;
3944 }
3945
3946 item->CalculateSize(this, dc);
3947
3948 // set its position
3949 item->SetX( x+m_spacing );
3950 item->SetY( y );
3951 y += GetLineHeight(item);
3952
3953 if ( !item->IsExpanded() )
3954 {
3955 // we don't need to calculate collapsed branches
3956 return;
3957 }
3958
3959 Recurse:
3960 wxArrayGenericTreeItems& children = item->GetChildren();
3961 size_t n, count = children.GetCount();
3962 ++level;
3963 for (n = 0; n < count; ++n )
3964 CalculateLevel( children[n], dc, level, y ); // recurse
3965 }
3966
3967 void wxGenericTreeCtrl::CalculatePositions()
3968 {
3969 if ( !m_anchor ) return;
3970
3971 wxClientDC dc(this);
3972 PrepareDC( dc );
3973
3974 dc.SetFont( m_normalFont );
3975
3976 dc.SetPen( m_dottedPen );
3977 //if(GetImageList() == NULL)
3978 // m_lineHeight = (int)(dc.GetCharHeight() + 4);
3979
3980 int y = 2;
3981 CalculateLevel( m_anchor, dc, 0, y ); // start recursion
3982 }
3983
3984 void wxGenericTreeCtrl::Refresh(bool eraseBackground, const wxRect *rect)
3985 {
3986 if ( !IsFrozen() )
3987 wxTreeCtrlBase::Refresh(eraseBackground, rect);
3988 }
3989
3990 void wxGenericTreeCtrl::RefreshSubtree(wxGenericTreeItem *item)
3991 {
3992 if (m_dirty || IsFrozen() )
3993 return;
3994
3995 wxSize client = GetClientSize();
3996
3997 wxRect rect;
3998 CalcScrolledPosition(0, item->GetY(), NULL, &rect.y);
3999 rect.width = client.x;
4000 rect.height = client.y;
4001
4002 Refresh(true, &rect);
4003
4004 AdjustMyScrollbars();
4005 }
4006
4007 void wxGenericTreeCtrl::RefreshLine( wxGenericTreeItem *item )
4008 {
4009 if (m_dirty || IsFrozen() )
4010 return;
4011
4012 wxRect rect;
4013 CalcScrolledPosition(0, item->GetY(), NULL, &rect.y);
4014 rect.width = GetClientSize().x;
4015 rect.height = GetLineHeight(item); //dc.GetCharHeight() + 6;
4016
4017 Refresh(true, &rect);
4018 }
4019
4020 void wxGenericTreeCtrl::RefreshSelected()
4021 {
4022 if (IsFrozen())
4023 return;
4024
4025 // TODO: this is awfully inefficient, we should keep the list of all
4026 // selected items internally, should be much faster
4027 if ( m_anchor )
4028 RefreshSelectedUnder(m_anchor);
4029 }
4030
4031 void wxGenericTreeCtrl::RefreshSelectedUnder(wxGenericTreeItem *item)
4032 {
4033 if (IsFrozen())
4034 return;
4035
4036 if ( item->IsSelected() )
4037 RefreshLine(item);
4038
4039 const wxArrayGenericTreeItems& children = item->GetChildren();
4040 size_t count = children.GetCount();
4041 for ( size_t n = 0; n < count; n++ )
4042 {
4043 RefreshSelectedUnder(children[n]);
4044 }
4045 }
4046
4047 void wxGenericTreeCtrl::DoThaw()
4048 {
4049 wxTreeCtrlBase::DoThaw();
4050
4051 if ( m_dirty )
4052 DoDirtyProcessing();
4053 else
4054 Refresh();
4055 }
4056
4057 // ----------------------------------------------------------------------------
4058 // changing colours: we need to refresh the tree control
4059 // ----------------------------------------------------------------------------
4060
4061 bool wxGenericTreeCtrl::SetBackgroundColour(const wxColour& colour)
4062 {
4063 if ( !wxWindow::SetBackgroundColour(colour) )
4064 return false;
4065
4066 Refresh();
4067
4068 return true;
4069 }
4070
4071 bool wxGenericTreeCtrl::SetForegroundColour(const wxColour& colour)
4072 {
4073 if ( !wxWindow::SetForegroundColour(colour) )
4074 return false;
4075
4076 Refresh();
4077
4078 return true;
4079 }
4080
4081 void wxGenericTreeCtrl::OnGetToolTip( wxTreeEvent &event )
4082 {
4083 #if wxUSE_TOOLTIPS
4084 wxTreeItemId itemId = event.GetItem();
4085 const wxGenericTreeItem* const pItem = (wxGenericTreeItem*)itemId.m_pItem;
4086
4087 // Check if the item fits into the client area:
4088 if ( pItem->GetX() + pItem->GetWidth() > GetClientSize().x )
4089 {
4090 // If it doesn't, show its full text in the tooltip.
4091 event.SetLabel(pItem->GetText());
4092 }
4093 else
4094 #endif // wxUSE_TOOLTIPS
4095 {
4096 // veto processing the event, nixing any tooltip
4097 event.Veto();
4098 }
4099 }
4100
4101
4102 // NOTE: If using the wxListBox visual attributes works everywhere then this can
4103 // be removed, as well as the #else case below.
4104 #define _USE_VISATTR 0
4105
4106 //static
4107 wxVisualAttributes
4108 #if _USE_VISATTR
4109 wxGenericTreeCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
4110 #else
4111 wxGenericTreeCtrl::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
4112 #endif
4113 {
4114 #if _USE_VISATTR
4115 // Use the same color scheme as wxListBox
4116 return wxListBox::GetClassDefaultAttributes(variant);
4117 #else
4118 wxVisualAttributes attr;
4119 attr.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT);
4120 attr.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX);
4121 attr.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
4122 return attr;
4123 #endif
4124 }
4125
4126 void wxGenericTreeCtrl::DoDirtyProcessing()
4127 {
4128 if (IsFrozen())
4129 return;
4130
4131 m_dirty = false;
4132
4133 CalculatePositions();
4134 Refresh();
4135 AdjustMyScrollbars();
4136 }
4137
4138 wxSize wxGenericTreeCtrl::DoGetBestSize() const
4139 {
4140 // make sure all positions are calculated as normally this only done during
4141 // idle time but we need them for base class DoGetBestSize() to return the
4142 // correct result
4143 wxConstCast(this, wxGenericTreeCtrl)->CalculatePositions();
4144
4145 wxSize size = wxTreeCtrlBase::DoGetBestSize();
4146
4147 // there seems to be an implicit extra border around the items, although
4148 // I'm not really sure where does it come from -- but without this, the
4149 // scrollbars appear in a tree with default/best size
4150 size.IncBy(4, 4);
4151
4152 // and the border has to be rounded up to a multiple of PIXELS_PER_UNIT or
4153 // scrollbars still appear
4154 const wxSize& borderSize = GetWindowBorderSize();
4155
4156 int dx = (size.x - borderSize.x) % PIXELS_PER_UNIT;
4157 if ( dx )
4158 size.x += PIXELS_PER_UNIT - dx;
4159 int dy = (size.y - borderSize.y) % PIXELS_PER_UNIT;
4160 if ( dy )
4161 size.y += PIXELS_PER_UNIT - dy;
4162
4163 // we need to update the cache too as the base class cached its own value
4164 CacheBestSize(size);
4165
4166 return size;
4167 }
4168
4169 #endif // wxUSE_TREECTRL