Skip to content

Commit

Permalink
[labeling] Add a new capitalization option for "Title Case", and
Browse files Browse the repository at this point in the history
rename the confusing "Capitalize First Letter" option to
"Force First Letter to Capital"

This change is intended to clarify the role of the "capitalize
first letter" option, and to provide an option which actually
does what users expect the "capitalize first letter" option
to do.

Fixes #16539
  • Loading branch information
nyalldawson committed Sep 16, 2020
1 parent 6bd3dc5 commit 484ba6f
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 26 deletions.
18 changes: 18 additions & 0 deletions python/core/auto_generated/textrenderer/qgstextformat.sip.in
Expand Up @@ -368,6 +368,24 @@ Sets the ``orientation`` for the text.
.. seealso:: :py:func:`orientation`

.. versionadded:: 3.10
%End

QgsStringUtils::Capitalization capitalization() const;
%Docstring
Returns the text capitalization style.

.. seealso:: :py:func:`setCapitalization`

.. versionadded:: 3.16
%End

void setCapitalization( QgsStringUtils::Capitalization capitalization );
%Docstring
Sets the text ``capitalization`` style.

.. seealso:: :py:func:`capitalization`

.. versionadded:: 3.16
%End

bool allowHtmlFormatting() const;
Expand Down
10 changes: 7 additions & 3 deletions src/core/labeling/qgspallabeling.cpp
Expand Up @@ -129,7 +129,7 @@ void QgsPalLayerSettings::initPropertyDefinitions()
{ QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
{ QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
{ QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
{ QgsPalLayerSettings::FontCase, QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + QStringLiteral( "[<b>NoChange</b>|<b>Upper</b>|<br><b>Lower</b>|<b>Capitalize</b>]" ), origin ) },
{ QgsPalLayerSettings::FontCase, QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + QStringLiteral( "[<b>NoChange</b>|<b>Upper</b>|<br><b>Lower</b>|<b>Title</b>|<b>Capitalize</b>]" ), origin ) },
{ QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
{ QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
{ QgsPalLayerSettings::FontBlendMode, QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
Expand Down Expand Up @@ -1834,9 +1834,9 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
}

// apply capitalization
QgsStringUtils::Capitalization capitalization = QgsStringUtils::MixedCase;
QgsStringUtils::Capitalization capitalization = mFormat.capitalization();
// maintain API - capitalization may have been set in textFont
if ( mFormat.font().capitalization() != QFont::MixedCase )
if ( capitalization == QgsStringUtils::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
{
capitalization = static_cast< QgsStringUtils::Capitalization >( mFormat.font().capitalization() );
}
Expand Down Expand Up @@ -1867,6 +1867,10 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
{
capitalization = QgsStringUtils::ForceFirstLetterToCapital;
}
else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::TitleCase;
}
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/core/labeling/qgsvectorlayerlabeling.cpp
Expand Up @@ -269,15 +269,18 @@ void QgsAbstractVectorLayerLabeling::writeTextSymbolizer( QDomNode &parent, QgsP
}
else
{
if ( font.capitalization() == QFont::AllUppercase )
QgsStringUtils::Capitalization capitalization = format.capitalization();
if ( capitalization == QgsStringUtils::MixedCase && font.capitalization() != QFont::MixedCase )
capitalization = static_cast< QgsStringUtils::Capitalization >( font.capitalization() );
if ( capitalization == QgsStringUtils::AllUppercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), settings.fieldName );
}
else if ( font.capitalization() == QFont::AllLowercase )
else if ( capitalization == QgsStringUtils::AllLowercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), settings.fieldName );
}
else if ( font.capitalization() == QFont::Capitalize )
else if ( capitalization == QgsStringUtils::ForceFirstLetterToCapital )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), settings.fieldName );
}
Expand Down
24 changes: 21 additions & 3 deletions src/core/textrenderer/qgstextformat.cpp
Expand Up @@ -74,6 +74,7 @@ bool QgsTextFormat::operator==( const QgsTextFormat &other ) const
|| d->orientation != other.orientation()
|| d->previewBackgroundColor != other.previewBackgroundColor()
|| d->allowHtmlFormatting != other.allowHtmlFormatting()
|| d->capitalization != other.capitalization()
|| mBufferSettings != other.mBufferSettings
|| mBackgroundSettings != other.mBackgroundSettings
|| mShadowSettings != other.mShadowSettings
Expand Down Expand Up @@ -283,6 +284,19 @@ void QgsTextFormat::setOrientation( TextOrientation orientation )
d->orientation = orientation;
}

