properly implement Do[GS]etColumnsOrder() in the generic wxHeaderCtrl
[wxWidgets.git] / src / generic / headerctrlg.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/headerctrlg.cpp
3 // Purpose: generic wxHeaderCtrl implementation
4 // Author: Vadim Zeitlin
5 // Created: 2008-12-03
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 #endif // WX_PRECOMP
28
29 #include "wx/headerctrl.h"
30
31 #ifdef wxHAS_GENERIC_HEADERCTRL
32
33 #include "wx/dcbuffer.h"
34 #include "wx/renderer.h"
35
36 // ----------------------------------------------------------------------------
37 // constants
38 // ----------------------------------------------------------------------------
39
40 namespace
41 {
42
43 const unsigned NO_SORT = (unsigned)-1;
44
45 const unsigned COL_NONE = (unsigned)-1;
46
47 } // anonymous namespace
48
49 // ============================================================================
50 // wxHeaderCtrl implementation
51 // ============================================================================
52
53 // ----------------------------------------------------------------------------
54 // wxHeaderCtrl creation
55 // ----------------------------------------------------------------------------
56
57 void wxHeaderCtrl::Init()
58 {
59 m_numColumns = 0;
60 m_hover =
61 m_colBeingResized = COL_NONE;
62 m_scrollOffset = 0;
63 }
64
65 bool wxHeaderCtrl::Create(wxWindow *parent,
66 wxWindowID id,
67 const wxPoint& pos,
68 const wxSize& size,
69 long style,
70 const wxString& name)
71 {
72 if ( !wxHeaderCtrlBase::Create(parent, id, pos, size,
73 style, wxDefaultValidator, name) )
74 return false;
75
76 // tell the system to not paint the background at all to avoid flicker as
77 // we paint the entire window area in our OnPaint()
78 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
79
80 return true;
81 }
82
83 wxHeaderCtrl::~wxHeaderCtrl()
84 {
85 }
86
87 // ----------------------------------------------------------------------------
88 // wxHeaderCtrl columns manipulation
89 // ----------------------------------------------------------------------------
90
91 void wxHeaderCtrl::DoSetCount(unsigned int count)
92 {
93 // update the column indices array if necessary
94 if ( count > m_numColumns )
95 {
96 // all new columns have default positions equal to their indices
97 for ( unsigned n = m_numColumns; n < count; n++ )
98 m_colIndices.push_back(n);
99 }
100 else if ( count < m_numColumns )
101 {
102 // filter out all the positions which are invalid now while keeping the
103 // order of the remaining ones
104 wxArrayInt colIndices;
105 for ( unsigned n = 0; n < m_numColumns; n++ )
106 {
107 const unsigned idx = m_colIndices[n];
108 if ( idx < count )
109 colIndices.push_back(idx);
110 }
111
112 wxASSERT_MSG( colIndices.size() == count, "logic error" );
113
114 m_colIndices = colIndices;
115 }
116
117 m_numColumns = count;
118
119 Refresh();
120 }
121
122 unsigned int wxHeaderCtrl::DoGetCount() const
123 {
124 return m_numColumns;
125 }
126
127 void wxHeaderCtrl::DoUpdate(unsigned int idx)
128 {
129 // we need to refresh not only this column but also the ones after it in
130 // case it was shown or hidden or its width changed -- it would be nice to
131 // avoid doing this unnecessary by storing the old column width (TODO)
132 RefreshColsAfter(idx);
133 }
134
135 // ----------------------------------------------------------------------------
136 // wxHeaderCtrl scrolling
137 // ----------------------------------------------------------------------------
138
139 void wxHeaderCtrl::DoScrollHorz(int dx)
140 {
141 m_scrollOffset += dx;
142
143 // don't call our own version which calls this function!
144 wxControl::ScrollWindow(dx, 0);
145 }
146
147 // ----------------------------------------------------------------------------
148 // wxHeaderCtrl geometry
149 // ----------------------------------------------------------------------------
150
151 wxSize wxHeaderCtrl::DoGetBestSize() const
152 {
153 // the vertical size is rather arbitrary but it looks better if we leave
154 // some space around the text
155 return wxSize(GetColStart(GetColumnCount()), (7*GetCharHeight())/4);
156 }
157
158 int wxHeaderCtrl::GetColStart(unsigned int idx) const
159 {
160 wxHeaderCtrl * const self = const_cast<wxHeaderCtrl *>(this);
161
162 int pos = m_scrollOffset;
163 for ( unsigned n = 0; n < idx; n++ )
164 {
165 const wxHeaderColumnBase& col = self->GetColumn(m_colIndices[n]);
166 if ( col.IsShown() )
167 pos += col.GetWidth();
168 }
169
170 return pos;
171 }
172
173 int wxHeaderCtrl::FindColumnAtPoint(int x, bool& onSeparator) const
174 {
175 wxHeaderCtrl * const self = const_cast<wxHeaderCtrl *>(this);
176
177 int pos = 0;
178 const unsigned count = GetColumnCount();
179 for ( unsigned n = 0; n < count; n++ )
180 {
181 const wxHeaderColumnBase& col = self->GetColumn(m_colIndices[n]);
182 if ( col.IsHidden() )
183 continue;
184
185 pos += col.GetWidth();
186
187 // if the column is resizeable, check if we're approximatively over the
188 // line separating it from the next column
189 //
190 // TODO: don't hardcode sensitivity
191 if ( col.IsResizeable() && abs(x - pos) < 8 )
192 {
193 onSeparator = true;
194 return n;
195 }
196
197 // inside this column?
198 if ( x < pos )
199 {
200 onSeparator = false;
201 return GetColumnAt(n);
202 }
203 }
204
205 return COL_NONE;
206 }
207
208 // ----------------------------------------------------------------------------
209 // wxHeaderCtrl repainting
210 // ----------------------------------------------------------------------------
211
212 void wxHeaderCtrl::RefreshCol(unsigned int idx)
213 {
214 wxRect rect = GetClientRect();
215 rect.x += GetColStart(idx);
216 rect.width = GetColumn(idx).GetWidth();
217
218 RefreshRect(rect);
219 }
220
221 void wxHeaderCtrl::RefreshColIfNotNone(unsigned int idx)
222 {
223 if ( idx != COL_NONE )
224 RefreshCol(idx);
225 }
226
227 void wxHeaderCtrl::RefreshColsAfter(unsigned int idx)
228 {
229 wxRect rect = GetClientRect();
230 const int ofs = GetColStart(idx);
231 rect.x += ofs;
232 rect.width -= ofs;
233
234 RefreshRect(rect);
235 }
236
237 // ----------------------------------------------------------------------------
238 // wxHeaderCtrl dragging
239 // ----------------------------------------------------------------------------
240
241 bool wxHeaderCtrl::IsResizing() const
242 {
243 return m_colBeingResized != COL_NONE;
244 }
245
246 void wxHeaderCtrl::UpdateResizingMarker(int xPhysical)
247 {
248 // unfortunately drawing the marker over the parent window doesn't work as
249 // it's usually covered by another window (the main control view) so just
250 // draw the marker over the header itself, even if it makes it not very
251 // useful
252 wxClientDC dc(this);
253
254 wxDCOverlay dcover(m_overlay, &dc);
255 dcover.Clear();
256
257 if ( xPhysical != -1 )
258 {
259 dc.SetPen(*wxLIGHT_GREY_PEN);
260 dc.DrawLine(xPhysical, 0, xPhysical, GetClientSize().y);
261 }
262 }
263
264 void wxHeaderCtrl::EndDragging()
265 {
266 UpdateResizingMarker(-1);
267
268 m_overlay.Reset();
269
270 // don't use the special dragging cursor any more
271 SetCursor(wxNullCursor);
272 }
273
274 int wxHeaderCtrl::ConstrainByMinWidth(unsigned int col, int& xPhysical)
275 {
276 const int xStart = GetColStart(col);
277
278 // notice that GetMinWidth() returns 0 if there is no minimal width so it
279 // still makes sense to use it even in this case
280 const int xMinEnd = xStart + GetColumn(col).GetMinWidth();
281
282 if ( xPhysical < xMinEnd )
283 xPhysical = xMinEnd;
284
285 return xPhysical - xStart;
286 }
287
288 void wxHeaderCtrl::StartOrContinueResizing(unsigned int col, int xPhysical)
289 {
290 wxHeaderCtrlEvent event(IsResizing() ? wxEVT_COMMAND_HEADER_RESIZING
291 : wxEVT_COMMAND_HEADER_BEGIN_RESIZE,
292 GetId());
293 event.SetEventObject(this);
294 event.SetColumn(col);
295
296 event.SetWidth(ConstrainByMinWidth(col, xPhysical));
297
298 if ( GetEventHandler()->ProcessEvent(event) && !event.IsAllowed() )
299 {
300 if ( IsResizing() )
301 {
302 ReleaseMouse();
303 EndResizing(-1);
304 }
305 //else: nothing to do -- we just don't start to resize
306 }
307 else // go ahead with resizing
308 {
309 if ( !IsResizing() )
310 {
311 m_colBeingResized = col;
312 SetCursor(wxCursor(wxCURSOR_SIZEWE));
313 CaptureMouse();
314 }
315 //else: we had already done the above when we started
316
317 UpdateResizingMarker(xPhysical);
318 }
319 }
320
321 void wxHeaderCtrl::EndResizing(int xPhysical)
322 {
323 wxASSERT_MSG( IsResizing(), "shouldn't be called if we're not resizing" );
324
325 EndDragging();
326
327 const bool cancelled = xPhysical == -1;
328
329 // if dragging was cancelled we must have already lost the mouse capture so
330 // don't try to release it
331 if ( !cancelled )
332 ReleaseMouse();
333
334 wxHeaderCtrlEvent event(cancelled ? wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED
335 : wxEVT_COMMAND_HEADER_END_RESIZE,
336 GetId());
337 event.SetEventObject(this);
338 event.SetColumn(m_colBeingResized);
339 if ( !cancelled )
340 event.SetWidth(ConstrainByMinWidth(m_colBeingResized, xPhysical));
341
342 GetEventHandler()->ProcessEvent(event);
343
344 m_colBeingResized = COL_NONE;
345 }
346
347 // ----------------------------------------------------------------------------
348 // wxHeaderCtrl column reordering
349 // ----------------------------------------------------------------------------
350
351 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt& order)
352 {
353 m_colIndices = order;
354 Refresh();
355 }
356
357 wxArrayInt wxHeaderCtrl::DoGetColumnsOrder() const
358 {
359 return m_colIndices;
360 }
361
362 // ----------------------------------------------------------------------------
363 // wxHeaderCtrl event handlers
364 // ----------------------------------------------------------------------------
365
366 BEGIN_EVENT_TABLE(wxHeaderCtrl, wxHeaderCtrlBase)
367 EVT_PAINT(wxHeaderCtrl::OnPaint)
368
369 EVT_MOUSE_EVENTS(wxHeaderCtrl::OnMouse)
370
371 EVT_MOUSE_CAPTURE_LOST(wxHeaderCtrl::OnCaptureLost)
372
373 EVT_KEY_DOWN(wxHeaderCtrl::OnKeyDown)
374 END_EVENT_TABLE()
375
376 void wxHeaderCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
377 {
378 int w, h;
379 GetClientSize(&w, &h);
380
381 wxAutoBufferedPaintDC dc(this);
382
383 dc.SetBackground(GetBackgroundColour());
384 dc.Clear();
385
386 // account for the horizontal scrollbar offset in the parent window
387 dc.SetDeviceOrigin(m_scrollOffset, 0);
388
389 const unsigned int count = m_numColumns;
390 int xpos = 0;
391 for ( unsigned int i = 0; i < count; i++ )
392 {
393 const wxHeaderColumnBase& col = GetColumn(m_colIndices[i]);
394 if ( col.IsHidden() )
395 continue;
396
397 const int colWidth = col.GetWidth();
398
399 wxHeaderSortIconType sortArrow;
400 if ( col.IsSortKey() )
401 {
402 sortArrow = col.IsSortOrderAscending() ? wxHDR_SORT_ICON_UP
403 : wxHDR_SORT_ICON_DOWN;
404 }
405 else // not sorting by this column
406 {
407 sortArrow = wxHDR_SORT_ICON_NONE;
408 }
409
410 int state = 0;
411 if ( IsEnabled() )
412 {
413 if ( i == m_hover )
414 state = wxCONTROL_CURRENT;
415 }
416 else // disabled
417 {
418 state = wxCONTROL_DISABLED;
419 }
420
421 wxHeaderButtonParams params;
422 params.m_labelText = col.GetTitle();
423 params.m_labelBitmap = col.GetBitmap();
424 params.m_labelAlignment = col.GetAlignment();
425
426 wxRendererNative::Get().DrawHeaderButton
427 (
428 this,
429 dc,
430 wxRect(xpos, 0, colWidth, h),
431 state,
432 sortArrow,
433 &params
434 );
435
436 xpos += colWidth;
437 }
438 }
439
440 void wxHeaderCtrl::OnCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
441 {
442 if ( IsResizing() )
443 EndResizing(-1);
444 }
445
446 void wxHeaderCtrl::OnKeyDown(wxKeyEvent& event)
447 {
448 if ( IsResizing() && event.GetKeyCode() == WXK_ESCAPE )
449 {
450 ReleaseMouse();
451 EndResizing(-1);
452 }
453 else
454 {
455 event.Skip();
456 }
457 }
458
459 void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent)
460 {
461 // do this in advance to allow simply returning if we're not interested,
462 // we'll undo it if we do handle the event below
463 mevent.Skip();
464
465
466 // account for the control displacement
467 const int xPhysical = mevent.GetX();
468 const int xLogical = xPhysical - m_scrollOffset;
469
470 // first deal with the [continuation of any] dragging operations in
471 // progress
472 if ( IsResizing() )
473 {
474 if ( mevent.LeftUp() )
475 EndResizing(xPhysical);
476 else // update the live separator position
477 StartOrContinueResizing(m_colBeingResized, xPhysical);
478
479 return;
480 }
481
482
483 // find if the event is over a column at all
484 bool onSeparator;
485 const unsigned col = mevent.Leaving()
486 ? (onSeparator = false, COL_NONE)
487 : FindColumnAtPoint(xLogical, onSeparator);
488
489
490 // update the highlighted column if it changed
491 if ( col != m_hover )
492 {
493 const unsigned hoverOld = m_hover;
494 m_hover = col;
495
496 RefreshColIfNotNone(hoverOld);
497 RefreshColIfNotNone(m_hover);
498 }
499
500 // update mouse cursor as it moves around
501 if ( mevent.Moving() )
502 {
503 SetCursor(onSeparator ? wxCursor(wxCURSOR_SIZEWE) : wxNullCursor);
504 return;
505 }
506
507 // all the other events only make sense when they happen over a column
508 if ( col == COL_NONE )
509 return;
510
511
512 // enter various dragging modes on left mouse press
513 if ( mevent.LeftDown() )
514 {
515 if ( onSeparator )
516 {
517 // start resizing the column
518 wxASSERT_MSG( !IsResizing(), "reentering resize mode?" );
519 StartOrContinueResizing(col, xPhysical);
520 }
521 else // on column itself
522 {
523 // TODO: drag column
524 ;
525 }
526
527 return;
528 }
529
530 // determine the type of header event corresponding to click events
531 wxEventType evtType = wxEVT_NULL;
532 const bool click = mevent.ButtonUp(),
533 dblclk = mevent.ButtonDClick();
534 if ( click || dblclk )
535 {
536 switch ( mevent.GetButton() )
537 {
538 case wxMOUSE_BTN_LEFT:
539 // treat left double clicks on separator specially
540 if ( onSeparator && dblclk )
541 {
542 evtType = wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK;
543 }
544 else // not double click on separator
545 {
546 evtType = click ? wxEVT_COMMAND_HEADER_CLICK
547 : wxEVT_COMMAND_HEADER_DCLICK;
548 }
549 break;
550
551 case wxMOUSE_BTN_RIGHT:
552 evtType = click ? wxEVT_COMMAND_HEADER_RIGHT_CLICK
553 : wxEVT_COMMAND_HEADER_RIGHT_DCLICK;
554 break;
555
556 case wxMOUSE_BTN_MIDDLE:
557 evtType = click ? wxEVT_COMMAND_HEADER_MIDDLE_CLICK
558 : wxEVT_COMMAND_HEADER_MIDDLE_DCLICK;
559 break;
560
561 default:
562 // ignore clicks from other mouse buttons
563 ;
564 }
565 }
566
567 if ( evtType == wxEVT_NULL )
568 return;
569
570 wxHeaderCtrlEvent event(evtType, GetId());
571 event.SetEventObject(this);
572 event.SetColumn(col);
573
574 if ( GetEventHandler()->ProcessEvent(event) )
575 mevent.Skip(false);
576 }
577
578 #endif // wxHAS_GENERIC_HEADERCTRL