made wxHeaderCtrl::GetColumn() const to get rid of the const_cast<>s
[wxWidgets.git] / src / common / headerctrlcmn.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/headerctrlcmn.cpp
3 // Purpose: implementation of wxHeaderCtrlBase
4 // Author: Vadim Zeitlin
5 // Created: 2008-12-02
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 #include "wx/menu.h"
28 #endif // WX_PRECOMP
29
30 #include "wx/headerctrl.h"
31 #include "wx/rearrangectrl.h"
32
33 namespace
34 {
35
36 // ----------------------------------------------------------------------------
37 // constants
38 // ----------------------------------------------------------------------------
39
40 const unsigned int wxNO_COLUMN = static_cast<unsigned>(-1);
41
42 // ----------------------------------------------------------------------------
43 // wxHeaderColumnsRearrangeDialog: dialog for customizing our columns
44 // ----------------------------------------------------------------------------
45
46 class wxHeaderColumnsRearrangeDialog : public wxRearrangeDialog
47 {
48 public:
49 wxHeaderColumnsRearrangeDialog(wxWindow *parent,
50 const wxArrayInt& order,
51 const wxArrayString& items)
52 : wxRearrangeDialog
53 (
54 parent,
55 _("Please select the columns to show and define their order:"),
56 _("Customize Columns"),
57 order,
58 items
59 )
60 {
61 }
62 };
63
64 } // anonymous namespace
65
66 // ============================================================================
67 // wxHeaderCtrlBase implementation
68 // ============================================================================
69
70 extern WXDLLIMPEXP_DATA_CORE(const char) wxHeaderCtrlNameStr[] = "wxHeaderCtrl";
71
72 BEGIN_EVENT_TABLE(wxHeaderCtrlBase, wxControl)
73 EVT_HEADER_SEPARATOR_DCLICK(wxID_ANY, wxHeaderCtrlBase::OnSeparatorDClick)
74 EVT_HEADER_RIGHT_CLICK(wxID_ANY, wxHeaderCtrlBase::OnRClick)
75 END_EVENT_TABLE()
76
77 void wxHeaderCtrlBase::ScrollWindow(int dx,
78 int WXUNUSED_UNLESS_DEBUG(dy),
79 const wxRect * WXUNUSED_UNLESS_DEBUG(rect))
80
81 {
82 // this doesn't make sense at all
83 wxASSERT_MSG( !dy, "header window can't be scrolled vertically" );
84
85 // this would actually be nice to support for "frozen" headers but it isn't
86 // supported currently
87 wxASSERT_MSG( !rect, "header window can't be scrolled partially" );
88
89 DoScrollHorz(dx);
90 }
91
92 void wxHeaderCtrlBase::SetColumnCount(unsigned int count)
93 {
94 if ( count != GetColumnCount() )
95 OnColumnCountChanging(count);
96
97 // still call DoSetCount() even if the count didn't really change in order
98 // to update all the columns
99 DoSetCount(count);
100 }
101
102 // ----------------------------------------------------------------------------
103 // wxHeaderCtrlBase event handling
104 // ----------------------------------------------------------------------------
105
106 void wxHeaderCtrlBase::OnSeparatorDClick(wxHeaderCtrlEvent& event)
107 {
108 const unsigned col = event.GetColumn();
109
110 int w = wxWindowBase::GetTextExtent(GetColumn(col).GetTitle()).x;
111 w += 4*GetCharWidth(); // add some arbitrary margins around text
112
113 if ( !UpdateColumnWidthToFit(col, w) )
114 event.Skip();
115 else
116 UpdateColumn(col);
117 }
118
119 void wxHeaderCtrlBase::OnRClick(wxHeaderCtrlEvent& event)
120 {
121 if ( !HasFlag(wxHD_ALLOW_HIDE) )
122 {
123 event.Skip();
124 return;
125 }
126
127 ShowColumnsMenu(ScreenToClient(wxGetMousePosition()));
128 }
129
130 // ----------------------------------------------------------------------------
131 // wxHeaderCtrlBase column reordering
132 // ----------------------------------------------------------------------------
133
134 void wxHeaderCtrlBase::SetColumnsOrder(const wxArrayInt& order)
135 {
136 const unsigned count = GetColumnCount();
137 wxCHECK_RET( order.size() == count, "wrong number of columns" );
138
139 // check the array validity
140 wxArrayInt seen(count, 0);
141 for ( unsigned n = 0; n < count; n++ )
142 {
143 const unsigned idx = order[n];
144 wxCHECK_RET( idx < count, "invalid column index" );
145 wxCHECK_RET( !seen[idx], "duplicate column index" );
146
147 seen[idx] = 1;
148 }
149
150 DoSetColumnsOrder(order);
151
152 // TODO-RTL: do we need to reverse the array?
153 }
154
155 void wxHeaderCtrlBase::ResetColumnsOrder()
156 {
157 const unsigned count = GetColumnCount();
158 wxArrayInt order(count);
159 for ( unsigned n = 0; n < count; n++ )
160 order[n] = n;
161
162 DoSetColumnsOrder(order);
163 }
164
165 wxArrayInt wxHeaderCtrlBase::GetColumnsOrder() const
166 {
167 const wxArrayInt order = DoGetColumnsOrder();
168
169 wxASSERT_MSG( order.size() == GetColumnCount(), "invalid order array" );
170
171 return order;
172 }
173
174 unsigned int wxHeaderCtrlBase::GetColumnAt(unsigned int pos) const
175 {
176 wxCHECK_MSG( pos < GetColumnCount(), wxNO_COLUMN, "invalid position" );
177
178 return GetColumnsOrder()[pos];
179 }
180
181 unsigned int wxHeaderCtrlBase::GetColumnPos(unsigned int idx) const
182 {
183 const unsigned count = GetColumnCount();
184
185 wxCHECK_MSG( idx < count, wxNO_COLUMN, "invalid index" );
186
187 const wxArrayInt order = GetColumnsOrder();
188 for ( unsigned n = 0; n < count; n++ )
189 {
190 if ( (unsigned)order[n] == idx )
191 return n;
192 }
193
194 wxFAIL_MSG( "column unexpectedly not displayed at all" );
195
196 return wxNO_COLUMN;
197 }
198
199 /* static */
200 void wxHeaderCtrlBase::MoveColumnInOrderArray(wxArrayInt& order,
201 unsigned int idx,
202 unsigned int pos)
203 {
204 const unsigned count = order.size();
205
206 wxArrayInt orderNew;
207 orderNew.reserve(count);
208 for ( unsigned n = 0; ; n++ )
209 {
210 // NB: order of checks is important for this to work when the new
211 // column position is the same as the old one
212
213 // insert the column at its new position
214 if ( orderNew.size() == pos )
215 orderNew.push_back(idx);
216
217 if ( n == count )
218 break;
219
220 // delete the column from its old position
221 const unsigned idxOld = order[n];
222 if ( idxOld == idx )
223 continue;
224
225 orderNew.push_back(idxOld);
226 }
227
228 order.swap(orderNew);
229 }
230
231 void
232 wxHeaderCtrlBase::DoResizeColumnIndices(wxArrayInt& colIndices, unsigned int count)
233 {
234 // update the column indices array if necessary
235 const unsigned countOld = colIndices.size();
236 if ( count > countOld )
237 {
238 // all new columns have default positions equal to their indices
239 for ( unsigned n = countOld; n < count; n++ )
240 colIndices.push_back(n);
241 }
242 else if ( count < countOld )
243 {
244 // filter out all the positions which are invalid now while keeping the
245 // order of the remaining ones
246 wxArrayInt colIndicesNew;
247 colIndicesNew.reserve(count);
248 for ( unsigned n = 0; n < countOld; n++ )
249 {
250 const unsigned idx = colIndices[n];
251 if ( idx < count )
252 colIndicesNew.push_back(idx);
253 }
254
255 colIndices.swap(colIndicesNew);
256 }
257 //else: count didn't really change, nothing to do
258
259 wxASSERT_MSG( colIndices.size() == count, "logic error" );
260 }
261
262 // ----------------------------------------------------------------------------
263 // wxHeaderCtrl extra UI
264 // ----------------------------------------------------------------------------
265
266 bool wxHeaderCtrlBase::ShowColumnsMenu(const wxPoint& pt, const wxString& title)
267 {
268 // construct the menu with the entries for all columns
269 wxMenu menu;
270 if ( !title.empty() )
271 menu.SetTitle(title);
272
273 const unsigned count = GetColumnCount();
274 for ( unsigned n = 0; n < count; n++ )
275 {
276 const wxHeaderColumn& col = GetColumn(n);
277 menu.AppendCheckItem(n, col.GetTitle());
278 if ( col.IsShown() )
279 menu.Check(n, true);
280 }
281
282 // ... and an extra one to show the customization dialog if the user is
283 // allowed to reorder the columns too
284 if ( HasFlag(wxHD_ALLOW_REORDER) )
285 {
286 menu.AppendSeparator();
287 menu.Append(count, _("&Customize..."));
288 }
289
290 // do show the menu and get the user selection
291 const int rc = GetPopupMenuSelectionFromUser(menu, pt);
292 if ( rc == wxID_NONE )
293 return false;
294
295 if ( static_cast<unsigned>(rc) == count )
296 {
297 return ShowCustomizeDialog();
298 }
299 else // a column selected from the menu
300 {
301 UpdateColumnVisibility(rc, !GetColumn(rc).IsShown());
302 }
303
304 return true;
305 }
306
307 bool wxHeaderCtrlBase::ShowCustomizeDialog()
308 {
309 // prepare the data for showing the dialog
310 wxArrayInt order = GetColumnsOrder();
311
312 const unsigned count = GetColumnCount();
313
314 // notice that titles are always in the index order, they will be shown
315 // rearranged according to the display order in the dialog
316 wxArrayString titles;
317 titles.reserve(count);
318 for ( unsigned n = 0; n < count; n++ )
319 titles.push_back(GetColumn(n).GetTitle());
320
321 // this loop is however over positions and not indices
322 unsigned pos;
323 for ( pos = 0; pos < count; pos++ )
324 {
325 int& idx = order[pos];
326 if ( GetColumn(idx).IsHidden() )
327 {
328 // indicate that this one is hidden
329 idx = ~idx;
330 }
331 }
332
333 // do show it
334 wxHeaderColumnsRearrangeDialog dlg(this, order, titles);
335 if ( dlg.ShowModal() != wxID_OK )
336 return false;
337
338 // and apply the changes
339 order = dlg.GetOrder();
340 for ( pos = 0; pos < count; pos++ )
341 {
342 int& idx = order[pos];
343 const bool show = idx >= 0;
344 if ( !show )
345 {
346 // make all indices positive for passing them to SetColumnsOrder()
347 idx = ~idx;
348 }
349
350 if ( show != GetColumn(idx).IsShown() )
351 UpdateColumnVisibility(idx, show);
352 }
353
354 UpdateColumnsOrder(order);
355 SetColumnsOrder(order);
356
357 return true;
358 }
359
360 // ============================================================================
361 // wxHeaderCtrlSimple implementation
362 // ============================================================================
363
364 void wxHeaderCtrlSimple::Init()
365 {
366 m_sortKey = wxNO_COLUMN;
367 }
368
369 const wxHeaderColumn& wxHeaderCtrlSimple::GetColumn(unsigned int idx) const
370 {
371 return m_cols[idx];
372 }
373
374 void wxHeaderCtrlSimple::DoInsert(const wxHeaderColumnSimple& col, unsigned int idx)
375 {
376 m_cols.insert(m_cols.begin() + idx, col);
377
378 UpdateColumnCount();
379 }
380
381 void wxHeaderCtrlSimple::DoDelete(unsigned int idx)
382 {
383 m_cols.erase(m_cols.begin() + idx);
384 if ( idx == m_sortKey )
385 m_sortKey = wxNO_COLUMN;
386
387 UpdateColumnCount();
388 }
389
390 void wxHeaderCtrlSimple::DeleteAllColumns()
391 {
392 m_cols.clear();
393 m_sortKey = wxNO_COLUMN;
394
395 UpdateColumnCount();
396 }
397
398
399 void wxHeaderCtrlSimple::DoShowColumn(unsigned int idx, bool show)
400 {
401 if ( show != m_cols[idx].IsShown() )
402 {
403 m_cols[idx].SetHidden(!show);
404
405 UpdateColumn(idx);
406 }
407 }
408
409 void wxHeaderCtrlSimple::DoShowSortIndicator(unsigned int idx, bool ascending)
410 {
411 RemoveSortIndicator();
412
413 m_cols[idx].SetAsSortKey(ascending);
414 m_sortKey = idx;
415
416 UpdateColumn(idx);
417 }
418
419 void wxHeaderCtrlSimple::RemoveSortIndicator()
420 {
421 if ( m_sortKey != wxNO_COLUMN )
422 {
423 const unsigned sortOld = m_sortKey;
424 m_sortKey = wxNO_COLUMN;
425
426 m_cols[sortOld].UnsetAsSortKey();
427
428 UpdateColumn(sortOld);
429 }
430 }
431
432 bool
433 wxHeaderCtrlSimple::UpdateColumnWidthToFit(unsigned int idx, int widthTitle)
434 {
435 const int widthContents = GetBestFittingWidth(idx);
436 if ( widthContents == -1 )
437 return false;
438
439 m_cols[idx].SetWidth(wxMax(widthContents, widthTitle));
440
441 return true;
442 }
443
444 // ============================================================================
445 // wxHeaderCtrlEvent implementation
446 // ============================================================================
447
448 IMPLEMENT_DYNAMIC_CLASS(wxHeaderCtrlEvent, wxNotifyEvent)
449
450 const wxEventType wxEVT_COMMAND_HEADER_CLICK = wxNewEventType();
451 const wxEventType wxEVT_COMMAND_HEADER_RIGHT_CLICK = wxNewEventType();
452 const wxEventType wxEVT_COMMAND_HEADER_MIDDLE_CLICK = wxNewEventType();
453
454 const wxEventType wxEVT_COMMAND_HEADER_DCLICK = wxNewEventType();
455 const wxEventType wxEVT_COMMAND_HEADER_RIGHT_DCLICK = wxNewEventType();
456 const wxEventType wxEVT_COMMAND_HEADER_MIDDLE_DCLICK = wxNewEventType();
457
458 const wxEventType wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK = wxNewEventType();
459
460 const wxEventType wxEVT_COMMAND_HEADER_BEGIN_RESIZE = wxNewEventType();
461 const wxEventType wxEVT_COMMAND_HEADER_RESIZING = wxNewEventType();
462 const wxEventType wxEVT_COMMAND_HEADER_END_RESIZE = wxNewEventType();
463
464 const wxEventType wxEVT_COMMAND_HEADER_BEGIN_REORDER = wxNewEventType();
465 const wxEventType wxEVT_COMMAND_HEADER_END_REORDER = wxNewEventType();
466
467 const wxEventType wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED = wxNewEventType();