/**
Replaces parts of the @a label string with ellipsis, if needed, so
that it doesn't exceed @a maxWidth.
+
+ Note that this functions is guaranteed to always returns a string
+ whose rendering on the given DC takes less than @a maxWidth pixels
+ in horizontal.
@param label
The string to ellipsize
The DC used to retrieve the character widths through the
wxDC::GetPartialTextExtents() function.
@param mode
- The ellipsization modes. See ::wxEllipsizeMode.
+ The ellipsization mode. This is the setting which determines
+ which part of the string should be replaced by the ellipsis.
+ See ::wxEllipsizeMode enumeration values for more info.
@param maxWidth
The maximum width of the returned string in pixels.
+ This argument determines how much characters of the string need to
+ be removed (and replaced by ellipsis).
@param flags
- One or more of the ::wxEllipsize
+ One or more of the ::wxEllipsizeFlags enumeration values combined.
*/
static wxString Ellipsize(const wxString& label, const wxDC& dc,
wxEllipsizeMode mode, int maxWidth,
{
wxASSERT_MSG(replacementWidthPx > 0 && marginWidthPx > 0,
"Invalid parameters");
- wxASSERT_MSG(!curLine.Contains('\n'),
- "Use Ellipsize() instead!");
+ wxASSERT_LEVEL_2_MSG(!curLine.Contains('\n'),
+ "Use Ellipsize() instead!");
wxASSERT_MSG( mode != wxELLIPSIZE_NONE, "shouldn't be called at all then" );
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!
- int excessPx = totalWidthPx - maxFinalWidthPx +
- replacementWidthPx +
- marginWidthPx; // security margin (NEEDED!)
- wxASSERT(excessPx>0);
+ int excessPx = wxMin(totalWidthPx - maxFinalWidthPx +
+ replacementWidthPx +
+ marginWidthPx, // security margin
+ totalWidthPx);
+ wxASSERT(excessPx>0); // excessPx should be in the [1;totalWidthPx] range
- // remove characters in excess
- size_t initialCharToRemove, // index of first char to erase
- nCharsToRemove; // how many chars do we need to erase?
+ // 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)
+ 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]
+
+ // let's compute the range of characters to remove depending on the ellipsization mode:
switch (mode)
{
case wxELLIPSIZE_START:
initialCharToRemove = 0;
- for ( nCharsToRemove=0;
- nCharsToRemove < len && charOffsetsPx[nCharsToRemove] < excessPx;
+ for ( nCharsToRemove = 1;
+ nCharsToRemove < len && charOffsetsPx[nCharsToRemove-1] < excessPx;
nCharsToRemove++ )
;
break;
case wxELLIPSIZE_MIDDLE:
{
- // the start & end of the removed span of chars
+ // 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)!
initialCharToRemove = len/2;
- size_t endChar = len/2;
+ size_t endCharToRemove = len/2; // index of the last character to remove; valid range is [0;len-1]
int removedPx = 0;
for ( ; removedPx < excessPx; )
{
+ // try to remove the last character of the first part of the string
if (initialCharToRemove > 0)
{
- // widthPx of the initialCharToRemove-th character
- int widthPx = charOffsetsPx[initialCharToRemove] -
- charOffsetsPx[initialCharToRemove-1];
-
- // remove the initialCharToRemove-th character
- removedPx += widthPx;
+ // 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;
}
- if (endChar < len - 1 &&
+ // try to remove the first character of the last part of the string
+ if (endCharToRemove < len - 1 &&
removedPx < excessPx)
{
- // widthPx of the (endChar+1)-th character
- int widthPx = charOffsetsPx[endChar+1] -
- charOffsetsPx[endChar];
+ // 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
- // remove the endChar-th character
+ // mark the (endCharToRemove+1)-th character as removable
+ endCharToRemove++;
removedPx += widthPx;
- endChar++;
}
- if (initialCharToRemove == 0 && endChar == len-1)
+ if (initialCharToRemove == 0 && endCharToRemove == len-1)
{
- nCharsToRemove = len+1;
+ // we need to remove all the characters of the string!
break;
}
}
- initialCharToRemove++;
- nCharsToRemove = endChar - initialCharToRemove + 1;
+ nCharsToRemove = endCharToRemove - initialCharToRemove + 1;
}
break;
case wxELLIPSIZE_END:
{
- wxASSERT(len > 0);
-
int maxWidthPx = totalWidthPx - excessPx;
- for ( initialCharToRemove = 0;
- initialCharToRemove < len && charOffsetsPx[initialCharToRemove] < maxWidthPx;
- initialCharToRemove++ )
- ;
- if (initialCharToRemove == 0)
- {
- nCharsToRemove = len;
- }
- else
- {
- //initialCharToRemove--; // go back one character
- nCharsToRemove = len - initialCharToRemove;
- }
+ // 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;
}
break;
return curLine;
}
+ wxASSERT(initialCharToRemove >= 0 && 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);
- if (nCharsToRemove >= len)
- {
- // need to remove the entire row!
- ret.clear();
- }
- else
- {
- // erase nCharsToRemove characters after initialCharToRemove (included):
- ret.erase(initialCharToRemove, nCharsToRemove+1);
+ ret.erase(initialCharToRemove, nCharsToRemove);
- // if there is space for the replacement dots, add them
- if (maxFinalWidthPx > replacementWidthPx)
- ret.insert(initialCharToRemove, wxELLIPSE_REPLACEMENT);
- }
+ int removedPx;
+ if (initialCharToRemove >= 1)
+ removedPx = charOffsetsPx[initialCharToRemove+nCharsToRemove-1] - charOffsetsPx[initialCharToRemove-1];
+ else
+ removedPx = charOffsetsPx[initialCharToRemove+nCharsToRemove-1];
+ wxASSERT(removedPx >= excessPx);
+
+ // if there is space for the replacement dots, add them
+ if ((int)totalWidthPx-removedPx+replacementWidthPx < maxFinalWidthPx)
+ ret.insert(initialCharToRemove, wxELLIPSE_REPLACEMENT);
// if everything was ok, we should have shortened this line
// enough to make it fit in maxFinalWidthPx:
- wxASSERT(dc.GetTextExtent(ret).GetWidth() < maxFinalWidthPx);
+ wxASSERT_LEVEL_2(dc.GetTextExtent(ret).GetWidth() <= maxFinalWidthPx);
return ret;
}
test_gui_size.o \
test_gui_point.o \
test_gui_colour.o \
+ test_gui_ellipsization.o \
test_gui_measuring.o \
test_gui_config.o \
test_gui_comboboxtest.o \
test_gui_colour.o: $(srcdir)/graphics/colour.cpp $(TEST_GUI_ODEP)
$(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/graphics/colour.cpp
+test_gui_ellipsization.o: $(srcdir)/graphics/ellipsization.cpp $(TEST_GUI_ODEP)
+ $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/graphics/ellipsization.cpp
+
test_gui_measuring.o: $(srcdir)/graphics/measuring.cpp $(TEST_GUI_ODEP)
$(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/graphics/measuring.cpp
--- /dev/null
+///////////////////////////////////////////////////////////////////////////////\r
+// Name: tests/graphics/ellipsization.cpp\r
+// Purpose: wxControlBase::*Ellipsize* unit test\r
+// Author: Francesco Montorsi\r
+// Created: 2010-03-10\r
+// RCS-ID: $Id$\r
+// Copyright: (c) 2010 Francesco Montorsi\r
+///////////////////////////////////////////////////////////////////////////////\r
+\r
+// ----------------------------------------------------------------------------\r
+// headers\r
+// ----------------------------------------------------------------------------\r
+\r
+#include "testprec.h"\r
+\r
+#ifdef __BORLANDC__\r
+ #pragma hdrstop\r
+#endif\r
+\r
+#include "wx/control.h"\r
+#include "wx/dcmemory.h"\r
+\r
+// ----------------------------------------------------------------------------\r
+// test class\r
+// ----------------------------------------------------------------------------\r
+\r
+class EllipsizationTestCase : public CppUnit::TestCase\r
+{\r
+public:\r
+ EllipsizationTestCase() { }\r
+\r
+private:\r
+ CPPUNIT_TEST_SUITE( EllipsizationTestCase );\r
+ CPPUNIT_TEST( Ellipsize );\r
+ CPPUNIT_TEST_SUITE_END();\r
+\r
+ void Ellipsize();\r
+\r
+ DECLARE_NO_COPY_CLASS(EllipsizationTestCase)\r
+};\r
+\r
+// register in the unnamed registry so that these tests are run by default\r
+CPPUNIT_TEST_SUITE_REGISTRATION( EllipsizationTestCase );\r
+\r
+// also include in it's own registry so that these tests can be run alone\r
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EllipsizationTestCase, "EllipsizationTestCase" );\r
+\r
+void EllipsizationTestCase::Ellipsize()\r
+{\r
+ wxMemoryDC dc;\r
+\r
+ wxString stringsToTest[] = \r
+ { \r
+ "N", ".", "x", "foobar", wxS("\u03B1"), "Another test", "a very very very very very very very long string",\r
+ "\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4\xCE\xB5\xCE\xB6\xCE\xB7\xCE\xB8\xCE\xB9", \r
+ // alpha+beta+gamma+delta+epsilon+zeta+eta+theta+iota\r
+ "\t", "\t\t\t\t\t", "a\tstring\twith\ttabs",\r
+ "\n", "\n\n\n\n\n", "a\nstring\nwith\nnewlines",\r
+ "&", "&&&&&&&", "a&string&with&newlines",\r
+ "\t\n&", "a\t\n&string\t\n&with\t\n&many\t\n&chars"\r
+ };\r
+ int flagsToTest[] = { 0, wxELLIPSIZE_FLAGS_PROCESS_MNEMONICS, wxELLIPSIZE_FLAGS_EXPAND_TABS, \r
+ wxELLIPSIZE_FLAGS_PROCESS_MNEMONICS|wxELLIPSIZE_FLAGS_EXPAND_TABS };\r
+ wxEllipsizeMode modesToTest[] = { wxELLIPSIZE_START, wxELLIPSIZE_MIDDLE, wxELLIPSIZE_END };\r
+ int widthsToTest[] = { 0, 1, 2, 3, 10, 20, 100 };\r
+\r
+ for (unsigned int s=0; s<WXSIZEOF(stringsToTest); s++)\r
+ for (unsigned int f=0; f<WXSIZEOF(flagsToTest); f++)\r
+ for (unsigned int m=0; m<WXSIZEOF(modesToTest); m++)\r
+ for (unsigned int w=0; w<WXSIZEOF(widthsToTest); w++)\r
+ {\r
+ wxString ret = wxControlBase::Ellipsize(stringsToTest[s], dc, modesToTest[m], \r
+ widthsToTest[w], flagsToTest[f]);\r
+\r
+ CPPUNIT_ASSERT_MESSAGE((std::string)("invalid ellipsization for: " + stringsToTest[s]),\r
+ dc.GetMultiLineTextExtent(ret).GetWidth() <= widthsToTest[w]);\r
+ }\r
+}\r
$(OBJS)\test_gui_size.obj \\r
$(OBJS)\test_gui_point.obj \\r
$(OBJS)\test_gui_colour.obj \\r
+ $(OBJS)\test_gui_ellipsization.obj \\r
$(OBJS)\test_gui_measuring.obj \\r
$(OBJS)\test_gui_config.obj \\r
$(OBJS)\test_gui_comboboxtest.obj \\r
$(OBJS)\test_gui_colour.obj: .\graphics\colour.cpp\r
$(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\graphics\colour.cpp\r
\r
+$(OBJS)\test_gui_ellipsization.obj: .\graphics\ellipsization.cpp\r
+ $(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\graphics\ellipsization.cpp\r
+\r
$(OBJS)\test_gui_measuring.obj: .\graphics\measuring.cpp\r
$(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\graphics\measuring.cpp\r
\r
$(OBJS)\test_gui_size.o \\r
$(OBJS)\test_gui_point.o \\r
$(OBJS)\test_gui_colour.o \\r
+ $(OBJS)\test_gui_ellipsization.o \\r
$(OBJS)\test_gui_measuring.o \\r
$(OBJS)\test_gui_config.o \\r
$(OBJS)\test_gui_comboboxtest.o \\r
$(OBJS)\test_gui_colour.o: ./graphics/colour.cpp\r
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<\r
\r
+$(OBJS)\test_gui_ellipsization.o: ./graphics/ellipsization.cpp\r
+ $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<\r
+\r
$(OBJS)\test_gui_measuring.o: ./graphics/measuring.cpp\r
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<\r
\r
$(OBJS)\test_gui_size.obj \\r
$(OBJS)\test_gui_point.obj \\r
$(OBJS)\test_gui_colour.obj \\r
+ $(OBJS)\test_gui_ellipsization.obj \\r
$(OBJS)\test_gui_measuring.obj \\r
$(OBJS)\test_gui_config.obj \\r
$(OBJS)\test_gui_comboboxtest.obj \\r
$(OBJS)\test_gui_colour.obj: .\graphics\colour.cpp\r
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\graphics\colour.cpp\r
\r
+$(OBJS)\test_gui_ellipsization.obj: .\graphics\ellipsization.cpp\r
+ $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\graphics\ellipsization.cpp\r
+\r
$(OBJS)\test_gui_measuring.obj: .\graphics\measuring.cpp\r
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\graphics\measuring.cpp\r
\r
$(OBJS)\test_gui_size.obj &\r
$(OBJS)\test_gui_point.obj &\r
$(OBJS)\test_gui_colour.obj &\r
+ $(OBJS)\test_gui_ellipsization.obj &\r
$(OBJS)\test_gui_measuring.obj &\r
$(OBJS)\test_gui_config.obj &\r
$(OBJS)\test_gui_comboboxtest.obj &\r
$(OBJS)\test_gui_colour.obj : .AUTODEPEND .\graphics\colour.cpp\r
$(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<\r
\r
+$(OBJS)\test_gui_ellipsization.obj : .AUTODEPEND .\graphics\ellipsization.cpp\r
+ $(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<\r
+\r
$(OBJS)\test_gui_measuring.obj : .AUTODEPEND .\graphics\measuring.cpp\r
$(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<\r
\r
geometry/size.cpp
geometry/point.cpp
graphics/colour.cpp
+ graphics/ellipsization.cpp
graphics/measuring.cpp
config/config.cpp
controls/comboboxtest.cpp
# End Source File\r
# Begin Source File\r
\r
+SOURCE=.\graphics\ellipsization.cpp\r
+# End Source File\r
+# Begin Source File\r
+\r
SOURCE=.\font\fonttest.cpp\r
# End Source File\r
# Begin Source File\r
UsePrecompiledHeader="1"/>\r
</FileConfiguration>\r
</File>\r
+ <File\r
+ RelativePath=".\graphics\ellipsization.cpp">\r
+ </File>\r
<File\r
RelativePath=".\font\fonttest.cpp">\r
</File>\r
/>\r
</FileConfiguration>\r
</File>\r
+ <File\r
+ RelativePath=".\graphics\ellipsization.cpp"\r
+ >\r
+ </File>\r
<File\r
RelativePath=".\font\fonttest.cpp"\r
>\r
/>\r
</FileConfiguration>\r
</File>\r
+ <File\r
+ RelativePath=".\graphics\ellipsization.cpp"\r
+ >\r
+ </File>\r
<File\r
RelativePath=".\font\fonttest.cpp"\r
>\r