QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
25#include <QFileDialog>
26#include <QUrl>
27
29 : QgsLayoutItemBaseWidget( nullptr, frame ? qobject_cast< QgsLayoutItemHtml* >( frame->multiFrame() ) : nullptr )
30 , mHtml( frame ? qobject_cast< QgsLayoutItemHtml* >( frame->multiFrame() ) : nullptr )
31 , mFrame( frame )
32{
33 setupUi( this );
34 connect( mUrlLineEdit, &QLineEdit::editingFinished, this, &QgsLayoutHtmlWidget::mUrlLineEdit_editingFinished );
35 connect( mFileToolButton, &QToolButton::clicked, this, &QgsLayoutHtmlWidget::mFileToolButton_clicked );
36 connect( mResizeModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutHtmlWidget::mResizeModeComboBox_currentIndexChanged );
37 connect( mEvaluateExpressionsCheckbox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEvaluateExpressionsCheckbox_toggled );
38 connect( mUseSmartBreaksCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutHtmlWidget::mUseSmartBreaksCheckBox_toggled );
39 connect( mMaxDistanceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutHtmlWidget::mMaxDistanceSpinBox_valueChanged );
40 connect( mUserStylesheetCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutHtmlWidget::mUserStylesheetCheckBox_toggled );
41 connect( mRadioManualSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioManualSource_clicked );
42 connect( mRadioUrlSource, &QRadioButton::clicked, this, &QgsLayoutHtmlWidget::mRadioUrlSource_clicked );
43 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mInsertExpressionButton_clicked );
44 connect( mReloadPushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
45 connect( mReloadPushButton2, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mReloadPushButton_clicked );
46 connect( mAddFramePushButton, &QPushButton::clicked, this, &QgsLayoutHtmlWidget::mAddFramePushButton_clicked );
47 connect( mEmptyFrameCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled );
48 connect( mHideEmptyBgCheckBox, &QCheckBox::toggled, this, &QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled );
49 setPanelTitle( tr( "HTML Properties" ) );
50
51 //setup html editor
52 mHtmlEditor = new QgsCodeEditorHTML( this );
53 connect( mHtmlEditor, &QsciScintilla::textChanged, this, &QgsLayoutHtmlWidget::htmlEditorChanged );
54 htmlEditorLayout->addWidget( mHtmlEditor );
55
56 //setup stylesheet editor
57 mStylesheetEditor = new QgsCodeEditorCSS( this );
58 connect( mStylesheetEditor, &QsciScintilla::textChanged, this, &QgsLayoutHtmlWidget::stylesheetEditorChanged );
59 stylesheetEditorLayout->addWidget( mStylesheetEditor );
60
61 blockSignals( true );
62 mResizeModeComboBox->addItem( tr( "Use Existing Frames" ), QgsLayoutMultiFrame::UseExistingFrames );
63 mResizeModeComboBox->addItem( tr( "Extend to Next Page" ), QgsLayoutMultiFrame::ExtendToNextPage );
64 mResizeModeComboBox->addItem( tr( "Repeat on Every Page" ), QgsLayoutMultiFrame::RepeatOnEveryPage );
65 mResizeModeComboBox->addItem( tr( "Repeat Until Finished" ), QgsLayoutMultiFrame::RepeatUntilFinished );
66 blockSignals( false );
67 setGuiElementValues();
68
69 if ( mHtml )
70 {
71 connect( mHtml, &QgsLayoutMultiFrame::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
72 }
73
74 //embed widget for general options
75 if ( mFrame )
76 {
77 //add widget for general composer item properties
78 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, mFrame );
79 mainLayout->addWidget( mItemPropertiesWidget );
80 }
81
82 //connections for data defined buttons
83 connect( mUrlDDBtn, &QgsPropertyOverrideButton::activated, mUrlLineEdit, &QLineEdit::setDisabled );
85}
86
88{
89 if ( mItemPropertiesWidget )
90 mItemPropertiesWidget->setMasterLayout( masterLayout );
91}
92
94{
95 QgsLayoutFrame *frame = qobject_cast< QgsLayoutFrame * >( item );
96 if ( !frame )
97 return false;
98
99 QgsLayoutMultiFrame *multiFrame = frame->multiFrame();
100 if ( !multiFrame )
101 return false;
102
103 if ( multiFrame->type() != QgsLayoutItemRegistry::LayoutHtml )
104 return false;
105
106 if ( mHtml )
107 {
108 disconnect( mHtml, &QgsLayoutObject::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
109 }
110
111 mHtml = qobject_cast< QgsLayoutItemHtml * >( multiFrame );
112 mFrame = frame;
113 mItemPropertiesWidget->setItem( frame );
114
115 if ( mHtml )
116 {
117 connect( mHtml, &QgsLayoutObject::changed, this, &QgsLayoutHtmlWidget::setGuiElementValues );
118 }
119
120 setGuiElementValues();
121
122 return true;
123}
124
125void QgsLayoutHtmlWidget::blockSignals( bool block )
126{
127 mUrlLineEdit->blockSignals( block );
128 mFileToolButton->blockSignals( block );
129 mResizeModeComboBox->blockSignals( block );
130 mUseSmartBreaksCheckBox->blockSignals( block );
131 mMaxDistanceSpinBox->blockSignals( block );
132 mHtmlEditor->blockSignals( block );
133 mStylesheetEditor->blockSignals( block );
134 mUserStylesheetCheckBox->blockSignals( block );
135 mRadioManualSource->blockSignals( block );
136 mRadioUrlSource->blockSignals( block );
137 mEvaluateExpressionsCheckbox->blockSignals( block );
138 mEmptyFrameCheckBox->blockSignals( block );
139 mHideEmptyBgCheckBox->blockSignals( block );
140}
141
142void QgsLayoutHtmlWidget::mUrlLineEdit_editingFinished()
143{
144 if ( mHtml )
145 {
146 const QUrl newUrl( mUrlLineEdit->text() );
147 if ( newUrl == mHtml->url() )
148 {
149 return;
150 }
151
152 mHtml->beginCommand( tr( "Change HTML Url" ) );
153 mHtml->setUrl( newUrl );
154 mHtml->update();
155 mHtml->endCommand();
156 }
157}
158
159void QgsLayoutHtmlWidget::mFileToolButton_clicked()
160{
161 QgsSettings s;
162 const QString lastDir = s.value( QStringLiteral( "/UI/lastHtmlDir" ), QDir::homePath() ).toString();
163 const QString file = QFileDialog::getOpenFileName( this, tr( "Select HTML document" ), lastDir, QStringLiteral( "HTML (*.html *.htm);;All files (*.*)" ) );
164 if ( !file.isEmpty() )
165 {
166 const QUrl url = QUrl::fromLocalFile( file );
167 mUrlLineEdit->setText( url.toString() );
168 mUrlLineEdit_editingFinished();
169 mHtml->update();
170 s.setValue( QStringLiteral( "/UI/lastHtmlDir" ), QFileInfo( file ).absolutePath() );
171 }
172}
173
174void QgsLayoutHtmlWidget::mResizeModeComboBox_currentIndexChanged( int index )
175{
176 if ( !mHtml )
177 {
178 return;
179 }
180
181 mHtml->beginCommand( tr( "Change Resize Mode" ) );
182 mHtml->setResizeMode( static_cast< QgsLayoutMultiFrame::ResizeMode >( mResizeModeComboBox->itemData( index ).toInt() ) );
183 mHtml->endCommand();
184
185 mAddFramePushButton->setEnabled( mHtml->resizeMode() == QgsLayoutMultiFrame::UseExistingFrames );
186}
187
188void QgsLayoutHtmlWidget::mEvaluateExpressionsCheckbox_toggled( bool checked )
189{
190 if ( !mHtml )
191 {
192 return;
193 }
194
195 blockSignals( true );
196 mHtml->beginCommand( tr( "Change Evaluate Expressions" ) );
197 mHtml->setEvaluateExpressions( checked );
198 mHtml->endCommand();
199 blockSignals( false );
200}
201
202void QgsLayoutHtmlWidget::mUseSmartBreaksCheckBox_toggled( bool checked )
203{
204 if ( !mHtml )
205 {
206 return;
207 }
208
209 blockSignals( true );
210 mHtml->beginCommand( tr( "Change Smart Breaks" ) );
211 mHtml->setUseSmartBreaks( checked );
212 mHtml->endCommand();
213 blockSignals( false );
214}
215
216void QgsLayoutHtmlWidget::mMaxDistanceSpinBox_valueChanged( double val )
217{
218 if ( !mHtml )
219 {
220 return;
221 }
222
223 blockSignals( true );
224 mHtml->beginCommand( tr( "Change Page Break Distance" ), QgsLayoutMultiFrame::UndoHtmlBreakDistance );
225 mHtml->setMaxBreakDistance( val );
226 mHtml->endCommand();
227 blockSignals( false );
228}
229
230void QgsLayoutHtmlWidget::htmlEditorChanged()
231{
232 if ( !mHtml )
233 {
234 return;
235 }
236
237 blockSignals( true );
238 mHtml->beginCommand( tr( "Change HTML" ), QgsLayoutMultiFrame::UndoHtmlSource );
239 mHtml->setHtml( mHtmlEditor->text() );
240 mHtml->endCommand();
241 blockSignals( false );
242}
243
244void QgsLayoutHtmlWidget::stylesheetEditorChanged()
245{
246 if ( !mHtml )
247 {
248 return;
249 }
250
251 blockSignals( true );
252 mHtml->beginCommand( tr( "Change User Stylesheet" ), QgsLayoutMultiFrame::UndoHtmlStylesheet );
253 mHtml->setUserStylesheet( mStylesheetEditor->text() );
254 mHtml->endCommand();
255 blockSignals( false );
256}
257
258void QgsLayoutHtmlWidget::mUserStylesheetCheckBox_toggled( bool checked )
259{
260 if ( !mHtml )
261 {
262 return;
263 }
264
265 blockSignals( true );
266 mHtml->beginCommand( tr( "Toggle User Stylesheet" ) );
267 mHtml->setUserStylesheetEnabled( checked );
268 mHtml->endCommand();
269 blockSignals( false );
270}
271
272void QgsLayoutHtmlWidget::mEmptyFrameCheckBox_toggled( bool checked )
273{
274 if ( !mFrame )
275 {
276 return;
277 }
278
279 mFrame->beginCommand( tr( "Toggle Empty Frame Mode" ) );
280 mFrame->setHidePageIfEmpty( checked );
281 mFrame->endCommand();
282}
283
284void QgsLayoutHtmlWidget::mHideEmptyBgCheckBox_toggled( bool checked )
285{
286 if ( !mFrame )
287 {
288 return;
289 }
290
291 mFrame->beginCommand( tr( "Toggle Hide Background" ) );
292 mFrame->setHideBackgroundIfEmpty( checked );
293 mFrame->endCommand();
294}
295
296void QgsLayoutHtmlWidget::mRadioManualSource_clicked( bool checked )
297{
298 if ( !mHtml )
299 {
300 return;
301 }
302
303 blockSignals( true );
304 mHtml->beginCommand( tr( "Change HTML Source" ) );
305 mHtml->setContentMode( checked ? QgsLayoutItemHtml::ManualHtml : QgsLayoutItemHtml::Url );
306 blockSignals( false );
307
308 mHtmlEditor->setEnabled( checked );
309 mInsertExpressionButton->setEnabled( checked );
310 mUrlLineEdit->setEnabled( !checked );
311 mFileToolButton->setEnabled( !checked );
312
313 mHtml->loadHtml();
314 mHtml->endCommand();
315}
316
317void QgsLayoutHtmlWidget::mRadioUrlSource_clicked( bool checked )
318{
319 if ( !mHtml )
320 {
321 return;
322 }
323
324 blockSignals( true );
325 mHtml->beginCommand( tr( "Change HTML Source" ) );
326 mHtml->setContentMode( checked ? QgsLayoutItemHtml::Url : QgsLayoutItemHtml::ManualHtml );
327 blockSignals( false );
328
329 mHtmlEditor->setEnabled( !checked );
330 mInsertExpressionButton->setEnabled( !checked );
331 mUrlLineEdit->setEnabled( checked );
332 mFileToolButton->setEnabled( checked );
333
334 mHtml->loadHtml();
335 mHtml->endCommand();
336}
337
338void QgsLayoutHtmlWidget::mInsertExpressionButton_clicked()
339{
340 if ( !mHtml )
341 {
342 return;
343 }
344
345 int line = 0;
346 int index = 0;
347 QString selText;
348 if ( mHtmlEditor->hasSelectedText() )
349 {
350 selText = mHtmlEditor->selectedText();
351
352 // edit the selected expression if there's one
353 if ( selText.startsWith( QLatin1String( "[%" ) ) && selText.endsWith( QLatin1String( "%]" ) ) )
354 selText = selText.mid( 2, selText.size() - 4 );
355 }
356 else
357 {
358 mHtmlEditor->getCursorPosition( &line, &index );
359 }
360
361 // use the atlas coverage layer, if any
362 QgsVectorLayer *layer = coverageLayer();
363
364 const QgsExpressionContext context = mHtml->createExpressionContext();
365 QgsExpressionBuilderDialog exprDlg( layer, selText, this, QStringLiteral( "generic" ), context );
366 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
367 if ( exprDlg.exec() == QDialog::Accepted )
368 {
369 const QString expression = exprDlg.expressionText();
370 if ( !expression.isEmpty() )
371 {
372 blockSignals( true );
373 mHtml->beginCommand( tr( "Change HTML Source" ) );
374 if ( mHtmlEditor->hasSelectedText() )
375 {
376 mHtmlEditor->replaceSelectedText( "[%" + expression + "%]" );
377 }
378 else
379 {
380 mHtmlEditor->insertAt( "[%" + expression + "%]", line, index );
381 }
382 mHtml->setHtml( mHtmlEditor->text() );
383 mHtml->endCommand();
384 blockSignals( false );
385 }
386 }
387
388}
389
390void QgsLayoutHtmlWidget::mReloadPushButton_clicked()
391{
392 if ( !mHtml )
393 {
394 return;
395 }
396
397 if ( mHtml->layout() )
398 mHtml->layout()->undoStack()->blockCommands( true );
399 mHtml->loadHtml();
400 if ( mHtml->layout() )
401 mHtml->layout()->undoStack()->blockCommands( false );
402}
403
404void QgsLayoutHtmlWidget::mAddFramePushButton_clicked()
405{
406 if ( !mHtml || !mFrame )
407 {
408 return;
409 }
410
411 //create a new frame based on the current frame
412 QPointF pos = mFrame->pos();
413 //shift new frame so that it sits 10 units below current frame
414 pos.ry() += mFrame->rect().height() + 10;
415
416 QgsLayoutFrame *newFrame = mHtml->createNewFrame( mFrame, pos, mFrame->rect().size() );
417 mHtml->recalculateFrameSizes();
418
419 //set new frame as selection
420 if ( QgsLayout *layout = mHtml->layout() )
421 {
422 layout->setSelectedItem( newFrame );
423 }
424}
425
426void QgsLayoutHtmlWidget::setGuiElementValues()
427{
428 if ( !mHtml || !mFrame )
429 {
430 return;
431 }
432
433 blockSignals( true );
434 mUrlLineEdit->setText( mHtml->url().toString() );
435 mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mHtml->resizeMode() ) );
436 mEvaluateExpressionsCheckbox->setChecked( mHtml->evaluateExpressions() );
437 mUseSmartBreaksCheckBox->setChecked( mHtml->useSmartBreaks() );
438 mMaxDistanceSpinBox->setValue( mHtml->maxBreakDistance() );
439
440 mAddFramePushButton->setEnabled( mHtml->resizeMode() == QgsLayoutMultiFrame::UseExistingFrames );
441 mHtmlEditor->setText( mHtml->html() );
442
443 mRadioUrlSource->setChecked( mHtml->contentMode() == QgsLayoutItemHtml::Url );
444 mUrlLineEdit->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::Url );
445 mFileToolButton->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::Url );
446 mRadioManualSource->setChecked( mHtml->contentMode() == QgsLayoutItemHtml::ManualHtml );
447 mHtmlEditor->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::ManualHtml );
448 mInsertExpressionButton->setEnabled( mHtml->contentMode() == QgsLayoutItemHtml::ManualHtml );
449
450 mUserStylesheetCheckBox->setChecked( mHtml->userStylesheetEnabled() );
451 mStylesheetEditor->setText( mHtml->userStylesheet() );
452
453 mEmptyFrameCheckBox->setChecked( mFrame->hidePageIfEmpty() );
454 mHideEmptyBgCheckBox->setChecked( mFrame->hideBackgroundIfEmpty() );
455
457
458 blockSignals( false );
459}
460
462{
463 updateDataDefinedButton( mUrlDDBtn );
464
465 //initial state of controls - disable related controls when dd buttons are active
466 mUrlLineEdit->setEnabled( !mUrlDDBtn->isActive() );
467}
A CSS editor based on QScintilla2.
A HTML editor based on QScintilla2.
A generic dialog for building expression strings.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
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.
@ SourceUrl
Html source url.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
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:62
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.