/***************************************************************************
                          emoticontheme.cpp - holds a collection of emoticons
                             -------------------
    begin                : Tue April 10 2007
    copyright            : (C) 2007 by Valerio Pilo
    email                : valerio@kmess.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "emoticontheme.h"

#include "utils/kmessconfig.h"
#include "utils/kmessshared.h"
#include "kmessdebug.h"

#include <QDir>
#include <QDomDocument>
#include <QFile>
#include <QTextCodec>
#include <QTextDocument>

#include <KLocale>
#include <KMessageBox>
#include <KStandardDirs>


#ifdef KMESSDEBUG_EMOTICONS
  #define KMESSDEBUG_EMOTICON_THEMES
#endif



/**
 * Constructor
 */
EmoticonTheme::EmoticonTheme()
  : QObject()
  , loadingTheme_(false)
  , isCustomTheme_(false)
{
}



/**
 * Copy constructor
 *
 * Also duplicates the list of emoticons: simply copying the source's list would cause our list to be deleted
 * when the source theme is deleted
 *
 * @param other  The theme to duplicate
 */
EmoticonTheme::EmoticonTheme( const EmoticonTheme &other )
  : QObject()
  , loadingTheme_(other.loadingTheme_)
  , isCustomTheme_(other.isCustomTheme_)
  , themePath_(other.themePath_)
{
  // Duplicate one by one the other theme's emoticons
  const QList<Emoticon*>emoticonList( other.getEmoticons() );
  foreach( Emoticon *emoticon, emoticonList )
  {
    emoticon = new Emoticon( *emoticon );
    emoticon->update();
    emoticons_.append( emoticon );
  }

  // The caches cannot be easily copied (due to QHashs being implicitly shared), so regenerate them.
  updateCache();
}



/**
 * Destructor
 */
EmoticonTheme::~EmoticonTheme()
{
  qDeleteAll( emoticons_ );
  emoticons_.clear();
}



/**
 * Create a new emoticon and add it to the theme
 *
 * Finds out by itself if we're adding a custom emoticon or a standard one.
 *
 * @param pictureFile  The file name of the emoticon, without path and without extension (will be guessed)
 * @param shortcuts    A list of text shortcuts which will be translated to the pictureFile image
 */
void EmoticonTheme::addEmoticon( const QString& pictureFile, const QStringList& shortcuts )
{
  Emoticon *emoticon;

  if( isCustomTheme_ )
  {
    emoticon = new Emoticon( pictureFile, shortcuts.first(), themePath_ );
  }
  else
  {
    emoticon = new Emoticon( pictureFile, shortcuts.first() );
  }

  emoticon->setShortcuts( shortcuts );

  emoticons_.append( emoticon );

  // Only update the caches if we're adding a new custom emoticon
  if( ! loadingTheme_ && isCustomTheme_ )
  {
    updateCache();
  }
}



/**
 * Check if the theme contains a certain emoticon
 *
 * @param shortcut    The shortcut text to search for
 */
bool EmoticonTheme::contains( QString shortcut )
{
  foreach( Emoticon *emoticon, emoticons_ )
  {
    if( emoticon->getShortcuts().indexOf( shortcut ) != -1 )
    {
      return true;
    }
  }

  return false;
}



/**
 * Create the theme from nothing
 *
 * Any emoticons currently in the theme will be discarded!
 *
 * @param themeDir  The absolute path where the theme's emoticons.xml definition file is located
 */
bool EmoticonTheme::createTheme( const QString& themeDir )
{
  const QString themeFileName( themeDir + "emoticons.xml" );
  QFile xmlFile( themeFileName );

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Loading emoticons from theme XML: '" << themeFileName << "'";
#endif

  // Try to read the XML
  if( ! xmlFile.open( QIODevice::ReadOnly ) )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Could not open '" << themeFileName << "'";
#endif
    return false;
  }

  // Then try to parse it
  QDomDocument xml;
  QString      xmlError;
  if( ! xml.setContent( xmlFile.readAll(), false, &xmlError ) )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Failure parsing '" << themeFileName << "': " << xmlError;
