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