 /***************************************************************************
                          plugin_kateinsertcommand.cpp  -  description
                             -------------------
    begin                : THU Apr 19 2001
    copyright            : (C) 2001 by Anders Lund
    email                : anders@alweb.dk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
//BEGIN includes
#include "plugin_kateinsertcommand.h"
#include "plugin_kateinsertcommand.moc"

#include <tqbuttongroup.h>
#include <tqcheckbox.h>
#include <tqdir.h>
#include <tqfile.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqradiobutton.h>
#include <tqspinbox.h>
#include <tqstringlist.h>
#include <tqwhatsthis.h>
#include <tqwidget.h>

#include <tdeaction.h>
#include <kanimwidget.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <tdefiledialog.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kprocess.h>
#include <kstandarddirs.h>
#include <kgenericfactory.h>
#include <tdeapplication.h>
//END includes

K_EXPORT_COMPONENT_FACTORY( kateinsertcommandplugin, KGenericFactory<PluginKateInsertCommand>( "kateinsertcommand" ) )

//BEGIN obligatory stuff
class PluginView : public KXMLGUIClient
{
  friend class PluginKateInsertCommand;

  public:
    Kate::MainWindow *win;
};
//END

//BEGIN PluginKateInsertCommand
PluginKateInsertCommand::PluginKateInsertCommand( TQObject* parent, const char* name, const TQStringList& )
    : Kate::Plugin ( (Kate::Application *)parent, name ),
      kv              ( 0 ),
      sh              ( 0 )
{
  config = new TDEConfig("kateinsertcommandpluginrc");
  cmdhist = config->readListEntry("Command History");
  wdlg = 0;
  workingdir = TQDir::currentDirPath();
}

PluginKateInsertCommand::~PluginKateInsertCommand()
{
  // write config file
  config->writeEntry("Command History", cmdhist);
  config->writeEntry("Dialog Settings", dialogSettings);
  config->sync();
  delete config;
  delete sh;
}

void PluginKateInsertCommand::addView(Kate::MainWindow *win)
{
    // TODO: doesn't this have to be deleted?
    PluginView *view = new PluginView ();

    (void) new TDEAction ( i18n("Insert Command..."), "", 0, this,
                      TQ_SLOT( slotInsertCommand() ), view->actionCollection(),
                      "edit_insert_command" );

    view->setInstance (new TDEInstance("kate"));
    view->setXMLFile("plugins/kateinsertcommand/ui.rc");
    win->guiFactory()->addClient (view);
    view->win = win;

   m_views.append (view);
}

void PluginKateInsertCommand::removeView(Kate::MainWindow *win)
{
  for (uint z=0; z < m_views.count(); z++)
    if (m_views.at(z)->win == win)
    {
      PluginView *view = m_views.at(z);
      m_views.remove (view);
      win->guiFactory()->removeClient (view);
      delete view;
    }
}

void PluginKateInsertCommand::slotInsertCommand()
{
  if (!kapp->authorize("shell_access")) {
      KMessageBox::sorry(0,i18n("You are not allowed to execute arbitrary external applications. If you want to be able to do this, contact your system administrator."),i18n("Access Restrictions"));
      return;
  }
  if ( sh && sh->isRunning() ) {
    KMessageBox::sorry (0, i18n("A process is currently being executed."),
                        i18n("Error"));
    return;
  }

  if (!application()->activeMainWindow() || !application()->activeMainWindow()->viewManager()->activeView())
    return;

  kv = application()->activeMainWindow()->viewManager()->activeView();

  TQString dir = workingdir;
  TQString docdir;
  KURL docurl = kv->getDoc()->url();
  if (docurl.isLocalFile())
    docdir = docurl.directory();
  TQString lwd( config->readPathEntry("Last WD") );
  switch ( (int)config->readNumEntry("Start In", 0) )
  {
    case 1:
      if ( ! docdir.isEmpty() ) dir = docdir;
      break;
    case 2:
      if ( ! lwd.isEmpty() ) dir = lwd;
      break;
    default:
      break;
  }
  dialogSettings = config->readNumEntry("Dialog Settings", 0);
  CmdPrompt *d = new CmdPrompt((TQWidget*)kv, 0, cmdhist, dir,
                               docdir, dialogSettings);
  if ( d->exec() && ! d->command().isEmpty() ) {
    if ( ! sh ) {
    sh = new KShellProcess;

    connect ( sh, TQ_SIGNAL(receivedStdout(TDEProcess*, char*, int)),
              this, TQ_SLOT(slotReceivedStdout(TDEProcess*, char*, int)) );
    connect ( sh, TQ_SIGNAL(receivedStderr(TDEProcess*, char*, int)),
              this, TQ_SLOT(slotReceivedStderr(TDEProcess*, char*, int)) );
    connect ( sh, TQ_SIGNAL(processExited(TDEProcess*)),
              this, TQ_SLOT(slotProcessExited(TDEProcess*)) ) ;
    }

    sh->clearArguments();

    bInsStdErr = d->insertStdErr();

    if ( d->printCmd() ) {
      if ( ! d->wd().isEmpty() )
        kv->insertText( d->wd() + ": ");
      kv->insertText( d->command()+":\n" );
    }
    if ( ! d->wd().isEmpty() ) {
      *sh << "cd" << d->wd() << "&&";
      config->writePathEntry("Last WD", d->wd());
    }
    *sh << TQFile::encodeName(d->command()).data();
    sh->start( TDEProcess::NotifyOnExit, TDEProcess::All );

    // add command to history
    if ( cmdhist.contains( d->command() ) ) {
      cmdhist.remove( d->command() );
    }
    cmdhist.prepend( d->command() );
    int cmdhistlen = config->readNumEntry("Command History Length", 20);
    while ( (int)cmdhist.count() > cmdhistlen )
         cmdhist.remove( cmdhist.last() );
    // save dialog state
    dialogSettings = 0;
    if ( d->insertStdErr() )
      dialogSettings += 1;
    if ( d->printCmd() )
      dialogSettings += 2;

    cmd = d->command();
    delete d;
    // If process is still running, display a dialog to cancel...
    slotShowWaitDlg();

    config->writeEntry("Dialog Settings", dialogSettings);
    config->sync();
  }
}

void PluginKateInsertCommand::slotAbort()
{
  if ( sh->isRunning() )
    if (! sh->kill() )
      KMessageBox::sorry(0, i18n("Could not kill command."), i18n("Kill Failed"));
}

void PluginKateInsertCommand::slotShowWaitDlg()
{
    if ( sh->isRunning() ) {
      wdlg = new WaitDlg( (TQWidget*)kv, i18n(
        "Executing command:\n%1\n\nPress 'Cancel' to abort.").arg(cmd)  );
      connect(wdlg, TQ_SIGNAL(cancelClicked()), this, TQ_SLOT(slotAbort()) );
    }
    if ( sh->isRunning() )    // we may have finished while creating the dialog.
      wdlg->show();
    else if (wdlg) { // process may have exited before the WaitDlg constructor returned.
      delete wdlg;
      wdlg = 0;
    }
}

void PluginKateInsertCommand::slotReceivedStdout( TDEProcess* /*p*/, char* text,
                                                  int len )
{
  TQString t = TQString::fromLocal8Bit ( text );
  t.truncate(len);
  kv->insertText( t );
}

