[needs-docs] New gui widget QgsFontButton

A standard widget for configuring text format properties for use
with QgsTextRenderer/QgsTextFormat.

It's modelled heavily off QgsColorButton, and supports lots of nice
things like dragging formats between buttons, copying and pasting
format settings, dropping colors from color buttons, dragging colors
from font buttons to color buttons, directly setting font size
and opacity/color without having to open a dialog.
This commit is contained in:
Nyall Dawson 2017-07-06 19:30:04 +10:00
parent a73131176f
commit 0b9fb5d6e1
11 changed files with 1187 additions and 0 deletions

View File

@ -1231,6 +1231,22 @@ class QgsTextFormat
:rtype: QDomElement
%End
QMimeData *toMimeData() const /Factory/;
%Docstring
Returns new mime data representing the text format settings.
Caller takes responsibility for deleting the returned object.
.. seealso:: fromMimeData()
:rtype: QMimeData
%End
static QgsTextFormat fromMimeData( const QMimeData *data, bool *ok /Out/ = 0 );
%Docstring
Attempts to parse the provided mime ``data`` as a QgsTextFormat.
If data can be parsed as a text format, ``ok`` will be set to true.
.. seealso:: toMimeData()
:rtype: QgsTextFormat
%End
bool containsAdvancedEffects() const;
%Docstring
Returns true if any component of the font format requires advanced effects

View File

@ -110,6 +110,7 @@
%Include qgsfilterlineedit.sip
%Include qgsfloatingwidget.sip
%Include qgsfocuswatcher.sip
%Include qgsfontbutton.sip
%Include qgsformannotation.sip
%Include qgsgradientcolorrampdialog.sip
%Include qgsgradientstopeditor.sip

View File

