input comntext improvements

This commit is contained in:
Uwe Rathmann 2018-04-27 13:48:51 +02:00
parent 16efc695b9
commit 602e3748df
10 changed files with 461 additions and 460 deletions

View File

@ -6,16 +6,15 @@
#include <SkinnyFont.h>
#include <SkinnyShortcut.h>
#include <QskInputPanel.h>
#include <QskDialog.h>
#include <QskFocusIndicator.h>
#include <QskLinearBox.h>
#include <QskListView.h>
#include <QskTextInput.h>
#include <QskInputPanel.h>
#include <QskInputContext.h>
#include <QskWindow.h>
#include <QskSetup.h>
#include <QskAspect.h>
#include <QskObjectCounter.h>
@ -258,17 +257,16 @@ int main( int argc, char* argv[] )
SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts );
#if 1
// We don't want to have a top level window.
// We don't want to have the input panel in a top level window.
qskDialog->setPolicy( QskDialog::EmbeddedBox );
#endif
#if 0
/*
QskInputContext is connected to QskSetup::inputPanelChanged,
making it the system input. If no input panel has been assigned
QskInputContext would create a window or subwindow on the fly.
If no input panel has been assigned QskInputContext creates
default panel if none has been assigned
*/
qskSetup->setInputPanel( new QskInputPanel() );
QskInputContext::setInputPanel( new QskInputPanel() );
#endif
auto box = new QskLinearBox( Qt::Horizontal );

View File

@ -159,9 +159,6 @@ public:
Q_PROPERTY( QStringList skinList READ skinList NOTIFY skinListChanged )
Q_PRIVATE_PROPERTY( setup(), QQuickItem* inputPanel READ inputPanel
WRITE setInputPanel NOTIFY inputPanelChanged )
Q_PRIVATE_PROPERTY( setup(), QskSetupFlagsProvider controlFlags
READ controlFlags WRITE setControlFlags NOTIFY controlFlagsChanged )
@ -177,8 +174,7 @@ public:
connect( setup(), &QskSetup::skinChanged,
this, &QskMain::skinChanged, Qt::QueuedConnection );
connect( setup(), &QskSetup::inputPanelChanged,
this, &QskMain::inputPanelChanged );
connect( setup(), &QskSetup::controlFlagsChanged,
this, &QskMain::controlFlagsChanged, Qt::QueuedConnection );
}

View File

@ -172,34 +172,37 @@ void qskForceActiveFocus( QQuickItem* item, Qt::FocusReason reason )
void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries )
{
if ( ( item == nullptr ) ||
!( item->flags() & QQuickItem::ItemAcceptsInputMethod ) )
{
return;
}
auto inputMethod = QGuiApplication::inputMethod();
bool doUpdate = item->hasActiveFocus();
if ( !doUpdate )
/*
We could also get the inputContext from QInputMethodPrivate
but for some reason the gcc sanitizer reports errors
when using it. So let's go with QGuiApplicationPrivate.
*/
const auto inputContext =
QGuiApplicationPrivate::platformIntegration()->inputContext();
if ( inputContext && inputContext->isInputPanelVisible() )
{
/*
We could also get the inputContext from QInputMethodPrivate
but for some reason the gcc sanitizer reports errors
when using it. So let's go with QGuiApplicationPrivate.
QskInputContext allows to navigate inside the input panel
without losing the connected input item
*/
const auto inputContext =
QGuiApplicationPrivate::platformIntegration()->inputContext();
if ( inputContext && inputContext->isInputPanelVisible() )
QQuickItem* inputItem = nullptr;
if ( QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) )
{
/*
QskInputContext allows to navigate inside the input panel
without losing the connected input item
*/
QQuickItem* inputItem = nullptr;
if ( QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) )
{
doUpdate = ( item == inputItem );
}
doUpdate = ( item == inputItem );
}
}

View File

