QGIS API Documentation  3.12.1-București (121cc00ff0)
qgslayoutpicturewidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutpicturewidget.cpp
3  --------------------------
4  begin : October 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgslayoutpicturewidget.h"
19 #include "qgsapplication.h"
20 #include "qgslayoutitemmap.h"
21 #include "qgslayoutitempicture.h"
22 #include "qgslayout.h"
24 #include "qgssvgcache.h"
25 #include "qgssettings.h"
26 
27 #include <QDoubleValidator>
28 #include <QFileDialog>
29 #include <QFileInfo>
30 #include <QImageReader>
31 #include <QMessageBox>
32 #include <QPainter>
33 #include <QProgressDialog>
34 #include <QSvgRenderer>
35 
37  : QgsLayoutItemBaseWidget( nullptr, picture )
38  , mPicture( picture )
39 {
40  setupUi( this );
41 
42  mResizeModeComboBox->addItem( tr( "Zoom" ), QgsLayoutItemPicture::Zoom );
43  mResizeModeComboBox->addItem( tr( "Stretch" ), QgsLayoutItemPicture::Stretch );
44  mResizeModeComboBox->addItem( tr( "Clip" ), QgsLayoutItemPicture::Clip );
45  mResizeModeComboBox->addItem( tr( "Zoom and Resize Frame" ), QgsLayoutItemPicture::ZoomResizeFrame );
46  mResizeModeComboBox->addItem( tr( "Resize Frame to Image Size" ), QgsLayoutItemPicture::FrameToImageSize );
47 
48  mAnchorPointComboBox->addItem( tr( "Top Left" ), QgsLayoutItem::UpperLeft );
49  mAnchorPointComboBox->addItem( tr( "Top Center" ), QgsLayoutItem::UpperMiddle );
50  mAnchorPointComboBox->addItem( tr( "Top Right" ), QgsLayoutItem::UpperRight );
51  mAnchorPointComboBox->addItem( tr( "Middle Left" ), QgsLayoutItem::MiddleLeft );
52  mAnchorPointComboBox->addItem( tr( "Middle" ), QgsLayoutItem::Middle );
53  mAnchorPointComboBox->addItem( tr( "Middle Right" ), QgsLayoutItem::MiddleRight );
54  mAnchorPointComboBox->addItem( tr( "Bottom Left" ), QgsLayoutItem::LowerLeft );
55  mAnchorPointComboBox->addItem( tr( "Bottom Center" ), QgsLayoutItem::LowerMiddle );
56  mAnchorPointComboBox->addItem( tr( "Bottom Right" ), QgsLayoutItem::LowerRight );
57 
58  connect( mPictureBrowseButton, &QPushButton::clicked, this, &QgsLayoutPictureWidget::mPictureBrowseButton_clicked );
59  connect( mPictureLineEdit, &QLineEdit::editingFinished, this, &QgsLayoutPictureWidget::mPictureLineEdit_editingFinished );
60  connect( mPictureRotationSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPictureWidget::mPictureRotationSpinBox_valueChanged );
61  connect( mPreviewListWidget, &QListWidget::currentItemChanged, this, &QgsLayoutPictureWidget::mPreviewListWidget_currentItemChanged );
62  connect( mAddDirectoryButton, &QPushButton::clicked, this, &QgsLayoutPictureWidget::mAddDirectoryButton_clicked );
63  connect( mRemoveDirectoryButton, &QPushButton::clicked, this, &QgsLayoutPictureWidget::mRemoveDirectoryButton_clicked );
64  connect( mRotationFromComposerMapCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutPictureWidget::mRotationFromComposerMapCheckBox_stateChanged );
65  connect( mResizeModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutPictureWidget::mResizeModeComboBox_currentIndexChanged );
66  connect( mAnchorPointComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutPictureWidget::mAnchorPointComboBox_currentIndexChanged );
67  connect( mFillColorButton, &QgsColorButton::colorChanged, this, &QgsLayoutPictureWidget::mFillColorButton_colorChanged );
68  connect( mStrokeColorButton, &QgsColorButton::colorChanged, this, &QgsLayoutPictureWidget::mStrokeColorButton_colorChanged );
69  connect( mStrokeWidthSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPictureWidget::mStrokeWidthSpinBox_valueChanged );
70  connect( mPictureRotationOffsetSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPictureWidget::mPictureRotationOffsetSpinBox_valueChanged );
71  connect( mNorthTypeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutPictureWidget::mNorthTypeComboBox_currentIndexChanged );
72  setPanelTitle( tr( "Picture Properties" ) );
73 
74  mFillColorButton->setAllowOpacity( true );
75  mFillColorButton->setColorDialogTitle( tr( "Select Fill Color" ) );
76  mFillColorButton->setContext( QStringLiteral( "composer" ) );
77  mStrokeColorButton->setAllowOpacity( true );
78  mStrokeColorButton->setColorDialogTitle( tr( "Select Stroke Color" ) );
79  mStrokeColorButton->setContext( QStringLiteral( "composer" ) );
80 
81  mFillColorDDBtn->registerLinkedWidget( mFillColorButton );
82  mStrokeColorDDBtn->registerLinkedWidget( mStrokeColorButton );
83 
84  mNorthTypeComboBox->blockSignals( true );
85  mNorthTypeComboBox->addItem( tr( "Grid North" ), QgsLayoutItemPicture::GridNorth );
86  mNorthTypeComboBox->addItem( tr( "True North" ), QgsLayoutItemPicture::TrueNorth );
87  mNorthTypeComboBox->blockSignals( false );
88  mPictureRotationOffsetSpinBox->setClearValue( 0.0 );
89  mPictureRotationSpinBox->setClearValue( 0.0 );
90 
91  //add widget for general composer item properties
92  mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, picture );
93  mainLayout->addWidget( mItemPropertiesWidget );
94 
95  if ( mPicture->layout() )
96  {
97  mComposerMapComboBox->setCurrentLayout( mPicture->layout() );
98  mComposerMapComboBox->setItemType( QgsLayoutItemRegistry::LayoutMap );
99  connect( mComposerMapComboBox, &QgsLayoutItemComboBox::itemChanged, this, &QgsLayoutPictureWidget::mapChanged );
100  }
101 
102  setGuiElementValues();
103  mPreviewsLoadingLabel->hide();
104 
105  mPreviewListWidget->setIconSize( QSize( 30, 30 ) );
106 
107  // mSearchDirectoriesGroupBox is a QgsCollapsibleGroupBoxBasic, so its collapsed state should not be saved/restored
108  mSearchDirectoriesGroupBox->setCollapsed( true );
109  // setup connection for loading previews on first expansion of group box
110  connect( mSearchDirectoriesGroupBox, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsLayoutPictureWidget::loadPicturePreviews );
111 
112  connect( mPicture, &QgsLayoutObject::changed, this, &QgsLayoutPictureWidget::setGuiElementValues );
113  connect( mPicture, &QgsLayoutItemPicture::pictureRotationChanged, this, &QgsLayoutPictureWidget::setPicRotationSpinValue );
114 
115  //connections for data defined buttons
116  connect( mSourceDDBtn, &QgsPropertyOverrideButton::activated, mPictureLineEdit, &QLineEdit::setDisabled );
121 }
122 
124 {
125  if ( mItemPropertiesWidget )
126  mItemPropertiesWidget->setMasterLayout( masterLayout );
127 }
128 
129 void QgsLayoutPictureWidget::mPictureBrowseButton_clicked()
130 {
131  QgsSettings s;
132  QString openDir;
133  QString lineEditText = mPictureLineEdit->text();
134  if ( !lineEditText.isEmpty() )
135  {
136  QFileInfo openDirFileInfo( lineEditText );
137  openDir = openDirFileInfo.path();
138  }
139 
140  if ( openDir.isEmpty() )
141  {
142  openDir = s.value( QStringLiteral( "/UI/lastComposerPictureDir" ), QDir::homePath() ).toString();
143  }
144 
145  //show file dialog
146  QString filePath = QFileDialog::getOpenFileName( this, tr( "Select SVG or Image File" ), openDir );
147  if ( filePath.isEmpty() )
148  {
149  return;
150  }
151 
152  //check if file exists
153  QFileInfo fileInfo( filePath );
154  if ( !fileInfo.exists() || !fileInfo.isReadable() )
155  {
156  QMessageBox::critical( nullptr, tr( "Select File" ), tr( "Error, file does not exist or is not readable." ) );
157  return;
158  }
159 
160  s.setValue( QStringLiteral( "/UI/lastComposerPictureDir" ), fileInfo.absolutePath() );
161 
162  mPictureLineEdit->blockSignals( true );
163  mPictureLineEdit->setText( filePath );
164  mPictureLineEdit->blockSignals( false );
165  updateSvgParamGui();
166 
167  //pass file path to QgsLayoutItemPicture
168  if ( mPicture )
169  {
170  mPicture->beginCommand( tr( "Change Picture" ) );
171  mPicture->setPicturePath( filePath );
172  mPicture->update();
173  mPicture->endCommand();
174  }
175 }
176 
177 void QgsLayoutPictureWidget::mPictureLineEdit_editingFinished()
178 {
179  if ( mPicture )
180  {
181  QString filePath = mPictureLineEdit->text();
182 
183  mPicture->beginCommand( tr( "Change Picture" ) );
184  mPicture->setPicturePath( filePath );
185  mPicture->update();
186  mPicture->endCommand();
187  updateSvgParamGui();
188  }
189 }
190 
191 void QgsLayoutPictureWidget::mPictureRotationSpinBox_valueChanged( double d )
192 {
193  if ( mPicture )
194  {
195  mPicture->beginCommand( tr( "Change Picture Rotation" ), QgsLayoutItem::UndoPictureRotation );
196  mPicture->setPictureRotation( d );
197  mPicture->endCommand();
198  }
199 }
200 
201 void QgsLayoutPictureWidget::mPreviewListWidget_currentItemChanged( QListWidgetItem *current, QListWidgetItem *previous )
202 {
203  Q_UNUSED( previous )
204  if ( !mPicture || !current )
205  {
206  return;
207  }
208 
209  QString absoluteFilePath = current->data( Qt::UserRole ).toString();
210  mPicture->beginCommand( tr( "Change Picture" ) );
211  mPicture->setPicturePath( absoluteFilePath );
212  mPictureLineEdit->setText( absoluteFilePath );
213  mPicture->update();
214  mPicture->endCommand();
215  updateSvgParamGui();
216 }
217 
218 void QgsLayoutPictureWidget::mAddDirectoryButton_clicked()
219 {
220  //let user select a directory
221  QString directory = QFileDialog::getExistingDirectory( this, tr( "Select New Preview Directory" ) );
222  if ( directory.isNull() )
223  {
224  return; //dialog canceled by user
225  }
226 
227  //add entry to mSearchDirectoriesComboBox
228  mSearchDirectoriesComboBox->addItem( directory );
229 
230  //and add icons to the preview
231  addDirectoryToPreview( directory );
232 
233  //update the image directory list in the settings
234  QgsSettings s;
235  QStringList userDirList = s.value( QStringLiteral( "/Composer/PictureWidgetDirectories" ) ).toStringList();
236  if ( !userDirList.contains( directory ) )
237  {
238  userDirList.append( directory );
239  }
240  s.setValue( QStringLiteral( "/Composer/PictureWidgetDirectories" ), userDirList );
241 }
242 
243 void QgsLayoutPictureWidget::mRemoveDirectoryButton_clicked()
244 {
245  QString directoryToRemove = mSearchDirectoriesComboBox->currentText();
246  if ( directoryToRemove.isEmpty() )
247  {
248  return;
249  }
250  mSearchDirectoriesComboBox->removeItem( mSearchDirectoriesComboBox->currentIndex() );
251 
252  //remove entries from back to front (to have the indices of existing items constant)
253  for ( int i = ( mPreviewListWidget->count() - 1 ); i >= 0; --i )
254  {
255  QListWidgetItem *currentItem = mPreviewListWidget->item( i );
256  if ( currentItem && currentItem->data( Qt::UserRole ).toString().startsWith( directoryToRemove ) )
257  {
258  delete ( mPreviewListWidget->takeItem( i ) );
259  }
260  }
261 
262  //update the image directory list in the settings
263  QgsSettings s;
264  QStringList userDirList = s.value( QStringLiteral( "/Composer/PictureWidgetDirectories" ) ).toStringList();
265  userDirList.removeOne( directoryToRemove );
266  s.setValue( QStringLiteral( "/Composer/PictureWidgetDirectories" ), userDirList );
267 }
268 
269 void QgsLayoutPictureWidget::mResizeModeComboBox_currentIndexChanged( int )
270 {
271  if ( !mPicture )
272  {
273  return;
274  }
275 
276  mPicture->beginCommand( tr( "Change Resize Mode" ) );
277  mPicture->setResizeMode( static_cast< QgsLayoutItemPicture::ResizeMode >( mResizeModeComboBox->currentData().toInt() ) );
278  mPicture->endCommand();
279 
280  //disable picture rotation for non-zoom modes
281  mRotationGroupBox->setEnabled( mPicture->resizeMode() == QgsLayoutItemPicture::Zoom ||
282  mPicture->resizeMode() == QgsLayoutItemPicture::ZoomResizeFrame );
283 
284  //disable anchor point control for certain zoom modes
285  if ( mPicture->resizeMode() == QgsLayoutItemPicture::Zoom ||
286  mPicture->resizeMode() == QgsLayoutItemPicture::Clip )
287  {
288  mAnchorPointComboBox->setEnabled( true );
289  }
290  else
291  {
292  mAnchorPointComboBox->setEnabled( false );
293  }
294 }
295 
296 void QgsLayoutPictureWidget::mAnchorPointComboBox_currentIndexChanged( int )
297 {
298  if ( !mPicture )
299  {
300  return;
301  }
302 
303  mPicture->beginCommand( tr( "Change Placement" ) );
304  mPicture->setPictureAnchor( static_cast< QgsLayoutItem::ReferencePoint >( mAnchorPointComboBox->currentData().toInt() ) );
305  mPicture->endCommand();
306 }
307 
309 {
311  return false;
312 
313  if ( mPicture )
314  {
315  disconnect( mPicture, &QgsLayoutObject::changed, this, &QgsLayoutPictureWidget::setGuiElementValues );
316  disconnect( mPicture, &QgsLayoutItemPicture::pictureRotationChanged, this, &QgsLayoutPictureWidget::setPicRotationSpinValue );
317  }
318 
319  mPicture = qobject_cast< QgsLayoutItemPicture * >( item );
320  mItemPropertiesWidget->setItem( mPicture );
321 
322  if ( mPicture )
323  {
324  connect( mPicture, &QgsLayoutObject::changed, this, &QgsLayoutPictureWidget::setGuiElementValues );
325  connect( mPicture, &QgsLayoutItemPicture::pictureRotationChanged, this, &QgsLayoutPictureWidget::setPicRotationSpinValue );
326  }
327 
328  setGuiElementValues();
329 
330  return true;
331 }
332 
333 void QgsLayoutPictureWidget::mRotationFromComposerMapCheckBox_stateChanged( int state )
334 {
335  if ( !mPicture )
336  {
337  return;
338  }
339 
340  mPicture->beginCommand( tr( "Toggle Rotation Sync" ) );
341  if ( state == Qt::Unchecked )
342  {
343  mPicture->setLinkedMap( nullptr );
344  mPictureRotationSpinBox->setEnabled( true );
345  mComposerMapComboBox->setEnabled( false );
346  mNorthTypeComboBox->setEnabled( false );
347  mPictureRotationOffsetSpinBox->setEnabled( false );
348  mPicture->setPictureRotation( mPictureRotationSpinBox->value() );
349  }
350  else
351  {
352  QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( mComposerMapComboBox->currentItem() );
353  mPicture->setLinkedMap( map );
354  mPictureRotationSpinBox->setEnabled( false );
355  mNorthTypeComboBox->setEnabled( true );
356  mPictureRotationOffsetSpinBox->setEnabled( true );
357  mComposerMapComboBox->setEnabled( true );
358  }
359  mPicture->endCommand();
360 }
361 
362 void QgsLayoutPictureWidget::mapChanged( QgsLayoutItem *item )
363 {
364  if ( !mPicture )
365  {
366  return;
367  }
368 
369  //get composition
370  const QgsLayout *layout = mPicture->layout();
371  if ( !layout )
372  {
373  return;
374  }
375 
376  QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap *>( item );
377  if ( !map )
378  {
379  return;
380  }
381 
382  mPicture->beginCommand( tr( "Change Rotation Map" ) );
383  mPicture->setLinkedMap( map );
384  mPicture->update();
385  mPicture->endCommand();
386 }
387 
388 void QgsLayoutPictureWidget::setPicRotationSpinValue( double r )
389 {
390  mPictureRotationSpinBox->blockSignals( true );
391  mPictureRotationSpinBox->setValue( r );
392  mPictureRotationSpinBox->blockSignals( false );
393 }
394 
395 void QgsLayoutPictureWidget::setGuiElementValues()
396 {
397  //set initial gui values
398  if ( mPicture )
399  {
400  mPictureRotationSpinBox->blockSignals( true );
401  mPictureLineEdit->blockSignals( true );
402  mComposerMapComboBox->blockSignals( true );
403  mRotationFromComposerMapCheckBox->blockSignals( true );
404  mNorthTypeComboBox->blockSignals( true );
405  mPictureRotationOffsetSpinBox->blockSignals( true );
406  mResizeModeComboBox->blockSignals( true );
407  mAnchorPointComboBox->blockSignals( true );
408  mFillColorButton->blockSignals( true );
409  mStrokeColorButton->blockSignals( true );
410  mStrokeWidthSpinBox->blockSignals( true );
411 
412  mPictureLineEdit->setText( mPicture->picturePath() );
413  mPictureRotationSpinBox->setValue( mPicture->pictureRotation() );
414 
415  mComposerMapComboBox->setItem( mPicture->linkedMap() );
416 
417  if ( mPicture->linkedMap() )
418  {
419  mRotationFromComposerMapCheckBox->setCheckState( Qt::Checked );
420  mPictureRotationSpinBox->setEnabled( false );
421  mComposerMapComboBox->setEnabled( true );
422  mNorthTypeComboBox->setEnabled( true );
423  mPictureRotationOffsetSpinBox->setEnabled( true );
424  }
425  else
426  {
427  mRotationFromComposerMapCheckBox->setCheckState( Qt::Unchecked );
428  mPictureRotationSpinBox->setEnabled( true );
429  mComposerMapComboBox->setEnabled( false );
430  mNorthTypeComboBox->setEnabled( false );
431  mPictureRotationOffsetSpinBox->setEnabled( false );
432  }
433  mNorthTypeComboBox->setCurrentIndex( mNorthTypeComboBox->findData( mPicture->northMode() ) );
434  mPictureRotationOffsetSpinBox->setValue( mPicture->northOffset() );
435 
436  mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mPicture->resizeMode() ) );
437  //disable picture rotation for non-zoom modes
438  mRotationGroupBox->setEnabled( mPicture->resizeMode() == QgsLayoutItemPicture::Zoom ||
439  mPicture->resizeMode() == QgsLayoutItemPicture::ZoomResizeFrame );
440 
441  mAnchorPointComboBox->setCurrentIndex( mAnchorPointComboBox->findData( mPicture->pictureAnchor() ) );
442  //disable anchor point control for certain zoom modes
443  if ( mPicture->resizeMode() == QgsLayoutItemPicture::Zoom ||
444  mPicture->resizeMode() == QgsLayoutItemPicture::Clip )
445  {
446  mAnchorPointComboBox->setEnabled( true );
447  }
448  else
449  {
450  mAnchorPointComboBox->setEnabled( false );
451  }
452 
453  updateSvgParamGui( false );
454  mFillColorButton->setColor( mPicture->svgFillColor() );
455  mStrokeColorButton->setColor( mPicture->svgStrokeColor() );
456  mStrokeWidthSpinBox->setValue( mPicture->svgStrokeWidth() );
457 
458  mRotationFromComposerMapCheckBox->blockSignals( false );
459  mPictureRotationSpinBox->blockSignals( false );
460  mPictureLineEdit->blockSignals( false );
461  mComposerMapComboBox->blockSignals( false );
462  mNorthTypeComboBox->blockSignals( false );
463  mPictureRotationOffsetSpinBox->blockSignals( false );
464  mResizeModeComboBox->blockSignals( false );
465  mAnchorPointComboBox->blockSignals( false );
466  mFillColorButton->blockSignals( false );
467  mStrokeColorButton->blockSignals( false );
468  mStrokeWidthSpinBox->blockSignals( false );
469 
471  }
472 }
473 
474 QIcon QgsLayoutPictureWidget::svgToIcon( const QString &filePath ) const
475 {
476  QColor fill, stroke;
477  double strokeWidth, fillOpacity, strokeOpacity;
478  bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
479  bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultStrokeColor = false,
480  hasDefaultStrokeWidth = false, hasDefaultStrokeOpacity = false;
481  QgsApplication::svgCache()->containsParams( filePath, fillParam, hasDefaultFillColor, fill,
482  fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
483  strokeParam, hasDefaultStrokeColor, stroke,
484  strokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
485  strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
486 
487  //if defaults not set in symbol, use these values
488  if ( !hasDefaultFillColor )
489  fill = QColor( 200, 200, 200 );
490  fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
491  if ( !hasDefaultStrokeColor )
492  stroke = Qt::black;
493  stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
494  if ( !hasDefaultStrokeWidth )
495  strokeWidth = 0.6;
496 
497  bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
498  const QImage &img = QgsApplication::svgCache()->svgAsImage( filePath, 30.0, fill, stroke, strokeWidth, 3.5 /*appr. 88 dpi*/, fitsInCache );
499 
500  return QIcon( QPixmap::fromImage( img ) );
501 }
502 
503 void QgsLayoutPictureWidget::updateSvgParamGui( bool resetValues )
504 {
505  if ( !mPicture )
506  return;
507 
508  QString picturePath = mPicture->picturePath();
509  if ( !picturePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
510  {
511  mFillColorButton->setEnabled( false );
512  mStrokeColorButton->setEnabled( false );
513  mStrokeWidthSpinBox->setEnabled( false );
514  return;
515  }
516 
517  //activate gui for svg parameters only if supported by the svg file
518  bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
519  QColor defaultFill, defaultStroke;
520  double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
521  bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
522  QgsApplication::svgCache()->containsParams( picturePath, hasFillParam, hasDefaultFillColor, defaultFill,
523  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
524  hasStrokeParam, hasDefaultStrokeColor, defaultStroke,
525  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
526  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
527 
528  if ( resetValues )
529  {
530  QColor fill = mFillColorButton->color();
531  double newOpacity = hasFillOpacityParam ? fill.alphaF() : 1.0;
532  if ( hasDefaultFillColor )
533  {
534  fill = defaultFill;
535  }
536  fill.setAlphaF( hasDefaultFillOpacity ? defaultFillOpacity : newOpacity );
537  mFillColorButton->setColor( fill );
538  }
539  mFillColorButton->setEnabled( hasFillParam );
540  mFillColorButton->setAllowOpacity( hasFillOpacityParam );
541  if ( resetValues )
542  {
543  QColor stroke = mStrokeColorButton->color();
544  double newOpacity = hasStrokeOpacityParam ? stroke.alphaF() : 1.0;
545  if ( hasDefaultStrokeColor )
546  {
547  stroke = defaultStroke;
548  }
549  stroke.setAlphaF( hasDefaultStrokeOpacity ? defaultStrokeOpacity : newOpacity );
550  mStrokeColorButton->setColor( stroke );
551  }
552  mStrokeColorButton->setEnabled( hasStrokeParam );
553  mStrokeColorButton->setAllowOpacity( hasStrokeOpacityParam );
554  if ( hasDefaultStrokeWidth && resetValues )
555  {
556  mStrokeWidthSpinBox->setValue( defaultStrokeWidth );
557  }
558  mStrokeWidthSpinBox->setEnabled( hasStrokeWidthParam );
559 }
560 
561 int QgsLayoutPictureWidget::addDirectoryToPreview( const QString &path )
562 {
563  //go through all files of a directory
564  QDir directory( path );
565  if ( !directory.exists() || !directory.isReadable() )
566  {
567  return 1; //error
568  }
569 
570  QFileInfoList fileList = directory.entryInfoList( QDir::Files );
571  QFileInfoList::const_iterator fileIt = fileList.constBegin();
572 
573  QProgressDialog progress( tr( "Adding Icons…" ), tr( "Abort" ), 0, fileList.size() - 1, this );
574  //cancel button does not seem to work properly with modal dialog
575  //progress.setWindowModality(Qt::WindowModal);
576 
577  int counter = 0;
578  for ( ; fileIt != fileList.constEnd(); ++fileIt )
579  {
580 
581  progress.setLabelText( tr( "Creating icon for file %1" ).arg( fileIt->fileName() ) );
582  progress.setValue( counter );
583  QCoreApplication::processEvents();
584  if ( progress.wasCanceled() )
585  {
586  break;
587  }
588  QString filePath = fileIt->absoluteFilePath();
589 
590  //test if file is svg or pixel format
591  bool fileIsPixel = false;
592  bool fileIsSvg = testSvgFile( filePath );
593  if ( !fileIsSvg )
594  {
595  fileIsPixel = testImageFile( filePath );
596  }
597 
598  //exclude files that are not svg or image
599  if ( !fileIsSvg && !fileIsPixel )
600  {
601  ++counter;
602  continue;
603  }
604 
605  QListWidgetItem *listItem = new QListWidgetItem( mPreviewListWidget );
606  listItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
607 
608  if ( fileIsSvg )
609  {
610  // render SVG file
611  QIcon icon = svgToIcon( filePath );
612  listItem->setIcon( icon );
613  }
614  else //for pixel formats: create icon from scaled pixmap
615  {
616  QPixmap iconPixmap( filePath );
617  if ( iconPixmap.isNull() )
618  {
619  ++counter;
620  continue; //unknown file format or other problem
621  }
622  //set pixmap hardcoded to 30/30, same as icon size for mPreviewListWidget
623  QPixmap scaledPixmap( iconPixmap.scaled( QSize( 30, 30 ), Qt::KeepAspectRatio ) );
624  QIcon icon( scaledPixmap );
625  listItem->setIcon( icon );
626  }
627 
628  listItem->setText( QString() );
629  //store the absolute icon file path as user data
630  listItem->setData( Qt::UserRole, fileIt->absoluteFilePath() );
631  ++counter;
632  }
633 
634  return 0;
635 }
636 
638 {
639  mPreviewListWidget->clear();
640 
641  //list all directories in $prefix/share/qgis/svg
642  QStringList svgPaths = QgsApplication::svgPaths();
643  for ( int i = 0; i < svgPaths.size(); i++ )
644  {
645  QDir svgDirectory( svgPaths[i] );
646  if ( !svgDirectory.exists() || !svgDirectory.isReadable() )
647  {
648  continue;
649  }
650 
651  //add directory itself
652  mSearchDirectoriesComboBox->addItem( svgDirectory.absolutePath() );
653  addDirectoryToPreview( svgDirectory.absolutePath() );
654 
655  //and also subdirectories
656  QFileInfoList directoryList = svgDirectory.entryInfoList( QDir::Dirs | QDir::NoDotAndDotDot );
657  QFileInfoList::const_iterator dirIt = directoryList.constBegin();
658  for ( ; dirIt != directoryList.constEnd(); ++dirIt )
659  {
660  if ( addDirectoryToPreview( dirIt->absoluteFilePath() ) == 0 )
661  {
662  mSearchDirectoriesComboBox->addItem( dirIt->absoluteFilePath() );
663  }
664  }
665  }
666 
667  //include additional user-defined directories for images
668  QgsSettings s;
669  QStringList userDirList = s.value( QStringLiteral( "/Composer/PictureWidgetDirectories" ) ).toStringList();
670  QStringList::const_iterator userDirIt = userDirList.constBegin();
671  for ( ; userDirIt != userDirList.constEnd(); ++userDirIt )
672  {
673  addDirectoryToPreview( *userDirIt );
674  mSearchDirectoriesComboBox->addItem( *userDirIt );
675  }
676 
677  mPreviewsLoaded = true;
678 }
679 
680 bool QgsLayoutPictureWidget::testSvgFile( const QString &filename ) const
681 {
682  //QSvgRenderer crashes with some (non-svg) xml documents.
683  //So at least we try to sort out the ones with different suffixes
684  if ( !filename.endsWith( QLatin1String( ".svg" ) ) )
685  {
686  return false;
687  }
688 
689  QSvgRenderer svgRenderer( filename );
690  return svgRenderer.isValid();
691 }
692 
693 bool QgsLayoutPictureWidget::testImageFile( const QString &filename ) const
694 {
695  QString formatName = QString( QImageReader::imageFormat( filename ) );
696  return !formatName.isEmpty(); //file is in a supported pixel format
697 }
698 
699 void QgsLayoutPictureWidget::loadPicturePreviews( bool collapsed )
700 {
701  if ( mPreviewsLoaded )
702  {
703  return;
704  }
705 
706  if ( !collapsed ) // load the previews only on first parent group box expansion
707  {
708  mPreviewListWidget->hide();
709  mPreviewsLoadingLabel->show();
711  mPreviewsLoadingLabel->hide();
712  mPreviewListWidget->show();
713  }
714 }
715 
716 void QgsLayoutPictureWidget::mFillColorButton_colorChanged( const QColor &color )
717 {
718  mPicture->beginCommand( tr( "Change Picture Fill Color" ), QgsLayoutItem::UndoPictureFillColor );
719  mPicture->setSvgFillColor( color );
720  mPicture->endCommand();
721  mPicture->update();
722 }
723 
724 void QgsLayoutPictureWidget::mStrokeColorButton_colorChanged( const QColor &color )
725 {
726  mPicture->beginCommand( tr( "Change Picture Stroke Color" ), QgsLayoutItem::UndoPictureStrokeColor );
727  mPicture->setSvgStrokeColor( color );
728  mPicture->endCommand();
729  mPicture->update();
730 }
731 
732 void QgsLayoutPictureWidget::mStrokeWidthSpinBox_valueChanged( double d )
733 {
734  mPicture->beginCommand( tr( "Change Picture Stroke Width" ), QgsLayoutItem::UndoPictureStrokeWidth );
735  mPicture->setSvgStrokeWidth( d );
736  mPicture->endCommand();
737  mPicture->update();
738 }
739 
740 void QgsLayoutPictureWidget::mPictureRotationOffsetSpinBox_valueChanged( double d )
741 {
742  mPicture->beginCommand( tr( "Change Picture North Offset" ), QgsLayoutItem::UndoPictureNorthOffset );
743  mPicture->setNorthOffset( d );
744  mPicture->endCommand();
745  mPicture->update();
746 }
747 
748 void QgsLayoutPictureWidget::mNorthTypeComboBox_currentIndexChanged( int index )
749 {
750  mPicture->beginCommand( tr( "Change Picture North Mode" ) );
751  mPicture->setNorthMode( static_cast< QgsLayoutItemPicture::NorthMode >( mNorthTypeComboBox->itemData( index ).toInt() ) );
752  mPicture->endCommand();
753  mPicture->update();
754 }
755 
756 void QgsLayoutPictureWidget::resizeEvent( QResizeEvent *event )
757 {
758  Q_UNUSED( event )
759  mSearchDirectoriesComboBox->setMinimumWidth( mPreviewListWidget->sizeHint().width() );
760 }
761 
763 {
764  updateDataDefinedButton( mSourceDDBtn );
765  updateDataDefinedButton( mFillColorDDBtn );
766  updateDataDefinedButton( mStrokeColorDDBtn );
767  updateDataDefinedButton( mStrokeWidthDDBtn );
768 
769  //initial state of controls - disable related controls when dd buttons are active
770  mPictureLineEdit->setEnabled( !mSourceDDBtn->isActive() );
771 }
772 
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
QgsLayoutPictureWidget(QgsLayoutItemPicture *picture)
constructor
Base class for graphical items within a QgsLayout.
int type() const override
Returns a unique graphics item type identifier.
Lower left corner of 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.
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false)
Gets SVG as QImage.
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an svg file contains parameters for fill, stroke color, stroke width.
Stretches image to fit frame, ignores aspect ratio.
Upper center of item.
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
A layout item subclass that displays SVG files or raster format images (jpg, png, ...
A widget for controlling the common properties of layout items (e.g.
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...
Lower right corner of item.
void registerDataDefinedButton(QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty property)
Registers a data defined button, setting up its initial value, connections and description.
Layout graphical items for displaying a map.
Upper right corner of item.
bool setNewItem(QgsLayoutItem *item) override
Attempts to update the widget to show the properties for the specified item.
Lower center of item.
Middle right of item.
void pictureRotationChanged(double newRotation)
Emitted on picture rotation change.
Upper left corner of item.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void itemChanged(QgsLayoutItem *item)
Emitted whenever the currently selected item changes.
Middle left of item.
Enlarges image to fit frame, then resizes frame to fit resultant image.
void colorChanged(const QColor &color)
Emitted whenever a new color is set for the button.
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.
Center of item.
void addStandardDirectoriesToPreview()
Add the icons of the standard directories to the preview.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout) override
Sets the master layout associated with the item.
void resizeEvent(QResizeEvent *event) override
Sets size of frame to match original size of image without scaling.
Draws image at original size and clips any portion which falls outside frame.
static QStringList svgPaths()
Returns the paths to svg directories.
void setItem(QgsLayoutItem *item)
Sets the layout item.
Enlarges image to fit frame while maintaining aspect ratio of picture.
Interface for master layout type objects, such as print layouts and reports.
void populateDataDefinedButtons()
Initializes data defined buttons to current atlas coverage layer.
void changed()
Emitted when the object&#39;s properties change.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
void setMasterLayout(QgsMasterLayoutInterface *masterLayout)
Sets the master layout associated with the item.