/*
 * theme.h
 *
 * Copyright (c) 1998 Stefan Taferner <taferner@kde.org>
 *
 * Requires the Qt widget libraries, available at no cost at
 * http://www.troll.no/
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>

#include <qdir.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qfileinfo.h>
#include <kapplication.h>
#include <qpixmap.h>
#include <qbitmap.h>
#include <qpainter.h>
#include <qwindowdefs.h>
#include <qimage.h>
#include <qregexp.h>
#include <dcopclient.h>

#include <kconfigbackend.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kconfig.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kdesktopfile.h>
#include <kicontheme.h>
#include <kipc.h>
#include <kdebug.h>
#include <kprocess.h>

#include "theme.h"

#include <kio/netaccess.h>

#include <X11/Xlib.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>

extern int dropError(Display *, XErrorEvent *);

//-----------------------------------------------------------------------------
Theme::Theme()
{
  int len;

  instOverwrite = false;
  mConfig = 0;
  mConfigDir = KGlobal::dirs()->saveLocation("config");
  len = mConfigDir.length();
  if (len > 0 && mConfigDir[len-1] != '/') mConfigDir += '/';
  mKwmCount = 0;

  mMappings = NULL;
  mValid = false;
  loadMappings();

  loadSettings();
}


//-----------------------------------------------------------------------------
Theme::~Theme()
{
  saveSettings();
  delete mMappings;
}


//-----------------------------------------------------------------------------
void Theme::loadSettings(void)
{
  KConfig* cfg = kapp->config();

  cfg->setGroup("Install");
  mRestartCmd = cfg->readPathEntry("restart-cmd",
			       "kill `pidof %s`; %s >/dev/null 2>&1 &");
}


//-----------------------------------------------------------------------------
void Theme::saveSettings(void)
{
}

//-----------------------------------------------------------------------------
const QString Theme::workDir(void)
{
  static QString *str = 0;
  if (!str)
    str = new QString(locateLocal("data", "kthememgr/Work/"));
  return *str;
}

const QString Theme::baseDir()
{
  static QString *str = 0;
  if (!str)
  {
     str = new QString(KGlobal::dirs()->saveLocation("config"));
     str->truncate(str->length()-7);
  }
  return *str;
}

bool Theme::checkExtension(const QString &file)
{
  return ((file.right(4) == ".tgz") ||
          (file.right(4) == ".zip") ||
          (file.right(7) == ".tar.gz") ||
          (file.right(7) == ".ktheme"));
}

QString Theme::removeExtension(const QString &file)
{
  QString result = file;
  if (file.right(4) == ".tgz")
     result.truncate(file.length()-4);
  else if (file.right(4) == ".zip")
     result.truncate(file.length()-4);
  else if (file.right(7) == ".tar.gz")
     result.truncate(file.length()-7);
  else if (file.right(7) == ".ktheme")
     result.truncate(file.length()-7);

  return result;
}

QString Theme::defaultExtension()
{
   return QString::fromLatin1(".ktheme");
}

QString Theme::allExtensions()
{
   return i18n("*.tgz *.zip *.tar.gz *.ktheme|Theme files");
}

//-----------------------------------------------------------------------------
void Theme::loadMappings()
{
  QFile file;

  file.setName(locate("data", "kthememgr/theme.mappings"));
  if (!file.exists())
  {
     kdFatal() << "Mappings file theme.mappings not found." << endl;
  }

  delete mMappings;
  mMappings = new KSimpleConfig(file.name(), true);
}


//-----------------------------------------------------------------------------
void Theme::cleanupWorkDir(void)
{
  // cleanup work directory
  if (!workDir().isEmpty()) {
    KProcess p;
    p << "/bin/rm" << "-rf" << workDir();
    if(!p.start(KProcess::Block) || !p.normalExit() || p.exitStatus())
      kdWarning() << "Error during cleanup of work directory: " << workDir() << endl;
  }
}

void Theme::findThemerc(const QString &path, const QStringList &list)
{
  for(QStringList::ConstIterator it = list.begin();
      it != list.end(); ++it)
  {
     QString filename = (*it).lower();
     if (filename.right(8) == ".themerc")
     {
        mThemeType = Theme_KDE;
        mThemercFile = path + *it;
        break;
     }
     if (filename.right(6) == ".theme")
     {
        mThemeType = Theme_Windows;
        mThemercFile = path + *it;
        break;
     }
  }
}

//-----------------------------------------------------------------------------
bool Theme::load(const QString &aPath, QString &error)
{
  QString cmd, str;
  QFileInfo finfo(aPath);
  int rc, i;

  assert(!aPath.isEmpty());

  delete mConfig; mConfig = 0;
  cleanupWorkDir();
  mValid = false;

  mFileName = aPath;
  i = mFileName.findRev('/');
  if (i >= 0)
     mFileName = mFileName.mid(i+1);
  if ( !QDir(workDir()).exists())
  {
      KProcess p;
      p << "/bin/mkdir" << workDir();
      if(!p.start(KProcess::Block) || !p.normalExit() || p.exitStatus())
          kdWarning() << "Error during creating of work directory: " << workDir() << endl;

  }
  if (finfo.isDir())
  {
    // The theme given is a directory. Copy files over into work dir.

    i = aPath.findRev('/');
    if (i >= 0) str = workDir() + aPath.mid(i);
    else str = workDir();

    KProcess p;
    p << "/bin/cp" << "-r" << aPath << str;
    if(!p.start(KProcess::Block) || !p.normalExit() || p.exitStatus()) {
      error = i18n("Theme contents could not be copied from\n%1\ninto\n%2")
		.arg(aPath).arg(str);
      return false;
    }
  }
  else if (aPath.right(4) == ".zip")
  {
    // The theme given is a zip archive. Unpack the archive.
    cmd = QString::fromLatin1("mkdir -p ") + KProcess::quote(workDir()) +
        " && " + QString::fromLatin1("cd ") + KProcess::quote(workDir()) + 
        " && unzip -qq " + KProcess::quote(aPath);
    kdDebug() << cmd << endl;
    rc = system(QFile::encodeName(cmd).data());
    if (rc)
    {
      error = i18n("Theme contents could not be extracted from\n%1\ninto\n%2")
                .arg(aPath).arg(workDir());
      return false;
    }
  }
  else
  {
    // The theme given is a tar package. Unpack theme package.
    cmd = QString::fromLatin1("mkdir -p ") + KProcess::quote(workDir()) + 
        " && " + QString::fromLatin1("cd ") + KProcess::quote(workDir()) + 
        " && gzip -c -d " + KProcess::quote(aPath) + " | tar xf -";
    kdDebug() << cmd << endl;
    rc = system(QFile::encodeName(cmd).data());
    if (rc)
    {
      error = i18n("Theme contents could not be extracted from\n%1\ninto\n%2")
                .arg(aPath).arg(workDir());
      return false;
    }
  }

  // Let's see if the theme is stored in a subdirectory.
  mThemercFile = QString::null;
  mThemePath = workDir();
  QDir dir(mThemePath, QString::null, QDir::Name, QDir::Files|QDir::Dirs);
  QStringList list = dir.entryList();
  findThemerc(mThemePath, list);

  if (mThemercFile.isEmpty())
  {
    bool hasDir = false;
    for(QStringList::ConstIterator it = list.begin();
         it != list.end(); ++it)
    {
       if ((*it)[0] == '.') continue;
       finfo.setFile(mThemePath + *it);
       if (finfo.isDir())
       {
          mThemePath += *it + '/';
          hasDir = true;
          break;
       }
    }

    if (hasDir)
    {
       dir.setPath(mThemePath);
       list = dir.entryList();
       findThemerc(mThemePath, list);
    }
  }
  mFileList = list;
  if (mThemercFile.isEmpty())
  {
    error = i18n("Theme does not contain a .themerc nor a .theme file.");
    return false;
  }

  // Search for the preview image
  dir.setNameFilter("*.preview.*");
  mPreviewFile = dir[0];
  mPreviewFile = mThemePath+mPreviewFile;

  if (mThemeType == Theme_Windows)
  {
    // Convert '\' to '/'.
    // KSimpleConfig uses '\' for escaping, so we have to do that here!
    QFile file1(mThemercFile);
    QFile file2(mThemercFile+"_rc");
    if (file1.open(IO_ReadOnly) && file2.open(IO_WriteOnly))
    {
       mThemercFile += "_rc";
       QTextStream stream1(&file1);
       QTextStream stream2(&file2);
       QRegExp reg("\\\\");
       while(!stream1.atEnd())
       {
          QString line(stream1.readLine());
          line.replace(reg, "/");
          stream2 << line << endl;
       }
    }
  }

  // read theme config file
  mConfig = new KSimpleConfig(mThemercFile);
  loadGroupGeneral();

  mValid = true;
  emit changed();
  return true;
}


//-----------------------------------------------------------------------------
bool Theme::save(const QString &aPath)
{
  if (!mValid) return false;

  emit apply();

  mConfig->sync();

  QString path = aPath;
  if (!checkExtension(path))
  {
    path += defaultExtension();
  }

  QString cmd = QString::fromLatin1("cd ") + KProcess::quote(workDir()) + ";tar cf - *|gzip -c >" + KProcess::quote(path);
  kdDebug() << cmd << endl;
  int rc = system(QFile::encodeName(cmd).data());
  if (rc) kdDebug() << "Failed to save theme to " << aPath << " with command " << cmd << endl;

  return (rc==0);
}


//-----------------------------------------------------------------------------
void Theme::removeFile(const QString& aName, const QString &aDirName)
{
  if (aName.isEmpty()) return;

  if (aName[0] == '/' || aDirName.isEmpty())
    QFile::remove( aName );
  else if (aDirName[aDirName.length()-1] == '/')
    QFile::remove( aDirName + aName );
  else QFile::remove( (aDirName + '/' + aName) );
}

QString Theme::findFile(const QString &aSrc)
{
  QString src = mThemePath + aSrc;
  QFileInfo finfo(src);
  if (!finfo.exists())
  {
    kdDebug() << "File " << src << " not found." << endl;
    src = aSrc;
    int i = src.findRev('/');
    if (i == -1)
    {
       kdDebug() << "File " << aSrc << " is not in theme package." << endl;
       return QString::null;
    }
    src = src.mid(i+1).lower();
    for(QStringList::ConstIterator it = mFileList.begin();
        it != mFileList.end(); ++it)
    {
       if (src == (*it).lower())
       {
          src = mThemePath + *it;
          finfo.setFile(src);
          break;
       }
    }
    if (!finfo.exists())
    {
       kdDebug() << "File " << aSrc << " is not in theme package." << endl;
       return QString::null;
    }
  }

  if (finfo.isDir())
  {
    kdDebug() << src << " is a direcotry instead of a file." << endl;
    return QString::null;
  }
  return src;
}


//-----------------------------------------------------------------------------
bool Theme::installFile(const QString& aSrc, const QString& aDest)
{
  QString cmd, dest;
  QString src(aSrc);
  int len, i;
  char buf[32768];
  bool backupMade = false;

  if (src.isEmpty()) return true;

  assert(aDest[0] == '/');
  dest = aDest;

  src = findFile(src);
  if (src.isEmpty())
     return false;


  QFileInfo finfo(dest);
  if (finfo.isDir())  // destination is a directory
  {
    len = dest.length();
    if (dest[len-1]=='/') dest[len-1] = '\0';
    i = src.findRev('/');
    dest = dest + '/' + src.mid(i+1);
    finfo.setFile(dest);
  }

  if (!instOverwrite && finfo.exists()) // make backup copy
  {
    QFile::remove( dest+'~' );
    rename(dest.local8Bit(), (dest+'~').local8Bit());
    backupMade = true;
  }

  QFile srcFile(src);
  if (!srcFile.open(IO_ReadOnly))
  {
    kdWarning() << "Cannot open file " << src << " for reading" << endl;
    return false;
  }

  QFile destFile(dest);
  if (!destFile.open(IO_WriteOnly))
  {
    kdWarning() << "Cannot open file " << dest << " for writing" << endl;
    return false;
  }

  while (!srcFile.atEnd())
  {
    len = srcFile.readBlock(buf, 32768);
    if (len <= 0) break;
    if (destFile.writeBlock(buf, len) != len)
    {
      kdWarning() <<"Write error to " << dest << ": " << strerror(errno) << endl;
      return false;
    }
  }

  srcFile.close();
  destFile.close();

  addInstFile(dest);
//  kdDebug() << "Installed " << src << " to " << dest << ". Backup: backupMade" << endl;

  return true;
}

//-----------------------------------------------------------------------------
bool Theme::installDirectory(const QString& aSrc, const QString& aDest)
{
  bool backupMade = false; // Not used ??

  if (aSrc.isEmpty()) return true;

  assert(aDest[0] == '/');
  QString dest = aDest;

  QString src = mThemePath + aSrc;

  QFileInfo finfo(src);
  if (!finfo.exists())
  {
    kdDebug() << "Directory " << aSrc << " is not in theme package." << endl;
    return false;
  }
  if (!finfo.isDir())
  {
    kdDebug() << aSrc << " is not a directory." << endl;
    return false;
  }

  if (finfo.exists()) // delete and/or make backup copy
  {
    if (instOverwrite)
    {
       KURL url;
       url.setPath(dest);
       KIO::NetAccess::del(url);
    }
    else
    {
       KURL url;
       url.setPath(dest+'~');
       KIO::NetAccess::del(url);
       rename(QFile::encodeName(dest).data(), QFile::encodeName(dest+'~').data());
       backupMade = true;
    }
  }

  KURL url1;
  KURL url2;
  url1.setPath(src);
  url2.setPath(dest);

  KIO::NetAccess::dircopy(url1, url2);

  addInstFile(dest);
//  kdDebug() << "Installed " << src << " to " << dest << ". Backup: backupMade" << endl;

  return true;
}


//-----------------------------------------------------------------------------
int Theme::installGroup(const char* aGroupName)
{
    QString value, oldValue, cfgFile, cfgGroup, appDir, group;
    QString oldCfgFile, key, cfgKey, cfgValue, themeValue, instCmd;
    QString preInstCmd;
    bool absPath = false;
    KSimpleConfig* cfg = NULL;
    int len, i, installed = 0;
    const char* missing = 0;

    group = aGroupName;
    if (mThemeType == Theme_Windows)
    {
        if (group == "Colors")
            group = "Control Panel/Colors";
        else if (group == "Display")
            group = "Control Panel/Desktop";
        else if (group == "Sounds")
            group = "AppEvents/Schemes/Apps/.Default/Minimize/.Current";
    }
    mConfig->setGroup(group);

    if (!instOverwrite)
        uninstallFiles(aGroupName);
    else
        readInstFileList(aGroupName);

    while (!group.isEmpty())
    {
        mMappings->setGroup(group);

        // Read config settings
        value = mMappings->readEntry("ConfigThemeGroup");
        if (!value.isEmpty())
            mConfig->setGroup(value);

        value = mMappings->readPathEntry("ConfigFile");
        if (!value.isEmpty())
        {
            cfgFile = value;
            if (cfgFile == "KDERC")
                cfgFile = QDir::homeDirPath() + "/.kderc";
            else if (cfgFile[0] != '/')
                cfgFile = mConfigDir + cfgFile;
        }

        value = mMappings->readEntry("ConfigGroup");
        if (!value.isEmpty())
            cfgGroup = value;

        value = mMappings->readPathEntry("ConfigAppDir");
        if (!value.isEmpty() && (value[0] != '/'))
        {
            appDir = value;
            appDir = baseDir() + appDir;

            len = appDir.length();
            if (len > 0 && appDir[len-1]!='/')
                appDir += '/';
        }

        absPath = mMappings->readBoolEntry("ConfigAbsolutePaths", absPath);
        QString emptyValue = mMappings->readEntry("ConfigEmpty");

        value = mMappings->readEntry("ConfigActivateCmd");
        if (!value.isEmpty() && (mCmdList.findIndex(value) < 0))
            mCmdList.append(value);
        instCmd = mMappings->readPathEntry("ConfigInstallCmd", instCmd).stripWhiteSpace();
        preInstCmd = mMappings->readPathEntry("ConfigPreInstallCmd", preInstCmd).stripWhiteSpace();

        // Some checks
        if (cfgFile.isEmpty()) missing = "ConfigFile";
        if (cfgGroup.isEmpty()) missing = "ConfigGroup";
        if (missing)
        {
            kdWarning() << "Internal error in theme mappings (file theme.mappings) in group " << group << ":" << endl
                        << "Entry `" << missing << "' is missing or has no value." << endl;
            break;
        }

        // Open config file and sync/close old one
        if (oldCfgFile != cfgFile)
        {
            if (cfg)
            {
                cfg->sync();
                delete cfg;
            }
            cfg = new KSimpleConfig(cfgFile);
            oldCfgFile = cfgFile;
        }

        // Set group in config file
        cfg->setGroup(cfgGroup);

        // Execute pre-install command (if given)
        if (!preInstCmd.isEmpty()) preInstallCmd(cfg, preInstCmd);

        // Process all mapping entries for the group
        QMap<QString, QString> aMap = mMappings->entryMap(group);
        QMap<QString, QString>::Iterator aIt(aMap.begin());

        for (; aIt != aMap.end(); ++aIt) {
            key = aIt.key();
            if ( key.startsWith("Config") ) continue;
            value = (*aIt).stripWhiteSpace();
            len = value.length();
            bool bInstallFile = false;
            bool bInstallDir = false;
            if (len>0 && value[len-1]=='!')
                value.truncate(len - 1);
            else if (len>0 && value[len-1]=='*')
            {
                value.truncate(len - 1);
                bInstallDir = true;
            }
            else
                bInstallFile = true;

            // parse mapping
            i = value.find(':');
            if (i >= 0)
            {
                cfgKey = value.left(i);
                cfgValue = value.mid(i+1);
            }
            else
            {
                cfgKey = value;
                cfgValue = QString::null;
            }
            if (cfgKey.isEmpty())
                cfgKey = key;

            if (bInstallFile || bInstallDir)
            {
                oldValue = cfg->readEntry(cfgKey);
                if (!oldValue.isEmpty() && oldValue==emptyValue)
                    oldValue = QString::null;
            }
            else
                oldValue = QString::null;

            themeValue = mConfig->readEntry(key);
            if (group.left(20) == "Control Panel/Colors")
                themeValue.replace(QRegExp("\\s"), QString::fromLatin1(","));
            else if (group.left(21) == "Control Panel/Desktop")
            {
                if (key == "WallpaperStyle")
                    themeValue = "Scaled";
            }
            if (bInstallFile && (mThemeType == Theme_Windows))
                themeValue.replace(QRegExp("%.+%"), QString::null);

            if (cfgValue.isEmpty())
                cfgValue = themeValue;

            // Install file
            if (bInstallFile)
            {
                // Strip leading path
                i = cfgValue.findRev('/');
                if (i != -1)
                    cfgValue = cfgValue.mid(i+1);
                if (!themeValue.isEmpty())
                {
                    KStandardDirs::makeDir(appDir);
                    if (installFile(themeValue, appDir + cfgValue))
                        installed++;
                    else
                        bInstallFile = false;
                }
            }
            // Install dir
            if (bInstallDir)
            {
                if (!themeValue.isEmpty())
                {
                    KStandardDirs::makeDir(appDir);
                    if (installDirectory(themeValue, appDir + cfgValue))
                        installed++;
                    else bInstallDir = false;
                }
            }

            bool bDeleteKey = false;
            // Determine config value
            if (cfgValue.isEmpty())
            {
                cfgValue = emptyValue;
                if (cfgValue.isEmpty())
                    bDeleteKey = true;
            }
            else if ((bInstallFile || bInstallDir) && absPath)
                cfgValue = appDir + cfgValue;

            // Set config entry
            if (bDeleteKey)
                cfg->deleteEntry(cfgKey, false);
            else
                cfg->writeEntry(cfgKey, cfgValue);
        }

        if (!instCmd.isEmpty())
            installCmd(cfg, instCmd, installed);
        group = mMappings->readEntry("ConfigNextGroup");
    }

    if (cfg)
    {
        cfg->sync();
        delete cfg;
    }

    writeInstFileList(aGroupName);
    return installed;
}


//-----------------------------------------------------------------------------
void Theme::preInstallCmd(KSimpleConfig* aCfg, const QString& aCmd)
{
  QString grp = aCfg->group();
  QString value, cmd;

  cmd = aCmd.stripWhiteSpace();

  if (cmd == "stretchBorders")
  {
    value = mConfig->readEntry("ShapePixmapBottom");
    if (!value.isEmpty()) stretchPixmap(mThemePath + value, false);
    value = mConfig->readEntry("ShapePixmapTop");
    if (!value.isEmpty()) stretchPixmap(mThemePath + value, false);
    value = mConfig->readEntry("ShapePixmapLeft");
    if (!value.isEmpty()) stretchPixmap(mThemePath + value, true);
    value = mConfig->readEntry("ShapePixmapRight");
    if (!value.isEmpty()) stretchPixmap(mThemePath + value, true);
  }
  else
  {
    kdWarning() << "Unknown pre-install command `" << aCmd << "' in theme.mappings file in group "
	    << mMappings->group() << endl;
  }
}

static void cleanKWMPixmapEntry(KSimpleConfig *aCfg, const char *entry)
{
  QString pixmap1 = QString::fromLatin1(entry) + ".png";
  QString pixmap2 = aCfg->readEntry(entry);
  if (pixmap2 != pixmap1)
  {
     QString file = locateLocal("data", "kwin/pics/"+pixmap1);
     ::unlink(QFile::encodeName(file));
  }
}

static int countKWMPixmapEntry(KSimpleConfig *aCfg, const char *entry)
{
  return aCfg->readEntry(entry).isEmpty() ? 0 : 1;
}

//-----------------------------------------------------------------------------
void Theme::installCmd(KSimpleConfig* aCfg, const QString& aCmd,
		       int &aInstalled)
{
  QString grp = aCfg->group();
  QString cmd = aCmd.stripWhiteSpace();

  if (cmd == "setColorScheme")
  {
    updateColorScheme(aCfg);
  }
  else if (cmd == "setWallpaperMode")
  {
    QString value = aCfg->readEntry("wallpaper",QString::null);
    aCfg->writeEntry("UseWallpaper", !value.isEmpty());
  }
  else if (cmd == "oneDesktopMode")
  {
    bool flag = (aInstalled == 1);
    bool value = aCfg->readBoolEntry("CommonDesktop", false);
    if (flag || value)
      aCfg->writeEntry("CommonDesktop", true);
    if (flag)
      aCfg->writeEntry("DeskNum", 0);
  }
  else if (cmd == "setSound")
  {
    bool flag = (aInstalled > 0);
    aInstalled = 0;
    int presentation = aCfg->readNumEntry("presentation",0);
    if (flag)
       aCfg->writeEntry("presentation", presentation | 1);
    else
       aCfg->writeEntry("presentation", presentation & ~1);
  }
  else if (cmd == "setKWM")
  {
    mKwmCount = 0;
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_top");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_bottom");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_left");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_right");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_topleft");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_topright");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_bottomleft");
    mKwmCount += countKWMPixmapEntry(aCfg, "wm_bottomright");
  }
  else if (cmd == "setKWM2")
  {
    // Clean up spurious theme pixmaps
    cleanKWMPixmapEntry(aCfg, "menu");
    cleanKWMPixmapEntry(aCfg, "iconify");
    cleanKWMPixmapEntry(aCfg, "maximize");
    cleanKWMPixmapEntry(aCfg, "maximizedown");
    cleanKWMPixmapEntry(aCfg, "close");
    cleanKWMPixmapEntry(aCfg, "pinup");
    cleanKWMPixmapEntry(aCfg, "pindown");
  }
  else if (cmd == "setKWM3")
  {
    QString plugin = aCfg->readEntry("PluginLib");
    kdDebug() << "KWin Plugin: " << plugin << endl;
    if ((mKwmCount == 8) && (plugin.isEmpty()))
       aCfg->writeEntry("PluginLib", "kwin_kwmtheme");
    else if (!plugin.isEmpty())  //an installed kwin client
       aCfg->writeEntry("PluginLib", plugin);
  }
  else if (cmd == "setKicker")
  {
    QString value = aCfg->readEntry("BackgroundTheme",QString::null);
    aCfg->writeEntry("UseBackgroundTheme", !value.isEmpty());
    if (!value.isEmpty())
        aCfg->writeEntry("RotateBackground", true);
  }
  else if (cmd == "setKmenu")
  {
    QString value = aCfg->readEntry("SideImage",QString::null);
    QString value2 = aCfg->readEntry("SideImageTile",QString::null);
    if (!value.isEmpty() || !value2.isEmpty())
        aCfg->writeEntry("UseSidePixmap", true);
  }
  else
  {
    kdWarning() << "Unknown command `" << aCmd << "' in theme.mappings "
		 << "file in group " << mMappings->group() << endl;
  }

  if ( aCfg->group() != grp )
    aCfg->setGroup(grp);
}

void Theme::applyIcons()
{
  QString theme = KIconTheme::current();

  KIconTheme icontheme(theme);

  const char * const groups[] = { "Desktop", "Toolbar", "MainToolbar", "Small", 0L };

  KConfig *config = new KConfig("kdeglobals");
  for (KIcon::Group i=KIcon::FirstGroup; i<KIcon::User; i++)
  {
    if (groups[i] == 0L)
      break;
    config->setGroup(QString::fromLatin1(groups[i]) + "Icons");
    config->writeEntry("Size", icontheme.defaultSize(i));
  }

  config->setGroup("Icons");
  //kdDebug() << k_funcinfo << "Icon theme is: " << theme <<endl;
  config->writeEntry("Theme", theme);
  config->sync();

  for (int i=0; i<KIcon::User; i++)
  {
    KIPC::sendMessageAll(KIPC::IconChanged, i);
  }

  delete config;
}


//-----------------------------------------------------------------------------
void Theme::doCmdList(void)
{
  QString cmd, str, appName;
  //  int rc;
  for (QStringList::ConstIterator it = mCmdList.begin();
       it != mCmdList.end();
       ++it)
  {
    cmd = *it;
    kdDebug() << "do command: " << cmd << endl;
    if (cmd.startsWith("kfmclient"))
    {
      system(QFile::encodeName(cmd));
    }
    else if (cmd == "applyColors")
    {
      runKrdb();
      colorSchemeApply();
    }
    else if (cmd == "applyWallpaper")
    {
       // reconfigure kdesktop. kdesktop will notify all clients
       DCOPClient *client = kapp->dcopClient();
       if (!client->isAttached())
          client->attach();
       client->send("kdesktop", "KBackgroundIface", "configure()", "");
    }
    else if (cmd == "applyIcons")
    {
       applyIcons();
    }
    else if (cmd == "applySound")
    {
       // reconfigure knotify
       DCOPClient *client = kapp->dcopClient();
       if (!client->isAttached())
          client->attach();
       client->send("knotify", "", "reconfigure()", "");
    }
    else if (cmd == "applyKWM")
    {
       // Clean up spurious theme pixmaps
       kapp->config()->reparseConfiguration();
       // reconfigure kwin
       DCOPClient *client = kapp->dcopClient();
       if (!client->isAttached())
          client->attach();
       client->send("kwin", "", "reconfigure()", "");
    }
    else if (cmd == "applyKicker" || cmd == "applyKmenu")
    {
       // reconfigure kicker
       DCOPClient *client = kapp->dcopClient();
       if (!client->isAttached())
          client->attach();
       client->send("kicker", "", "configure()", "");
    }
    else if (cmd.startsWith("restart"))
    {
      appName = cmd.mid(7).stripWhiteSpace();
      str = i18n("Restart %1 to activate the new settings?").arg( appName );
      if (KMessageBox::questionYesNo(0, str) == KMessageBox::Yes) {
          str.sprintf(mRestartCmd.local8Bit().data(), appName.local8Bit().data(),
                      appName.local8Bit().data());
          system(QFile::encodeName(str));
      }
    }
  }

  mCmdList.clear();
}


//-----------------------------------------------------------------------------
bool Theme::backupFile(const QString &fname) const
{
  QFileInfo fi(fname);
  QString cmd;
  int rc;

  if (!fi.exists()) return false;

  QFile::remove((fname + '~'));
  rc = ::rename(QFile::encodeName(fname).data(), QFile::encodeName(fname + "~").data());
  if (rc < 0) kdWarning() << "Cannot make backup copy of "
          << fname << ": mv returned " << rc << endl;
  return (rc==0);
}

//-----------------------------------------------------------------------------
void Theme::addInstFile(const QString &aFileName)
{
  if (!aFileName.isEmpty() && (mInstFiles.findIndex(aFileName) < 0))
    mInstFiles.append(aFileName);
}


//-----------------------------------------------------------------------------
void Theme::readInstFileList(const char* aGroupName)
{
  KConfig* cfg = kapp->config();

  assert(aGroupName!=0);
  cfg->setGroup("Installed Files");
  mInstFiles = cfg->readListEntry(aGroupName, ':');
}


//-----------------------------------------------------------------------------
void Theme::writeInstFileList(const char* aGroupName)
{
  KConfig* cfg = kapp->config();

  assert(aGroupName!=0);
  cfg->setGroup("Installed Files");
  cfg->writeEntry(aGroupName, mInstFiles, ':');
}


//-----------------------------------------------------------------------------
void Theme::uninstallFiles(const char* aGroupName)
{
  QString cmd, fname, value;
  QFileInfo finfo;
  bool reverted = false;
  int processed = 0;

  readInstFileList(aGroupName);
  for (QStringList::ConstIterator it=mInstFiles.begin();
       it != mInstFiles.end();
       ++it)
  {
    fname = *it;
    reverted = false;
    if (unlink(QFile::encodeName(fname).data())==0)
       reverted = true;
    finfo.setFile(fname+'~');
    if (finfo.exists())
    {
      if (rename(QFile::encodeName(fname+'~').data(), QFile::encodeName(fname).data()))
      kdWarning() << "Failed to rename " << fname << " to "
        << fname << "~:" << strerror(errno) << endl;
      else reverted = true;
    }

    if (reverted)
    {
      processed++;
    }
  }
  mInstFiles.clear();
  writeInstFileList(aGroupName);
}


//-----------------------------------------------------------------------------
void Theme::install(void)
{
  if (!mValid) return;

  loadMappings();
  mCmdList.clear();

  if (instWallpapers) installGroup("Display");
  if (instSounds) installGroup("Sounds");
  if (instIcons) installGroup("Icons");

  // Colors & WM are installed behind each other to get a smoother update.
  if (instColors) installGroup("Colors");
  if (instWM)
  {
     installGroup("Window Border");
     installGroup("Window Titlebar");
  }
  if (instPanel)
      installGroup("Panel");

  if (instKmenu)
      installGroup("KMenu");

  doCmdList();

  saveSettings();
}


//-----------------------------------------------------------------------------
void Theme::readCurrent(void)
{
  emit changed();
}


//-----------------------------------------------------------------------------
KConfig* Theme::openConfig(const QString &aAppName) const
{
  return new KConfig(aAppName + "rc");
}


//-----------------------------------------------------------------------------
void Theme::loadGroupGeneral(void)
{
  QString fname;
  QColor col;
  col.setRgb(192,192,192);

  if (mThemeType == Theme_Windows)
  {
    mName = mThemercFile;
    int i = mName.findRev('/');
    if (i != -1)
       mName = mName.mid(i+1);
    i = mName.findRev('.');
    if (i != -1)
       mName = mName.left(i);
    mDescription = mName + " Theme";
    mAuthor = "Divide by Zero's WinTheme patch imported";
    mEmail = "";
    mHomePage = "";
    mVersion = "";
  }
  else
  {
    mConfig->setGroup("General");
    mName = mConfig->readEntry("Name");
    if (mName.isEmpty())
       mName = mConfig->readEntry("name", "<unknown>");
    mDescription = mConfig->readEntry("Comment");
    if (mDescription.isEmpty())
       mDescription = mConfig->readEntry("description", i18n("%1 Theme").arg(mName));
    mAuthor = mConfig->readEntry("author");
    mEmail = mConfig->readEntry("email");
    mHomePage = mConfig->readEntry("homepage");
    mVersion = mConfig->readEntry("version");
  }
  mPreview.resize(0,0);
  if (!mPreviewFile.isEmpty())
  {
    mPreview.load(mPreviewFile);
  }
  if (mPreview.isNull())
  {
     if (mThemeType == Theme_Windows)
     {
        mConfig->setGroup("Control Panel/Desktop");
        mPreviewFile = mConfig->readEntry("Wallpaper");
        mPreviewFile.replace(QRegExp("%.+%"), QString::null);
        mPreviewFile = findFile(mPreviewFile);
        if (!mPreviewFile.isEmpty())
        {
           QImage img(mPreviewFile);
           if (!img.isNull())
           {
              mPreview = img.smoothScale(320,240);
           }
        }
     }
  }
}

//-----------------------------------------------------------------------------
bool Theme::hasGroup(const QString& aName, bool aNotEmpty)
{
  bool found;
  QString gName;

  if (mThemeType == Theme_Windows)
  {
    if (aName == "Colors")
        gName = "Control Panel/Colors";
    else if (aName == "Display")
        gName = "Control Panel/Desktop";
    else if (aName == "Sounds")
        gName = "AppEvents/Schemes/Apps/.Default/Minimize/.Current";
  } else
      gName = aName;
  found = mConfig->hasGroup(gName);

  if (!aNotEmpty)
      return found;

  QMap<QString, QString> aMap = mConfig->entryMap(gName);
  if (found && aNotEmpty)
      found = !aMap.isEmpty();

  return found;
}

//-----------------------------------------------------------------------------
void Theme::stretchPixmap(const QString &aFname, bool aStretchVert)
{
  QPixmap src, dest;
  QBitmap *srcMask, *destMask;
  int w, h, w2, h2;
  QPainter p;

  src.load(aFname);
  if (src.isNull()) return;

  w = src.width();
  h = src.height();

  if (aStretchVert)
  {
    w2 = w;
    for (h2=h; h2<64; h2=h2<<1)
      ;
  }
  else
  {
    h2 = h;
    for (w2=w; w2<64; w2=w2<<1)
      ;
  }

  dest = src;
  dest.resize(w2, h2);

  p.begin(&dest);
  p.drawTiledPixmap(0, 0, w2, h2, src);
  p.end();

  srcMask = (QBitmap*)src.mask();
  if (srcMask)
  {
    destMask = (QBitmap*)dest.mask();
    p.begin(destMask);
    p.drawTiledPixmap(0, 0, w2, h2, *srcMask);
    p.end();
  }

  dest.save(aFname, QPixmap::imageFormat(aFname));
}

//-----------------------------------------------------------------------------
void Theme::runKrdb(void) const
{
  KSimpleConfig cfg("kcmdisplayrc", true);

  cfg.setGroup("X11");
  if (cfg.readBoolEntry("exportKDEColors", true)) {
    QString exe = locate("exe", "krdb");
    if (exe.isEmpty())
      exe = "krdb";
    system(QFile::encodeName(exe));
  }
}


//-----------------------------------------------------------------------------
void Theme::colorSchemeApply(void)
{
  KIPC::sendMessageAll(KIPC::PaletteChanged);
}


void Theme::updateColorScheme(KSimpleConfig *config)
{
    // define some KDE2 default colors
    QColor kde2Blue;
    if (QPixmap::defaultDepth() > 8)
      kde2Blue.setRgb(10, 95, 137);
    else
      kde2Blue.setRgb(0, 0, 192);

    QColor widget(220, 220, 220);

    QColor button;
    if (QPixmap::defaultDepth() > 8)
      button.setRgb(228, 228, 228);
    else
      button.setRgb(220, 220, 220);

    QColor link(0, 0, 192);
    QColor visitedLink(128, 0,128);

    // Current scheme
    config->setGroup("General");

    bool isDefault = config->readEntry( "background").isEmpty();
    if (isDefault)
    {
       QString sCurrentScheme = locateLocal("data", "kdisplay/color-schemes/thememgr.kcsrc");
       unlink(QFile::encodeName(sCurrentScheme));
       config->setGroup("KDE");
       config->writeEntry("colorScheme", "<default>", true, true);
       return;
    }

    // note: defaults should be the same as the KDE default
    QColor txt = config->readColorEntry( "foreground", &black );
    QColor back = config->readColorEntry( "background", &widget );
    QColor select = config->readColorEntry( "selectBackground", &kde2Blue );
    QColor selectTxt = config->readColorEntry( "selectForeground", &white );
    QColor window = config->readColorEntry( "windowBackground", &white );
    QColor windowTxt = config->readColorEntry( "windowForeground", &black );
    button = config->readColorEntry( "buttonBackground", &button );
    QColor buttonTxt = config->readColorEntry( "buttonForeground", &black );
    link = config->readColorEntry( "linkColor", &link );
    visitedLink = config->readColorEntry( "visitedLinkColor", &visitedLink );

    config->setGroup( "WM" );

    QColor iaTitle = config->readColorEntry("inactiveBackground", &widget);
    QColor iaTxt = config->readColorEntry("inactiveForeground", &black);
    QColor iaBlend = config->readColorEntry("inactiveBlend", &widget);
    QColor aTitle = config->readColorEntry("activeBackground", &kde2Blue);
    QColor aTxt = config->readColorEntry("activeForeground", &white);
    QColor aBlend = config->readColorEntry("activeBlend", &kde2Blue);
    // hack - this is all going away. For now just set all to button bg
    QColor aTitleBtn = config->readColorEntry("activeTitleBtnBg", &back);
    QColor iTitleBtn = config->readColorEntry("inactiveTitleBtnBg", &back);

    config->setGroup( "KDE" );
    int contrast = config->readNumEntry( "contrast", 7 );

    QString sCurrentScheme = locateLocal("data", "kdisplay/color-schemes/thememgr.kcsrc");
    KSimpleConfig *colorScheme = new KSimpleConfig(sCurrentScheme );
    int i = sCurrentScheme.findRev('/');
    if (i >= 0)
      sCurrentScheme = sCurrentScheme.mid(i+1);
    config->setGroup("KDE");
    config->writeEntry("colorScheme", sCurrentScheme, true, true);

    colorScheme->setGroup("Color Scheme" );
    colorScheme->writeEntry("Name", i18n("Theme Colors"));
    colorScheme->writeEntry("background", back );
    colorScheme->writeEntry("selectBackground", select );
    colorScheme->writeEntry("foreground", txt );
    colorScheme->writeEntry("activeForeground", aTxt );
    colorScheme->writeEntry("inactiveBackground", iaTitle );
    colorScheme->writeEntry("inactiveBlend", iaBlend );
    colorScheme->writeEntry("activeBackground", aTitle );
    colorScheme->writeEntry("activeBlend", aBlend );
    colorScheme->writeEntry("inactiveForeground", iaTxt );
    colorScheme->writeEntry("windowForeground", windowTxt );
    colorScheme->writeEntry("windowBackground", window );
    colorScheme->writeEntry("selectForeground", selectTxt );
    colorScheme->writeEntry("contrast", contrast );
    colorScheme->writeEntry("buttonForeground", buttonTxt );
    colorScheme->writeEntry("buttonBackground", button );
    colorScheme->writeEntry("activeTitleBtnBg", aTitleBtn);
    colorScheme->writeEntry("inactiveTitleBtnBg", iTitleBtn);
    colorScheme->writeEntry("linkColor", link);
    colorScheme->writeEntry("visitedLinkColor", visitedLink);

    delete colorScheme;
}

//-----------------------------------------------------------------------------
const QString Theme::fileOf(const QString& aName) const
{
  int i = aName.findRev('/');
  if (i >= 0) return aName.mid(i+1);
  return aName;
}


//-----------------------------------------------------------------------------
const QString Theme::pathOf(const QString& aName) const
{
  int i = aName.findRev('/');
  if (i >= 0) return aName.left(i);
  return aName;
}


//-----------------------------------------------------------------------------
#include "theme.moc"
