File indexing completed on 2024-04-28 04:38:22

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "contextbrowserview.h"
0008 
0009 #include <QVBoxLayout>
0010 #include <QIcon>
0011 #include <QMenu>
0012 #include <QShowEvent>
0013 #include <QTextBrowser>
0014 
0015 #include <KToggleAction>
0016 #include <KLocalizedString>
0017 #include <KTextEditor/Document>
0018 
0019 #include <language/duchain/declaration.h>
0020 #include <language/duchain/topducontext.h>
0021 #include <language/duchain/ducontext.h>
0022 #include <language/duchain/duchain.h>
0023 #include <language/duchain/duchainlock.h>
0024 
0025 #include <interfaces/icore.h>
0026 
0027 #include "contextbrowser.h"
0028 #include "debug.h"
0029 #include <language/duchain/duchainutils.h>
0030 #include <language/duchain/types/functiontype.h>
0031 #include <language/duchain/specializationstore.h>
0032 #include "browsemanager.h"
0033 #include <language/duchain/navigation/abstractnavigationwidget.h>
0034 #include <language/interfaces/codecontext.h>
0035 #include <language/duchain/navigation/abstractdeclarationnavigationcontext.h>
0036 #include <language/duchain/navigation/useswidget.h>
0037 #include <interfaces/contextmenuextension.h>
0038 #include <interfaces/iplugincontroller.h>
0039 
0040 using namespace KDevelop;
0041 
0042 namespace {
0043 enum Direction
0044 {
0045     NextUse,
0046     PreviousUse
0047 };
0048 
0049 void selectUse(ContextBrowserView* view, Direction direction)
0050 {
0051     auto abstractNaviWidget = qobject_cast<AbstractNavigationWidget*>(view->navigationWidget());
0052 
0053     if (!abstractNaviWidget) {
0054         return;
0055     }
0056 
0057     auto usesWidget = qobject_cast<UsesWidget*>(abstractNaviWidget->context()->widget());
0058     if (!usesWidget) {
0059         return;
0060     }
0061 
0062     OneUseWidget* first = nullptr, * previous = nullptr, * current = nullptr;
0063     const auto& usesWidgetItems = usesWidget->items();
0064     for (auto item : usesWidgetItems) {
0065         auto topContext = qobject_cast<TopContextUsesWidget*>(item);
0066         if (!topContext) {
0067             continue;
0068         }
0069         const auto& topContextItems = topContext->items();
0070         for (auto item : topContextItems) {
0071             auto navigationList = qobject_cast<NavigatableWidgetList*>(item);
0072             if (!navigationList) {
0073                 continue;
0074             }
0075             const auto& navigationListItems = navigationList->items();
0076             for (auto item : navigationListItems) {
0077                 auto use = qobject_cast<OneUseWidget*>(item);
0078                 if (!use) {
0079                     continue;
0080                 }
0081                 if (!first) {
0082                     first = use;
0083                 }
0084                 current = use;
0085                 if (direction == PreviousUse && current->isHighlighted() && previous) {
0086                     previous->setHighlighted(true);
0087                     previous->activateLink();
0088                     current->setHighlighted(false);
0089                     return;
0090                 }
0091                 if (direction == NextUse && previous && previous->isHighlighted()) {
0092                     current->setHighlighted(true);
0093                     current->activateLink();
0094                     previous->setHighlighted(false);
0095                     return;
0096                 }
0097                 previous = current;
0098             }
0099         }
0100     }
0101 
0102     if (direction == NextUse && first) {
0103         first->setHighlighted(true);
0104         first->activateLink();
0105         if (current && current->isHighlighted())
0106             current->setHighlighted(false);
0107         return;
0108     }
0109     if (direction == PreviousUse && current) {
0110         current->setHighlighted(true);
0111         current->activateLink();
0112         if (first && first->isHighlighted()) {
0113             first->setHighlighted(false);
0114         }
0115     }
0116 }
0117 }
0118 
0119 AbstractNavigationWidget* ContextBrowserView::createWidget(KDevelop::DUContext* context)
0120 {
0121     m_context = IndexedDUContext(context);
0122     if (m_context.data()) {
0123         return m_context.data()->createNavigationWidget(nullptr, nullptr, AbstractNavigationWidget::EmbeddableWidget);
0124     }
0125     return nullptr;
0126 }
0127 
0128 KDevelop::IndexedDeclaration ContextBrowserView::declaration() const
0129 {
0130     return m_declaration;
0131 }
0132 
0133 AbstractNavigationWidget* ContextBrowserView::createWidget(Declaration* decl, TopDUContext* topContext)
0134 {
0135     m_declaration = IndexedDeclaration(decl);
0136     return decl->context()->createNavigationWidget(decl, topContext, AbstractNavigationWidget::EmbeddableWidget);
0137 }
0138 
0139 void ContextBrowserView::resetWidget()
0140 {
0141     if (m_navigationWidget) {
0142         delete m_navigationWidget;
0143         m_navigationWidget = nullptr;
0144     }
0145 }
0146 
0147 void ContextBrowserView::declarationMenu()
0148 {
0149     DUChainReadLocker lock(DUChain::lock());
0150 
0151     auto* navigationWidget = qobject_cast<AbstractNavigationWidget*>(m_navigationWidget.data());
0152     if (navigationWidget) {
0153         auto* navigationContext = qobject_cast<AbstractDeclarationNavigationContext*>(navigationWidget->context().data());
0154         if (navigationContext && navigationContext->declaration().data()) {
0155             auto* c = new KDevelop::DeclarationContext(navigationContext->declaration().data());
0156             lock.unlock();
0157             QMenu menu(this);
0158             QList<ContextMenuExtension> extensions =
0159                 ICore::self()->pluginController()->queryPluginsForContextMenuExtensions(c, &menu);
0160 
0161             ContextMenuExtension::populateMenu(&menu, extensions);
0162             menu.exec(QCursor::pos());
0163         }
0164     }
0165 }
0166 
0167 ContextBrowserView::ContextBrowserView(ContextBrowserPlugin* plugin, QWidget* parent) : QWidget(parent)
0168     , m_plugin(plugin)
0169     , m_navigationWidget(new QTextBrowser())
0170     , m_autoLocked(false)
0171 {
0172     setWindowTitle(i18nc("@title:window", "Code Browser"));
0173     setWindowIcon(QIcon::fromTheme(QStringLiteral("code-context"), windowIcon()));
0174 
0175     m_allowLockedUpdate = false;
0176 
0177     m_declarationMenuAction = new QAction(QIcon::fromTheme(QStringLiteral("code-class")), QString(), this);
0178     m_declarationMenuAction->setToolTip(i18nc("@info:tooltip", "Show declaration menu"));
0179     // expose the declaration menu via the context menu; allows hiding the toolbar to save some space
0180     // (this will not make it behave like a submenu though)
0181     m_declarationMenuAction->setText(i18nc("@action", "Declaration Menu"));
0182     connect(m_declarationMenuAction, &QAction::triggered, this, &ContextBrowserView::declarationMenu);
0183     addAction(m_declarationMenuAction);
0184     m_lockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("object-unlocked")),
0185                                      i18nc("@action", "Lock Current View"), this);
0186     m_lockAction->setToolTip(i18nc("@info:tooltip", "Lock current view"));
0187     m_lockAction->setCheckedState(KGuiItem(i18nc("@action", "Unlock Current View"),
0188                                            QIcon::fromTheme(QStringLiteral("object-locked")),
0189                                            i18nc("@info:tooltip", "Unlock current view")));
0190     m_lockAction->setChecked(false);
0191     addAction(m_lockAction);
0192 
0193     m_layout = new QVBoxLayout;
0194     m_layout->setSpacing(0);
0195     m_layout->setContentsMargins(0, 0, 0, 0);
0196     m_layout->addWidget(m_navigationWidget);
0197     //m_layout->addStretch();
0198     setLayout(m_layout);
0199 
0200     m_plugin->registerToolView(this);
0201 }
0202 
0203 ContextBrowserView::~ContextBrowserView()
0204 {
0205     m_plugin->unRegisterToolView(this);
0206 }
0207 
0208 void ContextBrowserView::focusInEvent(QFocusEvent* event)
0209 {
0210     //Indicate that we have focus
0211     qCDebug(PLUGIN_CONTEXTBROWSER) << "got focus";
0212 //     parentWidget()->setBackgroundRole(QPalette::ToolTipBase);
0213 /*    m_layout->removeItem(m_buttons);*/
0214 
0215     QWidget::focusInEvent(event);
0216 }
0217 
0218 void ContextBrowserView::focusOutEvent(QFocusEvent* event)
0219 {
0220     qCDebug(PLUGIN_CONTEXTBROWSER) << "lost focus";
0221 //     parentWidget()->setBackgroundRole(QPalette::Background);
0222 /*    m_layout->insertLayout(0, m_buttons);
0223     for(int a = 0; a < m_buttons->count(); ++a) {
0224         QWidgetItem* item = dynamic_cast<QWidgetItem*>(m_buttons->itemAt(a));
0225     }*/
0226     QWidget::focusOutEvent(event);
0227 }
0228 
0229 bool ContextBrowserView::event(QEvent* event)
0230 {
0231     auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
0232 
0233     if (hasFocus() && keyEvent) {
0234         auto* navigationWidget = qobject_cast<AbstractNavigationWidget*>(m_navigationWidget.data());
0235         if (navigationWidget && event->type() == QEvent::KeyPress) {
0236             int key = keyEvent->key();
0237             if (key == Qt::Key_Left)
0238                 navigationWidget->previous();
0239             if (key == Qt::Key_Right)
0240                 navigationWidget->next();
0241             if (key == Qt::Key_Up)
0242                 navigationWidget->up();
0243             if (key == Qt::Key_Down)
0244                 navigationWidget->down();
0245             if (key == Qt::Key_Return || key == Qt::Key_Enter)
0246                 navigationWidget->accept();
0247 
0248             if (key == Qt::Key_L)
0249                 m_lockAction->toggle();
0250         }
0251     }
0252     return QWidget::event(event);
0253 }
0254 
0255 void ContextBrowserView::showEvent(QShowEvent* event)
0256 {
0257     DUChainReadLocker lock(DUChain::lock(), 200);
0258     if (!lock.locked()) {
0259         QWidget::showEvent(event);
0260         return;
0261     }
0262 
0263     TopDUContext* top = m_lastUsedTopContext.data();
0264     if (top && m_navigationWidgetDeclaration.isValid()) {
0265         //Update the navigation-widget
0266         Declaration* decl = m_navigationWidgetDeclaration.declaration(top);
0267         if (decl)
0268             setDeclaration(decl, top, true);
0269     }
0270     QWidget::showEvent(event);
0271 }
0272 
0273 bool ContextBrowserView::isLocked() const
0274 {
0275     bool isLocked;
0276     if (m_allowLockedUpdate) {
0277         isLocked = false;
0278     } else {
0279         isLocked = m_lockAction->isChecked();
0280     }
0281     return isLocked;
0282 }
0283 
0284 void ContextBrowserView::updateMainWidget(QWidget* widget)
0285 {
0286     if (widget) {
0287         setUpdatesEnabled(false);
0288         qCDebug(PLUGIN_CONTEXTBROWSER) << "";
0289         resetWidget();
0290         m_navigationWidget = widget;
0291         m_layout->insertWidget(1, widget, 1);
0292         m_allowLockedUpdate = false;
0293         setUpdatesEnabled(true);
0294         if (widget->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("contextChanged(bool,bool)").constData())
0295             != -1) {
0296             connect(widget, SIGNAL(contextChanged(bool,bool)), this, SLOT(navigationContextChanged(bool,bool)));
0297         }
0298     }
0299 }
0300 
0301 void ContextBrowserView::navigationContextChanged(bool wasInitial, bool isInitial)
0302 {
0303     if (wasInitial && !isInitial && !m_lockAction->isChecked()) {
0304         m_autoLocked = true;
0305         m_lockAction->setChecked(true);
0306     } else if (!wasInitial && isInitial && m_autoLocked) {
0307         m_autoLocked = false;
0308         m_lockAction->setChecked(false);
0309     } else if (isInitial) {
0310         m_autoLocked = false;
0311     }
0312 }
0313 
0314 void ContextBrowserView::selectNextItem()
0315 {
0316     selectUse(this, NextUse);
0317 }
0318 
0319 void ContextBrowserView::selectPreviousItem()
0320 {
0321     selectUse(this, PreviousUse);
0322 }
0323 
0324 void ContextBrowserView::setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force)
0325 {
0326     m_lastUsedTopContext = IndexedTopDUContext(topContext);
0327 
0328     if (isLocked() && (!m_navigationWidget.data() || !isVisible())) {
0329         // Automatically remove the locked state if the view is not visible or the widget was deleted,
0330         // because the locked state has side-effects on other navigation functionality.
0331         m_autoLocked = false;
0332         m_lockAction->setChecked(false);
0333     }
0334 
0335     if (m_navigationWidgetDeclaration == decl->id() && !force)
0336         return;
0337 
0338     m_navigationWidgetDeclaration = decl->id();
0339 
0340     if (!isLocked() && (isVisible() || force)) {  // NO-OP if tool view is hidden, for performance reasons
0341         updateMainWidget(createWidget(decl, topContext));
0342     }
0343 }
0344 
0345 KDevelop::IndexedDeclaration ContextBrowserView::lockedDeclaration() const
0346 {
0347     if (m_lockAction->isChecked())
0348         return declaration();
0349     else
0350         return KDevelop::IndexedDeclaration();
0351 }
0352 
0353 void ContextBrowserView::allowLockedUpdate()
0354 {
0355     m_allowLockedUpdate = true;
0356 }
0357 
0358 void ContextBrowserView::setNavigationWidget(QWidget* widget)
0359 {
0360     updateMainWidget(widget);
0361 }
0362 
0363 void ContextBrowserView::setContext(KDevelop::DUContext* context)
0364 {
0365     if (!context)
0366         return;
0367 
0368     m_lastUsedTopContext = IndexedTopDUContext(context->topContext());
0369 
0370     if (context->owner()) {
0371         if (context->owner()->id() == m_navigationWidgetDeclaration)
0372             return;
0373         m_navigationWidgetDeclaration = context->owner()->id();
0374     } else {
0375         m_navigationWidgetDeclaration = DeclarationId();
0376     }
0377 
0378     if (!isLocked() && isVisible()) { // NO-OP if tool view is hidden, for performance reasons
0379         updateMainWidget(createWidget(context));
0380     }
0381 }
0382 
0383 void ContextBrowserView::setSpecialNavigationWidget(QWidget* widget)
0384 {
0385     if (!isLocked() && isVisible()) {
0386         Q_ASSERT(widget);
0387         updateMainWidget(widget);
0388     } else if (widget) {
0389         widget->deleteLater();
0390     }
0391 }
0392 
0393 #include "moc_contextbrowserview.cpp"