/* minicli.cpp
*
* Copyright (C) 1997 Matthias Ettrich <ettrich@kde.org>
* Copyright (c) 1999 Preston Brown <pbrown@kde.org>
*
* Copyright (C) 1999,2000 Dawit Alemayehu <adawit@kde.org>
* Copyright (C) 2000 Malte Starostik <starosti@zedat.fu-berlin.de>
* Copyright (C) 2000 Geert Jansen <jansen@kde.org>
*/

#include <pwd.h>
#include <string.h>
#include <errno.h>

#include <qvbox.h>
#include <qlabel.h>
#include <qbitmap.h>
#include <qslider.h>
#include <qlayout.h>
#include <qcheckbox.h>
#include <qwhatsthis.h>
#include <qstylesheet.h>

#include <klocale.h>
#include <kmessagebox.h>
#include <kprocess.h>
#include <kcombobox.h>
#include <klineedit.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kpassdlg.h>
#include <krun.h>
#include <kwin.h>
#include <kdesu/su.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#include <kiconloader.h>
#include <kpushbutton.h>
#include <kguiitem.h>
#include <kstdguiitem.h>
#include <kmimetype.h>
#include "minicli.moc"

#define KDESU_ERR strerror(errno)

#define MSGBOX_WRAPPER( code ) \
        KWin::clearState( winId(), NET::StaysOnTop ); \
        code \
        KWin::setState( winId(), NET::StaysOnTop );

Minicli::Minicli( QWidget *parent, const char *name)
        :KDialog( parent, name )
{
    m_vbox = new QGridLayout( this, 4, 1, KDialog::marginHint(),
                                         KDialog::spacingHint() );
    m_vbox->setResizeMode( QLayout::Fixed );
    KWin::setIcons( winId(), DesktopIcon("run"), SmallIcon("run") );
    setCaption( i18n("Run Command") );

    QHBox *hBox = new QHBox( this );
    m_vbox->addWidget( hBox, 0, 0 );
    hBox->setSpacing( KDialog::marginHint() );

    m_runIcon = new QLabel( hBox );
    m_runIcon->setPixmap(DesktopIcon("go"));
    m_runIcon->setFixedSize(m_runIcon->sizeHint());

    QLabel *label = new QLabel( i18n("Enter the name of the application you want "
                                     "to run or the URL you want to view."), hBox);
    label->setAlignment( Qt::WordBreak );
    hBox = new QHBox( this );
    m_vbox->addWidget( hBox, 1, 0 );
    hBox->setSpacing( KDialog::marginHint() );

    label = new QLabel(i18n("Co&mmand:"), hBox);
    label->setFixedSize(label->sizeHint());

    m_runCombo = new KHistoryCombo( hBox );
    QWhatsThis::add(m_runCombo, i18n("Enter the command you wish to execute or the address "
                                     "of the resource you want to open. This can be a remote URL "
                                     "like \"www.kde.org\" or a local one like \"~/.kderc\""));
    m_runCombo->setDuplicatesEnabled( false );
    m_runCombo->setTrapReturnKey( true );

    label->setBuddy(m_runCombo);
    m_runCombo->setFixedWidth( m_runCombo->fontMetrics().width('W') * 23 );

    m_parseTimer = new QTimer(this);

    m_bAdvanced = false;
    mpAdvanced = 0;

    QWidget* btnBox = new QWidget( this );
    QBoxLayout* bbLay = new QHBoxLayout( btnBox );
    bbLay->setSpacing( KDialog::spacingHint() );

    m_btnOptions = new KPushButton( KGuiItem( i18n( "&Options >>" ),
                                    "configure" ), btnBox );
    if (!kapp->authorize("shell_access"))
       m_btnOptions->hide();
    bbLay->addWidget( m_btnOptions );

    bbLay->addStretch( 1 );

    m_btnRun = new KPushButton( KGuiItem( i18n( "&Run" ), "run" ), btnBox );
    bbLay->addWidget( m_btnRun );
    m_btnRun->setDefault( true );

    m_btnCancel = new KPushButton( KStdGuiItem::cancel(), btnBox );
    bbLay->addWidget( m_btnCancel );

    m_vbox->addWidget( btnBox, 3, 0 );

    m_btnRun->setEnabled(!m_runCombo->currentText().isEmpty());
    // Very important, to get the size right before showing
    m_vbox->activate();

    m_filterData = new KURIFilterData();
    m_IconName = QString::null;
    m_FocusWidget = 0;

    connect( m_btnRun, SIGNAL(clicked()), this, SLOT(accept()) );
    connect( m_btnCancel, SIGNAL(clicked()), this, SLOT(reject()) );
    connect( m_btnOptions, SIGNAL(clicked()), SLOT(slotAdvanced()) );
    connect( m_parseTimer, SIGNAL(timeout()), SLOT(slotParseTimer()) );

    connect( m_runCombo, SIGNAL( textChanged( const QString& ) ),
             SLOT( slotCmdChanged(const QString&) ) );
    connect( m_runCombo, SIGNAL( activated( const QString& ) ),
             m_btnRun, SLOT( animateClick() ) );

    loadConfig();
    KWin::setState( winId(), NET::StaysOnTop );
}

