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