@ -126,7 +126,6 @@ public:
QskGraphicProviderMap graphicProviders;
QPointer< QQuickItem > inputPanel;
QskSetup::Flags controlFlags;
};
@ -261,20 +260,6 @@ QskGraphicProvider* QskSetup::graphicProvider( const QString& providerId ) const
return m_data->graphicProviders.provider( providerId );
}
void QskSetup::setInputPanel( QQuickItem* inputPanel )
{
if ( m_data->inputPanel == inputPanel )
return;
m_data->inputPanel = inputPanel;
Q_EMIT inputPanelChanged( m_data->inputPanel );
}
QQuickItem* QskSetup::inputPanel()
{
return m_data->inputPanel;
}
QLocale QskSetup::inheritedLocale( const QObject* object )
{
VisitorLocale visitor;

View File

@ -58,9 +58,6 @@ public:
QskSkin* skin();
void setInputPanel( QQuickItem* );
QQuickItem* inputPanel();
void addGraphicProvider( const QString& providerId, QskGraphicProvider* );
QskGraphicProvider* graphicProvider( const QString& providerId ) const;
@ -74,7 +71,6 @@ public:
Q_SIGNALS:
void skinChanged( QskSkin* );
void inputPanelChanged( QQuickItem* );
void controlFlagsChanged();
private:

View File

@ -540,6 +540,7 @@ void QskTextInput::setReadOnly( bool on )
m_data->textInput->setReadOnly( on );
// we are killing user settings here ?
m_data->textInput->setFlag( QQuickItem::ItemAcceptsInputMethod, !on );
qskUpdateInputMethod( this, Qt::ImEnabled );
@ -645,10 +646,13 @@ QskTextInput::EchoMode QskTextInput::echoMode() const
void QskTextInput::setEchoMode( EchoMode mode )
{
m_data->textInput->setEchoMode(
static_cast< QQuickTextInput::EchoMode >( mode ) );
if ( mode != echoMode() )
{
m_data->textInput->setEchoMode(
static_cast< QQuickTextInput::EchoMode >( mode ) );
qskUpdateInputMethod( this, Qt::ImHints );
qskUpdateInputMethod( this, Qt::ImHints );
}
}
QString QskTextInput::displayText() const

View File

@ -4,21 +4,68 @@
*****************************************************************************/
#include "QskInputContext.h"
#include "QskInputPanel.h"
#include "QskTextPredictor.h"
#include "QskInputPanel.h"
#include "QskInputEngine.h"
#include "QskLinearBox.h"
#include <QskLinearBox.h>
#include <QskDialog.h>
#include <QskPopup.h>
#include <QskWindow.h>
#include <QskSetup.h>
#include <QskEvent.h>
#include <QHash>
#include <QPointer>
#include <QGuiApplication>
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h>
static QPointer< QskInputPanel > qskInputPanel = nullptr;
static void qskDeletePanel()
{
delete qskInputPanel;
}
static void qskInputPanelHook()
{
qAddPostRoutine( qskDeletePanel );
}
Q_COREAPP_STARTUP_FUNCTION( qskInputPanelHook )
static void qskSetInputPanel( QskInputPanel* inputPanel )
{
if ( inputPanel == qskInputPanel )
return;
delete qskInputPanel;
qskInputPanel = inputPanel;
}
void QskInputContext::setInputPanel( QskInputPanel* inputPanel )
{
if ( inputPanel == qskInputPanel )
return;
qskSetInputPanel( inputPanel );
const auto inputContext =
QGuiApplicationPrivate::platformIntegration()->inputContext();
if ( auto context = qobject_cast< QskInputContext* >( inputContext ) )
context->hideInputPanel();
}
QskInputPanel* QskInputContext::inputPanel()
{
return qskInputPanel;
}
static inline uint qskHashLocale( const QLocale& locale )
{
return uint( locale.language() + ( uint( locale.country() ) << 16 ) );
@ -77,32 +124,20 @@ public:
// item receiving the input
QPointer< QQuickItem > inputItem;
// item, wher the user enters texts/keys
QPointer< QQuickItem > inputPanel;
// popup or window embedding the inputPanel
// popup or window embedding qskInputPanel
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
PredictorTable predictorTable;
QskInputEngine* engine = nullptr;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
};
QskInputContext::QskInputContext():
m_data( new PrivateData() )
{
setObjectName( "InputContext" );
m_data->engine = new QskInputEngine( this );
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
setInputPanel( qskSetup->inputPanel() );
}
QskInputContext::~QskInputContext()
@ -125,37 +160,6 @@ QQuickItem* QskInputContext::inputItem()
return m_data->inputItem;
}
void QskInputContext::setInputItem( QQuickItem* item )
{
if ( m_data->inputItem == item )
return;
auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel );
if ( isInputPanelVisible() )
{
if ( item == nullptr )
{
hideInputPanel();
}
else
{
if ( panel )
panel->attachInputItem( item );
update( Qt::ImQueryAll );
}
}
else
{
// no need for updates
if ( panel )
panel->attachInputItem( nullptr );
}
m_data->inputItem = item;
}
void QskInputContext::update( Qt::InputMethodQueries queries )
{
if ( queries & Qt::ImEnabled )
@ -170,225 +174,172 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
}
}
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->processInputMethodQueries( queries );
if ( qskInputPanel )
qskInputPanel->processInputMethodQueries( queries );
}
QRectF QskInputContext::keyboardRect() const
{
if ( m_data->inputPanel
&& QskDialog::instance()->policy() != QskDialog::TopLevelWindow )
{
return qskItemGeometry( m_data->inputPanel );
}
// is this correct and what is this good for ?
if ( m_data->inputPopup )
return m_data->inputPopup->geometry();
return Inherited::keyboardRect();
}
bool QskInputContext::isAnimating() const
{
// can be implemented once we have some sliding/fading effects
return false;
}
void QskInputContext::showInputPanel()
{
auto& inputPanel = m_data->inputPanel;
auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() );
if ( focusItem == nullptr )
return;
if ( ( focusItem == qskInputPanel )
|| qskIsAncestorOf( qskInputPanel, focusItem ) )
{
// ignore: usually the input proxy of the panel
return;
}
m_data->inputItem = focusItem;
auto& inputPopup = m_data->inputPopup;
auto& inputWindow = m_data->inputWindow;
if ( inputPanel == nullptr )
if ( qskInputPanel == nullptr )
qskSetInputPanel( new QskInputPanel() );
connect( qskInputPanel, &QQuickItem::visibleChanged,
this, &QPlatformInputContext::emitInputPanelVisibleChanged,
Qt::UniqueConnection );
connect( qskInputPanel, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged,
Qt::UniqueConnection );
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
{
auto panel = new QskInputPanel();
// The input panel is embedded in a top level window
panel->setParent( this );
panel->setInputProxy( true );
setInputPanel( panel );
}
const bool isPopupPanel = qobject_cast< QskPopup* >( inputPanel );
bool useWindow = false;
if ( !isPopupPanel )
{
useWindow = ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow );
}
if ( useWindow )
{
delete inputPopup;
if ( inputWindow == nullptr )
{
inputWindow = new QskWindow();
inputWindow->setDeleteOnClose( true );
#if 0
inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
#endif
inputPanel->setParentItem( inputWindow->contentItem() );
QSizeF size;
if ( auto control = qobject_cast< const QskControl* >( inputPanel ) )
size = control->sizeHint();
if ( size.isEmpty() )
size = QSizeF( 800, 240 ); // ### what size?
inputWindow->resize( size.toSize() );
inputWindow->show();
inputWindow->installEventFilter( this );
qskInputPanel->setParentItem( inputWindow->contentItem() );
}
QSize size = qskInputPanel->sizeHint().toSize();
if ( size.isEmpty() )
{
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
inputWindow->resize( size );
inputWindow->show();
inputWindow->installEventFilter( this );
}
else
{
// The input panel is embedded in a popup
delete inputWindow;
if ( inputPopup == nullptr )
{
if ( isPopupPanel )
{
inputPopup = qobject_cast< QskPopup* >( inputPanel );
}
else
{
auto popup = new QskPopup( m_data->inputItem->window()->contentItem() );
inputPopup = new QskPopup( m_data->inputItem->window()->contentItem() );
popup->setAutoLayoutChildren( true );
popup->setTransparentForPositioner( false );
popup->setOverlay( false );
popup->setModal( true );
inputPopup->setAutoLayoutChildren( true );
inputPopup->setTransparentForPositioner( false );
inputPopup->setModal( true );
auto box = new QskLinearBox( popup );
box->addItem( inputPanel );
auto box = new QskLinearBox( inputPopup );
box->addItem( qskInputPanel );
if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) )
{
if ( panel->hasInputProxy() )
{
popup->setOverlay( true );
}
}
/*
When the panel has an input proxy ( usually a local text input )
we don't need to see the input item and display the overlay
and align in the center of the window.
*/
const bool hasInputProxy = qskInputPanel->hasInputProxy();
if ( !popup->hasOverlay() )
{
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
}
inputPopup->setOverlay( hasInputProxy );
inputPopup = popup;
}
inputPopup->installEventFilter( this );
}
if ( inputPopup->window() == nullptr )
{
QQuickWindow* window = nullptr;
if ( m_data->inputItem )
window = m_data->inputItem->window();
else
window = qobject_cast< QQuickWindow* >( QGuiApplication::focusWindow() );
if ( window )
{
inputPopup->setParentItem( window->contentItem() );
}
if ( !hasInputProxy )
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
}
inputPopup->setParentItem( m_data->inputItem->window()->contentItem() );
inputPopup->setVisible( true );
inputPopup->installEventFilter( this );
}
update( Qt::ImQueryAll );
#if 1
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->updateInputProxy( m_data->inputItem );
#endif
inputPanel->setVisible( true );
#if 0
if ( auto focusItem = inputPanel->nextItemInFocusChain( true ) )
qskForceActiveFocus( focusItem, Qt::OtherFocusReason );
#endif
connect( inputPanel->window(), &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged );
updateInputPanel( m_data->inputItem );
m_data->engine->setPredictor(
m_data->predictorTable.find( locale() ) );
qskInputPanel->setLocale( locale() );
qskInputPanel->attachInputItem( m_data->inputItem );
qskInputPanel->setEngine( m_data->engine );
}
void QskInputContext::hideInputPanel()
{
if ( m_data->inputPanel )
if ( m_data->inputPopup )
{
// to get rid of the scene graph nodes
m_data->inputPanel->setVisible( false );
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->setEngine( nullptr );
}
if ( m_data->inputPopup == m_data->inputPanel )
{
m_data->inputPopup->removeEventFilter( this );
m_data->inputPopup = nullptr;
}
else
{
if ( m_data->inputPopup )
{
#if 1
if ( auto focusItem = m_data->inputPopup->scopedFocusItem() )
{
/*
Qt bug: QQuickItem::hasFocus() is not cleared
when the corresponding focusScope gets deleted.
Usually no problem, but here the focusItem is no
child and will be reused with a different parent
later.
*/
focusItem->setFocus( false );
}
if ( auto focusItem = m_data->inputPopup->scopedFocusItem() )
{
/*
Qt bug: QQuickItem::hasFocus() is not cleared
when the corresponding focusScope gets deleted.
Usually no problem, but here the focusItem is no
child and will be reused with a different parent
later.
*/
focusItem->setFocus( false );
}
#endif
m_data->inputPopup->deleteLater();
}
m_data->inputPopup->deleteLater();
}
QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr;
if ( window )
if ( m_data->inputWindow )
{
QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr;
window->removeEventFilter( this );
window->close(); // deleteOnClose is set
}
qGuiApp->removeEventFilter( this );
if ( qskInputPanel )
{
//qskInputPanel->setVisible( false );
qskInputPanel->setParentItem( nullptr );
qskInputPanel->attachInputItem( nullptr );
qskInputPanel->setEngine( nullptr );
}
updateInputPanel( nullptr );
}
void QskInputContext::updateInputPanel( QQuickItem* inputItem )
{
auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel );
if ( panel == nullptr )
return;
panel->setLocale( locale() );
panel->attachInputItem( inputItem );
panel->setEngine( inputItem ? m_data->engine : nullptr );
m_data->inputItem = nullptr;
}
bool QskInputContext::isInputPanelVisible() const
{
auto panel = m_data->inputPanel;
return panel && panel->isVisible()
&& panel->window() && panel->window()->isVisible();
return qskInputPanel && qskInputPanel->isVisible()
&& qskInputPanel->window() && qskInputPanel->window()->isVisible();
}
QLocale QskInputContext::locale() const
@ -411,48 +362,52 @@ Qt::LayoutDirection QskInputContext::inputDirection() const
void QskInputContext::setFocusObject( QObject* focusObject )
{
auto focusItem = qobject_cast< QQuickItem* >( focusObject );
if ( focusItem == nullptr )
if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject )
{
if ( m_data->inputItem )
// we don't care
return;
}
bool doTerminate = true;
if ( focusObject == nullptr && m_data->inputPopup )
{
if ( const auto window = m_data->inputItem->window() )
{
if ( m_data->inputItem->window() == QGuiApplication::focusWindow() )
setInputItem( nullptr );
auto focusItem = window->contentItem()->scopedFocusItem();
if ( focusItem == m_data->inputPopup )
doTerminate = false;
}
}
else
if ( doTerminate )
{
/*
Do not change the input item when
navigating to or inside the input popup/window
*/
bool isAccepted = ( m_data->inputItem == nullptr );
if ( !isAccepted )
if ( m_data->inputWindow )
{
if ( m_data->inputWindow )
auto focusWindow = QGuiApplication::focusWindow();
if ( focusWindow == nullptr ||
QGuiApplication::focusWindow() == m_data->inputWindow )
{
if ( focusItem->window() != m_data->inputWindow )
isAccepted = true;
}
else if ( m_data->inputPopup )
{
if ( ( focusItem != m_data->inputPopup )
&& !qskIsAncestorOf( m_data->inputPopup, focusItem ) )
{
isAccepted = true;
}
}
else
{
isAccepted = true;
doTerminate = false;
}
}
else if ( m_data->inputPopup )
{
auto focusItem = qobject_cast< QQuickItem* >( focusObject );
if ( isAccepted )
setInputItem( focusItem );
if ( ( focusItem == m_data->inputPopup )
|| qskIsAncestorOf( m_data->inputPopup, focusItem ) )
{
doTerminate = false;
}
}
}
if ( doTerminate )
{
hideInputPanel();
m_data->inputItem = nullptr;
}
}
@ -484,51 +439,17 @@ void QskInputContext::invokeAction( QInputMethod::Action, int )
{
}
void QskInputContext::setInputPanel( QQuickItem* inputPanel )
{
if ( m_data->inputPanel == inputPanel )
return;
if ( m_data->inputPanel )
{
m_data->inputPanel->disconnect( this );
if ( m_data->inputPanel->parent() == this )
{
delete m_data->inputPanel;
}
else
{
m_data->inputPanel->setParentItem( nullptr );
}
}
m_data->inputPanel = inputPanel;
m_data->ownsInputPanelWindow = false;
if ( inputPanel )
{
if ( inputPanel->parent() == nullptr )
inputPanel->setParent( this );
connect( inputPanel, &QQuickItem::visibleChanged,
this, &QPlatformInputContext::emitInputPanelVisibleChanged );
if ( auto control = qobject_cast< QskControl* >( inputPanel ) )
{
connect( control, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged );
}
}
}
void QskInputContext::reset()
{
}
void QskInputContext::commit()
{
// called on focus changes
/*
commit is called, when the input item loses the focus.
As it it should be possible to navigate inside of the
inputPanel this is no valid reason to hide the panel.
*/
}
bool QskInputContext::eventFilter( QObject* object, QEvent* event )
@ -539,15 +460,15 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
{
case QEvent::Move:
{
if ( m_data->inputPanel )
if ( qskInputPanel )
emitKeyboardRectChanged();
break;
}
case QEvent::Resize:
{
if ( m_data->inputPanel )
m_data->inputPanel->setSize( m_data->inputWindow->size() );
if ( qskInputPanel )
qskInputPanel->setSize( m_data->inputWindow->size() );
break;
}

View File

@ -11,6 +11,7 @@
#include <memory>
class QskTextPredictor;
class QskInputPanel;
class QQuickItem;
class QSK_EXPORT QskInputContext : public QPlatformInputContext
@ -51,19 +52,16 @@ public:
virtual bool filterEvent( const QEvent* ) override;
static void setInputPanel( QskInputPanel* );
static QskInputPanel* inputPanel();
protected:
virtual void updateInputPanel( QQuickItem* inputItem );
private Q_SLOTS:
void setInputPanel( QQuickItem* );
virtual bool eventFilter( QObject*, QEvent* ) override;
private:
void setInputItem( QQuickItem* );
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
#endif

View File

@ -18,10 +18,27 @@
#include <QInputMethodQueryEvent>
#include <QTextCharFormat>
static inline void qskSendText( QQuickItem* inputItem,
static inline void qskSendReplaceText( QQuickItem* receiver, const QString& text )
{
if ( receiver == nullptr )
return;
QInputMethodEvent::Attribute attribute(
QInputMethodEvent::Selection, 0, 32767, QVariant() );
QInputMethodEvent event1( QString(), { attribute } );
QCoreApplication::sendEvent( receiver, &event1 );
QInputMethodEvent event2;
event2.setCommitString( text );
QCoreApplication::sendEvent( receiver, &event2 );
}
static inline void qskSendText( QQuickItem* receiver,
const QString& text, bool isFinal )
{
if ( inputItem == nullptr )
if ( receiver == nullptr )
return;
if ( isFinal )
@ -29,7 +46,7 @@ static inline void qskSendText( QQuickItem* inputItem,
QInputMethodEvent event;
event.setCommitString( text );
QCoreApplication::sendEvent( inputItem, &event );
QCoreApplication::sendEvent( receiver, &event );
}
else
{
@ -41,20 +58,20 @@ static inline void qskSendText( QQuickItem* inputItem,
QInputMethodEvent event( text, { attribute } );
QCoreApplication::sendEvent( inputItem, &event );
QCoreApplication::sendEvent( receiver, &event );
}
}
static inline void qskSendKey( QQuickItem* inputItem, int key )
static inline void qskSendKey( QQuickItem* receiver, int key )
{
if ( inputItem == nullptr )
if ( receiver == nullptr )
return;
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyPress );
QCoreApplication::sendEvent( receiver, &keyPress );
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyRelease );
QCoreApplication::sendEvent( receiver, &keyRelease );
}
namespace
@ -65,6 +82,7 @@ namespace
TextInput( QQuickItem* parentItem = nullptr ):
QskTextInput( parentItem )
{
setObjectName( "InputPanelInputProxy" );
}
};
}
@ -75,10 +93,18 @@ class QskInputPanel::PrivateData
{
public:
PrivateData():
inputHints( 0 ),
maxChars( -1 ),
hasPrediction( true ),
hasInputProxy( true )
{
}
QQuickItem* receiverItem()
{
return hasInputProxy ? inputProxy : inputItem;
}
QPointer< QskInputEngine > engine;
QPointer< QQuickItem > inputItem;
@ -88,6 +114,10 @@ public:
QskInputPredictionBar* predictionBar;
QskVirtualKeyboard* keyboard;
Qt::InputMethodHints inputHints;
int maxChars;
bool hasPrediction : 1;
bool hasInputProxy : 1;
};
@ -146,7 +176,8 @@ void QskInputPanel::setEngine( QskInputEngine* engine )
this, &QskInputPanel::updatePredictionBar );
}
m_data->predictionBar->setVisible( engine && engine->predictor() );
m_data->predictionBar->setVisible(
m_data->hasPrediction && engine && engine->predictor() );
}
void QskInputPanel::attachInputItem( QQuickItem* item )
@ -165,6 +196,9 @@ void QskInputPanel::attachInputItem( QQuickItem* item )
queries &= ~Qt::ImEnabled;
processInputMethodQueries( queries );
if ( m_data->hasInputProxy )
m_data->inputProxy->setEditing( true );
}
}
@ -234,56 +268,6 @@ void QskInputPanel::setInputProxy( bool on )
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 >() );
@ -302,34 +286,23 @@ 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 ) )
if ( !( m_data->inputHints & Qt::ImhMultiLine ) )
{
QInputMethodQueryEvent event(
Qt::ImSurroundingText | Qt::ImMaximumTextLength );
auto receiver = m_data->receiverItem();
QCoreApplication::sendEvent( inputItem, &event );
const int max = event.value( Qt::ImMaximumTextLength ).toInt();
if ( max > 0 )
if ( m_data->maxChars >= 0 )
{
QInputMethodQueryEvent event( Qt::ImSurroundingText );
QCoreApplication::sendEvent( receiver, &event );
const auto text = event.value( Qt::ImSurroundingText ).toString();
spaceLeft = max - text.length();
spaceLeft = m_data->maxChars - text.length();
}
}
processKey( key, inputHints, spaceLeft );
processKey( key, m_data->inputHints, spaceLeft );
}
void QskInputPanel::processKey( int key,
@ -338,16 +311,35 @@ void QskInputPanel::processKey( int key,
const auto result = m_data->engine->processKey( key, inputHints, spaceLeft );
auto inputItem = m_data->inputItem;
auto inputProxy = m_data->inputProxy;
if ( result.key )
{
// sending a control key
qskSendKey( inputItem, result.key );
switch( result.key )
{
case Qt::Key_Return:
{
if ( m_data->hasInputProxy )
qskSendReplaceText( inputItem, inputProxy->text() );
qskSendKey( inputItem, result.key );
break;
}
case Qt::Key_Escape:
{
qskSendKey( inputItem, result.key );
break;
}
default:
{
qskSendKey( m_data->receiverItem(), result.key );
}
}
}
else if ( !result.text.isEmpty() )
{
// changing the current text
qskSendText( inputItem, result.text, result.isFinal );
qskSendText( m_data->receiverItem(), result.text, result.isFinal );
}
}
@ -356,73 +348,182 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
if ( m_data->inputItem == nullptr )
return;
/*
adjust the input panel to information provided from the input item
*/
QInputMethodQueryEvent event( queries );
QCoreApplication::sendEvent( m_data->inputItem, &event );
QInputMethodQueryEvent queryEvent( queries );
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
if ( queryEvent.queries() & Qt::ImHints )
if ( 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
bool hasPrediction = true;
bool hasEchoMode = false;
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() );
event.value( Qt::ImHints ).toInt() );
#endif
if ( hints & Qt::ImhHiddenText )
{
hasEchoMode = true;
}
if ( hints & Qt::ImhSensitiveData )
{
}
if ( hints & Qt::ImhNoAutoUppercase )
{
}
if ( hints & Qt::ImhPreferNumbers )
{
// we should start with having the number keys being visible
}
if ( hints & Qt::ImhPreferUppercase )
{
// we should start with having the upper keys being visible
}
if ( hints & Qt::ImhPreferLowercase )
{
// we should start with having the upper keys being visible
}
if ( hints & Qt::ImhNoPredictiveText )
{
hasPrediction = false;
}
if ( hints & Qt::ImhDate )
{
// we should have a date/time input
}
if ( hints & Qt::ImhTime )
{
// we should have a date/time input
}
if ( hints & Qt::ImhPreferLatin )
{
// conflicts with our concept of using the locale
}
if ( hints & Qt::ImhMultiLine )
{
// we need an implementation of QskTextEdit for this
}
if ( hints & Qt::ImhDigitsOnly )
{
// using a numpad instead of our virtual keyboard
}
if ( hints & Qt::ImhFormattedNumbersOnly )
{
// a numpad with decimal point and minus sign
}
if ( hints & Qt::ImhUppercaseOnly )
{
// locking all other keys
}
if ( hints & Qt::ImhLowercaseOnly )
{
// locking all other keys
}
if ( hints & Qt::ImhDialableCharactersOnly )
{
// characters suitable for phone dialing
}
if ( hints & Qt::ImhEmailCharactersOnly )
{
// characters suitable for email addresses
}
if ( hints & Qt::ImhUrlCharactersOnly )
{
// characters suitable for URLs
}
if ( hints & Qt::ImhLatinOnly )
{
// locking all other keys
}
m_data->hasPrediction = hasPrediction;
m_data->predictionBar->setVisible(
hasPrediction && m_data->engine && m_data->engine->predictor() );
m_data->inputProxy->setEchoMode(
hasEchoMode ? QskTextInput::PasswordEchoOnEdit : QskTextInput::Normal );
m_data->inputHints = hints;
}
#if 0
if ( queryEvent.queries() & Qt::ImPreferredLanguage )
if ( queries & Qt::ImPreferredLanguage )
{
// already handled by the input context
}
if ( queries & Qt::ImMaximumTextLength )
{
// needs to be handled before Qt::ImCursorPosition !
m_data->maxChars = event.value( Qt::ImMaximumTextLength ).toInt();
#if 1
if ( m_data->maxChars >= 32767 )
m_data->maxChars = -1;
#endif
if ( m_data->hasInputProxy )
m_data->inputProxy->setMaxLength( m_data->maxChars );
}
if ( queries & Qt::ImSurroundingText )
{
if ( m_data->hasInputProxy )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
m_data->inputProxy->setText( text );
}
}
if ( queries & Qt::ImCursorPosition )
{
if ( m_data->hasInputProxy )
{
const auto pos = event.value( Qt::ImCursorPosition ).toInt();
m_data->inputProxy->setCursorPosition( pos );
}
}
if ( queries & Qt::ImCurrentSelection )
{
#if 0
const auto text = event.value( Qt::ImCurrentSelection ).toString();
if ( !text.isEmpty() )
{
}
#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::ImTextBeforeCursor
Qt::ImTextAfterCursor
Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType
Qt::ImAnchorRectangle
Qt::ImInputItemClipRectangle // could be used for the geometry of the panel
Qt::ImInputItemClipRectangle
*/
}
void QskInputPanel::keyPressEvent( QKeyEvent* event )

View File

@ -16,7 +16,7 @@ class QLocale;
template class QVector< QString >;
class QSK_EXPORT QskInputPanel: public QskBox
class QSK_EXPORT QskInputPanel : public QskBox
{
Q_OBJECT
@ -49,7 +49,6 @@ public:
virtual QskAspect::Subcontrol effectiveSubcontrol(
QskAspect::Subcontrol ) const override;
void updateInputProxy( const QQuickItem* );
virtual void processInputMethodQueries( Qt::InputMethodQueries );
Q_SIGNALS: