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 );
auto* textInput4 = new QskTextInput( this );
textInput4->setEchoMode( QskTextInput::PasswordEchoOnEdit );
textInput4->setEchoMode( QskTextInput::Password );
textInput4->setPasswordMaskDelay( 1000 );
textInput4->setMaxLength( 8 );
textInput4->setText( "12345678" );
textInput4->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred );
@ -248,8 +249,10 @@ int main( int argc, char* argv[] )
QskObjectCounter counter( true );
#endif
#if 1
qputenv( "QT_IM_MODULE", "skinny" );
qputenv( "QT_PLUGIN_PATH", STRING( PLUGIN_PATH ) );
#endif
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 ) )
{
return;
}
/*
The item that is connected to the input context.
- often the item having the active focus.
*/
auto inputMethod = QGuiApplication::inputMethod();
bool doUpdate = item->hasActiveFocus();
QQuickItem* inputItem = nullptr;
/*
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
*/
QQuickItem* inputItem = nullptr;
if ( QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) )
{
doUpdate = ( item == inputItem );
}
QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) );
}
return 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 );
else
doUpdate = item->hasActiveFocus();
if ( doUpdate )
inputMethod->update( queries );
QGuiApplication::inputMethod()->update( queries );
}
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 void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries );
QSK_EXPORT QQuickItem* qskInputContextItem();
QSK_EXPORT const QSGNode* qskItemNode( 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,
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,
input, &QskControl::resetImplicitSize );
@ -381,22 +387,18 @@ void QskTextInput::focusInEvent( QFocusEvent* event )
void QskTextInput::focusOutEvent( QFocusEvent* event )
{
#if 1
if ( event->reason() != Qt::ActiveWindowFocusReason
&& event->reason() != Qt::PopupFocusReason )
switch( event->reason() )
{
m_data->textInput->deselect();
}
#endif
if ( m_data->activationModes & ActivationOnFocus )
{
#if 0
if ( !hasFocus() )
case Qt::ActiveWindowFocusReason:
case Qt::PopupFocusReason:
{
break;
}
default:
{
m_data->textInput->deselect();
setEditing( false );
}
#endif
}
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
{
return m_data->textInput->displayText();

View File

@ -31,6 +31,14 @@ class QSK_EXPORT QskTextInput : public QskControl
Q_PROPERTY( bool editing READ isEditing
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;
public:
@ -103,8 +111,15 @@ public:
EchoMode echoMode() const;
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;
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
@ -152,7 +167,10 @@ Q_SIGNALS:
#endif
void maximumLengthChanged( int );
void echoModeChanged( EchoMode );
void passwordMaskDelayChanged();
void passwordCharacterChanged();
void validatorChanged();
void inputMaskChanged( const QString& );

View File

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

View File

@ -36,6 +36,16 @@ static inline QString qskKeyString( int 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
{
public:
@ -114,7 +124,7 @@ QskInputEngine::Result QskInputEngine::processKey( int key,
auto& preedit = m_data->preedit;
QskTextPredictor* predictor = nullptr;
if ( !( inputHints & Qt::ImhHiddenText ) )
if ( qskUsePrediction( inputHints ) )
predictor = m_data->predictor;
/*
@ -175,6 +185,7 @@ QskInputEngine::Result QskInputEngine::processKey( int key,
{
if ( !preedit.isEmpty() && spaceLeft)
{
preedit += qskKeyString( key );
preedit = preedit.left( spaceLeft );
result.text = preedit;

View File

@ -74,15 +74,59 @@ static inline void qskSendKey( QQuickItem* receiver, int key )
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
{
class TextInput : public QskTextInput
class TextInput final : public QskTextInput
{
public:
TextInput( QQuickItem* parentItem = nullptr ):
QskTextInput( parentItem )
{
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 );
if ( m_data->hasInputProxy )
{
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 );
QCoreApplication::sendEvent( m_data->inputItem, &event );
if ( queries & Qt::ImHints )
if ( event.queries() & Qt::ImHints )
{
bool hasPrediction = true;
bool hasEchoMode = false;
QskTextInput::EchoMode echoMode = QskTextInput::Normal;
const auto hints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
if ( hints & Qt::ImhHiddenText )
{
hasEchoMode = true;
echoMode = QskTextInput::NoEcho;
}
if ( hints & Qt::ImhSensitiveData )
@ -415,11 +471,13 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
if ( hints & Qt::ImhDigitsOnly )
{
// using a numpad instead of our virtual keyboard
hasPrediction = false;
}
if ( hints & Qt::ImhFormattedNumbersOnly )
{
// a numpad with decimal point and minus sign
hasPrediction = false;
}
if ( hints & Qt::ImhUppercaseOnly )
@ -435,16 +493,19 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
if ( hints & Qt::ImhDialableCharactersOnly )
{
// characters suitable for phone dialing
hasPrediction = false;
}
if ( hints & Qt::ImhEmailCharactersOnly )
{
// characters suitable for email addresses
hasPrediction = false;
}
if ( hints & Qt::ImhUrlCharactersOnly )
{
// characters suitable for URLs
hasPrediction = false;
}
if ( hints & Qt::ImhLatinOnly )
@ -457,18 +518,17 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
m_data->predictionBar->setVisible(
hasPrediction && m_data->engine && m_data->engine->predictor() );
m_data->inputProxy->setEchoMode(
hasEchoMode ? QskTextInput::PasswordEchoOnEdit : QskTextInput::Normal );
m_data->inputProxy->setEchoMode( echoMode );
m_data->inputHints = hints;
}
if ( queries & Qt::ImPreferredLanguage )
if ( event.queries() & Qt::ImPreferredLanguage )
{
// already handled by the input context
}
if ( queries & Qt::ImMaximumTextLength )
if ( event.queries() & Qt::ImMaximumTextLength )
{
// 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 )
{
@ -492,7 +552,7 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
}
}
if ( queries & Qt::ImCursorPosition )
if ( event.queries() & Qt::ImCursorPosition )
{
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
const auto text = event.value( Qt::ImCurrentSelection ).toString();