X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/11358d3976afbde5e5924799e4dce2ffc0280f2c..1daa0751dbaa8a886ba9d730a09f12a2623e7461:/src/msw/listctrl.cpp diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index 406282e305..6b4a882131 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: listctrl.cpp +// Name: src/msw/listctrl.cpp // Purpose: wxListCtrl // Author: Julian Smart // Modified by: @@ -84,6 +84,31 @@ #define LVN_ODCACHEHINT (-113) #endif +#ifndef ListView_GetHeader + #define ListView_GetHeader(w) (HWND)SendMessage((w),LVM_GETHEADER,0,0) +#endif + +#ifndef LVM_GETHEADER + #define LVM_GETHEADER (LVM_FIRST+31) +#endif + +#ifndef Header_GetItemRect + #define Header_GetItemRect(w,i,r) \ + (BOOL)SendMessage((w),HDM_GETITEMRECT,(WPARAM)(i),(LPARAM)(r)) +#endif + +#ifndef HDM_GETITEMRECT + #define HDM_GETITEMRECT (HDM_FIRST+7) +#endif + +#ifndef LVCF_IMAGE + #define LVCF_IMAGE 0x0010 +#endif + +#ifndef LVCFMT_BITMAP_ON_RIGHT + #define LVCFMT_BITMAP_ON_RIGHT 0x1000 +#endif + // ---------------------------------------------------------------------------- // private functions // ---------------------------------------------------------------------------- @@ -100,6 +125,10 @@ static void wxConvertFromMSWListItem(HWND hwndListCtrl, wxListItem& info, /* const */ LV_ITEM& lvItem); +// convert our wxListItem to LV_COLUMN +static void wxConvertToMSWListCol(int col, const wxListItem& item, + LV_COLUMN& lvCol); + // ---------------------------------------------------------------------------- // events // ---------------------------------------------------------------------------- @@ -124,12 +153,15 @@ DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_END_DRAG) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_ACTIVATED) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_FOCUSED) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_CACHE_HINT) IMPLEMENT_DYNAMIC_CLASS(wxListCtrl, wxControl) IMPLEMENT_DYNAMIC_CLASS(wxListView, wxListCtrl) IMPLEMENT_DYNAMIC_CLASS(wxListItem, wxObject) +IMPLEMENT_DYNAMIC_CLASS(wxListEvent, wxNotifyEvent) + BEGIN_EVENT_TABLE(wxListCtrl, wxControl) EVT_PAINT(wxListCtrl::OnPaint) END_EVENT_TABLE() @@ -138,41 +170,6 @@ END_EVENT_TABLE() // implementation // ============================================================================ -// ---------------------------------------------------------------------------- -// wxListEvent -// ---------------------------------------------------------------------------- - -void wxListEvent::CopyObject(wxObject& object_dest) const -{ - wxListEvent *obj = (wxListEvent *)&object_dest; - - wxNotifyEvent::CopyObject(object_dest); - - obj->m_code = m_code; - obj->m_itemIndex = m_itemIndex; - obj->m_oldItemIndex = m_oldItemIndex; - obj->m_col = m_col; - obj->m_cancelled = m_cancelled; - obj->m_pointDrag = m_pointDrag; - obj->m_item.m_mask = m_item.m_mask; - obj->m_item.m_itemId = m_item.m_itemId; - obj->m_item.m_col = m_item.m_col; - obj->m_item.m_state = m_item.m_state; - obj->m_item.m_stateMask = m_item.m_stateMask; - obj->m_item.m_text = m_item.m_text; - obj->m_item.m_image = m_item.m_image; - obj->m_item.m_data = m_item.m_data; - obj->m_item.m_format = m_item.m_format; - obj->m_item.m_width = m_item.m_width; - - if ( m_item.HasAttributes() ) - { - obj->m_item.SetTextColour(m_item.GetTextColour()); - obj->m_item.SetBackgroundColour(m_item.GetBackgroundColour()); - obj->m_item.SetFont(m_item.GetFont()); - } -} - // ---------------------------------------------------------------------------- // wxListCtrl construction // ---------------------------------------------------------------------------- @@ -536,9 +533,7 @@ bool wxListCtrl::SetBackgroundColour(const wxColour& col) bool wxListCtrl::GetColumn(int col, wxListItem& item) const { LV_COLUMN lvCol; - lvCol.mask = 0; - lvCol.fmt = 0; - lvCol.pszText = NULL; + wxZeroMemory(lvCol); if ( item.m_mask & wxLIST_MASK_TEXT ) { @@ -547,7 +542,7 @@ bool wxListCtrl::GetColumn(int col, wxListItem& item) const lvCol.cchTextMax = 512; } - bool success = (ListView_GetColumn(GetHwnd(), col, & lvCol) != 0); + bool success = ListView_GetColumn(GetHwnd(), col, & lvCol) != 0; // item.m_subItem = lvCol.iSubItem; item.m_width = lvCol.cx; @@ -575,41 +570,9 @@ bool wxListCtrl::GetColumn(int col, wxListItem& item) const bool wxListCtrl::SetColumn(int col, wxListItem& item) { LV_COLUMN lvCol; - lvCol.mask = 0; - lvCol.fmt = 0; - lvCol.pszText = NULL; - - if ( item.m_mask & wxLIST_MASK_TEXT ) - { - lvCol.mask |= LVCF_TEXT; - lvCol.pszText = WXSTRINGCAST item.m_text; - lvCol.cchTextMax = 0; // Ignored - } - if ( item.m_mask & wxLIST_MASK_FORMAT ) - { - lvCol.mask |= LVCF_FMT; - - if ( item.m_format == wxLIST_FORMAT_LEFT ) - lvCol.fmt = LVCFMT_LEFT; - if ( item.m_format == wxLIST_FORMAT_RIGHT ) - lvCol.fmt = LVCFMT_RIGHT; - if ( item.m_format == wxLIST_FORMAT_CENTRE ) - lvCol.fmt = LVCFMT_CENTER; - } - - if ( item.m_mask & wxLIST_MASK_WIDTH ) - { - lvCol.mask |= LVCF_WIDTH; - lvCol.cx = item.m_width; + wxConvertToMSWListCol(col, item, lvCol); - if ( lvCol.cx == wxLIST_AUTOSIZE) - lvCol.cx = LVSCW_AUTOSIZE; - else if ( lvCol.cx == wxLIST_AUTOSIZE_USEHEADER) - lvCol.cx = LVSCW_AUTOSIZE_USEHEADER; - } - lvCol.mask |= LVCF_SUBITEM; - lvCol.iSubItem = col; - return (ListView_SetColumn(GetHwnd(), col, & lvCol) != 0); + return ListView_SetColumn(GetHwnd(), col, &lvCol) != 0; } // Gets the column width @@ -631,7 +594,7 @@ bool wxListCtrl::SetColumnWidth(int col, int width) else if ( width2 == wxLIST_AUTOSIZE_USEHEADER) width2 = LVSCW_AUTOSIZE_USEHEADER; - return (ListView_SetColumnWidth(GetHwnd(), col2, width2) != 0); + return ListView_SetColumnWidth(GetHwnd(), col2, width2) != 0; } // Gets the number of items that can fit vertically in the @@ -706,12 +669,17 @@ bool wxListCtrl::SetItem(wxListItem& info) LV_ITEM item; wxConvertToMSWListItem(this, info, item); - item.cchTextMax = 0; - if ( !ListView_SetItem(GetHwnd(), &item) ) + // we could be changing only the attribute in which case we don't need to + // call ListView_SetItem() at all + if ( item.mask ) { - wxLogDebug(_T("ListView_SetItem() failed")); + item.cchTextMax = 0; + if ( !ListView_SetItem(GetHwnd(), &item) ) + { + wxLogDebug(_T("ListView_SetItem() failed")); - return FALSE; + return FALSE; + } } // we need to update the item immediately to show the new image @@ -736,7 +704,7 @@ bool wxListCtrl::SetItem(wxListItem& info) if ( updateNow ) { // we need this to make the change visible right now - ListView_Update(GetHwnd(), item.iItem); + RefreshItem(item.iItem); } return TRUE; @@ -783,6 +751,22 @@ bool wxListCtrl::SetItemState(long item, long state, long stateMask) wxConvertToMSWFlags(state, stateMask, lvItem); + // 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() && + (stateMask & wxLIST_STATE_FOCUSED) && + (state & wxLIST_STATE_FOCUSED) ) + { + focusOld = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); + } + else + { + focusOld = -1; + } + if ( !::SendMessage(GetHwnd(), LVM_SETITEMSTATE, (WPARAM)item, (LPARAM)&lvItem) ) { @@ -791,6 +775,17 @@ bool wxListCtrl::SetItemState(long item, long state, long stateMask) 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); + } + } + return TRUE; } @@ -859,26 +854,33 @@ bool wxListCtrl::SetItemData(long item, long data) // Gets the item rectangle bool wxListCtrl::GetItemRect(long item, wxRect& rect, int code) const { - RECT rect2; + RECT rectWin; - int code2 = LVIR_BOUNDS; + int codeWin; if ( code == wxLIST_RECT_BOUNDS ) - code2 = LVIR_BOUNDS; + codeWin = LVIR_BOUNDS; else if ( code == wxLIST_RECT_ICON ) - code2 = LVIR_ICON; + codeWin = LVIR_ICON; else if ( code == wxLIST_RECT_LABEL ) - code2 = LVIR_LABEL; + codeWin = LVIR_LABEL; + else + { + wxFAIL_MSG( _T("incorrect code in GetItemRect()") ); + + codeWin = LVIR_BOUNDS; + } #ifdef __WXWINE__ - bool success = (ListView_GetItemRect(GetHwnd(), (int) item, &rect2 ) != 0); + bool success = ListView_GetItemRect(GetHwnd(), (int) item, &rectWin ) != 0; #else - bool success = (ListView_GetItemRect(GetHwnd(), (int) item, &rect2, code2) != 0); + bool success = ListView_GetItemRect(GetHwnd(), (int) item, &rectWin, codeWin) != 0; #endif - rect.x = rect2.left; - rect.y = rect2.top; - rect.width = rect2.right - rect2.left; - rect.height = rect2.bottom - rect2.top; + rect.x = rectWin.left; + rect.y = rectWin.top; + rect.width = rectWin.right - rectWin.left; + rect.height = rectWin.bottom - rectWin.top; + return success; } @@ -1054,13 +1056,41 @@ bool wxListCtrl::Arrange(int flag) // Deletes an item bool wxListCtrl::DeleteItem(long item) { - return (ListView_DeleteItem(GetHwnd(), (int) item) != 0); + if ( !ListView_DeleteItem(GetHwnd(), (int) item) ) + { + wxLogLastError(_T("ListView_DeleteItem")); + return FALSE; + } + + // 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() { - return (ListView_DeleteAllItems(GetHwnd()) != 0); + return ListView_DeleteAllItems(GetHwnd()) != 0; } // Deletes all items @@ -1285,49 +1315,33 @@ long wxListCtrl::InsertItem(long index, const wxString& label, int imageIndex) long wxListCtrl::InsertColumn(long col, wxListItem& item) { LV_COLUMN lvCol; - lvCol.mask = 0; - lvCol.fmt = 0; - lvCol.pszText = NULL; + wxConvertToMSWListCol(col, item, lvCol); - if ( item.m_mask & wxLIST_MASK_TEXT ) - { - lvCol.mask |= LVCF_TEXT; - lvCol.pszText = WXSTRINGCAST item.m_text; - lvCol.cchTextMax = 0; // Ignored - } - if ( item.m_mask & wxLIST_MASK_FORMAT ) - { - lvCol.mask |= LVCF_FMT; - - if ( item.m_format == wxLIST_FORMAT_LEFT ) - lvCol.fmt = LVCFMT_LEFT; - if ( item.m_format == wxLIST_FORMAT_RIGHT ) - lvCol.fmt = LVCFMT_RIGHT; - if ( item.m_format == wxLIST_FORMAT_CENTRE ) - lvCol.fmt = LVCFMT_CENTER; - } - - lvCol.mask |= LVCF_WIDTH; - if ( item.m_mask & wxLIST_MASK_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; - } - else + if ( !(lvCol.mask & LVCF_WIDTH) ) { // always give some width to the new column: this one is compatible - // with wxGTK + // with the generic version + lvCol.mask |= LVCF_WIDTH; lvCol.cx = 80; } - lvCol.mask |= LVCF_SUBITEM; - lvCol.iSubItem = col; + // when we insert a column which can contain an image, we must specify this + // flag right now as doing it later in SetColumn() has no effect + // + // we use LVCFMT_BITMAP_ON_RIGHT by default because without it there is no + // way to dynamically set/clear the bitmap as the column without a bitmap + // on the left looks ugly (there is a hole) + // + // unfortunately with my version of comctl32.dll (5.80), the left column + // image is always on the left and it seems that it's a "feature" - I + // didn't find any way to work around it in any case + if ( lvCol.mask & LVCF_IMAGE ) + { + lvCol.mask |= LVCF_FMT; + lvCol.fmt |= LVCFMT_BITMAP_ON_RIGHT; + } - bool success = ListView_InsertColumn(GetHwnd(), col, & lvCol) != -1; + bool success = ListView_InsertColumn(GetHwnd(), col, &lvCol) != -1; if ( success ) { m_colCount++; @@ -1478,17 +1492,28 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) // ----------------- 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 LVM_FIRST + // 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 ) { +#ifdef __WATCOMC__ + HD_NOTIFY *nmHDR = (HD_NOTIFY *)nmhdr; +#else NMHEADER *nmHDR = (NMHEADER *)nmhdr; +#endif event.m_itemIndex = -1; switch ( nmhdr->code ) @@ -1563,7 +1588,9 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) return wxControl::MSWOnNotify(idCtrl, lParam, result); } } - else if ( nmhdr->hwndFrom == GetHwnd() ) + else +#endif // defined(LVM_FIRST) + if ( nmhdr->hwndFrom == GetHwnd() ) { // almost all messages use NM_LISTVIEW NM_LISTVIEW *nmLV = (NM_LISTVIEW *)nmhdr; @@ -1625,7 +1652,7 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) { eventType = wxEVT_COMMAND_LIST_END_LABEL_EDIT; LV_DISPINFO *info = (LV_DISPINFO *)lParam; - wxConvertFromMSWListItem(GetHwnd(), event.m_item, info->item); + wxConvertFromMSWListItem(NULL, event.m_item, info->item); if ( info->item.pszText == NULL || info->item.iItem == -1 ) return FALSE; @@ -1647,22 +1674,52 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) break; case LVN_ITEMCHANGED: - // This needs to be sent to wxListCtrl as a rather more concrete - // event. For now, just detect a selection or deselection. - if ( (nmLV->uNewState & LVIS_SELECTED) && !(nmLV->uOldState & LVIS_SELECTED) ) - { - eventType = wxEVT_COMMAND_LIST_ITEM_SELECTED; - event.m_itemIndex = nmLV->iItem; - } - else if ( !(nmLV->uNewState & LVIS_SELECTED) && (nmLV->uOldState & LVIS_SELECTED) ) + // we translate this catch all message into more interesting + // (and more easy to process) wxWindows events + + // first of all, we deal with the state change events only + if ( nmLV->uChanged & LVIF_STATE ) { - eventType = wxEVT_COMMAND_LIST_ITEM_DESELECTED; - event.m_itemIndex = nmLV->iItem; + // temp vars for readability + const UINT stOld = nmLV->uOldState; + const UINT stNew = nmLV->uNewState; + + // has the focus changed? + if ( !(stOld & LVIS_FOCUSED) && (stNew & LVIS_FOCUSED) ) + { + eventType = wxEVT_COMMAND_LIST_ITEM_FOCUSED; + event.m_itemIndex = nmLV->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)GetEventHandler()->ProcessEvent(event); + } + else // no focus event to send + { + // then need to set m_itemIndex as it wasn't done + // above + event.m_itemIndex = nmLV->iItem; + } + + eventType = stNew & LVIS_SELECTED + ? wxEVT_COMMAND_LIST_ITEM_SELECTED + : wxEVT_COMMAND_LIST_ITEM_DESELECTED; + } } - else + + if ( eventType == wxEVT_NULL ) { + // not an interesting event for us return FALSE; } + break; case LVN_KEYDOWN: @@ -1687,7 +1744,11 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) else { eventType = wxEVT_COMMAND_LIST_KEY_DOWN; - event.m_code = wxCharCodeMSWToWX(wVKey); + + // 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_itemIndex = @@ -1796,10 +1857,13 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) wxStrncpy(lvi.pszText, text, lvi.cchTextMax); } +#if defined(_WIN32_IE) && _WIN32_IE >= 0x300 \ + && !( defined(__GNUWIN32__) && !wxCHECK_W32API_VERSION( 1, 1 ) ) if ( lvi.mask & LVIF_IMAGE ) { lvi.iImage = OnGetItemImage(item); } +#endif // a little dose of healthy paranoia: as we never use // LVM_SETCALLBACKMASK we're not supposed to get these ones @@ -1823,7 +1887,6 @@ bool wxListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) // process the event // ----------------- - event.SetEventObject( this ); event.SetEventType(eventType); if ( !GetEventHandler()->ProcessEvent(event) ) @@ -2055,10 +2118,18 @@ void wxListCtrl::SetItemCount(long count) void wxListCtrl::RefreshItem(long item) { + // strangely enough, ListView_Update() results in much more flicker here + // than a dumb Refresh() -- why? +#if 0 if ( !ListView_Update(GetHwnd(), item) ) { wxLogLastError(_T("ListView_Update")); } +#else // 1 + wxRect rect; + GetItemRect(item, rect); + RefreshRect(rect); +#endif // 0/1 } void wxListCtrl::RefreshItems(long itemFrom, long itemTo) @@ -2271,20 +2342,52 @@ static void wxConvertToMSWListItem(const wxListCtrl *ctrl, lvItem.mask |= LVIF_PARAM; } -// ---------------------------------------------------------------------------- -// List event -// ---------------------------------------------------------------------------- +static void wxConvertToMSWListCol(int col, const wxListItem& item, + LV_COLUMN& lvCol) +{ + wxZeroMemory(lvCol); -IMPLEMENT_DYNAMIC_CLASS(wxListEvent, wxNotifyEvent) + if ( item.m_mask & wxLIST_MASK_TEXT ) + { + lvCol.mask |= LVCF_TEXT; + lvCol.pszText = (wxChar *)item.m_text.c_str(); // cast is safe + } -wxListEvent::wxListEvent(wxEventType commandType, int id) - : wxNotifyEvent(commandType, id) -{ - m_code = 0; - m_itemIndex = 0; - m_oldItemIndex = 0; - m_col = 0; - m_cancelled = FALSE; + 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; + } + +#if defined(_WIN32_IE) && _WIN32_IE >= 0x300 \ + && !( defined(__GNUWIN32__) && !wxCHECK_W32API_VERSION( 1, 1 ) ) + if ( item.m_mask & wxLIST_MASK_IMAGE ) + { + if ( wxTheApp->GetComCtl32Version() >= 470 ) + { + lvCol.mask |= LVCF_IMAGE; + lvCol.iImage = item.m_image; + } + //else: it doesn't support item images anyhow + } +#endif } #endif // wxUSE_LISTCTRL