File indexing completed on 2024-05-12 04:37:35
0001 /* 0002 SPDX-FileCopyrightText: 2008 Vladimir Prus <ghost@cs.msu.su> 0003 SPDX-FileCopyrightText: 2013 Vlas Puhov <vlas.puhov@mail.ru> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "breakpointwidget.h" 0009 0010 #include <QIcon> 0011 #include <QGroupBox> 0012 #include <QVBoxLayout> 0013 #include <QTreeView> 0014 #include <QHeaderView> 0015 #include <QMenu> 0016 #include <QContextMenuEvent> 0017 0018 #include <KLocalizedString> 0019 #include <KNotification> 0020 0021 #include "breakpointdetails.h" 0022 #include "../breakpoint/breakpoint.h" 0023 #include "../breakpoint/breakpointmodel.h" 0024 #include <debug.h> 0025 #include <interfaces/idebugcontroller.h> 0026 #include <util/autoorientedsplitter.h> 0027 0028 #define IF_DEBUG(x) 0029 #include <interfaces/icore.h> 0030 #include <interfaces/idocumentcontroller.h> 0031 #include <util/placeholderitemproxymodel.h> 0032 0033 using namespace KDevelop; 0034 0035 class KDevelop::BreakpointWidgetPrivate 0036 { 0037 public: 0038 explicit BreakpointWidgetPrivate(IDebugController *controller) 0039 : debugController(controller) 0040 { 0041 } 0042 0043 QTreeView* breakpointsView = nullptr; 0044 BreakpointDetails* details = nullptr; 0045 QMenu* popup = nullptr; 0046 bool firstShow = true; 0047 IDebugController* debugController; 0048 QAction* breakpointDisableAllAction = nullptr; 0049 QAction* breakpointEnableAllAction = nullptr; 0050 QAction* breakpointRemoveAll = nullptr; 0051 QAbstractProxyModel* proxyModel = nullptr; 0052 QMap<QString, size_t> breakpointErrorMessages; 0053 bool breakpointErrorMessageVisibile = false; 0054 }; 0055 0056 BreakpointWidget::BreakpointWidget(IDebugController *controller, QWidget *parent) 0057 : AutoOrientedSplitter(parent) 0058 , d_ptr(new BreakpointWidgetPrivate(controller)) 0059 { 0060 Q_D(BreakpointWidget); 0061 0062 setWindowTitle(i18nc("@title:window", "Debugger Breakpoints")); 0063 setWhatsThis(i18nc("@info:whatsthis", "Displays a list of breakpoints with " 0064 "their current status. Clicking on a " 0065 "breakpoint item allows you to change " 0066 "the breakpoint and will take you " 0067 "to the source in the editor window.")); 0068 setWindowIcon( QIcon::fromTheme( QStringLiteral( "media-playback-pause"), windowIcon() ) ); 0069 0070 d->breakpointsView = new QTreeView(this); 0071 d->breakpointsView->setSelectionBehavior(QAbstractItemView::SelectRows); 0072 d->breakpointsView->setSelectionMode(QAbstractItemView::SingleSelection); 0073 d->breakpointsView->setRootIsDecorated(false); 0074 0075 auto detailsContainer = new QGroupBox(i18n("Breakpoint Details"), this); 0076 auto detailsLayout = new QVBoxLayout(detailsContainer); 0077 d->details = new BreakpointDetails(detailsContainer); 0078 detailsLayout->addWidget(d->details); 0079 0080 setStretchFactor(0, 2); 0081 0082 auto* proxyModel = new PlaceholderItemProxyModel(this); 0083 proxyModel->setSourceModel(d->debugController->breakpointModel()); 0084 proxyModel->setColumnHint(Breakpoint::LocationColumn, i18n("New code breakpoint ...")); 0085 proxyModel->setColumnHint(Breakpoint::ConditionColumn, i18n("Enter condition ...")); 0086 d->breakpointsView->setModel(proxyModel); 0087 connect(proxyModel, &PlaceholderItemProxyModel::dataInserted, this, &BreakpointWidget::slotDataInserted); 0088 d->proxyModel = proxyModel; 0089 0090 connect(d->breakpointsView, &QTreeView::activated, this, &BreakpointWidget::slotOpenFile); 0091 connect(d->breakpointsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); 0092 connect(d->debugController->breakpointModel(), &BreakpointModel::rowsInserted, this, &BreakpointWidget::slotUpdateBreakpointDetail); 0093 connect(d->debugController->breakpointModel(), &BreakpointModel::rowsRemoved, this, &BreakpointWidget::slotUpdateBreakpointDetail); 0094 connect(d->debugController->breakpointModel(), &BreakpointModel::modelReset, this, &BreakpointWidget::slotUpdateBreakpointDetail); 0095 connect(d->debugController->breakpointModel(), &BreakpointModel::dataChanged, this, &BreakpointWidget::slotUpdateBreakpointDetail); 0096 0097 0098 connect(d->debugController->breakpointModel(), 0099 &BreakpointModel::hit, 0100 this, &BreakpointWidget::breakpointHit); 0101 0102 connect(d->debugController->breakpointModel(), 0103 &BreakpointModel::error, 0104 this, &BreakpointWidget::breakpointError); 0105 0106 setupPopupMenu(); 0107 } 0108 0109 BreakpointWidget::~BreakpointWidget() = default; 0110 0111 void BreakpointWidget::setupPopupMenu() 0112 { 0113 Q_D(BreakpointWidget); 0114 0115 d->popup = new QMenu(this); 0116 0117 QMenu* newBreakpoint = d->popup->addMenu(i18nc("New breakpoint", "&New")); 0118 newBreakpoint->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 0119 0120 QAction* action = newBreakpoint->addAction( 0121 i18nc("Code breakpoint", "&Code"), 0122 this, 0123 SLOT(slotAddBlankBreakpoint()) ); 0124 // Use this action also to provide a local shortcut 0125 action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_B, Qt::Key_C)); 0126 addAction(action); 0127 0128 newBreakpoint->addAction( 0129 i18nc("Data breakpoint", "Data &Write"), 0130 this, SLOT(slotAddBlankWatchpoint())); 0131 newBreakpoint->addAction( 0132 i18nc("Data read breakpoint", "Data &Read"), 0133 this, SLOT(slotAddBlankReadWatchpoint())); 0134 newBreakpoint->addAction( 0135 i18nc("Data access breakpoint", "Data &Access"), 0136 this, SLOT(slotAddBlankAccessWatchpoint())); 0137 0138 QAction* breakpointDelete = d->popup->addAction( 0139 QIcon::fromTheme(QStringLiteral("edit-delete")), 0140 i18n( "&Delete" ), 0141 this, 0142 SLOT(slotRemoveBreakpoint())); 0143 breakpointDelete->setShortcut(Qt::Key_Delete); 0144 breakpointDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0145 addAction(breakpointDelete); 0146 0147 0148 d->popup->addSeparator(); 0149 d->breakpointDisableAllAction = d->popup->addAction(i18n("Disable &All"), this, SLOT(slotDisableAllBreakpoints())); 0150 d->breakpointEnableAllAction = d->popup->addAction(i18n("&Enable All"), this, SLOT(slotEnableAllBreakpoints())); 0151 d->breakpointRemoveAll = d->popup->addAction(i18n("&Remove All"), this, SLOT(slotRemoveAllBreakpoints())); 0152 0153 connect(d->popup, &QMenu::aboutToShow, this, &BreakpointWidget::slotPopupMenuAboutToShow); 0154 } 0155 0156 0157 void BreakpointWidget::contextMenuEvent(QContextMenuEvent* event) 0158 { 0159 Q_D(BreakpointWidget); 0160 0161 d->popup->popup(event->globalPos()); 0162 } 0163 0164 void BreakpointWidget::slotPopupMenuAboutToShow() 0165 { 0166 Q_D(BreakpointWidget); 0167 0168 if (d->debugController->breakpointModel()->rowCount() < 1) { 0169 d->breakpointDisableAllAction->setDisabled(true); 0170 d->breakpointEnableAllAction->setDisabled(true); 0171 d->breakpointRemoveAll->setDisabled(true); 0172 } else { 0173 d->breakpointRemoveAll->setEnabled(true); 0174 bool allDisabled = true; 0175 bool allEnabled = true; 0176 for (int i = 0; i < d->debugController->breakpointModel()->rowCount(); ++i) { 0177 Breakpoint* bp = d->debugController->breakpointModel()->breakpoint(i); 0178 if (bp->enabled()) 0179 allDisabled = false; 0180 else 0181 allEnabled = false; 0182 } 0183 d->breakpointDisableAllAction->setDisabled(allDisabled); 0184 d->breakpointEnableAllAction->setDisabled(allEnabled); 0185 } 0186 0187 } 0188 0189 void BreakpointWidget::showEvent(QShowEvent *) 0190 { 0191 Q_D(BreakpointWidget); 0192 0193 if (d->firstShow && d->debugController->breakpointModel()->rowCount() > 0) { 0194 for (int i = 0; i < d->breakpointsView->model()->columnCount(); ++i) { 0195 if(i == Breakpoint::LocationColumn){ 0196 continue; 0197 } 0198 d->breakpointsView->resizeColumnToContents(i); 0199 } 0200 //for some reasons sometimes width can be very small about 200... But it doesn't matter as we use tooltip anyway. 0201 int width = d->breakpointsView->size().width(); 0202 0203 QHeaderView* header = d->breakpointsView->header(); 0204 header->resizeSection(Breakpoint::LocationColumn, width > 400 ? width/2 : header->sectionSize(Breakpoint::LocationColumn)*2 ); 0205 d->firstShow = false; 0206 } 0207 } 0208 0209 void BreakpointWidget::edit(KDevelop::Breakpoint *n) 0210 { 0211 Q_D(BreakpointWidget); 0212 0213 QModelIndex index = d->proxyModel->mapFromSource(d->debugController->breakpointModel()->breakpointIndex(n, Breakpoint::LocationColumn)); 0214 d->breakpointsView->setCurrentIndex(index); 0215 d->breakpointsView->edit(index); 0216 } 0217 0218 void BreakpointWidget::slotDataInserted(int column, const QVariant& value) 0219 { 0220 Q_D(BreakpointWidget); 0221 0222 Breakpoint* breakpoint = d->debugController->breakpointModel()->addCodeBreakpoint(); 0223 breakpoint->setData(column, value); 0224 } 0225 0226 void BreakpointWidget::slotAddBlankBreakpoint() 0227 { 0228 Q_D(BreakpointWidget); 0229 0230 edit(d->debugController->breakpointModel()->addCodeBreakpoint()); 0231 } 0232 0233 void BreakpointWidget::slotAddBlankWatchpoint() 0234 { 0235 Q_D(BreakpointWidget); 0236 0237 edit(d->debugController->breakpointModel()->addWatchpoint()); 0238 } 0239 0240 void BreakpointWidget::slotAddBlankReadWatchpoint() 0241 { 0242 Q_D(BreakpointWidget); 0243 0244 edit(d->debugController->breakpointModel()->addReadWatchpoint()); 0245 } 0246 0247 0248 void KDevelop::BreakpointWidget::slotAddBlankAccessWatchpoint() 0249 { 0250 Q_D(BreakpointWidget); 0251 0252 edit(d->debugController->breakpointModel()->addAccessWatchpoint()); 0253 } 0254 0255 0256 void BreakpointWidget::slotRemoveBreakpoint() 0257 { 0258 Q_D(BreakpointWidget); 0259 0260 QItemSelectionModel* sel = d->breakpointsView->selectionModel(); 0261 QModelIndexList selected = sel->selectedIndexes(); 0262 IF_DEBUG( qCDebug(DEBUGGER) << selected; ) 0263 if (!selected.isEmpty()) { 0264 d->debugController->breakpointModel()->removeRow(selected.first().row()); 0265 } 0266 } 0267 0268 void BreakpointWidget::slotRemoveAllBreakpoints() 0269 { 0270 Q_D(BreakpointWidget); 0271 0272 d->debugController->breakpointModel()->removeRows(0, d->debugController->breakpointModel()->rowCount()); 0273 } 0274 0275 0276 void BreakpointWidget::slotUpdateBreakpointDetail() 0277 { 0278 Q_D(BreakpointWidget); 0279 0280 showEvent(nullptr); 0281 QModelIndexList selected = d->breakpointsView->selectionModel()->selectedIndexes(); 0282 IF_DEBUG( qCDebug(DEBUGGER) << selected; ) 0283 if (selected.isEmpty()) { 0284 d->details->setItem(nullptr); 0285 } else { 0286 d->details->setItem(d->debugController->breakpointModel()->breakpoint(selected.first().row())); 0287 } 0288 } 0289 0290 void BreakpointWidget::breakpointHit(int row) 0291 { 0292 Q_D(BreakpointWidget); 0293 0294 const QModelIndex index = d->proxyModel->mapFromSource(d->debugController->breakpointModel()->index(row, 0)); 0295 d->breakpointsView->selectionModel()->select( 0296 index, 0297 QItemSelectionModel::Rows 0298 | QItemSelectionModel::ClearAndSelect); 0299 } 0300 0301 void BreakpointWidget::breakpointError(int row, const QString& msg) 0302 { 0303 Q_UNUSED(row); 0304 0305 Q_D(BreakpointWidget); 0306 0307 // if the error popup is still visible only queue the message, to avoid spam 0308 if (d->breakpointErrorMessageVisibile) { 0309 d->breakpointErrorMessages[msg]++; 0310 return; 0311 } 0312 0313 showBreakpointError(msg); 0314 } 0315 0316 void BreakpointWidget::breakpointErrorPopupClosed() 0317 { 0318 Q_D(BreakpointWidget); 0319 0320 d->breakpointErrorMessageVisibile = false; 0321 0322 if (d->breakpointErrorMessages.isEmpty()) { 0323 // no messages queued, do nothing 0324 return; 0325 } 0326 0327 QString errorMsg; 0328 0329 for (auto it = d->breakpointErrorMessages.constBegin(); it != d->breakpointErrorMessages.constEnd(); ++it) { 0330 if (!errorMsg.isEmpty()) { 0331 errorMsg += QLatin1Char('\n'); 0332 } 0333 errorMsg += i18np("%2", "%2 (repeated %1 times)", it.value(), it.key()); 0334 } 0335 0336 d->breakpointErrorMessages.clear(); 0337 0338 showBreakpointError(errorMsg); 0339 } 0340 0341 void BreakpointWidget::showBreakpointError(const QString& msg) 0342 { 0343 Q_D(BreakpointWidget); 0344 0345 auto * const errorPopup = new KNotification(QStringLiteral("BreakpointError")); 0346 connect(errorPopup, &KNotification::closed, this, &BreakpointWidget::breakpointErrorPopupClosed); 0347 errorPopup->setWidget(d->breakpointsView); 0348 errorPopup->setText(msg); 0349 errorPopup->sendEvent(); 0350 d->breakpointErrorMessageVisibile = true; 0351 } 0352 0353 void BreakpointWidget::slotOpenFile(const QModelIndex& breakpointIdx) 0354 { 0355 Q_D(BreakpointWidget); 0356 0357 if (breakpointIdx.column() != Breakpoint::LocationColumn){ 0358 return; 0359 } 0360 Breakpoint* bp = d->debugController->breakpointModel()->breakpoint(breakpointIdx.row()); 0361 if (!bp || bp->line() == -1 || bp->url().isEmpty() ){ 0362 return; 0363 } 0364 0365 ICore::self()->documentController()->openDocument(bp->url(), KTextEditor::Cursor(bp->line(), 0), IDocumentController::DoNotFocus); 0366 } 0367 0368 void BreakpointWidget::slotDisableAllBreakpoints() 0369 { 0370 Q_D(BreakpointWidget); 0371 0372 for (int i = 0; i < d->debugController->breakpointModel()->rowCount(); ++i) { 0373 Breakpoint* bp = d->debugController->breakpointModel()->breakpoint(i); 0374 bp->setData(Breakpoint::EnableColumn, Qt::Unchecked); 0375 } 0376 } 0377 0378 void BreakpointWidget::slotEnableAllBreakpoints() 0379 { 0380 Q_D(BreakpointWidget); 0381 0382 for (int i = 0; i < d->debugController->breakpointModel()->rowCount(); ++i) { 0383 Breakpoint* bp = d->debugController->breakpointModel()->breakpoint(i); 0384 bp->setData(Breakpoint::EnableColumn, Qt::Checked); 0385 } 0386 } 0387 0388 #include "moc_breakpointwidget.cpp"