QGIS/src/gui/qgscolorswatchgrid.cpp
2017-08-25 16:37:54 +10:00

428 lines
12 KiB
C++

/***************************************************************************
qgscolorswatchgrid.cpp
------------------
Date : July 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 "qgscolorswatchgrid.h"
#include "qgsapplication.h"
#include "qgslogger.h"
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
#define NUMBER_COLORS_PER_ROW 10 //number of color swatches per row
QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &context, QWidget *parent )
: QWidget( parent )
, mScheme( scheme )
, mContext( context )
, mDrawBoxDepressed( false )
, mCurrentHoverBox( -1 )
, mFocused( false )
, mCurrentFocusBox( 0 )
, mPressedOnWidget( false )
{
//need to receive all mouse over events
setMouseTracking( true );
setFocusPolicy( Qt::StrongFocus );
setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
mLabelHeight = Qgis::UI_SCALE_FACTOR * fontMetrics().height();
mLabelMargin = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "." ) );
mSwatchSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "X" ) ) * 1.75;
mSwatchOutlineSize = std::max( fontMetrics().width( QStringLiteral( "." ) ) * 0.4, 1.0 );
mSwatchSpacing = mSwatchSize * 0.3;
mSwatchMargin = mLabelMargin;
//calculate widget width
mWidth = NUMBER_COLORS_PER_ROW * mSwatchSize + ( NUMBER_COLORS_PER_ROW - 1 ) * mSwatchSpacing + mSwatchMargin + mSwatchMargin;
refreshColors();
}
QSize QgsColorSwatchGrid::minimumSizeHint() const
{
return QSize( mWidth, calculateHeight() );
}
QSize QgsColorSwatchGrid::sizeHint() const
{
return QSize( mWidth, calculateHeight() );
}
void QgsColorSwatchGrid::setContext( const QString &context )
{
mContext = context;
refreshColors();
}
void QgsColorSwatchGrid::setBaseColor( const QColor &baseColor )
{
mBaseColor = baseColor;
refreshColors();
}
void QgsColorSwatchGrid::refreshColors()
{
//get colors from scheme
mColors = mScheme->fetchColors( mContext, mBaseColor );
//have to update size of widget in case number of colors has changed
updateGeometry();
repaint();
}
void QgsColorSwatchGrid::paintEvent( QPaintEvent *event )
{
Q_UNUSED( event );
QPainter painter( this );
draw( painter );
painter.end();
}
void QgsColorSwatchGrid::mouseMoveEvent( QMouseEvent *event )
{
//calculate box mouse cursor is over
int newBox = swatchForPosition( event->pos() );
mDrawBoxDepressed = event->buttons() & Qt::LeftButton;
if ( newBox != mCurrentHoverBox )
{
//only repaint if changes are required
mCurrentHoverBox = newBox;
repaint();
updateTooltip( newBox );
}
emit hovered();
}
void QgsColorSwatchGrid::updateTooltip( const int colorIdx )
{
if ( colorIdx >= 0 && colorIdx < mColors.length() )
{
//if color has an associated name from the color scheme, use that
QString colorName = mColors.at( colorIdx ).second;
if ( colorName.isEmpty() )
{
//otherwise, build a default string
QColor color = mColors.at( colorIdx ).first;
colorName = QString( tr( "rgb(%1, %2, %3)" ) ).arg( color.red() ).arg( color.green() ).arg( color.blue() );
}
setToolTip( colorName );
}
else
{
//clear tooltip
setToolTip( QString() );
}
}
void QgsColorSwatchGrid::mousePressEvent( QMouseEvent *event )
{
if ( !mDrawBoxDepressed && event->buttons() & Qt::LeftButton )
{
mCurrentHoverBox = swatchForPosition( event->pos() );
mDrawBoxDepressed = true;
repaint();
}
mPressedOnWidget = true;
}
void QgsColorSwatchGrid::mouseReleaseEvent( QMouseEvent *event )
{
if ( ! mPressedOnWidget )
{
return;
}
int box = swatchForPosition( event->pos() );
if ( mDrawBoxDepressed && event->button() == Qt::LeftButton )
{
mCurrentHoverBox = box;
mDrawBoxDepressed = false;
repaint();
}
if ( box >= 0 && box < mColors.length() && event->button() == Qt::LeftButton )
{
//color clicked
emit colorChanged( mColors.at( box ).first );
}
}
void QgsColorSwatchGrid::keyPressEvent( QKeyEvent *event )
{
//handle keyboard navigation
if ( event->key() == Qt::Key_Right )
{
mCurrentFocusBox = std::min( mCurrentFocusBox + 1, mColors.length() - 1 );
}
else if ( event->key() == Qt::Key_Left )
{
mCurrentFocusBox = std::max( mCurrentFocusBox - 1, 0 );
}
else if ( event->key() == Qt::Key_Up )
{
int currentRow = mCurrentFocusBox / NUMBER_COLORS_PER_ROW;
int currentColumn = mCurrentFocusBox % NUMBER_COLORS_PER_ROW;
currentRow--;
if ( currentRow >= 0 )
{
mCurrentFocusBox = currentRow * NUMBER_COLORS_PER_ROW + currentColumn;
}
else
{
//moved above first row
focusPreviousChild();
}
}
else if ( event->key() == Qt::Key_Down )
{
int currentRow = mCurrentFocusBox / NUMBER_COLORS_PER_ROW;
int currentColumn = mCurrentFocusBox % NUMBER_COLORS_PER_ROW;
currentRow++;
int box = currentRow * NUMBER_COLORS_PER_ROW + currentColumn;
if ( box < mColors.length() )
{
mCurrentFocusBox = box;
}
else
{
//moved below first row
focusNextChild();
}
}
else if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Space )
{
//color clicked
emit colorChanged( mColors.at( mCurrentFocusBox ).first );
}
else
{
//some other key, pass it on
QWidget::keyPressEvent( event );
return;
}
repaint();
}
void QgsColorSwatchGrid::focusInEvent( QFocusEvent *event )
{
Q_UNUSED( event );
mFocused = true;
repaint();
}
void QgsColorSwatchGrid::focusOutEvent( QFocusEvent *event )
{
Q_UNUSED( event );
mFocused = false;
repaint();
}
int QgsColorSwatchGrid::calculateHeight() const
{
int numberRows = std::ceil( ( double )mColors.length() / NUMBER_COLORS_PER_ROW );
return numberRows * ( mSwatchSize ) + ( numberRows - 1 ) * mSwatchSpacing + mSwatchMargin + mLabelHeight + 0.5 * mLabelMargin + mSwatchMargin;
}
void QgsColorSwatchGrid::draw( QPainter &painter )
{
QPalette pal = QPalette( qApp->palette() );
QColor headerBgColor = pal.color( QPalette::Mid );
QColor headerTextColor = pal.color( QPalette::BrightText );
QColor highlight = pal.color( QPalette::Highlight );
//draw header background
painter.setBrush( headerBgColor );
painter.setPen( Qt::NoPen );
painter.drawRect( QRect( 0, 0, width(), mLabelHeight + 0.5 * mLabelMargin ) );
//draw header text
painter.setPen( headerTextColor );
painter.drawText( QRect( mLabelMargin, 0.25 * mLabelMargin, width() - 2 * mLabelMargin, mLabelHeight ),
Qt::AlignLeft | Qt::AlignVCenter, mScheme->schemeName() );
//draw color swatches
QgsNamedColorList::const_iterator colorIt = mColors.constBegin();
int index = 0;
for ( ; colorIt != mColors.constEnd(); ++colorIt )
{
int row = index / NUMBER_COLORS_PER_ROW;
int column = index % NUMBER_COLORS_PER_ROW;
QRect swatchRect = QRect( column * ( mSwatchSize + mSwatchSpacing ) + mSwatchMargin,
row * ( mSwatchSize + mSwatchSpacing ) + mSwatchMargin + mLabelHeight + 0.5 * mLabelMargin,
mSwatchSize, mSwatchSize );
if ( mCurrentHoverBox == index )
{
//hovered boxes are slightly larger
swatchRect.adjust( -1, -1, 1, 1 );
}
//start with checkboard pattern for semi-transparent colors
if ( ( *colorIt ).first.alpha() != 255 )
{
QBrush checkBrush = QBrush( transparentBackground() );
painter.setPen( Qt::NoPen );
painter.setBrush( checkBrush );
painter.drawRect( swatchRect );
}
if ( mCurrentHoverBox == index )
{
if ( mDrawBoxDepressed )
{
painter.setPen( QPen( QColor( 100, 100, 100 ), mSwatchOutlineSize ) );
}
else
{
//hover color
painter.setPen( QPen( QColor( 220, 220, 220 ), mSwatchOutlineSize ) );
}
}
else if ( mFocused && index == mCurrentFocusBox )
{
painter.setPen( highlight );
}
else if ( ( *colorIt ).first.name() == mBaseColor.name() )
{
//currently active color
painter.setPen( QPen( QColor( 75, 75, 75 ), mSwatchOutlineSize ) );
}
else
{
painter.setPen( QPen( QColor( 197, 197, 197 ), mSwatchOutlineSize ) );
}
painter.setBrush( ( *colorIt ).first );
painter.drawRect( swatchRect );
index++;
}
}
QPixmap QgsColorSwatchGrid::transparentBackground()
{
static QPixmap sTranspBkgrd;
if ( sTranspBkgrd.isNull() )
sTranspBkgrd = QgsApplication::getThemePixmap( QStringLiteral( "/transp-background_8x8.png" ) );
return sTranspBkgrd;
}
int QgsColorSwatchGrid::swatchForPosition( QPoint position ) const
{
//calculate box for position
int box = -1;
int column = ( position.x() - mSwatchMargin ) / ( mSwatchSize + mSwatchSpacing );
int xRem = ( position.x() - mSwatchMargin ) % ( mSwatchSize + mSwatchSpacing );
int row = ( position.y() - mSwatchMargin - mLabelHeight ) / ( mSwatchSize + mSwatchSpacing );
int yRem = ( position.y() - mSwatchMargin - mLabelHeight ) % ( mSwatchSize + mSwatchSpacing );
if ( xRem <= mSwatchSize + 1 && yRem <= mSwatchSize + 1 && column < NUMBER_COLORS_PER_ROW )
{
//if pos is actually inside a valid box, calculate which box
box = column + row * NUMBER_COLORS_PER_ROW;
}
return box;
}
//
// QgsColorGridAction
//
QgsColorSwatchGridAction::QgsColorSwatchGridAction( QgsColorScheme *scheme, QMenu *menu, const QString &context, QWidget *parent )
: QWidgetAction( parent )
, mMenu( menu )
, mSuppressRecurse( false )
, mDismissOnColorSelection( true )
{
mColorSwatchGrid = new QgsColorSwatchGrid( scheme, context, parent );
setDefaultWidget( mColorSwatchGrid );
connect( mColorSwatchGrid, &QgsColorSwatchGrid::colorChanged, this, &QgsColorSwatchGridAction::setColor );
connect( this, &QAction::hovered, this, &QgsColorSwatchGridAction::onHover );
connect( mColorSwatchGrid, &QgsColorSwatchGrid::hovered, this, &QgsColorSwatchGridAction::onHover );
//hide the action if no colors to be shown
setVisible( !mColorSwatchGrid->colors()->isEmpty() );
}
void QgsColorSwatchGridAction::setBaseColor( const QColor &baseColor )
{
mColorSwatchGrid->setBaseColor( baseColor );
}
QColor QgsColorSwatchGridAction::baseColor() const
{
return mColorSwatchGrid->baseColor();
}
QString QgsColorSwatchGridAction::context() const
{
return mColorSwatchGrid->context();
}
void QgsColorSwatchGridAction::setContext( const QString &context )
{
mColorSwatchGrid->setContext( context );
}
void QgsColorSwatchGridAction::refreshColors()
{
mColorSwatchGrid->refreshColors();
//hide the action if no colors shown
setVisible( !mColorSwatchGrid->colors()->isEmpty() );
}
void QgsColorSwatchGridAction::setColor( const QColor &color )
{
emit colorChanged( color );
QAction::trigger();
if ( mMenu && mDismissOnColorSelection )
{
mMenu->hide();
}
}
void QgsColorSwatchGridAction::onHover()
{
//see https://bugreports.qt-project.org/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
if ( mSuppressRecurse )
{
return;
}
if ( mMenu )
{
mSuppressRecurse = true;
mMenu->setActiveAction( this );
mSuppressRecurse = false;
}
}