1 /***************************************************************************
2      qgsfontbutton.h
3      ---------------
4     Date                 : May 2017
5     Copyright            : (C) 2017 by Nyall Dawson
6     Email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgsfontbutton.h"
17 #include "qgstextformatwidget.h"
18 #include "qgssymbollayerutils.h"
19 #include "qgscolorscheme.h"
20 #include "qgsmapcanvas.h"
21 #include "qgscolorwidgets.h"
22 #include "qgscolorschemeregistry.h"
23 #include "qgscolorswatchgrid.h"
24 #include "qgsdoublespinbox.h"
25 #include "qgsunittypes.h"
26 #include "qgsmenuheader.h"
27 #include "qgsfontutils.h"
28 #include "qgsapplication.h"
29 #include "qgsexpressioncontextutils.h"
30 #include "qgsvectorlayer.h"
31 #include "qgstextrenderer.h"
32 #include <QMenu>
33 #include <QClipboard>
34 #include <QDrag>
35 #include <QDesktopWidget>
36 #include <QToolTip>
37 
QgsFontButton(QWidget * parent,const QString & dialogTitle)38 QgsFontButton::QgsFontButton( QWidget *parent, const QString &dialogTitle )
39   : QToolButton( parent )
40   , mDialogTitle( dialogTitle.isEmpty() ? tr( "Text Format" ) : dialogTitle )
41   , mNullFormatString( tr( "No Format" ) )
42 {
43   setText( tr( "Font" ) );
44 
45   setAcceptDrops( true );
46   connect( this, &QAbstractButton::clicked, this, &QgsFontButton::showSettingsDialog );
47 
48   //setup dropdown menu
49   mMenu = new QMenu( this );
50   connect( mMenu, &QMenu::aboutToShow, this, &QgsFontButton::prepareMenu );
51   setMenu( mMenu );
52   setPopupMode( QToolButton::MenuButtonPopup );
53 
54   //make sure height of button looks good under different platforms
55   QSize size = QToolButton::minimumSizeHint();
56   int fontHeight = Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.4;
57 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
58   int minWidth = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 20;
59 #else
60   int minWidth = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 20;
61 #endif
62   mSizeHint = QSize( std::max( minWidth, size.width() ), std::max( size.height(), fontHeight ) );
63 }
64 
minimumSizeHint() const65 QSize QgsFontButton::minimumSizeHint() const
66 {
67   return mSizeHint;
68 }
69 
sizeHint() const70 QSize QgsFontButton::sizeHint() const
71 {
72   return mSizeHint;
73 }
74 
showSettingsDialog()75 void QgsFontButton::showSettingsDialog()
76 {
77   switch ( mMode )
78   {
79     case ModeTextRenderer:
80     {
81       QgsExpressionContext context;
82       if ( mExpressionContextGenerator )
83         context  = mExpressionContextGenerator->createExpressionContext();
84       else
85       {
86         context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer.data() ) );
87       }
88 
89       QgsSymbolWidgetContext symbolContext;
90       symbolContext.setExpressionContext( &context );
91       symbolContext.setMapCanvas( mMapCanvas );
92       symbolContext.setMessageBar( mMessageBar );
93 
94       QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
95       if ( panel && panel->dockMode() )
96       {
97         mActivePanel = new QgsTextFormatPanelWidget( mFormat, mMapCanvas, this, mLayer.data() );
98         mActivePanel->setPanelTitle( mDialogTitle );
99         mActivePanel->setContext( symbolContext );
100 
101         connect( mActivePanel, &QgsTextFormatPanelWidget::widgetChanged, this, [ this ] { setTextFormat( mActivePanel->format() ); } );
102         panel->openPanel( mActivePanel );
103         return;
104       }
105 
106       QgsTextFormatDialog dialog( mFormat, mMapCanvas, this, QgsGuiUtils::ModalDialogFlags, mLayer.data() );
107       dialog.setWindowTitle( mDialogTitle );
108       dialog.setContext( symbolContext );
109       if ( dialog.exec() )
110       {
111         setTextFormat( dialog.format() );
112         QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
113       }
114       break;
115     }
116 
117     case ModeQFont:
118     {
119       bool ok;
120       QFont newFont = QgsGuiUtils::getFont( ok, mFont, mDialogTitle );
121       if ( ok )
122       {
123         QgsFontUtils::addRecentFontFamily( newFont.family() );
124         setCurrentFont( newFont );
125       }
126       break;
127     }
128   }
129 
130   // reactivate button's window
131   activateWindow();
132   raise();
133 }
134 
mapCanvas() const135 QgsMapCanvas *QgsFontButton::mapCanvas() const
136 {
137   return mMapCanvas;
138 }
139 
setMapCanvas(QgsMapCanvas * mapCanvas)140 void QgsFontButton::setMapCanvas( QgsMapCanvas *mapCanvas )
141 {
142   mMapCanvas = mapCanvas;
143 }
144 
setMessageBar(QgsMessageBar * bar)145 void QgsFontButton::setMessageBar( QgsMessageBar *bar )
146 {
147   mMessageBar = bar;
148 }
149 
messageBar() const150 QgsMessageBar *QgsFontButton::messageBar() const
151 {
152   return mMessageBar;
153 }
154 
setTextFormat(const QgsTextFormat & format)155 void QgsFontButton::setTextFormat( const QgsTextFormat &format )
156 {
157   if ( mActivePanel && !format.isValid() )
158     mActivePanel->acceptPanel();
159 
160   mFormat = format;
161   updatePreview();
162 
163   if ( mActivePanel && format.isValid() )
164     mActivePanel->setFormat( format );
165   emit changed();
166 }
167 
setToNullFormat()168 void QgsFontButton::setToNullFormat()
169 {
170   mFormat = QgsTextFormat();
171   updatePreview();
172   emit changed();
173 }
174 
setColor(const QColor & color)175 void QgsFontButton::setColor( const QColor &color )
176 {
177   QColor opaque = color;
178   opaque.setAlphaF( 1.0 );
179 
180   if ( mNullFormatAction )
181     mNullFormatAction->setChecked( false );
182 
183   if ( mFormat.color() != opaque )
184   {
185     mFormat.setColor( opaque );
186     updatePreview();
187     emit changed();
188   }
189 }
190 
copyFormat()191 void QgsFontButton::copyFormat()
192 {
193   switch ( mMode )
194   {
195     case ModeTextRenderer:
196       QApplication::clipboard()->setMimeData( mFormat.toMimeData() );
197       break;
198 
199     case ModeQFont:
200       QApplication::clipboard()->setMimeData( QgsFontUtils::toMimeData( mFont ) );
201       break;
202   }
203 }
204 
pasteFormat()205 void QgsFontButton::pasteFormat()
206 {
207   QgsTextFormat tempFormat;
208   QFont font;
209   if ( mMode == ModeTextRenderer && formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
210   {
211     setTextFormat( tempFormat );
212     QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
213   }
214   else if ( mMode == ModeQFont && fontFromMimeData( QApplication::clipboard()->mimeData(), font ) )
215   {
216     QgsFontUtils::addRecentFontFamily( font.family() );
217     setCurrentFont( font );
218   }
219 }
220 
event(QEvent * e)221 bool QgsFontButton::event( QEvent *e )
222 {
223   if ( e->type() == QEvent::ToolTip )
224   {
225     QHelpEvent *helpEvent = static_cast< QHelpEvent *>( e );
226     QString toolTip;
227     double fontSize = 0.0;
228     switch ( mMode )
229     {
230       case ModeTextRenderer:
231         fontSize = mFormat.size();
232         break;
233 
234       case ModeQFont:
235         fontSize = mFont.pointSizeF();
236         break;
237     }
238     toolTip = QStringLiteral( "<b>%1</b><br>%2<br>Size: %3" ).arg( text(), mMode == ModeTextRenderer ? mFormat.font().family() : mFont.family() ).arg( fontSize );
239     QToolTip::showText( helpEvent->globalPos(), toolTip );
240   }
241   return QToolButton::event( e );
242 }
243 
mousePressEvent(QMouseEvent * e)244 void QgsFontButton::mousePressEvent( QMouseEvent *e )
245 {
246   if ( e->button() == Qt::RightButton )
247   {
248     QToolButton::showMenu();
249     return;
250   }
251   else if ( e->button() == Qt::LeftButton )
252   {
253     mDragStartPosition = e->pos();
254   }
255   QToolButton::mousePressEvent( e );
256 }
257 
mouseMoveEvent(QMouseEvent * e)258 void QgsFontButton::mouseMoveEvent( QMouseEvent *e )
259 {
260   //handle dragging fonts from button
261 
262   if ( !( e->buttons() & Qt::LeftButton ) )
263   {
264     //left button not depressed, so not a drag
265     QToolButton::mouseMoveEvent( e );
266     return;
267   }
268 
269   if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
270   {
271     //mouse not moved, so not a drag
272     QToolButton::mouseMoveEvent( e );
273     return;
274   }
275 
276   //user is dragging font
277   QDrag *drag = new QDrag( this );
278   switch ( mMode )
279   {
280     case ModeTextRenderer:
281       drag->setMimeData( mFormat.toMimeData() );
282       break;
283 
284     case ModeQFont:
285       drag->setMimeData( QgsFontUtils::toMimeData( mFont ) );
286       break;
287   }
288   const int iconSize = QgsGuiUtils::scaleIconSize( 50 );
289   drag->setPixmap( createDragIcon( QSize( iconSize, iconSize ) ) );
290   drag->exec( Qt::CopyAction );
291   setDown( false );
292 }
293 
colorFromMimeData(const QMimeData * mimeData,QColor & resultColor,bool & hasAlpha)294 bool QgsFontButton::colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha )
295 {
296   hasAlpha = false;
297   QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha );
298 
299   if ( mimeColor.isValid() )
300   {
301     resultColor = mimeColor;
302     return true;
303   }
304 
305   //could not get color from mime data
306   return false;
307 }
308 
dragEnterEvent(QDragEnterEvent * e)309 void QgsFontButton::dragEnterEvent( QDragEnterEvent *e )
310 {
311   //is dragged data valid font data?
312   QColor mimeColor;
313   QgsTextFormat format;
314   QFont font;
315   bool hasAlpha = false;
316 
317   if ( mMode == ModeTextRenderer && formatFromMimeData( e->mimeData(), format ) )
318   {
319     e->acceptProposedAction();
320     updatePreview( QColor(), &format );
321   }
322   else if ( mMode == ModeQFont && fontFromMimeData( e->mimeData(), font ) )
323   {
324     e->acceptProposedAction();
325     updatePreview( QColor(), nullptr, &font );
326   }
327   else if ( mMode == ModeTextRenderer && colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
328   {
329     //if so, we accept the drag, and temporarily change the button's color
330     //to match the dragged color. This gives immediate feedback to the user
331     //that colors can be dropped here
332     e->acceptProposedAction();
333     updatePreview( mimeColor );
334   }
335 }
336 
dragLeaveEvent(QDragLeaveEvent * e)337 void QgsFontButton::dragLeaveEvent( QDragLeaveEvent *e )
338 {
339   Q_UNUSED( e )
340   //reset button color
341   updatePreview();
342 }
343 
dropEvent(QDropEvent * e)344 void QgsFontButton::dropEvent( QDropEvent *e )
345 {
346   //is dropped data valid format data?
347   QColor mimeColor;
348   QgsTextFormat format;
349   QFont font;
350   bool hasAlpha = false;
351   if ( mMode == ModeTextRenderer && formatFromMimeData( e->mimeData(), format ) )
352   {
353     setTextFormat( format );
354     QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
355     return;
356   }
357   else if ( mMode == ModeQFont && fontFromMimeData( e->mimeData(), font ) )
358   {
359     QgsFontUtils::addRecentFontFamily( font.family() );
360     setCurrentFont( font );
361     return;
362   }
363   else if ( mMode == ModeTextRenderer && colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
364   {
365     //accept drop and set new color
366     e->acceptProposedAction();
367 
368     if ( hasAlpha )
369     {
370       mFormat.setOpacity( mimeColor.alphaF() );
371     }
372     mimeColor.setAlphaF( 1.0 );
373     mFormat.setColor( mimeColor );
374     QgsRecentColorScheme::addRecentColor( mimeColor );
375     updatePreview();
376     emit changed();
377   }
378   updatePreview();
379 }
380 
wheelEvent(QWheelEvent * event)381 void QgsFontButton::wheelEvent( QWheelEvent *event )
382 {
383   double size = 0;
384   switch ( mMode )
385   {
386     case ModeTextRenderer:
387       size = mFormat.size();
388       break;
389 
390     case ModeQFont:
391       size = mFont.pointSizeF();
392       break;
393   }
394 
395   double increment = ( event->modifiers() & Qt::ControlModifier ) ? 0.1 : 1;
396   if ( event->delta() > 0 )
397   {
398     size += increment;
399   }
400   else
401   {
402     size -= increment;
403   }
404   size = std::max( size, 1.0 );
405 
406   switch ( mMode )
407   {
408     case ModeTextRenderer:
409     {
410       QgsTextFormat newFormat = mFormat;
411       newFormat.setSize( size );
412       setTextFormat( newFormat );
413       break;
414     }
415 
416     case ModeQFont:
417     {
418       QFont newFont = mFont;
419       newFont.setPointSizeF( size );
420       setCurrentFont( newFont );
421       break;
422     }
423   }
424 
425   event->accept();
426 }
427 
createColorIcon(const QColor & color) const428 QPixmap QgsFontButton::createColorIcon( const QColor &color ) const
429 {
430   //create an icon pixmap
431   const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
432   QPixmap pixmap( iconSize, iconSize );
433   pixmap.fill( Qt::transparent );
434 
435   QPainter p;
436   p.begin( &pixmap );
437 
438   //draw color over pattern
439   p.setBrush( QBrush( color ) );
440 
441   //draw border
442   p.setPen( QColor( 197, 197, 197 ) );
443   p.drawRect( 0, 0, iconSize - 1, iconSize - 1 );
444   p.end();
445   return pixmap;
446 }
447 
createDragIcon(QSize size,const QgsTextFormat * tempFormat,const QFont * tempFont) const448 QPixmap QgsFontButton::createDragIcon( QSize size, const QgsTextFormat *tempFormat, const QFont *tempFont ) const
449 {
450   if ( !tempFormat )
451     tempFormat = &mFormat;
452   if ( !tempFont )
453     tempFont = &mFont;
454 
455   //create an icon pixmap
456   QPixmap pixmap( size.width(), size.height() );
457   pixmap.fill( Qt::transparent );
458   QPainter p;
459   p.begin( &pixmap );
460   p.setRenderHint( QPainter::Antialiasing );
461   QRect rect( 0, 0, size.width(), size.height() );
462 
463   if ( mMode == ModeQFont || tempFormat->color().lightnessF() < 0.7 )
464   {
465     p.setBrush( QBrush( QColor( 255, 255, 255 ) ) );
466     p.setPen( QPen( QColor( 150, 150, 150 ), 0 ) );
467   }
468   else
469   {
470     p.setBrush( QBrush( QColor( 0, 0, 0 ) ) );
471     p.setPen( QPen( QColor( 100, 100, 100 ), 0 ) );
472   }
473   p.drawRect( rect );
474   p.setBrush( Qt::NoBrush );
475   p.setPen( Qt::NoPen );
476 
477   switch ( mMode )
478   {
479     case ModeTextRenderer:
480     {
481       QgsRenderContext context;
482       QgsMapToPixel newCoordXForm;
483       newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
484       context.setMapToPixel( newCoordXForm );
485 
486       context.setScaleFactor( QgsApplication::desktop()->logicalDpiX() / 25.4 );
487       context.setUseAdvancedEffects( true );
488       context.setPainter( &p );
489 
490       // slightly inset text to account for buffer/background
491       double xtrans = 0;
492       if ( tempFormat->buffer().enabled() )
493         xtrans = context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() );
494       if ( tempFormat->background().enabled() && tempFormat->background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
495         xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat->background().size().width(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
496 
497       double ytrans = 0.0;
498       if ( tempFormat->buffer().enabled() )
499         ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() ) );
500       if ( tempFormat->background().enabled() )
501         ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat->background().size().height(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
502 
503       QRectF textRect = rect;
504       textRect.setLeft( xtrans );
505       textRect.setWidth( textRect.width() - xtrans );
506       textRect.setTop( ytrans );
507       if ( textRect.height() > 300 )
508         textRect.setHeight( 300 );
509       if ( textRect.width() > 2000 )
510         textRect.setWidth( 2000 );
511 
512       QgsTextRenderer::drawText( textRect, 0, QgsTextRenderer::AlignCenter, QStringList() << tr( "Aa" ),
513                                  context, *tempFormat );
514       break;
515     }
516     case ModeQFont:
517     {
518       p.setBrush( Qt::NoBrush );
519       p.setPen( QColor( 0, 0, 0 ) );
520       p.setFont( *tempFont );
521       QRectF textRect = rect;
522       textRect.setLeft( 2 );
523       p.drawText( textRect, Qt::AlignVCenter, tr( "Aa" ) );
524       break;
525     }
526   }
527 
528   p.end();
529   return pixmap;
530 }
531 
prepareMenu()532 void QgsFontButton::prepareMenu()
533 {
534   //we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
535   //QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
536   //for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
537   //menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
538   mMenu->clear();
539 
540   if ( mMode == ModeTextRenderer && mShowNoFormat )
541   {
542     mNullFormatAction = new QAction( mNullFormatString, this );
543     mMenu->addAction( mNullFormatAction );
544     connect( mNullFormatAction, &QAction::triggered, this, &QgsFontButton::setToNullFormat );
545     if ( !mFormat.isValid() )
546     {
547       mNullFormatAction->setCheckable( true );
548       mNullFormatAction->setChecked( true );
549     }
550   }
551 
552   QWidgetAction *sizeAction = new QWidgetAction( mMenu );
553   QWidget *sizeWidget = new QWidget();
554   QVBoxLayout *sizeLayout = new QVBoxLayout();
555   sizeLayout->setContentsMargins( 0, 0, 0, 3 );
556   sizeLayout->setSpacing( 2 );
557 
558   QString fontHeaderLabel;
559   switch ( mMode )
560   {
561     case ModeTextRenderer:
562       fontHeaderLabel = tr( "Font size (%1)" ).arg( QgsUnitTypes::toString( mFormat.sizeUnit() ) );
563       break;
564 
565     case ModeQFont:
566       fontHeaderLabel = tr( "Font size (pt)" );
567       break;
568   }
569 
570   QgsMenuHeader *sizeLabel = new QgsMenuHeader( fontHeaderLabel );
571   sizeLayout->addWidget( sizeLabel );
572 
573   QgsDoubleSpinBox *sizeSpin = new QgsDoubleSpinBox( nullptr );
574   sizeSpin->setDecimals( 4 );
575   sizeSpin->setMaximum( 1e+9 );
576   sizeSpin->setShowClearButton( false );
577   sizeSpin->setValue( mMode == ModeTextRenderer ? mFormat.size() : mFont.pointSizeF() );
578   connect( sizeSpin, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ),
579            this, [ = ]( double value )
580   {
581     switch ( mMode )
582     {
583       case ModeTextRenderer:
584         if ( mNullFormatAction )
585           mNullFormatAction->setChecked( false );
586         mFormat.setSize( value );
587         break;
588       case ModeQFont:
589         mFont.setPointSizeF( value );
590         break;
591     }
592     updatePreview();
593     emit changed();
594   } );
595   QHBoxLayout *spinLayout = new QHBoxLayout();
596   spinLayout->setContentsMargins( 4, 0, 4, 0 );
597   spinLayout->addWidget( sizeSpin );
598   sizeLayout->addLayout( spinLayout );
599   sizeWidget->setLayout( sizeLayout );
600   sizeAction->setDefaultWidget( sizeWidget );
601   sizeWidget->setFocusProxy( sizeSpin );
602   sizeWidget->setFocusPolicy( Qt::StrongFocus );
603   mMenu->addAction( sizeAction );
604 
605   QMenu *recentFontMenu = new QMenu( tr( "Recent Fonts" ), mMenu );
606   const auto recentFontFamilies { QgsFontUtils::recentFontFamilies() };
607   for ( const QString &family : recentFontFamilies )
608   {
609     QAction *fontAction = new QAction( family, recentFontMenu );
610     QFont f = fontAction->font();
611     f.setFamily( family );
612     fontAction->setFont( f );
613     fontAction->setToolTip( family );
614     recentFontMenu->addAction( fontAction );
615     if ( ( mMode == ModeTextRenderer && family == mFormat.font().family() )
616          || ( mMode == ModeQFont && family == mFont.family() ) )
617     {
618       fontAction->setCheckable( true );
619       fontAction->setChecked( true );
620     }
621     auto setFont = [this, family]
622     {
623       switch ( mMode )
624       {
625         case ModeTextRenderer:
626         {
627           QgsTextFormat newFormat = mFormat;
628           QFont f = newFormat.font();
629           f.setFamily( family );
630           newFormat.setFont( f );
631           setTextFormat( newFormat );
632           QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
633           break;
634         }
635         case ModeQFont:
636         {
637           QFont font = mFont;
638           font.setFamily( family );
639           setCurrentFont( font );
640           QgsFontUtils::addRecentFontFamily( family );
641           break;
642         }
643       }
644     };
645     connect( fontAction, &QAction::triggered, this, setFont );
646   }
647   mMenu->addMenu( recentFontMenu );
648 
649   QAction *configureAction = new QAction( tr( "Configure Format…" ), this );
650   mMenu->addAction( configureAction );
651   connect( configureAction, &QAction::triggered, this, &QgsFontButton::showSettingsDialog );
652 
653   QAction *copyFormatAction = new QAction( tr( "Copy Format" ), this );
654   mMenu->addAction( copyFormatAction );
655   connect( copyFormatAction, &QAction::triggered, this, &QgsFontButton::copyFormat );
656   QAction *pasteFormatAction = new QAction( tr( "Paste Format" ), this );
657   //enable or disable paste action based on current clipboard contents. We always show the paste
658   //action, even if it's disabled, to give hint to the user that pasting colors is possible
659   QgsTextFormat tempFormat;
660   QFont tempFont;
661   const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
662   if ( mMode == ModeTextRenderer && formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
663   {
664     tempFormat.setSizeUnit( QgsUnitTypes::RenderPixels );
665     tempFormat.setSize( 14 );
666     pasteFormatAction->setIcon( createDragIcon( QSize( iconSize, iconSize ), &tempFormat ) );
667   }
668   else if ( mMode == ModeQFont && fontFromMimeData( QApplication::clipboard()->mimeData(), tempFont ) )
669   {
670     tempFont.setPointSize( 8 );
671     pasteFormatAction->setIcon( createDragIcon( QSize( iconSize, iconSize ), nullptr, &tempFont ) );
672   }
673   else
674   {
675     pasteFormatAction->setEnabled( false );
676   }
677   mMenu->addAction( pasteFormatAction );
678   connect( pasteFormatAction, &QAction::triggered, this, &QgsFontButton::pasteFormat );
679 
680   if ( mMode == ModeTextRenderer )
681   {
682     mMenu->addSeparator();
683 
684     QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );
685     colorWheel->setColor( mFormat.color() );
686     QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
687     colorAction->setDismissOnColorSelection( false );
688     connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsFontButton::setColor );
689     mMenu->addAction( colorAction );
690 
691     QgsColorRampWidget *alphaRamp = new QgsColorRampWidget( mMenu, QgsColorWidget::Alpha, QgsColorRampWidget::Horizontal );
692     QColor alphaColor = mFormat.color();
693     alphaColor.setAlphaF( mFormat.opacity() );
694     alphaRamp->setColor( alphaColor );
695     QgsColorWidgetAction *alphaAction = new QgsColorWidgetAction( alphaRamp, mMenu, mMenu );
696     alphaAction->setDismissOnColorSelection( false );
697     connect( alphaAction, &QgsColorWidgetAction::colorChanged, this, [ = ]( const QColor & color )
698     {
699       double opacity = color.alphaF();
700       mFormat.setOpacity( opacity );
701       updatePreview();
702       if ( mNullFormatAction )
703         mNullFormatAction->setChecked( false );
704       emit changed();
705     } );
706     connect( colorAction, &QgsColorWidgetAction::colorChanged, alphaRamp, [alphaRamp]( const QColor & color ) { alphaRamp->setColor( color, false ); }
707            );
708     mMenu->addAction( alphaAction );
709 
710     //get schemes with ShowInColorButtonMenu flag set
711     QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu );
712     QList< QgsColorScheme * >::iterator it = schemeList.begin();
713     for ( ; it != schemeList.end(); ++it )
714     {
715       QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, QStringLiteral( "labeling" ), this );
716       colorAction->setBaseColor( mFormat.color() );
717       mMenu->addAction( colorAction );
718       connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsFontButton::setColor );
719       connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsFontButton::addRecentColor );
720     }
721 
722     mMenu->addSeparator();
723 
724     QAction *copyColorAction = new QAction( tr( "Copy Color" ), this );
725     mMenu->addAction( copyColorAction );
726     connect( copyColorAction, &QAction::triggered, this, &QgsFontButton::copyColor );
727 
728     QAction *pasteColorAction = new QAction( tr( "Paste Color" ), this );
729     //enable or disable paste action based on current clipboard contents. We always show the paste
730     //action, even if it's disabled, to give hint to the user that pasting colors is possible
731     QColor clipColor;
732     bool hasAlpha = false;
733     if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
734     {
735       pasteColorAction->setIcon( createColorIcon( clipColor ) );
736     }
737     else
738     {
739       pasteColorAction->setEnabled( false );
740     }
741     mMenu->addAction( pasteColorAction );
742     connect( pasteColorAction, &QAction::triggered, this, &QgsFontButton::pasteColor );
743   }
744 }
745 
addRecentColor(const QColor & color)746 void QgsFontButton::addRecentColor( const QColor &color )
747 {
748   QgsRecentColorScheme::addRecentColor( color );
749 }
750 
currentFont() const751 QFont QgsFontButton::currentFont() const
752 {
753   return mFont;
754 }
755 
layer() const756 QgsVectorLayer *QgsFontButton::layer() const
757 {
758   return mLayer;
759 }
760 
setLayer(QgsVectorLayer * layer)761 void QgsFontButton::setLayer( QgsVectorLayer *layer )
762 {
763   mLayer = layer;
764 }
765 
registerExpressionContextGenerator(QgsExpressionContextGenerator * generator)766 void QgsFontButton::registerExpressionContextGenerator( QgsExpressionContextGenerator *generator )
767 {
768   mExpressionContextGenerator = generator;
769 }
770 
setCurrentFont(const QFont & font)771 void QgsFontButton::setCurrentFont( const QFont &font )
772 {
773   mFont = font;
774   updatePreview();
775   emit changed();
776 }
777 
mode() const778 QgsFontButton::Mode QgsFontButton::mode() const
779 {
780   return mMode;
781 }
782 
setMode(Mode mode)783 void QgsFontButton::setMode( Mode mode )
784 {
785   mMode = mode;
786   updatePreview();
787 }
788 
formatFromMimeData(const QMimeData * mimeData,QgsTextFormat & resultFormat) const789 bool QgsFontButton::formatFromMimeData( const QMimeData *mimeData, QgsTextFormat &resultFormat ) const
790 {
791   bool ok = false;
792   resultFormat = QgsTextFormat::fromMimeData( mimeData, &ok );
793   return ok;
794 }
795 
fontFromMimeData(const QMimeData * mimeData,QFont & resultFont) const796 bool QgsFontButton::fontFromMimeData( const QMimeData *mimeData, QFont &resultFont ) const
797 {
798   bool ok = false;
799   resultFont = QgsFontUtils::fromMimeData( mimeData, &ok );
800   return ok;
801 }
802 
changeEvent(QEvent * e)803 void QgsFontButton::changeEvent( QEvent *e )
804 {
805   if ( e->type() == QEvent::EnabledChange )
806   {
807     updatePreview();
808   }
809   QToolButton::changeEvent( e );
810 }
811 
showEvent(QShowEvent * e)812 void QgsFontButton::showEvent( QShowEvent *e )
813 {
814   updatePreview();
815   QToolButton::showEvent( e );
816 }
817 
resizeEvent(QResizeEvent * event)818 void QgsFontButton::resizeEvent( QResizeEvent *event )
819 {
820   QToolButton::resizeEvent( event );
821   //recalculate icon size and redraw icon
822   mIconSize = QSize();
823   updatePreview();
824 }
825 
updatePreview(const QColor & color,QgsTextFormat * format,QFont * font)826 void QgsFontButton::updatePreview( const QColor &color, QgsTextFormat *format, QFont *font )
827 {
828   if ( mShowNoFormat && !mFormat.isValid() )
829   {
830     setIcon( QPixmap() );
831     return;
832   }
833 
834   QgsTextFormat tempFormat;
835   QFont tempFont;
836 
837   if ( format )
838     tempFormat = *format;
839   else
840     tempFormat = mFormat;
841   if ( font )
842     tempFont = *font;
843   else
844     tempFont = mFont;
845 
846   if ( color.isValid() )
847     tempFormat.setColor( color );
848 
849   QSize currentIconSize;
850   //icon size is button size with a small margin
851   if ( menu() )
852   {
853     if ( !mIconSize.isValid() )
854     {
855       //calculate size of push button part of widget (ie, without the menu dropdown button part)
856       QStyleOptionToolButton opt;
857       initStyleOption( &opt );
858       QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
859                          this );
860       //make sure height of icon looks good under different platforms
861 #ifdef Q_OS_WIN
862       mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
863 #elif defined(Q_OS_MAC)
864       mIconSize = QSize( buttonSize.width() - 10, height() - 2 );
865 #else
866       mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
867 #endif
868     }
869     currentIconSize = mIconSize;
870   }
871   else
872   {
873     //no menu
874 #ifdef Q_OS_WIN
875     currentIconSize = QSize( width() - 10, height() - 6 );
876 #else
877     currentIconSize = QSize( width() - 10, height() - 12 );
878 #endif
879   }
880 
881   if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
882   {
883     return;
884   }
885 
886   //create an icon pixmap
887   QPixmap pixmap( currentIconSize );
888   pixmap.fill( Qt::transparent );
889   QPainter p;
890   p.begin( &pixmap );
891   p.setRenderHint( QPainter::Antialiasing );
892   QRect rect( 0, 0, currentIconSize.width(), currentIconSize.height() );
893 
894   switch ( mMode )
895   {
896     case ModeTextRenderer:
897     {
898       QgsRenderContext context;
899       QgsMapToPixel newCoordXForm;
900       newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
901       context.setMapToPixel( newCoordXForm );
902 
903       context.setScaleFactor( QgsApplication::desktop()->logicalDpiX() / 25.4 );
904       context.setUseAdvancedEffects( true );
905       context.setFlag( QgsRenderContext::Antialiasing, true );
906       context.setPainter( &p );
907 
908       // slightly inset text to account for buffer/background
909       double xtrans = 0;
910       if ( tempFormat.buffer().enabled() )
911         xtrans = context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
912       if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
913         xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
914 
915       double ytrans = 0.0;
916       if ( tempFormat.buffer().enabled() )
917         ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
918       if ( tempFormat.background().enabled() )
919         ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
920 
921       QRectF textRect = rect;
922       textRect.setLeft( xtrans );
923       textRect.setWidth( textRect.width() - xtrans );
924       textRect.setTop( ytrans );
925       if ( textRect.height() > 300 )
926         textRect.setHeight( 300 );
927       if ( textRect.width() > 2000 )
928         textRect.setWidth( 2000 );
929 
930       QgsTextRenderer::drawText( textRect, 0, QgsTextRenderer::AlignLeft, QStringList() << text(),
931                                  context, tempFormat );
932       break;
933     }
934     case ModeQFont:
935     {
936       p.setBrush( Qt::NoBrush );
937       p.setPen( QColor( 0, 0, 0 ) );
938       p.setFont( tempFont );
939       QRectF textRect = rect;
940       textRect.setLeft( 2 );
941       p.drawText( textRect, Qt::AlignVCenter, text() );
942       break;
943     }
944 
945   }
946   p.end();
947   setIconSize( currentIconSize );
948   setIcon( pixmap );
949 }
950 
copyColor()951 void QgsFontButton::copyColor()
952 {
953   //copy color
954   QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mFormat.color() ) );
955 }
956 
pasteColor()957 void QgsFontButton::pasteColor()
958 {
959   QColor clipColor;
960   bool hasAlpha = false;
961   if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
962   {
963     //paste color
964     setColor( clipColor );
965     QgsRecentColorScheme::addRecentColor( clipColor );
966   }
967 }
968 
setDialogTitle(const QString & title)969 void QgsFontButton::setDialogTitle( const QString &title )
970 {
971   mDialogTitle = title;
972 }
973 
dialogTitle() const974 QString QgsFontButton::dialogTitle() const
975 {
976   return mDialogTitle;
977 }
978