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"
|
2017-07-21 18:21:34 +02:00
|
|
|
#include "QskLinearBox.h"
|
|
|
|
#include "QskTabButton.h"
|
|
|
|
#include "QskTextOptions.h"
|
|
|
|
|
|
|
|
QSK_SUBCONTROL( QskTabBar, Panel )
|
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
static inline Qt::Orientation qskOrientation( int position )
|
|
|
|
{
|
|
|
|
if ( ( position == Qsk::Top ) || ( position == Qsk::Bottom ) )
|
|
|
|
return Qt::Horizontal;
|
|
|
|
else
|
|
|
|
return Qt::Vertical;
|
|
|
|
}
|
|
|
|
|
2019-03-23 09:43:15 +01:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
class ButtonBox : public QskLinearBox
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ButtonBox( Qt::Orientation orientation, QQuickItem* parent )
|
|
|
|
: QskLinearBox( orientation, parent )
|
|
|
|
{
|
|
|
|
setObjectName( QStringLiteral( "QskTabBarLayoutBox" ) );
|
|
|
|
setExtraSpacingAt( Qt::RightEdge | Qt::BottomEdge );
|
|
|
|
}
|
|
|
|
|
|
|
|
void restack( int currentIndex )
|
|
|
|
{
|
2019-04-11 14:03:20 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
2019-03-23 09:43:15 +01:00
|
|
|
|
2019-07-17 17:54:16 +02:00
|
|
|
for ( int i = 0; i < count(); i++ )
|
2019-03-23 09:43:15 +01:00
|
|
|
{
|
2019-04-11 14:03:20 +02:00
|
|
|
if ( auto button = itemAtIndex( i ) )
|
|
|
|
button->setZ( i == currentIndex ? 0.001 : 0.0 );
|
2019-03-23 09:43:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
class QskTabBar::PrivateData
|
|
|
|
{
|
2018-08-03 08:15:28 +02:00
|
|
|
public:
|
2019-04-17 16:33:17 +02:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
ButtonBox* buttonBox = nullptr;
|
|
|
|
int currentIndex = -1;
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
QskTextOptions textOptions;
|
2019-04-17 16:33:17 +02:00
|
|
|
uint position : 2;
|
2017-07-21 18:21:34 +02:00
|
|
|
};
|
|
|
|
|
2018-08-03 08:15:28 +02:00
|
|
|
QskTabBar::QskTabBar( QQuickItem* parent )
|
2019-04-17 16:33:17 +02:00
|
|
|
: QskTabBar( Qsk::Top, parent )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
QskTabBar::QskTabBar( Qsk::Position position, QQuickItem* parent )
|
2018-08-03 08:15:28 +02:00
|
|
|
: Inherited( parent )
|
2019-04-17 16:33:17 +02:00
|
|
|
, m_data( new PrivateData( position ) )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2019-03-23 09:43:15 +01:00
|
|
|
setAutoLayoutChildren( true );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
const auto orientation = qskOrientation( position );
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
if ( orientation == Qt::Horizontal )
|
2017-08-31 09:09:05 +02:00
|
|
|
initSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Fixed );
|
2017-07-21 18:21:34 +02:00
|
|
|
else
|
2017-08-31 09:09:05 +02:00
|
|
|
initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Preferred );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2019-03-23 09:43:15 +01:00
|
|
|
m_data->buttonBox = new ButtonBox( orientation, this );
|
2019-04-23 08:51:17 +02:00
|
|
|
m_data->buttonBox->setSpacing( metric( QskTabBar::Panel | QskAspect::Spacing ) );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2019-03-23 09:43:15 +01:00
|
|
|
connect( this, &QskTabBar::currentIndexChanged,
|
|
|
|
m_data->buttonBox, &ButtonBox::restack, Qt::QueuedConnection );
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QskTabBar::~QskTabBar()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
void QskTabBar::setPosition( Qsk::Position position )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2019-04-17 16:33:17 +02:00
|
|
|
if ( position == m_data->position )
|
2017-07-21 18:21:34 +02:00
|
|
|
return;
|
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
m_data->position = position;
|
|
|
|
|
|
|
|
const auto orientation = qskOrientation( position );
|
|
|
|
if ( orientation != m_data->buttonBox->orientation() )
|
|
|
|
{
|
|
|
|
setSizePolicy( sizePolicy( Qt::Vertical ), sizePolicy( Qt::Horizontal ) );
|
|
|
|
m_data->buttonBox->setOrientation( orientation );
|
|
|
|
}
|
2017-07-21 18:21:34 +02:00
|
|
|
|
|
|
|
resetImplicitSize();
|
|
|
|
|
|
|
|
for ( int i = 0; i < count(); i++ )
|
|
|
|
buttonAt( i )->update();
|
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
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
|
|
|
|
{
|
2019-04-17 16:33:17 +02:00
|
|
|
return qskOrientation( m_data->position );
|
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;
|
2019-04-17 16:33:17 +02:00
|
|
|
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 )
|
|
|
|
{
|
2019-03-23 09:43:15 +01:00
|
|
|
auto buttonBox = m_data->buttonBox;
|
|
|
|
|
2019-07-17 17:54:16 +02:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-23 09:43:15 +01:00
|
|
|
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
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
Q_EMIT countChanged( count() );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskTabBar::removeTab( int index )
|
|
|
|
{
|
2019-03-23 09:43:15 +01:00
|
|
|
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
|
|
|
{
|
2019-04-17 16:33:17 +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
|
|
|
|
2019-04-17 16:33:17 +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-09-10 17:01:47 +02:00
|
|
|
|
2019-02-13 10:27:23 +01:00
|
|
|
m_data->currentIndex = nextIndex;
|
2019-09-10 17:01:47 +02:00
|
|
|
|
2019-04-17 16:33:17 +02:00
|
|
|
Q_EMIT countChanged( count() );
|
2019-02-13 10:27:23 +01:00
|
|
|
Q_EMIT currentIndexChanged( nextIndex );
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskTabBar::clear()
|
|
|
|
{
|
|
|
|
if ( count() == 0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const int idx = currentIndex();
|
2019-03-23 09:43:15 +01:00
|
|
|
m_data->buttonBox->clear();
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2019-04-17 16:33:17 +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
|
|
|
|
{
|
2019-07-17 17:54:16 +02:00
|
|
|
return m_data->buttonBox->count();
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QskTabButton* QskTabBar::buttonAt( int position )
|
|
|
|
{
|
2019-03-23 09:43:15 +01:00
|
|
|
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
|
|
|
|
2018-01-16 20:34:54 +01:00
|
|
|
return QString();
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int QskTabBar::indexOf( QskTabButton* button ) const
|
|
|
|
{
|
2019-03-23 09:43:15 +01:00
|
|
|
return m_data->buttonBox->indexOf( 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"
|