summaryrefslogtreecommitdiffstats
path: root/kopete/libkopete/private/kopeteemoticons.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/libkopete/private/kopeteemoticons.cpp')
-rw-r--r--kopete/libkopete/private/kopeteemoticons.cpp559
1 files changed, 559 insertions, 0 deletions
diff --git a/kopete/libkopete/private/kopeteemoticons.cpp b/kopete/libkopete/private/kopeteemoticons.cpp
new file mode 100644
index 00000000..87da4cf7
--- /dev/null
+++ b/kopete/libkopete/private/kopeteemoticons.cpp
@@ -0,0 +1,559 @@
+/*
+ kopeteemoticons.cpp - Kopete Preferences Container-Class
+
+ Copyright (c) 2002 by Stefan Gehn <metz AT gehn.net>
+ Copyright (c) 2002-2006 by Olivier Goffart <ogoffart @ kde.org>
+ Copyright (c) 2005 by Engin AYDOGAN <engin@bzzzt.biz>
+
+ Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Lesser General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ *************************************************************************
+*/
+
+#include "kopeteemoticons.h"
+
+#include "kopeteprefs.h"
+
+#include <qdom.h>
+#include <qfile.h>
+#include <qstylesheet.h>
+#include <qimage.h>
+#include <qdatetime.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <kdeversion.h>
+
+#include <set>
+#include <algorithm>
+#include <iterator>
+
+
+/*
+ * Testcases can be found in the kopeteemoticontest app in the tests/ directory.
+ */
+
+
+namespace Kopete {
+
+
+struct Emoticons::Emoticon
+{
+ Emoticon(){}
+ /* sort by longest to shortest matchText */
+ bool operator< (const Emoticon &e){ return matchText.length() > e.matchText.length(); }
+ QString matchText;
+ QString matchTextEscaped;
+ QString picPath;
+ QString picHTMLCode;
+};
+
+/* This is the object we will store each emoticon match in */
+struct Emoticons::EmoticonNode {
+ const Emoticon emoticon;
+ int pos;
+ EmoticonNode() : emoticon(), pos( -1 ) {}
+ EmoticonNode( const Emoticon e, int p ) : emoticon( e ), pos( p ) {}
+};
+
+class Emoticons::Private
+{
+public:
+ QMap<QChar, QValueList<Emoticon> > emoticonMap;
+ QMap<QString, QStringList> emoticonAndPicList;
+
+ /**
+ * The current icon theme from KopetePrefs
+ */
+ QString theme;
+
+
+};
+
+
+Emoticons *Emoticons::s_self = 0L;
+
+Emoticons *Emoticons::self()
+{
+ if( !s_self )
+ s_self = new Emoticons;
+ return s_self;
+}
+
+
+QString Emoticons::parseEmoticons(const QString& message, ParseMode mode ) //static
+{
+ return self()->parse( message, mode );
+}
+
+QValueList<Emoticons::Token> Emoticons::tokenizeEmoticons( const QString& message, ParseMode mode ) // static
+{
+ return self()->tokenize( message, mode );
+}
+
+QValueList<Emoticons::Token> Emoticons::tokenize( const QString& message, uint mode )
+{
+ QValueList<Token> result;
+ if ( !KopetePrefs::prefs()->useEmoticons() )
+ {
+ result.append( Token( Text, message ) );
+ return result;
+ }
+
+ if( ! ( mode & (StrictParse|RelaxedParse) ) )
+ {
+ //if none of theses two mode are selected, use the mode from the config
+ mode |= KopetePrefs::prefs()->emoticonsRequireSpaces() ? StrictParse : RelaxedParse ;
+ }
+
+ /* previous char, in the firs iteration assume that it is space since we want
+ * to let emoticons at the beginning, the very first previous QChar must be a space. */
+ QChar p = ' ';
+ QChar c; /* current char */
+ QChar n; /* next character after a match candidate, if strict this should be QChar::null or space */
+
+ /* This is the EmoticonNode container, it will represent each matched emoticon */
+ QValueList<EmoticonNode> foundEmoticons;
+ QValueList<EmoticonNode>::const_iterator found;
+ /* First-pass, store the matched emoticon locations in foundEmoticons */
+ QValueList<Emoticon> emoticonList;
+ QValueList<Emoticon>::const_iterator it;
+ size_t pos;
+
+ bool inHTMLTag = false;
+ bool inHTMLLink = false;
+ bool inHTMLEntity = false;
+ QString needle; // search for this
+ for ( pos = 0; pos < message.length(); pos++ )
+ {
+ c = message[ pos ];
+
+ if ( mode & SkipHTML ) // Shall we skip HTML ?
+ {
+ if ( !inHTMLTag ) // Are we already in an HTML tag ?
+ {
+ if ( c == '<' ) { // If not check if are going into one
+ inHTMLTag = true; // If we are, change the state to inHTML
+ p = c;
+ continue;
+ }
+ }
+ else // We are already in a HTML tag
+ {
+ if ( c == '>' ) { // Check if it ends
+ inHTMLTag = false; // If so, change the state
+ if ( p == 'a' )
+ {
+ inHTMLLink = false;
+ }
+ }
+ else if ( c == 'a' && p == '<' ) // check if we just entered an achor tag
+ {
+ inHTMLLink = true; // don't put smileys in urls
+ }
+ p = c;
+ continue;
+ }
+
+ if( !inHTMLEntity )
+ { // are we
+ if( c == '&' )
+ {
+ inHTMLEntity = true;
+ }
+ }
+ }
+
+ if ( inHTMLLink ) // i can't think of any situation where a link adress might need emoticons
+ {
+ p = c;
+ continue;
+ }
+
+ if ( (mode & StrictParse) && !p.isSpace() && p != '>')
+ { // '>' may mark the end of an html tag
+ p = c;
+ continue;
+ } /* strict requires space before the emoticon */
+ if ( d->emoticonMap.contains( c ) )
+ {
+ emoticonList = d->emoticonMap[ c ];
+ bool found = false;
+ for ( it = emoticonList.begin(); it != emoticonList.end(); ++it )
+ {
+ // If this is an HTML, then search for the HTML form of the emoticon.
+ // For instance <o) => &gt;o)
+ needle = ( mode & SkipHTML ) ? (*it).matchTextEscaped : (*it).matchText;
+ if ( ( pos == (size_t)message.find( needle, pos ) ) )
+ {
+ if( mode & StrictParse )
+ {
+ /* check if the character after this match is space or end of string*/
+ n = message[ pos + needle.length() ];
+ //<br/> marks the end of a line
+ if( n != '<' && !n.isSpace() && !n.isNull() && n!= '&')
+ break;
+ }
+ /* Perfect match */
+ foundEmoticons.append( EmoticonNode( (*it), pos ) );
+ found = true;
+ /* Skip the matched emoticon's matchText */
+ pos += needle.length() - 1;
+ break;
+ }
+ }
+ if( !found )
+ {
+ if( inHTMLEntity ){
+ // If we are in an HTML entitiy such as &gt;
+ int htmlEnd = message.find( ';', pos );
+ // Search for where it ends
+ if( htmlEnd == -1 )
+ {
+ // Apparently this HTML entity isn't ended, something is wrong, try skip the '&'
+ // and continue
+ kdDebug( 14000 ) << k_funcinfo << "Broken HTML entity, trying to recover." << endl;
+ inHTMLEntity = false;
+ pos++;
+ }
+ else
+ {
+ pos = htmlEnd;
+ inHTMLEntity = false;
+ }
+ }
+ }
+ } /* else no emoticons begin with this character, so don't do anything */
+ p = c;
+ }
+
+ /* if no emoticons found just return the text */
+ if ( foundEmoticons.isEmpty() )
+ {
+ result.append( Token( Text, message ) );
+ return result;
+ }
+
+ /* Second-pass, generate tokens based on the matches */
+
+ pos = 0;
+ int length;
+
+ for ( found = foundEmoticons.begin(); found != foundEmoticons.end(); ++found )
+ {
+ needle = ( mode & SkipHTML ) ? (*found).emoticon.matchTextEscaped : (*found).emoticon.matchText;
+ if ( ( length = ( (*found).pos - pos ) ) )
+ {
+ result.append( Token( Text, message.mid( pos, length ) ) );
+ result.append( Token( Image, (*found).emoticon.matchTextEscaped, (*found).emoticon.picPath, (*found).emoticon.picHTMLCode ) );
+ pos += length + needle.length();
+ }
+ else
+ {
+ result.append( Token( Image, (*found).emoticon.matchTextEscaped, (*found).emoticon.picPath, (*found).emoticon.picHTMLCode ) );
+ pos += needle.length();
+ }
+ }
+
+ if ( message.length() - pos ) // if there is remaining regular text
+ {
+ result.append( Token( Text, message.mid( pos ) ) );
+ }
+
+ return result;
+}
+
+Emoticons::Emoticons( const QString &theme ) : QObject( kapp, "KopeteEmoticons" )
+{
+// kdDebug(14010) << "KopeteEmoticons::KopeteEmoticons" << endl;
+ d=new Private;
+ if(theme.isNull())
+ {
+ initEmoticons();
+ connect( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(initEmoticons()) );
+ }
+ else
+ {
+ initEmoticons( theme );
+ }
+}
+
+
+Emoticons::~Emoticons( )
+{
+ delete d;
+}
+
+
+
+void Emoticons::addIfPossible( const QString& filenameNoExt, const QStringList &emoticons )
+{
+ KStandardDirs *dir = KGlobal::dirs();
+ QString pic;
+
+ //maybe an extension was given, so try to find the exact file
+ pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt );
+
+ if( pic.isNull() )
+ pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".mng" ) );
+ if ( pic.isNull() )
+ pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".png" ) );
+ if ( pic.isNull() )
+ pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".gif" ) );
+
+ if( !pic.isNull() ) // only add if we found one file
+ {
+ QPixmap p;
+ QString result;
+
+ d->emoticonAndPicList.insert( pic, emoticons );
+
+ for ( QStringList::const_iterator it = emoticons.constBegin(), end = emoticons.constEnd();
+ it != end; ++it )
+ {
+ QString matchEscaped=QStyleSheet::escape(*it);
+
+ Emoticon e;
+ e.picPath = pic;
+
+ // We need to include size (width, height attributes) hints in the emoticon HTML code
+ // Unless we do so, ChatMessagePart::slotScrollView does not work properly and causing
+ // HTMLPart not to be scrolled to the very last message.
+ p.load( e.picPath );
+ result = QString::fromLatin1( "<img align=\"center\" src=\"" ) +
+ e.picPath +
+ QString::fromLatin1( "\" title=\"" ) +
+ matchEscaped +
+ QString::fromLatin1( "\" width=\"" ) +
+ QString::number( p.width() ) +
+ QString::fromLatin1( "\" height=\"" ) +
+ QString::number( p.height() ) +
+ QString::fromLatin1( "\" />" );
+
+ e.picHTMLCode = result;
+ e.matchTextEscaped = matchEscaped;
+ e.matchText = *it;
+ d->emoticonMap[ matchEscaped[0] ].append( e );
+ d->emoticonMap[ (*it)[0] ].append( e );
+ }
+ }
+}
+
+void Emoticons::initEmoticons( const QString &theme )
+{
+ if(theme.isNull())
+ {
+ if ( d->theme == KopetePrefs::prefs()->iconTheme() )
+ return;
+
+ d->theme = KopetePrefs::prefs()->iconTheme();
+ }
+ else
+ {
+ d->theme = theme;
+ }
+
+// kdDebug(14010) << k_funcinfo << "Called" << endl;
+ d->emoticonAndPicList.clear();
+ d->emoticonMap.clear();
+
+ QString filename= KGlobal::dirs()->findResource( "emoticons", d->theme + QString::fromLatin1( "/emoticons.xml" ) );
+ if(!filename.isEmpty())
+ return initEmoticon_emoticonsxml( filename );
+ filename= KGlobal::dirs()->findResource( "emoticons", d->theme + QString::fromLatin1( "/icondef.xml" ) );
+ if(!filename.isEmpty())
+ return initEmoticon_JEP0038( filename );
+ kdWarning(14010) << k_funcinfo << "emotiucon XML theme description not found" <<endl;
+}
+
+void Emoticons::initEmoticon_emoticonsxml( const QString & filename)
+{
+ QDomDocument emoticonMap( QString::fromLatin1( "messaging-emoticon-map" ) );
+
+ QFile mapFile( filename );
+ mapFile.open( IO_ReadOnly );
+ emoticonMap.setContent( &mapFile );
+
+ QDomElement list = emoticonMap.documentElement();
+ QDomNode node = list.firstChild();
+ while( !node.isNull() )
+ {
+ QDomElement element = node.toElement();
+ if( !element.isNull() )
+ {
+ if( element.tagName() == QString::fromLatin1( "emoticon" ) )
+ {
+ QString emoticon_file = element.attribute(
+ QString::fromLatin1( "file" ), QString::null );
+ QStringList items;
+
+ QDomNode emoticonNode = node.firstChild();
+ while( !emoticonNode.isNull() )
+ {
+ QDomElement emoticonElement = emoticonNode.toElement();
+ if( !emoticonElement.isNull() )
+ {
+ if( emoticonElement.tagName() == QString::fromLatin1( "string" ) )
+ {
+ items << emoticonElement.text();
+ }
+ else
+ {
+ kdDebug(14010) << k_funcinfo <<
+ "Warning: Unknown element '" << element.tagName() <<
+ "' in emoticon data" << endl;
+ }
+ }
+ emoticonNode = emoticonNode.nextSibling();
+ }
+
+ addIfPossible ( emoticon_file, items );
+ }
+ else
+ {
+ kdDebug(14010) << k_funcinfo << "Warning: Unknown element '" <<
+ element.tagName() << "' in map file" << endl;
+ }
+ }
+ node = node.nextSibling();
+ }
+ mapFile.close();
+ sortEmoticons();
+}
+
+
+void Emoticons::initEmoticon_JEP0038( const QString & filename)
+{
+ QDomDocument emoticonMap( QString::fromLatin1( "icondef" ) );
+
+ QFile mapFile( filename );
+ mapFile.open( IO_ReadOnly );
+ emoticonMap.setContent( &mapFile );
+
+ QDomElement list = emoticonMap.documentElement();
+ QDomNode node = list.firstChild();
+ while( !node.isNull() )
+ {
+ QDomElement element = node.toElement();
+ if( !element.isNull() )
+ {
+ if( element.tagName() == QString::fromLatin1( "icon" ) )
+ {
+ QStringList items;
+ QString emoticon_file;
+
+ QDomNode emoticonNode = node.firstChild();
+ while( !emoticonNode.isNull() )
+ {
+ QDomElement emoticonElement = emoticonNode.toElement();
+ if( !emoticonElement.isNull() )
+ {
+ if( emoticonElement.tagName() == QString::fromLatin1( "text" ) )
+ {
+ //TODO xml:lang
+ items << emoticonElement.text();
+ }
+ else if( emoticonElement.tagName() == QString::fromLatin1( "object" ) && emoticon_file.isEmpty() )
+ {
+ QString mime= emoticonElement.attribute(
+ QString::fromLatin1( "mime" ), QString::fromLatin1("image/*") );
+ if(mime.startsWith(QString::fromLatin1("image/")) && !mime.endsWith(QString::fromLatin1("/svg+xml")))
+ {
+ emoticon_file = emoticonElement.text();
+ }
+ else
+ {
+ kdDebug(14010) << k_funcinfo << "Warning: Unsupported format '" << mime << endl;
+ }
+ }
+ /*else
+ {
+ kdDebug(14010) << k_funcinfo <<
+ "Warning: Unknown element '" << element.tagName() <<
+ "' in emoticon data" << endl;
+ }*/
+ }
+ emoticonNode = emoticonNode.nextSibling();
+ }
+ if( !items.isEmpty() && !emoticon_file.isEmpty() )
+ addIfPossible ( emoticon_file, items );
+ }
+ else
+ {
+ kdDebug(14010) << k_funcinfo << "Warning: Unknown element '" <<
+ element.tagName() << "' in map file" << endl;
+ }
+ }
+ node = node.nextSibling();
+ }
+ mapFile.close();
+ sortEmoticons();
+}
+
+
+void Emoticons::sortEmoticons()
+{
+ /* sort strings in order of longest to shortest to provide convenient input for
+ greedy matching in the tokenizer */
+ QValueList<QChar> keys = d->emoticonMap.keys();
+ for ( QValueList<QChar>::const_iterator it = keys.begin(); it != keys.end(); ++it )
+ {
+ QChar key = (*it);
+ QValueList<Emoticon> keyValues = d->emoticonMap[key];
+ qHeapSort(keyValues.begin(), keyValues.end());
+ d->emoticonMap[key] = keyValues;
+ }
+}
+
+
+
+
+QMap<QString, QStringList> Emoticons::emoticonAndPicList()
+{
+ return d->emoticonAndPicList;
+}
+
+
+QString Emoticons::parse( const QString &message, ParseMode mode )
+{
+ if ( !KopetePrefs::prefs()->useEmoticons() )
+ return message;
+
+ QValueList<Token> tokens = tokenize( message, mode );
+ QValueList<Token>::const_iterator token;
+ QString result;
+ QPixmap p;
+ for ( token = tokens.begin(); token != tokens.end(); ++token )
+ {
+ switch ( (*token).type )
+ {
+ case Text:
+ result += (*token).text;
+ break;
+ case Image:
+ result += (*token).picHTMLCode;
+ kdDebug( 14010 ) << k_funcinfo << "Emoticon html code: " << result << endl;
+ break;
+ default:
+ kdDebug( 14010 ) << k_funcinfo << "Unknown token type. Something's broken." << endl;
+ }
+ }
+ return result;
+}
+
+} //END namesapce Kopete
+
+#include "kopeteemoticons.moc"
+
+
+
+// vim: set noet ts=4 sts=4 sw=4:
+