add wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK and semi-automatic header resizing support
[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 = COL_NONE;
61 m_scrollOffset = 0;
62 }
63
64 bool wxHeaderCtrl::Create(wxWindow *parent,
65 wxWindowID id,
66 const wxPoint& pos,
67 const wxSize& size,
68 long style,
69 const wxString& name)
70 {
71 if ( !wxHeaderCtrlBase::Create(parent, id, pos, size,
72 style, wxDefaultValidator, name) )
73 return false;
74
75 // tell the system to not paint the background at all to avoid flicker as
76 // we paint the entire window area in our OnPaint()
77 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
78
79 return true;
80 }
81
82 wxHeaderCtrl::~wxHeaderCtrl()
83 {
84 }
85
86 // ----------------------------------------------------------------------------
87 // wxHeaderCtrl columns manipulation
88 // ----------------------------------------------------------------------------
89
90 void wxHeaderCtrl::DoSetCount(unsigned int count)
91 {
92 m_numColumns = count;
93
94 Refresh();
95 }
96
97 unsigned int wxHeaderCtrl::DoGetCount() const
98 {
99 return m_numColumns;
100 }
101
102 void wxHeaderCtrl::DoUpdate(unsigned int idx)
103 {
104 // we need to refresh not only this column but also the ones after it in
105 // case it was shown or hidden or its width changed -- it would be nice to
106 // avoid doing this unnecessary by storing the old column width (TODO)
107 RefreshColsAfter(idx);
108 }
109
110 // ----------------------------------------------------------------------------
111 // wxHeaderCtrl scrolling
112 // ----------------------------------------------------------------------------
113
114 void wxHeaderCtrl::DoScrollHorz(int dx)
115 {
116 m_scrollOffset += dx;
117
118 // don't call our own version which calls this function!
119 wxControl::ScrollWindow(dx, 0);
120 }
121
122 // ----------------------------------------------------------------------------
123 // wxHeaderCtrl geometry
124 // ----------------------------------------------------------------------------
125
126 wxSize wxHeaderCtrl::DoGetBestSize() const
127 {
128 // the vertical size is rather arbitrary but it looks better if we leave
129 // some space around the text
130 return wxSize(GetColStart(GetColumnCount()), (7*GetCharHeight())/4);
131 }
132
133 int wxHeaderCtrl::GetColStart(unsigned int idx) const
134 {
135 wxHeaderCtrl * const self = const_cast<wxHeaderCtrl *>(this);
136
137 int pos = m_scrollOffset;
138 for ( unsigned n = 0; n < idx; n++ )
139 {
140 const wxHeaderColumnBase& col = self->GetColumn(n);
141 if ( col.IsShown() )
142 pos += col.GetWidth();
143 }
144
145 return pos;
146 }
147
148 int wxHeaderCtrl::FindColumnAtPos(int x, bool& onSeparator) const
149 {
150 wxHeaderCtrl * const self = const_cast<wxHeaderCtrl *>(this);
151
152 int pos = 0;
153 const unsigned count = GetColumnCount();
154 for ( unsigned n = 0; n < count; n++ )
155 {
156 const wxHeaderColumnBase& col = self->GetColumn(n);
157 if ( col.IsHidden() )
158 continue;
159
160 pos += col.GetWidth();
161
162 // if the column is resizeable, check if we're approximatively over the
163 // line separating it from the next column
164 //
165 // TODO: don't hardcode sensitivity
166 if ( col.IsResizeable() && abs(x - pos) < 8 )
167 {
168 onSeparator = true;
169 return n;
170 }
171
172 // inside this column?
173 if ( x < pos )
174 {
175 onSeparator = false;
176 return n;
177 }
178 }
179
180 return COL_NONE;
181 }
182
183 // ----------------------------------------------------------------------------
184 // wxHeaderCtrl repainting
185 // ----------------------------------------------------------------------------
186
187 void wxHeaderCtrl::RefreshCol(unsigned int idx)
188 {
189 wxRect rect = GetClientRect();
190 rect.x += GetColStart(idx);
191 rect.width = GetColumn(idx).GetWidth();
192
193 RefreshRect(rect);
194 }
195
196 void wxHeaderCtrl::RefreshColIfNotNone(unsigned int idx)
197 {
198 if ( idx != COL_NONE )
199 RefreshCol(idx);
200 }
201
202 void wxHeaderCtrl::RefreshColsAfter(unsigned int idx)
203 {
204 wxRect rect = GetClientRect();
205 const int ofs = GetColStart(idx);
206 rect.x += ofs;
207 rect.width -= ofs;
208
209 RefreshRect(rect);
210 }
211
212 // ----------------------------------------------------------------------------
213 // wxHeaderCtrl event handlers
214 // ----------------------------------------------------------------------------
215
216 BEGIN_EVENT_TABLE(wxHeaderCtrl, wxHeaderCtrlBase)
217 EVT_PAINT(wxHeaderCtrl::OnPaint)
218
219 EVT_MOUSE_EVENTS(wxHeaderCtrl::OnMouse)
220 END_EVENT_TABLE()
221
222 void wxHeaderCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
223 {
224 int w, h;
225 GetClientSize(&w, &h);
226
227 wxAutoBufferedPaintDC dc(this);
228
229 dc.SetBackground(GetBackgroundColour());
230 dc.Clear();
231
232 // account for the horizontal scrollbar offset in the parent window
233 dc.SetDeviceOrigin(m_scrollOffset, 0);
234
235 const unsigned int count = m_numColumns;
236 int xpos = 0;
237 for ( unsigned int i = 0; i < count; i++ )
238 {
239 const wxHeaderColumnBase& col = GetColumn(i);
240 if ( col.IsHidden() )
241 continue;
242
243 const int colWidth = col.GetWidth();
244
245 wxHeaderSortIconType sortArrow;
246 if ( col.IsSortKey() )
247 {
248 sortArrow = col.IsSortOrderAscending() ? wxHDR_SORT_ICON_UP
249 : wxHDR_SORT_ICON_DOWN;
250 }
251 else // not sorting by this column
252 {
253 sortArrow = wxHDR_SORT_ICON_NONE;
254 }
255
256 int state = 0;
257 if ( IsEnabled() )
258 {
259 if ( i == m_hover )
260 state = wxCONTROL_CURRENT;
261 }
262 else // disabled
263 {
264 state = wxCONTROL_DISABLED;
265 }
266
267 wxHeaderButtonParams params;
268 params.m_labelText = col.GetTitle();
269 params.m_labelBitmap = col.GetBitmap();
270 params.m_labelAlignment = col.GetAlignment();
271
272 wxRendererNative::Get().DrawHeaderButton
273 (
274 this,
275 dc,
276 wxRect(xpos, 0, colWidth, h),
277 state,
278 sortArrow,
279 &params
280 );
281
282 xpos += colWidth;
283 }
284 }
285
286 void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent)
287 {
288 mevent.Skip();
289
290 // account for the control displacement
291 const int x = mevent.GetX() - m_scrollOffset;
292
293 // find if the event is over a column at all
294 bool onSeparator;
295 const unsigned col = mevent.Leaving()
296 ? (onSeparator = false, COL_NONE)
297 : FindColumnAtPos(x, onSeparator);
298
299 // update the highlighted column if it changed
300 if ( col != m_hover )
301 {
302 const unsigned hoverOld = m_hover;
303 m_hover = col;
304
305 RefreshColIfNotNone(hoverOld);
306 RefreshColIfNotNone(m_hover);
307 }
308
309 if ( col == COL_NONE )
310 return;
311
312 // update mouse cursor as it moves around
313 if ( mevent.Moving() )
314 {
315 SetCursor(onSeparator ? wxCursor(wxCURSOR_SIZEWE) : wxNullCursor);
316 return;
317 }
318
319 if ( mevent.LeftDown() )
320 {
321 // TODO
322 if ( onSeparator )
323 // resize column
324 ;
325 else
326 // drag column
327 ;
328
329 return;
330 }
331
332 // determine the type of header event corresponding to this mouse event
333 wxEventType evtType = wxEVT_NULL;
334 const bool click = mevent.ButtonUp(),
335 dblclk = mevent.ButtonDClick();
336 if ( click || dblclk )
337 {
338 switch ( mevent.GetButton() )
339 {
340 case wxMOUSE_BTN_LEFT:
341 // treat left double clicks on separator specially
342 if ( onSeparator && dblclk )
343 {
344 evtType = wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK;
345 }
346 else // not double click on separator
347 {
348 evtType = click ? wxEVT_COMMAND_HEADER_CLICK
349 : wxEVT_COMMAND_HEADER_DCLICK;
350 }
351 break;
352
353 case wxMOUSE_BTN_RIGHT:
354 evtType = click ? wxEVT_COMMAND_HEADER_RIGHT_CLICK
355 : wxEVT_COMMAND_HEADER_RIGHT_DCLICK;
356 break;
357
358 case wxMOUSE_BTN_MIDDLE:
359 evtType = click ? wxEVT_COMMAND_HEADER_MIDDLE_CLICK
360 : wxEVT_COMMAND_HEADER_MIDDLE_DCLICK;
361 break;
362
363 default:
364 // ignore clicks from other mouse buttons
365 ;
366 }
367 }
368
369 if ( evtType == wxEVT_NULL )
370 return;
371
372 wxHeaderCtrlEvent event(evtType, GetId());
373 event.SetEventObject(this);
374 event.SetColumn(col);
375
376 if ( GetEventHandler()->ProcessEvent(event) )
377 mevent.Skip(false);
378 }
379
380 #endif // wxHAS_GENERIC_HEADERCTRL