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 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING = wxNewEventType();
52 const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED = wxNewEventType();
53 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED = wxNewEventType();
54 const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED = wxNewEventType();
56 BEGIN_EVENT_TABLE(wxTreebook, wxBookCtrlBase)
57 EVT_TREE_SEL_CHANGED (wxID_ANY, wxTreebook::OnTreeSelectionChange)
58 EVT_TREE_ITEM_EXPANDED (wxID_ANY, wxTreebook::OnTreeNodeExpandedCollapsed)
59 EVT_TREE_ITEM_COLLAPSED(wxID_ANY, wxTreebook::OnTreeNodeExpandedCollapsed)
62 // ============================================================================
63 // wxTreebook implementation
64 // ============================================================================
66 // ----------------------------------------------------------------------------
67 // wxTreebook creation
68 // ----------------------------------------------------------------------------
70 void wxTreebook::Init()
73 m_actualSelection = wxNOT_FOUND;
77 wxTreebook::Create(wxWindow *parent,
84 // Check the style flag to have either wxTBK_RIGHT or wxTBK_LEFT
85 if ( (style & wxBK_ALIGN_MASK) == wxBK_DEFAULT )
89 style |= wxTAB_TRAVERSAL;
91 // no border for this control, it doesn't look nice together with the tree
92 style &= ~wxBORDER_MASK;
93 style |= wxBORDER_NONE;
95 if ( !wxControl::Create(parent, id, pos, size,
96 style, wxDefaultValidator, name) )
99 m_bookctrl = new wxTreeCtrl
106 wxBORDER_SIMPLE | // On wxMSW this produces a black border which is wrong
112 GetTreeCtrl()->SetQuickBestSize(false); // do full size calculation
113 GetTreeCtrl()->AddRoot(wxEmptyString); // label doesn't matter, it's hidden
116 // We need to add dummy size event to force possible scrollbar hiding
118 GetEventHandler()->AddPendingEvent(evt);
125 // insert a new page just before the pagePos
126 bool wxTreebook::InsertPage(size_t pagePos,
128 const wxString& text,
132 return DoInsertPage(pagePos, page, text, bSelect, imageId);
135 bool wxTreebook::InsertSubPage(size_t pagePos,
137 const wxString& text,
141 return DoInsertSubPage(pagePos, page, text, bSelect, imageId);
144 bool wxTreebook::AddPage(wxWindow *page, const wxString& text, bool bSelect,
147 return DoInsertPage(m_treeIds.GetCount(), page, text, bSelect, imageId);
150 // insertion time is linear to the number of top-pages
151 bool wxTreebook::AddSubPage(wxWindow *page, const wxString& text, bool bSelect, int imageId)
153 return DoAddSubPage(page, text, bSelect, imageId);
157 bool wxTreebook::DoInsertPage(size_t pagePos,
159 const wxString& text,
163 wxCHECK_MSG( pagePos <= DoInternalGetPageCount(), false,
164 wxT("Invalid treebook page position") );
166 if ( !wxBookCtrlBase::InsertPage(pagePos, page, text, bSelect, imageId) )
169 wxTreeCtrl *tree = GetTreeCtrl();
171 if ( pagePos == DoInternalGetPageCount() )
173 // append the page to the end
174 wxTreeItemId rootId = tree->GetRootItem();
176 newId = tree->AppendItem(rootId, text, imageId);
178 else // insert the new page before the given one
180 wxTreeItemId nodeId = m_treeIds[pagePos];
182 wxTreeItemId previousId = tree->GetPrevSibling(nodeId);
183 wxTreeItemId parentId = tree->GetItemParent(nodeId);
185 if ( previousId.IsOk() )
187 // insert before the sibling - previousId
188 newId = tree->InsertItem(parentId, previousId, text, imageId);
190 else // no prev siblings -- insert as a first child
192 wxASSERT_MSG( parentId.IsOk(), wxT( "Tree has no root node?" ) );
194 newId = tree->PrependItem(parentId, text, imageId);
200 //something wrong -> cleaning and returning with false
201 (void)wxBookCtrlBase::DoRemovePage(pagePos);
203 wxFAIL_MSG( wxT("Failed to insert treebook page") );
207 DoInternalAddPage(pagePos, page, newId);
209 DoUpdateSelection(bSelect, pagePos);
214 bool wxTreebook::DoAddSubPage(wxWindow *page, const wxString& text, bool bSelect, int imageId)
216 wxTreeCtrl *tree = GetTreeCtrl();
218 wxTreeItemId rootId = tree->GetRootItem();
220 wxTreeItemId lastNodeId = tree->GetLastChild(rootId);
222 wxCHECK_MSG( lastNodeId.IsOk(), false,
223 _T("Can't insert sub page when there are no pages") );
225 // now calculate its position (should we save/update it too?)
226 size_t newPos = tree->GetCount() -
227 (tree->GetChildrenCount(lastNodeId, true) + 1);
229 return DoInsertSubPage(newPos, page, text, bSelect, imageId);
232 bool wxTreebook::DoInsertSubPage(size_t pagePos,
233 wxTreebookPage *page,
234 const wxString& text,
238 wxTreeItemId parentId = DoInternalGetPage(pagePos);
239 wxCHECK_MSG( parentId.IsOk(), false, wxT("invalid tree item") );
241 wxTreeCtrl *tree = GetTreeCtrl();
243 size_t newPos = pagePos + tree->GetChildrenCount(parentId, true) + 1;
244 wxASSERT_MSG( newPos <= DoInternalGetPageCount(),
245 wxT("Internal error in tree insert point calculation") );
247 if ( !wxBookCtrlBase::InsertPage(newPos, page, text, bSelect, imageId) )
250 wxTreeItemId newId = tree->AppendItem(parentId, text, imageId);
254 (void)wxBookCtrlBase::DoRemovePage(newPos);
256 wxFAIL_MSG( wxT("Failed to insert treebook page") );
260 DoInternalAddPage(newPos, page, newId);
262 DoUpdateSelection(bSelect, newPos);
267 bool wxTreebook::DeletePage(size_t pagePos)
269 wxCHECK_MSG( IS_VALID_PAGE(pagePos), false, wxT("Invalid tree index") );
271 wxTreebookPage *oldPage = DoRemovePage(pagePos);
280 wxTreebookPage *wxTreebook::DoRemovePage(size_t pagePos)
282 wxTreeItemId pageId = DoInternalGetPage(pagePos);
283 wxCHECK_MSG( pageId.IsOk(), NULL, wxT("Invalid tree index") );
285 wxTreebookPage * oldPage = GetPage(pagePos);
286 wxTreeCtrl *tree = GetTreeCtrl();
288 size_t subCount = tree->GetChildrenCount(pageId, true);
289 wxASSERT_MSG ( IS_VALID_PAGE(pagePos + subCount),
290 wxT("Internal error in wxTreebook::DoRemovePage") );
292 // here we are going to delete ALL the pages in the range
293 // [pagePos, pagePos + subCount] -- the page and its children
295 // deleting all the pages from the base class
296 for ( size_t i = 0; i <= subCount; ++i )
298 wxTreebookPage *page = wxBookCtrlBase::DoRemovePage(pagePos);
300 // don't delete the page itself though -- it will be deleted in
301 // DeletePage() when we return
308 DoInternalRemovePageRange(pagePos, subCount);
310 tree->DeleteChildren( pageId );
311 tree->Delete( pageId );
316 bool wxTreebook::DeleteAllPages()
318 wxBookCtrlBase::DeleteAllPages();
321 m_actualSelection = wxNOT_FOUND;
323 wxTreeCtrl *tree = GetTreeCtrl();
324 tree->DeleteChildren(tree->GetRootItem());
329 void wxTreebook::DoInternalAddPage(size_t newPos,
330 wxTreebookPage *page,
333 wxASSERT_MSG( newPos <= m_treeIds.GetCount(), wxT("Ivalid index passed to wxTreebook::DoInternalAddPage") );
335 // hide newly inserted page initially (it will be shown when selected)
339 if ( newPos == m_treeIds.GetCount() )
342 m_treeIds.Add(pageId);
346 m_treeIds.Insert(pageId, newPos);
348 if ( m_selection != wxNOT_FOUND && newPos <= (size_t)m_selection )
350 // selection has been moved one unit toward the end
352 if ( m_actualSelection != wxNOT_FOUND )
355 else if ( m_actualSelection != wxNOT_FOUND &&
356 newPos <= (size_t)m_actualSelection )
358 DoSetSelection(m_selection);
363 void wxTreebook::DoInternalRemovePageRange(size_t pagePos, size_t subCount)
365 // Attention: this function is only for a situation when we delete a node
366 // with all its children so pagePos is the node's index and subCount is the
367 // node children count
368 wxASSERT_MSG( pagePos + subCount < m_treeIds.GetCount(),
369 wxT("Ivalid page index") );
371 wxTreeItemId pageId = m_treeIds[pagePos];
373 m_treeIds.RemoveAt(pagePos, subCount + 1);
375 if ( m_selection != wxNOT_FOUND )
377 if ( (size_t)m_selection > pagePos + subCount)
379 // selection is far after the deleted page, so just update the index and move on
380 m_selection -= 1 + subCount;
381 if ( m_actualSelection != wxNOT_FOUND)
383 m_actualSelection -= subCount + 1;
386 else if ( (size_t)m_selection >= pagePos )
388 wxTreeCtrl *tree = GetTreeCtrl();
390 // as selected page is going to be deleted, try to select the next
391 // sibling if exists, if not then the parent
392 wxTreeItemId nodeId = tree->GetNextSibling(pageId);
394 m_selection = wxNOT_FOUND;
395 m_actualSelection = wxNOT_FOUND;
399 // selecting next siblings
400 tree->SelectItem(nodeId);
402 else // no next sibling, select the parent
404 wxTreeItemId parentId = tree->GetItemParent(pageId);
406 if ( parentId.IsOk() && parentId != tree->GetRootItem() )
408 tree->SelectItem(parentId);
410 else // parent is root
412 // we can't select it as it's hidden
413 DoUpdateSelection(false, wxNOT_FOUND);
417 else if ( m_actualSelection != wxNOT_FOUND &&
418 (size_t)m_actualSelection >= pagePos )
420 // nothing to do -- selection is before the deleted node, but
421 // actually shown page (the first (sub)child with page != NULL) is
423 m_actualSelection = m_selection;
425 // send event as documented
426 DoSetSelection(m_selection, SetSelection_SendEvent);
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 GetTreeCtrl()->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 GetTreeCtrl()->Expand( pageId );
504 GetTreeCtrl()->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 = GetTreeCtrl()->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 GetTreeCtrl()->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 GetTreeCtrl()->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 GetTreeCtrl()->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 GetTreeCtrl()->SetItemImage(pageId, imageId);
562 wxSize wxTreebook::CalcSizeFromPage(const wxSize& sizePage) const
564 const wxSize sizeTree = GetControllerSize();
566 wxSize size = sizePage;
567 size.x += sizeTree.x;
572 int wxTreebook::GetSelection() const
577 int wxTreebook::DoSetSelection(size_t pagePos, int flags)
579 wxCHECK_MSG( IS_VALID_PAGE(pagePos), wxNOT_FOUND,
580 wxT("invalid page index in wxListbook::DoSetSelection()") );
581 wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(),
582 wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!"));
584 wxTreebookEvent event(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING, m_windowId);
585 const int oldSel = m_selection;
586 wxTreeCtrl *tree = GetTreeCtrl();
587 bool allowed = false;
589 if (flags & SetSelection_SendEvent)
591 event.SetEventObject(this);
592 event.SetSelection(pagePos);
593 event.SetOldSelection(m_selection);
595 // don't send the event if the old and new pages are the same; do send it
596 // otherwise and be prepared for it to be vetoed
597 allowed = (int)pagePos == m_selection ||
598 !GetEventHandler()->ProcessEvent(event) ||
602 if ( !(flags & SetSelection_SendEvent) || allowed )
604 // hide the previously shown page
605 wxTreebookPage * const oldPage = DoGetCurrentPage();
609 // then show the new one
610 m_selection = pagePos;
611 wxTreebookPage *page = wxBookCtrlBase::GetPage(m_selection);
614 // find the next page suitable to be shown: the first (grand)child
615 // of this one with a non-NULL associated page
616 wxTreeItemId childId = m_treeIds[pagePos];
617 int actualPagePos = pagePos;
618 while ( !page && childId.IsOk() )
620 wxTreeItemIdValue cookie;
621 childId = tree->GetFirstChild( childId, cookie );
622 if ( childId.IsOk() )
624 page = wxBookCtrlBase::GetPage(++actualPagePos);
628 m_actualSelection = page ? actualPagePos : m_selection;
634 tree->SelectItem(DoInternalGetPage(pagePos));
636 if (flags & SetSelection_SendEvent)
638 // notify about the (now completed) page change
639 event.SetEventType(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED);
640 (void)GetEventHandler()->ProcessEvent(event);
643 else if ( (flags & SetSelection_SendEvent) && !allowed) // page change vetoed
645 // tree selection might have already had changed
646 if ( oldSel != wxNOT_FOUND )
647 tree->SelectItem(DoInternalGetPage(oldSel));
653 wxTreebookPage *wxTreebook::DoGetCurrentPage() const
655 if ( m_selection == wxNOT_FOUND )
658 wxTreebookPage *page = wxBookCtrlBase::GetPage(m_selection);
659 if ( !page && m_actualSelection != wxNOT_FOUND )
661 page = wxBookCtrlBase::GetPage(m_actualSelection);
667 void wxTreebook::SetImageList(wxImageList *imageList)
669 wxBookCtrlBase::SetImageList(imageList);
670 GetTreeCtrl()->SetImageList(imageList);
673 void wxTreebook::AssignImageList(wxImageList *imageList)
675 wxBookCtrlBase::AssignImageList(imageList);
676 GetTreeCtrl()->SetImageList(imageList);
679 // ----------------------------------------------------------------------------
681 // ----------------------------------------------------------------------------
683 void wxTreebook::OnTreeSelectionChange(wxTreeEvent& event)
685 if ( event.GetEventObject() != m_bookctrl )
691 wxTreeItemId newId = event.GetItem();
693 if ( (m_selection == wxNOT_FOUND &&
694 (!newId.IsOk() || newId == GetTreeCtrl()->GetRootItem())) ||
695 (m_selection != wxNOT_FOUND && newId == m_treeIds[m_selection]) )
697 // this event can only come when we modify the tree selection ourselves
698 // so we should simply ignore it
702 int newPos = DoInternalFindPageById(newId);
704 if ( newPos != wxNOT_FOUND )
705 SetSelection( newPos );
708 void wxTreebook::OnTreeNodeExpandedCollapsed(wxTreeEvent & event)
710 if ( event.GetEventObject() != m_bookctrl )
716 wxTreeItemId nodeId = event.GetItem();
717 if ( !nodeId.IsOk() || nodeId == GetTreeCtrl()->GetRootItem() )
719 int pagePos = DoInternalFindPageById(nodeId);
720 wxCHECK_RET( pagePos != wxNOT_FOUND, wxT("Internal problem in wxTreebook!..") );
722 wxTreebookEvent ev(GetTreeCtrl()->IsExpanded(nodeId)
723 ? wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED
724 : wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED,
727 ev.SetSelection(pagePos);
728 ev.SetOldSelection(pagePos);
729 ev.SetEventObject(this);
731 GetEventHandler()->ProcessEvent(ev);
734 // ----------------------------------------------------------------------------
735 // wxTreebook geometry management
736 // ----------------------------------------------------------------------------
738 int wxTreebook::HitTest(wxPoint const & pt, long * flags) const
740 int pagePos = wxNOT_FOUND;
743 *flags = wxBK_HITTEST_NOWHERE;
745 // convert from wxTreebook coorindates to wxTreeCtrl ones
746 const wxTreeCtrl * const tree = GetTreeCtrl();
747 const wxPoint treePt = tree->ScreenToClient(ClientToScreen(pt));
749 // is it over the tree?
750 if ( wxRect(tree->GetSize()).Contains(treePt) )
753 wxTreeItemId id = tree->HitTest(treePt, flagsTree);
755 if ( id.IsOk() && (flagsTree & wxTREE_HITTEST_ONITEM) )
757 pagePos = DoInternalFindPageById(id);
762 if ( pagePos != wxNOT_FOUND )
765 if ( flagsTree & (wxTREE_HITTEST_ONITEMBUTTON |
766 wxTREE_HITTEST_ONITEMICON |
767 wxTREE_HITTEST_ONITEMSTATEICON) )
768 *flags |= wxBK_HITTEST_ONICON;
770 if ( flagsTree & wxTREE_HITTEST_ONITEMLABEL )
771 *flags |= wxBK_HITTEST_ONLABEL;
774 else // not over the tree
776 if ( flags && GetPageRect().Contains( pt ) )
777 *flags |= wxBK_HITTEST_ONPAGE;
783 #endif // wxUSE_TREEBOOK