Skip to content

Commit

Permalink
Move guts of wordwrap expression function to QgsStringUtils::wordWrap
Browse files Browse the repository at this point in the history
To allow use outside of expressions
  • Loading branch information
nyalldawson committed Oct 4, 2018
1 parent 706b13a commit dce8673
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 66 deletions.
14 changes: 14 additions & 0 deletions python/core/auto_generated/qgsstringutils.sip.in
Expand Up @@ -260,6 +260,20 @@ links.
:return: string with inserted links

.. versionadded:: 3.0
%End

static QString wordWrap( const QString &string, int length, bool useMaxLineLength = true, const QString &customDelimiter = QString() );
%Docstring
Automatically wraps a \string by inserting new line characters at appropriate locations in the string.

The ``length`` argument specifies either the minimum or maximum length of lines desired, depending
on whether ``useMaxLineLength`` is true. If ``useMaxLineLength`` is true, then the string will be wrapped
so that each line ideally will not exceed ``length`` of characters. If ``useMaxLineLength`` is false, then
the string will be wrapped so that each line will ideally exceed ``length`` of characters.

A custom delimiter can be specified to use instead of space characters.

.. versionadded:: 3.4
%End
};

Expand Down
68 changes: 2 additions & 66 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -1014,73 +1014,9 @@ static QVariant fcnWordwrap( const QVariantList &values, const QgsExpressionCont
QString str = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
qlonglong wrap = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );

if ( !str.isEmpty() && wrap != 0 )
{
QString newstr;
QRegExp rx;
QString customdelimiter = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );
int delimiterlength;

if ( customdelimiter.length() > 0 )
{
rx.setPatternSyntax( QRegExp::FixedString );
rx.setPattern( customdelimiter );
delimiterlength = customdelimiter.length();
}
else
{
// \x200B is a ZERO-WIDTH SPACE, needed for worwrap to support a number of complex scripts (Indic, Arabic, etc.)
rx.setPattern( QStringLiteral( "[\\s\\x200B]" ) );
delimiterlength = 1;
}

QString customdelimiter = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );

QStringList lines = str.split( '\n' );
int strlength, strcurrent, strhit, lasthit;

for ( int i = 0; i < lines.size(); i++ )
{
strlength = lines[i].length();
strcurrent = 0;
strhit = 0;
lasthit = 0;

while ( strcurrent < strlength )
{
// positive wrap value = desired maximum line width to wrap
// negative wrap value = desired minimum line width before wrap
if ( wrap > 0 )
{
//first try to locate delimiter backwards
strhit = lines[i].lastIndexOf( rx, strcurrent + wrap );
if ( strhit == lasthit || strhit == -1 )
{
//if no new backward delimiter found, try to locate forward
strhit = lines[i].indexOf( rx, strcurrent + std::labs( wrap ) );
}
lasthit = strhit;
}
else
{
strhit = lines[i].indexOf( rx, strcurrent + std::labs( wrap ) );
}
if ( strhit > -1 )
{
newstr.append( lines[i].midRef( strcurrent, strhit - strcurrent ) );
newstr.append( '\n' );
strcurrent = strhit + delimiterlength;
}
else
{
newstr.append( lines[i].midRef( strcurrent ) );
strcurrent = strlength;
}
}
if ( i < lines.size() - 1 ) newstr.append( '\n' );
}

return QVariant( newstr );
}
return QgsStringUtils::wordWrap( str, static_cast< int >( wrap ), wrap > 0, customdelimiter );
}

return QVariant();
Expand Down
70 changes: 70 additions & 0 deletions src/core/qgsstringutils.cpp
Expand Up @@ -442,6 +442,76 @@ QString QgsStringUtils::insertLinks( const QString &string, bool *foundLinks )
return converted;
}

