/*************************************************************************** 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 #include #include 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 QgsLayoutTableColumns; /** * \ingroup core * \brief List of column definitions for sorting a QgsLayoutTable * \since QGIS 3.14 */ typedef QVector 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 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 mMaxColumnWidthMap; //! Map of maximum height for each row QMap 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 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 &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