QGIS API Documentation 3.99.0-Master (26c88405ac0)
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
17#include "ui_qgsstyleexportimportdialogbase.h"
19
20#include "qgsapplication.h"
21#include "qgsgui.h"
22#include "qgsguiutils.h"
24#include "qgssettings.h"
25#include "qgsstyle.h"
28#include "qgsstylemodel.h"
29#include "qgssymbol.h"
30
31#include <QCloseEvent>
32#include <QFileDialog>
33#include <QInputDialog>
34#include <QMessageBox>
35#include <QNetworkReply>
36#include <QProgressDialog>
37#include <QPushButton>
38#include <QStandardItemModel>
39#include <QTemporaryFile>
40#include <QUrl>
41
42#include "moc_qgsstyleexportimportdialog.cpp"
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 = std::make_unique<QgsStyle>();
63 mTempStyle->createMemoryDatabase();
64
65 // TODO validate
66 mGroupSelectionDlg = nullptr;
67 mTempFile = nullptr;
68
69 QgsStyle *dialogStyle = nullptr;
70 if ( mDialogMode == Import )
71 {
72 setWindowTitle( tr( "Import Item(s)" ) );
73 // populate the import types
74 importTypeCombo->addItem( tr( "File" ), ImportSource::File );
75 // importTypeCombo->addItem( "official QGIS repo online", ImportSource::Official );
76 importTypeCombo->addItem( tr( "URL" ), ImportSource::Url );
77 connect( importTypeCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsStyleExportImportDialog::importTypeChanged );
79
80 mSymbolTags->setText( QStringLiteral( "imported" ) );
81
82 connect( mButtonFetch, &QAbstractButton::clicked, this, &QgsStyleExportImportDialog::fetch );
83
84 mImportFileWidget->setStorageMode( QgsFileWidget::GetFile );
85 mImportFileWidget->setDialogTitle( tr( "Load Styles" ) );
86 mImportFileWidget->setFilter( tr( "XML files (*.xml *.XML)" ) );
87
88 const QgsSettings settings;
89 mImportFileWidget->setDefaultRoot( settings.value( QStringLiteral( "StyleManager/lastImportDir" ), QDir::homePath(), QgsSettings::Gui ).toString() );
90 connect( mImportFileWidget, &QgsFileWidget::fileChanged, this, &QgsStyleExportImportDialog::importFileChanged );
91
92 label->setText( tr( "Select items to import" ) );
93 buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Import" ) );
94
95 dialogStyle = mTempStyle.get();
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 dialogStyle = mStyle;
119 }
120
121 const double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 10;
122 listItems->setIconSize( QSize( static_cast<int>( iconSize ), static_cast<int>( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
123 // set a grid size which allows sufficient vertical spacing to fit reasonably sized entity names
124 listItems->setGridSize( QSize( static_cast<int>( listItems->iconSize().width() * 1.4 ), static_cast<int>( listItems->iconSize().height() * 1.7 ) ) );
125 listItems->setTextElideMode( Qt::TextElideMode::ElideRight );
126
127 mModel = new QgsStyleProxyModel( dialogStyle, this );
128
129 mModel->addDesiredIconSize( listItems->iconSize() );
130 mModel->addTargetScreenProperties( QgsScreenProperties( screen() ) );
131
132 listItems->setModel( mModel );
133
134 connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged, 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)" ), tr( "You should select at least one symbol/color ramp." ) );
150 return;
151 }
152
153 if ( mDialogMode == Export )
154 {
155 QgsSettings settings;
156 const QString lastUsedDir = settings.value( QStringLiteral( "StyleManager/lastExportDir" ), QDir::homePath(), QgsSettings::Gui ).toString();
157 QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), lastUsedDir, tr( "XML files (*.xml *.XML)" ) );
158 // return dialog focus on Mac
159 activateWindow();
160 raise();
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" ), tr( "Error when saving selected symbols to file:\n%1" ).arg( mTempStyle->errorString() ) );
181 return;
182 }
183 else
184 {
185 mCursorOverride.reset();
186 QMessageBox::information( this, tr( "Export Symbols" ), tr( "The selected symbols were successfully exported to file:\n%1" ).arg( mFileName ) );
187 }
188 }
189 else // import
190 {
191 mCursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
192 moveStyles( &selection, mTempStyle.get(), mStyle );
193
194 accept();
195 mCursorOverride.reset();
196 }
197
198 mFileName.clear();
199 mTempStyle->clear();
200}
201
202bool QgsStyleExportImportDialog::populateStyles()
203{
204 QgsTemporaryCursorOverride override( Qt::WaitCursor );
205
206 // load symbols and color ramps from file
207 // NOTE mTempStyle is style here
208 mTempStyle->clear();
209 if ( !mTempStyle->importXml( mFileName ) )
210 {
211 override.release();
212 QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ), tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
213 return false;
214 }
215 return true;
216}
217
218void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
219{
220 QList<QgsStyleManagerDialog::ItemDetails> items;
221 items.reserve( selection->size() );
222 for ( int i = 0; i < selection->size(); ++i )
223 {
224 const QModelIndex index = selection->at( i );
225
226 QgsStyleManagerDialog::ItemDetails details;
227 details.entityType = static_cast<QgsStyle::StyleEntity>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::Type ) ).toInt() );
228 if ( details.entityType == QgsStyle::SymbolEntity )
229 details.symbolType = static_cast<Qgis::SymbolType>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::SymbolType ) ).toInt() );
230 details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
231
232 items << details;
233 }
234 QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import, 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
259{
260 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
261 {
262 const QModelIndex index = listItems->model()->index( row, 0 );
263 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
264 {
265 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
266 }
267 }
268}
269
271{
272 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
273 {
274 const QModelIndex index = listItems->model()->index( row, 0 );
275 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
276 {
277 const QItemSelection deselection( index, index );
278 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
279 }
280 }
281}
282
283void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
284{
285 const auto constSymbolNames = symbolNames;
286 for ( const QString &symbolName : constSymbolNames )
287 {
288 const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
289 const auto constIndexes = indexes;
290 for ( const QModelIndex &index : constIndexes )
291 {
292 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
293 }
294 }
295}
296
297void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
298{
299 const auto constSymbolNames = symbolNames;
300 for ( const QString &symbolName : constSymbolNames )
301 {
302 const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
303 const auto constIndexes = indexes;
304 for ( const QModelIndex &index : constIndexes )
305 {
306 const QItemSelection deselection( index, index );
307 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
308 }
309 }
310}
311
312void QgsStyleExportImportDialog::selectTag( const QString &tagName )
313{
314 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
315 {
316 const QModelIndex index = listItems->model()->index( row, 0 );
317 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::Tag ) ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
318 {
319 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
320 }
321 }
322}
323
324void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
325{
326 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
327 {
328 const QModelIndex index = listItems->model()->index( row, 0 );
329 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::Tag ) ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
330 {
331 const QItemSelection deselection( index, index );
332 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
333 }
334 }
335}
336
337void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
338{
339 QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
340 selectSymbols( symbolNames );
341 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
342 selectSymbols( symbolNames );
343 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
344 selectSymbols( symbolNames );
345}
346
348{
349 QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
350 deselectSymbols( symbolNames );
351 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
352 deselectSymbols( symbolNames );
353 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
354 deselectSymbols( symbolNames );
355}
356
358{
359 if ( !mGroupSelectionDlg )
360 {
361 mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
362 mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
371 }
372 mGroupSelectionDlg->show();
373 mGroupSelectionDlg->raise();
374 mGroupSelectionDlg->activateWindow();
375}
376
378{
379 const ImportSource source = static_cast<ImportSource>( importTypeCombo->itemData( index ).toInt() );
380
381 switch ( source )
382 {
383 case ImportSource::File:
384 {
385 mLocationStackedEdit->setCurrentIndex( 0 );
386 mLocationLabel->setText( tr( "File" ) );
387 break;
388 }
389#if 0
390 case ImportSource::Official:
391 {
392 btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
393 locationLineEdit->setEnabled( false );
394 break;
395 }
396#endif
397 case ImportSource::Url:
398 {
399 mLocationStackedEdit->setCurrentIndex( 1 );
400 mLocationLabel->setText( tr( "URL" ) );
401 break;
402 }
403 }
404}
405
406void QgsStyleExportImportDialog::fetch()
407{
408 downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
409}
410
411void QgsStyleExportImportDialog::importFileChanged( const QString &path )
412{
413 if ( path.isEmpty() )
414 return;
415
416 mFileName = path;
417 const QFileInfo pathInfo( mFileName );
418 const QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
419 mSymbolTags->setText( tag );
420 if ( QFileInfo::exists( mFileName ) )
421 {
422 mTempStyle->clear();
423 populateStyles();
424 mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
425 QgsSettings settings;
426 settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
427 }
428}
429
430void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
431{
432 mTempFile = new QTemporaryFile();
433 if ( mTempFile->open() )
434 {
435 mFileName = mTempFile->fileName();
436
437 QProgressDialog *progressDlg = new QProgressDialog( this );
438 progressDlg->setLabelText( tr( "Downloading style…" ) );
439 progressDlg->setAutoClose( true );
440 progressDlg->show();
441
442 QgsNetworkContentFetcherTask *fetcher = new QgsNetworkContentFetcherTask( url );
443 fetcher->setDescription( tr( "Downloading style" ) );
444 connect( progressDlg, &QProgressDialog::canceled, fetcher, &QgsNetworkContentFetcherTask::cancel );
445 connect( fetcher, &QgsNetworkContentFetcherTask::progressChanged, progressDlg, &QProgressDialog::setValue );
446 connect( fetcher, &QgsNetworkContentFetcherTask::fetched, this, [this, fetcher, progressDlg] {
447 QNetworkReply *reply = fetcher->reply();
448 if ( !reply || reply->error() != QNetworkReply::NoError )
449 {
450 mTempFile->remove();
451 mFileName.clear();
452 if ( reply )
453 QMessageBox::information( this, tr( "Import from URL" ), tr( "HTTP Error! Download failed: %1." ).arg( reply->errorString() ) );
454 }
455 else
456 {
457 mTempFile->write( reply->readAll() );
458 mTempFile->flush();
459 mTempFile->close();
460 populateStyles();
461 }
462 progressDlg->deleteLater();
463 } );
464
466 }
467}
468
469void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
470{
471 Q_UNUSED( selected )
472 Q_UNUSED( deselected )
473 const bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
474 buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
475}
476
477void QgsStyleExportImportDialog::showHelp()
478{
479 QgsHelp::openHelp( QStringLiteral( "style_library/style_manager.html#sharing-style-items" ) );
480}
SymbolType
Symbol types.
Definition qgis.h:610
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6222
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:221
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:38
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.
Stores settings for use within QGIS.
Definition qgssettings.h:65
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.
A dialog which presents available groups from a QgsStyle.
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 ...
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:88
StyleEntity
Enum for Entities involved in a style.
Definition qgsstyle.h:204
@ TextFormatEntity
Text formats.
Definition qgsstyle.h:209
@ SymbolEntity
Symbols.
Definition qgsstyle.h:205
@ ColorrampEntity
Color ramps.
Definition qgsstyle.h:207
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.