Minicli::~Minicli()
{
    delete m_filterData;
}

void Minicli::loadConfig()
{
    KConfig *config = KGlobal::config();
    config->setGroup("MiniCli");
    QStringList histList = config->readListEntry("History");
    int maxHistory = config->readNumEntry("HistoryLength", 50);
    terminalAppList = config->readListEntry("TerminalApps");
    if (terminalAppList.isEmpty())
       terminalAppList << "ls"; // Default :-)

    bool block = m_runCombo->signalsBlocked();
    m_runCombo->blockSignals( true );
    m_runCombo->setMaxCount( maxHistory );
    m_runCombo->setHistoryItems( histList );
    m_runCombo->blockSignals( block );

    QStringList compList = config->readListEntry("CompletionItems");
    if( compList.isEmpty() )
        m_runCombo->completionObject()->setItems( histList );
    else
        m_runCombo->completionObject()->setItems( compList );
    int mode = config->readNumEntry( "CompletionMode", KGlobalSettings::completionMode() );
    m_runCombo->setCompletionMode( (KGlobalSettings::Completion) mode );

    finalFilters = KURIFilter::self()->pluginNames();
    finalFilters.remove("kuriikwsfilter");

    middleFilters = finalFilters;
    middleFilters.remove("localdomainurifilter");
}

void Minicli::saveConfig()
{
    KConfig *config = KGlobal::config();
    config->setGroup("MiniCli");
    config->writeEntry( "History", m_runCombo->historyItems() );
    config->writeEntry( "TerminalApps", terminalAppList );
    config->writeEntry( "CompletionItems", m_runCombo->completionObject()->items() );
    config->writeEntry( "CompletionMode", (int) m_runCombo->completionMode() );
    config->sync();
}

void Minicli::accept()
{
    hide();
    if( runCommand() == 1 )
    {
        exec();
        return;
    }

    m_runCombo->addToHistory( m_runCombo->currentText().stripWhiteSpace() );
    reset();
    saveConfig();
    QDialog::accept();
}

void Minicli::reject()
{
    reset();
    QDialog::reject();
}

void Minicli::reset()
{
    if( m_bAdvanced )
        slotAdvanced();
    if( mpAdvanced )
        mpAdvanced->reset();
    m_runIcon->setPixmap( DesktopIcon("go") );

    bool block = m_runCombo->signalsBlocked();
    m_runCombo->blockSignals( true );
    m_runCombo->clearEdit();
    m_runCombo->setFocus();
    m_runCombo->reset();
    m_runCombo->blockSignals( block );
    m_btnRun->setEnabled( false );

    m_FocusWidget = 0;
}

void Minicli::keyPressEvent( QKeyEvent* e )
{
  if ( e->key() == Qt::Key_Escape )
  {
    e->accept();
    m_btnCancel->animateClick();
    return;
  }

  QDialog::keyPressEvent( e );
}

int Minicli::runCommand()
{
    m_parseTimer->stop();
    // Make sure we have an updated data
    parseLine( true );

    if ( m_runCombo->currentText().isEmpty() )
        return 1;  // Ignore

    QString cmd;
    if ( m_filterData->uri().isLocalFile() && !m_filterData->uri().hasRef() )
        cmd = m_filterData->uri().path();
    else
        cmd = m_filterData->uri().url();

    if (cmd == "logout")
    {
      kapp->propagateSessionManager();
      kapp->requestShutDown();
      return 0;
    }

    QString cmdNoArgs = cmd.stripWhiteSpace();
    {
       int i = cmdNoArgs.find(' ');
       if (i != -1)
          cmdNoArgs.truncate(i);
    }


    bool useTerminal = false;
    if (mpAdvanced)
    {
        if (mpAdvanced->isHidden()) // Advanced options have not been toucheda
        {
            // take the stored setting
            useTerminal = terminalAppList.contains(cmdNoArgs);
        }
        else
        {
            terminalAppList.remove(cmdNoArgs);

            if (useTerminal = mpAdvanced->terminal())
                terminalAppList.append(cmdNoArgs);
            else
                useTerminal = terminalAppList.contains(cmdNoArgs);

            /*Now the terminalAppList will contain cmdNoArgs iff the checkbox is marked
              (provided it is seen by the user)*/
        }
    }

    if (!kapp->authorize("shell_access"))
       useTerminal = false;

    if( mpAdvanced && mpAdvanced->needsKDEsu() )
    {
        QCString user;
        struct passwd *pw;
        if (mpAdvanced->changeUid())
        {
            pw = getpwnam(mpAdvanced->username().local8Bit());
            if (pw == 0L)
            {
                MSGBOX_WRAPPER(  KMessageBox::sorry( this, i18n("<qt>The user <b>%1</b> "
                                 "does not exist on this system.</qt>").arg(mpAdvanced->username())); )
                return 1;
            }
        }
        else
        {
            pw = getpwuid(getuid());
            if (pw == 0L)
            {
                MSGBOX_WRAPPER( KMessageBox::error( this, i18n("You don't exist!\n")); )
                return 1;
            }
        }

        user = pw->pw_name;

        {   // scoped. we want this object to go away before the fork
            // (maybe not necessary, but can't hurt)
            // according to the cvs log, creating the SuProcess object in the parent
            // and using it in the child creates crashes, but we need to check password
            // before the fork in order to not get in trouble with X for the messagebox
            SuProcess proc_checkpwd;
            proc_checkpwd.setUser( user );

            if (mpAdvanced->changeScheduler())
            {
                proc_checkpwd.setPriority(mpAdvanced->priority());
                proc_checkpwd.setScheduler(mpAdvanced->scheduler());
            }

            if (proc_checkpwd.checkInstall(mpAdvanced->password()) != 0)
            {
                MSGBOX_WRAPPER( KMessageBox::sorry(this, i18n("Incorrect password! Please try again.")); )
                return 1;
            }
        }

        QApplication::flushX();
        int pid = fork();
        if (pid < 0)
        {
            kdError(1207) << "fork(): " << KDESU_ERR << "\n";
            return -1;
        }
        if (pid > 0)
        {
            return 0;
        }

        // from here on, this is child...

        SuProcess proc;
        proc.setUser(user);

        if (mpAdvanced->changeScheduler())
        {
            proc.setPriority(mpAdvanced->priority());
            proc.setScheduler(mpAdvanced->scheduler());
        }

        QCString command = cmd.local8Bit();
        if( m_filterData->hasArgsAndOptions() )
            command += m_filterData->argsAndOptions().local8Bit();

        if (useTerminal)
        {
            KConfigGroupSaver gs(KGlobal::config(), "General");
            QString terminal=KGlobal::config()->readPathEntry("TerminalApplication", "konsole").stripWhiteSpace();
            if (terminal.endsWith("konsole"))
               terminal += " --noclose";
            command = terminal.local8Bit() + QCString(" -e /bin/sh -c \"") + command + "\"";
        }

        proc.setCommand(command);

        // Block SIGCHLD because SuProcess::exec() uses waitpid()
        sigset_t sset;
        sigemptyset(&sset);
        sigaddset(&sset, SIGCHLD);
        sigprocmask(SIG_BLOCK, &sset, 0L);
        proc.setTerminal(true);
        proc.setErase(true);
        int ret = proc.exec(mpAdvanced->password());
        _exit(ret);
    }
    else
    {
        // yes, this is a hack, but there is no way of doing it
        // through SuProcess without providing the user password
        if (mpAdvanced && mpAdvanced->priority() < 50)
        {
            int val = 20 - (int) (((double) mpAdvanced->priority()) * 40 / 100 + 0.5); // from kdesu_stub.c
            cmd = "nice -n " + QString::number( val ) + " " + cmd;
        }

        QString exec;

        if (useTerminal)
        {
            KConfigGroupSaver gs(KGlobal::config(), "General");
            QString terminal=KGlobal::config()->readPathEntry("TerminalApplication", "konsole").stripWhiteSpace();
            exec = terminal;
            if (terminal.endsWith("konsole"))
               terminal += " --noclose";
            cmd = terminal + QString::fromLatin1(" -e ") + cmd;
            if( m_filterData->hasArgsAndOptions() )
                cmd += m_filterData->argsAndOptions();
        }
        else
        {
            switch( m_filterData->uriType() )
            {
                case KURIFilterData::LOCAL_FILE:
                case KURIFilterData::LOCAL_DIR:
                case KURIFilterData::NET_PROTOCOL:
                case KURIFilterData::HELP:
                {
                    // No need for kfmclient, KRun does it all (David)
                    (void) new KRun( m_filterData->uri() );
                    return 0;
                }
                case KURIFilterData::EXECUTABLE:
                {
                    if( !m_filterData->hasArgsAndOptions() )
                    {
                       // Look for desktop file
                       KService::Ptr service = KService::serviceByDesktopName(cmd);
                       if (service)
                       {
                          KRun::run(*service, KURL::List());
                          return 0;
                       }
                    }
                }
                // Fall through!
                case KURIFilterData::SHELL:
                if (kapp->authorize("shell_access"))
                {
                    exec = cmd;
                    if( m_filterData->hasArgsAndOptions() )
                        cmd += m_filterData->argsAndOptions();
                    //kdDebug(1207) << "Command to execute: " << cmd << endl;
                    //kdDebug(1207) << "Executable name: " << exec << endl;
                    break;
                }
                else
                {
                    MSGBOX_WRAPPER(
                    KMessageBox::sorry( this, i18n("<center><b>%1</b></center>\n"
                                                   "You do not have permission to execute this command!")
                                                   .arg( QStyleSheet::convertFromPlainText(cmd) ));
                    )
                    return 1;
                }
                case KURIFilterData::UNKNOWN:
                case KURIFilterData::ERROR:
                default:
                    // Look for desktop file
                    KService::Ptr service = KService::serviceByDesktopName(cmd);
                    if (service)
                    {
                       KRun::run(*service, KURL::List());
                       return 0;
                    }
                    MSGBOX_WRAPPER(
                    KMessageBox::sorry( this, i18n("<center><b>%1</b></center>\n"
                                                   "Could not run the specified command!")
                                                   .arg( QStyleSheet::convertFromPlainText(cmd) ));
                    )
                    return 1;
            }
        }

        if ( KRun::runCommand( cmd, exec, m_IconName ) )
        {
            return 0;
        }
        else
        {
            MSGBOX_WRAPPER(
            KMessageBox::sorry( this, i18n("<center><b>%1</b></center>\n"
                                           "The specified command does not exist!").arg( cmd ) );
            )
            return 1; // Let the user try again...
        }
    }
    return 0;
}

