QGIS API Documentation  3.6.0-Noosa (5873452)
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 <QPushButton>
37 #include <QStandardItemModel>
38 
39 
41  : QDialog( parent )
42  , mDialogMode( mode )
43  , mStyle( style )
44 {
45  setupUi( this );
47 
48  // additional buttons
49  QPushButton *pb = nullptr;
50  pb = new QPushButton( tr( "Select All" ) );
51  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
52  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectAll );
53 
54  pb = new QPushButton( tr( "Clear Selection" ) );
55  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
56  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::clearSelection );
57 
58  mTempStyle = qgis::make_unique< QgsStyle >();
59  mTempStyle->createMemoryDatabase();
60 
61  // TODO validate
62  mProgressDlg = nullptr;
63  mGroupSelectionDlg = nullptr;
64  mTempFile = nullptr;
65  mNetManager = new QNetworkAccessManager( this );
66  mNetReply = nullptr;
67 
68  if ( mDialogMode == Import )
69  {
70  setWindowTitle( tr( "Import Item(s)" ) );
71  // populate the import types
72  importTypeCombo->addItem( tr( "File" ), ImportSource::File );
73  // importTypeCombo->addItem( "official QGIS repo online", ImportSource::Official );
74  importTypeCombo->addItem( tr( "URL" ), ImportSource::Url );
75  connect( importTypeCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsStyleExportImportDialog::importTypeChanged );
76  importTypeChanged( 0 );
77 
78  mSymbolTags->setText( QStringLiteral( "imported" ) );
79 
80  connect( mButtonFetch, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::fetch );
81 
82  mImportFileWidget->setStorageMode( QgsFileWidget::GetFile );
83  mImportFileWidget->setDialogTitle( tr( "Load Styles" ) );
84  mImportFileWidget->setFilter( tr( "XML files (*.xml *.XML)" ) );
85 
86  QgsSettings settings;
87  mImportFileWidget->setDefaultRoot( settings.value( QStringLiteral( "StyleManager/lastImportDir" ), QDir::homePath(), QgsSettings::Gui ).toString() );
88  connect( mImportFileWidget, &QgsFileWidget::fileChanged, this, &QgsStyleExportImportDialog::importFileChanged );
89 
90  label->setText( tr( "Select items to import" ) );
91  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Import" ) );
92 
93  mModel = new QgsStyleModel( mTempStyle.get(), this );
94  listItems->setModel( mModel );
95  }
96  else
97  {
98  setWindowTitle( tr( "Export Item(s)" ) );
99  // hide import specific controls when exporting
100  mLocationStackedEdit->setHidden( true );
101  fromLabel->setHidden( true );
102  importTypeCombo->setHidden( true );
103  mLocationLabel->setHidden( true );
104 
105  mFavorite->setHidden( true );
106  mIgnoreXMLTags->setHidden( true );
107 
108  pb = new QPushButton( tr( "Select by Group…" ) );
109  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
110  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectByGroup );
111  tagLabel->setHidden( true );
112  mSymbolTags->setHidden( true );
113  tagHintLabel->setHidden( true );
114 
115  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Export" ) );
116 
117  mModel = new QgsStyleModel( mStyle, this );
118  }
119 
120  double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 10;
121  listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
122 
123  mModel->addDesiredIconSize( listItems->iconSize() );
124  listItems->setModel( mModel );
125 
126  connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
127  this, &QgsStyleExportImportDialog::selectionChanged );
128 
129  // use Ok button for starting import and export operations
130  disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
131  connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
132  buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
133 
134  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
135 }
136 
138 {
139  QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
140  if ( selection.isEmpty() )
141  {
142  QMessageBox::warning( this, tr( "Export/import Item(s)" ),
143  tr( "You should select at least one symbol/color ramp." ) );
144  return;
145  }
146 
147  if ( mDialogMode == Export )
148  {
149  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), QDir::homePath(),
150  tr( "XML files (*.xml *.XML)" ) );
151  if ( fileName.isEmpty() )
152  {
153  return;
154  }
155 
156  // ensure the user never omitted the extension from the file name
157  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
158  {
159  fileName += QLatin1String( ".xml" );
160  }
161 
162  mFileName = fileName;
163 
164  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
165  moveStyles( &selection, mStyle, mTempStyle.get() );
166  if ( !mTempStyle->exportXml( mFileName ) )
167  {
168  mCursorOverride.reset();
169  QMessageBox::warning( this, tr( "Export Symbols" ),
170  tr( "Error when saving selected symbols to file:\n%1" )
171  .arg( mTempStyle->errorString() ) );
172  return;
173  }
174  else
175  {
176  mCursorOverride.reset();
177  QMessageBox::information( this, tr( "Export Symbols" ),
178  tr( "The selected symbols were successfully exported to file:\n%1" )
179  .arg( mFileName ) );
180  }
181  }
182  else // import
183  {
184  mCursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
185  moveStyles( &selection, mTempStyle.get(), mStyle );
186 
187  accept();
188  mCursorOverride.reset();
189  }
190 
191  mFileName.clear();
192  mTempStyle->clear();
193 }
194 
195 bool QgsStyleExportImportDialog::populateStyles()
196 {
197  QgsTemporaryCursorOverride override( Qt::WaitCursor );
198 
199  // load symbols and color ramps from file
200  // NOTE mTempStyle is style here
201  mTempStyle->clear();
202  if ( !mTempStyle->importXml( mFileName ) )
203  {
204  override.release();
205  QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ),
206  tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
207  return false;
208  }
209  return true;
210 }
211 
212 void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
213 {
214  QList< QgsStyleManagerDialog::ItemDetails > items;
215  items.reserve( selection->size() );
216  for ( int i = 0; i < selection->size(); ++i )
217  {
218  QModelIndex index = selection->at( i );
219 
220  QgsStyleManagerDialog::ItemDetails details;
221  details.entityType = static_cast< QgsStyle::StyleEntity >( mModel->data( index, QgsStyleModel::TypeRole ).toInt() );
222  if ( details.entityType == QgsStyle::SymbolEntity )
223  details.symbolType = static_cast< QgsSymbol::SymbolType >( mModel->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
224  details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
225 
226  items << details;
227  }
228  QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import,
229  mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
230 }
231 
233 {
234  delete mTempFile;
235  delete mGroupSelectionDlg;
236 }
237 
239 {
240  mImportFileWidget->setFilePath( path );
241 }
242 
244 {
245  listItems->selectAll();
246 }
247 
249 {
250  listItems->clearSelection();
251 }
252 
253 void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
254 {
255  Q_FOREACH ( const QString &symbolName, symbolNames )
256  {
257  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
258  Q_FOREACH ( const QModelIndex &index, indexes )
259  {
260  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
261  }
262  }
263 }
264 
265 void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
266 {
267  Q_FOREACH ( const QString &symbolName, symbolNames )
268  {
269  QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
270  Q_FOREACH ( const QModelIndex &index, indexes )
271  {
272  QItemSelection deselection( index, index );
273  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
274  }
275  }
276 }
277 
278 void QgsStyleExportImportDialog::selectTag( const QString &tagName )
279 {
280  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
281  selectSymbols( symbolNames );
282 }
283 
284 void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
285 {
286  QStringList symbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( tagName ) );
287  deselectSymbols( symbolNames );
288 }
289 
290 void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
291 {
292  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
293  selectSymbols( symbolNames );
294  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
295  selectSymbols( symbolNames );
296 }
297 
298 void QgsStyleExportImportDialog::deselectSmartgroup( const QString &groupName )
299 {
300  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
301  deselectSymbols( symbolNames );
302  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
303  deselectSymbols( symbolNames );
304 }
305 
307 {
308  if ( ! mGroupSelectionDlg )
309  {
310  mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
311  mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
318  }
319  mGroupSelectionDlg->show();
320  mGroupSelectionDlg->raise();
321  mGroupSelectionDlg->activateWindow();
322 }
323 
325 {
326  ImportSource source = static_cast< ImportSource >( importTypeCombo->itemData( index ).toInt() );
327 
328  switch ( source )
329  {
330  case ImportSource::File:
331  {
332  mLocationStackedEdit->setCurrentIndex( 0 );
333  mLocationLabel->setText( tr( "File" ) );
334  break;
335  }
336 #if 0
337  case ImportSource::Official:
338  {
339  btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
340  locationLineEdit->setEnabled( false );
341  break;
342  }
343 #endif
344  case ImportSource::Url:
345  {
346  mLocationStackedEdit->setCurrentIndex( 1 );
347  mLocationLabel->setText( tr( "URL" ) );
348  break;
349  }
350  }
351 }
352 
353 void QgsStyleExportImportDialog::fetch()
354 {
355  downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
356 }
357 
358 void QgsStyleExportImportDialog::importFileChanged( const QString &path )
359 {
360  if ( path.isEmpty() )
361  return;
362 
363  mFileName = path;
364  QFileInfo pathInfo( mFileName );
365  QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
366  mSymbolTags->setText( tag );
367  if ( QFileInfo::exists( mFileName ) )
368  {
369  mTempStyle->clear();
370  populateStyles();
371  mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
372  QgsSettings settings;
373  settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
374  }
375 }
376 
377 void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
378 {
379  // XXX Try to move this code to some core Network interface,
380  // HTTP downloading is a generic functionality that might be used elsewhere
381 
382  mTempFile = new QTemporaryFile();
383  if ( mTempFile->open() )
384  {
385  mFileName = mTempFile->fileName();
386 
387  if ( mProgressDlg )
388  {
389  QProgressDialog *dummy = mProgressDlg;
390  mProgressDlg = nullptr;
391  delete dummy;
392  }
393  mProgressDlg = new QProgressDialog();
394  mProgressDlg->setLabelText( tr( "Downloading style…" ) );
395  mProgressDlg->setAutoClose( true );
396 
397  connect( mProgressDlg, &QProgressDialog::canceled, this, &QgsStyleExportImportDialog::downloadCanceled );
398 
399  // open the network connection and connect the respective slots
400  if ( mNetReply )
401  {
402  QNetworkReply *dummyReply = mNetReply;
403  mNetReply = nullptr;
404  delete dummyReply;
405  }
406  mNetReply = mNetManager->get( QNetworkRequest( url ) );
407 
408  connect( mNetReply, &QNetworkReply::finished, this, &QgsStyleExportImportDialog::httpFinished );
409  connect( mNetReply, &QIODevice::readyRead, this, &QgsStyleExportImportDialog::fileReadyRead );
410  connect( mNetReply, &QNetworkReply::downloadProgress, this, &QgsStyleExportImportDialog::updateProgress );
411  }
412 }
413 
414 void QgsStyleExportImportDialog::httpFinished()
415 {
416  if ( mNetReply->error() )
417  {
418  mTempFile->remove();
419  mFileName.clear();
420  mProgressDlg->hide();
421  QMessageBox::information( this, tr( "Import from URL" ),
422  tr( "HTTP Error! Download failed: %1." ).arg( mNetReply->errorString() ) );
423  return;
424  }
425  else
426  {
427  mTempFile->flush();
428  mTempFile->close();
429  mTempStyle->clear();
430  populateStyles();
431  }
432 }
433 
434 void QgsStyleExportImportDialog::fileReadyRead()
435 {
436  mTempFile->write( mNetReply->readAll() );
437 }
438 
439 void QgsStyleExportImportDialog::updateProgress( qint64 bytesRead, qint64 bytesTotal )
440 {
441  mProgressDlg->setMaximum( bytesTotal );
442  mProgressDlg->setValue( bytesRead );
443 }
444 
445 void QgsStyleExportImportDialog::downloadCanceled()
446 {
447  mNetReply->abort();
448  mTempFile->remove();
449  mFileName.clear();
450 }
451 
452 void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
453 {
454  Q_UNUSED( selected );
455  Q_UNUSED( deselected );
456  bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
457  buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
458 }
459 
460 void QgsStyleExportImportDialog::showHelp()
461 {
462  QgsHelp::openHelp( QStringLiteral( "working_with_vector/style_library.html#share-symbols" ) );
463 }
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:186
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
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
void allSelected()
all selected
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:1327
int smartgroupId(const QString &smartgroup)
Returns the DB id for the given smartgroup name.
Definition: qgsstyle.cpp:1221
int tagId(const QString &tag)
Returns the DB id for the given tag name.
Definition: qgsstyle.cpp:1216
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