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