void Minicli::slotCmdChanged(const QString& text)
{
    bool state = text.isEmpty();
    m_btnRun->setEnabled( !state );
    if ( state )
    {
        // Reset values to default
        m_filterData->setData(KURL());
        if (mpAdvanced)
          mpAdvanced->slotTerminal(false);
        // Reset the icon if needed...
        const QPixmap pixmap = DesktopIcon("go");
        if ( pixmap.serialNumber() != m_runIcon->pixmap()->serialNumber())
            m_runIcon->setPixmap(pixmap);
        return;
    }
    else
    {
      QString cmdNoArgs = text.stripWhiteSpace();
      {
        int i = cmdNoArgs.find(' ');
        if (i != -1)
            cmdNoArgs.truncate(i);
      }

      // Look if the new app is a TerminalApp
      if (mpAdvanced)
        mpAdvanced->slotTerminal(terminalAppList.contains(cmdNoArgs));
    }

    m_parseTimer->start(250, true);
}

void Minicli::slotAdvanced()
{
    m_bAdvanced = !m_bAdvanced;
    if (!mpAdvanced)
    {
        mpAdvanced = new MinicliAdvanced(this);
        m_vbox->addWidget(mpAdvanced, 2, 0);
    }

    if (m_bAdvanced)
    {
        if ( !m_runCombo->currentText().isEmpty())
        {
           QString cmdNoArgs;
           if ( m_filterData->uri().isLocalFile() )
              cmdNoArgs = m_filterData->uri().path();
           else
              cmdNoArgs = m_filterData->uri().url();
           int i = cmdNoArgs.find(' ');
           if (i != -1)
               cmdNoArgs.truncate(i);
           mpAdvanced->slotTerminal(terminalAppList.contains(cmdNoArgs));
        }

        mpAdvanced->show();
        m_btnOptions->setText(i18n("&Options <<"));
        mpAdvanced->setMaximumSize(1000, 1000);
        mpAdvanced->setEnabled( true );
        // Set the focus back to the widget
        // that had it to begin with, i.e. do
        // not put the focus on the "Options"
        // button
        m_FocusWidget = focusWidget();
        if( m_FocusWidget )
            m_FocusWidget->setFocus();
    }
    else
    {
        mpAdvanced->hide();
        m_btnOptions->setText(i18n("&Options >>"));
        if( m_FocusWidget && m_FocusWidget->parent() != mpAdvanced )
            m_FocusWidget->setFocus();
        mpAdvanced->setMaximumSize(0, 0);
        mpAdvanced->setEnabled( false );
    }
}

void Minicli::slotParseTimer()
{
    parseLine( false );
}

void Minicli::parseLine( bool final )
{
    QString cmd = m_runCombo->currentText().stripWhiteSpace();

    m_filterData->setData( cmd );

    // Update checkbox if Advanced is visible
    if (m_bAdvanced)
    {
        if ( !m_runCombo->currentText().isEmpty())
        {
           QString cmdNoArgs;
           if ( m_filterData->uri().isLocalFile() )
              cmdNoArgs = m_filterData->uri().path();
           else
              cmdNoArgs = m_filterData->uri().url();
           int i = cmdNoArgs.find(' ');
           if (i != -1)
               cmdNoArgs.truncate(i);
         }
    }

    // Change the icon according to the command type.
    if( mpAdvanced && mpAdvanced->terminal() )
    {
        m_IconName = QString::fromLatin1( "konsole" );
    }
    else
    {
        if( final )
            KURIFilter::self()->filterURI( *(m_filterData), finalFilters );
        else
            KURIFilter::self()->filterURI( *(m_filterData), middleFilters );
        m_IconName = m_filterData->iconName();
        if( m_IconName.isEmpty() || m_IconName == "unknown" )
            m_IconName = QString::fromLatin1("go");
    }
    QPixmap icon = DesktopIcon( m_IconName );
    if ( m_IconName == "www" )
    {
        // Not using KIconEffect::overlay as that requires the same size
        // for the icon and the overlay, also the overlay definately doesn't
        // have a more that one-bit alpha channel here
        QPixmap overlay( locate ( "icon", KMimeType::favIconForURL( m_filterData->uri() ) + ".png" ) );
        if ( !overlay.isNull() )
        {
            int x = icon.width() - overlay.width(),
                y = icon.height() - overlay.height();
            if ( icon.mask() )
            {
                QBitmap mask = *icon.mask();
                bitBlt( &mask, x, y,
                        overlay.mask() ? const_cast<QBitmap *>(overlay.mask()) : &overlay,
                        0, 0, overlay.width(), overlay.height(),
                        overlay.mask() ? OrROP : SetROP );
                icon.setMask(mask);
            }
            bitBlt( &icon, x, y, &overlay );
        }
    }
    m_runIcon->setPixmap( icon );
}



