input context classes reorganized: the whole idea if using input methods
for a virtual keyboard misses fundamental problems like f.e. having an application with more than one window/screen.
This commit is contained in:
parent
87eea5a2a9
commit
cb6faf7a1f
|
@ -1,538 +0,0 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskInputEngine.h"
|
||||
#include "QskInputContext.h"
|
||||
#include "QskTextPredictor.h"
|
||||
#include "QskControl.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
static inline QQuickItem* qskReceiverItem( const QskInputEngine* engine )
|
||||
{
|
||||
auto item = engine->inputProxy();
|
||||
return item ? item : engine->inputItem();
|
||||
}
|
||||
|
||||
static inline void qskSendReplaceText( QQuickItem* receiver, const QString& text )
|
||||
{
|
||||
if ( receiver == nullptr )
|
||||
return;
|
||||
|
||||
QInputMethodEvent::Attribute attribute(
|
||||
QInputMethodEvent::Selection, 0, 32767, QVariant() );
|
||||
|
||||
QInputMethodEvent event1( QString(), { attribute } );
|
||||
QCoreApplication::sendEvent( receiver, &event1 );
|
||||
|
||||
QInputMethodEvent event2;
|
||||
event2.setCommitString( text );
|
||||
|
||||
QCoreApplication::sendEvent( receiver, &event2 );
|
||||
}
|
||||
|
||||
static inline void qskSendText( QQuickItem* receiver,
|
||||
const QString& text, bool isFinal )
|
||||
{
|
||||
if ( receiver == nullptr )
|
||||
return;
|
||||
|
||||
if ( isFinal )
|
||||
{
|
||||
QInputMethodEvent event;
|
||||
event.setCommitString( text );
|
||||
|
||||
QCoreApplication::sendEvent( receiver, &event );
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontUnderline( true );
|
||||
|
||||
const QInputMethodEvent::Attribute attribute(
|
||||
QInputMethodEvent::TextFormat, 0, text.length(), format );
|
||||
|
||||
QInputMethodEvent event( text, { attribute } );
|
||||
|
||||
QCoreApplication::sendEvent( receiver, &event );
|
||||
}
|
||||
}
|
||||
|
||||
static inline void qskSendKey( QQuickItem* receiver, int key )
|
||||
{
|
||||
if ( receiver == nullptr )
|
||||
return;
|
||||
|
||||
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
|
||||
QCoreApplication::sendEvent( receiver, &keyPress );
|
||||
|
||||
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
|
||||
QCoreApplication::sendEvent( receiver, &keyRelease );
|
||||
}
|
||||
|
||||
static inline bool qskUsePrediction( Qt::InputMethodHints hints )
|
||||
{
|
||||
constexpr Qt::InputMethodHints mask =
|
||||
Qt::ImhNoPredictiveText | Qt::ImhExclusiveInputMask | Qt::ImhHiddenText;
|
||||
|
||||
return ( hints & mask ) == 0;
|
||||
}
|
||||
|
||||
static inline QString qskKeyString( int keyCode )
|
||||
{
|
||||
// Special case entry codes here, else default to the symbol
|
||||
switch ( keyCode )
|
||||
{
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_CapsLock:
|
||||
case Qt::Key_Mode_switch:
|
||||
case Qt::Key_Backspace:
|
||||
case Qt::Key_Muhenkan:
|
||||
return QString();
|
||||
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Kanji:
|
||||
return QChar( QChar::CarriageReturn );
|
||||
|
||||
case Qt::Key_Space:
|
||||
return QChar( QChar::Space );
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QChar( keyCode );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class KeyProcessor
|
||||
{
|
||||
public:
|
||||
class Result
|
||||
{
|
||||
public:
|
||||
int key = 0;
|
||||
|
||||
QString text;
|
||||
bool isFinal = true;
|
||||
};
|
||||
|
||||
Result processKey( int key, Qt::InputMethodHints inputHints,
|
||||
QskTextPredictor* predictor, int spaceLeft )
|
||||
{
|
||||
Result result;
|
||||
|
||||
// First we have to handle the control keys
|
||||
|
||||
switch ( key )
|
||||
{
|
||||
case Qt::Key_Backspace:
|
||||
case Qt::Key_Muhenkan:
|
||||
{
|
||||
if ( predictor )
|
||||
{
|
||||
if ( !m_preedit.isEmpty() )
|
||||
{
|
||||
m_preedit.chop( 1 );
|
||||
|
||||
result.text = m_preedit;
|
||||
result.isFinal = false;
|
||||
|
||||
predictor->request( m_preedit );
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.key = Qt::Key_Backspace;
|
||||
return result;
|
||||
}
|
||||
case Qt::Key_Return:
|
||||
{
|
||||
if ( predictor )
|
||||
{
|
||||
if ( !m_preedit.isEmpty() )
|
||||
{
|
||||
if ( spaceLeft )
|
||||
{
|
||||
result.text = m_preedit.left( spaceLeft );
|
||||
result.isFinal = true;
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if( !( inputHints & Qt::ImhMultiLine ) )
|
||||
{
|
||||
result.key = Qt::Key_Return;
|
||||
return result;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Qt::Key_Space:
|
||||
{
|
||||
if ( predictor )
|
||||
{
|
||||
if ( !m_preedit.isEmpty() && spaceLeft)
|
||||
{
|
||||
m_preedit += qskKeyString( key );
|
||||
m_preedit = m_preedit.left( spaceLeft );
|
||||
|
||||
result.text = m_preedit;
|
||||
result.isFinal = true;
|
||||
|
||||
reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Qt::Key_Left:
|
||||
case Qt::Key_Right:
|
||||
case Qt::Key_Escape:
|
||||
{
|
||||
result.key = key;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const QString text = qskKeyString( key );
|
||||
|
||||
if ( predictor )
|
||||
{
|
||||
m_preedit += text;
|
||||
|
||||
predictor->request( m_preedit );
|
||||
|
||||
if ( predictor->candidateCount() > 0 )
|
||||
{
|
||||
result.text = m_preedit;
|
||||
result.isFinal = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.text = m_preedit.left( spaceLeft );
|
||||
result.isFinal = true;
|
||||
|
||||
m_preedit.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.text = text;
|
||||
result.isFinal = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_preedit.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_preedit;
|
||||
};
|
||||
}
|
||||
|
||||
class QskInputEngine::PrivateData
|
||||
{
|
||||
public:
|
||||
KeyProcessor keyProcessor;
|
||||
QPointer< QskControl > panel;
|
||||
QPointer< QQuickItem > inputItem;
|
||||
|
||||
QLocale predictorLocale;
|
||||
QPointer< QskTextPredictor > predictor;
|
||||
|
||||
Qt::InputMethodHints inputHints = 0;
|
||||
bool hasPredictorLocale = false;
|
||||
};
|
||||
|
||||
QskInputEngine::QskInputEngine( QObject* parent ):
|
||||
Inherited( parent ),
|
||||
m_data( new PrivateData() )
|
||||
{
|
||||
}
|
||||
|
||||
QskInputEngine::~QskInputEngine()
|
||||
{
|
||||
}
|
||||
|
||||
void QskInputEngine::attachInputItem( QQuickItem* item )
|
||||
{
|
||||
if ( item == m_data->inputItem )
|
||||
return;
|
||||
|
||||
m_data->inputItem = item;
|
||||
|
||||
if ( item )
|
||||
{
|
||||
auto context = QskInputContext::instance();
|
||||
const auto locale = context->locale();
|
||||
|
||||
if ( m_data->predictor )
|
||||
m_data->predictor->reset();
|
||||
|
||||
m_data->keyProcessor.reset();
|
||||
m_data->inputHints = 0;
|
||||
|
||||
attachToPanel( item );
|
||||
|
||||
Qt::InputMethodQueries queries = Qt::ImQueryAll;
|
||||
queries &= ~Qt::ImEnabled;
|
||||
|
||||
updateInputPanel( queries );
|
||||
}
|
||||
else
|
||||
{
|
||||
attachToPanel( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
void QskInputEngine::updateInputPanel(
|
||||
Qt::InputMethodQueries queries )
|
||||
{
|
||||
auto item = inputItem();
|
||||
if ( item == nullptr )
|
||||
return;
|
||||
|
||||
QInputMethodQueryEvent event( queries );
|
||||
QCoreApplication::sendEvent( item, &event );
|
||||
|
||||
if ( queries & Qt::ImHints )
|
||||
{
|
||||
m_data->inputHints = static_cast< Qt::InputMethodHints >(
|
||||
event.value( Qt::ImHints ).toInt() );
|
||||
|
||||
updatePanel();
|
||||
}
|
||||
|
||||
if ( queries & Qt::ImPreferredLanguage )
|
||||
{
|
||||
if ( m_data->panel )
|
||||
{
|
||||
m_data->panel->setLocale(
|
||||
event.value( Qt::ImPreferredLanguage ).toLocale() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QskControl* QskInputEngine::panel( bool doCreate )
|
||||
{
|
||||
if ( m_data->panel == nullptr && doCreate )
|
||||
{
|
||||
auto panel = createPanel();
|
||||
|
||||
connect( panel, &QQuickItem::visibleChanged,
|
||||
this, &QskInputEngine::activeChanged );
|
||||
|
||||
connect( panel, &QskControl::localeChanged,
|
||||
this, &QskInputEngine::updateLocale );
|
||||
|
||||
m_data->panel = panel;
|
||||
updateLocale( m_data->panel->locale() );
|
||||
}
|
||||
|
||||
return m_data->panel;
|
||||
}
|
||||
|
||||
Qt::Alignment QskInputEngine::panelAlignment() const
|
||||
{
|
||||
/*
|
||||
When we have an input proxy, we don't care if
|
||||
the input item becomes hidden
|
||||
*/
|
||||
|
||||
return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom;
|
||||
}
|
||||
|
||||
void QskInputEngine::updateLocale( const QLocale& locale )
|
||||
{
|
||||
if ( !m_data->hasPredictorLocale || locale != m_data->predictorLocale )
|
||||
{
|
||||
m_data->hasPredictorLocale = true;
|
||||
m_data->predictorLocale = locale;
|
||||
resetPredictor( locale );
|
||||
|
||||
m_data->keyProcessor.reset();
|
||||
updatePanel();
|
||||
}
|
||||
|
||||
Q_EMIT localeChanged();
|
||||
}
|
||||
|
||||
void QskInputEngine::updatePanel()
|
||||
{
|
||||
setPredictionEnabled(
|
||||
m_data->predictor && qskUsePrediction( m_data->inputHints ) );
|
||||
}
|
||||
|
||||
QQuickItem* QskInputEngine::inputItem() const
|
||||
{
|
||||
return m_data->inputItem;
|
||||
}
|
||||
|
||||
QQuickItem* QskInputEngine::inputProxy() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void QskInputEngine::resetPredictor( const QLocale& locale )
|
||||
{
|
||||
auto predictor = QskInputContext::instance()->textPredictor( locale );
|
||||
|
||||
if ( predictor == m_data->predictor )
|
||||
return;
|
||||
|
||||
if ( m_data->predictor )
|
||||
{
|
||||
if ( m_data->predictor->parent() == this )
|
||||
{
|
||||
delete m_data->predictor;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data->predictor->disconnect( this );
|
||||
m_data->predictor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if ( predictor )
|
||||
{
|
||||
if ( predictor->parent() == nullptr )
|
||||
predictor->setParent( this );
|
||||
|
||||
connect( predictor, &QskTextPredictor::predictionChanged,
|
||||
this, &QskInputEngine::updatePrediction );
|
||||
}
|
||||
|
||||
m_data->predictor = predictor;
|
||||
}
|
||||
|
||||
void QskInputEngine::applyInput( bool success )
|
||||
{
|
||||
auto item = inputItem();
|
||||
if ( item == nullptr )
|
||||
return;
|
||||
|
||||
if ( success )
|
||||
{
|
||||
if ( auto proxy = inputProxy() )
|
||||
{
|
||||
const auto value = proxy->property( "text" );
|
||||
if ( value.canConvert< QString >() )
|
||||
qskSendReplaceText( item, value.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
qskSendKey( item, success ? Qt::Key_Return : Qt::Key_Escape );
|
||||
}
|
||||
|
||||
void QskInputEngine::applyText( const QString& text, bool isFinal )
|
||||
{
|
||||
qskSendText( qskReceiverItem( this ), text, isFinal );
|
||||
}
|
||||
|
||||
void QskInputEngine::applyKey( int key )
|
||||
{
|
||||
// control keys like left/right
|
||||
qskSendKey( qskReceiverItem( this ), key );
|
||||
}
|
||||
|
||||
void QskInputEngine::commitPredictiveText( int index )
|
||||
{
|
||||
QString text;
|
||||
|
||||
if ( m_data->predictor )
|
||||
{
|
||||
text = m_data->predictor->candidate( index );
|
||||
m_data->predictor->reset();
|
||||
}
|
||||
|
||||
m_data->keyProcessor.reset();
|
||||
|
||||
showPrediction( QStringList() );
|
||||
applyText( text, true );
|
||||
}
|
||||
|
||||
void QskInputEngine::updatePrediction()
|
||||
{
|
||||
if ( m_data->predictor )
|
||||
showPrediction( m_data->predictor->candidates() );
|
||||
}
|
||||
|
||||
void QskInputEngine::setPredictionEnabled( bool on )
|
||||
{
|
||||
Q_UNUSED( on )
|
||||
}
|
||||
|
||||
void QskInputEngine::showPrediction( const QStringList& )
|
||||
{
|
||||
}
|
||||
|
||||
void QskInputEngine::commitKey( int key )
|
||||
{
|
||||
int spaceLeft = -1;
|
||||
|
||||
if ( !( m_data->inputHints & Qt::ImhMultiLine ) )
|
||||
{
|
||||
QInputMethodQueryEvent event1( Qt::ImMaximumTextLength );
|
||||
QCoreApplication::sendEvent( inputItem(), &event1 );
|
||||
|
||||
const int maxChars = event1.value( Qt::ImMaximumTextLength ).toInt();
|
||||
if ( maxChars >= 0 )
|
||||
{
|
||||
QInputMethodQueryEvent event2( Qt::ImSurroundingText );
|
||||
QCoreApplication::sendEvent( qskReceiverItem( this ), &event2 );
|
||||
|
||||
const auto text = event2.value( Qt::ImSurroundingText ).toString();
|
||||
spaceLeft = maxChars - text.length();
|
||||
}
|
||||
}
|
||||
|
||||
QskTextPredictor* predictor = nullptr;
|
||||
if ( qskUsePrediction( m_data->inputHints ) )
|
||||
predictor = m_data->predictor;
|
||||
|
||||
const auto result = m_data->keyProcessor.processKey(
|
||||
key, m_data->inputHints, predictor, spaceLeft );
|
||||
|
||||
if ( result.key )
|
||||
{
|
||||
switch( result.key )
|
||||
{
|
||||
case Qt::Key_Return:
|
||||
{
|
||||
applyInput( true );
|
||||
break;
|
||||
}
|
||||
case Qt::Key_Escape:
|
||||
{
|
||||
applyInput( false );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
applyKey( result.key );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( !result.text.isEmpty() )
|
||||
{
|
||||
applyText( result.text, result.isFinal );
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_QskInputEngine.cpp"
|
|
@ -1,68 +0,0 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_INPUT_ENGINE_H
|
||||
#define QSK_INPUT_ENGINE_H
|
||||
|
||||
#include "QskGlobal.h"
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
class QskTextPredictor;
|
||||
class QskControl;
|
||||
class QQuickItem;
|
||||
class QLocale;
|
||||
|
||||
class QSK_EXPORT QskInputEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QObject;
|
||||
|
||||
public:
|
||||
QskInputEngine( QObject* parent = nullptr );
|
||||
virtual ~QskInputEngine() override;
|
||||
|
||||
virtual void attachInputItem( QQuickItem* );
|
||||
virtual void updateInputPanel( Qt::InputMethodQueries );
|
||||
|
||||
QskControl* panel( bool doCreate );
|
||||
virtual Qt::Alignment panelAlignment() const;
|
||||
|
||||
virtual QQuickItem* inputProxy() const;
|
||||
virtual QQuickItem* inputItem() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void commitKey( int keyCode );
|
||||
void commitPredictiveText( int index );
|
||||
|
||||
Q_SIGNALS:
|
||||
void activeChanged();
|
||||
void localeChanged();
|
||||
|
||||
protected:
|
||||
virtual QskControl* createPanel() = 0;
|
||||
virtual void attachToPanel( QQuickItem* ) = 0;
|
||||
|
||||
virtual void setPredictionEnabled( bool on );
|
||||
virtual void showPrediction( const QStringList& );
|
||||
|
||||
void applyInput( bool success );
|
||||
void applyText( const QString&, bool isFinal );
|
||||
void applyKey( int keyCode );
|
||||
|
||||
private:
|
||||
void resetPredictor( const QLocale& );
|
||||
void updatePrediction();
|
||||
|
||||
void updatePanel();
|
||||
|
||||
void updateLocale( const QLocale& );
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -3,150 +3,274 @@
|
|||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskInputPanel.h"
|
||||
#include "QskVirtualKeyboard.h"
|
||||
#include "QskInputPredictionBar.h"
|
||||
#include "QskTextInput.h"
|
||||
#include "QskTextLabel.h"
|
||||
#include "QskLinearBox.h"
|
||||
#include "QskInputEngine.h"
|
||||
#include "QskInputContext.h"
|
||||
#include "QskTextPredictor.h"
|
||||
#include "QskControl.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QLocale>
|
||||
#include <QPointer>
|
||||
#include <QInputMethodQueryEvent>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
static inline QQuickItem* qskReceiverItem( const QskInputEngine* engine )
|
||||
{
|
||||
auto item = engine->inputProxy();
|
||||
return item ? item : engine->inputItem();
|
||||
}
|
||||
|
||||
static inline void qskSendReplaceText( QQuickItem* receiver, const QString& text )
|
||||
{
|
||||
if ( receiver == nullptr )
|
||||
return;
|
||||
|
||||
QInputMethodEvent::Attribute attribute(
|
||||
QInputMethodEvent::Selection, 0, 32767, QVariant() );
|
||||
|
||||
QInputMethodEvent event1( QString(), { attribute } );
|
||||
QCoreApplication::sendEvent( receiver, &event1 );
|
||||
|
||||
QInputMethodEvent event2;
|
||||
event2.setCommitString( text );
|
||||
|
||||
QCoreApplication::sendEvent( receiver, &event2 );
|
||||
}
|
||||
|
||||
static inline void qskSendText( QQuickItem* receiver,
|
||||
const QString& text, bool isFinal )
|
||||
{
|
||||
if ( receiver == nullptr )
|
||||
return;
|
||||
|
||||
if ( isFinal )
|
||||
{
|
||||
QInputMethodEvent event;
|
||||
event.setCommitString( text );
|
||||
|
||||
QCoreApplication::sendEvent( receiver, &event );
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontUnderline( true );
|
||||
|
||||
const QInputMethodEvent::Attribute attribute(
|
||||
QInputMethodEvent::TextFormat, 0, text.length(), format );
|
||||
|
||||
QInputMethodEvent event( text, { attribute } );
|
||||
|
||||
QCoreApplication::sendEvent( receiver, &event );
|
||||
}
|
||||
}
|
||||
|
||||
static inline void qskSendKey( QQuickItem* receiver, int key )
|
||||
{
|
||||
if ( receiver == nullptr )
|
||||
return;
|
||||
|
||||
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
|
||||
QCoreApplication::sendEvent( receiver, &keyPress );
|
||||
|
||||
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
|
||||
QCoreApplication::sendEvent( receiver, &keyRelease );
|
||||
}
|
||||
|
||||
static inline bool qskUsePrediction( Qt::InputMethodHints hints )
|
||||
{
|
||||
constexpr Qt::InputMethodHints mask =
|
||||
Qt::ImhNoPredictiveText | Qt::ImhExclusiveInputMask | Qt::ImhHiddenText;
|
||||
|
||||
return ( hints & mask ) == 0;
|
||||
}
|
||||
|
||||
static inline QString qskKeyString( int keyCode )
|
||||
{
|
||||
// Special case entry codes here, else default to the symbol
|
||||
switch ( keyCode )
|
||||
{
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_CapsLock:
|
||||
case Qt::Key_Mode_switch:
|
||||
case Qt::Key_Backspace:
|
||||
case Qt::Key_Muhenkan:
|
||||
return QString();
|
||||
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Kanji:
|
||||
return QChar( QChar::CarriageReturn );
|
||||
|
||||
case Qt::Key_Space:
|
||||
return QChar( QChar::Space );
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QChar( keyCode );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class TextInputProxy final : public QskTextInput
|
||||
class KeyProcessor
|
||||
{
|
||||
public:
|
||||
TextInputProxy( QskInputPanel* panel, QQuickItem* parentItem = nullptr ):
|
||||
QskTextInput( parentItem ),
|
||||
m_panel( panel )
|
||||
class Result
|
||||
{
|
||||
setObjectName( "InputPanelInputProxy" );
|
||||
setFocusPolicy( Qt::NoFocus );
|
||||
public:
|
||||
int key = 0;
|
||||
|
||||
QString text;
|
||||
bool isFinal = true;
|
||||
};
|
||||
|
||||
Result processKey( int key, Qt::InputMethodHints inputHints,
|
||||
QskTextPredictor* predictor, int spaceLeft )
|
||||
{
|
||||
Result result;
|
||||
|
||||
// First we have to handle the control keys
|
||||
|
||||
switch ( key )
|
||||
{
|
||||
case Qt::Key_Backspace:
|
||||
case Qt::Key_Muhenkan:
|
||||
{
|
||||
if ( predictor )
|
||||
{
|
||||
if ( !m_preedit.isEmpty() )
|
||||
{
|
||||
m_preedit.chop( 1 );
|
||||
|
||||
result.text = m_preedit;
|
||||
result.isFinal = false;
|
||||
|
||||
predictor->request( m_preedit );
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.key = Qt::Key_Backspace;
|
||||
return result;
|
||||
}
|
||||
case Qt::Key_Return:
|
||||
{
|
||||
if ( predictor )
|
||||
{
|
||||
if ( !m_preedit.isEmpty() )
|
||||
{
|
||||
if ( spaceLeft )
|
||||
{
|
||||
result.text = m_preedit.left( spaceLeft );
|
||||
result.isFinal = true;
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if( !( inputHints & Qt::ImhMultiLine ) )
|
||||
{
|
||||
result.key = Qt::Key_Return;
|
||||
return result;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Qt::Key_Space:
|
||||
{
|
||||
if ( predictor )
|
||||
{
|
||||
if ( !m_preedit.isEmpty() && spaceLeft)
|
||||
{
|
||||
m_preedit += qskKeyString( key );
|
||||
m_preedit = m_preedit.left( spaceLeft );
|
||||
|
||||
result.text = m_preedit;
|
||||
result.isFinal = true;
|
||||
|
||||
reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Qt::Key_Left:
|
||||
case Qt::Key_Right:
|
||||
case Qt::Key_Escape:
|
||||
{
|
||||
result.key = key;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const QString text = qskKeyString( key );
|
||||
|
||||
if ( predictor )
|
||||
{
|
||||
m_preedit += text;
|
||||
|
||||
predictor->request( m_preedit );
|
||||
|
||||
if ( predictor->candidateCount() > 0 )
|
||||
{
|
||||
result.text = m_preedit;
|
||||
result.isFinal = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.text = m_preedit.left( spaceLeft );
|
||||
result.isFinal = true;
|
||||
|
||||
m_preedit.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.text = text;
|
||||
result.isFinal = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual QskAspect::Subcontrol effectiveSubcontrol(
|
||||
QskAspect::Subcontrol subControl ) const override
|
||||
{
|
||||
if ( subControl == QskTextInput::Panel )
|
||||
return m_panel->effectiveSubcontrol( QskInputPanel::ProxyPanel );
|
||||
|
||||
if ( subControl == QskTextInput::Text )
|
||||
return m_panel->effectiveSubcontrol( QskInputPanel::ProxyText );
|
||||
|
||||
return subControl;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void focusInEvent( QFocusEvent* ) override final
|
||||
{
|
||||
}
|
||||
|
||||
virtual void focusOutEvent( QFocusEvent* ) override final
|
||||
void reset()
|
||||
{
|
||||
m_preedit.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
QskInputPanel* m_panel;
|
||||
QString m_preedit;
|
||||
};
|
||||
}
|
||||
|
||||
QSK_SUBCONTROL( QskInputPanel, Panel )
|
||||
QSK_SUBCONTROL( QskInputPanel, ProxyPanel )
|
||||
QSK_SUBCONTROL( QskInputPanel, ProxyText )
|
||||
|
||||
class QskInputPanel::PrivateData
|
||||
class QskInputEngine::PrivateData
|
||||
{
|
||||
public:
|
||||
KeyProcessor keyProcessor;
|
||||
QPointer< QskControl > panel;
|
||||
QPointer< QQuickItem > inputItem;
|
||||
|
||||
QskLinearBox* layout;
|
||||
QskTextLabel* prompt;
|
||||
TextInputProxy* inputProxy;
|
||||
QskInputPredictionBar* predictionBar;
|
||||
QskVirtualKeyboard* keyboard;
|
||||
QLocale predictorLocale;
|
||||
QPointer< QskTextPredictor > predictor;
|
||||
|
||||
int maxChars = -1;
|
||||
|
||||
QskInputPanel::PanelHints panelHints = QskInputPanel::InputProxy;
|
||||
Qt::InputMethodHints inputHints = 0;
|
||||
bool hasPredictorLocale = false;
|
||||
};
|
||||
|
||||
QskInputPanel::QskInputPanel( QQuickItem* parent ):
|
||||
QskInputEngine::QskInputEngine( QObject* parent ):
|
||||
Inherited( parent ),
|
||||
m_data( new PrivateData() )
|
||||
{
|
||||
setAutoLayoutChildren( true );
|
||||
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained );
|
||||
|
||||
m_data->prompt = new QskTextLabel();
|
||||
m_data->prompt->setVisible( false );
|
||||
|
||||
m_data->inputProxy = new TextInputProxy( this, nullptr );
|
||||
m_data->inputProxy->setVisible(
|
||||
m_data->panelHints & QskInputPanel::InputProxy );
|
||||
|
||||
m_data->predictionBar = new QskInputPredictionBar();
|
||||
m_data->predictionBar->setVisible(
|
||||
m_data->panelHints & QskInputPanel::Prediction );
|
||||
|
||||
m_data->keyboard = new QskVirtualKeyboard();
|
||||
|
||||
auto layout = new QskLinearBox( Qt::Vertical, this );
|
||||
|
||||
layout->addItem( m_data->prompt, Qt::AlignLeft | Qt::AlignHCenter );
|
||||
layout->addItem( m_data->inputProxy, Qt::AlignLeft | Qt::AlignHCenter );
|
||||
layout->addStretch( 10 );
|
||||
layout->addItem( m_data->predictionBar );
|
||||
layout->addItem( m_data->keyboard );
|
||||
|
||||
m_data->layout = layout;
|
||||
|
||||
connect( m_data->predictionBar, &QskInputPredictionBar::predictiveTextSelected,
|
||||
this, &QskInputPanel::predictiveTextSelected );
|
||||
|
||||
connect( m_data->keyboard, &QskVirtualKeyboard::keySelected,
|
||||
this, &QskInputPanel::keySelected );
|
||||
}
|
||||
|
||||
QskInputPanel::~QskInputPanel()
|
||||
QskInputEngine::~QskInputEngine()
|
||||
{
|
||||
}
|
||||
|
||||
void QskInputPanel::setPanelHint( PanelHint hint, bool on )
|
||||
{
|
||||
if ( on )
|
||||
setPanelHints( m_data->panelHints | hint );
|
||||
else
|
||||
setPanelHints( m_data->panelHints & ~hint );
|
||||
}
|
||||
|
||||
void QskInputPanel::setPanelHints( PanelHints hints )
|
||||
{
|
||||
if ( hints == m_data->panelHints )
|
||||
return;
|
||||
|
||||
m_data->panelHints = hints;
|
||||
|
||||
m_data->inputProxy->setVisible( hints & QskInputPanel::InputProxy );
|
||||
m_data->predictionBar->setVisible( hints & QskInputPanel::Prediction );
|
||||
|
||||
const bool showPrompt = ( hints & QskInputPanel::InputProxy )
|
||||
&& !m_data->prompt->text().isEmpty();
|
||||
|
||||
m_data->prompt->setVisible( showPrompt );
|
||||
|
||||
Q_EMIT panelHintsChanged();
|
||||
}
|
||||
|
||||
QskInputPanel::PanelHints QskInputPanel::panelHints() const
|
||||
{
|
||||
return m_data->panelHints;
|
||||
}
|
||||
|
||||
void QskInputPanel::attachInputItem( QQuickItem* item )
|
||||
void QskInputEngine::attachInputItem( QQuickItem* item )
|
||||
{
|
||||
if ( item == m_data->inputItem )
|
||||
return;
|
||||
|
@ -155,103 +279,260 @@ void QskInputPanel::attachInputItem( QQuickItem* item )
|
|||
|
||||
if ( item )
|
||||
{
|
||||
if ( m_data->panelHints & QskInputPanel::InputProxy )
|
||||
auto context = QskInputContext::instance();
|
||||
const auto locale = context->locale();
|
||||
|
||||
if ( m_data->predictor )
|
||||
m_data->predictor->reset();
|
||||
|
||||
m_data->keyProcessor.reset();
|
||||
m_data->inputHints = 0;
|
||||
|
||||
attachToPanel( item );
|
||||
|
||||
Qt::InputMethodQueries queries = Qt::ImQueryAll;
|
||||
queries &= ~Qt::ImEnabled;
|
||||
|
||||
updateInputPanel( queries );
|
||||
}
|
||||
else
|
||||
{
|
||||
attachToPanel( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
void QskInputEngine::updateInputPanel(
|
||||
Qt::InputMethodQueries queries )
|
||||
{
|
||||
auto item = inputItem();
|
||||
if ( item == nullptr )
|
||||
return;
|
||||
|
||||
QInputMethodQueryEvent event( queries );
|
||||
QCoreApplication::sendEvent( item, &event );
|
||||
|
||||
if ( queries & Qt::ImHints )
|
||||
{
|
||||
m_data->inputHints = static_cast< Qt::InputMethodHints >(
|
||||
event.value( Qt::ImHints ).toInt() );
|
||||
|
||||
updatePanel();
|
||||
}
|
||||
|
||||
if ( queries & Qt::ImPreferredLanguage )
|
||||
{
|
||||
if ( m_data->panel )
|
||||
{
|
||||
m_data->inputProxy->setupFrom( item );
|
||||
m_data->inputProxy->setEditing( true );
|
||||
|
||||
// hiding the cursor in item
|
||||
const QInputMethodEvent::Attribute attribute(
|
||||
QInputMethodEvent::Cursor, 0, 0, QVariant() );
|
||||
|
||||
QInputMethodEvent event( QString(), { attribute } );
|
||||
QCoreApplication::sendEvent( item, &event );
|
||||
m_data->panel->setLocale(
|
||||
event.value( Qt::ImPreferredLanguage ).toLocale() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQuickItem* QskInputPanel::attachedInputItem() const
|
||||
QskControl* QskInputEngine::panel( bool doCreate )
|
||||
{
|
||||
if ( m_data->panel == nullptr && doCreate )
|
||||
{
|
||||
auto panel = createPanel();
|
||||
|
||||
connect( panel, &QQuickItem::visibleChanged,
|
||||
this, &QskInputEngine::activeChanged );
|
||||
|
||||
connect( panel, &QskControl::localeChanged,
|
||||
this, &QskInputEngine::updateLocale );
|
||||
|
||||
m_data->panel = panel;
|
||||
updateLocale( m_data->panel->locale() );
|
||||
}
|
||||
|
||||
return m_data->panel;
|
||||
}
|
||||
|
||||
Qt::Alignment QskInputEngine::panelAlignment() const
|
||||
{
|
||||
/*
|
||||
When we have an input proxy, we don't care if
|
||||
the input item becomes hidden
|
||||
*/
|
||||
|
||||
return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom;
|
||||
}
|
||||
|
||||
void QskInputEngine::updateLocale( const QLocale& locale )
|
||||
{
|
||||
if ( !m_data->hasPredictorLocale || locale != m_data->predictorLocale )
|
||||
{
|
||||
m_data->hasPredictorLocale = true;
|
||||
m_data->predictorLocale = locale;
|
||||
resetPredictor( locale );
|
||||
|
||||
m_data->keyProcessor.reset();
|
||||
updatePanel();
|
||||
}
|
||||
|
||||
Q_EMIT localeChanged();
|
||||
}
|
||||
|
||||
void QskInputEngine::updatePanel()
|
||||
{
|
||||
setPredictionEnabled(
|
||||
m_data->predictor && qskUsePrediction( m_data->inputHints ) );
|
||||
}
|
||||
|
||||
QQuickItem* QskInputEngine::inputItem() const
|
||||
{
|
||||
return m_data->inputItem;
|
||||
}
|
||||
|
||||
QQuickItem* QskInputPanel::inputProxy() const
|
||||
QQuickItem* QskInputEngine::inputProxy() const
|
||||
{
|
||||
return m_data->inputProxy;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol(
|
||||
QskAspect::Subcontrol subControl ) const
|
||||
void QskInputEngine::resetPredictor( const QLocale& locale )
|
||||
{
|
||||
if( subControl == QskBox::Panel )
|
||||
return QskInputPanel::Panel;
|
||||
auto predictor = QskInputContext::instance()->textPredictor( locale );
|
||||
|
||||
#if 1
|
||||
// TODO ...
|
||||
if( subControl == QskInputPanel::ProxyPanel )
|
||||
return QskTextInput::Panel;
|
||||
if ( predictor == m_data->predictor )
|
||||
return;
|
||||
|
||||
if( subControl == QskInputPanel::ProxyText )
|
||||
return QskTextInput::Text;
|
||||
#endif
|
||||
|
||||
return subControl;
|
||||
}
|
||||
|
||||
QString QskInputPanel::inputPrompt() const
|
||||
{
|
||||
return m_data->prompt->text();
|
||||
}
|
||||
|
||||
void QskInputPanel::setInputPrompt( const QString& text )
|
||||
{
|
||||
auto prompt = m_data->prompt;
|
||||
|
||||
if ( text != prompt->text() )
|
||||
if ( m_data->predictor )
|
||||
{
|
||||
prompt->setText( text );
|
||||
|
||||
if ( m_data->panelHints & QskInputPanel::InputProxy )
|
||||
prompt->setVisible( !text.isEmpty() );
|
||||
|
||||
Q_EMIT inputPromptChanged( text );
|
||||
}
|
||||
}
|
||||
|
||||
void QskInputPanel::setPrediction( const QStringList& prediction )
|
||||
{
|
||||
m_data->predictionBar->setPrediction( prediction );
|
||||
}
|
||||
|
||||
void QskInputPanel::keyPressEvent( QKeyEvent* event )
|
||||
{
|
||||
int keyCode = -1;
|
||||
|
||||
switch( event->key() )
|
||||
{
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Escape:
|
||||
if ( m_data->predictor->parent() == this )
|
||||
{
|
||||
keyCode = event->key();
|
||||
break;
|
||||
delete m_data->predictor;
|
||||
}
|
||||
|
||||
default:
|
||||
else
|
||||
{
|
||||
const auto text = event->text();
|
||||
|
||||
if ( !text.isEmpty() )
|
||||
keyCode = text[0].unicode();
|
||||
else
|
||||
keyCode = event->key();
|
||||
m_data->predictor->disconnect( this );
|
||||
m_data->predictor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_data->keyboard->hasKey( keyCode ) )
|
||||
if ( predictor )
|
||||
{
|
||||
// animating the corresponding key button ???
|
||||
Q_EMIT keySelected( keyCode );
|
||||
if ( predictor->parent() == nullptr )
|
||||
predictor->setParent( this );
|
||||
|
||||
connect( predictor, &QskTextPredictor::predictionChanged,
|
||||
this, &QskInputEngine::updatePrediction );
|
||||
}
|
||||
|
||||
m_data->predictor = predictor;
|
||||
}
|
||||
|
||||
void QskInputEngine::applyInput( bool success )
|
||||
{
|
||||
auto item = inputItem();
|
||||
if ( item == nullptr )
|
||||
return;
|
||||
|
||||
if ( success )
|
||||
{
|
||||
if ( auto proxy = inputProxy() )
|
||||
{
|
||||
const auto value = proxy->property( "text" );
|
||||
if ( value.canConvert< QString >() )
|
||||
qskSendReplaceText( item, value.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
qskSendKey( item, success ? Qt::Key_Return : Qt::Key_Escape );
|
||||
}
|
||||
|
||||
void QskInputEngine::applyText( const QString& text, bool isFinal )
|
||||
{
|
||||
qskSendText( qskReceiverItem( this ), text, isFinal );
|
||||
}
|
||||
|
||||
void QskInputEngine::applyKey( int key )
|
||||
{
|
||||
// control keys like left/right
|
||||
qskSendKey( qskReceiverItem( this ), key );
|
||||
}
|
||||
|
||||
void QskInputEngine::commitPredictiveText( int index )
|
||||
{
|
||||
QString text;
|
||||
|
||||
if ( m_data->predictor )
|
||||
{
|
||||
text = m_data->predictor->candidate( index );
|
||||
m_data->predictor->reset();
|
||||
}
|
||||
|
||||
m_data->keyProcessor.reset();
|
||||
|
||||
showPrediction( QStringList() );
|
||||
applyText( text, true );
|
||||
}
|
||||
|
||||
void QskInputEngine::updatePrediction()
|
||||
{
|
||||
if ( m_data->predictor )
|
||||
showPrediction( m_data->predictor->candidates() );
|
||||
}
|
||||
|
||||
void QskInputEngine::setPredictionEnabled( bool on )
|
||||
{
|
||||
Q_UNUSED( on )
|
||||
}
|
||||
|
||||
void QskInputEngine::showPrediction( const QStringList& )
|
||||
{
|
||||
}
|
||||
|
||||
void QskInputEngine::commitKey( int key )
|
||||
{
|
||||
int spaceLeft = -1;
|
||||
|
||||
if ( !( m_data->inputHints & Qt::ImhMultiLine ) )
|
||||
{
|
||||
QInputMethodQueryEvent event1( Qt::ImMaximumTextLength );
|
||||
QCoreApplication::sendEvent( inputItem(), &event1 );
|
||||
|
||||
const int maxChars = event1.value( Qt::ImMaximumTextLength ).toInt();
|
||||
if ( maxChars >= 0 )
|
||||
{
|
||||
QInputMethodQueryEvent event2( Qt::ImSurroundingText );
|
||||
QCoreApplication::sendEvent( qskReceiverItem( this ), &event2 );
|
||||
|
||||
const auto text = event2.value( Qt::ImSurroundingText ).toString();
|
||||
spaceLeft = maxChars - text.length();
|
||||
}
|
||||
}
|
||||
|
||||
QskTextPredictor* predictor = nullptr;
|
||||
if ( qskUsePrediction( m_data->inputHints ) )
|
||||
predictor = m_data->predictor;
|
||||
|
||||
const auto result = m_data->keyProcessor.processKey(
|
||||
key, m_data->inputHints, predictor, spaceLeft );
|
||||
|
||||
if ( result.key )
|
||||
{
|
||||
switch( result.key )
|
||||
{
|
||||
case Qt::Key_Return:
|
||||
{
|
||||
applyInput( true );
|
||||
break;
|
||||
}
|
||||
case Qt::Key_Escape:
|
||||
{
|
||||
applyInput( false );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
applyKey( result.key );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( !result.text.isEmpty() )
|
||||
{
|
||||
applyText( result.text, result.isFinal );
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_QskInputPanel.cpp"
|
||||
#include "moc_QskInputEngine.cpp"
|
||||
|
|
|
@ -3,79 +3,66 @@
|
|||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_INPUT_PANEL_H
|
||||
#define QSK_INPUT_PANEL_H
|
||||
#ifndef QSK_INPUT_ENGINE_H
|
||||
#define QSK_INPUT_ENGINE_H
|
||||
|
||||
#include "QskGlobal.h"
|
||||
#include "QskBox.h"
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
class QskInputEngine;
|
||||
|
||||
class QString;
|
||||
class QskTextPredictor;
|
||||
class QskControl;
|
||||
class QQuickItem;
|
||||
class QLocale;
|
||||
|
||||
class QSK_EXPORT QskInputPanel : public QskBox
|
||||
class QSK_EXPORT QskInputEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskBox;
|
||||
|
||||
Q_PROPERTY( PanelHints panelHints READ panelHints
|
||||
WRITE setPanelHints NOTIFY panelHintsChanged )
|
||||
|
||||
Q_PROPERTY( QString inputPrompt READ inputPrompt
|
||||
WRITE setInputPrompt NOTIFY inputPromptChanged )
|
||||
using Inherited = QObject;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Panel, ProxyPanel, ProxyText )
|
||||
QskInputEngine( QObject* parent = nullptr );
|
||||
virtual ~QskInputEngine() override;
|
||||
|
||||
enum PanelHint
|
||||
{
|
||||
InputProxy = 1 << 0,
|
||||
Prediction = 1 << 1
|
||||
};
|
||||
virtual void attachInputItem( QQuickItem* );
|
||||
virtual void updateInputPanel( Qt::InputMethodQueries );
|
||||
|
||||
Q_ENUM( PanelHint )
|
||||
Q_DECLARE_FLAGS( PanelHints, PanelHint )
|
||||
QskControl* panel( bool doCreate );
|
||||
virtual Qt::Alignment panelAlignment() const;
|
||||
|
||||
QskInputPanel( QQuickItem* parent = nullptr );
|
||||
virtual ~QskInputPanel() override;
|
||||
|
||||
void attachInputItem( QQuickItem* );
|
||||
QQuickItem* attachedInputItem() const;
|
||||
|
||||
void setPanelHint( PanelHint, bool on );
|
||||
|
||||
void setPanelHints( PanelHints );
|
||||
PanelHints panelHints() const;
|
||||
|
||||
QQuickItem* inputProxy() const;
|
||||
|
||||
QString inputPrompt() const;
|
||||
|
||||
virtual QskAspect::Subcontrol effectiveSubcontrol(
|
||||
QskAspect::Subcontrol ) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void panelHintsChanged();
|
||||
void inputPromptChanged( const QString& );
|
||||
|
||||
void keySelected( int keyCode );
|
||||
void predictiveTextSelected( int );
|
||||
virtual QQuickItem* inputProxy() const;
|
||||
virtual QQuickItem* inputItem() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void setInputPrompt( const QString& );
|
||||
void setPrediction( const QStringList& );
|
||||
void commitKey( int keyCode );
|
||||
void commitPredictiveText( int index );
|
||||
|
||||
Q_SIGNALS:
|
||||
void activeChanged();
|
||||
void localeChanged();
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent( QKeyEvent* ) override;
|
||||
virtual QskControl* createPanel() = 0;
|
||||
virtual void attachToPanel( QQuickItem* ) = 0;
|
||||
|
||||
virtual void setPredictionEnabled( bool on );
|
||||
virtual void showPrediction( const QStringList& );
|
||||
|
||||
void applyInput( bool success );
|
||||
void applyText( const QString&, bool isFinal );
|
||||
void applyKey( int keyCode );
|
||||
|
||||
private:
|
||||
void resetPredictor( const QLocale& );
|
||||
void updatePrediction();
|
||||
|
||||
void updatePanel();
|
||||
|
||||
void updateLocale( const QLocale& );
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( QskInputPanel::PanelHints )
|
||||
Q_DECLARE_METATYPE( QskInputPanel::PanelHints )
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskInputPanel.h"
|
||||
#include "QskVirtualKeyboard.h"
|
||||
#include "QskInputPredictionBar.h"
|
||||
#include "QskTextInput.h"
|
||||
#include "QskTextLabel.h"
|
||||
#include "QskLinearBox.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QLocale>
|
||||
#include <QPointer>
|
||||
#include <QInputMethodQueryEvent>
|
||||
|
||||
namespace
|
||||
{
|
||||
class TextInputProxy final : public QskTextInput
|
||||
{
|
||||
public:
|
||||
TextInputProxy( QskInputPanel* panel, QQuickItem* parentItem = nullptr ):
|
||||
QskTextInput( parentItem ),
|
||||
m_panel( panel )
|
||||
{
|
||||
setObjectName( "InputPanelInputProxy" );
|
||||
setFocusPolicy( Qt::NoFocus );
|
||||
}
|
||||
|
||||
virtual QskAspect::Subcontrol effectiveSubcontrol(
|
||||
QskAspect::Subcontrol subControl ) const override
|
||||
{
|
||||
if ( subControl == QskTextInput::Panel )
|
||||
return m_panel->effectiveSubcontrol( QskInputPanel::ProxyPanel );
|
||||
|
||||
if ( subControl == QskTextInput::Text )
|
||||
return m_panel->effectiveSubcontrol( QskInputPanel::ProxyText );
|
||||
|
||||
return subControl;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void focusInEvent( QFocusEvent* ) override final
|
||||
{
|
||||
}
|
||||
|
||||
virtual void focusOutEvent( QFocusEvent* ) override final
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
QskInputPanel* m_panel;
|
||||
};
|
||||
}
|
||||
|
||||
QSK_SUBCONTROL( QskInputPanel, Panel )
|
||||
QSK_SUBCONTROL( QskInputPanel, ProxyPanel )
|
||||
QSK_SUBCONTROL( QskInputPanel, ProxyText )
|
||||
|
||||
class QskInputPanel::PrivateData
|
||||
{
|
||||
public:
|
||||
QPointer< QQuickItem > inputItem;
|
||||
|
||||
QskLinearBox* layout;
|
||||
QskTextLabel* prompt;
|
||||
TextInputProxy* inputProxy;
|
||||
QskInputPredictionBar* predictionBar;
|
||||
QskVirtualKeyboard* keyboard;
|
||||
|
||||
int maxChars = -1;
|
||||
|
||||
QskInputPanel::PanelHints panelHints = QskInputPanel::InputProxy;
|
||||
};
|
||||
|
||||
QskInputPanel::QskInputPanel( QQuickItem* parent ):
|
||||
Inherited( parent ),
|
||||
m_data( new PrivateData() )
|
||||
{
|
||||
setAutoLayoutChildren( true );
|
||||
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained );
|
||||
|
||||
m_data->prompt = new QskTextLabel();
|
||||
m_data->prompt->setVisible( false );
|
||||
|
||||
m_data->inputProxy = new TextInputProxy( this, nullptr );
|
||||
m_data->inputProxy->setVisible(
|
||||
m_data->panelHints & QskInputPanel::InputProxy );
|
||||
|
||||
m_data->predictionBar = new QskInputPredictionBar();
|
||||
m_data->predictionBar->setVisible(
|
||||
m_data->panelHints & QskInputPanel::Prediction );
|
||||
|
||||
m_data->keyboard = new QskVirtualKeyboard();
|
||||
|
||||
auto layout = new QskLinearBox( Qt::Vertical, this );
|
||||
|
||||
layout->addItem( m_data->prompt, Qt::AlignLeft | Qt::AlignHCenter );
|
||||
layout->addItem( m_data->inputProxy, Qt::AlignLeft | Qt::AlignHCenter );
|
||||
layout->addStretch( 10 );
|
||||
layout->addItem( m_data->predictionBar );
|
||||
layout->addItem( m_data->keyboard );
|
||||
|
||||
m_data->layout = layout;
|
||||
|
||||
connect( m_data->predictionBar, &QskInputPredictionBar::predictiveTextSelected,
|
||||
this, &QskInputPanel::predictiveTextSelected );
|
||||
|
||||
connect( m_data->keyboard, &QskVirtualKeyboard::keySelected,
|
||||
this, &QskInputPanel::keySelected );
|
||||
}
|
||||
|
||||
QskInputPanel::~QskInputPanel()
|
||||
{
|
||||
}
|
||||
|
||||
void QskInputPanel::setPanelHint( PanelHint hint, bool on )
|
||||
{
|
||||
if ( on )
|
||||
setPanelHints( m_data->panelHints | hint );
|
||||
else
|
||||
setPanelHints( m_data->panelHints & ~hint );
|
||||
}
|
||||
|
||||
void QskInputPanel::setPanelHints( PanelHints hints )
|
||||
{
|
||||
if ( hints == m_data->panelHints )
|
||||
return;
|
||||
|
||||
m_data->panelHints = hints;
|
||||
|
||||
m_data->inputProxy->setVisible( hints & QskInputPanel::InputProxy );
|
||||
m_data->predictionBar->setVisible( hints & QskInputPanel::Prediction );
|
||||
|
||||
const bool showPrompt = ( hints & QskInputPanel::InputProxy )
|
||||
&& !m_data->prompt->text().isEmpty();
|
||||
|
||||
m_data->prompt->setVisible( showPrompt );
|
||||
|
||||
Q_EMIT panelHintsChanged();
|
||||
}
|
||||
|
||||
QskInputPanel::PanelHints QskInputPanel::panelHints() const
|
||||
{
|
||||
return m_data->panelHints;
|
||||
}
|
||||
|
||||
void QskInputPanel::attachInputItem( QQuickItem* item )
|
||||
{
|
||||
if ( item == m_data->inputItem )
|
||||
return;
|
||||
|
||||
m_data->inputItem = item;
|
||||
|
||||
if ( item )
|
||||
{
|
||||
if ( m_data->panelHints & QskInputPanel::InputProxy )
|
||||
{
|
||||
m_data->inputProxy->setupFrom( item );
|
||||
m_data->inputProxy->setEditing( true );
|
||||
|
||||
// hiding the cursor in item
|
||||
const QInputMethodEvent::Attribute attribute(
|
||||
QInputMethodEvent::Cursor, 0, 0, QVariant() );
|
||||
|
||||
QInputMethodEvent event( QString(), { attribute } );
|
||||
QCoreApplication::sendEvent( item, &event );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQuickItem* QskInputPanel::attachedInputItem() const
|
||||
{
|
||||
return m_data->inputItem;
|
||||
}
|
||||
|
||||
QQuickItem* QskInputPanel::inputProxy() const
|
||||
{
|
||||
return m_data->inputProxy;
|
||||
}
|
||||
|
||||
QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol(
|
||||
QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
if( subControl == QskBox::Panel )
|
||||
return QskInputPanel::Panel;
|
||||
|
||||
#if 1
|
||||
// TODO ...
|
||||
if( subControl == QskInputPanel::ProxyPanel )
|
||||
return QskTextInput::Panel;
|
||||
|
||||
if( subControl == QskInputPanel::ProxyText )
|
||||
return QskTextInput::Text;
|
||||
#endif
|
||||
|
||||
return subControl;
|
||||
}
|
||||
|
||||
QString QskInputPanel::inputPrompt() const
|
||||
{
|
||||
return m_data->prompt->text();
|
||||
}
|
||||
|
||||
void QskInputPanel::setInputPrompt( const QString& text )
|
||||
{
|
||||
auto prompt = m_data->prompt;
|
||||
|
||||
if ( text != prompt->text() )
|
||||
{
|
||||
prompt->setText( text );
|
||||
|
||||
if ( m_data->panelHints & QskInputPanel::InputProxy )
|
||||
prompt->setVisible( !text.isEmpty() );
|
||||
|
||||
Q_EMIT inputPromptChanged( text );
|
||||
}
|
||||
}
|
||||
|
||||
void QskInputPanel::setPrediction( const QStringList& prediction )
|
||||
{
|
||||
m_data->predictionBar->setPrediction( prediction );
|
||||
}
|
||||
|
||||
void QskInputPanel::keyPressEvent( QKeyEvent* event )
|
||||
{
|
||||
int keyCode = -1;
|
||||
|
||||
switch( event->key() )
|
||||
{
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Escape:
|
||||
{
|
||||
keyCode = event->key();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
const auto text = event->text();
|
||||
|
||||
if ( !text.isEmpty() )
|
||||
keyCode = text[0].unicode();
|
||||
else
|
||||
keyCode = event->key();
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_data->keyboard->hasKey( keyCode ) )
|
||||
{
|
||||
// animating the corresponding key button ???
|
||||
Q_EMIT keySelected( keyCode );
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_QskInputPanel.cpp"
|
|
@ -0,0 +1,81 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_INPUT_PANEL_H
|
||||
#define QSK_INPUT_PANEL_H
|
||||
|
||||
#include "QskGlobal.h"
|
||||
#include "QskBox.h"
|
||||
|
||||
class QskInputEngine;
|
||||
|
||||
class QString;
|
||||
class QLocale;
|
||||
|
||||
class QSK_EXPORT QskInputPanel : public QskBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskBox;
|
||||
|
||||
Q_PROPERTY( PanelHints panelHints READ panelHints
|
||||
WRITE setPanelHints NOTIFY panelHintsChanged )
|
||||
|
||||
Q_PROPERTY( QString inputPrompt READ inputPrompt
|
||||
WRITE setInputPrompt NOTIFY inputPromptChanged )
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Panel, ProxyPanel, ProxyText )
|
||||
|
||||
enum PanelHint
|
||||
{
|
||||
InputProxy = 1 << 0,
|
||||
Prediction = 1 << 1
|
||||
};
|
||||
|
||||
Q_ENUM( PanelHint )
|
||||
Q_DECLARE_FLAGS( PanelHints, PanelHint )
|
||||
|
||||
QskInputPanel( QQuickItem* parent = nullptr );
|
||||
virtual ~QskInputPanel() override;
|
||||
|
||||
void attachInputItem( QQuickItem* );
|
||||
QQuickItem* attachedInputItem() const;
|
||||
|
||||
void setPanelHint( PanelHint, bool on );
|
||||
|
||||
void setPanelHints( PanelHints );
|
||||
PanelHints panelHints() const;
|
||||
|
||||
QQuickItem* inputProxy() const;
|
||||
|
||||
QString inputPrompt() const;
|
||||
|
||||
virtual QskAspect::Subcontrol effectiveSubcontrol(
|
||||
QskAspect::Subcontrol ) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void panelHintsChanged();
|
||||
void inputPromptChanged( const QString& );
|
||||
|
||||
void keySelected( int keyCode );
|
||||
void predictiveTextSelected( int );
|
||||
|
||||
public Q_SLOTS:
|
||||
void setInputPrompt( const QString& );
|
||||
void setPrediction( const QStringList& );
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent( QKeyEvent* ) override;
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( QskInputPanel::PanelHints )
|
||||
Q_DECLARE_METATYPE( QskInputPanel::PanelHints )
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue