File indexing completed on 2024-06-16 04:23:14
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "abstractnavigationwidget.h" 0008 0009 #include <QWheelEvent> 0010 #include <QApplication> 0011 #include <QVBoxLayout> 0012 #include <QMetaObject> 0013 #include <QScrollBar> 0014 #include <QTextBrowser> 0015 0016 #include <KLocalizedString> 0017 0018 #include "../duchainlock.h" 0019 0020 #include <util/widgetcolorizer.h> 0021 0022 #include <debug.h> 0023 0024 namespace { 0025 const int maxNavigationWidgetWidth = 580; 0026 const int maxNavigationWidgetHeight = 400; 0027 } 0028 0029 namespace KDevelop { 0030 class AbstractNavigationWidgetPrivate 0031 { 0032 public: 0033 AbstractNavigationWidgetPrivate(AbstractNavigationWidget* q) : q(q) {} 0034 0035 void anchorClicked(const QUrl&); 0036 0037 AbstractNavigationWidget* q; 0038 0039 NavigationContextPointer m_startContext; 0040 0041 QPointer<QTextBrowser> m_browser; 0042 QWidget* m_currentWidget = nullptr; 0043 QString m_currentText; 0044 mutable QSize m_idealTextSize; 0045 AbstractNavigationWidget::DisplayHints m_hints = AbstractNavigationWidget::NoHints; 0046 0047 NavigationContextPointer m_context; 0048 }; 0049 0050 AbstractNavigationWidget::AbstractNavigationWidget() 0051 : d_ptr(new AbstractNavigationWidgetPrivate(this)) 0052 { 0053 setPalette(QApplication::palette()); 0054 setFocusPolicy(Qt::NoFocus); 0055 resize(100, 100); 0056 } 0057 0058 QSize AbstractNavigationWidget::sizeHint() const 0059 { 0060 Q_D(const AbstractNavigationWidget); 0061 0062 if (d->m_browser) { 0063 updateIdealSize(); 0064 QSize ret = QSize(qMin(d->m_idealTextSize.width(), maxNavigationWidgetWidth), 0065 qMin(d->m_idealTextSize.height(), maxNavigationWidgetHeight)); 0066 if (d->m_idealTextSize.height() >= maxNavigationWidgetHeight) { 0067 //make space for the scrollbar in case it's not fitting 0068 ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason 0069 } 0070 0071 if (d->m_currentWidget) { 0072 ret.setHeight(ret.height() + d->m_currentWidget->sizeHint().height()); 0073 if (d->m_currentWidget->sizeHint().width() > ret.width()) 0074 ret.setWidth(d->m_currentWidget->sizeHint().width()); 0075 if (ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint 0076 ret.setWidth(500); 0077 } 0078 return ret; 0079 } else 0080 return QWidget::sizeHint(); 0081 } 0082 0083 void AbstractNavigationWidget::initBrowser(int height) 0084 { 0085 Q_D(AbstractNavigationWidget); 0086 0087 Q_ASSERT(!d->m_browser); 0088 Q_UNUSED(height); 0089 d->m_browser = new QTextBrowser; 0090 0091 d->m_browser->setOpenLinks(false); 0092 d->m_browser->setOpenExternalLinks(false); 0093 0094 auto* layout = new QVBoxLayout; 0095 layout->addWidget(d->m_browser); 0096 layout->setContentsMargins(0, 0, 0, 0); 0097 setLayout(layout); 0098 0099 connect(d->m_browser.data(), &QTextEdit::selectionChanged, this, [this]() { 0100 Q_D(AbstractNavigationWidget); 0101 d->m_browser->copy(); 0102 }); 0103 connect(d->m_browser.data(), &QTextBrowser::anchorClicked, this, [this](const QUrl& url) { 0104 Q_D(AbstractNavigationWidget); 0105 d->anchorClicked(url); 0106 }); 0107 0108 const auto childWidgets = findChildren<QWidget*>(); 0109 for (QWidget* w : childWidgets) { 0110 w->setContextMenuPolicy(Qt::NoContextMenu); 0111 } 0112 } 0113 0114 AbstractNavigationWidget::~AbstractNavigationWidget() 0115 { 0116 Q_D(AbstractNavigationWidget); 0117 0118 if (d->m_currentWidget) 0119 layout()->removeWidget(d->m_currentWidget); 0120 } 0121 0122 void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) 0123 { 0124 Q_D(AbstractNavigationWidget); 0125 0126 if (d->m_browser == nullptr) 0127 initBrowser(initBrows); 0128 0129 if (!context) { 0130 qCDebug(LANGUAGE) << "no new context created"; 0131 return; 0132 } 0133 if (context == d->m_context && (!context || context->alreadyComputed())) 0134 return; 0135 0136 if (!d->m_startContext) { 0137 d->m_startContext = context; 0138 } 0139 0140 bool wasInitial = (d->m_context == d->m_startContext); 0141 0142 d->m_context = context; 0143 update(); 0144 0145 emit contextChanged(wasInitial, d->m_context == d->m_startContext); 0146 emit sizeHintChanged(); 0147 } 0148 0149 void AbstractNavigationWidget::updateIdealSize() const 0150 { 0151 Q_D(const AbstractNavigationWidget); 0152 0153 if (d->m_context && !d->m_idealTextSize.isValid()) { 0154 QTextDocument doc; 0155 doc.setHtml(d->m_currentText); 0156 if (doc.idealWidth() > maxNavigationWidgetWidth) { 0157 doc.setTextWidth(maxNavigationWidgetWidth); 0158 d->m_idealTextSize.setWidth(maxNavigationWidgetWidth); 0159 } else { 0160 d->m_idealTextSize.setWidth(doc.idealWidth()); 0161 } 0162 d->m_idealTextSize.setHeight(doc.size().height()); 0163 } 0164 } 0165 0166 void AbstractNavigationWidget::setDisplayHints(DisplayHints hints) 0167 { 0168 Q_D(AbstractNavigationWidget); 0169 0170 d->m_hints = hints; 0171 } 0172 0173 void AbstractNavigationWidget::update() 0174 { 0175 Q_D(AbstractNavigationWidget); 0176 0177 setUpdatesEnabled(false); 0178 Q_ASSERT(d->m_context); 0179 0180 QString html; 0181 { 0182 DUChainReadLocker lock; 0183 html = d->m_context->html(); 0184 } 0185 0186 if (!html.isEmpty()) { 0187 int scrollPos = d->m_browser->verticalScrollBar()->value(); 0188 0189 if (!(d->m_hints & EmbeddableWidget)) { 0190 // TODO: Only show that the first time, or the first few times this context is shown? 0191 html += QStringLiteral("<p><small>"); 0192 if (d->m_context->linkCount() > 0) { 0193 html += 0194 i18n("(Hold <em>Alt</em> to show. Navigate via arrow keys, activate by pressing <em>Enter</em>)"); 0195 } else { 0196 html += i18n("(Hold <em>Alt</em> to show this tooltip)"); 0197 } 0198 html += QStringLiteral("</small></p>"); 0199 } 0200 0201 d->m_browser->setHtml(html); 0202 0203 WidgetColorizer::convertDocumentToDarkTheme(d->m_browser->document()); 0204 0205 d->m_currentText = html; 0206 0207 d->m_idealTextSize = QSize(); 0208 0209 QSize hint = sizeHint(); 0210 if (hint.height() >= d->m_idealTextSize.height()) { 0211 d->m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0212 } else { 0213 d->m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0214 } 0215 0216 d->m_browser->verticalScrollBar()->setValue(scrollPos); 0217 d->m_browser->scrollToAnchor(QStringLiteral("currentPosition")); 0218 d->m_browser->show(); 0219 } else { 0220 d->m_browser->hide(); 0221 } 0222 0223 if (d->m_currentWidget) { 0224 layout()->removeWidget(d->m_currentWidget); 0225 d->m_currentWidget->setParent(nullptr); 0226 } 0227 0228 d->m_currentWidget = d->m_context->widget(); 0229 0230 d->m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0231 d->m_browser->setMaximumHeight(10000); 0232 0233 if (d->m_currentWidget) { 0234 const auto signalSignature = QMetaObject::normalizedSignature( 0235 "navigateDeclaration(KDevelop::IndexedDeclaration)"); 0236 if (d->m_currentWidget->metaObject()->indexOfSignal(signalSignature.constData()) != -1) { 0237 connect(d->m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), 0238 this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); 0239 } 0240 layout()->addWidget(d->m_currentWidget); 0241 if (d->m_context->isWidgetMaximized()) { 0242 //Leave unused room to the widget 0243 d->m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); 0244 d->m_browser->setMaximumHeight(d->m_idealTextSize.height()); 0245 } 0246 } 0247 0248 setUpdatesEnabled(true); 0249 } 0250 0251 NavigationContextPointer AbstractNavigationWidget::context() const 0252 { 0253 Q_D(const AbstractNavigationWidget); 0254 0255 return d->m_context; 0256 } 0257 0258 void AbstractNavigationWidget::navigateDeclaration(const IndexedDeclaration& decl) 0259 { 0260 Q_D(AbstractNavigationWidget); 0261 0262 setContext(d->m_context->accept(decl)); 0263 } 0264 0265 void AbstractNavigationWidgetPrivate::anchorClicked(const QUrl& url) 0266 { 0267 //We may get deleted while the call to acceptLink, so make sure we don't crash in that case 0268 QPointer<AbstractNavigationWidget> thisPtr(q); 0269 NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); 0270 0271 if (thisPtr) 0272 q->setContext(nextContext); 0273 } 0274 0275 bool AbstractNavigationWidget::next() 0276 { 0277 Q_D(AbstractNavigationWidget); 0278 0279 Q_ASSERT(d->m_context); 0280 auto ret = d->m_context->nextLink(); 0281 update(); 0282 return ret; 0283 } 0284 0285 bool AbstractNavigationWidget::previous() 0286 { 0287 Q_D(AbstractNavigationWidget); 0288 0289 Q_ASSERT(d->m_context); 0290 auto ret = d->m_context->previousLink(); 0291 update(); 0292 return ret; 0293 } 0294 0295 void AbstractNavigationWidget::accept() 0296 { 0297 Q_D(AbstractNavigationWidget); 0298 0299 Q_ASSERT(d->m_context); 0300 0301 QPointer<AbstractNavigationWidget> thisPtr(this); 0302 NavigationContextPointer nextContext = d->m_context->accept(); 0303 0304 if (thisPtr) 0305 setContext(nextContext); 0306 } 0307 0308 void AbstractNavigationWidget::back() 0309 { 0310 Q_D(AbstractNavigationWidget); 0311 0312 QPointer<AbstractNavigationWidget> thisPtr(this); 0313 NavigationContextPointer nextContext = d->m_context->back(); 0314 0315 if (thisPtr) 0316 setContext(nextContext); 0317 } 0318 0319 bool AbstractNavigationWidget::up() 0320 { 0321 Q_D(AbstractNavigationWidget); 0322 0323 auto ret = d->m_context->up(); 0324 update(); 0325 return ret; 0326 } 0327 0328 bool AbstractNavigationWidget::down() 0329 { 0330 Q_D(AbstractNavigationWidget); 0331 0332 auto ret = d->m_context->down(); 0333 update(); 0334 return ret; 0335 } 0336 0337 void AbstractNavigationWidget::resetNavigationState() 0338 { 0339 Q_D(AbstractNavigationWidget); 0340 0341 d->m_context->resetNavigation(); 0342 update(); 0343 } 0344 0345 void AbstractNavigationWidget::embeddedWidgetAccept() 0346 { 0347 accept(); 0348 } 0349 void AbstractNavigationWidget::embeddedWidgetDown() 0350 { 0351 down(); 0352 } 0353 0354 void AbstractNavigationWidget::embeddedWidgetRight() 0355 { 0356 next(); 0357 } 0358 0359 void AbstractNavigationWidget::embeddedWidgetLeft() 0360 { 0361 previous(); 0362 } 0363 0364 void AbstractNavigationWidget::embeddedWidgetUp() 0365 { 0366 up(); 0367 } 0368 0369 void AbstractNavigationWidget::wheelEvent(QWheelEvent* event) 0370 { 0371 QWidget::wheelEvent(event); 0372 event->accept(); 0373 } 0374 } 0375 0376 #include "moc_abstractnavigationwidget.cpp"