1 /*
2     SPDX-FileCopyrightText: 2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
3     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
4     SPDX-FileCopyrightText: 2006, 2008 Vladimir Prus <ghost@cs.msu.su>
5     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
6     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
7 
8     SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "breakpointmodel.h"
12 
13 #include <QIcon>
14 #include <QPixmap>
15 #include <QTimer>
16 
17 #include <KLocalizedString>
18 #include <KTextEditor/Document>
19 #include <KTextEditor/MovingInterface>
20 
21 #include "../interfaces/icore.h"
22 #include "../interfaces/idebugcontroller.h"
23 #include "../interfaces/idocumentcontroller.h"
24 #include "../interfaces/idocument.h"
25 #include "../interfaces/ipartcontroller.h"
26 #include <interfaces/idebugsession.h>
27 #include <interfaces/ibreakpointcontroller.h>
28 #include <interfaces/isession.h>
29 #include <debug.h>
30 #include "breakpoint.h"
31 #include <KConfigGroup>
32 #include <QAction>
33 #include <QMenu>
34 #include <QMessageBox>
35 
36 #define IF_DEBUG(x)
37 
38 using namespace KDevelop;
39 using namespace KTextEditor;
40 
41 namespace {
42 
breakpointController()43 IBreakpointController* breakpointController()
44 {
45     KDevelop::ICore* core = KDevelop::ICore::self();
46     if (!core) {
47         return nullptr;
48     }
49     IDebugController* controller = core->debugController();
50     if (!controller) {
51         return nullptr;
52     }
53     IDebugSession* session = controller->currentSession();
54     return session ? session->breakpointController() : nullptr;
55 }
56 
57 } // anonymous namespace
58 
59 class KDevelop::BreakpointModelPrivate
60 {
61 public:
62     bool dirty = false;
63     bool dontUpdateMarks = false;
64     QList<Breakpoint*> breakpoints;
65     /// FIXME: this is just an ugly workaround to not leak deleted breakpoints
66     ///        a real fix would make sure that we actually delete breakpoints
67     ///        right when we delete them... aka remove Breakpoint::{set}deleted
68     QList<Breakpoint*> deletedBreakpoints;
69 };
70 
BreakpointModel(QObject * parent)71 BreakpointModel::BreakpointModel(QObject* parent)
72     : QAbstractTableModel(parent),
73       d_ptr(new BreakpointModelPrivate)
74 {
75     connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks);
76 
77     if (KDevelop::ICore::self()->partController()) { //TODO remove if
78         const auto parts = KDevelop::ICore::self()->partController()->parts();
79         for (KParts::Part* p : parts) {
80             slotPartAdded(p);
81         }
82         connect(KDevelop::ICore::self()->partController(),
83                 &IPartController::partAdded,
84                 this,
85                 &BreakpointModel::slotPartAdded);
86     }
87 
88 
89     connect (KDevelop::ICore::self()->documentController(),
90              &IDocumentController::textDocumentCreated,
91              this,
92              &BreakpointModel::textDocumentCreated);
93     connect (KDevelop::ICore::self()->documentController(),
94                 &IDocumentController::documentSaved,
95                 this, &BreakpointModel::documentSaved);
96 }
97 
~BreakpointModel()98 BreakpointModel::~BreakpointModel()
99 {
100     Q_D(BreakpointModel);
101 
102     qDeleteAll(d->breakpoints);
103     qDeleteAll(d->deletedBreakpoints);
104 }
105 
slotPartAdded(KParts::Part * part)106 void BreakpointModel::slotPartAdded(KParts::Part* part)
107 {
108     if (auto doc = qobject_cast<KTextEditor::Document*>(part))
109     {
110         auto *iface = qobject_cast<MarkInterface*>(doc);
111         if( !iface )
112             return;
113 
114         iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint"));
115         iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap());
116         iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap());
117         iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap());
118         iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap());
119         iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark );
120         updateMarks();
121     }
122 }
123 
textDocumentCreated(KDevelop::IDocument * doc)124 void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc)
125 {
126     Q_D(const BreakpointModel);
127 
128     KTextEditor::Document* const textDocument = doc->textDocument();
129 
130     if (qobject_cast<KTextEditor::MarkInterface*>(textDocument)) {
131         // can't use new signal slot syntax here, MarkInterface is not a QObject
132         connect(textDocument, SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)),
133                  this, SLOT(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)));
134         connect(textDocument, SIGNAL(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)),
135                 SLOT(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)));
136     }
137 
138     // markChanged() is not triggered for loaded breakpoints, so get them a moving cursor now
139     const QUrl docUrl = textDocument->url();
140     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
141         if (docUrl == breakpoint->url()) {
142             setupMovingCursor(textDocument, breakpoint);
143         }
144     }
145 }
146 
markContextMenuRequested(Document * document,Mark mark,const QPoint & pos,bool & handled)147 void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled)
148 {
149     int type = mark.type;
150     qCDebug(DEBUGGER) << type;
151 
152     Breakpoint *b = nullptr;
153     if ((type & AllBreakpointMarks)) {
154         b = breakpoint(document->url(), mark.line);
155         if (!b) {
156             QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line));
157         }
158     } else if (!(type & MarkInterface::Bookmark)) // neither breakpoint nor bookmark
159         return;
160 
161     QMenu menu; // TODO: needs qwidget
162     QAction* breakpointAction = menu.addAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("&Breakpoint"));
163     breakpointAction->setCheckable(true);
164     breakpointAction->setChecked(b);
165     QAction* enableAction = nullptr;
166     if (b) {
167         enableAction = b->enabled() ?
168             menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint")) :
169             menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint"));
170     }
171     menu.addSeparator();
172     QAction* bookmarkAction = menu.addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("&Bookmark"));
173     bookmarkAction->setCheckable(true);
174     bookmarkAction->setChecked((type & MarkInterface::Bookmark));
175 
176     QAction* triggeredAction = menu.exec(pos);
177     if (triggeredAction) {
178         if (triggeredAction == bookmarkAction) {
179             KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface*>(document);
180             if ((type & MarkInterface::Bookmark))
181                 iface->removeMark(mark.line, MarkInterface::Bookmark);
182             else
183                 iface->addMark(mark.line, MarkInterface::Bookmark);
184         } else if (triggeredAction == breakpointAction) {
185             if (b) {
186                 b->setDeleted();
187             } else {
188                 Breakpoint* breakpoint = addCodeBreakpoint(document->url(), mark.line);
189                 setupMovingCursor(document, breakpoint);
190             }
191         } else if (triggeredAction == enableAction) {
192             b->setData(Breakpoint::EnableColumn, b->enabled() ? Qt::Unchecked : Qt::Checked);
193         }
194     }
195 
196     handled = true;
197 }
198 
199 
200 QVariant
headerData(int section,Qt::Orientation orientation,int role) const201 BreakpointModel::headerData(int section, Qt::Orientation orientation,
202                                  int role) const
203 {
204     if (orientation == Qt::Vertical)
205         return QVariant();
206 
207     if (role == Qt::DecorationRole ) {
208         if (section == 0)
209             return QIcon::fromTheme(QStringLiteral("dialog-ok-apply"));
210         else if (section == 1)
211             return QIcon::fromTheme(QStringLiteral("system-switch-user"));
212     }
213 
214     if (role == Qt::DisplayRole) {
215         if (section == 0 || section == 1) return QString();
216         if (section == 2) return i18n("Type");
217         if (section == 3) return i18n("Location");
218         if (section == 4) return i18n("Condition");
219     }
220 
221     if (role == Qt::ToolTipRole) {
222         if (section == 0) return i18n("Active status");
223         if (section == 1) return i18n("State");
224         return headerData(section, orientation, Qt::DisplayRole);
225 
226     }
227     return QVariant();
228 }
229 
flags(const QModelIndex & index) const230 Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const
231 {
232     /* FIXME: all this logic must be in item */
233     if (!index.isValid())
234         return Qt::NoItemFlags;
235 
236     if (index.column() == 0)
237         return static_cast<Qt::ItemFlags>(
238             Qt::ItemIsEnabled | Qt::ItemIsSelectable
239             | Qt::ItemIsEditable | Qt::ItemIsUserCheckable);
240 
241     if (index.column() == Breakpoint::ConditionColumn)
242         return static_cast<Qt::ItemFlags>(
243             Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
244 
245     return static_cast<Qt::ItemFlags>(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
246 }
247 
breakpointIndex(KDevelop::Breakpoint * b,int column)248 QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column)
249 {
250     Q_D(BreakpointModel);
251 
252     int row = d->breakpoints.indexOf(b);
253     if (row == -1) return QModelIndex();
254     return index(row, column);
255 }
256 
removeRows(int row,int count,const QModelIndex & parent)257 bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent)
258 {
259     Q_D(BreakpointModel);
260 
261     if (count < 1 || (row < 0) || (row + count) > rowCount(parent))
262         return false;
263 
264     IBreakpointController* controller = breakpointController();
265 
266     beginRemoveRows(parent, row, row+count-1);
267     for (int i=0; i < count; ++i) {
268         Breakpoint* b = d->breakpoints.at(row);
269         b->m_deleted = true;
270         if (controller)
271             controller->breakpointAboutToBeDeleted(row);
272         d->breakpoints.removeAt(row);
273         b->m_model = nullptr;
274         // To be changed: the controller is currently still responsible for deleting the breakpoint
275         // object
276         // FIXME: this whole notion of m_deleted is utterly broken and needs to be fixed properly
277         // for now just prevent a leak...
278         d->deletedBreakpoints.append(b);
279     }
280     endRemoveRows();
281     updateMarks();
282     scheduleSave();
283     return true;
284 }
285 
rowCount(const QModelIndex & parent) const286 int KDevelop::BreakpointModel::rowCount(const QModelIndex& parent) const
287 {
288     Q_D(const BreakpointModel);
289 
290     if (!parent.isValid()) {
291         return d->breakpoints.count();
292     }
293     return 0;
294 }
295 
columnCount(const QModelIndex & parent) const296 int KDevelop::BreakpointModel::columnCount(const QModelIndex& parent) const
297 {
298     Q_UNUSED(parent);
299     return 5;
300 }
301 
data(const QModelIndex & index,int role) const302 QVariant BreakpointModel::data(const QModelIndex& index, int role) const
303 {
304     Q_D(const BreakpointModel);
305 
306     if (!index.parent().isValid() && index.row() < d->breakpoints.count()) {
307         return d->breakpoints.at(index.row())->data(index.column(), role);
308     }
309     return QVariant();
310 }
311 
setData(const QModelIndex & index,const QVariant & value,int role)312 bool KDevelop::BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role)
313 {
314     Q_D(const BreakpointModel);
315 
316     if (!index.parent().isValid() && index.row() < d->breakpoints.count() && (role == Qt::EditRole || role == Qt::CheckStateRole)) {
317         return d->breakpoints.at(index.row())->setData(index.column(), value);
318     }
319     return false;
320 }
321 
updateState(int row,Breakpoint::BreakpointState state)322 void BreakpointModel::updateState(int row, Breakpoint::BreakpointState state)
323 {
324     Q_D(BreakpointModel);
325 
326     Breakpoint* breakpoint = d->breakpoints.at(row);
327     if (state != breakpoint->m_state) {
328         breakpoint->m_state = state;
329         reportChange(breakpoint, Breakpoint::StateColumn);
330     }
331 }
332 
updateHitCount(int row,int hitCount)333 void BreakpointModel::updateHitCount(int row, int hitCount)
334 {
335     Q_D(BreakpointModel);
336 
337     Breakpoint* breakpoint = d->breakpoints.at(row);
338     if (hitCount != breakpoint->m_hitCount) {
339         breakpoint->m_hitCount = hitCount;
340         reportChange(breakpoint, Breakpoint::HitCountColumn);
341     }
342 }
343 
updateErrorText(int row,const QString & errorText)344 void BreakpointModel::updateErrorText(int row, const QString& errorText)
345 {
346     Q_D(BreakpointModel);
347 
348     Breakpoint* breakpoint = d->breakpoints.at(row);
349     if (breakpoint->m_errorText != errorText) {
350         breakpoint->m_errorText = errorText;
351         reportChange(breakpoint, Breakpoint::StateColumn);
352     }
353 
354     if (!errorText.isEmpty()) {
355         emit error(row, errorText);
356     }
357 }
358 
notifyHit(int row)359 void BreakpointModel::notifyHit(int row)
360 {
361     emit hit(row);
362 }
363 
markChanged(KTextEditor::Document * document,KTextEditor::Mark mark,KTextEditor::MarkInterface::MarkChangeAction action)364 void BreakpointModel::markChanged(
365     KTextEditor::Document *document,
366     KTextEditor::Mark mark,
367     KTextEditor::MarkInterface::MarkChangeAction action)
368 {
369     int type = mark.type;
370     /* Is this a breakpoint mark, to begin with? */
371     if (!(type & AllBreakpointMarks)) return;
372 
373     if (action == KTextEditor::MarkInterface::MarkAdded) {
374         Breakpoint *b = breakpoint(document->url(), mark.line);
375         if (b) {
376             //there was already a breakpoint, so delete instead of adding
377             b->setDeleted();
378             return;
379         }
380         Breakpoint* breakpoint = addCodeBreakpoint(document->url(), mark.line);
381         setupMovingCursor(document, breakpoint);
382     } else {
383         // Find this breakpoint and delete it
384         Breakpoint *b = breakpoint(document->url(), mark.line);
385         if (b) {
386             b->setDeleted();
387         }
388     }
389 
390 #if 0
391     if ( KDevelop::ICore::self()->documentController()->activeDocument() && KDevelop::ICore::self()->documentController()->activeDocument()->textDocument() == document )
392     {
393         //bring focus back to the editor
394         // TODO probably want a different command here
395         KDevelop::ICore::self()->documentController()->activateDocument(KDevelop::ICore::self()->documentController()->activeDocument());
396     }
397 #endif
398 }
399 
400 static constexpr int breakpointMarkPixmapSize = 32;
401 
breakpointPixmap()402 const QPixmap* BreakpointModel::breakpointPixmap()
403 {
404   static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Active, QIcon::Off);
405   return &pixmap;
406 }
407 
pendingBreakpointPixmap()408 const QPixmap* BreakpointModel::pendingBreakpointPixmap()
409 {
410   static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Normal, QIcon::Off);
411   return &pixmap;
412 }
413 
reachedBreakpointPixmap()414 const QPixmap* BreakpointModel::reachedBreakpointPixmap()
415 {
416   static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Selected, QIcon::Off);
417   return &pixmap;
418 }
419 
disabledBreakpointPixmap()420 const QPixmap* BreakpointModel::disabledBreakpointPixmap()
421 {
422   static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(breakpointMarkPixmapSize, breakpointMarkPixmapSize), QIcon::Disabled, QIcon::Off);
423   return &pixmap;
424 }
425 
toggleBreakpoint(const QUrl & url,const KTextEditor::Cursor & cursor)426 void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor)
427 {
428     Breakpoint *b = breakpoint(url, cursor.line());
429     if (b) {
430         b->setDeleted();
431     } else {
432         addCodeBreakpoint(url, cursor.line());
433     }
434 }
435 
reportChange(Breakpoint * breakpoint,Breakpoint::Column column)436 void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column)
437 {
438     Q_D(BreakpointModel);
439 
440     // note: just a portion of Breakpoint::Column is displayed in this model!
441     if (column >= 0 && column < columnCount()) {
442         QModelIndex idx = breakpointIndex(breakpoint, column);
443         Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged()
444         emit dataChanged(idx, idx);
445     }
446 
447     if (IBreakpointController* controller = breakpointController()) {
448         int row = d->breakpoints.indexOf(breakpoint);
449         Q_ASSERT(row != -1);
450         controller->breakpointModelChanged(row, ColumnFlags(1 << column));
451     }
452 
453     scheduleSave();
454 }
455 
breakpointType(Breakpoint * breakpoint) const456 uint BreakpointModel::breakpointType(Breakpoint *breakpoint) const
457 {
458     uint type = BreakpointMark;
459     if (!breakpoint->enabled()) {
460         type = DisabledBreakpointMark;
461     } else if (breakpoint->hitCount() > 0) {
462         type = ReachedBreakpointMark;
463     } else if (breakpoint->state() == Breakpoint::PendingState) {
464         type = PendingBreakpointMark;
465     }
466     return type;
467 }
468 
updateMarks()469 void KDevelop::BreakpointModel::updateMarks()
470 {
471     Q_D(BreakpointModel);
472 
473     if (d->dontUpdateMarks)
474         return;
475 
476     const auto* const documentController = ICore::self()->documentController();
477     if (!documentController) {
478         qCDebug(DEBUGGER) << "Cannot update marks without the document controller. "
479                              "KDevelop must be exiting and the document controller already destroyed.";
480         return;
481     }
482 
483     //add marks
484     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
485         if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue;
486         if (breakpoint->line() == -1) continue;
487         IDocument *doc = documentController->documentForUrl(breakpoint->url());
488         if (!doc) continue;
489         KTextEditor::MarkInterface *mark = qobject_cast<KTextEditor::MarkInterface*>(doc->textDocument());
490         if (!mark) continue;
491         uint type = breakpointType(breakpoint);
492         IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); )
493 
494         {
495             QSignalBlocker blocker(doc->textDocument());
496             if (mark->mark(breakpoint->line()) & AllBreakpointMarks) {
497                 if (!(mark->mark(breakpoint->line()) & type)) {
498                     mark->removeMark(breakpoint->line(), AllBreakpointMarks);
499                     mark->addMark(breakpoint->line(), type);
500                 }
501             } else {
502                 mark->addMark(breakpoint->line(), type);
503             }
504         }
505     }
506 
507     //remove marks
508     const auto documents = documentController->openDocuments();
509     for (IDocument* doc : documents) {
510         KTextEditor::MarkInterface *mark = qobject_cast<KTextEditor::MarkInterface*>(doc->textDocument());
511         if (!mark) continue;
512 
513         {
514             QSignalBlocker blocker(doc->textDocument());
515             const auto oldMarks = mark->marks();
516             for (KTextEditor::Mark* m : oldMarks) {
517                 if (!(m->type & AllBreakpointMarks)) continue;
518                 IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; )
519                 for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
520                     if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue;
521                     if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) {
522                         goto continueNextMark;
523                     }
524                 }
525                 mark->removeMark(m->line, AllBreakpointMarks);
526                 continueNextMark:;
527             }
528         }
529     }
530 }
531 
documentSaved(KDevelop::IDocument * doc)532 void BreakpointModel::documentSaved(KDevelop::IDocument* doc)
533 {
534     Q_D(BreakpointModel);
535 
536     IF_DEBUG( qCDebug(DEBUGGER); )
537     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
538         if (breakpoint->movingCursor()) {
539             if (breakpoint->movingCursor()->document() != doc->textDocument()) continue;
540             if (breakpoint->movingCursor()->line() == breakpoint->line()) continue;
541             d->dontUpdateMarks = true;
542             breakpoint->setLine(breakpoint->movingCursor()->line());
543             d->dontUpdateMarks = false;
544         }
545     }
546 }
aboutToDeleteMovingInterfaceContent(KTextEditor::Document * document)547 void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document)
548 {
549     Q_D(BreakpointModel);
550 
551     for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) {
552         if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) {
553             breakpoint->setMovingCursor(nullptr);
554         }
555     }
556 }
557 
load()558 void BreakpointModel::load()
559 {
560     KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints");
561     int count = breakpoints.readEntry("number", 0);
562     if (count == 0)
563         return;
564 
565     beginInsertRows(QModelIndex(), 0, count - 1);
566     for (int i = 0; i < count; ++i) {
567         if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) {
568             new Breakpoint(this, breakpoints.group(QString::number(i)));
569         }
570     }
571     endInsertRows();
572 }
573 
save()574 void BreakpointModel::save()
575 {
576     Q_D(BreakpointModel);
577 
578     d->dirty = false;
579 
580     auto* const activeSession = ICore::self()->activeSession();
581     if (!activeSession) {
582         qCDebug(DEBUGGER) << "Cannot save breakpoints because there is no active session. "
583                              "KDevelop must be exiting and already past SessionController::cleanup().";
584         return;
585     }
586 
587     KConfigGroup breakpoints = activeSession->config()->group("Breakpoints");
588     breakpoints.writeEntry("number", d->breakpoints.count());
589     int i = 0;
590     for (Breakpoint* b : qAsConst(d->breakpoints)) {
591         KConfigGroup g = breakpoints.group(QString::number(i));
592         b->save(g);
593         ++i;
594     }
595     breakpoints.sync();
596 }
597 
scheduleSave()598 void BreakpointModel::scheduleSave()
599 {
600     Q_D(BreakpointModel);
601 
602     if (d->dirty)
603         return;
604 
605     d->dirty = true;
606     QTimer::singleShot(0, this, &BreakpointModel::save);
607 }
608 
breakpoints() const609 QList<Breakpoint*> KDevelop::BreakpointModel::breakpoints() const
610 {
611     Q_D(const BreakpointModel);
612 
613     return d->breakpoints;
614 }
615 
breakpoint(int row) const616 Breakpoint* BreakpointModel::breakpoint(int row) const
617 {
618     Q_D(const BreakpointModel);
619 
620     if (row >= d->breakpoints.count()) return nullptr;
621     return d->breakpoints.at(row);
622 }
623 
addCodeBreakpoint()624 Breakpoint* BreakpointModel::addCodeBreakpoint()
625 {
626     Q_D(BreakpointModel);
627 
628     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
629     auto* n = new Breakpoint(this, Breakpoint::CodeBreakpoint);
630     endInsertRows();
631     return n;
632 }
633 
addCodeBreakpoint(const QUrl & url,int line)634 Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line)
635 {
636     Breakpoint* n = addCodeBreakpoint();
637     n->setLocation(url, line);
638     return n;
639 }
640 
addCodeBreakpoint(const QString & expression)641 Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression)
642 {
643     Breakpoint* n = addCodeBreakpoint();
644     n->setExpression(expression);
645     return n;
646 }
647 
addWatchpoint()648 Breakpoint* BreakpointModel::addWatchpoint()
649 {
650     Q_D(BreakpointModel);
651 
652     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
653     auto* n = new Breakpoint(this, Breakpoint::WriteBreakpoint);
654     endInsertRows();
655     return n;
656 }
657 
addWatchpoint(const QString & expression)658 Breakpoint* BreakpointModel::addWatchpoint(const QString& expression)
659 {
660     Breakpoint* n = addWatchpoint();
661     n->setExpression(expression);
662     return n;
663 }
664 
addReadWatchpoint()665 Breakpoint* BreakpointModel::addReadWatchpoint()
666 {
667     Q_D(BreakpointModel);
668 
669     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
670     auto* n = new Breakpoint(this, Breakpoint::ReadBreakpoint);
671     endInsertRows();
672     return n;
673 }
674 
addReadWatchpoint(const QString & expression)675 Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression)
676 {
677     Breakpoint* n = addReadWatchpoint();
678     n->setExpression(expression);
679     return n;
680 }
681 
addAccessWatchpoint()682 Breakpoint* BreakpointModel::addAccessWatchpoint()
683 {
684     Q_D(BreakpointModel);
685 
686     beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count());
687     auto* n = new Breakpoint(this, Breakpoint::AccessBreakpoint);
688     endInsertRows();
689     return n;
690 }
691 
692 
addAccessWatchpoint(const QString & expression)693 Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression)
694 {
695     Breakpoint* n = addAccessWatchpoint();
696     n->setExpression(expression);
697     return n;
698 }
699 
registerBreakpoint(Breakpoint * breakpoint)700 void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint)
701 {
702     Q_D(BreakpointModel);
703 
704     Q_ASSERT(!d->breakpoints.contains(breakpoint));
705     int row = d->breakpoints.size();
706     d->breakpoints << breakpoint;
707     if (IBreakpointController* controller = breakpointController()) {
708         controller->breakpointAdded(row);
709     }
710     scheduleSave();
711 }
712 
breakpoint(const QUrl & url,int line) const713 Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) const
714 {
715     Q_D(const BreakpointModel);
716 
717     auto it = std::find_if(d->breakpoints.constBegin(), d->breakpoints.constEnd(), [&](Breakpoint* b) {
718         return (b->url() == url && b->line() == line);
719     });
720     return (it != d->breakpoints.constEnd()) ? *it : nullptr;
721 }
722 
setupMovingCursor(KTextEditor::Document * document,Breakpoint * breakpoint) const723 void BreakpointModel::setupMovingCursor(KTextEditor::Document* document, Breakpoint* breakpoint) const
724 {
725     Q_ASSERT(document->url() == breakpoint->url() && breakpoint->movingCursor() == nullptr);
726 
727     auto* const movingInterface = qobject_cast<KTextEditor::MovingInterface*>(document);
728     if (movingInterface) {
729         auto* const cursor = movingInterface->newMovingCursor(KTextEditor::Cursor(breakpoint->line(), 0));
730         // can't use new signal/slot syntax here, MovingInterface is not a QObject
731         connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)),
732                 this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), Qt::UniqueConnection);
733         breakpoint->setMovingCursor(cursor);
734     }
735 }
736