qskinny/src/layouts/QskLinearBox.cpp

606 lines
14 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskLinearBox.h"
#include "QskLinearLayoutEngine.h"
2017-07-21 18:21:34 +02:00
#include "QskLayoutConstraint.h"
#include "QskEvent.h"
#include "QskQuick.h"
static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on )
{
if ( ( item == nullptr ) || ( qskControlCast( item ) != nullptr ) )
return;
/*
For QQuickItems not being derived from QskControl we manually
send QEvent::LayoutRequest events.
*/
if ( on )
{
auto sendLayoutRequest =
[receiver]()
{
QEvent event( QEvent::LayoutRequest );
QCoreApplication::sendEvent( receiver, &event );
};
QObject::connect( item, &QQuickItem::implicitWidthChanged,
receiver, sendLayoutRequest );
QObject::connect( item, &QQuickItem::implicitHeightChanged,
receiver, sendLayoutRequest );
QObject::connect( item, &QQuickItem::visibleChanged,
receiver, sendLayoutRequest );
}
else
{
QObject::disconnect( item, &QQuickItem::implicitWidthChanged, receiver, nullptr );
QObject::disconnect( item, &QQuickItem::implicitHeightChanged, receiver, nullptr );
QObject::disconnect( item, &QQuickItem::visibleChanged, receiver, nullptr );
}
}
2017-07-21 18:21:34 +02:00
class QskLinearBox::PrivateData
{
2018-08-03 08:15:28 +02:00
public:
PrivateData( Qt::Orientation orientation, uint dimension )
: engine( orientation, dimension )
2017-07-21 18:21:34 +02:00
{
}
QskLinearLayoutEngine engine;
2017-07-21 18:21:34 +02:00
};
2018-08-03 08:15:28 +02:00
QskLinearBox::QskLinearBox( QQuickItem* parent )
: QskLinearBox( Qt::Horizontal, std::numeric_limits< uint >::max(), parent )
2017-07-21 18:21:34 +02:00
{
}
2018-08-03 08:15:28 +02:00
QskLinearBox::QskLinearBox( Qt::Orientation orientation, QQuickItem* parent )
: QskLinearBox( orientation, std::numeric_limits< uint >::max(), parent )
2017-07-21 18:21:34 +02:00
{
}
QskLinearBox::QskLinearBox( Qt::Orientation orientation, uint dimension, QQuickItem* parent )
: QskIndexedLayoutBox( parent )
2018-08-03 08:15:28 +02:00
, m_data( new PrivateData( orientation, dimension ) )
2017-07-21 18:21:34 +02:00
{
}
QskLinearBox::~QskLinearBox()
{
}
int QskLinearBox::entryCount() const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.count();
2017-07-21 18:21:34 +02:00
}
QQuickItem* QskLinearBox::itemAtIndex( int index ) const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.itemAt( index );
2017-07-21 18:21:34 +02:00
}
int QskLinearBox::indexOf( const QQuickItem* item ) const
2017-07-21 18:21:34 +02:00
{
if ( item )
{
/*
Linear search might become slow for many items,
better introduce some sort of hash table TODO ...
indexOf is often used for configuring an item
after inserting it. So we iterate in reverse order
*/
const auto& engine = m_data->engine;
for ( int i = engine.count() - 1; i >= 0; --i )
{
if ( engine.itemAt( i ) == item )
return i;
}
}
return -1;
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::removeAt( int index )
2017-07-21 18:21:34 +02:00
{
removeItemInternal( index, false );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::removeItemInternal( int index, bool unparent )
2017-07-21 18:21:34 +02:00
{
auto& engine = m_data->engine;
if ( index < 0 || index >= engine.count() )
return;
2017-07-21 18:21:34 +02:00
auto item = engine.itemAt( index );
engine.removeAt( index );
2017-07-21 18:21:34 +02:00
if ( item )
2017-07-21 18:21:34 +02:00
{
qskSetItemActive( this, engine.itemAt( index ), false );
2017-07-21 18:21:34 +02:00
if ( !unparent )
{
if ( item->parentItem() == this )
item->setParentItem( nullptr );
}
}
2017-07-21 18:21:34 +02:00
resetImplicitSize();
polish();
}
2017-07-21 18:21:34 +02:00
void QskLinearBox::removeItem( const QQuickItem* item )
{
removeAt( indexOf( item ) );
}
2017-07-21 18:21:34 +02:00
void QskLinearBox::clear( bool autoDelete )
{
auto& engine = m_data->engine;
2017-07-21 18:21:34 +02:00
// do we have visible entries
const bool hasVisibleEntries = engine.rowCount() > 0;
2017-07-21 18:21:34 +02:00
for ( int i = engine.count() - 1; i >= 0; i-- )
{
auto item = engine.itemAt( i );
engine.removeAt( i );
2017-07-21 18:21:34 +02:00
if( item )
{
qskSetItemActive( this, item, false );
2017-07-21 18:21:34 +02:00
if( autoDelete && ( item->parent() == this ) )
delete item;
else
item->setParentItem( nullptr );
2017-07-21 18:21:34 +02:00
}
}
if ( hasVisibleEntries )
resetImplicitSize();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::autoAddItem( QQuickItem* item )
2017-07-21 18:21:34 +02:00
{
insertItem( -1, item );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::autoRemoveItem( QQuickItem* item )
2017-07-21 18:21:34 +02:00
{
removeItemInternal( indexOf( item ), true );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::activate()
2017-07-21 18:21:34 +02:00
{
polish();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::invalidate()
2017-07-21 18:21:34 +02:00
{
m_data->engine.invalidate();
2017-07-21 18:21:34 +02:00
resetImplicitSize();
polish();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::updateLayout()
2017-07-21 18:21:34 +02:00
{
2019-06-23 12:53:38 +02:00
if ( !maybeUnresized() )
m_data->engine.setGeometries( layoutRect() );
2017-07-21 18:21:34 +02:00
}
QSizeF QskLinearBox::contentsSizeHint() const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.sizeHint( Qt::PreferredSize, QSizeF() );
2017-07-21 18:21:34 +02:00
}
qreal QskLinearBox::heightForWidth( qreal width ) const
2017-07-21 18:21:34 +02:00
{
auto constrainedHeight =
[this]( QskLayoutConstraint::Type, const QskControl*, qreal width )
{
return m_data->engine.heightForWidth( width );
};
2017-07-21 18:21:34 +02:00
return QskLayoutConstraint::constrainedMetric(
QskLayoutConstraint::HeightForWidth, this, width, constrainedHeight );
}
2017-07-21 18:21:34 +02:00
qreal QskLinearBox::widthForHeight( qreal height ) const
{
auto constrainedWidth =
[this]( QskLayoutConstraint::Type, const QskControl*, qreal height )
{
return m_data->engine.widthForHeight( height );
};
2017-07-21 18:21:34 +02:00
return QskLayoutConstraint::constrainedMetric(
QskLayoutConstraint::WidthForHeight, this, height, constrainedWidth );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::geometryChangeEvent( QskGeometryChangeEvent* event )
2017-07-21 18:21:34 +02:00
{
Inherited::geometryChangeEvent( event );
if ( event->isResized() )
polish();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::itemChange( ItemChange change, const ItemChangeData& value )
2017-07-21 18:21:34 +02:00
{
Inherited::itemChange( change, value );
#if 1
if ( change == QQuickItem::ItemVisibleHasChanged )
{
// when becoming visible we should run into polish anyway
if ( value.boolValue )
polish();
}
#endif
2017-07-21 18:21:34 +02:00
}
bool QskLinearBox::event( QEvent* event )
2017-07-21 18:21:34 +02:00
{
switch ( event->type() )
2017-07-21 18:21:34 +02:00
{
case QEvent::LayoutRequest:
2017-07-21 18:21:34 +02:00
{
invalidate();
break;
2017-07-21 18:21:34 +02:00
}
case QEvent::LayoutDirectionChange:
{
m_data->engine.setVisualDirection(
layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight );
2017-07-21 18:21:34 +02:00
polish();
break;
}
case QEvent::ContentsRectChange:
{
polish();
break;
}
default:
break;
}
2017-07-21 18:21:34 +02:00
return Inherited::event( event );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::setDimension( uint dimension )
2017-07-21 18:21:34 +02:00
{
if ( dimension < 1 )
dimension = 1;
2017-07-21 18:21:34 +02:00
auto& engine = m_data->engine;
2017-07-21 18:21:34 +02:00
if ( dimension != engine.dimension() )
2017-07-21 18:21:34 +02:00
{
engine.setDimension( dimension );
2017-07-21 18:21:34 +02:00
polish();
resetImplicitSize();
2017-07-21 18:21:34 +02:00
Q_EMIT dimensionChanged();
}
2017-07-21 18:21:34 +02:00
}
uint QskLinearBox::dimension() const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.dimension();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::setOrientation( Qt::Orientation orientation )
2017-07-21 18:21:34 +02:00
{
auto& engine = m_data->engine;
2017-07-21 18:21:34 +02:00
if ( engine.orientation() != orientation )
2017-07-21 18:21:34 +02:00
{
engine.setOrientation( orientation );
polish();
resetImplicitSize();
Q_EMIT orientationChanged();
2017-07-21 18:21:34 +02:00
}
}
Qt::Orientation QskLinearBox::orientation() const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.orientation();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::transpose()
2017-07-21 18:21:34 +02:00
{
auto& engine = m_data->engine;
#if 0
#include <qendian.h>
for ( int i = 0; i < engine.itemCount(); i++ )
2017-07-21 18:21:34 +02:00
{
auto alignment = engine.alignmentAt( i );
qbswap( static_cast< quint16 >( alignment ) );
engine.setAlignmentAt( i, alignment );
2017-07-21 18:21:34 +02:00
}
// extraSpacingAt ???
#endif
if ( engine.orientation() == Qt::Horizontal )
setOrientation( Qt::Vertical );
else
setOrientation( Qt::Horizontal );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::setDefaultAlignment( Qt::Alignment alignment )
2017-07-21 18:21:34 +02:00
{
auto& engine = m_data->engine;
if ( alignment != engine.defaultAlignment() )
2017-07-21 18:21:34 +02:00
{
engine.setDefaultAlignment( alignment );
Q_EMIT defaultAlignmentChanged();
2017-07-21 18:21:34 +02:00
}
}
Qt::Alignment QskLinearBox::defaultAlignment() const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.defaultAlignment();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::setSpacing( qreal spacing )
2017-07-21 18:21:34 +02:00
{
/*
we should have setSpacing( qreal, Qt::Orientations ),
but need to create an API for Qml in QskQml
using qmlAttachedPropertiesObject then. TODO ...
*/
spacing = qMax( spacing, 0.0 );
auto& engine = m_data->engine;
if ( spacing != engine.spacing( Qt::Horizontal ) )
2017-07-21 18:21:34 +02:00
{
engine.setSpacing( spacing, Qt::Horizontal | Qt::Vertical );
polish();
Q_EMIT spacingChanged();
2017-07-21 18:21:34 +02:00
}
}
void QskLinearBox::resetSpacing()
2017-07-21 18:21:34 +02:00
{
const qreal spacing = m_data->engine.defaultSpacing( Qt::Horizontal );
setSpacing( spacing );
2017-07-21 18:21:34 +02:00
}
qreal QskLinearBox::spacing() const
2017-07-21 18:21:34 +02:00
{
// do we always want to have the same spacing for both orientations
return m_data->engine.spacing( Qt::Horizontal );
}
2017-07-21 18:21:34 +02:00
void QskLinearBox::setExtraSpacingAt( Qt::Edges edges )
{
if ( edges != m_data->engine.extraSpacingAt() )
{
m_data->engine.setExtraSpacingAt( edges );
polish();
2017-07-21 18:21:34 +02:00
Q_EMIT extraSpacingAtChanged();
}
2017-07-21 18:21:34 +02:00
}
Qt::Edges QskLinearBox::extraSpacingAt() const
2017-07-21 18:21:34 +02:00
{
return m_data->engine.extraSpacingAt();
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::addItem( QQuickItem* item, Qt::Alignment alignment )
2017-07-21 18:21:34 +02:00
{
insertItem( -1, item, alignment );
2017-07-21 18:21:34 +02:00
}
void QskLinearBox::insertItem(
int index, QQuickItem* item, Qt::Alignment alignment )
2017-07-21 18:21:34 +02:00
{
if ( item == nullptr )
return;
2017-07-21 18:21:34 +02:00
auto& engine = m_data->engine;
2017-07-21 18:21:34 +02:00
if ( item->parentItem() == this )
2017-07-21 18:21:34 +02:00
{
const int oldIndex = indexOf( item );
if ( oldIndex >= 0 )
2017-07-21 18:21:34 +02:00
{
// the item has been inserted before
2017-07-21 18:21:34 +02:00
const bool doAppend = index < 0 || index >= engine.count();
2017-07-21 18:21:34 +02:00
if ( ( index == oldIndex ) ||
( doAppend && oldIndex == engine.count() - 1 ) )
{
// already at its position, nothing to do
return;
}
2017-07-21 18:21:34 +02:00
removeAt( oldIndex );
2017-07-21 18:21:34 +02:00
}
}
reparentItem( item );
2017-07-21 18:21:34 +02:00
const int numItems = engine.count();
if ( index < 0 || index > numItems )
index = numItems;
2017-07-21 18:21:34 +02:00
engine.insertItem( item, index );
engine.setAlignmentAt( index, alignment );
2017-07-21 18:21:34 +02:00
// Re-ordering the child items to have a a proper focus tab chain
2017-07-21 18:21:34 +02:00
bool reordered = false;
2017-07-21 18:21:34 +02:00
if ( index < engine.count() - 1 )
2017-07-21 18:21:34 +02:00
{
for ( int i = index; i < engine.count(); i++ )
2017-07-21 18:21:34 +02:00
{
if ( auto nextItem = engine.itemAt( i ) )
2017-07-21 18:21:34 +02:00
{
item->stackBefore( nextItem );
reordered = true;
2017-07-21 18:21:34 +02:00
break;
}
}
}
2017-07-21 18:21:34 +02:00
if ( !reordered )
{
const auto children = childItems();
if ( item != children.last() )
item->stackAfter( children.last() );
}
2017-07-21 18:21:34 +02:00
qskSetItemActive( this, item, true );
#if 1
// Is there a way to block consecutive calls ???
resetImplicitSize();
polish();
#endif
}
void QskLinearBox::addSpacer( qreal spacing, int stretchFactor )
{
insertSpacer( -1, spacing, stretchFactor );
}
void QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor )
{
auto& engine = m_data->engine;
const int numItems = engine.count();
if ( index < 0 || index > numItems )
index = numItems;
engine.insertSpacerAt( index, spacing );
stretchFactor = qMax( stretchFactor, 0 );
engine.setStretchFactorAt( index, stretchFactor );
#if 1
// Is there a way to block consecutive calls ???
resetImplicitSize();
polish();
#endif
}
void QskLinearBox::addStretch( int stretchFactor )
{
insertSpacer( -1, 0, stretchFactor );
}
void QskLinearBox::insertStretch( int index, int stretchFactor )
{
insertSpacer( index, 0, stretchFactor );
}
void QskLinearBox::setAlignment( int index, Qt::Alignment alignment )
{
if ( alignment != m_data->engine.alignmentAt( index ) )
{
m_data->engine.setAlignmentAt( index, alignment );
polish();
2017-07-21 18:21:34 +02:00
}
}
2017-07-21 18:21:34 +02:00
Qt::Alignment QskLinearBox::alignment( int index ) const
{
return m_data->engine.alignmentAt( index );
}
void QskLinearBox::setAlignment( const QQuickItem* item, Qt::Alignment alignment )
{
setAlignment( indexOf( item ), alignment );
}
Qt::Alignment QskLinearBox::alignment( const QQuickItem* item ) const
{
return alignment( indexOf( item ) );
}
void QskLinearBox::setStretchFactor( int index, int stretchFactor )
{
auto& engine = m_data->engine;
if ( engine.stretchFactorAt( index ) != stretchFactor )
2017-07-21 18:21:34 +02:00
{
engine.setStretchFactorAt( index, stretchFactor );
polish();
}
}
2017-07-21 18:21:34 +02:00
int QskLinearBox::stretchFactor( int index ) const
{
return m_data->engine.stretchFactorAt( index );
}
2017-07-21 18:21:34 +02:00
void QskLinearBox::setStretchFactor( const QQuickItem* item, int stretch )
{
setStretchFactor( indexOf( item ), stretch );
}
2017-07-21 18:21:34 +02:00
int QskLinearBox::stretchFactor( const QQuickItem* item ) const
{
return stretchFactor( indexOf( item ) );
}
2017-07-21 18:21:34 +02:00
void QskLinearBox::setRetainSizeWhenHidden( int index, bool on )
{
auto& engine = m_data->engine;
if ( engine.retainSizeWhenHiddenAt( index ) != on )
{
engine.setRetainSizeWhenHiddenAt( index, on );
resetImplicitSize();
polish();
2017-07-21 18:21:34 +02:00
}
}
bool QskLinearBox::retainSizeWhenHidden( int index ) const
{
return m_data->engine.retainSizeWhenHiddenAt( index );
}
void QskLinearBox::setRetainSizeWhenHidden( const QQuickItem* item, bool on )
{
setRetainSizeWhenHidden( indexOf( item ), on );
}
2017-07-21 18:21:34 +02:00
bool QskLinearBox::retainSizeWhenHidden( const QQuickItem* item ) const
{
return retainSizeWhenHidden( indexOf( item ) );
2017-07-21 18:21:34 +02:00
}
#include "moc_QskLinearBox.cpp"