[FEATURE] Interactive curve editing for property overrides

This adds a new interactive "curve" to the assistant widgets.
It allows you to fine tune exactly how input values get
mapped to output sizes/colors/etc.

Think GIMP or Photoshop curves, but for your data...
This commit is contained in:
Nyall Dawson 2017-02-21 16:19:10 +10:00
parent 45861d39f8
commit cc9b5a47b7
8 changed files with 566 additions and 28 deletions

View File

@ -51,6 +51,7 @@
%Include qgsconfigureshortcutsdialog.sip
%Include qgscredentialdialog.sip
%Include qgscustomdrophandler.sip
%Include qgscurveeditorwidget.sip
%Include qgsdetaileditemdata.sip
%Include qgsdetaileditemdelegate.sip
%Include qgsdetaileditemwidget.sip

View File

@ -0,0 +1,23 @@
class QgsCurveEditorWidget : QWidget
{
%TypeHeaderCode
#include <qgscurveeditorwidget.h>
%End
public:
QgsCurveEditorWidget( QWidget* parent /TransferThis/ = 0, const QgsCurveTransform& curve = QgsCurveTransform() );
QgsCurveTransform curve() const;
void setCurve( const QgsCurveTransform& curve );
signals:
void changed();
protected:
virtual void keyPressEvent( QKeyEvent *event );
};

View File

