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

0001 /*
0002     SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "variablewidget.h"
0009 
0010 #include <QApplication>
0011 #include <QAction>
0012 #include <QActionGroup>
0013 #include <QClipboard>
0014 #include <QContextMenuEvent>
0015 #include <QMenu>
0016 #include <QVBoxLayout>
0017 
0018 #include <KConfigGroup>
0019 #include <KHistoryComboBox>
0020 #include <KLocalizedString>
0021 #include <KSharedConfig>
0022 
0023 #include "../util/treemodel.h"
0024 #include "../../interfaces/icore.h"
0025 #include <interfaces/idebugcontroller.h>
0026 #include "../interfaces/ivariablecontroller.h"
0027 #include "variablecollection.h"
0028 #include "variablesortmodel.h"
0029 #include <debug.h>
0030 
0031 /** The variables widget is passive, and is invoked by the rest of the
0032     code via two main Q_SLOTS:
0033     - slotDbgStatus
0034     - slotCurrentFrame
0035 
0036     The first is received the program status changes and the second is
0037     received after current frame in the debugger can possibly changes.
0038 
0039     The widget has a list item for each frame/thread combination, with
0040     variables as children. However, at each moment only one item is shown.
0041     When handling the slotCurrentFrame, we check if variables for the
0042     current frame are available. If yes, we simply show the corresponding item.
0043     Otherwise, we fetch the new data from debugger.
0044 
0045     Fetching the data is done by emitting the produceVariablesInfo signal.
0046     In response, we get slotParametersReady and slotLocalsReady signal,
0047     in that order.
0048 
0049     The data is parsed and changed variables are highlighted. After that,
0050     we 'trim' variable items that were not reported by gdb -- that is, gone
0051     out of scope.
0052 */
0053 
0054 // **************************************************************************
0055 // **************************************************************************
0056 // **************************************************************************
0057 
0058 namespace {
0059 
0060 constexpr const char* autoResizeColumnsKey = "autoResizeColumns";
0061 
0062 auto variablesViewConfigGroup()
0063 {
0064     return KSharedConfig::openConfig()->group("Variables View");
0065 }
0066 
0067 }
0068 
0069 namespace KDevelop
0070 {
0071 
0072 VariableCollection *variableCollection()
0073 {
0074     return ICore::self()->debugController()->variableCollection();
0075 }
0076 
0077 VariableWidget::VariableWidget(IDebugController* controller, QWidget *parent)
0078 : QWidget(parent), m_variablesRoot(controller->variableCollection()->root())
0079 {
0080   //setWindowIcon(QIcon::fromTheme("math_brace"));
0081     setWindowIcon(QIcon::fromTheme(QStringLiteral("debugger"), windowIcon()));
0082     setWindowTitle(i18n("Debugger Variables"));
0083 
0084     m_varTree = new VariableTree(controller, this, new VariableSortProxyModel(this));
0085     setFocusProxy(m_varTree);
0086 
0087     m_watchVarEditor = new KHistoryComboBox( this );
0088 
0089     auto *topLayout = new QVBoxLayout(this);
0090     topLayout->addWidget(m_varTree, 10);
0091     topLayout->addWidget(m_watchVarEditor);
0092     topLayout->setContentsMargins(0, 0, 0, 0);
0093 
0094     connect(m_watchVarEditor, QOverload<const QString&>::of(&KHistoryComboBox::returnPressed),
0095             this, &VariableWidget::slotAddWatch);
0096 
0097     const bool autoResizeColumns = variablesViewConfigGroup().readEntry(autoResizeColumnsKey, true);
0098     m_varTree->setAutoResizeColumns(autoResizeColumns);
0099 
0100     auto* const autoResizeColumnsAction = new QAction(i18nc("@option:check", "Auto-resize columns on click"), this);
0101     autoResizeColumnsAction->setIcon(QIcon::fromTheme(QStringLiteral("resizecol")));
0102     autoResizeColumnsAction->setCheckable(true);
0103     autoResizeColumnsAction->setChecked(autoResizeColumns);
0104     connect(autoResizeColumnsAction, &QAction::triggered, this, [this](bool on) {
0105         m_varTree->setAutoResizeColumns(on);
0106         variablesViewConfigGroup().writeEntry(autoResizeColumnsKey, on);
0107     });
0108     addAction(autoResizeColumnsAction);
0109 
0110     //TODO
0111     //connect(plugin, SIGNAL(raiseVariableViews()), this, SIGNAL(requestRaise()));
0112 
0113     // Setup help items.
0114 
0115     setWhatsThis( i18n(
0116         "<b>Variable tree</b>"
0117         "The variable tree allows you to see the values of local "
0118         "variables and arbitrary expressions.<br />"
0119         "Local variables are displayed automatically and are updated "
0120         "as you step through your program. "
0121         "For each expression you enter, you can either evaluate it once, "
0122         "or \"watch\" it (make it auto-updated). Expressions that are not "
0123         "auto-updated can be updated manually from the context menu. "
0124         "Expressions can be renamed to more descriptive names by clicking "
0125         "on the name column.<br />"
0126         "To change the value of a variable or an expression, "
0127         "click on the value.<br />"));
0128 
0129     m_watchVarEditor->setWhatsThis(
0130                     i18n("<b>Expression entry</b>"
0131                          "Type in expression to watch."));
0132 
0133 }
0134 
0135 void VariableWidget::slotAddWatch(const QString &expression)
0136 {
0137     if (!expression.isEmpty())
0138     {
0139         m_watchVarEditor->addToHistory(expression);
0140         qCDebug(DEBUGGER) << "Trying to add watch";
0141         Variable* v = m_variablesRoot->watches()->add(expression);
0142         if (v) {
0143             /* For watches on structures, we really do want them to be shown
0144             expanded.  Except maybe for structure with custom pretty printing,
0145             but will handle that later.
0146             FIXME: it does not actually works now.
0147             */
0148             //QModelIndex index = variableCollection()->indexForItem(v, 0);
0149             //varTree_->setExpanded(index, true);
0150         }
0151         m_watchVarEditor->clearEditText();
0152     }
0153 }
0154 
0155 void VariableWidget::hideEvent(QHideEvent* e)
0156 {
0157     QWidget::hideEvent(e);
0158     variableCollection()->variableWidgetHidden();
0159 }
0160 
0161 void VariableWidget::showEvent(QShowEvent* e)
0162 {
0163     QWidget::showEvent(e);
0164     variableCollection()->variableWidgetShown();
0165 }
0166 
0167 // **************************************************************************
0168 // **************************************************************************
0169 // **************************************************************************
0170 
0171 VariableTree::VariableTree(IDebugController* controller, VariableWidget* parent, QSortFilterProxyModel* proxy)
0172     : AsyncTreeView(*controller->variableCollection(), parent)
0173     , m_proxy(proxy)
0174 #if 0
0175 ,
0176       activePopup_(0),
0177       toggleWatch_(0)
0178 #endif
0179 {
0180     setRootIsDecorated(true);
0181     setAllColumnsShowFocus(true);
0182 
0183     // setting proxy model
0184     m_proxy->setSourceModel(&treeModel());
0185     setModel(m_proxy);
0186     setSortingEnabled(true);
0187     sortByColumn(VariableCollection::NameColumn, Qt::AscendingOrder);
0188 
0189     QModelIndex index = controller->variableCollection()->indexForItem(
0190         controller->variableCollection()->watches(), 0);
0191     setExpanded(index, true);
0192 
0193     setupActions();
0194 }
0195 
0196 VariableTree::~VariableTree()
0197 {
0198 }
0199 
0200 void VariableTree::setupActions()
0201 {
0202     // TODO decorate this properly to make nice menu title
0203     m_contextMenuTitle = new QAction(this);
0204     m_contextMenuTitle->setEnabled(false);
0205 
0206     // make Format menu action group
0207     m_formatMenu = new QMenu(i18n("&Format"), this);
0208     auto *ag= new QActionGroup(m_formatMenu);
0209 
0210     QAction* act;
0211 
0212     act = new QAction(i18n("&Natural"), ag);
0213     act->setData(Variable::Natural);
0214     act->setShortcut(Qt::Key_N);
0215     m_formatMenu->addAction(act);
0216 
0217     act = new QAction(i18n("&Binary"), ag);
0218     act->setData(Variable::Binary);
0219     act->setShortcut(Qt::Key_B);
0220     m_formatMenu->addAction(act);
0221 
0222     act = new QAction(i18n("&Octal"), ag);
0223     act->setData(Variable::Octal);
0224     act->setShortcut(Qt::Key_O);
0225     m_formatMenu->addAction(act);
0226 
0227     act = new QAction(i18n("&Decimal"), ag);
0228     act->setData(Variable::Decimal);
0229     act->setShortcut(Qt::Key_D);
0230     m_formatMenu->addAction(act);
0231 
0232     act = new QAction(i18n("&Hexadecimal"), ag);
0233     act->setData(Variable::Hexadecimal);
0234     act->setShortcut(Qt::Key_H);
0235     m_formatMenu->addAction(act);
0236 
0237     const auto formatMenuActions = m_formatMenu->actions();
0238     for (QAction* act : formatMenuActions) {
0239         act->setCheckable(true);
0240         act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0241         const int id = act->data().toInt();
0242         connect(act, &QAction::triggered, this, [this, id](){ changeVariableFormat(id); });
0243         addAction(act);
0244     }
0245 
0246     m_watchDelete = new QAction(
0247         QIcon::fromTheme(QStringLiteral("edit-delete")), i18n( "Remove Watch Variable" ), this);
0248 
0249     m_watchDelete->setShortcut(Qt::Key_Delete);
0250     m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0251     addAction(m_watchDelete);
0252     connect(m_watchDelete, &QAction::triggered, this, &VariableTree::watchDelete);
0253 
0254     m_copyVariableValue = new QAction(i18n("&Copy Value"), this);
0255     m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0256     m_copyVariableValue->setShortcut(QKeySequence::Copy);
0257     connect(m_copyVariableValue, &QAction::triggered, this, &VariableTree::copyVariableValue);
0258 
0259     m_stopOnChange = new QAction(i18n("&Stop on Change"), this);
0260     connect(m_stopOnChange, &QAction::triggered, this, &VariableTree::stopOnChange);
0261 }
0262 
0263 Variable* VariableTree::selectedVariable() const
0264 {
0265     if (selectionModel()->selectedRows().isEmpty()) return nullptr;
0266     auto item = selectionModel()->currentIndex().data(TreeModel::ItemRole).value<TreeItem*>();
0267     if (!item) return nullptr;
0268     return qobject_cast<Variable*>(item);
0269 }
0270 
0271 void VariableTree::contextMenuEvent(QContextMenuEvent* event)
0272 {
0273     if (!selectedVariable()) return;
0274 
0275     // set up menu
0276     QMenu contextMenu(this->parentWidget());
0277     m_contextMenuTitle->setText(selectedVariable()->expression());
0278     contextMenu.addAction(m_contextMenuTitle);
0279 
0280     if(selectedVariable()->canSetFormat())
0281         contextMenu.addMenu(m_formatMenu);
0282 
0283     const auto formatMenuActions = m_formatMenu->actions();
0284     for (QAction* act : formatMenuActions) {
0285         if(act->data().toInt()==selectedVariable()->format())
0286             act->setChecked(true);
0287     }
0288 
0289     if (qobject_cast<Watches*>(selectedVariable()->parent())) {
0290         contextMenu.addAction(m_watchDelete);
0291     }
0292 
0293     contextMenu.addSeparator();
0294     contextMenu.addAction(m_copyVariableValue);
0295     contextMenu.addAction(m_stopOnChange);
0296 
0297     contextMenu.exec(event->globalPos());
0298 }
0299 
0300 QModelIndex VariableTree::mapViewIndexToTreeModelIndex(const QModelIndex& viewIndex) const
0301 {
0302     return m_proxy->mapToSource(viewIndex);
0303 }
0304 
0305 void VariableTree::changeVariableFormat(int format)
0306 {
0307     if (!selectedVariable()) return;
0308     selectedVariable()->setFormat(static_cast<Variable::format_t>(format));
0309 }
0310 
0311 void VariableTree::watchDelete()
0312 {
0313     if (!selectedVariable()) return;
0314     if (!qobject_cast<Watches*>(selectedVariable()->parent())) return;
0315     selectedVariable()->die();
0316 }
0317 
0318 void VariableTree::copyVariableValue()
0319 {
0320     if (!selectedVariable()) return;
0321     QApplication::clipboard()->setText(selectedVariable()->value());
0322 }
0323 
0324 void VariableTree::stopOnChange()
0325 {
0326     if (!selectedVariable()) return;
0327     IDebugSession *session = ICore::self()->debugController()->currentSession();
0328     if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) {
0329         session->variableController()->addWatchpoint(selectedVariable());
0330     }
0331 }
0332 }
0333 
0334 #include "moc_variablewidget.cpp"