]> git.saurik.com Git - wxWidgets.git/blame - src/common/ctrlcmn.cpp
Dramatically optimise inserting many items in wxGenericListCtrl.
[wxWidgets.git] / src / common / ctrlcmn.cpp
CommitLineData
2d61b48d 1/////////////////////////////////////////////////////////////////////////////
0e2d2986 2// Name: src/common/ctrlcmn.cpp
2d61b48d
VZ
3// Purpose: wxControl common interface
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 26.07.99
7// RCS-ID: $Id$
77ffb593 8// Copyright: (c) wxWidgets team
65571936 9// Licence: wxWindows licence
2d61b48d
VZ
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
2d61b48d
VZ
20// For compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
24 #pragma hdrstop
25#endif
26
1e6feb95
VZ
27#if wxUSE_CONTROLS
28
93fbbe07
WS
29#include "wx/control.h"
30
2d61b48d 31#ifndef WX_PRECOMP
02b4f9fd 32 #include "wx/dc.h"
2d61b48d 33 #include "wx/log.h"
0e2d2986 34 #include "wx/radiobut.h"
e267406e 35 #include "wx/statbmp.h"
1e6feb95 36 #include "wx/bitmap.h"
74639764 37 #include "wx/utils.h" // for wxStripMenuCodes()
0534259a 38 #include "wx/settings.h"
0bca0373 39#endif
1e6feb95 40
3da9cffc
VZ
41#include "wx/private/markupparser.h"
42
f36e602b 43const char wxControlNameStr[] = "control";
e7445ff8 44
2d61b48d
VZ
45// ============================================================================
46// implementation
47// ============================================================================
48
799ea011
GD
49wxControlBase::~wxControlBase()
50{
51 // this destructor is required for Darwin
52}
53
1e6feb95
VZ
54bool wxControlBase::Create(wxWindow *parent,
55 wxWindowID id,
56 const wxPoint &pos,
57 const wxSize &size,
58 long style,
ac8d0c11 59 const wxValidator& wxVALIDATOR_PARAM(validator),
1e6feb95
VZ
60 const wxString &name)
61{
62 bool ret = wxWindow::Create(parent, id, pos, size, style, name);
63
64#if wxUSE_VALIDATORS
65 if ( ret )
66 SetValidator(validator);
67#endif // wxUSE_VALIDATORS
68
69 return ret;
70}
71
2d61b48d
VZ
72bool wxControlBase::CreateControl(wxWindowBase *parent,
73 wxWindowID id,
74 const wxPoint& pos,
75 const wxSize& size,
76 long style,
77 const wxValidator& validator,
78 const wxString& name)
79{
80 // even if it's possible to create controls without parents in some port,
81 // it should surely be discouraged because it doesn't work at all under
82 // Windows
c9d59ee7 83 wxCHECK_MSG( parent, false, wxT("all controls must have parents") );
2d61b48d
VZ
84
85 if ( !CreateBase(parent, id, pos, size, style, validator, name) )
c9d59ee7 86 return false;
2d61b48d
VZ
87
88 parent->AddChild(this);
89
c9d59ee7 90 return true;
2d61b48d
VZ
91}
92
2d61b48d
VZ
93void wxControlBase::Command(wxCommandEvent& event)
94{
bfbd6dc1 95 (void)GetEventHandler()->ProcessEvent(event);
2d61b48d 96}
bfbd6dc1
VZ
97
98void wxControlBase::InitCommandEvent(wxCommandEvent& event) const
99{
f48a1159 100 event.SetEventObject(const_cast<wxControlBase *>(this));
bfbd6dc1
VZ
101
102 // event.SetId(GetId()); -- this is usuall done in the event ctor
103
104 switch ( m_clientDataType )
105 {
1e6feb95 106 case wxClientData_Void:
bfbd6dc1
VZ
107 event.SetClientData(GetClientData());
108 break;
109
1e6feb95 110 case wxClientData_Object:
bfbd6dc1
VZ
111 event.SetClientObject(GetClientObject());
112 break;
113
1e6feb95 114 case wxClientData_None:
bfbd6dc1
VZ
115 // nothing to do
116 ;
117 }
118}
119
9f884528
RD
120bool wxControlBase::SetFont(const wxFont& font)
121{
122 InvalidateBestSize();
123 return wxWindow::SetFont(font);
124}
125
a3a4105d
VZ
126// wxControl-specific processing after processing the update event
127void wxControlBase::DoUpdateWindowUI(wxUpdateUIEvent& event)
128{
129 // call inherited
130 wxWindowBase::DoUpdateWindowUI(event);
131
132 // update label
133 if ( event.GetSetText() )
134 {
135 if ( event.GetText() != GetLabel() )
136 SetLabel(event.GetText());
137 }
138
139 // Unfortunately we don't yet have common base class for
140 // wxRadioButton, so we handle updates of radiobuttons here.
141 // TODO: If once wxRadioButtonBase will exist, move this code there.
142#if wxUSE_RADIOBTN
143 if ( event.GetSetChecked() )
144 {
145 wxRadioButton *radiobtn = wxDynamicCastThis(wxRadioButton);
146 if ( radiobtn )
147 radiobtn->SetValue(event.GetChecked());
148 }
149#endif // wxUSE_RADIOBTN
150}
151
32ee98eb
FM
152/* static */
153wxString wxControlBase::GetLabelText(const wxString& label)
154{
155 // we don't want strip the TABs here, just the mnemonics
156 return wxStripMenuCodes(label, wxStrip_Mnemonics);
157}
158
39bc0347
VZ
159/* static */
160wxString wxControlBase::RemoveMnemonics(const wxString& str)
161{
32ee98eb 162 // we don't want strip the TABs here, just the mnemonics
39bc0347
VZ
163 return wxStripMenuCodes(str, wxStrip_Mnemonics);
164}
165
5b8b2c84
VZ
166/* static */
167wxString wxControlBase::EscapeMnemonics(const wxString& text)
168{
169 wxString label(text);
170 label.Replace("&", "&&");
171 return label;
172}
173
916eabe6
VZ
174/* static */
175int wxControlBase::FindAccelIndex(const wxString& label, wxString *labelOnly)
176{
177 // the character following MNEMONIC_PREFIX is the accelerator for this
178 // control unless it is MNEMONIC_PREFIX too - this allows to insert
179 // literal MNEMONIC_PREFIX chars into the label
9a83f860 180 static const wxChar MNEMONIC_PREFIX = wxT('&');
916eabe6
VZ
181
182 if ( labelOnly )
183 {
184 labelOnly->Empty();
185 labelOnly->Alloc(label.length());
186 }
187
188 int indexAccel = -1;
189 for ( wxString::const_iterator pc = label.begin(); pc != label.end(); ++pc )
190 {
191 if ( *pc == MNEMONIC_PREFIX )
192 {
193 ++pc; // skip it
194 if ( pc == label.end() )
195 break;
196 else if ( *pc != MNEMONIC_PREFIX )
197 {
198 if ( indexAccel == -1 )
199 {
200 // remember it (-1 is for MNEMONIC_PREFIX itself
201 indexAccel = pc - label.begin() - 1;
202 }
203 else
204 {
9a83f860 205 wxFAIL_MSG(wxT("duplicate accel char in control label"));
916eabe6
VZ
206 }
207 }
208 }
209
210 if ( labelOnly )
211 {
212 *labelOnly += *pc;
213 }
214 }
215
216 return indexAccel;
217}
218
a78618b0
FM
219wxBorder wxControlBase::GetDefaultBorder() const
220{
221 return wxBORDER_THEME;
222}
223
0534259a
VZ
224/* static */ wxVisualAttributes
225wxControlBase::GetCompositeControlsDefaultAttributes(wxWindowVariant WXUNUSED(variant))
226{
227 wxVisualAttributes attrs;
228 attrs.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
229 attrs.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
230 attrs.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
231
232 return attrs;
233}
234
3da9cffc
VZ
235// ----------------------------------------------------------------------------
236// wxControl markup support
237// ----------------------------------------------------------------------------
238
f5bdfc69
VZ
239#if wxUSE_MARKUP
240
3da9cffc
VZ
241/* static */
242wxString wxControlBase::RemoveMarkup(const wxString& markup)
243{
244 return wxMarkupParser::Strip(markup);
245}
246
247bool wxControlBase::DoSetLabelMarkup(const wxString& markup)
248{
249 const wxString label = RemoveMarkup(markup);
250 if ( label.empty() && !markup.empty() )
251 return false;
252
253 SetLabel(label);
254
255 return true;
256}
257
f5bdfc69
VZ
258#endif // wxUSE_MARKUP
259
a78618b0
FM
260// ----------------------------------------------------------------------------
261// wxControlBase - ellipsization code
262// ----------------------------------------------------------------------------
263
8ab11e46 264#define wxELLIPSE_REPLACEMENT wxS("...")
5c87527c 265
38ddd614
VS
266namespace
267{
268
269struct EllipsizeCalculator
270{
271 EllipsizeCalculator(const wxString& s, const wxDC& dc,
272 int maxFinalWidthPx, int replacementWidthPx)
273 :
274 m_initialCharToRemove(0),
275 m_nCharsToRemove(0),
276 m_outputNeedsUpdate(true),
277 m_str(s),
278 m_dc(dc),
279 m_maxFinalWidthPx(maxFinalWidthPx),
280 m_replacementWidthPx(replacementWidthPx)
281 {
282 m_isOk = dc.GetPartialTextExtents(s, m_charOffsetsPx);
283 wxASSERT( m_charOffsetsPx.GetCount() == s.length() );
284 }
285
286 bool IsOk() const { return m_isOk; }
287
288 bool EllipsizationNotNeeded() const
289 {
290 // NOTE: charOffsetsPx[n] is the width in pixels of the first n characters (with the last one INCLUDED)
291 // thus charOffsetsPx[len-1] is the total width of the string
292 return m_charOffsetsPx.Last() <= m_maxFinalWidthPx;
293 }
294
295 void Init(size_t initialCharToRemove, size_t nCharsToRemove)
296 {
297 m_initialCharToRemove = initialCharToRemove;
298 m_nCharsToRemove = nCharsToRemove;
299 }
300
301 void RemoveFromEnd()
302 {
303 m_nCharsToRemove++;
304 }
305
306 void RemoveFromStart()
307 {
308 m_initialCharToRemove--;
309 m_nCharsToRemove++;
310 }
311
312 size_t GetFirstRemoved() const { return m_initialCharToRemove; }
313 size_t GetLastRemoved() const { return m_initialCharToRemove + m_nCharsToRemove - 1; }
314
315 const wxString& GetEllipsizedText()
316 {
317 if ( m_outputNeedsUpdate )
318 {
319 wxASSERT(m_initialCharToRemove <= m_str.length() - 1); // see valid range for initialCharToRemove above
320 wxASSERT(m_nCharsToRemove >= 1 && m_nCharsToRemove <= m_str.length() - m_initialCharToRemove); // see valid range for nCharsToRemove above
321
322 // erase m_nCharsToRemove characters after m_initialCharToRemove (included);
323 // e.g. if we have the string "foobar" (len = 6)
324 // ^
325 // \--- m_initialCharToRemove = 2
326 // and m_nCharsToRemove = 2, then we get "foar"
327 m_output = m_str;
328 m_output.replace(m_initialCharToRemove, m_nCharsToRemove, wxELLIPSE_REPLACEMENT);
329 }
330
331 return m_output;
332 }
333
334 bool IsShortEnough()
335 {
336 if ( m_nCharsToRemove == m_str.length() )
337 return true; // that's the best we could do
338
339 // Width calculation using partial extents is just an inaccurate
340 // estimate: partial extents have sub-pixel precision and are rounded
341 // by GetPartialTextExtents(); replacing part of the string with "..."
342 // may change them too thanks to changes in ligatures, kerning etc.
343 //
344 // The correct algorithm would be to call GetTextExtent() in every step
345 // of ellipsization, but that would be too expensive, especially when
346 // the difference is just a few pixels. So we use partial extents to
347 // estimate string width and only verify it with GetTextExtent() when
348 // it looks good.
349
350 int estimatedWidth = m_replacementWidthPx; // length of "..."
351
352 // length of text before the removed part:
353 if ( m_initialCharToRemove > 0 )
354 estimatedWidth += m_charOffsetsPx[m_initialCharToRemove - 1];
355
356 // length of text after the removed part:
357
358 if ( GetLastRemoved() < m_str.length() )
359 estimatedWidth += m_charOffsetsPx.Last() - m_charOffsetsPx[GetLastRemoved()];
360
361 if ( estimatedWidth > m_maxFinalWidthPx )
362 return false;
363
364 return m_dc.GetTextExtent(GetEllipsizedText()).GetWidth() <= m_maxFinalWidthPx;
365 }
366
367 // calculation state:
368
369 // REMEMBER: indexes inside the string have a valid range of [0;len-1] if not otherwise constrained
370 // lengths/counts of characters (e.g. nCharsToRemove) have a
371 // valid range of [0;len] if not otherwise constrained
372 // NOTE: since this point we know we have for sure a non-empty string from which we need
373 // to remove _at least_ one character (thus nCharsToRemove below is constrained to be >= 1)
374
375 // index of first character to erase, valid range is [0;len-1]:
376 size_t m_initialCharToRemove;
377 // how many chars do we need to erase? valid range is [0;len-m_initialCharToRemove]
378 size_t m_nCharsToRemove;
379
380 wxString m_output;
381 bool m_outputNeedsUpdate;
382
383 // inputs:
384 wxString m_str;
385 const wxDC& m_dc;
386 int m_maxFinalWidthPx;
387 int m_replacementWidthPx;
388 wxArrayInt m_charOffsetsPx;
389
390 bool m_isOk;
391};
392
393} // anonymous namespace
394
a78618b0
FM
395/* static and protected */
396wxString wxControlBase::DoEllipsizeSingleLine(const wxString& curLine, const wxDC& dc,
8ab11e46 397 wxEllipsizeMode mode, int maxFinalWidthPx,
1d065710 398 int replacementWidthPx)
a78618b0 399{
1d065710 400 wxASSERT_MSG(replacementWidthPx > 0, "Invalid parameters");
a37da0fa
FM
401 wxASSERT_LEVEL_2_MSG(!curLine.Contains('\n'),
402 "Use Ellipsize() instead!");
a78618b0 403
c937bcac
VZ
404 wxASSERT_MSG( mode != wxELLIPSIZE_NONE, "shouldn't be called at all then" );
405
a78618b0
FM
406 // NOTE: this function assumes that any mnemonic/tab character has already
407 // been handled if it was necessary to handle them (see Ellipsize())
408
8ab11e46 409 if (maxFinalWidthPx <= 0)
a78618b0
FM
410 return wxEmptyString;
411
a78618b0 412 size_t len = curLine.length();
38ddd614 413 if (len <= 1 )
a78618b0
FM
414 return curLine;
415
38ddd614 416 EllipsizeCalculator calc(curLine, dc, maxFinalWidthPx, replacementWidthPx);
a78618b0 417
38ddd614
VS
418 if ( !calc.IsOk() )
419 return curLine;
a78618b0 420
38ddd614
VS
421 if ( calc.EllipsizationNotNeeded() )
422 return curLine;
a37da0fa
FM
423
424 // let's compute the range of characters to remove depending on the ellipsization mode:
a78618b0
FM
425 switch (mode)
426 {
8d6e8e45 427 case wxELLIPSIZE_START:
38ddd614
VS
428 {
429 calc.Init(0, 1);
430 while ( !calc.IsShortEnough() )
431 calc.RemoveFromEnd();
4b52555d
VS
432
433 // always show at least one character of the string:
434 if ( calc.m_nCharsToRemove == len )
435 return wxString(wxELLIPSE_REPLACEMENT) + curLine[len-1];
436
38ddd614
VS
437 break;
438 }
a78618b0 439
8d6e8e45 440 case wxELLIPSIZE_MIDDLE:
a78618b0 441 {
a37da0fa
FM
442 // NOTE: the following piece of code works also when len == 1
443
444 // start the removal process from the middle of the string
ce00f59b 445 // i.e. separe the string in three parts:
a37da0fa
FM
446 // - the first one to preserve, valid range [0;initialCharToRemove-1] or the empty range if initialCharToRemove==0
447 // - the second one to remove, valid range [initialCharToRemove;endCharToRemove]
448 // - the third one to preserve, valid range [endCharToRemove+1;len-1] or the empty range if endCharToRemove==len-1
449 // NOTE: empty range != range [0;0] since the range [0;0] contains 1 character (the zero-th one)!
a78618b0 450
38ddd614
VS
451 calc.Init(len/2, 0);
452
3433de6e 453 bool removeFromStart = true;
38ddd614
VS
454
455 while ( !calc.IsShortEnough() )
a78618b0 456 {
38ddd614
VS
457 const bool canRemoveFromStart = calc.GetFirstRemoved() > 0;
458 const bool canRemoveFromEnd = calc.GetLastRemoved() < len - 1;
3433de6e
VS
459
460 if ( !canRemoveFromStart && !canRemoveFromEnd )
461 {
462 // we need to remove all the characters of the string!
463 break;
464 }
465
466 // Remove from the beginning in even steps and from the end
467 // in odd steps, unless we exhausted one side already:
468 removeFromStart = !removeFromStart;
469 if ( removeFromStart && !canRemoveFromStart )
470 removeFromStart = false;
471 else if ( !removeFromStart && !canRemoveFromEnd )
472 removeFromStart = true;
473
474 if ( removeFromStart )
38ddd614
VS
475 calc.RemoveFromStart();
476 else
477 calc.RemoveFromEnd();
a78618b0 478 }
4b52555d
VS
479
480 // Always show at least one character of the string.
481 // Additionally, if there's only one character left, prefer
482 // "a..." to "...a":
483 if ( calc.m_nCharsToRemove == len ||
484 calc.m_nCharsToRemove == len - 1 )
485 {
486 return curLine[0] + wxString(wxELLIPSE_REPLACEMENT);
487 }
a78618b0 488 }
8d6e8e45 489 break;
a78618b0 490
8d6e8e45
VZ
491 case wxELLIPSIZE_END:
492 {
38ddd614
VS
493 calc.Init(len - 1, 1);
494 while ( !calc.IsShortEnough() )
495 calc.RemoveFromStart();
4b52555d
VS
496
497 // always show at least one character of the string:
498 if ( calc.m_nCharsToRemove == len )
499 return curLine[0] + wxString(wxELLIPSE_REPLACEMENT);
500
38ddd614 501 break;
a78618b0 502 }
a78618b0 503
c937bcac 504 case wxELLIPSIZE_NONE:
8d6e8e45
VZ
505 default:
506 wxFAIL_MSG("invalid ellipsize mode");
507 return curLine;
a78618b0
FM
508 }
509
38ddd614 510 return calc.GetEllipsizedText();
a78618b0
FM
511}
512
5c87527c
FM
513/* static */
514wxString wxControlBase::Ellipsize(const wxString& label, const wxDC& dc,
a78618b0
FM
515 wxEllipsizeMode mode, int maxFinalWidth,
516 int flags)
5c87527c 517{
5c87527c
FM
518 wxString ret;
519
a78618b0
FM
520 // these cannot be cached between different Ellipsize() calls as they can
521 // change because of e.g. a font change; however we calculate them only once
522 // when ellipsizing multiline labels:
5c87527c 523 int replacementWidth = dc.GetTextExtent(wxELLIPSE_REPLACEMENT).GetWidth();
5c87527c
FM
524
525 // NB: we must handle correctly labels with newlines:
526 wxString curLine;
5c87527c
FM
527 for ( wxString::const_iterator pc = label.begin(); ; ++pc )
528 {
a78618b0 529 if ( pc == label.end() || *pc == wxS('\n') )
5c87527c 530 {
a78618b0 531 curLine = DoEllipsizeSingleLine(curLine, dc, mode, maxFinalWidth,
1d065710 532 replacementWidth);
5c87527c
FM
533
534 // add this (ellipsized) row to the rest of the label
535 ret << curLine;
536 if ( pc == label.end() )
537 {
7d27b626 538 // NOTE: this is the return which always exits the function
5c87527c
FM
539 return ret;
540 }
541 else
542 {
543 ret << *pc;
544 curLine.clear();
545 }
546 }
547 // we need to remove mnemonics from the label for correct calculations
355ce7ad 548 else if ( *pc == wxS('&') && (flags & wxELLIPSIZE_FLAGS_PROCESS_MNEMONICS) )
5c87527c
FM
549 {
550 // pc+1 is safe: at worst we'll be at end()
551 wxString::const_iterator next = pc + 1;
a78618b0
FM
552 if ( next != label.end() && *next == wxS('&') )
553 curLine += wxS('&'); // && becomes &
5c87527c
FM
554 //else: remove this ampersand
555 }
556 // we need also to expand tabs to properly calc their size
355ce7ad 557 else if ( *pc == wxS('\t') && (flags & wxELLIPSIZE_FLAGS_EXPAND_TABS) )
5c87527c
FM
558 {
559 // Windows natively expands the TABs to 6 spaces. Do the same:
a78618b0 560 curLine += wxS(" ");
5c87527c
FM
561 }
562 else
563 {
564 curLine += *pc;
565 }
566 }
567
02b4f9fd 568 // this return would generate a
7d27b626 569 // warning C4702: unreachable code
02b4f9fd 570 // with MSVC since the function always exits from inside the loop
7d27b626 571 //return ret;
5c87527c
FM
572}
573
dc797d8e
JS
574
575
1e6feb95
VZ
576// ----------------------------------------------------------------------------
577// wxStaticBitmap
578// ----------------------------------------------------------------------------
579
580#if wxUSE_STATBMP
581
799ea011
GD
582wxStaticBitmapBase::~wxStaticBitmapBase()
583{
584 // this destructor is required for Darwin
585}
586
b3fcfa4d 587wxSize wxStaticBitmapBase::DoGetBestSize() const
1e6feb95 588{
9f884528 589 wxSize best;
1e6feb95 590 wxBitmap bmp = GetBitmap();
a1b806b9 591 if ( bmp.IsOk() )
9f884528
RD
592 best = wxSize(bmp.GetWidth(), bmp.GetHeight());
593 else
594 // this is completely arbitrary
595 best = wxSize(16, 16);
596 CacheBestSize(best);
597 return best;
1e6feb95
VZ
598}
599
600#endif // wxUSE_STATBMP
601
602#endif // wxUSE_CONTROLS