QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
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 "moc_qgsstyleexportimportdialog.cpp"
19#include "ui_qgsstyleexportimportdialogbase.h"
20
21#include "qgsapplication.h"
22#include "qgsstyle.h"
23#include "qgssymbol.h"
26#include "qgsguiutils.h"
27#include "qgssettings.h"
28#include "qgsgui.h"
29#include "qgsstylemodel.h"
31
32#include <QInputDialog>
33#include <QCloseEvent>
34#include <QFileDialog>
35#include <QMessageBox>
36#include <QNetworkReply>
37#include <QProgressDialog>
38#include <QPushButton>
39#include <QStandardItemModel>
40#include <QTemporaryFile>
41#include <QUrl>
42
44 : QDialog( parent )
45 , mDialogMode( mode )
46 , mStyle( style )
47{
48 setupUi( this );
50
51 // additional buttons
52 QPushButton *pb = nullptr;
53 pb = new QPushButton( tr( "Select All" ) );
54 buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
55 connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::selectAll );
56
57 pb = new QPushButton( tr( "Clear Selection" ) );
58 buttonBox->addButton( pb, QDialogButtonBox::ActionRole );
59 connect( pb, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::clearSelection );
60
61 mTempStyle = std::make_unique< QgsStyle >();
62 mTempStyle->createMemoryDatabase();
63
64 // TODO validate
65 mGroupSelectionDlg = nullptr;
66 mTempFile = nullptr;
67
68 QgsStyle *dialogStyle = nullptr;
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 );
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 const 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 dialogStyle = mTempStyle.get();
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 dialogStyle = mStyle;
118 }
119
120 const double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 10;
121 listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
122 // set a grid size which allows sufficient vertical spacing to fit reasonably sized entity names
123 listItems->setGridSize( QSize( static_cast< int >( listItems->iconSize().width() * 1.4 ), static_cast< int >( listItems->iconSize().height() * 1.7 ) ) );
124 listItems->setTextElideMode( Qt::TextElideMode::ElideRight );
125
126 mModel = new QgsStyleProxyModel( dialogStyle, this );
127
128 mModel->addDesiredIconSize( listItems->iconSize() );
129 mModel->addTargetScreenProperties( QgsScreenProperties( screen() ) );
130
131 listItems->setModel( mModel );
132
133 connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
134 this, &QgsStyleExportImportDialog::selectionChanged );
135
136 // use Ok button for starting import and export operations
137 disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
138 connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
139 buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
140
141 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
142}
143
145{
146 QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
147 if ( selection.isEmpty() )
148 {
149 QMessageBox::warning( this, tr( "Export/import Item(s)" ),
150 tr( "You should select at least one symbol/color ramp." ) );
151 return;
152 }
153
154 if ( mDialogMode == Export )
155 {
156 QgsSettings settings;
157 const QString lastUsedDir = settings.value( QStringLiteral( "StyleManager/lastExportDir" ), QDir::homePath(), QgsSettings::Gui ).toString();
158 QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), lastUsedDir,
159 tr( "XML files (*.xml *.XML)" ) );
160 // return dialog focus on Mac
161 activateWindow();
162 raise();
163 if ( fileName.isEmpty() )
164 {
165 return;
166 }
167 settings.setValue( QStringLiteral( "StyleManager/lastExportDir" ), QFileInfo( fileName ).absolutePath(), QgsSettings::Gui );
168
169 // ensure the user never omitted the extension from the file name
170 if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
171 {
172 fileName += QLatin1String( ".xml" );
173 }
174
175 mFileName = fileName;
176
177 mCursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
178 moveStyles( &selection, mStyle, mTempStyle.get() );
179 if ( !mTempStyle->exportXml( mFileName ) )
180 {
181 mCursorOverride.reset();
182 QMessageBox::warning( this, tr( "Export Symbols" ),
183 tr( "Error when saving selected symbols to file:\n%1" )
184 .arg( mTempStyle->errorString() ) );
185 return;
186 }
187 else
188 {
189 mCursorOverride.reset();
190 QMessageBox::information( this, tr( "Export Symbols" ),
191 tr( "The selected symbols were successfully exported to file:\n%1" )
192 .arg( mFileName ) );
193 }
194 }
195 else // import
196 {
197 mCursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
198 moveStyles( &selection, mTempStyle.get(), mStyle );
199
200 accept();
201 mCursorOverride.reset();
202 }
203
204 mFileName.clear();
205 mTempStyle->clear();
206}
207
208bool QgsStyleExportImportDialog::populateStyles()
209{
210 QgsTemporaryCursorOverride override( Qt::WaitCursor );
211
212 // load symbols and color ramps from file
213 // NOTE mTempStyle is style here
214 mTempStyle->clear();
215 if ( !mTempStyle->importXml( mFileName ) )
216 {
217 override.release();
218 QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ),
219 tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
220 return false;
221 }
222 return true;
223}
224
225void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
226{
227 QList< QgsStyleManagerDialog::ItemDetails > items;
228 items.reserve( selection->size() );
229 for ( int i = 0; i < selection->size(); ++i )
230 {
231 const QModelIndex index = selection->at( i );
232
233 QgsStyleManagerDialog::ItemDetails details;
234 details.entityType = static_cast< QgsStyle::StyleEntity >( mModel->data( index, static_cast< int >( QgsStyleModel::CustomRole::Type ) ).toInt() );
235 if ( details.entityType == QgsStyle::SymbolEntity )
236 details.symbolType = static_cast< Qgis::SymbolType >( mModel->data( index, static_cast< int >( QgsStyleModel::CustomRole::SymbolType ) ).toInt() );
237 details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
238
239 items << details;
240 }
241 QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import,
242 mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
243}
244
246{
247 delete mTempFile;
248 delete mGroupSelectionDlg;
249}
250
252{
253 mImportFileWidget->setFilePath( path );
254}
255
257{
258 listItems->selectAll();
259}
260
262{
263 listItems->clearSelection();
264}
265
267{
268 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
269 {
270 const QModelIndex index = listItems->model()->index( row, 0 );
271 if ( index.data( static_cast< int >( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
272 {
273 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
274 }
275 }
276}
277
279{
280 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
281 {
282 const QModelIndex index = listItems->model()->index( row, 0 );
283 if ( index.data( static_cast< int >( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
284 {
285 const QItemSelection deselection( index, index );
286 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
287 }
288 }
289}
290
291void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
292{
293 const auto constSymbolNames = symbolNames;
294 for ( const QString &symbolName : constSymbolNames )
295 {
296 const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
297 const auto constIndexes = indexes;
298 for ( const QModelIndex &index : constIndexes )
299 {
300 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
301 }
302 }
303}
304
305void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
306{
307 const auto constSymbolNames = symbolNames;
308 for ( const QString &symbolName : constSymbolNames )
309 {
310 const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
311 const auto constIndexes = indexes;
312 for ( const QModelIndex &index : constIndexes )
313 {
314 const QItemSelection deselection( index, index );
315 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
316 }
317 }
318}
319
320void QgsStyleExportImportDialog::selectTag( const QString &tagName )
321{
322 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
323 {
324 const QModelIndex index = listItems->model()->index( row, 0 );
325 if ( index.data( static_cast< int >( QgsStyleModel::CustomRole::Tag ) ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
326 {
327 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
328 }
329 }
330}
331
332void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
333{
334 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
335 {
336 const QModelIndex index = listItems->model()->index( row, 0 );
337 if ( index.data( static_cast< int >( QgsStyleModel::CustomRole::Tag ) ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
338 {
339 const QItemSelection deselection( index, index );
340 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
341 }
342 }
343}
344
345void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
346{
347 QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
348 selectSymbols( symbolNames );
349 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
350 selectSymbols( symbolNames );
351 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
352 selectSymbols( symbolNames );
353}
354
356{
357 QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
358 deselectSymbols( symbolNames );
359 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
360 deselectSymbols( symbolNames );
361 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
362 deselectSymbols( symbolNames );
363}
364
366{
367 if ( ! mGroupSelectionDlg )
368 {
369 mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
370 mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
379 }
380 mGroupSelectionDlg->show();
381 mGroupSelectionDlg->raise();
382 mGroupSelectionDlg->activateWindow();
383}
384
386{
387 const ImportSource source = static_cast< ImportSource >( importTypeCombo->itemData( index ).toInt() );
388
389 switch ( source )
390 {
391 case ImportSource::File:
392 {
393 mLocationStackedEdit->setCurrentIndex( 0 );
394 mLocationLabel->setText( tr( "File" ) );
395 break;
396 }
397#if 0
398 case ImportSource::Official:
399 {
400 btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
401 locationLineEdit->setEnabled( false );
402 break;
403 }
404#endif
405 case ImportSource::Url:
406 {
407 mLocationStackedEdit->setCurrentIndex( 1 );
408 mLocationLabel->setText( tr( "URL" ) );
409 break;
410 }
411 }
412}
413
414void QgsStyleExportImportDialog::fetch()
415{
416 downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
417}
418
419void QgsStyleExportImportDialog::importFileChanged( const QString &path )
420{
421 if ( path.isEmpty() )
422 return;
423
424 mFileName = path;
425 const QFileInfo pathInfo( mFileName );
426 const QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
427 mSymbolTags->setText( tag );
428 if ( QFileInfo::exists( mFileName ) )
429 {
430 mTempStyle->clear();
431 populateStyles();
432 mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
433 QgsSettings settings;
434 settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
435 }
436}
437
438void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
439{
440 mTempFile = new QTemporaryFile();
441 if ( mTempFile->open() )
442 {
443 mFileName = mTempFile->fileName();
444
445 QProgressDialog *progressDlg = new QProgressDialog( this );
446 progressDlg->setLabelText( tr( "Downloading style…" ) );
447 progressDlg->setAutoClose( true );
448 progressDlg->show();
449
451 fetcher->setDescription( tr( "Downloading style" ) );
452 connect( progressDlg, &QProgressDialog::canceled, fetcher, &QgsNetworkContentFetcherTask::cancel );
453 connect( fetcher, &QgsNetworkContentFetcherTask::progressChanged, progressDlg, &QProgressDialog::setValue );
454 connect( fetcher, &QgsNetworkContentFetcherTask::fetched, this, [this, fetcher, progressDlg]
455 {
456 QNetworkReply *reply = fetcher->reply();
457 if ( !reply || reply->error() != QNetworkReply::NoError )
458 {
459 mTempFile->remove();
460 mFileName.clear();
461 if ( reply )
462 QMessageBox::information( this, tr( "Import from URL" ),
463 tr( "HTTP Error! Download failed: %1." ).arg( reply->errorString() ) );
464 }
465 else
466 {
467 mTempFile->write( reply->readAll() );
468 mTempFile->flush();
469 mTempFile->close();
470 populateStyles();
471 }
472 progressDlg->deleteLater();
473 } );
474
476 }
477}
478
479void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
480{
481 Q_UNUSED( selected )
482 Q_UNUSED( deselected )
483 const bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
484 buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
485}
486
487void QgsStyleExportImportDialog::showHelp()
488{
489 QgsHelp::openHelp( QStringLiteral( "style_library/style_manager.html#sharing-style-items" ) );
490}
SymbolType
Symbol types.
Definition qgis.h:574
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5667
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
@ GetFile
Select a single file.
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:209
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:39
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.
Stores properties relating to a screen.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
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
@ IsFavorite
Whether entity is flagged as a favorite.
@ SymbolType
Symbol type (for symbol or legend patch shape entities)
@ Type
Style entity type, see QgsStyle::StyleEntity.
@ Tag
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.
void addTargetScreenProperties(const QgsScreenProperties &properties)
Adds additional target screen properties to use when generating icons for Qt::DecorationRole data.
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
StyleEntity
Enum for Entities involved in a style.
Definition qgsstyle.h:203
@ TextFormatEntity
Text formats.
Definition qgsstyle.h:208
@ SymbolEntity
Symbols.
Definition qgsstyle.h:204
@ ColorrampEntity
Color ramps.
Definition qgsstyle.h:206
int smartgroupId(const QString &smartgroup)
Returns the database id for the given smartgroup name.
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.