///////////////////////////////////////////////////////////////////////////// // Name: src/common/ctrlcmn.cpp // Purpose: wxControl common interface // Author: Vadim Zeitlin // Modified by: // Created: 26.07.99 // Copyright: (c) wxWidgets team // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_CONTROLS #include "wx/control.h" #ifndef WX_PRECOMP #include "wx/dc.h" #include "wx/log.h" #include "wx/radiobut.h" #include "wx/statbmp.h" #include "wx/bitmap.h" #include "wx/utils.h" // for wxStripMenuCodes() #include "wx/settings.h" #endif #include "wx/private/markupparser.h" const char wxControlNameStr[] = "control"; // ============================================================================ // implementation // ============================================================================ wxControlBase::~wxControlBase() { // this destructor is required for Darwin } bool wxControlBase::Create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxValidator& wxVALIDATOR_PARAM(validator), const wxString &name) { bool ret = wxWindow::Create(parent, id, pos, size, style, name); #if wxUSE_VALIDATORS if ( ret ) SetValidator(validator); #endif // wxUSE_VALIDATORS return ret; } bool wxControlBase::CreateControl(wxWindowBase *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) { // even if it's possible to create controls without parents in some port, // it should surely be discouraged because it doesn't work at all under // Windows wxCHECK_MSG( parent, false, wxT("all controls must have parents") ); if ( !CreateBase(parent, id, pos, size, style, validator, name) ) return false; parent->AddChild(this); return true; } void wxControlBase::Command(wxCommandEvent& event) { (void)GetEventHandler()->ProcessEvent(event); } void wxControlBase::InitCommandEvent(wxCommandEvent& event) const { event.SetEventObject(const_cast(this)); // event.SetId(GetId()); -- this is usuall done in the event ctor switch ( m_clientDataType ) { case wxClientData_Void: event.SetClientData(GetClientData()); break; case wxClientData_Object: event.SetClientObject(GetClientObject()); break; case wxClientData_None: // nothing to do ; } } bool wxControlBase::SetFont(const wxFont& font) { InvalidateBestSize(); return wxWindow::SetFont(font); } // wxControl-specific processing after processing the update event void wxControlBase::DoUpdateWindowUI(wxUpdateUIEvent& event) { // call inherited wxWindowBase::DoUpdateWindowUI(event); // update label if ( event.GetSetText() ) { if ( event.GetText() != GetLabel() ) SetLabel(event.GetText()); } // Unfortunately we don't yet have common base class for // wxRadioButton, so we handle updates of radiobuttons here. // TODO: If once wxRadioButtonBase will exist, move this code there. #if wxUSE_RADIOBTN if ( event.GetSetChecked() ) { wxRadioButton *radiobtn = wxDynamicCastThis(wxRadioButton); if ( radiobtn ) radiobtn->SetValue(event.GetChecked()); } #endif // wxUSE_RADIOBTN } wxSize wxControlBase::DoGetSizeFromTextSize(int WXUNUSED(xlen), int WXUNUSED(ylen)) const { return wxSize(-1, -1); } /* static */ wxString wxControlBase::GetLabelText(const wxString& label) { // we don't want strip the TABs here, just the mnemonics return wxStripMenuCodes(label, wxStrip_Mnemonics); } /* static */ wxString wxControlBase::RemoveMnemonics(const wxString& str) { // we don't want strip the TABs here, just the mnemonics return wxStripMenuCodes(str, wxStrip_Mnemonics); } /* static */ wxString wxControlBase::EscapeMnemonics(const wxString& text) { wxString label(text); label.Replace("&", "&&"); return label; } /* static */ int wxControlBase::FindAccelIndex(const wxString& label, wxString *labelOnly) { // the character following MNEMONIC_PREFIX is the accelerator for this // control unless it is MNEMONIC_PREFIX too - this allows to insert // literal MNEMONIC_PREFIX chars into the label static const wxChar MNEMONIC_PREFIX = wxT('&'); if ( labelOnly ) { labelOnly->Empty(); labelOnly->Alloc(label.length()); } int indexAccel = -1; for ( wxString::const_iterator pc = label.begin(); pc != label.end(); ++pc ) { if ( *pc == MNEMONIC_PREFIX ) { ++pc; // skip it if ( pc == label.end() ) break; else if ( *pc != MNEMONIC_PREFIX ) { if ( indexAccel == -1 ) { // remember it (-1 is for MNEMONIC_PREFIX itself indexAccel = pc - label.begin() - 1; } else { wxFAIL_MSG(wxT("duplicate accel char in control label")); } } } if ( labelOnly ) { *labelOnly += *pc; } } return indexAccel; } wxBorder wxControlBase::GetDefaultBorder() const { return wxBORDER_THEME; } /* static */ wxVisualAttributes wxControlBase::GetCompositeControlsDefaultAttributes(wxWindowVariant WXUNUSED(variant)) { wxVisualAttributes attrs; attrs.font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); attrs.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); attrs.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); return attrs; } // ---------------------------------------------------------------------------- // wxControl markup support // ---------------------------------------------------------------------------- #if wxUSE_MARKUP /* static */ wxString wxControlBase::RemoveMarkup(const wxString& markup) { return wxMarkupParser::Strip(markup); } bool wxControlBase::DoSetLabelMarkup(const wxString& markup) { const wxString label = RemoveMarkup(markup); if ( label.empty() && !markup.empty() ) return false; SetLabel(label); return true; } #endif // wxUSE_MARKUP // ---------------------------------------------------------------------------- // wxControlBase - ellipsization code // ---------------------------------------------------------------------------- #define wxELLIPSE_REPLACEMENT wxS("...") namespace { struct EllipsizeCalculator { EllipsizeCalculator(const wxString& s, const wxDC& dc, int maxFinalWidthPx, int replacementWidthPx) : m_initialCharToRemove(0), m_nCharsToRemove(0), m_outputNeedsUpdate(true), m_str(s), m_dc(dc), m_maxFinalWidthPx(maxFinalWidthPx), m_replacementWidthPx(replacementWidthPx) { m_isOk = dc.GetPartialTextExtents(s, m_charOffsetsPx); wxASSERT( m_charOffsetsPx.GetCount() == s.length() ); } bool IsOk() const { return m_isOk; } bool EllipsizationNotNeeded() const { // NOTE: charOffsetsPx[n] is the width in pixels of the first n characters (with the last one INCLUDED) // thus charOffsetsPx[len-1] is the total width of the string return m_charOffsetsPx.Last() <= m_maxFinalWidthPx; } void Init(size_t initialCharToRemove, size_t nCharsToRemove) { m_initialCharToRemove = initialCharToRemove; m_nCharsToRemove = nCharsToRemove; } void RemoveFromEnd() { m_nCharsToRemove++; } void RemoveFromStart() { m_initialCharToRemove--; m_nCharsToRemove++; } size_t GetFirstRemoved() const { return m_initialCharToRemove; } size_t GetLastRemoved() const { return m_initialCharToRemove + m_nCharsToRemove - 1; } const wxString& GetEllipsizedText() { if ( m_outputNeedsUpdate ) { wxASSERT(m_initialCharToRemove <= m_str.length() - 1); // see valid range for initialCharToRemove above wxASSERT(m_nCharsToRemove >= 1 && m_nCharsToRemove <= m_str.length() - m_initialCharToRemove); // see valid range for nCharsToRemove above // erase m_nCharsToRemove characters after m_initialCharToRemove (included); // e.g. if we have the string "foobar" (len = 6) // ^ // \--- m_initialCharToRemove = 2 // and m_nCharsToRemove = 2, then we get "foar" m_output = m_str; m_output.replace(m_initialCharToRemove, m_nCharsToRemove, wxELLIPSE_REPLACEMENT); } return m_output; } bool IsShortEnough() { if ( m_nCharsToRemove == m_str.length() ) return true; // that's the best we could do // Width calculation using partial extents is just an inaccurate // estimate: partial extents have sub-pixel precision and are rounded // by GetPartialTextExtents(); replacing part of the string with "..." // may change them too thanks to changes in ligatures, kerning etc. // // The correct algorithm would be to call GetTextExtent() in every step // of ellipsization, but that would be too expensive, especially when // the difference is just a few pixels. So we use partial extents to // estimate string width and only verify it with GetTextExtent() when // it looks good. int estimatedWidth = m_replacementWidthPx; // length of "..." // length of text before the removed part: if ( m_initialCharToRemove > 0 ) estimatedWidth += m_charOffsetsPx[m_initialCharToRemove - 1]; // length of text after the removed part: if ( GetLastRemoved() < m_str.length() ) estimatedWidth += m_charOffsetsPx.Last() - m_charOffsetsPx[GetLastRemoved()]; if ( estimatedWidth > m_maxFinalWidthPx ) return false; return m_dc.GetTextExtent(GetEllipsizedText()).GetWidth() <= m_maxFinalWidthPx; } // calculation state: // REMEMBER: indexes inside the string have a valid range of [0;len-1] if not otherwise constrained // lengths/counts of characters (e.g. nCharsToRemove) have a // valid range of [0;len] if not otherwise constrained // NOTE: since this point we know we have for sure a non-empty string from which we need // to remove _at least_ one character (thus nCharsToRemove below is constrained to be >= 1) // index of first character to erase, valid range is [0;len-1]: size_t m_initialCharToRemove; // how many chars do we need to erase? valid range is [0;len-m_initialCharToRemove] size_t m_nCharsToRemove; wxString m_output; bool m_outputNeedsUpdate; // inputs: wxString m_str; const wxDC& m_dc; int m_maxFinalWidthPx; int m_replacementWidthPx; wxArrayInt m_charOffsetsPx; bool m_isOk; }; } // anonymous namespace /* static and protected */ wxString wxControlBase::DoEllipsizeSingleLine(const wxString& curLine, const wxDC& dc, wxEllipsizeMode mode, int maxFinalWidthPx, int replacementWidthPx) { wxASSERT_MSG(replacementWidthPx > 0, "Invalid parameters"); wxASSERT_LEVEL_2_MSG(!curLine.Contains('\n'), "Use Ellipsize() instead!"); wxASSERT_MSG( mode != wxELLIPSIZE_NONE, "shouldn't be called at all then" ); // NOTE: this function assumes that any mnemonic/tab character has already // been handled if it was necessary to handle them (see Ellipsize()) if (maxFinalWidthPx <= 0) return wxEmptyString; size_t len = curLine.length(); if (len <= 1 ) return curLine; EllipsizeCalculator calc(curLine, dc, maxFinalWidthPx, replacementWidthPx); if ( !calc.IsOk() ) return curLine; if ( calc.EllipsizationNotNeeded() ) return curLine; // let's compute the range of characters to remove depending on the ellipsization mode: switch (mode) { case wxELLIPSIZE_START: { calc.Init(0, 1); while ( !calc.IsShortEnough() ) calc.RemoveFromEnd(); // always show at least one character of the string: if ( calc.m_nCharsToRemove == len ) return wxString(wxELLIPSE_REPLACEMENT) + curLine[len-1]; break; } case wxELLIPSIZE_MIDDLE: { // NOTE: the following piece of code works also when len == 1 // start the removal process from the middle of the string // i.e. separe the string in three parts: // - the first one to preserve, valid range [0;initialCharToRemove-1] or the empty range if initialCharToRemove==0 // - the second one to remove, valid range [initialCharToRemove;endCharToRemove] // - the third one to preserve, valid range [endCharToRemove+1;len-1] or the empty range if endCharToRemove==len-1 // NOTE: empty range != range [0;0] since the range [0;0] contains 1 character (the zero-th one)! calc.Init(len/2, 0); bool removeFromStart = true; while ( !calc.IsShortEnough() ) { const bool canRemoveFromStart = calc.GetFirstRemoved() > 0; const bool canRemoveFromEnd = calc.GetLastRemoved() < len - 1; if ( !canRemoveFromStart && !canRemoveFromEnd ) { // we need to remove all the characters of the string! break; } // Remove from the beginning in even steps and from the end // in odd steps, unless we exhausted one side already: removeFromStart = !removeFromStart; if ( removeFromStart && !canRemoveFromStart ) removeFromStart = false; else if ( !removeFromStart && !canRemoveFromEnd ) removeFromStart = true; if ( removeFromStart ) calc.RemoveFromStart(); else calc.RemoveFromEnd(); } // Always show at least one character of the string. // Additionally, if there's only one character left, prefer // "a..." to "...a": if ( calc.m_nCharsToRemove == len || calc.m_nCharsToRemove == len - 1 ) { return curLine[0] + wxString(wxELLIPSE_REPLACEMENT); } } break; case wxELLIPSIZE_END: { calc.Init(len - 1, 1); while ( !calc.IsShortEnough() ) calc.RemoveFromStart(); // always show at least one character of the string: if ( calc.m_nCharsToRemove == len ) return curLine[0] + wxString(wxELLIPSE_REPLACEMENT); break; } case wxELLIPSIZE_NONE: default: wxFAIL_MSG("invalid ellipsize mode"); return curLine; } return calc.GetEllipsizedText(); } /* static */ wxString wxControlBase::Ellipsize(const wxString& label, const wxDC& dc, wxEllipsizeMode mode, int maxFinalWidth, int flags) { if (mode == wxELLIPSIZE_NONE) return label; wxString ret; // these cannot be cached between different Ellipsize() calls as they can // change because of e.g. a font change; however we calculate them only once // when ellipsizing multiline labels: int replacementWidth = dc.GetTextExtent(wxELLIPSE_REPLACEMENT).GetWidth(); // NB: we must handle correctly labels with newlines: wxString curLine; for ( wxString::const_iterator pc = label.begin(); ; ++pc ) { if ( pc == label.end() || *pc == wxS('\n') ) { curLine = DoEllipsizeSingleLine(curLine, dc, mode, maxFinalWidth, replacementWidth); // add this (ellipsized) row to the rest of the label ret << curLine; if ( pc == label.end() ) break; ret << *pc; curLine.clear(); } // we need to remove mnemonics from the label for correct calculations else if ( *pc == wxS('&') && (flags & wxELLIPSIZE_FLAGS_PROCESS_MNEMONICS) ) { // pc+1 is safe: at worst we'll be at end() wxString::const_iterator next = pc + 1; if ( next != label.end() && *next == wxS('&') ) curLine += wxS('&'); // && becomes & //else: remove this ampersand } // we need also to expand tabs to properly calc their size else if ( *pc == wxS('\t') && (flags & wxELLIPSIZE_FLAGS_EXPAND_TABS) ) { // Windows natively expands the TABs to 6 spaces. Do the same: curLine += wxS(" "); } else { curLine += *pc; } } return ret; } // ---------------------------------------------------------------------------- // wxStaticBitmap // ---------------------------------------------------------------------------- #if wxUSE_STATBMP wxStaticBitmapBase::~wxStaticBitmapBase() { // this destructor is required for Darwin } wxSize wxStaticBitmapBase::DoGetBestSize() const { wxSize best; wxBitmap bmp = GetBitmap(); if ( bmp.IsOk() ) best = bmp.GetScaledSize(); else // this is completely arbitrary best = wxSize(16, 16); CacheBestSize(best); return best; } #endif // wxUSE_STATBMP #endif // wxUSE_CONTROLS