qskinny/src/inputpanel/QskInputContext.cpp

590 lines
14 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"
#include "QskTextPredictor.h"
#include "QskInputPanel.h"
#include "QskInputEngine.h"
2018-04-12 12:03:51 +02:00
#include "QskLinearBox.h"
2017-07-21 18:21:34 +02:00
#include <QskDialog.h>
2018-04-11 17:33:43 +02:00
#include <QskPopup.h>
2017-07-21 18:21:34 +02:00
#include <QskWindow.h>
#include <QskSetup.h>
2018-04-11 17:33:43 +02:00
#include <QskEvent.h>
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
#include <QHash>
#include <QPointer>
2018-04-20 08:52:26 +02:00
#include <QGuiApplication>
static inline uint qskHashLocale( const QLocale& locale )
2018-04-20 08:52:26 +02:00
{
return uint( locale.language() + ( uint( locale.country() ) << 16 ) );
2018-04-20 08:52:26 +02:00
}
namespace
2018-04-04 20:19:47 +02:00
{
class PredictorTable
2018-04-04 20:19:47 +02:00
{
public:
void replace( const QLocale& locale, QskTextPredictor* predictor )
{
const auto key = qskHashLocale( locale );
2018-04-04 20:19:47 +02:00
if ( predictor )
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
if ( it.value() == predictor )
return;
2018-04-04 20:19:47 +02:00
delete it.value();
*it = predictor;
}
else
{
hashTab.insert( key, predictor );
}
}
else
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
delete it.value();
hashTab.erase( it );
}
}
}
QskTextPredictor* find( const QLocale& locale )
{
const auto key = qskHashLocale( locale );
return hashTab.value( key, nullptr );
}
2018-04-04 20:19:47 +02:00
private:
QHash< uint, QskTextPredictor* > hashTab;
};
2018-04-20 08:52:26 +02:00
}
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:
2018-04-11 17:33:43 +02:00
// item receiving the input
2018-04-01 12:47:44 +02:00
QPointer< QQuickItem > inputItem;
2018-04-11 17:33:43 +02:00
// item, wher the user enters texts/keys
2018-04-12 13:32:28 +02:00
QPointer< QQuickItem > inputPanel;
2018-04-11 17:33:43 +02:00
// popup or window embedding the inputPanel
2018-04-12 12:03:51 +02:00
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
2018-04-04 20:19:47 +02:00
PredictorTable predictorTable;
QskInputEngine* engine = nullptr;
// 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" );
m_data->engine = new QskInputEngine( this );
2018-04-20 08:52:26 +02:00
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
2018-04-02 17:01:04 +02:00
setInputPanel( qskSetup->inputPanel() );
2017-07-21 18:21:34 +02:00
}
QskInputContext::~QskInputContext()
{
}
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;
auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel );
if ( isInputPanelVisible() )
{
if ( item == nullptr )
{
hideInputPanel();
}
else
{
if ( panel )
panel->attachInputItem( item );
2018-04-04 15:19:51 +02:00
update( Qt::ImQueryAll );
}
2017-07-21 18:21:34 +02:00
}
else
2017-07-21 18:21:34 +02:00
{
// no need for updates
if ( panel )
panel->attachInputItem( nullptr );
}
2017-07-21 18:21:34 +02:00
m_data->inputItem = item;
}
void QskInputContext::update( Qt::InputMethodQueries queries )
{
if ( queries & Qt::ImEnabled )
{
QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &event );
2017-07-21 18:21:34 +02:00
if ( !event.value( Qt::ImEnabled ).toBool() )
2017-07-21 18:21:34 +02:00
{
hideInputPanel();
return;
2017-07-21 18:21:34 +02:00
}
}
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->processInputMethodQueries( queries );
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-11 17:33:43 +02:00
auto& inputPanel = m_data->inputPanel;
auto& inputPopup = m_data->inputPopup;
auto& inputWindow = m_data->inputWindow;
if ( inputPanel == nullptr )
{
auto panel = new QskInputPanel();
2018-04-11 17:33:43 +02:00
panel->setParent( this );
panel->setInputProxy( true );
2018-04-11 17:33:43 +02:00
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 );
inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
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() );
2018-04-11 17:33:43 +02:00
inputWindow->show();
inputWindow->installEventFilter( this );
}
}
else
2017-07-21 18:21:34 +02:00
{
2018-04-11 17:33:43 +02:00
delete inputWindow;
2017-07-21 18:21:34 +02:00
2018-04-11 17:33:43 +02:00
if ( inputPopup == nullptr )
2017-07-21 18:21:34 +02:00
{
2018-04-11 17:33:43 +02:00
if ( isPopupPanel )
{
inputPopup = qobject_cast< QskPopup* >( inputPanel );
}
else
{
auto popup = new QskPopup( m_data->inputItem->window()->contentItem() );
2018-04-11 17:33:43 +02:00
popup->setAutoLayoutChildren( true );
2018-04-12 12:03:51 +02:00
popup->setTransparentForPositioner( false );
popup->setOverlay( false );
2018-04-11 17:33:43 +02:00
popup->setModal( true );
2018-04-12 12:03:51 +02:00
auto box = new QskLinearBox( popup );
box->addItem( inputPanel );
if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) )
{
if ( panel->hasInputProxy() )
{
popup->setOverlay( true );
}
}
if ( !popup->hasOverlay() )
{
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
}
2018-04-11 17:33:43 +02:00
inputPopup = popup;
}
2018-04-11 17:33:43 +02:00
inputPopup->installEventFilter( this );
2017-07-21 18:21:34 +02:00
}
2018-04-11 17:33:43 +02:00
if ( inputPopup->window() == nullptr )
2017-07-21 18:21:34 +02:00
{
2018-04-11 17:33:43 +02:00
QQuickWindow* window = nullptr;
if ( m_data->inputItem )
window = m_data->inputItem->window();
else
window = qobject_cast< QQuickWindow* >( QGuiApplication::focusWindow() );
2017-07-21 18:21:34 +02:00
if ( window )
{
2018-04-11 17:33:43 +02:00
inputPopup->setParentItem( window->contentItem() );
2017-07-21 18:21:34 +02:00
}
}
2018-04-11 17:33:43 +02:00
inputPopup->setVisible( true );
}
2018-04-11 17:33:43 +02:00
2018-04-12 13:32:28 +02:00
update( Qt::ImQueryAll );
#if 1
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->updateInputProxy( m_data->inputItem );
#endif
2018-04-11 17:33:43 +02:00
inputPanel->setVisible( true );
#if 0
if ( auto focusItem = inputPanel->nextItemInFocusChain( true ) )
qskForceActiveFocus( focusItem, Qt::OtherFocusReason );
#endif
2018-04-11 17:33:43 +02:00
connect( inputPanel->window(), &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged );
updateInputPanel( m_data->inputItem );
m_data->engine->setPredictor(
m_data->predictorTable.find( locale() ) );
2017-07-21 18:21:34 +02:00
}
void QskInputContext::hideInputPanel()
{
2018-04-11 17:33:43 +02:00
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 );
2018-04-11 17:33:43 +02:00
}
2017-07-21 18:21:34 +02:00
2018-04-11 17:33:43 +02:00
if ( m_data->inputPopup == m_data->inputPanel )
{
2018-04-11 17:33:43 +02:00
m_data->inputPopup->removeEventFilter( this );
m_data->inputPopup = nullptr;
}
2017-07-21 18:21:34 +02:00
else
{
2018-04-11 17:33:43 +02:00
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 );
}
#endif
2018-04-12 12:03:51 +02:00
m_data->inputPopup->deleteLater();
}
2018-04-11 17:33:43 +02:00
}
QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr;
if ( window )
{
window->removeEventFilter( this );
window->close(); // deleteOnClose is set
}
qGuiApp->removeEventFilter( this );
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 );
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
{
if ( m_data->inputItem )
{
QInputMethodQueryEvent event( Qt::ImPreferredLanguage );
QCoreApplication::sendEvent( m_data->inputItem, &event );
return event.value( Qt::ImPreferredLanguage ).toLocale();
}
return QLocale();
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
2018-04-11 17:33:43 +02:00
navigating to or inside the input popup/window
*/
2017-07-21 18:21:34 +02:00
2018-04-11 17:33:43 +02:00
bool isAccepted = ( m_data->inputItem == nullptr );
if ( !isAccepted )
{
if ( 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;
}
}
2018-04-13 16:32:48 +02:00
else
{
isAccepted = true;
}
2018-04-11 17:33:43 +02:00
}
if ( isAccepted )
setInputItem( focusItem );
}
2017-07-21 18:21:34 +02:00
}
void QskInputContext::registerPredictor(
const QLocale& locale, QskTextPredictor* predictor )
2017-07-21 18:21:34 +02:00
{
auto oldPredictor = m_data->predictorTable.find( locale );
if ( predictor == oldPredictor )
return;
2018-04-20 08:52:26 +02:00
if ( predictor )
predictor->setParent( this );
2018-04-20 08:52:26 +02:00
m_data->predictorTable.replace( locale, predictor );
2018-04-20 08:52:26 +02:00
if ( oldPredictor )
delete oldPredictor;
2018-04-20 08:52:26 +02:00
if ( qskHashLocale( locale ) == qskHashLocale( this->locale() ) )
m_data->engine->setPredictor( predictor );
2017-07-21 18:21:34 +02:00
}
QskTextPredictor* QskInputContext::registeredPredictor( const QLocale& locale )
2018-04-20 08:52:26 +02:00
{
return m_data->predictorTable.find( locale );
2018-04-20 08:52:26 +02:00
}
void QskInputContext::invokeAction( QInputMethod::Action, int )
2018-04-20 08:52:26 +02:00
{
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
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-11 17:33:43 +02:00
if ( m_data->inputPanel->parent() == this )
{
delete m_data->inputPanel;
}
else
{
m_data->inputPanel->setParentItem( nullptr );
}
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-11 17:33:43 +02:00
if ( inputPanel->parent() == nullptr )
inputPanel->setParent( this );
2018-04-04 20:19:47 +02:00
connect( inputPanel, &QQuickItem::visibleChanged,
2018-03-31 18:34:51 +02:00
this, &QPlatformInputContext::emitInputPanelVisibleChanged );
2018-04-04 20:19:47 +02:00
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 );
}
2017-07-21 18:21:34 +02:00
}
}
2018-04-04 12:05:01 +02:00
void QskInputContext::reset()
{
}
void QskInputContext::commit()
{
// called on focus changes
2018-04-04 12:05:01 +02:00
}
bool QskInputContext::eventFilter( QObject* object, QEvent* event )
2018-04-11 17:33:43 +02:00
{
2018-04-12 12:03:51 +02:00
if ( object == m_data->inputWindow )
{
2018-04-12 12:03:51 +02:00
switch( event->type() )
2018-04-11 17:33:43 +02:00
{
2018-04-12 12:03:51 +02:00
case QEvent::Move:
{
if ( m_data->inputPanel )
emitKeyboardRectChanged();
2018-04-11 17:33:43 +02:00
2018-04-12 12:03:51 +02:00
break;
}
case QEvent::Resize:
{
if ( m_data->inputPanel )
m_data->inputPanel->setSize( m_data->inputWindow->size() );
break;
}
case QEvent::DeferredDelete:
{
m_data->inputWindow = nullptr;
break;
}
default:
break;
2018-04-11 17:33:43 +02:00
}
2018-04-12 12:03:51 +02:00
}
2018-04-20 08:52:26 +02:00
else if ( object == m_data->inputPopup )
2018-04-12 12:03:51 +02:00
{
2018-04-12 13:32:28 +02:00
switch( static_cast< int >( event->type() ) )
{
2018-04-12 12:03:51 +02:00
case QskEvent::GeometryChange:
2018-04-11 17:33:43 +02:00
{
emitKeyboardRectChanged();
2018-04-12 12:03:51 +02:00
break;
}
case QEvent::DeferredDelete:
{
2018-04-20 08:52:26 +02:00
m_data->inputPopup = nullptr;
2018-04-12 12:03:51 +02:00
break;
}
}
}
return Inherited::eventFilter( object, event );
}
2018-04-20 08:52:26 +02:00
bool QskInputContext::filterEvent( const QEvent* )
2018-04-04 12:05:01 +02:00
{
2018-04-04 15:19:51 +02:00
// called from QXcbKeyboard, but what about other platforms
2018-04-04 12:05:01 +02:00
return false;
}
2017-07-21 18:21:34 +02:00
#include "moc_QskInputContext.cpp"