Commit f0b2d653 authored by hjk's avatar hjk

Debugger: Rework editor tooltips handling

Fix expansion and updating.

Persistence and non-locals are still lacking.

Change-Id: I74e25199d50350516afc686a05836e239bfc8acb
Reviewed-by: default avatarJoerg Bornemann <joerg.bornemann@digia.com>
Reviewed-by: default avatarhjk <hjk121@nokiamail.com>
parent a97aa6be
......@@ -1968,8 +1968,10 @@ void CdbEngine::handleLocals(const CdbExtensionCommandPtr &reply)
foreach (const WatchData &wd, watchData)
nsp << wd.toString() <<'\n';
}
if (flags & LocalsUpdateForNewFrame)
if (flags & LocalsUpdateForNewFrame) {
emit stackFrameCompleted();
DebuggerToolTipManager::updateEngine(this);
}
} else {
showMessage(QString::fromLatin1(reply->errorMessage), LogWarning);
}
......
......@@ -60,7 +60,6 @@ class BreakHandler;
class SnapshotHandler;
class Symbol;
class Section;
class DebuggerToolTipManager;
class GlobalDebuggerOptions;
enum TestCases
......@@ -120,7 +119,6 @@ public:
virtual QStringList stringListSetting(int code) const = 0;
virtual void setThreads(const QStringList &list, int index) = 0;
virtual DebuggerToolTipManager *toolTipManager() const = 0;
virtual QSharedPointer<GlobalDebuggerOptions> globalDebuggerOptions() const = 0;
static QTreeView *inspectorView();
......
......@@ -35,6 +35,7 @@
#include "debuggerrunner.h"
#include "debuggerstringutils.h"
#include "debuggerstartparameters.h"
#include "debuggertooltipmanager.h"
#include "breakhandler.h"
#include "disassembleragent.h"
......@@ -1176,11 +1177,16 @@ void DebuggerEngine::setState(DebuggerState state, bool forced)
if (!forced && !isAllowedTransition(oldState, state))
qDebug() << "*** UNEXPECTED STATE TRANSITION: " << this << msg;
if (state == EngineRunRequested) {
DebuggerToolTipManager::registerEngine(this);
}
if (state == DebuggerFinished) {
// Give up ownership on claimed breakpoints.
BreakHandler *handler = breakHandler();
foreach (BreakpointModelId id, handler->engineBreakpointIds(this))
handler->notifyBreakpointReleased(id);
DebuggerToolTipManager::deregisterEngine(this);
}
showMessage(msg, LogDebug);
......
......@@ -1202,7 +1202,6 @@ public slots:
bool parseArguments(const QStringList &args, QString *errorMessage);
void parseCommandLineArguments();
DebuggerToolTipManager *toolTipManager() const { return m_toolTipManager; }
QSharedPointer<GlobalDebuggerOptions> globalDebuggerOptions() const { return m_globalDebuggerOptions; }
void updateQmlActions() {
......@@ -2526,7 +2525,7 @@ void DebuggerPluginPrivate::sessionLoaded()
{
m_breakHandler->loadSessionData();
dummyEngine()->watchHandler()->loadSessionData();
m_toolTipManager->loadSessionData();
DebuggerToolTipManager::loadSessionData();
}
void DebuggerPluginPrivate::aboutToUnloadSession()
......@@ -2537,8 +2536,8 @@ void DebuggerPluginPrivate::aboutToUnloadSession()
void DebuggerPluginPrivate::aboutToSaveSession()
{
dummyEngine()->watchHandler()->saveSessionData();
m_toolTipManager->saveSessionData();
m_breakHandler->saveSessionData();
DebuggerToolTipManager::saveSessionData();
}
void DebuggerPluginPrivate::showStatusMessage(const QString &msg0, int timeout)
......
......@@ -38,7 +38,6 @@
#include "debuggerrunconfigurationaspect.h"
#include "debuggerstartparameters.h"
#include "debuggerstringutils.h"
#include "debuggertooltipmanager.h"
#include "breakhandler.h"
#include "shared/peutils.h"
......@@ -136,9 +135,7 @@ DebuggerRunControl::DebuggerRunControl(RunConfiguration *runConfiguration,
QString errorMessage;
d->m_engine = DebuggerRunControlFactory::createEngine(sp.masterEngineType, sp, &errorMessage);
if (d->m_engine) {
DebuggerToolTipManager::registerEngine(d->m_engine);
} else {
if (!d->m_engine) {
debuggingFinished();
Core::ICore::showWarningWithOptions(DebuggerRunControl::tr("Debugger"), errorMessage);
}
......
......@@ -33,6 +33,9 @@
#include "debuggeractions.h"
#include "stackhandler.h"
#include "debuggercore.h"
#include "watchhandler.h"
#include "watchwindow.h"
#include "sourceutils.h"
#include <coreplugin/icore.h>
#include <coreplugin/coreconstants.h>
......@@ -61,9 +64,6 @@
using namespace Core;
using namespace TextEditor;
enum { debugToolTips = 0 };
enum { debugToolTipPositioning = 0 };
// Expire tooltips after n days on (no longer load them) in order
// to avoid them piling up.
enum { toolTipsExpiryDays = 6 };
......@@ -94,9 +94,6 @@ static const char modelItemElementC[] = "item";
// next start element of a desired type.
static bool readStartElement(QXmlStreamReader &r, const char *name)
{
if (debugToolTips > 1)
qDebug("readStartElement: looking for '%s', currently at: %s/%s",
name, qPrintable(r.tokenString()), qPrintable(r.name().toString()));
while (r.tokenType() != QXmlStreamReader::StartElement
|| r.name() != QLatin1String(name))
switch (r.readNext()) {
......@@ -127,38 +124,29 @@ static void debugMode(const QAbstractItemModel *model)
namespace Debugger {
namespace Internal {
/* A Label that emits a signal when the user drags for moving the parent
* widget around. */
// A label that can be dragged to drag something else.
class DraggableLabel : public QLabel
{
Q_OBJECT
public:
explicit DraggableLabel(QWidget *parent = 0);
bool isActive() const { return m_active; }
void setActive(bool v) { m_active = v; }
explicit DraggableLabel(QWidget *target)
: m_target(target), m_moveStartPos(-1, -1), active(false)
{}
signals:
void dragged(const QPoint &d);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
protected:
virtual void mousePressEvent(QMouseEvent * event);
virtual void mouseReleaseEvent(QMouseEvent * event);
virtual void mouseMoveEvent(QMouseEvent * event);
private:
public:
QWidget *m_target;
QPoint m_moveStartPos;
bool m_active;
QPoint m_offset;
bool active;
};
DraggableLabel::DraggableLabel(QWidget *parent) :
QLabel(parent), m_moveStartPos(-1, -1), m_active(false)
{
}
void DraggableLabel::mousePressEvent(QMouseEvent * event)
{
if (m_active && event->button() == Qt::LeftButton) {
if (active && event->button() == Qt::LeftButton) {
m_moveStartPos = event->globalPos();
event->accept();
}
......@@ -167,17 +155,21 @@ void DraggableLabel::mousePressEvent(QMouseEvent * event)
void DraggableLabel::mouseReleaseEvent(QMouseEvent * event)
{
if (m_active && event->button() == Qt::LeftButton)
if (active && event->button() == Qt::LeftButton)
m_moveStartPos = QPoint(-1, -1);
QLabel::mouseReleaseEvent(event);
}
void DraggableLabel::mouseMoveEvent(QMouseEvent * event)
{
if (m_active && (event->buttons() & Qt::LeftButton)) {
if (active && (event->buttons() & Qt::LeftButton)) {
if (m_moveStartPos != QPoint(-1, -1)) {
const QPoint newPos = event->globalPos();
emit dragged(event->globalPos() - m_moveStartPos);
const QPoint offset = newPos - m_moveStartPos;
m_target->move(m_target->pos() + offset);
m_offset += offset;
m_moveStartPos = newPos;
}
event->accept();
......@@ -191,7 +183,7 @@ void DraggableLabel::mouseMoveEvent(QMouseEvent * event)
class DebuggerToolTipEditor
{
public:
explicit DebuggerToolTipEditor(IEditor *ie = 0);
explicit DebuggerToolTipEditor(IEditor *ie);
bool isValid() const { return editor; }
QString fileName() const { return editor->document() ? editor->document()->filePath() : QString(); }
......@@ -274,8 +266,8 @@ void StandardItemTreeModelBuilder::endRow()
m_rowParents.pop();
}
/* Helper visitor base class for recursing over a tree model
* (see StandardItemTreeModelBuilder for the scheme). */
// Helper visitor base class for recursing over a tree model
// (see StandardItemTreeModelBuilder for the scheme).
class TreeModelVisitor
{
public:
......@@ -455,11 +447,7 @@ static QDebug operator<<(QDebug d, const QAbstractItemModel &model)
class TooltipFilterModel : public QSortFilterProxyModel
{
public:
TooltipFilterModel(QAbstractItemModel *model, const QByteArray &iname)
: m_iname(iname)
{
setSourceModel(model);
}
TooltipFilterModel() {}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
......@@ -478,11 +466,12 @@ public:
{
const QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent);
const QByteArray iname = nameIndex.data(LocalsINameRole).toByteArray();
// qDebug() << "ACCEPTING FILTER" << iname
// << (iname == m_iname || isSubIname(iname, m_iname) || isSubIname(m_iname, iname));
return iname == m_iname || isSubIname(iname, m_iname) || isSubIname(m_iname, iname);
}
private:
const QByteArray m_iname;
QByteArray m_iname;
};
/////////////////////////////////////////////////////////////////////////
......@@ -507,6 +496,29 @@ private:
StandardItemTreeModelBuilder m_builder;
};
class DebuggerToolTipWidget;
class DebuggerToolTipManagerData
{
public:
DebuggerToolTipManagerData()
: m_debugModeActive(false), m_lastToolTipPoint(-1, -1), m_lastToolTipEditor(0)
{}
void purgeClosedToolTips()
{
for (int i = m_tooltips.size(); --i >= 0; )
if (!m_tooltips.at(i))
m_tooltips.removeAt(i);
}
QList<QPointer<DebuggerToolTipWidget> > m_tooltips;
bool m_debugModeActive;
QPoint m_lastToolTipPoint;
Core::IEditor *m_lastToolTipEditor;
};
static DebuggerToolTipManagerData *d = 0;
/////////////////////////////////////////////////////////////////////////
//
......@@ -519,59 +531,46 @@ class DebuggerToolTipWidget : public QWidget
Q_OBJECT
public:
bool isPinned() const { return m_isPinned; }
explicit DebuggerToolTipWidget(QWidget *parent = 0);
bool engineAcquired() const { return m_engineAcquired; }
DebuggerToolTipWidget(const DebuggerToolTipContext &context);
bool isPinned() const { return m_isPinned; }
QString fileName() const { return m_context.fileName; }
QString function() const { return m_context.function; }
int position() const { return m_context.position; }
// Check for a match at position.
bool matches(const QString &fileName,
const QString &engineType = QString(),
const QString &function= QString()) const;
const DebuggerToolTipContext &context() const { return m_context; }
void setContext(const DebuggerToolTipContext &c) { m_context = c; }
QString engineType() const { return m_engineType; }
void setEngineType(const QString &e) { m_engineType = e; }
void acquireEngine();
void releaseEngine();
QDate creationDate() const { return m_creationDate; }
void setCreationDate(const QDate &d) { m_creationDate = d; }
void saveSessionData(QXmlStreamWriter &w) const;
void setWatchModel(QAbstractItemModel *watchModel);
void handleStackFrameCompleted(const QString &frameFile, const QString &frameFunction);
public slots:
void saveSessionData(QXmlStreamWriter &w) const;
void handleItemIsExpanded(const QModelIndex &sourceIdx)
{
QTC_ASSERT(m_filterModel.sourceModel() == sourceIdx.model(), return);
QModelIndex mappedIdx = m_filterModel.mapFromSource(sourceIdx);
if (!m_treeView->isExpanded(mappedIdx))
m_treeView->expand(mappedIdx);
}
void acquireEngine(Debugger::DebuggerEngine *engine);
void releaseEngine();
void copy();
bool positionShow(const DebuggerToolTipEditor &pe);
void positionShow(const DebuggerToolTipEditor &pe);
void pin();
void doLoadSessionData(QXmlStreamReader &r);
private slots:
void slotDragged(const QPoint &p);
void toolButtonClicked();
private:
void doReleaseEngine();
void doSaveSessionData(QXmlStreamWriter &w) const;
QString clipboardContents() const;
QAbstractItemModel *swapModel(QAbstractItemModel *newModel);
public:
bool m_isPinned;
QToolButton *m_toolButton;
DraggableLabel *m_titleLabel;
bool m_engineAcquired;
QString m_engineType;
DebuggerToolTipContext m_context;
QDate m_creationDate;
QPoint m_offset; //!< Offset to text cursor position (user dragging).
int m_debuggerModel;
DebuggerToolTipTreeView *m_treeView;
QStandardItemModel *m_defaultModel;
DebuggerToolTipTreeView *m_treeView; //!< Pointing to either m_defaultModel oder m_filterModel
DebuggerToolTipContext m_context;
TooltipFilterModel m_filterModel; //!< Pointing to a valid watchModel
QStandardItemModel m_defaultModel;
};
void DebuggerToolTipWidget::pin()
......@@ -589,7 +588,7 @@ void DebuggerToolTipWidget::pin()
// We have just be restored from session data.
setWindowFlags(Qt::ToolTip);
}
m_titleLabel->setActive(true); // User can now drag
m_titleLabel->active = true; // User can now drag
}
void DebuggerToolTipWidget::toolButtonClicked()
......@@ -611,27 +610,28 @@ void DebuggerToolTipWidget::toolButtonClicked()
on restoring.
*/
DebuggerToolTipContext::DebuggerToolTipContext() : position(0), line(0), column(0)
DebuggerToolTipContext::DebuggerToolTipContext()
: position(0), line(0), column(0)
{
}
DebuggerToolTipContext DebuggerToolTipContext::fromEditor(IEditor *ie, int pos)
bool DebuggerToolTipContext::matchesFrame(const QString &frameFile, const QString &frameFunction) const
{
DebuggerToolTipContext rc;
if (const IDocument *document = ie->document()) {
if (const ITextEditor *te = qobject_cast<const ITextEditor *>(ie)) {
rc.fileName = document->filePath();
rc.position = pos;
te->convertPosition(pos, &rc.line, &rc.column);
}
}
return rc;
return (fileName.isEmpty() || frameFile.isEmpty() || fileName == frameFile)
&& (function.isEmpty() || frameFunction.isEmpty() || function == frameFunction);
}
bool DebuggerToolTipContext::isSame(const DebuggerToolTipContext &other) const
{
return fileName == other.fileName
&& function == other.function
&& iname == other.iname;
}
QDebug operator<<(QDebug d, const DebuggerToolTipContext &c)
{
QDebug nsp = d.nospace();
nsp << c.fileName << '@' << c.line << ',' << c.column << " (" << c.position << ')';
nsp << c.fileName << '@' << c.line << ',' << c.column << " (" << c.position << ')' << "INAME: " << c.iname << " EXP: " << c.expression;
if (!c.function.isEmpty())
nsp << ' ' << c.function << "()";
return d;
......@@ -678,146 +678,189 @@ QDebug operator<<(QDebug d, const DebuggerToolTipContext &c)
static QString msgReleasedText() { return DebuggerToolTipWidget::tr("Previous"); }
DebuggerToolTipWidget::DebuggerToolTipWidget(QWidget *parent) :
QWidget(parent),
m_isPinned(false),
m_toolButton(new QToolButton),
m_titleLabel(new DraggableLabel),
m_engineAcquired(false),
m_creationDate(QDate::currentDate()),
m_treeView(new DebuggerToolTipTreeView(this)),
m_defaultModel(new QStandardItemModel(this))
DebuggerToolTipWidget::DebuggerToolTipWidget(const DebuggerToolTipContext &context)
{
setFocusPolicy(Qt::NoFocus);
m_isPinned = false;
m_context = context;
m_filterModel.m_iname = context.iname;
const QIcon pinIcon(QLatin1String(":/debugger/images/pin.xpm"));
const QList<QSize> pinIconSizes = pinIcon.availableSizes();
m_toolButton = new QToolButton;
m_toolButton->setIcon(pinIcon);
connect(m_toolButton, SIGNAL(clicked()), this, SLOT(toolButtonClicked()));
QToolButton *copyButton = new QToolButton;
auto copyButton = new QToolButton;
copyButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_COPY)));
connect(copyButton, SIGNAL(clicked()), this, SLOT(copy()));
m_titleLabel = new DraggableLabel(this);
m_titleLabel->setText(msgReleasedText());
m_titleLabel->setMinimumWidth(40); // Ensure a draggable area even if text is empty.
connect(m_titleLabel, SIGNAL(dragged(QPoint)), this, SLOT(slotDragged(QPoint)));
QToolBar *toolBar = new QToolBar(this);
auto toolBar = new QToolBar(this);
toolBar->setProperty("_q_custom_style_disabled", QVariant(true));
const QList<QSize> pinIconSizes = pinIcon.availableSizes();
if (!pinIconSizes.isEmpty())
toolBar->setIconSize(pinIconSizes.front());
toolBar->addWidget(m_toolButton);
toolBar->addWidget(m_titleLabel);
toolBar->addWidget(copyButton);
m_treeView = new DebuggerToolTipTreeView(this);
m_treeView->setFocusPolicy(Qt::NoFocus);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
auto mainLayout = new QVBoxLayout(this);
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addWidget(toolBar);
mainLayout->addWidget(m_treeView);
connect(m_toolButton, SIGNAL(clicked()), this, SLOT(toolButtonClicked()));
connect(copyButton, SIGNAL(clicked()), this, SLOT(copy()));
}
bool DebuggerToolTipWidget::matches(const QString &fileName,
const QString &engineType,
const QString &function) const
void DebuggerToolTipWidget::setWatchModel(QAbstractItemModel *watchModel)
{
if (fileName.isEmpty() || m_context.fileName != fileName)
return false;
// Optional.
if (!engineType.isEmpty() && engineType != m_engineType)
return false;
if (function.isEmpty() || m_context.function.isEmpty())
return true;
return function == m_context.function;
QTC_ASSERT(watchModel, return);
m_filterModel.setSourceModel(watchModel);
connect(watchModel, SIGNAL(itemIsExpanded(QModelIndex)),
this, SLOT(handleItemIsExpanded(QModelIndex)), Qt::UniqueConnection);
connect(watchModel, SIGNAL(columnAdjustmentRequested()),
m_treeView, SLOT(computeSize()), Qt::UniqueConnection);
}
void DebuggerToolTipWidget::acquireEngine(DebuggerEngine *engine)
void DebuggerToolTipWidget::handleStackFrameCompleted(const QString &frameFile, const QString &frameFunction)
{
QTC_ASSERT(engine, return);
if (debugToolTips)
qDebug() << this << " acquiring" << engine << m_engineAcquired;
if (m_engineAcquired)
return;
const bool sameFrame = m_context.matchesFrame(frameFile, frameFunction);
const bool isAcquired = m_treeView->model() == &m_filterModel;
if (isAcquired && !sameFrame)
releaseEngine();
else if (!isAcquired && sameFrame)
acquireEngine();
m_engineType = engine->objectName();
m_engineAcquired = true;
m_titleLabel->setText(QString());
if (isAcquired) {
m_treeView->expand(m_filterModel.index(0, 0));
WatchTreeView::reexpand(m_treeView, m_filterModel.index(0, 0));
}
}
// Create a filter model on the debugger's model and switch to it.
QAbstractItemModel *model = engine->watchModel();
TooltipFilterModel *filterModel = new TooltipFilterModel(model, m_context.iname);
swapModel(filterModel);
void DebuggerToolTipWidget::acquireEngine()
{
m_titleLabel->setText(m_context.expression);
m_treeView->setModel(&m_filterModel);
m_treeView->setRootIndex(m_filterModel.index(0, 0));
m_treeView->expand(m_filterModel.index(0, 0));
WatchTreeView::reexpand(m_treeView, m_filterModel.index(0, 0));
}
void DebuggerToolTipWidget::releaseEngine()
{
// Release engine of same type
if (!m_engineAcquired)
return;
if (debugToolTips)
qDebug() << "releaseEngine" << this;
doReleaseEngine();
// Save data to stream and restore to the backup m_defaultModel.
m_defaultModel.removeRows(0, m_defaultModel.rowCount());
TreeModelCopyVisitor v(&m_filterModel, &m_defaultModel);
v.run();
m_titleLabel->setText(msgReleasedText());
m_engineAcquired = false;
m_treeView->setModel(&m_defaultModel);
m_treeView->setRootIndex(m_defaultModel.index(0, 0));
m_treeView->expandAll();
}
void DebuggerToolTipWidget::copy()
{
const QString clipboardText = clipboardContents();
QString clipboardText = DebuggerToolTipManager::treeModelClipboardContents(m_treeView->model());
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(clipboardText, QClipboard::Selection);
clipboard->setText(clipboardText, QClipboard::Clipboard);
}
void DebuggerToolTipWidget::slotDragged(const QPoint &p)
{
move(pos() + p);
m_offset += p;
}
bool DebuggerToolTipWidget::positionShow(const DebuggerToolTipEditor &te)
void DebuggerToolTipWidget::positionShow(const DebuggerToolTipEditor &te)
{
// Figure out new position of tooltip using the text edit.
// If the line changed too much, close this tip.
QTC_ASSERT(te.isValid(), return false);
QTC_ASSERT(te.isValid(), return);
QTextCursor cursor(te.widget->document());
cursor.setPosition(m_context.position);
const int line = cursor.blockNumber();
if (qAbs(m_context.line - line) > 2) {
if (debugToolTips)
qDebug() << "Closing " << this << " in positionShow() lines "
<< line << m_context.line;
close();
return false;
return ;
}
if (debugToolTipPositioning)
qDebug() << "positionShow" << this << line << cursor.columnNumber();