X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/b77d96500ac802c84e0416e715c907e34d335632..1978421a6d8b81c1f8a961da4b8ddf544fec7b1b:/src/generic/treectlg.cpp?ds=sidebyside diff --git a/src/generic/treectlg.cpp b/src/generic/treectlg.cpp index fe255add4c..6354c38bfe 100644 --- a/src/generic/treectlg.cpp +++ b/src/generic/treectlg.cpp @@ -5,7 +5,7 @@ // Created: 01/02/97 // Modified: 22/10/98 - almost total rewrite, simpler interface (VZ) // Id: $Id$ -// Copyright: (c) 1998 Robert Roebling, Julian Smart and Markus Holzem +// Copyright: (c) 1998 Robert Roebling and Julian Smart // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -17,7 +17,7 @@ // headers // ----------------------------------------------------------------------------- -#ifdef __GNUG__ +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "treectlg.h" #endif @@ -25,18 +25,20 @@ #include "wx/wxprec.h" #ifdef __BORLANDC__ -#pragma hdrstop + #pragma hdrstop #endif +#if wxUSE_TREECTRL + +#include "wx/treebase.h" #include "wx/generic/treectlg.h" +#include "wx/timer.h" +#include "wx/textctrl.h" #include "wx/imaglist.h" #include "wx/settings.h" -#include "wx/log.h" -#include "wx/intl.h" -#include "wx/dynarray.h" -#include "wx/arrimpl.cpp" #include "wx/dcclient.h" -#include "wx/msgdlg.h" + +#include "wx/renderer.h" // ----------------------------------------------------------------------------- // array types @@ -44,8 +46,7 @@ class WXDLLEXPORT wxGenericTreeItem; -WX_DEFINE_EXPORTED_ARRAY(wxGenericTreeItem *, wxArrayGenericTreeItems); -//WX_DEFINE_OBJARRAY(wxArrayTreeItemIds); +WX_DEFINE_EXPORTED_ARRAY_NO_PTR(wxGenericTreeItem *, wxArrayGenericTreeItems); // ---------------------------------------------------------------------------- // constants @@ -53,7 +54,7 @@ WX_DEFINE_EXPORTED_ARRAY(wxGenericTreeItem *, wxArrayGenericTreeItems); static const int NO_IMAGE = -1; -#define PIXELS_PER_UNIT 10 +static const int PIXELS_PER_UNIT = 10; // ----------------------------------------------------------------------------- // private classes @@ -63,43 +64,60 @@ static const int NO_IMAGE = -1; class WXDLLEXPORT wxTreeRenameTimer: public wxTimer { public: + // start editing the current item after half a second (if the mouse hasn't + // been clicked/moved) + enum { DELAY = 500 }; + wxTreeRenameTimer( wxGenericTreeCtrl *owner ); - void Notify(); + virtual void Notify(); private: - wxGenericTreeCtrl *m_owner; + wxGenericTreeCtrl *m_owner; + + DECLARE_NO_COPY_CLASS(wxTreeRenameTimer) }; // control used for in-place edit class WXDLLEXPORT wxTreeTextCtrl: public wxTextCtrl { public: - wxTreeTextCtrl() { } - wxTreeTextCtrl( wxWindow *parent, - const wxWindowID id, - bool *accept, - wxString *res, - wxGenericTreeCtrl *owner, - const wxString &value = wxEmptyString, - const wxPoint &pos = wxDefaultPosition, - const wxSize &size = wxDefaultSize, - int style = wxSIMPLE_BORDER, - const wxValidator& validator = wxDefaultValidator, - const wxString &name = wxTextCtrlNameStr ); + wxTreeTextCtrl(wxGenericTreeCtrl *owner, wxGenericTreeItem *item); +protected: void OnChar( wxKeyEvent &event ); void OnKeyUp( wxKeyEvent &event ); void OnKillFocus( wxFocusEvent &event ); + bool AcceptChanges(); + void Finish(); + private: - bool *m_accept; - wxString *m_res; - wxGenericTreeCtrl *m_owner; + wxGenericTreeCtrl *m_owner; + wxGenericTreeItem *m_itemEdited; wxString m_startValue; + bool m_finished; DECLARE_EVENT_TABLE() - DECLARE_DYNAMIC_CLASS(wxTreeTextCtrl); + DECLARE_NO_COPY_CLASS(wxTreeTextCtrl) +}; + +// timer used to clear wxGenericTreeCtrl::m_findPrefix if no key was pressed +// for a sufficiently long time +class WXDLLEXPORT wxTreeFindTimer : public wxTimer +{ +public: + // reset the current prefix after half a second of inactivity + enum { DELAY = 500 }; + + wxTreeFindTimer( wxGenericTreeCtrl *owner ) { m_owner = owner; } + + virtual void Notify() { m_owner->m_findPrefix.clear(); } + +private: + wxGenericTreeCtrl *m_owner; + + DECLARE_NO_COPY_CLASS(wxTreeFindTimer) }; // a tree item @@ -109,10 +127,10 @@ public: // ctors & dtor wxGenericTreeItem() { m_data = NULL; } wxGenericTreeItem( wxGenericTreeItem *parent, - const wxString& text, - wxDC& dc, - int image, int selImage, - wxTreeItemData *data ); + const wxString& text, + int image, + int selImage, + wxTreeItemData *data ); ~wxGenericTreeItem(); @@ -148,15 +166,12 @@ public: void SetHeight(int h) { m_height = h; } void SetWidth(int w) { m_width = w; } - wxGenericTreeItem *GetParent() const { return m_parent; } // operations // deletes all children notifying the treectrl about it if !NULL // pointer given void DeleteChildren(wxGenericTreeCtrl *tree = NULL); - // FIXME don't know what is it for - void Reset(); // get count of all children (and grand children if 'recursively') size_t GetChildrenCount(bool recursively = TRUE) const; @@ -164,13 +179,15 @@ public: void Insert(wxGenericTreeItem *child, size_t index) { m_children.Insert(child, index); } - void SetCross( int x, int y ); void GetSize( int &x, int &y, const wxGenericTreeCtrl* ); // return the item at given position (or NULL if no item), onButton is // TRUE if the point belongs to the item's button, otherwise it lies // on the button's label - wxGenericTreeItem *HitTest( const wxPoint& point, const wxGenericTreeCtrl *, int &flags); + wxGenericTreeItem *HitTest( const wxPoint& point, + const wxGenericTreeCtrl *, + int &flags, + int level ); void Expand() { m_isCollapsed = FALSE; } void Collapse() { m_isCollapsed = TRUE; } @@ -191,19 +208,47 @@ public: wxTreeItemAttr& Attr() { if ( !m_attr ) + { m_attr = new wxTreeItemAttr; - + m_ownsAttr = TRUE; + } return *m_attr; } + // set them + void SetAttributes(wxTreeItemAttr *attr) + { + if ( m_ownsAttr ) delete m_attr; + m_attr = attr; + m_ownsAttr = FALSE; + } + // set them and delete when done + void AssignAttributes(wxTreeItemAttr *attr) + { + SetAttributes(attr); + m_ownsAttr = TRUE; + } private: - wxString m_text; + // since there can be very many of these, we save size by chosing + // the smallest representation for the elements and by ordering + // the members to avoid padding. + wxString m_text; // label to be rendered for item + + wxTreeItemData *m_data; // user-provided data + + wxArrayGenericTreeItems m_children; // list of children + wxGenericTreeItem *m_parent; // parent of this item + + wxTreeItemAttr *m_attr; // attributes??? // tree ctrl images for the normal, selected, expanded and // expanded+selected states - int m_images[wxTreeItemIcon_Max]; + short m_images[wxTreeItemIcon_Max]; - wxTreeItemData *m_data; + wxCoord m_x; // (virtual) offset from top + wxCoord m_y; // (virtual) offset from left + short m_width; // width of this item + unsigned char m_height; // height of this item // use bitfields to save size int m_isCollapsed :1; @@ -211,16 +256,9 @@ private: int m_hasPlus :1; // used for item which doesn't have // children but has a [+] button int m_isBold :1; // render the label in bold font + int m_ownsAttr :1; // delete attribute when done - wxCoord m_x, m_y; - wxCoord m_height, m_width; - int m_xCross, m_yCross; - int m_level; - - wxArrayGenericTreeItems m_children; - wxGenericTreeItem *m_parent; - - wxTreeItemAttr *m_attr; + DECLARE_NO_COPY_CLASS(wxGenericTreeItem) }; // ============================================================================= @@ -245,6 +283,23 @@ static void EventFlagsToSelType(long style, unselect_others = !(extended_select || (ctrlDown && is_multiple)); } +// check if the given item is under another one +static bool IsDescendantOf(wxGenericTreeItem *parent, wxGenericTreeItem *item) +{ + while ( item ) + { + if ( item == parent ) + { + // item is a descendant of parent + return TRUE; + } + + item = item->GetParent(); + } + + return FALSE; +} + // ----------------------------------------------------------------------------- // wxTreeRenameTimer (internal) // ----------------------------------------------------------------------------- @@ -263,101 +318,141 @@ void wxTreeRenameTimer::Notify() // wxTreeTextCtrl (internal) //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxTreeTextCtrl,wxTextCtrl); - BEGIN_EVENT_TABLE(wxTreeTextCtrl,wxTextCtrl) EVT_CHAR (wxTreeTextCtrl::OnChar) EVT_KEY_UP (wxTreeTextCtrl::OnKeyUp) EVT_KILL_FOCUS (wxTreeTextCtrl::OnKillFocus) END_EVENT_TABLE() -wxTreeTextCtrl::wxTreeTextCtrl( wxWindow *parent, - const wxWindowID id, - bool *accept, - wxString *res, - wxGenericTreeCtrl *owner, - const wxString &value, - const wxPoint &pos, - const wxSize &size, - int style, - const wxValidator& validator, - const wxString &name ) - : wxTextCtrl( parent, id, value, pos, size, style, validator, name ) -{ - m_res = res; - m_accept = accept; +wxTreeTextCtrl::wxTreeTextCtrl(wxGenericTreeCtrl *owner, + wxGenericTreeItem *item) + : m_itemEdited(item), m_startValue(item->GetText()) +{ m_owner = owner; - (*m_accept) = FALSE; - (*m_res) = wxEmptyString; - m_startValue = value; -} + m_finished = FALSE; -void wxTreeTextCtrl::OnChar( wxKeyEvent &event ) -{ - if (event.m_keyCode == WXK_RETURN) + int w = m_itemEdited->GetWidth(), + h = m_itemEdited->GetHeight(); + + int x, y; + m_owner->CalcScrolledPosition(item->GetX(), item->GetY(), &x, &y); + + int image_h = 0, + image_w = 0; + + int image = item->GetCurrentImage(); + if ( image != NO_IMAGE ) { - (*m_accept) = TRUE; - (*m_res) = GetValue(); + if ( m_owner->m_imageListNormal ) + { + m_owner->m_imageListNormal->GetSize( image, image_w, image_h ); + image_w += 4; + } + else + { + wxFAIL_MSG(_T("you must create an image list to use images!")); + } + } + + // FIXME: what are all these hardcoded 4, 8 and 11s really? + x += image_w; + w -= image_w + 4; - if (!wxPendingDelete.Member(this)) - wxPendingDelete.Append(this); + (void)Create(m_owner, wxID_ANY, m_startValue, + wxPoint(x - 4, y - 4), wxSize(w + 11, h + 8)); +} - if ((*m_accept) && ((*m_res) != m_startValue)) - m_owner->OnRenameAccept(); +bool wxTreeTextCtrl::AcceptChanges() +{ + const wxString value = GetValue(); - return; + if ( value == m_startValue ) + { + // nothing changed, always accept + return TRUE; } - if (event.m_keyCode == WXK_ESCAPE) + + if ( !m_owner->OnRenameAccept(m_itemEdited, value) ) { - (*m_accept) = FALSE; - (*m_res) = ""; + // vetoed by the user + return FALSE; + } - if (!wxPendingDelete.Member(this)) - wxPendingDelete.Append(this); + // accepted, do rename the item + m_owner->SetItemText(m_itemEdited, value); - return; - } - event.Skip(); + return TRUE; } -void wxTreeTextCtrl::OnKeyUp( wxKeyEvent &event ) +void wxTreeTextCtrl::Finish() { - // auto-grow the textctrl: - wxSize parentSize = m_owner->GetSize(); - wxPoint myPos = GetPosition(); - wxSize mySize = GetSize(); - int sx, sy; - GetTextExtent(GetValue() + _T("MM"), &sx, &sy); - if (myPos.x + sx > parentSize.x) sx = parentSize.x - myPos.x; - if (mySize.x > sx) sx = mySize.x; - SetSize(sx, -1); + if ( !m_finished ) + { + m_owner->ResetTextControl(); - event.Skip(); + wxPendingDelete.Append(this); + + m_finished = TRUE; + + m_owner->SetFocus(); // This doesn't work. TODO. + } } -void wxTreeTextCtrl::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) +void wxTreeTextCtrl::OnChar( wxKeyEvent &event ) { - if (!wxPendingDelete.Member(this)) - wxPendingDelete.Append(this); + switch ( event.m_keyCode ) + { + case WXK_RETURN: + if ( !AcceptChanges() ) + { + // vetoed by the user, don't disappear + break; + } + //else: fall through + + case WXK_ESCAPE: + Finish(); + m_owner->OnRenameCancelled(m_itemEdited); + break; - if ((*m_accept) && ((*m_res) != m_startValue)) - m_owner->OnRenameAccept(); + default: + event.Skip(); + } } -#if 0 -// ----------------------------------------------------------------------------- -// wxTreeEvent -// ----------------------------------------------------------------------------- +void wxTreeTextCtrl::OnKeyUp( wxKeyEvent &event ) +{ + if ( !m_finished ) + { + // auto-grow the textctrl: + wxSize parentSize = m_owner->GetSize(); + wxPoint myPos = GetPosition(); + wxSize mySize = GetSize(); + int sx, sy; + GetTextExtent(GetValue() + _T("M"), &sx, &sy); + if (myPos.x + sx > parentSize.x) + sx = parentSize.x - myPos.x; + if (mySize.x > sx) + sx = mySize.x; + SetSize(sx, -1); + } -IMPLEMENT_DYNAMIC_CLASS(wxTreeEvent, wxNotifyEvent) + event.Skip(); +} -wxTreeEvent::wxTreeEvent( wxEventType commandType, int id ) - : wxNotifyEvent( commandType, id ) +void wxTreeTextCtrl::OnKillFocus( wxFocusEvent &event ) { - m_code = 0; - m_itemOld = (wxGenericTreeItem *)NULL; + if ( m_finished ) + { + event.Skip(); + return; + } + + if ( AcceptChanges() ) + { + Finish(); + } } -#endif // ----------------------------------------------------------------------------- // wxGenericTreeItem @@ -365,7 +460,6 @@ wxTreeEvent::wxTreeEvent( wxEventType commandType, int id ) wxGenericTreeItem::wxGenericTreeItem(wxGenericTreeItem *parent, const wxString& text, - wxDC& WXUNUSED(dc), int image, int selImage, wxTreeItemData *data) : m_text(text) @@ -377,9 +471,6 @@ wxGenericTreeItem::wxGenericTreeItem(wxGenericTreeItem *parent, m_data = data; m_x = m_y = 0; - m_xCross = m_yCross = 0; - - m_level = 0; m_isCollapsed = TRUE; m_hasHilight = FALSE; @@ -389,6 +480,7 @@ wxGenericTreeItem::wxGenericTreeItem(wxGenericTreeItem *parent, m_parent = parent; m_attr = (wxTreeItemAttr *)NULL; + m_ownsAttr = FALSE; // We don't know the height here yet. m_width = 0; @@ -399,7 +491,7 @@ wxGenericTreeItem::~wxGenericTreeItem() { delete m_data; - delete m_attr; + if (m_ownsAttr) delete m_attr; wxASSERT_MSG( m_children.IsEmpty(), wxT("please call DeleteChildren() before deleting the item") ); @@ -426,28 +518,6 @@ void wxGenericTreeItem::SetText( const wxString &text ) m_text = text; } -void wxGenericTreeItem::Reset() -{ - m_text.Empty(); - for ( int i = 0; i < wxTreeItemIcon_Max; i++ ) - { - m_images[i] = NO_IMAGE; - } - - m_data = NULL; - m_x = m_y = - m_height = m_width = 0; - m_xCross = - m_yCross = 0; - - m_level = 0; - - DeleteChildren(); - m_isCollapsed = TRUE; - - m_parent = (wxGenericTreeItem *)NULL; -} - size_t wxGenericTreeItem::GetChildrenCount(bool recursively) const { size_t count = m_children.Count(); @@ -463,15 +533,10 @@ size_t wxGenericTreeItem::GetChildrenCount(bool recursively) const return total; } -void wxGenericTreeItem::SetCross( int x, int y ) -{ - m_xCross = x; - m_yCross = y; -} - -void wxGenericTreeItem::GetSize( int &x, int &y, const wxGenericTreeCtrl *theTree ) +void wxGenericTreeItem::GetSize( int &x, int &y, + const wxGenericTreeCtrl *theButton ) { - int bottomY=m_y+theTree->GetLineHeight(this); + int bottomY=m_y+theButton->GetLineHeight(this); if ( y < bottomY ) y = bottomY; int width = m_x + m_width; if ( x < width ) x = width; @@ -481,71 +546,81 @@ void wxGenericTreeItem::GetSize( int &x, int &y, const wxGenericTreeCtrl *theTre size_t count = m_children.Count(); for ( size_t n = 0; n < count; ++n ) { - m_children[n]->GetSize( x, y, theTree ); + m_children[n]->GetSize( x, y, theButton ); } } } -wxGenericTreeItem *wxGenericTreeItem::HitTest( const wxPoint& point, - const wxGenericTreeCtrl *theTree, - int &flags) +wxGenericTreeItem *wxGenericTreeItem::HitTest(const wxPoint& point, + const wxGenericTreeCtrl *theCtrl, + int &flags, + int level) { - if ((point.y > m_y) && (point.y < m_y + theTree->GetLineHeight(this))) + // for a hidden root node, don't evaluate it, but do evaluate children + if ( !(level == 0 && theCtrl->HasFlag(wxTR_HIDE_ROOT)) ) { - if (point.y < m_y+theTree->GetLineHeight(this)/2 ) - flags |= wxTREE_HITTEST_ONITEMUPPERPART; - else - flags |= wxTREE_HITTEST_ONITEMLOWERPART; - - // 5 is the size of the plus sign - if ((point.x > m_xCross-5) && (point.x < m_xCross+5) && - (point.y > m_yCross-5) && (point.y < m_yCross+5) && - (IsExpanded() || HasPlus())) + // evaluate the item + int h = theCtrl->GetLineHeight(this); + if ((point.y > m_y) && (point.y < m_y + h)) { - flags|=wxTREE_HITTEST_ONITEMBUTTON; - return this; - } + int y_mid = m_y + h/2; + if (point.y < y_mid ) + flags |= wxTREE_HITTEST_ONITEMUPPERPART; + else + flags |= wxTREE_HITTEST_ONITEMLOWERPART; - if ((point.x >= m_x) && (point.x <= m_x+m_width)) - { - int image_w = -1; - int image_h; + // 5 is the size of the plus sign + int xCross = m_x - theCtrl->GetSpacing(); + if ((point.x > xCross-5) && (point.x < xCross+5) && + (point.y > y_mid-5) && (point.y < y_mid+5) && + HasPlus() && theCtrl->HasButtons() ) + { + flags |= wxTREE_HITTEST_ONITEMBUTTON; + return this; + } - // assuming every image (normal and selected ) has the same size ! - if ( (GetImage() != NO_IMAGE) && theTree->m_imageListNormal ) - theTree->m_imageListNormal->GetSize(GetImage(), image_w, image_h); + if ((point.x >= m_x) && (point.x <= m_x+m_width)) + { + int image_w = -1; + int image_h; - if ((image_w != -1) && (point.x <= m_x + image_w + 1)) - flags |= wxTREE_HITTEST_ONITEMICON; - else - flags |= wxTREE_HITTEST_ONITEMLABEL; + // assuming every image (normal and selected) has the same size! + if ( (GetImage() != NO_IMAGE) && theCtrl->m_imageListNormal ) + theCtrl->m_imageListNormal->GetSize(GetImage(), + image_w, image_h); + + if ((image_w != -1) && (point.x <= m_x + image_w + 1)) + flags |= wxTREE_HITTEST_ONITEMICON; + else + flags |= wxTREE_HITTEST_ONITEMLABEL; + + return this; + } + + if (point.x < m_x) + flags |= wxTREE_HITTEST_ONITEMINDENT; + if (point.x > m_x+m_width) + flags |= wxTREE_HITTEST_ONITEMRIGHT; return this; } - if (point.x < m_x) - flags |= wxTREE_HITTEST_ONITEMINDENT; - if (point.x > m_x+m_width) - flags |= wxTREE_HITTEST_ONITEMRIGHT; - - return this; + // if children are expanded, fall through to evaluate them + if (m_isCollapsed) return (wxGenericTreeItem*) NULL; } - else + + // evaluate children + size_t count = m_children.Count(); + for ( size_t n = 0; n < count; n++ ) { - if (!m_isCollapsed) - { - size_t count = m_children.Count(); - for ( size_t n = 0; n < count; n++ ) - { - wxGenericTreeItem *res = m_children[n]->HitTest( point, theTree, flags ); - if ( res != NULL ) - return res; - } - } + wxGenericTreeItem *res = m_children[n]->HitTest( point, + theCtrl, + flags, + level + 1 ); + if ( res != NULL ) + return res; } - flags|=wxTREE_HITTEST_NOWHERE; - return (wxGenericTreeItem*) NULL; } @@ -572,12 +647,9 @@ int wxGenericTreeItem::GetCurrentImage() const image = GetImage(wxTreeItemIcon_Selected); } - // may be it doesn't have the specific image we want, try the default one - // instead - if ( image == NO_IMAGE ) - { - image = GetImage(); - } + // maybe it doesn't have the specific image we want, + // try the default one instead + if ( image == NO_IMAGE ) image = GetImage(); return image; } @@ -594,10 +666,9 @@ BEGIN_EVENT_TABLE(wxGenericTreeCtrl,wxScrolledWindow) EVT_CHAR (wxGenericTreeCtrl::OnChar) EVT_SET_FOCUS (wxGenericTreeCtrl::OnSetFocus) EVT_KILL_FOCUS (wxGenericTreeCtrl::OnKillFocus) - EVT_IDLE (wxGenericTreeCtrl::OnIdle) END_EVENT_TABLE() -#if !defined(__WXMSW__) || defined(__WIN16__) +#if !defined(__WXMSW__) || defined(__WIN16__) || defined(__WXUNIVERSAL__) /* * wxTreeCtrl has to be a real class or we have problems with * the run-time information. @@ -612,76 +683,116 @@ IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxGenericTreeCtrl) void wxGenericTreeCtrl::Init() { - m_current = - m_key_current = - m_anchor = (wxGenericTreeItem *) NULL; + m_current = m_key_current = m_anchor = m_select_me = (wxGenericTreeItem *) NULL; m_hasFocus = FALSE; m_dirty = FALSE; - m_xScroll = 0; - m_yScroll = 0; m_lineHeight = 10; m_indent = 15; m_spacing = 18; m_hilightBrush = new wxBrush - ( - wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT), - wxSOLID - ); - - m_imageListNormal = + ( + wxSystemSettings::GetColour + ( + wxSYS_COLOUR_HIGHLIGHT + ), + wxSOLID + ); + + m_hilightUnfocusedBrush = new wxBrush + ( + wxSystemSettings::GetColour + ( + wxSYS_COLOUR_BTNSHADOW + ), + wxSOLID + ); + + m_imageListNormal = m_imageListButtons = m_imageListState = (wxImageList *) NULL; - m_ownsImageListNormal = + m_ownsImageListNormal = m_ownsImageListButtons = m_ownsImageListState = FALSE; m_dragCount = 0; m_isDragging = FALSE; - m_dropTarget = - m_oldSelection = (wxGenericTreeItem *)NULL; + m_dropTarget = m_oldSelection = (wxGenericTreeItem *)NULL; + m_textCtrl = NULL; + + m_renameTimer = NULL; + m_findTimer = NULL; - m_renameTimer = new wxTreeRenameTimer( this ); m_lastOnSame = FALSE; - m_normalFont = wxSystemSettings::GetSystemFont( wxSYS_DEFAULT_GUI_FONT ); - m_boldFont = wxFont( m_normalFont.GetPointSize(), - m_normalFont.GetFamily(), - m_normalFont.GetStyle(), - wxBOLD, - m_normalFont.GetUnderlined()); + m_normalFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ); + m_boldFont = wxFont(m_normalFont.GetPointSize(), + m_normalFont.GetFamily(), + m_normalFont.GetStyle(), + wxBOLD, + m_normalFont.GetUnderlined(), + m_normalFont.GetFaceName(), + m_normalFont.GetEncoding()); } -bool wxGenericTreeCtrl::Create(wxWindow *parent, wxWindowID id, - const wxPoint& pos, const wxSize& size, - long style, - const wxValidator &validator, - const wxString& name ) +bool wxGenericTreeCtrl::Create(wxWindow *parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator &validator, + const wxString& name ) { - wxScrolledWindow::Create( parent, id, pos, size, style|wxHSCROLL|wxVSCROLL, name ); +#ifdef __WXMAC__ + int major,minor; + wxGetOsVersion( &major, &minor ); + + style &= ~wxTR_LINES_AT_ROOT; + style |= wxTR_NO_LINES; + if (major < 10) + style |= wxTR_ROW_LINES; +#endif // __WXMAC__ + + wxScrolledWindow::Create( parent, id, pos, size, + style|wxHSCROLL|wxVSCROLL, name ); + + // If the tree display has no buttons, but does have + // connecting lines, we can use a narrower layout. + // It may not be a good idea to force this... + if (!HasButtons() && !HasFlag(wxTR_NO_LINES)) + { + m_indent= 10; + m_spacing = 10; + } #if wxUSE_VALIDATORS SetValidator( validator ); #endif -#ifndef __WXMAC__ - SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_LISTBOX ) ); -#endif + SetForegroundColour( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) ); + SetBackgroundColour( wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX) ); // m_dottedPen = wxPen( "grey", 0, wxDOT ); too slow under XFree86 - m_dottedPen = wxPen( "grey", 0, 0 ); + m_dottedPen = wxPen( wxT("grey"), 0, 0 ); return TRUE; } wxGenericTreeCtrl::~wxGenericTreeCtrl() { - wxDELETE( m_hilightBrush ); + delete m_hilightBrush; + delete m_hilightUnfocusedBrush; DeleteAllItems(); delete m_renameTimer; - if (m_ownsImageListNormal) delete m_imageListNormal; - if (m_ownsImageListState) delete m_imageListState; + delete m_findTimer; + + if (m_ownsImageListNormal) + delete m_imageListNormal; + if (m_ownsImageListState) + delete m_imageListState; + if (m_ownsImageListButtons) + delete m_imageListButtons; } // ----------------------------------------------------------------------------- @@ -695,13 +806,13 @@ size_t wxGenericTreeCtrl::GetCount() const void wxGenericTreeCtrl::SetIndent(unsigned int indent) { - m_indent = indent; + m_indent = (unsigned short) indent; m_dirty = TRUE; } void wxGenericTreeCtrl::SetSpacing(unsigned int spacing) { - m_spacing = spacing; + m_spacing = (unsigned short) spacing; m_dirty = TRUE; } @@ -712,6 +823,23 @@ size_t wxGenericTreeCtrl::GetChildrenCount(const wxTreeItemId& item, bool recurs return ((wxGenericTreeItem*) item.m_pItem)->GetChildrenCount(recursively); } +void wxGenericTreeCtrl::SetWindowStyle(const long styles) +{ + if (!HasFlag(wxTR_HIDE_ROOT) && (styles & wxTR_HIDE_ROOT)) + { + // if we will hide the root, make sure children are visible + m_anchor->SetHasPlus(); + m_anchor->Expand(); + CalculatePositions(); + } + + // right now, just sets the styles. Eventually, we may + // want to update the inherited styles, but right now + // none of the parents has updatable styles + m_windowStyle = styles; + m_dirty = TRUE; +} + // ----------------------------------------------------------------------------- // functions to work with tree items // ----------------------------------------------------------------------------- @@ -738,6 +866,30 @@ wxTreeItemData *wxGenericTreeCtrl::GetItemData(const wxTreeItemId& item) const return ((wxGenericTreeItem*) item.m_pItem)->GetData(); } +wxColour wxGenericTreeCtrl::GetItemTextColour(const wxTreeItemId& item) const +{ + wxCHECK_MSG( item.IsOk(), wxNullColour, wxT("invalid tree item") ); + + wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem; + return pItem->Attr().GetTextColour(); +} + +wxColour wxGenericTreeCtrl::GetItemBackgroundColour(const wxTreeItemId& item) const +{ + wxCHECK_MSG( item.IsOk(), wxNullColour, wxT("invalid tree item") ); + + wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem; + return pItem->Attr().GetBackgroundColour(); +} + +wxFont wxGenericTreeCtrl::GetItemFont(const wxTreeItemId& item) const +{ + wxCHECK_MSG( item.IsOk(), wxNullFont, wxT("invalid tree item") ); + + wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem; + return pItem->Attr().GetFont(); +} + void wxGenericTreeCtrl::SetItemText(const wxTreeItemId& item, const wxString& text) { wxCHECK_RET( item.IsOk(), wxT("invalid tree item") ); @@ -826,11 +978,13 @@ bool wxGenericTreeCtrl::SetFont( const wxFont &font ) wxScrolledWindow::SetFont(font); m_normalFont = font ; - m_boldFont = wxFont( m_normalFont.GetPointSize(), - m_normalFont.GetFamily(), - m_normalFont.GetStyle(), - wxBOLD, - m_normalFont.GetUnderlined()); + m_boldFont = wxFont(m_normalFont.GetPointSize(), + m_normalFont.GetFamily(), + m_normalFont.GetStyle(), + wxBOLD, + m_normalFont.GetUnderlined(), + m_normalFont.GetFaceName(), + m_normalFont.GetEncoding()); return TRUE; } @@ -876,7 +1030,12 @@ bool wxGenericTreeCtrl::ItemHasChildren(const wxTreeItemId& item) const { wxCHECK_MSG( item.IsOk(), FALSE, wxT("invalid tree item") ); - return !((wxGenericTreeItem*) item.m_pItem)->GetChildren().IsEmpty(); + // consider that the item does have children if it has the "+" button: it + // might not have them (if it had never been expanded yet) but then it + // could have them as well and it's better to err on this side rather than + // disabling some operations which are restricted to the items with + // children for an item which does have them + return ((wxGenericTreeItem*) item.m_pItem)->HasPlus(); } bool wxGenericTreeCtrl::IsExpanded(const wxTreeItemId& item) const @@ -904,159 +1063,239 @@ bool wxGenericTreeCtrl::IsBold(const wxTreeItemId& item) const // navigation // ----------------------------------------------------------------------------- -wxTreeItemId wxGenericTreeCtrl::GetParent(const wxTreeItemId& item) const +wxTreeItemId wxGenericTreeCtrl::GetItemParent(const wxTreeItemId& item) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - return ((wxGenericTreeItem*) item.m_pItem)->GetParent(); + return ((wxGenericTreeItem*) item.m_pItem)->GetParent(); } -wxTreeItemId wxGenericTreeCtrl::GetFirstChild(const wxTreeItemId& item, long& cookie) const +wxTreeItemId wxGenericTreeCtrl::GetFirstChild(const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - cookie = 0; - return GetNextChild(item, cookie); + cookie = 0; + return GetNextChild(item, cookie); } -wxTreeItemId wxGenericTreeCtrl::GetNextChild(const wxTreeItemId& item, long& cookie) const +wxTreeItemId wxGenericTreeCtrl::GetNextChild(const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - wxArrayGenericTreeItems& children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren(); - if ( (size_t)cookie < children.Count() ) - { - return children.Item((size_t)cookie++); - } - else - { - // there are no more of them - return wxTreeItemId(); - } + wxArrayGenericTreeItems& children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren(); + + // it's ok to cast cookie to size_t, we never have indices big enough to + // overflow "void *" + size_t *pIndex = (size_t *)&cookie; + if ( *pIndex < children.Count() ) + { + return children.Item((*pIndex)++); + } + else + { + // there are no more of them + return wxTreeItemId(); + } +} + +#if WXWIN_COMPATIBILITY_2_4 + +wxTreeItemId wxGenericTreeCtrl::GetFirstChild(const wxTreeItemId& item, + long& cookie) const +{ + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + + cookie = 0; + return GetNextChild(item, cookie); +} + +wxTreeItemId wxGenericTreeCtrl::GetNextChild(const wxTreeItemId& item, + long& cookie) const +{ + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + + wxArrayGenericTreeItems& children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren(); + if ( (size_t)cookie < children.Count() ) + { + return children.Item((size_t)cookie++); + } + else + { + // there are no more of them + return wxTreeItemId(); + } } +#endif // WXWIN_COMPATIBILITY_2_4 + wxTreeItemId wxGenericTreeCtrl::GetLastChild(const wxTreeItemId& item) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - wxArrayGenericTreeItems& children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren(); - return (children.IsEmpty() ? wxTreeItemId() : wxTreeItemId(children.Last())); + wxArrayGenericTreeItems& children = ((wxGenericTreeItem*) item.m_pItem)->GetChildren(); + return (children.IsEmpty() ? wxTreeItemId() : wxTreeItemId(children.Last())); } wxTreeItemId wxGenericTreeCtrl::GetNextSibling(const wxTreeItemId& item) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem; - wxGenericTreeItem *parent = i->GetParent(); - if ( parent == NULL ) - { - // root item doesn't have any siblings - return wxTreeItemId(); - } + wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem; + wxGenericTreeItem *parent = i->GetParent(); + if ( parent == NULL ) + { + // root item doesn't have any siblings + return wxTreeItemId(); + } - wxArrayGenericTreeItems& siblings = parent->GetChildren(); - int index = siblings.Index(i); - wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent? + wxArrayGenericTreeItems& siblings = parent->GetChildren(); + int index = siblings.Index(i); + wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent? - size_t n = (size_t)(index + 1); - return n == siblings.Count() ? wxTreeItemId() : wxTreeItemId(siblings[n]); + size_t n = (size_t)(index + 1); + return n == siblings.Count() ? wxTreeItemId() : wxTreeItemId(siblings[n]); } wxTreeItemId wxGenericTreeCtrl::GetPrevSibling(const wxTreeItemId& item) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem; - wxGenericTreeItem *parent = i->GetParent(); - if ( parent == NULL ) - { - // root item doesn't have any siblings - return wxTreeItemId(); - } + wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem; + wxGenericTreeItem *parent = i->GetParent(); + if ( parent == NULL ) + { + // root item doesn't have any siblings + return wxTreeItemId(); + } - wxArrayGenericTreeItems& siblings = parent->GetChildren(); - int index = siblings.Index(i); - wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent? + wxArrayGenericTreeItems& siblings = parent->GetChildren(); + int index = siblings.Index(i); + wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent? - return index == 0 ? wxTreeItemId() - : wxTreeItemId(siblings[(size_t)(index - 1)]); + return index == 0 ? wxTreeItemId() + : wxTreeItemId(siblings[(size_t)(index - 1)]); } // Only for internal use right now, but should probably be public wxTreeItemId wxGenericTreeCtrl::GetNext(const wxTreeItemId& item) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem; + wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem; - // First see if there are any children. - wxArrayGenericTreeItems& children = i->GetChildren(); - if (children.GetCount() > 0) - { - return children.Item(0); - } - else - { - // Try a sibling of this or ancestor instead - wxTreeItemId p = item; - wxTreeItemId toFind; - do - { - toFind = GetNextSibling(p); - p = GetParent(p); - } while (p.IsOk() && !toFind.IsOk()); - return toFind; - } + // First see if there are any children. + wxArrayGenericTreeItems& children = i->GetChildren(); + if (children.GetCount() > 0) + { + return children.Item(0); + } + else + { + // Try a sibling of this or ancestor instead + wxTreeItemId p = item; + wxTreeItemId toFind; + do + { + toFind = GetNextSibling(p); + p = GetItemParent(p); + } while (p.IsOk() && !toFind.IsOk()); + return toFind; + } } -wxTreeItemId wxGenericTreeCtrl::GetPrev(const wxTreeItemId& item) const +wxTreeItemId wxGenericTreeCtrl::GetFirstVisibleItem() const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxTreeItemId id = GetRootItem(); + if (!id.IsOk()) + return id; - wxFAIL_MSG(wxT("not implemented")); + do + { + if (IsVisible(id)) + return id; + id = GetNext(id); + } while (id.IsOk()); - return wxTreeItemId(); + return wxTreeItemId(); } -wxTreeItemId wxGenericTreeCtrl::GetFirstVisibleItem() const +wxTreeItemId wxGenericTreeCtrl::GetNextVisible(const wxTreeItemId& item) const { - wxTreeItemId id = GetRootItem(); - if (!id.IsOk()) - return id; + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - do - { - if (IsVisible(id)) - return id; - id = GetNext(id); - } while (id.IsOk()); - - return wxTreeItemId(); + wxTreeItemId id = item; + if (id.IsOk()) + { + while (id = GetNext(id), id.IsOk()) + { + if (IsVisible(id)) + return id; + } + } + return wxTreeItemId(); } -wxTreeItemId wxGenericTreeCtrl::GetNextVisible(const wxTreeItemId& item) const +wxTreeItemId wxGenericTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); - wxTreeItemId id = item; - while (id.IsOk()) - { - id = GetNext(id); + wxFAIL_MSG(wxT("not implemented")); - if (id.IsOk() && IsVisible(id)) - return id; - } - return wxTreeItemId(); + return wxTreeItemId(); } -wxTreeItemId wxGenericTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const +// called by wxTextTreeCtrl when it marks itself for deletion +void wxGenericTreeCtrl::ResetTextControl() { - wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") ); + m_textCtrl = NULL; +} + +// find the first item starting with the given prefix after the given item +wxTreeItemId wxGenericTreeCtrl::FindItem(const wxTreeItemId& idParent, + const wxString& prefixOrig) const +{ + // match is case insensitive as this is more convenient to the user: having + // to press Shift-letter to go to the item starting with a capital letter + // would be too bothersome + wxString prefix = prefixOrig.Lower(); + + // determine the starting point: we shouldn't take the current item (this + // allows to switch between two items starting with the same letter just by + // pressing it) but we shouldn't jump to the next one if the user is + // continuing to type as otherwise he might easily skip the item he wanted + wxTreeItemId id = idParent; + if ( prefix.length() == 1 ) + { + id = GetNext(id); + } + + // look for the item starting with the given prefix after it + while ( id.IsOk() && !GetItemText(id).Lower().StartsWith(prefix) ) + { + id = GetNext(id); + } - wxFAIL_MSG(wxT("not implemented")); + // if we haven't found anything... + if ( !id.IsOk() ) + { + // ... wrap to the beginning + id = GetRootItem(); + if ( HasFlag(wxTR_HIDE_ROOT) ) + { + // can't select virtual root + id = GetNext(id); + } + + // and try all the items (stop when we get to the one we started from) + while ( id != idParent && !GetItemText(id).Lower().StartsWith(prefix) ) + { + id = GetNext(id); + } + } - return wxTreeItemId(); + return id; } // ----------------------------------------------------------------------------- @@ -1076,19 +1315,18 @@ wxTreeItemId wxGenericTreeCtrl::DoInsertItem(const wxTreeItemId& parentId, return AddRoot(text, image, selImage, data); } - wxClientDC dc(this); + m_dirty = TRUE; // do this first so stuff below doesn't cause flicker + wxGenericTreeItem *item = - new wxGenericTreeItem( parent, text, dc, image, selImage, data ); + new wxGenericTreeItem( parent, text, image, selImage, data ); if ( data != NULL ) { - data->m_pItem = (long) item; + data->m_pItem = item; } parent->Insert( item, previous ); - m_dirty = TRUE; - return item; } @@ -1098,12 +1336,22 @@ wxTreeItemId wxGenericTreeCtrl::AddRoot(const wxString& text, { wxCHECK_MSG( !m_anchor, wxTreeItemId(), wxT("tree can have only one root") ); - wxClientDC dc(this); - m_anchor = new wxGenericTreeItem((wxGenericTreeItem *)NULL, text, dc, + m_dirty = TRUE; // do this first so stuff below doesn't cause flicker + + m_anchor = new wxGenericTreeItem((wxGenericTreeItem *)NULL, text, image, selImage, data); if ( data != NULL ) { - data->m_pItem = (long) m_anchor; + data->m_pItem = m_anchor; + } + + if (HasFlag(wxTR_HIDE_ROOT)) + { + // if root is hidden, make sure we can navigate + // into children + m_anchor->SetHasPlus(); + m_anchor->Expand(); + CalculatePositions(); } if (!HasFlag(wxTR_MULTIPLE)) @@ -1112,8 +1360,6 @@ wxTreeItemId wxGenericTreeCtrl::AddRoot(const wxString& text, m_current->SetHilight( TRUE ); } - m_dirty = TRUE; - return m_anchor; } @@ -1138,9 +1384,13 @@ wxTreeItemId wxGenericTreeCtrl::InsertItem(const wxTreeItemId& parentId, return AddRoot(text, image, selImage, data); } - int index = parent->GetChildren().Index((wxGenericTreeItem*) idPrevious.m_pItem); - wxASSERT_MSG( index != wxNOT_FOUND, - wxT("previous item in wxGenericTreeCtrl::InsertItem() is not a sibling") ); + int index = -1; + if (idPrevious.IsOk()) + { + index = parent->GetChildren().Index((wxGenericTreeItem*) idPrevious.m_pItem); + wxASSERT_MSG( index != wxNOT_FOUND, + wxT("previous item in wxGenericTreeCtrl::InsertItem() is not a sibling") ); + } return DoInsertItem(parentId, (size_t)++index, text, image, selImage, data); } @@ -1180,69 +1430,78 @@ wxTreeItemId wxGenericTreeCtrl::AppendItem(const wxTreeItemId& parentId, void wxGenericTreeCtrl::SendDeleteEvent(wxGenericTreeItem *item) { wxTreeEvent event( wxEVT_COMMAND_TREE_DELETE_ITEM, GetId() ); - event.m_item = (long) item; + event.m_item = item; event.SetEventObject( this ); ProcessEvent( event ); } void wxGenericTreeCtrl::DeleteChildren(const wxTreeItemId& itemId) { + m_dirty = TRUE; // do this first so stuff below doesn't cause flicker + wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem; item->DeleteChildren(this); - - m_dirty = TRUE; } void wxGenericTreeCtrl::Delete(const wxTreeItemId& itemId) { + m_dirty = TRUE; // do this first so stuff below doesn't cause flicker + wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem; - // don't stay with invalid m_key_current or we will crash in the next call - // to OnChar() - bool changeKeyCurrent = FALSE; - wxGenericTreeItem *itemKey = m_key_current; - while ( itemKey && !changeKeyCurrent ) + wxGenericTreeItem *parent = item->GetParent(); + + // don't keep stale pointers around! + if ( IsDescendantOf(item, m_key_current) ) { - if ( itemKey == item ) - { - // m_key_current is a descendant of the item being deleted - changeKeyCurrent = TRUE; - } - else - { - itemKey = itemKey->GetParent(); - } + // Don't silently change the selection: + // do it properly in idle time, so event + // handlers get called. + + // m_key_current = parent; + m_key_current = NULL; } - wxGenericTreeItem *parent = item->GetParent(); + // m_select_me records whether we need to select + // a different item, in idle time. + if ( m_select_me && IsDescendantOf(item, m_select_me) ) + { + m_select_me = parent; + } + + if ( IsDescendantOf(item, m_current) ) + { + // Don't silently change the selection: + // do it properly in idle time, so event + // handlers get called. + + // m_current = parent; + m_current = NULL; + m_select_me = parent; + } + + // remove the item from the tree if ( parent ) { parent->GetChildren().Remove( item ); // remove by value } - - if ( changeKeyCurrent ) + else // deleting the root { - // may be NULL or not - m_key_current = parent; + // nothing will be left in the tree + m_anchor = NULL; } + // and delete all of its children and the item itself now item->DeleteChildren(this); SendDeleteEvent(item); delete item; - - m_dirty = TRUE; } void wxGenericTreeCtrl::DeleteAllItems() { if ( m_anchor ) { - m_anchor->DeleteChildren(this); - delete m_anchor; - - m_anchor = NULL; - - m_dirty = TRUE; + Delete(m_anchor); } } @@ -1251,6 +1510,8 @@ void wxGenericTreeCtrl::Expand(const wxTreeItemId& itemId) wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem; wxCHECK_RET( item, _T("invalid item in wxGenericTreeCtrl::Expand") ); + wxCHECK_RET( !HasFlag(wxTR_HIDE_ROOT) || itemId != GetRootItem(), + _T("can't expand hidden root") ); if ( !item->HasPlus() ) return; @@ -1259,7 +1520,7 @@ void wxGenericTreeCtrl::Expand(const wxTreeItemId& itemId) return; wxTreeEvent event( wxEVT_COMMAND_TREE_ITEM_EXPANDING, GetId() ); - event.m_item = (long) item; + event.m_item = item; event.SetEventObject( this ); if ( ProcessEvent( event ) && !event.IsAllowed() ) @@ -1279,29 +1540,35 @@ void wxGenericTreeCtrl::Expand(const wxTreeItemId& itemId) void wxGenericTreeCtrl::ExpandAll(const wxTreeItemId& item) { - Expand(item); - if ( IsExpanded(item) ) + if ( !HasFlag(wxTR_HIDE_ROOT) || item != GetRootItem()) { - long cookie; - wxTreeItemId child = GetFirstChild(item, cookie); - while ( child.IsOk() ) - { - ExpandAll(child); + Expand(item); + if ( !IsExpanded(item) ) + return; + } - child = GetNextChild(item, cookie); - } + wxTreeItemIdValue cookie; + wxTreeItemId child = GetFirstChild(item, cookie); + while ( child.IsOk() ) + { + ExpandAll(child); + + child = GetNextChild(item, cookie); } } void wxGenericTreeCtrl::Collapse(const wxTreeItemId& itemId) { + wxCHECK_RET( !HasFlag(wxTR_HIDE_ROOT) || itemId != GetRootItem(), + _T("can't collapse hidden root") ); + wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem; if ( !item->IsExpanded() ) return; wxTreeEvent event( wxEVT_COMMAND_TREE_ITEM_COLLAPSING, GetId() ); - event.m_item = (long) item; + event.m_item = item; event.SetEventObject( this ); if ( ProcessEvent( event ) && !event.IsAllowed() ) { @@ -1311,12 +1578,14 @@ void wxGenericTreeCtrl::Collapse(const wxTreeItemId& itemId) item->Collapse(); +#if 0 // TODO why should items be collapsed recursively? wxArrayGenericTreeItems& children = item->GetChildren(); size_t count = children.Count(); for ( size_t n = 0; n < count; n++ ) { Collapse(children[n]); } +#endif CalculatePositions(); @@ -1348,6 +1617,9 @@ void wxGenericTreeCtrl::Unselect() { m_current->SetHilight( FALSE ); RefreshLine( m_current ); + + m_current = NULL; + m_select_me = NULL; } } @@ -1372,7 +1644,13 @@ void wxGenericTreeCtrl::UnselectAllChildren(wxGenericTreeItem *item) void wxGenericTreeCtrl::UnselectAll() { - UnselectAllChildren((wxGenericTreeItem*) GetRootItem().m_pItem); + wxTreeItemId rootItem = GetRootItem(); + + // the tree might not have the root item at all + if ( rootItem ) + { + UnselectAllChildren((wxGenericTreeItem*) rootItem.m_pItem); + } } // Recursive function ! @@ -1427,6 +1705,7 @@ void wxGenericTreeCtrl::SelectItemRange(wxGenericTreeItem *item1, wxGenericTreeI { // item2 is not necessary after item1 wxGenericTreeItem *first=NULL, *last=NULL; + m_select_me = NULL; // choice first' and 'last' between item1 and item2 if (item1->GetY()GetY()) @@ -1449,11 +1728,13 @@ void wxGenericTreeCtrl::SelectItemRange(wxGenericTreeItem *item1, wxGenericTreeI } void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, - bool unselect_others, - bool extended_select) + bool unselect_others, + bool extended_select) { wxCHECK_RET( itemId.IsOk(), wxT("invalid tree item") ); + m_select_me = NULL; + bool is_single=!(GetWindowStyleFlag() & wxTR_MULTIPLE); wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem; @@ -1477,13 +1758,24 @@ void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, } wxTreeEvent event( wxEVT_COMMAND_TREE_SEL_CHANGING, GetId() ); - event.m_item = (long) item; - event.m_itemOld = (long) m_current; + event.m_item = item; + event.m_itemOld = m_current; event.SetEventObject( this ); // TODO : Here we don't send any selection mode yet ! if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() ) - return; + return; + + wxTreeItemId parent = GetItemParent( itemId ); + while (parent.IsOk()) + { + if (!IsExpanded(parent)) + Expand( parent ); + + parent = GetItemParent( parent ); + } + + EnsureVisible( itemId ); // ctrl press if (unselect_others) @@ -1497,8 +1789,7 @@ void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, { if ( !m_current ) { - m_current = - m_key_current = (wxGenericTreeItem*) GetRootItem().m_pItem; + m_current = m_key_current = (wxGenericTreeItem*) GetRootItem().m_pItem; } // don't change the mark (m_current) @@ -1510,7 +1801,7 @@ void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, // Check if we need to toggle hilight (ctrl mode) if (!unselect_others) - select=!item->IsSelected(); + select=!item->IsSelected(); m_current = m_key_current = item; m_current->SetHilight(select); @@ -1522,7 +1813,7 @@ void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, } void wxGenericTreeCtrl::FillArray(wxGenericTreeItem *item, - wxArrayTreeItemIds &array) const + wxArrayTreeItemIds &array) const { if ( item->IsSelected() ) array.Add(wxTreeItemId(item)); @@ -1538,15 +1829,15 @@ void wxGenericTreeCtrl::FillArray(wxGenericTreeItem *item, size_t wxGenericTreeCtrl::GetSelections(wxArrayTreeItemIds &array) const { - array.Empty(); - wxTreeItemId idRoot = GetRootItem(); - if ( idRoot.IsOk() ) - { - FillArray((wxGenericTreeItem*) idRoot.m_pItem, array); - } - //else: the tree is empty, so no selections + array.Empty(); + wxTreeItemId idRoot = GetRootItem(); + if ( idRoot.IsOk() ) + { + FillArray((wxGenericTreeItem*) idRoot.m_pItem, array); + } + //else: the tree is empty, so no selections - return array.Count(); + return array.Count(); } void wxGenericTreeCtrl::EnsureVisible(const wxTreeItemId& item) @@ -1557,10 +1848,22 @@ void wxGenericTreeCtrl::EnsureVisible(const wxTreeItemId& item) // first expand all parent branches wxGenericTreeItem *parent = gitem->GetParent(); - while ( parent ) + + if ( HasFlag(wxTR_HIDE_ROOT) ) { - Expand(parent); - parent = parent->GetParent(); + while ( parent && parent != m_anchor ) + { + Expand(parent); + parent = parent->GetParent(); + } + } + else + { + while ( parent ) + { + Expand(parent); + parent = parent->GetParent(); + } } //if (parent) CalculatePositions(); @@ -1584,7 +1887,7 @@ void wxGenericTreeCtrl::ScrollTo(const wxTreeItemId &item) int start_x = 0; int start_y = 0; - ViewStart( &start_x, &start_y ); + GetViewStart( &start_x, &start_y ); start_y *= PIXELS_PER_UNIT; int client_h = 0; @@ -1647,11 +1950,11 @@ void wxGenericTreeCtrl::SortChildren(const wxTreeItemId& itemId) wxArrayGenericTreeItems& children = item->GetChildren(); if ( children.Count() > 1 ) { + m_dirty = TRUE; + s_treeBeingSorted = this; children.Sort(tree_ctrl_compare_func); s_treeBeingSorted = NULL; - - m_dirty = TRUE; } //else: don't make the tree dirty as nothing changed } @@ -1661,41 +1964,67 @@ wxImageList *wxGenericTreeCtrl::GetImageList() const return m_imageListNormal; } +wxImageList *wxGenericTreeCtrl::GetButtonsImageList() const +{ + return m_imageListButtons; +} + wxImageList *wxGenericTreeCtrl::GetStateImageList() const { return m_imageListState; } -void wxGenericTreeCtrl::SetImageList(wxImageList *imageList) +void wxGenericTreeCtrl::CalculateLineHeight() { - if (m_ownsImageListNormal) delete m_imageListNormal; - - m_imageListNormal = imageList; - m_ownsImageListNormal = FALSE; - - if ( !m_imageListNormal ) - return; - - // Calculate a m_lineHeight value from the image sizes. - // May be toggle off. Then wxGenericTreeCtrl will spread when - // necessary (which might look ugly). wxClientDC dc(this); m_lineHeight = (int)(dc.GetCharHeight() + 4); - int width = 0, height = 0, - n = m_imageListNormal->GetImageCount(); - for (int i = 0; i < n ; i++) + if ( m_imageListNormal ) + { + // Calculate a m_lineHeight value from the normal Image sizes. + // May be toggle off. Then wxGenericTreeCtrl will spread when + // necessary (which might look ugly). + int n = m_imageListNormal->GetImageCount(); + for (int i = 0; i < n ; i++) + { + int width = 0, height = 0; + m_imageListNormal->GetSize(i, width, height); + if (height > m_lineHeight) m_lineHeight = height; + } + } + + if (m_imageListButtons) { - m_imageListNormal->GetSize(i, width, height); - if (height > m_lineHeight) m_lineHeight = height; + // Calculate a m_lineHeight value from the Button image sizes. + // May be toggle off. Then wxGenericTreeCtrl will spread when + // necessary (which might look ugly). + int n = m_imageListButtons->GetImageCount(); + for (int i = 0; i < n ; i++) + { + int width = 0, height = 0; + m_imageListButtons->GetSize(i, width, height); + if (height > m_lineHeight) m_lineHeight = height; + } } - if (m_lineHeight < 40) + if (m_lineHeight < 30) m_lineHeight += 2; // at least 2 pixels else m_lineHeight += m_lineHeight/10; // otherwise 10% extra spacing } +void wxGenericTreeCtrl::SetImageList(wxImageList *imageList) +{ + if (m_ownsImageListNormal) delete m_imageListNormal; + m_imageListNormal = imageList; + m_ownsImageListNormal = FALSE; + m_dirty = TRUE; + // Don't do any drawing if we're setting the list to NULL, + // since we may be in the process of deleting the tree control. + if (imageList) + CalculateLineHeight(); +} + void wxGenericTreeCtrl::SetStateImageList(wxImageList *imageList) { if (m_ownsImageListState) delete m_imageListState; @@ -1703,6 +2032,15 @@ void wxGenericTreeCtrl::SetStateImageList(wxImageList *imageList) m_ownsImageListState = FALSE; } +void wxGenericTreeCtrl::SetButtonsImageList(wxImageList *imageList) +{ + if (m_ownsImageListButtons) delete m_imageListButtons; + m_imageListButtons = imageList; + m_ownsImageListButtons = FALSE; + m_dirty = TRUE; + CalculateLineHeight(); +} + void wxGenericTreeCtrl::AssignImageList(wxImageList *imageList) { SetImageList(imageList); @@ -1715,6 +2053,12 @@ void wxGenericTreeCtrl::AssignStateImageList(wxImageList *imageList) m_ownsImageListState = TRUE; } +void wxGenericTreeCtrl::AssignButtonsImageList(wxImageList *imageList) +{ + SetButtonsImageList(imageList); + m_ownsImageListButtons = TRUE; +} + // ----------------------------------------------------------------------------- // helpers // ----------------------------------------------------------------------------- @@ -1723,8 +2067,7 @@ void wxGenericTreeCtrl::AdjustMyScrollbars() { if (m_anchor) { - int x = 0; - int y = 0; + int x = 0, y = 0; m_anchor->GetSize( x, y, this ); y += PIXELS_PER_UNIT+2; // one more scrollbar unit + 2 pixels x += PIXELS_PER_UNIT+2; // one more scrollbar unit + 2 pixels @@ -1748,18 +2091,18 @@ int wxGenericTreeCtrl::GetLineHeight(wxGenericTreeItem *item) const void wxGenericTreeCtrl::PaintItem(wxGenericTreeItem *item, wxDC& dc) { + // TODO implement "state" icon on items + wxTreeItemAttr *attr = item->GetAttributes(); if ( attr && attr->HasFont() ) dc.SetFont(attr->GetFont()); else if (item->IsBold()) dc.SetFont(m_boldFont); - long text_w = 0; - long text_h = 0; + long text_w = 0, text_h = 0; dc.GetTextExtent( item->GetText(), &text_w, &text_h ); - int image_h = 0; - int image_w = 0; + int image_h = 0, image_w = 0; int image = item->GetCurrentImage(); if ( image != NO_IMAGE ) { @@ -1776,8 +2119,23 @@ void wxGenericTreeCtrl::PaintItem(wxGenericTreeItem *item, wxDC& dc) int total_h = GetLineHeight(item); - if (item->IsSelected()) - dc.SetBrush(*m_hilightBrush); + if ( item->IsSelected() ) + { +// under mac selections are only a rectangle in case they don't have the focus +#ifdef __WXMAC__ + if ( !m_hasFocus ) + { + dc.SetBrush( *wxTRANSPARENT_BRUSH ) ; + dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ) , 1 , wxSOLID ) ) ; + } + else + { + dc.SetBrush( *m_hilightBrush ) ; + } +#else + dc.SetBrush(*(m_hasFocus ? m_hilightBrush : m_hilightUnfocusedBrush)); +#endif + } else { wxColour colBg; @@ -1788,21 +2146,31 @@ void wxGenericTreeCtrl::PaintItem(wxGenericTreeItem *item, wxDC& dc) dc.SetBrush(wxBrush(colBg, wxSOLID)); } - int offset = 0; - if (HasFlag(wxTR_ROW_LINES)) offset = 1; + int offset = HasFlag(wxTR_ROW_LINES) ? 1 : 0; - if (item->IsSelected() && image != NO_IMAGE) + if ( HasFlag(wxTR_FULL_ROW_HIGHLIGHT) ) { - // If it's selected, and there's an image, then we should - // take care to leave the area under the image painted in the - // background colour. - dc.DrawRectangle( item->GetX() + image_w - 2, item->GetY()+offset, - item->GetWidth() - image_w + 2, total_h-offset ); + int x, y, w, h; + + DoGetPosition(&x, &y); + DoGetSize(&w, &h); + dc.DrawRectangle(x, item->GetY()+offset, w, total_h-offset); } else { - dc.DrawRectangle( item->GetX()-2, item->GetY()+offset, - item->GetWidth()+2, total_h-offset ); + if ( item->IsSelected() && image != NO_IMAGE ) + { + // If it's selected, and there's an image, then we should + // take care to leave the area under the image painted in the + // background colour. + dc.DrawRectangle( item->GetX() + image_w - 2, item->GetY()+offset, + item->GetWidth() - image_w + 2, total_h-offset ); + } + else + { + dc.DrawRectangle( item->GetX()-2, item->GetY()+offset, + item->GetWidth()+2, total_h-offset ); + } } if ( image != NO_IMAGE ) @@ -1828,99 +2196,69 @@ void wxGenericTreeCtrl::PaintItem(wxGenericTreeItem *item, wxDC& dc) // Now y stands for the top of the item, whereas it used to stand for middle ! void wxGenericTreeCtrl::PaintLevel( wxGenericTreeItem *item, wxDC &dc, int level, int &y ) { - int x = (level+1)*m_indent; - - item->SetX( x+m_spacing ); - item->SetY( y ); - - int oldY = y; - y+=GetLineHeight(item)/2; - - item->SetCross( x, y ); - - int exposed_x = dc.LogicalToDeviceX( 0 ); - int exposed_y = dc.LogicalToDeviceY( item->GetY() ); - - bool drawLines = (HasFlag(wxTR_NO_LINES) && !HasFlag(wxTR_MAC_BUTTONS)); - - if (IsExposed( exposed_x, exposed_y, 10000, GetLineHeight(item) )) // 10000 = very much + int x = level*m_indent; + if (!HasFlag(wxTR_HIDE_ROOT)) { - int startX = x - m_indent; - int endX = x-5; - - if (!item->HasChildren()) endX += 20; - - if (HasFlag( wxTR_MAC_BUTTONS )) - { - if (item->HasPlus()) - { - dc.SetPen( *wxBLACK_PEN ); - dc.SetBrush( *m_hilightBrush ); - - wxPoint button[3]; - - if (item->IsExpanded()) - { - button[0].x = x-5; - button[0].y = y-2; - button[1].x = x+5; - button[1].y = y-2; - button[2].x = x; - button[2].y = y+3; - } - else - { - button[0].y = y-5; - button[0].x = x-2; - button[1].y = y+5; - button[1].x = x-2; - button[2].y = y; - button[2].x = x+3; - } - dc.DrawPolygon( 3, button ); - - dc.SetPen( m_dottedPen ); - } - } - else + x += m_indent; + } + else if (level == 0) + { + // always expand hidden root + int origY = y; + wxArrayGenericTreeItems& children = item->GetChildren(); + int count = children.Count(); + if (count > 0) { - if (drawLines) - dc.DrawLine( startX, y, endX, y ); + int n = 0, oldY; + do { + oldY = y; + PaintLevel(children[n], dc, 1, y); + } while (++n < count); - if (item->HasPlus()) + if (!HasFlag(wxTR_NO_LINES) && HasFlag(wxTR_LINES_AT_ROOT) && count > 0) { - if (drawLines) - dc.DrawLine( x+5, y, x+15, y ); - dc.SetPen( *wxGREY_PEN ); - dc.SetBrush( *wxWHITE_BRUSH ); - dc.DrawRectangle( x-5, y-4, 11, 9 ); - - dc.SetPen( *wxBLACK_PEN ); - dc.DrawLine( x-2, y, x+3, y ); - if (!item->IsExpanded()) - dc.DrawLine( x, y-2, x, y+3 ); - dc.SetPen( m_dottedPen ); + // draw line down to last child + origY += GetLineHeight(children[0])>>1; + oldY += GetLineHeight(children[n-1])>>1; + dc.DrawLine(3, origY, 3, oldY); } } + return; + } - wxPen *pen = wxTRANSPARENT_PEN; - wxColour colText; + item->SetX(x+m_spacing); + item->SetY(y); - if ( item->IsSelected() ) - { - colText = wxSystemSettings::GetSystemColour( wxSYS_COLOUR_HIGHLIGHTTEXT ); + int h = GetLineHeight(item); + int y_top = y; + int y_mid = y_top + (h>>1); + y += h; - if ( m_hasFocus ) - pen = wxBLACK_PEN; + int exposed_x = dc.LogicalToDeviceX(0); + int exposed_y = dc.LogicalToDeviceY(y_top); + + if (IsExposed(exposed_x, exposed_y, 10000, h)) // 10000 = very much + { + wxPen *pen = +#ifndef __WXMAC__ + // don't draw rect outline if we already have the + // background color under Mac + (item->IsSelected() && m_hasFocus) ? wxBLACK_PEN : +#endif // !__WXMAC__ + wxTRANSPARENT_PEN; + wxColour colText; + if ( item->IsSelected() ) + { + colText = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT); } else { wxTreeItemAttr *attr = item->GetAttributes(); - if ( attr && attr->HasTextColour() ) + if (attr && attr->HasTextColour()) colText = attr->GetTextColour(); else - colText = wxSystemSettings::GetSystemColour( wxSYS_COLOUR_WINDOWTEXT ); + colText = GetForegroundColour(); } // prepare to draw @@ -1930,42 +2268,109 @@ void wxGenericTreeCtrl::PaintLevel( wxGenericTreeItem *item, wxDC &dc, int level // draw PaintItem(item, dc); - if (HasFlag( wxTR_ROW_LINES )) + if (HasFlag(wxTR_ROW_LINES)) { - dc.SetPen( *wxWHITE_PEN ); - dc.DrawLine( 0, oldY, 10000, oldY ); - dc.DrawLine( 0, oldY + GetLineHeight(item), 10000, oldY + GetLineHeight(item) ); + // if the background colour is white, choose a + // contrasting color for the lines + dc.SetPen(*((GetBackgroundColour() == *wxWHITE) + ? wxMEDIUM_GREY_PEN : wxWHITE_PEN)); + dc.DrawLine(0, y_top, 10000, y_top); + dc.DrawLine(0, y, 10000, y); } - - // restore DC objects - dc.SetBrush( *wxWHITE_BRUSH ); - dc.SetPen( m_dottedPen ); - dc.SetTextForeground( *wxBLACK ); - } - y = oldY+GetLineHeight(item); + // restore DC objects + dc.SetBrush(*wxWHITE_BRUSH); + dc.SetPen(m_dottedPen); + dc.SetTextForeground(*wxBLACK); - if (item->IsExpanded()) - { - oldY+=GetLineHeight(item)/2; - int semiOldY=0; + if ( !HasFlag(wxTR_NO_LINES) ) + { + // draw the horizontal line here + int x_start = x; + if (x > (signed)m_indent) + x_start -= m_indent; + else if (HasFlag(wxTR_LINES_AT_ROOT)) + x_start = 3; + dc.DrawLine(x_start, y_mid, x + m_spacing, y_mid); + } - wxArrayGenericTreeItems& children = item->GetChildren(); - size_t n, count = children.Count(); - for ( n = 0; n < count; ++n ) + // should the item show a button? + if ( item->HasPlus() && HasButtons() ) { - semiOldY=y; - PaintLevel( children[n], dc, level+1, y ); + if ( m_imageListButtons ) + { + // draw the image button here + int image_h = 0, + image_w = 0; + int image = item->IsExpanded() ? wxTreeItemIcon_Expanded + : wxTreeItemIcon_Normal; + if ( item->IsSelected() ) + image += wxTreeItemIcon_Selected - wxTreeItemIcon_Normal; + + m_imageListButtons->GetSize(image, image_w, image_h); + int xx = x - image_w/2; + int yy = y_mid - image_h/2; + + wxDCClipper clip(dc, xx, yy, image_w, image_h); + m_imageListButtons->Draw(image, dc, xx, yy, + wxIMAGELIST_DRAW_TRANSPARENT); + } + else // no custom buttons + { + static const int wImage = 9; + static const int hImage = 9; + + wxRendererNative::Get().DrawTreeItemButton + ( + this, + dc, + wxRect(x - wImage/2, + y_mid - hImage/2, + wImage, hImage), + item->IsExpanded() + ? wxCONTROL_EXPANDED + : 0 + ); + } } + } - // it may happen that the item is expanded but has no items (when you - // delete all its children for example) - don't draw the vertical line - // in this case + if (item->IsExpanded()) + { + wxArrayGenericTreeItems& children = item->GetChildren(); + int count = children.Count(); if (count > 0) { - semiOldY+=GetLineHeight(children[--n])/2; - if (drawLines) - dc.DrawLine( x, oldY+5, x, semiOldY ); + int n = 0, oldY; + ++level; + do { + oldY = y; + PaintLevel(children[n], dc, level, y); + } while (++n < count); + + if (!HasFlag(wxTR_NO_LINES) && count > 0) + { + // draw line down to last child + oldY += GetLineHeight(children[n-1])>>1; + if (HasButtons()) y_mid += 5; + + // Only draw the portion of the line that is visible, in case it is huge + wxCoord xOrigin=0, yOrigin=0, width, height; + dc.GetDeviceOrigin(&xOrigin, &yOrigin); + yOrigin = abs(yOrigin); + GetClientSize(&width, &height); + + // Move end points to the begining/end of the view? + if (y_mid < yOrigin) + y_mid = yOrigin; + if (oldY > yOrigin + height) + oldY = yOrigin + height; + + // after the adjustments if y_mid is larger than oldY then the line + // isn't visible at all so don't draw anything + if (y_mid < oldY) + dc.DrawLine(x, y_mid, x, oldY); + } } } } @@ -2055,26 +2460,34 @@ void wxGenericTreeCtrl::OnPaint( wxPaintEvent &WXUNUSED(event) ) PaintLevel( m_anchor, dc, 0, y ); } -void wxGenericTreeCtrl::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) +void wxGenericTreeCtrl::OnSetFocus( wxFocusEvent &event ) { m_hasFocus = TRUE; - if (m_current) RefreshLine( m_current ); + RefreshSelected(); + + event.Skip(); } -void wxGenericTreeCtrl::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) +void wxGenericTreeCtrl::OnKillFocus( wxFocusEvent &event ) { m_hasFocus = FALSE; - if (m_current) RefreshLine( m_current ); + RefreshSelected(); + + event.Skip(); } void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) { wxTreeEvent te( wxEVT_COMMAND_TREE_KEY_DOWN, GetId() ); - te.m_code = (int)event.KeyCode(); + te.m_evtKey = event; te.SetEventObject( this ); - GetEventHandler()->ProcessEvent( te ); + if ( GetEventHandler()->ProcessEvent( te ) ) + { + // intercepted by the user code + return; + } if ( (m_current == 0) || (m_key_current == 0) ) { @@ -2099,7 +2512,9 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) // right : open if parent and go next // home : go to root // end : go to last item without opening parents - switch (event.KeyCode()) + // alnum : start or continue searching for the item with this prefix + int keyCode = event.GetKeyCode(); + switch ( keyCode ) { case '+': case WXK_ADD: @@ -2129,33 +2544,44 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) case ' ': case WXK_RETURN: + if ( !event.HasModifiers() ) { wxTreeEvent event( wxEVT_COMMAND_TREE_ITEM_ACTIVATED, GetId() ); - event.m_item = (long) m_current; - event.m_code = 0; + event.m_item = m_current; event.SetEventObject( this ); GetEventHandler()->ProcessEvent( event ); } + + // in any case, also generate the normal key event for this key, + // even if we generated the ACTIVATED event above: this is what + // wxMSW does and it makes sense because you might not want to + // process ACTIVATED event at all and handle Space and Return + // directly (and differently) which would be impossible otherwise + event.Skip(); break; - // up goes to the previous sibling or to the last of its children if - // it's expanded + // up goes to the previous sibling or to the last + // of its children if it's expanded case WXK_UP: { wxTreeItemId prev = GetPrevSibling( m_key_current ); if (!prev) { - prev = GetParent( m_key_current ); + prev = GetItemParent( m_key_current ); + if ((prev == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT)) + { + break; // don't go to root if it is hidden + } if (prev) { - long cockie = 0; + wxTreeItemIdValue cookie; wxTreeItemId current = m_key_current; - if (current == GetFirstChild( prev, cockie )) + // TODO: Huh? If we get here, we'd better be the first child of our parent. How else could it be? + if (current == GetFirstChild( prev, cookie )) { // otherwise we return to where we came from SelectItem( prev, unselect_others, extended_select ); m_key_current= (wxGenericTreeItem*) prev.m_pItem; - EnsureVisible( prev ); break; } } @@ -2173,7 +2599,6 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) SelectItem( prev, unselect_others, extended_select ); m_key_current=(wxGenericTreeItem*) prev.m_pItem; - EnsureVisible( prev ); } } break; @@ -2181,18 +2606,22 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) // left arrow goes to the parent case WXK_LEFT: { - wxTreeItemId prev = GetParent( m_current ); + wxTreeItemId prev = GetItemParent( m_current ); + if ((prev == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT)) + { + // don't go to root if it is hidden + prev = GetPrevSibling( m_current ); + } if (prev) { - EnsureVisible( prev ); SelectItem( prev, unselect_others, extended_select ); } } break; case WXK_RIGHT: - // this works the same as the down arrow except that we also expand the - // item if it wasn't expanded yet + // this works the same as the down arrow except that we + // also expand the item if it wasn't expanded yet Expand(m_current); // fall through @@ -2200,11 +2629,10 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) { if (IsExpanded(m_key_current) && HasChildren(m_key_current)) { - long cookie = 0; + wxTreeItemIdValue cookie; wxTreeItemId child = GetFirstChild( m_key_current, cookie ); SelectItem( child, unselect_others, extended_select ); m_key_current=(wxGenericTreeItem*) child.m_pItem; - EnsureVisible( child ); } else { @@ -2212,9 +2640,9 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) if (!next) { wxTreeItemId current = m_key_current; - while (current && !next) + while (current.IsOk() && !next) { - current = GetParent( current ); + current = GetItemParent( current ); if (current) next = GetNextSibling( current ); } } @@ -2222,7 +2650,6 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) { SelectItem( next, unselect_others, extended_select ); m_key_current=(wxGenericTreeItem*) next.m_pItem; - EnsureVisible( next ); } } } @@ -2248,7 +2675,6 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) if ( last.IsOk() ) { - EnsureVisible( last ); SelectItem( last, unselect_others, extended_select ); } } @@ -2258,51 +2684,93 @@ void wxGenericTreeCtrl::OnChar( wxKeyEvent &event ) case WXK_HOME: { wxTreeItemId prev = GetRootItem(); - if (prev) + if (!prev) + break; + + if ( HasFlag(wxTR_HIDE_ROOT) ) { - EnsureVisible( prev ); - SelectItem( prev, unselect_others, extended_select ); + wxTreeItemIdValue cookie; + prev = GetFirstChild(prev, cookie); + if (!prev) + break; } + + SelectItem( prev, unselect_others, extended_select ); } break; default: - event.Skip(); + // do not use wxIsalnum() here + if ( !event.HasModifiers() && + ((keyCode >= '0' && keyCode <= '9') || + (keyCode >= 'a' && keyCode <= 'z') || + (keyCode >= 'A' && keyCode <= 'Z' ))) + { + // find the next item starting with the given prefix + char ch = (char)keyCode; + + wxTreeItemId id = FindItem(m_current, m_findPrefix + (wxChar)ch); + if ( !id.IsOk() ) + { + // no such item + break; + } + + SelectItem(id); + + m_findPrefix += ch; + + // also start the timer to reset the current prefix if the user + // doesn't press any more alnum keys soon -- we wouldn't want + // to use this prefix for a new item search + if ( !m_findTimer ) + { + m_findTimer = new wxTreeFindTimer(this); + } + + m_findTimer->Start(wxTreeFindTimer::DELAY, wxTIMER_ONE_SHOT); + } + else + { + event.Skip(); + } } } wxTreeItemId wxGenericTreeCtrl::HitTest(const wxPoint& point, int& flags) { - // We have to call this here because the label in - // question might just have been added and no screen - // update taken place. - // JACS: removed this because the yield can cause the window to be - // deleted from under us if a close window event is pending - // if (m_dirty) wxYieldIfNeeded(); + // JACS: removed wxYieldIfNeeded() because it can cause the window + // to be deleted from under us if a close window event is pending - wxClientDC dc(this); - PrepareDC(dc); - wxCoord x = dc.DeviceToLogicalX( point.x ); - wxCoord y = dc.DeviceToLogicalY( point.y ); int w, h; GetSize(&w, &h); - flags=0; - if (point.x<0) flags|=wxTREE_HITTEST_TOLEFT; - if (point.x>w) flags|=wxTREE_HITTEST_TORIGHT; - if (point.y<0) flags|=wxTREE_HITTEST_ABOVE; - if (point.y>h) flags|=wxTREE_HITTEST_BELOW; + if (point.x<0) flags |= wxTREE_HITTEST_TOLEFT; + if (point.x>w) flags |= wxTREE_HITTEST_TORIGHT; + if (point.y<0) flags |= wxTREE_HITTEST_ABOVE; + if (point.y>h) flags |= wxTREE_HITTEST_BELOW; + if (flags) return wxTreeItemId(); - if (m_anchor) - return m_anchor->HitTest( wxPoint(x, y), this, flags); - else + if (m_anchor == NULL) + { + flags = wxTREE_HITTEST_NOWHERE; + return wxTreeItemId(); + } + + wxGenericTreeItem *hit = m_anchor->HitTest(CalcUnscrolledPosition(point), + this, flags, 0); + if (hit == NULL) + { + flags = wxTREE_HITTEST_NOWHERE; return wxTreeItemId(); + } + return hit; } // get the bounding rectangle of the item (or of its label only) bool wxGenericTreeCtrl::GetBoundingRect(const wxTreeItemId& item, - wxRect& rect, - bool WXUNUSED(textOnly)) const + wxRect& rect, + bool WXUNUSED(textOnly)) const { wxCHECK_MSG( item.IsOk(), FALSE, _T("invalid item in wxGenericTreeCtrl::GetBoundingRect") ); @@ -2320,82 +2788,70 @@ bool wxGenericTreeCtrl::GetBoundingRect(const wxTreeItemId& item, return TRUE; } -/* **** */ - void wxGenericTreeCtrl::Edit( const wxTreeItemId& item ) { - if (!item.IsOk()) return; + wxCHECK_RET( item.IsOk(), _T("can't edit an invalid item") ); - m_currentEdit = (wxGenericTreeItem*) item.m_pItem; + wxGenericTreeItem *itemEdit = (wxGenericTreeItem *)item.m_pItem; wxTreeEvent te( wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT, GetId() ); - te.m_item = (long) m_currentEdit; + te.m_item = itemEdit; te.SetEventObject( this ); - GetEventHandler()->ProcessEvent( te ); - - if (!te.IsAllowed()) return; + if ( GetEventHandler()->ProcessEvent( te ) && !te.IsAllowed() ) + { + // vetoed by user + return; + } // We have to call this here because the label in // question might just have been added and no screen // update taken place. - if (m_dirty) wxYieldIfNeeded(); - - wxString s = m_currentEdit->GetText(); - int x = m_currentEdit->GetX(); - int y = m_currentEdit->GetY(); - int w = m_currentEdit->GetWidth(); - int h = m_currentEdit->GetHeight(); - - int image_h = 0; - int image_w = 0; + if ( m_dirty ) + wxYieldIfNeeded(); - int image = m_currentEdit->GetCurrentImage(); - if ( image != NO_IMAGE ) - { - if ( m_imageListNormal ) - { - m_imageListNormal->GetSize( image, image_w, image_h ); - image_w += 4; - } - else - { - wxFAIL_MSG(_T("you must create an image list to use images!")); - } - } - x += image_w; - w -= image_w + 4; // I don't know why +4 is needed + m_textCtrl = new wxTreeTextCtrl(this, itemEdit); - wxClientDC dc(this); - PrepareDC( dc ); - x = dc.LogicalToDeviceX( x ); - y = dc.LogicalToDeviceY( y ); + m_textCtrl->SetFocus(); +} - wxTreeTextCtrl *text = new wxTreeTextCtrl(this, -1, - &m_renameAccept, - &m_renameRes, - this, - s, - wxPoint(x-4,y-4), - wxSize(w+11,h+8)); - text->SetFocus(); +// returns a pointer to the text edit control if the item is being +// edited, NULL otherwise (it's assumed that no more than one item may +// be edited simultaneously) +wxTextCtrl* wxGenericTreeCtrl::GetEditControl() const +{ + return m_textCtrl; } -void wxGenericTreeCtrl::OnRenameTimer() +bool wxGenericTreeCtrl::OnRenameAccept(wxGenericTreeItem *item, + const wxString& value) { - Edit( m_current ); + wxTreeEvent le( wxEVT_COMMAND_TREE_END_LABEL_EDIT, GetId() ); + le.m_item = item; + le.SetEventObject( this ); + le.m_label = value; + le.m_editCancelled = FALSE; + + return !GetEventHandler()->ProcessEvent( le ) || le.IsAllowed(); } -void wxGenericTreeCtrl::OnRenameAccept() +void wxGenericTreeCtrl::OnRenameCancelled(wxGenericTreeItem *item) { + // let owner know that the edit was cancelled wxTreeEvent le( wxEVT_COMMAND_TREE_END_LABEL_EDIT, GetId() ); - le.m_item = (long) m_currentEdit; + le.m_item = item; le.SetEventObject( this ); - le.m_label = m_renameRes; + le.m_label = wxEmptyString; + le.m_editCancelled = TRUE; + GetEventHandler()->ProcessEvent( le ); +} + + - if (!le.IsAllowed()) return; - SetItemText( m_currentEdit, m_renameRes ); +void wxGenericTreeCtrl::OnRenameTimer() +{ + Edit( m_current ); } void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) @@ -2417,18 +2873,15 @@ void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) return; } - wxClientDC dc(this); - PrepareDC(dc); - wxCoord x = dc.DeviceToLogicalX( event.GetX() ); - wxCoord y = dc.DeviceToLogicalY( event.GetY() ); + wxPoint pt = CalcUnscrolledPosition(event.GetPosition()); int flags = 0; - wxGenericTreeItem *item = m_anchor->HitTest( wxPoint(x,y), this, flags); + wxGenericTreeItem *item = m_anchor->HitTest(pt, this, flags, 0); if ( event.Dragging() && !m_isDragging ) { if (m_dragCount == 0) - m_dragStart = wxPoint(x,y); + m_dragStart = pt; m_dragCount++; @@ -2443,7 +2896,7 @@ void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) : wxEVT_COMMAND_TREE_BEGIN_DRAG; wxTreeEvent nevent( command, GetId() ); - nevent.m_item = (long) m_current; + nevent.m_item = m_current; nevent.SetEventObject(this); // by default the dragging is not supported, the user code must @@ -2494,11 +2947,18 @@ void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) // erase the highlighting DrawDropEffect(m_dropTarget); + if ( m_oldSelection ) + { + m_oldSelection->SetHilight(TRUE); + RefreshLine(m_oldSelection); + m_oldSelection = (wxGenericTreeItem *)NULL; + } + // generate the drag end event wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG, GetId()); - event.m_item = (long) item; - event.m_pointDrag = wxPoint(x, y); + event.m_item = item; + event.m_pointDrag = pt; event.SetEventObject(this); (void)GetEventHandler()->ProcessEvent(event); @@ -2506,13 +2966,6 @@ void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) m_isDragging = FALSE; m_dropTarget = (wxGenericTreeItem *)NULL; - if ( m_oldSelection ) - { - m_oldSelection->SetHilight(TRUE); - RefreshLine(m_oldSelection); - m_oldSelection = (wxGenericTreeItem *)NULL; - } - ReleaseMouse(); SetCursor(m_oldCursor); @@ -2530,26 +2983,45 @@ void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) if ( event.RightDown() ) { wxTreeEvent nevent(wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK, GetId()); - nevent.m_item = (long) item; - nevent.m_code = 0; - CalcScrolledPosition(x, y, - &nevent.m_pointDrag.x, - &nevent.m_pointDrag.y); + nevent.m_item = item; + nevent.m_pointDrag = CalcScrolledPosition(pt); nevent.SetEventObject(this); GetEventHandler()->ProcessEvent(nevent); } else if ( event.LeftUp() ) { + // this facilitates multiple-item drag-and-drop + + if (item && HasFlag(wxTR_MULTIPLE)) + { + wxArrayTreeItemIds selections; + size_t count = GetSelections(selections); + + if (count > 1 && + !event.ControlDown() && + !event.ShiftDown()) + { + SelectItem(item, true, false); + } + } + if ( m_lastOnSame ) { if ( (item == m_current) && (flags & wxTREE_HITTEST_ONITEMLABEL) && HasFlag(wxTR_EDIT_LABELS) ) { - if ( m_renameTimer->IsRunning() ) - m_renameTimer->Stop(); + if ( m_renameTimer ) + { + if ( m_renameTimer->IsRunning() ) + m_renameTimer->Stop(); + } + else + { + m_renameTimer = new wxTreeRenameTimer( this ); + } - m_renameTimer->Start( 100, TRUE ); + m_renameTimer->Start( wxTreeRenameTimer::DELAY, TRUE ); } m_lastOnSame = FALSE; @@ -2575,41 +3047,75 @@ void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event ) return; } - // how should the selection work for this event? - bool is_multiple, extended_select, unselect_others; - EventFlagsToSelType(GetWindowStyleFlag(), - event.ShiftDown(), - event.ControlDown(), - is_multiple, extended_select, unselect_others); - SelectItem(item, unselect_others, extended_select); + // clear the previously selected items, if the + // user clicked outside of the present selection. + // otherwise, perform the deselection on mouse-up. + // this allows multiple drag and drop to work. + + if (!IsSelected(item)) + { + // how should the selection work for this event? + bool is_multiple, extended_select, unselect_others; + EventFlagsToSelType(GetWindowStyleFlag(), + event.ShiftDown(), + event.ControlDown(), + is_multiple, extended_select, unselect_others); + + SelectItem(item, unselect_others, extended_select); + } + + // For some reason, Windows isn't recognizing a left double-click, + // so we need to simulate it here. Allow 200 milliseconds for now. if ( event.LeftDClick() ) { // double clicking should not start editing the item label - m_renameTimer->Stop(); + if ( m_renameTimer ) + m_renameTimer->Stop(); + m_lastOnSame = FALSE; + // send activate event first wxTreeEvent nevent( wxEVT_COMMAND_TREE_ITEM_ACTIVATED, GetId() ); - nevent.m_item = (long) item; - nevent.m_code = 0; - CalcScrolledPosition(x, y, - &nevent.m_pointDrag.x, - &nevent.m_pointDrag.y); + nevent.m_item = item; + nevent.m_pointDrag = CalcScrolledPosition(pt); nevent.SetEventObject( this ); - GetEventHandler()->ProcessEvent( nevent ); + if ( !GetEventHandler()->ProcessEvent( nevent ) ) + { + // if the user code didn't process the activate event, + // handle it ourselves by toggling the item when it is + // double clicked + if ( item->HasPlus() ) + { + Toggle(item); + } + } } } } } -void wxGenericTreeCtrl::OnIdle( wxIdleEvent &WXUNUSED(event) ) +void wxGenericTreeCtrl::OnInternalIdle() { + wxWindow::OnInternalIdle(); + + // Check if we need to select the root item + // because nothing else has been selected. + // Delaying it means that we can invoke event handlers + // as required, when a first item is selected. + if (!HasFlag(wxTR_MULTIPLE) && !GetSelection().IsOk()) + { + if (m_select_me) + SelectItem(m_select_me); + else if (GetRootItem().IsOk()) + SelectItem(GetRootItem()); + } + /* after all changes have been done to the tree control, * we actually redraw the tree when everything is over */ - if (!m_dirty) - return; + if (!m_dirty) return; m_dirty = FALSE; @@ -2623,7 +3129,10 @@ void wxGenericTreeCtrl::CalculateSize( wxGenericTreeItem *item, wxDC &dc ) wxCoord text_w = 0; wxCoord text_h = 0; - if (item->IsBold()) + wxTreeItemAttr *attr = item->GetAttributes(); + if ( attr && attr->HasFont() ) + dc.SetFont(attr->GetFont()); + else if ( item->IsBold() ) dc.SetFont(m_boldFont); dc.GetTextExtent( item->GetText(), &text_w, &text_h ); @@ -2646,7 +3155,7 @@ void wxGenericTreeCtrl::CalculateSize( wxGenericTreeItem *item, wxDC &dc ) int total_h = (image_h > text_h) ? image_h : text_h; - if (total_h < 40) + if (total_h < 30) total_h += 2; // at least 2 pixels else total_h += total_h/10; // otherwise 10% extra spacing @@ -2663,25 +3172,37 @@ void wxGenericTreeCtrl::CalculateSize( wxGenericTreeItem *item, wxDC &dc ) // not the middle of it ! void wxGenericTreeCtrl::CalculateLevel( wxGenericTreeItem *item, wxDC &dc, int level, int &y ) { - int horizX = level*m_indent; + int x = level*m_indent; + if (!HasFlag(wxTR_HIDE_ROOT)) + { + x += m_indent; + } + else if (level == 0) + { + // a hidden root is not evaluated, but its + // children are always calculated + goto Recurse; + } CalculateSize( item, dc ); // set its position - item->SetX( horizX+m_indent+m_spacing ); + item->SetX( x+m_spacing ); item->SetY( y ); - y+=GetLineHeight(item); + y += GetLineHeight(item); if ( !item->IsExpanded() ) { - // we dont need to calculate collapsed branches + // we don't need to calculate collapsed branches return; } + Recurse: wxArrayGenericTreeItems& children = item->GetChildren(); size_t n, count = children.Count(); + ++level; for (n = 0; n < count; ++n ) - CalculateLevel( children[n], dc, level+1, y ); // recurse + CalculateLevel( children[n], dc, level, y ); // recurse } void wxGenericTreeCtrl::CalculatePositions() @@ -2705,20 +3226,14 @@ void wxGenericTreeCtrl::RefreshSubtree(wxGenericTreeItem *item) { if (m_dirty) return; - wxClientDC dc(this); - PrepareDC(dc); - - int cw = 0; - int ch = 0; - GetClientSize( &cw, &ch ); + wxSize client = GetClientSize(); wxRect rect; - rect.x = dc.LogicalToDeviceX( 0 ); - rect.width = cw; - rect.y = dc.LogicalToDeviceY( item->GetY() ); - rect.height = ch; + CalcScrolledPosition(0, item->GetY(), NULL, &rect.y); + rect.width = client.x; + rect.height = client.y; - Refresh( TRUE, &rect ); + Refresh(TRUE, &rect); AdjustMyScrollbars(); } @@ -2727,19 +3242,57 @@ void wxGenericTreeCtrl::RefreshLine( wxGenericTreeItem *item ) { if (m_dirty) return; - wxClientDC dc(this); - PrepareDC( dc ); - - int cw = 0; - int ch = 0; - GetClientSize( &cw, &ch ); - wxRect rect; - rect.x = dc.LogicalToDeviceX( 0 ); - rect.y = dc.LogicalToDeviceY( item->GetY() ); - rect.width = cw; + CalcScrolledPosition(0, item->GetY(), NULL, &rect.y); + rect.width = GetClientSize().x; rect.height = GetLineHeight(item); //dc.GetCharHeight() + 6; - Refresh( TRUE, &rect ); + Refresh(TRUE, &rect); +} + +void wxGenericTreeCtrl::RefreshSelected() +{ + // TODO: this is awfully inefficient, we should keep the list of all + // selected items internally, should be much faster + if ( m_anchor ) + RefreshSelectedUnder(m_anchor); +} + +void wxGenericTreeCtrl::RefreshSelectedUnder(wxGenericTreeItem *item) +{ + if ( item->IsSelected() ) + RefreshLine(item); + + const wxArrayGenericTreeItems& children = item->GetChildren(); + size_t count = children.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + RefreshSelectedUnder(children[n]); + } +} + +// ---------------------------------------------------------------------------- +// changing colours: we need to refresh the tree control +// ---------------------------------------------------------------------------- + +bool wxGenericTreeCtrl::SetBackgroundColour(const wxColour& colour) +{ + if ( !wxWindow::SetBackgroundColour(colour) ) + return FALSE; + + Refresh(); + + return TRUE; +} + +bool wxGenericTreeCtrl::SetForegroundColour(const wxColour& colour) +{ + if ( !wxWindow::SetForegroundColour(colour) ) + return FALSE; + + Refresh(); + + return TRUE; } +#endif // wxUSE_TREECTRL