/* kopeteemoticons.cpp - Kopete Preferences Container-Class Copyright (c) 2002 by Stefan Gehn Copyright (c) 2002-2006 by Olivier Goffart Copyright (c) 2005 by Engin AYDOGAN Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * 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 #include #include #include #include #include #include #include #include #include #include #include /* * 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 > emoticonMap; QMap 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::tokenizeEmoticons( const QString& message, ParseMode mode ) // static { return self()->tokenize( message, mode ); } QValueList Emoticons::tokenize( const QString& message, uint mode ) { QValueList 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 foundEmoticons; QValueList::const_iterator found; /* First-pass, store the matched emoticon locations in foundEmoticons */ QValueList emoticonList; QValueList::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) 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() ]; //
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 > 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( "" ); 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" < keys = d->emoticonMap.keys(); for ( QValueList::const_iterator it = keys.begin(); it != keys.end(); ++it ) { QChar key = (*it); QValueList keyValues = d->emoticonMap[key]; qHeapSort(keyValues.begin(), keyValues.end()); d->emoticonMap[key] = keyValues; } } QMap Emoticons::emoticonAndPicList() { return d->emoticonAndPicList; } QString Emoticons::parse( const QString &message, ParseMode mode ) { if ( !KopetePrefs::prefs()->useEmoticons() ) return message; QValueList tokens = tokenize( message, mode ); QValueList::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: