diff --git a/debugger/framestack/framestackmodel.cpp b/debugger/framestack/framestackmodel.cpp index 60ed3b8b76..f67ee47b85 100644 --- a/debugger/framestack/framestackmodel.cpp +++ b/debugger/framestack/framestackmodel.cpp @@ -1,329 +1,330 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library 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 "framestackmodel.h" #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../../interfaces/iprojectcontroller.h" #include "../interfaces/isession.h" namespace KDevelop { FrameStackModel::FrameStackModel(IDebugSession *session) : IFrameStackModel(session), m_currentThread(-1), m_currentFrame(-1) { connect(session, SIGNAL(stateChanged(KDevelop::IDebugSession::DebuggerState)), SLOT(stateChanged(KDevelop::IDebugSession::DebuggerState))); } FrameStackModel::~FrameStackModel() { } void FrameStackModel::setThreads(const QList &threads) { kDebug() << threads.count(); if (!m_threads.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_threads.count()-1); m_threads.clear(); endRemoveRows(); } if (!threads.isEmpty()) { beginInsertRows(QModelIndex(), 0, threads.count()-1); m_threads = threads; endInsertRows(); } } QModelIndex FrameStackModel::indexForThreadNumber(int threadNumber) { int i=0; foreach (const ThreadItem &t, m_threads) { if (t.nr == threadNumber) { return index(i, 0); } i++; } return QModelIndex(); } void FrameStackModel::setFrames(int threadNumber, QList frames) { QModelIndex threadIndex = indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); if (!m_frames[threadNumber].isEmpty()) { beginRemoveRows(threadIndex, 0, m_frames[threadNumber].count()-1); m_frames[threadNumber].clear(); endRemoveRows(); } if (!frames.isEmpty()) { beginInsertRows(threadIndex, 0, frames.count()-1); m_frames[threadNumber] = frames; endInsertRows(); } // FIXME: Ugly hack. Apparently, when rows are added, the selection // in the view is cleared. Emit this so that some frame is still // selected. emit currentFrameChanged(m_currentFrame); } void FrameStackModel::insertFrames(int threadNumber, const QList &frames) { QModelIndex threadIndex = indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); beginInsertRows(threadIndex, m_frames[threadNumber].count()-1, - m_frames[threadNumber].count()+frames.count()-2); + m_frames[threadNumber].count()+frames.count()-1); m_frames[threadNumber] << frames; endInsertRows(); } void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames) { m_hasMoreFrames[threadNumber] = hasMoreFrames; } FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index) { Q_ASSERT(index.internalId()); Q_ASSERT(m_threads.count() >= index.internalId()); const ThreadItem &thread = m_threads.at(index.internalId()-1); return m_frames[thread.nr].at(index.row()); } QVariant FrameStackModel::data(const QModelIndex& index, int role) const { if (!index.internalId()) { //thread if (m_threads.count() <= index.row()) return QVariant(); const ThreadItem &thread = m_threads.at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name); } } } else { //frame if (m_threads.count() < index.internalId()) return QVariant(); const ThreadItem &thread = m_threads.at(index.internalId()-1); if (m_frames[thread.nr].count() <= index.row()) return QVariant(); const FrameItem &frame = m_frames[thread.nr].at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return QVariant(QString::number(frame.nr)); } } else if (index.column() == 1) { if (role == Qt::DisplayRole) { return QVariant(frame.name); } } else if (index.column() == 2) { if (role == Qt::DisplayRole) { QString ret = ICore::self()->projectController() ->prettyFileName(frame.file, IProjectController::FormatPlain); if (frame.line != -1) { ret += ":" + QString::number(frame.line + 1); } return ret; } else if (role == Qt::DecorationRole) { KMimeType::Ptr p = KMimeType::findByUrl(frame.file); return KIcon(p->iconName()); } } } return QVariant(); } int FrameStackModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 3; } int FrameStackModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return m_threads.count(); } else if (!parent.internalId() && parent.column() == 0) { if (parent.row() < m_threads.count()) { return m_frames[m_threads.at(parent.row()).nr].count(); } } return 0; } QModelIndex FrameStackModel::parent(const QModelIndex& child) const { if (!child.internalId()) { return QModelIndex(); } else { return index(child.internalId()-1, 0); } } QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) { Q_ASSERT(!parent.internalId()); Q_ASSERT(parent.column() == 0); Q_ASSERT(parent.row() < m_threads.count()); return createIndex(row, column, parent.row()+1); } else { return createIndex(row, column, 0); } } QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Depth"); } else if (section == 1) { return i18n("Function"); } else if (section == 2) { return i18n("Source"); } } return QAbstractItemModel::headerData(section, orientation, role); } void FrameStackModel::setCurrentThread(int threadNumber) { kDebug() << threadNumber; if (m_currentThread != threadNumber && threadNumber != -1) { // FIXME: this logic means that if we switch to thread 3 and // then to thread 2 and then to thread 3, we'll request frames // for thread 3 again, even if the program was not run in between // and therefore frames could not have changed. fetchFrames(threadNumber, 0, 20); } if (threadNumber != m_currentThread) { m_currentFrame = 0; kDebug() << "currentFrame" << m_currentFrame; m_currentThread = threadNumber; emit currentFrameChanged(m_currentFrame); } kDebug() << "currentThread: " << m_currentThread << "currentFrame: " << m_currentFrame; emit currentThreadChanged(threadNumber); session()->raiseEvent(IDebugSession::thread_or_frame_changed); } void FrameStackModel::setCurrentThread(const QModelIndex& index) { Q_ASSERT(index.isValid()); Q_ASSERT(!index.internalId()); Q_ASSERT(index.column() == 0); setCurrentThread(m_threads[index.row()].nr); } int FrameStackModel::currentThread() const { return m_currentThread; } QModelIndex FrameStackModel::currentThreadIndex() const { int i = 0; foreach (const ThreadItem &t, m_threads) { if (t.nr == currentThread()) { return index(i, 0); } ++i; } return QModelIndex(); } int FrameStackModel::currentFrame() const { return m_currentFrame; } QModelIndex FrameStackModel::currentFrameIndex() const { QModelIndex idx = currentThreadIndex(); return idx.child(m_currentFrame, 0); } void FrameStackModel::setCurrentFrame(int frame) { kDebug() << frame; if (frame != m_currentFrame) { m_currentFrame = frame; session()->raiseEvent(IDebugSession::thread_or_frame_changed); emit currentFrameChanged(frame); } } void FrameStackModel::update() { fetchThreads(); if (m_currentThread != -1) { fetchFrames(m_currentThread, 0, 20); } } void FrameStackModel::handleEvent(IDebugSession::event_t event) { switch (event) { case IDebugSession::program_state_changed: update(); break; default: break; } } void FrameStackModel::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::PausedState) { setCurrentFrame(0); } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { setThreads(QList()); } } // FIXME: it should be possible to fetch more frames for // an arbitrary thread, without making it current. void FrameStackModel::fetchMoreFrames() { if (m_currentThread != -1 && m_hasMoreFrames[m_currentThread]) { + setHasMoreFrames(m_currentThread, false); fetchFrames(m_currentThread, m_frames[m_currentThread].count(), m_frames[m_currentThread].count()-1+20); } } } #include "framestackmodel.moc" diff --git a/debugger/interfaces/ivariablecontroller.cpp b/debugger/interfaces/ivariablecontroller.cpp index 2a5802327e..c065e27f36 100644 --- a/debugger/interfaces/ivariablecontroller.cpp +++ b/debugger/interfaces/ivariablecontroller.cpp @@ -1,124 +1,125 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library 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 "ivariablecontroller.h" #include "idebugsession.h" #include "../../interfaces/icore.h" #include "../../interfaces/idebugcontroller.h" #include "../variable/variablecollection.h" #include "iframestackmodel.h" namespace KDevelop { IVariableController::IVariableController(IDebugSession* parent) : QObject(parent), m_activeThread(-1), m_activeFrame(-1) { connect(parent, SIGNAL(stateChanged(KDevelop::IDebugSession::DebuggerState)), SLOT(stateChanged(KDevelop::IDebugSession::DebuggerState))); } VariableCollection* IVariableController::variableCollection() { if (!ICore::self()) return 0; return ICore::self()->debugController()->variableCollection(); } IDebugSession* IVariableController::session() const { return static_cast(parent()); } void IVariableController::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::ActiveState) { //variables are now outdated, update them m_activeThread = -1; m_activeFrame = -1; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { // Remove all locals. foreach (Locals *l, variableCollection()->allLocals()) { l->deleteChildren(); l->setHasMore(false); } for (int i=0; i < variableCollection()->watches()->childCount(); ++i) { Variable *var = dynamic_cast(variableCollection()->watches()->child(i)); if (var) { var->setInScope(false); } } } } void IVariableController::updateIfFrameOrThreadChanged() { IFrameStackModel *sm = session()->frameStackModel(); if (sm->currentThread() != m_activeThread || sm->currentFrame() != m_activeFrame) { m_activeThread = sm->currentThread(); m_activeFrame = sm->currentFrame(); variableCollection()->root()->resetChanged(); update(); } } void IVariableController::handleEvent(IDebugSession::event_t event) { if (!variableCollection()) return; switch (event) { case IDebugSession::program_state_changed: case IDebugSession::thread_or_frame_changed: kDebug() << m_autoUpdate; if (!(m_autoUpdate & UpdateLocals)) { foreach (Locals *l, variableCollection()->allLocals()) { if (!l->isExpanded() && !l->childCount()) { l->setHasMore(true); } } } if (m_autoUpdate != UpdateNone) { updateIfFrameOrThreadChanged(); } break; default: break; } } void IVariableController::setAutoUpdate(QFlags autoUpdate) { IDebugSession::DebuggerState state = session()->state(); m_autoUpdate = autoUpdate; + kDebug() << m_autoUpdate; if (m_autoUpdate != UpdateNone && state == IDebugSession::PausedState) { - updateIfFrameOrThreadChanged(); + update(); } } QFlags IVariableController::autoUpdate() { return m_autoUpdate; } } #include "ivariablecontroller.moc" diff --git a/debugger/variable/variablecollection.cpp b/debugger/variable/variablecollection.cpp index a7d2b327b1..b49a1803c6 100644 --- a/debugger/variable/variablecollection.cpp +++ b/debugger/variable/variablecollection.cpp @@ -1,479 +1,485 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * 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 "variablecollection.h" #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent), inScope_(true), topLevel_(true), changed_(false), m_format(Natural) { expression_ = expression; // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector() << expression << QString()); else setData(QVector() << display << QString()); } QString Variable::expression() const { return expression_; } bool Variable::inScope() const { return inScope_; } void Variable::setValue(const QString& v) { itemData[1] = v; reportChange(); } QString Variable::value() const { return itemData[1].toString(); } void Variable::setTopLevel(bool v) { topLevel_ = v; } void Variable::setInScope(bool v) { inScope_ = v; for (int i=0; i < childCount(); ++i) { if (Variable *var = dynamic_cast(child(i))) { var->setInScope(v); } } reportChange(); } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { changed_=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } Variable::format_t Variable::str2format(const QString& str) { if(str=="Binary" || str=="binary") return Binary; if(str=="Octal" || str=="octal") return Octal; if(str=="Decimal" || str=="decimal") return Decimal; if(str=="Hexadecimal" || str=="hexadecimal")return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { case Natural: return "natural"; case Binary: return "binary"; case Octal: return "octal"; case Decimal: return "decimal"; case Hexadecimal: return "hexadecimal"; default: return ""; } } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } QVariant Variable::data(int column, int role) const { if (column == 1 && role == Qt::ForegroundRole) { // FIXME: returning hardcoded gray is bad, // but we don't have access to any widget, or pallette // thereof, at this point. if(!inScope_) return QColor(128, 128, 128); if(changed_) return QColor(255, 0, 0); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) : TreeItem(model, parent), finishResult_(0) { setData(QVector() << "Auto" << QString()); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return 0; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); + if (childCount() == 1 && !isExpanded()) { + setExpanded(true); + } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, "$ret"); appendChild(finishResult_); finishResult_->attachMaybe(); + if (childCount() == 1 && !isExpanded()) { + setExpanded(true); + } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = 0; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } QVariant Watches::data(int column, int role) const { #if 0 if (column == 0 && role == Qt::FontRole) { /* FIXME: is creating font again and agian efficient? */ QFont f = font(); f.setBold(true); return f; } #endif return TreeItem::data(column, role); } void Watches::reinstall() { for (int i = 0; i < childItems.size(); ++i) { Variable* v = static_cast(child(i)); v->attachMaybe(); } } Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) : TreeItem(model, parent) { setData(QVector() << name << QString()); } QList Locals::updateLocals(QStringList locals) { QSet existing, current; for (int i = 0; i < childItems.size(); i++) { Q_ASSERT(dynamic_cast(child(i))); Variable* var= static_cast(child(i)); existing << var->expression(); } foreach (const QString& var, locals) { current << var; // If we currently don't display this local var, add it. if( !existing.contains( var ) ) { // FIXME: passing variableCollection this way is awkward. // In future, variableCollection probably should get a // method to create variable. Variable* v = currentSession()->variableController()->createVariable( ICore::self()->debugController()->variableCollection(), this, var ); appendChild( v, false ); } } for (int i = 0; i < childItems.size(); ++i) { KDevelop::Variable* v = static_cast(child(i)); if (!current.contains(v->expression())) { removeChild(i); --i; // FIXME: check that -var-delete is sent. delete v; } } if (hasMore()) { setHasMore(false); } // Variables which changed just value are updated by a call to -var-update. // Variables that changed type -- likewise. QList ret; foreach (TreeItem *i, childItems) { Q_ASSERT(dynamic_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) : TreeItem(model) { watches_ = new Watches(model, this); appendChild(watches_, true); } Locals* VariablesRoot::locals(const QString& name) { if (!locals_.contains(name)) { locals_[name] = new Locals(model(), this, name); appendChild(locals_[name]); } return locals_[name]; } QHash VariablesRoot::allLocals() const { return locals_; } void VariablesRoot::resetChanged() { watches_->resetChanged(); foreach (Locals *l, locals_) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) : TreeModel(QVector() << i18n( "Name" ) << i18n( "Value" ), controller), m_widgetVisible(false) { universe_ = new VariablesRoot(this); setRootItem(universe_); //new ModelTest(this); connect (ICore::self()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(textDocumentCreated(KDevelop::IDocument*)) ); connect(controller, SIGNAL(currentSessionChanged(KDevelop::IDebugSession*)), SLOT(updateAutoUpdate(KDevelop::IDebugSession*))); connect(locals(), SIGNAL(expanded()), SLOT(updateAutoUpdate())); connect(locals(), SIGNAL(collapsed()), SLOT(updateAutoUpdate())); connect(watches(), SIGNAL(expanded()), SLOT(updateAutoUpdate())); connect(watches(), SIGNAL(collapsed()), SLOT(updateAutoUpdate())); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); kDebug() << session; if (!session) return; if (!m_widgetVisible) { session->variableController()->setAutoUpdate(IVariableController::UpdateNone); } else { QFlags t = IVariableController::UpdateNone; if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; session->variableController()->setAutoUpdate(t); } } VariableCollection::~ VariableCollection() { } void VariableCollection::textDocumentCreated(IDocument* doc) { connect( doc->textDocument(), SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), this, SLOT(viewCreated(KTextEditor::Document*,KTextEditor::View*)) ); foreach( KTextEditor::View* view, doc->textDocument()->views() ) viewCreated( doc->textDocument(), view ); } void VariableCollection::viewCreated(KTextEditor::Document* doc, KTextEditor::View* view) { Q_UNUSED(doc); using namespace KTextEditor; TextHintInterface *iface = dynamic_cast(view); if( !iface ) return; iface->enableTextHints(500); connect(view, SIGNAL(needTextHint(KTextEditor::Cursor,QString&)), this, SLOT(textHintRequested(KTextEditor::Cursor,QString&))); } void VariableCollection:: textHintRequested(const KTextEditor::Cursor& cursor, QString&) { // Don't do anything if there's already an open tooltip. if (activeTooltip_) return; if (!hasStartedSession()) return; if (ICore::self()->uiController()->activeArea()->objectName() != "debug") return; // Figure what is the parent widget and what is the text to show KTextEditor::View* view = dynamic_cast(sender()); if (!view) return; KTextEditor::Document* doc = view->document(); QString expression = currentSession()->variableController()->expressionUnderCursor(doc, cursor); if (expression.isEmpty()) return; QPoint local = view->cursorToCoordinate(cursor); QPoint global = view->mapToGlobal(local); QWidget* w = view->childAt(local); if (!w) w = view; activeTooltip_ = new VariableToolTip(w, global+QPoint(30,30), expression); } } #include "variablecollection.moc" diff --git a/debugger/variable/variablewidget.cpp b/debugger/variable/variablewidget.cpp index 6e9d8fe94c..34c9a9759f 100644 --- a/debugger/variable/variablewidget.cpp +++ b/debugger/variable/variablewidget.cpp @@ -1,500 +1,500 @@ // ************************************************************************** // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@kdevelop.org // ************************************************************************** // * Copyright 2006 Vladimir Prus // ************************************************************************** // * * // * 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. * // * * // ************************************************************************** #include "variablewidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include #include "../interfaces/ivariablecontroller.h" #include "variablecollection.h" /** The variables widget is passive, and is invoked by the rest of the code via two main Q_SLOTS: - slotDbgStatus - slotCurrentFrame The first is received the program status changes and the second is received after current frame in the debugger can possibly changes. The widget has a list item for each frame/thread combination, with variables as children. However, at each moment only one item is shown. When handling the slotCurrentFrame, we check if variables for the current frame are available. If yes, we simply show the corresponding item. Otherwise, we fetch the new data from debugger. Fetching the data is done by emitting the produceVariablesInfo signal. In response, we get slotParametersReady and slotLocalsReady signal, in that order. The data is parsed and changed variables are highlighted. After that, we 'trim' variable items that were not reported by gdb -- that is, gone out of scope. */ // ************************************************************************** // ************************************************************************** // ************************************************************************** namespace KDevelop { VariableCollection *variableCollection() { return ICore::self()->debugController()->variableCollection(); } VariableWidget::VariableWidget(IDebugController* controller, QWidget *parent) : QWidget(parent), variablesRoot_(controller->variableCollection()->root()) { //setWindowIcon(KIcon("math_brace")); setWindowIcon(KIcon("debugger")); setWindowTitle(i18n("Debugger Variables")); varTree_ = new VariableTree(controller, this); setFocusProxy(varTree_); watchVarEditor_ = new KHistoryComboBox( this ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(varTree_, 10); topLayout->addWidget(watchVarEditor_); topLayout->setMargin(0); connect(watchVarEditor_, SIGNAL(returnPressed(QString)), this, SLOT(slotAddWatch(QString))); //TODO //connect(plugin, SIGNAL(raiseVariableViews()), this, SIGNAL(requestRaise())); // Setup help items. setWhatsThis( i18n( "Variable tree

