input proxy feature added for QskInputPanel

This commit is contained in:
Uwe Rathmann 2018-04-27 16:55:50 +02:00
parent 602e3748df
commit 7fe675d74d
8 changed files with 184 additions and 44 deletions

View File

@ -138,7 +138,8 @@ public:
textInput3->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred ); textInput3->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred );
auto* textInput4 = new QskTextInput( this ); auto* textInput4 = new QskTextInput( this );
textInput4->setEchoMode( QskTextInput::PasswordEchoOnEdit ); textInput4->setEchoMode( QskTextInput::Password );
textInput4->setPasswordMaskDelay( 1000 );
textInput4->setMaxLength( 8 ); textInput4->setMaxLength( 8 );
textInput4->setText( "12345678" ); textInput4->setText( "12345678" );
textInput4->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred ); textInput4->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred );
@ -248,8 +249,10 @@ int main( int argc, char* argv[] )
QskObjectCounter counter( true ); QskObjectCounter counter( true );
#endif #endif
#if 1
qputenv( "QT_IM_MODULE", "skinny" ); qputenv( "QT_IM_MODULE", "skinny" );
qputenv( "QT_PLUGIN_PATH", STRING( PLUGIN_PATH ) ); qputenv( "QT_PLUGIN_PATH", STRING( PLUGIN_PATH ) );
#endif
QGuiApplication app( argc, argv ); QGuiApplication app( argc, argv );

View File

@ -170,17 +170,14 @@ void qskForceActiveFocus( QQuickItem* item, Qt::FocusReason reason )
} }
} }
void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries ) QQuickItem* qskInputContextItem()
{ {
if ( ( item == nullptr ) || /*
!( item->flags() & QQuickItem::ItemAcceptsInputMethod ) ) The item that is connected to the input context.
{ - often the item having the active focus.
return; */
}
auto inputMethod = QGuiApplication::inputMethod(); QQuickItem* inputItem = nullptr;
bool doUpdate = item->hasActiveFocus();
/* /*
We could also get the inputContext from QInputMethodPrivate We could also get the inputContext from QInputMethodPrivate
@ -197,17 +194,32 @@ void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries querie
without losing the connected input item without losing the connected input item
*/ */
QQuickItem* inputItem = nullptr; QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) );
}
if ( QMetaObject::invokeMethod( inputContext, "inputItem", return inputItem;
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) ) }
{
void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries )
{
if ( item == nullptr )
return;
#if 1
if ( !( item->flags() & QQuickItem::ItemAcceptsInputMethod ) )
return;
#endif
bool doUpdate;
if ( const QQuickItem* inputItem = qskInputContextItem() )
doUpdate = ( item == inputItem ); doUpdate = ( item == inputItem );
} else
} doUpdate = item->hasActiveFocus();
if ( doUpdate ) if ( doUpdate )
inputMethod->update( queries ); QGuiApplication::inputMethod()->update( queries );
} }
QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item ) QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item )

View File

@ -265,6 +265,7 @@ QSK_EXPORT void qskForceActiveFocus( QQuickItem*, Qt::FocusReason );
QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* ); QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* );
QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries ); QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries );
QSK_EXPORT QQuickItem* qskInputContextItem();
QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* );
QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* );

View File

