Ellipsization can't shorten 1-character string.
[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 const char wxControlNameStr[] = "control";
42
43 // ============================================================================
44 // implementation
45 // ============================================================================
46
47 wxControlBase::~wxControlBase()
48 {
49 // this destructor is required for Darwin
50 }
51
52 bool wxControlBase::Create(wxWindow *parent,
53 wxWindowID id,
54 const wxPoint &pos,
55 const wxSize &size,
56 long style,
57 const wxValidator& wxVALIDATOR_PARAM(validator),
58 const wxString &name)
59 {
60 bool ret = wxWindow::Create(parent, id, pos, size, style, name);
61
62 #if wxUSE_VALIDATORS
63 if ( ret )
64 SetValidator(validator);
65 #endif // wxUSE_VALIDATORS
66
67 return ret;
68 }
69
70 bool wxControlBase::CreateControl(wxWindowBase *parent,
71 wxWindowID id,
72 const wxPoint& pos,
73 const wxSize& size,
74 long style,
75 const wxValidator& validator,
76 const wxString& name)
77 {
78 // even if it's possible to create controls without parents in some port,
79 // it should surely be discouraged because it doesn't work at all under
80 // Windows
81 wxCHECK_MSG( parent, false, wxT("all controls must have parents") );
82
83 if ( !CreateBase(parent, id, pos, size, style, validator, name) )
84 return false;
85
86 parent->AddChild(this);
87
88 return true;
89 }
90
91 void wxControlBase::Command(wxCommandEvent& event)
92 {
93 (void)GetEventHandler()->ProcessEvent(event);
94 }
95
96 void wxControlBase::InitCommandEvent(wxCommandEvent& event) const
97 {
98 event.SetEventObject(const_cast<wxControlBase *>(this));
99
100 // event.SetId(GetId()); -- this is usuall done in the event ctor
101
102 switch ( m_clientDataType )
103 {
104 case wxClientData_Void:
105 event.SetClientData(GetClientData());
106 break;
107
108 case wxClientData_Object:
109 event.SetClientObject(GetClientObject());
110 break;
111
112 case wxClientData_None:
113 // nothing to do
114 ;
115 }
116 }
117
118 bool wxControlBase::SetFont(const wxFont& font)
119 {
120 InvalidateBestSize();
121 return wxWindow::SetFont(font);
122 }
123
124 // wxControl-specific processing after processing the update event
125 void wxControlBase::DoUpdateWindowUI(wxUpdateUIEvent& event)
126 {
127 // call inherited
128 wxWindowBase::DoUpdateWindowUI(event);
129
130 // update label
131 if ( event.GetSetText() )
132 {
133 if ( event.GetText() != GetLabel() )
134 SetLabel(event.GetText());
135 }
136
137 // Unfortunately we don't yet have common base class for
138 // wxRadioButton, so we handle updates of radiobuttons here.
139 // TODO: If once wxRadioButtonBase will exist, move this code there.
140 #if wxUSE_RADIOBTN
141 if ( event.GetSetChecked() )
142 {
143 wxRadioButton *radiobtn = wxDynamicCastThis(wxRadioButton);
144 if ( radiobtn )
145 radiobtn->SetValue(event.GetChecked());
146 }
147 #endif // wxUSE_RADIOBTN
148 }
149
150 /* static */
151 wxString wxControlBase::GetLabelText(const wxString& label)
152 {
153 // we don't want strip the TABs here, just the mnemonics
154 return wxStripMenuCodes(label, wxStrip_Mnemonics);
155 }
156
157 /* static */
158 wxString wxControlBase::RemoveMnemonics(const wxString& str)
159 {
160 // we don't want strip the TABs here, just the mnemonics
161 return wxStripMenuCodes(str, wxStrip_Mnemonics);
162 }
163
164 /* static */
165 wxString wxControlBase::EscapeMnemonics(const wxString& text)
166 {
167 wxString label(text);
168 label.Replace("&", "&&");
169 return label;
170 }
171
172 /* static */
173 int wxControlBase::FindAccelIndex(const wxString& label, wxString *labelOnly)
174 {
175 // the character following MNEMONIC_PREFIX is the accelerator for this
176 // control unless it is MNEMONIC_PREFIX too - this allows to insert
177 // literal MNEMONIC_PREFIX chars into the label
178 static const wxChar MNEMONIC_PREFIX = wxT('&');
179
180 if ( labelOnly )
181 {
182 labelOnly->Empty();
183 labelOnly->Alloc(label.length());
184 }
185
186 int indexAccel = -1;
187 for ( wxString::const_iterator pc = label.begin(); pc != label.end(); ++pc )
188 {
189 if ( *pc == MNEMONIC_PREFIX )
190 {
191 ++pc; // skip it
192 if ( pc == label.end() )
193 break;
194 else if ( *pc != MNEMONIC_PREFIX )
195 {
196 if ( indexAccel == -1 )
197 {
198 // remember it (-1 is for MNEMONIC_PREFIX itself
199 indexAccel = pc - label.begin() - 1;
200 }
201 else
202 {
203 wxFAIL_MSG(wxT("duplicate accel char in control label"));
204 }
205 }
206 }
207
208 if ( labelOnly )
209 {
210 *labelOnly += *pc;
211 }
212 }
213
214 return indexAccel;
215 }
216
217 wxBorder wxControlBase::GetDefaultBorder() const
218 {
219 return wxBORDER_THEME;
220 }
221
222 /* static */ wxVisualAttributes
223 wxControlBase::GetCompositeControlsDefaultAttributes(wxWindowVariant WXUNUSED(variant))
224 {
225 wxVisualAttributes attrs;
226 attrs.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
227 attrs.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
228 attrs.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
229
230 return attrs;
231 }
232
233 // ----------------------------------------------------------------------------
234 // wxControlBase - ellipsization code
235 // ----------------------------------------------------------------------------
236
237 #define wxELLIPSE_REPLACEMENT wxS("...")
238
239 /* static and protected */
240 wxString wxControlBase::DoEllipsizeSingleLine(const wxString& curLine, const wxDC& dc,
241 wxEllipsizeMode mode, int maxFinalWidthPx,
242 int replacementWidthPx)
243 {
244 wxASSERT_MSG(replacementWidthPx > 0, "Invalid parameters");
245 wxASSERT_LEVEL_2_MSG(!curLine.Contains('\n'),
246 "Use Ellipsize() instead!");
247
248 wxASSERT_MSG( mode != wxELLIPSIZE_NONE, "shouldn't be called at all then" );
249
250 // NOTE: this function assumes that any mnemonic/tab character has already
251 // been handled if it was necessary to handle them (see Ellipsize())
252
253 if (maxFinalWidthPx <= 0)
254 return wxEmptyString;
255
256 wxArrayInt charOffsetsPx;
257 size_t len = curLine.length();
258 if (len <= 1 ||
259 !dc.GetPartialTextExtents(curLine, charOffsetsPx))
260 return curLine;
261
262 wxASSERT(charOffsetsPx.GetCount() == len);
263
264 // NOTE: charOffsetsPx[n] is the width in pixels of the first n characters (with the last one INCLUDED)
265 // thus charOffsetsPx[len-1] is the total width of the string
266 size_t totalWidthPx = charOffsetsPx.Last();
267 if ( totalWidthPx <= (size_t)maxFinalWidthPx )
268 return curLine; // we don't need to do any ellipsization!
269
270 int excessPx = wxMin(totalWidthPx - maxFinalWidthPx +
271 replacementWidthPx,
272 totalWidthPx);
273 wxASSERT(excessPx>0); // excessPx should be in the [1;totalWidthPx] range
274
275 // REMEMBER: indexes inside the string have a valid range of [0;len-1] if not otherwise constrained
276 // lengths/counts of characters (e.g. nCharsToRemove) have a valid range of [0;len] if not otherwise constrained
277 // NOTE: since this point we know we have for sure a non-empty string from which we need
278 // to remove _at least_ one character (thus nCharsToRemove below is constrained to be >= 1)
279
280 size_t initialCharToRemove, // index of first character to erase, valid range is [0;len-1]
281 nCharsToRemove; // how many chars do we need to erase? valid range is [1;len-initialCharToRemove]
282
283 // let's compute the range of characters to remove depending on the ellipsization mode:
284 switch (mode)
285 {
286 case wxELLIPSIZE_START:
287 initialCharToRemove = 0;
288 for ( nCharsToRemove = 1;
289 nCharsToRemove < len && charOffsetsPx[nCharsToRemove-1] < excessPx;
290 nCharsToRemove++ )
291 ;
292 break;
293
294 case wxELLIPSIZE_MIDDLE:
295 {
296 // NOTE: the following piece of code works also when len == 1
297
298 // start the removal process from the middle of the string
299 // i.e. separe the string in three parts:
300 // - the first one to preserve, valid range [0;initialCharToRemove-1] or the empty range if initialCharToRemove==0
301 // - the second one to remove, valid range [initialCharToRemove;endCharToRemove]
302 // - the third one to preserve, valid range [endCharToRemove+1;len-1] or the empty range if endCharToRemove==len-1
303 // NOTE: empty range != range [0;0] since the range [0;0] contains 1 character (the zero-th one)!
304 initialCharToRemove = len/2; // index of the last character to remove; valid range is [0;len-1]
305 size_t endCharToRemove = initialCharToRemove - 1; // initial removal range is empty
306
307 int removedPx = 0;
308 bool removeFromStart = true;
309 for ( ; removedPx < excessPx; )
310 {
311 const bool canRemoveFromStart = initialCharToRemove > 0;
312 const bool canRemoveFromEnd = endCharToRemove < len - 1;
313
314 if ( !canRemoveFromStart && !canRemoveFromEnd )
315 {
316 // we need to remove all the characters of the string!
317 break;
318 }
319
320 // Remove from the beginning in even steps and from the end
321 // in odd steps, unless we exhausted one side already:
322 removeFromStart = !removeFromStart;
323 if ( removeFromStart && !canRemoveFromStart )
324 removeFromStart = false;
325 else if ( !removeFromStart && !canRemoveFromEnd )
326 removeFromStart = true;
327
328 if ( removeFromStart )
329 {
330 // try to remove the last character of the first part of the string
331
332 // width of the (initialCharToRemove-1)-th character
333 int widthPx;
334 if (initialCharToRemove >= 2)
335 widthPx = charOffsetsPx[initialCharToRemove-1] - charOffsetsPx[initialCharToRemove-2];
336 else
337 widthPx = charOffsetsPx[initialCharToRemove-1];
338 // the (initialCharToRemove-1)-th character is the first char of the string
339
340 wxASSERT(widthPx >= 0); // widthPx is zero for e.g. tab characters
341
342 // mark the (initialCharToRemove-1)-th character as removable
343 initialCharToRemove--;
344 removedPx += widthPx;
345
346 continue; // don't remove anything else
347 }
348 else // !removeFromStart
349 {
350 // try to remove the first character of the last part of the string
351
352 // width of the (endCharToRemove+1)-th character
353 int widthPx = charOffsetsPx[endCharToRemove+1] -
354 charOffsetsPx[endCharToRemove];
355
356 wxASSERT(widthPx >= 0); // widthPx is zero for e.g. tab characters
357
358 // mark the (endCharToRemove+1)-th character as removable
359 endCharToRemove++;
360 removedPx += widthPx;
361
362 continue; // don't remove anything else
363 }
364 }
365
366 nCharsToRemove = endCharToRemove - initialCharToRemove + 1;
367 }
368 break;
369
370 case wxELLIPSIZE_END:
371 {
372 int maxWidthPx = totalWidthPx - excessPx;
373
374 // go backward from the end of the string toward the start
375 for ( initialCharToRemove = len-1;
376 initialCharToRemove > 0 && charOffsetsPx[initialCharToRemove-1] > maxWidthPx;
377 initialCharToRemove-- )
378 ;
379 nCharsToRemove = len - initialCharToRemove;
380 }
381 break;
382
383 case wxELLIPSIZE_NONE:
384 default:
385 wxFAIL_MSG("invalid ellipsize mode");
386 return curLine;
387 }
388
389 wxASSERT(initialCharToRemove <= len-1); // see valid range for initialCharToRemove above
390 wxASSERT(nCharsToRemove >= 1 && nCharsToRemove <= len-initialCharToRemove); // see valid range for nCharsToRemove above
391
392 // erase nCharsToRemove characters after initialCharToRemove (included);
393 // e.g. if we have the string "foobar" (len = 6)
394 // ^
395 // \--- initialCharToRemove = 2
396 // and nCharsToRemove = 2, then we get "foar"
397 wxString ret(curLine);
398 ret.erase(initialCharToRemove, nCharsToRemove);
399
400 int removedPx;
401 if (initialCharToRemove >= 1)
402 removedPx = charOffsetsPx[initialCharToRemove+nCharsToRemove-1] - charOffsetsPx[initialCharToRemove-1];
403 else
404 removedPx = charOffsetsPx[initialCharToRemove+nCharsToRemove-1];
405 wxASSERT(removedPx >= excessPx);
406
407 // if there is space for the replacement dots, add them
408 if ((int)totalWidthPx-removedPx+replacementWidthPx <= maxFinalWidthPx)
409 ret.insert(initialCharToRemove, wxELLIPSE_REPLACEMENT);
410
411 // if everything was ok, we should have shortened this line
412 // enough to make it fit in maxFinalWidthPx:
413 wxASSERT(dc.GetTextExtent(ret).GetWidth() <= maxFinalWidthPx);
414
415 return ret;
416 }
417
418 /* static */
419 wxString wxControlBase::Ellipsize(const wxString& label, const wxDC& dc,
420 wxEllipsizeMode mode, int maxFinalWidth,
421 int flags)
422 {
423 wxString ret;
424
425 // these cannot be cached between different Ellipsize() calls as they can
426 // change because of e.g. a font change; however we calculate them only once
427 // when ellipsizing multiline labels:
428 int replacementWidth = dc.GetTextExtent(wxELLIPSE_REPLACEMENT).GetWidth();
429
430 // NB: we must handle correctly labels with newlines:
431 wxString curLine;
432 for ( wxString::const_iterator pc = label.begin(); ; ++pc )
433 {
434 if ( pc == label.end() || *pc == wxS('\n') )
435 {
436 curLine = DoEllipsizeSingleLine(curLine, dc, mode, maxFinalWidth,
437 replacementWidth);
438
439 // add this (ellipsized) row to the rest of the label
440 ret << curLine;
441 if ( pc == label.end() )
442 {
443 // NOTE: this is the return which always exits the function
444 return ret;
445 }
446 else
447 {
448 ret << *pc;
449 curLine.clear();
450 }
451 }
452 // we need to remove mnemonics from the label for correct calculations
453 else if ( *pc == wxS('&') && (flags & wxELLIPSIZE_FLAGS_PROCESS_MNEMONICS) )
454 {
455 // pc+1 is safe: at worst we'll be at end()
456 wxString::const_iterator next = pc + 1;
457 if ( next != label.end() && *next == wxS('&') )
458 curLine += wxS('&'); // && becomes &
459 //else: remove this ampersand
460 }
461 // we need also to expand tabs to properly calc their size
462 else if ( *pc == wxS('\t') && (flags & wxELLIPSIZE_FLAGS_EXPAND_TABS) )
463 {
464 // Windows natively expands the TABs to 6 spaces. Do the same:
465 curLine += wxS(" ");
466 }
467 else
468 {
469 curLine += *pc;
470 }
471 }
472
473 // this return would generate a
474 // warning C4702: unreachable code
475 // with MSVC since the function always exits from inside the loop
476 //return ret;
477 }
478
479
480
481 // ----------------------------------------------------------------------------
482 // wxStaticBitmap
483 // ----------------------------------------------------------------------------
484
485 #if wxUSE_STATBMP
486
487 wxStaticBitmapBase::~wxStaticBitmapBase()
488 {
489 // this destructor is required for Darwin
490 }
491
492 wxSize wxStaticBitmapBase::DoGetBestSize() const
493 {
494 wxSize best;
495 wxBitmap bmp = GetBitmap();
496 if ( bmp.Ok() )
497 best = wxSize(bmp.GetWidth(), bmp.GetHeight());
498 else
499 // this is completely arbitrary
500 best = wxSize(16, 16);
501 CacheBestSize(best);
502 return best;
503 }
504
505 #endif // wxUSE_STATBMP
506
507 #endif // wxUSE_CONTROLS