450 lines
9.9 KiB
C++
450 lines
9.9 KiB
C++
/******************************************************************************
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
*****************************************************************************/
|
|
|
|
#include "QskStackBox.h"
|
|
#include "QskStackBoxAnimator.h"
|
|
#include "QskEvent.h"
|
|
#include "QskQuick.h"
|
|
|
|
#include <QPointer>
|
|
|
|
class QskStackBox::PrivateData
|
|
{
|
|
public:
|
|
QVector< QQuickItem* > items;
|
|
QPointer< QskStackBoxAnimator > animator;
|
|
|
|
int currentIndex = -1;
|
|
Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter;
|
|
};
|
|
|
|
QskStackBox::QskStackBox( QQuickItem* parent )
|
|
: QskStackBox( false, parent )
|
|
{
|
|
}
|
|
|
|
QskStackBox::QskStackBox( bool autoAddChildren, QQuickItem* parent )
|
|
: QskIndexedLayoutBox( parent )
|
|
, m_data( new PrivateData() )
|
|
{
|
|
setAutoAddChildren( autoAddChildren );
|
|
}
|
|
|
|
QskStackBox::~QskStackBox()
|
|
{
|
|
}
|
|
|
|
void QskStackBox::setDefaultAlignment( Qt::Alignment alignment )
|
|
{
|
|
if ( alignment != m_data->defaultAlignment )
|
|
{
|
|
m_data->defaultAlignment = alignment;
|
|
Q_EMIT defaultAlignmentChanged( alignment );
|
|
|
|
polish();
|
|
}
|
|
}
|
|
|
|
Qt::Alignment QskStackBox::defaultAlignment() const
|
|
{
|
|
return m_data->defaultAlignment;
|
|
}
|
|
|
|
void QskStackBox::setAnimator( QskStackBoxAnimator* animator )
|
|
{
|
|
if ( m_data->animator == animator )
|
|
return;
|
|
|
|
if ( m_data->animator )
|
|
{
|
|
m_data->animator->stop();
|
|
delete m_data->animator;
|
|
}
|
|
|
|
if ( animator )
|
|
{
|
|
animator->stop();
|
|
animator->setParent( this );
|
|
}
|
|
|
|
m_data->animator = animator;
|
|
}
|
|
|
|
const QskStackBoxAnimator* QskStackBox::animator() const
|
|
{
|
|
return m_data->animator;
|
|
}
|
|
|
|
QskStackBoxAnimator* QskStackBox::animator()
|
|
{
|
|
return m_data->animator;
|
|
}
|
|
|
|
QskStackBoxAnimator* QskStackBox::effectiveAnimator()
|
|
{
|
|
if ( m_data->animator )
|
|
return m_data->animator;
|
|
|
|
// getting an animation from the skin. TODO ...
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int QskStackBox::itemCount() const
|
|
{
|
|
return m_data->items.count();
|
|
}
|
|
|
|
QQuickItem* QskStackBox::itemAtIndex( int index ) const
|
|
{
|
|
return m_data->items.value( index );
|
|
}
|
|
|
|
int QskStackBox::indexOf( const QQuickItem* item ) const
|
|
{
|
|
if ( item && ( item->parentItem() == this ) )
|
|
{
|
|
for ( int i = 0; i < m_data->items.count(); i++ )
|
|
{
|
|
if ( item == m_data->items[i] )
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
QQuickItem* QskStackBox::currentItem() const
|
|
{
|
|
return itemAtIndex( m_data->currentIndex );
|
|
}
|
|
|
|
int QskStackBox::currentIndex() const
|
|
{
|
|
return m_data->currentIndex;
|
|
}
|
|
|
|
void QskStackBox::setCurrentIndex( int index )
|
|
{
|
|
if ( index < 0 || index >= itemCount() )
|
|
{
|
|
// hide the current item
|
|
index = -1;
|
|
}
|
|
|
|
if ( index == m_data->currentIndex )
|
|
return;
|
|
|
|
// stop and complete the running transition
|
|
auto animator = effectiveAnimator();
|
|
if ( animator )
|
|
animator->stop();
|
|
|
|
if ( window() && isVisible() && isInitiallyPainted() && animator )
|
|
{
|
|
// start the animation
|
|
animator->setStartIndex( m_data->currentIndex );
|
|
animator->setEndIndex( index );
|
|
animator->setWindow( window() );
|
|
animator->start();
|
|
}
|
|
else
|
|
{
|
|
auto item1 = itemAtIndex( m_data->currentIndex );
|
|
auto item2 = itemAtIndex( index );
|
|
|
|
if ( item1 )
|
|
item1->setVisible( false );
|
|
|
|
if ( item2 )
|
|
item2->setVisible( true );
|
|
}
|
|
|
|
m_data->currentIndex = index;
|
|
polish();
|
|
|
|
Q_EMIT currentIndexChanged( m_data->currentIndex );
|
|
}
|
|
|
|
void QskStackBox::setCurrentItem( const QQuickItem* item )
|
|
{
|
|
setCurrentIndex( indexOf( item ) );
|
|
}
|
|
|
|
void QskStackBox::addItem( QQuickItem* item )
|
|
{
|
|
insertItem( -1, item );
|
|
}
|
|
|
|
void QskStackBox::addItem( QQuickItem* item, Qt::Alignment alignment )
|
|
{
|
|
insertItem( -1, item, alignment );
|
|
}
|
|
|
|
void QskStackBox::insertItem( int index, QQuickItem* item )
|
|
{
|
|
if ( item == nullptr || item == this )
|
|
return;
|
|
|
|
reparentItem( item );
|
|
|
|
if ( qskIsTransparentForPositioner( item ) )
|
|
{
|
|
// giving a warning, or ignoring the insert ???
|
|
qskSetTransparentForPositioner( item, false );
|
|
}
|
|
|
|
const bool doAppend = ( index < 0 ) || ( index >= itemCount() );
|
|
|
|
if ( item->parentItem() == this )
|
|
{
|
|
const int oldIndex = indexOf( item );
|
|
if ( oldIndex >= 0 )
|
|
{
|
|
// the item had been inserted before
|
|
|
|
if ( ( index == oldIndex ) || ( doAppend && ( oldIndex == itemCount() - 1 ) ) )
|
|
{
|
|
// already in place
|
|
return;
|
|
}
|
|
|
|
m_data->items.removeAt( oldIndex );
|
|
}
|
|
}
|
|
|
|
if ( doAppend )
|
|
index = itemCount();
|
|
|
|
m_data->items.insert( index, item );
|
|
|
|
const int oldCurrentIndex = m_data->currentIndex;
|
|
|
|
if ( m_data->items.count() == 1 )
|
|
{
|
|
m_data->currentIndex = 0;
|
|
item->setVisible( true );
|
|
}
|
|
else
|
|
{
|
|
item->setVisible( false );
|
|
|
|
if ( index <= m_data->currentIndex )
|
|
m_data->currentIndex++;
|
|
}
|
|
|
|
if ( oldCurrentIndex != m_data->currentIndex )
|
|
Q_EMIT currentIndexChanged( m_data->currentIndex );
|
|
|
|
resetImplicitSize();
|
|
polish();
|
|
}
|
|
|
|
void QskStackBox::insertItem(
|
|
int index, QQuickItem* item, Qt::Alignment alignment )
|
|
{
|
|
if ( auto control = qskControlCast( item ) )
|
|
control->setLayoutAlignmentHint( alignment );
|
|
|
|
insertItem( index, item );
|
|
}
|
|
|
|
void QskStackBox::removeAt( int index )
|
|
{
|
|
removeItemInternal( index, true );
|
|
}
|
|
|
|
void QskStackBox::removeItemInternal( int index, bool unparent )
|
|
{
|
|
if ( index < 0 || index >= m_data->items.count() )
|
|
return;
|
|
|
|
if ( unparent )
|
|
{
|
|
if ( auto item = m_data->items[ index ] )
|
|
unparentItem( item );
|
|
}
|
|
|
|
m_data->items.removeAt( index );
|
|
|
|
auto& currentIndex = m_data->currentIndex;
|
|
|
|
if ( index <= currentIndex )
|
|
{
|
|
currentIndex--;
|
|
|
|
if ( currentIndex < 0 && !m_data->items.isEmpty() )
|
|
currentIndex = 0;
|
|
|
|
if ( currentIndex >= 0 )
|
|
m_data->items[ currentIndex ]->setVisible( true );
|
|
|
|
Q_EMIT currentIndexChanged( currentIndex );
|
|
}
|
|
|
|
resetImplicitSize();
|
|
polish();
|
|
}
|
|
|
|
void QskStackBox::removeItem( const QQuickItem* item )
|
|
{
|
|
removeAt( indexOf( item ) );
|
|
}
|
|
|
|
void QskStackBox::autoAddItem( QQuickItem* item )
|
|
{
|
|
removeAt( indexOf( item ) );
|
|
}
|
|
|
|
void QskStackBox::autoRemoveItem( QQuickItem* item )
|
|
{
|
|
removeItemInternal( indexOf( item ), false );
|
|
}
|
|
|
|
void QskStackBox::clear( bool autoDelete )
|
|
{
|
|
for ( const auto item : qskAsConst( m_data->items ) )
|
|
{
|
|
if( autoDelete && ( item->parent() == this ) )
|
|
delete item;
|
|
else
|
|
item->setParentItem( nullptr );
|
|
}
|
|
|
|
m_data->items.clear();
|
|
|
|
if ( m_data->currentIndex >= 0 )
|
|
{
|
|
m_data->currentIndex = -1;
|
|
Q_EMIT currentIndexChanged( m_data->currentIndex );
|
|
}
|
|
}
|
|
|
|
QRectF QskStackBox::geometryForItemAt( int index ) const
|
|
{
|
|
const auto r = layoutRect();
|
|
|
|
if ( const auto item = m_data->items.value( index ) )
|
|
{
|
|
auto alignment = qskLayoutAlignmentHint( item );
|
|
if ( alignment == 0 )
|
|
alignment = m_data->defaultAlignment;
|
|
|
|
return qskConstrainedItemRect( item, r, alignment );
|
|
}
|
|
|
|
return QRectF( r.x(), r.y(), 0.0, 0.0 );
|
|
}
|
|
|
|
void QskStackBox::updateLayout()
|
|
{
|
|
if ( maybeUnresized() )
|
|
return;
|
|
|
|
#if 1
|
|
// what about QskControl::LayoutOutWhenHidden
|
|
#endif
|
|
|
|
const auto index = m_data->currentIndex;
|
|
|
|
if ( index >= 0 )
|
|
{
|
|
const auto rect = geometryForItemAt( index );
|
|
qskSetItemGeometry( m_data->items[ index ], rect );
|
|
}
|
|
}
|
|
|
|
QSizeF QskStackBox::layoutSizeHint(
|
|
Qt::SizeHint which, const QSizeF& constraint ) const
|
|
{
|
|
if ( which == Qt::MaximumSize )
|
|
return QSizeF();
|
|
|
|
qreal w = -1.0;
|
|
qreal h = -1.0;
|
|
|
|
for ( const auto item : m_data->items )
|
|
{
|
|
/*
|
|
We ignore the retainSizeWhenVisible flag and include all
|
|
invisible items. Maybe we should offer a flag to control this ?
|
|
*/
|
|
const auto policy = qskSizePolicy( item );
|
|
|
|
if ( constraint.width() >= 0.0 && policy.isConstrained( Qt::Vertical ) )
|
|
{
|
|
const auto hint = qskSizeConstraint( item, which, constraint );
|
|
h = qMax( h, hint.height() );
|
|
}
|
|
else if ( constraint.height() >= 0.0 && policy.isConstrained( Qt::Horizontal ) )
|
|
{
|
|
const auto hint = qskSizeConstraint( item, which, constraint );
|
|
w = qMax( w, hint.width() );
|
|
}
|
|
else
|
|
{
|
|
const auto hint = qskSizeConstraint( item, which, QSizeF() );
|
|
|
|
w = qMax( w, hint.width() );
|
|
h = qMax( h, hint.height() );
|
|
}
|
|
}
|
|
|
|
// minimum layout hint needs to be cached TODO ...
|
|
return QSizeF( w, h );
|
|
}
|
|
|
|
bool QskStackBox::event( QEvent* event )
|
|
{
|
|
switch ( static_cast< int >( event->type() ) )
|
|
{
|
|
case QEvent::LayoutRequest:
|
|
{
|
|
resetImplicitSize();
|
|
polish();
|
|
break;
|
|
}
|
|
case QEvent::ContentsRectChange:
|
|
case QskEvent::GeometryChange:
|
|
{
|
|
polish();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Inherited::event( event );
|
|
}
|
|
|
|
void QskStackBox::dump() const
|
|
{
|
|
auto debug = qDebug();
|
|
|
|
QDebugStateSaver saver( debug );
|
|
debug.nospace();
|
|
|
|
const auto constraint = sizeConstraint();
|
|
|
|
debug << "QskStackBox"
|
|
<< " w:" << constraint.width() << " h:" << constraint.height() << '\n';
|
|
|
|
for ( int i = 0; i < m_data->items.count(); i++ )
|
|
{
|
|
const auto item = m_data->items[i];
|
|
|
|
debug << " " << i << ": ";
|
|
|
|
const auto constraint = qskSizeConstraint( item, Qt::PreferredSize );
|
|
debug << item->metaObject()->className()
|
|
<< " w:" << constraint.width() << " h:" << constraint.height();
|
|
|
|
if ( i == m_data->currentIndex )
|
|
debug << " [X]";
|
|
|
|
debug << '\n';
|
|
}
|
|
}
|
|
|
|
#include "moc_QskStackBox.cpp"
|