QskTextField/QskTextInput seperated

This commit is contained in:
Uwe Rathmann 2024-12-18 12:37:18 +01:00
parent 74d14b6005
commit a91dcd5a56
13 changed files with 1297 additions and 1167 deletions

View File

@ -1776,14 +1776,14 @@ void Editor::setupTextFieldMetrics()
{
using Q = QskTextField;
setStrutSize( Q::Panel, { -1, 30_px } );
setPadding( Q::Panel, { 11_px, 0, 11_px, 0 } );
setStrutSize( Q::TextPanel, { -1, 30_px } );
setPadding( Q::TextPanel, { 11_px, 0, 11_px, 0 } );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxBorderMetrics( Q::TextPanel, 1_px );
for( const auto& state : { Q::Focused, Q::Editing } )
setBoxBorderMetrics( Q::Panel | state, { 1_px, 1_px, 1_px, 2_px } );
setBoxBorderMetrics( Q::TextPanel | state, { 1_px, 1_px, 1_px, 2_px } );
setBoxShape( Q::Panel, 3_px );
setBoxShape( Q::TextPanel, 3_px );
setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Text, Fluent2::Body );
@ -1801,7 +1801,7 @@ void Editor::setupTextFieldColors(
const auto& pal = theme.palette;
setColor( Q::Panel | SK::Selected, pal.fillColor.accent.selectedTextBackground );
setColor( Q::TextPanel | SK::Selected, pal.fillColor.accent.selectedTextBackground );
setColor( Q::Text | SK::Selected, pal.fillColor.textOnAccent.selectedText );
setColor( Q::PlaceholderText, pal.fillColor.text.secondary );
@ -1837,7 +1837,7 @@ void Editor::setupTextFieldColors(
textColor = pal.fillColor.text.disabled;
}
const auto panel = Q::Panel | section | state;
const auto panel = Q::TextPanel | section | state;
const auto text = Q::Text | section | state;
panelColor = rgbSolid( panelColor, pal.background.solid.base );

View File

@ -397,25 +397,25 @@ void Editor::setupTextField()
{
const auto colorGroup = ( state == A::NoState ) ? P::Active : P::Disabled;
setColor( Q::Panel | state, m_pal.color( colorGroup, P::Base ) );
setColor( Q::Panel | SK::Selected | state, m_pal.color( colorGroup, P::Highlight ) );
setColor( Q::TextPanel | state, m_pal.color( colorGroup, P::Base ) );
setColor( Q::TextPanel | SK::Selected | state, m_pal.color( colorGroup, P::Highlight ) );
setColor( Q::Text | state, m_pal.color( colorGroup, P::Text ) );
setColor( Q::Text | SK::Selected | state, m_pal.color( colorGroup, P::HighlightedText ) );
setColor( Q::PlaceholderText, m_pal.color( colorGroup, P::PlaceholderText ) );
}
setColor( Q::Panel | Q::ReadOnly, m_pal.disabled( P::Base ) );
setColor( Q::TextPanel | Q::ReadOnly, m_pal.disabled( P::Base ) );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxBorderMetrics( Q::TextPanel, 1_px );
setBoxBorderColors( Q::Panel, m_pal.outline );
setBoxBorderColors( Q::TextPanel, m_pal.outline );
#ifdef SHOW_FOCUS
setBoxBorderColors( Q::Panel | Q::Focused, m_pal.highlightedOutline );
setBoxBorderColors( Q::TextPanel | Q::Focused, m_pal.highlightedOutline );
#endif
setBoxShape( Q::Panel, 2_px );
setPadding( Q::Panel, 4_px );
setBoxShape( Q::TextPanel, 2_px );
setPadding( Q::TextPanel, 4_px );
}
void Editor::setupProgressBar()

View File

