Files
qt-creator/src/plugins/debugger/breakhandler.cpp

685 lines
20 KiB
C++
Raw Normal View History

/**************************************************************************
2008-12-02 12:01:29 +01:00
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
2008-12-02 12:01:29 +01:00
**
** Contact: Nokia Corporation (qt-info@nokia.com)
2008-12-02 12:01:29 +01:00
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
2008-12-02 12:01:29 +01:00
**
**************************************************************************/
2008-12-02 15:08:31 +01:00
2008-12-02 12:01:29 +01:00
#include "breakhandler.h"
#include "imports.h" // TextEditor::BaseTextMark
2008-12-09 16:18:28 +01:00
2008-12-02 12:01:29 +01:00
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
2008-12-02 12:01:29 +01:00
#include <QtCore/QFileInfo>
using namespace Debugger;
using namespace Debugger::Internal;
//////////////////////////////////////////////////////////////////
//
// BreakpointMarker
//
//////////////////////////////////////////////////////////////////
namespace Debugger {
namespace Internal {
// The red blob on the left side in the cpp editor.
class BreakpointMarker : public TextEditor::BaseTextMark
{
Q_OBJECT
public:
BreakpointMarker(BreakpointData *data, const QString &fileName, int lineNumber)
: BaseTextMark(fileName, lineNumber)
2008-12-02 12:01:29 +01:00
{
m_data = data;
m_pending = true;
m_enabled = true;
2008-12-02 12:01:29 +01:00
//qDebug() << "CREATE MARKER " << fileName << lineNumber;
}
~BreakpointMarker()
{
//qDebug() << "REMOVE MARKER ";
m_data = 0;
}
QIcon icon() const
{
return icon(m_pending, m_enabled);
}
static const QIcon &icon(bool pending, bool enabled)
2008-12-02 12:01:29 +01:00
{
static const QIcon icon(":/debugger/images/breakpoint.svg");
static const QIcon icon1(":/debugger/images/breakpoint_disabled.svg");
static const QIcon icon2(":/debugger/images/breakpoint_pending.svg");
return enabled ? (pending ? icon2 : icon) : icon1;
2008-12-02 12:01:29 +01:00
}
void setPending(bool pending, bool enabled)
2008-12-02 12:01:29 +01:00
{
if (pending == m_pending && enabled == m_enabled)
2008-12-02 12:01:29 +01:00
return;
m_pending = pending;
m_enabled = enabled;
2008-12-02 12:01:29 +01:00
updateMarker();
}
void updateBlock(const QTextBlock &)
{
//qDebug() << "BREAKPOINT MARKER UPDATE BLOCK";
}
void removedFromEditor()
{
if (!m_data)
return;
BreakHandler *handler = m_data->handler();
handler->removeBreakpoint(handler->indexOf(m_data));
handler->saveBreakpoints();
handler->updateMarkers();
}
void updateLineNumber(int lineNumber)
{
if (!m_data)
return;
//if (m_data->markerLineNumber == lineNumber)
// return;
if (m_data->markerLineNumber != lineNumber) {
m_data->markerLineNumber = lineNumber;
// FIXME: should we tell gdb about the change?
// Ignore it for now, as we would require re-compilation
// and debugger re-start anyway.
if (0 && !m_data->bpLineNumber.isEmpty()) {
if (!m_data->bpNumber.trimmed().isEmpty()) {
m_data->pending = true;
}
}
}
m_data->lineNumber = QString::number(lineNumber);
m_data->handler()->updateMarkers();
}
private:
BreakpointData *m_data;
bool m_pending;
bool m_enabled;
2008-12-02 12:01:29 +01:00
};
} // namespace Internal
} // namespace Debugger
//////////////////////////////////////////////////////////////////
//
// BreakpointData
//
//////////////////////////////////////////////////////////////////
BreakpointData::BreakpointData(BreakHandler *handler)
{
//qDebug() << "CREATE BREAKPOINTDATA" << this;
m_handler = handler;
enabled = true;
2008-12-02 12:01:29 +01:00
pending = true;
marker = 0;
markerLineNumber = 0;
bpMultiple = false;
//#if defined(Q_OS_MAC)
// // full names do not work on Mac/MI
useFullPath = false;
//#else
// //where = m_manager->shortName(data->fileName);
// useFullPath = true;
//#endif
2008-12-02 12:01:29 +01:00
}
BreakpointData::~BreakpointData()
{
removeMarker();
//qDebug() << "DESTROY BREAKPOINTDATA" << this;
}
void BreakpointData::removeMarker()
{
BreakpointMarker *m = marker;
marker = 0;
delete m;
}
void BreakpointData::updateMarker()
{
if (marker && (markerFileName != marker->fileName()
|| markerLineNumber != marker->lineNumber()))
removeMarker();
if (!marker && !markerFileName.isEmpty() && markerLineNumber > 0)
marker = new BreakpointMarker(this, markerFileName, markerLineNumber);
if (marker)
marker->setPending(pending, enabled);
2008-12-02 12:01:29 +01:00
}
QString BreakpointData::toToolTip() const
{
QString rc;
QTextStream str(&rc);
str << "<html><body><table>";
str << "<tr><td>" << BreakHandler::tr("Marker File:") << "</td><td>" << markerFileName << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Marker Line:") << "</td><td>" << markerLineNumber << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Breakpoint Number:") << "</td><td>" << bpNumber << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Breakpoint Address:") << "</td><td>" << bpAddress << "</td></tr>";
str << "</table><br><hr><table>";
str << "<tr><th>" << BreakHandler::tr("Property")
<< "</th><th>" << BreakHandler::tr("Requested")
<< "</th><th>" << BreakHandler::tr("Obtained") << "</th></tr>";
str << "<tr><td>" << BreakHandler::tr("Internal Number:") << "</td><td>&mdash;</td><td>" << bpNumber << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("File Name:") << "</td><td>" << fileName << "</td><td>" << bpFileName << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Function Name:") << "</td><td>" << funcName << "</td><td>" << bpFuncName << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Line Number:") << "</td><td>" << lineNumber << "</td><td>" << bpLineNumber << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Condition:") << "</td><td>" << condition << "</td><td>" << bpCondition << "</td></tr>";
str << "<tr><td>" << BreakHandler::tr("Ignore Count:") << "</td><td>" << ignoreCount << "</td><td>" << bpIgnoreCount << "</td></tr>";
str << "</table></body></html>";
return rc;
2008-12-02 12:01:29 +01:00
}
bool BreakpointData::isLocatedAt(const QString &fileName_, int lineNumber_) const
{
/*
if (lineNumber != QString::number(lineNumber_))
return false;
if (fileName == fileName_)
return true;
if (fileName_.endsWith(fileName))
return true;
return false;
*/
return lineNumber_ == markerLineNumber && fileName_ == markerFileName;
}
bool BreakpointData::conditionsMatch() const
{
// same versions of gdb "beautify" the passed condition
QString s1 = condition;
s1.remove(QChar(' '));
QString s2 = bpCondition;
s2.remove(QChar(' '));
return s1 == s2;
}
//////////////////////////////////////////////////////////////////
//
// BreakHandler
//
//////////////////////////////////////////////////////////////////
BreakHandler::BreakHandler(QObject *parent)
: QAbstractItemModel(parent)
{
}
BreakHandler::~BreakHandler()
{
clear();
}
2008-12-02 12:01:29 +01:00
int BreakHandler::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 6;
}
int BreakHandler::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : size();
}
bool BreakHandler::hasPendingBreakpoints() const
{
for (int i = size() - 1; i >= 0; i--)
if (at(i)->pending)
return true;
return false;
}
2008-12-02 12:01:29 +01:00
void BreakHandler::removeAt(int index)
{
BreakpointData *data = at(index);
m_bp.removeAt(index);
delete data;
}
void BreakHandler::clear()
{
for (int index = size(); --index >= 0; )
removeAt(index);
}
int BreakHandler::findBreakpoint(const BreakpointData &needle)
{
// looks for a breakpoint we might refer to
for (int index = 0; index != size(); ++index) {
const BreakpointData *data = at(index);
// clear hit.
if (data->bpNumber == needle.bpNumber)
return index;
// at least at a position we were looking for
// FIXME: breaks multiple breakpoints at the same location
if (data->fileName == needle.bpFileName
&& data->lineNumber == needle.bpLineNumber)
return index;
}
return -1;
}
int BreakHandler::findBreakpoint(const QString &fileName, int lineNumber)
{
for (int index = 0; index != size(); ++index)
if (at(index)->isLocatedAt(fileName, lineNumber))
return index;
return -1;
}
2008-12-02 12:01:29 +01:00
int BreakHandler::findBreakpoint(int bpNumber)
{
2009-06-10 10:29:31 +02:00
if (!size())
return -1;
QString numStr = QString::number(bpNumber);
2008-12-02 12:01:29 +01:00
for (int index = 0; index != size(); ++index)
2009-06-10 10:29:31 +02:00
if (at(index)->bpNumber == numStr)
2008-12-02 12:01:29 +01:00
return index;
return -1;
}
void BreakHandler::saveBreakpoints()
{
QList<QVariant> list;
for (int index = 0; index != size(); ++index) {
const BreakpointData *data = at(index);
QMap<QString, QVariant> map;
if (!data->fileName.isEmpty())
map["filename"] = data->fileName;
if (!data->lineNumber.isEmpty())
map["linenumber"] = data->lineNumber;
if (!data->funcName.isEmpty())
map["funcname"] = data->funcName;
if (!data->condition.isEmpty())
map["condition"] = data->condition;
if (!data->ignoreCount.isEmpty())
map["ignorecount"] = data->ignoreCount;
if (!data->enabled)
map["disabled"] = "1";
if (data->useFullPath)
map["usefullpath"] = "1";
2008-12-02 12:01:29 +01:00
list.append(map);
}
setSessionValueRequested("Breakpoints", list);
}
void BreakHandler::loadBreakpoints()
{
QVariant value;
sessionValueRequested("Breakpoints", &value);
QList<QVariant> list = value.toList();
clear();
foreach (const QVariant &var, list) {
const QMap<QString, QVariant> map = var.toMap();
BreakpointData *data = new BreakpointData(this);
data->fileName = map["filename"].toString();
data->lineNumber = map["linenumber"].toString();
data->condition = map["condition"].toString();
data->ignoreCount = map["ignorecount"].toString();
data->funcName = map["funcname"].toString();
data->enabled = !map["disabled"].toInt();
data->useFullPath = bool(map["usefullpath"].toInt());
2008-12-02 12:01:29 +01:00
data->markerFileName = data->fileName;
data->markerLineNumber = data->lineNumber.toInt();
append(data);
}
}
void BreakHandler::resetBreakpoints()
{
for (int index = size(); --index >= 0;) {
BreakpointData *data = at(index);
data->pending = true;
data->bpMultiple = false;
data->bpEnabled = true;
2008-12-02 12:01:29 +01:00
data->bpNumber.clear();
data->bpFuncName.clear();
data->bpFileName.clear();
data->bpLineNumber.clear();
data->bpCondition.clear();
data->bpIgnoreCount.clear();
data->bpAddress.clear();
2008-12-02 12:01:29 +01:00
// keep marker data if it was primary
if (data->markerFileName != data->fileName)
data->markerFileName.clear();
if (data->markerLineNumber != data->lineNumber.toInt())
data->markerLineNumber = 0;
}
m_enabled.clear();
m_disabled.clear();
m_removed.clear();
m_inserted.clear();
2008-12-02 12:01:29 +01:00
}
void BreakHandler::updateMarkers()
{
for (int index = 0; index != size(); ++index)
at(index)->updateMarker();
emit layoutChanged();
}
QVariant BreakHandler::headerData(int section,
Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
static QString headers[] = {
tr("Number"), tr("Function"), tr("File"), tr("Line"),
tr("Condition"), tr("Ignore")
};
return headers[section];
}
return QVariant();
}
QVariant BreakHandler::data(const QModelIndex &mi, int role) const
{
static const QString empty = QString(QLatin1Char('-'));
2008-12-09 12:08:56 +01:00
QTC_ASSERT(mi.isValid(), return QVariant());
2008-12-02 12:01:29 +01:00
if (mi.row() >= size())
return QVariant();
const BreakpointData *data = at(mi.row());
switch (mi.column()) {
case 0:
if (role == Qt::DisplayRole) {
QString str = data->bpNumber;
return str.isEmpty() ? empty : str;
}
//if (role == Qt::CheckStateRole)
// return data->enabled ? Qt::Checked : Qt::Unchecked;
if (role == Qt::UserRole)
return data->enabled;
2008-12-02 12:01:29 +01:00
if (role == Qt::DecorationRole)
return BreakpointMarker::icon(data->pending, data->enabled);
2008-12-02 12:01:29 +01:00
break;
case 1:
if (role == Qt::DisplayRole) {
QString str = data->pending ? data->funcName : data->bpFuncName;
return str.isEmpty() ? empty : str;
}
break;
case 2:
if (role == Qt::DisplayRole) {
QString str = data->pending ? data->fileName : data->bpFileName;
str = QFileInfo(str).fileName();
//if (data->bpMultiple && str.isEmpty() && !data->markerFileName.isEmpty())
// str = data->markerFileName;
str = str.isEmpty() ? empty : str;
if (data->useFullPath)
str = "/.../" + str;
return str;
2008-12-02 12:01:29 +01:00
}
if (role == Qt::UserRole)
return data->useFullPath;
2008-12-02 12:01:29 +01:00
break;
case 3:
if (role == Qt::DisplayRole) {
QString str = data->pending ? data->lineNumber : data->bpLineNumber;
//if (data->bpMultiple && str.isEmpty() && !data->markerFileName.isEmpty())
// str = data->markerLineNumber;
return str.isEmpty() ? empty : str;
}
break;
case 4:
if (role == Qt::DisplayRole)
return data->pending ? data->condition : data->bpCondition;
if (role == Qt::ToolTipRole)
return tr("Breakpoint will only be hit if this condition is met.");
break;
case 5:
if (role == Qt::DisplayRole)
return data->pending ? data->ignoreCount : data->bpIgnoreCount;
if (role == Qt::ToolTipRole)
return tr("Breakpoint will only be hit after being ignored so many times.");
break;
}
if (role == Qt::ToolTipRole)
return data->toToolTip();
return QVariant();
}
Qt::ItemFlags BreakHandler::flags(const QModelIndex &mi) const
{
switch (mi.column()) {
//case 0:
// return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
default:
return QAbstractItemModel::flags(mi);
}
}
2008-12-02 12:01:29 +01:00
bool BreakHandler::setData(const QModelIndex &mi, const QVariant &value, int role)
{
if (role != Qt::EditRole)
return false;
BreakpointData *data = at(mi.row());
switch (mi.column()) {
case 0: {
if (data->enabled != value.toBool()) {
toggleBreakpointEnabled(data);
dataChanged(mi, mi);
}
return true;
}
case 2: {
if (data->useFullPath != value.toBool()) {
data->useFullPath = value.toBool();
dataChanged(mi, mi);
}
return true;
}
2008-12-02 12:01:29 +01:00
case 4: {
QString val = value.toString();
if (val != data->condition) {
data->condition = val;
dataChanged(mi, mi);
}
return true;
}
case 5: {
QString val = value.toString();
if (val != data->ignoreCount) {
data->ignoreCount = val;
dataChanged(mi, mi);
}
return true;
}
default: {
return false;
}
}
}
void BreakHandler::append(BreakpointData *data)
{
m_bp.append(data);
m_inserted.append(data);
}
QList<BreakpointData *> BreakHandler::insertedBreakpoints() const
{
return m_inserted;
}
void BreakHandler::takeInsertedBreakPoint(BreakpointData *d)
{
m_inserted.removeAll(d);
}
2008-12-02 12:01:29 +01:00
QList<BreakpointData *> BreakHandler::takeRemovedBreakpoints()
{
QList<BreakpointData *> result = m_removed;
m_removed.clear();
return result;
}
QList<BreakpointData *> BreakHandler::takeEnabledBreakpoints()
{
QList<BreakpointData *> result = m_enabled;
m_enabled.clear();
return result;
}
QList<BreakpointData *> BreakHandler::takeDisabledBreakpoints()
{
QList<BreakpointData *> result = m_disabled;
m_disabled.clear();
return result;
}
2008-12-02 12:01:29 +01:00
void BreakHandler::removeBreakpointHelper(int index)
{
BreakpointData *data = m_bp.at(index);
m_bp.removeAt(index);
data->removeMarker();
m_removed.append(data);
}
void BreakHandler::removeBreakpoint(int index)
{
if (index < 0 || index >= size())
return;
removeBreakpointHelper(index);
2008-12-02 12:01:29 +01:00
emit layoutChanged();
saveBreakpoints();
}
void BreakHandler::toggleBreakpointEnabled(BreakpointData *data)
{
QTC_ASSERT(data, return);
data->enabled = !data->enabled;
if (data->enabled) {
m_enabled.append(data);
m_disabled.removeAll(data);
} else {
m_enabled.removeAll(data);
m_disabled.append(data);
}
saveBreakpoints();
updateMarkers();
}
2008-12-02 12:01:29 +01:00
void BreakHandler::toggleBreakpointEnabled(const QString &fileName, int lineNumber)
2008-12-02 12:01:29 +01:00
{
toggleBreakpointEnabled(at(findBreakpoint(fileName, lineNumber)));
2008-12-02 12:01:29 +01:00
}
void BreakHandler::setBreakpoint(const QString &fileName, int lineNumber)
{
QFileInfo fi(fileName);
BreakpointData *data = new BreakpointData(this);
data->fileName = fileName;
data->lineNumber = QString::number(lineNumber);
data->pending = true;
data->markerFileName = fileName;
data->markerLineNumber = lineNumber;
append(data);
emit layoutChanged();
saveBreakpoints();
updateMarkers();
}
void BreakHandler::removeAllBreakpoints()
{
for (int index = size(); --index >= 0;)
removeBreakpointHelper(index);
emit layoutChanged();
saveBreakpoints();
updateMarkers();
}
void BreakHandler::setAllPending()
{
loadBreakpoints();
for (int index = size(); --index >= 0;)
at(index)->pending = true;
saveBreakpoints();
updateMarkers();
}
void BreakHandler::saveSessionData()
{
saveBreakpoints();
updateMarkers();
}
void BreakHandler::loadSessionData()
{
//resetBreakpoints();
loadBreakpoints();
updateMarkers();
}
void BreakHandler::activateBreakpoint(int index)
2008-12-02 12:01:29 +01:00
{
const BreakpointData *data = at(index);
//qDebug() << "BREAKPOINT ACTIVATED: " << data->fileName;
if (!data->markerFileName.isEmpty())
emit gotoLocation(data->markerFileName, data->markerLineNumber, false);
}
void BreakHandler::breakByFunction(const QString &functionName)
{
// One per function is enough for now
for (int index = size(); --index >= 0;) {
const BreakpointData *data = at(index);
2008-12-09 12:08:56 +01:00
QTC_ASSERT(data, break);
2008-12-02 12:01:29 +01:00
if (data->funcName == functionName && data->condition.isEmpty()
&& data->ignoreCount.isEmpty())
return;
}
BreakpointData *data = new BreakpointData(this);
data->funcName = functionName;
append(data);
saveBreakpoints();
updateMarkers();
}
#include "breakhandler.moc"