QGIS API Documentation 3.41.0-Master (af5edcb665c)
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#include "moc_qgslayoutlabelwidget.cpp"
20#include "qgslayoutitemlabel.h"
21#include "qgslayout.h"
23#include "qgslayoutitemmap.h"
24#include "qgsvectorlayer.h"
25#include "qgsprojoperation.h"
27#include "qgsexpressionfinder.h"
28
29#include <QColorDialog>
30#include <QFontDialog>
31#include <QWidget>
32#include <QAction>
33#include <QMenu>
34
36 : QgsLayoutItemBaseWidget( nullptr, label )
37 , mLabel( label )
38{
39 Q_ASSERT( mLabel );
40
41 setupUi( this );
42 connect( mHtmlCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutLabelWidget::mHtmlCheckBox_stateChanged );
43 connect( mTextEdit, &QPlainTextEdit::textChanged, this, &QgsLayoutLabelWidget::mTextEdit_textChanged );
44 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutLabelWidget::mInsertExpressionButton_clicked );
45 connect( mMarginXDoubleSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLabelWidget::mMarginXDoubleSpinBox_valueChanged );
46 connect( mMarginYDoubleSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLabelWidget::mMarginYDoubleSpinBox_valueChanged );
47
48 mHAlignmentComboBox->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight | Qt::AlignJustify );
49 mVAlignmentComboBox->setAvailableAlignments( Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom );
50
51 connect( mHAlignmentComboBox, &QgsAlignmentComboBox::changed, this, &QgsLayoutLabelWidget::horizontalAlignmentChanged );
52 connect( mVAlignmentComboBox, &QgsAlignmentComboBox::changed, this, &QgsLayoutLabelWidget::verticalAlignmentChanged );
53
54 setPanelTitle( tr( "Label Properties" ) );
55
56 mFontButton->setMode( QgsFontButton::ModeTextRenderer );
57 mFontButton->setDialogTitle( tr( "Label Font" ) );
58 mFontButton->registerExpressionContextGenerator( this );
59
60 //add widget for general composer item properties
61 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, label );
62 mainLayout->addWidget( mItemPropertiesWidget );
63
64 mMarginXDoubleSpinBox->setClearValue( 0.0 );
65 mMarginYDoubleSpinBox->setClearValue( 0.0 );
66
67 setGuiElementValues();
68 connect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
69
70 connect( mFontButton, &QgsFontButton::changed, this, &QgsLayoutLabelWidget::fontChanged );
71
72 mDynamicTextMenu = new QMenu( this );
73 mDynamicTextButton->setMenu( mDynamicTextMenu );
74
75 connect( mDynamicTextMenu, &QMenu::aboutToShow, this, [=] {
76 mDynamicTextMenu->clear();
77 if ( mLabel->layout() )
78 {
79 // we need to rebuild this on each show, as the content varies depending on other available items...
80 buildInsertDynamicTextMenu( mLabel->layout(), mDynamicTextMenu, [=]( const QString &expression ) {
81 mLabel->beginCommand( tr( "Insert dynamic text" ) );
82 mTextEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
83 mLabel->endCommand();
84 } );
85 }
86 } );
87
88 QMenu *expressionMenu = new QMenu( this );
89 QAction *convertToStaticAction = new QAction( tr( "Convert to Static Text" ), this );
90 expressionMenu->addAction( convertToStaticAction );
91 connect( convertToStaticAction, &QAction::triggered, mLabel, &QgsLayoutItemLabel::convertToStaticText );
92 mInsertExpressionButton->setMenu( expressionMenu );
93
94 mFontButton->setLayer( coverageLayer() );
95 if ( mLabel->layout() )
96 {
97 connect( &mLabel->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mFontButton, &QgsFontButton::setLayer );
98 }
99}
100
102{
103 if ( mItemPropertiesWidget )
104 mItemPropertiesWidget->setMasterLayout( masterLayout );
105}
106
108{
109 return mLabel->createExpressionContext();
110}
111
112void QgsLayoutLabelWidget::buildInsertDynamicTextMenu( QgsLayout *layout, QMenu *menu, const std::function<void( const QString & )> &callback )
113{
114 Q_ASSERT( layout );
115 auto addExpression = [&callback]( QMenu *menu, const QString &name, const QString &expression ) {
116 QAction *action = new QAction( name, menu );
117 connect( action, &QAction::triggered, action, [callback, expression] {
118 callback( expression );
119 } );
120 menu->addAction( action );
121 };
122
123 QMenu *dateMenu = new QMenu( tr( "Current Date" ), menu );
124 for ( const std::pair<QString, QString> &expression :
125 {
126 std::make_pair( tr( "ISO Format (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "yyyy-MM-dd" ) ) ), QStringLiteral( "format_date(now(), 'yyyy-MM-dd')" ) ),
127 std::make_pair( tr( "Day/Month/Year (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "dd/MM/yyyy" ) ) ), QStringLiteral( "format_date(now(), 'dd/MM/yyyy')" ) ),
128 std::make_pair( tr( "Month/Day/Year (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "MM/dd/yyyy" ) ) ), QStringLiteral( "format_date(now(), 'MM/dd/yyyy')" ) ),
129 } )
130 {
131 addExpression( dateMenu, expression.first, expression.second );
132 }
133 menu->addMenu( dateMenu );
134
135 QMenu *mapsMenu = new QMenu( tr( "Map Properties" ), menu );
136 QList<QgsLayoutItemMap *> maps;
137 layout->layoutItems( maps );
138 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
139 {
140 // these expressions require the map to have a non-empty ID set
141 if ( map->id().isEmpty() )
142 continue;
143
144 QMenu *mapMenu = new QMenu( map->displayName(), mapsMenu );
145 for ( const std::pair<QString, QString> &expression :
146 {
147 std::make_pair( tr( "Scale (%1)" ).arg( map->scale() ), QStringLiteral( "format_number(item_variables('%1')['map_scale'], places:=6, omit_group_separators:=true, trim_trailing_zeroes:=true)" ).arg( map->id() ) ),
148 std::make_pair( tr( "Rotation (%1)" ).arg( map->rotation() ), QStringLiteral( "item_variables('%1')['map_rotation']" ).arg( map->id() ) ),
149 } )
150 {
151 addExpression( mapMenu, expression.first, expression.second );
152 }
153 mapMenu->addSeparator();
154 for ( const std::pair<QString, QString> &expression :
155 {
156 std::make_pair( tr( "CRS Identifier (%1)" ).arg( map->crs().authid() ), QStringLiteral( "item_variables('%1')['map_crs']" ).arg( map->id() ) ),
157 std::make_pair( tr( "CRS Name (%1)" ).arg( map->crs().description() ), QStringLiteral( "item_variables('%1')['map_crs_description']" ).arg( map->id() ) ),
158 std::make_pair( tr( "Ellipsoid Name (%1)" ).arg( map->crs().ellipsoidAcronym() ), QStringLiteral( "item_variables('%1')['map_crs_ellipsoid']" ).arg( map->id() ) ),
159 std::make_pair( tr( "Units (%1)" ).arg( QgsUnitTypes::toString( map->crs().mapUnits() ) ), QStringLiteral( "item_variables('%1')['map_units']" ).arg( map->id() ) ),
160 std::make_pair( tr( "Projection (%1)" ).arg( map->crs().operation().description() ), QStringLiteral( "item_variables('%1')['map_crs_projection']" ).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 {
171 std::make_pair( tr( "Center (X) (%1)" ).arg( center.x() ), QStringLiteral( "x(item_variables('%1')['map_extent_center'])" ).arg( map->id() ) ),
172 std::make_pair( tr( "Center (Y) (%1)" ).arg( center.y() ), QStringLiteral( "y(item_variables('%1')['map_extent_center'])" ).arg( map->id() ) ),
173 std::make_pair( tr( "X Minimum (%1)" ).arg( mapExtent.xMinimum() ), QStringLiteral( "x_min(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
174 std::make_pair( tr( "Y Minimum (%1)" ).arg( mapExtent.yMinimum() ), QStringLiteral( "y_min(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
175 std::make_pair( tr( "X Maximum (%1)" ).arg( mapExtent.xMaximum() ), QStringLiteral( "x_max(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
176 std::make_pair( tr( "Y Maximum (%1)" ).arg( mapExtent.yMaximum() ), QStringLiteral( "y_max(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
177 } )
178 {
179 addExpression( mapMenu, expression.first, expression.second );
180 }
181 mapMenu->addSeparator();
182 for ( const std::pair<QString, QString> &expression :
183 {
184 std::make_pair( tr( "Layer Credits" ), QStringLiteral( "array_to_string(map_credits('%1'))" ).arg( map->id() ) ),
185 } )
186 {
187 addExpression( mapMenu, expression.first, expression.second );
188 }
189 mapsMenu->addMenu( mapMenu );
190 }
191 menu->addMenu( mapsMenu );
192 menu->addSeparator();
193
194 if ( layout->reportContext().layer() )
195 {
196 const QgsFields fields = layout->reportContext().layer()->fields();
197
198 QMenu *fieldsMenu = new QMenu( tr( "Field" ), menu );
199 for ( const QgsField &field : fields )
200 {
201 addExpression( fieldsMenu, field.displayName(), QStringLiteral( "\"%1\"" ).arg( field.name() ) );
202 }
203
204 menu->addMenu( fieldsMenu );
205 menu->addSeparator();
206 }
207
208 for ( const std::pair<QString, QString> &expression :
209 {
210 std::make_pair( tr( "Layout Name" ), QStringLiteral( "@layout_name" ) ),
211 std::make_pair( tr( "Layout Page Number" ), QStringLiteral( "@layout_page" ) ),
212 std::make_pair( tr( "Layout Page Count" ), QStringLiteral( "@layout_numpages" ) ),
213 std::make_pair( tr( "Layer Credits" ), QStringLiteral( "array_to_string(map_credits())" ) )
214 } )
215 {
216 addExpression( menu, expression.first, expression.second );
217 }
218 menu->addSeparator();
219 for ( const std::pair<QString, QString> &expression :
220 {
221 std::make_pair( tr( "Project Author" ), QStringLiteral( "@project_author" ) ),
222 std::make_pair( tr( "Project Title" ), QStringLiteral( "@project_title" ) ),
223 std::make_pair( tr( "Project Path" ), QStringLiteral( "@project_path" ) )
224 } )
225 {
226 addExpression( menu, expression.first, expression.second );
227 }
228 menu->addSeparator();
229 for ( const std::pair<QString, QString> &expression :
230 {
231 std::make_pair( tr( "Current User Name" ), QStringLiteral( "@user_full_name" ) ),
232 std::make_pair( tr( "Current User Account" ), QStringLiteral( "@user_account_name" ) )
233 } )
234 {
235 addExpression( menu, expression.first, expression.second );
236 }
237}
238
240{
242 return false;
243
244 if ( mLabel )
245 {
246 disconnect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
247 }
248
249 mLabel = qobject_cast<QgsLayoutItemLabel *>( item );
250 mItemPropertiesWidget->setItem( mLabel );
251
252 if ( mLabel )
253 {
254 connect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
255 }
256
257 setGuiElementValues();
258
259 return true;
260}
261
262void QgsLayoutLabelWidget::mHtmlCheckBox_stateChanged( int state )
263{
264 if ( mLabel )
265 {
266 mVerticalAlignementLabel->setDisabled( state );
267 mVAlignmentComboBox->setDisabled( state );
268
269 mLabel->beginCommand( tr( "Change Label Mode" ) );
270 mLabel->blockSignals( true );
271 mLabel->setMode( state ? QgsLayoutItemLabel::ModeHtml : QgsLayoutItemLabel::ModeFont );
272 mLabel->setText( mTextEdit->toPlainText() );
273 mLabel->update();
274 mLabel->blockSignals( false );
275 mLabel->endCommand();
276 }
277}
278
279void QgsLayoutLabelWidget::mTextEdit_textChanged()
280{
281 if ( mLabel )
282 {
283 mLabel->beginCommand( tr( "Change Label Text" ), QgsLayoutItem::UndoLabelText );
284 mLabel->blockSignals( true );
285 mLabel->setText( mTextEdit->toPlainText() );
286 mLabel->update();
287 mLabel->blockSignals( false );
288 mLabel->endCommand();
289 }
290}
291
292void QgsLayoutLabelWidget::fontChanged()
293{
294 if ( mLabel )
295 {
296 mLabel->beginCommand( tr( "Change Label Font" ), QgsLayoutItem::UndoLabelFont );
297 mLabel->setTextFormat( mFontButton->textFormat() );
298 mLabel->update();
299 mLabel->endCommand();
300 }
301}
302
303void QgsLayoutLabelWidget::mMarginXDoubleSpinBox_valueChanged( double d )
304{
305 if ( mLabel )
306 {
307 mLabel->beginCommand( tr( "Change Label Margin" ), QgsLayoutItem::UndoLabelMargin );
308 mLabel->setMarginX( d );
309 mLabel->update();
310 mLabel->endCommand();
311 }
312}
313
314void QgsLayoutLabelWidget::mMarginYDoubleSpinBox_valueChanged( double d )
315{
316 if ( mLabel )
317 {
318 mLabel->beginCommand( tr( "Change Label Margin" ), QgsLayoutItem::UndoLabelMargin );
319 mLabel->setMarginY( d );
320 mLabel->update();
321 mLabel->endCommand();
322 }
323}
324
325void QgsLayoutLabelWidget::mInsertExpressionButton_clicked()
326{
327 if ( !mLabel )
328 {
329 return;
330 }
331
332 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mTextEdit );
333
334 // use the atlas coverage layer, if any
335 QgsVectorLayer *layer = coverageLayer();
336
337 QgsExpressionContext context = mLabel->createExpressionContext();
338 QgsExpressionBuilderDialog exprDlg( layer, expression, this, QStringLiteral( "generic" ), context );
339
340 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
341 if ( exprDlg.exec() == QDialog::Accepted )
342 {
343 expression = exprDlg.expressionText();
344 if ( !expression.isEmpty() )
345 {
346 mLabel->beginCommand( tr( "Insert expression" ) );
347 mTextEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
348 mLabel->endCommand();
349 }
350 }
351}
352
353void QgsLayoutLabelWidget::horizontalAlignmentChanged()
354{
355 if ( mLabel )
356 {
357 mLabel->beginCommand( tr( "Change Label Alignment" ) );
358 mLabel->setHAlign( static_cast<Qt::AlignmentFlag>( static_cast<int>( mHAlignmentComboBox->currentAlignment() ) ) );
359 mLabel->update();
360 mLabel->endCommand();
361 }
362}
363
364void QgsLayoutLabelWidget::verticalAlignmentChanged()
365{
366 if ( mLabel )
367 {
368 mLabel->beginCommand( tr( "Change Label Alignment" ) );
369 mLabel->setVAlign( static_cast<Qt::AlignmentFlag>( static_cast<int>( mVAlignmentComboBox->currentAlignment() ) ) );
370 mLabel->update();
371 mLabel->endCommand();
372 }
373}
374
375void QgsLayoutLabelWidget::setGuiElementValues()
376{
377 blockAllSignals( true );
378 mTextEdit->setPlainText( mLabel->text() );
379 mTextEdit->moveCursor( QTextCursor::End, QTextCursor::MoveAnchor );
380 mMarginXDoubleSpinBox->setValue( mLabel->marginX() );
381 mMarginYDoubleSpinBox->setValue( mLabel->marginY() );
382 mHtmlCheckBox->setChecked( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
383
384 mHAlignmentComboBox->setCurrentAlignment( mLabel->hAlign() );
385 mVAlignmentComboBox->setCurrentAlignment( mLabel->vAlign() );
386
387 mFontButton->setTextFormat( mLabel->textFormat() );
388 mVerticalAlignementLabel->setDisabled( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
389 mVAlignmentComboBox->setDisabled( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
390
391 blockAllSignals( false );
392}
393
394void QgsLayoutLabelWidget::blockAllSignals( bool block )
395{
396 mTextEdit->blockSignals( block );
397 mHtmlCheckBox->blockSignals( block );
398 mMarginXDoubleSpinBox->blockSignals( block );
399 mMarginYDoubleSpinBox->blockSignals( block );
400 mHAlignmentComboBox->blockSignals( block );
401 mVAlignmentComboBox->blockSignals( block );
402 mFontButton->blockSignals( block );
403}
void changed()
Emitted when the alignment is changed.
A generic dialog for building expression strings.
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:53
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.
A base class for property widgets for layout items.
QgsVectorLayer * coverageLayer() const
Returns the current layout context coverage layer (if set).
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.
Layout graphical items for displaying a map.
A widget for controlling the common properties of layout items (e.g.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout)
Sets the master layout associated with the item.
void setItem(QgsLayoutItem *item)
Sets the layout item.
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:49
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.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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.
Represents a vector layer which manages a vector based data sets.