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