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