#endif
    xmlFile.close();
    return false;
  }

  // Clean the current list
  qDeleteAll( emoticons_ );
  emoticons_.clear();

  int i, l;
  QStringList shortcuts;
  QString shortcut;
  const QDomNodeList allEmoticons( xml.elementsByTagName( "emoticon" ) );

  for( i = 0; i < allEmoticons.count(); i++ )
  {
    const QDomElement node( allEmoticons.at( i ).toElement() );
    const QString emoticonFile( node.attribute( "file" ) );

    const QDomNodeList shortcutNodes( node.elementsByTagName( "string" ) );
    shortcuts.clear();

    // Load all the shortcut for the current emoticons
    for( l = 0; l < shortcutNodes.count(); l++ )
    {
      const QDomElement shortcutNode( shortcutNodes.at( l ).toElement() );
      shortcut = shortcutNode.text();
      shortcut.truncate( 7 );
      shortcuts.append( shortcut );
    }

    // If is a valid emoticon, add it to the current theme
    if( ! emoticonFile.isEmpty() && ! shortcuts.isEmpty() )
    {
      addEmoticon( emoticonFile, shortcuts );
    }
  }

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Added " << emoticons_.count() << " emoticons.";
#endif

  return true;
}



// Returns true if a custom emoticon has already been added
bool EmoticonTheme::emoticonIsAdded( QString dataHash )
{
  foreach( Emoticon *emoticon, emoticons_ )
  {
    if( emoticon->getDataHash() == dataHash )
    {
      return true;
    }
  }

  return false;
}



/**
 * Obtain an emoticon by its shortcut
 *
 * @param shortcut Text shortcut of the emoticon to get.
 * @return Pointer to the emoticon. Zero on failure.
 */
const Emoticon *EmoticonTheme::getEmoticon( QString shortcut ) const
{
  foreach( Emoticon *emoticon, emoticons_ )
  {
    if( emoticon->getShortcuts().contains( shortcut ) )
    {
      return emoticon;
    }
  }

  return 0;
}



/**
 * Return the full list of emoticons
 */
const QList<Emoticon*> &EmoticonTheme::getEmoticons() const
{
  return emoticons_;
}



/**
 * Return the picture file names of all emoticons, mapped by their first shortcut code
 */
const QHash<QString,QString>& EmoticonTheme::getFileNames() const
{
  return themeFileNames_;
}



// Return a QHash to map shortcut to data hash
const QHash<QString,QString>& EmoticonTheme::getHashes() const
{
  return hashes_;
}



/**
 * Return the search pattern to find emoticons in an HTML text
 */
const QRegExp& EmoticonTheme::getHtmlPattern() const
{
  return patternHtml_;
}



/**
 * Return the HTML replacement codes for all emoticons
 */
const QHash<QString,QString>& EmoticonTheme::getHtmlReplacements( bool small ) const
{
  if( small )
  {
    return smallHtmlReplacements_;
  }
  else
  {
    return largeHtmlReplacements_;
  }
}



/**
 * Return a QStringList of emoticons
 */
const QStringList& EmoticonTheme::getList() const
{
  return emoticonList_;
}



/**
 * Return the search pattern to find emoticons in a text
 */
const QRegExp& EmoticonTheme::getPattern() const
{
  return patternText_;
}



/**
 * Return one replacement code for the given emoticon
 */
QString EmoticonTheme::getReplacement( const QString &code, bool small ) const
{
  if( small )
  {
    return smallReplacements_[ code ];
  }
  else
  {
    return largeReplacements_[ code ];
  }
}



/**
 * Return the replacement codes for all emoticons
 */
const QHash<QString,QString>& EmoticonTheme::getReplacements( bool small ) const
{
  if( small )
  {
    return smallReplacements_;
  }
  else
  {
    return largeReplacements_;
  }
}



/**
 * Return the full path of the first emoticon of the specified theme
 *
 * This method is used by EmoticonWidget. You use it to get a "quick preview" of how a theme
 * looks like.
 *
 * @param themeDir  The absolute path where the theme's emoticons.xml definition file is located
 * @return          The absolute path of the first emoticon in the theme, or null value on error
 */
QString EmoticonTheme::getThemeIcon( QString themeDir )
{
  // Add a trailing slash if the specified path doesn't have it
  if( themeDir.right( 1 ) != "/" )
  {
    themeDir += "/";
  }

  const QString themeFileName( themeDir + "emoticons.xml" );
  QFile xmlFile( themeFileName );

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Getting first emoticon from theme XML: '" << themeFileName << "'";
#endif

  // Try to read the XML
  if( ! xmlFile.open( QIODevice::ReadOnly ) )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Could not open '" << themeFileName << "'";
#endif
    return QString::null;
  }

  // Then try to parse it
  QDomDocument xml;
  QString      xmlError;
  if( ! xml.setContent( xmlFile.readAll(), false, &xmlError ) )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Failure parsing '" << themeFileName << "': " << xmlError;
#endif
    xmlFile.close();
    return QString::null;
  }

  const QDomNodeList allEmoticons( xml.elementsByTagName( "emoticon" ) );

  for( int i = 0; i < allEmoticons.count(); i++ )
  {
    const QDomElement node( allEmoticons.at( i ).toElement() );
    const QString emoticonFile( node.attribute( "file" ) );
    const Emoticon emoticon( emoticonFile, "", themeDir );

    if( emoticon.isValid() )
    {
        return emoticon.getPicturePath();
    }
  }

  // The theme contained no valid emoticons
  return QString::null;
}



/**
 * Return where the picture files for this theme are located
 */
const QString &EmoticonTheme::getThemePath()
{
  return themePath_;
}



/**
 * Load a theme, by creating it anew or by refreshing the current one
 *
 * @param themeName      The name of the theme, must be the same as the name of the folder which directly contains
 *                       emoticons.xml and the pictures
 * @param isCustomTheme  False if loading a standard theme, true if loading a custom theme
 */
bool EmoticonTheme::loadTheme( QString themeName, bool isCustomTheme )
{
  bool           success = false;

  const QString oldThemePath( themePath_ );
  isCustomTheme_ = isCustomTheme;

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Loading " << themeName << " as " << (isCustomTheme?"custom":"default") << " theme.";
#endif

  // Change the theme name (where it is stored)
  setThemeName( themeName );

  if( oldThemePath == themePath_ )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Theme" << themeName << "was already loaded!";
#endif
    return true;
  }

  // Set the theme loading status, needed by addEmoticon()
  loadingTheme_ = true;

  if( isCustomTheme || emoticons_.isEmpty() )
  {
    // If we're loading a standard theme, but there's none to update, we need to create it from scratch.
    success = createTheme( themePath_ );
  }
  else
  {
    // If loading a standard theme, always update the current one.
    success = updateTheme( themePath_ );
  }

  // Update the search&replace caches
  updateCache();

  // Reset the theme loading status, needed by addEmoticon()
  loadingTheme_ = false;

  return success;
}



/**
 * Delete a custom emoticon from the theme
 *
 * @param shortcut  The shortcut of the emoticon to remove
 */
bool EmoticonTheme::removeEmoticon( QString shortcut )
{
  // Can't remove emoticons from a standard theme
  if( ! isCustomTheme_ )
  {
    return false;
  }

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Removing emoticon" << shortcut << ".";
#endif

  foreach( Emoticon *emoticon, emoticons_ )
  {
    // This is not the emoticon we need to delete
    if( emoticon->getShortcuts().indexOf( shortcut ) == -1 )
    {
      continue;
    }

    // Delete the file
    QFile::remove( emoticon->getPicturePath() );

    // Remove the emoticon from the theme
    delete emoticons_.takeAt( emoticons_.indexOf( emoticon ) );

    // Update the cache
    updateCache();

    return true;
  }

  return false;
}



/**
 * Change the shortcut and the tooltip of a custom emoticon
 *
 * @param oldShortcut  The shortcut of the emoticon to replace
 * @param newShortcut  The new shortcut for that emoticon
 */
bool EmoticonTheme::renameEmoticon( QString oldShortcut, QString newShortcut )
{
  // Can't remove emoticons from a standard theme
  if( ! isCustomTheme_ )
  {
    return false;
  }

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Renaming emoticon" << oldShortcut << "to" << newShortcut << ".";
#endif

  foreach( Emoticon *emoticon, emoticons_ )
  {
    // This is not the emoticon we need to rename
    if( emoticon->getShortcuts().indexOf( oldShortcut ) == -1 )
    {
      continue;
    }

    // Replace all the shortcuts with the new one
    emoticon->setShortcut( newShortcut );

    // Also update its tooltip
    emoticon->setTooltip( newShortcut );

    // Update the cache
    updateCache();

    return true;
  }

  return false;
}



