qskinny/src/inputpanel/QskInputPanel.cpp

525 lines
13 KiB
C++
Raw Normal View History

2018-04-06 15:30:24 +00:00
/******************************************************************************
* 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 "QskInputEngine.h"
#include "QskVirtualKeyboard.h"
#include "QskInputPredictionBar.h"
#include "QskTextInput.h"
#include "QskTextLabel.h"
#include "QskLinearBox.h"
2018-04-06 15:30:24 +00:00
#include <QString>
#include <QLocale>
#include <QGuiApplication>
#include <QPointer>
#include <QInputMethodQueryEvent>
#include <QTextCharFormat>
static inline void qskSendText( QQuickItem* inputItem,
const QString& text, bool isFinal )
2018-04-06 15:30:24 +00:00
{
if ( inputItem == nullptr )
return;
2018-04-06 15:30:24 +00:00
if ( isFinal )
{
QInputMethodEvent event;
event.setCommitString( text );
2018-04-06 15:30:24 +00:00
QCoreApplication::sendEvent( inputItem, &event );
}
else
{
QTextCharFormat format;
format.setFontUnderline( true );
2018-04-06 15:30:24 +00:00
const QInputMethodEvent::Attribute attribute(
QInputMethodEvent::TextFormat, 0, text.length(), format );
2018-04-06 15:30:24 +00:00
QInputMethodEvent event( text, { attribute } );
2018-04-06 15:30:24 +00:00
QCoreApplication::sendEvent( inputItem, &event );
}
}
2018-04-06 15:30:24 +00:00
static inline void qskSendKey( QQuickItem* inputItem, int key )
{
if ( inputItem == nullptr )
return;
2018-04-06 15:30:24 +00:00
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyPress );
2018-04-06 15:30:24 +00:00
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyRelease );
2018-04-06 15:30:24 +00:00
}
namespace
{
class TextInput : public QskTextInput
{
public:
TextInput( QQuickItem* parentItem = nullptr ):
QskTextInput( parentItem )
{
}
};
}
QSK_SUBCONTROL( QskInputPanel, Panel )
class QskInputPanel::PrivateData
{
public:
PrivateData():
hasInputProxy( true )
{
}
QPointer< QskInputEngine > engine;
QPointer< QQuickItem > inputItem;
2018-04-12 10:03:51 +00:00
QskLinearBox* layout;
QskTextLabel* prompt;
TextInput* inputProxy;
QskInputPredictionBar* predictionBar;
QskVirtualKeyboard* keyboard;
bool hasInputProxy : 1;
};
QskInputPanel::QskInputPanel( QQuickItem* parent ):
Inherited( parent ),
m_data( new PrivateData() )
{
setAutoLayoutChildren( true );
2018-04-12 10:03:51 +00:00
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained );
m_data->prompt = new QskTextLabel();
m_data->prompt->setVisible( false );
m_data->inputProxy = new TextInput();
m_data->inputProxy->setVisible( m_data->hasInputProxy );
m_data->predictionBar = new QskInputPredictionBar();
m_data->predictionBar->setVisible( false );
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::commitPredictiveText );
connect( m_data->keyboard, &QskVirtualKeyboard::keySelected,
this, &QskInputPanel::commitKey );
}
QskInputPanel::~QskInputPanel()
{
}
void QskInputPanel::setEngine( QskInputEngine* engine )
2018-04-12 10:03:51 +00:00
{
if ( engine == m_data->engine )
return;
if ( m_data->engine )
m_data->engine->disconnect( this );
m_data->engine = engine;
2018-04-12 10:03:51 +00:00
if ( engine )
2018-04-12 10:03:51 +00:00
{
connect( engine, &QskInputEngine::predictionChanged,
this, &QskInputPanel::updatePredictionBar );
2018-04-12 10:03:51 +00:00
}
m_data->predictionBar->setVisible( engine && engine->predictor() );
2018-04-12 10:03:51 +00:00
}
void QskInputPanel::attachInputItem( QQuickItem* item )
2018-04-12 10:03:51 +00:00
{
if ( item == m_data->inputItem )
return;
2018-04-12 10:03:51 +00:00
m_data->inputItem = item;
2018-04-12 10:03:51 +00:00
if ( m_data->engine )
m_data->engine->reset();
if ( item )
2018-04-12 10:03:51 +00:00
{
Qt::InputMethodQueries queries = Qt::ImQueryAll;
queries &= ~Qt::ImEnabled;
processInputMethodQueries( queries );
2018-04-12 10:03:51 +00:00
}
}
2018-04-12 10:03:51 +00:00
QQuickItem* QskInputPanel::attachedInputItem() const
{
return m_data->inputItem;
}
void QskInputPanel::updatePredictionBar()
{
m_data->predictionBar->setPrediction(
m_data->engine->prediction() );
}
2018-04-12 10:03:51 +00:00
QskInputEngine* QskInputPanel::engine()
{
return m_data->engine;
}
QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
{
if( subControl == QskBox::Panel )
return QskInputPanel::Panel;
return subControl;
2018-04-12 10:03:51 +00:00
}
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->hasInputProxy )
prompt->setVisible( !text.isEmpty() );
Q_EMIT inputPromptChanged( text );
}
}
bool QskInputPanel::hasInputProxy() const
{
return m_data->hasInputProxy;
}
void QskInputPanel::setInputProxy( bool on )
{
if ( m_data->hasInputProxy == on )
return;
m_data->hasInputProxy = on;
m_data->inputProxy->setVisible( on );
auto prompt = m_data->prompt;
if ( on )
prompt->setVisible( !prompt->text().isEmpty() );
else
prompt->setVisible( false );
}
void QskInputPanel::updateInputProxy( const QQuickItem* inputItem )
{
if ( inputItem == nullptr )
return;
QInputMethodQueryEvent event( Qt::ImQueryAll );
QCoreApplication::sendEvent( const_cast< QQuickItem* >( inputItem ), &event );
const auto proxy = m_data->inputProxy;
if ( event.queries() & Qt::ImHints )
{
const auto hints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
const auto echoMode = ( hints & Qt::ImhHiddenText )
? QskTextInput::PasswordEchoOnEdit : QskTextInput::Normal;
proxy->setEchoMode( echoMode );
}
if ( event.queries() & Qt::ImSurroundingText )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
proxy->setText( text );
}
if ( event.queries() & Qt::ImCursorPosition )
{
const auto pos = event.value( Qt::ImCursorPosition ).toInt();
proxy->setCursorPosition( pos );
}
#if 0
if ( event.queries() & Qt::ImCurrentSelection )
{
const auto text = event.value( Qt::ImCursorPosition ).toString();
if ( !text.isEmpty() )
{
}
}
#endif
if ( event.queries() & Qt::ImMaximumTextLength )
{
const auto length = event.value( Qt::ImMaximumTextLength ).toInt();
proxy->setMaxLength( length );
}
}
void QskInputPanel::commitPredictiveText( int index )
{
m_data->predictionBar->setPrediction( QVector< QString >() );
if ( m_data->engine )
{
const QString text = m_data->engine->predictiveText( index );
m_data->engine->reset();
qskSendText( m_data->inputItem, text, true );
}
}
void QskInputPanel::commitKey( int key )
{
if ( m_data->engine == nullptr || m_data->inputItem == nullptr )
return;
auto engine = m_data->engine;
auto inputItem = m_data->inputItem;
QInputMethodQueryEvent event( Qt::ImHints );
QCoreApplication::sendEvent( inputItem, &event );
const auto inputHints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
int spaceLeft = -1;
if ( !( inputHints & Qt::ImhMultiLine ) )
{
QInputMethodQueryEvent event(
Qt::ImSurroundingText | Qt::ImMaximumTextLength );
QCoreApplication::sendEvent( inputItem, &event );
const int max = event.value( Qt::ImMaximumTextLength ).toInt();
if ( max > 0 )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
spaceLeft = max - text.length();
}
}
processKey( key, inputHints, spaceLeft );
}
void QskInputPanel::processKey( int key,
Qt::InputMethodHints inputHints, int spaceLeft )
{
const auto result = m_data->engine->processKey( key, inputHints, spaceLeft );
auto inputItem = m_data->inputItem;
if ( result.key )
{
// sending a control key
qskSendKey( inputItem, result.key );
}
else if ( !result.text.isEmpty() )
{
// changing the current text
qskSendText( inputItem, result.text, result.isFinal );
}
}
void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
{
if ( m_data->inputItem == nullptr )
return;
/*
adjust the input panel to information provided from the input item
*/
QInputMethodQueryEvent queryEvent( queries );
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
if ( queryEvent.queries() & Qt::ImHints )
{
/*
ImhHiddenText = 0x1, // might need to disable certain checks
ImhSensitiveData = 0x2, // shouldn't change anything
ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it
ImhPreferNumbers = 0x8, // default to number keyboard
ImhPreferUppercase = 0x10, // start with shift on
ImhPreferLowercase = 0x20, // start with shift off
ImhNoPredictiveText = 0x40, // not use predictive text
ImhDate = 0x80, // ignored for now (no date keyboard)
ImhTime = 0x100, // ignored for know (no time keyboard)
ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode
ImhMultiLine = 0x400, // not useful?
ImhDigitsOnly // default to number keyboard, disable other keys
ImhFormattedNumbersOnly // hard to say
ImhUppercaseOnly // caps-lock, disable shift
ImhLowercaseOnly // disable shift
ImhDialableCharactersOnly // dial pad (calculator?)
ImhEmailCharactersOnly // disable certain symbols (email-only kb?)
ImhUrlCharactersOnly // disable certain symbols (url-only kb?)
ImhLatinOnly // disable chinese input
*/
#if 0
const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() );
#endif
}
#if 0
if ( queryEvent.queries() & Qt::ImPreferredLanguage )
{
}
#endif
/*
Qt::ImMicroFocus
Qt::ImCursorRectangle
Qt::ImFont
Qt::ImCursorPosition
Qt::ImSurroundingText // important for chinese input
Qt::ImCurrentSelection // important for prediction
Qt::ImMaximumTextLength // should be monitored
Qt::ImAnchorPosition
Qt::ImAbsolutePosition
Qt::ImTextBeforeCursor // important for chinese
Qt::ImTextAfterCursor // important for chinese
Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType
Qt::ImAnchorRectangle
Qt::ImInputItemClipRectangle // could be used for the geometry of the panel
*/
}
2018-04-13 14:32:48 +00:00
void QskInputPanel::keyPressEvent( QKeyEvent* event )
{
// animate the corresponding key button TODO
switch( event->key() )
{
case Qt::Key_Return:
case Qt::Key_Escape:
{
commitKey( event->key() );
break;
}
default:
{
const auto text = event->text();
if ( !text.isEmpty() )
commitKey( text[0].unicode() );
else
commitKey( event->key() );
}
}
}
void QskInputPanel::keyReleaseEvent( QKeyEvent* event )
{
return Inherited::keyReleaseEvent( event );
}
qreal QskInputPanel::heightForWidth( qreal width ) const
{
/*
This code looks like as it could be generalized
and moved to QskLinearBox. TODO ...
*/
const auto margins = this->margins();
width -= margins.left() + margins.right();
const auto padding = innerPadding(
Panel, QSizeF( width, width ) );
width -= padding.left() + padding.right();
qreal height = m_data->keyboard->heightForWidth( width );
const QskControl* controls[] =
{ m_data->prompt, m_data->inputProxy, m_data->predictionBar };
for ( auto control : controls )
{
if ( control->isVisible() )
{
height += m_data->layout->spacing();
height += control->sizeHint().height();
}
}
height += padding.top() + padding.bottom();
height += margins.top() + margins.bottom();
return height;
}
qreal QskInputPanel::widthForHeight( qreal height ) const
{
const auto margins = this->margins();
height -= margins.top() + margins.bottom();
const auto padding = innerPadding(
Panel, QSizeF( height, height ) );
height -= padding.top() + padding.bottom();
const QskControl* controls[] =
{ m_data->prompt, m_data->inputProxy, m_data->predictionBar };
for ( auto control : controls )
{
if ( control->isVisible() )
{
height -= m_data->layout->spacing();
height -= control->sizeHint().height();
}
}
qreal width = m_data->keyboard->widthForHeight( height );
width += padding.left() + padding.right();
width += margins.left() + margins.right();
return width;
}
#include "moc_QskInputPanel.cpp"