Skip to content

Commit

Permalink
Tweaks to layout item combo box
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Mar 11, 2019
1 parent 0003e91 commit f9fb408
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 6 deletions.
32 changes: 32 additions & 0 deletions python/core/auto_generated/layout/qgslayoutmodel.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,38 @@ Returns the QgsLayoutItem corresponding to an index from the source
QgsLayoutModel model.
%End

QgsLayout *layout();
%Docstring
Returns the associated layout.

.. versionadded:: 3.8
%End

void setAllowEmptyItem( bool allowEmpty );
%Docstring
Sets whether an optional empty layout item is present in the model.

.. seealso:: :py:func:`allowEmptyItem`

.. versionadded:: 3.8
%End

bool allowEmptyItem() const;
%Docstring
Returns ``True`` if the model includes the empty item choice.

.. seealso:: :py:func:`setAllowEmptyItem`

.. versionadded:: 3.8
%End

virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;

virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;

virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole );


protected:
virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const;

Expand Down
27 changes: 27 additions & 0 deletions python/gui/auto_generated/layout/qgslayoutitemcombobox.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ until setCurrentLayout() is called
void setCurrentLayout( QgsLayout *layout );
%Docstring
Sets the ``layout`` containing the items to list in the combo box.

.. seealso:: :py:func:`currentLayout`
%End

QgsLayout *currentLayout();
%Docstring
Returns the current layout containing the items shown in the combo box.

.. seealso:: :py:func:`setCurrentLayout`
%End

void setItemType( QgsLayoutItemRegistry::ItemType itemType );
Expand Down Expand Up @@ -64,6 +73,24 @@ Sets a list of specific items to exclude from the combo box.
Returns the list of specific items excluded from the combo box.

.. seealso:: :py:func:`setExceptedItemList`
%End

void setAllowEmptyItem( bool allowEmpty );
%Docstring
Sets whether an optional empty layout item is present in the combobox.

.. seealso:: :py:func:`allowEmptyItem`

.. versionadded:: 3.8
%End

bool allowEmptyItem() const;
%Docstring
Returns ``True`` if the model includes the empty item choice.

.. seealso:: :py:func:`setAllowEmptyItem`

.. versionadded:: 3.8
%End

QgsLayoutItem *item( int index ) const;
Expand Down
8 changes: 4 additions & 4 deletions src/core/layout/qgslayoutmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ QgsLayoutManagerModel::QgsLayoutManagerModel( QgsLayoutManager *manager, QObject
int QgsLayoutManagerModel::rowCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent );
return mLayoutManager->layouts().count() + ( mAllowEmpty ? 1 : 0 );
return ( mLayoutManager ? mLayoutManager->layouts().count() : 0 ) + ( mAllowEmpty ? 1 : 0 );
}

QVariant QgsLayoutManagerModel::data( const QModelIndex &index, int role ) const
Expand All @@ -339,11 +339,11 @@ QVariant QgsLayoutManagerModel::data( const QModelIndex &index, int role ) const
case Qt::DisplayRole:
case Qt::ToolTipRole:
case Qt::EditRole:
return !isEmpty ? mLayoutManager->layouts().at( layoutRow )->name() : QVariant();
return !isEmpty && mLayoutManager ? mLayoutManager->layouts().at( layoutRow )->name() : QVariant();