@ -434,12 +434,12 @@ void Editor::setupTextLabel()
void Editor::setupTextField()
{
using Q = QskTextField;
using SK = QskTextFieldSkinlet;
using SK = QskTextInputSkinlet;
setStrutSize( Q::Panel, -1.0, 56_px );
setPadding( Q::Panel, { 12_px, 8_px, 12_px, 8_px } );
setGradient( Q::Panel, m_pal.surfaceVariant );
setColor( Q::Panel | SK::Selected, m_pal.primary12 );
setColor( Q::TextPanel | SK::Selected, m_pal.primary12 );
setBoxShape( Q::Panel, m_pal.shapeExtraSmallTop );
setBoxBorderMetrics( Q::Panel, { 0, 0, 0, 1_px } );
setBoxBorderColors( Q::Panel, m_pal.onSurfaceVariant );

View File

@ -281,6 +281,8 @@ list(APPEND HEADERS
controls/QskTabViewSkinlet.h
controls/QskTextField.h
controls/QskTextFieldSkinlet.h
controls/QskTextInput.h
controls/QskTextInputSkinlet.h
controls/QskTextLabel.h
controls/QskTextLabelSkinlet.h
controls/QskVariantAnimator.h
@ -387,6 +389,8 @@ list(APPEND SOURCES
controls/QskTabViewSkinlet.cpp
controls/QskTextField.cpp
controls/QskTextFieldSkinlet.cpp
controls/QskTextInput.cpp
controls/QskTextInputSkinlet.cpp
controls/QskTextLabel.cpp
controls/QskTextLabelSkinlet.cpp
controls/QskVariantAnimator.cpp

View File

@ -4,514 +4,32 @@
*****************************************************************************/
#include "QskTextField.h"
#include "QskTextFieldSkinlet.h"
#include "QskFontRole.h"
#include "QskQuick.h"
QSK_QT_PRIVATE_BEGIN
#include <private/qquicktextinput_p.h>
#include <private/qquicktextinput_p_p.h>
QSK_QT_PRIVATE_END
QSK_SUBCONTROL( QskTextField, Panel )
QSK_SUBCONTROL( QskTextField, Text )
QSK_SUBCONTROL( QskTextField, PlaceholderText )
QSK_SYSTEM_STATE( QskTextField, ReadOnly, QskAspect::FirstSystemState << 1 )
QSK_SYSTEM_STATE( QskTextField, Editing, QskAspect::FirstSystemState << 2 )
static inline void qskPropagateReadOnly( QskTextField* input )
{
Q_EMIT input->readOnlyChanged( input->isReadOnly() );
QEvent event( QEvent::ReadOnlyChange );
QCoreApplication::sendEvent( input, &event );
}
static inline void qskBindSignals(
const QQuickTextInput* input, QskTextField* field )
{
QObject::connect( input, &QQuickTextInput::textChanged,
field, [ field ] { Q_EMIT field->textChanged( field->text() ); } );
QObject::connect( input, &QQuickTextInput::displayTextChanged,
field, [ field ] { Q_EMIT field->displayTextChanged( field->displayText() ); } );
QObject::connect( input, &QQuickTextInput::textEdited,
field, [ field ] { Q_EMIT field->textEdited( field->text() ); } );
QObject::connect( input, &QQuickTextInput::validatorChanged,
field, &QskTextField::validatorChanged );
QObject::connect( input, &QQuickTextInput::inputMaskChanged,
field, &QskTextField::inputMaskChanged );
QObject::connect( input, &QQuickTextInput::readOnlyChanged,
field, [ field ] { qskPropagateReadOnly( field ); } );
QObject::connect( input, &QQuickTextInput::overwriteModeChanged,
field, &QskTextField::overwriteModeChanged );
QObject::connect( input, &QQuickTextInput::maximumLengthChanged,
field, &QskTextField::maximumLengthChanged );
QObject::connect( input, &QQuickTextInput::wrapModeChanged,
field, [ field ] { Q_EMIT field->wrapModeChanged( field->wrapMode() ); } );
QObject::connect( input, &QQuickTextInput::echoModeChanged,
field, [ field ] { Q_EMIT field->echoModeChanged( field->echoMode() ); } );
QObject::connect( input, &QQuickTextInput::passwordCharacterChanged,
field, &QskTextField::passwordCharacterChanged );
QObject::connect( input, &QQuickTextInput::passwordMaskDelayChanged,
field, &QskTextField::passwordMaskDelayChanged );
QObject::connect( input, &QQuickItem::implicitWidthChanged,
field, &QskControl::resetImplicitSize );
QObject::connect( input, &QQuickItem::implicitHeightChanged,
field, &QskControl::resetImplicitSize );
}
namespace
{
class TextInput final : public QQuickTextInput
{
using Inherited = QQuickTextInput;
public:
TextInput( QskTextField* );
void setEditing( bool on );
inline void setAlignment( Qt::Alignment alignment )
{
setHAlign( ( HAlignment ) ( int( alignment ) & 0x0f ) );
setVAlign( ( VAlignment ) ( int( alignment ) & 0xf0 ) );
}
bool fixup()
{
return QQuickTextInputPrivate::get( this )->fixup();
}
bool hasAcceptableInput() const
{
/*
we would like to call QQuickTextInputPrivate::hasAcceptableInput
but unfortunately it is private, so we need to hack somthing
together
*/
auto that = const_cast< TextInput* >( this );
auto d = QQuickTextInputPrivate::get( that );
if ( d->m_validator )
{
QString text = displayText();
int pos = d->m_cursor;
const auto state = d->m_validator->validate( text, pos );
if ( state != QValidator::Acceptable )
return false;
}
if ( d->m_maskData )
{
/*
We only want to do the check for the maskData here
and have to disable d->m_validator temporarily
*/
class Validator final : public QValidator
{
public:
State validate( QString&, int& ) const override
{
return QValidator::Acceptable;
}
};
const auto validator = d->m_validator;
const auto validInput = d->m_validInput;
const auto acceptableInput = d->m_acceptableInput;
d->m_acceptableInput = true;
static Validator noValidator;
that->setValidator( &noValidator ); // implicitly checking maskData
that->setValidator( d->m_validator );
const bool isAcceptable = d->m_acceptableInput;
// restoring old values
d->m_validInput = validInput;
d->m_acceptableInput = acceptableInput;
return isAcceptable;
}
return true;
}
void updateColors();
void updateMetrics();
inline bool handleEvent( QEvent* event )
{
return this->event( event );
}
protected:
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
void geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry ) override
{
Inherited::geometryChange( newGeometry, oldGeometry );
updateClip();
}
#else
void geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry ) override
{
Inherited::geometryChanged( newGeometry, oldGeometry );
updateClip();
}
#endif
void updateClip()
{
setClip( ( contentWidth() > width() ) ||
( contentHeight() > height() ) );
}
QSGNode* updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* data ) override
{
updateColors();
return Inherited::updatePaintNode( oldNode, data );
}
};
TextInput::TextInput( QskTextField* textField )
: QQuickTextInput( textField )
{
classBegin();
setActiveFocusOnTab( false );
setFlag( ItemAcceptsInputMethod, false );
setFocusOnPress( false );
componentComplete();
connect( this, &TextInput::contentSizeChanged,
this, &TextInput::updateClip );
}
void TextInput::setEditing( bool on )
{
auto d = QQuickTextInputPrivate::get( this );
if ( d->cursorVisible == on )
return;
setCursorVisible( on );
d->setBlinkingCursorEnabled( on );
if ( !on )
{
if ( d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive() )
d->updatePasswordEchoEditing( false );
}
polish();
update();
}
void TextInput::updateMetrics()
{
auto textField = static_cast< const QskTextField* >( parentItem() );
setAlignment( textField->alignment() );
setFont( textField->font() );
}
void TextInput::updateColors()
{
auto textField = static_cast< const QskTextField* >( parentItem() );
auto d = QQuickTextInputPrivate::get( this );
bool isDirty = false;
QColor color;
color = textField->color( QskTextField::Text );
if ( d->color != color )
{
d->color = color;
isDirty = true;
}
if ( d->hasSelectedText() )
{
color = textField->color( QskTextField::Panel | QskTextFieldSkinlet::Selected );
if ( d->selectionColor != color )
{
d->selectionColor = color;
isDirty = true;
}
color = textField->color( QskTextField::Text | QskTextFieldSkinlet::Selected );
if ( d->selectedTextColor != color )
{
d->selectedTextColor = color;
isDirty = true;
}
}
if ( isDirty )
{
d->textLayoutDirty = true;
d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
update();
}
}
}
class QskTextField::PrivateData
{
public:
TextInput* textInput;
QString placeholderText;
unsigned int activationModes : 3;
bool hasPanel : 1;
};
QskTextField::QskTextField( QQuickItem* parent )
: Inherited( parent )
, m_data( new PrivateData() )
{
m_data->activationModes = ActivationOnMouse | ActivationOnKey;
m_data->hasPanel = true;
setPolishOnResize( true );
setAcceptHoverEvents( true );
setFocusPolicy( Qt::StrongFocus );
setFlag( QQuickItem::ItemAcceptsInputMethod );
/*
QQuickTextInput is a beast of almost 5k lines of code, we don't
want to reimplement that - at least not now.
So this is more or less a simple wrapper making everything
conforming to qskinny.
*/
m_data->textInput = new TextInput( this );
qskBindSignals( m_data->textInput, this );
setAcceptedMouseButtons( m_data->textInput->acceptedMouseButtons() );
m_data->textInput->setAcceptedMouseButtons( Qt::NoButton );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed );
}
QskTextField::QskTextField( const QString& text, QQuickItem* parent )
: QskTextField( parent )
{
m_data->textInput->setText( text );
setText( text );
}
QskTextField::~QskTextField()
{
}
void QskTextField::setPanel( bool on )
{
if ( on != m_data->hasPanel )
{
m_data->hasPanel = on;
resetImplicitSize();
polish();
update();
}
}
bool QskTextField::hasPanel() const
{
return m_data->hasPanel;
}
bool QskTextField::event( QEvent* event )
{
if ( event->type() == QEvent::ShortcutOverride )
{
return m_data->textInput->handleEvent( event );
}
else if ( event->type() == QEvent::LocaleChange )
{
qskUpdateInputMethod( this, Qt::ImPreferredLanguage );
}
return Inherited::event( event );
}
void QskTextField::keyPressEvent( QKeyEvent* event )
{
if ( isEditing() )
{
switch ( event->key() )
{
case Qt::Key_Enter:
case Qt::Key_Return:
{
if ( hasAcceptableInput() || fixup() )
{
QGuiApplication::inputMethod()->commit();
if ( !( inputMethodHints() & Qt::ImhMultiLine ) )
{
setEditing( false );
// When returning from a virtual keyboard
qskForceActiveFocus( this, Qt::PopupFocusReason );
}
}
break;
}
#if 1
case Qt::Key_Escape:
{
setEditing( false );
qskForceActiveFocus( this, Qt::PopupFocusReason );
break;
}
#endif
default:
{
m_data->textInput->handleEvent( event );
}
}
return;
}
if ( ( m_data->activationModes & ActivationOnKey ) && !event->isAutoRepeat() )
{
if ( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space )
{
setEditing( true );
return;
}
}
Inherited::keyPressEvent( event );
}
void QskTextField::keyReleaseEvent( QKeyEvent* event )
{
Inherited::keyReleaseEvent( event );
}
void QskTextField::mousePressEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() )
setEditing( true );
}
void QskTextField::mouseMoveEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
}
void QskTextField::mouseReleaseEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() )
setEditing( true );
}
void QskTextField::mouseDoubleClickEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
}
void QskTextField::inputMethodEvent( QInputMethodEvent* event )
{
m_data->textInput->handleEvent( event );
}
void QskTextField::focusInEvent( QFocusEvent* event )
{
if ( m_data->activationModes & ActivationOnFocus )
{
switch ( event->reason() )
{
case Qt::ActiveWindowFocusReason:
case Qt::PopupFocusReason:
break;
default:
#if 1
// auto selecting the complete text ???
#endif
setEditing( true );
}
}
Inherited::focusInEvent( event );
}
void QskTextField::focusOutEvent( QFocusEvent* event )
{
switch ( event->reason() )
{
case Qt::ActiveWindowFocusReason:
case Qt::PopupFocusReason:
{
break;
}
default:
{
m_data->textInput->deselect();
setEditing( false );
}
}
Inherited::focusOutEvent( event );
}
void QskTextField::updateLayout()
{
m_data->textInput->updateMetrics();
qskSetItemGeometry( m_data->textInput, subControlRect( Text ) );
}
void QskTextField::updateNode( QSGNode* node )
{
m_data->textInput->updateColors();
Inherited::updateNode( node );
}
QString QskTextField::text() const
{
return m_data->textInput->text();
}
void QskTextField::setText( const QString& text )
{
m_data->textInput->setText( text );
}
void QskTextField::setPlaceholderText( const QString& text )
{
if ( m_data->placeholderText != text )
@ -526,437 +44,4 @@ QString QskTextField::placeholderText() const
return m_data->placeholderText;
}
QskTextField::ActivationModes QskTextField::activationModes() const
{
return static_cast< QskTextField::ActivationModes >( m_data->activationModes );
}
void QskTextField::setActivationModes( ActivationModes modes )
{
if ( static_cast< ActivationModes >( m_data->activationModes ) != modes )
{
m_data->activationModes = modes;
Q_EMIT activationModesChanged();
}
}
static inline void qskUpdateInputMethodFont( const QskTextField* input )
{
const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle;
qskUpdateInputMethod( input, queries );
}
void QskTextField::setFontRole( const QskFontRole& role )
{
if ( setFontRoleHint( Text, role ) )
{
qskUpdateInputMethodFont( this );
Q_EMIT fontRoleChanged();
}
}
void QskTextField::resetFontRole()
{
if ( resetFontRoleHint( Text ) )
{
qskUpdateInputMethodFont( this );
Q_EMIT fontRoleChanged();
}
}
QskFontRole QskTextField::fontRole() const
{
return fontRoleHint( Text );
}
void QskTextField::setAlignment( Qt::Alignment alignment )
{
if ( setAlignmentHint( Text, alignment ) )
{
m_data->textInput->setAlignment( alignment );
Q_EMIT alignmentChanged();
}
}
void QskTextField::resetAlignment()
{
if ( resetAlignmentHint( Text ) )
{
m_data->textInput->setAlignment( alignment() );
Q_EMIT alignmentChanged();
}
}
Qt::Alignment QskTextField::alignment() const
{
return alignmentHint( Text, Qt::AlignLeft | Qt::AlignTop );
}
void QskTextField::setWrapMode( QskTextOptions::WrapMode wrapMode )
{
m_data->textInput->setWrapMode(
static_cast< QQuickTextInput::WrapMode >( wrapMode ) );
}
QskTextOptions::WrapMode QskTextField::wrapMode() const
{
return static_cast< QskTextOptions::WrapMode >(
m_data->textInput->wrapMode() );
}
QFont QskTextField::font() const
{
return effectiveFont( QskTextField::Text );
}
bool QskTextField::isReadOnly() const
{
return m_data->textInput->isReadOnly();
}
void QskTextField::setReadOnly( bool on )
{
auto input = m_data->textInput;
if ( input->isReadOnly() == on )
return;
#if 1
// do we want to be able to restore the previous policy ?
setFocusPolicy( Qt::NoFocus );
#endif
input->setReadOnly( on );
// we are killing user settings here ?
input->setFlag( QQuickItem::ItemAcceptsInputMethod, !on );
qskUpdateInputMethod( this, Qt::ImEnabled );
setSkinStateFlag( ReadOnly, on );
}
void QskTextField::setEditing( bool on )
{
if ( isReadOnly() || on == isEditing() )
return;
setSkinStateFlag( Editing, on );
m_data->textInput->setEditing( on );
if ( on )
{
#if 0
updateInputMethod( Qt::ImCursorRectangle | Qt::ImAnchorRectangle );
QGuiApplication::inputMethod()->inputDirection
#endif
qskInputMethodSetVisible( this, true );
}
else
{
if ( hasAcceptableInput() || fixup() )
Q_EMIT m_data->textInput->editingFinished();
#if 0
inputMethod->reset();
#endif
qskInputMethodSetVisible( this, false );
}
Q_EMIT editingChanged( on );
}
bool QskTextField::isEditing() const
{
return hasSkinState( Editing );
}
void QskTextField::ensureVisible( int position )
{
m_data->textInput->ensureVisible( position );
}
int QskTextField::cursorPosition() const
{
return m_data->textInput->cursorPosition();
}
void QskTextField::setCursorPosition( int pos )
{
m_data->textInput->setCursorPosition( pos );
}
int QskTextField::maxLength() const
{
return m_data->textInput->maxLength();
}
void QskTextField::setMaxLength( int length )
{
m_data->textInput->setMaxLength( length );
}
QValidator* QskTextField::validator() const
{
return m_data->textInput->validator();
}
void QskTextField::setValidator( QValidator* validator )
{
m_data->textInput->setValidator( validator );
}
QString QskTextField::inputMask() const
{
return m_data->textInput->inputMask();
}
void QskTextField::setInputMask( const QString& mask )
{
m_data->textInput->setInputMask( mask );
}
QskTextField::EchoMode QskTextField::echoMode() const
{
const auto mode = m_data->textInput->echoMode();
return static_cast< QskTextField::EchoMode >( mode );
}
void QskTextField::setEchoMode( EchoMode mode )
{
if ( mode != echoMode() )
{
m_data->textInput->setEchoMode(
static_cast< QQuickTextInput::EchoMode >( mode ) );
qskUpdateInputMethod( this, Qt::ImHints );
}
}
QString QskTextField::passwordCharacter() const
{
return m_data->textInput->passwordCharacter();
}
void QskTextField::setPasswordCharacter( const QString& text )
{
m_data->textInput->setPasswordCharacter( text );
}
void QskTextField::resetPasswordCharacter()
{
m_data->textInput->setPasswordCharacter(
QGuiApplication::styleHints()->passwordMaskCharacter() );
}
int QskTextField::passwordMaskDelay() const
{
return m_data->textInput->passwordMaskDelay();
}
void QskTextField::setPasswordMaskDelay( int ms )
{
m_data->textInput->setPasswordMaskDelay( ms );
}
void QskTextField::resetPasswordMaskDelay()
{
m_data->textInput->resetPasswordMaskDelay();
}
QString QskTextField::displayText() const
{
return m_data->textInput->displayText();
}
QString QskTextField::preeditText() const
{
const auto d = QQuickTextInputPrivate::get( m_data->textInput );
return d->m_textLayout.preeditAreaText();
}
bool QskTextField::overwriteMode() const
{
return m_data->textInput->overwriteMode();
}
void QskTextField::setOverwriteMode( bool overwrite )
{
m_data->textInput->setOverwriteMode( overwrite );
}
bool QskTextField::hasAcceptableInput() const
{
return m_data->textInput->hasAcceptableInput();
}
bool QskTextField::fixup()
{
return m_data->textInput->fixup();
}
QVariant QskTextField::inputMethodQuery(
Qt::InputMethodQuery property ) const
{
return inputMethodQuery( property, QVariant() );
}
QVariant QskTextField::inputMethodQuery(
Qt::InputMethodQuery query, const QVariant& argument ) const
{
switch ( query )
{
case Qt::ImEnabled:
{
return QVariant( ( bool ) ( flags() & ItemAcceptsInputMethod ) );
}
case Qt::ImFont:
{
return font();
}
case Qt::ImPreferredLanguage:
{
return locale();
}
case Qt::ImInputItemClipRectangle:
case Qt::ImCursorRectangle:
{
QVariant v = m_data->textInput->inputMethodQuery( query, argument );
#if 1
if ( v.canConvert< QRectF >() )
v.setValue( v.toRectF().translated( m_data->textInput->position() ) );
#endif
return v;
}
default:
{
return m_data->textInput->inputMethodQuery( query, argument );
}
}
}
bool QskTextField::canUndo() const
{
return m_data->textInput->canUndo();
}
bool QskTextField::canRedo() const
{
return m_data->textInput->canRedo();
}
Qt::InputMethodHints QskTextField::inputMethodHints() const
{
return m_data->textInput->inputMethodHints();
}
void QskTextField::setInputMethodHints( Qt::InputMethodHints hints )
{
if ( m_data->textInput->inputMethodHints() != hints )
{
m_data->textInput->setInputMethodHints( hints );
qskUpdateInputMethod( this, Qt::ImHints );
}
}
void QskTextField::setupFrom( const QQuickItem* item )
{
if ( item == nullptr )
return;
// finding attributes from the input hints of item
int maxCharacters = 32767;
QskTextField::EchoMode echoMode = QskTextField::Normal;
Qt::InputMethodQueries queries = Qt::ImQueryAll;
queries &= ~Qt::ImEnabled;
QInputMethodQueryEvent event( queries );
QCoreApplication::sendEvent( const_cast< QQuickItem* >( item ), &event );
if ( event.queries() & Qt::ImHints )
{
const auto hints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
if ( hints & Qt::ImhHiddenText )
echoMode = QskTextField::NoEcho;
}
if ( event.queries() & Qt::ImMaximumTextLength )
{
// needs to be handled before Qt::ImCursorPosition !
const auto max = event.value( Qt::ImMaximumTextLength ).toInt();
maxCharacters = qBound( 0, max, maxCharacters );
}
setMaxLength( maxCharacters );
if ( event.queries() & Qt::ImSurroundingText )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
setText( text );
}
if ( event.queries() & Qt::ImCursorPosition )
{
const auto pos = event.value( Qt::ImCursorPosition ).toInt();
setCursorPosition( pos );
}
if ( event.queries() & Qt::ImCurrentSelection )
{
#if 0
const auto text = event.value( Qt::ImCurrentSelection ).toString();
if ( !text.isEmpty() )
{
}
#endif
}
int passwordMaskDelay = -1;
QString passwordCharacter;
if ( echoMode == QskTextField::NoEcho )
{
/*
Qt::ImhHiddenText does not provide information
to decide between NoEcho/Password, or provides
more details about how to deal with hidden inputs.
So we try to find out more from trying some properties.
*/
QVariant value;
value = item->property( "passwordMaskDelay" );
if ( value.canConvert< int >() )
passwordMaskDelay = value.toInt();
value = item->property( "passwordCharacter" );
if ( value.canConvert< QString >() )
passwordCharacter = value.toString();
value = item->property( "echoMode" );
if ( value.canConvert< int >() )
{
const auto mode = value.toInt();
if ( mode == QskTextField::Password )
echoMode = QskTextField::Password;
}
}
if ( passwordMaskDelay >= 0 )
setPasswordMaskDelay( passwordMaskDelay );
else
resetPasswordMaskDelay();
if ( !passwordCharacter.isEmpty() )
setPasswordCharacter( passwordCharacter );
else
resetPasswordCharacter();
setEchoMode( echoMode );
}
#include "moc_QskTextField.cpp"

View File

@ -6,218 +6,34 @@
#ifndef QSK_TEXT_FIELD_H
#define QSK_TEXT_FIELD_H
#include "QskControl.h"
#include "QskTextOptions.h"
#include "QskTextInput.h"
class QValidator;
class QskFontRole;
class QSK_EXPORT QskTextField : public QskControl
class QSK_EXPORT QskTextField : public QskTextInput
{
Q_OBJECT
Q_PROPERTY( QString text READ text WRITE setText NOTIFY textChanged USER true)
Q_PROPERTY( QString placeholderText READ placeholderText
WRITE setPlaceholderText NOTIFY placeholderTextChanged )
Q_PROPERTY( QskFontRole fontRole READ fontRole
WRITE setFontRole RESET resetFontRole NOTIFY fontRoleChanged )
Q_PROPERTY( QFont font READ font )
Q_PROPERTY( Qt::Alignment alignment READ alignment
WRITE setAlignment RESET resetAlignment NOTIFY alignmentChanged )
Q_PROPERTY( QskTextOptions::WrapMode wrapMode READ wrapMode
WRITE setWrapMode NOTIFY wrapModeChanged )
Q_PROPERTY( ActivationModes activationModes READ activationModes
WRITE setActivationModes NOTIFY activationModesChanged )
Q_PROPERTY( bool editing READ isEditing
WRITE setEditing NOTIFY editingChanged )
Q_PROPERTY( EchoMode echoMode READ echoMode
WRITE setEchoMode NOTIFY echoModeChanged )
Q_PROPERTY( QString passwordCharacter READ passwordCharacter
WRITE setPasswordCharacter RESET resetPasswordCharacter
NOTIFY passwordCharacterChanged )
Q_PROPERTY( int passwordMaskDelay READ passwordMaskDelay
WRITE setPasswordMaskDelay RESET resetPasswordMaskDelay
NOTIFY passwordMaskDelayChanged )
Q_PROPERTY( bool panel READ hasPanel
WRITE setPanel NOTIFY panelChanged )
using Inherited = QskControl;
using Inherited = QskTextInput;
public:
QSK_SUBCONTROLS( Panel, Text, PlaceholderText )
QSK_STATES( ReadOnly, Editing )
enum ActivationMode : quint8
{
NoActivation,
ActivationOnFocus = 1 << 0,
ActivationOnMouse = 1 << 1,
ActivationOnKey = 1 << 2,
ActivationOnInput = ActivationOnMouse | ActivationOnKey,
ActivationOnAll = ActivationOnFocus | ActivationOnMouse | ActivationOnKey
};
Q_ENUM( ActivationMode )
Q_DECLARE_FLAGS( ActivationModes, ActivationMode )
enum EchoMode : quint8
{
Normal,
NoEcho,
Password,
PasswordEchoOnEdit
};
Q_ENUM( EchoMode )
QSK_SUBCONTROLS( Panel, PlaceholderText )
QskTextField( QQuickItem* parent = nullptr );
QskTextField( const QString& text, QQuickItem* parent = nullptr );
~QskTextField() override;
void setupFrom( const QQuickItem* );
QString text() const;
void setPlaceholderText( const QString& );
QString placeholderText() const;
void setPanel( bool );
bool hasPanel() const;
void setFontRole( const QskFontRole& role );
void resetFontRole();
QskFontRole fontRole() const;
void setAlignment( Qt::Alignment );
void resetAlignment();
Qt::Alignment alignment() const;
void setWrapMode( QskTextOptions::WrapMode );
QskTextOptions::WrapMode wrapMode() const;
void setActivationModes( ActivationModes );
ActivationModes activationModes() const;
bool isEditing() const;
QFont font() const;
bool isReadOnly() const;
void setReadOnly( bool );
int cursorPosition() const;
void setCursorPosition( int );
int maxLength() const;
void setMaxLength( int );
QValidator* validator() const;
void setValidator( QValidator* );
QString inputMask() const;
void setInputMask( const QString& );
EchoMode echoMode() const;
void setEchoMode( EchoMode );
QString passwordCharacter() const;
void setPasswordCharacter( const QString& );
void resetPasswordCharacter();
int passwordMaskDelay() const;
void setPasswordMaskDelay( int );
void resetPasswordMaskDelay();
QString displayText() const;
QString preeditText() const;
bool overwriteMode() const;
void setOverwriteMode( bool );
bool hasAcceptableInput() const;
bool fixup();
QVariant inputMethodQuery( Qt::InputMethodQuery ) const override;
QVariant inputMethodQuery( Qt::InputMethodQuery, const QVariant& argument ) const;
bool canUndo() const;
bool canRedo() const;
Qt::InputMethodHints inputMethodHints() const;
void setInputMethodHints( Qt::InputMethodHints );
void ensureVisible( int position );
public Q_SLOTS:
void setText( const QString& );
void setEditing( bool );
Q_SIGNALS:
void editingChanged( bool );
void activationModesChanged();
void readOnlyChanged( bool );
void panelChanged( bool );
void textChanged( const QString& );
void displayTextChanged( const QString& );
void textEdited( const QString& );
void placeholderTextChanged( const QString& );
void fontRoleChanged();
void alignmentChanged();
void wrapModeChanged( QskTextOptions::WrapMode );
void overwriteModeChanged( bool );
void maximumLengthChanged( int );
void echoModeChanged( EchoMode );
void passwordMaskDelayChanged();
void passwordCharacterChanged();
void validatorChanged();
void inputMaskChanged( const QString& );
protected:
bool event( QEvent* ) override;
void inputMethodEvent( QInputMethodEvent* ) override;
void focusInEvent( QFocusEvent* ) override;
void focusOutEvent( QFocusEvent* ) override;
void mousePressEvent( QMouseEvent* ) override;
void mouseMoveEvent( QMouseEvent* ) override;
void mouseReleaseEvent( QMouseEvent* ) override;
void mouseDoubleClickEvent( QMouseEvent* ) override;
void keyPressEvent( QKeyEvent* ) override;
void keyReleaseEvent( QKeyEvent* ) override;
void updateLayout() override;
void updateNode( QSGNode* ) override;
private:
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QskTextField::ActivationModes )
Q_DECLARE_METATYPE( QskTextField::ActivationModes )
#endif

