2014-11-22 17:16:22 -05:00
/****************************************************************
*
* RetroShare is distributed under the following license :
*
* Copyright ( C ) 2006 , Thunder
*
* This program 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 2
* of the License , or ( at your option ) any later version .
*
* This program 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor ,
* Boston , MA 02110 - 1301 , USA .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Directory tree for stylesheets:
Install version : < RetroShare config dir > / stylesheets
Portable version : < Application dir > / stylesheets
stylesheets
+ style1
| - history
| - private
| - public
+ style2
| - history
| - private
| - public
Files and dirs of the style :
info . xml - description
incoming . htm - incoming messages
outgoing . htm - outgoing messages
hincoming . htm - incoming history messages
houtgoing . htm - outgoing history messages
ooutgoing . htm - outgoing offline messages ( private chat )
system . htm - system messages
main . css - stylesheet
variants - directory with variants ( optional )
+ - * . css - Stylesheets for the variants
Example :
info . xml
< ? xml version = " 1.0 " encoding = " UTF-8 " ? >
< ! DOCTYPE RetroShare_StyleInfo >
< RetroShare_Style version = " 1.0 " >
< style >
< name > Name < / name >
< description > Description < / description >
< / style >
< author >
< name > Author < / name >
< email > E - Mail < / email >
< / author >
< / RetroShare_Style >
incoming . htm
< style type = " text/css " >
% css - style %
< / style >
< span class = ' incomingTime ' > % time % < / span >
< span class = ' incomingName ' > < strong > % name % < / strong > < / span >
% message %
outgoing . htm
< style type = " text/css " >
% css - style %
< / style >
< span class = ' outgoingTime ' > % time % < / span >
< span class = ' outgoingName ' > < strong > % name % < / strong > < / span >
% message %
main . css
. incomingTime {
color : # C00000 ;
}
. incomingName {
color : # 2 D84C9 ;
}
. outgoingTime {
color : # C00000 ;
}
. outgoingName {
color : # 2 D84C9 ;
}
:
more definitions for history and offline messages
See standard styles in retroshare - gui / src / gui / qss / chat /
*/
# include <QApplication>
# include <QColor>
# include <QXmlStreamReader>
# include <QDomDocument>
# include <QTextStream>
# include "ChatStyle.h"
# include "gui/settings/rsharesettings.h"
# include "gui/notifyqt.h"
# include "util/DateTime.h"
# include "util/HandleRichText.h"
# include <retroshare/rsinit.h>
# define COLORED_NICKNAMES
enum enumGetStyle
{
GETSTYLE_INCOMING ,
GETSTYLE_OUTGOING ,
GETSTYLE_HINCOMING ,
GETSTYLE_HOUTGOING ,
GETSTYLE_OOUTGOING ,
GETSTYLE_SYSTEM
} ;
/* Default constructor */
ChatStyle : : ChatStyle ( ) : QObject ( )
{
m_styleType = TYPE_UNKNOWN ;
connect ( NotifyQt : : getInstance ( ) , SIGNAL ( chatStyleChanged ( int ) ) , SLOT ( styleChanged ( int ) ) ) ;
}
/* Destructor. */
ChatStyle : : ~ ChatStyle ( )
{
}
void ChatStyle : : styleChanged ( int styleType )
{
if ( m_styleType = = styleType ) {
setStyleFromSettings ( m_styleType ) ;
}
}
2015-08-27 15:43:18 -04:00
static QStringList getBaseDirList ( )
2014-11-22 17:16:22 -05:00
{
2015-08-27 15:43:18 -04:00
// Search chat styles in config dir and data dir (is application dir for portable)
QStringList baseDirs ;
baseDirs . append ( QString : : fromUtf8 ( RsAccounts : : ConfigDirectory ( ) . c_str ( ) ) ) ;
baseDirs . append ( QString : : fromUtf8 ( RsAccounts : : DataDirectory ( ) . c_str ( ) ) ) ;
2014-11-22 17:16:22 -05:00
2015-08-27 15:43:18 -04:00
return baseDirs ;
2014-11-22 17:16:22 -05:00
}
bool ChatStyle : : setStylePath ( const QString & stylePath , const QString & styleVariant )
{
m_styleType = TYPE_UNKNOWN ;
2015-08-27 15:43:18 -04:00
foreach ( QString dir , getBaseDirList ( ) ) {
m_styleDir . setPath ( dir ) ;
if ( m_styleDir . cd ( stylePath ) ) {
m_styleVariant = styleVariant ;
return true ;
}
2014-11-22 17:16:22 -05:00
}
2015-08-27 15:43:18 -04:00
m_styleDir . setPath ( " " ) ;
m_styleVariant . clear ( ) ;
return false ;
2014-11-22 17:16:22 -05:00
}
bool ChatStyle : : setStyleFromSettings ( enumStyleType styleType )
{
QString stylePath ;
QString styleVariant ;
switch ( styleType ) {
case TYPE_PUBLIC :
Settings - > getPublicChatStyle ( stylePath , styleVariant ) ;
break ;
case TYPE_PRIVATE :
Settings - > getPrivateChatStyle ( stylePath , styleVariant ) ;
break ;
case TYPE_HISTORY :
Settings - > getHistoryChatStyle ( stylePath , styleVariant ) ;
break ;
case TYPE_UNKNOWN :
return false ;
}
bool result = setStylePath ( stylePath , styleVariant ) ;
m_styleType = styleType ;
// reset cache
for ( int i = 0 ; i < FORMATMSG_COUNT ; + + i ) {
m_style [ i ] . clear ( ) ;
}
return result ;
}
static QString getStyle ( const QDir & styleDir , const QString & styleVariant , enumGetStyle type )
{
QString style ;
if ( styleDir = = QDir ( " " ) ) {
return " " ;
}
QFile fileHtml ;
switch ( type ) {
case GETSTYLE_INCOMING :
fileHtml . setFileName ( QFileInfo ( styleDir , " incoming.htm " ) . absoluteFilePath ( ) ) ;
break ;
case GETSTYLE_OUTGOING :
fileHtml . setFileName ( QFileInfo ( styleDir , " outgoing.htm " ) . absoluteFilePath ( ) ) ;
break ;
case GETSTYLE_HINCOMING :
fileHtml . setFileName ( QFileInfo ( styleDir , " hincoming.htm " ) . absoluteFilePath ( ) ) ;
break ;
case GETSTYLE_HOUTGOING :
fileHtml . setFileName ( QFileInfo ( styleDir , " houtgoing.htm " ) . absoluteFilePath ( ) ) ;
break ;
case GETSTYLE_OOUTGOING :
fileHtml . setFileName ( QFileInfo ( styleDir , " ooutgoing.htm " ) . absoluteFilePath ( ) ) ;
break ;
case GETSTYLE_SYSTEM :
fileHtml . setFileName ( QFileInfo ( styleDir , " system.htm " ) . absoluteFilePath ( ) ) ;
break ;
default :
return " " ;
}
if ( fileHtml . open ( QIODevice : : ReadOnly ) ) {
style = fileHtml . readAll ( ) ;
fileHtml . close ( ) ;
QFile fileCss ( QFileInfo ( styleDir , " main.css " ) . absoluteFilePath ( ) ) ;
QString css ;
if ( fileCss . open ( QIODevice : : ReadOnly ) ) {
css = fileCss . readAll ( ) ;
fileCss . close ( ) ;
}
QString variant = styleVariant ;
if ( styleVariant . isEmpty ( ) ) {
// use standard
variant = " Standard " ;
}
QFile fileCssVariant ( QFileInfo ( styleDir , " variants/ " + variant + " .css " ) . absoluteFilePath ( ) ) ;
QString cssVariant ;
if ( fileCssVariant . open ( QIODevice : : ReadOnly ) ) {
cssVariant = fileCssVariant . readAll ( ) ;
fileCssVariant . close ( ) ;
css + = " \n " + cssVariant ;
}
style . replace ( " %css-style% " , css ) . replace ( " %style-dir% " , " file:/// " + styleDir . absolutePath ( ) ) ;
}
return style ;
}
2016-03-12 07:25:10 -05:00
QString ChatStyle : : formatMessage ( enumFormatMessage type
, const QString & name
, const QDateTime & timestamp
, const QString & message
, unsigned int flag
, const QColor & backgroundColor /*= Qt::white*/
)
2014-11-22 17:16:22 -05:00
{
2015-12-06 19:21:16 -05:00
bool me = false ;
2014-11-22 17:16:22 -05:00
QDomDocument doc ;
QString styleOptimized ;
QString errorMsg ; int errorLine ; int errorColumn ;
QString messageBody = message ;
2015-12-20 15:31:45 -05:00
me = me | | message . trimmed ( ) . startsWith ( " /me " ) ;
2014-11-22 17:16:22 -05:00
if ( doc . setContent ( messageBody , & errorMsg , & errorLine , & errorColumn ) ) {
QDomElement body = doc . documentElement ( ) ;
if ( ! body . isNull ( ) ) {
messageBody = " " ;
int count = body . childNodes ( ) . count ( ) ;
for ( int curs = 0 ; curs < count ; + + curs ) {
QDomNode it = body . childNodes ( ) . item ( curs ) ;
if ( it . nodeName ( ) . toLower ( ) ! = " style " ) {
2015-12-06 19:21:16 -05:00
//find out if the message starts with /me
if ( it . isText ( ) ) {
me = me | | it . toText ( ) . data ( ) . trimmed ( ) . startsWith ( " /me " ) ;
} else if ( it . isElement ( ) ) {
me = me | | it . toElement ( ) . text ( ) . trimmed ( ) . startsWith ( " /me " ) ;
}
2014-11-22 17:16:22 -05:00
QString str ;
QTextStream stream ( & str ) ;
it . toElement ( ) . save ( stream , - 1 ) ;
//remove Body element, as it was saved with...
if ( ( str . startsWith ( " <body> " ) ) & & ( str . endsWith ( " </body> " ) ) ) {
str . remove ( 0 , 6 ) ;
str . chop ( 7 ) ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
//remove first <span> if it is without attribute
if ( ( str . startsWith ( " <span> " ) ) & & ( str . endsWith ( " </span> " ) ) ) {
str . remove ( 0 , 6 ) ;
str . chop ( 7 ) ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
messageBody + = str ;
} else {
QDomText text = it . firstChild ( ) . toText ( ) ;
styleOptimized = text . data ( ) ;
2014-11-23 14:22:57 -05:00
}
}
}
}
2014-11-22 17:16:22 -05:00
if ( messageBody . isEmpty ( ) ) messageBody = message ;
QString style = m_style [ type ] ;
if ( style . isEmpty ( ) ) {
switch ( type ) {
case FORMATMSG_INCOMING :
style = getStyle ( m_styleDir , m_styleVariant , GETSTYLE_INCOMING ) ;
break ;
case FORMATMSG_OUTGOING :
style = getStyle ( m_styleDir , m_styleVariant , GETSTYLE_OUTGOING ) ;
break ;
case FORMATMSG_HINCOMING :
style = getStyle ( m_styleDir , m_styleVariant , GETSTYLE_HINCOMING ) ;
break ;
case FORMATMSG_HOUTGOING :
style = getStyle ( m_styleDir , m_styleVariant , GETSTYLE_HOUTGOING ) ;
break ;
case FORMATMSG_OOUTGOING :
style = getStyle ( m_styleDir , m_styleVariant , GETSTYLE_OOUTGOING ) ;
break ;
case FORMATMSG_SYSTEM :
style = getStyle ( m_styleDir , m_styleVariant , GETSTYLE_SYSTEM ) ;
break ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
if ( style . isEmpty ( ) ) {
// default style
style = " <table width='100%'><tr><td><b>%name%</b></td><td width='130' align='right'>%time%</td></tr></table><table width='100%'><tr><td>%message%</td></tr></table> " ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
m_style [ type ] = style ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
# ifdef COLORED_NICKNAMES
2016-03-12 07:25:10 -05:00
QColor color ;
QString colorName ;
if ( flag & CHAT_FORMATMSG_SYSTEM ) {
color = Qt : : darkBlue ;
2014-11-23 14:22:57 -05:00
} else {
2016-03-12 07:25:10 -05:00
// Calculate color from the name
uint hash = 0 ;
for ( int i = 0 ; i < name . length ( ) ; + + i ) {
hash = ( ( ( hash < < 1 ) + ( hash > > 14 ) ) ^ ( ( int ) name [ i ] . toLatin1 ( ) ) ) & 0x3fff ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
2016-03-12 07:25:10 -05:00
color . setHsv ( hash , 255 , 150 ) ;
// Always fix colors
qreal desiredContrast = Settings - > valueFromGroup ( " Chat " , " MinimumContrast " , 4.5 ) . toDouble ( ) ;
colorName = color . name ( ) ;
RsHtml : : findBestColor ( colorName , backgroundColor , desiredContrast ) ;
2014-11-23 14:22:57 -05:00
}
2014-11-22 17:16:22 -05:00
# else
2016-03-12 07:25:10 -05:00
Q_UNUSED ( flag ) ;
Q_UNUSED ( backgroundColor ) ;
2014-11-22 17:16:22 -05:00
# endif
2015-10-28 02:18:20 -04:00
QString strName = RsHtml : : plainText ( name ) . prepend ( QString ( " <a name= \" name \" > " ) ) . append ( QString ( " </a> " ) ) ;
QString strDate = DateTime : : formatDate ( timestamp . date ( ) ) . prepend ( QString ( " <a name= \" date \" > " ) ) . append ( QString ( " </a> " ) ) ;
QString strTime = DateTime : : formatTime ( timestamp . time ( ) ) . prepend ( QString ( " <a name= \" time \" > " ) ) . append ( QString ( " </a> " ) ) ;
2015-12-06 19:21:16 -05:00
2015-12-20 15:31:45 -05:00
int bi = name . lastIndexOf ( QRegExp ( " \\ (.* \\ ) " ) ) ; //trim location from the end
QString strShortName = RsHtml : : plainText ( name . left ( bi ) ) . prepend ( QString ( " <a name= \" name \" > " ) ) . append ( QString ( " </a> " ) ) ;
2015-12-06 19:21:16 -05:00
//handle /me
2015-12-20 19:21:04 -05:00
//%nome% and %me% for including formatting conditionally
//meName class for modifying the style of the name in the palce of /me
2015-12-06 19:21:16 -05:00
if ( me ) {
2015-12-20 19:21:04 -05:00
messageBody = messageBody . replace ( messageBody . indexOf ( " /me " ) , 3 , strShortName . prepend ( QString ( " <span class= \" meName \" > " ) ) . append ( QString ( " </span> " ) ) ) ; //replace only the first /me
style = style . remove ( QRegExp ( " %nome%.*%/nome% " ) ) . remove ( " %me% " ) . remove ( " %/me% " ) ;
} else {
style = style . remove ( QRegExp ( " %me%.*%/me% " ) ) . remove ( " %nome% " ) . remove ( " %/nome% " ) ;
2015-12-06 19:21:16 -05:00
}
2015-10-28 02:18:20 -04:00
QString formatMsg = style . replace ( " %name% " , strName )
2015-12-20 19:21:04 -05:00
. replace ( " %shortname% " , strShortName )
2015-10-28 02:18:20 -04:00
. replace ( " %date% " , strDate )
. replace ( " %time% " , strTime )
2014-11-22 17:16:22 -05:00
# ifdef COLORED_NICKNAMES
2016-03-12 07:25:10 -05:00
. replace ( " %color% " , colorName )
2014-11-22 17:16:22 -05:00
# endif
. replace ( " %message% " , messageBody ) ;
if ( ! styleOptimized . isEmpty ( ) ) {
int pos = formatMsg . indexOf ( " </style> " ) ;
if ( pos > = 0 ) {
formatMsg . insert ( pos , styleOptimized ) ;
2014-11-23 14:22:57 -05:00
}
}
2014-11-22 17:16:22 -05:00
return formatMsg ;
}
static bool getStyleInfo ( QString stylePath , QString stylePathRelative , ChatStyleInfo & info )
{
// Initialize info
info = ChatStyleInfo ( ) ;
QFileInfo file ( stylePath , " info.xml " ) ;
QFile xmlFile ( file . filePath ( ) ) ;
if ( xmlFile . open ( QIODevice : : ReadOnly ) = = false ) {
// No info file found
return false ;
}
QXmlStreamReader reader ;
reader . setDevice ( & xmlFile ) ;
while ( reader . atEnd ( ) = = false ) {
reader . readNext ( ) ;
if ( reader . isStartElement ( ) ) {
if ( reader . name ( ) = = " RetroShare_Style " ) {
if ( reader . attributes ( ) . value ( " version " ) = = " 1.0 " ) {
info . stylePath = stylePathRelative ;
continue ;
}
// Not the right format of the xml file;
return false ;
}
if ( info . stylePath . isEmpty ( ) ) {
continue ;
}
if ( reader . name ( ) = = " style " ) {
// read style information
while ( reader . atEnd ( ) = = false ) {
reader . readNext ( ) ;
if ( reader . isEndElement ( ) ) {
if ( reader . name ( ) = = " style " ) {
break ;
}
continue ;
}
if ( reader . isStartElement ( ) ) {
if ( reader . name ( ) = = " name " ) {
info . styleName = reader . readElementText ( ) ;
continue ;
}
if ( reader . name ( ) = = " description " ) {
info . styleDescription = reader . readElementText ( ) ;
continue ;
}
// ingore all other entries
}
}
continue ;
}
if ( reader . name ( ) = = " author " ) {
// read author information
while ( reader . atEnd ( ) = = false ) {
reader . readNext ( ) ;
if ( reader . isEndElement ( ) ) {
if ( reader . name ( ) = = " author " ) {
break ;
}
continue ;
}
if ( reader . isStartElement ( ) ) {
if ( reader . name ( ) = = " name " ) {
info . authorName = reader . readElementText ( ) ;
continue ;
}
if ( reader . name ( ) = = " email " ) {
info . authorEmail = reader . readElementText ( ) ;
continue ;
}
// ingore all other entries
}
}
continue ;
}
// ingore all other entries
}
}
if ( reader . hasError ( ) ) {
return false ;
}
if ( info . stylePath . isEmpty ( ) ) {
return false ;
}
return true ;
}
/*static*/ bool ChatStyle : : getAvailableStyles ( enumStyleType styleType , QList < ChatStyleInfo > & styles )
{
styles . clear ( ) ;
ChatStyleInfo standardInfo ;
QString stylePath ;
switch ( styleType ) {
case TYPE_PUBLIC :
if ( getStyleInfo ( " :/qss/chat/standard/public " , " :/qss/chat/standard/public " , standardInfo ) ) {
standardInfo . styleDescription = tr ( " Standard style for group chat " ) ;
styles . append ( standardInfo ) ;
}
if ( getStyleInfo ( " :/qss/chat/compact/public " , " :/qss/chat/compact/public " , standardInfo ) ) {
standardInfo . styleDescription = tr ( " Compact style for group chat " ) ;
styles . append ( standardInfo ) ;
}
stylePath = " public " ;
break ;
case TYPE_PRIVATE :
if ( getStyleInfo ( " :/qss/chat/standard/private " , " :/qss/chat/standard/private " , standardInfo ) ) {
standardInfo . styleDescription = tr ( " Standard style for private chat " ) ;
styles . append ( standardInfo ) ;
}
if ( getStyleInfo ( " :/qss/chat/compact/private " , " :/qss/chat/compact/private " , standardInfo ) ) {
standardInfo . styleDescription = tr ( " Compact style for private chat " ) ;
styles . append ( standardInfo ) ;
}
stylePath = " private " ;
break ;
case TYPE_HISTORY :
if ( getStyleInfo ( " :/qss/chat/standard/history " , " :/qss/chat/standard/history " , standardInfo ) ) {
standardInfo . styleDescription = tr ( " Standard style for history " ) ;
styles . append ( standardInfo ) ;
}
if ( getStyleInfo ( " :/qss/chat/compact/history " , " :/qss/chat/compact/history " , standardInfo ) ) {
standardInfo . styleDescription = tr ( " Compact style for history " ) ;
styles . append ( standardInfo ) ;
}
stylePath = " history " ;
break ;
case TYPE_UNKNOWN :
default :
return false ;
}
2015-08-27 15:43:18 -04:00
foreach ( QDir baseDir , getBaseDirList ( ) ) {
QDir dir ( baseDir ) ;
if ( dir . cd ( " stylesheets " ) = = false ) {
// no user styles available here
2014-11-22 17:16:22 -05:00
continue ;
}
2015-08-27 15:43:18 -04:00
// get all style directories
QFileInfoList dirList = dir . entryInfoList ( QDir : : AllDirs | QDir : : NoDotAndDotDot , QDir : : Name ) ;
// iterate style directories and get info
for ( QFileInfoList : : iterator it = dirList . begin ( ) ; it ! = dirList . end ( ) ; + + it ) {
QDir styleDir = QDir ( it - > absoluteFilePath ( ) ) ;
if ( styleDir . cd ( stylePath ) = = false ) {
// no user styles available
continue ;
}
ChatStyleInfo info ;
if ( getStyleInfo ( styleDir . absolutePath ( ) , baseDir . relativeFilePath ( styleDir . absolutePath ( ) ) , info ) ) {
styles . append ( info ) ;
}
2014-11-22 17:16:22 -05:00
}
}
return true ;
}
/*static*/ bool ChatStyle : : getAvailableVariants ( const QString & stylePath , QStringList & variants )
{
variants . clear ( ) ;
2017-03-02 16:30:51 -05:00
std : : cerr < < " Getting variants for style: \" " < < stylePath . toStdString ( ) < < " \" " < < std : : endl ;
2014-11-22 17:16:22 -05:00
if ( stylePath . isEmpty ( ) ) {
2017-03-02 16:30:51 -05:00
std : : cerr < < " empty! " < < std : : endl ;
2014-11-22 17:16:22 -05:00
return false ;
}
// application path
QDir dir ( QApplication : : applicationDirPath ( ) ) ;
if ( dir . cd ( stylePath ) = = false ) {
// style not found
2017-03-02 16:30:51 -05:00
std : : cerr < < " no path! " < < std : : endl ;
2014-11-22 17:16:22 -05:00
return false ;
}
if ( dir . cd ( " variants " ) = = false ) {
// no variants available
2017-03-02 16:30:51 -05:00
std : : cerr < < " no variants! " < < std : : endl ;
2014-11-22 17:16:22 -05:00
return true ;
}
// get all variants
QStringList filters ;
filters . append ( " *.css " ) ;
QFileInfoList fileList = dir . entryInfoList ( filters , QDir : : Files , QDir : : Name ) ;
// iterate variants
for ( QFileInfoList : : iterator file = fileList . begin ( ) ; file ! = fileList . end ( ) ; + + file ) {
# ifndef COLORED_NICKNAMES
if ( file - > baseName ( ) . toLower ( ) = = " colored " ) {
continue ;
}
# endif
variants . append ( file - > baseName ( ) ) ;
}
return true ;
}