QString QgsStringUtils::wordWrap( const QString &string, const int length, const bool useMaxLineLength, const QString &customDelimiter )
{
if ( string.isEmpty() || length == 0 )
return string;

QString newstr;
QRegExp rx;
int delimiterLength = 0;

if ( !customDelimiter.isEmpty() )
{
rx.setPatternSyntax( QRegExp::FixedString );
rx.setPattern( customDelimiter );
delimiterLength = customDelimiter.length();
}
else
{
// \x200B is a ZERO-WIDTH SPACE, needed for worwrap to support a number of complex scripts (Indic, Arabic, etc.)
rx.setPattern( QStringLiteral( "[\\s\\x200B]" ) );
delimiterLength = 1;
}

const QStringList lines = string.split( '\n' );
int strLength, strCurrent, strHit, lastHit;

for ( int i = 0; i < lines.size(); i++ )
{
strLength = lines.at( i ).length();
strCurrent = 0;
strHit = 0;
lastHit = 0;

while ( strCurrent < strLength )
{
// positive wrap value = desired maximum line width to wrap
// negative wrap value = desired minimum line width before wrap
if ( useMaxLineLength )
{
//first try to locate delimiter backwards
strHit = lines.at( i ).lastIndexOf( rx, strCurrent + length );
if ( strHit == lastHit || strHit == -1 )
{
//if no new backward delimiter found, try to locate forward
strHit = lines.at( i ).indexOf( rx, strCurrent + std::abs( length ) );
}
lastHit = strHit;
}
else
{
strHit = lines.at( i ).indexOf( rx, strCurrent + std::abs( length ) );
}
if ( strHit > -1 )
{
newstr.append( lines.at( i ).midRef( strCurrent, strHit - strCurrent ) );
newstr.append( '\n' );
strCurrent = strHit + delimiterLength;
}
else
{
newstr.append( lines.at( i ).midRef( strCurrent ) );
strCurrent = strLength;
}
}
if ( i < lines.size() - 1 )
newstr.append( '\n' );
}

return newstr;
}

QgsStringReplacement::QgsStringReplacement( const QString &match, const QString &replacement, bool caseSensitive, bool wholeWordOnly )
: mMatch( match )
, mReplacement( replacement )
Expand Down
14 changes: 14 additions & 0 deletions src/core/qgsstringutils.h
Expand Up @@ -260,6 +260,20 @@ class CORE_EXPORT QgsStringUtils
* \since QGIS 3.0
*/
static QString insertLinks( const QString &string, bool *foundLinks = nullptr );

/**
* Automatically wraps a \string by inserting new line characters at appropriate locations in the string.
*
* The \a length argument specifies either the minimum or maximum length of lines desired, depending
* on whether \a useMaxLineLength is true. If \a useMaxLineLength is true, then the string will be wrapped
* so that each line ideally will not exceed \a length of characters. If \a useMaxLineLength is false, then
* the string will be wrapped so that each line will ideally exceed \a length of characters.
*
* A custom delimiter can be specified to use instead of space characters.
*
* \since QGIS 3.4
*/
static QString wordWrap( const QString &string, int length, bool useMaxLineLength = true, const QString &customDelimiter = QString() );
};

#endif //QGSSTRINGUTILS_H
31 changes: 31 additions & 0 deletions tests/src/core/testqgsstringutils.cpp
Expand Up @@ -38,6 +38,8 @@ class TestQgsStringUtils : public QObject
void titleCase();
void ampersandEncode_data();
void ampersandEncode();
void wordWrap_data();
void wordWrap();

};

Expand Down Expand Up @@ -206,6 +208,35 @@ void TestQgsStringUtils::ampersandEncode()

}

void TestQgsStringUtils::wordWrap_data()
{
QTest::addColumn<QString>( "input" );
QTest::addColumn<int>( "length" );
QTest::addColumn<bool>( "isMax" );
QTest::addColumn<QString>( "delimiter" );
QTest::addColumn<QString>( "expected" );

QTest::newRow( "wordwrap" ) << "university of qgis" << 13 << true << QString() << "university of\nqgis";
QTest::newRow( "optional parameters unspecified" ) << "test string" << 5 << true << QString() << "test\nstring";
QTest::newRow( "wordwrap with delim" ) << "university of qgis" << 13 << true << QStringLiteral( " " ) << "university of\nqgis";
QTest::newRow( "wordwrap min" ) << "university of qgis" << 3 << false << QStringLiteral( " " ) << "university\nof qgis";
QTest::newRow( "wordwrap min with delim" ) << "university of qgis" << 3 << false << QStringLiteral( " " ) << "university\nof qgis";
QTest::newRow( "wordwrap on multi line" ) << "university of qgis\nsupports many multiline" << 5 << false << QStringLiteral( " " ) << "university\nof qgis\nsupports\nmany multiline";
QTest::newRow( "wordwrap on zero-space width" ) << QStringLiteral( "test%1zero-width space" ).arg( QChar( 8203 ) ) << 4 << false << QString() << "test\nzero-width\nspace";
QTest::newRow( "optional parameters specified" ) << "testxstring" << 5 << true << "x" << "test\nstring";
}

void TestQgsStringUtils::wordWrap()
{
QFETCH( QString, input );
QFETCH( int, length );
QFETCH( bool, isMax );
QFETCH( QString, delimiter );
QFETCH( QString, expected );

QCOMPARE( QgsStringUtils::wordWrap( input, length, isMax, delimiter ), expected );
}


QGSTEST_MAIN( TestQgsStringUtils )
#include "testqgsstringutils.moc"

0 comments on commit dce8673

Please sign in to comment.