/**
 * Save the current theme to its XML theme definition file
 *
 * See the attached URL to view the specification for the XML emoticon definitions file.
 * We deliberately chose not to save the pictures' extensions.
 *
 * @see http://kopete.kde.org/emoticons/emoticonspec.html
 */
bool EmoticonTheme::saveTheme()
{
  // Standard emoticons sets do not get changed, so there is no need to save them.
  if( ! isCustomTheme_ )
  {
    return true;
  }

  // Create any missing directories.
  QDir dir;
  dir.mkpath( themePath_ );

  // Check if the folder exists
  if( ! dir.exists() )
  {
    // The theme hasn't been created!
    kmWarning() << "Couldn't create the new theme folder '" << themePath_ << "'!";

    return false;
  }

  const QString themeFileName( themePath_ + "emoticons.xml" );
  QFile xmlFile( themeFileName );

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Saving emoticons to theme XML: '" << themeFileName << "'";
#endif


  // Open the destination XML file
  if( ! xmlFile.open( QIODevice::WriteOnly ) )
  {
    kmWarning() << "Save failed - couldn't open file '" << themeFileName << "'.";
    KMessageBox::sorry( 0, i18n("Could not save the emoticon theme. Make sure you have permission to write to the theme folder '%1'.",  themePath_ ) );
    return false;
  }

  QTextCodec *codec = QTextCodec::codecForName("UTF-8");
  QTextStream xmlOutput( &xmlFile );
  xmlOutput.setCodec( codec );

  // Start the XML tree by writing the root tag
  xmlOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl << "<messaging-emoticon-map>" << endl << endl;

  foreach( Emoticon *emoticon, emoticons_ )
  {
    if( ! emoticon->isValid() )
    {
      continue;
    }

    // We only need base names of the emoticons: the specs
    const QString file( QFileInfo( emoticon->getPicturePath() ).baseName() );

    // New emoticon: the file name is encoded as HTML to avoid XML parsing errors
    xmlOutput << "\t<emoticon file=\"" << Qt::escape( file ) << "\">" << endl;

    // Add a <string> for each shortcut which maps to this emoticon,
    const QStringList &codes = emoticon->getShortcuts();
    for( QStringList::ConstIterator it = codes.begin(); it != codes.end(); ++it )
    {
      // Encode the shortcuts as HTML to avoid XML parsing errors
      QString code( *it );
      KMessShared::htmlEscape( code );

      xmlOutput << "\t\t<string>" << code << "</string>" << endl;
    }

    // End of emoticon
    xmlOutput << "\t</emoticon>" << endl << endl;
  }

  // Close the XML tree by closing the root
  xmlOutput << "</messaging-emoticon-map>" << endl;

  // Close the file, we're done
  xmlFile.close();

  return true;
}



/**
 * @brief Change the theme name
 *
 * Alter the name of the theme, which is the name of the directory where the theme will
 * be stored.
 *
 * @param newThemeName  The new name
 */
void EmoticonTheme::setThemeName( const QString &newThemeName )
{
  KStandardDirs *dirs = KGlobal::dirs();

  // Try to find the theme between all possible locations
  if( isCustomTheme_ )
  {
    // Custom themes are located in: <kmess app data folder>/<account@email>/customemoticons
    themePath_ = KMessConfig::instance()->getAccountDirectory( newThemeName ) + "/customemoticons/";
  }
  else
  {
    // Standard themes can be in the KDE global emoticons dir, or in the ~/.kde/share/emoticons folder, and possibly elsewhere
    themePath_ = dirs->findResourceDir( "emoticons", newThemeName + "/emoticons.xml" );

    // findResourceDir() only returns the base path for the file you pass to it.
    themePath_ += newThemeName + "/";
  }
}



/**
 * Rebuild the search&replace caches
 */