View File

@ -6,27 +6,12 @@
#include "QskTextFieldSkinlet.h"
#include "QskTextField.h"
#include <qfontmetrics.h>
using Q = QskTextField;
QSK_SYSTEM_STATE( QskTextFieldSkinlet, Selected, QskAspect::FirstSystemState << 3 )
static QString qskEffectivePlaceholderText( const QskTextField* textField )
{
if ( textField->text().isEmpty() &&
!( textField->isReadOnly() || textField->isEditing() ) )
{
return textField->placeholderText();
}
return QString();
}
QskTextFieldSkinlet::QskTextFieldSkinlet( QskSkin* skin )
: Inherited( skin )
{
setNodeRoles( { PanelRole, PlaceholderTextRole, } );
setNodeRoles( { PanelRole, TextPanelRole, PlaceholderTextRole } );
}
QskTextFieldSkinlet::~QskTextFieldSkinlet()
@ -37,14 +22,12 @@ QRectF QskTextFieldSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
if ( subControl == Q::Panel )
{
return contentsRect;
}
else if ( subControl == Q::Text )
{
if ( subControl == Q::TextPanel )
return skinnable->subControlContentsRect( contentsRect, Q::Panel );
}
else if ( subControl == Q::PlaceholderText )
if ( subControl == Q::PlaceholderText )
{
const auto textField = static_cast< const QskTextField* >( skinnable );
if( textField->text().isEmpty() )
@ -65,14 +48,12 @@ QSGNode* QskTextFieldSkinlet::updateSubNode(
{
case PanelRole:
{
if ( !textField->hasPanel() )
return nullptr;
return updateBoxNode( skinnable, node, Q::Panel );
}
case PlaceholderTextRole:
{
const auto text = qskEffectivePlaceholderText( textField );
const auto text = effectivePlaceholderText( textField );
if ( text.isEmpty() )
return nullptr;
@ -95,24 +76,28 @@ QSGNode* QskTextFieldSkinlet::updateSubNode(
}
QSizeF QskTextFieldSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
Qt::SizeHint which, const QSizeF& constraint ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
const auto textField = static_cast< const QskTextField* >( skinnable );
const QFontMetricsF fm( textField->effectiveFont( Q::Text ) );
auto hint = fm.size( Qt::TextSingleLine | Qt::TextExpandTabs, textField->text() );
if ( textField->hasPanel() )
{
hint = textField->outerBoxSize( Q::Panel, hint );
hint = hint.expandedTo( textField->strutSizeHint( Q::Panel ) );
}
auto hint = Inherited::sizeHint( skinnable, which, constraint );
hint = skinnable->outerBoxSize( Q::Panel, hint );
hint = hint.expandedTo( skinnable->strutSizeHint( Q::Panel ) );
return hint;
}
QString QskTextFieldSkinlet::effectivePlaceholderText(
const QskTextField* textField ) const
{
if ( textField->text().isEmpty() &&
!( textField->isReadOnly() || textField->isEditing() ) )
{
return textField->placeholderText();
}
return QString();
}
#include "moc_QskTextFieldSkinlet.cpp"

View File

@ -6,20 +6,21 @@
#ifndef QSK_TEXT_FIELD_SKINLET_H
#define QSK_TEXT_FIELD_SKINLET_H
#include "QskSkinlet.h"
#include "QskTextInputSkinlet.h"
class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet
class QskTextField;
class QSK_EXPORT QskTextFieldSkinlet : public QskTextInputSkinlet
{
Q_GADGET
using Inherited = QskSkinlet;
using Inherited = QskTextInputSkinlet;
public:
QSK_STATES( Selected )
enum NodeRole : quint8
{
PanelRole,
PanelRole = QskTextInputSkinlet::RoleCount,
PlaceholderTextRole,
RoleCount
};
@ -36,6 +37,8 @@ class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet
protected:
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
virtual QString effectivePlaceholderText( const QskTextField* ) const;
};
#endif

View File

@ -0,0 +1,917 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskTextInput.h"
#include "QskTextInputSkinlet.h"
#include "QskFontRole.h"
#include "QskQuick.h"
QSK_QT_PRIVATE_BEGIN
#include <private/qquicktextinput_p.h>
#include <private/qquicktextinput_p_p.h>
QSK_QT_PRIVATE_END
QSK_SUBCONTROL( QskTextInput, Text )
QSK_SUBCONTROL( QskTextInput, TextPanel )
QSK_SYSTEM_STATE( QskTextInput, ReadOnly, QskAspect::FirstSystemState << 1 )
QSK_SYSTEM_STATE( QskTextInput, Editing, QskAspect::FirstSystemState << 2 )
QSK_SYSTEM_STATE( QskTextInput, Error, QskAspect::FirstSystemState << 4 )
static inline void qskPropagateReadOnly( QskTextInput* input )
{
Q_EMIT input->readOnlyChanged( input->isReadOnly() );
QEvent event( QEvent::ReadOnlyChange );
QCoreApplication::sendEvent( input, &event );
}
static inline void qskBindSignals(
const QQuickTextInput* wrappedInput, QskTextInput* input )
{
QObject::connect( wrappedInput, &QQuickTextInput::textChanged,
input, [ input ] { Q_EMIT input->textChanged( input->text() ); } );
QObject::connect( wrappedInput, &QQuickTextInput::displayTextChanged,
input, [ input ] { Q_EMIT input->displayTextChanged( input->displayText() ); } );
QObject::connect( wrappedInput, &QQuickTextInput::textEdited,
input, [ input ] { Q_EMIT input->textEdited( input->text() ); } );
QObject::connect( wrappedInput, &QQuickTextInput::validatorChanged,
input, &QskTextInput::validatorChanged );
QObject::connect( wrappedInput, &QQuickTextInput::inputMaskChanged,
input, &QskTextInput::inputMaskChanged );
QObject::connect( wrappedInput, &QQuickTextInput::readOnlyChanged,
input, [ input ] { qskPropagateReadOnly( input ); } );
QObject::connect( wrappedInput, &QQuickTextInput::overwriteModeChanged,
input, &QskTextInput::overwriteModeChanged );
QObject::connect( wrappedInput, &QQuickTextInput::maximumLengthChanged,
input, &QskTextInput::maximumLengthChanged );
QObject::connect( wrappedInput, &QQuickTextInput::wrapModeChanged,
input, [ input ] { Q_EMIT input->wrapModeChanged( input->wrapMode() ); } );
QObject::connect( wrappedInput, &QQuickTextInput::echoModeChanged,
input, [ input ] { Q_EMIT input->echoModeChanged( input->echoMode() ); } );
QObject::connect( wrappedInput, &QQuickTextInput::passwordCharacterChanged,
input, &QskTextInput::passwordCharacterChanged );
QObject::connect( wrappedInput, &QQuickTextInput::passwordMaskDelayChanged,
input, &QskTextInput::passwordMaskDelayChanged );
QObject::connect( wrappedInput, &QQuickItem::implicitWidthChanged,
input, &QskControl::resetImplicitSize );
QObject::connect( wrappedInput, &QQuickItem::implicitHeightChanged,
input, &QskControl::resetImplicitSize );
}
namespace
{
class QuickTextInput final : public QQuickTextInput
{
using Inherited = QQuickTextInput;
public:
QuickTextInput( QskTextInput* );
void setEditing( bool on );
inline void setAlignment( Qt::Alignment alignment )
{
setHAlign( ( HAlignment ) ( int( alignment ) & 0x0f ) );
setVAlign( ( VAlignment ) ( int( alignment ) & 0xf0 ) );
}
bool fixup()
{
return QQuickTextInputPrivate::get( this )->fixup();
}
bool hasAcceptableInput() const
{
/*
we would like to call QQuickTextInputPrivate::hasAcceptableInput
but unfortunately it is private, so we need to hack somthing
together
*/
auto that = const_cast< QuickTextInput* >( this );
auto d = QQuickTextInputPrivate::get( that );
if ( d->m_validator )
{
QString text = displayText();
int pos = d->m_cursor;
const auto state = d->m_validator->validate( text, pos );
if ( state != QValidator::Acceptable )
return false;
}
if ( d->m_maskData )
{
/*
We only want to do the check for the maskData here
and have to disable d->m_validator temporarily
*/
class Validator final : public QValidator
{
public:
State validate( QString&, int& ) const override
{
return QValidator::Acceptable;
}
};
const auto validator = d->m_validator;
const auto validInput = d->m_validInput;
const auto acceptableInput = d->m_acceptableInput;
d->m_acceptableInput = true;
static Validator noValidator;
that->setValidator( &noValidator ); // implicitly checking maskData
that->setValidator( d->m_validator );
const bool isAcceptable = d->m_acceptableInput;
// restoring old values
d->m_validInput = validInput;
d->m_acceptableInput = acceptableInput;
return isAcceptable;
}
return true;
}
void updateColors();
void updateMetrics();
inline bool handleEvent( QEvent* event )
{
return this->event( event );
}
protected:
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
void geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry ) override
{
Inherited::geometryChange( newGeometry, oldGeometry );
updateClip();
}
#else
void geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry ) override
{
Inherited::geometryChanged( newGeometry, oldGeometry );
updateClip();
}
#endif
void updateClip()
{
setClip( ( contentWidth() > width() ) ||
( contentHeight() > height() ) );
}
QSGNode* updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* data ) override
{
updateColors();
return Inherited::updatePaintNode( oldNode, data );
}
};
QuickTextInput::QuickTextInput( QskTextInput* textInput )
: QQuickTextInput( textInput )
{
classBegin();
setActiveFocusOnTab( false );
setFlag( ItemAcceptsInputMethod, false );
setFocusOnPress( false );
componentComplete();
connect( this, &QQuickTextInput::contentSizeChanged,
this, &QuickTextInput::updateClip );
}
void QuickTextInput::setEditing( bool on )
{
auto d = QQuickTextInputPrivate::get( this );
if ( d->cursorVisible == on )
return;
setCursorVisible( on );
d->setBlinkingCursorEnabled( on );
if ( !on )
{
if ( d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive() )
d->updatePasswordEchoEditing( false );
}
polish();
update();
}
void QuickTextInput::updateMetrics()
{
auto textInput = static_cast< const QskTextInput* >( parentItem() );
setAlignment( textInput->alignment() );
setFont( textInput->font() );
}
void QuickTextInput::updateColors()
{
auto textInput = static_cast< const QskTextInput* >( parentItem() );
auto d = QQuickTextInputPrivate::get( this );
bool isDirty = false;
QColor color;
color = textInput->color( QskTextInput::Text );
if ( d->color != color )
{
d->color = color;
isDirty = true;
}
if ( d->hasSelectedText() )
{
QskAspect::States states = QskTextInputSkinlet::Selected;
#if 0
states |= textInput->skinStates();
#endif
color = textInput->color( QskTextInput::TextPanel | states );
if ( d->selectionColor != color )
{
d->selectionColor = color;
isDirty = true;
}
color = textInput->color( QskTextInput::Text | states );
if ( d->selectedTextColor != color )
{
d->selectedTextColor = color;
isDirty = true;
}
}
if ( isDirty )
{
d->textLayoutDirty = true;
d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
update();
}
}
}
class QskTextInput::PrivateData
{
public:
QuickTextInput* wrappedInput;
ActivationModes activationModes;
};
QskTextInput::QskTextInput( QQuickItem* parent )
: Inherited( parent )
, m_data( new PrivateData() )
{
m_data->activationModes = ActivationOnMouse | ActivationOnKey;
setPolishOnResize( true );
setAcceptHoverEvents( true );
setFocusPolicy( Qt::StrongFocus );
setFlag( QQuickItem::ItemAcceptsInputMethod );
/*
QQuickTextInput is a beast of almost 5k lines of code, we don't
want to reimplement that - at least not now.
So QskTextInput is more or less a simple wrapper making everything
conforming to qskinny.
*/
m_data->wrappedInput = new QuickTextInput( this );
qskBindSignals( m_data->wrappedInput, this );
setAcceptedMouseButtons( m_data->wrappedInput->acceptedMouseButtons() );
m_data->wrappedInput->setAcceptedMouseButtons( Qt::NoButton );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed );
}
QskTextInput::~QskTextInput()
{
}
bool QskTextInput::event( QEvent* event )
{
if ( event->type() == QEvent::ShortcutOverride )
{
return m_data->wrappedInput->handleEvent( event );
}
else if ( event->type() == QEvent::LocaleChange )
{
qskUpdateInputMethod( this, Qt::ImPreferredLanguage );
}
return Inherited::event( event );
}
void QskTextInput::keyPressEvent( QKeyEvent* event )
{
if ( isEditing() )
{
switch ( event->key() )
{
case Qt::Key_Enter:
case Qt::Key_Return:
{
if ( hasAcceptableInput() || fixup() )
{
QGuiApplication::inputMethod()->commit();
if ( !( inputMethodHints() & Qt::ImhMultiLine ) )
{
setEditing( false );
// When returning from a virtual keyboard
qskForceActiveFocus( this, Qt::PopupFocusReason );
}
}
break;
}
#if 1
case Qt::Key_Escape:
{
setEditing( false );
qskForceActiveFocus( this, Qt::PopupFocusReason );
break;
}
#endif
default:
{
m_data->wrappedInput->handleEvent( event );
}
}
return;
}
if ( ( m_data->activationModes & ActivationOnKey ) && !event->isAutoRepeat() )
{
if ( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space )
{
setEditing( true );
return;
}
}
Inherited::keyPressEvent( event );
}
void QskTextInput::keyReleaseEvent( QKeyEvent* event )
{
Inherited::keyReleaseEvent( event );
}
void QskTextInput::mousePressEvent( QMouseEvent* event )
{
m_data->wrappedInput->handleEvent( event );
if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() )
setEditing( true );
}
void QskTextInput::mouseMoveEvent( QMouseEvent* event )
{
m_data->wrappedInput->handleEvent( event );
}
void QskTextInput::mouseReleaseEvent( QMouseEvent* event )
{
m_data->wrappedInput->handleEvent( event );
if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() )
setEditing( true );
}
void QskTextInput::mouseDoubleClickEvent( QMouseEvent* event )
{
m_data->wrappedInput->handleEvent( event );
}
void QskTextInput::inputMethodEvent( QInputMethodEvent* event )
{
m_data->wrappedInput->handleEvent( event );
}
void QskTextInput::focusInEvent( QFocusEvent* event )
{
if ( m_data->activationModes & ActivationOnFocus )
{
switch ( event->reason() )
{
case Qt::ActiveWindowFocusReason:
case Qt::PopupFocusReason:
break;
default:
#if 1
// auto selecting the complete text ???
#endif
setEditing( true );
}
}
Inherited::focusInEvent( event );
}
void QskTextInput::focusOutEvent( QFocusEvent* event )
{
switch ( event->reason() )
{
case Qt::ActiveWindowFocusReason:
case Qt::PopupFocusReason:
{
break;
}
default:
{
m_data->wrappedInput->deselect();
setEditing( false );
}
}
Inherited::focusOutEvent( event );
}
void QskTextInput::updateLayout()
{
m_data->wrappedInput->updateMetrics();
qskSetItemGeometry( m_data->wrappedInput, subControlRect( Text ) );
}
void QskTextInput::updateNode( QSGNode* node )
{
m_data->wrappedInput->updateColors();
Inherited::updateNode( node );
}
QString QskTextInput::text() const
{
return m_data->wrappedInput->text();
}
void QskTextInput::setText( const QString& text )
{
m_data->wrappedInput->setText( text );
}
QskTextInput::ActivationModes QskTextInput::activationModes() const
{
return static_cast< QskTextInput::ActivationModes >( m_data->activationModes );
}
void QskTextInput::setActivationModes( ActivationModes modes )
{
if ( static_cast< ActivationModes >( m_data->activationModes ) != modes )
{
m_data->activationModes = modes;
Q_EMIT activationModesChanged();
}
}
static inline void qskUpdateInputMethodFont( const QskTextInput* input )
{
const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle;
qskUpdateInputMethod( input, queries );
}
void QskTextInput::setFontRole( const QskFontRole& role )
{
if ( setFontRoleHint( Text, role ) )
{
qskUpdateInputMethodFont( this );
Q_EMIT fontRoleChanged();
}
}
void QskTextInput::resetFontRole()
{
if ( resetFontRoleHint( Text ) )
{
qskUpdateInputMethodFont( this );
Q_EMIT fontRoleChanged();
}
}
QskFontRole QskTextInput::fontRole() const
{
return fontRoleHint( Text );
}
void QskTextInput::setAlignment( Qt::Alignment alignment )
{
if ( setAlignmentHint( Text, alignment ) )
{
m_data->wrappedInput->setAlignment( alignment );
Q_EMIT alignmentChanged();
}
}
void QskTextInput::resetAlignment()
{
if ( resetAlignmentHint( Text ) )
{
m_data->wrappedInput->setAlignment( alignment() );
Q_EMIT alignmentChanged();
}
}
Qt::Alignment QskTextInput::alignment() const
{
return alignmentHint( Text, Qt::AlignLeft | Qt::AlignTop );
}
void QskTextInput::setWrapMode( QskTextOptions::WrapMode wrapMode )
{
m_data->wrappedInput->setWrapMode(
static_cast< QQuickTextInput::WrapMode >( wrapMode ) );
}
QskTextOptions::WrapMode QskTextInput::wrapMode() const
{
return static_cast< QskTextOptions::WrapMode >(
m_data->wrappedInput->wrapMode() );
}
QFont QskTextInput::font() const
{
return effectiveFont( QskTextInput::Text );
}
bool QskTextInput::isReadOnly() const
{
return m_data->wrappedInput->isReadOnly();
}
void QskTextInput::setReadOnly( bool on )
{
auto input = m_data->wrappedInput;
if ( input->isReadOnly() == on )
return;
#if 1
// do we want to be able to restore the previous policy ?
setFocusPolicy( Qt::NoFocus );
#endif
input->setReadOnly( on );
// we are killing user settings here ?
input->setFlag( QQuickItem::ItemAcceptsInputMethod, !on );
qskUpdateInputMethod( this, Qt::ImEnabled );
setSkinStateFlag( ReadOnly, on );
}
void QskTextInput::setEditing( bool on )
{
if ( isReadOnly() || on == isEditing() )
return;
setSkinStateFlag( Editing, on );
m_data->wrappedInput->setEditing( on );
if ( on )
{
#if 0
updateInputMethod( Qt::ImCursorRectangle | Qt::ImAnchorRectangle );
QGuiApplication::inputMethod()->inputDirection
#endif
qskInputMethodSetVisible( this, true );
}
else
{
if ( hasAcceptableInput() || fixup() )
Q_EMIT m_data->wrappedInput->editingFinished();
#if 0
inputMethod->reset();
#endif
qskInputMethodSetVisible( this, false );
}
Q_EMIT editingChanged( on );
}
bool QskTextInput::isEditing() const
{
return hasSkinState( Editing );
}
void QskTextInput::ensureVisible( int position )
{
m_data->wrappedInput->ensureVisible( position );
}
int QskTextInput::cursorPosition() const
{
return m_data->wrappedInput->cursorPosition();
}
void QskTextInput::setCursorPosition( int pos )
{
m_data->wrappedInput->setCursorPosition( pos );
}
int QskTextInput::maxLength() const
{
return m_data->wrappedInput->maxLength();
}
void QskTextInput::setMaxLength( int length )
{
m_data->wrappedInput->setMaxLength( length );
}
QValidator* QskTextInput::validator() const
{
return m_data->wrappedInput->validator();
}
void QskTextInput::setValidator( QValidator* validator )
{
m_data->wrappedInput->setValidator( validator );
}
QString QskTextInput::inputMask() const
{
return m_data->wrappedInput->inputMask();
}
void QskTextInput::setInputMask( const QString& mask )
{
m_data->wrappedInput->setInputMask( mask );
}
QskTextInput::EchoMode QskTextInput::echoMode() const
{
const auto mode = m_data->wrappedInput->echoMode();
return static_cast< QskTextInput::EchoMode >( mode );
}
void QskTextInput::setEchoMode( EchoMode mode )
{
if ( mode != echoMode() )
{
m_data->wrappedInput->setEchoMode(
static_cast< QQuickTextInput::EchoMode >( mode ) );
qskUpdateInputMethod( this, Qt::ImHints );
}
}
QString QskTextInput::passwordCharacter() const
{
return m_data->wrappedInput->passwordCharacter();
}
void QskTextInput::setPasswordCharacter( const QString& text )
{
m_data->wrappedInput->setPasswordCharacter( text );
}
void QskTextInput::resetPasswordCharacter()
{
m_data->wrappedInput->setPasswordCharacter(
QGuiApplication::styleHints()->passwordMaskCharacter() );
}
int QskTextInput::passwordMaskDelay() const
{
return m_data->wrappedInput->passwordMaskDelay();
}
void QskTextInput::setPasswordMaskDelay( int ms )
{
m_data->wrappedInput->setPasswordMaskDelay( ms );
}
void QskTextInput::resetPasswordMaskDelay()
{
m_data->wrappedInput->resetPasswordMaskDelay();
}
QString QskTextInput::displayText() const
{
return m_data->wrappedInput->displayText();
}
QString QskTextInput::preeditText() const
{
const auto d = QQuickTextInputPrivate::get( m_data->wrappedInput );
return d->m_textLayout.preeditAreaText();
}
bool QskTextInput::overwriteMode() const
{
return m_data->wrappedInput->overwriteMode();
}
void QskTextInput::setOverwriteMode( bool overwrite )
{
m_data->wrappedInput->setOverwriteMode( overwrite );
}
bool QskTextInput::hasAcceptableInput() const
{
return m_data->wrappedInput->hasAcceptableInput();
}
bool QskTextInput::fixup()
{
return m_data->wrappedInput->fixup();
}
QVariant QskTextInput::inputMethodQuery(
Qt::InputMethodQuery property ) const
{
return inputMethodQuery( property, QVariant() );
}
QVariant QskTextInput::inputMethodQuery(
Qt::InputMethodQuery query, const QVariant& argument ) const
{
switch ( query )
{
case Qt::ImEnabled:
{
return QVariant( ( bool ) ( flags() & ItemAcceptsInputMethod ) );
}
case Qt::ImFont:
{
return font();
}
case Qt::ImPreferredLanguage:
{
return locale();
}
case Qt::ImInputItemClipRectangle:
case Qt::ImCursorRectangle:
{
QVariant v = m_data->wrappedInput->inputMethodQuery( query, argument );
#if 1
if ( v.canConvert< QRectF >() )
v.setValue( v.toRectF().translated( m_data->wrappedInput->position() ) );
#endif
return v;
}
default:
{
return m_data->wrappedInput->inputMethodQuery( query, argument );
}
}
}
Qt::InputMethodHints QskTextInput::inputMethodHints() const
{
return m_data->wrappedInput->inputMethodHints();
}
void QskTextInput::setInputMethodHints( Qt::InputMethodHints hints )
{
if ( m_data->wrappedInput->inputMethodHints() != hints )
{
m_data->wrappedInput->setInputMethodHints( hints );
qskUpdateInputMethod( this, Qt::ImHints );
}
}
void QskTextInput::setupFrom( const QQuickItem* item )
{
if ( item == nullptr )
return;
// finding attributes from the input hints of item
int maxCharacters = 32767;
QskTextInput::EchoMode echoMode = QskTextInput::Normal;
Qt::InputMethodQueries queries = Qt::ImQueryAll;
queries &= ~Qt::ImEnabled;
QInputMethodQueryEvent event( queries );
QCoreApplication::sendEvent( const_cast< QQuickItem* >( item ), &event );
if ( event.queries() & Qt::ImHints )
{
const auto hints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
if ( hints & Qt::ImhHiddenText )
echoMode = QskTextInput::NoEcho;
}
if ( event.queries() & Qt::ImMaximumTextLength )
{
// needs to be handled before Qt::ImCursorPosition !
const auto max = event.value( Qt::ImMaximumTextLength ).toInt();
maxCharacters = qBound( 0, max, maxCharacters );
}
setMaxLength( maxCharacters );
if ( event.queries() & Qt::ImSurroundingText )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
setText( text );
}
if ( event.queries() & Qt::ImCursorPosition )
{
const auto pos = event.value( Qt::ImCursorPosition ).toInt();
setCursorPosition( pos );
}
if ( event.queries() & Qt::ImCurrentSelection )
{
#if 0
const auto text = event.value( Qt::ImCurrentSelection ).toString();
if ( !text.isEmpty() )
{
}
#endif
}
int passwordMaskDelay = -1;
QString passwordCharacter;
if ( echoMode == QskTextInput::NoEcho )
{
/*
Qt::ImhHiddenText does not provide information
to decide between NoEcho/Password, or provides
more details about how to deal with hidden inputs.
So we try to find out more from trying some properties.
*/
QVariant value;
value = item->property( "passwordMaskDelay" );
if ( value.canConvert< int >() )
passwordMaskDelay = value.toInt();
value = item->property( "passwordCharacter" );
if ( value.canConvert< QString >() )
passwordCharacter = value.toString();
value = item->property( "echoMode" );
if ( value.canConvert< int >() )
{
const auto mode = value.toInt();
if ( mode == QskTextInput::Password )
echoMode = QskTextInput::Password;
}
}
if ( passwordMaskDelay >= 0 )
setPasswordMaskDelay( passwordMaskDelay );
else
resetPasswordMaskDelay();
if ( !passwordCharacter.isEmpty() )
setPasswordCharacter( passwordCharacter );
else
resetPasswordCharacter();
setEchoMode( echoMode );
}
#include "moc_QskTextInput.cpp"