@ -62,6 +62,12 @@ static inline void qskBindSignals( const QQuickTextInput* wrappedInput,
QObject::connect( wrappedInput, &QQuickTextInput::echoModeChanged, QObject::connect( wrappedInput, &QQuickTextInput::echoModeChanged,
input, [ input ] { input->Q_EMIT echoModeChanged( input->echoMode() ); } ); input, [ input ] { input->Q_EMIT echoModeChanged( input->echoMode() ); } );
QObject::connect( wrappedInput, &QQuickTextInput::passwordCharacterChanged,
input, &QskTextInput::passwordCharacterChanged );
QObject::connect( wrappedInput, &QQuickTextInput::passwordMaskDelayChanged,
input, &QskTextInput::passwordMaskDelayChanged );
QObject::connect( wrappedInput, &QQuickItem::implicitWidthChanged, QObject::connect( wrappedInput, &QQuickItem::implicitWidthChanged,
input, &QskControl::resetImplicitSize ); input, &QskControl::resetImplicitSize );
@ -381,22 +387,18 @@ void QskTextInput::focusInEvent( QFocusEvent* event )
void QskTextInput::focusOutEvent( QFocusEvent* event ) void QskTextInput::focusOutEvent( QFocusEvent* event )
{ {
#if 1 switch( event->reason() )
if ( event->reason() != Qt::ActiveWindowFocusReason {
&& event->reason() != Qt::PopupFocusReason ) case Qt::ActiveWindowFocusReason:
case Qt::PopupFocusReason:
{
break;
}
default:
{ {
m_data->textInput->deselect(); m_data->textInput->deselect();
}
#endif
if ( m_data->activationModes & ActivationOnFocus )
{
#if 0
if ( !hasFocus() )
{
setEditing( false ); setEditing( false );
} }
#endif
} }
Inherited::focusOutEvent( event ); Inherited::focusOutEvent( event );
@ -655,6 +657,37 @@ void QskTextInput::setEchoMode( EchoMode mode )
} }
} }
QString QskTextInput::passwordCharacter() const
{
return m_data->textInput->passwordCharacter();
}
void QskTextInput::setPasswordCharacter( const QString& text )
{
m_data->textInput->setPasswordCharacter( text );
}
void QskTextInput::resetPasswordCharacter()
{
m_data->textInput->setPasswordCharacter(
QGuiApplication::styleHints()->passwordMaskCharacter() );
}
int QskTextInput::passwordMaskDelay() const
{
return m_data->textInput->passwordMaskDelay();
}
void QskTextInput::setPasswordMaskDelay( int ms )
{
return m_data->textInput->setPasswordMaskDelay( ms );
}
void QskTextInput::resetPasswordMaskDelay()
{
return m_data->textInput->resetPasswordMaskDelay();
}
QString QskTextInput::displayText() const QString QskTextInput::displayText() const
{ {
return m_data->textInput->displayText(); return m_data->textInput->displayText();

View File

@ -31,6 +31,14 @@ class QSK_EXPORT QskTextInput : public QskControl
Q_PROPERTY( bool editing READ isEditing Q_PROPERTY( bool editing READ isEditing
WRITE setEditing NOTIFY editingChanged ) WRITE setEditing NOTIFY editingChanged )
Q_PROPERTY( QString passwordCharacter READ passwordCharacter
WRITE setPasswordCharacter RESET resetPasswordCharacter
NOTIFY passwordCharacterChanged )
Q_PROPERTY( int passwordMaskDelay READ passwordMaskDelay
WRITE setPasswordMaskDelay RESET resetPasswordMaskDelay
NOTIFY passwordMaskDelayChanged )
using Inherited = QskControl; using Inherited = QskControl;
public: public:
@ -103,8 +111,15 @@ public:
EchoMode echoMode() const; EchoMode echoMode() const;
void setEchoMode( EchoMode ); void setEchoMode( EchoMode );
QString displayText() const; QString passwordCharacter() const;
void setPasswordCharacter( const QString& );
void resetPasswordCharacter();
int passwordMaskDelay() const;
void setPasswordMaskDelay( int );
void resetPasswordMaskDelay();
QString displayText() const;
QString preeditText() const; QString preeditText() const;
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
@ -152,7 +167,10 @@ Q_SIGNALS:
#endif #endif
void maximumLengthChanged( int ); void maximumLengthChanged( int );
void echoModeChanged( EchoMode ); void echoModeChanged( EchoMode );
void passwordMaskDelayChanged();
void passwordCharacterChanged();
void validatorChanged(); void validatorChanged();
void inputMaskChanged( const QString& ); void inputMaskChanged( const QString& );

View File

@ -278,7 +278,9 @@ void QskInputContext::showInputPanel()
inputPopup->setOverlay( hasInputProxy ); inputPopup->setOverlay( hasInputProxy );
if ( !hasInputProxy ) if ( hasInputProxy )
box->setMargins( QMarginsF( 5, 5, 5, 5 ) );
else
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge ); box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
} }

View File

