QGIS/src/core/layout/qgslayouttable.h

809 lines
26 KiB
C++

/***************************************************************************
qgslayouttable.h
----------------
begin : November 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLAYOUTTABLE_H
#define QGSLAYOUTTABLE_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgslayoutmultiframe.h"
#include "qgsconditionalstyle.h"
#include "qgstextformat.h"
#include <QFont>
#include <QColor>
#include <QPair>
class QgsLayoutTableColumn;
/**
* \ingroup core
* \brief List of QVariants, representing a the contents of a single row in
* a QgsLayoutTable
*/
typedef QVector< QVariant > QgsLayoutTableRow;
/**
* \ingroup core
* \brief List of QgsLayoutTableRows, representing rows and column cell contents
* for a QgsLayoutTable
*/
#ifndef SIP_RUN
typedef QVector< QgsLayoutTableRow > QgsLayoutTableContents;
#else
typedef QVector< QVector< QVariant > > QgsLayoutTableContents;
#endif
/**
* \ingroup core
* \brief List of column definitions for a QgsLayoutTable
*/
typedef QVector<QgsLayoutTableColumn> QgsLayoutTableColumns;
/**
* \ingroup core
* \brief List of column definitions for sorting a QgsLayoutTable
* \since QGIS 3.14
*/
typedef QVector<QgsLayoutTableColumn> QgsLayoutTableSortColumns;
/**
* \ingroup core
* \class QgsLayoutTableStyle
* \brief Styling option for a layout table cell
*/
class CORE_EXPORT QgsLayoutTableStyle
{
public:
QgsLayoutTableStyle() = default;
//! Whether the styling option is enabled
bool enabled = false;
//! Cell background color
QColor cellBackgroundColor = QColor( 255, 255, 255, 255 );
/**
* Writes the style's properties to XML for storage.
* \param styleElem an existing QDomElement in which to store the style's properties.
* \param doc QDomDocument for the destination XML.
* \see readXml
*/
bool writeXml( QDomElement &styleElem, QDomDocument &doc ) const;
/**
* Reads the style's properties from XML.
* \param styleElem a QDomElement holding the style's desired properties.
* \see writeXml
*/
bool readXml( const QDomElement &styleElem );
};
/**
* \brief A class to display a table in the print layout, and allow
* the table to span over multiple frames
* \ingroup core
*/
class CORE_EXPORT QgsLayoutTable: public QgsLayoutMultiFrame
{
Q_OBJECT
public:
/**
* Controls how headers are horizontally aligned in a table
*/
enum HeaderHAlignment
{
FollowColumn, //!< Header uses the same alignment as the column
HeaderLeft, //!< Align headers left
HeaderCenter, //!< Align headers to center
HeaderRight //!< Align headers right
};
/**
* Controls where headers are shown in the table
*/
enum HeaderMode
{
FirstFrame = 0, //!< Header shown on first frame only
AllFrames, //!< Headers shown on all frames
NoHeaders //!< No headers shown for table
};
/**
* Controls how empty tables are displayed
*/
enum EmptyTableMode
{
HeadersOnly = 0, //!< Show header rows only
HideTable, //!< Hides entire table if empty
ShowMessage //!< Shows preset message instead of table contents
};
/**
* Controls how long strings in the table are handled
*/
enum WrapBehavior
{
TruncateText = 0, //!< Text which doesn't fit inside the cell is truncated
WrapText //!< Text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns with a fixed width.
};
/**
* Row or column groups for cell styling
*/
enum CellStyleGroup
{
OddColumns, //!< Style odd numbered columns
EvenColumns, //!< Style even numbered columns
OddRows, //!< Style odd numbered rows
EvenRows, //!< Style even numbered rows
FirstColumn, //!< Style first column only
LastColumn, //!< Style last column only
HeaderRow, //!< Style header row
FirstRow, //!< Style first row only
LastRow //!< Style last row only
};
/**
* Constructor for QgsLayoutTable, belonging to the specified \a layout.
*/
QgsLayoutTable( QgsLayout *layout );
~QgsLayoutTable() override;
/**
* Sets the \a margin distance in mm between cell borders and their contents.
* \see cellMargin()
*/
void setCellMargin( double margin );
/**
* Returns the margin distance between cell borders and their contents in mm.
* \see setCellMargin()
*/
double cellMargin() const { return mCellMargin; }
/**
* Sets the behavior \a mode for empty tables with no content rows.
* \see emptyTableBehavior()
*/
void setEmptyTableBehavior( EmptyTableMode mode );
/**
* Returns the behavior mode for empty tables. This property controls
* how the table is drawn if it contains no content rows.
* \see setEmptyTableBehavior()
*/
EmptyTableMode emptyTableBehavior() const { return mEmptyTableMode; }
/**
* Sets the \a message for empty tables with no content rows. This message
* is displayed in the table body if the empty table behavior is
* set to ShowMessage.
* \see emptyTableMessage()
* \see setEmptyTableBehavior()
*/
void setEmptyTableMessage( const QString &message );
/**
* Returns the message for empty tables with no content rows. This message
* is displayed in the table body if the empty table behavior is
* set to ShowMessage.
* \see setEmptyTableMessage()
* \see emptyTableBehavior()
*/
QString emptyTableMessage() const { return mEmptyTableMessage; }
/**
* Sets whether empty rows should be drawn. Tables default to hiding empty rows.
* \param showEmpty set to TRUE to show empty rows in the table
* \see showEmptyRows()
*/
void setShowEmptyRows( bool showEmpty );
/**
* Returns whether empty rows are drawn in the table.
* \see setShowEmptyRows()
*/
bool showEmptyRows() const { return mShowEmptyRows; }
/**
* Sets the \a font used to draw header text in the table.
* \see headerFont()
* \see setContentFont()
* \deprecated QGIS 3.40. Use setHeaderTextFormat() instead.
*/
Q_DECL_DEPRECATED void setHeaderFont( const QFont &font ) SIP_DEPRECATED;
/**
* Returns the font used to draw header text in the table.
* \see setHeaderFont()
* \see contentFont()
* \deprecated QGIS 3.40. Use headerTextFormat() instead.
*/
Q_DECL_DEPRECATED QFont headerFont() const SIP_DEPRECATED;
/**
* Sets the \a color used to draw header text in the table.
* \see headerFontColor()
* \see setHeaderFont()
* \see setContentFontColor()
* \deprecated QGIS 3.40. Use setHeaderTextFormat() instead.
*/
Q_DECL_DEPRECATED void setHeaderFontColor( const QColor &color ) SIP_DEPRECATED;
/**
* Returns the color used to draw header text in the table.
* \see setHeaderFontColor()
* \see headerFont()
* \see contentFontColor()
* \deprecated QGIS 3.40. Use headerTextFormat() instead.
*/
Q_DECL_DEPRECATED QColor headerFontColor() const SIP_DEPRECATED;
/**
* Sets the \a format used to draw header text in the table.
* \see headerTextFormat()
* \see setContentTextFormat()
* \since QGIS 3.16
*/
void setHeaderTextFormat( const QgsTextFormat &format );
/**
* Returns the format used to draw header text in the table.
* \see setHeaderTextFormat()
* \see contentTextFormat()
* \since QGIS 3.16
*/
QgsTextFormat headerTextFormat() const;
/**
* Sets the horizontal \a alignment for table headers.
* \see headerHAlignment()
*/
void setHeaderHAlignment( HeaderHAlignment alignment );
/**
* Returns the horizontal alignment for table headers.
* \see setHeaderHAlignment()
*/
HeaderHAlignment headerHAlignment() const { return mHeaderHAlignment; }
/**
* Sets the display \a mode for headers in the table. This property controls
* if and where headers are shown in the table.
* \see headerMode()
*/
void setHeaderMode( HeaderMode mode );
/**
* Returns the display mode for headers in the table. This property controls
* if and where headers are shown in the table.
* \see setHeaderMode()
*/
HeaderMode headerMode() const { return mHeaderMode; }
/**
* Sets the \a font used to draw text in table body cells.
* \see contentFont()
* \see setHeaderFont()
* \deprecated QGIS 3.40. Use setContentTextFormat() instead.
*/
Q_DECL_DEPRECATED void setContentFont( const QFont &font ) SIP_DEPRECATED;
/**
* Returns the font used to draw text in table body cells.
* \see setContentFont()
* \see headerFont()
* \deprecated QGIS 3.40. Use contextTextFormat() instead.
*/
Q_DECL_DEPRECATED QFont contentFont() const SIP_DEPRECATED;
/**
* Sets the \a color used to draw text in table body cells.
* \see contentFontColor()
* \see setContentFont()
* \see setHeaderFontColor()
* \deprecated QGIS 3.40. Use setContentTextFormat() instead.
*/
Q_DECL_DEPRECATED void setContentFontColor( const QColor &color ) SIP_DEPRECATED;
/**
* Returns the color used to draw text in table body cells.
* \see setContentFontColor()
* \see contentFont()
* \see headerFontColor()
* \deprecated QGIS 3.40. Use contextTextFormat() instead.
*/
Q_DECL_DEPRECATED QColor contentFontColor() const SIP_DEPRECATED;
/**
* Sets the \a format used to draw content text in the table.
* \see contentTextFormat()
* \see setHeaderTextFormat()
* \since QGIS 3.16
*/
void setContentTextFormat( const QgsTextFormat &format );
/**
* Returns the format used to draw content text in the table.
* \see setContentTextFormat()
* \see headerTextFormat()
* \since QGIS 3.16
*/
QgsTextFormat contentTextFormat() const;
/**
* Sets whether grid lines should be drawn in the table
* \param showGrid set to TRUE to show grid lines
* \see showGrid()
* \see setGridStrokeWidth()
* \see setGridColor()
*/
void setShowGrid( bool showGrid );
/**
* Returns whether grid lines are drawn in the table
* \see setShowGrid()
* \see gridStrokeWidth()
* \see gridColor()
*/
bool showGrid() const { return mShowGrid; }
/**
* Sets the \a width in mm for grid lines in the table.
* \see gridStrokeWidth()
* \see setShowGrid()
* \see setGridColor()
*/
void setGridStrokeWidth( double width );
/**
* Returns the width of grid lines in the table in mm.
* \see setGridStrokeWidth()
* \see showGrid()
* \see gridColor()
*/
double gridStrokeWidth() const { return mGridStrokeWidth; }
/**
* Sets the \a color used for grid lines in the table.
* \see gridColor()
* \see setShowGrid()
* \see setGridStrokeWidth()
*/
void setGridColor( const QColor &color );
/**
* Returns the color used for grid lines in the table.
* \see setGridColor()
* \see showGrid()
* \see gridStrokeWidth()
*/
QColor gridColor() const { return mGridColor; }
/**
* Sets whether the grid's horizontal lines should be drawn in the table
* \param horizontalGrid set to TRUE to draw grid's horizontal lines
* \see setShowGrid()
* \see setGridStrokeWidth()
* \see setGridColor()
* \see setVerticalGrid()
*/
void setHorizontalGrid( bool horizontalGrid );
/**
* Returns whether the grid's horizontal lines are drawn in the table.
* \see setShowGrid()
* \see setGridStrokeWidth()
* \see setGridColor()
* \see setVerticalGrid()
*/
bool horizontalGrid() const { return mHorizontalGrid; }
/**
* Sets whether the grid's vertical lines should be drawn in the table
* \param verticalGrid set to TRUE to draw grid's vertical lines
* \see setShowGrid()
* \see setGridStrokeWidth()
* \see setGridColor()
* \see setHorizontalGrid()
*/
void setVerticalGrid( bool verticalGrid );
/**
* Returns whether the grid's vertical lines are drawn in the table.
* \see setShowGrid()
* \see setGridStrokeWidth()
* \see setGridColor()
* \see setHorizontalGrid()
*/
bool verticalGrid() const { return mVerticalGrid; }
/**
* Sets the \a color used for background of table.
* \see backgroundColor()
* \see setGridColor()
*/
void setBackgroundColor( const QColor &color );
/**
* Returns the color used for the background of the table.
* \see setBackgroundColor()
* \see gridColor()
*/
QColor backgroundColor() const { return mBackgroundColor; }
/**
* Sets the wrap \a behavior for the table, which controls how text within cells is
* automatically wrapped.
* \see wrapBehavior()
*/
void setWrapBehavior( WrapBehavior behavior );
/**
* Returns the wrap behavior for the table, which controls how text within cells is
* automatically wrapped.
* \see setWrapBehavior()
*/
WrapBehavior wrapBehavior() const { return mWrapBehavior; }
/**
* Returns a reference to the list of QgsLayoutTableColumns shown in the table
* \see setColumns()
*/
QgsLayoutTableColumns &columns() { return mColumns; }
/**
* Replaces the columns in the table with a specified list of QgsLayoutTableColumns.
* \param columns list of QgsLayoutTableColumns to show in table.
* \see columns()
*/
void setColumns( const QgsLayoutTableColumns &columns );
/**
* Returns a reference to the list of QgsLayoutTableSortColumns shown in the table.
*
*
* \warning It is expected that the QgsLayoutTableColumn::attribute() values in the columns
* are valid QGIS expression values, and that column references are correctly escaped accordingly.
*
* \see setSortColumns()
* \since QGIS 3.14
*/
QgsLayoutTableSortColumns &sortColumns() { return mSortColumns; }
/**
* Replaces the sorting columns in the table with a specified list of QgsLayoutTableSortColumns.
* \param sortColumns list of QgsLayoutTableColumns used to sort the table.
*
* \warning It is expected that the QgsLayoutTableColumn::attribute() values in \a sortColumns
* are valid QGIS expression values, and that column references are correctly escaped accordingly.
*
* \see sortColumns()
* \since QGIS 3.14
*/
void setSortColumns( const QgsLayoutTableSortColumns &sortColumns );
/**
* Sets the cell \a style for a cell \a group.
* \see cellStyle()
*/
void setCellStyle( CellStyleGroup group, const QgsLayoutTableStyle &style );
/**
* Returns the cell style for a cell \a group.
* \see setCellStyle()
*/
const QgsLayoutTableStyle *cellStyle( CellStyleGroup group ) const;
/**
* Returns the text used in the column headers for the table.
* \returns QMap of int to QString, where the int is the column index (starting at 0),
* and the string is the text to use for the column's header
* \note not available in Python bindings
*/
virtual QMap<int, QString> headerLabels() const SIP_SKIP;
/**
* Fetches the contents used for the cells in the table.
* \returns TRUE if table contents were successfully retrieved.
* \param contents QgsLayoutTableContents to store retrieved row data in
*/
virtual bool getTableContents( QgsLayoutTableContents &contents ) = 0;
/**
* Returns the conditional style to use for the cell at \a row, \a column.
*
* \since QGIS 3.12
*/
virtual QgsConditionalStyle conditionalCellStyle( int row, int column ) const;
/**
* Creates a new QgsExpressionContextScope for the cell at \a row, \a column.
*
* \since QGIS 3.16
*/
virtual QgsExpressionContextScope *scopeForCell( int row, int column ) const SIP_FACTORY;
/**
* Returns the row span for the cell a \a row, \a column.
*
* \see columnSpan()
* \since QGIS 3.40
*/
virtual int rowSpan( int row, int column ) const;
/**
* Returns the column span for the cell a \a row, \a column.
*
* \see rowSpan()
* \since QGIS 3.40
*/
virtual int columnSpan( int row, int column ) const;
/**
* Returns the current contents of the table. Excludes header cells.
*/
QgsLayoutTableContents &contents() { return mTableContents; }
QSizeF fixedFrameSize( int frameIndex = -1 ) const override;
QSizeF minFrameSize( int frameIndex = -1 ) const override;
bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context ) override;
QSizeF totalSize() const override;
void render( QgsLayoutItemRenderContext &context, const QRectF &renderExtent, int frameIndex ) override;
public slots:
void refresh() override;
/**
* Refreshes the contents shown in the table by querying for new data.
* This also causes the column widths and size of the table to change to accommodate the
* new data.
*/
virtual void refreshAttributes();
void recalculateFrameSizes() override;
protected:
//! Margin between cell borders and cell text
double mCellMargin = 1.0;
//! Behavior for empty tables
EmptyTableMode mEmptyTableMode = HeadersOnly;
//! String to show in empty tables
QString mEmptyTableMessage;
//! True if empty rows should be shown in the table
bool mShowEmptyRows = false;
//! Alignment for table headers
HeaderHAlignment mHeaderHAlignment = FollowColumn;
//! Header display mode
HeaderMode mHeaderMode = FirstFrame;
QgsTextFormat mHeaderTextFormat;
QgsTextFormat mContentTextFormat;
//! True if grid should be shown
bool mShowGrid = true;
//! Width of grid lines
double mGridStrokeWidth = 0.5;
//! Color for grid lines
QColor mGridColor = Qt::black;
//! True if grid should be shown
bool mHorizontalGrid = true;
//! True if grid should be shown
bool mVerticalGrid = true;
//! Color for table background
QColor mBackgroundColor = Qt::white;
//! Columns to show in table
QgsLayoutTableColumns mColumns;
//! Columns to sort the table
QgsLayoutTableSortColumns mSortColumns;
//! Contents to show in table
QgsLayoutTableContents mTableContents;
//! Map of maximum width for each column
QMap<int, double> mMaxColumnWidthMap;
//! Map of maximum height for each row
QMap<int, double> mMaxRowHeightMap;
QSizeF mTableSize;
WrapBehavior mWrapBehavior = TruncateText;
QMap< CellStyleGroup, QgsLayoutTableStyle * > mCellStyles;
/**
* Calculates the maximum width of text shown in columns.
*/
virtual bool calculateMaxColumnWidths();
/**
* Calculates the maximum height of text shown in rows.
*/
virtual bool calculateMaxRowHeights();
/**
* Returns total width of table contents.
* \returns table width
* \see totalHeight
*/
//not const, as needs to call calculateMaxColumnWidths()
double totalWidth();
/**
* Returns total height of table contents.
* \see totalWidth()
*/
//not const, as needs to call calculateMaxRowHeights()
double totalHeight();
/**
* Calculates how many content rows would be visible within a frame of the specified
* height.
* \param context render context
* \param frameHeight height of frame
* \param firstRow index of first row visible in frame (where 0 = first row in table)
* \param includeHeader set to TRUE if frame would include a header row
* \param includeEmptyRows set to TRUE to also include rows which would be empty in the returned count. For instance,
* if the frame would include all table content rows and have space left for extra rows then setting this parameter
* to TRUE would also include a count of these extra blank rows.
* \returns number of visible content rows (excluding header row)
*/
int rowsVisible( QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const;
/**
* Calculates how many content rows are visible within a given frame.
* \param context render context
* \param frameIndex index number for frame
* \param firstRow index of first row visible in frame (where 0 = first row in table)
* \param includeEmptyRows set to TRUE to also include rows which would be empty in the returned count. For instance,
* if the frame would include all table content rows and have space left for extra rows then setting this parameter
* to TRUE would also include a count of these extra blank rows.
* \returns number of visible content rows (excludes header rows)
*/
int rowsVisible( QgsRenderContext &context, int frameIndex, int firstRow, bool includeEmptyRows ) const;
/**
* Calculates a range of rows which should be visible in a given frame.
* \param context render context
* \param frameIndex index number for frame
* \returns row range
*/
QPair<int, int> rowRange( QgsRenderContext &context, int frameIndex ) const;
/**
* Draws the horizontal grid lines for the table.
* \param context destination render context
* \param firstRow index corresponding to first row shown in frame
* \param lastRow index corresponding to last row shown in frame. If greater than the number of content rows in the
* table, then the default row height will be used for the remaining rows.
* \param drawHeaderLines set to TRUE to include for the table header
* \see drawVerticalGridLines()
*/
void drawHorizontalGridLines( QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines ) const;
/**
* Draws the vertical grid lines for the table.
* \param context destination render context
* \param maxWidthMap QMap of int to double, where the int contains the column number and the double is the
* maximum width of text present in the column.
* \param firstRow index corresponding to first row shown in frame
* \param lastRow index corresponding to last row shown in frame. If greater than the number of content rows in the
* table, then the default row height will be used for the remaining rows.
* \param hasHeader set to TRUE if table frame includes header cells
* \param mergeCells set to TRUE to merge table content cells
* \note not available in Python bindings
* \see drawVerticalGridLines()
* \see calculateMaxColumnWidths()
* \note not available in Python bindings
*/
void drawVerticalGridLines( QgsLayoutItemRenderContext &context, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells = false ) const SIP_SKIP;
/**
* Recalculates and updates the size of the table and all table frames.
*/
void recalculateTableSize();
/**
* Checks whether a table contents contains a given row
* \param contents table contents to check
* \param row row to check for
* \returns TRUE if contents contains rows
*/
bool contentsContainsRow( const QgsLayoutTableContents &contents, const QgsLayoutTableRow &row ) const;
/**
* Returns the text format to use for the cell at the specified \a row and \a column.
*
* \see textFormatForHeader()
* \since QGIS 3.16
*/
virtual QgsTextFormat textFormatForCell( int row, int column ) const;
/**
* Returns the text format to use for the header cell at the specified \a column.
*
* \see textFormatForCell()
* \since QGIS 3.16
*/
virtual QgsTextFormat textFormatForHeader( int column ) const;
/**
* Returns the horizontal alignment to use for the cell at the specified \a row and \a column.
*
* \see verticalAlignmentForCell()
* \since QGIS 3.16
*/
virtual Qt::Alignment horizontalAlignmentForCell( int row, int column ) const;
/**
* Returns the vertical alignment to use for the cell at the specified \a row and \a column.
*
* \see horizontalAlignmentForCell()
* \since QGIS 3.16
*/
virtual Qt::Alignment verticalAlignmentForCell( int row, int column ) const;
private:
QMap< CellStyleGroup, QString > mCellStyleNames;
//! Initializes cell style map
void initStyles();
/**
* Returns the calculated background color for a row and column combination.
* \param row row number, where -1 is the header row, and 0 is the first body row
* \param column column number, where 0 is the first column
* \param rowSpan number of rows spanned by cell
* \param columnSpan number of columns spanned by cell
* \returns background color, or invalid QColor if no background should be drawn
*/
QColor backgroundColor( int row, int column, int rowSpan = 1, int columnSpan = 1 ) const;
friend class TestQgsLayoutTable;
friend class TestQgsLayoutManualTable;
friend class QgsCompositionConverter;
};
#endif // QGSLAYOUTTABLE_H