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