@ -193,6 +193,7 @@ SET(QGIS_GUI_SRCS
qgscredentialdialog.cpp
qgscursors.cpp
qgscustomdrophandler.cpp
qgscurveeditorwidget.cpp
qgsdatumtransformdialog.cpp
qgsdetaileditemdata.cpp
qgsdetaileditemdelegate.cpp
@ -345,6 +346,7 @@ SET(QGIS_GUI_MOC_HDRS
qgscompoundcolorwidget.h
qgsconfigureshortcutsdialog.h
qgscredentialdialog.h
qgscurveeditorwidget.h
qgsdatumtransformdialog.h
qgsdetaileditemdelegate.h
qgsdetaileditemwidget.h

View File

@ -0,0 +1,312 @@
/***************************************************************************
qgscurveeditorwidget.cpp
------------------------
begin : February 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson 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 "qgscurveeditorwidget.h"
#include <qmath.h>
#include <QPainter>
#include <QVBoxLayout>
#include <QMouseEvent>
// QWT Charting widget
#include <qwt_global.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_marker.h>
#include <qwt_plot_picker.h>
#include <qwt_picker_machine.h>
#include <qwt_plot_layout.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
QgsCurveEditorWidget::QgsCurveEditorWidget( QWidget* parent, const QgsCurveTransform& transform )
: QWidget( parent )
, mCurve( transform )
, mCurrentPlotMarkerIndex( -1 )
{
mPlot = new QwtPlot();
mPlot->setMinimumSize( QSize( 0, 100 ) );
mPlot->setAxisScale( QwtPlot::yLeft, 0, 1 );
mPlot->setAxisScale( QwtPlot::yRight, 0, 1 );
mPlot->setAxisScale( QwtPlot::xBottom, 0, 1 );
mPlot->setAxisScale( QwtPlot::xTop, 0, 1 );
QVBoxLayout* vlayout = new QVBoxLayout();
vlayout->addWidget( mPlot );
setLayout( vlayout );
// hide the ugly canvas frame
mPlot->setFrameStyle( QFrame::NoFrame );
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
QFrame* plotCanvasFrame = dynamic_cast<QFrame*>( mPlot->canvas() );
if ( plotCanvasFrame )
plotCanvasFrame->setFrameStyle( QFrame::NoFrame );
#else
mPlot->canvas()->setFrameStyle( QFrame::NoFrame );
#endif
mPlot->enableAxis( QwtPlot::yLeft, false );
mPlot->enableAxis( QwtPlot::xBottom, false );
// add a grid
QwtPlotGrid * grid = new QwtPlotGrid();
QwtScaleDiv gridDiv( 0.0, 1.0, QList<double>(), QList<double>(), QList<double>() << 0.2 << 0.4 << 0.6 << 0.8 );
grid->setXDiv( gridDiv );
grid->setYDiv( gridDiv );
grid->setPen( QPen( QColor( 0, 0, 0, 50 ) ) );
grid->attach( mPlot );
mPlotCurve = new QwtPlotCurve();
mPlotCurve->setTitle( QStringLiteral( "Curve" ) );
mPlotCurve->setPen( QPen( QColor( 30, 30, 30 ), 0.0 ) ),
mPlotCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
mPlotCurve->attach( mPlot );
mPlotFilter = new QgsCurveEditorPlotEventFilter( mPlot );
connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mousePress, this, &QgsCurveEditorWidget::plotMousePress );
connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mouseRelease, this, &QgsCurveEditorWidget::plotMouseRelease );
connect( mPlotFilter, &QgsCurveEditorPlotEventFilter::mouseMove, this, &QgsCurveEditorWidget::plotMouseMove );
mPlotCurve->setVisible( true );
updatePlot();
}
void QgsCurveEditorWidget::setCurve( const QgsCurveTransform& curve )
{
mCurve = curve;
updatePlot();
emit changed();
}
void QgsCurveEditorWidget::keyPressEvent( QKeyEvent* event )
{
if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
{
QList< QgsPoint > cp = mCurve.controlPoints();
if ( mCurrentPlotMarkerIndex > 0 && mCurrentPlotMarkerIndex < cp.count() - 1 )
{
cp.removeAt( mCurrentPlotMarkerIndex );
mCurve.setControlPoints( cp );
updatePlot();
emit changed();
}
}
}
void QgsCurveEditorWidget::plotMousePress( QPointF point )
{
mCurrentPlotMarkerIndex = findNearestControlPoint( point );
if ( mCurrentPlotMarkerIndex < 0 )
{
// add a new point
mCurve.addControlPoint( point.x(), point.y() );
mCurrentPlotMarkerIndex = findNearestControlPoint( point );
emit changed();
}
updatePlot();
}
int QgsCurveEditorWidget::findNearestControlPoint( QPointF point ) const
{
double minDist = 3.0 / mPlot->width();
int currentPlotMarkerIndex = -1;
QList< QgsPoint > controlPoints = mCurve.controlPoints();
for ( int i = 0; i < controlPoints.count(); ++i )
{
QgsPoint currentPoint = controlPoints.at( i );
double currentDist;
currentDist = qPow( point.x() - currentPoint.x(), 2.0 ) + qPow( point.y() - currentPoint.y(), 2.0 );
if ( currentDist < minDist )
{
minDist = currentDist;
currentPlotMarkerIndex = i;
}
}
return currentPlotMarkerIndex;
}
void QgsCurveEditorWidget::plotMouseRelease( QPointF )
{
}
void QgsCurveEditorWidget::plotMouseMove( QPointF point )
{
if ( mCurrentPlotMarkerIndex < 0 )
return;
QList< QgsPoint > cp = mCurve.controlPoints();
bool removePoint = false;
if ( mCurrentPlotMarkerIndex == 0 )
{
point.setX( qMin( point.x(), cp.at( 1 ).x() - 0.01 ) );
}
else
{
removePoint = point.x() <= cp.at( mCurrentPlotMarkerIndex - 1 ).x();
}
if ( mCurrentPlotMarkerIndex == cp.count() - 1 )
{
point.setX( qMax( point.x(), cp.at( mCurrentPlotMarkerIndex - 1 ).x() + 0.01 ) );
removePoint = false;
}
else
{
removePoint = removePoint || point.x() >= cp.at( mCurrentPlotMarkerIndex + 1 ).x();
}
if ( removePoint )
{
cp.removeAt( mCurrentPlotMarkerIndex );
mCurrentPlotMarkerIndex = -1;
}
else
{
cp[ mCurrentPlotMarkerIndex ] = QgsPoint( point.x(), point.y() );
}
mCurve.setControlPoints( cp );
updatePlot();
emit changed();
}
void QgsCurveEditorWidget::addPlotMarker( double x, double y, bool isSelected )
{
QColor borderColor( 0, 0, 0 );
QColor brushColor = isSelected ? borderColor : QColor( 255, 255, 255, 0 );
QwtPlotMarker *marker = new QwtPlotMarker();
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
marker->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 6, 6 ) ) );
#else
marker->setSymbol( QwtSymbol( QwtSymbol::Ellipse, QBrush( brushColor ), QPen( borderColor, isSelected ? 2 : 1 ), QSize( 6, 6 ) ) );
#endif
marker->setValue( x, y );
marker->attach( mPlot );
marker->setRenderHint( QwtPlotItem::RenderAntialiased, true );
mMarkers << marker;
}
void QgsCurveEditorWidget::updatePlot()
{
// remove existing markers
Q_FOREACH ( QwtPlotMarker* marker, mMarkers )
{
marker->detach();
delete marker;
}
mMarkers.clear();
QPolygonF curvePoints;
QVector< double > x;
int i = 0;
Q_FOREACH ( const QgsPoint& point, mCurve.controlPoints() )
{
x << point.x();
addPlotMarker( point.x(), point.y(), mCurrentPlotMarkerIndex == i );
i++;
}
//add extra intermediate points
for ( double p = 0; p <= 1.0; p += 0.01 )
{
x << p;
}
std::sort( x.begin(), x.end() );
QVector< double > y = mCurve.y( x );
for ( int j = 0; j < x.count(); ++j )
{
curvePoints << QPointF( x.at( j ), y.at( j ) );
}
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
mPlotCurve->setSamples( curvePoints );
#else
mPlotCurve->setData( curvePoints );
#endif
mPlot->replot();
}
/// @cond PRIVATE
QgsCurveEditorPlotEventFilter::QgsCurveEditorPlotEventFilter( QwtPlot *plot )
: QObject( plot )
, mPlot( plot )
{
mPlot->canvas()->installEventFilter( this );
}
bool QgsCurveEditorPlotEventFilter::eventFilter( QObject *object, QEvent *event )
{
if ( !mPlot->isEnabled() )
return QObject::eventFilter( object, event );
switch ( event->type() )
{
case QEvent::MouseButtonPress:
{
const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
if ( mouseEvent->button() == Qt::LeftButton )
{
emit mousePress( mapPoint( mouseEvent->pos() ) );
}
break;
}
case QEvent::MouseMove:
{
const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
if ( mouseEvent->buttons() & Qt::LeftButton )
{
// only emit when button pressed
emit mouseMove( mapPoint( mouseEvent->pos() ) );
}
break;
}
case QEvent::MouseButtonRelease:
{
const QMouseEvent* mouseEvent = static_cast<QMouseEvent* >( event );
if ( mouseEvent->button() == Qt::LeftButton )
{
emit mouseRelease( mapPoint( mouseEvent->pos() ) );
}
break;
}
default:
break;
}
return QObject::eventFilter( object, event );
}
QPointF QgsCurveEditorPlotEventFilter::mapPoint( QPointF point ) const
{
if ( !mPlot )
return QPointF();
return QPointF( mPlot->canvasMap( QwtPlot::xBottom ).invTransform( point.x() ),
mPlot->canvasMap( QwtPlot::yLeft ).invTransform( point.y() ) );
}
///@endcond

View File

@ -0,0 +1,118 @@
/***************************************************************************
qgscurveeditorwidget.h
----------------------
begin : February 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson 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 QGSCURVEEDITORWIDGET_H
#define QGSCURVEEDITORWIDGET_H
#include <QWidget>
#include "qgis_gui.h"
#include "qgspropertytransformer.h"
class QwtPlot;
class QwtPlotCurve;
class QwtPlotMarker;
class QgsCurveEditorPlotEventFilter;
/** \ingroup gui
* \class QgsCurveEditorWidget
* A widget for manipulating QgsCurveTransform curves.
* \note added in QGIS 3.0
*/
class GUI_EXPORT QgsCurveEditorWidget : public QWidget
{
Q_OBJECT
public:
/**
* Constructor for QgsCurveEditorWidget.
*/
QgsCurveEditorWidget( QWidget* parent = nullptr, const QgsCurveTransform& curve = QgsCurveTransform() );
/**
* Returns a curve representing the current curve from the widget.
* @see setCurve()
*/
QgsCurveTransform curve() const { return mCurve; }
/**
* Sets the \a curve to show in the widget.
* @see curve()
*/
void setCurve( const QgsCurveTransform& curve );
signals:
//! Emitted when the widget curve changes
void changed();
protected:
virtual void keyPressEvent( QKeyEvent *event ) override ;
private slots:
void plotMousePress( QPointF point );
void plotMouseRelease( QPointF point );
void plotMouseMove( QPointF point );
private:
QgsCurveTransform mCurve;
QwtPlot* mPlot = nullptr;
QwtPlotCurve* mPlotCurve = nullptr;
QList< QwtPlotMarker* > mMarkers;
QgsCurveEditorPlotEventFilter* mPlotFilter = nullptr;
int mCurrentPlotMarkerIndex;
void updatePlot();
void addPlotMarker( double x, double y, bool isSelected = false );
int findNearestControlPoint( QPointF point ) const;
};
//
// NOTE:
// For private only, not part of stable api or exposed to Python bindings
//
/// @cond PRIVATE
class GUI_EXPORT QgsCurveEditorPlotEventFilter: public QObject
{
Q_OBJECT
public:
QgsCurveEditorPlotEventFilter( QwtPlot *plot );
virtual bool eventFilter( QObject* object, QEvent* event ) override;
signals:
void mousePress( QPointF );
void mouseRelease( QPointF );
void mouseMove( QPointF );
private:
QwtPlot* mPlot;
QPointF mapPoint( QPointF point ) const;
};
///@endcond
#endif // QGSCURVEEDITORWIDGET_H

View File

@ -51,6 +51,12 @@ QgsPropertyAssistantWidget::QgsPropertyAssistantWidget( QWidget* parent ,
{
minValueSpinBox->setValue( initialState.transformer()->minValue() );
maxValueSpinBox->setValue( initialState.transformer()->maxValue() );
if ( initialState.transformer()->curveTransform() )
{
mTransformCurveCheckBox->setChecked( true );
mCurveEditor->setCurve( *initialState.transformer()->curveTransform() );
}
}
connect( computeValuesButton, &QPushButton::clicked, this, &QgsPropertyAssistantWidget::computeValuesFromLayer );
@ -108,10 +114,12 @@ QgsPropertyAssistantWidget::QgsPropertyAssistantWidget( QWidget* parent ,
mOutputWidget->layout()->addWidget( mTransformerWidget );
connect( mTransformerWidget, &QgsPropertyAbstractTransformerWidget::widgetChanged, this, &QgsPropertyAssistantWidget::widgetChanged );
}
mTransformCurveCheckBox->setVisible( mTransformerWidget );
connect( minValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
connect( maxValueSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString& ) > ( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsPropertyAssistantWidget::widgetChanged );
connect( mCurveEditor, &QgsCurveEditorWidget::changed, this, &QgsPropertyAssistantWidget::widgetChanged );
connect( this, &QgsPropertyAssistantWidget::widgetChanged, this, &QgsPropertyAssistantWidget::updatePreview );
updatePreview();
}
@ -131,7 +139,18 @@ void QgsPropertyAssistantWidget::updateProperty( QgsProperty& property )
property.setField( mExpressionWidget->currentField() );
if ( mTransformerWidget )
property.setTransformer( mTransformerWidget->createTransformer( minValueSpinBox->value(), maxValueSpinBox->value() ) );
{
std::unique_ptr< QgsPropertyTransformer> t( mTransformerWidget->createTransformer( minValueSpinBox->value(), maxValueSpinBox->value() ) );
if ( mTransformCurveCheckBox->isChecked() )
{
t->setCurveTransform( new QgsCurveTransform( mCurveEditor->curve() ) );
}
else
{
t->setCurveTransform( nullptr );
}
property.setTransformer( t.release() );
}
}
void QgsPropertyAssistantWidget::setDockMode( bool dockMode )
@ -180,8 +199,9 @@ void QgsPropertyAssistantWidget::updatePreview()
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( minValueSpinBox->value(),
maxValueSpinBox->value(), 8 );
QgsCurveTransform curve = mCurveEditor->curve();
QList< QgsSymbolLegendNode* > nodes = mTransformerWidget->generatePreviews( breaks, mLayerTreeLayer, mSymbol.get(), minValueSpinBox->value(),
maxValueSpinBox->value() );
maxValueSpinBox->value(), mTransformCurveCheckBox->isChecked() ? &curve : nullptr );
int widthMax = 0;
int i = 0;
@ -353,7 +373,7 @@ QgsSizeScaleTransformer* QgsPropertySizeAssistantWidget::createTransformer( doub
return transformer;
}
QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const
QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const
{
QList< QgsSymbolLegendNode* > nodes;
@ -376,6 +396,8 @@ QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews(
return nodes;
std::unique_ptr< QgsSizeScaleTransformer > t( createTransformer( minValue, maxValue ) );
if ( curve )
t->setCurveTransform( new QgsCurveTransform( *curve ) );
for ( int i = 0; i < breaks.length(); i++ )
{
@ -401,7 +423,7 @@ QList< QgsSymbolLegendNode* > QgsPropertySizeAssistantWidget::generatePreviews(
return nodes;
}
QList<QgsSymbolLegendNode*> QgsPropertyAbstractTransformerWidget::generatePreviews( const QList<double>& , QgsLayerTreeLayer* , const QgsSymbol*, double, double ) const
QList<QgsSymbolLegendNode*> QgsPropertyAbstractTransformerWidget::generatePreviews( const QList<double>& , QgsLayerTreeLayer* , const QgsSymbol*, double, double, QgsCurveTransform* ) const
{
return QList< QgsSymbolLegendNode* >();
}
@ -451,7 +473,7 @@ QgsColorRampTransformer* QgsPropertyColorAssistantWidget::createTransformer( dou
return transformer;
}
QList<QgsSymbolLegendNode*> QgsPropertyColorAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const
QList<QgsSymbolLegendNode*> QgsPropertyColorAssistantWidget::generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue , QgsCurveTransform* curve ) const
{
QList< QgsSymbolLegendNode* > nodes;
@ -467,6 +489,8 @@ QList<QgsSymbolLegendNode*> QgsPropertyColorAssistantWidget::generatePreviews( c
return nodes;
std::unique_ptr< QgsColorRampTransformer > t( createTransformer( minValue, maxValue ) );
if ( curve )
t->setCurveTransform( new QgsCurveTransform( *curve ) );
for ( int i = 0; i < breaks.length(); i++ )
{

View File

@ -48,7 +48,7 @@ class GUI_EXPORT QgsPropertyAbstractTransformerWidget : public QWidget
virtual QgsPropertyTransformer* createTransformer( double minValue, double maxValue ) const = 0;
virtual QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const;
virtual QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const;
signals:
@ -82,7 +82,7 @@ class GUI_EXPORT QgsPropertySizeAssistantWidget : public QgsPropertyAbstractTran
virtual QgsSizeScaleTransformer* createTransformer( double minValue, double maxValue ) const override;
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const override;
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const override;
};
class GUI_EXPORT QgsPropertyColorAssistantWidget : public QgsPropertyAbstractTransformerWidget, private Ui::PropertyColorAssistant
@ -95,7 +95,7 @@ class GUI_EXPORT QgsPropertyColorAssistantWidget : public QgsPropertyAbstractTra
virtual QgsColorRampTransformer* createTransformer( double minValue, double maxValue ) const override;
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue ) const override;
QList< QgsSymbolLegendNode* > generatePreviews( const QList<double>& breaks, QgsLayerTreeLayer* parent, const QgsSymbol* symbol, double minValue, double maxValue, QgsCurveTransform* curve ) const override;
};
///@endcond PRIVATE

View File

@ -6,22 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>522</width>
<height>332</height>
<width>525</width>
<height>426</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0">
<item row="0" column="1" rowspan="2">
<widget class="QTreeView" name="mLegendPreview">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0">
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Output</string>
@ -60,6 +50,16 @@
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QFrame" name="mLegendVerticalFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
@ -153,14 +153,60 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QFrame" name="mLegendVerticalFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<item row="0" column="1" rowspan="3">
<widget class="QTreeView" name="mLegendPreview">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</widget>
</item>
<item row="1" column="0">
<widget class="QgsCollapsibleGroupBoxBasic" name="mTransformCurveCheckBox">
<property name="title">
<string>Apply transform curve</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="collapsed" stdset="0">
<bool>true</bool>
</property>
<property name="syncGroup" stdset="0">
<string notr="true">composeritem</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QgsCurveEditorWidget" name="mCurveEditor" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@ -183,6 +229,18 @@
<header>qgspanelwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header location="global">qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsCurveEditorWidget</class>
<extends>QWidget</extends>
<header location="global">qgscurveeditorwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mExpressionWidget</tabstop>