mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-25 03:12:53 +00:00
536 lines
19 KiB
C++
536 lines
19 KiB
C++
/* This file is part of KDevelop
|
|
*
|
|
* Copyright 1999-2001 John Birch <jbb@kdevelop.org>
|
|
* Copyright 2001 by Bernd Gehrmann <bernd@kdevelop.org>
|
|
* Copyright 2006 Vladimir Prus <ghost@cs.msu.su>
|
|
* Copyright 2007 Hamish Rodda <rodda@kde.org>
|
|
* Copyright 2009 Niko Sams <niko.sams@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
#include "debugcontroller.h"
|
|
|
|
#include <QtCore/QMetaEnum>
|
|
|
|
#include <KDE/KLocale>
|
|
#include <KDE/KDebug>
|
|
#include <KDE/KActionCollection>
|
|
#include <KDE/KAction>
|
|
#include <KDE/KParts/Part>
|
|
#include <KDE/KParts/PartManager>
|
|
#include <KDE/KTextEditor/Document>
|
|
#include <KDE/KTextEditor/MarkInterface>
|
|
|
|
#include "../interfaces/idocument.h"
|
|
#include "../interfaces/icore.h"
|
|
#include "../interfaces/idocumentcontroller.h"
|
|
#include "../interfaces/ipartcontroller.h"
|
|
#include "../interfaces/contextmenuextension.h"
|
|
#include "../interfaces/context.h"
|
|
#include "../language/interfaces/editorcontext.h"
|
|
#include "../sublime/view.h"
|
|
#include "../sublime/mainwindow.h"
|
|
#include "../sublime/area.h"
|
|
#include "core.h"
|
|
#include "uicontroller.h"
|
|
#include "../debugger/breakpoint/breakpointmodel.h"
|
|
#include "../debugger/breakpoint/breakpointwidget.h"
|
|
#include "../debugger/variable/variablewidget.h"
|
|
#include "../debugger/framestack/framestackmodel.h"
|
|
#include "../debugger/framestack/framestackwidget.h"
|
|
#include <KXMLGUIFactory>
|
|
|
|
|
|
namespace KDevelop {
|
|
|
|
template<class T>
|
|
class DebuggerToolFactory : public KDevelop::IToolViewFactory
|
|
{
|
|
public:
|
|
DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea)
|
|
: m_controller(controller), m_id(id), m_defaultArea(defaultArea) {}
|
|
|
|
virtual QWidget* create(QWidget *parent = 0)
|
|
{
|
|
return new T(m_controller, parent);
|
|
}
|
|
|
|
virtual QString id() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
virtual Qt::DockWidgetArea defaultPosition()
|
|
{
|
|
return m_defaultArea;
|
|
}
|
|
|
|
virtual void viewCreated(Sublime::View* view)
|
|
{
|
|
if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1)
|
|
QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise()));
|
|
}
|
|
|
|
/* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts
|
|
work, but they don't need any toolbar. So, suppress toolbar action. */
|
|
virtual QList<QAction*> toolBarActions( QWidget* viewWidget ) const
|
|
{
|
|
Q_UNUSED(viewWidget);
|
|
return QList<QAction*>();
|
|
}
|
|
|
|
private:
|
|
DebugController* m_controller;
|
|
QString m_id;
|
|
Qt::DockWidgetArea m_defaultArea;
|
|
};
|
|
|
|
DebugController::DebugController(QObject *parent)
|
|
: IDebugController(parent), KXMLGUIClient(),
|
|
m_continueDebugger(0), m_stopDebugger(0),
|
|
m_interruptDebugger(0), m_runToCursor(0),
|
|
m_jumpToCursor(0), m_stepOver(0),
|
|
m_stepIntoInstruction(0), m_stepInto(0),
|
|
m_stepOverInstruction(0), m_stepOut(0),
|
|
m_toggleBreakpoint(0),
|
|
m_breakpointModel(new BreakpointModel(this)),
|
|
m_variableCollection(new VariableCollection(this)),
|
|
m_uiInitialized(false)
|
|
{
|
|
setComponentData(KComponentData("kdevdebugger"));
|
|
setXMLFile("kdevdebuggershellui.rc");
|
|
}
|
|
|
|
void DebugController::initialize()
|
|
{
|
|
}
|
|
|
|
void DebugController::initializeUi()
|
|
{
|
|
if (m_uiInitialized) return;
|
|
m_uiInitialized = true;
|
|
|
|
if((Core::self()->setupFlags() & Core::NoUi)) return;
|
|
setupActions();
|
|
|
|
ICore::self()->uiController()->addToolView(
|
|
i18n("Frame Stack"),
|
|
new DebuggerToolFactory<FramestackWidget>(
|
|
this, "org.kdevelop.debugger.StackView",
|
|
Qt::BottomDockWidgetArea));
|
|
|
|
ICore::self()->uiController()->addToolView(
|
|
i18n("Breakpoints"),
|
|
new DebuggerToolFactory<BreakpointWidget>(
|
|
this, "org.kdevelop.debugger.BreakpointsView",
|
|
Qt::BottomDockWidgetArea));
|
|
|
|
ICore::self()->uiController()->addToolView(
|
|
i18n("Variables"),
|
|
new DebuggerToolFactory<VariableWidget>(
|
|
this, "org.kdevelop.debugger.VariablesView",
|
|
Qt::LeftDockWidgetArea));
|
|
|
|
foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts())
|
|
partAdded(p);
|
|
connect(KDevelop::ICore::self()->partController(),
|
|
SIGNAL(partAdded(KParts::Part*)),
|
|
this,
|
|
SLOT(partAdded(KParts::Part*)));
|
|
|
|
|
|
ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this);
|
|
|
|
stateChanged("ended");
|
|
}
|
|
|
|
|
|
void DebugController::cleanup()
|
|
{
|
|
if (m_currentSession) m_currentSession.data()->stopDebugger();
|
|
}
|
|
|
|
DebugController::~DebugController()
|
|
{
|
|
}
|
|
|
|
BreakpointModel* DebugController::breakpointModel()
|
|
{
|
|
return m_breakpointModel;
|
|
}
|
|
|
|
VariableCollection* DebugController::variableCollection()
|
|
{
|
|
return m_variableCollection;
|
|
}
|
|
|
|
void DebugController::partAdded(KParts::Part* part)
|
|
{
|
|
if (KTextEditor::Document* doc = dynamic_cast<KTextEditor::Document*>(part)) {
|
|
KTextEditor::MarkInterface *iface = dynamic_cast<KTextEditor::MarkInterface*>(doc);
|
|
if( !iface )
|
|
return;
|
|
|
|
iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap());
|
|
}
|
|
}
|
|
|
|
IDebugSession* DebugController::currentSession()
|
|
{
|
|
return m_currentSession.data();
|
|
}
|
|
|
|
void DebugController::setupActions()
|
|
{
|
|
KActionCollection* ac = actionCollection();
|
|
|
|
KAction* action = m_continueDebugger = new KAction(KIcon("media-playback-start"), i18n("&Continue"), this);
|
|
action->setToolTip( i18n("Continue application execution") );
|
|
action->setWhatsThis( i18n("Continues the execution of your application in the "
|
|
"debugger. This only takes effect when the application "
|
|
"has been halted by the debugger (i.e. a breakpoint has "
|
|
"been activated or the interrupt was pressed).") );
|
|
ac->addAction("debug_continue", action);
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(run()));
|
|
|
|
#if 0
|
|
m_restartDebugger = action = new KAction(KIcon("media-seek-backward"), i18n("&Restart"), this);
|
|
action->setToolTip( i18n("Restart program") );
|
|
action->setWhatsThis( i18n("Restarts applications from the beginning.") );
|
|
action->setEnabled(false);
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger()));
|
|
ac->addAction("debug_restart", action);
|
|
#endif
|
|
|
|
m_interruptDebugger = action = new KAction(KIcon("media-playback-pause"), i18n("Interrupt"), this);
|
|
action->setToolTip( i18n("Interrupt application") );
|
|
action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command."));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(interruptDebugger()));
|
|
ac->addAction("debug_pause", action);
|
|
|
|
m_runToCursor = action = new KAction(KIcon("debug-run-cursor"), i18n("Run to &Cursor"), this);
|
|
action->setToolTip( i18n("Run to cursor") );
|
|
action->setWhatsThis(i18n("Continues execution until the cursor position is reached."));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(runToCursor()));
|
|
ac->addAction("debug_runtocursor", action);
|
|
|
|
|
|
m_jumpToCursor = action = new KAction(KIcon("debug-execute-to-cursor"), i18n("Set E&xecution Position to Cursor"), this);
|
|
action->setToolTip( i18n("Jump to cursor") );
|
|
action->setWhatsThis(i18n("Continue execution from the current cursor position."));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(jumpToCursor()));
|
|
ac->addAction("debug_jumptocursor", action);
|
|
|
|
m_stepOver = action = new KAction(KIcon("debug-step-over"), i18n("Step &Over"), this);
|
|
action->setShortcut(Qt::Key_F10);
|
|
action->setToolTip( i18n("Step over the next line") );
|
|
action->setWhatsThis( i18n("Executes one line of source in the current source file. "
|
|
"If the source line is a call to a function the whole "
|
|
"function is executed and the app will stop at the line "
|
|
"following the function call.") );
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOver()));
|
|
ac->addAction("debug_stepover", action);
|
|
|
|
|
|
m_stepOverInstruction = action = new KAction(KIcon("debug-step-instruction"), i18n("Step over Ins&truction"), this);
|
|
action->setToolTip( i18n("Step over instruction") );
|
|
action->setWhatsThis(i18n("Steps over the next assembly instruction."));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOverInstruction()));
|
|
ac->addAction("debug_stepoverinst", action);
|
|
|
|
|
|
m_stepInto = action = new KAction(KIcon("debug-step-into"), i18n("Step &Into"), this);
|
|
action->setShortcut(Qt::Key_F11);
|
|
action->setToolTip( i18n("Step into the next statement") );
|
|
action->setWhatsThis( i18n("Executes exactly one line of source. If the source line "
|
|
"is a call to a function then execution will stop after "
|
|
"the function has been entered.") );
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepInto()));
|
|
ac->addAction("debug_stepinto", action);
|
|
|
|
|
|
m_stepIntoInstruction = action = new KAction(KIcon("debug-step-into-instruction"), i18n("Step into I&nstruction"), this);
|
|
action->setToolTip( i18n("Step into instruction") );
|
|
action->setWhatsThis(i18n("Steps into the next assembly instruction."));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepIntoInstruction()));
|
|
ac->addAction("debug_stepintoinst", action);
|
|
|
|
m_stepOut = action = new KAction(KIcon("debug-step-out"), i18n("Step O&ut"), this);
|
|
action->setShortcut(Qt::Key_F12);
|
|
action->setToolTip( i18n("Step out of the current function") );
|
|
action->setWhatsThis( i18n("Executes the application until the currently executing "
|
|
"function is completed. The debugger will then display "
|
|
"the line after the original call to that function. If "
|
|
"program execution is in the outermost frame (i.e. in "
|
|
"main()) then this operation has no effect.") );
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOut()));
|
|
ac->addAction("debug_stepout", action);
|
|
|
|
m_toggleBreakpoint = action = new KAction(KIcon("script-error"), i18n("Toggle Breakpoint"), this);
|
|
action->setShortcut( i18n("Ctrl+Alt+B") );
|
|
action->setToolTip(i18n("Toggle breakpoint"));
|
|
action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor."));
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleBreakpoint()));
|
|
ac->addAction("debug_toggle_breakpoint", action);
|
|
}
|
|
|
|
void DebugController::addSession(IDebugSession* session)
|
|
{
|
|
kDebug() << session;
|
|
Q_ASSERT(session->variableController());
|
|
Q_ASSERT(session->breakpointController());
|
|
Q_ASSERT(session->frameStackModel());
|
|
|
|
//TODO support multiple sessions
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stopDebugger();
|
|
}
|
|
m_currentSession = session;
|
|
|
|
connect(session, SIGNAL(stateChanged(KDevelop::IDebugSession::DebuggerState)), SLOT(debuggerStateChanged(KDevelop::IDebugSession::DebuggerState)));
|
|
connect(session, SIGNAL(showStepInSource(KUrl,int,QString)), SLOT(showStepInSource(KUrl,int)));
|
|
connect(session, SIGNAL(clearExecutionPoint()), SLOT(clearExecutionPoint()));
|
|
connect(session, SIGNAL(raiseFramestackViews()), SIGNAL(raiseFramestackViews()));
|
|
|
|
updateDebuggerState(session->state(), session);
|
|
|
|
emit currentSessionChanged(session);
|
|
|
|
if((Core::self()->setupFlags() & Core::NoUi)) return;
|
|
|
|
|
|
Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow();
|
|
if (mainWindow->area()->objectName() != "debug") {
|
|
QString workingSet = mainWindow->area()->workingSet();
|
|
ICore::self()->uiController()->switchToArea("debug", IUiController::ThisWindow);
|
|
mainWindow->area()->setWorkingSet(workingSet);
|
|
connect(mainWindow, SIGNAL(areaChanged(Sublime::Area*)), SLOT(areaChanged(Sublime::Area*)));
|
|
}
|
|
}
|
|
|
|
void DebugController::clearExecutionPoint()
|
|
{
|
|
kDebug();
|
|
foreach (KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments()) {
|
|
KTextEditor::MarkInterface *iface = dynamic_cast<KTextEditor::MarkInterface*>(document->textDocument());
|
|
if (!iface)
|
|
continue;
|
|
|
|
QHashIterator<int, KTextEditor::Mark*> it = iface->marks();
|
|
while (it.hasNext())
|
|
{
|
|
KTextEditor::Mark* mark = it.next().value();
|
|
if( mark->type & KTextEditor::MarkInterface::Execution )
|
|
iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebugController::showStepInSource(const KUrl &url, int lineNum)
|
|
{
|
|
if((Core::self()->setupFlags() & Core::NoUi)) return;
|
|
|
|
clearExecutionPoint();
|
|
kDebug() << url << lineNum;
|
|
|
|
Q_ASSERT(dynamic_cast<IDebugSession*>(sender()));
|
|
QPair<KUrl,int> openUrl = static_cast<IDebugSession*>(sender())->convertToLocalUrl(qMakePair<KUrl,int>( url, lineNum ));
|
|
KDevelop::IDocument* document = KDevelop::ICore::self()
|
|
->documentController()
|
|
->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus);
|
|
|
|
if( !document )
|
|
return;
|
|
|
|
KTextEditor::MarkInterface *iface = dynamic_cast<KTextEditor::MarkInterface*>(document->textDocument());
|
|
if( !iface )
|
|
return;
|
|
|
|
document->textDocument()->blockSignals(true);
|
|
iface->addMark( lineNum, KTextEditor::MarkInterface::Execution );
|
|
document->textDocument()->blockSignals(false);
|
|
}
|
|
|
|
|
|
void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state)
|
|
{
|
|
Q_ASSERT(dynamic_cast<IDebugSession*>(sender()));
|
|
IDebugSession* session = static_cast<IDebugSession*>(sender());
|
|
kDebug() << session << state << "current" << m_currentSession.data();
|
|
if (session == m_currentSession.data()) {
|
|
updateDebuggerState(state, session);
|
|
}
|
|
|
|
if (state == IDebugSession::EndedState) {
|
|
if (session == m_currentSession.data()) {
|
|
m_currentSession.clear();
|
|
emit currentSessionChanged(0);
|
|
if (!Core::self()->shuttingDown()) {
|
|
Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow();
|
|
if (mainWindow && mainWindow->area()->objectName() != "code") {
|
|
QString workingSet = mainWindow->area()->workingSet();
|
|
ICore::self()->uiController()->switchToArea("code", IUiController::ThisWindow);
|
|
mainWindow->area()->setWorkingSet(workingSet);
|
|
}
|
|
ICore::self()->uiController()->findToolView(i18n("Debug"), 0, IUiController::Raise);
|
|
}
|
|
}
|
|
session->deleteLater();
|
|
}
|
|
}
|
|
|
|
void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session)
|
|
{
|
|
Q_UNUSED(session);
|
|
if((Core::self()->setupFlags() & Core::NoUi)) return;
|
|
|
|
kDebug() << state;
|
|
switch (state) {
|
|
case IDebugSession::StoppedState:
|
|
case IDebugSession::NotStartedState:
|
|
case IDebugSession::StoppingState:
|
|
kDebug() << "new state: stopped";
|
|
stateChanged("stopped");
|
|
//m_restartDebugger->setEnabled(session->restartAvailable());
|
|
break;
|
|
case IDebugSession::StartingState:
|
|
case IDebugSession::PausedState:
|
|
kDebug() << "new state: paused";
|
|
stateChanged("paused");
|
|
//m_restartDebugger->setEnabled(session->restartAvailable());
|
|
break;
|
|
case IDebugSession::ActiveState:
|
|
kDebug() << "new state: active";
|
|
stateChanged("active");
|
|
//m_restartDebugger->setEnabled(false);
|
|
break;
|
|
case IDebugSession::EndedState:
|
|
kDebug() << "new state: ended";
|
|
stateChanged("ended");
|
|
//m_restartDebugger->setEnabled(false);
|
|
break;
|
|
}
|
|
if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) {
|
|
ICore::self()->uiController()->activeMainWindow()->activateWindow();
|
|
}
|
|
}
|
|
|
|
ContextMenuExtension DebugController::contextMenuExtension( Context* context )
|
|
{
|
|
ContextMenuExtension menuExt;
|
|
|
|
if( context->type() != Context::EditorContext )
|
|
return menuExt;
|
|
|
|
KDevelop::EditorContext *econtext = dynamic_cast<KDevelop::EditorContext*>(context);
|
|
if (!econtext)
|
|
return menuExt;
|
|
|
|
if (m_currentSession && m_currentSession.data()->isRunning()) {
|
|
menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor);
|
|
}
|
|
|
|
if (econtext->url().isLocalFile()) {
|
|
menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint);
|
|
}
|
|
return menuExt;
|
|
}
|
|
|
|
#if 0
|
|
void DebugController::restartDebugger() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->restartDebugger();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void DebugController::stopDebugger() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stopDebugger();
|
|
}
|
|
}
|
|
void DebugController::interruptDebugger() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->interruptDebugger();
|
|
}
|
|
}
|
|
|
|
void DebugController::run() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->run();
|
|
}
|
|
}
|
|
|
|
void DebugController::runToCursor() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->runToCursor();
|
|
}
|
|
}
|
|
void DebugController::jumpToCursor() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->jumpToCursor();
|
|
}
|
|
}
|
|
void DebugController::stepOver() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stepOver();
|
|
}
|
|
}
|
|
void DebugController::stepIntoInstruction() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stepIntoInstruction();
|
|
}
|
|
}
|
|
void DebugController::stepInto() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stepInto();
|
|
}
|
|
}
|
|
void DebugController::stepOverInstruction() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stepOverInstruction();
|
|
}
|
|
}
|
|
void DebugController::stepOut() {
|
|
if (m_currentSession) {
|
|
m_currentSession.data()->stepOut();
|
|
}
|
|
}
|
|
|
|
void DebugController::areaChanged(Sublime::Area* newArea)
|
|
{
|
|
if (newArea->objectName()!="debug") {
|
|
stopDebugger();
|
|
}
|
|
}
|
|
|
|
void DebugController::toggleBreakpoint()
|
|
{
|
|
if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) {
|
|
KTextEditor::Cursor cursor = document->cursorPosition();
|
|
if (!cursor.isValid()) return;
|
|
breakpointModel()->toggleBreakpoint(document->url(), cursor);
|
|
}
|
|
}
|
|
|
|
const QPixmap* DebugController::executionPointPixmap()
|
|
{
|
|
static QPixmap pixmap=KIcon("go-next").pixmap(QSize(22,22), QIcon::Normal, QIcon::Off);
|
|
return &pixmap;
|
|
}
|
|
|
|
}
|