QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsvectortilelayerproperties.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectortilelayerproperties.cpp
3 --------------------------------------
4 Date : May 2020
5 Copyright : (C) 2020 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgsfileutils.h"
19#include "qgshelp.h"
24#include "qgsvectortilelayer.h"
25#include "qgsgui.h"
26#include "qgsnative.h"
27#include "qgsapplication.h"
28#include "qgsmetadatawidget.h"
31#include <QFileDialog>
32#include <QMenu>
33#include <QMessageBox>
34#include <QDesktopServices>
35#include <QTextStream>
36
37QgsVectorTileLayerProperties::QgsVectorTileLayerProperties( QgsVectorTileLayer *lyr, QgsMapCanvas *canvas, QgsMessageBar *messageBar, QWidget *parent, Qt::WindowFlags flags )
38 : QgsOptionsDialogBase( QStringLiteral( "VectorTileLayerProperties" ), parent, flags )
39 , mLayer( lyr )
40 , mMapCanvas( canvas )
41{
42 setupUi( this );
43
44 mRendererWidget = new QgsVectorTileBasicRendererWidget( nullptr, canvas, messageBar, this );
45 mOptsPage_Style->layout()->addWidget( mRendererWidget );
46 mOptsPage_Style->layout()->setContentsMargins( 0, 0, 0, 0 );
47
48 mLabelingWidget = new QgsVectorTileBasicLabelingWidget( nullptr, canvas, messageBar, this );
49 mOptsPage_Labeling->layout()->addWidget( mLabelingWidget );
50 mOptsPage_Labeling->layout()->setContentsMargins( 0, 0, 0, 0 );
51
52 connect( this, &QDialog::accepted, this, &QgsVectorTileLayerProperties::apply );
53 connect( this, &QDialog::rejected, this, &QgsVectorTileLayerProperties::onCancel );
54 connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsVectorTileLayerProperties::apply );
55 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsVectorTileLayerProperties::showHelp );
56
57 // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states,
58 // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left),
59 // and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots
60 initOptionsBase( false );
61
62#ifdef WITH_QTWEBKIT
63 // Setup information tab
64
65 const int horizontalDpi = logicalDpiX();
66
67 // Adjust zoom: text is ok, but HTML seems rather big at least on Linux/KDE
68 if ( horizontalDpi > 96 )
69 {
70 mMetadataViewer->setZoomFactor( mMetadataViewer->zoomFactor() * 0.9 );
71 }
72 mMetadataViewer->page()->setLinkDelegationPolicy( QWebPage::LinkDelegationPolicy::DelegateAllLinks );
73 connect( mMetadataViewer->page(), &QWebPage::linkClicked, this, &QgsVectorTileLayerProperties::urlClicked );
74 mMetadataViewer->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
75 mMetadataViewer->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
76
77#endif
78 mOptsPage_Information->setContentsMargins( 0, 0, 0, 0 );
79
80 QVBoxLayout *layout = new QVBoxLayout( metadataFrame );
81 layout->setContentsMargins( 0, 0, 0, 0 );
82 metadataFrame->setContentsMargins( 0, 0, 0, 0 );
83 mMetadataWidget = new QgsMetadataWidget( this, mLayer );
84 mMetadataWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
85 mMetadataWidget->setMapCanvas( mMapCanvas );
86 layout->addWidget( mMetadataWidget );
87 metadataFrame->setLayout( layout );
88 mOptsPage_Metadata->setContentsMargins( 0, 0, 0, 0 );
89
90 // update based on lyr's current state
91 syncToLayer();
92
93 QgsSettings settings;
94 // if dialog hasn't been opened/closed yet, default to Styles tab, which is used most often
95 // this will be read by restoreOptionsBaseUi()
96 if ( !settings.contains( QStringLiteral( "/Windows/VectorTileLayerProperties/tab" ) ) )
97 {
98 settings.setValue( QStringLiteral( "Windows/VectorTileLayerProperties/tab" ),
99 mOptStackedWidget->indexOf( mOptsPage_Style ) );
100 }
101
102 QString title = tr( "Layer Properties - %1" ).arg( mLayer->name() );
103
104 mBtnStyle = new QPushButton( tr( "Style" ) );
105 QMenu *menuStyle = new QMenu( this );
106 menuStyle->addAction( tr( "Load Style…" ), this, &QgsVectorTileLayerProperties::loadStyle );
107 menuStyle->addAction( tr( "Save Style…" ), this, &QgsVectorTileLayerProperties::saveStyleAs );
108 menuStyle->addSeparator();
109 menuStyle->addAction( tr( "Save as Default" ), this, &QgsVectorTileLayerProperties::saveDefaultStyle );
110 menuStyle->addAction( tr( "Restore Default" ), this, &QgsVectorTileLayerProperties::loadDefaultStyle );
111 mBtnStyle->setMenu( menuStyle );
112 connect( menuStyle, &QMenu::aboutToShow, this, &QgsVectorTileLayerProperties::aboutToShowStyleMenu );
113
114 buttonBox->addButton( mBtnStyle, QDialogButtonBox::ResetRole );
115
116 mBtnMetadata = new QPushButton( tr( "Metadata" ), this );
117 QMenu *menuMetadata = new QMenu( this );
118 mActionLoadMetadata = menuMetadata->addAction( tr( "Load Metadata…" ), this, &QgsVectorTileLayerProperties::loadMetadata );
119 mActionSaveMetadataAs = menuMetadata->addAction( tr( "Save Metadata…" ), this, &QgsVectorTileLayerProperties::saveMetadataAs );
120 mBtnMetadata->setMenu( menuMetadata );
121 buttonBox->addButton( mBtnMetadata, QDialogButtonBox::ResetRole );
122
123 if ( !mLayer->styleManager()->isDefault( mLayer->styleManager()->currentStyle() ) )
124 title += QStringLiteral( " (%1)" ).arg( mLayer->styleManager()->currentStyle() );
125 restoreOptionsBaseUi( title );
126}
127
128void QgsVectorTileLayerProperties::apply()
129{
130 mRendererWidget->apply();
131 mLabelingWidget->apply();
132 mMetadataWidget->acceptMetadata();
133}
134
135void QgsVectorTileLayerProperties::onCancel()
136{
137 if ( mOldStyle.xmlData() != mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() ).xmlData() )
138 {
139 // need to reset style to previous - style applied directly to the layer (not in apply())
140 QString myMessage;
141 QDomDocument doc( QStringLiteral( "qgis" ) );
142 int errorLine, errorColumn;
143 doc.setContent( mOldStyle.xmlData(), false, &myMessage, &errorLine, &errorColumn );
144 mLayer->importNamedStyle( doc, myMessage );
145 syncToLayer();
146 }
147}
148
149void QgsVectorTileLayerProperties::syncToLayer()
150{
151 /*
152 * Information Tab
153 */
154 const QString myStyle = QgsApplication::reportStyleSheet( QgsApplication::StyleSheetType::WebBrowser );
155 // Inject the stylesheet
156 const QString html { mLayer->htmlMetadata().replace( QLatin1String( "<head>" ), QStringLiteral( R"raw(<head><style type="text/css">%1</style>)raw" ) ).arg( myStyle ) };
157 mMetadataViewer->setHtml( html );
158
159 /*
160 * Symbology Tab
161 */
162 mRendererWidget->setLayer( mLayer );
163
164 /*
165 * Labels Tab
166 */
167 mLabelingWidget->setLayer( mLayer );
168}
169
170
171void QgsVectorTileLayerProperties::loadDefaultStyle()
172{
173 bool defaultLoadedFlag = false;
174 const QString myMessage = mLayer->loadDefaultStyle( defaultLoadedFlag );
175 // reset if the default style was loaded OK only
176 if ( defaultLoadedFlag )
177 {
178 syncToLayer();
179 }
180 else
181 {
182 // otherwise let the user know what went wrong
183 QMessageBox::information( this,
184 tr( "Default Style" ),
185 myMessage
186 );
187 }
188}
189
190void QgsVectorTileLayerProperties::saveDefaultStyle()
191{
192 apply(); // make sure the style to save is up-to-date
193
194 // a flag passed by reference
195 bool defaultSavedFlag = false;
196 // TODO Once the deprecated `saveDefaultStyle()` method is gone, just
197 // remove the NOWARN_DEPRECATED tags
199 // after calling this the above flag will be set true for success
200 // or false if the save operation failed
201 const QString myMessage = mLayer->saveDefaultStyle( defaultSavedFlag );
203 if ( !defaultSavedFlag )
204 {
205 // let the user know what went wrong
206 QMessageBox::information( this,
207 tr( "Default Style" ),
208 myMessage
209 );
210 }
211}
212
213void QgsVectorTileLayerProperties::loadStyle()
214{
215 const QgsSettings settings; // where we keep last used filter in persistent state
216
217 QgsMapLayerLoadStyleDialog dlg( mLayer );
218
219 if ( dlg.exec() )
220 {
221 mOldStyle = mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() );
222 const QgsMapLayer::StyleCategories categories = dlg.styleCategories();
223 const QString type = dlg.fileExtension();
224 if ( type.compare( QLatin1String( "qml" ), Qt::CaseInsensitive ) == 0 )
225 {
226 QString message;
227 bool defaultLoadedFlag = false;
228 const QString filePath = dlg.filePath();
229 message = mLayer->loadNamedStyle( filePath, defaultLoadedFlag, categories );
230
231 //reset if the default style was loaded OK only
232 if ( defaultLoadedFlag )
233 {
234 syncToLayer();
235 }
236 else
237 {
238 //let the user know what went wrong
239 QMessageBox::warning( this, tr( "Load Style" ), message );
240 }
241 }
242 else if ( type.compare( QLatin1String( "json" ), Qt::CaseInsensitive ) == 0 )
243 {
244 QFile file( dlg.filePath() );
245 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
246 {
247 QMessageBox::warning( this, tr( "Load Style" ), tr( "Could not read %1" ).arg( QDir::toNativeSeparators( dlg.filePath() ) ) );
248 }
249 else
250 {
251 QTextStream in( &file );
252#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
253 in.setCodec( "UTF-8" );
254#endif
255 const QString content = in.readAll();
256
258 // convert automatically from pixel sizes to millimeters, because pixel sizes
259 // are a VERY edge case in QGIS and don't play nice with hidpi map renders or print layouts
260 context.setTargetUnit( Qgis::RenderUnit::Millimeters );
261 //assume source uses 96 dpi
262 context.setPixelSizeConversionFactor( 25.4 / 96.0 );
263
265
266 if ( converter.convert( content, &context ) != QgsMapBoxGlStyleConverter::Success )
267 {
268 QMessageBox::warning( this, tr( "Load Style" ), converter.errorMessage() );
269 }
270 else
271 {
272 if ( dlg.styleCategories().testFlag( QgsMapLayer::StyleCategory::Symbology ) )
273 {
274 mLayer->setRenderer( converter.renderer() );
275 }
276 if ( dlg.styleCategories().testFlag( QgsMapLayer::StyleCategory::Labeling ) )
277 {
278 mLayer->setLabeling( converter.labeling() );
279 }
280 syncToLayer();
281 }
282 }
283 }
284 activateWindow(); // set focus back to properties dialog
285 }
286}
287
288void QgsVectorTileLayerProperties::saveStyleAs()
289{
290 QgsSettings settings;
291 const QString lastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
292
293 QString outputFileName = QFileDialog::getSaveFileName(
294 this,
295 tr( "Save layer properties as style file" ),
296 lastUsedDir,
297 tr( "QGIS Layer Style File" ) + " (*.qml)" );
298 if ( outputFileName.isEmpty() )
299 return;
300
301 // ensure the user never omits the extension from the file name
302 outputFileName = QgsFileUtils::ensureFileNameHasExtension( outputFileName, QStringList() << QStringLiteral( "qml" ) );
303
304 apply(); // make sure the style to save is up-to-date
305
306 // then export style
307 bool defaultLoadedFlag = false;
308 QString message;
309 message = mLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
310
311 if ( defaultLoadedFlag )
312 {
313 settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( outputFileName ).absolutePath() );
314 }
315 else
316 QMessageBox::information( this, tr( "Save Style" ), message );
317}
318
319void QgsVectorTileLayerProperties::aboutToShowStyleMenu()
320{
321 QMenu *m = qobject_cast<QMenu *>( sender() );
322
324 // re-add style manager actions!
325 m->addSeparator();
327}
328
329void QgsVectorTileLayerProperties::loadMetadata()
330{
331 QgsSettings myQSettings; // where we keep last used filter in persistent state
332 const QString myLastUsedDir = myQSettings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
333
334 const QString myFileName = QFileDialog::getOpenFileName( this, tr( "Load layer metadata from metadata file" ), myLastUsedDir,
335 tr( "QGIS Layer Metadata File" ) + " (*.qmd)" );
336 if ( myFileName.isNull() )
337 {
338 return;
339 }
340
341 QString myMessage;
342 bool defaultLoadedFlag = false;
343 myMessage = mLayer->loadNamedMetadata( myFileName, defaultLoadedFlag );
344
345 //reset if the default style was loaded OK only
346 if ( defaultLoadedFlag )
347 {
348 mMetadataWidget->setMetadata( &mLayer->metadata() );
349 }
350 else
351 {
352 //let the user know what went wrong
353 QMessageBox::warning( this, tr( "Load Metadata" ), myMessage );
354 }
355
356 const QFileInfo myFI( myFileName );
357 const QString myPath = myFI.path();
358 myQSettings.setValue( QStringLiteral( "style/lastStyleDir" ), myPath );
359
360 activateWindow(); // set focus back to properties dialog
361}
362
363void QgsVectorTileLayerProperties::saveMetadataAs()
364{
365 QgsSettings myQSettings; // where we keep last used filter in persistent state
366 const QString myLastUsedDir = myQSettings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
367
368 QString myOutputFileName = QFileDialog::getSaveFileName( this, tr( "Save Layer Metadata as QMD" ),
369 myLastUsedDir, tr( "QMD File" ) + " (*.qmd)" );
370 if ( myOutputFileName.isNull() ) //dialog canceled
371 {
372 return;
373 }
374
375 mMetadataWidget->acceptMetadata();
376
377 //ensure the user never omitted the extension from the file name
378 if ( !myOutputFileName.endsWith( QgsMapLayer::extensionPropertyType( QgsMapLayer::Metadata ), Qt::CaseInsensitive ) )
379 {
381 }
382
383 bool defaultLoadedFlag = false;
384 const QString message = mLayer->saveNamedMetadata( myOutputFileName, defaultLoadedFlag );
385 if ( defaultLoadedFlag )
386 myQSettings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( myOutputFileName ).absolutePath() );
387 else
388 QMessageBox::information( this, tr( "Save Metadata" ), message );
389}
390
391void QgsVectorTileLayerProperties::showHelp()
392{
393 const QVariant helpPage = mOptionsStackedWidget->currentWidget()->property( "helpPage" );
394
395 if ( helpPage.isValid() )
396 {
397 QgsHelp::openHelp( helpPage.toString() );
398 }
399 else
400 {
401 QgsHelp::openHelp( QStringLiteral( "working_with_vector_tiles/vector_tiles_properties.html" ) );
402 }
403}
404
405void QgsVectorTileLayerProperties::urlClicked( const QUrl &url )
406{
407 const QFileInfo file( url.toLocalFile() );
408 if ( file.exists() && !file.isDir() )
409 QgsGui::nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
410 else
411 QDesktopServices::openUrl( url );
412}
413
415{
417
418 const bool isMetadataPanel = ( index == mOptStackedWidget->indexOf( mOptsPage_Metadata ) );
419 mBtnStyle->setVisible( ! isMetadataPanel );
420 mBtnMetadata->setVisible( isMetadataPanel );
421}
static QString reportStyleSheet(QgsApplication::StyleSheetType styleSheetType=QgsApplication::StyleSheetType::Qt)
Returns a css style sheet for reports, the styleSheetType argument determines what type of stylesheet...
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static QgsNative * nativePlatformInterface()
Returns the global native interface, which offers abstraction to the host OS's underlying public inte...
Definition: qgsgui.cpp:73
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:38
Context for a MapBox GL style conversion operation.
void setTargetUnit(Qgis::RenderUnit targetUnit)
Sets the target unit type.
void setPixelSizeConversionFactor(double sizeConversionFactor)
Sets the pixel size conversion factor, used to scale the original pixel sizes when converting styles.
Handles conversion of MapBox GL styles to QGIS vector tile renderers and labeling settings.
QgsVectorTileRenderer * renderer() const
Returns a new instance of a vector tile renderer representing the converted style,...
QgsVectorTileLabeling * labeling() const
Returns a new instance of a vector tile labeling representing the converted style,...
Result convert(const QVariantMap &style, QgsMapBoxGlStyleConversionContext *context=nullptr)
Converts a JSON style map, and returns the resultant status of the conversion.
@ Success
Conversion was successful.
QString errorMessage() const
Returns a descriptive error message if an error was encountered during the style conversion,...
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
A reusable dialog which allows users to select stored layer styles and categories to load for a map l...
void removesExtraMenuSeparators(QMenu *m)
removes extra separators from the menu
void addStyleManagerActions(QMenu *m, QgsMapLayer *layer)
adds actions to the menu in accordance to the layer
static QgsMapLayerStyleGuiUtils * instance()
returns a singleton instance of this class
QString currentStyle() const
Returns name of the current style.
static bool isDefault(const QString &styleName)
Returns true if this is the default style.
QgsMapLayerStyle style(const QString &name) const
Returns data of a stored style - accessed by its unique name.
QString xmlData() const
Returns XML content of the style.
QString name
Definition: qgsmaplayer.h:76
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg, QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories)
Import the properties of this layer from a QDomDocument.
virtual QString loadNamedMetadata(const QString &uri, bool &resultFlag)
Retrieve a named metadata for this layer if one exists (either as a .qmd file on disk or as a record ...
QgsLayerMetadata metadata
Definition: qgsmaplayer.h:78
virtual QString loadNamedStyle(const QString &uri, bool &resultFlag, QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories)
Retrieve a named style for this layer if one exists (either as a .qml file on disk or as a record in ...
static QString extensionPropertyType(PropertyType type)
Returns the extension of a Property.
Definition: qgsmaplayer.cpp:69
QString saveNamedMetadata(const QString &uri, bool &resultFlag)
Save the current metadata of this layer as a named metadata (either as a .qmd file on disk or as a re...
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
virtual QString saveNamedStyle(const QString &uri, bool &resultFlag, StyleCategories categories=AllStyleCategories)
Save the properties of this layer as a named style (either as a .qml file on disk or as a record in t...
virtual QString saveDefaultStyle(bool &resultFlag, StyleCategories categories)
Save the properties of this layer as the default style (either as a .qml file on disk or as a record ...
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
A wizard to edit metadata on a map layer.
void acceptMetadata()
Saves the metadata to the layer.
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas associated with the widget.
void setMetadata(const QgsAbstractMetadataBase *metadata)
Sets the metadata to display in the widget.
A base dialog for options and properties dialogs that offers vertical tabs.
virtual void optionsStackedWidget_CurrentChanged(int index)
Select relevant tab on current page change.
void restoreOptionsBaseUi(const QString &title=QString())
Restore the base ui.
QStackedWidget * mOptStackedWidget
void initOptionsBase(bool restoreUi=true, const QString &title=QString())
Set up the base ui connections for vertical tabs.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
bool contains(const QString &key, QgsSettings::Section section=QgsSettings::NoSection) const
Returns true if there exists a setting called key; returns false otherwise.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void optionsStackedWidget_CurrentChanged(int index) override
QgsVectorTileLayerProperties(QgsVectorTileLayer *lyr, QgsMapCanvas *canvas, QgsMessageBar *messageBar, QWidget *parent=nullptr, Qt::WindowFlags=QgsGuiUtils::ModalDialogFlags)
Constructor.
Implements a map layer that is dedicated to rendering of vector tiles.
void setRenderer(QgsVectorTileRenderer *r)
Sets renderer for the map layer.
void setLabeling(QgsVectorTileLabeling *labeling)
Sets labeling for the map layer.
QString loadDefaultStyle(bool &resultFlag) override
Retrieve the default style for this layer if one exists (either as a .qml file on disk or as a record...
QString htmlMetadata() const override
Obtain a formatted HTML string containing assorted metadata for this layer.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:4093
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:4092