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