MinicliAdvanced::MinicliAdvanced(QWidget *parent, const char *name)
                :QGroupBox(parent, name)
{
    setTitle(i18n("Advanced Settings"));
    QBoxLayout *top = new QVBoxLayout( this, KDialog::marginHint(),
                                       KDialog::spacingHint() );
    top->addSpacing( fontMetrics().lineSpacing() );
    mpCBTerm = new QCheckBox(i18n("Run in &terminal"), this);
    QWhatsThis::add(mpCBTerm, i18n("Check this option if the application you "
                                   "want to run is a text mode application. The "
                                   "application will then be run in a terminal "
                                   "emulator window."));
    connect(mpCBTerm, SIGNAL(toggled(bool)), SLOT(slotTerminal(bool)));
    top->addWidget(mpCBTerm, AlignLeft);
    mpCBUser = new QCheckBox(i18n("Run as a different &user"), this);
    QWhatsThis::add(mpCBUser, i18n("Check this option if you want to run the "
                                   "application with a different user id. "
                                   "Every process has a user id associated with "
                                   "it. This id code determines file access and "
                                   "other permissions. The password of the user "
                                   "is required to do this."));
    connect(mpCBUser, SIGNAL(toggled(bool)), SLOT(slotChangeUid(bool)));
    top->addWidget(mpCBUser, AlignLeft);
    QBoxLayout *hbox = new QHBoxLayout(0L, KDialog::marginHint(), KDialog::spacingHint());
    top->addLayout(hbox);
    hbox->addSpacing( KDialog::spacingHint() );
    mpEditLabel = new QLabel(i18n("User&name:"), this);
    hbox->addWidget(mpEditLabel);
    mpEdit = new KLineEdit(this);
    QWhatsThis::add(mpEdit, i18n("Enter the user here who you want to run the "
                                 "application as."));
    mpEditLabel->setBuddy(mpEdit);
    connect(mpEdit, SIGNAL(textChanged(const QString &)),
            SLOT(slotUsername(const QString &)));
    hbox->addWidget(mpEdit);
    hbox->addStretch();
    mpCBPrio = new QCheckBox(i18n("Run with a different &priority"), this);
    QWhatsThis::add(mpCBPrio, i18n("Check this option if you want to run the "
                                   "application with a different priority. A "
                                   "higher priority tells the operating system "
                                   "to give more processing time to your "
                                   "application."));
    top->addWidget(mpCBPrio, AlignLeft);
    connect(mpCBPrio, SIGNAL(toggled(bool)), SLOT(slotChangeScheduler(bool)));

    hbox = new QHBoxLayout(0L, KDialog::marginHint(), KDialog::spacingHint());
    top->addLayout(hbox);
    hbox->addSpacing( KDialog::spacingHint() );
    priorityLowLbl = new QLabel(i18n("Low"), this);
    hbox->addWidget(priorityLowLbl);
    mpSlider = new QSlider(0, 100, 10, 50, QSlider::Horizontal, this);
    QWhatsThis::add(mpSlider, i18n("The priority can be set here. From left "
                                   "to right, it goes from low to high. The "
                                   "center position is the default value. For "
                                   "priorities higher than the default, you "
                                   "will need root's password."));
    mpSlider->setLineStep(10);
    connect(mpSlider, SIGNAL(valueChanged(int)), SLOT(slotPriority(int)));
    hbox->addWidget(mpSlider);
    priorityHighLbl = new QLabel(i18n("High"), this);
    hbox->addWidget(priorityHighLbl);
    hbox->addStretch();

    hbox = new QHBoxLayout(0L, KDialog::marginHint(), KDialog::spacingHint());
    top->addLayout(hbox);
    hbox->addSpacing(KDialog::spacingHint());
    mpComboLabel = new QLabel(i18n("&Scheduler:"), this);
    hbox->addWidget(mpComboLabel);
    mpCombo = new KComboBox(this);
// does completion make sense for a read-only combobox?
//    mpCombo->completionBox()->setTabHandling( true );
    QWhatsThis::add(mpCombo, i18n("Here you can select which scheduler to use "
                                  "for the application. The scheduler governs "
                                  "which process will run and which will have "
                                  "to wait. Two schedulers are available:"
                                  "<ul><li><em>Normal:</em> This is the "
                                  "standard, timesharing scheduler. It will "
                                  "divide fairly the available processing time "
                                  "between all processes. </li><li><em>Realtime:</em> "
                                  "This scheduler will run your application "
                                  "uninterrupted until it gives up the processor. "
                                  "This can be dangerous. An application that does "
                                  "not give up the processor might hang the system. "
                                  "You need root's password to use the scheduler."));
    mpComboLabel->setBuddy(mpCombo);
    connect(mpCombo, SIGNAL(activated(int)), SLOT(slotScheduler(int)));
    hbox->addWidget(mpCombo);
    hbox->addStretch();

    mpAuthLabel = new QLabel(this);
    top->addWidget(mpAuthLabel);
    hbox = new QHBoxLayout(0L, KDialog::marginHint(), KDialog::spacingHint());
    hbox->addSpacing( KDialog::spacingHint() );
    top->addLayout(hbox);
    mpPasswordLabel = new QLabel(i18n("Pass&word:"), this);
    hbox->addWidget(mpPasswordLabel);
    mpPassword = new KPasswordEdit(this);
    QWhatsThis::add(mpPassword, i18n("Enter the requested password here."));
    mpPasswordLabel->setBuddy(mpPassword);
    hbox->addWidget(mpPassword);
    hbox->addStretch();

    // Provide username completion up to 1000 users.
    KCompletion *completion = new KCompletion;
    completion->setOrder(KCompletion::Sorted);
    struct passwd *pw;
    int i, maxEntries = 1000;
    setpwent();
    for (i=0; ((pw = getpwent()) != 0L) && (i < maxEntries); i++)
        completion->addItem(QString::fromLocal8Bit(pw->pw_name));
    endpwent();
    if (i < maxEntries)
    {
        mpEdit->setCompletionObject(completion, true);
        mpEdit->setCompletionMode(KGlobalSettings::completionMode());
        mpEdit->setAutoDeleteCompletionObject( true );
    }
    else
        delete completion;

    mpCombo->insertItem(i18n("Normal"), StubProcess::SchedNormal);
    mpCombo->insertItem(i18n("Realtime"), StubProcess::SchedRealtime);
    mpEdit->setEnabled(false);
    mpEditLabel->setEnabled(false);
    mpCombo->setEnabled(false);
    mpComboLabel->setEnabled(false);
    mpSlider->setEnabled(false);
    priorityLowLbl->setEnabled(false);
    priorityHighLbl->setEnabled(false);
    reset();
}

