diff options
Diffstat (limited to 'src/qscriptremotetargetdebugger.cpp')
-rw-r--r-- | src/qscriptremotetargetdebugger.cpp | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/src/qscriptremotetargetdebugger.cpp b/src/qscriptremotetargetdebugger.cpp new file mode 100644 index 0000000..58ccc3a --- /dev/null +++ b/src/qscriptremotetargetdebugger.cpp @@ -0,0 +1,596 @@ +#include "qscriptremotetargetdebugger.h" +#include <QtNetwork/qtcpsocket.h> +#include <QtGui> + +#include <private/qscriptdebuggerfrontend_p.h> +#include <private/qscriptdebugger_p.h> +#include <private/qscriptdebuggercommand_p.h> +#include <private/qscriptdebuggerevent_p.h> +#include <private/qscriptdebuggerresponse_p.h> +#include <private/qscriptdebuggerwidgetfactoryinterface_p.h> +#include <private/qscriptdebugoutputwidget_p.h> +#include <private/qscriptdebuggerconsolewidget_p.h> +#include <private/qscripterrorlogwidget_p.h> +#include <private/qscriptbreakpointswidget_p.h> +#include <private/qscriptdebuggercodewidget_p.h> +#include <private/qscriptdebuggercodefinderwidget_p.h> +#include <private/qscriptdebuggerstackwidget_p.h> +#include <private/qscriptdebuggerscriptswidget_p.h> +#include <private/qscriptdebuggerlocalswidget_p.h> + +// #define DEBUG_DEBUGGER + +namespace { + +class WidgetClosedNotifier : public QObject +{ + Q_OBJECT +public: + WidgetClosedNotifier(QWidget *w, QObject *parent = 0) + : QObject(parent), widget(w) + { + w->installEventFilter(this); + } + + bool eventFilter(QObject *watched, QEvent *e) + { + if (watched != widget) + return false; + if (e->type() != QEvent::Close) + return false; + emit widgetClosed(); + return true; + } + +Q_SIGNALS: + void widgetClosed(); + +private: + QWidget *widget; +}; + +} // namespace + +class QScriptRemoteTargetDebuggerFrontend + : public QObject, public QScriptDebuggerFrontend +{ + Q_OBJECT +public: + enum Error { + NoError, + HostNotFoundError, + ConnectionRefusedError, + HandshakeError, + SocketError + }; + + enum State { + UnattachedState, + ConnectingState, + HandshakingState, + AttachedState, + DetachingState + }; + + QScriptRemoteTargetDebuggerFrontend(); + ~QScriptRemoteTargetDebuggerFrontend(); + + void attachTo(const QHostAddress &address, quint16 port); + void detach(); + +Q_SIGNALS: + void attached(); + void detached(); + void error(Error error); + +protected: + void processCommand(int id, const QScriptDebuggerCommand &command); + +private Q_SLOTS: + void onSocketStateChanged(QAbstractSocket::SocketState); + void onSocketError(QAbstractSocket::SocketError); + void onReadyRead(); + +private: + State m_state; + QTcpSocket *m_socket; + int m_blockSize; + + Q_DISABLE_COPY(QScriptRemoteTargetDebuggerFrontend) +}; + +QScriptRemoteTargetDebuggerFrontend::QScriptRemoteTargetDebuggerFrontend() + : m_state(UnattachedState), m_socket(0), m_blockSize(0) +{ +} + +QScriptRemoteTargetDebuggerFrontend::~QScriptRemoteTargetDebuggerFrontend() +{ +} + +void QScriptRemoteTargetDebuggerFrontend::attachTo(const QHostAddress &address, quint16 port) +{ + Q_ASSERT(m_state == UnattachedState); + if (!m_socket) { + m_socket = new QTcpSocket(this); + QObject::connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); + QObject::connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(onSocketError(QAbstractSocket::SocketError))); + QObject::connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + } + m_socket->connectToHost(address, port); +} + +void QScriptRemoteTargetDebuggerFrontend::detach() +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "implement me"); +} + +void QScriptRemoteTargetDebuggerFrontend::onSocketStateChanged(QAbstractSocket::SocketState state) +{ + switch (state) { + case QAbstractSocket::UnconnectedState: + m_state = UnattachedState; + break; + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + m_state = ConnectingState; + break; + case QAbstractSocket::ConnectedState: { + m_state = HandshakingState; + QByteArray handshakeData("QtScriptDebug-Handshake"); +#ifdef DEBUG_DEBUGGER + qDebug("writing handshake data"); +#endif + m_socket->write(handshakeData); + } break; + case QAbstractSocket::BoundState: + break; + case QAbstractSocket::ClosingState: + Q_ASSERT(0); + break; + case QAbstractSocket::ListeningState: + break; + } +} + +void QScriptRemoteTargetDebuggerFrontend::onSocketError(QAbstractSocket::SocketError error) +{ + Q_ASSERT_X(false, Q_FUNC_INFO, qPrintable(m_socket->errorString())); +} + +void QScriptRemoteTargetDebuggerFrontend::onReadyRead() +{ + switch (m_state) { + case UnattachedState: + case ConnectingState: + case DetachingState: + Q_ASSERT(0); + break; + + case HandshakingState: { + QByteArray handshakeData("QtScriptDebug-Handshake"); + if (m_socket->bytesAvailable() >= handshakeData.size()) { + QByteArray ba = m_socket->read(handshakeData.size()); + if (ba == handshakeData) { +#ifdef DEBUG_DEBUGGER + qDebug("handshake ok!"); +#endif + m_state = AttachedState; + QMetaObject::invokeMethod(this, "emitAttachedSignal", Qt::QueuedConnection); + if (m_socket->bytesAvailable() > 0) + QMetaObject::invokeMethod(this, "onReadyRead", Qt::QueuedConnection); + } else { +// d->error = HandshakeError; +// d->errorString = QString::fromLatin1("Incorrect handshake data received"); + m_state = DetachingState; + emit error(HandshakeError); + m_socket->close(); + } + } + } break; + + case AttachedState: { +#ifdef DEBUG_DEBUGGER + qDebug() << "got something! bytes available:" << m_socket->bytesAvailable(); +#endif + QDataStream in(m_socket); + in.setVersion(QDataStream::Qt_4_5); + if (m_blockSize == 0) { + if (m_socket->bytesAvailable() < (int)sizeof(quint32)) + return; + in >> m_blockSize; +#ifdef DEBUG_DEBUGGER + qDebug() << "blockSize:" << m_blockSize; +#endif + } + if (m_socket->bytesAvailable() < m_blockSize) { +#ifdef DEBUG_DEBUGGER + qDebug("waiting for %lld more bytes...", m_blockSize - m_socket->bytesAvailable()); +#endif + return; + } + + int wasAvailable = m_socket->bytesAvailable(); + quint8 type; + in >> type; + if (type == 0) { + // event +#ifdef DEBUG_DEBUGGER + qDebug("deserializing event"); +#endif + QScriptDebuggerEvent event(QScriptDebuggerEvent::None); + in >> event; +#ifdef DEBUG_DEBUGGER + qDebug("notifying event of type %d", event.type()); +#endif + notifyEvent(event); + } else { + // command response +#ifdef DEBUG_DEBUGGER + qDebug("deserializing command response"); +#endif + qint32 id; + in >> id; + QScriptDebuggerResponse response; + in >> response; +#ifdef DEBUG_DEBUGGER + qDebug("notifying command %d finished", id); +#endif + notifyCommandFinished((int)id, response); + } + Q_ASSERT(m_socket->bytesAvailable() == wasAvailable - m_blockSize); + m_blockSize = 0; +#ifdef DEBUG_DEBUGGER + qDebug("bytes available is now %lld", m_socket->bytesAvailable()); +#endif + if (m_socket->bytesAvailable() != 0) + QMetaObject::invokeMethod(this, "onReadyRead", Qt::QueuedConnection); + } break; + } +} + +void QScriptRemoteTargetDebuggerFrontend::processCommand(int id, const QScriptDebuggerCommand &command) +{ + Q_ASSERT(m_state == AttachedState); + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_4_5); + out << (quint32)0; // reserve 4 bytes for block size + out << (qint32)id; + out << command; + out.device()->seek(0); + out << (quint32)(block.size() - sizeof(quint32)); +#ifdef DEBUG_DEBUGGER + qDebug("writing command (id=%d, %d bytes)", id, block.size()); +#endif + m_socket->write(block); +} + +QScriptRemoteTargetDebugger::QScriptRemoteTargetDebugger(QObject *parent) + : QObject(parent), m_frontend(0), m_debugger(0), m_autoShow(true), + m_standardWindow(0) +{ +} + +QScriptRemoteTargetDebugger::~QScriptRemoteTargetDebugger() +{ + delete m_frontend; + delete m_debugger; +} + +void QScriptRemoteTargetDebugger::attachTo(const QHostAddress &address, quint16 port) +{ + createDebugger(); + if (!m_frontend) { + m_frontend = new QScriptRemoteTargetDebuggerFrontend(); + QObject::connect(m_frontend, SIGNAL(attached()), this, SIGNAL(attached())); + QObject::connect(m_frontend, SIGNAL(detached()), this, SIGNAL(detached())); + } + m_frontend->attachTo(address, port); + m_debugger->setFrontend(m_frontend); +} + +void QScriptRemoteTargetDebugger::createDebugger() +{ + if (!m_debugger) { + m_debugger = new QScriptDebugger(); + m_debugger->setWidgetFactory(this); + QObject::connect(m_debugger, SIGNAL(started()), + this, SIGNAL(evaluationResumed())); + QObject::connect(m_debugger, SIGNAL(stopped()), + this, SIGNAL(evaluationSuspended())); + if (m_autoShow) { + QObject::connect(this, SIGNAL(evaluationSuspended()), + this, SLOT(showStandardWindow())); + } + } +} + +QMainWindow *QScriptRemoteTargetDebugger::standardWindow() const +{ + if (m_standardWindow) + return m_standardWindow; + if (!QApplication::instance()) + return 0; + QScriptRemoteTargetDebugger *that = const_cast<QScriptRemoteTargetDebugger*>(this); + + QMainWindow *win = new QMainWindow(); + QDockWidget *scriptsDock = new QDockWidget(win); + scriptsDock->setObjectName(QLatin1String("qtscriptdebugger_scriptsDockWidget")); + scriptsDock->setWindowTitle(QObject::tr("Loaded Scripts")); + scriptsDock->setWidget(widget(ScriptsWidget)); + win->addDockWidget(Qt::LeftDockWidgetArea, scriptsDock); + + QDockWidget *breakpointsDock = new QDockWidget(win); + breakpointsDock->setObjectName(QLatin1String("qtscriptdebugger_breakpointsDockWidget")); + breakpointsDock->setWindowTitle(QObject::tr("Breakpoints")); + breakpointsDock->setWidget(widget(BreakpointsWidget)); + win->addDockWidget(Qt::LeftDockWidgetArea, breakpointsDock); + + QDockWidget *stackDock = new QDockWidget(win); + stackDock->setObjectName(QLatin1String("qtscriptdebugger_stackDockWidget")); + stackDock->setWindowTitle(QObject::tr("Stack")); + stackDock->setWidget(widget(StackWidget)); + win->addDockWidget(Qt::RightDockWidgetArea, stackDock); + + QDockWidget *localsDock = new QDockWidget(win); + localsDock->setObjectName(QLatin1String("qtscriptdebugger_localsDockWidget")); + localsDock->setWindowTitle(QObject::tr("Locals")); + localsDock->setWidget(widget(LocalsWidget)); + win->addDockWidget(Qt::RightDockWidgetArea, localsDock); + + QDockWidget *consoleDock = new QDockWidget(win); + consoleDock->setObjectName(QLatin1String("qtscriptdebugger_consoleDockWidget")); + consoleDock->setWindowTitle(QObject::tr("Console")); + consoleDock->setWidget(widget(ConsoleWidget)); + win->addDockWidget(Qt::BottomDockWidgetArea, consoleDock); + + QDockWidget *debugOutputDock = new QDockWidget(win); + debugOutputDock->setObjectName(QLatin1String("qtscriptdebugger_debugOutputDockWidget")); + debugOutputDock->setWindowTitle(QObject::tr("Debug Output")); + debugOutputDock->setWidget(widget(DebugOutputWidget)); + win->addDockWidget(Qt::BottomDockWidgetArea, debugOutputDock); + + QDockWidget *errorLogDock = new QDockWidget(win); + errorLogDock->setObjectName(QLatin1String("qtscriptdebugger_errorLogDockWidget")); + errorLogDock->setWindowTitle(QObject::tr("Error Log")); + errorLogDock->setWidget(widget(ErrorLogWidget)); + win->addDockWidget(Qt::BottomDockWidgetArea, errorLogDock); + + win->tabifyDockWidget(errorLogDock, debugOutputDock); + win->tabifyDockWidget(debugOutputDock, consoleDock); + + win->addToolBar(Qt::TopToolBarArea, that->createStandardToolBar()); + +#ifndef QT_NO_MENUBAR + win->menuBar()->addMenu(that->createStandardMenu(win)); + + QMenu *editMenu = win->menuBar()->addMenu(QObject::tr("Search")); + editMenu->addAction(action(FindInScriptAction)); + editMenu->addAction(action(FindNextInScriptAction)); + editMenu->addAction(action(FindPreviousInScriptAction)); + editMenu->addSeparator(); + editMenu->addAction(action(GoToLineAction)); + + QMenu *viewMenu = win->menuBar()->addMenu(QObject::tr("View")); + viewMenu->addAction(scriptsDock->toggleViewAction()); + viewMenu->addAction(breakpointsDock->toggleViewAction()); + viewMenu->addAction(stackDock->toggleViewAction()); + viewMenu->addAction(localsDock->toggleViewAction()); + viewMenu->addAction(consoleDock->toggleViewAction()); + viewMenu->addAction(debugOutputDock->toggleViewAction()); + viewMenu->addAction(errorLogDock->toggleViewAction()); +#endif + + QWidget *central = new QWidget(); + QVBoxLayout *vbox = new QVBoxLayout(central); + vbox->addWidget(widget(CodeWidget)); + vbox->addWidget(widget(CodeFinderWidget)); + widget(CodeFinderWidget)->hide(); + win->setCentralWidget(central); + + win->setWindowTitle(QObject::tr("Qt Script Debugger")); + + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + QVariant geometry = settings.value(QLatin1String("Qt/scripttools/debugging/mainWindowGeometry")); + if (geometry.isValid()) + win->restoreGeometry(geometry.toByteArray()); + QVariant state = settings.value(QLatin1String("Qt/scripttools/debugging/mainWindowState")); + if (state.isValid()) + win->restoreState(state.toByteArray()); + + WidgetClosedNotifier *closedNotifier = new WidgetClosedNotifier(win, that); + QObject::connect(closedNotifier, SIGNAL(widgetClosed()), + action(ContinueAction), SLOT(trigger())); + + const_cast<QScriptRemoteTargetDebugger*>(this)->m_standardWindow = win; + return win; +} + +void QScriptRemoteTargetDebugger::showStandardWindow() +{ + (void)standardWindow(); // ensure it's created + m_standardWindow->show(); +} + +QWidget *QScriptRemoteTargetDebugger::widget(DebuggerWidget widget) const +{ + const_cast<QScriptRemoteTargetDebugger*>(this)->createDebugger(); + switch (widget) { + case ConsoleWidget: { + QScriptDebuggerConsoleWidgetInterface *w = m_debugger->consoleWidget(); + if (!w) { + w = new QScriptDebuggerConsoleWidget(); + m_debugger->setConsoleWidget(w); + } + return w; + } + case StackWidget: { + QScriptDebuggerStackWidgetInterface *w = m_debugger->stackWidget(); + if (!w) { + w = new QScriptDebuggerStackWidget(); + m_debugger->setStackWidget(w); + } + return w; + } + case ScriptsWidget: { + QScriptDebuggerScriptsWidgetInterface *w = m_debugger->scriptsWidget(); + if (!w) { + w = new QScriptDebuggerScriptsWidget(); + m_debugger->setScriptsWidget(w); + } + return w; + } + case LocalsWidget: { + QScriptDebuggerLocalsWidgetInterface *w = m_debugger->localsWidget(); + if (!w) { + w = new QScriptDebuggerLocalsWidget(); + m_debugger->setLocalsWidget(w); + } + return w; + } + case CodeWidget: { + QScriptDebuggerCodeWidgetInterface *w = m_debugger->codeWidget(); + if (!w) { + w = new QScriptDebuggerCodeWidget(); + m_debugger->setCodeWidget(w); + } + return w; + } + case CodeFinderWidget: { + QScriptDebuggerCodeFinderWidgetInterface *w = m_debugger->codeFinderWidget(); + if (!w) { + w = new QScriptDebuggerCodeFinderWidget(); + m_debugger->setCodeFinderWidget(w); + } + return w; + } + case BreakpointsWidget: { + QScriptBreakpointsWidgetInterface *w = m_debugger->breakpointsWidget(); + if (!w) { + w = new QScriptBreakpointsWidget(); + m_debugger->setBreakpointsWidget(w); + } + return w; + } + case DebugOutputWidget: { + QScriptDebugOutputWidgetInterface *w = m_debugger->debugOutputWidget(); + if (!w) { + w = new QScriptDebugOutputWidget(); + m_debugger->setDebugOutputWidget(w); + } + return w; + } + case ErrorLogWidget: { + QScriptErrorLogWidgetInterface *w = m_debugger->errorLogWidget(); + if (!w) { + w = new QScriptErrorLogWidget(); + m_debugger->setErrorLogWidget(w); + } + return w; + } + } + return 0; +} + +QAction *QScriptRemoteTargetDebugger::action(DebuggerAction action) const +{ + QScriptRemoteTargetDebugger *that = const_cast<QScriptRemoteTargetDebugger*>(this); + that->createDebugger(); + switch (action) { + case InterruptAction: + return m_debugger->interruptAction(that); + case ContinueAction: + return m_debugger->continueAction(that); + case StepIntoAction: + return m_debugger->stepIntoAction(that); + case StepOverAction: + return m_debugger->stepOverAction(that); + case StepOutAction: + return m_debugger->stepOutAction(that); + case RunToCursorAction: + return m_debugger->runToCursorAction(that); + case RunToNewScriptAction: + return m_debugger->runToNewScriptAction(that); + case ToggleBreakpointAction: + return m_debugger->toggleBreakpointAction(that); + case ClearDebugOutputAction: + return m_debugger->clearDebugOutputAction(that); + case ClearErrorLogAction: + return m_debugger->clearErrorLogAction(that); + case ClearConsoleAction: + return m_debugger->clearConsoleAction(that); + case FindInScriptAction: + return m_debugger->findInScriptAction(that); + case FindNextInScriptAction: + return m_debugger->findNextInScriptAction(that); + case FindPreviousInScriptAction: + return m_debugger->findPreviousInScriptAction(that); + case GoToLineAction: + return m_debugger->goToLineAction(that); + } + return 0; +} + +QToolBar *QScriptRemoteTargetDebugger::createStandardToolBar(QWidget *parent) +{ + QToolBar *tb = new QToolBar(parent); + tb->setObjectName(QLatin1String("qtscriptdebugger_standardToolBar")); + tb->addAction(action(ContinueAction)); + tb->addAction(action(InterruptAction)); + tb->addAction(action(StepIntoAction)); + tb->addAction(action(StepOverAction)); + tb->addAction(action(StepOutAction)); + tb->addAction(action(RunToCursorAction)); + tb->addAction(action(RunToNewScriptAction)); + tb->addSeparator(); + tb->addAction(action(FindInScriptAction)); + return tb; +} + +QMenu *QScriptRemoteTargetDebugger::createStandardMenu(QWidget *parent) +{ + QMenu *menu = new QMenu(parent); + menu->setTitle(QObject::tr("Debug")); + menu->addAction(action(ContinueAction)); + menu->addAction(action(InterruptAction)); + menu->addAction(action(StepIntoAction)); + menu->addAction(action(StepOverAction)); + menu->addAction(action(StepOutAction)); + menu->addAction(action(RunToCursorAction)); + menu->addAction(action(RunToNewScriptAction)); + + menu->addSeparator(); + menu->addAction(action(ToggleBreakpointAction)); + + menu->addSeparator(); + menu->addAction(action(ClearDebugOutputAction)); + menu->addAction(action(ClearErrorLogAction)); + menu->addAction(action(ClearConsoleAction)); + + return menu; +} + +QScriptDebugOutputWidgetInterface *QScriptRemoteTargetDebugger::createDebugOutputWidget() +{ + return new QScriptDebugOutputWidget(); +} + +QScriptDebuggerConsoleWidgetInterface *QScriptRemoteTargetDebugger::createConsoleWidget() +{ + return new QScriptDebuggerConsoleWidget(); +} + +QScriptErrorLogWidgetInterface *QScriptRemoteTargetDebugger::createErrorLogWidget() +{ + return new QScriptErrorLogWidget(); +} + +QScriptDebuggerCodeFinderWidgetInterface *QScriptRemoteTargetDebugger::createCodeFinderWidget() +{ + return new QScriptDebuggerCodeFinderWidget(); +} + +#include "qscriptremotetargetdebugger.moc" |