2023-02-14 07:58:37 +00:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2023 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "QskComboBox.h"
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
#include "QskGraphicProvider.h"
|
2023-02-14 07:58:37 +00:00
|
|
|
#include "QskGraphic.h"
|
|
|
|
#include "QskMenu.h"
|
|
|
|
#include "QskTextOptions.h"
|
2023-03-06 09:44:00 +00:00
|
|
|
#include "QskEvent.h"
|
2023-02-14 07:58:37 +00:00
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
#include <qpointer.h>
|
2023-03-06 06:47:49 +00:00
|
|
|
#include <qquickwindow.h>
|
|
|
|
|
2023-02-14 07:58:37 +00:00
|
|
|
QSK_SUBCONTROL( QskComboBox, Panel )
|
2023-03-08 13:11:27 +00:00
|
|
|
QSK_SUBCONTROL( QskComboBox, Icon )
|
2023-02-14 07:58:37 +00:00
|
|
|
QSK_SUBCONTROL( QskComboBox, Text )
|
2023-03-08 13:11:27 +00:00
|
|
|
QSK_SUBCONTROL( QskComboBox, StatusIndicator )
|
2023-02-14 07:58:37 +00:00
|
|
|
|
2023-03-06 09:44:00 +00:00
|
|
|
QSK_SYSTEM_STATE( QskComboBox, PopupOpen, QskAspect::FirstSystemState << 1 )
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
static inline void qskTraverseOptions( QskComboBox* comboBox, int steps )
|
2023-03-06 16:37:32 +00:00
|
|
|
{
|
|
|
|
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;
|
2023-03-07 12:26:36 +00:00
|
|
|
|
2023-03-06 16:37:32 +00:00
|
|
|
if ( nextIndex != index )
|
2023-03-07 12:26:36 +00:00
|
|
|
comboBox->setCurrentIndex( nextIndex );
|
|
|
|
}
|
|
|
|
|
2023-03-07 13:32:53 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
class Option
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Option( const QskGraphic& graphic, const QString& text )
|
|
|
|
: text( text )
|
|
|
|
, graphic( graphic )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Option( const QUrl& graphicSource, const QString& text )
|
|
|
|
: graphicSource( graphicSource )
|
|
|
|
, text( text )
|
|
|
|
{
|
|
|
|
#if 1
|
|
|
|
// lazy loading TODO ...
|
|
|
|
if( !graphicSource.isEmpty() )
|
|
|
|
graphic = Qsk::loadGraphic( graphicSource );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl graphicSource;
|
|
|
|
QString text;
|
|
|
|
|
|
|
|
QskGraphic graphic;
|
|
|
|
};
|
2023-03-06 16:37:32 +00:00
|
|
|
}
|
|
|
|
|
2023-02-14 07:58:37 +00:00
|
|
|
class QskComboBox::PrivateData
|
|
|
|
{
|
|
|
|
public:
|
2023-03-07 14:00:33 +00:00
|
|
|
QPointer < QskMenu > menu;
|
2023-02-14 07:58:37 +00:00
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
QVector< Option > options;
|
2023-03-03 18:01:40 +00:00
|
|
|
QString placeholderText;
|
2023-03-06 16:37:32 +00:00
|
|
|
|
|
|
|
int currentIndex = -1;
|
2023-02-14 07:58:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
QskComboBox::QskComboBox( QQuickItem* parent )
|
|
|
|
: Inherited( parent )
|
2023-03-07 12:26:36 +00:00
|
|
|
, m_data( new PrivateData() )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
|
|
|
initSizePolicy( QskSizePolicy::Minimum, QskSizePolicy::Fixed );
|
|
|
|
|
|
|
|
setPolishOnResize( true );
|
|
|
|
|
|
|
|
setAcceptedMouseButtons( Qt::LeftButton );
|
|
|
|
setWheelEnabled( true );
|
|
|
|
setFocusPolicy( Qt::StrongFocus );
|
|
|
|
|
|
|
|
setAcceptHoverEvents( true );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
QskComboBox::~QskComboBox()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::setPopupOpen( bool on )
|
|
|
|
{
|
|
|
|
if ( on == isPopupOpen() )
|
|
|
|
return;
|
|
|
|
|
2023-03-06 09:44:00 +00:00
|
|
|
setSkinStateFlag( PopupOpen, on );
|
|
|
|
|
2023-02-14 07:58:37 +00:00
|
|
|
if( on )
|
|
|
|
openPopup();
|
|
|
|
else
|
|
|
|
closePopup();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QskComboBox::isPopupOpen() const
|
|
|
|
{
|
|
|
|
return hasSkinState( PopupOpen );
|
|
|
|
}
|
|
|
|
|
2023-03-08 13:11:27 +00:00
|
|
|
QskGraphic QskComboBox::icon() const
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-06 16:37:32 +00:00
|
|
|
if( m_data->currentIndex >= 0 )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-06 16:37:32 +00:00
|
|
|
const auto option = optionAt( m_data->currentIndex );
|
2023-02-14 07:58:37 +00:00
|
|
|
return option.at( 0 ).value< QskGraphic >();
|
|
|
|
}
|
2023-03-03 18:01:40 +00:00
|
|
|
|
|
|
|
return QskGraphic();
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::setTextOptions( const QskTextOptions& textOptions )
|
|
|
|
{
|
|
|
|
setTextOptionsHint( Text, textOptions );
|
|
|
|
}
|
|
|
|
|
|
|
|
QskTextOptions QskComboBox::textOptions() const
|
|
|
|
{
|
|
|
|
return textOptionsHint( Text );
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
void QskComboBox::addOption( const QString& text )
|
|
|
|
{
|
|
|
|
addOption( QUrl(), text );
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::addOption( const QskGraphic& graphic, const QString& text )
|
|
|
|
{
|
|
|
|
m_data->options += Option( graphic, text );
|
|
|
|
|
|
|
|
resetImplicitSize();
|
|
|
|
update();
|
|
|
|
|
|
|
|
if ( isComponentComplete() )
|
|
|
|
Q_EMIT countChanged( count() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::addOption( const QString& graphicSource, const QString& text )
|
|
|
|
{
|
|
|
|
addOption( QUrl( graphicSource ), text );
|
|
|
|
}
|
|
|
|
|
2023-02-14 07:58:37 +00:00
|
|
|
void QskComboBox::addOption( const QUrl& graphicSource, const QString& text )
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
m_data->options += Option( graphicSource, text );
|
|
|
|
|
|
|
|
resetImplicitSize();
|
|
|
|
update();
|
|
|
|
|
|
|
|
if ( isComponentComplete() )
|
|
|
|
Q_EMIT countChanged( count() );
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariantList QskComboBox::optionAt( int index ) const
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
const auto& options = m_data->options;
|
|
|
|
|
|
|
|
if( index < 0 || index >= options.count() )
|
|
|
|
return QVariantList();
|
|
|
|
|
|
|
|
const auto& option = options[ index ];
|
|
|
|
|
|
|
|
QVariantList list;
|
|
|
|
list += QVariant::fromValue( option.graphic );
|
|
|
|
list += QVariant::fromValue( option.text );
|
|
|
|
|
|
|
|
return list;
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 18:01:40 +00:00
|
|
|
QString QskComboBox::placeholderText() const
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-03 18:01:40 +00:00
|
|
|
return m_data->placeholderText;
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 18:01:40 +00:00
|
|
|
void QskComboBox::setPlaceholderText( const QString& text )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-03 18:01:40 +00:00
|
|
|
if ( m_data->placeholderText == text )
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_data->placeholderText = text;
|
|
|
|
resetImplicitSize();
|
|
|
|
|
2023-03-06 16:37:32 +00:00
|
|
|
if ( m_data->currentIndex < 0 )
|
2023-03-03 18:01:40 +00:00
|
|
|
update();
|
|
|
|
|
|
|
|
Q_EMIT placeholderTextChanged( text );
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-07 13:32:53 +00:00
|
|
|
QString QskComboBox::textAt( int index ) const
|
|
|
|
{
|
|
|
|
if ( index >= 0 && index < m_data->options.count() )
|
|
|
|
return m_data->options[ index ].text;
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2023-03-06 09:44:00 +00:00
|
|
|
QString QskComboBox::currentText() const
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-06 16:37:32 +00:00
|
|
|
if( m_data->currentIndex >= 0 )
|
2023-03-07 13:32:53 +00:00
|
|
|
return m_data->options[ m_data->currentIndex ].text;
|
2023-03-03 18:01:40 +00:00
|
|
|
|
2023-03-07 13:32:53 +00:00
|
|
|
return m_data->placeholderText;
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::openPopup()
|
|
|
|
{
|
2023-03-07 14:00:33 +00:00
|
|
|
/*
|
|
|
|
maybe we should implement an alternative implementation
|
|
|
|
using a QskSelectionDialog, that could be en/disabled
|
|
|
|
by setting a mode TODO ...
|
|
|
|
*/
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
if ( m_data->menu )
|
|
|
|
return;
|
|
|
|
|
2023-03-06 06:47:49 +00:00
|
|
|
const auto cr = contentsRect();
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
auto menu = new QskMenu();
|
2023-03-06 06:47:49 +00:00
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
menu->setParent( this );
|
2023-03-06 06:47:49 +00:00
|
|
|
menu->setParentItem( window()->contentItem() );
|
2023-03-07 12:26:36 +00:00
|
|
|
menu->setPopupFlag( QskPopup::DeleteOnClose, true );
|
|
|
|
|
2023-03-06 06:47:49 +00:00
|
|
|
menu->setOrigin( mapToScene( cr.bottomLeft() ) );
|
|
|
|
menu->setFixedWidth( cr.width() );
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
for ( const auto& option : m_data->options )
|
|
|
|
menu->addOption( option.graphic, option.text );
|
|
|
|
|
2023-03-07 14:00:33 +00:00
|
|
|
connect( menu, &QskMenu::currentIndexChanged,
|
|
|
|
this, &QskComboBox::indexInPopupChanged );
|
|
|
|
|
2023-03-07 12:26:36 +00:00
|
|
|
connect( menu, &QskMenu::triggered,
|
|
|
|
this, &QskComboBox::setCurrentIndex );
|
|
|
|
|
|
|
|
connect( menu, &QskMenu::closed, this,
|
|
|
|
[ this ]() { setPopupOpen( false ); setFocus( true ); } );
|
|
|
|
|
|
|
|
m_data->menu = menu;
|
2023-03-06 06:47:49 +00:00
|
|
|
menu->open();
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::closePopup()
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
if ( m_data->menu )
|
|
|
|
m_data->menu->close();
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-06 06:47:49 +00:00
|
|
|
void QskComboBox::mousePressEvent( QMouseEvent* )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-06 09:44:00 +00:00
|
|
|
setPopupOpen( true );
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 18:01:40 +00:00
|
|
|
void QskComboBox::mouseReleaseEvent( QMouseEvent* )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::keyPressEvent( QKeyEvent* event )
|
|
|
|
{
|
2023-03-06 11:26:38 +00:00
|
|
|
if ( qskIsButtonPressKey( event ) )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-06 09:44:00 +00:00
|
|
|
if ( !event->isAutoRepeat() )
|
2023-02-14 07:58:37 +00:00
|
|
|
{
|
2023-03-06 09:44:00 +00:00
|
|
|
setPopupOpen( true );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2023-02-14 07:58:37 +00:00
|
|
|
|
2023-03-06 09:44:00 +00:00
|
|
|
switch( event->key() )
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
case Qt::Key_F4:
|
|
|
|
{
|
|
|
|
// QComboBox does this ???
|
|
|
|
setPopupOpen( true );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
case Qt::Key_Up:
|
|
|
|
case Qt::Key_PageUp:
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
qskTraverseOptions( this, -1 );
|
2023-03-06 09:44:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
case Qt::Key_Down:
|
|
|
|
case Qt::Key_PageDown:
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
qskTraverseOptions( this, 1 );
|
2023-03-06 09:44:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
case Qt::Key_Home:
|
|
|
|
{
|
|
|
|
if ( count() > 0 )
|
|
|
|
setCurrentIndex( 0 );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case Qt::Key_End:
|
|
|
|
{
|
|
|
|
if ( count() > 0 )
|
|
|
|
setCurrentIndex( count() - 1 );
|
2023-02-14 07:58:37 +00:00
|
|
|
return;
|
|
|
|
}
|
2023-03-06 09:44:00 +00:00
|
|
|
|
|
|
|
default:
|
2023-03-07 13:32:53 +00:00
|
|
|
{
|
|
|
|
const int index = qskFindOption( this, event->text() );
|
|
|
|
if ( index >= 0 )
|
|
|
|
{
|
|
|
|
setCurrentIndex( index );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Inherited::keyPressEvent( event );
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::keyReleaseEvent( QKeyEvent* event )
|
|
|
|
{
|
|
|
|
Inherited::keyReleaseEvent( event );
|
|
|
|
}
|
|
|
|
|
2023-03-06 09:44:00 +00:00
|
|
|
void QskComboBox::wheelEvent( QWheelEvent* event )
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
if ( isPopupOpen() )
|
|
|
|
{
|
|
|
|
if ( m_data->menu )
|
2023-03-07 12:52:03 +00:00
|
|
|
{
|
|
|
|
#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 );
|
|
|
|
}
|
2023-03-07 12:26:36 +00:00
|
|
|
}
|
|
|
|
else
|
2023-03-06 16:37:32 +00:00
|
|
|
{
|
|
|
|
const auto steps = -qRound( qskWheelSteps( event ) );
|
2023-03-07 12:26:36 +00:00
|
|
|
qskTraverseOptions( this, steps );
|
2023-03-06 16:37:32 +00:00
|
|
|
}
|
2023-03-06 09:44:00 +00:00
|
|
|
}
|
|
|
|
|
2023-02-14 07:58:37 +00:00
|
|
|
void QskComboBox::clear()
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
if ( !m_data->options.isEmpty() )
|
|
|
|
{
|
|
|
|
m_data->options.clear();
|
|
|
|
|
|
|
|
if ( isComponentComplete() )
|
|
|
|
Q_EMIT countChanged( count() );
|
|
|
|
}
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskComboBox::setCurrentIndex( int index )
|
|
|
|
{
|
2023-03-06 16:37:32 +00:00
|
|
|
if ( m_data->currentIndex != index )
|
|
|
|
{
|
|
|
|
m_data->currentIndex = index;
|
|
|
|
update();
|
|
|
|
|
|
|
|
Q_EMIT currentIndexChanged( index );
|
|
|
|
}
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int QskComboBox::currentIndex() const
|
|
|
|
{
|
2023-03-06 16:37:32 +00:00
|
|
|
return m_data->currentIndex;
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int QskComboBox::count() const
|
|
|
|
{
|
2023-03-07 12:26:36 +00:00
|
|
|
return m_data->options.count();
|
2023-02-14 07:58:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-07 14:00:33 +00:00
|
|
|
int QskComboBox::indexInPopup() const
|
|
|
|
{
|
|
|
|
if ( m_data->menu )
|
|
|
|
return m_data->menu->currentIndex();
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-02-14 07:58:37 +00:00
|
|
|
#include "moc_QskComboBox.cpp"
|