X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/0c9c5b4376e303f32438853cb73e689e691e76ca..43c42c18d36c703a88b1b7b697bac27fe5608eca:/src/msw/headerctrl.cpp diff --git a/src/msw/headerctrl.cpp b/src/msw/headerctrl.cpp index d351d6ca62..4845715d03 100644 --- a/src/msw/headerctrl.cpp +++ b/src/msw/headerctrl.cpp @@ -23,7 +23,10 @@ #pragma hdrstop #endif +#if wxUSE_HEADERCTRL + #ifndef WX_PRECOMP + #include "wx/app.h" #include "wx/log.h" #endif // WX_PRECOMP @@ -36,6 +39,15 @@ #include "wx/msw/wrapcctl.h" #include "wx/msw/private.h" +#ifndef HDM_SETBITMAPMARGIN + #define HDM_SETBITMAPMARGIN 0x1234 +#endif + +#ifndef Header_SetBitmapMargin + #define Header_SetBitmapMargin(hwnd, margin) \ + ::SendMessage((hwnd), HDM_SETBITMAPMARGIN, (WPARAM)(margin), 0) +#endif + // from src/msw/listctrl.cpp extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick); @@ -49,7 +61,10 @@ extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick); void wxHeaderCtrl::Init() { + m_numColumns = 0; m_imageList = NULL; + m_scrollOffset = 0; + m_colBeingDragged = -1; } bool wxHeaderCtrl::Create(wxWindow *parent, @@ -66,9 +81,19 @@ bool wxHeaderCtrl::Create(wxWindow *parent, if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) ) return false; - if ( !MSWCreateControl(WC_HEADER, _T(""), pos, size) ) + if ( !MSWCreateControl(WC_HEADER, wxT(""), pos, size) ) return false; + // special hack for margins when using comctl32.dll v6 or later: the + // default margin is too big and results in label truncation when the + // column width is just about right to show it together with the sort + // indicator, so reduce it to a smaller value (in principle we could even + // use 0 here but this starts to look ugly) + if ( wxApp::GetComCtl32Version() >= 600 ) + { + Header_SetBitmapMargin(GetHwnd(), ::GetSystemMetrics(SM_CXEDGE)); + } + return true; } @@ -76,13 +101,15 @@ WXDWORD wxHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const { WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle); - if ( style & wxHD_DRAGDROP ) + if ( style & wxHD_ALLOW_REORDER ) msStyle |= HDS_DRAGDROP; // the control looks nicer with these styles and there doesn't seem to be // any reason to not use them so we always do (as for HDS_HORZ it is 0 // anyhow but include it for clarity) - msStyle |= HDS_HORZ | HDS_BUTTONS | HDS_FLAT | HDS_FULLDRAG | HDS_HOTTRACK; + // NOTE: don't use however HDS_FLAT because it makes the control look + // non-native when running WinXP in classic mode + msStyle |= HDS_HORZ | HDS_BUTTONS | HDS_FULLDRAG | HDS_HOTTRACK; return msStyle; } @@ -96,6 +123,14 @@ wxHeaderCtrl::~wxHeaderCtrl() // wxHeaderCtrl scrolling // ---------------------------------------------------------------------------- +void wxHeaderCtrl::DoSetSize(int x, int y, + int w, int h, + int sizeFlags) +{ + wxHeaderCtrlBase::DoSetSize(x + m_scrollOffset, y, w - m_scrollOffset, h, + sizeFlags); +} + void wxHeaderCtrl::DoScrollHorz(int dx) { // as the native control doesn't support offsetting its contents, we use a @@ -104,7 +139,11 @@ void wxHeaderCtrl::DoScrollHorz(int dx) // itself: to be precise, offset it by the scroll increment to the left and // increment its width to still extend to the right boundary to compensate // for it (notice that dx is negative when scrolling to the right) - SetSize(GetPosition().x + dx, -1, GetSize().x - dx, -1, wxSIZE_USE_EXISTING); + m_scrollOffset += dx; + + wxHeaderCtrlBase::DoSetSize(GetPosition().x + dx, -1, + GetSize().x - dx, -1, + wxSIZE_USE_EXISTING); } // ---------------------------------------------------------------------------- @@ -118,7 +157,7 @@ wxSize wxHeaderCtrl::DoGetBestSize() const HDLAYOUT layout = { &rc, &wpos }; if ( !Header_Layout(GetHwnd(), &layout) ) { - wxLogLastError(_T("Header_Layout")); + wxLogLastError(wxT("Header_Layout")); return wxControl::DoGetBestSize(); } @@ -131,7 +170,21 @@ wxSize wxHeaderCtrl::DoGetBestSize() const unsigned int wxHeaderCtrl::DoGetCount() const { - return Header_GetItemCount(GetHwnd()); + // we can't use Header_GetItemCount() here because it doesn't take the + // hidden columns into account and we can't find the hidden columns after + // the last shown one in MSWFromNativeIdx() without knowing where to stop + // so we have to store the columns count internally + return m_numColumns; +} + +int wxHeaderCtrl::GetShownColumnsCount() const +{ + const int numItems = Header_GetItemCount(GetHwnd()); + + wxASSERT_MSG( numItems >= 0 && (unsigned)numItems <= m_numColumns, + "unexpected number of items in the native control" ); + + return numItems; } void wxHeaderCtrl::DoSetCount(unsigned int count) @@ -139,75 +192,126 @@ void wxHeaderCtrl::DoSetCount(unsigned int count) unsigned n; // first delete all old columns - const unsigned countOld = DoGetCount(); + const unsigned countOld = GetShownColumnsCount(); for ( n = 0; n < countOld; n++ ) { if ( !Header_DeleteItem(GetHwnd(), 0) ) { - wxLogLastError(_T("Header_DeleteItem")); + wxLogLastError(wxT("Header_DeleteItem")); } } + // update the column indices order array before changing m_numColumns + DoResizeColumnIndices(m_colIndices, count); + // and add the new ones + m_numColumns = count; + m_isHidden.resize(m_numColumns); for ( n = 0; n < count; n++ ) { - DoSetOrInsertItem(Insert, n); + const wxHeaderColumn& col = GetColumn(n); + if ( col.IsShown() ) + { + m_isHidden[n] = false; + + DoInsertItem(col, n); + } + else // hidden initially + { + m_isHidden[n] = true; + } } } void wxHeaderCtrl::DoUpdate(unsigned int idx) { - DoSetOrInsertItem(Set, idx); + // the native control does provide Header_SetItem() but it's inconvenient + // to use it because it sends HDN_ITEMCHANGING messages and we'd have to + // arrange not to block setting the width from there and the logic would be + // more complicated as we'd have to reset the old values as well as setting + // the new ones -- so instead just recreate the column + + const wxHeaderColumn& col = GetColumn(idx); + if ( col.IsHidden() ) + { + // column is hidden now + if ( !m_isHidden[idx] ) + { + // but it wasn't hidden before, so remove it + Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx)); + + m_isHidden[idx] = true; + } + //else: nothing to do, updating hidden column doesn't have any effect + } + else // column is shown now + { + if ( m_isHidden[idx] ) + { + m_isHidden[idx] = false; + } + else // and it was shown before as well + { + // we need to remove the old column + Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx)); + } + + DoInsertItem(col, idx); + } } -void wxHeaderCtrl::DoSetOrInsertItem(Operation oper, unsigned int idx) +void wxHeaderCtrl::DoInsertItem(const wxHeaderColumn& col, unsigned int idx) { - const wxHeaderColumnBase& col = GetColumn(idx); + wxASSERT_MSG( !col.IsHidden(), "should only be called for shown columns" ); wxHDITEM hdi; // notice that we need to store the string we use the pointer to until we // pass it to the control - wxWxCharBuffer buf; - if ( !col.GetTitle().empty() ) - { - hdi.mask |= HDI_TEXT; - - buf = col.GetTitle().wx_str(); - hdi.pszText = buf.data(); - hdi.cchTextMax = wxStrlen(buf); - } + hdi.mask |= HDI_TEXT; + wxWxCharBuffer buf = col.GetTitle().t_str(); + hdi.pszText = buf.data(); + hdi.cchTextMax = wxStrlen(buf); const wxBitmap bmp = col.GetBitmap(); if ( bmp.IsOk() ) { - const int bmpWidth = bmp.GetWidth(), - bmpHeight = bmp.GetHeight(); + hdi.mask |= HDI_IMAGE; - if ( !m_imageList ) + if ( bmp.IsOk() ) { - m_imageList = new wxImageList(bmpWidth, bmpHeight); - Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList)); + const int bmpWidth = bmp.GetWidth(), + bmpHeight = bmp.GetHeight(); + + if ( !m_imageList ) + { + m_imageList = new wxImageList(bmpWidth, bmpHeight); + (void) // suppress mingw32 warning about unused computed value + Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList)); + } + else // already have an image list + { + // check that all bitmaps we use have the same size + int imageWidth, + imageHeight; + m_imageList->GetSize(0, imageWidth, imageHeight); + + wxASSERT_MSG( imageWidth == bmpWidth && imageHeight == bmpHeight, + "all column bitmaps must have the same size" ); + } + + m_imageList->Add(bmp); + hdi.iImage = m_imageList->GetImageCount() - 1; } - else // already have an image list + else // no bitmap but we still need to update the item { - // check that all bitmaps we use have the same size - int imageWidth, - imageHeight; - m_imageList->GetSize(0, imageWidth, imageHeight); - - wxASSERT_MSG( imageWidth == bmpWidth && imageHeight == bmpHeight, - "all column bitmaps must have the same size" ); + hdi.iImage = I_IMAGENONE; } - - m_imageList->Add(bmp); - hdi.mask |= HDI_IMAGE; - hdi.iImage = m_imageList->GetImageCount() - 1; } if ( col.GetAlignment() != wxALIGN_NOT ) { - hdi.mask |= HDI_FORMAT; + hdi.mask |= HDI_FORMAT | HDF_LEFT; switch ( col.GetAlignment() ) { case wxALIGN_LEFT: @@ -234,26 +338,131 @@ void wxHeaderCtrl::DoSetOrInsertItem(Operation oper, unsigned int idx) hdi.fmt |= col.IsSortOrderAscending() ? HDF_SORTUP : HDF_SORTDOWN; } - if ( col.GetWidth() != wxCOL_WIDTH_DEFAULT || col.IsHidden() ) + if ( col.GetWidth() != wxCOL_WIDTH_DEFAULT ) { hdi.mask |= HDI_WIDTH; - hdi.cxy = col.IsHidden() ? 0 : col.GetWidth(); + hdi.cxy = col.GetWidth(); + } + + hdi.mask |= HDI_ORDER; + hdi.iOrder = MSWToNativeOrder(m_colIndices.Index(idx)); + + if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM, + MSWToNativeIdx(idx), (LPARAM)&hdi) == -1 ) + { + wxLogLastError(wxT("Header_InsertItem()")); + } +} + +void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt& order) +{ + wxArrayInt orderShown; + orderShown.reserve(m_numColumns); + + for ( unsigned n = 0; n < m_numColumns; n++ ) + { + const int idx = order[n]; + if ( GetColumn(idx).IsShown() ) + orderShown.push_back(MSWToNativeIdx(idx)); + } + + if ( !Header_SetOrderArray(GetHwnd(), orderShown.size(), &orderShown[0]) ) + { + wxLogLastError(wxT("Header_GetOrderArray")); + } + + m_colIndices = order; +} + +wxArrayInt wxHeaderCtrl::DoGetColumnsOrder() const +{ + // we don't use Header_GetOrderArray() here because it doesn't return + // information about the hidden columns, instead we just save the columns + // order array in DoSetColumnsOrder() and update it when they're reordered + return m_colIndices; +} + +// ---------------------------------------------------------------------------- +// wxHeaderCtrl indexes and positions translation +// ---------------------------------------------------------------------------- + +int wxHeaderCtrl::MSWToNativeIdx(int idx) +{ + // don't check for GetColumn(idx).IsShown() as it could have just became + // false and we may be called from DoUpdate() to delete the old column + wxASSERT_MSG( !m_isHidden[idx], + "column must be visible to have an " + "index in the native control" ); + + int item = idx; + for ( int i = 0; i < idx; i++ ) + { + if ( GetColumn(i).IsHidden() ) + item--; // one less column the native control knows about + } + + wxASSERT_MSG( item >= 0 && item <= GetShownColumnsCount(), "logic error" ); + + return item; +} + +int wxHeaderCtrl::MSWFromNativeIdx(int item) +{ + wxASSERT_MSG( item >= 0 && item < GetShownColumnsCount(), + "column index out of range" ); + + // reverse the above function + + unsigned idx = item; + for ( unsigned n = 0; n < m_numColumns; n++ ) + { + if ( n > idx ) + break; + + if ( GetColumn(n).IsHidden() ) + idx++; } - const LRESULT rc = ::SendMessage(GetHwnd(), - oper == Set ? HDM_SETITEM : HDM_INSERTITEM, - idx, - (LPARAM)&hdi); - if ( oper == Set ) + wxASSERT_MSG( MSWToNativeIdx(idx) == item, "logic error" ); + + return idx; +} + +int wxHeaderCtrl::MSWToNativeOrder(int pos) +{ + wxASSERT_MSG( pos >= 0 && static_cast(pos) < m_numColumns, + "column position out of range" ); + + int order = pos; + for ( int n = 0; n < pos; n++ ) { - if ( !rc ) - wxLogLastError(_T("Header_SetItem()")); + if ( GetColumn(m_colIndices[n]).IsHidden() ) + order--; } - else // Insert + + wxASSERT_MSG( order >= 0 && order <= GetShownColumnsCount(), "logic error" ); + + return order; +} + +int wxHeaderCtrl::MSWFromNativeOrder(int order) +{ + wxASSERT_MSG( order >= 0 && order < GetShownColumnsCount(), + "native column position out of range" ); + + unsigned pos = order; + for ( unsigned n = 0; n < m_numColumns; n++ ) { - if ( rc == -1 ) - wxLogLastError(_T("Header_InsertItem()")); + if ( n > pos ) + break; + + if ( GetColumn(m_colIndices[n]).IsHidden() ) + pos++; } + + wxASSERT_MSG( MSWToNativeOrder(pos) == order, "logic error" ); + + return pos; } // ---------------------------------------------------------------------------- @@ -266,18 +475,18 @@ wxEventType wxHeaderCtrl::GetClickEventType(bool dblclk, int button) switch ( button ) { case 0: - evtType = dblclk ? wxEVT_COMMAND_HEADER_DCLICK - : wxEVT_COMMAND_HEADER_CLICK; + evtType = dblclk ? wxEVT_HEADER_DCLICK + : wxEVT_HEADER_CLICK; break; case 1: - evtType = dblclk ? wxEVT_COMMAND_HEADER_RIGHT_DCLICK - : wxEVT_COMMAND_HEADER_RIGHT_CLICK; + evtType = dblclk ? wxEVT_HEADER_RIGHT_DCLICK + : wxEVT_HEADER_RIGHT_CLICK; break; case 2: - evtType = dblclk ? wxEVT_COMMAND_HEADER_MIDDLE_DCLICK - : wxEVT_COMMAND_HEADER_MIDDLE_CLICK; + evtType = dblclk ? wxEVT_HEADER_MIDDLE_DCLICK + : wxEVT_HEADER_MIDDLE_CLICK; break; default: @@ -293,9 +502,22 @@ bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) NMHEADER * const nmhdr = (NMHEADER *)lParam; wxEventType evtType = wxEVT_NULL; - int idx = nmhdr->iItem; int width = 0; + int order = -1; + bool veto = false; const UINT code = nmhdr->hdr.code; + + // we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE + // so only access for header control events (and yes, the direction of + // comparisons with FIRST/LAST is correct even if it seems inverted) + int idx = code <= HDN_FIRST && code > HDN_LAST ? nmhdr->iItem : -1; + if ( idx != -1 ) + { + // we also get bogus HDN_BEGINDRAG with -1 index so don't call + // MSWFromNativeIdx() unconditionally for nmhdr->iItem + idx = MSWFromNativeIdx(idx); + } + switch ( code ) { // click events @@ -304,6 +526,9 @@ bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) case HDN_ITEMCLICK: case HDN_ITEMDBLCLICK: evtType = GetClickEventType(code == HDN_ITEMDBLCLICK, nmhdr->iButton); + + // We're not dragging any more. + m_colBeingDragged = -1; break; // although we should get the notifications about the right clicks @@ -315,13 +540,21 @@ bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) POINT pt; idx = wxMSWGetColumnClicked(&nmhdr->hdr, &pt); if ( idx != wxNOT_FOUND ) - evtType = GetClickEventType(code == NM_RDBLCLK, 1); + { + idx = MSWFromNativeIdx(idx); + + // due to a bug in mingw32 headers NM_RDBLCLK is signed + // there so we need a cast to avoid warnings about signed/ + // unsigned comparison + evtType = GetClickEventType( + code == static_cast(NM_RDBLCLK), 1); + } //else: ignore clicks outside any column } break; case HDN_DIVIDERDBLCLICK: - evtType = wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK; + evtType = wxEVT_HEADER_SEPARATOR_DCLICK; break; @@ -332,21 +565,109 @@ bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) // ASCII and Unicode versions of this message case HDN_BEGINTRACKA: case HDN_BEGINTRACKW: - evtType = wxEVT_COMMAND_HEADER_BEGIN_DRAG; - // fall through + // non-resizable columns can't be resized no matter what, don't + // even generate any events for them + if ( !GetColumn(idx).IsResizeable() ) + { + veto = true; + break; + } - case HDN_TRACKA: - case HDN_TRACKW: - if ( evtType == wxEVT_NULL ) - evtType = wxEVT_COMMAND_HEADER_DRAGGING; + evtType = wxEVT_HEADER_BEGIN_RESIZE; // fall through case HDN_ENDTRACKA: case HDN_ENDTRACKW: + width = nmhdr->pitem->cxy; + if ( evtType == wxEVT_NULL ) - evtType = wxEVT_COMMAND_HEADER_END_DRAG; + { + evtType = wxEVT_HEADER_END_RESIZE; - width = nmhdr->pitem->cxy; + // don't generate events with invalid width + const int minWidth = GetColumn(idx).GetMinWidth(); + if ( width < minWidth ) + width = minWidth; + } + break; + + // The control is not supposed to send HDN_TRACK when using + // HDS_FULLDRAG (which we do use) but apparently some versions of + // comctl32.dll still do it, see #13506, so catch both messages + // just in case we are dealing with one of these buggy versions. + case HDN_TRACK: + case HDN_ITEMCHANGING: + if ( nmhdr->pitem && (nmhdr->pitem->mask & HDI_WIDTH) ) + { + // prevent the column from being shrunk beneath its min width + width = nmhdr->pitem->cxy; + if ( width < GetColumn(idx).GetMinWidth() ) + { + // don't generate any events and prevent the change from + // happening + veto = true; + } + else // width is acceptable + { + // generate the resizing event from here as we don't seem + // to be getting HDN_TRACK events at all, at least with + // comctl32.dll v6 + evtType = wxEVT_HEADER_RESIZING; + } + } + break; + + + // column reordering events + // ------------------------ + + case HDN_BEGINDRAG: + // Windows sometimes sends us events with invalid column indices + if ( nmhdr->iItem == -1 ) + break; + + // If we are dragging a column that is not draggable and the mouse + // is moved over a different column then we get the column number from + // the column under the mouse. This results in an unexpected behaviour + // if this column is draggable. To prevent this remember the column we + // are dragging for the complete drag and drop cycle. + if ( m_colBeingDragged == -1 ) + { + m_colBeingDragged = idx; + } + + // column must have the appropriate flag to be draggable + if ( !GetColumn(m_colBeingDragged).IsReorderable() ) + { + veto = true; + break; + } + + evtType = wxEVT_HEADER_BEGIN_REORDER; + break; + + case HDN_ENDDRAG: + wxASSERT_MSG( nmhdr->pitem->mask & HDI_ORDER, "should have order" ); + order = nmhdr->pitem->iOrder; + + // we also get messages with invalid order when column reordering + // is cancelled (e.g. by pressing Esc) + if ( order == -1 ) + break; + + order = MSWFromNativeOrder(order); + + evtType = wxEVT_HEADER_END_REORDER; + + // We (successfully) ended dragging the column. + m_colBeingDragged = -1; + break; + + case NM_RELEASEDCAPTURE: + evtType = wxEVT_HEADER_DRAGGING_CANCELLED; + + // Dragging the column was cancelled. + m_colBeingDragged = -1; break; } @@ -358,22 +679,43 @@ bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) event.SetEventObject(this); event.SetColumn(idx); event.SetWidth(width); + if ( order != -1 ) + event.SetNewOrder(order); - if ( GetEventHandler()->ProcessEvent(event) ) + const bool processed = GetEventHandler()->ProcessEvent(event); + + if ( processed && !event.IsAllowed() ) + veto = true; + + if ( !veto ) { - if ( !event.IsAllowed() ) + // special post-processing for HDN_ENDDRAG: we need to update the + // internal column indices array if this is allowed to go ahead as + // the native control is going to reorder its columns now + if ( evtType == wxEVT_HEADER_END_REORDER ) + MoveColumnInOrderArray(m_colIndices, idx, order); + + if ( processed ) { - // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING - // interpret TRUE return value as meaning to stop the control - // default handling of the message - *result = TRUE; + // skip default processing below + return true; } - - return true; } } + if ( veto ) + { + // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING + // interpret TRUE return value as meaning to stop the control + // default handling of the message + *result = TRUE; + + return true; + } + return wxHeaderCtrlBase::MSWOnNotify(idCtrl, lParam, result); } #endif // wxHAS_GENERIC_HEADERCTRL + +#endif // wxUSE_HEADERCTRL