From b9fc2b51d9324f9c15e381d9fc0f0787dd70fec8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 Sep 2014 21:31:43 +1000 Subject: [PATCH] Add a bunch of useful interactive color widgets to GUI, including color wheels, ramps, boxes, and text edits. --- images/images.qrc | 1 + images/themes/default/mIconDropDownMenu.svg | 68 + python/gui/gui.sip | 1 + python/gui/qgscolorwidgets.sip | 407 +++++ src/gui/CMakeLists.txt | 3 + src/gui/qgscolorwidgets.cpp | 1513 +++++++++++++++++++ src/gui/qgscolorwidgets.h | 631 ++++++++ 7 files changed, 2624 insertions(+) create mode 100644 images/themes/default/mIconDropDownMenu.svg create mode 100755 python/gui/qgscolorwidgets.sip create mode 100644 src/gui/qgscolorwidgets.cpp create mode 100644 src/gui/qgscolorwidgets.h diff --git a/images/images.qrc b/images/images.qrc index c122765e3f0..5cc0f89e0a8 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -305,6 +305,7 @@ themes/default/mIconDbSchema.png themes/default/mIconDelete.png themes/default/mIconDeselected.svg + themes/default/mIconDropDownMenu.svg themes/default/mIconEditable.png themes/default/mIconEditableEdits.png themes/default/mIconExpand.png diff --git a/images/themes/default/mIconDropDownMenu.svg b/images/themes/default/mIconDropDownMenu.svg new file mode 100644 index 00000000000..5c757d8519c --- /dev/null +++ b/images/themes/default/mIconDropDownMenu.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/python/gui/gui.sip b/python/gui/gui.sip index 72f65666ef2..936d2b58c4b 100644 --- a/python/gui/gui.sip +++ b/python/gui/gui.sip @@ -37,6 +37,7 @@ %Include qgscolordialog.sip %Include qgscolorswatchgrid.sip %Include qgscolorschemelist.sip +%Include qgscolorwidgets.sip %Include qgscomposerview.sip %Include qgscredentialdialog.sip %Include qgsdatetimeedit.sip diff --git a/python/gui/qgscolorwidgets.sip b/python/gui/qgscolorwidgets.sip new file mode 100755 index 00000000000..a66dc0adee3 --- /dev/null +++ b/python/gui/qgscolorwidgets.sip @@ -0,0 +1,407 @@ +/** \ingroup gui + * \class QgsColorWidget + * A base class for interactive color widgets. Widgets can either allow setting a single component of + * a color (eg the red or green components), or an entire color. The QgsColorWidget also keeps track of + * any explicitely set hue for the color, so that this information is not lost when the widget is + * set to a color with an ambiguous hue (eg black or white shades). + * \note Added in version 2.5 + */ + +class QgsColorWidget : QWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /*! Specifies the color component which the widget alters + */ + enum ColorComponent + { + Multiple = 0, /*!< widget alters multiple color components */ + Red, /*!< red component of color */ + Green, /*!< green component of color */ + Blue, /*!< blue component of color */ + Hue, /*!< hue component of color (based on HSV model) */ + Saturation, /*!< saturation component of color (based on HSV model) */ + Value, /*!< value component of color (based on HSV model) */ + Alpha /*!< alpha component (opacity) of color */ + }; + + /**Construct a new color widget. + * @param parent parent QWidget for the widget + * @param component color component the widget alters + */ + QgsColorWidget( QWidget* parent /TransferThis/ = 0, const ColorComponent component = Multiple ); + + virtual ~QgsColorWidget(); + + /**Returns the current color for the widget + * @returns current widget color + * @see setColor + */ + QColor color() const; + + /**Returns the color component which the widget controls + * @returns color component for widget + * @see setComponent + */ + ColorComponent component() const; + + /**Returns the current value of the widget's color component + * @returns value of color component, or -1 if widget has multiple components or an invalid color + * set + * @see setComponentValue + * @see component + */ + int componentValue() const; + + public slots: + + /**Sets the color for the widget + * @param color widget color + * @see color + */ + virtual void setColor( const QColor color ); + + /**Sets the color component which the widget controls + * @param component color component for widget + * @see component + */ + virtual void setComponent( const ColorComponent component ); + + /**Alters the widget's color by setting the value for the widget's color component + * @param value value for widget's color component. This value is automatically + * clipped to the range of valid values for the color component. + * @see componentValue + * @see setComponent + * @note this method has no effect if the widget is set to the QgsColorWidget::Multiple + * component + */ + virtual void setComponentValue( const int value ); + + signals: + + /**Emitted when the widget's color changes + * @param color new widget color + */ + void colorChanged( const QColor color ); + + protected: + + /**Returns the range of valid values for the color widget's component + * @returns maximum value allowed for color component, or -1 if widget has multiple components + */ + int componentRange() const; + + /**Returns the range of valid values a color component + * @returns maximum value allowed for color component + */ + int componentRange( const ColorComponent component ) const; + + /**Returns the value of a component of the widget's current color. This method correctly + * handles hue values when the color has an ambiguous hue (eg black or white shades) + * @param component color component to return + * @returns value of color component, or -1 if widget has an invalid color set + * @see hue + */ + int componentValue( const ColorComponent component ) const; + + /**Returns the hue for the widget. This may differ from the hue for the QColor returned by color(), + * as QColor returns a hue of -1 if the color's hue is ambiguous (eg, if the saturation is zero). + * @returns explicitly set hue for widget + */ + int hue() const; + + /**Alters a color by modifiying the value of a specific color component + * @param color color to alter + * @param component color component to alter + * @param newValue new value of color component. Values are automatically clipped to a + * valid range for the color component. + */ + void alterColor( QColor& color, const QgsColorWidget::ColorComponent component, const int newValue ) const; + + /**Generates a checkboard pattern pixmap for use as a background to transparent colors + * @returns checkerboard pixmap + */ + static const QPixmap& transparentBackground(); + +}; + + +/** \ingroup gui + * \class QgsColorWheel + * A color wheel widget. This widget consists of an outer ring which allows for hue selection, and an + * inner rotating triangle which allows for saturation and value selection. + * \note Added in version 2.5 + */ + +class QgsColorWheel : QgsColorWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /**Constructs a new color wheel widget. + * @param parent parent QWidget for the widget + */ + QgsColorWheel( QWidget* parent /TransferThis/ = 0 ); + + virtual ~QgsColorWheel(); + + void paintEvent( QPaintEvent* event ); + + public slots: + + virtual void setColor( const QColor color ); + + protected: + + virtual void resizeEvent( QResizeEvent *event ); + virtual void mouseMoveEvent( QMouseEvent *event ); + virtual void mousePressEvent( QMouseEvent *event ); + virtual void mouseReleaseEvent( QMouseEvent *event ); + +}; + + +/** \ingroup gui + * \class QgsColorBox + * A color box widget. This widget consists of a two dimensional rectangle filled with color + * variations, where a different color component varies along both the horizontal and vertical + * axis. + * \note Added in version 2.5 + */ + +class QgsColorBox : QgsColorWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /**Construct a new color box widget. + * @param parent parent QWidget for the widget + * @param component constant color component for the widget. The color components + * which vary along the horizontal and vertical axis are automatically assigned + * based on this constant color component. + */ + QgsColorBox( QWidget* parent /TransferThis/ = 0, const ColorComponent component = Value ); + + virtual ~QgsColorBox(); + + virtual QSize sizeHint() const; + void paintEvent( QPaintEvent* event ); + + virtual void setComponent( const ColorComponent component ); + + public slots: + + virtual void setColor( const QColor color ); + + protected: + + virtual void resizeEvent( QResizeEvent *event ); + virtual void mouseMoveEvent( QMouseEvent *event ); + virtual void mousePressEvent( QMouseEvent *event ); +}; + + +/** \ingroup gui + * \class QgsColorRampWidget + * A color ramp widget. This widget consists of an interactive box filled with a color which varies along + * its length by a single color component (eg, varying saturation from 0 to 100%). + * \note Added in version 2.5 + */ + +class QgsColorRampWidget : QgsColorWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /*! Specifies the orientation of a color ramp + */ + enum Orientation + { + Horizontal, /*!< horizontal ramp */ + Vertical /*!< vertical ramp */ + }; + + /**Construct a new color ramp widget. + * @param parent parent QWidget for the widget + * @param component color component which varies along the ramp + * @param orientation orientation for widget + */ + QgsColorRampWidget( QWidget* parent /TransferThis/ = 0, + const ColorComponent component = QgsColorWidget::Red, + const Orientation orientation = QgsColorRampWidget::Horizontal ); + + virtual ~QgsColorRampWidget(); + + virtual QSize sizeHint() const; + void paintEvent( QPaintEvent* event ); + + /**Sets the orientation for the color ramp + * @param orientation new orientation for the ramp + * @see orientation + */ + void setOrientation( const Orientation orientation ); + + /**Fetches the orientation for the color ramp + * @returns orientation for the ramp + * @see setOrientation + */ + Orientation orientation() const; + + /**Sets the margin between the edge of the widget and the ramp + * @param margin margin around the ramp + * @see interiorMargin + */ + void setInteriorMargin( const int margin ); + + /**Fetches the margin between the edge of the widget and the ramp + * @returns margin around the ramp + * @see setInteriorMargin + */ + int interiorMargin() const; + + /**Sets whether the ramp should be drawn within a frame + * @param showFrame set to true to draw a frame around the ramp + * @see showFrame + */ + void setShowFrame( const bool showFrame ); + + /**Fetches whether the ramp is drawn within a frame + * @returns true if a frame is drawn around the ramp + * @see setShowFrame + */ + bool showFrame() const; + + /**Sets the size for drawing the triangular markers on the ramp + * @param markerSize marker size in pixels + */ + void setMarkerSize( const int markerSize ); + + signals: + + /**Emitted when the widget's color component value changes + * @param value new value of color component + */ + void valueChanged( const int value ); + + protected: + + virtual void mouseMoveEvent( QMouseEvent *event ); + virtual void mousePressEvent( QMouseEvent *event ); + virtual void keyPressEvent( QKeyEvent * event ); +}; + + +/** \ingroup gui + * \class QgsColorSliderWidget + * A composite horizontal color ramp widget and associated spinbox for manual value entry. + * \note Added in version 2.5 + */ + +class QgsColorSliderWidget : QgsColorWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /**Construct a new color slider widget. + * @param parent parent QWidget for the widget + * @param component color component which is controlled by the slider + */ + QgsColorSliderWidget( QWidget* parent /TransferThis/ = 0, const ColorComponent component = QgsColorWidget::Red ); + + virtual ~QgsColorSliderWidget(); + + virtual void setComponent( const ColorComponent component ); + virtual void setComponentValue( const int value ); + virtual void setColor( const QColor color ); + +}; + + +/** \ingroup gui + * \class QgsColorTextWidget + * A line edit widget which displays colors as text and accepts string representations + * of colors. + * \note Added in version 2.5 + */ + +class QgsColorTextWidget : QgsColorWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /**Construct a new color line edit widget. + * @param parent parent QWidget for the widget + */ + QgsColorTextWidget( QWidget* parent /TransferThis/ = 0 ); + + virtual ~QgsColorTextWidget(); + + virtual void setColor( const QColor color ); + + protected: + void resizeEvent( QResizeEvent * event ); + +}; + + +/** \ingroup gui + * \class QgsColorPreviewWidget + * A preview box which displays one or two colors as swatches. + * \note Added in version 2.5 + */ + +class QgsColorPreviewWidget : QgsColorWidget +{ +%TypeHeaderCode +#include +%End + + public: + + /**Construct a new color preview widget. + * @param parent parent QWidget for the widget + */ + QgsColorPreviewWidget( QWidget* parent /TransferThis/ = 0 ); + + virtual ~QgsColorPreviewWidget(); + + void paintEvent( QPaintEvent* event ); + + /**Returns the secondary color for the widget + * @returns secondary widget color, or an invalid color if the widget + * has no secondary color + * @see color + * @see setColor2 + */ + QColor color2() const; + + public slots: + + /**Sets the second color for the widget + * @param color secondary widget color. Set to an invalid color to prevent + * drawing of a secondary color + * @see setColor + * @see color2 + */ + virtual void setColor2( const QColor& color ); + +}; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6596393a13b..89761542c5c 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -130,6 +130,7 @@ qgscolorbuttonv2.cpp qgscolordialog.cpp qgscolorswatchgrid.cpp qgscolorschemelist.cpp +qgscolorwidgets.cpp qgscodeeditor.cpp qgscodeeditorpython.cpp qgscodeeditorsql.cpp @@ -336,6 +337,7 @@ qgscodeeditorsql.h qgscodeeditorhtml.h qgscodeeditorcss.h qgscolordialog.h +qgscolorwidgets.h qgsprevieweffect.h qgscomposerruler.h qgscomposerview.h @@ -427,6 +429,7 @@ qgsbusyindicatordialog.h qgscharacterselectdialog.h qgscollapsiblegroupbox.h qgscolordialog.h +qgscolorwidgets.h qgscredentialdialog.h qgscursors.h qgsdatadefinedbutton.h diff --git a/src/gui/qgscolorwidgets.cpp b/src/gui/qgscolorwidgets.cpp new file mode 100644 index 00000000000..c3c3a7da20a --- /dev/null +++ b/src/gui/qgscolorwidgets.cpp @@ -0,0 +1,1513 @@ +/*************************************************************************** + qgscolorwidgets.cpp - color selection widgets + --------------------- + begin : September 2014 + copyright : (C) 2014 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 "qgscolorwidgets.h" +#include "qgsapplication.h" +#include "qgssymbollayerv2utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// +// QgsColorWidget +// + +QgsColorWidget::QgsColorWidget( QWidget* parent, const ColorComponent component ) + : QWidget( parent ) + , mCurrentColor( Qt::red ) + , mComponent( component ) +{ + +} + +QgsColorWidget::~QgsColorWidget() +{ + +} + +int QgsColorWidget::componentValue() const +{ + return componentValue( mComponent ); +} + +int QgsColorWidget::componentValue( const QgsColorWidget::ColorComponent component ) const +{ + if ( !mCurrentColor.isValid() ) + { + return -1; + } + + switch ( component ) + { + case QgsColorWidget::Red: + return mCurrentColor.red(); + case QgsColorWidget::Green: + return mCurrentColor.green(); + case QgsColorWidget::Blue: + return mCurrentColor.blue(); + case QgsColorWidget::Hue: + //hue is treated specially, to avoid -1 hues values from QColor for ambiguous hues + return hue(); + case QgsColorWidget::Saturation: + return mCurrentColor.hsvSaturation(); + case QgsColorWidget::Value: + return mCurrentColor.value(); + case QgsColorWidget::Alpha: + return mCurrentColor.alpha(); + default: + return -1; + } +} + +int QgsColorWidget::componentRange() const +{ + return componentRange( mComponent ); +} + +int QgsColorWidget::componentRange( const QgsColorWidget::ColorComponent component ) const +{ + if ( component == QgsColorWidget::Multiple ) + { + //no component + return -1; + } + + if ( component == QgsColorWidget::Hue ) + { + //hue ranges to 359 + return 359; + } + else + { + //all other components range to 255 + return 255; + } +} + +int QgsColorWidget::hue() const +{ + if ( mCurrentColor.hue() >= 0 ) + { + return mCurrentColor.hue() ; + } + else + { + return mExplicitHue; + } +} + +void QgsColorWidget::alterColor( QColor& color, const QgsColorWidget::ColorComponent component, const int newValue ) const +{ + int h, s, v, a; + color.getHsv( &h, &s, &v, &a ); + + //clip value to sensible range + int clippedValue = qMin( qMax( 0, newValue ), componentRange( component ) ); + + switch ( component ) + { + case QgsColorWidget::Red: + color.setRed( clippedValue ); + return; + case QgsColorWidget::Green: + color.setGreen( clippedValue ); + return; + case QgsColorWidget::Blue: + color.setBlue( clippedValue ); + return; + case QgsColorWidget::Hue: + color.setHsv( clippedValue, s, v, a ); + return; + case QgsColorWidget::Saturation: + color.setHsv( h, clippedValue, v, a ); + return; + case QgsColorWidget::Value: + color.setHsv( h, s, clippedValue, a ); + return; + case QgsColorWidget::Alpha: + color.setAlpha( clippedValue ); + return; + default: + return; + } +} + +const QPixmap &QgsColorWidget::transparentBackground() +{ + static QPixmap transpBkgrd; + + if ( transpBkgrd.isNull() ) + transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" ); + + return transpBkgrd; +} + +QColor QgsColorWidget::color() const +{ + return mCurrentColor; +} + +void QgsColorWidget::setComponent( const QgsColorWidget::ColorComponent component ) +{ + if ( component == mComponent ) + { + return; + } + + mComponent = component; + update(); +} + +void QgsColorWidget::setComponentValue( const int value ) +{ + if ( mComponent == QgsColorWidget::Multiple ) + { + return; + } + + //clip value to valid range + int valueClipped = qMin( value, componentRange() ); + valueClipped = qMax( valueClipped, 0 ); + + int r, g, b, a; + mCurrentColor.getRgb( &r, &g, &b, &a ); + int h, s, v; + mCurrentColor.getHsv( &h, &s, &v ); + + switch ( mComponent ) + { + case QgsColorWidget::Red: + if ( r == valueClipped ) + { + return; + } + mCurrentColor.setRed( valueClipped ); + break; + case QgsColorWidget::Green: + if ( g == valueClipped ) + { + return; + } + mCurrentColor.setGreen( valueClipped ); + break; + case QgsColorWidget::Blue: + if ( b == valueClipped ) + { + return; + } + mCurrentColor.setBlue( valueClipped ); + break; + case QgsColorWidget::Hue: + if ( h == valueClipped ) + { + return; + } + mCurrentColor.setHsv( valueClipped, s, v, a ); + break; + case QgsColorWidget::Saturation: + if ( s == valueClipped ) + { + return; + } + mCurrentColor.setHsv( h, valueClipped, v, a ); + break; + case QgsColorWidget::Value: + if ( v == valueClipped ) + { + return; + } + mCurrentColor.setHsv( h, s, valueClipped, a ); + break; + case QgsColorWidget::Alpha: + if ( a == valueClipped ) + { + return; + } + mCurrentColor.setAlpha( valueClipped ); + break; + default: + return; + } + + //update recorded hue + if ( mCurrentColor.hue() >= 0 ) + { + mExplicitHue = mCurrentColor.hue(); + } + + update(); +} + +void QgsColorWidget::setColor( const QColor color ) +{ + if ( color == mCurrentColor ) + { + return; + } + + mCurrentColor = color; + + //update recorded hue + if ( color.hue() >= 0 ) + { + mExplicitHue = color.hue(); + } + + update(); +} + + +// +// QgsColorWheel +// + +QgsColorWheel::QgsColorWheel( QWidget *parent ) + : QgsColorWidget( parent ) + , mMargin( 4 ) + , mWheelThickness( 18 ) + , mClickedPart( QgsColorWheel::None ) + , mWheelImage( 0 ) + , mTriangleImage( 0 ) + , mWidgetImage( 0 ) + , mWheelDirty( true ) + , mTriangleDirty( true ) +{ + //create wheel hue brush - only do this once + QConicalGradient wheelGradient = QConicalGradient( 0, 0, 0 ); + int wheelStops = 20; + QColor gradColor = QColor::fromHsvF( 1.0, 1.0, 1.0 ); + for ( int pos = 0; pos <= wheelStops; ++pos ) + { + double relativePos = ( double )pos / wheelStops; + gradColor.setHsvF( relativePos, 1, 1 ); + wheelGradient.setColorAt( relativePos, gradColor ); + } + mWheelBrush = QBrush( wheelGradient ); +} + +QgsColorWheel::~QgsColorWheel() +{ + delete mWheelImage; + delete mTriangleImage; + delete mWidgetImage; +} + +void QgsColorWheel::paintEvent( QPaintEvent *event ) +{ + Q_UNUSED( event ); + QPainter painter( this ); + + //draw a frame + QStyleOptionFrameV3 option = QStyleOptionFrameV3(); + option.initFrom( this ); + option.state = this->hasFocus() ? QStyle::State_Active : QStyle::State_None; + style()->drawPrimitive( QStyle::PE_Frame, &option, &painter ); + + if ( !mWidgetImage || !mWheelImage || !mTriangleImage ) + { + createImages( size() ); + } + + //draw everything in an image + mWidgetImage->fill( Qt::transparent ); + QPainter imagePainter( mWidgetImage ); + imagePainter.setRenderHint( QPainter::Antialiasing ); + + if ( mWheelDirty ) + { + //need to redraw the wheel image + createWheel(); + } + + //draw wheel centered on widget + QPointF center = QPointF( width() / 2.0 , height() / 2.0 ); + imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mWheelImage ); + + //draw hue marker + int h = hue(); + double length = mWheelImage->width() / 2.0; + QLineF hueMarkerLine = QLineF( center.x(), center.y(), center.x() + length, center.y() ); + hueMarkerLine.setAngle( h ); + imagePainter.save(); + //use sourceIn mode for nicer antialiasing + imagePainter.setCompositionMode( QPainter::CompositionMode_SourceIn ); + QPen pen; + pen.setWidth( 2 ); + //adapt pen color for hue + pen.setColor( h > 20 && h < 200 ? Qt::black : Qt::white ); + imagePainter.setPen( pen ); + imagePainter.drawLine( hueMarkerLine ); + imagePainter.restore(); + + //draw triangle + if ( mTriangleDirty ) + { + createTriangle(); + } + imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mTriangleImage ); + + //draw current color marker + double triangleRadius = length - mWheelThickness - 1; + + //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann + double lightness = mCurrentColor.lightnessF(); + double hueRadians = ( h * M_PI / 180.0 ); + double hx = cos( hueRadians ) * triangleRadius; + double hy = -sin( hueRadians ) * triangleRadius; + double sx = -cos( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius; + double sy = -sin( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius; + double vx = -cos( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius; + double vy = sin( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius; + double mx = ( sx + vx ) / 2.0; + double my = ( sy + vy ) / 2.0; + + double a = ( 1 - 2.0 * fabs( lightness - 0.5 ) ) * mCurrentColor.hslSaturationF(); + double x = sx + ( vx - sx ) * lightness + ( hx - mx ) * a; + double y = sy + ( vy - sy ) * lightness + ( hy - my ) * a; + + //adapt pen color for lightness + pen.setColor( lightness > 0.7 ? Qt::black : Qt::white ); + imagePainter.setPen( pen ); + imagePainter.setBrush( Qt::NoBrush ); + imagePainter.drawEllipse( QPointF( x + center.x(), y + center.y() ), 4.0, 4.0 ); + imagePainter.end(); + + //draw image onto widget + painter.drawImage( QPoint( 0, 0 ), *mWidgetImage ); + painter.end(); +} + +void QgsColorWheel::setColor( const QColor color ) +{ + if ( color.hue() >= 0 && color.hue() != hue() ) + { + //hue has changed, need to redraw the triangle + mTriangleDirty = true; + } + + QgsColorWidget::setColor( color ); +} + +void QgsColorWheel::createImages( const QSizeF size ) +{ + double wheelSize = qMin( size.width(), size.height() ) - mMargin * 2.0; + mWheelThickness = wheelSize / 15.0; + + //recreate cache images at correct size + delete mWheelImage; + mWheelImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 ); + delete mTriangleImage; + mTriangleImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 ); + delete mWidgetImage; + mWidgetImage = new QImage( size.width(), size.height(), QImage::Format_ARGB32 ); + + //trigger a redraw for the images + mWheelDirty = true; + mTriangleDirty = true; +} + +void QgsColorWheel::resizeEvent( QResizeEvent *event ) +{ + //recreate images for new size + createImages( event->size() ); + QgsColorWidget::resizeEvent( event ); +} + +void QgsColorWheel::setColorFromPos( const QPointF pos ) +{ + QPointF center = QPointF( width() / 2.0 , height() / 2.0 ); + //line from center to mouse position + QLineF line = QLineF( center.x(), center.y(), pos.x(), pos.y() ); + + QColor newColor = QColor(); + + int h, s, l, alpha; + mCurrentColor.getHsl( &h, &s, &l, &alpha ); + //override hue with explicit hue, so we don't get -1 values from QColor for hue + h = hue(); + + if ( mClickedPart == QgsColorWheel::Triangle ) + { + //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann + + //position of event relative to triangle center + double x = pos.x() - center.x(); + double y = pos.y() - center.y(); + + double eventAngleRadians = line.angle() * M_PI / 180.0; + double hueRadians = h * M_PI / 180.0; + double rad0 = fmod( eventAngleRadians + 2.0 * M_PI - hueRadians , 2.0 * M_PI ); + double rad1 = fmod( rad0, (( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 ); + double length = mWheelImage->width() / 2.0; + double triangleLength = length - mWheelThickness - 1; + + double a = 0.5 * triangleLength; + double b = tan( rad1 ) * a; + double r = sqrt( x * x + y * y ); + double maxR = sqrt( a * a + b * b ); + + if ( r > maxR ) + { + double dx = tan( rad1 ) * r; + double rad2 = atan( dx / maxR ); + rad2 = qMin( rad2, M_PI / 3.0 ); + rad2 = qMax( rad2, -M_PI / 3.0 ); + eventAngleRadians += rad2 - rad1; + rad0 = fmod( eventAngleRadians + 2.0 * M_PI - hueRadians , 2.0 * M_PI ); + rad1 = fmod( rad0, (( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 ); + b = tan( rad1 ) * a; + r = sqrt( a * a + b * b ); + } + x = ( cos( eventAngleRadians ) * r ); + y = ( -sin( eventAngleRadians ) * r ); + + double triangleSideLength = sqrt( 3.0 ) * triangleLength; + double newL = (( -sin( rad0 ) * r ) / triangleSideLength ) + 0.5; + double widthShare = 1.0 - ( fabs( newL - 0.5 ) * 2.0 ); + double newS = ((( cos( rad0 ) * r ) + ( triangleLength / 2.0 ) ) / ( 1.5 * triangleLength ) ) / widthShare; + s = qMin( qRound( qMax( 0.0, newS ) * 255.0 ), 255 ); + l = qMin( qRound( qMax( 0.0, newL ) * 255.0 ), 255 ); + newColor = QColor::fromHsl( h, s, l ); + //explicitly set the hue again, so that it's exact + newColor.setHsv( h, newColor.hsvSaturation(), newColor.value(), alpha ); + } + else if ( mClickedPart == QgsColorWheel::Wheel ) + { + //use hue angle + s = mCurrentColor.hsvSaturation(); + int v = mCurrentColor.value(); + int newHue = line.angle(); + newColor = QColor::fromHsv( newHue, s, v, alpha ); + //hue has changed, need to redraw triangle + mTriangleDirty = true; + } + + if ( newColor.isValid() && newColor != mCurrentColor ) + { + //color has changed + mCurrentColor = QColor( newColor ); + + if ( mCurrentColor.hue() >= 0 ) + { + //color has a valid hue, so update the QgsColorWidget's explicit hue + mExplicitHue = mCurrentColor.hue(); + } + + update(); + emit colorChanged( mCurrentColor ); + } +} + +void QgsColorWheel::mouseMoveEvent( QMouseEvent *event ) +{ + setColorFromPos( event->posF() ); +} + +void QgsColorWheel::mousePressEvent( QMouseEvent *event ) +{ + //calculate where the event occurred -- on the wheel or inside the triangle? + + //create a line from the widget's center to the event + QLineF line = QLineF( width() / 2.0, height() / 2.0, event->pos().x(), event->pos().y() ); + + double innerLength = mWheelImage->width() / 2.0 - mWheelThickness; + if ( line.length() < innerLength ) + { + mClickedPart = QgsColorWheel::Triangle; + } + else + { + mClickedPart = QgsColorWheel::Wheel; + } + setColorFromPos( event->posF() ); +} + +void QgsColorWheel::mouseReleaseEvent( QMouseEvent *event ) +{ + Q_UNUSED( event ); + mClickedPart = QgsColorWheel::None; +} + +void QgsColorWheel::createWheel() +{ + if ( !mWheelImage ) + { + return; + } + + int maxSize = qMin( mWheelImage->width(), mWheelImage->height() ); + double wheelRadius = maxSize / 2.0; + + mWheelImage->fill( Qt::transparent ); + QPainter p( mWheelImage ); + p.setRenderHint( QPainter::Antialiasing ); + p.setBrush( mWheelBrush ); + p.setPen( Qt::NoPen ); + + //draw hue wheel as a circle + p.translate( wheelRadius, wheelRadius ); + p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius, wheelRadius ); + + //cut hole in center of circle to make a ring + p.setCompositionMode( QPainter::CompositionMode_DestinationOut ); + p.setBrush( QBrush( Qt::black ) ); + p.drawEllipse( QPointF( 0.0 , 0.0 ), wheelRadius - mWheelThickness, wheelRadius - mWheelThickness ); + p.end(); + + mWheelDirty = false; +} + +void QgsColorWheel::createTriangle() +{ + if ( !mWheelImage || !mTriangleImage ) + { + return; + } + + QPointF center = QPointF( mWheelImage->width() / 2.0, mWheelImage->height() / 2.0 ); + mTriangleImage->fill( Qt::transparent ); + + QPainter imagePainter( mTriangleImage ); + imagePainter.setRenderHint( QPainter::Antialiasing ); + + int angle = hue(); + double wheelRadius = mWheelImage->width() / 2.0; + double triangleRadius = wheelRadius - mWheelThickness - 1; + + //pure version of hue (at full saturation and value) + QColor pureColor = QColor::fromHsv( angle, 255, 255 ); + //create copy of color but with 0 alpha + QColor alphaColor = QColor( pureColor ); + alphaColor.setAlpha( 0 ); + + //some rather ugly shortcuts to obtain corners and midpoints of triangle + QLineF line1 = QLineF( center.x(), center.y(), center.x() - triangleRadius * cos( M_PI / 3.0 ), center.y() - triangleRadius * sin( M_PI / 3.0 ) ); + QLineF line2 = QLineF( center.x(), center.y(), center.x() + triangleRadius, center.y() ); + QLineF line3 = QLineF( center.x(), center.y(), center.x() - triangleRadius * cos( M_PI / 3.0 ) , center.y() + triangleRadius * sin( M_PI / 3.0 ) ); + QLineF line4 = QLineF( center.x(), center.y(), center.x() - triangleRadius * cos( M_PI / 3.0 ) , center.y() ); + QLineF line5 = QLineF( center.x(), center.y(), ( line2.p2().x() + line1.p2().x() ) / 2.0, ( line2.p2().y() + line1.p2().y() ) / 2.0 ); + line1.setAngle( line1.angle() + angle ); + line2.setAngle( line2.angle() + angle ); + line3.setAngle( line3.angle() + angle ); + line4.setAngle( line4.angle() + angle ); + line5.setAngle( line5.angle() + angle ); + QPointF p1 = line1.p2(); + QPointF p2 = line2.p2(); + QPointF p3 = line3.p2(); + QPointF p4 = line4.p2(); + QPointF p5 = line5.p2(); + + //inspired by Tim Baumann's work at https://github.com/timjb/colortriangle/blob/master/colortriangle.js + QLinearGradient colorGrad = QLinearGradient( p4.x(), p4.y(), p2.x(), p2.y() ); + colorGrad.setColorAt( 0, alphaColor ); + colorGrad.setColorAt( 1, pureColor ); + QLinearGradient whiteGrad = QLinearGradient( p3.x(), p3.y(), p5.x(), p5.y() ); + whiteGrad.setColorAt( 0, QColor( 255, 255, 255, 255 ) ); + whiteGrad.setColorAt( 1, QColor( 255, 255, 255, 0 ) ); + + QPolygonF triangle; + triangle << p2 << p1 << p3 << p2; + imagePainter.setPen( Qt::NoPen ); + //start with a black triangle + imagePainter.setBrush( QBrush( Qt::black ) ); + imagePainter.drawPolygon( triangle ); + //draw a gradient from transparent to the pure color at the triangle's tip + imagePainter.setBrush( QBrush( colorGrad ) ); + imagePainter.drawPolygon( triangle ); + //draw a white gradient using additive composition mode + imagePainter.setCompositionMode( QPainter::CompositionMode_Plus ); + imagePainter.setBrush( QBrush( whiteGrad ) ); + imagePainter.drawPolygon( triangle ); + + //above process results in some small artifacts on the edge of the triangle. Let's clear these up + //use source composition mode and draw an outline using a transparent pen + //this clears the edge pixels and leaves a nice smooth image + imagePainter.setCompositionMode( QPainter::CompositionMode_Source ); + imagePainter.setBrush( Qt::NoBrush ); + imagePainter.setPen( QPen( Qt::transparent ) ); + imagePainter.drawPolygon( triangle ); + + imagePainter.end(); + mTriangleDirty = false; +} + + + +// +// QgsColorBox +// + +QgsColorBox::QgsColorBox( QWidget *parent, const ColorComponent component ) + : QgsColorWidget( parent, component ) + , mMargin( 2 ) + , mBoxImage( 0 ) + , mDirty( true ) +{ + setFocusPolicy( Qt::StrongFocus ); + setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); + + mBoxImage = new QImage( width() - mMargin * 2, height() - mMargin * 2, QImage::Format_RGB32 ); +} + +QgsColorBox::~QgsColorBox() +{ + delete mBoxImage; +} + +QSize QgsColorBox::sizeHint() const +{ + return QSize( 200, 200 ); +} + +void QgsColorBox::paintEvent( QPaintEvent *event ) +{ + Q_UNUSED( event ); + QPainter painter( this ); + + QStyleOptionFrameV3 option; + option.initFrom( this ); + option.state = hasFocus() ? QStyle::State_Active : QStyle::State_None; + style()->drawPrimitive( QStyle::PE_Frame, &option, &painter ); + + if ( mDirty ) + { + createBox(); + } + + //draw background image + painter.drawImage( QPoint( mMargin, mMargin ), *mBoxImage ); + + //draw cross lines + double xPos = mMargin + ( width() - 2 * mMargin - 1 ) * ( double )xComponentValue() / ( double )valueRangeX(); + double yPos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * ( double )yComponentValue() / ( double )valueRangeY(); + + painter.setBrush( Qt::white ); + painter.setPen( Qt::NoPen ); + + painter.drawRect( xPos - 1, mMargin, 3, height() - 2 * mMargin - 1 ); + painter.drawRect( mMargin, yPos - 1 , width() - 2 * mMargin - 1, 3 ); + painter.setPen( Qt::black ); + painter.drawLine( xPos, mMargin, xPos, height() - mMargin - 1 ); + painter.drawLine( mMargin, yPos, width() - mMargin - 1, yPos ); + + painter.end(); +} + +void QgsColorBox::setComponent( const QgsColorWidget::ColorComponent component ) +{ + if ( component != mComponent ) + { + //need to redraw + mDirty = true; + } + QgsColorWidget::setComponent( component ); +} + +void QgsColorBox::setColor( const QColor color ) +{ + //check if we need to redraw the box image + if ( mComponent == QgsColorWidget::Red && mCurrentColor.red() != color.red() ) + { + mDirty = true; + } + else if ( mComponent == QgsColorWidget::Green && mCurrentColor.green() != color.green() ) + { + mDirty = true; + } + else if ( mComponent == QgsColorWidget::Blue && mCurrentColor.blue() != color.blue() ) + { + mDirty = true; + } + else if ( mComponent == QgsColorWidget::Hue && color.hsvHue() >= 0 && hue() != color.hsvHue() ) + { + mDirty = true; + } + else if ( mComponent == QgsColorWidget::Saturation && mCurrentColor.hsvSaturation() != color.hsvSaturation() ) + { + mDirty = true; + } + else if ( mComponent == QgsColorWidget::Value && mCurrentColor.value() != color.value() ) + { + mDirty = true; + } + QgsColorWidget::setColor( color ); +} + +void QgsColorBox::resizeEvent( QResizeEvent *event ) +{ + mDirty = true; + delete mBoxImage; + mBoxImage = new QImage( event->size().width() - mMargin * 2, event->size().height() - mMargin * 2, QImage::Format_RGB32 ); + QgsColorWidget::resizeEvent( event ); +} + +void QgsColorBox::mouseMoveEvent( QMouseEvent *event ) +{ + setColorFromPoint( event->pos() ); +} + +void QgsColorBox::mousePressEvent( QMouseEvent *event ) +{ + setColorFromPoint( event->pos() ); +} + +void QgsColorBox::createBox() +{ + int maxValueX = mBoxImage->width(); + int maxValueY = mBoxImage->height(); + + //create a temporary color object + QColor currentColor = QColor( mCurrentColor ); + int colorComponentValue; + + for ( int y = 0; y < maxValueY; ++y ) + { + QRgb* scanLine = ( QRgb* )mBoxImage->scanLine( y ); + + colorComponentValue = int( valueRangeY() - valueRangeY() * ( double( y ) / maxValueY ) ); + alterColor( currentColor, yComponent(), colorComponentValue ); + for ( int x = 0; x < maxValueX; ++x ) + { + colorComponentValue = int( valueRangeX() * ( double( x ) / maxValueX ) ); + alterColor( currentColor, xComponent(), colorComponentValue ); + scanLine[x] = currentColor.rgb(); + } + } + mDirty = false; +} + +int QgsColorBox::valueRangeX() const +{ + return componentRange( xComponent() ); +} + +int QgsColorBox::valueRangeY() const +{ + return componentRange( yComponent() ); +} + +QgsColorWidget::ColorComponent QgsColorBox::yComponent() const +{ + switch ( mComponent ) + { + case QgsColorWidget::Red: + return QgsColorWidget::Green; + case QgsColorWidget:: Green: + case QgsColorWidget:: Blue: + return QgsColorWidget::Red; + case QgsColorWidget::Hue: + return QgsColorWidget:: Saturation; + case QgsColorWidget:: Saturation: + case QgsColorWidget:: Value: + return QgsColorWidget::Hue; + default: + //should not occur + return QgsColorWidget::Red; + } +} + +int QgsColorBox::yComponentValue() const +{ + return componentValue( yComponent() ); +} + +QgsColorWidget::ColorComponent QgsColorBox::xComponent() const +{ + switch ( mComponent ) + { + case QgsColorWidget::Red: + case QgsColorWidget:: Green: + return QgsColorWidget::Blue; + case QgsColorWidget:: Blue: + return QgsColorWidget::Green; + case QgsColorWidget::Hue: + case QgsColorWidget:: Saturation: + return QgsColorWidget:: Value; + case QgsColorWidget:: Value: + return QgsColorWidget::Saturation; + default: + //should not occur + return QgsColorWidget::Red; + } +} + +int QgsColorBox::xComponentValue() const +{ + return componentValue( xComponent() ); +} + +void QgsColorBox::setColorFromPoint( const QPoint &point ) +{ + int valX = valueRangeX() * ( point.x() - mMargin ) / ( width() - 2 * mMargin - 1 ); + valX = qMin( qMax( valX, 0 ), valueRangeX() ); + + int valY = valueRangeY() - valueRangeY() * ( point.y() - mMargin ) / ( height() - 2 * mMargin - 1 ); + valY = qMin( qMax( valY, 0 ), valueRangeY() ); + + QColor color = QColor( mCurrentColor ); + alterColor( color, xComponent(), valX ); + alterColor( color, yComponent(), valY ); + + if ( color == mCurrentColor ) + { + return; + } + + if ( color.hue() >= 0 ) + { + mExplicitHue = color.hue(); + } + + mCurrentColor = color; + update(); + emit colorChanged( color ); +} + + +// +// QgsColorRampWidget +// + +QgsColorRampWidget::QgsColorRampWidget( QWidget *parent, + const QgsColorWidget::ColorComponent component, + const Orientation orientation ) + : QgsColorWidget( parent, component ) + , mMargin( 4 ) + , mShowFrame( false ) +{ + setFocusPolicy( Qt::StrongFocus ); + setOrientation( orientation ); + + //create triangle polygons + setMarkerSize( 5 ); +} + +QgsColorRampWidget::~QgsColorRampWidget() +{ + +} + +QSize QgsColorRampWidget::sizeHint() const +{ + if ( mOrientation == QgsColorRampWidget::Horizontal ) + { + //horizontal + return QSize( 200, 28 ); + } + else + { + //vertical + return QSize( 18, 200 ); + } +} + +void QgsColorRampWidget::paintEvent( QPaintEvent *event ) +{ + Q_UNUSED( event ); + QPainter painter( this ); + + if ( mShowFrame ) + { + //draw frame + QStyleOptionFrameV3 option; + option.initFrom( this ); + option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None; + style()->drawPrimitive( QStyle::PE_Frame, &option, &painter ); + } + + if ( hasFocus() ) + { + //draw focus rect + QStyleOptionFocusRect option; + option.initFrom( this ); + option.state = QStyle::State_KeyboardFocusChange; + style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter ); + } + + if ( mComponent != QgsColorWidget::Alpha ) + { + int maxValue = ( mOrientation == QgsColorRampWidget::Horizontal ? width() : height() ) - 1 - 2 * mMargin; + QColor color = QColor( mCurrentColor ); + color.setAlpha( 255 ); + QPen pen; + pen.setWidth( 0 ); + painter.setPen( pen ); + painter.setBrush( Qt::NoBrush ); + + //draw background ramp + for ( int c = 0; c <= maxValue; ++c ) + { + int colorVal = componentRange() * ( double )c / maxValue; + //vertical sliders are reversed + if ( mOrientation == QgsColorRampWidget::Vertical ) + { + colorVal = componentRange() - colorVal; + } + alterColor( color, mComponent, colorVal ); + if ( color.hue() < 0 ) + { + color.setHsv( hue(), color.saturation(), color.value() ); + } + pen.setColor( color ); + painter.setPen( pen ); + if ( mOrientation == QgsColorRampWidget::Horizontal ) + { + //horizontal + painter.drawLine( c + mMargin, mMargin, c + mMargin, height() - mMargin - 1 ); + } + else + { + //vertical + painter.drawLine( mMargin, c + mMargin, width() - mMargin - 1, c + mMargin ); + } + } + } + else if ( mComponent == QgsColorWidget::Alpha ) + { + //alpha ramps are drawn differently + //start with the checkboard pattern + QBrush checkBrush = QBrush( transparentBackground() ); + painter.setBrush( checkBrush ); + painter.setPen( Qt::NoPen ); + painter.drawRect( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 ); + QLinearGradient colorGrad; + if ( mOrientation == QgsColorRampWidget::Horizontal ) + { + //horizontal + colorGrad = QLinearGradient( mMargin, 0, width() - mMargin - 1, 0 ); + } + else + { + //vertical + colorGrad = QLinearGradient( 0, mMargin, 0, height() - mMargin - 1 ); + } + QColor transparent = QColor( mCurrentColor ); + transparent.setAlpha( 0 ); + colorGrad.setColorAt( 0, transparent ); + QColor opaque = QColor( mCurrentColor ); + opaque.setAlpha( 255 ); + colorGrad.setColorAt( 1, opaque ); + QBrush colorBrush = QBrush( colorGrad ); + painter.setBrush( colorBrush ); + painter.drawRect( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 ); + } + + if ( mOrientation == QgsColorRampWidget::Horizontal ) + { + //draw marker triangles for horizontal ramps + painter.setRenderHint( QPainter::Antialiasing ); + painter.setBrush( QBrush( Qt::black ) ); + painter.setPen( Qt::NoPen ); + painter.translate( mMargin + ( width() - 2 * mMargin ) * ( double )componentValue() / componentRange(), mMargin - 1 ); + painter.drawPolygon( mTopTriangle ); + painter.translate( 0, height() - mMargin - 2 ); + painter.setBrush( QBrush( Qt::white ) ); + painter.drawPolygon( mBottomTriangle ); + painter.end(); + } + else + { + //draw cross lines for vertical ramps + int ypos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * ( double )componentValue() / componentRange(); + painter.setBrush( Qt::white ); + painter.setPen( Qt::NoPen ); + painter.drawRect( mMargin, ypos - 1, width( ) - 2 * mMargin - 1, 3 ); + painter.setPen( Qt::black ); + painter.drawLine( mMargin, ypos, width() - mMargin - 1, ypos ); + } +} + +void QgsColorRampWidget::setOrientation( const QgsColorRampWidget::Orientation orientation ) +{ + mOrientation = orientation; + if ( orientation == QgsColorRampWidget::Horizontal ) + { + //horizontal + setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); + } + else + { + //vertical + setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding ); + } + updateGeometry(); +} + +void QgsColorRampWidget::setInteriorMargin( const int margin ) +{ + if ( margin == mMargin ) + { + return; + } + mMargin = margin; + update(); +} + +void QgsColorRampWidget::setShowFrame( const bool showFrame ) +{ + if ( showFrame == mShowFrame ) + { + return; + } + mShowFrame = showFrame; + update(); +} + +void QgsColorRampWidget::setMarkerSize( const int markerSize ) +{ + //create triangle polygons + mTopTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, markerSize ); + mBottomTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, -markerSize ); + update(); +} + +void QgsColorRampWidget::mouseMoveEvent( QMouseEvent *event ) +{ + setColorFromPoint( event->posF() ); +} + +void QgsColorRampWidget::mousePressEvent( QMouseEvent *event ) +{ + setColorFromPoint( event->posF() ); +} + +void QgsColorRampWidget::keyPressEvent( QKeyEvent *event ) +{ + int oldValue = componentValue(); + if (( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ) ) + || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Up ) ) ) + { + setComponentValue( componentValue() + 1 ); + } + else if (( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Down ) ) + || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Down ) ) ) + { + setComponentValue( componentValue() - 1 ); + } + else if (( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageDown ) + || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageUp ) ) + { + setComponentValue( componentValue() + 10 ); + } + else if (( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageUp ) + || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageDown ) ) + { + setComponentValue( componentValue() - 10 ); + } + else if (( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_Home ) + || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_End ) ) + { + setComponentValue( 0 ); + } + else if (( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_End ) + || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_Home ) ) + { + //set to maximum value + setComponentValue( componentRange() ); + } + else + { + QgsColorWidget::keyPressEvent( event ); + return; + } + + if ( componentValue() != oldValue ) + { + //value has changed + emit colorChanged( mCurrentColor ); + emit valueChanged( componentValue() ); + } +} + +void QgsColorRampWidget::setColorFromPoint( const QPointF &point ) +{ + int oldValue = componentValue(); + int val; + if ( mOrientation == QgsColorRampWidget::Horizontal ) + { + val = componentRange() * ( point.x() - mMargin ) / ( width() - 2 * mMargin ); + } + else + { + val = componentRange() - componentRange() * ( point.y() - mMargin ) / ( height() - 2 * mMargin ); + } + val = qMax( 0, qMin( val, componentRange() ) ); + setComponentValue( val ); + + if ( componentValue() != oldValue ) + { + //value has changed + emit colorChanged( mCurrentColor ); + emit valueChanged( componentValue() ); + } +} + +// +// QgsColorSliderWidget +// + +QgsColorSliderWidget::QgsColorSliderWidget( QWidget *parent, const ColorComponent component ) + : QgsColorWidget( parent, component ) + , mRampWidget( 0 ) + , mSpinBox( 0 ) +{ + QHBoxLayout* hLayout = new QHBoxLayout(); + hLayout->setMargin( 0 ); + hLayout->setSpacing( 5 ); + + mRampWidget = new QgsColorRampWidget( 0, component ); + mRampWidget->setColor( mCurrentColor ); + hLayout->addWidget( mRampWidget, 1 ); + + mSpinBox = new QSpinBox(); + //set spinbox to a reasonable width + int largestCharWidth = mSpinBox->fontMetrics().width( "888%" ); + mSpinBox->setMinimumWidth( largestCharWidth + 35 ); + mSpinBox->setMinimum( 0 ); + mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) ); + mSpinBox->setValue( convertRealToDisplay( componentValue() ) ); + if ( component == QgsColorWidget::Hue ) + { + //degrees suffix for hue + mSpinBox->setSuffix( QChar( 176 ) ); + } + else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha ) + { + mSpinBox->setSuffix( tr( "%" ) ); + } + hLayout->addWidget( mSpinBox ); + setLayout( hLayout ); + + connect( mRampWidget, SIGNAL( valueChanged( int ) ), this, SLOT( rampChanged( int ) ) ); + connect( mRampWidget, SIGNAL( colorChanged( const QColor ) ), this, SLOT( rampColorChanged( const QColor ) ) ); + connect( mSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( spinChanged( int ) ) ); +} + +QgsColorSliderWidget::~QgsColorSliderWidget() +{ +} + +void QgsColorSliderWidget::setComponent( const QgsColorWidget::ColorComponent component ) +{ + QgsColorWidget::setComponent( component ); + mRampWidget->setComponent( component ); + mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) ); + if ( component == QgsColorWidget::Hue ) + { + //degrees suffix for hue + mSpinBox->setSuffix( QChar( 176 ) ); + } + else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha ) + { + //saturation, value and alpha are in % + mSpinBox->setSuffix( tr( "%" ) ); + } + else + { + //clear suffix + mSpinBox->setSuffix( QString() ); + } +} + +void QgsColorSliderWidget::setComponentValue( const int value ) +{ + QgsColorWidget::setComponentValue( value ); + mRampWidget->blockSignals( true ); + mRampWidget->setComponentValue( value ); + mRampWidget->blockSignals( false ); + mSpinBox->blockSignals( true ); + mSpinBox->setValue( convertRealToDisplay( value ) ); + mSpinBox->blockSignals( false ); +} + +void QgsColorSliderWidget::setColor( const QColor color ) +{ + QgsColorWidget::setColor( color ); + mRampWidget->setColor( color ); + mSpinBox->blockSignals( true ); + mSpinBox->setValue( convertRealToDisplay( componentValue() ) ); + mSpinBox->blockSignals( false ); +} + +void QgsColorSliderWidget::rampColorChanged( const QColor color ) +{ + emit colorChanged( color ); +} + +void QgsColorSliderWidget::spinChanged( int value ) +{ + int convertedValue = convertDisplayToReal( value ); + QgsColorWidget::setComponentValue( convertedValue ); + mRampWidget->setComponentValue( convertedValue ); + emit colorChanged( mCurrentColor ); +} + +void QgsColorSliderWidget::rampChanged( int value ) +{ + mSpinBox->blockSignals( true ); + mSpinBox->setValue( convertRealToDisplay( value ) ); + mSpinBox->blockSignals( false ); +} + + +int QgsColorSliderWidget::convertRealToDisplay( const int realValue ) const +{ + //scale saturation, value or alpha to 0->100 range. This makes more sense for users + //for whom "255" is a totally arbitrary value! + if ( mComponent == QgsColorWidget::Saturation || mComponent == QgsColorWidget::Value || mComponent == QgsColorWidget::Alpha ) + { + return qRound( 100.0 * realValue / 255.0 ); + } + + //leave all other values intact + return realValue; +} + +int QgsColorSliderWidget::convertDisplayToReal( const int displayValue ) const +{ + //scale saturation, value or alpha from 0->100 range (see note in convertRealToDisplay) + if ( mComponent == QgsColorWidget::Saturation || mComponent == QgsColorWidget::Value || mComponent == QgsColorWidget::Alpha ) + { + return qRound( 255.0 * displayValue / 100.0 ); + } + + //leave all other values intact + return displayValue; +} + +// +// QgsColorTextWidget +// + +QgsColorTextWidget::QgsColorTextWidget( QWidget *parent ) + : QgsColorWidget( parent ) + , mLineEdit( 0 ) + , mMenuButton( 0 ) + , mFormat( QgsColorTextWidget::HexRgb ) +{ + QHBoxLayout* hLayout = new QHBoxLayout(); + hLayout->setMargin( 0 ); + hLayout->setSpacing( 0 ); + + mLineEdit = new QLineEdit( 0 ); + hLayout->addWidget( mLineEdit ); + + mMenuButton = new QToolButton( mLineEdit ); + mMenuButton->setIcon( QgsApplication::getThemeIcon( "/mIconDropDownMenu.svg" ) ); + mMenuButton->setCursor( Qt::ArrowCursor ); + mMenuButton->setFocusPolicy( Qt::NoFocus ); + mMenuButton->setStyleSheet( "QToolButton { border: none; padding: 0px; }" ); + + setLayout( hLayout ); + + int frameWidth = mLineEdit->style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + mLineEdit->setStyleSheet( QString( "QLineEdit { padding-right: %1px; } " ) + .arg( mMenuButton->sizeHint().width() + frameWidth + 1 ) ); + + connect( mLineEdit, SIGNAL( editingFinished() ), this, SLOT( textChanged() ) ); + connect( mMenuButton, SIGNAL( clicked() ), this, SLOT( showMenu() ) ); + + //restore format setting + QSettings settings; + mFormat = ( ColorTextFormat )settings.value( "/ColorWidgets/textWidgetFormat", 0 ).toInt(); + + updateText(); +} + +QgsColorTextWidget::~QgsColorTextWidget() +{ + +} + +void QgsColorTextWidget::setColor( const QColor color ) +{ + QgsColorWidget::setColor( color ); + updateText(); +} + +void QgsColorTextWidget::resizeEvent( QResizeEvent * event ) +{ + Q_UNUSED( event ); + QSize sz = mMenuButton->sizeHint(); + int frameWidth = style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + mMenuButton->move( mLineEdit->rect().right() - frameWidth - sz.width(), + ( mLineEdit->rect().bottom() + 1 - sz.height() ) / 2 ); +} + +void QgsColorTextWidget::updateText() +{ + switch ( mFormat ) + { + case HexRgb: + mLineEdit->setText( mCurrentColor.name() ); + break; + case HexRgbA: + mLineEdit->setText( mCurrentColor.name() + QString( "%1" ).arg( mCurrentColor.alpha(), 2, 16, QChar( '0' ) ) ); + break; + case Rgb: + mLineEdit->setText( QString( tr( "rgb( %1, %2, %3 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ) ); + break; + case Rgba: + mLineEdit->setText( QString( tr( "rgba( %1, %2, %3, %4 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ).arg( QString::number( mCurrentColor.alphaF(), 'f', 2 ) ) ); + break; + } +} + +void QgsColorTextWidget::textChanged() +{ + QString testString = mLineEdit->text(); + QColor color = QgsSymbolLayerV2Utils::parseColor( testString ); + if ( !color.isValid() ) + { + //bad color string + updateText(); + return; + } + + //good color string + if ( color != mCurrentColor ) + { + //color has changed + mCurrentColor = color; + emit colorChanged( mCurrentColor ); + } + updateText(); +} + +void QgsColorTextWidget::showMenu() +{ + QMenu colorContextMenu; + + QAction* hexRgbAction = new QAction( tr( "#RRGGBB" ), 0 ); + colorContextMenu.addAction( hexRgbAction ); + QAction* hexRgbaAction = new QAction( tr( "#RRGGBBAA" ), 0 ); + colorContextMenu.addAction( hexRgbaAction ); + QAction* rgbAction = new QAction( tr( "rgb( r, g, b )" ), 0 ); + colorContextMenu.addAction( rgbAction ); + QAction* rgbaAction = new QAction( tr( "rgba( r, g, b, a )" ), 0 ); + colorContextMenu.addAction( rgbaAction ); + + QAction* selectedAction = colorContextMenu.exec( QCursor::pos() ); + if ( selectedAction == hexRgbAction ) + { + mFormat = QgsColorTextWidget::HexRgb; + } + else if ( selectedAction == hexRgbaAction ) + { + mFormat = QgsColorTextWidget::HexRgbA; + } + else if ( selectedAction == rgbAction ) + { + mFormat = QgsColorTextWidget::Rgb; + } + else if ( selectedAction == rgbaAction ) + { + mFormat = QgsColorTextWidget::Rgba; + } + + //save format setting + QSettings settings; + settings.setValue( "/ColorWidgets/textWidgetFormat", ( int )mFormat ); + + updateText(); +} + + +// +// QgsColorPreviewWidget +// + +QgsColorPreviewWidget::QgsColorPreviewWidget( QWidget *parent ) + : QgsColorWidget( parent ) + , mColor2( QColor() ) +{ + +} + +QgsColorPreviewWidget::~QgsColorPreviewWidget() +{ + +} + +void QgsColorPreviewWidget::drawColor( const QColor &color, const QRect &rect, QPainter& painter ) +{ + painter.setPen( Qt::NoPen ); + //if color has an alpha, start with a checkboard pattern + if ( color.alpha() < 255 ) + { + QBrush checkBrush = QBrush( transparentBackground() ); + painter.setBrush( checkBrush ); + painter.drawRect( rect ); + + //draw half of widget showing solid color, the other half showing color with alpha + + //ensure at least a 1px overlap to avoid artefacts + QBrush colorBrush = QBrush( color ); + painter.setBrush( colorBrush ); + painter.drawRect( floor( rect.width() / 2.0 ) + rect.left(), rect.top(), rect.width() - floor( rect.width() / 2.0 ), rect.height() ); + + QColor opaqueColor = QColor( color ); + opaqueColor.setAlpha( 255 ); + QBrush opaqueBrush = QBrush( opaqueColor ); + painter.setBrush( opaqueBrush ); + painter.drawRect( rect.left(), rect.top(), ceil( rect.width() / 2.0 ), rect.height() ); + } + else + { + //no alpha component, just draw a solid rectangle + QBrush brush = QBrush( color ); + painter.setBrush( brush ); + painter.drawRect( rect ); + } +} + +void QgsColorPreviewWidget::paintEvent( QPaintEvent *event ) +{ + Q_UNUSED( event ); + QPainter painter( this ); + + if ( mColor2.isValid() ) + { + //drawing with two color sections + int verticalSplit = qRound( height() / 2.0 ); + drawColor( mCurrentColor, QRect( 0, 0, width(), verticalSplit ), painter ); + drawColor( mColor2, QRect( 0, verticalSplit, width(), height() - verticalSplit ), painter ); + } + else + { + drawColor( mCurrentColor, QRect( 0, 0, width(), height() ), painter ); + } + + painter.end(); +} + +void QgsColorPreviewWidget::setColor2( const QColor &color ) +{ + if ( color == mColor2 ) + { + return; + } + mColor2 = color; + update(); +} diff --git a/src/gui/qgscolorwidgets.h b/src/gui/qgscolorwidgets.h new file mode 100644 index 00000000000..f4a3e5896b8 --- /dev/null +++ b/src/gui/qgscolorwidgets.h @@ -0,0 +1,631 @@ +/*************************************************************************** + qgscolorwidgets.h - color selection widgets + --------------------- + begin : September 2014 + copyright : (C) 2014 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 QGSCOLORWIDGETS_H +#define QGSCOLORWIDGETS_H + +#include + +class QColor; +class QSpinBox; +class QLineEdit; +class QToolButton; + +/** \ingroup gui + * \class QgsColorWidget + * A base class for interactive color widgets. Widgets can either allow setting a single component of + * a color (eg the red or green components), or an entire color. The QgsColorWidget also keeps track of + * any explicitely set hue for the color, so that this information is not lost when the widget is + * set to a color with an ambiguous hue (eg black or white shades). + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorWidget : public QWidget +{ + Q_OBJECT + + public: + + /*! Specifies the color component which the widget alters + */ + enum ColorComponent + { + Multiple = 0, /*!< widget alters multiple color components */ + Red, /*!< red component of color */ + Green, /*!< green component of color */ + Blue, /*!< blue component of color */ + Hue, /*!< hue component of color (based on HSV model) */ + Saturation, /*!< saturation component of color (based on HSV model) */ + Value, /*!< value component of color (based on HSV model) */ + Alpha /*!< alpha component (opacity) of color */ + }; + + /**Construct a new color widget. + * @param parent parent QWidget for the widget + * @param component color component the widget alters + */ + QgsColorWidget( QWidget* parent = 0, const ColorComponent component = Multiple ); + + virtual ~QgsColorWidget(); + + /**Returns the current color for the widget + * @returns current widget color + * @see setColor + */ + QColor color() const; + + /**Returns the color component which the widget controls + * @returns color component for widget + * @see setComponent + */ + ColorComponent component() const { return mComponent; } + + /**Returns the current value of the widget's color component + * @returns value of color component, or -1 if widget has multiple components or an invalid color + * set + * @see setComponentValue + * @see component + */ + int componentValue() const; + + public slots: + + /**Sets the color for the widget + * @param color widget color + * @see color + */ + virtual void setColor( const QColor color ); + + /**Sets the color component which the widget controls + * @param component color component for widget + * @see component + */ + virtual void setComponent( const ColorComponent component ); + + /**Alters the widget's color by setting the value for the widget's color component + * @param value value for widget's color component. This value is automatically + * clipped to the range of valid values for the color component. + * @see componentValue + * @see setComponent + * @note this method has no effect if the widget is set to the QgsColorWidget::Multiple + * component + */ + virtual void setComponentValue( const int value ); + + signals: + + /**Emitted when the widget's color changes + * @param color new widget color + */ + void colorChanged( const QColor color ); + + protected: + + QColor mCurrentColor; + + ColorComponent mComponent; + + /**QColor wipes the hue information when it is ambiguous (eg, for saturation = 0). So + * the hue is stored in mExplicit hue to keep it around, as it is useful when modifying colors + */ + int mExplicitHue; + + /**Returns the range of valid values for the color widget's component + * @returns maximum value allowed for color component, or -1 if widget has multiple components + */ + int componentRange() const; + + /**Returns the range of valid values a color component + * @returns maximum value allowed for color component + */ + int componentRange( const ColorComponent component ) const; + + /**Returns the value of a component of the widget's current color. This method correctly + * handles hue values when the color has an ambiguous hue (eg black or white shades) + * @param component color component to return + * @returns value of color component, or -1 if widget has an invalid color set + * @see hue + */ + int componentValue( const ColorComponent component ) const; + + /**Returns the hue for the widget. This may differ from the hue for the QColor returned by color(), + * as QColor returns a hue of -1 if the color's hue is ambiguous (eg, if the saturation is zero). + * @returns explicitly set hue for widget + */ + int hue() const; + + /**Alters a color by modifiying the value of a specific color component + * @param color color to alter + * @param component color component to alter + * @param newValue new value of color component. Values are automatically clipped to a + * valid range for the color component. + */ + void alterColor( QColor& color, const QgsColorWidget::ColorComponent component, const int newValue ) const; + + /**Generates a checkboard pattern pixmap for use as a background to transparent colors + * @returns checkerboard pixmap + */ + static const QPixmap& transparentBackground(); +}; + + +/** \ingroup gui + * \class QgsColorWheel + * A color wheel widget. This widget consists of an outer ring which allows for hue selection, and an + * inner rotating triangle which allows for saturation and value selection. + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorWheel : public QgsColorWidget +{ + Q_OBJECT + + public: + + /**Constructs a new color wheel widget. + * @param parent parent QWidget for the widget + */ + QgsColorWheel( QWidget* parent = 0 ); + + virtual ~QgsColorWheel(); + + void paintEvent( QPaintEvent* event ); + + public slots: + + virtual void setColor( const QColor color ); + + protected: + + virtual void resizeEvent( QResizeEvent *event ); + virtual void mouseMoveEvent( QMouseEvent *event ); + virtual void mousePressEvent( QMouseEvent *event ); + virtual void mouseReleaseEvent( QMouseEvent *event ); + + private: + + enum ControlPart + { + None, + Wheel, + Triangle + }; + + /*Margin between outer ring and edge of widget*/ + int mMargin; + + /*Thickness of hue ring in pixels*/ + int mWheelThickness; + + /*Part of the wheel where the mouse was originally depressed*/ + ControlPart mClickedPart; + + /*Cached image of hue wheel*/ + QImage* mWheelImage; + + /*Cached image of inner triangle*/ + QImage* mTriangleImage; + + /*Resuable, temporary image for drawing widget*/ + QImage* mWidgetImage; + + /*Whether the color wheel image requires redrawing*/ + bool mWheelDirty; + + /*Whether the inner triangle image requires redrawing*/ + bool mTriangleDirty; + + /*Conical gradient brush used for drawing hue wheel*/ + QBrush mWheelBrush; + + /**Creates cache images for specified widget size + * @param size widget size for images + */ + void createImages( const QSizeF size ); + + /**Creates the hue wheel image*/ + void createWheel(); + + /**Creates the inner triangle image*/ + void createTriangle(); + + /**Sets the widget color based on a point in the widget + * @param pos position for color + */ + void setColorFromPos( const QPointF pos ); + +}; + + +/** \ingroup gui + * \class QgsColorBox + * A color box widget. This widget consists of a two dimensional rectangle filled with color + * variations, where a different color component varies along both the horizontal and vertical + * axis. + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorBox : public QgsColorWidget +{ + Q_OBJECT + + public: + + /**Construct a new color box widget. + * @param parent parent QWidget for the widget + * @param component constant color component for the widget. The color components + * which vary along the horizontal and vertical axis are automatically assigned + * based on this constant color component. + */ + QgsColorBox( QWidget* parent = 0, const ColorComponent component = Value ); + + virtual ~QgsColorBox(); + + virtual QSize sizeHint() const; + void paintEvent( QPaintEvent* event ); + + virtual void setComponent( const ColorComponent component ); + + public slots: + + virtual void setColor( const QColor color ); + + protected: + + virtual void resizeEvent( QResizeEvent *event ); + virtual void mouseMoveEvent( QMouseEvent *event ); + virtual void mousePressEvent( QMouseEvent *event ); + + private: + + /*Margin between outer ring and edge of widget*/ + int mMargin; + + /*Cached image for color box*/ + QImage* mBoxImage; + + /*Whether the cached image requires redrawing*/ + bool mDirty; + + /**Creates the color box background cached image + */ + void createBox(); + + /**Returns the range of permissible values along the x axis + * @returns maximum color component value for x axis + */ + int valueRangeX() const; + + /**Returns the range of permissible values along the y axis + * @returns maximum color component value for y axis + */ + int valueRangeY() const; + + /**Returns the color component which varies along the y axis + */ + QgsColorWidget::ColorComponent yComponent() const; + + /**Returns the value of the color component which varies along the y axis + */ + int yComponentValue() const; + + /**Returns the color component which varies along the x axis + */ + QgsColorWidget::ColorComponent xComponent() const; + + /**Returns the value of the color component which varies along the x axis + */ + int xComponentValue() const; + + /**Updates the widget's color based on a point within the widget + * @param point point within the widget + */ + void setColorFromPoint( const QPoint& point ); + +}; + + +/** \ingroup gui + * \class QgsColorRampWidget + * A color ramp widget. This widget consists of an interactive box filled with a color which varies along + * its length by a single color component (eg, varying saturation from 0 to 100%). + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorRampWidget : public QgsColorWidget +{ + Q_OBJECT + + public: + + /*! Specifies the orientation of a color ramp + */ + enum Orientation + { + Horizontal = 0, /*!< horizontal ramp */ + Vertical /*!< vertical ramp */ + }; + + /**Construct a new color ramp widget. + * @param parent parent QWidget for the widget + * @param component color component which varies along the ramp + * @param orientation orientation for widget + */ + QgsColorRampWidget( QWidget* parent = 0, + const ColorComponent component = QgsColorWidget::Red, + const Orientation orientation = QgsColorRampWidget::Horizontal ); + + virtual ~QgsColorRampWidget(); + + virtual QSize sizeHint() const; + void paintEvent( QPaintEvent* event ); + + /**Sets the orientation for the color ramp + * @param orientation new orientation for the ramp + * @see orientation + */ + void setOrientation( const Orientation orientation ); + + /**Fetches the orientation for the color ramp + * @returns orientation for the ramp + * @see setOrientation + */ + Orientation orientation() const { return mOrientation; } + + /**Sets the margin between the edge of the widget and the ramp + * @param margin margin around the ramp + * @see interiorMargin + */ + void setInteriorMargin( const int margin ); + + /**Fetches the margin between the edge of the widget and the ramp + * @returns margin around the ramp + * @see setInteriorMargin + */ + int interiorMargin() const { return mMargin; } + + /**Sets whether the ramp should be drawn within a frame + * @param showFrame set to true to draw a frame around the ramp + * @see showFrame + */ + void setShowFrame( const bool showFrame ); + + /**Fetches whether the ramp is drawn within a frame + * @returns true if a frame is drawn around the ramp + * @see setShowFrame + */ + bool showFrame() const { return mShowFrame; } + + /**Sets the size for drawing the triangular markers on the ramp + * @param markerSize marker size in pixels + */ + void setMarkerSize( const int markerSize ); + + signals: + + /**Emitted when the widget's color component value changes + * @param value new value of color component + */ + void valueChanged( const int value ); + + protected: + + virtual void mouseMoveEvent( QMouseEvent *event ); + virtual void mousePressEvent( QMouseEvent *event ); + virtual void keyPressEvent( QKeyEvent * event ); + + private: + + /*Orientation for ramp*/ + Orientation mOrientation; + + /*Margin around ramp*/ + int mMargin; + + /*Whether to draw a frame around the ramp*/ + bool mShowFrame; + + /*Polygon for upper triangle marker*/ + QPolygonF mTopTriangle; + + /*Polygon for lower triangle marker*/ + QPolygonF mBottomTriangle; + + /**Updates the widget's color based on a point within the widget + * @param point point within the widget + */ + void setColorFromPoint( const QPointF &point ); + +}; + + +/** \ingroup gui + * \class QgsColorSliderWidget + * A composite horizontal color ramp widget and associated spinbox for manual value entry. + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorSliderWidget : public QgsColorWidget +{ + Q_OBJECT + + public: + + /**Construct a new color slider widget. + * @param parent parent QWidget for the widget + * @param component color component which is controlled by the slider + */ + QgsColorSliderWidget( QWidget* parent = 0, const ColorComponent component = QgsColorWidget::Red ); + + virtual ~QgsColorSliderWidget(); + + virtual void setComponent( const ColorComponent component ); + virtual void setComponentValue( const int value ); + virtual void setColor( const QColor color ); + + private: + + /*Color ramp widget*/ + QgsColorRampWidget* mRampWidget; + + /*Spin box widget*/ + QSpinBox* mSpinBox; + + /**Converts the real value of a color component to a friendly display value. For instance, + * alpha values from 0-255 have little meaning to users, so we translate them to 0-100% + * @param realValue actual value of the color component + * @returns display value of color component + * @see convertDisplayToReal + */ + int convertRealToDisplay( const int realValue ) const; + + /**Converts the display value of a color component to a real value. + * @param displayValue friendly display value of the color component + * @returns real value of color component + * @see convertRealToDisplay + */ + int convertDisplayToReal( const int displayValue ) const; + + private slots: + + /**Called when the color for the ramp changes + */ + void rampColorChanged( const QColor color ); + + /**Called when the value of the spin box changes + */ + void spinChanged( int value ); + + /**Called when the value for the ramp changes + */ + void rampChanged( int value ); + +}; + + +/** \ingroup gui + * \class QgsColorTextWidget + * A line edit widget which displays colors as text and accepts string representations + * of colors. + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorTextWidget : public QgsColorWidget +{ + Q_OBJECT + + public: + + /**Construct a new color line edit widget. + * @param parent parent QWidget for the widget + */ + QgsColorTextWidget( QWidget* parent = 0 ); + + virtual ~QgsColorTextWidget(); + + virtual void setColor( const QColor color ); + + protected: + void resizeEvent( QResizeEvent * event ); + + private: + + /*! Specifies the display format for a color + */ + enum ColorTextFormat + { + HexRgb = 0, /*!< #RRGGBB in hexadecimal */ + HexRgbA, /*!< #RRGGBBAA in hexadecimal, with alpha */ + Rgb, /*!< rgb( r, g, b ) format */ + Rgba /*!< rgba( r, g, b, a ) format, with alpha */ + }; + + QLineEdit* mLineEdit; + + /*Dropdown menu button*/ + QToolButton* mMenuButton; + + /*Display format for colors*/ + ColorTextFormat mFormat; + + /**Updates the text based on the current color + */ + void updateText(); + + private slots: + + /**Called when the user enters text into the widget + */ + void textChanged(); + + /**Called when the dropdown arrow is clicked to show the format selection menu + */ + void showMenu(); +}; + + +/** \ingroup gui + * \class QgsColorPreviewWidget + * A preview box which displays one or two colors as swatches. + * \note Added in version 2.5 + */ + +class GUI_EXPORT QgsColorPreviewWidget : public QgsColorWidget +{ + Q_OBJECT + + public: + + /**Construct a new color preview widget. + * @param parent parent QWidget for the widget + */ + QgsColorPreviewWidget( QWidget* parent = 0 ); + + virtual ~QgsColorPreviewWidget(); + + void paintEvent( QPaintEvent* event ); + + /**Returns the secondary color for the widget + * @returns secondary widget color, or an invalid color if the widget + * has no secondary color + * @see color + * @see setColor2 + */ + QColor color2() const { return mColor2; } + + public slots: + + /**Sets the second color for the widget + * @param color secondary widget color. Set to an invalid color to prevent + * drawing of a secondary color + * @see setColor + * @see color2 + */ + virtual void setColor2( const QColor& color ); + + private: + + /*Secondary color for widget*/ + QColor mColor2; + + /*Draws a color preview within the specified rect. + * @param color color to draw + * @param rect rect to draw color in + * @param painter destination painter + */ + void drawColor( const QColor& color, const QRect& rect, QPainter &painter ); +}; + +#endif // #ifndef QGSCOLORWIDGETS_H