void PluginKateInsertCommand::slotReceivedStderr( TDEProcess* p, char* text,
                                                  int len )
{
  if ( bInsStdErr )
    slotReceivedStdout( p, text, len );
}

void PluginKateInsertCommand::slotProcessExited( TDEProcess* p )
{
  if (wdlg) {
    wdlg->hide();
    delete wdlg;
    wdlg = 0;
  }
  if ( ! p->normalExit() )
    KMessageBox::sorry(0, i18n("Command exited with status %1").
                              arg( p->exitStatus()), i18n("Oops!"));
  kv->setFocus();
}
//END PluginKateInsertCommand

//BEGIN PluginConfigPage
Kate::PluginConfigPage* PluginKateInsertCommand::configPage (uint,
                                  TQWidget *w, const char */*name*/)
{
  InsertCommandConfigPage* p = new InsertCommandConfigPage(this, w);
  initConfigPage( p );
  connect( p, TQ_SIGNAL(configPageApplyRequest(InsertCommandConfigPage*)),
           this, TQ_SLOT(applyConfig(InsertCommandConfigPage*)) );
  return (Kate::PluginConfigPage*)p;
}

void PluginKateInsertCommand::initConfigPage( InsertCommandConfigPage *p )
{
  p->sb_cmdhistlen->setValue( config->readNumEntry("Command History Length", 20) );
  p->rg_startin->setButton( config->readNumEntry("Start In", 0) );
}

void PluginKateInsertCommand::applyConfig( InsertCommandConfigPage *p )
{
  config->writeEntry( "Command History Length", p->sb_cmdhistlen->value() );
  // truncate the cmd hist if nessecary?
  config->writeEntry( "Start In", p->rg_startin->id(p->rg_startin->selected()) );
  config->sync();
}
//END PluginConfigPage

