]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/common/ctrlcmn.cpp
compute scrollbar spacing the same way GtkScrolledWindow does it
[wxWidgets.git] / src / common / ctrlcmn.cpp
... / ...
CommitLineData
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
43const char wxControlNameStr[] = "control";
44
45// ============================================================================
46// implementation
47// ============================================================================
48
49wxControlBase::~wxControlBase()
50{
51 // this destructor is required for Darwin
52}
53
54bool 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
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
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
93void wxControlBase::Command(wxCommandEvent& event)
94{
95 (void)GetEventHandler()->ProcessEvent(event);
96}
97
98void 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
120bool wxControlBase::SetFont(const wxFont& font)
121{
122 InvalidateBestSize();
123 return wxWindow::SetFont(font);
124}
125
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
152wxSize wxControlBase::DoGetSizeFromTextSize(int WXUNUSED(xlen),
153 int WXUNUSED(ylen)) const
154{
155 return wxSize(-1, -1);
156}
157
158/* static */
159wxString wxControlBase::GetLabelText(const wxString& label)
160{
161 // we don't want strip the TABs here, just the mnemonics
162 return wxStripMenuCodes(label, wxStrip_Mnemonics);
163}
164
165/* static */
166wxString wxControlBase::RemoveMnemonics(const wxString& str)
167{
168 // we don't want strip the TABs here, just the mnemonics
169 return wxStripMenuCodes(str, wxStrip_Mnemonics);
170}
171
172/* static */
173wxString wxControlBase::EscapeMnemonics(const wxString& text)
174{
175 wxString label(text);
176 label.Replace("&", "&&");
177 return label;
178}
179
180/* static */
181int wxControlBase::FindAccelIndex(const wxString& label, wxString *labelOnly)
182{
183 // the character following MNEMONIC_PREFIX is the accelerator for this
184 // control unless it is MNEMONIC_PREFIX too - this allows to insert
185 // literal MNEMONIC_PREFIX chars into the label
186 static const wxChar MNEMONIC_PREFIX = wxT('&');
187
188 if ( labelOnly )
189 {
190 labelOnly->Empty();
191 labelOnly->Alloc(label.length());
192 }
193
194 int indexAccel = -1;
195 for ( wxString::const_iterator pc = label.begin(); pc != label.end(); ++pc )
196 {
197 if ( *pc == MNEMONIC_PREFIX )
198 {
199 ++pc; // skip it
200 if ( pc == label.end() )
201 break;
202 else if ( *pc != MNEMONIC_PREFIX )
203 {
204 if ( indexAccel == -1 )
205 {
206 // remember it (-1 is for MNEMONIC_PREFIX itself
207 indexAccel = pc - label.begin() - 1;
208 }
209 else
210 {
211 wxFAIL_MSG(wxT("duplicate accel char in control label"));
212 }
213 }
214 }
215
216 if ( labelOnly )
217 {
218 *labelOnly += *pc;
219 }
220 }
221
222 return indexAccel;
223}
224
225wxBorder wxControlBase::GetDefaultBorder() const
226{
227 return wxBORDER_THEME;
228}
229
230/* static */ wxVisualAttributes
231wxControlBase::GetCompositeControlsDefaultAttributes(wxWindowVariant WXUNUSED(variant))
232{
233 wxVisualAttributes attrs;
234 attrs.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
235 attrs.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
236 attrs.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
237
238 return attrs;
239}
240
241// ----------------------------------------------------------------------------
242// wxControl markup support
243// ----------------------------------------------------------------------------
244
245#if wxUSE_MARKUP
246
247/* static */
248wxString wxControlBase::RemoveMarkup(const wxString& markup)
249{
250 return wxMarkupParser::Strip(markup);
251}
252
253bool wxControlBase::DoSetLabelMarkup(const wxString& markup)
254{
255 const wxString label = RemoveMarkup(markup);
256 if ( label.empty() && !markup.empty() )
257 return false;
258
259 SetLabel(label);
260
261 return true;
262}
263
264#endif // wxUSE_MARKUP
265
266// ----------------------------------------------------------------------------
267// wxControlBase - ellipsization code
268// ----------------------------------------------------------------------------
269
270#define wxELLIPSE_REPLACEMENT wxS("...")
271
272namespace
273{
274
275struct EllipsizeCalculator
276{
277 EllipsizeCalculator(const wxString& s, const wxDC& dc,
278 int maxFinalWidthPx, int replacementWidthPx)
279 :
280 m_initialCharToRemove(0),
281 m_nCharsToRemove(0),
282 m_outputNeedsUpdate(true),
283 m_str(s),
284 m_dc(dc),
285 m_maxFinalWidthPx(maxFinalWidthPx),
286 m_replacementWidthPx(replacementWidthPx)
287 {
288 m_isOk = dc.GetPartialTextExtents(s, m_charOffsetsPx);
289 wxASSERT( m_charOffsetsPx.GetCount() == s.length() );
290 }
291
292 bool IsOk() const { return m_isOk; }
293
294 bool EllipsizationNotNeeded() const
295 {
296 // NOTE: charOffsetsPx[n] is the width in pixels of the first n characters (with the last one INCLUDED)
297 // thus charOffsetsPx[len-1] is the total width of the string
298 return m_charOffsetsPx.Last() <= m_maxFinalWidthPx;
299 }
300
301 void Init(size_t initialCharToRemove, size_t nCharsToRemove)
302 {
303 m_initialCharToRemove = initialCharToRemove;
304 m_nCharsToRemove = nCharsToRemove;
305 }
306
307 void RemoveFromEnd()
308 {
309 m_nCharsToRemove++;
310 }
311
312 void RemoveFromStart()
313 {
314 m_initialCharToRemove--;
315 m_nCharsToRemove++;
316 }
317
318 size_t GetFirstRemoved() const { return m_initialCharToRemove; }
319 size_t GetLastRemoved() const { return m_initialCharToRemove + m_nCharsToRemove - 1; }
320
321 const wxString& GetEllipsizedText()
322 {
323 if ( m_outputNeedsUpdate )
324 {
325 wxASSERT(m_initialCharToRemove <= m_str.length() - 1); // see valid range for initialCharToRemove above
326 wxASSERT(m_nCharsToRemove >= 1 && m_nCharsToRemove <= m_str.length() - m_initialCharToRemove); // see valid range for nCharsToRemove above
327
328 // erase m_nCharsToRemove characters after m_initialCharToRemove (included);
329 // e.g. if we have the string "foobar" (len = 6)
330 // ^
331 // \--- m_initialCharToRemove = 2
332 // and m_nCharsToRemove = 2, then we get "foar"
333 m_output = m_str;
334 m_output.replace(m_initialCharToRemove, m_nCharsToRemove, wxELLIPSE_REPLACEMENT);
335 }
336
337 return m_output;
338 }
339
340 bool IsShortEnough()
341 {
342 if ( m_nCharsToRemove == m_str.length() )
343 return true; // that's the best we could do
344
345 // Width calculation using partial extents is just an inaccurate
346 // estimate: partial extents have sub-pixel precision and are rounded
347 // by GetPartialTextExtents(); replacing part of the string with "..."
348 // may change them too thanks to changes in ligatures, kerning etc.
349 //
350 // The correct algorithm would be to call GetTextExtent() in every step
351 // of ellipsization, but that would be too expensive, especially when
352 // the difference is just a few pixels. So we use partial extents to
353 // estimate string width and only verify it with GetTextExtent() when
354 // it looks good.
355
356 int estimatedWidth = m_replacementWidthPx; // length of "..."
357
358 // length of text before the removed part:
359 if ( m_initialCharToRemove > 0 )
360 estimatedWidth += m_charOffsetsPx[m_initialCharToRemove - 1];
361
362 // length of text after the removed part:
363
364 if ( GetLastRemoved() < m_str.length() )
365 estimatedWidth += m_charOffsetsPx.Last() - m_charOffsetsPx[GetLastRemoved()];
366
367 if ( estimatedWidth > m_maxFinalWidthPx )
368 return false;
369
370 return m_dc.GetTextExtent(GetEllipsizedText()).GetWidth() <= m_maxFinalWidthPx;
371 }
372
373 // calculation state:
374
375 // REMEMBER: indexes inside the string have a valid range of [0;len-1] if not otherwise constrained
376 // lengths/counts of characters (e.g. nCharsToRemove) have a
377 // valid range of [0;len] if not otherwise constrained
378 // NOTE: since this point we know we have for sure a non-empty string from which we need
379 // to remove _at least_ one character (thus nCharsToRemove below is constrained to be >= 1)
380
381 // index of first character to erase, valid range is [0;len-1]:
382 size_t m_initialCharToRemove;
383 // how many chars do we need to erase? valid range is [0;len-m_initialCharToRemove]
384 size_t m_nCharsToRemove;
385
386 wxString m_output;
387 bool m_outputNeedsUpdate;
388
389 // inputs:
390 wxString m_str;
391 const wxDC& m_dc;
392 int m_maxFinalWidthPx;
393 int m_replacementWidthPx;
394 wxArrayInt m_charOffsetsPx;
395
396 bool m_isOk;
397};
398
399} // anonymous namespace
400
401/* static and protected */
402wxString wxControlBase::DoEllipsizeSingleLine(const wxString& curLine, const wxDC& dc,
403 wxEllipsizeMode mode, int maxFinalWidthPx,
404 int replacementWidthPx)
405{
406 wxASSERT_MSG(replacementWidthPx > 0, "Invalid parameters");
407 wxASSERT_LEVEL_2_MSG(!curLine.Contains('\n'),
408 "Use Ellipsize() instead!");
409
410 wxASSERT_MSG( mode != wxELLIPSIZE_NONE, "shouldn't be called at all then" );
411
412 // NOTE: this function assumes that any mnemonic/tab character has already
413 // been handled if it was necessary to handle them (see Ellipsize())
414
415 if (maxFinalWidthPx <= 0)
416 return wxEmptyString;
417
418 size_t len = curLine.length();
419 if (len <= 1 )
420 return curLine;
421
422 EllipsizeCalculator calc(curLine, dc, maxFinalWidthPx, replacementWidthPx);
423
424 if ( !calc.IsOk() )
425 return curLine;
426
427 if ( calc.EllipsizationNotNeeded() )
428 return curLine;
429
430 // let's compute the range of characters to remove depending on the ellipsization mode:
431 switch (mode)
432 {
433 case wxELLIPSIZE_START:
434 {
435 calc.Init(0, 1);
436 while ( !calc.IsShortEnough() )
437 calc.RemoveFromEnd();
438
439 // always show at least one character of the string:
440 if ( calc.m_nCharsToRemove == len )
441 return wxString(wxELLIPSE_REPLACEMENT) + curLine[len-1];
442
443 break;
444 }
445
446 case wxELLIPSIZE_MIDDLE:
447 {
448 // NOTE: the following piece of code works also when len == 1
449
450 // start the removal process from the middle of the string
451 // i.e. separe the string in three parts:
452 // - the first one to preserve, valid range [0;initialCharToRemove-1] or the empty range if initialCharToRemove==0
453 // - the second one to remove, valid range [initialCharToRemove;endCharToRemove]
454 // - the third one to preserve, valid range [endCharToRemove+1;len-1] or the empty range if endCharToRemove==len-1
455 // NOTE: empty range != range [0;0] since the range [0;0] contains 1 character (the zero-th one)!
456
457 calc.Init(len/2, 0);
458
459 bool removeFromStart = true;
460
461 while ( !calc.IsShortEnough() )
462 {
463 const bool canRemoveFromStart = calc.GetFirstRemoved() > 0;
464 const bool canRemoveFromEnd = calc.GetLastRemoved() < len - 1;
465
466 if ( !canRemoveFromStart && !canRemoveFromEnd )
467 {
468 // we need to remove all the characters of the string!
469 break;
470 }
471
472 // Remove from the beginning in even steps and from the end
473 // in odd steps, unless we exhausted one side already:
474 removeFromStart = !removeFromStart;
475 if ( removeFromStart && !canRemoveFromStart )
476 removeFromStart = false;
477 else if ( !removeFromStart && !canRemoveFromEnd )
478 removeFromStart = true;
479
480 if ( removeFromStart )
481 calc.RemoveFromStart();
482 else
483 calc.RemoveFromEnd();
484 }
485
486 // Always show at least one character of the string.
487 // Additionally, if there's only one character left, prefer
488 // "a..." to "...a":
489 if ( calc.m_nCharsToRemove == len ||
490 calc.m_nCharsToRemove == len - 1 )
491 {
492 return curLine[0] + wxString(wxELLIPSE_REPLACEMENT);
493 }
494 }
495 break;
496
497 case wxELLIPSIZE_END:
498 {
499 calc.Init(len - 1, 1);
500 while ( !calc.IsShortEnough() )
501 calc.RemoveFromStart();
502
503 // always show at least one character of the string:
504 if ( calc.m_nCharsToRemove == len )
505 return curLine[0] + wxString(wxELLIPSE_REPLACEMENT);
506
507 break;
508 }
509
510 case wxELLIPSIZE_NONE:
511 default:
512 wxFAIL_MSG("invalid ellipsize mode");
513 return curLine;
514 }
515
516 return calc.GetEllipsizedText();
517}
518
519/* static */
520wxString wxControlBase::Ellipsize(const wxString& label, const wxDC& dc,
521 wxEllipsizeMode mode, int maxFinalWidth,
522 int flags)
523{
524 wxString ret;
525
526 // these cannot be cached between different Ellipsize() calls as they can
527 // change because of e.g. a font change; however we calculate them only once
528 // when ellipsizing multiline labels:
529 int replacementWidth = dc.GetTextExtent(wxELLIPSE_REPLACEMENT).GetWidth();
530
531 // NB: we must handle correctly labels with newlines:
532 wxString curLine;
533 for ( wxString::const_iterator pc = label.begin(); ; ++pc )
534 {
535 if ( pc == label.end() || *pc == wxS('\n') )
536 {
537 curLine = DoEllipsizeSingleLine(curLine, dc, mode, maxFinalWidth,
538 replacementWidth);
539
540 // add this (ellipsized) row to the rest of the label
541 ret << curLine;
542 if ( pc == label.end() )
543 break;
544
545 ret << *pc;
546 curLine.clear();
547 }
548 // we need to remove mnemonics from the label for correct calculations
549 else if ( *pc == wxS('&') && (flags & wxELLIPSIZE_FLAGS_PROCESS_MNEMONICS) )
550 {
551 // pc+1 is safe: at worst we'll be at end()
552 wxString::const_iterator next = pc + 1;
553 if ( next != label.end() && *next == wxS('&') )
554 curLine += wxS('&'); // && becomes &
555 //else: remove this ampersand
556 }
557 // we need also to expand tabs to properly calc their size
558 else if ( *pc == wxS('\t') && (flags & wxELLIPSIZE_FLAGS_EXPAND_TABS) )
559 {
560 // Windows natively expands the TABs to 6 spaces. Do the same:
561 curLine += wxS(" ");
562 }
563 else
564 {
565 curLine += *pc;
566 }
567 }
568
569 return ret;
570}
571
572// ----------------------------------------------------------------------------
573// wxStaticBitmap
574// ----------------------------------------------------------------------------
575
576#if wxUSE_STATBMP
577
578wxStaticBitmapBase::~wxStaticBitmapBase()
579{
580 // this destructor is required for Darwin
581}
582
583wxSize wxStaticBitmapBase::DoGetBestSize() const
584{
585 wxSize best;
586 wxBitmap bmp = GetBitmap();
587 if ( bmp.IsOk() )
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