QGIS API Documentation 3.43.0-Master (b60ef06885e)
qgslayouthtmlwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayouthtmlwidget.cpp
3 -----------------------
4 begin : November 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15#include "qgslayouthtmlwidget.h"
16#include "moc_qgslayouthtmlwidget.cpp"
17#include "qgslayoutframe.h"
18#include "qgslayoutitemhtml.h"
19#include "qgslayout.h"
21#include "qgscodeeditorhtml.h"
22#include "qgscodeeditorcss.h"
23#include "qgssettings.h"
24#include "qgslayoutundostack.h"
25#include "qgsexpressionfinder.h"
26
27#include <QFileDialog>
28#include <QUrl>
29
31 : QgsLayoutItemBaseWidget( nullptr, frame ? qobject_cast<QgsLayoutItemHtml *>( frame->multiFrame() ) : nullptr )
32 , mHtml( frame ? qobject_cast<QgsLayoutItemHtml *>( frame->multiFrame() ) : nullptr )
33 , mFrame( frame )
34{
35 setupUi( this );
36 connect( mUrlLineEdit, &QLineEdit::editingFinished, this, &QgsLayoutHtmlWidget::mUrlLineEdit_editingFinished );
37 connect( mFileToolButton, &QToolButton::clicked, this, &QgsLayoutHtmlWidget::mFileToolButton_clicked );
38 connect( mResizeModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutHtmlWidget::mResizeModeComboBox_currentIndexChanged );
39 connect( mEvaluateExpressionsCheckbox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEvaluateExpressionsCheckbox_toggled );
40 connect( mUseSmartBreaksCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutHtmlWidget::mUseSmartBreaksCheckBox_toggled );
41 connect( mMaxDistanceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutHtmlWidget::mMaxDistanceSpinBox_valueChanged );
42 connect( mUserStylesheetCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutHtmlWidget::mUserStylesheetCheckBox_toggled );
43 connect( mRadioManualSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioManualSource_clicked );
44 connect( mRadioUrlSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioUrlSource_clicked );
45 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mInsertExpressionButton_clicked );
46 connect( mReloadPushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
47 connect( mReloadPushButton2, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
48 connect( mAddFramePushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mAddFramePushButton_clicked );
49 connect( mEmptyFrameCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled );
50 connect( mHideEmptyBgCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled );
51 setPanelTitle( tr( "HTML Properties" ) );
52
53 //setup html editor
54 mHtmlEditor = new QgsCodeEditorHTML( this );
55 connect( mHtmlEditor, &QsciScintilla::textChanged, this, &QgsLayoutHtmlWidget::htmlEditorChanged );
56 htmlEditorLayout->addWidget( mHtmlEditor );
57
58 //setup stylesheet editor
59 mStylesheetEditor = new QgsCodeEditorCSS( this );
60 connect( mStylesheetEditor, &QsciScintilla::textChanged, this, &QgsLayoutHtmlWidget::stylesheetEditorChanged );
61 stylesheetEditorLayout->addWidget( mStylesheetEditor );
62
63 blockSignals( true );
64 mResizeModeComboBox->addItem( tr( "Use Existing Frames" ), QgsLayoutMultiFrame::UseExistingFrames );
65 mResizeModeComboBox->addItem( tr( "Extend to Next Page" ), QgsLayoutMultiFrame::ExtendToNextPage );
66 mResizeModeComboBox->addItem( tr( "Repeat on Every Page" ), QgsLayoutMultiFrame::RepeatOnEveryPage );
67 mResizeModeComboBox->addItem( tr( "Repeat Until Finished" ), QgsLayoutMultiFrame::RepeatUntilFinished );
68 blockSignals( false );
69 setGuiElementValues();
70
71 if ( mHtml )
72 {
73 connect( mHtml, &QgsLayoutMultiFrame::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
74 }
75
76 //embed widget for general options
77 if ( mFrame )
78 {
79 //add widget for general composer item properties
80 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, mFrame );
81 mainLayout->addWidget( mItemPropertiesWidget );
82 }
83
84 //connections for data defined buttons
85 connect( mUrlDDBtn, &QgsPropertyOverrideButton::activated, mUrlLineEdit, &QLineEdit::setDisabled );
87}
88
90{
91 if ( mItemPropertiesWidget )
92 mItemPropertiesWidget->setMasterLayout( masterLayout );
93}
94
96{
97 QgsLayoutFrame *frame = qobject_cast<QgsLayoutFrame *>( item );
98 if ( !frame )
99 return false;
100
101 QgsLayoutMultiFrame *multiFrame = frame->multiFrame();
102 if ( !multiFrame )
103 return false;
104
105 if ( multiFrame->type() != QgsLayoutItemRegistry::LayoutHtml )
106 return false;
107
108 if ( mHtml )
109 {
110 disconnect( mHtml, &QgsLayoutObject::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
111 }
112
113 mHtml = qobject_cast<QgsLayoutItemHtml *>( multiFrame );
114 mFrame = frame;
115 mItemPropertiesWidget->setItem( frame );
116
117 if ( mHtml )
118 {
119 connect( mHtml, &QgsLayoutObject::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
120 }
121
122 setGuiElementValues();
123
124 return true;
125}
126
127void QgsLayoutHtmlWidget::blockSignals( bool block )
128{
129 mUrlLineEdit->blockSignals( block );
130 mFileToolButton->blockSignals( block );
131 mResizeModeComboBox->blockSignals( block );
132 mUseSmartBreaksCheckBox->blockSignals( block );
133 mMaxDistanceSpinBox->blockSignals( block );
134 mHtmlEditor->blockSignals( block );
135 mStylesheetEditor->blockSignals( block );
136 mUserStylesheetCheckBox->blockSignals( block );
137 mRadioManualSource->blockSignals( block );
138 mRadioUrlSource->blockSignals( block );
139 mEvaluateExpressionsCheckbox->blockSignals( block );
140 mEmptyFrameCheckBox->blockSignals( block );
141 mHideEmptyBgCheckBox->blockSignals( block );
142}
143
144void QgsLayoutHtmlWidget::mUrlLineEdit_editingFinished()
145{
146 if ( mHtml )
147 {
148 const QUrl newUrl( mUrlLineEdit->text() );
149 if ( newUrl == mHtml->url() )
150 {
151 return;
152 }
153
154 mHtml->beginCommand( tr( "Change HTML Url" ) );
155 mHtml->setUrl( newUrl );
156 mHtml->update();
157 mHtml->endCommand();
158 }
159}
160
161void QgsLayoutHtmlWidget::mFileToolButton_clicked()
162{
163 QgsSettings s;
164 const QString lastDir = s.value( QStringLiteral( "/UI/lastHtmlDir" ), QDir::homePath() ).toString();
165 const QString file = QFileDialog::getOpenFileName( this, tr( "Select HTML document" ), lastDir, QStringLiteral( "HTML (*.html *.htm);;All files (*.*)" ) );
166 if ( !file.isEmpty() )
167 {
168 const QUrl url = QUrl::fromLocalFile( file );
169 mUrlLineEdit->setText( url.toString() );
170 mUrlLineEdit_editingFinished();
171 mHtml->update();
172 s.setValue( QStringLiteral( "/UI/lastHtmlDir" ), QFileInfo( file ).absolutePath() );
173 }
174}
175
176void QgsLayoutHtmlWidget::mResizeModeComboBox_currentIndexChanged( int index )
177{
178 if ( !mHtml )
179 {
180 return;
181 }
182
183 mHtml->beginCommand( tr( "Change Resize Mode" ) );
184 mHtml->setResizeMode( static_cast<QgsLayoutMultiFrame::ResizeMode>( mResizeModeComboBox->itemData( index ).toInt() ) );
185 mHtml->endCommand();
186
187 mAddFramePushButton->setEnabled( mHtml->resizeMode() == QgsLayoutMultiFrame::UseExistingFrames );
188}
189
190void QgsLayoutHtmlWidget::mEvaluateExpressionsCheckbox_toggled( bool checked )
191{
192 if ( !mHtml )
193 {
194 return;
195 }
196
197 blockSignals( true );
198 mHtml->beginCommand( tr( "Change Evaluate Expressions" ) );
199 mHtml->setEvaluateExpressions( checked );
200 mHtml->endCommand();
201 blockSignals( false );
202}
203
204void QgsLayoutHtmlWidget::mUseSmartBreaksCheckBox_toggled( bool checked )
205{
206 if ( !mHtml )
207 {
208 return;
209 }
210
211 blockSignals( true );
212 mHtml->beginCommand( tr( "Change Smart Breaks" ) );
213 mHtml->setUseSmartBreaks( checked );
214 mHtml->endCommand();
215 blockSignals( false );
216}
217
218void QgsLayoutHtmlWidget::mMaxDistanceSpinBox_valueChanged( double val )
219{
220 if ( !mHtml )
221 {
222 return;
223 }
224
225 blockSignals( true );
226 mHtml->beginCommand( tr( "Change Page Break Distance" ), QgsLayoutMultiFrame::UndoHtmlBreakDistance );
227 mHtml->setMaxBreakDistance( val );
228 mHtml->endCommand();
229 blockSignals( false );
230}
231
232void QgsLayoutHtmlWidget::htmlEditorChanged()
233{
234 if ( !mHtml )
235 {
236 return;
237 }
238
239 blockSignals( true );
240 mHtml->beginCommand( tr( "Change HTML" ), QgsLayoutMultiFrame::UndoHtmlSource );
241 mHtml->setHtml( mHtmlEditor->text() );
242 mHtml->endCommand();
243 blockSignals( false );
244}
245
246void QgsLayoutHtmlWidget::stylesheetEditorChanged()
247{
248 if ( !mHtml )
249 {
250 return;
251 }
252
253 blockSignals( true );
254 mHtml->beginCommand( tr( "Change User Stylesheet" ), QgsLayoutMultiFrame::UndoHtmlStylesheet );
255 mHtml->setUserStylesheet( mStylesheetEditor->text() );
256 mHtml->endCommand();
257 blockSignals( false );
258}
259
260void QgsLayoutHtmlWidget::mUserStylesheetCheckBox_toggled( bool checked )
261{
262 if ( !mHtml )
263 {
264 return;
265 }
266
267 blockSignals( true );
268 mHtml->beginCommand( tr( "Toggle User Stylesheet" ) );
269 mHtml->setUserStylesheetEnabled( checked );
270 mHtml->endCommand();
271 blockSignals( false );
272}
273
274void QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled( bool checked )
275{
276 if ( !mFrame )
277 {
278 return;
279 }
280
281 mFrame->beginCommand( tr( "Toggle Empty Frame Mode" ) );
282 mFrame->setHidePageIfEmpty( checked );
283 mFrame->endCommand();
284}
285
286void QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled( bool checked )
287{
288 if ( !mFrame )
289 {
290 return;
291 }
292
293 mFrame->beginCommand( tr( "Toggle Hide Background" ) );
294 mFrame->setHideBackgroundIfEmpty( checked );
295 mFrame->endCommand();
296}
297
298void QgsLayoutHtmlWidget::mRadioManualSource_clicked( bool checked )
299{
300 if ( !mHtml )
301 {
302 return;
303 }
304
305 blockSignals( true );
306 mHtml->beginCommand( tr( "Change HTML Source" ) );
307 mHtml->setContentMode( checked ? QgsLayoutItemHtml::ManualHtml : QgsLayoutItemHtml::Url );
308 blockSignals( false );
309
310 mHtmlEditor->setEnabled( checked );
311 mInsertExpressionButton->setEnabled( checked );
312 mUrlLineEdit->setEnabled( !checked );
313 mFileToolButton->setEnabled( !checked );
314
315 mHtml->loadHtml();
316 mHtml->endCommand();
317}
318
319void QgsLayoutHtmlWidget::mRadioUrlSource_clicked( bool checked )
320{
321 if ( !mHtml )
322 {
323 return;
324 }
325
326 blockSignals( true );
327 mHtml->beginCommand( tr( "Change HTML Source" ) );
328 mHtml->setContentMode( checked ? QgsLayoutItemHtml::Url : QgsLayoutItemHtml::ManualHtml );
329 blockSignals( false );
330
331 mHtmlEditor->setEnabled( !checked );
332 mInsertExpressionButton->setEnabled( !checked );
333 mUrlLineEdit->setEnabled( checked );
334 mFileToolButton->setEnabled( checked );
335
336 mHtml->loadHtml();
337 mHtml->endCommand();
338}
339
340void QgsLayoutHtmlWidget::mInsertExpressionButton_clicked()
341{
342 if ( !mHtml )
343 {
344 return;
345 }
346
347 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mHtmlEditor );
348
349 // use the atlas coverage layer, if any
350 QgsVectorLayer *layer = coverageLayer();
351
352 const QgsExpressionContext context = mHtml->createExpressionContext();
353 QgsExpressionBuilderDialog exprDlg( layer, expression, this, QStringLiteral( "generic" ), context );
354 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
355 if ( exprDlg.exec() == QDialog::Accepted )
356 {
357 expression = exprDlg.expressionText();
358 if ( !expression.isEmpty() )
359 {
360 blockSignals( true );
361 mHtml->beginCommand( tr( "Change HTML Source" ) );
362 mHtmlEditor->insertText( "[%" + expression.trimmed() + "%]" );
363 mHtml->setHtml( mHtmlEditor->text() );
364 mHtml->endCommand();
365 blockSignals( false );
366 }
367 }
368}
369
370void QgsLayoutHtmlWidget::mReloadPushButton_clicked()
371{
372 if ( !mHtml )
373 {
374 return;
375 }
376
377 if ( mHtml->layout() )
378 mHtml->layout()->undoStack()->blockCommands( true );
379 mHtml->loadHtml();
380 if ( mHtml->layout() )
381 mHtml->layout()->undoStack()->blockCommands( false );
382}
383
384void QgsLayoutHtmlWidget::mAddFramePushButton_clicked()
385{
386 if ( !mHtml || !mFrame )
387 {
388 return;
389 }
390
391 //create a new frame based on the current frame
392 QPointF pos = mFrame->pos();
393 //shift new frame so that it sits 10 units below current frame
394 pos.ry() += mFrame->rect().height() + 10;
395
396 QgsLayoutFrame *newFrame = mHtml->createNewFrame( mFrame, pos, mFrame->rect().size() );
397 mHtml->recalculateFrameSizes();
398
399 //set new frame as selection
400 if ( QgsLayout *layout = mHtml->layout() )
401 {
402 layout->setSelectedItem( newFrame );
403 }
404}
405
406void QgsLayoutHtmlWidget::setGuiElementValues()
407{
408 if ( !mHtml || !mFrame )
409 {
410 return;
411 }
412
413 blockSignals( true );
414 mUrlLineEdit->setText( mHtml->url().toString() );
415 mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mHtml->resizeMode() ) );
416 mEvaluateExpressionsCheckbox->setChecked( mHtml->evaluateExpressions() );
417 mUseSmartBreaksCheckBox->setChecked( mHtml->useSmartBreaks() );
418 mMaxDistanceSpinBox->setValue( mHtml->maxBreakDistance() );
419
420 mAddFramePushButton->setEnabled( mHtml->resizeMode() == QgsLayoutMultiFrame::UseExistingFrames );
421 mHtmlEditor->setText( mHtml->html() );
422
423 mRadioUrlSource->setChecked( mHtml->contentMode() == QgsLayoutItemHtml::Url );
424 mUrlLineEdit->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::Url );
425 mFileToolButton->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::Url );
426 mRadioManualSource->setChecked( mHtml->contentMode() == QgsLayoutItemHtml::ManualHtml );
427 mHtmlEditor->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::ManualHtml );
428 mInsertExpressionButton->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::ManualHtml );
429
430 mUserStylesheetCheckBox->setChecked( mHtml->userStylesheetEnabled() );
431 mStylesheetEditor->setText( mHtml->userStylesheet() );
432
433 mEmptyFrameCheckBox->setChecked( mFrame->hidePageIfEmpty() );
434 mHideEmptyBgCheckBox->setChecked( mFrame->hideBackgroundIfEmpty() );
435
437
438 blockSignals( false );
439}
440
442{
443 updateDataDefinedButton( mUrlDDBtn );
444
445 //initial state of controls - disable related controls when dd buttons are active
446 mUrlLineEdit->setEnabled( !mUrlDDBtn->isActive() );
447}
A CSS editor based on QScintilla2.
A HTML editor based on QScintilla2.
void setText(const QString &text) override
void insertText(const QString &text)
Insert text at cursor position, or replace any selected text if user has made a selection.
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.
Base class for frame items, which form a layout multiframe item.
QgsLayoutMultiFrame * multiFrame() const
Returns the parent multiframe for the frame.
bool setNewItem(QgsLayoutItem *item) override
Attempts to update the widget to show the properties for the specified item.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout) override
Sets the master layout associated with the item.
void populateDataDefinedButtons()
Initializes data defined buttons to current atlas coverage layer.
QgsLayoutHtmlWidget()=delete
A base class for property widgets for layout items.
void updateDataDefinedButton(QgsPropertyOverrideButton *button)
Updates a previously registered data defined button to reflect the item's current properties.
QgsVectorLayer * coverageLayer() const
Returns the current layout context coverage layer (if set).
void registerDataDefinedButton(QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty property)
Registers a data defined button, setting up its initial value, connections and description.
A layout multiframe subclass for HTML content.
@ ManualHtml
HTML content is manually set for the item.
@ Url
Using this mode item fetches its content via a url.
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.
@ LayoutHtml
Html multiframe item.
Base class for graphical items within a QgsLayout.
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
virtual int type() const =0
Returns unique multiframe type id.
ResizeMode
Specifies the behavior for creating new frames to fit the multiframe's content.
@ UseExistingFrames
Don't automatically create new frames, just use existing frames.
@ RepeatOnEveryPage
Repeats the same frame on every page.
@ ExtendToNextPage
Creates new full page frames on the following page(s) until the entire multiframe content is visible.
@ UndoHtmlBreakDistance
HTML page break distance.
@ UndoHtmlStylesheet
HTML stylesheet.
void changed()
Emitted when the object's properties change.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:49
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.
void activated(bool isActive)
Emitted when the activated status of the widget changes.
Stores settings for use within QGIS.
Definition qgssettings.h:66
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Represents a vector layer which manages a vector based dataset.