qskinny/src/controls/QskComboBox.cpp
2023-07-04 12:54:59 +02:00

418 lines
8.9 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskComboBox.h"
#include "QskLabelData.h"
#include "QskMenu.h"
#include "QskTextOptions.h"
#include "QskEvent.h"
#include <qpointer.h>
#include <qquickwindow.h>
QSK_SUBCONTROL( QskComboBox, Panel )
QSK_SUBCONTROL( QskComboBox, Icon )
QSK_SUBCONTROL( QskComboBox, Text )
QSK_SUBCONTROL( QskComboBox, StatusIndicator )
QSK_SYSTEM_STATE( QskComboBox, Pressed, QskAspect::FirstSystemState << 1 )
QSK_SYSTEM_STATE( QskComboBox, PopupOpen, QskAspect::FirstSystemState << 2 )
static inline void qskTraverseOptions( QskComboBox* comboBox, int steps )
{
const auto count = comboBox->count();
if ( count == 0 )
return;
const int index = comboBox->currentIndex();
if ( index == -1 && steps < 0 )
steps++;
int nextIndex = ( index + steps ) % count;
if ( nextIndex < 0 )
nextIndex += count;
if ( nextIndex != index )
comboBox->setCurrentIndex( nextIndex );
}
static inline int qskFindOption( QskComboBox* comboBox, const QString& key )
{
if ( !key.isEmpty() )
{
const int currentIndex = comboBox->currentIndex();
const int count = comboBox->count();
for ( int i = 1; i < count; i++ )
{
const int index = ( currentIndex + i ) % count;
const auto text = comboBox->textAt( index );
if ( text.startsWith( key ) )
return index;
}
}
return -1;
}
class QskComboBox::PrivateData
{
public:
QPointer < QskMenu > menu;
QVector< QskLabelData > options;
QString placeholderText;
int currentIndex = -1;
};
QskComboBox::QskComboBox( QQuickItem* parent )
: Inherited( parent )
, m_data( new PrivateData() )
{
initSizePolicy( QskSizePolicy::Minimum, QskSizePolicy::Fixed );
setPolishOnResize( true );
setAcceptedMouseButtons( Qt::LeftButton );
setWheelEnabled( true );
setFocusPolicy( Qt::StrongFocus );
setAcceptHoverEvents( true );
}
QskComboBox::~QskComboBox()
{
}
bool QskComboBox::isPressed() const
{
return hasSkinState( Pressed );
}
void QskComboBox::setPressed( bool on )
{
if ( on == isPressed() )
return;
setSkinStateFlag( Pressed, on );
Q_EMIT pressedChanged( on );
if ( on )
Q_EMIT pressed();
else
Q_EMIT released();
}
void QskComboBox::setPopupOpen( bool on )
{
if ( on == isPopupOpen() )
return;
setSkinStateFlag( PopupOpen, on );
if( on )
openPopup();
else
closePopup();
}
bool QskComboBox::isPopupOpen() const
{
return hasSkinState( PopupOpen );
}
void QskComboBox::setTextOptions( const QskTextOptions& textOptions )
{
setTextOptionsHint( Text, textOptions );
}
QskTextOptions QskComboBox::textOptions() const
{
return textOptionsHint( Text );
}
int QskComboBox::addOption( const QString& graphicSource, const QString& text )
{
return addOption( QskLabelData( text, QskIcon( graphicSource ) ) );
}
int QskComboBox::addOption( const QUrl& graphicSource, const QString& text )
{
return addOption( QskLabelData( text, QskIcon( graphicSource ) ) );
}
int QskComboBox::addOption( const QskLabelData& option )
{
m_data->options += option;
resetImplicitSize();
update();
if ( isComponentComplete() )
Q_EMIT optionsChanged();
return count() - 1;
}
void QskComboBox::setOptions( const QStringList& options )
{
setOptions( qskCreateLabelData( options ) );
}
void QskComboBox::setOptions( const QVector< QskLabelData >& options )
{
if ( options == m_data->options )
return;
m_data->options = options;
m_data->currentIndex = -1; // currentIndexChanged ???
resetImplicitSize();
update();
Q_EMIT optionsChanged();
}
QVector< QskLabelData > QskComboBox::options() const
{
return m_data->options;
}
QskLabelData QskComboBox::optionAt( int index ) const
{
return m_data->options.value( index );
}
QString QskComboBox::placeholderText() const
{
return m_data->placeholderText;
}
void QskComboBox::setPlaceholderText( const QString& text )
{
if ( m_data->placeholderText == text )
return;
m_data->placeholderText = text;
resetImplicitSize();
if ( m_data->currentIndex < 0 )
update();
Q_EMIT placeholderTextChanged( text );
}
QString QskComboBox::textAt( int index ) const
{
return optionAt( index ).text();
}
QString QskComboBox::currentText() const
{
if( m_data->currentIndex >= 0 && m_data->currentIndex < m_data->options.count() )
return m_data->options[ m_data->currentIndex ].text();
return m_data->placeholderText;
}
void QskComboBox::openPopup()
{
/*
maybe we should implement an alternative implementation
using a QskSelectionDialog, that could be en/disabled
by setting a mode TODO ...
*/
if ( m_data->menu )
return;
const auto cr = contentsRect();
auto menu = new QskMenu();
menu->setParent( this );
menu->setParentItem( window()->contentItem() );
menu->setPopupFlag( QskPopup::DeleteOnClose, true );
menu->setOrigin( mapToScene( cr.bottomLeft() ) );
menu->setFixedWidth( cr.width() );
menu->setOptions( m_data->options );
connect( menu, &QskMenu::currentIndexChanged,
this, &QskComboBox::indexInPopupChanged );
connect( menu, &QskMenu::triggered,
this, &QskComboBox::setCurrentIndex );
connect( menu, &QskMenu::closed, this,
[ this ]() { setPopupOpen( false ); setFocus( true ); } );
m_data->menu = menu;
menu->open();
}
void QskComboBox::closePopup()
{
if ( m_data->menu )
m_data->menu->close();
}
void QskComboBox::mousePressEvent( QMouseEvent* )
{
setPressed( true );
setPopupOpen( true );
}
void QskComboBox::mouseReleaseEvent( QMouseEvent* )
{
setPressed( false );
}
void QskComboBox::keyPressEvent( QKeyEvent* event )
{
if ( qskIsButtonPressKey( event ) )
{
if ( !event->isAutoRepeat() )
{
setPressed( true );
setPopupOpen( true );
return;
}
}
switch( event->key() )
{
#if 0
case Qt::Key_F4:
{
// QComboBox does this ???
setPressed( true );
setPopupOpen( true );
return;
}
#endif
case Qt::Key_Up:
case Qt::Key_PageUp:
{
qskTraverseOptions( this, -1 );
return;
}
case Qt::Key_Down:
case Qt::Key_PageDown:
{
qskTraverseOptions( this, 1 );
return;
}
case Qt::Key_Home:
{
if ( count() > 0 )
setCurrentIndex( 0 );
return;
}
case Qt::Key_End:
{
if ( count() > 0 )
setCurrentIndex( count() - 1 );
return;
}
default:
{
const int index = qskFindOption( this, event->text() );
if ( index >= 0 )
{
setCurrentIndex( index );
return;
}
}
}
Inherited::keyPressEvent( event );
}
void QskComboBox::keyReleaseEvent( QKeyEvent* event )
{
Inherited::keyReleaseEvent( event );
}
void QskComboBox::wheelEvent( QWheelEvent* event )
{
if ( isPopupOpen() )
{
if ( m_data->menu )
{
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
auto wheelEvent = new QWheelEvent(
event->position(), event->globalPosition(),
event->pixelDelta(), event->angleDelta(),
event->buttons(), event->modifiers(),
event->phase(), event->inverted(), event->source() );
#else
auto wheelEvent = event->clone();
#endif
QCoreApplication::postEvent( m_data->menu, wheelEvent );
}
}
else
{
const auto steps = -qRound( qskWheelSteps( event ) );
qskTraverseOptions( this, steps );
}
}
void QskComboBox::clear()
{
if ( !m_data->options.isEmpty() )
{
m_data->options.clear();
Q_EMIT optionsChanged();
if ( m_data->currentIndex >= 0 )
{
m_data->currentIndex = -1;
Q_EMIT currentIndexChanged( m_data->currentIndex );
}
update();
}
}
void QskComboBox::setCurrentIndex( int index )
{
if ( m_data->currentIndex != index )
{
m_data->currentIndex = index;
update();
Q_EMIT currentIndexChanged( index );
}
}
int QskComboBox::currentIndex() const
{
return m_data->currentIndex;
}
int QskComboBox::count() const
{
return m_data->options.count();
}
int QskComboBox::indexInPopup() const
{
if ( m_data->menu )
return m_data->menu->currentIndex();
return -1;
}
#include "moc_QskComboBox.cpp"