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

0001 /*
0002     SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0004     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0005     SPDX-FileCopyrightText: 2009 Aleix Pol <aleixpol@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "framestackwidget.h"
0011 
0012 #include <QHeaderView>
0013 #include <QScrollBar>
0014 #include <QListView>
0015 #include <QItemDelegate>
0016 #include <QVBoxLayout>
0017 #include <QLabel>
0018 #include <QTreeView>
0019 #include <QMenu>
0020 #include <QApplication>
0021 #include <QClipboard>
0022 #include <QIcon>
0023 #include <QAction>
0024 
0025 #include <KStandardAction>
0026 #include <KLocalizedString>
0027 #include <KTextEditor/Cursor>
0028 
0029 #include <interfaces/icore.h>
0030 #include <interfaces/idebugcontroller.h>
0031 #include <interfaces/idocumentcontroller.h>
0032 #include <debug.h>
0033 #include "framestackmodel.h"
0034 
0035 namespace KDevelop {
0036 
0037 class FrameStackItemDelegate : public QItemDelegate
0038 {
0039     Q_OBJECT
0040 
0041 public:
0042     using QItemDelegate::QItemDelegate;
0043 
0044     void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
0045 };
0046 
0047 void FrameStackItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
0048                                         const QModelIndex& index) const
0049 {
0050     QStyleOptionViewItem newOption(option);
0051     newOption.textElideMode = index.column() == 2 ? Qt::ElideMiddle : Qt::ElideRight;
0052 
0053     QItemDelegate::paint(painter, newOption, index);
0054 }
0055 
0056 FramestackWidget::FramestackWidget(IDebugController* controller, QWidget* parent)
0057     : AutoOrientedSplitter(Qt::Horizontal, parent), m_session(nullptr)
0058 {
0059     connect(controller,
0060             &IDebugController::currentSessionChanged,
0061             this, &FramestackWidget::currentSessionChanged);
0062 
0063     //TODO: shouldn't this signal be in IDebugController? Otherwise we are effectively depending on it being a DebugController here
0064     connect(controller, SIGNAL(raiseFramestackViews()), SIGNAL(requestRaise()));
0065 
0066     setWhatsThis(i18n("<b>Frame stack</b>"
0067                       "Often referred to as the \"call stack\", "
0068                       "this is a list showing which function is "
0069                       "currently active, and what called each "
0070                       "function to get to this point in your "
0071                       "program. By clicking on an item you "
0072                       "can see the values in any of the "
0073                       "previous calling functions."));
0074     setWindowIcon(QIcon::fromTheme(QStringLiteral("view-list-text"), windowIcon()));
0075     m_threadsWidget = new QWidget(this);
0076     m_threadsListView = new QListView(m_threadsWidget);
0077     m_framesTreeView = new QTreeView(this);
0078     m_framesTreeView->setRootIsDecorated(false);
0079     m_framesTreeView->setItemDelegate(new FrameStackItemDelegate(this));
0080     m_framesTreeView->setSelectionMode(QAbstractItemView::ContiguousSelection);
0081     m_framesTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
0082     m_framesTreeView->setAllColumnsShowFocus(true);
0083     m_framesTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
0084 
0085     m_framesContextMenu = new QMenu(m_framesTreeView);
0086 
0087     QAction* selectAllAction = KStandardAction::selectAll(m_framesTreeView);
0088     selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ?
0089     selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0090     connect(selectAllAction, &QAction::triggered, this, &FramestackWidget::selectAll);
0091     m_framesContextMenu->addAction(selectAllAction);
0092 
0093     QAction* copyAction = KStandardAction::copy(m_framesTreeView);
0094     copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0095     connect(copyAction, &QAction::triggered, this, &FramestackWidget::copySelection);
0096     m_framesContextMenu->addAction(copyAction);
0097     addAction(copyAction);
0098 
0099     connect(m_framesTreeView, &QTreeView::customContextMenuRequested, this, &FramestackWidget::frameContextMenuRequested);
0100 
0101     m_threadsWidget->setLayout(new QVBoxLayout());
0102     m_threadsWidget->layout()->addWidget(new QLabel(i18n("Threads:")));
0103     m_threadsWidget->layout()->addWidget(m_threadsListView);
0104     addWidget(m_threadsWidget);
0105     addWidget(m_framesTreeView);
0106 
0107     setStretchFactor(1, 3);
0108     connect(m_framesTreeView->verticalScrollBar(), &QScrollBar::valueChanged, this, &FramestackWidget::checkFetchMoreFrames);
0109 
0110     // Show the selected frame when clicked, even if it has previously been selected
0111     connect(m_framesTreeView, &QTreeView::clicked, this, &FramestackWidget::frameSelectionChanged);
0112 
0113     currentSessionChanged(controller->currentSession());
0114 }
0115 
0116 FramestackWidget::~FramestackWidget()
0117 {
0118 }
0119 
0120 void FramestackWidget::currentSessionChanged(KDevelop::IDebugSession* session)
0121 {
0122     m_session = session;
0123 
0124     m_threadsListView->setModel(session ? session->frameStackModel() : nullptr);
0125     m_framesTreeView->setModel(session ? session->frameStackModel() : nullptr);
0126 
0127     if (session) {
0128         connect(session->frameStackModel(), &IFrameStackModel::dataChanged,
0129                 this, &FramestackWidget::checkFetchMoreFrames);
0130         connect(session->frameStackModel(), &IFrameStackModel::currentThreadChanged,
0131                 this, &FramestackWidget::currentThreadChanged);
0132         currentThreadChanged(session->frameStackModel()->currentThread());
0133         connect(session->frameStackModel(), &IFrameStackModel::currentFrameChanged,
0134                 this, &FramestackWidget::currentFrameChanged);
0135         currentFrameChanged(session->frameStackModel()->currentFrame());
0136         connect(session, &IDebugSession::stateChanged,
0137                 this, &FramestackWidget::sessionStateChanged);
0138 
0139         connect(m_threadsListView->selectionModel(), &QItemSelectionModel::currentChanged,
0140                 this, &FramestackWidget::setThreadShown);
0141 
0142         // Show the selected frame, independent of the means by which it has been selected
0143         connect(m_framesTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
0144                 this, &FramestackWidget::frameSelectionChanged);
0145 
0146         sessionStateChanged(session->state());
0147     }
0148 }
0149 
0150 void FramestackWidget::setThreadShown(const QModelIndex& current)
0151 {
0152     if (!current.isValid())
0153         return;
0154     m_session->frameStackModel()->setCurrentThread(current);
0155 }
0156 
0157 void FramestackWidget::checkFetchMoreFrames()
0158 {
0159     int val = m_framesTreeView->verticalScrollBar()->value();
0160     int max = m_framesTreeView->verticalScrollBar()->maximum();
0161     const int offset = 20;
0162 
0163     if (val + offset > max && m_session) {
0164         m_session->frameStackModel()->fetchMoreFrames();
0165     }
0166 }
0167 
0168 void FramestackWidget::currentThreadChanged(int thread)
0169 {
0170     if (thread != -1) {
0171         IFrameStackModel* model = m_session->frameStackModel();
0172         QModelIndex idx = model->currentThreadIndex();
0173         m_threadsListView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0174         m_threadsWidget->setVisible(model->rowCount() > 1);
0175         m_framesTreeView->setRootIndex(idx);
0176         m_framesTreeView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
0177     } else {
0178         m_threadsWidget->hide();
0179         m_threadsListView->selectionModel()->clear();
0180         m_framesTreeView->setRootIndex(QModelIndex());
0181     }
0182 }
0183 
0184 void FramestackWidget::currentFrameChanged(int frame)
0185 {
0186     if (frame != -1) {
0187         IFrameStackModel* model = m_session->frameStackModel();
0188         QModelIndex idx = model->currentFrameIndex();
0189         m_framesTreeView->selectionModel()->select(
0190             idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0191     } else {
0192         m_framesTreeView->selectionModel()->clear();
0193     }
0194 }
0195 
0196 void FramestackWidget::frameSelectionChanged(const QModelIndex& current /* previous */)
0197 {
0198     if (!current.isValid())
0199         return;
0200     IFrameStackModel::FrameItem f = m_session->frameStackModel()->frame(current);
0201     /* If line is -1, then it's not a source file at all.  */
0202     if (f.line != -1) {
0203         QPair<QUrl, int> file = m_session->convertToLocalUrl(qMakePair(f.file, f.line));
0204         ICore::self()->documentController()->openDocument(file.first, KTextEditor::Cursor(file.second, 0), IDocumentController::DoNotFocus);
0205     }
0206 
0207     m_session->frameStackModel()->setCurrentFrame(f.nr);
0208 }
0209 
0210 void FramestackWidget::frameContextMenuRequested(const QPoint& pos)
0211 {
0212     m_framesContextMenu->popup(m_framesTreeView->viewport()->mapToGlobal(pos));
0213 }
0214 
0215 void FramestackWidget::copySelection()
0216 {
0217     QClipboard *cb = QApplication::clipboard();
0218     const QModelIndexList indexes = m_framesTreeView->selectionModel()->selectedRows();
0219     QString content;
0220     for (const QModelIndex& index : indexes) {
0221         IFrameStackModel::FrameItem frame = m_session->frameStackModel()->frame(index);
0222         if (frame.line == -1) {
0223             content += i18nc("#frame function() at file", "#%1 %2() at %3\n",
0224                 frame.nr, frame.name, frame.file.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash));
0225         } else {
0226             content += i18nc("#frame function() at file:line", "#%1 %2() at %3:%4\n",
0227                 frame.nr, frame.name, frame.file.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash), frame.line+1);
0228         }
0229     }
0230     cb->setText(content);
0231 }
0232 
0233 void FramestackWidget::selectAll()
0234 {
0235     m_framesTreeView->selectAll();
0236 }
0237 
0238 void FramestackWidget::sessionStateChanged(KDevelop::IDebugSession::DebuggerState state)
0239 {
0240     bool enable = state == IDebugSession::PausedState || state == IDebugSession::StoppedState;
0241     setEnabled(enable);
0242 }
0243 
0244 }
0245 
0246 #include "framestackwidget.moc"
0247 #include "moc_framestackwidget.cpp"