qskinny/src/controls/QskTabBar.cpp

659 lines
16 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 "QskTabBar.h"
2018-08-03 08:15:28 +02:00
#include "QskAspect.h"
2020-03-13 07:39:31 +01:00
#include "QskScrollBox.h"
2017-07-21 18:21:34 +02:00
#include "QskLinearBox.h"
#include "QskTabButton.h"
#include "QskTextOptions.h"
2020-03-13 07:39:31 +01:00
#include "QskAnimationHint.h"
#include "QskQuick.h"
2017-07-21 18:21:34 +02:00
#include <qquickwindow.h>
2017-07-21 18:21:34 +02:00
QSK_SUBCONTROL( QskTabBar, Panel )
static inline Qt::Orientation qskOrientation( int position )
{
if ( ( position == Qsk::Top ) || ( position == Qsk::Bottom ) )
return Qt::Horizontal;
else
return Qt::Vertical;
}
2020-03-13 14:50:09 +01:00
static inline void qskTransposeSizePolicy( QskControl* control )
{
control->setSizePolicy( control->sizePolicy().transposed() );
}
namespace
{
2020-03-13 07:39:31 +01:00
class ButtonBox final : public QskLinearBox
{
2020-03-13 07:39:31 +01:00
using Inherited = QskLinearBox;
public:
ButtonBox( Qt::Orientation orientation, QQuickItem* parent )
: QskLinearBox( orientation, parent )
{
setObjectName( QStringLiteral( "QskTabBarLayoutBox" ) );
setExtraSpacingAt( Qt::RightEdge | Qt::BottomEdge );
}
void restack( int currentIndex )
{
/*
We can't simply reorder the buttons as the focus tab chain depends on it.
Again we have some QML only API ( QQuickKeyNavigationAttached ),
that is useless for C++, but nothing like QWidget::setTabOrder.
Maybe it makes sense to implement our own navigation in
QskControl::keyPressEvent.
*/
for ( int i = 0; i < count(); i++ )
{
if ( auto button = itemAtIndex( i ) )
button->setZ( i == currentIndex ? 0.001 : 0.0 );
}
}
};
2020-03-13 07:39:31 +01:00
class ScrollBox final : public QskScrollBox
{
using Inherited = QskScrollBox;
public:
ScrollBox( QQuickItem* parent )
: QskScrollBox( parent )
{
setPolishOnResize( true );
enableAutoTranslation( true );
setFocusPolicy( Qt::NoFocus );
connect( this, &ScrollBox::scrollPosChanged,
this, &QskControl::focusIndicatorRectChanged );
2020-03-13 07:39:31 +01:00
}
QRectF focusIndicatorClipRect() const override
2020-03-13 07:39:31 +01:00
{
auto r = clipRect();
if ( window() )
{
if ( auto focusItem = window()->activeFocusItem() )
{
const auto itemRect = mapRectFromItem(
focusItem, focusItem->boundingRect() );
if ( r.intersects( itemRect ) )
return QRectF();
}
}
return r;
2020-03-13 07:39:31 +01:00
}
QskAnimationHint flickHint() const override
{
if ( auto tabBar = qobject_cast< const QskTabBar* >( parentItem() ) )
{
return tabBar->effectiveAnimation( QskAspect::Metric,
QskTabBar::Panel, QskAspect::NoState );
}
return QskAnimationHint( 200, QEasingCurve::OutCubic );
}
QRectF viewContentsRect() const override
{
return layoutRect();
}
void setOrientation( Qt::Orientation orientation )
{
setFlickableOrientations( orientation );
if ( orientation == Qt::Horizontal )
setSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::MinimumExpanding );
else
setSizePolicy( QskSizePolicy::MinimumExpanding, QskSizePolicy::Ignored );
}
void ensureItemVisible( const QQuickItem* item )
{
if ( qskIsAncestorOf( this, item ) )
{
const auto pos = mapFromItem( item, QPointF() );
ensureVisible( QRectF( pos.x(), pos.y(), item->width(), item->height() ) );
}
}
protected:
bool event( QEvent* event ) override
{
if ( event->type() == QEvent::LayoutRequest )
{
resetImplicitSize();
polish();
}
return Inherited::event( event );
}
void updateLayout() override
{
2020-03-13 14:04:28 +01:00
auto box = buttonBox();
2020-03-13 07:39:31 +01:00
2020-03-13 14:04:28 +01:00
auto boxSize = viewContentsRect().size();
boxSize = qskConstrainedItemSize( box, boxSize );
2020-03-13 07:39:31 +01:00
2020-03-13 14:04:28 +01:00
if ( auto box = buttonBox() )
box->setSize( boxSize );
2020-03-13 07:39:31 +01:00
enableAutoTranslation( false );
2020-03-13 14:04:28 +01:00
setScrollableSize( boxSize );
2020-03-13 07:39:31 +01:00
setScrollPos( scrollPos() );
enableAutoTranslation( true );
2020-03-13 14:04:28 +01:00
translateButtonBox();
2020-03-13 07:39:31 +01:00
2020-03-13 14:04:28 +01:00
setClip( size().width() < boxSize.width()
|| size().height() < boxSize.height() );
2020-03-13 07:39:31 +01:00
}
QSizeF layoutSizeHint( Qt::SizeHint which, const QSizeF& constraint ) const override
{
2020-03-13 14:04:28 +01:00
auto hint = buttonBox()->sizeConstraint( which, constraint );
2020-03-13 07:39:31 +01:00
if ( which == Qt::MinimumSize )
{
if ( sizePolicy().horizontalPolicy() & QskSizePolicy::ShrinkFlag )
hint.setWidth( -1 );
if ( sizePolicy().verticalPolicy() & QskSizePolicy::ShrinkFlag )
hint.setHeight( -1 );
}
return hint;
}
private:
2020-03-13 14:04:28 +01:00
inline QskLinearBox* buttonBox() const
2020-03-13 07:39:31 +01:00
{
2020-03-13 14:04:28 +01:00
return qobject_cast< QskLinearBox* >( childItems().first() );
2020-03-13 07:39:31 +01:00
}
void enableAutoTranslation( bool on )
{
if ( on )
{
connect( this, &QskScrollBox::scrollPosChanged,
2020-03-13 14:04:28 +01:00
this, &ScrollBox::translateButtonBox );
2020-03-13 07:39:31 +01:00
}
else
{
disconnect( this, &QskScrollBox::scrollPosChanged,
2020-03-13 14:04:28 +01:00
this, &ScrollBox::translateButtonBox );
2020-03-13 07:39:31 +01:00
}
}
2020-03-13 14:04:28 +01:00
void translateButtonBox()
2020-03-13 07:39:31 +01:00
{
2020-03-13 14:04:28 +01:00
if ( auto box = buttonBox() )
2020-03-13 07:39:31 +01:00
{
const QPointF pos = viewContentsRect().topLeft() - scrollPos();
2020-03-13 14:04:28 +01:00
box->setPosition( pos );
2020-03-13 07:39:31 +01:00
}
}
};
}
2017-07-21 18:21:34 +02:00
class QskTabBar::PrivateData
{
2018-08-03 08:15:28 +02:00
public:
PrivateData( Qsk::Position position )
: position( position )
2017-07-21 18:21:34 +02:00
{
}
2019-02-13 10:27:23 +01:00
void connectButton( QskTabButton* button, QskTabBar* tabBar, bool on )
{
if ( on )
{
connect( button, &QskTabButton::toggled,
tabBar, &QskTabBar::adjustCurrentIndex, Qt::UniqueConnection );
}
else
{
disconnect( button, &QskTabButton::toggled,
tabBar, &QskTabBar::adjustCurrentIndex );
}
}
2020-03-13 07:39:31 +01:00
ScrollBox* scrollBox = nullptr;
ButtonBox* buttonBox = nullptr;
int currentIndex = -1;
2017-07-21 18:21:34 +02:00
QskTextOptions textOptions;
uint position : 2;
2017-07-21 18:21:34 +02:00
};
2018-08-03 08:15:28 +02:00
QskTabBar::QskTabBar( QQuickItem* parent )
: QskTabBar( Qsk::Top, parent )
2017-07-21 18:21:34 +02:00
{
}
QskTabBar::QskTabBar( Qsk::Position position, QQuickItem* parent )
2018-08-03 08:15:28 +02:00
: Inherited( parent )
, m_data( new PrivateData( position ) )
2017-07-21 18:21:34 +02:00
{
setAutoLayoutChildren( true );
2017-07-21 18:21:34 +02:00
const auto orientation = qskOrientation( position );
2017-07-21 18:21:34 +02:00
if ( orientation == Qt::Horizontal )
initSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Fixed );
2017-07-21 18:21:34 +02:00
else
initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Preferred );
2017-07-21 18:21:34 +02:00
2020-03-13 07:39:31 +01:00
m_data->scrollBox = new ScrollBox( this );
m_data->scrollBox->setOrientation( orientation );
m_data->buttonBox = new ButtonBox( orientation, m_data->scrollBox );
m_data->buttonBox->setSpacing( metric( QskTabBar::Panel | QskAspect::Spacing ) );
2020-03-13 07:39:31 +01:00
m_data->buttonBox->setSizePolicy( QskSizePolicy::Maximum, QskSizePolicy::Maximum );
connect( this, &QskTabBar::currentIndexChanged,
m_data->buttonBox, &ButtonBox::restack, Qt::QueuedConnection );
2017-07-21 18:21:34 +02:00
}
QskTabBar::~QskTabBar()
{
}
void QskTabBar::setPosition( Qsk::Position position )
2017-07-21 18:21:34 +02:00
{
if ( position == m_data->position )
2017-07-21 18:21:34 +02:00
return;
m_data->position = position;
const auto orientation = qskOrientation( position );
2020-03-13 14:04:28 +01:00
if ( orientation != m_data->buttonBox->orientation() )
{
2020-03-13 14:50:09 +01:00
qskTransposeSizePolicy( this );
2020-03-13 14:04:28 +01:00
m_data->buttonBox->setOrientation( orientation );
2020-03-13 14:50:09 +01:00
qskTransposeSizePolicy( m_data->buttonBox );
2020-03-13 07:39:31 +01:00
m_data->scrollBox->setOrientation( orientation );
}
2017-07-21 18:21:34 +02:00
resetImplicitSize();
for ( int i = 0; i < count(); i++ )
buttonAt( i )->update();
Q_EMIT positionChanged( position );
}
Qsk::Position QskTabBar::position() const
{
return static_cast< Qsk::Position >( m_data->position );
2017-07-21 18:21:34 +02:00
}
Qt::Orientation QskTabBar::orientation() const
{
return qskOrientation( m_data->position );
2017-07-21 18:21:34 +02:00
}
2020-03-13 07:39:31 +01:00
void QskTabBar::setAutoScrollFocusedButton( bool on )
{
if ( m_data->scrollBox->autoScrollFocusItem() != on )
{
m_data->scrollBox->setAutoScrollFocusedItem( on );
Q_EMIT autoScrollFocusedButtonChanged( on );
}
}
bool QskTabBar::autoScrollFocusButton() const
{
return m_data->scrollBox->autoScrollFocusItem();
}
2020-03-13 14:50:09 +01:00
void QskTabBar::setAutoFitTabs( bool on )
{
const auto orientation = qskOrientation( m_data->position );
int policy = m_data->buttonBox->sizePolicy( orientation );
if ( ( policy & QskSizePolicy::GrowFlag ) != on )
{
if ( on )
policy |= QskSizePolicy::GrowFlag;
else
policy &= ~QskSizePolicy::GrowFlag;
// we need operators for QskSizePolicy::Policy: TODO ...
m_data->buttonBox->setSizePolicy(
orientation, static_cast< QskSizePolicy::Policy >( policy ) );
polish();
Q_EMIT autoFitTabsChanged( on );
}
}
bool QskTabBar::autoFitTabs() const
{
const auto policy = m_data->buttonBox->sizePolicy( orientation() );
return ( policy & QskSizePolicy::GrowFlag );
}
2017-07-21 18:21:34 +02:00
void QskTabBar::setTextOptions( const QskTextOptions& options )
{
if ( options != m_data->textOptions )
{
// we should consider text options being something for
// QskControl - maybe added to the propagation system ???
m_data->textOptions = options;
Q_EMIT textOptionsChanged( options );
2017-07-21 18:21:34 +02:00
for ( int i = 0; i < count(); i++ )
buttonAt( i )->setTextOptions( options );
}
}
QskTextOptions QskTabBar::textOptions() const
{
return m_data->textOptions;
}
int QskTabBar::addTab( const QString& text )
{
return insertTab( -1, text );
}
int QskTabBar::insertTab( int index, const QString& text )
{
return insertTab( index, new QskTabButton( text ) );
}
int QskTabBar::addTab( QskTabButton* button )
{
return insertTab( -1, button );
}
int QskTabBar::insertTab( int index, QskTabButton* button )
{
auto buttonBox = m_data->buttonBox;
if ( index < 0 || index >= buttonBox->count() )
index = buttonBox->count();
2017-07-21 18:21:34 +02:00
if ( isComponentComplete() )
{
if ( count() == 0 )
{
m_data->currentIndex = 0;
button->setChecked( true );
}
}
buttonBox->insertItem( index, button );
buttonBox->restack( m_data->currentIndex );
2017-07-21 18:21:34 +02:00
if ( button->textOptions() != m_data->textOptions )
button->setTextOptions( m_data->textOptions );
2019-02-13 10:27:23 +01:00
m_data->connectButton( button, this, true );
2017-07-21 18:21:34 +02:00
Q_EMIT countChanged( count() );
2017-07-21 18:21:34 +02:00
return index;
}
void QskTabBar::removeTab( int index )
{
auto item = m_data->buttonBox->itemAtIndex( index );
2019-02-13 10:27:23 +01:00
if ( item == nullptr )
return;
delete item;
if ( index > m_data->currentIndex )
2017-07-21 18:21:34 +02:00
{
Q_EMIT countChanged( count() );
2019-02-13 10:27:23 +01:00
}
else if ( index < m_data->currentIndex )
{
m_data->currentIndex--;
2017-07-21 18:21:34 +02:00
Q_EMIT countChanged( count() );
2019-02-13 10:27:23 +01:00
Q_EMIT currentIndexChanged( m_data->currentIndex );
}
else
{
QskTabButton* nextButton = nullptr;
int nextIndex = -1;
2017-07-21 18:21:34 +02:00
2019-02-13 10:27:23 +01:00
for ( int i = m_data->currentIndex; i >= 0; i-- )
{
auto button = buttonAt( i );
if ( button && button->isEnabled() )
2017-07-21 18:21:34 +02:00
{
2019-02-13 10:27:23 +01:00
nextButton = button;
nextIndex = i;
break;
2017-07-21 18:21:34 +02:00
}
2019-02-13 10:27:23 +01:00
}
2017-07-21 18:21:34 +02:00
2019-02-13 10:27:23 +01:00
if ( nextButton == nullptr )
{
for ( int i = m_data->currentIndex + 1; i < count(); i++ )
2017-07-21 18:21:34 +02:00
{
2019-02-13 10:27:23 +01:00
auto button = buttonAt( i );
if ( button && button->isEnabled() )
2017-07-21 18:21:34 +02:00
{
2019-02-13 10:27:23 +01:00
nextButton = button;
nextIndex = i;
break;
2017-07-21 18:21:34 +02:00
}
}
2019-02-13 10:27:23 +01:00
}
2017-07-21 18:21:34 +02:00
2019-02-13 10:27:23 +01:00
if ( nextButton )
{
m_data->connectButton( nextButton, this, false );
nextButton->setChecked( true );
m_data->connectButton( nextButton, this, true );
2017-07-21 18:21:34 +02:00
}
2019-02-13 10:27:23 +01:00
m_data->currentIndex = nextIndex;
Q_EMIT countChanged( count() );
2019-02-13 10:27:23 +01:00
Q_EMIT currentIndexChanged( nextIndex );
2017-07-21 18:21:34 +02:00
}
}
2020-03-10 10:27:28 +01:00
void QskTabBar::clear( bool autoDelete )
2017-07-21 18:21:34 +02:00
{
if ( count() == 0 )
return;
const int idx = currentIndex();
2020-03-10 10:27:28 +01:00
m_data->buttonBox->clear( autoDelete );
2017-07-21 18:21:34 +02:00
Q_EMIT countChanged( count() );
2017-07-21 18:21:34 +02:00
if ( idx >= 0 )
Q_EMIT currentIndexChanged( -1 );
}
bool QskTabBar::isTabEnabled( int index ) const
{
2019-02-13 10:27:23 +01:00
const auto button = buttonAt( index );
return button ? button->isEnabled() : false;
2017-07-21 18:21:34 +02:00
}
void QskTabBar::setTabEnabled( int index, bool enabled )
{
2019-02-13 10:27:23 +01:00
if ( auto button = buttonAt( index ) )
2017-07-21 18:21:34 +02:00
{
// what happens, when it is the current button ???
2019-02-13 10:27:23 +01:00
button->setEnabled( enabled );
2017-07-21 18:21:34 +02:00
}
}
void QskTabBar::setCurrentIndex( int index )
{
if ( index != m_data->currentIndex )
{
if ( isComponentComplete() )
{
2019-02-13 10:27:23 +01:00
auto button = buttonAt( index );
if ( button && button->isEnabled() && !button->isChecked() )
button->setChecked( true );
2017-07-21 18:21:34 +02:00
}
else
{
m_data->currentIndex = index;
Q_EMIT currentIndexChanged( m_data->currentIndex );
}
}
}
int QskTabBar::currentIndex() const
{
return m_data->currentIndex;
}
int QskTabBar::count() const
{
return m_data->buttonBox->count();
2017-07-21 18:21:34 +02:00
}
QskTabButton* QskTabBar::buttonAt( int position )
{
return qobject_cast< QskTabButton* >(
m_data->buttonBox->itemAtIndex( position ) );
2017-07-21 18:21:34 +02:00
}
const QskTabButton* QskTabBar::buttonAt( int position ) const
{
2019-02-13 10:27:23 +01:00
auto that = const_cast< QskTabBar* >( this );
2017-07-21 18:21:34 +02:00
return that->buttonAt( position );
}
QskTabButton* QskTabBar::currentButton()
{
return buttonAt( currentIndex() );
}
const QskTabButton* QskTabBar::currentButton() const
{
return buttonAt( currentIndex() );
}
QString QskTabBar::currentButtonText() const
{
return buttonTextAt( currentIndex() );
}
QString QskTabBar::buttonTextAt( int index ) const
{
2019-02-13 10:27:23 +01:00
if ( const auto button = buttonAt( index ) )
return button->text();
2017-07-21 18:21:34 +02:00
return QString();
2017-07-21 18:21:34 +02:00
}
int QskTabBar::indexOf( QskTabButton* button ) const
{
return m_data->buttonBox->indexOf( button );
2017-07-21 18:21:34 +02:00
}
2020-03-13 07:39:31 +01:00
void QskTabBar::ensureButtonVisible( const QskTabButton* button )
{
m_data->scrollBox->ensureItemVisible( button );
}
2017-07-21 18:21:34 +02:00
void QskTabBar::componentComplete()
{
Inherited::componentComplete();
if ( m_data->currentIndex < 0 && count() >= 0 )
m_data->currentIndex = 0;
2019-02-13 10:27:23 +01:00
if ( auto button = buttonAt( m_data->currentIndex ) )
{
if ( button->isEnabled() && !button->isChecked() )
button->setChecked( true );
}
2017-07-21 18:21:34 +02:00
}
2019-02-13 10:27:23 +01:00
void QskTabBar::adjustCurrentIndex()
{
int index = -1;
for ( int i = 0; i < count(); i++ )
{
if ( auto button = buttonAt( i ) )
{
if ( button->isChecked() )
{
index = i;
break;
}
}
}
if ( index != m_data->currentIndex )
{
m_data->currentIndex = index;
Q_EMIT currentIndexChanged( index );
2017-07-21 18:21:34 +02:00
}
}
QskAspect::Subcontrol QskTabBar::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
{
if ( subControl == QskBox::Panel )
return QskTabBar::Panel;
return Inherited::effectiveSubcontrol( subControl );
}
2019-04-18 16:17:35 +02:00
QskAspect::Placement QskTabBar::effectivePlacement() const
{
switch ( m_data->position )
{
case Qsk::Left:
return QskAspect::Left;
case Qsk::Right:
return QskAspect::Right;
case Qsk::Top:
return QskAspect::Top;
case Qsk::Bottom:
return QskAspect::Bottom;
}
return QskAspect::NoPlacement;
}
2017-07-21 18:21:34 +02:00
#include "moc_QskTabBar.cpp"