Fix no newlines warnings. Patch by Peter Oberndorfer
[kdevelopdvcssupport.git] / shell / documentcontroller.cpp
blob1b93bc7bd943958ca8973856bfbc9c7ef43a6da0
1 /* This file is part of the KDE project
2 Copyright 2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
3 Copyright 2002 Bernd Gehrmann <bernd@kdevelop.org>
4 Copyright 2003 Roberto Raggi <roberto@kdevelop.org>
5 Copyright 2003-2008 Hamish Rodda <rodda@kde.org>
6 Copyright 2003 Harald Fernengel <harry@kdevelop.org>
7 Copyright 2003 Jens Dagerbo <jens.dagerbo@swipnet.se>
8 Copyright 2005 Adam Treat <treat@kde.org>
9 Copyright 2004-2007 Alexander Dymo <adymo@kdevelop.org>
10 Copyright 2007 Andreas Pakulat <apaku@gmx.de>
12 This library is free software; you can redistribute it and/or
13 modify it under the terms of the GNU Library General Public
14 License as published by the Free Software Foundation; either
15 version 2 of the License, or (at your option) any later version.
17 This library is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 Library General Public License for more details.
22 You should have received a copy of the GNU Library General Public License
23 along with this library; see the file COPYING.LIB. If not, write to
24 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 Boston, MA 02110-1301, USA.
27 #include "documentcontroller.h"
29 #include <QFileInfo>
30 #include <QtDBus/QtDBus>
31 #include <QApplication>
33 #include <kio/netaccess.h>
34 #include <kfiledialog.h>
35 #include <kactioncollection.h>
36 #include <klocale.h>
37 #include <krecentfilesaction.h>
38 #include <ktemporaryfile.h>
39 #include <kplugininfo.h>
40 #include <ktexteditor/document.h>
42 #include <sublime/area.h>
43 #include <sublime/view.h>
44 #include <interfaces/iplugincontroller.h>
46 #include "core.h"
47 #include "mainwindow.h"
48 #include "textdocument.h"
49 #include "uicontroller.h"
50 #include "partcontroller.h"
51 #include "savedialog.h"
54 namespace KDevelop
57 struct DocumentControllerPrivate {
58 DocumentControllerPrivate(DocumentController* c)
59 : controller(c)
63 ~DocumentControllerPrivate()
65 //delete temporary files so they are removed from disk
66 foreach (KTemporaryFile *temp, tempFiles)
67 delete temp;
70 QString presetEncoding;
72 // used to map urls to open docs
73 QHash< KUrl, IDocument* > documents;
75 QHash< QString, IDocumentFactory* > factories;
76 QList<KTemporaryFile*> tempFiles;
78 struct HistoryEntry
80 HistoryEntry() {}
81 HistoryEntry( const KUrl & u, const KTextEditor::Cursor& cursor );
83 KUrl url;
84 KTextEditor::Cursor cursor;
85 int id;
88 void removeDocument(Sublime::Document *doc)
90 QList<KUrl> urlsForDoc = documents.keys(dynamic_cast<KDevelop::IDocument*>(doc));
91 foreach (const KUrl &url, urlsForDoc)
93 kDebug() << "destroying document" << doc;
94 documents.remove(url);
97 void chooseDocument()
99 controller->openDocument(KUrl());
102 void changeDocumentUrl(KDevelop::IDocument* document)
104 QMutableHashIterator<KUrl, IDocument*> it = documents;
105 while (it.hasNext()) {
106 if (it.next().value() == document) {
107 if (documents.contains(document->url())) {
108 // Weird situation (saving as a file that is aready open)
109 IDocument* origDoc = documents[document->url()];
110 if (origDoc->state() & IDocument::Modified) {
111 // given that the file has been saved, close the saved file as the other instance will become conflicted on disk
112 document->close();
113 controller->activateDocument( origDoc );
114 break;
116 // Otherwise close the original document
117 origDoc->close();
118 } else {
119 // Remove the original document
120 it.remove();
123 documents.insert(document->url(), document);
124 break;
129 DocumentController* controller;
131 QList<HistoryEntry> backHistory;
132 QList<HistoryEntry> forwardHistory;
133 bool isJumping;
135 QPointer<KAction> saveAll;
136 QPointer<KAction> revertAll;
137 QPointer<KAction> close;
138 QPointer<KAction> closeAll;
139 QPointer<KAction> closeAllOthers;
140 KRecentFilesAction* fileOpenRecent;
142 /* HistoryEntry createHistoryEntry();
143 void addHistoryEntry();
144 void jumpTo( const HistoryEntry & );*/
148 DocumentController::DocumentController( QObject *parent )
149 : IDocumentController( parent )
151 d = new DocumentControllerPrivate(this);
152 QDBusConnection::sessionBus().registerObject( "/org/kdevelop/DocumentController",
153 this, QDBusConnection::ExportScriptableSlots );
155 connect(this, SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(changeDocumentUrl(KDevelop::IDocument*)));
157 if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions();
160 void KDevelop::DocumentController::initialize()
164 void DocumentController::cleanup()
166 d->fileOpenRecent->saveEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) );
168 // Close all documents without checking if they should be saved.
169 // This is because the user gets a chance to save them during MainWindow::queryClose.
170 foreach (Sublime::MainWindow* mw, Core::self()->uiControllerInternal()->mainWindows())
171 foreach (IDocument* doc, documentsInWindow(dynamic_cast<KDevelop::MainWindow*>(mw)))
172 doc->close(IDocument::Discard);
175 DocumentController::~DocumentController()
177 delete d;
180 void DocumentController::setupActions()
182 KActionCollection * ac =
183 Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection();
185 KAction *action;
187 action = ac->addAction( "file_open" );
188 action->setIcon(KIcon("document-open"));
189 action->setShortcut( Qt::CTRL + Qt::Key_O );
190 action->setText(i18n( "&Open File..." ) );
191 connect( action, SIGNAL( triggered( bool ) ), SLOT( chooseDocument() ) );
192 action->setToolTip( i18n( "Open file" ) );
193 action->setWhatsThis( i18n( "<b>Open file</b><p>Opens a file for editing.</p>" ) );
195 d->fileOpenRecent = KStandardAction::openRecent(this,
196 SLOT(slotOpenDocument(const KUrl&)), ac);
197 d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again."));
198 d->fileOpenRecent->loadEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) );
200 action = d->saveAll = ac->addAction( "file_save_all" );
201 action->setIcon(KIcon("document-save"));
202 action->setText(i18n( "Save Al&l" ) );
203 connect( action, SIGNAL( triggered( bool ) ), SLOT( slotSaveAllDocuments() ) );
204 action->setToolTip( i18n( "Save all open documents" ) );
205 action->setWhatsThis( i18n( "<b>Save all documents</b><p>Save all open documents, prompting for additional information when necessary.</p>" ) );
206 action->setEnabled(false);
208 action = d->revertAll = ac->addAction( "file_revert_all" );
209 action->setIcon(KIcon("document-revert"));
210 action->setText(i18n( "Rever&t All" ) );
211 connect( action, SIGNAL( triggered( bool ) ), SLOT( reloadAllDocuments() ) );
212 action->setToolTip( i18n( "Revert all open documents" ) );
213 action->setWhatsThis( i18n( "<b>Revert all documents</b><p>Revert all open documents, returning to the previously saved state.</p>" ) );
214 action->setEnabled(false);
216 action = d->close = ac->addAction( "file_close" );
217 action->setIcon(KIcon("window-close"));
218 action->setShortcut( Qt::CTRL + Qt::Key_W );
219 action->setText( i18n( "&Close File" ) );
220 connect( action, SIGNAL( triggered( bool ) ), SLOT( fileClose() ) );
221 action->setToolTip( i18n( "Close File" ) );
222 action->setWhatsThis( i18n( "<b>Close File</b><p>Closes current file.</p>" ) );
223 action->setEnabled(false);
225 action = d->closeAll = ac->addAction( "file_close_all" );
226 action->setIcon(KIcon("window-close"));
227 action->setText(i18n( "Clos&e All" ) );
228 connect( action, SIGNAL( triggered( bool ) ), SLOT( closeAllDocuments() ) );
229 action->setToolTip( i18n( "Close all open documents" ) );
230 action->setWhatsThis( i18n( "<b>Close all documents</b><p>Close all open documents, prompting for additional information when necessary.</p>" ) );
231 action->setEnabled(false);
233 action = d->closeAllOthers = ac->addAction( "file_closeother" );
234 action->setIcon(KIcon("window-close"));
235 action->setText(i18n( "Close All Ot&hers" ) );
236 connect( action, SIGNAL( triggered( bool ) ), SLOT( closeAllOtherDocuments() ) );
237 action->setToolTip( i18n( "Close all other documents" ) );
238 action->setWhatsThis( i18n( "<b>Close all other documents</b><p>Close all open documents, with the exception of the currently active document.</p>" ) );
239 action->setEnabled(false);
242 void DocumentController::setEncoding( const QString &encoding )
244 d->presetEncoding = encoding;
247 QString KDevelop::DocumentController::encoding() const
249 return d->presetEncoding;
252 void DocumentController::slotOpenDocument(const KUrl &url)
254 openDocument(url);
257 IDocument* DocumentController::openDocumentFromText( const QString& data )
259 KTemporaryFile *temp = new KTemporaryFile();
260 temp->setSuffix("kdevtmp");
261 temp->open();
262 temp->write(data.toUtf8());
263 temp->flush();
264 d->tempFiles << temp;
265 return openDocument(temp->fileName());
268 IDocument* DocumentController::openDocument( const KUrl & inputUrl,
269 const KTextEditor::Range& range,
270 DocumentActivationParams activationParams)
272 UiController *uiController = Core::self()->uiControllerInternal();
273 Sublime::Area *area = uiController->activeArea();
275 KUrl url = inputUrl;
277 if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) )
279 KSharedConfig * config = KGlobal::config().data();
280 KConfigGroup group = config->group( "General Options" );
281 QString dir;
282 if( group.hasKey( "DefaultProjectsDirectory" ) )
284 dir = group.readEntry( "DefaultProjectsDirectory",
285 QDir::homePath() );
286 }else if( activeDocument() )
288 dir = activeDocument()->url().directory();
289 }else
291 dir = QDir::homePath();
294 url = KFileDialog::getOpenUrl( dir, i18n( "*.*|Text File\n" ),
295 Core::self()->uiControllerInternal()->defaultMainWindow(),
296 i18n( "Open File" ) );
298 if ( url.isEmpty() )
299 //still no url
300 return 0;
302 bool emitOpened = false;
304 //get a part document
305 if (!d->documents.contains(url))
307 //make sure the URL exists
308 if ( !url.isValid() || !KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, 0 ) )
310 kDebug() << "cannot find URL:" << url.url();
311 return 0;
314 // clean it and resolve possible symlink
315 url.cleanPath( KUrl::SimplifyDirSeparators );
316 if ( url.isLocalFile() )
318 QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath();
319 if ( !path.isEmpty() )
320 url.setPath( path );
323 KMimeType::Ptr mimeType = KMimeType::findByUrl( url );
325 // is the URL pointing to a directory?
326 if ( mimeType->is( "inode/directory" ) )
328 kDebug() << "cannot open directory:" << url.url();
329 return 0;
332 // Try to find a plugin that handles this mimetype
333 QString constraint = QString("'%1' in [X-KDevelop-SupportedMimeTypes]").arg(mimeType->name());
334 KPluginInfo::List plugins = IPluginController::queryPlugins( constraint );
336 if( !plugins.isEmpty() )
338 KPluginInfo info = plugins.first();
339 kDebug() << "loading" << info.pluginName();
340 Core::self()->pluginController()->loadPlugin( info.pluginName() );
341 if( d->factories.contains( mimeType->name() ) )
343 IDocument* idoc = d->factories[mimeType->name()]->create(url, Core::self());
344 if( idoc )
346 d->documents[url] = idoc;
350 if ( !d->documents.contains(url) && Core::self()->partManagerInternal()->isTextType(mimeType))
351 d->documents[url] = new TextDocument(url, Core::self());
352 else if( !d->documents.contains(url) )
353 d->documents[url] = new PartDocument(url, Core::self());
354 emitOpened = d->documents.contains(url);
356 IDocument *doc = d->documents[url];
358 Sublime::Document *sdoc = dynamic_cast<Sublime::Document*>(doc);
359 if( !sdoc )
361 d->documents.remove(url);
362 delete doc;
363 return 0;
365 //react on document deletion - we need to cleanup controller structures
366 connect(sdoc, SIGNAL(aboutToDelete(Sublime::Document*)), this, SLOT(removeDocument(Sublime::Document*)));
368 if (!activationParams.testFlag(IDocumentController::DoNotCreateView))
370 //find a view if there's one already opened in this area
371 Sublime::View *partView = 0;
372 foreach (Sublime::View *view, sdoc->views())
374 if (area->views().contains(view))
376 partView = view;
377 break;
380 if (!partView)
382 //no view currently shown for this url
383 partView = sdoc->createView();
385 //add view to the area
386 area->addView(partView, uiController->activeSublimeWindow()->activeView());
388 if (!activationParams.testFlag(IDocumentController::DoNotActivate))
390 uiController->activeSublimeWindow()->activateView(partView);
392 d->fileOpenRecent->addUrl( url );
394 if( range.isValid() )
396 if (range.isEmpty())
397 doc->setCursorPosition( range.start() );
398 else
399 doc->setTextSelection( range );
403 // Deferred signals, wait until it's all ready first
404 if( emitOpened ) {
405 emit documentOpened( d->documents[url] );
408 if (!activationParams.testFlag(IDocumentController::DoNotActivate))
409 emit documentActivated( doc );
411 d->saveAll->setEnabled(true);
412 d->revertAll->setEnabled(true);
413 d->close->setEnabled(true);
414 d->closeAll->setEnabled(true);
415 d->closeAllOthers->setEnabled(true);
417 return doc;
420 void DocumentController::fileClose()
422 IDocument *activeDoc = activeDocument();
423 if (activeDoc)
425 UiController *uiController = Core::self()->uiControllerInternal();
426 Sublime::View *activeView = uiController->activeSublimeWindow()->activeView();
427 if (activeView->document()->views().count() > 1)
429 //close only one active view
430 Sublime::View *deletedView = uiController->activeArea()->removeView(activeView);
431 deletedView->deleteLater();
433 else
435 //close the document instead
436 activeDoc->close();
441 void DocumentController::closeDocument( const KUrl &url )
443 if( !d->documents.contains(url) )
444 return;
446 //this will remove all views and after the last view is removed, the
447 //document will be self-destructed and removeDocument() slot will catch that
448 //and clean up internal data structures
449 d->documents[url]->close();
452 void DocumentController::notifyDocumentClosed(IDocument* doc)
454 d->documents.remove(doc->url());
456 if (d->documents.isEmpty()) {
457 if (d->saveAll)
458 d->saveAll->setEnabled(false);
459 if (d->revertAll)
460 d->revertAll->setEnabled(false);
461 if (d->close)
462 d->close->setEnabled(false);
463 if (d->closeAll)
464 d->closeAll->setEnabled(false);
465 if (d->closeAllOthers)
466 d->closeAllOthers->setEnabled(false);
469 emit documentClosed(doc);
472 IDocument * DocumentController::documentForUrl( const KUrl & url ) const
474 if ( d->documents.contains( url ) )
475 return d->documents.value( url );
477 return 0;
480 QList<IDocument*> DocumentController::openDocuments() const
482 QList<IDocument*> opened;
483 foreach (IDocument *doc, d->documents.values())
485 Sublime::Document *sdoc = dynamic_cast<Sublime::Document*>(doc);
486 if( !sdoc )
488 continue;
490 if (!sdoc->views().isEmpty())
491 opened << doc;
493 return opened;
496 void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range )
498 // TODO avoid some code in openDocument?
499 openDocument(document->url(), range);
502 void DocumentController::slotSaveAllDocuments()
504 saveAllDocuments(IDocument::Silent);
507 bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode)
509 return saveSomeDocuments(openDocuments(), mode);
512 bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode)
514 if (mode & IDocument::Silent) {
515 foreach (IDocument* doc, modifiedDocuments(list)) {
516 bool ret = doc->save(mode);
517 Q_ASSERT(ret);
518 // TODO if (!ret) showErrorDialog() ?
521 } else {
522 // Ask the user which documents to save
523 QList<IDocument*> checkSave = modifiedDocuments(list);
525 if (!checkSave.isEmpty()) {
526 KSaveSelectDialog* dialog = new KSaveSelectDialog(checkSave, qApp->activeWindow());
527 if (dialog->exec() == QDialog::Rejected)
528 return false;
532 return true;
535 QList< IDocument * > KDevelop::DocumentController::documentsInWindow(MainWindow * mw) const
537 // Gather a list of all documents which do have a view in the given main window
538 QList<IDocument*> list;
539 foreach (IDocument* doc, openDocuments()) {
540 if (Sublime::Document* sdoc = dynamic_cast<Sublime::Document*>(doc)) {
541 foreach (Sublime::View* view, sdoc->views()) {
542 if (view->hasWidget() && view->widget()->window() == mw) {
543 list.append(doc);
544 break;
549 return list;
552 QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw) const
554 // Gather a list of all documents which have views only in the given main window
555 QList<IDocument*> checkSave;
556 foreach (IDocument* doc, openDocuments()) {
557 if (Sublime::Document* sdoc = dynamic_cast<Sublime::Document*>(doc)) {
558 bool inOtherWindow = false;
560 foreach (Sublime::View* view, sdoc->views()) {
561 if (view->hasWidget() && view->widget()->window() != mw) {
562 inOtherWindow = true;
563 break;
567 if (!inOtherWindow)
568 checkSave.append(doc);
571 return checkSave;
574 QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const
576 QList< IDocument * > ret;
577 foreach (IDocument* doc, list)
578 if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified)
579 ret.append(doc);
580 return ret;
583 bool DocumentController::saveAllDocumentsForWindow(MainWindow* mw, IDocument::DocumentSaveMode mode)
585 QList<IDocument*> checkSave = documentsExclusivelyInWindow(mw);
587 return saveSomeDocuments(checkSave, mode);
590 void DocumentController::reloadAllDocuments()
592 if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) {
593 QList<IDocument*> views = documentsInWindow(dynamic_cast<KDevelop::MainWindow*>(mw));
595 if (!saveSomeDocuments(views, IDocument::Default))
596 // User cancelled or other error
597 return;
599 foreach (IDocument* doc, views)
600 doc->reload();
604 void DocumentController::closeAllDocuments()
606 if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) {
607 QList<IDocument*> views = documentsInWindow(dynamic_cast<KDevelop::MainWindow*>(mw));
609 if (!saveSomeDocuments(views, IDocument::Default))
610 // User cancelled or other error
611 return;
613 foreach (IDocument* doc, views)
614 doc->close(IDocument::Discard);
618 void DocumentController::closeAllOtherDocuments()
620 if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) {
621 Sublime::View* activeView = mw->activeView();
623 if (!activeView) {
624 kWarning() << "Shouldn't there always be an active view when this function is called?";
625 return;
628 // Deal with saving unsaved solo views
629 QList<IDocument*> soloViews = documentsExclusivelyInWindow(dynamic_cast<KDevelop::MainWindow*>(mw));
630 soloViews.removeAll(dynamic_cast<IDocument*>(activeView->document()));
632 if (!saveSomeDocuments(soloViews, IDocument::Default))
633 // User cancelled or other error
634 return;
636 foreach (Sublime::View* view, mw->area()->views()) {
637 if (view != activeView) {
638 if (view->document()->views().count() > 1)
640 //close only the view in question
641 mw->area()->removeView(view);
642 view->deleteLater();
644 else
646 //close the document instead as no views remain
647 IDocument* doc = dynamic_cast<IDocument*>(view->document());
648 if (doc) {
649 doc->close(IDocument::Discard);
651 } else {
652 // Fallback, ick
653 kWarning() << "Tried to close non-IDocument sublime document";
654 mw->area()->removeView(view);
655 view->deleteLater();
663 IDocument* DocumentController::activeDocument() const
665 UiController *uiController = Core::self()->uiControllerInternal();
666 if( !uiController->activeSublimeWindow() || !uiController->activeSublimeWindow()->activeView() ) return 0;
667 return dynamic_cast<IDocument*>(uiController->activeSublimeWindow()->activeView()->document());
670 void DocumentController::registerDocumentForMimetype( const QString& mimetype,
671 KDevelop::IDocumentFactory* factory )
673 if( !d->factories.contains( mimetype ) )
674 d->factories[mimetype] = factory;
677 QStringList DocumentController::documentTypes() const
679 return QStringList() << "Text";
684 #include "documentcontroller.moc"