]> git.saurik.com Git - wxWidgets.git/blob - src/msw/headerctrl.cpp
4845715d03e77bb14133d197a22f009e1f4b8d3e
[wxWidgets.git] / src / msw / headerctrl.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/headerctrl.cpp
3 // Purpose: implementation of wxHeaderCtrl for wxMSW
4 // Author: Vadim Zeitlin
5 // Created: 2008-12-01
6 // RCS-ID: $Id$
7 // Copyright: (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_HEADERCTRL
27
28 #ifndef WX_PRECOMP
29 #include "wx/app.h"
30 #include "wx/log.h"
31 #endif // WX_PRECOMP
32
33 #include "wx/headerctrl.h"
34
35 #ifndef wxHAS_GENERIC_HEADERCTRL
36
37 #include "wx/imaglist.h"
38
39 #include "wx/msw/wrapcctl.h"
40 #include "wx/msw/private.h"
41
42 #ifndef HDM_SETBITMAPMARGIN
43 #define HDM_SETBITMAPMARGIN 0x1234
44 #endif
45
46 #ifndef Header_SetBitmapMargin
47 #define Header_SetBitmapMargin(hwnd, margin) \
48 ::SendMessage((hwnd), HDM_SETBITMAPMARGIN, (WPARAM)(margin), 0)
49 #endif
50
51 // from src/msw/listctrl.cpp
52 extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick);
53
54 // ============================================================================
55 // wxHeaderCtrl implementation
56 // ============================================================================
57
58 // ----------------------------------------------------------------------------
59 // wxHeaderCtrl construction/destruction
60 // ----------------------------------------------------------------------------
61
62 void wxHeaderCtrl::Init()
63 {
64 m_numColumns = 0;
65 m_imageList = NULL;
66 m_scrollOffset = 0;
67 m_colBeingDragged = -1;
68 }
69
70 bool wxHeaderCtrl::Create(wxWindow *parent,
71 wxWindowID id,
72 const wxPoint& pos,
73 const wxSize& size,
74 long style,
75 const wxString& name)
76 {
77 // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES)
78 // here as we already call InitCommonControls() in wxApp initialization
79 // code which covers this
80
81 if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
82 return false;
83
84 if ( !MSWCreateControl(WC_HEADER, wxT(""), pos, size) )
85 return false;
86
87 // special hack for margins when using comctl32.dll v6 or later: the
88 // default margin is too big and results in label truncation when the
89 // column width is just about right to show it together with the sort
90 // indicator, so reduce it to a smaller value (in principle we could even
91 // use 0 here but this starts to look ugly)
92 if ( wxApp::GetComCtl32Version() >= 600 )
93 {
94 Header_SetBitmapMargin(GetHwnd(), ::GetSystemMetrics(SM_CXEDGE));
95 }
96
97 return true;
98 }
99
100 WXDWORD wxHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
101 {
102 WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle);
103
104 if ( style & wxHD_ALLOW_REORDER )
105 msStyle |= HDS_DRAGDROP;
106
107 // the control looks nicer with these styles and there doesn't seem to be
108 // any reason to not use them so we always do (as for HDS_HORZ it is 0
109 // anyhow but include it for clarity)
110 // NOTE: don't use however HDS_FLAT because it makes the control look
111 // non-native when running WinXP in classic mode
112 msStyle |= HDS_HORZ | HDS_BUTTONS | HDS_FULLDRAG | HDS_HOTTRACK;
113
114 return msStyle;
115 }
116
117 wxHeaderCtrl::~wxHeaderCtrl()
118 {
119 delete m_imageList;
120 }
121
122 // ----------------------------------------------------------------------------
123 // wxHeaderCtrl scrolling
124 // ----------------------------------------------------------------------------
125
126 void wxHeaderCtrl::DoSetSize(int x, int y,
127 int w, int h,
128 int sizeFlags)
129 {
130 wxHeaderCtrlBase::DoSetSize(x + m_scrollOffset, y, w - m_scrollOffset, h,
131 sizeFlags);
132 }
133
134 void wxHeaderCtrl::DoScrollHorz(int dx)
135 {
136 // as the native control doesn't support offsetting its contents, we use a
137 // hack here to make it appear correctly when the parent is scrolled:
138 // instead of scrolling or repainting we simply move the control window
139 // itself: to be precise, offset it by the scroll increment to the left and
140 // increment its width to still extend to the right boundary to compensate
141 // for it (notice that dx is negative when scrolling to the right)
142 m_scrollOffset += dx;
143
144 wxHeaderCtrlBase::DoSetSize(GetPosition().x + dx, -1,
145 GetSize().x - dx, -1,
146 wxSIZE_USE_EXISTING);
147 }
148
149 // ----------------------------------------------------------------------------
150 // wxHeaderCtrl geometry calculation
151 // ----------------------------------------------------------------------------
152
153 wxSize wxHeaderCtrl::DoGetBestSize() const
154 {
155 RECT rc = wxGetClientRect(GetHwndOf(GetParent()));
156 WINDOWPOS wpos;
157 HDLAYOUT layout = { &rc, &wpos };
158 if ( !Header_Layout(GetHwnd(), &layout) )
159 {
160 wxLogLastError(wxT("Header_Layout"));
161 return wxControl::DoGetBestSize();
162 }
163
164 return wxSize(wpos.cx, wpos.cy);
165 }
166
167 // ----------------------------------------------------------------------------
168 // wxHeaderCtrl columns managements
169 // ----------------------------------------------------------------------------
170
171 unsigned int wxHeaderCtrl::DoGetCount() const
172 {
173 // we can't use Header_GetItemCount() here because it doesn't take the
174 // hidden columns into account and we can't find the hidden columns after
175 // the last shown one in MSWFromNativeIdx() without knowing where to stop
176 // so we have to store the columns count internally
177 return m_numColumns;
178 }
179
180 int wxHeaderCtrl::GetShownColumnsCount() const
181 {
182 const int numItems = Header_GetItemCount(GetHwnd());
183
184 wxASSERT_MSG( numItems >= 0 && (unsigned)numItems <= m_numColumns,
185 "unexpected number of items in the native control" );
186
187 return numItems;
188 }
189
190 void wxHeaderCtrl::DoSetCount(unsigned int count)
191 {
192 unsigned n;
193
194 // first delete all old columns
195 const unsigned countOld = GetShownColumnsCount();
196 for ( n = 0; n < countOld; n++ )
197 {
198 if ( !Header_DeleteItem(GetHwnd(), 0) )
199 {
200 wxLogLastError(wxT("Header_DeleteItem"));
201 }
202 }
203
204 // update the column indices order array before changing m_numColumns
205 DoResizeColumnIndices(m_colIndices, count);
206
207 // and add the new ones
208 m_numColumns = count;
209 m_isHidden.resize(m_numColumns);
210 for ( n = 0; n < count; n++ )
211 {
212 const wxHeaderColumn& col = GetColumn(n);
213 if ( col.IsShown() )
214 {
215 m_isHidden[n] = false;
216
217 DoInsertItem(col, n);
218 }
219 else // hidden initially
220 {
221 m_isHidden[n] = true;
222 }
223 }
224 }
225
226 void wxHeaderCtrl::DoUpdate(unsigned int idx)
227 {
228 // the native control does provide Header_SetItem() but it's inconvenient
229 // to use it because it sends HDN_ITEMCHANGING messages and we'd have to
230 // arrange not to block setting the width from there and the logic would be
231 // more complicated as we'd have to reset the old values as well as setting
232 // the new ones -- so instead just recreate the column
233
234 const wxHeaderColumn& col = GetColumn(idx);
235 if ( col.IsHidden() )
236 {
237 // column is hidden now
238 if ( !m_isHidden[idx] )
239 {
240 // but it wasn't hidden before, so remove it
241 Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx));
242
243 m_isHidden[idx] = true;
244 }
245 //else: nothing to do, updating hidden column doesn't have any effect
246 }
247 else // column is shown now
248 {
249 if ( m_isHidden[idx] )
250 {
251 m_isHidden[idx] = false;
252 }
253 else // and it was shown before as well
254 {
255 // we need to remove the old column
256 Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx));
257 }
258
259 DoInsertItem(col, idx);
260 }
261 }
262
263 void wxHeaderCtrl::DoInsertItem(const wxHeaderColumn& col, unsigned int idx)
264 {
265 wxASSERT_MSG( !col.IsHidden(), "should only be called for shown columns" );
266
267 wxHDITEM hdi;
268
269 // notice that we need to store the string we use the pointer to until we
270 // pass it to the control
271 hdi.mask |= HDI_TEXT;
272 wxWxCharBuffer buf = col.GetTitle().t_str();
273 hdi.pszText = buf.data();
274 hdi.cchTextMax = wxStrlen(buf);
275
276 const wxBitmap bmp = col.GetBitmap();
277 if ( bmp.IsOk() )
278 {
279 hdi.mask |= HDI_IMAGE;
280
281 if ( bmp.IsOk() )
282 {
283 const int bmpWidth = bmp.GetWidth(),
284 bmpHeight = bmp.GetHeight();
285
286 if ( !m_imageList )
287 {
288 m_imageList = new wxImageList(bmpWidth, bmpHeight);
289 (void) // suppress mingw32 warning about unused computed value
290 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList));
291 }
292 else // already have an image list
293 {
294 // check that all bitmaps we use have the same size
295 int imageWidth,
296 imageHeight;
297 m_imageList->GetSize(0, imageWidth, imageHeight);
298
299 wxASSERT_MSG( imageWidth == bmpWidth && imageHeight == bmpHeight,
300 "all column bitmaps must have the same size" );
301 }
302
303 m_imageList->Add(bmp);
304 hdi.iImage = m_imageList->GetImageCount() - 1;
305 }
306 else // no bitmap but we still need to update the item
307 {
308 hdi.iImage = I_IMAGENONE;
309 }
310 }
311
312 if ( col.GetAlignment() != wxALIGN_NOT )
313 {
314 hdi.mask |= HDI_FORMAT | HDF_LEFT;
315 switch ( col.GetAlignment() )
316 {
317 case wxALIGN_LEFT:
318 hdi.fmt |= HDF_LEFT;
319 break;
320
321 case wxALIGN_CENTER:
322 case wxALIGN_CENTER_HORIZONTAL:
323 hdi.fmt |= HDF_CENTER;
324 break;
325
326 case wxALIGN_RIGHT:
327 hdi.fmt |= HDF_RIGHT;
328 break;
329
330 default:
331 wxFAIL_MSG( "invalid column header alignment" );
332 }
333 }
334
335 if ( col.IsSortKey() )
336 {
337 hdi.mask |= HDI_FORMAT;
338 hdi.fmt |= col.IsSortOrderAscending() ? HDF_SORTUP : HDF_SORTDOWN;
339 }
340
341 if ( col.GetWidth() != wxCOL_WIDTH_DEFAULT )
342 {
343 hdi.mask |= HDI_WIDTH;
344 hdi.cxy = col.GetWidth();
345 }
346
347 hdi.mask |= HDI_ORDER;
348 hdi.iOrder = MSWToNativeOrder(m_colIndices.Index(idx));
349
350 if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM,
351 MSWToNativeIdx(idx), (LPARAM)&hdi) == -1 )
352 {
353 wxLogLastError(wxT("Header_InsertItem()"));
354 }
355 }
356
357 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt& order)
358 {
359 wxArrayInt orderShown;
360 orderShown.reserve(m_numColumns);
361
362 for ( unsigned n = 0; n < m_numColumns; n++ )
363 {
364 const int idx = order[n];
365 if ( GetColumn(idx).IsShown() )
366 orderShown.push_back(MSWToNativeIdx(idx));
367 }
368
369 if ( !Header_SetOrderArray(GetHwnd(), orderShown.size(), &orderShown[0]) )
370 {
371 wxLogLastError(wxT("Header_GetOrderArray"));
372 }
373
374 m_colIndices = order;
375 }
376
377 wxArrayInt wxHeaderCtrl::DoGetColumnsOrder() const
378 {
379 // we don't use Header_GetOrderArray() here because it doesn't return
380 // information about the hidden columns, instead we just save the columns
381 // order array in DoSetColumnsOrder() and update it when they're reordered
382 return m_colIndices;
383 }
384
385 // ----------------------------------------------------------------------------
386 // wxHeaderCtrl indexes and positions translation
387 // ----------------------------------------------------------------------------
388
389 int wxHeaderCtrl::MSWToNativeIdx(int idx)
390 {
391 // don't check for GetColumn(idx).IsShown() as it could have just became
392 // false and we may be called from DoUpdate() to delete the old column
393 wxASSERT_MSG( !m_isHidden[idx],
394 "column must be visible to have an "
395 "index in the native control" );
396
397 int item = idx;
398 for ( int i = 0; i < idx; i++ )
399 {
400 if ( GetColumn(i).IsHidden() )
401 item--; // one less column the native control knows about
402 }
403
404 wxASSERT_MSG( item >= 0 && item <= GetShownColumnsCount(), "logic error" );
405
406 return item;
407 }
408
409 int wxHeaderCtrl::MSWFromNativeIdx(int item)
410 {
411 wxASSERT_MSG( item >= 0 && item < GetShownColumnsCount(),
412 "column index out of range" );
413
414 // reverse the above function
415
416 unsigned idx = item;
417 for ( unsigned n = 0; n < m_numColumns; n++ )
418 {
419 if ( n > idx )
420 break;
421
422 if ( GetColumn(n).IsHidden() )
423 idx++;
424 }
425
426 wxASSERT_MSG( MSWToNativeIdx(idx) == item, "logic error" );
427
428 return idx;
429 }
430
431 int wxHeaderCtrl::MSWToNativeOrder(int pos)
432 {
433 wxASSERT_MSG( pos >= 0 && static_cast<unsigned>(pos) < m_numColumns,
434 "column position out of range" );
435
436 int order = pos;
437 for ( int n = 0; n < pos; n++ )
438 {
439 if ( GetColumn(m_colIndices[n]).IsHidden() )
440 order--;
441 }
442
443 wxASSERT_MSG( order >= 0 && order <= GetShownColumnsCount(), "logic error" );
444
445 return order;
446 }
447
448 int wxHeaderCtrl::MSWFromNativeOrder(int order)
449 {
450 wxASSERT_MSG( order >= 0 && order < GetShownColumnsCount(),
451 "native column position out of range" );
452
453 unsigned pos = order;
454 for ( unsigned n = 0; n < m_numColumns; n++ )
455 {
456 if ( n > pos )
457 break;
458
459 if ( GetColumn(m_colIndices[n]).IsHidden() )
460 pos++;
461 }
462
463 wxASSERT_MSG( MSWToNativeOrder(pos) == order, "logic error" );
464
465 return pos;
466 }
467
468 // ----------------------------------------------------------------------------
469 // wxHeaderCtrl events
470 // ----------------------------------------------------------------------------
471
472 wxEventType wxHeaderCtrl::GetClickEventType(bool dblclk, int button)
473 {
474 wxEventType evtType;
475 switch ( button )
476 {
477 case 0:
478 evtType = dblclk ? wxEVT_HEADER_DCLICK
479 : wxEVT_HEADER_CLICK;
480 break;
481
482 case 1:
483 evtType = dblclk ? wxEVT_HEADER_RIGHT_DCLICK
484 : wxEVT_HEADER_RIGHT_CLICK;
485 break;
486
487 case 2:
488 evtType = dblclk ? wxEVT_HEADER_MIDDLE_DCLICK
489 : wxEVT_HEADER_MIDDLE_CLICK;
490 break;
491
492 default:
493 wxFAIL_MSG( wxS("unexpected event type") );
494 evtType = wxEVT_NULL;
495 }
496
497 return evtType;
498 }
499
500 bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
501 {
502 NMHEADER * const nmhdr = (NMHEADER *)lParam;
503
504 wxEventType evtType = wxEVT_NULL;
505 int width = 0;
506 int order = -1;
507 bool veto = false;
508 const UINT code = nmhdr->hdr.code;
509
510 // we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE
511 // so only access for header control events (and yes, the direction of
512 // comparisons with FIRST/LAST is correct even if it seems inverted)
513 int idx = code <= HDN_FIRST && code > HDN_LAST ? nmhdr->iItem : -1;
514 if ( idx != -1 )
515 {
516 // we also get bogus HDN_BEGINDRAG with -1 index so don't call
517 // MSWFromNativeIdx() unconditionally for nmhdr->iItem
518 idx = MSWFromNativeIdx(idx);
519 }
520
521 switch ( code )
522 {
523 // click events
524 // ------------
525
526 case HDN_ITEMCLICK:
527 case HDN_ITEMDBLCLICK:
528 evtType = GetClickEventType(code == HDN_ITEMDBLCLICK, nmhdr->iButton);
529
530 // We're not dragging any more.
531 m_colBeingDragged = -1;
532 break;
533
534 // although we should get the notifications about the right clicks
535 // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
536 // happen in practice on any Windows system up to 2003
537 case NM_RCLICK:
538 case NM_RDBLCLK:
539 {
540 POINT pt;
541 idx = wxMSWGetColumnClicked(&nmhdr->hdr, &pt);
542 if ( idx != wxNOT_FOUND )
543 {
544 idx = MSWFromNativeIdx(idx);
545
546 // due to a bug in mingw32 headers NM_RDBLCLK is signed
547 // there so we need a cast to avoid warnings about signed/
548 // unsigned comparison
549 evtType = GetClickEventType(
550 code == static_cast<UINT>(NM_RDBLCLK), 1);
551 }
552 //else: ignore clicks outside any column
553 }
554 break;
555
556 case HDN_DIVIDERDBLCLICK:
557 evtType = wxEVT_HEADER_SEPARATOR_DCLICK;
558 break;
559
560
561 // column resizing events
562 // ----------------------
563
564 // see comments in wxListCtrl::MSWOnNotify() for why we catch both
565 // ASCII and Unicode versions of this message
566 case HDN_BEGINTRACKA:
567 case HDN_BEGINTRACKW:
568 // non-resizable columns can't be resized no matter what, don't
569 // even generate any events for them
570 if ( !GetColumn(idx).IsResizeable() )
571 {
572 veto = true;
573 break;
574 }
575
576 evtType = wxEVT_HEADER_BEGIN_RESIZE;
577 // fall through
578
579 case HDN_ENDTRACKA:
580 case HDN_ENDTRACKW:
581 width = nmhdr->pitem->cxy;
582
583 if ( evtType == wxEVT_NULL )
584 {
585 evtType = wxEVT_HEADER_END_RESIZE;
586
587 // don't generate events with invalid width
588 const int minWidth = GetColumn(idx).GetMinWidth();
589 if ( width < minWidth )
590 width = minWidth;
591 }
592 break;
593
594 // The control is not supposed to send HDN_TRACK when using
595 // HDS_FULLDRAG (which we do use) but apparently some versions of
596 // comctl32.dll still do it, see #13506, so catch both messages
597 // just in case we are dealing with one of these buggy versions.
598 case HDN_TRACK:
599 case HDN_ITEMCHANGING:
600 if ( nmhdr->pitem && (nmhdr->pitem->mask & HDI_WIDTH) )
601 {
602 // prevent the column from being shrunk beneath its min width
603 width = nmhdr->pitem->cxy;
604 if ( width < GetColumn(idx).GetMinWidth() )
605 {
606 // don't generate any events and prevent the change from
607 // happening
608 veto = true;
609 }
610 else // width is acceptable
611 {
612 // generate the resizing event from here as we don't seem
613 // to be getting HDN_TRACK events at all, at least with
614 // comctl32.dll v6
615 evtType = wxEVT_HEADER_RESIZING;
616 }
617 }
618 break;
619
620
621 // column reordering events
622 // ------------------------
623
624 case HDN_BEGINDRAG:
625 // Windows sometimes sends us events with invalid column indices
626 if ( nmhdr->iItem == -1 )
627 break;
628
629 // If we are dragging a column that is not draggable and the mouse
630 // is moved over a different column then we get the column number from
631 // the column under the mouse. This results in an unexpected behaviour
632 // if this column is draggable. To prevent this remember the column we
633 // are dragging for the complete drag and drop cycle.
634 if ( m_colBeingDragged == -1 )
635 {
636 m_colBeingDragged = idx;
637 }
638
639 // column must have the appropriate flag to be draggable
640 if ( !GetColumn(m_colBeingDragged).IsReorderable() )
641 {
642 veto = true;
643 break;
644 }
645
646 evtType = wxEVT_HEADER_BEGIN_REORDER;
647 break;
648
649 case HDN_ENDDRAG:
650 wxASSERT_MSG( nmhdr->pitem->mask & HDI_ORDER, "should have order" );
651 order = nmhdr->pitem->iOrder;
652
653 // we also get messages with invalid order when column reordering
654 // is cancelled (e.g. by pressing Esc)
655 if ( order == -1 )
656 break;
657
658 order = MSWFromNativeOrder(order);
659
660 evtType = wxEVT_HEADER_END_REORDER;
661
662 // We (successfully) ended dragging the column.
663 m_colBeingDragged = -1;
664 break;
665
666 case NM_RELEASEDCAPTURE:
667 evtType = wxEVT_HEADER_DRAGGING_CANCELLED;
668
669 // Dragging the column was cancelled.
670 m_colBeingDragged = -1;
671 break;
672 }
673
674
675 // do generate the corresponding wx event
676 if ( evtType != wxEVT_NULL )
677 {
678 wxHeaderCtrlEvent event(evtType, GetId());
679 event.SetEventObject(this);
680 event.SetColumn(idx);
681 event.SetWidth(width);
682 if ( order != -1 )
683 event.SetNewOrder(order);
684
685 const bool processed = GetEventHandler()->ProcessEvent(event);
686
687 if ( processed && !event.IsAllowed() )
688 veto = true;
689
690 if ( !veto )
691 {
692 // special post-processing for HDN_ENDDRAG: we need to update the
693 // internal column indices array if this is allowed to go ahead as
694 // the native control is going to reorder its columns now
695 if ( evtType == wxEVT_HEADER_END_REORDER )
696 MoveColumnInOrderArray(m_colIndices, idx, order);
697
698 if ( processed )
699 {
700 // skip default processing below
701 return true;
702 }
703 }
704 }
705
706 if ( veto )
707 {
708 // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
709 // interpret TRUE return value as meaning to stop the control
710 // default handling of the message
711 *result = TRUE;
712
713 return true;
714 }
715
716 return wxHeaderCtrlBase::MSWOnNotify(idCtrl, lParam, result);
717 }
718
719 #endif // wxHAS_GENERIC_HEADERCTRL
720
721 #endif // wxUSE_HEADERCTRL