/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtSCriptTools module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qscriptdebugger_p.h"
#include "qscriptdebuggerconsole_p.h"
#include "qscriptdebuggerconsolecommandmanager_p.h"
#include "qscriptdebuggerconsolecommandjob_p.h"
#include "qscriptstdmessagehandler_p.h"
#include "qscriptdebuggerfrontend_p.h"
#include "qscriptdebuggereventhandlerinterface_p.h"
#include "qscriptdebuggerresponsehandlerinterface_p.h"
#include "qscriptdebuggerjobschedulerinterface_p.h"
#include "qscriptdebuggerconsolewidgetinterface_p.h"
#include "qscriptcompletionproviderinterface_p.h"
#include "qscriptcompletiontask_p.h"
#include "qscripttooltipproviderinterface_p.h"
#include "qscriptdebuggerstackwidgetinterface_p.h"
#include "qscriptdebuggerstackmodel_p.h"
#include "qscriptdebuggerscriptswidgetinterface_p.h"
#include "qscriptdebuggerscriptsmodel_p.h"
#include "qscriptdebuggerlocalswidgetinterface_p.h"
#include "qscriptdebuggerlocalsmodel_p.h"
#include "qscriptdebuggercodewidgetinterface_p.h"
#include "qscriptdebuggercodeviewinterface_p.h"
#include "qscriptdebuggercodefinderwidgetinterface_p.h"
#include "qscriptbreakpointswidgetinterface_p.h"
#include "qscriptbreakpointsmodel_p.h"
#include "qscriptdebugoutputwidgetinterface_p.h"
#include "qscripterrorlogwidgetinterface_p.h"
#include "qscriptdebuggerwidgetfactoryinterface_p.h"
#include "qscriptdebuggerevent_p.h"
#include "qscriptdebuggervalue_p.h"
#include "qscriptdebuggerresponse_p.h"
#include "qscriptdebuggercommand_p.h"
#include "qscriptdebuggercommandschedulerfrontend_p.h"
#include "qscriptdebuggercommandschedulerjob_p.h"
#include "qscriptdebuggerjob_p_p.h"
#include "qscriptxmlparser_p.h"

#include "private/qobject_p.h"

#include <QtScript/qscriptcontext.h>
#include <QtScript/qscriptcontextinfo.h>

#include <QtCore/qcoreapplication.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qdebug.h>

#include <QtWidgets/qaction.h>
#include <QtGui/qevent.h>
#include <QtGui/qicon.h>
#include <QtWidgets/qinputdialog.h>
#include <QtWidgets/qmenu.h>
#include <QtWidgets/qtoolbar.h>
#include <QtWidgets/qtooltip.h>

QT_BEGIN_NAMESPACE
typedef QPair<QList<qint64>, QList<qint64> > QScriptScriptsDelta;
typedef QPair<QList<qint64>, QList<qint64> > QScriptContextsDelta;
QT_END_NAMESPACE

Q_DECLARE_METATYPE(QScriptScriptsDelta)

QT_BEGIN_NAMESPACE

Q_SCRIPT_EXPORT QString qt_scriptToXml(const QString &program, int lineNumber = 1);

namespace {

static int scriptDebuggerCount = 0;
static bool eventCallbackRegistered = false;
static bool widgetInPaintEvent = false;

static bool scriptDebuggerEventCallback(void **data)
{
    QEvent *event = reinterpret_cast<QEvent*>(data[1]);
    if (event->type() == QEvent::Paint) {
        QObject *receiver = reinterpret_cast<QObject*>(data[0]);
        bool was = widgetInPaintEvent;
        widgetInPaintEvent = true;
        QCoreApplication::instance()->notify(receiver, event);
        widgetInPaintEvent = was;
        bool *result = reinterpret_cast<bool*>(data[2]);
        *result = true;
        return true;
    }
    return false;
}

}

/*!
  \since 4.5
  \class QScriptDebugger
  \internal

  \brief The QScriptDebugger class provides a Qt Script debugger.

  \ingroup script

*/

class QScriptDebuggerPrivate
    : public QObjectPrivate,
      public QScriptDebuggerCommandSchedulerInterface,
      public QScriptDebuggerJobSchedulerInterface,
      public QScriptDebuggerEventHandlerInterface,
      public QScriptDebuggerResponseHandlerInterface,
      public QScriptCompletionProviderInterface,
      public QScriptToolTipProviderInterface
{
    Q_DECLARE_PUBLIC(QScriptDebugger)
public:
    QScriptDebuggerPrivate();
    ~QScriptDebuggerPrivate();

    int scheduleJob(QScriptDebuggerJob *job);
    void finishJob(QScriptDebuggerJob *job);
    void hibernateUntilEvaluateFinished(QScriptDebuggerJob *job);

    void maybeStartNewJob();

    int scheduleCommand(const QScriptDebuggerCommand &command,
                        QScriptDebuggerResponseHandlerInterface *responseHandler);

    void handleResponse(
        const QScriptDebuggerResponse &response, int commandId);
    bool debuggerEvent(const QScriptDebuggerEvent &event);

    QScriptCompletionTaskInterface *createCompletionTask(
        const QString &contents, int cursorPosition, int frameIndex, int options);

    void showToolTip(const QPoint &pos, int frameIndex,
                     int lineNumber, const QStringList &path);

    static QPixmap pixmap(const QString &path);

    void startInteraction(QScriptDebuggerEvent::Type type,
                          qint64 scriptId, int lineNumber);
    void sync();
    void loadLocals(int frameIndex);
    QScriptDebuggerLocalsModel *createLocalsModel();
    void selectScriptForFrame(int frameIndex);
    void emitStoppedSignal();

    void maybeDelete(QWidget *widget);

    // private slots
    void _q_onLineEntered(const QString &contents);
    void _q_onCurrentFrameChanged(int frameIndex);
    void _q_onCurrentScriptChanged(qint64 scriptId);
    void _q_onScriptLocationSelected(int lineNumber);
    void _q_interrupt();
    void _q_continue();
    void _q_stepInto();
    void _q_stepOver();
    void _q_stepOut();
    void _q_runToCursor();
    void _q_runToNewScript();
    void _q_toggleBreakpoint();
    void _q_clearDebugOutput();
    void _q_clearErrorLog();
    void _q_clearConsole();
    void _q_findInScript();
    void _q_findNextInScript();
    void _q_findPreviousInScript();
    void _q_onFindCodeRequest(const QString &, int);
    void _q_goToLine();

    void executeConsoleCommand(const QString &command);
    void findCode(const QString &exp, int options);

    QScriptDebuggerFrontend *frontend;

    bool interactive;
    QScriptDebuggerConsole *console;

    int nextJobId;
    QList<QScriptDebuggerJob*> pendingJobs;
    QList<int> pendingJobIds;
    QScriptDebuggerJob *activeJob;
    bool activeJobHibernating;
    QHash<int, QScriptDebuggerCommand> watchedCommands;
    QHash<int, QScriptDebuggerResponseHandlerInterface*> responseHandlers;

    QScriptDebuggerConsoleWidgetInterface *consoleWidget;
    QScriptDebuggerStackWidgetInterface *stackWidget;
    QScriptDebuggerStackModel *stackModel;
    QScriptDebuggerScriptsWidgetInterface *scriptsWidget;
    QScriptDebuggerScriptsModel *scriptsModel;
    QScriptDebuggerLocalsWidgetInterface *localsWidget;
    QHash<int, QScriptDebuggerLocalsModel*> localsModels;
    QScriptDebuggerCodeWidgetInterface *codeWidget;
    QScriptDebuggerCodeFinderWidgetInterface *codeFinderWidget;
    QScriptBreakpointsWidgetInterface *breakpointsWidget;
    QScriptBreakpointsModel *breakpointsModel;
    QScriptDebugOutputWidgetInterface *debugOutputWidget;
    QScriptErrorLogWidgetInterface *errorLogWidget;
    QScriptDebuggerWidgetFactoryInterface *widgetFactory;

    QAction *interruptAction;
    QAction *continueAction;
    QAction *stepIntoAction;
    QAction *stepOverAction;
    QAction *stepOutAction;
    QAction *runToCursorAction;
    QAction *runToNewScriptAction;

    QAction *toggleBreakpointAction;

    QAction *clearDebugOutputAction;
    QAction *clearErrorLogAction;
    QAction *clearConsoleAction;

    QAction *findInScriptAction;
    QAction *findNextInScriptAction;
    QAction *findPreviousInScriptAction;
    QAction *goToLineAction;

    int updatesEnabledTimerId;
};

QScriptDebuggerPrivate::QScriptDebuggerPrivate()
{
    frontend = 0;
    activeJob = 0;
    activeJobHibernating = false;
    nextJobId = 0;
    interactive = false;

    console = new QScriptDebuggerConsole();
    QString scriptsPath = QLatin1String(":/qt/scripttools/debugging/scripts/commands");
    QScriptStdMessageHandler tmp;
    console->loadScriptedCommands(scriptsPath, &tmp);

    consoleWidget = 0;
    stackWidget = 0;
    stackModel = 0;
    scriptsWidget = 0;
    scriptsModel = 0;
    localsWidget = 0;
    codeWidget = 0;
    codeFinderWidget = 0;
    breakpointsWidget = 0;
    breakpointsModel = 0;
    debugOutputWidget = 0;
    errorLogWidget = 0;
    widgetFactory = 0;

    interruptAction = 0;
    continueAction = 0;
    stepIntoAction = 0;
    stepOverAction = 0;
    stepOutAction = 0;
    runToCursorAction = 0;
    runToNewScriptAction = 0;

    toggleBreakpointAction = 0;

    clearErrorLogAction = 0;
    clearDebugOutputAction = 0;
    clearConsoleAction = 0;

    findInScriptAction = 0;
    findNextInScriptAction = 0;
    findPreviousInScriptAction = 0;
    goToLineAction = 0;

    updatesEnabledTimerId = -1;
}

QScriptDebuggerPrivate::~QScriptDebuggerPrivate()
{
    delete console;
    qDeleteAll(pendingJobs);
    delete activeJob;
    maybeDelete(consoleWidget);
    maybeDelete(stackWidget);
    maybeDelete(scriptsWidget);
    maybeDelete(localsWidget);
    maybeDelete(codeWidget);
    maybeDelete(codeFinderWidget);
    maybeDelete(breakpointsWidget);
    maybeDelete(debugOutputWidget);
    maybeDelete(errorLogWidget);
}

void QScriptDebuggerPrivate::maybeDelete(QWidget *widget)
{
    if (widget && !widget->parent())
        delete widget;
}

QPixmap QScriptDebuggerPrivate::pixmap(const QString &path)
{
    static QString prefix = QString::fromLatin1(":/qt/scripttools/debugging/images/");
    return QPixmap(prefix + path);
}

/*!
  \reimp
*/
int QScriptDebuggerPrivate::scheduleJob(QScriptDebuggerJob *job)
{
    QScriptDebuggerJobPrivate *priv = QScriptDebuggerJobPrivate::get(job);
    Q_ASSERT(priv->jobScheduler == 0);
    priv->jobScheduler = this;
    int id = nextJobId;
    pendingJobs.append(job);
    pendingJobIds.append(id);
    maybeStartNewJob();
    return id;
}

/*!
  \reimp
*/
void QScriptDebuggerPrivate::finishJob(QScriptDebuggerJob *job)
{
    Q_UNUSED(job);
    Q_ASSERT(activeJob == job);
    delete activeJob;
    activeJob = 0;
    activeJobHibernating = false;
    maybeStartNewJob();
}

/*!
  \reimp
*/
void QScriptDebuggerPrivate::hibernateUntilEvaluateFinished(QScriptDebuggerJob *job)
{
    Q_UNUSED(job);
    Q_ASSERT(activeJob == job);
    activeJobHibernating = true;
}

/*!
  Starts a new job if appropriate.
*/
void QScriptDebuggerPrivate::maybeStartNewJob()
{
    if (activeJob || pendingJobs.isEmpty())
        return;
    activeJob = pendingJobs.takeFirst();
    activeJob->start();
}

/*!
  \reimp
*/
int QScriptDebuggerPrivate::scheduleCommand(
    const QScriptDebuggerCommand &command,
    QScriptDebuggerResponseHandlerInterface *responseHandler)
{
    if (!frontend)
        return -1;
    int id = frontend->scheduleCommand(command, this);
    if (responseHandler && (responseHandler != this))
        responseHandlers.insert(id, responseHandler);
    if ((command.type() == QScriptDebuggerCommand::SetBreakpoint)
        || (command.type() == QScriptDebuggerCommand::SetBreakpointData)
        || (command.type() == QScriptDebuggerCommand::DeleteBreakpoint)) {
        // need to watch this command and update the breakpoints model afterwards
        watchedCommands.insert(id, command);
    }
    return id;
}

/*!
  \reimp
*/
void QScriptDebuggerPrivate::handleResponse(
    const QScriptDebuggerResponse &response, int commandId)
{
    Q_Q(QScriptDebugger);
    if (watchedCommands.contains(commandId)) {
        QScriptDebuggerCommand command = watchedCommands.take(commandId);
        if (response.error() == QScriptDebuggerResponse::NoError) {
            if (!breakpointsModel)
                breakpointsModel = new QScriptBreakpointsModel(this, this, q);
            switch (command.type()) {
            case QScriptDebuggerCommand::SetBreakpoint: {
                int breakpointId = response.resultAsInt();
                QScriptBreakpointData data = command.breakpointData();
                breakpointsModel->addBreakpoint(breakpointId, data);
            }   break;
            case QScriptDebuggerCommand::SetBreakpointData: {
                int breakpointId = command.breakpointId();
                QScriptBreakpointData data = command.breakpointData();
                breakpointsModel->modifyBreakpoint(breakpointId, data);
            }   break;
            case QScriptDebuggerCommand::DeleteBreakpoint: {
                int breakpointId = command.breakpointId();
                breakpointsModel->removeBreakpoint(breakpointId);
            }   break;
            default:
                Q_ASSERT(false);
            }
        }
    } else if (response.async()) {
        interactive = false;
        // disable/invalidate/enable stuff
        if (continueAction)
            continueAction->setEnabled(false);
        if (stepIntoAction)
            stepIntoAction->setEnabled(false);
        if (stepOverAction)
            stepOverAction->setEnabled(false);
        if (stepOutAction)
            stepOutAction->setEnabled(false);
        if (runToCursorAction)
            runToCursorAction->setEnabled(false);
        if (runToNewScriptAction)
            runToNewScriptAction->setEnabled(false);
        if (interruptAction)
            interruptAction->setEnabled(true);

        // the timer is to avoid flicker when stepping
        if (stackWidget) {
            stackWidget->setUpdatesEnabled(false);
            stackWidget->setEnabled(false);
            if (updatesEnabledTimerId == -1)
                updatesEnabledTimerId = q->startTimer(75);
        }
        if (localsWidget) {
            localsWidget->setUpdatesEnabled(false);
            localsWidget->setEnabled(false);
            if (updatesEnabledTimerId == -1)
                updatesEnabledTimerId = q->startTimer(75);
        }
        if (codeWidget)
            codeWidget->invalidateExecutionLineNumbers();

        emit q->started();
    }

    QScriptDebuggerResponseHandlerInterface *realHandler = responseHandlers.take(commandId);
    if (realHandler)
        realHandler->handleResponse(response, commandId);
}

/*!
  \reimp

  Handles a debugger event from the frontend.
*/
bool QScriptDebuggerPrivate::debuggerEvent(const QScriptDebuggerEvent &event)
{
    Q_Q(QScriptDebugger);
    switch (event.type()) {
    case QScriptDebuggerEvent::None:
    case QScriptDebuggerEvent::UserEvent:
    case QScriptDebuggerEvent::MaxUserEvent:
        Q_ASSERT(false);
        break;

    case QScriptDebuggerEvent::Trace:
        if (!debugOutputWidget && widgetFactory)
            q->setDebugOutputWidget(widgetFactory->createDebugOutputWidget());
        if (debugOutputWidget)
            debugOutputWidget->message(QtDebugMsg, event.message());
        return true; // trace doesn't stall execution

    case QScriptDebuggerEvent::SteppingFinished: {
        if (!consoleWidget && widgetFactory)
            q->setConsoleWidget(widgetFactory->createConsoleWidget());
        if (consoleWidget) {
            QString msg = event.message();
            if (!msg.isEmpty())
                consoleWidget->message(QtDebugMsg, msg);
        }
    }   break;

    case QScriptDebuggerEvent::Interrupted:
    case QScriptDebuggerEvent::LocationReached:
        break;

    case QScriptDebuggerEvent::Breakpoint: {
        int bpId = event.breakpointId();
        if (!consoleWidget && widgetFactory)
            q->setConsoleWidget(widgetFactory->createConsoleWidget());
        if (consoleWidget) {
            consoleWidget->message(QtDebugMsg,
                                   QString::fromLatin1("Breakpoint %0 at %1, line %2.")
                                   .arg(bpId).arg(event.fileName())
                                   .arg(event.lineNumber()));
        }
        if (breakpointsModel->breakpointData(bpId).isSingleShot())
            breakpointsModel->removeBreakpoint(bpId);
    }   break;

    case QScriptDebuggerEvent::Exception: {
        if (event.hasExceptionHandler()) {
            // Let the exception be handled like normal.
            // We may want to add a "Break on all exceptions" option
            // to be able to customize this behavior.
            return true;
        }
        if (!consoleWidget && widgetFactory)
            q->setConsoleWidget(widgetFactory->createConsoleWidget());
        if (!errorLogWidget && widgetFactory)
            q->setErrorLogWidget(widgetFactory->createErrorLogWidget());
        if (consoleWidget || errorLogWidget) {
            QString fn = event.fileName();
            if (fn.isEmpty()) {
                if (event.scriptId() != -1)
                    fn = QString::fromLatin1("<anonymous script, id=%0>").arg(event.scriptId());
                else
                    fn = QString::fromLatin1("<native>");
            }
            QString msg = QString::fromLatin1("Uncaught exception at %0:%1: %2").arg(fn)
                          .arg(event.lineNumber()).arg(event.message());
            if (consoleWidget)
                consoleWidget->message(QtCriticalMsg, msg);
            if (errorLogWidget)
                errorLogWidget->message(QtCriticalMsg, msg);
        }
    }   break;

    case QScriptDebuggerEvent::InlineEvalFinished: {
        QScriptDebuggerValue result = event.scriptValue();
        Q_ASSERT(console != 0);
        int action = console->evaluateAction();
        console->setEvaluateAction(0);
        switch (action) {
        case 0: { // eval command
            if (activeJob) {
                if (activeJobHibernating) {
                    activeJobHibernating = false;
                    activeJob->evaluateFinished(result);
                }
            } else if (consoleWidget) {
                // ### if the result is an object, need to do a tostring job on it
//          messageHandler->message(QtDebugMsg, result.toString());
                if (result.type() != QScriptDebuggerValue::UndefinedValue)
                    consoleWidget->message(QtDebugMsg, event.message());
            }
        }   break;
        case 1: { // return command
            QScriptDebuggerCommandSchedulerFrontend frontend(this, this);
            frontend.scheduleForceReturn(console->currentFrameIndex(), result);
        }   return false;
        }
        if (!event.isNestedEvaluate()) {
            // in the case when evaluate() was called while the
            // engine was not running, we don't want to enter interactive mode
            return true;
        }
    }   break;

    case QScriptDebuggerEvent::DebuggerInvocationRequest: {
        if (!consoleWidget && widgetFactory)
            q->setConsoleWidget(widgetFactory->createConsoleWidget());
        if (consoleWidget) {
            QString fn = event.fileName();
            if (fn.isEmpty())
                fn = QString::fromLatin1("<anonymous script, id=%0>").arg(event.scriptId());
            consoleWidget->message(QtDebugMsg,
                                   QString::fromLatin1("Debugger invoked from %1, line %2.")
                                   .arg(fn).arg(event.lineNumber()));
        }
    }   break;

    case QScriptDebuggerEvent::ForcedReturn: {
    }   break;

    }

    if (widgetInPaintEvent) {
        QString msg = QString::fromLatin1("Suspending evaluation in paintEvent() is not supported; resuming.");
        if (!consoleWidget && widgetFactory)
            q->setConsoleWidget(widgetFactory->createConsoleWidget());
        if (!errorLogWidget && widgetFactory)
            q->setErrorLogWidget(widgetFactory->createErrorLogWidget());
        if (consoleWidget)
            consoleWidget->message(QtWarningMsg, msg);
        if (errorLogWidget)
            errorLogWidget->message(QtCriticalMsg, msg);
        return true;
    }

    if (activeJobHibernating) {
        // evaluate() did not finish normally (e.g. due to a breakpoint),
        // so cancel the job that's waiting for it
        delete activeJob;
        activeJob = 0;
        activeJobHibernating = false;
    }

    startInteraction(event.type(), event.scriptId(), event.lineNumber());
    return !interactive;
}

class QScriptToolTipJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    QScriptToolTipJob(const QPoint &pos, int frameIndex,
                      int lineNumber, const QStringList &path,
                      QScriptDebuggerCommandSchedulerInterface *scheduler)
        : QScriptDebuggerCommandSchedulerJob(scheduler), m_pos(pos),
          m_frameIndex(frameIndex), m_lineNumber(lineNumber), m_path(path)
        {}

    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleGetPropertyExpressionValue(m_frameIndex, m_lineNumber, m_path);
    }
    void handleResponse(const QScriptDebuggerResponse &response, int /*commandId*/)
    {
        QString tip = response.result().toString();
        if (tip.indexOf(QLatin1Char('\n')) != -1) {
            QStringList lines = tip.split(QLatin1Char('\n'));
            int lineCount = lines.size();
            if (lineCount > 5) {
                lines = lines.mid(0, 5);
                lines.append(QString::fromLatin1("(... %0 more lines ...)").arg(lineCount - 5));
            }
            tip = lines.join(QLatin1String("\n"));
        }
        QToolTip::showText(m_pos, tip);
        finish();
    }

private:
    QPoint m_pos;
    int m_frameIndex;
    int m_lineNumber;
    QStringList m_path;
};

/*!
  \reimp
*/
void QScriptDebuggerPrivate::showToolTip(const QPoint &pos, int frameIndex,
                                         int lineNumber, const QStringList &path)
{
    if (frameIndex == -1) {
        if (stackWidget)
            frameIndex = stackWidget->currentFrameIndex();
        else
            frameIndex = console->currentFrameIndex();
    }
    QScriptDebuggerJob *job = new QScriptToolTipJob(pos, frameIndex, lineNumber, path, this);
    scheduleJob(job);
}

/*!
  \reimp
*/
QScriptCompletionTaskInterface *QScriptDebuggerPrivate::createCompletionTask(
    const QString &contents, int cursorPosition, int frameIndex, int options)
{
    return new QScriptCompletionTask(
        contents, cursorPosition, frameIndex, this, this,
        (options & QScriptCompletionProviderInterface::ConsoleCommandCompletion) ? console : 0);
}

/*!
  Slot called when a line has been entered in the console widget.
*/
void QScriptDebuggerPrivate::_q_onLineEntered(const QString &contents)
{
    QScriptDebuggerConsoleCommandJob *commandJob;
    commandJob = console->consumeInput(contents, consoleWidget, this);
    if (commandJob != 0) {
        scheduleJob(commandJob);
        consoleWidget->setLineContinuationMode(false);
    } else if (console->hasIncompleteInput()) {
        consoleWidget->setLineContinuationMode(true);
    }
}

/*!
  Slot called when the current index has changed in the stack widget.
*/
void QScriptDebuggerPrivate::_q_onCurrentFrameChanged(int frameIndex)
{
    loadLocals(frameIndex);
    selectScriptForFrame(frameIndex);
}

/*!
  Slot called when the current script has changed in the scripts widget.
*/
void QScriptDebuggerPrivate::_q_onCurrentScriptChanged(qint64 scriptId)
{
    if (codeWidget && (codeWidget->currentScriptId() != scriptId)) {
        codeWidget->setCurrentScript(scriptId);
        QScriptDebuggerCodeViewInterface *view = codeWidget->currentView();
        if (view)
            view->setExecutionLineNumber(-1, /*error=*/false);
    }
}

void QScriptDebuggerPrivate::_q_onScriptLocationSelected(int lineNumber)
{
    QScriptDebuggerCodeViewInterface *view = codeWidget->currentView();
    if (!view)
        return;
    view->gotoLine(lineNumber);
}

void QScriptDebuggerPrivate::_q_interrupt()
{
    executeConsoleCommand(QString::fromLatin1("interrupt"));
}

void QScriptDebuggerPrivate::_q_continue()
{
    executeConsoleCommand(QString::fromLatin1("continue"));
}

void QScriptDebuggerPrivate::_q_stepInto()
{
    executeConsoleCommand(QString::fromLatin1("step"));
}

void QScriptDebuggerPrivate::_q_stepOver()
{
    executeConsoleCommand(QString::fromLatin1("next"));
}

void QScriptDebuggerPrivate::_q_stepOut()
{
    executeConsoleCommand(QString::fromLatin1("finish"));
}

