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