@ -0,0 +1,151 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsfontbutton.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsFontButton : QToolButton
{
%Docstring
A button for customising QgsTextFormat settings.
The button will open a detailed text format settings dialog when clicked. An attached drop down
menu allows for copying and pasting text styles, picking colors for the text, and for dropping
colors from other color widgets.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsfontbutton.h"
%End
public:
QgsFontButton( QWidget *parent /TransferThis/ = 0, const QString &dialogTitle = QString() );
%Docstring
Construct a new font button.
Use ``parent`` to attach a parent QWidget to the dialog.
Use ``dialogTitle`` string to define the title to show in the text settings dialog.
%End
virtual QSize sizeHint() const;
void setDialogTitle( const QString &title );
%Docstring
Sets the ``title`` for the text settings dialog window.
.. seealso:: dialogTitle()
%End
QString dialogTitle() const;
%Docstring
Returns the title for the text settings dialog window.
.. seealso:: setDialogTitle()
:rtype: str
%End
QgsMapCanvas *mapCanvas() const;
%Docstring
Returns the map canvas associated with the widget.
.. seealso:: setMapCanvas()
:rtype: QgsMapCanvas
%End
void setMapCanvas( QgsMapCanvas *canvas );
%Docstring
Sets a map ``canvas`` to associate with the widget. This allows the
widget to fetch current settings from the map canvas, such as current scale.
.. seealso:: mapCanvas()
%End
QgsTextFormat textFormat() const;
%Docstring
Returns the current text formatting set by the widget.
.. seealso:: setTextFormat()
:rtype: QgsTextFormat
%End
public slots:
void setTextFormat( const QgsTextFormat &format );
%Docstring
Sets the current text ``format`` to show in the widget.
.. seealso:: textFormat()
%End
void setColor( const QColor &color );
%Docstring
Sets the current ``color`` for the text. Will emit a changed signal if the color is different
to the previous text color.
%End
void copyFormat();
%Docstring
Copies the current text format to the clipboard.
.. seealso:: pasteFormat()
%End
void pasteFormat();
%Docstring
Pastes a format from the clipboard. If clipboard does not contain a valid
format then no change is applied.
.. seealso:: copyFormat()
%End
void copyColor();
%Docstring
Copies the current text color to the clipboard.
.. seealso:: pasteColor()
%End
void pasteColor();
%Docstring
Pastes a color from the clipboard to the text format. If clipboard does not contain a valid
color or string representation of a color, then no change is applied.
.. seealso:: copyColor()
%End
signals:
void changed();
%Docstring
Emitted when the widget's text format settings are changed.
%End
protected:
virtual bool event( QEvent *e );
virtual void changeEvent( QEvent *e );
virtual void showEvent( QShowEvent *e );
virtual void resizeEvent( QResizeEvent *event );
virtual void mousePressEvent( QMouseEvent *e );
virtual void mouseMoveEvent( QMouseEvent *e );
virtual void dragEnterEvent( QDragEnterEvent *e );
virtual void dragLeaveEvent( QDragLeaveEvent *e );
virtual void dropEvent( QDropEvent *e );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsfontbutton.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -1594,6 +1594,49 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex
return textStyleElem;
}
QMimeData *QgsTextFormat::toMimeData() const
{
//set both the mime color data, and the text (format settings).
QMimeData *mimeData = new QMimeData;
mimeData->setColorData( QVariant( color() ) );
QgsReadWriteContext rwContext;
QDomDocument textDoc;
QDomElement textElem = writeXml( textDoc, rwContext );
textDoc.appendChild( textElem );
mimeData->setText( textDoc.toString() );
return mimeData;
}
QgsTextFormat QgsTextFormat::fromMimeData( const QMimeData *data, bool *ok )
{
if ( ok )
*ok = false;
QgsTextFormat format;
if ( !data )
return format;
QString text = data->text();
if ( !text.isEmpty() )
{
QDomDocument doc;
QDomElement elem;
QgsReadWriteContext rwContext;
if ( doc.setContent( text ) )
{
elem = doc.documentElement();
format.readXml( elem, rwContext );
if ( ok )
*ok = true;
return format;
}
}
return format;
}
bool QgsTextFormat::containsAdvancedEffects() const
{
if ( d->blendMode != QPainter::CompositionMode_SourceOver )

View File

@ -1068,6 +1068,20 @@ class CORE_EXPORT QgsTextFormat
*/
QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
/**
* Returns new mime data representing the text format settings.
* Caller takes responsibility for deleting the returned object.
* \see fromMimeData()
*/
QMimeData *toMimeData() const SIP_FACTORY;
/**
* Attempts to parse the provided mime \a data as a QgsTextFormat.
* If data can be parsed as a text format, \a ok will be set to true.
* \see toMimeData()
*/
static QgsTextFormat fromMimeData( const QMimeData *data, bool *ok SIP_OUT = nullptr );
/** Returns true if any component of the font format requires advanced effects
* such as blend modes, which require output in raster formats to be fully respected.
*/

View File

@ -227,6 +227,7 @@ SET(QGIS_GUI_SRCS
qgsfilterlineedit.cpp
qgsfloatingwidget.cpp
qgsfocuswatcher.cpp
qgsfontbutton.cpp
qgsformannotation.cpp
qgsgeometryrubberband.cpp
qgsgradientcolorrampdialog.cpp
@ -387,6 +388,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsfilterlineedit.h
qgsfloatingwidget.h
qgsfocuswatcher.h
qgsfontbutton.h
qgsformannotation.h
qgsgradientcolorrampdialog.h
qgsgradientstopeditor.h

646
src/gui/qgsfontbutton.cpp Normal file
View File

@ -0,0 +1,646 @@
/***************************************************************************
qgsfontbutton.h
---------------
Date : May 2017
Copyright : (C) 2017 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsfontbutton.h"
#include "qgstextformatwidget.h"
#include "qgssymbollayerutils.h"
#include "qgscolorscheme.h"
#include "qgsmapcanvas.h"
#include "qgscolorwidgets.h"
#include "qgscolorschemeregistry.h"
#include "qgscolorswatchgrid.h"
#include "qgsdoublespinbox.h"
#include "qgsunittypes.h"
#include "qgsmenuheader.h"
#include <QMenu>
#include <QClipboard>
#include <QDrag>
#include <QDesktopWidget>
QgsFontButton::QgsFontButton( QWidget *parent, const QString &dialogTitle )
: QToolButton( parent )
, mDialogTitle( dialogTitle.isEmpty() ? tr( "Text Format" ) : dialogTitle )
, mMenu( nullptr )
{
setAcceptDrops( true );
setMinimumSize( QSize( 24, 16 ) );
connect( this, &QAbstractButton::clicked, this, &QgsFontButton::showSettingsDialog );
//setup dropdown menu
mMenu = new QMenu( this );
connect( mMenu, &QMenu::aboutToShow, this, &QgsFontButton::prepareMenu );
setMenu( mMenu );
setPopupMode( QToolButton::MenuButtonPopup );
}
QSize QgsFontButton::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
}
void QgsFontButton::showSettingsDialog()
{
QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
if ( panel && panel->dockMode() )
{
QgsTextFormatPanelWidget *formatWidget = new QgsTextFormatPanelWidget( mFormat, mMapCanvas, this );
formatWidget->setPanelTitle( mDialogTitle );
connect( formatWidget, &QgsTextFormatPanelWidget::widgetChanged, this, [ this, formatWidget ] { this->setTextFormat( formatWidget->format() ); } );
panel->openPanel( formatWidget );
return;
}
QgsTextFormatDialog dialog( mFormat, mMapCanvas, this );
dialog.setWindowTitle( mDialogTitle );
if ( dialog.exec() )
{
setTextFormat( dialog.format() );
}
// reactivate button's window
activateWindow();
}
QgsMapCanvas *QgsFontButton::mapCanvas() const
{
return mMapCanvas;
}
void QgsFontButton::setMapCanvas( QgsMapCanvas *mapCanvas )
{
mMapCanvas = mapCanvas;
}
void QgsFontButton::setTextFormat( const QgsTextFormat &format )
{
mFormat = format;
updatePreview();
emit changed();
}
void QgsFontButton::setColor( const QColor &color )
{
QColor opaque = color;
opaque.setAlphaF( 1.0 );
if ( mFormat.color() != opaque )
{
mFormat.setColor( opaque );
updatePreview();
emit changed();
}
}
void QgsFontButton::copyFormat()
{
QApplication::clipboard()->setMimeData( mFormat.toMimeData() );
}
void QgsFontButton::pasteFormat()
{
QgsTextFormat tempFormat;
if ( formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
{
setTextFormat( tempFormat );
}
}
bool QgsFontButton::event( QEvent *e )
{
if ( e->type() == QEvent::ToolTip )
{
QString toolTip = QStringLiteral( "%1\nSize: %2" ).arg( mFormat.font().family() ).arg( mFormat.size() );
setToolTip( toolTip );
}
return QToolButton::event( e );
}
void QgsFontButton::mousePressEvent( QMouseEvent *e )
{
if ( e->button() == Qt::RightButton )
{
QToolButton::showMenu();
return;
}
else if ( e->button() == Qt::LeftButton )
{
mDragStartPosition = e->pos();
}
QToolButton::mousePressEvent( e );
}
void QgsFontButton::mouseMoveEvent( QMouseEvent *e )
{
//handle dragging fonts from button
if ( !( e->buttons() & Qt::LeftButton ) )
{
//left button not depressed, 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( mFormat.toMimeData() );
drag->setPixmap( createDragIcon() );
drag->exec( Qt::CopyAction );
setDown( false );
}
bool QgsFontButton::colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha )
{
hasAlpha = false;
QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha );
if ( mimeColor.isValid() )
{
resultColor = mimeColor;
return true;
}
//could not get color from mime data
return false;
}
void QgsFontButton::dragEnterEvent( QDragEnterEvent *e )
{
//is dragged data valid color data?
QColor mimeColor;
QgsTextFormat format;
bool hasAlpha = false;
if ( formatFromMimeData( e->mimeData(), format ) )
{
e->acceptProposedAction();
updatePreview( QColor(), &format );
}
else if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
{
//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();
updatePreview( mimeColor );
}
}
void QgsFontButton::dragLeaveEvent( QDragLeaveEvent *e )
{
Q_UNUSED( e );
//reset button color
updatePreview();
}
void QgsFontButton::dropEvent( QDropEvent *e )
{
//is dropped data valid color data?
QColor mimeColor;
QgsTextFormat format;
bool hasAlpha = false;
if ( formatFromMimeData( e->mimeData(), format ) )
{
setTextFormat( format );
return;
}
else if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
{
//accept drop and set new color
e->acceptProposedAction();
if ( hasAlpha )
{
mFormat.setOpacity( mimeColor.alphaF() );
}
mimeColor.setAlphaF( 1.0 );
mFormat.setColor( mimeColor );
QgsRecentColorScheme::addRecentColor( mimeColor );
updatePreview();
emit changed();
}
updatePreview();
}
QPixmap QgsFontButton::createMenuIcon( const QColor &color ) const
{
//create an icon pixmap
QPixmap pixmap( 16, 16 );
pixmap.fill( Qt::transparent );
QPainter p;
p.begin( &pixmap );
//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;
}
QPixmap QgsFontButton::createDragIcon( QSize size, const QgsTextFormat *tempFormat ) const
{
if ( !tempFormat )
tempFormat = &mFormat;
//create an icon pixmap
QPixmap pixmap( size.width(), size.height() );
pixmap.fill( Qt::transparent );
QPainter p;
p.begin( &pixmap );
p.setRenderHint( QPainter::Antialiasing );
QRect rect( 0, 0, size.width(), size.height() );
if ( tempFormat->color().lightnessF() < 0.7 )
{
p.setBrush( QBrush( QColor( 255, 255, 255 ) ) );
p.setPen( QPen( QColor( 150, 150, 150 ), 0 ) );
}
else
{
p.setBrush( QBrush( QColor( 0, 0, 0 ) ) );
p.setPen( QPen( QColor( 100, 100, 100 ), 0 ) );
}
p.drawRect( rect );
p.setBrush( Qt::NoBrush );
p.setPen( Qt::NoPen );
QgsRenderContext context;
QgsMapToPixel newCoordXForm;
newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
context.setMapToPixel( newCoordXForm );
context.setScaleFactor( QgsApplication::desktop()->logicalDpiX() / 25.4 );
context.setUseAdvancedEffects( true );
context.setPainter( &p );
// slightly inset text to account for buffer/background
double xtrans = 0;
if ( tempFormat->buffer().enabled() )
xtrans = context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() );
if ( tempFormat->background().enabled() && tempFormat->background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
xtrans = qMax( xtrans, context.convertToPainterUnits( tempFormat->background().size().width(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
double ytrans = 0.0;
if ( tempFormat->buffer().enabled() )
ytrans = qMax( ytrans, context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() ) );
if ( tempFormat->background().enabled() )
ytrans = qMax( ytrans, context.convertToPainterUnits( tempFormat->background().size().height(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
QRectF textRect = rect;
textRect.setLeft( xtrans );
textRect.setWidth( textRect.width() - xtrans );
textRect.setTop( ytrans );
if ( textRect.height() > 300 )
textRect.setHeight( 300 );
if ( textRect.width() > 2000 )
textRect.setWidth( 2000 );
QgsTextRenderer::drawText( textRect, 0, QgsTextRenderer::AlignCenter, QStringList() << tr( "Aa" ),
context, *tempFormat );
p.end();
return pixmap;
}
void QgsFontButton::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 0 // too slow if many fonts...
QMenu *fontMenu = new QMenu( mFormat.font().family(), mMenu );
QFontDatabase db;
Q_FOREACH ( const QString &family, db.families() )
{
QAction *fontAction = new QAction( family, fontMenu );
QFont f = fontAction->font();
f.setFamily( family );
fontAction->setFont( f );
fontAction->setToolTip( family );
fontMenu->addAction( fontAction );
if ( family == mFormat.font().family() )
{
fontAction->setCheckable( true );
fontAction->setChecked( true );
}
auto setFont = [this, family]
{
QFont f = mFormat.font();
f.setFamily( family );
mFormat.setFont( f );
updatePreview();
emit changed();
};
connect( fontAction, &QAction::triggered, this, setFont );
}
mMenu->addMenu( fontMenu );
#endif
QWidgetAction *sizeAction = new QWidgetAction( mMenu );
QWidget *sizeWidget = new QWidget();
QVBoxLayout *sizeLayout = new QVBoxLayout();
sizeLayout->setMargin( 0 );
sizeLayout->setContentsMargins( 0, 0, 0, 3 );
sizeLayout->setSpacing( 2 );
QgsMenuHeader *sizeLabel = new QgsMenuHeader( tr( "Font size (%1)" ).arg( QgsUnitTypes::toString( mFormat.sizeUnit() ) ) );
sizeLayout->addWidget( sizeLabel );
QgsDoubleSpinBox *sizeSpin = new QgsDoubleSpinBox( nullptr );
sizeSpin->setDecimals( 4 );
sizeSpin->setMaximum( 1e+9 );
sizeSpin->setShowClearButton( false );
sizeSpin->setValue( mFormat.size() );
connect( sizeSpin, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ),
this, [ = ]( double value )
{
mFormat.setSize( value );
updatePreview();
emit changed();
} );
QHBoxLayout *spinLayout = new QHBoxLayout();
spinLayout->setMargin( 0 );
spinLayout->setContentsMargins( 4, 0, 4, 0 );
spinLayout->addWidget( sizeSpin );
sizeLayout->addLayout( spinLayout );
sizeWidget->setLayout( sizeLayout );
sizeAction->setDefaultWidget( sizeWidget );
sizeWidget->setFocusProxy( sizeSpin );
sizeWidget->setFocusPolicy( Qt::StrongFocus );
mMenu->addAction( sizeAction );
QAction *configureAction = new QAction( tr( "Configure format..." ), this );
mMenu->addAction( configureAction );
connect( configureAction, &QAction::triggered, this, &QgsFontButton::showSettingsDialog );
QAction *copyFormatAction = new QAction( tr( "Copy format" ), this );
mMenu->addAction( copyFormatAction );
connect( copyFormatAction, &QAction::triggered, this, &QgsFontButton::copyFormat );
QAction *pasteFormatAction = new QAction( tr( "Paste format" ), 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
QgsTextFormat tempFormat;
if ( formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
{
tempFormat.setSizeUnit( QgsUnitTypes::RenderPixels );
tempFormat.setSize( 14 );
pasteFormatAction->setIcon( createDragIcon( QSize( 16, 16 ), &tempFormat ) );
}
else
{
pasteFormatAction->setEnabled( false );
}
mMenu->addAction( pasteFormatAction );
connect( pasteFormatAction, &QAction::triggered, this, &QgsFontButton::pasteFormat );
mMenu->addSeparator();
QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );
colorWheel->setColor( mFormat.color() );
QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
colorAction->setDismissOnColorSelection( false );
connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsFontButton::setColor );
mMenu->addAction( colorAction );
QgsColorRampWidget *alphaRamp = new QgsColorRampWidget( mMenu, QgsColorWidget::Alpha, QgsColorRampWidget::Horizontal );
QColor alphaColor = mFormat.color();
alphaColor.setAlphaF( mFormat.opacity() );
alphaRamp->setColor( alphaColor );
QgsColorWidgetAction *alphaAction = new QgsColorWidgetAction( alphaRamp, mMenu, mMenu );
alphaAction->setDismissOnColorSelection( false );
connect( alphaAction, &QgsColorWidgetAction::colorChanged, this, [ = ]( const QColor & color )
{
double opacity = color.alphaF();
mFormat.setOpacity( opacity );
updatePreview();
emit changed();
} );
connect( colorAction, &QgsColorWidgetAction::colorChanged, alphaRamp, [alphaRamp]( const QColor & color ) { alphaRamp->setColor( color, false ); }
);
mMenu->addAction( alphaAction );
//get schemes with ShowInColorButtonMenu flag set
QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu );
QList< QgsColorScheme * >::iterator it = schemeList.begin();
for ( ; it != schemeList.end(); ++it )
{
QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, QStringLiteral( "labeling" ), this );
colorAction->setBaseColor( mFormat.color() );
mMenu->addAction( colorAction );
connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsFontButton::setColor );
connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsFontButton::addRecentColor );
}
mMenu->addSeparator();
QAction *copyColorAction = new QAction( tr( "Copy color" ), this );
mMenu->addAction( copyColorAction );
connect( copyColorAction, &QAction::triggered, this, &QgsFontButton::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;
bool hasAlpha = false;
if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
{
pasteColorAction->setIcon( createMenuIcon( clipColor ) );
}
else
{
pasteColorAction->setEnabled( false );
}
mMenu->addAction( pasteColorAction );
connect( pasteColorAction, &QAction::triggered, this, &QgsFontButton::pasteColor );
}
void QgsFontButton::addRecentColor( const QColor &color )
{
QgsRecentColorScheme::addRecentColor( color );
}
bool QgsFontButton::formatFromMimeData( const QMimeData *mimeData, QgsTextFormat &resultFormat ) const
{
bool ok = false;
resultFormat = QgsTextFormat::fromMimeData( mimeData, &ok );
return ok;
}
void QgsFontButton::changeEvent( QEvent *e )
{
if ( e->type() == QEvent::EnabledChange )
{
updatePreview();
}
QToolButton::changeEvent( e );
}
void QgsFontButton::showEvent( QShowEvent *e )
{
updatePreview();
QToolButton::showEvent( e );
}
void QgsFontButton::resizeEvent( QResizeEvent *event )
{
QToolButton::resizeEvent( event );
//recalculate icon size and redraw icon
mIconSize = QSize();
updatePreview();
}
void QgsFontButton::updatePreview( const QColor &color, QgsTextFormat *format )
{
QgsTextFormat tempFormat;
if ( format )
tempFormat = *format;
else
tempFormat = mFormat;
if ( color.isValid() )
tempFormat.setColor( color );
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 );
QPainter p;
p.begin( &pixmap );
p.setRenderHint( QPainter::Antialiasing );
QRect rect( 0, 0, currentIconSize.width(), currentIconSize.height() );
QgsRenderContext context;
QgsMapToPixel newCoordXForm;
newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
context.setMapToPixel( newCoordXForm );
context.setScaleFactor( QgsApplication::desktop()->logicalDpiX() / 25.4 );
context.setUseAdvancedEffects( true );
context.setPainter( &p );
// slightly inset text to account for buffer/background
double xtrans = 0;
if ( tempFormat.buffer().enabled() )
xtrans = context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
xtrans = qMax( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
double ytrans = 0.0;
if ( tempFormat.buffer().enabled() )
ytrans = qMax( ytrans, context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
if ( tempFormat.background().enabled() )
ytrans = qMax( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
QRectF textRect = rect;
textRect.setLeft( xtrans );
textRect.setWidth( textRect.width() - xtrans );
textRect.setTop( ytrans );
if ( textRect.height() > 300 )
textRect.setHeight( 300 );
if ( textRect.width() > 2000 )
textRect.setWidth( 2000 );
QgsTextRenderer::drawText( textRect, 0, QgsTextRenderer::AlignLeft, QStringList() << "Font",
context, tempFormat );
p.end();
setIconSize( currentIconSize );
setIcon( pixmap );
}
void QgsFontButton::copyColor()
{
//copy color
QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mFormat.color() ) );
}
void QgsFontButton::pasteColor()
{
QColor clipColor;
bool hasAlpha = false;
if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
{
//paste color
setColor( clipColor );
QgsRecentColorScheme::addRecentColor( clipColor );
}
}
void QgsFontButton::setDialogTitle( const QString &title )
{
mDialogTitle = title;
}
QString QgsFontButton::dialogTitle() const
{
return mDialogTitle;
}

210
src/gui/qgsfontbutton.h Normal file
View File

@ -0,0 +1,210 @@
/***************************************************************************
qgsfontbutton.h
---------------
Date : May 2017
Copyright : (C) 2017 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFONTBUTTON_H
#define QGSFONTBUTTON_H
#include "qgis_gui.h"
#include "qgis.h"
#include "qgstextrenderer.h"
#include <QToolButton>
class QgsMapCanvas;
/**
* \ingroup gui
* \class QgsFontButton
* A button for customising QgsTextFormat settings.
*
* The button will open a detailed text format settings dialog when clicked. An attached drop down
* menu allows for copying and pasting text styles, picking colors for the text, and for dropping
* colors from other color widgets.
* \since QGIS 3.0
*/
class GUI_EXPORT QgsFontButton : public QToolButton
{
Q_OBJECT
Q_PROPERTY( QString dialogTitle READ dialogTitle WRITE setDialogTitle )
Q_PROPERTY( QgsTextFormat textFormat READ textFormat WRITE setTextFormat NOTIFY changed )
public:
/**
* Construct a new font button.
* Use \a parent to attach a parent QWidget to the dialog.
* Use \a dialogTitle string to define the title to show in the text settings dialog.
*/
QgsFontButton( QWidget *parent SIP_TRANSFERTHIS = nullptr, const QString &dialogTitle = QString() );
virtual QSize sizeHint() const override;
/**
* Sets the \a title for the text settings dialog window.
* \see dialogTitle()
*/
void setDialogTitle( const QString &title );
/**
* Returns the title for the text settings dialog window.
* \see setDialogTitle()
*/
QString dialogTitle() const;
/**
* Returns the map canvas associated with the widget.
* \see setMapCanvas()
*/
QgsMapCanvas *mapCanvas() const;
/**
* Sets a map \a canvas to associate with the widget. This allows the
* widget to fetch current settings from the map canvas, such as current scale.
* \see mapCanvas()
*/
void setMapCanvas( QgsMapCanvas *canvas );
/**
* Returns the current text formatting set by the widget.
* \see setTextFormat()
*/
QgsTextFormat textFormat() const { return mFormat; }
public slots:
/**
* Sets the current text \a format to show in the widget.
* \see textFormat()
*/
void setTextFormat( const QgsTextFormat &format );
/**
* Sets the current \a color for the text. Will emit a changed signal if the color is different
* to the previous text color.
*/
void setColor( const QColor &color );
/** Copies the current text format to the clipboard.
* \see pasteFormat()
*/
void copyFormat();
/**
* Pastes a format from the clipboard. If clipboard does not contain a valid
* format then no change is applied.
* \see copyFormat()
*/
void pasteFormat();
/** Copies the current text color to the clipboard.
* \see pasteColor()
*/
void copyColor();
/**
* Pastes a color from the clipboard to the text format. If clipboard does not contain a valid
* color or string representation of a color, then no change is applied.
* \see copyColor()
*/
void pasteColor();
signals:
/**
* Emitted when the widget's text format settings are changed.
*/
void changed();
protected:
bool event( QEvent *e ) override;
void changeEvent( QEvent *e ) override;
void showEvent( QShowEvent *e ) override;
void resizeEvent( QResizeEvent *event ) override;
// Reimplemented to detect right mouse button clicks on the color button and allow dragging colors
void mousePressEvent( QMouseEvent *e ) override;
// Reimplemented to allow dragging fonts from button
void mouseMoveEvent( QMouseEvent *e ) override;
// Reimplemented to accept dragged colors
void dragEnterEvent( QDragEnterEvent *e ) override;
// Reimplemented to reset button appearance after drag leave
void dragLeaveEvent( QDragLeaveEvent *e ) override;
// Reimplemented to accept dropped colors
void dropEvent( QDropEvent *e ) override;
private slots:
void showSettingsDialog();
/** Creates the drop down menu entries
*/
void prepareMenu();
void addRecentColor( const QColor &color );
private:
QString mDialogTitle;
QgsTextFormat mFormat;
QgsMapCanvas *mMapCanvas = nullptr;
QPoint mDragStartPosition;
QMenu *mMenu = nullptr;
QSize mIconSize;
/**
* Attempts to parse mimeData as a text format.
* \returns true if mime data could be intrepreted as a format
* \param mimeData mime data
* \param resultFormat destination for text format
* \see colorFromMimeData
*/
bool formatFromMimeData( const QMimeData *mimeData, QgsTextFormat &resultFormat ) const;
/** Attempts to parse mimeData as a color, either via the mime data's color data or by
* parsing a textual representation of a color.
* \returns true if mime data could be intrepreted as a color
* \param mimeData mime data
* \param resultColor QColor to store evaluated color
* \param hasAlpha will be set to true if mime data also included an alpha component
* \see formatFromMimeData
*/
bool colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha );
/**
* Create a \a color icon for display in the drop down menu.
*/
QPixmap createMenuIcon( const QColor &color ) const;
/**
* Creates a drag icon showing the current font style.
*/
QPixmap createDragIcon( QSize size = QSize( 50, 50 ), const QgsTextFormat *tempFormat = nullptr ) const;
/**
* Regenerates the text preview. If \a color is specified, a temporary color preview
* is shown instead.
*/
void updatePreview( const QColor &color = QColor(), QgsTextFormat *tempFormat = nullptr );
};
#endif // QGSFONTBUTTON_H

View File

@ -61,6 +61,7 @@ ADD_PYTHON_TEST(PyQgsField test_qgsfield.py)
ADD_PYTHON_TEST(PyQgsFieldModel test_qgsfieldmodel.py)
ADD_PYTHON_TEST(PyQgsFilterLineEdit test_qgsfilterlineedit.py)
ADD_PYTHON_TEST(PyQgsFloatingWidget test_qgsfloatingwidget.py)
ADD_PYTHON_TEST(PyQgsFontButton test_qgsfontbutton.py)
ADD_PYTHON_TEST(PyQgsFontUtils test_qgsfontutils.py)
ADD_PYTHON_TEST(PyQgsGeometryAvoidIntersections test_qgsgeometry_avoid_intersections.py)
ADD_PYTHON_TEST(PyQgsGeometryGeneratorSymbolLayer test_qgsgeometrygeneratorsymbollayer.py)

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsFontButton.
.. note:: 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.
"""
__author__ = 'Nyall Dawson'
__date__ = '04/06/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis # NOQA
from qgis.core import QgsTextFormat
from qgis.gui import QgsFontButton, QgsMapCanvas
from qgis.testing import start_app, unittest
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtTest import QSignalSpy
from utilities import getTestFont
start_app()
class TestQgsFontButton(unittest.TestCase):
def testGettersSetters(self):
button = QgsFontButton()
canvas = QgsMapCanvas()
button.setDialogTitle('test title')
self.assertEqual(button.dialogTitle(), 'test title')
button.setMapCanvas(canvas)
self.assertEqual(button.mapCanvas(), canvas)
def testSetGetFormat(self):
button = QgsFontButton()
s = QgsTextFormat()
s.setFont(getTestFont())
s.setNamedStyle('Italic')
s.setSize(5)
s.setColor(QColor(255, 0, 0))
s.setOpacity(0.5)
signal_spy = QSignalSpy(button.changed)
button.setTextFormat(s)
self.assertEqual(len(signal_spy), 1)
r = button.textFormat()
self.assertEqual(r.font().family(), 'QGIS Vera Sans')
self.assertEqual(r.namedStyle(), 'Italic')
self.assertEqual(r.size(), 5)
self.assertEqual(r.color(), QColor(255, 0, 0))
self.assertEqual(r.opacity(), 0.5)
def testSetColor(self):
button = QgsFontButton()
s = QgsTextFormat()
s.setFont(getTestFont())
s.setNamedStyle('Italic')
s.setSize(5)
s.setColor(QColor(255, 0, 0))
s.setOpacity(0.5)
button.setTextFormat(s)
signal_spy = QSignalSpy(button.changed)
button.setColor(QColor(0, 255, 0))
self.assertEqual(len(signal_spy), 1)
r = button.textFormat()
self.assertEqual(r.font().family(), 'QGIS Vera Sans')
self.assertEqual(r.namedStyle(), 'Italic')
self.assertEqual(r.size(), 5)
self.assertEqual(r.color().name(), QColor(0, 255, 0).name())
self.assertEqual(r.opacity(), 0.5)
# set same color, should not emit signal
button.setColor(QColor(0, 255, 0))
self.assertEqual(len(signal_spy), 1)
# color with transparency - should be stripped
button.setColor(QColor(0, 255, 0, 100))
r = button.textFormat()
self.assertEqual(r.color(), QColor(0, 255, 0))
if __name__ == '__main__':
unittest.main()

View File

@ -324,6 +324,16 @@ class PyQgsTextRenderer(unittest.TestCase):
t.readXml(parent, QgsReadWriteContext())
self.checkTextFormat(t)
def testFormatToFromMimeData(self):
"""Test converting format to and from mime data"""
s = self.createFormatSettings()
md = s.toMimeData()
from_mime, ok = QgsTextFormat.fromMimeData(None)
self.assertFalse(ok)
from_mime, ok = QgsTextFormat.fromMimeData(md)
self.assertTrue(ok)
self.checkTextFormat(from_mime)
def containsAdvancedEffects(self):
t = QgsTextFormat()
self.assertFalse(t.containsAdvancedEffects())