]> git.saurik.com Git - wxWidgets.git/blob - src/msw/headerctrl.cpp
explain EVT_GRID/EVT_GRID_CMD difference and don't duplicate the events documentation...
[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 #ifndef WX_PRECOMP
27 #include "wx/log.h"
28 #endif // WX_PRECOMP
29
30 #include "wx/headerctrl.h"
31
32 #ifndef wxHAS_GENERIC_HEADERCTRL
33
34 #include "wx/imaglist.h"
35
36 #include "wx/msw/wrapcctl.h"
37 #include "wx/msw/private.h"
38
39 // from src/msw/listctrl.cpp
40 extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick);
41
42 // ============================================================================
43 // wxHeaderCtrl implementation
44 // ============================================================================
45
46 // ----------------------------------------------------------------------------
47 // wxHeaderCtrl construction/destruction
48 // ----------------------------------------------------------------------------
49
50 void wxHeaderCtrl::Init()
51 {
52 m_imageList = NULL;
53 m_scrollOffset = 0;
54 }
55
56 bool wxHeaderCtrl::Create(wxWindow *parent,
57 wxWindowID id,
58 const wxPoint& pos,
59 const wxSize& size,
60 long style,
61 const wxString& name)
62 {
63 // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES)
64 // here as we already call InitCommonControls() in wxApp initialization
65 // code which covers this
66
67 if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
68 return false;
69
70 if ( !MSWCreateControl(WC_HEADER, _T(""), pos, size) )
71 return false;
72
73 return true;
74 }
75
76 WXDWORD wxHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
77 {
78 WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle);
79
80 if ( style & wxHD_DRAGDROP )
81 msStyle |= HDS_DRAGDROP;
82
83 // the control looks nicer with these styles and there doesn't seem to be
84 // any reason to not use them so we always do (as for HDS_HORZ it is 0
85 // anyhow but include it for clarity)
86 msStyle |= HDS_HORZ | HDS_BUTTONS | HDS_FLAT | HDS_FULLDRAG | HDS_HOTTRACK;
87
88 return msStyle;
89 }
90
91 wxHeaderCtrl::~wxHeaderCtrl()
92 {
93 delete m_imageList;
94 }
95
96 // ----------------------------------------------------------------------------
97 // wxHeaderCtrl scrolling
98 // ----------------------------------------------------------------------------
99
100 void wxHeaderCtrl::DoSetSize(int x, int y,
101 int w, int h,
102 int sizeFlags)
103 {
104 wxHeaderCtrlBase::DoSetSize(x + m_scrollOffset, y, w - m_scrollOffset, h,
105 sizeFlags);
106 }
107
108 void wxHeaderCtrl::DoScrollHorz(int dx)
109 {
110 // as the native control doesn't support offsetting its contents, we use a
111 // hack here to make it appear correctly when the parent is scrolled:
112 // instead of scrolling or repainting we simply move the control window
113 // itself: to be precise, offset it by the scroll increment to the left and
114 // increment its width to still extend to the right boundary to compensate
115 // for it (notice that dx is negative when scrolling to the right)
116 m_scrollOffset += dx;
117
118 wxHeaderCtrlBase::DoSetSize(GetPosition().x + dx, -1,
119 GetSize().x - dx, -1,
120 wxSIZE_USE_EXISTING);
121 }
122
123 // ----------------------------------------------------------------------------
124 // wxHeaderCtrl geometry calculation
125 // ----------------------------------------------------------------------------
126
127 wxSize wxHeaderCtrl::DoGetBestSize() const
128 {
129 RECT rc = wxGetClientRect(GetHwndOf(GetParent()));
130 WINDOWPOS wpos;
131 HDLAYOUT layout = { &rc, &wpos };
132 if ( !Header_Layout(GetHwnd(), &layout) )
133 {
134 wxLogLastError(_T("Header_Layout"));
135 return wxControl::DoGetBestSize();
136 }
137
138 return wxSize(wpos.cx, wpos.cy);
139 }
140
141 // ----------------------------------------------------------------------------
142 // wxHeaderCtrl columns managements
143 // ----------------------------------------------------------------------------
144
145 unsigned int wxHeaderCtrl::DoGetCount() const
146 {
147 return Header_GetItemCount(GetHwnd());
148 }
149
150 void wxHeaderCtrl::DoSetCount(unsigned int count)
151 {
152 unsigned n;
153
154 // first delete all old columns
155 const unsigned countOld = DoGetCount();
156 for ( n = 0; n < countOld; n++ )
157 {
158 if ( !Header_DeleteItem(GetHwnd(), 0) )
159 {
160 wxLogLastError(_T("Header_DeleteItem"));
161 }
162 }
163
164 // and add the new ones
165 for ( n = 0; n < count; n++ )
166 {
167 DoInsertItem(n, -1 /* default order, i.e. append */);
168 }
169 }
170
171 void wxHeaderCtrl::DoUpdate(unsigned int idx)
172 {
173 // the native control does provide Header_SetItem() but it's inconvenient
174 // to use it because it sends HDN_ITEMCHANGING messages and we'd have to
175 // arrange not to block setting the width from there and the logic would be
176 // more complicated as we'd have to reset the old values as well as setting
177 // the new ones -- so instead just recreate the column
178
179 // we need to preserve the old position ourselves as the column doesn't
180 // store it (TODO: should it?)
181 const unsigned int pos = GetColumnPos(idx);
182 Header_DeleteItem(GetHwnd(), idx);
183 DoInsertItem(idx, pos);
184 }
185
186 void wxHeaderCtrl::DoInsertItem(unsigned int idx, int order)
187 {
188 const wxHeaderColumn& col = GetColumn(idx);
189
190 wxHDITEM hdi;
191
192 // notice that we need to store the string we use the pointer to until we
193 // pass it to the control
194 hdi.mask |= HDI_TEXT;
195 wxWxCharBuffer buf = col.GetTitle().wx_str();
196 hdi.pszText = buf.data();
197 hdi.cchTextMax = wxStrlen(buf);
198
199 const wxBitmap bmp = col.GetBitmap();
200 if ( bmp.IsOk() )
201 {
202 hdi.mask |= HDI_IMAGE;
203
204 if ( bmp.IsOk() )
205 {
206 const int bmpWidth = bmp.GetWidth(),
207 bmpHeight = bmp.GetHeight();
208
209 if ( !m_imageList )
210 {
211 m_imageList = new wxImageList(bmpWidth, bmpHeight);
212 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList));
213 }
214 else // already have an image list
215 {
216 // check that all bitmaps we use have the same size
217 int imageWidth,
218 imageHeight;
219 m_imageList->GetSize(0, imageWidth, imageHeight);
220
221 wxASSERT_MSG( imageWidth == bmpWidth && imageHeight == bmpHeight,
222 "all column bitmaps must have the same size" );
223 }
224
225 m_imageList->Add(bmp);
226 hdi.iImage = m_imageList->GetImageCount() - 1;
227 }
228 else // no bitmap but we still need to update the item
229 {
230 hdi.iImage = I_IMAGENONE;
231 }
232 }
233
234 if ( col.GetAlignment() != wxALIGN_NOT )
235 {
236 hdi.mask |= HDI_FORMAT;
237 switch ( col.GetAlignment() )
238 {
239 case wxALIGN_LEFT:
240 hdi.fmt |= HDF_LEFT;
241 break;
242
243 case wxALIGN_CENTER:
244 case wxALIGN_CENTER_HORIZONTAL:
245 hdi.fmt |= HDF_CENTER;
246 break;
247
248 case wxALIGN_RIGHT:
249 hdi.fmt |= HDF_RIGHT;
250 break;
251
252 default:
253 wxFAIL_MSG( "invalid column header alignment" );
254 }
255 }
256
257 if ( col.IsSortKey() )
258 {
259 hdi.mask |= HDI_FORMAT;
260 hdi.fmt |= col.IsSortOrderAscending() ? HDF_SORTUP : HDF_SORTDOWN;
261 }
262
263 if ( col.GetWidth() != wxCOL_WIDTH_DEFAULT || col.IsHidden() )
264 {
265 hdi.mask |= HDI_WIDTH;
266 hdi.cxy = col.IsHidden() ? 0 : col.GetWidth();
267 }
268
269 if ( order != -1 )
270 {
271 hdi.mask |= HDI_ORDER;
272 hdi.iOrder = order;
273 }
274
275 if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM, idx, (LPARAM)&hdi) == -1 )
276 {
277 wxLogLastError(_T("Header_InsertItem()"));
278 }
279 }
280
281 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt& order)
282 {
283 if ( !Header_SetOrderArray(GetHwnd(), order.size(), &order[0]) )
284 {
285 wxLogLastError(_T("Header_GetOrderArray"));
286 }
287 }
288
289 wxArrayInt wxHeaderCtrl::DoGetColumnsOrder() const
290 {
291 const unsigned count = GetColumnCount();
292 wxArrayInt order(count);
293 if ( !Header_GetOrderArray(GetHwnd(), count, &order[0]) )
294 {
295 wxLogLastError(_T("Header_GetOrderArray"));
296 }
297
298 return order;
299 }
300
301 // ----------------------------------------------------------------------------
302 // wxHeaderCtrl events
303 // ----------------------------------------------------------------------------
304
305 wxEventType wxHeaderCtrl::GetClickEventType(bool dblclk, int button)
306 {
307 wxEventType evtType;
308 switch ( button )
309 {
310 case 0:
311 evtType = dblclk ? wxEVT_COMMAND_HEADER_DCLICK
312 : wxEVT_COMMAND_HEADER_CLICK;
313 break;
314
315 case 1:
316 evtType = dblclk ? wxEVT_COMMAND_HEADER_RIGHT_DCLICK
317 : wxEVT_COMMAND_HEADER_RIGHT_CLICK;
318 break;
319
320 case 2:
321 evtType = dblclk ? wxEVT_COMMAND_HEADER_MIDDLE_DCLICK
322 : wxEVT_COMMAND_HEADER_MIDDLE_CLICK;
323 break;
324
325 default:
326 wxFAIL_MSG( wxS("unexpected event type") );
327 evtType = wxEVT_NULL;
328 }
329
330 return evtType;
331 }
332
333 bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
334 {
335 NMHEADER * const nmhdr = (NMHEADER *)lParam;
336
337 wxEventType evtType = wxEVT_NULL;
338 int idx = nmhdr->iItem;
339 int width = 0;
340 int order = -1;
341 bool veto = false;
342 const UINT code = nmhdr->hdr.code;
343 switch ( code )
344 {
345 // click events
346 // ------------
347
348 case HDN_ITEMCLICK:
349 case HDN_ITEMDBLCLICK:
350 evtType = GetClickEventType(code == HDN_ITEMDBLCLICK, nmhdr->iButton);
351 break;
352
353 // although we should get the notifications about the right clicks
354 // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
355 // happen in practice on any Windows system up to 2003
356 case NM_RCLICK:
357 case NM_RDBLCLK:
358 {
359 POINT pt;
360 idx = wxMSWGetColumnClicked(&nmhdr->hdr, &pt);
361 if ( idx != wxNOT_FOUND )
362 evtType = GetClickEventType(code == NM_RDBLCLK, 1);
363 //else: ignore clicks outside any column
364 }
365 break;
366
367 case HDN_DIVIDERDBLCLICK:
368 evtType = wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK;
369 break;
370
371
372 // column resizing events
373 // ----------------------
374
375 // see comments in wxListCtrl::MSWOnNotify() for why we catch both
376 // ASCII and Unicode versions of this message
377 case HDN_BEGINTRACKA:
378 case HDN_BEGINTRACKW:
379 // non-resizeable columns can't be resized no matter what, don't
380 // even generate any events for them
381 if ( !GetColumn(idx).IsResizeable() )
382 {
383 veto = true;
384 break;
385 }
386
387 evtType = wxEVT_COMMAND_HEADER_BEGIN_RESIZE;
388 // fall through
389
390 case HDN_ENDTRACKA:
391 case HDN_ENDTRACKW:
392 width = nmhdr->pitem->cxy;
393
394 if ( evtType == wxEVT_NULL )
395 {
396 evtType = wxEVT_COMMAND_HEADER_END_RESIZE;
397
398 // don't generate events with invalid width
399 const int minWidth = GetColumn(idx).GetMinWidth();
400 if ( width < minWidth )
401 width = minWidth;
402 }
403 break;
404
405 case HDN_ITEMCHANGING:
406 if ( nmhdr->pitem && (nmhdr->pitem->mask & HDI_WIDTH) )
407 {
408 // prevent the column from being shrunk beneath its min width
409 width = nmhdr->pitem->cxy;
410 if ( width < GetColumn(idx).GetMinWidth() )
411 {
412 // don't generate any events and prevent the change from
413 // happening
414 veto = true;
415 }
416 else // width is acceptable
417 {
418 // generate the resizing event from here as we don't seem
419 // to be getting HDN_TRACK events at all, at least with
420 // comctl32.dll v6
421 evtType = wxEVT_COMMAND_HEADER_RESIZING;
422 }
423 }
424 break;
425
426
427 // column reordering events
428 // ------------------------
429
430 case HDN_BEGINDRAG:
431 // Windows sometimes sends us events with invalid column indices
432 if ( idx == -1 )
433 break;
434
435 // column must have the appropriate flag to be draggable
436 if ( !GetColumn(idx).IsReorderable() )
437 {
438 veto = true;
439 break;
440 }
441
442 evtType = wxEVT_COMMAND_HEADER_BEGIN_REORDER;
443 break;
444
445 case HDN_ENDDRAG:
446 evtType = wxEVT_COMMAND_HEADER_END_REORDER;
447
448 wxASSERT_MSG( nmhdr->pitem->mask & HDI_ORDER, "should have order" );
449 order = nmhdr->pitem->iOrder;
450 break;
451
452 case NM_RELEASEDCAPTURE:
453 evtType = wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED;
454 break;
455 }
456
457
458 // do generate the corresponding wx event
459 if ( evtType != wxEVT_NULL )
460 {
461 wxHeaderCtrlEvent event(evtType, GetId());
462 event.SetEventObject(this);
463 event.SetColumn(idx);
464 event.SetWidth(width);
465 if ( order != -1 )
466 event.SetNewOrder(order);
467
468 if ( GetEventHandler()->ProcessEvent(event) )
469 {
470 if ( event.IsAllowed() )
471 return true;
472
473 // we need to veto the default handling of this message, don't
474 // return to execute the code in the "if veto" branch below
475 veto = true;
476 }
477 }
478
479 if ( veto )
480 {
481 // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
482 // interpret TRUE return value as meaning to stop the control
483 // default handling of the message
484 *result = TRUE;
485
486 return true;
487 }
488
489 return wxHeaderCtrlBase::MSWOnNotify(idCtrl, lParam, result);
490 }
491
492 #endif // wxHAS_GENERIC_HEADERCTRL