207
src/controls/QskTextInput.h Normal file
View File

@ -0,0 +1,207 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_TEXT_INPUT_H
#define QSK_TEXT_INPUT_H
#include "QskControl.h"
#include "QskTextOptions.h"
class QValidator;
class QskFontRole;
class QSK_EXPORT QskTextInput : public QskControl
{
Q_OBJECT
Q_PROPERTY( QString text READ text
WRITE setText NOTIFY textChanged USER true )
Q_PROPERTY( QskFontRole fontRole READ fontRole
WRITE setFontRole RESET resetFontRole NOTIFY fontRoleChanged )
Q_PROPERTY( QFont font READ font )
Q_PROPERTY( Qt::Alignment alignment READ alignment
WRITE setAlignment RESET resetAlignment NOTIFY alignmentChanged )
Q_PROPERTY( QskTextOptions::WrapMode wrapMode READ wrapMode
WRITE setWrapMode NOTIFY wrapModeChanged )
Q_PROPERTY( ActivationModes activationModes READ activationModes
WRITE setActivationModes NOTIFY activationModesChanged )
Q_PROPERTY( bool editing READ isEditing
WRITE setEditing NOTIFY editingChanged )
Q_PROPERTY( EchoMode echoMode READ echoMode
WRITE setEchoMode NOTIFY echoModeChanged )
Q_PROPERTY( QString passwordCharacter READ passwordCharacter
WRITE setPasswordCharacter RESET resetPasswordCharacter
NOTIFY passwordCharacterChanged )
Q_PROPERTY( int passwordMaskDelay READ passwordMaskDelay
WRITE setPasswordMaskDelay RESET resetPasswordMaskDelay
NOTIFY passwordMaskDelayChanged )
using Inherited = QskControl;
public:
QSK_SUBCONTROLS( TextPanel, Text )
QSK_STATES( ReadOnly, Editing, Error )
enum ActivationMode : quint8
{
NoActivation,
ActivationOnFocus = 1 << 0,
ActivationOnMouse = 1 << 1,
ActivationOnKey = 1 << 2,
ActivationOnInput = ActivationOnMouse | ActivationOnKey,
ActivationOnAll = ActivationOnFocus | ActivationOnMouse | ActivationOnKey
};
Q_ENUM( ActivationMode )
Q_DECLARE_FLAGS( ActivationModes, ActivationMode )
enum EchoMode : quint8
{
Normal,
NoEcho,
Password,
PasswordEchoOnEdit
};
Q_ENUM( EchoMode )
~QskTextInput() override;
void setupFrom( const QQuickItem* );
QString text() const;
void setFontRole( const QskFontRole& role );
void resetFontRole();
QskFontRole fontRole() const;
void setAlignment( Qt::Alignment );
void resetAlignment();
Qt::Alignment alignment() const;
void setWrapMode( QskTextOptions::WrapMode );
QskTextOptions::WrapMode wrapMode() const;
void setActivationModes( ActivationModes );
ActivationModes activationModes() const;
bool isEditing() const;
QFont font() const;
bool isReadOnly() const;
void setReadOnly( bool );
int cursorPosition() const;
void setCursorPosition( int );
int maxLength() const;
void setMaxLength( int );
QValidator* validator() const;
void setValidator( QValidator* );
QString inputMask() const;
void setInputMask( const QString& );
EchoMode echoMode() const;
void setEchoMode( EchoMode );
QString passwordCharacter() const;
void setPasswordCharacter( const QString& );
void resetPasswordCharacter();
int passwordMaskDelay() const;
void setPasswordMaskDelay( int );
void resetPasswordMaskDelay();
QString displayText() const;
QString preeditText() const;
bool overwriteMode() const;
void setOverwriteMode( bool );
bool hasAcceptableInput() const;
bool fixup();
QVariant inputMethodQuery( Qt::InputMethodQuery ) const override;
QVariant inputMethodQuery( Qt::InputMethodQuery, const QVariant& argument ) const;
Qt::InputMethodHints inputMethodHints() const;
void setInputMethodHints( Qt::InputMethodHints );
void ensureVisible( int position );
public Q_SLOTS:
void setText( const QString& );
void setEditing( bool );
Q_SIGNALS:
void editingChanged( bool );
void activationModesChanged();
void readOnlyChanged( bool );
void textChanged( const QString& );
void displayTextChanged( const QString& );
void textEdited( const QString& );
void fontRoleChanged();
void alignmentChanged();
void wrapModeChanged( QskTextOptions::WrapMode );
void overwriteModeChanged( bool );
void maximumLengthChanged( int );
void echoModeChanged( EchoMode );
void passwordMaskDelayChanged();
void passwordCharacterChanged();
void validatorChanged();
void inputMaskChanged( const QString& );
protected:
QskTextInput( QQuickItem* parent = nullptr );
bool event( QEvent* ) override;
void inputMethodEvent( QInputMethodEvent* ) override;
void focusInEvent( QFocusEvent* ) override;
void focusOutEvent( QFocusEvent* ) override;
void mousePressEvent( QMouseEvent* ) override;
void mouseMoveEvent( QMouseEvent* ) override;
void mouseReleaseEvent( QMouseEvent* ) override;
void mouseDoubleClickEvent( QMouseEvent* ) override;
void keyPressEvent( QKeyEvent* ) override;
void keyReleaseEvent( QKeyEvent* ) override;
void updateLayout() override;
void updateNode( QSGNode* ) override;
private:
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QskTextInput::ActivationModes )
Q_DECLARE_METATYPE( QskTextInput::ActivationModes )
#endif

