Set width of the new columns to wxLIST_DEFAULT_COL_WIDTH and not 0 in the generic version: this is more compatible with the MSW version and more useful. Document that omitting list column width in XRC results in columns of default, rather than null, as previously, width.
5906 lines
166 KiB
C++
5906 lines
166 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/generic/listctrl.cpp
|
|
// Purpose: generic implementation of wxListCtrl
|
|
// Author: Robert Roebling
|
|
// Vadim Zeitlin (virtual list control support)
|
|
// 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"
|
|
|
|
|
|
#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"
|
|
#include "wx/generic/private/widthcalc.h"
|
|
|
|
#ifdef __WXMAC__
|
|
#include "wx/osx/private.h"
|
|
#endif
|
|
|
|
#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
|
|
#include "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
|
|
// ----------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
#ifdef __WXGTK__
|
|
// This probably needs to be done
|
|
// on all platforms as the icons
|
|
// otherwise nearly touch the border
|
|
static const int ICON_OFFSET_X = 2;
|
|
#else
|
|
static const int ICON_OFFSET_X = 0;
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// space after a checkbox
|
|
static const int MARGIN_AROUND_CHECKBOX = 5;
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// arrays/list implementations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "wx/listimpl.cpp"
|
|
WX_DEFINE_LIST(wxListItemDataList)
|
|
|
|
#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;
|
|
}
|
|
|
|
// Check if the item is visible
|
|
bool wxGenericListCtrl::IsVisible(long item) const
|
|
{
|
|
wxRect itemRect;
|
|
GetItemRect( item, itemRect );
|
|
const wxRect clientRect = GetClientRect();
|
|
bool visible = clientRect.Intersects( itemRect );
|
|
if ( visible && m_headerWin )
|
|
{
|
|
wxRect headerRect = m_headerWin->GetClientRect();
|
|
// take into account the +1 added in GetSubItemRect()
|
|
headerRect.height++;
|
|
visible = itemRect.GetBottom() > headerRect.GetBottom();
|
|
}
|
|
return visible;
|
|
}
|
|
|
|
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 wxItemAttr(*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;
|
|
|
|
// Always give some initial width to the new columns (it's still possible
|
|
// to set the width to 0 explicitly, however).
|
|
SetWidth(m_mask & wxLIST_MASK_WIDTH ? item.m_width : wxLIST_DEFAULT_COL_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 ? wxLIST_DEFAULT_COL_WIDTH : 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;
|
|
m_checked = 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_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 ) const
|
|
{
|
|
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();
|
|
}
|
|
|
|
wxItemAttr *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(wxItemAttr *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 wxItemAttr * const attr = GetAttr();
|
|
|
|
wxWindow * const listctrl = m_owner->GetParent();
|
|
|
|
const bool hasFocus = listctrl->HasFocus();
|
|
|
|
// 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
|
|
|
|
// Note: GetSubItemRect() needs to be modified if the layout here changes.
|
|
|
|
ApplyAttributes(dc, rectHL, highlighted, current);
|
|
|
|
wxCoord x = rect.x + HEADER_OFFSET_X + ICON_OFFSET_X,
|
|
yMid = rect.y + rect.height/2;
|
|
|
|
if ( m_owner->HasCheckBoxes() )
|
|
{
|
|
wxSize cbSize = wxRendererNative::Get().GetCheckBoxSize(m_owner);
|
|
int yOffset = (rect.height - cbSize.GetHeight()) / 2;
|
|
wxRect rr(wxPoint(x, rect.y + yOffset), cbSize);
|
|
rr.x += MARGIN_AROUND_CHECKBOX;
|
|
|
|
int flags = 0;
|
|
if (m_checked)
|
|
flags |= wxCONTROL_CHECKED;
|
|
wxRendererNative::Get().DrawCheckBox(m_owner, *dc, rr, flags);
|
|
|
|
x += cbSize.GetWidth() + (2 * MARGIN_AROUND_CHECKBOX);
|
|
}
|
|
|
|
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);
|
|
if (col == 0 && m_owner->HasCheckBoxes())
|
|
width -= x;
|
|
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;
|
|
m_owner->UpdateSelectionCount(on);
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListLineData::ReverseHighlight( void )
|
|
{
|
|
Highlight(!IsHighlighted());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListHeaderWindow
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxBEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow)
|
|
EVT_PAINT (wxListHeaderWindow::OnPaint)
|
|
EVT_MOUSE_EVENTS (wxListHeaderWindow::OnMouse)
|
|
wxEND_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;
|
|
}
|
|
|
|
bool wxListHeaderWindow::Create( wxWindow *win,
|
|
wxWindowID id,
|
|
wxListMainWindow *owner,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxString &name )
|
|
{
|
|
if ( !wxWindow::Create(win, id, pos, size, style, name) )
|
|
return false;
|
|
|
|
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
|
|
|
|
return true;
|
|
}
|
|
|
|
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
|
|
#if defined(__WXGTK__) && !defined(__WXGTK3__)
|
|
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") );
|
|
wxFALLTHROUGH;
|
|
|
|
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()
|
|
{
|
|
m_sendSetColumnWidth = true;
|
|
m_colToSend = m_column;
|
|
m_widthToSend = m_currentX - m_minX;
|
|
}
|
|
|
|
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);
|
|
|
|
if (m_isDragging)
|
|
{
|
|
SendListEvent(wxEVT_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_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 )
|
|
{
|
|
// 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_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_LIST_COL_CLICK
|
|
: wxEVT_LIST_COL_RIGHT_CLICK,
|
|
event.GetPosition());
|
|
}
|
|
}
|
|
else if ( event.LeftDClick() && hit_border )
|
|
{
|
|
// Autosize the column when the divider is clicked: if there are
|
|
// any items, fit the columns to its contents, otherwise just fit
|
|
// it to its label width.
|
|
parent->SetColumnWidth(m_column,
|
|
parent->IsEmpty() ? wxLIST_AUTOSIZE_USEHEADER
|
|
: wxLIST_AUTOSIZE);
|
|
}
|
|
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;
|
|
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();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListFindTimer (internal)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void wxListFindTimer::Notify()
|
|
{
|
|
m_owner->OnFindTimer();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListTextCtrlWrapper (internal)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxBEGIN_EVENT_TABLE(wxListTextCtrlWrapper, wxEvtHandler)
|
|
EVT_CHAR (wxListTextCtrlWrapper::OnChar)
|
|
EVT_KEY_UP (wxListTextCtrlWrapper::OnKeyUp)
|
|
EVT_KILL_FOCUS (wxListTextCtrlWrapper::OnKillFocus)
|
|
wxEND_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)
|
|
{
|
|
if( m_aboutToFinish )
|
|
{
|
|
// We already called Finish which cannot be called
|
|
// more than once.
|
|
return;
|
|
}
|
|
|
|
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 )
|
|
{
|
|
m_aboutToFinish = true;
|
|
if ( !AcceptChanges() )
|
|
m_owner->OnRenameCancelled( m_itemEdited );
|
|
|
|
Finish( false );
|
|
}
|
|
|
|
// We must let the native text control handle focus
|
|
event.Skip();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxListMainWindow
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxBEGIN_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)
|
|
wxEND_EVENT_TABLE()
|
|
|
|
void wxListMainWindow::Init()
|
|
{
|
|
m_dirty = true;
|
|
m_selCount =
|
|
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_findTimer = NULL;
|
|
m_findBell = 0; // default is to not ring bell at all
|
|
m_textctrlWrapper = NULL;
|
|
|
|
m_current =
|
|
m_lineLastClicked =
|
|
m_lineSelectSingleOnUp =
|
|
m_lineBeforeLastClicked =
|
|
m_anchor = (size_t)-1;
|
|
|
|
m_hasCheckBoxes = false;
|
|
m_extendRulesAndAlternateColour = false;
|
|
}
|
|
|
|
wxListMainWindow::wxListMainWindow()
|
|
{
|
|
Init();
|
|
|
|
m_highlightBrush =
|
|
m_highlightUnfocusedBrush = NULL;
|
|
}
|
|
|
|
wxListMainWindow::wxListMainWindow( wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size )
|
|
: wxWindow( parent, id, pos, size,
|
|
wxWANTS_CHARS | wxBORDER_NONE )
|
|
{
|
|
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;
|
|
delete m_findTimer;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
if ( HasCheckBoxes() )
|
|
{
|
|
ld->Check(listctrl->OnGetItemIsChecked(line));
|
|
}
|
|
|
|
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.empty() &&
|
|
m_lines[0]->m_items.GetCount() != (size_t)GetColumnCount() )
|
|
{
|
|
self->m_lines.Clear();
|
|
}
|
|
|
|
if ( m_lines.empty() )
|
|
{
|
|
wxListLineData *line = new wxListLineData(self);
|
|
self->m_lines.push_back(line);
|
|
|
|
// don't waste extra memory -- there never going to be anything
|
|
// else/more in this array
|
|
wxShrinkToFit(self->m_lines);
|
|
}
|
|
|
|
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,
|
|
SendEvent sendEvent )
|
|
{
|
|
// It is safe to swap the bounds here if they are not in order.
|
|
if ( lineFrom > lineTo )
|
|
wxSwap(lineFrom, lineTo);
|
|
|
|
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, sendEvent) )
|
|
RefreshLine(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool wxListMainWindow::HighlightLine( size_t line, bool highlight, SendEvent sendEvent )
|
|
{
|
|
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 && sendEvent )
|
|
{
|
|
SendNotify( line, highlight ? wxEVT_LIST_ITEM_SELECTED
|
|
: wxEVT_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 > visibleTo || lineTo < visibleFrom )
|
|
{
|
|
// None of these lines are currently visible at all, don't bother
|
|
// doing anything.
|
|
return;
|
|
}
|
|
|
|
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 )
|
|
{
|
|
// Calling Refresh() from inside OnPaint() doesn't work under macOS, so
|
|
// don't do it immediately...
|
|
RecalculatePositions();
|
|
|
|
// ... but schedule it for later.
|
|
CallAfter(&wxWindow::Refresh, true, (const wxRect*)NULL);
|
|
|
|
// Don't bother redoing the relayout again the next time nor redrawing
|
|
// now, as we'll be refresh soon anyhow.
|
|
m_dirty = false;
|
|
return;
|
|
}
|
|
|
|
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;
|
|
const size_t linesPerPage = (unsigned int) m_linesPerPage;
|
|
GetVisibleLinesRange(&visibleFrom, &visibleTo);
|
|
|
|
// We may need to iterate beyond visibleTo if we want to draw striped
|
|
// background across the entire window.
|
|
size_t visibleEnd;
|
|
wxColour colAlt;
|
|
if ( m_extendRulesAndAlternateColour )
|
|
{
|
|
colAlt = GetListCtrl()->GetAlternateRowColour();
|
|
visibleEnd = wxMax(linesPerPage, visibleTo);
|
|
}
|
|
else
|
|
{
|
|
visibleEnd = 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_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 <= visibleEnd; 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;
|
|
}
|
|
|
|
if ( line > visibleTo )
|
|
{
|
|
// We only iterate beyond visibleTo when we have to draw the
|
|
// odd rows background, so do this if needed.
|
|
if ( line % 2 )
|
|
{
|
|
dc.SetBrush(colAlt);
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
dc.DrawRectangle(rectLine);
|
|
}
|
|
|
|
// But don't do anything else, as there is no valid item.
|
|
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 <= visibleEnd; 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 ( visibleEnd == 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) &&
|
|
(m_extendRulesAndAlternateColour || !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);
|
|
|
|
int clientHeight, clientWidth;
|
|
GetSize( &clientWidth, &clientHeight );
|
|
|
|
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;
|
|
|
|
int ruleHeight = m_extendRulesAndAlternateColour && visibleEnd > visibleTo
|
|
? clientHeight
|
|
: lastItemRect.GetBottom() + 1 - dev_y;
|
|
|
|
dc.DrawLine(x_pos, firstItemRect.GetY() - 1 - dev_y,
|
|
x_pos, ruleHeight);
|
|
}
|
|
}
|
|
}
|
|
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::HighlightOnly( size_t line, size_t oldLine )
|
|
{
|
|
const unsigned selCount = GetSelectedItemCount();
|
|
|
|
if ( selCount == 1 && IsHighlighted(line) )
|
|
{
|
|
return; // Nothing changed.
|
|
}
|
|
|
|
if ( oldLine != (size_t)-1 )
|
|
{
|
|
IsHighlighted(oldLine) ? ReverseHighlight(oldLine)
|
|
: RefreshLine(oldLine); // refresh the old focus to remove it
|
|
}
|
|
|
|
if ( selCount > 1 ) // multiple-selection only
|
|
{
|
|
// Deselecting many items at once will generate wxEVT_XXX_DESELECTED event
|
|
// for each one of them. although this may be inefficient if the number of
|
|
// deselected items is too much, we keep doing this (for non-virtual list
|
|
// controls) for backward compatibility concerns. For virtual listctrl (in
|
|
// multi-selection mode), wxMSW sends only a notification to indicate that
|
|
// something has been deselected. Notice that to be fully compatible with
|
|
// wxMSW behaviour, _line_ shouldn't be deselected if it was selected.
|
|
|
|
const SendEvent sendEvent = IsVirtual() ? SendEvent_None : SendEvent_Normal;
|
|
|
|
size_t lineFrom = 0,
|
|
lineTo = GetItemCount() - 1;
|
|
|
|
if ( line > lineFrom && line < lineTo )
|
|
{
|
|
HighlightLines(lineFrom, line - 1, false, sendEvent);
|
|
HighlightLines(line + 1, lineTo, false, sendEvent);
|
|
}
|
|
else // _line_ is equal to lineFrom or lineTo
|
|
{
|
|
line == lineFrom ? ++lineFrom : --lineTo;
|
|
HighlightLines(lineFrom, lineTo, false, sendEvent);
|
|
}
|
|
|
|
// If we didn't send the event for individual items above, send it for all of them now.
|
|
if ( sendEvent == SendEvent_None )
|
|
SendNotify((size_t)-1, wxEVT_LIST_ITEM_DESELECTED);
|
|
}
|
|
|
|
// _line_ should be the only selected item.
|
|
HighlightLine(line);
|
|
// refresh the new focus to add it.
|
|
RefreshLine(line);
|
|
}
|
|
|
|
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 )
|
|
{
|
|
// 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 window,
|
|
// so why should it get positions relative to it?
|
|
le.m_pointDrag = GetPosition() + point;
|
|
}
|
|
|
|
// provide information about the (first column of the) item in the event if
|
|
// we have a valid item and any columns at all
|
|
if ( line != (size_t)-1 && GetColumnCount() )
|
|
{
|
|
GetLine(line)->GetItem( 0, le.m_item );
|
|
}
|
|
|
|
GetParent()->GetEventHandler()->ProcessEvent( le );
|
|
}
|
|
|
|
bool wxListMainWindow::ChangeCurrentWithoutEvent(size_t current)
|
|
{
|
|
if ( current == m_current )
|
|
{
|
|
return false; // Nothing changed!
|
|
}
|
|
|
|
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();
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListMainWindow::ChangeCurrent(size_t current)
|
|
{
|
|
if ( ChangeCurrentWithoutEvent(current) )
|
|
SendNotify(current, wxEVT_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(wxCLASSINFO(wxTextCtrl)),
|
|
wxT("EditLabel() needs a text control") );
|
|
|
|
size_t itemEdit = (size_t)item;
|
|
|
|
wxListEvent le( wxEVT_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;
|
|
}
|
|
|
|
if ( m_dirty )
|
|
{
|
|
// Ensure the display is updated before we start editing.
|
|
Update();
|
|
}
|
|
|
|
wxTextCtrl * const text = (wxTextCtrl *)textControlClass->CreateObject();
|
|
m_textctrlWrapper = new wxListTextCtrlWrapper(this, text, item);
|
|
return m_textctrlWrapper->GetText();
|
|
}
|
|
|
|
bool wxListMainWindow::EndEditLabel(bool cancel)
|
|
{
|
|
if (!m_textctrlWrapper)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_textctrlWrapper->EndEdit(cancel ? wxListTextCtrlWrapper::End_Discard : wxListTextCtrlWrapper::End_Accept);
|
|
return true;
|
|
}
|
|
|
|
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_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_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::OnFindTimer()
|
|
{
|
|
m_findPrefix.clear();
|
|
if ( m_findBell )
|
|
m_findBell = 1;
|
|
}
|
|
|
|
void wxListMainWindow::EnableBellOnNoMatch( bool on )
|
|
{
|
|
m_findBell = on;
|
|
}
|
|
|
|
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() )
|
|
{
|
|
// Ensure we skip the event to let the system set focus to this window.
|
|
event.Skip();
|
|
}
|
|
|
|
// Pretend that the event happened in wxListCtrl itself.
|
|
wxMouseEvent me(event);
|
|
me.SetEventObject( GetParent() );
|
|
me.SetId(GetParent()->GetId());
|
|
if ( GetParent()->GetEventHandler()->ProcessEvent( me ))
|
|
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_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
|
|
|
|
wxContextMenuEvent evtCtx(wxEVT_CONTEXT_MENU,
|
|
GetParent()->GetId(),
|
|
ClientToScreen(event.GetPosition()));
|
|
evtCtx.SetEventObject(GetParent());
|
|
GetParent()->GetEventHandler()->ProcessEvent(evtCtx);
|
|
}
|
|
|
|
if ( IsEmpty() )
|
|
return;
|
|
|
|
// Continue processing...
|
|
}
|
|
|
|
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 events that can be generated without any valid item are
|
|
// wxEVT_LIST_ITEM_DESELECTED for virtual lists, and
|
|
// wxEVT_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())
|
|
{
|
|
wxContextMenuEvent evtCtx(
|
|
wxEVT_CONTEXT_MENU,
|
|
GetParent()->GetId(),
|
|
ClientToScreen(event.GetPosition()));
|
|
evtCtx.SetEventObject(GetParent());
|
|
GetParent()->GetEventHandler()->ProcessEvent(evtCtx);
|
|
}
|
|
else if (event.LeftDown())
|
|
{
|
|
// reset the selection and bail out
|
|
HighlightAll(false);
|
|
// generate a DESELECTED event for
|
|
// virtual multi-selection lists
|
|
if ( IsVirtual() && !IsSingleSel() )
|
|
SendNotify( m_lineLastClicked, wxEVT_LIST_ITEM_DESELECTED );
|
|
}
|
|
|
|
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_LIST_BEGIN_RDRAG
|
|
: wxEVT_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_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
|
|
HighlightOnly(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;
|
|
}
|
|
|
|
if ( GetSelectedItemCount() == 1 || event.CmdDown() )
|
|
{
|
|
// In multiple selection mode, the anchor is set to the first selected
|
|
// item or can be changed to m_current if Ctrl key is down, as is the
|
|
// case under wxMSW.
|
|
m_anchor = m_current;
|
|
}
|
|
|
|
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))
|
|
{
|
|
ChangeCurrent(current);
|
|
HighlightOnly(m_current);
|
|
}
|
|
|
|
SendNotify( current, wxEVT_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
|
|
|
|
// Allow generation of context menu event
|
|
event.Skip();
|
|
}
|
|
else if (event.MiddleDown())
|
|
{
|
|
SendNotify( current, wxEVT_LIST_ITEM_MIDDLE_CLICK );
|
|
}
|
|
else if ( event.LeftDown() || forceClick )
|
|
{
|
|
m_lineBeforeLastClicked = m_lineLastClicked;
|
|
m_lineLastClicked = current;
|
|
|
|
size_t oldCurrent = m_current;
|
|
bool oldWasSelected = HasCurrent() && IsHighlighted(m_current);
|
|
|
|
bool cmdModifierDown = event.CmdDown();
|
|
if ( IsSingleSel() || !(cmdModifierDown || event.ShiftDown()) )
|
|
{
|
|
if (IsInsideCheckBox(current, x, y))
|
|
{
|
|
CheckItem(current, !IsItemChecked(current));
|
|
}
|
|
else if (IsSingleSel() || !IsHighlighted(current))
|
|
{
|
|
ChangeCurrent(current);
|
|
HighlightOnly(m_current, oldWasSelected ? oldCurrent : (size_t)-1);
|
|
}
|
|
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);
|
|
|
|
if ( oldCurrent == (size_t)-1 )
|
|
{
|
|
// Highlight m_current only if there is no previous selection.
|
|
HighlightLine(m_current);
|
|
}
|
|
else if ( oldCurrent != current && m_anchor != (size_t)-1 )
|
|
{
|
|
ExtendSelection(oldCurrent, current);
|
|
}
|
|
}
|
|
else // !ctrl, !shift
|
|
{
|
|
// test in the enclosing if should make it impossible
|
|
wxFAIL_MSG( wxT("how did we get here?") );
|
|
}
|
|
}
|
|
|
|
if (m_current != oldCurrent && oldCurrent != (size_t)-1)
|
|
RefreshLine( oldCurrent );
|
|
|
|
// Set the flag telling us whether the next click on this item should
|
|
// start editing its label. This should happen if we clicked on the
|
|
// current item and it was already selected, i.e. if this click was not
|
|
// done to select it.
|
|
//
|
|
// It should not happen if this was a double click (forceClick is true)
|
|
// nor if we hadn't had the focus before as then this click was used to
|
|
// give focus to the control.
|
|
m_lastOnSame = (m_current == oldCurrent) && oldWasSelected &&
|
|
!forceClick && HasFocus();
|
|
}
|
|
}
|
|
|
|
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 );
|
|
|
|
#if defined(__WXMAC__) || defined(__WXUNIVERSAL__)
|
|
// 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
|
|
// Same in wxUniv/X11
|
|
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);
|
|
|
|
#if defined(__WXMAC__) || defined(__WXUNIVERSAL__)
|
|
// see comment in MoveToItem() for why we do this
|
|
ResetVisibleLinesRange();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// keyboard handling
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Helper function which handles items selection correctly and efficiently. and
|
|
// simply, mimic the wxMSW behaviour. And The benefit of this function is that it
|
|
// ensures that wxEVT_LIST_ITEM_{DE}SELECTED events are only generated for items
|
|
// freshly (de)selected (i.e. items that have really changed state). Useful for
|
|
// multi-selection mode when selecting using mouse or arrows with Shift key down.
|
|
void wxListMainWindow::ExtendSelection(size_t oldCurrent, size_t newCurrent)
|
|
{
|
|
// Refresh the old/new focus to remove/add it
|
|
RefreshLine(oldCurrent);
|
|
RefreshLine(newCurrent);
|
|
|
|
// Given a selection [anchor, old], to change/extend it to new (i.e. the
|
|
// selection becomes [anchor, new]) we discriminate three possible cases:
|
|
//
|
|
// Case 1) new < old <= anchor || anchor <= old < new
|
|
// i.e. oldCurrent between anchor and newCurrent, in which case we:
|
|
// - Highlight everything between anchor and newCurrent (inclusive).
|
|
//
|
|
// Case 2) old < new <= anchor || anchor <= new < old
|
|
// i.e. newCurrent between anchor and oldCurrent, in which case we:
|
|
// - Unhighlight everything between oldCurrent and newCurrent (exclusive).
|
|
//
|
|
// Case 3) old < anchor < new || new < anchor < old
|
|
// i.e. anchor between oldCurrent and newCurrent, in which case we
|
|
// - Highlight everything between anchor and newCurrent (inclusive).
|
|
// - Unhighlight everything between anchor (exclusive) and oldCurrent.
|
|
|
|
size_t lineFrom, lineTo;
|
|
|
|
if ( (newCurrent < oldCurrent && oldCurrent <= m_anchor) ||
|
|
(newCurrent > oldCurrent && oldCurrent >= m_anchor) )
|
|
{
|
|
lineFrom = m_anchor;
|
|
lineTo = newCurrent;
|
|
|
|
HighlightLines(lineFrom, lineTo);
|
|
}
|
|
else if ( (oldCurrent < newCurrent && newCurrent <= m_anchor) ||
|
|
(oldCurrent > newCurrent && newCurrent >= m_anchor) )
|
|
{
|
|
lineFrom = oldCurrent;
|
|
lineTo = newCurrent;
|
|
|
|
// Exclude newCurrent from being deselected
|
|
(lineTo < lineFrom) ? ++lineTo : --lineTo;
|
|
|
|
HighlightLines(lineFrom, lineTo, false);
|
|
|
|
// For virtual listctrl (in multi-selection mode), wxMSW sends only
|
|
// a notification to indicate that something has been deselected.
|
|
if ( IsVirtual() )
|
|
SendNotify((size_t)-1, wxEVT_LIST_ITEM_DESELECTED);
|
|
}
|
|
else if ( (oldCurrent < m_anchor && m_anchor < newCurrent) ||
|
|
(newCurrent < m_anchor && m_anchor < oldCurrent) )
|
|
{
|
|
lineFrom = m_anchor;
|
|
lineTo = oldCurrent;
|
|
|
|
// Exclude anchor from being deselected
|
|
(lineTo < lineFrom) ? --lineFrom : ++lineFrom;
|
|
|
|
HighlightLines(lineFrom, lineTo, false);
|
|
|
|
// See above.
|
|
if ( IsVirtual() )
|
|
SendNotify((size_t)-1, wxEVT_LIST_ITEM_DESELECTED);
|
|
|
|
lineFrom = m_anchor;
|
|
lineTo = newCurrent;
|
|
|
|
HighlightLines(lineFrom, lineTo);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
ChangeCurrent(newCurrent);
|
|
|
|
// in single selection we just ignore Shift as we can't select several
|
|
// items anyhow
|
|
if ( event.ShiftDown() && !IsSingleSel() )
|
|
{
|
|
ExtendSelection(oldCurrent, newCurrent);
|
|
}
|
|
else // !shift
|
|
{
|
|
// all previously selected items are unselected unless ctrl is held in
|
|
// a multi-selection control. in single selection mode we must always
|
|
// have a selected item.
|
|
if ( !event.ControlDown() || IsSingleSel() )
|
|
{
|
|
HighlightOnly(m_current, oldCurrent);
|
|
|
|
// Update anchor
|
|
m_anchor = m_current;
|
|
}
|
|
else
|
|
{
|
|
// refresh the old/new focus to remove/add it
|
|
RefreshLine(oldCurrent);
|
|
RefreshLine(m_current);
|
|
}
|
|
}
|
|
|
|
MoveToFocus();
|
|
}
|
|
|
|
void wxListMainWindow::OnKeyDown( wxKeyEvent &event )
|
|
{
|
|
wxWindow *parent = GetParent();
|
|
|
|
// propagate the key event upwards
|
|
wxKeyEvent ke(event);
|
|
ke.SetEventObject( parent );
|
|
ke.SetId(GetParent()->GetId());
|
|
if (parent->GetEventHandler()->ProcessEvent( ke ))
|
|
return;
|
|
|
|
// send a list event
|
|
wxListEvent le( wxEVT_LIST_KEY_DOWN, parent->GetId() );
|
|
const size_t current = ShouldSendEventForCurrent() ? m_current : (size_t)-1;
|
|
le.m_item.m_itemId =
|
|
le.m_itemIndex = current;
|
|
if ( current != (size_t)-1 )
|
|
GetLine(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);
|
|
ke.SetEventObject( parent );
|
|
ke.SetId(GetParent()->GetId());
|
|
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 );
|
|
ke.SetId(GetParent()->GetId());
|
|
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;
|
|
}
|
|
|
|
int keyCode = event.GetKeyCode();
|
|
switch ( keyCode )
|
|
{
|
|
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 if ( ShouldSendEventForCurrent() ) // normal space press
|
|
{
|
|
SendNotify( m_current, wxEVT_LIST_ITEM_ACTIVATED );
|
|
}
|
|
}
|
|
else // multiple selection
|
|
{
|
|
ReverseHighlight(m_current);
|
|
}
|
|
break;
|
|
|
|
case WXK_RETURN:
|
|
case WXK_EXECUTE:
|
|
if ( event.HasModifiers() || !ShouldSendEventForCurrent() )
|
|
{
|
|
event.Skip();
|
|
break;
|
|
}
|
|
|
|
SendNotify( m_current, wxEVT_LIST_ITEM_ACTIVATED );
|
|
break;
|
|
|
|
default:
|
|
if ( !event.HasModifiers() &&
|
|
((keyCode >= '0' && keyCode <= '9') ||
|
|
(keyCode >= 'a' && keyCode <= 'z') ||
|
|
(keyCode >= 'A' && keyCode <= 'Z') ||
|
|
(keyCode == '_') ||
|
|
(keyCode == '+') ||
|
|
(keyCode == '*') ||
|
|
(keyCode == '-')))
|
|
{
|
|
// find the next item starting with the given prefix
|
|
wxChar ch = (wxChar)keyCode;
|
|
size_t item;
|
|
|
|
// if the same character is typed multiple times then go to the
|
|
// next entry starting with that character instead of searching
|
|
// for an item starting with multiple copies of this character,
|
|
// this is more useful and is how it works under Windows.
|
|
if ( m_findPrefix.length() == 1 && m_findPrefix[0] == ch )
|
|
{
|
|
item = PrefixFindItem(m_current, ch);
|
|
}
|
|
else
|
|
{
|
|
const wxString newPrefix(m_findPrefix + ch);
|
|
item = PrefixFindItem(m_current, newPrefix);
|
|
if ( item != (size_t)-1 )
|
|
m_findPrefix = newPrefix;
|
|
}
|
|
|
|
// also start the timer to reset the current prefix if the user
|
|
// doesn't press any more alnum keys soon -- we wouldn't want
|
|
// to use this prefix for a new item search
|
|
if ( !m_findTimer )
|
|
{
|
|
m_findTimer = new wxListFindTimer( this );
|
|
}
|
|
|
|
// Notice that we should start the timer even if we didn't find
|
|
// anything to make sure we reset the search state later.
|
|
m_findTimer->Start(wxListFindTimer::DELAY, wxTIMER_ONE_SHOT);
|
|
|
|
// restart timer even when there's no match so bell get's reset
|
|
if ( item != (size_t)-1 )
|
|
{
|
|
// Select the found item and go to it.
|
|
HighlightAll(false);
|
|
SetItemState(item,
|
|
wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
|
|
wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
|
|
EnsureVisible(item);
|
|
|
|
// Reset the bell flag if it had been temporarily disabled
|
|
// before.
|
|
if ( m_findBell )
|
|
m_findBell = 1;
|
|
}
|
|
else // No such item
|
|
{
|
|
// Signal it with a bell if enabled.
|
|
if ( m_findBell == 1 )
|
|
{
|
|
::wxBell();
|
|
|
|
// Disable it for the next unsuccessful match, we only
|
|
// beep once, this is usually enough and continuing to
|
|
// do it would be annoying.
|
|
m_findBell = -1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
|
|
UpdateCurrent();
|
|
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;
|
|
|
|
if ((imageList) && (imageList->GetImageCount()) )
|
|
{
|
|
int height;
|
|
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;
|
|
}
|
|
|
|
class wxListCtrlMaxWidthCalculator : public wxMaxWidthCalculatorBase
|
|
{
|
|
public:
|
|
wxListCtrlMaxWidthCalculator(wxListMainWindow *listmain, unsigned int column)
|
|
: wxMaxWidthCalculatorBase(column),
|
|
m_listmain(listmain)
|
|
{
|
|
}
|
|
|
|
virtual void UpdateWithRow(int row) wxOVERRIDE
|
|
{
|
|
wxListLineData *line = m_listmain->GetLine( row );
|
|
wxListItemDataList::compatibility_iterator n = line->m_items.Item( GetColumn() );
|
|
|
|
wxCHECK_RET( n, wxS("no subitem?") );
|
|
|
|
wxListItemData* const itemData = n->GetData();
|
|
|
|
wxListItem item;
|
|
itemData->GetItem(item);
|
|
|
|
UpdateWithWidth(m_listmain->GetItemWidthWithImage(&item));
|
|
}
|
|
|
|
private:
|
|
wxListMainWindow* const m_listmain;
|
|
};
|
|
|
|
|
|
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();
|
|
|
|
if ( width == wxLIST_AUTOSIZE_USEHEADER || width == wxLIST_AUTOSIZE )
|
|
{
|
|
wxListCtrlMaxWidthCalculator calculator(this, col);
|
|
|
|
calculator.UpdateWithWidth(AUTOSIZE_COL_MARGIN);
|
|
|
|
if ( width == wxLIST_AUTOSIZE_USEHEADER )
|
|
calculator.UpdateWithWidth(ComputeMinHeaderWidth(column));
|
|
|
|
// if the cached column width isn't valid then recalculate it
|
|
wxColWidthInfo* const pWidthInfo = m_aColWidths.Item(col);
|
|
if ( pWidthInfo->bNeedsUpdate )
|
|
{
|
|
size_t first_visible, last_visible;
|
|
GetVisibleLinesRange(&first_visible, &last_visible);
|
|
|
|
calculator.ComputeBestColumnWidth(GetItemCount(),
|
|
first_visible, last_visible);
|
|
pWidthInfo->nMaxWidth = calculator.GetMaxWidth();
|
|
pWidthInfo->bNeedsUpdate = false;
|
|
}
|
|
else
|
|
{
|
|
calculator.UpdateWithWidth(pWidthInfo->nMaxWidth);
|
|
}
|
|
|
|
width = calculator.GetMaxWidth() + AUTOSIZE_COL_MARGIN;
|
|
|
|
if ( col == 0 && HasCheckBoxes() )
|
|
{
|
|
// also account for the space needed by the checkbox
|
|
width += wxRendererNative::Get().GetCheckBoxSize(this).x
|
|
+ 2*MARGIN_AROUND_CHECKBOX;
|
|
}
|
|
|
|
// expand the last column to fit the client size
|
|
// only for AUTOSIZE_USEHEADER to mimic MSW behaviour
|
|
if ( (width == wxLIST_AUTOSIZE_USEHEADER) && (col == GetColumnCount() - 1) )
|
|
{
|
|
int margin = GetClientSize().GetX();
|
|
for ( int i = 0; i < col && margin > 0; ++i )
|
|
margin -= m_columns.Item(i)->GetData()->GetWidth();
|
|
|
|
if ( margin > width )
|
|
width = margin;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
wxColWidthInfo* const pWidthInfo = m_aColWidths.Item(item.m_col);
|
|
if ( width > pWidthInfo->nMaxWidth )
|
|
{
|
|
pWidthInfo->nMaxWidth = width;
|
|
pWidthInfo->bNeedsUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.size();
|
|
}
|
|
|
|
void wxListMainWindow::SetItemCount(long count)
|
|
{
|
|
// Update the current item if it's not valid any longer (notice that this
|
|
// invalidates it completely if the control is becoming empty, which is the
|
|
// right thing to do).
|
|
if ( HasCurrent() && m_current >= (size_t)count )
|
|
ChangeCurrent(count - 1);
|
|
|
|
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();
|
|
|
|
return m_selCount;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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,
|
|
int code) 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();
|
|
}
|
|
|
|
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);
|
|
|
|
switch ( code )
|
|
{
|
|
case wxLIST_RECT_BOUNDS:
|
|
// Nothing to do.
|
|
break;
|
|
|
|
case wxLIST_RECT_ICON:
|
|
case wxLIST_RECT_LABEL:
|
|
// Note: this needs to be kept in sync with DrawInReportMode().
|
|
{
|
|
rect.x += ICON_OFFSET_X;
|
|
rect.width -= ICON_OFFSET_X;
|
|
|
|
wxListLineData* const line = GetLine(item);
|
|
if ( subItem == 0 && line->HasImage() )
|
|
{
|
|
int ix, iy;
|
|
GetImageSize(line->GetImage(), ix, iy);
|
|
|
|
const int iconWidth = ix + IMAGE_MARGIN_IN_REPORT_MODE;
|
|
|
|
if ( code == wxLIST_RECT_ICON )
|
|
{
|
|
rect.width = iconWidth;
|
|
}
|
|
else // wxLIST_RECT_LABEL
|
|
{
|
|
rect.x += iconWidth;
|
|
rect.width -= iconWidth;
|
|
}
|
|
}
|
|
else // No icon
|
|
{
|
|
if ( code == wxLIST_RECT_ICON )
|
|
rect = wxRect();
|
|
//else: label rect is the same as the full one
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
wxFAIL_MSG(wxS("Unknown rectangle requested"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// checkboxes
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxListMainWindow::HasCheckBoxes() const
|
|
{
|
|
return m_hasCheckBoxes;
|
|
}
|
|
|
|
bool wxListMainWindow::EnableCheckBoxes(bool enable)
|
|
{
|
|
m_hasCheckBoxes = enable;
|
|
|
|
m_dirty = true;
|
|
m_headerWidth = 0;
|
|
Refresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListMainWindow::CheckItem(long item, bool state)
|
|
{
|
|
wxListLineData *line = GetLine((size_t)item);
|
|
line->Check(state);
|
|
|
|
RefreshLine(item);
|
|
|
|
SendNotify(item, state ? wxEVT_LIST_ITEM_CHECKED
|
|
: wxEVT_LIST_ITEM_UNCHECKED);
|
|
}
|
|
|
|
bool wxListMainWindow::IsItemChecked(long item) const
|
|
{
|
|
wxListLineData *line = GetLine((size_t)item);
|
|
return line->IsChecked();
|
|
}
|
|
|
|
bool wxListMainWindow::IsInsideCheckBox(long item, int x, int y)
|
|
{
|
|
if ( HasCheckBoxes() )
|
|
{
|
|
wxRect lineRect = GetLineRect(item);
|
|
wxSize cbSize = wxRendererNative::Get().GetCheckBoxSize(this);
|
|
int yOffset = (lineRect.height - cbSize.GetHeight()) / 2;
|
|
wxRect rr(wxPoint(MARGIN_AROUND_CHECKBOX, lineRect.y + yOffset), cbSize);
|
|
|
|
return rr.Contains(wxPoint(x, y));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// geometry calculation
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListMainWindow::RecalculatePositions()
|
|
{
|
|
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
|
|
);
|
|
}
|
|
}
|
|
|
|
m_dirty = false;
|
|
}
|
|
|
|
void wxListMainWindow::RecalculatePositionsAndRefresh()
|
|
{
|
|
RecalculatePositions();
|
|
|
|
Refresh();
|
|
|
|
wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin;
|
|
if ( headerWin && headerWin->m_dirty )
|
|
{
|
|
headerWin->m_dirty = false;
|
|
headerWin->Refresh();
|
|
}
|
|
}
|
|
|
|
void wxListMainWindow::UpdateCurrent()
|
|
{
|
|
if ( !HasCurrent() && !IsEmpty() )
|
|
{
|
|
// Initialise m_current to the first item without sending any
|
|
// wxEVT_LIST_ITEM_FOCUSED event (typicaly when the control gains focus)
|
|
// and this is to allow changing the focused item using the arrow keys.
|
|
// which is the behaviour found in the wxMSW port.
|
|
ChangeCurrentWithoutEvent(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;
|
|
wxListItem item;
|
|
|
|
for (size_t i = 0; i < m_columns.GetCount(); i++)
|
|
{
|
|
n = line->m_items.Item( i );
|
|
wxListItemData* itemData;
|
|
itemData = n->GetData();
|
|
itemData->GetItem(item);
|
|
|
|
int itemWidth;
|
|
itemWidth = GetItemWidthWithImage(&item);
|
|
|
|
wxColWidthInfo *pWidthInfo = m_aColWidths.Item(i);
|
|
if ( itemWidth >= pWidthInfo->nMaxWidth )
|
|
pWidthInfo->bNeedsUpdate = true;
|
|
}
|
|
|
|
ResetVisibleLinesRange();
|
|
}
|
|
|
|
SendNotify( index, wxEVT_LIST_DELETE_ITEM, wxDefaultPosition );
|
|
|
|
if ( IsVirtual() )
|
|
{
|
|
m_countVirt--;
|
|
m_selStore.OnItemDelete(index);
|
|
}
|
|
else
|
|
{
|
|
delete m_lines[index];
|
|
m_lines.erase( m_lines.begin() + index );
|
|
}
|
|
|
|
// we need to refresh the (vert) scrollbar as the number of items changed
|
|
m_dirty = true;
|
|
|
|
RefreshAfter(index);
|
|
|
|
// This might be a wxGTK bug, but when deleting the last item in a control
|
|
// with many items, the vertical scroll position may change so that the new
|
|
// last item is not visible any longer, which is very annoying from the
|
|
// user point of view. Ensure that whatever happens, this item is visible.
|
|
if ( count > 1 && m_current != (size_t)-1 )
|
|
EnsureVisible(m_current);
|
|
}
|
|
|
|
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.size(); 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()
|
|
{
|
|
// We will need to update all columns if any items are inserted again.
|
|
if ( InReportView() )
|
|
{
|
|
for ( size_t i = 0; i < m_aColWidths.GetCount(); i++ )
|
|
m_aColWidths.Item(i)->bNeedsUpdate = true;
|
|
}
|
|
|
|
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_LIST_DELETE_ALL_ITEMS, GetParent()->GetId() );
|
|
event.SetEventObject( GetParent() );
|
|
GetParent()->GetEventHandler()->ProcessEvent( event );
|
|
|
|
if ( IsVirtual() )
|
|
{
|
|
m_countVirt = 0;
|
|
m_selStore.Clear();
|
|
}
|
|
else
|
|
{
|
|
m_selCount = 0;
|
|
}
|
|
|
|
if ( InReportView() )
|
|
ResetVisibleLinesRange();
|
|
|
|
m_lines.Clear();
|
|
}
|
|
|
|
void wxListMainWindow::DeleteAllItems()
|
|
{
|
|
DoDeleteAllItems();
|
|
|
|
RecalculatePositionsAndRefresh();
|
|
}
|
|
|
|
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();
|
|
|
|
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;
|
|
pWidthInfo->bNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
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( m_lines.begin() + id, line );
|
|
|
|
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_LIST_INSERT_ITEM);
|
|
|
|
RefreshLines(id, GetItemCount() - 1);
|
|
}
|
|
|
|
long wxListMainWindow::InsertColumn( long col, const wxListItem &item )
|
|
{
|
|
long idx = -1;
|
|
|
|
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(0, IsVirtual());
|
|
|
|
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 );
|
|
idx = col;
|
|
}
|
|
else
|
|
{
|
|
idx = m_aColWidths.GetCount();
|
|
m_columns.Append( column );
|
|
m_aColWidths.Add( colWidthInfo );
|
|
}
|
|
|
|
if ( !IsVirtual() )
|
|
{
|
|
// update all the items
|
|
for ( size_t i = 0; i < m_lines.size(); 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;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
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 + IMAGE_MARGIN_IN_REPORT_MODE;
|
|
}
|
|
|
|
if (!item->GetText().empty())
|
|
{
|
|
wxCoord w;
|
|
dc.GetTextExtent( item->GetText(), &w, NULL );
|
|
width += w;
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// sorting
|
|
// ----------------------------------------------------------------------------
|
|
|
|
struct wxListLineComparator
|
|
{
|
|
wxListLineComparator(wxListCtrlCompare& f, wxIntPtr data)
|
|
: m_f(f),
|
|
m_data(data)
|
|
{
|
|
}
|
|
|
|
bool operator()(wxListLineData* const& line1,
|
|
wxListLineData* const& line2) const
|
|
{
|
|
wxListItem item;
|
|
line1->GetItem( 0, item );
|
|
wxUIntPtr data1 = item.m_data;
|
|
line2->GetItem( 0, item );
|
|
wxUIntPtr data2 = item.m_data;
|
|
return m_f(data1, data2, m_data) < 0;
|
|
}
|
|
|
|
const wxListCtrlCompare m_f;
|
|
const wxIntPtr m_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();
|
|
|
|
std::sort(m_lines.begin(), m_lines.end(), wxListLineComparator(fn, data));
|
|
|
|
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;
|
|
}
|
|
|
|
size_t
|
|
wxListMainWindow::PrefixFindItem(size_t idParent,
|
|
const wxString& prefixOrig) const
|
|
{
|
|
// if no items then just return
|
|
if ( idParent == (size_t)-1 )
|
|
return idParent;
|
|
|
|
// match is case insensitive as this is more convenient to the user: having
|
|
// to press Shift-letter to go to the item starting with a capital letter
|
|
// would be too bothersome
|
|
wxString prefix = prefixOrig.Lower();
|
|
|
|
// determine the starting point: we shouldn't take the current item (this
|
|
// allows to switch between two items starting with the same letter just by
|
|
// pressing it) but we shouldn't jump to the next one if the user is
|
|
// continuing to type as otherwise he might easily skip the item he wanted
|
|
size_t itemid = idParent;
|
|
if ( prefix.length() == 1 )
|
|
{
|
|
itemid += 1;
|
|
}
|
|
|
|
// look for the item starting with the given prefix after it
|
|
while ( ( itemid < (size_t)GetItemCount() ) &&
|
|
!GetLine(itemid)->GetText(0).Lower().StartsWith(prefix) )
|
|
{
|
|
itemid += 1;
|
|
}
|
|
|
|
// if we haven't found anything...
|
|
if ( !( itemid < (size_t)GetItemCount() ) )
|
|
{
|
|
// ... wrap to the beginning
|
|
itemid = 0;
|
|
|
|
// and try all the items (stop when we get to the one we started from)
|
|
while ( ( itemid < (size_t)GetItemCount() ) && itemid != idParent &&
|
|
!GetLine(itemid)->GetText(0).Lower().StartsWith(prefix) )
|
|
{
|
|
itemid += 1;
|
|
}
|
|
// If we haven't found the item, id will be (size_t)-1, as per
|
|
// documentation
|
|
if ( !( itemid < (size_t)GetItemCount() ) ||
|
|
( ( itemid == idParent ) &&
|
|
!GetLine(itemid)->GetText(0).Lower().StartsWith(prefix) ) )
|
|
{
|
|
itemid = (size_t)-1;
|
|
}
|
|
}
|
|
|
|
return itemid;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
// wxGenericListCtrl
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxGenericListCtrl, wxControl);
|
|
|
|
wxBEGIN_EVENT_TABLE(wxGenericListCtrl,wxListCtrlBase)
|
|
EVT_SIZE(wxGenericListCtrl::OnSize)
|
|
EVT_SCROLLWIN(wxGenericListCtrl::OnScroll)
|
|
wxEND_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)
|
|
{
|
|
// Notice that we must initialize m_headerWin first, and create the
|
|
// real window only later, so that the test in the beginning of the
|
|
// function blocks repeated creation of the header as it could happen
|
|
// before via wxNavigationEnabled::AddChild() -> ToggleWindowStyle() ->
|
|
// SetWindowStyleFlag().
|
|
m_headerWin = new wxListHeaderWindow();
|
|
m_headerWin->Create
|
|
(
|
|
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;
|
|
|
|
m_mainWin = new wxListMainWindow(this, wxID_ANY, wxPoint(0, 0), size);
|
|
|
|
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;
|
|
}
|
|
|
|
void wxGenericListCtrl::ExtendRulesAndAlternateColour(bool state)
|
|
{
|
|
wxCHECK_RET( m_mainWin, "can't be called before creation" );
|
|
|
|
wxASSERT_MSG( InReportView(), "can only be called in report mode" );
|
|
|
|
m_mainWin->ExtendRulesAndAlternateColour(state);
|
|
m_mainWin->Refresh();
|
|
}
|
|
|
|
wxBorder wxGenericListCtrl::GetDefaultBorder() const
|
|
{
|
|
return wxBORDER_THEME;
|
|
}
|
|
|
|
#if defined(__WXMSW__) && !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();
|
|
|
|
if ( event.GetOrientation() == wxHORIZONTAL && HasHeader() )
|
|
{
|
|
m_headerWin->Refresh();
|
|
m_headerWin->Update();
|
|
}
|
|
|
|
// Let the window be scrolled as usual by the default handler.
|
|
event.Skip();
|
|
}
|
|
|
|
bool wxGenericListCtrl::HasCheckBoxes() const
|
|
{
|
|
if (!InReportView())
|
|
return false;
|
|
|
|
return m_mainWin->HasCheckBoxes();
|
|
}
|
|
|
|
bool wxGenericListCtrl::EnableCheckBoxes(bool enable)
|
|
{
|
|
if (!InReportView())
|
|
return false;
|
|
|
|
return m_mainWin->EnableCheckBoxes(enable);
|
|
}
|
|
|
|
void wxGenericListCtrl::CheckItem(long item, bool state)
|
|
{
|
|
if (InReportView())
|
|
m_mainWin->CheckItem(item, state);
|
|
}
|
|
|
|
bool wxGenericListCtrl::IsItemChecked(long item) const
|
|
{
|
|
if (!InReportView())
|
|
return false;
|
|
|
|
return m_mainWin->IsItemChecked(item);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Column ordering functions
|
|
int wxGenericListCtrl::GetColumnOrder(int col) const
|
|
{
|
|
// TODO: Implement this on generic port
|
|
return col;
|
|
}
|
|
|
|
int wxGenericListCtrl::GetColumnIndexFromOrder(int order) const
|
|
{
|
|
// TODO: Implement this on generic port
|
|
return order;
|
|
}
|
|
|
|
wxArrayInt wxGenericListCtrl::GetColumnsOrder() const
|
|
{
|
|
// TODO: Implement this on generic port
|
|
return wxArrayInt();
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetColumnsOrder(const wxArrayInt& WXUNUSED(orders))
|
|
{
|
|
// TODO: Implement this on generic port
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool 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 code) const
|
|
{
|
|
if ( !m_mainWin->GetSubItemRect( item, subItem, rect, code ) )
|
|
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);
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
bool wxGenericListCtrl::EndEditLabel(bool cancel)
|
|
{
|
|
return m_mainWin->EndEditLabel(cancel);
|
|
}
|
|
|
|
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") );
|
|
|
|
long idx = 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 idx;
|
|
}
|
|
|
|
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->RecalculatePositionsAndRefresh();
|
|
|
|
AdjustScrollbars();
|
|
}
|
|
|
|
void wxGenericListCtrl::OnInternalIdle()
|
|
{
|
|
wxWindow::OnInternalIdle();
|
|
|
|
if (m_mainWin->m_dirty)
|
|
m_mainWin->RecalculatePositionsAndRefresh();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// font/colours
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxGenericListCtrl::SetBackgroundColour( const wxColour &colour )
|
|
{
|
|
if ( !wxWindow::SetBackgroundColour( colour ) )
|
|
return false;
|
|
|
|
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;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGenericListCtrl::SetFont( const wxFont &font )
|
|
{
|
|
if (!BaseType::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();
|
|
}
|
|
|
|
wxSize wxGenericListCtrl::DoGetBestClientSize() const
|
|
{
|
|
// The base class version can compute the best size in report view only.
|
|
wxSize sizeBest = wxListCtrlBase::DoGetBestClientSize();
|
|
|
|
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();
|
|
|
|
// 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
|
|
// ----------------------------------------------------------------------------
|
|
|
|
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);
|
|
}
|
|
|
|
void wxGenericListCtrl::EnableBellOnNoMatch( bool on )
|
|
{
|
|
m_mainWin->EnableBellOnNoMatch(on);
|
|
}
|
|
|
|
// 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->RecalculatePositionsAndRefresh();
|
|
|
|
m_mainWin->Update();
|
|
}
|
|
|
|
if ( m_headerWin )
|
|
m_headerWin->Update();
|
|
}
|
|
|
|
#endif // wxUSE_LISTCTRL
|