void QScriptDebuggerPrivate::_q_runToCursor()
{
    qint64 scriptId = codeWidget->currentScriptId();
    int lineNumber = codeWidget->currentView()->cursorLineNumber();
    QScriptDebuggerCommandSchedulerFrontend frontend(this, this);
    frontend.scheduleRunToLocation(scriptId, lineNumber);
}

void QScriptDebuggerPrivate::_q_runToNewScript()
{
    QScriptDebuggerCommandSchedulerFrontend frontend(this, this);
    frontend.scheduleRunToLocation(QString(), -1);
}

void QScriptDebuggerPrivate::_q_toggleBreakpoint()
{
    Q_ASSERT(codeWidget != 0);
    QScriptDebuggerCodeViewInterface *view = codeWidget->currentView();
    if (!view)
        return;
    qint64 scriptId = codeWidget->currentScriptId();
    int lineNumber = view->cursorLineNumber();
    Q_ASSERT(breakpointsModel != 0);
    int bpId = breakpointsModel->resolveBreakpoint(scriptId, lineNumber);
    if (bpId != -1) {
        breakpointsModel->deleteBreakpoint(bpId);
    } else {
        QScriptBreakpointData data(scriptId, lineNumber);
        if (scriptsModel)
            data.setFileName(scriptsModel->scriptData(scriptId).fileName());
        breakpointsModel->setBreakpoint(data);
    }
}

void QScriptDebuggerPrivate::_q_clearDebugOutput()
{
    if (debugOutputWidget)
        debugOutputWidget->clear();
}

void QScriptDebuggerPrivate::_q_clearErrorLog()
{
    if (errorLogWidget)
        errorLogWidget->clear();
}

void QScriptDebuggerPrivate::_q_clearConsole()
{
    if (consoleWidget)
        consoleWidget->clear();
}

void QScriptDebuggerPrivate::executeConsoleCommand(const QString &command)
{
    QString tmp = console->incompleteInput();
    console->setIncompleteInput(QString());
    QScriptDebuggerJob *job = console->consumeInput(console->commandPrefix() + command, debugOutputWidget, this);
    console->setIncompleteInput(tmp);
    if (job != 0) {
        scheduleJob(job);
        // once to send the command...
        QCoreApplication::processEvents();
        // ... and once to receive the response
        QCoreApplication::processEvents();
    }
}

void QScriptDebuggerPrivate::_q_findInScript()
{
    if (!codeFinderWidget && widgetFactory)
        q_func()->setCodeFinderWidget(widgetFactory->createCodeFinderWidget());
    if (codeFinderWidget) {
        codeFinderWidget->show();
        codeFinderWidget->setFocus(Qt::OtherFocusReason);
    }
}

void QScriptDebuggerPrivate::_q_findNextInScript()
{
    findCode(codeFinderWidget->text(), codeFinderWidget->findOptions());
}

void QScriptDebuggerPrivate::_q_findPreviousInScript()
{
    int options = codeFinderWidget->findOptions();
    options |= QTextDocument::FindBackward;
    findCode(codeFinderWidget->text(), options);
}

void QScriptDebuggerPrivate::_q_onFindCodeRequest(
    const QString &exp, int options)
{
    findCode(exp, options);
    if (findNextInScriptAction)
        findNextInScriptAction->setEnabled(!exp.isEmpty());
    if (findPreviousInScriptAction)
        findPreviousInScriptAction->setEnabled(!exp.isEmpty());
}

void QScriptDebuggerPrivate::findCode(const QString &exp, int options)
{
    QScriptDebuggerCodeViewInterface *view = codeWidget->currentView();
    if (!view)
        return;
    int result = view->find(exp, options);
    codeFinderWidget->setOK(((result & 0x1) != 0) || exp.isEmpty());
    codeFinderWidget->setWrapped((result & 0x2) != 0);
}

void QScriptDebuggerPrivate::_q_goToLine()
{
    QScriptDebuggerCodeViewInterface *view = codeWidget->currentView();
    if (!view)
        return;
#ifndef QT_NO_INPUTDIALOG
    bool ok = false;
    int lineNumber = QInputDialog::getInt(0, QScriptDebugger::tr("Go to Line"),
                                          QScriptDebugger::tr("Line:"),
                                          view->cursorLineNumber(),
                                          1, INT_MAX, 1, &ok);
    if (ok)
        view->gotoLine(lineNumber);
#endif
}

class QScriptDebuggerShowLineJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    QScriptDebuggerShowLineJob(qint64 scriptId, int lineNumber,
                               QScriptMessageHandlerInterface *messageHandler,
                               QScriptDebuggerCommandSchedulerInterface *scheduler)
        : QScriptDebuggerCommandSchedulerJob(scheduler),
          m_scriptId(scriptId), m_lineNumber(lineNumber),
        m_messageHandler(messageHandler) {}

    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleGetScriptData(m_scriptId);
    }
    void handleResponse(const QScriptDebuggerResponse &response, int /*commandId*/)
    {
        QScriptScriptData data = response.resultAsScriptData();
        QString line = data.lines(m_lineNumber, 1).value(0);
        m_messageHandler->message(QtDebugMsg, QString::fromLatin1("%0\t%1")
                                  .arg(m_lineNumber).arg(line));
        finish();
    }

private:
    qint64 m_scriptId;
    int m_lineNumber;
    QScriptMessageHandlerInterface *m_messageHandler;
};

namespace {

class SyncStackJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    SyncStackJob(QScriptDebuggerPrivate *debugger)
        : QScriptDebuggerCommandSchedulerJob(debugger),
          m_debugger(debugger), m_index(0) {}
    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleGetContextInfo(m_index); // ### getContextInfos()
    }
    void handleResponse(const QScriptDebuggerResponse &response,
                        int)
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        if (response.error() != QScriptDebuggerResponse::InvalidContextIndex) {
            m_infos.append(response.resultAsContextInfo());
            frontend.scheduleGetContextInfo(++m_index);
        } else {
            m_debugger->stackModel->setContextInfos(m_infos);
            if (m_debugger->stackWidget->currentFrameIndex() == -1)
                m_debugger->stackWidget->setCurrentFrameIndex(0);
            m_debugger->stackWidget->setUpdatesEnabled(true);
            m_debugger->stackWidget->setEnabled(true);
            finish();
        }
    }

private:
    QScriptDebuggerPrivate *m_debugger;
    int m_index;
    QList<QScriptContextInfo> m_infos;
};

class SyncScriptsJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    SyncScriptsJob(QScriptDebuggerPrivate *debugger)
        : QScriptDebuggerCommandSchedulerJob(debugger),
          m_debugger(debugger), m_index(-1) {}

    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleScriptsCheckpoint();
    }
    void handleResponse(const QScriptDebuggerResponse &response,
                        int)
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        if (m_index == -1) {
            QScriptScriptsDelta delta;
            delta = qvariant_cast<QScriptScriptsDelta>(response.result());

            const QList<qint64> &removed = delta.second;
            for (int i = 0; i < removed.size(); ++i)
                m_debugger->scriptsModel->removeScript(removed.at(i));

            m_added = delta.first;
            if (!m_added.isEmpty()) {
                frontend.scheduleGetScriptData(m_added.at(++m_index));
            } else {
                m_debugger->scriptsModel->commit();
                finish();
            }
        } else {
            QScriptScriptData data = response.resultAsScriptData();
            qint64 scriptId = m_added.at(m_index);
            m_debugger->scriptsModel->addScript(scriptId, data);

            // ### could be slow, might want to do this in a separate thread
//            Q_ASSERT_X(false, Q_FUNC_INFO, "implement me");
            QString xml; // = qt_scriptToXml(data.contents(), data.baseLineNumber());
            QScriptXmlParser::Result extraInfo = QScriptXmlParser::parse(xml);
            m_debugger->scriptsModel->addExtraScriptInfo(
                scriptId, extraInfo.functionsInfo, extraInfo.executableLineNumbers);

            if (++m_index < m_added.size())
                frontend.scheduleGetScriptData(m_added.at(m_index));
            else {
                m_debugger->scriptsModel->commit();
                finish();
            }
        }
    }

private:
    QScriptDebuggerPrivate *m_debugger;
    int m_index;
    QList<qint64> m_added;
};

class SyncBreakpointsJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    SyncBreakpointsJob(QScriptDebuggerPrivate *debugger)
        : QScriptDebuggerCommandSchedulerJob(debugger),
          m_debugger(debugger) {}
    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleGetBreakpoints();
    }
    void handleResponse(const QScriptDebuggerResponse &response,
                        int)
    {
        QScriptBreakpointMap breakpoints = response.resultAsBreakpoints();
        QScriptBreakpointMap::const_iterator it;
        for (it = breakpoints.constBegin(); it != breakpoints.constEnd(); ++it) {
            int id = it.key();
            QScriptBreakpointData newData = it.value();
            QScriptBreakpointData existingData = m_debugger->breakpointsModel->breakpointData(id);
            if (existingData.isValid() && (existingData != newData))
                m_debugger->breakpointsModel->modifyBreakpoint(id, newData);
        }
        finish();
    }

private:
    QScriptDebuggerPrivate *m_debugger;
    QList<QScriptContextInfo> m_infos;
};

class SyncLocalsJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    SyncLocalsJob(QScriptDebuggerPrivate *debugger)
        : QScriptDebuggerCommandSchedulerJob(debugger),
          m_debugger(debugger) {}

    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleContextsCheckpoint();
    }
    void handleResponse(const QScriptDebuggerResponse &response,
                        int)
    {
        QScriptContextsDelta delta = qvariant_cast<QScriptContextsDelta>(response.result());
        for (int i = 0; i < delta.first.size(); ++i) {
            QScriptDebuggerLocalsModel *model = m_debugger->localsModels.take(delta.first.at(i));
            delete model;
        }
        finish();
    }

private:
    QScriptDebuggerPrivate *m_debugger;
};

class LoadLocalsJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    LoadLocalsJob(QScriptDebuggerPrivate *debugger, int frameIndex)
        : QScriptDebuggerCommandSchedulerJob(debugger),
          m_debugger(debugger), m_frameIndex(frameIndex) {}

    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleGetContextId(m_frameIndex);
    }
    void handleResponse(const QScriptDebuggerResponse &response,
                        int)
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        qint64 contextId = response.resultAsLongLong();
        QScriptDebuggerLocalsModel *model = m_debugger->localsModels.value(contextId);
        if (model) {
            model->sync(m_frameIndex);
        } else {
            model = m_debugger->createLocalsModel();
            m_debugger->localsModels.insert(contextId, model);
            model->init(m_frameIndex);
        }
        if (m_debugger->localsWidget) {
            if (m_debugger->localsWidget->localsModel() != model) // ### bug in qtreeview
                m_debugger->localsWidget->setLocalsModel(model);
            m_debugger->localsWidget->setUpdatesEnabled(true);
            m_debugger->localsWidget->setEnabled(true);
        }
        finish();
    }

private:
    QScriptDebuggerPrivate *m_debugger;
    int m_frameIndex;
};

class EmitStoppedSignalJob : public QScriptDebuggerJob
{
public:
    EmitStoppedSignalJob(QScriptDebuggerPrivate *debugger)
        : m_debugger(debugger) {}

    void start()
    {
        m_debugger->emitStoppedSignal();
        finish();
    }

private:
    QScriptDebuggerPrivate *m_debugger;
};

} // namespace

void QScriptDebuggerPrivate::startInteraction(QScriptDebuggerEvent::Type type,
                                              qint64 scriptId, int lineNumber)
{
    Q_Q(QScriptDebugger);
    if (type != QScriptDebuggerEvent::InlineEvalFinished) {
        if (stackWidget)
            stackWidget->setCurrentFrameIndex(0);
        console->setCurrentFrameIndex(0);
        console->setCurrentScriptId(scriptId);
        console->setCurrentLineNumber(lineNumber);
    }

    if ((scriptId != -1) && consoleWidget) {
        QScriptDebuggerJob *job = new QScriptDebuggerShowLineJob(scriptId, lineNumber, consoleWidget, this);
        scheduleJob(job);
    }

    sync();

    if (!interactive) {
        interactive = true;
        if (updatesEnabledTimerId != -1) {
            q->killTimer(updatesEnabledTimerId);
            updatesEnabledTimerId = -1;
        }
        console->bumpSessionId();
        scheduleJob(new EmitStoppedSignalJob(this));
    }

    if (consoleWidget)
        consoleWidget->activateWindow();
    else if (codeWidget)
        codeWidget->activateWindow();

    if (continueAction)
        continueAction->setEnabled(true);
    if (stepIntoAction)
        stepIntoAction->setEnabled(true);
    if (stepOverAction)
        stepOverAction->setEnabled(true);
    if (stepOutAction)
        stepOutAction->setEnabled(true);
    if (runToCursorAction)
        runToCursorAction->setEnabled(true);
    if (runToNewScriptAction)
        runToNewScriptAction->setEnabled(true);
    if (interruptAction)
        interruptAction->setEnabled(false);

    bool hasScript = (codeWidget != 0);
    if (findInScriptAction)
        findInScriptAction->setEnabled(hasScript);
    if (toggleBreakpointAction)
        toggleBreakpointAction->setEnabled(hasScript);
    if (goToLineAction)
        goToLineAction->setEnabled(hasScript);
}

void QScriptDebuggerPrivate::sync()
{
    if (localsWidget) {
        QScriptDebuggerJob *job = new SyncLocalsJob(this);
        scheduleJob(job);
    }
    if (scriptsModel) {
        QScriptDebuggerJob *job = new SyncScriptsJob(this);
        scheduleJob(job);
    }
    if (stackModel) {
        QScriptDebuggerJob *job = new SyncStackJob(this);
        scheduleJob(job);
    }
    if (breakpointsModel) {
        // need to sync because the ignore-count could have changed
        QScriptDebuggerJob *job = new SyncBreakpointsJob(this);
        scheduleJob(job);
    }

    if (stackWidget && (stackWidget->currentFrameIndex() != -1)) {
        int index = stackWidget->currentFrameIndex();
        loadLocals(index);
        selectScriptForFrame(index);
    } else if (codeWidget && (console->currentFrameIndex() != -1)) {
        selectScriptForFrame(console->currentFrameIndex());
    }
}

void QScriptDebuggerPrivate::loadLocals(int frameIndex)
{
    LoadLocalsJob *job = new LoadLocalsJob(this, frameIndex);
    scheduleJob(job);
}

QScriptDebuggerLocalsModel *QScriptDebuggerPrivate::createLocalsModel()
{
    return new QScriptDebuggerLocalsModel(this, this, q_func());
}

namespace {

class ShowFrameCodeJob : public QScriptDebuggerCommandSchedulerJob
{
public:
    ShowFrameCodeJob(QScriptDebuggerPrivate *debugger, int frameIndex)
        : QScriptDebuggerCommandSchedulerJob(debugger),
          m_debugger(debugger), m_frameIndex(frameIndex) {}

    void start()
    {
        QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
        frontend.scheduleGetContextInfo(m_frameIndex);
    }
    void handleResponse(const QScriptDebuggerResponse &response,
                        int)
    {
        if (m_info.isNull()) {
            m_info = response.resultAsContextInfo();
            QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
            frontend.scheduleGetContextState(m_frameIndex);
        } else {
            int contextState = response.resultAsInt();
            bool error = (contextState == QScriptContext::ExceptionState);
            if (m_debugger->scriptsWidget) {
                m_debugger->scriptsWidget->setCurrentScript(m_info.scriptId());
            }
            if (m_debugger->codeWidget) {
                m_debugger->codeWidget->setCurrentScript(m_info.scriptId());
                QScriptDebuggerCodeViewInterface *view = m_debugger->codeWidget->currentView();
                if (view)
                    view->setExecutionLineNumber(m_info.lineNumber(), error);
            }
            finish();
        }
    }

private:
    QScriptDebuggerPrivate *m_debugger;
    int m_frameIndex;
    QScriptContextInfo m_info;
};

} // namespace

void QScriptDebuggerPrivate::selectScriptForFrame(int frameIndex)
{
    QScriptDebuggerJob *job = new ShowFrameCodeJob(this, frameIndex);
    scheduleJob(job);
}

void QScriptDebuggerPrivate::emitStoppedSignal()
{
    emit q_func()->stopped();
}

/*!
  Constructs a new QScriptDebugger object.
*/
QScriptDebugger::QScriptDebugger(QObject *parent)
    : QObject(*new QScriptDebuggerPrivate, parent)
{
    ++scriptDebuggerCount;
}

/*!
  Destroys this QScriptDebugger.
*/
QScriptDebugger::~QScriptDebugger()
{
    --scriptDebuggerCount;
    if ((scriptDebuggerCount == 0) && eventCallbackRegistered) {
        eventCallbackRegistered = false;
        QInternal::unregisterCallback(QInternal::EventNotifyCallback,
                                      scriptDebuggerEventCallback);
    }
}