void EmoticonTheme::updateCache()
{
  QStringList::const_iterator it;
  QString   emoticonHtmlPattern;
  QString   emoticonTextPattern;
  QFileInfo file;

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Updating the caches.";
#endif

  // Remove all replacements, some emoticons may be no longer valid
  emoticonList_.clear();
  smallReplacements_.clear();
  largeReplacements_.clear();
  smallHtmlReplacements_.clear();
  largeHtmlReplacements_.clear();
  themeFileNames_.clear();
  hashes_.clear();

  foreach( Emoticon *emoticon, emoticons_ )
  {
    if( ! emoticon->isValid() )
    {
#ifdef KMESSDEBUG_EMOTICON_THEMES
      kmDebug() << "Invalid emoticon '" << emoticon->getPicturePath()
                << "' with code: " << emoticon->getShortcut() << endl;
#endif
      continue;
    }

    // Generate the HTML tag which represents the emoticon
    const QString htmlSmall(  emoticon->getHtml( true  ) );
    const QString htmlLarge(  emoticon->getHtml( false ) );
    const QString& dataHash = emoticon->getDataHash();

    // Traverse all shortcuts of this emoticon
    const QStringList &codes = emoticon->getShortcuts();
    foreach( const QString &code, codes )
    {
      // Convert to HTML the main significant HTML control characters
      QString htmlCode( code );
      KMessShared::htmlEscape( htmlCode );

      // Add the current code to the replacement lists..
      emoticonList_ << code;
      hashes_.insert( htmlCode, dataHash );
      smallReplacements_.insert( code, htmlSmall );
      largeReplacements_.insert( code, htmlLarge );
      smallHtmlReplacements_.insert( htmlCode, htmlSmall );
      largeHtmlReplacements_.insert( htmlCode, htmlLarge );

      // ..to the filenames list..
      file.setFile( emoticon->getPicturePath() );
      themeFileNames_.insert( code, file.fileName() );

      // ..and to the emoticon regexp patterns
      if( emoticonTextPattern.length() > 0 )
      {
        // The code is already escaped
        emoticonHtmlPattern += "|";
        emoticonTextPattern += "|";
      }

      emoticonHtmlPattern += QRegExp::escape( htmlCode );
      emoticonTextPattern += QRegExp::escape( code );
    }
  }

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Cache contains " << largeReplacements_.count() << " emoticon shortcuts.";
#endif

  // Cache the patterns.
  if( emoticonHtmlPattern.isEmpty() || emoticonTextPattern.isEmpty() )
  {
    patternHtml_.setPattern( "\0" );
    patternText_.setPattern( "\0" );
  }
  else
  {
    patternHtml_.setPattern( emoticonHtmlPattern );
    patternText_.setPattern( emoticonTextPattern );
  }

  // Update the emoticon titles (only for standard themes)
  if( ! isCustomTheme_ )
  {
    updateTitles();
  }

  emit updated();
}



/**
 * Update a standard theme with names for the standard MSN emoticons
 */
