wxTreebook

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
Troels
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Jan 07, 2005 12:02 pm
Location: Denmark

wxTreebook

Post by Troels »

Hi,
wxTreebook is sadly missed in wxWidgets. By me, at least.
Here's a rough hacked-up version, a cut & paste job combining Hartmut Seichter's wxTreebook (not derived from wxBookCtrlBase) with wxListbook, resulting in a hopefully true wxBookCtrlBase-derived wxListbook/wxNotebook drop-in replacement.
Might be buggy. Feel free to improve upon it!
Regards,
Troels

Notes:
- Tested only on Windows (wxMSW 2.6.0-2.6.3). Probably works with 2.4.2 too.
- Uploaded new version 2005/09/23 (no more deprecated warnings)
- Superseeded by VZ implementation, included in wxWidgets version 2.7+

Todo:
- Keyboard-only operation not possible (wxTAB_TRAVERSAL?)

Image

treebook.h:

Code: Select all

/////////////////////////////////////////////////////////////////////////////
// Name:        treebook.h
// Purpose:     
// Author:      Hartmut Seichter, Troels K
// Modified by:
// Created:     2005/06/21
// License:     wxWindows license
/////////////////////////////////////////////////////////////////////////////

#ifndef __TREEBOOK_H__
#define __TREEBOOK_H__

#include "wx/treectrl.h"
#include "wx/bookctrl.h"

// ----------------------------------------------------------------------------
// wxTreebook
// ----------------------------------------------------------------------------

class wxTreebook : public wxBookCtrlBase
{
public:
    wxTreebook() : wxBookCtrlBase() { Init(); }
    wxTreebook(wxWindow *parent,
               wxWindowID id,
               const wxPoint& pos = wxDefaultPosition,
               const wxSize& size = wxDefaultSize,
               long style = wxLB_LEFT | wxTAB_TRAVERSAL,
               const wxString& name = wxEmptyString) : wxBookCtrlBase()
    {
        Init();

        (void)Create(parent, id, pos, size, style, name);
    }

    // quasi ctor
    bool Create(wxWindow *parent,
                wxWindowID id,
                const wxPoint& pos = wxDefaultPosition,
                const wxSize& size = wxDefaultSize,
                long style = wxLB_LEFT | wxTAB_TRAVERSAL,
                const wxString& name = wxEmptyString);

    wxTreeItemId InsertPage(const wxTreeItemId& parent,
                            size_t n,
                            wxWindow *page,
                            const wxString& text,
                            bool bSelect = false,
                            int imageId = -1);

    wxTreeItemId AddPage(const wxTreeItemId& parent, wxWindow *page,
                         const wxString& text,
                         bool bSelect = false,
                         int imageId = -1)
    {
        InvalidateBestSize();
        return InsertPage(parent, GetPageCount(), page, text, bSelect, imageId);
    }
    virtual bool AddPage(wxWindow *page,
                         const wxString& text,
                         bool bSelect = false,
                         int imageId = -1)
    {
        InvalidateBestSize();
        return InsertPage(GetRootItem(), GetPageCount(), page, text, bSelect, imageId).IsOk();
    }

    virtual int GetSelection() const;
    virtual bool SetPageText(size_t n, const wxString& strText);
    virtual wxString GetPageText(size_t n) const;
    virtual int GetPageImage(size_t n) const;
    virtual bool SetPageImage(size_t n, int imageId);
    virtual wxSize CalcSizeFromPage(const wxSize& sizePage) const;
    virtual bool InsertPage(size_t n,
                            wxWindow *page,
                            const wxString& text,
                            bool bSelect = false,
                            int imageId = -1)
    {
       return InsertPage(GetRootItem(), n, page, text, bSelect, imageId).IsOk();
    }
    virtual int SetSelection(size_t n);

    // returns true if we have wxLB_TOP or wxLB_BOTTOM style
    bool IsVertical() const { return HasFlag(wxLB_BOTTOM | wxLB_TOP); }

    virtual bool DeleteAllPages();

