QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
qgslayoutlabelwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutlabelwidget.cpp
3 ------------------------
4 begin : October 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
21#include "qgsexpressionfinder.h"
22#include "qgslayout.h"
23#include "qgslayoutitemlabel.h"
24#include "qgslayoutitemmap.h"
26#include "qgsprojoperation.h"
27#include "qgsvectorlayer.h"
28
29#include <QAction>
30#include <QColorDialog>
31#include <QFontDialog>
32#include <QMenu>
33#include <QString>
34#include <QWidget>
35
36#include "moc_qgslayoutlabelwidget.cpp"
37
38using namespace Qt::StringLiterals;
39
41 : QgsLayoutItemBaseWidget( nullptr, label )
42 , mLabel( label )
43{
44 Q_ASSERT( mLabel );
45
46 setupUi( this );
47 connect( mHtmlCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutLabelWidget::mHtmlCheckBox_stateChanged );
48 connect( mTextEdit, &QPlainTextEdit::textChanged, this, &QgsLayoutLabelWidget::mTextEdit_textChanged );
49 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutLabelWidget::mInsertExpressionButton_clicked );
50 connect( mMarginXDoubleSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLabelWidget::mMarginXDoubleSpinBox_valueChanged );
51 connect( mMarginYDoubleSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLabelWidget::mMarginYDoubleSpinBox_valueChanged );
52
53 mHAlignmentComboBox->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight | Qt::AlignJustify );
54 mVAlignmentComboBox->setAvailableAlignments( Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom );
55
56 connect( mHAlignmentComboBox, &QgsAlignmentComboBox::changed, this, &QgsLayoutLabelWidget::horizontalAlignmentChanged );
57 connect( mVAlignmentComboBox, &QgsAlignmentComboBox::changed, this, &QgsLayoutLabelWidget::verticalAlignmentChanged );
58
59 setPanelTitle( tr( "Label Properties" ) );
60
61 mFontButton->setMode( QgsFontButton::ModeTextRenderer );
62 mFontButton->setDialogTitle( tr( "Label Font" ) );
63 mFontButton->registerExpressionContextGenerator( this );
64
65 //add widget for general composer item properties
66 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, label );
67 mainLayout->addWidget( mItemPropertiesWidget );
68
69 mMarginXDoubleSpinBox->setClearValue( 0.0 );
70 mMarginYDoubleSpinBox->setClearValue( 0.0 );
71
72 setGuiElementValues();
73 connect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
74
75 connect( mFontButton, &QgsFontButton::changed, this, &QgsLayoutLabelWidget::fontChanged );
76
77 mDynamicTextMenu = new QMenu( this );
78 mDynamicTextButton->setMenu( mDynamicTextMenu );
79
80 connect( mDynamicTextMenu, &QMenu::aboutToShow, this, [this] {
81 mDynamicTextMenu->clear();
82 if ( mLabel->layout() )
83 {
84 // we need to rebuild this on each show, as the content varies depending on other available items...
85 buildInsertDynamicTextMenu( mLabel->layout(), mDynamicTextMenu, [this]( const QString &expression ) {
86 mLabel->beginCommand( tr( "Insert dynamic text" ) );
87 mTextEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
88 mLabel->endCommand();
89 } );
90 }
91 } );
92
93 QMenu *expressionMenu = new QMenu( this );
94 QAction *convertToStaticAction = new QAction( tr( "Convert to Static Text" ), this );
95 expressionMenu->addAction( convertToStaticAction );
96 connect( convertToStaticAction, &QAction::triggered, mLabel, &QgsLayoutItemLabel::convertToStaticText );
97 mInsertExpressionButton->setMenu( expressionMenu );
98
99 mFontButton->setLayer( coverageLayer() );
100 if ( mLabel->layout() )
101 {
102 connect( &mLabel->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mFontButton, &QgsFontButton::setLayer );
103 }
104}
105
107{
108 if ( mItemPropertiesWidget )
109 mItemPropertiesWidget->setMasterLayout( masterLayout );
110}
111
113{
114 return mLabel->createExpressionContext();
115}
116
117void QgsLayoutLabelWidget::buildInsertDynamicTextMenu( QgsLayout *layout, QMenu *menu, const std::function<void( const QString & )> &callback )
118{
119 Q_ASSERT( layout );
120 auto addExpression = [&callback]( QMenu *menu, const QString &name, const QString &expression ) {
121 QAction *action = new QAction( name, menu );
122 connect( action, &QAction::triggered, action, [callback, expression] { callback( expression ); } );
123 menu->addAction( action );
124 };
125
126 QMenu *dateMenu = new QMenu( tr( "Current Date" ), menu );
127 for ( const std::pair<QString, QString> &expression : {
128 std::make_pair( tr( "ISO Format (%1)" ).arg( QDateTime::currentDateTime().toString( u"yyyy-MM-dd"_s ) ), u"format_date(now(), 'yyyy-MM-dd')"_s ),
129 std::make_pair( tr( "Day/Month/Year (%1)" ).arg( QDateTime::currentDateTime().toString( u"dd/MM/yyyy"_s ) ), u"format_date(now(), 'dd/MM/yyyy')"_s ),
130 std::make_pair( tr( "Month/Day/Year (%1)" ).arg( QDateTime::currentDateTime().toString( u"MM/dd/yyyy"_s ) ), u"format_date(now(), 'MM/dd/yyyy')"_s ),
131 } )
132 {
133 addExpression( dateMenu, expression.first, expression.second );
134 }
135 menu->addMenu( dateMenu );
136
137 QMenu *mapsMenu = new QMenu( tr( "Map Properties" ), menu );
138 QList<QgsLayoutItemMap *> maps;
139 layout->layoutItems( maps );
140 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
141 {
142 // these expressions require the map to have a non-empty ID set
143 if ( map->id().isEmpty() )
144 continue;
145
146 QMenu *mapMenu = new QMenu( map->displayName(), mapsMenu );
147 for ( const std::pair<QString, QString> &expression : {
148 std::make_pair( tr( "Scale (%1)" ).arg( map->scale() ), u"format_number(item_variables('%1')['map_scale'], places:=6, omit_group_separators:=true, trim_trailing_zeroes:=true)"_s.arg( map->id() ) ),
149 std::make_pair( tr( "Rotation (%1)" ).arg( map->rotation() ), u"item_variables('%1')['map_rotation']"_s.arg( map->id() ) ),
150 } )
151 {
152 addExpression( mapMenu, expression.first, expression.second );
153 }
154 mapMenu->addSeparator();
155 for ( const std::pair<QString, QString> &expression : {
156 std::make_pair( tr( "CRS Identifier (%1)" ).arg( map->crs().authid() ), u"item_variables('%1')['map_crs']"_s.arg( map->id() ) ),
157 std::make_pair( tr( "CRS Name (%1)" ).arg( map->crs().description() ), u"item_variables('%1')['map_crs_description']"_s.arg( map->id() ) ),
158 std::make_pair( tr( "Ellipsoid Name (%1)" ).arg( map->crs().ellipsoidAcronym() ), u"item_variables('%1')['map_crs_ellipsoid']"_s.arg( map->id() ) ),
159 std::make_pair( tr( "Units (%1)" ).arg( QgsUnitTypes::toString( map->crs().mapUnits() ) ), u"item_variables('%1')['map_units']"_s.arg( map->id() ) ),
160 std::make_pair( tr( "Projection (%1)" ).arg( map->crs().operation().description() ), u"item_variables('%1')['map_crs_projection']"_s.arg( map->id() ) ),
161 } )
162 {
163 addExpression( mapMenu, expression.first, expression.second );
164 }
165 mapMenu->addSeparator();
166
167 const QgsRectangle mapExtent = map->extent();
168 const QgsPointXY center = mapExtent.center();
169 for ( const std::pair<QString, QString> &expression : {
170 std::make_pair( tr( "Center (X) (%1)" ).arg( center.x() ), u"x(item_variables('%1')['map_extent_center'])"_s.arg( map->id() ) ),
171 std::make_pair( tr( "Center (Y) (%1)" ).arg( center.y() ), u"y(item_variables('%1')['map_extent_center'])"_s.arg( map->id() ) ),
172 std::make_pair( tr( "X Minimum (%1)" ).arg( mapExtent.xMinimum() ), u"x_min(item_variables('%1')['map_extent'])"_s.arg( map->id() ) ),
173 std::make_pair( tr( "Y Minimum (%1)" ).arg( mapExtent.yMinimum() ), u"y_min(item_variables('%1')['map_extent'])"_s.arg( map->id() ) ),
174 std::make_pair( tr( "X Maximum (%1)" ).arg( mapExtent.xMaximum() ), u"x_max(item_variables('%1')['map_extent'])"_s.arg( map->id() ) ),
175 std::make_pair( tr( "Y Maximum (%1)" ).arg( mapExtent.yMaximum() ), u"y_max(item_variables('%1')['map_extent'])"_s.arg( map->id() ) ),
176 } )
177 {
178 addExpression( mapMenu, expression.first, expression.second );
179 }
180 mapMenu->addSeparator();
181 for ( const std::pair<QString, QString> &expression : {
182 std::make_pair( tr( "Layer Credits" ), u"array_to_string(map_credits('%1'))"_s.arg( map->id() ) ),
183 } )
184 {
185 addExpression( mapMenu, expression.first, expression.second );
186 }
187 mapsMenu->addMenu( mapMenu );
188 }
189 menu->addMenu( mapsMenu );
190 menu->addSeparator();
191
192 if ( layout->reportContext().layer() )
193 {
194 const QgsFields fields = layout->reportContext().layer()->fields();
195
196 QMenu *fieldsMenu = new QMenu( tr( "Field" ), menu );
197 for ( const QgsField &field : fields )
198 {
199 addExpression( fieldsMenu, field.displayName(), u"\"%1\""_s.arg( field.name() ) );
200 }
201
202 menu->addMenu( fieldsMenu );
203 menu->addSeparator();
204 }
205
206 for ( const std::pair<QString, QString> &expression :
207 { std::make_pair( tr( "Layout Name" ), u"@layout_name"_s ),
208 std::make_pair( tr( "Layout Page Number" ), u"@layout_page"_s ),
209 std::make_pair( tr( "Layout Page Count" ), u"@layout_numpages"_s ),
210 std::make_pair( tr( "Layer Credits" ), u"array_to_string(map_credits())"_s ) } )
211 {
212 addExpression( menu, expression.first, expression.second );
213 }
214 menu->addSeparator();
215 for ( const std::pair<QString, QString> &expression :
216 { std::make_pair( tr( "Project Author" ), u"@project_author"_s ), std::make_pair( tr( "Project Title" ), u"@project_title"_s ), std::make_pair( tr( "Project Path" ), u"@project_path"_s ) } )
217 {
218 addExpression( menu, expression.first, expression.second );
219 }
220 menu->addSeparator();
221 for ( const std::pair<QString, QString> &expression : { std::make_pair( tr( "Current User Name" ), u"@user_full_name"_s ), std::make_pair( tr( "Current User Account" ), u"@user_account_name"_s ) } )
222 {
223 addExpression( menu, expression.first, expression.second );
224 }
225}
226
228{
230 return false;
231
232 if ( mLabel )
233 {
234 disconnect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
235 }
236
237 mLabel = qobject_cast<QgsLayoutItemLabel *>( item );
238 mItemPropertiesWidget->setItem( mLabel );
239
240 if ( mLabel )
241 {
242 connect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
243 }
244
245 setGuiElementValues();
246
247 return true;
248}
249
250void QgsLayoutLabelWidget::mHtmlCheckBox_stateChanged( int state )
251{
252 if ( mLabel )
253 {
254 mVerticalAlignmentLabel->setDisabled( state );
255 mVAlignmentComboBox->setDisabled( state );
256
257 mLabel->beginCommand( tr( "Change Label Mode" ) );
258 mLabel->blockSignals( true );
260 mLabel->setText( mTextEdit->toPlainText() );
261 mLabel->update();
262 mLabel->blockSignals( false );
263 mLabel->endCommand();
264 }
265}
266
267void QgsLayoutLabelWidget::mTextEdit_textChanged()
268{
269 if ( mLabel )
270 {
271 mLabel->beginCommand( tr( "Change Label Text" ), QgsLayoutItem::UndoLabelText );
272 mLabel->blockSignals( true );
273 mLabel->setText( mTextEdit->toPlainText() );
274 mLabel->update();
275 mLabel->blockSignals( false );
276 mLabel->endCommand();
277 }
278}
279
280void QgsLayoutLabelWidget::fontChanged()
281{
282 if ( mLabel )
283 {
284 mLabel->beginCommand( tr( "Change Label Font" ), QgsLayoutItem::UndoLabelFont );
285 mLabel->setTextFormat( mFontButton->textFormat() );
286 mLabel->update();
287 mLabel->endCommand();
288 }
289}
290
291void QgsLayoutLabelWidget::mMarginXDoubleSpinBox_valueChanged( double d )
292{
293 if ( mLabel )
294 {
295 mLabel->beginCommand( tr( "Change Label Margin" ), QgsLayoutItem::UndoLabelMargin );
296 mLabel->setMarginX( d );
297 mLabel->update();
298 mLabel->endCommand();
299 }
300}
301
302void QgsLayoutLabelWidget::mMarginYDoubleSpinBox_valueChanged( double d )
303{
304 if ( mLabel )
305 {
306 mLabel->beginCommand( tr( "Change Label Margin" ), QgsLayoutItem::UndoLabelMargin );
307 mLabel->setMarginY( d );
308 mLabel->update();
309 mLabel->endCommand();
310 }
311}
312
313void QgsLayoutLabelWidget::mInsertExpressionButton_clicked()
314{
315 if ( !mLabel )
316 {
317 return;
318 }
319
320 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mTextEdit );
321
322 // use the atlas coverage layer, if any
323 QgsVectorLayer *layer = coverageLayer();
324
325 QgsExpressionContext context = mLabel->createExpressionContext();
326 QgsExpressionBuilderDialog exprDlg( layer, expression, this, u"generic"_s, context );
327
328 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
329 if ( exprDlg.exec() == QDialog::Accepted )
330 {
331 expression = exprDlg.expressionText();
332 if ( !expression.isEmpty() )
333 {
334 mLabel->beginCommand( tr( "Insert expression" ) );
335 mTextEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
336 mLabel->endCommand();
337 }
338 }
339}
340
341void QgsLayoutLabelWidget::horizontalAlignmentChanged()
342{
343 if ( mLabel )
344 {
345 mLabel->beginCommand( tr( "Change Label Alignment" ) );
346 mLabel->setHAlign( static_cast<Qt::AlignmentFlag>( static_cast<int>( mHAlignmentComboBox->currentAlignment() ) ) );
347 mLabel->update();
348 mLabel->endCommand();
349 }
350}
351
352void QgsLayoutLabelWidget::verticalAlignmentChanged()
353{
354 if ( mLabel )
355 {
356 mLabel->beginCommand( tr( "Change Label Alignment" ) );
357 mLabel->setVAlign( static_cast<Qt::AlignmentFlag>( static_cast<int>( mVAlignmentComboBox->currentAlignment() ) ) );
358 mLabel->update();
359 mLabel->endCommand();
360 }
361}
362
363void QgsLayoutLabelWidget::setGuiElementValues()
364{
365 blockAllSignals( true );
366 mTextEdit->setPlainText( mLabel->text() );
367 mTextEdit->moveCursor( QTextCursor::End, QTextCursor::MoveAnchor );
368 mMarginXDoubleSpinBox->setValue( mLabel->marginX() );
369 mMarginYDoubleSpinBox->setValue( mLabel->marginY() );
370 mHtmlCheckBox->setChecked( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
371
372 mHAlignmentComboBox->setCurrentAlignment( mLabel->hAlign() );
373 mVAlignmentComboBox->setCurrentAlignment( mLabel->vAlign() );
374
375 mFontButton->setTextFormat( mLabel->textFormat() );
376 mVerticalAlignmentLabel->setDisabled( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
377 mVAlignmentComboBox->setDisabled( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
378
379 blockAllSignals( false );
380}
381
382void QgsLayoutLabelWidget::blockAllSignals( bool block )
383{
384 mTextEdit->blockSignals( block );
385 mHtmlCheckBox->blockSignals( block );
386 mMarginXDoubleSpinBox->blockSignals( block );
387 mMarginYDoubleSpinBox->blockSignals( block );
388 mHAlignmentComboBox->blockSignals( block );
389 mVAlignmentComboBox->blockSignals( block );
390 mFontButton->blockSignals( block );
391}
void changed()
Emitted when the alignment is changed.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static QString findAndSelectActiveExpression(QgsCodeEditor *editor, const QString &pattern=QString())
Find the expression under the cursor in the given editor and select it.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:46
@ ModeTextRenderer
Configure font settings for use with QgsTextRenderer.
void setLayer(QgsVectorLayer *layer)
Sets a layer to associate with the widget.
void changed()
Emitted when the widget's text format settings are changed.
QgsVectorLayer * coverageLayer() const
Returns the current layout context coverage layer (if set).
QgsLayoutItemBaseWidget(QWidget *parent SIP_TRANSFERTHIS, QgsLayoutObject *layoutObject)
Constructor for QgsLayoutItemBaseWidget, linked with the specified layoutObject.
A layout item subclass for text labels.
void convertToStaticText()
Converts the label's text() to a static string, by evaluating any expressions included in the text an...
@ ModeHtml
Label displays rendered HTML content.
@ ModeFont
Label displays text rendered using a single font.
Layout graphical items for displaying a map.
A widget for controlling the common properties of layout items (e.g.
Base class for graphical items within a QgsLayout.
@ UndoLabelMargin
Label margin.
@ UndoLabelFont
Label font.
@ UndoLabelText
Label text.
int type() const override
Returns a unique graphics item type identifier.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout) override
Sets the master layout associated with the item.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool setNewItem(QgsLayoutItem *item) override
Attempts to update the widget to show the properties for the specified item.
static void buildInsertDynamicTextMenu(QgsLayout *layout, QMenu *menu, const std::function< void(const QString &expression)> &callback)
Populates the specified menu with actions reflecting dynamic text expressions applicable for a layout...
QgsLayoutLabelWidget(QgsLayoutItemLabel *label)
constructor
void changed()
Emitted when the object's properties change.
void layerChanged(QgsVectorLayer *layer)
Emitted when the context's layer is changed.
QgsVectorLayer * layer() const
Returns the vector layer associated with the layout's context.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:50
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition qgslayout.h:120
QgsLayoutReportContext & reportContext()
Returns a reference to the layout's report context, which stores information relating to the current ...
Interface for master layout type objects, such as print layouts and reports.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
QgsPointXY center
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.