the edit label process in wxTreeCtrl and wxListCtrl. Take icon size in the first column into account when shwoing text control. Only activate label editing when clicked on the first label, not somewhere else git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@47097 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
3690 lines
107 KiB
C++
3690 lines
107 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/generic/treectlg.cpp
|
|
// Purpose: generic tree control implementation
|
|
// Author: Robert Roebling
|
|
// Created: 01/02/97
|
|
// Modified: 22/10/98 - almost total rewrite, simpler interface (VZ)
|
|
// Id: $Id$
|
|
// Copyright: (c) 1998 Robert Roebling and Julian Smart
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// =============================================================================
|
|
// declarations
|
|
// =============================================================================
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// headers
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_TREECTRL
|
|
|
|
#include "wx/treectrl.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/dcclient.h"
|
|
#include "wx/timer.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/listbox.h"
|
|
#include "wx/textctrl.h"
|
|
#endif
|
|
|
|
#include "wx/generic/treectlg.h"
|
|
#include "wx/imaglist.h"
|
|
|
|
#include "wx/renderer.h"
|
|
|
|
#ifdef __WXMAC__
|
|
#include "wx/mac/private.h"
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// array types
|
|
// -----------------------------------------------------------------------------
|
|
|
|
class WXDLLEXPORT wxGenericTreeItem;
|
|
|
|
WX_DEFINE_EXPORTED_ARRAY_PTR(wxGenericTreeItem *, wxArrayGenericTreeItems);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// constants
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static const int NO_IMAGE = -1;
|
|
|
|
static const int PIXELS_PER_UNIT = 10;
|
|
|
|
// the margin between the item image and the item text
|
|
static const int MARGIN_BETWEEN_IMAGE_AND_TEXT = 4;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// private classes
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// timer used for enabling in-place edit
|
|
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 = 250 };
|
|
|
|
wxTreeRenameTimer( wxGenericTreeCtrl *owner );
|
|
|
|
virtual void Notify();
|
|
|
|
private:
|
|
wxGenericTreeCtrl *m_owner;
|
|
|
|
DECLARE_NO_COPY_CLASS(wxTreeRenameTimer)
|
|
};
|
|
|
|
// control used for in-place edit
|
|
class WXDLLEXPORT wxTreeTextCtrl: public wxTextCtrl
|
|
{
|
|
public:
|
|
wxTreeTextCtrl(wxGenericTreeCtrl *owner, wxGenericTreeItem *item);
|
|
|
|
void EndEdit(bool discardChanges = false)
|
|
{
|
|
if ( discardChanges )
|
|
{
|
|
StopEditing();
|
|
}
|
|
else
|
|
{
|
|
m_aboutToFinish = true;
|
|
|
|
// Notify the owner about the changes
|
|
AcceptChanges();
|
|
|
|
// Even if vetoed, close the control (consistent with MSW)
|
|
Finish();
|
|
}
|
|
}
|
|
|
|
void StopEditing()
|
|
{
|
|
Finish();
|
|
m_owner->OnRenameCancelled(m_itemEdited);
|
|
}
|
|
const wxGenericTreeItem* item() const { return m_itemEdited; }
|
|
|
|
protected:
|
|
void OnChar( wxKeyEvent &event );
|
|
void OnKeyUp( wxKeyEvent &event );
|
|
void OnKillFocus( wxFocusEvent &event );
|
|
|
|
bool AcceptChanges();
|
|
void Finish();
|
|
|
|
private:
|
|
wxGenericTreeCtrl *m_owner;
|
|
wxGenericTreeItem *m_itemEdited;
|
|
wxString m_startValue;
|
|
bool m_finished;
|
|
bool m_aboutToFinish;
|
|
|
|
DECLARE_EVENT_TABLE()
|
|
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
|
|
class WXDLLEXPORT wxGenericTreeItem
|
|
{
|
|
public:
|
|
// ctors & dtor
|
|
wxGenericTreeItem() { m_data = NULL; }
|
|
wxGenericTreeItem( wxGenericTreeItem *parent,
|
|
const wxString& text,
|
|
int image,
|
|
int selImage,
|
|
wxTreeItemData *data );
|
|
|
|
~wxGenericTreeItem();
|
|
|
|
// trivial accessors
|
|
wxArrayGenericTreeItems& GetChildren() { return m_children; }
|
|
|
|
const wxString& GetText() const { return m_text; }
|
|
int GetImage(wxTreeItemIcon which = wxTreeItemIcon_Normal) const
|
|
{ return m_images[which]; }
|
|
wxTreeItemData *GetData() const { return m_data; }
|
|
|
|
// returns the current image for the item (depending on its
|
|
// selected/expanded/whatever state)
|
|
int GetCurrentImage() const;
|
|
|
|
void SetText( const wxString &text );
|
|
void SetImage(int image, wxTreeItemIcon which) { m_images[which] = image; }
|
|
void SetData(wxTreeItemData *data) { m_data = data; }
|
|
|
|
void SetHasPlus(bool has = true) { m_hasPlus = has; }
|
|
|
|
void SetBold(bool bold) { m_isBold = bold; }
|
|
|
|
int GetX() const { return m_x; }
|
|
int GetY() const { return m_y; }
|
|
|
|
void SetX(int x) { m_x = x; }
|
|
void SetY(int y) { m_y = y; }
|
|
|
|
int GetHeight() const { return m_height; }
|
|
int GetWidth() const { return m_width; }
|
|
|
|
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
|
|
void DeleteChildren(wxGenericTreeCtrl *tree);
|
|
|
|
// get count of all children (and grand children if 'recursively')
|
|
size_t GetChildrenCount(bool recursively = true) const;
|
|
|
|
void Insert(wxGenericTreeItem *child, size_t index)
|
|
{ m_children.Insert(child, index); }
|
|
|
|
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 item's label
|
|
wxGenericTreeItem *HitTest( const wxPoint& point,
|
|
const wxGenericTreeCtrl *,
|
|
int &flags,
|
|
int level );
|
|
|
|
void Expand() { m_isCollapsed = false; }
|
|
void Collapse() { m_isCollapsed = true; }
|
|
|
|
void SetHilight( bool set = true ) { m_hasHilight = set; }
|
|
|
|
// status inquiries
|
|
bool HasChildren() const { return !m_children.IsEmpty(); }
|
|
bool IsSelected() const { return m_hasHilight != 0; }
|
|
bool IsExpanded() const { return !m_isCollapsed; }
|
|
bool HasPlus() const { return m_hasPlus || HasChildren(); }
|
|
bool IsBold() const { return m_isBold != 0; }
|
|
|
|
// attributes
|
|
// get them - may be NULL
|
|
wxTreeItemAttr *GetAttributes() const { return m_attr; }
|
|
// get them ensuring that the pointer is not NULL
|
|
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:
|
|
// 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];
|
|
|
|
wxCoord m_x; // (virtual) offset from top
|
|
wxCoord m_y; // (virtual) offset from left
|
|
int m_width; // width of this item
|
|
int m_height; // height of this item
|
|
|
|
// use bitfields to save size
|
|
unsigned int m_isCollapsed :1;
|
|
unsigned int m_hasHilight :1; // same as focused
|
|
unsigned int m_hasPlus :1; // used for item which doesn't have
|
|
// children but has a [+] button
|
|
unsigned int m_isBold :1; // render the label in bold font
|
|
unsigned int m_ownsAttr :1; // delete attribute when done
|
|
|
|
DECLARE_NO_COPY_CLASS(wxGenericTreeItem)
|
|
};
|
|
|
|
// =============================================================================
|
|
// implementation
|
|
// =============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// private functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// translate the key or mouse event flags to the type of selection we're
|
|
// dealing with
|
|
static void EventFlagsToSelType(long style,
|
|
bool shiftDown,
|
|
bool ctrlDown,
|
|
bool &is_multiple,
|
|
bool &extended_select,
|
|
bool &unselect_others)
|
|
{
|
|
is_multiple = (style & wxTR_MULTIPLE) != 0;
|
|
extended_select = shiftDown && is_multiple;
|
|
unselect_others = !(extended_select || (ctrlDown && is_multiple));
|
|
}
|
|
|
|
// check if the given item is under another one
|
|
static bool IsDescendantOf(const wxGenericTreeItem *parent, const wxGenericTreeItem *item)
|
|
{
|
|
while ( item )
|
|
{
|
|
if ( item == parent )
|
|
{
|
|
// item is a descendant of parent
|
|
return true;
|
|
}
|
|
|
|
item = item->GetParent();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// wxTreeRenameTimer (internal)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
wxTreeRenameTimer::wxTreeRenameTimer( wxGenericTreeCtrl *owner )
|
|
{
|
|
m_owner = owner;
|
|
}
|
|
|
|
void wxTreeRenameTimer::Notify()
|
|
{
|
|
m_owner->OnRenameTimer();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxTreeTextCtrl (internal)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BEGIN_EVENT_TABLE(wxTreeTextCtrl,wxTextCtrl)
|
|
EVT_CHAR (wxTreeTextCtrl::OnChar)
|
|
EVT_KEY_UP (wxTreeTextCtrl::OnKeyUp)
|
|
EVT_KILL_FOCUS (wxTreeTextCtrl::OnKillFocus)
|
|
END_EVENT_TABLE()
|
|
|
|
wxTreeTextCtrl::wxTreeTextCtrl(wxGenericTreeCtrl *owner,
|
|
wxGenericTreeItem *item)
|
|
: m_itemEdited(item), m_startValue(item->GetText())
|
|
{
|
|
m_owner = owner;
|
|
m_finished = false;
|
|
m_aboutToFinish = false;
|
|
|
|
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 )
|
|
{
|
|
if ( m_owner->m_imageListNormal )
|
|
{
|
|
m_owner->m_imageListNormal->GetSize( image, image_w, image_h );
|
|
image_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
|
|
}
|
|
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;
|
|
#ifdef __WXMAC__
|
|
wxSize bs = DoGetBestSize() ;
|
|
// edit control height
|
|
if ( h > bs.y - 8 )
|
|
{
|
|
int diff = h - ( bs.y - 8 ) ;
|
|
h -= diff ;
|
|
y += diff / 2 ;
|
|
}
|
|
#endif
|
|
|
|
(void)Create(m_owner, wxID_ANY, m_startValue,
|
|
wxPoint(x - 4, y - 4), wxSize(w + 11, h + 8));
|
|
}
|
|
|
|
bool wxTreeTextCtrl::AcceptChanges()
|
|
{
|
|
const wxString value = GetValue();
|
|
|
|
if ( value == m_startValue )
|
|
{
|
|
// nothing changed, always accept
|
|
// when an item remains unchanged, the owner
|
|
// needs to be notified that the user decided
|
|
// not to change the tree item label, and that
|
|
// the edit has been cancelled
|
|
|
|
m_owner->OnRenameCancelled(m_itemEdited);
|
|
return true;
|
|
}
|
|
|
|
if ( !m_owner->OnRenameAccept(m_itemEdited, value) )
|
|
{
|
|
// vetoed by the user
|
|
return false;
|
|
}
|
|
|
|
// accepted, do rename the item
|
|
m_owner->SetItemText(m_itemEdited, value);
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxTreeTextCtrl::Finish()
|
|
{
|
|
if ( !m_finished )
|
|
{
|
|
m_owner->ResetTextControl();
|
|
|
|
wxPendingDelete.Append(this);
|
|
|
|
m_finished = true;
|
|
|
|
m_owner->SetFocus();
|
|
}
|
|
}
|
|
|
|
void wxTreeTextCtrl::OnChar( wxKeyEvent &event )
|
|
{
|
|
switch ( event.m_keyCode )
|
|
{
|
|
case WXK_RETURN:
|
|
EndEdit();
|
|
break;
|
|
|
|
case WXK_ESCAPE:
|
|
StopEditing();
|
|
break;
|
|
|
|
default:
|
|
event.Skip();
|
|
}
|
|
}
|
|
|
|
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, wxDefaultCoord);
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxTreeTextCtrl::OnKillFocus( wxFocusEvent &event )
|
|
{
|
|
if ( !m_finished && !m_aboutToFinish )
|
|
{
|
|
// We must finish regardless of success, otherwise we'll get
|
|
// focus problems:
|
|
Finish();
|
|
|
|
if ( !AcceptChanges() )
|
|
m_owner->OnRenameCancelled( m_itemEdited );
|
|
}
|
|
|
|
// We must let the native text control handle focus, too, otherwise
|
|
// it could have problems with the cursor (e.g., in wxGTK).
|
|
event.Skip();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// wxGenericTreeItem
|
|
// -----------------------------------------------------------------------------
|
|
|
|
wxGenericTreeItem::wxGenericTreeItem(wxGenericTreeItem *parent,
|
|
const wxString& text,
|
|
int image, int selImage,
|
|
wxTreeItemData *data)
|
|
: m_text(text)
|
|
{
|
|
m_images[wxTreeItemIcon_Normal] = image;
|
|
m_images[wxTreeItemIcon_Selected] = selImage;
|
|
m_images[wxTreeItemIcon_Expanded] = NO_IMAGE;
|
|
m_images[wxTreeItemIcon_SelectedExpanded] = NO_IMAGE;
|
|
|
|
m_data = data;
|
|
m_x = m_y = 0;
|
|
|
|
m_isCollapsed = true;
|
|
m_hasHilight = false;
|
|
m_hasPlus = false;
|
|
m_isBold = false;
|
|
|
|
m_parent = parent;
|
|
|
|
m_attr = (wxTreeItemAttr *)NULL;
|
|
m_ownsAttr = false;
|
|
|
|
// We don't know the height here yet.
|
|
m_width = 0;
|
|
m_height = 0;
|
|
}
|
|
|
|
wxGenericTreeItem::~wxGenericTreeItem()
|
|
{
|
|
delete m_data;
|
|
|
|
if (m_ownsAttr) delete m_attr;
|
|
|
|
wxASSERT_MSG( m_children.IsEmpty(),
|
|
wxT("please call DeleteChildren() before deleting the item") );
|
|
}
|
|
|
|
void wxGenericTreeItem::DeleteChildren(wxGenericTreeCtrl *tree)
|
|
{
|
|
size_t count = m_children.Count();
|
|
for ( size_t n = 0; n < count; n++ )
|
|
{
|
|
wxGenericTreeItem *child = m_children[n];
|
|
tree->SendDeleteEvent(child);
|
|
|
|
child->DeleteChildren(tree);
|
|
if ( child == tree->m_select_me )
|
|
tree->m_select_me = NULL;
|
|
delete child;
|
|
}
|
|
|
|
m_children.Empty();
|
|
}
|
|
|
|
void wxGenericTreeItem::SetText( const wxString &text )
|
|
{
|
|
m_text = text;
|
|
}
|
|
|
|
size_t wxGenericTreeItem::GetChildrenCount(bool recursively) const
|
|
{
|
|
size_t count = m_children.Count();
|
|
if ( !recursively )
|
|
return count;
|
|
|
|
size_t total = count;
|
|
for (size_t n = 0; n < count; ++n)
|
|
{
|
|
total += m_children[n]->GetChildrenCount();
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
void wxGenericTreeItem::GetSize( int &x, int &y,
|
|
const wxGenericTreeCtrl *theButton )
|
|
{
|
|
int bottomY=m_y+theButton->GetLineHeight(this);
|
|
if ( y < bottomY ) y = bottomY;
|
|
int width = m_x + m_width;
|
|
if ( x < width ) x = width;
|
|
|
|
if (IsExpanded())
|
|
{
|
|
size_t count = m_children.Count();
|
|
for ( size_t n = 0; n < count; ++n )
|
|
{
|
|
m_children[n]->GetSize( x, y, theButton );
|
|
}
|
|
}
|
|
}
|
|
|
|
wxGenericTreeItem *wxGenericTreeItem::HitTest(const wxPoint& point,
|
|
const wxGenericTreeCtrl *theCtrl,
|
|
int &flags,
|
|
int level)
|
|
{
|
|
// for a hidden root node, don't evaluate it, but do evaluate children
|
|
if ( !(level == 0 && theCtrl->HasFlag(wxTR_HIDE_ROOT)) )
|
|
{
|
|
// evaluate the item
|
|
int h = theCtrl->GetLineHeight(this);
|
|
if ((point.y > m_y) && (point.y < m_y + h))
|
|
{
|
|
int y_mid = m_y + h/2;
|
|
if (point.y < y_mid )
|
|
flags |= wxTREE_HITTEST_ONITEMUPPERPART;
|
|
else
|
|
flags |= wxTREE_HITTEST_ONITEMLOWERPART;
|
|
|
|
int xCross = m_x - theCtrl->GetSpacing();
|
|
#ifdef __WXMAC__
|
|
// according to the drawing code the triangels are drawn
|
|
// at -4 , -4 from the position up to +10/+10 max
|
|
if ((point.x > xCross-4) && (point.x < xCross+10) &&
|
|
(point.y > y_mid-4) && (point.y < y_mid+10) &&
|
|
HasPlus() && theCtrl->HasButtons() )
|
|
#else
|
|
// 5 is the size of the plus sign
|
|
if ((point.x > xCross-6) && (point.x < xCross+6) &&
|
|
(point.y > y_mid-6) && (point.y < y_mid+6) &&
|
|
HasPlus() && theCtrl->HasButtons() )
|
|
#endif
|
|
{
|
|
flags |= wxTREE_HITTEST_ONITEMBUTTON;
|
|
return this;
|
|
}
|
|
|
|
if ((point.x >= m_x) && (point.x <= m_x+m_width))
|
|
{
|
|
int image_w = -1;
|
|
int image_h;
|
|
|
|
// 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 children are expanded, fall through to evaluate them
|
|
if (m_isCollapsed) return (wxGenericTreeItem*) NULL;
|
|
}
|
|
|
|
// evaluate children
|
|
size_t count = m_children.Count();
|
|
for ( size_t n = 0; n < count; n++ )
|
|
{
|
|
wxGenericTreeItem *res = m_children[n]->HitTest( point,
|
|
theCtrl,
|
|
flags,
|
|
level + 1 );
|
|
if ( res != NULL )
|
|
return res;
|
|
}
|
|
|
|
return (wxGenericTreeItem*) NULL;
|
|
}
|
|
|
|
int wxGenericTreeItem::GetCurrentImage() const
|
|
{
|
|
int image = NO_IMAGE;
|
|
if ( IsExpanded() )
|
|
{
|
|
if ( IsSelected() )
|
|
{
|
|
image = GetImage(wxTreeItemIcon_SelectedExpanded);
|
|
}
|
|
|
|
if ( image == NO_IMAGE )
|
|
{
|
|
// we usually fall back to the normal item, but try just the
|
|
// expanded one (and not selected) first in this case
|
|
image = GetImage(wxTreeItemIcon_Expanded);
|
|
}
|
|
}
|
|
else // not expanded
|
|
{
|
|
if ( IsSelected() )
|
|
image = GetImage(wxTreeItemIcon_Selected);
|
|
}
|
|
|
|
// maybe it doesn't have the specific image we want,
|
|
// try the default one instead
|
|
if ( image == NO_IMAGE ) image = GetImage();
|
|
|
|
return image;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// wxGenericTreeCtrl implementation
|
|
// -----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxGenericTreeCtrl, wxControl)
|
|
|
|
BEGIN_EVENT_TABLE(wxGenericTreeCtrl, wxTreeCtrlBase)
|
|
EVT_PAINT (wxGenericTreeCtrl::OnPaint)
|
|
EVT_SIZE (wxGenericTreeCtrl::OnSize)
|
|
EVT_MOUSE_EVENTS (wxGenericTreeCtrl::OnMouse)
|
|
EVT_CHAR (wxGenericTreeCtrl::OnChar)
|
|
EVT_SET_FOCUS (wxGenericTreeCtrl::OnSetFocus)
|
|
EVT_KILL_FOCUS (wxGenericTreeCtrl::OnKillFocus)
|
|
EVT_TREE_ITEM_GETTOOLTIP(wxID_ANY, wxGenericTreeCtrl::OnGetToolTip)
|
|
END_EVENT_TABLE()
|
|
|
|
#if !defined(__WXMSW__) || defined(__WXUNIVERSAL__)
|
|
/*
|
|
* wxTreeCtrl has to be a real class or we have problems with
|
|
* the run-time information.
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxTreeCtrl, wxGenericTreeCtrl)
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// construction/destruction
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void wxGenericTreeCtrl::Init()
|
|
{
|
|
m_current =
|
|
m_key_current =
|
|
m_anchor =
|
|
m_select_me = (wxGenericTreeItem *) NULL;
|
|
m_hasFocus = false;
|
|
m_dirty = false;
|
|
|
|
m_lineHeight = 10;
|
|
m_indent = 15;
|
|
m_spacing = 18;
|
|
|
|
m_hilightBrush = new wxBrush
|
|
(
|
|
wxSystemSettings::GetColour
|
|
(
|
|
wxSYS_COLOUR_HIGHLIGHT
|
|
),
|
|
wxSOLID
|
|
);
|
|
|
|
m_hilightUnfocusedBrush = new wxBrush
|
|
(
|
|
wxSystemSettings::GetColour
|
|
(
|
|
wxSYS_COLOUR_BTNSHADOW
|
|
),
|
|
wxSOLID
|
|
);
|
|
|
|
m_imageListButtons = NULL;
|
|
m_ownsImageListButtons = false;
|
|
|
|
m_dragCount = 0;
|
|
m_isDragging = false;
|
|
m_dropTarget = m_oldSelection = NULL;
|
|
m_underMouse = NULL;
|
|
m_textCtrl = NULL;
|
|
|
|
m_renameTimer = NULL;
|
|
m_freezeCount = 0;
|
|
|
|
m_findTimer = NULL;
|
|
|
|
m_dropEffectAboveItem = false;
|
|
|
|
m_lastOnSame = false;
|
|
|
|
#ifdef __WXMAC_CARBON__
|
|
m_normalFont.MacCreateThemeFont( kThemeViewsFont ) ;
|
|
#else
|
|
m_normalFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
|
|
#endif
|
|
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 )
|
|
{
|
|
#ifdef __WXMAC__
|
|
int major,minor;
|
|
wxGetOsVersion( &major, &minor );
|
|
|
|
style &= ~wxTR_LINES_AT_ROOT;
|
|
style |= wxTR_NO_LINES;
|
|
if (major < 10)
|
|
style |= wxTR_ROW_LINES;
|
|
|
|
if (style == 0 || style & wxTR_DEFAULT_STYLE)
|
|
style |= wxTR_FULL_ROW_HIGHLIGHT;
|
|
|
|
#endif // __WXMAC__
|
|
#ifdef __WXGTK20__
|
|
style |= wxTR_NO_LINES;
|
|
#endif
|
|
|
|
if ( !wxControl::Create( parent, id, pos, size,
|
|
style|wxHSCROLL|wxVSCROLL,
|
|
validator,
|
|
name ) )
|
|
return false;
|
|
|
|
// 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;
|
|
}
|
|
|
|
wxVisualAttributes attr = GetDefaultAttributes();
|
|
SetOwnForegroundColour( attr.colFg );
|
|
SetOwnBackgroundColour( attr.colBg );
|
|
if (!m_hasFont)
|
|
SetOwnFont(attr.font);
|
|
|
|
m_dottedPen = wxPen( wxT("grey"), 0, 0 );
|
|
|
|
SetInitialSize(size);
|
|
|
|
return true;
|
|
}
|
|
|
|
wxGenericTreeCtrl::~wxGenericTreeCtrl()
|
|
{
|
|
delete m_hilightBrush;
|
|
delete m_hilightUnfocusedBrush;
|
|
|
|
DeleteAllItems();
|
|
|
|
delete m_renameTimer;
|
|
delete m_findTimer;
|
|
|
|
if (m_ownsImageListButtons)
|
|
delete m_imageListButtons;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// accessors
|
|
// -----------------------------------------------------------------------------
|
|
|
|
unsigned int wxGenericTreeCtrl::GetCount() const
|
|
{
|
|
if ( !m_anchor )
|
|
{
|
|
// the tree is empty
|
|
return 0;
|
|
}
|
|
|
|
unsigned int count = m_anchor->GetChildrenCount();
|
|
if ( !HasFlag(wxTR_HIDE_ROOT) )
|
|
{
|
|
// take the root itself into account
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetIndent(unsigned int indent)
|
|
{
|
|
m_indent = (unsigned short) indent;
|
|
m_dirty = true;
|
|
}
|
|
|
|
size_t
|
|
wxGenericTreeCtrl::GetChildrenCount(const wxTreeItemId& item,
|
|
bool recursively) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), 0u, wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->GetChildrenCount(recursively);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetWindowStyle(const long styles)
|
|
{
|
|
// Do not try to expand the root node if it hasn't been created yet
|
|
if (m_anchor && !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
|
|
// -----------------------------------------------------------------------------
|
|
|
|
wxString wxGenericTreeCtrl::GetItemText(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), wxEmptyString, wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->GetText();
|
|
}
|
|
|
|
int wxGenericTreeCtrl::GetItemImage(const wxTreeItemId& item,
|
|
wxTreeItemIcon which) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), -1, wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->GetImage(which);
|
|
}
|
|
|
|
wxTreeItemData *wxGenericTreeCtrl::GetItemData(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), NULL, wxT("invalid tree item") );
|
|
|
|
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") );
|
|
|
|
wxClientDC dc(this);
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->SetText(text);
|
|
CalculateSize(pItem, dc);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemImage(const wxTreeItemId& item,
|
|
int image,
|
|
wxTreeItemIcon which)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->SetImage(image, which);
|
|
|
|
wxClientDC dc(this);
|
|
CalculateSize(pItem, dc);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemData(const wxTreeItemId& item, wxTreeItemData *data)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
if (data)
|
|
data->SetId( item );
|
|
|
|
((wxGenericTreeItem*) item.m_pItem)->SetData(data);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemHasChildren(const wxTreeItemId& item, bool has)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->SetHasPlus(has);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemBold(const wxTreeItemId& item, bool bold)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
// avoid redrawing the tree if no real change
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
if ( pItem->IsBold() != bold )
|
|
{
|
|
pItem->SetBold(bold);
|
|
|
|
// recalculate the item size as bold and non bold fonts have different
|
|
// widths
|
|
wxClientDC dc(this);
|
|
CalculateSize(pItem, dc);
|
|
|
|
RefreshLine(pItem);
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemDropHighlight(const wxTreeItemId& item,
|
|
bool highlight)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxColour fg, bg;
|
|
|
|
if (highlight)
|
|
{
|
|
bg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
|
|
fg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
|
|
}
|
|
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->Attr().SetTextColour(fg);
|
|
pItem->Attr().SetBackgroundColour(bg);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemTextColour(const wxTreeItemId& item,
|
|
const wxColour& col)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->Attr().SetTextColour(col);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemBackgroundColour(const wxTreeItemId& item,
|
|
const wxColour& col)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->Attr().SetBackgroundColour(col);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemFont(const wxTreeItemId& item, const wxFont& font)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
pItem->Attr().SetFont(font);
|
|
RefreshLine(pItem);
|
|
}
|
|
|
|
bool wxGenericTreeCtrl::SetFont( const wxFont &font )
|
|
{
|
|
wxTreeCtrlBase::SetFont(font);
|
|
|
|
m_normalFont = font ;
|
|
m_boldFont = wxFont(m_normalFont.GetPointSize(),
|
|
m_normalFont.GetFamily(),
|
|
m_normalFont.GetStyle(),
|
|
wxBOLD,
|
|
m_normalFont.GetUnderlined(),
|
|
m_normalFont.GetFaceName(),
|
|
m_normalFont.GetEncoding());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// item status inquiries
|
|
// -----------------------------------------------------------------------------
|
|
|
|
bool wxGenericTreeCtrl::IsVisible(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
|
|
|
|
// An item is only visible if it's not a descendant of a collapsed item
|
|
wxGenericTreeItem *pItem = (wxGenericTreeItem*) item.m_pItem;
|
|
wxGenericTreeItem* parent = pItem->GetParent();
|
|
while (parent)
|
|
{
|
|
if (!parent->IsExpanded())
|
|
return false;
|
|
parent = parent->GetParent();
|
|
}
|
|
|
|
int startX, startY;
|
|
GetViewStart(& startX, & startY);
|
|
|
|
wxSize clientSize = GetClientSize();
|
|
|
|
wxRect rect;
|
|
if (!GetBoundingRect(item, rect))
|
|
return false;
|
|
if (rect.GetWidth() == 0 || rect.GetHeight() == 0)
|
|
return false;
|
|
if (rect.GetBottom() < 0 || rect.GetTop() > clientSize.y)
|
|
return false;
|
|
if (rect.GetRight() < 0 || rect.GetLeft() > clientSize.x)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericTreeCtrl::ItemHasChildren(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
|
|
|
|
// 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
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->IsExpanded();
|
|
}
|
|
|
|
bool wxGenericTreeCtrl::IsSelected(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->IsSelected();
|
|
}
|
|
|
|
bool wxGenericTreeCtrl::IsBold(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->IsBold();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// navigation
|
|
// -----------------------------------------------------------------------------
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::GetItemParent(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
|
|
|
|
return ((wxGenericTreeItem*) item.m_pItem)->GetParent();
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::GetFirstChild(const wxTreeItemId& item,
|
|
wxTreeItemIdValue& cookie) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
|
|
|
|
cookie = 0;
|
|
return GetNextChild(item, cookie);
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::GetNextChild(const wxTreeItemId& item,
|
|
wxTreeItemIdValue& cookie) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
|
|
|
|
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") );
|
|
|
|
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") );
|
|
|
|
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?
|
|
|
|
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") );
|
|
|
|
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?
|
|
|
|
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") );
|
|
|
|
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 = GetItemParent(p);
|
|
} while (p.IsOk() && !toFind.IsOk());
|
|
return toFind;
|
|
}
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::GetFirstVisibleItem() const
|
|
{
|
|
wxTreeItemId id = GetRootItem();
|
|
if (!id.IsOk())
|
|
return id;
|
|
|
|
do
|
|
{
|
|
if (IsVisible(id))
|
|
return id;
|
|
id = GetNext(id);
|
|
} while (id.IsOk());
|
|
|
|
return wxTreeItemId();
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::GetNextVisible(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
|
|
|
|
wxTreeItemId id = item;
|
|
if (id.IsOk())
|
|
{
|
|
while (id = GetNext(id), id.IsOk())
|
|
{
|
|
if (IsVisible(id))
|
|
return id;
|
|
}
|
|
}
|
|
return wxTreeItemId();
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), wxTreeItemId(), wxT("invalid tree item") );
|
|
|
|
wxFAIL_MSG(wxT("not implemented"));
|
|
|
|
return wxTreeItemId();
|
|
}
|
|
|
|
// called by wxTextTreeCtrl when it marks itself for deletion
|
|
void wxGenericTreeCtrl::ResetTextControl()
|
|
{
|
|
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);
|
|
}
|
|
|
|
// 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.IsOk() && id != idParent && !GetItemText(id).Lower().StartsWith(prefix) )
|
|
{
|
|
id = GetNext(id);
|
|
}
|
|
// If we haven't found the item, id.IsOk() will be false, as per
|
|
// documentation
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// operations
|
|
// -----------------------------------------------------------------------------
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::DoInsertItem(const wxTreeItemId& parentId,
|
|
size_t previous,
|
|
const wxString& text,
|
|
int image,
|
|
int selImage,
|
|
wxTreeItemData *data)
|
|
{
|
|
wxGenericTreeItem *parent = (wxGenericTreeItem*) parentId.m_pItem;
|
|
if ( !parent )
|
|
{
|
|
// should we give a warning here?
|
|
return AddRoot(text, image, selImage, data);
|
|
}
|
|
|
|
m_dirty = true; // do this first so stuff below doesn't cause flicker
|
|
|
|
wxGenericTreeItem *item =
|
|
new wxGenericTreeItem( parent, text, image, selImage, data );
|
|
|
|
if ( data != NULL )
|
|
{
|
|
data->m_pItem = item;
|
|
}
|
|
|
|
parent->Insert( item, previous == (size_t)-1 ? parent->GetChildren().size()
|
|
: previous );
|
|
|
|
InvalidateBestSize();
|
|
return item;
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::AddRoot(const wxString& text,
|
|
int image,
|
|
int selImage,
|
|
wxTreeItemData *data)
|
|
{
|
|
wxCHECK_MSG( !m_anchor, wxTreeItemId(), wxT("tree can have only one root") );
|
|
|
|
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 = 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))
|
|
{
|
|
m_current = m_key_current = m_anchor;
|
|
m_current->SetHilight( true );
|
|
}
|
|
|
|
InvalidateBestSize();
|
|
return m_anchor;
|
|
}
|
|
|
|
wxTreeItemId wxGenericTreeCtrl::DoInsertAfter(const wxTreeItemId& parentId,
|
|
const wxTreeItemId& idPrevious,
|
|
const wxString& text,
|
|
int image, int selImage,
|
|
wxTreeItemData *data)
|
|
{
|
|
wxGenericTreeItem *parent = (wxGenericTreeItem*) parentId.m_pItem;
|
|
if ( !parent )
|
|
{
|
|
// should we give a warning here?
|
|
return AddRoot(text, image, selImage, data);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
void wxGenericTreeCtrl::SendDeleteEvent(wxGenericTreeItem *item)
|
|
{
|
|
wxTreeEvent event(wxEVT_COMMAND_TREE_DELETE_ITEM, this, item);
|
|
ProcessEvent( event );
|
|
}
|
|
|
|
// Don't leave edit or selection on a child which is about to disappear
|
|
void wxGenericTreeCtrl::ChildrenClosing(wxGenericTreeItem* item)
|
|
{
|
|
if (m_textCtrl != NULL && item != m_textCtrl->item() && IsDescendantOf(item, m_textCtrl->item())) {
|
|
m_textCtrl->StopEditing();
|
|
}
|
|
if (item != m_key_current && IsDescendantOf(item, m_key_current)) {
|
|
m_key_current = NULL;
|
|
}
|
|
if (IsDescendantOf(item, m_select_me)) {
|
|
m_select_me = item;
|
|
}
|
|
if (item != m_current && IsDescendantOf(item, m_current)) {
|
|
m_current->SetHilight( false );
|
|
m_current = NULL;
|
|
m_select_me = item;
|
|
}
|
|
}
|
|
|
|
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;
|
|
ChildrenClosing(item);
|
|
item->DeleteChildren(this);
|
|
InvalidateBestSize();
|
|
}
|
|
|
|
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;
|
|
|
|
if (m_textCtrl != NULL && IsDescendantOf(item, m_textCtrl->item()))
|
|
{
|
|
// can't delete the item being edited, cancel editing it first
|
|
m_textCtrl->StopEditing();
|
|
}
|
|
|
|
wxGenericTreeItem *parent = item->GetParent();
|
|
|
|
// don't keep stale pointers around!
|
|
if ( IsDescendantOf(item, m_key_current) )
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
// 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
|
|
}
|
|
else // deleting the root
|
|
{
|
|
// 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);
|
|
|
|
if (item == m_select_me)
|
|
m_select_me = NULL;
|
|
|
|
delete item;
|
|
|
|
InvalidateBestSize();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::DeleteAllItems()
|
|
{
|
|
if ( m_anchor )
|
|
{
|
|
Delete(m_anchor);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if ( item->IsExpanded() )
|
|
return;
|
|
|
|
wxTreeEvent event(wxEVT_COMMAND_TREE_ITEM_EXPANDING, this, item);
|
|
|
|
if ( ProcessEvent( event ) && !event.IsAllowed() )
|
|
{
|
|
// cancelled by program
|
|
return;
|
|
}
|
|
|
|
item->Expand();
|
|
CalculatePositions();
|
|
|
|
RefreshSubtree(item);
|
|
|
|
event.SetEventType(wxEVT_COMMAND_TREE_ITEM_EXPANDED);
|
|
ProcessEvent( event );
|
|
}
|
|
|
|
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, this, item);
|
|
if ( ProcessEvent( event ) && !event.IsAllowed() )
|
|
{
|
|
// cancelled by program
|
|
return;
|
|
}
|
|
|
|
ChildrenClosing(item);
|
|
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();
|
|
|
|
RefreshSubtree(item);
|
|
|
|
event.SetEventType(wxEVT_COMMAND_TREE_ITEM_COLLAPSED);
|
|
ProcessEvent( event );
|
|
}
|
|
|
|
void wxGenericTreeCtrl::CollapseAndReset(const wxTreeItemId& item)
|
|
{
|
|
Collapse(item);
|
|
DeleteChildren(item);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::Toggle(const wxTreeItemId& itemId)
|
|
{
|
|
wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
|
|
|
|
if (item->IsExpanded())
|
|
Collapse(itemId);
|
|
else
|
|
Expand(itemId);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::Unselect()
|
|
{
|
|
if (m_current)
|
|
{
|
|
m_current->SetHilight( false );
|
|
RefreshLine( m_current );
|
|
|
|
m_current = NULL;
|
|
m_select_me = NULL;
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::UnselectAllChildren(wxGenericTreeItem *item)
|
|
{
|
|
if (item->IsSelected())
|
|
{
|
|
item->SetHilight(false);
|
|
RefreshLine(item);
|
|
}
|
|
|
|
if (item->HasChildren())
|
|
{
|
|
wxArrayGenericTreeItems& children = item->GetChildren();
|
|
size_t count = children.Count();
|
|
for ( size_t n = 0; n < count; ++n )
|
|
{
|
|
UnselectAllChildren(children[n]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::UnselectAll()
|
|
{
|
|
wxTreeItemId rootItem = GetRootItem();
|
|
|
|
// the tree might not have the root item at all
|
|
if ( rootItem )
|
|
{
|
|
UnselectAllChildren((wxGenericTreeItem*) rootItem.m_pItem);
|
|
}
|
|
}
|
|
|
|
// Recursive function !
|
|
// To stop we must have crt_item<last_item
|
|
// Algorithm :
|
|
// Tag all next children, when no more children,
|
|
// Move to parent (not to tag)
|
|
// Keep going... if we found last_item, we stop.
|
|
bool wxGenericTreeCtrl::TagNextChildren(wxGenericTreeItem *crt_item, wxGenericTreeItem *last_item, bool select)
|
|
{
|
|
wxGenericTreeItem *parent = crt_item->GetParent();
|
|
|
|
if (parent == NULL) // This is root item
|
|
return TagAllChildrenUntilLast(crt_item, last_item, select);
|
|
|
|
wxArrayGenericTreeItems& children = parent->GetChildren();
|
|
int index = children.Index(crt_item);
|
|
wxASSERT( index != wxNOT_FOUND ); // I'm not a child of my parent?
|
|
|
|
size_t count = children.Count();
|
|
for (size_t n=(size_t)(index+1); n<count; ++n)
|
|
{
|
|
if (TagAllChildrenUntilLast(children[n], last_item, select)) return true;
|
|
}
|
|
|
|
return TagNextChildren(parent, last_item, select);
|
|
}
|
|
|
|
bool wxGenericTreeCtrl::TagAllChildrenUntilLast(wxGenericTreeItem *crt_item, wxGenericTreeItem *last_item, bool select)
|
|
{
|
|
crt_item->SetHilight(select);
|
|
RefreshLine(crt_item);
|
|
|
|
if (crt_item==last_item)
|
|
return true;
|
|
|
|
if (crt_item->HasChildren())
|
|
{
|
|
wxArrayGenericTreeItems& children = crt_item->GetChildren();
|
|
size_t count = children.Count();
|
|
for ( size_t n = 0; n < count; ++n )
|
|
{
|
|
if (TagAllChildrenUntilLast(children[n], last_item, select))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SelectItemRange(wxGenericTreeItem *item1, wxGenericTreeItem *item2)
|
|
{
|
|
m_select_me = NULL;
|
|
|
|
// item2 is not necessary after item1
|
|
// choice first' and 'last' between item1 and item2
|
|
wxGenericTreeItem *first= (item1->GetY()<item2->GetY()) ? item1 : item2;
|
|
wxGenericTreeItem *last = (item1->GetY()<item2->GetY()) ? item2 : item1;
|
|
|
|
bool select = m_current->IsSelected();
|
|
|
|
if ( TagAllChildrenUntilLast(first,last,select) )
|
|
return;
|
|
|
|
TagNextChildren(first,last,select);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::DoSelectItem(const wxTreeItemId& itemId,
|
|
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;
|
|
|
|
//wxCHECK_RET( ( (!unselect_others) && is_single),
|
|
// wxT("this is a single selection tree") );
|
|
|
|
// to keep going anyhow !!!
|
|
if (is_single)
|
|
{
|
|
if (item->IsSelected())
|
|
return; // nothing to do
|
|
unselect_others = true;
|
|
extended_select = false;
|
|
}
|
|
else if ( unselect_others && item->IsSelected() )
|
|
{
|
|
// selection change if there is more than one item currently selected
|
|
wxArrayTreeItemIds selected_items;
|
|
if ( GetSelections(selected_items) == 1 )
|
|
return;
|
|
}
|
|
|
|
wxTreeEvent event(wxEVT_COMMAND_TREE_SEL_CHANGING, this, item);
|
|
event.m_itemOld = m_current;
|
|
// TODO : Here we don't send any selection mode yet !
|
|
|
|
if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
|
|
return;
|
|
|
|
wxTreeItemId parent = GetItemParent( itemId );
|
|
while (parent.IsOk())
|
|
{
|
|
if (!IsExpanded(parent))
|
|
Expand( parent );
|
|
|
|
parent = GetItemParent( parent );
|
|
}
|
|
|
|
// ctrl press
|
|
if (unselect_others)
|
|
{
|
|
if (is_single) Unselect(); // to speed up thing
|
|
else UnselectAll();
|
|
}
|
|
|
|
// shift press
|
|
if (extended_select)
|
|
{
|
|
if ( !m_current )
|
|
{
|
|
m_current = m_key_current = (wxGenericTreeItem*) GetRootItem().m_pItem;
|
|
}
|
|
|
|
// don't change the mark (m_current)
|
|
SelectItemRange(m_current, item);
|
|
}
|
|
else
|
|
{
|
|
bool select = true; // the default
|
|
|
|
// Check if we need to toggle hilight (ctrl mode)
|
|
if (!unselect_others)
|
|
select=!item->IsSelected();
|
|
|
|
m_current = m_key_current = item;
|
|
m_current->SetHilight(select);
|
|
RefreshLine( m_current );
|
|
}
|
|
|
|
// This can cause idle processing to select the root
|
|
// if no item is selected, so it must be after the
|
|
// selection is set
|
|
EnsureVisible( itemId );
|
|
|
|
event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGED);
|
|
GetEventHandler()->ProcessEvent( event );
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SelectItem(const wxTreeItemId& itemId, bool select)
|
|
{
|
|
if ( select )
|
|
{
|
|
DoSelectItem(itemId, !HasFlag(wxTR_MULTIPLE));
|
|
}
|
|
else // deselect
|
|
{
|
|
wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
|
|
wxCHECK_RET( item, wxT("SelectItem(): invalid tree item") );
|
|
|
|
wxTreeEvent event(wxEVT_COMMAND_TREE_SEL_CHANGING, this, item);
|
|
if ( GetEventHandler()->ProcessEvent( event ) && !event.IsAllowed() )
|
|
return;
|
|
|
|
item->SetHilight(false);
|
|
RefreshLine(item);
|
|
|
|
event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGED);
|
|
GetEventHandler()->ProcessEvent( event );
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::FillArray(wxGenericTreeItem *item,
|
|
wxArrayTreeItemIds &array) const
|
|
{
|
|
if ( item->IsSelected() )
|
|
array.Add(wxTreeItemId(item));
|
|
|
|
if ( item->HasChildren() )
|
|
{
|
|
wxArrayGenericTreeItems& children = item->GetChildren();
|
|
size_t count = children.GetCount();
|
|
for ( size_t n = 0; n < count; ++n )
|
|
FillArray(children[n], array);
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
return array.Count();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::EnsureVisible(const wxTreeItemId& item)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
|
|
|
|
if (!item.IsOk()) return;
|
|
|
|
wxGenericTreeItem *gitem = (wxGenericTreeItem*) item.m_pItem;
|
|
|
|
// first expand all parent branches
|
|
wxGenericTreeItem *parent = gitem->GetParent();
|
|
|
|
if ( HasFlag(wxTR_HIDE_ROOT) )
|
|
{
|
|
while ( parent && parent != m_anchor )
|
|
{
|
|
Expand(parent);
|
|
parent = parent->GetParent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ( parent )
|
|
{
|
|
Expand(parent);
|
|
parent = parent->GetParent();
|
|
}
|
|
}
|
|
|
|
//if (parent) CalculatePositions();
|
|
|
|
ScrollTo(item);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::ScrollTo(const wxTreeItemId &item)
|
|
{
|
|
if (!item.IsOk()) 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)
|
|
#if defined( __WXMSW__ ) || defined(__WXMAC__)
|
|
Update();
|
|
#else
|
|
DoDirtyProcessing();
|
|
#endif
|
|
wxGenericTreeItem *gitem = (wxGenericTreeItem*) item.m_pItem;
|
|
|
|
// now scroll to the item
|
|
int item_y = gitem->GetY();
|
|
|
|
int start_x = 0;
|
|
int start_y = 0;
|
|
GetViewStart( &start_x, &start_y );
|
|
start_y *= PIXELS_PER_UNIT;
|
|
|
|
int client_h = 0;
|
|
int client_w = 0;
|
|
GetClientSize( &client_w, &client_h );
|
|
|
|
if (item_y < start_y+3)
|
|
{
|
|
// going down
|
|
int x = 0;
|
|
int 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
|
|
int x_pos = GetScrollPos( wxHORIZONTAL );
|
|
// Item should appear at top
|
|
SetScrollbars( PIXELS_PER_UNIT, PIXELS_PER_UNIT, x/PIXELS_PER_UNIT, y/PIXELS_PER_UNIT, x_pos, item_y/PIXELS_PER_UNIT );
|
|
}
|
|
else if (item_y+GetLineHeight(gitem) > start_y+client_h)
|
|
{
|
|
// going up
|
|
int x = 0;
|
|
int 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
|
|
item_y += PIXELS_PER_UNIT+2;
|
|
int x_pos = GetScrollPos( wxHORIZONTAL );
|
|
// Item should appear at bottom
|
|
SetScrollbars( PIXELS_PER_UNIT, PIXELS_PER_UNIT, x/PIXELS_PER_UNIT, y/PIXELS_PER_UNIT, x_pos, (item_y+GetLineHeight(gitem)-client_h)/PIXELS_PER_UNIT );
|
|
}
|
|
}
|
|
|
|
// FIXME: tree sorting functions are not reentrant and not MT-safe!
|
|
static wxGenericTreeCtrl *s_treeBeingSorted = NULL;
|
|
|
|
static int LINKAGEMODE tree_ctrl_compare_func(wxGenericTreeItem **item1,
|
|
wxGenericTreeItem **item2)
|
|
{
|
|
wxCHECK_MSG( s_treeBeingSorted, 0, wxT("bug in wxGenericTreeCtrl::SortChildren()") );
|
|
|
|
return s_treeBeingSorted->OnCompareItems(*item1, *item2);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SortChildren(const wxTreeItemId& itemId)
|
|
{
|
|
wxCHECK_RET( itemId.IsOk(), wxT("invalid tree item") );
|
|
|
|
wxGenericTreeItem *item = (wxGenericTreeItem*) itemId.m_pItem;
|
|
|
|
wxCHECK_RET( !s_treeBeingSorted,
|
|
wxT("wxGenericTreeCtrl::SortChildren is not reentrant") );
|
|
|
|
wxArrayGenericTreeItems& children = item->GetChildren();
|
|
if ( children.Count() > 1 )
|
|
{
|
|
m_dirty = true;
|
|
|
|
s_treeBeingSorted = this;
|
|
children.Sort(tree_ctrl_compare_func);
|
|
s_treeBeingSorted = NULL;
|
|
}
|
|
//else: don't make the tree dirty as nothing changed
|
|
}
|
|
|
|
void wxGenericTreeCtrl::CalculateLineHeight()
|
|
{
|
|
wxClientDC dc(this);
|
|
m_lineHeight = (int)(dc.GetCharHeight() + 4);
|
|
|
|
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)
|
|
{
|
|
// 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 < 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;
|
|
m_imageListState = 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::AssignButtonsImageList(wxImageList *imageList)
|
|
{
|
|
SetButtonsImageList(imageList);
|
|
m_ownsImageListButtons = true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// helpers
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void wxGenericTreeCtrl::AdjustMyScrollbars()
|
|
{
|
|
if (m_anchor)
|
|
{
|
|
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
|
|
int x_pos = GetScrollPos( wxHORIZONTAL );
|
|
int y_pos = GetScrollPos( wxVERTICAL );
|
|
SetScrollbars( PIXELS_PER_UNIT, PIXELS_PER_UNIT, x/PIXELS_PER_UNIT, y/PIXELS_PER_UNIT, x_pos, y_pos );
|
|
}
|
|
else
|
|
{
|
|
SetScrollbars( 0, 0, 0, 0 );
|
|
}
|
|
}
|
|
|
|
int wxGenericTreeCtrl::GetLineHeight(wxGenericTreeItem *item) const
|
|
{
|
|
if (GetWindowStyleFlag() & wxTR_HAS_VARIABLE_ROW_HEIGHT)
|
|
return item->GetHeight();
|
|
else
|
|
return m_lineHeight;
|
|
}
|
|
|
|
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, text_h = 0;
|
|
dc.GetTextExtent( item->GetText(), &text_w, &text_h );
|
|
|
|
int image_h = 0, image_w = 0;
|
|
int image = item->GetCurrentImage();
|
|
if ( image != NO_IMAGE )
|
|
{
|
|
if ( m_imageListNormal )
|
|
{
|
|
m_imageListNormal->GetSize( image, image_w, image_h );
|
|
image_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
|
|
}
|
|
else
|
|
{
|
|
image = NO_IMAGE;
|
|
}
|
|
}
|
|
|
|
int total_h = GetLineHeight(item);
|
|
bool drawItemBackground = false;
|
|
|
|
if ( item->IsSelected() )
|
|
{
|
|
dc.SetBrush(*(m_hasFocus ? m_hilightBrush : m_hilightUnfocusedBrush));
|
|
drawItemBackground = true;
|
|
}
|
|
else
|
|
{
|
|
wxColour colBg;
|
|
if ( attr && attr->HasBackgroundColour() )
|
|
{
|
|
drawItemBackground = true;
|
|
colBg = attr->GetBackgroundColour();
|
|
}
|
|
else
|
|
{
|
|
colBg = GetBackgroundColour();
|
|
}
|
|
dc.SetBrush(wxBrush(colBg, wxSOLID));
|
|
}
|
|
|
|
int offset = HasFlag(wxTR_ROW_LINES) ? 1 : 0;
|
|
|
|
if ( HasFlag(wxTR_FULL_ROW_HIGHLIGHT) )
|
|
{
|
|
int x = 0, w = 0, h = 0;
|
|
GetVirtualSize(&w, &h);
|
|
wxRect rect( x, item->GetY()+offset, w, total_h-offset);
|
|
#if !defined(__WXGTK20__) && !defined(__WXMAC__)
|
|
dc.DrawRectangle(rect);
|
|
#else
|
|
if (!item->IsSelected())
|
|
{
|
|
dc.DrawRectangle(rect);
|
|
}
|
|
else
|
|
{
|
|
int flags = wxCONTROL_SELECTED;
|
|
if (m_hasFocus
|
|
#ifdef __WXMAC__
|
|
&& IsControlActive( (ControlRef)GetHandle() )
|
|
#endif
|
|
)
|
|
flags |= wxCONTROL_FOCUSED;
|
|
if ((item == m_current) && (m_hasFocus))
|
|
flags |= wxCONTROL_CURRENT;
|
|
wxRendererNative::Get().DrawItemSelectionRect( this, dc, rect, flags );
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
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.
|
|
wxRect rect( item->GetX() + image_w - 2, item->GetY()+offset,
|
|
item->GetWidth() - image_w + 2, total_h-offset );
|
|
#if !defined(__WXGTK20__) && !defined(__WXMAC__)
|
|
dc.DrawRectangle( rect );
|
|
#else
|
|
rect.x -= 1;
|
|
rect.width += 2;
|
|
|
|
int flags = wxCONTROL_SELECTED;
|
|
if (m_hasFocus)
|
|
flags |= wxCONTROL_FOCUSED;
|
|
if ((item == m_current) && (m_hasFocus))
|
|
flags |= wxCONTROL_CURRENT;
|
|
wxRendererNative::Get().DrawItemSelectionRect( this, dc, rect, flags );
|
|
#endif
|
|
}
|
|
// On GTK+ 2, drawing a 'normal' background is wrong for themes that
|
|
// don't allow backgrounds to be customized. Not drawing the background,
|
|
// except for custom item backgrounds, works for both kinds of theme.
|
|
else if (drawItemBackground)
|
|
{
|
|
wxRect rect( item->GetX()-2, item->GetY()+offset,
|
|
item->GetWidth()+2, total_h-offset );
|
|
#if !defined(__WXGTK20__) && !defined(__WXMAC__)
|
|
dc.DrawRectangle( rect );
|
|
#else
|
|
if ( attr && attr->HasBackgroundColour() )
|
|
{
|
|
dc.DrawRectangle( rect );
|
|
}
|
|
else
|
|
{
|
|
rect.x -= 1;
|
|
rect.width += 2;
|
|
|
|
int flags = wxCONTROL_SELECTED;
|
|
if (m_hasFocus)
|
|
flags |= wxCONTROL_FOCUSED;
|
|
if ((item == m_current) && (m_hasFocus))
|
|
flags |= wxCONTROL_CURRENT;
|
|
wxRendererNative::Get().DrawItemSelectionRect( this, dc, rect, flags );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if ( image != NO_IMAGE )
|
|
{
|
|
dc.SetClippingRegion( item->GetX(), item->GetY(), image_w-2, total_h );
|
|
m_imageListNormal->Draw( image, dc,
|
|
item->GetX(),
|
|
item->GetY() +((total_h > image_h)?((total_h-image_h)/2):0),
|
|
wxIMAGELIST_DRAW_TRANSPARENT );
|
|
dc.DestroyClippingRegion();
|
|
}
|
|
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
int extraH = (total_h > text_h) ? (total_h - text_h)/2 : 0;
|
|
dc.DrawText( item->GetText(),
|
|
(wxCoord)(image_w + item->GetX()),
|
|
(wxCoord)(item->GetY() + extraH));
|
|
|
|
// restore normal font
|
|
dc.SetFont( m_normalFont );
|
|
}
|
|
|
|
// 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*m_indent;
|
|
if (!HasFlag(wxTR_HIDE_ROOT))
|
|
{
|
|
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)
|
|
{
|
|
int n = 0, oldY;
|
|
do {
|
|
oldY = y;
|
|
PaintLevel(children[n], dc, 1, y);
|
|
} while (++n < count);
|
|
|
|
if (!HasFlag(wxTR_NO_LINES) && HasFlag(wxTR_LINES_AT_ROOT) && count > 0)
|
|
{
|
|
// draw line down to last child
|
|
origY += GetLineHeight(children[0])>>1;
|
|
oldY += GetLineHeight(children[n-1])>>1;
|
|
dc.DrawLine(3, origY, 3, oldY);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
item->SetX(x+m_spacing);
|
|
item->SetY(y);
|
|
|
|
int h = GetLineHeight(item);
|
|
int y_top = y;
|
|
int y_mid = y_top + (h>>1);
|
|
y += h;
|
|
|
|
int exposed_x = dc.LogicalToDeviceX(0);
|
|
int exposed_y = dc.LogicalToDeviceY(y_top);
|
|
|
|
if (IsExposed(exposed_x, exposed_y, 10000, h)) // 10000 = very much
|
|
{
|
|
const 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()
|
|
#ifdef __WXMAC__
|
|
// On wxMac, if the tree doesn't have the focus we draw an empty
|
|
// rectangle, so we want to make sure that the text is visible
|
|
// against the normal background, not the highlightbackground, so
|
|
// don't use the highlight text colour unless we have the focus.
|
|
&& m_hasFocus && IsControlActive( (ControlRef)GetHandle() )
|
|
#endif
|
|
)
|
|
{
|
|
#ifdef __WXMAC__
|
|
colText = *wxWHITE;
|
|
#else
|
|
colText = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
wxTreeItemAttr *attr = item->GetAttributes();
|
|
if (attr && attr->HasTextColour())
|
|
colText = attr->GetTextColour();
|
|
else
|
|
colText = GetForegroundColour();
|
|
}
|
|
|
|
// prepare to draw
|
|
dc.SetTextForeground(colText);
|
|
dc.SetPen(*pen);
|
|
|
|
// draw
|
|
PaintItem(item, dc);
|
|
|
|
if (HasFlag(wxTR_ROW_LINES))
|
|
{
|
|
// 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);
|
|
|
|
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);
|
|
}
|
|
|
|
// should the item show a button?
|
|
if ( item->HasPlus() && HasButtons() )
|
|
{
|
|
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;
|
|
|
|
int flag = 0;
|
|
if (item->IsExpanded())
|
|
flag |= wxCONTROL_EXPANDED;
|
|
if (item == m_underMouse)
|
|
flag |= wxCONTROL_CURRENT;
|
|
|
|
wxRendererNative::Get().DrawTreeItemButton
|
|
(
|
|
this,
|
|
dc,
|
|
wxRect(x - wImage/2,
|
|
y_mid - hImage/2,
|
|
wImage, hImage),
|
|
flag
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (item->IsExpanded())
|
|
{
|
|
wxArrayGenericTreeItems& children = item->GetChildren();
|
|
int count = children.Count();
|
|
if (count > 0)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::DrawDropEffect(wxGenericTreeItem *item)
|
|
{
|
|
if ( item )
|
|
{
|
|
if ( item->HasPlus() )
|
|
{
|
|
// it's a folder, indicate it by a border
|
|
DrawBorder(item);
|
|
}
|
|
else
|
|
{
|
|
// draw a line under the drop target because the item will be
|
|
// dropped there
|
|
DrawLine(item, !m_dropEffectAboveItem );
|
|
}
|
|
|
|
SetCursor(wxCURSOR_BULLSEYE);
|
|
}
|
|
else
|
|
{
|
|
// can't drop here
|
|
SetCursor(wxCURSOR_NO_ENTRY);
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::DrawBorder(const wxTreeItemId &item)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), _T("invalid item in wxGenericTreeCtrl::DrawLine") );
|
|
|
|
wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
|
|
|
|
wxClientDC dc(this);
|
|
PrepareDC( dc );
|
|
dc.SetLogicalFunction(wxINVERT);
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
|
|
int w = i->GetWidth() + 2;
|
|
int h = GetLineHeight(i) + 2;
|
|
|
|
dc.DrawRectangle( i->GetX() - 1, i->GetY() - 1, w, h);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::DrawLine(const wxTreeItemId &item, bool below)
|
|
{
|
|
wxCHECK_RET( item.IsOk(), _T("invalid item in wxGenericTreeCtrl::DrawLine") );
|
|
|
|
wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
|
|
|
|
wxClientDC dc(this);
|
|
PrepareDC( dc );
|
|
dc.SetLogicalFunction(wxINVERT);
|
|
|
|
int x = i->GetX(),
|
|
y = i->GetY();
|
|
if ( below )
|
|
{
|
|
y += GetLineHeight(i) - 1;
|
|
}
|
|
|
|
dc.DrawLine( x, y, x + i->GetWidth(), y);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// wxWidgets callbacks
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void wxGenericTreeCtrl::OnSize( wxSizeEvent &event )
|
|
{
|
|
#ifdef __WXGTK__
|
|
if (HasFlag( wxTR_FULL_ROW_HIGHLIGHT) && m_current)
|
|
RefreshLine( m_current );
|
|
#endif
|
|
|
|
event.Skip(true);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnPaint( wxPaintEvent &WXUNUSED(event) )
|
|
{
|
|
wxPaintDC dc(this);
|
|
PrepareDC( dc );
|
|
|
|
if ( !m_anchor)
|
|
return;
|
|
|
|
dc.SetFont( m_normalFont );
|
|
dc.SetPen( m_dottedPen );
|
|
|
|
// this is now done dynamically
|
|
//if(GetImageList() == NULL)
|
|
// m_lineHeight = (int)(dc.GetCharHeight() + 4);
|
|
|
|
int y = 2;
|
|
PaintLevel( m_anchor, dc, 0, y );
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnSetFocus( wxFocusEvent &event )
|
|
{
|
|
m_hasFocus = true;
|
|
|
|
RefreshSelected();
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnKillFocus( wxFocusEvent &event )
|
|
{
|
|
m_hasFocus = false;
|
|
|
|
RefreshSelected();
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnChar( wxKeyEvent &event )
|
|
{
|
|
wxTreeEvent te( wxEVT_COMMAND_TREE_KEY_DOWN, this);
|
|
te.m_evtKey = event;
|
|
if ( GetEventHandler()->ProcessEvent( te ) )
|
|
{
|
|
// intercepted by the user code
|
|
return;
|
|
}
|
|
|
|
if ( (m_current == 0) || (m_key_current == 0) )
|
|
{
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
// how should the selection work for this event?
|
|
bool is_multiple, extended_select, unselect_others;
|
|
EventFlagsToSelType(GetWindowStyleFlag(),
|
|
event.ShiftDown(),
|
|
event.CmdDown(),
|
|
is_multiple, extended_select, unselect_others);
|
|
|
|
if (GetLayoutDirection() == wxLayout_RightToLeft)
|
|
{
|
|
if (event.GetKeyCode() == WXK_RIGHT)
|
|
event.m_keyCode = WXK_LEFT;
|
|
else if (event.GetKeyCode() == WXK_LEFT)
|
|
event.m_keyCode = WXK_RIGHT;
|
|
}
|
|
|
|
// + : Expand
|
|
// - : Collaspe
|
|
// * : Expand all/Collapse all
|
|
// ' ' | return : activate
|
|
// up : go up (not last children!)
|
|
// down : go down
|
|
// left : go to parent
|
|
// right : open if parent and go next
|
|
// home : go to root
|
|
// end : go to last item without opening parents
|
|
// alnum : start or continue searching for the item with this prefix
|
|
int keyCode = event.GetKeyCode();
|
|
switch ( keyCode )
|
|
{
|
|
case '+':
|
|
case WXK_ADD:
|
|
if (m_current->HasPlus() && !IsExpanded(m_current))
|
|
{
|
|
Expand(m_current);
|
|
}
|
|
break;
|
|
|
|
case '*':
|
|
case WXK_MULTIPLY:
|
|
if ( !IsExpanded(m_current) )
|
|
{
|
|
// expand all
|
|
ExpandAllChildren(m_current);
|
|
break;
|
|
}
|
|
//else: fall through to Collapse() it
|
|
|
|
case '-':
|
|
case WXK_SUBTRACT:
|
|
if (IsExpanded(m_current))
|
|
{
|
|
Collapse(m_current);
|
|
}
|
|
break;
|
|
|
|
case WXK_MENU:
|
|
{
|
|
// Use the item's bounding rectangle to determine position for the event
|
|
wxRect ItemRect;
|
|
GetBoundingRect(m_current, ItemRect, true);
|
|
|
|
wxTreeEvent eventMenu(wxEVT_COMMAND_TREE_ITEM_MENU, this, m_current);
|
|
// Use the left edge, vertical middle
|
|
eventMenu.m_pointDrag = wxPoint(ItemRect.GetX(),
|
|
ItemRect.GetY() + ItemRect.GetHeight() / 2);
|
|
GetEventHandler()->ProcessEvent( eventMenu );
|
|
}
|
|
break;
|
|
|
|
case ' ':
|
|
case WXK_RETURN:
|
|
if ( !event.HasModifiers() )
|
|
{
|
|
wxTreeEvent eventAct(wxEVT_COMMAND_TREE_ITEM_ACTIVATED, this, m_current);
|
|
GetEventHandler()->ProcessEvent( eventAct );
|
|
}
|
|
|
|
// 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
|
|
case WXK_UP:
|
|
{
|
|
wxTreeItemId prev = GetPrevSibling( m_key_current );
|
|
if (!prev)
|
|
{
|
|
prev = GetItemParent( m_key_current );
|
|
if ((prev == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT))
|
|
{
|
|
break; // don't go to root if it is hidden
|
|
}
|
|
if (prev)
|
|
{
|
|
wxTreeItemIdValue cookie;
|
|
wxTreeItemId current = m_key_current;
|
|
// 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
|
|
DoSelectItem( prev, unselect_others, extended_select );
|
|
m_key_current= (wxGenericTreeItem*) prev.m_pItem;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (prev)
|
|
{
|
|
while ( IsExpanded(prev) && HasChildren(prev) )
|
|
{
|
|
wxTreeItemId child = GetLastChild(prev);
|
|
if ( child )
|
|
{
|
|
prev = child;
|
|
}
|
|
}
|
|
|
|
DoSelectItem( prev, unselect_others, extended_select );
|
|
m_key_current=(wxGenericTreeItem*) prev.m_pItem;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// left arrow goes to the parent
|
|
case WXK_LEFT:
|
|
{
|
|
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)
|
|
{
|
|
DoSelectItem( 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
|
|
Expand(m_current);
|
|
// fall through
|
|
|
|
case WXK_DOWN:
|
|
{
|
|
if (IsExpanded(m_key_current) && HasChildren(m_key_current))
|
|
{
|
|
wxTreeItemIdValue cookie;
|
|
wxTreeItemId child = GetFirstChild( m_key_current, cookie );
|
|
DoSelectItem( child, unselect_others, extended_select );
|
|
m_key_current=(wxGenericTreeItem*) child.m_pItem;
|
|
}
|
|
else
|
|
{
|
|
wxTreeItemId next = GetNextSibling( m_key_current );
|
|
if (!next)
|
|
{
|
|
wxTreeItemId current = m_key_current;
|
|
while (current.IsOk() && !next)
|
|
{
|
|
current = GetItemParent( current );
|
|
if (current) next = GetNextSibling( current );
|
|
}
|
|
}
|
|
if (next)
|
|
{
|
|
DoSelectItem( next, unselect_others, extended_select );
|
|
m_key_current=(wxGenericTreeItem*) next.m_pItem;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
// <End> selects the last visible tree item
|
|
case WXK_END:
|
|
{
|
|
wxTreeItemId last = GetRootItem();
|
|
|
|
while ( last.IsOk() && IsExpanded(last) )
|
|
{
|
|
wxTreeItemId lastChild = GetLastChild(last);
|
|
|
|
// it may happen if the item was expanded but then all of
|
|
// its children have been deleted - so IsExpanded() returned
|
|
// true, but GetLastChild() returned invalid item
|
|
if ( !lastChild )
|
|
break;
|
|
|
|
last = lastChild;
|
|
}
|
|
|
|
if ( last.IsOk() )
|
|
{
|
|
DoSelectItem( last, unselect_others, extended_select );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// <Home> selects the root item
|
|
case WXK_HOME:
|
|
{
|
|
wxTreeItemId prev = GetRootItem();
|
|
if (!prev)
|
|
break;
|
|
|
|
if ( HasFlag(wxTR_HIDE_ROOT) )
|
|
{
|
|
wxTreeItemIdValue cookie;
|
|
prev = GetFirstChild(prev, cookie);
|
|
if (!prev)
|
|
break;
|
|
}
|
|
|
|
DoSelectItem( prev, unselect_others, extended_select );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// 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
|
|
wxChar ch = (wxChar)keyCode;
|
|
|
|
wxTreeItemId id = FindItem(m_current, m_findPrefix + 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::DoTreeHitTest(const wxPoint& point, int& flags) const
|
|
{
|
|
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 (flags) return wxTreeItemId();
|
|
|
|
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 textOnly) const
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), false, _T("invalid item in wxGenericTreeCtrl::GetBoundingRect") );
|
|
|
|
wxGenericTreeItem *i = (wxGenericTreeItem*) item.m_pItem;
|
|
|
|
if ( textOnly )
|
|
{
|
|
rect.x = i->GetX();
|
|
rect.width = i->GetWidth();
|
|
|
|
if ( m_imageListNormal )
|
|
{
|
|
int image_w, image_h;
|
|
m_imageListNormal->GetSize( 0, image_w, image_h );
|
|
rect.width += image_w + MARGIN_BETWEEN_IMAGE_AND_TEXT;
|
|
}
|
|
}
|
|
else // the entire line
|
|
{
|
|
rect.x = 0;
|
|
rect.width = GetClientSize().x;
|
|
}
|
|
|
|
rect.y = i->GetY();
|
|
rect.height = GetLineHeight(i);
|
|
|
|
// we have to return the logical coordinates, not physical ones
|
|
rect.SetTopLeft(CalcScrolledPosition(rect.GetTopLeft()));
|
|
|
|
return true;
|
|
}
|
|
|
|
wxTextCtrl *wxGenericTreeCtrl::EditLabel(const wxTreeItemId& item,
|
|
wxClassInfo * WXUNUSED(textCtrlClass))
|
|
{
|
|
wxCHECK_MSG( item.IsOk(), NULL, _T("can't edit an invalid item") );
|
|
|
|
wxGenericTreeItem *itemEdit = (wxGenericTreeItem *)item.m_pItem;
|
|
|
|
wxTreeEvent te(wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT, this, itemEdit);
|
|
if ( GetEventHandler()->ProcessEvent( te ) && !te.IsAllowed() )
|
|
{
|
|
// vetoed by user
|
|
return NULL;
|
|
}
|
|
|
|
// 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 )
|
|
#if defined( __WXMSW__ ) || defined(__WXMAC__)
|
|
Update();
|
|
#else
|
|
DoDirtyProcessing();
|
|
#endif
|
|
|
|
// TODO: use textCtrlClass here to create the control of correct class
|
|
m_textCtrl = new wxTreeTextCtrl(this, itemEdit);
|
|
|
|
m_textCtrl->SetFocus();
|
|
|
|
return m_textCtrl;
|
|
}
|
|
|
|
// 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::EndEditLabel(const wxTreeItemId& WXUNUSED(item),
|
|
bool discardChanges)
|
|
{
|
|
wxCHECK_RET( m_textCtrl, _T("not editing label") );
|
|
|
|
m_textCtrl->EndEdit(discardChanges);
|
|
}
|
|
|
|
bool wxGenericTreeCtrl::OnRenameAccept(wxGenericTreeItem *item,
|
|
const wxString& value)
|
|
{
|
|
wxTreeEvent le(wxEVT_COMMAND_TREE_END_LABEL_EDIT, this, item);
|
|
le.m_label = value;
|
|
le.m_editCancelled = false;
|
|
|
|
return !GetEventHandler()->ProcessEvent( le ) || le.IsAllowed();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnRenameCancelled(wxGenericTreeItem *item)
|
|
{
|
|
// let owner know that the edit was cancelled
|
|
wxTreeEvent le(wxEVT_COMMAND_TREE_END_LABEL_EDIT, this, item);
|
|
le.m_label = wxEmptyString;
|
|
le.m_editCancelled = true;
|
|
|
|
GetEventHandler()->ProcessEvent( le );
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnRenameTimer()
|
|
{
|
|
EditLabel( m_current );
|
|
}
|
|
|
|
void wxGenericTreeCtrl::OnMouse( wxMouseEvent &event )
|
|
{
|
|
if ( !m_anchor )return;
|
|
|
|
wxPoint pt = CalcUnscrolledPosition(event.GetPosition());
|
|
|
|
// Is the mouse over a tree item button?
|
|
int flags = 0;
|
|
wxGenericTreeItem *thisItem = m_anchor->HitTest(pt, this, flags, 0);
|
|
wxGenericTreeItem *underMouse = thisItem;
|
|
#if wxUSE_TOOLTIPS
|
|
bool underMouseChanged = (underMouse != m_underMouse) ;
|
|
#endif // wxUSE_TOOLTIPS
|
|
|
|
if ((underMouse) &&
|
|
(flags & wxTREE_HITTEST_ONITEMBUTTON) &&
|
|
(!event.LeftIsDown()) &&
|
|
(!m_isDragging) &&
|
|
(!m_renameTimer || !m_renameTimer->IsRunning()))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
underMouse = NULL;
|
|
}
|
|
|
|
if (underMouse != m_underMouse)
|
|
{
|
|
if (m_underMouse)
|
|
{
|
|
// unhighlight old item
|
|
wxGenericTreeItem *tmp = m_underMouse;
|
|
m_underMouse = NULL;
|
|
RefreshLine( tmp );
|
|
}
|
|
|
|
m_underMouse = underMouse;
|
|
if (m_underMouse)
|
|
RefreshLine( m_underMouse );
|
|
}
|
|
|
|
#if wxUSE_TOOLTIPS
|
|
// Determines what item we are hovering over and need a tooltip for
|
|
wxTreeItemId hoverItem = thisItem;
|
|
|
|
// We do not want a tooltip if we are dragging, or if the rename timer is running
|
|
if (underMouseChanged && hoverItem.IsOk() && !m_isDragging && (!m_renameTimer || !m_renameTimer->IsRunning()))
|
|
{
|
|
// Ask the tree control what tooltip (if any) should be shown
|
|
wxTreeEvent hevent(wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, this, hoverItem);
|
|
|
|
if ( GetEventHandler()->ProcessEvent(hevent) && hevent.IsAllowed() )
|
|
{
|
|
SetToolTip(hevent.m_label);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// we process left mouse up event (enables in-place edit), middle/right down
|
|
// (pass to the user code), left dbl click (activate item) and
|
|
// dragging/moving events for items drag-and-drop
|
|
if ( !(event.LeftDown() ||
|
|
event.LeftUp() ||
|
|
event.MiddleDown() ||
|
|
event.RightDown() ||
|
|
event.LeftDClick() ||
|
|
event.Dragging() ||
|
|
((event.Moving() || event.RightUp()) && m_isDragging)) )
|
|
{
|
|
event.Skip();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
flags = 0;
|
|
wxGenericTreeItem *item = m_anchor->HitTest(pt, this, flags, 0);
|
|
|
|
if ( event.Dragging() && !m_isDragging )
|
|
{
|
|
if (m_dragCount == 0)
|
|
m_dragStart = pt;
|
|
|
|
m_dragCount++;
|
|
|
|
if (m_dragCount != 3)
|
|
{
|
|
// wait until user drags a bit further...
|
|
return;
|
|
}
|
|
|
|
wxEventType command = event.RightIsDown()
|
|
? wxEVT_COMMAND_TREE_BEGIN_RDRAG
|
|
: wxEVT_COMMAND_TREE_BEGIN_DRAG;
|
|
|
|
wxTreeEvent nevent(command, this, m_current);
|
|
nevent.SetPoint(CalcScrolledPosition(pt));
|
|
|
|
// by default the dragging is not supported, the user code must
|
|
// explicitly allow the event for it to take place
|
|
nevent.Veto();
|
|
|
|
if ( GetEventHandler()->ProcessEvent(nevent) && nevent.IsAllowed() )
|
|
{
|
|
// we're going to drag this item
|
|
m_isDragging = true;
|
|
|
|
// remember the old cursor because we will change it while
|
|
// dragging
|
|
m_oldCursor = m_cursor;
|
|
|
|
// in a single selection control, hide the selection temporarily
|
|
if ( !(GetWindowStyleFlag() & wxTR_MULTIPLE) )
|
|
{
|
|
m_oldSelection = (wxGenericTreeItem*) GetSelection().m_pItem;
|
|
|
|
if ( m_oldSelection )
|
|
{
|
|
m_oldSelection->SetHilight(false);
|
|
RefreshLine(m_oldSelection);
|
|
}
|
|
}
|
|
|
|
CaptureMouse();
|
|
}
|
|
}
|
|
else if ( event.Dragging() )
|
|
{
|
|
if ( item != m_dropTarget )
|
|
{
|
|
// unhighlight the previous drop target
|
|
DrawDropEffect(m_dropTarget);
|
|
|
|
m_dropTarget = item;
|
|
|
|
// highlight the current drop target if any
|
|
DrawDropEffect(m_dropTarget);
|
|
|
|
#if defined(__WXMSW__) || defined(__WXMAC__) || defined(__WXGTK20__)
|
|
Update();
|
|
#else
|
|
wxYieldIfNeeded();
|
|
#endif
|
|
}
|
|
}
|
|
else if ( (event.LeftUp() || event.RightUp()) && m_isDragging )
|
|
{
|
|
ReleaseMouse();
|
|
|
|
// 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 eventEndDrag(wxEVT_COMMAND_TREE_END_DRAG, this, item);
|
|
|
|
eventEndDrag.m_pointDrag = CalcScrolledPosition(pt);
|
|
|
|
(void)GetEventHandler()->ProcessEvent(eventEndDrag);
|
|
|
|
m_isDragging = false;
|
|
m_dropTarget = (wxGenericTreeItem *)NULL;
|
|
|
|
SetCursor(m_oldCursor);
|
|
|
|
#if defined( __WXMSW__ ) || defined(__WXMAC__)
|
|
Update();
|
|
#else
|
|
wxYieldIfNeeded();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// If we got to this point, we are not dragging or moving the mouse.
|
|
// Because the code in carbon/toplevel.cpp will only set focus to the tree
|
|
// if we skip for EVT_LEFT_DOWN, we MUST skip this event here for focus to work.
|
|
// We skip even if we didn't hit an item because we still should
|
|
// restore focus to the tree control even if we didn't exactly hit an item.
|
|
if ( event.LeftDown() )
|
|
{
|
|
event.Skip();
|
|
}
|
|
|
|
// here we process only the messages which happen on tree items
|
|
|
|
m_dragCount = 0;
|
|
|
|
if (item == NULL) return; /* we hit the blank area */
|
|
|
|
if ( event.RightDown() )
|
|
{
|
|
// If the item is already selected, do not update the selection.
|
|
// Multi-selections should not be cleared if a selected item is clicked.
|
|
if (!IsSelected(item))
|
|
{
|
|
DoSelectItem(item, true, false);
|
|
}
|
|
|
|
wxTreeEvent nevent(wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK, this, item);
|
|
nevent.m_pointDrag = CalcScrolledPosition(pt);
|
|
event.Skip(!GetEventHandler()->ProcessEvent(nevent));
|
|
|
|
// Consistent with MSW (for now), send the ITEM_MENU *after*
|
|
// the RIGHT_CLICK event. TODO: This behavior may change.
|
|
wxTreeEvent nevent2(wxEVT_COMMAND_TREE_ITEM_MENU, this, item);
|
|
nevent2.m_pointDrag = CalcScrolledPosition(pt);
|
|
GetEventHandler()->ProcessEvent(nevent2);
|
|
}
|
|
else if ( event.MiddleDown() )
|
|
{
|
|
wxTreeEvent nevent(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLICK, this, item);
|
|
nevent.m_pointDrag = CalcScrolledPosition(pt);
|
|
event.Skip(!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.CmdDown() &&
|
|
!event.ShiftDown())
|
|
{
|
|
DoSelectItem(item, true, false);
|
|
}
|
|
}
|
|
|
|
if ( m_lastOnSame )
|
|
{
|
|
if ( (item == m_current) &&
|
|
(flags & wxTREE_HITTEST_ONITEMLABEL) &&
|
|
HasFlag(wxTR_EDIT_LABELS) )
|
|
{
|
|
if ( m_renameTimer )
|
|
{
|
|
if ( m_renameTimer->IsRunning() )
|
|
m_renameTimer->Stop();
|
|
}
|
|
else
|
|
{
|
|
m_renameTimer = new wxTreeRenameTimer( this );
|
|
}
|
|
|
|
m_renameTimer->Start( wxTreeRenameTimer::DELAY, true );
|
|
}
|
|
|
|
m_lastOnSame = false;
|
|
}
|
|
}
|
|
else // !RightDown() && !MiddleDown() && !LeftUp() ==> LeftDown() || LeftDClick()
|
|
{
|
|
if ( event.LeftDown() )
|
|
{
|
|
m_lastOnSame = item == m_current;
|
|
}
|
|
|
|
if ( flags & wxTREE_HITTEST_ONITEMBUTTON )
|
|
{
|
|
// only toggle the item for a single click, double click on
|
|
// the button doesn't do anything (it toggles the item twice)
|
|
if ( event.LeftDown() )
|
|
{
|
|
Toggle( item );
|
|
}
|
|
|
|
// don't select the item if the button was clicked
|
|
return;
|
|
}
|
|
|
|
|
|
// 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.
|
|
// but if Cmd is down, toggle selection of the clicked item
|
|
if (!IsSelected(item) || event.CmdDown())
|
|
{
|
|
// how should the selection work for this event?
|
|
bool is_multiple, extended_select, unselect_others;
|
|
EventFlagsToSelType(GetWindowStyleFlag(),
|
|
event.ShiftDown(),
|
|
event.CmdDown(),
|
|
is_multiple, extended_select, unselect_others);
|
|
|
|
DoSelectItem(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
|
|
if ( m_renameTimer )
|
|
m_renameTimer->Stop();
|
|
|
|
m_lastOnSame = false;
|
|
|
|
// send activate event first
|
|
wxTreeEvent nevent(wxEVT_COMMAND_TREE_ITEM_ACTIVATED, this, item);
|
|
nevent.m_pointDrag = CalcScrolledPosition(pt);
|
|
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::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,
|
|
// actually redraw the tree when everything is over
|
|
if (m_dirty)
|
|
DoDirtyProcessing();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::CalculateSize( wxGenericTreeItem *item, wxDC &dc )
|
|
{
|
|
wxCoord text_w = 0;
|
|
wxCoord text_h = 0;
|
|
|
|
wxTreeItemAttr *attr = item->GetAttributes();
|
|
if ( attr && attr->HasFont() )
|
|
dc.SetFont(attr->GetFont());
|
|
else if ( item->IsBold() )
|
|
dc.SetFont(m_boldFont);
|
|
else
|
|
dc.SetFont(m_normalFont);
|
|
|
|
dc.GetTextExtent( item->GetText(), &text_w, &text_h );
|
|
text_h+=2;
|
|
|
|
// restore normal font
|
|
dc.SetFont( m_normalFont );
|
|
|
|
int image_h = 0;
|
|
int image_w = 0;
|
|
int image = item->GetCurrentImage();
|
|
if ( image != NO_IMAGE )
|
|
{
|
|
if ( m_imageListNormal )
|
|
{
|
|
m_imageListNormal->GetSize( image, image_w, image_h );
|
|
image_w += MARGIN_BETWEEN_IMAGE_AND_TEXT;
|
|
}
|
|
}
|
|
|
|
int total_h = (image_h > text_h) ? image_h : text_h;
|
|
|
|
if (total_h < 30)
|
|
total_h += 2; // at least 2 pixels
|
|
else
|
|
total_h += total_h/10; // otherwise 10% extra spacing
|
|
|
|
item->SetHeight(total_h);
|
|
if (total_h>m_lineHeight)
|
|
m_lineHeight=total_h;
|
|
|
|
item->SetWidth(image_w+text_w+2);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// for developper : y is now the top of the level
|
|
// not the middle of it !
|
|
void wxGenericTreeCtrl::CalculateLevel( wxGenericTreeItem *item, wxDC &dc, int level, int &y )
|
|
{
|
|
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( x+m_spacing );
|
|
item->SetY( y );
|
|
y += GetLineHeight(item);
|
|
|
|
if ( !item->IsExpanded() )
|
|
{
|
|
// 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, y ); // recurse
|
|
}
|
|
|
|
void wxGenericTreeCtrl::CalculatePositions()
|
|
{
|
|
if ( !m_anchor ) return;
|
|
|
|
wxClientDC dc(this);
|
|
PrepareDC( dc );
|
|
|
|
dc.SetFont( m_normalFont );
|
|
|
|
dc.SetPen( m_dottedPen );
|
|
//if(GetImageList() == NULL)
|
|
// m_lineHeight = (int)(dc.GetCharHeight() + 4);
|
|
|
|
int y = 2;
|
|
CalculateLevel( m_anchor, dc, 0, y ); // start recursion
|
|
}
|
|
|
|
void wxGenericTreeCtrl::Refresh(bool eraseBackground, const wxRect *rect)
|
|
{
|
|
if ( !m_freezeCount )
|
|
wxTreeCtrlBase::Refresh(eraseBackground, rect);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::RefreshSubtree(wxGenericTreeItem *item)
|
|
{
|
|
if (m_dirty || m_freezeCount)
|
|
return;
|
|
|
|
wxSize client = GetClientSize();
|
|
|
|
wxRect rect;
|
|
CalcScrolledPosition(0, item->GetY(), NULL, &rect.y);
|
|
rect.width = client.x;
|
|
rect.height = client.y;
|
|
|
|
Refresh(true, &rect);
|
|
|
|
AdjustMyScrollbars();
|
|
}
|
|
|
|
void wxGenericTreeCtrl::RefreshLine( wxGenericTreeItem *item )
|
|
{
|
|
if (m_dirty || m_freezeCount)
|
|
return;
|
|
|
|
wxRect rect;
|
|
CalcScrolledPosition(0, item->GetY(), NULL, &rect.y);
|
|
rect.width = GetClientSize().x;
|
|
rect.height = GetLineHeight(item); //dc.GetCharHeight() + 6;
|
|
|
|
Refresh(true, &rect);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::RefreshSelected()
|
|
{
|
|
if (m_freezeCount)
|
|
return;
|
|
|
|
// 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 (m_freezeCount)
|
|
return;
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
void wxGenericTreeCtrl::Freeze()
|
|
{
|
|
m_freezeCount++;
|
|
}
|
|
|
|
void wxGenericTreeCtrl::Thaw()
|
|
{
|
|
wxCHECK_RET( m_freezeCount > 0, _T("thawing unfrozen tree control?") );
|
|
|
|
if ( --m_freezeCount == 0 )
|
|
{
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
// Process the tooltip event, to speed up event processing.
|
|
// Doesn't actually get a tooltip.
|
|
void wxGenericTreeCtrl::OnGetToolTip( wxTreeEvent &event )
|
|
{
|
|
event.Veto();
|
|
}
|
|
|
|
|
|
// NOTE: If using the wxListBox visual attributes works everywhere then this can
|
|
// be removed, as well as the #else case below.
|
|
#define _USE_VISATTR 0
|
|
|
|
//static
|
|
wxVisualAttributes
|
|
#if _USE_VISATTR
|
|
wxGenericTreeCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
|
|
#else
|
|
wxGenericTreeCtrl::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
|
|
#endif
|
|
{
|
|
#if _USE_VISATTR
|
|
// Use the same color scheme as wxListBox
|
|
return wxListBox::GetClassDefaultAttributes(variant);
|
|
#else
|
|
wxVisualAttributes attr;
|
|
attr.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
|
attr.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX);
|
|
attr.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|
return attr;
|
|
#endif
|
|
}
|
|
|
|
#if WXWIN_COMPATIBILITY_2_4
|
|
|
|
int wxGenericTreeCtrl::GetItemSelectedImage(const wxTreeItemId& item) const
|
|
{
|
|
return GetItemImage(item, wxTreeItemIcon_Selected);
|
|
}
|
|
|
|
void wxGenericTreeCtrl::SetItemSelectedImage(const wxTreeItemId& item, int image)
|
|
{
|
|
SetItemImage(item, image, wxTreeItemIcon_Selected);
|
|
}
|
|
|
|
#endif // WXWIN_COMPATIBILITY_2_4
|
|
|
|
void wxGenericTreeCtrl::DoDirtyProcessing()
|
|
{
|
|
if (m_freezeCount)
|
|
return;
|
|
|
|
m_dirty = false;
|
|
|
|
CalculatePositions();
|
|
Refresh();
|
|
AdjustMyScrollbars();
|
|
}
|
|
|
|
wxSize wxGenericTreeCtrl::DoGetBestSize() const
|
|
{
|
|
// make sure all positions are calculated as normally this only done during
|
|
// idle time but we need them for base class DoGetBestSize() to return the
|
|
// correct result
|
|
wxConstCast(this, wxGenericTreeCtrl)->CalculatePositions();
|
|
|
|
wxSize size = wxTreeCtrlBase::DoGetBestSize();
|
|
|
|
// there seems to be an implicit extra border around the items, although
|
|
// I'm not really sure where does it come from -- but without this, the
|
|
// scrollbars appear in a tree with default/best size
|
|
size.IncBy(4, 4);
|
|
|
|
// and the border has to be rounded up to a multiple of PIXELS_PER_UNIT or
|
|
// scrollbars still appear
|
|
const wxSize& borderSize = GetWindowBorderSize();
|
|
|
|
int dx = (size.x - borderSize.x) % PIXELS_PER_UNIT;
|
|
if ( dx )
|
|
size.x += PIXELS_PER_UNIT - dx;
|
|
int dy = (size.y - borderSize.y) % PIXELS_PER_UNIT;
|
|
if ( dy )
|
|
size.y += PIXELS_PER_UNIT - dy;
|
|
|
|
// we need to update the cache too as the base class cached its own value
|
|
CacheBestSize(size);
|
|
|
|
return size;
|
|
}
|
|
|
|
#endif // wxUSE_TREECTRL
|