QGIS API Documentation 3.41.0-Master (af5edcb665c)
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, this, &QgsStyleExportImportDialog::selectionChanged );
134
135 // use Ok button for starting import and export operations
136 disconnect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
137 connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStyleExportImportDialog::doExportImport );
138 buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
139
140 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleExportImportDialog::showHelp );
141}
142
144{
145 QModelIndexList selection = listItems->selectionModel()->selectedIndexes();
146 if ( selection.isEmpty() )
147 {
148 QMessageBox::warning( this, tr( "Export/import Item(s)" ), tr( "You should select at least one symbol/color ramp." ) );
149 return;
150 }
151
152 if ( mDialogMode == Export )
153 {
154 QgsSettings settings;
155 const QString lastUsedDir = settings.value( QStringLiteral( "StyleManager/lastExportDir" ), QDir::homePath(), QgsSettings::Gui ).toString();
156 QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Styles" ), lastUsedDir, tr( "XML files (*.xml *.XML)" ) );
157 // return dialog focus on Mac
158 activateWindow();
159 raise();
160 if ( fileName.isEmpty() )
161 {
162 return;
163 }
164 settings.setValue( QStringLiteral( "StyleManager/lastExportDir" ), QFileInfo( fileName ).absolutePath(), QgsSettings::Gui );
165
166 // ensure the user never omitted the extension from the file name
167 if ( !fileName.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) )
168 {
169 fileName += QLatin1String( ".xml" );
170 }
171
172 mFileName = fileName;
173
174 mCursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
175 moveStyles( &selection, mStyle, mTempStyle.get() );
176 if ( !mTempStyle->exportXml( mFileName ) )
177 {
178 mCursorOverride.reset();
179 QMessageBox::warning( this, tr( "Export Symbols" ), tr( "Error when saving selected symbols to file:\n%1" ).arg( mTempStyle->errorString() ) );
180 return;
181 }
182 else
183 {
184 mCursorOverride.reset();
185 QMessageBox::information( this, tr( "Export Symbols" ), tr( "The selected symbols were successfully exported to file:\n%1" ).arg( mFileName ) );
186 }
187 }
188 else // import
189 {
190 mCursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
191 moveStyles( &selection, mTempStyle.get(), mStyle );
192
193 accept();
194 mCursorOverride.reset();
195 }
196
197 mFileName.clear();
198 mTempStyle->clear();
199}
200
201bool QgsStyleExportImportDialog::populateStyles()
202{
203 QgsTemporaryCursorOverride override( Qt::WaitCursor );
204
205 // load symbols and color ramps from file
206 // NOTE mTempStyle is style here
207 mTempStyle->clear();
208 if ( !mTempStyle->importXml( mFileName ) )
209 {
210 override.release();
211 QMessageBox::warning( this, tr( "Import Symbols or Color Ramps" ), tr( "An error occurred during import:\n%1" ).arg( mTempStyle->errorString() ) );
212 return false;
213 }
214 return true;
215}
216
217void QgsStyleExportImportDialog::moveStyles( QModelIndexList *selection, QgsStyle *src, QgsStyle *dst )
218{
219 QList<QgsStyleManagerDialog::ItemDetails> items;
220 items.reserve( selection->size() );
221 for ( int i = 0; i < selection->size(); ++i )
222 {
223 const QModelIndex index = selection->at( i );
224
225 QgsStyleManagerDialog::ItemDetails details;
226 details.entityType = static_cast<QgsStyle::StyleEntity>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::Type ) ).toInt() );
227 if ( details.entityType == QgsStyle::SymbolEntity )
228 details.symbolType = static_cast<Qgis::SymbolType>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::SymbolType ) ).toInt() );
229 details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
230
231 items << details;
232 }
233 QgsStyleManagerDialog::copyItems( items, src, dst, this, mCursorOverride, mDialogMode == Import, mSymbolTags->text().split( ',' ), mFavorite->isChecked(), mIgnoreXMLTags->isChecked() );
234}
235
237{
238 delete mTempFile;
239 delete mGroupSelectionDlg;
240}
241
243{
244 mImportFileWidget->setFilePath( path );
245}
246
248{
249 listItems->selectAll();
250}
251
253{
254 listItems->clearSelection();
255}
256
258{
259 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
260 {
261 const QModelIndex index = listItems->model()->index( row, 0 );
262 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
263 {
264 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
265 }
266 }
267}
268
270{
271 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
272 {
273 const QModelIndex index = listItems->model()->index( row, 0 );
274 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
275 {
276 const QItemSelection deselection( index, index );
277 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
278 }
279 }
280}
281
282void QgsStyleExportImportDialog::selectSymbols( const QStringList &symbolNames )
283{
284 const auto constSymbolNames = symbolNames;
285 for ( const QString &symbolName : constSymbolNames )
286 {
287 const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
288 const auto constIndexes = indexes;
289 for ( const QModelIndex &index : constIndexes )
290 {
291 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
292 }
293 }
294}
295
296void QgsStyleExportImportDialog::deselectSymbols( const QStringList &symbolNames )
297{
298 const auto constSymbolNames = symbolNames;
299 for ( const QString &symbolName : constSymbolNames )
300 {
301 const QModelIndexList indexes = listItems->model()->match( listItems->model()->index( 0, QgsStyleModel::Name ), Qt::DisplayRole, symbolName, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive );
302 const auto constIndexes = indexes;
303 for ( const QModelIndex &index : constIndexes )
304 {
305 const QItemSelection deselection( index, index );
306 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
307 }
308 }
309}
310
311void QgsStyleExportImportDialog::selectTag( const QString &tagName )
312{
313 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
314 {
315 const QModelIndex index = listItems->model()->index( row, 0 );
316 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::Tag ) ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
317 {
318 listItems->selectionModel()->select( index, QItemSelectionModel::Select );
319 }
320 }
321}
322
323void QgsStyleExportImportDialog::deselectTag( const QString &tagName )
324{
325 for ( int row = 0; row < listItems->model()->rowCount(); ++row )
326 {
327 const QModelIndex index = listItems->model()->index( row, 0 );
328 if ( index.data( static_cast<int>( QgsStyleModel::CustomRole::Tag ) ).toStringList().contains( tagName, Qt::CaseInsensitive ) )
329 {
330 const QItemSelection deselection( index, index );
331 listItems->selectionModel()->select( deselection, QItemSelectionModel::Deselect );
332 }
333 }
334}
335
336void QgsStyleExportImportDialog::selectSmartgroup( const QString &groupName )
337{
338 QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
339 selectSymbols( symbolNames );
340 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
341 selectSymbols( symbolNames );
342 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
343 selectSymbols( symbolNames );
344}
345
347{
348 QStringList symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mStyle->smartgroupId( groupName ) );
349 deselectSymbols( symbolNames );
350 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mStyle->smartgroupId( groupName ) );
351 deselectSymbols( symbolNames );
352 symbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mStyle->smartgroupId( groupName ) );
353 deselectSymbols( symbolNames );
354}
355
357{
358 if ( !mGroupSelectionDlg )
359 {
360 mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this );
361 mGroupSelectionDlg->setWindowTitle( tr( "Select Item(s) by Group" ) );
370 }
371 mGroupSelectionDlg->show();
372 mGroupSelectionDlg->raise();
373 mGroupSelectionDlg->activateWindow();
374}
375
377{
378 const ImportSource source = static_cast<ImportSource>( importTypeCombo->itemData( index ).toInt() );
379
380 switch ( source )
381 {
382 case ImportSource::File:
383 {
384 mLocationStackedEdit->setCurrentIndex( 0 );
385 mLocationLabel->setText( tr( "File" ) );
386 break;
387 }
388#if 0
389 case ImportSource::Official:
390 {
391 btnBrowse->setText( QStringLiteral( "Fetch Items" ) );
392 locationLineEdit->setEnabled( false );
393 break;
394 }
395#endif
396 case ImportSource::Url:
397 {
398 mLocationStackedEdit->setCurrentIndex( 1 );
399 mLocationLabel->setText( tr( "URL" ) );
400 break;
401 }
402 }
403}
404
405void QgsStyleExportImportDialog::fetch()
406{
407 downloadStyleXml( QUrl( mUrlLineEdit->text() ) );
408}
409
410void QgsStyleExportImportDialog::importFileChanged( const QString &path )
411{
412 if ( path.isEmpty() )
413 return;
414
415 mFileName = path;
416 const QFileInfo pathInfo( mFileName );
417 const QString tag = pathInfo.fileName().remove( QStringLiteral( ".xml" ) );
418 mSymbolTags->setText( tag );
419 if ( QFileInfo::exists( mFileName ) )
420 {
421 mTempStyle->clear();
422 populateStyles();
423 mImportFileWidget->setDefaultRoot( pathInfo.absolutePath() );
424 QgsSettings settings;
425 settings.setValue( QStringLiteral( "StyleManager/lastImportDir" ), pathInfo.absolutePath(), QgsSettings::Gui );
426 }
427}
428
429void QgsStyleExportImportDialog::downloadStyleXml( const QUrl &url )
430{
431 mTempFile = new QTemporaryFile();
432 if ( mTempFile->open() )
433 {
434 mFileName = mTempFile->fileName();
435
436 QProgressDialog *progressDlg = new QProgressDialog( this );
437 progressDlg->setLabelText( tr( "Downloading style…" ) );
438 progressDlg->setAutoClose( true );
439 progressDlg->show();
440
442 fetcher->setDescription( tr( "Downloading style" ) );
443 connect( progressDlg, &QProgressDialog::canceled, fetcher, &QgsNetworkContentFetcherTask::cancel );
444 connect( fetcher, &QgsNetworkContentFetcherTask::progressChanged, progressDlg, &QProgressDialog::setValue );
445 connect( fetcher, &QgsNetworkContentFetcherTask::fetched, this, [this, fetcher, progressDlg] {
446 QNetworkReply *reply = fetcher->reply();
447 if ( !reply || reply->error() != QNetworkReply::NoError )
448 {
449 mTempFile->remove();
450 mFileName.clear();
451 if ( reply )
452 QMessageBox::information( this, tr( "Import from URL" ), tr( "HTTP Error! Download failed: %1." ).arg( reply->errorString() ) );
453 }
454 else
455 {
456 mTempFile->write( reply->readAll() );
457 mTempFile->flush();
458 mTempFile->close();
459 populateStyles();
460 }
461 progressDlg->deleteLater();
462 } );
463
465 }
466}
467
468void QgsStyleExportImportDialog::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
469{
470 Q_UNUSED( selected )
471 Q_UNUSED( deselected )
472 const bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
473 buttonBox->button( QDialogButtonBox::Ok )->setDisabled( nothingSelected );
474}
475
476void QgsStyleExportImportDialog::showHelp()
477{
478 QgsHelp::openHelp( QStringLiteral( "style_library/style_manager.html#sharing-style-items" ) );
479}
SymbolType
Symbol types.
Definition qgis.h:574
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5775
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:210
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.