You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
11 KiB
377 lines
11 KiB
15 years ago
|
// *************************************************************************
|
||
|
// gdboutputwidget.cpp - description
|
||
|
// -------------------
|
||
|
// begin : 10th April 2003
|
||
|
// copyright : (C) 2003 by John Birch
|
||
|
// email : jbb@kdevelop.org
|
||
|
// **************************************************************************
|
||
|
//
|
||
|
// **************************************************************************
|
||
|
// * *
|
||
|
// * 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. *
|
||
|
// * *
|
||
|
// **************************************************************************
|
||
|
|
||
|
#include "gdboutputwidget.h"
|
||
|
#include "dbgcontroller.h"
|
||
|
|
||
|
#include <kcombobox.h>
|
||
|
#include <kdebug.h>
|
||
|
#include <kiconloader.h>
|
||
|
#include <klocale.h>
|
||
|
#include <kpopupmenu.h>
|
||
|
|
||
|
#include <qlabel.h>
|
||
|
#include <qlayout.h>
|
||
|
#include <qtextedit.h>
|
||
|
#include <qtoolbutton.h>
|
||
|
#include <qtooltip.h>
|
||
|
#include <qapplication.h>
|
||
|
#include <qclipboard.h>
|
||
|
#include <qdom.h>
|
||
|
|
||
|
|
||
|
namespace GDBDebugger
|
||
|
{
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
GDBOutputWidget::GDBOutputWidget( QWidget *parent, const char *name) :
|
||
|
QWidget(parent, name),
|
||
|
m_userGDBCmdEditor(0),
|
||
|
m_Interrupt(0),
|
||
|
m_gdbView(0),
|
||
|
showInternalCommands_(false),
|
||
|
maxLines_(5000)
|
||
|
{
|
||
|
|
||
|
m_gdbView = new OutputText(this);
|
||
|
m_gdbView->setTextFormat(QTextEdit::LogText);
|
||
|
|
||
|
QBoxLayout *userGDBCmdEntry = new QHBoxLayout();
|
||
|
m_userGDBCmdEditor = new KHistoryCombo (this, "gdb-user-cmd-editor");
|
||
|
|
||
|
QLabel *label = new QLabel(i18n("&GDB cmd:"), this);
|
||
|
label->setBuddy(m_userGDBCmdEditor);
|
||
|
userGDBCmdEntry->addWidget(label);
|
||
|
|
||
|
userGDBCmdEntry->addWidget(m_userGDBCmdEditor);
|
||
|
userGDBCmdEntry->setStretchFactor(m_userGDBCmdEditor, 1);
|
||
|
|
||
|
m_Interrupt = new QToolButton( this, "add breakpoint" );
|
||
|
m_Interrupt->setSizePolicy ( QSizePolicy ( (QSizePolicy::SizeType)0,
|
||
|
( QSizePolicy::SizeType)0,
|
||
|
0,
|
||
|
0,
|
||
|
m_Interrupt->sizePolicy().hasHeightForWidth())
|
||
|
);
|
||
|
m_Interrupt->setPixmap ( SmallIcon ( "player_pause" ) );
|
||
|
userGDBCmdEntry->addWidget(m_Interrupt);
|
||
|
QToolTip::add ( m_Interrupt, i18n ( "Pause execution of the app to enter gdb commands" ) );
|
||
|
|
||
|
QVBoxLayout *topLayout = new QVBoxLayout(this, 2);
|
||
|
topLayout->addWidget(m_gdbView, 10);
|
||
|
topLayout->addLayout(userGDBCmdEntry);
|
||
|
|
||
|
slotDbgStatus( "", s_dbgNotStarted);
|
||
|
|
||
|
connect( m_userGDBCmdEditor, SIGNAL(returnPressed()), SLOT(slotGDBCmd()) );
|
||
|
connect( m_Interrupt, SIGNAL(clicked()), SIGNAL(breakInto()));
|
||
|
|
||
|
connect( &updateTimer_, SIGNAL(timeout()),
|
||
|
this, SLOT(flushPending()));
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
GDBOutputWidget::~GDBOutputWidget()
|
||
|
{
|
||
|
delete m_gdbView;
|
||
|
delete m_userGDBCmdEditor;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
void GDBOutputWidget::clear()
|
||
|
{
|
||
|
if (m_gdbView)
|
||
|
m_gdbView->clear();
|
||
|
|
||
|
userCommands_.clear();
|
||
|
allCommands_.clear();
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
void GDBOutputWidget::slotInternalCommandStdout(const char* line)
|
||
|
{
|
||
|
newStdoutLine(line, true);
|
||
|
}
|
||
|
|
||
|
void GDBOutputWidget::slotUserCommandStdout(const char* line)
|
||
|
{
|
||
|
newStdoutLine(line, false);
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
QString colorify(QString text, const QString& color)
|
||
|
{
|
||
|
// Make sure the newline is at the end of the newly-added
|
||
|
// string. This is so that we can always correctly remove
|
||
|
// newline inside 'flushPending'.
|
||
|
Q_ASSERT(text.endsWith("\n"));
|
||
|
if (text.endsWith("\n"))
|
||
|
{
|
||
|
text.remove(text.length()-1, 1);
|
||
|
}
|
||
|
text = "<font color=\"" + color + "\">" + text + "</font>\n";
|
||
|
return text;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void GDBOutputWidget::newStdoutLine(const QString& line,
|
||
|
bool internal)
|
||
|
{
|
||
|
QString s = html_escape(line);
|
||
|
if (s.startsWith("(gdb)"))
|
||
|
{
|
||
|
s = colorify(s, "blue");
|
||
|
}
|
||
|
|
||
|
allCommands_.append(s);
|
||
|
allCommandsRaw_.append(line);
|
||
|
trimList(allCommands_, maxLines_);
|
||
|
trimList(allCommandsRaw_, maxLines_);
|
||
|
|
||
|
if (!internal)
|
||
|
{
|
||
|
userCommands_.append(s);
|
||
|
userCommandsRaw_.append(line);
|
||
|
trimList(userCommands_, maxLines_);
|
||
|
trimList(userCommandsRaw_, maxLines_);
|
||
|
}
|
||
|
|
||
|
if (!internal || showInternalCommands_)
|
||
|
showLine(s);
|
||
|
}
|
||
|
|
||
|
|
||
|
void GDBOutputWidget::showLine(const QString& line)
|
||
|
{
|
||
|
pendingOutput_ += line;
|
||
|
|
||
|
// To improve performance, we update the view after some delay.
|
||
|
if (!updateTimer_.isActive())
|
||
|
{
|
||
|
updateTimer_.start(100, true /* single shot */);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GDBOutputWidget::trimList(QStringList& l, unsigned max_size)
|
||
|
{
|
||
|
unsigned int length = l.count();
|
||
|
if (length > max_size)
|
||
|
{
|
||
|
for(int to_delete = length - max_size; to_delete; --to_delete)
|
||
|
{
|
||
|
l.erase(l.begin());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GDBOutputWidget::setShowInternalCommands(bool show)
|
||
|
{
|
||
|
if (show != showInternalCommands_)
|
||
|
{
|
||
|
showInternalCommands_ = show;
|
||
|
|
||
|
// Set of strings to show changes, text edit still has old
|
||
|
// set. Refresh.
|
||
|
m_gdbView->clear();
|
||
|
QStringList& newList =
|
||
|
showInternalCommands_ ? allCommands_ : userCommands_;
|
||
|
|
||
|
QStringList::iterator i = newList.begin(), e = newList.end();
|
||
|
for(; i != e; ++i)
|
||
|
{
|
||
|
// Note that color formatting is already applied to '*i'.
|
||
|
showLine(*i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
void GDBOutputWidget::slotReceivedStderr(const char* line)
|
||
|
{
|
||
|
QString colored = colorify(html_escape(line), "red");
|
||
|
// Errors are shown inside user commands too.
|
||
|
allCommands_.append(colored);
|
||
|
trimList(allCommands_, maxLines_);
|
||
|
userCommands_.append(colored);
|
||
|
trimList(userCommands_, maxLines_);
|
||
|
|
||
|
allCommandsRaw_.append(line);
|
||
|
trimList(allCommandsRaw_, maxLines_);
|
||
|
userCommandsRaw_.append(line);
|
||
|
trimList(userCommandsRaw_, maxLines_);
|
||
|
|
||
|
showLine(colored);
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
void GDBOutputWidget::slotGDBCmd()
|
||
|
{
|
||
|
QString GDBCmd(m_userGDBCmdEditor->currentText());
|
||
|
if (!GDBCmd.isEmpty())
|
||
|
{
|
||
|
m_userGDBCmdEditor->addToHistory(GDBCmd);
|
||
|
m_userGDBCmdEditor->clearEdit();
|
||
|
emit userGDBCmd(GDBCmd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GDBOutputWidget::flushPending()
|
||
|
{
|
||
|
m_gdbView->setUpdatesEnabled(false);
|
||
|
|
||
|
// QTextEdit adds newline after paragraph automatically.
|
||
|
// So, remove trailing newline to avoid double newlines.
|
||
|
if (pendingOutput_.endsWith("\n"))
|
||
|
pendingOutput_.remove(pendingOutput_.length()-1, 1);
|
||
|
Q_ASSERT(!pendingOutput_.endsWith("\n"));
|
||
|
|
||
|
m_gdbView->append(pendingOutput_);
|
||
|
pendingOutput_ = "";
|
||
|
|
||
|
m_gdbView->scrollToBottom();
|
||
|
m_gdbView->setUpdatesEnabled(true);
|
||
|
m_gdbView->update();
|
||
|
m_userGDBCmdEditor->setFocus();
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
void GDBOutputWidget::slotDbgStatus(const QString &, int statusFlag)
|
||
|
{
|
||
|
if (statusFlag & s_dbgNotStarted)
|
||
|
{
|
||
|
m_Interrupt->setEnabled(false);
|
||
|
m_userGDBCmdEditor->setEnabled(false);
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Interrupt->setEnabled(true);
|
||
|
}
|
||
|
|
||
|
if (statusFlag & s_dbgBusy)
|
||
|
{
|
||
|
m_userGDBCmdEditor->setEnabled(false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_userGDBCmdEditor->setEnabled(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|
||
|
void GDBOutputWidget::focusInEvent(QFocusEvent */*e*/)
|
||
|
{
|
||
|
m_gdbView->scrollToBottom();
|
||
|
m_userGDBCmdEditor->setFocus();
|
||
|
}
|
||
|
|
||
|
QString GDBOutputWidget::html_escape(const QString& s)
|
||
|
{
|
||
|
QString r(s);
|
||
|
r.replace("<", "<");
|
||
|
r.replace(">", ">");
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
void GDBOutputWidget::savePartialProjectSession(QDomElement* el)
|
||
|
{
|
||
|
QDomDocument doc = el->ownerDocument();
|
||
|
|
||
|
QDomElement showInternal = doc.createElement("showInternalCommands");
|
||
|
showInternal.setAttribute("value", QString::number(showInternalCommands_));
|
||
|
|
||
|
el->appendChild(showInternal);
|
||
|
}
|
||
|
|
||
|
void GDBOutputWidget::restorePartialProjectSession(const QDomElement* el)
|
||
|
{
|
||
|
QDomElement showInternal =
|
||
|
el->namedItem("showInternalCommands").toElement();
|
||
|
|
||
|
if (!showInternal.isNull())
|
||
|
{
|
||
|
showInternalCommands_ = showInternal.attribute("value", "0").toInt();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//void OutputText::contextMenuEvent(QContextMenuEvent* e)
|
||
|
QPopupMenu* OutputText::createPopupMenu(const QPoint&)
|
||
|
{
|
||
|
KPopupMenu* popup = new KPopupMenu;
|
||
|
|
||
|
int id = popup->insertItem(i18n("Show Internal Commands"),
|
||
|
this,
|
||
|
SLOT(toggleShowInternalCommands()));
|
||
|
|
||
|
popup->setItemChecked(id, parent_->showInternalCommands_);
|
||
|
popup->setWhatsThis(
|
||
|
id,
|
||
|
i18n(
|
||
|
"Controls if commands issued internally by KDevelop "
|
||
|
"will be shown or not.<br>"
|
||
|
"This option will affect only future commands, it will not "
|
||
|
"add or remove already issued commands from the view."));
|
||
|
|
||
|
popup->insertItem(i18n("Copy All"),
|
||
|
this,
|
||
|
SLOT(copyAll()));
|
||
|
|
||
|
|
||
|
return popup;
|
||
|
}
|
||
|
|
||
|
void OutputText::copyAll()
|
||
|
{
|
||
|
/* See comments for allCommandRaw_ for explanations of
|
||
|
this complex logic, as opposed to calling text(). */
|
||
|
QStringList& raw = parent_->showInternalCommands_ ?
|
||
|
parent_->allCommandsRaw_ : parent_->userCommandsRaw_;
|
||
|
QString text;
|
||
|
for (unsigned i = 0; i < raw.size(); ++i)
|
||
|
text += raw[i];
|
||
|
|
||
|
// Make sure the text is pastable both with Ctrl-C and with
|
||
|
// middle click.
|
||
|
QApplication::clipboard()->setText(text, QClipboard::Clipboard);
|
||
|
QApplication::clipboard()->setText(text, QClipboard::Selection);
|
||
|
}
|
||
|
|
||
|
void OutputText::toggleShowInternalCommands()
|
||
|
{
|
||
|
parent_->setShowInternalCommands(!parent_->showInternalCommands_);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************/
|
||
|
/***************************************************************************/
|
||
|
/***************************************************************************/
|
||
|
}
|
||
|
|
||
|
|
||
|
#include "gdboutputwidget.moc"
|
||
|
|