//BEGIN CmdPrompt
// This is a simple dialog to retrieve a command and decide if
// stdErr should be included in the text inserted.
CmdPrompt::CmdPrompt(TQWidget* parent,
                     const char* name,
                     const TQStringList& cmdhist,
                     const TQString& dir,
                     const TQString& /*docdir*/,
                     int settings)
  : KDialogBase (parent, name, true, i18n("Insert Command"), Ok|Cancel, Ok, true)
{
   TQWidget *page = new TQWidget( this );
   setMainWidget(page);

   TQVBoxLayout *lo = new TQVBoxLayout( page, 0, spacingHint() );

   TQLabel *l = new TQLabel( i18n("Enter &command:"), page );
   lo->addWidget( l );
   cmb_cmd = new KHistoryCombo(true, page);
   cmb_cmd->setHistoryItems(cmdhist, true);
   cmb_cmd->setCurrentItem(0);
   cmb_cmd->lineEdit()->setSelection(0, cmb_cmd->currentText().length());
   l->setBuddy(cmb_cmd);
   cmb_cmd->setFocus();
   lo->addWidget(cmb_cmd);
   connect( cmb_cmd->lineEdit(),TQ_SIGNAL(textChanged ( const TQString & )),
            this, TQ_SLOT( slotTextChanged(const TQString &)));

   TQLabel *lwd = new TQLabel( i18n("Choose &working folder:"), page );
   lo->addWidget( lwd );
   wdreq = new KURLRequester( page );
   if ( ! dir.isEmpty() )
     wdreq->setURL( dir );
   wdreq->setMode( static_cast<KFile::Mode>(KFile::Directory|KFile::LocalOnly|KFile::ExistingOnly) );
   lwd->setBuddy( wdreq );
   lo->addWidget( wdreq );

   //kdDebug()<<"settings: "<<settings<<endl;
   cb_insStdErr = new TQCheckBox( i18n("Insert Std&Err messages"), page );
   cb_insStdErr->setChecked(settings & 1);
   lo->addWidget( cb_insStdErr );
   cb_printCmd = new TQCheckBox( i18n("&Print command name"), page );
   cb_printCmd->setChecked(settings & 2);
   lo->addWidget( cb_printCmd );

   TQWhatsThis::add( cmb_cmd, i18n(
     "Enter the shell command, the output of which you want inserted into your "
     "document. Feel free to use a pipe or two if you wish.") );
   TQWhatsThis::add( wdreq, i18n(
     "Sets the working folder of the command. The command executed is 'cd <dir> "
     "&& <command>'") );
   TQWhatsThis::add( cb_insStdErr, i18n(
     "Check this if you want the error output from <command> inserted as well.\n"
     "Some commands, such as locate, print everything to STDERR") );
   TQWhatsThis::add( cb_printCmd, i18n(
     "If you check this, the command string will be printed followed by a "
     "newline before the output.") );
   slotTextChanged(cmb_cmd->lineEdit()->text());
}

CmdPrompt::~CmdPrompt() {}

void CmdPrompt::slotTextChanged(const TQString &text)
{
    enableButtonOK( !text.isEmpty());
}
//END CmdPrompt

//BEGIN WaitDlg implementation
// This is a dialog that is displayed while a command is running,
// with a cancel button to allow the user to kill the command
WaitDlg::WaitDlg(TQWidget* parent, const TQString& text, const TQString& title)
  : KDialogBase( parent, "wait dialog", true, title, Cancel, Cancel, true )
{
  TQWidget *page = new TQWidget( this );
  setMainWidget( page );
  TQHBoxLayout *lo = new TQHBoxLayout( page, 0, spacingHint() );

  KAnimWidget *aw = new KAnimWidget( TQString::fromLatin1("kde"), 48, page );
  lo->addWidget(aw);
  TQLabel *l = new TQLabel( text, page );
  lo->addWidget( l );

  aw->start();
}
WaitDlg::~WaitDlg()
{
}
//END WaitDlg

//BEGIN InsertCommandConfigPage
// This is the config page for this plugin.
InsertCommandConfigPage::InsertCommandConfigPage(TQObject* /*parent*/,
                                                 TQWidget *parentWidget)
  : Kate::PluginConfigPage( parentWidget )
{
  TQVBoxLayout* lo = new TQVBoxLayout( this );
  lo->setSpacing(KDialogBase::spacingHint());

  // command history length
  TQHBox *hb1 = new TQHBox( this );
  hb1->setSpacing(KDialogBase::spacingHint());
  (void) new TQLabel( i18n("Remember"), hb1 );
  sb_cmdhistlen = new TQSpinBox( hb1 );
  TQLabel *l1 =  new TQLabel( sb_cmdhistlen, i18n("Co&mmands"), hb1);
  hb1->setStretchFactor(l1, 1);
  lo->addWidget( hb1 );

  // dir history length

  // initial dir choice
  rg_startin = new TQButtonGroup( 1, TQt::Horizontal, i18n("Start In"), this );
  rg_startin->setRadioButtonExclusive( true );
  (void) new TQRadioButton( i18n("Application &working folder"), rg_startin);
  (void) new TQRadioButton( i18n("&Document folder"), rg_startin);
  (void) new TQRadioButton( i18n("&Latest used working folder"), rg_startin);
  lo->addWidget( rg_startin );
  // other?

  lo->addStretch(1);  // look nice

  // Be helpfull!
  TQWhatsThis::add( sb_cmdhistlen, i18n(
    "Sets the number of commands to remember. The command history is saved "
    "over sessions.") );
  TQWhatsThis::add( rg_startin, i18n(
    "<qt><p>Decides what is suggested as <em>working folder</em> for the "
    "command.</p><p><strong>Application Working Folder (default):</strong> "
    "The folder from which you launched the application hosting the plugin, "
    "usually your home folder.</p><p><strong>Document Folder:</strong> The "
    "folder of the document. Used only for local documents.</p><p><strong>"
    "Latest Working Folder:</strong> The folder used last time you used this "
    "plugin.</p></qt>") );
}

void InsertCommandConfigPage::apply()
{
  emit configPageApplyRequest( this );
}
//END InsertCommandConfigPage
