1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/treebkg.cpp
3 // Purpose: generic implementation of wxTreebook
4 // Author: Evgeniy Tarassov, Vadim Zeitlin
7 // Copyright: (c) 2005 Vadim Zeitlin <vadim@wxwidgets.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
28 #include "wx/treebook.h"
31 #include "wx/settings.h"
34 #include "wx/imaglist.h"
36 // ----------------------------------------------------------------------------
37 // various wxWidgets macros
38 // ----------------------------------------------------------------------------
40 // check that the page index is valid
41 #define IS_VALID_PAGE(nPage) ((nPage) < DoInternalGetPageCount())
43 // ----------------------------------------------------------------------------
45 // ----------------------------------------------------------------------------
47 IMPLEMENT_DYNAMIC_CLASS(wxTreebook
, wxBookCtrlBase
)
49 wxDEFINE_EVENT( wxEVT_TREEBOOK_PAGE_CHANGING
, wxBookCtrlEvent
);
50 wxDEFINE_EVENT( wxEVT_TREEBOOK_PAGE_CHANGED
, wxBookCtrlEvent
);
51 wxDEFINE_EVENT( wxEVT_TREEBOOK_NODE_COLLAPSED
, wxBookCtrlEvent
);
52 wxDEFINE_EVENT( wxEVT_TREEBOOK_NODE_EXPANDED
, wxBookCtrlEvent
);
54 BEGIN_EVENT_TABLE(wxTreebook
, wxBookCtrlBase
)
55 EVT_TREE_SEL_CHANGED (wxID_ANY
, wxTreebook::OnTreeSelectionChange
)
56 EVT_TREE_ITEM_EXPANDED (wxID_ANY
, wxTreebook::OnTreeNodeExpandedCollapsed
)
57 EVT_TREE_ITEM_COLLAPSED(wxID_ANY
, wxTreebook::OnTreeNodeExpandedCollapsed
)
60 // ============================================================================
61 // wxTreebook implementation
62 // ============================================================================
64 // ----------------------------------------------------------------------------
65 // wxTreebook creation
66 // ----------------------------------------------------------------------------
68 void wxTreebook::Init()
71 m_actualSelection
= wxNOT_FOUND
;
75 wxTreebook::Create(wxWindow
*parent
,
82 // Check the style flag to have either wxTBK_RIGHT or wxTBK_LEFT
83 if ( (style
& wxBK_ALIGN_MASK
) == wxBK_DEFAULT
)
87 style
|= wxTAB_TRAVERSAL
;
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
108 GetTreeCtrl()->SetQuickBestSize(false); // do full size calculation
109 GetTreeCtrl()->AddRoot(wxEmptyString
); // label doesn't matter, it's hidden
112 // We need to add dummy size event to force possible scrollbar hiding
114 GetEventHandler()->AddPendingEvent(evt
);
121 // insert a new page just before the pagePos
122 bool wxTreebook::InsertPage(size_t pagePos
,
124 const wxString
& text
,
128 return DoInsertPage(pagePos
, page
, text
, bSelect
, imageId
);
131 bool wxTreebook::InsertSubPage(size_t pagePos
,
133 const wxString
& text
,
137 return DoInsertSubPage(pagePos
, page
, text
, bSelect
, imageId
);
140 bool wxTreebook::AddPage(wxWindow
*page
, const wxString
& text
, bool bSelect
,
143 return DoInsertPage(m_treeIds
.GetCount(), page
, text
, bSelect
, imageId
);
146 // insertion time is linear to the number of top-pages
147 bool wxTreebook::AddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
149 return DoAddSubPage(page
, text
, bSelect
, imageId
);
153 bool wxTreebook::DoInsertPage(size_t pagePos
,
155 const wxString
& text
,
159 wxCHECK_MSG( pagePos
<= DoInternalGetPageCount(), false,
160 wxT("Invalid treebook page position") );
162 if ( !wxBookCtrlBase::InsertPage(pagePos
, page
, text
, bSelect
, imageId
) )
165 wxTreeCtrl
*tree
= GetTreeCtrl();
167 if ( pagePos
== DoInternalGetPageCount() )
169 // append the page to the end
170 wxTreeItemId rootId
= tree
->GetRootItem();
172 newId
= tree
->AppendItem(rootId
, text
, imageId
);
174 else // insert the new page before the given one
176 wxTreeItemId nodeId
= m_treeIds
[pagePos
];
178 wxTreeItemId previousId
= tree
->GetPrevSibling(nodeId
);
179 wxTreeItemId parentId
= tree
->GetItemParent(nodeId
);
181 if ( previousId
.IsOk() )
183 // insert before the sibling - previousId
184 newId
= tree
->InsertItem(parentId
, previousId
, text
, imageId
);
186 else // no prev siblings -- insert as a first child
188 wxASSERT_MSG( parentId
.IsOk(), wxT( "Tree has no root node?" ) );
190 newId
= tree
->PrependItem(parentId
, text
, imageId
);
196 //something wrong -> cleaning and returning with false
197 (void)wxBookCtrlBase::DoRemovePage(pagePos
);
199 wxFAIL_MSG( wxT("Failed to insert treebook page") );
203 DoInternalAddPage(pagePos
, page
, newId
);
205 DoUpdateSelection(bSelect
, pagePos
);
210 bool wxTreebook::DoAddSubPage(wxWindow
*page
, const wxString
& text
, bool bSelect
, int imageId
)
212 wxTreeCtrl
*tree
= GetTreeCtrl();
214 wxTreeItemId rootId
= tree
->GetRootItem();
216 wxTreeItemId lastNodeId
= tree
->GetLastChild(rootId
);
218 wxCHECK_MSG( lastNodeId
.IsOk(), false,
219 wxT("Can't insert sub page when there are no pages") );
221 // now calculate its position (should we save/update it too?)
222 size_t newPos
= tree
->GetCount() -
223 (tree
->GetChildrenCount(lastNodeId
, true) + 1);
225 return DoInsertSubPage(newPos
, page
, text
, bSelect
, imageId
);
228 bool wxTreebook::DoInsertSubPage(size_t pagePos
,
229 wxTreebookPage
*page
,
230 const wxString
& text
,
234 wxTreeItemId parentId
= DoInternalGetPage(pagePos
);
235 wxCHECK_MSG( parentId
.IsOk(), false, wxT("invalid tree item") );
237 wxTreeCtrl
*tree
= GetTreeCtrl();
239 size_t newPos
= pagePos
+ tree
->GetChildrenCount(parentId
, true) + 1;
240 wxASSERT_MSG( newPos
<= DoInternalGetPageCount(),
241 wxT("Internal error in tree insert point calculation") );
243 if ( !wxBookCtrlBase::InsertPage(newPos
, page
, text
, bSelect
, imageId
) )
246 wxTreeItemId newId
= tree
->AppendItem(parentId
, text
, imageId
);
250 (void)wxBookCtrlBase::DoRemovePage(newPos
);
252 wxFAIL_MSG( wxT("Failed to insert treebook page") );
256 DoInternalAddPage(newPos
, page
, newId
);
258 DoUpdateSelection(bSelect
, newPos
);
263 bool wxTreebook::DeletePage(size_t pagePos
)
265 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), false, wxT("Invalid tree index") );
267 wxTreebookPage
*oldPage
= DoRemovePage(pagePos
);
276 wxTreebookPage
*wxTreebook::DoRemovePage(size_t pagePos
)
278 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
279 wxCHECK_MSG( pageId
.IsOk(), NULL
, wxT("Invalid tree index") );
281 wxTreebookPage
* oldPage
= GetPage(pagePos
);
282 wxTreeCtrl
*tree
= GetTreeCtrl();
284 size_t subCount
= tree
->GetChildrenCount(pageId
, true);
285 wxASSERT_MSG ( IS_VALID_PAGE(pagePos
+ subCount
),
286 wxT("Internal error in wxTreebook::DoRemovePage") );
288 // here we are going to delete ALL the pages in the range
289 // [pagePos, pagePos + subCount] -- the page and its children
291 // deleting all the pages from the base class
292 for ( size_t i
= 0; i
<= subCount
; ++i
)
294 wxTreebookPage
*page
= wxBookCtrlBase::DoRemovePage(pagePos
);
296 // don't delete the page itself though -- it will be deleted in
297 // DeletePage() when we return
304 DoInternalRemovePageRange(pagePos
, subCount
);
306 tree
->DeleteChildren( pageId
);
307 tree
->Delete( pageId
);
312 bool wxTreebook::DeleteAllPages()
314 wxBookCtrlBase::DeleteAllPages();
317 m_actualSelection
= wxNOT_FOUND
;
319 wxTreeCtrl
*tree
= GetTreeCtrl();
320 tree
->DeleteChildren(tree
->GetRootItem());
325 void wxTreebook::DoInternalAddPage(size_t newPos
,
326 wxTreebookPage
*page
,
329 wxASSERT_MSG( newPos
<= m_treeIds
.GetCount(), wxT("Ivalid index passed to wxTreebook::DoInternalAddPage") );
331 // hide newly inserted page initially (it will be shown when selected)
335 if ( newPos
== m_treeIds
.GetCount() )
338 m_treeIds
.Add(pageId
);
342 m_treeIds
.Insert(pageId
, newPos
);
344 if ( m_selection
!= wxNOT_FOUND
&& newPos
<= (size_t)m_selection
)
346 // selection has been moved one unit toward the end
348 if ( m_actualSelection
!= wxNOT_FOUND
)
351 else if ( m_actualSelection
!= wxNOT_FOUND
&&
352 newPos
<= (size_t)m_actualSelection
)
354 DoSetSelection(m_selection
);
359 void wxTreebook::DoInternalRemovePageRange(size_t pagePos
, size_t subCount
)
361 // Attention: this function is only for a situation when we delete a node
362 // with all its children so pagePos is the node's index and subCount is the
363 // node children count
364 wxASSERT_MSG( pagePos
+ subCount
< m_treeIds
.GetCount(),
365 wxT("Ivalid page index") );
367 wxTreeItemId pageId
= m_treeIds
[pagePos
];
369 m_treeIds
.RemoveAt(pagePos
, subCount
+ 1);
371 if ( m_selection
!= wxNOT_FOUND
)
373 if ( (size_t)m_selection
> pagePos
+ subCount
)
375 // selection is far after the deleted page, so just update the index and move on
376 m_selection
-= 1 + subCount
;
377 if ( m_actualSelection
!= wxNOT_FOUND
)
379 m_actualSelection
-= subCount
+ 1;
382 else if ( (size_t)m_selection
>= pagePos
)
384 wxTreeCtrl
*tree
= GetTreeCtrl();
386 // as selected page is going to be deleted, try to select the next
387 // sibling if exists, if not then the parent
388 wxTreeItemId nodeId
= tree
->GetNextSibling(pageId
);
390 m_selection
= wxNOT_FOUND
;
391 m_actualSelection
= wxNOT_FOUND
;
395 // selecting next siblings
396 tree
->SelectItem(nodeId
);
398 else // no next sibling, select the parent
400 wxTreeItemId parentId
= tree
->GetItemParent(pageId
);
402 if ( parentId
.IsOk() && parentId
!= tree
->GetRootItem() )
404 tree
->SelectItem(parentId
);
406 else // parent is root
408 // we can't select it as it's hidden
409 DoUpdateSelection(false, wxNOT_FOUND
);
413 else if ( m_actualSelection
!= wxNOT_FOUND
&&
414 (size_t)m_actualSelection
>= pagePos
)
416 // nothing to do -- selection is before the deleted node, but
417 // actually shown page (the first (sub)child with page != NULL) is
419 m_actualSelection
= m_selection
;
421 // send event as documented
422 DoSetSelection(m_selection
, SetSelection_SendEvent
);
424 //else: nothing to do -- selection is before the deleted node
428 DoUpdateSelection(false, wxNOT_FOUND
);
433 void wxTreebook::DoUpdateSelection(bool bSelect
, int newPos
)
440 else if ( m_selection
== wxNOT_FOUND
&& DoInternalGetPageCount() > 0 )
446 newSelPos
= wxNOT_FOUND
;
449 if ( newSelPos
!= wxNOT_FOUND
)
451 SetSelection((size_t)newSelPos
);
455 wxTreeItemId
wxTreebook::DoInternalGetPage(size_t pagePos
) const
457 if ( pagePos
>= m_treeIds
.GetCount() )
459 // invalid position but ok here, in this internal function, don't assert
460 // (the caller will do it)
461 return wxTreeItemId();
464 return m_treeIds
[pagePos
];
467 int wxTreebook::DoInternalFindPageById(wxTreeItemId pageId
) const
469 const size_t count
= m_treeIds
.GetCount();
470 for ( size_t i
= 0; i
< count
; ++i
)
472 if ( m_treeIds
[i
] == pageId
)
479 bool wxTreebook::IsNodeExpanded(size_t pagePos
) const
481 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
483 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
485 return GetTreeCtrl()->IsExpanded(pageId
);
488 bool wxTreebook::ExpandNode(size_t pagePos
, bool expand
)
490 wxTreeItemId pageId
= DoInternalGetPage(pagePos
);
492 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
496 GetTreeCtrl()->Expand( pageId
);
500 GetTreeCtrl()->Collapse( pageId
);
502 // rely on the events generated by wxTreeCtrl to update selection
508 int wxTreebook::GetPageParent(size_t pagePos
) const
510 wxTreeItemId nodeId
= DoInternalGetPage( pagePos
);
511 wxCHECK_MSG( nodeId
.IsOk(), wxNOT_FOUND
, wxT("Invalid page index spacified!") );
513 const wxTreeItemId parent
= GetTreeCtrl()->GetItemParent( nodeId
);
515 return parent
.IsOk() ? DoInternalFindPageById(parent
) : wxNOT_FOUND
;
518 bool wxTreebook::SetPageText(size_t n
, const wxString
& strText
)
520 wxTreeItemId pageId
= DoInternalGetPage(n
);
522 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
524 GetTreeCtrl()->SetItemText(pageId
, strText
);
529 wxString
wxTreebook::GetPageText(size_t n
) const
531 wxTreeItemId pageId
= DoInternalGetPage(n
);
533 wxCHECK_MSG( pageId
.IsOk(), wxString(), wxT("invalid tree item") );
535 return GetTreeCtrl()->GetItemText(pageId
);
538 int wxTreebook::GetPageImage(size_t n
) const
540 wxTreeItemId pageId
= DoInternalGetPage(n
);
542 wxCHECK_MSG( pageId
.IsOk(), wxNOT_FOUND
, wxT("invalid tree item") );
544 return GetTreeCtrl()->GetItemImage(pageId
);
547 bool wxTreebook::SetPageImage(size_t n
, int imageId
)
549 wxTreeItemId pageId
= DoInternalGetPage(n
);
551 wxCHECK_MSG( pageId
.IsOk(), false, wxT("invalid tree item") );
553 GetTreeCtrl()->SetItemImage(pageId
, imageId
);
558 int wxTreebook::DoSetSelection(size_t pagePos
, int flags
)
560 wxCHECK_MSG( IS_VALID_PAGE(pagePos
), wxNOT_FOUND
,
561 wxT("invalid page index in wxListbook::DoSetSelection()") );
562 wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(),
563 wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!"));
565 wxBookCtrlEvent
event(wxEVT_TREEBOOK_PAGE_CHANGING
, m_windowId
);
566 const int oldSel
= m_selection
;
567 wxTreeCtrl
*tree
= GetTreeCtrl();
568 bool allowed
= false;
570 if (flags
& SetSelection_SendEvent
)
572 event
.SetEventObject(this);
573 event
.SetSelection(pagePos
);
574 event
.SetOldSelection(m_selection
);
576 // don't send the event if the old and new pages are the same; do send it
577 // otherwise and be prepared for it to be vetoed
578 allowed
= (int)pagePos
== m_selection
||
579 !GetEventHandler()->ProcessEvent(event
) ||
583 if ( !(flags
& SetSelection_SendEvent
) || allowed
)
585 // hide the previously shown page
586 wxTreebookPage
* const oldPage
= DoGetCurrentPage();
590 // then show the new one
591 m_selection
= pagePos
;
592 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
595 // find the next page suitable to be shown: the first (grand)child
596 // of this one with a non-NULL associated page
597 wxTreeItemId childId
= m_treeIds
[pagePos
];
598 int actualPagePos
= pagePos
;
599 while ( !page
&& childId
.IsOk() )
601 wxTreeItemIdValue cookie
;
602 childId
= tree
->GetFirstChild( childId
, cookie
);
603 if ( childId
.IsOk() )
605 page
= wxBookCtrlBase::GetPage(++actualPagePos
);
609 m_actualSelection
= page
? actualPagePos
: m_selection
;
615 tree
->SelectItem(DoInternalGetPage(pagePos
));
617 if (flags
& SetSelection_SendEvent
)
619 // notify about the (now completed) page change
620 event
.SetEventType(wxEVT_TREEBOOK_PAGE_CHANGED
);
621 (void)GetEventHandler()->ProcessEvent(event
);
624 else if ( (flags
& SetSelection_SendEvent
) && !allowed
) // page change vetoed
626 // tree selection might have already had changed
627 if ( oldSel
!= wxNOT_FOUND
)
628 tree
->SelectItem(DoInternalGetPage(oldSel
));
634 wxTreebookPage
*wxTreebook::DoGetCurrentPage() const
636 if ( m_selection
== wxNOT_FOUND
)
639 wxTreebookPage
*page
= wxBookCtrlBase::GetPage(m_selection
);
640 if ( !page
&& m_actualSelection
!= wxNOT_FOUND
)
642 page
= wxBookCtrlBase::GetPage(m_actualSelection
);
648 void wxTreebook::SetImageList(wxImageList
*imageList
)
650 wxBookCtrlBase::SetImageList(imageList
);
651 GetTreeCtrl()->SetImageList(imageList
);
654 void wxTreebook::AssignImageList(wxImageList
*imageList
)
656 wxBookCtrlBase::AssignImageList(imageList
);
657 GetTreeCtrl()->SetImageList(imageList
);
660 // ----------------------------------------------------------------------------
662 // ----------------------------------------------------------------------------
664 void wxTreebook::OnTreeSelectionChange(wxTreeEvent
& event
)
666 if ( event
.GetEventObject() != m_bookctrl
)
672 wxTreeItemId newId
= event
.GetItem();
674 if ( (m_selection
== wxNOT_FOUND
&&
675 (!newId
.IsOk() || newId
== GetTreeCtrl()->GetRootItem())) ||
676 (m_selection
!= wxNOT_FOUND
&& newId
== m_treeIds
[m_selection
]) )
678 // this event can only come when we modify the tree selection ourselves
679 // so we should simply ignore it
683 int newPos
= DoInternalFindPageById(newId
);
685 if ( newPos
!= wxNOT_FOUND
)
686 SetSelection( newPos
);
689 void wxTreebook::OnTreeNodeExpandedCollapsed(wxTreeEvent
& event
)
691 if ( event
.GetEventObject() != m_bookctrl
)
697 wxTreeItemId nodeId
= event
.GetItem();
698 if ( !nodeId
.IsOk() || nodeId
== GetTreeCtrl()->GetRootItem() )
700 int pagePos
= DoInternalFindPageById(nodeId
);
701 wxCHECK_RET( pagePos
!= wxNOT_FOUND
, wxT("Internal problem in wxTreebook!..") );
703 wxBookCtrlEvent
ev(GetTreeCtrl()->IsExpanded(nodeId
)
704 ? wxEVT_TREEBOOK_NODE_EXPANDED
705 : wxEVT_TREEBOOK_NODE_COLLAPSED
,
708 ev
.SetSelection(pagePos
);
709 ev
.SetOldSelection(pagePos
);
710 ev
.SetEventObject(this);
712 GetEventHandler()->ProcessEvent(ev
);
715 // ----------------------------------------------------------------------------
716 // wxTreebook geometry management
717 // ----------------------------------------------------------------------------
719 int wxTreebook::HitTest(wxPoint
const & pt
, long * flags
) const
721 int pagePos
= wxNOT_FOUND
;
724 *flags
= wxBK_HITTEST_NOWHERE
;
726 // convert from wxTreebook coorindates to wxTreeCtrl ones
727 const wxTreeCtrl
* const tree
= GetTreeCtrl();
728 const wxPoint treePt
= tree
->ScreenToClient(ClientToScreen(pt
));
730 // is it over the tree?
731 if ( wxRect(tree
->GetSize()).Contains(treePt
) )
734 wxTreeItemId id
= tree
->HitTest(treePt
, flagsTree
);
736 if ( id
.IsOk() && (flagsTree
& wxTREE_HITTEST_ONITEM
) )
738 pagePos
= DoInternalFindPageById(id
);
743 if ( pagePos
!= wxNOT_FOUND
)
746 if ( flagsTree
& (wxTREE_HITTEST_ONITEMBUTTON
|
747 wxTREE_HITTEST_ONITEMICON
|
748 wxTREE_HITTEST_ONITEMSTATEICON
) )
749 *flags
|= wxBK_HITTEST_ONICON
;
751 if ( flagsTree
& wxTREE_HITTEST_ONITEMLABEL
)
752 *flags
|= wxBK_HITTEST_ONLABEL
;
755 else // not over the tree
757 if ( flags
&& GetPageRect().Contains( pt
) )
758 *flags
|= wxBK_HITTEST_ONPAGE
;
764 #endif // wxUSE_TREEBOOK