diff --git a/shell/documentcontroller.cpp b/shell/documentcontroller.cpp index 06189bdefb..033842c04c 100644 --- a/shell/documentcontroller.cpp +++ b/shell/documentcontroller.cpp @@ -1,1205 +1,1205 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include #include #include "workingsetcontroller.h" #include #if HAVE_KOMPARE #include "patchdocument.h" #endif #define EMPTY_DOCUMENT_URL i18n("Untitled") namespace KDevelop { struct DocumentControllerPrivate { DocumentControllerPrivate(DocumentController* c) : controller(c) , fileOpenRecent(0) , globalTextEditorInstance(0) { } ~DocumentControllerPrivate() { //delete temporary files so they are removed from disk foreach (KTemporaryFile *temp, tempFiles) delete temp; } QString presetEncoding; // used to map urls to open docs QHash< KUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; QList tempFiles; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const KUrl & u, const KTextEditor::Cursor& cursor ); KUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { QList urlsForDoc = documents.keys(dynamic_cast(doc)); foreach (const KUrl &url, urlsForDoc) { kDebug() << "destroying document" << doc; documents.remove(url); } } void chooseDocument() { KUrl dir; if( controller->activeDocument() ) { dir = KUrl( controller->activeDocument()->url() ); dir.setFileName(QString()); }else { dir = KGlobal::config()->group("Open File").readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } KEncodingFileDialog::Result res = KEncodingFileDialog::getOpenUrlsAndEncoding( controller->encoding(), dir.url(), i18n( "*|Text File\n" ), Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Open File" ) ); if( !res.URLs.isEmpty() ) { QString encoding = res.encoding; foreach( const KUrl& u, res.URLs ) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { if (documents.contains(document->url())) { // Weird situation (saving as a file that is aready open) IDocument* origDoc = documents[document->url()]; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const KUrl &url, IBuddyDocumentFinder* finder) { QList allDocs = controller->openDocuments(); foreach( KDevelop::IDocument* doc, allDocs ) { if(finder->areBuddies(url, doc->url())) { return doc; } } return 0; } IDocument* openDocumentInternal( const KUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = "", DocumentController::DocumentActivationParams activationParams = 0, IDocument* buddy = 0) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::Cursor previousActivePosition; if(previousActiveDocument && previousActiveDocument->textDocument() && previousActiveDocument->textDocument()->activeView()) previousActivePosition = previousActiveDocument->textDocument()->activeView()->cursorPosition(); QString _encoding = encoding; KUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { KUrl dir; if( controller->activeDocument() ) { dir = controller->activeDocument()->url().upUrl(); }else { dir = KGlobal::config()->group("Open File").readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } KEncodingFileDialog::Result res = KEncodingFileDialog::getOpenUrlAndEncoding( "", dir.url(), i18n( "*|Text File\n" ), Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Open File" ) ); if( !res.URLs.isEmpty() ) url = res.URLs.first(); _encoding = res.encoding; } if ( url.isEmpty() ) //still no url return 0; KGlobal::config()->group("Open File").writeEntry( "Last Open File Directory", url.upUrl() ); // clean it and resolve possible symlink url.cleanPath( KUrl::SimplifyDirSeparators ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url.setPath( path ); } //get a part document IDocument* doc=0; if (documents.contains(url)) doc=documents.value(url); else { KMimeType::Ptr mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = KMimeType::mimeType("text/plain"); } else { //make sure the URL exists if ( !url.isValid() || !KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, 0 ) ) { kDebug() << "cannot find URL:" << url.url(); return 0; } mimeType = KMimeType::findByUrl( url ); if( !url.isLocalFile() && mimeType->isDefault() ) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a syncronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = KMimeType::mimeType("text/plain"); } } // is the URL pointing to a directory? if ( mimeType->is( "inode/directory" ) ) { kDebug() << "cannot open directory:" << url.url(); return 0; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QString constraint = QString("'%1' in [X-KDevelop-SupportedMimeTypes]").arg(mimeType->name()); KPluginInfo::List plugins = IPluginController::queryPlugins( constraint ); if( !plugins.isEmpty() ) { KPluginInfo info = plugins.first(); Core::self()->pluginController()->loadPlugin( info.pluginName() ); } } if( factories.contains( mimeType->name() ) ) { doc = factories[mimeType->name()]->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { int openAsText = KMessageBox::questionYesNo(0, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType->name()), i18n("Could Not Find Editor")); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else return 0; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else return 0; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, IDocument* buddy = 0) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::Cursor previousActivePosition; if(previousActiveDocument && previousActiveDocument->textDocument() && previousActiveDocument->textDocument()->activeView()) previousActivePosition = previousActiveDocument->textDocument()->activeView()->cursorPosition(); KUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, SIGNAL(aboutToDelete(Sublime::Document*)), controller, SLOT(notifyDocumentClosed(Sublime::Document*))); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area Sublime::View *partView = 0; foreach (Sublime::View *view, sdoc->views()) { if (area->views().contains(view) && area->indexOf(view) == area->indexOf(uiController->activeSublimeWindow()->activeView())) { partView = view; break; } } bool addView = false, applyRange = true; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } KDevelop::TextView* textView = dynamic_cast(partView); if(textView && textView->textView()) { applyRange = false; if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); }else if(textView) { textView->setInitialRange(range); } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually Sublime::View* buddyView = 0; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document if(!buddy && doc->mimeType()) { QString mime = doc->mimeType()->name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { Sublime::Document* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab foreach (Sublime::View *pView, pActiveViewIndex->views()) { if(sublimeDocBuddy->views().contains(pView)) { buddyView = pView; break; } } } // if we did not find it, then search in the whole area if(!buddyView) { foreach (Sublime::View *view, sublimeDocBuddy->views()) { if (area->views().contains(view)) { buddyView = view; break; } } } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); Sublime::UrlDocument *activeDoc = 0; IBuddyDocumentFinder *buddyFinder = 0; if(activeView) activeDoc = dynamic_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = KMimeType::findByUrl(activeDoc->url())->name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, 0); Sublime::UrlDocument *activeDoc = 0, *afterActiveDoc = 0; if(activeView && afterActiveView) { activeDoc = dynamic_cast(activeView->document()); afterActiveDoc = dynamic_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no ploblem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView(partView); } if (!controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( applyRange && range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); if(doc) { KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(doc->textDocument() && doc->textDocument()->activeView()) activePosition = doc->textDocument()->activeView()->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); } return true; } DocumentController* controller; QList backHistory; QList forwardHistory; bool isJumping; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; KTextEditor::Document* globalTextEditorInstance; /* HistoryEntry createHistoryEntry(); void addHistoryEntry(); void jumpTo( const HistoryEntry & );*/ }; DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) { setObjectName("DocumentController"); d = new DocumentControllerPrivate(this); QDBusConnection::sessionBus().registerObject( "/org/kdevelop/DocumentController", this, QDBusConnection::ExportScriptableSlots ); connect(this, SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(changeDocumentUrl(KDevelop::IDocument*))); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); #if HAVE_KOMPARE registerDocumentForMimetype("text/x-patch", new PatchDocumentFactory); #endif } void KDevelop::DocumentController::initialize() { connect(ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), SLOT(slotProjectOpened(KDevelop::IProject*))); } void DocumentController::cleanup() { if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. - foreach (Sublime::MainWindow* mw, Core::self()->uiControllerInternal()->mainWindows()) - foreach (IDocument* doc, documentsInWindow(dynamic_cast(mw))) - doc->close(IDocument::Discard); + foreach (IDocument* doc, openDocuments()) + doc->close(IDocument::Discard); } DocumentController::~DocumentController() { delete d; } void DocumentController::setupActions() { KActionCollection * ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); KAction *action; action = ac->addAction( "file_open" ); action->setIcon(KIcon("document-open")); action->setShortcut( Qt::CTRL + Qt::Key_O ); action->setText(i18n( "&Open..." ) ); connect( action, SIGNAL(triggered(bool)), SLOT(chooseDocument()) ); action->setToolTip( i18n( "Open file" ) ); action->setWhatsThis( i18n( "Open file

