#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,
if (maxFinalWidthPx <= 0)
return wxEmptyString;
- wxArrayInt charOffsetsPx;
size_t len = curLine.length();
- if (len <= 1 ||
- !dc.GetPartialTextExtents(curLine, charOffsetsPx))
+ if (len <= 1 )
return curLine;
- wxASSERT(charOffsetsPx.GetCount() == len);
-
- // 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
- size_t totalWidthPx = charOffsetsPx.Last();
- if ( totalWidthPx <= (size_t)maxFinalWidthPx )
- return curLine; // we don't need to do any ellipsization!
+ EllipsizeCalculator calc(curLine, dc, maxFinalWidthPx, replacementWidthPx);
- int excessPx = wxMin(totalWidthPx - maxFinalWidthPx +
- replacementWidthPx,
- totalWidthPx);
- wxASSERT(excessPx>0); // excessPx should be in the [1;totalWidthPx] range
-
- // 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)
+ if ( !calc.IsOk() )
+ return curLine;
- size_t initialCharToRemove, // index of first character to erase, valid range is [0;len-1]
- nCharsToRemove; // how many chars do we need to erase? valid range is [1;len-initialCharToRemove]
+ if ( calc.EllipsizationNotNeeded() )
+ return curLine;
// let's compute the range of characters to remove depending on the ellipsization mode:
switch (mode)
{
case wxELLIPSIZE_START:
- initialCharToRemove = 0;
- for ( nCharsToRemove = 1;
- nCharsToRemove < len && charOffsetsPx[nCharsToRemove-1] < excessPx;
- nCharsToRemove++ )
- ;
- break;
+ {
+ calc.Init(0, 1);
+ while ( !calc.IsShortEnough() )
+ calc.RemoveFromEnd();
+ break;
+ }
case wxELLIPSIZE_MIDDLE:
{
// - 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)!
- initialCharToRemove = len/2; // index of the last character to remove; valid range is [0;len-1]
- size_t endCharToRemove = initialCharToRemove - 1; // initial removal range is empty
- int removedPx = 0;
+ calc.Init(len/2, 0);
+
bool removeFromStart = true;
- for ( ; removedPx < excessPx; )
+
+ while ( !calc.IsShortEnough() )
{
- const bool canRemoveFromStart = initialCharToRemove > 0;
- const bool canRemoveFromEnd = endCharToRemove < len - 1;
+ const bool canRemoveFromStart = calc.GetFirstRemoved() > 0;
+ const bool canRemoveFromEnd = calc.GetLastRemoved() < len - 1;
if ( !canRemoveFromStart && !canRemoveFromEnd )
{
removeFromStart = true;
if ( removeFromStart )
- {
- // try to remove the last character of the first part of the string
-
- // width of the (initialCharToRemove-1)-th character
- int widthPx;
- if (initialCharToRemove >= 2)
- widthPx = charOffsetsPx[initialCharToRemove-1] - charOffsetsPx[initialCharToRemove-2];
- else
- widthPx = charOffsetsPx[initialCharToRemove-1];
- // the (initialCharToRemove-1)-th character is the first char of the string
-
- wxASSERT(widthPx >= 0); // widthPx is zero for e.g. tab characters
-
- // mark the (initialCharToRemove-1)-th character as removable
- initialCharToRemove--;
- removedPx += widthPx;
-
- continue; // don't remove anything else
- }
- else // !removeFromStart
- {
- // try to remove the first character of the last part of the string
-
- // width of the (endCharToRemove+1)-th character
- int widthPx = charOffsetsPx[endCharToRemove+1] -
- charOffsetsPx[endCharToRemove];
-
- wxASSERT(widthPx >= 0); // widthPx is zero for e.g. tab characters
-
- // mark the (endCharToRemove+1)-th character as removable
- endCharToRemove++;
- removedPx += widthPx;
-
- continue; // don't remove anything else
- }
+ calc.RemoveFromStart();
+ else
+ calc.RemoveFromEnd();
}
-
- nCharsToRemove = endCharToRemove - initialCharToRemove + 1;
}
break;
case wxELLIPSIZE_END:
{
- int maxWidthPx = totalWidthPx - excessPx;
-
- // go backward from the end of the string toward the start
- for ( initialCharToRemove = len-1;
- initialCharToRemove > 0 && charOffsetsPx[initialCharToRemove-1] > maxWidthPx;
- initialCharToRemove-- )
- ;
- nCharsToRemove = len - initialCharToRemove;
+ calc.Init(len - 1, 1);
+ while ( !calc.IsShortEnough() )
+ calc.RemoveFromStart();
+ break;
}
- break;
case wxELLIPSIZE_NONE:
default:
return curLine;
}
- wxASSERT(initialCharToRemove <= len-1); // see valid range for initialCharToRemove above
- wxASSERT(nCharsToRemove >= 1 && nCharsToRemove <= len-initialCharToRemove); // see valid range for nCharsToRemove above
-
- // erase nCharsToRemove characters after initialCharToRemove (included);
- // e.g. if we have the string "foobar" (len = 6)
- // ^
- // \--- initialCharToRemove = 2
- // and nCharsToRemove = 2, then we get "foar"
- wxString ret(curLine);
- ret.erase(initialCharToRemove, nCharsToRemove);
-
- ret.insert(initialCharToRemove, wxELLIPSE_REPLACEMENT);
-
- return ret;
+ return calc.GetEllipsizedText();
}
/* static */