/////////////////////////////////////////////////////////////////////////////// // Name: src/generic/treebkg.cpp // Purpose: generic implementation of wxTreebook // Author: Evgeniy Tarassov, Vadim Zeitlin // Modified by: // Created: 2005-09-15 // RCS-ID: $Id: treebkg.cpp 54645 2008-07-15 21:29:10Z JS $ // Copyright: (c) 2005 Vadim Zeitlin // Licence: wxWindows licence /////////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_TREEBOOK #include "wx/treebook.h" #ifndef WX_PRECOMP #include "wx/settings.h" #endif #include "wx/imaglist.h" // ---------------------------------------------------------------------------- // various wxWidgets macros // ---------------------------------------------------------------------------- // check that the page index is valid #define IS_VALID_PAGE(nPage) ((nPage) < DoInternalGetPageCount()) // ---------------------------------------------------------------------------- // event table // ---------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxTreebook, wxBookCtrlBase) IMPLEMENT_DYNAMIC_CLASS(wxTreebookEvent, wxNotifyEvent) #if !WXWIN_COMPATIBILITY_EVENT_TYPES const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING = wxNewEventType(); const wxEventType wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED = wxNewEventType(); const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED = wxNewEventType(); const wxEventType wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED = wxNewEventType(); #endif BEGIN_EVENT_TABLE(wxTreebook, wxBookCtrlBase) EVT_TREE_SEL_CHANGED (wxID_ANY, wxTreebook::OnTreeSelectionChange) EVT_TREE_ITEM_EXPANDED (wxID_ANY, wxTreebook::OnTreeNodeExpandedCollapsed) EVT_TREE_ITEM_COLLAPSED(wxID_ANY, wxTreebook::OnTreeNodeExpandedCollapsed) WX_EVENT_TABLE_CONTROL_CONTAINER(wxTreebook) END_EVENT_TABLE() // ============================================================================ // wxTreebook implementation // ============================================================================ WX_DELEGATE_TO_CONTROL_CONTAINER(wxTreebook, wxControl) // ---------------------------------------------------------------------------- // wxTreebook creation // ---------------------------------------------------------------------------- void wxTreebook::Init() { m_container.SetContainerWindow(this); m_selection = m_actualSelection = wxNOT_FOUND; } bool wxTreebook::Create(wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name) { // Check the style flag to have either wxTBK_RIGHT or wxTBK_LEFT if ( (style & wxBK_ALIGN_MASK) == wxBK_DEFAULT ) { style |= wxBK_LEFT; } style |= wxTAB_TRAVERSAL; // no border for this control, it doesn't look nice together with the tree style &= ~wxBORDER_MASK; style |= wxBORDER_NONE; if ( !wxControl::Create(parent, id, pos, size, style, wxDefaultValidator, name) ) return false; #ifdef __WXMSW__ long treeStyle = GetThemedBorderStyle(); #else long treeStyle = wxBORDER_SUNKEN; #endif m_bookctrl = new wxTreeCtrl ( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, treeStyle| wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT | wxTR_SINGLE ); GetTreeCtrl()->SetQuickBestSize(false); // do full size calculation GetTreeCtrl()->AddRoot(wxEmptyString); // label doesn't matter, it's hidden #ifdef __WXMSW__ // We need to add dummy size event to force possible scrollbar hiding wxSizeEvent evt; GetEventHandler()->AddPendingEvent(evt); #endif return true; } // insert a new page just before the pagePos bool wxTreebook::InsertPage(size_t pagePos, wxWindow *page, const wxString& text, bool bSelect, int imageId) { return DoInsertPage(pagePos, page, text, bSelect, imageId); } bool wxTreebook::InsertSubPage(size_t pagePos, wxWindow *page, const wxString& text, bool bSelect, int imageId) { return DoInsertSubPage(pagePos, page, text, bSelect, imageId); } bool wxTreebook::AddPage(wxWindow *page, const wxString& text, bool bSelect, int imageId) { return DoInsertPage(m_treeIds.GetCount(), page, text, bSelect, imageId); } // insertion time is linear to the number of top-pages bool wxTreebook::AddSubPage(wxWindow *page, const wxString& text, bool bSelect, int imageId) { return DoAddSubPage(page, text, bSelect, imageId); } bool wxTreebook::DoInsertPage(size_t pagePos, wxWindow *page, const wxString& text, bool bSelect, int imageId) { wxCHECK_MSG( pagePos <= DoInternalGetPageCount(), false, wxT("Invalid treebook page position") ); if ( !wxBookCtrlBase::InsertPage(pagePos, page, text, bSelect, imageId) ) return false; wxTreeCtrl *tree = GetTreeCtrl(); wxTreeItemId newId; if ( pagePos == DoInternalGetPageCount() ) { // append the page to the end wxTreeItemId rootId = tree->GetRootItem(); newId = tree->AppendItem(rootId, text, imageId); } else // insert the new page before the given one { wxTreeItemId nodeId = m_treeIds[pagePos]; wxTreeItemId previousId = tree->GetPrevSibling(nodeId); wxTreeItemId parentId = tree->GetItemParent(nodeId); if ( previousId.IsOk() ) { // insert before the sibling - previousId newId = tree->InsertItem(parentId, previousId, text, imageId); } else // no prev siblings -- insert as a first child { wxASSERT_MSG( parentId.IsOk(), wxT( "Tree has no root node?" ) ); newId = tree->PrependItem(parentId, text, imageId); } } if ( !newId.IsOk() ) { //something wrong -> cleaning and returning with false (void)wxBookCtrlBase::DoRemovePage(pagePos); wxFAIL_MSG( wxT("Failed to insert treebook page") ); return false; } DoInternalAddPage(pagePos, page, newId); DoUpdateSelection(bSelect, pagePos); return true; } bool wxTreebook::DoAddSubPage(wxWindow *page, const wxString& text, bool bSelect, int imageId) { wxTreeCtrl *tree = GetTreeCtrl(); wxTreeItemId rootId = tree->GetRootItem(); wxTreeItemId lastNodeId = tree->GetLastChild(rootId); wxCHECK_MSG( lastNodeId.IsOk(), false, _T("Can't insert sub page when there are no pages") ); // now calculate its position (should we save/update it too?) size_t newPos = tree->GetCount() - (tree->GetChildrenCount(lastNodeId, true) + 1); return DoInsertSubPage(newPos, page, text, bSelect, imageId); } bool wxTreebook::DoInsertSubPage(size_t pagePos, wxTreebookPage *page, const wxString& text, bool bSelect, int imageId) { wxTreeItemId parentId = DoInternalGetPage(pagePos); wxCHECK_MSG( parentId.IsOk(), false, wxT("invalid tree item") ); wxTreeCtrl *tree = GetTreeCtrl(); size_t newPos = pagePos + tree->GetChildrenCount(parentId, true) + 1; wxASSERT_MSG( newPos <= DoInternalGetPageCount(), wxT("Internal error in tree insert point calculation") ); if ( !wxBookCtrlBase::InsertPage(newPos, page, text, bSelect, imageId) ) return false; wxTreeItemId newId = tree->AppendItem(parentId, text, imageId); if ( !newId.IsOk() ) { (void)wxBookCtrlBase::DoRemovePage(newPos); wxFAIL_MSG( wxT("Failed to insert treebook page") ); return false; } DoInternalAddPage(newPos, page, newId); DoUpdateSelection(bSelect, newPos); return true; } bool wxTreebook::DeletePage(size_t pagePos) { wxCHECK_MSG( IS_VALID_PAGE(pagePos), false, wxT("Invalid tree index") ); wxTreebookPage *oldPage = DoRemovePage(pagePos); if ( !oldPage ) return false; delete oldPage; return true; } wxTreebookPage *wxTreebook::DoRemovePage(size_t pagePos) { wxTreeItemId pageId = DoInternalGetPage(pagePos); wxCHECK_MSG( pageId.IsOk(), NULL, wxT("Invalid tree index") ); wxTreebookPage * oldPage = GetPage(pagePos); wxTreeCtrl *tree = GetTreeCtrl(); size_t subCount = tree->GetChildrenCount(pageId, true); wxASSERT_MSG ( IS_VALID_PAGE(pagePos + subCount), wxT("Internal error in wxTreebook::DoRemovePage") ); // here we are going to delete ALL the pages in the range // [pagePos, pagePos + subCount] -- the page and its children // deleting all the pages from the base class for ( size_t i = 0; i <= subCount; ++i ) { wxTreebookPage *page = wxBookCtrlBase::DoRemovePage(pagePos); // don't delete the page itself though -- it will be deleted in // DeletePage() when we return if ( i ) { delete page; } } DoInternalRemovePageRange(pagePos, subCount); tree->DeleteChildren( pageId ); tree->Delete( pageId ); return oldPage; } bool wxTreebook::DeleteAllPages() { wxBookCtrlBase::DeleteAllPages(); m_treeIds.Clear(); m_selection = m_actualSelection = wxNOT_FOUND; wxTreeCtrl *tree = GetTreeCtrl(); tree->DeleteChildren(tree->GetRootItem()); return true; } void wxTreebook::DoInternalAddPage(size_t newPos, wxTreebookPage *page, wxTreeItemId pageId) { wxASSERT_MSG( newPos <= m_treeIds.GetCount(), wxT("Ivalid index passed to wxTreebook::DoInternalAddPage") ); // hide newly inserted page initially (it will be shown when selected) if ( page ) page->Hide(); if ( newPos == m_treeIds.GetCount() ) { // append m_treeIds.Add(pageId); } else // insert { m_treeIds.Insert(pageId, newPos); if ( m_selection != wxNOT_FOUND && newPos <= (size_t)m_selection ) { // selection has been moved one unit toward the end ++m_selection; if ( m_actualSelection != wxNOT_FOUND ) ++m_actualSelection; } else if ( m_actualSelection != wxNOT_FOUND && newPos <= (size_t)m_actualSelection ) { DoSetSelection(m_selection); } } } void wxTreebook::DoInternalRemovePageRange(size_t pagePos, size_t subCount) { // Attention: this function is only for a situation when we delete a node // with all its children so pagePos is the node's index and subCount is the // node children count wxASSERT_MSG( pagePos + subCount < m_treeIds.GetCount(), wxT("Ivalid page index") ); wxTreeItemId pageId = m_treeIds[pagePos]; m_treeIds.RemoveAt(pagePos, subCount + 1); if ( m_selection != wxNOT_FOUND ) { if ( (size_t)m_selection > pagePos + subCount) { // selection is far after the deleted page, so just update the index and move on m_selection -= 1 + subCount; if ( m_actualSelection != wxNOT_FOUND) { m_actualSelection -= subCount + 1; } } else if ( (size_t)m_selection >= pagePos ) { wxTreeCtrl *tree = GetTreeCtrl(); // as selected page is going to be deleted, try to select the next // sibling if exists, if not then the parent wxTreeItemId nodeId = tree->GetNextSibling(pageId); m_selection = wxNOT_FOUND; m_actualSelection = wxNOT_FOUND; if ( nodeId.IsOk() ) { // selecting next siblings tree->SelectItem(nodeId); } else // no next sibling, select the parent { wxTreeItemId parentId = tree->GetItemParent(pageId); if ( parentId.IsOk() && parentId != tree->GetRootItem() ) { tree->SelectItem(parentId); } else // parent is root { // we can't select it as it's hidden DoUpdateSelection(false, wxNOT_FOUND); } } } else if ( m_actualSelection != wxNOT_FOUND && (size_t)m_actualSelection >= pagePos ) { // nothing to do -- selection is before the deleted node, but // actually shown page (the first (sub)child with page != NULL) is // already deleted m_actualSelection = m_selection; // send event as documented DoSetSelection(m_selection, SetSelection_SendEvent); } //else: nothing to do -- selection is before the deleted node } else { DoUpdateSelection(false, wxNOT_FOUND); } } void wxTreebook::DoUpdateSelection(bool bSelect, int newPos) { int newSelPos; if ( bSelect ) { newSelPos = newPos; } else if ( m_selection == wxNOT_FOUND && DoInternalGetPageCount() > 0 ) { newSelPos = 0; } else { newSelPos = wxNOT_FOUND; } if ( newSelPos != wxNOT_FOUND ) { SetSelection((size_t)newSelPos); } } wxTreeItemId wxTreebook::DoInternalGetPage(size_t pagePos) const { if ( pagePos >= m_treeIds.GetCount() ) { // invalid position but ok here, in this internal function, don't assert // (the caller will do it) return wxTreeItemId(); } return m_treeIds[pagePos]; } int wxTreebook::DoInternalFindPageById(wxTreeItemId pageId) const { const size_t count = m_treeIds.GetCount(); for ( size_t i = 0; i < count; ++i ) { if ( m_treeIds[i] == pageId ) return i; } return wxNOT_FOUND; } bool wxTreebook::IsNodeExpanded(size_t pagePos) const { wxTreeItemId pageId = DoInternalGetPage(pagePos); wxCHECK_MSG( pageId.IsOk(), false, wxT("invalid tree item") ); return GetTreeCtrl()->IsExpanded(pageId); } bool wxTreebook::ExpandNode(size_t pagePos, bool expand) { wxTreeItemId pageId = DoInternalGetPage(pagePos); wxCHECK_MSG( pageId.IsOk(), false, wxT("invalid tree item") ); if ( expand ) { GetTreeCtrl()->Expand( pageId ); } else // collapse { GetTreeCtrl()->Collapse( pageId ); // rely on the events generated by wxTreeCtrl to update selection } return true; } int wxTreebook::GetPageParent(size_t pagePos) const { wxTreeItemId nodeId = DoInternalGetPage( pagePos ); wxCHECK_MSG( nodeId.IsOk(), wxNOT_FOUND, wxT("Invalid page index spacified!") ); const wxTreeItemId parent = GetTreeCtrl()->GetItemParent( nodeId ); return parent.IsOk() ? DoInternalFindPageById(parent) : wxNOT_FOUND; } bool wxTreebook::SetPageText(size_t n, const wxString& strText) { wxTreeItemId pageId = DoInternalGetPage(n); wxCHECK_MSG( pageId.IsOk(), false, wxT("invalid tree item") ); GetTreeCtrl()->SetItemText(pageId, strText); return true; } wxString wxTreebook::GetPageText(size_t n) const { wxTreeItemId pageId = DoInternalGetPage(n); wxCHECK_MSG( pageId.IsOk(), wxString(), wxT("invalid tree item") ); return GetTreeCtrl()->GetItemText(pageId); } int wxTreebook::GetPageImage(size_t n) const { wxTreeItemId pageId = DoInternalGetPage(n); wxCHECK_MSG( pageId.IsOk(), wxNOT_FOUND, wxT("invalid tree item") ); return GetTreeCtrl()->GetItemImage(pageId); } bool wxTreebook::SetPageImage(size_t n, int imageId) { wxTreeItemId pageId = DoInternalGetPage(n); wxCHECK_MSG( pageId.IsOk(), false, wxT("invalid tree item") ); GetTreeCtrl()->SetItemImage(pageId, imageId); return true; } wxSize wxTreebook::CalcSizeFromPage(const wxSize& sizePage) const { const wxSize sizeTree = GetControllerSize(); wxSize size = sizePage; size.x += sizeTree.x; return size; } int wxTreebook::GetSelection() const { return m_selection; } int wxTreebook::DoSetSelection(size_t pagePos, int flags) { wxCHECK_MSG( IS_VALID_PAGE(pagePos), wxNOT_FOUND, wxT("invalid page index in wxListbook::DoSetSelection()") ); wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(), wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!")); wxTreebookEvent event(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING, m_windowId); const int oldSel = m_selection; wxTreeCtrl *tree = GetTreeCtrl(); bool allowed = false; if (flags & SetSelection_SendEvent) { event.SetEventObject(this); event.SetSelection(pagePos); event.SetOldSelection(m_selection); // don't send the event if the old and new pages are the same; do send it // otherwise and be prepared for it to be vetoed allowed = (int)pagePos == m_selection || !GetEventHandler()->ProcessEvent(event) || event.IsAllowed(); } if ( !(flags & SetSelection_SendEvent) || allowed ) { // hide the previously shown page wxTreebookPage * const oldPage = DoGetCurrentPage(); if ( oldPage ) oldPage->Hide(); // then show the new one m_selection = pagePos; wxTreebookPage *page = wxBookCtrlBase::GetPage(m_selection); if ( !page ) { // find the next page suitable to be shown: the first (grand)child // of this one with a non-NULL associated page wxTreeItemId childId = m_treeIds[pagePos]; int actualPagePos = pagePos; while ( !page && childId.IsOk() ) { wxTreeItemIdValue cookie; childId = tree->GetFirstChild( childId, cookie ); if ( childId.IsOk() ) { page = wxBookCtrlBase::GetPage(++actualPagePos); } } m_actualSelection = page ? actualPagePos : m_selection; } if ( page ) page->Show(); tree->SelectItem(DoInternalGetPage(pagePos)); if (flags & SetSelection_SendEvent) { // notify about the (now completed) page change event.SetEventType(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED); (void)GetEventHandler()->ProcessEvent(event); } } else if ( (flags & SetSelection_SendEvent) && !allowed) // page change vetoed { // tree selection might have already had changed if ( oldSel != wxNOT_FOUND ) tree->SelectItem(DoInternalGetPage(oldSel)); } return oldSel; } wxTreebookPage *wxTreebook::DoGetCurrentPage() const { if ( m_selection == wxNOT_FOUND ) return NULL; wxTreebookPage *page = wxBookCtrlBase::GetPage(m_selection); if ( !page && m_actualSelection != wxNOT_FOUND ) { page = wxBookCtrlBase::GetPage(m_actualSelection); } return page; } void wxTreebook::SetImageList(wxImageList *imageList) { wxBookCtrlBase::SetImageList(imageList); GetTreeCtrl()->SetImageList(imageList); } void wxTreebook::AssignImageList(wxImageList *imageList) { wxBookCtrlBase::AssignImageList(imageList); GetTreeCtrl()->SetImageList(imageList); } // ---------------------------------------------------------------------------- // event handlers // ---------------------------------------------------------------------------- void wxTreebook::OnTreeSelectionChange(wxTreeEvent& event) { if ( event.GetEventObject() != m_bookctrl ) { event.Skip(); return; } wxTreeItemId newId = event.GetItem(); if ( (m_selection == wxNOT_FOUND && (!newId.IsOk() || newId == GetTreeCtrl()->GetRootItem())) || (m_selection != wxNOT_FOUND && newId == m_treeIds[m_selection]) ) { // this event can only come when we modify the tree selection ourselves // so we should simply ignore it return; } int newPos = DoInternalFindPageById(newId); if ( newPos != wxNOT_FOUND ) SetSelection( newPos ); } void wxTreebook::OnTreeNodeExpandedCollapsed(wxTreeEvent & event) { if ( event.GetEventObject() != m_bookctrl ) { event.Skip(); return; } wxTreeItemId nodeId = event.GetItem(); if ( !nodeId.IsOk() || nodeId == GetTreeCtrl()->GetRootItem() ) return; int pagePos = DoInternalFindPageById(nodeId); wxCHECK_RET( pagePos != wxNOT_FOUND, wxT("Internal problem in wxTreebook!..") ); wxTreebookEvent ev(GetTreeCtrl()->IsExpanded(nodeId) ? wxEVT_COMMAND_TREEBOOK_NODE_EXPANDED : wxEVT_COMMAND_TREEBOOK_NODE_COLLAPSED, m_windowId); ev.SetSelection(pagePos); ev.SetOldSelection(pagePos); ev.SetEventObject(this); GetEventHandler()->ProcessEvent(ev); } // ---------------------------------------------------------------------------- // wxTreebook geometry management // ---------------------------------------------------------------------------- int wxTreebook::HitTest(wxPoint const & pt, long * flags) const { int pagePos = wxNOT_FOUND; if ( flags ) *flags = wxBK_HITTEST_NOWHERE; // convert from wxTreebook coorindates to wxTreeCtrl ones const wxTreeCtrl * const tree = GetTreeCtrl(); const wxPoint treePt = tree->ScreenToClient(ClientToScreen(pt)); // is it over the tree? if ( wxRect(tree->GetSize()).Contains(treePt) ) { int flagsTree; wxTreeItemId id = tree->HitTest(treePt, flagsTree); if ( id.IsOk() && (flagsTree & wxTREE_HITTEST_ONITEM) ) { pagePos = DoInternalFindPageById(id); } if ( flags ) { if ( pagePos != wxNOT_FOUND ) *flags = 0; if ( flagsTree & (wxTREE_HITTEST_ONITEMBUTTON | wxTREE_HITTEST_ONITEMICON | wxTREE_HITTEST_ONITEMSTATEICON) ) *flags |= wxBK_HITTEST_ONICON; if ( flagsTree & wxTREE_HITTEST_ONITEMLABEL ) *flags |= wxBK_HITTEST_ONLABEL; } } else // not over the tree { if ( flags && GetPageRect().Contains( pt ) ) *flags |= wxBK_HITTEST_ONPAGE; } return pagePos; } #endif // wxUSE_TREEBOOK