Opens a file for editing.

" ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(KUrl)), ac); d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); action = d->saveAll = ac->addAction( "file_save_all" ); action->setIcon(KIcon("document-save")); action->setText(i18n( "Save Al&l" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveAllDocuments()) ); action->setToolTip( i18n( "Save all open documents" ) ); action->setWhatsThis( i18n( "Save all documents

Save all open documents, prompting for additional information when necessary.

" ) ); action->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( "file_revert_all" ); action->setIcon(KIcon("document-revert")); action->setText(i18n( "Reload All" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(reloadAllDocuments()) ); action->setToolTip( i18n( "Revert all open documents" ) ); action->setWhatsThis( i18n( "Revert all documents

Revert all open documents, returning to the previously saved state.

" ) ); action->setEnabled(false); action = d->close = ac->addAction( "file_close" ); action->setIcon(KIcon("window-close")); action->setShortcut( Qt::CTRL + Qt::Key_W ); action->setText( i18n( "&Close" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(fileClose()) ); action->setToolTip( i18n( "Close File" ) ); action->setWhatsThis( i18n( "Close File

Closes current file.

" ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( "file_close_all" ); action->setIcon(KIcon("window-close")); action->setText(i18n( "Clos&e All" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(closeAllDocuments()) ); action->setToolTip( i18n( "Close all open documents" ) ); action->setWhatsThis( i18n( "Close all documents

