diff --git a/kdevplatform/debugger/framestack/framestackmodel.cpp b/kdevplatform/debugger/framestack/framestackmodel.cpp index a85e7624f8..09df8b9b72 100644 --- a/kdevplatform/debugger/framestack/framestackmodel.cpp +++ b/kdevplatform/debugger/framestack/framestackmodel.cpp @@ -1,451 +1,451 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "framestackmodel.h" #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/iprojectcontroller.h" #include "../interfaces/isession.h" #include namespace KDevelop { class FrameStackModelPrivate { public: explicit FrameStackModelPrivate(FrameStackModel* q) : q(q) {} void update(); QModelIndex indexForThreadNumber(int threadNumber); FrameStackModel* q; int m_currentThread = -1; int m_currentFrame = -1; int m_crashedThreadIndex = -1; // used to count how often a user has scrolled down and more frames needed to be fetched; // this way, the number of frames fetched in each chunk can be increased if the user wants // to scroll far int m_subsequentFrameFetchOperations = 0; bool m_updateCurrentFrameOnNextFetch = false; QVector m_threads; QHash> m_frames; QHash m_hasMoreFrames; // Caches mutable QHash m_fileExistsCache; }; FrameStackModel::FrameStackModel(IDebugSession *session) : IFrameStackModel(session) , d_ptr(new FrameStackModelPrivate(this)) { connect(session, &IDebugSession::stateChanged, this, &FrameStackModel::stateChanged); } FrameStackModel::~FrameStackModel() { } void FrameStackModel::setThreads(const QVector& threads) { Q_D(FrameStackModel); qCDebug(DEBUGGER) << threads.count(); if (!d->m_threads.isEmpty()) { beginRemoveRows(QModelIndex(), 0, d->m_threads.count()-1); d->m_threads.clear(); endRemoveRows(); } if (!threads.isEmpty()) { beginInsertRows(QModelIndex(), 0, threads.count()-1); d->m_threads = threads; endInsertRows(); } } QModelIndex FrameStackModelPrivate::indexForThreadNumber(int threadNumber) { int i=0; for (const auto& t : qAsConst(m_threads)) { if (t.nr == threadNumber) { return q->index(i, 0); } i++; } return QModelIndex(); } void FrameStackModel::setFrames(int threadNumber, const QVector& frames) { Q_D(FrameStackModel); QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); if (!d->m_frames[threadNumber].isEmpty()) { beginRemoveRows(threadIndex, 0, d->m_frames[threadNumber].count()-1); d->m_frames[threadNumber].clear(); endRemoveRows(); } if (!frames.isEmpty()) { beginInsertRows(threadIndex, 0, frames.count()-1); d->m_frames[threadNumber] = frames; endInsertRows(); } if (d->m_currentThread == threadNumber && d->m_updateCurrentFrameOnNextFetch) { d->m_currentFrame = 0; d->m_updateCurrentFrameOnNextFetch = false; } d->m_fileExistsCache.clear(); session()->raiseEvent(IDebugSession::thread_or_frame_changed); // FIXME: Ugly hack. Apparently, when rows are added, the selection // in the view is cleared. Emit this so that some frame is still // selected. emit currentFrameChanged(d->m_currentFrame); } void FrameStackModel::insertFrames(int threadNumber, const QVector& frames) { Q_D(FrameStackModel); QModelIndex threadIndex = d->indexForThreadNumber(threadNumber); Q_ASSERT(threadIndex.isValid()); beginInsertRows(threadIndex, d->m_frames[threadNumber].count()-1, d->m_frames[threadNumber].count()+frames.count()-1); d->m_frames[threadNumber] << frames; endInsertRows(); } void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames) { Q_D(FrameStackModel); d->m_hasMoreFrames[threadNumber] = hasMoreFrames; } FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index) { Q_D(FrameStackModel); Q_ASSERT(index.internalId()); Q_ASSERT(static_cast(d->m_threads.count()) >= index.internalId()); const ThreadItem &thread = d->m_threads.at(index.internalId()-1); return d->m_frames[thread.nr].at(index.row()); } QVector FrameStackModel::frames(int threadNumber) const { Q_D(const FrameStackModel); return d->m_frames.value(threadNumber); } QVariant FrameStackModel::data(const QModelIndex& index, int role) const { Q_D(const FrameStackModel); if (!index.internalId()) { //thread if (d->m_threads.count() <= index.row()) return QVariant(); const ThreadItem &thread = d->m_threads.at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { if (thread.nr == d->m_crashedThreadIndex) { return i18nc("#thread-id at function-name or address", "#%1 at %2 (crashed)", thread.nr, thread.name); } else { return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name); } - } else if (role == Qt::TextColorRole) { + } else if (role == Qt::ForegroundRole) { if (thread.nr == d->m_crashedThreadIndex) { KColorScheme scheme(QPalette::Active); return scheme.foreground(KColorScheme::NegativeText).color(); } } } } else { //frame if (static_cast(d->m_threads.count()) < index.internalId()) return QVariant(); const ThreadItem &thread = d->m_threads.at(index.internalId()-1); if (d->m_frames[thread.nr].count() <= index.row()) return QVariant(); const FrameItem &frame = d->m_frames[thread.nr].at(index.row()); if (index.column() == 0) { if (role == Qt::DisplayRole) { return QVariant(QString::number(frame.nr)); } } else if (index.column() == 1) { if (role == Qt::DisplayRole) { return QVariant(frame.name); } } else if (index.column() == 2) { if (role == Qt::DisplayRole) { QString ret = ICore::self()->projectController() ->prettyFileName(frame.file, IProjectController::FormatPlain); if (frame.line != -1) { ret += QLatin1Char(':') + QString::number(frame.line + 1); } return ret; } else if (role == Qt::DecorationRole) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(frame.file); return QIcon::fromTheme(mime.iconName()); - } else if (role == Qt::TextColorRole) { + } else if (role == Qt::ForegroundRole) { const auto fileName = frame.file.toLocalFile(); auto cacheIt = d->m_fileExistsCache.find(fileName); if (cacheIt == d->m_fileExistsCache.end()) { cacheIt = d->m_fileExistsCache.insert(fileName, QFileInfo::exists(fileName)); } const bool fileExists = cacheIt.value(); if (!fileExists) { KColorScheme scheme(QPalette::Active); return scheme.foreground(KColorScheme::InactiveText).color(); } } } } return QVariant(); } int FrameStackModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 3; } int FrameStackModel::rowCount(const QModelIndex& parent) const { Q_D(const FrameStackModel); if (!parent.isValid()) { return d->m_threads.count(); } else if (!parent.internalId() && parent.column() == 0) { if (parent.row() < d->m_threads.count()) { return d->m_frames[d->m_threads.at(parent.row()).nr].count(); } } return 0; } QModelIndex FrameStackModel::parent(const QModelIndex& child) const { if (!child.internalId()) { return QModelIndex(); } else { return index(child.internalId()-1, 0); } } QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const { Q_D(const FrameStackModel); if (parent.isValid()) { Q_ASSERT(!parent.internalId()); Q_ASSERT(parent.column() == 0); Q_ASSERT(parent.row() < d->m_threads.count()); return createIndex(row, column, parent.row()+1); } else { return createIndex(row, column); } } QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return i18n("Depth"); } else if (section == 1) { return i18n("Function"); } else if (section == 2) { return i18n("Source"); } } return QAbstractItemModel::headerData(section, orientation, role); } void FrameStackModel::setCurrentThread(int threadNumber) { Q_D(FrameStackModel); qCDebug(DEBUGGER) << threadNumber; if (d->m_currentThread != threadNumber && threadNumber != -1) { // FIXME: this logic means that if we switch to thread 3 and // then to thread 2 and then to thread 3, we'll request frames // for thread 3 again, even if the program was not run in between // and therefore frames could not have changed. d->m_currentFrame = 0; //set before fetchFrames else --frame argument would be wrong d->m_updateCurrentFrameOnNextFetch = true; fetchFrames(threadNumber, 0, 20); } if (threadNumber != d->m_currentThread) { d->m_currentFrame = 0; d->m_currentThread = threadNumber; emit currentFrameChanged(d->m_currentFrame); } qCDebug(DEBUGGER) << "currentThread: " << d->m_currentThread << "currentFrame: " << d->m_currentFrame; emit currentThreadChanged(threadNumber); session()->raiseEvent(IDebugSession::thread_or_frame_changed); } void FrameStackModel::setCurrentThread(const QModelIndex& index) { Q_D(const FrameStackModel); Q_ASSERT(index.isValid()); Q_ASSERT(!index.internalId()); Q_ASSERT(index.column() == 0); setCurrentThread(d->m_threads[index.row()].nr); } void FrameStackModel::setCrashedThreadIndex(int index) { Q_D(FrameStackModel); d->m_crashedThreadIndex = index; } int FrameStackModel::crashedThreadIndex() const { Q_D(const FrameStackModel); return d->m_crashedThreadIndex; } int FrameStackModel::currentThread() const { Q_D(const FrameStackModel); return d->m_currentThread; } QModelIndex FrameStackModel::currentThreadIndex() const { Q_D(const FrameStackModel); int i = 0; for (const ThreadItem& t : qAsConst(d->m_threads)) { if (t.nr == currentThread()) { return index(i, 0); } ++i; } return QModelIndex(); } int FrameStackModel::currentFrame() const { Q_D(const FrameStackModel); return d->m_currentFrame; } QModelIndex FrameStackModel::currentFrameIndex() const { Q_D(const FrameStackModel); return index(d->m_currentFrame, 0, currentThreadIndex()); } void FrameStackModel::setCurrentFrame(int frame) { Q_D(FrameStackModel); qCDebug(DEBUGGER) << frame; if (frame != d->m_currentFrame) { d->m_currentFrame = frame; session()->raiseEvent(IDebugSession::thread_or_frame_changed); emit currentFrameChanged(frame); } } void FrameStackModelPrivate::update() { m_subsequentFrameFetchOperations = 0; q->fetchThreads(); if (m_currentThread != -1) { q->fetchFrames(m_currentThread, 0, 20); } } void FrameStackModel::handleEvent(IDebugSession::event_t event) { Q_D(FrameStackModel); switch (event) { case IDebugSession::program_state_changed: d->update(); break; default: break; } } void FrameStackModel::stateChanged(IDebugSession::DebuggerState state) { Q_D(FrameStackModel); if (state == IDebugSession::PausedState) { setCurrentFrame(-1); d->m_updateCurrentFrameOnNextFetch = true; } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { setThreads(QVector()); } } // FIXME: it should be possible to fetch more frames for // an arbitrary thread, without making it current. void FrameStackModel::fetchMoreFrames() { Q_D(FrameStackModel); d->m_subsequentFrameFetchOperations += 1; const int fetch = 20 * d->m_subsequentFrameFetchOperations * d->m_subsequentFrameFetchOperations; if (d->m_currentThread != -1 && d->m_hasMoreFrames[d->m_currentThread]) { setHasMoreFrames(d->m_currentThread, false); fetchFrames(d->m_currentThread, d->m_frames[d->m_currentThread].count(), d->m_frames[d->m_currentThread].count()-1+fetch); } } } diff --git a/kdevplatform/debugger/variable/variablecollection.cpp b/kdevplatform/debugger/variable/variablecollection.cpp index b6ca2e73c9..cc6dbd8285 100644 --- a/kdevplatform/debugger/variable/variablecollection.cpp +++ b/kdevplatform/debugger/variable/variablecollection.cpp @@ -1,571 +1,571 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "variablecollection.h" #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include #include "util/texteditorhelpers.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent) , m_expression(expression) , m_inScope(true) , m_topLevel(true) , m_changed(false) , m_showError(false) , m_format(Natural) { // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector{expression, QString(), QString()}); else setData(QVector{display, QString(), QString()}); } QString Variable::expression() const { return m_expression; } bool Variable::inScope() const { return m_inScope; } void Variable::setValue(const QString& v) { itemData[VariableCollection::ValueColumn] = v; reportChange(); } QString Variable::value() const { return itemData[VariableCollection::ValueColumn].toString(); } void Variable::setType(const QString& type) { itemData[VariableCollection::TypeColumn] = type; reportChange(); } QString Variable::type() const { return itemData[VariableCollection::TypeColumn].toString(); } void Variable::setTopLevel(bool v) { m_topLevel = v; } void Variable::setInScope(bool v) { m_inScope = v; for (int i=0; i < childCount(); ++i) { if (auto *var = qobject_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { m_showError = v; reportChange(); } bool Variable::showError() { return m_showError; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { m_changed=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } Variable::format_t Variable::str2format(const QString& str) { if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary; if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal; if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal; if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { case Natural: return QStringLiteral("natural"); case Binary: return QStringLiteral("binary"); case Octal: return QStringLiteral("octal"); case Decimal: return QStringLiteral("decimal"); case Hexadecimal: return QStringLiteral("hexadecimal"); } return QString(); } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } bool Variable::isPotentialProblematicValue() const { const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString(); return value == QLatin1String("0x0"); } QVariant Variable::data(int column, int role) const { if (m_showError) { if (role == Qt::FontRole) { QVariant ret = TreeItem::data(column, role); QFont font = ret.value(); font.setStyle(QFont::StyleItalic); return font; } else if (column == 1 && role == Qt::DisplayRole) { return i18n("Error"); } } - if (column == 1 && role == Qt::TextColorRole) + if (column == 1 && role == Qt::ForegroundRole) { KColorScheme scheme(QPalette::Active); if (!m_inScope) { return scheme.foreground(KColorScheme::InactiveText).color(); } else if (isPotentialProblematicValue()) { return scheme.foreground(KColorScheme::NegativeText).color(); } else if (m_changed) { return scheme.foreground(KColorScheme::NeutralText).color(); } } if (role == Qt::ToolTipRole) { return TreeItem::data(column, Qt::DisplayRole); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) : TreeItem(model, parent), finishResult_(nullptr) { setData(QVector{i18n("Auto"), QString()}); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return nullptr; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, QStringLiteral("$ret")); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = nullptr; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } QVariant Watches::data(int column, int role) const { #if 0 if (column == 0 && role == Qt::FontRole) { /* FIXME: is creating font again and again efficient? */ QFont f = font(); f.setBold(true); return f; } #endif return TreeItem::data(column, role); } void Watches::reinstall() { for (int i = 0; i < childItems.size(); ++i) { auto* v = static_cast(child(i)); v->attachMaybe(); } } Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) : TreeItem(model, parent) { setData(QVector{name, QString()}); } QList Locals::updateLocals(const QStringList& locals) { QSet existing, current; for (int i = 0; i < childItems.size(); i++) { Q_ASSERT(qobject_cast(child(i))); auto* var= static_cast(child(i)); existing << var->expression(); } for (const QString& var : locals) { current << var; // If we currently don't display this local var, add it. if( !existing.contains( var ) ) { // FIXME: passing variableCollection this way is awkward. // In future, variableCollection probably should get a // method to create variable. Variable* v = currentSession()->variableController()->createVariable( ICore::self()->debugController()->variableCollection(), this, var ); appendChild( v, false ); } } for (int i = 0; i < childItems.size(); ++i) { auto* v = static_cast(child(i)); if (!current.contains(v->expression())) { removeChild(i); --i; // FIXME: check that -var-delete is sent. delete v; } } if (hasMore()) { setHasMore(false); } // Variables which changed just value are updated by a call to -var-update. // Variables that changed type -- likewise. QList ret; ret.reserve(childItems.size()); for (TreeItem* i : qAsConst(childItems)) { Q_ASSERT(qobject_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) : TreeItem(model) , m_watches(new Watches(model, this)) { appendChild(m_watches, true); } Locals* VariablesRoot::locals(const QString& name) { auto localsIt = m_locals.find(name); if (localsIt == m_locals.end()) { localsIt = m_locals.insert(name, new Locals(model(), this, name)); appendChild(*localsIt); } return *localsIt; } QHash VariablesRoot::allLocals() const { return m_locals; } void VariablesRoot::resetChanged() { m_watches->resetChanged(); for (Locals* l : qAsConst(m_locals)) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller) , m_widgetVisible(false) , m_textHintProvider(this) { m_universe = new VariablesRoot(this); setRootItem(m_universe); //new ModelTest(this); connect (ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &VariableCollection::textDocumentCreated ); connect(controller, &IDebugController::currentSessionChanged, this, &VariableCollection::updateAutoUpdate); // Qt5 signal slot syntax does not support default arguments auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); }; connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate); connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate); connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate); connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); qCDebug(DEBUGGER) << session; if (!session) return; if (!m_widgetVisible) { session->variableController()->setAutoUpdate(IVariableController::UpdateNone); } else { QFlags t = IVariableController::UpdateNone; if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; session->variableController()->setAutoUpdate(t); } } VariableCollection::~ VariableCollection() { for (auto* view : qAsConst(m_textHintProvidedViews)) { auto* iface = qobject_cast(view); iface->unregisterTextHintProvider(&m_textHintProvider); } } void VariableCollection::textDocumentCreated(IDocument* doc) { connect( doc->textDocument(), &KTextEditor::Document::viewCreated, this, &VariableCollection::viewCreated ); const auto views = doc->textDocument()->views(); for (KTextEditor::View* view : views) { viewCreated( doc->textDocument(), view ); } } void VariableCollection::viewCreated(KTextEditor::Document* doc, KTextEditor::View* view) { Q_UNUSED(doc); using namespace KTextEditor; auto* iface = qobject_cast(view); if( !iface ) return; if (m_textHintProvidedViews.contains(view)) { return; } connect(view, &View::destroyed, this, &VariableCollection::viewDestroyed); iface->registerTextHintProvider(&m_textHintProvider); m_textHintProvidedViews.append(view); } void VariableCollection::viewDestroyed(QObject* obj) { m_textHintProvidedViews.removeOne(static_cast(obj)); } Locals* VariableCollection::locals(const QString &name) const { return m_universe->locals(name.isEmpty() ? i18n("Locals") : name); } VariableProvider::VariableProvider(VariableCollection* collection) : KTextEditor::TextHintProvider() , m_collection(collection) { } QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) { if (!hasStartedSession()) return QString(); if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug")) return QString(); //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area. if (QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == Qt::AltModifier){ return QString(); } KTextEditor::Document* doc = view->document(); KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor); if (!expressionRange.isValid()) return QString(); QString expression = doc->text(expressionRange).trimmed(); // Don't do anything if there's already an open tooltip with matching range if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression) return QString(); if (expression.isEmpty()) return QString(); QPoint local = view->cursorToCoordinate(cursor); QPoint global = view->mapToGlobal(local); QWidget* w = view->childAt(local); if (!w) w = view; m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression); m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, expressionRange)); return QString(); } }