View File

@ -0,0 +1,74 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskTextInputSkinlet.h"
#include "QskTextInput.h"
#include <qfontmetrics.h>
using Q = QskTextInput;
QSK_SYSTEM_STATE( QskTextInputSkinlet, Selected, QskAspect::FirstSystemState << 3 )
QskTextInputSkinlet::QskTextInputSkinlet( QskSkin* skin )
: Inherited( skin )
{
setNodeRoles( { TextPanelRole } );
}
QskTextInputSkinlet::~QskTextInputSkinlet()
{
}
QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
if ( subControl == Q::TextPanel )
return contentsRect;
if ( subControl == Q::Text )
{
auto rect = skinnable->subControlContentsRect( contentsRect, Q::TextPanel );
const auto h = skinnable->effectiveFontHeight( Q::Text );
rect.setTop( rect.center().y() - 0.5 * h );
rect.setHeight( h );
rect = rect.marginsAdded( skinnable->marginHint( Q::Text ) );
return rect;
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
QSGNode* QskTextInputSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
switch ( nodeRole )
{
case TextPanelRole:
return updateBoxNode( skinnable, node, Q::TextPanel );
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QSizeF QskTextInputSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
const auto text = static_cast< const QskTextInput* >( skinnable )->text();
const QFontMetricsF fm( skinnable->effectiveFont( Q::Text ) );
auto hint = fm.size( Qt::TextSingleLine | Qt::TextExpandTabs, text );
hint = skinnable->outerBoxSize( Q::TextPanel, hint );
hint = hint.expandedTo( skinnable->strutSizeHint( Q::TextPanel ) );
return hint;
}

View File

@ -0,0 +1,39 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_TEXT_INPUT_SKINLET_H
#define QSK_TEXT_INPUT_SKINLET_H
#include "QskSkinlet.h"
class QSK_EXPORT QskTextInputSkinlet : public QskSkinlet
{
using Inherited = QskSkinlet;
public:
QSK_STATES( Selected )
enum NodeRole : quint8
{
TextPanelRole,
RoleCount
};
~QskTextInputSkinlet() override;
QRectF subControlRect( const QskSkinnable*,
const QRectF& rect, QskAspect::Subcontrol ) const override;
QSizeF sizeHint( const QskSkinnable*,
Qt::SizeHint, const QSizeF& ) const override;
protected:
QskTextInputSkinlet( QskSkin* = nullptr );
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
};
#endif

View File

@ -32,7 +32,7 @@ namespace
QskAspect::Subcontrol substitutedSubcontrol(
QskAspect::Subcontrol subControl ) const override
{
if ( subControl == QskTextField::Panel )
if ( subControl == QskTextField::TextPanel )
return m_panelBox->effectiveSubcontrol( QskInputPanelBox::ProxyPanel );
if ( subControl == QskTextField::Text )
@ -184,7 +184,7 @@ QskAspect::Subcontrol QskInputPanelBox::substitutedSubcontrol(
#if 1
// TODO ...
if ( subControl == QskInputPanelBox::ProxyPanel )
return QskTextField::Panel;
return QskTextField::TextPanel;
if ( subControl == QskInputPanelBox::ProxyText )
return QskTextField::Text;