306 lines
8.2 KiB
C++
306 lines
8.2 KiB
C++
#include "QskInputCompositionModel.h"
|
|
|
|
#include <QGuiApplication>
|
|
#include <QInputMethodEvent>
|
|
#include <QTextCharFormat>
|
|
#include <QWindow>
|
|
|
|
#include <unordered_map>
|
|
|
|
static QString qskKeyString( int code )
|
|
{
|
|
// 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();
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Kanji:
|
|
return QChar(QChar::CarriageReturn);
|
|
case Qt::Key_Space:
|
|
return QChar(QChar::Space);
|
|
default:
|
|
break;
|
|
}
|
|
return QChar(code);
|
|
}
|
|
|
|
class QskInputCompositionModel::PrivateData
|
|
{
|
|
public:
|
|
// QInputMethod
|
|
QString preedit;
|
|
QTextCharFormat preeditFormat;
|
|
QList< QInputMethodEvent::Attribute > preeditAttributes;
|
|
|
|
int groupIndex;
|
|
};
|
|
|
|
static inline void sendCompositionEvent( QInputMethodEvent* e )
|
|
{
|
|
if ( auto focusObject = QGuiApplication::focusObject() )
|
|
QCoreApplication::sendEvent( focusObject, e );
|
|
}
|
|
|
|
QskInputCompositionModel::QskInputCompositionModel():
|
|
m_data( new PrivateData )
|
|
{
|
|
m_data->groupIndex = 0;
|
|
|
|
m_data->preeditFormat.setFontUnderline(true);
|
|
m_data->preeditAttributes.append( QInputMethodEvent::Attribute(
|
|
QInputMethodEvent::TextFormat, 0, 0, m_data->preeditFormat ) );
|
|
}
|
|
|
|
QskInputCompositionModel::~QskInputCompositionModel()
|
|
{
|
|
}
|
|
|
|
void QskInputCompositionModel::composeKey( Qt::Key key )
|
|
{
|
|
auto inputMethod = QGuiApplication::inputMethod();
|
|
if ( !inputMethod )
|
|
return;
|
|
|
|
auto focusObject = QGuiApplication::focusObject();
|
|
if ( !focusObject )
|
|
return;
|
|
|
|
QInputMethodQueryEvent queryEvent(
|
|
Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints );
|
|
QCoreApplication::sendEvent( focusObject, &queryEvent );
|
|
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;
|
|
if ( !( hints& Qt::ImhMultiLine ) && maxLength > 0 )
|
|
spaceLeft = maxLength - currentLength;
|
|
|
|
switch ( key )
|
|
{
|
|
case Qt::Key_Backspace:
|
|
case Qt::Key_Muhenkan:
|
|
{
|
|
backspace();
|
|
return;
|
|
}
|
|
case Qt::Key_Space:
|
|
{
|
|
if ( !spaceLeft )
|
|
return;
|
|
|
|
if ( !m_data->preedit.isEmpty() )
|
|
{
|
|
if ( candidateCount() > 0 ) // Commit first candidate
|
|
commit( QChar( candidate( 0 ) ) );
|
|
else // Commit what is in the buffer
|
|
commit( m_data->preedit.left( spaceLeft ) );
|
|
}
|
|
else
|
|
{
|
|
commit( qskKeyString(key) );
|
|
}
|
|
return;
|
|
}
|
|
case Qt::Key_Return:
|
|
{
|
|
if ( !spaceLeft )
|
|
return;
|
|
|
|
// Commit what is in the buffer
|
|
if ( !m_data->preedit.isEmpty() )
|
|
commit( m_data->preedit.left( spaceLeft ) );
|
|
else if ( hints & Qt::ImhMultiLine )
|
|
commit( qskKeyString( key ) );
|
|
|
|
return;
|
|
}
|
|
case Qt::Key_Left:
|
|
case Qt::Key_Right:
|
|
{
|
|
moveCursor( key );
|
|
return;
|
|
}
|
|
default:
|
|
{
|
|
if ( !spaceLeft )
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( hints & Qt::ImhHiddenText )
|
|
{
|
|
commit( qskKeyString( key ) );
|
|
return;
|
|
}
|
|
|
|
const auto firstCandidate = candidateCount() > 0 ? QChar( candidate( 0 ) ) : QString();
|
|
const auto oldPreedit = m_data->preedit;
|
|
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 ) );
|
|
spaceLeft -= oldPreedit.leftRef( spaceLeft ).length();
|
|
}
|
|
else
|
|
{
|
|
commit( firstCandidate );
|
|
--spaceLeft;
|
|
}
|
|
|
|
if ( !spaceLeft )
|
|
return;
|
|
|
|
m_data->preedit = qskKeyString( key );
|
|
displayPreedit = polishPreedit( m_data->preedit );
|
|
if ( !hasIntermediate() )
|
|
{
|
|
commit(m_data->preedit);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_data->preeditAttributes.first().length = displayPreedit.length();
|
|
QInputMethodEvent e(displayPreedit, m_data->preeditAttributes);
|
|
sendCompositionEvent(&e);
|
|
}
|
|
|
|
void QskInputCompositionModel::clearPreedit()
|
|
{
|
|
m_data->preedit.clear();
|
|
polishPreedit( m_data->preedit );
|
|
}
|
|
|
|
int QskInputCompositionModel::candidateCount() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Qt::Key QskInputCompositionModel::candidate( int ) const
|
|
{
|
|
return Qt::Key( 0 );
|
|
}
|
|
|
|
// This method is called before displaying a new preedit string. It can be used
|
|
// to return a modified preedit string which is not stored as the underlying
|
|
// preedit text. This allows for marking up the preedit text without changing the
|
|
// data model. If the actual text needs to be modified, it is safe to call
|
|
// setPreeditText() here.
|
|
QString QskInputCompositionModel::polishPreedit( const QString& preedit )
|
|
{
|
|
return preedit;
|
|
}
|
|
|
|
void QskInputCompositionModel::commit( const QString& text )
|
|
{
|
|
QInputMethodEvent e( QString(), { } );
|
|
e.setCommitString( text );
|
|
sendCompositionEvent( &e );
|
|
m_data->preedit.clear();
|
|
polishPreedit( m_data->preedit );
|
|
}
|
|
|
|
void QskInputCompositionModel::commitCandidate( int index )
|
|
{
|
|
commit( qskKeyString( candidate( index ) ) );
|
|
}
|
|
|
|
void QskInputCompositionModel::backspace()
|
|
{
|
|
auto focusWindow = QGuiApplication::focusWindow();
|
|
if ( !focusWindow )
|
|
return;
|
|
|
|
if ( !m_data->preedit.isEmpty() )
|
|
{
|
|
m_data->preedit.chop(1);
|
|
}
|
|
else
|
|
{
|
|
// Backspace one character only if preedit was inactive
|
|
QKeyEvent keyPress( QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier );
|
|
QKeyEvent keyRelease( QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier );
|
|
QCoreApplication::sendEvent( focusWindow, &keyPress );
|
|
QCoreApplication::sendEvent( focusWindow, &keyRelease );
|
|
return;
|
|
}
|
|
|
|
const QString displayText = polishPreedit( m_data->preedit );
|
|
m_data->preeditAttributes.first().length = displayText.length();
|
|
QInputMethodEvent e( displayText, m_data->preeditAttributes );
|
|
sendCompositionEvent( &e );
|
|
}
|
|
|
|
void QskInputCompositionModel::moveCursor( Qt::Key key )
|
|
{
|
|
if ( key != Qt::Key_Left && key != Qt::Key_Right )
|
|
return;
|
|
|
|
auto focusWindow = QGuiApplication::focusWindow();
|
|
if ( !focusWindow )
|
|
return;
|
|
|
|
// Moving cursor is disabled when preedit is active.
|
|
if ( !m_data->preedit.isEmpty() )
|
|
return;
|
|
|
|
QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier );
|
|
QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier );
|
|
QCoreApplication::sendEvent( focusWindow, &moveCursorPress );
|
|
QCoreApplication::sendEvent( focusWindow, &moveCursorRelease );
|
|
}
|
|
|
|
bool QskInputCompositionModel::hasIntermediate() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool QskInputCompositionModel::isComposable(const QStringRef& preedit) const
|
|
{
|
|
Q_UNUSED(preedit);
|
|
return false;
|
|
}
|
|
|
|
int QskInputCompositionModel::groupIndex() const
|
|
{
|
|
return m_data->groupIndex;
|
|
}
|
|
|
|
void QskInputCompositionModel::setGroupIndex( int groupIndex )
|
|
{
|
|
if ( groupIndex == m_data->groupIndex )
|
|
return;
|
|
|
|
m_data->groupIndex = groupIndex;
|
|
const QString displayText = polishPreedit( m_data->preedit );
|
|
m_data->preeditAttributes.first().length = displayText.length();
|
|
QInputMethodEvent e( displayText, m_data->preeditAttributes );
|
|
sendCompositionEvent( &e );
|
|
}
|
|
|
|
QVector< Qt::Key > QskInputCompositionModel::groups() const
|
|
{
|
|
return QVector< Qt::Key >();
|
|
}
|
|
|
|
bool QskInputCompositionModel::nextGroupIndex(int& index, bool forward) const
|
|
{
|
|
Q_UNUSED(index);
|
|
Q_UNUSED(forward);
|
|
return false;
|
|
}
|
|
|
|
#include "moc_QskInputCompositionModel.cpp"
|