/*!
  \internal
*/
QScriptDebugger::QScriptDebugger(QScriptDebuggerPrivate &dd, QObject *parent)
    : QObject(dd, parent)
{
}

QScriptDebuggerFrontend *QScriptDebugger::frontend() const
{
    Q_D(const QScriptDebugger);
    return d->frontend;
}

void QScriptDebugger::setFrontend(QScriptDebuggerFrontend *frontend)
{
    Q_D(QScriptDebugger);
    if (d->frontend)
        d->frontend->setEventHandler(0);
    d->frontend = frontend;
    if (frontend) {
        frontend->setEventHandler(d);
        if (!eventCallbackRegistered) {
            eventCallbackRegistered = true;
            QInternal::registerCallback(QInternal::EventNotifyCallback,
                                        scriptDebuggerEventCallback);
        }
    }
}

QAction *QScriptDebugger::action(DebuggerAction action, QObject *parent)
{
    switch (action) {
    case InterruptAction:
        return interruptAction(parent);
    case ContinueAction:
        return continueAction(parent);
    case StepIntoAction:
        return stepIntoAction(parent);
    case StepOverAction:
        return stepOverAction(parent);
    case StepOutAction:
        return stepOutAction(parent);
    case RunToCursorAction:
        return runToCursorAction(parent);
    case RunToNewScriptAction:
        return runToNewScriptAction(parent);
    case ToggleBreakpointAction:
        return toggleBreakpointAction(parent);
    case ClearDebugOutputAction:
        return clearDebugOutputAction(parent);
    case ClearErrorLogAction:
        return clearErrorLogAction(parent);
    case ClearConsoleAction:
        return clearConsoleAction(parent);
    case FindInScriptAction:
        return findInScriptAction(parent);
    case FindNextInScriptAction:
        return findNextInScriptAction(parent);
    case FindPreviousInScriptAction:
        return findPreviousInScriptAction(parent);
    case GoToLineAction:
        return goToLineAction(parent);
    }
    return 0;
}

QWidget *QScriptDebugger::widget(DebuggerWidget widget)
{
    switch (widget) {
    case ConsoleWidget: {
        QScriptDebuggerConsoleWidgetInterface *w = consoleWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createConsoleWidget();
            setConsoleWidget(w);
        }
        return w;
    }
    case StackWidget: {
        QScriptDebuggerStackWidgetInterface *w = stackWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createStackWidget();
            setStackWidget(w);
        }
        return w;
    }
    case ScriptsWidget: {
        QScriptDebuggerScriptsWidgetInterface *w = scriptsWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createScriptsWidget();
            setScriptsWidget(w);
        }
        return w;
    }
    case LocalsWidget: {
        QScriptDebuggerLocalsWidgetInterface *w = localsWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createLocalsWidget();
            setLocalsWidget(w);
        }
        return w;
    }
    case CodeWidget: {
        QScriptDebuggerCodeWidgetInterface *w = codeWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createCodeWidget();
            setCodeWidget(w);
        }
        return w;
    }
    case CodeFinderWidget: {
        QScriptDebuggerCodeFinderWidgetInterface *w = codeFinderWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createCodeFinderWidget();
            setCodeFinderWidget(w);
        }
        return w;
    }
    case BreakpointsWidget: {
        QScriptBreakpointsWidgetInterface *w = breakpointsWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createBreakpointsWidget();
            setBreakpointsWidget(w);
        }
        return w;
    }
    case DebugOutputWidget: {
        QScriptDebugOutputWidgetInterface *w = debugOutputWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createDebugOutputWidget();
            setDebugOutputWidget(w);
        }
        return w;
    }
    case ErrorLogWidget: {
        QScriptErrorLogWidgetInterface *w = errorLogWidget();
        if (!w && widgetFactory()) {
            w = widgetFactory()->createErrorLogWidget();
            setErrorLogWidget(w);
        }
        return w;
    }
    }
    return 0;
}

QScriptDebuggerConsoleWidgetInterface *QScriptDebugger::consoleWidget() const
{
    Q_D(const QScriptDebugger);
    return d->consoleWidget;
}

void QScriptDebugger::setConsoleWidget(QScriptDebuggerConsoleWidgetInterface *consoleWidget)
{
    Q_D(QScriptDebugger);
    if (d->consoleWidget) {
        QObject::disconnect(d->consoleWidget, 0, this, 0);
    }
    d->consoleWidget = consoleWidget;
    if (consoleWidget) {
        consoleWidget->setCommandHistorian(d->console);
        consoleWidget->setCompletionProvider(d);
        QObject::connect(consoleWidget, SIGNAL(lineEntered(QString)),
                         this, SLOT(_q_onLineEntered(QString)));

        d->console->showDebuggerInfoMessage(consoleWidget);
    }
}

QScriptDebuggerStackWidgetInterface *QScriptDebugger::stackWidget() const
{
    Q_D(const QScriptDebugger);
    return d->stackWidget;
}

void QScriptDebugger::setStackWidget(QScriptDebuggerStackWidgetInterface *stackWidget)
{
    Q_D(QScriptDebugger);
    if (d->stackWidget) {
        QObject::disconnect(d->stackWidget, 0, this, 0);
    }
    d->stackWidget = stackWidget;
    if (stackWidget) {
        if (!d->stackModel) {
            d->stackModel = new QScriptDebuggerStackModel(this);
            if (d->interactive)
                d->scheduleJob(new SyncStackJob(d));
        }
        stackWidget->setStackModel(d->stackModel);
        QObject::connect(stackWidget, SIGNAL(currentFrameChanged(int)),
                         this, SLOT(_q_onCurrentFrameChanged(int)));
    }
}

QScriptDebuggerScriptsWidgetInterface *QScriptDebugger::scriptsWidget() const
{
    Q_D(const QScriptDebugger);
    return d->scriptsWidget;
}

void QScriptDebugger::setScriptsWidget(QScriptDebuggerScriptsWidgetInterface *scriptsWidget)
{
    Q_D(QScriptDebugger);
    if (d->scriptsWidget) {
        QObject::disconnect(d->scriptsWidget, 0, this, 0);
    }
    d->scriptsWidget = scriptsWidget;
    if (scriptsWidget) {
        if (!d->scriptsModel) {
            d->scriptsModel = new QScriptDebuggerScriptsModel(this);
            if (d->interactive)
                d->scheduleJob(new SyncScriptsJob(d));
        }
        scriptsWidget->setScriptsModel(d->scriptsModel);
        QObject::connect(scriptsWidget, SIGNAL(currentScriptChanged(qint64)),
                         this, SLOT(_q_onCurrentScriptChanged(qint64)));
        QObject::connect(d->scriptsWidget, SIGNAL(scriptLocationSelected(int)),
                         this, SLOT(_q_onScriptLocationSelected(int)));
    }
}

QScriptDebuggerLocalsWidgetInterface *QScriptDebugger::localsWidget() const
{
    Q_D(const QScriptDebugger);
    return d->localsWidget;
}

void QScriptDebugger::setLocalsWidget(QScriptDebuggerLocalsWidgetInterface *localsWidget)
{
    Q_D(QScriptDebugger);
    if (d->localsWidget) {
        // ### d->localsWidget->setLocalsModel(0);
    }
    localsWidget->setCompletionProvider(d);
    d->localsWidget = localsWidget;
}

QScriptDebuggerCodeWidgetInterface *QScriptDebugger::codeWidget() const
{
    Q_D(const QScriptDebugger);
    return d->codeWidget;
}

void QScriptDebugger::setCodeWidget(QScriptDebuggerCodeWidgetInterface *codeWidget)
{
    Q_D(QScriptDebugger);
    if (d->codeWidget) {
        d->codeWidget->removeEventFilter(this);
    }
    d->codeWidget = codeWidget;
    if (codeWidget) {
        if (!d->scriptsModel) {
            d->scriptsModel = new QScriptDebuggerScriptsModel(this);
            if (d->interactive)
                d->scheduleJob(new SyncScriptsJob(d));
        }
        codeWidget->setScriptsModel(d->scriptsModel);
        if (!d->breakpointsModel) {
            d->breakpointsModel = new QScriptBreakpointsModel(d, d, this);
            if (d->interactive)
                d->scheduleJob(new SyncBreakpointsJob(d));
        }
        codeWidget->setBreakpointsModel(d->breakpointsModel);
        codeWidget->setToolTipProvider(d);
        codeWidget->installEventFilter(this);
    }
    bool hasScript = (codeWidget != 0) && (codeWidget->currentView() != 0);
    if (d->findInScriptAction)
        d->findInScriptAction->setEnabled(hasScript && (d->codeFinderWidget != 0));
    if (d->goToLineAction)
        d->goToLineAction->setEnabled(hasScript);
    if (d->toggleBreakpointAction)
        d->toggleBreakpointAction->setEnabled(hasScript);
}