MinicliAdvanced::~MinicliAdvanced()
{
}

void MinicliAdvanced::updateAuthLabel()
{
    QString authUser;
    if (mbChangeScheduler && (mPriority > 50) || (mScheduler != StubProcess::SchedNormal))
    {
        authUser = QString::fromLatin1("\"root\"");
        mpPassword->setEnabled(true);
        mpPasswordLabel->setEnabled(true);
    } else if (mbChangeUid && !mUsername.isEmpty())
    {
        authUser = "\""+mpEdit->text()+"\"";
        mpPassword->setEnabled(true);
        mpPasswordLabel->setEnabled(true);
    } else
    {
        authUser = i18n("none");
        mpPassword->setEnabled(false);
        mpPasswordLabel->setEnabled(false);
    }
    mpAuthLabel->setText(i18n("Password required: %1").arg(authUser));
}

void MinicliAdvanced::slotTerminal(bool ena)
{
    mbTerminal = ena;
    mpCBTerm->setChecked(ena);
}

void MinicliAdvanced::slotChangeUid(bool ena)
{
    mbChangeUid = ena;
    mpEdit->setEnabled(ena);
    mpEditLabel->setEnabled(ena);
    if(ena)
    {
        mpEdit->selectAll();
        mpEdit->setFocus();
    }
    updateAuthLabel();
}

