/////////////////////////////////////////////////////////////////////////////// // Name: src/generic/treebkg.cpp // Purpose: generic implementation of wxTreebook // Author: Evgeniy Tarassov, Vadim Zeitlin // Modified by: // Created: 2005-09-15 // RCS-ID: $Id$ // 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" #include "wx/imaglist.h" #include "wx/settings.h" // ---------------------------------------------------------------------------- // various wxWidgets macros // ---------------------------------------------------------------------------- // check that the page index is valid #define IS_VALID_PAGE(nPage) ((nPage) < DoInternalGetPageCount()) // ---------------------------------------------------------------------------- // event table // ---------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxTreebook, wxControl) IMPLEMENT_DYNAMIC_CLASS(wxTreebookEvent, wxNotifyEvent) 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(); const int wxID_TREEBOOKTREEVIEW = wxNewId(); BEGIN_EVENT_TABLE(wxTreebook, wxBookCtrlBase) EVT_SIZE(wxTreebook::OnSize) EVT_TREE_SEL_CHANGED (wxID_TREEBOOKTREEVIEW, wxTreebook::OnTreeSelectionChange) EVT_TREE_ITEM_EXPANDED (wxID_TREEBOOKTREEVIEW, wxTreebook::OnTreeNodeExpandedCollapsed) EVT_TREE_ITEM_COLLAPSED(wxID_TREEBOOKTREEVIEW, wxTreebook::OnTreeNodeExpandedCollapsed) END_EVENT_TABLE() // ============================================================================ // wxTreebook implementation // ============================================================================ // ---------------------------------------------------------------------------- // wxTreebook creation // ---------------------------------------------------------------------------- void wxTreebook::Init() { m_tree = NULL; 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 & wxTBK_RIGHT ) { wxASSERT_MSG( !(style & wxTBK_LEFT), _T("RIGHT and LEFT can't be used together") ); } else { style |= wxTBK_LEFT; } // 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; m_tree = new wxTreeCtrl ( this, wxID_TREEBOOKTREEVIEW, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE | wxTR_HAS_BUTTONS | wxTR_HIDE_ROOT | wxTR_LINES_AT_ROOT | wxTR_SINGLE ); m_tree->AddRoot(wxEmptyString); // label doesn't matter, it's hidden #ifdef __WXMSW__ // see listbook.h for origins of that // On XP with themes enabled the GetViewRect used in GetListSize to // determine the space needed for the list view will incorrectly return // (0,0,0,0) the first time. So send a pending event so OnSize will be // called again after the window is ready to go. Technically we don't // need to do this on non-XP windows, but if things are already sized // correctly then nothing changes and so there is no harm. 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::AddSubPage(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; wxTreeItemId newId; if ( pagePos == DoInternalGetPageCount() ) { // append the page to the end wxTreeItemId rootId = m_tree->GetRootItem(); newId = m_tree->AppendItem(rootId, text, imageId); } else // insert the new page before the given one { wxTreeItemId nodeId = m_treeIds[pagePos]; wxTreeItemId previousId = m_tree->GetPrevSibling(nodeId); wxTreeItemId parentId = m_tree->GetItemParent(nodeId); if ( previousId.IsOk() ) { // insert before the sibling - previousId newId = m_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 = m_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) { wxTreeItemId rootId = m_tree->GetRootItem(); wxTreeItemId lastNodeId = m_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 = m_tree->GetCount() - (m_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") ); size_t newPos = pagePos + m_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 = m_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); size_t subCount = m_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); m_tree->DeleteChildren( pageId ); m_tree->Delete( pageId ); return oldPage; } bool wxTreebook::DeleteAllPages() { wxBookCtrlBase::DeleteAllPages(); m_treeIds.Clear(); m_selection = m_actualSelection = wxNOT_FOUND; m_tree->DeleteChildren(m_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 ) { // as selected page is going to be deleted, try to select the next // sibling if exists, if not then the parent wxTreeItemId nodeId = m_tree->GetNextSibling(pageId); m_selection = wxNOT_FOUND; m_actualSelection = wxNOT_FOUND; if ( nodeId.IsOk() ) { // selecting next siblings m_tree->SelectItem(nodeId); } else // no next sibling, select the parent { wxTreeItemId parentId = m_tree->GetItemParent(pageId); if ( parentId.IsOk() && parentId != m_tree->GetRootItem() ) { m_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; DoSetSelection(m_selection); } //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 m_tree->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 ) { m_tree->Expand( pageId ); } else // collapse { m_tree->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 = m_tree->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") ); m_tree->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 m_tree->GetItemText(pageId); } int wxTreebook::GetPageImage(size_t n) const { wxTreeItemId pageId = DoInternalGetPage(n); wxCHECK_MSG( pageId.IsOk(), wxNOT_FOUND, wxT("invalid tree item") ); return m_tree->GetItemImage(pageId); } bool wxTreebook::SetPageImage(size_t n, int imageId) { wxTreeItemId pageId = DoInternalGetPage(n); wxCHECK_MSG( pageId.IsOk(), false, wxT("invalid tree item") ); m_tree->SetItemImage(pageId, imageId); return true; } wxSize wxTreebook::CalcSizeFromPage(const wxSize& sizePage) const { const wxSize sizeTree = GetTreeSize(); wxSize size = sizePage; size.x += sizeTree.x; return size; } int wxTreebook::GetSelection() const { return m_selection; } int wxTreebook::SetSelection(size_t pagePos) { if ( (size_t)m_selection != pagePos ) return DoSetSelection(pagePos); return m_selection; } int wxTreebook::DoSetSelection(size_t pagePos) { wxCHECK_MSG( IS_VALID_PAGE(pagePos), wxNOT_FOUND, wxT("invalid page index in wxListbook::SetSelection()") ); wxASSERT_MSG( GetPageCount() == DoInternalGetPageCount(), wxT("wxTreebook logic error: m_treeIds and m_pages not in sync!")); const int oldSel = m_selection; wxTreebookEvent event(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGING, m_windowId); 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 if ( (int)pagePos == m_selection || !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) { // 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]; m_actualSelection = pagePos; while ( !page && childId.IsOk() ) { wxTreeItemIdValue cookie; childId = m_tree->GetFirstChild( childId, cookie ); if ( childId.IsOk() ) { page = wxBookCtrlBase::GetPage(++m_actualSelection); } } wxASSERT_MSG( page, wxT("no page to show found!") ); } if ( page ) { page->SetSize(GetPageRect()); page->Show(); } m_tree->SelectItem(DoInternalGetPage(pagePos)); // notify about the (now completed) page change event.SetEventType(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED); (void)GetEventHandler()->ProcessEvent(event); } else // page change vetoed { // tree selection might have already had changed m_tree->SelectItem(DoInternalGetPage(oldSel)); } return oldSel; } void wxTreebook::SetImageList(wxImageList *imageList) { wxBookCtrlBase::SetImageList(imageList); m_tree->SetImageList(imageList); } void wxTreebook::AssignImageList(wxImageList *imageList) { wxBookCtrlBase::AssignImageList(imageList); m_tree->SetImageList(imageList); } // ---------------------------------------------------------------------------- // event handlers // ---------------------------------------------------------------------------- void wxTreebook::OnTreeSelectionChange(wxTreeEvent& event) { wxTreeItemId newId = event.GetItem(); if ( (m_selection == wxNOT_FOUND && (!newId.IsOk() || newId == m_tree->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) { wxTreeItemId nodeId = event.GetItem(); if ( !nodeId.IsOk() || nodeId == m_tree->GetRootItem() ) return; int pagePos = DoInternalFindPageById(nodeId); wxCHECK_RET( pagePos != wxNOT_FOUND, wxT("Internal problem in wxTreebook!..") ); wxTreebookEvent ev(m_tree->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 // ---------------------------------------------------------------------------- wxSize wxTreebook::GetTreeSize() const { const wxSize sizeClient = GetClientSize(), sizeBorder = m_tree->GetSize() - m_tree->GetClientSize(), sizeTree = m_tree->GetBestSize() + sizeBorder; wxSize size; size.x = sizeTree.x; size.y = sizeClient.y; return size; } wxRect wxTreebook::GetPageRect() const { const wxSize sizeTree = m_tree->GetSize(); wxPoint pt; wxRect rectPage(pt, GetClientSize()); switch ( GetWindowStyle() & wxTBK_ALIGN_MASK ) { default: wxFAIL_MSG( _T("unexpected wxTreebook alignment") ); // fall through case wxTBK_LEFT: rectPage.x = sizeTree.x; // + MARGIN; // fall through case wxTBK_RIGHT: rectPage.width -= sizeTree.x; // + MARGIN; break; } return rectPage; } void wxTreebook::OnSize(wxSizeEvent& event) { event.Skip(); if ( !m_tree ) { // we're not fully created yet return; } // resize the list control and the page area to fit inside our new size const wxSize sizeClient = GetClientSize(), sizeBorder = m_tree->GetSize() - m_tree->GetClientSize(), sizeTree = GetTreeSize(); m_tree->SetClientSize( sizeTree.x - sizeBorder.x, sizeTree.y - sizeBorder.y ); const wxSize sizeNew = m_tree->GetSize(); wxPoint posTree; switch ( GetWindowStyle() & wxTBK_ALIGN_MASK ) { default: wxFAIL_MSG( _T("unexpected wxTreebook alignment") ); // fall through case wxTBK_LEFT: // posTree is already ok break; case wxTBK_RIGHT: posTree.x = sizeClient.x - sizeNew.x; break; } if ( m_tree->GetPosition() != posTree ) m_tree->Move(posTree); // resize the currently shown page wxTreebookPage *page = DoGetCurrentPage(); if ( page ) { wxRect rectPage = GetPageRect(); page->SetSize(rectPage); } } 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; } #endif // wxUSE_TREEBOOK