qskinny/inputcontext/QskInputCompositionModel.cpp

263 lines
6.4 KiB
C++
Raw Normal View History

2018-02-06 14:55:35 +01:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
2017-07-21 18:21:34 +02:00
#include "QskInputCompositionModel.h"
2018-04-04 15:19:51 +02:00
#include "QskInputContext.h"
2017-07-21 18:21:34 +02:00
#include <QGuiApplication>
#include <QInputMethodEvent>
#include <QTextCharFormat>
2018-04-02 17:01:04 +02:00
static inline QString qskKeyString( int code )
2017-07-21 18:21:34 +02:00
{
// Special case entry codes here, else default to the symbol
switch ( code )
{
case Qt::Key_Shift:
case Qt::Key_CapsLock:
case Qt::Key_Mode_switch:
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
return QString();
2017-07-21 18:21:34 +02:00
case Qt::Key_Return:
case Qt::Key_Kanji:
return QChar( QChar::CarriageReturn );
2017-07-21 18:21:34 +02:00
case Qt::Key_Space:
return QChar( QChar::Space );
2017-07-21 18:21:34 +02:00
default:
break;
}
return QChar( code );
2017-07-21 18:21:34 +02:00
}
class QskInputCompositionModel::PrivateData
{
public:
QString preedit;
};
2018-04-04 15:19:51 +02:00
QskInputCompositionModel::QskInputCompositionModel( QskInputContext* context ):
QObject( context ),
2017-07-21 18:21:34 +02:00
m_data( new PrivateData )
{
}
QskInputCompositionModel::~QskInputCompositionModel()
{
}
2018-04-04 15:19:51 +02:00
QskInputContext* QskInputCompositionModel::context() const
{
return qobject_cast< QskInputContext* >( parent() );
}
bool QskInputCompositionModel::supportsSuggestions() const
{
return false;
}
2017-07-21 18:21:34 +02:00
void QskInputCompositionModel::composeKey( Qt::Key key )
{
/*
* This operation might be expensive (e.g. for Hunspell) and
* should be done asynchronously to be able to run e.g. suggestions
* in a separate thread to not block the UI.
* TODO
*/
2018-04-04 15:19:51 +02:00
const auto queryEvent = context()->queryInputMethod(
2017-07-21 18:21:34 +02:00
Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints );
2018-04-04 15:19:51 +02:00
2017-07-21 18:21:34 +02:00
const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() );
const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt();
const int currentLength = queryEvent.value( Qt::ImSurroundingText ).toString().length();
int spaceLeft = -1;
2018-04-04 15:19:51 +02:00
if ( !( hints & Qt::ImhMultiLine ) && maxLength > 0 )
2017-07-21 18:21:34 +02:00
spaceLeft = maxLength - currentLength;
switch ( key )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
2017-08-31 12:36:10 +02:00
{
2018-04-04 15:19:51 +02:00
if ( !m_data->preedit.isEmpty() )
{
m_data->preedit.chop( 1 );
2018-04-11 11:02:29 +02:00
sendPreeditTextEvent( polishPreedit( m_data->preedit ) );
2018-04-04 15:19:51 +02:00
}
else
{
// Backspace one character only if preedit was inactive
sendKeyEvents( Qt::Key_Backspace );
}
2017-07-21 18:21:34 +02:00
return;
2017-08-31 12:36:10 +02:00
}
2017-07-21 18:21:34 +02:00
case Qt::Key_Space:
{
if( !spaceLeft )
2017-07-21 18:21:34 +02:00
{
return;
2017-07-21 18:21:34 +02:00
}
if( !m_data->preedit.isEmpty() )
2017-07-21 18:21:34 +02:00
{
commit( m_data->preedit.left( spaceLeft ) );
2017-07-21 18:21:34 +02:00
}
commit( qskKeyString( key ) );
2017-07-21 18:21:34 +02:00
return;
}
2017-07-21 18:21:34 +02:00
case Qt::Key_Return:
{
if ( !spaceLeft )
return;
// Commit what is in the buffer
if( !m_data->preedit.isEmpty() )
{
2017-07-21 18:21:34 +02:00
commit( m_data->preedit.left( spaceLeft ) );
}
else if( hints & Qt::ImhMultiLine )
{
2018-04-01 12:47:44 +02:00
commit( qskKeyString( key ) );
}
else
{
2018-04-04 15:19:51 +02:00
sendKeyEvents( Qt::Key_Return );
}
2018-04-02 17:01:04 +02:00
2018-03-30 15:04:26 +02:00
return;
2017-08-31 12:36:10 +02:00
}
2017-07-21 18:21:34 +02:00
case Qt::Key_Left:
case Qt::Key_Right:
2017-08-31 12:36:10 +02:00
{
2018-04-04 15:19:51 +02:00
if ( m_data->preedit.isEmpty() )
sendKeyEvents( key );
2017-07-21 18:21:34 +02:00
return;
2017-08-31 12:36:10 +02:00
}
2017-07-21 18:21:34 +02:00
default:
2017-08-31 12:36:10 +02:00
{
2017-07-21 18:21:34 +02:00
if ( !spaceLeft )
return;
2017-08-31 12:36:10 +02:00
}
2017-07-21 18:21:34 +02:00
}
if ( hints & Qt::ImhHiddenText )
{
commit( qskKeyString( key ) );
return;
}
const auto firstCandidate = candidateCount() > 0 ? candidate( 0 ) : QString();
2017-07-21 18:21:34 +02:00
const auto oldPreedit = m_data->preedit;
2018-04-11 11:02:29 +02:00
2017-07-21 18:21:34 +02:00
m_data->preedit += qskKeyString( key );
auto displayPreedit = polishPreedit( m_data->preedit );
// If there is no intermediate, decide between committing the first candidate and skipping
if ( !hasIntermediate() )
{
// Skip preedit phase if there are no candidates/intermediates
if ( firstCandidate.isEmpty() )
{
commit( oldPreedit.left( spaceLeft ) );
2017-10-30 08:33:43 +01:00
spaceLeft -= oldPreedit.leftRef( spaceLeft ).length();
2017-07-21 18:21:34 +02:00
}
else
{
commit( firstCandidate );
--spaceLeft;
}
if ( !spaceLeft )
return;
m_data->preedit = qskKeyString( key );
displayPreedit = polishPreedit( m_data->preedit );
2017-07-21 18:21:34 +02:00
if ( !hasIntermediate() )
{
commit( m_data->preedit );
2017-07-21 18:21:34 +02:00
return;
}
}
2018-04-11 11:02:29 +02:00
sendPreeditTextEvent( displayPreedit );
2017-07-21 18:21:34 +02:00
}
void QskInputCompositionModel::clearPreedit()
{
m_data->preedit.clear();
polishPreedit( m_data->preedit );
}
int QskInputCompositionModel::candidateCount() const
{
return 0;
}
QString QskInputCompositionModel::candidate( int ) const
2017-07-21 18:21:34 +02:00
{
return QString();
2017-07-21 18:21:34 +02:00
}
QString QskInputCompositionModel::polishPreedit( const QString& preedit )
{
return preedit;
}
void QskInputCompositionModel::commit( const QString& text )
{
2018-04-11 11:02:29 +02:00
QInputMethodEvent event;
2018-04-02 17:01:04 +02:00
event.setCommitString( text );
2018-04-11 11:02:29 +02:00
context()->sendEventToInputItem( &event );
2018-04-02 17:01:04 +02:00
2018-04-11 11:02:29 +02:00
clearPreedit();
2017-07-21 18:21:34 +02:00
}
void QskInputCompositionModel::commitCandidate( int index )
{
commit( candidate( index ) );
2017-07-21 18:21:34 +02:00
}
2018-04-11 11:02:29 +02:00
void QskInputCompositionModel::sendPreeditTextEvent( const QString& text )
2017-07-21 18:21:34 +02:00
{
2018-04-11 11:02:29 +02:00
QTextCharFormat format;
format.setFontUnderline( true );
const QInputMethodEvent::Attribute attribute(
QInputMethodEvent::TextFormat, 0, text.length(), format );
QInputMethodEvent event( text, { attribute } );
context()->sendEventToInputItem( &event );
2017-07-21 18:21:34 +02:00
}
2018-04-04 15:19:51 +02:00
void QskInputCompositionModel::sendKeyEvents( int key )
2017-07-21 18:21:34 +02:00
{
2018-04-04 15:19:51 +02:00
auto context = this->context();
2017-07-21 18:21:34 +02:00
2018-04-04 15:19:51 +02:00
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
context->sendEventToInputItem( &keyPress );
2017-07-21 18:21:34 +02:00
2018-04-04 15:19:51 +02:00
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
context->sendEventToInputItem( &keyRelease );
2018-03-14 17:30:39 +01:00
}
2017-07-21 18:21:34 +02:00
bool QskInputCompositionModel::hasIntermediate() const
{
return false;
}
#include "moc_QskInputCompositionModel.cpp"