qskinny/inputcontext/QskInputContext.cpp

507 lines
13 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 "QskInputContext.h"
2018-04-04 20:19:47 +02:00
#include "QskVirtualKeyboard.h"
2017-07-21 18:21:34 +02:00
#include "QskInputCompositionModel.h"
#include "QskPinyinCompositionModel.h"
#include <QskDialog.h>
#include <QskWindow.h>
2018-04-04 20:19:47 +02:00
#include <QskControl.h>
2017-07-21 18:21:34 +02:00
#include <QskSetup.h>
#include <QGuiApplication>
2018-04-01 12:47:44 +02:00
#include <QHash>
#include <QPointer>
2017-07-21 18:21:34 +02:00
2018-04-04 20:19:47 +02:00
void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale )
{
if ( auto control = qobject_cast< QskControl* >( inputPanel ) )
{
control->setLocale( locale );
}
else
{
const auto mo = inputPanel->metaObject();
const auto property = mo->property( mo->indexOfProperty( "locale" ) );
if ( property.isWritable() )
property.write( inputPanel, locale );
}
}
QLocale qskLocale( const QQuickItem* inputPanel )
{
if ( inputPanel == nullptr )
return QLocale();
if ( auto control = qobject_cast< const QskControl* >( inputPanel ) )
return control->locale();
return inputPanel->property( "locale" ).toLocale();
}
QskVirtualKeyboard* qskVirtualKeyboard( QQuickItem* inputPanel )
{
// we should not depend on QskVirtualKeyboard TODO ...
if ( inputPanel )
return inputPanel->findChild< QskVirtualKeyboard* >();
return nullptr;
}
2018-04-01 12:47:44 +02:00
class QskInputContext::PrivateData
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
public:
PrivateData():
ownsInputPanelWindow( false )
{
}
2018-04-01 12:47:44 +02:00
QPointer< QQuickItem > inputItem;
2018-04-04 20:19:47 +02:00
QPointer< QQuickItem > inputPanel;
2018-04-01 12:47:44 +02:00
QskInputCompositionModel* compositionModel;
2018-04-02 17:01:04 +02:00
QHash< QLocale, QskInputCompositionModel* > compositionModels;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
2018-04-01 12:47:44 +02:00
};
2018-04-04 15:19:51 +02:00
QskInputContext::QskInputContext():
2018-04-01 12:47:44 +02:00
m_data( new PrivateData() )
{
2018-04-03 20:15:20 +02:00
setObjectName( "InputContext" );
2018-04-04 12:05:01 +02:00
m_data->compositionModel = new QskInputCompositionModel( this );
2018-04-01 12:47:44 +02:00
2018-04-02 17:01:04 +02:00
connect( m_data->compositionModel, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
2017-07-21 18:21:34 +02:00
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
2018-04-01 12:47:44 +02:00
2018-04-02 17:01:04 +02:00
#if 1
2018-04-04 12:05:01 +02:00
setCompositionModel( QLocale::Chinese, new QskPinyinCompositionModel( this ) );
2018-04-02 17:01:04 +02:00
#endif
2018-04-02 17:01:04 +02:00
setInputPanel( qskSetup->inputPanel() );
2017-07-21 18:21:34 +02:00
}
QskInputContext::~QskInputContext()
{
2018-04-04 12:05:01 +02:00
#if 1
2018-04-01 12:47:44 +02:00
if ( m_data->inputPanel )
delete m_data->inputPanel;
2018-04-04 12:05:01 +02:00
#endif
2017-07-21 18:21:34 +02:00
}
bool QskInputContext::isValid() const
{
return true;
}
2018-04-04 12:05:01 +02:00
bool QskInputContext::hasCapability( Capability ) const
{
// what is QPlatformInputContext::HiddenTextCapability ???
return true;
}
QQuickItem* QskInputContext::inputItem()
2017-07-21 18:21:34 +02:00
{
return m_data->inputItem;
}
void QskInputContext::setInputItem( QQuickItem* item )
{
if ( m_data->inputItem == item )
return;
m_data->inputItem = item;
2018-04-04 12:05:01 +02:00
if ( m_data->inputItem == nullptr )
{
hideInputPanel();
return;
}
update( Qt::ImQueryAll );
}
void QskInputContext::update( Qt::InputMethodQueries queries )
{
if ( m_data->inputItem == nullptr || m_data->inputPanel == nullptr )
2017-07-21 18:21:34 +02:00
return;
2018-04-04 15:19:51 +02:00
const auto queryEvent = queryInputMethod( queries );
2017-07-21 18:21:34 +02:00
if ( queryEvent.queries() & Qt::ImEnabled )
{
if ( !queryEvent.value( Qt::ImEnabled ).toBool() )
{
hideInputPanel();
return;
}
}
2017-07-21 18:21:34 +02:00
if ( queryEvent.queries() & Qt::ImHints )
2017-07-21 18:21:34 +02:00
{
/*
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, // ignored for now
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
*/
2018-04-04 15:19:51 +02:00
#if 0
const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() );
#endif
2017-07-21 18:21:34 +02:00
}
if ( queryEvent.queries() & Qt::ImPreferredLanguage )
2017-07-21 18:21:34 +02:00
{
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
2018-04-01 12:47:44 +02:00
auto oldModel = compositionModel();
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
if( m_data->inputPanel )
2018-04-04 20:19:47 +02:00
qskSetLocale( m_data->inputPanel, locale );
2018-04-01 12:47:44 +02:00
auto newModel = compositionModel();
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
if( oldModel != newModel )
2017-07-21 18:21:34 +02:00
{
2018-04-04 20:19:47 +02:00
connect( newModel, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
2018-04-01 12:47:44 +02:00
2018-04-04 20:19:47 +02:00
if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) )
keyboard->setCandidateBarVisible( newModel->supportsSuggestions() );
2017-07-21 18:21:34 +02:00
}
}
/*
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-04 15:19:51 +02:00
}
2017-07-21 18:21:34 +02:00
QRectF QskInputContext::keyboardRect() const
{
2018-04-01 12:47:44 +02:00
if ( m_data->inputPanel
2017-07-21 18:21:34 +02:00
&& QskDialog::instance()->policy() != QskDialog::TopLevelWindow )
{
2018-04-04 20:19:47 +02:00
return qskItemGeometry( m_data->inputPanel );
2017-07-21 18:21:34 +02:00
}
return Inherited::keyboardRect();
}
bool QskInputContext::isAnimating() const
{
return false;
}
void QskInputContext::showInputPanel()
{
2018-04-01 12:47:44 +02:00
if ( !m_data->inputPanel )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
setInputPanel( new QskVirtualKeyboard() );
2017-07-21 18:21:34 +02:00
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
{
m_data->ownsInputPanelWindow = true;
2017-07-21 18:21:34 +02:00
auto window = new QskWindow;
window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
window->resize( 800, 240 ); // ### what size?
2018-04-01 12:47:44 +02:00
m_data->inputPanel->setParentItem( window->contentItem() );
2017-07-21 18:21:34 +02:00
connect( window, &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged );
}
else
{
auto window = qobject_cast< QQuickWindow* >( QGuiApplication::focusWindow() );
if ( window )
{
2018-04-01 12:47:44 +02:00
m_data->inputPanel->setParentItem( window->contentItem() );
m_data->inputPanel->setSize( window->size() );
2017-07-21 18:21:34 +02:00
}
}
}
if ( m_data->ownsInputPanelWindow )
{
if ( m_data->inputPanel->window() )
m_data->inputPanel->window()->show();
}
2017-07-21 18:21:34 +02:00
else
{
2018-04-01 12:47:44 +02:00
m_data->inputPanel->setVisible( true );
}
2017-07-21 18:21:34 +02:00
}
void QskInputContext::hideInputPanel()
{
2018-04-03 20:15:20 +02:00
if ( m_data->inputPanel == nullptr )
2017-07-21 18:21:34 +02:00
return;
if ( m_data->ownsInputPanelWindow )
{
if ( auto window = m_data->inputPanel->window() )
window->hide();
}
2017-07-21 18:21:34 +02:00
else
{
2018-04-01 12:47:44 +02:00
m_data->inputPanel->setVisible( false );
}
2017-07-21 18:21:34 +02:00
}
bool QskInputContext::isInputPanelVisible() const
{
2018-04-01 12:47:44 +02:00
auto panel = m_data->inputPanel;
2018-04-03 20:15:20 +02:00
2018-04-01 12:47:44 +02:00
return panel && panel->isVisible()
2018-04-03 20:15:20 +02:00
&& panel->window() && panel->window()->isVisible();
2017-07-21 18:21:34 +02:00
}
QLocale QskInputContext::locale() const
{
2018-04-04 20:19:47 +02:00
return qskLocale( m_data->inputPanel );
2017-07-21 18:21:34 +02:00
}
2018-04-04 12:05:01 +02:00
Qt::LayoutDirection QskInputContext::inputDirection() const
{
return Inherited::inputDirection();
}
2017-07-21 18:21:34 +02:00
void QskInputContext::setFocusObject( QObject* focusObject )
{
2018-04-01 12:47:44 +02:00
auto focusItem = qobject_cast< QQuickItem* >( focusObject );
2018-03-14 17:30:39 +01:00
if ( focusItem == nullptr )
2018-03-14 17:30:39 +01:00
{
if ( m_data->inputItem )
2018-03-14 17:30:39 +01:00
{
if ( m_data->inputItem->window() == QGuiApplication::focusWindow() )
setInputItem( nullptr );
2018-03-14 17:30:39 +01:00
}
2017-07-21 18:21:34 +02:00
}
else
{
/*
Do not change the input item when
navigating on or into the panel
*/
2017-07-21 18:21:34 +02:00
if( qskNearestFocusScope( focusItem ) != m_data->inputPanel )
setInputItem( focusItem );
}
2017-07-21 18:21:34 +02:00
}
2018-04-02 17:01:04 +02:00
void QskInputContext::setCompositionModel(
2018-04-01 12:47:44 +02:00
const QLocale& locale, QskInputCompositionModel* model )
{
2018-04-02 17:01:04 +02:00
auto& models = m_data->compositionModels;
if ( model )
{
const auto it = models.find( locale );
if ( it != models.end() )
{
if ( it.value() == model )
return;
delete it.value();
*it = model;
}
else
{
models.insert( locale, model );
}
2018-04-02 17:01:04 +02:00
connect( model, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
}
else
{
const auto it = models.find( locale );
if ( it != models.end() )
{
delete it.value();
models.erase( it );
}
}
}
2018-04-01 12:47:44 +02:00
QskInputCompositionModel* QskInputContext::compositionModel() const
{
2018-04-02 17:01:04 +02:00
return m_data->compositionModels.value( locale(), m_data->compositionModel );
}
2018-04-04 20:19:47 +02:00
void QskInputContext::invokeAction( QInputMethod::Action action, int value )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
auto model = compositionModel();
2017-07-21 18:21:34 +02:00
2018-04-04 12:05:01 +02:00
switch ( static_cast< int >( action ) )
2017-07-21 18:21:34 +02:00
{
case QskVirtualKeyboard::Compose:
2018-04-01 12:47:44 +02:00
{
2018-04-04 20:19:47 +02:00
model->composeKey( static_cast< Qt::Key >( value ) );
2017-07-21 18:21:34 +02:00
break;
2018-04-01 12:47:44 +02:00
}
case QskVirtualKeyboard::SelectCandidate:
2018-04-01 12:47:44 +02:00
{
2018-04-04 20:19:47 +02:00
model->commitCandidate( value );
2017-07-21 18:21:34 +02:00
break;
2018-04-01 12:47:44 +02:00
}
2018-04-04 12:05:01 +02:00
case QInputMethod::Click:
case QInputMethod::ContextMenu:
{
break;
}
2017-07-21 18:21:34 +02:00
}
}
void QskInputContext::handleCandidatesChanged()
{
2018-04-01 12:47:44 +02:00
const auto model = compositionModel();
2018-04-02 17:01:04 +02:00
if ( model == nullptr || m_data->inputPanel == nullptr )
return;
const auto count = model->candidateCount();
2018-04-01 12:47:44 +02:00
2018-04-02 17:01:04 +02:00
QVector< QString > candidates;
candidates.reserve( count );
2018-04-02 17:01:04 +02:00
for( int i = 0; i < count; i++ )
candidates += model->candidate( i );
2017-07-21 18:21:34 +02:00
2018-04-04 20:19:47 +02:00
if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) )
keyboard->setPreeditCandidates( candidates );
2017-07-21 18:21:34 +02:00
}
2018-04-04 20:19:47 +02:00
void QskInputContext::setInputPanel( QQuickItem* inputPanel )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
if ( m_data->inputPanel == inputPanel )
2017-07-21 18:21:34 +02:00
return;
2018-04-01 12:47:44 +02:00
auto model = compositionModel();
2018-03-31 18:34:51 +02:00
2018-04-01 12:47:44 +02:00
if ( m_data->inputPanel )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
m_data->inputPanel->disconnect( this );
2018-03-31 18:34:51 +02:00
2018-04-01 12:47:44 +02:00
if ( model )
model->disconnect( m_data->inputPanel );
2017-07-21 18:21:34 +02:00
}
2018-04-01 12:47:44 +02:00
m_data->inputPanel = inputPanel;
m_data->ownsInputPanelWindow = false;
2017-07-21 18:21:34 +02:00
2018-03-31 18:34:51 +02:00
if ( inputPanel )
2017-07-21 18:21:34 +02:00
{
2018-04-04 20:19:47 +02:00
// maybe using a QQuickItemChangeListener instead
#if 1
connect( inputPanel, &QQuickItem::visibleChanged,
2018-03-31 18:34:51 +02:00
this, &QPlatformInputContext::emitInputPanelVisibleChanged );
2018-04-04 20:19:47 +02:00
connect( inputPanel, &QQuickItem::xChanged,
2018-03-31 18:34:51 +02:00
this, &QPlatformInputContext::emitKeyboardRectChanged );
2018-04-04 20:19:47 +02:00
connect( inputPanel, &QQuickItem::yChanged,
this, &QPlatformInputContext::emitKeyboardRectChanged );
2018-03-31 18:34:51 +02:00
2018-04-04 20:19:47 +02:00
connect( inputPanel, &QQuickItem::widthChanged,
this, &QPlatformInputContext::emitKeyboardRectChanged );
connect( inputPanel, &QQuickItem::heightChanged,
this, &QPlatformInputContext::emitKeyboardRectChanged );
#endif
if ( auto control = qobject_cast< QskControl* >( inputPanel ) )
2018-03-31 18:34:51 +02:00
{
2018-04-04 20:19:47 +02:00
connect( control, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged );
}
2018-03-31 18:34:51 +02:00
2018-04-04 20:19:47 +02:00
if ( model )
{
if ( auto keyboard = qskVirtualKeyboard( inputPanel ) )
keyboard->setCandidateBarVisible( model->supportsSuggestions() );
2018-03-31 18:34:51 +02:00
}
2017-07-21 18:21:34 +02:00
}
}
2018-04-04 12:05:01 +02:00
void QskInputContext::reset()
{
}
void QskInputContext::commit()
{
}
2018-04-04 15:19:51 +02:00
bool QskInputContext::filterEvent( const QEvent* event )
2018-04-04 12:05:01 +02:00
{
2018-04-04 15:19:51 +02:00
// called from QXcbKeyboard, but what about other platforms
Q_UNUSED( event )
2018-04-04 12:05:01 +02:00
return false;
}
2018-04-04 15:19:51 +02:00
QInputMethodQueryEvent QskInputContext::queryInputMethod(
Qt::InputMethodQueries queries ) const
2018-04-04 12:05:01 +02:00
{
2018-04-04 15:19:51 +02:00
QInputMethodQueryEvent event( queries );
sendEventToInputItem( &event );
return event;
}
void QskInputContext::sendEventToInputItem( QEvent* event ) const
{
if ( m_data->inputItem && event )
QCoreApplication::sendEvent( m_data->inputItem, event );
2018-04-04 12:05:01 +02:00
}
2017-07-21 18:21:34 +02:00
#include "moc_QskInputContext.cpp"