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"
32 #include "wx/settings.h"
35 #include "wx/imaglist.h"
37 // ----------------------------------------------------------------------------
38 // various wxWidgets macros
39 // ----------------------------------------------------------------------------
41 // check that the page index is valid
42 #define IS_VALID_PAGE(nPage) ((nPage) < DoInternalGetPageCount())
44 // ----------------------------------------------------------------------------
46 // ----------------------------------------------------------------------------
48 IMPLEMENT_DYNAMIC_CLASS(wxTreebook
, wxBookCtrlBase
)
49 IMPLEMENT_DYNAMIC_CLASS(wxTreebookEvent
, wxNotifyEvent
)
51 #if !WXWIN_COMPATIBILITY_EVENT_TYPES
52 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING
= wxNewEventType();
53 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED
= wxNewEventType();
54 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED
= wxNewEventType();
55 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
= wxNewEventType();
57 const int wxID_TREEBOOKTREEVIEW
= wxNewId();
59 BEGIN_EVENT_TABLE(wxTreebook
, wxBookCtrlBase
)
60 EVT_TREE_SEL_CHANGED (wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeSelectionChange
)
61 EVT_TREE_ITEM_EXPANDED (wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeNodeExpandedCollapsed
)
62 EVT_TREE_ITEM_COLLAPSED(wxID_TREEBOOKTREEVIEW
, wxTreebook::OnTreeNodeExpandedCollapsed
)
64 WX_EVENT_TABLE_CONTROL_CONTAINER(wxTreebook
)
67 // ============================================================================
68 // wxTreebook implementation
69 // ============================================================================
71 WX_DELEGATE_TO_CONTROL_CONTAINER(wxTreebook
, wxControl
)
73 // ----------------------------------------------------------------------------
74 // wxTreebook creation
75 // ----------------------------------------------------------------------------
77 void wxTreebook::Init()
79 m_container
.SetContainerWindow(this);
82 m_actualSelection
= wxNOT_FOUND
;
86 wxTreebook::Create(wxWindow
*parent
,
93 // Check the style flag to have either wxTBK_RIGHT or wxTBK_LEFT
94 if ( (style
& wxBK_ALIGN_MASK
) == wxBK_DEFAULT
)
98 style
|= wxTAB_TRAVERSAL
;
100 // no border for this control, it doesn't look nice together with the tree
101 style
&= ~wxBORDER_MASK
;
102 style
|= wxBORDER_NONE
;
104 if ( !wxControl::Create(parent
, id
, pos
, size
,
105 style
, wxDefaultValidator
, name
) )
108 m_bookctrl
= new wxTreeCtrl
111 wxID_TREEBOOKTREEVIEW
,
115 wxBORDER_SIMPLE
| // On wxMSW this produces a black border which is wrong
121 GetTreeCtrl()->SetQuickBestSize(false); // do full size calculation
122 GetTreeCtrl()->AddRoot(wxEmptyString
); // label doesn't matter, it's hidden
125 // We need to add dummy size event to force possible scrollbar hiding
127 GetEventHandler()->AddPendingEvent(evt
);
134 // insert a new page just before the pagePos
135 bool wxTreebook::InsertPage(size_t pagePos
,
137 const wxString
& text
,
141 return DoInsertPage(pagePos
, page
, text
, bSelect
, imageId
);
144 bool wxTreebook::InsertSubPage(size_t pagePos
,
146 const wxString
& text
,
150 return DoInsertSubPage(pagePos
, page
, text
, bSelect
, imageId
);
153 bool wxTreebook::AddPage(wxWindow
*page
, const wxString
& text
, bool bSelect
,
156 return DoInsertPage(m_treeIds
.GetCount(), page
, text
, bSelect
, imageId
);
159 // insertion time is linear to the number of top-pages
160 bool wxTreebook::AddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
162 return DoAddSubPage(page
, text
, bSelect
, imageId
);
166 bool wxTreebook::DoInsertPage(size_t pagePos
,
168 const wxString
& text
,
172 wxCHECK_MSG( pagePos
<= DoInternalGetPageCount(), false,
173 wxT("Invalid treebook page position") );
175 if ( !wxBookCtrlBase::InsertPage(pagePos
, page
, text
, bSelect
, imageId
) )
178 wxTreeCtrl
*tree
= GetTreeCtrl();
180 if ( pagePos
== DoInternalGetPageCount() )
182 // append the page to the end
183 wxTreeItemId rootId
= tree
->GetRootItem();
185 newId
= tree
->AppendItem(rootId
, text
, imageId
);
187 else // insert the new page before the given one
189 wxTreeItemId nodeId
= m_treeIds
[pagePos
];
191 wxTreeItemId previousId
= tree
->GetPrevSibling(nodeId
);
192 wxTreeItemId parentId
= tree
->GetItemParent(nodeId
);
194 if ( previousId
.IsOk() )
196 // insert before the sibling - previousId
197 newId
= tree
->InsertItem(parentId
, previousId
, text
, imageId
);
199 else // no prev siblings -- insert as a first child
201 wxASSERT_MSG( parentId
.IsOk(), wxT( "Tree has no root node?" ) );
203 newId
= tree
->PrependItem(parentId
, text
, imageId
);
209 //something wrong -> cleaning and returning with false
210 (void)wxBookCtrlBase::DoRemovePage(pagePos
);
212 wxFAIL_MSG( wxT("Failed to insert treebook page") );
216 DoInternalAddPage(pagePos
, page
, newId
);
218 DoUpdateSelection(bSelect
, pagePos
);
223 bool wxTreebook::DoAddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
225 wxTreeCtrl
*tree
= GetTreeCtrl();
227 wxTreeItemId rootId
= tree
->GetRootItem();
229 wxTreeItemId lastNodeId
= tree
->GetLastChild(rootId
);
231 wxCHECK_MSG( lastNodeId
.IsOk(), false,
232 _T("Can't insert sub page when there are no pages") );
234 // now calculate its position (should we save/update it too?)
235 size_t newPos
= tree
->GetCount() -
236 (tree
->GetChildrenCount(lastNodeId
, true) + 1);
238 return DoInsertSubPage(newPos
, page
, text
, bSelect
, imageId
);
241 bool wxTreebook::DoInsertSubPage(size_t pagePos
,
242 wxTreebookPage
*page
,
243 const wxString
& text
,
247 wxTreeItemId parentId
= DoInternalGetPage(pagePos
);
248 wxCHECK_MSG( parentId
.IsOk(), false, wxT("invalid tree item") );
250 wxTreeCtrl
*tree
= GetTreeCtrl();
252 size_t newPos
= pagePos
+ tree
->GetChildrenCount(parentId
, true) + 1;
253 wxASSERT_MSG( newPos
<= DoInternalGetPageCount(),
254 wxT("Internal error in tree insert point calculation") );
256 if ( !wxBookCtrlBase::InsertPage(newPos
, page
, text
, bSelect
, imageId
) )
259 wxTreeItemId newId
= tree
->AppendItem(parentId
, text
, imageId
);
261 tree
->InvalidateBestSize();
265 (void)wxBookCtrlBase::DoRemovePage(newPos
);
267 wxFAIL_MSG( wxT("Failed to insert treebook page") );
271 DoInternalAddPage(newPos
, page
, newId
);
273 DoUpdateSelection(bSelect
, newPos
);
278 bool wxTreebook::DeletePage(size_t pagePos
)
280 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), false, wxT("Invalid tree index") );
282 wxTreebookPage
*oldPage
= DoRemovePage(pagePos
);
291 wxTreebookPage
*wxTreebook::DoRemovePage(size_t pagePos
)
293 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
294 wxCHECK_MSG( pageId
.IsOk(), NULL
, wxT("Invalid tree index") );
296 wxTreebookPage
* oldPage
= GetPage(pagePos
);
297 wxTreeCtrl
*tree
= GetTreeCtrl();
299 size_t subCount
= tree
->GetChildrenCount(pageId
, true);
300 wxASSERT_MSG ( IS_VALID_PAGE(pagePos
+ subCount
),
301 wxT("Internal error in wxTreebook::DoRemovePage") );
303 // here we are going to delete ALL the pages in the range
304 // [pagePos, pagePos + subCount] -- the page and its children
306 // deleting all the pages from the base class
307 for ( size_t i
= 0; i
<= subCount
; ++i
)
309 wxTreebookPage
*page
= wxBookCtrlBase::DoRemovePage(pagePos
);
311 // don't delete the page itself though -- it will be deleted in
312 // DeletePage() when we return
319 DoInternalRemovePageRange(pagePos
, subCount
);
321 tree
->DeleteChildren( pageId
);
322 tree
->Delete( pageId
);
327 bool wxTreebook::DeleteAllPages()
329 wxBookCtrlBase::DeleteAllPages();
332 m_actualSelection
= wxNOT_FOUND
;
334 wxTreeCtrl
*tree
= GetTreeCtrl();
335 tree
->DeleteChildren(tree
->GetRootItem());
340 void wxTreebook::DoInternalAddPage(size_t newPos
,
341 wxTreebookPage
*page
,
344 wxASSERT_MSG( newPos
<= m_treeIds
.GetCount(), wxT("Ivalid index passed to wxTreebook::DoInternalAddPage") );
346 // hide newly inserted page initially (it will be shown when selected)
350 if ( newPos
== m_treeIds
.GetCount() )
353 m_treeIds
.Add(pageId
);
357 m_treeIds
.Insert(pageId
, newPos
);
359 if ( m_selection
!= wxNOT_FOUND
&& newPos
<= (size_t)m_selection
)
361 // selection has been moved one unit toward the end
363 if ( m_actualSelection
!= wxNOT_FOUND
)
366 else if ( m_actualSelection
!= wxNOT_FOUND
&&
367 newPos
<= (size_t)m_actualSelection
)
369 DoSetSelection(m_selection
);
374 void wxTreebook::DoInternalRemovePageRange(size_t pagePos
, size_t subCount
)
376 // Attention: this function is only for a situation when we delete a node
377 // with all its children so pagePos is the node's index and subCount is the
378 // node children count
379 wxASSERT_MSG( pagePos
+ subCount
< m_treeIds
.GetCount(),
380 wxT("Ivalid page index") );
382 wxTreeItemId pageId
= m_treeIds
[pagePos
];
384 m_treeIds
.RemoveAt(pagePos
, subCount
+ 1);
386 if ( m_selection
!= wxNOT_FOUND
)
388 if ( (size_t)m_selection
> pagePos
+ subCount
)
390 // selection is far after the deleted page, so just update the index and move on
391 m_selection
-= 1 + subCount
;
392 if ( m_actualSelection
!= wxNOT_FOUND
)
394 m_actualSelection
-= subCount
+ 1;
397 else if ( (size_t)m_selection
>= pagePos
)
399 wxTreeCtrl
*tree
= GetTreeCtrl();
401 // as selected page is going to be deleted, try to select the next
402 // sibling if exists, if not then the parent
403 wxTreeItemId nodeId
= tree
->GetNextSibling(pageId
);
405 m_selection
= wxNOT_FOUND
;
406 m_actualSelection
= wxNOT_FOUND
;
410 // selecting next siblings
411 tree
->SelectItem(nodeId
);
413 else // no next sibling, select the parent
415 wxTreeItemId parentId
= tree
->GetItemParent(pageId
);
417 if ( parentId
.IsOk() && parentId
!= tree
->GetRootItem() )
419 tree
->SelectItem(parentId
);
421 else // parent is root
423 // we can't select it as it's hidden
424 DoUpdateSelection(false, wxNOT_FOUND
);
428 else if ( m_actualSelection
!= wxNOT_FOUND
&&
429 (size_t)m_actualSelection
>= pagePos
)
431 // nothing to do -- selection is before the deleted node, but
432 // actually shown page (the first (sub)child with page != NULL) is
434 m_actualSelection
= m_selection
;
435 DoSetSelection(m_selection
);
437 //else: nothing to do -- selection is before the deleted node
441 DoUpdateSelection(false, wxNOT_FOUND
);
446 void wxTreebook::DoUpdateSelection(bool bSelect
, int newPos
)
453 else if ( m_selection
== wxNOT_FOUND
&& DoInternalGetPageCount() > 0 )
459 newSelPos
= wxNOT_FOUND
;
462 if ( newSelPos
!= wxNOT_FOUND
)
464 SetSelection((size_t)newSelPos
);
468 wxTreeItemId
wxTreebook::DoInternalGetPage(size_t pagePos
) const
470 if ( pagePos
>= m_treeIds
.GetCount() )
472 // invalid position but ok here, in this internal function, don't assert
473 // (the caller will do it)
474 return wxTreeItemId();
477 return m_treeIds
[pagePos
];
480 int wxTreebook::DoInternalFindPageById(wxTreeItemId pageId
) const
482 const size_t count
= m_treeIds
.GetCount();
483 for ( size_t i
= 0; i
< count
; ++i
)
485 if ( m_treeIds
[i
] == pageId
)
492 bool wxTreebook::IsNodeExpanded(size_t pagePos
) const
494 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
496 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
498 return GetTreeCtrl()->IsExpanded(pageId
);
501 bool wxTreebook::ExpandNode(size_t pagePos
, bool expand
)
503 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
505 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
509 GetTreeCtrl()->Expand( pageId
);
513 GetTreeCtrl()->Collapse( pageId
);
515 // rely on the events generated by wxTreeCtrl to update selection
521 int wxTreebook::GetPageParent(size_t pagePos
) const
523 wxTreeItemId nodeId
= DoInternalGetPage( pagePos
);
524 wxCHECK_MSG( nodeId
.IsOk(), wxNOT_FOUND
, wxT("Invalid page index spacified!") );
526 const wxTreeItemId parent
= GetTreeCtrl()->GetItemParent( nodeId
);
528 return parent
.IsOk() ? DoInternalFindPageById(parent
) : wxNOT_FOUND
;
531 bool wxTreebook::SetPageText(size_t n
, const wxString
& strText
)
533 wxTreeItemId pageId
= DoInternalGetPage(n
);
535 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
537 GetTreeCtrl()->SetItemText(pageId
, strText
);
542 wxString
wxTreebook::GetPageText(size_t n
) const
544 wxTreeItemId pageId
= DoInternalGetPage(n
);
546 wxCHECK_MSG( pageId
.IsOk(), wxString(), wxT("invalid tree item") );
548 return GetTreeCtrl()->GetItemText(pageId
);
551 int wxTreebook::GetPageImage(size_t n
) const
553 wxTreeItemId pageId
= DoInternalGetPage(n
);
555 wxCHECK_MSG( pageId
.IsOk(), wxNOT_FOUND
, wxT("invalid tree item") );
557 return GetTreeCtrl()->GetItemImage(pageId
);
560 bool wxTreebook::SetPageImage(size_t n
, int imageId
)
562 wxTreeItemId pageId
= DoInternalGetPage(n
);
564 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
566 GetTreeCtrl()->SetItemImage(pageId
, imageId
);
571 wxSize
wxTreebook::CalcSizeFromPage(const wxSize
& sizePage
) const
573 const wxSize sizeTree
= GetControllerSize();
575 wxSize size
= sizePage
;
576 size
.x
+= sizeTree
.x
;
581 int wxTreebook::GetSelection() const
586 int wxTreebook::SetSelection(size_t pagePos
)
588 if ( (size_t)m_selection
!= pagePos
)
589 return DoSetSelection(pagePos
);
594 int wxTreebook::DoSetSelection(size_t pagePos
)
596 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), wxNOT_FOUND
,
597 wxT("invalid page index in wxListbook::SetSelection()") );
598 wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(),
599 wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!"));
601 const int oldSel
= m_selection
;
602 wxTreeCtrl
*tree
= GetTreeCtrl();
604 wxTreebookEvent
event(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING
, m_windowId
);
605 event
.SetEventObject(this);
606 event
.SetSelection(pagePos
);
607 event
.SetOldSelection(m_selection
);
609 // don't send the event if the old and new pages are the same; do send it
610 // otherwise and be prepared for it to be vetoed
611 if ( (int)pagePos
== m_selection
||
612 !GetEventHandler()->ProcessEvent(event
) ||
615 // hide the previously shown page
616 wxTreebookPage
* const oldPage
= DoGetCurrentPage();
620 // then show the new one
621 m_selection
= pagePos
;
622 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
625 // find the next page suitable to be shown: the first (grand)child
626 // of this one with a non-NULL associated page
627 wxTreeItemId childId
= m_treeIds
[pagePos
];
628 int actualPagePos
= pagePos
;
629 while ( !page
&& childId
.IsOk() )
631 wxTreeItemIdValue cookie
;
632 childId
= tree
->GetFirstChild( childId
, cookie
);
633 if ( childId
.IsOk() )
635 page
= wxBookCtrlBase::GetPage(++actualPagePos
);
639 m_actualSelection
= page
? actualPagePos
: m_selection
;
645 tree
->SelectItem(DoInternalGetPage(pagePos
));
647 // notify about the (now completed) page change
648 event
.SetEventType(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED
);
649 (void)GetEventHandler()->ProcessEvent(event
);
651 else // page change vetoed
653 // tree selection might have already had changed
654 if ( oldSel
!= wxNOT_FOUND
)
655 tree
->SelectItem(DoInternalGetPage(oldSel
));
661 wxTreebookPage
*wxTreebook::DoGetCurrentPage() const
663 if ( m_selection
== wxNOT_FOUND
)
666 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
667 if ( !page
&& m_actualSelection
!= wxNOT_FOUND
)
669 page
= wxBookCtrlBase::GetPage(m_actualSelection
);
675 void wxTreebook::SetImageList(wxImageList
*imageList
)
677 wxBookCtrlBase::SetImageList(imageList
);
678 GetTreeCtrl()->SetImageList(imageList
);
681 void wxTreebook::AssignImageList(wxImageList
*imageList
)
683 wxBookCtrlBase::AssignImageList(imageList
);
684 GetTreeCtrl()->SetImageList(imageList
);
687 // ----------------------------------------------------------------------------
689 // ----------------------------------------------------------------------------
691 void wxTreebook::OnTreeSelectionChange(wxTreeEvent
& event
)
693 wxTreeItemId newId
= event
.GetItem();
695 if ( (m_selection
== wxNOT_FOUND
&&
696 (!newId
.IsOk() || newId
== GetTreeCtrl()->GetRootItem())) ||
697 (m_selection
!= wxNOT_FOUND
&& newId
== m_treeIds
[m_selection
]) )
699 // this event can only come when we modify the tree selection ourselves
700 // so we should simply ignore it
704 int newPos
= DoInternalFindPageById(newId
);
706 if ( newPos
!= wxNOT_FOUND
)
707 SetSelection( newPos
);
710 void wxTreebook::OnTreeNodeExpandedCollapsed(wxTreeEvent
& event
)
712 wxTreeItemId nodeId
= event
.GetItem();
713 if ( !nodeId
.IsOk() || nodeId
== GetTreeCtrl()->GetRootItem() )
715 int pagePos
= DoInternalFindPageById(nodeId
);
716 wxCHECK_RET( pagePos
!= wxNOT_FOUND
, wxT("Internal problem in wxTreebook!..") );
718 wxTreebookEvent
ev(GetTreeCtrl()->IsExpanded(nodeId
)
719 ? wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
720 : wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED
,
723 ev
.SetSelection(pagePos
);
724 ev
.SetOldSelection(pagePos
);
725 ev
.SetEventObject(this);
727 GetEventHandler()->ProcessEvent(ev
);
730 // ----------------------------------------------------------------------------
731 // wxTreebook geometry management
732 // ----------------------------------------------------------------------------
734 int wxTreebook::HitTest(wxPoint
const & pt
, long * flags
) const
736 int pagePos
= wxNOT_FOUND
;
739 *flags
= wxBK_HITTEST_NOWHERE
;
741 // convert from wxTreebook coorindates to wxTreeCtrl ones
742 const wxTreeCtrl
* const tree
= GetTreeCtrl();
743 const wxPoint treePt
= tree
->ScreenToClient(ClientToScreen(pt
));
745 // is it over the tree?
746 if ( wxRect(tree
->GetSize()).Inside(treePt
) )
749 wxTreeItemId id
= tree
->HitTest(treePt
, flagsTree
);
751 if ( id
.IsOk() && (flagsTree
& wxTREE_HITTEST_ONITEM
) )
753 pagePos
= DoInternalFindPageById(id
);
758 if ( pagePos
!= wxNOT_FOUND
)
761 if ( flagsTree
& (wxTREE_HITTEST_ONITEMBUTTON
|
762 wxTREE_HITTEST_ONITEMICON
|
763 wxTREE_HITTEST_ONITEMSTATEICON
) )
764 *flags
|= wxBK_HITTEST_ONICON
;
766 if ( flagsTree
& wxTREE_HITTEST_ONITEMLABEL
)
767 *flags
|= wxBK_HITTEST_ONLABEL
;
770 else // not over the tree
772 if ( flags
&& GetPageRect().Inside( pt
) )
773 *flags
|= wxBK_HITTEST_ONPAGE
;
779 #endif // wxUSE_TREEBOOK