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