#include "qscriptremotetargetdebugger.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #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(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(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(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(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"