File indexing completed on 2024-05-12 04:37:35

0001 /*
0002     SPDX-FileCopyrightText: 2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
0003     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0004     SPDX-FileCopyrightText: 2006, 2008 Vladimir Prus <ghost@cs.msu.su>
0005     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "breakpointmodel.h"
0012 
0013 #include <QIcon>
0014 #include <QPixmap>
0015 #include <QTimer>
0016 
0017 #include <KLocalizedString>
0018 #include <KTextEditor/Document>
0019 #include <KTextEditor/MovingInterface>
0020 
0021 #include "../interfaces/icore.h"
0022 #include "../interfaces/idebugcontroller.h"
0023 #include "../interfaces/idocumentcontroller.h"
0024 #include "../interfaces/idocument.h"
0025 #include "../interfaces/ipartcontroller.h"
0026 #include <interfaces/idebugsession.h>
0027 #include <interfaces/ibreakpointcontroller.h>
0028 #include <interfaces/isession.h>
0029 #include <debug.h>
0030 #include "breakpoint.h"
0031 #include <KConfigGroup>
0032 #include <QAction>
0033 #include <QMenu>
0034 #include <QMessageBox>
0035 
0036 #define IF_DEBUG(x)
0037 
0038 using namespace KDevelop;
0039 using namespace KTextEditor;
0040 
0041 namespace {
0042 
0043 IBreakpointController* breakpointController()
0044 {
0045     KDevelop::ICore* core = KDevelop::ICore::self();
0046     if (!core) {
0047         return nullptr;
0048     }
0049     IDebugController* controller = core->debugController();
0050     if (!controller) {
0051         return nullptr;
0052     }
0053     IDebugSession* session = controller->currentSession();
0054     return session ? session->breakpointController() : nullptr;
0055 }
0056 
0057 } // anonymous namespace
0058 
0059 class KDevelop::BreakpointModelPrivate
0060 {
0061 public:
0062     bool dirty = false;
0063     bool dontUpdateMarks = false;
0064     QList<Breakpoint*> breakpoints;
0065     /// FIXME: this is just an ugly workaround to not leak deleted breakpoints
0066     ///        a real fix would make sure that we actually delete breakpoints
0067     ///        right when we delete them... aka remove Breakpoint::{set}deleted
0068     QList<Breakpoint*> deletedBreakpoints;
0069 };
0070 
0071 BreakpointModel::BreakpointModel(QObject* parent)
0072     : QAbstractTableModel(parent),
0073       d_ptr(new BreakpointModelPrivate)
0074 {
0075     connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks);
0076 
0077     if (KDevelop::ICore::self()->partController()) { //TODO remove if
0078         const auto parts = KDevelop::ICore::self()->partController()->parts();
0079         for (KParts::Part* p : parts) {
0080             slotPartAdded(p);
0081         }
0082         connect(KDevelop::ICore::self()->partController(),
0083                 &IPartController::partAdded,
0084                 this,
0085                 &BreakpointModel::slotPartAdded);
0086     }
0087 
0088 
0089     connect (KDevelop::ICore::self()->documentController(),
0090              &IDocumentController::textDocumentCreated,
0091              this,
0092              &BreakpointModel::textDocumentCreated);
0093     connect (KDevelop::ICore::self()->documentController(),
0094                 &IDocumentController::documentSaved,
0095                 this, &BreakpointModel::documentSaved);
0096 }
0097 
0098 BreakpointModel::~BreakpointModel()
0099 {
0100     Q_D(BreakpointModel);
0101 
0102     qDeleteAll(d->breakpoints);
0103     qDeleteAll(d->deletedBreakpoints);
0104 }
0105 
0106 void BreakpointModel::slotPartAdded(KParts::Part* part)
0107 {
0108     if (auto doc = qobject_cast<KTextEditor::Document*>(part))
0109     {
0110         auto *iface = qobject_cast<MarkInterface*>(doc);
0111         if( !iface )
0112             return;
0113 
0114         iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint"));
0115         iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap());
0116         iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap());
0117         iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap());
0118         iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap());
0119         iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark );
0120         updateMarks();
0121     }
0122 }
0123 
0124 void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc)
0125 {
0126     Q_D(const BreakpointModel);
0127 
0128     KTextEditor::Document* const textDocument = doc->textDocument();
0129 
0130     if (qobject_cast<KTextEditor::MarkInterface*>(textDocument)) {
0131         // can't use new signal slot syntax here, MarkInterface is not a QObject
0132         connect(textDocument, SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)),
0133                  this, SLOT(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)));
0134         connect(textDocument, SIGNAL(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)),
0135                 SLOT(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)));
0136     }
0137 
0138     // markChanged() is not triggered for loaded breakpoints, so get them a moving cursor now
0139     const QUrl docUrl = textDocument->url();
0140     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
0141         if (docUrl == breakpoint->url()) {
0142             setupMovingCursor(textDocument, breakpoint);
0143         }
0144     }
0145 }
0146 
0147 void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled)
0148 {
0149     int type = mark.type;
0150     qCDebug(DEBUGGER) << type;
0151 
0152     Breakpoint *b = nullptr;
0153     if ((type & AllBreakpointMarks)) {
0154         b = breakpoint(document->url(), mark.line);
0155         if (!b) {
0156             QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line));
0157         }
0158     } else if (!(type & MarkInterface::Bookmark)) // neither breakpoint nor bookmark
0159         return;
0160 
0161     QMenu menu; // TODO: needs qwidget
0162     QAction* breakpointAction = menu.addAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("&Breakpoint"));
0163     breakpointAction->setCheckable(true);
0164     breakpointAction->setChecked(b);
0165     QAction* enableAction = nullptr;
0166     if (b) {
0167         enableAction = b->enabled() ?
0168             menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint")) :
0169             menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint"));
0170     }
0171     menu.addSeparator();
0172     QAction* bookmarkAction = menu.addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("&Bookmark"));
0173     bookmarkAction->setCheckable(true);
0174     bookmarkAction->setChecked((type & MarkInterface::Bookmark));
0175 
0176     QAction* triggeredAction = menu.exec(pos);
0177     if (triggeredAction) {
0178         if (triggeredAction == bookmarkAction) {
0179             KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface*>(document);
0180             if ((type & MarkInterface::Bookmark))
0181                 iface->removeMark(mark.line, MarkInterface::Bookmark);
0182             else
0183                 iface->addMark(mark.line, MarkInterface::Bookmark);
0184         } else if (triggeredAction == breakpointAction) {
0185             if (b) {
0186                 removeBreakpoint(b);
0187             } else {
0188                 Breakpoint* breakpoint = addCodeBreakpoint(document->url(), mark.line);
0189                 setupMovingCursor(document, breakpoint);
0190             }
0191         } else if (triggeredAction == enableAction) {
0192             b->setData(Breakpoint::EnableColumn, b->enabled() ? Qt::Unchecked : Qt::Checked);
0193         }
0194     }
0195 
0196     handled = true;
0197 }
0198 
0199 
0200 QVariant
0201 BreakpointModel::headerData(int section, Qt::Orientation orientation,
0202                                  int role) const
0203 {
0204     if (orientation == Qt::Vertical)
0205         return QVariant();
0206 
0207     if (role == Qt::DecorationRole ) {
0208         if (section == 0)
0209             return QIcon::fromTheme(QStringLiteral("dialog-ok-apply"));
0210         else if (section == 1)
0211             return QIcon::fromTheme(QStringLiteral("system-switch-user"));
0212     }
0213 
0214     if (role == Qt::DisplayRole) {
0215         if (section == 0 || section == 1) return QString();
0216         if (section == 2) return i18n("Type");
0217         if (section == 3) return i18n("Location");
0218         if (section == 4) return i18n("Condition");
0219     }
0220 
0221     if (role == Qt::ToolTipRole) {
0222         if (section == 0) return i18n("Active status");
0223         if (section == 1) return i18n("State");
0224         return headerData(section, orientation, Qt::DisplayRole);
0225 
0226     }
0227     return QVariant();
0228 }
0229 
0230 Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const
0231 {
0232     /* FIXME: all this logic must be in item */
0233     if (!index.isValid())
0234         return Qt::NoItemFlags;
0235 
0236     if (index.column() == 0)
0237         return static_cast<Qt::ItemFlags>(
0238             Qt::ItemIsEnabled | Qt::ItemIsSelectable
0239             | Qt::ItemIsEditable | Qt::ItemIsUserCheckable);
0240 
0241     if (index.column() == Breakpoint::ConditionColumn)
0242         return static_cast<Qt::ItemFlags>(
0243             Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
0244 
0245     return static_cast<Qt::ItemFlags>(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
0246 }
0247 
0248 QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column)
0249 {
0250     Q_D(BreakpointModel);
0251 
0252     int row = d->breakpoints.indexOf(b);
0253     if (row == -1) return QModelIndex();
0254     return index(row, column);
0255 }
0256 
0257 bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent)
0258 {
0259     Q_D(BreakpointModel);
0260 
0261     if (count < 1 || (row < 0) || (row + count) > rowCount(parent))
0262         return false;
0263 
0264     IBreakpointController* controller = breakpointController();
0265 
0266     beginRemoveRows(parent, row, row+count-1);
0267     for (int i=0; i < count; ++i) {
0268         Breakpoint* b = d->breakpoints.at(row);
0269         b->m_deleted = true;
0270         if (controller)
0271             controller->breakpointAboutToBeDeleted(row);
0272         d->breakpoints.removeAt(row);
0273         b->m_model = nullptr;
0274         // To be changed: the controller is currently still responsible for deleting the breakpoint
0275         // object
0276         // FIXME: this whole notion of m_deleted is utterly broken and needs to be fixed properly
0277         // for now just prevent a leak...
0278         d->deletedBreakpoints.append(b);
0279     }
0280     endRemoveRows();
0281     updateMarks();
0282     scheduleSave();
0283     return true;
0284 }
0285 
0286 int KDevelop::BreakpointModel::rowCount(const QModelIndex& parent) const
0287 {
0288     Q_D(const BreakpointModel);
0289 
0290     if (!parent.isValid()) {
0291         return d->breakpoints.count();
0292     }
0293     return 0;
0294 }
0295 
0296 int KDevelop::BreakpointModel::columnCount(const QModelIndex& parent) const
0297 {
0298     Q_UNUSED(parent);
0299     return 5;
0300 }
0301 
0302 QVariant BreakpointModel::data(const QModelIndex& index, int role) const
0303 {
0304     Q_D(const BreakpointModel);
0305 
0306     if (!index.parent().isValid() && index.row() < d->breakpoints.count()) {
0307         return d->breakpoints.at(index.row())->data(index.column(), role);
0308     }
0309     return QVariant();
0310 }
0311 
0312 bool KDevelop::BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role)
0313 {
0314     Q_D(const BreakpointModel);
0315 
0316     if (!index.parent().isValid() && index.row() < d->breakpoints.count() && (role == Qt::EditRole || role == Qt::CheckStateRole)) {
0317         return d->breakpoints.at(index.row())->setData(index.column(), value);
0318     }
0319     return false;
0320 }
0321 
0322 void BreakpointModel::updateState(int row, Breakpoint::BreakpointState state)
0323 {
0324     Q_D(BreakpointModel);
0325 
0326     Breakpoint* breakpoint = d->breakpoints.at(row);
0327     if (state != breakpoint->m_state) {
0328         breakpoint->m_state = state;
0329         reportChange(breakpoint, Breakpoint::StateColumn);
0330     }
0331 }
0332 
0333 void BreakpointModel::updateHitCount(int row, int hitCount)
0334 {
0335     Q_D(BreakpointModel);
0336 
0337     Breakpoint* breakpoint = d->breakpoints.at(row);
0338     if (hitCount != breakpoint->m_hitCount) {
0339         breakpoint->m_hitCount = hitCount;
0340         reportChange(breakpoint, Breakpoint::HitCountColumn);
0341     }
0342 }
0343 
0344 void BreakpointModel::updateErrorText(int row, const QString& errorText)
0345 {
0346     Q_D(BreakpointModel);
0347 
0348     Breakpoint* breakpoint = d->breakpoints.at(row);
0349     if (breakpoint->m_errorText != errorText) {
0350         breakpoint->m_errorText = errorText;
0351         reportChange(breakpoint, Breakpoint::StateColumn);
0352     }
0353 
0354     if (!errorText.isEmpty()) {
0355         emit error(row, errorText);
0356     }
0357 }
0358 
0359 void BreakpointModel::notifyHit(int row)
0360 {
0361     emit hit(row);
0362 }
0363 
0364 void BreakpointModel::markChanged(
0365     KTextEditor::Document *document,
0366     KTextEditor::Mark mark,
0367     KTextEditor::MarkInterface::MarkChangeAction action)
0368 {
0369     int type = mark.type;
0370     /* Is this a breakpoint mark, to begin with? */
0371     if (!(type & AllBreakpointMarks)) return;
0372 
0373     if (action == KTextEditor::MarkInterface::MarkAdded) {
0374         Breakpoint *b = breakpoint(document->url(), mark.line);
0375         if (b) {
0376             //there was already a breakpoint, so delete instead of adding
0377             removeBreakpoint(b);
0378             return;
0379         }
0380         Breakpoint* breakpoint = addCodeBreakpoint(document->url(), mark.line);
0381         setupMovingCursor(document, breakpoint);
0382     } else {
0383         // Find this breakpoint and delete it
0384         Breakpoint *b = breakpoint(document->url(), mark.line);
0385         if (b) {
0386             removeBreakpoint(b);
0387         }
0388     }
0389 
0390 #if 0
0391     if ( KDevelop::ICore::self()->documentController()->activeDocument() && KDevelop::ICore::self()->documentController()->activeDocument()->textDocument() == document )
0392     {
0393         //bring focus back to the editor
0394         // TODO probably want a different command here
0395         KDevelop::ICore::self()->documentController()->activateDocument(KDevelop::ICore::self()->documentController()->activeDocument());
0396     }
0397 #endif
0398 }
0399 
0400 static constexpr int breakpointMarkPixmapSize = 32;
0401 
0402 const QPixmap* BreakpointModel::breakpointPixmap()
0403 {
0404     static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Active, QIcon::Off);
0405     return &pixmap;
0406 }
0407 
0408 const QPixmap* BreakpointModel::pendingBreakpointPixmap()
0409 {
0410     static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Normal, QIcon::Off);
0411     return &pixmap;
0412 }
0413 
0414 const QPixmap* BreakpointModel::reachedBreakpointPixmap()
0415 {
0416     static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Selected, QIcon::Off);
0417     return &pixmap;
0418 }
0419 
0420 const QPixmap* BreakpointModel::disabledBreakpointPixmap()
0421 {
0422     static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Disabled, QIcon::Off);
0423     return &pixmap;
0424 }
0425 
0426 void BreakpointModel::removeBreakpoint(Breakpoint* breakpoint)
0427 {
0428     Q_D(BreakpointModel);
0429 
0430     Q_ASSERT(breakpoint);
0431     const auto row = d->breakpoints.indexOf(breakpoint);
0432     Q_ASSERT(row != -1);
0433     removeRow(row);
0434 }
0435 
0436 void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor)
0437 {
0438     Breakpoint *b = breakpoint(url, cursor.line());
0439     if (b) {
0440         removeBreakpoint(b);
0441     } else {
0442         addCodeBreakpoint(url, cursor.line());
0443     }
0444 }
0445 
0446 void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column)
0447 {
0448     Q_D(BreakpointModel);
0449 
0450     // note: just a portion of Breakpoint::Column is displayed in this model!
0451     if (column >= 0 && column < columnCount()) {
0452         QModelIndex idx = breakpointIndex(breakpoint, column);
0453         Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged()
0454         emit dataChanged(idx, idx);
0455     }
0456 
0457     if (IBreakpointController* controller = breakpointController()) {
0458         int row = d->breakpoints.indexOf(breakpoint);
0459         Q_ASSERT(row != -1);
0460         controller->breakpointModelChanged(row, ColumnFlags(1 << column));
0461     }
0462 
0463     scheduleSave();
0464 }
0465 
0466 uint BreakpointModel::breakpointType(Breakpoint *breakpoint) const
0467 {
0468     uint type = BreakpointMark;
0469     if (!breakpoint->enabled()) {
0470         type = DisabledBreakpointMark;
0471     } else if (breakpoint->hitCount() > 0) {
0472         type = ReachedBreakpointMark;
0473     } else if (breakpoint->state() == Breakpoint::PendingState) {
0474         type = PendingBreakpointMark;
0475     }
0476     return type;
0477 }
0478 
0479 void KDevelop::BreakpointModel::updateMarks()
0480 {
0481     Q_D(BreakpointModel);
0482 
0483     if (d->dontUpdateMarks)
0484         return;
0485 
0486     const auto* const documentController = ICore::self()->documentController();
0487     if (!documentController) {
0488         qCDebug(DEBUGGER) << "Cannot update marks without the document controller. "
0489                              "KDevelop must be exiting and the document controller already destroyed.";
0490         return;
0491     }
0492 
0493     //add marks
0494     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
0495         if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue;
0496         if (breakpoint->line() == -1) continue;
0497         IDocument *doc = documentController->documentForUrl(breakpoint->url());
0498         if (!doc) continue;
0499         KTextEditor::MarkInterface *mark = qobject_cast<KTextEditor::MarkInterface*>(doc->textDocument());
0500         if (!mark) continue;
0501         uint type = breakpointType(breakpoint);
0502         IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); )
0503 
0504         {
0505             QSignalBlocker blocker(doc->textDocument());
0506             if (mark->mark(breakpoint->line()) & AllBreakpointMarks) {
0507                 if (!(mark->mark(breakpoint->line()) & type)) {
0508                     mark->removeMark(breakpoint->line(), AllBreakpointMarks);
0509                     mark->addMark(breakpoint->line(), type);
0510                 }
0511             } else {
0512                 mark->addMark(breakpoint->line(), type);
0513             }
0514         }
0515     }
0516 
0517     //remove marks
0518     const auto documents = documentController->openDocuments();
0519     for (IDocument* doc : documents) {
0520         KTextEditor::MarkInterface *mark = qobject_cast<KTextEditor::MarkInterface*>(doc->textDocument());
0521         if (!mark) continue;
0522 
0523         {
0524             QSignalBlocker blocker(doc->textDocument());
0525             const auto oldMarks = mark->marks();
0526             for (KTextEditor::Mark* m : oldMarks) {
0527                 if (!(m->type & AllBreakpointMarks)) continue;
0528                 IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; )
0529                 for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
0530                     if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue;
0531                     if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) {
0532                         goto continueNextMark;
0533                     }
0534                 }
0535                 mark->removeMark(m->line, AllBreakpointMarks);
0536                 continueNextMark:;
0537             }
0538         }
0539     }
0540 }
0541 
0542 void BreakpointModel::documentSaved(KDevelop::IDocument* doc)
0543 {
0544     Q_D(BreakpointModel);
0545 
0546     IF_DEBUG( qCDebug(DEBUGGER); )
0547     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
0548         if (breakpoint->movingCursor()) {
0549             if (breakpoint->movingCursor()->document() != doc->textDocument()) continue;
0550             if (breakpoint->movingCursor()->line() == breakpoint->line()) continue;
0551             d->dontUpdateMarks = true;
0552             breakpoint->setLine(breakpoint->movingCursor()->line());
0553             d->dontUpdateMarks = false;
0554         }
0555     }
0556 }
0557 void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document)
0558 {
0559     Q_D(BreakpointModel);
0560 
0561     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
0562         if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) {
0563             breakpoint->setMovingCursor(nullptr);
0564         }
0565     }
0566 }
0567 
0568 void BreakpointModel::load()
0569 {
0570     KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints");
0571     int count = breakpoints.readEntry("number", 0);
0572     if (count == 0)
0573         return;
0574 
0575     beginInsertRows(QModelIndex(), 0, count - 1);
0576     for (int i = 0; i < count; ++i) {
0577         if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) {
0578             new Breakpoint(this, breakpoints.group(QString::number(i)));
0579         }
0580     }
0581     endInsertRows();
0582 }
0583 
0584 void BreakpointModel::save()
0585 {
0586     Q_D(BreakpointModel);
0587 
0588     d->dirty = false;
0589 
0590     auto* const activeSession = ICore::self()->activeSession();
0591     if (!activeSession) {
0592         qCDebug(DEBUGGER) << "Cannot save breakpoints because there is no active session. "
0593                              "KDevelop must be exiting and already past SessionController::cleanup().";
0594         return;
0595     }
0596 
0597     KConfigGroup breakpoints = activeSession->config()->group("Breakpoints");
0598     breakpoints.writeEntry("number", d->breakpoints.count());
0599     int i = 0;
0600     for (Breakpoint* b : qAsConst(d->breakpoints)) {
0601         KConfigGroup g = breakpoints.group(QString::number(i));
0602         b->save(g);
0603         ++i;
0604     }
0605     breakpoints.sync();
0606 }
0607 
0608 void BreakpointModel::scheduleSave()
0609 {
0610     Q_D(BreakpointModel);
0611 
0612     if (d->dirty)
0613         return;
0614 
0615     d->dirty = true;
0616     QTimer::singleShot(0, this, &BreakpointModel::save);
0617 }
0618 
0619 QList<Breakpoint*> KDevelop::BreakpointModel::breakpoints() const
0620 {
0621     Q_D(const BreakpointModel);
0622 
0623     return d->breakpoints;
0624 }
0625 
0626 Breakpoint* BreakpointModel::breakpoint(int row) const
0627 {
0628     Q_D(const BreakpointModel);
0629 
0630     if (row >= d->breakpoints.count()) return nullptr;
0631     return d->breakpoints.at(row);
0632 }
0633 
0634 Breakpoint* BreakpointModel::addCodeBreakpoint()
0635 {
0636     Q_D(BreakpointModel);
0637 
0638     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
0639     auto* n = new Breakpoint(this, Breakpoint::CodeBreakpoint);
0640     endInsertRows();
0641     return n;
0642 }
0643 
0644 Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line)
0645 {
0646     Breakpoint* n = addCodeBreakpoint();
0647     n->setLocation(url, line);
0648     return n;
0649 }
0650 
0651 Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression)
0652 {
0653     Breakpoint* n = addCodeBreakpoint();
0654     n->setExpression(expression);
0655     return n;
0656 }
0657 
0658 Breakpoint* BreakpointModel::addWatchpoint()
0659 {
0660     Q_D(BreakpointModel);
0661 
0662     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
0663     auto* n = new Breakpoint(this, Breakpoint::WriteBreakpoint);
0664     endInsertRows();
0665     return n;
0666 }
0667 
0668 Breakpoint* BreakpointModel::addWatchpoint(const QString& expression)
0669 {
0670     Breakpoint* n = addWatchpoint();
0671     n->setExpression(expression);
0672     return n;
0673 }
0674 
0675 Breakpoint* BreakpointModel::addReadWatchpoint()
0676 {
0677     Q_D(BreakpointModel);
0678 
0679     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
0680     auto* n = new Breakpoint(this, Breakpoint::ReadBreakpoint);
0681     endInsertRows();
0682     return n;
0683 }
0684 
0685 Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression)
0686 {
0687     Breakpoint* n = addReadWatchpoint();
0688     n->setExpression(expression);
0689     return n;
0690 }
0691 
0692 Breakpoint* BreakpointModel::addAccessWatchpoint()
0693 {
0694     Q_D(BreakpointModel);
0695 
0696     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
0697     auto* n = new Breakpoint(this, Breakpoint::AccessBreakpoint);
0698     endInsertRows();
0699     return n;
0700 }
0701 
0702 
0703 Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression)
0704 {
0705     Breakpoint* n = addAccessWatchpoint();
0706     n->setExpression(expression);
0707     return n;
0708 }
0709 
0710 void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint)
0711 {
0712     Q_D(BreakpointModel);
0713 
0714     Q_ASSERT(!d->breakpoints.contains(breakpoint));
0715     int row = d->breakpoints.size();
0716     d->breakpoints << breakpoint;
0717     if (IBreakpointController* controller = breakpointController()) {
0718         controller->breakpointAdded(row);
0719     }
0720     scheduleSave();
0721 }
0722 
0723 Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) const
0724 {
0725     Q_D(const BreakpointModel);
0726 
0727     auto it = std::find_if(d->breakpoints.constBegin(), d->breakpoints.constEnd(), [&](Breakpoint* b) {
0728         return (b->url() == url && b->line() == line);
0729     });
0730     return (it != d->breakpoints.constEnd()) ? *it : nullptr;
0731 }
0732 
0733 void BreakpointModel::setupMovingCursor(KTextEditor::Document* document, Breakpoint* breakpoint) const
0734 {
0735     Q_ASSERT(document->url() == breakpoint->url() && breakpoint->movingCursor() == nullptr);
0736 
0737     auto* const movingInterface = qobject_cast<KTextEditor::MovingInterface*>(document);
0738     if (movingInterface) {
0739         auto* const cursor = movingInterface->newMovingCursor(KTextEditor::Cursor(breakpoint->line(), 0));
0740         // can't use new signal/slot syntax here, MovingInterface is not a QObject
0741         connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)),
0742                 this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), Qt::UniqueConnection);
0743         breakpoint->setMovingCursor(cursor);
0744     }
0745 }
0746 
0747 #include "moc_breakpointmodel.cpp"