diff --git a/python/core/composer/qgscomposerlabel.sip b/python/core/composer/qgscomposerlabel.sip index 2738018d827..71dc9ec5f77 100644 --- a/python/core/composer/qgscomposerlabel.sip +++ b/python/core/composer/qgscomposerlabel.sip @@ -189,6 +189,7 @@ Get font color %End public slots: + void refreshExpressionContext(); diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index c81d8679de7..7c815571ad3 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -168,7 +168,6 @@ %Include layout/qgslayoutsize.sip %Include layout/qgslayoutsnapper.sip %Include layout/qgslayoutundocommand.sip -%Include layout/qgslayoutundostack.sip %Include layout/qgslayoututils.sip %Include metadata/qgslayermetadata.sip %Include metadata/qgslayermetadatavalidator.sip @@ -408,6 +407,7 @@ %Include layout/qgslayoutguidecollection.sip %Include layout/qgslayoutitem.sip %Include layout/qgslayoutitemgroup.sip +%Include layout/qgslayoutitemlabel.sip %Include layout/qgslayoutitemmap.sip %Include layout/qgslayoutitemmapgrid.sip %Include layout/qgslayoutitemmapitem.sip @@ -422,6 +422,7 @@ %Include layout/qgslayoutmodel.sip %Include layout/qgslayoutpagecollection.sip %Include layout/qgslayoutobject.sip +%Include layout/qgslayoutundostack.sip %Include symbology/qgscptcityarchive.sip %Include symbology/qgssvgcache.sip %Include symbology/qgsstyle.sip diff --git a/python/core/layout/qgslayoutitemlabel.sip b/python/core/layout/qgslayoutitemlabel.sip new file mode 100644 index 00000000000..1176d59b980 --- /dev/null +++ b/python/core/layout/qgslayoutitemlabel.sip @@ -0,0 +1,224 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitemlabel.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsLayoutItemLabel: QgsLayoutItem +{ +%Docstring + A layout item subclass for text labels. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemlabel.h" +%End + public: + + enum Mode + { + ModeFont, + ModeHtml, + }; + + QgsLayoutItemLabel( QgsLayout *layout ); +%Docstring + Constructor for QgsLayoutItemLabel, with the specified parent ``layout``. +%End + + static QgsLayoutItemLabel *create( QgsLayout *layout ) /Factory/; +%Docstring + Returns a new label item for the specified ``layout``. + + The caller takes responsibility for deleting the returned object. + :rtype: QgsLayoutItemLabel +%End + + + virtual int type() const; + + virtual QString stringType() const; + + virtual QString displayName() const; + + void adjustSizeToText(); +%Docstring + Resizes the item so that the label's text fits to the item. Keeps the top left point stationary. +%End + + QString text(); +%Docstring + Returns the label's preset text. +.. seealso:: currentText() +.. seealso:: setText() + :rtype: str +%End + + void setText( const QString &text ); +%Docstring + Sets the label's preset ``text``. +.. seealso:: text() +%End + + QString currentText() const; +%Docstring + Returns the text as it appears on the label (with evaluated expressions + and other dynamic content). +.. seealso:: text() + :rtype: str +%End + + Mode mode() const; +%Docstring + Returns the label's current mode. +.. seealso:: setMode() + :rtype: Mode +%End + + void setMode( Mode mode ); +%Docstring + Sets the label's current ``mode``, allowing the label + to switch between font based and HTML based rendering. +.. seealso:: mode() +%End + + QFont font() const; +%Docstring + Returns the label's current font. +.. seealso:: setFont() + :rtype: QFont +%End + + void setFont( const QFont &font ); +%Docstring + Sets the label's current ``font``. +.. seealso:: font() +%End + + Qt::AlignmentFlag vAlign() const; +%Docstring + Returns for the vertical alignment of the label. +.. seealso:: setVAlign() +.. seealso:: hAlign() + :rtype: Qt.AlignmentFlag +%End + + Qt::AlignmentFlag hAlign() const; +%Docstring + Returns the horizontal alignment of the label. +.. seealso:: vAlign() +.. seealso:: setHAlign() + :rtype: Qt.AlignmentFlag +%End + + void setHAlign( Qt::AlignmentFlag alignment ); +%Docstring + Sets the horizontal ``alignment`` of the label. +.. seealso:: hAlign() +.. seealso:: setVAlign() +%End + + void setVAlign( Qt::AlignmentFlag alignment ); +%Docstring + Sets for the vertical ``alignment`` of the label. +.. seealso:: vAlign() +.. seealso:: setHAlign() +%End + + double marginX() const; +%Docstring + Returns the horizontal margin between the edge of the frame and the label + contents, in layout units. +.. seealso:: setMargin() +.. seealso:: marginY() + :rtype: float +%End + + double marginY() const; +%Docstring + Returns the vertical margin between the edge of the frame and the label + contents, in layout units. +.. seealso:: setMargin() +.. seealso:: marginX() + :rtype: float +%End + + void setMargin( double margin ); +%Docstring + Sets the ``margin`` between the edge of the frame and the label contents. + This method sets both the horizontal and vertical margins to the same + value. The margins can be individually controlled using the setMarginX() + and setMarginY() methods. + + Margins are set using the current layout units. + +.. seealso:: setMarginX() +.. seealso:: setMarginY() +%End + + void setMarginX( double margin ); +%Docstring + Sets the horizontal ``margin`` between the edge of the frame and the label + contents, in layout units. +.. seealso:: setMargin() +.. seealso:: setMarginY() +%End + + void setMarginY( double margin ); +%Docstring + Sets the vertical ``margin`` between the edge of the frame and the label + contents, in layout units. +.. seealso:: setMargin() +.. seealso:: setMarginX() +%End + + void setFontColor( const QColor &color ); +%Docstring + Sets the label font ``color``. +.. seealso:: fontColor() +%End + + QColor fontColor() const; +%Docstring + Returns the label font color. +.. seealso:: setFontColor() + :rtype: QColor +%End + + virtual QRectF boundingRect() const; + + + virtual void setFrameEnabled( const bool drawFrame ); + + + virtual void setFrameStrokeWidth( const QgsLayoutMeasurement &strokeWidth ); + + + public slots: + + virtual void refresh(); + + + protected: + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + + virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + + virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ); + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitemlabel.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/layout/qgslayoutitemregistry.sip b/python/core/layout/qgslayoutitemregistry.sip index aec061359af..bc8ace372de 100644 --- a/python/core/layout/qgslayoutitemregistry.sip +++ b/python/core/layout/qgslayoutitemregistry.sip @@ -104,6 +104,7 @@ class QgsLayoutItemRegistry : QObject LayoutPage, LayoutMap, LayoutPicture, + LayoutLabel, LayoutShape, LayoutPolygon, LayoutPolyline, diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index dd2fceeff1c..a7598a47902 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -369,6 +369,7 @@ SET(QGIS_CORE_SRCS layout/qgslayoutitem.cpp layout/qgslayoutitemgroup.cpp layout/qgslayoutitemgroupundocommand.cpp + layout/qgslayoutitemlabel.cpp layout/qgslayoutitemmap.cpp layout/qgslayoutitemmapgrid.cpp layout/qgslayoutitemmapitem.cpp @@ -728,6 +729,7 @@ SET(QGIS_CORE_MOC_HDRS layout/qgslayoutitem.h layout/qgslayoutitemgroup.h layout/qgslayoutitemgroupundocommand.h + layout/qgslayoutitemlabel.h layout/qgslayoutitemmap.h layout/qgslayoutitemmapgrid.h layout/qgslayoutitemmapitem.h diff --git a/src/core/composer/qgscomposerlabel.h b/src/core/composer/qgscomposerlabel.h index 212517cc4fc..bd93d39198e 100644 --- a/src/core/composer/qgscomposerlabel.h +++ b/src/core/composer/qgscomposerlabel.h @@ -170,6 +170,7 @@ class CORE_EXPORT QgsComposerLabel: public QgsComposerItem virtual void setFrameStrokeWidth( const double strokeWidth ) override; public slots: + void refreshExpressionContext(); diff --git a/src/core/layout/qgslayoutitemlabel.cpp b/src/core/layout/qgslayoutitemlabel.cpp new file mode 100644 index 00000000000..b80ac768ab7 --- /dev/null +++ b/src/core/layout/qgslayoutitemlabel.cpp @@ -0,0 +1,603 @@ +/*************************************************************************** + qgslayoutitemlabel.cpp + ------------------- + begin : October 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgslayoutitemlabel.h" +#include "qgslayoutitemregistry.h" +#include "qgslayout.h" +#include "qgslayoututils.h" +#include "qgslayoutmodel.h" +#include "qgsexpression.h" +#include "qgsnetworkaccessmanager.h" +#include "qgscomposermodel.h" +#include "qgsvectorlayer.h" +#include "qgsproject.h" +#include "qgsdistancearea.h" +#include "qgsfontutils.h" +#include "qgsexpressioncontext.h" +#include "qgsmapsettings.h" +#include "qgscomposermap.h" +#include "qgssettings.h" + +#include "qgswebview.h" +#include "qgswebframe.h" +#include "qgswebpage.h" + +#include +#include +#include +#include +#include +#include + +QgsLayoutItemLabel::QgsLayoutItemLabel( QgsLayout *layout ) + : QgsLayoutItem( layout ) +{ + mDistanceArea.reset( new QgsDistanceArea() ); + mHtmlUnitsToLayoutUnits = htmlUnitsToLayoutUnits(); + + //get default composer font from settings + QgsSettings settings; + QString defaultFontString = settings.value( QStringLiteral( "Composer/defaultFont" ) ).toString(); + if ( !defaultFontString.isEmpty() ) + { + mFont.setFamily( defaultFontString ); + } + + //default to a 10 point font size + mFont.setPointSizeF( 10 ); + + //default to no background + setBackgroundEnabled( false ); + + //a label added while atlas preview is enabled needs to have the expression context set, + //otherwise fields in the label aren't correctly evaluated until atlas preview feature changes (#9457) + refreshExpressionContext(); + + if ( mLayout ) + { +#if 0 //TODO + //connect to atlas feature changes + //to update the expression context + connect( &mLayout->atlasComposition(), &QgsAtlasComposition::featureChanged, this, &QgsLayoutItemLabel::refreshExpressionContext ); +#endif + } + + mWebPage.reset( new QgsWebPage( this ) ); + mWebPage->setIdentifier( tr( "Layout label item" ) ); + mWebPage->setNetworkAccessManager( QgsNetworkAccessManager::instance() ); + + //This makes the background transparent. Found on http://blog.qt.digia.com/blog/2009/06/30/transparent-qwebview-or-qwebpage/ + QPalette palette = mWebPage->palette(); + palette.setBrush( QPalette::Base, Qt::transparent ); + mWebPage->setPalette( palette ); + + mWebPage->mainFrame()->setZoomFactor( 10.0 ); + mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff ); + mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff ); + + connect( mWebPage.get(), &QWebPage::loadFinished, this, &QgsLayoutItemLabel::loadingHtmlFinished ); +} + +QgsLayoutItemLabel *QgsLayoutItemLabel::create( QgsLayout *layout ) +{ + return new QgsLayoutItemLabel( layout ); +} + +int QgsLayoutItemLabel::type() const +{ + return QgsLayoutItemRegistry::LayoutLabel; +} + +QString QgsLayoutItemLabel::stringType() const +{ + return QStringLiteral( "ItemLabel" ); +} + +void QgsLayoutItemLabel::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) +{ + QPainter *painter = context.painter(); + painter->save(); + + // painter is scaled to dots, so scale back to layout units + painter->scale( context.scaleFactor(), context.scaleFactor() ); + + double penWidth = hasFrame() ? ( pen().widthF() / 2.0 ) : 0; + double xPenAdjust = mMarginX < 0 ? -penWidth : penWidth; + double yPenAdjust = mMarginY < 0 ? -penWidth : penWidth; + QRectF painterRect( xPenAdjust + mMarginX, yPenAdjust + mMarginY, rect().width() - 2 * xPenAdjust - 2 * mMarginX, rect().height() - 2 * yPenAdjust - 2 * mMarginY ); + + switch ( mMode ) + { + case ModeHtml: + { + if ( mFirstRender ) + { + contentChanged(); + mFirstRender = false; + } + painter->scale( 1.0 / mHtmlUnitsToLayoutUnits / 10.0, 1.0 / mHtmlUnitsToLayoutUnits / 10.0 ); + mWebPage->setViewportSize( QSize( painterRect.width() * mHtmlUnitsToLayoutUnits * 10.0, painterRect.height() * mHtmlUnitsToLayoutUnits * 10.0 ) ); + mWebPage->settings()->setUserStyleSheetUrl( createStylesheetUrl() ); + mWebPage->mainFrame()->render( painter ); + break; + } + + case ModeFont: + { + const QString textToDraw = currentText(); + painter->setFont( mFont ); + QgsLayoutUtils::drawText( painter, painterRect, textToDraw, mFont, mFontColor, mHAlignment, mVAlignment, Qt::TextWordWrap ); + break; + } + } + + painter->restore(); +} + +void QgsLayoutItemLabel::contentChanged() +{ + switch ( mMode ) + { + case ModeHtml: + { + const QString textToDraw = currentText(); + + //mHtmlLoaded tracks whether the QWebPage has completed loading + //its html contents, set it initially to false. The loadingHtmlFinished slot will + //set this to true after html is loaded. + mHtmlLoaded = false; + + const QUrl baseUrl = QUrl::fromLocalFile( mLayout->project()->fileInfo().absoluteFilePath() ); + mWebPage->mainFrame()->setHtml( textToDraw, baseUrl ); + + //For very basic html labels with no external assets, the html load will already be + //complete before we even get a chance to start the QEventLoop. Make sure we check + //this before starting the loop + if ( !mHtmlLoaded ) + { + //Setup event loop and timeout for rendering html + QEventLoop loop; + + //Connect timeout and webpage loadFinished signals to loop + connect( mWebPage.get(), &QWebPage::loadFinished, &loop, &QEventLoop::quit ); + + // Start a 20 second timeout in case html loading will never complete + QTimer timeoutTimer; + timeoutTimer.setSingleShot( true ); + connect( &timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit ); + timeoutTimer.start( 20000 ); + + // Pause until html is loaded + loop.exec( QEventLoop::ExcludeUserInputEvents ); + } + break; + } + case ModeFont: + break; + } +} + +void QgsLayoutItemLabel::loadingHtmlFinished( bool result ) +{ + Q_UNUSED( result ); + mHtmlLoaded = true; +} + +double QgsLayoutItemLabel::htmlUnitsToLayoutUnits() +{ + if ( !mLayout ) + { + return 1.0; + } + + //TODO : fix this more precisely so that the label's default text size is the same with or without "display as html" + return mLayout->convertToLayoutUnits( QgsLayoutMeasurement( mLayout->context().dpi() / 72.0, QgsUnitTypes::LayoutMillimeters ) ); //webkit seems to assume a standard dpi of 72 +} + +void QgsLayoutItemLabel::setText( const QString &text ) +{ + mText = text; + emit changed(); + + contentChanged(); + + if ( mLayout && id().isEmpty() && mMode != ModeHtml ) + { + //notify the model that the display name has changed + mLayout->itemsModel()->updateItemDisplayName( this ); + } +} + +void QgsLayoutItemLabel::setMode( Mode mode ) +{ + if ( mode == mMode ) + { + return; + } + + mMode = mode; + contentChanged(); + + if ( mLayout && id().isEmpty() ) + { + //notify the model that the display name has changed + mLayout->itemsModel()->updateItemDisplayName( this ); + } +} + +void QgsLayoutItemLabel::refreshExpressionContext() +{ + if ( !mLayout ) + return; + + QgsVectorLayer *layer = nullptr; +#if 0 //TODO + if ( mComposition->atlasComposition().enabled() ) + { + layer = mComposition->atlasComposition().coverageLayer(); + } +#endif + + //setup distance area conversion + if ( layer ) + { + mDistanceArea->setSourceCrs( layer->crs() ); + } + else + { +#if 0 //TODO + //set to composition's reference map's crs + QgsLayoutItemMap *referenceMap = mComposition->referenceMap(); + if ( referenceMap ) + mDistanceArea->setSourceCrs( referenceMap->crs() ); +#endif + } + mDistanceArea->setEllipsoid( mLayout->project()->ellipsoid() ); + contentChanged(); + + update(); +} + +QString QgsLayoutItemLabel::currentText() const +{ + QString displayText = mText; + replaceDateText( displayText ); + + QgsExpressionContext context = createExpressionContext(); + + return QgsExpression::replaceExpressionText( displayText, &context, mDistanceArea.get() ); +} + +void QgsLayoutItemLabel::replaceDateText( QString &text ) const +{ + QString constant = QStringLiteral( "$CURRENT_DATE" ); + int currentDatePos = text.indexOf( constant ); + if ( currentDatePos != -1 ) + { + //check if there is a bracket just after $CURRENT_DATE + QString formatText; + int openingBracketPos = text.indexOf( '(', currentDatePos ); + int closingBracketPos = text.indexOf( ')', openingBracketPos + 1 ); + if ( openingBracketPos != -1 && + closingBracketPos != -1 && + ( closingBracketPos - openingBracketPos ) > 1 && + openingBracketPos == currentDatePos + constant.size() ) + { + formatText = text.mid( openingBracketPos + 1, closingBracketPos - openingBracketPos - 1 ); + text.replace( currentDatePos, closingBracketPos - currentDatePos + 1, QDate::currentDate().toString( formatText ) ); + } + else //no bracket + { + text.replace( QLatin1String( "$CURRENT_DATE" ), QDate::currentDate().toString() ); + } + } +} + +void QgsLayoutItemLabel::setFont( const QFont &f ) +{ + mFont = f; +} + +void QgsLayoutItemLabel::setMargin( const double m ) +{ + mMarginX = m; + mMarginY = m; + prepareGeometryChange(); +} + +void QgsLayoutItemLabel::setMarginX( const double margin ) +{ + mMarginX = margin; + prepareGeometryChange(); +} + +void QgsLayoutItemLabel::setMarginY( const double margin ) +{ + mMarginY = margin; + prepareGeometryChange(); +} + +void QgsLayoutItemLabel::adjustSizeToText() +{ + double textWidth = QgsLayoutUtils::textWidthMM( mFont, currentText() ); + double fontHeight = QgsLayoutUtils::fontHeightMM( mFont ); + + double penWidth = hasFrame() ? ( pen().widthF() / 2.0 ) : 0; + + double width = textWidth + 2 * mMarginX + 2 * penWidth + 1; + double height = fontHeight + 2 * mMarginY + 2 * penWidth; + + //keep alignment point constant + double xShift = 0; + double yShift = 0; + + QSizeF newSize = mLayout->convertToLayoutUnits( QgsLayoutSize( width, height, QgsUnitTypes::LayoutMillimeters ) ); + itemShiftAdjustSize( newSize.width(), newSize.height(), xShift, yShift ); + + //update rect for data defined size and position + attemptSetSceneRect( QRectF( pos().x() + xShift, pos().y() + yShift, width, height ) ); +} + +QFont QgsLayoutItemLabel::font() const +{ + return mFont; +} + +bool QgsLayoutItemLabel::writePropertiesToElement( QDomElement &composerLabelElem, QDomDocument &doc, const QgsReadWriteContext & ) const +{ + composerLabelElem.setAttribute( QStringLiteral( "htmlState" ), static_cast< int >( mMode ) ); + + composerLabelElem.setAttribute( QStringLiteral( "labelText" ), mText ); + composerLabelElem.setAttribute( QStringLiteral( "marginX" ), QString::number( mMarginX ) ); + composerLabelElem.setAttribute( QStringLiteral( "marginY" ), QString::number( mMarginY ) ); + composerLabelElem.setAttribute( QStringLiteral( "halign" ), mHAlignment ); + composerLabelElem.setAttribute( QStringLiteral( "valign" ), mVAlignment ); + + //font + QDomElement labelFontElem = QgsFontUtils::toXmlElement( mFont, doc, QStringLiteral( "LabelFont" ) ); + composerLabelElem.appendChild( labelFontElem ); + + //font color + QDomElement fontColorElem = doc.createElement( QStringLiteral( "FontColor" ) ); + fontColorElem.setAttribute( QStringLiteral( "red" ), mFontColor.red() ); + fontColorElem.setAttribute( QStringLiteral( "green" ), mFontColor.green() ); + fontColorElem.setAttribute( QStringLiteral( "blue" ), mFontColor.blue() ); + composerLabelElem.appendChild( fontColorElem ); + + return true; +} + +bool QgsLayoutItemLabel::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & ) +{ + //restore label specific properties + + //text + mText = itemElem.attribute( QStringLiteral( "labelText" ) ); + + //html state + mMode = static_cast< Mode >( itemElem.attribute( QStringLiteral( "htmlState" ) ).toInt() ); + + //margin + bool marginXOk = false; + bool marginYOk = false; + mMarginX = itemElem.attribute( QStringLiteral( "marginX" ) ).toDouble( &marginXOk ); + mMarginY = itemElem.attribute( QStringLiteral( "marginY" ) ).toDouble( &marginYOk ); + if ( !marginXOk || !marginYOk ) + { + //upgrade old projects where margins where stored in a single attribute + double margin = itemElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "1.0" ) ).toDouble(); + mMarginX = margin; + mMarginY = margin; + } + + //Horizontal alignment + mHAlignment = static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "halign" ) ).toInt() ); + + //Vertical alignment + mVAlignment = static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "valign" ) ).toInt() ); + + //font + QgsFontUtils::setFromXmlChildNode( mFont, itemElem, QStringLiteral( "LabelFont" ) ); + + //font color + QDomNodeList fontColorList = itemElem.elementsByTagName( QStringLiteral( "FontColor" ) ); + if ( !fontColorList.isEmpty() ) + { + QDomElement fontColorElem = fontColorList.at( 0 ).toElement(); + int red = fontColorElem.attribute( QStringLiteral( "red" ), QStringLiteral( "0" ) ).toInt(); + int green = fontColorElem.attribute( QStringLiteral( "green" ), QStringLiteral( "0" ) ).toInt(); + int blue = fontColorElem.attribute( QStringLiteral( "blue" ), QStringLiteral( "0" ) ).toInt(); + mFontColor = QColor( red, green, blue ); + } + else + { + mFontColor = QColor( 0, 0, 0 ); + } + + return true; +} + +QString QgsLayoutItemLabel::displayName() const +{ + if ( !id().isEmpty() ) + { + return id(); + } + + switch ( mMode ) + { + case ModeHtml: + return tr( "" ); + + case ModeFont: + { + + //if no id, default to portion of label text + QString text = mText; + if ( text.isEmpty() ) + { + return tr( "