QScriptDebuggerCodeFinderWidgetInterface *QScriptDebugger::codeFinderWidget() const
{
    Q_D(const QScriptDebugger);
    return d->codeFinderWidget;
}

void QScriptDebugger::setCodeFinderWidget(QScriptDebuggerCodeFinderWidgetInterface *codeFinderWidget)
{
    Q_D(QScriptDebugger);
    if (d->codeFinderWidget) {
        QObject::disconnect(d->codeFinderWidget, 0, this, 0);
    }
    d->codeFinderWidget = codeFinderWidget;
    if (codeFinderWidget) {
        QObject::connect(codeFinderWidget, SIGNAL(findRequest(QString,int)),
                         this, SLOT(_q_onFindCodeRequest(QString,int)));
    }
    if (d->findInScriptAction) {
        d->findInScriptAction->setEnabled(
            (codeFinderWidget != 0)
            && (d->codeWidget != 0)
            && (d->codeWidget->currentView() != 0));
    }
}

QScriptDebugOutputWidgetInterface *QScriptDebugger::debugOutputWidget() const
{
    Q_D(const QScriptDebugger);
    return d->debugOutputWidget;
}

void QScriptDebugger::setDebugOutputWidget(QScriptDebugOutputWidgetInterface *debugOutputWidget)
{
    Q_D(QScriptDebugger);
    d->debugOutputWidget = debugOutputWidget;
}

QScriptBreakpointsWidgetInterface *QScriptDebugger::breakpointsWidget() const
{
    Q_D(const QScriptDebugger);
    return d->breakpointsWidget;
}

void QScriptDebugger::setBreakpointsWidget(QScriptBreakpointsWidgetInterface *breakpointsWidget)
{
    Q_D(QScriptDebugger);
    if (d->breakpointsWidget) {
        // ### invalidate
    }
    d->breakpointsWidget = breakpointsWidget;
    if (breakpointsWidget) {
        if (!d->breakpointsModel) {
            d->breakpointsModel = new QScriptBreakpointsModel(d, d, this);
            if (d->interactive)
                d->scheduleJob(new SyncBreakpointsJob(d));
        }
        d->breakpointsWidget->setBreakpointsModel(d->breakpointsModel);
        d->breakpointsWidget->setScriptsModel(d->scriptsModel);
    }
}

QScriptErrorLogWidgetInterface *QScriptDebugger::errorLogWidget() const
{
    Q_D(const QScriptDebugger);
    return d->errorLogWidget;
}

void QScriptDebugger::setErrorLogWidget(QScriptErrorLogWidgetInterface *errorLogWidget)
{
    Q_D(QScriptDebugger);
    d->errorLogWidget = errorLogWidget;
}

QScriptDebuggerWidgetFactoryInterface *QScriptDebugger::widgetFactory() const
{
    Q_D(const QScriptDebugger);
    return d->widgetFactory;
}

void QScriptDebugger::setWidgetFactory(QScriptDebuggerWidgetFactoryInterface *factory)
{
    Q_D(QScriptDebugger);
    d->widgetFactory = factory;
}

QAction *QScriptDebugger::interruptAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->interruptAction) {
        QIcon interruptIcon;
        interruptIcon.addPixmap(d->pixmap(QString::fromLatin1("interrupt.png")), QIcon::Normal);
        interruptIcon.addPixmap(d->pixmap(QString::fromLatin1("d_interrupt.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->interruptAction = new QAction(interruptIcon, QScriptDebugger::tr("Interrupt"), parent);
        d->interruptAction->setEnabled(!d->interactive);
#ifndef QT_NO_SHORTCUT
        d->interruptAction->setShortcut(QScriptDebugger::tr("Shift+F5"));
#endif
        QObject::connect(d->interruptAction, SIGNAL(triggered()),
                         that, SLOT(_q_interrupt()));
    }
    return d->interruptAction;
}

QAction *QScriptDebugger::continueAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->continueAction) {
        QIcon continueIcon;
        continueIcon.addPixmap(d->pixmap(QString::fromLatin1("play.png")), QIcon::Normal);
        continueIcon.addPixmap(d->pixmap(QString::fromLatin1("d_play.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->continueAction = new QAction(continueIcon, QScriptDebugger::tr("Continue"), parent);
        d->continueAction->setEnabled(d->interactive);
#ifndef QT_NO_SHORTCUT
        d->continueAction->setShortcut(QScriptDebugger::tr("F5"));
#endif
        QObject::connect(d->continueAction, SIGNAL(triggered()),
                         that, SLOT(_q_continue()));
    }
    return d->continueAction;
}

QAction *QScriptDebugger::stepIntoAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->stepIntoAction) {
        QIcon stepIntoIcon;
        stepIntoIcon.addPixmap(d->pixmap(QString::fromLatin1("stepinto.png")), QIcon::Normal);
        stepIntoIcon.addPixmap(d->pixmap(QString::fromLatin1("d_stepinto.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->stepIntoAction = new QAction(stepIntoIcon, QScriptDebugger::tr("Step Into"), parent);
        d->stepIntoAction->setEnabled(d->interactive);
#ifndef QT_NO_SHORTCUT
        d->stepIntoAction->setShortcut(QScriptDebugger::tr("F11"));
#endif
        QObject::connect(d->stepIntoAction, SIGNAL(triggered()),
                         that, SLOT(_q_stepInto()));
    }
    return d->stepIntoAction;
}

QAction *QScriptDebugger::stepOverAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->stepOverAction) {
        QIcon stepOverIcon;
        stepOverIcon.addPixmap(d->pixmap(QString::fromLatin1("stepover.png")), QIcon::Normal);
        stepOverIcon.addPixmap(d->pixmap(QString::fromLatin1("d_stepover.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->stepOverAction = new QAction(stepOverIcon, QScriptDebugger::tr("Step Over"), parent);
        d->stepOverAction->setEnabled(d->interactive);
#ifndef QT_NO_SHORTCUT
        d->stepOverAction->setShortcut(QScriptDebugger::tr("F10"));
#endif
        QObject::connect(d->stepOverAction, SIGNAL(triggered()),
                         that, SLOT(_q_stepOver()));
    }
    return d->stepOverAction;
}

QAction *QScriptDebugger::stepOutAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->stepOutAction) {
        QIcon stepOutIcon;
        stepOutIcon.addPixmap(d->pixmap(QString::fromLatin1("stepout.png")), QIcon::Normal);
        stepOutIcon.addPixmap(d->pixmap(QString::fromLatin1("d_stepout.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->stepOutAction = new QAction(stepOutIcon, QScriptDebugger::tr("Step Out"), parent);
        d->stepOutAction->setEnabled(d->interactive);
#ifndef QT_NO_SHORTCUT
        d->stepOutAction->setShortcut(QScriptDebugger::tr("Shift+F11"));
#endif
        QObject::connect(d->stepOutAction, SIGNAL(triggered()),
                         that, SLOT(_q_stepOut()));
    }
    return d->stepOutAction;
}

QAction *QScriptDebugger::runToCursorAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->runToCursorAction) {
        QIcon runToCursorIcon;
        runToCursorIcon.addPixmap(d->pixmap(QString::fromLatin1("runtocursor.png")), QIcon::Normal);
        runToCursorIcon.addPixmap(d->pixmap(QString::fromLatin1("d_runtocursor.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->runToCursorAction = new QAction(runToCursorIcon, QScriptDebugger::tr("Run to Cursor"), parent);
        d->runToCursorAction->setEnabled(d->interactive);
#ifndef QT_NO_SHORTCUT
        d->runToCursorAction->setShortcut(QScriptDebugger::tr("Ctrl+F10"));
#endif
        QObject::connect(d->runToCursorAction, SIGNAL(triggered()),
                         that, SLOT(_q_runToCursor()));
    }
    return d->runToCursorAction;
}

QAction *QScriptDebugger::runToNewScriptAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->runToNewScriptAction) {
        QIcon runToNewScriptIcon;
        runToNewScriptIcon.addPixmap(d->pixmap(QString::fromLatin1("runtonewscript.png")), QIcon::Normal);
        runToNewScriptIcon.addPixmap(d->pixmap(QString::fromLatin1("d_breakonscriptload.png")), QIcon::Disabled);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->runToNewScriptAction = new QAction(runToNewScriptIcon,
                                                           QScriptDebugger::tr("Run to New Script"), parent);
        d->runToNewScriptAction->setEnabled(d->interactive);
        QObject::connect(d->runToNewScriptAction, SIGNAL(triggered()),
                         that, SLOT(_q_runToNewScript()));
    }
    return d->runToNewScriptAction;
}

QAction *QScriptDebugger::toggleBreakpointAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->toggleBreakpointAction) {
        QIcon toggleBreakpointIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->toggleBreakpointAction = new QAction(toggleBreakpointIcon,
                                                             QScriptDebugger::tr("Toggle Breakpoint"), parent);
#ifndef QT_NO_SHORTCUT
        d->toggleBreakpointAction->setShortcut(QScriptDebugger::tr("F9"));
#endif
        d->toggleBreakpointAction->setEnabled((d->codeWidget != 0) && (d->codeWidget->currentView() != 0));
        QObject::connect(d->toggleBreakpointAction, SIGNAL(triggered()),
                         that, SLOT(_q_toggleBreakpoint()));
    }
    return d->toggleBreakpointAction;
}

QAction *QScriptDebugger::clearDebugOutputAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->clearDebugOutputAction) {
        QIcon clearDebugOutputIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->clearDebugOutputAction = new QAction(clearDebugOutputIcon, QScriptDebugger::tr("Clear Debug Output"), parent);
        QObject::connect(d->clearDebugOutputAction, SIGNAL(triggered()),
                         that, SLOT(_q_clearDebugOutput()));
    }
    return d->clearDebugOutputAction;
}

QAction *QScriptDebugger::clearErrorLogAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->clearErrorLogAction) {
        QIcon clearErrorLogIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->clearErrorLogAction = new QAction(clearErrorLogIcon, QScriptDebugger::tr("Clear Error Log"), parent);
        QObject::connect(d->clearErrorLogAction, SIGNAL(triggered()),
                         that, SLOT(_q_clearErrorLog()));
    }
    return d->clearErrorLogAction;
}

