QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsfontbutton.cpp
Go to the documentation of this file.
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"
23#include "qgscolorswatchgrid.h"
24#include "qgsdoublespinbox.h"
25#include "qgsunittypes.h"
26#include "qgsmenuheader.h"
27#include "qgsfontutils.h"
28#include "qgsapplication.h"
30#include "qgsvectorlayer.h"
31#include "qgstextrenderer.h"
32#include "qgsscreenhelper.h"
33#include <QMenu>
34#include <QClipboard>
35#include <QDrag>
36#include <QToolTip>
37
38QgsFontButton::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 const QSize size = QToolButton::minimumSizeHint();
56 const int fontHeight = Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.4;
57 const int minWidth = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 20;
58 mSizeHint = QSize( std::max( minWidth, size.width() ), std::max( size.height(), fontHeight ) );
59
60 mScreenHelper = new QgsScreenHelper( this );
61 connect( mScreenHelper, &QgsScreenHelper::screenDpiChanged, this, [ = ] { updatePreview(); } );
62}
63
65{
66 return mSizeHint;
67}
68
70{
71 return mSizeHint;
72}
73
74void QgsFontButton::showSettingsDialog()
75{
76 switch ( mMode )
77 {
79 {
81 if ( mExpressionContextGenerator )
82 context = mExpressionContextGenerator->createExpressionContext();
83 else
84 {
86 }
87
88 QgsSymbolWidgetContext symbolContext;
89 symbolContext.setExpressionContext( &context );
90 symbolContext.setMapCanvas( mMapCanvas );
91 symbolContext.setMessageBar( mMessageBar );
92
94 if ( panel && panel->dockMode() )
95 {
96 mActivePanel = new QgsTextFormatPanelWidget( mFormat, mMapCanvas, this, mLayer.data() );
97 mActivePanel->setPanelTitle( mDialogTitle );
98 mActivePanel->setContext( symbolContext );
99
100 connect( mActivePanel, &QgsTextFormatPanelWidget::widgetChanged, this, [ this ] { setTextFormat( mActivePanel->format() ); } );
101 panel->openPanel( mActivePanel );
102 return;
103 }
104
105 QgsTextFormatDialog dialog( mFormat, mMapCanvas, this, QgsGuiUtils::ModalDialogFlags, mLayer.data() );
106 dialog.setWindowTitle( mDialogTitle );
107 dialog.setContext( symbolContext );
108 if ( dialog.exec() )
109 {
110 setTextFormat( dialog.format() );
111 QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
112 }
113 break;
114 }
115
116 case ModeQFont:
117 {
118 bool ok;
119 const QFont newFont = QgsGuiUtils::getFont( ok, mFont, mDialogTitle );
120 if ( ok )
121 {
122 QgsFontUtils::addRecentFontFamily( newFont.family() );
123 setCurrentFont( newFont );
124 }
125 break;
126 }
127 }
128
129 // reactivate button's window
130 activateWindow();
131 raise();
132}
133
135{
136 return mMapCanvas;
137}
138
140{
141 mMapCanvas = mapCanvas;
142}
143
145{
146 mMessageBar = bar;
147}
148
150{
151 return mMessageBar;
152}
153
155{
156 if ( mActivePanel && !format.isValid() )
157 mActivePanel->acceptPanel();
158
159 mFormat = format;
160 updatePreview();
161
162 if ( mActivePanel && format.isValid() )
163 mActivePanel->setFormat( format );
164 emit changed();
165}
166
168{
169 mFormat = QgsTextFormat();
170 updatePreview();
171 emit changed();
172}
173
174void QgsFontButton::setColor( const QColor &color )
175{
176 QColor opaque = color;
177 opaque.setAlphaF( 1.0 );
178
179 if ( mNullFormatAction )
180 mNullFormatAction->setChecked( false );
181
182 if ( mFormat.color() != opaque )
183 {
184 mFormat.setColor( opaque );
185 updatePreview();
186 emit changed();
187 }
188}
189
191{
192 switch ( mMode )
193 {
194 case ModeTextRenderer:
195 QApplication::clipboard()->setMimeData( mFormat.toMimeData() );
196 break;
197
198 case ModeQFont:
199 QApplication::clipboard()->setMimeData( QgsFontUtils::toMimeData( mFont ) );
200 break;
201 }
202}
203
205{
206 QgsTextFormat tempFormat;
207 QFont font;
208 if ( mMode == ModeTextRenderer && formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
209 {
210 setTextFormat( tempFormat );
211 QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
212 }
213 else if ( mMode == ModeQFont && fontFromMimeData( QApplication::clipboard()->mimeData(), font ) )
214 {
215 QgsFontUtils::addRecentFontFamily( font.family() );
216 setCurrentFont( font );
217 }
218}
219
220bool QgsFontButton::event( QEvent *e )
221{
222 if ( e->type() == QEvent::ToolTip )
223 {
224 QHelpEvent *helpEvent = static_cast< QHelpEvent *>( e );
225 QString toolTip;
226 double fontSize = 0.0;
227 switch ( mMode )
228 {
229 case ModeTextRenderer:
230 fontSize = mFormat.size();
231 break;
232
233 case ModeQFont:
234 fontSize = mFont.pointSizeF();
235 break;
236 }
237 toolTip = QStringLiteral( "<b>%1</b><br>%2<br>Size: %3" ).arg( text(), mMode == ModeTextRenderer ? mFormat.font().family() : mFont.family() ).arg( fontSize );
238 QToolTip::showText( helpEvent->globalPos(), toolTip );
239 }
240 return QToolButton::event( e );
241}
242
243void QgsFontButton::mousePressEvent( QMouseEvent *e )
244{
245 if ( e->button() == Qt::RightButton )
246 {
247 QToolButton::showMenu();
248 return;
249 }
250 else if ( e->button() == Qt::LeftButton )
251 {
252 mDragStartPosition = e->pos();
253 }
254 QToolButton::mousePressEvent( e );
255}
256
257void QgsFontButton::mouseMoveEvent( QMouseEvent *e )
258{
259 //handle dragging fonts from button
260
261 if ( !( e->buttons() & Qt::LeftButton ) )
262 {
263 //left button not depressed, so not a drag
264 QToolButton::mouseMoveEvent( e );
265 return;
266 }
267
268 if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
269 {
270 //mouse not moved, so not a drag
271 QToolButton::mouseMoveEvent( e );
272 return;
273 }
274
275 //user is dragging font
276 QDrag *drag = new QDrag( this );
277 switch ( mMode )
278 {
279 case ModeTextRenderer:
280 drag->setMimeData( mFormat.toMimeData() );
281 break;
282
283 case ModeQFont:
284 drag->setMimeData( QgsFontUtils::toMimeData( mFont ) );
285 break;
286 }
287 const int iconSize = QgsGuiUtils::scaleIconSize( 50 );
288 drag->setPixmap( createDragIcon( QSize( iconSize, iconSize ) ) );
289 drag->exec( Qt::CopyAction );
290 setDown( false );
291}
292
293bool QgsFontButton::colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha )
294{
295 hasAlpha = false;
296 const QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha );
297
298 if ( mimeColor.isValid() )
299 {
300 resultColor = mimeColor;
301 return true;
302 }
303
304 //could not get color from mime data
305 return false;
306}
307
308void QgsFontButton::dragEnterEvent( QDragEnterEvent *e )
309{
310 //is dragged data valid font data?
311 QColor mimeColor;
312 QgsTextFormat format;
313 QFont font;
314 bool hasAlpha = false;
315
316 if ( mMode == ModeTextRenderer && formatFromMimeData( e->mimeData(), format ) )
317 {
318 e->acceptProposedAction();
319 updatePreview( QColor(), &format );
320 }
321 else if ( mMode == ModeQFont && fontFromMimeData( e->mimeData(), font ) )
322 {
323 e->acceptProposedAction();
324 updatePreview( QColor(), nullptr, &font );
325 }
326 else if ( mMode == ModeTextRenderer && colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
327 {
328 //if so, we accept the drag, and temporarily change the button's color
329 //to match the dragged color. This gives immediate feedback to the user
330 //that colors can be dropped here
331 e->acceptProposedAction();
332 updatePreview( mimeColor );
333 }
334}
335
336void QgsFontButton::dragLeaveEvent( QDragLeaveEvent *e )
337{
338 Q_UNUSED( e )
339 //reset button color
340 updatePreview();
341}
342
343void QgsFontButton::dropEvent( QDropEvent *e )
344{
345 //is dropped data valid format data?
346 QColor mimeColor;
347 QgsTextFormat format;
348 QFont font;
349 bool hasAlpha = false;
350 if ( mMode == ModeTextRenderer && formatFromMimeData( e->mimeData(), format ) )
351 {
352 setTextFormat( format );
353 QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
354 return;
355 }
356 else if ( mMode == ModeQFont && fontFromMimeData( e->mimeData(), font ) )
357 {
358 QgsFontUtils::addRecentFontFamily( font.family() );
359 setCurrentFont( font );
360 return;
361 }
362 else if ( mMode == ModeTextRenderer && colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
363 {
364 //accept drop and set new color
365 e->acceptProposedAction();
366
367 if ( hasAlpha )
368 {
369 mFormat.setOpacity( mimeColor.alphaF() );
370 }
371 mimeColor.setAlphaF( 1.0 );
372 mFormat.setColor( mimeColor );
374 updatePreview();
375 emit changed();
376 }
377 updatePreview();
378}
379
380void QgsFontButton::wheelEvent( QWheelEvent *event )
381{
382 double size = 0;
383 switch ( mMode )
384 {
385 case ModeTextRenderer:
386 size = mFormat.size();
387 break;
388
389 case ModeQFont:
390 size = mFont.pointSizeF();
391 break;
392 }
393
394 const double increment = ( event->modifiers() & Qt::ControlModifier ) ? 0.1 : 1;
395 if ( event->angleDelta().y() > 0 )
396 {
397 size += increment;
398 }
399 else
400 {
401 size -= increment;
402 }
403 size = std::max( size, 1.0 );
404
405 switch ( mMode )
406 {
407 case ModeTextRenderer:
408 {
409 QgsTextFormat newFormat = mFormat;
410 newFormat.setSize( size );
411 setTextFormat( newFormat );
412 break;
413 }
414
415 case ModeQFont:
416 {
417 QFont newFont = mFont;
418 newFont.setPointSizeF( size );
419 setCurrentFont( newFont );
420 break;
421 }
422 }
423
424 event->accept();
425}
426
427QPixmap QgsFontButton::createColorIcon( const QColor &color ) const
428{
429 //create an icon pixmap
430 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
431 QPixmap pixmap( iconSize, iconSize );
432 pixmap.fill( Qt::transparent );
433
434 QPainter p;
435 p.begin( &pixmap );
436
437 //draw color over pattern
438 p.setBrush( QBrush( color ) );
439
440 //draw border
441 p.setPen( QColor( 197, 197, 197 ) );
442 p.drawRect( 0, 0, iconSize - 1, iconSize - 1 );
443 p.end();
444 return pixmap;
445}
446
447QPixmap QgsFontButton::createDragIcon( QSize size, const QgsTextFormat *tempFormat, const QFont *tempFont ) const
448{
449 if ( !tempFormat )
450 tempFormat = &mFormat;
451 if ( !tempFont )
452 tempFont = &mFont;
453
454 //create an icon pixmap
455 QPixmap pixmap( size.width(), size.height() );
456 pixmap.fill( Qt::transparent );
457 QPainter p;
458 p.begin( &pixmap );
459 p.setRenderHint( QPainter::Antialiasing );
460 const QRect rect( 0, 0, size.width(), size.height() );
461
462 if ( mMode == ModeQFont || tempFormat->color().lightnessF() < 0.7 )
463 {
464 p.setBrush( QBrush( QColor( 255, 255, 255 ) ) );
465 p.setPen( QPen( QColor( 150, 150, 150 ), 0 ) );
466 }
467 else
468 {
469 p.setBrush( QBrush( QColor( 0, 0, 0 ) ) );
470 p.setPen( QPen( QColor( 100, 100, 100 ), 0 ) );
471 }
472 p.drawRect( rect );
473 p.setBrush( Qt::NoBrush );
474 p.setPen( Qt::NoPen );
475
476 switch ( mMode )
477 {
478 case ModeTextRenderer:
479 {
480 QgsRenderContext context;
481 QgsMapToPixel newCoordXForm;
482 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
483 context.setMapToPixel( newCoordXForm );
484
485 context.setScaleFactor( mScreenHelper->screenDpi() / 25.4 );
486 context.setUseAdvancedEffects( true );
487 context.setPainter( &p );
488
489 // slightly inset text to account for buffer/background
490 const double fontSize = context.convertToPainterUnits( tempFormat->size(), tempFormat->sizeUnit(), tempFormat->sizeMapUnitScale() );
491 double xtrans = 0;
492 if ( tempFormat->buffer().enabled() )
493 xtrans = tempFormat->buffer().sizeUnit() == Qgis::RenderUnit::Percentage
494 ? fontSize * tempFormat->buffer().size() / 100
495 : context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() );
496 if ( tempFormat->background().enabled() && tempFormat->background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
497 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat->background().size().width(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
498
499 double ytrans = 0.0;
500 if ( tempFormat->buffer().enabled() )
501 ytrans = std::max( ytrans, tempFormat->buffer().sizeUnit() == Qgis::RenderUnit::Percentage
502 ? fontSize * tempFormat->buffer().size() / 100
503 : context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() ) );
504 if ( tempFormat->background().enabled() )
505 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat->background().size().height(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
506
507 QRectF textRect = rect;
508 textRect.setLeft( xtrans );
509 textRect.setWidth( textRect.width() - xtrans );
510 textRect.setTop( ytrans );
511 if ( textRect.height() > 300 )
512 textRect.setHeight( 300 );
513 if ( textRect.width() > 2000 )
514 textRect.setWidth( 2000 );
515
516 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << tr( "Aa" ),
517 context, *tempFormat );
518 break;
519 }
520 case ModeQFont:
521 {
522 p.setBrush( Qt::NoBrush );
523 p.setPen( QColor( 0, 0, 0 ) );
524 p.setFont( *tempFont );
525 QRectF textRect = rect;
526 textRect.setLeft( 2 );
527 p.drawText( textRect, Qt::AlignVCenter, tr( "Aa" ) );
528 break;
529 }
530 }
531
532 p.end();
533 return pixmap;
534}
535
536void QgsFontButton::prepareMenu()
537{
538 //we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
539 //QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
540 //for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
541 //menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
542 mMenu->clear();
543
544 if ( mMode == ModeTextRenderer && mShowNoFormat )
545 {
546 mNullFormatAction = new QAction( mNullFormatString, this );
547 mMenu->addAction( mNullFormatAction );
548 connect( mNullFormatAction, &QAction::triggered, this, &QgsFontButton::setToNullFormat );
549 if ( !mFormat.isValid() )
550 {
551 mNullFormatAction->setCheckable( true );
552 mNullFormatAction->setChecked( true );
553 }
554 }
555
556 QWidgetAction *sizeAction = new QWidgetAction( mMenu );
557 QWidget *sizeWidget = new QWidget();
558 QVBoxLayout *sizeLayout = new QVBoxLayout();
559 sizeLayout->setContentsMargins( 0, 0, 0, 3 );
560 sizeLayout->setSpacing( 2 );
561
562 QString fontHeaderLabel;
563 switch ( mMode )
564 {
565 case ModeTextRenderer:
566 fontHeaderLabel = tr( "Font size (%1)" ).arg( QgsUnitTypes::toString( mFormat.sizeUnit() ) );
567 break;
568
569 case ModeQFont:
570 fontHeaderLabel = tr( "Font size (pt)" );
571 break;
572 }
573
574 QgsMenuHeader *sizeLabel = new QgsMenuHeader( fontHeaderLabel );
575 sizeLayout->addWidget( sizeLabel );
576
577 QgsDoubleSpinBox *sizeSpin = new QgsDoubleSpinBox( nullptr );
578 sizeSpin->setDecimals( 4 );
579 sizeSpin->setMaximum( 1e+9 );
580 sizeSpin->setShowClearButton( false );
581 sizeSpin->setValue( mMode == ModeTextRenderer ? mFormat.size() : mFont.pointSizeF() );
582 connect( sizeSpin, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ),
583 this, [ = ]( double value )
584 {
585 switch ( mMode )
586 {
587 case ModeTextRenderer:
588 if ( mNullFormatAction )
589 mNullFormatAction->setChecked( false );
590 mFormat.setSize( value );
591 break;
592 case ModeQFont:
593 mFont.setPointSizeF( value );
594 break;
595 }
596 updatePreview();
597 emit changed();
598 } );
599 QHBoxLayout *spinLayout = new QHBoxLayout();
600 spinLayout->setContentsMargins( 4, 0, 4, 0 );
601 spinLayout->addWidget( sizeSpin );
602 sizeLayout->addLayout( spinLayout );
603 sizeWidget->setLayout( sizeLayout );
604 sizeAction->setDefaultWidget( sizeWidget );
605 sizeWidget->setFocusProxy( sizeSpin );
606 sizeWidget->setFocusPolicy( Qt::StrongFocus );
607 mMenu->addAction( sizeAction );
608
609 QMenu *recentFontMenu = new QMenu( tr( "Recent Fonts" ), mMenu );
610 const auto recentFontFamilies { QgsFontUtils::recentFontFamilies() };
611 for ( const QString &family : recentFontFamilies )
612 {
613 QAction *fontAction = new QAction( family, recentFontMenu );
614 QFont f = fontAction->font();
615 QgsFontUtils::setFontFamily( f, family );
616 fontAction->setFont( f );
617 fontAction->setToolTip( family );
618 recentFontMenu->addAction( fontAction );
619 if ( ( mMode == ModeTextRenderer && family == mFormat.font().family() )
620 || ( mMode == ModeQFont && family == mFont.family() ) )
621 {
622 fontAction->setCheckable( true );
623 fontAction->setChecked( true );
624 }
625 auto setFont = [this, family]
626 {
627 switch ( mMode )
628 {
629 case ModeTextRenderer:
630 {
631 QgsTextFormat newFormat = mFormat;
632 QFont f = newFormat.font();
633 QgsFontUtils::setFontFamily( f, family );
634 newFormat.setFont( f );
635 setTextFormat( newFormat );
636 QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
637 break;
638 }
639 case ModeQFont:
640 {
641 QFont font = mFont;
642 QgsFontUtils::setFontFamily( font, family );
643 setCurrentFont( font );
645 break;
646 }
647 }
648 };
649 connect( fontAction, &QAction::triggered, this, setFont );
650 }
651 mMenu->addMenu( recentFontMenu );
652
653 QAction *configureAction = new QAction( tr( "Configure Format…" ), this );
654 mMenu->addAction( configureAction );
655 connect( configureAction, &QAction::triggered, this, &QgsFontButton::showSettingsDialog );
656
657 QAction *copyFormatAction = new QAction( tr( "Copy Format" ), this );
658 mMenu->addAction( copyFormatAction );
659 connect( copyFormatAction, &QAction::triggered, this, &QgsFontButton::copyFormat );
660 QAction *pasteFormatAction = new QAction( tr( "Paste Format" ), this );
661 //enable or disable paste action based on current clipboard contents. We always show the paste
662 //action, even if it's disabled, to give hint to the user that pasting colors is possible
663 QgsTextFormat tempFormat;
664 QFont tempFont;
665 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
666 if ( mMode == ModeTextRenderer && formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
667 {
669 tempFormat.setSize( 14 );
670 pasteFormatAction->setIcon( createDragIcon( QSize( iconSize, iconSize ), &tempFormat ) );
671 }
672 else if ( mMode == ModeQFont && fontFromMimeData( QApplication::clipboard()->mimeData(), tempFont ) )
673 {
674 tempFont.setPointSize( 8 );
675 pasteFormatAction->setIcon( createDragIcon( QSize( iconSize, iconSize ), nullptr, &tempFont ) );
676 }
677 else
678 {
679 pasteFormatAction->setEnabled( false );
680 }
681 mMenu->addAction( pasteFormatAction );
682 connect( pasteFormatAction, &QAction::triggered, this, &QgsFontButton::pasteFormat );
683
684 if ( mMode == ModeTextRenderer )
685 {
686 mMenu->addSeparator();
687
688 QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );
689 colorWheel->setColor( mFormat.color() );
690 QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
691 colorAction->setDismissOnColorSelection( false );
692 connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsFontButton::setColor );
693 mMenu->addAction( colorAction );
694
696 QColor alphaColor = mFormat.color();
697 alphaColor.setAlphaF( mFormat.opacity() );
698 alphaRamp->setColor( alphaColor );
699 QgsColorWidgetAction *alphaAction = new QgsColorWidgetAction( alphaRamp, mMenu, mMenu );
700 alphaAction->setDismissOnColorSelection( false );
701 connect( alphaAction, &QgsColorWidgetAction::colorChanged, this, [ = ]( const QColor & color )
702 {
703 const double opacity = color.alphaF();
704 mFormat.setOpacity( opacity );
705 updatePreview();
706 if ( mNullFormatAction )
707 mNullFormatAction->setChecked( false );
708 emit changed();
709 } );
710 connect( colorAction, &QgsColorWidgetAction::colorChanged, alphaRamp, [alphaRamp]( const QColor & color ) { alphaRamp->setColor( color, false ); }
711 );
712 mMenu->addAction( alphaAction );
713
714 //get schemes with ShowInColorButtonMenu flag set
715 QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu );
716 QList< QgsColorScheme * >::iterator it = schemeList.begin();
717 for ( ; it != schemeList.end(); ++it )
718 {
719 QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, QStringLiteral( "labeling" ), this );
720 colorAction->setBaseColor( mFormat.color() );
721 mMenu->addAction( colorAction );
723 connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsFontButton::addRecentColor );
724 }
725
726 mMenu->addSeparator();
727
728 QAction *copyColorAction = new QAction( tr( "Copy Color" ), this );
729 mMenu->addAction( copyColorAction );
730 connect( copyColorAction, &QAction::triggered, this, &QgsFontButton::copyColor );
731
732 QAction *pasteColorAction = new QAction( tr( "Paste Color" ), this );
733 //enable or disable paste action based on current clipboard contents. We always show the paste
734 //action, even if it's disabled, to give hint to the user that pasting colors is possible
735 QColor clipColor;
736 bool hasAlpha = false;
737 if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
738 {
739 pasteColorAction->setIcon( createColorIcon( clipColor ) );
740 }
741 else
742 {
743 pasteColorAction->setEnabled( false );
744 }
745 mMenu->addAction( pasteColorAction );
746 connect( pasteColorAction, &QAction::triggered, this, &QgsFontButton::pasteColor );
747 }
748}
749
750void QgsFontButton::addRecentColor( const QColor &color )
751{
753}
754
756{
757 return mFont;
758}
759
761{
762 return mLayer;
763}
764
766{
767 mLayer = layer;
768}
769
771{
772 mExpressionContextGenerator = generator;
773}
774
775void QgsFontButton::setCurrentFont( const QFont &font )
776{
777 mFont = font;
778 updatePreview();
779 emit changed();
780}
781
783{
784 return mMode;
785}
786
788{
789 mMode = mode;
790 updatePreview();
791}
792
793bool QgsFontButton::formatFromMimeData( const QMimeData *mimeData, QgsTextFormat &resultFormat ) const
794{
795 bool ok = false;
796 resultFormat = QgsTextFormat::fromMimeData( mimeData, &ok );
797 return ok;
798}
799
800bool QgsFontButton::fontFromMimeData( const QMimeData *mimeData, QFont &resultFont ) const
801{
802 bool ok = false;
803 resultFont = QgsFontUtils::fromMimeData( mimeData, &ok );
804 return ok;
805}
806
808{
809 if ( e->type() == QEvent::EnabledChange )
810 {
811 updatePreview();
812 }
813 QToolButton::changeEvent( e );
814}
815
816void QgsFontButton::showEvent( QShowEvent *e )
817{
818 updatePreview();
819 QToolButton::showEvent( e );
820}
821
822void QgsFontButton::resizeEvent( QResizeEvent *event )
823{
824 QToolButton::resizeEvent( event );
825 //recalculate icon size and redraw icon
826 mIconSize = QSize();
827 updatePreview();
828}
829
830void QgsFontButton::updatePreview( const QColor &color, QgsTextFormat *format, QFont *font )
831{
832 if ( mShowNoFormat && !mFormat.isValid() )
833 {
834 setIcon( QPixmap() );
835 return;
836 }
837
838 QgsTextFormat tempFormat;
839 QFont tempFont;
840
841 if ( format )
842 tempFormat = *format;
843 else
844 tempFormat = mFormat;
845 if ( font )
846 tempFont = *font;
847 else
848 tempFont = mFont;
849
850 if ( color.isValid() )
851 tempFormat.setColor( color );
852
853 QSize currentIconSize;
854 //icon size is button size with a small margin
855 if ( menu() )
856 {
857 if ( !mIconSize.isValid() )
858 {
859 //calculate size of push button part of widget (ie, without the menu dropdown button part)
860 QStyleOptionToolButton opt;
861 initStyleOption( &opt );
862 const QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
863 this );
864 //make sure height of icon looks good under different platforms
865#ifdef Q_OS_WIN
866 mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
867#elif defined(Q_OS_MAC)
868 mIconSize = QSize( buttonSize.width() - 10, height() - 2 );
869#else
870 mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
871#endif
872 }
873 currentIconSize = mIconSize;
874 }
875 else
876 {
877 //no menu
878#ifdef Q_OS_WIN
879 currentIconSize = QSize( width() - 10, height() - 6 );
880#else
881 currentIconSize = QSize( width() - 10, height() - 12 );
882#endif
883 }
884
885 if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
886 {
887 return;
888 }
889
890 //create an icon pixmap
891 QPixmap pixmap( currentIconSize * devicePixelRatioF() );
892 pixmap.fill( Qt::transparent );
893 pixmap.setDevicePixelRatio( devicePixelRatioF() );
894 QPainter p;
895 p.begin( &pixmap );
896 p.setRenderHint( QPainter::Antialiasing );
897 const QRect rect( 0, 0, currentIconSize.width(), currentIconSize.height() );
898
899 switch ( mMode )
900 {
901 case ModeTextRenderer:
902 {
903 QgsRenderContext context;
904 QgsMapToPixel newCoordXForm;
905 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
906 context.setMapToPixel( newCoordXForm );
907
908 context.setScaleFactor( mScreenHelper->screenDpi() / 25.4 );
909 context.setDevicePixelRatio( devicePixelRatioF() );
910 context.setUseAdvancedEffects( true );
912 context.setPainter( &p );
913
914 // slightly inset text to account for buffer/background
915 const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
916 double xtrans = 0;
917 if ( tempFormat.buffer().enabled() )
918 xtrans = tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
919 ? fontSize * tempFormat.buffer().size() / 100
920 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
921 if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
922 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
923
924 double ytrans = 0.0;
925 if ( tempFormat.buffer().enabled() )
926 ytrans = std::max( ytrans, tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
927 ? fontSize * tempFormat.buffer().size() / 100
928 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
929 if ( tempFormat.background().enabled() )
930 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
931
932 QRectF textRect = rect;
933 textRect.setLeft( xtrans );
934 textRect.setWidth( textRect.width() - xtrans );
935 textRect.setTop( ytrans );
936 if ( textRect.height() > 300 )
937 textRect.setHeight( 300 );
938 if ( textRect.width() > 2000 )
939 textRect.setWidth( 2000 );
940
941 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Left, QStringList() << text(),
942 context, tempFormat );
943 break;
944 }
945 case ModeQFont:
946 {
947 p.setBrush( Qt::NoBrush );
948 p.setPen( QColor( 0, 0, 0 ) );
949 p.setFont( tempFont );
950 QRectF textRect = rect;
951 textRect.setLeft( 2 );
952 p.drawText( textRect, Qt::AlignVCenter, text() );
953 break;
954 }
955
956 }
957 p.end();
958 setIconSize( currentIconSize );
959 setIcon( pixmap );
960}
961
963{
964 //copy color
965 QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mFormat.color() ) );
966}
967
969{
970 QColor clipColor;
971 bool hasAlpha = false;
972 if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
973 {
974 //paste color
975 setColor( clipColor );
977 }
978}
979
980void QgsFontButton::setDialogTitle( const QString &title )
981{
982 mDialogTitle = title;
983}
984
986{
987 return mDialogTitle;
988}
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Antialiasing
Use antialiasing while drawing.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:4927
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
A color ramp widget.
@ Horizontal
Horizontal ramp.
QList< QgsColorScheme * > schemes() const
Returns all color schemes in the registry.
@ ShowInColorButtonMenu
Show scheme in color button drop-down menu.
A color swatch grid which can be embedded into a menu.
void setBaseColor(const QColor &baseColor)
Sets the base color for the color grid.
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
A color wheel widget.
void setColor(const QColor &color, bool emitSignals=false) override
An action containing a color widget, which can be embedded into a menu.
void setDismissOnColorSelection(bool dismiss)
Sets whether the parent menu should be dismissed and closed when a color is selected from the action'...
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
virtual void setColor(const QColor &color, bool emitSignals=false)
Sets the color for the widget.
@ Alpha
Alpha component (opacity) of color.
The QgsSpinBox is a spin box with a clear button that will set the value to the defined clear value.
void setShowClearButton(bool showClearButton)
Sets whether the widget will show a clear button.
Abstract interface for generating an expression context.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
void copyColor()
Copies the current text color to the clipboard.
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
void pasteColor()
Pastes a color from the clipboard to the text format.
QSize sizeHint() const override
void setToNullFormat()
Sets the text format to a null (invalid) QgsTextFormat.
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas to associate with the widget.
void setMessageBar(QgsMessageBar *bar)
Sets the message bar associated with the widget.
QgsMessageBar * messageBar() const
Returns the message bar associated with the widget.
Mode
Available button modes.
Definition: qgsfontbutton.h:60
@ ModeQFont
Configure font settings for use with QFont objects.
Definition: qgsfontbutton.h:62
@ ModeTextRenderer
Configure font settings for use with QgsTextRenderer.
Definition: qgsfontbutton.h:61
QString dialogTitle
Definition: qgsfontbutton.h:52
void setCurrentFont(const QFont &font)
Sets the current text font to show in the widget.
void mousePressEvent(QMouseEvent *e) override
QgsVectorLayer * layer() const
Returns the layer associated with the widget.
void setLayer(QgsVectorLayer *layer)
Sets a layer to associate with the widget.
QSize minimumSizeHint() const override
void mouseMoveEvent(QMouseEvent *e) override
QgsFontButton(QWidget *parent=nullptr, const QString &dialogTitle=QString())
Construct a new font button.
void changeEvent(QEvent *e) override
void changed()
Emitted when the widget's text format settings are changed.
void resizeEvent(QResizeEvent *event) override
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
void dragEnterEvent(QDragEnterEvent *e) override
void dragLeaveEvent(QDragLeaveEvent *e) override
void setColor(const QColor &color)
Sets the current color for the text.
void pasteFormat()
Pastes a format from the clipboard.
void showEvent(QShowEvent *e) override
void setDialogTitle(const QString &title)
Sets the title for the text settings dialog window.
void wheelEvent(QWheelEvent *event) override
bool event(QEvent *e) override
void dropEvent(QDropEvent *e) override
void setTextFormat(const QgsTextFormat &format)
Sets the current text format to show in the widget.
void copyFormat()
Copies the current text format to the clipboard.
void setMode(Mode mode)
Sets the current button mode.
static QMimeData * toMimeData(const QFont &font)
Returns new mime data representing the specified font settings.
static void setFontFamily(QFont &font, const QString &family)
Sets the family for a font object.
static void addRecentFontFamily(const QString &family)
Adds a font family to the list of recently used font families.
static QFont fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QFont.
static QStringList recentFontFamilies()
Returns a list of recently used font families.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:93
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Sets parameters for use in transforming coordinates.
Custom widget for displaying subheaders within a QMenu in a standard style.
Definition: qgsmenuheader.h:32
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
bool dockMode()
Returns the dock mode state.
static void addRecentColor(const QColor &color)
Adds a color to the list of recent colors.
Contains information about the context of a rendering operation.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
void setUseAdvancedEffects(bool enabled)
Used to enable or disable advanced effects such as blend modes.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
A utility class for dynamic handling of changes to screen properties.
double screenDpi() const
Returns the current screen DPI for the screen that the parent widget appears on.
void screenDpiChanged(double dpi)
Emitted whenever the screen dpi associated with the widget is changed.
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
static QMimeData * colorToMimeData(const QColor &color)
Creates mime data from a color.
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
void setMessageBar(QgsMessageBar *bar)
Sets the message bar associated with the widget.
void setExpressionContext(QgsExpressionContext *context)
Sets the optional expression context used for the widget.
QSizeF size() const
Returns the size of the background shape.
bool enabled() const
Returns whether the background is enabled.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
Qgis::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
Qgis::RenderUnit sizeUnit() const
Returns the units for the buffer size.
double size() const
Returns the size of the buffer.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
bool enabled() const
Returns whether the buffer is enabled.
A simple dialog for customizing text formatting settings.
A panel widget for customizing text formatting settings.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
void setFont(const QFont &font)
Sets the font used for rendering text.
static QgsTextFormat fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsTextFormat.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
void setOpacity(double opacity)
Sets the text's opacity.
bool isValid() const
Returns true if the format is valid.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
Qgis::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
double opacity() const
Returns the text's opacity.
double size() const
Returns the size for rendered text.
QMimeData * toMimeData() const
Returns new mime data representing the text format settings.
QColor color() const
Returns the color that text will be rendered in.
QFont font() const
Returns the font used for rendering text.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
Represents a vector layer which manages a vector based data sets.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
QFont getFont(bool &ok, const QFont &initial, const QString &title)
Show font selection dialog.