QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 "qgsapplication.h"
21 #include "qgsstyle.h"
22 #include "qgssymbol.h"
23 #include "qgssymbollayerutils.h"
24 #include "qgscolorramp.h"
25 #include "qgslogger.h"
28 #include "qgsguiutils.h"
29 #include "qgssettings.h"
30 #include "qgsgui.h"
31 #include "qgsstylemodel.h"
32 #include "qgsstylemanagerdialog.h"
33 
34 #include <QInputDialog>
35 #include <QCloseEvent>
36 #include <QFileDialog>
37 #include <QMessageBox>
38 #include <QNetworkReply>
39 #include <QProgressDialog>
40 #include <QPushButton>
41 #include <QStandardItemModel>
42 
43 
45  : QDialog( parent )
46  , mDialogMode( mode )
47  , mStyle( style )
48 {
49  setupUi( this );
51 
52  // additional buttons
53  QPushButton *pb = nullptr;
54  pb = new QPushButton( tr( "Select All" ) );
55  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
56  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectAll );
57 
58  pb = new QPushButton( tr( "Clear Selection" ) );
59  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
60  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::clearSelection );
61 
62  mTempStyle = qgis::make_unique< QgsStyle >();
63  mTempStyle->createMemoryDatabase();
64 
65  // TODO validate
66  mGroupSelectionDlg = nullptr;
67  mTempFile = nullptr;
68 
69  if ( mDialogMode == Import )
70  {
71  setWindowTitle( tr( "Import Item(s)" ) );
72  // populate the import types
73  importTypeCombo->addItem( tr( "File" ), ImportSource::File );
74  // importTypeCombo->addItem( "official QGIS repo online", ImportSource::Official );
75  importTypeCombo->addItem( tr( "URL" ), ImportSource::Url );
76  connect( importTypeCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsStyleExportImportDialog::importTypeChanged );
77  importTypeChanged( 0 );
78 
79  mSymbolTags->setText( QStringLiteral( "imported" ) );
80 
81  connect( mButtonFetch, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::fetch );
82 
83  mImportFileWidget->setStorageMode( QgsFileWidget::GetFile );
84  mImportFileWidget->setDialogTitle( tr( "Load Styles" ) );
85  mImportFileWidget->setFilter( tr( "XML files (*.xml *.XML)" ) );
86 
87  QgsSettings settings;
88  mImportFileWidget->setDefaultRoot( settings.value( QStringLiteral( "StyleManager/lastImportDir" ), QDir::homePath(), QgsSettings::Gui ).toString() );
89  connect( mImportFileWidget, &QgsFileWidget::fileChanged, this, &QgsStyleExportImportDialog::importFileChanged );
90 
91  label->setText( tr( "Select items to import" ) );
92  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Import" ) );
93 
94  mModel = new QgsStyleModel( mTempStyle.get(), this );
95  listItems->setModel( mModel );
96  }
97  else
98  {
99  setWindowTitle( tr( "Export Item(s)" ) );
100  // hide import specific controls when exporting
101  mLocationStackedEdit->setHidden( true );
102  fromLabel->setHidden( true );
103  importTypeCombo->setHidden( true );
104  mLocationLabel->setHidden( true );
105 
106  mFavorite->setHidden( true );
107  mIgnoreXMLTags->setHidden( true );
108 
109  pb = new QPushButton( tr( "Select by Group…" ) );
110  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
111  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectByGroup );
112  tagLabel->setHidden( true );
113  mSymbolTags->setHidden( true );
114  tagHintLabel->setHidden( true );
115 
116  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Export" ) );
117 
118  mModel = new QgsStyleModel( mStyle, this );
119  }
120 
121  double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 10;
122  listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
123 
124  mModel->addDesiredIconSize( listItems->iconSize() );
125  listItems->setModel( mModel );
126 
127  connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
128  this, &QgsStyleExportImportDialog::selectionChanged );
129 
130  // use Ok button for starting import and export operations
131  disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
132  connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
133  buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
134 
135  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
136 }
137 
139 {
140  QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
141  if ( selection.isEmpty() )
142  {
143  QMessageBox::warning( this, tr( "Export/import Item(s)" ),
144  tr( "You should select at least one symbol/color ramp." ) );
145  return;
146  }
147 
148  if ( mDialogMode == Export )
149  {
150  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), QDir::homePath(),
151  tr( "XML files (*.xml *.XML)" ) );
152  if ( fileName.isEmpty() )
153  {
154  return;
155  }
156 
157  // ensure the user never omitted the extension from the file name
158  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
159  {
160  fileName += QLatin1String( ".xml" );
161  }
162 
163  mFileName = fileName;
164 
165  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
166  moveStyles( &selection, mStyle, mTempStyle.get() );
167  if ( !mTempStyle->exportXml( mFileName ) )
168  {
169  mCursorOverride.reset();
170  QMessageBox::warning( this, tr( "Export Symbols" ),
171  tr( "Error when saving selected symbols to file:\n%1" )
172  .arg( mTempStyle->errorString() ) );
173  return;
174  }
175  else
176  {
177  mCursorOverride.reset();
178  QMessageBox::information( this, tr( "Export Symbols" ),
179  tr( "The selected symbols were successfully exported to file:\n%1" )
180  .arg( mFileName ) );
181  }
182  }
183  else // import
184  {
185  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
186  moveStyles( &selection, mTempStyle.get(), mStyle );
187 
188  accept();
189  mCursorOverride.reset();
190  }
191 
192  mFileName.clear();
193  mTempStyle->clear();
194 }
195 
196 bool QgsStyleExportImportDialog::populateStyles()
197 {
198  QgsTemporaryCursorOverride override( Qt::WaitCursor );
199 
200  // load symbols and color ramps from file
201  // NOTE mTempStyle is style here
202  mTempStyle->clear();
203  if ( !mTempStyle->importXml( mFileName ) )
204  {
205  override.release();
206  QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ),
207  tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
208  return false;
209  }
210  return true;
211 }
212 
213 void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
214 {
215  QList< QgsStyleManagerDialog::ItemDetails > items;
216  items.reserve( selection->size() );
217  for ( int i = 0; i < selection->size(); ++i )
218  {
219  QModelIndex index = selection->at( i );
220 
221  QgsStyleManagerDialog::ItemDetails details;
222  details.entityType = static_cast< QgsStyle::StyleEntity >( mModel->data( index, QgsStyleModel::TypeRole ).toInt() );
223  if ( details.entityType == QgsStyle::SymbolEntity )
224  details.symbolType = static_cast< QgsSymbol::SymbolType >( mModel->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
225  details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
226 
227  items << details;
228  }
229  QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import,
230  mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
231 }
232 
234 {
235  delete mTempFile;
236  delete mGroupSelectionDlg;
237 }
238 
240 {
241  mImportFileWidget->setFilePath( path );
242 }
243 
245 {
246  listItems->selectAll();
247 }
248 
250 {
251  listItems->clearSelection();
252 }
253 
254 void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
255 {
256  const auto constSymbolNames = symbolNames;
257  for ( const QString &symbolName : constSymbolNames )
258  {
259  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
260  const auto constIndexes = indexes;
261  for ( const QModelIndex &index : constIndexes )
262  {
263  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
264  }
265  }
266 }
267 
268 void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
269 {
270  const auto constSymbolNames = symbolNames;
271  for ( const QString &symbolName : constSymbolNames )
272  {
273  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
274  const auto constIndexes = indexes;
275  for ( const QModelIndex &index : constIndexes )
276  {
277  QItemSelection deselection( index, index );
278  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
279  }
280  }
281 }
282 
283 void QgsStyleExportImportDialog::selectTag( const QString &tagName )
284 {
285  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
286  selectSymbols( symbolNames );
287 }
288 
289 void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
290 {
291  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
292  deselectSymbols( symbolNames );
293 }
294 
295 void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
296 {
297  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
298  selectSymbols( symbolNames );
299  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
300  selectSymbols( symbolNames );
301  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, 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  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
312  deselectSymbols( symbolNames );
313 }
314 
316 {
317  if ( ! mGroupSelectionDlg )
318  {
319  mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
320  mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
327  }
328  mGroupSelectionDlg->show();
329  mGroupSelectionDlg->raise();
330  mGroupSelectionDlg->activateWindow();
331 }
332 
334 {
335  ImportSource source = static_cast< ImportSource >( importTypeCombo->itemData( index ).toInt() );
336 
337  switch ( source )
338  {
339  case ImportSource::File:
340  {
341  mLocationStackedEdit->setCurrentIndex( 0 );
342  mLocationLabel->setText( tr( "File" ) );
343  break;
344  }
345 #if 0
346  case ImportSource::Official:
347  {
348  btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
349  locationLineEdit->setEnabled( false );
350  break;
351  }
352 #endif
353  case ImportSource::Url:
354  {
355  mLocationStackedEdit->setCurrentIndex( 1 );
356  mLocationLabel->setText( tr( "URL" ) );
357  break;
358  }
359  }
360 }
361 
362 void QgsStyleExportImportDialog::fetch()
363 {
364  downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
365 }
366 
367 void QgsStyleExportImportDialog::importFileChanged( const QString &path )
368 {
369  if ( path.isEmpty() )
370  return;
371 
372  mFileName = path;
373  QFileInfo pathInfo( mFileName );
374  QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
375  mSymbolTags->setText( tag );
376  if ( QFileInfo::exists( mFileName ) )
377  {
378  mTempStyle->clear();
379  populateStyles();
380  mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
381  QgsSettings settings;
382  settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
383  }
384 }
385 
386 void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
387 {
388  mTempFile = new QTemporaryFile();
389  if ( mTempFile->open() )
390  {
391  mFileName = mTempFile->fileName();
392 
393  QProgressDialog *progressDlg = new QProgressDialog( this );
394  progressDlg->setLabelText( tr( "Downloading style…" ) );
395  progressDlg->setAutoClose( true );
396  progressDlg->show();
397 
399  fetcher->setDescription( tr( "Downloading style" ) );
400  connect( progressDlg, &QProgressDialog::canceled, fetcher, &QgsNetworkContentFetcherTask::cancel );
401  connect( fetcher, &QgsNetworkContentFetcherTask::progressChanged, [progressDlg]( double progress ) { progressDlg->setValue( progress ); } );
402  connect( fetcher, &QgsNetworkContentFetcherTask::fetched, this, [this, fetcher, progressDlg]
403  {
404  QNetworkReply *reply = fetcher->reply();
405  if ( !reply || reply->error() != QNetworkReply::NoError )
406  {
407  mTempFile->remove();
408  mFileName.clear();
409  if ( reply )
410  QMessageBox::information( this, tr( "Import from URL" ),
411  tr( "HTTP Error! Download failed: %1." ).arg( reply->errorString() ) );
412  }
413  else
414  {
415  mTempFile->write( reply->readAll() );
416  mTempFile->flush();
417  mTempFile->close();
418  populateStyles();
419  }
420  progressDlg->deleteLater();
421  } );
422 
423  QgsApplication::taskManager()->addTask( fetcher );
424  }
425 }
426 
427 void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
428 {
429  Q_UNUSED( selected )
430  Q_UNUSED( deselected )
431  bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
432  buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
433 }
434 
435 void QgsStyleExportImportDialog::showHelp()
436 {
437  QgsHelp::openHelp( QStringLiteral( "working_with_vector/style_library.html#share-symbols" ) );
438 }
A QAbstractItemModel subclass for showing symbol and color ramp entities contained within a QgsStyle ...
Definition: qgsstylemodel.h:45
void setDescription(const QString &description)
Sets the task&#39;s description.
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.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:154
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
Definition: qgsstyle.cpp:933
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
QNetworkReply * reply()
Returns the network reply.
void selectSmartgroup(const QString &groupName)
selectSmartgroup selects all symbols from a smart group
Handles HTTP network content fetching in a background task.
void progressChanged(double progress)
Will be emitted by task when its progress changes.
void allDeselected()
all deselected
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:177
static QgsTaskManager * taskManager()
Returns the application&#39;s task manager, used for managing application wide background task handling...
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
void fileChanged(const QString &path)
Emitted whenever the current file or directory path is changed.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
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:2212
int smartgroupId(const QString &smartgroup)
Returns the DB id for the given smartgroup name.
Definition: qgsstyle.cpp:2081
int tagId(const QString &tag)
Returns the DB id for the given tag name.
Definition: qgsstyle.cpp:2076
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
Name column.
Definition: qgsstylemodel.h:54
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:127
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:61
void setImportFilePath(const QString &path)
Sets the initial path to use for importing files, when the dialog is in a Import mode.
void cancel() override
Notifies the task that it should terminate.
void tagDeselected(const QString &tagName)
tag with tagName has been deselected
Symbol type (for symbol entities)
Definition: qgsstylemodel.h:63