          wxTreeCtrl* GetTreeCtrl()       { return m_list; }
    const wxTreeCtrl* GetTreeCtrl() const { return m_list; }

protected:
    virtual wxWindow *DoRemovePage(size_t page);

    // get the size which the list control should have
    wxSize GetListSize() const;

    // get the page area
    wxRect GetPageRect() const;

    // event handlers
    void OnSize(wxSizeEvent& event);
    void OnListSelected(wxTreeEvent& event);

    // the list control we use for showing the pages index
    wxTreeCtrl *m_list;

#if wxUSE_LINE_IN_LISTBOOK
    // the line separating it from the page area
    wxStaticLine *m_line;
#endif // wxUSE_LINE_IN_LISTBOOK

    // the currently selected page or wxNOT_FOUND if none
    int m_selection;
public:
    wxTreeItemId GetRootItem() const { return GetTreeCtrl()->GetRootItem(); }

    bool FindItemRecursively(const wxTreeItemId& idParent, 
                                     wxTreeItemIdValue* cookie, 
                                     int nPage, 
                                     wxTreeItemId* foundid);
    wxTreeItemId FindItemByPageNum(const wxTreeItemId& parent,int nPage);
    wxTreeItemId FindItemByPageNum(int nPage) { return FindItemByPageNum(GetRootItem(), nPage); }
    void SelectByPageNum(int nPage);

protected:
    // common part of all constructors
    void Init();

    DECLARE_EVENT_TABLE()
    DECLARE_DYNAMIC_CLASS_NO_COPY(wxTreebook)
};

#endif // __TREEBOOK_H__
treebook.cpp:

Code: Select all

/////////////////////////////////////////////////////////////////////////////
// Name:        treebook.cpp
// Purpose:     
// Author:      Hartmut Seichter, Troels K
// Modified by:
// Created:     2005/06/21
// License:     wxWindows license
/////////////////////////////////////////////////////////////////////////////

#include "wx/listctrl.h"
#include "wx/statline.h"
#include "wx/listbook.h"  // wxListbookEvent
#include "treebook.h"

class wxTreebookItemData : public wxTreeItemData
{
protected:
    int m_page;
public:    
    wxTreebookItemData(int nPage) : wxTreeItemData(), m_page(nPage) { }
    int GetPage(void) const { return m_page; }
};

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

// margin between the list and the page, should be bigger than wxStaticLine
// size
const wxCoord MARGIN = 5;

// ----------------------------------------------------------------------------
// various wxWidgets macros
// ----------------------------------------------------------------------------

// check that the page index is valid
#define IS_VALID_PAGE(nPage) ((nPage) < GetPageCount())

// ----------------------------------------------------------------------------
// event table
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS(wxTreebook, wxControl)

const int wxID_TREEBOOKCTRL = wxNewId();

BEGIN_EVENT_TABLE(wxTreebook, wxBookCtrlBase)
    EVT_SIZE(wxTreebook::OnSize)
    EVT_TREE_SEL_CHANGED(wxID_TREEBOOKCTRL, wxTreebook::OnListSelected)
END_EVENT_TABLE()

// ============================================================================
// wxTreebook implementation
// ============================================================================

// ----------------------------------------------------------------------------
// wxTreebook creation
// ----------------------------------------------------------------------------

void wxTreebook::Init()
{
    m_list = NULL;
#if wxUSE_LINE_IN_LISTBOOK
    m_line = NULL;
#endif // wxUSE_LINE_IN_LISTBOOK
    m_selection = wxNOT_FOUND;
}

