QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsstyleexportimportdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsstyleexportimportdialog.cpp
3  ---------------------
4  begin : Jan 2011
5  copyright : (C) 2011 by Alexander Bruy
6  email : alexander dot bruy at gmail dot com
7 
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
18 #include "ui_qgsstyleexportimportdialogbase.h"
19 
20 #include "qgsstyle.h"
21 #include "qgssymbol.h"
22 #include "qgssymbollayerutils.h"
23 #include "qgscolorramp.h"
24 #include "qgslogger.h"
26 #include "qgsguiutils.h"
27 #include "qgssettings.h"
28 #include "qgsgui.h"
29 #include "qgsstylemodel.h"
30 #include "qgsstylemanagerdialog.h"
31 
32 #include <QInputDialog>
33 #include <QCloseEvent>
34 #include <QFileDialog>
35 #include <QMessageBox>
36 #include <QProgressDialog>
37 #include <QPushButton>
38 #include <QStandardItemModel>
39 #include <QNetworkAccessManager>
40 #include <QNetworkReply>
41 
42 
44  : QDialog( parent )
45  , mDialogMode( mode )
46  , mStyle( style )
47 {
48  setupUi( this );
50 
51  // additional buttons
52  QPushButton *pb = nullptr;
53  pb = new QPushButton( tr( "Select All" ) );
54  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
55  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectAll );
56 
57  pb = new QPushButton( tr( "Clear Selection" ) );
58  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
59  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::clearSelection );
60 
61  mTempStyle = qgis::make_unique< QgsStyle >();
62  mTempStyle->createMemoryDatabase();
63 
64  // TODO validate
65  mProgressDlg = nullptr;
66  mGroupSelectionDlg = nullptr;
67  mTempFile = nullptr;
68  mNetManager = new QNetworkAccessManager( this );
69  mNetReply = nullptr;
70 
71  if ( mDialogMode == Import )
72  {
73  setWindowTitle( tr( "Import Item(s)" ) );
74  // populate the import types
75  importTypeCombo->addItem( tr( "File" ), ImportSource::File );
76  // importTypeCombo->addItem( "official QGIS repo online", ImportSource::Official );
77  importTypeCombo->addItem( tr( "URL" ), ImportSource::Url );
78  connect( importTypeCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsStyleExportImportDialog::importTypeChanged );
79  importTypeChanged( 0 );
80 
81  mSymbolTags->setText( QStringLiteral( "imported" ) );
82 
83  connect( mButtonFetch, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::fetch );
84 
85  mImportFileWidget->setStorageMode( QgsFileWidget::GetFile );
86  mImportFileWidget->setDialogTitle( tr( "Load Styles" ) );
87  mImportFileWidget->setFilter( tr( "XML files (*.xml *.XML)" ) );
88 
89  QgsSettings settings;
90  mImportFileWidget->setDefaultRoot( settings.value( QStringLiteral( "StyleManager/lastImportDir" ), QDir::homePath(), QgsSettings::Gui ).toString() );
91  connect( mImportFileWidget, &QgsFileWidget::fileChanged, this, &QgsStyleExportImportDialog::importFileChanged );
92 
93  label->setText( tr( "Select items to import" ) );
94  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Import" ) );
95 
96  mModel = new QgsStyleModel( mTempStyle.get(), this );
97  listItems->setModel( mModel );
98  }
99  else
100  {
101  setWindowTitle( tr( "Export Item(s)" ) );
102  // hide import specific controls when exporting
103  mLocationStackedEdit->setHidden( true );
104  fromLabel->setHidden( true );
105  importTypeCombo->setHidden( true );
106  mLocationLabel->setHidden( true );
107 
108  mFavorite->setHidden( true );
109  mIgnoreXMLTags->setHidden( true );
110 
111  pb = new QPushButton( tr( "Select by Group…" ) );
112  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
113  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectByGroup );
114  tagLabel->setHidden( true );
115  mSymbolTags->setHidden( true );
116  tagHintLabel->setHidden( true );
117 
118  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Export" ) );
119 
120  mModel = new QgsStyleModel( mStyle, this );
121  }
122 
123  double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 10;
124  listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
125 
126  mModel->addDesiredIconSize( listItems->iconSize() );
127  listItems->setModel( mModel );
128 
129  connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
130  this, &QgsStyleExportImportDialog::selectionChanged );
131 
132  // use Ok button for starting import and export operations
133  disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
134  connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
135  buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
136 
137  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
138 }
139 
141 {
142  QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
143  if ( selection.isEmpty() )
144  {
145  QMessageBox::warning( this, tr( "Export/import Item(s)" ),
146  tr( "You should select at least one symbol/color ramp." ) );
147  return;
148  }
149 
150  if ( mDialogMode == Export )
151  {
152  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), QDir::homePath(),
153  tr( "XML files (*.xml *.XML)" ) );
154  if ( fileName.isEmpty() )
155  {
156  return;
157  }
158 
159  // ensure the user never omitted the extension from the file name
160  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
161  {
162  fileName += QLatin1String( ".xml" );
163  }
164 
165  mFileName = fileName;
166 
167  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
168  moveStyles( &selection, mStyle, mTempStyle.get() );
169  if ( !mTempStyle->exportXml( mFileName ) )
170  {
171  mCursorOverride.reset();
172  QMessageBox::warning( this, tr( "Export Symbols" ),
173  tr( "Error when saving selected symbols to file:\n%1" )
174  .arg( mTempStyle->errorString() ) );
175  return;
176  }
177  else
178  {
179  mCursorOverride.reset();
180  QMessageBox::information( this, tr( "Export Symbols" ),
181  tr( "The selected symbols were successfully exported to file:\n%1" )
182  .arg( mFileName ) );
183  }
184  }
185  else // import
186  {
187  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
188  moveStyles( &selection, mTempStyle.get(), mStyle );
189 
190  accept();
191  mCursorOverride.reset();
192  }
193 
194  mFileName.clear();
195  mTempStyle->clear();
196 }
197 
198 bool QgsStyleExportImportDialog::populateStyles()
199 {
200  QgsTemporaryCursorOverride override( Qt::WaitCursor );
201 
202  // load symbols and color ramps from file
203  // NOTE mTempStyle is style here
204  mTempStyle->clear();
205  if ( !mTempStyle->importXml( mFileName ) )
206  {
207  override.release();
208  QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ),
209  tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
210  return false;
211  }
212  return true;
213 }
214 
215 void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
216 {
217  QList< QgsStyleManagerDialog::ItemDetails > items;
218  items.reserve( selection->size() );
219  for ( int i = 0; i < selection->size(); ++i )
220  {
221  QModelIndex index = selection->at( i );
222 
223  QgsStyleManagerDialog::ItemDetails details;
224  details.entityType = static_cast< QgsStyle::StyleEntity >( mModel->data( index, QgsStyleModel::TypeRole ).toInt() );
225  if ( details.entityType == QgsStyle::SymbolEntity )
226  details.symbolType = static_cast< QgsSymbol::SymbolType >( mModel->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
227  details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
228 
229  items << details;
230  }
231  QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import,
232  mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
233 }
234 
236 {
237  delete mTempFile;
238  delete mGroupSelectionDlg;
239 }
240 
242 {
243  mImportFileWidget->setFilePath( path );
244 }
245 
247 {
248  listItems->selectAll();
249 }
250 
252 {
253  listItems->clearSelection();
254 }
255 
256 void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
257 {
258  const auto constSymbolNames = symbolNames;
259  for ( const QString &symbolName : constSymbolNames )
260  {
261  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
262  const auto constIndexes = indexes;
263  for ( const QModelIndex &index : constIndexes )
264  {
265  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
266  }
267  }
268 }
269 
270 void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
271 {
272  const auto constSymbolNames = symbolNames;
273  for ( const QString &symbolName : constSymbolNames )
274  {
275  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
276  const auto constIndexes = indexes;
277  for ( const QModelIndex &index : constIndexes )
278  {
279  QItemSelection deselection( index, index );
280  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
281  }
282  }
283 }
284 
285 void QgsStyleExportImportDialog::selectTag( const QString &tagName )
286 {
287  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
288  selectSymbols( symbolNames );
289 }
290 
291 void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
292 {
293  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
294  deselectSymbols( symbolNames );
295 }
296 
297 void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
298 {
299  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
300  selectSymbols( symbolNames );
301  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
302  selectSymbols( symbolNames );
303 }
304 
305 void QgsStyleExportImportDialog::deselectSmartgroup( const QString &groupName )
306 {
307  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
308  deselectSymbols( symbolNames );
309  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
310  deselectSymbols( symbolNames );
311 }
312 
314 {
315  if ( ! mGroupSelectionDlg )
316  {
317  mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
318  mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
325  }
326  mGroupSelectionDlg->show();
327  mGroupSelectionDlg->raise();
328  mGroupSelectionDlg->activateWindow();
329 }
330 
332 {
333  ImportSource source = static_cast< ImportSource >( importTypeCombo->itemData( index ).toInt() );
334 
335  switch ( source )
336  {
337  case ImportSource::File:
338  {
339  mLocationStackedEdit->setCurrentIndex( 0 );
340  mLocationLabel->setText( tr( "File" ) );
341  break;
342  }
343 #if 0
344  case ImportSource::Official:
345  {
346  btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
347  locationLineEdit->setEnabled( false );
348  break;
349  }
350 #endif
351  case ImportSource::Url:
352  {
353  mLocationStackedEdit->setCurrentIndex( 1 );
354  mLocationLabel->setText( tr( "URL" ) );
355  break;
356  }
357  }
358 }
359 
360 void QgsStyleExportImportDialog::fetch()
361 {
362  downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
363 }
364 
365 void QgsStyleExportImportDialog::importFileChanged( const QString &path )
366 {
367  if ( path.isEmpty() )
368  return;
369 
370  mFileName = path;
371  QFileInfo pathInfo( mFileName );
372  QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
373  mSymbolTags->setText( tag );
374  if ( QFileInfo::exists( mFileName ) )
375  {
376  mTempStyle->clear();
377  populateStyles();
378  mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
379  QgsSettings settings;
380  settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
381  }
382 }
383 
384 void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
385 {
386  // XXX Try to move this code to some core Network interface,
387  // HTTP downloading is a generic functionality that might be used elsewhere
388 
389  mTempFile = new QTemporaryFile();
390  if ( mTempFile->open() )
391  {
392  mFileName = mTempFile->fileName();
393 
394  if ( mProgressDlg )
395  {
396  QProgressDialog *dummy = mProgressDlg;
397  mProgressDlg = nullptr;
398  delete dummy;
399  }
400  mProgressDlg = new QProgressDialog();
401  mProgressDlg->setLabelText( tr( "Downloading style…" ) );
402  mProgressDlg->setAutoClose( true );
403 
404  connect( mProgressDlg, &QProgressDialog::canceled, this, &QgsStyleExportImportDialog::downloadCanceled );
405 
406  // open the network connection and connect the respective slots
407  if ( mNetReply )
408  {
409  QNetworkReply *dummyReply = mNetReply;
410  mNetReply = nullptr;
411  delete dummyReply;
412  }
413  mNetReply = mNetManager->get( QNetworkRequest( url ) );
414 
415  connect( mNetReply, &QNetworkReply::finished, this, &QgsStyleExportImportDialog::httpFinished );
416  connect( mNetReply, &QIODevice::readyRead, this, &QgsStyleExportImportDialog::fileReadyRead );
417  connect( mNetReply, &QNetworkReply::downloadProgress, this, &QgsStyleExportImportDialog::updateProgress );
418  }
419 }
420 
421 void QgsStyleExportImportDialog::httpFinished()
422 {
423  if ( mNetReply->error() )
424  {
425  mTempFile->remove();
426  mFileName.clear();
427  mProgressDlg->hide();
428  QMessageBox::information( this, tr( "Import from URL" ),
429  tr( "HTTP Error! Download failed: %1." ).arg( mNetReply->errorString() ) );
430  return;
431  }
432  else
433  {
434  mTempFile->flush();
435  mTempFile->close();
436  mTempStyle->clear();
437  populateStyles();
438  }
439 }
440 
441 void QgsStyleExportImportDialog::fileReadyRead()
442 {
443  mTempFile->write( mNetReply->readAll() );
444 }
445 
446 void QgsStyleExportImportDialog::updateProgress( qint64 bytesRead, qint64 bytesTotal )
447 {
448  mProgressDlg->setMaximum( bytesTotal );
449  mProgressDlg->setValue( bytesRead );
450 }
451 
452 void QgsStyleExportImportDialog::downloadCanceled()
453 {
454  mNetReply->abort();
455  mTempFile->remove();
456  mFileName.clear();
457 }
458 
459 void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
460 {
461  Q_UNUSED( selected )
462  Q_UNUSED( deselected )
463  bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
464  buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
465 }
466 
467 void QgsStyleExportImportDialog::showHelp()
468 {
469  QgsHelp::openHelp( QStringLiteral( "working_with_vector/style_library.html#share-symbols" ) );
470 }
A QAbstractItemModel subclass for showing symbol and color ramp entities contained within a QgsStyle ...
Definition: qgsstylemodel.h:41
void selectByGroup()
selectByGroup open select by group dialog
void deselectSmartgroup(const QString &groupName)
deselectSmartgroup deselects all symbols from a smart group
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
Definition: qgsguiutils.h:203
QgsStyleExportImportDialog(QgsStyle *style, QWidget *parent=nullptr, Mode mode=Export)
Constructor for QgsStyleExportImportDialog, with the specified parent widget.
void fileChanged(const QString &)
emitted as soon as the current file or directory is changed
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:139
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
Definition: qgsstyle.cpp:578
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void tagSelected(const QString &tagName)
tag with tagName has been selected
void clearSelection()
clearSelection deselects all symbols
void smartgroupSelected(const QString &groupName)
smartgroup with groupName has been selected
void selectSmartgroup(const QString &groupName)
selectSmartgroup selects all symbols from a smart group
void allDeselected()
all deselected
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:95
SymbolType
Type of the symbol.
Definition: qgssymbol.h:83
void deselectSymbols(const QStringList &symbolNames)
deselectSymbols deselect symbols by name
Select a single file.
Definition: qgsfilewidget.h:65
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window&#39;s toolbar icons.
void allSelected()
all selected
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:1329
int smartgroupId(const QString &smartgroup)
Returns the DB id for the given smartgroup name.
Definition: qgsstyle.cpp:1223
int tagId(const QString &tag)
Returns the DB id for the given tag name.
Definition: qgsstyle.cpp:1218
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
Name column.
Definition: qgsstylemodel.h:50
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void selectTag(const QString &tagName)
Select the symbols belonging to the given tag.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:104
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
void selectSymbols(const QStringList &symbolNames)
selectSymbols select symbols by name
void deselectTag(const QString &tagName)
Deselect the symbols belonging to the given tag.
void selectAll()
selectAll selects all symbols
QVariant data(const QModelIndex &index, int role) const override
void smartgroupDeselected(const QString &groupName)
smart group with groupName has been deselected
Style entity type, see QgsStyle::StyleEntity.
Definition: qgsstylemodel.h:57
void setImportFilePath(const QString &path)
Sets the initial path to use for importing files, when the dialog is in a Import mode.
void tagDeselected(const QString &tagName)
tag with tagName has been deselected
Symbol type (for symbol entities)
Definition: qgsstylemodel.h:59