" "The variable tree allows you to see the values of local " "variables and arbitrary expressions.

" "

Local variables are displayed automatically and are updated " "as you step through your program. " "For each expression you enter, you can either evaluate it once, " "or \"watch\" it (make it auto-updated). Expressions that are not " "auto-updated can be updated manually from the context menu. " "Expressions can be renamed to more descriptive names by clicking " "on the name column.

" "

To change the value of a variable or an expression, " "click on the value.

")); watchVarEditor_->setWhatsThis( i18n("Expression entry" "

Type in expression to watch.

")); } void VariableWidget::slotAddWatch(const QString &expression) { if (!expression.isEmpty()) { watchVarEditor_->addToHistory(expression); kDebug(9012) << "Trying to add watch\n"; Variable* v = variablesRoot_->watches()->add(expression); if (v) { QModelIndex index = variableCollection()->indexForItem(v, 0); /* For watches on structures, we really do want them to be shown expanded. Except maybe for structure with custom pretty printing, but will handle that later. FIXME: it does not actually works now. */ //varTree_->setExpanded(index, true); } watchVarEditor_->clearEditText(); } } void VariableWidget::hideEvent(QHideEvent* e) { QWidget::hideEvent(e); variableCollection()->variableWidgetHidden(); } void VariableWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); variableCollection()->variableWidgetShown(); } // ************************************************************************** // ************************************************************************** // ************************************************************************** VariableTree::VariableTree(IDebugController *controller, VariableWidget *parent) : AsyncTreeView(controller->variableCollection(), parent) #if 0 , activePopup_(0), toggleWatch_(0) #endif { setRootIsDecorated(true); setAllColumnsShowFocus(true); QModelIndex index = controller->variableCollection()->indexForItem( controller->variableCollection()->watches(), 0); setExpanded(index, true); m_signalMapper = new QSignalMapper(this); setupActions(); } VariableCollection* VariableTree::collection() const { Q_ASSERT(qobject_cast(model())); return static_cast(model()); } VariableTree::~VariableTree() { } void VariableTree::setupActions() { // TODO decorate this properly to make nice menu title m_contextMenuTitle = new QAction(this); m_contextMenuTitle->setEnabled(false); // make Format menu action group m_formatMenu = new QMenu(i18n("&Format"), this); QActionGroup *ag= new QActionGroup(m_formatMenu); QAction* act; act = new QAction(i18n("&Natural"), ag); act->setData(Variable::Natural); act->setShortcut(Qt::Key_N); m_formatMenu->addAction(act); act = new QAction(i18n("&Binary"), ag); act->setData(Variable::Binary); act->setShortcut(Qt::Key_B); m_formatMenu->addAction(act); act = new QAction(i18n("&Octal"), ag); act->setData(Variable::Octal); act->setShortcut(Qt::Key_O); m_formatMenu->addAction(act); act = new QAction(i18n("&Decimal"), ag); act->setData(Variable::Decimal); act->setShortcut(Qt::Key_D); m_formatMenu->addAction(act); act = new QAction(i18n("&Hexadecimal"), ag); act->setData(Variable::Hexadecimal); act->setShortcut(Qt::Key_H); m_formatMenu->addAction(act); foreach(QAction* act, m_formatMenu->actions()) { act->setCheckable(true); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_signalMapper->setMapping(act, act->data().toInt()); connect(act, SIGNAL(triggered()), m_signalMapper, SLOT(map())); addAction(act); } connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(changeVariableFormat(int))); m_watchDelete = new QAction( KIcon("edit-delete"), i18n( "Remove Watch Variable" ), this); m_watchDelete->setShortcut(Qt::Key_Delete); m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(m_watchDelete); connect(m_watchDelete, SIGNAL(triggered(bool)), SLOT(watchDelete())); m_copyVariableValue = new QAction(i18n("&Copy Value"), this); m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_copyVariableValue->setShortcut(QKeySequence::Copy); connect(m_copyVariableValue, SIGNAL(triggered(bool)), SLOT(copyVariableValue())); } Variable* VariableTree::selectedVariable() const { if (selectionModel()->selectedRows().isEmpty()) return 0; TreeItem* item = collection()->itemForIndex(selectionModel()->selectedRows().first()); if (!item) return 0; return dynamic_cast(item); } void VariableTree::contextMenuEvent(QContextMenuEvent* event) { if (!selectedVariable()) return; // set up menu QMenu contextMenu(this->parentWidget()); m_contextMenuTitle->setText(selectedVariable()->expression()); contextMenu.addAction(m_contextMenuTitle); if(selectedVariable()->canSetFormat()) contextMenu.addMenu(m_formatMenu); foreach(QAction* act, m_formatMenu->actions()) { if(act->data().toInt()==selectedVariable()->format()) act->setChecked(true); } if (dynamic_cast(selectedVariable()->parent())) { contextMenu.addAction(m_watchDelete); } contextMenu.addSeparator(); contextMenu.addAction(m_copyVariableValue); contextMenu.exec(event->globalPos()); } void VariableTree::changeVariableFormat(int format) { if (!selectedVariable()) return; selectedVariable()->setFormat(static_cast(format)); } void VariableTree::watchDelete() { if (!selectedVariable()) return; - Q_ASSERT(dynamic_cast(selectedVariable()->parent())); + if (!dynamic_cast(selectedVariable()->parent())) return; selectedVariable()->die(); } void VariableTree::copyVariableValue() { if (!selectedVariable()) return; QApplication::clipboard()->setText(selectedVariable()->value()); } #if 0 void VariableTree::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) return; AbstractVariableItem* item = collection()->itemForIndex(index); if (RecentItem* recent = qobject_cast(item)) { KMenu popup(this); popup.addTitle(i18n("Recent Expressions")); QAction* remove = popup.addAction(KIcon("editdelete"), i18n("Remove All")); QAction* reevaluate = popup.addAction(KIcon("reload"), i18n("Re-evaluate All")); if (controller()->stateIsOn(s_dbgNotStarted)) reevaluate->setEnabled(false); QAction* res = popup.exec(QCursor::pos()); if (res == remove) { collection()->deleteItem(recent); } else if (res == reevaluate) { foreach (AbstractVariableItem* item, recent->children()) { if (VariableItem* variable = qobject_cast(item)) variable->updateValue(); } } } else { activePopup_ = new KMenu(this); KMenu format(this); QAction* remember = 0; QAction* remove = 0; QAction* reevaluate = 0; QAction* watch = 0; QAction* natural = 0; QAction* hex = 0; QAction* decimal = 0; QAction* character = 0; QAction* binary = 0; #define MAYBE_DISABLE(action) if (!var->isAlive()) action->setEnabled(false) VariableItem* var = qobject_cast(item); AbstractVariableItem* root = item->abstractRoot(); RecentItem* recentRoot = qobject_cast(root); if (!recentRoot) { remember = activePopup_->addAction(KIcon("draw-freehand"), i18n("Remember Value")); MAYBE_DISABLE(remember); } if (!recentRoot) { watch = activePopup_->addAction(i18n("Watch Variable")); MAYBE_DISABLE(watch); } if (recentRoot) { reevaluate = activePopup_->addAction(KIcon("reload"), i18n("Reevaluate Expression")); MAYBE_DISABLE(reevaluate); remove = activePopup_->addAction(KIcon("editdelete"), i18n("Remove Expression")); remove->setShortcut(Qt::Key_Delete); } if (var) { toggleWatch_ = activePopup_->addAction( i18n("Data write breakpoint") ); toggleWatch_->setCheckable(true); toggleWatch_->setEnabled(false); } /* This code can be executed when debugger is stopped, and we invoke popup menu on a var under "recent expressions" just to delete it. */ if (var && var->isAlive() && !controller()->stateIsOn(s_dbgNotStarted)) { GDBCommand* cmd = new GDBCommand(DataEvaluateExpression, QString("&%1") .arg(var->gdbExpression())); cmd->setHandler(this, &VariableTree::handleAddressComputed, true /*handles error*/); cmd->setThread(var->thread()); cmd->setFrame(var->frame()); controller_->addCommand(cmd); } QAction* res = activePopup_->exec(event->globalPos()); delete activePopup_; activePopup_ = 0; if (res == remember) { if (var) { ((VariableWidget*)parent())-> slotEvaluateExpression(var->gdbExpression()); } } else if (res == watch) { if (var) { ((VariableWidget*)parent())-> slotAddWatchVariable(var->gdbExpression()); } } else if (res == remove) { delete item; } else if (res == toggleWatch_) { if (var) emit toggleWatchpoint(var->gdbExpression()); } else if (res == reevaluate) { if (var) { var->updateValue(); } } event->accept(); } } void VariableTree::updateCurrentFrame() { } // ************************************************************************** void VariableTree::handleAddressComputed(const GDBMI::ResultRecord& r) { if (r.reason == "error") { // Not lvalue, leave item disabled. return; } if (activePopup_) { toggleWatch_->setEnabled(true); //quint64 address = r["value"].literal().toULongLong(0, 16); /*if (breakpointWidget_->hasWatchpointForAddress(address)) { toggleWatch_->setChecked(true); }*/ } } VariableCollection * VariableTree::collection() const { return controller_->variables(); } GDBController * VariableTree::controller() const { return controller_; } void VariableTree::showEvent(QShowEvent * event) { Q_UNUSED(event) for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } #endif // ************************************************************************** // ************************************************************************** // ************************************************************************** } #include "variablewidget.moc"