Even though the last alpha was .91 this one is going to be 82 as I'd like to have...
[kdevelopdvcssupport.git] / plugins / contextbrowser / contextbrowserview.cpp
bloba9998468bbd983b0772a6218f686f965047fdb11
1 /*
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>
25 #include <QLabel>
26 #include <QToolButton>
27 #include <QAction>
28 #include <QMenu>
29 #include <KIcon>
30 #include <KTextBrowser>
31 #include <KLocale>
32 #include <KComboBox>
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 {
59 return m_nextButton;
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 {
100 return m_view;
103 KComboBox* ContextController::currentContextBox() const {
104 return m_currentContextBox;
107 ContextController::~ContextController() {
108 delete m_nextMenu;
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()) {
121 IndexedString u;
122 SimpleCursor c;
124 KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() );
125 Declaration* activated = m_listDeclarations[index].data();
126 if(activated) {
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
136 if(c.isValid())
137 ICore::self()->documentController()->openDocument(KUrl(u.str()), c.textCursor());
141 void ContextController::historyNext() {
142 if(m_nextHistoryIndex >= m_history.size()) {
143 return;
145 m_view->allowLockedUpdate();
146 openDocument(m_nextHistoryIndex); // opening the document at given position
147 // will update the widget for us
148 ++m_nextHistoryIndex;
149 updateButtonState();
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) {
166 return;
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
172 updateButtonState();
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>";
183 actionText += " @ ";
184 QString fileName = KUrl(entry.absoluteCursorPosition.document().str()).fileName();
185 actionText += QString("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1);
186 return actionText;
189 inline QDebug operator<<(QDebug debug, const ContextController::HistoryEntry &he)
191 DocumentCursor c = he.computePosition();
192 debug << "\n\tHistoryEntry " << c.line() << " " << c.document().str();
193 return debug;
196 void ContextController::nextMenuAboutToShow() {
197 QList<int> indices;
198 for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) {
199 indices << a;
201 fillHistoryPopup(m_nextMenu, indices);
204 void ContextController::previousMenuAboutToShow() {
205 QList<int> indices;
206 for(int a = m_nextHistoryIndex-2; a >= 0; --a) {
207 indices << a;
209 fillHistoryPopup(m_previousMenu, indices);
212 void ContextController::fillHistoryPopup(QMenu* menu, const QList<int>& historyIndices) {
213 menu->clear();
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);
233 updateButtonState();
237 ContextController::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KDevelop::SimpleCursor& cursorPosition) : context(ctx) {
238 //Use a position relative to the context
239 setCursorPosition(cursorPosition);
240 if(ctx.data())
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() );
248 DocumentCursor ret;
249 if(context.data()) {
250 ret = DocumentCursor(context.data()->url().str(), relativeCursorPosition.textCursor());
251 ret.setLine(ret.line() + context.data()->range().start.line);
252 }else{
253 ret = absoluteCursorPosition;
255 return ret;
258 void ContextController::HistoryEntry::setCursorPosition(const KDevelop::SimpleCursor& cursorPosition) {
259 KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() );
260 if(context.data()) {
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??
272 Q_ASSERT(context);
273 return IndexedDUContext(context) == he.context;
276 void ContextController::updateDeclarationListBox(DUContext* context) {
277 if(!context) {
278 m_listUrl = IndexedString(); ///@todo Compute the context in the document here
279 m_currentContextBox->clear();
280 return;
282 m_listUrl = context->url();
284 class FunctionListFilter : public DUChainUtils::DUChainItemFilter {
285 public:
286 FunctionListFilter(KComboBox* box, QList<IndexedDeclaration>& _declarations) : m_box(box), declarations(_declarations) {
287 box->clear();
288 declarations.clear();
291 virtual bool accept(Declaration* decl) {
292 if(decl->range().isEmpty())
293 return false;
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();
300 if(function)
301 text += function->partToString(KDevelop::FunctionType::SignatureArguments);
303 m_box->addItem(text);
304 declarations.append(decl);
307 return true;
309 virtual bool accept(DUContext* ctx) {
310 return ctx->type() == DUContext::Global || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Class;
312 KComboBox* m_box;
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);
338 return;
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)) {
343 m_history.remove(a);
344 --m_nextHistoryIndex;
348 m_history.resize(m_nextHistoryIndex); // discard forward history
349 m_history.append(HistoryEntry(IndexedDUContext(context), position));
350 ++m_nextHistoryIndex;
352 updateButtonState();
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();
365 return 0;
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();
419 setLayout(m_layout);
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 {
431 bool isLocked;
432 if (m_allowLockedUpdate) {
433 isLocked = false;
434 } else {
435 isLocked = m_lockButton->isChecked();
437 return isLocked;
440 void ContextBrowserView::updateHistory(KDevelop::DUContext* context, const KDevelop::SimpleCursor& position)
442 if (!isLocked()) {
443 m_contextCtrl->updateHistory(context, position);
447 void ContextBrowserView::updateMainWidget(QWidget* widget)
449 if (widget) {
450 kDebug() << "";
451 resetWidget();
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);
461 updateMainWidget(w);
465 KDevelop::IndexedDeclaration ContextBrowserView::lockedDeclaration() const {
466 if(m_lockButton->isChecked())
467 return m_declarationCtrl->declaration();
468 else
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);
479 updateMainWidget(w);
483 void ContextBrowserView::setSpecialNavigationWidget(QWidget* widget) {
484 if (!isLocked() && isVisible()) {
485 Q_ASSERT(widget);
486 updateMainWidget(widget);
490 #include "contextbrowserview.moc"