void EmoticonTheme::updateTitles()
{
  // Preset titles are only valid for standard themes
  if( isCustomTheme_ )
  {
    return;
  }

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Updating standard emoticons names...";
#endif

  QHash<QString,QString> emoticonTitles;

  emoticonTitles[ "smile"          ] = i18n( "Smile"                   );
  emoticonTitles[ "wink"           ] = i18n( "Wink"                    );
  emoticonTitles[ "tongue"         ] = i18n( "Tongue out"              );
  emoticonTitles[ "teeth"          ] = i18n( "Big smile"               );
  emoticonTitles[ "sad"            ] = i18n( "Sad"                     );
  emoticonTitles[ "cry"            ] = i18n( "Crying"                  );
  emoticonTitles[ "angry"          ] = i18n( "Angry"                   );
  emoticonTitles[ "confused"       ] = i18n( "Confused"                );
  emoticonTitles[ "embarrassed"    ] = i18n( "Embarrassed"             );
  emoticonTitles[ "ugly"           ] = i18n( "Disappointed"            );
  emoticonTitles[ "shade"          ] = i18n( "Hot"                     );
  emoticonTitles[ "baringteeth"    ] = i18n( "Baring teeth"            );
  emoticonTitles[ "nerd"           ] = i18n( "Nerd"                    );
  emoticonTitles[ "sick"           ] = i18n( "Sick"                    );
  emoticonTitles[ "omg"            ] = i18n( "Surprised"               );
  emoticonTitles[ "party"          ] = i18n( "Party"                   );
  emoticonTitles[ "sleepy"         ] = i18n( "Sleepy"                  );
  emoticonTitles[ "thinking"       ] = i18n( "Thinking"                );
  emoticonTitles[ "sshh"           ] = i18n( "Don't tell anyone"       );
  emoticonTitles[ "secret"         ] = i18n( "Secret telling"          );
  emoticonTitles[ "eyeroll"        ] = i18n( "Eye-rolling"             );
  emoticonTitles[ "sarcastic"      ] = i18n( "Sarcastic"               );
  emoticonTitles[ "huh"            ] = i18n( "I don't know"            );
  emoticonTitles[ "brb"            ] = i18n( "Be right back"           );
  emoticonTitles[ "angel"          ] = i18n( "Angel"                   );
  emoticonTitles[ "dude_hug"       ] = i18n( "Left hug"                );
  emoticonTitles[ "boy"            ] = i18n( "Boy"                     );
  emoticonTitles[ "love"           ] = i18n( "Red heart"               );
  emoticonTitles[ "rose"           ] = i18n( "Red rose"                );
  emoticonTitles[ "thumbs_up"      ] = i18n( "Thumbs up"               );
  emoticonTitles[ "dog"            ] = i18n( "Dog face"                );
  emoticonTitles[ "sun"            ] = i18n( "Sun"                     );
  emoticonTitles[ "devil"          ] = i18n( "Devil"                   );
  emoticonTitles[ "girl_hug"       ] = i18n( "Right hug"               );
  emoticonTitles[ "girl"           ] = i18n( "Girl"                    );
  emoticonTitles[ "unlove"         ] = i18n( "Broken heart"            );
  emoticonTitles[ "wilted_rose"    ] = i18n( "Wilted rose"             );
  emoticonTitles[ "thumbs_down"    ] = i18n( "Thumbs down"             );
  emoticonTitles[ "cat"            ] = i18n( "Cat face"                );
  emoticonTitles[ "moon"           ] = i18n( "Sleeping half-moon"      );
  emoticonTitles[ "kiss"           ] = i18n( "Red lips"                );
  emoticonTitles[ "highfive"       ] = i18n( "Clapping"                );
  emoticonTitles[ "fingerscrossed" ] = i18n( "Crossed fingers"         );
  emoticonTitles[ "automobile"     ] = i18n( "Auto"                    );
  emoticonTitles[ "airplane"       ] = i18n( "Airplane"                );
  emoticonTitles[ "turtle"         ] = i18n( "Turtle"                  );
  emoticonTitles[ "snail"          ] = i18n( "Snail"                   );
  emoticonTitles[ "sheep"          ] = i18n( "Sheep"                   );
  emoticonTitles[ "goat"           ] = i18n( "Goat"                    );
  emoticonTitles[ "bat"            ] = i18n( "Vampire bat"             );
  emoticonTitles[ "pizza"          ] = i18n( "Pizza"                   );
  emoticonTitles[ "beer"           ] = i18n( "Beer mug"                );
  emoticonTitles[ "cocktail"       ] = i18n( "Martini glass"           );
  emoticonTitles[ "cup"            ] = i18n( "Coffee cup"              );
  emoticonTitles[ "cake"           ] = i18n( "Birthday cake"           );
  emoticonTitles[ "plate"          ] = i18n( "Plate"                   );
  emoticonTitles[ "bowl"           ] = i18n( "Bowl"                    );
  emoticonTitles[ "star"           ] = i18n( "Star"                    );
  emoticonTitles[ "rainbow"        ] = i18n( "Rainbow"                 );
  emoticonTitles[ "storm"          ] = i18n( "Stormy cloud"            );
  emoticonTitles[ "lightning"      ] = i18n( "Lightning"               );
  emoticonTitles[ "umbrella"       ] = i18n( "Umbrella"                );
  emoticonTitles[ "island"         ] = i18n( "Island with a palm tree" );
  emoticonTitles[ "phone"          ] = i18n( "Telephone receiver"      );
  emoticonTitles[ "mobilephone"    ] = i18n( "Mobile Phone"            );
  emoticonTitles[ "envelope"       ] = i18n( "Email"                   );
  emoticonTitles[ "clock"          ] = i18n( "Clock"                   );
  emoticonTitles[ "camera"         ] = i18n( "Camera"                  );
  emoticonTitles[ "film"           ] = i18n( "Filmstrip"               );
  emoticonTitles[ "note"           ] = i18n( "Note"                    );
  emoticonTitles[ "handcuffs"      ] = i18n( "Handcuffs"               );
  emoticonTitles[ "money"          ] = i18n( "Money"                   );
  emoticonTitles[ "lightbulb"      ] = i18n( "Light bulb"              );
  emoticonTitles[ "cigarette"      ] = i18n( "Cigarette"               );
  emoticonTitles[ "soccer"         ] = i18n( "Football ball"           );
  emoticonTitles[ "present"        ] = i18n( "Gift with a bow"         );
  emoticonTitles[ "gameconsole"    ] = i18n( "X-Box"                   );
  emoticonTitles[ "computer"       ] = i18n( "Computer"                );
  emoticonTitles[ "messenger"      ] = i18n( "KMess icon"              );

  foreach( Emoticon *emoticon, emoticons_ )
  {
    const QString& name = emoticon->getPictureName();
    if( ! emoticonTitles.value( name, QString() ).isEmpty() )
    {
      emoticon->setTooltip( emoticonTitles[ name ] );
    }
  }
}



