QGIS API Documentation 3.41.0-Master (af5edcb665c)
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#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 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.
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.