QgsStringUtils::Capitalization QgsTextFormat::capitalization() const
{
// bit of complexity here to maintain API..
return d->capitalization == QgsStringUtils::MixedCase && d->textFont.capitalization() != QFont::MixedCase ? static_cast< QgsStringUtils::Capitalization >( d->textFont.capitalization() ) : d->capitalization ;
}

void QgsTextFormat::setCapitalization( QgsStringUtils::Capitalization capitalization )
{
d->isValid = true;
d->capitalization = capitalization;
d->textFont.setCapitalization( QFont::MixedCase );
}

bool QgsTextFormat::allowHtmlFormatting() const
{
return d->allowHtmlFormatting;
Expand Down Expand Up @@ -365,7 +379,7 @@ void QgsTextFormat::readFromLayer( QgsVectorLayer *layer )
d->textFont = QFont( fontFamily, d->fontSize, fontWeight, fontItalic );
d->textNamedStyle = QgsFontUtils::translateNamedStyle( layer->customProperty( QStringLiteral( "labeling/namedStyle" ), QVariant( "" ) ).toString() );
QgsFontUtils::updateFontViaStyle( d->textFont, d->textNamedStyle ); // must come after textFont.setPointSizeF()
d->textFont.setCapitalization( static_cast< QFont::Capitalization >( layer->customProperty( QStringLiteral( "labeling/fontCapitals" ), QVariant( 0 ) ).toUInt() ) );
d->capitalization = static_cast< QgsStringUtils::Capitalization >( layer->customProperty( QStringLiteral( "labeling/fontCapitals" ), QVariant( 0 ) ).toUInt() );
d->textFont.setUnderline( layer->customProperty( QStringLiteral( "labeling/fontUnderline" ) ).toBool() );
d->textFont.setStrikeOut( layer->customProperty( QStringLiteral( "labeling/fontStrikeout" ) ).toBool() );
d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, layer->customProperty( QStringLiteral( "labeling/fontLetterSpacing" ), QVariant( 0.0 ) ).toDouble() );
Expand Down Expand Up @@ -453,7 +467,6 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext
d->textFont.setPointSizeF( d->fontSize ); //double precision needed because of map units
d->textNamedStyle = QgsFontUtils::translateNamedStyle( textStyleElem.attribute( QStringLiteral( "namedStyle" ) ) );
QgsFontUtils::updateFontViaStyle( d->textFont, d->textNamedStyle ); // must come after textFont.setPointSizeF()
d->textFont.setCapitalization( static_cast< QFont::Capitalization >( textStyleElem.attribute( QStringLiteral( "fontCapitals" ), QStringLiteral( "0" ) ).toUInt() ) );
d->textFont.setUnderline( textStyleElem.attribute( QStringLiteral( "fontUnderline" ) ).toInt() );
d->textFont.setStrikeOut( textStyleElem.attribute( QStringLiteral( "fontStrikeout" ) ).toInt() );
d->textFont.setKerning( textStyleElem.attribute( QStringLiteral( "fontKerning" ), QStringLiteral( "1" ) ).toInt() );
Expand Down Expand Up @@ -484,6 +497,11 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext
d->multilineHeight = textStyleElem.attribute( QStringLiteral( "multilineHeight" ), QStringLiteral( "1" ) ).toDouble();
}

if ( textStyleElem.hasAttribute( QStringLiteral( "capitalization" ) ) )
d->capitalization = static_cast< QgsStringUtils::Capitalization >( textStyleElem.attribute( QStringLiteral( "capitalization" ), QString::number( QgsStringUtils::MixedCase ) ).toInt() );
else
d->capitalization = static_cast< QgsStringUtils::Capitalization >( textStyleElem.attribute( QStringLiteral( "fontCapitals" ), QStringLiteral( "0" ) ).toUInt() );

d->allowHtmlFormatting = textStyleElem.attribute( QStringLiteral( "allowHtml" ), QStringLiteral( "0" ) ).toInt();

