1 /***************************************************************************
2                              qgslayoutapputils.cpp
3                              ---------------------
4     Date                 : October 2017
5     Copyright            : (C) 2017 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 "qgslayoutguiutils.h"
17 #include "qgsgui.h"
18 #include "qgslayout.h"
19 #include "qgslayoutitemguiregistry.h"
20 #include "qgslayoutitemregistry.h"
21 #include "qgslayoutviewrubberband.h"
22 #include "qgslayoutitemshape.h"
23 #include "qgslayoutmapwidget.h"
24 #include "qgslayoutshapewidget.h"
25 #include "qgslayoutmarkerwidget.h"
26 #include "qgslayoutitemmap.h"
27 #include "qgslayoutitempolygon.h"
28 #include "qgslayoutitempolyline.h"
29 #include "qgslayoutitemmarker.h"
30 #include "qgslayoutpolygonwidget.h"
31 #include "qgslayoutpolylinewidget.h"
32 #include "qgslayoutpicturewidget.h"
33 #include "qgslayoutitempicture.h"
34 #include "qgslayoutitemlabel.h"
35 #include "qgslayoutlabelwidget.h"
36 #include "qgslayoutitemlegend.h"
37 #include "qgslayoutitemscalebar.h"
38 #include "qgslayoutlegendwidget.h"
39 #include "qgslayoutframe.h"
40 #include "qgslayoutitemhtml.h"
41 #include "qgslayouthtmlwidget.h"
42 #include "qgslayoutscalebarwidget.h"
43 #include "qgslayoutitemattributetable.h"
44 #include "qgslayoutattributetablewidget.h"
45 #include "qgslayoutitemmanualtable.h"
46 #include "qgslayoutmanualtablewidget.h"
47 #include "qgsmapcanvas.h"
48 
49 /**
50  * Attempts to find the best guess at a map item to link \a referenceItem to,
51  * by:
52  *
53  * # Prioritizing a selected map
54  * # If no selection, prioritizing the topmost map the item was drawn over
55  * # If still none, use the layout's reference map (or biggest map)
56  */
findSensibleDefaultLinkedMapItem(QgsLayoutItem * referenceItem)57 QgsLayoutItemMap *findSensibleDefaultLinkedMapItem( QgsLayoutItem *referenceItem )
58 {
59   // start by trying to find a selected map
60   QList<QgsLayoutItemMap *> mapItems;
61   referenceItem->layout()->layoutItems( mapItems );
62 
63   QgsLayoutItemMap *targetMap = nullptr;
64   for ( QgsLayoutItemMap *map : qgis::as_const( mapItems ) )
65   {
66     if ( map->isSelected() )
67     {
68       return map;
69     }
70   }
71 
72   // nope, no selection... hm, was the item drawn over a map? If so, use the topmost intersecting one
73   double largestZValue = std::numeric_limits< double >::lowest();
74   for ( QgsLayoutItemMap *map : qgis::as_const( mapItems ) )
75   {
76     if ( map->collidesWithItem( referenceItem ) && map->zValue() > largestZValue )
77     {
78       targetMap = map;
79       largestZValue = map->zValue();
80     }
81   }
82   if ( targetMap )
83     return targetMap;
84 
85   // ah frick it, just use the reference (or biggest!) map
86   return referenceItem->layout()->referenceMap();
87 }
88 
registerGuiForKnownItemTypes(QgsMapCanvas * mapCanvas)89 void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
90 {
91   QgsLayoutItemGuiRegistry *registry = QgsGui::layoutItemGuiRegistry();
92 
93   registry->addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "shapes" ), QObject::tr( "Shape" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) ) );
94   registry->addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "nodes" ), QObject::tr( "Node Item" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddNodesItem.svg" ) ) ) );
95 
96   auto createRubberBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand *
97   {
98     return new QgsLayoutViewRectangularRubberBand( view );
99   } );
100   auto createEllipseBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand *
101   {
102     return new QgsLayoutViewEllipticalRubberBand( view );
103   } );
104   auto createTriangleBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand *
105   {
106     return new QgsLayoutViewTriangleRubberBand( view );
107   } );
108 
109 #if 0
110   registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 1002, QStringLiteral( "test" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), nullptr, createRubberBand ) );
111 #endif
112 
113   // map item
114 
115   auto mapItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutMap, QObject::tr( "Map" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMap.svg" ) ),
116                          [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
117   {
118     return new QgsLayoutMapWidget( qobject_cast< QgsLayoutItemMap * >( item ), mapCanvas );
119   }, createRubberBand );
120   mapItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
121   {
122     QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( item );
123     Q_ASSERT( map );
124 
125     //get the color for map canvas background and set map background color accordingly
126     map->setBackgroundColor( QgsProject::instance()->backgroundColor() );
127 
128     if ( mapCanvas )
129     {
130       map->zoomToExtent( mapCanvas->mapSettings().visibleExtent() );
131     }
132 
133     // auto assign a unique id to map items
134     QList<QgsLayoutItemMap *> mapsList;
135     if ( map->layout() )
136       map->layout()->layoutItems( mapsList );
137 
138     int counter = mapsList.size() + 1;
139     bool existing = false;
140     while ( true )
141     {
142       existing = false;
143       for ( QgsLayoutItemMap *otherMap : qgis::as_const( mapsList ) )
144       {
145         if ( map == otherMap )
146           continue;
147 
148         if ( otherMap->id() == QObject::tr( "Map %1" ).arg( counter ) )
149         {
150           existing = true;
151           break;
152         }
153       }
154       if ( existing )
155         counter++;
156       else
157         break;
158     }
159     map->setId( QObject::tr( "Map %1" ).arg( counter ) );
160   } );
161   registry->addLayoutItemGuiMetadata( mapItemMetadata.release() );
162 
163   // picture item
164 
165   registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPicture, QObject::tr( "Picture" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddImage.svg" ) ),
166                                       [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
167   {
168     return new QgsLayoutPictureWidget( qobject_cast< QgsLayoutItemPicture * >( item ) );
169   }, createRubberBand ) );
170 
171 
172   // label item
173 
174   auto labelItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutLabel, QObject::tr( "Label" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionLabel.svg" ) ),
175                            [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
176   {
177     return new QgsLayoutLabelWidget( qobject_cast< QgsLayoutItemLabel * >( item ) );
178   }, createRubberBand );
179   labelItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
180   {
181     QgsLayoutItemLabel *label = qobject_cast< QgsLayoutItemLabel * >( item );
182     Q_ASSERT( label );
183 
184     label->setText( QObject::tr( "Lorem ipsum" ) );
185     if ( QApplication::isRightToLeft() )
186     {
187       label->setHAlign( Qt::AlignRight );
188     }
189     QSizeF minSize = label->sizeForText();
190     QSizeF currentSize = label->rect().size();
191 
192     //make sure label size is sufficient to fit text
193     double labelWidth = std::max( minSize.width(), currentSize.width() );
194     double labelHeight = std::max( minSize.height(), currentSize.height() );
195     label->attemptSetSceneRect( QRectF( label->pos().x(), label->pos().y(), labelWidth, labelHeight ) );
196   } );
197 
198   registry->addLayoutItemGuiMetadata( labelItemMetadata.release() );
199 
200 
201   // legend item
202 
203   auto legendItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutLegend, QObject::tr( "Legend" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLegend.svg" ) ),
204                             [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
205   {
206     return new QgsLayoutLegendWidget( qobject_cast< QgsLayoutItemLegend * >( item ), mapCanvas );
207   }, createRubberBand );
208   legendItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
209   {
210     QgsLayoutItemLegend *legend = qobject_cast< QgsLayoutItemLegend * >( item );
211     Q_ASSERT( legend );
212 
213     // try to find a good map to link the legend with by default
214     legend->setLinkedMap( findSensibleDefaultLinkedMapItem( legend ) );
215 
216     if ( QApplication::isRightToLeft() )
217     {
218       // for right-to-left locales, use an appropriate default layout
219       legend->setSymbolAlignment( Qt::AlignRight );
220       legend->rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignRight );
221       legend->rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignRight );
222       legend->rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignRight );
223       legend->setTitleAlignment( Qt::AlignRight );
224     }
225 
226     //set default legend font from settings
227     QgsSettings settings;
228     const QString defaultFontString = settings.value( QStringLiteral( "LayoutDesigner/defaultFont" ), QVariant(), QgsSettings::Gui ).toString();
229     if ( !defaultFontString.isEmpty() )
230     {
231       legend->rstyle( QgsLegendStyle::Title ).rfont().setFamily( defaultFontString );
232       legend->rstyle( QgsLegendStyle::Group ).rfont().setFamily( defaultFontString );
233       legend->rstyle( QgsLegendStyle::Subgroup ).rfont().setFamily( defaultFontString );
234       legend->rstyle( QgsLegendStyle::SymbolLabel ).rfont().setFamily( defaultFontString );
235     }
236 
237     legend->updateLegend();
238   } );
239 
240   registry->addLayoutItemGuiMetadata( legendItemMetadata.release() );
241 
242   // scalebar item
243 
244   auto scalebarItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutScaleBar, QObject::tr( "Scale Bar" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ),
245                               [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
246   {
247     return new QgsLayoutScaleBarWidget( qobject_cast< QgsLayoutItemScaleBar * >( item ) );
248   }, createRubberBand );
249   scalebarItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
250   {
251     QgsLayoutItemScaleBar *scalebar = qobject_cast< QgsLayoutItemScaleBar * >( item );
252     Q_ASSERT( scalebar );
253 
254     // try to find a good map to link the scalebar with by default
255     if ( QgsLayoutItemMap *targetMap = findSensibleDefaultLinkedMapItem( scalebar ) )
256     {
257       scalebar->setLinkedMap( targetMap );
258       scalebar->applyDefaultSize( scalebar->guessUnits() );
259     }
260   } );
261 
262   registry->addLayoutItemGuiMetadata( scalebarItemMetadata.release() );
263 
264 
265   // north arrow
266   std::unique_ptr< QgsLayoutItemGuiMetadata > northArrowMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata>(
267         QgsLayoutItemRegistry::LayoutPicture, QObject::tr( "North Arrow" ), QgsApplication::getThemeIcon( QStringLiteral( "/north_arrow.svg" ) ),
268         [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
269   {
270     return new QgsLayoutPictureWidget( qobject_cast< QgsLayoutItemPicture * >( item ) );
271   }, createRubberBand );
272   northArrowMetadata->setItemCreationFunction( []( QgsLayout * layout )->QgsLayoutItem *
273   {
274 
275     // count how many existing north arrows are already in layout
276     QList< QgsLayoutItemPicture * > pictureItems;
277     layout->layoutItems( pictureItems );
278     int northArrowCount = 0;
279 
280     QgsSettings settings;
281     const QString defaultPath = settings.value( QStringLiteral( "LayoutDesigner/defaultNorthArrow" ), QStringLiteral( ":/images/north_arrows/layout_default_north_arrow.svg" ), QgsSettings::Gui ).toString();
282 
283     for ( QgsLayoutItemPicture *p : qgis::as_const( pictureItems ) )
284     {
285       // look for pictures which use the default north arrow svg
286       if ( p->picturePath() == defaultPath )
287         northArrowCount++;
288     }
289 
290     std::unique_ptr< QgsLayoutItemPicture > picture = qgis::make_unique< QgsLayoutItemPicture >( layout );
291     picture->setNorthMode( QgsLayoutItemPicture::GridNorth );
292     picture->setPicturePath( defaultPath );
293     // set an id by default, so that north arrows are discernible in layout item lists
294     picture->setId( northArrowCount > 0 ? QObject::tr( "North Arrow %1" ).arg( northArrowCount + 1 ) : QObject::tr( "North Arrow" ) );
295     return picture.release();
296   } );
297   northArrowMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
298   {
299     QgsLayoutItemPicture *picture = qobject_cast< QgsLayoutItemPicture * >( item );
300     Q_ASSERT( picture );
301 
302     QList<QgsLayoutItemMap *> mapItems;
303     picture->layout()->layoutItems( mapItems );
304 
305     // try to find a good map to link the north arrow with by default
306     picture->setLinkedMap( findSensibleDefaultLinkedMapItem( picture ) );
307   } );
308   registry->addLayoutItemGuiMetadata( northArrowMetadata.release() );
309 
310   // shape items
311 
312   auto createShapeWidget =
313     []( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
314   {
315     return new QgsLayoutShapeWidget( qobject_cast< QgsLayoutItemShape * >( item ) );
316   };
317 
318   registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Rectangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), createShapeWidget, createRubberBand, QStringLiteral( "shapes" ), false, QgsLayoutItemAbstractGuiMetadata::Flags(), []( QgsLayout * layout )->QgsLayoutItem*
319   {
320     std::unique_ptr< QgsLayoutItemShape > shape = qgis::make_unique< QgsLayoutItemShape >( layout );
321     shape->setShapeType( QgsLayoutItemShape::Rectangle );
322     return shape.release();
323   } ) );
324   registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Ellipse" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), createShapeWidget, createEllipseBand, QStringLiteral( "shapes" ), false, QgsLayoutItemAbstractGuiMetadata::Flags(), []( QgsLayout * layout )->QgsLayoutItem*
325   {
326     std::unique_ptr< QgsLayoutItemShape > shape = qgis::make_unique< QgsLayoutItemShape >( layout );
327     shape->setShapeType( QgsLayoutItemShape::Ellipse );
328     return shape.release();
329   } ) );
330   registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Triangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), createShapeWidget, createTriangleBand, QStringLiteral( "shapes" ), false, QgsLayoutItemAbstractGuiMetadata::Flags(), []( QgsLayout * layout )->QgsLayoutItem*
331   {
332     std::unique_ptr< QgsLayoutItemShape > shape = qgis::make_unique< QgsLayoutItemShape >( layout );
333     shape->setShapeType( QgsLayoutItemShape::Triangle );
334     return shape.release();
335   } ) );
336 
337   // marker
338   registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutMarker, QObject::tr( "Marker" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMarker.svg" ) ),
339                                       [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
340   {
341     return new QgsLayoutMarkerWidget( qobject_cast< QgsLayoutItemMarker * >( item ) );
342   }, nullptr ) );
343 
344   // arrow
345   std::unique_ptr< QgsLayoutItemGuiMetadata > arrowMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata>(
346         QgsLayoutItemRegistry::LayoutPolyline, QObject::tr( "Arrow" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddArrow.svg" ) ),
347         [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
348   {
349     return new QgsLayoutPolylineWidget( qobject_cast< QgsLayoutItemPolyline * >( item ) );
350   }, createRubberBand, QString(), true );
351   arrowMetadata->setItemCreationFunction( []( QgsLayout * layout )->QgsLayoutItem *
352   {
353     std::unique_ptr< QgsLayoutItemPolyline > arrow = qgis::make_unique< QgsLayoutItemPolyline >( layout );
354     arrow->setEndMarker( QgsLayoutItemPolyline::ArrowHead );
355     return arrow.release();
356   } );
357   arrowMetadata->setNodeRubberBandCreationFunction( []( QgsLayoutView * )->QGraphicsPathItem*
358   {
359     std::unique_ptr< QGraphicsPathItem > band = qgis::make_unique< QGraphicsPathItem >();
360     band->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
361     band->setZValue( QgsLayout::ZViewTool );
362     return band.release();
363   } );
364   registry->addLayoutItemGuiMetadata( arrowMetadata.release() );
365 
366   // node items
367 
368   std::unique_ptr< QgsLayoutItemGuiMetadata > polygonMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >(
369         QgsLayoutItemRegistry::LayoutPolygon, QObject::tr( "Polygon" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolygon.svg" ) ),
370         [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
371   {
372     return new QgsLayoutPolygonWidget( qobject_cast< QgsLayoutItemPolygon * >( item ) );
373   }, createRubberBand, QStringLiteral( "nodes" ), true );
374   polygonMetadata->setNodeRubberBandCreationFunction( []( QgsLayoutView * )->QGraphicsPolygonItem*
375   {
376     std::unique_ptr< QGraphicsPolygonItem > band = qgis::make_unique< QGraphicsPolygonItem >();
377     band->setBrush( Qt::NoBrush );
378     band->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
379     band->setZValue( QgsLayout::ZViewTool );
380     return band.release();
381   } );
382   registry->addLayoutItemGuiMetadata( polygonMetadata.release() );
383 
384   std::unique_ptr< QgsLayoutItemGuiMetadata > polylineMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata>(
385         QgsLayoutItemRegistry::LayoutPolyline, QObject::tr( "Polyline" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolyline.svg" ) ),
386         [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
387   {
388     return new QgsLayoutPolylineWidget( qobject_cast< QgsLayoutItemPolyline * >( item ) );
389   }, createRubberBand, QStringLiteral( "nodes" ), true );
390   polylineMetadata->setNodeRubberBandCreationFunction( []( QgsLayoutView * )->QGraphicsPathItem*
391   {
392     std::unique_ptr< QGraphicsPathItem > band = qgis::make_unique< QGraphicsPathItem >();
393     band->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
394     band->setZValue( QgsLayout::ZViewTool );
395     return band.release();
396   } );
397   registry->addLayoutItemGuiMetadata( polylineMetadata.release() );
398 
399 
400   // html item
401 
402   auto htmlItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutHtml, QObject::tr( "HTML" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddHtml.svg" ) ),
403                           [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
404   {
405     return new QgsLayoutHtmlWidget( qobject_cast< QgsLayoutFrame * >( item ) );
406   }, createRubberBand );
407   htmlItemMetadata->setItemCreationFunction( [ = ]( QgsLayout * layout )->QgsLayoutItem *
408   {
409     std::unique_ptr< QgsLayoutItemHtml > htmlMultiFrame = qgis::make_unique< QgsLayoutItemHtml >( layout );
410     QgsLayoutItemHtml *html = htmlMultiFrame.get();
411     layout->addMultiFrame( htmlMultiFrame.release() );
412     std::unique_ptr< QgsLayoutFrame > frame = qgis::make_unique< QgsLayoutFrame >( layout, html );
413     QgsLayoutFrame *f = frame.get();
414     html->addFrame( frame.release() );
415     return f;
416   } );
417   registry->addLayoutItemGuiMetadata( htmlItemMetadata.release() );
418 
419   // attribute table item
420 
421   auto attributeTableItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutAttributeTable, QObject::tr( "Attribute Table" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddTable.svg" ) ),
422                                     [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
423   {
424     return new QgsLayoutAttributeTableWidget( qobject_cast< QgsLayoutFrame * >( item ) );
425   }, createRubberBand );
426   attributeTableItemMetadata->setItemCreationFunction( [ = ]( QgsLayout * layout )->QgsLayoutItem *
427   {
428     std::unique_ptr< QgsLayoutItemAttributeTable > tableMultiFrame = qgis::make_unique< QgsLayoutItemAttributeTable >( layout );
429     QgsLayoutItemAttributeTable *table = tableMultiFrame.get();
430 
431     //set first vector layer from layer registry as table source
432     QMap<QString, QgsMapLayer *> layerMap = layout->project()->mapLayers();
433     for ( auto it = layerMap.constBegin() ; it != layerMap.constEnd(); ++it )
434     {
435       if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() ) )
436       {
437         table->setVectorLayer( vl );
438         break;
439       }
440     }
441 
442     //set default table fonts from settings
443     QgsSettings settings;
444     const QString defaultFontString = settings.value( QStringLiteral( "LayoutDesigner/defaultFont" ), QVariant(), QgsSettings::Gui ).toString();
445     if ( !defaultFontString.isEmpty() )
446     {
447       QgsTextFormat format;
448       QFont f = format.font();
449       f.setFamily( defaultFontString );
450       format.setFont( f );
451       tableMultiFrame->setContentTextFormat( format );
452       f.setBold( true );
453       format.setFont( f );
454       tableMultiFrame->setHeaderTextFormat( format );
455     }
456 
457     layout->addMultiFrame( tableMultiFrame.release() );
458     std::unique_ptr< QgsLayoutFrame > frame = qgis::make_unique< QgsLayoutFrame >( layout, table );
459     QgsLayoutFrame *f = frame.get();
460     table->addFrame( frame.release() );
461     return f;
462   } );
463   registry->addLayoutItemGuiMetadata( attributeTableItemMetadata.release() );
464 
465   // manual table item
466 
467   auto manualTableItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutManualTable, QObject::tr( "Fixed Table" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddManualTable.svg" ) ),
468                                  [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
469   {
470     return new QgsLayoutManualTableWidget( qobject_cast< QgsLayoutFrame * >( item ) );
471   }, createRubberBand );
472   manualTableItemMetadata->setItemCreationFunction( [ = ]( QgsLayout * layout )->QgsLayoutItem *
473   {
474     std::unique_ptr< QgsLayoutItemManualTable > tableMultiFrame = qgis::make_unique< QgsLayoutItemManualTable >( layout );
475     QgsLayoutItemManualTable *table = tableMultiFrame.get();
476 
477     // initially start with a 2x2 empty table
478     QgsTableContents contents;
479     contents << ( QgsTableRow() << QgsTableCell() << QgsTableCell() );
480     contents << ( QgsTableRow() << QgsTableCell() << QgsTableCell() );
481     table->setTableContents( contents );
482 
483     //set default table fonts from settings
484     QgsSettings settings;
485     const QString defaultFontString = settings.value( QStringLiteral( "LayoutDesigner/defaultFont" ), QVariant(), QgsSettings::Gui ).toString();
486     if ( !defaultFontString.isEmpty() )
487     {
488       QgsTextFormat format;
489       QFont f = format.font();
490       f.setFamily( defaultFontString );
491       format.setFont( f );
492       tableMultiFrame->setContentTextFormat( format );
493       f.setBold( true );
494       format.setFont( f );
495       tableMultiFrame->setHeaderTextFormat( format );
496     }
497 
498     layout->addMultiFrame( tableMultiFrame.release() );
499 
500     std::unique_ptr< QgsLayoutFrame > frame = qgis::make_unique< QgsLayoutFrame >( layout, table );
501     QgsLayoutFrame *f = frame.get();
502     table->addFrame( frame.release() );
503     return f;
504   } );
505   registry->addLayoutItemGuiMetadata( manualTableItemMetadata.release() );
506 }
507