Tweaks to layout item combo box

This commit is contained in:
Nyall Dawson 2019-03-11 17:04:33 +10:00
parent 0003e91f27
commit f9fb4085b2
8 changed files with 219 additions and 6 deletions

View File

@ -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;

View File

@ -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 );
@ -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;

View File

@ -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
@ -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 );
@ -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:

View File

@ -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 );
@ -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 )
@ -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;
@ -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 ) )

View File

@ -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;
@ -360,6 +384,7 @@ class CORE_EXPORT QgsLayoutProxyModel: public QSortFilterProxyModel
QgsLayout *mLayout = nullptr;
QgsLayoutItemRegistry::ItemType mItemTypeFilter;
QList< QgsLayoutItem * > mExceptedList;
bool mAllowEmpty = false;
};

View File

@ -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() )
@ -50,7 +55,7 @@ void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item )
return;
}
}
setCurrentIndex( -1 );
setCurrentIndex( mProxyModel->allowEmptyItem() ? 0 : -1 );
}
QgsLayoutItem *QgsLayoutItemComboBox::currentItem() const
@ -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 );

View File

@ -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
@ -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()

View File

@ -21,6 +21,7 @@
#include "qgsapplication.h"
#include "qgsmapsettings.h"
#include "qgsproject.h"
#include "qgslayoutitemlabel.h"
#include <QObject>
#include "qgstest.h"
@ -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();
};
@ -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