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