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