case LayoutRole:
{
if ( isEmpty )
if ( isEmpty || !mLayoutManager )
return QVariant();
else if ( QgsLayout *l = dynamic_cast< QgsLayout * >( mLayoutManager->layouts().at( layoutRow ) ) )
return QVariant::fromValue( l );
Expand All @@ -355,7 +355,7 @@ QVariant QgsLayoutManagerModel::data( const QModelIndex &index, int role ) const

case Qt::DecorationRole:
{
return isEmpty ? QIcon() : mLayoutManager->layouts().at( layoutRow )->icon();
return isEmpty || !mLayoutManager ? QIcon() : mLayoutManager->layouts().at( layoutRow )->icon();
}

default:
Expand Down
49 changes: 48 additions & 1 deletion src/core/layout/qgslayoutmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,13 @@ QgsLayoutProxyModel::QgsLayoutProxyModel( QgsLayout *layout, QObject *parent )

bool QgsLayoutProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
{
const QString leftText = sourceModel()->data( left, Qt::DisplayRole ).toString();
const QString rightText = sourceModel()->data( right, Qt::DisplayRole ).toString();
if ( leftText.isEmpty() )
return true;
if ( rightText.isEmpty() )
return false;

//sort by item id
const QgsLayoutItem *item1 = itemFromSourceIndex( left );
const QgsLayoutItem *item2 = itemFromSourceIndex( right );
Expand All @@ -970,6 +977,35 @@ bool QgsLayoutProxyModel::lessThan( const QModelIndex &left, const QModelIndex &
return QString::localeAwareCompare( item1->displayName(), item2->displayName() ) < 0;
}

int QgsLayoutProxyModel::rowCount( const QModelIndex &parent ) const
{
return QSortFilterProxyModel::rowCount( parent ) + ( mAllowEmpty ? 1 : 0 );
}

QVariant QgsLayoutProxyModel::data( const QModelIndex &index, int role ) const
{
if ( mAllowEmpty && index.row() == rowCount() - 1 )
{
return QVariant();
}
else
{
return QSortFilterProxyModel::data( index, role );
}
}

bool QgsLayoutProxyModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( mAllowEmpty && index.row() == rowCount() - 1 )
{
return false;
}
else
{
return QSortFilterProxyModel::setData( index, value, role );
}
}

QgsLayoutItem *QgsLayoutProxyModel::itemFromSourceIndex( const QModelIndex &sourceIndex ) const
{
if ( !mLayout )
Expand All @@ -980,6 +1016,17 @@ QgsLayoutItem *QgsLayoutProxyModel::itemFromSourceIndex( const QModelIndex &sour
return qobject_cast<QgsLayoutItem *>( itemAsVariant.value<QObject *>() );
}

void QgsLayoutProxyModel::setAllowEmptyItem( bool allowEmpty )
{
mAllowEmpty = allowEmpty;
invalidateFilter();
}

bool QgsLayoutProxyModel::allowEmptyItem() const
{
return mAllowEmpty;
}

void QgsLayoutProxyModel::setFilterType( QgsLayoutItemRegistry::ItemType filter )
{
mItemTypeFilter = filter;
Expand All @@ -1002,7 +1049,7 @@ bool QgsLayoutProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &so
QgsLayoutItem *item = itemFromSourceIndex( index );

if ( !item )
return false;
return mAllowEmpty;

// specific exceptions
if ( mExceptedList.contains( item ) )
Expand Down
25 changes: 25 additions & 0 deletions src/core/layout/qgslayoutmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,30 @@ class CORE_EXPORT QgsLayoutProxyModel: public QSortFilterProxyModel
*/
QgsLayoutItem *itemFromSourceIndex( const QModelIndex &sourceIndex ) const;

/**
* Returns the associated layout.
* \since QGIS 3.8
*/
QgsLayout *layout() { return mLayout; }

/**
* Sets whether an optional empty layout item is present in the model.
* \see allowEmptyItem()
* \since QGIS 3.8
*/
void setAllowEmptyItem( bool allowEmpty );

/**
* Returns TRUE if the model includes the empty item choice.
* \see setAllowEmptyItem()
* \since QGIS 3.8
*/
bool allowEmptyItem() const;

int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;

protected:
bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override;
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override;
Expand All @@ -360,6 +384,7 @@ class CORE_EXPORT QgsLayoutProxyModel: public QSortFilterProxyModel
QgsLayout *mLayout = nullptr;
QgsLayoutItemRegistry::ItemType mItemTypeFilter;
QList< QgsLayoutItem * > mExceptedList;
bool mAllowEmpty = false;

};

Expand Down
17 changes: 16 additions & 1 deletion src/gui/layout/qgslayoutitemcombobox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ void QgsLayoutItemComboBox::setCurrentLayout( QgsLayout *layout )
mProxyModel->sort( 0, Qt::AscendingOrder );
}

QgsLayout *QgsLayoutItemComboBox::currentLayout()
{
return mProxyModel->layout();
}

void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item )
{
if ( !mProxyModel->sourceLayerModel() )
Expand All @@ -50,7 +55,7 @@ void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item )
return;
}
}
setCurrentIndex( -1 );
setCurrentIndex( mProxyModel->allowEmptyItem() ? 0 : -1 );
}

QgsLayoutItem *QgsLayoutItemComboBox::currentItem() const
Expand Down Expand Up @@ -97,6 +102,16 @@ QList< QgsLayoutItem *> QgsLayoutItemComboBox::exceptedItemList() const
return mProxyModel->exceptedItemList();
}

void QgsLayoutItemComboBox::setAllowEmptyItem( bool allowEmpty )
{
mProxyModel->setAllowEmptyItem( allowEmpty );
}

bool QgsLayoutItemComboBox::allowEmptyItem() const
{
return mProxyModel->allowEmptyItem();
}

