Skip to content
qwt_dyngrid_layout.cpp 14.1 KiB
Newer Older
pixhawk's avatar
pixhawk committed
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
 * Qwt Widget Library
 * Copyright (C) 1997   Josef Wilgen
 * Copyright (C) 2002   Uwe Rathmann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the Qwt License, Version 1.0
 *****************************************************************************/

#include "qwt_dyngrid_layout.h"
#include "qwt_math.h"
Bryant's avatar
Bryant committed
#include <qvector.h>
pixhawk's avatar
pixhawk committed
#include <qlist.h>

class QwtDynGridLayout::PrivateData
{
public:
    PrivateData():
Bryant's avatar
Bryant committed
        isDirty( true )
    {
pixhawk's avatar
pixhawk committed
    }

Bryant's avatar
Bryant committed
    void updateLayoutCache();
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    mutable QList<QLayoutItem*> itemList;
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    uint maxColumns;
pixhawk's avatar
pixhawk committed
    uint numRows;
Bryant's avatar
Bryant committed
    uint numColumns;
pixhawk's avatar
pixhawk committed

    Qt::Orientations expanding;

    bool isDirty;
Bryant's avatar
Bryant committed
    QVector<QSize> itemSizeHints;
pixhawk's avatar
pixhawk committed
};

Bryant's avatar
Bryant committed
void QwtDynGridLayout::PrivateData::updateLayoutCache()
{
    itemSizeHints.resize( itemList.count() );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    int index = 0;
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    for ( QList<QLayoutItem*>::iterator it = itemList.begin();
        it != itemList.end(); ++it, index++ )
    {
        itemSizeHints[ index ] = ( *it )->sizeHint();
    }
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    isDirty = false;
pixhawk's avatar
pixhawk committed
}

/*!
  \param parent Parent widget
Bryant's avatar
Bryant committed
  \param margin Margin
pixhawk's avatar
pixhawk committed
  \param spacing Spacing
*/
Bryant's avatar
Bryant committed

QwtDynGridLayout::QwtDynGridLayout( QWidget *parent,
        int margin, int spacing ):
    QLayout( parent )
pixhawk's avatar
pixhawk committed
{
    init();
Bryant's avatar
Bryant committed

    setSpacing( spacing );
    setMargin( margin );
pixhawk's avatar
pixhawk committed
}

/*!
  \param spacing Spacing
*/

Bryant's avatar
Bryant committed
QwtDynGridLayout::QwtDynGridLayout( int spacing )
pixhawk's avatar
pixhawk committed
{
    init();
Bryant's avatar
Bryant committed
    setSpacing( spacing );
pixhawk's avatar
pixhawk committed
}

/*!
  Initialize the layout with default values.
*/
void QwtDynGridLayout::init()
{
    d_data = new QwtDynGridLayout::PrivateData;
Bryant's avatar
Bryant committed
    d_data->maxColumns = d_data->numRows = d_data->numColumns = 0;
pixhawk's avatar
pixhawk committed
    d_data->expanding = 0;
}

//! Destructor

QwtDynGridLayout::~QwtDynGridLayout()
{
Bryant's avatar
Bryant committed
    for ( int i = 0; i < d_data->itemList.size(); i++ )
        delete d_data->itemList[i];
pixhawk's avatar
pixhawk committed

    delete d_data;
}

Bryant's avatar
Bryant committed
//! Invalidate all internal caches
pixhawk's avatar
pixhawk committed
void QwtDynGridLayout::invalidate()
{
    d_data->isDirty = true;
    QLayout::invalidate();
}

/*!
  Limit the number of columns.
Bryant's avatar
Bryant committed
  \param maxColumns upper limit, 0 means unlimited
  \sa maxColumns()
pixhawk's avatar
pixhawk committed
*/
Bryant's avatar
Bryant committed
void QwtDynGridLayout::setMaxColumns( uint maxColumns )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    d_data->maxColumns = maxColumns;
pixhawk's avatar
pixhawk committed
}

/*!
Bryant's avatar
Bryant committed
  \brief Return the upper limit for the number of columns.

pixhawk's avatar
pixhawk committed
  0 means unlimited, what is the default.

Bryant's avatar
Bryant committed
  \return Upper limit for the number of columns
  \sa setMaxColumns()
*/
uint QwtDynGridLayout::maxColumns() const
Bryant's avatar
Bryant committed
    return d_data->maxColumns;
pixhawk's avatar
pixhawk committed
}

