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. QgsLayoutModel model.
%End %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: protected:
virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const; virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const;

View File

@ -33,6 +33,15 @@ until setCurrentLayout() is called
void setCurrentLayout( QgsLayout *layout ); void setCurrentLayout( QgsLayout *layout );
%Docstring %Docstring
Sets the ``layout`` containing the items to list in the combo box. 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 %End
void setItemType( QgsLayoutItemRegistry::ItemType itemType ); 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. Returns the list of specific items excluded from the combo box.
.. seealso:: :py:func:`setExceptedItemList` .. 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 %End
QgsLayoutItem *item( int index ) const; QgsLayoutItem *item( int index ) const;

View File

@ -323,7 +323,7 @@ QgsLayoutManagerModel::QgsLayoutManagerModel( QgsLayoutManager *manager, QObject
int QgsLayoutManagerModel::rowCount( const QModelIndex &parent ) const int QgsLayoutManagerModel::rowCount( const QModelIndex &parent ) const
{ {
Q_UNUSED( parent ); 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 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::DisplayRole:
case Qt::ToolTipRole: case Qt::ToolTipRole:
case Qt::EditRole: case Qt::EditRole:
return !isEmpty ? mLayoutManager->layouts().at( layoutRow )->name() : QVariant(); return !isEmpty && mLayoutManager ? mLayoutManager->layouts().at( layoutRow )->name() : QVariant();
case LayoutRole: case LayoutRole:
{ {
if ( isEmpty ) if ( isEmpty || !mLayoutManager )
return QVariant(); return QVariant();
else if ( QgsLayout *l = dynamic_cast< QgsLayout * >( mLayoutManager->layouts().at( layoutRow ) ) ) else if ( QgsLayout *l = dynamic_cast< QgsLayout * >( mLayoutManager->layouts().at( layoutRow ) ) )
return QVariant::fromValue( l ); return QVariant::fromValue( l );
@ -355,7 +355,7 @@ QVariant QgsLayoutManagerModel::data( const QModelIndex &index, int role ) const
case Qt::DecorationRole: case Qt::DecorationRole:
{ {
return isEmpty ? QIcon() : mLayoutManager->layouts().at( layoutRow )->icon(); return isEmpty || !mLayoutManager ? QIcon() : mLayoutManager->layouts().at( layoutRow )->icon();
} }
default: default:

View File

@ -958,6 +958,13 @@ QgsLayoutProxyModel::QgsLayoutProxyModel( QgsLayout *layout, QObject *parent )
bool QgsLayoutProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const 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 //sort by item id
const QgsLayoutItem *item1 = itemFromSourceIndex( left ); const QgsLayoutItem *item1 = itemFromSourceIndex( left );
const QgsLayoutItem *item2 = itemFromSourceIndex( right ); 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; 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 QgsLayoutItem *QgsLayoutProxyModel::itemFromSourceIndex( const QModelIndex &sourceIndex ) const
{ {
if ( !mLayout ) if ( !mLayout )
@ -980,6 +1016,17 @@ QgsLayoutItem *QgsLayoutProxyModel::itemFromSourceIndex( const QModelIndex &sour
return qobject_cast<QgsLayoutItem *>( itemAsVariant.value<QObject *>() ); 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 ) void QgsLayoutProxyModel::setFilterType( QgsLayoutItemRegistry::ItemType filter )
{ {
mItemTypeFilter = filter; mItemTypeFilter = filter;
@ -1002,7 +1049,7 @@ bool QgsLayoutProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &so
QgsLayoutItem *item = itemFromSourceIndex( index ); QgsLayoutItem *item = itemFromSourceIndex( index );
if ( !item ) if ( !item )
return false; return mAllowEmpty;
// specific exceptions // specific exceptions
if ( mExceptedList.contains( item ) ) if ( mExceptedList.contains( item ) )

View File

@ -352,6 +352,30 @@ class CORE_EXPORT QgsLayoutProxyModel: public QSortFilterProxyModel
*/ */
QgsLayoutItem *itemFromSourceIndex( const QModelIndex &sourceIndex ) const; 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: protected:
bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override; bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override;
bool lessThan( const QModelIndex &left, const QModelIndex &right ) 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; QgsLayout *mLayout = nullptr;
QgsLayoutItemRegistry::ItemType mItemTypeFilter; QgsLayoutItemRegistry::ItemType mItemTypeFilter;
QList< QgsLayoutItem * > mExceptedList; QList< QgsLayoutItem * > mExceptedList;
bool mAllowEmpty = false;
}; };

View File

@ -35,6 +35,11 @@ void QgsLayoutItemComboBox::setCurrentLayout( QgsLayout *layout )
mProxyModel->sort( 0, Qt::AscendingOrder ); mProxyModel->sort( 0, Qt::AscendingOrder );
} }
QgsLayout *QgsLayoutItemComboBox::currentLayout()
{
return mProxyModel->layout();
}
void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item ) void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item )
{ {
if ( !mProxyModel->sourceLayerModel() ) if ( !mProxyModel->sourceLayerModel() )
@ -50,7 +55,7 @@ void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item )
return; return;
} }
} }
setCurrentIndex( -1 ); setCurrentIndex( mProxyModel->allowEmptyItem() ? 0 : -1 );
} }
QgsLayoutItem *QgsLayoutItemComboBox::currentItem() const QgsLayoutItem *QgsLayoutItemComboBox::currentItem() const
@ -97,6 +102,16 @@ QList< QgsLayoutItem *> QgsLayoutItemComboBox::exceptedItemList() const
return mProxyModel->exceptedItemList(); return mProxyModel->exceptedItemList();
} }
void QgsLayoutItemComboBox::setAllowEmptyItem( bool allowEmpty )
{
mProxyModel->setAllowEmptyItem( allowEmpty );
}
bool QgsLayoutItemComboBox::allowEmptyItem() const
{
return mProxyModel->allowEmptyItem();
}
QgsLayoutItem *QgsLayoutItemComboBox::item( int index ) const QgsLayoutItem *QgsLayoutItemComboBox::item( int index ) const
{ {
const QModelIndex proxyIndex = mProxyModel->index( index, 0 ); 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. * Sets the \a layout containing the items to list in the combo box.
*
* \see currentLayout()
*/ */
void setCurrentLayout( QgsLayout *layout ); 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. * 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 * \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; 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. * Returns the item currently shown at the specified \a index within the combo box.
* \see currentItem() * \see currentItem()

View File

@ -21,6 +21,7 @@
#include "qgsapplication.h" #include "qgsapplication.h"
#include "qgsmapsettings.h" #include "qgsmapsettings.h"
#include "qgsproject.h" #include "qgsproject.h"
#include "qgslayoutitemlabel.h"
#include <QObject> #include <QObject>
#include "qgstest.h" #include "qgstest.h"
@ -58,6 +59,7 @@ class TestQgsLayoutModel : public QObject
void reorderToTopWithRemoved(); //test reordering to top with removed items void reorderToTopWithRemoved(); //test reordering to top with removed items
void reorderToBottomWithRemoved(); //test reordering to bottom with removed items void reorderToBottomWithRemoved(); //test reordering to bottom with removed items
void proxy();
void proxyCrash(); void proxyCrash();
}; };
@ -771,6 +773,48 @@ void TestQgsLayoutModel::reorderToBottomWithRemoved()
QCOMPARE( layout.itemsModel()->mItemsInScene.at( 1 ), item2 ); 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() void TestQgsLayoutModel::proxyCrash()
{ {
// test for a possible crash when using QgsComposerProxyModel and reordering items // test for a possible crash when using QgsComposerProxyModel and reordering items