bool
wxTreebook::Create(wxWindow *parent,
                   wxWindowID id,
                   const wxPoint& pos,
                   const wxSize& size,
                   long style,
                   const wxString& name)
{
    if ( (style & wxLB_ALIGN_MASK) == wxLB_DEFAULT )
    {
#ifdef __WXMAC__
        style |= wxLB_TOP;
#else // !__WXMAC__
        style |= wxLB_LEFT;
#endif // __WXMAC__/!__WXMAC__
    }

    // no border for this control, it doesn't look nice together with
    // wxListCtrl border
    style &= ~wxBORDER_MASK;
    style |= wxBORDER_NONE;

    if ( !wxControl::Create(parent, id, pos, size, style,
                            wxDefaultValidator, name) )
        return false;

    m_list = new wxTreeCtrl(this,wxID_TREEBOOKCTRL,
        wxDefaultPosition,
        wxDefaultSize,
        wxTR_HIDE_ROOT | wxTR_SINGLE /*| wxTR_HIDE_ROOT | */| wxTR_LINES_AT_ROOT | wxTR_HAS_BUTTONS);

    GetTreeCtrl()->AddRoot(name);

#if wxUSE_LINE_IN_LISTBOOK
    m_line = new wxStaticLine
                 (
                    this,
                    wxID_ANY,
                    wxDefaultPosition,
                    wxDefaultSize,
                    IsVertical() ? wxLI_HORIZONTAL : wxLI_VERTICAL
                 );
#endif // wxUSE_LINE_IN_LISTBOOK

#ifdef __WXMSW__
    // 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;
}

// ----------------------------------------------------------------------------
// wxTreebook geometry management
// ----------------------------------------------------------------------------
wxSize wxTreebook::GetListSize() const
{
    const wxSize sizeClient = GetClientSize(),
                 sizeList = m_list->/*GetViewRect().*/GetSize();
    wxSize size;
    if ( IsVertical() )
    {
        size.x = sizeClient.x;
        size.y = sizeList.y;
    }
    else // left/right aligned
    {
        size.x = sizeList.x;
        size.y = sizeClient.y;
    }
    return size;
}

wxRect wxTreebook::GetPageRect() const
{
    const wxSize sizeList = m_list->GetSize();

    wxPoint pt;
    wxRect rectPage(pt, GetClientSize());
    switch ( GetWindowStyle() & wxLB_ALIGN_MASK )
    {
        default:
            wxFAIL_MSG( _T("unexpected wxTreebook alignment") );
            // fall through

        case wxLB_TOP:
            rectPage.y = sizeList.y + MARGIN;
            // fall through

        case wxLB_BOTTOM:
            rectPage.height -= sizeList.y + MARGIN;
            break;

        case wxLB_LEFT:
            rectPage.x = sizeList.x + MARGIN;
            // fall through

        case wxLB_RIGHT:
            rectPage.width -= sizeList.x + MARGIN;
            break;
    }
    return rectPage;
}