Bryant's avatar
Bryant committed
/*! 
  \brief Add an item to the next free position.
  \param item Layout item
 */
void QwtDynGridLayout::addItem( QLayoutItem *item )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    d_data->itemList.append( item );
pixhawk's avatar
pixhawk committed
    invalidate();
}

/*!
  \return true if this layout is empty.
pixhawk's avatar
pixhawk committed
*/
bool QwtDynGridLayout::isEmpty() const
{
    return d_data->itemList.isEmpty();
}

pixhawk's avatar
pixhawk committed
  \return number of layout items
*/
uint QwtDynGridLayout::itemCount() const
{
    return d_data->itemList.count();
}

Bryant's avatar
Bryant committed
  Find the item at a specific index
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
  \param index Index
  \return Item at a specific index
  \sa takeAt()
pixhawk's avatar
pixhawk committed
*/
QLayoutItem *QwtDynGridLayout::itemAt( int index ) const
{
    if ( index < 0 || index >= d_data->itemList.count() )
        return NULL;

Bryant's avatar
Bryant committed
    return d_data->itemList.at( index );
pixhawk's avatar
pixhawk committed
}
Bryant's avatar
Bryant committed
/*!
  Find the item at a specific index and remove it from the layout

  \param index Index
  \return Layout item, removed from the layout
  \sa itemAt()
*/
pixhawk's avatar
pixhawk committed
QLayoutItem *QwtDynGridLayout::takeAt( int index )
{
    if ( index < 0 || index >= d_data->itemList.count() )
        return NULL;
pixhawk's avatar
pixhawk committed
    d_data->isDirty = true;
Bryant's avatar
Bryant committed
    return d_data->itemList.takeAt( index );
pixhawk's avatar
pixhawk committed
}

Bryant's avatar
Bryant committed
//! \return Number of items in the layout
pixhawk's avatar
pixhawk committed
int QwtDynGridLayout::count() const
{
    return d_data->itemList.count();
}

Bryant's avatar
Bryant committed
/*!
  Set whether this layout can make use of more space than sizeHint().
  A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only
  one dimension, while Qt::Vertical | Qt::Horizontal means that it wants
  to grow in both dimensions. The default value is 0.

  \param expanding Or'd orientations
  \sa expandingDirections()
*/
void QwtDynGridLayout::setExpandingDirections( Qt::Orientations expanding )
pixhawk's avatar
pixhawk committed
{
    d_data->expanding = expanding;
}

Bryant's avatar
Bryant committed
/*!
  \brief Returns whether this layout can make use of more space than sizeHint().

  A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only
  one dimension, while Qt::Vertical | Qt::Horizontal means that it wants
  to grow in both dimensions.

  \return Orientations, where the layout expands
  \sa setExpandingDirections()
*/
pixhawk's avatar
pixhawk committed
Qt::Orientations QwtDynGridLayout::expandingDirections() const
{
    return d_data->expanding;
}

/*!
Bryant's avatar
Bryant committed
  Reorganizes columns and rows and resizes managed items within
  a rectangle.
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
  \param rect Layout geometry
*/
void QwtDynGridLayout::setGeometry( const QRect &rect )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    QLayout::setGeometry( rect );
pixhawk's avatar
pixhawk committed

    if ( isEmpty() )
        return;

Bryant's avatar
Bryant committed
    d_data->numColumns = columnsForWidth( rect.width() );
    d_data->numRows = itemCount() / d_data->numColumns;
    if ( itemCount() % d_data->numColumns )
pixhawk's avatar
pixhawk committed
        d_data->numRows++;

Bryant's avatar
Bryant committed
    QList<QRect> itemGeometries = layoutItems( rect, d_data->numColumns );
pixhawk's avatar
pixhawk committed

    int index = 0;
Bryant's avatar
Bryant committed
    for ( QList<QLayoutItem*>::iterator it = d_data->itemList.begin();
        it != d_data->itemList.end(); ++it )
    {
        ( *it )->setGeometry( itemGeometries[index] );
        index++;
pixhawk's avatar
pixhawk committed
    }
}

Bryant's avatar
Bryant committed
  \brief Calculate the number of columns for a given width. 

  The calculation tries to use as many columns as possible 
  ( limited by maxColumns() )
pixhawk's avatar
pixhawk committed

  \param width Available width for all columns
Bryant's avatar
Bryant committed
  \return Number of columns for a given width
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
  \sa maxColumns(), setMaxColumns()
