QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
125 void 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 
142 void 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 
159 void 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 
174 void 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 
188 void 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 
202 void 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 
216 void 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 
230 void 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 
244 void 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 
258 void 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 
272 void 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 
284 void 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 
296 void 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 
317 void 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 
338 void 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 
390 void 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 
404 void 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 
426 void 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.