QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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"
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 );
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
206bool 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
223void 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
289void 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
303void 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
318void 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
330void 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
343void 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
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
412void QgsStyleExportImportDialog::fetch()
413{
414 downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
415}
416
417void 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
436void 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
474 }
475}
476
477void 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
485void QgsStyleExportImportDialog::showHelp()
486{
487 QgsHelp::openHelp( QStringLiteral( "style_library/style_manager.html#sharing-style-items" ) );
488}
SymbolType
Symbol types.
Definition: qgis.h:206
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2304
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:178
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:38
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:2372
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:2233
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.