if ( textStyleElem.firstChildElement( QStringLiteral( "text-buffer" ) ).isNull() )
Expand Down Expand Up @@ -549,7 +567,6 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex
textStyleElem.setAttribute( QStringLiteral( "fontUnderline" ), d->textFont.underline() );
textStyleElem.setAttribute( QStringLiteral( "textColor" ), QgsSymbolLayerUtils::encodeColor( d->textColor ) );
textStyleElem.setAttribute( QStringLiteral( "previewBkgrdColor" ), QgsSymbolLayerUtils::encodeColor( d->previewBackgroundColor ) );
textStyleElem.setAttribute( QStringLiteral( "fontCapitals" ), static_cast< unsigned int >( d->textFont.capitalization() ) );
textStyleElem.setAttribute( QStringLiteral( "fontLetterSpacing" ), d->textFont.letterSpacing() );
textStyleElem.setAttribute( QStringLiteral( "fontWordSpacing" ), d->textFont.wordSpacing() );
textStyleElem.setAttribute( QStringLiteral( "fontKerning" ), d->textFont.kerning() );
Expand All @@ -558,6 +575,7 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex
textStyleElem.setAttribute( QStringLiteral( "blendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
textStyleElem.setAttribute( QStringLiteral( "multilineHeight" ), d->multilineHeight );
textStyleElem.setAttribute( QStringLiteral( "allowHtml" ), d->allowHtmlFormatting ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
textStyleElem.setAttribute( QStringLiteral( "capitalization" ), QString::number( static_cast< int >( d->capitalization ) ) );

QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) );
d->mDataDefinedProperties.writeXml( ddElem, QgsPalLayerSettings::propertyDefinitions() );
Expand Down
17 changes: 17 additions & 0 deletions src/core/textrenderer/qgstextformat.h
Expand Up @@ -23,6 +23,7 @@
#include "qgstextbackgroundsettings.h"
#include "qgstextshadowsettings.h"
#include "qgstextmasksettings.h"
#include "qgsstringutils.h"

#include <QSharedDataPointer>

Expand Down Expand Up @@ -339,6 +340,22 @@ class CORE_EXPORT QgsTextFormat
*/
void setOrientation( TextOrientation orientation );

/**
* Returns the text capitalization style.
*
* \see setCapitalization()
* \since QGIS 3.16
*/
QgsStringUtils::Capitalization capitalization() const;

/**
* Sets the text \a capitalization style.
*
* \see capitalization()
* \since QGIS 3.16
*/
void setCapitalization( QgsStringUtils::Capitalization capitalization );

/**
* Returns TRUE if text should be treated as a HTML document and HTML tags should be used for formatting
* the rendered text.
Expand Down
5 changes: 4 additions & 1 deletion src/core/textrenderer/qgstextrenderer_p.h
Expand Up @@ -28,6 +28,8 @@
#include "qgsapplication.h"
#include "qgspainteffect.h"
#include "qgssymbollayerreference.h"
#include "qgsstringutils.h"

#include <QSharedData>
#include <QPainter>

Expand Down Expand Up @@ -271,6 +273,7 @@ class QgsTextSettingsPrivate : public QSharedData
, orientation( other.orientation )
, previewBackgroundColor( other.previewBackgroundColor )
, allowHtmlFormatting( other.allowHtmlFormatting )
, capitalization( other.capitalization )
, mDataDefinedProperties( other.mDataDefinedProperties )
{
}
Expand All @@ -287,8 +290,8 @@ class QgsTextSettingsPrivate : public QSharedData
double multilineHeight = 1.0 ; //0.0 to 10.0, leading between lines as multiplyer of line height
QgsTextFormat::TextOrientation orientation = QgsTextFormat::HorizontalOrientation;
QColor previewBackgroundColor = Qt::white;

bool allowHtmlFormatting = false;
QgsStringUtils::Capitalization capitalization = QgsStringUtils::MixedCase;

//! Property collection for data defined settings
QgsPropertyCollection mDataDefinedProperties;
Expand Down
21 changes: 7 additions & 14 deletions src/gui/qgstextformatwidget.cpp
Expand Up @@ -70,7 +70,6 @@ void QgsTextFormatWidget::initWidget()

connect( mShapeSVGPathLineEdit, &QLineEdit::textChanged, this, &QgsTextFormatWidget::mShapeSVGPathLineEdit_textChanged );
connect( mFontSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsTextFormatWidget::mFontSizeSpinBox_valueChanged );
connect( mFontCapitalsComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsTextFormatWidget::mFontCapitalsComboBox_currentIndexChanged );
connect( mFontFamilyCmbBx, &QFontComboBox::currentFontChanged, this, &QgsTextFormatWidget::mFontFamilyCmbBx_currentFontChanged );
connect( mFontStyleComboBox, &QComboBox::currentTextChanged, this, &QgsTextFormatWidget::mFontStyleComboBox_currentIndexChanged );
connect( mFontUnderlineBtn, &QToolButton::toggled, this, &QgsTextFormatWidget::mFontUnderlineBtn_toggled );
Expand Down Expand Up @@ -890,6 +889,7 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format )
mFontLetterSpacingSpinBox->setValue( format.font().letterSpacing() );
whileBlocking( mKerningCheckBox )->setChecked( format.font().kerning() );

whileBlocking( mFontCapitalsComboBox )->setCurrentIndex( mFontCapitalsComboBox->findData( format.capitalization() ) );
QgsFontUtils::updateFontViaStyle( mRefFont, format.namedStyle() );
updateFont( mRefFont );

Expand Down Expand Up @@ -1025,6 +1025,7 @@ QgsTextFormat QgsTextFormatWidget::format( bool includeDataDefinedProperties ) c
format.setPreviewBackgroundColor( mPreviewBackgroundColor );
format.setOrientation( static_cast< QgsTextFormat::TextOrientation >( mTextOrientationComboBox->currentData().toInt() ) );
format.setAllowHtmlFormatting( mHtmlFormattingCheckBox->isChecked( ) );
format.setCapitalization( static_cast< QgsStringUtils::Capitalization >( mFontCapitalsComboBox->currentData().toInt() ) );

// buffer
QgsTextBufferSettings buffer;
Expand Down Expand Up @@ -1218,8 +1219,6 @@ void QgsTextFormatWidget::updateFont( const QFont &font )
blockFontChangeSignals( true );
mFontFamilyCmbBx->setCurrentFont( mRefFont );
populateFontStyleComboBox();
int idx = mFontCapitalsComboBox->findData( QVariant( static_cast< unsigned int >( mRefFont.capitalization() ) ) );
mFontCapitalsComboBox->setCurrentIndex( idx == -1 ? 0 : idx );
mFontUnderlineBtn->setChecked( mRefFont.underline() );
mFontStrikethroughBtn->setChecked( mRefFont.strikeOut() );
mKerningCheckBox->setChecked( mRefFont.kerning() );
Expand Down Expand Up @@ -1417,13 +1416,14 @@ void QgsTextFormatWidget::updatePlacementWidgets()

void QgsTextFormatWidget::populateFontCapitalsComboBox()
{
mFontCapitalsComboBox->addItem( tr( "No Change" ), QVariant( 0 ) );
mFontCapitalsComboBox->addItem( tr( "All Uppercase" ), QVariant( 1 ) );
mFontCapitalsComboBox->addItem( tr( "All Lowercase" ), QVariant( 2 ) );
mFontCapitalsComboBox->addItem( tr( "No Change" ), QgsStringUtils::MixedCase );
mFontCapitalsComboBox->addItem( tr( "All Uppercase" ), QgsStringUtils::AllUppercase );
mFontCapitalsComboBox->addItem( tr( "All Lowercase" ), QgsStringUtils::AllLowercase );
// Small caps doesn't work right with QPainterPath::addText()
// https://bugreports.qt.io/browse/QTBUG-13965
// mFontCapitalsComboBox->addItem( tr( "Small Caps" ), QVariant( 3 ) );
mFontCapitalsComboBox->addItem( tr( "Capitalize First Letter" ), QVariant( 4 ) );
mFontCapitalsComboBox->addItem( tr( "Title Case" ), QgsStringUtils::TitleCase );
mFontCapitalsComboBox->addItem( tr( "Force First Letter to Capital" ), QgsStringUtils::ForceFirstLetterToCapital );
}

void QgsTextFormatWidget::populateFontStyleComboBox()
Expand Down Expand Up @@ -1459,13 +1459,6 @@ void QgsTextFormatWidget::mFontSizeSpinBox_valueChanged( double d )
updateFont( mRefFont );
}

void QgsTextFormatWidget::mFontCapitalsComboBox_currentIndexChanged( int index )
{
int capitalsindex = mFontCapitalsComboBox->itemData( index ).toInt();
mRefFont.setCapitalization( static_cast< QFont::Capitalization >( capitalsindex ) );
updateFont( mRefFont );
}

void QgsTextFormatWidget::mFontFamilyCmbBx_currentFontChanged( const QFont &f )
{
mRefFont.setFamily( f.family() );
Expand Down
1 change: 0 additions & 1 deletion src/gui/qgstextformatwidget.h
Expand Up @@ -272,7 +272,6 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, public QgsExpressionConte
void onSubstitutionsChanged( const QgsStringReplacementCollection &substitutions );
void previewScaleChanged( double scale );
void mFontSizeSpinBox_valueChanged( double d );
void mFontCapitalsComboBox_currentIndexChanged( int index );
void mFontFamilyCmbBx_currentFontChanged( const QFont &f );
void mFontStyleComboBox_currentIndexChanged( const QString &text );
void mFontUnderlineBtn_toggled( bool ckd );
Expand Down
42 changes: 42 additions & 0 deletions tests/src/core/testqgslabelingengine.cpp
Expand Up @@ -650,8 +650,19 @@ void TestQgsLabelingEngine::testCapitalization()
provider2->registerFeature( f, context );
QCOMPARE( provider2->mLabels.at( 0 )->labelText(), QString( "A TEST LABEL" ) );

font.setCapitalization( QFont::MixedCase );
format.setCapitalization( QgsStringUtils::AllUppercase );
format.setFont( font );
settings.setFormat( format );
QgsVectorLayerLabelProvider *provider2b = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test2" ), true, &settings );
engine.addProvider( provider2b );
provider2b->prepare( context, attributes );
provider2b->registerFeature( f, context );
QCOMPARE( provider2b->mLabels.at( 0 )->labelText(), QString( "A TEST LABEL" ) );

//lowercase
font.setCapitalization( QFont::AllLowercase );
format.setCapitalization( QgsStringUtils::MixedCase );
format.setFont( font );
settings.setFormat( format );
QgsVectorLayerLabelProvider *provider3 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test3" ), true, &settings );
Expand All @@ -660,15 +671,46 @@ void TestQgsLabelingEngine::testCapitalization()
provider3->registerFeature( f, context );
QCOMPARE( provider3->mLabels.at( 0 )->labelText(), QString( "a test label" ) );

font.setCapitalization( QFont::MixedCase );
format.setCapitalization( QgsStringUtils::AllLowercase );
format.setFont( font );
settings.setFormat( format );
QgsVectorLayerLabelProvider *provider3b = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test3" ), true, &settings );
engine.addProvider( provider3b );
provider3b->prepare( context, attributes );
provider3b->registerFeature( f, context );
QCOMPARE( provider3b->mLabels.at( 0 )->labelText(), QString( "a test label" ) );

//first letter uppercase
font.setCapitalization( QFont::Capitalize );
format.setCapitalization( QgsStringUtils::MixedCase );
format.setFont( font );
settings.setFormat( format );
QgsVectorLayerLabelProvider *provider4 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test4" ), true, &settings );
engine.addProvider( provider4 );
provider4->prepare( context, attributes );
provider4->registerFeature( f, context );
QCOMPARE( provider4->mLabels.at( 0 )->labelText(), QString( "A TeSt LABEL" ) );

font.setCapitalization( QFont::MixedCase );
format.setCapitalization( QgsStringUtils::ForceFirstLetterToCapital );
format.setFont( font );
settings.setFormat( format );
QgsVectorLayerLabelProvider *provider4b = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test4" ), true, &settings );
engine.addProvider( provider4b );
provider4b->prepare( context, attributes );
provider4b->registerFeature( f, context );
QCOMPARE( provider4b->mLabels.at( 0 )->labelText(), QString( "A TeSt LABEL" ) );

settings.fieldName = QStringLiteral( "'A TEST LABEL'" );
format.setCapitalization( QgsStringUtils::TitleCase );
format.setFont( font );
settings.setFormat( format );
QgsVectorLayerLabelProvider *provider5 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test4" ), true, &settings );
engine.addProvider( provider5 );
provider5->prepare( context, attributes );
provider5->registerFeature( f, context );
QCOMPARE( provider5->mLabels.at( 0 )->labelText(), QString( "A Test Label" ) );
}

void TestQgsLabelingEngine::testNumberFormat()
Expand Down

0 comments on commit 484ba6f

Please sign in to comment.