2014-05-30 12:53:19 +04:00
/*
This file is part of Telegram Desktop ,
2014-12-01 13:47:38 +03:00
the official desktop version of Telegram messaging app , see https : //telegram.org
2014-05-30 12:53:19 +04:00
Telegram Desktop is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
It is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
2015-10-03 16:16:42 +03:00
In addition , as a special exception , the copyright holders give permission
to link the code of portions of this program with the OpenSSL library .
2014-05-30 12:53:19 +04:00
Full license : https : //github.com/telegramdesktop/tdesktop/blob/master/LICENSE
2016-02-08 13:56:18 +03:00
Copyright ( c ) 2014 - 2016 John Preston , https : //desktop.telegram.org
2014-05-30 12:53:19 +04:00
*/
# pragma once
2015-10-23 18:06:56 +02:00
enum EntityInTextType {
EntityInTextUrl ,
EntityInTextCustomUrl ,
EntityInTextEmail ,
EntityInTextHashtag ,
EntityInTextMention ,
EntityInTextBotCommand ,
EntityInTextBold ,
EntityInTextItalic ,
EntityInTextCode , // inline
EntityInTextPre , // block
} ;
struct EntityInText {
EntityInText ( EntityInTextType type , int32 offset , int32 length , const QString & text = QString ( ) ) : type ( type ) , offset ( offset ) , length ( length ) , text ( text ) {
}
EntityInTextType type ;
int32 offset , length ;
QString text ;
} ;
typedef QList < EntityInText > EntitiesInText ;
2014-10-07 21:57:57 +04:00
// text preprocess
QString textClean ( const QString & text ) ;
QString textRichPrepare ( const QString & text ) ;
QString textOneLine ( const QString & text , bool trim = true , bool rich = false ) ;
QString textAccentFold ( const QString & text ) ;
QString textSearchKey ( const QString & text ) ;
2015-10-23 18:06:56 +02:00
bool textSplit ( QString & sendingText , EntitiesInText & sendingEntities , QString & leftText , EntitiesInText & leftEntities , int32 limit ) ;
2014-10-07 21:57:57 +04:00
2015-04-08 02:03:32 +03:00
enum {
2015-10-23 18:06:56 +02:00
TextParseMultiline = 0x001 ,
TextParseLinks = 0x002 ,
TextParseRichText = 0x004 ,
TextParseMentions = 0x008 ,
TextParseHashtags = 0x010 ,
TextParseBotCommands = 0x020 ,
TextParseMono = 0x040 ,
TextTwitterMentions = 0x100 ,
TextTwitterHashtags = 0x200 ,
TextInstagramMentions = 0x400 ,
TextInstagramHashtags = 0x800 ,
2015-04-08 02:03:32 +03:00
} ;
2015-10-23 18:06:56 +02:00
inline EntitiesInText entitiesFromMTP ( const QVector < MTPMessageEntity > & entities ) {
EntitiesInText result ;
2015-08-24 13:53:04 +03:00
if ( ! entities . isEmpty ( ) ) {
result . reserve ( entities . size ( ) ) ;
for ( int32 i = 0 , l = entities . size ( ) ; i ! = l ; + + i ) {
const MTPMessageEntity & e ( entities . at ( i ) ) ;
switch ( e . type ( ) ) {
2015-10-23 18:06:56 +02:00
case mtpc_messageEntityUrl : { const MTPDmessageEntityUrl & d ( e . c_messageEntityUrl ( ) ) ; result . push_back ( EntityInText ( EntityInTextUrl , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityTextUrl : { const MTPDmessageEntityTextUrl & d ( e . c_messageEntityTextUrl ( ) ) ; result . push_back ( EntityInText ( EntityInTextCustomUrl , d . voffset . v , d . vlength . v , textClean ( qs ( d . vurl ) ) ) ) ; } break ;
case mtpc_messageEntityEmail : { const MTPDmessageEntityEmail & d ( e . c_messageEntityEmail ( ) ) ; result . push_back ( EntityInText ( EntityInTextEmail , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityHashtag : { const MTPDmessageEntityHashtag & d ( e . c_messageEntityHashtag ( ) ) ; result . push_back ( EntityInText ( EntityInTextHashtag , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityMention : { const MTPDmessageEntityMention & d ( e . c_messageEntityMention ( ) ) ; result . push_back ( EntityInText ( EntityInTextMention , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityBotCommand : { const MTPDmessageEntityBotCommand & d ( e . c_messageEntityBotCommand ( ) ) ; result . push_back ( EntityInText ( EntityInTextBotCommand , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityBold : { const MTPDmessageEntityBold & d ( e . c_messageEntityBold ( ) ) ; result . push_back ( EntityInText ( EntityInTextBold , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityItalic : { const MTPDmessageEntityItalic & d ( e . c_messageEntityItalic ( ) ) ; result . push_back ( EntityInText ( EntityInTextItalic , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityCode : { const MTPDmessageEntityCode & d ( e . c_messageEntityCode ( ) ) ; result . push_back ( EntityInText ( EntityInTextCode , d . voffset . v , d . vlength . v ) ) ; } break ;
case mtpc_messageEntityPre : { const MTPDmessageEntityPre & d ( e . c_messageEntityPre ( ) ) ; result . push_back ( EntityInText ( EntityInTextPre , d . voffset . v , d . vlength . v , textClean ( qs ( d . vlanguage ) ) ) ) ; } break ;
2015-08-24 13:53:04 +03:00
}
}
}
return result ;
}
2015-10-23 18:06:56 +02:00
inline MTPVector < MTPMessageEntity > linksToMTP ( const EntitiesInText & links , bool sending = false ) {
2015-08-24 13:53:04 +03:00
MTPVector < MTPMessageEntity > result ( MTP_vector < MTPMessageEntity > ( 0 ) ) ;
QVector < MTPMessageEntity > & v ( result . _vector ( ) . v ) ;
for ( int32 i = 0 , s = links . size ( ) ; i ! = s ; + + i ) {
2015-10-23 18:06:56 +02:00
const EntityInText & l ( links . at ( i ) ) ;
if ( l . length < = 0 | | ( sending & & l . type ! = EntityInTextCode & & l . type ! = EntityInTextPre ) ) continue ;
2015-08-24 13:53:04 +03:00
switch ( l . type ) {
2015-10-23 18:06:56 +02:00
case EntityInTextUrl : v . push_back ( MTP_messageEntityUrl ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextCustomUrl : v . push_back ( MTP_messageEntityTextUrl ( MTP_int ( l . offset ) , MTP_int ( l . length ) , MTP_string ( l . text ) ) ) ; break ;
case EntityInTextEmail : v . push_back ( MTP_messageEntityEmail ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextHashtag : v . push_back ( MTP_messageEntityHashtag ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextMention : v . push_back ( MTP_messageEntityMention ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextBotCommand : v . push_back ( MTP_messageEntityBotCommand ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextBold : v . push_back ( MTP_messageEntityBold ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextItalic : v . push_back ( MTP_messageEntityItalic ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextCode : v . push_back ( MTP_messageEntityCode ( MTP_int ( l . offset ) , MTP_int ( l . length ) ) ) ; break ;
case EntityInTextPre : v . push_back ( MTP_messageEntityPre ( MTP_int ( l . offset ) , MTP_int ( l . length ) , MTP_string ( l . text ) ) ) ; break ;
2015-08-24 13:53:04 +03:00
}
}
return result ;
}
2015-10-23 18:06:56 +02:00
EntitiesInText textParseEntities ( QString & text , int32 flags , bool rich = false ) ; // changes text if (flags & TextParseMono)
2016-02-23 17:31:06 +03:00
QString textApplyEntities ( const QString & text , const EntitiesInText & entities ) ;
2014-10-07 21:57:57 +04:00
2014-05-30 12:53:19 +04:00
# include "gui/emoji_config.h"
2015-05-08 15:45:14 +03:00
void emojiDraw ( QPainter & p , EmojiPtr e , int x , int y ) ;
2014-05-30 12:53:19 +04:00
# include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h"
enum TextBlockType {
2015-10-23 18:06:56 +02:00
TextBlockTNewline = 0x01 ,
TextBlockTText = 0x02 ,
TextBlockTEmoji = 0x03 ,
TextBlockTSkip = 0x04 ,
2014-05-30 12:53:19 +04:00
} ;
enum TextBlockFlags {
2015-10-23 18:06:56 +02:00
TextBlockFBold = 0x01 ,
TextBlockFItalic = 0x02 ,
TextBlockFUnderline = 0x04 ,
2015-10-27 23:43:42 -04:00
TextBlockFTilde = 0x08 , // tilde fix in OpenSans
2015-10-23 18:06:56 +02:00
TextBlockFSemibold = 0x10 ,
TextBlockFCode = 0x20 ,
TextBlockFPre = 0x40 ,
2014-05-30 12:53:19 +04:00
} ;
class ITextBlock {
public :
ITextBlock ( const style : : font & font , const QString & str , uint16 from , uint16 length , uchar flags , const style : : color & color , uint16 lnkIndex ) : _from ( from ) , _flags ( ( flags & 0xFF ) | ( ( lnkIndex & 0xFFFF ) < < 12 ) ) /*, _color(color)*/ , _lpadding ( 0 ) {
if ( length ) {
if ( str . at ( _from + length - 1 ) . unicode ( ) = = QChar : : Space ) {
_rpadding = font - > spacew ;
}
if ( length > 1 & & str . at ( 0 ) . unicode ( ) = = QChar : : Space ) {
_lpadding = font - > spacew ;
}
}
}
uint16 from ( ) const {
return _from ;
}
int32 width ( ) const {
return _width . toInt ( ) ;
}
int32 lpadding ( ) const {
return _lpadding . toInt ( ) ;
}
int32 rpadding ( ) const {
return _rpadding . toInt ( ) ;
}
QFixed f_width ( ) const {
return _width ;
}
QFixed f_lpadding ( ) const {
return _lpadding ;
}
QFixed f_rpadding ( ) const {
return _rpadding ;
}
uint16 lnkIndex ( ) const {
return ( _flags > > 12 ) & 0xFFFF ;
}
void setLnkIndex ( uint16 lnkIndex ) {
_flags = ( _flags & ~ ( 0xFFFF < < 12 ) ) | ( lnkIndex < < 12 ) ;
}
TextBlockType type ( ) const {
return TextBlockType ( ( _flags > > 8 ) & 0x0F ) ;
}
int32 flags ( ) const {
return ( _flags & 0xFF ) ;
}
const style : : color & color ( ) const {
static style : : color tmp ;
return tmp ; //_color;
}
2015-04-04 23:01:34 +03:00
virtual ITextBlock * clone ( ) const = 0 ;
2014-05-30 12:53:19 +04:00
virtual ~ ITextBlock ( ) {
}
protected :
uint16 _from ;
uint32 _flags ; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
QFixed _width , _lpadding , _rpadding ;
} ;
class NewlineBlock : public ITextBlock {
public :
Qt : : LayoutDirection nextDirection ( ) const {
return _nextDir ;
}
2015-04-04 23:01:34 +03:00
ITextBlock * clone ( ) const {
return new NewlineBlock ( * this ) ;
}
2014-05-30 12:53:19 +04:00
private :
NewlineBlock ( const style : : font & font , const QString & str , uint16 from , uint16 length ) : ITextBlock ( font , str , from , length , 0 , st : : transparent , 0 ) , _nextDir ( Qt : : LayoutDirectionAuto ) {
2015-10-23 18:06:56 +02:00
_flags | = ( ( TextBlockTNewline & 0x0F ) < < 8 ) ;
2014-05-30 12:53:19 +04:00
}
Qt : : LayoutDirection _nextDir ;
friend class Text ;
friend class TextParser ;
friend class TextPainter ;
} ;
struct TextWord {
TextWord ( ) {
}
2014-06-16 13:31:10 +04:00
TextWord ( uint16 from , QFixed width , QFixed rbearing , QFixed rpadding = 0 ) : from ( from ) ,
_rbearing ( rbearing . value ( ) > 0x7FFF ? 0x7FFF : ( rbearing . value ( ) < - 0x7FFF ? - 0x7FFF : rbearing . value ( ) ) ) , width ( width ) , rpadding ( rpadding ) {
2014-05-30 12:53:19 +04:00
}
QFixed f_rbearing ( ) const {
return QFixed : : fromFixed ( _rbearing ) ;
}
uint16 from ;
int16 _rbearing ;
QFixed width , rpadding ;
} ;
class TextBlock : public ITextBlock {
public :
QFixed f_rbearing ( ) const {
return _words . isEmpty ( ) ? 0 : _words . back ( ) . f_rbearing ( ) ;
}
2015-04-04 23:01:34 +03:00
ITextBlock * clone ( ) const {
return new TextBlock ( * this ) ;
}
2014-05-30 12:53:19 +04:00
private :
TextBlock ( const style : : font & font , const QString & str , QFixed minResizeWidth , uint16 from , uint16 length , uchar flags , const style : : color & color , uint16 lnkIndex ) ;
typedef QVector < TextWord > TextWords ;
TextWords _words ;
friend class Text ;
friend class TextParser ;
friend class BlockParser ;
friend class TextPainter ;
} ;
class EmojiBlock : public ITextBlock {
public :
2015-04-04 23:01:34 +03:00
ITextBlock * clone ( ) const {
return new EmojiBlock ( * this ) ;
}
2014-05-30 12:53:19 +04:00
private :
EmojiBlock ( const style : : font & font , const QString & str , uint16 from , uint16 length , uchar flags , const style : : color & color , uint16 lnkIndex , const EmojiData * emoji ) ;
const EmojiData * emoji ;
friend class Text ;
friend class TextParser ;
friend class TextPainter ;
} ;
class SkipBlock : public ITextBlock {
public :
int32 height ( ) const {
return _height ;
}
2015-04-04 23:01:34 +03:00
ITextBlock * clone ( ) const {
return new SkipBlock ( * this ) ;
}
2014-05-30 12:53:19 +04:00
private :
SkipBlock ( const style : : font & font , const QString & str , uint16 from , int32 w , int32 h , uint16 lnkIndex ) ;
int32 _height ;
friend class Text ;
friend class TextParser ;
friend class TextPainter ;
} ;
class ITextLink {
public :
virtual void onClick ( Qt : : MouseButton ) const = 0 ;
virtual const QString & text ( ) const {
static const QString _tmp ;
return _tmp ;
}
virtual const QString & readable ( ) const {
static const QString _tmp ;
return _tmp ;
}
virtual bool fullDisplayed ( ) const {
return true ;
}
2015-12-23 14:13:08 +03:00
virtual void setFullDisplayed ( bool full ) {
}
2014-05-30 12:53:19 +04:00
virtual QString encoded ( ) const {
return QString ( ) ;
}
2015-06-27 16:02:00 +03:00
virtual const QLatin1String & type ( ) const = 0 ;
2014-05-30 12:53:19 +04:00
virtual ~ ITextLink ( ) {
}
} ;
2015-06-27 16:02:00 +03:00
# define TEXT_LINK_CLASS(ClassName) public: \
const QLatin1String & type ( ) const { \
static const QLatin1String _type ( qstr ( # ClassName ) ) ; \
return _type ; \
}
2014-05-30 12:53:19 +04:00
typedef QSharedPointer < ITextLink > TextLinkPtr ;
class TextLink : public ITextLink {
2015-06-27 16:02:00 +03:00
TEXT_LINK_CLASS ( TextLink )
2014-05-30 12:53:19 +04:00
public :
TextLink ( const QString & url , bool fullDisplayed = true ) : _url ( url ) , _fullDisplayed ( fullDisplayed ) {
QUrl u ( _url ) , good ( u . isValid ( ) ? u . toEncoded ( ) : QString ( ) ) ;
_readable = good . isValid ( ) ? good . toDisplayString ( ) : _url ;
}
const QString & text ( ) const {
return _url ;
}
2014-12-03 16:10:32 +03:00
void onClick ( Qt : : MouseButton button ) const ;
2014-05-30 12:53:19 +04:00
const QString & readable ( ) const {
return _readable ;
}
bool fullDisplayed ( ) const {
return _fullDisplayed ;
}
2015-12-23 14:13:08 +03:00
void setFullDisplayed ( bool full ) {
_fullDisplayed = full ;
}
2014-05-30 12:53:19 +04:00
QString encoded ( ) const {
QUrl u ( _url ) , good ( u . isValid ( ) ? u . toEncoded ( ) : QString ( ) ) ;
2015-06-24 20:24:48 +03:00
QString result ( good . isValid ( ) ? QString : : fromUtf8 ( good . toEncoded ( ) ) : _url ) ;
2014-05-30 12:53:19 +04:00
2016-02-17 19:37:21 +03:00
if ( ! QRegularExpression ( qsl ( " ^[a-zA-Z]+: " ) ) . match ( result ) . hasMatch ( ) ) { // no protocol
2014-05-30 12:53:19 +04:00
return qsl ( " http:// " ) + result ;
}
return result ;
}
private :
QString _url , _readable ;
bool _fullDisplayed ;
} ;
2015-09-10 14:20:28 +03:00
class CustomTextLink : public TextLink {
public :
CustomTextLink ( const QString & url ) : TextLink ( url , false ) {
}
void onClick ( Qt : : MouseButton button ) const ;
} ;
2014-05-30 12:53:19 +04:00
class EmailLink : public ITextLink {
2015-06-27 16:02:00 +03:00
TEXT_LINK_CLASS ( EmailLink )
2014-05-30 12:53:19 +04:00
public :
EmailLink ( const QString & email ) : _email ( email ) {
}
const QString & text ( ) const {
return _email ;
}
2015-08-28 18:15:56 +03:00
void onClick ( Qt : : MouseButton button ) const ;
2014-05-30 12:53:19 +04:00
const QString & readable ( ) const {
return _email ;
}
QString encoded ( ) const {
return _email ;
}
private :
QString _email ;
} ;
2016-02-28 16:54:04 +03:00
struct LocationCoords {
LocationCoords ( ) : lat ( 0 ) , lon ( 0 ) {
}
LocationCoords ( float64 lat , float64 lon ) : lat ( lat ) , lon ( lon ) {
}
float64 lat , lon ;
} ;
inline bool operator = = ( const LocationCoords & a , const LocationCoords & b ) {
return ( a . lat = = b . lat ) & & ( a . lon = = b . lon ) ;
}
inline bool operator < ( const LocationCoords & a , const LocationCoords & b ) {
return ( a . lat < b . lat ) | | ( ( a . lat = = b . lat ) & & ( a . lon < b . lon ) ) ;
}
inline uint qHash ( const LocationCoords & t , uint seed = 0 ) {
return qHash ( QtPrivate : : QHashCombine ( ) . operator ( ) ( qHash ( t . lat ) , t . lon ) , seed ) ;
}
2016-02-17 19:37:21 +03:00
class LocationLink : public ITextLink {
TEXT_LINK_CLASS ( LocationLink )
public :
2016-02-28 16:54:04 +03:00
LocationLink ( const LocationCoords & coords ) : _coords ( coords ) {
2016-02-17 19:37:21 +03:00
setup ( ) ;
}
const QString & text ( ) const {
return _text ;
}
void onClick ( Qt : : MouseButton button ) const ;
const QString & readable ( ) const {
return _text ;
}
QString encoded ( ) const {
return _text ;
}
private :
void setup ( ) ;
2016-02-28 16:54:04 +03:00
LocationCoords _coords ;
QString _text ;
2016-02-17 19:37:21 +03:00
} ;
2015-03-19 12:18:19 +03:00
class MentionLink : public ITextLink {
2015-06-27 16:02:00 +03:00
TEXT_LINK_CLASS ( MentionLink )
2015-03-19 12:18:19 +03:00
public :
MentionLink ( const QString & tag ) : _tag ( tag ) {
}
const QString & text ( ) const {
return _tag ;
}
void onClick ( Qt : : MouseButton button ) const ;
const QString & readable ( ) const {
return _tag ;
}
QString encoded ( ) const {
return _tag ;
}
private :
QString _tag ;
} ;
2014-07-16 10:58:36 +04:00
class HashtagLink : public ITextLink {
2015-06-27 16:02:00 +03:00
TEXT_LINK_CLASS ( HashtagLink )
2014-07-16 10:58:36 +04:00
public :
HashtagLink ( const QString & tag ) : _tag ( tag ) {
}
const QString & text ( ) const {
return _tag ;
}
void onClick ( Qt : : MouseButton button ) const ;
const QString & readable ( ) const {
return _tag ;
}
QString encoded ( ) const {
return _tag ;
}
private :
QString _tag ;
} ;
2015-06-10 18:54:24 +03:00
class BotCommandLink : public ITextLink {
2015-06-27 16:02:00 +03:00
TEXT_LINK_CLASS ( BotCommandLink )
2015-06-10 18:54:24 +03:00
public :
BotCommandLink ( const QString & cmd ) : _cmd ( cmd ) {
}
const QString & text ( ) const {
return _cmd ;
}
void onClick ( Qt : : MouseButton button ) const ;
const QString & readable ( ) const {
return _cmd ;
}
QString encoded ( ) const {
return _cmd ;
}
private :
QString _cmd ;
} ;
2014-05-30 12:53:19 +04:00
static const QChar TextCommand ( 0x0010 ) ;
enum TextCommands {
TextCommandBold = 0x01 ,
TextCommandNoBold = 0x02 ,
TextCommandItalic = 0x03 ,
TextCommandNoItalic = 0x04 ,
TextCommandUnderline = 0x05 ,
TextCommandNoUnderline = 0x06 ,
2016-03-05 00:04:15 +02:00
TextCommandSemibold = 0x07 ,
TextCommandNoSemibold = 0x08 ,
TextCommandLinkIndex = 0x09 , // 0 - NoLink
TextCommandLinkText = 0x0A ,
TextCommandColor = 0x0B ,
TextCommandNoColor = 0x0C ,
TextCommandSkipBlock = 0x0D ,
2014-12-18 21:40:49 +03:00
TextCommandLangTag = 0x20 ,
2014-05-30 12:53:19 +04:00
} ;
struct TextParseOptions {
int32 flags ;
int32 maxw ;
int32 maxh ;
Qt : : LayoutDirection dir ;
} ;
2015-06-15 20:19:24 +03:00
extern const TextParseOptions _defaultOptions , _textPlainOptions ;
2014-05-30 12:53:19 +04:00
enum TextSelectType {
TextSelectLetters = 0x01 ,
TextSelectWords = 0x02 ,
TextSelectParagraphs = 0x03 ,
} ;
typedef QPair < QString , QString > TextCustomTag ; // open str and close str
typedef QMap < QChar , TextCustomTag > TextCustomTagsMap ;
class Text {
public :
Text ( int32 minResizeWidth = QFIXED_MAX ) ;
Text ( style : : font font , const QString & text , const TextParseOptions & options = _defaultOptions , int32 minResizeWidth = QFIXED_MAX , bool richText = false ) ;
2015-04-04 23:01:34 +03:00
Text ( const Text & other ) ;
2016-03-25 14:29:45 +03:00
Text ( Text & & other ) ;
2015-05-20 22:28:24 +03:00
Text & operator = ( const Text & other ) ;
2016-03-25 14:29:45 +03:00
Text & operator = ( Text & & other ) ;
2014-05-30 12:53:19 +04:00
2016-02-07 18:38:49 +03:00
int32 countWidth ( int32 width ) const ;
2014-05-30 12:53:19 +04:00
int32 countHeight ( int32 width ) const ;
void setText ( style : : font font , const QString & text , const TextParseOptions & options = _defaultOptions ) ;
void setRichText ( style : : font font , const QString & text , TextParseOptions options = _defaultOptions , const TextCustomTagsMap & custom = TextCustomTagsMap ( ) ) ;
2015-10-23 18:06:56 +02:00
void setMarkedText ( style : : font font , const QString & text , const EntitiesInText & entities , const TextParseOptions & options = _defaultOptions ) ;
2014-05-30 12:53:19 +04:00
void setLink ( uint16 lnkIndex , const TextLinkPtr & lnk ) ;
bool hasLinks ( ) const ;
2015-05-20 22:28:24 +03:00
bool hasSkipBlock ( ) const {
2015-10-23 18:06:56 +02:00
return _blocks . isEmpty ( ) ? false : _blocks . back ( ) - > type ( ) = = TextBlockTSkip ;
2015-05-20 22:28:24 +03:00
}
2015-08-24 13:53:04 +03:00
void setSkipBlock ( int32 width , int32 height ) ;
2015-08-21 14:23:44 +03:00
void removeSkipBlock ( ) ;
2015-05-20 22:28:24 +03:00
2014-05-30 12:53:19 +04:00
int32 maxWidth ( ) const {
2014-06-14 23:32:11 +04:00
return _maxWidth . ceil ( ) . toInt ( ) ;
2014-05-30 12:53:19 +04:00
}
int32 minHeight ( ) const {
return _minHeight ;
}
2014-08-11 13:03:45 +04:00
void replaceFont ( style : : font f ) ; // does not recount anything, use at your own risk!
2014-05-30 12:53:19 +04:00
void draw ( QPainter & p , int32 left , int32 top , int32 width , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , uint16 selectedFrom = 0 , uint16 selectedTo = 0 ) const ;
2016-02-19 14:53:49 +03:00
void drawElided ( QPainter & p , int32 left , int32 top , int32 width , int32 lines = 1 , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , int32 removeFromEnd = 0 , bool breakEverywhere = false ) const ;
2015-10-14 13:51:37 +02:00
void drawLeft ( QPainter & p , int32 left , int32 top , int32 width , int32 outerw , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , uint16 selectedFrom = 0 , uint16 selectedTo = 0 ) const {
draw ( p , rtl ( ) ? ( outerw - left - width ) : left , top , width , align , yFrom , yTo , selectedFrom , selectedTo ) ;
}
2016-02-19 14:53:49 +03:00
void drawLeftElided ( QPainter & p , int32 left , int32 top , int32 width , int32 outerw , int32 lines = 1 , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , int32 removeFromEnd = 0 , bool breakEverywhere = false ) const {
drawElided ( p , rtl ( ) ? ( outerw - left - width ) : left , top , width , lines , align , yFrom , yTo , removeFromEnd , breakEverywhere ) ;
2015-10-14 13:51:37 +02:00
}
void drawRight ( QPainter & p , int32 right , int32 top , int32 width , int32 outerw , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , uint16 selectedFrom = 0 , uint16 selectedTo = 0 ) const {
draw ( p , rtl ( ) ? right : ( outerw - right - width ) , top , width , align , yFrom , yTo , selectedFrom , selectedTo ) ;
}
2016-02-19 14:53:49 +03:00
void drawRightElided ( QPainter & p , int32 right , int32 top , int32 width , int32 outerw , int32 lines = 1 , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , int32 removeFromEnd = 0 , bool breakEverywhere = false ) const {
drawElided ( p , rtl ( ) ? right : ( outerw - right - width ) , top , width , lines , align , yFrom , yTo , removeFromEnd , breakEverywhere ) ;
2015-10-14 13:51:37 +02:00
}
2014-05-30 12:53:19 +04:00
const TextLinkPtr & link ( int32 x , int32 y , int32 width , style : : align align = style : : al_left ) const ;
2015-10-14 13:51:37 +02:00
const TextLinkPtr & linkLeft ( int32 x , int32 y , int32 width , int32 outerw , style : : align align = style : : al_left ) const {
return link ( rtl ( ) ? ( outerw - x - width ) : x , y , width , align ) ;
}
2016-02-19 14:53:49 +03:00
void getState ( TextLinkPtr & lnk , bool & inText , int32 x , int32 y , int32 width , style : : align align = style : : al_left , bool breakEverywhere = false ) const ;
void getStateLeft ( TextLinkPtr & lnk , bool & inText , int32 x , int32 y , int32 width , int32 outerw , style : : align align = style : : al_left , bool breakEverywhere = false ) const {
return getState ( lnk , inText , rtl ( ) ? ( outerw - x - width ) : x , y , width , align , breakEverywhere ) ;
2015-10-14 13:51:37 +02:00
}
2014-05-30 12:53:19 +04:00
void getSymbol ( uint16 & symbol , bool & after , bool & upon , int32 x , int32 y , int32 width , style : : align align = style : : al_left ) const ;
2015-10-14 13:51:37 +02:00
void getSymbolLeft ( uint16 & symbol , bool & after , bool & upon , int32 x , int32 y , int32 width , int32 outerw , style : : align align = style : : al_left ) const {
return getSymbol ( symbol , after , upon , rtl ( ) ? ( outerw - x - width ) : x , y , width , align ) ;
}
2014-05-30 12:53:19 +04:00
uint32 adjustSelection ( uint16 from , uint16 to , TextSelectType selectType ) const ;
2015-04-04 23:01:34 +03:00
bool isEmpty ( ) const {
return _text . isEmpty ( ) ;
}
2015-06-25 13:12:38 +03:00
bool isNull ( ) const {
return ! _font ;
}
2015-10-25 18:08:45 +01:00
enum ExpandLinksMode {
ExpandLinksNone ,
ExpandLinksShortened ,
ExpandLinksAll ,
} ;
QString original ( uint16 selectedFrom = 0 , uint16 selectedTo = 0xFFFF , ExpandLinksMode mode = ExpandLinksShortened ) const ;
EntitiesInText originalEntities ( ) const ;
2014-05-30 12:53:19 +04:00
2014-06-16 13:31:10 +04:00
bool lastDots ( int32 dots , int32 maxdots = 3 ) { // hack for typing animation
2014-05-30 12:53:19 +04:00
if ( _text . size ( ) < maxdots ) return false ;
int32 nowDots = 0 , from = _text . size ( ) - maxdots , to = _text . size ( ) ;
for ( int32 i = from ; i < to ; + + i ) {
if ( _text . at ( i ) = = QChar ( ' . ' ) ) {
+ + nowDots ;
}
}
if ( nowDots = = dots ) return false ;
for ( int32 j = from ; j < from + dots ; + + j ) {
_text [ j ] = QChar ( ' . ' ) ;
}
for ( int32 j = from + dots ; j < to ; + + j ) {
_text [ j ] = QChar ( ' ' ) ;
}
return true ;
}
2016-03-25 14:29:45 +03:00
void clear ( ) ;
2014-05-30 12:53:19 +04:00
~ Text ( ) {
2016-03-25 14:29:45 +03:00
clear ( ) ;
2014-05-30 12:53:19 +04:00
}
private :
2015-08-21 14:23:44 +03:00
void recountNaturalSize ( bool initial , Qt : : LayoutDirection optionsDir = Qt : : LayoutDirectionAuto ) ;
2016-03-25 14:29:45 +03:00
// clear() deletes all blocks and calls this method
// it is also called from move constructor / assignment operator
void clearFields ( ) ;
2014-05-30 12:53:19 +04:00
QFixed _minResizeWidth , _maxWidth ;
int32 _minHeight ;
QString _text ;
style : : font _font ;
typedef QVector < ITextBlock * > TextBlocks ;
TextBlocks _blocks ;
typedef QVector < TextLinkPtr > TextLinks ;
TextLinks _links ;
Qt : : LayoutDirection _startDir ;
friend class TextParser ;
friend class TextPainter ;
} ;
2015-04-07 01:15:29 +03:00
void initLinkSets ( ) ;
const QSet < int32 > & validProtocols ( ) ;
const QSet < int32 > & validTopDomains ( ) ;
const QRegularExpression & reDomain ( ) ;
const QRegularExpression & reMailName ( ) ;
2015-09-10 14:20:28 +03:00
const QRegularExpression & reMailStart ( ) ;
2015-03-24 13:00:27 +03:00
const QRegularExpression & reHashtag ( ) ;
2015-06-10 18:54:24 +03:00
const QRegularExpression & reBotCommand ( ) ;
2015-03-24 13:00:27 +03:00
2014-05-30 12:53:19 +04:00
// text style
const style : : textStyle * textstyleCurrent ( ) ;
void textstyleSet ( const style : : textStyle * style ) ;
inline void textstyleRestore ( ) {
textstyleSet ( 0 ) ;
}
// textlnk
void textlnkOver ( const TextLinkPtr & lnk ) ;
const TextLinkPtr & textlnkOver ( ) ;
void textlnkDown ( const TextLinkPtr & lnk ) ;
const TextLinkPtr & textlnkDown ( ) ;
2015-12-11 21:11:38 +03:00
bool textlnkDrawOver ( const TextLinkPtr & lnk ) ;
2014-05-30 12:53:19 +04:00
// textcmd
QString textcmdSkipBlock ( ushort w , ushort h ) ;
QString textcmdStartLink ( ushort lnkIndex ) ;
QString textcmdStartLink ( const QString & url ) ;
QString textcmdStopLink ( ) ;
QString textcmdLink ( ushort lnkIndex , const QString & text ) ;
QString textcmdLink ( const QString & url , const QString & text ) ;
QString textcmdStartColor ( const style : : color & color ) ;
QString textcmdStopColor ( ) ;
2016-03-05 00:04:15 +02:00
QString textcmdStartSemibold ( ) ;
QString textcmdStopSemibold ( ) ;
2014-12-18 21:40:49 +03:00
const QChar * textSkipCommand ( const QChar * from , const QChar * end , bool canLink = true ) ;
2015-01-05 23:17:33 +03:00
2015-04-07 01:15:29 +03:00
inline bool chIsSpace ( QChar ch , bool rich = false ) {
return ch . isSpace ( ) | | ( ch < 32 & & ! ( rich & & ch = = TextCommand ) ) | | ( ch = = QChar : : ParagraphSeparator ) | | ( ch = = QChar : : LineSeparator ) | | ( ch = = QChar : : ObjectReplacementCharacter ) | | ( ch = = QChar : : SoftHyphen ) | | ( ch = = QChar : : CarriageReturn ) | | ( ch = = QChar : : Tabulation ) ;
}
2015-10-23 22:02:29 +02:00
inline bool chIsDiac ( QChar ch ) { // diac and variation selectors
return ( ch . category ( ) = = QChar : : Mark_NonSpacing ) | | ( ch . unicode ( ) = = 1652 ) ;
}
2015-04-07 01:15:29 +03:00
inline bool chIsBad ( QChar ch ) {
2015-12-03 21:16:34 +03:00
return ( ch = = 0 ) | | ( ch > = 8232 & & ch < 8237 ) | | ( ch > = 65024 & & ch < 65040 & & ch ! = 65039 ) | | ( ch > = 127 & & ch < 160 & & ch ! = 156 ) | | ( cPlatform ( ) = = dbipMac & & ch > = 0x0B00 & & ch < = 0x0B7F & & chIsDiac ( ch ) & & cIsElCapitan ( ) ) ; // tmp hack see https://bugreports.qt.io/browse/QTBUG-48910
2015-04-07 01:15:29 +03:00
}
inline bool chIsTrimmed ( QChar ch , bool rich = false ) {
return ( ! rich | | ch ! = TextCommand ) & & ( chIsSpace ( ch ) | | chIsBad ( ch ) ) ;
}
2015-10-23 18:06:56 +02:00
inline bool chReplacedBySpace ( QChar ch ) {
// \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
// \xcc[\xb3\xbf\x8a] // 819, 831, 778
// QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a");
// [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09
return ( /*code >= 0x00 && */ ch < = 0x02 ) | | ( ch > = 0x07 & & ch < = 0x09 ) | | ( ch > = 0x0b & & ch < = 0x1f ) | |
( ch = = 819 ) | | ( ch = = 831 ) | | ( ch = = 778 ) | | ( ch > = 8232 & & ch < = 8237 ) ;
}
2015-04-07 01:15:29 +03:00
inline int32 chMaxDiacAfterSymbol ( ) {
2015-05-11 13:18:57 +03:00
return 2 ;
2015-04-07 01:15:29 +03:00
}
inline bool chIsNewline ( QChar ch ) {
return ( ch = = QChar : : LineFeed | | ch = = 156 ) ;
}
inline bool chIsLinkEnd ( QChar ch ) {
return ch = = TextCommand | | chIsBad ( ch ) | | chIsSpace ( ch ) | | chIsNewline ( ch ) | | ch . isLowSurrogate ( ) | | ch . isHighSurrogate ( ) ;
}
inline bool chIsAlmostLinkEnd ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case ' ? ' :
case ' , ' :
case ' . ' :
case ' " ' :
case ' : ' :
case ' ! ' :
case ' \' ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsWordSeparator ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case QChar : : Space :
case QChar : : LineFeed :
case ' . ' :
case ' , ' :
case ' ? ' :
case ' ! ' :
case ' @ ' :
case ' # ' :
case ' $ ' :
case ' : ' :
case ' ; ' :
case ' - ' :
case ' < ' :
case ' > ' :
case ' [ ' :
case ' ] ' :
case ' ( ' :
case ' ) ' :
case ' { ' :
case ' } ' :
case ' = ' :
case ' / ' :
case ' + ' :
case ' % ' :
case ' & ' :
case ' ^ ' :
case ' * ' :
case ' \' ' :
case ' " ' :
case ' ` ' :
case ' ~ ' :
case ' | ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsSentenceEnd ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case ' . ' :
case ' ? ' :
case ' ! ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsSentencePartEnd ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case ' , ' :
case ' : ' :
case ' ; ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsParagraphSeparator ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case QChar : : LineFeed :
return true ;
default :
break ;
}
return false ;
}
2015-10-18 14:49:34 +02:00
inline QString myUrlEncode ( const QString & str ) {
return QString : : fromLatin1 ( QUrl : : toPercentEncoding ( str ) ) ;
}
inline QString myUrlDecode ( const QString & enc ) {
return QUrl : : fromPercentEncoding ( enc . toUtf8 ( ) ) ;
}
2015-10-23 18:06:56 +02:00
QString prepareTextWithEntities ( QString result , EntitiesInText & entities , int32 flags ) ;
inline QString prepareText ( QString result , bool checkLinks = false ) {
EntitiesInText entities ;
return prepareTextWithEntities ( result , entities , checkLinks ? ( TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands ) : 0 ) ;
}
inline void moveStringPart ( QChar * start , int32 & to , int32 & from , int32 count , EntitiesInText & entities ) {
if ( count > 0 ) {
if ( to < from ) {
memmove ( start + to , start + from , count * sizeof ( QChar ) ) ;
for ( EntitiesInText : : iterator i = entities . begin ( ) , e = entities . end ( ) ; i ! = e ; + + i ) {
if ( i - > offset > = from + count ) break ;
if ( i - > offset + i - > length < from ) continue ;
if ( i - > offset > = from ) {
i - > offset - = ( from - to ) ;
i - > length + = ( from - to ) ;
}
if ( i - > offset + i - > length < from + count ) {
i - > length - = ( from - to ) ;
}
}
}
to + = count ;
from + = count ;
}
}
// replace bad symbols with space and remove \r
inline void cleanTextWithEntities ( QString & result , EntitiesInText & entities ) {
result = result . replace ( ' \t ' , qstr ( " " ) ) ;
int32 len = result . size ( ) , to = 0 , from = 0 ;
QChar * start = result . data ( ) ;
for ( QChar * ch = start , * end = start + len ; ch < end ; + + ch ) {
if ( ch - > unicode ( ) = = ' \r ' ) {
moveStringPart ( start , to , from , ( ch - start ) - from , entities ) ;
+ + from ;
} else if ( chReplacedBySpace ( * ch ) ) {
* ch = ' ' ;
}
}
moveStringPart ( start , to , from , len - from , entities ) ;
if ( to < len ) result . resize ( to ) ;
}
inline void trimTextWithEntities ( QString & result , EntitiesInText & entities ) {
2015-10-25 18:08:45 +01:00
bool foundNotTrimmed = false ;
2015-10-23 18:06:56 +02:00
for ( QChar * s = result . data ( ) , * e = s + result . size ( ) , * ch = e ; ch ! = s ; ) { // rtrim
- - ch ;
if ( ! chIsTrimmed ( * ch ) ) {
if ( ch + 1 < e ) {
int32 l = ch + 1 - s ;
for ( EntitiesInText : : iterator i = entities . begin ( ) , e = entities . end ( ) ; i ! = e ; + + i ) {
if ( i - > offset > l ) {
i - > offset = l ;
i - > length = 0 ;
} else if ( i - > offset + i - > length > l ) {
i - > length = l - i - > offset ;
}
}
result . resize ( l ) ;
}
2015-10-25 18:08:45 +01:00
foundNotTrimmed = true ;
2015-10-23 18:06:56 +02:00
break ;
}
}
2015-10-25 18:08:45 +01:00
if ( ! foundNotTrimmed ) {
result . clear ( ) ;
entities . clear ( ) ;
return ;
}
2015-10-23 18:06:56 +02:00
for ( QChar * s = result . data ( ) , * ch = s , * e = s + result . size ( ) ; ch ! = e ; + + ch ) { // ltrim
if ( ! chIsTrimmed ( * ch ) ) {
if ( ch > s ) {
int32 l = ch - s ;
for ( EntitiesInText : : iterator i = entities . begin ( ) , e = entities . end ( ) ; i ! = e ; + + i ) {
if ( i - > offset + i - > length < = l ) {
i - > length = 0 ;
i - > offset = 0 ;
} else if ( i - > offset < l ) {
i - > length = i - > offset + i - > length - l ;
i - > offset = 0 ;
} else {
i - > offset - = l ;
}
}
result = result . mid ( l ) ;
}
break ;
}
}
}