2 * This file is part of KDevelop
4 * Copyright 2008 David Nolden <david.nolden.kdevelop@art-master.de>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Library General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include "contextbrowserview.h"
23 #include <QVBoxLayout>
24 #include <QHBoxLayout>
26 #include <QToolButton>
30 #include <KTextBrowser>
34 #include <language/duchain/declaration.h>
35 #include <language/duchain/topducontext.h>
36 #include <language/duchain/ducontext.h>
37 #include <language/duchain/duchain.h>
38 #include <language/duchain/duchainlock.h>
39 #include <language/duchain/indexedstring.h>
41 #include <interfaces/icore.h>
42 #include <interfaces/idocumentcontroller.h>
44 #include "contextbrowser.h"
45 #include <language/duchain/duchainutils.h>
46 #include <language/duchain/types/functiontype.h>
47 #include <language/duchain/specializationstore.h>
48 #include "browsemanager.h"
50 const int maxHistoryLength
= 30;
52 using namespace KDevelop
;
54 QToolButton
* ContextController::previousButton() const {
55 return m_previousButton
;
58 QToolButton
* ContextController::nextButton() const {
62 QToolButton
* ContextController::browseButton() const {
63 return m_browseButton
;
66 ContextController::ContextController(ContextBrowserView
* view
) : m_nextHistoryIndex(0), m_view(view
) {
67 m_browseManager
= new BrowseManager(this);
69 m_previousButton
= new QToolButton();
70 m_previousButton
->setPopupMode(QToolButton::MenuButtonPopup
);
71 m_previousButton
->setIcon(KIcon("go-previous"));
72 m_previousButton
->setEnabled(false);
73 m_previousMenu
= new QMenu();
74 m_previousButton
->setMenu(m_previousMenu
);
75 connect(m_previousButton
, SIGNAL(clicked(bool)), this, SLOT(historyPrevious()));
76 connect(m_previousMenu
, SIGNAL(aboutToShow()), this, SLOT(previousMenuAboutToShow()));
78 m_nextButton
= new QToolButton();
79 m_nextButton
->setPopupMode(QToolButton::MenuButtonPopup
);
80 m_nextButton
->setIcon(KIcon("go-next"));
81 m_nextButton
->setEnabled(false);
82 m_nextMenu
= new QMenu();
83 m_nextButton
->setMenu(m_nextMenu
);
84 connect(m_nextButton
, SIGNAL(clicked(bool)), this, SLOT(historyNext()));
85 connect(m_nextMenu
, SIGNAL(aboutToShow()), this, SLOT(nextMenuAboutToShow()));
87 m_browseButton
= new QToolButton();
88 m_browseButton
->setIcon(KIcon("games-hint"));
89 m_browseButton
->setToolTip(i18n("Enable/disable source browse mode"));
90 m_browseButton
->setWhatsThis(i18n("When this is enabled, you can browse the source-code by clicking in the editor."));
91 m_browseButton
->setCheckable(true);
92 connect(m_browseButton
, SIGNAL(clicked(bool)), m_browseManager
, SLOT(setBrowsing(bool)));
94 m_currentContextBox
= new KComboBox();
95 m_currentContextBox
->setSizeAdjustPolicy(QComboBox::AdjustToContents
);
96 connect(m_currentContextBox
, SIGNAL(activated(int)), this, SLOT(comboItemActivated(int)));
99 ContextBrowserView
* ContextController::view() const {
103 KComboBox
* ContextController::currentContextBox() const {
104 return m_currentContextBox
;
107 ContextController::~ContextController() {
109 delete m_previousMenu
;
112 void ContextController::updateButtonState()
114 m_nextButton
->setEnabled( m_nextHistoryIndex
< m_history
.size() );
115 m_previousButton
->setEnabled( m_nextHistoryIndex
>= 2 );
118 void ContextController::comboItemActivated(int index
)
120 if(index
>= 0 && index
< m_listDeclarations
.size()) {
124 KDevelop::DUChainReadLocker
lock( KDevelop::DUChain::lock() );
125 Declaration
* activated
= m_listDeclarations
[index
].data();
127 u
= activated
->url();
128 c
= activated
->range().start
;
129 if(activated
->internalContext() && activated
->internalContext()->url() == u
) {
130 c
= activated
->internalContext()->range().start
;
131 if(c
.line
+1 <= activated
->internalContext()->range().end
.line
)
132 c
= SimpleCursor(c
.line
+1, 0); //Move more into the body
137 ICore::self()->documentController()->openDocument(KUrl(u
.str()), c
.textCursor());
141 void ContextController::historyNext() {
142 if(m_nextHistoryIndex
>= m_history
.size()) {
145 m_view
->allowLockedUpdate();
146 openDocument(m_nextHistoryIndex
); // opening the document at given position
147 // will update the widget for us
148 ++m_nextHistoryIndex
;
152 void ContextController::openDocument(int historyIndex
) {
153 Q_ASSERT_X(historyIndex
>= 0, "openDocument", "negative history index");
154 Q_ASSERT_X(historyIndex
< m_history
.size(), "openDocument", "history index out of range");
155 DocumentCursor c
= m_history
[historyIndex
].computePosition();
156 if (c
.isValid() && !c
.document().str().isEmpty()) {
157 ICore::self()->documentController()->openDocument(KUrl(c
.document().str()), c
);
159 KDevelop::DUChainReadLocker
lock( KDevelop::DUChain::lock() );
160 updateDeclarationListBox(m_history
[historyIndex
].context
.data());
164 void ContextController::historyPrevious() {
165 if(m_nextHistoryIndex
< 2) {
168 --m_nextHistoryIndex
;
169 m_view
->allowLockedUpdate();
170 openDocument(m_nextHistoryIndex
-1); // opening the document at given position
171 // will update the widget for us
175 QString
ContextController::actionTextFor(int historyIndex
)
177 HistoryEntry
& entry
= m_history
[historyIndex
];
178 QString actionText
= entry
.context
.data() ? entry
.context
.data()->scopeIdentifier(true).toString() : QString();
179 if(actionText
.isEmpty())
180 actionText
= entry
.alternativeString
;
181 if(actionText
.isEmpty())
182 actionText
= "<unnamed>";
184 QString fileName
= KUrl(entry
.absoluteCursorPosition
.document().str()).fileName();
185 actionText
+= QString("%1:%2").arg(fileName
).arg(entry
.absoluteCursorPosition
.line()+1);
189 inline QDebug
operator<<(QDebug debug
, const ContextController::HistoryEntry
&he
)
191 DocumentCursor c
= he
.computePosition();
192 debug
<< "\n\tHistoryEntry " << c
.line() << " " << c
.document().str();
196 void ContextController::nextMenuAboutToShow() {
198 for(int a
= m_nextHistoryIndex
; a
< m_history
.size(); ++a
) {
201 fillHistoryPopup(m_nextMenu
, indices
);
204 void ContextController::previousMenuAboutToShow() {
206 for(int a
= m_nextHistoryIndex
-2; a
>= 0; --a
) {
209 fillHistoryPopup(m_previousMenu
, indices
);
212 void ContextController::fillHistoryPopup(QMenu
* menu
, const QList
<int>& historyIndices
) {
214 KDevelop::DUChainReadLocker
lock( KDevelop::DUChain::lock() );
215 foreach(int index
, historyIndices
) {
216 QAction
* action
= new QAction(actionTextFor(index
), menu
);
217 action
->setData(index
);
218 menu
->addAction(action
);
219 connect(action
, SIGNAL(triggered(bool)), this, SLOT(actionTriggered()));
223 void ContextController::actionTriggered() {
224 QAction
* action
= qobject_cast
<QAction
*>(sender());
225 Q_ASSERT(action
); Q_ASSERT(action
->data().type() == QVariant::Int
);
226 int historyPosition
= action
->data().toInt();
227 // kDebug() << "history pos" << historyPosition << m_history.size() << m_history;
228 if(historyPosition
>= 0 && historyPosition
< m_history
.size()) {
229 m_nextHistoryIndex
= historyPosition
+ 1;
230 m_view
->allowLockedUpdate(); // opening the document at given position
231 // will update the widget for us
232 openDocument(historyPosition
);
237 ContextController::HistoryEntry::HistoryEntry(IndexedDUContext ctx
, const KDevelop::SimpleCursor
& cursorPosition
) : context(ctx
) {
238 //Use a position relative to the context
239 setCursorPosition(cursorPosition
);
241 alternativeString
= ctx
.data()->scopeIdentifier(true).toString();;
242 if(!alternativeString
.isEmpty())
243 alternativeString
+= i18n("(changed)"); //This is used when the context was deleted in between
246 DocumentCursor
ContextController::HistoryEntry::computePosition() const {
247 KDevelop::DUChainReadLocker
lock( KDevelop::DUChain::lock() );
250 ret
= DocumentCursor(context
.data()->url().str(), relativeCursorPosition
.textCursor());
251 ret
.setLine(ret
.line() + context
.data()->range().start
.line
);
253 ret
= absoluteCursorPosition
;
258 void ContextController::HistoryEntry::setCursorPosition(const KDevelop::SimpleCursor
& cursorPosition
) {
259 KDevelop::DUChainReadLocker
lock( KDevelop::DUChain::lock() );
261 absoluteCursorPosition
= DocumentCursor(context
.data()->url().str(), cursorPosition
.textCursor());
262 relativeCursorPosition
= cursorPosition
;
263 relativeCursorPosition
.line
-= context
.data()->range().start
.line
;
267 bool ContextController::isPreviousEntry(KDevelop::DUContext
* context
, const KDevelop::SimpleCursor
& /*position*/) {
268 if (m_nextHistoryIndex
== 0) return false;
269 Q_ASSERT(m_nextHistoryIndex
<= m_history
.count());
270 HistoryEntry
& he
= m_history
[m_nextHistoryIndex
-1];
271 KDevelop::DUChainReadLocker
lock( KDevelop::DUChain::lock() ); // is this necessary??
273 return IndexedDUContext(context
) == he
.context
;
276 void ContextController::updateDeclarationListBox(DUContext
* context
) {
278 m_listUrl
= IndexedString(); ///@todo Compute the context in the document here
279 m_currentContextBox
->clear();
282 m_listUrl
= context
->url();
284 class FunctionListFilter
: public DUChainUtils::DUChainItemFilter
{
286 FunctionListFilter(KComboBox
* box
, QList
<IndexedDeclaration
>& _declarations
) : m_box(box
), declarations(_declarations
) {
288 declarations
.clear();
291 virtual bool accept(Declaration
* decl
) {
292 if(decl
->range().isEmpty())
295 if(decl
->isFunctionDeclaration() || (decl
->internalContext() && decl
->internalContext()->type() == DUContext::Class
)) {
296 Declaration
* specialDecl
= SpecializationStore::self().applySpecialization(decl
, decl
->topContext());
298 FunctionType::Ptr function
= specialDecl
->type
<FunctionType
>();
299 QString text
= specialDecl
->qualifiedIdentifier().toString();
301 text
+= function
->partToString(KDevelop::FunctionType::SignatureArguments
);
303 m_box
->addItem(text
);
304 declarations
.append(decl
);
309 virtual bool accept(DUContext
* ctx
) {
310 return ctx
->type() == DUContext::Global
|| ctx
->type() == DUContext::Namespace
|| ctx
->type() == DUContext::Class
;
313 QList
<IndexedDeclaration
>& declarations
;
316 m_currentContextBox
->clear();
317 FunctionListFilter
f(currentContextBox(), m_listDeclarations
);
318 DUChainUtils::collectItems(context
->topContext(), f
);
319 currentContextBox()->setCurrentIndex(m_listDeclarations
.indexOf(context
->owner()));
322 void ContextController::updateHistory(KDevelop::DUContext
* context
, const KDevelop::SimpleCursor
& position
)
324 if (context
== 0) return;
325 if(!context
->owner())
326 return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes
327 //This keeps the history cleaner
329 if (!isPreviousEntry(context
, position
) || context
->url() != m_listUrl
)
330 if(m_currentContextBox
->isVisible())
331 updateDeclarationListBox(context
);
333 if (isPreviousEntry(context
, position
)) {
334 if(m_nextHistoryIndex
) {
335 HistoryEntry
& he
= m_history
[m_nextHistoryIndex
-1];
336 he
.setCursorPosition(position
);
339 } else { // Append new history entry
340 //To have a more useful history, do not duplicate items in it, so remove old occurences of the same context
341 for(int a
= 0; a
< m_nextHistoryIndex
; ++a
) {
342 if(m_history
[a
].context
== IndexedDUContext(context
)) {
344 --m_nextHistoryIndex
;
348 m_history
.resize(m_nextHistoryIndex
); // discard forward history
349 m_history
.append(HistoryEntry(IndexedDUContext(context
), position
));
350 ++m_nextHistoryIndex
;
353 if(m_history
.size() > (maxHistoryLength
+ 5)) {
354 m_history
= m_history
.mid(m_history
.size() - maxHistoryLength
);
355 m_nextHistoryIndex
= m_history
.size();
360 QWidget
* ContextController::createWidget(KDevelop::DUContext
* context
) {
361 m_context
= IndexedDUContext(context
);
362 if(m_context
.data()) {
363 return m_context
.data()->createNavigationWidget();
368 DeclarationController::DeclarationController() {
371 KDevelop::IndexedDeclaration
DeclarationController::declaration() {
372 return m_declaration
;
375 QWidget
* DeclarationController::createWidget(Declaration
* decl
, TopDUContext
* topContext
) {
376 m_declaration
= IndexedDeclaration(decl
);
377 return decl
->context()->createNavigationWidget(decl
, topContext
);
381 void ContextBrowserView::resetWidget()
383 if (m_navigationWidget
) {
384 delete m_navigationWidget
;
385 m_navigationWidget
= 0;
389 void ContextBrowserView::updateLockIcon(bool checked
) {
390 m_lockButton
->setIcon(KIcon(checked
? "document-encrypt" : "document-decrypt"));
393 ContextBrowserView::ContextBrowserView( ContextBrowserPlugin
* plugin
) : m_plugin(plugin
), m_navigationWidget(new KTextBrowser()) {
394 setWindowIcon( KIcon("applications-development-web") );
396 m_declarationCtrl
= new DeclarationController();
397 m_contextCtrl
= new ContextController(this);
399 m_allowLockedUpdate
= false;
401 QHBoxLayout
* buttons
= new QHBoxLayout
;
402 m_lockButton
= new QToolButton();
403 m_lockButton
->setCheckable(true);
404 m_lockButton
->setChecked(false);
405 updateLockIcon(m_lockButton
->isChecked());
406 connect(m_lockButton
, SIGNAL(toggled(bool)), SLOT(updateLockIcon(bool)));
408 buttons
->addWidget(m_contextCtrl
->previousButton());
409 buttons
->addWidget(m_contextCtrl
->currentContextBox());
410 buttons
->addWidget(m_contextCtrl
->nextButton());
411 buttons
->addWidget(m_contextCtrl
->browseButton());
412 buttons
->addStretch();
413 buttons
->addWidget(m_lockButton
);
415 m_layout
= new QVBoxLayout
;
416 m_layout
->addLayout(buttons
);
417 m_layout
->addWidget(m_navigationWidget
);
418 //m_layout->addStretch();
421 m_plugin
->registerToolView(this);
422 connect(plugin
, SIGNAL(previousContextShortcut()), m_contextCtrl
, SLOT(historyPrevious()));
423 connect(plugin
, SIGNAL(nextContextShortcut()), m_contextCtrl
, SLOT(historyNext()));
426 ContextBrowserView::~ContextBrowserView() {
427 m_plugin
->unRegisterToolView(this);
430 bool ContextBrowserView::isLocked() const {
432 if (m_allowLockedUpdate
) {
435 isLocked
= m_lockButton
->isChecked();
440 void ContextBrowserView::updateHistory(KDevelop::DUContext
* context
, const KDevelop::SimpleCursor
& position
)
443 m_contextCtrl
->updateHistory(context
, position
);
447 void ContextBrowserView::updateMainWidget(QWidget
* widget
)
452 m_navigationWidget
= widget
;
453 m_layout
->insertWidget(1, widget
, 1);
454 m_allowLockedUpdate
= false;
458 void ContextBrowserView::setDeclaration(KDevelop::Declaration
* decl
, KDevelop::TopDUContext
* topContext
) {
459 if (!isLocked() && isVisible()) { // NO-OP if toolview is hidden, for performance reasons
460 QWidget
* w
= m_declarationCtrl
->createWidget(decl
, topContext
);
465 KDevelop::IndexedDeclaration
ContextBrowserView::lockedDeclaration() const {
466 if(m_lockButton
->isChecked())
467 return m_declarationCtrl
->declaration();
469 return KDevelop::IndexedDeclaration();
472 void ContextBrowserView::allowLockedUpdate() {
473 m_allowLockedUpdate
= true;
476 void ContextBrowserView::setContext(KDevelop::DUContext
* context
) {
477 if (!isLocked() && isVisible()) { // NO-OP if toolview is hidden, for performance reasons
478 QWidget
* w
= m_contextCtrl
->createWidget(context
);
483 void ContextBrowserView::setSpecialNavigationWidget(QWidget
* widget
) {
484 if (!isLocked() && isVisible()) {
486 updateMainWidget(widget
);
490 #include "contextbrowserview.moc"