QGIS API Documentation  3.12.1-București (121cc00ff0)
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 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
122  double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 10;
123 #else
124  double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 10;
125 #endif
126  listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
127 
128  mModel->addDesiredIconSize( listItems->iconSize() );
129  listItems->setModel( mModel );
130 
131  connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
132  this, &QgsStyleExportImportDialog::selectionChanged );
133 
134  // use Ok button for starting import and export operations
135  disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
136  connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
137  buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
138 
139  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
140 }
141 
143 {
144  QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
145  if ( selection.isEmpty() )
146  {
147  QMessageBox::warning( this, tr( "Export/import Item(s)" ),
148  tr( "You should select at least one symbol/color ramp." ) );
149  return;
150  }
151 
152  if ( mDialogMode == Export )
153  {
154  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), QDir::homePath(),
155  tr( "XML files (*.xml *.XML)" ) );
156  if ( fileName.isEmpty() )
157  {
158  return;
159  }
160 
161  // ensure the user never omitted the extension from the file name
162  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
163  {
164  fileName += QLatin1String( ".xml" );
165  }
166 
167  mFileName = fileName;
168 
169  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
170  moveStyles( &selection, mStyle, mTempStyle.get() );
171  if ( !mTempStyle->exportXml( mFileName ) )
172  {
173  mCursorOverride.reset();
174  QMessageBox::warning( this, tr( "Export Symbols" ),
175  tr( "Error when saving selected symbols to file:\n%1" )
176  .arg( mTempStyle->errorString() ) );
177  return;
178  }
179  else
180  {
181  mCursorOverride.reset();
182  QMessageBox::information( this, tr( "Export Symbols" ),
183  tr( "The selected symbols were successfully exported to file:\n%1" )
184  .arg( mFileName ) );
185  }
186  }
187  else // import
188  {
189  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
190  moveStyles( &selection, mTempStyle.get(), mStyle );
191 
192  accept();
193  mCursorOverride.reset();
194  }
195 
196  mFileName.clear();
197  mTempStyle->clear();
198 }
199 
200 bool QgsStyleExportImportDialog::populateStyles()
201 {
202  QgsTemporaryCursorOverride override( Qt::WaitCursor );
203 
204  // load symbols and color ramps from file
205  // NOTE mTempStyle is style here
206  mTempStyle->clear();
207  if ( !mTempStyle->importXml( mFileName ) )
208  {
209  override.release();
210  QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ),
211  tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
212  return false;
213  }
214  return true;
215 }
216 
217 void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
218 {
219  QList< QgsStyleManagerDialog::ItemDetails > items;
220  items.reserve( selection->size() );
221  for ( int i = 0; i < selection->size(); ++i )
222  {
223  QModelIndex index = selection->at( i );
224 
225  QgsStyleManagerDialog::ItemDetails details;
226  details.entityType = static_cast< QgsStyle::StyleEntity >( mModel->data( index, QgsStyleModel::TypeRole ).toInt() );
227  if ( details.entityType == QgsStyle::SymbolEntity )
228  details.symbolType = static_cast< QgsSymbol::SymbolType >( mModel->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
229  details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
230 
231  items << details;
232  }
233  QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import,
234  mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
235 }
236 
238 {
239  delete mTempFile;
240  delete mGroupSelectionDlg;
241 }
242 
244 {
245  mImportFileWidget->setFilePath( path );
246 }
247 
249 {
250  listItems->selectAll();
251 }
252 
254 {
255  listItems->clearSelection();
256 }
257 
258 void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
259 {
260  const auto constSymbolNames = symbolNames;
261  for ( const QString &symbolName : constSymbolNames )
262  {
263  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
264  const auto constIndexes = indexes;
265  for ( const QModelIndex &index : constIndexes )
266  {
267  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
268  }
269  }
270 }
271 
272 void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
273 {
274  const auto constSymbolNames = symbolNames;
275  for ( const QString &symbolName : constSymbolNames )
276  {
277  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
278  const auto constIndexes = indexes;
279  for ( const QModelIndex &index : constIndexes )
280  {
281  QItemSelection deselection( index, index );
282  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
283  }
284  }
285 }
286 
287 void QgsStyleExportImportDialog::selectTag( const QString &tagName )
288 {
289  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
290  selectSymbols( symbolNames );
291 }
292 
293 void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
294 {
295  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
296  deselectSymbols( symbolNames );
297 }
298 
299 void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
300 {
301  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
302  selectSymbols( symbolNames );
303  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
304  selectSymbols( symbolNames );
305  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
306  selectSymbols( symbolNames );
307 }
308 
309 void QgsStyleExportImportDialog::deselectSmartgroup( const QString &groupName )
310 {
311  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
312  deselectSymbols( symbolNames );
313  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
314  deselectSymbols( symbolNames );
315  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
316  deselectSymbols( symbolNames );
317 }
318 
320 {
321  if ( ! mGroupSelectionDlg )
322  {
323  mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
324  mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
331  }
332  mGroupSelectionDlg->show();
333  mGroupSelectionDlg->raise();
334  mGroupSelectionDlg->activateWindow();
335 }
336 
338 {
339  ImportSource source = static_cast< ImportSource >( importTypeCombo->itemData( index ).toInt() );
340 
341  switch ( source )
342  {
343  case ImportSource::File:
344  {
345  mLocationStackedEdit->setCurrentIndex( 0 );
346  mLocationLabel->setText( tr( "File" ) );
347  break;
348  }
349 #if 0
350  case ImportSource::Official:
351  {
352  btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
353  locationLineEdit->setEnabled( false );
354  break;
355  }
356 #endif
357  case ImportSource::Url:
358  {
359  mLocationStackedEdit->setCurrentIndex( 1 );
360  mLocationLabel->setText( tr( "URL" ) );
361  break;
362  }
363  }
364 }
365 
366 void QgsStyleExportImportDialog::fetch()
367 {
368  downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
369 }
370 
371 void QgsStyleExportImportDialog::importFileChanged( const QString &path )
372 {
373  if ( path.isEmpty() )
374  return;
375 
376  mFileName = path;
377  QFileInfo pathInfo( mFileName );
378  QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
379  mSymbolTags->setText( tag );
380  if ( QFileInfo::exists( mFileName ) )
381  {
382  mTempStyle->clear();
383  populateStyles();
384  mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
385  QgsSettings settings;
386  settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
387  }
388 }
389 
390 void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
391 {
392  mTempFile = new QTemporaryFile();
393  if ( mTempFile->open() )
394  {
395  mFileName = mTempFile->fileName();
396 
397  QProgressDialog *progressDlg = new QProgressDialog( this );
398  progressDlg->setLabelText( tr( "Downloading style…" ) );
399  progressDlg->setAutoClose( true );
400  progressDlg->show();
401 
403  fetcher->setDescription( tr( "Downloading style" ) );
404  connect( progressDlg, &QProgressDialog::canceled, fetcher, &QgsNetworkContentFetcherTask::cancel );
405  connect( fetcher, &QgsNetworkContentFetcherTask::progressChanged, progressDlg, &QProgressDialog::setValue );
406  connect( fetcher, &QgsNetworkContentFetcherTask::fetched, this, [this, fetcher, progressDlg]
407  {
408  QNetworkReply *reply = fetcher->reply();
409  if ( !reply || reply->error() != QNetworkReply::NoError )
410  {
411  mTempFile->remove();
412  mFileName.clear();
413  if ( reply )
414  QMessageBox::information( this, tr( "Import from URL" ),
415  tr( "HTTP Error! Download failed: %1." ).arg( reply->errorString() ) );
416  }
417  else
418  {
419  mTempFile->write( reply->readAll() );
420  mTempFile->flush();
421  mTempFile->close();
422  populateStyles();
423  }
424  progressDlg->deleteLater();
425  } );
426 
427  QgsApplication::taskManager()->addTask( fetcher );
428  }
429 }
430 
431 void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
432 {
433  Q_UNUSED( selected )
434  Q_UNUSED( deselected )
435  bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
436  buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
437 }
438 
439 void QgsStyleExportImportDialog::showHelp()
440 {
441  QgsHelp::openHelp( QStringLiteral( "style_library/style_manager.html#sharing-style-items" ) );
442 }
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:182
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:84
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:133
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