QgsLayoutItem *QgsLayoutItemComboBox::item( int index ) const
{
const QModelIndex proxyIndex = mProxyModel->index( index, 0 );
Expand Down
23 changes: 23 additions & 0 deletions src/gui/layout/qgslayoutitemcombobox.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,18 @@ class GUI_EXPORT QgsLayoutItemComboBox : public QComboBox

/**
* Sets the \a layout containing the items to list in the combo box.
*
* \see currentLayout()
*/
void setCurrentLayout( QgsLayout *layout );

/**
* Returns the current layout containing the items shown in the combo box.
*
* \see setCurrentLayout()
*/
QgsLayout *currentLayout();

/**
* Sets a filter for the item type to show in the combo box.
* \param itemType type of items to show. Set to QgsLayoutItemRegistry::LayoutItem to
Expand All @@ -75,6 +84,20 @@ class GUI_EXPORT QgsLayoutItemComboBox : public QComboBox
*/
QList< QgsLayoutItem * > exceptedItemList() const;

/**
* Sets whether an optional empty layout item is present in the combobox.
* \see allowEmptyItem()
* \since QGIS 3.8
*/
void setAllowEmptyItem( bool allowEmpty );

/**
* Returns TRUE if the model includes the empty item choice.
* \see setAllowEmptyItem()
* \since QGIS 3.8
*/
bool allowEmptyItem() const;

/**
* Returns the item currently shown at the specified \a index within the combo box.
* \see currentItem()
Expand Down
44 changes: 44 additions & 0 deletions tests/src/core/testqgslayoutmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "qgsapplication.h"
#include "qgsmapsettings.h"
#include "qgsproject.h"
#include "qgslayoutitemlabel.h"

#include <QObject>
#include "qgstest.h"
Expand Down Expand Up @@ -58,6 +59,7 @@ class TestQgsLayoutModel : public QObject
void reorderToTopWithRemoved(); //test reordering to top with removed items
void reorderToBottomWithRemoved(); //test reordering to bottom with removed items

void proxy();
void proxyCrash();

};
Expand Down Expand Up @@ -771,6 +773,48 @@ void TestQgsLayoutModel::reorderToBottomWithRemoved()
QCOMPARE( layout.itemsModel()->mItemsInScene.at( 1 ), item2 );
}

void TestQgsLayoutModel::proxy()
{
QgsLayout *layout = new QgsLayout( QgsProject::instance() );
QgsLayoutProxyModel *proxy = new QgsLayoutProxyModel( layout );
// add some items to composition
QgsLayoutItemMap *item1 = new QgsLayoutItemMap( layout );
item1->setId( QStringLiteral( "c" ) );
layout->addLayoutItem( item1 );
QgsLayoutItemMap *item2 = new QgsLayoutItemMap( layout );
item2->setId( QStringLiteral( "b" ) );
layout->addLayoutItem( item2 );
QgsLayoutItemLabel *item3 = new QgsLayoutItemLabel( layout );
item3->setId( QStringLiteral( "a" ) );
layout->addLayoutItem( item3 );
QCOMPARE( proxy->rowCount( QModelIndex() ), 3 );
QCOMPARE( proxy->data( proxy->index( 0, 2, QModelIndex() ) ).toString(), QStringLiteral( "a" ) );
QCOMPARE( proxy->data( proxy->index( 1, 2, QModelIndex() ) ).toString(), QStringLiteral( "b" ) );
QCOMPARE( proxy->data( proxy->index( 2, 2, QModelIndex() ) ).toString(), QStringLiteral( "c" ) );

proxy->setAllowEmptyItem( true );
QCOMPARE( proxy->rowCount( QModelIndex() ), 4 );
QCOMPARE( proxy->data( proxy->index( 0, 2, QModelIndex() ) ).toString(), QStringLiteral( "a" ) );
QCOMPARE( proxy->data( proxy->index( 1, 2, QModelIndex() ) ).toString(), QStringLiteral( "b" ) );
QCOMPARE( proxy->data( proxy->index( 2, 2, QModelIndex() ) ).toString(), QStringLiteral( "c" ) );
QCOMPARE( proxy->data( proxy->index( 3, 2, QModelIndex() ) ).toString(), QString() );

proxy->setFilterType( QgsLayoutItemRegistry::LayoutMap );
QCOMPARE( proxy->rowCount( QModelIndex() ), 3 );
QCOMPARE( proxy->data( proxy->index( 0, 2, QModelIndex() ) ).toString(), QStringLiteral( "b" ) );
QCOMPARE( proxy->data( proxy->index( 1, 2, QModelIndex() ) ).toString(), QStringLiteral( "c" ) );
QCOMPARE( proxy->data( proxy->index( 2, 2, QModelIndex() ) ).toString(), QString() );

proxy->setFilterType( QgsLayoutItemRegistry::LayoutLabel );
QCOMPARE( proxy->rowCount( QModelIndex() ), 2 );
QCOMPARE( proxy->data( proxy->index( 0, 2, QModelIndex() ) ).toString(), QStringLiteral( "a" ) );
QCOMPARE( proxy->data( proxy->index( 1, 2, QModelIndex() ) ).toString(), QString() );

proxy->setFilterType( QgsLayoutItemRegistry::LayoutScaleBar );
QCOMPARE( proxy->rowCount( QModelIndex() ), 1 );
QCOMPARE( proxy->data( proxy->index( 0, 2, QModelIndex() ) ).toString(), QString() );
}

void TestQgsLayoutModel::proxyCrash()
{
// test for a possible crash when using QgsComposerProxyModel and reordering items
Expand Down

0 comments on commit f9fb408

Please sign in to comment.