@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
/*
Copyright © 2014 - 2015 by The qTox Project Contributors
Copyright © 2014 - 2017 by The qTox Project Contributors
This file is part of qTox , a Qt - based graphical interface for Tox .
@ -19,34 +19,17 @@
@@ -19,34 +19,17 @@
# include "smileypack.h"
# include "src/persistence/settings.h"
# include "src/widget/style.h"
# include <QBuffer>
# include <QBuffer>
# include <QCoreApplication>
# include <QCoreApplication>
# include <QDir>
# include <QDir>
# include <QDomDocument>
# include <QDomDocument>
# include <QDomElement>
# include <QDomElement>
# include <QFile>
# include <QFileInfo>
# include <QFont>
# include <QFontInfo>
# include <QIcon>
# include <QPixmap>
# include <QRegularExpression>
# include <QStandardPaths>
# include <QStringBuilder>
# include <QtConcurrent/QtConcurrentRun>
# if defined(Q_OS_FREEBSD)
# include <locale.h>
# endif
# define EMOTICONS_SUB_DIR QStringLiteral("emoticons")
/**
* @ class SmileyPack
* @ brief Maps emoticons to smileys .
@ -67,36 +50,29 @@
@@ -67,36 +50,29 @@
* @ brief Contains all directories where smileys could be found
*/
QStringList SmileyPack : : defaultPaths = loadDefaultPaths ( ) ;
QStringList loadDefaultPaths ( ) ;
SmileyPack : : SmileyPack ( )
{
loadingMutex . lock ( ) ;
QtConcurrent : : run ( this , & SmileyPack : : load , Settings : : getInstance ( ) . getSmileyPack ( ) ) ;
connect ( & Settings : : getInstance ( ) , & Settings : : smileyPackChanged , this ,
& SmileyPack : : onSmileyPackChanged ) ;
}
static const QStringList DEFAULT_PATHS = loadDefaultPaths ( ) ;
static const QString RICH_TEXT_PATTERN = QStringLiteral ( " <img title= \" %1 \" src= \" key:%1 \" \\ > " ) ;
static const QString EMOTICONS_FILE_NAME = QStringLiteral ( " emoticons.xml " ) ;
/**
* @ brief Returns the singleton instance .
* @ brief Construct list of standard directories with " emoticons " sub dir , whether these directories
* exist or not
* @ return Constructed list of default emoticons directories
*/
SmileyPack & SmileyPack : : getInstance ( )
{
static SmileyPack smileyPack ;
return smileyPack ;
}
QStringList SmileyPack : : loadDefaultPaths ( )
QStringList loadDefaultPaths ( )
{
# if defined(Q_OS_FREEBSD)
// TODO: Remove when will be fixed.
// Workaround to fix https://bugreports.qt.io/browse/QTBUG-57522
setlocale ( LC_ALL , " " ) ;
# endif
QStringList paths =
QStringList { " :/smileys " , " ~/.kde4/share/emoticons " , " ~/.kde/share/emoticons " } ;
// qTox should find emoticons next to the binary
paths . append ( ' . ' + QDir : : separator ( ) + EMOTICONS_SUB_DIR ) ;
const QString EMOTICONS_SUB_PATH = QDir : : separator ( ) + QStringLiteral ( " emoticons " ) ;
QStringList paths { " :/smileys " , " ~/.kde4/share/emoticons " , " ~/.kde/share/emoticons " ,
EMOTICONS_SUB_PATH } ;
// qTox exclusive emoticons
QStandardPaths : : StandardLocation location ;
@ -106,36 +82,68 @@ QStringList SmileyPack::loadDefaultPaths()
@@ -106,36 +82,68 @@ QStringList SmileyPack::loadDefaultPaths()
# warning "Qt < 5.4.0 has a trouble with unicode symbols in path on few systems"
location = QStandardPaths : : DataLocation ;
# endif
for ( auto qtoxPath : QStandardPaths : : standardLocations ( location ) ) {
qtoxPath + = QDir : : separator ( ) + EMOTICONS_SUB_DIR ;
if ( ! paths . contains ( qtoxPath ) ) {
paths < < qtoxPath ;
}
}
QStringList locations = QStandardPaths : : standardLocations ( location ) ;
// system wide emoticons
for ( auto genericPath : QStandardPaths : : standardLocations ( QStandardPaths : : GenericDataLocation ) ) {
genericPath + = QDir : : separator ( ) + EMOTICONS_SUB_DIR ;
if ( ! paths . contains ( genericPath ) ) {
paths < < genericPath ;
locations . append ( QStandardPaths : : standardLocations ( QStandardPaths : : GenericDataLocation ) ) ;
for ( QString qtoxPath : locations ) {
qtoxPath . append ( EMOTICONS_SUB_PATH ) ;
if ( ! paths . contains ( qtoxPath ) ) {
paths . append ( qtoxPath ) ;
}
}
return paths ;
}
/**
* @ brief Wraps passed string into smiley HTML image reference
* @ param key Describes which smiley is needed
* @ return Key that wrapped into image ref
*/
QString getAsRichText ( const QString & key )
{
return RICH_TEXT_PATTERN . arg ( key ) ;
}
SmileyPack : : SmileyPack ( )
{
loadingMutex . lock ( ) ;
QtConcurrent : : run ( this , & SmileyPack : : load , Settings : : getInstance ( ) . getSmileyPack ( ) ) ;
connect ( & Settings : : getInstance ( ) , & Settings : : smileyPackChanged , this ,
& SmileyPack : : onSmileyPackChanged ) ;
}
/**
* @ brief Returns the singleton instance .
*/
SmileyPack & SmileyPack : : getInstance ( )
{
static SmileyPack smileyPack ;
return smileyPack ;
}
/**
* @ brief Does the same as listSmileyPaths , but with default paths
*/
QList < QPair < QString , QString > > SmileyPack : : listSmileyPacks ( )
{
return listSmileyPacks ( defaultPaths ) ;
return listSmileyPacks ( DEFAULT_PATHS ) ;
}
/**
* @ brief Searches all files called " emoticons.xml " within the every passed path in the depth of 2
* @ param paths Paths where to search for file
* @ return Vector of pairs : { directoryName , absolutePathToFile }
*/
QList < QPair < QString , QString > > SmileyPack : : listSmileyPacks ( const QStringList & paths )
{
QList < QPair < QString , QString > > smileyPacks ;
const QString homePath = QDir : : homePath ( ) ;
for ( QString path : paths ) {
if ( path . leftRef ( 1 ) = = " ~ " ) {
path . replace ( 0 , 1 , QDir : : homePath ( ) ) ;
if ( path . startsWith ( ' ~ ' ) ) {
path . replace ( 0 , 1 , homePath ) ;
}
QDir dir ( path ) ;
@ -145,14 +153,11 @@ QList<QPair<QString, QString>> SmileyPack::listSmileyPacks(const QStringList& pa
@@ -145,14 +153,11 @@ QList<QPair<QString, QString>> SmileyPack::listSmileyPacks(const QStringList& pa
for ( const QString & subdirectory : dir . entryList ( QDir : : Dirs | QDir : : NoDotAndDotDot ) ) {
dir . cd ( subdirectory ) ;
QFileInfoList entries = dir . entryInfoList ( QStringList ( ) < < " emoticons.xml " , QDir : : Files ) ;
// Does it contain a file called emoticons.xml?
if ( entries . size ( ) > 0 ) {
QString packageName = dir . dirName ( ) ;
QString absPath = entries [ 0 ] . absoluteFilePath ( ) ;
if ( ! smileyPacks . contains ( QPair < QString , QString > ( packageName , absPath ) ) ) {
smileyPacks < < QPair < QString , QString > ( packageName , absPath ) ;
if ( dir . exists ( EMOTICONS_FILE_NAME ) ) {
QString absPath = dir . absolutePath ( ) + QDir : : separator ( ) + EMOTICONS_FILE_NAME ;
QPair < QString , QString > p { dir . dirName ( ) , absPath } ;
if ( ! smileyPacks . contains ( p ) ) {
smileyPacks . append ( p ) ;
}
}
@ -163,11 +168,6 @@ QList<QPair<QString, QString>> SmileyPack::listSmileyPacks(const QStringList& pa
@@ -163,11 +168,6 @@ QList<QPair<QString, QString>> SmileyPack::listSmileyPacks(const QStringList& pa
return smileyPacks ;
}
bool SmileyPack : : isValid ( const QString & filename )
{
return QFile ( filename ) . exists ( ) ;
}
/**
* @ brief Load smile pack
* @ note The caller must lock loadingMutex and should run it in a thread
@ -176,19 +176,16 @@ bool SmileyPack::isValid(const QString& filename)
@@ -176,19 +176,16 @@ bool SmileyPack::isValid(const QString& filename)
*/
bool SmileyPack : : load ( const QString & filename )
{
// discard old data
filenameTable . clear ( ) ;
iconCache . clear ( ) ;
emoticons . clear ( ) ;
path . clear ( ) ;
// open emoticons.xml
QFile xmlFile ( filename ) ;
if ( ! xmlFile . open ( QIODevice : : ReadOnly ) ) {
if ( ! xmlFile . exists ( ) | | ! xmlFile . open ( QIODevice : : ReadOnly ) ) {
loadingMutex . unlock ( ) ;
return false ; // cannot open file
return false ;
}
QDomDocument doc ;
doc . setContent ( xmlFile . readAll ( ) ) ;
xmlFile . close ( ) ;
/* parse the cfg file
* sample :
* < ? xml version = ' 1.0 ' ? >
@ -205,103 +202,80 @@ bool SmileyPack::load(const QString& filename)
@@ -205,103 +202,80 @@ bool SmileyPack::load(const QString& filename)
*/
path = QFileInfo ( filename ) . absolutePath ( ) ;
QDomDocument doc ;
doc . setContent ( xmlFile . readAll ( ) ) ;
QDomNodeList emoticonElements = doc . elementsByTagName ( " emoticon " ) ;
for ( int i = 0 ; i < emoticonElements . size ( ) ; + + i ) {
QString file = emoticonElements . at ( i ) . attributes ( ) . namedItem ( " file " ) . nodeValue ( ) ;
QDomElement stringElement = emoticonElements . at ( i ) . firstChildElement ( " string " ) ;
QStringList emoticonSet ; // { ":)", ":-)" } etc.
const QString itemName = QStringLiteral ( " file " ) ;
const QString childName = QStringLiteral ( " string " ) ;
const int iconsCount = emoticonElements . size ( ) ;
emoticons . clear ( ) ;
emoticonToIcon . clear ( ) ;
icons . clear ( ) ;
icons . reserve ( iconsCount ) ;
for ( int i = 0 ; i < iconsCount ; + + i ) {
QDomNode node = emoticonElements . at ( i ) ;
QString iconName = node . attributes ( ) . namedItem ( itemName ) . nodeValue ( ) ;
QString iconPath = QDir { path } . filePath ( iconName ) ;
icons . append ( QIcon { iconPath } ) ;
QDomElement stringElement = node . firstChildElement ( childName ) ;
QStringList emoticonList ;
while ( ! stringElement . isNull ( ) ) {
QString emoticon = stringElement . text ( ) . replace ( " < " , " < " ) . replace ( " > " , " > " ) ;
filenameTable . insert ( emoticon , file ) ;
cacheSmiley ( file ) ; // preload all smileys
if ( ! getCachedSmiley ( emoticon ) . isNull ( ) ) {
emoticonSet . push_back ( emoticon ) ;
}
emoticonToIcon . insert ( emoticon , & icons [ i ] ) ;
emoticonList . append ( emoticon ) ;
stringElement = stringElement . nextSibling ( ) . toElement ( ) ;
}
if ( emoticonSet . size ( ) > 0 ) {
emoticons . push_back ( emoticonSet ) ;
}
emoticons . append ( emoticonList ) ;
}
// success!
loadingMutex . unlock ( ) ;
return true ;
}
QString SmileyPack : : smileyfied ( QString msg )
/**
* @ brief Replaces all found text emoticons to HTML reference with its according icon filename
* @ param msg Message where to search for emoticons
* @ return Formatted copy of message
*/
QString SmileyPack : : smileyfied ( const QString & msg )
{
QMutexLocker locker ( & loadingMutex ) ;
QRegExp exp ( " \\ S+ " ) ; // matches words
int index = msg . indexOf ( exp ) ;
// if a word is key of a smiley, replace it by its corresponding image in Rich Text
while ( index > = 0 ) {
QString key = exp . cap ( ) ;
if ( filenameTable . contains ( key ) ) {
QString result ( msg ) ;
QRegularExpression exp ( " \\ S+ " ) ;
QRegularExpressionMatchIterator iter = exp . globalMatch ( result ) ;
int replaceDiff = 0 ;
while ( iter . hasNext ( ) ) {
QRegularExpressionMatch match = iter . next ( ) ;
QString key = match . captured ( ) ;
int startPos = match . capturedStart ( ) ;
int keyLength = key . length ( ) ;
if ( emoticonToIcon . contains ( key ) ) {
QString imgRichText = getAsRichText ( key ) ;
msg . replace ( index , key . length ( ) , imgRichText ) ;
index + = imgRichText . length ( ) - key . length ( ) ;
result . replace ( startPos + replaceDiff , keyLength , imgRichText ) ;
replaceDiff + = imgRichText . length ( ) - keyLength ;
}
index = msg . indexOf ( exp , index + key . length ( ) ) ;
}
return msg ;
return result ;
}
/**
* @ brief Returns all emoticons that was extracted from files , grouped by according icon file
*/
QList < QStringList > SmileyPack : : getEmoticons ( ) const
{
QMutexLocker locker ( & loadingMutex ) ;
return emoticons ;
}
QString SmileyPack : : getAsRichText ( const QString & key )
{
return QString ( " <img title= \" %1 \" src= \" key:%1 \" \\ > " ) . arg ( key ) ;
}
QIcon SmileyPack : : getAsIcon ( const QString & key )
/**
* @ brief Gets icon accoring to passed emoticon
* @ param emoticon Passed emoticon
* @ return Returns cached icon according to passed emoticon , null if no icon mapped to this emoticon
*/
QIcon SmileyPack : : getAsIcon ( const QString & emoticon )
{
QMutexLocker locker ( & loadingMutex ) ;
return getCachedSmiley ( key ) ;
}
void SmileyPack : : cacheSmiley ( const QString & name )
{
QString filename = QDir ( path ) . filePath ( name ) ;
QIcon icon ;
icon . addFile ( filename ) ;
iconCache . insert ( name , icon ) ;
}
QIcon SmileyPack : : getCachedSmiley ( const QString & key )
{
// valid key?
if ( ! filenameTable . contains ( key ) ) {
return QPixmap ( ) ;
}
// cache it if needed
QString file = filenameTable . value ( key ) ;
if ( ! iconCache . contains ( file ) ) {
cacheSmiley ( file ) ;
}
return iconCache . value ( file ) ;
return emoticonToIcon . contains ( emoticon ) ? * ( emoticonToIcon [ emoticon ] ) : QIcon ( ) ;
}
void SmileyPack : : onSmileyPackChanged ( )