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"