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