diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in index 43ed5d671d9a..1fbf9c9725c5 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in @@ -227,6 +227,8 @@ or read on demand virtual bool addDataset( const QString &uri ) = 0; %Docstring Associate dataset with the mesh + +emits dataChanged when successful %End virtual int datasetCount() const = 0; diff --git a/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in b/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in index 5faeb0c51966..db2a93c9b944 100644 --- a/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in @@ -10,6 +10,7 @@ + class QgsMeshRendererMeshSettings { %Docstring @@ -73,54 +74,19 @@ Represents a mesh renderer settings for scalar datasets #include "qgsmeshrenderersettings.h" %End public: - QColor maxColor() const; -%Docstring -Returns color representing maximum scalar value in the dataset -%End - void setMaxColor( const QColor &maxColor ); -%Docstring -Sets color representing maximum scalar value in the dataset -%End - - QColor minColor() const; + QgsColorRampShader colorRampShader() const; %Docstring -Returns color representing minimum scalar value in the dataset +Returns color ramp shader function %End - void setMinColor( const QColor &minColor ); + void setColorRampShader( const QgsColorRampShader &shader ); %Docstring -Sets color representing maximum scalar value in the dataset +Sets color ramp shader function %End - double minValue() const; -%Docstring -Returns min scalar value that represents minColor() - -if set to numerical_limits.quiet_NaN(), value for minColor() is -taken from minimum value of active scalar dataset -%End - - void setMinValue( double minValue ); -%Docstring -Sets min scalar value that represents minColor() - -.. seealso:: :py:func:`QgsMeshRendererScalarSettings.minValue` -%End - - double maxValue() const; -%Docstring -Returns max scalar value that represents maxColor() - -if set to numerical_limits.quiet_NaN(), value for maxColor() is -taken from maximum value of active scalar dataset -%End - - void setMaxValue( double maxValue ); + bool isEnabled() const; %Docstring -Sets min scalar value that represents minColor() - -.. seealso:: :py:func:`QgsMeshRendererScalarSettings.maxValue` +Returns whether color ramp has any items assigned %End - }; class QgsMeshRendererVectorSettings diff --git a/python/core/auto_generated/raster/qgscolorrampshader.sip.in b/python/core/auto_generated/raster/qgscolorrampshader.sip.in index 45fd0bd703b0..6fdb86640eed 100644 --- a/python/core/auto_generated/raster/qgscolorrampshader.sip.in +++ b/python/core/auto_generated/raster/qgscolorrampshader.sip.in @@ -92,6 +92,13 @@ Returns the color ramp type as a string. void setColorRampType( QgsColorRampShader::Type colorRampType ); %Docstring Sets the color ramp type +%End + + bool isEmpty() const; +%Docstring +Whether the color ramp contains any items + +.. versionadded:: 3.4 %End QgsColorRamp *sourceColorRamp() const /Factory/; diff --git a/python/gui/auto_generated/raster/qgssinglebandpseudocolorrendererwidget.sip.in b/python/gui/auto_generated/raster/qgssinglebandpseudocolorrendererwidget.sip.in index f4fcc8729100..b35b858d12ce 100644 --- a/python/gui/auto_generated/raster/qgssinglebandpseudocolorrendererwidget.sip.in +++ b/python/gui/auto_generated/raster/qgssinglebandpseudocolorrendererwidget.sip.in @@ -23,12 +23,22 @@ class QgsSingleBandPseudoColorRendererWidget: QgsRasterRendererWidget static QgsRasterRendererWidget *create( QgsRasterLayer *layer, const QgsRectangle &extent ) /Factory/; virtual QgsRasterRenderer *renderer(); + + QgsColorRampShader *shaderFunction() const /Factory/; +%Docstring +Returns shader function used in the renderer. Caller takes ownership and deletes it. +%End virtual void setMapCanvas( QgsMapCanvas *canvas ); virtual void doComputations(); virtual QgsRasterMinMaxWidget *minMaxWidget(); + int currentBand() const; +%Docstring +Returns the current raster band number +%End + void setFromRenderer( const QgsRasterRenderer *r ); public slots: diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index fb6b0a9f6aec..c75cd2fda8f9 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -209,6 +209,14 @@ SET(QGIS_APP_SRCS qgssettingstree.cpp qgsvariantdelegate.cpp qgscrashhandler.cpp + + mesh/qgsmeshlayerproperties.cpp + mesh/qgsrenderermeshpropertieswidget.cpp + mesh/qgsmeshrenderermeshsettingswidget.cpp + mesh/qgsmeshrendererscalarsettingswidget.cpp + mesh/qgsmeshrenderervectorsettingswidget.cpp + mesh/qgsmeshrendereractivedatasetwidget.cpp + mesh/qgsmeshdatasetgrouptreeview.cpp ) SET (QGIS_APP_MOC_HDRS @@ -408,6 +416,14 @@ SET (QGIS_APP_MOC_HDRS qgssettingstree.h qgsvariantdelegate.h + + mesh/qgsmeshlayerproperties.h + mesh/qgsrenderermeshpropertieswidget.h + mesh/qgsmeshrenderermeshsettingswidget.h + mesh/qgsmeshrendererscalarsettingswidget.h + mesh/qgsmeshrenderervectorsettingswidget.h + mesh/qgsmeshrendereractivedatasetwidget.h + mesh/qgsmeshdatasetgrouptreeview.h ) @@ -582,6 +598,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/app/pluginmanager ${CMAKE_SOURCE_DIR}/src/app/gps ${CMAKE_SOURCE_DIR}/src/app/dwg + ${CMAKE_SOURCE_DIR}/src/app/mesh ${CMAKE_SOURCE_DIR}/src/app/locator ${CMAKE_SOURCE_DIR}/src/analysis/raster ${CMAKE_SOURCE_DIR}/src/core @@ -658,6 +675,7 @@ INCLUDE_DIRECTORIES( ../core/processing ../core/providers/memory ../core/raster + ../core/mesh ../core/scalebar ../core/symbology ../gui diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp new file mode 100644 index 000000000000..290904acfac3 --- /dev/null +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp @@ -0,0 +1,286 @@ +/*************************************************************************** + qgsmeshdatasetgrouptreeview.cpp + ------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshdatasetgrouptreeview.h" + +#include "qgis.h" +#include "qgsmeshlayer.h" + +#include +#include + +QgsMeshDatasetGroupTreeItem::QgsMeshDatasetGroupTreeItem( QgsMeshDatasetGroupTreeItem *parent ) + : mParent( parent ) +{ +} + +QgsMeshDatasetGroupTreeItem::QgsMeshDatasetGroupTreeItem( const QString &name, QgsMeshDatasetGroupTreeItem *parent ) + : mParent( parent ) + , mName( name ) +{ +} + +QgsMeshDatasetGroupTreeItem::~QgsMeshDatasetGroupTreeItem() +{ + qDeleteAll( mChildren ); +} + +void QgsMeshDatasetGroupTreeItem::appendChild( QgsMeshDatasetGroupTreeItem *node ) +{ + mChildren.append( node ); +} + +QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupTreeItem::child( int row ) const +{ + if ( row < mChildren.count() ) + return mChildren.at( row ); + else + return nullptr; +} + +int QgsMeshDatasetGroupTreeItem::columnCount() const +{ + return 1; +} + +int QgsMeshDatasetGroupTreeItem::childCount() const +{ + return mChildren.count(); +} + +QgsMeshDatasetGroupTreeItem *QgsMeshDatasetGroupTreeItem::parentItem() const +{ + return mParent; +} + +int QgsMeshDatasetGroupTreeItem::row() const +{ + if ( mParent ) + return mParent->mChildren.indexOf( const_cast( this ) ); + + return 0; +} + +QVariant QgsMeshDatasetGroupTreeItem::data( int column ) const +{ + Q_UNUSED( column ); + return mName; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +QgsMeshDatasetGroupTreeModel::QgsMeshDatasetGroupTreeModel( QObject *parent ) + : QAbstractItemModel( parent ) + , mRootItem( new QgsMeshDatasetGroupTreeItem() ) +{ +} + +QgsMeshDatasetGroupTreeModel::~QgsMeshDatasetGroupTreeModel() = default; + +int QgsMeshDatasetGroupTreeModel::columnCount( const QModelIndex &parent ) const +{ + if ( parent.isValid() ) + return static_cast( parent.internalPointer() )->columnCount(); + else + return mRootItem->columnCount(); +} + +QVariant QgsMeshDatasetGroupTreeModel::data( const QModelIndex &index, int role ) const +{ + if ( !index.isValid() ) + return QVariant(); + + if ( role != Qt::DisplayRole ) + return QVariant(); + + QgsMeshDatasetGroupTreeItem *item = static_cast( index.internalPointer() ); + + return item->data( index.column() ); +} + +Qt::ItemFlags QgsMeshDatasetGroupTreeModel::flags( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return Qt::NoItemFlags; + + return QAbstractItemModel::flags( index ); +} + +QVariant QgsMeshDatasetGroupTreeModel::headerData( int section, + Qt::Orientation orientation, + int role ) const +{ + Q_UNUSED( section ); + + if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) + return tr( "Groups" ); + + return QVariant(); +} + +QModelIndex QgsMeshDatasetGroupTreeModel::index( int row, int column, const QModelIndex &parent ) +const +{ + if ( !hasIndex( row, column, parent ) ) + return QModelIndex(); + + QgsMeshDatasetGroupTreeItem *parentItem; + + if ( !parent.isValid() ) + parentItem = mRootItem.get(); + else + parentItem = static_cast( parent.internalPointer() ); + + QgsMeshDatasetGroupTreeItem *childItem = parentItem->child( row ); + if ( childItem ) + return createIndex( row, column, childItem ); + else + return QModelIndex(); +} + +QModelIndex QgsMeshDatasetGroupTreeModel::parent( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return QModelIndex(); + + QgsMeshDatasetGroupTreeItem *childItem = static_cast( index.internalPointer() ); + QgsMeshDatasetGroupTreeItem *parentItem = childItem->parentItem(); + + if ( parentItem == mRootItem.get() ) + return QModelIndex(); + + return createIndex( parentItem->row(), 0, parentItem ); +} + +int QgsMeshDatasetGroupTreeModel::rowCount( const QModelIndex &parent ) const +{ + QgsMeshDatasetGroupTreeItem *parentItem; + if ( parent.column() > 0 ) + return 0; + + if ( !parent.isValid() ) + parentItem = mRootItem.get(); + else + parentItem = static_cast( parent.internalPointer() ); + + return parentItem->childCount(); +} + +void QgsMeshDatasetGroupTreeModel::setupModelData( const QStringList &groups ) +{ + beginResetModel(); + + mRootItem.reset( new QgsMeshDatasetGroupTreeItem() ); + + for ( const QString &groupName : groups ) + { + QgsMeshDatasetGroupTreeItem *item = new QgsMeshDatasetGroupTreeItem( groupName, mRootItem.get() ); + mRootItem->appendChild( item ); + } + + endResetModel(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +QgsMeshDatasetGroupTreeView::QgsMeshDatasetGroupTreeView( QWidget *parent ) + : QTreeView( parent ) +{ + setModel( &mModel ); + + setSelectionMode( QAbstractItemView::SingleSelection ) ; + connect( selectionModel(), + &QItemSelectionModel::selectionChanged, + this, + &QgsMeshDatasetGroupTreeView::onSelectionChanged + ); +} + +void QgsMeshDatasetGroupTreeView::setLayer( QgsMeshLayer *layer ) +{ + if ( layer != mMeshLayer ) + { + mMeshLayer = layer; + syncToLayer(); + } +} + +QVector QgsMeshDatasetGroupTreeView::datasetsInActiveGroup() const +{ + if ( mGroups.constFind( mActiveGroup ) == mGroups.constEnd() ) + return QVector(); + else + return mGroups[mActiveGroup]; +} + +void QgsMeshDatasetGroupTreeView::onSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected ) +{ + Q_UNUSED( deselected ); + + if ( selected.isEmpty() ) + { + mActiveGroup = QString(); + return; + } + + if ( selected.first().indexes().isEmpty() ) + { + mActiveGroup = QString(); + return; + } + + QModelIndex index = selected.first().indexes().first(); //single selection only + QVariant name = mModel.data( index, 0 ); + mActiveGroup = name.toString(); + emit activeGroupChanged(); +} + + +void QgsMeshDatasetGroupTreeView::extractGroups() +{ + // TODO replace with MDAL groups when introduced + mGroups.clear(); + + if ( !mMeshLayer || !mMeshLayer->dataProvider() ) + return; + + for ( int i = 0; i < mMeshLayer->dataProvider()->datasetCount(); ++i ) + { + const QgsMeshDatasetMetadata meta = mMeshLayer->dataProvider()->datasetMetadata( i ); + QString name = meta.extraOptions()["name"]; + if ( mGroups.constFind( name ) == mGroups.constEnd() ) + { + QVector datasets; + datasets.append( i ); + mGroups[name] = datasets; + } + else + { + mGroups[name].append( i ); + } + } +} + +void QgsMeshDatasetGroupTreeView::syncToLayer() +{ + mActiveGroup.clear(); + + extractGroups(); + + mModel.setupModelData( mGroups.keys() ); + + if ( mGroups.size() > 0 ) + setCurrentIndex( mModel.index( 0, 0 ) ); +} diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.h b/src/app/mesh/qgsmeshdatasetgrouptreeview.h new file mode 100644 index 000000000000..c8da8ed719e1 --- /dev/null +++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.h @@ -0,0 +1,136 @@ +/*************************************************************************** + qgsmeshdatasetgrouptreeview.h + ----------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHDATASETGROUPTREE_H +#define QGSMESHDATASETGROUPTREE_H + +#include "qgis_app.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class QgsMeshLayer; + +/** + * Tree item for display of the mesh dataset groups. + * Dataset group is set of datasets with the same name, + * but different control variable (e.g. time) + * + * Support for multiple levels, because groups can have + * subgroups, for example + * + * Groups: + * Depth + * - Maximum/Depth + * Velocity + * Wind speed + * - Maximum/Wind Speed + */ +class APP_NO_EXPORT QgsMeshDatasetGroupTreeItem +{ + public: + QgsMeshDatasetGroupTreeItem( QgsMeshDatasetGroupTreeItem *parent = nullptr ); + QgsMeshDatasetGroupTreeItem( const QString &name, QgsMeshDatasetGroupTreeItem *parent = nullptr ); + ~QgsMeshDatasetGroupTreeItem(); + + void appendChild( QgsMeshDatasetGroupTreeItem *node ); + QgsMeshDatasetGroupTreeItem *child( int row ) const; + int columnCount() const; + int childCount() const; + QgsMeshDatasetGroupTreeItem *parentItem() const; + int row() const; + QVariant data( int column ) const; + + private: + QgsMeshDatasetGroupTreeItem *mParent = nullptr; + QList< QgsMeshDatasetGroupTreeItem * > mChildren; + + // Data + QString mName; +}; + +/** + * Item Model for QgsMeshDatasetGroupTreeItem + */ +class APP_NO_EXPORT QgsMeshDatasetGroupTreeModel : public QAbstractItemModel +{ + Q_OBJECT + + public: + explicit QgsMeshDatasetGroupTreeModel( QObject *parent = nullptr ); + ~QgsMeshDatasetGroupTreeModel(); + + QVariant data( const QModelIndex &index, int role ) const override; + Qt::ItemFlags flags( const QModelIndex &index ) const override; + QVariant headerData( int section, Qt::Orientation orientation, + int role = Qt::DisplayRole ) const override; + QModelIndex index( int row, int column, + const QModelIndex &parent = QModelIndex() ) const override; + QModelIndex parent( const QModelIndex &index ) const override; + int rowCount( const QModelIndex &parent = QModelIndex() ) const override; + int columnCount( const QModelIndex &parent = QModelIndex() ) const override; + + //! Add groups to the model + void setupModelData( const QStringList &groups ); + + private: + std::unique_ptr mRootItem; +}; + +/** + * Tree widget for display of the mesh dataset groups. + * + * One dataset group is selected (active) + */ +class APP_EXPORT QgsMeshDatasetGroupTreeView : public QTreeView +{ + Q_OBJECT + + public: + QgsMeshDatasetGroupTreeView( QWidget *parent = nullptr ); + ~QgsMeshDatasetGroupTreeView() = default; + + //! Associates mesh layer with the widget + void setLayer( QgsMeshLayer *layer ); + + //! Returns all the dataset indexes in the active group + QVector datasetsInActiveGroup() const; + + //! Synchronize widgets state with associated mesh layer + void syncToLayer(); + + signals: + //! Selected dataset group changed + void activeGroupChanged(); + + private slots: + void onSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected ); + + private: + void extractGroups(); + + QgsMeshDatasetGroupTreeModel mModel; + QgsMeshLayer *mMeshLayer = nullptr; // not owned + QMap> mGroups; // group name -> dataset indices + QString mActiveGroup; +}; + +#endif // QGSMESHDATASETGROUPTREE_H diff --git a/src/app/mesh/qgsmeshlayerproperties.cpp b/src/app/mesh/qgsmeshlayerproperties.cpp new file mode 100644 index 000000000000..30e4777904e2 --- /dev/null +++ b/src/app/mesh/qgsmeshlayerproperties.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** + qgsmeshlayerproperties.cpp + -------------------------- + begin : Jun 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include + +#include "qgisapp.h" +#include "qgsapplication.h" +#include "qgscoordinatetransform.h" +#include "qgshelp.h" +#include "qgslogger.h" +#include "qgsmapcanvas.h" +#include "qgsmaplayerstylemanager.h" +#include "qgsmeshlayer.h" +#include "qgsmeshlayerproperties.h" +#include "qgsproject.h" +#include "qgsprojectionselectiondialog.h" +#include "qgsrenderermeshpropertieswidget.h" +#include "qgssettings.h" + +#include +#include + +QgsMeshLayerProperties::QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas *canvas, QWidget *parent, Qt::WindowFlags fl ) + : QgsOptionsDialogBase( QStringLiteral( "MeshLayerProperties" ), parent, fl ) + , mMeshLayer( qobject_cast( lyr ) ) +{ + Q_ASSERT( mMeshLayer ); + + setupUi( this ); + mRendererMeshPropertiesWidget = new QgsRendererMeshPropertiesWidget( mMeshLayer, canvas, this ); + mOptsPage_StyleContent->layout()->addWidget( mRendererMeshPropertiesWidget ); + + connect( mLayerOrigNameLineEd, &QLineEdit::textEdited, this, &QgsMeshLayerProperties::updateLayerName ); + connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, &QgsMeshLayerProperties::changeCrs ); + connect( mAddDatasetButton, &QPushButton::clicked, this, &QgsMeshLayerProperties::addDataset ); + + // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states, + // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left), + // and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots + initOptionsBase( false ); + + connect( lyr->styleManager(), &QgsMapLayerStyleManager::currentStyleChanged, this, &QgsMeshLayerProperties::syncAndRepaint ); + + connect( this, &QDialog::accepted, this, &QgsMeshLayerProperties::apply ); + connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsMeshLayerProperties::apply ); + + connect( mMeshLayer, &QgsMeshLayer::dataChanged, this, &QgsMeshLayerProperties::syncAndRepaint ); + + // update based on lyr's current state + syncToLayer(); + + QgsSettings settings; + // if dialog hasn't been opened/closed yet, default to Styles tab, which is used most often + // this will be read by restoreOptionsBaseUi() + if ( !settings.contains( QStringLiteral( "/Windows/MeshLayerProperties/tab" ) ) ) + { + settings.setValue( QStringLiteral( "Windows/MeshLayerProperties/tab" ), + mOptStackedWidget->indexOf( mOptsPage_Style ) ); + } + + QString title = QString( tr( "Layer Properties - %1" ) ).arg( lyr->name() ); + + if ( !mMeshLayer->styleManager()->isDefault( mMeshLayer->styleManager()->currentStyle() ) ) + title += QStringLiteral( " (%1)" ).arg( mMeshLayer->styleManager()->currentStyle() ); + restoreOptionsBaseUi( title ); +} + +void QgsMeshLayerProperties::syncToLayer() +{ + Q_ASSERT( mRendererMeshPropertiesWidget ); + + QgsDebugMsg( "populate general information tab" ); + /* + * Information Tab + */ + QString info; + if ( mMeshLayer->dataProvider() ) + { + info += QStringLiteral( "" ); + info += QStringLiteral( "" ).arg( tr( "Uri" ) ).arg( mMeshLayer->dataProvider()->dataSourceUri() ); + info += QStringLiteral( "" ).arg( tr( "Vertex count" ) ).arg( mMeshLayer->dataProvider()->vertexCount() ); + info += QStringLiteral( "" ).arg( tr( "Face count" ) ).arg( mMeshLayer->dataProvider()->faceCount() ); + info += QStringLiteral( "" ).arg( tr( "Dataset count" ) ).arg( mMeshLayer->dataProvider()->datasetCount() ); + info += QStringLiteral( "
%1: %2
%1: %2
%1: %2
%1: %2
" ); + } + else + { + info += tr( "Invalid data provider" ); + } + mInformationTextBrowser->setText( info ); + + QgsDebugMsg( "populate source tab" ); + /* + * Source Tab + */ + mLayerOrigNameLineEd->setText( mMeshLayer->name() ); + leDisplayName->setText( mMeshLayer->name() ); + + if ( mMeshLayer && mMeshLayer->dataProvider() ) + { + mUriLabel->setText( mMeshLayer->dataProvider()->dataSourceUri() ); + } + else + { + mUriLabel->setText( tr( "Not assigned" ) ); + } + + QgsDebugMsg( "populate styling tab" ); + /* + * Styling Tab + */ + mRendererMeshPropertiesWidget->syncToLayer(); +} + +void QgsMeshLayerProperties::addDataset() +{ + if ( !mMeshLayer->dataProvider() ) + return; + + QgsSettings settings; + QString openFileDir = settings.value( QStringLiteral( "lastMeshDatasetDir" ), QDir::homePath(), QgsSettings::App ).toString(); + QString openFileString = QFileDialog::getOpenFileName( nullptr, tr( "Load mesh datasets" ), openFileDir, tr( "Layout mesh datasets" ) + " (*.*)" ); + + if ( openFileString.isEmpty() ) + { + return; //canceled by the user + } + + QFileInfo openFileInfo( openFileString ); + settings.setValue( QStringLiteral( "lastMeshDatasetDir" ), openFileInfo.absolutePath(), QgsSettings::App ); + QFile datasetFile( openFileString ); + + bool ok = mMeshLayer->dataProvider()->addDataset( openFileString ); + if ( ok ) + { + syncToLayer(); + QgsDebugMsg( "datasets added to the mesh layer" ); + } + else + { + QMessageBox::warning( this, tr( "Load mesh datasets" ), tr( "Could not read mesh dataset." ) ); + } +} + +void QgsMeshLayerProperties::apply() +{ + Q_ASSERT( mRendererMeshPropertiesWidget ); + + QgsDebugMsg( "processing general tab" ); + /* + * General Tab + */ + mMeshLayer->setName( mLayerOrigNameLineEd->text() ); + + QgsDebugMsg( "processing style tab" ); + /* + * Style Tab + */ + mRendererMeshPropertiesWidget->apply(); + + //make sure the layer is redrawn + mMeshLayer->triggerRepaint(); + + // notify the project we've made a change + QgsProject::instance()->setDirty( true ); +} + +void QgsMeshLayerProperties::changeCrs( const QgsCoordinateReferenceSystem &crs ) +{ + mMeshLayer->setCrs( crs ); +} + +void QgsMeshLayerProperties::updateLayerName( const QString &text ) +{ + leDisplayName->setText( mMeshLayer->formatLayerName( text ) ); +} + +void QgsMeshLayerProperties::syncAndRepaint() +{ + syncToLayer(); + mMeshLayer->triggerRepaint(); +} diff --git a/src/app/mesh/qgsmeshlayerproperties.h b/src/app/mesh/qgsmeshlayerproperties.h new file mode 100644 index 000000000000..a3d6d84d399d --- /dev/null +++ b/src/app/mesh/qgsmeshlayerproperties.h @@ -0,0 +1,72 @@ +/*************************************************************************** + qgsmeshlayerproperties.h + ------------------------ + begin : Jun 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSMESHLAYERPROPERTIES_H +#define QGSMESHLAYERPROPERTIES_H + +#include "ui_qgsmeshlayerpropertiesbase.h" + +#include "qgsoptionsdialogbase.h" +#include "qgsguiutils.h" +#include "qgis_app.h" + +class QgsMapLayer; +class QgsMapCanvas; +class QgsMeshLayer; +class QgsRendererMeshPropertiesWidget; + +/** + * Property sheet for a mesh map layer. + * Contains information, source and style tabs + */ +class APP_EXPORT QgsMeshLayerProperties : public QgsOptionsDialogBase, private Ui::QgsMeshLayerPropertiesBase +{ + Q_OBJECT + + public: + + /** + * \brief Constructor + * \param lyr Mesh map layer for which properties will be displayed + */ + QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas *canvas, QWidget *parent = nullptr, Qt::WindowFlags = QgsGuiUtils::ModalDialogFlags ); + ~QgsMeshLayerProperties() = default; + + private slots: + //! Synchronize widgets state with associated mesh layer + void syncToLayer(); + + //!Applies the settings made in the dialog without closing the box + void apply(); + //! \brief Slot to update layer display name as original is edited. + void updateLayerName( const QString &text ); + //! Synchronize GUI state with associated mesh layer and trigger repaint + void syncAndRepaint(); + //! Change layer coordinate reference system + void changeCrs( const QgsCoordinateReferenceSystem &crs ); + //! Associate dataset to the mesh layer + void addDataset(); + + private: + //! Pointer to the mesh styling widget + QgsRendererMeshPropertiesWidget *mRendererMeshPropertiesWidget = nullptr; + + //! \brief Pointer to the mesh layer that this property dilog changes the behavior of. + QgsMeshLayer *mMeshLayer = nullptr; +}; + + +#endif // QGSMESHLAYERPROPERTIES_H diff --git a/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp b/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp new file mode 100644 index 000000000000..7241e6559b26 --- /dev/null +++ b/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp @@ -0,0 +1,186 @@ +/*************************************************************************** + qgsmeshrendereractivedatasetwidget.cpp + --------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshrendereractivedatasetwidget.h" + +#include "qgis.h" +#include "qgsmeshlayer.h" +#include "qgsmessagelog.h" +#include "qgsmeshrenderersettings.h" + +QgsMeshRendererActiveDatasetWidget::QgsMeshRendererActiveDatasetWidget( QWidget *parent ) + : QWidget( parent ) +{ + setupUi( this ); + connect( mDatasetGroupTreeView, &QgsMeshDatasetGroupTreeView::activeGroupChanged, this, &QgsMeshRendererActiveDatasetWidget::onActiveGroupChanged ); + connect( mDatasetSlider, &QSlider::valueChanged, this, &QgsMeshRendererActiveDatasetWidget::onActiveDatasetChanged ); + connect( mDisplayScalarsCheckBox, &QCheckBox::stateChanged, this, &QgsMeshRendererActiveDatasetWidget::onScalarChecked ); + connect( mDisplayVectorsCheckBox, &QCheckBox::stateChanged, this, &QgsMeshRendererActiveDatasetWidget::onVectorChecked ); + connect( mDisplayNativeMeshCheckBox, &QCheckBox::stateChanged, this, &QgsMeshRendererActiveDatasetWidget::onNativeMeshChecked ); + connect( mDisplayTriangularMeshCheckBox, &QCheckBox::stateChanged, this, &QgsMeshRendererActiveDatasetWidget::onTringularMeshChecked ); +} + +void QgsMeshRendererActiveDatasetWidget::setLayer( QgsMeshLayer *layer ) +{ + if ( layer != mMeshLayer ) + { + mMeshLayer = layer; + } + + setEnabled( mMeshLayer ); + syncToLayer(); + + mDatasetGroupTreeView->setLayer( layer ); +} + +int QgsMeshRendererActiveDatasetWidget::activeScalarDataset() const +{ + if ( isEnabled() && + mDisplayScalarsCheckBox->isEnabled() && + mDisplayScalarsCheckBox->isChecked() ) + return datasetIndex(); + else + return -1; +} + +int QgsMeshRendererActiveDatasetWidget::activeVectorDataset() const +{ + if ( isEnabled() && + mDisplayVectorsCheckBox->isEnabled() && + mDisplayVectorsCheckBox->isChecked() ) + return datasetIndex(); + else + return -1; +} + +bool QgsMeshRendererActiveDatasetWidget::isNativeMeshEnabled() const +{ + return isEnabled() && mDisplayNativeMeshCheckBox->isChecked(); +} + +bool QgsMeshRendererActiveDatasetWidget::isTriangularMeshEnabled() const +{ + return isEnabled() && mDisplayTriangularMeshCheckBox->isChecked(); +} + +void QgsMeshRendererActiveDatasetWidget::onActiveGroupChanged() +{ + const QVector datasets = mDatasetGroupTreeView->datasetsInActiveGroup(); + + mDatasetSlider->setMinimum( 0 ); + mDatasetSlider->setMaximum( datasets.size() - 1 ); + mDatasetSlider->setValue( 0 ); +} + +void QgsMeshRendererActiveDatasetWidget::onActiveDatasetChanged( int value ) +{ + int datasetIndex = -1; + const QVector datasets = mDatasetGroupTreeView->datasetsInActiveGroup(); + if ( datasets.size() < value || !mMeshLayer || !mMeshLayer->dataProvider() ) + { + mDisplayScalarsCheckBox->setEnabled( false ); + mDisplayVectorsCheckBox->setEnabled( false ); + } + else + { + datasetIndex = datasets[value]; + const QgsMeshDatasetMetadata meta = mMeshLayer->dataProvider()->datasetMetadata( datasetIndex ); + mDisplayScalarsCheckBox->setEnabled( true ); + mDisplayVectorsCheckBox->setEnabled( meta.isVector() ); + } + + updateMetadata( datasetIndex ); + + emit activeScalarDatasetChanged( activeScalarDataset() ); + emit activeVectorDatasetChanged( activeVectorDataset() ); + + emit widgetChanged(); +} + +void QgsMeshRendererActiveDatasetWidget::onScalarChecked( int toggle ) +{ + Q_UNUSED( toggle ); + emit activeScalarDatasetChanged( activeScalarDataset() ); + emit widgetChanged(); +} + +void QgsMeshRendererActiveDatasetWidget::onVectorChecked( int toggle ) +{ + Q_UNUSED( toggle ); + emit activeVectorDatasetChanged( activeVectorDataset() ); + emit widgetChanged(); +} + +void QgsMeshRendererActiveDatasetWidget::onNativeMeshChecked( int toggle ) +{ + Q_UNUSED( toggle ); + emit nativeMeshEnabledChanged( isNativeMeshEnabled() ); + emit widgetChanged(); +} + +void QgsMeshRendererActiveDatasetWidget::onTringularMeshChecked( int toggle ) +{ + Q_UNUSED( toggle ); + emit triangularMeshEnabledChanged( isTriangularMeshEnabled() ); + emit widgetChanged(); +} + +void QgsMeshRendererActiveDatasetWidget::updateMetadata( int datasetIndex ) +{ + if ( datasetIndex == -1 ) + { + mActiveDatasetMetadata->setText( tr( "N/A" ) ); + } + else + { + const QgsMeshDatasetMetadata meta = mMeshLayer->dataProvider()->datasetMetadata( datasetIndex ); + QString msg; + msg += QStringLiteral( "" ); + msg += QStringLiteral( "" ).arg( tr( "is on vertices" ) ).arg( meta.isOnVertices() ); + msg += QStringLiteral( "" ).arg( tr( "is vector" ) ).arg( meta.isVector() ); + for ( auto it = meta.extraOptions().constBegin(); it != meta.extraOptions().constEnd(); ++it ) + { + msg += QStringLiteral( "" ).arg( it.key() ).arg( it.value() ); + } + msg += QStringLiteral( "
%1%2
%1%2
%1%2
" ); + mActiveDatasetMetadata->setText( msg ); + } + +} + +int QgsMeshRendererActiveDatasetWidget::datasetIndex() const +{ + const QVector datasets = mDatasetGroupTreeView->datasetsInActiveGroup(); + int value = mDatasetSlider->value(); + int datasetIndex = -1; + if ( value < datasets.size() ) + { + datasetIndex = datasets[value]; + } + return datasetIndex; +} + +void QgsMeshRendererActiveDatasetWidget::syncToLayer() +{ + mDatasetGroupTreeView->syncToLayer(); + + if ( mMeshLayer ) + { + whileBlocking( mDisplayNativeMeshCheckBox )->setChecked( mMeshLayer->rendererNativeMeshSettings().isEnabled() ); + whileBlocking( mDisplayTriangularMeshCheckBox )->setChecked( mMeshLayer->rendererTriangularMeshSettings().isEnabled() ); + whileBlocking( mDisplayScalarsCheckBox )->setChecked( mMeshLayer->activeScalarDataset() != -1 ); + whileBlocking( mDisplayVectorsCheckBox )->setChecked( mMeshLayer->activeVectorDataset() != -1 ); + } +} diff --git a/src/app/mesh/qgsmeshrendereractivedatasetwidget.h b/src/app/mesh/qgsmeshrendereractivedatasetwidget.h new file mode 100644 index 000000000000..90859169036f --- /dev/null +++ b/src/app/mesh/qgsmeshrendereractivedatasetwidget.h @@ -0,0 +1,94 @@ +/*************************************************************************** + qgsmeshrendereractivedatasetwidget.h + ------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHRENDERERACTIVEDATASETWIDGET_H +#define QGSMESHRENDERERACTIVEDATASETWIDGET_H + +#include "ui_qgsmeshrendereractivedatasetwidgetbase.h" +#include "qgis_app.h" + +#include + +class QgsMeshLayer; + +/** + * Widget for selection of active dataset group from tree view. + * Also selects the active scalar and vector dataset by slider + * and whether mesh rendering is enabled by checkboxes. + */ +class APP_EXPORT QgsMeshRendererActiveDatasetWidget : public QWidget, private Ui::QgsMeshRendererActiveDatasetWidgetBase +{ + Q_OBJECT + + public: + + /** + * A widget to hold the renderer scalar settings for a mesh layer. + * \param parent Parent object + */ + QgsMeshRendererActiveDatasetWidget( QWidget *parent = nullptr ); + ~QgsMeshRendererActiveDatasetWidget() = default; + + //! Associates mesh layer with the widget + void setLayer( QgsMeshLayer *layer ); + + //! Gets index of the selected/active scalar dataset + int activeScalarDataset() const; + + //! Gets index of the selected/active vector dataset + int activeVectorDataset() const; + + //! Returns whether rendering of the native mesh is enabled + bool isNativeMeshEnabled() const; + + //! Returns whether rendering of the triangular mesh is enabled + bool isTriangularMeshEnabled() const; + + //! Synchronizes widgets state with associated mesh layer + void syncToLayer(); + + signals: + + //! Emitted when active scalar dataset changed + void activeScalarDatasetChanged( int index ); + + //! Emitted when active vector dataset changed + void activeVectorDatasetChanged( int index ); + + //! Emitted when rendering of the native mesh changed + void nativeMeshEnabledChanged( bool on ); + + //! Emitted when rendering of the triangular mesh changed + void triangularMeshEnabledChanged( bool on ); + + //! Emitted when any settings related to rendering changed + void widgetChanged(); + + private slots: + void onActiveGroupChanged(); + void onActiveDatasetChanged( int value ); + void onScalarChecked( int toggle ); + void onVectorChecked( int toggle ); + void onNativeMeshChecked( int toggle ); + void onTringularMeshChecked( int toggle ); + void updateMetadata( int datasetIndex ); + + private: + int datasetIndex() const; + + QgsMeshLayer *mMeshLayer = nullptr; // not owned +}; + +#endif // QGSMESHRENDERERSCALARSETTINGSWIDGET_H diff --git a/src/app/mesh/qgsmeshrenderermeshsettingswidget.cpp b/src/app/mesh/qgsmeshrenderermeshsettingswidget.cpp new file mode 100644 index 000000000000..a9ff6faeb843 --- /dev/null +++ b/src/app/mesh/qgsmeshrenderermeshsettingswidget.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** + qgsmeshrenderermeshsettingswidget.cpp + --------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshrenderermeshsettingswidget.h" +#include + +#include "qgis.h" +#include "qgsmapcanvas.h" +#include "qgsmeshlayer.h" +#include "qgsrasterlayer.h" +#include "raster/qgscolorrampshaderwidget.h" +#include "raster/qgsrasterminmaxwidget.h" +#include "qgsrasterminmaxorigin.h" +#include "qgsmessagelog.h" +#include "qgscolorbutton.h" +#include "qgsdoublespinbox.h" + +QgsMeshRendererMeshSettingsWidget::QgsMeshRendererMeshSettingsWidget( QWidget *parent ) + : QWidget( parent ) + +{ + setupUi( this ); + + connect( mColorWidget, &QgsColorButton::colorChanged, this, &QgsMeshRendererMeshSettingsWidget::widgetChanged ); + connect( mLineWidthSpinBox, QOverload::of( &QgsDoubleSpinBox::valueChanged ), + this, &QgsMeshRendererMeshSettingsWidget::widgetChanged ); +} + +void QgsMeshRendererMeshSettingsWidget::setLayer( QgsMeshLayer *layer, bool isTriangularMesh ) +{ + mIsTriangularMesh = isTriangularMesh; + + if ( layer != mMeshLayer ) + { + mMeshLayer = layer; + syncToLayer(); + } +} + +QgsMeshRendererMeshSettings QgsMeshRendererMeshSettingsWidget::settings() const +{ + QgsMeshRendererMeshSettings settings; + settings.setColor( mColorWidget->color() ); + settings.setLineWidth( mLineWidthSpinBox->value() ); + return settings; +} + +void QgsMeshRendererMeshSettingsWidget::syncToLayer( ) +{ + if ( !mMeshLayer ) + return; + + QgsMeshRendererMeshSettings settings; + if ( mIsTriangularMesh ) + settings = mMeshLayer->rendererTriangularMeshSettings(); + else + settings = mMeshLayer->rendererNativeMeshSettings(); + + mColorWidget->setColor( settings.color() ); + mLineWidthSpinBox->setValue( settings.lineWidth() ); +} diff --git a/src/app/mesh/qgsmeshrenderermeshsettingswidget.h b/src/app/mesh/qgsmeshrenderermeshsettingswidget.h new file mode 100644 index 000000000000..3b4f59f96ed9 --- /dev/null +++ b/src/app/mesh/qgsmeshrenderermeshsettingswidget.h @@ -0,0 +1,72 @@ +/*************************************************************************** + qgsmeshrenderermeshsettingswidget.h + ------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHRENDERERMESHSETTINGSWIDGET_H +#define QGSMESHRENDERERMESHSETTINGSWIDGET_H + +#include "ui_qgsmeshrenderermeshsettingswidgetbase.h" +#include "qgsmeshrenderersettings.h" +#include "qgis_app.h" + +#include + +class QgsMeshLayer; + +/** + * A widget for setup of the mesh frame settings of + * the mesh layer. Can be used for both native and + * triangular mesh renderer settings. + */ +class APP_EXPORT QgsMeshRendererMeshSettingsWidget : public QWidget, private Ui::QgsMeshRendererMeshSettingsWidgetBase +{ + Q_OBJECT + + public: + + /** + * A widget to hold the renderer mesh settings for a mesh layer. + * \param parent Parent object + */ + QgsMeshRendererMeshSettingsWidget( QWidget *parent = nullptr ); + ~QgsMeshRendererMeshSettingsWidget() = default; + + /** + * Associates mesh layer with the widget + * \param layer mesh layer that contains mesh frame + * \param isTriangularMesh whether use settings for triangular mesh or native mesh + */ + void setLayer( QgsMeshLayer *layer, bool isTriangularMesh ); + + //! Returns the mesh rendering settings (native or triangular) + QgsMeshRendererMeshSettings settings() const; + + //! Synchronizes widgets state with associated mesh layer + void syncToLayer(); + + signals: + //! Mesh rendering settings changed + void widgetChanged(); + + private: + QgsMeshLayer *mMeshLayer = nullptr; // not owned + + /** + * If true, the widget works for triangular (derived) mesh + * If false, the widget works for native mesh + */ + bool mIsTriangularMesh = true; +}; + +#endif // QGSMESHRENDERERMESHSETTINGSWIDGET_H diff --git a/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp b/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp new file mode 100644 index 000000000000..7ef2a0004fc7 --- /dev/null +++ b/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** + qgsmeshrendererscalarsettingswidget.cpp + --------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshrendererscalarsettingswidget.h" + +#include "qgis.h" +#include "qgsmeshlayer.h" +#include "qgsmessagelog.h" + + +QgsMeshRendererScalarSettingsWidget::QgsMeshRendererScalarSettingsWidget( QWidget *parent ) + : QWidget( parent ) + +{ + setupUi( this ); + + connect( mScalarRecalculateMinMaxButton, &QPushButton::clicked, this, &QgsMeshRendererScalarSettingsWidget::recalculateMinMaxButtonClicked ); + connect( mScalarMinLineEdit, &QLineEdit::textChanged, this, &QgsMeshRendererScalarSettingsWidget::minMaxChanged ); + connect( mScalarMaxLineEdit, &QLineEdit::textChanged, this, &QgsMeshRendererScalarSettingsWidget::minMaxChanged ); + connect( mScalarMinLineEdit, &QLineEdit::textEdited, this, &QgsMeshRendererScalarSettingsWidget::minMaxEdited ); + connect( mScalarMaxLineEdit, &QLineEdit::textEdited, this, &QgsMeshRendererScalarSettingsWidget::minMaxEdited ); + connect( mScalarColorRampShaderWidget, &QgsColorRampShaderWidget::widgetChanged, this, &QgsMeshRendererScalarSettingsWidget::widgetChanged ); + +} + +void QgsMeshRendererScalarSettingsWidget::setLayer( QgsMeshLayer *layer ) +{ + if ( layer != mMeshLayer ) + { + mMeshLayer = layer; + syncToLayer(); + } +} + +QgsMeshRendererScalarSettings QgsMeshRendererScalarSettingsWidget::settings() const +{ + QgsMeshRendererScalarSettings settings; + settings.setColorRampShader( mScalarColorRampShaderWidget->shader() ); + return settings; +} + +void QgsMeshRendererScalarSettingsWidget::syncToLayer( ) +{ + if ( !mMeshLayer ) + return; + + if ( mMeshLayer->rendererScalarSettings().isEnabled() ) + { + const QgsColorRampShader shader = mMeshLayer->rendererScalarSettings().colorRampShader(); + whileBlocking( mScalarMinLineEdit )->setText( QString::number( shader.minimumValue() ) ); + whileBlocking( mScalarMaxLineEdit )->setText( QString::number( shader.maximumValue() ) ); + whileBlocking( mScalarColorRampShaderWidget )->setFromShader( shader ); + } +} + +double QgsMeshRendererScalarSettingsWidget::lineEditValue( const QLineEdit *lineEdit ) const +{ + if ( lineEdit->text().isEmpty() ) + { + return std::numeric_limits::quiet_NaN(); + } + + return lineEdit->text().toDouble(); +} + +void QgsMeshRendererScalarSettingsWidget::minMaxChanged() +{ + double min = lineEditValue( mScalarMinLineEdit ); + double max = lineEditValue( mScalarMaxLineEdit ); + mScalarColorRampShaderWidget->setMinMax( min, max ); +} + +void QgsMeshRendererScalarSettingsWidget::minMaxEdited() +{ + double min = lineEditValue( mScalarMinLineEdit ); + double max = lineEditValue( mScalarMaxLineEdit ); + mScalarColorRampShaderWidget->setMinMaxAndClassify( min, max ); +} + +void QgsMeshRendererScalarSettingsWidget::recalculateMinMaxButtonClicked() +{ + double min, max; + calcMinMax( mActiveDataset, min, max ); + whileBlocking( mScalarMinLineEdit )->setText( QString::number( min ) ); + whileBlocking( mScalarMaxLineEdit )->setText( QString::number( max ) ); + mScalarColorRampShaderWidget->setMinMaxAndClassify( min, max ); +} + +void QgsMeshRendererScalarSettingsWidget::setActiveDataset( int activeDataset ) +{ + mActiveDataset = activeDataset; +} + +void QgsMeshRendererScalarSettingsWidget::calcMinMax( int datasetIndex, double &min, double &max ) const +{ + if ( !mMeshLayer ) + return; + + if ( !mMeshLayer->dataProvider() ) + return; + + const QgsMeshDatasetMetadata metadata = mMeshLayer->dataProvider()->datasetMetadata( datasetIndex ); + bool scalarDataOnVertices = metadata.isOnVertices(); + int count; + if ( scalarDataOnVertices ) + count = mMeshLayer->dataProvider()->vertexCount(); + else + count = mMeshLayer->dataProvider()->faceCount(); + + bool myFirstIterationFlag = true; + for ( int i = 0; i < count; ++i ) + { + double myValue = mMeshLayer->dataProvider()->datasetValue( datasetIndex, i ).scalar(); + if ( std::isnan( myValue ) ) continue; // NULL + if ( myFirstIterationFlag ) + { + myFirstIterationFlag = false; + min = myValue; + max = myValue; + } + else + { + if ( myValue < min ) + { + min = myValue; + } + if ( myValue > max ) + { + max = myValue; + } + } + } +} diff --git a/src/app/mesh/qgsmeshrendererscalarsettingswidget.h b/src/app/mesh/qgsmeshrendererscalarsettingswidget.h new file mode 100644 index 000000000000..b62dff94d08d --- /dev/null +++ b/src/app/mesh/qgsmeshrendererscalarsettingswidget.h @@ -0,0 +1,75 @@ +/*************************************************************************** + qgsmeshrendererscalarsettingswidget.h + ------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHRENDERERSCALARSETTINGSWIDGET_H +#define QGSMESHRENDERERSCALARSETTINGSWIDGET_H + +#include "ui_qgsmeshrendererscalarsettingswidgetbase.h" +#include "qgis_app.h" +#include "qgsmeshrenderersettings.h" + +#include + +class QgsMeshLayer; + +/*! + * A widget for setup of the scalar dataset renderer settings of + * a mesh layer. The layer must be connected and an active dataset + * must be selected. + */ +class APP_EXPORT QgsMeshRendererScalarSettingsWidget : public QWidget, private Ui::QgsMeshRendererScalarSettingsWidgetBase +{ + Q_OBJECT + + public: + + /** + * A widget to hold the renderer scalar settings for a mesh layer. + * \param parent Parent object + */ + QgsMeshRendererScalarSettingsWidget( QWidget *parent = nullptr ); + ~QgsMeshRendererScalarSettingsWidget() = default; + + //! Associates mesh layer with the widget + void setLayer( QgsMeshLayer *layer ); + + //! Returns scalar settings + QgsMeshRendererScalarSettings settings() const; + + //! Synchronizes widgets state with associated mesh layer + void syncToLayer(); + + signals: + //! Mesh rendering settings changed + void widgetChanged(); + + public slots: + //! Set active scalar dataset to be used + void setActiveDataset( int activeDatase ); + + private slots: + void minMaxChanged(); + void minMaxEdited(); + void recalculateMinMaxButtonClicked(); + + private: + double lineEditValue( const QLineEdit *lineEdit ) const; + void calcMinMax( int datasetIndex, double &min, double &max ) const; + + QgsMeshLayer *mMeshLayer = nullptr; // not owned + int mActiveDataset = -1; +}; + +#endif // QGSMESHRENDERERSCALARSETTINGSWIDGET_H diff --git a/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp b/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp new file mode 100644 index 000000000000..79dc7d823fb3 --- /dev/null +++ b/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** + qgsmeshrenderervectorsettingswidget.cpp + --------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshrenderervectorsettingswidget.h" + +#include "qgis.h" +#include "qgsmeshlayer.h" +#include "qgsmessagelog.h" + + +QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidget *parent ) + : QWidget( parent ) + +{ + setupUi( this ); + + connect( mColorWidget, &QgsColorButton::colorChanged, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); + connect( mLineWidthSpinBox, QOverload::of( &QgsDoubleSpinBox::valueChanged ), + this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); + + connect( mShaftLengthComboBox, QOverload::of( &QComboBox::currentIndexChanged ), + this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); + + connect( mShaftLengthComboBox, QOverload::of( &QComboBox::currentIndexChanged ), + mShaftOptionsStackedWidget, &QStackedWidget::setCurrentIndex ); + + QVector widgets; + widgets << mMinMagLineEdit << mMaxMagLineEdit + << mHeadWidthLineEdit << mHeadLengthLineEdit + << mMinimumShaftLineEdit << mMaximumShaftLineEdit + << mScaleShaftByFactorOfLineEdit << mShaftLengthLineEdit; + + for ( auto widget : widgets ) + { + connect( widget, &QLineEdit::textChanged, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); + } +} + +void QgsMeshRendererVectorSettingsWidget::setLayer( QgsMeshLayer *layer ) +{ + if ( layer != mMeshLayer ) + { + mMeshLayer = layer; + syncToLayer(); + } +} + +QgsMeshRendererVectorSettings QgsMeshRendererVectorSettingsWidget::settings() const +{ + QgsMeshRendererVectorSettings settings; + + // basic + settings.setColor( mColorWidget->color() ); + settings.setLineWidth( mLineWidthSpinBox->value() ); + + // filter by magnitude + double val = filterValue( mMinMagLineEdit->text(), -1 ); + settings.setFilterMin( val ); + + val = filterValue( mMaxMagLineEdit->text(), -1 ); + settings.setFilterMax( val ); + + // arrow head + val = filterValue( mHeadWidthLineEdit->text(), settings.arrowHeadWidthRatio() ); + settings.setArrowHeadWidthRatio( val ); + + val = filterValue( mHeadLengthLineEdit->text(), settings.arrowHeadLengthRatio() ); + settings.setArrowHeadLengthRatio( val ); + + // shaft length + auto method = static_cast( mShaftLengthComboBox->currentIndex() ); + settings.setShaftLengthMethod( method ); + + val = filterValue( mMinimumShaftLineEdit->text(), settings.minShaftLength() ); + settings.setMinShaftLength( val ); + + val = filterValue( mMaximumShaftLineEdit->text(), settings.maxShaftLength() ); + settings.setMaxShaftLength( val ); + + val = filterValue( mScaleShaftByFactorOfLineEdit->text(), settings.scaleFactor() ); + settings.setScaleFactor( val ); + + val = filterValue( mShaftLengthLineEdit->text(), settings.fixedShaftLength() ); + settings.setFixedShaftLength( val ); + + return settings; +} + +void QgsMeshRendererVectorSettingsWidget::setActiveDataset( int activeDataset ) +{ + mActiveDataset = activeDataset; +} + +void QgsMeshRendererVectorSettingsWidget::syncToLayer( ) +{ + if ( !mMeshLayer ) + return; + + QgsMeshRendererVectorSettings settings = mMeshLayer->rendererVectorSettings(); + + // basic + mColorWidget->setColor( settings.color() ); + mLineWidthSpinBox->setValue( settings.lineWidth() ); + + // filter by magnitude + if ( settings.filterMin() > 0 ) + { + mMinMagLineEdit->setText( QString::number( settings.filterMin() ) ); + } + if ( settings.filterMax() > 0 ) + { + mMaxMagLineEdit->setText( QString::number( settings.filterMax() ) ); + } + + // arrow head + mHeadWidthLineEdit->setText( QString::number( settings.arrowHeadWidthRatio() ) ); + mHeadLengthLineEdit->setText( QString::number( settings.arrowHeadLengthRatio() ) ); + + // shaft length + mShaftLengthComboBox->setCurrentIndex( settings.shaftLengthMethod() ); + + mMinimumShaftLineEdit->setText( QString::number( settings.minShaftLength() ) ); + mMaximumShaftLineEdit->setText( QString::number( settings.maxShaftLength() ) ); + mScaleShaftByFactorOfLineEdit->setText( QString::number( settings.scaleFactor() ) ); + mShaftLengthLineEdit->setText( QString::number( settings.fixedShaftLength() ) ); + +} + +double QgsMeshRendererVectorSettingsWidget::filterValue( const QString &text, double errVal ) const +{ + if ( text.isEmpty() ) + return errVal; + + bool ok; + double val = text.toDouble( &ok ); + if ( !ok ) + return errVal; + + if ( val < 0 ) + return errVal; + + return val; +} diff --git a/src/app/mesh/qgsmeshrenderervectorsettingswidget.h b/src/app/mesh/qgsmeshrenderervectorsettingswidget.h new file mode 100644 index 000000000000..d5f8a739b114 --- /dev/null +++ b/src/app/mesh/qgsmeshrenderervectorsettingswidget.h @@ -0,0 +1,75 @@ +/*************************************************************************** + qgsmeshrenderervectorsettingswidget.h + ------------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHRENDERERVECTORSETTINGSWIDGET_H +#define QGSMESHRENDERERVECTORSETTINGSWIDGET_H + +#include "ui_qgsmeshrenderervectorsettingswidgetbase.h" +#include "qgis_app.h" +#include "qgsmeshrenderersettings.h" + +#include +#include + +class QgsMeshLayer; + +/** + * A widget for setup of the vector dataset renderer settings of + * a mesh layer. The layer must be connected and an active dataset + * must be selected. + */ +class APP_EXPORT QgsMeshRendererVectorSettingsWidget : public QWidget, private Ui::QgsMeshRendererVectorSettingsWidgetBase +{ + Q_OBJECT + + public: + + /** + * A widget to hold the renderer Vector settings for a mesh layer. + * \param parent Parent object + */ + QgsMeshRendererVectorSettingsWidget( QWidget *parent = nullptr ); + ~QgsMeshRendererVectorSettingsWidget() = default; + + //! Associates mesh layer with the widget + void setLayer( QgsMeshLayer *layer ); + + //! Returns vector settings + QgsMeshRendererVectorSettings settings() const; + + //! Synchronizes widgets state with associated mesh layer + void syncToLayer(); + + signals: + //! Mesh rendering settings changed + void widgetChanged(); + + public slots: + //! Set active vector dataset to be used + void setActiveDataset( int activeDataset ); + + private: + + /** + * convert text to double, return err_val if + * text is not possible to convert or the value is negative + */ + double filterValue( const QString &text, double errVal ) const; + + QgsMeshLayer *mMeshLayer = nullptr; //not owned + int mActiveDataset = -1; +}; + +#endif // QGSMESHRENDERERVECTORSETTINGSWIDGET_H diff --git a/src/app/mesh/qgsrenderermeshpropertieswidget.cpp b/src/app/mesh/qgsrenderermeshpropertieswidget.cpp new file mode 100644 index 000000000000..7a1eb7eddcde --- /dev/null +++ b/src/app/mesh/qgsrenderermeshpropertieswidget.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + qgsrenderermeshpropertieswidget.cpp + ----------------------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsrenderermeshpropertieswidget.h" + +#include "qgis.h" +#include "qgsmapcanvas.h" +#include "qgsmeshlayer.h" +#include "qgsmessagelog.h" +#include "qgsmeshrendererscalarsettingswidget.h" +#include "qgsmeshdatasetgrouptreeview.h" +#include "qgsmeshrendereractivedatasetwidget.h" + +QgsRendererMeshPropertiesWidget::QgsRendererMeshPropertiesWidget( QgsMeshLayer *layer, QgsMapCanvas *canvas, QWidget *parent ) + : QgsMapLayerConfigWidget( layer, canvas, parent ) + , mMeshLayer( layer ) +{ + if ( !mMeshLayer ) + return; + + setupUi( this ); + + connect( mMeshLayer, + &QgsMeshLayer::dataChanged, + this, + &QgsRendererMeshPropertiesWidget::syncToLayer ); + + mMeshRendererActiveDatasetWidget->setLayer( mMeshLayer ); + mMeshRendererScalarSettingsWidget->setLayer( mMeshLayer ); + mNativeMeshSettingsWidget->setLayer( mMeshLayer, false ); + mTriangularMeshSettingsWidget->setLayer( mMeshLayer, true ); + mMeshRendererVectorSettingsWidget->setLayer( mMeshLayer ); + + connect( mMeshRendererActiveDatasetWidget, &QgsMeshRendererActiveDatasetWidget::activeScalarDatasetChanged, + mMeshRendererScalarSettingsWidget, &QgsMeshRendererScalarSettingsWidget::setActiveDataset ); + connect( mMeshRendererActiveDatasetWidget, &QgsMeshRendererActiveDatasetWidget::activeVectorDatasetChanged, + mMeshRendererVectorSettingsWidget, &QgsMeshRendererVectorSettingsWidget::setActiveDataset ); + + connect( mMeshRendererActiveDatasetWidget, &QgsMeshRendererActiveDatasetWidget::widgetChanged, this, &QgsPanelWidget::widgetChanged ); + connect( mMeshRendererScalarSettingsWidget, &QgsMeshRendererScalarSettingsWidget::widgetChanged, this, &QgsPanelWidget::widgetChanged ); + connect( mMeshRendererVectorSettingsWidget, &QgsMeshRendererVectorSettingsWidget::widgetChanged, this, &QgsPanelWidget::widgetChanged ); + connect( mNativeMeshSettingsWidget, &QgsMeshRendererMeshSettingsWidget::widgetChanged, + this, &QgsPanelWidget::widgetChanged ); + connect( mTriangularMeshSettingsWidget, &QgsMeshRendererMeshSettingsWidget::widgetChanged, + this, &QgsPanelWidget::widgetChanged ); +} + +void QgsRendererMeshPropertiesWidget::apply() +{ + if ( !mMeshLayer ) + return; + + // MESH + bool meshRenderingIsEnabled = mMeshRendererActiveDatasetWidget->isNativeMeshEnabled(); + QgsMeshRendererMeshSettings meshSettings = mNativeMeshSettingsWidget->settings(); + meshSettings.setEnabled( meshRenderingIsEnabled ); + whileBlocking( mMeshLayer )->setRendererNativeMeshSettings( meshSettings ); + + // TRIANGULAR MESH + bool triangularMeshRenderingIsEnabled = mMeshRendererActiveDatasetWidget->isTriangularMeshEnabled(); + QgsMeshRendererMeshSettings triangularMeshSettings = mTriangularMeshSettingsWidget->settings(); + triangularMeshSettings.setEnabled( triangularMeshRenderingIsEnabled ); + whileBlocking( mMeshLayer )->setRendererTriangularMeshSettings( triangularMeshSettings ); + + // SCALAR + int activeScalarDatasetIndex = mMeshRendererActiveDatasetWidget->activeScalarDataset(); + whileBlocking( mMeshLayer )->setActiveScalarDataset( activeScalarDatasetIndex ); + if ( activeScalarDatasetIndex != -1 ) + { + const QgsMeshRendererScalarSettings settings = mMeshRendererScalarSettingsWidget->settings(); + whileBlocking( mMeshLayer )->setRendererScalarSettings( settings ); + } + + // VECTOR + int activeVectorDatasetIndex = mMeshRendererActiveDatasetWidget->activeVectorDataset(); + whileBlocking( mMeshLayer )->setActiveVectorDataset( activeVectorDatasetIndex ); + if ( activeVectorDatasetIndex != -1 ) + { + const QgsMeshRendererVectorSettings settings = mMeshRendererVectorSettingsWidget->settings(); + whileBlocking( mMeshLayer )->setRendererVectorSettings( settings ); + } + + mMeshLayer->triggerRepaint(); +} + +void QgsRendererMeshPropertiesWidget::syncToLayer() +{ + mMeshRendererActiveDatasetWidget->syncToLayer(); + mMeshRendererScalarSettingsWidget->syncToLayer(); + mNativeMeshSettingsWidget->syncToLayer(); + mTriangularMeshSettingsWidget->syncToLayer(); + mMeshRendererVectorSettingsWidget->syncToLayer(); +} diff --git a/src/app/mesh/qgsrenderermeshpropertieswidget.h b/src/app/mesh/qgsrenderermeshpropertieswidget.h new file mode 100644 index 000000000000..70dad3a29438 --- /dev/null +++ b/src/app/mesh/qgsrenderermeshpropertieswidget.h @@ -0,0 +1,60 @@ +/*************************************************************************** + qgsrenderermeshpropertieswidget.h + --------------------- + begin : June 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSRENDERERMESHPROPERTIESWIDGET_H +#define QGSRENDERERMESHPROPERTIESWIDGET_H + +#include +#include + +#include "ui_qgsrenderermeshpropswidgetbase.h" + +#include "qgsmaplayerconfigwidget.h" +#include "qgis_app.h" +#include + +class QgsMeshLayer; +class QgsMapCanvas; + +/** + * Widget for renderer properties of the mesh, countours (scalars) + * and vectors data associated with the mesh layer + */ +class APP_EXPORT QgsRendererMeshPropertiesWidget : public QgsMapLayerConfigWidget, private Ui::QgsRendererMeshPropsWidgetBase +{ + Q_OBJECT + + public: + + /** + * A widget to hold the renderer properties for a mesh layer. + * \param layer The mesh layer to style + * \param canvas The canvas object used + * \param parent Parent object + */ + QgsRendererMeshPropertiesWidget( QgsMeshLayer *layer, QgsMapCanvas *canvas, QWidget *parent = nullptr ); + ~QgsRendererMeshPropertiesWidget() = default; + + public slots: + //! Applies the settings made in the dialog + void apply() override; + + //! Synchronize widgets state with associated mesh layer + void syncToLayer(); + + private: + QgsMeshLayer *mMeshLayer = nullptr; //not owned +}; + +#endif // QGSRENDERERMESHPROPERTIESWIDGET_H diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 5b04dfbed356..f0122a502437 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -230,6 +230,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsmessagebar.h" #include "qgsmessagebaritem.h" #include "qgsmeshlayer.h" +#include "qgsmeshlayerproperties.h" #include "qgsmemoryproviderutils.h" #include "qgsmimedatautils.h" #include "qgsmessagelog.h" @@ -13037,6 +13038,17 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer ) rasterLayerPropertiesDialog->deleteLater(); } ); } + else if ( mapLayer->type() == QgsMapLayer::MeshLayer ) + { + QgsMeshLayerProperties meshLayerPropertiesDialog( mapLayer, mMapCanvas, this ); + mMapStyleWidget->blockUpdates( true ); + if ( meshLayerPropertiesDialog.exec() ) + { + activateDeactivateLayerRelatedActions( mapLayer ); + mMapStyleWidget->updateCurrentWidgetLayer(); + } + mMapStyleWidget->blockUpdates( false ); // delete since dialog cannot be reused without updating code + } else if ( mapLayer->type() == QgsMapLayer::VectorLayer ) // VECTOR { QgsVectorLayer *vlayer = qobject_cast( mapLayer ); diff --git a/src/app/qgslayerstylingwidget.cpp b/src/app/qgslayerstylingwidget.cpp index 309b8bf1e707..bfd430cedf19 100644 --- a/src/app/qgslayerstylingwidget.cpp +++ b/src/app/qgslayerstylingwidget.cpp @@ -26,6 +26,7 @@ #include "qgsrastertransparencywidget.h" #include "qgsrendererpropertiesdialog.h" #include "qgsrendererrasterpropertieswidget.h" +#include "qgsrenderermeshpropertieswidget.h" #include "qgsrasterhistogramwidget.h" #include "qgsrasterrenderer.h" #include "qgsrasterrendererwidget.h" @@ -33,6 +34,7 @@ #include "qgsmaplayer.h" #include "qgsstyle.h" #include "qgsvectorlayer.h" +#include "qgsmeshlayer.h" #include "qgsproject.h" #include "qgsundowidget.h" #include "qgsreadwritecontext.h" @@ -189,6 +191,13 @@ void QgsLayerStylingWidget::setLayer( QgsMapLayer *layer ) histogramItem->setToolTip( tr( "Histogram" ) ); } } + else if ( layer->type() == QgsMapLayer::MeshLayer ) + { + QListWidgetItem *symbolItem = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/symbology.svg" ) ), QString() ); + symbolItem->setData( Qt::UserRole, Symbology ); + symbolItem->setToolTip( tr( "Symbology" ) ); + mOptionsListWidget->addItem( symbolItem ); + } Q_FOREACH ( QgsMapLayerConfigWidgetFactory *factory, mPageFactories ) { @@ -335,7 +344,10 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() mVector3DWidget = widget; } #endif - + else if ( QgsRendererMeshPropertiesWidget *widget = qobject_cast( current ) ) + { + mMeshStyleWidget = widget; + } } mWidgetStack->clear(); @@ -481,6 +493,24 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() break; } } + else if ( mCurrentLayer->type() == QgsMapLayer::MeshLayer ) + { + QgsMeshLayer *meshLayer = qobject_cast( mCurrentLayer ); + switch ( row ) + { + case 0: // Style + { + mMeshStyleWidget = new QgsRendererMeshPropertiesWidget( meshLayer, mMapCanvas, mWidgetStack ); + + mMeshStyleWidget->setDockMode( true ); + connect( mMeshStyleWidget, &QgsPanelWidget::widgetChanged, this, &QgsLayerStylingWidget::autoApply ); + mWidgetStack->setMainPanel( mMeshStyleWidget ); + break; + } + default: + break; + } + } else { mStackedWidget->setCurrentIndex( mNotSupportedPage ); @@ -591,5 +621,5 @@ QgsMapLayerConfigWidget *QgsLayerStyleManagerWidgetFactory::createWidget( QgsMap bool QgsLayerStyleManagerWidgetFactory::supportsLayer( QgsMapLayer *layer ) const { - return ( layer->type() == QgsMapLayer::VectorLayer || layer->type() == QgsMapLayer::RasterLayer ); + return ( layer->type() == QgsMapLayer::VectorLayer || layer->type() == QgsMapLayer::RasterLayer || layer->type() == QgsMapLayer::MeshLayer ); } diff --git a/src/app/qgslayerstylingwidget.h b/src/app/qgslayerstylingwidget.h index 3903349be4cb..dd07b6dfc157 100644 --- a/src/app/qgslayerstylingwidget.h +++ b/src/app/qgslayerstylingwidget.h @@ -36,6 +36,7 @@ class QgsMapLayer; class QgsMapCanvas; class QgsRendererPropertiesDialog; class QgsRendererRasterPropertiesWidget; +class QgsRendererMeshPropertiesWidget; class QgsUndoWidget; class QgsRasterHistogramWidget; class QgsMapLayerStyleManagerWidget; @@ -140,6 +141,7 @@ class APP_EXPORT QgsLayerStylingWidget : public QWidget, private Ui::QgsLayerSty QgsVectorLayer3DRendererWidget *mVector3DWidget = nullptr; #endif QgsRendererRasterPropertiesWidget *mRasterStyleWidget = nullptr; + QgsRendererMeshPropertiesWidget *mMeshStyleWidget = nullptr; QList mPageFactories; QMap mUserPages; QgsLayerStyleManagerWidgetFactory *mStyleManagerFactory = nullptr; diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h index 8d629f2db035..fbba5842bd1d 100644 --- a/src/core/mesh/qgsmeshdataprovider.h +++ b/src/core/mesh/qgsmeshdataprovider.h @@ -215,6 +215,8 @@ class CORE_EXPORT QgsMeshDatasetSourceInterface SIP_ABSTRACT /** * \brief Associate dataset with the mesh + * + * emits dataChanged when successful */ virtual bool addDataset( const QString &uri ) = 0; diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index bf99b9347638..eda9c4276d10 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -338,5 +338,7 @@ bool QgsMeshLayer::setDataProvider( QString const &provider, const QgsDataProvid mDataSource = mDataSource + QStringLiteral( "&uid=%1" ).arg( QUuid::createUuid().toString() ); } + connect( mDataProvider, &QgsMeshDataProvider::dataChanged, this, &QgsMeshLayer::dataChanged ); + return true; } // QgsMeshLayer:: setDataProvider diff --git a/src/core/mesh/qgsmeshlayerrenderer.cpp b/src/core/mesh/qgsmeshlayerrenderer.cpp index c9b7104181b4..fa5846c1ca96 100644 --- a/src/core/mesh/qgsmeshlayerrenderer.cpp +++ b/src/core/mesh/qgsmeshlayerrenderer.cpp @@ -31,8 +31,8 @@ #include "qgsmeshlayerinterpolator.h" #include "qgsmeshvectorrenderer.h" #include "qgsfillsymbollayer.h" - - +#include "qgssettings.h" +#include "qgsstyle.h" QgsMeshLayerRenderer::QgsMeshLayerRenderer( QgsMeshLayer *layer, QgsRenderContext &context ) : QgsMapLayerRenderer( layer->id() ) @@ -55,9 +55,29 @@ QgsMeshLayerRenderer::QgsMeshLayerRenderer( QgsMeshLayer *layer, QgsRenderContex copyScalarDatasetValues( layer ); copyVectorDatasetValues( layer ); + assignDefaultScalarShader(); + calculateOutputSize(); } +void QgsMeshLayerRenderer::assignDefaultScalarShader( ) +{ + if ( mScalarDatasetValues.isEmpty() || mRendererScalarSettings.isEnabled() ) + return; // no need for default shader, either rendering is off or we already have some shader + + auto bounds = std::minmax_element( mScalarDatasetValues.constBegin(), mScalarDatasetValues.constEnd() ); + double vMin = *bounds.first; + double vMax = *bounds.second; + + QgsSettings settings; + QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString(); + std::unique_ptr colorRamp( QgsStyle::defaultStyle()->colorRamp( defaultPalette ) ); + QgsColorRampShader fcn( vMin, vMax, colorRamp.release() ); + fcn.classifyColorRamp( 5, -1, QgsRectangle(), nullptr ); + + mRendererScalarSettings.setColorRampShader( fcn ); +} + QgsFeedback *QgsMeshLayerRenderer::feedback() const { return mFeedback.get(); @@ -203,29 +223,20 @@ void QgsMeshLayerRenderer::renderMesh( const std::unique_ptr &symbol, void QgsMeshLayerRenderer::renderScalarDataset() { if ( mScalarDatasetValues.isEmpty() ) - return; - - // do not render if magnitude is outside of the filtered range (if filtering is enabled) - double vMin = mRendererScalarSettings.minValue(); - if ( std::isnan( vMin ) ) - vMin = *std::min_element( mScalarDatasetValues.constBegin(), mScalarDatasetValues.constEnd() ); + return; // activeScalarDataset == NO_ACTIVE_MESH_DATASET + if ( !mRendererScalarSettings.isEnabled() ) + return; // no shader - double vMax = mRendererScalarSettings.maxValue(); - if ( std::isnan( vMax ) ) - vMax = *std::max_element( mScalarDatasetValues.constBegin(), mScalarDatasetValues.constEnd() ); - - QList lst; - lst << QgsColorRampShader::ColorRampItem( vMin, mRendererScalarSettings.minColor(), QString::number( vMin ) ); - lst << QgsColorRampShader::ColorRampItem( vMax, mRendererScalarSettings.maxColor(), QString::number( vMax ) ); - - QgsColorRampShader *fcn = new QgsColorRampShader( vMin, vMax ); - fcn->setColorRampItemList( lst ); - QgsRasterShader *sh = new QgsRasterShader( vMin, vMax ); + QgsColorRampShader *fcn = new QgsColorRampShader( mRendererScalarSettings.colorRampShader() ); + QgsRasterShader *sh = new QgsRasterShader(); sh->setRasterShaderFunction( fcn ); // takes ownership of fcn QgsMeshLayerInterpolator interpolator( mTriangularMesh, mScalarDatasetValues, mScalarDataOnVertices, mContext, mOutputSize ); - QgsSingleBandPseudoColorRenderer r( &interpolator, 0, sh ); // takes ownership of sh - std::unique_ptr bl( r.block( 0, mContext.extent(), mOutputSize.width(), mOutputSize.height(), mFeedback.get() ) ); + QgsSingleBandPseudoColorRenderer renderer( &interpolator, 0, sh ); // takes ownership of sh + renderer.setClassificationMin( fcn->minimumValue() ); + renderer.setClassificationMax( fcn->maximumValue() ); + + std::unique_ptr bl( renderer.block( 0, mContext.extent(), mOutputSize.width(), mOutputSize.height(), mFeedback.get() ) ); QImage img = bl->image(); mContext.painter()->drawImage( 0, 0, img ); diff --git a/src/core/mesh/qgsmeshlayerrenderer.h b/src/core/mesh/qgsmeshlayerrenderer.h index f38f10ed39d2..a2ade8a076d7 100644 --- a/src/core/mesh/qgsmeshlayerrenderer.h +++ b/src/core/mesh/qgsmeshlayerrenderer.h @@ -46,7 +46,6 @@ class QgsMeshLayerRendererFeedback : public QgsRasterBlockFeedback ///@endcond - /** * \ingroup core * Implementation of threaded rendering for mesh layers. @@ -69,6 +68,8 @@ class QgsMeshLayerRenderer : public QgsMapLayerRenderer void renderVectorDataset(); void copyScalarDatasetValues( QgsMeshLayer *layer ); void copyVectorDatasetValues( QgsMeshLayer *layer ); + void assignDefaultScalarShader( ); + void createMeshSymbol( std::unique_ptr &symbol, const QgsMeshRendererMeshSettings &settings ); void calculateOutputSize(); diff --git a/src/core/mesh/qgsmeshmemorydataprovider.cpp b/src/core/mesh/qgsmeshmemorydataprovider.cpp index 233e51ac5c25..2bf92d1c1bf9 100644 --- a/src/core/mesh/qgsmeshmemorydataprovider.cpp +++ b/src/core/mesh/qgsmeshmemorydataprovider.cpp @@ -307,6 +307,9 @@ bool QgsMeshMemoryDataProvider::addDataset( const QString &uri ) mDatasets.push_back( ds ); + if ( ds.valid ) + emit dataChanged(); + return ds.valid; } diff --git a/src/core/mesh/qgsmeshrenderersettings.cpp b/src/core/mesh/qgsmeshrenderersettings.cpp index b76e2da76dc1..1f9988efd881 100644 --- a/src/core/mesh/qgsmeshrenderersettings.cpp +++ b/src/core/mesh/qgsmeshrenderersettings.cpp @@ -47,45 +47,23 @@ void QgsMeshRendererMeshSettings::setColor( const QColor &color ) mColor = color; } -QColor QgsMeshRendererScalarSettings::maxColor() const -{ - return mMaxColor; -} - -void QgsMeshRendererScalarSettings::setMaxColor( const QColor &maxColor ) -{ - mMaxColor = maxColor; -} - -QColor QgsMeshRendererScalarSettings::minColor() const -{ - return mMinColor; -} -void QgsMeshRendererScalarSettings::setMinColor( const QColor &minColor ) +QgsColorRampShader QgsMeshRendererScalarSettings::colorRampShader() const { - mMinColor = minColor; -} + return mColorRampShader; -double QgsMeshRendererScalarSettings::minValue() const -{ - return mMinValue; } -void QgsMeshRendererScalarSettings::setMinValue( double minValue ) +void QgsMeshRendererScalarSettings::setColorRampShader( const QgsColorRampShader &shader ) { - mMinValue = minValue; + mColorRampShader = shader; } -double QgsMeshRendererScalarSettings::maxValue() const +bool QgsMeshRendererScalarSettings::isEnabled() const { - return mMaxValue; + return !mColorRampShader.isEmpty(); } -void QgsMeshRendererScalarSettings::setMaxValue( double maxValue ) -{ - mMaxValue = maxValue; -} double QgsMeshRendererVectorSettings::lineWidth() const { diff --git a/src/core/mesh/qgsmeshrenderersettings.h b/src/core/mesh/qgsmeshrenderersettings.h index e8a97ffbee40..b916fcde4c8e 100644 --- a/src/core/mesh/qgsmeshrenderersettings.h +++ b/src/core/mesh/qgsmeshrenderersettings.h @@ -23,6 +23,8 @@ #include "qgis_core.h" #include "qgis.h" +#include "qgscolorrampshader.h" + /** * \ingroup core @@ -69,49 +71,15 @@ class CORE_EXPORT QgsMeshRendererMeshSettings class CORE_EXPORT QgsMeshRendererScalarSettings { public: - //! Returns color representing maximum scalar value in the dataset - QColor maxColor() const; - //! Sets color representing maximum scalar value in the dataset - void setMaxColor( const QColor &maxColor ); - - //! Returns color representing minimum scalar value in the dataset - QColor minColor() const; - //! Sets color representing maximum scalar value in the dataset - void setMinColor( const QColor &minColor ); - - /** - * Returns min scalar value that represents minColor() - * - * if set to numerical_limits::quiet_NaN(), value for minColor() is - * taken from minimum value of active scalar dataset - */ - double minValue() const; - - /** - * Sets min scalar value that represents minColor() - * \see QgsMeshRendererScalarSettings::minValue() - */ - void setMinValue( double minValue ); - - /** - * Returns max scalar value that represents maxColor() - * - * if set to numerical_limits::quiet_NaN(), value for maxColor() is - * taken from maximum value of active scalar dataset - */ - double maxValue() const; - - /** - * Sets min scalar value that represents minColor() - * \see QgsMeshRendererScalarSettings::maxValue() - */ - void setMaxValue( double maxValue ); + //! Returns color ramp shader function + QgsColorRampShader colorRampShader() const; + //! Sets color ramp shader function + void setColorRampShader( const QgsColorRampShader &shader ); + //! Returns whether color ramp has any items assigned + bool isEnabled() const; private: - QColor mMaxColor = QColor::fromRgb( 255, 0, 0 ); - QColor mMinColor = QColor::fromRgb( 0, 0, 255 ); - double mMaxValue = std::numeric_limits::quiet_NaN(); //disabled - double mMinValue = std::numeric_limits::quiet_NaN(); //disabled + QgsColorRampShader mColorRampShader; }; /** diff --git a/src/core/mesh/qgsmeshvectorrenderer.cpp b/src/core/mesh/qgsmeshvectorrenderer.cpp index 4607a6d75bb7..d7df93d8efe4 100644 --- a/src/core/mesh/qgsmeshvectorrenderer.cpp +++ b/src/core/mesh/qgsmeshvectorrenderer.cpp @@ -55,15 +55,7 @@ QgsMeshVectorRenderer::QgsMeshVectorRenderer( const QgsTriangularMesh &m, , mDataOnVertices( dataIsOnVertices ) , mOutputSize( size ) { - auto bounds = std::minmax_element( mDatasetValuesX.constBegin(), mDatasetValuesX.constEnd() ); - mMinX = *bounds.first; - mMaxX = *bounds.second; - - bounds = std::minmax_element( mDatasetValuesY.constBegin(), mDatasetValuesY.constEnd() ); - mMinY = *bounds.first; - mMaxY = *bounds.second; - - bounds = std::minmax_element( mDatasetValuesMag.constBegin(), mDatasetValuesMag.constEnd() ); + auto bounds = std::minmax_element( mDatasetValuesMag.constBegin(), mDatasetValuesMag.constEnd() ); mMinMag = *bounds.first; mMaxMag = *bounds.second; } diff --git a/src/core/mesh/qgsmeshvectorrenderer.h b/src/core/mesh/qgsmeshvectorrenderer.h index f6306359ab7c..a5192d742c84 100644 --- a/src/core/mesh/qgsmeshvectorrenderer.h +++ b/src/core/mesh/qgsmeshvectorrenderer.h @@ -84,10 +84,6 @@ class QgsMeshVectorRenderer const QVector &mDatasetValuesX; const QVector &mDatasetValuesY; const QVector &mDatasetValuesMag; //magnitudes - double mMinX = 0.0; - double mMaxX = 0.0; - double mMinY = 0.0; - double mMaxY = 0.0; double mMinMag = 0.0; double mMaxMag = 0.0; QgsRenderContext &mContext; diff --git a/src/core/raster/qgscolorrampshader.cpp b/src/core/raster/qgscolorrampshader.cpp index ac4d5a7cbc54..eb17811cbf20 100644 --- a/src/core/raster/qgscolorrampshader.cpp +++ b/src/core/raster/qgscolorrampshader.cpp @@ -50,12 +50,18 @@ QgsColorRampShader::QgsColorRampShader( const QgsColorRampShader &other ) , mLUTInitialized( other.mLUTInitialized ) , mClip( other.mClip ) { - mSourceColorRamp.reset( other.sourceColorRamp()->clone() ); + if ( other.sourceColorRamp() ) + mSourceColorRamp.reset( other.sourceColorRamp()->clone() ); + mColorRampItemList = other.mColorRampItemList; } QgsColorRampShader &QgsColorRampShader::operator=( const QgsColorRampShader &other ) { - mSourceColorRamp.reset( other.sourceColorRamp()->clone() ); + if ( other.sourceColorRamp() ) + mSourceColorRamp.reset( other.sourceColorRamp()->clone() ); + else + mSourceColorRamp.reset(); + mColorRampType = other.mColorRampType; mClassificationMode = other.mClassificationMode; mLUT = other.mLUT; @@ -63,6 +69,7 @@ QgsColorRampShader &QgsColorRampShader::operator=( const QgsColorRampShader &oth mLUTFactor = other.mLUTFactor; mLUTInitialized = other.mLUTInitialized; mClip = other.mClip; + mColorRampItemList = other.mColorRampItemList; return *this; } @@ -93,6 +100,11 @@ void QgsColorRampShader::setColorRampType( QgsColorRampShader::Type colorRampTyp mColorRampType = colorRampType; } +bool QgsColorRampShader::isEmpty() const +{ + return mColorRampItemList.isEmpty(); +} + void QgsColorRampShader::setColorRampType( const QString &type ) { if ( type == QLatin1String( "INTERPOLATED" ) ) diff --git a/src/core/raster/qgscolorrampshader.h b/src/core/raster/qgscolorrampshader.h index 3aa047eafd1b..50e271b017c8 100644 --- a/src/core/raster/qgscolorrampshader.h +++ b/src/core/raster/qgscolorrampshader.h @@ -116,6 +116,12 @@ class CORE_EXPORT QgsColorRampShader : public QgsRasterShaderFunction //! Sets the color ramp type void setColorRampType( QgsColorRampShader::Type colorRampType ); + /** + * Whether the color ramp contains any items + * \since QGIS 3.4 + */ + bool isEmpty() const; + /** * Gets the source color ramp * \see setSourceColorRamp() diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 957c67f1b2c3..db376c16c3e7 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,4 +1,5 @@ SET(QGIS_GUI_SRCS + raster/qgscolorrampshaderwidget.cpp raster/qgsmultibandcolorrendererwidget.cpp raster/qgspalettedrendererwidget.cpp raster/qgsrasterbandcombobox.cpp @@ -541,6 +542,7 @@ SET(QGIS_GUI_MOC_HDRS ogr/qgsnewogrconnection.h ogr/qgsvectorlayersaveasdialog.h + raster/qgscolorrampshaderwidget.h raster/qgsmultibandcolorrendererwidget.h raster/qgspalettedrendererwidget.h raster/qgsrasterbandcombobox.h diff --git a/src/gui/raster/qgscolorrampshaderwidget.cpp b/src/gui/raster/qgscolorrampshaderwidget.cpp new file mode 100644 index 000000000000..f28b6446b6b4 --- /dev/null +++ b/src/gui/raster/qgscolorrampshaderwidget.cpp @@ -0,0 +1,778 @@ +/*************************************************************************** + qgscolorrampshaderwidget.cpp + ---------------------------- + begin : Jun 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsrasterdataprovider.h" + +#include "qgscolorrampshaderwidget.h" +#include "qgssinglebandpseudocolorrenderer.h" +#include "qgsrasterlayer.h" +#include "qgsrasterdataprovider.h" +#include "qgsrastershader.h" +#include "qgsrasterminmaxwidget.h" +#include "qgstreewidgetitem.h" +#include "qgssettings.h" +#include "qgsstyle.h" +#include "qgscolorramp.h" +#include "qgscolorrampbutton.h" +#include "qgscolordialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QgsColorRampShaderWidget::QgsColorRampShaderWidget( QWidget *parent ) + : QWidget( parent ) +{ + QgsSettings settings; + + setupUi( this ); + mLoadFromBandButton->setVisible( false ); // only for raster version + + connect( mAddEntryButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mAddEntryButton_clicked ); + connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mDeleteEntryButton_clicked ); + connect( mLoadFromBandButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mLoadFromBandButton_clicked ); + connect( mLoadFromFileButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mLoadFromFileButton_clicked ); + connect( mExportToFileButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mExportToFileButton_clicked ); + connect( mUnitLineEdit, &QLineEdit::textEdited, this, &QgsColorRampShaderWidget::mUnitLineEdit_textEdited ); + connect( mColormapTreeWidget, &QTreeWidget::itemDoubleClicked, this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked ); + connect( mColorInterpolationComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged ); + connect( mClassificationModeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged ); + + contextMenu = new QMenu( tr( "Options" ), this ); + contextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) ); + contextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) ); + + mColormapTreeWidget->setColumnWidth( ColorColumn, 50 ); + mColormapTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu ); + mColormapTreeWidget->setSelectionMode( QAbstractItemView::ExtendedSelection ); + connect( mColormapTreeWidget, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); } + ); + + QString defaultPalette = settings.value( QStringLiteral( "Raster/defaultPalette" ), "" ).toString(); + btnColorRamp->setColorRampFromName( defaultPalette ); + + mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::Discrete ); + mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::Interpolated ); + mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::Exact ); + mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) ); + + mClassificationModeComboBox->addItem( tr( "Continuous" ), QgsColorRampShader::Continuous ); + mClassificationModeComboBox->addItem( tr( "Equal Interval" ), QgsColorRampShader::EqualInterval ); + // Quantile added only on demand + mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::Continuous ) ); + + mNumberOfEntriesSpinBox->setValue( 5 ); // some default + + mClassificationModeComboBox_currentIndexChanged( 0 ); + + resetClassifyButton(); + + connect( mClassificationModeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::classify ); + connect( mClassifyButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::applyColorRamp ); + connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsColorRampShaderWidget::applyColorRamp ); + connect( mNumberOfEntriesSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsColorRampShaderWidget::classify ); +} + +void QgsColorRampShaderWidget::initForUseWithRasterLayer() +{ + Q_ASSERT( mClassificationModeComboBox->findData( QgsColorRampShader::Quantile < 0 ) ); + mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile ); + mLoadFromBandButton->setVisible( bool( mRasterDataProvider ) ); // only for raster version +} + +void QgsColorRampShaderWidget::setRasterBand( QgsRasterDataProvider *dp, + int band, + const QgsRectangle &extent ) +{ + mRasterDataProvider = dp; + mBand = band; + mExtent = extent; +} + +QgsColorRampShader QgsColorRampShaderWidget::shader() const +{ + QgsColorRampShader colorRampShader( mMin, mMax ); + colorRampShader.setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) ); + colorRampShader.setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) ); + colorRampShader.setClip( mClipCheckBox->isChecked() ); + + //iterate through mColormapTreeWidget and set colormap info of layer + QList colorRampItems; + int topLevelItemCount = mColormapTreeWidget->topLevelItemCount(); + QTreeWidgetItem *currentItem = nullptr; + for ( int i = 0; i < topLevelItemCount; ++i ) + { + currentItem = mColormapTreeWidget->topLevelItem( i ); + if ( !currentItem ) + { + continue; + } + QgsColorRampShader::ColorRampItem newColorRampItem; + newColorRampItem.value = currentItem->text( ValueColumn ).toDouble(); + newColorRampItem.color = currentItem->background( ColorColumn ).color(); + newColorRampItem.label = currentItem->text( LabelColumn ); + colorRampItems.append( newColorRampItem ); + } + // sort the shader items + std::sort( colorRampItems.begin(), colorRampItems.end() ); + colorRampShader.setColorRampItemList( colorRampItems ); + + if ( !btnColorRamp->isNull() ) + { + colorRampShader.setSourceColorRamp( btnColorRamp->colorRamp() ); + } + return colorRampShader; +} + +void QgsColorRampShaderWidget::autoLabel() +{ + QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ); + bool discrete = interpolation == QgsColorRampShader::Discrete; + QString unit = mUnitLineEdit->text(); + QString label; + int topLevelItemCount = mColormapTreeWidget->topLevelItemCount(); + QTreeWidgetItem *currentItem = nullptr; + for ( int i = 0; i < topLevelItemCount; ++i ) + { + currentItem = mColormapTreeWidget->topLevelItem( i ); + //If the item is null or does not have a pixel values set, skip + if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() ) + { + continue; + } + + if ( discrete ) + { + if ( i == 0 ) + { + label = "<= " + currentItem->text( ValueColumn ) + unit; + } + else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits::infinity() ) + { + label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit; + } + else + { + label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit; + } + } + else + { + label = currentItem->text( ValueColumn ) + unit; + } + + if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) ) + { + currentItem->setText( LabelColumn, label ); + currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) ); + } + } +} + +void QgsColorRampShaderWidget::setUnitFromLabels() +{ + QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ); + bool discrete = interpolation == QgsColorRampShader::Discrete; + QStringList allSuffixes; + QString label; + int topLevelItemCount = mColormapTreeWidget->topLevelItemCount(); + QTreeWidgetItem *currentItem = nullptr; + for ( int i = 0; i < topLevelItemCount; ++i ) + { + currentItem = mColormapTreeWidget->topLevelItem( i ); + //If the item is null or does not have a pixel values set, skip + if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() ) + { + continue; + } + + if ( discrete ) + { + if ( i == 0 ) + { + label = "<= " + currentItem->text( ValueColumn ); + } + else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits::infinity() ) + { + label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ); + } + else + { + label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ); + } + } + else + { + label = currentItem->text( ValueColumn ); + } + + if ( currentItem->text( LabelColumn ).startsWith( label ) ) + { + allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) ); + } + } + // find most common suffix + QStringList suffixes = QStringList( allSuffixes ); + suffixes.removeDuplicates(); + int max = 0; + QString unit; + for ( int i = 0; i < suffixes.count(); ++i ) + { + int n = allSuffixes.count( suffixes[i] ); + if ( n > max ) + { + max = n; + unit = suffixes[i]; + } + } + // Set this suffix as unit if at least used twice + if ( max >= 2 ) + { + mUnitLineEdit->setText( unit ); + } + autoLabel(); +} + + +void QgsColorRampShaderWidget::mAddEntryButton_clicked() +{ + QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget ); + newItem->setText( ValueColumn, QStringLiteral( "0" ) ); + newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) ); + newItem->setText( LabelColumn, QString() ); + newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable ); + connect( newItem, &QgsTreeWidgetItemObject::itemEdited, + this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited ); + mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder ); + autoLabel(); + + loadMinMaxFromTree(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::mDeleteEntryButton_clicked() +{ + QList itemList; + itemList = mColormapTreeWidget->selectedItems(); + if ( itemList.isEmpty() ) + { + return; + } + + Q_FOREACH ( QTreeWidgetItem *item, itemList ) + { + delete item; + } + + loadMinMaxFromTree(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::classify() +{ + std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() ); + if ( !ramp || std::isnan( mMin ) || std::isnan( mMax ) ) + { + return; + } + + std::unique_ptr< QgsColorRampShader > colorRampShader( new QgsColorRampShader( + mMin, mMax, + ramp.release(), + static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ), + static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) ) + ); + + // only for Quantile we need band and provider and extent + colorRampShader->classifyColorRamp( mNumberOfEntriesSpinBox->value(), + mBand, + mExtent, + mRasterDataProvider ); + colorRampShader->setClip( mClipCheckBox->isChecked() ); + + + mColormapTreeWidget->clear(); + + const QList colorRampItemList = colorRampShader->colorRampItemList(); + QList::const_iterator it = colorRampItemList.constBegin(); + for ( ; it != colorRampItemList.end(); ++it ) + { + QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget ); + newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) ); + newItem->setBackground( ColorColumn, QBrush( it->color ) ); + newItem->setText( LabelColumn, it->label ); + newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable ); + connect( newItem, &QgsTreeWidgetItemObject::itemEdited, + this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited ); + } + mClipCheckBox->setChecked( colorRampShader->clip() ); + + + autoLabel(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged( int index ) +{ + QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() ); + mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous ); + emit classificationModeChanged( mode ); + +} + +void QgsColorRampShaderWidget::applyColorRamp() +{ + std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() ); + if ( !ramp ) + { + return; + } + + if ( !btnColorRamp->colorRampName().isEmpty() ) + { + // Remember last used color ramp + QgsSettings settings; + settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() ); + } + + bool enableContinuous = ( ramp->count() > 0 ); + mClassificationModeComboBox->setEnabled( enableContinuous ); + if ( !enableContinuous ) + { + mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) ); + } + + classify(); +} + +void QgsColorRampShaderWidget::populateColormapTreeWidget( const QList &colorRampItems ) +{ + mColormapTreeWidget->clear(); + QList::const_iterator it = colorRampItems.constBegin(); + for ( ; it != colorRampItems.constEnd(); ++it ) + { + QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget ); + newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) ); + newItem->setBackground( ColorColumn, QBrush( it->color ) ); + newItem->setText( LabelColumn, it->label ); + newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable ); + connect( newItem, &QgsTreeWidgetItemObject::itemEdited, + this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited ); + } + setUnitFromLabels(); + + autoLabel(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::mLoadFromBandButton_clicked() +{ + if ( !mRasterDataProvider ) + return; + + QList colorRampList = mRasterDataProvider->colorTable( mBand ); + if ( !colorRampList.isEmpty() ) + { + populateColormapTreeWidget( colorRampList ); + mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) ); + } + else + { + QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( mBand ) ); + } + + loadMinMaxFromTree(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::mLoadFromFileButton_clicked() +{ + int lineCounter = 0; + bool importError = false; + QString badLines; + QgsSettings settings; + QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString(); + QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) ); + QFile inputFile( fileName ); + if ( inputFile.open( QFile::ReadOnly ) ) + { + //clear the current tree + mColormapTreeWidget->clear(); + + QTextStream inputStream( &inputFile ); + QString inputLine; + QStringList inputStringComponents; + QList colorRampItems; + + //read through the input looking for valid data + while ( !inputStream.atEnd() ) + { + lineCounter++; + inputLine = inputStream.readLine(); + if ( !inputLine.isEmpty() ) + { + if ( !inputLine.simplified().startsWith( '#' ) ) + { + if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) ) + { + inputStringComponents = inputLine.split( ':' ); + if ( inputStringComponents.size() == 2 ) + { + if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 ) + { + mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) ); + } + else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 ) + { + mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) ); + } + else + { + mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) ); + } + } + else + { + importError = true; + badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n"; + } + } + else + { + inputStringComponents = inputLine.split( ',' ); + if ( inputStringComponents.size() == 6 ) + { + QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(), + QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(), + inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ), + inputStringComponents[5] ); + colorRampItems.push_back( currentItem ); + } + else + { + importError = true; + badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n"; + } + } + } + } + lineCounter++; + } + populateColormapTreeWidget( colorRampItems ); + + QFileInfo fileInfo( fileName ); + settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() ); + + if ( importError ) + { + QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + badLines ); + } + } + else if ( !fileName.isEmpty() ) + { + QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) ); + } + + loadMinMaxFromTree(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::mExportToFileButton_clicked() +{ + QgsSettings settings; + QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString(); + QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) ); + if ( !fileName.isEmpty() ) + { + if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) ) + { + fileName = fileName + ".txt"; + } + + QFile outputFile( fileName ); + if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) ) + { + QTextStream outputStream( &outputFile ); + outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n'; + outputStream << "INTERPOLATION:"; + QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ); + switch ( interpolation ) + { + case QgsColorRampShader::Interpolated: + outputStream << "INTERPOLATED\n"; + break; + case QgsColorRampShader::Discrete: + outputStream << "DISCRETE\n"; + break; + case QgsColorRampShader::Exact: + outputStream << "EXACT\n"; + break; + } + + int topLevelItemCount = mColormapTreeWidget->topLevelItemCount(); + QTreeWidgetItem *currentItem = nullptr; + QColor color; + for ( int i = 0; i < topLevelItemCount; ++i ) + { + currentItem = mColormapTreeWidget->topLevelItem( i ); + if ( !currentItem ) + { + continue; + } + color = currentItem->background( ColorColumn ).color(); + outputStream << currentItem->text( ValueColumn ).toDouble() << ','; + outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ','; + if ( currentItem->text( LabelColumn ).isEmpty() ) + { + outputStream << "Color entry " << i + 1 << '\n'; + } + else + { + outputStream << currentItem->text( LabelColumn ) << '\n'; + } + } + outputStream.flush(); + outputFile.close(); + + QFileInfo fileInfo( fileName ); + settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() ); + } + else + { + QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) ); + } + } +} + +void QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column ) +{ + if ( !item ) + { + return; + } + + if ( column == ColorColumn ) + { + item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + QColor newColor = QgsColorDialog::getColor( item->background( column ).color(), this, QStringLiteral( "Change Color" ), true ); + if ( newColor.isValid() ) + { + item->setBackground( ColorColumn, QBrush( newColor ) ); + loadMinMaxFromTree(); + emit widgetChanged(); + } + } + else + { + if ( column == LabelColumn ) + { + // Set text color to default black, which signifies a manually edited label + item->setForeground( LabelColumn, QBrush() ); + } + item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable ); + } +} + +void QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column ) +{ + Q_UNUSED( item ); + + if ( column == ValueColumn ) + { + mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder ); + autoLabel(); + + loadMinMaxFromTree(); + + emit widgetChanged(); + } + else if ( column == LabelColumn ) + { + // call autoLabel to fill when empty or gray out when same as autoLabel + autoLabel(); + emit widgetChanged(); + } +} + +void QgsColorRampShaderWidget::setFromShader( const QgsColorRampShader &colorRampShader ) +{ + if ( colorRampShader.sourceColorRamp() ) + { + btnColorRamp->setColorRamp( colorRampShader.sourceColorRamp() ); + } + else + { + QgsSettings settings; + QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString(); + btnColorRamp->setColorRampFromName( defaultPalette ); + } + + mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader.colorRampType() ) ); + + const QList colorRampItemList = colorRampShader.colorRampItemList(); + QList::const_iterator it = colorRampItemList.constBegin(); + for ( ; it != colorRampItemList.end(); ++it ) + { + QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget ); + newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) ); + newItem->setBackground( ColorColumn, QBrush( it->color ) ); + newItem->setText( LabelColumn, it->label ); + newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable ); + connect( newItem, &QgsTreeWidgetItemObject::itemEdited, + this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited ); + } + setUnitFromLabels(); + mClipCheckBox->setChecked( colorRampShader.clip() ); + mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader.classificationMode() ) ); + mNumberOfEntriesSpinBox->setValue( colorRampShader.colorRampItemList().count() ); // some default +} + +void QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged( int index ) +{ + QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() ); + + mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated ); + + QString valueLabel; + QString valueToolTip; + switch ( interpolation ) + { + case QgsColorRampShader::Interpolated: + valueLabel = tr( "Value" ); + valueToolTip = tr( "Value for color stop" ); + break; + case QgsColorRampShader::Discrete: + valueLabel = tr( "Value <=" ); + valueToolTip = tr( "Maximum value for class" ); + break; + case QgsColorRampShader::Exact: + valueLabel = tr( "Value =" ); + valueToolTip = tr( "Value for color" ); + break; + } + + QTreeWidgetItem *header = mColormapTreeWidget->headerItem(); + header->setText( ValueColumn, valueLabel ); + header->setToolTip( ValueColumn, valueToolTip ); + + autoLabel(); + emit widgetChanged(); +} + +void QgsColorRampShaderWidget::setMinMaxAndClassify( double min, double max ) +{ + if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) ) + { + mMin = min; + mMax = max; + + classify(); + } +} + +void QgsColorRampShaderWidget::setMinMax( double min, double max ) +{ + mMin = min; + mMax = max; + resetClassifyButton(); +} + +void QgsColorRampShaderWidget::loadMinMaxFromTree() +{ + QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 ); + if ( !item ) + { + return; + } + + double min = item->text( ValueColumn ).toDouble(); + item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 ); + double max = item->text( ValueColumn ).toDouble(); + + if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) ) + { + mMin = min; + mMax = max; + emit minMaxChangedFromTree( min, max ); + } +} + +void QgsColorRampShaderWidget::resetClassifyButton() +{ + mClassifyButton->setEnabled( true ); + if ( std::isnan( mMin ) || std::isnan( mMax ) || mMin >= mMax ) + { + mClassifyButton->setEnabled( false ); + } +} + +void QgsColorRampShaderWidget::changeColor() +{ + QList itemList; + itemList = mColormapTreeWidget->selectedItems(); + if ( itemList.isEmpty() ) + { + return; + } + QTreeWidgetItem *firstItem = itemList.first(); + + QColor newColor = QgsColorDialog::getColor( firstItem->background( ColorColumn ).color(), this, QStringLiteral( "Change Color" ), true ); + if ( newColor.isValid() ) + { + Q_FOREACH ( QTreeWidgetItem *item, itemList ) + { + item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + item->setBackground( ColorColumn, QBrush( newColor ) ); + } + + loadMinMaxFromTree(); + emit widgetChanged(); + } +} + +void QgsColorRampShaderWidget::changeOpacity() +{ + QList itemList; + itemList = mColormapTreeWidget->selectedItems(); + if ( itemList.isEmpty() ) + { + return; + } + QTreeWidgetItem *firstItem = itemList.first(); + + bool ok; + double oldOpacity = firstItem->background( ColorColumn ).color().alpha() / 255 * 100; + double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok ); + if ( ok ) + { + int newOpacity = static_cast( opacity / 100 * 255 ); + Q_FOREACH ( QTreeWidgetItem *item, itemList ) + { + QColor newColor = item->background( ColorColumn ).color(); + newColor.setAlpha( newOpacity ); + item->setBackground( ColorColumn, QBrush( newColor ) ); + } + + loadMinMaxFromTree(); + emit widgetChanged(); + } +} diff --git a/src/gui/raster/qgscolorrampshaderwidget.h b/src/gui/raster/qgscolorrampshaderwidget.h new file mode 100644 index 000000000000..ac3e59153ffd --- /dev/null +++ b/src/gui/raster/qgscolorrampshaderwidget.h @@ -0,0 +1,142 @@ +/*************************************************************************** + qgscolorrampshaderwidget.h + -------------------------- + begin : Jun 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSCOLORRAMPSHADERWIDGET_H +#define QGSCOLORRAMPSHADERWIDGET_H + +#include "qgis_sip.h" +#include "qgscolorrampshader.h" +#include "qgsrasterrenderer.h" +#include "ui_qgscolorrampshaderwidgetbase.h" +#include "qgis_gui.h" +#include "qgsrasterrendererwidget.h" + +class QgsRasterDataProvider; + +/** + * \ingroup gui + * \class QgsColorRampShaderWidget + * + * It has 2 ways how to use it. For raster layers, raster data provider and band is assigned and + * the Quantile classification mode can be used and the LoadFromBandButton is visible. + * + * The other mode is used to style mesh layer contours (scalar datasets) + * + * \since QGIS 3.4 + */ +class GUI_EXPORT QgsColorRampShaderWidget: public QWidget, protected Ui::QgsColorRampShaderWidgetBase +{ + + Q_OBJECT + + public: + + QgsColorRampShaderWidget( QWidget *parent = nullptr ); + + //! Allows quantile classification mode for raster layers + void initForUseWithRasterLayer(); + + //! Associates raster with the widget + void setRasterBand( QgsRasterDataProvider *dp, int band, const QgsRectangle &extent ); + + //! Sets min max and classify color tree + void setMinMaxAndClassify( double min, double max ); + + //! Sets min max + void setMinMax( double min, double max ); + + //! Returns shared function used in the renderer + QgsColorRampShader shader() const; + //! Sets widget state from the color ramp shader + void setFromShader( const QgsColorRampShader &colorRampShader ); + + signals: + //! color ramp tree has changed + void minMaxChangedFromTree( double min, double max ); + + //! widget changed + void widgetChanged(); + + //! classification mode changed + void classificationModeChanged( QgsColorRampShader::ClassificationMode mode ); + + public slots: + + /** + * Executes the single band pseudo raster classification + */ + void classify(); + + //! called when the color ramp tree has changed + void loadMinMaxFromTree(); + + protected: + void populateColormapTreeWidget( const QList &colorRampItems ); + + private: + + enum Column + { + ValueColumn = 0, + ColorColumn = 1, + LabelColumn = 2, + }; + + /** + * Generate labels from the values in the color map. + * Skip labels which were manually edited (black text). + * Text of generated labels is made gray + */ + void autoLabel(); + + //! Extract the unit out of the current labels and set the unit field. + void setUnitFromLabels(); + + QMenu *contextMenu = nullptr; + + private slots: + + void applyColorRamp(); + void mAddEntryButton_clicked(); + void mDeleteEntryButton_clicked(); + void mLoadFromBandButton_clicked(); + void mLoadFromFileButton_clicked(); + void mExportToFileButton_clicked(); + void mUnitLineEdit_textEdited( const QString &text ) { Q_UNUSED( text ); autoLabel(); } + void mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column ); + void mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column ); + void mColorInterpolationComboBox_currentIndexChanged( int index ); + void mClassificationModeComboBox_currentIndexChanged( int index ); + void changeColor(); + void changeOpacity(); + + private: + void setLineEditValue( QLineEdit *lineEdit, double value ); + double lineEditValue( const QLineEdit *lineEdit ) const; + void resetClassifyButton(); + + double mMin = std::numeric_limits::quiet_NaN(); + double mMax = std::numeric_limits::quiet_NaN(); + + // For mode with raster layer + QgsRasterDataProvider *mRasterDataProvider = nullptr; + int mBand = -1; + QgsRectangle mExtent; + +}; + +#endif // QGSCOLORRAMPSHADERWIDGET_H diff --git a/src/gui/raster/qgssinglebandpseudocolorrendererwidget.cpp b/src/gui/raster/qgssinglebandpseudocolorrendererwidget.cpp index bf5fe54b2ef1..95d8f5bec4ec 100644 --- a/src/gui/raster/qgssinglebandpseudocolorrendererwidget.cpp +++ b/src/gui/raster/qgssinglebandpseudocolorrendererwidget.cpp @@ -142,9 +142,8 @@ QgsSingleBandPseudoColorRendererWidget::QgsSingleBandPseudoColorRendererWidget( connect( mClipCheckBox, &QAbstractButton::toggled, this, &QgsRasterRendererWidget::widgetChanged ); } -QgsRasterRenderer *QgsSingleBandPseudoColorRendererWidget::renderer() +QgsColorRampShader *QgsSingleBandPseudoColorRendererWidget::shaderFunction() const { - QgsRasterShader *rasterShader = new QgsRasterShader(); QgsColorRampShader *colorRampShader = new QgsColorRampShader( lineEditValue( mMinLineEdit ), lineEditValue( mMaxLineEdit ) ); colorRampShader->setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) ); colorRampShader->setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) ); @@ -175,8 +174,13 @@ QgsRasterRenderer *QgsSingleBandPseudoColorRendererWidget::renderer() { colorRampShader->setSourceColorRamp( btnColorRamp->colorRamp() ); } + return colorRampShader; +} - rasterShader->setRasterShaderFunction( colorRampShader ); +QgsRasterRenderer *QgsSingleBandPseudoColorRendererWidget::renderer() +{ + QgsRasterShader *rasterShader = new QgsRasterShader(); + rasterShader->setRasterShaderFunction( shaderFunction() ); int bandNumber = mBandComboBox->currentBand(); QgsSingleBandPseudoColorRenderer *renderer = new QgsSingleBandPseudoColorRenderer( mRasterLayer->dataProvider(), bandNumber, rasterShader ); @@ -191,6 +195,11 @@ void QgsSingleBandPseudoColorRendererWidget::doComputations() mMinMaxWidget->doComputations(); } +int QgsSingleBandPseudoColorRendererWidget::currentBand() const +{ + return mBandComboBox->currentBand(); +} + void QgsSingleBandPseudoColorRendererWidget::setMapCanvas( QgsMapCanvas *canvas ) { QgsRasterRendererWidget::setMapCanvas( canvas ); diff --git a/src/gui/raster/qgssinglebandpseudocolorrendererwidget.h b/src/gui/raster/qgssinglebandpseudocolorrendererwidget.h index 7071fa05100d..8c0d90f4f4dc 100644 --- a/src/gui/raster/qgssinglebandpseudocolorrendererwidget.h +++ b/src/gui/raster/qgssinglebandpseudocolorrendererwidget.h @@ -42,10 +42,16 @@ class GUI_EXPORT QgsSingleBandPseudoColorRendererWidget: public QgsRasterRendere static QgsRasterRendererWidget *create( QgsRasterLayer *layer, const QgsRectangle &extent ) SIP_FACTORY { return new QgsSingleBandPseudoColorRendererWidget( layer, extent ); } QgsRasterRenderer *renderer() override; + + //! Returns shader function used in the renderer. Caller takes ownership and deletes it. + QgsColorRampShader *shaderFunction() const SIP_FACTORY; void setMapCanvas( QgsMapCanvas *canvas ) override; void doComputations() override; QgsRasterMinMaxWidget *minMaxWidget() override { return mMinMaxWidget; } + //! Returns the current raster band number + int currentBand() const; + void setFromRenderer( const QgsRasterRenderer *r ); public slots: diff --git a/src/providers/mdal/qgsmdalprovider.cpp b/src/providers/mdal/qgsmdalprovider.cpp index 0d613a95bcc2..234ef8e94ae8 100644 --- a/src/providers/mdal/qgsmdalprovider.cpp +++ b/src/providers/mdal/qgsmdalprovider.cpp @@ -98,10 +98,21 @@ QgsMeshFace QgsMdalProvider::face( int index ) const bool QgsMdalProvider::addDataset( const QString &uri ) { + int datasetCount = mDatasets.count(); + std::string str = uri.toStdString(); MDAL_M_LoadDatasets( mMeshH, str.c_str() ); refreshDatasets(); - return true; + + if ( datasetCount == mDatasets.count() ) + { + return false; + } + else + { + emit dataChanged(); + return true; // Ok + } } int QgsMdalProvider::datasetCount() const diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 377194f7b463..19ce33137568 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -8,6 +8,7 @@ FILE(GLOB PROCESSING_UIS "${CMAKE_CURRENT_SOURCE_DIR}/processing/*.ui") FILE(GLOB AUTH_UIS "${CMAKE_CURRENT_SOURCE_DIR}/auth/*.ui") FILE(GLOB RASTER_UIS "${CMAKE_CURRENT_SOURCE_DIR}/raster/*.ui") FILE(GLOB STYLEDOCK_UIS "${CMAKE_CURRENT_SOURCE_DIR}/styledock/*.ui") +FILE(GLOB MESH_UIS "${CMAKE_CURRENT_SOURCE_DIR}/mesh/*.ui") FILE(GLOB _3D_UIS "${CMAKE_CURRENT_SOURCE_DIR}/3d/*.ui") QT5_WRAP_UI(QGIS_UIS_H @@ -20,6 +21,7 @@ QT5_WRAP_UI(QGIS_UIS_H ${RASTER_UIS} ${STYLEDOCK_UIS} ${LAYOUT_UIS} + ${MESH_UIS} ${_3D_UIS} ) diff --git a/src/ui/mesh/qgsmeshlayerpropertiesbase.ui b/src/ui/mesh/qgsmeshlayerpropertiesbase.ui new file mode 100644 index 000000000000..285620519b85 --- /dev/null +++ b/src/ui/mesh/qgsmeshlayerpropertiesbase.ui @@ -0,0 +1,467 @@ + + + QgsMeshLayerPropertiesBase + + + + 0 + 0 + 815 + 777 + + + + + 700 + 0 + + + + Raster Layer Properties + + + + + + Qt::Horizontal + + + false + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 58 + 0 + + + + + 150 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + + 32 + 32 + + + + Qt::ElideNone + + + QListView::Adjust + + + true + + + + Information + + + + :/images/themes/default/propertyicons/metadata.svg:/images/themes/default/propertyicons/metadata.svg + + + + + Source + + + + :/images/themes/default/propertyicons/system.svg:/images/themes/default/propertyicons/system.svg + + + + + Style + + + Style + + + + :/images/themes/default/propertyicons/symbology.svg:/images/themes/default/propertyicons/symbology.svg + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + 0 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 261 + 144 + + + + + + + + + Layer name + + + + + + + + + + displayed as + + + + + + + color: #505050; +background-color: #F0F0F0; +border: 1px solid #B0B0B0; +border-radius: 2px; + + + true + + + + + + + + + 5 + + + + + Set source coordinate reference system + + + + + + + + + Qt::StrongFocus + + + + + + + + + Uri + + + + + + + + + + + + + + + + Assign extra dataset to mesh + + + + + + + Qt::Vertical + + + + 20 + 415 + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 100 + 30 + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + QgsFilterLineEdit + QLineEdit +
qgsfilterlineedit.h
+
+ + QgsProjectionSelectionWidget + QWidget +
qgsprojectionselectionwidget.h
+ 1 +
+ + QgsScrollArea + QScrollArea +
qgsscrollarea.h
+ 1 +
+
+ + mSearchLineEdit + mOptionsListWidget + mInformationTextBrowser + scrollArea_3 + mLayerOrigNameLineEd + leDisplayName + mCrsSelector + scrollArea + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mOptionsListWidget + currentRowChanged(int) + mOptionsStackedWidget + setCurrentIndex(int) + + + 86 + 325 + + + 794 + 14 + + + + +
diff --git a/src/ui/mesh/qgsmeshrendereractivedatasetwidgetbase.ui b/src/ui/mesh/qgsmeshrendereractivedatasetwidgetbase.ui new file mode 100644 index 000000000000..8eb9eb67b818 --- /dev/null +++ b/src/ui/mesh/qgsmeshrendereractivedatasetwidgetbase.ui @@ -0,0 +1,130 @@ + + + QgsMeshRendererActiveDatasetWidgetBase + + + false + + + + 0 + 0 + 254 + 260 + + + + Form + + + + + + + 0 + 55 + + + + + 1 + + + + + + + + Dataset in Selected Group(s) + + + + + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 1 + + + + + + + Display + + + + + + + + + Contours + + + + + + + Vectors + + + + + + + Mesh + + + + + + + Triangular Mesh + + + + + + + + + Metadata + + + + + + + + + Qt::RichText + + + + + + + + + + + QgsMeshDatasetGroupTreeView + QTreeView +
mesh/qgsmeshdatasetgrouptreeview.h
+
+ + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+
+ + +
diff --git a/src/ui/mesh/qgsmeshrenderermeshsettingswidgetbase.ui b/src/ui/mesh/qgsmeshrenderermeshsettingswidgetbase.ui new file mode 100644 index 000000000000..08c6c2ba5b41 --- /dev/null +++ b/src/ui/mesh/qgsmeshrenderermeshsettingswidgetbase.ui @@ -0,0 +1,53 @@ + + + QgsMeshRendererMeshSettingsWidgetBase + + + + 0 + 0 + 328 + 71 + + + + Form + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsColorButton + QFrame +
qgscolorbutton.h
+ 1 +
+
+ + +
diff --git a/src/ui/mesh/qgsmeshrendererscalarsettingswidgetbase.ui b/src/ui/mesh/qgsmeshrendererscalarsettingswidgetbase.ui new file mode 100644 index 000000000000..6190459eeab1 --- /dev/null +++ b/src/ui/mesh/qgsmeshrendererscalarsettingswidgetbase.ui @@ -0,0 +1,78 @@ + + + QgsMeshRendererScalarSettingsWidgetBase + + + + 0 + 0 + 344 + 267 + + + + Form + + + + + + + + min + + + + + + + 0 + + + + + + + max + + + + + + + 1 + + + + + + + Recalculate + + + + + + + + + + 0 + 0 + + + + + + + + + QgsColorRampShaderWidget + QWidget +
raster/qgscolorrampshaderwidget.h
+ 1 +
+
+ + +
diff --git a/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui b/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui new file mode 100644 index 000000000000..524464b67d3a --- /dev/null +++ b/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui @@ -0,0 +1,228 @@ + + + QgsMeshRendererVectorSettingsWidgetBase + + + + 0 + 0 + 310 + 419 + + + + Form + + + + + + + + + + + + + + + + Filter by Magnitude + + + false + + + + + + Min + + + + + + + + + + Max + + + + + + + + + + + + + Head Options + + + + + + Width + + + + + + + + + + % of Shaft Length + + + + + + + Length + + + + + + + + + + % of Shaft Length + + + + + + + + + + Arrow Length + + + + + + + 0 + 0 + + + + QComboBox::AdjustToContents + + + + Defined by Min and Max + + + + + Scaled to Magnitude + + + + + Fixed + + + + + + + + 2 + + + + + + + Minimum + + + + + + + + + + Maximum + + + + + + + + + + + + + + Scale by a Factor of: + + + + + + + + + + + + + + Length + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsColorButton + QFrame +
qgscolorbutton.h
+ 1 +
+
+ + +
diff --git a/src/ui/mesh/qgsrenderermeshpropswidgetbase.ui b/src/ui/mesh/qgsrenderermeshpropswidgetbase.ui new file mode 100644 index 000000000000..ac9f2f24f329 --- /dev/null +++ b/src/ui/mesh/qgsrenderermeshpropswidgetbase.ui @@ -0,0 +1,144 @@ + + + QgsRendererMeshPropsWidgetBase + + + + 0 + 0 + 386 + 593 + + + + Form + + + + + + Active Dataset + + + + + + true + + + + 0 + 0 + + + + + + + + + + + Scalar rendering + + + + + + + + + + + + Vector rendering + + + + + + + + + + + + Mesh rendering + + + false + + + + + + Native mesh + + + + + + + + + + Triangular mesh + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+ + QgsMeshRendererScalarSettingsWidget + QWidget +
mesh/qgsmeshrendererscalarsettingswidget.h
+ 1 +
+ + QgsMeshRendererActiveDatasetWidget + QWidget +
mesh/qgsmeshrendereractivedatasetwidget.h
+ 1 +
+ + QgsMeshRendererMeshSettingsWidget + QWidget +
mesh/qgsmeshrenderermeshsettingswidget.h
+ 1 +
+ + QgsMeshRendererVectorSettingsWidget + QWidget +
mesh/qgsmeshrenderervectorsettingswidget.h
+ 1 +
+
+ + +
diff --git a/src/ui/qgscolorrampshaderwidgetbase.ui b/src/ui/qgscolorrampshaderwidgetbase.ui new file mode 100644 index 000000000000..14ac1f67cf0e --- /dev/null +++ b/src/ui/qgscolorrampshaderwidgetbase.ui @@ -0,0 +1,322 @@ + + + QgsColorRampShaderWidgetBase + + + + 0 + 0 + 396 + 605 + + + + Form + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + + 0 + 250 + + + + 70 + + + 10 + + + true + + + + Value + + + + + Color + + + + + Label + + + + + + + + Color ramp + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 16777215 + 16777215 + + + + + + + + + + + Interpolation + + + + + + + + + Classify + + + + + + + Add values manually + + + + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + + + + + + + Remove selected row(s) + + + + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + + + + Load color map from band + + + + :/images/themes/default/mActionDraw.svg:/images/themes/default/mActionDraw.svg + + + + + + + Load color map from file + + + + :/images/themes/default/mActionFileOpen.svg:/images/themes/default/mActionFileOpen.svg + + + + + + + Export color map to file + + + + :/images/themes/default/mActionFileSaveAs.svg:/images/themes/default/mActionFileSaveAs.svg + + + + + + + Qt::Horizontal + + + + 48 + 28 + + + + + + + + + + If checked, any pixels with a value out of range will not be rendered + + + Clip out of range values + + + + + + + + + Mode + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Classes + + + + + + + + 0 + 0 + + + + 2 + + + 255 + + + 5 + + + + + + + + + Unit suffix + + + + + + + Label unit +suffix + + + + + + + + QgsColorRampButton + QToolButton +
qgscolorrampbutton.h
+ 1 +
+
+ + mColorInterpolationComboBox + btnColorRamp + mUnitLineEdit + mColormapTreeWidget + mClassificationModeComboBox + mNumberOfEntriesSpinBox + mClassifyButton + mAddEntryButton + mDeleteEntryButton + mLoadFromBandButton + mLoadFromFileButton + mExportToFileButton + mClipCheckBox + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/ui/qgsrasterlayerpropertiesbase.ui b/src/ui/qgsrasterlayerpropertiesbase.ui index d461550b3cc4..f90822d5df46 100644 --- a/src/ui/qgsrasterlayerpropertiesbase.ui +++ b/src/ui/qgsrasterlayerpropertiesbase.ui @@ -42,7 +42,16 @@ QFrame::Raised - + + 0 + + + 0 + + + 0 + + 0 @@ -209,7 +218,16 @@ QFrame::Raised - + + 0 + + + 0 + + + 0 + + 0 @@ -232,7 +250,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -248,8 +275,8 @@ 0 0 - 744 - 705 + 643 + 729 @@ -302,7 +329,7 @@ border-radius: 2px; - + Qt::StrongFocus @@ -329,7 +356,16 @@ border-radius: 2px; - + + 0 + + + 0 + + + 0 + + 0 @@ -345,8 +381,8 @@ border-radius: 2px; 0 0 - 850 - 748 + 643 + 729 @@ -361,7 +397,7 @@ border-radius: 2px; Band rendering - + rasterstyle @@ -406,13 +442,13 @@ border-radius: 2px; Color rendering - + false - + rasterstyle - + true @@ -603,7 +639,7 @@ border-radius: 2px; 16777215 - + @@ -708,13 +744,13 @@ border-radius: 2px; false - + false - + rasterstyle - + true @@ -908,7 +944,16 @@ border-radius: 2px; - + + 0 + + + 0 + + + 0 + + 0 @@ -924,8 +969,8 @@ border-radius: 2px; 0 0 - 502 - 662 + 348 + 446 @@ -940,7 +985,7 @@ border-radius: 2px; Global opacity - + rastertransp @@ -959,7 +1004,7 @@ border-radius: 2px; No data value - + rastertransp @@ -1031,7 +1076,7 @@ border-radius: 2px; Custom transparency options - + rastertransp @@ -1246,7 +1291,16 @@ border-radius: 2px; - + + 0 + + + 0 + + + 0 + + 0 @@ -1262,8 +1316,8 @@ border-radius: 2px; 0 0 - 80 - 38 + 86 + 42 @@ -1307,18 +1361,27 @@ border-radius: 2px; false - + rastergeneral - + + 11 + + + 11 + + + 11 + + 11 6 - + @@ -1393,7 +1456,16 @@ border-radius: 2px; - + + 0 + + + 0 + + + 0 + + 0 @@ -1409,8 +1481,8 @@ border-radius: 2px; 0 0 - 1004 - 254 + 553 + 193 @@ -1473,7 +1545,7 @@ border-radius: 2px; <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:11pt;"><br /></span></p></body></html> @@ -1584,7 +1656,16 @@ p, li { white-space: pre-wrap; } - + + 0 + + + 0 + + + 0 + + 0 @@ -1600,8 +1681,8 @@ p, li { white-space: pre-wrap; } 0 0 - 724 - 1060 + 643 + 729 @@ -1610,7 +1691,7 @@ p, li { white-space: pre-wrap; } Description - + rastermeta @@ -1746,7 +1827,7 @@ p, li { white-space: pre-wrap; } Attribution - + vectormeta @@ -1792,7 +1873,7 @@ p, li { white-space: pre-wrap; } MetadataUrl - + vectormeta @@ -2011,7 +2092,16 @@ p, li { white-space: pre-wrap; } QFrame::Raised - + + 0 + + + 0 + + + 0 + + 0 @@ -2031,15 +2121,15 @@ p, li { white-space: pre-wrap; } - QgsCollapsibleGroupBox - QGroupBox -
qgscollapsiblegroupbox.h
+ QgsColorButton + QFrame +
qgscolorbutton.h
1
- QgsColorButton - QPushButton -
qgscolorbutton.h
+ QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
1
@@ -2155,6 +2245,34 @@ p, li { white-space: pre-wrap; } + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/qgssinglebandpseudocolorrendererwidgetbase.ui b/src/ui/qgssinglebandpseudocolorrendererwidgetbase.ui index 6a40eb0958c7..b46c6de0a52a 100644 --- a/src/ui/qgssinglebandpseudocolorrendererwidgetbase.ui +++ b/src/ui/qgssinglebandpseudocolorrendererwidgetbase.ui @@ -342,6 +342,34 @@ suffix + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/raster/qgscolorrampshaderwidgetbase.ui b/src/ui/raster/qgscolorrampshaderwidgetbase.ui new file mode 100644 index 000000000000..8176e6c15a54 --- /dev/null +++ b/src/ui/raster/qgscolorrampshaderwidgetbase.ui @@ -0,0 +1,294 @@ + + + QgsColorRampShaderWidgetBase + + + + 0 + 0 + 396 + 605 + + + + Form + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + + 0 + 250 + + + + 70 + + + 10 + + + true + + + + Value + + + + + Color + + + + + Label + + + + + + + + Color ramp + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 16777215 + 16777215 + + + + + + + + + + + Interpolation + + + + + + + + + Classify + + + + + + + Add values manually + + + + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + + + + + + + Remove selected row(s) + + + + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + + + + Load color map from band + + + + :/images/themes/default/mActionDraw.svg:/images/themes/default/mActionDraw.svg + + + + + + + Load color map from file + + + + :/images/themes/default/mActionFileOpen.svg:/images/themes/default/mActionFileOpen.svg + + + + + + + Export color map to file + + + + :/images/themes/default/mActionFileSaveAs.svg:/images/themes/default/mActionFileSaveAs.svg + + + + + + + Qt::Horizontal + + + + 48 + 28 + + + + + + + + + + If checked, any pixels with a value out of range will not be rendered + + + Clip out of range values + + + + + + + + + Mode + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Classes + + + + + + + + 0 + 0 + + + + 2 + + + 255 + + + 5 + + + + + + + + + Unit suffix + + + + + + + Label unit +suffix + + + + + + + + QgsColorRampButton + QToolButton +
qgscolorrampbutton.h
+ 1 +
+
+ + mColorInterpolationComboBox + btnColorRamp + mUnitLineEdit + mColormapTreeWidget + mClassificationModeComboBox + mNumberOfEntriesSpinBox + mClassifyButton + mAddEntryButton + mDeleteEntryButton + mLoadFromBandButton + mLoadFromFileButton + mExportToFileButton + mClipCheckBox + + + + + +