2017-10-23 07:46:46 +02:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "QskRichTextRenderer.h"
|
|
|
|
#include "QskTextColors.h"
|
|
|
|
#include "QskTextOptions.h"
|
|
|
|
|
2018-07-19 14:10:48 +02:00
|
|
|
#include <qglobalstatic.h>
|
|
|
|
#include <qthread.h>
|
|
|
|
#include <qmutex.h>
|
|
|
|
|
|
|
|
class QQuickWindow;
|
2017-10-23 07:46:46 +02:00
|
|
|
|
|
|
|
QSK_QT_PRIVATE_BEGIN
|
|
|
|
#include <private/qquicktext_p.h>
|
|
|
|
#include <private/qquicktext_p_p.h>
|
|
|
|
QSK_QT_PRIVATE_END
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
class TextItem final : public QQuickText
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
TextItem()
|
|
|
|
{
|
|
|
|
// fonts are supposed to be defined in the application skin and we
|
|
|
|
// probably don't want to have them scaled
|
|
|
|
setFontSizeMode( QQuickText::FixedSize );
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
setAntialiasing( true );
|
|
|
|
setRenderType( QQuickText::QtRendering );
|
|
|
|
setPadding( 0 );
|
|
|
|
|
|
|
|
setMinimumPixelSize();
|
|
|
|
setMinimumPointSize();
|
|
|
|
|
|
|
|
// also something, that should be defined in an application skin
|
|
|
|
setLineHeightMode( ... );
|
|
|
|
setLineHeight();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-01-05 09:36:55 +01:00
|
|
|
inline void setGeometry( const QRectF& rect )
|
|
|
|
{
|
|
|
|
auto d = QQuickTextPrivate::get( this );
|
|
|
|
|
|
|
|
d->heightValid = true;
|
|
|
|
d->widthValid = true;
|
|
|
|
|
|
|
|
if ( ( d->x != rect.x() ) || ( d->y != rect.y() ) )
|
|
|
|
{
|
|
|
|
d->x = rect.x();
|
|
|
|
d->y = rect.y();
|
|
|
|
d->dirty( QQuickItemPrivate::Position );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ( d->width != rect.width() ) || ( d->height != rect.height() ) )
|
|
|
|
{
|
|
|
|
d->height = rect.height();
|
|
|
|
d->width = rect.width();
|
|
|
|
d->dirty( QQuickItemPrivate::Size );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-23 07:46:46 +02:00
|
|
|
inline void setAlignment( Qt::Alignment alignment )
|
|
|
|
{
|
|
|
|
setHAlign( ( QQuickText::HAlignment ) ( int( alignment ) & 0x0f ) );
|
|
|
|
setVAlign( ( QQuickText::VAlignment ) ( int( alignment ) & 0xf0 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void setOptions( const QskTextOptions& options )
|
|
|
|
{
|
|
|
|
// what about Qt::TextShowMnemonic ???
|
|
|
|
setTextFormat( ( QQuickText::TextFormat ) options.format() );
|
|
|
|
setElideMode( ( QQuickText::TextElideMode ) options.elideMode() );
|
|
|
|
setMaximumLineCount( options.maximumLineCount() );
|
|
|
|
setWrapMode( static_cast< QQuickText::WrapMode >( options.wrapMode() ) );
|
|
|
|
}
|
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
inline void begin()
|
2018-01-05 09:36:55 +01:00
|
|
|
{
|
|
|
|
classBegin();
|
|
|
|
QQuickTextPrivate::get( this )->updateOnComponentComplete = true;
|
|
|
|
}
|
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
inline void end()
|
2018-01-05 09:36:55 +01:00
|
|
|
{
|
|
|
|
componentComplete();
|
|
|
|
}
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
inline void reset()
|
|
|
|
{
|
|
|
|
setText( QString() );
|
|
|
|
}
|
|
|
|
|
|
|
|
inline QRectF layedOutTextRect() const
|
2017-10-23 07:46:46 +02:00
|
|
|
{
|
|
|
|
auto that = const_cast< TextItem* >( this );
|
|
|
|
return QQuickTextPrivate::get( that )->layedOutTextRect;
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateTextNode( QQuickWindow* window, QSGNode* parentNode )
|
|
|
|
{
|
|
|
|
QQuickItemPrivate::get( this )->refWindow( window );
|
|
|
|
|
|
|
|
while ( parentNode->firstChild() )
|
2018-01-05 09:36:55 +01:00
|
|
|
delete parentNode->firstChild();
|
2017-10-23 07:46:46 +02:00
|
|
|
|
|
|
|
auto node = QQuickText::updatePaintNode( nullptr, nullptr );
|
|
|
|
node->reparentChildNodesTo( parentNode );
|
|
|
|
delete node;
|
|
|
|
|
|
|
|
QQuickItemPrivate::get( this )->derefWindow();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
2018-07-31 17:32:25 +02:00
|
|
|
QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override
|
2017-10-23 07:46:46 +02:00
|
|
|
{
|
|
|
|
Q_ASSERT( false );
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
};
|
2018-04-17 14:46:50 +02:00
|
|
|
|
|
|
|
class TextItemMap
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
~TextItemMap()
|
|
|
|
{
|
|
|
|
qDeleteAll( m_hash );
|
|
|
|
}
|
|
|
|
|
|
|
|
inline TextItem* item()
|
|
|
|
{
|
|
|
|
const auto thread = QThread::currentThread();
|
|
|
|
|
|
|
|
QMutexLocker locker( &m_mutex );
|
|
|
|
|
|
|
|
auto it = m_hash.constFind( thread );
|
|
|
|
if ( it == m_hash.constEnd() )
|
|
|
|
{
|
|
|
|
auto textItem = new TextItem();
|
|
|
|
QObject::connect( thread, &QThread::finished,
|
|
|
|
textItem, [this, thread] { removeItem( thread ); } );
|
|
|
|
|
|
|
|
m_hash.insert( thread, textItem );
|
|
|
|
return textItem;
|
|
|
|
}
|
|
|
|
|
|
|
|
return it.value();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void removeItem( const QThread* thread )
|
|
|
|
{
|
|
|
|
auto textItem = m_hash.take( thread );
|
|
|
|
if ( textItem )
|
|
|
|
textItem->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
QMutex m_mutex;
|
|
|
|
QHash< const QThread*, TextItem* > m_hash;
|
|
|
|
};
|
2017-10-23 07:46:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
size requests and rendering might be from different threads and we
|
|
|
|
better use different items as we might end up in events internally
|
|
|
|
being sent, that leads to crashes because of it
|
|
|
|
*/
|
2018-04-17 14:46:50 +02:00
|
|
|
Q_GLOBAL_STATIC( TextItemMap, qskTextItemMap )
|
2017-10-23 07:46:46 +02:00
|
|
|
|
|
|
|
QSizeF QskRichTextRenderer::textSize( const QString& text,
|
|
|
|
const QFont& font, const QskTextOptions& options )
|
|
|
|
{
|
2018-04-17 14:46:50 +02:00
|
|
|
auto& textItem = *qskTextItemMap->item();
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
textItem.begin();
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
textItem.setFont( font );
|
|
|
|
textItem.setOptions( options );
|
|
|
|
|
|
|
|
textItem.setWidth( -1 );
|
|
|
|
textItem.setText( text );
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
textItem.end();
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
const QSizeF sz( textItem.implicitWidth(), textItem.implicitHeight() );
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
textItem.reset();
|
|
|
|
|
|
|
|
return sz;
|
2017-10-23 07:46:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QRectF QskRichTextRenderer::textRect( const QString& text,
|
|
|
|
const QFont& font, const QskTextOptions& options, const QSizeF& size )
|
|
|
|
{
|
2018-04-17 14:46:50 +02:00
|
|
|
auto& textItem = *qskTextItemMap->item();
|
2017-10-23 07:46:46 +02:00
|
|
|
|
|
|
|
textItem.begin();
|
|
|
|
|
|
|
|
textItem.setFont( font );
|
|
|
|
textItem.setOptions( options );
|
|
|
|
textItem.setAlignment( Qt::Alignment() );
|
|
|
|
|
|
|
|
textItem.setWidth( size.width() );
|
|
|
|
textItem.setHeight( size.height() );
|
|
|
|
|
|
|
|
textItem.setText( text );
|
|
|
|
|
|
|
|
textItem.end();
|
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
const auto rect = textItem.layedOutTextRect();
|
|
|
|
|
|
|
|
textItem.reset();
|
|
|
|
|
|
|
|
return rect;
|
2017-10-23 07:46:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskRichTextRenderer::updateNode( const QString& text,
|
|
|
|
const QFont& font, const QskTextOptions& options,
|
|
|
|
Qsk::TextStyle style, const QskTextColors& colors,
|
|
|
|
Qt::Alignment alignment, const QRectF& rect,
|
|
|
|
const QQuickItem* item, QSGTransformNode* node )
|
|
|
|
{
|
|
|
|
// are we killing internal caches of QQuickText, when always using
|
2018-01-05 09:36:55 +01:00
|
|
|
// the same item for the creation the text nodes. TODO ...
|
2017-10-23 07:46:46 +02:00
|
|
|
|
2018-04-17 14:46:50 +02:00
|
|
|
auto& textItem = *qskTextItemMap->item();
|
2017-10-23 07:46:46 +02:00
|
|
|
|
|
|
|
textItem.begin();
|
|
|
|
|
2018-01-05 09:36:55 +01:00
|
|
|
textItem.setGeometry( rect );
|
|
|
|
|
2018-01-04 13:59:51 +01:00
|
|
|
textItem.setBottomPadding( 0 );
|
|
|
|
textItem.setTopPadding( 0 );
|
2017-10-23 07:46:46 +02:00
|
|
|
textItem.setFont( font );
|
|
|
|
textItem.setOptions( options );
|
|
|
|
textItem.setAlignment( alignment );
|
|
|
|
|
|
|
|
textItem.setColor( colors.textColor );
|
|
|
|
textItem.setStyle( static_cast< QQuickText::TextStyle >( style ) );
|
|
|
|
textItem.setStyleColor( colors.styleColor );
|
|
|
|
textItem.setLinkColor( colors.linkColor );
|
|
|
|
|
|
|
|
textItem.setText( text );
|
|
|
|
|
|
|
|
textItem.end();
|
|
|
|
|
2018-01-04 13:59:51 +01:00
|
|
|
if ( alignment & Qt::AlignVCenter )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
We need to have a stable algo for rounding the text base line,
|
|
|
|
so that texts don't start wobbling, when processing transitions
|
|
|
|
between margins/paddings. We manipulate the layout code
|
2018-01-05 09:36:55 +01:00
|
|
|
by adding some padding, so that the position of base line
|
2018-01-04 13:59:51 +01:00
|
|
|
gets always floored.
|
|
|
|
*/
|
|
|
|
auto d = QQuickTextPrivate::get( &textItem );
|
|
|
|
|
|
|
|
const qreal h = d->layedOutTextRect.height() + d->lineHeightOffset();
|
|
|
|
|
|
|
|
if ( static_cast< int >( rect.height() - h ) % 2 )
|
|
|
|
{
|
2018-01-05 09:36:55 +01:00
|
|
|
if ( static_cast< int >( h ) % 2 )
|
2018-01-04 13:59:51 +01:00
|
|
|
d->extra.value().bottomPadding = 1;
|
|
|
|
else
|
|
|
|
d->extra.value().topPadding = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-23 07:46:46 +02:00
|
|
|
textItem.updateTextNode( item->window(), node );
|
2018-04-17 14:46:50 +02:00
|
|
|
textItem.reset();
|
2017-10-23 07:46:46 +02:00
|
|
|
}
|