Don't override SetFocus() to set the focus to the main window, this isn't enough as we can be given the focus by the underlying toolkit itself, without our SetFocus() being called -- this happened in wxGTK when focus was changed from the keyboard using TAB and in this case it didn't get to the main window but remaining on the wxListCtrl itself meaning that keyboard input wasn't processed at all. Use wxNavigationEnabled<> now to ensure that OnSetFocus() and related methods do the right thing instead now. Also get rid of OnSetFocus() in wxListHeaderWindow, it's better to override AcceptsFocus() to return false to avoid getting focus in the first place. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@71039 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
5329 lines
147 KiB
C++
5329 lines
147 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/generic/listctrl.cpp
|
|
// Purpose: generic implementation of wxListCtrl
|
|
// Author: Robert Roebling
|
|
// Vadim Zeitlin (virtual list control support)
|
|
// Id: $Id$
|
|
// Copyright: (c) 1998 Robert Roebling
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// TODO
|
|
//
|
|
// 1. we need to implement searching/sorting for virtual controls somehow
|
|
// 2. when changing selection the lines are refreshed twice
|
|
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_LISTCTRL
|
|
|
|
#include "wx/listctrl.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/scrolwin.h"
|
|
#include "wx/timer.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/dynarray.h"
|
|
#include "wx/dcclient.h"
|
|
#include "wx/dcscreen.h"
|
|
#include "wx/math.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/sizer.h"
|
|
#endif
|
|
|
|
#include "wx/imaglist.h"
|
|
#include "wx/renderer.h"
|
|
#include "wx/generic/private/listctrl.h"
|
|
|
|
#ifdef __WXMAC__
|
|
#include "wx/osx/private.h"
|
|
#endif
|
|
|
|
#if defined(__WXMSW__) && !defined(__WXWINCE__) && !defined(__WXUNIVERSAL__)
|
|
#define "wx/msw/wrapwin.h"
|
|
#endif
|
|
|
|
// 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
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// constants
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// // the height of the header window (FIXME: should depend on its font!)
|
|
// static const int HEADER_HEIGHT = 23;
|
|
|
|
static const int SCROLL_UNIT_X = 15;
|
|
|
|
// the spacing between the lines (in report mode)
|
|
static const int LINE_SPACING = 0;
|
|
|
|
// extra margins around the text label
|
|
#ifdef __WXGTK__
|
|
static const int EXTRA_WIDTH = 6;
|
|
#else
|
|
static const int EXTRA_WIDTH = 4;
|
|
#endif
|
|
|
|
#ifdef __WXGTK__
|
|
static const int EXTRA_HEIGHT = 6;
|
|
#else
|
|
static const int EXTRA_HEIGHT = 4;
|
|
#endif
|
|
|
|
// margin between the window and the items
|
|
static const int EXTRA_BORDER_X = 2;
|
|
static const int EXTRA_BORDER_Y = 2;
|
|
|
|
// offset for the header window
|
|
static const int HEADER_OFFSET_X = 0;
|
|
static const int HEADER_OFFSET_Y = 0;
|
|
|
|
// margin between rows of icons in [small] icon view
|
|
static const int MARGIN_BETWEEN_ROWS = 6;
|
|
|
|
// when autosizing the columns, add some slack
|
|
static const int AUTOSIZE_COL_MARGIN = 10;
|
|
|
|
// default width for the header columns
|
|
static const int WIDTH_COL_DEFAULT = 80;
|
|
|
|
// the space between the image and the text in the report mode
|
|
static const int IMAGE_MARGIN_IN_REPORT_MODE = 5;
|
|
|
|
// the space between the image and the text in the report mode in header
|
|
static const int HEADER_IMAGE_MARGIN_IN_REPORT_MODE = 2;
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// arrays/list implementations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "wx/listimpl.cpp"
|
|
WX_DEFINE_LIST(wxListItemDataList)
|
|
|
|
#include "wx/arrimpl.cpp"
|
|
WX_DEFINE_OBJARRAY(wxListLineDataArray)
|
|
|
|
#include "wx/listimpl.cpp"
|
|
WX_DEFINE_LIST(wxListHeaderDataList)
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxListItemData
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxListItemData::~wxListItemData()
|
|
{
|
|
// in the virtual list control the attributes are managed by the main
|
|
// program, so don't delete them
|
|
if ( !m_owner->IsVirtual() )
|
|
delete m_attr;
|
|
|
|
delete m_rect;
|
|
}
|
|
|
|
void wxListItemData::Init()
|
|
{
|
|
m_image = -1;
|
|
m_data = 0;
|
|
|
|
m_attr = NULL;
|
|
}
|
|
|
|
wxListItemData::wxListItemData(wxListMainWindow *owner)
|
|
{
|
|
Init();
|
|
|
|
m_owner = owner;
|
|
|
|
if ( owner->InReportView() )
|
|
m_rect = NULL;
|
|
else
|
|
m_rect = new wxRect;
|
|
}
|
|
|
|
void wxListItemData::SetItem( const wxListItem &info )
|
|
{
|
|
if ( info.m_mask & wxLIST_MASK_TEXT )
|
|
SetText(info.m_text);
|
|
if ( info.m_mask & wxLIST_MASK_IMAGE )
|
|
m_image = info.m_image;
|
|
if ( info.m_mask & wxLIST_MASK_DATA )
|
|
m_data = info.m_data;
|
|
|
|
if ( info.HasAttributes() )
|
|
{
|
|
if ( m_attr )
|
|
m_attr->AssignFrom(*info.GetAttributes());
|
|
else
|
|
m_attr = new wxListItemAttr(*info.GetAttributes());
|
|
}
|
|
|
|
if ( m_rect )
|
|
{
|
|
m_rect->x =
|
|
m_rect->y =
|
|
m_rect->height = 0;
|
|
m_rect->width = info.m_width;
|
|
}
|
|
}
|
|
|
|
void wxListItemData::SetPosition( int x, int y )
|
|
{
|
|
wxCHECK_RET( m_rect, wxT("unexpected SetPosition() call") );
|
|
|
|
m_rect->x = x;
|
|
m_rect->y = y;
|
|
}
|
|
|
|
void wxListItemData::SetSize( int width, int height )
|
|
{
|
|
wxCHECK_RET( m_rect, wxT("unexpected SetSize() call") );
|
|
|
|
if ( width != -1 )
|
|
m_rect->width = width;
|
|
if ( height != -1 )
|
|
m_rect->height = height;
|
|
}
|
|
|
|
bool wxListItemData::IsHit( int x, int y ) const
|
|
{
|
|
wxCHECK_MSG( m_rect, false, wxT("can't be called in this mode") );
|
|
|
|
return wxRect(GetX(), GetY(), GetWidth(), GetHeight()).Contains(x, y);
|
|
}
|
|
|
|
int wxListItemData::GetX() const
|
|
{
|
|
wxCHECK_MSG( m_rect, 0, wxT("can't be called in this mode") );
|
|
|
|
return m_rect->x;
|
|
}
|
|
|
|
int wxListItemData::GetY() const
|
|
{
|
|
wxCHECK_MSG( m_rect, 0, wxT("can't be called in this mode") );
|
|
|
|
return m_rect->y;
|
|
}
|
|
|
|
int wxListItemData::GetWidth() const
|
|
{
|
|
wxCHECK_MSG( m_rect, 0, wxT("can't be called in this mode") );
|
|
|
|
return m_rect->width;
|
|
}
|
|
|
|
int wxListItemData::GetHeight() const
|
|
{
|
|
wxCHECK_MSG( m_rect, 0, wxT("can't be called in this mode") );
|
|
|
|
return m_rect->height;
|
|
}
|
|
|
|
void wxListItemData::GetItem( wxListItem &info ) const
|
|
{
|
|
long mask = info.m_mask;
|
|
if ( !mask )
|
|
// by default, get everything for backwards compatibility
|
|
mask = -1;
|
|
|
|
if ( mask & wxLIST_MASK_TEXT )
|
|
info.m_text = m_text;
|
|
if ( mask & wxLIST_MASK_IMAGE )
|
|
info.m_image = m_image;
|
|
if ( mask & wxLIST_MASK_DATA )
|
|
info.m_data = m_data;
|
|
|
|
if ( m_attr )
|
|
{
|
|
if ( m_attr->HasTextColour() )
|
|
info.SetTextColour(m_attr->GetTextColour());
|
|
if ( m_attr->HasBackgroundColour() )
|
|
info.SetBackgroundColour(m_attr->GetBackgroundColour());
|
|
if ( m_attr->HasFont() )
|
|
info.SetFont(m_attr->GetFont());
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListHeaderData
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void wxListHeaderData::Init()
|
|
{
|
|
m_mask = 0;
|
|
m_image = -1;
|
|
m_format = 0;
|
|
m_width = 0;
|
|
m_xpos = 0;
|
|
m_ypos = 0;
|
|
m_height = 0;
|
|
m_state = 0;
|
|
}
|
|
|
|
wxListHeaderData::wxListHeaderData()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
wxListHeaderData::wxListHeaderData( const wxListItem &item )
|
|
{
|
|
Init();
|
|
|
|
SetItem( item );
|
|
}
|
|
|
|
void wxListHeaderData::SetItem( const wxListItem &item )
|
|
{
|
|
m_mask = item.m_mask;
|
|
|
|
if ( m_mask & wxLIST_MASK_TEXT )
|
|
m_text = item.m_text;
|
|
|
|
if ( m_mask & wxLIST_MASK_IMAGE )
|
|
m_image = item.m_image;
|
|
|
|
if ( m_mask & wxLIST_MASK_FORMAT )
|
|
m_format = item.m_format;
|
|
|
|
if ( m_mask & wxLIST_MASK_WIDTH )
|
|
SetWidth(item.m_width);
|
|
|
|
if ( m_mask & wxLIST_MASK_STATE )
|
|
SetState(item.m_state);
|
|
}
|
|
|
|
void wxListHeaderData::SetPosition( int x, int y )
|
|
{
|
|
m_xpos = x;
|
|
m_ypos = y;
|
|
}
|
|
|
|
void wxListHeaderData::SetHeight( int h )
|
|
{
|
|
m_height = h;
|
|
}
|
|
|
|
void wxListHeaderData::SetWidth( int w )
|
|
{
|
|
m_width = w < 0 ? WIDTH_COL_DEFAULT : w;
|
|
}
|
|
|
|
void wxListHeaderData::SetState( int flag )
|
|
{
|
|
m_state = flag;
|
|
}
|
|
|
|
void wxListHeaderData::SetFormat( int format )
|
|
{
|
|
m_format = format;
|
|
}
|
|
|
|
bool wxListHeaderData::HasImage() const
|
|
{
|
|
return m_image != -1;
|
|
}
|
|
|
|
bool wxListHeaderData::IsHit( int x, int y ) const
|
|
{
|
|
return ((x >= m_xpos) && (x <= m_xpos+m_width) && (y >= m_ypos) && (y <= m_ypos+m_height));
|
|
}
|
|
|
|
void wxListHeaderData::GetItem( wxListItem& item )
|
|
{
|
|
long mask = item.m_mask;
|
|
if ( !mask )
|
|
{
|
|
// by default, get everything for backwards compatibility
|
|
mask = -1;
|
|
}
|
|
|
|
if ( mask & wxLIST_MASK_STATE )
|
|
item.m_state = m_state;
|
|
if ( mask & wxLIST_MASK_TEXT )
|
|
item.m_text = m_text;
|
|
if ( mask & wxLIST_MASK_IMAGE )
|
|
item.m_image = m_image;
|
|
if ( mask & wxLIST_MASK_WIDTH )
|
|
item.m_width = m_width;
|
|
if ( mask & wxLIST_MASK_FORMAT )
|
|
item.m_format = m_format;
|
|
}
|
|
|
|
int wxListHeaderData::GetImage() const
|
|
{
|
|
return m_image;
|
|
}
|
|
|
|
int wxListHeaderData::GetWidth() const
|
|
{
|
|
return m_width;
|
|
}
|
|
|
|
int wxListHeaderData::GetFormat() const
|
|
{
|
|
return m_format;
|
|
}
|
|
|
|
int wxListHeaderData::GetState() const
|
|
{
|
|
return m_state;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListLineData
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline int wxListLineData::GetMode() const
|
|
{
|
|
return m_owner->GetListCtrl()->GetWindowStyleFlag() & wxLC_MASK_TYPE;
|
|
}
|
|
|
|
inline bool wxListLineData::InReportView() const
|
|
{
|
|
return m_owner->HasFlag(wxLC_REPORT);
|
|
}
|
|
|
|
inline bool wxListLineData::IsVirtual() const
|
|
{
|
|
return m_owner->IsVirtual();
|
|
}
|
|
|
|
wxListLineData::wxListLineData( wxListMainWindow *owner )
|
|
{
|
|
m_owner = owner;
|
|
|
|
if ( InReportView() )
|
|
m_gi = NULL;
|
|
else // !report
|
|
m_gi = new GeometryInfo;
|
|
|
|
m_highlighted = false;
|
|
|
|
InitItems( GetMode() == wxLC_REPORT ? m_owner->GetColumnCount() : 1 );
|
|
}
|
|
|
|
void wxListLineData::CalculateSize( wxDC *dc, int spacing )
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.GetFirst();
|
|
wxCHECK_RET( node, wxT("no subitems at all??") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
|
|
wxString s;
|
|
wxCoord lw, lh;
|
|
|
|
switch ( GetMode() )
|
|
{
|
|
case wxLC_ICON:
|
|
case wxLC_SMALL_ICON:
|
|
m_gi->m_rectAll.width = spacing;
|
|
|
|
s = item->GetText();
|
|
|
|
if ( s.empty() )
|
|
{
|
|
lh =
|
|
m_gi->m_rectLabel.width =
|
|
m_gi->m_rectLabel.height = 0;
|
|
}
|
|
else // has label
|
|
{
|
|
dc->GetTextExtent( s, &lw, &lh );
|
|
lw += EXTRA_WIDTH;
|
|
lh += EXTRA_HEIGHT;
|
|
|
|
m_gi->m_rectAll.height = spacing + lh;
|
|
if (lw > spacing)
|
|
m_gi->m_rectAll.width = lw;
|
|
|
|
m_gi->m_rectLabel.width = lw;
|
|
m_gi->m_rectLabel.height = lh;
|
|
}
|
|
|
|
if (item->HasImage())
|
|
{
|
|
int w, h;
|
|
m_owner->GetImageSize( item->GetImage(), w, h );
|
|
m_gi->m_rectIcon.width = w + 8;
|
|
m_gi->m_rectIcon.height = h + 8;
|
|
|
|
if ( m_gi->m_rectIcon.width > m_gi->m_rectAll.width )
|
|
m_gi->m_rectAll.width = m_gi->m_rectIcon.width;
|
|
if ( m_gi->m_rectIcon.height + lh > m_gi->m_rectAll.height - 4 )
|
|
m_gi->m_rectAll.height = m_gi->m_rectIcon.height + lh + 4;
|
|
}
|
|
|
|
if ( item->HasText() )
|
|
{
|
|
m_gi->m_rectHighlight.width = m_gi->m_rectLabel.width;
|
|
m_gi->m_rectHighlight.height = m_gi->m_rectLabel.height;
|
|
}
|
|
else // no text, highlight the icon
|
|
{
|
|
m_gi->m_rectHighlight.width = m_gi->m_rectIcon.width;
|
|
m_gi->m_rectHighlight.height = m_gi->m_rectIcon.height;
|
|
}
|
|
break;
|
|
|
|
case wxLC_LIST:
|
|
s = item->GetTextForMeasuring();
|
|
|
|
dc->GetTextExtent( s, &lw, &lh );
|
|
lw += EXTRA_WIDTH;
|
|
lh += EXTRA_HEIGHT;
|
|
|
|
m_gi->m_rectLabel.width = lw;
|
|
m_gi->m_rectLabel.height = lh;
|
|
|
|
m_gi->m_rectAll.width = lw;
|
|
m_gi->m_rectAll.height = lh;
|
|
|
|
if (item->HasImage())
|
|
{
|
|
int w, h;
|
|
m_owner->GetImageSize( item->GetImage(), w, h );
|
|
m_gi->m_rectIcon.width = w;
|
|
m_gi->m_rectIcon.height = h;
|
|
|
|
m_gi->m_rectAll.width += 4 + w;
|
|
if (h > m_gi->m_rectAll.height)
|
|
m_gi->m_rectAll.height = h;
|
|
}
|
|
|
|
m_gi->m_rectHighlight.width = m_gi->m_rectAll.width;
|
|
m_gi->m_rectHighlight.height = m_gi->m_rectAll.height;
|
|
break;
|
|
|
|
case wxLC_REPORT:
|
|
wxFAIL_MSG( wxT("unexpected call to SetSize") );
|
|
break;
|
|
|
|
default:
|
|
wxFAIL_MSG( wxT("unknown mode") );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void wxListLineData::SetPosition( int x, int y, int spacing )
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.GetFirst();
|
|
wxCHECK_RET( node, wxT("no subitems at all??") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
|
|
switch ( GetMode() )
|
|
{
|
|
case wxLC_ICON:
|
|
case wxLC_SMALL_ICON:
|
|
m_gi->m_rectAll.x = x;
|
|
m_gi->m_rectAll.y = y;
|
|
|
|
if ( item->HasImage() )
|
|
{
|
|
m_gi->m_rectIcon.x = m_gi->m_rectAll.x + 4 +
|
|
(m_gi->m_rectAll.width - m_gi->m_rectIcon.width) / 2;
|
|
m_gi->m_rectIcon.y = m_gi->m_rectAll.y + 4;
|
|
}
|
|
|
|
if ( item->HasText() )
|
|
{
|
|
if (m_gi->m_rectAll.width > spacing)
|
|
m_gi->m_rectLabel.x = m_gi->m_rectAll.x + (EXTRA_WIDTH/2);
|
|
else
|
|
m_gi->m_rectLabel.x = m_gi->m_rectAll.x + (EXTRA_WIDTH/2) + (spacing / 2) - (m_gi->m_rectLabel.width / 2);
|
|
m_gi->m_rectLabel.y = m_gi->m_rectAll.y + m_gi->m_rectAll.height + 2 - m_gi->m_rectLabel.height;
|
|
m_gi->m_rectHighlight.x = m_gi->m_rectLabel.x - 2;
|
|
m_gi->m_rectHighlight.y = m_gi->m_rectLabel.y - 2;
|
|
}
|
|
else // no text, highlight the icon
|
|
{
|
|
m_gi->m_rectHighlight.x = m_gi->m_rectIcon.x - 4;
|
|
m_gi->m_rectHighlight.y = m_gi->m_rectIcon.y - 4;
|
|
}
|
|
break;
|
|
|
|
case wxLC_LIST:
|
|
m_gi->m_rectAll.x = x;
|
|
m_gi->m_rectAll.y = y;
|
|
|
|
m_gi->m_rectHighlight.x = m_gi->m_rectAll.x;
|
|
m_gi->m_rectHighlight.y = m_gi->m_rectAll.y;
|
|
m_gi->m_rectLabel.y = m_gi->m_rectAll.y + 2;
|
|
|
|
if (item->HasImage())
|
|
{
|
|
m_gi->m_rectIcon.x = m_gi->m_rectAll.x + 2;
|
|
m_gi->m_rectIcon.y = m_gi->m_rectAll.y + 2;
|
|
m_gi->m_rectLabel.x = m_gi->m_rectAll.x + 4 + (EXTRA_WIDTH/2) + m_gi->m_rectIcon.width;
|
|
}
|
|
else
|
|
{
|
|
m_gi->m_rectLabel.x = m_gi->m_rectAll.x + (EXTRA_WIDTH/2);
|
|
}
|
|
break;
|
|
|
|
case wxLC_REPORT:
|
|
wxFAIL_MSG( wxT("unexpected call to SetPosition") );
|
|
break;
|
|
|
|
default:
|
|
wxFAIL_MSG( wxT("unknown mode") );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void wxListLineData::InitItems( int num )
|
|
{
|
|
for (int i = 0; i < num; i++)
|
|
m_items.Append( new wxListItemData(m_owner) );
|
|
}
|
|
|
|
void wxListLineData::SetItem( int index, const wxListItem &info )
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.Item( index );
|
|
wxCHECK_RET( node, wxT("invalid column index in SetItem") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
item->SetItem( info );
|
|
}
|
|
|
|
void wxListLineData::GetItem( int index, wxListItem &info )
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.Item( index );
|
|
if (node)
|
|
{
|
|
wxListItemData *item = node->GetData();
|
|
item->GetItem( info );
|
|
}
|
|
}
|
|
|
|
wxString wxListLineData::GetText(int index) const
|
|
{
|
|
wxString s;
|
|
|
|
wxListItemDataList::compatibility_iterator node = m_items.Item( index );
|
|
if (node)
|
|
{
|
|
wxListItemData *item = node->GetData();
|
|
s = item->GetText();
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
void wxListLineData::SetText( int index, const wxString& s )
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.Item( index );
|
|
if (node)
|
|
{
|
|
wxListItemData *item = node->GetData();
|
|
item->SetText( s );
|
|
}
|
|
}
|
|
|
|
void wxListLineData::SetImage( int index, int image )
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.Item( index );
|
|
wxCHECK_RET( node, wxT("invalid column index in SetImage()") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
item->SetImage(image);
|
|
}
|
|
|
|
int wxListLineData::GetImage( int index ) const
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.Item( index );
|
|
wxCHECK_MSG( node, -1, wxT("invalid column index in GetImage()") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
return item->GetImage();
|
|
}
|
|
|
|
wxListItemAttr *wxListLineData::GetAttr() const
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.GetFirst();
|
|
wxCHECK_MSG( node, NULL, wxT("invalid column index in GetAttr()") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
return item->GetAttr();
|
|
}
|
|
|
|
void wxListLineData::SetAttr(wxListItemAttr *attr)
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.GetFirst();
|
|
wxCHECK_RET( node, wxT("invalid column index in SetAttr()") );
|
|
|
|
wxListItemData *item = node->GetData();
|
|
item->SetAttr(attr);
|
|
}
|
|
|
|
void wxListLineData::ApplyAttributes(wxDC *dc,
|
|
const wxRect& rectHL,
|
|
bool highlighted,
|
|
bool current)
|
|
{
|
|
const wxListItemAttr * const attr = GetAttr();
|
|
|
|
wxWindow * const listctrl = m_owner->GetParent();
|
|
|
|
const bool hasFocus = listctrl->HasFocus()
|
|
#if defined(__WXMAC__) && !defined(__WXUNIVERSAL__) && wxOSX_USE_CARBON
|
|
&& IsControlActive( (ControlRef)listctrl->GetHandle() )
|
|
#endif
|
|
;
|
|
|
|
// fg colour
|
|
|
|
// don't use foreground colour for drawing highlighted items - this might
|
|
// make them completely invisible (and there is no way to do bit
|
|
// arithmetics on wxColour, unfortunately)
|
|
wxColour colText;
|
|
if ( highlighted )
|
|
{
|
|
#ifdef __WXMAC__
|
|
if ( hasFocus )
|
|
colText = *wxWHITE;
|
|
else
|
|
colText = *wxBLACK;
|
|
#else
|
|
if ( hasFocus )
|
|
colText = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
|
|
else
|
|
colText = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT);
|
|
#endif
|
|
}
|
|
else if ( attr && attr->HasTextColour() )
|
|
colText = attr->GetTextColour();
|
|
else
|
|
colText = listctrl->GetForegroundColour();
|
|
|
|
dc->SetTextForeground(colText);
|
|
|
|
// font
|
|
wxFont font;
|
|
if ( attr && attr->HasFont() )
|
|
font = attr->GetFont();
|
|
else
|
|
font = listctrl->GetFont();
|
|
|
|
dc->SetFont(font);
|
|
|
|
// background
|
|
if ( highlighted )
|
|
{
|
|
// Use the renderer method to ensure that the selected items use the
|
|
// native look.
|
|
int flags = wxCONTROL_SELECTED;
|
|
if ( hasFocus )
|
|
flags |= wxCONTROL_FOCUSED;
|
|
if (current)
|
|
flags |= wxCONTROL_CURRENT;
|
|
wxRendererNative::Get().
|
|
DrawItemSelectionRect( m_owner, *dc, rectHL, flags );
|
|
}
|
|
else if ( attr && attr->HasBackgroundColour() )
|
|
{
|
|
// Draw the background using the items custom background colour.
|
|
dc->SetBrush(attr->GetBackgroundColour());
|
|
dc->SetPen(*wxTRANSPARENT_PEN);
|
|
dc->DrawRectangle(rectHL);
|
|
}
|
|
|
|
// just for debugging to better see where the items are
|
|
#if 0
|
|
dc->SetPen(*wxRED_PEN);
|
|
dc->SetBrush(*wxTRANSPARENT_BRUSH);
|
|
dc->DrawRectangle( m_gi->m_rectAll );
|
|
dc->SetPen(*wxGREEN_PEN);
|
|
dc->DrawRectangle( m_gi->m_rectIcon );
|
|
#endif
|
|
}
|
|
|
|
void wxListLineData::Draw(wxDC *dc, bool current)
|
|
{
|
|
wxListItemDataList::compatibility_iterator node = m_items.GetFirst();
|
|
wxCHECK_RET( node, wxT("no subitems at all??") );
|
|
|
|
ApplyAttributes(dc, m_gi->m_rectHighlight, IsHighlighted(), current);
|
|
|
|
wxListItemData *item = node->GetData();
|
|
if (item->HasImage())
|
|
{
|
|
// centre the image inside our rectangle, this looks nicer when items
|
|
// ae aligned in a row
|
|
const wxRect& rectIcon = m_gi->m_rectIcon;
|
|
|
|
m_owner->DrawImage(item->GetImage(), dc, rectIcon.x, rectIcon.y);
|
|
}
|
|
|
|
if (item->HasText())
|
|
{
|
|
const wxRect& rectLabel = m_gi->m_rectLabel;
|
|
|
|
wxDCClipper clipper(*dc, rectLabel);
|
|
dc->DrawText(item->GetText(), rectLabel.x, rectLabel.y);
|
|
}
|
|
}
|
|
|
|
void wxListLineData::DrawInReportMode( wxDC *dc,
|
|
const wxRect& rect,
|
|
const wxRect& rectHL,
|
|
bool highlighted,
|
|
bool current )
|
|
{
|
|
// TODO: later we should support setting different attributes for
|
|
// different columns - to do it, just add "col" argument to
|
|
// GetAttr() and move these lines into the loop below
|
|
|
|
ApplyAttributes(dc, rectHL, highlighted, current);
|
|
|
|
wxCoord x = rect.x + HEADER_OFFSET_X,
|
|
yMid = rect.y + rect.height/2;
|
|
#ifdef __WXGTK__
|
|
// This probably needs to be done
|
|
// on all platforms as the icons
|
|
// otherwise nearly touch the border
|
|
x += 2;
|
|
#endif
|
|
|
|
size_t col = 0;
|
|
for ( wxListItemDataList::compatibility_iterator node = m_items.GetFirst();
|
|
node;
|
|
node = node->GetNext(), col++ )
|
|
{
|
|
wxListItemData *item = node->GetData();
|
|
|
|
int width = m_owner->GetColumnWidth(col);
|
|
int xOld = x;
|
|
x += width;
|
|
|
|
width -= 8;
|
|
const int wText = width;
|
|
wxDCClipper clipper(*dc, xOld, rect.y, wText, rect.height);
|
|
|
|
if ( item->HasImage() )
|
|
{
|
|
int ix, iy;
|
|
m_owner->GetImageSize( item->GetImage(), ix, iy );
|
|
m_owner->DrawImage( item->GetImage(), dc, xOld, yMid - iy/2 );
|
|
|
|
ix += IMAGE_MARGIN_IN_REPORT_MODE;
|
|
|
|
xOld += ix;
|
|
width -= ix;
|
|
}
|
|
|
|
if ( item->HasText() )
|
|
DrawTextFormatted(dc, item->GetText(), col, xOld, yMid, width);
|
|
}
|
|
}
|
|
|
|
void wxListLineData::DrawTextFormatted(wxDC *dc,
|
|
const wxString& textOrig,
|
|
int col,
|
|
int x,
|
|
int yMid,
|
|
int width)
|
|
{
|
|
// we don't support displaying multiple lines currently (and neither does
|
|
// wxMSW FWIW) so just merge all the lines
|
|
wxString text(textOrig);
|
|
text.Replace(wxT("\n"), wxT(" "));
|
|
|
|
wxCoord w, h;
|
|
dc->GetTextExtent(text, &w, &h);
|
|
|
|
const wxCoord y = yMid - (h + 1)/2;
|
|
|
|
wxDCClipper clipper(*dc, x, y, width, h);
|
|
|
|
// determine if the string can fit inside the current width
|
|
if (w <= width)
|
|
{
|
|
// it can, draw it using the items alignment
|
|
wxListItem item;
|
|
m_owner->GetColumn(col, item);
|
|
switch ( item.GetAlign() )
|
|
{
|
|
case wxLIST_FORMAT_LEFT:
|
|
// nothing to do
|
|
break;
|
|
|
|
case wxLIST_FORMAT_RIGHT:
|
|
x += width - w;
|
|
break;
|
|
|
|
case wxLIST_FORMAT_CENTER:
|
|
x += (width - w) / 2;
|
|
break;
|
|
|
|
default:
|
|
wxFAIL_MSG( wxT("unknown list item format") );
|
|
break;
|
|
}
|
|
|
|
dc->DrawText(text, x, y);
|
|
}
|
|
else // otherwise, truncate and add an ellipsis if possible
|
|
{
|
|
// determine the base width
|
|
wxString ellipsis(wxT("..."));
|
|
wxCoord base_w;
|
|
dc->GetTextExtent(ellipsis, &base_w, &h);
|
|
|
|
// continue until we have enough space or only one character left
|
|
wxCoord w_c, h_c;
|
|
size_t len = text.length();
|
|
wxString drawntext = text.Left(len);
|
|
while (len > 1)
|
|
{
|
|
dc->GetTextExtent(drawntext.Last(), &w_c, &h_c);
|
|
drawntext.RemoveLast();
|
|
len--;
|
|
w -= w_c;
|
|
if (w + base_w <= width)
|
|
break;
|
|
}
|
|
|
|
// if still not enough space, remove ellipsis characters
|
|
while (ellipsis.length() > 0 && w + base_w > width)
|
|
{
|
|
ellipsis = ellipsis.Left(ellipsis.length() - 1);
|
|
dc->GetTextExtent(ellipsis, &base_w, &h);
|
|
}
|
|
|
|
// now draw the text
|
|
dc->DrawText(drawntext, x, y);
|
|
dc->DrawText(ellipsis, x + w, y);
|
|
}
|
|
}
|
|
|
|
bool wxListLineData::Highlight( bool on )
|
|
{
|
|
wxCHECK_MSG( !IsVirtual(), false, wxT("unexpected call to Highlight") );
|
|
|
|
if ( on == m_highlighted )
|
|
return false;
|
|
|
|
m_highlighted = on;
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListLineData::ReverseHighlight( void )
|
|
{
|
|
Highlight(!IsHighlighted());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListHeaderWindow
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow)
|
|
EVT_PAINT (wxListHeaderWindow::OnPaint)
|
|
EVT_MOUSE_EVENTS (wxListHeaderWindow::OnMouse)
|
|
END_EVENT_TABLE()
|
|
|
|
void wxListHeaderWindow::Init()
|
|
{
|
|
m_currentCursor = NULL;
|
|
m_isDragging = false;
|
|
m_dirty = false;
|
|
m_sendSetColumnWidth = false;
|
|
}
|
|
|
|
wxListHeaderWindow::wxListHeaderWindow()
|
|
{
|
|
Init();
|
|
|
|
m_owner = NULL;
|
|
m_resizeCursor = NULL;
|
|
}
|
|
|
|
wxListHeaderWindow::wxListHeaderWindow( wxWindow *win,
|
|
wxWindowID id,
|
|
wxListMainWindow *owner,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxString &name )
|
|
: wxWindow( win, id, pos, size, style, name )
|
|
{
|
|
Init();
|
|
|
|
m_owner = owner;
|
|
m_resizeCursor = new wxCursor( wxCURSOR_SIZEWE );
|
|
|
|
#if _USE_VISATTR
|
|
wxVisualAttributes attr = wxPanel::GetClassDefaultAttributes();
|
|
SetOwnForegroundColour( attr.colFg );
|
|
SetOwnBackgroundColour( attr.colBg );
|
|
if (!m_hasFont)
|
|
SetOwnFont( attr.font );
|
|
#else
|
|
SetOwnForegroundColour( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
|
SetOwnBackgroundColour( wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
|
|
if (!m_hasFont)
|
|
SetOwnFont( wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT ));
|
|
#endif
|
|
}
|
|
|
|
wxListHeaderWindow::~wxListHeaderWindow()
|
|
{
|
|
delete m_resizeCursor;
|
|
}
|
|
|
|
#ifdef __WXUNIVERSAL__
|
|
#include "wx/univ/renderer.h"
|
|
#include "wx/univ/theme.h"
|
|
#endif
|
|
|
|
// shift the DC origin to match the position of the main window horz
|
|
// scrollbar: this allows us to always use logical coords
|
|
void wxListHeaderWindow::AdjustDC(wxDC& dc)
|
|
{
|
|
wxGenericListCtrl *parent = m_owner->GetListCtrl();
|
|
|
|
int xpix;
|
|
parent->GetScrollPixelsPerUnit( &xpix, NULL );
|
|
|
|
int view_start;
|
|
parent->GetViewStart( &view_start, NULL );
|
|
|
|
|
|
int org_x = 0;
|
|
int org_y = 0;
|
|
dc.GetDeviceOrigin( &org_x, &org_y );
|
|
|
|
// account for the horz scrollbar offset
|
|
#ifdef __WXGTK__
|
|
if (GetLayoutDirection() == wxLayout_RightToLeft)
|
|
{
|
|
// Maybe we just have to check for m_signX
|
|
// in the DC, but I leave the #ifdef __WXGTK__
|
|
// for now
|
|
dc.SetDeviceOrigin( org_x + (view_start * xpix), org_y );
|
|
}
|
|
else
|
|
#endif
|
|
dc.SetDeviceOrigin( org_x - (view_start * xpix), org_y );
|
|
}
|
|
|
|
void wxListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
|
|
{
|
|
wxGenericListCtrl *parent = m_owner->GetListCtrl();
|
|
|
|
wxPaintDC dc( this );
|
|
|
|
AdjustDC( dc );
|
|
|
|
dc.SetFont( GetFont() );
|
|
|
|
// width and height of the entire header window
|
|
int w, h;
|
|
GetClientSize( &w, &h );
|
|
parent->CalcUnscrolledPosition(w, 0, &w, NULL);
|
|
|
|
dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
|
|
dc.SetTextForeground(GetForegroundColour());
|
|
|
|
int x = HEADER_OFFSET_X;
|
|
int numColumns = m_owner->GetColumnCount();
|
|
wxListItem item;
|
|
for ( int i = 0; i < numColumns && x < w; i++ )
|
|
{
|
|
m_owner->GetColumn( i, item );
|
|
int wCol = item.m_width;
|
|
|
|
int cw = wCol;
|
|
int ch = h;
|
|
|
|
int flags = 0;
|
|
if (!m_parent->IsEnabled())
|
|
flags |= wxCONTROL_DISABLED;
|
|
|
|
// NB: The code below is not really Mac-specific, but since we are close
|
|
// to 2.8 release and I don't have time to test on other platforms, I
|
|
// defined this only for wxMac. If this behaviour is desired on
|
|
// other platforms, please go ahead and revise or remove the #ifdef.
|
|
#ifdef __WXMAC__
|
|
if ( !m_owner->IsVirtual() && (item.m_mask & wxLIST_MASK_STATE) &&
|
|
(item.m_state & wxLIST_STATE_SELECTED) )
|
|
flags |= wxCONTROL_SELECTED;
|
|
#endif
|
|
|
|
if (i == 0)
|
|
flags |= wxCONTROL_SPECIAL; // mark as first column
|
|
|
|
wxRendererNative::Get().DrawHeaderButton
|
|
(
|
|
this,
|
|
dc,
|
|
wxRect(x, HEADER_OFFSET_Y, cw, ch),
|
|
flags
|
|
);
|
|
|
|
// see if we have enough space for the column label
|
|
|
|
// for this we need the width of the text
|
|
wxCoord wLabel;
|
|
wxCoord hLabel;
|
|
dc.GetTextExtent(item.GetText(), &wLabel, &hLabel);
|
|
wLabel += 2 * EXTRA_WIDTH;
|
|
|
|
// and the width of the icon, if any
|
|
int ix = 0, iy = 0; // init them just to suppress the compiler warnings
|
|
const int image = item.m_image;
|
|
wxImageList *imageList;
|
|
if ( image != -1 )
|
|
{
|
|
imageList = m_owner->GetSmallImageList();
|
|
if ( imageList )
|
|
{
|
|
imageList->GetSize(image, ix, iy);
|
|
wLabel += ix + HEADER_IMAGE_MARGIN_IN_REPORT_MODE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
imageList = NULL;
|
|
}
|
|
|
|
// ignore alignment if there is not enough space anyhow
|
|
int xAligned;
|
|
switch ( wLabel < cw ? item.GetAlign() : wxLIST_FORMAT_LEFT )
|
|
{
|
|
default:
|
|
wxFAIL_MSG( wxT("unknown list item format") );
|
|
// fall through
|
|
|
|
case wxLIST_FORMAT_LEFT:
|
|
xAligned = x;
|
|
break;
|
|
|
|
case wxLIST_FORMAT_RIGHT:
|
|
xAligned = x + cw - wLabel;
|
|
break;
|
|
|
|
case wxLIST_FORMAT_CENTER:
|
|
xAligned = x + (cw - wLabel) / 2;
|
|
break;
|
|
}
|
|
|
|
// draw the text and image clipping them so that they
|
|
// don't overwrite the column boundary
|
|
wxDCClipper clipper(dc, x, HEADER_OFFSET_Y, cw, h);
|
|
|
|
// if we have an image, draw it on the right of the label
|
|
if ( imageList )
|
|
{
|
|
imageList->Draw
|
|
(
|
|
image,
|
|
dc,
|
|
xAligned + wLabel - ix - HEADER_IMAGE_MARGIN_IN_REPORT_MODE,
|
|
HEADER_OFFSET_Y + (h - iy)/2,
|
|
wxIMAGELIST_DRAW_TRANSPARENT
|
|
);
|
|
}
|
|
|
|
dc.DrawText( item.GetText(),
|
|
xAligned + EXTRA_WIDTH, (h - hLabel) / 2 );
|
|
|
|
x += wCol;
|
|
}
|
|
|
|
// Fill in what's missing to the right of the columns, otherwise we will
|
|
// leave an unpainted area when columns are removed (and it looks better)
|
|
if ( x < w )
|
|
{
|
|
wxRendererNative::Get().DrawHeaderButton
|
|
(
|
|
this,
|
|
dc,
|
|
wxRect(x, HEADER_OFFSET_Y, w - x, h),
|
|
wxCONTROL_DIRTY // mark as last column
|
|
);
|
|
}
|
|
}
|
|
|
|
void wxListHeaderWindow::OnInternalIdle()
|
|
{
|
|
wxWindow::OnInternalIdle();
|
|
|
|
if (m_sendSetColumnWidth)
|
|
{
|
|
m_owner->SetColumnWidth( m_colToSend, m_widthToSend );
|
|
m_sendSetColumnWidth = false;
|
|
}
|
|
}
|
|
|
|
void wxListHeaderWindow::DrawCurrent()
|
|
{
|
|
#if 1
|
|
// m_owner->SetColumnWidth( m_column, m_currentX - m_minX );
|
|
m_sendSetColumnWidth = true;
|
|
m_colToSend = m_column;
|
|
m_widthToSend = m_currentX - m_minX;
|
|
#else
|
|
int x1 = m_currentX;
|
|
int y1 = 0;
|
|
m_owner->ClientToScreen( &x1, &y1 );
|
|
|
|
int x2 = m_currentX;
|
|
int y2 = 0;
|
|
m_owner->GetClientSize( NULL, &y2 );
|
|
m_owner->ClientToScreen( &x2, &y2 );
|
|
|
|
wxScreenDC dc;
|
|
dc.SetLogicalFunction( wxINVERT );
|
|
dc.SetPen( wxPen(*wxBLACK, 2) );
|
|
dc.SetBrush( *wxTRANSPARENT_BRUSH );
|
|
|
|
AdjustDC(dc);
|
|
|
|
dc.DrawLine( x1, y1, x2, y2 );
|
|
|
|
dc.SetLogicalFunction( wxCOPY );
|
|
|
|
dc.SetPen( wxNullPen );
|
|
dc.SetBrush( wxNullBrush );
|
|
#endif
|
|
}
|
|
|
|
void wxListHeaderWindow::OnMouse( wxMouseEvent &event )
|
|
{
|
|
wxGenericListCtrl *parent = m_owner->GetListCtrl();
|
|
|
|
// we want to work with logical coords
|
|
int x;
|
|
parent->CalcUnscrolledPosition(event.GetX(), 0, &x, NULL);
|
|
int y = event.GetY();
|
|
|
|
if (m_isDragging)
|
|
{
|
|
SendListEvent(wxEVT_COMMAND_LIST_COL_DRAGGING, event.GetPosition());
|
|
|
|
// we don't draw the line beyond our window, but we allow dragging it
|
|
// there
|
|
int w = 0;
|
|
GetClientSize( &w, NULL );
|
|
parent->CalcUnscrolledPosition(w, 0, &w, NULL);
|
|
w -= 6;
|
|
|
|
// erase the line if it was drawn
|
|
if ( m_currentX < w )
|
|
DrawCurrent();
|
|
|
|
if (event.ButtonUp())
|
|
{
|
|
ReleaseMouse();
|
|
m_isDragging = false;
|
|
m_dirty = true;
|
|
m_owner->SetColumnWidth( m_column, m_currentX - m_minX );
|
|
SendListEvent(wxEVT_COMMAND_LIST_COL_END_DRAG, event.GetPosition());
|
|
}
|
|
else
|
|
{
|
|
if (x > m_minX + 7)
|
|
m_currentX = x;
|
|
else
|
|
m_currentX = m_minX + 7;
|
|
|
|
// draw in the new location
|
|
if ( m_currentX < w )
|
|
DrawCurrent();
|
|
}
|
|
}
|
|
else // not dragging
|
|
{
|
|
m_minX = 0;
|
|
bool hit_border = false;
|
|
|
|
// end of the current column
|
|
int xpos = 0;
|
|
|
|
// find the column where this event occurred
|
|
int col,
|
|
countCol = m_owner->GetColumnCount();
|
|
for (col = 0; col < countCol; col++)
|
|
{
|
|
xpos += m_owner->GetColumnWidth( col );
|
|
m_column = col;
|
|
|
|
if ( (abs(x-xpos) < 3) && (y < 22) )
|
|
{
|
|
// near the column border
|
|
hit_border = true;
|
|
break;
|
|
}
|
|
|
|
if ( x < xpos )
|
|
{
|
|
// inside the column
|
|
break;
|
|
}
|
|
|
|
m_minX = xpos;
|
|
}
|
|
|
|
if ( col == countCol )
|
|
m_column = -1;
|
|
|
|
if (event.LeftDown() || event.RightUp())
|
|
{
|
|
if (hit_border && event.LeftDown())
|
|
{
|
|
if ( SendListEvent(wxEVT_COMMAND_LIST_COL_BEGIN_DRAG,
|
|
event.GetPosition()) )
|
|
{
|
|
m_isDragging = true;
|
|
m_currentX = x;
|
|
CaptureMouse();
|
|
DrawCurrent();
|
|
}
|
|
//else: column resizing was vetoed by the user code
|
|
}
|
|
else // click on a column
|
|
{
|
|
// record the selected state of the columns
|
|
if (event.LeftDown())
|
|
{
|
|
for (int i=0; i < m_owner->GetColumnCount(); i++)
|
|
{
|
|
wxListItem colItem;
|
|
m_owner->GetColumn(i, colItem);
|
|
long state = colItem.GetState();
|
|
if (i == m_column)
|
|
colItem.SetState(state | wxLIST_STATE_SELECTED);
|
|
else
|
|
colItem.SetState(state & ~wxLIST_STATE_SELECTED);
|
|
m_owner->SetColumn(i, colItem);
|
|
}
|
|
}
|
|
|
|
SendListEvent( event.LeftDown()
|
|
? wxEVT_COMMAND_LIST_COL_CLICK
|
|
: wxEVT_COMMAND_LIST_COL_RIGHT_CLICK,
|
|
event.GetPosition());
|
|
}
|
|
}
|
|
else if (event.Moving())
|
|
{
|
|
bool setCursor;
|
|
if (hit_border)
|
|
{
|
|
setCursor = m_currentCursor == wxSTANDARD_CURSOR;
|
|
m_currentCursor = m_resizeCursor;
|
|
}
|
|
else
|
|
{
|
|
setCursor = m_currentCursor != wxSTANDARD_CURSOR;
|
|
m_currentCursor = wxSTANDARD_CURSOR;
|
|
}
|
|
|
|
if ( setCursor )
|
|
SetCursor(*m_currentCursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool wxListHeaderWindow::SendListEvent(wxEventType type, const wxPoint& pos)
|
|
{
|
|
wxWindow *parent = GetParent();
|
|
wxListEvent le( type, parent->GetId() );
|
|
le.SetEventObject( parent );
|
|
le.m_pointDrag = pos;
|
|
|
|
// the position should be relative to the parent window, not
|
|
// this one for compatibility with MSW and common sense: the
|
|
// user code doesn't know anything at all about this header
|
|
// window, so why should it get positions relative to it?
|
|
le.m_pointDrag.y -= GetSize().y;
|
|
|
|
le.m_col = m_column;
|
|
return !parent->GetEventHandler()->ProcessEvent( le ) || le.IsAllowed();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListRenameTimer (internal)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxListRenameTimer::wxListRenameTimer( wxListMainWindow *owner )
|
|
{
|
|
m_owner = owner;
|
|
}
|
|
|
|
void wxListRenameTimer::Notify()
|
|
{
|
|
m_owner->OnRenameTimer();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListTextCtrlWrapper (internal)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BEGIN_EVENT_TABLE(wxListTextCtrlWrapper, wxEvtHandler)
|
|
EVT_CHAR (wxListTextCtrlWrapper::OnChar)
|
|
EVT_KEY_UP (wxListTextCtrlWrapper::OnKeyUp)
|
|
EVT_KILL_FOCUS (wxListTextCtrlWrapper::OnKillFocus)
|
|
END_EVENT_TABLE()
|
|
|
|
wxListTextCtrlWrapper::wxListTextCtrlWrapper(wxListMainWindow *owner,
|
|
wxTextCtrl *text,
|
|
size_t itemEdit)
|
|
: m_startValue(owner->GetItemText(itemEdit)),
|
|
m_itemEdited(itemEdit)
|
|
{
|
|
m_owner = owner;
|
|
m_text = text;
|
|
m_aboutToFinish = false;
|
|
|
|
wxGenericListCtrl *parent = m_owner->GetListCtrl();
|
|
|
|
wxRect rectLabel = owner->GetLineLabelRect(itemEdit);
|
|
|
|
parent->CalcScrolledPosition(rectLabel.x, rectLabel.y,
|
|
&rectLabel.x, &rectLabel.y);
|
|
|
|
m_text->Create(owner, wxID_ANY, m_startValue,
|
|
wxPoint(rectLabel.x-4,rectLabel.y-4),
|
|
wxSize(rectLabel.width+11,rectLabel.height+8));
|
|
m_text->SetFocus();
|
|
|
|
m_text->PushEventHandler(this);
|
|
}
|
|
|
|
void wxListTextCtrlWrapper::EndEdit(EndReason reason)
|
|
{
|
|
m_aboutToFinish = true;
|
|
|
|
switch ( reason )
|
|
{
|
|
case End_Accept:
|
|
// Notify the owner about the changes
|
|
AcceptChanges();
|
|
|
|
// Even if vetoed, close the control (consistent with MSW)
|
|
Finish( true );
|
|
break;
|
|
|
|
case End_Discard:
|
|
m_owner->OnRenameCancelled(m_itemEdited);
|
|
|
|
Finish( true );
|
|
break;
|
|
|
|
case End_Destroy:
|
|
// Don't generate any notifications for the control being destroyed
|
|
// and don't set focus to it neither.
|
|
Finish(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void wxListTextCtrlWrapper::Finish( bool setfocus )
|
|
{
|
|
m_text->RemoveEventHandler(this);
|
|
m_owner->ResetTextControl( m_text );
|
|
|
|
wxPendingDelete.Append( this );
|
|
|
|
if (setfocus)
|
|
m_owner->SetFocus();
|
|
}
|
|
|
|
bool wxListTextCtrlWrapper::AcceptChanges()
|
|
{
|
|
const wxString value = m_text->GetValue();
|
|
|
|
// notice that we should always call OnRenameAccept() to generate the "end
|
|
// label editing" event, even if the user hasn't really changed anything
|
|
if ( !m_owner->OnRenameAccept(m_itemEdited, value) )
|
|
{
|
|
// vetoed by the user
|
|
return false;
|
|
}
|
|
|
|
// accepted, do rename the item (unless nothing changed)
|
|
if ( value != m_startValue )
|
|
m_owner->SetItemText(m_itemEdited, value);
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListTextCtrlWrapper::OnChar( wxKeyEvent &event )
|
|
{
|
|
if ( !CheckForEndEditKey(event) )
|
|
event.Skip();
|
|
}
|
|
|
|
bool wxListTextCtrlWrapper::CheckForEndEditKey(const wxKeyEvent& event)
|
|
{
|
|
switch ( event.m_keyCode )
|
|
{
|
|
case WXK_RETURN:
|
|
EndEdit( End_Accept );
|
|
break;
|
|
|
|
case WXK_ESCAPE:
|
|
EndEdit( End_Discard );
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListTextCtrlWrapper::OnKeyUp( wxKeyEvent &event )
|
|
{
|
|
if (m_aboutToFinish)
|
|
{
|
|
// auto-grow the textctrl:
|
|
wxSize parentSize = m_owner->GetSize();
|
|
wxPoint myPos = m_text->GetPosition();
|
|
wxSize mySize = m_text->GetSize();
|
|
int sx, sy;
|
|
m_text->GetTextExtent(m_text->GetValue() + wxT("MM"), &sx, &sy);
|
|
if (myPos.x + sx > parentSize.x)
|
|
sx = parentSize.x - myPos.x;
|
|
if (mySize.x > sx)
|
|
sx = mySize.x;
|
|
m_text->SetSize(sx, wxDefaultCoord);
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxListTextCtrlWrapper::OnKillFocus( wxFocusEvent &event )
|
|
{
|
|
if ( !m_aboutToFinish )
|
|
{
|
|
if ( !AcceptChanges() )
|
|
m_owner->OnRenameCancelled( m_itemEdited );
|
|
|
|
Finish( false );
|
|
}
|
|
|
|
// We must let the native text control handle focus
|
|
event.Skip();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListMainWindow
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BEGIN_EVENT_TABLE(wxListMainWindow, wxWindow)
|
|
EVT_PAINT (wxListMainWindow::OnPaint)
|
|
EVT_MOUSE_EVENTS (wxListMainWindow::OnMouse)
|
|
EVT_CHAR_HOOK (wxListMainWindow::OnCharHook)
|
|
EVT_CHAR (wxListMainWindow::OnChar)
|
|
EVT_KEY_DOWN (wxListMainWindow::OnKeyDown)
|
|
EVT_KEY_UP (wxListMainWindow::OnKeyUp)
|
|
EVT_SET_FOCUS (wxListMainWindow::OnSetFocus)
|
|
EVT_KILL_FOCUS (wxListMainWindow::OnKillFocus)
|
|
EVT_SCROLLWIN (wxListMainWindow::OnScroll)
|
|
EVT_CHILD_FOCUS (wxListMainWindow::OnChildFocus)
|
|
END_EVENT_TABLE()
|
|
|
|
void wxListMainWindow::Init()
|
|
{
|
|
m_dirty = true;
|
|
m_countVirt = 0;
|
|
m_lineFrom =
|
|
m_lineTo = (size_t)-1;
|
|
m_linesPerPage = 0;
|
|
|
|
m_headerWidth =
|
|
m_lineHeight = 0;
|
|
|
|
m_small_image_list = NULL;
|
|
m_normal_image_list = NULL;
|
|
|
|
m_small_spacing = 30;
|
|
m_normal_spacing = 40;
|
|
|
|
m_hasFocus = false;
|
|
m_dragCount = 0;
|
|
m_isCreated = false;
|
|
|
|
m_lastOnSame = false;
|
|
m_renameTimer = new wxListRenameTimer( this );
|
|
m_textctrlWrapper = NULL;
|
|
|
|
m_current =
|
|
m_lineLastClicked =
|
|
m_lineSelectSingleOnUp =
|
|
m_lineBeforeLastClicked = (size_t)-1;
|
|
}
|
|
|
|
wxListMainWindow::wxListMainWindow()
|
|
{
|
|
Init();
|
|
|
|
m_highlightBrush =
|
|
m_highlightUnfocusedBrush = NULL;
|
|
}
|
|
|
|
wxListMainWindow::wxListMainWindow( wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxString &name )
|
|
: wxWindow( parent, id, pos, size, style, name )
|
|
{
|
|
Init();
|
|
|
|
m_highlightBrush = new wxBrush
|
|
(
|
|
wxSystemSettings::GetColour
|
|
(
|
|
wxSYS_COLOUR_HIGHLIGHT
|
|
),
|
|
wxBRUSHSTYLE_SOLID
|
|
);
|
|
|
|
m_highlightUnfocusedBrush = new wxBrush
|
|
(
|
|
wxSystemSettings::GetColour
|
|
(
|
|
wxSYS_COLOUR_BTNSHADOW
|
|
),
|
|
wxBRUSHSTYLE_SOLID
|
|
);
|
|
|
|
wxVisualAttributes attr = wxGenericListCtrl::GetClassDefaultAttributes();
|
|
SetOwnForegroundColour( attr.colFg );
|
|
SetOwnBackgroundColour( attr.colBg );
|
|
if (!m_hasFont)
|
|
SetOwnFont( attr.font );
|
|
}
|
|
|
|
wxListMainWindow::~wxListMainWindow()
|
|
{
|
|
if ( m_textctrlWrapper )
|
|
m_textctrlWrapper->EndEdit(wxListTextCtrlWrapper::End_Destroy);
|
|
|
|
DoDeleteAllItems();
|
|
WX_CLEAR_LIST(wxListHeaderDataList, m_columns);
|
|
WX_CLEAR_ARRAY(m_aColWidths);
|
|
|
|
delete m_highlightBrush;
|
|
delete m_highlightUnfocusedBrush;
|
|
delete m_renameTimer;
|
|
}
|
|
|
|
void wxListMainWindow::SetReportView(bool inReportView)
|
|
{
|
|
const size_t count = m_lines.size();
|
|
for ( size_t n = 0; n < count; n++ )
|
|
{
|
|
m_lines[n].SetReportView(inReportView);
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::CacheLineData(size_t line)
|
|
{
|
|
wxGenericListCtrl *listctrl = GetListCtrl();
|
|
|
|
wxListLineData *ld = GetDummyLine();
|
|
|
|
size_t countCol = GetColumnCount();
|
|
for ( size_t col = 0; col < countCol; col++ )
|
|
{
|
|
ld->SetText(col, listctrl->OnGetItemText(line, col));
|
|
ld->SetImage(col, listctrl->OnGetItemColumnImage(line, col));
|
|
}
|
|
|
|
ld->SetAttr(listctrl->OnGetItemAttr(line));
|
|
}
|
|
|
|
wxListLineData *wxListMainWindow::GetDummyLine() const
|
|
{
|
|
wxASSERT_MSG( !IsEmpty(), wxT("invalid line index") );
|
|
wxASSERT_MSG( IsVirtual(), wxT("GetDummyLine() shouldn't be called") );
|
|
|
|
wxListMainWindow *self = wxConstCast(this, wxListMainWindow);
|
|
|
|
// we need to recreate the dummy line if the number of columns in the
|
|
// control changed as it would have the incorrect number of fields
|
|
// otherwise
|
|
if ( !m_lines.IsEmpty() &&
|
|
m_lines[0].m_items.GetCount() != (size_t)GetColumnCount() )
|
|
{
|
|
self->m_lines.Clear();
|
|
}
|
|
|
|
if ( m_lines.IsEmpty() )
|
|
{
|
|
wxListLineData *line = new wxListLineData(self);
|
|
self->m_lines.Add(line);
|
|
|
|
// don't waste extra memory -- there never going to be anything
|
|
// else/more in this array
|
|
self->m_lines.Shrink();
|
|
}
|
|
|
|
return &m_lines[0];
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// line geometry (report mode only)
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxCoord wxListMainWindow::GetLineHeight() const
|
|
{
|
|
// we cache the line height as calling GetTextExtent() is slow
|
|
if ( !m_lineHeight )
|
|
{
|
|
wxListMainWindow *self = wxConstCast(this, wxListMainWindow);
|
|
|
|
wxClientDC dc( self );
|
|
dc.SetFont( GetFont() );
|
|
|
|
wxCoord y;
|
|
dc.GetTextExtent(wxT("H"), NULL, &y);
|
|
|
|
if ( m_small_image_list && m_small_image_list->GetImageCount() )
|
|
{
|
|
int iw = 0, ih = 0;
|
|
m_small_image_list->GetSize(0, iw, ih);
|
|
y = wxMax(y, ih);
|
|
}
|
|
|
|
y += EXTRA_HEIGHT;
|
|
self->m_lineHeight = y + LINE_SPACING;
|
|
}
|
|
|
|
return m_lineHeight;
|
|
}
|
|
|
|
wxCoord wxListMainWindow::GetLineY(size_t line) const
|
|
{
|
|
wxASSERT_MSG( InReportView(), wxT("only works in report mode") );
|
|
|
|
return LINE_SPACING + line * GetLineHeight();
|
|
}
|
|
|
|
wxRect wxListMainWindow::GetLineRect(size_t line) const
|
|
{
|
|
if ( !InReportView() )
|
|
return GetLine(line)->m_gi->m_rectAll;
|
|
|
|
wxRect rect;
|
|
rect.x = HEADER_OFFSET_X;
|
|
rect.y = GetLineY(line);
|
|
rect.width = GetHeaderWidth();
|
|
rect.height = GetLineHeight();
|
|
|
|
return rect;
|
|
}
|
|
|
|
wxRect wxListMainWindow::GetLineLabelRect(size_t line) const
|
|
{
|
|
if ( !InReportView() )
|
|
return GetLine(line)->m_gi->m_rectLabel;
|
|
|
|
int image_x = 0;
|
|
wxListLineData *data = GetLine(line);
|
|
wxListItemDataList::compatibility_iterator node = data->m_items.GetFirst();
|
|
if (node)
|
|
{
|
|
wxListItemData *item = node->GetData();
|
|
if ( item->HasImage() )
|
|
{
|
|
int ix, iy;
|
|
GetImageSize( item->GetImage(), ix, iy );
|
|
image_x = 3 + ix + IMAGE_MARGIN_IN_REPORT_MODE;
|
|
}
|
|
}
|
|
|
|
wxRect rect;
|
|
rect.x = image_x + HEADER_OFFSET_X;
|
|
rect.y = GetLineY(line);
|
|
rect.width = GetColumnWidth(0) - image_x;
|
|
rect.height = GetLineHeight();
|
|
|
|
return rect;
|
|
}
|
|
|
|
wxRect wxListMainWindow::GetLineIconRect(size_t line) const
|
|
{
|
|
if ( !InReportView() )
|
|
return GetLine(line)->m_gi->m_rectIcon;
|
|
|
|
wxListLineData *ld = GetLine(line);
|
|
wxASSERT_MSG( ld->HasImage(), wxT("should have an image") );
|
|
|
|
wxRect rect;
|
|
rect.x = HEADER_OFFSET_X;
|
|
rect.y = GetLineY(line);
|
|
GetImageSize(ld->GetImage(), rect.width, rect.height);
|
|
|
|
return rect;
|
|
}
|
|
|
|
wxRect wxListMainWindow::GetLineHighlightRect(size_t line) const
|
|
{
|
|
return InReportView() ? GetLineRect(line)
|
|
: GetLine(line)->m_gi->m_rectHighlight;
|
|
}
|
|
|
|
long wxListMainWindow::HitTestLine(size_t line, int x, int y) const
|
|
{
|
|
wxASSERT_MSG( line < GetItemCount(), wxT("invalid line in HitTestLine") );
|
|
|
|
wxListLineData *ld = GetLine(line);
|
|
|
|
if ( ld->HasImage() && GetLineIconRect(line).Contains(x, y) )
|
|
return wxLIST_HITTEST_ONITEMICON;
|
|
|
|
// VS: Testing for "ld->HasText() || InReportView()" instead of
|
|
// "ld->HasText()" is needed to make empty lines in report view
|
|
// possible
|
|
if ( ld->HasText() || InReportView() )
|
|
{
|
|
wxRect rect = InReportView() ? GetLineRect(line)
|
|
: GetLineLabelRect(line);
|
|
|
|
if ( rect.Contains(x, y) )
|
|
return wxLIST_HITTEST_ONITEMLABEL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// highlight (selection) handling
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxListMainWindow::IsHighlighted(size_t line) const
|
|
{
|
|
if ( IsVirtual() )
|
|
{
|
|
return m_selStore.IsSelected(line);
|
|
}
|
|
else // !virtual
|
|
{
|
|
wxListLineData *ld = GetLine(line);
|
|
wxCHECK_MSG( ld, false, wxT("invalid index in IsHighlighted") );
|
|
|
|
return ld->IsHighlighted();
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::HighlightLines( size_t lineFrom,
|
|
size_t lineTo,
|
|
bool highlight )
|
|
{
|
|
if ( IsVirtual() )
|
|
{
|
|
wxArrayInt linesChanged;
|
|
if ( !m_selStore.SelectRange(lineFrom, lineTo, highlight,
|
|
&linesChanged) )
|
|
{
|
|
// meny items changed state, refresh everything
|
|
RefreshLines(lineFrom, lineTo);
|
|
}
|
|
else // only a few items changed state, refresh only them
|
|
{
|
|
size_t count = linesChanged.GetCount();
|
|
for ( size_t n = 0; n < count; n++ )
|
|
{
|
|
RefreshLine(linesChanged[n]);
|
|
}
|
|
}
|
|
}
|
|
else // iterate over all items in non report view
|
|
{
|
|
for ( size_t line = lineFrom; line <= lineTo; line++ )
|
|
{
|
|
if ( HighlightLine(line, highlight) )
|
|
RefreshLine(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool wxListMainWindow::HighlightLine( size_t line, bool highlight )
|
|
{
|
|
bool changed;
|
|
|
|
if ( IsVirtual() )
|
|
{
|
|
changed = m_selStore.SelectItem(line, highlight);
|
|
}
|
|
else // !virtual
|
|
{
|
|
wxListLineData *ld = GetLine(line);
|
|
wxCHECK_MSG( ld, false, wxT("invalid index in HighlightLine") );
|
|
|
|
changed = ld->Highlight(highlight);
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
SendNotify( line, highlight ? wxEVT_COMMAND_LIST_ITEM_SELECTED
|
|
: wxEVT_COMMAND_LIST_ITEM_DESELECTED );
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void wxListMainWindow::RefreshLine( size_t line )
|
|
{
|
|
if ( InReportView() )
|
|
{
|
|
size_t visibleFrom, visibleTo;
|
|
GetVisibleLinesRange(&visibleFrom, &visibleTo);
|
|
|
|
if ( line < visibleFrom || line > visibleTo )
|
|
return;
|
|
}
|
|
|
|
wxRect rect = GetLineRect(line);
|
|
|
|
GetListCtrl()->CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y );
|
|
RefreshRect( rect );
|
|
}
|
|
|
|
void wxListMainWindow::RefreshLines( size_t lineFrom, size_t lineTo )
|
|
{
|
|
// we suppose that they are ordered by caller
|
|
wxASSERT_MSG( lineFrom <= lineTo, wxT("indices in disorder") );
|
|
|
|
wxASSERT_MSG( lineTo < GetItemCount(), wxT("invalid line range") );
|
|
|
|
if ( InReportView() )
|
|
{
|
|
size_t visibleFrom, visibleTo;
|
|
GetVisibleLinesRange(&visibleFrom, &visibleTo);
|
|
|
|
if ( lineFrom < visibleFrom )
|
|
lineFrom = visibleFrom;
|
|
if ( lineTo > visibleTo )
|
|
lineTo = visibleTo;
|
|
|
|
wxRect rect;
|
|
rect.x = 0;
|
|
rect.y = GetLineY(lineFrom);
|
|
rect.width = GetClientSize().x;
|
|
rect.height = GetLineY(lineTo) - rect.y + GetLineHeight();
|
|
|
|
GetListCtrl()->CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y );
|
|
RefreshRect( rect );
|
|
}
|
|
else // !report
|
|
{
|
|
// TODO: this should be optimized...
|
|
for ( size_t line = lineFrom; line <= lineTo; line++ )
|
|
{
|
|
RefreshLine(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::RefreshAfter( size_t lineFrom )
|
|
{
|
|
if ( InReportView() )
|
|
{
|
|
size_t visibleFrom, visibleTo;
|
|
GetVisibleLinesRange(&visibleFrom, &visibleTo);
|
|
|
|
if ( lineFrom < visibleFrom )
|
|
lineFrom = visibleFrom;
|
|
else if ( lineFrom > visibleTo )
|
|
return;
|
|
|
|
wxRect rect;
|
|
rect.x = 0;
|
|
rect.y = GetLineY(lineFrom);
|
|
GetListCtrl()->CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y );
|
|
|
|
wxSize size = GetClientSize();
|
|
rect.width = size.x;
|
|
|
|
// refresh till the bottom of the window
|
|
rect.height = size.y - rect.y;
|
|
|
|
RefreshRect( rect );
|
|
}
|
|
else // !report
|
|
{
|
|
// TODO: how to do it more efficiently?
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::RefreshSelected()
|
|
{
|
|
if ( IsEmpty() )
|
|
return;
|
|
|
|
size_t from, to;
|
|
if ( InReportView() )
|
|
{
|
|
GetVisibleLinesRange(&from, &to);
|
|
}
|
|
else // !virtual
|
|
{
|
|
from = 0;
|
|
to = GetItemCount() - 1;
|
|
}
|
|
|
|
if ( HasCurrent() && m_current >= from && m_current <= to )
|
|
RefreshLine(m_current);
|
|
|
|
for ( size_t line = from; line <= to; line++ )
|
|
{
|
|
// NB: the test works as expected even if m_current == -1
|
|
if ( line != m_current && IsHighlighted(line) )
|
|
RefreshLine(line);
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
|
|
{
|
|
// Note: a wxPaintDC must be constructed even if no drawing is
|
|
// done (a Windows requirement).
|
|
wxPaintDC dc( this );
|
|
|
|
if ( IsEmpty() )
|
|
{
|
|
// nothing to draw or not the moment to draw it
|
|
return;
|
|
}
|
|
|
|
if ( m_dirty )
|
|
RecalculatePositions( false );
|
|
|
|
GetListCtrl()->PrepareDC( dc );
|
|
|
|
int dev_x, dev_y;
|
|
GetListCtrl()->CalcScrolledPosition( 0, 0, &dev_x, &dev_y );
|
|
|
|
dc.SetFont( GetFont() );
|
|
|
|
if ( InReportView() )
|
|
{
|
|
int lineHeight = GetLineHeight();
|
|
|
|
size_t visibleFrom, visibleTo;
|
|
GetVisibleLinesRange(&visibleFrom, &visibleTo);
|
|
|
|
wxRect rectLine;
|
|
int xOrig = dc.LogicalToDeviceX( 0 );
|
|
int yOrig = dc.LogicalToDeviceY( 0 );
|
|
|
|
// tell the caller cache to cache the data
|
|
if ( IsVirtual() )
|
|
{
|
|
wxListEvent evCache(wxEVT_COMMAND_LIST_CACHE_HINT,
|
|
GetParent()->GetId());
|
|
evCache.SetEventObject( GetParent() );
|
|
evCache.m_oldItemIndex = visibleFrom;
|
|
evCache.m_item.m_itemId =
|
|
evCache.m_itemIndex = visibleTo;
|
|
GetParent()->GetEventHandler()->ProcessEvent( evCache );
|
|
}
|
|
|
|
for ( size_t line = visibleFrom; line <= visibleTo; line++ )
|
|
{
|
|
rectLine = GetLineRect(line);
|
|
|
|
|
|
if ( !IsExposed(rectLine.x + xOrig, rectLine.y + yOrig,
|
|
rectLine.width, rectLine.height) )
|
|
{
|
|
// don't redraw unaffected lines to avoid flicker
|
|
continue;
|
|
}
|
|
|
|
GetLine(line)->DrawInReportMode( &dc,
|
|
rectLine,
|
|
GetLineHighlightRect(line),
|
|
IsHighlighted(line),
|
|
line == m_current );
|
|
}
|
|
|
|
if ( HasFlag(wxLC_HRULES) )
|
|
{
|
|
wxPen pen(GetRuleColour(), 1, wxPENSTYLE_SOLID);
|
|
wxSize clientSize = GetClientSize();
|
|
|
|
size_t i = visibleFrom;
|
|
if (i == 0) i = 1; // Don't draw the first one
|
|
for ( ; i <= visibleTo; i++ )
|
|
{
|
|
dc.SetPen(pen);
|
|
dc.SetBrush( *wxTRANSPARENT_BRUSH );
|
|
dc.DrawLine(0 - dev_x, i * lineHeight,
|
|
clientSize.x - dev_x, i * lineHeight);
|
|
}
|
|
|
|
// Draw last horizontal rule
|
|
if ( visibleTo == GetItemCount() - 1 )
|
|
{
|
|
dc.SetPen( pen );
|
|
dc.SetBrush( *wxTRANSPARENT_BRUSH );
|
|
dc.DrawLine(0 - dev_x, (m_lineTo + 1) * lineHeight,
|
|
clientSize.x - dev_x , (m_lineTo + 1) * lineHeight );
|
|
}
|
|
}
|
|
|
|
// Draw vertical rules if required
|
|
if ( HasFlag(wxLC_VRULES) && !IsEmpty() )
|
|
{
|
|
wxPen pen(GetRuleColour(), 1, wxPENSTYLE_SOLID);
|
|
wxRect firstItemRect, lastItemRect;
|
|
|
|
GetItemRect(visibleFrom, firstItemRect);
|
|
GetItemRect(visibleTo, lastItemRect);
|
|
int x = firstItemRect.GetX();
|
|
dc.SetPen(pen);
|
|
dc.SetBrush(* wxTRANSPARENT_BRUSH);
|
|
|
|
for (int col = 0; col < GetColumnCount(); col++)
|
|
{
|
|
int colWidth = GetColumnWidth(col);
|
|
x += colWidth;
|
|
int x_pos = x - dev_x;
|
|
if (col < GetColumnCount()-1) x_pos -= 2;
|
|
dc.DrawLine(x_pos, firstItemRect.GetY() - 1 - dev_y,
|
|
x_pos, lastItemRect.GetBottom() + 1 - dev_y);
|
|
}
|
|
}
|
|
}
|
|
else // !report
|
|
{
|
|
size_t count = GetItemCount();
|
|
for ( size_t i = 0; i < count; i++ )
|
|
{
|
|
GetLine(i)->Draw( &dc, i == m_current );
|
|
}
|
|
}
|
|
|
|
// DrawFocusRect() is unusable under Mac, it draws outside of the highlight
|
|
// rectangle somehow and so leaves traces when the item is not selected any
|
|
// more, see #12229.
|
|
#ifndef __WXMAC__
|
|
if ( HasCurrent() )
|
|
{
|
|
int flags = 0;
|
|
if ( IsHighlighted(m_current) )
|
|
flags |= wxCONTROL_SELECTED;
|
|
|
|
wxRendererNative::Get().
|
|
DrawFocusRect(this, dc, GetLineHighlightRect(m_current), flags);
|
|
}
|
|
#endif // !__WXMAC__
|
|
}
|
|
|
|
void wxListMainWindow::HighlightAll( bool on )
|
|
{
|
|
if ( IsSingleSel() )
|
|
{
|
|
wxASSERT_MSG( !on, wxT("can't do this in a single selection control") );
|
|
|
|
// we just have one item to turn off
|
|
if ( HasCurrent() && IsHighlighted(m_current) )
|
|
{
|
|
HighlightLine(m_current, false);
|
|
RefreshLine(m_current);
|
|
}
|
|
}
|
|
else // multi selection
|
|
{
|
|
if ( !IsEmpty() )
|
|
HighlightLines(0, GetItemCount() - 1, on);
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::OnChildFocus(wxChildFocusEvent& WXUNUSED(event))
|
|
{
|
|
// Do nothing here. This prevents the default handler in wxScrolledWindow
|
|
// from needlessly scrolling the window when the edit control is
|
|
// dismissed. See ticket #9563.
|
|
}
|
|
|
|
void wxListMainWindow::SendNotify( size_t line,
|
|
wxEventType command,
|
|
const wxPoint& point )
|
|
{
|
|
wxListEvent le( command, GetParent()->GetId() );
|
|
le.SetEventObject( GetParent() );
|
|
|
|
le.m_item.m_itemId =
|
|
le.m_itemIndex = line;
|
|
|
|
// set only for events which have position
|
|
if ( point != wxDefaultPosition )
|
|
le.m_pointDrag = point;
|
|
|
|
// don't try to get the line info for virtual list controls: the main
|
|
// program has it anyhow and if we did it would result in accessing all
|
|
// the lines, even those which are not visible now and this is precisely
|
|
// what we're trying to avoid
|
|
if ( !IsVirtual() )
|
|
{
|
|
if ( line != (size_t)-1 )
|
|
{
|
|
GetLine(line)->GetItem( 0, le.m_item );
|
|
}
|
|
//else: this happens for wxEVT_COMMAND_LIST_ITEM_FOCUSED event
|
|
}
|
|
//else: there may be no more such item
|
|
|
|
GetParent()->GetEventHandler()->ProcessEvent( le );
|
|
}
|
|
|
|
void wxListMainWindow::ChangeCurrent(size_t current)
|
|
{
|
|
m_current = current;
|
|
|
|
// as the current item changed, we shouldn't start editing it when the
|
|
// "slow click" timer expires as the click happened on another item
|
|
if ( m_renameTimer->IsRunning() )
|
|
m_renameTimer->Stop();
|
|
|
|
SendNotify(current, wxEVT_COMMAND_LIST_ITEM_FOCUSED);
|
|
}
|
|
|
|
wxTextCtrl *wxListMainWindow::EditLabel(long item, wxClassInfo* textControlClass)
|
|
{
|
|
wxCHECK_MSG( (item >= 0) && ((size_t)item < GetItemCount()), NULL,
|
|
wxT("wrong index in wxGenericListCtrl::EditLabel()") );
|
|
|
|
wxASSERT_MSG( textControlClass->IsKindOf(CLASSINFO(wxTextCtrl)),
|
|
wxT("EditLabel() needs a text control") );
|
|
|
|
size_t itemEdit = (size_t)item;
|
|
|
|
wxListEvent le( wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT, GetParent()->GetId() );
|
|
le.SetEventObject( GetParent() );
|
|
le.m_item.m_itemId =
|
|
le.m_itemIndex = item;
|
|
wxListLineData *data = GetLine(itemEdit);
|
|
wxCHECK_MSG( data, NULL, wxT("invalid index in EditLabel()") );
|
|
data->GetItem( 0, le.m_item );
|
|
|
|
if ( GetParent()->GetEventHandler()->ProcessEvent( le ) && !le.IsAllowed() )
|
|
{
|
|
// vetoed by user code
|
|
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 )
|
|
{
|
|
// TODO: use wxTheApp->SafeYieldFor(NULL, wxEVT_CATEGORY_UI) instead
|
|
// so that no pending events may change the item count (see below)
|
|
// IMPORTANT: needs to be tested!
|
|
wxSafeYield();
|
|
|
|
// Pending events dispatched by wxSafeYield might have changed the item
|
|
// count
|
|
if ( (size_t)item >= GetItemCount() )
|
|
return NULL;
|
|
}
|
|
|
|
wxTextCtrl * const text = (wxTextCtrl *)textControlClass->CreateObject();
|
|
m_textctrlWrapper = new wxListTextCtrlWrapper(this, text, item);
|
|
return m_textctrlWrapper->GetText();
|
|
}
|
|
|
|
void wxListMainWindow::OnRenameTimer()
|
|
{
|
|
wxCHECK_RET( HasCurrent(), wxT("unexpected rename timer") );
|
|
|
|
EditLabel( m_current );
|
|
}
|
|
|
|
bool wxListMainWindow::OnRenameAccept(size_t itemEdit, const wxString& value)
|
|
{
|
|
wxListEvent le( wxEVT_COMMAND_LIST_END_LABEL_EDIT, GetParent()->GetId() );
|
|
le.SetEventObject( GetParent() );
|
|
le.m_item.m_itemId =
|
|
le.m_itemIndex = itemEdit;
|
|
|
|
wxListLineData *data = GetLine(itemEdit);
|
|
|
|
wxCHECK_MSG( data, false, wxT("invalid index in OnRenameAccept()") );
|
|
|
|
data->GetItem( 0, le.m_item );
|
|
le.m_item.m_text = value;
|
|
return !GetParent()->GetEventHandler()->ProcessEvent( le ) ||
|
|
le.IsAllowed();
|
|
}
|
|
|
|
void wxListMainWindow::OnRenameCancelled(size_t itemEdit)
|
|
{
|
|
// let owner know that the edit was cancelled
|
|
wxListEvent le( wxEVT_COMMAND_LIST_END_LABEL_EDIT, GetParent()->GetId() );
|
|
|
|
le.SetEditCanceled(true);
|
|
|
|
le.SetEventObject( GetParent() );
|
|
le.m_item.m_itemId =
|
|
le.m_itemIndex = itemEdit;
|
|
|
|
wxListLineData *data = GetLine(itemEdit);
|
|
wxCHECK_RET( data, wxT("invalid index in OnRenameCancelled()") );
|
|
|
|
data->GetItem( 0, le.m_item );
|
|
GetEventHandler()->ProcessEvent( le );
|
|
}
|
|
|
|
void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
|
{
|
|
#ifdef __WXMAC__
|
|
// On wxMac we can't depend on the EVT_KILL_FOCUS event to properly
|
|
// shutdown the edit control when the mouse is clicked elsewhere on the
|
|
// listctrl because the order of events is different (or something like
|
|
// that), so explicitly end the edit if it is active.
|
|
if ( event.LeftDown() && m_textctrlWrapper )
|
|
m_textctrlWrapper->EndEdit(wxListTextCtrlWrapper::End_Accept);
|
|
#endif // __WXMAC__
|
|
|
|
if ( event.LeftDown() )
|
|
SetFocus();
|
|
|
|
event.SetEventObject( GetParent() );
|
|
if ( GetParent()->GetEventHandler()->ProcessEvent( event) )
|
|
return;
|
|
|
|
if (event.GetEventType() == wxEVT_MOUSEWHEEL)
|
|
{
|
|
// let the base class handle mouse wheel events.
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
if ( !HasCurrent() || IsEmpty() )
|
|
{
|
|
if (event.RightDown())
|
|
{
|
|
SendNotify( (size_t)-1, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
|
|
|
|
wxContextMenuEvent evtCtx(wxEVT_CONTEXT_MENU,
|
|
GetParent()->GetId(),
|
|
ClientToScreen(event.GetPosition()));
|
|
evtCtx.SetEventObject(GetParent());
|
|
GetParent()->GetEventHandler()->ProcessEvent(evtCtx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (m_dirty)
|
|
return;
|
|
|
|
if ( !(event.Dragging() || event.ButtonDown() || event.LeftUp() ||
|
|
event.ButtonDClick()) )
|
|
return;
|
|
|
|
int x = event.GetX();
|
|
int y = event.GetY();
|
|
GetListCtrl()->CalcUnscrolledPosition( x, y, &x, &y );
|
|
|
|
// where did we hit it (if we did)?
|
|
long hitResult = 0;
|
|
|
|
size_t count = GetItemCount(),
|
|
current;
|
|
|
|
if ( InReportView() )
|
|
{
|
|
current = y / GetLineHeight();
|
|
if ( current < count )
|
|
hitResult = HitTestLine(current, x, y);
|
|
}
|
|
else // !report
|
|
{
|
|
// TODO: optimize it too! this is less simple than for report view but
|
|
// enumerating all items is still not a way to do it!!
|
|
for ( current = 0; current < count; current++ )
|
|
{
|
|
hitResult = HitTestLine(current, x, y);
|
|
if ( hitResult )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update drag events counter first as we must do it even if the mouse is
|
|
// not on any item right now as we must keep count in case we started
|
|
// dragging from the empty control area but continued to do it over a valid
|
|
// item -- in this situation we must not start dragging this item.
|
|
if (event.Dragging())
|
|
m_dragCount++;
|
|
else
|
|
m_dragCount = 0;
|
|
|
|
// The only mouse event that can be generated without any valid item is
|
|
// wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK as it can be useful to have a global
|
|
// popup menu for the list control itself which should be shown even when
|
|
// the user clicks outside of any item.
|
|
if ( !hitResult )
|
|
{
|
|
// outside of any item
|
|
if (event.RightDown())
|
|
{
|
|
SendNotify( (size_t) -1, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
|
|
|
|
wxContextMenuEvent evtCtx(
|
|
wxEVT_CONTEXT_MENU,
|
|
GetParent()->GetId(),
|
|
ClientToScreen(event.GetPosition()));
|
|
evtCtx.SetEventObject(GetParent());
|
|
GetParent()->GetEventHandler()->ProcessEvent(evtCtx);
|
|
}
|
|
else
|
|
{
|
|
// reset the selection and bail out
|
|
HighlightAll(false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( event.Dragging() )
|
|
{
|
|
if (m_dragCount == 1)
|
|
{
|
|
// we have to report the raw, physical coords as we want to be
|
|
// able to call HitTest(event.m_pointDrag) from the user code to
|
|
// get the item being dragged
|
|
m_dragStart = event.GetPosition();
|
|
}
|
|
|
|
if (m_dragCount != 3)
|
|
return;
|
|
|
|
int command = event.RightIsDown() ? wxEVT_COMMAND_LIST_BEGIN_RDRAG
|
|
: wxEVT_COMMAND_LIST_BEGIN_DRAG;
|
|
|
|
SendNotify( m_lineLastClicked, command, m_dragStart );
|
|
|
|
return;
|
|
}
|
|
|
|
bool forceClick = false;
|
|
if (event.ButtonDClick())
|
|
{
|
|
if ( m_renameTimer->IsRunning() )
|
|
m_renameTimer->Stop();
|
|
|
|
m_lastOnSame = false;
|
|
|
|
if ( current == m_lineLastClicked )
|
|
{
|
|
SendNotify( current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED );
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// The first click was on another item, so don't interpret this as
|
|
// a double click, but as a simple click instead
|
|
forceClick = true;
|
|
}
|
|
}
|
|
|
|
if (event.LeftUp())
|
|
{
|
|
if (m_lineSelectSingleOnUp != (size_t)-1)
|
|
{
|
|
// select single line
|
|
HighlightAll( false );
|
|
ReverseHighlight(m_lineSelectSingleOnUp);
|
|
}
|
|
|
|
if (m_lastOnSame)
|
|
{
|
|
if ((current == m_current) &&
|
|
(hitResult == wxLIST_HITTEST_ONITEMLABEL) &&
|
|
HasFlag(wxLC_EDIT_LABELS) )
|
|
{
|
|
if ( !InReportView() ||
|
|
GetLineLabelRect(current).Contains(x, y) )
|
|
{
|
|
int dclick = wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC);
|
|
m_renameTimer->Start(dclick > 0 ? dclick : 250, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_lastOnSame = false;
|
|
m_lineSelectSingleOnUp = (size_t)-1;
|
|
}
|
|
else
|
|
{
|
|
// This is necessary, because after a DnD operation in
|
|
// from and to ourself, the up event is swallowed by the
|
|
// DnD code. So on next non-up event (which means here and
|
|
// now) m_lineSelectSingleOnUp should be reset.
|
|
m_lineSelectSingleOnUp = (size_t)-1;
|
|
}
|
|
if (event.RightDown())
|
|
{
|
|
m_lineBeforeLastClicked = m_lineLastClicked;
|
|
m_lineLastClicked = current;
|
|
|
|
// If the item is already selected, do not update the selection.
|
|
// Multi-selections should not be cleared if a selected item is clicked.
|
|
if (!IsHighlighted(current))
|
|
{
|
|
HighlightAll(false);
|
|
ChangeCurrent(current);
|
|
ReverseHighlight(m_current);
|
|
}
|
|
|
|
SendNotify( current, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
|
|
|
|
// Allow generation of context menu event
|
|
event.Skip();
|
|
}
|
|
else if (event.MiddleDown())
|
|
{
|
|
SendNotify( current, wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK );
|
|
}
|
|
else if ( event.LeftDown() || forceClick )
|
|
{
|
|
m_lineBeforeLastClicked = m_lineLastClicked;
|
|
m_lineLastClicked = current;
|
|
|
|
size_t oldCurrent = m_current;
|
|
bool oldWasSelected = IsHighlighted(m_current);
|
|
|
|
bool cmdModifierDown = event.CmdDown();
|
|
if ( IsSingleSel() || !(cmdModifierDown || event.ShiftDown()) )
|
|
{
|
|
if ( IsSingleSel() || !IsHighlighted(current) )
|
|
{
|
|
HighlightAll( false );
|
|
|
|
ChangeCurrent(current);
|
|
|
|
ReverseHighlight(m_current);
|
|
}
|
|
else // multi sel & current is highlighted & no mod keys
|
|
{
|
|
m_lineSelectSingleOnUp = current;
|
|
ChangeCurrent(current); // change focus
|
|
}
|
|
}
|
|
else // multi sel & either ctrl or shift is down
|
|
{
|
|
if (cmdModifierDown)
|
|
{
|
|
ChangeCurrent(current);
|
|
|
|
ReverseHighlight(m_current);
|
|
}
|
|
else if (event.ShiftDown())
|
|
{
|
|
ChangeCurrent(current);
|
|
|
|
size_t lineFrom = oldCurrent,
|
|
lineTo = current;
|
|
|
|
if ( lineTo < lineFrom )
|
|
{
|
|
lineTo = lineFrom;
|
|
lineFrom = m_current;
|
|
}
|
|
|
|
HighlightLines(lineFrom, lineTo);
|
|
}
|
|
else // !ctrl, !shift
|
|
{
|
|
// test in the enclosing if should make it impossible
|
|
wxFAIL_MSG( wxT("how did we get here?") );
|
|
}
|
|
}
|
|
|
|
if (m_current != oldCurrent)
|
|
RefreshLine( oldCurrent );
|
|
|
|
// forceClick is only set if the previous click was on another item
|
|
m_lastOnSame = !forceClick && (m_current == oldCurrent) && oldWasSelected;
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::MoveToItem(size_t item)
|
|
{
|
|
if ( item == (size_t)-1 )
|
|
return;
|
|
|
|
wxRect rect = GetLineRect(item);
|
|
|
|
int client_w, client_h;
|
|
GetClientSize( &client_w, &client_h );
|
|
|
|
const int hLine = GetLineHeight();
|
|
|
|
int view_x = SCROLL_UNIT_X * GetListCtrl()->GetScrollPos( wxHORIZONTAL );
|
|
int view_y = hLine * GetListCtrl()->GetScrollPos( wxVERTICAL );
|
|
|
|
if ( InReportView() )
|
|
{
|
|
// the next we need the range of lines shown it might be different,
|
|
// so recalculate it
|
|
ResetVisibleLinesRange();
|
|
|
|
if (rect.y < view_y)
|
|
GetListCtrl()->Scroll( -1, rect.y / hLine );
|
|
if (rect.y + rect.height + 5 > view_y + client_h)
|
|
GetListCtrl()->Scroll( -1, (rect.y + rect.height - client_h + hLine) / hLine );
|
|
|
|
#ifdef __WXMAC__
|
|
// At least on Mac the visible lines value will get reset inside of
|
|
// Scroll *before* it actually scrolls the window because of the
|
|
// Update() that happens there, so it will still have the wrong value.
|
|
// So let's reset it again and wait for it to be recalculated in the
|
|
// next paint event. I would expect this problem to show up in wxGTK
|
|
// too but couldn't duplicate it there. Perhaps the order of events
|
|
// is different... --Robin
|
|
ResetVisibleLinesRange();
|
|
#endif
|
|
}
|
|
else // !report
|
|
{
|
|
int sx = -1,
|
|
sy = -1;
|
|
|
|
if (rect.x-view_x < 5)
|
|
sx = (rect.x - 5) / SCROLL_UNIT_X;
|
|
if (rect.x + rect.width - 5 > view_x + client_w)
|
|
sx = (rect.x + rect.width - client_w + SCROLL_UNIT_X) / SCROLL_UNIT_X;
|
|
|
|
if (rect.y-view_y < 5)
|
|
sy = (rect.y - 5) / hLine;
|
|
if (rect.y + rect.height - 5 > view_y + client_h)
|
|
sy = (rect.y + rect.height - client_h + hLine) / hLine;
|
|
|
|
GetListCtrl()->Scroll(sx, sy);
|
|
}
|
|
}
|
|
|
|
bool wxListMainWindow::ScrollList(int WXUNUSED(dx), int dy)
|
|
{
|
|
if ( !InReportView() )
|
|
{
|
|
// TODO: this should work in all views but is not implemented now
|
|
return false;
|
|
}
|
|
|
|
size_t top, bottom;
|
|
GetVisibleLinesRange(&top, &bottom);
|
|
|
|
if ( bottom == (size_t)-1 )
|
|
return 0;
|
|
|
|
ResetVisibleLinesRange();
|
|
|
|
int hLine = GetLineHeight();
|
|
|
|
GetListCtrl()->Scroll(-1, top + dy / hLine);
|
|
|
|
#ifdef __WXMAC__
|
|
// see comment in MoveToItem() for why we do this
|
|
ResetVisibleLinesRange();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// keyboard handling
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::OnArrowChar(size_t newCurrent, const wxKeyEvent& event)
|
|
{
|
|
wxCHECK_RET( newCurrent < (size_t)GetItemCount(),
|
|
wxT("invalid item index in OnArrowChar()") );
|
|
|
|
size_t oldCurrent = m_current;
|
|
|
|
// in single selection we just ignore Shift as we can't select several
|
|
// items anyhow
|
|
if ( event.ShiftDown() && !IsSingleSel() )
|
|
{
|
|
ChangeCurrent(newCurrent);
|
|
|
|
// refresh the old focus to remove it
|
|
RefreshLine( oldCurrent );
|
|
|
|
// select all the items between the old and the new one
|
|
if ( oldCurrent > newCurrent )
|
|
{
|
|
newCurrent = oldCurrent;
|
|
oldCurrent = m_current;
|
|
}
|
|
|
|
HighlightLines(oldCurrent, newCurrent);
|
|
}
|
|
else // !shift
|
|
{
|
|
// all previously selected items are unselected unless ctrl is held
|
|
// in a multiselection control
|
|
if ( !event.ControlDown() || IsSingleSel() )
|
|
HighlightAll(false);
|
|
|
|
ChangeCurrent(newCurrent);
|
|
|
|
// refresh the old focus to remove it
|
|
RefreshLine( oldCurrent );
|
|
|
|
// in single selection mode we must always have a selected item
|
|
if ( !event.ControlDown() || IsSingleSel() )
|
|
HighlightLine( m_current, true );
|
|
}
|
|
|
|
RefreshLine( m_current );
|
|
|
|
MoveToFocus();
|
|
}
|
|
|
|
void wxListMainWindow::OnKeyDown( wxKeyEvent &event )
|
|
{
|
|
wxWindow *parent = GetParent();
|
|
|
|
// propagate the key event upwards
|
|
wxKeyEvent ke(event);
|
|
ke.SetEventObject( parent );
|
|
if (parent->GetEventHandler()->ProcessEvent( ke ))
|
|
return;
|
|
|
|
// send a list event
|
|
wxListEvent le( wxEVT_COMMAND_LIST_KEY_DOWN, parent->GetId() );
|
|
le.m_item.m_itemId =
|
|
le.m_itemIndex = m_current;
|
|
if (HasCurrent())
|
|
GetLine(m_current)->GetItem( 0, le.m_item );
|
|
le.m_code = event.GetKeyCode();
|
|
le.SetEventObject( parent );
|
|
if (parent->GetEventHandler()->ProcessEvent( le ))
|
|
return;
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxListMainWindow::OnKeyUp( wxKeyEvent &event )
|
|
{
|
|
wxWindow *parent = GetParent();
|
|
|
|
// propagate the key event upwards
|
|
wxKeyEvent ke(event);
|
|
if (parent->GetEventHandler()->ProcessEvent( ke ))
|
|
return;
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxListMainWindow::OnCharHook( wxKeyEvent &event )
|
|
{
|
|
if ( m_textctrlWrapper )
|
|
{
|
|
// When an in-place editor is active we should ensure that it always
|
|
// gets the key events that are special to it.
|
|
if ( m_textctrlWrapper->CheckForEndEditKey(event) )
|
|
{
|
|
// Skip the call to wxEvent::Skip() below.
|
|
return;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxListMainWindow::OnChar( wxKeyEvent &event )
|
|
{
|
|
wxWindow *parent = GetParent();
|
|
|
|
// propagate the char event upwards
|
|
wxKeyEvent ke(event);
|
|
ke.SetEventObject( parent );
|
|
if (parent->GetEventHandler()->ProcessEvent( ke ))
|
|
return;
|
|
|
|
if ( HandleAsNavigationKey(event) )
|
|
return;
|
|
|
|
// no item -> nothing to do
|
|
if (!HasCurrent())
|
|
{
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
// don't use m_linesPerPage directly as it might not be computed yet
|
|
const int pageSize = GetCountPerPage();
|
|
wxCHECK_RET( pageSize, wxT("should have non zero page size") );
|
|
|
|
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;
|
|
}
|
|
|
|
switch ( event.GetKeyCode() )
|
|
{
|
|
case WXK_UP:
|
|
if ( m_current > 0 )
|
|
OnArrowChar( m_current - 1, event );
|
|
break;
|
|
|
|
case WXK_DOWN:
|
|
if ( m_current < (size_t)GetItemCount() - 1 )
|
|
OnArrowChar( m_current + 1, event );
|
|
break;
|
|
|
|
case WXK_END:
|
|
if (!IsEmpty())
|
|
OnArrowChar( GetItemCount() - 1, event );
|
|
break;
|
|
|
|
case WXK_HOME:
|
|
if (!IsEmpty())
|
|
OnArrowChar( 0, event );
|
|
break;
|
|
|
|
case WXK_PAGEUP:
|
|
{
|
|
int steps = InReportView() ? pageSize - 1
|
|
: m_current % pageSize;
|
|
|
|
int index = m_current - steps;
|
|
if (index < 0)
|
|
index = 0;
|
|
|
|
OnArrowChar( index, event );
|
|
}
|
|
break;
|
|
|
|
case WXK_PAGEDOWN:
|
|
{
|
|
int steps = InReportView()
|
|
? pageSize - 1
|
|
: pageSize - (m_current % pageSize) - 1;
|
|
|
|
size_t index = m_current + steps;
|
|
size_t count = GetItemCount();
|
|
if ( index >= count )
|
|
index = count - 1;
|
|
|
|
OnArrowChar( index, event );
|
|
}
|
|
break;
|
|
|
|
case WXK_LEFT:
|
|
if ( !InReportView() )
|
|
{
|
|
int index = m_current - pageSize;
|
|
if (index < 0)
|
|
index = 0;
|
|
|
|
OnArrowChar( index, event );
|
|
}
|
|
break;
|
|
|
|
case WXK_RIGHT:
|
|
if ( !InReportView() )
|
|
{
|
|
size_t index = m_current + pageSize;
|
|
|
|
size_t count = GetItemCount();
|
|
if ( index >= count )
|
|
index = count - 1;
|
|
|
|
OnArrowChar( index, event );
|
|
}
|
|
break;
|
|
|
|
case WXK_SPACE:
|
|
if ( IsSingleSel() )
|
|
{
|
|
if ( event.ControlDown() )
|
|
{
|
|
ReverseHighlight(m_current);
|
|
}
|
|
else // normal space press
|
|
{
|
|
SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED );
|
|
}
|
|
}
|
|
else // multiple selection
|
|
{
|
|
ReverseHighlight(m_current);
|
|
}
|
|
break;
|
|
|
|
case WXK_RETURN:
|
|
case WXK_EXECUTE:
|
|
SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED );
|
|
break;
|
|
|
|
default:
|
|
event.Skip();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// focus handling
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) )
|
|
{
|
|
if ( GetParent() )
|
|
{
|
|
wxFocusEvent event( wxEVT_SET_FOCUS, GetParent()->GetId() );
|
|
event.SetEventObject( GetParent() );
|
|
if ( GetParent()->GetEventHandler()->ProcessEvent( event) )
|
|
return;
|
|
}
|
|
|
|
// wxGTK sends us EVT_SET_FOCUS events even if we had never got
|
|
// EVT_KILL_FOCUS before which means that we finish by redrawing the items
|
|
// which are already drawn correctly resulting in horrible flicker - avoid
|
|
// it
|
|
if ( !m_hasFocus )
|
|
{
|
|
m_hasFocus = true;
|
|
|
|
RefreshSelected();
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::OnKillFocus( wxFocusEvent &WXUNUSED(event) )
|
|
{
|
|
if ( GetParent() )
|
|
{
|
|
wxFocusEvent event( wxEVT_KILL_FOCUS, GetParent()->GetId() );
|
|
event.SetEventObject( GetParent() );
|
|
if ( GetParent()->GetEventHandler()->ProcessEvent( event) )
|
|
return;
|
|
}
|
|
|
|
m_hasFocus = false;
|
|
RefreshSelected();
|
|
}
|
|
|
|
void wxListMainWindow::DrawImage( int index, wxDC *dc, int x, int y )
|
|
{
|
|
if ( HasFlag(wxLC_ICON) && (m_normal_image_list))
|
|
{
|
|
m_normal_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT );
|
|
}
|
|
else if ( HasFlag(wxLC_SMALL_ICON) && (m_small_image_list))
|
|
{
|
|
m_small_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT );
|
|
}
|
|
else if ( HasFlag(wxLC_LIST) && (m_small_image_list))
|
|
{
|
|
m_small_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT );
|
|
}
|
|
else if ( InReportView() && (m_small_image_list))
|
|
{
|
|
m_small_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT );
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::GetImageSize( int index, int &width, int &height ) const
|
|
{
|
|
if ( HasFlag(wxLC_ICON) && m_normal_image_list )
|
|
{
|
|
m_normal_image_list->GetSize( index, width, height );
|
|
}
|
|
else if ( HasFlag(wxLC_SMALL_ICON) && m_small_image_list )
|
|
{
|
|
m_small_image_list->GetSize( index, width, height );
|
|
}
|
|
else if ( HasFlag(wxLC_LIST) && m_small_image_list )
|
|
{
|
|
m_small_image_list->GetSize( index, width, height );
|
|
}
|
|
else if ( InReportView() && m_small_image_list )
|
|
{
|
|
m_small_image_list->GetSize( index, width, height );
|
|
}
|
|
else
|
|
{
|
|
width =
|
|
height = 0;
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::SetImageList( wxImageList *imageList, int which )
|
|
{
|
|
m_dirty = true;
|
|
|
|
// calc the spacing from the icon size
|
|
int width = 0, height = 0;
|
|
|
|
if ((imageList) && (imageList->GetImageCount()) )
|
|
imageList->GetSize(0, width, height);
|
|
|
|
if (which == wxIMAGE_LIST_NORMAL)
|
|
{
|
|
m_normal_image_list = imageList;
|
|
m_normal_spacing = width + 8;
|
|
}
|
|
|
|
if (which == wxIMAGE_LIST_SMALL)
|
|
{
|
|
m_small_image_list = imageList;
|
|
m_small_spacing = width + 14;
|
|
m_lineHeight = 0; // ensure that the line height will be recalc'd
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::SetItemSpacing( int spacing, bool isSmall )
|
|
{
|
|
m_dirty = true;
|
|
if (isSmall)
|
|
m_small_spacing = spacing;
|
|
else
|
|
m_normal_spacing = spacing;
|
|
}
|
|
|
|
int wxListMainWindow::GetItemSpacing( bool isSmall )
|
|
{
|
|
return isSmall ? m_small_spacing : m_normal_spacing;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// columns
|
|
// ----------------------------------------------------------------------------
|
|
|
|
int
|
|
wxListMainWindow::ComputeMinHeaderWidth(const wxListHeaderData* column) const
|
|
{
|
|
wxClientDC dc(const_cast<wxListMainWindow*>(this));
|
|
|
|
int width = dc.GetTextExtent(column->GetText()).x + AUTOSIZE_COL_MARGIN;
|
|
|
|
width += 2*EXTRA_WIDTH;
|
|
|
|
// check for column header's image availability
|
|
const int image = column->GetImage();
|
|
if ( image != -1 )
|
|
{
|
|
if ( m_small_image_list )
|
|
{
|
|
int ix = 0, iy = 0;
|
|
m_small_image_list->GetSize(image, ix, iy);
|
|
width += ix + HEADER_IMAGE_MARGIN_IN_REPORT_MODE;
|
|
}
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
void wxListMainWindow::SetColumn( int col, const wxListItem &item )
|
|
{
|
|
wxListHeaderDataList::compatibility_iterator node = m_columns.Item( col );
|
|
|
|
wxCHECK_RET( node, wxT("invalid column index in SetColumn") );
|
|
|
|
wxListHeaderData *column = node->GetData();
|
|
column->SetItem( item );
|
|
|
|
if ( item.m_width == wxLIST_AUTOSIZE_USEHEADER )
|
|
column->SetWidth(ComputeMinHeaderWidth(column));
|
|
|
|
wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin;
|
|
if ( headerWin )
|
|
headerWin->m_dirty = true;
|
|
|
|
m_dirty = true;
|
|
|
|
// invalidate it as it has to be recalculated
|
|
m_headerWidth = 0;
|
|
}
|
|
|
|
void wxListMainWindow::SetColumnWidth( int col, int width )
|
|
{
|
|
wxCHECK_RET( col >= 0 && col < GetColumnCount(),
|
|
wxT("invalid column index") );
|
|
|
|
wxCHECK_RET( InReportView(),
|
|
wxT("SetColumnWidth() can only be called in report mode.") );
|
|
|
|
m_dirty = true;
|
|
|
|
wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin;
|
|
if ( headerWin )
|
|
headerWin->m_dirty = true;
|
|
|
|
wxListHeaderDataList::compatibility_iterator node = m_columns.Item( col );
|
|
wxCHECK_RET( node, wxT("no column?") );
|
|
|
|
wxListHeaderData *column = node->GetData();
|
|
|
|
size_t count = GetItemCount();
|
|
|
|
if (width == wxLIST_AUTOSIZE_USEHEADER)
|
|
{
|
|
width = ComputeMinHeaderWidth(column);
|
|
}
|
|
else if ( width == wxLIST_AUTOSIZE )
|
|
{
|
|
width = ComputeMinHeaderWidth(column);
|
|
|
|
if ( !IsVirtual() )
|
|
{
|
|
wxClientDC dc(this);
|
|
dc.SetFont( GetFont() );
|
|
|
|
int max = AUTOSIZE_COL_MARGIN;
|
|
|
|
// if the cached column width isn't valid then recalculate it
|
|
if (m_aColWidths.Item(col)->bNeedsUpdate)
|
|
{
|
|
for (size_t i = 0; i < count; i++)
|
|
{
|
|
wxListLineData *line = GetLine( i );
|
|
wxListItemDataList::compatibility_iterator n = line->m_items.Item( col );
|
|
|
|
wxCHECK_RET( n, wxT("no subitem?") );
|
|
|
|
wxListItemData *itemData = n->GetData();
|
|
wxListItem item;
|
|
|
|
itemData->GetItem(item);
|
|
int itemWidth = GetItemWidthWithImage(&item);
|
|
if (itemWidth > max)
|
|
max = itemWidth;
|
|
}
|
|
|
|
m_aColWidths.Item(col)->bNeedsUpdate = false;
|
|
m_aColWidths.Item(col)->nMaxWidth = max;
|
|
}
|
|
|
|
max = m_aColWidths.Item(col)->nMaxWidth + AUTOSIZE_COL_MARGIN;
|
|
if ( width < max )
|
|
width = max;
|
|
}
|
|
}
|
|
|
|
column->SetWidth( width );
|
|
|
|
// invalidate it as it has to be recalculated
|
|
m_headerWidth = 0;
|
|
}
|
|
|
|
int wxListMainWindow::GetHeaderWidth() const
|
|
{
|
|
if ( !m_headerWidth )
|
|
{
|
|
wxListMainWindow *self = wxConstCast(this, wxListMainWindow);
|
|
|
|
size_t count = GetColumnCount();
|
|
for ( size_t col = 0; col < count; col++ )
|
|
{
|
|
self->m_headerWidth += GetColumnWidth(col);
|
|
}
|
|
}
|
|
|
|
return m_headerWidth;
|
|
}
|
|
|
|
void wxListMainWindow::GetColumn( int col, wxListItem &item ) const
|
|
{
|
|
wxListHeaderDataList::compatibility_iterator node = m_columns.Item( col );
|
|
wxCHECK_RET( node, wxT("invalid column index in GetColumn") );
|
|
|
|
wxListHeaderData *column = node->GetData();
|
|
column->GetItem( item );
|
|
}
|
|
|
|
int wxListMainWindow::GetColumnWidth( int col ) const
|
|
{
|
|
wxListHeaderDataList::compatibility_iterator node = m_columns.Item( col );
|
|
wxCHECK_MSG( node, 0, wxT("invalid column index") );
|
|
|
|
wxListHeaderData *column = node->GetData();
|
|
return column->GetWidth();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// item state
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::SetItem( wxListItem &item )
|
|
{
|
|
long id = item.m_itemId;
|
|
wxCHECK_RET( id >= 0 && (size_t)id < GetItemCount(),
|
|
wxT("invalid item index in SetItem") );
|
|
|
|
if ( !IsVirtual() )
|
|
{
|
|
wxListLineData *line = GetLine((size_t)id);
|
|
line->SetItem( item.m_col, item );
|
|
|
|
// Set item state if user wants
|
|
if ( item.m_mask & wxLIST_MASK_STATE )
|
|
SetItemState( item.m_itemId, item.m_state, item.m_state );
|
|
|
|
if (InReportView())
|
|
{
|
|
// update the Max Width Cache if needed
|
|
int width = GetItemWidthWithImage(&item);
|
|
|
|
if (width > m_aColWidths.Item(item.m_col)->nMaxWidth)
|
|
m_aColWidths.Item(item.m_col)->nMaxWidth = width;
|
|
}
|
|
}
|
|
|
|
// update the item on screen unless we're going to update everything soon
|
|
// anyhow
|
|
if ( !m_dirty )
|
|
{
|
|
wxRect rectItem;
|
|
GetItemRect(id, rectItem);
|
|
RefreshRect(rectItem);
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::SetItemStateAll(long state, long stateMask)
|
|
{
|
|
if ( IsEmpty() )
|
|
return;
|
|
|
|
// first deal with selection
|
|
if ( stateMask & wxLIST_STATE_SELECTED )
|
|
{
|
|
// set/clear select state
|
|
if ( IsVirtual() )
|
|
{
|
|
// optimized version for virtual listctrl.
|
|
m_selStore.SelectRange(0, GetItemCount() - 1, state == wxLIST_STATE_SELECTED);
|
|
Refresh();
|
|
}
|
|
else if ( state & wxLIST_STATE_SELECTED )
|
|
{
|
|
const long count = GetItemCount();
|
|
for( long i = 0; i < count; i++ )
|
|
{
|
|
SetItemState( i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// clear for non virtual (somewhat optimized by using GetNextItem())
|
|
long i = -1;
|
|
while ( (i = GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1 )
|
|
{
|
|
SetItemState( i, 0, wxLIST_STATE_SELECTED );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( HasCurrent() && (state == 0) && (stateMask & wxLIST_STATE_FOCUSED) )
|
|
{
|
|
// unfocus all: only one item can be focussed, so clearing focus for
|
|
// all items is simply clearing focus of the focussed item.
|
|
SetItemState(m_current, state, stateMask);
|
|
}
|
|
//(setting focus to all items makes no sense, so it is not handled here.)
|
|
}
|
|
|
|
void wxListMainWindow::SetItemState( long litem, long state, long stateMask )
|
|
{
|
|
if ( litem == -1 )
|
|
{
|
|
SetItemStateAll(state, stateMask);
|
|
return;
|
|
}
|
|
|
|
wxCHECK_RET( litem >= 0 && (size_t)litem < GetItemCount(),
|
|
wxT("invalid list ctrl item index in SetItem") );
|
|
|
|
size_t oldCurrent = m_current;
|
|
size_t item = (size_t)litem; // safe because of the check above
|
|
|
|
// do we need to change the focus?
|
|
if ( stateMask & wxLIST_STATE_FOCUSED )
|
|
{
|
|
if ( state & wxLIST_STATE_FOCUSED )
|
|
{
|
|
// don't do anything if this item is already focused
|
|
if ( item != m_current )
|
|
{
|
|
ChangeCurrent(item);
|
|
|
|
if ( oldCurrent != (size_t)-1 )
|
|
{
|
|
if ( IsSingleSel() )
|
|
{
|
|
HighlightLine(oldCurrent, false);
|
|
}
|
|
|
|
RefreshLine(oldCurrent);
|
|
}
|
|
|
|
RefreshLine( m_current );
|
|
}
|
|
}
|
|
else // unfocus
|
|
{
|
|
// don't do anything if this item is not focused
|
|
if ( item == m_current )
|
|
{
|
|
ResetCurrent();
|
|
|
|
if ( IsSingleSel() )
|
|
{
|
|
// we must unselect the old current item as well or we
|
|
// might end up with more than one selected item in a
|
|
// single selection control
|
|
HighlightLine(oldCurrent, false);
|
|
}
|
|
|
|
RefreshLine( oldCurrent );
|
|
}
|
|
}
|
|
}
|
|
|
|
// do we need to change the selection state?
|
|
if ( stateMask & wxLIST_STATE_SELECTED )
|
|
{
|
|
bool on = (state & wxLIST_STATE_SELECTED) != 0;
|
|
|
|
if ( IsSingleSel() )
|
|
{
|
|
if ( on )
|
|
{
|
|
// selecting the item also makes it the focused one in the
|
|
// single sel mode
|
|
if ( m_current != item )
|
|
{
|
|
ChangeCurrent(item);
|
|
|
|
if ( oldCurrent != (size_t)-1 )
|
|
{
|
|
HighlightLine( oldCurrent, false );
|
|
RefreshLine( oldCurrent );
|
|
}
|
|
}
|
|
}
|
|
else // off
|
|
{
|
|
// only the current item may be selected anyhow
|
|
if ( item != m_current )
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( HighlightLine(item, on) )
|
|
{
|
|
RefreshLine(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
int wxListMainWindow::GetItemState( long item, long stateMask ) const
|
|
{
|
|
wxCHECK_MSG( item >= 0 && (size_t)item < GetItemCount(), 0,
|
|
wxT("invalid list ctrl item index in GetItemState()") );
|
|
|
|
int ret = wxLIST_STATE_DONTCARE;
|
|
|
|
if ( stateMask & wxLIST_STATE_FOCUSED )
|
|
{
|
|
if ( (size_t)item == m_current )
|
|
ret |= wxLIST_STATE_FOCUSED;
|
|
}
|
|
|
|
if ( stateMask & wxLIST_STATE_SELECTED )
|
|
{
|
|
if ( IsHighlighted(item) )
|
|
ret |= wxLIST_STATE_SELECTED;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void wxListMainWindow::GetItem( wxListItem &item ) const
|
|
{
|
|
wxCHECK_RET( item.m_itemId >= 0 && (size_t)item.m_itemId < GetItemCount(),
|
|
wxT("invalid item index in GetItem") );
|
|
|
|
wxListLineData *line = GetLine((size_t)item.m_itemId);
|
|
line->GetItem( item.m_col, item );
|
|
|
|
// Get item state if user wants it
|
|
if ( item.m_mask & wxLIST_MASK_STATE )
|
|
item.m_state = GetItemState( item.m_itemId, wxLIST_STATE_SELECTED |
|
|
wxLIST_STATE_FOCUSED );
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// item count
|
|
// ----------------------------------------------------------------------------
|
|
|
|
size_t wxListMainWindow::GetItemCount() const
|
|
{
|
|
return IsVirtual() ? m_countVirt : m_lines.GetCount();
|
|
}
|
|
|
|
void wxListMainWindow::SetItemCount(long count)
|
|
{
|
|
m_selStore.SetItemCount(count);
|
|
m_countVirt = count;
|
|
|
|
ResetVisibleLinesRange();
|
|
|
|
// scrollbars must be reset
|
|
m_dirty = true;
|
|
}
|
|
|
|
int wxListMainWindow::GetSelectedItemCount() const
|
|
{
|
|
// deal with the quick case first
|
|
if ( IsSingleSel() )
|
|
return HasCurrent() ? IsHighlighted(m_current) : false;
|
|
|
|
// virtual controls remmebers all its selections itself
|
|
if ( IsVirtual() )
|
|
return m_selStore.GetSelectedCount();
|
|
|
|
// TODO: we probably should maintain the number of items selected even for
|
|
// non virtual controls as enumerating all lines is really slow...
|
|
size_t countSel = 0;
|
|
size_t count = GetItemCount();
|
|
for ( size_t line = 0; line < count; line++ )
|
|
{
|
|
if ( GetLine(line)->IsHighlighted() )
|
|
countSel++;
|
|
}
|
|
|
|
return countSel;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// item position/size
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxRect wxListMainWindow::GetViewRect() const
|
|
{
|
|
wxASSERT_MSG( !HasFlag(wxLC_LIST), "not implemented for list view" );
|
|
|
|
// we need to find the longest/tallest label
|
|
wxCoord xMax = 0, yMax = 0;
|
|
const int count = GetItemCount();
|
|
if ( count )
|
|
{
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
// we need logical, not physical, coordinates here, so use
|
|
// GetLineRect() instead of GetItemRect()
|
|
wxRect r = GetLineRect(i);
|
|
|
|
wxCoord x = r.GetRight(),
|
|
y = r.GetBottom();
|
|
|
|
if ( x > xMax )
|
|
xMax = x;
|
|
if ( y > yMax )
|
|
yMax = y;
|
|
}
|
|
}
|
|
|
|
// some fudge needed to make it look prettier
|
|
xMax += 2 * EXTRA_BORDER_X;
|
|
yMax += 2 * EXTRA_BORDER_Y;
|
|
|
|
// account for the scrollbars if necessary
|
|
const wxSize sizeAll = GetClientSize();
|
|
if ( xMax > sizeAll.x )
|
|
yMax += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y);
|
|
if ( yMax > sizeAll.y )
|
|
xMax += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
|
|
|
|
return wxRect(0, 0, xMax, yMax);
|
|
}
|
|
|
|
bool
|
|
wxListMainWindow::GetSubItemRect(long item, long subItem, wxRect& rect) const
|
|
{
|
|
wxCHECK_MSG( subItem == wxLIST_GETSUBITEMRECT_WHOLEITEM || InReportView(),
|
|
false,
|
|
wxT("GetSubItemRect only meaningful in report view") );
|
|
wxCHECK_MSG( item >= 0 && (size_t)item < GetItemCount(), false,
|
|
wxT("invalid item in GetSubItemRect") );
|
|
|
|
// ensure that we're laid out, otherwise we could return nonsense
|
|
if ( m_dirty )
|
|
{
|
|
wxConstCast(this, wxListMainWindow)->
|
|
RecalculatePositions(true /* no refresh */);
|
|
}
|
|
|
|
rect = GetLineRect((size_t)item);
|
|
|
|
// Adjust rect to specified column
|
|
if ( subItem != wxLIST_GETSUBITEMRECT_WHOLEITEM )
|
|
{
|
|
wxCHECK_MSG( subItem >= 0 && subItem < GetColumnCount(), false,
|
|
wxT("invalid subItem in GetSubItemRect") );
|
|
|
|
for (int i = 0; i < subItem; i++)
|
|
{
|
|
rect.x += GetColumnWidth(i);
|
|
}
|
|
rect.width = GetColumnWidth(subItem);
|
|
}
|
|
|
|
GetListCtrl()->CalcScrolledPosition(rect.x, rect.y, &rect.x, &rect.y);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxListMainWindow::GetItemPosition(long item, wxPoint& pos) const
|
|
{
|
|
wxRect rect;
|
|
GetItemRect(item, rect);
|
|
|
|
pos.x = rect.x;
|
|
pos.y = rect.y;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// geometry calculation
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::RecalculatePositions(bool noRefresh)
|
|
{
|
|
const int lineHeight = GetLineHeight();
|
|
|
|
wxClientDC dc( this );
|
|
dc.SetFont( GetFont() );
|
|
|
|
const size_t count = GetItemCount();
|
|
|
|
int iconSpacing;
|
|
if ( HasFlag(wxLC_ICON) && m_normal_image_list )
|
|
iconSpacing = m_normal_spacing;
|
|
else if ( HasFlag(wxLC_SMALL_ICON) && m_small_image_list )
|
|
iconSpacing = m_small_spacing;
|
|
else
|
|
iconSpacing = 0;
|
|
|
|
// Note that we do not call GetClientSize() here but
|
|
// GetSize() and subtract the border size for sunken
|
|
// borders manually. This is technically incorrect,
|
|
// but we need to know the client area's size WITHOUT
|
|
// scrollbars here. Since we don't know if there are
|
|
// any scrollbars, we use GetSize() instead. Another
|
|
// solution would be to call SetScrollbars() here to
|
|
// remove the scrollbars and call GetClientSize() then,
|
|
// but this might result in flicker and - worse - will
|
|
// reset the scrollbars to 0 which is not good at all
|
|
// if you resize a dialog/window, but don't want to
|
|
// reset the window scrolling. RR.
|
|
// Furthermore, we actually do NOT subtract the border
|
|
// width as 2 pixels is just the extra space which we
|
|
// need around the actual content in the window. Other-
|
|
// wise the text would e.g. touch the upper border. RR.
|
|
int clientWidth,
|
|
clientHeight;
|
|
GetSize( &clientWidth, &clientHeight );
|
|
|
|
if ( InReportView() )
|
|
{
|
|
// all lines have the same height and we scroll one line per step
|
|
int entireHeight = count * lineHeight + LINE_SPACING;
|
|
|
|
m_linesPerPage = clientHeight / lineHeight;
|
|
|
|
ResetVisibleLinesRange();
|
|
|
|
GetListCtrl()->SetScrollbars( SCROLL_UNIT_X, lineHeight,
|
|
GetHeaderWidth() / SCROLL_UNIT_X,
|
|
(entireHeight + lineHeight - 1) / lineHeight,
|
|
GetListCtrl()->GetScrollPos(wxHORIZONTAL),
|
|
GetListCtrl()->GetScrollPos(wxVERTICAL),
|
|
true );
|
|
}
|
|
else // !report
|
|
{
|
|
// we have 3 different layout strategies: either layout all items
|
|
// horizontally/vertically (wxLC_ALIGN_XXX styles explicitly given) or
|
|
// to arrange them in top to bottom, left to right (don't ask me why
|
|
// not the other way round...) order
|
|
if ( HasFlag(wxLC_ALIGN_LEFT | wxLC_ALIGN_TOP) )
|
|
{
|
|
int x = EXTRA_BORDER_X;
|
|
int y = EXTRA_BORDER_Y;
|
|
|
|
wxCoord widthMax = 0;
|
|
|
|
size_t i;
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
wxListLineData *line = GetLine(i);
|
|
line->CalculateSize( &dc, iconSpacing );
|
|
line->SetPosition( x, y, iconSpacing );
|
|
|
|
wxSize sizeLine = GetLineSize(i);
|
|
|
|
if ( HasFlag(wxLC_ALIGN_TOP) )
|
|
{
|
|
if ( sizeLine.x > widthMax )
|
|
widthMax = sizeLine.x;
|
|
|
|
y += sizeLine.y;
|
|
}
|
|
else // wxLC_ALIGN_LEFT
|
|
{
|
|
x += sizeLine.x + MARGIN_BETWEEN_ROWS;
|
|
}
|
|
}
|
|
|
|
if ( HasFlag(wxLC_ALIGN_TOP) )
|
|
{
|
|
// traverse the items again and tweak their sizes so that they are
|
|
// all the same in a row
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
wxListLineData *line = GetLine(i);
|
|
line->m_gi->ExtendWidth(widthMax);
|
|
}
|
|
}
|
|
|
|
GetListCtrl()->SetScrollbars
|
|
(
|
|
SCROLL_UNIT_X,
|
|
lineHeight,
|
|
(x + SCROLL_UNIT_X) / SCROLL_UNIT_X,
|
|
(y + lineHeight) / lineHeight,
|
|
GetListCtrl()->GetScrollPos( wxHORIZONTAL ),
|
|
GetListCtrl()->GetScrollPos( wxVERTICAL ),
|
|
true
|
|
);
|
|
}
|
|
else // "flowed" arrangement, the most complicated case
|
|
{
|
|
// at first we try without any scrollbars, if the items don't fit into
|
|
// the window, we recalculate after subtracting the space taken by the
|
|
// scrollbar
|
|
|
|
int entireWidth = 0;
|
|
|
|
for (int tries = 0; tries < 2; tries++)
|
|
{
|
|
entireWidth = 2 * EXTRA_BORDER_X;
|
|
|
|
if (tries == 1)
|
|
{
|
|
// Now we have decided that the items do not fit into the
|
|
// client area, so we need a scrollbar
|
|
entireWidth += SCROLL_UNIT_X;
|
|
}
|
|
|
|
int x = EXTRA_BORDER_X;
|
|
int y = EXTRA_BORDER_Y;
|
|
|
|
// Note that "row" here is vertical, i.e. what is called
|
|
// "column" in many other places in wxWidgets.
|
|
int maxWidthInThisRow = 0;
|
|
|
|
m_linesPerPage = 0;
|
|
int currentlyVisibleLines = 0;
|
|
|
|
for (size_t i = 0; i < count; i++)
|
|
{
|
|
currentlyVisibleLines++;
|
|
wxListLineData *line = GetLine( i );
|
|
line->CalculateSize( &dc, iconSpacing );
|
|
line->SetPosition( x, y, iconSpacing );
|
|
|
|
wxSize sizeLine = GetLineSize( i );
|
|
|
|
if ( maxWidthInThisRow < sizeLine.x )
|
|
maxWidthInThisRow = sizeLine.x;
|
|
|
|
y += sizeLine.y;
|
|
if (currentlyVisibleLines > m_linesPerPage)
|
|
m_linesPerPage = currentlyVisibleLines;
|
|
|
|
// Have we reached the end of the row either because no
|
|
// more items would fit or because there are simply no more
|
|
// items?
|
|
if ( y + sizeLine.y >= clientHeight
|
|
|| i == count - 1)
|
|
{
|
|
// Adjust all items in this row to have the same
|
|
// width to ensure that they all align horizontally in
|
|
// icon view.
|
|
if ( HasFlag(wxLC_ICON) || HasFlag(wxLC_SMALL_ICON) )
|
|
{
|
|
size_t firstRowLine = i - currentlyVisibleLines + 1;
|
|
for (size_t j = firstRowLine; j <= i; j++)
|
|
{
|
|
GetLine(j)->m_gi->ExtendWidth(maxWidthInThisRow);
|
|
}
|
|
}
|
|
|
|
currentlyVisibleLines = 0;
|
|
y = EXTRA_BORDER_Y;
|
|
maxWidthInThisRow += MARGIN_BETWEEN_ROWS;
|
|
x += maxWidthInThisRow;
|
|
entireWidth += maxWidthInThisRow;
|
|
maxWidthInThisRow = 0;
|
|
}
|
|
|
|
if ( (tries == 0) &&
|
|
(entireWidth + SCROLL_UNIT_X > clientWidth) )
|
|
{
|
|
clientHeight -= wxSystemSettings::
|
|
GetMetric(wxSYS_HSCROLL_Y);
|
|
m_linesPerPage = 0;
|
|
break;
|
|
}
|
|
|
|
if ( i == count - 1 )
|
|
tries = 1; // Everything fits, no second try required.
|
|
}
|
|
}
|
|
|
|
GetListCtrl()->SetScrollbars
|
|
(
|
|
SCROLL_UNIT_X,
|
|
lineHeight,
|
|
(entireWidth + SCROLL_UNIT_X) / SCROLL_UNIT_X,
|
|
0,
|
|
GetListCtrl()->GetScrollPos( wxHORIZONTAL ),
|
|
0,
|
|
true
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( !noRefresh )
|
|
{
|
|
// FIXME: why should we call it from here?
|
|
UpdateCurrent();
|
|
|
|
RefreshAll();
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::RefreshAll()
|
|
{
|
|
m_dirty = false;
|
|
Refresh();
|
|
|
|
wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin;
|
|
if ( headerWin && headerWin->m_dirty )
|
|
{
|
|
headerWin->m_dirty = false;
|
|
headerWin->Refresh();
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::UpdateCurrent()
|
|
{
|
|
if ( !HasCurrent() && !IsEmpty() )
|
|
ChangeCurrent(0);
|
|
}
|
|
|
|
long wxListMainWindow::GetNextItem( long item,
|
|
int WXUNUSED(geometry),
|
|
int state ) const
|
|
{
|
|
long ret = item,
|
|
max = GetItemCount();
|
|
wxCHECK_MSG( (ret == -1) || (ret < max), -1,
|
|
wxT("invalid listctrl index in GetNextItem()") );
|
|
|
|
// notice that we start with the next item (or the first one if item == -1)
|
|
// and this is intentional to allow writing a simple loop to iterate over
|
|
// all selected items
|
|
ret++;
|
|
if ( ret == max )
|
|
// this is not an error because the index was OK initially,
|
|
// just no such item
|
|
return -1;
|
|
|
|
if ( !state )
|
|
// any will do
|
|
return (size_t)ret;
|
|
|
|
size_t count = GetItemCount();
|
|
for ( size_t line = (size_t)ret; line < count; line++ )
|
|
{
|
|
if ( (state & wxLIST_STATE_FOCUSED) && (line == m_current) )
|
|
return line;
|
|
|
|
if ( (state & wxLIST_STATE_SELECTED) && IsHighlighted(line) )
|
|
return line;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// deleting stuff
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::DeleteItem( long lindex )
|
|
{
|
|
size_t count = GetItemCount();
|
|
|
|
wxCHECK_RET( (lindex >= 0) && ((size_t)lindex < count),
|
|
wxT("invalid item index in DeleteItem") );
|
|
|
|
size_t index = (size_t)lindex;
|
|
|
|
// we don't need to adjust the index for the previous items
|
|
if ( HasCurrent() && m_current >= index )
|
|
{
|
|
// if the current item is being deleted, we want the next one to
|
|
// become selected - unless there is no next one - so don't adjust
|
|
// m_current in this case
|
|
if ( m_current != index || m_current == count - 1 )
|
|
m_current--;
|
|
}
|
|
|
|
if ( InReportView() )
|
|
{
|
|
// mark the Column Max Width cache as dirty if the items in the line
|
|
// we're deleting contain the Max Column Width
|
|
wxListLineData * const line = GetLine(index);
|
|
wxListItemDataList::compatibility_iterator n;
|
|
wxListItemData *itemData;
|
|
wxListItem item;
|
|
int itemWidth;
|
|
|
|
for (size_t i = 0; i < m_columns.GetCount(); i++)
|
|
{
|
|
n = line->m_items.Item( i );
|
|
itemData = n->GetData();
|
|
itemData->GetItem(item);
|
|
|
|
itemWidth = GetItemWidthWithImage(&item);
|
|
|
|
if (itemWidth >= m_aColWidths.Item(i)->nMaxWidth)
|
|
m_aColWidths.Item(i)->bNeedsUpdate = true;
|
|
}
|
|
|
|
ResetVisibleLinesRange();
|
|
}
|
|
|
|
SendNotify( index, wxEVT_COMMAND_LIST_DELETE_ITEM, wxDefaultPosition );
|
|
|
|
if ( IsVirtual() )
|
|
{
|
|
m_countVirt--;
|
|
m_selStore.OnItemDelete(index);
|
|
}
|
|
else
|
|
{
|
|
m_lines.RemoveAt( index );
|
|
}
|
|
|
|
// we need to refresh the (vert) scrollbar as the number of items changed
|
|
m_dirty = true;
|
|
|
|
RefreshAfter(index);
|
|
}
|
|
|
|
void wxListMainWindow::DeleteColumn( int col )
|
|
{
|
|
wxListHeaderDataList::compatibility_iterator node = m_columns.Item( col );
|
|
|
|
wxCHECK_RET( node, wxT("invalid column index in DeleteColumn()") );
|
|
|
|
m_dirty = true;
|
|
delete node->GetData();
|
|
m_columns.Erase( node );
|
|
|
|
if ( !IsVirtual() )
|
|
{
|
|
// update all the items
|
|
for ( size_t i = 0; i < m_lines.GetCount(); i++ )
|
|
{
|
|
wxListLineData * const line = GetLine(i);
|
|
|
|
// In the following atypical but possible scenario it can be
|
|
// legal to call DeleteColumn() but the items may not have any
|
|
// values for it:
|
|
// 1. In report view, insert a second column.
|
|
// 2. Still in report view, add an item with 2 values.
|
|
// 3. Switch to an icon (or list) view.
|
|
// 4. Add an item -- necessarily with 1 value only.
|
|
// 5. Switch back to report view.
|
|
// 6. Call DeleteColumn().
|
|
// So we need to check for this as otherwise we would simply crash
|
|
// if this happens.
|
|
if ( line->m_items.GetCount() <= static_cast<unsigned>(col) )
|
|
continue;
|
|
|
|
wxListItemDataList::compatibility_iterator n = line->m_items.Item( col );
|
|
delete n->GetData();
|
|
line->m_items.Erase(n);
|
|
}
|
|
}
|
|
|
|
if ( InReportView() ) // we only cache max widths when in Report View
|
|
{
|
|
delete m_aColWidths.Item(col);
|
|
m_aColWidths.RemoveAt(col);
|
|
}
|
|
|
|
// invalidate it as it has to be recalculated
|
|
m_headerWidth = 0;
|
|
}
|
|
|
|
void wxListMainWindow::DoDeleteAllItems()
|
|
{
|
|
if ( IsEmpty() )
|
|
// nothing to do - in particular, don't send the event
|
|
return;
|
|
|
|
ResetCurrent();
|
|
|
|
// to make the deletion of all items faster, we don't send the
|
|
// notifications for each item deletion in this case but only one event
|
|
// for all of them: this is compatible with wxMSW and documented in
|
|
// DeleteAllItems() description
|
|
|
|
wxListEvent event( wxEVT_COMMAND_LIST_DELETE_ALL_ITEMS, GetParent()->GetId() );
|
|
event.SetEventObject( GetParent() );
|
|
GetParent()->GetEventHandler()->ProcessEvent( event );
|
|
|
|
if ( IsVirtual() )
|
|
{
|
|
m_countVirt = 0;
|
|
m_selStore.Clear();
|
|
}
|
|
|
|
if ( InReportView() )
|
|
{
|
|
ResetVisibleLinesRange();
|
|
for (size_t i = 0; i < m_aColWidths.GetCount(); i++)
|
|
{
|
|
m_aColWidths.Item(i)->bNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
m_lines.Clear();
|
|
}
|
|
|
|
void wxListMainWindow::DeleteAllItems()
|
|
{
|
|
DoDeleteAllItems();
|
|
|
|
RecalculatePositions();
|
|
}
|
|
|
|
void wxListMainWindow::DeleteEverything()
|
|
{
|
|
WX_CLEAR_LIST(wxListHeaderDataList, m_columns);
|
|
WX_CLEAR_ARRAY(m_aColWidths);
|
|
|
|
DeleteAllItems();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// scanning for an item
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::EnsureVisible( long index )
|
|
{
|
|
wxCHECK_RET( index >= 0 && (size_t)index < GetItemCount(),
|
|
wxT("invalid index in EnsureVisible") );
|
|
|
|
// We have to call this here because the label in question might just have
|
|
// been added and its position is not known yet
|
|
if ( m_dirty )
|
|
RecalculatePositions(true /* no refresh */);
|
|
|
|
MoveToItem((size_t)index);
|
|
}
|
|
|
|
long wxListMainWindow::FindItem(long start, const wxString& str, bool partial )
|
|
{
|
|
if (str.empty())
|
|
return wxNOT_FOUND;
|
|
|
|
long pos = start;
|
|
wxString str_upper = str.Upper();
|
|
if (pos < 0)
|
|
pos = 0;
|
|
|
|
size_t count = GetItemCount();
|
|
for ( size_t i = (size_t)pos; i < count; i++ )
|
|
{
|
|
wxListLineData *line = GetLine(i);
|
|
wxString line_upper = line->GetText(0).Upper();
|
|
if (!partial)
|
|
{
|
|
if (line_upper == str_upper )
|
|
return i;
|
|
}
|
|
else
|
|
{
|
|
if (line_upper.find(str_upper) == 0)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
long wxListMainWindow::FindItem(long start, wxUIntPtr data)
|
|
{
|
|
long pos = start;
|
|
if (pos < 0)
|
|
pos = 0;
|
|
|
|
size_t count = GetItemCount();
|
|
for (size_t i = (size_t)pos; i < count; i++)
|
|
{
|
|
wxListLineData *line = GetLine(i);
|
|
wxListItem item;
|
|
line->GetItem( 0, item );
|
|
if (item.m_data == data)
|
|
return i;
|
|
}
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
long wxListMainWindow::FindItem( const wxPoint& pt )
|
|
{
|
|
size_t topItem;
|
|
GetVisibleLinesRange( &topItem, NULL );
|
|
|
|
wxPoint p;
|
|
GetItemPosition( GetItemCount() - 1, p );
|
|
if ( p.y == 0 )
|
|
return topItem;
|
|
|
|
long id = (long)floor( pt.y * double(GetItemCount() - topItem - 1) / p.y + topItem );
|
|
if ( id >= 0 && id < (long)GetItemCount() )
|
|
return id;
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
long wxListMainWindow::HitTest( int x, int y, int &flags ) const
|
|
{
|
|
GetListCtrl()->CalcUnscrolledPosition( x, y, &x, &y );
|
|
|
|
size_t count = GetItemCount();
|
|
|
|
if ( InReportView() )
|
|
{
|
|
size_t current = y / GetLineHeight();
|
|
if ( current < count )
|
|
{
|
|
flags = HitTestLine(current, x, y);
|
|
if ( flags )
|
|
return current;
|
|
}
|
|
}
|
|
else // !report
|
|
{
|
|
// TODO: optimize it too! this is less simple than for report view but
|
|
// enumerating all items is still not a way to do it!!
|
|
for ( size_t current = 0; current < count; current++ )
|
|
{
|
|
flags = HitTestLine(current, x, y);
|
|
if ( flags )
|
|
return current;
|
|
}
|
|
}
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// adding stuff
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::InsertItem( wxListItem &item )
|
|
{
|
|
wxASSERT_MSG( !IsVirtual(), wxT("can't be used with virtual control") );
|
|
|
|
int count = GetItemCount();
|
|
wxCHECK_RET( item.m_itemId >= 0, wxT("invalid item index") );
|
|
|
|
if (item.m_itemId > count)
|
|
item.m_itemId = count;
|
|
|
|
size_t id = item.m_itemId;
|
|
|
|
m_dirty = true;
|
|
|
|
if ( InReportView() )
|
|
{
|
|
ResetVisibleLinesRange();
|
|
|
|
const unsigned col = item.GetColumn();
|
|
wxCHECK_RET( col < m_aColWidths.size(), "invalid item column" );
|
|
|
|
// calculate the width of the item and adjust the max column width
|
|
wxColWidthInfo *pWidthInfo = m_aColWidths.Item(col);
|
|
int width = GetItemWidthWithImage(&item);
|
|
item.SetWidth(width);
|
|
if (width > pWidthInfo->nMaxWidth)
|
|
pWidthInfo->nMaxWidth = width;
|
|
}
|
|
|
|
wxListLineData *line = new wxListLineData(this);
|
|
|
|
line->SetItem( item.m_col, item );
|
|
if ( item.m_mask & wxLIST_MASK_IMAGE )
|
|
{
|
|
// Reset the buffered height if it's not big enough for the new image.
|
|
int image = item.GetImage();
|
|
if ( m_small_image_list && image != -1 && InReportView() )
|
|
{
|
|
int imageWidth, imageHeight;
|
|
m_small_image_list->GetSize(image, imageWidth, imageHeight);
|
|
|
|
if ( imageHeight > m_lineHeight )
|
|
m_lineHeight = 0;
|
|
}
|
|
}
|
|
|
|
m_lines.Insert( line, id );
|
|
|
|
m_dirty = true;
|
|
|
|
// If an item is selected at or below the point of insertion, we need to
|
|
// increment the member variables because the current row's index has gone
|
|
// up by one
|
|
if ( HasCurrent() && m_current >= id )
|
|
m_current++;
|
|
|
|
SendNotify(id, wxEVT_COMMAND_LIST_INSERT_ITEM);
|
|
|
|
RefreshLines(id, GetItemCount() - 1);
|
|
}
|
|
|
|
void wxListMainWindow::InsertColumn( long col, const wxListItem &item )
|
|
{
|
|
m_dirty = true;
|
|
if ( InReportView() )
|
|
{
|
|
wxListHeaderData *column = new wxListHeaderData( item );
|
|
if (item.m_width == wxLIST_AUTOSIZE_USEHEADER)
|
|
column->SetWidth(ComputeMinHeaderWidth(column));
|
|
|
|
wxColWidthInfo *colWidthInfo = new wxColWidthInfo();
|
|
|
|
bool insert = (col >= 0) && ((size_t)col < m_columns.GetCount());
|
|
if ( insert )
|
|
{
|
|
wxListHeaderDataList::compatibility_iterator
|
|
node = m_columns.Item( col );
|
|
m_columns.Insert( node, column );
|
|
m_aColWidths.Insert( colWidthInfo, col );
|
|
}
|
|
else
|
|
{
|
|
m_columns.Append( column );
|
|
m_aColWidths.Add( colWidthInfo );
|
|
}
|
|
|
|
if ( !IsVirtual() )
|
|
{
|
|
// update all the items
|
|
for ( size_t i = 0; i < m_lines.GetCount(); i++ )
|
|
{
|
|
wxListLineData * const line = GetLine(i);
|
|
wxListItemData * const data = new wxListItemData(this);
|
|
if ( insert )
|
|
line->m_items.Insert(col, data);
|
|
else
|
|
line->m_items.Append(data);
|
|
}
|
|
}
|
|
|
|
// invalidate it as it has to be recalculated
|
|
m_headerWidth = 0;
|
|
}
|
|
}
|
|
|
|
int wxListMainWindow::GetItemWidthWithImage(wxListItem * item)
|
|
{
|
|
int width = 0;
|
|
wxClientDC dc(this);
|
|
|
|
dc.SetFont( GetFont() );
|
|
|
|
if (item->GetImage() != -1)
|
|
{
|
|
int ix, iy;
|
|
GetImageSize( item->GetImage(), ix, iy );
|
|
width += ix + 5;
|
|
}
|
|
|
|
if (!item->GetText().empty())
|
|
{
|
|
wxCoord w;
|
|
dc.GetTextExtent( item->GetText(), &w, NULL );
|
|
width += w;
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// sorting
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static wxListCtrlCompare list_ctrl_compare_func_2;
|
|
static wxIntPtr list_ctrl_compare_data;
|
|
|
|
int LINKAGEMODE list_ctrl_compare_func_1( wxListLineData **arg1, wxListLineData **arg2 )
|
|
{
|
|
wxListLineData *line1 = *arg1;
|
|
wxListLineData *line2 = *arg2;
|
|
wxListItem item;
|
|
line1->GetItem( 0, item );
|
|
wxUIntPtr data1 = item.m_data;
|
|
line2->GetItem( 0, item );
|
|
wxUIntPtr data2 = item.m_data;
|
|
return list_ctrl_compare_func_2( data1, data2, list_ctrl_compare_data );
|
|
}
|
|
|
|
void wxListMainWindow::SortItems( wxListCtrlCompare fn, wxIntPtr data )
|
|
{
|
|
// selections won't make sense any more after sorting the items so reset
|
|
// them
|
|
HighlightAll(false);
|
|
ResetCurrent();
|
|
|
|
list_ctrl_compare_func_2 = fn;
|
|
list_ctrl_compare_data = data;
|
|
m_lines.Sort( list_ctrl_compare_func_1 );
|
|
m_dirty = true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// scrolling
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::OnScroll(wxScrollWinEvent& event)
|
|
{
|
|
// update our idea of which lines are shown when we redraw the window the
|
|
// next time
|
|
ResetVisibleLinesRange();
|
|
|
|
if ( event.GetOrientation() == wxHORIZONTAL && HasHeader() )
|
|
{
|
|
wxGenericListCtrl* lc = GetListCtrl();
|
|
wxCHECK_RET( lc, wxT("no listctrl window?") );
|
|
|
|
if (lc->m_headerWin) // when we use wxLC_NO_HEADER, m_headerWin==NULL
|
|
{
|
|
lc->m_headerWin->Refresh();
|
|
lc->m_headerWin->Update();
|
|
}
|
|
}
|
|
}
|
|
|
|
int wxListMainWindow::GetCountPerPage() const
|
|
{
|
|
if ( !m_linesPerPage )
|
|
{
|
|
wxConstCast(this, wxListMainWindow)->
|
|
m_linesPerPage = GetClientSize().y / GetLineHeight();
|
|
}
|
|
|
|
return m_linesPerPage;
|
|
}
|
|
|
|
void wxListMainWindow::GetVisibleLinesRange(size_t *from, size_t *to)
|
|
{
|
|
wxASSERT_MSG( InReportView(), wxT("this is for report mode only") );
|
|
|
|
if ( m_lineFrom == (size_t)-1 )
|
|
{
|
|
size_t count = GetItemCount();
|
|
if ( count )
|
|
{
|
|
m_lineFrom = GetListCtrl()->GetScrollPos(wxVERTICAL);
|
|
|
|
// this may happen if SetScrollbars() hadn't been called yet
|
|
if ( m_lineFrom >= count )
|
|
m_lineFrom = count - 1;
|
|
|
|
// we redraw one extra line but this is needed to make the redrawing
|
|
// logic work when there is a fractional number of lines on screen
|
|
m_lineTo = m_lineFrom + m_linesPerPage;
|
|
if ( m_lineTo >= count )
|
|
m_lineTo = count - 1;
|
|
}
|
|
else // empty control
|
|
{
|
|
m_lineFrom = 0;
|
|
m_lineTo = (size_t)-1;
|
|
}
|
|
}
|
|
|
|
wxASSERT_MSG( IsEmpty() ||
|
|
(m_lineFrom <= m_lineTo && m_lineTo < GetItemCount()),
|
|
wxT("GetVisibleLinesRange() returns incorrect result") );
|
|
|
|
if ( from )
|
|
*from = m_lineFrom;
|
|
if ( to )
|
|
*to = m_lineTo;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
// wxGenericListCtrl
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxGenericListCtrl, wxControl)
|
|
|
|
BEGIN_EVENT_TABLE(wxGenericListCtrl,wxListCtrlBase)
|
|
EVT_SIZE(wxGenericListCtrl::OnSize)
|
|
EVT_SCROLLWIN(wxGenericListCtrl::OnScroll)
|
|
END_EVENT_TABLE()
|
|
|
|
void wxGenericListCtrl::Init()
|
|
{
|
|
m_imageListNormal = NULL;
|
|
m_imageListSmall = NULL;
|
|
m_imageListState = NULL;
|
|
|
|
m_ownsImageListNormal =
|
|
m_ownsImageListSmall =
|
|
m_ownsImageListState = false;
|
|
|
|
m_mainWin = NULL;
|
|
m_headerWin = NULL;
|
|
}
|
|
|
|
wxGenericListCtrl::~wxGenericListCtrl()
|
|
{
|
|
if (m_ownsImageListNormal)
|
|
delete m_imageListNormal;
|
|
if (m_ownsImageListSmall)
|
|
delete m_imageListSmall;
|
|
if (m_ownsImageListState)
|
|
delete m_imageListState;
|
|
}
|
|
|
|
void wxGenericListCtrl::CreateOrDestroyHeaderWindowAsNeeded()
|
|
{
|
|
bool needs_header = HasHeader();
|
|
bool has_header = (m_headerWin != NULL);
|
|
|
|
if (needs_header == has_header)
|
|
return;
|
|
|
|
if (needs_header)
|
|
{
|
|
m_headerWin = new wxListHeaderWindow
|
|
(
|
|
this, wxID_ANY, m_mainWin,
|
|
wxPoint(0,0),
|
|
wxSize
|
|
(
|
|
GetClientSize().x,
|
|
wxRendererNative::Get().GetHeaderButtonHeight(this)
|
|
),
|
|
wxTAB_TRAVERSAL
|
|
);
|
|
|
|
#if defined( __WXMAC__ )
|
|
static wxFont font( wxOSX_SYSTEM_FONT_SMALL );
|
|
m_headerWin->SetFont( font );
|
|
#endif
|
|
|
|
GetSizer()->Prepend( m_headerWin, 0, wxGROW );
|
|
}
|
|
else
|
|
{
|
|
GetSizer()->Detach( m_headerWin );
|
|
|
|
wxDELETE(m_headerWin);
|
|
}
|
|
}
|
|
|
|
bool wxGenericListCtrl::Create(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint &pos,
|
|
const wxSize &size,
|
|
long style,
|
|
const wxValidator &validator,
|
|
const wxString &name)
|
|
{
|
|
Init();
|
|
|
|
// just like in other ports, an assert will fail if the user doesn't give any type style:
|
|
wxASSERT_MSG( (style & wxLC_MASK_TYPE),
|
|
wxT("wxListCtrl style should have exactly one mode bit set") );
|
|
|
|
if ( !wxListCtrlBase::Create( parent, id, pos, size,
|
|
style | wxVSCROLL | wxHSCROLL,
|
|
validator, name ) )
|
|
return false;
|
|
|
|
#ifdef __WXGTK__
|
|
style &= ~wxBORDER_MASK;
|
|
style |= wxBORDER_THEME;
|
|
#endif
|
|
|
|
m_mainWin = new wxListMainWindow( this, wxID_ANY, wxPoint(0, 0), size, style );
|
|
|
|
SetTargetWindow( m_mainWin );
|
|
|
|
// We use the cursor keys for moving the selection, not scrolling, so call
|
|
// this method to ensure wxScrollHelperEvtHandler doesn't catch all
|
|
// keyboard events forwarded to us from wxListMainWindow.
|
|
DisableKeyboardScrolling();
|
|
|
|
wxBoxSizer *sizer = new wxBoxSizer( wxVERTICAL );
|
|
sizer->Add( m_mainWin, 1, wxGROW );
|
|
SetSizer( sizer );
|
|
|
|
CreateOrDestroyHeaderWindowAsNeeded();
|
|
|
|
SetInitialSize(size);
|
|
|
|
return true;
|
|
}
|
|
|
|
wxBorder wxGenericListCtrl::GetDefaultBorder() const
|
|
{
|
|
return wxBORDER_THEME;
|
|
}
|
|
|
|
#if defined(__WXMSW__) && !defined(__WXWINCE__) && !defined(__WXUNIVERSAL__)
|
|
WXLRESULT wxGenericListCtrl::MSWWindowProc(WXUINT nMsg,
|
|
WXWPARAM wParam,
|
|
WXLPARAM lParam)
|
|
{
|
|
WXLRESULT rc = wxListCtrlBase::MSWWindowProc(nMsg, wParam, lParam);
|
|
|
|
// we need to process arrows ourselves for scrolling
|
|
if ( nMsg == WM_GETDLGCODE )
|
|
{
|
|
rc |= DLGC_WANTARROWS;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
#endif // __WXMSW__
|
|
|
|
wxSize wxGenericListCtrl::GetSizeAvailableForScrollTarget(const wxSize& size)
|
|
{
|
|
wxSize newsize = size;
|
|
if (m_headerWin)
|
|
newsize.y -= m_headerWin->GetSize().y;
|
|
|
|
return newsize;
|
|
}
|
|
|
|
void wxGenericListCtrl::OnScroll(wxScrollWinEvent& event)
|
|
{
|
|
// update our idea of which lines are shown when we redraw
|
|
// the window the next time
|
|
m_mainWin->ResetVisibleLinesRange();
|
|
|
|
HandleOnScroll( event );
|
|
|
|
if ( event.GetOrientation() == wxHORIZONTAL && HasHeader() )
|
|
{
|
|
m_headerWin->Refresh();
|
|
m_headerWin->Update();
|
|
}
|
|
}
|
|
|
|
void wxGenericListCtrl::SetSingleStyle( long style, bool add )
|
|
{
|
|
wxASSERT_MSG( !(style & wxLC_VIRTUAL),
|
|
wxT("wxLC_VIRTUAL can't be [un]set") );
|
|
|
|
long flag = GetWindowStyle();
|
|
|
|
if (add)
|
|
{
|
|
if (style & wxLC_MASK_TYPE)
|
|
flag &= ~(wxLC_MASK_TYPE | wxLC_VIRTUAL);
|
|
if (style & wxLC_MASK_ALIGN)
|
|
flag &= ~wxLC_MASK_ALIGN;
|
|
if (style & wxLC_MASK_SORT)
|
|
flag &= ~wxLC_MASK_SORT;
|
|
}
|
|
|
|
if (add)
|
|
flag |= style;
|
|
else
|
|
flag &= ~style;
|
|
|
|
// some styles can be set without recreating everything (as happens in
|
|
// SetWindowStyleFlag() which calls wxListMainWindow::DeleteEverything())
|
|
if ( !(style & ~(wxLC_HRULES | wxLC_VRULES)) )
|
|
{
|
|
Refresh();
|
|
wxWindow::SetWindowStyleFlag(flag);
|
|
}
|
|
else
|
|
{
|
|
SetWindowStyleFlag( flag );
|
|
}
|
|
}
|
|
|
|
void wxGenericListCtrl::SetWindowStyleFlag( long flag )
|
|
{
|
|
// we add wxHSCROLL and wxVSCROLL in ctor unconditionally and it never
|
|
// makes sense to remove them as we'll always add scrollbars anyhow when
|
|
// needed
|
|
flag |= wxHSCROLL | wxVSCROLL;
|
|
|
|
const bool wasInReportView = HasFlag(wxLC_REPORT);
|
|
|
|
// update the window style first so that the header is created or destroyed
|
|
// corresponding to the new style
|
|
wxWindow::SetWindowStyleFlag( flag );
|
|
|
|
if (m_mainWin)
|
|
{
|
|
const bool inReportView = (flag & wxLC_REPORT) != 0;
|
|
if ( inReportView != wasInReportView )
|
|
{
|
|
// we need to notify the main window about this change as it must
|
|
// update its data structures
|
|
m_mainWin->SetReportView(inReportView);
|
|
}
|
|
|
|
// m_mainWin->DeleteEverything(); wxMSW doesn't do that
|
|
|
|
CreateOrDestroyHeaderWindowAsNeeded();
|
|
|
|
GetSizer()->Layout();
|
|
}
|
|
}
|
|
|
|
bool wxGenericListCtrl::GetColumn(int col, wxListItem &item) const
|
|
{
|
|
m_mainWin->GetColumn( col, item );
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetColumn( int col, const wxListItem& item )
|
|
{
|
|
m_mainWin->SetColumn( col, item );
|
|
return true;
|
|
}
|
|
|
|
int wxGenericListCtrl::GetColumnWidth( int col ) const
|
|
{
|
|
return m_mainWin->GetColumnWidth( col );
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetColumnWidth( int col, int width )
|
|
{
|
|
m_mainWin->SetColumnWidth( col, width );
|
|
return true;
|
|
}
|
|
|
|
int wxGenericListCtrl::GetCountPerPage() const
|
|
{
|
|
return m_mainWin->GetCountPerPage(); // different from Windows ?
|
|
}
|
|
|
|
bool wxGenericListCtrl::GetItem( wxListItem &info ) const
|
|
{
|
|
m_mainWin->GetItem( info );
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetItem( wxListItem &info )
|
|
{
|
|
m_mainWin->SetItem( info );
|
|
return true;
|
|
}
|
|
|
|
long wxGenericListCtrl::SetItem( long index, int col, const wxString& label, int imageId )
|
|
{
|
|
wxListItem info;
|
|
info.m_text = label;
|
|
info.m_mask = wxLIST_MASK_TEXT;
|
|
info.m_itemId = index;
|
|
info.m_col = col;
|
|
if ( imageId > -1 )
|
|
{
|
|
info.m_image = imageId;
|
|
info.m_mask |= wxLIST_MASK_IMAGE;
|
|
}
|
|
|
|
m_mainWin->SetItem(info);
|
|
return true;
|
|
}
|
|
|
|
int wxGenericListCtrl::GetItemState( long item, long stateMask ) const
|
|
{
|
|
return m_mainWin->GetItemState( item, stateMask );
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetItemState( long item, long state, long stateMask )
|
|
{
|
|
m_mainWin->SetItemState( item, state, stateMask );
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
wxGenericListCtrl::SetItemImage( long item, int image, int WXUNUSED(selImage) )
|
|
{
|
|
return SetItemColumnImage(item, 0, image);
|
|
}
|
|
|
|
bool
|
|
wxGenericListCtrl::SetItemColumnImage( long item, long column, int image )
|
|
{
|
|
wxListItem info;
|
|
info.m_image = image;
|
|
info.m_mask = wxLIST_MASK_IMAGE;
|
|
info.m_itemId = item;
|
|
info.m_col = column;
|
|
m_mainWin->SetItem( info );
|
|
return true;
|
|
}
|
|
|
|
wxString wxGenericListCtrl::GetItemText( long item, int col ) const
|
|
{
|
|
return m_mainWin->GetItemText(item, col);
|
|
}
|
|
|
|
void wxGenericListCtrl::SetItemText( long item, const wxString& str )
|
|
{
|
|
m_mainWin->SetItemText(item, str);
|
|
}
|
|
|
|
wxUIntPtr wxGenericListCtrl::GetItemData( long item ) const
|
|
{
|
|
wxListItem info;
|
|
info.m_mask = wxLIST_MASK_DATA;
|
|
info.m_itemId = item;
|
|
m_mainWin->GetItem( info );
|
|
return info.m_data;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetItemPtrData( long item, wxUIntPtr data )
|
|
{
|
|
wxListItem info;
|
|
info.m_mask = wxLIST_MASK_DATA;
|
|
info.m_itemId = item;
|
|
info.m_data = data;
|
|
m_mainWin->SetItem( info );
|
|
return true;
|
|
}
|
|
|
|
wxRect wxGenericListCtrl::GetViewRect() const
|
|
{
|
|
return m_mainWin->GetViewRect();
|
|
}
|
|
|
|
bool wxGenericListCtrl::GetItemRect(long item, wxRect& rect, int code) const
|
|
{
|
|
return GetSubItemRect(item, wxLIST_GETSUBITEMRECT_WHOLEITEM, rect, code);
|
|
}
|
|
|
|
bool wxGenericListCtrl::GetSubItemRect(long item,
|
|
long subItem,
|
|
wxRect& rect,
|
|
int WXUNUSED(code)) const
|
|
{
|
|
if ( !m_mainWin->GetSubItemRect( item, subItem, rect ) )
|
|
return false;
|
|
|
|
if ( m_mainWin->HasHeader() )
|
|
rect.y += m_headerWin->GetSize().y + 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::GetItemPosition( long item, wxPoint& pos ) const
|
|
{
|
|
m_mainWin->GetItemPosition( item, pos );
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetItemPosition( long WXUNUSED(item), const wxPoint& WXUNUSED(pos) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int wxGenericListCtrl::GetItemCount() const
|
|
{
|
|
return m_mainWin->GetItemCount();
|
|
}
|
|
|
|
int wxGenericListCtrl::GetColumnCount() const
|
|
{
|
|
return m_mainWin->GetColumnCount();
|
|
}
|
|
|
|
void wxGenericListCtrl::SetItemSpacing( int spacing, bool isSmall )
|
|
{
|
|
m_mainWin->SetItemSpacing( spacing, isSmall );
|
|
}
|
|
|
|
wxSize wxGenericListCtrl::GetItemSpacing() const
|
|
{
|
|
const int spacing = m_mainWin->GetItemSpacing(HasFlag(wxLC_SMALL_ICON));
|
|
|
|
return wxSize(spacing, spacing);
|
|
}
|
|
|
|
#if WXWIN_COMPATIBILITY_2_6
|
|
int wxGenericListCtrl::GetItemSpacing( bool isSmall ) const
|
|
{
|
|
return m_mainWin->GetItemSpacing( isSmall );
|
|
}
|
|
#endif // WXWIN_COMPATIBILITY_2_6
|
|
|
|
void wxGenericListCtrl::SetItemTextColour( long item, const wxColour &col )
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
info.SetTextColour( col );
|
|
m_mainWin->SetItem( info );
|
|
}
|
|
|
|
wxColour wxGenericListCtrl::GetItemTextColour( long item ) const
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
m_mainWin->GetItem( info );
|
|
return info.GetTextColour();
|
|
}
|
|
|
|
void wxGenericListCtrl::SetItemBackgroundColour( long item, const wxColour &col )
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
info.SetBackgroundColour( col );
|
|
m_mainWin->SetItem( info );
|
|
}
|
|
|
|
wxColour wxGenericListCtrl::GetItemBackgroundColour( long item ) const
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
m_mainWin->GetItem( info );
|
|
return info.GetBackgroundColour();
|
|
}
|
|
|
|
void wxGenericListCtrl::SetItemFont( long item, const wxFont &f )
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
info.SetFont( f );
|
|
m_mainWin->SetItem( info );
|
|
}
|
|
|
|
wxFont wxGenericListCtrl::GetItemFont( long item ) const
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
m_mainWin->GetItem( info );
|
|
return info.GetFont();
|
|
}
|
|
|
|
int wxGenericListCtrl::GetSelectedItemCount() const
|
|
{
|
|
return m_mainWin->GetSelectedItemCount();
|
|
}
|
|
|
|
wxColour wxGenericListCtrl::GetTextColour() const
|
|
{
|
|
return GetForegroundColour();
|
|
}
|
|
|
|
void wxGenericListCtrl::SetTextColour(const wxColour& col)
|
|
{
|
|
SetForegroundColour(col);
|
|
}
|
|
|
|
long wxGenericListCtrl::GetTopItem() const
|
|
{
|
|
size_t top;
|
|
m_mainWin->GetVisibleLinesRange(&top, NULL);
|
|
return (long)top;
|
|
}
|
|
|
|
long wxGenericListCtrl::GetNextItem( long item, int geom, int state ) const
|
|
{
|
|
return m_mainWin->GetNextItem( item, geom, state );
|
|
}
|
|
|
|
wxImageList *wxGenericListCtrl::GetImageList(int which) const
|
|
{
|
|
if (which == wxIMAGE_LIST_NORMAL)
|
|
return m_imageListNormal;
|
|
else if (which == wxIMAGE_LIST_SMALL)
|
|
return m_imageListSmall;
|
|
else if (which == wxIMAGE_LIST_STATE)
|
|
return m_imageListState;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void wxGenericListCtrl::SetImageList( wxImageList *imageList, int which )
|
|
{
|
|
if ( which == wxIMAGE_LIST_NORMAL )
|
|
{
|
|
if (m_ownsImageListNormal)
|
|
delete m_imageListNormal;
|
|
m_imageListNormal = imageList;
|
|
m_ownsImageListNormal = false;
|
|
}
|
|
else if ( which == wxIMAGE_LIST_SMALL )
|
|
{
|
|
if (m_ownsImageListSmall)
|
|
delete m_imageListSmall;
|
|
m_imageListSmall = imageList;
|
|
m_ownsImageListSmall = false;
|
|
}
|
|
else if ( which == wxIMAGE_LIST_STATE )
|
|
{
|
|
if (m_ownsImageListState)
|
|
delete m_imageListState;
|
|
m_imageListState = imageList;
|
|
m_ownsImageListState = false;
|
|
}
|
|
|
|
m_mainWin->SetImageList( imageList, which );
|
|
}
|
|
|
|
void wxGenericListCtrl::AssignImageList(wxImageList *imageList, int which)
|
|
{
|
|
SetImageList(imageList, which);
|
|
if ( which == wxIMAGE_LIST_NORMAL )
|
|
m_ownsImageListNormal = true;
|
|
else if ( which == wxIMAGE_LIST_SMALL )
|
|
m_ownsImageListSmall = true;
|
|
else if ( which == wxIMAGE_LIST_STATE )
|
|
m_ownsImageListState = true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::Arrange( int WXUNUSED(flag) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool wxGenericListCtrl::DeleteItem( long item )
|
|
{
|
|
m_mainWin->DeleteItem( item );
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::DeleteAllItems()
|
|
{
|
|
m_mainWin->DeleteAllItems();
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::DeleteAllColumns()
|
|
{
|
|
size_t count = m_mainWin->m_columns.GetCount();
|
|
for ( size_t n = 0; n < count; n++ )
|
|
DeleteColumn( 0 );
|
|
return true;
|
|
}
|
|
|
|
void wxGenericListCtrl::ClearAll()
|
|
{
|
|
m_mainWin->DeleteEverything();
|
|
}
|
|
|
|
bool wxGenericListCtrl::DeleteColumn( int col )
|
|
{
|
|
m_mainWin->DeleteColumn( col );
|
|
|
|
// if we don't have the header any longer, we need to relayout the window
|
|
// if ( !GetColumnCount() )
|
|
|
|
|
|
// Ensure that the non-existent columns are really removed from display.
|
|
Refresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
wxTextCtrl *wxGenericListCtrl::EditLabel(long item,
|
|
wxClassInfo* textControlClass)
|
|
{
|
|
return m_mainWin->EditLabel( item, textControlClass );
|
|
}
|
|
|
|
wxTextCtrl *wxGenericListCtrl::GetEditControl() const
|
|
{
|
|
return m_mainWin->GetEditControl();
|
|
}
|
|
|
|
bool wxGenericListCtrl::EnsureVisible( long item )
|
|
{
|
|
m_mainWin->EnsureVisible( item );
|
|
return true;
|
|
}
|
|
|
|
long wxGenericListCtrl::FindItem( long start, const wxString& str, bool partial )
|
|
{
|
|
return m_mainWin->FindItem( start, str, partial );
|
|
}
|
|
|
|
long wxGenericListCtrl::FindItem( long start, wxUIntPtr data )
|
|
{
|
|
return m_mainWin->FindItem( start, data );
|
|
}
|
|
|
|
long wxGenericListCtrl::FindItem( long WXUNUSED(start), const wxPoint& pt,
|
|
int WXUNUSED(direction))
|
|
{
|
|
return m_mainWin->FindItem( pt );
|
|
}
|
|
|
|
// TODO: sub item hit testing
|
|
long wxGenericListCtrl::HitTest(const wxPoint& point, int& flags, long *) const
|
|
{
|
|
return m_mainWin->HitTest( (int)point.x, (int)point.y, flags );
|
|
}
|
|
|
|
long wxGenericListCtrl::InsertItem( wxListItem& info )
|
|
{
|
|
m_mainWin->InsertItem( info );
|
|
return info.m_itemId;
|
|
}
|
|
|
|
long wxGenericListCtrl::InsertItem( long index, const wxString &label )
|
|
{
|
|
wxListItem info;
|
|
info.m_text = label;
|
|
info.m_mask = wxLIST_MASK_TEXT;
|
|
info.m_itemId = index;
|
|
return InsertItem( info );
|
|
}
|
|
|
|
long wxGenericListCtrl::InsertItem( long index, int imageIndex )
|
|
{
|
|
wxListItem info;
|
|
info.m_mask = wxLIST_MASK_IMAGE;
|
|
info.m_image = imageIndex;
|
|
info.m_itemId = index;
|
|
return InsertItem( info );
|
|
}
|
|
|
|
long wxGenericListCtrl::InsertItem( long index, const wxString &label, int imageIndex )
|
|
{
|
|
wxListItem info;
|
|
info.m_text = label;
|
|
info.m_image = imageIndex;
|
|
info.m_mask = wxLIST_MASK_TEXT;
|
|
if (imageIndex > -1)
|
|
info.m_mask |= wxLIST_MASK_IMAGE;
|
|
info.m_itemId = index;
|
|
return InsertItem( info );
|
|
}
|
|
|
|
long wxGenericListCtrl::DoInsertColumn( long col, const wxListItem &item )
|
|
{
|
|
wxCHECK_MSG( InReportView(), -1, wxT("can't add column in non report mode") );
|
|
|
|
m_mainWin->InsertColumn( col, item );
|
|
|
|
// NOTE: if wxLC_NO_HEADER was given, then we are in report view mode but
|
|
// still have m_headerWin==NULL
|
|
if (m_headerWin)
|
|
m_headerWin->Refresh();
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool wxGenericListCtrl::ScrollList( int dx, int dy )
|
|
{
|
|
return m_mainWin->ScrollList(dx, dy);
|
|
}
|
|
|
|
// Sort items.
|
|
// fn is a function which takes 3 long arguments: item1, item2, data.
|
|
// item1 is the long data associated with a first item (NOT the index).
|
|
// item2 is the long data associated with a second item (NOT the index).
|
|
// data is the same value as passed to SortItems.
|
|
// The return value is a negative number if the first item should precede the second
|
|
// item, a positive number of the second item should precede the first,
|
|
// or zero if the two items are equivalent.
|
|
// data is arbitrary data to be passed to the sort function.
|
|
|
|
bool wxGenericListCtrl::SortItems( wxListCtrlCompare fn, wxIntPtr data )
|
|
{
|
|
m_mainWin->SortItems( fn, data );
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// event handlers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxGenericListCtrl::OnSize(wxSizeEvent& WXUNUSED(event))
|
|
{
|
|
if (!m_mainWin) return;
|
|
|
|
// We need to override OnSize so that our scrolled
|
|
// window a) does call Layout() to use sizers for
|
|
// positioning the controls but b) does not query
|
|
// the sizer for their size and use that for setting
|
|
// the scrollable area as set that ourselves by
|
|
// calling SetScrollbar() further down.
|
|
|
|
Layout();
|
|
|
|
m_mainWin->RecalculatePositions();
|
|
|
|
AdjustScrollbars();
|
|
}
|
|
|
|
void wxGenericListCtrl::OnInternalIdle()
|
|
{
|
|
wxWindow::OnInternalIdle();
|
|
|
|
if (m_mainWin->m_dirty)
|
|
m_mainWin->RecalculatePositions();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// font/colours
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxGenericListCtrl::SetBackgroundColour( const wxColour &colour )
|
|
{
|
|
if (m_mainWin)
|
|
{
|
|
m_mainWin->SetBackgroundColour( colour );
|
|
m_mainWin->m_dirty = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetForegroundColour( const wxColour &colour )
|
|
{
|
|
if ( !wxWindow::SetForegroundColour( colour ) )
|
|
return false;
|
|
|
|
if (m_mainWin)
|
|
{
|
|
m_mainWin->SetForegroundColour( colour );
|
|
m_mainWin->m_dirty = true;
|
|
}
|
|
|
|
if (m_headerWin)
|
|
m_headerWin->SetForegroundColour( colour );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetFont( const wxFont &font )
|
|
{
|
|
if ( !wxWindow::SetFont( font ) )
|
|
return false;
|
|
|
|
if (m_mainWin)
|
|
{
|
|
m_mainWin->SetFont( font );
|
|
m_mainWin->m_dirty = true;
|
|
}
|
|
|
|
if (m_headerWin)
|
|
{
|
|
m_headerWin->SetFont( font );
|
|
// CalculateAndSetHeaderHeight();
|
|
}
|
|
|
|
Refresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
wxVisualAttributes
|
|
wxGenericListCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
|
|
{
|
|
#if _USE_VISATTR
|
|
// Use the same color scheme as wxListBox
|
|
return wxListBox::GetClassDefaultAttributes(variant);
|
|
#else
|
|
wxUnusedVar(variant);
|
|
wxVisualAttributes attr;
|
|
attr.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT);
|
|
attr.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX);
|
|
attr.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|
return attr;
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// methods forwarded to m_mainWin
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#if wxUSE_DRAG_AND_DROP
|
|
|
|
void wxGenericListCtrl::SetDropTarget( wxDropTarget *dropTarget )
|
|
{
|
|
m_mainWin->SetDropTarget( dropTarget );
|
|
}
|
|
|
|
wxDropTarget *wxGenericListCtrl::GetDropTarget() const
|
|
{
|
|
return m_mainWin->GetDropTarget();
|
|
}
|
|
|
|
#endif
|
|
|
|
bool wxGenericListCtrl::SetCursor( const wxCursor &cursor )
|
|
{
|
|
return m_mainWin ? m_mainWin->wxWindow::SetCursor(cursor) : false;
|
|
}
|
|
|
|
wxColour wxGenericListCtrl::GetBackgroundColour() const
|
|
{
|
|
return m_mainWin ? m_mainWin->GetBackgroundColour() : wxColour();
|
|
}
|
|
|
|
wxColour wxGenericListCtrl::GetForegroundColour() const
|
|
{
|
|
return m_mainWin ? m_mainWin->GetForegroundColour() : wxColour();
|
|
}
|
|
|
|
bool wxGenericListCtrl::DoPopupMenu( wxMenu *menu, int x, int y )
|
|
{
|
|
#if wxUSE_MENUS
|
|
return m_mainWin->PopupMenu( menu, x, y );
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void wxGenericListCtrl::DoClientToScreen( int *x, int *y ) const
|
|
{
|
|
// It's not clear whether this can be called before m_mainWin is created
|
|
// but it seems better to be on the safe side and check.
|
|
if ( m_mainWin )
|
|
m_mainWin->DoClientToScreen(x, y);
|
|
else
|
|
wxListCtrlBase::DoClientToScreen(x, y);
|
|
}
|
|
|
|
void wxGenericListCtrl::DoScreenToClient( int *x, int *y ) const
|
|
{
|
|
// At least in wxGTK/Univ build this method can be called before m_mainWin
|
|
// is created so avoid crashes in this case.
|
|
if ( m_mainWin )
|
|
m_mainWin->DoScreenToClient(x, y);
|
|
else
|
|
wxListCtrlBase::DoScreenToClient(x, y);
|
|
}
|
|
|
|
wxSize wxGenericListCtrl::DoGetBestClientSize() const
|
|
{
|
|
// Something is better than nothing even if this is completely arbitrary.
|
|
wxSize sizeBest(100, 80);
|
|
|
|
if ( !InReportView() )
|
|
{
|
|
// Ensure that our minimal width is at least big enough to show all our
|
|
// items. This is important for wxListbook to size itself correctly.
|
|
|
|
// Remember the offset of the first item: this corresponds to the
|
|
// margins around the item so we will add it to the minimal size below
|
|
// to ensure that we have equal margins on all sides.
|
|
wxPoint ofs;
|
|
|
|
// We can iterate over all items as there shouldn't be too many of them
|
|
// in non-report view. If it ever becomes a problem, we could examine
|
|
// just the first few items probably, the determination of the best
|
|
// size is less important if we will need scrollbars anyhow.
|
|
for ( int n = 0; n < GetItemCount(); n++ )
|
|
{
|
|
const wxRect itemRect = m_mainWin->GetLineRect(n);
|
|
if ( !n )
|
|
{
|
|
// Remember the position of the first item as all the rest are
|
|
// offset by at least this number of pixels too.
|
|
ofs = itemRect.GetPosition();
|
|
}
|
|
|
|
sizeBest.IncTo(itemRect.GetSize());
|
|
}
|
|
|
|
sizeBest.IncBy(2*ofs);
|
|
|
|
|
|
// If we have the scrollbars we need to account for them too. And to
|
|
// make sure the scrollbars status is up to date we need to call this
|
|
// function to set them.
|
|
m_mainWin->RecalculatePositions(true /* no refresh */);
|
|
|
|
// Unfortunately we can't use wxWindow::HasScrollbar() here as we need
|
|
// to use m_mainWin client/virtual size for determination of whether we
|
|
// use scrollbars and not the size of this window itself. Maybe that
|
|
// function should be extended to work correctly in the case when our
|
|
// scrollbars manage a different window from this one but currently it
|
|
// doesn't work.
|
|
const wxSize sizeClient = m_mainWin->GetClientSize();
|
|
const wxSize sizeVirt = m_mainWin->GetVirtualSize();
|
|
|
|
if ( sizeVirt.x > sizeClient.x /* HasScrollbar(wxHORIZONTAL) */ )
|
|
sizeBest.y += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y);
|
|
|
|
if ( sizeVirt.y > sizeClient.y /* HasScrollbar(wxVERTICAL) */ )
|
|
sizeBest.x += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
|
|
}
|
|
|
|
return sizeBest;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// virtual list control support
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxString wxGenericListCtrl::OnGetItemText(long WXUNUSED(item), long WXUNUSED(col)) const
|
|
{
|
|
// this is a pure virtual function, in fact - which is not really pure
|
|
// because the controls which are not virtual don't need to implement it
|
|
wxFAIL_MSG( wxT("wxGenericListCtrl::OnGetItemText not supposed to be called") );
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
int wxGenericListCtrl::OnGetItemImage(long WXUNUSED(item)) const
|
|
{
|
|
wxCHECK_MSG(!GetImageList(wxIMAGE_LIST_SMALL),
|
|
-1,
|
|
wxT("List control has an image list, OnGetItemImage or OnGetItemColumnImage should be overridden."));
|
|
return -1;
|
|
}
|
|
|
|
int wxGenericListCtrl::OnGetItemColumnImage(long item, long column) const
|
|
{
|
|
if (!column)
|
|
return OnGetItemImage(item);
|
|
|
|
return -1;
|
|
}
|
|
|
|
wxListItemAttr *
|
|
wxGenericListCtrl::OnGetItemAttr(long WXUNUSED_UNLESS_DEBUG(item)) const
|
|
{
|
|
wxASSERT_MSG( item >= 0 && item < GetItemCount(),
|
|
wxT("invalid item index in OnGetItemAttr()") );
|
|
|
|
// no attributes by default
|
|
return NULL;
|
|
}
|
|
|
|
void wxGenericListCtrl::SetItemCount(long count)
|
|
{
|
|
wxASSERT_MSG( IsVirtual(), wxT("this is for virtual controls only") );
|
|
|
|
m_mainWin->SetItemCount(count);
|
|
}
|
|
|
|
void wxGenericListCtrl::RefreshItem(long item)
|
|
{
|
|
m_mainWin->RefreshLine(item);
|
|
}
|
|
|
|
void wxGenericListCtrl::RefreshItems(long itemFrom, long itemTo)
|
|
{
|
|
m_mainWin->RefreshLines(itemFrom, itemTo);
|
|
}
|
|
|
|
// Generic wxListCtrl is more or less a container for two other
|
|
// windows which drawings are done upon. These are namely
|
|
// 'm_headerWin' and 'm_mainWin'.
|
|
// Here we override 'virtual wxWindow::Refresh()' to mimic the
|
|
// behaviour wxListCtrl has under wxMSW.
|
|
//
|
|
void wxGenericListCtrl::Refresh(bool eraseBackground, const wxRect *rect)
|
|
{
|
|
if (!rect)
|
|
{
|
|
// The easy case, no rectangle specified.
|
|
if (m_headerWin)
|
|
m_headerWin->Refresh(eraseBackground);
|
|
|
|
if (m_mainWin)
|
|
m_mainWin->Refresh(eraseBackground);
|
|
}
|
|
else
|
|
{
|
|
// Refresh the header window
|
|
if (m_headerWin)
|
|
{
|
|
wxRect rectHeader = m_headerWin->GetRect();
|
|
rectHeader.Intersect(*rect);
|
|
if (rectHeader.GetWidth() && rectHeader.GetHeight())
|
|
{
|
|
int x, y;
|
|
m_headerWin->GetPosition(&x, &y);
|
|
rectHeader.Offset(-x, -y);
|
|
m_headerWin->Refresh(eraseBackground, &rectHeader);
|
|
}
|
|
}
|
|
|
|
// Refresh the main window
|
|
if (m_mainWin)
|
|
{
|
|
wxRect rectMain = m_mainWin->GetRect();
|
|
rectMain.Intersect(*rect);
|
|
if (rectMain.GetWidth() && rectMain.GetHeight())
|
|
{
|
|
int x, y;
|
|
m_mainWin->GetPosition(&x, &y);
|
|
rectMain.Offset(-x, -y);
|
|
m_mainWin->Refresh(eraseBackground, &rectMain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxGenericListCtrl::Update()
|
|
{
|
|
if ( m_mainWin )
|
|
{
|
|
if ( m_mainWin->m_dirty )
|
|
m_mainWin->RecalculatePositions();
|
|
|
|
m_mainWin->Update();
|
|
}
|
|
|
|
if ( m_headerWin )
|
|
m_headerWin->Update();
|
|
}
|
|
|
|
#endif // wxUSE_LISTCTRL
|