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