qskinny/src/inputpanel/QskInputContext.cpp

502 lines
12 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 "QskInputPanel.h"
2018-06-12 08:20:48 +02:00
#include "QskInputPanelBox.h"
2018-05-01 12:41:20 +02:00
#include "QskLinearBox.h"
#include "QskDialog.h"
#include "QskPopup.h"
#include "QskWindow.h"
#include "QskEvent.h"
#include "QskQuick.h"
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
#include <QPointer>
2018-04-20 08:52:26 +02:00
#include <QGuiApplication>
#include <QMap>
2018-04-20 08:52:26 +02:00
2018-04-27 13:48:51 +02:00
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h>
#include <qpa/qplatforminputcontext.h>
2018-04-27 13:48:51 +02:00
namespace
{
2018-06-12 08:20:48 +02:00
class Panel final : public QskInputPanel
{
public:
2018-06-12 08:20:48 +02:00
Panel( QQuickItem* parent = nullptr ):
QskInputPanel( parent )
{
2018-06-12 08:20:48 +02:00
setAutoLayoutChildren( true );
2018-06-12 08:20:48 +02:00
m_box = new QskInputPanelBox( this );
2018-06-12 08:20:48 +02:00
connect( m_box, &QskInputPanelBox::keySelected,
this, &QskInputPanel::keySelected );
2018-06-12 08:20:48 +02:00
connect( m_box, &QskInputPanelBox::predictiveTextSelected,
this, &QskInputPanel::predictiveTextSelected );
}
2018-06-12 08:20:48 +02:00
virtual void attachItem( QQuickItem* item ) override
{
m_box->attachInputItem( item );
}
virtual QQuickItem* inputProxy() const override
{
2018-06-12 08:20:48 +02:00
return m_box->inputProxy();
}
2018-06-12 08:20:48 +02:00
virtual void setPrompt( const QString& prompt ) override
{
m_box->setInputPrompt( prompt );
}
2018-06-12 08:20:48 +02:00
virtual void setPredictionEnabled( bool on ) override
{
2018-06-12 08:20:48 +02:00
m_box->setPanelHint( QskInputPanelBox::Prediction, on );
}
2018-06-12 08:20:48 +02:00
virtual void setPrediction( const QStringList& prediction ) override
{
2018-06-12 08:20:48 +02:00
m_box->setPrediction( prediction );
}
private:
2018-06-12 08:20:48 +02:00
QskInputPanelBox* m_box;
};
class Channel
{
public:
// item receiving the input
QPointer< QQuickItem > item;
// panel for inserting the input
QPointer< QskInputPanel > panel;
// popup or window embedding the panel
QPointer< QskPopup > popup;
QPointer< QskWindow > window;
};
class ChannelTable
{
public:
inline Channel* currentChannel() const
{
const auto object = QGuiApplication::focusObject();
return channel( qobject_cast< const QQuickItem* >( object ) );
}
inline Channel* channel( const QQuickWindow* window ) const
{
if ( window )
{
auto it = m_map.constFind( window );
if ( it != m_map.constEnd() )
return const_cast< Channel* >( &it.value() );
}
return nullptr;
}
inline Channel* channel( const QQuickItem* item ) const
{
if ( item )
{
auto channel = this->channel( item->window() );
if ( channel && channel->item == item )
return channel;
}
return nullptr;
}
inline Channel* ancestorChannel( const QQuickItem* item ) const
{
for ( auto it = m_map.constBegin();
it != m_map.constEnd(); ++it )
{
if ( const auto panel = it.value().panel )
{
if ( ( item == panel )
|| qskIsAncestorOf( panel, item ) )
{
return const_cast< Channel*>( &it.value() );
}
}
}
return nullptr;
}
inline Channel* insert( const QQuickWindow* window )
{
return &m_map[ window ];
}
inline void remove( const QQuickWindow* window )
{
m_map.remove( window );
}
private:
QMap< const QQuickWindow*, Channel > m_map;
};
}
static QPointer< QskInputContext > qskInputContext = nullptr;
2018-04-27 13:48:51 +02:00
static void qskSendToPlatformContext( QEvent::Type type )
2018-04-27 13:48:51 +02:00
{
const auto platformInputContext =
QGuiApplicationPrivate::platformIntegration()->inputContext();
2018-04-27 13:48:51 +02:00
if ( platformInputContext )
{
QEvent event( type );
QCoreApplication::sendEvent( platformInputContext, &event );
}
2018-04-27 13:48:51 +02:00
}
static void qskInputContextHook()
2018-04-27 13:48:51 +02:00
{
qAddPostRoutine( [] { delete qskInputContext; } );
2018-04-27 13:48:51 +02:00
}
Q_COREAPP_STARTUP_FUNCTION( qskInputContextHook )
2018-04-27 13:48:51 +02:00
void QskInputContext::setInstance( QskInputContext* inputContext )
{
if ( inputContext != qskInputContext )
{
const auto oldContext = qskInputContext;
qskInputContext = inputContext;
2018-04-27 13:48:51 +02:00
if ( oldContext && oldContext->parent() == nullptr )
delete oldContext;
2018-04-27 13:48:51 +02:00
qskSendToPlatformContext( QEvent::PlatformPanel );
}
2018-04-27 13:48:51 +02:00
}
QskInputContext* QskInputContext::instance()
2018-04-27 13:48:51 +02:00
{
return qskInputContext;
2018-04-27 13:48:51 +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
inline QskInputPanel* createPanel( QskInputContext* context ) const
{
QskInputPanel* panel = nullptr;
if ( this->factory )
panel = this->factory->createPanel();
if ( panel == nullptr )
panel = new Panel();
connect( panel, &QskInputPanel::visibleChanged,
context, &QskInputContext::activeChanged );
connect( panel, &QskInputPanel::localeChanged,
context, [] { qskSendToPlatformContext( QEvent::LocaleChange ); } );
return panel;
}
inline QskPopup* createPopup( QskInputPanel* panel ) const
{
auto popup = new QskPopup();
popup->setAutoLayoutChildren( true );
popup->setTransparentForPositioner( false );
popup->setModal( true );
auto box = new QskLinearBox( popup );
box->addItem( panel );
const auto alignment = panel->alignment() & Qt::AlignVertical_Mask;
popup->setOverlay( alignment == Qt::AlignVCenter );
switch( alignment )
{
case Qt::AlignTop:
{
box->setExtraSpacingAt( Qt::BottomEdge | Qt::LeftEdge | Qt::RightEdge );
break;
}
case Qt::AlignVCenter:
{
box->setMargins( QMarginsF( 5, 5, 5, 5 ) );
break;
}
case Qt::AlignBottom:
default:
{
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
}
}
return popup;
}
inline QskWindow* createWindow( QskInputPanel* panel ) const
{
auto window = new QskWindow();
window->setFlags( window->flags() & Qt::Dialog );
//window->setModality( Qt::ApplicationModal );
window->setAutoLayoutChildren( true );
#if 0
window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
#endif
panel->setParentItem( window->contentItem() );
return window;
}
2018-04-04 20:19:47 +02:00
ChannelTable channels;
2018-06-12 08:20:48 +02:00
QPointer< QskInputContextFactory > factory;
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" );
2017-07-21 18:21:34 +02:00
}
QskInputContext::~QskInputContext()
{
}
2018-06-12 08:20:48 +02:00
void QskInputContext::setFactory( QskInputContextFactory* factory )
{
2018-06-12 08:20:48 +02:00
if ( m_data->factory == factory )
return;
2018-06-12 08:20:48 +02:00
if ( m_data->factory && m_data->factory->parent() == this )
delete m_data->factory;
2018-06-12 08:20:48 +02:00
m_data->factory = factory;
2018-06-12 08:20:48 +02:00
if ( factory && factory->parent() == nullptr )
factory->setParent( this );
}
2018-06-12 08:20:48 +02:00
QskInputContextFactory* QskInputContext::factory() const
{
2018-06-12 08:20:48 +02:00
return m_data->factory;
}
2018-06-12 08:20:48 +02:00
QskTextPredictor* QskInputContext::textPredictor( const QLocale& locale )
2017-07-21 18:21:34 +02:00
{
2018-06-12 08:20:48 +02:00
if ( m_data->factory )
return m_data->factory->createPredictor( locale );
return nullptr;
2017-07-21 18:21:34 +02:00
}
void QskInputContext::update( const QQuickItem* item, Qt::InputMethodQueries queries )
2018-04-04 12:05:01 +02:00
{
if ( item == nullptr )
{
// those are coming from QQuickWindow based on focus changes
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
}
auto channel = m_data->channels.channel( item );
if ( channel == nullptr )
return;
if ( queries & Qt::ImEnabled )
{
QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( channel->item, &event );
2017-07-21 18:21:34 +02:00
if ( !event.value( Qt::ImEnabled ).toBool() )
2017-07-21 18:21:34 +02:00
{
hidePanel( item );
return;
2017-07-21 18:21:34 +02:00
}
}
channel->panel->updateInputPanel( queries );
2018-04-04 15:19:51 +02:00
}
QRectF QskInputContext::panelRect() const
2017-07-21 18:21:34 +02:00
{
/*
As we can have more than panel at the same time we
better don't return any geometry
*/
return QRectF();
2017-07-21 18:21:34 +02:00
}
void QskInputContext::showPanel( const QQuickItem* item )
2018-04-30 10:03:51 +02:00
{
if ( item == nullptr )
2018-06-12 08:20:48 +02:00
return;
if ( m_data->channels.ancestorChannel( item ) )
{
// We are inside of an existing panel
2018-04-27 13:48:51 +02:00
return;
}
2018-04-27 13:48:51 +02:00
if ( auto channel = m_data->channels.channel( item->window() ) )
2018-04-11 17:33:43 +02:00
{
if ( channel->item == item )
return;
hidePanel( channel->item );
2018-04-27 13:48:51 +02:00
}
auto panel = m_data->createPanel( this );
auto channel = m_data->channels.insert( item->window() );
channel->item = const_cast< QQuickItem*>( item );
channel->panel = panel;
2018-04-11 17:33:43 +02:00
2018-04-27 13:48:51 +02:00
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
2018-04-11 17:33:43 +02:00
{
2018-04-27 13:48:51 +02:00
// The input panel is embedded in a top level window
auto window = m_data->createWindow( panel );
2018-04-11 17:33:43 +02:00
QSize size = window->effectivePreferredSize();
if ( size.isEmpty() )
2018-04-11 17:33:43 +02:00
{
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
window->resize( size );
window->show();
window->setDeleteOnClose( true );
2018-04-11 17:33:43 +02:00
channel->window = window;
2018-04-11 17:33:43 +02:00
}
else
2017-07-21 18:21:34 +02:00
{
2018-04-27 13:48:51 +02:00
// The input panel is embedded in a popup
auto popup = m_data->createPopup( panel );
2017-07-21 18:21:34 +02:00
popup->setParentItem( item->window()->contentItem() );
popup->setParent( this );
#if 1
popup->setVisible( true );
#endif
2018-06-01 12:00:31 +02:00
channel->popup = popup;
}
2018-04-11 17:33:43 +02:00
panel->attachInputItem( const_cast< QQuickItem* >( item ) );
2017-07-21 18:21:34 +02:00
}
void QskInputContext::hidePanel( const QQuickItem* item )
2017-07-21 18:21:34 +02:00
{
if ( item == nullptr )
return;
2018-04-30 10:03:51 +02:00
if ( auto channel = m_data->channels.channel( item ) )
{
if ( channel->popup )
channel->popup->deleteLater();
2018-06-01 12:00:31 +02:00
if ( channel->window )
channel->window->close(); // deleteOnClose is set
m_data->channels.remove( item->window() );
}
2017-07-21 18:21:34 +02:00
}
void QskInputContext::setInputPanelVisible( const QQuickItem* item, bool on )
2017-07-21 18:21:34 +02:00
{
// called from inside the controls
if ( item == nullptr )
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
if ( item )
{
if ( on )
showPanel( item );
else
hidePanel( item );
}
2017-07-21 18:21:34 +02:00
}
bool QskInputContext::isInputPanelVisible() const
2017-07-21 18:21:34 +02:00
{
return m_data->channels.currentChannel() != nullptr;
2017-07-21 18:21:34 +02:00
}
QLocale QskInputContext::locale() const
2017-07-21 18:21:34 +02:00
{
if ( auto channel = m_data->channels.currentChannel() )
2018-04-27 13:48:51 +02:00
{
if ( channel->panel )
return channel->panel->locale();
2018-04-27 13:48:51 +02:00
}
2018-03-14 17:30:39 +01:00
return QLocale();
}
2018-04-30 10:03:51 +02:00
void QskInputContext::setFocusObject( QObject* )
{
2017-07-21 18:21:34 +02:00
}
void QskInputContext::invokeAction(
QInputMethod::Action, int cursorPosition )
2018-04-20 08:52:26 +02:00
{
// called from qquicktextinput/qquicktextedit
Q_UNUSED( cursorPosition );
2017-07-21 18:21:34 +02:00
}
void QskInputContext::commitPrediction( bool )
2018-04-04 12:05:01 +02:00
{
2018-04-27 13:48:51 +02:00
/*
called, when the input item loses the focus.
2018-04-27 13:48:51 +02:00
As it it should be possible to navigate inside of the
2018-06-12 08:20:48 +02:00
panel what should we do here ?
2018-04-27 13:48:51 +02:00
*/
2018-04-04 12:05:01 +02:00
}
2018-06-12 08:20:48 +02:00
QskInputContextFactory::QskInputContextFactory( QObject* parent ):
QObject( parent )
{
}
QskInputContextFactory::~QskInputContextFactory()
{
}
QskTextPredictor* QskInputContextFactory::createPredictor( const QLocale& ) const
{
return nullptr;
}
QskInputPanel* QskInputContextFactory::createPanel() const
{
return new Panel();
}
2017-07-21 18:21:34 +02:00
#include "moc_QskInputContext.cpp"