QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 {
77 mDynamicTextMenu->clear();
78 if ( mLabel->layout() )
79 {
80 // we need to rebuild this on each show, as the content varies depending on other available items...
81 buildInsertDynamicTextMenu( mLabel->layout(), mDynamicTextMenu, [ = ]( const QString & expression )
82 {
83 mLabel->beginCommand( tr( "Insert dynamic text" ) );
84 mTextEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
85 mLabel->endCommand();
86 } );
87 }
88 } );
89
90 QMenu *expressionMenu = new QMenu( this );
91 QAction *convertToStaticAction = new QAction( tr( "Convert to Static Text" ), this );
92 expressionMenu->addAction( convertToStaticAction );
93 connect( convertToStaticAction, &QAction::triggered, mLabel, &QgsLayoutItemLabel::convertToStaticText );
94 mInsertExpressionButton->setMenu( expressionMenu );
95
96 mFontButton->setLayer( coverageLayer() );
97 if ( mLabel->layout() )
98 {
99 connect( &mLabel->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mFontButton, &QgsFontButton::setLayer );
100 }
101}
102
104{
105 if ( mItemPropertiesWidget )
106 mItemPropertiesWidget->setMasterLayout( masterLayout );
107}
108
110{
111 return mLabel->createExpressionContext();
112}
113
114void QgsLayoutLabelWidget::buildInsertDynamicTextMenu( QgsLayout *layout, QMenu *menu, const std::function<void ( const QString & )> &callback )
115{
116 Q_ASSERT( layout );
117 auto addExpression = [&callback]( QMenu * menu, const QString & name, const QString & expression )
118 {
119 QAction *action = new QAction( name, menu );
120 connect( action, &QAction::triggered, action, [callback, expression]
121 {
122 callback( expression );
123 } );
124 menu->addAction( action );
125 };
126
127 QMenu *dateMenu = new QMenu( tr( "Current Date" ), menu );
128 for ( const std::pair< QString, QString > &expression :
129 {
130 std::make_pair( tr( "ISO Format (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "yyyy-MM-dd" ) ) ), QStringLiteral( "format_date(now(), 'yyyy-MM-dd')" ) ),
131 std::make_pair( tr( "Day/Month/Year (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "dd/MM/yyyy" ) ) ), QStringLiteral( "format_date(now(), 'dd/MM/yyyy')" ) ),
132 std::make_pair( tr( "Month/Day/Year (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "MM/dd/yyyy" ) ) ), QStringLiteral( "format_date(now(), 'MM/dd/yyyy')" ) ),
133 } )
134 {
135 addExpression( dateMenu, expression.first, expression.second );
136 }
137 menu->addMenu( dateMenu );
138
139 QMenu *mapsMenu = new QMenu( tr( "Map Properties" ), menu );
140 QList< QgsLayoutItemMap * > maps;
141 layout->layoutItems( maps );
142 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
143 {
144 // these expressions require the map to have a non-empty ID set
145 if ( map->id().isEmpty() )
146 continue;
147
148 QMenu *mapMenu = new QMenu( map->displayName(), mapsMenu );
149 for ( const std::pair< QString, QString > &expression :
150 {
151 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() ) ),
152 std::make_pair( tr( "Rotation (%1)" ).arg( map->rotation() ), QStringLiteral( "item_variables('%1')['map_rotation']" ).arg( map->id() ) ),
153 } )
154 {
155 addExpression( mapMenu, expression.first, expression.second );
156 }
157 mapMenu->addSeparator();
158 for ( const std::pair< QString, QString > &expression :
159 {
160 std::make_pair( tr( "CRS Identifier (%1)" ).arg( map->crs().authid() ), QStringLiteral( "item_variables('%1')['map_crs']" ).arg( map->id() ) ),
161 std::make_pair( tr( "CRS Name (%1)" ).arg( map->crs().description() ), QStringLiteral( "item_variables('%1')['map_crs_description']" ).arg( map->id() ) ),
162 std::make_pair( tr( "Ellipsoid Name (%1)" ).arg( map->crs().ellipsoidAcronym() ), QStringLiteral( "item_variables('%1')['map_crs_ellipsoid']" ).arg( map->id() ) ),
163 std::make_pair( tr( "Units (%1)" ).arg( QgsUnitTypes::toString( map->crs().mapUnits() ) ), QStringLiteral( "item_variables('%1')['map_units']" ).arg( map->id() ) ),
164 std::make_pair( tr( "Projection (%1)" ).arg( map->crs().operation().description() ), QStringLiteral( "item_variables('%1')['map_crs_projection']" ).arg( map->id() ) ),
165 } )
166 {
167 addExpression( mapMenu, expression.first, expression.second );
168 }
169 mapMenu->addSeparator();
170
171 const QgsRectangle mapExtent = map->extent();
172 const QgsPointXY center = mapExtent.center();
173 for ( const std::pair< QString, QString > &expression :
174 {
175 std::make_pair( tr( "Center (X) (%1)" ).arg( center.x() ), QStringLiteral( "x(item_variables('%1')['map_extent_center'])" ).arg( map->id() ) ),
176 std::make_pair( tr( "Center (Y) (%1)" ).arg( center.y() ), QStringLiteral( "y(item_variables('%1')['map_extent_center'])" ).arg( map->id() ) ),
177 std::make_pair( tr( "X Minimum (%1)" ).arg( mapExtent.xMinimum() ), QStringLiteral( "x_min(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
178 std::make_pair( tr( "Y Minimum (%1)" ).arg( mapExtent.yMinimum() ), QStringLiteral( "y_min(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
179 std::make_pair( tr( "X Maximum (%1)" ).arg( mapExtent.xMaximum() ), QStringLiteral( "x_max(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
180 std::make_pair( tr( "Y Maximum (%1)" ).arg( mapExtent.yMaximum() ), QStringLiteral( "y_max(item_variables('%1')['map_extent'])" ).arg( map->id() ) ),
181 } )
182 {
183 addExpression( mapMenu, expression.first, expression.second );
184 }
185 mapMenu->addSeparator();
186 for ( const std::pair< QString, QString > &expression :
187 {
188 std::make_pair( tr( "Layer Credits" ), QStringLiteral( "array_to_string(map_credits('%1'))" ).arg( map->id() ) ),
189 } )
190 {
191 addExpression( mapMenu, expression.first, expression.second );
192 }
193 mapsMenu->addMenu( mapMenu );
194 }
195 menu->addMenu( mapsMenu );
196 menu->addSeparator();
197
198 if ( layout->reportContext().layer() )
199 {
200 const QgsFields fields = layout->reportContext().layer()->fields();
201
202 QMenu *fieldsMenu = new QMenu( tr( "Field" ), menu );
203 for ( const QgsField &field : fields )
204 {
205 addExpression( fieldsMenu, field.displayName(), QStringLiteral( "\"%1\"" ).arg( field.name() ) );
206 }
207
208 menu->addMenu( fieldsMenu );
209 menu->addSeparator();
210 }
211
212 for ( const std::pair< QString, QString > &expression :
213 {
214 std::make_pair( tr( "Layout Name" ), QStringLiteral( "@layout_name" ) ),
215 std::make_pair( tr( "Layout Page Number" ), QStringLiteral( "@layout_page" ) ),
216 std::make_pair( tr( "Layout Page Count" ), QStringLiteral( "@layout_numpages" ) ),
217 std::make_pair( tr( "Layer Credits" ), QStringLiteral( "array_to_string(map_credits())" ) )
218 } )
219 {
220 addExpression( menu, expression.first, expression.second );
221 }
222 menu->addSeparator();
223 for ( const std::pair< QString, QString > &expression :
224 {
225 std::make_pair( tr( "Project Author" ), QStringLiteral( "@project_author" ) ),
226 std::make_pair( tr( "Project Title" ), QStringLiteral( "@project_title" ) ),
227 std::make_pair( tr( "Project Path" ), QStringLiteral( "@project_path" ) )
228 } )
229 {
230 addExpression( menu, expression.first, expression.second );
231 }
232 menu->addSeparator();
233 for ( const std::pair< QString, QString > &expression :
234 {
235 std::make_pair( tr( "Current User Name" ), QStringLiteral( "@user_full_name" ) ),
236 std::make_pair( tr( "Current User Account" ), QStringLiteral( "@user_account_name" ) )
237 } )
238 {
239 addExpression( menu, expression.first, expression.second );
240 }
241}
242
244{
246 return false;
247
248 if ( mLabel )
249 {
250 disconnect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
251 }
252
253 mLabel = qobject_cast< QgsLayoutItemLabel * >( item );
254 mItemPropertiesWidget->setItem( mLabel );
255
256 if ( mLabel )
257 {
258 connect( mLabel, &QgsLayoutObject::changed, this, &QgsLayoutLabelWidget::setGuiElementValues );
259 }
260
261 setGuiElementValues();
262
263 return true;
264}
265
266void QgsLayoutLabelWidget::mHtmlCheckBox_stateChanged( int state )
267{
268 if ( mLabel )
269 {
270 mVerticalAlignementLabel->setDisabled( state );
271 mVAlignmentComboBox->setDisabled( state );
272
273 mLabel->beginCommand( tr( "Change Label Mode" ) );
274 mLabel->blockSignals( true );
275 mLabel->setMode( state ? QgsLayoutItemLabel::ModeHtml : QgsLayoutItemLabel::ModeFont );
276 mLabel->setText( mTextEdit->toPlainText() );
277 mLabel->update();
278 mLabel->blockSignals( false );
279 mLabel->endCommand();
280 }
281}
282
283void QgsLayoutLabelWidget::mTextEdit_textChanged()
284{
285 if ( mLabel )
286 {
287 mLabel->beginCommand( tr( "Change Label Text" ), QgsLayoutItem::UndoLabelText );
288 mLabel->blockSignals( true );
289 mLabel->setText( mTextEdit->toPlainText() );
290 mLabel->update();
291 mLabel->blockSignals( false );
292 mLabel->endCommand();
293 }
294}
295
296void QgsLayoutLabelWidget::fontChanged()
297{
298 if ( mLabel )
299 {
300 mLabel->beginCommand( tr( "Change Label Font" ), QgsLayoutItem::UndoLabelFont );
301 mLabel->setTextFormat( mFontButton->textFormat() );
302 mLabel->update();
303 mLabel->endCommand();
304 }
305}
306
307void QgsLayoutLabelWidget::mMarginXDoubleSpinBox_valueChanged( double d )
308{
309 if ( mLabel )
310 {
311 mLabel->beginCommand( tr( "Change Label Margin" ), QgsLayoutItem::UndoLabelMargin );
312 mLabel->setMarginX( d );
313 mLabel->update();
314 mLabel->endCommand();
315 }
316}
317
318void QgsLayoutLabelWidget::mMarginYDoubleSpinBox_valueChanged( double d )
319{
320 if ( mLabel )
321 {
322 mLabel->beginCommand( tr( "Change Label Margin" ), QgsLayoutItem::UndoLabelMargin );
323 mLabel->setMarginY( d );
324 mLabel->update();
325 mLabel->endCommand();
326 }
327}
328
329void QgsLayoutLabelWidget::mInsertExpressionButton_clicked()
330{
331 if ( !mLabel )
332 {
333 return;
334 }
335
336 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mTextEdit );
337
338 // use the atlas coverage layer, if any
339 QgsVectorLayer *layer = coverageLayer();
340
341 QgsExpressionContext context = mLabel->createExpressionContext();
342 QgsExpressionBuilderDialog exprDlg( layer, expression, this, QStringLiteral( "generic" ), context );
343
344 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
345 if ( exprDlg.exec() == QDialog::Accepted )
346 {
347 expression = exprDlg.expressionText();
348 if ( !expression.isEmpty() )
349 {
350 mLabel->beginCommand( tr( "Insert expression" ) );
351 mTextEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
352 mLabel->endCommand();
353 }
354 }
355}
356
357void QgsLayoutLabelWidget::horizontalAlignmentChanged()
358{
359 if ( mLabel )
360 {
361 mLabel->beginCommand( tr( "Change Label Alignment" ) );
362 mLabel->setHAlign( static_cast< Qt::AlignmentFlag >( static_cast< int >( mHAlignmentComboBox->currentAlignment() ) ) );
363 mLabel->update();
364 mLabel->endCommand();
365 }
366}
367
368void QgsLayoutLabelWidget::verticalAlignmentChanged()
369{
370 if ( mLabel )
371 {
372 mLabel->beginCommand( tr( "Change Label Alignment" ) );
373 mLabel->setVAlign( static_cast< Qt::AlignmentFlag >( static_cast< int >( mVAlignmentComboBox->currentAlignment() ) ) );
374 mLabel->update();
375 mLabel->endCommand();
376 }
377}
378
379void QgsLayoutLabelWidget::setGuiElementValues()
380{
381 blockAllSignals( true );
382 mTextEdit->setPlainText( mLabel->text() );
383 mTextEdit->moveCursor( QTextCursor::End, QTextCursor::MoveAnchor );
384 mMarginXDoubleSpinBox->setValue( mLabel->marginX() );
385 mMarginYDoubleSpinBox->setValue( mLabel->marginY() );
386 mHtmlCheckBox->setChecked( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
387
388 mHAlignmentComboBox->setCurrentAlignment( mLabel->hAlign() );
389 mVAlignmentComboBox->setCurrentAlignment( mLabel->vAlign() );
390
391 mFontButton->setTextFormat( mLabel->textFormat() );
392 mVerticalAlignementLabel->setDisabled( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
393 mVAlignmentComboBox->setDisabled( mLabel->mode() == QgsLayoutItemLabel::ModeHtml );
394
395 blockAllSignals( false );
396}
397
398void QgsLayoutLabelWidget::blockAllSignals( bool block )
399{
400 mTextEdit->blockSignals( block );
401 mHtmlCheckBox->blockSignals( block );
402 mMarginXDoubleSpinBox->blockSignals( block );
403 mMarginYDoubleSpinBox->blockSignals( block );
404 mHAlignmentComboBox->blockSignals( block );
405 mVAlignmentComboBox->blockSignals( block );
406 mFontButton->blockSignals( block );
407}
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.
QgsLayoutLabelWidget(QgsLayoutItemLabel *label)
constructor
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...
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.