//-----------------------------------------------------------------------------
//
// KDE xscreensaver configuration dialog
//
// Copyright (c)  Martin R. Jones <mjones@kde.org> 1999
//
// 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;
// version 2 of the License.
//
// 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; see the file COPYING.  If not, write to
// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.

// This file contains code copied from xscreensaver.  The xscreensaver
// copyright notice follows.

/*
 * xscreensaver, Copyright (c) 1993-2002 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 */
#include <config-kxsconfig.h>

#include <stdlib.h>
#include <qlayout.h>
#include <qtimer.h>
#include <kvbox.h>
#include <qlabel.h>
#include <qfileinfo.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QBoxLayout>

#include <kdebug.h>
#include <kapplication.h>
#include <kconfig.h>
#include <kstandarddirs.h>
#include <klocale.h>
#include <kcmdlineargs.h>
#include <kshell.h>
#include <kmessagebox.h>

#include "kxsconfig.h"
#include "kxscontrol.h"
#include "kxsxml.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/IntrinsicP.h>
#include <X11/ShellP.h>

int ignoreXError(Display *, XErrorEvent *);

//===========================================================================

const uint widgetEventMask =                 // X event mask
(uint)(
       ExposureMask |
       PropertyChangeMask |
       StructureNotifyMask
      );

KXSConfigDialog::KXSConfigDialog(const QString &filename, const QString &name)
  : KDialog(0),
    mFilename(filename), mPreviewProc(0), mKilled(false)
{
    Q_UNUSED(name)

    setButtons(Ok| Cancel);
    setDefaultButton( Ok);
    setModal(false);
    int slash = filename.lastIndexOf('/');
    if (slash >= 0)
	mConfigFile = filename.mid(slash+1);
    else
	mConfigFile = filename;

    mExeName = mConfigFile;
    mConfigFile += "rc";
    connect( this,SIGNAL(okClicked()),this,SLOT(slotOk()));
    connect( this,SIGNAL(cancelClicked()),this,SLOT(slotCancel()));
}

bool KXSConfigDialog::create()
{
    QWidget *main = new QWidget(this);
    setMainWidget(main);
    QVBoxLayout *topLayout = new QVBoxLayout(main);
    topLayout->setSpacing(spacingHint());
    QHBoxLayout *layout = new QHBoxLayout();
    topLayout->addLayout(layout);
    layout->setSpacing(spacingHint());
    KVBox *controlLayout = new KVBox(main);
    controlLayout->setSpacing(spacingHint());
    layout->addWidget(controlLayout);
    ((QBoxLayout*)controlLayout->layout())->addStrut(120);

    KConfig config(mConfigFile);

    QString xmlFile = "/doesntexist";
#ifdef XSCREENSAVER_CONFIG_DIR
    xmlFile = XSCREENSAVER_CONFIG_DIR;
#endif

    xmlFile += '/' + mExeName + ".xml";
    if ( QFile::exists( xmlFile ) ) {
	// We can use the xscreensaver xml config files.
	KXSXml xmlParser(controlLayout);
	xmlParser.parse( xmlFile );
	mConfigItemList = xmlParser.items();
	QWidget *spacer = new QWidget(controlLayout);
	controlLayout->setStretchFactor(spacer, 1 );
	QString descr = xmlParser.description();
	if ( !descr.isEmpty() ) {
	    descr = descr.replace('\n', ' ').simplified();
	    QLabel *l = new QLabel( i18n( descr.toUtf8() ), main );
	    l->setWordWrap( 1 );
	    topLayout->addWidget( l );
	}
    } else {
        // fall back to KDE's old config files.
	int idx = 0;
	while (true) {
	    QString group = QString("Arg%1").arg(idx);
	    if (config.hasGroup(group)) {
		KConfigGroup grp = config.group(group);
		QString type = grp.readEntry("Type");
		if (type == "Range") {
		    KXSRangeControl *rc = new KXSRangeControl(controlLayout, group, config);
		    mConfigItemList.append(rc);
		} else if (type == "DoubleRange") {
		    KXSDoubleRangeControl *rc =
			new KXSDoubleRangeControl(controlLayout, group, config);
		    mConfigItemList.append(rc);
		} else if (type == "Check") {
		    KXSCheckBoxControl *cc = new KXSCheckBoxControl(controlLayout, group,
			    config);
		    mConfigItemList.append(cc);
		} else if (type == "DropList") {
		    KXSDropListControl *dl = new KXSDropListControl(controlLayout, group,
			    config);
		    mConfigItemList.append(dl);
		}
	    } else {
		break;
	    }
	    idx++;
	}
	if ( idx == 0 )
	    return false;
    }

    for ( int i = 0; i < mConfigItemList.size(); i++ ) {
        KXSConfigItem *item = mConfigItemList[i];
        item->read( config );
        QWidget *widget = dynamic_cast<QWidget*>( item );
        if ( widget ) {
            connect( widget, SIGNAL(changed()), SLOT(slotChanged()) );
        }
    }

    mPreviewProc = new KProcess;
    connect(mPreviewProc, SIGNAL(finished(int,QProcess::ExitStatus)),
	    SLOT(slotPreviewProcFinished(int,QProcess::ExitStatus)));

    mPreviewTimer = new QTimer(this);
    mPreviewTimer->setSingleShot(true);
    connect(mPreviewTimer, SIGNAL(timeout()), SLOT(slotNewPreview()));

    mPreview = new QWidget(main);
    mPreview->setFixedSize(250, 200);
    {
        QPalette palette;
        palette.setColor(mPreview->backgroundRole(), Qt::black);
        mPreview->setPalette(palette);
	mPreview->setAutoFillBackground(true);
    }

    layout->addWidget(mPreview);
    show();

    // So that hacks can XSelectInput ButtonPressMask
    XSelectInput(QX11Info::display(), mPreview->winId(), widgetEventMask );

    startProcess();
    return true;
}