QAction *QScriptDebugger::clearConsoleAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->clearConsoleAction) {
        QIcon clearConsoleIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->clearConsoleAction = new QAction(clearConsoleIcon, QScriptDebugger::tr("Clear Console"), parent);
        QObject::connect(d->clearConsoleAction, SIGNAL(triggered()),
                         that, SLOT(_q_clearConsole()));
    }
    return d->clearConsoleAction;
}

QAction *QScriptDebugger::findInScriptAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->findInScriptAction) {
        QIcon findInScriptIcon;
        findInScriptIcon.addPixmap(d->pixmap(QString::fromLatin1("find.png")), QIcon::Normal);
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->findInScriptAction = new QAction(findInScriptIcon, QScriptDebugger::tr("&Find in Script..."), parent);
#ifndef QT_NO_SHORTCUT
        d->findInScriptAction->setShortcut(QScriptDebugger::tr("Ctrl+F"));
#endif
        d->findInScriptAction->setEnabled(
            (d->codeFinderWidget != 0)
            && (d->codeWidget != 0)
            && (d->codeWidget->currentView() != 0));
        QObject::connect(d->findInScriptAction, SIGNAL(triggered()),
                         that, SLOT(_q_findInScript()));
    }
    return d->findInScriptAction;
}

QAction *QScriptDebugger::findNextInScriptAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->findNextInScriptAction) {
        QIcon findNextInScriptIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->findNextInScriptAction = new QAction(findNextInScriptIcon, QScriptDebugger::tr("Find &Next"), parent);
        d->findNextInScriptAction->setEnabled(d->codeFinderWidget && !d->codeFinderWidget->text().isEmpty());
#ifndef QT_NO_SHORTCUT
        d->findNextInScriptAction->setShortcut(QScriptDebugger::tr("F3"));
#endif
        QObject::connect(d->findNextInScriptAction, SIGNAL(triggered()),
                         that, SLOT(_q_findNextInScript()));
    }
    return d->findNextInScriptAction;
}

QAction *QScriptDebugger::findPreviousInScriptAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->findPreviousInScriptAction) {
        QIcon findPreviousInScriptIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->findPreviousInScriptAction = new QAction(findPreviousInScriptIcon, QScriptDebugger::tr("Find &Previous"), parent);
        d->findPreviousInScriptAction->setEnabled(d->codeFinderWidget && !d->codeFinderWidget->text().isEmpty());
#ifndef QT_NO_SHORTCUT
        d->findPreviousInScriptAction->setShortcut(QScriptDebugger::tr("Shift+F3"));
#endif
        QObject::connect(d->findPreviousInScriptAction, SIGNAL(triggered()),
                         that, SLOT(_q_findPreviousInScript()));
    }
    return d->findPreviousInScriptAction;
}

QAction *QScriptDebugger::goToLineAction(QObject *parent) const
{
    Q_D(const QScriptDebugger);
    if (!d->goToLineAction) {
        QIcon goToLineIcon;
        QScriptDebugger *that = const_cast<QScriptDebugger*>(this);
        that->d_func()->goToLineAction = new QAction(goToLineIcon, QScriptDebugger::tr("Go to Line"), parent);
#ifndef QT_NO_SHORTCUT
        d->goToLineAction->setShortcut(QScriptDebugger::tr("Ctrl+G"));
#endif
        d->goToLineAction->setEnabled((d->codeWidget != 0) && (d->codeWidget->currentView() != 0));
        QObject::connect(d->goToLineAction, SIGNAL(triggered()),
                         that, SLOT(_q_goToLine()));
    }
    return d->goToLineAction;
}

QMenu *QScriptDebugger::createStandardMenu(QWidget *widgetParent, QObject *actionParent)
{
    QMenu *menu = new QMenu(widgetParent);
    menu->setTitle(QScriptDebugger::tr("Debug"));
    menu->addAction(action(ContinueAction, actionParent));
    menu->addAction(action(InterruptAction, actionParent));
    menu->addAction(action(StepIntoAction, actionParent));
    menu->addAction(action(StepOverAction, actionParent));
    menu->addAction(action(StepOutAction, actionParent));
    menu->addAction(action(RunToCursorAction, actionParent));
    menu->addAction(action(RunToNewScriptAction, actionParent));

    menu->addSeparator();
    menu->addAction(action(ToggleBreakpointAction, actionParent));

    menu->addSeparator();
    menu->addAction(action(ClearDebugOutputAction, actionParent));
    menu->addAction(action(ClearErrorLogAction, actionParent));
    menu->addAction(action(ClearConsoleAction, actionParent));

    return menu;
}

#ifndef QT_NO_TOOLBAR
QToolBar *QScriptDebugger::createStandardToolBar(QWidget *widgetParent, QObject *actionParent)
{
    QToolBar *tb = new QToolBar(widgetParent);
    tb->setObjectName(QLatin1String("qtscriptdebugger_standardToolBar"));
    tb->addAction(action(ContinueAction, actionParent));
    tb->addAction(action(InterruptAction, actionParent));
    tb->addAction(action(StepIntoAction, actionParent));
    tb->addAction(action(StepOverAction, actionParent));
    tb->addAction(action(StepOutAction, actionParent));
    tb->addAction(action(RunToCursorAction, actionParent));
    tb->addAction(action(RunToNewScriptAction, actionParent));
    tb->addSeparator();
    tb->addAction(action(FindInScriptAction, actionParent));
    return tb;
}
#endif

bool QScriptDebugger::isInteractive() const
{
    Q_D(const QScriptDebugger);
    return d->interactive;
}

/*!
  \reimp
*/
bool QScriptDebugger::eventFilter(QObject *watched, QEvent *e)
{
    Q_D(QScriptDebugger);
    if (watched == d->codeWidget) {
        if (e->type() == QEvent::KeyPress) {
            d->_q_findInScript();
            d->codeFinderWidget->setText(static_cast<QKeyEvent*>(e)->text());
            return true;
        }
    }
    return false;
}

/*!
  \reimp
*/
void QScriptDebugger::timerEvent(QTimerEvent *e)
{
    Q_D(QScriptDebugger);
    if (e->timerId() == d->updatesEnabledTimerId) {
        killTimer(d->updatesEnabledTimerId);
        d->updatesEnabledTimerId = -1;
        if (d->stackWidget)
            d->stackWidget->setUpdatesEnabled(true);
        if (d->localsWidget)
            d->localsWidget->setUpdatesEnabled(true);
    } else {
        QObject::timerEvent(e);
    }
}

QT_END_NAMESPACE

#include "moc_qscriptdebugger_p.cpp"
