1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/headerctrl.cpp
3 // Purpose: implementation of wxHeaderCtrl for wxMSW
4 // Author: Vadim Zeitlin
5 // Created: 2008-12-01
6 // Copyright: (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
7 // Licence: wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17
18 // for compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21
22 #if wxUSE_HEADERCTRL
23
24 #ifndef WX_PRECOMP
25 #include "wx/app.h"
26 #include "wx/log.h"
27 #endif // WX_PRECOMP
28
29 #include "wx/headerctrl.h"
30
31 #ifndef wxHAS_GENERIC_HEADERCTRL
32
33 #include "wx/imaglist.h"
34
35 #include "wx/msw/wrapcctl.h"
36 #include "wx/msw/private.h"
37 #include "wx/msw/private/customdraw.h"
38 #include "wx/msw/private/winstyle.h"
39
40 #ifndef HDM_SETBITMAPMARGIN
41 #define HDM_SETBITMAPMARGIN 0x1234
42 #endif
43
44 #ifndef Header_SetBitmapMargin
45 #define Header_SetBitmapMargin(hwnd, margin) \
46 ::SendMessage((hwnd), HDM_SETBITMAPMARGIN, (WPARAM)(margin), 0)
47 #endif
48
49 // from src/msw/listctrl.cpp
50 extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick);
51
52 // ----------------------------------------------------------------------------
53 // wxMSWHeaderCtrlCustomDraw: our custom draw helper
54 // ----------------------------------------------------------------------------
55
56 class wxMSWHeaderCtrlCustomDraw : public wxMSWImpl::CustomDraw
57 {
58 public:
wxMSWHeaderCtrlCustomDraw()59 wxMSWHeaderCtrlCustomDraw()
60 {
61 }
62
63 // Make this field public to let wxHeaderCtrl update it directly when its
64 // attributes change.
65 wxItemAttr m_attr;
66
67 private:
HasCustomDrawnItems() const68 virtual bool HasCustomDrawnItems() const wxOVERRIDE
69 {
70 // We only exist if the header does need to be custom drawn.
71 return true;
72 }
73
74 virtual const wxItemAttr*
GetItemAttr(DWORD_PTR WXUNUSED (dwItemSpec)) const75 GetItemAttr(DWORD_PTR WXUNUSED(dwItemSpec)) const wxOVERRIDE
76 {
77 // We use the same attribute for all items for now.
78 return &m_attr;
79 }
80 };
81
82 // ----------------------------------------------------------------------------
83 // wxMSWHeaderCtrl: the native header control
84 // ----------------------------------------------------------------------------
85 class wxMSWHeaderCtrl : public wxControl
86 {
87 public:
wxMSWHeaderCtrl(wxHeaderCtrl & header)88 explicit wxMSWHeaderCtrl(wxHeaderCtrl& header) :
89 m_header(header)
90 {
91 Init();
92 }
93
94 bool Create(wxWindow *parent,
95 wxWindowID id,
96 const wxPoint& pos,
97 const wxSize& size,
98 long style,
99 const wxString& name);
100
101 virtual ~wxMSWHeaderCtrl();
102
103 // Override to implement colours support via custom drawing.
104 virtual bool SetBackgroundColour(const wxColour& colour) wxOVERRIDE;
105 virtual bool SetForegroundColour(const wxColour& colour) wxOVERRIDE;
106 virtual bool SetFont(const wxFont& font) wxOVERRIDE;
107
108 // The implementation of wxHeaderCtrlBase virtual functions
109 void SetCount(unsigned int count);
110 unsigned int GetCount() const;
111 void UpdateHeader(unsigned int idx);
112
113 void ScrollHorz(int dx);
114
115 void SetColumnsOrder(const wxArrayInt& order);
116 wxArrayInt GetColumnsOrder() const;
117
118 protected:
119 // override wxWindow methods which must be implemented by a new control
120 virtual wxSize DoGetBestSize() const wxOVERRIDE;
121 virtual void DoSetSize(int x, int y,
122 int width, int height,
123 int sizeFlags = wxSIZE_AUTO) wxOVERRIDE;
124 virtual void MSWUpdateFontOnDPIChange(const wxSize& newDPI) wxOVERRIDE;
125
126 private:
127 // override MSW-specific methods needed for new control
128 virtual WXDWORD MSWGetStyle(long style, WXDWORD *exstyle) const wxOVERRIDE;
129 virtual bool MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) wxOVERRIDE;
130
131 // common part of all ctors
132 void Init();
133
134 // wrapper around Header_InsertItem(): insert the item using information
135 // from the given column at the given index
136 void DoInsertItem(const wxHeaderColumn& col, unsigned int idx);
137
138 // get the number of currently visible items: this is also the total number
139 // of items contained in the native control
140 int GetShownColumnsCount() const;
141
142 // due to the discrepancy for the hidden columns which we know about but
143 // the native control does not, there can be a difference between the
144 // column indices we use and the ones used by the native control; these
145 // functions translate between them
146 //
147 // notice that MSWToNativeIdx() shouldn't be called for hidden columns and
148 // MSWFromNativeIdx() always returns an index of a visible column
149 int MSWToNativeIdx(int idx);
150 int MSWFromNativeIdx(int item);
151
152 // this is the same as above but for order, not index
153 int MSWToNativeOrder(int order);
154 int MSWFromNativeOrder(int order);
155
156 // get the event type corresponding to a click or double click event
157 // (depending on dblclk value) with the specified (using MSW convention)
158 // mouse button
159 wxEventType GetClickEventType(bool dblclk, int button);
160
161 // allocate m_customDraw if we need it or free it if it no longer is,
162 // return the pointer which can be used to update it if it's non-null
163 wxMSWHeaderCtrlCustomDraw* GetCustomDraw();
164
165
166 // the real wxHeaderCtrl control
167 wxHeaderCtrl &m_header;
168
169 // the number of columns in the control, including the hidden ones (not
170 // taken into account by the native control, see comment in DoGetCount())
171 unsigned int m_numColumns;
172
173 // this is a lookup table allowing us to check whether the column with the
174 // given index is currently shown in the native control, in which case the
175 // value of this array element with this index is 0, or hidden
176 //
177 // notice that this may be different from GetColumn(idx).IsHidden() and in
178 // fact we need this array precisely because it will be different from it
179 // in DoUpdate() when the column hidden flag gets toggled and we need it to
180 // handle this transition correctly
181 wxArrayInt m_isHidden;
182
183 // the order of our columns: this array contains the index of the column
184 // shown at the position n as the n-th element
185 //
186 // this is necessary only to handle the hidden columns: the native control
187 // doesn't know about them and so we can't use Header_GetOrderArray()
188 wxArrayInt m_colIndices;
189
190 // the image list: initially NULL, created on demand
191 wxImageList *m_imageList;
192
193 // the offset of the window used to emulate scrolling it
194 int m_scrollOffset;
195
196 // actual column we are dragging or -1 if not dragging anything
197 int m_colBeingDragged;
198
199 // a column is currently being resized
200 bool m_isColBeingResized;
201
202 // the custom draw helper: initially NULL, created on demand, use
203 // GetCustomDraw() to do it
204 wxMSWHeaderCtrlCustomDraw *m_customDraw;
205 };
206
207 // ============================================================================
208 // wxMSWHeaderCtrl implementation
209 // ============================================================================
210
211 extern WXDLLIMPEXP_DATA_CORE(const char) wxMSWHeaderCtrlNameStr[] = "wxMSWHeaderCtrl";
212
213 // ----------------------------------------------------------------------------
214 // wxMSWHeaderCtrl construction/destruction
215 // ----------------------------------------------------------------------------
216
Init()217 void wxMSWHeaderCtrl::Init()
218 {
219 m_numColumns = 0;
220 m_imageList = NULL;
221 m_scrollOffset = 0;
222 m_colBeingDragged = -1;
223 m_isColBeingResized = false;
224 m_customDraw = NULL;
225 }
226
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)227 bool wxMSWHeaderCtrl::Create(wxWindow *parent,
228 wxWindowID id,
229 const wxPoint& pos,
230 const wxSize& size,
231 long style,
232 const wxString& name)
233 {
234 // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES)
235 // here as we already call InitCommonControls() in wxApp initialization
236 // code which covers this
237
238 if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
239 return false;
240
241 if ( !MSWCreateControl(WC_HEADER, wxT(""), pos, size) )
242 return false;
243
244 // special hack for margins when using comctl32.dll v6 or later: the
245 // default margin is too big and results in label truncation when the
246 // column width is just about right to show it together with the sort
247 // indicator, so reduce it to a smaller value (in principle we could even
248 // use 0 here but this starts to look ugly)
249 if ( wxApp::GetComCtl32Version() >= 600 )
250 {
251 (void)Header_SetBitmapMargin(GetHwnd(), wxGetSystemMetrics(SM_CXEDGE, parent));
252 }
253
254 return true;
255 }
256
MSWGetStyle(long style,WXDWORD * exstyle) const257 WXDWORD wxMSWHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
258 {
259 WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle);
260
261 if ( style & wxHD_ALLOW_REORDER )
262 msStyle |= HDS_DRAGDROP;
263
264 // the control looks nicer with these styles and there doesn't seem to be
265 // any reason to not use them so we always do (as for HDS_HORZ it is 0
266 // anyhow but include it for clarity)
267 // NOTE: don't use however HDS_FLAT because it makes the control look
268 // non-native when running WinXP in classic mode
269 msStyle |= HDS_HORZ | HDS_BUTTONS | HDS_FULLDRAG | HDS_HOTTRACK;
270
271 return msStyle;
272 }
273
~wxMSWHeaderCtrl()274 wxMSWHeaderCtrl::~wxMSWHeaderCtrl()
275 {
276 delete m_imageList;
277 delete m_customDraw;
278 }
279
280 // ----------------------------------------------------------------------------
281 // wxMSWHeaderCtrl scrolling
282 // ----------------------------------------------------------------------------
283
DoSetSize(int x,int y,int w,int h,int sizeFlags)284 void wxMSWHeaderCtrl::DoSetSize(int x, int y,
285 int w, int h,
286 int sizeFlags)
287 {
288 wxControl::DoSetSize(x + m_scrollOffset, y, w - m_scrollOffset, h,
289 sizeFlags & wxSIZE_FORCE);
290 }
291
ScrollHorz(int dx)292 void wxMSWHeaderCtrl::ScrollHorz(int dx)
293 {
294 // as the native control doesn't support offsetting its contents, we use a
295 // hack here to make it appear correctly when the parent is scrolled:
296 // instead of scrolling or repainting we simply move the control window
297 // itself: to be precise, offset it by the scroll increment to the left and
298 // increment its width to still extend to the right boundary to compensate
299 // for it (notice that dx is negative when scrolling to the right)
300 m_scrollOffset += dx;
301
302 wxControl::DoSetSize(GetPosition().x + dx, -1,
303 GetSize().x - dx, -1,
304 wxSIZE_USE_EXISTING);
305 }
306
307 // ----------------------------------------------------------------------------
308 // wxMSWHeaderCtrl geometry calculation
309 // ----------------------------------------------------------------------------
310
DoGetBestSize() const311 wxSize wxMSWHeaderCtrl::DoGetBestSize() const
312 {
313 RECT rc = wxGetClientRect(GetHwndOf(m_header.GetParent()));
314 WINDOWPOS wpos;
315 HDLAYOUT layout = { &rc, &wpos };
316 if ( !Header_Layout(GetHwnd(), &layout) )
317 {
318 wxLogLastError(wxT("Header_Layout"));
319 return wxControl::DoGetBestSize();
320 }
321
322 return wxSize(wxDefaultCoord, wpos.cy);
323 }
324
MSWUpdateFontOnDPIChange(const wxSize & newDPI)325 void wxMSWHeaderCtrl::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
326 {
327 wxControl::MSWUpdateFontOnDPIChange(newDPI);
328
329 if ( wxMSWHeaderCtrlCustomDraw * customDraw = GetCustomDraw() )
330 {
331 customDraw->m_attr.SetFont(m_font);
332 }
333 }
334
335 // ----------------------------------------------------------------------------
336 // wxMSWHeaderCtrl columns managements
337 // ----------------------------------------------------------------------------
338
GetCount() const339 unsigned int wxMSWHeaderCtrl::GetCount() const
340 {
341 // we can't use Header_GetItemCount() here because it doesn't take the
342 // hidden columns into account and we can't find the hidden columns after
343 // the last shown one in MSWFromNativeIdx() without knowing where to stop
344 // so we have to store the columns count internally
345 return m_numColumns;
346 }
347
GetShownColumnsCount() const348 int wxMSWHeaderCtrl::GetShownColumnsCount() const
349 {
350 const int numItems = Header_GetItemCount(GetHwnd());
351
352 wxASSERT_MSG( numItems >= 0 && (unsigned)numItems <= m_numColumns,
353 "unexpected number of items in the native control" );
354
355 return numItems;
356 }
357
SetCount(unsigned int count)358 void wxMSWHeaderCtrl::SetCount(unsigned int count)
359 {
360 unsigned n;
361
362 // first delete all old columns
363 const unsigned countOld = GetShownColumnsCount();
364 for ( n = 0; n < countOld; n++ )
365 {
366 if ( !Header_DeleteItem(GetHwnd(), 0) )
367 {
368 wxLogLastError(wxT("Header_DeleteItem"));
369 }
370 }
371
372 // update the column indices order array before changing m_numColumns
373 m_header.DoResizeColumnIndices(m_colIndices, count);
374
375 // and add the new ones
376 m_numColumns = count;
377 m_isHidden.resize(m_numColumns);
378 for ( n = 0; n < count; n++ )
379 {
380 const wxHeaderColumn& col = m_header.GetColumn(n);
381 if ( col.IsShown() )
382 {
383 m_isHidden[n] = false;
384
385 DoInsertItem(col, n);
386 }
387 else // hidden initially
388 {
389 m_isHidden[n] = true;
390 }
391 }
392 }
393
UpdateHeader(unsigned int idx)394 void wxMSWHeaderCtrl::UpdateHeader(unsigned int idx)
395 {
396 // the native control does provide Header_SetItem() but it's inconvenient
397 // to use it because it sends HDN_ITEMCHANGING messages and we'd have to
398 // arrange not to block setting the width from there and the logic would be
399 // more complicated as we'd have to reset the old values as well as setting
400 // the new ones -- so instead just recreate the column
401
402 const wxHeaderColumn& col = m_header.GetColumn(idx);
403 if ( col.IsHidden() )
404 {
405 // column is hidden now
406 if ( !m_isHidden[idx] )
407 {
408 // but it wasn't hidden before, so remove it
409 if ( !Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx)) )
410 wxLogLastError(wxS("Header_DeleteItem()"));
411
412 m_isHidden[idx] = true;
413 }
414 //else: nothing to do, updating hidden column doesn't have any effect
415 }
416 else // column is shown now
417 {
418 if ( m_isHidden[idx] )
419 {
420 m_isHidden[idx] = false;
421 }
422 else // and it was shown before as well
423 {
424 // we need to remove the old column
425 if ( !Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx)) )
426 wxLogLastError(wxS("Header_DeleteItem()"));
427 }
428
429 DoInsertItem(col, idx);
430 }
431 }
432
DoInsertItem(const wxHeaderColumn & col,unsigned int idx)433 void wxMSWHeaderCtrl::DoInsertItem(const wxHeaderColumn& col, unsigned int idx)
434 {
435 wxASSERT_MSG( !col.IsHidden(), "should only be called for shown columns" );
436
437 wxHDITEM hdi;
438
439 // notice that we need to store the string we use the pointer to until we
440 // pass it to the control
441 hdi.mask |= HDI_TEXT;
442 wxWxCharBuffer buf = col.GetTitle().t_str();
443 hdi.pszText = buf.data();
444 hdi.cchTextMax = wxStrlen(buf);
445
446 const wxBitmap bmp = col.GetBitmap();
447 if ( bmp.IsOk() )
448 {
449 hdi.mask |= HDI_IMAGE;
450
451 if ( HasFlag(wxHD_BITMAP_ON_RIGHT) )
452 hdi.fmt |= HDF_BITMAP_ON_RIGHT;
453
454 const int bmpWidth = bmp.GetWidth(),
455 bmpHeight = bmp.GetHeight();
456
457 if ( !m_imageList )
458 {
459 m_imageList = new wxImageList(bmpWidth, bmpHeight);
460 (void) // suppress mingw32 warning about unused computed value
461 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList));
462 }
463 else // already have an image list
464 {
465 // check that all bitmaps we use have the same size
466 int imageWidth,
467 imageHeight;
468 m_imageList->GetSize(0, imageWidth, imageHeight);
469
470 wxASSERT_MSG( imageWidth == bmpWidth && imageHeight == bmpHeight,
471 "all column bitmaps must have the same size" );
472 }
473
474 m_imageList->Add(bmp);
475 hdi.iImage = m_imageList->GetImageCount() - 1;
476 }
477
478 if ( col.GetAlignment() != wxALIGN_NOT )
479 {
480 hdi.mask |= HDI_FORMAT;
481
482 // wxALIGN_LEFT is the same as wxALIGN_NOT
483 switch ( col.GetAlignment() )
484 {
485 case wxALIGN_CENTER:
486 case wxALIGN_CENTER_HORIZONTAL:
487 hdi.fmt |= HDF_CENTER;
488 break;
489
490 case wxALIGN_RIGHT:
491 hdi.fmt |= HDF_RIGHT;
492 break;
493
494 default:
495 wxFAIL_MSG( "invalid column header alignment" );
496 }
497 }
498
499 if ( col.IsSortKey() )
500 {
501 hdi.mask |= HDI_FORMAT;
502 hdi.fmt |= col.IsSortOrderAscending() ? HDF_SORTUP : HDF_SORTDOWN;
503 }
504
505 if ( col.GetWidth() != wxCOL_WIDTH_DEFAULT )
506 {
507 hdi.mask |= HDI_WIDTH;
508 hdi.cxy = col.GetWidth();
509 }
510
511 hdi.mask |= HDI_ORDER;
512 hdi.iOrder = MSWToNativeOrder(m_colIndices.Index(idx));
513
514 if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM,
515 MSWToNativeIdx(idx), (LPARAM)&hdi) == -1 )
516 {
517 wxLogLastError(wxT("Header_InsertItem()"));
518 }
519
520 // Resizing cursor that correctly reflects per-column IsResizable() cannot
521 // be implemented, it is per-control rather than per-column in the native
522 // control. Enable resizing cursor if at least one column is resizeble.
523 bool hasResizableColumns = false;
524 for ( unsigned n = 0; n < m_header.GetColumnCount(); n++ )
525 {
526 const wxHeaderColumn& c = m_header.GetColumn(n);
527 if (c.IsShown() && c.IsResizeable())
528 {
529 hasResizableColumns = true;
530 break;
531 }
532 }
533
534 wxMSWWinStyleUpdater(GetHwnd())
535 .TurnOnOrOff(!hasResizableColumns, HDS_NOSIZING);
536 }
537
SetColumnsOrder(const wxArrayInt & order)538 void wxMSWHeaderCtrl::SetColumnsOrder(const wxArrayInt& order)
539 {
540 // This can happen if we don't have any columns at all and "order" is empty
541 // anyhow in this case, so we don't have anything to do (note that we
542 // already know that the input array contains m_numColumns elements, as
543 // it's checked by the public SetColumnsOrder()).
544 if ( !m_numColumns )
545 return;
546
547 wxArrayInt orderShown;
548 orderShown.reserve(m_numColumns);
549
550 for ( unsigned n = 0; n < m_numColumns; n++ )
551 {
552 const int idx = order[n];
553 if ( m_header.GetColumn(idx).IsShown() )
554 orderShown.push_back(MSWToNativeIdx(idx));
555 }
556
557 if ( !Header_SetOrderArray(GetHwnd(), orderShown.size(), &orderShown[0]) )
558 {
559 wxLogLastError(wxT("Header_GetOrderArray"));
560 }
561
562 m_colIndices = order;
563 }
564
GetColumnsOrder() const565 wxArrayInt wxMSWHeaderCtrl::GetColumnsOrder() const
566 {
567 // we don't use Header_GetOrderArray() here because it doesn't return
568 // information about the hidden columns, instead we just save the columns
569 // order array in DoSetColumnsOrder() and update it when they're reordered
570 return m_colIndices;
571 }
572
573 // ----------------------------------------------------------------------------
574 // wxMSWHeaderCtrl indexes and positions translation
575 // ----------------------------------------------------------------------------
576
MSWToNativeIdx(int idx)577 int wxMSWHeaderCtrl::MSWToNativeIdx(int idx)
578 {
579 // don't check for GetColumn(idx).IsShown() as it could have just became
580 // false and we may be called from DoUpdate() to delete the old column
581 wxASSERT_MSG( !m_isHidden[idx],
582 "column must be visible to have an "
583 "index in the native control" );
584
585 int item = idx;
586 for ( int i = 0; i < idx; i++ )
587 {
588 if ( m_header.GetColumn(i).IsHidden() )
589 item--; // one less column the native control knows about
590 }
591
592 wxASSERT_MSG( item >= 0 && item <= GetShownColumnsCount(), "logic error" );
593
594 return item;
595 }
596
MSWFromNativeIdx(int item)597 int wxMSWHeaderCtrl::MSWFromNativeIdx(int item)
598 {
599 wxASSERT_MSG( item >= 0 && item < GetShownColumnsCount(),
600 "column index out of range" );
601
602 // reverse the above function
603
604 unsigned idx = item;
605 for ( unsigned n = 0; n < m_numColumns; n++ )
606 {
607 if ( n > idx )
608 break;
609
610 if ( m_header.GetColumn(n).IsHidden() )
611 idx++;
612 }
613
614 wxASSERT_MSG( MSWToNativeIdx(idx) == item, "logic error" );
615
616 return idx;
617 }
618
MSWToNativeOrder(int pos)619 int wxMSWHeaderCtrl::MSWToNativeOrder(int pos)
620 {
621 wxASSERT_MSG( pos >= 0 && static_cast<unsigned>(pos) < m_numColumns,
622 "column position out of range" );
623
624 int order = pos;
625 for ( int n = 0; n < pos; n++ )
626 {
627 if ( m_header.GetColumn(m_colIndices[n]).IsHidden() )
628 order--;
629 }
630
631 wxASSERT_MSG( order >= 0 && order <= GetShownColumnsCount(), "logic error" );
632
633 return order;
634 }
635
MSWFromNativeOrder(int order)636 int wxMSWHeaderCtrl::MSWFromNativeOrder(int order)
637 {
638 wxASSERT_MSG( order >= 0 && order < GetShownColumnsCount(),
639 "native column position out of range" );
640
641 unsigned pos = order;
642 for ( unsigned n = 0; n < m_numColumns; n++ )
643 {
644 if ( n > pos )
645 break;
646
647 if ( m_header.GetColumn(m_colIndices[n]).IsHidden() )
648 pos++;
649 }
650
651 wxASSERT_MSG( MSWToNativeOrder(pos) == order, "logic error" );
652
653 return pos;
654 }
655
656 // ----------------------------------------------------------------------------
657 // wxMSWHeaderCtrl appearance
658 // ----------------------------------------------------------------------------
659
GetCustomDraw()660 wxMSWHeaderCtrlCustomDraw* wxMSWHeaderCtrl::GetCustomDraw()
661 {
662 // There is no need to make the control custom drawn just because it has a
663 // custom font, the native control handles the font just fine on its own,
664 // so if our custom colours were reset, don't bother with custom drawing
665 // any longer.
666 if ( !m_hasBgCol && !m_hasFgCol )
667 {
668 if ( m_customDraw )
669 {
670 delete m_customDraw;
671 m_customDraw = NULL;
672 }
673
674 return NULL;
675 }
676
677 // We do have at least one custom colour, so enable custom drawing.
678 if ( !m_customDraw )
679 m_customDraw = new wxMSWHeaderCtrlCustomDraw();
680
681 return m_customDraw;
682 }
683
SetBackgroundColour(const wxColour & colour)684 bool wxMSWHeaderCtrl::SetBackgroundColour(const wxColour& colour)
685 {
686 if ( !wxControl::SetBackgroundColour(colour) )
687 return false;
688
689 if ( wxMSWHeaderCtrlCustomDraw* customDraw = GetCustomDraw() )
690 {
691 customDraw->m_attr.SetBackgroundColour(colour);
692 }
693
694 return true;
695 }
696
SetForegroundColour(const wxColour & colour)697 bool wxMSWHeaderCtrl::SetForegroundColour(const wxColour& colour)
698 {
699 if ( !wxControl::SetForegroundColour(colour) )
700 return false;
701
702 if ( wxMSWHeaderCtrlCustomDraw* customDraw = GetCustomDraw() )
703 {
704 customDraw->m_attr.SetTextColour(colour);
705 }
706
707 return true;
708 }
709
SetFont(const wxFont & font)710 bool wxMSWHeaderCtrl::SetFont(const wxFont& font)
711 {
712 if ( !wxControl::SetFont(font) )
713 return false;
714
715 if ( wxMSWHeaderCtrlCustomDraw* customDraw = GetCustomDraw() )
716 {
717 customDraw->m_attr.SetFont(m_font);
718 }
719
720 return true;
721 }
722
723 // ----------------------------------------------------------------------------
724 // wxMSWHeaderCtrl events
725 // ----------------------------------------------------------------------------
726
GetClickEventType(bool dblclk,int button)727 wxEventType wxMSWHeaderCtrl::GetClickEventType(bool dblclk, int button)
728 {
729 wxEventType evtType;
730 switch ( button )
731 {
732 case 0:
733 evtType = dblclk ? wxEVT_HEADER_DCLICK
734 : wxEVT_HEADER_CLICK;
735 break;
736
737 case 1:
738 evtType = dblclk ? wxEVT_HEADER_RIGHT_DCLICK
739 : wxEVT_HEADER_RIGHT_CLICK;
740 break;
741
742 case 2:
743 evtType = dblclk ? wxEVT_HEADER_MIDDLE_DCLICK
744 : wxEVT_HEADER_MIDDLE_CLICK;
745 break;
746
747 default:
748 wxFAIL_MSG( wxS("unexpected event type") );
749 evtType = wxEVT_NULL;
750 }
751
752 return evtType;
753 }
754
MSWOnNotify(int idCtrl,WXLPARAM lParam,WXLPARAM * result)755 bool wxMSWHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
756 {
757 NMHEADER * const nmhdr = (NMHEADER *)lParam;
758
759 wxEventType evtType = wxEVT_NULL;
760 int width = 0;
761 int order = -1;
762 bool veto = false;
763 const UINT code = nmhdr->hdr.code;
764
765 // we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE
766 // so only access for header control events (and yes, the direction of
767 // comparisons with FIRST/LAST is correct even if it seems inverted)
768 int idx = code <= HDN_FIRST && code > HDN_LAST ? nmhdr->iItem : -1;
769 if ( idx != -1 )
770 {
771 // we also get bogus HDN_BEGINDRAG with -1 index so don't call
772 // MSWFromNativeIdx() unconditionally for nmhdr->iItem
773 idx = MSWFromNativeIdx(idx);
774 }
775
776 switch ( code )
777 {
778 // click events
779 // ------------
780
781 case HDN_ITEMCLICK:
782 case HDN_ITEMDBLCLICK:
783 evtType = GetClickEventType(code == HDN_ITEMDBLCLICK, nmhdr->iButton);
784
785 // We're not dragging any more.
786 m_colBeingDragged = -1;
787 break;
788
789 // although we should get the notifications about the right clicks
790 // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
791 // happen in practice on any Windows system up to 2003
792 case NM_RCLICK:
793 case NM_RDBLCLK:
794 {
795 POINT pt;
796 idx = wxMSWGetColumnClicked(&nmhdr->hdr, &pt);
797 if ( idx != wxNOT_FOUND )
798 {
799 idx = MSWFromNativeIdx(idx);
800
801 // due to a bug in mingw32 headers NM_RDBLCLK is signed
802 // there so we need a cast to avoid warnings about signed/
803 // unsigned comparison
804 evtType = GetClickEventType(
805 code == static_cast<UINT>(NM_RDBLCLK), 1);
806 }
807 //else: ignore clicks outside any column
808 }
809 break;
810
811 case HDN_DIVIDERDBLCLICK:
812 evtType = wxEVT_HEADER_SEPARATOR_DCLICK;
813 break;
814
815
816 // column resizing events
817 // ----------------------
818
819 // see comments in wxListCtrl::MSWOnNotify() for why we catch both
820 // ASCII and Unicode versions of this message
821 case HDN_BEGINTRACKA:
822 case HDN_BEGINTRACKW:
823 // non-resizable columns can't be resized no matter what, don't
824 // even generate any events for them
825 if ( !m_header.GetColumn(idx).IsResizeable() )
826 {
827 veto = true;
828 break;
829 }
830
831 m_isColBeingResized = true;
832 evtType = wxEVT_HEADER_BEGIN_RESIZE;
833 wxFALLTHROUGH;
834
835 case HDN_ENDTRACKA:
836 case HDN_ENDTRACKW:
837 width = nmhdr->pitem->cxy;
838
839 if ( evtType == wxEVT_NULL )
840 {
841 evtType = wxEVT_HEADER_END_RESIZE;
842
843 // don't generate events with invalid width
844 const int minWidth = m_header.GetColumn(idx).GetMinWidth();
845 if ( width < minWidth )
846 width = minWidth;
847
848 m_isColBeingResized = false;
849 }
850 break;
851
852 // The control is not supposed to send HDN_TRACK when using
853 // HDS_FULLDRAG (which we do use) but apparently some versions of
854 // comctl32.dll still do it, see #13506, so catch both messages
855 // just in case we are dealing with one of these buggy versions.
856 case HDN_TRACK:
857 case HDN_ITEMCHANGING:
858 // With "Show window contents while dragging" option enabled
859 // the sequence of notifications is as follows:
860 // HDN_BEGINTRACK
861 // HDN_ITEMCHANGING
862 // HDN_ITEMCHANGED
863 // ...
864 // HDN_ITEMCHANGING
865 // HDN_ITEMCHANGED
866 // HDN_ENDTRACK
867 // HDN_ITEMCHANGING
868 // HDN_ITEMCHANGED
869 // With "Show window contents while dragging" option disabled
870 // the sequence looks in turn like this:
871 // HDN_BEGINTRACK
872 // HDN_ITEMTRACK
873 // HDN_ITEMCHANGING
874 // ...
875 // HDN_ITEMTRACK
876 // HDN_ITEMCHANGING
877 // HDN_ENDTRACK
878 // HDN_ITEMCHANGING
879 // HDN_ITEMCHANGED
880 // In both cases last HDN_ITEMCHANGING notification is sent
881 // after HDN_ENDTRACK so we have to skip it.
882 if ( nmhdr->pitem && (nmhdr->pitem->mask & HDI_WIDTH) )
883 {
884 // prevent the column from being shrunk beneath its min width
885 width = nmhdr->pitem->cxy;
886 if ( width < m_header.GetColumn(idx).GetMinWidth() )
887 {
888 // don't generate any events and prevent the change from
889 // happening
890 veto = true;
891 }
892 // width is acceptable and notification arrived before HDN_ENDTRACK
893 else if ( m_isColBeingResized )
894 {
895 // generate the resizing event from here as we don't seem
896 // to be getting HDN_TRACK events at all, at least with
897 // comctl32.dll v6
898 evtType = wxEVT_HEADER_RESIZING;
899 }
900 // else
901 // Notification arriving after HDN_ENDTRACK is handled normally
902 // by the control but EVT_HEADER_RESIZING event cannot be generated
903 // because EVT_HEADER_END_RESIZE finalizing the resizing has been
904 // already emitted.
905 }
906 break;
907
908
909 // column reordering events
910 // ------------------------
911
912 case HDN_BEGINDRAG:
913 // Windows sometimes sends us events with invalid column indices
914 if ( nmhdr->iItem == -1 )
915 break;
916
917 // If we are dragging a column that is not draggable and the mouse
918 // is moved over a different column then we get the column number from
919 // the column under the mouse. This results in an unexpected behaviour
920 // if this column is draggable. To prevent this remember the column we
921 // are dragging for the complete drag and drop cycle.
922 if ( m_colBeingDragged == -1 )
923 {
924 m_colBeingDragged = idx;
925 }
926
927 // column must have the appropriate flag to be draggable
928 if ( !m_header.GetColumn(m_colBeingDragged).IsReorderable() )
929 {
930 veto = true;
931 break;
932 }
933
934 evtType = wxEVT_HEADER_BEGIN_REORDER;
935 break;
936
937 case HDN_ENDDRAG:
938 wxASSERT_MSG( nmhdr->pitem->mask & HDI_ORDER, "should have order" );
939 order = nmhdr->pitem->iOrder;
940
941 // we also get messages with invalid order when column reordering
942 // is cancelled (e.g. by pressing Esc)
943 if ( order == -1 )
944 break;
945
946 order = MSWFromNativeOrder(order);
947
948 evtType = wxEVT_HEADER_END_REORDER;
949
950 // We (successfully) ended dragging the column.
951 m_colBeingDragged = -1;
952 break;
953
954 case NM_RELEASEDCAPTURE:
955 evtType = wxEVT_HEADER_DRAGGING_CANCELLED;
956
957 // Dragging the column was cancelled.
958 m_colBeingDragged = -1;
959 break;
960
961 // other events
962 // ------------
963
964 case NM_CUSTOMDRAW:
965 if ( m_customDraw )
966 {
967 *result = m_customDraw->HandleCustomDraw(lParam);
968 if ( *result != CDRF_DODEFAULT )
969 return true;
970 }
971 break;
972 }
973
974
975 // do generate the corresponding wx event
976 if ( evtType != wxEVT_NULL )
977 {
978 wxHeaderCtrlEvent event(evtType, GetId());
979 event.SetEventObject(this);
980 event.SetColumn(idx);
981 event.SetWidth(width);
982 if ( order != -1 )
983 event.SetNewOrder(order);
984
985 const bool processed = m_header.GetEventHandler()->ProcessEvent(event);
986
987 if ( processed && !event.IsAllowed() )
988 veto = true;
989
990 if ( !veto )
991 {
992 // special post-processing for HDN_ENDDRAG: we need to update the
993 // internal column indices array if this is allowed to go ahead as
994 // the native control is going to reorder its columns now
995 if ( evtType == wxEVT_HEADER_END_REORDER )
996 m_header.MoveColumnInOrderArray(m_colIndices, idx, order);
997
998 if ( processed )
999 {
1000 // skip default processing below
1001 return true;
1002 }
1003 }
1004 }
1005
1006 if ( veto )
1007 {
1008 // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
1009 // interpret TRUE return value as meaning to stop the control
1010 // default handling of the message
1011 *result = TRUE;
1012
1013 return true;
1014 }
1015
1016 return wxControl::MSWOnNotify(idCtrl, lParam, result);
1017 }
1018
1019 // ============================================================================
1020 // wxHeaderCtrl implementation
1021 // ============================================================================
1022
1023 // ----------------------------------------------------------------------------
1024 // wxHeaderCtrl construction/destruction
1025 // ----------------------------------------------------------------------------
1026
Init()1027 void wxHeaderCtrl::Init()
1028 {
1029 m_nativeControl = NULL;
1030 }
1031
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)1032 bool wxHeaderCtrl::Create(wxWindow *parent,
1033 wxWindowID id,
1034 const wxPoint& pos,
1035 const wxSize& size,
1036 long style,
1037 const wxString& name)
1038 {
1039 long newStyle = style | wxCLIP_CHILDREN | wxTAB_TRAVERSAL;
1040 if ( !wxWindow::Create(parent, id, pos, size, newStyle, name) )
1041 return false;
1042
1043 m_nativeControl = new wxMSWHeaderCtrl(*this);
1044 if ( !m_nativeControl->Create(this,
1045 wxID_ANY,
1046 wxDefaultPosition,
1047 wxDefaultSize,
1048 wxNO_BORDER,
1049 wxMSWHeaderCtrlNameStr) )
1050 return false;
1051
1052 SetWindowStyle(newStyle);
1053
1054 Bind(wxEVT_SIZE, &wxHeaderCtrl::OnSize, this);
1055
1056 return true;
1057 }
1058
1059 // ----------------------------------------------------------------------------
1060 // wxHeaderCtrl events
1061 // ----------------------------------------------------------------------------
1062
OnSize(wxSizeEvent & WXUNUSED (event))1063 void wxHeaderCtrl::OnSize(wxSizeEvent& WXUNUSED(event))
1064 {
1065 if (m_nativeControl != NULL) // check whether initialisation has been done
1066 {
1067 int cw, ch;
1068 GetClientSize(&cw, &ch);
1069
1070 m_nativeControl->SetSize(0, 0, cw, ch);
1071 }
1072 }
1073
1074 // ----------------------------------------------------------------------------
1075 // wxHeaderCtrl scrolling
1076 // ----------------------------------------------------------------------------
1077
DoScrollHorz(int dx)1078 void wxHeaderCtrl::DoScrollHorz(int dx)
1079 {
1080 m_nativeControl->ScrollHorz(dx);
1081 }
1082
1083 // ----------------------------------------------------------------------------
1084 // wxHeaderCtrl geometry calculation
1085 // ----------------------------------------------------------------------------
1086
DoGetBestSize() const1087 wxSize wxHeaderCtrl::DoGetBestSize() const
1088 {
1089 return m_nativeControl->GetBestSize();
1090 }
1091
1092 // ----------------------------------------------------------------------------
1093 // wxHeaderCtrl columns managements
1094 // ----------------------------------------------------------------------------
1095
DoGetCount() const1096 unsigned int wxHeaderCtrl::DoGetCount() const
1097 {
1098 return m_nativeControl->GetCount();
1099 }
1100
DoSetCount(unsigned int count)1101 void wxHeaderCtrl::DoSetCount(unsigned int count)
1102 {
1103 m_nativeControl->SetCount(count);
1104 }
1105
DoUpdate(unsigned int idx)1106 void wxHeaderCtrl::DoUpdate(unsigned int idx)
1107 {
1108 m_nativeControl->UpdateHeader(idx);
1109 }
1110
DoSetColumnsOrder(const wxArrayInt & order)1111 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt& order)
1112 {
1113 m_nativeControl->SetColumnsOrder(order);
1114 }
1115
DoGetColumnsOrder() const1116 wxArrayInt wxHeaderCtrl::DoGetColumnsOrder() const
1117 {
1118 return m_nativeControl->GetColumnsOrder();
1119 }
1120
1121 // ----------------------------------------------------------------------------
1122 // wxHeaderCtrl composite window
1123 // ----------------------------------------------------------------------------
1124
GetCompositeWindowParts() const1125 wxWindowList wxHeaderCtrl::GetCompositeWindowParts() const
1126 {
1127 wxWindowList parts;
1128 parts.push_back(m_nativeControl);
1129 return parts;
1130 }
1131
SetWindowStyleFlag(long style)1132 void wxHeaderCtrl::SetWindowStyleFlag(long style)
1133 {
1134 wxHeaderCtrlBase::SetWindowStyleFlag(style);
1135
1136 // Update the native control style.
1137 long flags = m_nativeControl->GetWindowStyleFlag();
1138
1139 if ( HasFlag(wxHD_ALLOW_REORDER) )
1140 flags |= wxHD_ALLOW_REORDER;
1141 else
1142 flags &= ~wxHD_ALLOW_REORDER;
1143
1144 if ( HasFlag(wxHD_BITMAP_ON_RIGHT) )
1145 flags |= wxHD_BITMAP_ON_RIGHT;
1146 else
1147 flags &= ~wxHD_BITMAP_ON_RIGHT;
1148
1149 m_nativeControl->SetWindowStyleFlag(flags);
1150 }
1151
1152 #endif // wxHAS_GENERIC_HEADERCTRL
1153
1154 #endif // wxUSE_HEADERCTRL
1155