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