void wxTreebook::OnSize(wxSizeEvent& event)
{
    event.Skip();
    if ( !m_list )
    {
        // 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(),
                 sizeList = GetListSize();
    wxPoint posList;
    switch ( GetWindowStyle() & wxLB_ALIGN_MASK )
    {
        default:
            wxFAIL_MSG( _T("unexpected wxTreebook alignment") );
            // fall through

        case wxLB_TOP:
        case wxLB_LEFT:
            // posList is already ok
            break;

        case wxLB_BOTTOM:
            posList.y = sizeClient.y - sizeList.y;
            break;

        case wxLB_RIGHT:
            posList.x = sizeClient.x - sizeList.x;
            break;
    }

    m_list->Move(posList.x, posList.y);
    m_list->SetClientSize(sizeList.x, sizeList.y);

#if wxUSE_LINE_IN_LISTBOOK
    if ( m_line )
    {
        wxRect rectLine(sizeClient);
        switch ( GetWindowStyle() & wxLB_ALIGN_MASK )
        {
            case wxLB_TOP:
                rectLine.y = sizeList.y + 1;
                rectLine.height = MARGIN - 2;
                break;

            case wxLB_BOTTOM:
                rectLine.height = MARGIN - 2;
                rectLine.y = sizeClient.y - sizeList.y - rectLine.height;
                break;

            case wxLB_LEFT:
                rectLine.x = sizeList.x + 1;
                rectLine.width = MARGIN - 2;
                break;

            case wxLB_RIGHT:
                rectLine.width = MARGIN - 2;
                rectLine.x = sizeClient.x - sizeList.x - rectLine.width;
                break;
        }

        m_line->SetSize(rectLine);
    }
#endif // wxUSE_LINE_IN_LISTBOOK
    // resize the currently shown page
    if (m_selection != wxNOT_FOUND)
    {
        wxWindow *page = m_pages[m_selection];
        wxCHECK_RET( page, _T("NULL page in wxTreebook?") );
        page->SetSize(GetPageRect());
    }
}

wxSize wxTreebook::CalcSizeFromPage(const wxSize& sizePage) const
{
    // we need to add the size of the list control and the margin
    const wxSize sizeList = GetListSize();

    wxSize size = sizePage;
    if ( IsVertical() )
    {
        size.y += sizeList.y + MARGIN;
    }
    else // left/right aligned
    {
        size.x += sizeList.x + MARGIN;
    }

    return size;
}

// ----------------------------------------------------------------------------
// accessing the pages
// ----------------------------------------------------------------------------
bool wxTreebook::SetPageText(size_t n, const wxString& strText)
{
    m_list->SetItemText(n, strText);

    return true;
}

wxString wxTreebook::GetPageText(size_t n) const
{
    return m_list->GetItemText(n);
}

// ----------------------------------------------------------------------------
// selection
// ----------------------------------------------------------------------------
int wxTreebook::GetSelection() const
{
    return m_selection;
}

int wxTreebook::SetSelection(size_t n)
{
    wxCHECK_MSG( IS_VALID_PAGE(n), wxNOT_FOUND,
                 wxT("invalid page index in wxTreebook::SetSelection()") );

    const int oldSel = m_selection;

    if ( int(n) != m_selection )
    {
        wxListbookEvent event(wxEVT_COMMAND_LISTBOOK_PAGE_CHANGING, m_windowId);
        event.SetSelection(n);
        event.SetOldSelection(m_selection);
        event.SetEventObject(this);
        if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
        {
            if ( m_selection != wxNOT_FOUND )
                m_pages[m_selection]->Hide();

            wxWindow *page = m_pages[n];
            page->SetSize(GetPageRect());
            page->Show();

            // change m_selection now to ignore the selection change event
            m_selection = n;
            SelectByPageNum(n);

            // program allows the page change
            event.SetEventType(wxEVT_COMMAND_LISTBOOK_PAGE_CHANGED);
            (void)GetEventHandler()->ProcessEvent(event);
        }
    }
    return oldSel;
}

// ----------------------------------------------------------------------------
// adding/removing the pages
// ----------------------------------------------------------------------------
wxTreeItemId 
wxTreebook::InsertPage(const wxTreeItemId& parent,
                       size_t n,
                       wxWindow *page,
                       const wxString& text,
                       bool bSelect,
                       int imageId)
{
    wxTreeItemId _id;
    if (wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId) )
    {
       _id = GetTreeCtrl()->AppendItem(parent, text, imageId, imageId, new wxTreebookItemData(n));
       if (_id.IsOk())
       {
          if (parent != GetRootItem()) GetTreeCtrl()->Expand(parent);
          // if the inserted page is before the selected one, we must update the
          // index of the selected page
          if ( int(n) <= m_selection )
          {
              // one extra page added
              m_selection++;
              SelectByPageNum(m_selection);
          }
          // some page should be selected: either this one or the first one if there
          // is still no selection
          int selNew = -1;
          if ( bSelect )
              selNew = n;
          else if ( m_selection == -1 ) selNew = 0;

          if ( selNew != m_selection )
              page->Hide();

          //if ( selNew != -1 ) SetSelection(selNew); // this activates TransferFromWindow! it's too early! 

          InvalidateBestSize();
       }
    }
    return _id;
}

wxWindow *wxTreebook::DoRemovePage(size_t page)
{
    const int page_count = GetPageCount();
    wxWindow *win = wxBookCtrlBase::DoRemovePage(page);
    if ( win )
    {
        m_list->Delete(FindItemByPageNum(page));

        if (m_selection >= (int)page)
        {
            // force new sel valid if possible
            int sel = m_selection - 1;
            if (page_count == 1)
                sel = wxNOT_FOUND;
            else if ((page_count == 2) || (sel == -1))
                sel = 0;

            // force sel invalid if deleting current page - don't try to hide it
            m_selection = (m_selection == (int)page) ? wxNOT_FOUND : m_selection - 1;

            if ((sel != wxNOT_FOUND) && (sel != m_selection))
                SetSelection(sel);
        }
    }
    return win;
}

