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