#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
wxDECLARE_NO_COPY_CLASS(wxMSWListItemData);
};
-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;
{
if ( flag != m_windowStyle )
{
- wxControl::SetWindowStyleFlag(flag);
+ wxListCtrlBase::SetWindowStyleFlag(flag);
UpdateStyle();
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
// ----------------------------------------------------------------------------
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 )
return false;
- // Newer versions of Windows have a special message for cancelling editing,
- // use it if available.
-#ifdef ListView_CancelEditLabel
- if ( cancel && (wxApp::GetComCtl32Version() >= 600) )
- {
- ListView_CancelEditLabel(GetHwnd());
- }
- else
-#endif // ListView_CancelEditLabel
- {
- // 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);
- }
+ // 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;
}
{
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
}
if ( ignore )
- return wxControl::MSWOnNotify(idCtrl, lParam, result);
+ return wxListCtrlBase::MSWOnNotify(idCtrl, lParam, result);
}
else
#endif // defined(HDN_BEGINTRACKA)
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;
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);
}
// ----------------------------------------------------------------------------