#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/vector.h"
#include "wx/msw/private.h"
+#include "wx/msw/private/keyboard.h"
#if defined(__WXWINCE__) && !defined(__HANDHELDPC__)
#include <ole2.h>
wxDECLARE_NO_COPY_CLASS(wxMSWListItemData);
};
-#if wxUSE_EXTENDED_RTTI
-WX_DEFINE_FLAGS( wxListCtrlStyle )
-
-wxBEGIN_FLAGS( wxListCtrlStyle )
- // new style border flags, we put them first to
- // use them for streaming out
- wxFLAGS_MEMBER(wxBORDER_SIMPLE)
- wxFLAGS_MEMBER(wxBORDER_SUNKEN)
- wxFLAGS_MEMBER(wxBORDER_DOUBLE)
- wxFLAGS_MEMBER(wxBORDER_RAISED)
- wxFLAGS_MEMBER(wxBORDER_STATIC)
- wxFLAGS_MEMBER(wxBORDER_NONE)
-
- // old style border flags
- wxFLAGS_MEMBER(wxSIMPLE_BORDER)
- wxFLAGS_MEMBER(wxSUNKEN_BORDER)
- wxFLAGS_MEMBER(wxDOUBLE_BORDER)
- wxFLAGS_MEMBER(wxRAISED_BORDER)
- wxFLAGS_MEMBER(wxSTATIC_BORDER)
- wxFLAGS_MEMBER(wxBORDER)
-
- // standard window styles
- wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
- wxFLAGS_MEMBER(wxCLIP_CHILDREN)
- wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
- wxFLAGS_MEMBER(wxWANTS_CHARS)
- wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
- wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
- wxFLAGS_MEMBER(wxVSCROLL)
- wxFLAGS_MEMBER(wxHSCROLL)
-
- wxFLAGS_MEMBER(wxLC_LIST)
- wxFLAGS_MEMBER(wxLC_REPORT)
- wxFLAGS_MEMBER(wxLC_ICON)
- wxFLAGS_MEMBER(wxLC_SMALL_ICON)
- wxFLAGS_MEMBER(wxLC_ALIGN_TOP)
- wxFLAGS_MEMBER(wxLC_ALIGN_LEFT)
- wxFLAGS_MEMBER(wxLC_AUTOARRANGE)
- wxFLAGS_MEMBER(wxLC_USER_TEXT)
- wxFLAGS_MEMBER(wxLC_EDIT_LABELS)
- wxFLAGS_MEMBER(wxLC_NO_HEADER)
- wxFLAGS_MEMBER(wxLC_SINGLE_SEL)
- wxFLAGS_MEMBER(wxLC_SORT_ASCENDING)
- wxFLAGS_MEMBER(wxLC_SORT_DESCENDING)
- wxFLAGS_MEMBER(wxLC_VIRTUAL)
-
-wxEND_FLAGS( wxListCtrlStyle )
-
-IMPLEMENT_DYNAMIC_CLASS_XTI(wxListCtrl, wxControl,"wx/listctrl.h")
-
-wxBEGIN_PROPERTIES_TABLE(wxListCtrl)
- wxEVENT_PROPERTY( TextUpdated , wxEVT_COMMAND_TEXT_UPDATED , wxCommandEvent )
-
- wxPROPERTY_FLAGS( WindowStyle , wxListCtrlStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
-wxEND_PROPERTIES_TABLE()
-
-wxBEGIN_HANDLERS_TABLE(wxListCtrl)
-wxEND_HANDLERS_TABLE()
-
-wxCONSTRUCTOR_5( wxListCtrl , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size , long , WindowStyle )
-
-/*
- TODO : Expose more information of a list's layout etc. via appropriate objects (a la NotebookPageInfo)
-*/
-#else
-IMPLEMENT_DYNAMIC_CLASS(wxListCtrl, wxControl)
-#endif
-
-IMPLEMENT_DYNAMIC_CLASS(wxListView, wxListCtrl)
-IMPLEMENT_DYNAMIC_CLASS(wxListItem, wxObject)
-
-IMPLEMENT_DYNAMIC_CLASS(wxListEvent, wxNotifyEvent)
-
-BEGIN_EVENT_TABLE(wxListCtrl, wxControl)
+BEGIN_EVENT_TABLE(wxListCtrl, wxListCtrlBase)
EVT_PAINT(wxListCtrl::OnPaint)
+ EVT_CHAR_HOOK(wxListCtrl::OnCharHook)
END_EVENT_TABLE()
// ============================================================================
WXDWORD wxListCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
{
- WXDWORD wstyle = wxControl::MSWGetStyle(style, exstyle);
+ WXDWORD wstyle = wxListCtrlBase::MSWGetStyle(style, exstyle);
wstyle |= LVS_SHAREIMAGELISTS | LVS_SHOWSELALWAYS;
{
m_textCtrl->UnsubclassWin();
m_textCtrl->SetHWND(0);
- delete m_textCtrl;
- m_textCtrl = NULL;
+ wxDELETE(m_textCtrl);
}
}
{
if ( flag != m_windowStyle )
{
- wxControl::SetWindowStyleFlag(flag);
+ wxListCtrlBase::SetWindowStyleFlag(flag);
UpdateStyle();
}
// Gets the item text
-wxString wxListCtrl::GetItemText(long item) const
+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;
m_ownsImageListState = true;
}
+// ----------------------------------------------------------------------------
+// Geometry
+// ----------------------------------------------------------------------------
+
+wxSize wxListCtrl::MSWGetBestViewRect(int x, int y) const
+{
+ const DWORD rc = ListView_ApproximateViewRect(GetHwnd(), x, y, -1);
+
+ wxSize size(LOWORD(rc), HIWORD(rc));
+
+ // We have to add space for the scrollbars ourselves, they're not taken
+ // into account by ListView_ApproximateViewRect(), at least not with
+ // commctrl32.dll v6.
+ const DWORD mswStyle = ::GetWindowLong(GetHwnd(), GWL_STYLE);
+
+ if ( mswStyle & WS_HSCROLL )
+ size.y += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y);
+ if ( mswStyle & WS_VSCROLL )
+ size.x += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
+
+ return size;
+}
+
// ----------------------------------------------------------------------------
// Operations
// ----------------------------------------------------------------------------
// Deletes all items
bool wxListCtrl::DeleteAllItems()
{
- return ListView_DeleteAllItems(GetHwnd()) != 0;
+ // 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
m_textCtrl->SubclassWin(hWnd);
m_textCtrl->SetParent(this);
- // we must disallow TABbing away from the control while the edit contol is
+ // 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)
if ( !hWnd )
{
// failed to start editing
- delete m_textCtrl;
- m_textCtrl = NULL;
+ wxDELETE(m_textCtrl);
return NULL;
}
if ( !hwnd )
return false;
- if ( cancel )
- ::SetWindowText(hwnd, wxEmptyString); // dubious but better than nothing
+ // 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
- //
- // posting WM_CLOSE to it does seem to work without any side effects
- ::PostMessage(hwnd, WM_CLOSE, 0, 0);
+ // 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;
}
{
wxASSERT_MSG( !IsVirtual(), wxT("can't be used with virtual controls") );
+ // In 2.8 it was possible to succeed inserting an item without initializing
+ // its ID as it defaulted to 0. This was however never supported and in 2.9
+ // the ID is -1 by default and inserting it simply fails, but it might be
+ // not obvious why does it happen, so check it proactively.
+ wxASSERT_MSG( info.m_itemId != -1, wxS("Item ID must be set.") );
+
LV_ITEM item;
wxConvertToMSWListItem(this, info, item);
item.mask &= ~LVIF_PARAM;
wxListItem info;
info.m_image = imageIndex;
info.m_text = label;
- info.m_mask = wxLIST_MASK_IMAGE | wxLIST_MASK_TEXT;
+ 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)
+long wxListCtrl::DoInsertColumn(long col, const wxListItem& item)
{
LV_COLUMN lvCol;
wxConvertToMSWListCol(GetHwnd(), col, item, lvCol);
- if ( !(lvCol.mask & LVCF_WIDTH) )
+ // LVSCW_AUTOSIZE_USEHEADER is not supported when inserting new column,
+ // we'll deal with it below instead. Plain LVSCW_AUTOSIZE is not supported
+ // neither but it doesn't need any special handling as we use fixed value
+ // for it here, both because we can't do anything else (there are no items
+ // with values in this column to compute the size from yet) and for
+ // compatibility as wxLIST_AUTOSIZE == -1 and -1 as InsertColumn() width
+ // parameter used to mean "arbitrary fixed width".
+ if ( !(lvCol.mask & LVCF_WIDTH) || lvCol.cx < 0 )
{
// always give some width to the new column: this one is compatible
// with the generic version
}
long n = ListView_InsertColumn(GetHwnd(), col, &lvCol);
- if ( n != -1 )
- {
- m_colCount++;
- }
- else // failed to insert?
+ if ( n == -1 )
{
wxLogDebug(wxT("Failed to insert the column '%s' into listview!"),
lvCol.pszText);
+ return -1;
}
- return n;
-}
+ m_colCount++;
-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 )
+ // Now adjust the new column size.
+ if ( (item.GetMask() & wxLIST_MASK_WIDTH) &&
+ (item.GetWidth() == wxLIST_AUTOSIZE_USEHEADER) )
{
- item.m_mask |= wxLIST_MASK_WIDTH;
- item.m_width = width;
+ SetColumnWidth(n, wxLIST_AUTOSIZE_USEHEADER);
}
- item.m_format = format;
- return InsertColumn(col, item);
+ return n;
}
// scroll the control by the given number of pixels (exception: in list view,
wxMSWListItemData *data1 = (wxMSWListItemData *) lParam1;
wxMSWListItemData *data2 = (wxMSWListItemData *) lParam2;
- long d1 = (data1 == NULL ? 0 : data1->lParam);
- long d2 = (data2 == NULL ? 0 : data2->lParam);
+ wxIntPtr d1 = (data1 == NULL ? 0 : data1->lParam);
+ wxIntPtr d2 = (data2 == NULL ? 0 : data2->lParam);
return internalData->user_fn(d1, d2, internalData->data);
return false;
}
}
- return wxControl::MSWShouldPreProcessMessage(msg);
+ return wxListCtrlBase::MSWShouldPreProcessMessage(msg);
}
bool wxListCtrl::MSWCommand(WXUINT cmd, WXWORD id_)
}
else
#endif //__WXWINCE__
- if ( !::GetCursorPos(ptClick) )
{
- wxLogLastError(wxT("GetCursorPos"));
+ wxGetCursorPosMSW(ptClick);
}
// we need to use listctrl coordinates for the event point so this is what
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 and no HDN_TRACK at all!
+ // 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...)
//
- // note that fr HDN_TRACK another possibility could be to use
- // HDN_ITEMCHANGING but it is sent even after HDN_ENDTRACK and when
- // something other than the item width changes so we'd have to
- // filter out the unwanted events then
+ // 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_TRACKA:
- case HDN_TRACKW:
+ 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:
return true;
default:
- return wxControl::MSWOnNotify(idCtrl, lParam, result);
+ ignore = true;
}
+
+ if ( ignore )
+ return wxListCtrlBase::MSWOnNotify(idCtrl, lParam, result);
}
else
#endif // defined(HDN_BEGINTRACKA)
eventType = wxEVT_COMMAND_LIST_DELETE_ITEM;
event.m_itemIndex = iItem;
- // delete the associated internal data
- if ( wxMSWListItemData *data = MSWGetItemData(iItem) )
- {
- 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);
- delete data;
- data = NULL;
- break;
- }
- }
-
- wxASSERT_MSG( !data, "invalid internal data pointer?" );
- }
break;
case LVN_INSERTITEM:
{
eventType = wxEVT_COMMAND_LIST_KEY_DOWN;
- // wxCharCodeMSWToWX() returns 0 if the key is an ASCII
- // value which should be used as is
- int code = wxCharCodeMSWToWX(wVKey);
- event.m_code = code ? code : wVKey;
+ 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 =
case NM_DBLCLK:
// if the user processes it in wxEVT_COMMAND_LEFT_CLICK(), don't do
// anything else
- if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
+ if ( wxListCtrlBase::MSWOnNotify(idCtrl, lParam, result) )
{
return true;
}
case NM_RCLICK:
// if the user processes it in wxEVT_COMMAND_RIGHT_CLICK(),
// don't do anything else
- if ( wxControl::MSWOnNotify(idCtrl, lParam, result) )
+ if ( wxListCtrlBase::MSWOnNotify(idCtrl, lParam, result) )
{
return true;
}
else
#endif //__WXWINCE__
{
- ::GetCursorPos(&(lvhti.pt));
+ wxGetCursorPosMSW(&(lvhti.pt));
}
::ScreenToClient(GetHwnd(), &lvhti.pt);
#ifdef HAVE_NMLVFINDITEM
case LVN_ODFINDITEM:
- // this message is only used with the virtual list control but
- // even there we don't want to always use it: in a control with
- // sufficiently big number of items (defined as > 1000 here),
- // accidentally pressing a key could result in hanging an
- // application waiting while it performs linear search
- if ( IsVirtual() && GetItemCount() <= 1000 )
+ // Find an item in a (necessarily virtual) list control.
+ if ( IsVirtual() )
{
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)lParam;
// this is the first item we should examine, search from it
// wrapping if necessary
- const int startPos = pFindInfo->iStart;
+ int startPos = pFindInfo->iStart;
const int maxPos = GetItemCount();
- wxCHECK_MSG( startPos <= maxPos, false,
- wxT("bad starting position in LVN_ODFINDITEM") );
- int currentPos = startPos;
- do
+ // Check that the index is valid to ensure that our loop
+ // below always terminates.
+ if ( startPos < 0 || startPos >= maxPos )
{
- // wrap to the beginning if necessary
- if ( currentPos == 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 )
{
- // somewhat surprisingly, LVFI_WRAP isn't set in
- // flags but we still should wrap
- currentPos = 0;
+ 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;
}
- }
- while ( ++currentPos != startPos );
- if ( *result == -1 )
- {
- // not found
- return false;
+ // 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,
}
if ( !processed )
- return wxControl::MSWOnNotify(idCtrl, lParam, result);
+ return wxListCtrlBase::MSWOnNotify(idCtrl, lParam, result);
}
else
{
// 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)
- if ( event.m_itemIndex != -1 && !event.m_item.GetMask() )
+ // 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;
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
wxPaintDC dc(this);
- wxControl::OnPaint(event);
+ wxListCtrlBase::OnPaint(event);
// Reset the device origin since it may have been set
dc.SetDeviceOrigin(0, 0);
}
}
+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)
{
//else: break
}
- return wxControl::MSWWindowProc(nMsg, wParam, lParam);
+ return wxListCtrlBase::MSWWindowProc(nMsg, wParam, lParam);
}
// ----------------------------------------------------------------------------