6#include "QskInputPanel.h"
7#include "QskInputContext.h"
8#include "QskTextPredictor.h"
11#include <qtextformat.h>
24static void qskRegisterInputPanel()
26 qRegisterMetaType< Result >(
"Result" );
29Q_CONSTRUCTOR_FUNCTION( qskRegisterInputPanel )
31static inline QQuickItem* qskReceiverItem(
const QskInputPanel* panel )
33 if (
auto item = panel->inputProxy() )
36 return panel->inputItem();
39static inline bool qskUsePrediction( Qt::InputMethodHints hints )
41 constexpr Qt::InputMethodHints mask =
42 Qt::ImhNoPredictiveText | Qt::ImhExclusiveInputMask | Qt::ImhHiddenText;
44 return ( hints & mask ) == 0;
47static inline void qskSendText(
48 QQuickItem* receiver,
const QString& text,
bool isFinal )
50 if ( receiver ==
nullptr )
55 QInputMethodEvent event;
63 event.setCommitString( text, 0, 1 );
65 event.setCommitString( text );
67 QCoreApplication::sendEvent( receiver, &event );
71 QTextCharFormat format;
72 format.setFontUnderline(
true );
74 const QInputMethodEvent::Attribute attribute(
75 QInputMethodEvent::TextFormat, 0, text.length(), format );
77 QInputMethodEvent event( text, { attribute } );
79 QCoreApplication::sendEvent( receiver, &event );
83static inline void qskSendReplaceText( QQuickItem* receiver,
const QString& text )
85 if ( receiver ==
nullptr )
88 QInputMethodEvent::Attribute attribute(
89 QInputMethodEvent::Selection, 0, 32767, QVariant() );
91 QInputMethodEvent event( QString(), { attribute } );
92 QCoreApplication::sendEvent( receiver, &event );
94 qskSendText( receiver, text,
true );
97static inline void qskSendKey( QQuickItem* receiver,
int key )
99 if ( receiver ==
nullptr )
102 QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
103 QCoreApplication::sendEvent( receiver, &keyPress );
105 QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
106 QCoreApplication::sendEvent( receiver, &keyRelease );
111 class KeyProcessor :
public QObject
116 QString preedit()
const
122 int key, Qt::InputMethodHints inputHints,
QskInputPanel* panel,
126 m_currentResult.isFinal =
true;
127 m_currentResult.text.clear();
128 m_currentResult.key = 0;
130 m_predictor = predictor;
131 m_spaceLeft = spaceLeft;
137 case Qt::Key_Backspace:
138 case Qt::Key_Muhenkan:
140 if ( predictor && !m_preedit.isEmpty() )
144 m_currentResult.text = m_preedit;
145 m_currentResult.isFinal =
false;
147 Q_EMIT panel->predictionRequested( m_preedit );
150 Q_EMIT keyProcessingFinished( m_currentResult );
154 m_currentResult.key = Qt::Key_Backspace;
155 Q_EMIT keyProcessingFinished( m_currentResult );
164 if ( !m_preedit.isEmpty() )
168 m_currentResult.text = m_preedit.left( spaceLeft );
169 m_currentResult.isFinal =
true;
173 Q_EMIT keyProcessingFinished( m_currentResult );
178 if ( !( inputHints & Qt::ImhMultiLine ) )
180 m_currentResult.key = Qt::Key_Return;
181 Q_EMIT keyProcessingFinished( m_currentResult );
191 if ( !m_preedit.isEmpty() && spaceLeft )
193 m_preedit += keyString( key );
194 m_preedit = m_preedit.left( spaceLeft );
196 m_currentResult.text = m_preedit;
197 m_currentResult.isFinal =
true;
201 Q_EMIT keyProcessingFinished( m_currentResult );
213 m_currentResult.key = key;
214 Q_EMIT keyProcessingFinished( m_currentResult );
219 const QString text = keyString( key );
224 Q_EMIT panel->predictionRequested( m_preedit );
228 m_currentResult.text = text;
229 m_currentResult.isFinal =
true;
230 Q_EMIT keyProcessingFinished( m_currentResult );
239 void continueProcessingKey(
const QStringList& candidates )
243 if ( candidates.count() > 0 )
245 m_currentResult.text = m_preedit;
246 m_currentResult.isFinal =
false;
250 m_currentResult.text = m_preedit.left( m_spaceLeft );
251 m_currentResult.isFinal =
true;
257 Q_EMIT keyProcessingFinished( m_currentResult );
261 void keyProcessingFinished(
const Result& );
264 inline QString keyString(
int keyCode )
const
270 case Qt::Key_CapsLock:
271 case Qt::Key_Mode_switch:
272 case Qt::Key_Backspace:
273 case Qt::Key_Muhenkan:
278 return QChar( QChar::CarriageReturn );
281 return QChar( QChar::Space );
287 return QChar( keyCode );
291 int m_spaceLeft = -1;
293 Result m_currentResult;
297class QskInputPanel::PrivateData
305 KeyProcessor keyProcessor;
306 QPointer< QQuickItem > inputItem;
308 QLocale predictorLocale;
309 std::shared_ptr< QskTextPredictor > predictor;
310 QStringList candidates;
312 Qt::InputMethodHints inputHints;
313 bool hasPredictorLocale =
false;
316 void handleKeyProcessingFinished(
const Result& result )
318 switch ( result.key )
322 qskSendText( qskReceiverItem( panel ),
323 result.text, result.isFinal );
328 panel->commitCurrentText(
false );
329 qskSendKey( inputItem, result.key );
335 qskSendKey( inputItem, result.key );
340 qskSendKey( qskReceiverItem( panel ), result.key );
346QskInputPanel::QskInputPanel( QQuickItem* parent )
347 : Inherited( parent )
348 , m_data( new PrivateData( this ) )
350 setAutoLayoutChildren(
true );
351 setLayoutAlignmentHint( Qt::AlignHCenter | Qt::AlignBottom );
353 initSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Constrained );
355 connect(
this, &QskInputPanel::keySelected,
356 this, &QskInputPanel::commitKey );
358 connect(
this, &QskInputPanel::predictiveTextSelected,
359 this, &QskInputPanel::commitPredictiveText );
362 this, &QskInputPanel::updateLocale );
364 connect( &m_data->keyProcessor, &KeyProcessor::keyProcessingFinished,
365 this, [
this](
const Result& result ) { m_data->handleKeyProcessingFinished( result ); } );
367 updateLocale( locale() );
370QskInputPanel::~QskInputPanel()
374void QskInputPanel::attachInputItem( QQuickItem* item )
376 if ( item == m_data->inputItem )
379 if ( m_data->inputItem )
381 disconnect( m_data->inputItem, &QObject::destroyed,
382 this, &QskInputPanel::inputItemDestroyed );
385 m_data->inputItem = item;
389 if ( m_data->predictor )
390 Q_EMIT predictionReset();
392 m_data->keyProcessor.reset();
393 m_data->inputHints = Qt::InputMethodHints();
397 Qt::InputMethodQueries queries = Qt::ImQueryAll;
398 queries &= ~Qt::ImEnabled;
400 updateInputPanel( queries );
409 const QInputMethodEvent::Attribute attribute(
410 QInputMethodEvent::Cursor, 0, 0, QVariant() );
412 QCoreApplication::postEvent( item,
413 new QInputMethodEvent( QString(), { attribute } ) );
416 connect( item, &QObject::destroyed,
417 this, &QskInputPanel::inputItemDestroyed,
418 Qt::UniqueConnection );
422 attachItem(
nullptr );
426void QskInputPanel::updateInputPanel( Qt::InputMethodQueries queries )
428 if ( m_data->inputItem ==
nullptr )
431 QInputMethodQueryEvent event( queries );
432 QCoreApplication::sendEvent( m_data->inputItem, &event );
434 if ( queries & Qt::ImHints )
436 m_data->inputHints =
static_cast< Qt::InputMethodHints
>(
437 event.value( Qt::ImHints ).toInt() );
439 setPredictionEnabled(
440 m_data->predictor && qskUsePrediction( m_data->inputHints ) );
443 if ( queries & Qt::ImPreferredLanguage )
445 setLocale( event.value( Qt::ImPreferredLanguage ).toLocale() );
448 if ( queries & Qt::ImInputItemClipRectangle )
457void QskInputPanel::updateLocale(
const QLocale& locale )
459 if ( !m_data->hasPredictorLocale ||
locale != m_data->predictorLocale )
461 m_data->hasPredictorLocale =
true;
462 m_data->predictorLocale =
locale;
465 m_data->keyProcessor.reset();
469void QskInputPanel::resetPredictor(
const QLocale& locale )
471 auto predictor = QskInputContext::instance()->textPredictor(
locale );
473 if ( predictor == m_data->predictor )
476 m_data->predictor = predictor;
481 connect(
this, &QskInputPanel::predictionReset,
482 predictor.get(), &QskTextPredictor::reset );
483 connect(
this, &QskInputPanel::predictionRequested,
484 predictor.get(), &QskTextPredictor::request );
486 connect( predictor.get(), &QskTextPredictor::predictionChanged,
487 this, &QskInputPanel::updatePrediction );
490 setPredictionEnabled(
491 predictor && qskUsePrediction( m_data->inputHints ) );
494void QskInputPanel::commitPredictiveText(
int index )
498 if ( m_data->predictor )
500 text = m_data->candidates.at( index );
501 Q_EMIT predictionReset();
504 m_data->keyProcessor.reset();
508 qskSendText( qskReceiverItem(
this ), text,
true );
511void QskInputPanel::updatePrediction(
const QString& text,
const QStringList& candidates )
513 if ( m_data->predictor )
515 if( m_data->keyProcessor.preedit() != text )
521 setPrediction( candidates );
522 m_data->keyProcessor.continueProcessingKey( candidates );
526 qWarning() <<
"got prediction update, but no predictor. Something is wrong";
530QQuickItem* QskInputPanel::inputProxy()
const
535QQuickItem* QskInputPanel::inputItem()
const
537 return m_data->inputItem;
540void QskInputPanel::setPrompt(
const QString& )
544void QskInputPanel::setPredictionEnabled(
bool )
548void QskInputPanel::setPrediction(
const QStringList& candidates )
550 m_data->candidates = candidates;
553Qt::Alignment QskInputPanel::alignment()
const
560 return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom;
563QStringList QskInputPanel::candidates()
const
565 return m_data->candidates;
568void QskInputPanel::commitKey(
int key )
570 if ( m_data->inputItem ==
nullptr )
575 if ( !( m_data->inputHints & Qt::ImhMultiLine ) )
577 QInputMethodQueryEvent event1( Qt::ImMaximumTextLength );
578 QCoreApplication::sendEvent( m_data->inputItem, &event1 );
580 const int maxChars = event1.value( Qt::ImMaximumTextLength ).toInt();
583 QInputMethodQueryEvent event2( Qt::ImSurroundingText );
584 QCoreApplication::sendEvent( qskReceiverItem(
this ), &event2 );
586 const auto text = event2.value( Qt::ImSurroundingText ).toString();
587 spaceLeft = maxChars - text.length();
592 if ( qskUsePrediction( m_data->inputHints ) )
593 predictor = m_data->predictor.get();
595 m_data->keyProcessor.processKey(
596 key, m_data->inputHints,
this, predictor, spaceLeft );
599void QskInputPanel::commitCurrentText(
bool isFinal )
601 if (
auto proxy = inputProxy() )
604 const auto value = proxy->property(
"text" );
605 if ( value.canConvert< QString >() )
606 qskSendReplaceText( m_data->inputItem, value.toString() );
610 commitKey( Qt::Key_Escape );
613#include "moc_QskInputPanel.cpp"
614#include "QskInputPanel.moc"
void setLocale(const QLocale &)
void localeChanged(const QLocale &)