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