/**
 * Update the currently loaded theme with new images
 *
 * The existing emoticons are not deleted: every emoticon in the new theme which matches a
 * shortcut in the current one will have the corresponding image updated.
 *
 * @param themeDir  The absolute path where the theme's emoticons.xml definition file is located
 */
bool EmoticonTheme::updateTheme( const QString& themeDir )
{
  const QString themeFileName( themeDir + "emoticons.xml" );
  QFile xmlFile( themeFileName );

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Updating emoticons from theme XML: '" << themeFileName << "'";
#endif


  // Try to read the XML
  if( ! xmlFile.open( QIODevice::ReadOnly ) )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Could not open '" << themeFileName << "'";
#endif
    return false;
  }

  // Try to parse XML
  QDomDocument xml;
  QString      xmlError;
  if( ! xml.setContent( xmlFile.readAll(), false, &xmlError ) )
  {
#ifdef KMESSDEBUG_EMOTICON_THEMES
    kmDebug() << "Failure parsing '" << themeFileName << "': " << xmlError;
#endif

    xmlFile.close();
    return false;
  }


  // Load the codes in a hash for efficient lookup
  QHash<QString,QString> themeFiles;

  int i, l;
  QString shortcut;
  const QDomNodeList allEmoticons( xml.elementsByTagName( "emoticon" ) );
  for( i = 0; i < allEmoticons.count(); i++ )
  {
    const QDomElement node( allEmoticons.at( i ).toElement() );
    const QString emoticonFile( node.attribute( "file" ) );

    const QDomNodeList shortcutNodes( node.elementsByTagName( "string" ) );

    for( l = 0; l < shortcutNodes.count(); l++ )
    {
      const QDomElement shortcutNode( shortcutNodes.at( l ).toElement() );
      shortcut = shortcutNode.text();
      shortcut.truncate( 7 );
      themeFiles.insert( shortcut, emoticonFile );
    }
  }

#ifdef KMESSDEBUG_EMOTICON_THEMES
  kmDebug() << "Updated " << themeFiles.count() << " emoticons, setting them up.";
#endif

  // Loop through all current emoticons.
  foreach( Emoticon *emoticon, emoticons_ )
  {
    // Loop through all codes of the emoticon
    bool  codeFound = false;
    const QStringList &codes = emoticon->getShortcuts();

    for( QStringList::ConstIterator it = codes.begin(); it != codes.end(); ++it )
    {
      const QString &code = *it;
      // If the emoticon uses that code, set it.
      if( ! themeFiles.value( code, QString() ).isEmpty() )
      {
        emoticon->setPictureName( themeFiles[code] );    // already calls update()
        codeFound = true;
        break;
      }
    }

    // If the code was not found, the file still needs to be updated manually.
    if( ! codeFound )
    {
      emoticon->update();
    }
  }

  return true;
}



#include "emoticontheme.moc"
