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