1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/treebkg.cpp
3 // Purpose: generic implementation of wxTreebook
4 // Author: Evgeniy Tarassov, Vadim Zeitlin
8 // Copyright: (c) 2005 Vadim Zeitlin <vadim@wxwidgets.org>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
29 #include "wx/treebook.h"
30 #include "wx/imaglist.h"
31 #include "wx/settings.h"
33 // ----------------------------------------------------------------------------
34 // various wxWidgets macros
35 // ----------------------------------------------------------------------------
37 // check that the page index is valid
38 #define IS_VALID_PAGE(nPage) ((nPage) < DoInternalGetPageCount())
40 // ----------------------------------------------------------------------------
42 // ----------------------------------------------------------------------------
44 IMPLEMENT_DYNAMIC_CLASS(wxTreebook
, wxBookCtrlBase
)
45 IMPLEMENT_DYNAMIC_CLASS(wxTreebookEvent
, wxNotifyEvent
)
47 #if !WXWIN_COMPATIBILITY_EVENT_TYPES
48 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING
= wxNewEventType();
49 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED
= wxNewEventType();
50 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED
= wxNewEventType();
51 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
= wxNewEventType();
53 const int wxID_TREEBOOKTREEVIEW
= wxNewId();
55 BEGIN_EVENT_TABLE(wxTreebook
, wxBookCtrlBase
)
56 EVT_TREE_SEL_CHANGED (wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeSelectionChange
)
57 EVT_TREE_ITEM_EXPANDED (wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeNodeExpandedCollapsed
)
58 EVT_TREE_ITEM_COLLAPSED(wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeNodeExpandedCollapsed
)
61 // ============================================================================
62 // wxTreebook implementation
63 // ============================================================================
65 // ----------------------------------------------------------------------------
66 // wxTreebook creation
67 // ----------------------------------------------------------------------------
69 void wxTreebook::Init()
72 m_actualSelection
= wxNOT_FOUND
;
76 wxTreebook::Create(wxWindow
*parent
,
83 // Check the style flag to have either wxTBK_RIGHT or wxTBK_LEFT
84 if ( (style
& wxBK_ALIGN_MASK
) == wxBK_DEFAULT
)
89 // no border for this control, it doesn't look nice together with the tree
90 style
&= ~wxBORDER_MASK
;
91 style
|= wxBORDER_NONE
;
93 if ( !wxControl::Create(parent
, id
, pos
, size
,
94 style
, wxDefaultValidator
, name
) )
97 m_bookctrl
= new wxTreeCtrl
100 wxID_TREEBOOKTREEVIEW
,
108 GetTreeCtrl()->AddRoot(wxEmptyString
); // label doesn't matter, it's hidden
111 // We need to add dummy size event to force possible scrollbar hiding
113 GetEventHandler()->AddPendingEvent(evt
);
120 // insert a new page just before the pagePos
121 bool wxTreebook::InsertPage(size_t pagePos
,
123 const wxString
& text
,
127 return DoInsertPage(pagePos
, page
, text
, bSelect
, imageId
);
130 bool wxTreebook::InsertSubPage(size_t pagePos
,
132 const wxString
& text
,
136 return DoInsertSubPage(pagePos
, page
, text
, bSelect
, imageId
);
139 bool wxTreebook::AddPage(wxWindow
*page
, const wxString
& text
, bool bSelect
,
142 return DoInsertPage(m_treeIds
.GetCount(), page
, text
, bSelect
, imageId
);
145 // insertion time is linear to the number of top-pages
146 bool wxTreebook::AddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
148 return DoAddSubPage(page
, text
, bSelect
, imageId
);
152 bool wxTreebook::DoInsertPage(size_t pagePos
,
154 const wxString
& text
,
158 wxCHECK_MSG( pagePos
<= DoInternalGetPageCount(), false,
159 wxT("Invalid treebook page position") );
161 if ( !wxBookCtrlBase::InsertPage(pagePos
, page
, text
, bSelect
, imageId
) )
164 wxTreeCtrl
*tree
= GetTreeCtrl();
166 if ( pagePos
== DoInternalGetPageCount() )
168 // append the page to the end
169 wxTreeItemId rootId
= tree
->GetRootItem();
171 newId
= tree
->AppendItem(rootId
, text
, imageId
);
173 else // insert the new page before the given one
175 wxTreeItemId nodeId
= m_treeIds
[pagePos
];
177 wxTreeItemId previousId
= tree
->GetPrevSibling(nodeId
);
178 wxTreeItemId parentId
= tree
->GetItemParent(nodeId
);
180 if ( previousId
.IsOk() )
182 // insert before the sibling - previousId
183 newId
= tree
->InsertItem(parentId
, previousId
, text
, imageId
);
185 else // no prev siblings -- insert as a first child
187 wxASSERT_MSG( parentId
.IsOk(), wxT( "Tree has no root node?" ) );
189 newId
= tree
->PrependItem(parentId
, text
, imageId
);
195 //something wrong -> cleaning and returning with false
196 (void)wxBookCtrlBase::DoRemovePage(pagePos
);
198 wxFAIL_MSG( wxT("Failed to insert treebook page") );
202 DoInternalAddPage(pagePos
, page
, newId
);
204 DoUpdateSelection(bSelect
, pagePos
);
209 bool wxTreebook::DoAddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
211 wxTreeCtrl
*tree
= GetTreeCtrl();
213 wxTreeItemId rootId
= tree
->GetRootItem();
215 wxTreeItemId lastNodeId
= tree
->GetLastChild(rootId
);
217 wxCHECK_MSG( lastNodeId
.IsOk(), false,
218 _T("Can't insert sub page when there are no pages") );
220 // now calculate its position (should we save/update it too?)
221 size_t newPos
= tree
->GetCount() -
222 (tree
->GetChildrenCount(lastNodeId
, true) + 1);
224 return DoInsertSubPage(newPos
, page
, text
, bSelect
, imageId
);
227 bool wxTreebook::DoInsertSubPage(size_t pagePos
,
228 wxTreebookPage
*page
,
229 const wxString
& text
,
233 wxTreeItemId parentId
= DoInternalGetPage(pagePos
);
234 wxCHECK_MSG( parentId
.IsOk(), false, wxT("invalid tree item") );
236 wxTreeCtrl
*tree
= GetTreeCtrl();
238 size_t newPos
= pagePos
+ tree
->GetChildrenCount(parentId
, true) + 1;
239 wxASSERT_MSG( newPos
<= DoInternalGetPageCount(),
240 wxT("Internal error in tree insert point calculation") );
242 if ( !wxBookCtrlBase::InsertPage(newPos
, page
, text
, bSelect
, imageId
) )
245 wxTreeItemId newId
= tree
->AppendItem(parentId
, text
, imageId
);
249 (void)wxBookCtrlBase::DoRemovePage(newPos
);
251 wxFAIL_MSG( wxT("Failed to insert treebook page") );
255 DoInternalAddPage(newPos
, page
, newId
);
257 DoUpdateSelection(bSelect
, newPos
);
262 bool wxTreebook::DeletePage(size_t pagePos
)
264 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), false, wxT("Invalid tree index") );
266 wxTreebookPage
*oldPage
= DoRemovePage(pagePos
);
275 wxTreebookPage
*wxTreebook::DoRemovePage(size_t pagePos
)
277 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
278 wxCHECK_MSG( pageId
.IsOk(), NULL
, wxT("Invalid tree index") );
280 wxTreebookPage
* oldPage
= GetPage(pagePos
);
281 wxTreeCtrl
*tree
= GetTreeCtrl();
283 size_t subCount
= tree
->GetChildrenCount(pageId
, true);
284 wxASSERT_MSG ( IS_VALID_PAGE(pagePos
+ subCount
),
285 wxT("Internal error in wxTreebook::DoRemovePage") );
287 // here we are going to delete ALL the pages in the range
288 // [pagePos, pagePos + subCount] -- the page and its children
290 // deleting all the pages from the base class
291 for ( size_t i
= 0; i
<= subCount
; ++i
)
293 wxTreebookPage
*page
= wxBookCtrlBase::DoRemovePage(pagePos
);
295 // don't delete the page itself though -- it will be deleted in
296 // DeletePage() when we return
303 DoInternalRemovePageRange(pagePos
, subCount
);
305 tree
->DeleteChildren( pageId
);
306 tree
->Delete( pageId
);
311 bool wxTreebook::DeleteAllPages()
313 wxBookCtrlBase::DeleteAllPages();
316 m_actualSelection
= wxNOT_FOUND
;
318 wxTreeCtrl
*tree
= GetTreeCtrl();
319 tree
->DeleteChildren(tree
->GetRootItem());
324 void wxTreebook::DoInternalAddPage(size_t newPos
,
325 wxTreebookPage
*page
,
328 wxASSERT_MSG( newPos
<= m_treeIds
.GetCount(), wxT("Ivalid index passed to wxTreebook::DoInternalAddPage") );
330 // hide newly inserted page initially (it will be shown when selected)
334 if ( newPos
== m_treeIds
.GetCount() )
337 m_treeIds
.Add(pageId
);
341 m_treeIds
.Insert(pageId
, newPos
);
343 if ( m_selection
!= wxNOT_FOUND
&& newPos
<= (size_t)m_selection
)
345 // selection has been moved one unit toward the end
347 if ( m_actualSelection
!= wxNOT_FOUND
)
350 else if ( m_actualSelection
!= wxNOT_FOUND
&&
351 newPos
<= (size_t)m_actualSelection
)
353 DoSetSelection(m_selection
);
358 void wxTreebook::DoInternalRemovePageRange(size_t pagePos
, size_t subCount
)
360 // Attention: this function is only for a situation when we delete a node
361 // with all its children so pagePos is the node's index and subCount is the
362 // node children count
363 wxASSERT_MSG( pagePos
+ subCount
< m_treeIds
.GetCount(),
364 wxT("Ivalid page index") );
366 wxTreeItemId pageId
= m_treeIds
[pagePos
];
368 m_treeIds
.RemoveAt(pagePos
, subCount
+ 1);
370 if ( m_selection
!= wxNOT_FOUND
)
372 if ( (size_t)m_selection
> pagePos
+ subCount
)
374 // selection is far after the deleted page, so just update the index and move on
375 m_selection
-= 1 + subCount
;
376 if ( m_actualSelection
!= wxNOT_FOUND
)
378 m_actualSelection
-= subCount
+ 1;
381 else if ( (size_t)m_selection
>= pagePos
)
383 wxTreeCtrl
*tree
= GetTreeCtrl();
385 // as selected page is going to be deleted, try to select the next
386 // sibling if exists, if not then the parent
387 wxTreeItemId nodeId
= tree
->GetNextSibling(pageId
);
389 m_selection
= wxNOT_FOUND
;
390 m_actualSelection
= wxNOT_FOUND
;
394 // selecting next siblings
395 tree
->SelectItem(nodeId
);
397 else // no next sibling, select the parent
399 wxTreeItemId parentId
= tree
->GetItemParent(pageId
);
401 if ( parentId
.IsOk() && parentId
!= tree
->GetRootItem() )
403 tree
->SelectItem(parentId
);
405 else // parent is root
407 // we can't select it as it's hidden
408 DoUpdateSelection(false, wxNOT_FOUND
);
412 else if ( m_actualSelection
!= wxNOT_FOUND
&&
413 (size_t)m_actualSelection
>= pagePos
)
415 // nothing to do -- selection is before the deleted node, but
416 // actually shown page (the first (sub)child with page != NULL) is
418 m_actualSelection
= m_selection
;
419 DoSetSelection(m_selection
);
421 //else: nothing to do -- selection is before the deleted node
425 DoUpdateSelection(false, wxNOT_FOUND
);
430 void wxTreebook::DoUpdateSelection(bool bSelect
, int newPos
)
437 else if ( m_selection
== wxNOT_FOUND
&& DoInternalGetPageCount() > 0 )
443 newSelPos
= wxNOT_FOUND
;
446 if ( newSelPos
!= wxNOT_FOUND
)
448 SetSelection((size_t)newSelPos
);
452 wxTreeItemId
wxTreebook::DoInternalGetPage(size_t pagePos
) const
454 if ( pagePos
>= m_treeIds
.GetCount() )
456 // invalid position but ok here, in this internal function, don't assert
457 // (the caller will do it)
458 return wxTreeItemId();
461 return m_treeIds
[pagePos
];
464 int wxTreebook::DoInternalFindPageById(wxTreeItemId pageId
) const
466 const size_t count
= m_treeIds
.GetCount();
467 for ( size_t i
= 0; i
< count
; ++i
)
469 if ( m_treeIds
[i
] == pageId
)
476 bool wxTreebook::IsNodeExpanded(size_t pagePos
) const
478 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
480 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
482 return GetTreeCtrl()->IsExpanded(pageId
);
485 bool wxTreebook::ExpandNode(size_t pagePos
, bool expand
)
487 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
489 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
493 GetTreeCtrl()->Expand( pageId
);
497 GetTreeCtrl()->Collapse( pageId
);
499 // rely on the events generated by wxTreeCtrl to update selection
505 int wxTreebook::GetPageParent(size_t pagePos
) const
507 wxTreeItemId nodeId
= DoInternalGetPage( pagePos
);
508 wxCHECK_MSG( nodeId
.IsOk(), wxNOT_FOUND
, wxT("Invalid page index spacified!") );
510 const wxTreeItemId parent
= GetTreeCtrl()->GetItemParent( nodeId
);
512 return parent
.IsOk() ? DoInternalFindPageById(parent
) : wxNOT_FOUND
;
515 bool wxTreebook::SetPageText(size_t n
, const wxString
& strText
)
517 wxTreeItemId pageId
= DoInternalGetPage(n
);
519 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
521 GetTreeCtrl()->SetItemText(pageId
, strText
);
526 wxString
wxTreebook::GetPageText(size_t n
) const
528 wxTreeItemId pageId
= DoInternalGetPage(n
);
530 wxCHECK_MSG( pageId
.IsOk(), wxString(), wxT("invalid tree item") );
532 return GetTreeCtrl()->GetItemText(pageId
);
535 int wxTreebook::GetPageImage(size_t n
) const
537 wxTreeItemId pageId
= DoInternalGetPage(n
);
539 wxCHECK_MSG( pageId
.IsOk(), wxNOT_FOUND
, wxT("invalid tree item") );
541 return GetTreeCtrl()->GetItemImage(pageId
);
544 bool wxTreebook::SetPageImage(size_t n
, int imageId
)
546 wxTreeItemId pageId
= DoInternalGetPage(n
);
548 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
550 GetTreeCtrl()->SetItemImage(pageId
, imageId
);
555 wxSize
wxTreebook::CalcSizeFromPage(const wxSize
& sizePage
) const
557 const wxSize sizeTree
= GetControllerSize();
559 wxSize size
= sizePage
;
560 size
.x
+= sizeTree
.x
;
565 int wxTreebook::GetSelection() const
570 int wxTreebook::SetSelection(size_t pagePos
)
572 if ( (size_t)m_selection
!= pagePos
)
573 return DoSetSelection(pagePos
);
578 int wxTreebook::DoSetSelection(size_t pagePos
)
580 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), wxNOT_FOUND
,
581 wxT("invalid page index in wxListbook::SetSelection()") );
582 wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(),
583 wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!"));
585 const int oldSel
= m_selection
;
586 wxTreeCtrl
*tree
= GetTreeCtrl();
588 wxTreebookEvent
event(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING
, m_windowId
);
589 event
.SetEventObject(this);
590 event
.SetSelection(pagePos
);
591 event
.SetOldSelection(m_selection
);
593 // don't send the event if the old and new pages are the same; do send it
594 // otherwise and be prepared for it to be vetoed
595 if ( (int)pagePos
== m_selection
||
596 !GetEventHandler()->ProcessEvent(event
) ||
599 // hide the previously shown page
600 wxTreebookPage
* const oldPage
= DoGetCurrentPage();
604 // then show the new one
605 m_selection
= pagePos
;
606 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
609 // find the next page suitable to be shown: the first (grand)child
610 // of this one with a non-NULL associated page
611 wxTreeItemId childId
= m_treeIds
[pagePos
];
612 int actualPagePos
= pagePos
;
613 while ( !page
&& childId
.IsOk() )
615 wxTreeItemIdValue cookie
;
616 childId
= tree
->GetFirstChild( childId
, cookie
);
617 if ( childId
.IsOk() )
619 page
= wxBookCtrlBase::GetPage(++actualPagePos
);
623 m_actualSelection
= page
? actualPagePos
: m_selection
;
629 tree
->SelectItem(DoInternalGetPage(pagePos
));
631 // notify about the (now completed) page change
632 event
.SetEventType(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED
);
633 (void)GetEventHandler()->ProcessEvent(event
);
635 else // page change vetoed
637 // tree selection might have already had changed
638 tree
->SelectItem(DoInternalGetPage(oldSel
));
644 void wxTreebook::SetImageList(wxImageList
*imageList
)
646 wxBookCtrlBase::SetImageList(imageList
);
647 GetTreeCtrl()->SetImageList(imageList
);
650 void wxTreebook::AssignImageList(wxImageList
*imageList
)
652 wxBookCtrlBase::AssignImageList(imageList
);
653 GetTreeCtrl()->SetImageList(imageList
);
656 // ----------------------------------------------------------------------------
658 // ----------------------------------------------------------------------------
660 void wxTreebook::OnTreeSelectionChange(wxTreeEvent
& event
)
662 wxTreeItemId newId
= event
.GetItem();
664 if ( (m_selection
== wxNOT_FOUND
&&
665 (!newId
.IsOk() || newId
== GetTreeCtrl()->GetRootItem())) ||
666 (m_selection
!= wxNOT_FOUND
&& newId
== m_treeIds
[m_selection
]) )
668 // this event can only come when we modify the tree selection ourselves
669 // so we should simply ignore it
673 int newPos
= DoInternalFindPageById(newId
);
675 if ( newPos
!= wxNOT_FOUND
)
676 SetSelection( newPos
);
679 void wxTreebook::OnTreeNodeExpandedCollapsed(wxTreeEvent
& event
)
681 wxTreeItemId nodeId
= event
.GetItem();
682 if ( !nodeId
.IsOk() || nodeId
== GetTreeCtrl()->GetRootItem() )
684 int pagePos
= DoInternalFindPageById(nodeId
);
685 wxCHECK_RET( pagePos
!= wxNOT_FOUND
, wxT("Internal problem in wxTreebook!..") );
687 wxTreebookEvent
ev(GetTreeCtrl()->IsExpanded(nodeId
)
688 ? wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
689 : wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED
,
692 ev
.SetSelection(pagePos
);
693 ev
.SetOldSelection(pagePos
);
694 ev
.SetEventObject(this);
696 GetEventHandler()->ProcessEvent(ev
);
699 // ----------------------------------------------------------------------------
700 // wxTreebook geometry management
701 // ----------------------------------------------------------------------------
703 wxTreebookPage
* wxTreebook::DoGetCurrentPage() const
705 if ( m_selection
== wxNOT_FOUND
)
708 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
709 if ( !page
&& m_actualSelection
!= wxNOT_FOUND
)
711 page
= wxBookCtrlBase::GetPage(m_actualSelection
);
717 #endif // wxUSE_TREEBOOK