void MinicliAdvanced::slotUsername(const QString &name)
{
    //kdDebug(1207) << "text: " << mpEdit->text() << endl;
    mUsername = name;
    updateAuthLabel();
}

void MinicliAdvanced::slotChangeScheduler(bool ena)
{
    mbChangeScheduler = ena;
    mpCombo->setEnabled(ena);
    mpComboLabel->setEnabled(ena);
    mpSlider->setEnabled(ena);
    priorityLowLbl->setEnabled(ena);
    priorityHighLbl->setEnabled(ena);
    updateAuthLabel();
}

bool MinicliAdvanced::needsKDEsu()
{
    return ((mbChangeScheduler && ((mPriority > 50) || (mScheduler != StubProcess::SchedNormal)))
            || (mbChangeUid && !mUsername.isEmpty()));
}

void MinicliAdvanced::slotScheduler(int scheduler)
{
    mScheduler = scheduler;
    if (mScheduler == StubProcess::SchedRealtime)
    {
        // we have to set the flags of the toplevelwidget, not ours
        KWin::clearState( topLevelWidget()->winId(), NET::StaysOnTop );
        if (KMessageBox::warningContinueCancel(this,
                    i18n("Running a realtime application can be very dangerous. "
                         "If the application misbehaves, the system might hang "
                         "unrecoverably.\nAre you sure you want to continue?"),
                    i18n("Warning - Run Command"))
            != KMessageBox::Continue )
        {
          mScheduler = StubProcess::SchedNormal;
          mpCombo->setCurrentItem(mScheduler);
        }
        KWin::setState( topLevelWidget()->winId(), NET::StaysOnTop );
    }
    updateAuthLabel();
}

void MinicliAdvanced::slotPriority(int priority)
{
    // Provide a way to easily return to the default priority
    if ((priority > 40) && (priority < 60))
    {
        priority = 50;
        mpSlider->setValue(50);
    }
    mPriority = priority;
    updateAuthLabel();
}

const char *MinicliAdvanced::password()
{
    return mpPassword->password();
}

void MinicliAdvanced::reset()
{
    slotTerminal(false);
    mbChangeUid = false;
    mpCBUser->setChecked(false);
    mUsername = "root";
    mpEdit->setText(mUsername);
    mbChangeScheduler = false;
    mpCBPrio->setChecked(false);
    mPriority = 50;
    mpSlider->setValue(mPriority);
    mScheduler = StubProcess::SchedNormal;
    mpCombo->setCurrentItem(mScheduler);
    mpPassword->erase();
    updateAuthLabel();
}