Close all open documents, prompting for additional information when necessary.

" ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( "file_closeother" ); action->setIcon(KIcon("window-close")); action->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_W ); action->setText(i18n( "Close All Ot&hers" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(closeAllOtherDocuments()) ); action->setToolTip( i18n( "Close all other documents" ) ); action->setWhatsThis( i18n( "Close all other documents

Close all open documents, with the exception of the currently active document.

" ) ); action->setEnabled(false); } void DocumentController::setEncoding( const QString &encoding ) { d->presetEncoding = encoding; } QString KDevelop::DocumentController::encoding() const { return d->presetEncoding; } void DocumentController::slotOpenDocument(const KUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url ) { return (bool)openDocument( KUrl(url) ); } IDocument* DocumentController::openDocument( const KUrl& inputUrl, const QString& prefName ) { return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const KUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { return d->openDocumentInternal( inputUrl, "", range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } void DocumentController::closeDocument( const KUrl &url ) { if( !d->documents.contains(url) ) return; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures d->documents[url]->close(); } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { IDocument* doc = dynamic_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const KUrl & dirtyUrl ) const { //Fix urls that might not be absolute KUrl url(dirtyUrl); url.cleanPath(); if ( d->documents.contains( url ) ) return d->documents.value( url ); return 0; } QList DocumentController::openDocuments() const { QList opened; foreach (IDocument *doc, d->documents) { Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { foreach (IDocument* doc, modifiedDocuments(list)) { if( !doc->save(mode) ) { if( doc ) qWarning() << "!! Could not save document:" << doc->url(); else qWarning() << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { KSaveSelectDialog dialog(checkSave, qApp->activeWindow()); if (dialog.exec() == QDialog::Rejected) return false; } } return true; } -QList< IDocument * > KDevelop::DocumentController::documentsInWindow(MainWindow * mw) const +QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window + // Does not find documents which are open in inactive areas QList list; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { foreach (Sublime::View* view, sdoc->views()) { if (view->hasWidget() && view->widget()->window() == mw) { list.append(doc); break; } } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; foreach (Sublime::View* view, sdoc->views()) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) inOtherWindow = true; } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; foreach (IDocument* doc, list) if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(dynamic_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { - QList views = documentsInWindow(dynamic_cast(mw)); + QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) doc->reload(); } } void DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { - QList views = documentsInWindow(dynamic_cast(mw)); + QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) doc->close(IDocument::Discard); } } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { kWarning() << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(dynamic_cast(mw)); soloViews.removeAll(dynamic_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; foreach (Sublime::View* view, mw->area()->views()) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() || !uiController->activeSublimeWindow()->activeView() ) return 0; return dynamic_cast(uiController->activeSublimeWindow()->activeView()->document()); } QString DocumentController::activeDocumentPath() const { IDocument* doc = activeDocument(); if(!doc) return QString(); return doc->url().pathOrUrl(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; foreach(Sublime::View* view, uiController->activeSublimeWindow()->area()->views()) documents.insert(KUrl(view->document()->documentSpecifier()).pathOrUrl()); return documents.toList(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << "Text"; } bool DocumentController::isEmptyDocumentUrl(const KUrl &url) { QRegExp r(QString("^%1(\\s\\(\\d+\\))?$").arg(EMPTY_DOCUMENT_URL)); return r.indexIn(url.prettyUrl()) != -1; } KUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; foreach (IDocument *doc, Core::self()->documentControllerInternal()->openDocuments()) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { QRegExp r(QString("^%1\\s\\((\\d+)\\)$").arg(EMPTY_DOCUMENT_URL)); if (r.indexIn(doc->url().prettyUrl()) != -1) nextEmptyDocNumber = qMax(nextEmptyDocNumber, r.cap(1).toInt()+1); else nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } KUrl url; if (nextEmptyDocNumber > 0) url = KUrl(QString("%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = KUrl(EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { return d->factories.value(mime); } KTextEditor::Document* DocumentController::globalTextEditorInstance() { if(!d->globalTextEditorInstance) d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart(); return d->globalTextEditorInstance; } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); kDebug() << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); kDebug() << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { kDebug() << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) QStringList separators = QStringList() << "/" << "-"; QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == "[") { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == "]") { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ kDebug() << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { foreach(QStringList group, groups) ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); }else{ while(index->isSplitted()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(KUrl(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << "[" << "]"; // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1]) || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == "/" ? Qt::Horizontal : Qt::Vertical; if(!index->isSplitted()) { kDebug() << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); kDebug() << "WARNING: Area is already splitted (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplitted()) { kDebug() << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplitted()) { kDebug() << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::slotProjectOpened(IProject* p) { connect(p->managerPlugin(), SIGNAL(fileRenamed(KUrl,KDevelop::ProjectFileItem*)), SLOT(slotFileRenamed(KUrl,KDevelop::ProjectFileItem*))); } void DocumentController::slotFileRenamed(const KUrl& oldname, ProjectFileItem* newitem) { IDocument* doc=documentForUrl(oldname); if(!doc) return; KTextEditor::Cursor c; KTextEditor::Document* textdoc=doc->textDocument(); if(textdoc && textdoc->activeView()) c = textdoc->activeView()->cursorPosition(); doc->close(IDocument::Default); ICore::self()->documentController()->openDocument(newitem->url(), c); } } #include "documentcontroller.moc" diff --git a/shell/documentcontroller.h b/shell/documentcontroller.h index ded4d8fdd3..e9154d11a9 100644 --- a/shell/documentcontroller.h +++ b/shell/documentcontroller.h @@ -1,180 +1,180 @@ /* This document is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the document COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEV_DOCUMENTCONTROLLER_H #define KDEV_DOCUMENTCONTROLLER_H #include #include #include "shellexport.h" namespace Sublime { class Document; class Area; class AreaIndex; } namespace KDevelop { class ProjectFileItem; class IProject; class MainWindow; /** * \short Interface to control open documents. * The document controller manages open documents in the IDE. * Open documents are usually editors, GUI designers, html documentation etc. * * Please note that this interface gives access to documents and not to their views. * It is possible that more than 1 view is shown in KDevelop for a document. */ class KDEVPLATFORMSHELL_EXPORT DocumentController: public IDocumentController { Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.DocumentController" ) public: /**Constructor. @param parent The parent object.*/ DocumentController( QObject *parent = 0 ); virtual ~DocumentController(); /**Call this before a call to @ref editDocument to set the encoding of the document to be opened. @param encoding The encoding to open as.*/ virtual void setEncoding( const QString &encoding ); virtual QString encoding() const; /**Finds the first document object corresponding to a given url. @param url The Url of the document. @return The corresponding document, or null if not found.*/ virtual IDocument* documentForUrl( const KUrl & url ) const; /**@return The list of open documents*/ virtual QList openDocuments() const; /**Refers to the document currently active or focused. @return The active document.*/ virtual IDocument* activeDocument() const; virtual void activateDocument( IDocument * document, const KTextEditor::Range& range = KTextEditor::Range::invalid() ); virtual void registerDocumentForMimetype( const QString&, KDevelop::IDocumentFactory* ); /// Request the document controller to save all documents. /// If the \a mode is not IDocument::Silent, ask the user which documents to save. /// Returns false if the user cancels the save dialog. virtual bool saveAllDocuments(IDocument::DocumentSaveMode mode); bool saveAllDocumentsForWindow(KParts::MainWindow* mw, IDocument::DocumentSaveMode mode, bool currentAreaOnly = false); void initialize(); void cleanup(); virtual QStringList documentTypes() const; QString documentType(Sublime::Document* document) const; using IDocumentController::openDocument; /**checks that url is an url of empty document*/ static bool isEmptyDocumentUrl(const KUrl &url); static KUrl nextEmptyDocumentUrl(); virtual IDocumentFactory* factory(const QString& mime) const; virtual bool openDocument(IDocument* doc, const KTextEditor::Range& range = KTextEditor::Range::invalid(), DocumentActivationParams activationParams = 0, IDocument* buddy = 0); virtual KTextEditor::Document* globalTextEditorInstance(); public Q_SLOTS: /**Opens a new or existing document. @param url The full Url of the document to open. If it is empty, a dialog to choose the document will be opened. @param range The location information, if applicable. @param activationParams Indicates whether to fully activate the document. @param buddy The buddy document @return The opened document */ virtual Q_SCRIPTABLE IDocument* openDocument( const KUrl &url, const KTextEditor::Range& range = KTextEditor::Range::invalid(), DocumentActivationParams activationParams = 0, const QString& encoding = "", IDocument* buddy = 0 ); virtual Q_SCRIPTABLE IDocument* openDocumentFromText( const QString& data ); virtual KDevelop::IDocument* openDocument( const KUrl &url, const QString& prefname ); virtual void closeDocument( const KUrl &url ); void fileClose(); void slotSaveAllDocuments(); virtual void closeAllDocuments(); void closeAllOtherDocuments(); void reloadAllDocuments(); // DBUS-compatible versions of openDocument virtual Q_SCRIPTABLE bool openDocumentSimple( QString url ); // Opens a list of documents, with optional split-view separators, like: "file1 / [ file2 - fil3 ]" (see kdevplatform_shell_environment.sh) virtual Q_SCRIPTABLE bool openDocumentsSimple( QStringList urls ); virtual Q_SCRIPTABLE bool openDocumentFromTextSimple( QString text ); // Returns the currently active document Q_SCRIPTABLE QString activeDocumentPath() const; // Returns all open documents in the current area Q_SCRIPTABLE QStringList activeDocumentPaths() const; private Q_SLOTS: virtual void slotOpenDocument(const KUrl &url); void slotProjectOpened(KDevelop::IProject* p); void slotFileRenamed(const KUrl& oldname, KDevelop::ProjectFileItem* newitem); void notifyDocumentClosed(Sublime::Document* doc); private: bool openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ); - QList documentsInWindow(MainWindow* mw) const; + QList visibleDocumentsInWindow(MainWindow* mw) const; QList documentsExclusivelyInWindow(MainWindow* mw, bool currentAreaOnly = false) const; QList modifiedDocuments(const QList& list) const; bool saveSomeDocuments(const QList& list, IDocument::DocumentSaveMode mode); void setupActions(); Q_PRIVATE_SLOT(d, void removeDocument(Sublime::Document*)) Q_PRIVATE_SLOT(d, void chooseDocument()) Q_PRIVATE_SLOT(d, void changeDocumentUrl(KDevelop::IDocument*)) friend struct DocumentControllerPrivate; struct DocumentControllerPrivate *d; }; } #endif