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"