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