blob: 00050f9d63606857ecebac7d53c6d73f68a4756d [file] [log] [blame]
/*
* This file is part of the KDE libraries
* Copyright (C) 2000-2001 Harri Porten (porten@kde.org)
* Copyright (C) 2001 Peter Kelly (pmk@post.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "kjs_debugwin.h"
#ifdef KJS_DEBUGGER
#include <qlayout.h>
#include <qpushbutton.h>
#include <qtextedit.h>
#include <qlistbox.h>
#include <qlineedit.h>
#include <qapplication.h>
#include <qsplitter.h>
#include <qcombobox.h>
#include <qbitmap.h>
#include <qwidgetlist.h>
#include <klocale.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <kmessagebox.h>
#include "kjs_dom.h"
using namespace KJS;
static KJSDebugWin *kjs_html_debugger = 0;
bool FakeModal::eventFilter( QObject *o, QEvent *e )
{
switch (e->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::Destroy:
case QEvent::Close:
case QEvent::Quit:
while (o->parent())
o = o->parent();
if (o == modalWidget)
return QWidget::eventFilter( o, e );
else
return TRUE;
break;
default:
return QWidget::eventFilter( o, e );
}
}
void FakeModal::enable(QWidget *modal)
{
QWidgetList *widgets = QApplication::allWidgets();
QWidgetListIt it(*widgets);
for (; it.current(); ++it)
it.current()->installEventFilter(this);
modalWidget = modal;
}
void FakeModal::disable()
{
QWidgetList *widgets = QApplication::allWidgets();
QWidgetListIt it(*widgets);
for (; it.current(); ++it)
it.current()->removeEventFilter(this);
modalWidget = 0;
}
//-------------------------------------------------------------------------
QString StackFrame::toString()
{
QString str = "";
if (!name.isNull())
str.sprintf("%s() at sourceId %d, line %d",name.ascii(),sourceId,lineno);
else
str.sprintf("??? at sourceId %d, line %d",sourceId,lineno);
return str;
}
SourceFragment::SourceFragment(int sid, int bl, SourceFile *sf)
{
sourceId = sid;
baseLine = bl;
sourceFile = sf;
sourceFile->ref();
}
SourceFragment::~SourceFragment()
{
sourceFile->deref();
}
//-------------------------------------------------------------------------
KJSDebugWin::KJSDebugWin(QWidget *parent, const char *name)
: QWidget(parent, name),
m_inSession(false),
m_curSourceFile(0)
{
setCaption(i18n("JavaScript Debugger"));
QVBoxLayout *vl = new QVBoxLayout(this, 5);
// frame list & code
QSplitter *splitter = new QSplitter(this);
QFont font("courier",10);
m_frameList = new QListBox(splitter);
m_frameList->setFont(font);
m_frameList->setMinimumSize(100,200);
connect(m_frameList,SIGNAL(highlighted(int)),this,SLOT(showFrame(int)));
// source selection & display
QWidget *sourceSelDisplay = new QWidget(splitter);
QVBoxLayout *ssdvl = new QVBoxLayout(sourceSelDisplay);
m_sourceSel = new QComboBox(sourceSelDisplay);
connect(m_sourceSel,SIGNAL(activated(int)),this,SLOT(sourceSelected(int)));
ssdvl->addWidget(m_sourceSel);
m_sourceDisplay = new QListBox(sourceSelDisplay);
m_sourceDisplay->setFont(font);
ssdvl->addWidget(m_sourceDisplay);
vl->addWidget(splitter);
QValueList<int> splitSizes;
splitSizes.insert(splitSizes.end(),200);
splitSizes.insert(splitSizes.end(),400);
splitter->setSizes(splitSizes);
// evaluate
QHBoxLayout *hl1 = new QHBoxLayout(vl);
m_evalEdit = new QLineEdit(this);
m_evalButton = new QPushButton(i18n("&Evaluate"),this);
m_evalButton->setEnabled(false);
hl1->addWidget(m_evalEdit);
hl1->addWidget(m_evalButton);
connect(m_evalButton, SIGNAL(clicked()), SLOT(eval()));
connect(m_evalEdit, SIGNAL(returnPressed()), SLOT(eval()));
// control buttons
QHBoxLayout *hl2 = new QHBoxLayout(vl);
m_nextButton = new QPushButton(i18n("&Next"), this);
m_stepButton = new QPushButton(i18n("&Step"), this);
m_continueButton = new QPushButton(i18n("&Continue"), this);
m_stopButton = new QPushButton(i18n("St&op"), this);
hl2->addWidget(m_nextButton);
hl2->addWidget(m_stepButton);
hl2->addWidget(m_continueButton);
hl2->addWidget(m_stopButton);
hl2->addStretch();
connect(m_nextButton, SIGNAL(clicked()), SLOT(next()));
connect(m_stepButton, SIGNAL(clicked()), SLOT(step()));
connect(m_continueButton, SIGNAL(clicked()), SLOT(cont()));
connect(m_stopButton, SIGNAL(clicked()), SLOT(stop()));
m_nextButton->setEnabled(false);
m_stepButton->setEnabled(false);
m_continueButton->setEnabled(false);
m_stopButton->setEnabled(false);
// frame list
m_frames.setAutoDelete(true);
StackFrame *sf = new StackFrame(-1,-1,"Global code",false);
sf->next = true;
m_frames.append(sf);
// m_frameList->insertItem(sf->toString());
setMinimumSize(300,200);
resize(600,450);
m_mode = Continue;
m_sourceBreakpoints = 0;
KIconLoader loader;
m_stopIcon = loader.loadIcon("stop",KIcon::Small);
m_emptyIcon = QPixmap(m_stopIcon.width(),m_stopIcon.height());
QBitmap emptyMask(m_stopIcon.width(),m_stopIcon.height(),true);
// m_emptyIcon.fill(m_sourceDisplay,0,0);
m_emptyIcon.setMask(emptyMask);
m_nextSourceBaseLine = 0;
m_nextSourceUrl = "";
updateFrameList();
m_inSession = false;
m_curContext = 0;
m_curScript = 0;
}
KJSDebugWin::~KJSDebugWin()
{
// detach();
}
KJSDebugWin *KJSDebugWin::createInstance()
{
assert(!kjs_html_debugger);
kjs_html_debugger = new KJSDebugWin();
kjs_html_debugger->show();
return kjs_html_debugger;
}
void KJSDebugWin::destroyInstance()
{
assert(kjs_html_debugger);
kjs_html_debugger->hide();
delete kjs_html_debugger;
}
KJSDebugWin *KJSDebugWin::instance()
{
return kjs_html_debugger;
}
void KJSDebugWin::next()
{
m_mode = Next;
leaveSession();
}
void KJSDebugWin::step()
{
m_mode = Step;
leaveSession();
}
void KJSDebugWin::cont()
{
m_mode = Continue;
leaveSession();
}
void KJSDebugWin::stop()
{
m_mode = Stop;
leaveSession();
}
void KJSDebugWin::showFrame(int frameno)
{
StackFrame *frame = m_frames.at(frameno);
if (!frame)
return;
highLight(frame->sourceId,frame->lineno);
}
void KJSDebugWin::sourceSelected(int sourceSelIndex)
{
// a souce file has been selected from the drop-down list - display the file
// and hilight the line if it's in the current stack frame
if (sourceSelIndex < 0 || sourceSelIndex >= (int)m_sourceSel->count())
return;
SourceFile *sourceFile = m_sourceSelFiles[sourceSelIndex];
if (m_curSourceFile != sourceFile)
setCode(sourceFile->code);
m_curSourceFile = sourceFile;
SourceFragment *lastFragment = 0;
StackFrame *curFrame = m_frames.at(m_frameList->currentItem() >= 0 ? m_frameList->currentItem() : m_frames.count()-1);
if (curFrame)
lastFragment = m_sourceFragments[curFrame->sourceId];
if (lastFragment && lastFragment->sourceFile == m_curSourceFile)
m_sourceDisplay->setCurrentItem(lastFragment->baseLine+curFrame->lineno);
else
m_sourceDisplay->setCurrentItem(-1);
}
void KJSDebugWin::eval()
{
// evaluate the js code from m_evalEdit
if (!m_inSession)
return;
// create function
KJS::Constructor constr(KJS::Global::current().get("Function").imp());
KJS::List args;
args.append(KJS::String("event"));
args.append(KJS::String("return "+m_evalEdit->text()+";"));
KJS::KJSO func = constr.construct(args);
// execute
Mode oldMode = m_mode;
m_mode = Continue; // prevents us from stopping during evaluation
KJSO ret = m_curContext->executeCall(m_curScript,func,func,0);
KMessageBox::information(this, ret.toString().value().qstring(), "JavaScript eval");
m_mode = oldMode;
}
void KJSDebugWin::closeEvent(QCloseEvent *e)
{
if (m_inSession)
leaveSession();
return QWidget::closeEvent(e);
}
bool KJSDebugWin::sourceParsed(KJScript */*script*/, int sourceId,
const UString &source, int /*errorLine*/)
{
// the interpreter has parsed some js code - store it in a SourceFragment object
// ### report errors (errorLine >= 0)
SourceFile *sourceFile = m_sourceFiles[m_nextSourceUrl];
if (!sourceFile) {
sourceFile = new SourceFile("(unknown)",source.qstring());
m_sourceSelFiles[m_sourceSel->count()] = sourceFile;
if (m_nextSourceUrl.isNull() || m_nextSourceUrl == "")
m_sourceSel->insertItem("???");
else
m_sourceSel->insertItem(m_nextSourceUrl);
}
SourceFragment *sf = new SourceFragment(sourceId,m_nextSourceBaseLine,sourceFile);
m_sourceFragments[sourceId] = sf;
m_nextSourceBaseLine = 0;
m_nextSourceUrl = "";
return (m_mode != Stop);
}
bool KJSDebugWin::sourceUnused(KJScript */*script*/, int sourceId)
{
// the source fragment is no longer in use, so we can free it
SourceFragment *fragment = m_sourceFragments[sourceId];
m_sourceFragments.erase(sourceId);
delete fragment;
return (m_mode != Stop);
}
bool KJSDebugWin::error(KJScript */*script*/, int /*sourceId*/, int /*lineno*/,
int /*errorType*/, const KJS::UString &errorMessage)
{
// ### bring up source & hilight line
KMessageBox::error(this, errorMessage.qstring(), "JavaScript error");
return (m_mode != Stop);
}
bool KJSDebugWin::atLine(KJScript *script, int sourceId, int lineno,
const ExecutionContext *execContext)
{
const ExecutionContext *oldCurContext = m_curContext;
KJScript *oldCurScript = m_curScript;
m_curContext = execContext;
m_curScript = script;
/*
if (haveBreakpoint(sourceId,lineno)) {
m_mode = Next;
m_frames.last()->next = true;
}
*/
m_frames.last()->sourceId = sourceId;
m_frames.last()->lineno = lineno;
// highLight(sourceId,lineno);
if (m_mode == KJSDebugWin::Step || m_mode == KJSDebugWin::Next) {
if (m_frames.last()->next)
enterSession();
}
m_curContext = oldCurContext;
m_curScript = oldCurScript;
return (m_mode != Stop);
}
bool KJSDebugWin::callEvent(KJScript *script, int sourceId, int lineno,
const ExecutionContext *execContext,
FunctionImp *function, const List */*args*/)
{
// highLight(sourceId,lineno);
const ExecutionContext *oldCurContext = m_curContext;
KJScript *oldCurScript = m_curScript;
m_curContext = execContext;
m_curScript = script;
QString name = function->name().qstring();
StackFrame *sf = new StackFrame(sourceId,lineno,name,m_mode == Step);
m_frames.append(sf);
if (m_mode == Step)
enterSession();
m_curContext = oldCurContext;
m_curScript = oldCurScript;
return (m_mode != Stop);
}
bool KJSDebugWin::returnEvent(KJScript *script, int sourceId, int lineno,
const ExecutionContext *execContext,
FunctionImp */*function*/)
{
// highLight(sourceId,lineno);
const ExecutionContext *oldCurContext = m_curContext;
KJScript *oldCurScript = m_curScript;
m_curContext = execContext;
m_curScript = script;
m_frames.last()->sourceId = sourceId;
m_frames.last()->lineno = lineno;
if (m_frames.last()->step)
enterSession();
m_frames.removeLast();
// m_frameList->removeItem(m_frameList->count()-1);
m_curContext = oldCurContext;
m_curScript = oldCurScript;
return (m_mode != Stop);
}
void KJSDebugWin::setCode(const QString &code)
{
const QChar *chars = code.unicode();
uint len = code.length();
QChar newLine('\n');
uint lineStart = 0;
m_sourceDisplay->clear();
// ### support for \r\n and \n\r (?)
for (uint i = 0; i < len; i++) {
if (chars[i] == newLine) {
QString line;
if (lineStart == i)
line = "";
else
line = QString(chars+lineStart,i-lineStart);
QListBoxPixmap *qbp;
if (m_sourceDisplay->count() == 5)
qbp = new QListBoxPixmap(m_stopIcon,line);
else
qbp = new QListBoxPixmap(m_emptyIcon,line);
m_sourceDisplay->insertItem(qbp);
lineStart = i+1;
}
}
}
void KJSDebugWin::highLight(int sourceId, int line)
{
if (!isVisible())
show();
SourceFragment *source = m_sourceFragments[sourceId];
if (!source)
return;
SourceFile *sourceFile = source->sourceFile;
if (m_curSourceFile != source->sourceFile)
setCode(sourceFile->code);
m_curSourceFile = source->sourceFile;
if (line >= 0)
m_sourceDisplay->setCurrentItem(line+source->baseLine);
else
m_sourceDisplay->setCurrentItem(-1);
}
void KJSDebugWin::setNextSourceInfo(QString url, int baseLine)
{
m_nextSourceUrl = url;
m_nextSourceBaseLine = baseLine;
}
void KJSDebugWin::setSourceFile(QString url, QString code)
{
SourceFile *existing = m_sourceFiles[url];
if (existing)
existing->deref();
SourceFile *newSF = new SourceFile(url,code);
m_sourceFiles[url] = newSF;
}
void KJSDebugWin::appendSourceFile(QString url, QString code)
{
SourceFile *existing = m_sourceFiles[url];
if (!existing) {
setSourceFile(url,code);
return;
}
existing->code.append(code);
}
void KJSDebugWin::enterSession()
{
// This "enters" a new debugging session, i.e. enables usage of the debugging window
// It re-enters the qt event loop here, allowing execution of other parts of the
// program to continue while the script is stopped. We have to be a bit careful here,
// i.e. make sure the user can't quite the app, and not executing more js code
assert(!m_inSession);
m_fakeModal.enable(this);
m_inSession = true;
m_mode = Continue;
m_nextButton->setEnabled(true);
m_stepButton->setEnabled(true);
m_continueButton->setEnabled(true);
m_stopButton->setEnabled(true);
m_evalButton->setEnabled(true);
updateFrameList();
qApp->enter_loop(); // won't return until leaveSession() is called
assert(!m_inSession);
}
void KJSDebugWin::leaveSession()
{
// Disables debugging for this window and returns to execute the rest of the script
// (or aborts execution, if the user pressed stop). When this returns, the program
// will exit the qt event loop, i.e. return to whatever processing was being done
// before the debugger was stopped.
assert(m_inSession);
m_nextButton->setEnabled(false);
m_stepButton->setEnabled(false);
m_continueButton->setEnabled(false);
m_stopButton->setEnabled(false);
m_evalButton->setEnabled(false);
m_inSession = false;
qApp->exit_loop();
m_fakeModal.disable();
}
void KJSDebugWin::updateFrameList()
{
uint frameno;
// m_frameList->setUpdatesEnabled(false);
m_frameList->clear();
for (frameno = 0; frameno < m_frames.count(); frameno++) {
m_frameList->insertItem(m_frames.at(frameno)->toString(),frameno);
}
// m_frameList->setUpdatesEnabled(true);
// m_frameList->triggerUpdate(true);
highLight(m_frames.last()->sourceId,m_frames.last()->lineno);
}
bool KJSDebugWin::setBreakpoint(int sourceId, int line)
{
if (haveBreakpoint(sourceId,line,line))
return false;
SourceBreakpoints *sbp = m_sourceBreakpoints;
while(sbp && sbp->sourceId != sourceId)
sbp = sbp->next;
if (!sbp) {
sbp = new SourceBreakpoints;
sbp->sourceId = sourceId;
sbp->breakpoints = 0;
sbp->next = m_sourceBreakpoints;
m_sourceBreakpoints = sbp;
}
Breakpoint *newbp = new Breakpoint;
newbp->lineno = line;
newbp->next = sbp->breakpoints;
sbp->breakpoints = newbp;
return true;
}
bool KJSDebugWin::deleteBreakpoint(int sourceId, int line)
{
for (SourceBreakpoints *sbp = m_sourceBreakpoints; sbp; sbp = sbp->next) {
if (sbp->sourceId == sourceId) {
// found breakpoints for this sourceId
Breakpoint *bp = sbp->breakpoints;
if (bp && bp->lineno == line) {
// was the first breakpoint
Breakpoint *next = bp->next;
delete bp;
bp = next;
return true;
}
while (bp->next && bp->next->lineno != line)
bp = bp->next;
if (bp->next && bp->next->lineno == line) {
// found at subsequent breakpoint
Breakpoint *next = bp->next;
delete bp;
bp = next;
return true;
}
return false;
}
}
// no breakpoints at all for this sourceId
return false;
}
void KJSDebugWin::clearAllBreakpoints(int sourceId)
{
SourceBreakpoints *nextsbp = 0;
for (SourceBreakpoints *sbp = m_sourceBreakpoints; sbp; sbp = nextsbp) {
nextsbp = sbp->next;
if (sourceId == -1 || sbp->sourceId == sourceId) {
Breakpoint *nextbp;
for (Breakpoint *bp = sbp->breakpoints; bp; bp = bp->next) {
nextbp = bp->next;
delete bp;
}
delete sbp;
}
}
}
int KJSDebugWin::breakpointLine(int sourceId, int line0, int line1)
{
for (SourceBreakpoints *sbp = m_sourceBreakpoints; sbp; sbp = sbp->next) {
if (sbp->sourceId == sourceId) {
// found breakpoints for this sourceId
for (Breakpoint *bp = sbp->breakpoints; bp; bp = bp->next) {
if (bp->lineno >= 0 && bp->lineno >= line0 && bp->lineno <= line1)
return bp->lineno;
}
return -1;
}
}
// no breakpoints at all for this sourceId
return -1;
}
bool KJSDebugWin::haveBreakpoint(int sourceId, int line0, int line1)
{
return (breakpointLine(sourceId,line0,line1) != -1);
}
#include "kjs_debugwin.moc"
#endif // KJS_DEBUGGER