X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/7d27b6263c7400185e74f76870c68e5886de583f..9ba371fa05c50f65e887ca894518f293b4f88c25:/src/common/ctrlcmn.cpp diff --git a/src/common/ctrlcmn.cpp b/src/common/ctrlcmn.cpp index b9e57a8eec..1382c3102c 100644 --- a/src/common/ctrlcmn.cpp +++ b/src/common/ctrlcmn.cpp @@ -29,13 +29,17 @@ #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"; // ============================================================================ @@ -86,13 +90,6 @@ bool wxControlBase::CreateControl(wxWindowBase *parent, return true; } -/* static */ -wxString wxControlBase::GetLabelText(const wxString& label) -{ - // we don't want strip the TABs here, just the mnemonics - return wxStripMenuCodes(label, wxStrip_Mnemonics); -} - void wxControlBase::Command(wxCommandEvent& event) { (void)GetEventHandler()->ProcessEvent(event); @@ -100,7 +97,7 @@ void wxControlBase::Command(wxCommandEvent& event) void wxControlBase::InitCommandEvent(wxCommandEvent& event) const { - event.SetEventObject((wxControlBase *)this); // const_cast + event.SetEventObject(const_cast(this)); // event.SetId(GetId()); -- this is usuall done in the event ctor @@ -152,19 +149,35 @@ void wxControlBase::DoUpdateWindowUI(wxUpdateUIEvent& event) #endif // wxUSE_RADIOBTN } +/* 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 = _T('&'); + static const wxChar MNEMONIC_PREFIX = wxT('&'); if ( labelOnly ) { @@ -189,7 +202,7 @@ int wxControlBase::FindAccelIndex(const wxString& label, wxString *labelOnly) } else { - wxFAIL_MSG(_T("duplicate accel char in control label")); + wxFAIL_MSG(wxT("duplicate accel char in control label")); } } } @@ -203,137 +216,320 @@ int wxControlBase::FindAccelIndex(const wxString& label, wxString *labelOnly) return indexAccel; } -#define wxELLIPSE_REPLACEMENT wxT("...") +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::Ellipsize(const wxString& label, const wxDC& dc, - wxEllipsizeMode mode, int maxFinalWidth) +wxString wxControlBase::RemoveMarkup(const wxString& markup) { - wxArrayInt charOffsets; - wxString ret; + return wxMarkupParser::Strip(markup); +} - // these cannot be cached as they can change because of e.g. a font change - int replacementWidth = dc.GetTextExtent(wxELLIPSE_REPLACEMENT).GetWidth(); - int marginWidth = dc.GetCharWidth()*2; +bool wxControlBase::DoSetLabelMarkup(const wxString& markup) +{ + const wxString label = RemoveMarkup(markup); + if ( label.empty() && !markup.empty() ) + return false; - // NB: we must handle correctly labels with newlines: - wxString curLine; - wxSize reqsize; - size_t len; - for ( wxString::const_iterator pc = label.begin(); ; ++pc ) + 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 { - if ( pc == label.end() || *pc == _T('\n') ) + // 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 ) { - len = curLine.length(); - if (len > 0 && - dc.GetPartialTextExtents(curLine, charOffsets)) + 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: { - wxASSERT(charOffsets.GetCount() == len); + 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; + } - size_t totalWidth = charOffsets.Last(); - if ( totalWidth > (size_t)maxFinalWidth ) + 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() ) { - // we need to ellipsize this row - int excessPixels = totalWidth - maxFinalWidth + - replacementWidth + - marginWidth; // security margin (NEEDED!) - - // remove characters in excess - size_t initialChar, // index of first char to erase - nChars; // how many chars do we need to erase? - if (mode == wxELLIPSIZE_START) - { - initialChar = 0; - for (nChars=0; - nChars < len && charOffsets[nChars] < excessPixels; - nChars++) - ; - } - else if (mode == wxELLIPSIZE_MIDDLE) - { - // the start & end of the removed span of chars - initialChar = len/2; - size_t endChar = len/2; - - int removed = 0; - for ( ; removed < excessPixels; ) - { - if (initialChar > 0) - { - // width of the initialChar-th character - int width = charOffsets[initialChar] - - charOffsets[initialChar-1]; - - // remove the initialChar-th character - removed += width; - initialChar--; - } - - if (endChar < len - 1 && - removed < excessPixels) - { - // width of the (endChar+1)-th character - int width = charOffsets[endChar+1] - - charOffsets[endChar]; - - // remove the endChar-th character - removed += width; - endChar++; - } - - if (initialChar == 0 && endChar == len-1) - { - nChars = len+1; - break; - } - } - - initialChar++; - nChars = endChar - initialChar + 1; - } - else - { - wxASSERT(mode == wxELLIPSIZE_END); - wxASSERT(len > 0); - - int maxWidth = totalWidth - excessPixels; - for (initialChar=0; - initialChar < len && - charOffsets[initialChar] < maxWidth; - initialChar++) - ; - - if (initialChar == 0) - { - nChars = len; - } - else - { - initialChar--; // go back one character - nChars = len - initialChar; - } - } + const bool canRemoveFromStart = calc.GetFirstRemoved() > 0; + const bool canRemoveFromEnd = calc.GetLastRemoved() < len - 1; - if (nChars > len) + if ( !canRemoveFromStart && !canRemoveFromEnd ) { - // need to remove the entire row! - curLine.clear(); + // we need to remove all the characters of the string! + break; } - else - { - // erase nChars characters after initialChar (included): - curLine.erase(initialChar, nChars+1); - // if there is space for the replacement dots, add them - if (maxFinalWidth > replacementWidth) - curLine.insert(initialChar, wxELLIPSE_REPLACEMENT); - } + // 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 everything was ok, we should have shortened this line - // enough to make it fit in maxFinalWidth: - wxASSERT(dc.GetTextExtent(curLine).GetWidth() < maxFinalWidth); + 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) +{ + 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; @@ -349,19 +545,19 @@ wxString wxControlBase::Ellipsize(const wxString& label, const wxDC& dc, } } // we need to remove mnemonics from the label for correct calculations - else if ( *pc == _T('&') ) + 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 == _T('&') ) - curLine += _T('&'); // && becomes & + 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 == _T('\t') ) + else if ( *pc == wxS('\t') && (flags & wxELLIPSIZE_FLAGS_EXPAND_TABS) ) { // Windows natively expands the TABs to 6 spaces. Do the same: - curLine += wxT(" "); + curLine += wxS(" "); } else { @@ -369,16 +565,12 @@ wxString wxControlBase::Ellipsize(const wxString& label, const wxDC& dc, } } - // this return would generate a + // this return would generate a // warning C4702: unreachable code - // with MSVC since the function always exits from inside the loop + // with MSVC since the function always exits from inside the loop //return ret; } -wxBorder wxControlBase::GetDefaultBorder() const -{ - return wxBORDER_THEME; -} // ---------------------------------------------------------------------------- @@ -396,7 +588,7 @@ wxSize wxStaticBitmapBase::DoGetBestSize() const { wxSize best; wxBitmap bmp = GetBitmap(); - if ( bmp.Ok() ) + if ( bmp.IsOk() ) best = wxSize(bmp.GetWidth(), bmp.GetHeight()); else // this is completely arbitrary