QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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(
512 ytrans,
514 ? fontSize * tempFormat->buffer().size() / 100
515 : context.convertToPainterUnits( tempFormat->buffer().size(), tempFormat->buffer().sizeUnit(), tempFormat->buffer().sizeMapUnitScale() )
516 );
517 if ( tempFormat->background().enabled() )
518 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat->background().size().height(), tempFormat->background().sizeUnit(), tempFormat->background().sizeMapUnitScale() ) );
519
520 QRectF textRect = rect;
521 textRect.setLeft( xtrans );
522 textRect.setWidth( textRect.width() - xtrans );
523 textRect.setTop( ytrans );
524 if ( textRect.height() > 300 )
525 textRect.setHeight( 300 );
526 if ( textRect.width() > 2000 )
527 textRect.setWidth( 2000 );
528
529 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << tr( "Aa" ), context, *tempFormat );
530 break;
531 }
532 case ModeQFont:
533 {
534 p.setBrush( Qt::NoBrush );
535 p.setPen( QColor( 0, 0, 0 ) );
536 p.setFont( *tempFont );
537 QRectF textRect = rect;
538 textRect.setLeft( 2 );
539 p.drawText( textRect, Qt::AlignVCenter, tr( "Aa" ) );
540 break;
541 }
542 }
543
544 p.end();
545 return pixmap;
546}
547
548void QgsFontButton::prepareMenu()
549{
550 //we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
551 //QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
552 //for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
553 //menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
554 mMenu->clear();
555
556 if ( mMode == ModeTextRenderer && mShowNoFormat )
557 {
558 mNullFormatAction = new QAction( mNullFormatString, this );
559 mMenu->addAction( mNullFormatAction );
560 connect( mNullFormatAction, &QAction::triggered, this, &QgsFontButton::setToNullFormat );
561 if ( !mFormat.isValid() )
562 {
563 mNullFormatAction->setCheckable( true );
564 mNullFormatAction->setChecked( true );
565 }
566 }
567
568 QWidgetAction *sizeAction = new QWidgetAction( mMenu );
569 QWidget *sizeWidget = new QWidget();
570 QVBoxLayout *sizeLayout = new QVBoxLayout();
571 sizeLayout->setContentsMargins( 0, 0, 0, 3 );
572 sizeLayout->setSpacing( 2 );
573
574 QString fontHeaderLabel;
575 switch ( mMode )
576 {
577 case ModeTextRenderer:
578 fontHeaderLabel = tr( "Font size (%1)" ).arg( QgsUnitTypes::toString( mFormat.sizeUnit() ) );
579 break;
580
581 case ModeQFont:
582 fontHeaderLabel = tr( "Font size (pt)" );
583 break;
584 }
585
586 QgsMenuHeader *sizeLabel = new QgsMenuHeader( fontHeaderLabel );
587 sizeLayout->addWidget( sizeLabel );
588
589 QgsDoubleSpinBox *sizeSpin = new QgsDoubleSpinBox( nullptr );
590 sizeSpin->setDecimals( 4 );
591 sizeSpin->setMaximum( 1e+9 );
592 sizeSpin->setShowClearButton( false );
593 sizeSpin->setValue( mMode == ModeTextRenderer ? mFormat.size() : mFont.pointSizeF() );
594 connect( sizeSpin, static_cast<void ( QgsDoubleSpinBox::* )( double )>( &QgsDoubleSpinBox::valueChanged ), this, [this]( double value ) {
595 switch ( mMode )
596 {
597 case ModeTextRenderer:
598 if ( mNullFormatAction )
599 mNullFormatAction->setChecked( false );
600 mFormat.setSize( value );
601 break;
602 case ModeQFont:
603 mFont.setPointSizeF( value );
604 break;
605 }
606 updatePreview();
607 emit changed();
608 } );
609 QHBoxLayout *spinLayout = new QHBoxLayout();
610 spinLayout->setContentsMargins( 4, 0, 4, 0 );
611 spinLayout->addWidget( sizeSpin );
612 sizeLayout->addLayout( spinLayout );
613 sizeWidget->setLayout( sizeLayout );
614 sizeAction->setDefaultWidget( sizeWidget );
615 sizeWidget->setFocusProxy( sizeSpin );
616 sizeWidget->setFocusPolicy( Qt::StrongFocus );
617 mMenu->addAction( sizeAction );
618
619 QMenu *recentFontMenu = new QMenu( tr( "Recent Fonts" ), mMenu );
620 const auto recentFontFamilies { QgsFontUtils::recentFontFamilies() };
621 for ( const QString &family : recentFontFamilies )
622 {
623 QAction *fontAction = new QAction( family, recentFontMenu );
624 QFont f = fontAction->font();
625 QgsFontUtils::setFontFamily( f, family );
626 fontAction->setFont( f );
627 fontAction->setToolTip( family );
628 recentFontMenu->addAction( fontAction );
629 if ( ( mMode == ModeTextRenderer && family == mFormat.font().family() ) || ( mMode == ModeQFont && family == mFont.family() ) )
630 {
631 fontAction->setCheckable( true );
632 fontAction->setChecked( true );
633 }
634 auto setFont = [this, family] {
635 switch ( mMode )
636 {
637 case ModeTextRenderer:
638 {
639 QgsTextFormat newFormat = mFormat;
640 QFont f = newFormat.font();
641 QgsFontUtils::setFontFamily( f, family );
642 newFormat.setFont( f );
643 setTextFormat( newFormat );
644 QgsFontUtils::addRecentFontFamily( mFormat.font().family() );
645 break;
646 }
647 case ModeQFont:
648 {
649 QFont font = mFont;
650 QgsFontUtils::setFontFamily( font, family );
651 setCurrentFont( font );
653 break;
654 }
655 }
656 };
657 connect( fontAction, &QAction::triggered, this, setFont );
658 }
659 mMenu->addMenu( recentFontMenu );
660
661 QAction *configureAction = new QAction( tr( "Configure Format…" ), this );
662 mMenu->addAction( configureAction );
663 connect( configureAction, &QAction::triggered, this, &QgsFontButton::showSettingsDialog );
664
665 QAction *copyFormatAction = new QAction( tr( "Copy Format" ), this );
666 mMenu->addAction( copyFormatAction );
667 connect( copyFormatAction, &QAction::triggered, this, &QgsFontButton::copyFormat );
668 QAction *pasteFormatAction = new QAction( tr( "Paste Format" ), this );
669 //enable or disable paste action based on current clipboard contents. We always show the paste
670 //action, even if it's disabled, to give hint to the user that pasting colors is possible
671 QgsTextFormat tempFormat;
672 QFont tempFont;
673 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
674 if ( mMode == ModeTextRenderer && formatFromMimeData( QApplication::clipboard()->mimeData(), tempFormat ) )
675 {
677 tempFormat.setSize( 14 );
678 pasteFormatAction->setIcon( createDragIcon( QSize( iconSize, iconSize ), &tempFormat ) );
679 }
680 else if ( mMode == ModeQFont && fontFromMimeData( QApplication::clipboard()->mimeData(), tempFont ) )
681 {
682 tempFont.setPointSize( 8 );
683 pasteFormatAction->setIcon( createDragIcon( QSize( iconSize, iconSize ), nullptr, &tempFont ) );
684 }
685 else
686 {
687 pasteFormatAction->setEnabled( false );
688 }
689 mMenu->addAction( pasteFormatAction );
690 connect( pasteFormatAction, &QAction::triggered, this, &QgsFontButton::pasteFormat );
691
692 if ( mMode == ModeTextRenderer )
693 {
694 mMenu->addSeparator();
695
696 QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );
697 colorWheel->setColor( mFormat.color() );
698 QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
699 colorAction->setDismissOnColorSelection( false );
700 connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsFontButton::setColor );
701 mMenu->addAction( colorAction );
702
703 QgsColorRampWidget *alphaRamp = new QgsColorRampWidget( mMenu, QgsColorWidget::Alpha, QgsColorRampWidget::Horizontal );
704 QColor alphaColor = mFormat.color();
705 alphaColor.setAlphaF( mFormat.opacity() );
706 alphaRamp->setColor( alphaColor );
707 QgsColorWidgetAction *alphaAction = new QgsColorWidgetAction( alphaRamp, mMenu, mMenu );
708 alphaAction->setDismissOnColorSelection( false );
709 connect( alphaAction, &QgsColorWidgetAction::colorChanged, this, [this]( const QColor &color ) {
710 const double opacity = color.alphaF();
711 mFormat.setOpacity( opacity );
712 updatePreview();
713 if ( mNullFormatAction )
714 mNullFormatAction->setChecked( false );
715 emit changed();
716 } );
717 connect( colorAction, &QgsColorWidgetAction::colorChanged, alphaRamp, [alphaRamp]( const QColor &color ) { alphaRamp->setColor( color, false ); } );
718 mMenu->addAction( alphaAction );
719
720 //get schemes with ShowInColorButtonMenu flag set
722 QList<QgsColorScheme *>::iterator it = schemeList.begin();
723 for ( ; it != schemeList.end(); ++it )
724 {
725 QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, u"labeling"_s, this );
726 colorAction->setBaseColor( mFormat.color() );
727 mMenu->addAction( colorAction );
729 connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsFontButton::addRecentColor );
730 }
731
732 mMenu->addSeparator();
733
734 QAction *copyColorAction = new QAction( tr( "Copy Color" ), this );
735 mMenu->addAction( copyColorAction );
736 connect( copyColorAction, &QAction::triggered, this, &QgsFontButton::copyColor );
737
738 QAction *pasteColorAction = new QAction( tr( "Paste Color" ), this );
739 //enable or disable paste action based on current clipboard contents. We always show the paste
740 //action, even if it's disabled, to give hint to the user that pasting colors is possible
741 QColor clipColor;
742 bool hasAlpha = false;
743 if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
744 {
745 pasteColorAction->setIcon( createColorIcon( clipColor ) );
746 }
747 else
748 {
749 pasteColorAction->setEnabled( false );
750 }
751 mMenu->addAction( pasteColorAction );
752 connect( pasteColorAction, &QAction::triggered, this, &QgsFontButton::pasteColor );
753 }
754}
755
756void QgsFontButton::addRecentColor( const QColor &color )
757{
759}
760
762{
763 return mFont;
764}
765
767{
768 return mLayer;
769}
770
772{
773 mLayer = layer;
774}
775
777{
778 mExpressionContextGenerator = generator;
779}
780
781void QgsFontButton::setCurrentFont( const QFont &font )
782{
783 mFont = font;
784 updatePreview();
785 emit changed();
786}
787
789{
790 return mMode;
791}
792
794{
795 mMode = mode;
796 updatePreview();
797}
798
799bool QgsFontButton::formatFromMimeData( const QMimeData *mimeData, QgsTextFormat &resultFormat ) const
800{
801 if ( !mimeData )
802 return false;
803
804 bool ok = false;
805 resultFormat = QgsTextFormat::fromMimeData( mimeData, &ok );
806 return ok;
807}
808
809bool QgsFontButton::fontFromMimeData( const QMimeData *mimeData, QFont &resultFont ) const
810{
811 if ( !mimeData )
812 return false;
813
814 bool ok = false;
815 resultFont = QgsFontUtils::fromMimeData( mimeData, &ok );
816 return ok;
817}
818
820{
821 if ( e->type() == QEvent::EnabledChange )
822 {
823 updatePreview();
824 }
825 QToolButton::changeEvent( e );
826}
827
828void QgsFontButton::showEvent( QShowEvent *e )
829{
830 updatePreview();
831 QToolButton::showEvent( e );
832}
833
835{
836 QToolButton::resizeEvent( event );
837 //recalculate icon size and redraw icon
838 mIconSize = QSize();
839 updatePreview();
840}
841
842void QgsFontButton::updatePreview( const QColor &color, QgsTextFormat *format, QFont *font )
843{
844 if ( mShowNoFormat && !mFormat.isValid() )
845 {
846 setIcon( QPixmap() );
847 return;
848 }
849
850 QgsTextFormat tempFormat;
851 QFont tempFont;
852
853 if ( format )
854 tempFormat = *format;
855 else
856 tempFormat = mFormat;
857 if ( font )
858 tempFont = *font;
859 else
860 tempFont = mFont;
861
862 if ( color.isValid() )
863 tempFormat.setColor( color );
864
865 // always show font previews based on the standard font size for buttons. Otherwise large/small text
866 // will be unreadable, making the button very non-user-friendly.
867 // Note that we take away a few points here, as the text in these buttons is rendered with a fairly large
868 // margin and we'd like to avoid cropping the text.
869 tempFormat.setSize( QToolButton::font().pointSizeF() - 2 );
871
872 QSize currentIconSize;
873 //icon size is button size with a small margin
874 if ( menu() )
875 {
876 if ( !mIconSize.isValid() )
877 {
878 //calculate size of push button part of widget (ie, without the menu dropdown button part)
879 QStyleOptionToolButton opt;
880 initStyleOption( &opt );
881 const QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton, this );
882 //make sure height of icon looks good under different platforms
883#ifdef Q_OS_WIN
884 mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
885#elif defined( Q_OS_MAC )
886 mIconSize = QSize( buttonSize.width() - 10, height() - 2 );
887#else
888 mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
889#endif
890 }
891 currentIconSize = mIconSize;
892 }
893 else
894 {
895 //no menu
896#ifdef Q_OS_WIN
897 currentIconSize = QSize( width() - 10, height() - 6 );
898#else
899 currentIconSize = QSize( width() - 10, height() - 12 );
900#endif
901 }
902
903 if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
904 {
905 return;
906 }
907
908 //create an icon pixmap
909 QPixmap pixmap( currentIconSize * devicePixelRatioF() );
910 pixmap.fill( Qt::transparent );
911 pixmap.setDevicePixelRatio( devicePixelRatioF() );
912 QPainter p;
913 p.begin( &pixmap );
914 p.setRenderHint( QPainter::Antialiasing );
915 const QRect rect( 0, 0, currentIconSize.width(), currentIconSize.height() );
916
917 switch ( mMode )
918 {
919 case ModeTextRenderer:
920 {
921 QgsRenderContext context;
922 QgsMapToPixel newCoordXForm;
923 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
924 context.setMapToPixel( newCoordXForm );
925
926 context.setScaleFactor( mScreenHelper->screenDpi() / 25.4 );
927 context.setDevicePixelRatio( devicePixelRatioF() );
930 context.setPainter( &p );
931
932 // slightly inset text to account for buffer/background
933 const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
934 double xtrans = 0;
935 if ( tempFormat.buffer().enabled() )
936 xtrans = tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
937 ? fontSize * tempFormat.buffer().size() / 100
938 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
939 if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
940 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
941
942 double ytrans = 0.0;
943 if ( tempFormat.buffer().enabled() )
944 ytrans = std::max(
945 ytrans,
947 ? fontSize * tempFormat.buffer().size() / 100
948 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() )
949 );
950 if ( tempFormat.background().enabled() )
951 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
952
953 QRectF textRect = rect;
954 textRect.setLeft( xtrans );
955 textRect.setWidth( textRect.width() - xtrans );
956 textRect.setTop( ytrans );
957 if ( textRect.height() > 300 )
958 textRect.setHeight( 300 );
959 if ( textRect.width() > 2000 )
960 textRect.setWidth( 2000 );
961
962 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Left, QStringList() << text(), context, tempFormat );
963 break;
964 }
965 case ModeQFont:
966 {
967 p.setBrush( Qt::NoBrush );
968 p.setPen( QColor( 0, 0, 0 ) );
969 p.setFont( tempFont );
970 QRectF textRect = rect;
971 textRect.setLeft( 2 );
972 p.drawText( textRect, Qt::AlignVCenter, text() );
973 break;
974 }
975 }
976 p.end();
977 setIconSize( currentIconSize );
978 setIcon( pixmap );
979}
980
982{
983 //copy color
984 QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mFormat.color() ) );
985}
986
988{
989 QColor clipColor;
990 bool hasAlpha = false;
991 if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
992 {
993 //paste color
994 setColor( clipColor );
996 }
997}
998
999void QgsFontButton::setDialogTitle( const QString &title )
1000{
1001 mDialogTitle = title;
1002}
1003
1005{
1006 return mDialogTitle;
1007}
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2800
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5344
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5345
@ Pixels
Pixels.
Definition qgis.h:5343
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2853
@ Center
Center align.
Definition qgis.h:3045
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6591
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.