//---------------------------------------------------------------------------
KXSConfigDialog::~KXSConfigDialog()
{
  if (mPreviewProc && mPreviewProc->state() == QProcess::Running) {
    mPreviewProc->kill();
    mPreviewProc->waitForFinished(5000);
    delete mPreviewProc;
  }
}

//---------------------------------------------------------------------------
QString KXSConfigDialog::command()
{
  QString cmd;

  for (int i = 0; i < mConfigItemList.size(); i++)
  {
    if (mConfigItemList[i]) {
      cmd += " " + mConfigItemList[i]->command();
    }
  }

  return cmd;
}

//---------------------------------------------------------------------------
void KXSConfigDialog::startProcess()
{
    mKilled = false;
    mPreviewProc->clearProgram();
    QString saver = QString( "%1 -window-id 0x%2" )
        .arg( mFilename )
        .arg( long(mPreview->winId()), 0, 16 );
    saver += command();
    saver = saver.trimmed();

    kDebug() << "Command: " <<  saver;

    int i = 0;
    QString exe;
    while ( !saver[i].isSpace() ) {
        exe += saver[i++];
    }
    // work around a KStandarDirs::findExe() "feature" where it looks in
    // $KDEDIR/bin first no matter what and sometimes finds the wrong executable
    QFileInfo checkExe;
    QString saverdir = QString(XSCREENSAVER_HACKS_DIR "/") + exe;
    QString path;
    checkExe.setFile(saverdir);
    if (checkExe.exists() && checkExe.isExecutable() && checkExe.isFile())
    {
        path = saverdir;
    }
    if (!path.isEmpty()) {
        (*mPreviewProc) << path;

        (*mPreviewProc) << KShell::splitArgs(saver.mid(i));

        mPreviewProc->start();
    }
}

//---------------------------------------------------------------------------
void KXSConfigDialog::slotPreviewProcFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    Q_UNUSED(exitCode)
    Q_UNUSED(exitStatus)

    if ( mKilled ) {
        startProcess();
    } else {
	// stops us from spawning the hack really fast, but still not the best
	QString path = KStandardDirs::findExe(mFilename, XSCREENSAVER_HACKS_DIR);
	if ( QFile::exists(path) ) {
	    mKilled = true;
	    slotChanged();
	}
    }
}

//---------------------------------------------------------------------------
void KXSConfigDialog::slotNewPreview()
{
  if (mPreviewProc->state() == QProcess::Running) {
    mKilled = true;
    mPreviewProc->kill(); // restarted in slotPreviewExited()
  } else {
    startProcess();
  }
}

//---------------------------------------------------------------------------
void KXSConfigDialog::slotChanged()
{
    mPreviewTimer->start(1000);
}

//---------------------------------------------------------------------------
void KXSConfigDialog::slotOk()
{
  KConfig config(mConfigFile);

  for (int i = 0; i < mConfigItemList.size(); i++)
  {
    if (mConfigItemList[i]) {
      mConfigItemList[i]->save(config);
    }
  }

  kapp->quit();
}

//---------------------------------------------------------------------------
void KXSConfigDialog::slotCancel()
{
  kapp->quit();
}


//===========================================================================

static const char appName[] = "kxsconfig";

static const char description[] = I18N_NOOP("KDE X Screen Saver Configuration tool");

static const char version[] = "3.0.0";

static const char *defaults[] = {
#include "XScreenSaver_ad.h"
 0
};

const char *progname = 0;
const char *progclass = "XScreenSaver";
XrmDatabase db;

int main(int argc, char *argv[])
{
  KCmdLineArgs::init(argc, argv, appName, 0, ki18n("KXSConfig"), version, ki18n(description));


  KCmdLineOptions options;

  options.add("+screensaver", ki18n("Filename of the screen saver to configure"));

  options.add("+[savername]", ki18n("Optional screen saver name used in messages"));

  KCmdLineArgs::addCmdLineOptions(options);

  KApplication app;

  KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
  if(args->count()==0)
    exit(1);

  /* We must read exactly the same resources as xscreensaver.
     That means we must have both the same progclass *and* progname,
     at least as far as the resource database is concerned.  So,
     put "xscreensaver" in argv[0] while initializing Xt.
   */
  const char *dummyargs[] = { "xscreensaver" };
  int dummyargc = 1;
  progname = dummyargs[0];

  // Teach Xt to use the Display that Qt has already opened.
  XtToolkitInitialize ();
  XtAppContext xtApp = XtCreateApplicationContext ();
  Display *dpy = QX11Info::display();
  XtAppSetFallbackResources (xtApp, const_cast<char**>(defaults));
  XtDisplayInitialize (xtApp, dpy, progname, progclass, 0, 0,
                       &dummyargc,
                       const_cast<char**>(dummyargs));
  Widget toplevel_shell = XtAppCreateShell (progname, progclass,
	  applicationShellWidgetClass,
	  dpy, 0, 0);
  dpy = XtDisplay (toplevel_shell);
  db = XtDatabase (dpy);
  XtGetApplicationNameAndClass (dpy, const_cast<char**>(&progname),
                                const_cast<char**>(&progclass));

  QString name = args->arg(args->count() - 1);
  KXSConfigDialog *dialog=new KXSConfigDialog(args->arg(0), name);
  if ( dialog->create() ) {
      dialog->show();
      app.exec();
  } else {
      KMessageBox::sorry(0,
	      i18n("No configuration available for %1", name),
	      name );
  }

  delete dialog;
}

#include "kxsconfig.moc"
