/*
 * Copyright Johannes Sixt
 * This file is licensed under the GNU General Public License Version 2.
 * See the file COPYING in the toplevel directory of the source directory.
 */

#include "procattach.h"
#include <tqlistview.h>
#include <tqtoolbutton.h>
#include <tqlineedit.h>
#include <kprocess.h>
#include <ctype.h>
#include <tdeapplication.h>
#include <kiconloader.h>
#include <tdelocale.h>			/* i18n */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif


ProcAttachPS::ProcAttachPS(TQWidget* parent) :
	ProcAttachBase(parent),
	m_pidCol(-1),
	m_ppidCol(-1)
{
    m_ps = new TDEProcess;
    connect(m_ps, SIGNAL(receivedStdout(TDEProcess*, char*, int)),
	    this, SLOT(slotTextReceived(TDEProcess*, char*, int)));
    connect(m_ps, SIGNAL(processExited(TDEProcess*)),
	    this, SLOT(slotPSDone()));

    TQIconSet icon = SmallIconSet("clear_left");
    filterClear->setIconSet(icon);

    processList->setColumnWidth(0, 300);
    processList->setColumnWidthMode(0, TQListView::Manual);
    processList->setColumnAlignment(1, TQt::AlignRight);
    processList->setColumnAlignment(2, TQt::AlignRight);

    // set the command line
    static const char* const psCommand[] = {
#ifdef PS_COMMAND
	PS_COMMAND,
#else
	"/bin/false",
#endif
	0
    };
    for (int i = 0; psCommand[i] != 0; i++) {
	*m_ps << psCommand[i];
    }

    runPS();
}

ProcAttachPS::~ProcAttachPS()
{
    delete m_ps;	// kills a running ps
}

void ProcAttachPS::runPS()
{
    // clear the parse state from previous runs
    m_token = "";
    m_line.clear();
    m_pidCol = -1;
    m_ppidCol = -1;

    m_ps->start(TDEProcess::NotifyOnExit, TDEProcess::Stdout);
}

void ProcAttachPS::slotTextReceived(TDEProcess*, char* buffer, int buflen)
{
    const char* end = buffer+buflen;
    while (buffer < end)
    {
	// check new line
	if (*buffer == '\n')
	{
	    // push a tokens onto the line
	    if (!m_token.isEmpty()) {
		m_line.push_back(TQString::fromLatin1(m_token));
		m_token = "";
	    }
	    // and insert the line in the list
	    pushLine();
	    m_line.clear();
	    ++buffer;
	}
	// blanks: the last column gets the rest of the line, including blanks
	else if ((m_pidCol < 0 || int(m_line.size()) < processList->columns()-1) &&
		 isspace(*buffer))
	{
	    // push a token onto the line
	    if (!m_token.isEmpty()) {
		m_line.push_back(TQString::fromLatin1(m_token));
		m_token = "";
	    }
	    do {
		++buffer;
	    } while (buffer < end && isspace(*buffer));
	}
	// tokens
	else
	{
	    const char* start = buffer;
	    do {
		++buffer;
	    } while (buffer < end && !isspace(*buffer));
	    // append to the current token
	    m_token += TQCString(start, buffer-start+1);	// must count the '\0'
	}
    }
}

void ProcAttachPS::pushLine()
{
    if (m_line.size() < 3)	// we need the PID, PPID, and COMMAND columns
	return;

    if (m_pidCol < 0)
    {
	// create columns if we don't have them yet
	bool allocate =	processList->columns() == 3;

	// we assume that the last column is the command
	m_line.pop_back();

	for (uint i = 0; i < m_line.size(); i++) {
	    // we don't allocate the PID and PPID columns,
	    // but we need to know where in the ps output they are
	    if (m_line[i] == "PID") {
		m_pidCol = i;
	    } else if (m_line[i] == "PPID") {
		m_ppidCol = i;
	    } else if (allocate) {
		processList->addColumn(m_line[i]);
		// these columns are normally numbers
		processList->setColumnAlignment(processList->columns()-1,
						TQt::AlignRight);
	    }
	}
    }
    else
    {
	// insert a line
	// find the parent process
	TQListViewItem* parent = 0;
	if (m_ppidCol >= 0 && m_ppidCol < int(m_line.size())) {
	    parent = processList->findItem(m_line[m_ppidCol], 1);
	}

	// we assume that the last column is the command
	TQListViewItem* item;
	if (parent == 0) {
	    item = new TQListViewItem(processList, m_line.back());
	} else {
	    item = new TQListViewItem(parent, m_line.back());
	}
	item->setOpen(true);
	m_line.pop_back();
	int k = 3;
	for (uint i = 0; i < m_line.size(); i++)
	{
	    // display the pid and ppid columns' contents in columns 1 and 2
	    if (int(i) == m_pidCol)
		item->setText(1, m_line[i]);
	    else if (int(i) == m_ppidCol)
		item->setText(2, m_line[i]);
	    else
		item->setText(k++, m_line[i]);
	}

	if (m_ppidCol >= 0 && m_pidCol >= 0) {	// need PID & PPID for this
	    /*
	     * It could have happened that a process was earlier inserted,
	     * whose parent process is the current process. Such processes
	     * were placed at the root. Here we go through all root items
	     * and check whether we must reparent them.
	     */
	    TQListViewItem* i = processList->firstChild();
	    while (i != 0)
	    {
		// advance before we reparent the item
		TQListViewItem* it = i;
		i = i->nextSibling();
		if (it->text(2) == m_line[m_pidCol]) {
		    processList->takeItem(it);
		    item->insertItem(it);
		}
	    }
	}
    }
}

void ProcAttachPS::slotPSDone()
{
    filterEdited(filterEdit->text());
}

TQString ProcAttachPS::text() const
{
    TQListViewItem* item = processList->selectedItem();

    if (item == 0)
	return TQString();

    return item->text(1);
}

void ProcAttachPS::refresh()
{
    if (!m_ps->isRunning())
    {
	processList->clear();
	buttonOk->setEnabled(false);	// selection was cleared
	runPS();
    }
}

void ProcAttachPS::filterEdited(const TQString& text)
{
    TQListViewItem* i = processList->firstChild();
    if (i) {
	setVisibility(i, text);
    }
}

/**
 * Sets the visibility of \a i and
 * returns whether it was made visible.
 */
bool ProcAttachPS::setVisibility(TQListViewItem* i, const TQString& text)
{
    bool visible = false;
    for (TQListViewItem* j = i->firstChild(); j; j = j->nextSibling())
    {
	if (setVisibility(j, text))
	    visible = true;
    }
    // look for text in the process name and in the PID
    visible = visible || text.isEmpty() ||
	i->text(0).find(text, 0, false) >= 0 ||
	i->text(1).find(text) >= 0;

    i->setVisible(visible);

    // disable the OK button if the selected item becomes invisible
    if (i->isSelected())
	buttonOk->setEnabled(visible);

    return visible;
}

void ProcAttachPS::selectedChanged()
{
    buttonOk->setEnabled(processList->selectedItem() != 0);
}


ProcAttach::ProcAttach(TQWidget* parent) :
	TQDialog(parent, "procattach", true),
	m_label(this, "label"),
	m_processId(this, "procid"),
	m_buttonOK(this, "ok"),
	m_buttonCancel(this, "cancel"),
	m_layout(this, 8),
	m_buttons(4)
{
    TQString title = kapp->caption();
    title += i18n(": Attach to process");
    setCaption(title);

    m_label.setMinimumSize(330, 24);
    m_label.setText(i18n("Specify the process number to attach to:"));

    m_processId.setMinimumSize(330, 24);
    m_processId.setMaxLength(100);
    m_processId.setFrame(true);

    m_buttonOK.setMinimumSize(100, 30);
    connect(&m_buttonOK, SIGNAL(clicked()), SLOT(accept()));
    m_buttonOK.setText(i18n("OK"));
    m_buttonOK.setDefault(true);

    m_buttonCancel.setMinimumSize(100, 30);
    connect(&m_buttonCancel, SIGNAL(clicked()), SLOT(reject()));
    m_buttonCancel.setText(i18n("Cancel"));

    m_layout.addWidget(&m_label);
    m_layout.addWidget(&m_processId);
    m_layout.addLayout(&m_buttons);
    m_layout.addStretch(10);
    m_buttons.addStretch(10);
    m_buttons.addWidget(&m_buttonOK);
    m_buttons.addSpacing(40);
    m_buttons.addWidget(&m_buttonCancel);
    m_buttons.addStretch(10);

    m_layout.activate();

    m_processId.setFocus();
    resize(350, 120);
}

ProcAttach::~ProcAttach()
{
}

#include "procattach.moc"
