QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 "qgslayoutframe.h"
17#include "qgslayoutitemhtml.h"
18#include "qgslayout.h"
20#include "qgscodeeditorhtml.h"
21#include "qgscodeeditorcss.h"
22#include "qgssettings.h"
23#include "qgslayoutundostack.h"
24#include "qgsexpressionfinder.h"
25
26#include <QFileDialog>
27#include <QUrl>
28
30 : QgsLayoutItemBaseWidget( nullptr, frame ? qobject_cast< QgsLayoutItemHtml* >( frame->multiFrame() ) : nullptr )
31 , mHtml( frame ? qobject_cast< QgsLayoutItemHtml* >( frame->multiFrame() ) : nullptr )
32 , mFrame( frame )
33{
34 setupUi( this );
35 connect( mUrlLineEdit, &QLineEdit::editingFinished, this, &QgsLayoutHtmlWidget::mUrlLineEdit_editingFinished );
36 connect( mFileToolButton, &QToolButton::clicked, this, &QgsLayoutHtmlWidget::mFileToolButton_clicked );
37 connect( mResizeModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutHtmlWidget::mResizeModeComboBox_currentIndexChanged );
38 connect( mEvaluateExpressionsCheckbox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEvaluateExpressionsCheckbox_toggled );
39 connect( mUseSmartBreaksCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutHtmlWidget::mUseSmartBreaksCheckBox_toggled );
40 connect( mMaxDistanceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutHtmlWidget::mMaxDistanceSpinBox_valueChanged );
41 connect( mUserStylesheetCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutHtmlWidget::mUserStylesheetCheckBox_toggled );
42 connect( mRadioManualSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioManualSource_clicked );
43 connect( mRadioUrlSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioUrlSource_clicked );
44 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mInsertExpressionButton_clicked );
45 connect( mReloadPushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
46 connect( mReloadPushButton2, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
47 connect( mAddFramePushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mAddFramePushButton_clicked );
48 connect( mEmptyFrameCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled );
49 connect( mHideEmptyBgCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled );
50 setPanelTitle( tr( "HTML Properties" ) );
51
52 //setup html editor
53 mHtmlEditor = new QgsCodeEditorHTML( this );
54 connect( mHtmlEditor, &QsciScintilla::textChanged, this, &QgsLayoutHtmlWidget::htmlEditorChanged );
55 htmlEditorLayout->addWidget( mHtmlEditor );
56
57 //setup stylesheet editor
58 mStylesheetEditor = new QgsCodeEditorCSS( this );
59 connect( mStylesheetEditor, &QsciScintilla::textChanged, this, &QgsLayoutHtmlWidget::stylesheetEditorChanged );
60 stylesheetEditorLayout->addWidget( mStylesheetEditor );
61
62 blockSignals( true );
63 mResizeModeComboBox->addItem( tr( "Use Existing Frames" ), QgsLayoutMultiFrame::UseExistingFrames );
64 mResizeModeComboBox->addItem( tr( "Extend to Next Page" ), QgsLayoutMultiFrame::ExtendToNextPage );
65 mResizeModeComboBox->addItem( tr( "Repeat on Every Page" ), QgsLayoutMultiFrame::RepeatOnEveryPage );
66 mResizeModeComboBox->addItem( tr( "Repeat Until Finished" ), QgsLayoutMultiFrame::RepeatUntilFinished );
67 blockSignals( false );
68 setGuiElementValues();
69
70 if ( mHtml )
71 {
72 connect( mHtml, &QgsLayoutMultiFrame::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
73 }
74
75 //embed widget for general options
76 if ( mFrame )
77 {
78 //add widget for general composer item properties
79 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, mFrame );
80 mainLayout->addWidget( mItemPropertiesWidget );
81 }
82
83 //connections for data defined buttons
84 connect( mUrlDDBtn, &QgsPropertyOverrideButton::activated, mUrlLineEdit, &QLineEdit::setDisabled );
86}
87
89{
90 if ( mItemPropertiesWidget )
91 mItemPropertiesWidget->setMasterLayout( masterLayout );
92}
93
95{
96 QgsLayoutFrame *frame = qobject_cast< QgsLayoutFrame * >( item );
97 if ( !frame )
98 return false;
99
100 QgsLayoutMultiFrame *multiFrame = frame->multiFrame();
101 if ( !multiFrame )
102 return false;
103
104 if ( multiFrame->type() != QgsLayoutItemRegistry::LayoutHtml )
105 return false;
106
107 if ( mHtml )
108 {
109 disconnect( mHtml, &QgsLayoutObject::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
110 }
111
112 mHtml = qobject_cast< QgsLayoutItemHtml * >( multiFrame );
113 mFrame = frame;
114 mItemPropertiesWidget->setItem( frame );
115
116 if ( mHtml )
117 {
118 connect( mHtml, &QgsLayoutObject::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
119 }
120
121 setGuiElementValues();
122
123 return true;
124}
125
126void QgsLayoutHtmlWidget::blockSignals( bool block )
127{
128 mUrlLineEdit->blockSignals( block );
129 mFileToolButton->blockSignals( block );
130 mResizeModeComboBox->blockSignals( block );
131 mUseSmartBreaksCheckBox->blockSignals( block );
132 mMaxDistanceSpinBox->blockSignals( block );
133 mHtmlEditor->blockSignals( block );
134 mStylesheetEditor->blockSignals( block );
135 mUserStylesheetCheckBox->blockSignals( block );
136 mRadioManualSource->blockSignals( block );
137 mRadioUrlSource->blockSignals( block );
138 mEvaluateExpressionsCheckbox->blockSignals( block );
139 mEmptyFrameCheckBox->blockSignals( block );
140 mHideEmptyBgCheckBox->blockSignals( block );
141}
142
143void QgsLayoutHtmlWidget::mUrlLineEdit_editingFinished()
144{
145 if ( mHtml )
146 {
147 const QUrl newUrl( mUrlLineEdit->text() );
148 if ( newUrl == mHtml->url() )
149 {
150 return;
151 }
152
153 mHtml->beginCommand( tr( "Change HTML Url" ) );
154 mHtml->setUrl( newUrl );
155 mHtml->update();
156 mHtml->endCommand();
157 }
158}
159
160void QgsLayoutHtmlWidget::mFileToolButton_clicked()
161{
162 QgsSettings s;
163 const QString lastDir = s.value( QStringLiteral( "/UI/lastHtmlDir" ), QDir::homePath() ).toString();
164 const QString file = QFileDialog::getOpenFileName( this, tr( "Select HTML document" ), lastDir, QStringLiteral( "HTML (*.html *.htm);;All files (*.*)" ) );
165 if ( !file.isEmpty() )
166 {
167 const QUrl url = QUrl::fromLocalFile( file );
168 mUrlLineEdit->setText( url.toString() );
169 mUrlLineEdit_editingFinished();
170 mHtml->update();
171 s.setValue( QStringLiteral( "/UI/lastHtmlDir" ), QFileInfo( file ).absolutePath() );
172 }
173}
174
175void QgsLayoutHtmlWidget::mResizeModeComboBox_currentIndexChanged( int index )
176{
177 if ( !mHtml )
178 {
179 return;
180 }
181
182 mHtml->beginCommand( tr( "Change Resize Mode" ) );
183 mHtml->setResizeMode( static_cast< QgsLayoutMultiFrame::ResizeMode >( mResizeModeComboBox->itemData( index ).toInt() ) );
184 mHtml->endCommand();
185
186 mAddFramePushButton->setEnabled( mHtml->resizeMode() == QgsLayoutMultiFrame::UseExistingFrames );
187}
188
189void QgsLayoutHtmlWidget::mEvaluateExpressionsCheckbox_toggled( bool checked )
190{
191 if ( !mHtml )
192 {
193 return;
194 }
195
196 blockSignals( true );
197 mHtml->beginCommand( tr( "Change Evaluate Expressions" ) );
198 mHtml->setEvaluateExpressions( checked );
199 mHtml->endCommand();
200 blockSignals( false );
201}
202
203void QgsLayoutHtmlWidget::mUseSmartBreaksCheckBox_toggled( bool checked )
204{
205 if ( !mHtml )
206 {
207 return;
208 }
209
210 blockSignals( true );
211 mHtml->beginCommand( tr( "Change Smart Breaks" ) );
212 mHtml->setUseSmartBreaks( checked );
213 mHtml->endCommand();
214 blockSignals( false );
215}
216
217void QgsLayoutHtmlWidget::mMaxDistanceSpinBox_valueChanged( double val )
218{
219 if ( !mHtml )
220 {
221 return;
222 }
223
224 blockSignals( true );
225 mHtml->beginCommand( tr( "Change Page Break Distance" ), QgsLayoutMultiFrame::UndoHtmlBreakDistance );
226 mHtml->setMaxBreakDistance( val );
227 mHtml->endCommand();
228 blockSignals( false );
229}
230
231void QgsLayoutHtmlWidget::htmlEditorChanged()
232{
233 if ( !mHtml )
234 {
235 return;
236 }
237
238 blockSignals( true );
239 mHtml->beginCommand( tr( "Change HTML" ), QgsLayoutMultiFrame::UndoHtmlSource );
240 mHtml->setHtml( mHtmlEditor->text() );
241 mHtml->endCommand();
242 blockSignals( false );
243}
244
245void QgsLayoutHtmlWidget::stylesheetEditorChanged()
246{
247 if ( !mHtml )
248 {
249 return;
250 }
251
252 blockSignals( true );
253 mHtml->beginCommand( tr( "Change User Stylesheet" ), QgsLayoutMultiFrame::UndoHtmlStylesheet );
254 mHtml->setUserStylesheet( mStylesheetEditor->text() );
255 mHtml->endCommand();
256 blockSignals( false );
257}
258
259void QgsLayoutHtmlWidget::mUserStylesheetCheckBox_toggled( bool checked )
260{
261 if ( !mHtml )
262 {
263 return;
264 }
265
266 blockSignals( true );
267 mHtml->beginCommand( tr( "Toggle User Stylesheet" ) );
268 mHtml->setUserStylesheetEnabled( checked );
269 mHtml->endCommand();
270 blockSignals( false );
271}
272
273void QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled( bool checked )
274{
275 if ( !mFrame )
276 {
277 return;
278 }
279
280 mFrame->beginCommand( tr( "Toggle Empty Frame Mode" ) );
281 mFrame->setHidePageIfEmpty( checked );
282 mFrame->endCommand();
283}
284
285void QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled( bool checked )
286{
287 if ( !mFrame )
288 {
289 return;
290 }
291
292 mFrame->beginCommand( tr( "Toggle Hide Background" ) );
293 mFrame->setHideBackgroundIfEmpty( checked );
294 mFrame->endCommand();
295}
296
297void QgsLayoutHtmlWidget::mRadioManualSource_clicked( bool checked )
298{
299 if ( !mHtml )
300 {
301 return;
302 }
303
304 blockSignals( true );
305 mHtml->beginCommand( tr( "Change HTML Source" ) );
306 mHtml->setContentMode( checked ? QgsLayoutItemHtml::ManualHtml : QgsLayoutItemHtml::Url );
307 blockSignals( false );
308
309 mHtmlEditor->setEnabled( checked );
310 mInsertExpressionButton->setEnabled( checked );
311 mUrlLineEdit->setEnabled( !checked );
312 mFileToolButton->setEnabled( !checked );
313
314 mHtml->loadHtml();
315 mHtml->endCommand();
316}
317
318void QgsLayoutHtmlWidget::mRadioUrlSource_clicked( bool checked )
319{
320 if ( !mHtml )
321 {
322 return;
323 }
324
325 blockSignals( true );
326 mHtml->beginCommand( tr( "Change HTML Source" ) );
327 mHtml->setContentMode( checked ? QgsLayoutItemHtml::Url : QgsLayoutItemHtml::ManualHtml );
328 blockSignals( false );
329
330 mHtmlEditor->setEnabled( !checked );
331 mInsertExpressionButton->setEnabled( !checked );
332 mUrlLineEdit->setEnabled( checked );
333 mFileToolButton->setEnabled( checked );
334
335 mHtml->loadHtml();
336 mHtml->endCommand();
337}
338
339void QgsLayoutHtmlWidget::mInsertExpressionButton_clicked()
340{
341 if ( !mHtml )
342 {
343 return;
344 }
345
346 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mHtmlEditor );
347
348 // use the atlas coverage layer, if any
349 QgsVectorLayer *layer = coverageLayer();
350
351 const QgsExpressionContext context = mHtml->createExpressionContext();
352 QgsExpressionBuilderDialog exprDlg( layer, expression, this, QStringLiteral( "generic" ), context );
353 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
354 if ( exprDlg.exec() == QDialog::Accepted )
355 {
356 expression = exprDlg.expressionText();
357 if ( !expression.isEmpty() )
358 {
359 blockSignals( true );
360 mHtml->beginCommand( tr( "Change HTML Source" ) );
361 mHtmlEditor->insertText( "[%" + expression.trimmed() + "%]" );
362 mHtml->setHtml( mHtmlEditor->text() );
363 mHtml->endCommand();
364 blockSignals( false );
365 }
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 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
constructor
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.
@ UndoHtmlSource
HTML source.
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
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 data sets.