When using virtual list control the main program already has the items data so it's completely useless to call GetItem() when preparing an event for it. It is also inefficient to call GetItem() for potentially many items. For both of these reasons the generic version already didn't fill the item contents for virtual list controls events. Now modify wxMSW to not do it neither. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@70271 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
3348 lines
100 KiB
C++
3348 lines
100 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/msw/listctrl.cpp
|
|
// Purpose: wxListCtrl
|
|
// Author: Julian Smart
|
|
// Modified by: Agron Selimaj
|
|
// Created: 04/01/98
|
|
// RCS-ID: $Id$
|
|
// Copyright: (c) Julian Smart
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ============================================================================
|
|
// declarations
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_LISTCTRL
|
|
|
|
#include "wx/listctrl.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
|
|
#include "wx/app.h"
|
|
#include "wx/intl.h"
|
|
#include "wx/log.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/stopwatch.h"
|
|
#include "wx/dcclient.h"
|
|
#include "wx/textctrl.h"
|
|
#endif
|
|
|
|
#include "wx/imaglist.h"
|
|
#include "wx/vector.h"
|
|
|
|
#include "wx/msw/private.h"
|
|
#include "wx/msw/private/keyboard.h"
|
|
|
|
#if defined(__WXWINCE__) && !defined(__HANDHELDPC__)
|
|
#include <ole2.h>
|
|
#include <shellapi.h>
|
|
#if _WIN32_WCE < 400
|
|
#include <aygshell.h>
|
|
#endif
|
|
#endif
|
|
|
|
// Currently gcc and watcom don't define NMLVFINDITEM, and DMC only defines
|
|
// it by its old name NM_FINDTIEM.
|
|
//
|
|
#if defined(__VISUALC__) || defined(__BORLANDC__) || defined(NMLVFINDITEM)
|
|
#define HAVE_NMLVFINDITEM 1
|
|
#elif defined(__DMC__) || defined(NM_FINDITEM)
|
|
#define HAVE_NMLVFINDITEM 1
|
|
#define NMLVFINDITEM NM_FINDITEM
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// private functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// convert our state and mask flags to LV_ITEM constants
|
|
static void wxConvertToMSWFlags(long state, long mask, LV_ITEM& lvItem);
|
|
|
|
// convert wxListItem to LV_ITEM
|
|
static void wxConvertToMSWListItem(const wxListCtrl *ctrl,
|
|
const wxListItem& info, LV_ITEM& lvItem);
|
|
|
|
// convert LV_ITEM to wxListItem
|
|
static void wxConvertFromMSWListItem(HWND hwndListCtrl,
|
|
wxListItem& info,
|
|
/* const */ LV_ITEM& lvItem);
|
|
|
|
// convert our wxListItem to LV_COLUMN
|
|
static void wxConvertToMSWListCol(HWND hwndList,
|
|
int col,
|
|
const wxListItem& item,
|
|
LV_COLUMN& lvCol);
|
|
|
|
namespace
|
|
{
|
|
|
|
// replacement for ListView_GetSubItemRect() which provokes warnings like
|
|
// "the address of 'rc' will always evaluate as 'true'" when used with mingw32
|
|
// 4.3+
|
|
//
|
|
// this function does no error checking on item and subitem parameters, notice
|
|
// that subitem 0 means the whole item so there is no way to retrieve the
|
|
// rectangle of the first subitem using this function, in particular notice
|
|
// that the index is *not* 1-based, in spite of what MSDN says
|
|
inline bool
|
|
wxGetListCtrlSubItemRect(HWND hwnd, int item, int subitem, int flags, RECT& rect)
|
|
{
|
|
rect.top = subitem;
|
|
rect.left = flags;
|
|
return ::SendMessage(hwnd, LVM_GETSUBITEMRECT, item, (LPARAM)&rect) != 0;
|
|
}
|
|
|
|
inline bool
|
|
wxGetListCtrlItemRect(HWND hwnd, int item, int flags, RECT& rect)
|
|
{
|
|
return wxGetListCtrlSubItemRect(hwnd, item, 0, flags, rect);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// private helper classes
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// We have to handle both fooW and fooA notifications in several cases
|
|
// because of broken comctl32.dll and/or unicows.dll. This class is used to
|
|
// convert LV_ITEMA and LV_ITEMW to LV_ITEM (which is either LV_ITEMA or
|
|
// LV_ITEMW depending on wxUSE_UNICODE setting), so that it can be processed
|
|
// by wxConvertToMSWListItem().
|
|
#if wxUSE_UNICODE
|
|
#define LV_ITEM_NATIVE LV_ITEMW
|
|
#define LV_ITEM_OTHER LV_ITEMA
|
|
|
|
#define LV_CONV_TO_WX cMB2WX
|
|
#define LV_CONV_BUF wxMB2WXbuf
|
|
#else // ANSI
|
|
#define LV_ITEM_NATIVE LV_ITEMA
|
|
#define LV_ITEM_OTHER LV_ITEMW
|
|
|
|
#define LV_CONV_TO_WX cWC2WX
|
|
#define LV_CONV_BUF wxWC2WXbuf
|
|
#endif // Unicode/ANSI
|
|
|
|
class wxLV_ITEM
|
|
{
|
|
public:
|
|
// default ctor, use Init() later
|
|
wxLV_ITEM() { m_buf = NULL; m_pItem = NULL; }
|
|
|
|
// init without conversion
|
|
void Init(LV_ITEM_NATIVE& item)
|
|
{
|
|
wxASSERT_MSG( !m_pItem, wxT("Init() called twice?") );
|
|
|
|
m_pItem = &item;
|
|
}
|
|
|
|
// init with conversion
|
|
void Init(const LV_ITEM_OTHER& item)
|
|
{
|
|
// avoid unnecessary dynamic memory allocation, jjust make m_pItem
|
|
// point to our own m_item
|
|
|
|
// memcpy() can't work if the struct sizes are different
|
|
wxCOMPILE_TIME_ASSERT( sizeof(LV_ITEM_OTHER) == sizeof(LV_ITEM_NATIVE),
|
|
CodeCantWorkIfDiffSizes);
|
|
|
|
memcpy(&m_item, &item, sizeof(LV_ITEM_NATIVE));
|
|
|
|
// convert text from ANSI to Unicod if necessary
|
|
if ( (item.mask & LVIF_TEXT) && item.pszText )
|
|
{
|
|
m_buf = new LV_CONV_BUF(wxConvLocal.LV_CONV_TO_WX(item.pszText));
|
|
m_item.pszText = (wxChar *)m_buf->data();
|
|
}
|
|
}
|
|
|
|
// ctor without conversion
|
|
wxLV_ITEM(LV_ITEM_NATIVE& item) : m_buf(NULL), m_pItem(&item) { }
|
|
|
|
// ctor with conversion
|
|
wxLV_ITEM(LV_ITEM_OTHER& item) : m_buf(NULL)
|
|
{
|
|
Init(item);
|
|
}
|
|
|
|
~wxLV_ITEM() { delete m_buf; }
|
|
|
|
// conversion to the real LV_ITEM
|
|
operator LV_ITEM_NATIVE&() const { return *m_pItem; }
|
|
|
|
private:
|
|
LV_CONV_BUF *m_buf;
|
|
|
|
LV_ITEM_NATIVE *m_pItem;
|
|
LV_ITEM_NATIVE m_item;
|
|
|
|
wxDECLARE_NO_COPY_CLASS(wxLV_ITEM);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////
|
|
// Problem:
|
|
// The MSW version had problems with SetTextColour() et
|
|
// al as the wxListItemAttr's were stored keyed on the
|
|
// item index. If a item was inserted anywhere but the end
|
|
// of the list the text attributes (colour etc) for
|
|
// the following items were out of sync.
|
|
//
|
|
// Solution:
|
|
// Under MSW the only way to associate data with a List
|
|
// item independent of its position in the list is to
|
|
// store a pointer to it in its lParam attribute. However
|
|
// user programs are already using this (via the
|
|
// SetItemData() GetItemData() calls).
|
|
//
|
|
// However what we can do is store a pointer to a
|
|
// structure which contains the attributes we want *and*
|
|
// a lParam -- and this is what wxMSWListItemData does.
|
|
//
|
|
// To conserve memory, a wxMSWListItemData is
|
|
// only allocated for a LV_ITEM if text attributes or
|
|
// user data(lparam) are being set.
|
|
class wxMSWListItemData
|
|
{
|
|
public:
|
|
wxMSWListItemData() : attr(NULL), lParam(0) {}
|
|
~wxMSWListItemData() { delete attr; }
|
|
|
|
wxListItemAttr *attr;
|
|
LPARAM lParam; // real user data
|
|
|
|
wxDECLARE_NO_COPY_CLASS(wxMSWListItemData);
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(wxListCtrl, wxControl)
|
|
EVT_PAINT(wxListCtrl::OnPaint)
|
|
EVT_CHAR_HOOK(wxListCtrl::OnCharHook)
|
|
END_EVENT_TABLE()
|
|
|
|
// ============================================================================
|
|
// implementation
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxListCtrl construction
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxListCtrl::Init()
|
|
{
|
|
m_imageListNormal =
|
|
m_imageListSmall =
|
|
m_imageListState = NULL;
|
|
m_ownsImageListNormal =
|
|
m_ownsImageListSmall =
|
|
m_ownsImageListState = false;
|
|
|
|
m_colCount = 0;
|
|
m_count = 0;
|
|
m_textCtrl = NULL;
|
|
|
|
m_hasAnyAttr = false;
|
|
}
|
|
|
|
bool wxListCtrl::Create(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxValidator& validator,
|
|
const wxString& name)
|
|
{
|
|
if ( !CreateControl(parent, id, pos, size, style, validator, name) )
|
|
return false;
|
|
|
|
if ( !MSWCreateControl(WC_LISTVIEW, wxEmptyString, pos, size) )
|
|
return false;
|
|
|
|
// explicitly say that we want to use Unicode because otherwise we get ANSI
|
|
// versions of _some_ messages (notably LVN_GETDISPINFOA) in MSLU build
|
|
wxSetCCUnicodeFormat(GetHwnd());
|
|
|
|
// We must set the default text colour to the system/theme color, otherwise
|
|
// GetTextColour will always return black
|
|
SetTextColour(GetDefaultAttributes().colFg);
|
|
|
|
if ( InReportView() )
|
|
MSWSetExListStyles();
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxListCtrl::MSWSetExListStyles()
|
|
{
|
|
// for comctl32.dll v 4.70+ we want to have some non default extended
|
|
// styles because it's prettier (and also because wxGTK does it like this)
|
|
if ( wxApp::GetComCtl32Version() >= 470 )
|
|
{
|
|
::SendMessage
|
|
(
|
|
GetHwnd(), LVM_SETEXTENDEDLISTVIEWSTYLE, 0,
|
|
// LVS_EX_LABELTIP shouldn't be used under Windows CE where it's
|
|
// not defined in the SDK headers
|
|
#ifdef LVS_EX_LABELTIP
|
|
LVS_EX_LABELTIP |
|
|
#endif
|
|
LVS_EX_FULLROWSELECT |
|
|
LVS_EX_SUBITEMIMAGES |
|
|
// normally this should be governed by a style as it's probably not
|
|
// always appropriate, but we don't have any free styles left and
|
|
// it seems better to enable it by default than disable
|
|
LVS_EX_HEADERDRAGDROP
|
|
);
|
|
}
|
|
}
|
|
|
|
WXDWORD wxListCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
|
|
{
|
|
WXDWORD wstyle = wxControl::MSWGetStyle(style, exstyle);
|
|
|
|
wstyle |= LVS_SHAREIMAGELISTS | LVS_SHOWSELALWAYS;
|
|
|
|
#if wxDEBUG_LEVEL
|
|
size_t nModes = 0;
|
|
|
|
#define MAP_MODE_STYLE(wx, ms) \
|
|
if ( style & (wx) ) { wstyle |= (ms); nModes++; }
|
|
#else // !wxDEBUG_LEVEL
|
|
#define MAP_MODE_STYLE(wx, ms) \
|
|
if ( style & (wx) ) wstyle |= (ms);
|
|
#endif // wxDEBUG_LEVEL/!wxDEBUG_LEVEL
|
|
|
|
MAP_MODE_STYLE(wxLC_ICON, LVS_ICON)
|
|
MAP_MODE_STYLE(wxLC_SMALL_ICON, LVS_SMALLICON)
|
|
MAP_MODE_STYLE(wxLC_LIST, LVS_LIST)
|
|
MAP_MODE_STYLE(wxLC_REPORT, LVS_REPORT)
|
|
|
|
wxASSERT_MSG( nModes == 1,
|
|
wxT("wxListCtrl style should have exactly one mode bit set") );
|
|
|
|
#undef MAP_MODE_STYLE
|
|
|
|
if ( style & wxLC_ALIGN_LEFT )
|
|
wstyle |= LVS_ALIGNLEFT;
|
|
|
|
if ( style & wxLC_ALIGN_TOP )
|
|
wstyle |= LVS_ALIGNTOP;
|
|
|
|
if ( style & wxLC_AUTOARRANGE )
|
|
wstyle |= LVS_AUTOARRANGE;
|
|
|
|
if ( style & wxLC_NO_SORT_HEADER )
|
|
wstyle |= LVS_NOSORTHEADER;
|
|
|
|
if ( style & wxLC_NO_HEADER )
|
|
wstyle |= LVS_NOCOLUMNHEADER;
|
|
|
|
if ( style & wxLC_EDIT_LABELS )
|
|
wstyle |= LVS_EDITLABELS;
|
|
|
|
if ( style & wxLC_SINGLE_SEL )
|
|
wstyle |= LVS_SINGLESEL;
|
|
|
|
if ( style & wxLC_SORT_ASCENDING )
|
|
{
|
|
wstyle |= LVS_SORTASCENDING;
|
|
|
|
wxASSERT_MSG( !(style & wxLC_SORT_DESCENDING),
|
|
wxT("can't sort in ascending and descending orders at once") );
|
|
}
|
|
else if ( style & wxLC_SORT_DESCENDING )
|
|
wstyle |= LVS_SORTDESCENDING;
|
|
|
|
#if !( defined(__GNUWIN32__) && !wxCHECK_W32API_VERSION( 1, 0 ) )
|
|
if ( style & wxLC_VIRTUAL )
|
|
{
|
|
int ver = wxApp::GetComCtl32Version();
|
|
if ( ver < 470 )
|
|
{
|
|
wxLogWarning(_("Please install a newer version of comctl32.dll\n(at least version 4.70 is required but you have %d.%02d)\nor this program won't operate correctly."),
|
|
ver / 100, ver % 100);
|
|
}
|
|
|
|
wstyle |= LVS_OWNERDATA;
|
|
}
|
|
#endif // ancient cygwin
|
|
|
|
return wstyle;
|
|
}
|
|
|
|
void wxListCtrl::UpdateStyle()
|
|
{
|
|
if ( GetHwnd() )
|
|
{
|
|
// The new window view style
|
|
DWORD dwStyleNew = MSWGetStyle(m_windowStyle, NULL);
|
|
|
|
// some styles are not returned by MSWGetStyle()
|
|
if ( IsShown() )
|
|
dwStyleNew |= WS_VISIBLE;
|
|
|
|
// Get the current window style.
|
|
DWORD dwStyleOld = ::GetWindowLong(GetHwnd(), GWL_STYLE);
|
|
|
|
// we don't have wxVSCROLL style, but the list control may have it,
|
|
// don't change it then
|
|
dwStyleNew |= dwStyleOld & (WS_HSCROLL | WS_VSCROLL);
|
|
|
|
// Only set the window style if the view bits have changed.
|
|
if ( dwStyleOld != dwStyleNew )
|
|
{
|
|
::SetWindowLong(GetHwnd(), GWL_STYLE, dwStyleNew);
|
|
|
|
// if we switched to the report view, set the extended styles for
|
|
// it too
|
|
if ( !(dwStyleOld & LVS_REPORT) && (dwStyleNew & LVS_REPORT) )
|
|
MSWSetExListStyles();
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxListCtrl::FreeAllInternalData()
|
|
{
|
|
const unsigned count = m_internalData.size();
|
|
for ( unsigned n = 0; n < count; n++ )
|
|
delete m_internalData[n];
|
|
|
|
m_internalData.clear();
|
|
}
|
|
|
|
void wxListCtrl::DeleteEditControl()
|
|
{
|
|
if ( m_textCtrl )
|
|
{
|
|
m_textCtrl->UnsubclassWin();
|
|
m_textCtrl->SetHWND(0);
|
|
wxDELETE(m_textCtrl);
|
|
}
|
|
}
|
|
|
|
wxListCtrl::~wxListCtrl()
|
|
{
|
|
FreeAllInternalData();
|
|
|
|
DeleteEditControl();
|
|
|
|
if (m_ownsImageListNormal)
|
|
delete m_imageListNormal;
|
|
if (m_ownsImageListSmall)
|
|
delete m_imageListSmall;
|
|
if (m_ownsImageListState)
|
|
delete m_imageListState;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// set/get/change style
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Add or remove a single window style
|
|
void wxListCtrl::SetSingleStyle(long style, bool add)
|
|
{
|
|
long flag = GetWindowStyleFlag();
|
|
|
|
// Get rid of conflicting styles
|
|
if ( add )
|
|
{
|
|
if ( style & wxLC_MASK_TYPE)
|
|
flag = flag & ~wxLC_MASK_TYPE;
|
|
if ( style & wxLC_MASK_ALIGN )
|
|
flag = flag & ~wxLC_MASK_ALIGN;
|
|
if ( style & wxLC_MASK_SORT )
|
|
flag = flag & ~wxLC_MASK_SORT;
|
|
}
|
|
|
|
if ( add )
|
|
flag |= style;
|
|
else
|
|
flag &= ~style;
|
|
|
|
SetWindowStyleFlag(flag);
|
|
}
|
|
|
|
// Set the whole window style
|
|
void wxListCtrl::SetWindowStyleFlag(long flag)
|
|
{
|
|
if ( flag != m_windowStyle )
|
|
{
|
|
wxControl::SetWindowStyleFlag(flag);
|
|
|
|
UpdateStyle();
|
|
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// accessors
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* static */ wxVisualAttributes
|
|
wxListCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
|
|
{
|
|
wxVisualAttributes attrs = GetCompositeControlsDefaultAttributes(variant);
|
|
|
|
// common controls have their own default font
|
|
attrs.font = wxGetCCDefaultFont();
|
|
|
|
return attrs;
|
|
}
|
|
|
|
// Sets the foreground, i.e. text, colour
|
|
bool wxListCtrl::SetForegroundColour(const wxColour& col)
|
|
{
|
|
if ( !wxWindow::SetForegroundColour(col) )
|
|
return false;
|
|
|
|
ListView_SetTextColor(GetHwnd(), wxColourToRGB(col));
|
|
|
|
return true;
|
|
}
|
|
|
|
// Sets the background colour
|
|
bool wxListCtrl::SetBackgroundColour(const wxColour& col)
|
|
{
|
|
if ( !wxWindow::SetBackgroundColour(col) )
|
|
return false;
|
|
|
|
// we set the same colour for both the "empty" background and the items
|
|
// background
|
|
COLORREF color = wxColourToRGB(col);
|
|
ListView_SetBkColor(GetHwnd(), color);
|
|
ListView_SetTextBkColor(GetHwnd(), color);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Gets information about this column
|
|
bool wxListCtrl::GetColumn(int col, wxListItem& item) const
|
|
{
|
|
LV_COLUMN lvCol;
|
|
wxZeroMemory(lvCol);
|
|
|
|
lvCol.mask = LVCF_WIDTH;
|
|
|
|
if ( item.m_mask & wxLIST_MASK_TEXT )
|
|
{
|
|
lvCol.mask |= LVCF_TEXT;
|
|
lvCol.pszText = new wxChar[513];
|
|
lvCol.cchTextMax = 512;
|
|
}
|
|
|
|
if ( item.m_mask & wxLIST_MASK_FORMAT )
|
|
{
|
|
lvCol.mask |= LVCF_FMT;
|
|
}
|
|
|
|
if ( item.m_mask & wxLIST_MASK_IMAGE )
|
|
{
|
|
lvCol.mask |= LVCF_IMAGE;
|
|
}
|
|
|
|
bool success = ListView_GetColumn(GetHwnd(), col, &lvCol) != 0;
|
|
|
|
// item.m_subItem = lvCol.iSubItem;
|
|
item.m_width = lvCol.cx;
|
|
|
|
if ( (item.m_mask & wxLIST_MASK_TEXT) && lvCol.pszText )
|
|
{
|
|
item.m_text = lvCol.pszText;
|
|
delete[] lvCol.pszText;
|
|
}
|
|
|
|
if ( item.m_mask & wxLIST_MASK_FORMAT )
|
|
{
|
|
switch (lvCol.fmt & LVCFMT_JUSTIFYMASK) {
|
|
case LVCFMT_LEFT:
|
|
item.m_format = wxLIST_FORMAT_LEFT;
|
|
break;
|
|
case LVCFMT_RIGHT:
|
|
item.m_format = wxLIST_FORMAT_RIGHT;
|
|
break;
|
|
case LVCFMT_CENTER:
|
|
item.m_format = wxLIST_FORMAT_CENTRE;
|
|
break;
|
|
default:
|
|
item.m_format = -1; // Unknown?
|
|
break;
|
|
}
|
|
}
|
|
|
|
// the column images were not supported in older versions but how to check
|
|
// for this? we can't use _WIN32_IE because we always define it to a very
|
|
// high value, so see if another symbol which is only defined starting from
|
|
// comctl32.dll 4.70 is available
|
|
#ifdef NM_CUSTOMDRAW // _WIN32_IE >= 0x0300
|
|
if ( item.m_mask & wxLIST_MASK_IMAGE )
|
|
{
|
|
item.m_image = lvCol.iImage;
|
|
}
|
|
#endif // LVCOLUMN::iImage exists
|
|
|
|
return success;
|
|
}
|
|
|
|
// Sets information about this column
|
|
bool wxListCtrl::SetColumn(int col, const wxListItem& item)
|
|
{
|
|
LV_COLUMN lvCol;
|
|
wxConvertToMSWListCol(GetHwnd(), col, item, lvCol);
|
|
|
|
return ListView_SetColumn(GetHwnd(), col, &lvCol) != 0;
|
|
}
|
|
|
|
// Gets the column width
|
|
int wxListCtrl::GetColumnWidth(int col) const
|
|
{
|
|
return ListView_GetColumnWidth(GetHwnd(), col);
|
|
}
|
|
|
|
// Sets the column width
|
|
bool wxListCtrl::SetColumnWidth(int col, int width)
|
|
{
|
|
if ( m_windowStyle & wxLC_LIST )
|
|
col = 0;
|
|
|
|
if ( width == wxLIST_AUTOSIZE)
|
|
width = LVSCW_AUTOSIZE;
|
|
else if ( width == wxLIST_AUTOSIZE_USEHEADER)
|
|
width = LVSCW_AUTOSIZE_USEHEADER;
|
|
|
|
return ListView_SetColumnWidth(GetHwnd(), col, width) != 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// columns order
|
|
// ----------------------------------------------------------------------------
|
|
|
|
int wxListCtrl::GetColumnIndexFromOrder(int order) const
|
|
{
|
|
const int numCols = GetColumnCount();
|
|
wxCHECK_MSG( order >= 0 && order < numCols, -1,
|
|
wxT("Column position out of bounds") );
|
|
|
|
wxArrayInt indexArray(numCols);
|
|
if ( !ListView_GetColumnOrderArray(GetHwnd(), numCols, &indexArray[0]) )
|
|
return -1;
|
|
|
|
return indexArray[order];
|
|
}
|
|
|
|
int wxListCtrl::GetColumnOrder(int col) const
|
|
{
|
|
const int numCols = GetColumnCount();
|
|
wxASSERT_MSG( col >= 0 && col < numCols, wxT("Column index out of bounds") );
|
|
|
|
wxArrayInt indexArray(numCols);
|
|
if ( !ListView_GetColumnOrderArray(GetHwnd(), numCols, &indexArray[0]) )
|
|
return -1;
|
|
|
|
for ( int pos = 0; pos < numCols; pos++ )
|
|
{
|
|
if ( indexArray[pos] == col )
|
|
return pos;
|
|
}
|
|
|
|
wxFAIL_MSG( wxT("no column with with given order?") );
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Gets the column order for all columns
|
|
wxArrayInt wxListCtrl::GetColumnsOrder() const
|
|
{
|
|
const int numCols = GetColumnCount();
|
|
|
|
wxArrayInt orders(numCols);
|
|
if ( !ListView_GetColumnOrderArray(GetHwnd(), numCols, &orders[0]) )
|
|
orders.clear();
|
|
|
|
return orders;
|
|
}
|
|
|
|
// Sets the column order for all columns
|
|
bool wxListCtrl::SetColumnsOrder(const wxArrayInt& orders)
|
|
{
|
|
const int numCols = GetColumnCount();
|
|
|
|
wxCHECK_MSG( orders.size() == (size_t)numCols, false,
|
|
wxT("wrong number of elements in column orders array") );
|
|
|
|
return ListView_SetColumnOrderArray(GetHwnd(), numCols, &orders[0]) != 0;
|
|
}
|
|
|
|
|
|
// Gets the number of items that can fit vertically in the
|
|
// visible area of the list control (list or report view)
|
|
// or the total number of items in the list control (icon
|
|
// or small icon view)
|
|
int wxListCtrl::GetCountPerPage() const
|
|
{
|
|
return ListView_GetCountPerPage(GetHwnd());
|
|
}
|
|
|
|
// Gets the edit control for editing labels.
|
|
wxTextCtrl* wxListCtrl::GetEditControl() const
|
|
{
|
|
// first check corresponds to the case when the label editing was started
|
|
// by user and hence m_textCtrl wasn't created by EditLabel() at all, while
|
|
// the second case corresponds to us being called from inside EditLabel()
|
|
// (e.g. from a user wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT handler): in this
|
|
// case EditLabel() did create the control but it didn't have an HWND to
|
|
// initialize it with yet
|
|
if ( !m_textCtrl || !m_textCtrl->GetHWND() )
|
|
{
|
|
HWND hwndEdit = ListView_GetEditControl(GetHwnd());
|
|
if ( hwndEdit )
|
|
{
|
|
wxListCtrl * const self = const_cast<wxListCtrl *>(this);
|
|
|
|
if ( !m_textCtrl )
|
|
self->m_textCtrl = new wxTextCtrl;
|
|
self->InitEditControl((WXHWND)hwndEdit);
|
|
}
|
|
}
|
|
|
|
return m_textCtrl;
|
|
}
|
|
|
|
// Gets information about the item
|
|
bool wxListCtrl::GetItem(wxListItem& info) const
|
|
{
|
|
LV_ITEM lvItem;
|
|
wxZeroMemory(lvItem);
|
|
|
|
lvItem.iItem = info.m_itemId;
|
|
lvItem.iSubItem = info.m_col;
|
|
|
|
if ( info.m_mask & wxLIST_MASK_TEXT )
|
|
{
|
|
lvItem.mask |= LVIF_TEXT;
|
|
lvItem.pszText = new wxChar[513];
|
|
lvItem.cchTextMax = 512;
|
|
}
|
|
else
|
|
{
|
|
lvItem.pszText = NULL;
|
|
}
|
|
|
|
if (info.m_mask & wxLIST_MASK_DATA)
|
|
lvItem.mask |= LVIF_PARAM;
|
|
|
|
if (info.m_mask & wxLIST_MASK_IMAGE)
|
|
lvItem.mask |= LVIF_IMAGE;
|
|
|
|
if ( info.m_mask & wxLIST_MASK_STATE )
|
|
{
|
|
lvItem.mask |= LVIF_STATE;
|
|
wxConvertToMSWFlags(0, info.m_stateMask, lvItem);
|
|
}
|
|
|
|
bool success = ListView_GetItem((HWND)GetHWND(), &lvItem) != 0;
|
|
if ( !success )
|
|
{
|
|
wxLogError(_("Couldn't retrieve information about list control item %d."),
|
|
lvItem.iItem);
|
|
}
|
|
else
|
|
{
|
|
// give NULL as hwnd as we already have everything we need
|
|
wxConvertFromMSWListItem(NULL, info, lvItem);
|
|
}
|
|
|
|
if (lvItem.pszText)
|
|
delete[] lvItem.pszText;
|
|
|
|
return success;
|
|
}
|
|
|
|
// Sets information about the item
|
|
bool wxListCtrl::SetItem(wxListItem& info)
|
|
{
|
|
const long id = info.GetId();
|
|
wxCHECK_MSG( id >= 0 && id < GetItemCount(), false,
|
|
wxT("invalid item index in SetItem") );
|
|
|
|
LV_ITEM item;
|
|
wxConvertToMSWListItem(this, info, item);
|
|
|
|
// we never update the lParam if it contains our pointer
|
|
// to the wxMSWListItemData structure
|
|
item.mask &= ~LVIF_PARAM;
|
|
|
|
// check if setting attributes or lParam
|
|
if ( info.HasAttributes() || (info.m_mask & wxLIST_MASK_DATA) )
|
|
{
|
|
// get internal item data
|
|
wxMSWListItemData *data = MSWGetItemData(id);
|
|
|
|
if ( !data )
|
|
{
|
|
// need to allocate the internal data object
|
|
data = new wxMSWListItemData;
|
|
m_internalData.push_back(data);
|
|
item.lParam = (LPARAM) data;
|
|
item.mask |= LVIF_PARAM;
|
|
}
|
|
|
|
|
|
// user data
|
|
if ( info.m_mask & wxLIST_MASK_DATA )
|
|
data->lParam = info.m_data;
|
|
|
|
// attributes
|
|
if ( info.HasAttributes() )
|
|
{
|
|
const wxListItemAttr& attrNew = *info.GetAttributes();
|
|
|
|
// don't overwrite the already set attributes if we have them
|
|
if ( data->attr )
|
|
data->attr->AssignFrom(attrNew);
|
|
else
|
|
data->attr = new wxListItemAttr(attrNew);
|
|
}
|
|
}
|
|
|
|
|
|
// we could be changing only the attribute in which case we don't need to
|
|
// call ListView_SetItem() at all
|
|
if ( item.mask )
|
|
{
|
|
if ( !ListView_SetItem(GetHwnd(), &item) )
|
|
{
|
|
wxLogDebug(wxT("ListView_SetItem() failed"));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// we need to update the item immediately to show the new image
|
|
bool updateNow = (info.m_mask & wxLIST_MASK_IMAGE) != 0;
|
|
|
|
// check whether it has any custom attributes
|
|
if ( info.HasAttributes() )
|
|
{
|
|
m_hasAnyAttr = true;
|
|
|
|
// if the colour has changed, we must redraw the item
|
|
updateNow = true;
|
|
}
|
|
|
|
if ( updateNow )
|
|
{
|
|
// we need this to make the change visible right now
|
|
RefreshItem(item.iItem);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
long wxListCtrl::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;
|
|
}
|
|
return SetItem(info);
|
|
}
|
|
|
|
|
|
// Gets the item state
|
|
int wxListCtrl::GetItemState(long item, long stateMask) const
|
|
{
|
|
wxListItem info;
|
|
|
|
info.m_mask = wxLIST_MASK_STATE;
|
|
info.m_stateMask = stateMask;
|
|
info.m_itemId = item;
|
|
|
|
if (!GetItem(info))
|
|
return 0;
|
|
|
|
return info.m_state;
|
|
}
|
|
|
|
// Sets the item state
|
|
bool wxListCtrl::SetItemState(long item, long state, long stateMask)
|
|
{
|
|
// NB: don't use SetItem() here as it doesn't work with the virtual list
|
|
// controls
|
|
LV_ITEM lvItem;
|
|
wxZeroMemory(lvItem);
|
|
|
|
wxConvertToMSWFlags(state, stateMask, lvItem);
|
|
|
|
const bool changingFocus = (stateMask & wxLIST_STATE_FOCUSED) &&
|
|
(state & wxLIST_STATE_FOCUSED);
|
|
|
|
// for the virtual list controls we need to refresh the previously focused
|
|
// item manually when changing focus without changing selection
|
|
// programmatically because otherwise it keeps its focus rectangle until
|
|
// next repaint (yet another comctl32 bug)
|
|
long focusOld;
|
|
if ( IsVirtual() && changingFocus )
|
|
{
|
|
focusOld = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
|
|
}
|
|
else
|
|
{
|
|
focusOld = -1;
|
|
}
|
|
|
|
if ( !::SendMessage(GetHwnd(), LVM_SETITEMSTATE,
|
|
(WPARAM)item, (LPARAM)&lvItem) )
|
|
{
|
|
wxLogLastError(wxT("ListView_SetItemState"));
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( focusOld != -1 )
|
|
{
|
|
// no need to refresh the item if it was previously selected, it would
|
|
// only result in annoying flicker
|
|
if ( !(GetItemState(focusOld,
|
|
wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED) )
|
|
{
|
|
RefreshItem(focusOld);
|
|
}
|
|
}
|
|
|
|
// we expect the selection anchor, i.e. the item from which multiple
|
|
// selection (such as performed with e.g. Shift-arrows) starts, to be the
|
|
// same as the currently focused item but the native control doesn't update
|
|
// it when we change focus and leaves at the last item it set itself focus
|
|
// to, so do it explicitly
|
|
if ( changingFocus && !HasFlag(wxLC_SINGLE_SEL) )
|
|
{
|
|
ListView_SetSelectionMark(GetHwnd(), item);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Sets the item image
|
|
bool wxListCtrl::SetItemImage(long item, int image, int WXUNUSED(selImage))
|
|
{
|
|
return SetItemColumnImage(item, 0, image);
|
|
}
|
|
|
|
// Sets the item image
|
|
bool wxListCtrl::SetItemColumnImage(long item, long column, int image)
|
|
{
|
|
wxListItem info;
|
|
|
|
info.m_mask = wxLIST_MASK_IMAGE;
|
|
info.m_image = image;
|
|
info.m_itemId = item;
|
|
info.m_col = column;
|
|
|
|
return SetItem(info);
|
|
}
|
|
|
|
// Gets the item text
|
|
wxString wxListCtrl::GetItemText(long item, int col) const
|
|
{
|
|
wxListItem info;
|
|
|
|
info.m_mask = wxLIST_MASK_TEXT;
|
|
info.m_itemId = item;
|
|
info.m_col = col;
|
|
|
|
if (!GetItem(info))
|
|
return wxEmptyString;
|
|
return info.m_text;
|
|
}
|
|
|
|
// Sets the item text
|
|
void wxListCtrl::SetItemText(long item, const wxString& str)
|
|
{
|
|
wxListItem info;
|
|
|
|
info.m_mask = wxLIST_MASK_TEXT;
|
|
info.m_itemId = item;
|
|
info.m_text = str;
|
|
|
|
SetItem(info);
|
|
}
|
|
|
|
// Gets the internal item data
|
|
wxMSWListItemData *wxListCtrl::MSWGetItemData(long itemId) const
|
|
{
|
|
LV_ITEM it;
|
|
it.mask = LVIF_PARAM;
|
|
it.iItem = itemId;
|
|
|
|
if ( !ListView_GetItem(GetHwnd(), &it) )
|
|
return NULL;
|
|
|
|
return (wxMSWListItemData *) it.lParam;
|
|
}
|
|
|
|
// Gets the item data
|
|
wxUIntPtr wxListCtrl::GetItemData(long item) const
|
|
{
|
|
wxListItem info;
|
|
|
|
info.m_mask = wxLIST_MASK_DATA;
|
|
info.m_itemId = item;
|
|
|
|
if (!GetItem(info))
|
|
return 0;
|
|
return info.m_data;
|
|
}
|
|
|
|
// Sets the item data
|
|
bool wxListCtrl::SetItemPtrData(long item, wxUIntPtr data)
|
|
{
|
|
wxListItem info;
|
|
|
|
info.m_mask = wxLIST_MASK_DATA;
|
|
info.m_itemId = item;
|
|
info.m_data = data;
|
|
|
|
return SetItem(info);
|
|
}
|
|
|
|
wxRect wxListCtrl::GetViewRect() const
|
|
{
|
|
wxRect rect;
|
|
|
|
// ListView_GetViewRect() can only be used in icon and small icon views
|
|
// (this is documented in MSDN and, indeed, it returns bogus results in
|
|
// report view, at least with comctl32.dll v6 under Windows 2003)
|
|
if ( HasFlag(wxLC_ICON | wxLC_SMALL_ICON) )
|
|
{
|
|
RECT rc;
|
|
if ( !ListView_GetViewRect(GetHwnd(), &rc) )
|
|
{
|
|
wxLogDebug(wxT("ListView_GetViewRect() failed."));
|
|
|
|
wxZeroMemory(rc);
|
|
}
|
|
|
|
wxCopyRECTToRect(rc, rect);
|
|
}
|
|
else if ( HasFlag(wxLC_REPORT) )
|
|
{
|
|
const long count = GetItemCount();
|
|
if ( count )
|
|
{
|
|
GetItemRect(wxMin(GetTopItem() + GetCountPerPage(), count - 1), rect);
|
|
|
|
// extend the rectangle to start at the top (we include the column
|
|
// headers, if any, for compatibility with the generic version)
|
|
rect.height += rect.y;
|
|
rect.y = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wxFAIL_MSG( wxT("not implemented in this mode") );
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
// Gets the item rectangle
|
|
bool wxListCtrl::GetItemRect(long item, wxRect& rect, int code) const
|
|
{
|
|
return GetSubItemRect( item, wxLIST_GETSUBITEMRECT_WHOLEITEM, rect, code) ;
|
|
}
|
|
|
|
bool wxListCtrl::GetSubItemRect(long item, long subItem, wxRect& rect, int code) const
|
|
{
|
|
// ListView_GetSubItemRect() doesn't do subItem error checking and returns
|
|
// true even for the out of range values of it (even if the results are
|
|
// completely bogus in this case), so we check item validity ourselves
|
|
wxCHECK_MSG( subItem == wxLIST_GETSUBITEMRECT_WHOLEITEM ||
|
|
(subItem >= 0 && subItem < GetColumnCount()),
|
|
false, wxT("invalid sub item index") );
|
|
|
|
// use wxCHECK_MSG against "item" too, for coherency with the generic implementation:
|
|
wxCHECK_MSG( item >= 0 && item < GetItemCount(), false,
|
|
wxT("invalid item in GetSubItemRect") );
|
|
|
|
int codeWin;
|
|
if ( code == wxLIST_RECT_BOUNDS )
|
|
codeWin = LVIR_BOUNDS;
|
|
else if ( code == wxLIST_RECT_ICON )
|
|
codeWin = LVIR_ICON;
|
|
else if ( code == wxLIST_RECT_LABEL )
|
|
codeWin = LVIR_LABEL;
|
|
else
|
|
{
|
|
wxFAIL_MSG( wxT("incorrect code in GetItemRect() / GetSubItemRect()") );
|
|
codeWin = LVIR_BOUNDS;
|
|
}
|
|
|
|
RECT rectWin;
|
|
if ( !wxGetListCtrlSubItemRect
|
|
(
|
|
GetHwnd(),
|
|
item,
|
|
subItem == wxLIST_GETSUBITEMRECT_WHOLEITEM ? 0 : subItem,
|
|
codeWin,
|
|
rectWin
|
|
) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
wxCopyRECTToRect(rectWin, rect);
|
|
|
|
// there is no way to retrieve the first sub item bounding rectangle using
|
|
// wxGetListCtrlSubItemRect() as 0 means the whole item, so we need to
|
|
// truncate it at first column ourselves
|
|
if ( subItem == 0 && code == wxLIST_RECT_BOUNDS )
|
|
rect.width = GetColumnWidth(0);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Gets the item position
|
|
bool wxListCtrl::GetItemPosition(long item, wxPoint& pos) const
|
|
{
|
|
POINT pt;
|
|
|
|
bool success = (ListView_GetItemPosition(GetHwnd(), (int) item, &pt) != 0);
|
|
|
|
pos.x = pt.x; pos.y = pt.y;
|
|
return success;
|
|
}
|
|
|
|
// Sets the item position.
|
|
bool wxListCtrl::SetItemPosition(long item, const wxPoint& pos)
|
|
{
|
|
return (ListView_SetItemPosition(GetHwnd(), (int) item, pos.x, pos.y) != 0);
|
|
}
|
|
|
|
// Gets the number of items in the list control
|
|
int wxListCtrl::GetItemCount() const
|
|
{
|
|
return m_count;
|
|
}
|
|
|
|
wxSize wxListCtrl::GetItemSpacing() const
|
|
{
|
|
const int spacing = ListView_GetItemSpacing(GetHwnd(), (BOOL)HasFlag(wxLC_SMALL_ICON));
|
|
|
|
return wxSize(LOWORD(spacing), HIWORD(spacing));
|
|
}
|
|
|
|
#if WXWIN_COMPATIBILITY_2_6
|
|
|
|
int wxListCtrl::GetItemSpacing(bool isSmall) const
|
|
{
|
|
return ListView_GetItemSpacing(GetHwnd(), (BOOL) isSmall);
|
|
}
|
|
|
|
#endif // WXWIN_COMPATIBILITY_2_6
|
|
|
|
void wxListCtrl::SetItemTextColour( long item, const wxColour &col )
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
info.SetTextColour( col );
|
|
SetItem( info );
|
|
}
|
|
|
|
wxColour wxListCtrl::GetItemTextColour( long item ) const
|
|
{
|
|
wxColour col;
|
|
wxMSWListItemData *data = MSWGetItemData(item);
|
|
if ( data && data->attr )
|
|
col = data->attr->GetTextColour();
|
|
|
|
return col;
|
|
}
|
|
|
|
void wxListCtrl::SetItemBackgroundColour( long item, const wxColour &col )
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
info.SetBackgroundColour( col );
|
|
SetItem( info );
|
|
}
|
|
|
|
wxColour wxListCtrl::GetItemBackgroundColour( long item ) const
|
|
{
|
|
wxColour col;
|
|
wxMSWListItemData *data = MSWGetItemData(item);
|
|
if ( data && data->attr )
|
|
col = data->attr->GetBackgroundColour();
|
|
|
|
return col;
|
|
}
|
|
|
|
void wxListCtrl::SetItemFont( long item, const wxFont &f )
|
|
{
|
|
wxListItem info;
|
|
info.m_itemId = item;
|
|
info.SetFont( f );
|
|
SetItem( info );
|
|
}
|
|
|
|
wxFont wxListCtrl::GetItemFont( long item ) const
|
|
{
|
|
wxFont f;
|
|
wxMSWListItemData *data = MSWGetItemData(item);
|
|
if ( data && data->attr )
|
|
f = data->attr->GetFont();
|
|
|
|
return f;
|
|
}
|
|
|
|
// Gets the number of selected items in the list control
|
|
int wxListCtrl::GetSelectedItemCount() const
|
|
{
|
|
return ListView_GetSelectedCount(GetHwnd());
|
|
}
|
|
|
|
// Gets the text colour of the listview
|
|
wxColour wxListCtrl::GetTextColour() const
|
|
{
|
|
COLORREF ref = ListView_GetTextColor(GetHwnd());
|
|
wxColour col(GetRValue(ref), GetGValue(ref), GetBValue(ref));
|
|
return col;
|
|
}
|
|
|
|
// Sets the text colour of the listview
|
|
void wxListCtrl::SetTextColour(const wxColour& col)
|
|
{
|
|
ListView_SetTextColor(GetHwnd(), PALETTERGB(col.Red(), col.Green(), col.Blue()));
|
|
}
|
|
|
|
// Gets the index of the topmost visible item when in
|
|
// list or report view
|
|
long wxListCtrl::GetTopItem() const
|
|
{
|
|
return (long) ListView_GetTopIndex(GetHwnd());
|
|
}
|
|
|
|
// Searches for an item, starting from 'item'.
|
|
// 'geometry' is one of
|
|
// wxLIST_NEXT_ABOVE/ALL/BELOW/LEFT/RIGHT.
|
|
// 'state' is a state bit flag, one or more of
|
|
// wxLIST_STATE_DROPHILITED/FOCUSED/SELECTED/CUT.
|
|
// item can be -1 to find the first item that matches the
|
|
// specified flags.
|
|
// Returns the item or -1 if unsuccessful.
|
|
long wxListCtrl::GetNextItem(long item, int geom, int state) const
|
|
{
|
|
long flags = 0;
|
|
|
|
if ( geom == wxLIST_NEXT_ABOVE )
|
|
flags |= LVNI_ABOVE;
|
|
if ( geom == wxLIST_NEXT_ALL )
|
|
flags |= LVNI_ALL;
|
|
if ( geom == wxLIST_NEXT_BELOW )
|
|
flags |= LVNI_BELOW;
|
|
if ( geom == wxLIST_NEXT_LEFT )
|
|
flags |= LVNI_TOLEFT;
|
|
if ( geom == wxLIST_NEXT_RIGHT )
|
|
flags |= LVNI_TORIGHT;
|
|
|
|
if ( state & wxLIST_STATE_CUT )
|
|
flags |= LVNI_CUT;
|
|
if ( state & wxLIST_STATE_DROPHILITED )
|
|
flags |= LVNI_DROPHILITED;
|
|
if ( state & wxLIST_STATE_FOCUSED )
|
|
flags |= LVNI_FOCUSED;
|
|
if ( state & wxLIST_STATE_SELECTED )
|
|
flags |= LVNI_SELECTED;
|
|
|
|
return (long) ListView_GetNextItem(GetHwnd(), item, flags);
|
|
}
|
|
|
|
|
|
wxImageList *wxListCtrl::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 wxListCtrl::SetImageList(wxImageList *imageList, int which)
|
|
{
|
|
int flags = 0;
|
|
if ( which == wxIMAGE_LIST_NORMAL )
|
|
{
|
|
flags = LVSIL_NORMAL;
|
|
if (m_ownsImageListNormal) delete m_imageListNormal;
|
|
m_imageListNormal = imageList;
|
|
m_ownsImageListNormal = false;
|
|
}
|
|
else if ( which == wxIMAGE_LIST_SMALL )
|
|
{
|
|
flags = LVSIL_SMALL;
|
|
if (m_ownsImageListSmall) delete m_imageListSmall;
|
|
m_imageListSmall = imageList;
|
|
m_ownsImageListSmall = false;
|
|
}
|
|
else if ( which == wxIMAGE_LIST_STATE )
|
|
{
|
|
flags = LVSIL_STATE;
|
|
if (m_ownsImageListState) delete m_imageListState;
|
|
m_imageListState = imageList;
|
|
m_ownsImageListState = false;
|
|
}
|
|
(void) ListView_SetImageList(GetHwnd(), (HIMAGELIST) imageList ? imageList->GetHIMAGELIST() : 0, flags);
|
|
}
|
|
|
|
void wxListCtrl::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;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Operations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Arranges the items
|
|
bool wxListCtrl::Arrange(int flag)
|
|
{
|
|
UINT code = 0;
|
|
if ( flag == wxLIST_ALIGN_LEFT )
|
|
code = LVA_ALIGNLEFT;
|
|
else if ( flag == wxLIST_ALIGN_TOP )
|
|
code = LVA_ALIGNTOP;
|
|
else if ( flag == wxLIST_ALIGN_DEFAULT )
|
|
code = LVA_DEFAULT;
|
|
else if ( flag == wxLIST_ALIGN_SNAP_TO_GRID )
|
|
code = LVA_SNAPTOGRID;
|
|
|
|
return (ListView_Arrange(GetHwnd(), code) != 0);
|
|
}
|
|
|
|
// Deletes an item
|
|
bool wxListCtrl::DeleteItem(long item)
|
|
{
|
|
if ( !ListView_DeleteItem(GetHwnd(), (int) item) )
|
|
{
|
|
wxLogLastError(wxT("ListView_DeleteItem"));
|
|
return false;
|
|
}
|
|
|
|
m_count--;
|
|
wxASSERT_MSG( m_count == ListView_GetItemCount(GetHwnd()),
|
|
wxT("m_count should match ListView_GetItemCount"));
|
|
|
|
// the virtual list control doesn't refresh itself correctly, help it
|
|
if ( IsVirtual() )
|
|
{
|
|
// we need to refresh all the lines below the one which was deleted
|
|
wxRect rectItem;
|
|
if ( item > 0 && GetItemCount() )
|
|
{
|
|
GetItemRect(item - 1, rectItem);
|
|
}
|
|
else
|
|
{
|
|
rectItem.y =
|
|
rectItem.height = 0;
|
|
}
|
|
|
|
wxRect rectWin = GetRect();
|
|
rectWin.height = rectWin.GetBottom() - rectItem.GetBottom();
|
|
rectWin.y = rectItem.GetBottom();
|
|
|
|
RefreshRect(rectWin);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Deletes all items
|
|
bool wxListCtrl::DeleteAllItems()
|
|
{
|
|
// Calling ListView_DeleteAllItems() will always generate an event but we
|
|
// shouldn't do it if the control is empty
|
|
return !GetItemCount() || ListView_DeleteAllItems(GetHwnd()) != 0;
|
|
}
|
|
|
|
// Deletes all items
|
|
bool wxListCtrl::DeleteAllColumns()
|
|
{
|
|
while ( m_colCount > 0 )
|
|
{
|
|
if ( ListView_DeleteColumn(GetHwnd(), 0) == 0 )
|
|
{
|
|
wxLogLastError(wxT("ListView_DeleteColumn"));
|
|
|
|
return false;
|
|
}
|
|
|
|
m_colCount--;
|
|
}
|
|
|
|
wxASSERT_MSG( m_colCount == 0, wxT("no columns should be left") );
|
|
|
|
return true;
|
|
}
|
|
|
|
// Deletes a column
|
|
bool wxListCtrl::DeleteColumn(int col)
|
|
{
|
|
bool success = (ListView_DeleteColumn(GetHwnd(), col) != 0);
|
|
|
|
if ( success && (m_colCount > 0) )
|
|
m_colCount --;
|
|
return success;
|
|
}
|
|
|
|
// Clears items, and columns if there are any.
|
|
void wxListCtrl::ClearAll()
|
|
{
|
|
DeleteAllItems();
|
|
if ( m_colCount > 0 )
|
|
DeleteAllColumns();
|
|
}
|
|
|
|
void wxListCtrl::InitEditControl(WXHWND hWnd)
|
|
{
|
|
m_textCtrl->SetHWND(hWnd);
|
|
m_textCtrl->SubclassWin(hWnd);
|
|
m_textCtrl->SetParent(this);
|
|
|
|
// we must disallow TABbing away from the control while the edit control is
|
|
// shown because this leaves it in some strange state (just try removing
|
|
// this line and then pressing TAB while editing an item in listctrl
|
|
// inside a panel)
|
|
m_textCtrl->SetWindowStyle(m_textCtrl->GetWindowStyle() | wxTE_PROCESS_TAB);
|
|
}
|
|
|
|
wxTextCtrl* wxListCtrl::EditLabel(long item, wxClassInfo* textControlClass)
|
|
{
|
|
wxCHECK_MSG( textControlClass->IsKindOf(CLASSINFO(wxTextCtrl)), NULL,
|
|
"control used for label editing must be a wxTextCtrl" );
|
|
|
|
// ListView_EditLabel requires that the list has focus.
|
|
SetFocus();
|
|
|
|
// create m_textCtrl here before calling ListView_EditLabel() because it
|
|
// generates wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT event from inside it and
|
|
// the user handler for it can call GetEditControl() resulting in an on
|
|
// demand creation of a stock wxTextCtrl instead of the control of a
|
|
// (possibly) custom wxClassInfo
|
|
DeleteEditControl();
|
|
m_textCtrl = (wxTextCtrl *)textControlClass->CreateObject();
|
|
|
|
WXHWND hWnd = (WXHWND) ListView_EditLabel(GetHwnd(), item);
|
|
if ( !hWnd )
|
|
{
|
|
// failed to start editing
|
|
wxDELETE(m_textCtrl);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// if GetEditControl() hasn't been called, we need to initialize the edit
|
|
// control ourselves
|
|
if ( !m_textCtrl->GetHWND() )
|
|
InitEditControl(hWnd);
|
|
|
|
return m_textCtrl;
|
|
}
|
|
|
|
// End label editing, optionally cancelling the edit
|
|
bool wxListCtrl::EndEditLabel(bool cancel)
|
|
{
|
|
// m_textCtrl is not always ready, ie. in EVT_LIST_BEGIN_LABEL_EDIT
|
|
HWND hwnd = ListView_GetEditControl(GetHwnd());
|
|
if ( !hwnd )
|
|
return false;
|
|
|
|
// Newer versions of Windows have a special ListView_CancelEditLabel()
|
|
// message for cancelling editing but it, rather counter-intuitively, keeps
|
|
// the last text entered in the dialog while cancelling as we do it below
|
|
// restores the original text which is the more expected behaviour.
|
|
|
|
// We shouldn't destroy the control ourselves according to MSDN, which
|
|
// proposes WM_CANCELMODE to do this, but it doesn't seem to work so
|
|
// emulate the corresponding user action instead.
|
|
::SendMessage(hwnd, WM_KEYDOWN, cancel ? VK_ESCAPE : VK_RETURN, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Ensures this item is visible
|
|
bool wxListCtrl::EnsureVisible(long item)
|
|
{
|
|
return ListView_EnsureVisible(GetHwnd(), (int) item, FALSE) != FALSE;
|
|
}
|
|
|
|
// Find an item whose label matches this string, starting from the item after 'start'
|
|
// or the beginning if 'start' is -1.
|
|
long wxListCtrl::FindItem(long start, const wxString& str, bool partial)
|
|
{
|
|
LV_FINDINFO findInfo;
|
|
|
|
findInfo.flags = LVFI_STRING;
|
|
if ( partial )
|
|
findInfo.flags |= LVFI_PARTIAL;
|
|
findInfo.psz = str.wx_str();
|
|
|
|
// ListView_FindItem() excludes the first item from search and to look
|
|
// through all the items you need to start from -1 which is unnatural and
|
|
// inconsistent with the generic version - so we adjust the index
|
|
if (start != -1)
|
|
start --;
|
|
return ListView_FindItem(GetHwnd(), start, &findInfo);
|
|
}
|
|
|
|
// Find an item whose data matches this data, starting from the item after
|
|
// 'start' or the beginning if 'start' is -1.
|
|
long wxListCtrl::FindItem(long start, wxUIntPtr data)
|
|
{
|
|
// we can't use ListView_FindItem() directly as we don't store the data
|
|
// pointer itself in the control but rather our own internal data, so first
|
|
// we need to find the right value to search for (and there can be several
|
|
// of them)
|
|
int idx = wxNOT_FOUND;
|
|
const unsigned count = m_internalData.size();
|
|
for ( unsigned n = 0; n < count; n++ )
|
|
{
|
|
if ( m_internalData[n]->lParam == (LPARAM)data )
|
|
{
|
|
LV_FINDINFO findInfo;
|
|
findInfo.flags = LVFI_PARAM;
|
|
findInfo.lParam = (LPARAM)wxPtrToUInt(m_internalData[n]);
|
|
|
|
int rc = ListView_FindItem(GetHwnd(), start, &findInfo);
|
|
if ( rc != -1 )
|
|
{
|
|
if ( idx == wxNOT_FOUND || rc < idx )
|
|
{
|
|
idx = rc;
|
|
if ( idx == start + 1 )
|
|
{
|
|
// we can stop here, we don't risk finding a closer
|
|
// match
|
|
break;
|
|
}
|
|
}
|
|
//else: this item is after the previously found one
|
|
}
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
// Find an item nearest this position in the specified direction, starting from
|
|
// the item after 'start' or the beginning if 'start' is -1.
|
|
long wxListCtrl::FindItem(long start, const wxPoint& pt, int direction)
|
|
{
|
|
LV_FINDINFO findInfo;
|
|
|
|
findInfo.flags = LVFI_NEARESTXY;
|
|
findInfo.pt.x = pt.x;
|
|
findInfo.pt.y = pt.y;
|
|
findInfo.vkDirection = VK_RIGHT;
|
|
|
|
if ( direction == wxLIST_FIND_UP )
|
|
findInfo.vkDirection = VK_UP;
|
|
else if ( direction == wxLIST_FIND_DOWN )
|
|
findInfo.vkDirection = VK_DOWN;
|
|
else if ( direction == wxLIST_FIND_LEFT )
|
|
findInfo.vkDirection = VK_LEFT;
|
|
else if ( direction == wxLIST_FIND_RIGHT )
|
|
findInfo.vkDirection = VK_RIGHT;
|
|
|
|
return ListView_FindItem(GetHwnd(), start, &findInfo);
|
|
}
|
|
|
|
// Determines which item (if any) is at the specified point,
|
|
// giving details in 'flags' (see wxLIST_HITTEST_... flags above)
|
|
long
|
|
wxListCtrl::HitTest(const wxPoint& point, int& flags, long *ptrSubItem) const
|
|
{
|
|
LV_HITTESTINFO hitTestInfo;
|
|
hitTestInfo.pt.x = (int) point.x;
|
|
hitTestInfo.pt.y = (int) point.y;
|
|
|
|
long item;
|
|
#ifdef LVM_SUBITEMHITTEST
|
|
if ( ptrSubItem && wxApp::GetComCtl32Version() >= 470 )
|
|
{
|
|
item = ListView_SubItemHitTest(GetHwnd(), &hitTestInfo);
|
|
*ptrSubItem = hitTestInfo.iSubItem;
|
|
}
|
|
else
|
|
#endif // LVM_SUBITEMHITTEST
|
|
{
|
|
item = ListView_HitTest(GetHwnd(), &hitTestInfo);
|
|
}
|
|
|
|
flags = 0;
|
|
|
|
if ( hitTestInfo.flags & LVHT_ABOVE )
|
|
flags |= wxLIST_HITTEST_ABOVE;
|
|
if ( hitTestInfo.flags & LVHT_BELOW )
|
|
flags |= wxLIST_HITTEST_BELOW;
|
|
if ( hitTestInfo.flags & LVHT_TOLEFT )
|
|
flags |= wxLIST_HITTEST_TOLEFT;
|
|
if ( hitTestInfo.flags & LVHT_TORIGHT )
|
|
flags |= wxLIST_HITTEST_TORIGHT;
|
|
|
|
if ( hitTestInfo.flags & LVHT_NOWHERE )
|
|
flags |= wxLIST_HITTEST_NOWHERE;
|
|
|
|
// note a bug or at least a very strange feature of comtl32.dll (tested
|
|
// with version 4.0 under Win95 and 6.0 under Win 2003): if you click to
|
|
// the right of the item label, ListView_HitTest() returns a combination of
|
|
// LVHT_ONITEMICON, LVHT_ONITEMLABEL and LVHT_ONITEMSTATEICON -- filter out
|
|
// the bits which don't make sense
|
|
if ( hitTestInfo.flags & LVHT_ONITEMLABEL )
|
|
{
|
|
flags |= wxLIST_HITTEST_ONITEMLABEL;
|
|
|
|
// do not translate LVHT_ONITEMICON here, as per above
|
|
}
|
|
else
|
|
{
|
|
if ( hitTestInfo.flags & LVHT_ONITEMICON )
|
|
flags |= wxLIST_HITTEST_ONITEMICON;
|
|
if ( hitTestInfo.flags & LVHT_ONITEMSTATEICON )
|
|
flags |= wxLIST_HITTEST_ONITEMSTATEICON;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
// Inserts an item, returning the index of the new item if successful,
|
|
// -1 otherwise.
|
|
long wxListCtrl::InsertItem(const wxListItem& info)
|
|
{
|
|
wxASSERT_MSG( !IsVirtual(), wxT("can't be used with virtual controls") );
|
|
|
|
LV_ITEM item;
|
|
wxConvertToMSWListItem(this, info, item);
|
|
item.mask &= ~LVIF_PARAM;
|
|
|
|
// check whether we need to allocate our internal data
|
|
bool needInternalData = (info.m_mask & wxLIST_MASK_DATA) ||
|
|
info.HasAttributes();
|
|
if ( needInternalData )
|
|
{
|
|
item.mask |= LVIF_PARAM;
|
|
|
|
wxMSWListItemData * const data = new wxMSWListItemData;
|
|
m_internalData.push_back(data);
|
|
item.lParam = (LPARAM)data;
|
|
|
|
if ( info.m_mask & wxLIST_MASK_DATA )
|
|
data->lParam = info.m_data;
|
|
|
|
// check whether it has any custom attributes
|
|
if ( info.HasAttributes() )
|
|
{
|
|
// take copy of attributes
|
|
data->attr = new wxListItemAttr(*info.GetAttributes());
|
|
|
|
// and remember that we have some now...
|
|
m_hasAnyAttr = true;
|
|
}
|
|
}
|
|
|
|
const long rv = ListView_InsertItem(GetHwnd(), & item);
|
|
|
|
// failing to insert the item is really unexpected
|
|
wxCHECK_MSG( rv != -1, rv, "failed to insert an item in wxListCtrl" );
|
|
|
|
m_count++;
|
|
wxASSERT_MSG( m_count == ListView_GetItemCount(GetHwnd()),
|
|
wxT("m_count should match ListView_GetItemCount"));
|
|
|
|
return rv;
|
|
}
|
|
|
|
long wxListCtrl::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);
|
|
}
|
|
|
|
// Inserts an image item
|
|
long wxListCtrl::InsertItem(long index, int imageIndex)
|
|
{
|
|
wxListItem info;
|
|
info.m_image = imageIndex;
|
|
info.m_mask = wxLIST_MASK_IMAGE;
|
|
info.m_itemId = index;
|
|
return InsertItem(info);
|
|
}
|
|
|
|
// Inserts an image/string item
|
|
long wxListCtrl::InsertItem(long index, const wxString& label, int imageIndex)
|
|
{
|
|
wxListItem info;
|
|
info.m_image = imageIndex;
|
|
info.m_text = label;
|
|
info.m_mask = wxLIST_MASK_TEXT;
|
|
if (imageIndex > -1)
|
|
info.m_mask |= wxLIST_MASK_IMAGE;
|
|
info.m_itemId = index;
|
|
return InsertItem(info);
|
|
}
|
|
|
|
// For list view mode (only), inserts a column.
|
|
long wxListCtrl::InsertColumn(long col, const wxListItem& item)
|
|
{
|
|
LV_COLUMN lvCol;
|
|
wxConvertToMSWListCol(GetHwnd(), col, item, lvCol);
|
|
|
|
if ( !(lvCol.mask & LVCF_WIDTH) )
|
|
{
|
|
// always give some width to the new column: this one is compatible
|
|
// with the generic version
|
|
lvCol.mask |= LVCF_WIDTH;
|
|
lvCol.cx = 80;
|
|
}
|
|
|
|
long n = ListView_InsertColumn(GetHwnd(), col, &lvCol);
|
|
if ( n != -1 )
|
|
{
|
|
m_colCount++;
|
|
}
|
|
else // failed to insert?
|
|
{
|
|
wxLogDebug(wxT("Failed to insert the column '%s' into listview!"),
|
|
lvCol.pszText);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
long wxListCtrl::InsertColumn(long col,
|
|
const wxString& heading,
|
|
int format,
|
|
int width)
|
|
{
|
|
wxListItem item;
|
|
item.m_mask = wxLIST_MASK_TEXT | wxLIST_MASK_FORMAT;
|
|
item.m_text = heading;
|
|
if ( width > -1 )
|
|
{
|
|
item.m_mask |= wxLIST_MASK_WIDTH;
|
|
item.m_width = width;
|
|
}
|
|
item.m_format = format;
|
|
|
|
return InsertColumn(col, item);
|
|
}
|
|
|
|
// scroll the control by the given number of pixels (exception: in list view,
|
|
// dx is interpreted as number of columns)
|
|
bool wxListCtrl::ScrollList(int dx, int dy)
|
|
{
|
|
if ( !ListView_Scroll(GetHwnd(), dx, dy) )
|
|
{
|
|
wxLogDebug(wxT("ListView_Scroll(%d, %d) failed"), dx, dy);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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.
|
|
|
|
// Internal structures for proxying the user compare function
|
|
// so that we can pass it the *real* user data
|
|
|
|
// translate lParam data and call user func
|
|
struct wxInternalDataSort
|
|
{
|
|
wxListCtrlCompare user_fn;
|
|
wxIntPtr data;
|
|
};
|
|
|
|
int CALLBACK wxInternalDataCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
|
{
|
|
wxInternalDataSort * const internalData = (wxInternalDataSort *) lParamSort;
|
|
|
|
wxMSWListItemData *data1 = (wxMSWListItemData *) lParam1;
|
|
wxMSWListItemData *data2 = (wxMSWListItemData *) lParam2;
|
|
|
|
wxIntPtr d1 = (data1 == NULL ? 0 : data1->lParam);
|
|
wxIntPtr d2 = (data2 == NULL ? 0 : data2->lParam);
|
|
|
|
return internalData->user_fn(d1, d2, internalData->data);
|
|
|
|
}
|
|
|
|
bool wxListCtrl::SortItems(wxListCtrlCompare fn, wxIntPtr data)
|
|
{
|
|
wxInternalDataSort internalData;
|
|
internalData.user_fn = fn;
|
|
internalData.data = data;
|
|
|
|
// WPARAM cast is needed for mingw/cygwin
|
|
if ( !ListView_SortItems(GetHwnd(),
|
|
wxInternalDataCompareFunc,
|
|
(WPARAM) &internalData) )
|
|
{
|
|
wxLogDebug(wxT("ListView_SortItems() failed"));
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// message processing
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxListCtrl::MSWShouldPreProcessMessage(WXMSG* msg)
|
|
{
|
|
if ( msg->message == WM_KEYDOWN )
|
|
{
|
|
// Only eat VK_RETURN if not being used by the application in
|
|
// conjunction with modifiers
|
|
if ( msg->wParam == VK_RETURN && !wxIsAnyModifierDown() )
|
|
{
|
|
// we need VK_RETURN to generate wxEVT_COMMAND_LIST_ITEM_ACTIVATED
|
|
return false;
|
|
}
|
|
}
|
|
return wxControl::MSWShouldPreProcessMessage(msg);
|
|
}
|
|
|
|
bool wxListCtrl::MSWCommand(WXUINT cmd, WXWORD id_)
|
|
{
|
|
const int id = (signed short)id_;
|
|
if (cmd == EN_UPDATE)
|
|
{
|
|
wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, id);
|
|
event.SetEventObject( this );
|
|
ProcessCommand(event);
|
|
return true;
|
|
}
|
|
else if (cmd == EN_KILLFOCUS)
|
|
{
|
|
wxCommandEvent event(wxEVT_KILL_FOCUS, id);
|
|
event.SetEventObject( this );
|
|
ProcessCommand(event);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// utility used by wxListCtrl::MSWOnNotify and by wxDataViewHeaderWindowMSW::MSWOnNotify
|
|
int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick)
|
|
{
|
|
// find the column clicked: we have to search for it ourselves as the
|
|
// notification message doesn't provide this info
|
|
|
|
// where did the click occur?
|
|
#if defined(__WXWINCE__) && !defined(__HANDHELDPC__) && _WIN32_WCE < 400
|
|
if ( nmhdr->code == GN_CONTEXTMENU )
|
|
{
|
|
*ptClick = ((NMRGINFO*)nmhdr)->ptAction;
|
|
}
|
|
else
|
|
#endif //__WXWINCE__
|
|
{
|
|
wxGetCursorPosMSW(ptClick);
|
|
}
|
|
|
|
// we need to use listctrl coordinates for the event point so this is what
|
|
// we return in ptClick, but for comparison with Header_GetItemRect()
|
|
// result below we need to use header window coordinates
|
|
POINT ptClickHeader = *ptClick;
|
|
if ( !::ScreenToClient(nmhdr->hwndFrom, &ptClickHeader) )
|
|
{
|
|
wxLogLastError(wxT("ScreenToClient(listctrl header)"));
|
|
}
|
|
|
|
if ( !::ScreenToClient(::GetParent(nmhdr->hwndFrom), ptClick) )
|
|
{
|
|
wxLogLastError(wxT("ScreenToClient(listctrl)"));
|
|
}
|
|
|
|
const int colCount = Header_GetItemCount(nmhdr->hwndFrom);
|
|
for ( int col = 0; col < colCount; col++ )
|
|
{
|
|
RECT rect;
|
|
if ( Header_GetItemRect(nmhdr->hwndFrom, col, &rect) )
|
|
{
|
|
if ( ::PtInRect(&rect, ptClickHeader) )
|
|
{
|
|
return col;
|
|
}
|
|
}
|
|
}
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
|
|
{
|
|
|
|
// prepare the event
|
|
// -----------------
|
|
|
|
wxListEvent event(wxEVT_NULL, m_windowId);
|
|
event.SetEventObject(this);
|
|
|
|
wxEventType eventType = wxEVT_NULL;
|
|
|
|
NMHDR *nmhdr = (NMHDR *)lParam;
|
|
|
|
// if your compiler is as broken as this, you should really change it: this
|
|
// code is needed for normal operation! #ifdef below is only useful for
|
|
// automatic rebuilds which are done with a very old compiler version
|
|
#ifdef HDN_BEGINTRACKA
|
|
|
|
// check for messages from the header (in report view)
|
|
HWND hwndHdr = ListView_GetHeader(GetHwnd());
|
|
|
|
// is it a message from the header?
|
|
if ( nmhdr->hwndFrom == hwndHdr )
|
|
{
|
|
HD_NOTIFY *nmHDR = (HD_NOTIFY *)nmhdr;
|
|
|
|
event.m_itemIndex = -1;
|
|
|
|
bool ignore = false;
|
|
switch ( nmhdr->code )
|
|
{
|
|
// yet another comctl32.dll bug: under NT/W2K it sends Unicode
|
|
// TRACK messages even to ANSI programs: on my system I get
|
|
// HDN_BEGINTRACKW and HDN_ENDTRACKA!
|
|
//
|
|
// work around is to simply catch both versions and hope that it
|
|
// works (why should this message exist in ANSI and Unicode is
|
|
// beyond me as it doesn't deal with strings at all...)
|
|
//
|
|
// another problem is that HDN_TRACK is not sent at all by header
|
|
// with HDS_FULLDRAG style which is used by default by wxListCtrl
|
|
// under recent Windows versions (starting from at least XP) so we
|
|
// need to use HDN_ITEMCHANGING instead of it
|
|
case HDN_BEGINTRACKA:
|
|
case HDN_BEGINTRACKW:
|
|
eventType = wxEVT_COMMAND_LIST_COL_BEGIN_DRAG;
|
|
// fall through
|
|
|
|
case HDN_ITEMCHANGING:
|
|
if ( eventType == wxEVT_NULL )
|
|
{
|
|
if ( !nmHDR->pitem || !(nmHDR->pitem->mask & HDI_WIDTH) )
|
|
{
|
|
// something other than the width is being changed,
|
|
// ignore it
|
|
ignore = true;
|
|
break;
|
|
}
|
|
|
|
// also ignore the events sent when the width didn't really
|
|
// change: this is not just an optimization but also gets
|
|
// rid of a useless and unexpected DRAGGING event which
|
|
// would otherwise be sent after the END_DRAG one as we get
|
|
// an HDN_ITEMCHANGING after HDN_ENDTRACK for some reason
|
|
if ( nmHDR->pitem->cxy == GetColumnWidth(nmHDR->iItem) )
|
|
{
|
|
ignore = true;
|
|
break;
|
|
}
|
|
|
|
eventType = wxEVT_COMMAND_LIST_COL_DRAGGING;
|
|
}
|
|
// fall through
|
|
|
|
case HDN_ENDTRACKA:
|
|
case HDN_ENDTRACKW:
|
|
if ( eventType == wxEVT_NULL )
|
|
eventType = wxEVT_COMMAND_LIST_COL_END_DRAG;
|
|
|
|
event.m_item.m_width = nmHDR->pitem->cxy;
|
|
event.m_col = nmHDR->iItem;
|
|
break;
|
|
|
|
#if defined(__WXWINCE__) && !defined(__HANDHELDPC__) && _WIN32_WCE < 400
|
|
case GN_CONTEXTMENU:
|
|
#endif //__WXWINCE__
|
|
case NM_RCLICK:
|
|
{
|
|
POINT ptClick;
|
|
|
|
eventType = wxEVT_COMMAND_LIST_COL_RIGHT_CLICK;
|
|
event.m_col = wxMSWGetColumnClicked(nmhdr, &ptClick);
|
|
event.m_pointDrag.x = ptClick.x;
|
|
event.m_pointDrag.y = ptClick.y;
|
|
}
|
|
break;
|
|
|
|
case HDN_GETDISPINFOW:
|
|
// letting Windows XP handle this message results in mysterious
|
|
// crashes in comctl32.dll seemingly because of bad message
|
|
// parameters
|
|
//
|
|
// I have no idea what is the real cause of the bug (which is,
|
|
// just to make things interesting, impossible to reproduce
|
|
// reliably) but ignoring all these messages does fix it and
|
|
// doesn't seem to have any negative consequences
|
|
return true;
|
|
|
|
default:
|
|
ignore = true;
|
|
}
|
|
|
|
if ( ignore )
|
|
return wxControl::MSWOnNotify(idCtrl, lParam, result);
|
|
}
|
|
else
|
|
#endif // defined(HDN_BEGINTRACKA)
|
|
if ( nmhdr->hwndFrom == GetHwnd() )
|
|
{
|
|
// almost all messages use NM_LISTVIEW
|
|
NM_LISTVIEW *nmLV = (NM_LISTVIEW *)nmhdr;
|
|
|
|
const int iItem = nmLV->iItem;
|
|
|
|
|
|
// If we have a valid item then check if there is a data value
|
|
// associated with it and put it in the event.
|
|
if ( iItem >= 0 && iItem < GetItemCount() )
|
|
{
|
|
wxMSWListItemData *internaldata =
|
|
MSWGetItemData(iItem);
|
|
|
|
if ( internaldata )
|
|
event.m_item.m_data = internaldata->lParam;
|
|
}
|
|
|
|
bool processed = true;
|
|
switch ( nmhdr->code )
|
|
{
|
|
case LVN_BEGINRDRAG:
|
|
eventType = wxEVT_COMMAND_LIST_BEGIN_RDRAG;
|
|
// fall through
|
|
|
|
case LVN_BEGINDRAG:
|
|
if ( eventType == wxEVT_NULL )
|
|
{
|
|
eventType = wxEVT_COMMAND_LIST_BEGIN_DRAG;
|
|
}
|
|
|
|
event.m_itemIndex = iItem;
|
|
event.m_pointDrag.x = nmLV->ptAction.x;
|
|
event.m_pointDrag.y = nmLV->ptAction.y;
|
|
break;
|
|
|
|
// NB: we have to handle both *A and *W versions here because some
|
|
// versions of comctl32.dll send ANSI messages even to the
|
|
// Unicode windows
|
|
case LVN_BEGINLABELEDITA:
|
|
case LVN_BEGINLABELEDITW:
|
|
{
|
|
wxLV_ITEM item;
|
|
if ( nmhdr->code == LVN_BEGINLABELEDITA )
|
|
{
|
|
item.Init(((LV_DISPINFOA *)lParam)->item);
|
|
}
|
|
else // LVN_BEGINLABELEDITW
|
|
{
|
|
item.Init(((LV_DISPINFOW *)lParam)->item);
|
|
}
|
|
|
|
eventType = wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT;
|
|
wxConvertFromMSWListItem(GetHwnd(), event.m_item, item);
|
|
event.m_itemIndex = event.m_item.m_itemId;
|
|
}
|
|
break;
|
|
|
|
case LVN_ENDLABELEDITA:
|
|
case LVN_ENDLABELEDITW:
|
|
{
|
|
wxLV_ITEM item;
|
|
if ( nmhdr->code == LVN_ENDLABELEDITA )
|
|
{
|
|
item.Init(((LV_DISPINFOA *)lParam)->item);
|
|
}
|
|
else // LVN_ENDLABELEDITW
|
|
{
|
|
item.Init(((LV_DISPINFOW *)lParam)->item);
|
|
}
|
|
|
|
// was editing cancelled?
|
|
const LV_ITEM& lvi = (LV_ITEM)item;
|
|
if ( !lvi.pszText || lvi.iItem == -1 )
|
|
{
|
|
// EDIT control will be deleted by the list control
|
|
// itself so prevent us from deleting it as well
|
|
DeleteEditControl();
|
|
|
|
event.SetEditCanceled(true);
|
|
}
|
|
|
|
eventType = wxEVT_COMMAND_LIST_END_LABEL_EDIT;
|
|
wxConvertFromMSWListItem(NULL, event.m_item, item);
|
|
event.m_itemIndex = event.m_item.m_itemId;
|
|
}
|
|
break;
|
|
|
|
case LVN_COLUMNCLICK:
|
|
eventType = wxEVT_COMMAND_LIST_COL_CLICK;
|
|
event.m_itemIndex = -1;
|
|
event.m_col = nmLV->iSubItem;
|
|
break;
|
|
|
|
case LVN_DELETEALLITEMS:
|
|
eventType = wxEVT_COMMAND_LIST_DELETE_ALL_ITEMS;
|
|
event.m_itemIndex = -1;
|
|
break;
|
|
|
|
case LVN_DELETEITEM:
|
|
if ( m_count == 0 )
|
|
{
|
|
// this should be prevented by the post-processing code
|
|
// below, but "just in case"
|
|
return false;
|
|
}
|
|
|
|
eventType = wxEVT_COMMAND_LIST_DELETE_ITEM;
|
|
event.m_itemIndex = iItem;
|
|
|
|
break;
|
|
|
|
case LVN_INSERTITEM:
|
|
eventType = wxEVT_COMMAND_LIST_INSERT_ITEM;
|
|
event.m_itemIndex = iItem;
|
|
break;
|
|
|
|
case LVN_ITEMCHANGED:
|
|
// we translate this catch all message into more interesting
|
|
// (and more easy to process) wxWidgets events
|
|
|
|
// first of all, we deal with the state change events only and
|
|
// only for valid items (item == -1 for the virtual list
|
|
// control)
|
|
if ( nmLV->uChanged & LVIF_STATE && iItem != -1 )
|
|
{
|
|
// temp vars for readability
|
|
const UINT stOld = nmLV->uOldState;
|
|
const UINT stNew = nmLV->uNewState;
|
|
|
|
event.m_item.SetId(iItem);
|
|
event.m_item.SetMask(wxLIST_MASK_TEXT |
|
|
wxLIST_MASK_IMAGE |
|
|
wxLIST_MASK_DATA);
|
|
GetItem(event.m_item);
|
|
|
|
// has the focus changed?
|
|
if ( !(stOld & LVIS_FOCUSED) && (stNew & LVIS_FOCUSED) )
|
|
{
|
|
eventType = wxEVT_COMMAND_LIST_ITEM_FOCUSED;
|
|
event.m_itemIndex = iItem;
|
|
}
|
|
|
|
if ( (stNew & LVIS_SELECTED) != (stOld & LVIS_SELECTED) )
|
|
{
|
|
if ( eventType != wxEVT_NULL )
|
|
{
|
|
// focus and selection have both changed: send the
|
|
// focus event from here and the selection one
|
|
// below
|
|
event.SetEventType(eventType);
|
|
(void)HandleWindowEvent(event);
|
|
}
|
|
else // no focus event to send
|
|
{
|
|
// then need to set m_itemIndex as it wasn't done
|
|
// above
|
|
event.m_itemIndex = iItem;
|
|
}
|
|
|
|
eventType = stNew & LVIS_SELECTED
|
|
? wxEVT_COMMAND_LIST_ITEM_SELECTED
|
|
: wxEVT_COMMAND_LIST_ITEM_DESELECTED;
|
|
}
|
|
}
|
|
|
|
if ( eventType == wxEVT_NULL )
|
|
{
|
|
// not an interesting event for us
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case LVN_KEYDOWN:
|
|
{
|
|
LV_KEYDOWN *info = (LV_KEYDOWN *)lParam;
|
|
WORD wVKey = info->wVKey;
|
|
|
|
// get the current selection
|
|
long lItem = GetNextItem(-1,
|
|
wxLIST_NEXT_ALL,
|
|
wxLIST_STATE_SELECTED);
|
|
|
|
// <Enter> or <Space> activate the selected item if any (but
|
|
// not with any modifiers as they have a predefined meaning
|
|
// then)
|
|
if ( lItem != -1 &&
|
|
(wVKey == VK_RETURN || wVKey == VK_SPACE) &&
|
|
!wxIsAnyModifierDown() )
|
|
{
|
|
eventType = wxEVT_COMMAND_LIST_ITEM_ACTIVATED;
|
|
}
|
|
else
|
|
{
|
|
eventType = wxEVT_COMMAND_LIST_KEY_DOWN;
|
|
|
|
event.m_code = wxMSWKeyboard::VKToWX(wVKey);
|
|
|
|
if ( event.m_code == WXK_NONE )
|
|
{
|
|
// We can't translate this to a standard key code,
|
|
// until support for Unicode key codes is added to
|
|
// wxListEvent we just ignore them.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
event.m_itemIndex =
|
|
event.m_item.m_itemId = lItem;
|
|
|
|
if ( lItem != -1 )
|
|
{
|
|
// fill the other fields too
|
|
event.m_item.m_text = GetItemText(lItem);
|
|
event.m_item.m_data = GetItemData(lItem);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NM_DBLCLK:
|
|
// if the user processes it in wxEVT_COMMAND_LEFT_CLICK(), don't do
|
|
// anything else
|
|
if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// else translate it into wxEVT_COMMAND_LIST_ITEM_ACTIVATED event
|
|
// if it happened on an item (and not on empty place)
|
|
if ( iItem == -1 )
|
|
{
|
|
// not on item
|
|
return false;
|
|
}
|
|
|
|
eventType = wxEVT_COMMAND_LIST_ITEM_ACTIVATED;
|
|
event.m_itemIndex = iItem;
|
|
event.m_item.m_text = GetItemText(iItem);
|
|
event.m_item.m_data = GetItemData(iItem);
|
|
break;
|
|
|
|
#if defined(__WXWINCE__) && !defined(__HANDHELDPC__) && _WIN32_WCE < 400
|
|
case GN_CONTEXTMENU:
|
|
#endif //__WXWINCE__
|
|
case NM_RCLICK:
|
|
// if the user processes it in wxEVT_COMMAND_RIGHT_CLICK(),
|
|
// don't do anything else
|
|
if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// else translate it into wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK event
|
|
LV_HITTESTINFO lvhti;
|
|
wxZeroMemory(lvhti);
|
|
|
|
#if defined(__WXWINCE__) && !defined(__HANDHELDPC__) && _WIN32_WCE < 400
|
|
if ( nmhdr->code == GN_CONTEXTMENU )
|
|
{
|
|
lvhti.pt = ((NMRGINFO*)nmhdr)->ptAction;
|
|
}
|
|
else
|
|
#endif //__WXWINCE__
|
|
{
|
|
wxGetCursorPosMSW(&(lvhti.pt));
|
|
}
|
|
|
|
::ScreenToClient(GetHwnd(), &lvhti.pt);
|
|
if ( ListView_HitTest(GetHwnd(), &lvhti) != -1 )
|
|
{
|
|
if ( lvhti.flags & LVHT_ONITEM )
|
|
{
|
|
eventType = wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK;
|
|
event.m_itemIndex = lvhti.iItem;
|
|
event.m_pointDrag.x = lvhti.pt.x;
|
|
event.m_pointDrag.y = lvhti.pt.y;
|
|
}
|
|
}
|
|
break;
|
|
|
|
#ifdef NM_CUSTOMDRAW
|
|
case NM_CUSTOMDRAW:
|
|
*result = OnCustomDraw(lParam);
|
|
|
|
return *result != CDRF_DODEFAULT;
|
|
#endif // _WIN32_IE >= 0x300
|
|
|
|
case LVN_ODCACHEHINT:
|
|
{
|
|
const NM_CACHEHINT *cacheHint = (NM_CACHEHINT *)lParam;
|
|
|
|
eventType = wxEVT_COMMAND_LIST_CACHE_HINT;
|
|
|
|
// we get some really stupid cache hints like ones for
|
|
// items in range 0..0 for an empty control or, after
|
|
// deleting an item, for items in invalid range -- filter
|
|
// this garbage out
|
|
if ( cacheHint->iFrom > cacheHint->iTo )
|
|
return false;
|
|
|
|
event.m_oldItemIndex = cacheHint->iFrom;
|
|
|
|
const long iMax = GetItemCount();
|
|
event.m_itemIndex = cacheHint->iTo < iMax ? cacheHint->iTo
|
|
: iMax - 1;
|
|
}
|
|
break;
|
|
|
|
#ifdef HAVE_NMLVFINDITEM
|
|
case LVN_ODFINDITEM:
|
|
// Find an item in a (necessarily virtual) list control.
|
|
if ( IsVirtual() )
|
|
{
|
|
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)lParam;
|
|
|
|
// no match by default
|
|
*result = -1;
|
|
|
|
// we only handle string-based searches here
|
|
//
|
|
// TODO: what about LVFI_PARTIAL, should we handle this?
|
|
if ( !(pFindInfo->lvfi.flags & LVFI_STRING) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const wxChar * const searchstr = pFindInfo->lvfi.psz;
|
|
const size_t len = wxStrlen(searchstr);
|
|
|
|
// this is the first item we should examine, search from it
|
|
// wrapping if necessary
|
|
int startPos = pFindInfo->iStart;
|
|
const int maxPos = GetItemCount();
|
|
|
|
// Check that the index is valid to ensure that our loop
|
|
// below always terminates.
|
|
if ( startPos < 0 || startPos >= maxPos )
|
|
{
|
|
// When the last item in the control is selected,
|
|
// iStart is really set to (invalid) maxPos index so
|
|
// accept this silently.
|
|
if ( startPos != maxPos )
|
|
{
|
|
wxLogDebug(wxT("Ignoring invalid search start ")
|
|
wxT("position %d in list control with ")
|
|
wxT("%d items."), startPos, maxPos);
|
|
}
|
|
|
|
startPos = 0;
|
|
}
|
|
|
|
// Linear search in a control with a lot of items can take
|
|
// a long time so we limit the total time of the search to
|
|
// ensure that the program doesn't appear to hang.
|
|
#if wxUSE_STOPWATCH
|
|
wxStopWatch sw;
|
|
#endif // wxUSE_STOPWATCH
|
|
for ( int currentPos = startPos; ; )
|
|
{
|
|
// does this item begin with searchstr?
|
|
if ( wxStrnicmp(searchstr,
|
|
GetItemText(currentPos), len) == 0 )
|
|
{
|
|
*result = currentPos;
|
|
break;
|
|
}
|
|
|
|
// Go to next item with wrapping if necessary.
|
|
if ( ++currentPos == maxPos )
|
|
{
|
|
// Surprisingly, LVFI_WRAP seems to be never set in
|
|
// the flags so wrap regardless of it.
|
|
currentPos = 0;
|
|
}
|
|
|
|
if ( currentPos == startPos )
|
|
{
|
|
// We examined all items without finding anything.
|
|
//
|
|
// Notice that we still return true as we did
|
|
// perform the search, if we didn't do this the
|
|
// message would have been considered unhandled and
|
|
// the control seems to always select the first
|
|
// item by default in this case.
|
|
return true;
|
|
}
|
|
|
|
#if wxUSE_STOPWATCH
|
|
// Check the time elapsed only every thousand
|
|
// iterations for performance reasons: if we did it
|
|
// more often calling wxStopWatch::Time() could take
|
|
// noticeable time on its own.
|
|
if ( !((currentPos - startPos)%1000) )
|
|
{
|
|
// We use half a second to limit the search time
|
|
// which is about as long as we can take without
|
|
// annoying the user.
|
|
if ( sw.Time() > 500 )
|
|
{
|
|
// As above, return true to prevent the control
|
|
// from selecting the first item by default.
|
|
return true;
|
|
}
|
|
}
|
|
#endif // wxUSE_STOPWATCH
|
|
|
|
}
|
|
|
|
SetItemState(*result,
|
|
wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED,
|
|
wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
|
|
EnsureVisible(*result);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
processed = false;
|
|
}
|
|
break;
|
|
#endif // HAVE_NMLVFINDITEM
|
|
|
|
case LVN_GETDISPINFO:
|
|
if ( IsVirtual() )
|
|
{
|
|
LV_DISPINFO *info = (LV_DISPINFO *)lParam;
|
|
|
|
LV_ITEM& lvi = info->item;
|
|
long item = lvi.iItem;
|
|
|
|
if ( lvi.mask & LVIF_TEXT )
|
|
{
|
|
wxString text = OnGetItemText(item, lvi.iSubItem);
|
|
wxStrlcpy(lvi.pszText, text.c_str(), lvi.cchTextMax);
|
|
}
|
|
|
|
// see comment at the end of wxListCtrl::GetColumn()
|
|
#ifdef NM_CUSTOMDRAW
|
|
if ( lvi.mask & LVIF_IMAGE )
|
|
{
|
|
lvi.iImage = OnGetItemColumnImage(item, lvi.iSubItem);
|
|
}
|
|
#endif // NM_CUSTOMDRAW
|
|
|
|
// even though we never use LVM_SETCALLBACKMASK, we still
|
|
// can get messages with LVIF_STATE in lvi.mask under Vista
|
|
if ( lvi.mask & LVIF_STATE )
|
|
{
|
|
// we don't have anything to return from here...
|
|
lvi.stateMask = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// fall through
|
|
|
|
default:
|
|
processed = false;
|
|
}
|
|
|
|
if ( !processed )
|
|
return wxControl::MSWOnNotify(idCtrl, lParam, result);
|
|
}
|
|
else
|
|
{
|
|
// where did this one come from?
|
|
return false;
|
|
}
|
|
|
|
// process the event
|
|
// -----------------
|
|
|
|
event.SetEventType(eventType);
|
|
|
|
// fill in the item before passing it to the event handler if we do have a
|
|
// valid item index and haven't filled it yet (e.g. for LVN_ITEMCHANGED)
|
|
// and we're not using a virtual control as in this case the program
|
|
// already has the data anyhow and we don't want to call GetItem() for
|
|
// potentially many items
|
|
if ( event.m_itemIndex != -1 && !event.m_item.GetMask()
|
|
&& !IsVirtual() )
|
|
{
|
|
wxListItem& item = event.m_item;
|
|
|
|
item.SetId(event.m_itemIndex);
|
|
item.SetMask(wxLIST_MASK_TEXT | wxLIST_MASK_IMAGE | wxLIST_MASK_DATA);
|
|
GetItem(item);
|
|
}
|
|
|
|
bool processed = HandleWindowEvent(event);
|
|
|
|
// post processing
|
|
// ---------------
|
|
switch ( nmhdr->code )
|
|
{
|
|
case LVN_DELETEALLITEMS:
|
|
// always return true to suppress all additional LVN_DELETEITEM
|
|
// notifications - this makes deleting all items from a list ctrl
|
|
// much faster
|
|
*result = TRUE;
|
|
|
|
// also, we may free all user data now (couldn't do it before as
|
|
// the user should have access to it in OnDeleteAllItems() handler)
|
|
FreeAllInternalData();
|
|
|
|
// the control is empty now, synchronize the cached number of items
|
|
// with the real one
|
|
m_count = 0;
|
|
return true;
|
|
|
|
case LVN_DELETEITEM:
|
|
// Delete the associated internal data. Notice that this can be
|
|
// done only after the event has been handled as the data could be
|
|
// accessed during the handling of the event.
|
|
if ( wxMSWListItemData *data = MSWGetItemData(event.m_itemIndex) )
|
|
{
|
|
const unsigned count = m_internalData.size();
|
|
for ( unsigned n = 0; n < count; n++ )
|
|
{
|
|
if ( m_internalData[n] == data )
|
|
{
|
|
m_internalData.erase(m_internalData.begin() + n);
|
|
wxDELETE(data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
wxASSERT_MSG( !data, "invalid internal data pointer?" );
|
|
}
|
|
break;
|
|
|
|
case LVN_ENDLABELEDITA:
|
|
case LVN_ENDLABELEDITW:
|
|
// logic here is inverted compared to all the other messages
|
|
*result = event.IsAllowed();
|
|
|
|
// EDIT control will be deleted by the list control itself so
|
|
// prevent us from deleting it as well
|
|
DeleteEditControl();
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( processed )
|
|
*result = !event.IsAllowed();
|
|
|
|
return processed;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// custom draw stuff
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// see comment at the end of wxListCtrl::GetColumn()
|
|
#ifdef NM_CUSTOMDRAW // _WIN32_IE >= 0x0300
|
|
|
|
static RECT GetCustomDrawnItemRect(const NMCUSTOMDRAW& nmcd)
|
|
{
|
|
RECT rc;
|
|
wxGetListCtrlItemRect(nmcd.hdr.hwndFrom, nmcd.dwItemSpec, LVIR_BOUNDS, rc);
|
|
|
|
RECT rcIcon;
|
|
wxGetListCtrlItemRect(nmcd.hdr.hwndFrom, nmcd.dwItemSpec, LVIR_ICON, rcIcon);
|
|
|
|
// exclude the icon part, neither the selection background nor focus rect
|
|
// should cover it
|
|
rc.left = rcIcon.right;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
bool HandleSubItemPrepaint(LPNMLVCUSTOMDRAW pLVCD, HFONT hfont, int colCount)
|
|
{
|
|
NMCUSTOMDRAW& nmcd = pLVCD->nmcd;
|
|
|
|
HDC hdc = nmcd.hdc;
|
|
HWND hwndList = nmcd.hdr.hwndFrom;
|
|
const int col = pLVCD->iSubItem;
|
|
const DWORD item = nmcd.dwItemSpec;
|
|
|
|
// the font must be valid, otherwise we wouldn't be painting the item at all
|
|
SelectInHDC selFont(hdc, hfont);
|
|
|
|
// get the rectangle to paint
|
|
int subitem = colCount ? col + 1 : col;
|
|
RECT rc;
|
|
wxGetListCtrlSubItemRect(hwndList, item, subitem, LVIR_BOUNDS, rc);
|
|
rc.left += 6;
|
|
|
|
// get the image and text to draw
|
|
wxChar text[512];
|
|
LV_ITEM it;
|
|
wxZeroMemory(it);
|
|
it.mask = LVIF_TEXT | LVIF_IMAGE;
|
|
it.iItem = item;
|
|
it.iSubItem = col;
|
|
it.pszText = text;
|
|
it.cchTextMax = WXSIZEOF(text);
|
|
ListView_GetItem(hwndList, &it);
|
|
|
|
HIMAGELIST himl = ListView_GetImageList(hwndList, LVSIL_SMALL);
|
|
if ( himl && ImageList_GetImageCount(himl) )
|
|
{
|
|
if ( it.iImage != -1 )
|
|
{
|
|
ImageList_Draw(himl, it.iImage, hdc, rc.left, rc.top,
|
|
nmcd.uItemState & CDIS_SELECTED ? ILD_SELECTED
|
|
: ILD_TRANSPARENT);
|
|
}
|
|
|
|
// notice that even if this item doesn't have any image, the list
|
|
// control still leaves space for the image in the first column if the
|
|
// image list is not empty (presumably so that items with and without
|
|
// images align?)
|
|
if ( it.iImage != -1 || it.iSubItem == 0 )
|
|
{
|
|
int wImage, hImage;
|
|
ImageList_GetIconSize(himl, &wImage, &hImage);
|
|
|
|
rc.left += wImage + 2;
|
|
}
|
|
}
|
|
|
|
::SetBkMode(hdc, TRANSPARENT);
|
|
|
|
UINT fmt = DT_SINGLELINE |
|
|
#ifndef __WXWINCE__
|
|
DT_WORD_ELLIPSIS |
|
|
#endif // __WXWINCE__
|
|
DT_NOPREFIX |
|
|
DT_VCENTER;
|
|
|
|
LV_COLUMN lvCol;
|
|
wxZeroMemory(lvCol);
|
|
lvCol.mask = LVCF_FMT;
|
|
if ( ListView_GetColumn(hwndList, col, &lvCol) )
|
|
{
|
|
switch ( lvCol.fmt & LVCFMT_JUSTIFYMASK )
|
|
{
|
|
case LVCFMT_LEFT:
|
|
fmt |= DT_LEFT;
|
|
break;
|
|
|
|
case LVCFMT_CENTER:
|
|
fmt |= DT_CENTER;
|
|
break;
|
|
|
|
case LVCFMT_RIGHT:
|
|
fmt |= DT_RIGHT;
|
|
break;
|
|
}
|
|
}
|
|
//else: failed to get alignment, assume it's DT_LEFT (default)
|
|
|
|
DrawText(hdc, text, -1, &rc, fmt);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void HandleItemPostpaint(NMCUSTOMDRAW nmcd)
|
|
{
|
|
if ( nmcd.uItemState & CDIS_FOCUS )
|
|
{
|
|
RECT rc = GetCustomDrawnItemRect(nmcd);
|
|
|
|
// don't use the provided HDC, it's in some strange state by now
|
|
::DrawFocusRect(WindowHDC(nmcd.hdr.hwndFrom), &rc);
|
|
}
|
|
}
|
|
|
|
// pLVCD->clrText and clrTextBk should contain the colours to use
|
|
static void HandleItemPaint(LPNMLVCUSTOMDRAW pLVCD, HFONT hfont)
|
|
{
|
|
NMCUSTOMDRAW& nmcd = pLVCD->nmcd; // just a shortcut
|
|
|
|
const HWND hwndList = nmcd.hdr.hwndFrom;
|
|
const int item = nmcd.dwItemSpec;
|
|
|
|
// unfortunately we can't trust CDIS_SELECTED, it is often set even when
|
|
// the item is not at all selected for some reason (comctl32 6), but we
|
|
// also can't always trust ListView_GetItem() as it could return the old
|
|
// item status if we're called just after the (de)selection, so remember
|
|
// the last item to gain selection and also check for it here
|
|
for ( int i = -1;; )
|
|
{
|
|
i = ListView_GetNextItem(hwndList, i, LVNI_SELECTED);
|
|
if ( i == -1 )
|
|
{
|
|
nmcd.uItemState &= ~CDIS_SELECTED;
|
|
break;
|
|
}
|
|
|
|
if ( i == item )
|
|
{
|
|
nmcd.uItemState |= CDIS_SELECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// same thing for CDIS_FOCUS (except simpler as there is only one of them)
|
|
//
|
|
// NB: cast is needed to work around the bug in mingw32 headers which don't
|
|
// have it inside ListView_GetNextItem() itself (unlike SDK ones)
|
|
if ( ::GetFocus() == hwndList &&
|
|
ListView_GetNextItem(
|
|
hwndList, static_cast<WPARAM>(-1), LVNI_FOCUSED) == item )
|
|
{
|
|
nmcd.uItemState |= CDIS_FOCUS;
|
|
}
|
|
else
|
|
{
|
|
nmcd.uItemState &= ~CDIS_FOCUS;
|
|
}
|
|
|
|
if ( nmcd.uItemState & CDIS_SELECTED )
|
|
{
|
|
int syscolFg, syscolBg;
|
|
if ( ::GetFocus() == hwndList )
|
|
{
|
|
syscolFg = COLOR_HIGHLIGHTTEXT;
|
|
syscolBg = COLOR_HIGHLIGHT;
|
|
}
|
|
else // selected but unfocused
|
|
{
|
|
syscolFg = COLOR_WINDOWTEXT;
|
|
syscolBg = COLOR_BTNFACE;
|
|
|
|
// don't grey out the icon in this case neither
|
|
nmcd.uItemState &= ~CDIS_SELECTED;
|
|
}
|
|
|
|
pLVCD->clrText = ::GetSysColor(syscolFg);
|
|
pLVCD->clrTextBk = ::GetSysColor(syscolBg);
|
|
}
|
|
//else: not selected, use normal colours from pLVCD
|
|
|
|
HDC hdc = nmcd.hdc;
|
|
RECT rc = GetCustomDrawnItemRect(nmcd);
|
|
|
|
::SetTextColor(hdc, pLVCD->clrText);
|
|
::FillRect(hdc, &rc, AutoHBRUSH(pLVCD->clrTextBk));
|
|
|
|
// we could use CDRF_NOTIFYSUBITEMDRAW here but it results in weird repaint
|
|
// problems so just draw everything except the focus rect from here instead
|
|
const int colCount = Header_GetItemCount(ListView_GetHeader(hwndList));
|
|
for ( int col = 0; col < colCount; col++ )
|
|
{
|
|
pLVCD->iSubItem = col;
|
|
HandleSubItemPrepaint(pLVCD, hfont, colCount);
|
|
}
|
|
|
|
HandleItemPostpaint(nmcd);
|
|
}
|
|
|
|
static WXLPARAM HandleItemPrepaint(wxListCtrl *listctrl,
|
|
LPNMLVCUSTOMDRAW pLVCD,
|
|
wxListItemAttr *attr)
|
|
{
|
|
if ( !attr )
|
|
{
|
|
// nothing to do for this item
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
|
|
|
|
// set the colours to use for text drawing
|
|
pLVCD->clrText = attr->HasTextColour()
|
|
? wxColourToRGB(attr->GetTextColour())
|
|
: wxColourToRGB(listctrl->GetTextColour());
|
|
pLVCD->clrTextBk = attr->HasBackgroundColour()
|
|
? wxColourToRGB(attr->GetBackgroundColour())
|
|
: wxColourToRGB(listctrl->GetBackgroundColour());
|
|
|
|
// select the font if non default one is specified
|
|
if ( attr->HasFont() )
|
|
{
|
|
wxFont font = attr->GetFont();
|
|
if ( font.GetEncoding() != wxFONTENCODING_SYSTEM )
|
|
{
|
|
// the standard control ignores the font encoding/charset, at least
|
|
// with recent comctl32.dll versions (5 and 6, it uses to work with
|
|
// 4.something) so we have to draw the item entirely ourselves in
|
|
// this case
|
|
HandleItemPaint(pLVCD, GetHfontOf(font));
|
|
return CDRF_SKIPDEFAULT;
|
|
}
|
|
|
|
::SelectObject(pLVCD->nmcd.hdc, GetHfontOf(font));
|
|
|
|
return CDRF_NEWFONT;
|
|
}
|
|
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
|
|
WXLPARAM wxListCtrl::OnCustomDraw(WXLPARAM lParam)
|
|
{
|
|
LPNMLVCUSTOMDRAW pLVCD = (LPNMLVCUSTOMDRAW)lParam;
|
|
NMCUSTOMDRAW& nmcd = pLVCD->nmcd;
|
|
switch ( nmcd.dwDrawStage )
|
|
{
|
|
case CDDS_PREPAINT:
|
|
// if we've got any items with non standard attributes,
|
|
// notify us before painting each item
|
|
//
|
|
// for virtual controls, always suppose that we have attributes as
|
|
// there is no way to check for this
|
|
if ( IsVirtual() || m_hasAnyAttr )
|
|
return CDRF_NOTIFYITEMDRAW;
|
|
break;
|
|
|
|
case CDDS_ITEMPREPAINT:
|
|
// get a message for each subitem
|
|
return CDRF_NOTIFYITEMDRAW;
|
|
|
|
case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
|
|
const int item = nmcd.dwItemSpec;
|
|
const int column = pLVCD->iSubItem;
|
|
|
|
// we get this message with item == 0 for an empty control, we
|
|
// must ignore it as calling OnGetItemAttr() would be wrong
|
|
if ( item < 0 || item >= GetItemCount() )
|
|
break;
|
|
// same for columns
|
|
if ( column < 0 || column >= GetColumnCount() )
|
|
break;
|
|
|
|
return HandleItemPrepaint(this, pLVCD, DoGetItemColumnAttr(item, column));
|
|
}
|
|
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
|
|
#endif // NM_CUSTOMDRAW supported
|
|
|
|
// Necessary for drawing hrules and vrules, if specified
|
|
void wxListCtrl::OnPaint(wxPaintEvent& event)
|
|
{
|
|
const int itemCount = GetItemCount();
|
|
const bool drawHRules = HasFlag(wxLC_HRULES);
|
|
const bool drawVRules = HasFlag(wxLC_VRULES);
|
|
|
|
if (!InReportView() || !(drawHRules || drawVRules) || !itemCount)
|
|
{
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
wxPaintDC dc(this);
|
|
|
|
wxControl::OnPaint(event);
|
|
|
|
// Reset the device origin since it may have been set
|
|
dc.SetDeviceOrigin(0, 0);
|
|
|
|
wxPen pen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT));
|
|
dc.SetPen(pen);
|
|
dc.SetBrush(* wxTRANSPARENT_BRUSH);
|
|
|
|
wxSize clientSize = GetClientSize();
|
|
wxRect itemRect;
|
|
|
|
if (drawHRules)
|
|
{
|
|
const long top = GetTopItem();
|
|
for ( int i = top; i < top + GetCountPerPage() + 1; i++ )
|
|
{
|
|
if (GetItemRect(i, itemRect))
|
|
{
|
|
int cy = itemRect.GetTop();
|
|
if (i != 0) // Don't draw the first one
|
|
{
|
|
dc.DrawLine(0, cy, clientSize.x, cy);
|
|
}
|
|
// Draw last line
|
|
if (i == itemCount - 1)
|
|
{
|
|
cy = itemRect.GetBottom();
|
|
dc.DrawLine(0, cy, clientSize.x, cy);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (drawVRules)
|
|
{
|
|
wxRect firstItemRect;
|
|
GetItemRect(0, firstItemRect);
|
|
|
|
if (GetItemRect(itemCount - 1, itemRect))
|
|
{
|
|
// this is a fix for bug 673394: erase the pixels which we would
|
|
// otherwise leave on the screen
|
|
static const int gap = 2;
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
dc.SetBrush(wxBrush(GetBackgroundColour()));
|
|
dc.DrawRectangle(0, firstItemRect.GetY() - gap,
|
|
clientSize.GetWidth(), gap);
|
|
|
|
dc.SetPen(pen);
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
|
|
const int numCols = GetColumnCount();
|
|
wxVector<int> indexArray(numCols);
|
|
if ( !ListView_GetColumnOrderArray(GetHwnd(),
|
|
numCols,
|
|
&indexArray[0]) )
|
|
{
|
|
wxFAIL_MSG( wxT("invalid column index array in OnPaint()") );
|
|
return;
|
|
}
|
|
|
|
int x = itemRect.GetX();
|
|
for (int col = 0; col < numCols; col++)
|
|
{
|
|
int colWidth = GetColumnWidth(indexArray[col]);
|
|
x += colWidth ;
|
|
dc.DrawLine(x-1, firstItemRect.GetY() - gap,
|
|
x-1, itemRect.GetBottom());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxListCtrl::OnCharHook(wxKeyEvent& event)
|
|
{
|
|
if ( GetEditControl() )
|
|
{
|
|
// We need to ensure that Escape is not stolen from the in-place editor
|
|
// by the containing dialog.
|
|
//
|
|
// Notice that we don't have to care about Enter key here as we return
|
|
// false from MSWShouldPreProcessMessage() for it.
|
|
if ( event.GetKeyCode() == WXK_ESCAPE )
|
|
{
|
|
EndEditLabel(true /* cancel */);
|
|
|
|
// Don't call Skip() below.
|
|
return;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
WXLRESULT
|
|
wxListCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
|
{
|
|
switch ( nMsg )
|
|
{
|
|
#ifdef WM_PRINT
|
|
case WM_PRINT:
|
|
// we should bypass our own WM_PRINT handling as we don't handle
|
|
// PRF_CHILDREN flag, so leave it to the native control itself
|
|
return MSWDefWindowProc(nMsg, wParam, lParam);
|
|
#endif // WM_PRINT
|
|
|
|
case WM_CONTEXTMENU:
|
|
// because this message is propagated upwards the child-parent
|
|
// chain, we get it for the right clicks on the header window but
|
|
// this is confusing in wx as right clicking there already
|
|
// generates a separate wxEVT_COMMAND_LIST_COL_RIGHT_CLICK event
|
|
// so just ignore them
|
|
if ( (HWND)wParam == ListView_GetHeader(GetHwnd()) )
|
|
return 0;
|
|
//else: break
|
|
}
|
|
|
|
return wxControl::MSWWindowProc(nMsg, wParam, lParam);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// virtual list controls
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxString wxListCtrl::OnGetItemText(long WXUNUSED(item), long WXUNUSED(col)) const
|
|
{
|
|
// this is a pure virtual function, in fact - which is not really pure
|
|
// because the controls which are not virtual don't need to implement it
|
|
wxFAIL_MSG( wxT("wxListCtrl::OnGetItemText not supposed to be called") );
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
int wxListCtrl::OnGetItemImage(long WXUNUSED(item)) const
|
|
{
|
|
wxCHECK_MSG(!GetImageList(wxIMAGE_LIST_SMALL),
|
|
-1,
|
|
wxT("List control has an image list, OnGetItemImage or OnGetItemColumnImage should be overridden."));
|
|
return -1;
|
|
}
|
|
|
|
int wxListCtrl::OnGetItemColumnImage(long item, long column) const
|
|
{
|
|
if (!column)
|
|
return OnGetItemImage(item);
|
|
|
|
return -1;
|
|
}
|
|
|
|
wxListItemAttr *wxListCtrl::OnGetItemAttr(long WXUNUSED_UNLESS_DEBUG(item)) const
|
|
{
|
|
wxASSERT_MSG( item >= 0 && item < GetItemCount(),
|
|
wxT("invalid item index in OnGetItemAttr()") );
|
|
|
|
// no attributes by default
|
|
return NULL;
|
|
}
|
|
|
|
wxListItemAttr *wxListCtrl::DoGetItemColumnAttr(long item, long column) const
|
|
{
|
|
if ( IsVirtual() )
|
|
return OnGetItemColumnAttr(item, column);
|
|
|
|
wxMSWListItemData * const data = MSWGetItemData(item);
|
|
return data ? data->attr : NULL;
|
|
}
|
|
|
|
void wxListCtrl::SetItemCount(long count)
|
|
{
|
|
wxASSERT_MSG( IsVirtual(), wxT("this is for virtual controls only") );
|
|
|
|
if ( !::SendMessage(GetHwnd(), LVM_SETITEMCOUNT, (WPARAM)count,
|
|
LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL) )
|
|
{
|
|
wxLogLastError(wxT("ListView_SetItemCount"));
|
|
}
|
|
m_count = count;
|
|
wxASSERT_MSG( m_count == ListView_GetItemCount(GetHwnd()),
|
|
wxT("m_count should match ListView_GetItemCount"));
|
|
}
|
|
|
|
void wxListCtrl::RefreshItem(long item)
|
|
{
|
|
RefreshItems(item, item);
|
|
}
|
|
|
|
void wxListCtrl::RefreshItems(long itemFrom, long itemTo)
|
|
{
|
|
ListView_RedrawItems(GetHwnd(), itemFrom, itemTo);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxWin <-> MSW items conversions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static void wxConvertFromMSWListItem(HWND hwndListCtrl,
|
|
wxListItem& info,
|
|
LV_ITEM& lvItem)
|
|
{
|
|
wxMSWListItemData *internaldata =
|
|
(wxMSWListItemData *) lvItem.lParam;
|
|
|
|
if (internaldata)
|
|
info.m_data = internaldata->lParam;
|
|
|
|
info.m_mask = 0;
|
|
info.m_state = 0;
|
|
info.m_stateMask = 0;
|
|
info.m_itemId = lvItem.iItem;
|
|
|
|
long oldMask = lvItem.mask;
|
|
|
|
bool needText = false;
|
|
if (hwndListCtrl != 0)
|
|
{
|
|
if ( lvItem.mask & LVIF_TEXT )
|
|
needText = false;
|
|
else
|
|
needText = true;
|
|
|
|
if ( needText )
|
|
{
|
|
lvItem.pszText = new wxChar[513];
|
|
lvItem.cchTextMax = 512;
|
|
}
|
|
lvItem.mask |= LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
|
|
::SendMessage(hwndListCtrl, LVM_GETITEM, 0, (LPARAM)& lvItem);
|
|
}
|
|
|
|
if ( lvItem.mask & LVIF_STATE )
|
|
{
|
|
info.m_mask |= wxLIST_MASK_STATE;
|
|
|
|
if ( lvItem.stateMask & LVIS_CUT)
|
|
{
|
|
info.m_stateMask |= wxLIST_STATE_CUT;
|
|
if ( lvItem.state & LVIS_CUT )
|
|
info.m_state |= wxLIST_STATE_CUT;
|
|
}
|
|
if ( lvItem.stateMask & LVIS_DROPHILITED)
|
|
{
|
|
info.m_stateMask |= wxLIST_STATE_DROPHILITED;
|
|
if ( lvItem.state & LVIS_DROPHILITED )
|
|
info.m_state |= wxLIST_STATE_DROPHILITED;
|
|
}
|
|
if ( lvItem.stateMask & LVIS_FOCUSED)
|
|
{
|
|
info.m_stateMask |= wxLIST_STATE_FOCUSED;
|
|
if ( lvItem.state & LVIS_FOCUSED )
|
|
info.m_state |= wxLIST_STATE_FOCUSED;
|
|
}
|
|
if ( lvItem.stateMask & LVIS_SELECTED)
|
|
{
|
|
info.m_stateMask |= wxLIST_STATE_SELECTED;
|
|
if ( lvItem.state & LVIS_SELECTED )
|
|
info.m_state |= wxLIST_STATE_SELECTED;
|
|
}
|
|
}
|
|
|
|
if ( lvItem.mask & LVIF_TEXT )
|
|
{
|
|
info.m_mask |= wxLIST_MASK_TEXT;
|
|
info.m_text = lvItem.pszText;
|
|
}
|
|
if ( lvItem.mask & LVIF_IMAGE )
|
|
{
|
|
info.m_mask |= wxLIST_MASK_IMAGE;
|
|
info.m_image = lvItem.iImage;
|
|
}
|
|
if ( lvItem.mask & LVIF_PARAM )
|
|
info.m_mask |= wxLIST_MASK_DATA;
|
|
if ( lvItem.mask & LVIF_DI_SETITEM )
|
|
info.m_mask |= wxLIST_SET_ITEM;
|
|
info.m_col = lvItem.iSubItem;
|
|
|
|
if (needText)
|
|
{
|
|
if (lvItem.pszText)
|
|
delete[] lvItem.pszText;
|
|
}
|
|
lvItem.mask = oldMask;
|
|
}
|
|
|
|
static void wxConvertToMSWFlags(long state, long stateMask, LV_ITEM& lvItem)
|
|
{
|
|
if (stateMask & wxLIST_STATE_CUT)
|
|
{
|
|
lvItem.stateMask |= LVIS_CUT;
|
|
if (state & wxLIST_STATE_CUT)
|
|
lvItem.state |= LVIS_CUT;
|
|
}
|
|
if (stateMask & wxLIST_STATE_DROPHILITED)
|
|
{
|
|
lvItem.stateMask |= LVIS_DROPHILITED;
|
|
if (state & wxLIST_STATE_DROPHILITED)
|
|
lvItem.state |= LVIS_DROPHILITED;
|
|
}
|
|
if (stateMask & wxLIST_STATE_FOCUSED)
|
|
{
|
|
lvItem.stateMask |= LVIS_FOCUSED;
|
|
if (state & wxLIST_STATE_FOCUSED)
|
|
lvItem.state |= LVIS_FOCUSED;
|
|
}
|
|
if (stateMask & wxLIST_STATE_SELECTED)
|
|
{
|
|
lvItem.stateMask |= LVIS_SELECTED;
|
|
if (state & wxLIST_STATE_SELECTED)
|
|
lvItem.state |= LVIS_SELECTED;
|
|
}
|
|
}
|
|
|
|
static void wxConvertToMSWListItem(const wxListCtrl *ctrl,
|
|
const wxListItem& info,
|
|
LV_ITEM& lvItem)
|
|
{
|
|
if ( ctrl->InReportView() )
|
|
{
|
|
wxASSERT_MSG( 0 <= info.m_col && info.m_col < ctrl->GetColumnCount(),
|
|
"wxListCtrl column index out of bounds" );
|
|
}
|
|
else // not in report view
|
|
{
|
|
wxASSERT_MSG( info.m_col == 0, "columns only exist in report view" );
|
|
}
|
|
|
|
lvItem.iItem = (int) info.m_itemId;
|
|
|
|
lvItem.iImage = info.m_image;
|
|
lvItem.stateMask = 0;
|
|
lvItem.state = 0;
|
|
lvItem.mask = 0;
|
|
lvItem.iSubItem = info.m_col;
|
|
|
|
if (info.m_mask & wxLIST_MASK_STATE)
|
|
{
|
|
lvItem.mask |= LVIF_STATE;
|
|
|
|
wxConvertToMSWFlags(info.m_state, info.m_stateMask, lvItem);
|
|
}
|
|
|
|
if (info.m_mask & wxLIST_MASK_TEXT)
|
|
{
|
|
lvItem.mask |= LVIF_TEXT;
|
|
if ( ctrl->HasFlag(wxLC_USER_TEXT) )
|
|
{
|
|
lvItem.pszText = LPSTR_TEXTCALLBACK;
|
|
}
|
|
else
|
|
{
|
|
// pszText is not const, hence the cast
|
|
lvItem.pszText = (wxChar *)info.m_text.wx_str();
|
|
if ( lvItem.pszText )
|
|
lvItem.cchTextMax = info.m_text.length();
|
|
else
|
|
lvItem.cchTextMax = 0;
|
|
}
|
|
}
|
|
if (info.m_mask & wxLIST_MASK_IMAGE)
|
|
lvItem.mask |= LVIF_IMAGE;
|
|
}
|
|
|
|
static void wxConvertToMSWListCol(HWND hwndList,
|
|
int col,
|
|
const wxListItem& item,
|
|
LV_COLUMN& lvCol)
|
|
{
|
|
wxZeroMemory(lvCol);
|
|
|
|
if ( item.m_mask & wxLIST_MASK_TEXT )
|
|
{
|
|
lvCol.mask |= LVCF_TEXT;
|
|
lvCol.pszText = (wxChar *)item.m_text.wx_str(); // cast is safe
|
|
}
|
|
|
|
if ( item.m_mask & wxLIST_MASK_FORMAT )
|
|
{
|
|
lvCol.mask |= LVCF_FMT;
|
|
|
|
if ( item.m_format == wxLIST_FORMAT_LEFT )
|
|
lvCol.fmt = LVCFMT_LEFT;
|
|
else if ( item.m_format == wxLIST_FORMAT_RIGHT )
|
|
lvCol.fmt = LVCFMT_RIGHT;
|
|
else if ( item.m_format == wxLIST_FORMAT_CENTRE )
|
|
lvCol.fmt = LVCFMT_CENTER;
|
|
}
|
|
|
|
if ( item.m_mask & wxLIST_MASK_WIDTH )
|
|
{
|
|
lvCol.mask |= LVCF_WIDTH;
|
|
if ( item.m_width == wxLIST_AUTOSIZE)
|
|
lvCol.cx = LVSCW_AUTOSIZE;
|
|
else if ( item.m_width == wxLIST_AUTOSIZE_USEHEADER)
|
|
lvCol.cx = LVSCW_AUTOSIZE_USEHEADER;
|
|
else
|
|
lvCol.cx = item.m_width;
|
|
}
|
|
|
|
// see comment at the end of wxListCtrl::GetColumn()
|
|
#ifdef NM_CUSTOMDRAW // _WIN32_IE >= 0x0300
|
|
if ( item.m_mask & wxLIST_MASK_IMAGE )
|
|
{
|
|
if ( wxApp::GetComCtl32Version() >= 470 )
|
|
{
|
|
lvCol.mask |= LVCF_IMAGE;
|
|
|
|
// we use LVCFMT_BITMAP_ON_RIGHT because the images on the right
|
|
// seem to be generally nicer than on the left and the generic
|
|
// version only draws them on the right (we don't have a flag to
|
|
// specify the image location anyhow)
|
|
//
|
|
// we don't use LVCFMT_COL_HAS_IMAGES because it doesn't seem to
|
|
// make any difference in my tests -- but maybe we should?
|
|
if ( item.m_image != -1 )
|
|
{
|
|
// as we're going to overwrite the format field, get its
|
|
// current value first -- unless we want to overwrite it anyhow
|
|
if ( !(lvCol.mask & LVCF_FMT) )
|
|
{
|
|
LV_COLUMN lvColOld;
|
|
wxZeroMemory(lvColOld);
|
|
lvColOld.mask = LVCF_FMT;
|
|
if ( ListView_GetColumn(hwndList, col, &lvColOld) )
|
|
{
|
|
lvCol.fmt = lvColOld.fmt;
|
|
}
|
|
|
|
lvCol.mask |= LVCF_FMT;
|
|
}
|
|
|
|
lvCol.fmt |= LVCFMT_BITMAP_ON_RIGHT | LVCFMT_IMAGE;
|
|
}
|
|
|
|
lvCol.iImage = item.m_image;
|
|
}
|
|
//else: it doesn't support item images anyhow
|
|
}
|
|
#endif // _WIN32_IE >= 0x0300
|
|
}
|
|
|
|
#endif // wxUSE_LISTCTRL
|