QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 #include <QTemporaryFile>
43 #include <QUrl>
44 
46  : QDialog( parent )
47  , mDialogMode( mode )
48  , mStyle( style )
49 {
50  setupUi( this );
52 
53  // additional buttons
54  QPushButton *pb = nullptr;
55  pb = new QPushButton( tr( "Select All" ) );
56  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
57  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectAll );
58 
59  pb = new QPushButton( tr( "Clear Selection" ) );
60  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
61  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::clearSelection );
62 
63  mTempStyle = std::make_unique< QgsStyle >();
64  mTempStyle->createMemoryDatabase();
65 
66  // TODO validate
67  mGroupSelectionDlg = nullptr;
68  mTempFile = nullptr;
69 
70  QgsStyle *dialogStyle = nullptr;
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  const 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  dialogStyle = mTempStyle.get();
97  }
98  else
99  {
100  setWindowTitle( tr( "Export Item(s)" ) );
101  // hide import specific controls when exporting
102  mLocationStackedEdit->setHidden( true );
103  fromLabel->setHidden( true );
104  importTypeCombo->setHidden( true );
105  mLocationLabel->setHidden( true );
106 
107  mFavorite->setHidden( true );
108  mIgnoreXMLTags->setHidden( true );
109 
110  pb = new QPushButton( tr( "Select by Group…" ) );
111  buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
112  connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectByGroup );
113  tagLabel->setHidden( true );
114  mSymbolTags->setHidden( true );
115  tagHintLabel->setHidden( true );
116 
117  buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Export" ) );
118 
119  dialogStyle = mStyle;
120  }
121 
122  const double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 10;
123  listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
124  // set a grid size which allows sufficient vertical spacing to fit reasonably sized entity names
125  listItems->setGridSize( QSize( static_cast< int >( listItems->iconSize().width() * 1.4 ), static_cast< int >( listItems->iconSize().height() * 1.7 ) ) );
126  listItems->setTextElideMode( Qt::TextElideMode::ElideRight );
127 
128  mModel = new QgsStyleProxyModel( dialogStyle, this );
129 
130  mModel->addDesiredIconSize( listItems->iconSize() );
131 
132  listItems->setModel( mModel );
133 
134  connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
135  this, &QgsStyleExportImportDialog::selectionChanged );
136 
137  // use Ok button for starting import and export operations
138  disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
139  connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
140  buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
141 
142  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
143 }
144 
146 {
147  QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
148  if ( selection.isEmpty() )
149  {
150  QMessageBox::warning( this, tr( "Export/import Item(s)" ),
151  tr( "You should select at least one symbol/color ramp." ) );
152  return;
153  }
154 
155  if ( mDialogMode == Export )
156  {
157  QgsSettings settings;
158  const QString lastUsedDir = settings.value( QStringLiteral( "StyleManager/lastExportDir" ), QDir::homePath(), QgsSettings::Gui ).toString();
159  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), lastUsedDir,
160  tr( "XML files (*.xml *.XML)" ) );
161  if ( fileName.isEmpty() )
162  {
163  return;
164  }
165  settings.setValue( QStringLiteral( "StyleManager/lastExportDir" ), QFileInfo( fileName ).absolutePath(), QgsSettings::Gui );
166 
167  // ensure the user never omitted the extension from the file name
168  if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
169  {
170  fileName += QLatin1String( ".xml" );
171  }
172 
173  mFileName = fileName;
174 
175  mCursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
176  moveStyles( &selection, mStyle, mTempStyle.get() );
177  if ( !mTempStyle->exportXml( mFileName ) )
178  {
179  mCursorOverride.reset();
180  QMessageBox::warning( this, tr( "Export Symbols" ),
181  tr( "Error when saving selected symbols to file:\n%1" )
182  .arg( mTempStyle->errorString() ) );
183  return;
184  }
185  else
186  {
187  mCursorOverride.reset();
188  QMessageBox::information( this, tr( "Export Symbols" ),
189  tr( "The selected symbols were successfully exported to file:\n%1" )
190  .arg( mFileName ) );
191  }
192  }
193  else // import
194  {
195  mCursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
196  moveStyles( &selection, mTempStyle.get(), mStyle );
197 
198  accept();
199  mCursorOverride.reset();
200  }
201 
202  mFileName.clear();
203  mTempStyle->clear();
204 }
205 
206 bool QgsStyleExportImportDialog::populateStyles()
207 {
208  QgsTemporaryCursorOverride override( Qt::WaitCursor );
209 
210  // load symbols and color ramps from file
211  // NOTE mTempStyle is style here
212  mTempStyle->clear();
213  if ( !mTempStyle->importXml( mFileName ) )
214  {
215  override.release();
216  QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ),
217  tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
218  return false;
219  }
220  return true;
221 }
222 
223 void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
224 {
225  QList< QgsStyleManagerDialog::ItemDetails > items;
226  items.reserve( selection->size() );
227  for ( int i = 0; i < selection->size(); ++i )
228  {
229  const QModelIndex index = selection->at( i );
230 
231  QgsStyleManagerDialog::ItemDetails details;
232  details.entityType = static_cast< QgsStyle::StyleEntity >( mModel->data( index, QgsStyleModel::TypeRole ).toInt() );
233  if ( details.entityType == QgsStyle::SymbolEntity )
234  details.symbolType = static_cast< Qgis::SymbolType >( mModel->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
235  details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
236 
237  items << details;
238  }
239  QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import,
240  mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
241 }
242 
244 {
245  delete mTempFile;
246  delete mGroupSelectionDlg;
247 }
248 
250 {
251  mImportFileWidget->setFilePath( path );
252 }
253 
255 {
256  listItems->selectAll();
257 }
258 
260 {
261  listItems->clearSelection();
262 }
263 
265 {
266  for ( int row = 0; row < listItems->model()->rowCount(); ++row )
267  {
268  const QModelIndex index = listItems->model()->index( row, 0 );
269  if ( index.data( QgsStyleModel::IsFavoriteRole ).toBool() )
270  {
271  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
272  }
273  }
274 }
275 
277 {
278  for ( int row = 0; row < listItems->model()->rowCount(); ++row )
279  {
280  const QModelIndex index = listItems->model()->index( row, 0 );
281  if ( index.data( QgsStyleModel::IsFavoriteRole ).toBool() )
282  {
283  const QItemSelection deselection( index, index );
284  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
285  }
286  }
287 }
288 
289 void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
290 {
291  const auto constSymbolNames = symbolNames;
292  for ( const QString &symbolName : constSymbolNames )
293  {
294  const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
295  const auto constIndexes = indexes;
296  for ( const QModelIndex &index : constIndexes )
297  {
298  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
299  }
300  }
301 }
302 
303 void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
304 {
305  const auto constSymbolNames = symbolNames;
306  for ( const QString &symbolName : constSymbolNames )
307  {
308  const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
309  const auto constIndexes = indexes;
310  for ( const QModelIndex &index : constIndexes )
311  {
312  const QItemSelection deselection( index, index );
313  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
314  }
315  }
316 }
317 
318 void QgsStyleExportImportDialog::selectTag( const QString &tagName )
319 {
320  for ( int row = 0; row < listItems->model()->rowCount(); ++row )
321  {
322  const QModelIndex index = listItems->model()->index( row, 0 );
323  if ( index.data( QgsStyleModel::TagRole ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
324  {
325  listItems->selectionModel()->select( index, QItemSelectionModel::Select );
326  }
327  }
328 }
329 
330 void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
331 {
332  for ( int row = 0; row < listItems->model()->rowCount(); ++row )
333  {
334  const QModelIndex index = listItems->model()->index( row, 0 );
335  if ( index.data( QgsStyleModel::TagRole ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
336  {
337  const QItemSelection deselection( index, index );
338  listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
339  }
340  }
341 }
342 
343 void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
344 {
345  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
346  selectSymbols( symbolNames );
347  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
348  selectSymbols( symbolNames );
349  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
350  selectSymbols( symbolNames );
351 }
352 
353 void QgsStyleExportImportDialog::deselectSmartgroup( const QString &groupName )
354 {
355  QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
356  deselectSymbols( symbolNames );
357  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
358  deselectSymbols( symbolNames );
359  symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
360  deselectSymbols( symbolNames );
361 }
362 
364 {
365  if ( ! mGroupSelectionDlg )
366  {
367  mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
368  mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
377  }
378  mGroupSelectionDlg->show();
379  mGroupSelectionDlg->raise();
380  mGroupSelectionDlg->activateWindow();
381 }
382 
384 {
385  const ImportSource source = static_cast< ImportSource >( importTypeCombo->itemData( index ).toInt() );
386 
387  switch ( source )
388  {
389  case ImportSource::File:
390  {
391  mLocationStackedEdit->setCurrentIndex( 0 );
392  mLocationLabel->setText( tr( "File" ) );
393  break;
394  }
395 #if 0
396  case ImportSource::Official:
397  {
398  btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
399  locationLineEdit->setEnabled( false );
400  break;
401  }
402 #endif
403  case ImportSource::Url:
404  {
405  mLocationStackedEdit->setCurrentIndex( 1 );
406  mLocationLabel->setText( tr( "URL" ) );
407  break;
408  }
409  }
410 }
411 
412 void QgsStyleExportImportDialog::fetch()
413 {
414  downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
415 }
416 
417 void QgsStyleExportImportDialog::importFileChanged( const QString &path )
418 {
419  if ( path.isEmpty() )
420  return;
421 
422  mFileName = path;
423  const QFileInfo pathInfo( mFileName );
424  const QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
425  mSymbolTags->setText( tag );
426  if ( QFileInfo::exists( mFileName ) )
427  {
428  mTempStyle->clear();
429  populateStyles();
430  mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
431  QgsSettings settings;
432  settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
433  }
434 }
435 
436 void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
437 {
438  mTempFile = new QTemporaryFile();
439  if ( mTempFile->open() )
440  {
441  mFileName = mTempFile->fileName();
442 
443  QProgressDialog *progressDlg = new QProgressDialog( this );
444  progressDlg->setLabelText( tr( "Downloading style…" ) );
445  progressDlg->setAutoClose( true );
446  progressDlg->show();
447 
449  fetcher->setDescription( tr( "Downloading style" ) );
450  connect( progressDlg, &QProgressDialog::canceled, fetcher, &QgsNetworkContentFetcherTask::cancel );
451  connect( fetcher, &QgsNetworkContentFetcherTask::progressChanged, progressDlg, &QProgressDialog::setValue );
452  connect( fetcher, &QgsNetworkContentFetcherTask::fetched, this, [this, fetcher, progressDlg]
453  {
454  QNetworkReply *reply = fetcher->reply();
455  if ( !reply || reply->error() != QNetworkReply::NoError )
456  {
457  mTempFile->remove();
458  mFileName.clear();
459  if ( reply )
460  QMessageBox::information( this, tr( "Import from URL" ),
461  tr( "HTTP Error! Download failed: %1." ).arg( reply->errorString() ) );
462  }
463  else
464  {
465  mTempFile->write( reply->readAll() );
466  mTempFile->flush();
467  mTempFile->close();
468  populateStyles();
469  }
470  progressDlg->deleteLater();
471  } );
472 
473  QgsApplication::taskManager()->addTask( fetcher );
474  }
475 }
476 
477 void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
478 {
479  Q_UNUSED( selected )
480  Q_UNUSED( deselected )
481  const bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
482  buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
483 }
484 
485 void QgsStyleExportImportDialog::showHelp()
486 {
487  QgsHelp::openHelp( QStringLiteral( "style_library/style_manager.html#sharing-style-items" ) );
488 }
SymbolType
Symbol types.
Definition: qgis.h:169
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:1052
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
@ GetFile
Select a single file.
Definition: qgsfilewidget.h:68
void fileChanged(const QString &path)
Emitted whenever the current file or directory path is changed.
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:168
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
Handles HTTP network content fetching in a background task.
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
QNetworkReply * reply()
Returns the network reply.
void cancel() override
Notifies the task that it should terminate.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void clearSelection()
clearSelection deselects all symbols
void selectTag(const QString &tagName)
Select the symbols belonging to the given tag.
@ Export
Export existing symbols mode.
void deselectSymbols(const QStringList &symbolNames)
deselectSymbols deselect symbols by name
void selectAll()
selectAll selects all symbols
void selectSymbols(const QStringList &symbolNames)
selectSymbols select symbols by name
void selectSmartgroup(const QString &groupName)
selectSmartgroup selects all symbols from a smart group
void selectByGroup()
selectByGroup open select by group dialog
void deselectFavorites()
Deselects favorite symbols.
void setImportFilePath(const QString &path)
Sets the initial path to use for importing files, when the dialog is in a Import mode.
void deselectSmartgroup(const QString &groupName)
deselectSmartgroup deselects all symbols from a smart group
QgsStyleExportImportDialog(QgsStyle *style, QWidget *parent=nullptr, Mode mode=Export)
Constructor for QgsStyleExportImportDialog, with the specified parent widget.
void selectFavorites()
Selects favorite symbols.
void deselectTag(const QString &tagName)
Deselect the symbols belonging to the given tag.
void favoritesDeselected()
Favorites has been deselected.
void allDeselected()
all deselected
void tagSelected(const QString &tagName)
tag with tagName has been selected
void tagDeselected(const QString &tagName)
tag with tagName has been deselected
void smartgroupDeselected(const QString &groupName)
smart group with groupName has been deselected
void favoritesSelected()
Favorites has need selected.
void smartgroupSelected(const QString &groupName)
smartgroup with groupName has been selected
void allSelected()
all selected
@ TypeRole
Style entity type, see QgsStyle::StyleEntity.
@ IsFavoriteRole
Whether entity is flagged as a favorite.
@ SymbolTypeRole
Symbol type (for symbol or legend patch shape entities)
@ TagRole
String list of tags.
@ Name
Name column.
A QSortFilterProxyModel subclass for showing filtered symbol and color ramps entries from a QgsStyle ...
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:2374
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:179
@ TextFormatEntity
Text formats.
Definition: qgsstyle.h:184
@ SymbolEntity
Symbols.
Definition: qgsstyle.h:180
@ ColorrampEntity
Color ramps.
Definition: qgsstyle.h:182
int smartgroupId(const QString &smartgroup)
Returns the database id for the given smartgroup name.
Definition: qgsstyle.cpp:2235
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void progressChanged(double progress)
Will be emitted by task when its progress changes.
void setDescription(const QString &description)
Sets the task's description.
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
Definition: qgsguiutils.h:221
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.