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

View File

@ -159,9 +159,6 @@ public:
Q_PROPERTY( QStringList skinList READ skinList NOTIFY skinListChanged ) 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 Q_PRIVATE_PROPERTY( setup(), QskSetupFlagsProvider controlFlags
READ controlFlags WRITE setControlFlags NOTIFY controlFlagsChanged ) READ controlFlags WRITE setControlFlags NOTIFY controlFlagsChanged )
@ -177,8 +174,7 @@ public:
connect( setup(), &QskSetup::skinChanged, connect( setup(), &QskSetup::skinChanged,
this, &QskMain::skinChanged, Qt::QueuedConnection ); this, &QskMain::skinChanged, Qt::QueuedConnection );
connect( setup(), &QskSetup::inputPanelChanged,
this, &QskMain::inputPanelChanged );
connect( setup(), &QskSetup::controlFlagsChanged, connect( setup(), &QskSetup::controlFlagsChanged,
this, &QskMain::controlFlagsChanged, Qt::QueuedConnection ); this, &QskMain::controlFlagsChanged, Qt::QueuedConnection );
} }

View File

@ -172,12 +172,16 @@ void qskForceActiveFocus( QQuickItem* item, Qt::FocusReason reason )
void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries ) void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries )
{ {
if ( ( item == nullptr ) ||
!( item->flags() & QQuickItem::ItemAcceptsInputMethod ) )
{
return;
}
auto inputMethod = QGuiApplication::inputMethod(); auto inputMethod = QGuiApplication::inputMethod();
bool doUpdate = item->hasActiveFocus(); bool doUpdate = item->hasActiveFocus();
if ( !doUpdate )
{
/* /*
We could also get the inputContext from QInputMethodPrivate We could also get the inputContext from QInputMethodPrivate
but for some reason the gcc sanitizer reports errors but for some reason the gcc sanitizer reports errors
@ -201,7 +205,6 @@ void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries querie
doUpdate = ( item == inputItem ); doUpdate = ( item == inputItem );
} }
} }
}
if ( doUpdate ) if ( doUpdate )
inputMethod->update( queries ); inputMethod->update( queries );

View File

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

View File

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

View File

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

View File

@ -4,21 +4,68 @@
*****************************************************************************/ *****************************************************************************/
#include "QskInputContext.h" #include "QskInputContext.h"
#include "QskInputPanel.h"
#include "QskTextPredictor.h" #include "QskTextPredictor.h"
#include "QskInputPanel.h" #include "QskInputPanel.h"
#include "QskInputEngine.h" #include "QskInputEngine.h"
#include "QskLinearBox.h" #include <QskLinearBox.h>
#include <QskDialog.h> #include <QskDialog.h>
#include <QskPopup.h> #include <QskPopup.h>
#include <QskWindow.h> #include <QskWindow.h>
#include <QskSetup.h>
#include <QskEvent.h> #include <QskEvent.h>
#include <QHash>
#include <QPointer> #include <QPointer>
#include <QGuiApplication> #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 ) static inline uint qskHashLocale( const QLocale& locale )
{ {
return uint( locale.language() + ( uint( locale.country() ) << 16 ) ); return uint( locale.language() + ( uint( locale.country() ) << 16 ) );
@ -77,32 +124,20 @@ public:
// item receiving the input // item receiving the input
QPointer< QQuickItem > inputItem; QPointer< QQuickItem > inputItem;
// item, wher the user enters texts/keys // popup or window embedding qskInputPanel
QPointer< QQuickItem > inputPanel;
// popup or window embedding the inputPanel
QskPopup* inputPopup = nullptr; QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr; QskWindow* inputWindow = nullptr;
PredictorTable predictorTable; PredictorTable predictorTable;
QskInputEngine* engine = nullptr; QskInputEngine* engine = nullptr;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
}; };
QskInputContext::QskInputContext(): QskInputContext::QskInputContext():
m_data( new PrivateData() ) m_data( new PrivateData() )
{ {
setObjectName( "InputContext" ); setObjectName( "InputContext" );
m_data->engine = new QskInputEngine( this ); m_data->engine = new QskInputEngine( this );
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
setInputPanel( qskSetup->inputPanel() );
} }
QskInputContext::~QskInputContext() QskInputContext::~QskInputContext()
@ -125,37 +160,6 @@ QQuickItem* QskInputContext::inputItem()
return m_data->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 ) void QskInputContext::update( Qt::InputMethodQueries queries )
{ {
if ( queries & Qt::ImEnabled ) if ( queries & Qt::ImEnabled )
@ -170,173 +174,129 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
} }
} }
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) ) if ( qskInputPanel )
panel->processInputMethodQueries( queries ); qskInputPanel->processInputMethodQueries( queries );
} }
QRectF QskInputContext::keyboardRect() const QRectF QskInputContext::keyboardRect() const
{ {
if ( m_data->inputPanel // is this correct and what is this good for ?
&& QskDialog::instance()->policy() != QskDialog::TopLevelWindow ) if ( m_data->inputPopup )
{ return m_data->inputPopup->geometry();
return qskItemGeometry( m_data->inputPanel );
}
return Inherited::keyboardRect(); return Inherited::keyboardRect();
} }
bool QskInputContext::isAnimating() const bool QskInputContext::isAnimating() const
{ {
// can be implemented once we have some sliding/fading effects
return false; return false;
} }
void QskInputContext::showInputPanel() 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& inputPopup = m_data->inputPopup;
auto& inputWindow = m_data->inputWindow; 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; delete inputPopup;
if ( inputWindow == nullptr ) if ( inputWindow == nullptr )
{ {
inputWindow = new QskWindow(); inputWindow = new QskWindow();
inputWindow->setDeleteOnClose( true ); inputWindow->setDeleteOnClose( true );
#if 0
inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
#endif
inputPanel->setParentItem( inputWindow->contentItem() ); qskInputPanel->setParentItem( inputWindow->contentItem() );
}
QSizeF size;
if ( auto control = qobject_cast< const QskControl* >( inputPanel ) )
size = control->sizeHint();
QSize size = qskInputPanel->sizeHint().toSize();
if ( size.isEmpty() ) if ( size.isEmpty() )
size = QSizeF( 800, 240 ); // ### what size? {
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
inputWindow->resize( size.toSize() ); inputWindow->resize( size );
inputWindow->show(); inputWindow->show();
inputWindow->installEventFilter( this ); inputWindow->installEventFilter( this );
} }
}
else else
{ {
// The input panel is embedded in a popup
delete inputWindow; delete inputWindow;
if ( inputPopup == nullptr ) if ( inputPopup == nullptr )
{ {
if ( isPopupPanel ) inputPopup = new QskPopup( m_data->inputItem->window()->contentItem() );
{
inputPopup = qobject_cast< QskPopup* >( inputPanel );
}
else
{
auto popup = new QskPopup( m_data->inputItem->window()->contentItem() );
popup->setAutoLayoutChildren( true ); inputPopup->setAutoLayoutChildren( true );
popup->setTransparentForPositioner( false ); inputPopup->setTransparentForPositioner( false );
popup->setOverlay( false ); inputPopup->setModal( true );
popup->setModal( true );
auto box = new QskLinearBox( popup ); auto box = new QskLinearBox( inputPopup );
box->addItem( inputPanel ); box->addItem( qskInputPanel );
if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) ) /*
{ When the panel has an input proxy ( usually a local text input )
if ( panel->hasInputProxy() ) we don't need to see the input item and display the overlay
{ and align in the center of the window.
popup->setOverlay( true ); */
} const bool hasInputProxy = qskInputPanel->hasInputProxy();
}
if ( !popup->hasOverlay() ) inputPopup->setOverlay( hasInputProxy );
{
if ( !hasInputProxy )
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge ); box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
} }
inputPopup = popup; inputPopup->setParentItem( m_data->inputItem->window()->contentItem() );
} inputPopup->setVisible( true );
inputPopup->installEventFilter( this ); 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() );
}
}
inputPopup->setVisible( true );
}
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->engine->setPredictor(
m_data->predictorTable.find( locale() ) ); m_data->predictorTable.find( locale() ) );
qskInputPanel->setLocale( locale() );
qskInputPanel->attachInputItem( m_data->inputItem );
qskInputPanel->setEngine( m_data->engine );
} }
void QskInputContext::hideInputPanel() void QskInputContext::hideInputPanel()
{ {
if ( m_data->inputPanel )
{
// 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 ( m_data->inputPopup )
{ {
#if 1 #if 1
@ -355,40 +315,31 @@ void QskInputContext::hideInputPanel()
m_data->inputPopup->deleteLater(); m_data->inputPopup->deleteLater();
} }
}
if ( m_data->inputWindow )
{
QskWindow* window = m_data->inputWindow; QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr; m_data->inputWindow = nullptr;
if ( window )
{
window->removeEventFilter( this ); window->removeEventFilter( this );
window->close(); // deleteOnClose is set window->close(); // deleteOnClose is set
} }
qGuiApp->removeEventFilter( this ); if ( qskInputPanel )
{
//qskInputPanel->setVisible( false );
qskInputPanel->setParentItem( nullptr );
qskInputPanel->attachInputItem( nullptr );
qskInputPanel->setEngine( nullptr );
}
updateInputPanel( nullptr ); m_data->inputItem = 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 );
} }
bool QskInputContext::isInputPanelVisible() const bool QskInputContext::isInputPanelVisible() const
{ {
auto panel = m_data->inputPanel; return qskInputPanel && qskInputPanel->isVisible()
&& qskInputPanel->window() && qskInputPanel->window()->isVisible();
return panel && panel->isVisible()
&& panel->window() && panel->window()->isVisible();
} }
QLocale QskInputContext::locale() const QLocale QskInputContext::locale() const
@ -411,48 +362,52 @@ Qt::LayoutDirection QskInputContext::inputDirection() const
void QskInputContext::setFocusObject( QObject* focusObject ) void QskInputContext::setFocusObject( QObject* focusObject )
{ {
auto focusItem = qobject_cast< QQuickItem* >( focusObject ); if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject )
{
// we don't care
return;
}
if ( focusItem == nullptr ) bool doTerminate = true;
if ( focusObject == nullptr && m_data->inputPopup )
{ {
if ( m_data->inputItem ) if ( const auto window = m_data->inputItem->window() )
{ {
if ( m_data->inputItem->window() == QGuiApplication::focusWindow() ) auto focusItem = window->contentItem()->scopedFocusItem();
setInputItem( nullptr ); if ( focusItem == m_data->inputPopup )
doTerminate = false;
} }
} }
else
{
/*
Do not change the input item when
navigating to or inside the input popup/window
*/
bool isAccepted = ( m_data->inputItem == nullptr ); if ( doTerminate )
if ( !isAccepted )
{ {
if ( m_data->inputWindow ) if ( m_data->inputWindow )
{ {
if ( focusItem->window() != m_data->inputWindow ) auto focusWindow = QGuiApplication::focusWindow();
isAccepted = true;
if ( focusWindow == nullptr ||
QGuiApplication::focusWindow() == m_data->inputWindow )
{
doTerminate = false;
}
} }
else if ( m_data->inputPopup ) else if ( m_data->inputPopup )
{ {
if ( ( focusItem != m_data->inputPopup ) auto focusItem = qobject_cast< QQuickItem* >( focusObject );
&& !qskIsAncestorOf( m_data->inputPopup, focusItem ) )
if ( ( focusItem == m_data->inputPopup )
|| qskIsAncestorOf( m_data->inputPopup, focusItem ) )
{ {
isAccepted = true; doTerminate = false;
} }
} }
else
{
isAccepted = true;
}
} }
if ( isAccepted ) if ( doTerminate )
setInputItem( focusItem ); {
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::reset()
{ {
} }
void QskInputContext::commit() 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 ) bool QskInputContext::eventFilter( QObject* object, QEvent* event )
@ -539,15 +460,15 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
{ {
case QEvent::Move: case QEvent::Move:
{ {
if ( m_data->inputPanel ) if ( qskInputPanel )
emitKeyboardRectChanged(); emitKeyboardRectChanged();
break; break;
} }
case QEvent::Resize: case QEvent::Resize:
{ {
if ( m_data->inputPanel ) if ( qskInputPanel )
m_data->inputPanel->setSize( m_data->inputWindow->size() ); qskInputPanel->setSize( m_data->inputWindow->size() );
break; break;
} }

View File

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

View File

@ -18,10 +18,27 @@
#include <QInputMethodQueryEvent> #include <QInputMethodQueryEvent>
#include <QTextCharFormat> #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 ) const QString& text, bool isFinal )
{ {
if ( inputItem == nullptr ) if ( receiver == nullptr )
return; return;
if ( isFinal ) if ( isFinal )
@ -29,7 +46,7 @@ static inline void qskSendText( QQuickItem* inputItem,
QInputMethodEvent event; QInputMethodEvent event;
event.setCommitString( text ); event.setCommitString( text );
QCoreApplication::sendEvent( inputItem, &event ); QCoreApplication::sendEvent( receiver, &event );
} }
else else
{ {
@ -41,20 +58,20 @@ static inline void qskSendText( QQuickItem* inputItem,
QInputMethodEvent event( text, { attribute } ); 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; return;
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier ); QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyPress ); QCoreApplication::sendEvent( receiver, &keyPress );
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier ); QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyRelease ); QCoreApplication::sendEvent( receiver, &keyRelease );
} }
namespace namespace
@ -65,6 +82,7 @@ namespace
TextInput( QQuickItem* parentItem = nullptr ): TextInput( QQuickItem* parentItem = nullptr ):
QskTextInput( parentItem ) QskTextInput( parentItem )
{ {
setObjectName( "InputPanelInputProxy" );
} }
}; };
} }
@ -75,10 +93,18 @@ class QskInputPanel::PrivateData
{ {
public: public:
PrivateData(): PrivateData():
inputHints( 0 ),
maxChars( -1 ),
hasPrediction( true ),
hasInputProxy( true ) hasInputProxy( true )
{ {
} }
QQuickItem* receiverItem()
{
return hasInputProxy ? inputProxy : inputItem;
}
QPointer< QskInputEngine > engine; QPointer< QskInputEngine > engine;
QPointer< QQuickItem > inputItem; QPointer< QQuickItem > inputItem;
@ -88,6 +114,10 @@ public:
QskInputPredictionBar* predictionBar; QskInputPredictionBar* predictionBar;
QskVirtualKeyboard* keyboard; QskVirtualKeyboard* keyboard;
Qt::InputMethodHints inputHints;
int maxChars;
bool hasPrediction : 1;
bool hasInputProxy : 1; bool hasInputProxy : 1;
}; };
@ -146,7 +176,8 @@ void QskInputPanel::setEngine( QskInputEngine* engine )
this, &QskInputPanel::updatePredictionBar ); 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 ) void QskInputPanel::attachInputItem( QQuickItem* item )
@ -165,6 +196,9 @@ void QskInputPanel::attachInputItem( QQuickItem* item )
queries &= ~Qt::ImEnabled; queries &= ~Qt::ImEnabled;
processInputMethodQueries( queries ); processInputMethodQueries( queries );
if ( m_data->hasInputProxy )
m_data->inputProxy->setEditing( true );
} }
} }
@ -234,56 +268,6 @@ void QskInputPanel::setInputProxy( bool on )
prompt->setVisible( false ); 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 ) void QskInputPanel::commitPredictiveText( int index )
{ {
m_data->predictionBar->setPrediction( QVector< QString >() ); m_data->predictionBar->setPrediction( QVector< QString >() );
@ -302,34 +286,23 @@ void QskInputPanel::commitKey( int key )
if ( m_data->engine == nullptr || m_data->inputItem == nullptr ) if ( m_data->engine == nullptr || m_data->inputItem == nullptr )
return; 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; int spaceLeft = -1;
if ( !( inputHints & Qt::ImhMultiLine ) ) if ( !( m_data->inputHints & Qt::ImhMultiLine ) )
{ {
QInputMethodQueryEvent event( auto receiver = m_data->receiverItem();
Qt::ImSurroundingText | Qt::ImMaximumTextLength );
QCoreApplication::sendEvent( inputItem, &event ); if ( m_data->maxChars >= 0 )
const int max = event.value( Qt::ImMaximumTextLength ).toInt();
if ( max > 0 )
{ {
QInputMethodQueryEvent event( Qt::ImSurroundingText );
QCoreApplication::sendEvent( receiver, &event );
const auto text = event.value( Qt::ImSurroundingText ).toString(); 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, void QskInputPanel::processKey( int key,
@ -338,16 +311,35 @@ void QskInputPanel::processKey( int key,
const auto result = m_data->engine->processKey( key, inputHints, spaceLeft ); const auto result = m_data->engine->processKey( key, inputHints, spaceLeft );
auto inputItem = m_data->inputItem; auto inputItem = m_data->inputItem;
auto inputProxy = m_data->inputProxy;
if ( result.key ) if ( result.key )
{ {
// sending a control key switch( result.key )
{
case Qt::Key_Return:
{
if ( m_data->hasInputProxy )
qskSendReplaceText( inputItem, inputProxy->text() );
qskSendKey( inputItem, result.key ); 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() ) else if ( !result.text.isEmpty() )
{ {
// changing the current text // 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 ) if ( m_data->inputItem == nullptr )
return; return;
/* QInputMethodQueryEvent event( queries );
adjust the input panel to information provided from the input item QCoreApplication::sendEvent( m_data->inputItem, &event );
*/
QInputMethodQueryEvent queryEvent( queries ); if ( queries & Qt::ImHints )
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
if ( queryEvent.queries() & Qt::ImHints )
{ {
/* bool hasPrediction = true;
ImhHiddenText = 0x1, // might need to disable certain checks bool hasEchoMode = false;
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
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 >( 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 ( 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 #if 0
if ( queryEvent.queries() & Qt::ImPreferredLanguage ) const auto text = event.value( Qt::ImCurrentSelection ).toString();
if ( !text.isEmpty() )
{ {
} }
#endif #endif
}
/* /*
Qt::ImMicroFocus Qt::ImMicroFocus
Qt::ImCursorRectangle Qt::ImCursorRectangle
Qt::ImFont Qt::ImFont
Qt::ImCursorPosition
Qt::ImSurroundingText // important for chinese input
Qt::ImCurrentSelection // important for prediction
Qt::ImMaximumTextLength // should be monitored
Qt::ImAnchorPosition Qt::ImAnchorPosition
Qt::ImAbsolutePosition Qt::ImAbsolutePosition
Qt::ImTextBeforeCursor // important for chinese Qt::ImTextBeforeCursor
Qt::ImTextAfterCursor // important for chinese Qt::ImTextAfterCursor
Qt::ImPlatformData // hard to say... Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType Qt::ImEnterKeyType
Qt::ImAnchorRectangle Qt::ImAnchorRectangle
Qt::ImInputItemClipRectangle // could be used for the geometry of the panel Qt::ImInputItemClipRectangle
*/ */
} }
void QskInputPanel::keyPressEvent( QKeyEvent* event ) void QskInputPanel::keyPressEvent( QKeyEvent* event )

View File

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