*/
uint QwtDynGridLayout::columnsForWidth( int width ) const
pixhawk's avatar
pixhawk committed
{
    if ( isEmpty() )
        return 0;

Bryant's avatar
Bryant committed
    uint maxColumns = itemCount();
    if ( d_data->maxColumns > 0 ) 
        maxColumns = qMin( d_data->maxColumns, maxColumns );

    if ( maxRowWidth( maxColumns ) <= width )
        return maxColumns;
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    for ( uint numColumns = 2; numColumns <= maxColumns; numColumns++ )
    {
        const int rowWidth = maxRowWidth( numColumns );
pixhawk's avatar
pixhawk committed
        if ( rowWidth > width )
Bryant's avatar
Bryant committed
            return numColumns - 1;
pixhawk's avatar
pixhawk committed
    }

    return 1; // At least 1 column
}

pixhawk's avatar
pixhawk committed
  Calculate the width of a layout for a given number of
  columns.

Bryant's avatar
Bryant committed
  \param numColumns Given number of columns
pixhawk's avatar
pixhawk committed
  \param itemWidth Array of the width hints for all items
*/
Bryant's avatar
Bryant committed
int QwtDynGridLayout::maxRowWidth( int numColumns ) const
pixhawk's avatar
pixhawk committed
{
    int col;

Bryant's avatar
Bryant committed
    QVector<int> colWidth( numColumns );
    for ( col = 0; col < numColumns; col++ )
pixhawk's avatar
pixhawk committed
        colWidth[col] = 0;

    if ( d_data->isDirty )
Bryant's avatar
Bryant committed
        d_data->updateLayoutCache();
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    for ( int index = 0;
        index < d_data->itemSizeHints.count(); index++ )
    {
        col = index % numColumns;
        colWidth[col] = qMax( colWidth[col],
            d_data->itemSizeHints[int( index )].width() );
pixhawk's avatar
pixhawk committed
    }

Bryant's avatar
Bryant committed
    int rowWidth = 2 * margin() + ( numColumns - 1 ) * spacing();
    for ( col = 0; col < numColumns; col++ )
pixhawk's avatar
pixhawk committed
        rowWidth += colWidth[col];

    return rowWidth;
}

/*!
  \return the maximum width of all layout items
*/
int QwtDynGridLayout::maxItemWidth() const
{
    if ( isEmpty() )
        return 0;

    if ( d_data->isDirty )
Bryant's avatar
Bryant committed
        d_data->updateLayoutCache();
pixhawk's avatar
pixhawk committed

    int w = 0;
Bryant's avatar
Bryant committed
    for ( int i = 0; i < d_data->itemSizeHints.count(); i++ )
    {
        const int itemW = d_data->itemSizeHints[i].width();
pixhawk's avatar
pixhawk committed
        if ( itemW > w )
            w = itemW;
    }

    return w;
}

/*!
  Calculate the geometries of the layout items for a layout
Bryant's avatar
Bryant committed
  with numColumns columns and a given rectangle.

pixhawk's avatar
pixhawk committed
  \param rect Rect where to place the items
Bryant's avatar
Bryant committed
  \param numColumns Number of columns
pixhawk's avatar
pixhawk committed
  \return item geometries
*/

Bryant's avatar
Bryant committed
QList<QRect> QwtDynGridLayout::layoutItems( const QRect &rect,
    uint numColumns ) const
pixhawk's avatar
pixhawk committed
{
    QList<QRect> itemGeometries;
Bryant's avatar
Bryant committed
    if ( numColumns == 0 || isEmpty() )
pixhawk's avatar
pixhawk committed
        return itemGeometries;

Bryant's avatar
Bryant committed
    uint numRows = itemCount() / numColumns;
    if ( numColumns % itemCount() )
pixhawk's avatar
pixhawk committed
        numRows++;
Bryant's avatar
Bryant committed
    if ( numRows == 0 )
        return itemGeometries;

    QVector<int> rowHeight( numRows );
    QVector<int> colWidth( numColumns );
Bryant's avatar
Bryant committed
    layoutGrid( numColumns, rowHeight, colWidth );
pixhawk's avatar
pixhawk committed

    bool expandH, expandV;
    expandH = expandingDirections() & Qt::Horizontal;
    expandV = expandingDirections() & Qt::Vertical;

    if ( expandH || expandV )
Bryant's avatar
Bryant committed
        stretchGrid( rect, numColumns, rowHeight, colWidth );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    const int maxColumns = d_data->maxColumns;
    d_data->maxColumns = numColumns;
    const QRect alignedRect = alignmentRect( rect );
    d_data->maxColumns = maxColumns;
pixhawk's avatar
pixhawk committed

    const int xOffset = expandH ? 0 : alignedRect.x();
    const int yOffset = expandV ? 0 : alignedRect.y();

Bryant's avatar
Bryant committed
    QVector<int> colX( numColumns );
    QVector<int> rowY( numRows );
pixhawk's avatar
pixhawk committed

    const int xySpace = spacing();

    rowY[0] = yOffset + margin();
Bryant's avatar
Bryant committed
    for ( uint r = 1; r < numRows; r++ )
pixhawk's avatar
pixhawk committed
        rowY[r] = rowY[r-1] + rowHeight[r-1] + xySpace;

    colX[0] = xOffset + margin();
Bryant's avatar
Bryant committed
    for ( uint c = 1; c < numColumns; c++ )
pixhawk's avatar
pixhawk committed
        colX[c] = colX[c-1] + colWidth[c-1] + xySpace;
pixhawk's avatar
pixhawk committed
    const int itemCount = d_data->itemList.size();
Bryant's avatar
Bryant committed
    for ( int i = 0; i < itemCount; i++ )
    {
        const int row = i / numColumns;
        const int col = i % numColumns;
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
        QRect itemGeometry( colX[col], rowY[row],
            colWidth[col], rowHeight[row] );
        itemGeometries.append( itemGeometry );
pixhawk's avatar
pixhawk committed
    }

    return itemGeometries;
}


/*!
  Calculate the dimensions for the columns and rows for a grid
Bryant's avatar
Bryant committed
  of numColumns columns.

  \param numColumns Number of columns.
pixhawk's avatar
pixhawk committed
  \param rowHeight Array where to fill in the calculated row heights.
  \param colWidth Array where to fill in the calculated column widths.
*/

Bryant's avatar
Bryant committed
void QwtDynGridLayout::layoutGrid( uint numColumns,
    QVector<int>& rowHeight, QVector<int>& colWidth ) const
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    if ( numColumns <= 0 )
pixhawk's avatar
pixhawk committed
        return;

    if ( d_data->isDirty )
Bryant's avatar
Bryant committed
        d_data->updateLayoutCache();
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    for ( int index = 0; index < d_data->itemSizeHints.count(); index++ )
    {
        const int row = index / numColumns;
        const int col = index % numColumns;
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
        const QSize &size = d_data->itemSizeHints[int( index )];
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
        rowHeight[row] = ( col == 0 )
            ? size.height() : qMax( rowHeight[row], size.height() );
        colWidth[col] = ( row == 0 )
            ? size.width() : qMax( colWidth[col], size.width() );
pixhawk's avatar
pixhawk committed
    }
}

/*!
Bryant's avatar
Bryant committed
  \return true: QwtDynGridLayout implements heightForWidth().
  \sa heightForWidth()
pixhawk's avatar
pixhawk committed
*/
bool QwtDynGridLayout::hasHeightForWidth() const
{
    return true;
}

/*!
Bryant's avatar
Bryant committed
  \return The preferred height for this layout, given a width.
  \sa hasHeightForWidth()
pixhawk's avatar
pixhawk committed
*/
Bryant's avatar
Bryant committed
int QwtDynGridLayout::heightForWidth( int width ) const
pixhawk's avatar
pixhawk committed
{
    if ( isEmpty() )
        return 0;

Bryant's avatar
Bryant committed
    const uint numColumns = columnsForWidth( width );
    uint numRows = itemCount() / numColumns;
    if ( itemCount() % numColumns )
pixhawk's avatar
pixhawk committed
        numRows++;

Bryant's avatar
Bryant committed
    QVector<int> rowHeight( numRows );
    QVector<int> colWidth( numColumns );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    layoutGrid( numColumns, rowHeight, colWidth );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    int h = 2 * margin() + ( numRows - 1 ) * spacing();
    for ( uint row = 0; row < numRows; row++ )
pixhawk's avatar
pixhawk committed
        h += rowHeight[row];

    return h;
}

/*!
  Stretch columns in case of expanding() & QSizePolicy::Horizontal and
  rows in case of expanding() & QSizePolicy::Vertical to fill the entire
  rect. Rows and columns are stretched with the same factor.

Bryant's avatar
Bryant committed
  \param rect Bounding rectangle
  \param numColumns Number of columns
  \param rowHeight Array to be filled with the calculated row heights
  \param colWidth Array to be filled with the calculated column widths

  \sa setExpanding(), expanding()
*/
void QwtDynGridLayout::stretchGrid( const QRect &rect,
    uint numColumns, QVector<int>& rowHeight, QVector<int>& colWidth ) const
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    if ( numColumns == 0 || isEmpty() )
pixhawk's avatar
pixhawk committed
        return;

    bool expandH, expandV;
    expandH = expandingDirections() & Qt::Horizontal;
    expandV = expandingDirections() & Qt::Vertical;
Bryant's avatar
Bryant committed

    if ( expandH )
    {
        int xDelta = rect.width() - 2 * margin() - ( numColumns - 1 ) * spacing();
        for ( uint col = 0; col < numColumns; col++ )
pixhawk's avatar
pixhawk committed
            xDelta -= colWidth[col];

Bryant's avatar
Bryant committed
        if ( xDelta > 0 )
        {
            for ( uint col = 0; col < numColumns; col++ )
            {
                const int space = xDelta / ( numColumns - col );
pixhawk's avatar
pixhawk committed
                colWidth[col] += space;
                xDelta -= space;
            }
        }
    }

Bryant's avatar
Bryant committed
    if ( expandV )
    {
        uint numRows = itemCount() / numColumns;
        if ( itemCount() % numColumns )
pixhawk's avatar
pixhawk committed
            numRows++;

Bryant's avatar
Bryant committed
        int yDelta = rect.height() - 2 * margin() - ( numRows - 1 ) * spacing();
        for ( uint row = 0; row < numRows; row++ )
pixhawk's avatar
pixhawk committed
            yDelta -= rowHeight[row];

Bryant's avatar
Bryant committed
        if ( yDelta > 0 )
        {
            for ( uint row = 0; row < numRows; row++ )
            {
                const int space = yDelta / ( numRows - row );
pixhawk's avatar
pixhawk committed
                rowHeight[row] += space;
                yDelta -= space;
            }
        }
    }
}

/*!
Bryant's avatar
Bryant committed
   Return the size hint. If maxColumns() > 0 it is the size for
   a grid with maxColumns() columns, otherwise it is the size for
pixhawk's avatar
pixhawk committed
   a grid with only one row.

Bryant's avatar
Bryant committed
   \return Size hint
   \sa maxColumns(), setMaxColumns()
*/
pixhawk's avatar
pixhawk committed
QSize QwtDynGridLayout::sizeHint() const
{
    if ( isEmpty() )
        return QSize();

Bryant's avatar
Bryant committed
    uint numColumns = itemCount();
    if ( d_data->maxColumns > 0 )
        numColumns = qMin( d_data->maxColumns, numColumns );

    uint numRows = itemCount() / numColumns;
    if ( itemCount() % numColumns )
pixhawk's avatar
pixhawk committed
        numRows++;

Bryant's avatar
Bryant committed
    QVector<int> rowHeight( numRows );
    QVector<int> colWidth( numColumns );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    layoutGrid( numColumns, rowHeight, colWidth );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    int h = 2 * margin() + ( numRows - 1 ) * spacing();
    for ( uint row = 0; row < numRows; row++ )
pixhawk's avatar
pixhawk committed
        h += rowHeight[row];

Bryant's avatar
Bryant committed
    int w = 2 * margin() + ( numColumns - 1 ) * spacing();
    for ( uint col = 0; col < numColumns; col++ )
pixhawk's avatar
pixhawk committed
        w += colWidth[col];

Bryant's avatar
Bryant committed
    return QSize( w, h );
pixhawk's avatar
pixhawk committed
}

/*!
  \return Number of rows of the current layout.
Bryant's avatar
Bryant committed
  \sa numColumns()
pixhawk's avatar
pixhawk committed
  \warning The number of rows might change whenever the geometry changes
*/
uint QwtDynGridLayout::numRows() const
{
    return d_data->numRows;
pixhawk's avatar
pixhawk committed
}

/*!
  \return Number of columns of the current layout.
Bryant's avatar
Bryant committed
  \sa numRows()
pixhawk's avatar
pixhawk committed
  \warning The number of columns might change whenever the geometry changes
*/
Bryant's avatar
Bryant committed
uint QwtDynGridLayout::numColumns() const
Bryant's avatar
Bryant committed
    return d_data->numColumns;
pixhawk's avatar
pixhawk committed
}