bool wxTreebook::DeleteAllPages()
{
    m_list->DeleteAllItems();
    return wxBookCtrlBase::DeleteAllPages();
}

// ----------------------------------------------------------------------------
// wxTreebook events
// ----------------------------------------------------------------------------

void wxTreebook::OnListSelected(wxTreeEvent& event)
{
   int selNew = wxNOT_FOUND;

    wxTreeItemId _itemId = event.GetItem();
    if (_itemId)
    {
       wxTreebookItemData *_item = (wxTreebookItemData *)GetTreeCtrl()->GetItemData(_itemId);
       if (_item)  selNew = _item->GetPage();
    }

    if ( (selNew == wxNOT_FOUND) || (selNew == m_selection) )
    {
        // this event can only come from our own Select(m_selection) below
        // which we call when the page change is vetoed, so we should simply
        // ignore it
        return;
    }

    SetSelection(selNew);

    // change wasn't allowed, return to previous state
    if (m_selection != selNew)
    {
       SelectByPageNum(m_selection);
    }
}

bool wxTreebook::FindItemRecursively(const wxTreeItemId& idParent, 
                                     wxTreeItemIdValue* cookie, 
                                     int nPage, 
                                     wxTreeItemId* foundid)
{
   wxTreeItemIdValue temp;
   wxTreeItemId id = cookie ? GetTreeCtrl()->GetNextChild(idParent, *cookie) : GetTreeCtrl()->GetFirstChild(idParent, temp);
   if (cookie == NULL) cookie = &temp;

    if(id <= 0) return FALSE;

    // do something with the thingiee here
    wxTreebookItemData *_item = (wxTreebookItemData *)GetTreeCtrl()->GetItemData(id);

    if (nPage == _item->GetPage()) 
    {
        *foundid = id;
        return TRUE;
    }
    if (GetTreeCtrl()->ItemHasChildren(id)) 
    {        
        if (FindItemRecursively(id,NULL,nPage,foundid)) return TRUE;    
    }
    if (FindItemRecursively(idParent, cookie, nPage, foundid)) return TRUE;
    return FALSE;
}

wxTreeItemId wxTreebook::FindItemByPageNum(const wxTreeItemId& parent,int nPage) 
{   
    wxTreeItemId id;
    FindItemRecursively(parent,NULL,nPage,&id);
    return id;    
}

void wxTreebook::SelectByPageNum(int nPage)
{
    GetTreeCtrl()->SelectItem(FindItemByPageNum(nPage));
}

int wxTreebook::GetPageImage(size_t WXUNUSED(n)) const
{
    wxFAIL_MSG( _T("wxListbook::GetPageImage() not implemented") );
    return -1;
}

bool wxTreebook::SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId))
{
    //return m_list->SetItemImage(n, imageId);
   return false;
}
Last edited by Troels on Mon Apr 24, 2006 11:09 am, edited 8 times in total.
Avi
Super wx Problem Solver
Super wx Problem Solver
Posts: 398
Joined: Mon Aug 30, 2004 9:27 pm
Location: Tel-Aviv, Israel

Post by Avi »

Could you post a screenshot showing us how this control looks like? :oops:

EDIT: you can use this free image host: http://www.imagehigh.com/ :)
Last edited by Avi on Mon May 30, 2005 11:39 pm, edited 1 time in total.
Troels
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Jan 07, 2005 12:02 pm
Location: Denmark

Post by Troels »

Ah, I can't remember where to park images on web.
It should look like the Settings dialog in DialogBlocks
/Troels
Aardbei
Earned a small fee
Earned a small fee
Posts: 23
Joined: Thu Mar 31, 2005 9:01 am

Post by Aardbei »

Troels
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Jan 07, 2005 12:02 pm
Location: Denmark

Post by Troels »

Ok, image added.
/Troels
Post Reply