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