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
, wxControl
)
45 IMPLEMENT_DYNAMIC_CLASS(wxTreebookEvent
, wxNotifyEvent
)
47 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING
= wxNewEventType();
48 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED
= wxNewEventType();
49 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED
= wxNewEventType();
50 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
= wxNewEventType();
51 const int wxID_TREEBOOKTREEVIEW
= wxNewId();
53 BEGIN_EVENT_TABLE(wxTreebook
, wxBookCtrlBase
)
54 EVT_SIZE(wxTreebook::OnSize
)
55 EVT_TREE_SEL_CHANGED (wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeSelectionChange
)
56 EVT_TREE_ITEM_EXPANDED (wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeNodeExpandedCollapsed
)
57 EVT_TREE_ITEM_COLLAPSED(wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeNodeExpandedCollapsed
)
60 // ============================================================================
61 // wxTreebook implementation
62 // ============================================================================
64 // ----------------------------------------------------------------------------
65 // wxTreebook creation
66 // ----------------------------------------------------------------------------
68 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
& wxTBK_RIGHT
)
86 wxASSERT_MSG( !(style
& wxTBK_LEFT
),
87 _T("RIGHT and LEFT can't be used together") );
94 // no border for this control, it doesn't look nice together with the tree
95 style
&= ~wxBORDER_MASK
;
96 style
|= wxBORDER_NONE
;
98 if ( !wxControl::Create(parent
, id
, pos
, size
,
99 style
, wxDefaultValidator
, name
) )
102 m_tree
= new wxTreeCtrl
105 wxID_TREEBOOKTREEVIEW
,
113 m_tree
->AddRoot(wxEmptyString
); // label doesn't matter, it's hidden
116 // see listbook.h for origins of that
117 // On XP with themes enabled the GetViewRect used in GetListSize to
118 // determine the space needed for the list view will incorrectly return
119 // (0,0,0,0) the first time. So send a pending event so OnSize will be
120 // called again after the window is ready to go. Technically we don't
121 // need to do this on non-XP windows, but if things are already sized
122 // correctly then nothing changes and so there is no harm.
124 GetEventHandler()->AddPendingEvent(evt
);
131 // insert a new page just before the pagePos
132 bool wxTreebook::InsertPage(size_t pagePos
,
134 const wxString
& text
,
138 return DoInsertPage(pagePos
, page
, text
, bSelect
, imageId
);
141 bool wxTreebook::AddSubPage(size_t pagePos
,
143 const wxString
& text
,
147 return DoInsertSubPage(pagePos
, page
, text
, bSelect
, imageId
);
150 bool wxTreebook::AddPage(wxWindow
*page
, const wxString
& text
, bool bSelect
,
153 return DoInsertPage(m_treeIds
.GetCount(), page
, text
, bSelect
, imageId
);
156 // insertion time is linear to the number of top-pages
157 bool wxTreebook::AddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
159 return DoAddSubPage(page
, text
, bSelect
, imageId
);
163 bool wxTreebook::DoInsertPage(size_t pagePos
,
165 const wxString
& text
,
169 wxCHECK_MSG( pagePos
<= DoInternalGetPageCount(), false,
170 wxT("Invalid treebook page position") );
172 if ( !wxBookCtrlBase::InsertPage(pagePos
, page
, text
, bSelect
, imageId
) )
176 if ( pagePos
== DoInternalGetPageCount() )
178 // append the page to the end
179 wxTreeItemId rootId
= m_tree
->GetRootItem();
181 newId
= m_tree
->AppendItem(rootId
, text
, imageId
);
183 else // insert the new page before the given one
185 wxTreeItemId nodeId
= m_treeIds
[pagePos
];
187 wxTreeItemId previousId
= m_tree
->GetPrevSibling(nodeId
);
188 wxTreeItemId parentId
= m_tree
->GetItemParent(nodeId
);
190 if ( previousId
.IsOk() )
192 // insert before the sibling - previousId
193 newId
= m_tree
->InsertItem(parentId
, previousId
, text
, imageId
);
195 else // no prev siblings -- insert as a first child
197 wxASSERT_MSG( parentId
.IsOk(), wxT( "Tree has no root node?" ) );
199 newId
= m_tree
->PrependItem(parentId
, text
, imageId
);
205 //something wrong -> cleaning and returning with false
206 (void)wxBookCtrlBase::DoRemovePage(pagePos
);
208 wxFAIL_MSG( wxT("Failed to insert treebook page") );
212 DoInternalAddPage(pagePos
, page
, newId
);
214 DoUpdateSelection(bSelect
, pagePos
);
216 m_tree
->InvalidateBestSize();
221 bool wxTreebook::DoAddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
223 wxTreeItemId rootId
= m_tree
->GetRootItem();
225 wxTreeItemId lastNodeId
= m_tree
->GetLastChild(rootId
);
227 wxCHECK_MSG( lastNodeId
.IsOk(), false,
228 _T("Can't insert sub page when there are no pages") );
230 // now calculate its position (should we save/update it too?)
231 size_t newPos
= m_tree
->GetCount() -
232 (m_tree
->GetChildrenCount(lastNodeId
, true) + 1);
234 return DoInsertSubPage(newPos
, page
, text
, bSelect
, imageId
);
237 bool wxTreebook::DoInsertSubPage(size_t pagePos
,
238 wxTreebookPage
*page
,
239 const wxString
& text
,
243 wxTreeItemId parentId
= DoInternalGetPage(pagePos
);
244 wxCHECK_MSG( parentId
.IsOk(), false, wxT("invalid tree item") );
246 size_t newPos
= pagePos
+ m_tree
->GetChildrenCount(parentId
, true) + 1;
247 wxASSERT_MSG( newPos
<= DoInternalGetPageCount(),
248 wxT("Internal error in tree insert point calculation") );
250 if ( !wxBookCtrlBase::InsertPage(newPos
, page
, text
, bSelect
, imageId
) )
253 wxTreeItemId newId
= m_tree
->AppendItem(parentId
, text
, imageId
);
257 (void)wxBookCtrlBase::DoRemovePage(newPos
);
259 wxFAIL_MSG( wxT("Failed to insert treebook page") );
263 DoInternalAddPage(newPos
, page
, newId
);
265 DoUpdateSelection(bSelect
, newPos
);
267 m_tree
->InvalidateBestSize();
272 bool wxTreebook::DeletePage(size_t pagePos
)
274 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), false, wxT("Invalid tree index") );
276 wxTreebookPage
*oldPage
= DoRemovePage(pagePos
);
285 wxTreebookPage
*wxTreebook::DoRemovePage(size_t pagePos
)
287 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
288 wxCHECK_MSG( pageId
.IsOk(), NULL
, wxT("Invalid tree index") );
290 wxTreebookPage
* oldPage
= GetPage(pagePos
);
292 size_t subCount
= m_tree
->GetChildrenCount(pageId
, true);
293 wxASSERT_MSG ( IS_VALID_PAGE(pagePos
+ subCount
),
294 wxT("Internal error in wxTreebook::DoRemovePage") );
296 // here we are going to delete ALL the pages in the range
297 // [pagePos, pagePos + subCount] -- the page and its children
299 // deleting all the pages from the base class
300 for ( size_t i
= 0; i
<= subCount
; ++i
)
302 wxTreebookPage
*page
= wxBookCtrlBase::DoRemovePage(pagePos
);
304 // don't delete the page itself though -- it will be deleted in
305 // DeletePage() when we return
312 DoInternalRemovePageRange(pagePos
, subCount
);
314 m_tree
->DeleteChildren( pageId
);
315 m_tree
->Delete( pageId
);
316 m_tree
->InvalidateBestSize();
321 bool wxTreebook::DeleteAllPages()
323 wxBookCtrlBase::DeleteAllPages();
326 m_actualSelection
= wxNOT_FOUND
;
328 m_tree
->DeleteChildren(m_tree
->GetRootItem());
333 void wxTreebook::DoInternalAddPage(size_t newPos
,
334 wxTreebookPage
*page
,
337 wxASSERT_MSG( newPos
<= m_treeIds
.GetCount(), wxT("Ivalid index passed to wxTreebook::DoInternalAddPage") );
339 // hide newly inserted page initially (it will be shown when selected)
343 if ( newPos
== m_treeIds
.GetCount() )
346 m_treeIds
.Add(pageId
);
350 m_treeIds
.Insert(pageId
, newPos
);
352 if ( m_selection
!= wxNOT_FOUND
&& newPos
<= (size_t)m_selection
)
354 // selection has been moved one unit toward the end
356 if ( m_actualSelection
!= wxNOT_FOUND
)
359 else if ( m_actualSelection
!= wxNOT_FOUND
&&
360 newPos
<= (size_t)m_actualSelection
)
362 DoSetSelection(m_selection
);
367 void wxTreebook::DoInternalRemovePageRange(size_t pagePos
, size_t subCount
)
369 // Attention: this function is only for a situation when we delete a node
370 // with all its children so pagePos is the node's index and subCount is the
371 // node children count
372 wxASSERT_MSG( pagePos
+ subCount
< m_treeIds
.GetCount(),
373 wxT("Ivalid page index") );
375 wxTreeItemId pageId
= m_treeIds
[pagePos
];
377 m_treeIds
.RemoveAt(pagePos
, subCount
+ 1);
379 if ( m_selection
!= wxNOT_FOUND
)
381 if ( (size_t)m_selection
> pagePos
+ subCount
)
383 // selection is far after the deleted page, so just update the index and move on
384 m_selection
-= 1 + subCount
;
385 if ( m_actualSelection
!= wxNOT_FOUND
)
387 m_actualSelection
-= subCount
+ 1;
390 else if ( (size_t)m_selection
>= pagePos
)
392 // as selected page is going to be deleted, try to select the next
393 // sibling if exists, if not then the parent
394 wxTreeItemId nodeId
= m_tree
->GetNextSibling(pageId
);
396 m_selection
= wxNOT_FOUND
;
397 m_actualSelection
= wxNOT_FOUND
;
401 // selecting next siblings
402 m_tree
->SelectItem(nodeId
);
404 else // no next sibling, select the parent
406 wxTreeItemId parentId
= m_tree
->GetItemParent(pageId
);
408 if ( parentId
.IsOk() && parentId
!= m_tree
->GetRootItem() )
410 m_tree
->SelectItem(parentId
);
412 else // parent is root
414 // we can't select it as it's hidden
415 DoUpdateSelection(false, wxNOT_FOUND
);
419 else if ( m_actualSelection
!= wxNOT_FOUND
&&
420 (size_t)m_actualSelection
>= pagePos
)
422 // nothing to do -- selection is before the deleted node, but
423 // actually shown page (the first (sub)child with page != NULL) is
425 m_actualSelection
= m_selection
;
426 DoSetSelection(m_selection
);
428 //else: nothing to do -- selection is before the deleted node
432 DoUpdateSelection(false, wxNOT_FOUND
);
437 void wxTreebook::DoUpdateSelection(bool bSelect
, int newPos
)
444 else if ( m_selection
== wxNOT_FOUND
&& DoInternalGetPageCount() > 0 )
450 newSelPos
= wxNOT_FOUND
;
453 if ( newSelPos
!= wxNOT_FOUND
)
455 SetSelection((size_t)newSelPos
);
459 wxTreeItemId
wxTreebook::DoInternalGetPage(size_t pagePos
) const
461 if ( pagePos
>= m_treeIds
.GetCount() )
463 // invalid position but ok here, in this internal function, don't assert
464 // (the caller will do it)
465 return wxTreeItemId();
468 return m_treeIds
[pagePos
];
471 int wxTreebook::DoInternalFindPageById(wxTreeItemId pageId
) const
473 const size_t count
= m_treeIds
.GetCount();
474 for ( size_t i
= 0; i
< count
; ++i
)
476 if ( m_treeIds
[i
] == pageId
)
483 bool wxTreebook::IsNodeExpanded(size_t pagePos
) const
485 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
487 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
489 return m_tree
->IsExpanded(pageId
);
492 bool wxTreebook::ExpandNode(size_t pagePos
, bool expand
)
494 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
496 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
500 m_tree
->Expand( pageId
);
504 m_tree
->Collapse( pageId
);
506 // rely on the events generated by wxTreeCtrl to update selection
512 int wxTreebook::GetPageParent(size_t pagePos
) const
514 wxTreeItemId nodeId
= DoInternalGetPage( pagePos
);
515 wxCHECK_MSG( nodeId
.IsOk(), wxNOT_FOUND
, wxT("Invalid page index spacified!") );
517 const wxTreeItemId parent
= m_tree
->GetItemParent( nodeId
);
519 return parent
.IsOk() ? DoInternalFindPageById(parent
) : wxNOT_FOUND
;
522 bool wxTreebook::SetPageText(size_t n
, const wxString
& strText
)
524 wxTreeItemId pageId
= DoInternalGetPage(n
);
526 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
528 m_tree
->SetItemText(pageId
, strText
);
533 wxString
wxTreebook::GetPageText(size_t n
) const
535 wxTreeItemId pageId
= DoInternalGetPage(n
);
537 wxCHECK_MSG( pageId
.IsOk(), wxString(), wxT("invalid tree item") );
539 return m_tree
->GetItemText(pageId
);
542 int wxTreebook::GetPageImage(size_t n
) const
544 wxTreeItemId pageId
= DoInternalGetPage(n
);
546 wxCHECK_MSG( pageId
.IsOk(), wxNOT_FOUND
, wxT("invalid tree item") );
548 return m_tree
->GetItemImage(pageId
);
551 bool wxTreebook::SetPageImage(size_t n
, int imageId
)
553 wxTreeItemId pageId
= DoInternalGetPage(n
);
555 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
557 m_tree
->SetItemImage(pageId
, imageId
);
562 wxSize
wxTreebook::CalcSizeFromPage(const wxSize
& sizePage
) const
564 const wxSize sizeTree
= GetTreeSize();
566 wxSize size
= sizePage
;
567 size
.x
+= sizeTree
.x
;
572 int wxTreebook::GetSelection() const
577 int wxTreebook::SetSelection(size_t pagePos
)
579 if ( (size_t)m_selection
!= pagePos
)
580 return DoSetSelection(pagePos
);
585 int wxTreebook::DoSetSelection(size_t pagePos
)
587 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), wxNOT_FOUND
,
588 wxT("invalid page index in wxListbook::SetSelection()") );
589 wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(),
590 wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!"));
592 const int oldSel
= m_selection
;
594 wxTreebookEvent
event(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING
, m_windowId
);
595 event
.SetEventObject(this);
596 event
.SetSelection(pagePos
);
597 event
.SetOldSelection(m_selection
);
599 // don't send the event if the old and new pages are the same; do send it
600 // otherwise and be prepared for it to be vetoed
601 if ( (int)pagePos
== m_selection
||
602 !GetEventHandler()->ProcessEvent(event
) ||
605 // hide the previously shown page
606 wxTreebookPage
* const oldPage
= DoGetCurrentPage();
610 // then show the new one
611 m_selection
= pagePos
;
612 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
615 // find the next page suitable to be shown: the first (grand)child
616 // of this one with a non-NULL associated page
617 wxTreeItemId childId
= m_treeIds
[pagePos
];
618 m_actualSelection
= pagePos
;
619 while ( !page
&& childId
.IsOk() )
621 wxTreeItemIdValue cookie
;
622 childId
= m_tree
->GetFirstChild( childId
, cookie
);
623 if ( childId
.IsOk() )
625 page
= wxBookCtrlBase::GetPage(++m_actualSelection
);
629 wxASSERT_MSG( page
, wxT("no page to show found!") );
634 page
->SetSize(GetPageRect());
638 m_tree
->SelectItem(DoInternalGetPage(pagePos
));
640 // notify about the (now completed) page change
641 event
.SetEventType(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED
);
642 (void)GetEventHandler()->ProcessEvent(event
);
644 else // page change vetoed
646 // tree selection might have already had changed
647 m_tree
->SelectItem(DoInternalGetPage(oldSel
));
653 void wxTreebook::SetImageList(wxImageList
*imageList
)
655 wxBookCtrlBase::SetImageList(imageList
);
656 m_tree
->SetImageList(imageList
);
659 void wxTreebook::AssignImageList(wxImageList
*imageList
)
661 wxBookCtrlBase::AssignImageList(imageList
);
662 m_tree
->SetImageList(imageList
);
665 // ----------------------------------------------------------------------------
667 // ----------------------------------------------------------------------------
669 void wxTreebook::OnTreeSelectionChange(wxTreeEvent
& event
)
671 wxTreeItemId newId
= event
.GetItem();
673 if ( (m_selection
== wxNOT_FOUND
&&
674 (!newId
.IsOk() || newId
== m_tree
->GetRootItem())) ||
675 (m_selection
!= wxNOT_FOUND
&& newId
== m_treeIds
[m_selection
]) )
677 // this event can only come when we modify the tree selection ourselves
678 // so we should simply ignore it
682 int newPos
= DoInternalFindPageById(newId
);
684 if ( newPos
!= wxNOT_FOUND
)
685 SetSelection( newPos
);
688 void wxTreebook::OnTreeNodeExpandedCollapsed(wxTreeEvent
& event
)
690 wxTreeItemId nodeId
= event
.GetItem();
691 if ( !nodeId
.IsOk() || nodeId
== m_tree
->GetRootItem() )
693 int pagePos
= DoInternalFindPageById(nodeId
);
694 wxCHECK_RET( pagePos
!= wxNOT_FOUND
, wxT("Internal problem in wxTreebook!..") );
696 wxTreebookEvent
ev(m_tree
->IsExpanded(nodeId
)
697 ? wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
698 : wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED
,
701 ev
.SetSelection(pagePos
);
702 ev
.SetOldSelection(pagePos
);
703 ev
.SetEventObject(this);
705 GetEventHandler()->ProcessEvent(ev
);
708 // ----------------------------------------------------------------------------
709 // wxTreebook geometry management
710 // ----------------------------------------------------------------------------
712 wxSize
wxTreebook::GetTreeSize() const
714 const wxSize sizeClient
= GetClientSize(),
715 sizeBorder
= m_tree
->GetSize() - m_tree
->GetClientSize(),
716 sizeTree
= m_tree
->GetBestSize() + sizeBorder
;
721 size
.y
= sizeClient
.y
;
726 wxRect
wxTreebook::GetPageRect() const
728 const wxSize sizeTree
= m_tree
->GetSize();
731 wxRect
rectPage(pt
, GetClientSize());
732 switch ( GetWindowStyle() & wxTBK_ALIGN_MASK
)
735 wxFAIL_MSG( _T("unexpected wxTreebook alignment") );
739 rectPage
.x
= sizeTree
.x
; // + MARGIN;
743 rectPage
.width
-= sizeTree
.x
; // + MARGIN;
750 void wxTreebook::OnSize(wxSizeEvent
& event
)
756 // we're not fully created yet
760 // resize the list control and the page area to fit inside our new size
761 const wxSize sizeClient
= GetClientSize(),
762 sizeBorder
= m_tree
->GetSize() - m_tree
->GetClientSize(),
763 sizeTree
= GetTreeSize();
765 m_tree
->SetClientSize( sizeTree
.x
- sizeBorder
.x
, sizeTree
.y
- sizeBorder
.y
);
767 const wxSize sizeNew
= m_tree
->GetSize();
769 switch ( GetWindowStyle() & wxTBK_ALIGN_MASK
)
772 wxFAIL_MSG( _T("unexpected wxTreebook alignment") );
776 // posTree is already ok
780 posTree
.x
= sizeClient
.x
- sizeNew
.x
;
784 if ( m_tree
->GetPosition() != posTree
)
785 m_tree
->Move(posTree
);
787 // resize the currently shown page
788 wxTreebookPage
*page
= DoGetCurrentPage();
791 wxRect rectPage
= GetPageRect();
792 page
->SetSize(rectPage
);
796 wxTreebookPage
* wxTreebook::DoGetCurrentPage() const
798 if ( m_selection
== wxNOT_FOUND
)
801 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
802 if ( !page
&& m_actualSelection
!= wxNOT_FOUND
)
804 page
= wxBookCtrlBase::GetPage(m_actualSelection
);
810 #endif // wxUSE_TREEBOOK