QGIS/src/gui/qgscolorbutton.cpp
Nyall Dawson 5407ae8a6a [FEATURE] Color dialog can be embedded in layer style panel
Now clicking color buttons inside the layer style panel causes
the color picker dialog to be opened inside the style panel itself
rather than as a separate dialog
2016-08-15 18:04:07 +10:00

733 lines
20 KiB
C++

/***************************************************************************
qgscolorbutton.cpp - Button which displays a color
--------------------------------------
Date : 12-Dec-2006
Copyright : (C) 2006 by Tom Elwertowski
Email : telwertowski at users dot sourceforge dot net
***************************************************************************
* *
* 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 "qgscolorbutton.h"
#include "qgscolordialog.h"
#include "qgsapplication.h"
#include "qgslogger.h"
#include "qgssymbollayerutils.h"
#include "qgscursors.h"
#include "qgscolorswatchgrid.h"
#include "qgscolorschemeregistry.h"
#include "qgscolorwidgets.h"
#include <QPainter>
#include <QSettings>
#include <QTemporaryFile>
#include <QMouseEvent>
#include <QMenu>
#include <QClipboard>
#include <QDrag>
#include <QDesktopWidget>
#include <QStyle>
#include <QStyleOptionToolButton>
#include <QWidgetAction>
#include <QLabel>
#include <QGridLayout>
#include <QPushButton>
QgsColorButton::QgsColorButton( QWidget *parent, const QString& cdt, QgsColorSchemeRegistry* registry )
: QToolButton( parent )
, mBehaviour( QgsColorButton::ShowDialog )
, mColorDialogTitle( cdt.isEmpty() ? tr( "Select Color" ) : cdt )
, mColor( QColor() )
, mDefaultColor( QColor() ) //default to invalid color
, mAllowAlpha( false )
, mAcceptLiveUpdates( true )
, mColorSet( false )
, mShowNoColorOption( false )
, mNoColorString( tr( "No color" ) )
, mShowNull( false )
, mPickingColor( false )
, mMenu( nullptr )
{
//if a color scheme registry was specified, use it, otherwise use the global instance
mColorSchemeRegistry = registry ? registry : QgsColorSchemeRegistry::instance();
setAcceptDrops( true );
setMinimumSize( QSize( 24, 16 ) );
connect( this, SIGNAL( clicked() ), this, SLOT( buttonClicked() ) );
//setup dropdown menu
mMenu = new QMenu( this );
connect( mMenu, SIGNAL( aboutToShow() ), this, SLOT( prepareMenu() ) );
setMenu( mMenu );
setPopupMode( QToolButton::MenuButtonPopup );
}
QgsColorButton::~QgsColorButton()
{
}
QSize QgsColorButton::sizeHint() const
{
//make sure height of button looks good under different platforms
#ifdef Q_OS_WIN
return QSize( 120, 22 );
#else
return QSize( 120, 28 );
#endif
}
const QPixmap& QgsColorButton::transparentBackground()
{
static QPixmap transpBkgrd;
if ( transpBkgrd.isNull() )
transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
return transpBkgrd;
}
void QgsColorButton::showColorDialog()
{
if ( QgsPanelWidget* panel = QgsPanelWidget::findParentPanel( this ) )
{
QgsCompoundColorWidget* colorWidget = new QgsCompoundColorWidget( panel, color() );
colorWidget->setPanelTitle( mColorDialogTitle );
colorWidget->setAllowAlpha( mAllowAlpha );
connect( colorWidget, SIGNAL( currentColorChanged( QColor ) ), this, SLOT( setValidTemporaryColor( QColor ) ) );
connect( colorWidget, SIGNAL( panelAccepted( QgsPanelWidget* ) ), this, SLOT( panelAccepted( QgsPanelWidget* ) ) );
panel->openPanel( colorWidget );
return;
}
QColor newColor;
QSettings settings;
if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
{
// live updating dialog - QgsColorDialog will automatically use native dialog if option is set
newColor = QgsColorDialog::getLiveColor(
color(), this, SLOT( setValidColor( const QColor& ) ),
this, mColorDialogTitle, mAllowAlpha );
}
else
{
// not using live updating dialog - first check if we need to use the limited native dialogs
bool useNative = settings.value( "/qgis/native_color_dialogs", false ).toBool();
if ( useNative )
{
// why would anyone want this? who knows.... maybe the limited nature of native dialogs helps ease the transition for MapInfo users?
newColor = QColorDialog::getColor( color(), this, mColorDialogTitle, mAllowAlpha ? QColorDialog::ShowAlphaChannel : ( QColorDialog::ColorDialogOption )0 );
}
else
{
QgsColorDialog dialog( this, 0, color() );
dialog.setTitle( mColorDialogTitle );
dialog.setAllowAlpha( mAllowAlpha );
if ( dialog.exec() )
{
newColor = dialog.color();
}
}
}
if ( newColor.isValid() )
{
setValidColor( newColor );
addRecentColor( newColor );
}
// reactivate button's window
activateWindow();
}
void QgsColorButton::setToDefaultColor()
{
if ( !mDefaultColor.isValid() )
{
return;
}
setColor( mDefaultColor );
}
void QgsColorButton::setToNull()
{
setColor( QColor() );
}
bool QgsColorButton::event( QEvent *e )
{
if ( e->type() == QEvent::ToolTip )
{
QString name = this->color().name();
int hue = this->color().hue();
int value = this->color().value();
int saturation = this->color().saturation();
QString info = QString( "HEX: %1 \n"
"RGB: %2 \n"
"HSV: %3,%4,%4" ).arg( name )
.arg( QgsSymbolLayerUtils::encodeColor( this->color() ) )
.arg( hue ).arg( value ).arg( saturation );
setToolTip( info );
}
return QToolButton::event( e );
}
void QgsColorButton::setToNoColor()
{
if ( mAllowAlpha )
{
QColor noColor = QColor( mColor );
noColor.setAlpha( 0 );
setColor( noColor );
}
}
void QgsColorButton::mousePressEvent( QMouseEvent *e )
{
if ( mPickingColor )
{
//don't show dialog if in color picker mode
e->accept();
return;
}
if ( e->button() == Qt::RightButton )
{
QToolButton::showMenu();
return;
}
else if ( e->button() == Qt::LeftButton )
{
mDragStartPosition = e->pos();
}
QToolButton::mousePressEvent( e );
}
bool QgsColorButton::colorFromMimeData( const QMimeData * mimeData, QColor& resultColor )
{
bool hasAlpha = false;
QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha );
if ( mimeColor.isValid() )
{
if ( !mAllowAlpha )
{
//remove alpha channel
mimeColor.setAlpha( 255 );
}
else if ( !hasAlpha )
{
//mime color has no explicit alpha component, so keep existing alpha
mimeColor.setAlpha( mColor.alpha() );
}
resultColor = mimeColor;
return true;
}
//could not get color from mime data
return false;
}
void QgsColorButton::mouseMoveEvent( QMouseEvent *e )
{
if ( mPickingColor )
{
//currently in color picker mode
if ( e->buttons() & Qt::LeftButton )
{
//if left button depressed, sample color under cursor and temporarily update button color
//to give feedback to user
QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), e->globalPos().x(), e->globalPos().y(), 1, 1 );
QImage snappedImage = snappedPixmap.toImage();
QColor hoverColor = snappedImage.pixel( 0, 0 );
setButtonBackground( hoverColor );
}
e->accept();
return;
}
//handle dragging colors from button
if ( !( e->buttons() & Qt::LeftButton ) || !mColor.isValid() )
{
//left button not depressed or no color set, so not a drag
QToolButton::mouseMoveEvent( e );
return;
}
if (( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
{
//mouse not moved, so not a drag
QToolButton::mouseMoveEvent( e );
return;
}
//user is dragging color
QDrag *drag = new QDrag( this );
drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mColor ) );
drag->setPixmap( QgsColorWidget::createDragIcon( mColor ) );
drag->exec( Qt::CopyAction );
setDown( false );
}
void QgsColorButton::mouseReleaseEvent( QMouseEvent *e )
{
if ( mPickingColor )
{
//end color picking operation by sampling the color under cursor
stopPicking( e->globalPos() );
e->accept();
return;
}
QToolButton::mouseReleaseEvent( e );
}
void QgsColorButton::stopPicking( QPointF eventPos, bool sampleColor )
{
//release mouse and keyboard, and reset cursor
releaseMouse();
releaseKeyboard();
unsetCursor();
mPickingColor = false;
if ( !sampleColor )
{
//not sampling color, nothing more to do
return;
}
//grab snapshot of pixel under mouse cursor
QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), eventPos.x(), eventPos.y(), 1, 1 );
QImage snappedImage = snappedPixmap.toImage();
//extract color from pixel and set color
setColor( snappedImage.pixel( 0, 0 ) );
addRecentColor( mColor );
}
void QgsColorButton::keyPressEvent( QKeyEvent *e )
{
if ( !mPickingColor )
{
//if not picking a color, use default tool button behaviour
QToolButton::keyPressEvent( e );
return;
}
//cancel picking, sampling the color if space was pressed
stopPicking( QCursor::pos(), e->key() == Qt::Key_Space );
}
void QgsColorButton::dragEnterEvent( QDragEnterEvent *e )
{
//is dragged data valid color data?
QColor mimeColor;
if ( colorFromMimeData( e->mimeData(), mimeColor ) )
{
//if so, we accept the drag, and temporarily change the button's color
//to match the dragged color. This gives immediate feedback to the user
//that colors can be dropped here
e->acceptProposedAction();
setButtonBackground( mimeColor );
}
}
void QgsColorButton::dragLeaveEvent( QDragLeaveEvent *e )
{
Q_UNUSED( e );
//reset button color
setButtonBackground( mColor );
}
void QgsColorButton::dropEvent( QDropEvent *e )
{
//is dropped data valid color data?
QColor mimeColor;
if ( colorFromMimeData( e->mimeData(), mimeColor ) )
{
//accept drop and set new color
e->acceptProposedAction();
setColor( mimeColor );
addRecentColor( mimeColor );
}
}
void QgsColorButton::setValidColor( const QColor& newColor )
{
if ( newColor.isValid() )
{
setColor( newColor );
addRecentColor( newColor );
}
}
void QgsColorButton::setValidTemporaryColor( const QColor& newColor )
{
if ( newColor.isValid() )
{
setColor( newColor );
}
}
void QgsColorButton::panelAccepted( QgsPanelWidget* widget )
{
if ( QgsCompoundColorWidget* colorWidget = qobject_cast< QgsCompoundColorWidget* >( widget ) )
{
addRecentColor( colorWidget->color() );
}
}
QPixmap QgsColorButton::createMenuIcon( const QColor &color, const bool showChecks )
{
//create an icon pixmap
QPixmap pixmap( 16, 16 );
pixmap.fill( Qt::transparent );
QPainter p;
p.begin( &pixmap );
//start with checkboard pattern
if ( showChecks )
{
QBrush checkBrush = QBrush( transparentBackground() );
p.setPen( Qt::NoPen );
p.setBrush( checkBrush );
p.drawRect( 0, 0, 15, 15 );
}
//draw color over pattern
p.setBrush( QBrush( color ) );
//draw border
p.setPen( QColor( 197, 197, 197 ) );
p.drawRect( 0, 0, 15, 15 );
p.end();
return pixmap;
}
void QgsColorButton::buttonClicked()
{
switch ( mBehaviour )
{
case ShowDialog:
showColorDialog();
return;
case SignalOnly:
emit colorClicked( mColor );
return;
}
}
void QgsColorButton::prepareMenu()
{
//we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
//QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
//for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
//menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
mMenu->clear();
if ( mShowNull )
{
QAction* nullAction = new QAction( tr( "Clear color" ), this );
nullAction->setIcon( createMenuIcon( Qt::transparent, false ) );
mMenu->addAction( nullAction );
connect( nullAction, SIGNAL( triggered() ), this, SLOT( setToNull() ) );
}
//show default color option if set
if ( mDefaultColor.isValid() )
{
QAction* defaultColorAction = new QAction( tr( "Default color" ), this );
defaultColorAction->setIcon( createMenuIcon( mDefaultColor ) );
mMenu->addAction( defaultColorAction );
connect( defaultColorAction, SIGNAL( triggered() ), this, SLOT( setToDefaultColor() ) );
}
if ( mShowNoColorOption && mAllowAlpha )
{
QAction* noColorAction = new QAction( mNoColorString, this );
noColorAction->setIcon( createMenuIcon( Qt::transparent, false ) );
mMenu->addAction( noColorAction );
connect( noColorAction, SIGNAL( triggered() ), this, SLOT( setToNoColor() ) );
}
mMenu->addSeparator();
QgsColorWheel* colorWheel = new QgsColorWheel( mMenu );
colorWheel->setColor( color() );
QgsColorWidgetAction* colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
colorAction->setDismissOnColorSelection( false );
connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setColor( const QColor& ) ) );
mMenu->addAction( colorAction );
if ( mColorSchemeRegistry )
{
//get schemes with ShowInColorButtonMenu flag set
QList< QgsColorScheme* > schemeList = mColorSchemeRegistry->schemes( QgsColorScheme::ShowInColorButtonMenu );
QList< QgsColorScheme* >::iterator it = schemeList.begin();
for ( ; it != schemeList.end(); ++it )
{
QgsColorSwatchGridAction* colorAction = new QgsColorSwatchGridAction( *it, mMenu, mContext, this );
colorAction->setBaseColor( mColor );
mMenu->addAction( colorAction );
connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setValidColor( const QColor& ) ) );
connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( addRecentColor( const QColor& ) ) );
}
}
mMenu->addSeparator();
QAction* copyColorAction = new QAction( tr( "Copy color" ), this );
mMenu->addAction( copyColorAction );
connect( copyColorAction, SIGNAL( triggered() ), this, SLOT( copyColor() ) );
QAction* pasteColorAction = new QAction( tr( "Paste color" ), this );
//enable or disable paste action based on current clipboard contents. We always show the paste
//action, even if it's disabled, to give hint to the user that pasting colors is possible
QColor clipColor;
if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
{
pasteColorAction->setIcon( createMenuIcon( clipColor ) );
}
else
{
pasteColorAction->setEnabled( false );
}
mMenu->addAction( pasteColorAction );
connect( pasteColorAction, SIGNAL( triggered() ), this, SLOT( pasteColor() ) );
#ifndef Q_OS_MAC
//disabled for OSX, as it is impossible to grab the mouse under OSX
//see note for QWidget::grabMouse() re OSX Cocoa
//http://qt-project.org/doc/qt-4.8/qwidget.html#grabMouse
QAction* pickColorAction = new QAction( tr( "Pick color" ), this );
mMenu->addAction( pickColorAction );
connect( pickColorAction, SIGNAL( triggered() ), this, SLOT( activatePicker() ) );
#endif
QAction* chooseColorAction = new QAction( tr( "Choose color..." ), this );
mMenu->addAction( chooseColorAction );
connect( chooseColorAction, SIGNAL( triggered() ), this, SLOT( showColorDialog() ) );
}
void QgsColorButton::changeEvent( QEvent* e )
{
if ( e->type() == QEvent::EnabledChange )
{
setButtonBackground();
}
QToolButton::changeEvent( e );
}
#if 0 // causes too many cyclical updates, but may be needed on some platforms
void QgsColorButton::paintEvent( QPaintEvent* e )
{
QToolButton::paintEvent( e );
if ( !mBackgroundSet )
{
setButtonBackground();
}
}
#endif
void QgsColorButton::showEvent( QShowEvent* e )
{
setButtonBackground();
QToolButton::showEvent( e );
}
void QgsColorButton::resizeEvent( QResizeEvent *event )
{
QToolButton::resizeEvent( event );
//recalculate icon size and redraw icon
mIconSize = QSize();
setButtonBackground( mColor );
}
void QgsColorButton::setColor( const QColor &color )
{
QColor oldColor = mColor;
mColor = color;
// handle when initially set color is same as default (Qt::black); consider it a color change
if ( oldColor != mColor || ( mColor == QColor( Qt::black ) && !mColorSet ) )
{
setButtonBackground();
if ( isEnabled() )
{
// TODO: May be beneficial to have the option to set color without emitting this signal.
// Now done by blockSignals( bool ) where button is used
emit colorChanged( mColor );
}
}
mColorSet = true;
}
void QgsColorButton::addRecentColor( const QColor& color )
{
QgsRecentColorScheme::addRecentColor( color );
}
void QgsColorButton::setButtonBackground( const QColor &color )
{
QColor backgroundColor = color;
if ( !color.isValid() )
{
backgroundColor = mColor;
}
QSize currentIconSize;
//icon size is button size with a small margin
if ( menu() )
{
if ( !mIconSize.isValid() )
{
//calculate size of push button part of widget (ie, without the menu dropdown button part)
QStyleOptionToolButton opt;
initStyleOption( &opt );
QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
this );
//make sure height of icon looks good under different platforms
#ifdef Q_OS_WIN
mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
#else
mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
#endif
}
currentIconSize = mIconSize;
}
else
{
//no menu
#ifdef Q_OS_WIN
currentIconSize = QSize( width() - 10, height() - 6 );
#else
currentIconSize = QSize( width() - 10, height() - 12 );
#endif
}
if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
{
return;
}
//create an icon pixmap
QPixmap pixmap( currentIconSize );
pixmap.fill( Qt::transparent );
if ( backgroundColor.isValid() )
{
QRect rect( 0, 0, currentIconSize.width(), currentIconSize.height() );
QPainter p;
p.begin( &pixmap );
p.setRenderHint( QPainter::Antialiasing );
p.setPen( Qt::NoPen );
if ( mAllowAlpha && backgroundColor.alpha() < 255 )
{
//start with checkboard pattern
QBrush checkBrush = QBrush( transparentBackground() );
p.setBrush( checkBrush );
p.drawRoundedRect( rect, 3, 3 );
}
//draw semi-transparent color on top
p.setBrush( backgroundColor );
p.drawRoundedRect( rect, 3, 3 );
p.end();
}
setIconSize( currentIconSize );
setIcon( pixmap );
}
void QgsColorButton::copyColor()
{
//copy color
QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mColor ) );
}
void QgsColorButton::pasteColor()
{
QColor clipColor;
if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
{
//paste color
setColor( clipColor );
addRecentColor( clipColor );
}
}
void QgsColorButton::activatePicker()
{
//pick color
QPixmap samplerPixmap = QPixmap(( const char ** ) sampler_cursor );
setCursor( QCursor( samplerPixmap, 0, 0 ) );
grabMouse();
grabKeyboard();
mPickingColor = true;
}
QColor QgsColorButton::color() const
{
return mColor;
}
void QgsColorButton::setAllowAlpha( const bool allowAlpha )
{
mAllowAlpha = allowAlpha;
}
void QgsColorButton::setColorDialogTitle( const QString& title )
{
mColorDialogTitle = title;
}
QString QgsColorButton::colorDialogTitle() const
{
return mColorDialogTitle;
}
void QgsColorButton::setShowMenu( const bool showMenu )
{
setMenu( showMenu ? mMenu : nullptr );
setPopupMode( showMenu ? QToolButton::MenuButtonPopup : QToolButton::DelayedPopup );
//force recalculation of icon size
mIconSize = QSize();
setButtonBackground( mColor );
}
void QgsColorButton::setBehaviour( const QgsColorButton::Behaviour behaviour )
{
mBehaviour = behaviour;
}
void QgsColorButton::setDefaultColor( const QColor& color )
{
mDefaultColor = color;
}
void QgsColorButton::setShowNull( bool showNull )
{
mShowNull = showNull;
}
bool QgsColorButton::showNull() const
{
return mShowNull;
}
bool QgsColorButton::isNull() const
{
return !mColor.isValid();
}