QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
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  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  QString lastDir = s.value( QStringLiteral( "/UI/lastHtmlDir" ), QDir::homePath() ).toString();
163  QString file = QFileDialog::getOpenFileName( this, tr( "Select HTML document" ), lastDir, QStringLiteral( "HTML (*.html *.htm);;All files (*.*)" ) );
164  if ( !file.isEmpty() )
165  {
166  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  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  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 }
Base class for graphical items within a QgsLayout.
HTML content is manually set for the item.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout) override
Sets the master layout associated with the item.
Don&#39;t automatically create new frames, just use existing frames.
A widget for controlling the common properties of layout items (e.g.
QgsLayoutHtmlWidget()=delete
constructor
A HTML editor based on QScintilla2.
void activated(bool isActive)
Emitted when the activated status of the widget changes.
void updateDataDefinedButton(QgsPropertyOverrideButton *button)
Updates a previously registered data defined button to reflect the item&#39;s current properties...
Creates new full page frames on the following page(s) until the entire multiframe content is visible...
A CSS editor based on QScintilla2.
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
void registerDataDefinedButton(QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty property)
Registers a data defined button, setting up its initial value, connections and description.
bool setNewItem(QgsLayoutItem *item) override
Attempts to update the widget to show the properties for the specified item.
Using this mode item fetches its content via a url.
Repeats the same frame on every page.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsLayoutMultiFrame * multiFrame() const
Returns the parent multiframe for the frame.
void populateDataDefinedButtons()
Initializes data defined buttons to current atlas coverage layer.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QgsVectorLayer * coverageLayer() const
Returns the current layout context coverage layer (if set).
A base class for property widgets for layout items.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
virtual int type() const =0
Returns unique multiframe type id.
void setItem(QgsLayoutItem *item)
Sets the layout item.
Interface for master layout type objects, such as print layouts and reports.
void changed()
Emitted when the object&#39;s properties change.
Represents a vector layer which manages a vector based data sets.
Base class for frame items, which form a layout multiframe item.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
A layout multiframe subclass for HTML content.
A generic dialog for building expression strings.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout)
Sets the master layout associated with the item.