QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 QStringList ids, names, descriptions;
218
219 QgsMapLayerLoadStyleDialog dlg( mLayer );
220
221 if ( dlg.exec() )
222 {
223 mOldStyle = mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() );
224 const QgsMapLayer::StyleCategories categories = dlg.styleCategories();
225 const QString type = dlg.fileExtension();
226 if ( type.compare( QLatin1String( "qml" ), Qt::CaseInsensitive ) == 0 )
227 {
228 QString message;
229 bool defaultLoadedFlag = false;
230 const QString filePath = dlg.filePath();
231 message = mLayer->loadNamedStyle( filePath, defaultLoadedFlag, categories );
232
233 //reset if the default style was loaded OK only
234 if ( defaultLoadedFlag )
235 {
236 syncToLayer();
237 }
238 else
239 {
240 //let the user know what went wrong
241 QMessageBox::warning( this, tr( "Load Style" ), message );
242 }
243 }
244 else if ( type.compare( QLatin1String( "json" ), Qt::CaseInsensitive ) == 0 )
245 {
246 QFile file( dlg.filePath() );
247 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
248 {
249 QMessageBox::warning( this, tr( "Load Style" ), tr( "Could not read %1" ).arg( QDir::toNativeSeparators( dlg.filePath() ) ) );
250 }
251 else
252 {
253 QTextStream in( &file );
254 const QString content = in.readAll();
255
257 // convert automatically from pixel sizes to millimeters, because pixel sizes
258 // are a VERY edge case in QGIS and don't play nice with hidpi map renders or print layouts
260 //assume source uses 96 dpi
261 context.setPixelSizeConversionFactor( 25.4 / 96.0 );
262
264
265 if ( converter.convert( content, &context ) != QgsMapBoxGlStyleConverter::Success )
266 {
267 QMessageBox::warning( this, tr( "Load Style" ), converter.errorMessage() );
268 }
269 else
270 {
271 if ( dlg.styleCategories().testFlag( QgsMapLayer::StyleCategory::Symbology ) )
272 {
273 mLayer->setRenderer( converter.renderer() );
274 }
275 if ( dlg.styleCategories().testFlag( QgsMapLayer::StyleCategory::Labeling ) )
276 {
277 mLayer->setLabeling( converter.labeling() );
278 }
279 syncToLayer();
280 }
281 }
282 }
283 activateWindow(); // set focus back to properties dialog
284 }
285}
286
287void QgsVectorTileLayerProperties::saveStyleAs()
288{
289 QgsSettings settings;
290 const QString lastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
291
292 QString outputFileName = QFileDialog::getSaveFileName(
293 this,
294 tr( "Save layer properties as style file" ),
295 lastUsedDir,
296 tr( "QGIS Layer Style File" ) + " (*.qml)" );
297 if ( outputFileName.isEmpty() )
298 return;
299
300 // ensure the user never omits the extension from the file name
301 outputFileName = QgsFileUtils::ensureFileNameHasExtension( outputFileName, QStringList() << QStringLiteral( "qml" ) );
302
303 apply(); // make sure the style to save is up-to-date
304
305 // then export style
306 bool defaultLoadedFlag = false;
307 QString message;
308 message = mLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
309
310 if ( defaultLoadedFlag )
311 {
312 settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( outputFileName ).absolutePath() );
313 }
314 else
315 QMessageBox::information( this, tr( "Save Style" ), message );
316}
317
318void QgsVectorTileLayerProperties::aboutToShowStyleMenu()
319{
320 QMenu *m = qobject_cast<QMenu *>( sender() );
321
323 // re-add style manager actions!
324 m->addSeparator();
326}
327
328void QgsVectorTileLayerProperties::loadMetadata()
329{
330 QgsSettings myQSettings; // where we keep last used filter in persistent state
331 const QString myLastUsedDir = myQSettings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
332
333 const QString myFileName = QFileDialog::getOpenFileName( this, tr( "Load layer metadata from metadata file" ), myLastUsedDir,
334 tr( "QGIS Layer Metadata File" ) + " (*.qmd)" );
335 if ( myFileName.isNull() )
336 {
337 return;
338 }
339
340 QString myMessage;
341 bool defaultLoadedFlag = false;
342 myMessage = mLayer->loadNamedMetadata( myFileName, defaultLoadedFlag );
343
344 //reset if the default style was loaded OK only
345 if ( defaultLoadedFlag )
346 {
347 mMetadataWidget->setMetadata( &mLayer->metadata() );
348 }
349 else
350 {
351 //let the user know what went wrong
352 QMessageBox::warning( this, tr( "Load Metadata" ), myMessage );
353 }
354
355 const QFileInfo myFI( myFileName );
356 const QString myPath = myFI.path();
357 myQSettings.setValue( QStringLiteral( "style/lastStyleDir" ), myPath );
358
359 activateWindow(); // set focus back to properties dialog
360}
361
362void QgsVectorTileLayerProperties::saveMetadataAs()
363{
364 QgsSettings myQSettings; // where we keep last used filter in persistent state
365 const QString myLastUsedDir = myQSettings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
366
367 QString myOutputFileName = QFileDialog::getSaveFileName( this, tr( "Save Layer Metadata as QMD" ),
368 myLastUsedDir, tr( "QMD File" ) + " (*.qmd)" );
369 if ( myOutputFileName.isNull() ) //dialog canceled
370 {
371 return;
372 }
373
374 mMetadataWidget->acceptMetadata();
375
376 //ensure the user never omitted the extension from the file name
377 if ( !myOutputFileName.endsWith( QgsMapLayer::extensionPropertyType( QgsMapLayer::Metadata ), Qt::CaseInsensitive ) )
378 {
380 }
381
382 bool defaultLoadedFlag = false;
383 const QString message = mLayer->saveNamedMetadata( myOutputFileName, defaultLoadedFlag );
384 if ( defaultLoadedFlag )
385 myQSettings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( myOutputFileName ).absolutePath() );
386 else
387 QMessageBox::information( this, tr( "Save Metadata" ), message );
388}
389
390void QgsVectorTileLayerProperties::showHelp()
391{
392 const QVariant helpPage = mOptionsStackedWidget->currentWidget()->property( "helpPage" );
393
394 if ( helpPage.isValid() )
395 {
396 QgsHelp::openHelp( helpPage.toString() );
397 }
398 else
399 {
400 QgsHelp::openHelp( QStringLiteral( "working_with_vector_tiles/vector_tiles_properties.html" ) );
401 }
402}
403
404void QgsVectorTileLayerProperties::urlClicked( const QUrl &url )
405{
406 const QFileInfo file( url.toLocalFile() );
407 if ( file.exists() && !file.isDir() )
408 QgsGui::nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
409 else
410 QDesktopServices::openUrl( url );
411}
412
414{
416
417 const bool isMetadataPanel = ( index == mOptStackedWidget->indexOf( mOptsPage_Metadata ) );
418 mBtnStyle->setVisible( ! isMetadataPanel );
419 mBtnMetadata->setVisible( isMetadataPanel );
420}
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 setPixelSizeConversionFactor(double sizeConversionFactor)
Sets the pixel size conversion factor, used to scale the original pixel sizes when converting styles.
void setTargetUnit(QgsUnitTypes::RenderUnit targetUnit)
Sets the target unit type.
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:68
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:62
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.
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
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:3061
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3060