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