/* This file is part of the KDE project
   Copyright (C) 1999-2001 Bernd Gehrmann <bernd@kdevelop.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "processwidget.h"
#include "processlinemaker.h"

#include <tqdir.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kprocess.h>
#include <tqpainter.h>
#include <tqapplication.h>


ProcessListBoxItem::ProcessListBoxItem(const TQString &s, Type type)
    : TQListBoxText(s), t(type)
{
    TQString clean = s;
    clean.replace( TQChar('\t'), TQString("  ") );
    clean.replace( TQChar('\n'), TQString() );
    clean.replace( TQChar('\r'), TQString() );
    setText( clean );

    setCustomHighlighting(true);
}


bool ProcessListBoxItem::isCustomItem()
{
    return false;
}

static inline unsigned char normalize(int a)
{
    return (a < 0 ? 0 : a > 255 ? 255 : a);
}

static inline double blend1(double a, double b, double k)
{
    return a + (b - a) * k;
}

TQColor ProcessListBoxItem::blend(const TQColor &c1, const TQColor &c2, double k) const
{
    if (k < 0.0) return c1;
    if (k > 1.0) return c2;

    int r = normalize((int)blend1((double)c1.red(),   (double)c2.red(),   k));
    int g = normalize((int)blend1((double)c1.green(), (double)c2.green(), k));
    int b = normalize((int)blend1((double)c1.blue(),  (double)c2.blue(),  k));

    return TQColor(tqRgb(r, g, b));
}

void ProcessListBoxItem::paint(TQPainter *p)
{
    TQColor dim, warn, err, back;
    if (listBox()) {
        const TQColorGroup& group = listBox()->palette().active();
        if (isSelected()) {
            back = group.button();
            warn = group.buttonText();
        }
        else
        {
            back = group.base();
            warn = group.text();
        }
        err = group.linkVisited();
        dim = blend(warn, back);
    }
    else
    {
        warn = TQt::black;
        dim = TQt::darkBlue;
        err = TQt::darkRed;
        if (isSelected())
            back = TQt::lightGray;
        else
            back = TQt::white;
    }
    p->fillRect(p->window(), TQBrush(back));
    p->setPen((t==Error)? err :
              (t==Diagnostic)? warn : dim);
    TQListBoxText::paint(p);
}


ProcessWidget::ProcessWidget(TQWidget *parent, const char *name)
    : TDEListBox(parent, name)
{
    setFocusPolicy(TQWidget::NoFocus);

    // Don't override the palette, as that can mess up styles. Instead, draw
    // the background ourselves (see ProcessListBoxItem::paint).


    childproc = new TDEProcess();
    childproc->setUseShell(true);

    procLineMaker = new ProcessLineMaker( childproc );

    connect( procLineMaker, TQ_SIGNAL(receivedStdoutLine(const TQCString&)),
             this, TQ_SLOT(insertStdoutLine(const TQCString&) ));
    connect( procLineMaker, TQ_SIGNAL(receivedStderrLine(const TQCString&)),
             this, TQ_SLOT(insertStderrLine(const TQCString&) ));
    connect( procLineMaker, TQ_SIGNAL(receivedPartialStdoutLine(const TQCString&)),
             this, TQ_SLOT(addPartialStdoutLine(const TQCString&) ));
    connect( procLineMaker, TQ_SIGNAL(receivedPartialStderrLine(const TQCString&)),
             this, TQ_SLOT(addPartialStderrLine(const TQCString&) ));

    connect(childproc, TQ_SIGNAL(processExited(TDEProcess*)),
            this, TQ_SLOT(slotProcessExited(TDEProcess*) )) ;
}


ProcessWidget::~ProcessWidget()
{
    delete childproc;
    delete procLineMaker;
}


void ProcessWidget::startJob(const TQString &dir, const TQString &command)
{
    procLineMaker->clearBuffers();
    procLineMaker->blockSignals( false );

    clear();
    insertItem(new ProcessListBoxItem(command, ProcessListBoxItem::Diagnostic));
    childproc->clearArguments();
    if (!dir.isNull()) {
        childproc->setWorkingDirectory( dir );
    }

    *childproc << command;
    childproc->start(TDEProcess::OwnGroup, TDEProcess::AllOutput);
}


void ProcessWidget::killJob( int signo )
{
    procLineMaker->blockSignals( true );

	childproc->kill( signo );
}


bool ProcessWidget::isRunning()
{
    return childproc->isRunning();
}


void ProcessWidget::slotProcessExited(TDEProcess *)
{
    procLineMaker->flush();
    if( !stdoutbuf.isEmpty() ) 
        insertStdoutLine("");
    if( !stderrbuf.isEmpty() ) 
        insertStderrLine("");
    childFinished(childproc->normalExit(), childproc->exitStatus());
    maybeScrollToBottom();
    emit processExited(childproc);
}


void ProcessWidget::insertStdoutLine(const TQCString &line)
{
    if( !stdoutbuf.isEmpty() )
    {
        stdoutbuf += line;
        insertItem( new ProcessListBoxItem( TQString::fromLocal8Bit(stdoutbuf),
                                            ProcessListBoxItem::Normal ),
                    lastRowStdout+1 );
        stdoutbuf.truncate( 0 );
    }else
    {
        insertItem( new ProcessListBoxItem( TQString::fromLocal8Bit( line ),
                                            ProcessListBoxItem::Normal) );
    }
    lastRowStdout = count() - 1;
    maybeScrollToBottom();
}


void ProcessWidget::insertStderrLine(const TQCString &line)
{
    if( !stderrbuf.isEmpty() )
    {
        stderrbuf += line;
        insertItem( new ProcessListBoxItem( TQString::fromLocal8Bit( stderrbuf ),
                                            ProcessListBoxItem::Error ),
                    lastRowStderr+1 );
        stderrbuf.truncate( 0 );
    } else
    {
        insertItem( new ProcessListBoxItem( TQString::fromLocal8Bit( line ),
                                            ProcessListBoxItem::Error) );
    }
    lastRowStderr = count() - 1;
    maybeScrollToBottom();
}


void ProcessWidget::childFinished(bool normal, int status)
{
    TQString s;
    ProcessListBoxItem::Type t;

    if (normal) {
        if (status) {
            s = i18n("*** Exited with status: %1 ***").arg(status);
            t = ProcessListBoxItem::Error;
        } else {
            s = i18n("*** Exited normally ***");
            t = ProcessListBoxItem::Diagnostic;
        }
    } else {
      if ( childproc->signalled() && childproc->exitSignal() == SIGSEGV )
      {
        s = i18n("*** Process aborted. Segmentation fault ***");
      }
      else
      {
        s = i18n("*** Process aborted ***");
      }
        t = ProcessListBoxItem::Error;
    }

    insertItem(new ProcessListBoxItem(s, t));
}


TQSize ProcessWidget::minimumSizeHint() const
{
    // I'm not sure about this, but when I don't use override minimumSizeHint(),
    // the initial size in clearly too small

    return TQSize( TQListBox::sizeHint().width(),
                  (fontMetrics().lineSpacing()+2)*4 );
}

/** Should be called right after an insertItem(),
   will automatic scroll the listbox if it is already at the bottom
   to prevent automatic scrolling when the user has scrolled up
*/
void ProcessWidget::maybeScrollToBottom()
{
    if ( verticalScrollBar()->value() == verticalScrollBar()->maxValue() )
    {
        setBottomItem( count() -1 );
    }
}

void ProcessWidget::addPartialStderrLine(const TQCString& linepart)
{
    stderrbuf += linepart;
}

void ProcessWidget::addPartialStdoutLine(const TQCString& linepart)
{
    stdoutbuf += linepart;
}

#include "processwidget.moc"
