]> git.saurik.com Git - wxWidgets.git/blame - src/generic/treelist.cpp
No changes, just fix the file name in the header comment of wxDVC test.
[wxWidgets.git] / src / generic / treelist.cpp
CommitLineData
524cb040
VZ
1///////////////////////////////////////////////////////////////////////////////
2// Name: src/generic/treelist.cpp
3// Purpose: Generic wxTreeListCtrl implementation.
4// Author: Vadim Zeitlin
5// Created: 2011-08-19
6// RCS-ID: $Id: wxhead.cpp,v 1.11 2010-04-22 12:44:51 zeitlin Exp $
7// Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
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#ifndef WX_PRECOMP
27#endif // WX_PRECOMP
28
29#include "wx/treelist.h"
30
31#include "wx/dataview.h"
32#include "wx/renderer.h"
33#include "wx/scopedarray.h"
34#include "wx/scopedptr.h"
35
36// ----------------------------------------------------------------------------
37// Constants
38// ----------------------------------------------------------------------------
39
40const char wxTreeListCtrlNameStr[] = "wxTreeListCtrl";
41
42const wxTreeListItem wxTLI_FIRST(reinterpret_cast<wxTreeListModelNode*>(-1));
43const wxTreeListItem wxTLI_LAST(reinterpret_cast<wxTreeListModelNode*>(-2));
44
45// ----------------------------------------------------------------------------
46// wxTreeListModelNode: a node in the internal tree representation.
47// ----------------------------------------------------------------------------
48
49class wxTreeListModelNode
50{
51public:
52 wxTreeListModelNode(wxTreeListModelNode* parent,
53 const wxString& text = wxString(),
54 int imageClosed = wxWithImages::NO_IMAGE,
55 int imageOpened = wxWithImages::NO_IMAGE,
56 wxClientData* data = NULL)
57 : m_text(text),
58 m_parent(parent)
59 {
60 m_child =
61 m_next = NULL;
62
63 m_imageClosed = imageClosed;
64 m_imageOpened = imageOpened;
65
66 m_checkedState = wxCHK_UNCHECKED;
67
68 m_data = data;
69
70 m_columnsTexts = NULL;
71 }
72
73 // Destroying the node also (recursively) destroys its children.
74 ~wxTreeListModelNode()
75 {
76 for ( wxTreeListModelNode* node = m_child; node; )
77 {
78 wxTreeListModelNode* child = node;
79 node = node->m_next;
80 delete child;
81 }
82
83 delete m_data;
84
85 delete [] m_columnsTexts;
86 }
87
88
89 // Public fields for the first column text and other simple attributes:
90 // there is no need to have accessors/mutators for those as there is no
91 // encapsulation anyhow, all of those are exposed in our public API.
92 wxString m_text;
93
94 int m_imageClosed,
95 m_imageOpened;
96
97 wxCheckBoxState m_checkedState;
98
99
100 // Accessors for the fields that are not directly exposed.
101
102 // Client data is owned by us so delete the old value when setting the new
103 // one.
104 wxClientData* GetClientData() const { return m_data; }
105 void SetClientData(wxClientData* data) { delete m_data; m_data = data; }
106
107 // Setting or getting the non-first column text. Getting is simple but you
108 // need to call HasColumnsTexts() first as the column data is only
109 // allocated on demand. And when setting the text we require to be given
110 // the total number of columns as we allocate the entire array at once,
111 // this is more efficient than using dynamically-expandable wxVector that
112 // we know won't be needed as the number of columns is usually fixed. But
113 // if it does change, our OnInsertColumn() must be called.
114 //
115 // Notice the presence of -1 everywhere in these methods: this is because
116 // the text for the first column is always stored in m_text and so we don't
117 // store it in m_columnsTexts.
118
119 bool HasColumnsTexts() const { return m_columnsTexts != NULL; }
120 const wxString& GetColumnText(unsigned col) const
121 {
122 return m_columnsTexts[col - 1];
123 }
124
125 void SetColumnText(const wxString& text, unsigned col, unsigned numColumns)
126 {
127 if ( !m_columnsTexts )
128 m_columnsTexts = new wxString[numColumns - 1];
129
130 m_columnsTexts[col - 1] = text;
131 }
132
133 void OnInsertColumn(unsigned col, unsigned numColumns)
134 {
135 wxASSERT_MSG( col, "Shouldn't be called for the first column" );
136
137 // Nothing to do if we don't have any text.
138 if ( !m_columnsTexts )
139 return;
140
141 wxScopedArray<wxString> oldTexts(m_columnsTexts);
142 m_columnsTexts = new wxString[numColumns - 1];
143
144 // In the loop below n is the index in the new column texts array and m
145 // is the index in the old one.
146 for ( unsigned n = 1, m = 1; n < numColumns - 1; n++, m++ )
147 {
148 if ( n == col )
149 {
150 // Leave the new array text initially empty and just adjust the
151 // index (to compensate for "m++" done by the loop anyhow).
152 m--;
153 }
154 else // Not the newly inserted column.
155 {
156 // Copy the old text value.
157 m_columnsTexts[n - 1] = oldTexts[m - 1];
158 }
159 }
160 }
161
162
163 // Functions for modifying the tree.
164
165 // Insert the given item as the first child of this one. The parent pointer
166 // must have been already set correctly at creation and we take ownership
167 // of the pointer and will delete it later.
168 void InsertChild(wxTreeListModelNode* child)
169 {
170 wxASSERT( child->m_parent == this );
171
172 // Our previous first child becomes the next sibling of the new child.
173 child->m_next = m_child;
174 m_child = child;
175 }
176
177 // Insert the given item as our next sibling. As above, the item must have
178 // the correct parent pointer and we take ownership of it.
179 void InsertNext(wxTreeListModelNode* next)
180 {
181 wxASSERT( next->m_parent == m_parent );
182
183 next->m_next = m_next;
184 m_next = next;
185 }
186
187 // Remove the first child of this item from the tree and delete it.
188 void DeleteChild()
189 {
190 wxTreeListModelNode* const oldChild = m_child;
191 m_child = m_child->m_next;
192 delete oldChild;
193 }
194
195 // Remove the next sibling of this item from the tree and deletes it.
196 void DeleteNext()
197 {
198 wxTreeListModelNode* const oldNext = m_next;
199 m_next = m_next->m_next;
200 delete oldNext;
201 }
202
203
204 // Functions for tree traversal. All of them can return NULL.
205
206 // Only returns NULL when called on the root item.
207 wxTreeListModelNode* GetParent() const { return m_parent; }
208
209 // Returns the first child of this item.
210 wxTreeListModelNode* GetChild() const { return m_child; }
211
212 // Returns the next sibling of this item.
213 wxTreeListModelNode* GetNext() const { return m_next; }
214
215 // Unlike the previous two functions, this one is not a simple accessor
216 // (hence it's not called "GetSomething") but computes the next node after
217 // this one in tree order.
218 wxTreeListModelNode* NextInTree() const
219 {
220 if ( m_child )
221 return m_child;
222
223 if ( m_next )
224 return m_next;
225
226 // Recurse upwards until we find the next sibling.
227 for ( wxTreeListModelNode* node = m_parent; node; node = node->m_parent )
228 {
229 if ( node->m_next )
230 return node->m_next;
231 }
232
233 return NULL;
234 }
235
236
237private:
238 // The (never changing after creation) parent of this node and the possibly
239 // NULL pointers to its first child and next sibling.
240 wxTreeListModelNode* const m_parent;
241 wxTreeListModelNode* m_child;
242 wxTreeListModelNode* m_next;
243
244 // Client data pointer owned by the control. May be NULL.
245 wxClientData* m_data;
246
247 // Array of column values for all the columns except the first one. May be
248 // NULL if no values had been set for them.
249 wxString* m_columnsTexts;
250};
251
252// ----------------------------------------------------------------------------
253// wxTreeListModel: wxDataViewModel implementation used by wxTreeListCtrl.
254// ----------------------------------------------------------------------------
255
256class wxTreeListModel : public wxDataViewModel
257{
258public:
259 typedef wxTreeListModelNode Node;
260
261 // Unlike a general wxDataViewModel, this model can only be used with a
262 // single control at once. The main reason for this is that we need to
263 // support different icons for opened and closed items and the item state
264 // is associated with the control, not the model, so our GetValue() is also
265 // bound to it (otherwise, what would it return for an item expanded in one
266 // associated control and collapsed in another one?).
267 wxTreeListModel(wxTreeListCtrl* treelist);
268 virtual ~wxTreeListModel();
269
270
271 // Helpers for converting between wxDataViewItem and wxTreeListItem. These
272 // methods simply cast the pointer to/from wxDataViewItem except for the
273 // root node that we handle specially unless explicitly disabled.
274 //
275 // The advantage of using them is that they're greppable and stand out
276 // better, hopefully making the code more clear.
277 Node* FromNonRootDVI(wxDataViewItem dvi) const
278 {
279 return static_cast<Node*>(dvi.GetID());
280 }
281
282 Node* FromDVI(wxDataViewItem dvi) const
283 {
284 if ( !dvi.IsOk() )
285 return m_root;
286
287 return FromNonRootDVI(dvi);
288 }
289
290 wxDataViewItem ToNonRootDVI(Node* node) const
291 {
292 return wxDataViewItem(node);
293 }
294
295 wxDataViewItem ToDVI(Node* node) const
296 {
297 // Our root item must be represented as NULL at wxDVC level to map to
298 // its own invisible root.
299 if ( !node->GetParent() )
300 return wxDataViewItem();
301
302 return ToNonRootDVI(node);
303 }
304
305
306 // Methods called by wxTreeListCtrl.
307 void InsertColumn(unsigned col);
308
309 Node* InsertItem(Node* parent,
310 Node* previous,
311 const wxString& text,
312 int imageClosed,
313 int imageOpened,
314 wxClientData* data);
315 void DeleteItem(Node* item);
316 void DeleteAllItems();
317
318 Node* GetRootItem() const { return m_root; }
319
320 const wxString& GetItemText(Node* item, unsigned col) const;
321 void SetItemText(Node* item, unsigned col, const wxString& text);
322 void SetItemImage(Node* item, int closed, int opened);
323 wxClientData* GetItemData(Node* item) const;
324 void SetItemData(Node* item, wxClientData* data);
325
326 void CheckItem(Node* item, wxCheckBoxState checkedState);
327 void ToggleItem(wxDataViewItem item);
328
329
330 // Implement the base class pure virtual methods.
331 virtual unsigned GetColumnCount() const;
332 virtual wxString GetColumnType(unsigned col) const;
333 virtual void GetValue(wxVariant& variant,
334 const wxDataViewItem& item,
335 unsigned col) const;
336 virtual bool SetValue(const wxVariant& variant,
337 const wxDataViewItem& item,
338 unsigned col);
339 virtual wxDataViewItem GetParent(const wxDataViewItem& item) const;
340 virtual bool IsContainer(const wxDataViewItem& item) const;
341 virtual bool HasContainerColumns(const wxDataViewItem& item) const;
342 virtual unsigned GetChildren(const wxDataViewItem& item,
343 wxDataViewItemArray& children) const;
344
345private:
346 // The control we're associated with.
347 wxTreeListCtrl* const m_treelist;
348
349 // The unique invisible root element.
350 Node* const m_root;
351
352 // Number of columns we maintain.
353 unsigned m_numColumns;
354};
355
356// ============================================================================
357// wxDataViewCheckIconText[Renderer]: special renderer for our first column.
358// ============================================================================
359
360// Currently this class is private but it could be extracted and made part of
361// public API later as could be used directly with wxDataViewCtrl as well.
362namespace
363{
364
365const char* CHECK_ICON_TEXT_TYPE = "wxDataViewCheckIconText";
366
367// The value used by wxDataViewCheckIconTextRenderer
368class wxDataViewCheckIconText : public wxDataViewIconText
369{
370public:
371 wxDataViewCheckIconText(const wxString& text = wxString(),
372 const wxIcon& icon = wxNullIcon,
373 wxCheckBoxState checkedState = wxCHK_UNDETERMINED)
374 : wxDataViewIconText(text, icon),
375 m_checkedState(checkedState)
376 {
377 }
378
379 wxDataViewCheckIconText(const wxDataViewCheckIconText& other)
380 : wxDataViewIconText(other),
381 m_checkedState(other.m_checkedState)
382 {
383 }
384
385 bool IsSameAs(const wxDataViewCheckIconText& other) const
386 {
387 return wxDataViewIconText::IsSameAs(other) &&
388 m_checkedState == other.m_checkedState;
389 }
390
391 // There is no encapsulation anyhow, so just expose this field directly.
392 wxCheckBoxState m_checkedState;
393
394
395private:
396 wxDECLARE_DYNAMIC_CLASS(wxDataViewCheckIconText);
397};
398
399wxIMPLEMENT_DYNAMIC_CLASS(wxDataViewCheckIconText, wxDataViewIconText);
400
401DECLARE_VARIANT_OBJECT(wxDataViewCheckIconText)
402IMPLEMENT_VARIANT_OBJECT(wxDataViewCheckIconText)
403
404
405class wxDataViewCheckIconTextRenderer : public wxDataViewCustomRenderer
406{
407public:
408 wxDataViewCheckIconTextRenderer()
409 : wxDataViewCustomRenderer(CHECK_ICON_TEXT_TYPE,
410 wxDATAVIEW_CELL_ACTIVATABLE)
411 {
412 }
413
414 virtual bool SetValue(const wxVariant& value)
415 {
416 m_value << value;
417 return true;
418 }
419
420 virtual bool GetValue(wxVariant& WXUNUSED(value)) const
421 {
422 return false;
423 }
424
425 wxSize GetSize() const
426 {
427 wxSize size = GetCheckSize();
428 size.x += MARGIN_CHECK_ICON;
429
430 if ( m_value.GetIcon().IsOk() )
431 {
432 const wxSize sizeIcon = m_value.GetIcon().GetSize();
433 if ( sizeIcon.y > size.y )
434 size.y = sizeIcon.y;
435
436 size.x += sizeIcon.x + MARGIN_ICON_TEXT;
437 }
438
439 wxString text = m_value.GetText();
440 if ( text.empty() )
441 text = "Dummy";
442
443 const wxSize sizeText = GetTextExtent(text);
444 if ( sizeText.y > size.y )
445 size.y = sizeText.y;
446
447 size.x += sizeText.x;
448
449 return size;
450 }
451
452 virtual bool Render(wxRect cell, wxDC* dc, int state)
453 {
454 // Draw the checkbox first.
455 int renderFlags = 0;
456 switch ( m_value.m_checkedState )
457 {
458 case wxCHK_UNCHECKED:
459 break;
460
461 case wxCHK_CHECKED:
462 renderFlags |= wxCONTROL_CHECKED;
463 break;
464
465 case wxCHK_UNDETERMINED:
466 renderFlags |= wxCONTROL_UNDETERMINED;
467 break;
468 }
469
470 if ( state & wxDATAVIEW_CELL_PRELIT )
471 renderFlags |= wxCONTROL_CURRENT;
472
473 const wxSize sizeCheck = GetCheckSize();
474
475 wxRect rectCheck(cell.GetPosition(), sizeCheck);
476 rectCheck = rectCheck.CentreIn(cell, wxVERTICAL);
477
478 wxRendererNative::Get().DrawCheckBox
479 (
480 GetView(), *dc, rectCheck, renderFlags
481 );
482
483 // Then the icon, if any.
484 int xoffset = sizeCheck.x + MARGIN_CHECK_ICON;
485
486 const wxIcon& icon = m_value.GetIcon();
487 if ( icon.IsOk() )
488 {
489 const wxSize sizeIcon = icon.GetSize();
490 wxRect rectIcon(cell.GetPosition(), sizeIcon);
491 rectIcon.x += xoffset;
492 rectIcon = rectIcon.CentreIn(cell, wxVERTICAL);
493
494 dc->DrawIcon(icon, rectIcon.GetPosition());
495
496 xoffset += sizeIcon.x + MARGIN_ICON_TEXT;
497 }
498
499 // Finally the text.
500 RenderText(m_value.GetText(), xoffset, cell, dc, state);
501
502 return true;
503 }
504
505 // Event handlers toggling the items checkbox if it was clicked.
506 virtual bool Activate(const wxRect& WXUNUSED(cell),
507 wxDataViewModel* model,
508 const wxDataViewItem& item,
509 unsigned int WXUNUSED(col))
510 {
511 static_cast<wxTreeListModel*>(model)->ToggleItem(item);
512 return true;
513 }
514
515 virtual bool LeftClick(const wxPoint& pos,
516 const wxRect& WXUNUSED(cell),
517 wxDataViewModel* model,
518 const wxDataViewItem& item,
519 unsigned int WXUNUSED(col))
520 {
521 if ( !wxRect(GetCheckSize()).Contains(pos) )
522 return false;
523
524 static_cast<wxTreeListModel*>(model)->ToggleItem(item);
525 return true;
526 }
527
528protected:
529 wxSize GetCheckSize() const
530 {
531 return wxRendererNative::Get().GetCheckBoxSize(GetView());
532 }
533
534private:
535 // Just some arbitrary constants defining margins, in pixels.
536 enum
537 {
538 MARGIN_CHECK_ICON = 3,
539 MARGIN_ICON_TEXT = 4
540 };
541
542 wxDataViewCheckIconText m_value;
543};
544
545} // anonymous namespace
546
547// ============================================================================
548// wxTreeListModel implementation
549// ============================================================================
550
551wxTreeListModel::wxTreeListModel(wxTreeListCtrl* treelist)
552 : m_treelist(treelist),
553 m_root(new Node(NULL))
554{
555 m_numColumns = 0;
556}
557
558wxTreeListModel::~wxTreeListModel()
559{
560 delete m_root;
561}
562
563void wxTreeListModel::InsertColumn(unsigned col)
564{
565 m_numColumns++;
566
567 // There is no need to update anything when inserting the first column.
568 if ( m_numColumns == 1 )
569 return;
570
571 // Update all the items as they may have texts for the old columns.
572 for ( Node* node = m_root->GetChild(); node; node = node->NextInTree() )
573 {
574 node->OnInsertColumn(col, m_numColumns);
575 }
576}
577
578wxTreeListModelNode*
579wxTreeListModel::InsertItem(Node* parent,
580 Node* previous,
581 const wxString& text,
582 int imageClosed,
583 int imageOpened,
584 wxClientData* data)
585{
586 wxCHECK_MSG( parent, NULL,
587 "Must have a valid parent (maybe GetRootItem()?)" );
588
589 wxCHECK_MSG( previous, NULL,
590 "Must have a valid previous item (maybe wxTLI_FIRST/LAST?)" );
591
592 wxScopedPtr<Node>
593 newItem(new Node(parent, text, imageClosed, imageOpened, data));
594
595 // If we have no children at all, then inserting as last child is the same
596 // as inserting as the first one so check for it here too.
597 if ( previous == wxTLI_FIRST ||
598 (previous == wxTLI_LAST && !parent->GetChild()) )
599 {
600 parent->InsertChild(newItem.get());
601 }
602 else // Not the first item, find the previous one.
603 {
604 if ( previous == wxTLI_LAST )
605 {
606 previous = parent->GetChild();
607
608 // Find the last child.
609 for ( ;; )
610 {
611 Node* const next = previous->GetNext();
612 if ( !next )
613 break;
614
615 previous = next;
616 }
617 }
618 else // We already have the previous item.
619 {
620 // Just check it's under the correct parent.
621 wxCHECK_MSG( previous->GetParent() == parent, NULL,
622 "Previous item is not under the right parent" );
623 }
624
625 previous->InsertNext(newItem.get());
626 }
627
628 ItemAdded(ToDVI(parent), ToDVI(newItem.get()));
629
630 // The item was successfully inserted in the tree and so will be deleted by
631 // it, we can detach it now.
632 return newItem.release();
633}
634
635void wxTreeListModel::DeleteItem(Node* item)
636{
637 wxCHECK_RET( item, "Invalid item" );
638
639 wxCHECK_RET( item != m_root, "Can't delete the root item" );
640
641 Node* const parent = item->GetParent();
642
643 ItemDeleted(ToDVI(parent), ToDVI(item));
644
645 Node* previous = parent->GetChild();
646 if ( previous == item )
647 {
648 parent->DeleteChild();
649 }
650 else // Not the first child of its parent.
651 {
652 // Find the sibling just before it.
653 for ( ;; )
654 {
655 Node* const next = previous->GetNext();
656 if ( next == item )
657 break;
658
659 wxCHECK_RET( next, "Item not a child of its parent?" );
660
661 previous = next;
662 }
663
664 previous->DeleteNext();
665 }
666}
667
668void wxTreeListModel::DeleteAllItems()
669{
670 while ( m_root->GetChild() )
671 {
672 m_root->DeleteChild();
673 }
674
675 Cleared();
676}
677
678const wxString& wxTreeListModel::GetItemText(Node* item, unsigned col) const
679{
680 // Returning root item text here is bogus, it just happens to be an always
681 // empty string we can return reference to.
682 wxCHECK_MSG( item, m_root->m_text, "Invalid item" );
683
684 return col == 0 ? item->m_text : item->GetColumnText(col);
685}
686
687void wxTreeListModel::SetItemText(Node* item, unsigned col, const wxString& text)
688{
689 wxCHECK_RET( item, "Invalid item" );
690
691 if ( col == 0 )
692 item->m_text = text;
693 else
694 item->SetColumnText(text, col, m_numColumns);
695
696 ValueChanged(ToDVI(item), col);
697}
698
699void wxTreeListModel::SetItemImage(Node* item, int closed, int opened)
700{
701 wxCHECK_RET( item, "Invalid item" );
702
703 item->m_imageClosed = closed;
704 item->m_imageOpened = opened;
705
706 ValueChanged(ToDVI(item), 0);
707}
708
709wxClientData* wxTreeListModel::GetItemData(Node* item) const
710{
711 wxCHECK_MSG( item, NULL, "Invalid item" );
712
713 return item->GetClientData();
714}
715
716void wxTreeListModel::SetItemData(Node* item, wxClientData* data)
717{
718 wxCHECK_RET( item, "Invalid item" );
719
720 item->SetClientData(data);
721}
722
723void wxTreeListModel::CheckItem(Node* item, wxCheckBoxState checkedState)
724{
725 wxCHECK_RET( item, "Invalid item" );
726
727 item->m_checkedState = checkedState;
728
729 ItemChanged(ToDVI(item));
730}
731
732void wxTreeListModel::ToggleItem(wxDataViewItem dvi)
733{
734 Node* const item = FromDVI(dvi);
735
736 wxCHECK_RET( item, "Invalid item" );
737
738 const wxCheckBoxState stateOld = item->m_checkedState;
739
740 // If the 3rd state is user-settable then the cycle is
741 // unchecked->checked->undetermined.
742 switch ( stateOld )
743 {
744 case wxCHK_CHECKED:
745 item->m_checkedState = m_treelist->HasFlag(wxTL_USER_3STATE)
746 ? wxCHK_UNDETERMINED
747 : wxCHK_UNCHECKED;
748 break;
749
750 case wxCHK_UNDETERMINED:
751 // Whether 3rd state is user-settable or not, the next state is
752 // unchecked.
753 item->m_checkedState = wxCHK_UNCHECKED;
754 break;
755
756 case wxCHK_UNCHECKED:
757 item->m_checkedState = wxCHK_CHECKED;
758 break;
759 }
760
761 ItemChanged(ToDVI(item));
762
763 m_treelist->OnItemToggled(item, stateOld);
764}
765
766unsigned wxTreeListModel::GetColumnCount() const
767{
768 return m_numColumns;
769}
770
771wxString wxTreeListModel::GetColumnType(unsigned col) const
772{
773 if ( col == 0 )
774 {
775 return m_treelist->HasFlag(wxTL_CHECKBOX)
776 ? wxS("wxDataViewCheckIconText")
777 : wxS("wxDataViewIconText");
778 }
779 else // All the other columns contain just text.
780 {
781 return wxS("string");
782 }
783}
784
785void
786wxTreeListModel::GetValue(wxVariant& variant,
787 const wxDataViewItem& item,
788 unsigned col) const
789{
790 Node* const node = FromDVI(item);
791
792 if ( col == 0 )
793 {
794 // Determine the correct image to use depending on the item state.
795 int image = wxWithImages::NO_IMAGE;
796 if ( m_treelist->IsExpanded(node) )
797 image = node->m_imageOpened;
798
799 if ( image == wxWithImages::NO_IMAGE )
800 image = node->m_imageClosed;
801
802 wxIcon icon = m_treelist->GetImage(image);
803
804 if ( m_treelist->HasFlag(wxTL_CHECKBOX) )
805 variant << wxDataViewCheckIconText(node->m_text, icon,
806 node->m_checkedState);
807 else
808 variant << wxDataViewIconText(node->m_text, icon);
809 }
810 else
811 {
812 // Notice that we must still assign wxString to wxVariant to ensure
813 // that it at least has the correct type.
814 wxString text;
815 if ( node->HasColumnsTexts() )
816 text = node->GetColumnText(col);
817
818 variant = text;
819 }
820}
821
822bool
823wxTreeListModel::SetValue(const wxVariant& WXUNUSED(variant),
824 const wxDataViewItem& WXUNUSED(item),
825 unsigned WXUNUSED(col))
826{
827 // We are not editable currently.
828 return false;
829}
830
831wxDataViewItem wxTreeListModel::GetParent(const wxDataViewItem& item) const
832{
833 Node* const node = FromDVI(item);
834
835 return ToDVI(node->GetParent());
836}
837
838bool wxTreeListModel::IsContainer(const wxDataViewItem& item) const
839{
840 // FIXME: In the generic (and native OS X) versions we implement this
841 // method normally, i.e. only items with children are containers.
842 // But for the native GTK version we must pretend that all items are
843 // containers because otherwise adding children to them later would
844 // fail because wxGTK code calls IsContainer() too early (when
845 // adding the item itself) and we can't know whether we're container
846 // or not by then. Luckily, always returning true doesn't have any
847 // serious drawbacks for us.
848#ifdef __WXGTK__
849 wxUnusedVar(item);
850
851 return true;
852#else
853 Node* const node = FromDVI(item);
854
855 return node->GetChild() != NULL;
856#endif
857}
858
859bool
860wxTreeListModel::HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const
861{
862 return true;
863}
864
865unsigned
866wxTreeListModel::GetChildren(const wxDataViewItem& item,
867 wxDataViewItemArray& children) const
868{
869 Node* const node = FromDVI(item);
870
871 unsigned numChildren = 0;
872 for ( Node* child = node->GetChild(); child; child = child->GetNext() )
873 {
874 children.push_back(ToDVI(child));
875 numChildren++;
876 }
877
878 return numChildren;
879}
880
881// ============================================================================
882// wxTreeListCtrl implementation
883// ============================================================================
884
885BEGIN_EVENT_TABLE(wxTreeListCtrl, wxWindow)
886 EVT_DATAVIEW_SELECTION_CHANGED(wxID_ANY, wxTreeListCtrl::OnSelectionChanged)
887 EVT_DATAVIEW_ITEM_EXPANDING(wxID_ANY, wxTreeListCtrl::OnItemExpanding)
888 EVT_DATAVIEW_ITEM_EXPANDED(wxID_ANY, wxTreeListCtrl::OnItemExpanded)
889 EVT_DATAVIEW_ITEM_ACTIVATED(wxID_ANY, wxTreeListCtrl::OnItemActivated)
890 EVT_DATAVIEW_ITEM_CONTEXT_MENU(wxID_ANY, wxTreeListCtrl::OnItemContextMenu)
891
892 EVT_SIZE(wxTreeListCtrl::OnSize)
893END_EVENT_TABLE()
894
895// ----------------------------------------------------------------------------
896// Creation
897// ----------------------------------------------------------------------------
898
899void wxTreeListCtrl::Init()
900{
901 m_view = NULL;
902 m_model = NULL;
903}
904
905bool wxTreeListCtrl::Create(wxWindow* parent,
906 wxWindowID id,
907 const wxPoint& pos,
908 const wxSize& size,
909 long style,
910 const wxString& name)
911{
912 if ( style & wxTL_USER_3STATE )
913 style |= wxTL_3STATE;
914
915 if ( style & wxTL_3STATE )
916 style |= wxTL_CHECKBOX;
917
918 // Create the window itself and wxDataViewCtrl used by it.
919 if ( !wxWindow::Create(parent, id,
920 pos, size,
921 style, name) )
922 {
923 return false;
924 }
925
926 m_view = new wxDataViewCtrl;
927 if ( !m_view->Create(this, wxID_ANY,
928 wxPoint(0, 0), GetClientSize(),
929 HasFlag(wxTL_MULTIPLE) ? wxDV_MULTIPLE
930 : wxDV_SINGLE) )
931 {
932 delete m_view;
933 m_view = NULL;
934
935 return false;
936 }
937
938
939 // Set up the model for wxDataViewCtrl.
940 m_model = new wxTreeListModel(this);
941 m_view->AssociateModel(m_model);
942
943 return true;
944}
945
946wxTreeListCtrl::~wxTreeListCtrl()
947{
948 if ( m_model )
949 m_model->DecRef();
950}
951
952// ----------------------------------------------------------------------------
953// Columns
954// ----------------------------------------------------------------------------
955
956int
957wxTreeListCtrl::DoInsertColumn(const wxString& title,
958 int pos,
959 int width,
960 wxAlignment align,
961 int flags)
962{
963 wxCHECK_MSG( m_view, wxNOT_FOUND, "Must Create() first" );
964
965 const unsigned oldNumColumns = m_view->GetColumnCount();
966
967 if ( pos == wxNOT_FOUND )
968 pos = oldNumColumns;
969
970 wxDataViewRenderer* renderer;
971 if ( pos == 0 )
972 {
973 // Inserting the first column which is special as it uses a different
974 // renderer.
975
976 // Also, currently it can be done only once.
977 wxCHECK_MSG( !oldNumColumns, wxNOT_FOUND,
978 "Inserting column at position 0 currently not supported" );
979
980 if ( HasFlag(wxTL_CHECKBOX) )
981 {
982 // Use our custom renderer to show the checkbox.
983 renderer = new wxDataViewCheckIconTextRenderer;
984 }
985 else // We still need a special renderer to show the icons.
986 {
987 renderer = new wxDataViewIconTextRenderer;
988 }
989 }
990 else // Not the first column.
991 {
992 // All the other ones use a simple text renderer.
993 renderer = new wxDataViewTextRenderer;
994 }
995
996 wxDataViewColumn*
997 column = new wxDataViewColumn(title, renderer, pos, width, align, flags);
998
999 m_model->InsertColumn(pos);
1000
1001 m_view->InsertColumn(pos, column);
1002
1003 return pos;
1004}
1005
1006unsigned wxTreeListCtrl::GetColumnCount() const
1007{
1008 return m_view ? m_view->GetColumnCount() : 0u;
1009}
1010
1011bool wxTreeListCtrl::DeleteColumn(unsigned col)
1012{
1013 wxCHECK_MSG( col < GetColumnCount(), false, "Invalid column index" );
1014
1015 return m_view->DeleteColumn(m_view->GetColumn(col));
1016}
1017
1018void wxTreeListCtrl::ClearColumns()
1019{
1020 if ( m_view )
1021 m_view->ClearColumns();
1022}
1023
1024void wxTreeListCtrl::SetColumnWidth(unsigned col, int width)
1025{
1026 wxCHECK_RET( col < GetColumnCount(), "Invalid column index" );
1027
1028 wxDataViewColumn* const column = m_view->GetColumn(col);
1029 wxCHECK_RET( column, "No such column?" );
1030
1031 return column->SetWidth(width);
1032}
1033
1034int wxTreeListCtrl::GetColumnWidth(unsigned col) const
1035{
1036 wxCHECK_MSG( col < GetColumnCount(), -1, "Invalid column index" );
1037
1038 wxDataViewColumn* column = m_view->GetColumn(col);
1039 wxCHECK_MSG( column, -1, "No such column?" );
1040
1041 return column->GetWidth();
1042}
1043
1044int wxTreeListCtrl::WidthFor(const wxString& text) const
1045{
1046 return GetTextExtent(text).x;
1047}
1048
1049// ----------------------------------------------------------------------------
1050// Items
1051// ----------------------------------------------------------------------------
1052
1053wxTreeListItem
1054wxTreeListCtrl::DoInsertItem(wxTreeListItem parent,
1055 wxTreeListItem previous,
1056 const wxString& text,
1057 int imageClosed,
1058 int imageOpened,
1059 wxClientData* data)
1060{
1061 wxCHECK_MSG( m_model, wxTreeListItem(), "Must create first" );
1062
1063 return wxTreeListItem(m_model->InsertItem(parent, previous, text,
1064 imageClosed, imageOpened, data));
1065}
1066
1067void wxTreeListCtrl::DeleteItem(wxTreeListItem item)
1068{
1069 wxCHECK_RET( m_model, "Must create first" );
1070
1071 m_model->DeleteItem(item);
1072}
1073
1074void wxTreeListCtrl::DeleteAllItems()
1075{
1076 if ( m_model )
1077 m_model->DeleteAllItems();
1078}
1079
1080// ----------------------------------------------------------------------------
1081// Tree navigation
1082// ----------------------------------------------------------------------------
1083
1084// The simple accessors in this section are implemented directly using
1085// wxTreeListModelNode methods, without passing by the model. This is just a
1086// shortcut and avoids us the trouble of defining more trivial methods in
1087// wxTreeListModel.
1088
1089wxTreeListItem wxTreeListCtrl::GetRootItem() const
1090{
1091 wxCHECK_MSG( m_model, wxTreeListItem(), "Must create first" );
1092
1093 return m_model->GetRootItem();
1094}
1095
1096wxTreeListItem wxTreeListCtrl::GetItemParent(wxTreeListItem item) const
1097{
1098 wxCHECK_MSG( item.IsOk(), wxTreeListItem(), "Invalid item" );
1099
1100 return item->GetParent();
1101}
1102
1103wxTreeListItem wxTreeListCtrl::GetFirstChild(wxTreeListItem item) const
1104{
1105 wxCHECK_MSG( item.IsOk(), wxTreeListItem(), "Invalid item" );
1106
1107 return item->GetChild();
1108}
1109
1110wxTreeListItem
1111wxTreeListCtrl::GetNextSibling(wxTreeListItem item) const
1112{
1113 wxCHECK_MSG( item.IsOk(), wxTreeListItem(), "Invalid item" );
1114
1115 return item->GetNext();
1116}
1117
1118wxTreeListItem wxTreeListCtrl::GetNextItem(wxTreeListItem item) const
1119{
1120 wxCHECK_MSG( item.IsOk(), wxTreeListItem(), "Invalid item" );
1121
1122 return item->NextInTree();
1123}
1124
1125// ----------------------------------------------------------------------------
1126// Item attributes
1127// ----------------------------------------------------------------------------
1128
1129const wxString&
1130wxTreeListCtrl::GetItemText(wxTreeListItem item, unsigned col) const
1131{
1132 // We can't use wxCHECK_MSG() here because we don't have any empty string
1133 // reference to return so we use a static variable that exists just for the
1134 // purpose of this check -- and so we put it in its own scope so that it's
1135 // never even created during normal program execution.
1136 if ( !m_model || col >= m_model->GetColumnCount() )
1137 {
1138 static wxString s_empty;
1139
1140 if ( !m_model )
1141 {
1142 wxFAIL_MSG( "Must create first" );
1143 }
1144 else if ( col >= m_model->GetColumnCount() )
1145 {
1146 wxFAIL_MSG( "Invalid column index" );
1147 }
1148
1149 return s_empty;
1150 }
1151
1152 return m_model->GetItemText(item, col);
1153}
1154
1155void
1156wxTreeListCtrl::SetItemText(wxTreeListItem item,
1157 unsigned col,
1158 const wxString& text)
1159{
1160 wxCHECK_RET( m_model, "Must create first" );
1161 wxCHECK_RET( col < m_model->GetColumnCount(), "Invalid column index" );
1162
1163 m_model->SetItemText(item, col, text);
1164}
1165
1166void wxTreeListCtrl::SetItemImage(wxTreeListItem item, int closed, int opened)
1167{
1168 wxCHECK_RET( m_model, "Must create first" );
1169
1170 if ( closed != NO_IMAGE || opened != NO_IMAGE )
1171 {
1172 wxImageList* const imageList = GetImageList();
1173 wxCHECK_RET( imageList, "Can't set images without image list" );
1174
1175 const int imageCount = imageList->GetImageCount();
1176
1177 wxCHECK_RET( closed < imageCount, "Invalid image index" );
1178 wxCHECK_RET( opened < imageCount, "Invalid opened image index" );
1179 }
1180
1181 m_model->SetItemImage(item, closed, opened);
1182}
1183
1184wxClientData* wxTreeListCtrl::GetItemData(wxTreeListItem item) const
1185{
1186 wxCHECK_MSG( m_model, NULL, "Must create first" );
1187
1188 return m_model->GetItemData(item);
1189}
1190
1191void wxTreeListCtrl::SetItemData(wxTreeListItem item, wxClientData* data)
1192{
1193 wxCHECK_RET( m_model, "Must create first" );
1194
1195 m_model->SetItemData(item, data);
1196}
1197
1198// ----------------------------------------------------------------------------
1199// Expanding and collapsing
1200// ----------------------------------------------------------------------------
1201
1202void wxTreeListCtrl::Expand(wxTreeListItem item)
1203{
1204 wxCHECK_RET( m_view, "Must create first" );
1205
1206 m_view->Expand(m_model->ToDVI(item));
1207}
1208
1209void wxTreeListCtrl::Collapse(wxTreeListItem item)
1210{
1211 wxCHECK_RET( m_view, "Must create first" );
1212
1213 m_view->Collapse(m_model->ToDVI(item));
1214}
1215
1216bool wxTreeListCtrl::IsExpanded(wxTreeListItem item) const
1217{
1218 wxCHECK_MSG( m_view, false, "Must create first" );
1219
1220 return m_view->IsExpanded(m_model->ToDVI(item));
1221}
1222
1223// ----------------------------------------------------------------------------
1224// Selection
1225// ----------------------------------------------------------------------------
1226
1227wxTreeListItem wxTreeListCtrl::GetSelection() const
1228{
1229 wxCHECK_MSG( m_view, wxTreeListItem(), "Must create first" );
1230
1231 wxCHECK_MSG( !HasFlag(wxTL_MULTIPLE), wxTreeListItem(),
1232 "Must use GetSelections() with multi-selection controls!" );
1233
1234 const wxDataViewItem dvi = m_view->GetSelection();
1235
1236 return m_model->FromNonRootDVI(dvi);
1237}
1238
1239unsigned wxTreeListCtrl::GetSelections(wxTreeListItems& selections) const
1240{
1241 wxCHECK_MSG( m_view, 0, "Must create first" );
1242
1243 wxDataViewItemArray selectionsDV;
1244 const unsigned numSelected = m_view->GetSelections(selectionsDV);
1245 selections.resize(numSelected);
1246 for ( unsigned n = 0; n < numSelected; n++ )
1247 selections[n] = m_model->FromNonRootDVI(selectionsDV[n]);
1248
1249 return numSelected;
1250}
1251
1252void wxTreeListCtrl::Select(wxTreeListItem item)
1253{
1254 wxCHECK_RET( m_view, "Must create first" );
1255
1256 m_view->Select(m_model->ToNonRootDVI(item));
1257}
1258
1259void wxTreeListCtrl::Unselect(wxTreeListItem item)
1260{
1261 wxCHECK_RET( m_view, "Must create first" );
1262
1263 m_view->Unselect(m_model->ToNonRootDVI(item));
1264}
1265
1266bool wxTreeListCtrl::IsSelected(wxTreeListItem item) const
1267{
1268 wxCHECK_MSG( m_view, false, "Must create first" );
1269
1270 return m_view->IsSelected(m_model->ToNonRootDVI(item));
1271}
1272
1273void wxTreeListCtrl::SelectAll()
1274{
1275 wxCHECK_RET( m_view, "Must create first" );
1276
1277 m_view->SelectAll();
1278}
1279
1280void wxTreeListCtrl::UnselectAll()
1281{
1282 wxCHECK_RET( m_view, "Must create first" );
1283
1284 m_view->UnselectAll();
1285}
1286
1287// ----------------------------------------------------------------------------
1288// Checkbox handling
1289// ----------------------------------------------------------------------------
1290
1291void wxTreeListCtrl::CheckItem(wxTreeListItem item, wxCheckBoxState state)
1292{
1293 wxCHECK_RET( m_model, "Must create first" );
1294
1295 m_model->CheckItem(item, state);
1296}
1297
1298void
1299wxTreeListCtrl::CheckItemRecursively(wxTreeListItem item, wxCheckBoxState state)
1300{
1301 wxCHECK_RET( m_model, "Must create first" );
1302
1303 m_model->CheckItem(item, state);
1304
1305 for ( wxTreeListItem child = GetFirstChild(item);
1306 child.IsOk();
1307 child = GetNextSibling(child) )
1308 {
1309 CheckItemRecursively(child, state);
1310 }
1311}
1312
1313void wxTreeListCtrl::UpdateItemParentStateRecursively(wxTreeListItem item)
1314{
1315 wxCHECK_RET( item.IsOk(), "Invalid item" );
1316
1317 wxASSERT_MSG( HasFlag(wxTL_3STATE), "Can only be used with wxTL_3STATE" );
1318
1319 for ( ;; )
1320 {
1321 wxTreeListItem parent = GetItemParent(item);
1322 if ( parent == GetRootItem() )
1323 {
1324 // There is no checked state associated with the root item.
1325 return;
1326 }
1327
1328 // Set parent state to the state of this item if all the other children
1329 // have the same state too. Otherwise make it indeterminate.
1330 const wxCheckBoxState stateItem = GetCheckedState(item);
1331 CheckItem(parent, AreAllChildrenInState(parent, stateItem)
1332 ? stateItem
1333 : wxCHK_UNDETERMINED);
1334
1335 // And do the same thing with the parent's parent too.
1336 item = parent;
1337 }
1338}
1339
1340wxCheckBoxState wxTreeListCtrl::GetCheckedState(wxTreeListItem item) const
1341{
1342 wxCHECK_MSG( item.IsOk(), wxCHK_UNDETERMINED, "Invalid item" );
1343
1344 return item->m_checkedState;
1345}
1346
1347bool
1348wxTreeListCtrl::AreAllChildrenInState(wxTreeListItem item,
1349 wxCheckBoxState state) const
1350{
1351 wxCHECK_MSG( item.IsOk(), false, "Invalid item" );
1352
1353 for ( wxTreeListItem child = GetFirstChild(item);
1354 child.IsOk();
1355 child = GetNextSibling(child) )
1356 {
1357 if ( GetCheckedState(child) != state )
1358 return false;
1359 }
1360
1361 return true;
1362}
1363
1364// ----------------------------------------------------------------------------
1365// Events
1366// ----------------------------------------------------------------------------
1367
1368void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV)
1369{
1370 wxTreeListEvent eventTL(evt, this, m_model->FromDVI(eventDV.GetItem()));
1371
1372 if ( !ProcessWindowEvent(eventTL) )
1373 {
1374 eventDV.Skip();
1375 return;
1376 }
1377
1378 if ( !eventTL.IsAllowed() )
1379 {
1380 eventDV.Veto();
1381 }
1382}
1383
1384void
1385wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld)
1386{
1387 wxTreeListEvent event(wxEVT_COMMAND_TREELIST_ITEM_CHECKED, this, item);
1388 event.SetOldCheckedState(stateOld);
1389
1390 ProcessWindowEvent(event);
1391}
1392
1393void wxTreeListCtrl::OnSelectionChanged(wxDataViewEvent& event)
1394{
1395 SendEvent(wxEVT_COMMAND_TREELIST_SELECTION_CHANGED, event);
1396}
1397
1398void wxTreeListCtrl::OnItemExpanding(wxDataViewEvent& event)
1399{
1400 SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDING, event);
1401}
1402
1403void wxTreeListCtrl::OnItemExpanded(wxDataViewEvent& event)
1404{
1405 SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDED, event);
1406}
1407
1408void wxTreeListCtrl::OnItemActivated(wxDataViewEvent& event)
1409{
1410 SendEvent(wxEVT_COMMAND_TREELIST_ITEM_ACTIVATED, event);
1411}
1412
1413void wxTreeListCtrl::OnItemContextMenu(wxDataViewEvent& event)
1414{
1415 SendEvent(wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU, event);
1416}
1417
1418// ----------------------------------------------------------------------------
1419// Geometry
1420// ----------------------------------------------------------------------------
1421
1422void wxTreeListCtrl::OnSize(wxSizeEvent& event)
1423{
1424 event.Skip();
1425
1426 if ( m_view )
1427 {
1428 // Resize the real control to cover our entire client area.
1429 const wxRect rect = GetClientRect();
1430 m_view->SetSize(rect);
1431
1432 // Resize the first column to take the remaining available space, if
1433 // any.
1434 const unsigned numColumns = GetColumnCount();
1435 if ( !numColumns )
1436 return;
1437
1438 // There is a bug in generic wxDataViewCtrl: if the column width sums
1439 // up to the total size, horizontal scrollbar (unnecessarily) appears,
1440 // so subtract 10 pixels to ensure this doesn't happen.
1441 int remainingWidth = rect.width - 10;
1442 for ( unsigned n = 1; n < GetColumnCount(); n++ )
1443 {
1444 remainingWidth -= GetColumnWidth(n);
1445 if ( remainingWidth < 0 )
1446 break;
1447 }
1448
1449 // We don't decrease the width of the first column, even if we had
1450 // increased it ourselves, because we want to avoid changing its size
1451 // if the user resized it. We might want to remember if this was the
1452 // case or if we only ever adjusted it automatically in the future.
1453 if ( remainingWidth > GetColumnWidth(0) )
1454 SetColumnWidth(0, remainingWidth);
1455 }
1456}
1457
1458// ============================================================================
1459// wxTreeListEvent implementation
1460// ============================================================================
1461
1462wxIMPLEMENT_ABSTRACT_CLASS(wxTreeListEvent, wxNotifyEvent)
1463
1464#define wxDEFINE_TREELIST_EVENT(name) \
1465 wxDEFINE_EVENT(wxEVT_COMMAND_TREELIST_##name, wxTreeListEvent)
1466
1467wxDEFINE_TREELIST_EVENT(SELECTION_CHANGED);
1468wxDEFINE_TREELIST_EVENT(ITEM_EXPANDING);
1469wxDEFINE_TREELIST_EVENT(ITEM_EXPANDED);
1470wxDEFINE_TREELIST_EVENT(ITEM_CHECKED);
1471wxDEFINE_TREELIST_EVENT(ITEM_ACTIVATED);
1472wxDEFINE_TREELIST_EVENT(ITEM_CONTEXT_MENU);
1473
1474#undef wxDEFINE_TREELIST_EVENT