@ -36,6 +36,16 @@ static inline QString qskKeyString( int keyCode )
return QChar( keyCode ); return QChar( keyCode );
} }
static inline bool qskUsePrediction( Qt::InputMethodHints hints )
{
constexpr Qt::InputMethodHints mask =
Qt::ImhNoPredictiveText | Qt::ImhHiddenText
| Qt::ImhDialableCharactersOnly | Qt::ImhEmailCharactersOnly
| Qt::ImhUrlCharactersOnly;
return ( hints & mask ) == 0;
}
class QskInputEngine::PrivateData class QskInputEngine::PrivateData
{ {
public: public:
@ -114,7 +124,7 @@ QskInputEngine::Result QskInputEngine::processKey( int key,
auto& preedit = m_data->preedit; auto& preedit = m_data->preedit;
QskTextPredictor* predictor = nullptr; QskTextPredictor* predictor = nullptr;
if ( !( inputHints & Qt::ImhHiddenText ) ) if ( qskUsePrediction( inputHints ) )
predictor = m_data->predictor; predictor = m_data->predictor;
/* /*
@ -175,6 +185,7 @@ QskInputEngine::Result QskInputEngine::processKey( int key,
{ {
if ( !preedit.isEmpty() && spaceLeft) if ( !preedit.isEmpty() && spaceLeft)
{ {
preedit += qskKeyString( key );
preedit = preedit.left( spaceLeft ); preedit = preedit.left( spaceLeft );
result.text = preedit; result.text = preedit;

View File

@ -74,15 +74,59 @@ static inline void qskSendKey( QQuickItem* receiver, int key )
QCoreApplication::sendEvent( receiver, &keyRelease ); QCoreApplication::sendEvent( receiver, &keyRelease );
} }
static inline void qskSyncInputProxy(
QQuickItem* inputItem, QskTextInput* inputProxy )
{
int passwordMaskDelay = -1;
QString passwordCharacter;
if ( auto textInput = qobject_cast< QskTextInput* >( inputItem ) )
{
passwordMaskDelay = textInput->passwordMaskDelay();
passwordCharacter = textInput->passwordCharacter();
if ( inputProxy->echoMode() == QskTextInput::NoEcho )
{
/*
Qt::ImhHiddenText does not provide information
to decide between NoEcho/Password
*/
auto mode = textInput->echoMode();
if ( mode == QskTextInput::Password )
inputProxy->setEchoMode( mode );
}
}
if ( passwordMaskDelay >= 0 )
inputProxy->setPasswordMaskDelay( passwordMaskDelay );
else
inputProxy->resetPasswordMaskDelay();
if ( !passwordCharacter.isEmpty() )
inputProxy->setPasswordCharacter( passwordCharacter );
else
inputProxy->resetPasswordCharacter();
}
namespace namespace
{ {
class TextInput : public QskTextInput class TextInput final : public QskTextInput
{ {
public: public:
TextInput( QQuickItem* parentItem = nullptr ): TextInput( QQuickItem* parentItem = nullptr ):
QskTextInput( parentItem ) QskTextInput( parentItem )
{ {
setObjectName( "InputPanelInputProxy" ); setObjectName( "InputPanelInputProxy" );
setFocusPolicy( Qt::NoFocus );
}
protected:
virtual void focusInEvent( QFocusEvent* ) override final
{
}
virtual void focusOutEvent( QFocusEvent* ) override final
{
} }
}; };
} }
@ -198,7 +242,19 @@ void QskInputPanel::attachInputItem( QQuickItem* item )
processInputMethodQueries( queries ); processInputMethodQueries( queries );
if ( m_data->hasInputProxy ) if ( m_data->hasInputProxy )
{
m_data->inputProxy->setEditing( true ); m_data->inputProxy->setEditing( true );
// hiding the cursor in the real input item
const QInputMethodEvent::Attribute attribute(
QInputMethodEvent::Cursor, 0, 0, QVariant() );
QInputMethodEvent event( QString(), { attribute } );
QCoreApplication::sendEvent( item, &event );
// not all information is available from the input method query
qskSyncInputProxy( item, m_data->inputProxy );
}
} }
} }
@ -351,17 +407,17 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
QInputMethodQueryEvent event( queries ); QInputMethodQueryEvent event( queries );
QCoreApplication::sendEvent( m_data->inputItem, &event ); QCoreApplication::sendEvent( m_data->inputItem, &event );
if ( queries & Qt::ImHints ) if ( event.queries() & Qt::ImHints )
{ {
bool hasPrediction = true; bool hasPrediction = true;
bool hasEchoMode = false; QskTextInput::EchoMode echoMode = QskTextInput::Normal;
const auto hints = static_cast< Qt::InputMethodHints >( const auto hints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() ); event.value( Qt::ImHints ).toInt() );
if ( hints & Qt::ImhHiddenText ) if ( hints & Qt::ImhHiddenText )
{ {
hasEchoMode = true; echoMode = QskTextInput::NoEcho;
} }
if ( hints & Qt::ImhSensitiveData ) if ( hints & Qt::ImhSensitiveData )
@ -415,11 +471,13 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
if ( hints & Qt::ImhDigitsOnly ) if ( hints & Qt::ImhDigitsOnly )
{ {
// using a numpad instead of our virtual keyboard // using a numpad instead of our virtual keyboard
hasPrediction = false;
} }
if ( hints & Qt::ImhFormattedNumbersOnly ) if ( hints & Qt::ImhFormattedNumbersOnly )
{ {
// a numpad with decimal point and minus sign // a numpad with decimal point and minus sign
hasPrediction = false;
} }
if ( hints & Qt::ImhUppercaseOnly ) if ( hints & Qt::ImhUppercaseOnly )
@ -435,16 +493,19 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
if ( hints & Qt::ImhDialableCharactersOnly ) if ( hints & Qt::ImhDialableCharactersOnly )
{ {
// characters suitable for phone dialing // characters suitable for phone dialing
hasPrediction = false;
} }
if ( hints & Qt::ImhEmailCharactersOnly ) if ( hints & Qt::ImhEmailCharactersOnly )
{ {
// characters suitable for email addresses // characters suitable for email addresses
hasPrediction = false;
} }
if ( hints & Qt::ImhUrlCharactersOnly ) if ( hints & Qt::ImhUrlCharactersOnly )
{ {
// characters suitable for URLs // characters suitable for URLs
hasPrediction = false;
} }
if ( hints & Qt::ImhLatinOnly ) if ( hints & Qt::ImhLatinOnly )
@ -457,18 +518,17 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
m_data->predictionBar->setVisible( m_data->predictionBar->setVisible(
hasPrediction && m_data->engine && m_data->engine->predictor() ); hasPrediction && m_data->engine && m_data->engine->predictor() );
m_data->inputProxy->setEchoMode( m_data->inputProxy->setEchoMode( echoMode );
hasEchoMode ? QskTextInput::PasswordEchoOnEdit : QskTextInput::Normal );
m_data->inputHints = hints; m_data->inputHints = hints;
} }
if ( queries & Qt::ImPreferredLanguage ) if ( event.queries() & Qt::ImPreferredLanguage )
{ {
// already handled by the input context // already handled by the input context
} }
if ( queries & Qt::ImMaximumTextLength ) if ( event.queries() & Qt::ImMaximumTextLength )
{ {
// needs to be handled before Qt::ImCursorPosition ! // needs to be handled before Qt::ImCursorPosition !
@ -483,7 +543,7 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
} }
if ( queries & Qt::ImSurroundingText ) if ( event.queries() & Qt::ImSurroundingText )
{ {
if ( m_data->hasInputProxy ) if ( m_data->hasInputProxy )
{ {
@ -492,7 +552,7 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
} }
} }
if ( queries & Qt::ImCursorPosition ) if ( event.queries() & Qt::ImCursorPosition )
{ {
if ( m_data->hasInputProxy ) if ( m_data->hasInputProxy )
{ {
@ -501,7 +561,7 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
} }
} }
if ( queries & Qt::ImCurrentSelection ) if ( event.queries() & Qt::ImCurrentSelection )
{ {
#if 0 #if 0
const auto text = event.value( Qt::ImCurrentSelection ).toString(); const auto text = event.value( Qt::ImCurrentSelection ).toString();