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